Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Runs the Java tests. See more information on run_instrumentation_tests.py.""" | 5 """Runs the Java tests. See more information on run_instrumentation_tests.py.""" |
| 6 | 6 |
| 7 import logging | 7 import logging |
| 8 import os | 8 import os |
| 9 import re | 9 import re |
| 10 import shutil | 10 import shutil |
| 11 import sys | 11 import sys |
| 12 import threading | |
|
frankf
2013/03/01 20:32:07
where's this used?
craigdh
2013/03/01 21:45:11
Done.
| |
| 12 import time | 13 import time |
| 13 | 14 |
| 14 from pylib import android_commands | 15 from pylib import android_commands |
| 15 from pylib import cmd_helper | 16 from pylib import cmd_helper |
| 16 from pylib import constants | 17 from pylib import constants |
| 17 from pylib import forwarder | 18 from pylib import forwarder |
| 18 from pylib import json_perf_parser | 19 from pylib import json_perf_parser |
| 19 from pylib import perf_tests_helper | 20 from pylib import perf_tests_helper |
| 20 from pylib import valgrind_tools | 21 from pylib import valgrind_tools |
| 21 from pylib.base import base_test_runner | 22 from pylib.base import base_test_runner |
| 22 from pylib.base import base_test_sharder | |
| 23 from pylib.base import test_result | 23 from pylib.base import test_result |
| 24 | 24 |
| 25 import apk_info | 25 import apk_info |
| 26 | 26 |
| 27 | 27 |
| 28 _PERF_TEST_ANNOTATION = 'PerfTest' | 28 _PERF_TEST_ANNOTATION = 'PerfTest' |
| 29 | 29 |
| 30 | 30 |
| 31 class TestRunner(base_test_runner.BaseTestRunner): | 31 class TestRunner(base_test_runner.BaseTestRunner): |
| 32 """Responsible for running a series of tests connected to a single device.""" | 32 """Responsible for running a series of tests connected to a single device.""" |
| 33 | 33 |
| 34 _DEVICE_DATA_DIR = 'chrome/test/data' | 34 _DEVICE_DATA_DIR = 'chrome/test/data' |
| 35 _EMMA_JAR = os.path.join(os.environ.get('ANDROID_BUILD_TOP', ''), | 35 _EMMA_JAR = os.path.join(os.environ.get('ANDROID_BUILD_TOP', ''), |
| 36 'external/emma/lib/emma.jar') | 36 'external/emma/lib/emma.jar') |
| 37 _COVERAGE_MERGED_FILENAME = 'unittest_coverage.es' | 37 _COVERAGE_MERGED_FILENAME = 'unittest_coverage.es' |
| 38 _COVERAGE_WEB_ROOT_DIR = os.environ.get('EMMA_WEB_ROOTDIR') | 38 _COVERAGE_WEB_ROOT_DIR = os.environ.get('EMMA_WEB_ROOTDIR') |
| 39 _COVERAGE_FILENAME = 'coverage.ec' | 39 _COVERAGE_FILENAME = 'coverage.ec' |
| 40 _COVERAGE_RESULT_PATH = ('/data/data/com.google.android.apps.chrome/files/' + | 40 _COVERAGE_RESULT_PATH = ('/data/data/com.google.android.apps.chrome/files/' + |
| 41 _COVERAGE_FILENAME) | 41 _COVERAGE_FILENAME) |
| 42 _COVERAGE_META_INFO_PATH = os.path.join(os.environ.get('ANDROID_BUILD_TOP', | 42 _COVERAGE_META_INFO_PATH = os.path.join(os.environ.get('ANDROID_BUILD_TOP', |
| 43 ''), | 43 ''), |
| 44 'out/target/common/obj/APPS', | 44 'out/target/common/obj/APPS', |
| 45 'Chrome_intermediates/coverage.em') | 45 'Chrome_intermediates/coverage.em') |
| 46 _HOSTMACHINE_PERF_OUTPUT_FILE = '/tmp/chrome-profile' | 46 _HOSTMACHINE_PERF_OUTPUT_FILE = '/tmp/chrome-profile' |
| 47 _DEVICE_PERF_OUTPUT_SEARCH_PREFIX = (constants.DEVICE_PERF_OUTPUT_DIR + | 47 _DEVICE_PERF_OUTPUT_SEARCH_PREFIX = (constants.DEVICE_PERF_OUTPUT_DIR + |
| 48 '/chrome-profile*') | 48 '/chrome-profile*') |
| 49 _DEVICE_HAS_TEST_FILES = {} | 49 _DEVICE_HAS_TEST_FILES = {} |
| 50 | 50 |
| 51 def __init__(self, options, device, tests_iter, coverage, shard_index, apks, | 51 def __init__(self, options, device, shard_index, coverage, apks, |
| 52 ports_to_forward): | 52 ports_to_forward): |
| 53 """Create a new TestRunner. | 53 """Create a new TestRunner. |
| 54 | 54 |
| 55 Args: | 55 Args: |
| 56 options: An options object with the following required attributes: | 56 options: An options object with the following required attributes: |
| 57 - build_type: 'Release' or 'Debug'. | 57 - build_type: 'Release' or 'Debug'. |
| 58 - install_apk: Re-installs the apk if opted. | 58 - install_apk: Re-installs the apk if opted. |
| 59 - save_perf_json: Whether or not to save the JSON file from UI perf | 59 - save_perf_json: Whether or not to save the JSON file from UI perf |
| 60 tests. | 60 tests. |
| 61 - screenshot_failures: Take a screenshot for a test failure | 61 - screenshot_failures: Take a screenshot for a test failure |
| 62 - tool: Name of the Valgrind tool. | 62 - tool: Name of the Valgrind tool. |
| 63 - wait_for_debugger: blocks until the debugger is connected. | 63 - wait_for_debugger: blocks until the debugger is connected. |
| 64 - disable_assertions: Whether to disable java assertions on the device. | 64 - disable_assertions: Whether to disable java assertions on the device. |
| 65 device: Attached android device. | 65 device: Attached android device. |
| 66 tests_iter: A list of tests to be run. | 66 shard_index: Shard index. |
| 67 coverage: Collects coverage information if opted. | 67 coverage: Collects coverage information if opted. |
| 68 shard_index: shard # for this TestRunner, used to create unique port | |
| 69 numbers. | |
| 70 apks: A list of ApkInfo objects need to be installed. The first element | 68 apks: A list of ApkInfo objects need to be installed. The first element |
| 71 should be the tests apk, the rests could be the apks used in test. | 69 should be the tests apk, the rests could be the apks used in test. |
| 72 The default is ChromeTest.apk. | 70 The default is ChromeTest.apk. |
| 73 ports_to_forward: A list of port numbers for which to set up forwarders. | 71 ports_to_forward: A list of port numbers for which to set up forwarders. |
| 74 Can be optionally requested by a test case. | 72 Can be optionally requested by a test case. |
| 75 Raises: | 73 Raises: |
| 76 Exception: if coverage metadata is not available. | 74 Exception: if coverage metadata is not available. |
| 77 """ | 75 """ |
| 78 super(TestRunner, self).__init__( | 76 super(TestRunner, self).__init__(device, options.tool, options.build_type) |
| 79 device, options.tool, shard_index, options.build_type) | 77 self._lighttp_port = constants.LIGHTTPD_RANDOM_PORT_FIRST + shard_index |
| 80 | 78 |
| 81 if not apks: | 79 if not apks: |
| 82 apks = [apk_info.ApkInfo(options.test_apk_path, | 80 apks = [apk_info.ApkInfo(options.test_apk_path, |
| 83 options.test_apk_jar_path)] | 81 options.test_apk_jar_path)] |
| 84 | 82 |
| 85 self.build_type = options.build_type | 83 self.build_type = options.build_type |
| 86 self.install_apk = options.install_apk | 84 self.install_apk = options.install_apk |
| 87 self.test_data = options.test_data | 85 self.test_data = options.test_data |
| 88 self.save_perf_json = options.save_perf_json | 86 self.save_perf_json = options.save_perf_json |
| 89 self.screenshot_failures = options.screenshot_failures | 87 self.screenshot_failures = options.screenshot_failures |
| 90 self.wait_for_debugger = options.wait_for_debugger | 88 self.wait_for_debugger = options.wait_for_debugger |
| 91 self.disable_assertions = options.disable_assertions | 89 self.disable_assertions = options.disable_assertions |
| 92 | 90 |
| 93 self.tests_iter = tests_iter | |
| 94 self.coverage = coverage | 91 self.coverage = coverage |
| 95 self.apks = apks | 92 self.apks = apks |
| 96 self.test_apk = apks[0] | 93 self.test_apk = apks[0] |
| 97 self.instrumentation_class_path = self.test_apk.GetPackageName() | 94 self.instrumentation_class_path = self.test_apk.GetPackageName() |
| 98 self.ports_to_forward = ports_to_forward | 95 self.ports_to_forward = ports_to_forward |
| 99 | 96 |
| 100 self.test_results = test_result.TestResults() | |
| 101 self.forwarder = None | 97 self.forwarder = None |
| 102 | 98 |
| 103 if self.coverage: | 99 if self.coverage: |
| 104 if os.path.exists(TestRunner._COVERAGE_MERGED_FILENAME): | 100 if os.path.exists(TestRunner._COVERAGE_MERGED_FILENAME): |
| 105 os.remove(TestRunner._COVERAGE_MERGED_FILENAME) | 101 os.remove(TestRunner._COVERAGE_MERGED_FILENAME) |
| 106 if not os.path.exists(TestRunner._COVERAGE_META_INFO_PATH): | 102 if not os.path.exists(TestRunner._COVERAGE_META_INFO_PATH): |
| 107 raise Exception('FATAL ERROR in ' + sys.argv[0] + | 103 raise Exception('FATAL ERROR in ' + sys.argv[0] + |
| 108 ' : Coverage meta info [' + | 104 ' : Coverage meta info [' + |
| 109 TestRunner._COVERAGE_META_INFO_PATH + | 105 TestRunner._COVERAGE_META_INFO_PATH + |
| 110 '] does not exist.') | 106 '] does not exist.') |
| 111 if (not TestRunner._COVERAGE_WEB_ROOT_DIR or | 107 if (not TestRunner._COVERAGE_WEB_ROOT_DIR or |
| 112 not os.path.exists(TestRunner._COVERAGE_WEB_ROOT_DIR)): | 108 not os.path.exists(TestRunner._COVERAGE_WEB_ROOT_DIR)): |
| 113 raise Exception('FATAL ERROR in ' + sys.argv[0] + | 109 raise Exception('FATAL ERROR in ' + sys.argv[0] + |
| 114 ' : Path specified in $EMMA_WEB_ROOTDIR [' + | 110 ' : Path specified in $EMMA_WEB_ROOTDIR [' + |
| 115 TestRunner._COVERAGE_WEB_ROOT_DIR + | 111 TestRunner._COVERAGE_WEB_ROOT_DIR + |
| 116 '] does not exist.') | 112 '] does not exist.') |
| 117 | 113 |
| 118 def _GetTestsIter(self): | |
| 119 if not self.tests_iter: | |
| 120 # multiprocessing.Queue can't be pickled across processes if we have it as | |
| 121 # a member set during constructor. Grab one here instead. | |
| 122 self.tests_iter = (base_test_sharder.BaseTestSharder.tests_container) | |
| 123 assert self.tests_iter | |
| 124 return self.tests_iter | |
| 125 | |
| 126 def CopyTestFilesOnce(self): | 114 def CopyTestFilesOnce(self): |
| 127 """Pushes the test data files to the device. Installs the apk if opted.""" | 115 """Pushes the test data files to the device. Installs the apk if opted.""" |
| 128 if TestRunner._DEVICE_HAS_TEST_FILES.get(self.device, False): | 116 if TestRunner._DEVICE_HAS_TEST_FILES.get(self.device, False): |
| 129 logging.warning('Already copied test files to device %s, skipping.', | 117 logging.warning('Already copied test files to device %s, skipping.', |
| 130 self.device) | 118 self.device) |
| 131 return | 119 return |
| 132 for dest_host_pair in self.test_data: | 120 for dest_host_pair in self.test_data: |
| 133 dst_src = dest_host_pair.split(':',1) | 121 dst_src = dest_host_pair.split(':',1) |
| 134 dst_layer = dst_src[0] | 122 dst_layer = dst_src[0] |
| 135 host_src = dst_src[1] | 123 host_src = dst_src[1] |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 206 ret['debug'] = 'true' | 194 ret['debug'] = 'true' |
| 207 return ret | 195 return ret |
| 208 | 196 |
| 209 def _TakeScreenshot(self, test): | 197 def _TakeScreenshot(self, test): |
| 210 """Takes a screenshot from the device.""" | 198 """Takes a screenshot from the device.""" |
| 211 screenshot_name = os.path.join(constants.SCREENSHOTS_DIR, test + '.png') | 199 screenshot_name = os.path.join(constants.SCREENSHOTS_DIR, test + '.png') |
| 212 logging.info('Taking screenshot named %s', screenshot_name) | 200 logging.info('Taking screenshot named %s', screenshot_name) |
| 213 self.adb.TakeScreenshot(screenshot_name) | 201 self.adb.TakeScreenshot(screenshot_name) |
| 214 | 202 |
| 215 def SetUp(self): | 203 def SetUp(self): |
| 204 super(TestRunner, self).SetUp() | |
|
frankf
2013/03/01 20:32:07
why is this before the docstring?
craigdh
2013/03/01 21:45:11
No idea...
| |
| 216 """Sets up the test harness and device before all tests are run.""" | 205 """Sets up the test harness and device before all tests are run.""" |
| 217 super(TestRunner, self).SetUp() | |
| 218 if not self.adb.IsRootEnabled(): | 206 if not self.adb.IsRootEnabled(): |
| 219 logging.warning('Unable to enable java asserts for %s, non rooted device', | 207 logging.warning('Unable to enable java asserts for %s, non rooted device', |
| 220 self.device) | 208 self.device) |
| 221 else: | 209 else: |
| 222 if self.adb.SetJavaAssertsEnabled(enable=not self.disable_assertions): | 210 if self.adb.SetJavaAssertsEnabled(enable=not self.disable_assertions): |
| 223 self.adb.Reboot(full_reboot=False) | 211 self.adb.Reboot(full_reboot=False) |
| 224 | 212 |
| 225 # We give different default value to launch HTTP server based on shard index | 213 # We give different default value to launch HTTP server based on shard index |
| 226 # because it may have race condition when multiple processes are trying to | 214 # because it may have race condition when multiple processes are trying to |
| 227 # launch lighttpd with same port at same time. | 215 # launch lighttpd with same port at same time. |
| 228 http_server_ports = self.LaunchTestHttpServer( | 216 http_server_ports = self.LaunchTestHttpServer( |
| 229 os.path.join(constants.CHROME_DIR), | 217 os.path.join(constants.CHROME_DIR), self._lighttp_port) |
| 230 (constants.LIGHTTPD_RANDOM_PORT_FIRST + self.shard_index)) | |
| 231 if self.ports_to_forward: | 218 if self.ports_to_forward: |
| 232 port_pairs = [(port, port) for port in self.ports_to_forward] | 219 port_pairs = [(port, port) for port in self.ports_to_forward] |
| 233 # We need to remember which ports the HTTP server is using, since the | 220 # We need to remember which ports the HTTP server is using, since the |
| 234 # forwarder will stomp on them otherwise. | 221 # forwarder will stomp on them otherwise. |
| 235 port_pairs.append(http_server_ports) | 222 port_pairs.append(http_server_ports) |
| 236 self.forwarder = forwarder.Forwarder(self.adb, self.build_type) | 223 self.forwarder = forwarder.Forwarder(self.adb, self.build_type) |
| 237 self.forwarder.Run(port_pairs, self.tool, '127.0.0.1') | 224 self.forwarder.Run(port_pairs, self.tool, '127.0.0.1') |
| 238 self.CopyTestFilesOnce() | 225 self.CopyTestFilesOnce() |
| 239 self.flags.AddFlags(['--enable-test-intents']) | 226 self.flags.AddFlags(['--enable-test-intents']) |
| 240 | 227 |
| (...skipping 142 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 383 if 'Manual' in annotations: | 370 if 'Manual' in annotations: |
| 384 return 600 * 60 | 371 return 600 * 60 |
| 385 if 'External' in annotations: | 372 if 'External' in annotations: |
| 386 return 10 * 60 | 373 return 10 * 60 |
| 387 if 'LargeTest' in annotations or _PERF_TEST_ANNOTATION in annotations: | 374 if 'LargeTest' in annotations or _PERF_TEST_ANNOTATION in annotations: |
| 388 return 5 * 60 | 375 return 5 * 60 |
| 389 if 'MediumTest' in annotations: | 376 if 'MediumTest' in annotations: |
| 390 return 3 * 60 | 377 return 3 * 60 |
| 391 return 1 * 60 | 378 return 1 * 60 |
| 392 | 379 |
| 393 def RunTests(self): | 380 def RunTest(self, test): |
| 394 """Runs the tests, generating the coverage if needed. | 381 """Runs the tests, generating the coverage if needed. |
|
frankf
2013/03/01 20:32:07
tests -> test
craigdh
2013/03/01 21:45:11
Done.
| |
| 395 | 382 |
| 396 Returns: | 383 Returns: |
| 397 A test_result.TestResults object. | 384 A test_result.TestResults object. |
| 398 """ | 385 """ |
| 399 instrumentation_path = (self.instrumentation_class_path + | 386 instrumentation_path = (self.instrumentation_class_path + |
| 400 '/android.test.InstrumentationTestRunner') | 387 '/android.test.InstrumentationTestRunner') |
| 401 instrumentation_args = self._GetInstrumentationArgs() | 388 instrumentation_args = self._GetInstrumentationArgs() |
| 402 for test in self._GetTestsIter(): | 389 raw_result = None |
| 390 start_date_ms = None | |
| 391 test_results = test_result.TestResults() | |
| 392 try: | |
| 393 self.TestSetup(test) | |
| 394 start_date_ms = int(time.time()) * 1000 | |
| 395 args_with_filter = dict(instrumentation_args) | |
| 396 args_with_filter['class'] = test | |
| 397 # |raw_results| is a list that should contain | |
| 398 # a single TestResult object. | |
| 399 logging.warn(args_with_filter) | |
| 400 (raw_results, _) = self.adb.Adb().StartInstrumentation( | |
| 401 instrumentation_path=instrumentation_path, | |
| 402 instrumentation_args=args_with_filter, | |
| 403 timeout_time=(self._GetIndividualTestTimeoutSecs(test) * | |
| 404 self._GetIndividualTestTimeoutScale(test) * | |
| 405 self.tool.GetTimeoutScale())) | |
| 406 duration_ms = int(time.time()) * 1000 - start_date_ms | |
| 407 assert len(raw_results) == 1 | |
| 408 raw_result = raw_results[0] | |
| 409 status_code = raw_result.GetStatusCode() | |
| 410 if status_code: | |
| 411 log = raw_result.GetFailureReason() | |
| 412 if not log: | |
| 413 log = 'No information.' | |
| 414 if self.screenshot_failures or log.find('INJECT_EVENTS perm') >= 0: | |
| 415 self._TakeScreenshot(test) | |
| 416 test_results.failed = [test_result.SingleTestResult( | |
| 417 test, start_date_ms, duration_ms, log)] | |
| 418 else: | |
| 419 test_results.ok = [test_result.SingleTestResult(test, start_date_ms, | |
| 420 duration_ms)] | |
| 421 # Catch exceptions thrown by StartInstrumentation(). | |
| 422 # See ../../third_party/android/testrunner/adb_interface.py | |
| 423 except (android_commands.errors.WaitForResponseTimedOutError, | |
| 424 android_commands.errors.DeviceUnresponsiveError, | |
| 425 android_commands.errors.InstrumentationError), e: | |
| 426 if start_date_ms: | |
| 427 duration_ms = int(time.time()) * 1000 - start_date_ms | |
| 428 else: | |
| 429 start_date_ms = int(time.time()) * 1000 | |
| 430 duration_ms = 0 | |
| 431 message = str(e) | |
| 432 if not message: | |
| 433 message = 'No information.' | |
| 434 test_results.crashed = [test_result.SingleTestResult( | |
| 435 test, start_date_ms, duration_ms, message)] | |
| 403 raw_result = None | 436 raw_result = None |
| 404 start_date_ms = None | 437 self.TestTeardown(test, raw_result) |
| 405 try: | 438 return (test_results, None if test_results.ok else test) |
| 406 self.TestSetup(test) | |
| 407 start_date_ms = int(time.time()) * 1000 | |
| 408 args_with_filter = dict(instrumentation_args) | |
| 409 args_with_filter['class'] = test | |
| 410 # |raw_results| is a list that should contain | |
| 411 # a single TestResult object. | |
| 412 logging.warn(args_with_filter) | |
| 413 (raw_results, _) = self.adb.Adb().StartInstrumentation( | |
| 414 instrumentation_path=instrumentation_path, | |
| 415 instrumentation_args=args_with_filter, | |
| 416 timeout_time=(self._GetIndividualTestTimeoutSecs(test) * | |
| 417 self._GetIndividualTestTimeoutScale(test) * | |
| 418 self.tool.GetTimeoutScale())) | |
| 419 duration_ms = int(time.time()) * 1000 - start_date_ms | |
| 420 assert len(raw_results) == 1 | |
| 421 raw_result = raw_results[0] | |
| 422 status_code = raw_result.GetStatusCode() | |
| 423 if status_code: | |
| 424 log = raw_result.GetFailureReason() | |
| 425 if not log: | |
| 426 log = 'No information.' | |
| 427 if self.screenshot_failures or log.find('INJECT_EVENTS perm') >= 0: | |
| 428 self._TakeScreenshot(test) | |
| 429 self.test_results.failed += [test_result.SingleTestResult( | |
| 430 test, start_date_ms, duration_ms, log)] | |
| 431 else: | |
| 432 result = [test_result.SingleTestResult(test, start_date_ms, | |
| 433 duration_ms)] | |
| 434 self.test_results.ok += result | |
| 435 # Catch exceptions thrown by StartInstrumentation(). | |
| 436 # See ../../third_party/android/testrunner/adb_interface.py | |
| 437 except (android_commands.errors.WaitForResponseTimedOutError, | |
| 438 android_commands.errors.DeviceUnresponsiveError, | |
| 439 android_commands.errors.InstrumentationError), e: | |
| 440 if start_date_ms: | |
| 441 duration_ms = int(time.time()) * 1000 - start_date_ms | |
| 442 else: | |
| 443 start_date_ms = int(time.time()) * 1000 | |
| 444 duration_ms = 0 | |
| 445 message = str(e) | |
| 446 if not message: | |
| 447 message = 'No information.' | |
| 448 self.test_results.crashed += [test_result.SingleTestResult( | |
| 449 test, start_date_ms, duration_ms, message)] | |
| 450 raw_result = None | |
| 451 self.TestTeardown(test, raw_result) | |
| 452 return self.test_results | |
| OLD | NEW |