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