instrumentation_test_instance.py (41637B)
1 # Copyright 2015 The Chromium Authors 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 5 6 import copy 7 import logging 8 import os 9 import re 10 11 from devil.android import apk_helper 12 from pylib import constants 13 from pylib.base import base_test_result 14 from pylib.base import test_exception 15 from pylib.base import test_instance 16 from pylib.constants import host_paths 17 from pylib.instrumentation import instrumentation_parser 18 from pylib.instrumentation import test_result 19 from pylib.symbols import deobfuscator 20 from pylib.symbols import stack_symbolizer 21 from pylib.utils import dexdump 22 from pylib.utils import gold_utils 23 from pylib.utils import test_filter 24 25 with host_paths.SysPath(host_paths.BUILD_UTIL_PATH): 26 from lib.common import unittest_util 27 28 # Ref: http://developer.android.com/reference/android/app/Activity.html 29 _ACTIVITY_RESULT_CANCELED = 0 30 _ACTIVITY_RESULT_OK = -1 31 32 _COMMAND_LINE_PARAMETER = 'cmdlinearg-parameter' 33 _DEFAULT_ANNOTATIONS = [ 34 'SmallTest', 'MediumTest', 'LargeTest', 'EnormousTest', 'IntegrationTest'] 35 # This annotation is for disabled tests that should not be run in Test Reviver. 36 _DO_NOT_REVIVE_ANNOTATIONS = ['DoNotRevive', 'Manual'] 37 _EXCLUDE_UNLESS_REQUESTED_ANNOTATIONS = [ 38 'DisabledTest', 'FlakyTest', 'Manual'] 39 _VALID_ANNOTATIONS = set(_DEFAULT_ANNOTATIONS + _DO_NOT_REVIVE_ANNOTATIONS + 40 _EXCLUDE_UNLESS_REQUESTED_ANNOTATIONS) 41 42 _BASE_INSTRUMENTATION_CLASS_NAME = ( 43 'org.chromium.base.test.BaseChromiumAndroidJUnitRunner') 44 45 _SKIP_PARAMETERIZATION = 'SkipCommandLineParameterization' 46 _PARAMETERIZED_COMMAND_LINE_FLAGS = 'ParameterizedCommandLineFlags' 47 _PARAMETERIZED_COMMAND_LINE_FLAGS_SWITCHES = ( 48 'ParameterizedCommandLineFlags$Switches') 49 _NATIVE_CRASH_RE = re.compile('(process|native) crash', re.IGNORECASE) 50 51 # The ID of the bundle value Instrumentation uses to report which test index the 52 # results are for in a collection of tests. Note that this index is 1-based. 53 _BUNDLE_CURRENT_ID = 'current' 54 # The ID of the bundle value Instrumentation uses to report the test class. 55 _BUNDLE_CLASS_ID = 'class' 56 # The ID of the bundle value Instrumentation uses to report the test name. 57 _BUNDLE_TEST_ID = 'test' 58 # The ID of the bundle value Instrumentation uses to report the crash stack, if 59 # the test crashed. 60 _BUNDLE_STACK_ID = 'stack' 61 62 # The ID of the bundle value Chrome uses to report the test duration. 63 _BUNDLE_DURATION_ID = 'duration_ms' 64 65 # The following error messages are too general to be useful in failure 66 # clustering. The runner doesn't report failure reason when such failure 67 # reason is parsed from test logs. 68 _BANNED_FAILURE_REASONS = [ 69 # Default error message from org.chromium.base.test.util.CallbackHelper 70 # when timeout at expecting call back. 71 'java.util.concurrent.TimeoutException: waitForCallback timed out!', 72 ] 73 74 75 class MissingSizeAnnotationError(test_exception.TestException): 76 def __init__(self, class_name): 77 super().__init__( 78 class_name + 79 ': Test method is missing required size annotation. Add one of: ' + 80 ', '.join('@' + a for a in _VALID_ANNOTATIONS)) 81 82 83 class CommandLineParameterizationException(test_exception.TestException): 84 pass 85 86 87 def GenerateTestResults(result_code, result_bundle, statuses, duration_ms, 88 device_abi, symbolizer): 89 """Generate test results from |statuses|. 90 91 Args: 92 result_code: The overall status code as an integer. 93 result_bundle: The summary bundle dump as a dict. 94 statuses: A list of 2-tuples containing: 95 - the status code as an integer 96 - the bundle dump as a dict mapping string keys to string values 97 Note that this is the same as the third item in the 3-tuple returned by 98 |_ParseAmInstrumentRawOutput|. 99 duration_ms: The duration of the test in milliseconds. 100 device_abi: The device_abi, which is needed for symbolization. 101 symbolizer: The symbolizer used to symbolize stack. 102 103 Returns: 104 A list containing an instance of InstrumentationTestResult for each test 105 parsed. 106 """ 107 108 results = [] 109 # Results from synthetic ClassName#null tests, which occur from exceptions in 110 # @BeforeClass / @AfterClass. 111 class_failure_results = [] 112 113 def add_result(result): 114 if result.GetName().endswith('#null'): 115 if result.GetType() == base_test_result.ResultType.UNKNOWN: 116 # Not sure how this happens. http://crbug.com/397924393 117 # It presumably means two START events happen in a row. 118 pass 119 else: 120 assert result.GetType() == base_test_result.ResultType.FAIL, ( 121 'test result of %r should be %r, but got %r' % 122 (result.GetName(), base_test_result.ResultType.FAIL, 123 result.GetType())) 124 class_failure_results.append(result) 125 else: 126 results.append(result) 127 128 current_result = None 129 cumulative_duration = 0 130 131 for status_code, bundle in statuses: 132 # If the last test was a failure already, don't override that failure with 133 # post-test failures that could be caused by the original failure. 134 if (status_code == instrumentation_parser.STATUS_CODE_BATCH_FAILURE 135 and current_result.GetType() != base_test_result.ResultType.FAIL): 136 current_result.SetType(base_test_result.ResultType.FAIL) 137 _MaybeSetLog(bundle, current_result, symbolizer, device_abi) 138 continue 139 140 if status_code == instrumentation_parser.STATUS_CODE_TEST_DURATION: 141 # For the first result, duration will be set below to the difference 142 # between the reported and actual durations to account for overhead like 143 # starting instrumentation. 144 if results: 145 current_duration = int(bundle.get(_BUNDLE_DURATION_ID, duration_ms)) 146 current_result.SetDuration(current_duration) 147 cumulative_duration += current_duration 148 continue 149 150 test_class = bundle.get(_BUNDLE_CLASS_ID, '') 151 test_method = bundle.get(_BUNDLE_TEST_ID, '') 152 if test_class and test_method: 153 test_name = '%s#%s' % (test_class, test_method) 154 else: 155 continue 156 157 if status_code == instrumentation_parser.STATUS_CODE_START: 158 if current_result: 159 add_result(current_result) 160 current_result = test_result.InstrumentationTestResult( 161 test_name, base_test_result.ResultType.UNKNOWN, duration_ms) 162 else: 163 if status_code == instrumentation_parser.STATUS_CODE_OK: 164 if current_result.GetType() == base_test_result.ResultType.UNKNOWN: 165 current_result.SetType(base_test_result.ResultType.PASS) 166 elif status_code == instrumentation_parser.STATUS_CODE_SKIP: 167 current_result.SetType(base_test_result.ResultType.SKIP) 168 elif status_code == instrumentation_parser.STATUS_CODE_ASSUMPTION_FAILURE: 169 current_result.SetType(base_test_result.ResultType.SKIP) 170 else: 171 if status_code not in (instrumentation_parser.STATUS_CODE_ERROR, 172 instrumentation_parser.STATUS_CODE_FAILURE): 173 logging.error('Unrecognized status code %d. Handling as an error.', 174 status_code) 175 current_result.SetType(base_test_result.ResultType.FAIL) 176 _MaybeSetLog(bundle, current_result, symbolizer, device_abi) 177 178 if current_result: 179 if current_result.GetType() == base_test_result.ResultType.UNKNOWN: 180 crashed = (result_code == _ACTIVITY_RESULT_CANCELED and any( 181 _NATIVE_CRASH_RE.search(l) for l in result_bundle.values())) 182 if crashed: 183 current_result.SetType(base_test_result.ResultType.CRASH) 184 185 add_result(current_result) 186 187 if results: 188 logging.info('Adding cumulative overhead to test %s: %dms', 189 results[0].GetName(), duration_ms - cumulative_duration) 190 results[0].SetDuration(duration_ms - cumulative_duration) 191 192 # Copy failures from @BeforeClass / @AfterClass into all tests that are 193 # marked as passing. 194 for class_result in class_failure_results: 195 prefix = class_result.GetName()[:-len('null')] 196 for result in results: 197 if (result.GetName().startswith(prefix) 198 and result.GetType() == base_test_result.ResultType.PASS): 199 result.SetType(base_test_result.ResultType.FAIL) 200 result.SetLog(class_result.GetLog()) 201 result.SetFailureReason(class_result.GetFailureReason()) 202 203 return results 204 205 206 def _MaybeSetLog(bundle, current_result, symbolizer, device_abi): 207 if _BUNDLE_STACK_ID in bundle: 208 stack = bundle[_BUNDLE_STACK_ID] 209 if symbolizer and device_abi: 210 current_result.SetLog('%s\n%s' % (stack, '\n'.join( 211 symbolizer.ExtractAndResolveNativeStackTraces(stack, device_abi)))) 212 else: 213 current_result.SetLog(stack) 214 215 parsed_failure_reason = _ParseExceptionMessage(stack) 216 if parsed_failure_reason not in _BANNED_FAILURE_REASONS: 217 current_result.SetFailureReason(parsed_failure_reason) 218 219 220 def _ParseExceptionMessage(stack): 221 """Extracts the exception message from the given stack trace. 222 """ 223 # This interprets stack traces reported via InstrumentationResultPrinter: 224 # https://source.chromium.org/chromium/chromium/src/+/main:third_party/android_support_test_runner/runner/src/main/java/android/support/test/internal/runner/listener/InstrumentationResultPrinter.java;l=181?q=InstrumentationResultPrinter&type=cs 225 # This is a standard Java stack trace, of the form: 226 # <Result of Exception.toString()> 227 # at SomeClass.SomeMethod(...) 228 # at ... 229 lines = stack.split('\n') 230 for i, line in enumerate(lines): 231 if line.startswith('\tat'): 232 return '\n'.join(lines[0:i]) 233 # No call stack found, so assume everything is the exception message. 234 return stack 235 236 237 def FilterTests(tests, 238 filter_strs=None, 239 annotations=None, 240 excluded_annotations=None): 241 """Filter a list of tests 242 243 Args: 244 tests: a list of tests. e.g. [ 245 {'annotations": {}, 'class': 'com.example.TestA', 'method':'test1'}, 246 {'annotations": {}, 'class': 'com.example.TestB', 'method':'test2'}] 247 filter_strs: list of googletest-style filter string. 248 annotations: a dict of wanted annotations for test methods. 249 excluded_annotations: a dict of annotations to exclude. 250 251 Return: 252 A list of filtered tests 253 """ 254 255 def test_names_from_pattern(combined_pattern, test_names): 256 patterns = combined_pattern.split(':') 257 258 hashable_patterns = set() 259 filename_patterns = [] 260 for pattern in patterns: 261 if ('*' in pattern or '?' in pattern or '[' in pattern): 262 filename_patterns.append(pattern) 263 else: 264 hashable_patterns.add(pattern) 265 266 filter_test_names = set( 267 unittest_util.FilterTestNames(test_names, ':'.join( 268 filename_patterns))) if len(filename_patterns) > 0 else set() 269 270 for test_name in test_names: 271 if test_name in hashable_patterns: 272 filter_test_names.add(test_name) 273 274 return filter_test_names 275 276 def get_test_names(test): 277 test_names = set() 278 # Allow fully-qualified name as well as an omitted package. 279 unqualified_class_test = { 280 'class': test['class'].split('.')[-1], 281 'method': test['method'] 282 } 283 284 test_name = GetTestName(test, sep='.') 285 test_names.add(test_name) 286 287 unqualified_class_test_name = GetTestName(unqualified_class_test, sep='.') 288 test_names.add(unqualified_class_test_name) 289 290 unique_test_name = GetUniqueTestName(test, sep='.') 291 test_names.add(unique_test_name) 292 293 junit4_test_name = GetTestNameWithoutParameterSuffix(test, sep='.') 294 test_names.add(junit4_test_name) 295 296 unqualified_junit4_test_name = GetTestNameWithoutParameterSuffix( 297 unqualified_class_test, sep='.') 298 test_names.add(unqualified_junit4_test_name) 299 return test_names 300 301 def get_tests_from_names(tests, test_names, tests_to_names): 302 ''' Returns the tests for which the given names apply 303 304 Args: 305 tests: a list of tests. e.g. [ 306 {'annotations": {}, 'class': 'com.example.TestA', 'method':'test1'}, 307 {'annotations": {}, 'class': 'com.example.TestB', 'method':'test2'}] 308 test_names: a collection of names determining tests to return. 309 310 Return: 311 A list of tests that match the given test names 312 ''' 313 filtered_tests = [] 314 for t in tests: 315 current_test_names = tests_to_names[id(t)] 316 317 for current_test_name in current_test_names: 318 if current_test_name in test_names: 319 filtered_tests.append(t) 320 break 321 322 return filtered_tests 323 324 def remove_tests_from_names(tests, remove_test_names, tests_to_names): 325 ''' Returns the tests from the given list with given names removed 326 327 Args: 328 tests: a list of tests. e.g. [ 329 {'annotations": {}, 'class': 'com.example.TestA', 'method':'test1'}, 330 {'annotations": {}, 'class': 'com.example.TestB', 'method':'test2'}] 331 remove_test_names: a collection of names determining tests to remove. 332 tests_to_names: a dcitionary of test ids to a collection of applicable 333 names for that test 334 335 Return: 336 A list of tests that don't match the given test names 337 ''' 338 filtered_tests = [] 339 340 for t in tests: 341 for name in tests_to_names[id(t)]: 342 if name in remove_test_names: 343 break 344 else: 345 filtered_tests.append(t) 346 return filtered_tests 347 348 def gtests_filter(tests, combined_filters): 349 ''' Returns the tests after the combined_filters have been applied 350 351 Args: 352 tests: a list of tests. e.g. [ 353 {'annotations": {}, 'class': 'com.example.TestA', 'method':'test1'}, 354 {'annotations": {}, 'class': 'com.example.TestB', 'method':'test2'}] 355 combined_filters: the filter string representing tests to exclude 356 357 Return: 358 A list of tests that should still be included after the combined_filters 359 are applied to their names 360 ''' 361 362 if not combined_filters: 363 return tests 364 365 # Collect all test names 366 all_test_names = set() 367 tests_to_names = {} 368 for t in tests: 369 tests_to_names[id(t)] = get_test_names(t) 370 for name in tests_to_names[id(t)]: 371 all_test_names.add(name) 372 373 for combined_filter in combined_filters: 374 pattern_groups = combined_filter.split('-') 375 negative_pattern = pattern_groups[1] if len(pattern_groups) > 1 else None 376 positive_pattern = pattern_groups[0] 377 if positive_pattern: 378 # Only use the test names that match the positive pattern 379 positive_test_names = test_names_from_pattern(positive_pattern, 380 all_test_names) 381 tests = get_tests_from_names(tests, positive_test_names, tests_to_names) 382 383 if negative_pattern: 384 # Remove any test the negative filter matches 385 remove_names = test_names_from_pattern(negative_pattern, all_test_names) 386 tests = remove_tests_from_names(tests, remove_names, tests_to_names) 387 388 return tests 389 390 def annotation_filter(all_annotations): 391 if not annotations: 392 return True 393 return any_annotation_matches(annotations, all_annotations) 394 395 def excluded_annotation_filter(all_annotations): 396 if not excluded_annotations: 397 return True 398 return not any_annotation_matches(excluded_annotations, 399 all_annotations) 400 401 def any_annotation_matches(filter_annotations, all_annotations): 402 return any( 403 ak in all_annotations 404 and annotation_value_matches(av, all_annotations[ak]) 405 for ak, av in filter_annotations) 406 407 def annotation_value_matches(filter_av, av): 408 if filter_av is None: 409 return True 410 if isinstance(av, dict): 411 tav_from_dict = av['value'] 412 # If tav_from_dict is an int, the 'in' operator breaks, so convert 413 # filter_av and manually compare. See https://crbug.com/1019707 414 if isinstance(tav_from_dict, int): 415 return int(filter_av) == tav_from_dict 416 return filter_av in tav_from_dict 417 if isinstance(av, list): 418 return filter_av in av 419 return filter_av == av 420 421 return_tests = [] 422 for t in gtests_filter(tests, filter_strs): 423 # Enforce that all tests declare their size. 424 if not any(a in _VALID_ANNOTATIONS for a in t['annotations']): 425 raise MissingSizeAnnotationError(GetTestName(t)) 426 427 if (not annotation_filter(t['annotations']) 428 or not excluded_annotation_filter(t['annotations'])): 429 continue 430 return_tests.append(t) 431 432 return return_tests 433 434 435 def GetTestsFromDexdump(test_apk): 436 dex_dumps = dexdump.Dump(test_apk) 437 tests = [] 438 439 def get_test_methods(methods, annotations): 440 test_methods = [] 441 442 for method in methods: 443 if method.startswith('test'): 444 method_annotations = annotations.get(method, {}) 445 446 # Dexdump used to not return any annotation info 447 # So MediumTest annotation was added to all methods 448 # Preserving this behaviour by adding MediumTest if none of the 449 # size annotations are included in these annotations 450 if not any(valid in method_annotations for valid in _VALID_ANNOTATIONS): 451 method_annotations.update({'MediumTest': None}) 452 453 test_methods.append({ 454 'method': method, 455 'annotations': method_annotations 456 }) 457 458 return test_methods 459 460 for dump in dex_dumps: 461 for package_name, package_info in dump.items(): 462 for class_name, class_info in package_info['classes'].items(): 463 if class_name.endswith('Test') and not class_info['is_abstract']: 464 classAnnotations, methodsAnnotations = class_info['annotations'] 465 tests.append({ 466 'class': 467 '%s.%s' % (package_name, class_name), 468 'annotations': 469 classAnnotations, 470 'methods': 471 get_test_methods(class_info['methods'], methodsAnnotations), 472 }) 473 return tests 474 475 476 def GetTestName(test, sep='#'): 477 """Gets the name of the given test. 478 479 Note that this may return the same name for more than one test, e.g. if a 480 test is being run multiple times with different parameters. 481 482 Args: 483 test: the instrumentation test dict. 484 sep: the character(s) that should join the class name and the method name. 485 Returns: 486 The test name as a string. 487 """ 488 test_name = '%s%s%s' % (test['class'], sep, test['method']) 489 assert not any(char in test_name for char in ' *-:'), ( 490 'The test name must not contain any of the characters in " *-:". See ' 491 'https://crbug.com/912199') 492 return test_name 493 494 495 def GetTestNameWithoutParameterSuffix(test, sep='#', parameterization_sep='__'): 496 """Gets the name of the given JUnit4 test without parameter suffix. 497 498 For most WebView JUnit4 javatests, each test is parameterizatized with 499 "__sandboxed_mode" to run in both non-sandboxed mode and sandboxed mode. 500 501 This function returns the name of the test without parameterization 502 so test filters can match both parameterized and non-parameterized tests. 503 504 Args: 505 test: the instrumentation test dict. 506 sep: the character(s) that should join the class name and the method name. 507 parameterization_sep: the character(s) that separate method name and method 508 parameterization suffix. 509 Returns: 510 The test name without parameter suffix as a string. 511 """ 512 name = GetTestName(test, sep=sep) 513 return name.split(parameterization_sep)[0] 514 515 516 def GetUniqueTestName(test, sep='#'): 517 """Gets the unique name of the given test. 518 519 This will include text to disambiguate between tests for which GetTestName 520 would return the same name. 521 522 Args: 523 test: the instrumentation test dict. 524 sep: the character(s) that should join the class name and the method name. 525 Returns: 526 The unique test name as a string. 527 """ 528 display_name = GetTestName(test, sep=sep) 529 if test.get('flags', [None])[0]: 530 sanitized_flags = [x.replace('-', '_') for x in test['flags']] 531 display_name = '%s_with_%s' % (display_name, '_'.join(sanitized_flags)) 532 533 assert not any(char in display_name for char in ' *-:'), ( 534 'The test name must not contain any of the characters in " *-:". See ' 535 'https://crbug.com/912199') 536 537 return display_name 538 539 540 class InstrumentationTestInstance(test_instance.TestInstance): 541 542 def __init__(self, args, data_deps_delegate, error_func): 543 super().__init__() 544 545 self._additional_apks = [] 546 self._additional_apexs = [] 547 self._forced_queryable_additional_apks = [] 548 self._instant_additional_apks = [] 549 self._apk_under_test = None 550 self._apk_under_test_incremental_install_json = None 551 self._modules = None 552 self._fake_modules = None 553 self._additional_locales = None 554 self._package_info = None 555 self._suite = None 556 self._test_apk = None 557 self._test_apk_as_instant = False 558 self._test_apk_incremental_install_json = None 559 self._test_package = None 560 self._junit4_runner_class = None 561 self._uses_base_instrumentation = None 562 self._has_chromium_test_listener = None 563 self._use_native_coverage_listener = None 564 self._test_support_apk = None 565 self._initializeApkAttributes(args, error_func) 566 567 self._data_deps = None 568 self._data_deps_delegate = None 569 self._runtime_deps_path = None 570 self._variations_test_seed_path = args.variations_test_seed_path 571 self._webview_variations_test_seed_path = ( 572 args.webview_variations_test_seed_path) 573 self._store_data_dependencies_in_temp = False 574 self._initializeDataDependencyAttributes(args, data_deps_delegate) 575 self._annotations = None 576 self._excluded_annotations = None 577 self._has_external_annotation_filters = None 578 self._test_filters = None 579 self._initializeTestFilterAttributes(args) 580 581 self._run_setup_commands = [] 582 self._run_teardown_commands = [] 583 self._initializeSetupTeardownCommandAttributes(args) 584 585 self._flags = None 586 self._use_apk_under_test_flags_file = False 587 self._webview_flags = args.webview_command_line_arg 588 self._initializeFlagAttributes(args) 589 590 self._screenshot_dir = None 591 self._timeout_scale = None 592 self._wait_for_java_debugger = None 593 self._initializeTestControlAttributes(args) 594 595 self._coverage_directory = None 596 self._initializeTestCoverageAttributes(args) 597 598 self._store_tombstones = False 599 self._symbolizer = None 600 self._enable_breakpad_dump = False 601 self._proguard_mapping_path = None 602 self._deobfuscator = None 603 self._initializeLogAttributes(args) 604 605 self._replace_system_package = None 606 self._initializeReplaceSystemPackageAttributes(args) 607 608 self._system_packages_to_remove = None 609 self._initializeSystemPackagesToRemoveAttributes(args) 610 611 self._use_voice_interaction_service = None 612 self._initializeUseVoiceInteractionService(args) 613 614 self._use_webview_provider = None 615 self._initializeUseWebviewProviderAttributes(args) 616 617 self._skia_gold_properties = None 618 self._initializeSkiaGoldAttributes(args) 619 620 self._test_launcher_batch_limit = None 621 self._initializeTestLauncherAttributes(args) 622 623 self._approve_app_links_domain = None 624 self._approve_app_links_package = None 625 self._initializeApproveAppLinksAttributes(args) 626 627 self._webview_process_mode = args.webview_process_mode 628 629 self._webview_rebaseline_mode = args.webview_rebaseline_mode 630 631 self._wpr_enable_record = args.wpr_enable_record 632 633 self._external_shard_index = args.test_launcher_shard_index 634 self._total_external_shards = args.test_launcher_total_shards 635 636 self._is_unit_test = False 637 self._initializeUnitTestFlag(args) 638 639 self._run_disabled = args.run_disabled 640 641 def _initializeApkAttributes(self, args, error_func): 642 if args.apk_under_test: 643 apk_under_test_path = args.apk_under_test 644 if (not args.apk_under_test.endswith('.apk') 645 and not args.apk_under_test.endswith('.apks')): 646 apk_under_test_path = os.path.join( 647 constants.GetOutDirectory(), constants.SDK_BUILD_APKS_DIR, 648 '%s.apk' % args.apk_under_test) 649 650 # TODO(jbudorick): Move the realpath up to the argument parser once 651 # APK-by-name is no longer supported. 652 apk_under_test_path = os.path.realpath(apk_under_test_path) 653 654 if not os.path.exists(apk_under_test_path): 655 error_func('Unable to find APK under test: %s' % apk_under_test_path) 656 657 self._apk_under_test = apk_helper.ToHelper(apk_under_test_path) 658 659 test_apk_path = args.test_apk 660 if (not args.test_apk.endswith('.apk') 661 and not args.test_apk.endswith('.apks')): 662 test_apk_path = os.path.join( 663 constants.GetOutDirectory(), constants.SDK_BUILD_APKS_DIR, 664 '%s.apk' % args.test_apk) 665 666 # TODO(jbudorick): Move the realpath up to the argument parser once 667 # APK-by-name is no longer supported. 668 test_apk_path = os.path.realpath(test_apk_path) 669 670 if not os.path.exists(test_apk_path): 671 error_func('Unable to find test APK: %s' % test_apk_path) 672 673 self._test_apk = apk_helper.ToHelper(test_apk_path) 674 self._suite = os.path.splitext(os.path.basename(args.test_apk))[0] 675 676 self._test_apk_as_instant = args.test_apk_as_instant 677 678 self._apk_under_test_incremental_install_json = ( 679 args.apk_under_test_incremental_install_json) 680 self._test_apk_incremental_install_json = ( 681 args.test_apk_incremental_install_json) 682 683 if self._test_apk_incremental_install_json: 684 assert self._suite.endswith('_incremental') 685 self._suite = self._suite[:-len('_incremental')] 686 687 self._modules = args.modules 688 self._fake_modules = args.fake_modules 689 self._additional_locales = args.additional_locales 690 691 self._test_support_apk = apk_helper.ToHelper(os.path.join( 692 constants.GetOutDirectory(), constants.SDK_BUILD_TEST_JAVALIB_DIR, 693 '%sSupport.apk' % self._suite)) 694 695 self._test_package = self._test_apk.GetPackageName() 696 all_instrumentations = self._test_apk.GetAllInstrumentations() 697 698 if len(all_instrumentations) > 1: 699 logging.warning('This test apk has more than one instrumentation') 700 701 self._junit4_runner_class = (all_instrumentations[0]['android:name'] 702 if all_instrumentations else None) 703 704 test_apk_metadata = dict(self._test_apk.GetAllMetadata()) 705 self._has_chromium_test_listener = bool( 706 test_apk_metadata.get('org.chromium.hasTestRunListener')) 707 self._use_native_coverage_listener = bool( 708 test_apk_metadata.get('org.chromium.useNativeCoverageListener')) 709 if self._junit4_runner_class: 710 if self._test_apk_incremental_install_json: 711 for name, value in test_apk_metadata.items(): 712 if (name.startswith('incremental-install-instrumentation-') 713 and value == _BASE_INSTRUMENTATION_CLASS_NAME): 714 self._uses_base_instrumentation = True 715 break 716 else: 717 self._uses_base_instrumentation = ( 718 self._junit4_runner_class == _BASE_INSTRUMENTATION_CLASS_NAME) 719 720 self._package_info = None 721 if self._apk_under_test: 722 package_under_test = self._apk_under_test.GetPackageName() 723 for package_info in constants.PACKAGE_INFO.values(): 724 if package_under_test == package_info.package: 725 self._package_info = package_info 726 break 727 if not self._package_info: 728 logging.warning( 729 'Unable to find package info for %s. ' 730 '(This may just mean that the test package is ' 731 'currently being installed.)', self._test_package) 732 733 for x in set(args.additional_apks + args.forced_queryable_additional_apks + 734 args.instant_additional_apks): 735 if not os.path.exists(x): 736 error_func('Unable to find additional APK: %s' % x) 737 738 apk = apk_helper.ToHelper(x) 739 self._additional_apks.append(apk) 740 741 if x in args.forced_queryable_additional_apks: 742 self._forced_queryable_additional_apks.append(apk) 743 744 if x in args.instant_additional_apks: 745 self._instant_additional_apks.append(apk) 746 747 self._additional_apexs = args.additional_apexs 748 749 def _initializeDataDependencyAttributes(self, args, data_deps_delegate): 750 self._data_deps = [] 751 self._data_deps_delegate = data_deps_delegate 752 self._store_data_dependencies_in_temp = args.store_data_dependencies_in_temp 753 self._runtime_deps_path = args.runtime_deps_path 754 755 if not self._runtime_deps_path: 756 logging.warning('No data dependencies will be pushed.') 757 758 def _initializeTestFilterAttributes(self, args): 759 self._test_filters = test_filter.InitializeFiltersFromArgs(args) 760 self._has_external_annotation_filters = bool(args.annotation_str 761 or args.exclude_annotation_str) 762 763 def annotation_element(a): 764 a = a.split('=', 1) 765 return (a[0], a[1] if len(a) == 2 else None) 766 767 if args.annotation_str: 768 self._annotations = [ 769 annotation_element(a) for a in args.annotation_str.split(',')] 770 elif not self._test_filters: 771 self._annotations = [ 772 annotation_element(a) for a in _DEFAULT_ANNOTATIONS] 773 else: 774 self._annotations = [] 775 776 if args.exclude_annotation_str: 777 self._excluded_annotations = [ 778 annotation_element(a) for a in args.exclude_annotation_str.split(',')] 779 else: 780 self._excluded_annotations = [] 781 782 requested_annotations = set(a[0] for a in self._annotations) 783 if args.run_disabled: 784 self._excluded_annotations.extend( 785 annotation_element(a) for a in _DO_NOT_REVIVE_ANNOTATIONS 786 if a not in requested_annotations) 787 else: 788 self._excluded_annotations.extend( 789 annotation_element(a) for a in _EXCLUDE_UNLESS_REQUESTED_ANNOTATIONS 790 if a not in requested_annotations) 791 792 def _initializeSetupTeardownCommandAttributes(self, args): 793 self._run_setup_commands = args.run_setup_commands 794 self._run_teardown_commands = args.run_teardown_commands 795 796 def _initializeFlagAttributes(self, args): 797 self._use_apk_under_test_flags_file = args.use_apk_under_test_flags_file 798 self._flags = ['--enable-test-intents'] 799 if args.command_line_flags: 800 self._flags.extend(args.command_line_flags) 801 if args.device_flags_file: 802 with open(args.device_flags_file) as device_flags_file: 803 stripped_lines = (l.strip() for l in device_flags_file) 804 self._flags.extend(flag for flag in stripped_lines if flag) 805 if args.strict_mode and args.strict_mode != 'off' and ( 806 # TODO(yliuyliu): Turn on strict mode for coverage once 807 # crbug/1006397 is fixed. 808 not args.coverage_dir): 809 self._flags.append('--strict-mode=' + args.strict_mode) 810 811 def _initializeTestControlAttributes(self, args): 812 self._screenshot_dir = args.screenshot_dir 813 self._timeout_scale = args.timeout_scale or 1 814 self._wait_for_java_debugger = args.wait_for_java_debugger 815 816 def _initializeTestCoverageAttributes(self, args): 817 self._coverage_directory = args.coverage_dir 818 819 def _initializeLogAttributes(self, args): 820 self._enable_breakpad_dump = args.enable_breakpad_dump 821 self._proguard_mapping_path = args.proguard_mapping_path 822 self._store_tombstones = args.store_tombstones 823 self._symbolizer = stack_symbolizer.Symbolizer( 824 self.apk_under_test.path if self.apk_under_test else None) 825 826 def _initializeReplaceSystemPackageAttributes(self, args): 827 if (not hasattr(args, 'replace_system_package') 828 or not args.replace_system_package): 829 return 830 self._replace_system_package = args.replace_system_package 831 832 def _initializeSystemPackagesToRemoveAttributes(self, args): 833 if (not hasattr(args, 'system_packages_to_remove') 834 or not args.system_packages_to_remove): 835 return 836 self._system_packages_to_remove = args.system_packages_to_remove 837 838 def _initializeUseVoiceInteractionService(self, args): 839 if (not hasattr(args, 'use_voice_interaction_service') 840 or not args.use_voice_interaction_service): 841 return 842 self._use_voice_interaction_service = args.use_voice_interaction_service 843 844 def _initializeUseWebviewProviderAttributes(self, args): 845 if (not hasattr(args, 'use_webview_provider') 846 or not args.use_webview_provider): 847 return 848 self._use_webview_provider = args.use_webview_provider 849 850 def _initializeSkiaGoldAttributes(self, args): 851 self._skia_gold_properties = gold_utils.AndroidSkiaGoldProperties(args) 852 853 def _initializeTestLauncherAttributes(self, args): 854 if hasattr(args, 'test_launcher_batch_limit'): 855 self._test_launcher_batch_limit = args.test_launcher_batch_limit 856 857 def _initializeApproveAppLinksAttributes(self, args): 858 if (not hasattr(args, 'approve_app_links') or not args.approve_app_links): 859 return 860 861 # The argument will be formatted as com.android.thing:www.example.com . 862 app_links = args.approve_app_links.split(':') 863 864 if (len(app_links) != 2 or not app_links[0] or not app_links[1]): 865 logging.warning('--approve_app_links option provided, but malformed.') 866 return 867 868 self._approve_app_links_package = app_links[0] 869 self._approve_app_links_domain = app_links[1] 870 871 def _initializeUnitTestFlag(self, args): 872 self._is_unit_test = args.is_unit_test 873 874 @property 875 def additional_apks(self): 876 return self._additional_apks 877 878 @property 879 def additional_apexs(self): 880 return self._additional_apexs 881 882 @property 883 def apk_under_test(self): 884 return self._apk_under_test 885 886 @property 887 def apk_under_test_incremental_install_json(self): 888 return self._apk_under_test_incremental_install_json 889 890 @property 891 def approve_app_links_package(self): 892 return self._approve_app_links_package 893 894 @property 895 def approve_app_links_domain(self): 896 return self._approve_app_links_domain 897 898 @property 899 def modules(self): 900 return self._modules 901 902 @property 903 def fake_modules(self): 904 return self._fake_modules 905 906 @property 907 def additional_locales(self): 908 return self._additional_locales 909 910 @property 911 def coverage_directory(self): 912 return self._coverage_directory 913 914 @property 915 def enable_breakpad_dump(self): 916 return self._enable_breakpad_dump 917 918 @property 919 def external_shard_index(self): 920 return self._external_shard_index 921 922 @property 923 def flags(self): 924 return self._flags[:] 925 926 @property 927 def is_unit_test(self): 928 return self._is_unit_test 929 930 @property 931 def junit4_runner_class(self): 932 return self._junit4_runner_class 933 934 @property 935 def has_chromium_test_listener(self): 936 return self._has_chromium_test_listener 937 938 @property 939 def has_external_annotation_filters(self): 940 return self._has_external_annotation_filters 941 942 @property 943 def uses_base_instrumentation(self): 944 return self._uses_base_instrumentation 945 946 @property 947 def use_native_coverage_listener(self): 948 return self._use_native_coverage_listener 949 950 @property 951 def package_info(self): 952 return self._package_info 953 954 @property 955 def replace_system_package(self): 956 return self._replace_system_package 957 958 @property 959 def run_setup_commands(self): 960 return self._run_setup_commands 961 962 @property 963 def run_teardown_commands(self): 964 return self._run_teardown_commands 965 966 @property 967 def use_voice_interaction_service(self): 968 return self._use_voice_interaction_service 969 970 @property 971 def use_webview_provider(self): 972 return self._use_webview_provider 973 974 @property 975 def webview_flags(self): 976 return self._webview_flags[:] 977 978 @property 979 def screenshot_dir(self): 980 return self._screenshot_dir 981 982 @property 983 def skia_gold_properties(self): 984 return self._skia_gold_properties 985 986 @property 987 def store_data_dependencies_in_temp(self): 988 return self._store_data_dependencies_in_temp 989 990 @property 991 def store_tombstones(self): 992 return self._store_tombstones 993 994 @property 995 def suite(self): 996 return self._suite 997 998 @property 999 def symbolizer(self): 1000 return self._symbolizer 1001 1002 @property 1003 def system_packages_to_remove(self): 1004 return self._system_packages_to_remove 1005 1006 @property 1007 def test_apk(self): 1008 return self._test_apk 1009 1010 @property 1011 def test_apk_as_instant(self): 1012 return self._test_apk_as_instant 1013 1014 @property 1015 def test_apk_incremental_install_json(self): 1016 return self._test_apk_incremental_install_json 1017 1018 @property 1019 def test_filters(self): 1020 return self._test_filters 1021 1022 @property 1023 def test_launcher_batch_limit(self): 1024 return self._test_launcher_batch_limit 1025 1026 @property 1027 def test_support_apk(self): 1028 return self._test_support_apk 1029 1030 @property 1031 def test_package(self): 1032 return self._test_package 1033 1034 @property 1035 def timeout_scale(self): 1036 return self._timeout_scale 1037 1038 @property 1039 def total_external_shards(self): 1040 return self._total_external_shards 1041 1042 @property 1043 def use_apk_under_test_flags_file(self): 1044 return self._use_apk_under_test_flags_file 1045 1046 @property 1047 def variations_test_seed_path(self): 1048 return self._variations_test_seed_path 1049 1050 @property 1051 def webview_variations_test_seed_path(self): 1052 return self._webview_variations_test_seed_path 1053 1054 @property 1055 def wait_for_java_debugger(self): 1056 return self._wait_for_java_debugger 1057 1058 @property 1059 def wpr_record_mode(self): 1060 return self._wpr_enable_record 1061 1062 @property 1063 def webview_process_mode(self): 1064 return self._webview_process_mode 1065 1066 @property 1067 def webview_rebaseline_mode(self): 1068 return self._webview_rebaseline_mode 1069 1070 @property 1071 def wpr_replay_mode(self): 1072 return not self._wpr_enable_record 1073 1074 #override 1075 def TestType(self): 1076 return 'instrumentation' 1077 1078 #override 1079 def GetPreferredAbis(self): 1080 # We could alternatively take the intersection of what they all support, 1081 # but it should never be the case that they support different things. 1082 apks = [self._test_apk, self._apk_under_test] + self._additional_apks 1083 for apk in apks: 1084 if apk: 1085 ret = apk.GetAbis() 1086 if ret: 1087 return ret 1088 return [] 1089 1090 #override 1091 def SetUp(self): 1092 self._data_deps.extend( 1093 self._data_deps_delegate(self._runtime_deps_path)) 1094 if self._proguard_mapping_path: 1095 self._deobfuscator = deobfuscator.DeobfuscatorPool( 1096 self._proguard_mapping_path) 1097 1098 def GetDataDependencies(self): 1099 return self._data_deps 1100 1101 def GetRunDisabledFlag(self): 1102 return self._run_disabled 1103 1104 def MaybeDeobfuscateLines(self, lines): 1105 if not self._deobfuscator: 1106 return lines 1107 return self._deobfuscator.TransformLines(lines) 1108 1109 def ProcessRawTests(self, raw_tests): 1110 inflated_tests = self._ParameterizeTestsWithFlags( 1111 self._InflateTests(raw_tests)) 1112 filtered_tests = FilterTests(inflated_tests, self._test_filters, 1113 self._annotations, self._excluded_annotations) 1114 if self._test_filters and not filtered_tests: 1115 for t in inflated_tests: 1116 logging.debug(' %s', GetUniqueTestName(t)) 1117 logging.warning('Unmatched Filters: %s', self._test_filters) 1118 return filtered_tests 1119 1120 def IsApkForceQueryable(self, apk): 1121 return apk in self._forced_queryable_additional_apks 1122 1123 def IsApkInstant(self, apk): 1124 return apk in self._instant_additional_apks 1125 1126 # pylint: disable=no-self-use 1127 def _InflateTests(self, tests): 1128 inflated_tests = [] 1129 for clazz in tests: 1130 for method in clazz['methods']: 1131 annotations = dict(clazz['annotations']) 1132 annotations.update(method['annotations']) 1133 1134 # Preserve historic default. 1135 if (not self._uses_base_instrumentation 1136 and not any(a in _VALID_ANNOTATIONS for a in annotations)): 1137 annotations['MediumTest'] = None 1138 1139 inflated_tests.append({ 1140 'class': clazz['class'], 1141 'method': method['method'], 1142 'annotations': annotations, 1143 }) 1144 return inflated_tests 1145 1146 def _ParameterizeTestsWithFlags(self, tests): 1147 1148 def _checkParameterization(annotations): 1149 types = [ 1150 _PARAMETERIZED_COMMAND_LINE_FLAGS_SWITCHES, 1151 _PARAMETERIZED_COMMAND_LINE_FLAGS, 1152 ] 1153 if types[0] in annotations and types[1] in annotations: 1154 raise CommandLineParameterizationException( 1155 'Multiple command-line parameterization types: {}.'.format( 1156 ', '.join(types))) 1157 1158 def _switchesToFlags(switches): 1159 return ['--{}'.format(s) for s in switches if s] 1160 1161 def _annotationToSwitches(clazz, methods): 1162 if clazz == _PARAMETERIZED_COMMAND_LINE_FLAGS_SWITCHES: 1163 return [methods['value']] 1164 if clazz == _PARAMETERIZED_COMMAND_LINE_FLAGS: 1165 list_of_switches = [] 1166 for annotation in methods['value']: 1167 for c, m in annotation.items(): 1168 list_of_switches += _annotationToSwitches(c, m) 1169 return list_of_switches 1170 return [] 1171 1172 def _setTestFlags(test, flags): 1173 if flags: 1174 test['flags'] = flags 1175 elif 'flags' in test: 1176 del test['flags'] 1177 1178 new_tests = [] 1179 for t in tests: 1180 annotations = t['annotations'] 1181 list_of_switches = [] 1182 _checkParameterization(annotations) 1183 if _SKIP_PARAMETERIZATION not in annotations: 1184 for clazz, methods in annotations.items(): 1185 list_of_switches += _annotationToSwitches(clazz, methods) 1186 if list_of_switches: 1187 _setTestFlags(t, _switchesToFlags(list_of_switches[0])) 1188 for p in list_of_switches[1:]: 1189 parameterized_t = copy.copy(t) 1190 _setTestFlags(parameterized_t, _switchesToFlags(p)) 1191 new_tests.append(parameterized_t) 1192 return tests + new_tests 1193 1194 @staticmethod 1195 def GenerateTestResults(result_code, result_bundle, statuses, duration_ms, 1196 device_abi, symbolizer): 1197 return GenerateTestResults(result_code, result_bundle, statuses, 1198 duration_ms, device_abi, symbolizer) 1199 1200 #override 1201 def TearDown(self): 1202 self.symbolizer.CleanUp() 1203 if self._deobfuscator: 1204 self._deobfuscator.Close() 1205 self._deobfuscator = None