Merge "Include binderDriverInterfaceTest_IPC_32 in VTS package"
diff --git a/harnesses/tradefed/src/com/android/tradefed/testtype/VtsMultiDeviceTest.java b/harnesses/tradefed/src/com/android/tradefed/testtype/VtsMultiDeviceTest.java
index 715ae42..4d9b816 100644
--- a/harnesses/tradefed/src/com/android/tradefed/testtype/VtsMultiDeviceTest.java
+++ b/harnesses/tradefed/src/com/android/tradefed/testtype/VtsMultiDeviceTest.java
@@ -82,6 +82,7 @@
     static final String TESTMODULE = "TestModule";
     static final String TEST_PLAN_REPORT_FILE = "TEST_PLAN_REPORT_FILE";
     static final String TEST_SUITE = "test_suite";
+    static final String TEST_MAX_TIMEOUT = "test_max_timeout";
     static final String VIRTUAL_ENV_PATH = "VIRTUALENVPATH";
     static final String ABI_NAME = "abi_name";
     static final String ABI_BITNESS = "abi_bitness";
@@ -130,6 +131,7 @@
     static final String TEMPLATE_HAL_HIDL_GTEST_PATH = "vts/testcases/template/hal_hidl_gtest/hal_hidl_gtest";
     static final String TEMPLATE_HAL_HIDL_REPLAY_TEST_PATH = "vts/testcases/template/hal_hidl_replay_test/hal_hidl_replay_test";
     static final String TEMPLATE_HOST_BINARY_TEST_PATH = "vts/testcases/template/host_binary_test/host_binary_test";
+    static final long TEST_ABORT_TIMEOUT_MSECS = 1000 * 15;
     static final String TEST_RUN_SUMMARY_FILE_NAME = "test_run_summary.json";
     static final float DEFAULT_TARGET_VERSION = -1;
     static final String DEFAULT_TESTCASE_CONFIG_PATH = "vts/tools/vts-tradefed/res/default/DefaultTestCase.config";
@@ -137,8 +139,11 @@
     private ITestDevice mDevice = null;
     private IAbi mAbi = null;
 
