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