-    @Option(name = "test-timeout", description = "maximum amount of time"
-            + "(im milliseconds) tests are allowed to run",
+    @Option(name = "test-timeout",
+            description = "The amount of time (in milliseconds) for a test invocation. "
+                    + "If the test cannot finish before timeout, it should interrupt itself and "
+                    + "clean up in " + TEST_ABORT_TIMEOUT_MSECS + "ms. Hence the actual timeout "
+                    + "is the specified value + " + TEST_ABORT_TIMEOUT_MSECS + "ms.",
             isTimeVal = true)
     private long mTestTimeout = 1000 * 60 * 60 * 3;
 
@@ -644,6 +649,9 @@
         jsonObject.put(TEST_SUITE, suite);
         CLog.i("Added %s to the Json object", TEST_SUITE);
 
+        jsonObject.put(TEST_MAX_TIMEOUT, mTestTimeout);
+        CLog.i("Added %s to the Json object: %d", TEST_MAX_TIMEOUT, mTestTimeout);
+
         if (mAbi != null) {
             jsonObject.put(ABI_NAME, mAbi.getName());
             CLog.i("Added %s to the Json object", ABI_NAME);
@@ -875,7 +883,8 @@
         cmd = ArrayUtil.buildArray(baseOpts, testModule);
 
         printToDeviceLogcatAboutTestModuleStatus("BEGIN");
-        CommandResult commandResult = mRunUtil.runTimedCmd(mTestTimeout, cmd);
+        CommandResult commandResult =
+                mRunUtil.runTimedCmd(mTestTimeout + TEST_ABORT_TIMEOUT_MSECS, cmd);
 
         if (commandResult != null) {
             CommandStatus commandStatus = commandResult.getStatus();
diff --git a/runners/host/config_parser.py b/runners/host/config_parser.py
index 5fcf85d..ad1d845 100755
--- a/runners/host/config_parser.py
+++ b/runners/host/config_parser.py
@@ -44,25 +44,6 @@
     return result
 
 
-def gen_term_signal_handler(test_runners):
-    """Generates a termination signal handler function.
-
-    Args:
-        test_runners: A list of TestRunner objects.
-
-    Returns:
-        A function to be called when termination signals are received from
-        command line. This function stops all TestRunner objects.
-    """
-
-    def termination_sig_handler(signal_num, frame):
-        for t in test_runners:
-            t.stop()
-        sys.exit(1)
-
-    return termination_sig_handler
-
-
 def load_test_config_file(test_config_path,
                           tb_filters=None,
                           baseline_config=None):
diff --git a/runners/host/keys.py b/runners/host/keys.py
index 687e5af..4782545 100644
--- a/runners/host/keys.py
+++ b/runners/host/keys.py
@@ -29,6 +29,7 @@
     KEY_TESTBED_NAME = "name"
     KEY_TEST_PATHS = "test_paths"
     KEY_TEST_SUITE = "test_suite"
+    KEY_TEST_MAX_TIMEOUT = "test_max_timeout"
 
     # Keys in test suite
     KEY_INCLUDE_FILTER = "include_filter"
diff --git a/runners/host/test_runner.py b/runners/host/test_runner.py
index b17d57a..2bd7999 100644
--- a/runners/host/test_runner.py
+++ b/runners/host/test_runner.py
@@ -25,6 +25,8 @@
 import pkgutil
 import signal
 import sys
+import thread
+import threading
 
 from vts.runners.host import base_test
 from vts.runners.host import config_parser
@@ -98,20 +100,38 @@
     test_identifiers = [(test_cls_name, None)]
 
     for config in test_configs:
+        if keys.ConfigKeys.KEY_TEST_MAX_TIMEOUT in config:
+            timeout_sec = int(config[keys.ConfigKeys.KEY_TEST_MAX_TIMEOUT]) / 1000.0
+        else:
+            timeout_sec = 60 * 60 * 3
+            logging.warning("%s unspecified. Set timeout to %s seconds.",
+                            keys.ConfigKeys.KEY_TEST_MAX_TIMEOUT, timeout_sec)
+        # The default SIGINT handler sends KeyboardInterrupt to main thread.
+        # On Windows, raising CTRL_C_EVENT, which is received as SIGINT,
+        # has no effect on non-console process. interrupt_main() works but
+        # does not unblock main thread's IO immediately.
+        timeout_func = (raiseSigint if not utils.is_on_windows() else
+                        thread.interrupt_main)
+        sig_timer = threading.Timer(timeout_sec, timeout_func)
+
         tr = TestRunner(config, test_identifiers)
         tr.parseTestConfig(config)
         try:
-            # Create console signal handler to make sure TestRunner is stopped
-            # in the event of termination.
-            handler = config_parser.gen_term_signal_handler([tr])
-            signal.signal(signal.SIGTERM, handler)
-            signal.signal(signal.SIGINT, handler)
+            sig_timer.start()
             tr.runTestClass(test_class, None)
+        except KeyboardInterrupt as e:
+            logging.exception("Aborted by timeout or ctrl+C: %s", e)
         finally:
+            sig_timer.cancel()
             tr.stop()
             return tr.results
 
 
+def raiseSigint():
+    """Raises SIGINT."""
+    os.kill(os.getpid(), signal.SIGINT)
+
+
 class TestRunner(object):
     """The class that instantiates test classes, executes test cases, and
     report results.
diff --git a/testcases/template/gtest_binary_test/gtest_binary_test.py b/testcases/template/gtest_binary_test/gtest_binary_test.py
index c919130..12bbef2 100644
--- a/testcases/template/gtest_binary_test/gtest_binary_test.py
+++ b/testcases/template/gtest_binary_test/gtest_binary_test.py
@@ -78,7 +78,7 @@
         ld_library_path = self.ld_library_path[
             tag] if tag in self.ld_library_path else None
         profiling_library_path = self.profiling_library_path[
-            tag] if tag in self.ld_library_path else None
+            tag] if tag in self.profiling_library_path else None
 
         args += " --gtest_list_tests"
         list_test_case = binary_test_case.BinaryTestCase(
diff --git a/testcases/template/hal_hidl_gtest/hal_hidl_gtest.py b/testcases/template/hal_hidl_gtest/hal_hidl_gtest.py
index 9236f42..554b919 100644
--- a/testcases/template/hal_hidl_gtest/hal_hidl_gtest.py
+++ b/testcases/template/hal_hidl_gtest/hal_hidl_gtest.py
@@ -14,13 +14,14 @@
 # limitations under the License.
 #
 
+import copy
 import logging
 
 from vts.runners.host import const
 from vts.runners.host import keys
 from vts.runners.host import test_runner
 from vts.testcases.template.gtest_binary_test import gtest_binary_test
-
+from vts.testcases.template.gtest_binary_test import gtest_test_case
 from vts.utils.python.cpu import cpu_frequency_scaling
 
 
@@ -72,6 +73,107 @@
         if passthrough_opt or self.coverage.enabled:
             self._EnablePassthroughMode()
 
+    # @Override
+    def CreateTestCase(self, path, tag=''):
+        '''Create a list of GtestTestCase objects from a binary path.
+
+        Support testing against different service names by first executing a
+        dummpy test case which lists all the registered hal services. Then
+        query the service name(s) for each registered service with lshal.
+        For each service name, create a new test case each with the service
+        name as an additional argument.
+
+        Args:
+            path: string, absolute path of a gtest binary on device
+            tag: string, a tag that will be appended to the end of test name
+
+        Returns:
+            A list of GtestTestCase objects.
+        '''
+        initial_test_cases = super(HidlHalGTest, self).CreateTestCase(path,
+                                                                      tag)
+        if not initial_test_cases:
+            return initial_test_cases
+        # first, run one test with --list_registered_services.
+        list_service_test_case = copy.copy(initial_test_cases[0])
+        list_service_test_case.args += " --list_registered_services"
+        results = self.shell.Execute(list_service_test_case.GetRunCommand())
+        if (results[const.EXIT_CODE][0]):
+            logging.error('Failed to list test cases from binary %s',
+                          list_service_test_case.path)
+        # parse the results to get the registered service list.
+        registered_services = []
+        for line in results[const.STDOUT][0].split('\n'):
+            line = str(line)
+            if line.startswith('hal_service: '):
+                service = line[len('hal_service: '):]
+                registered_services.append(service)
+
+        # If no service registered, return the initial test cases directly.
+        if not registered_services:
+            return initial_test_cases
+
+        # find the correponding service name(s) for each registered service and
+        # store the mapping in dict service_instances.
+        service_instances = {}
+        for service in registered_services:
+            cmd = '"lshal -i | grep -o %s/.* | sort -u"' % service
+            out = str(self._dut.adb.shell(cmd)).split()
+            service_names = map(lambda x: x[x.find('/') + 1:], out)
+            logging.info("registered service: %s with name: %s" %
+                         (service, ' '.join(service_names)))
+            service_instances[service] = service_names
+
+        # get all the combination of service instances.
+        service_instance_combinations = self._GetServiceInstancesCombinations(
+            registered_services, service_instances)
+
+        new_test_cases = []
+        for test_case in initial_test_cases:
+            for instance_combination in service_instance_combinations:
+                new_test_case = copy.copy(test_case)
+                for instance in instance_combination:
+                    new_test_case.args += " --hal_service_instance=" + instance
+                    new_test_case.tag = instance[instance.find(
+                        '/'):] + new_test_case.tag
+                new_test_cases.append(new_test_case)
+        return new_test_cases
+
+    @classmethod
+    def _GetServiceInstancesCombinations(self, services, service_instances):
+        '''Create all combinations of instances for all services.
+
+        Args:
+            services: list, all services used in the test. e.g. [s1, s2]
+            service_instances: dictionary, mapping of each service and the
+                               corresponding service name(s).
+                               e.g. {"s1": ["n1"], "s2": ["n2", "n3"]}
+
+        Returns:
+            A list of all service instance combinations.
+            e.g. [[s1/n1, s2/n2], [s1/n1, s2/n3]]
+        '''
+
+        service_instance_combinations = []
+        if not services:
+            return service_instance_combinations
+        service = services.pop()
+        pre_instance_combs = self._GetServiceInstancesCombinations(
+            services, service_instances)
+        if service not in service_instances:
+            return pre_instance_combs
+        for name in service_instances[service]:
+            if not pre_instance_combs:
+                new_instance_comb = [service + '/' + name]
+                service_instance_combinations.append(new_instance_comb)
+            else:
+                for instance_comb in pre_instance_combs:
+                    new_instance_comb = [service + '/' + name]
+                    new_instance_comb.extend(instance_comb)
+                    service_instance_combinations.append(new_instance_comb)
+
+        return service_instance_combinations
+
     def _EnablePassthroughMode(self):
         """Enable passthrough mode by setting getStub to true.
 
@@ -94,21 +196,20 @@
         super(HidlHalGTest, self).setUp()
 
         if (self._skip_if_thermal_throttling and
-            getattr(self, "_cpu_freq", None)):
+                getattr(self, "_cpu_freq", None)):
             self._cpu_freq.SkipIfThermalThrottling(retry_delay_secs=30)
 
     def tearDown(self):
         """Skips the test case if there is thermal throttling."""
         if (self._skip_if_thermal_throttling and
-            getattr(self, "_cpu_freq", None)):
+                getattr(self, "_cpu_freq", None)):
             self._cpu_freq.SkipIfThermalThrottling()
 
         super(HidlHalGTest, self).tearDown()
 
     def tearDownClass(self):
         """Turns off CPU frequency scaling."""
-        if (not self._skip_all_testcases and
-            getattr(self, "_cpu_freq", None)):
+        if (not self._skip_all_testcases and getattr(self, "_cpu_freq", None)):
             logging.info("Enable CPU frequency scaling")
             self._cpu_freq.EnableCpuScaling()
 
diff --git a/testcases/template/hal_hidl_gtest/hal_hidl_gtest_unittest.py b/testcases/template/hal_hidl_gtest/hal_hidl_gtest_unittest.py
new file mode 100644
index 0000000..bde230e
--- /dev/null
+++ b/testcases/template/hal_hidl_gtest/hal_hidl_gtest_unittest.py
@@ -0,0 +1,54 @@
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import unittest
+
+from vts.testcases.template.hal_hidl_gtest import hal_hidl_gtest
+
+class HidlHalGTestUnitTest(unittest.TestCase):
+    """Tests for hal hidl gtest template"""
+
+    def testGetServiceInstancesCombinations(self):
+        """Test the function to get service instance combinations"""
+
+        comb1 = hal_hidl_gtest.HidlHalGTest._GetServiceInstancesCombinations(
+            [], {})
+        self.assertEquals(0, len(comb1))
+        comb2 = hal_hidl_gtest.HidlHalGTest._GetServiceInstancesCombinations(
+            ["s1"], {})
+        self.assertEquals(0, len(comb2))
+        comb3 = hal_hidl_gtest.HidlHalGTest._GetServiceInstancesCombinations(
+            ["s1"], {"s1": ["n1"]})
+        self.assertEqual([["s1/n1"]], comb3)
+        comb4 = hal_hidl_gtest.HidlHalGTest._GetServiceInstancesCombinations(
+            ["s1"], {"s1": ["n1", "n2"]})
+        self.assertEqual([["s1/n1"], ["s1/n2"]], comb4)
+        comb5 = hal_hidl_gtest.HidlHalGTest._GetServiceInstancesCombinations(
+            ["s1", "s2"], {"s1": ["n1", "n2"]})
+        self.assertEqual([["s1/n1"], ["s1/n2"]], comb5)
+        comb6 = hal_hidl_gtest.HidlHalGTest._GetServiceInstancesCombinations(
+            ["s1", "s2"], {"s1": ["n1", "n2"],
+                           "s2": ["n3"]})
+        self.assertEqual([["s2/n3", "s1/n1"], ["s2/n3", "s1/n2"]], comb6)
+        comb7 = hal_hidl_gtest.HidlHalGTest._GetServiceInstancesCombinations(
+            ["s1", "s2"], {"s1": ["n1", "n2"],
+                           "s2": ["n3", "n4"]})
+        self.assertEqual([["s2/n3", "s1/n1"], ["s2/n3", "s1/n2"],
+                          ["s2/n4", "s1/n1"], ["s2/n4", "s1/n2"]], comb7)
+
+
+if __name__ == '__main__':
+    unittest.main()