| OLD | NEW |
| 1 # Copyright 2015 The Chromium Authors. All rights reserved. | 1 # Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import collections | 5 import collections |
| 6 import copy | 6 import copy |
| 7 import logging | 7 import logging |
| 8 import os | 8 import os |
| 9 import pickle | 9 import pickle |
| 10 import re | 10 import re |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 44 _EXTRA_DRIVER_TARGET_CLASS = ( | 44 _EXTRA_DRIVER_TARGET_CLASS = ( |
| 45 'org.chromium.test.driver.OnDeviceInstrumentationDriver.TargetClass') | 45 'org.chromium.test.driver.OnDeviceInstrumentationDriver.TargetClass') |
| 46 _EXTRA_TIMEOUT_SCALE = ( | 46 _EXTRA_TIMEOUT_SCALE = ( |
| 47 'org.chromium.test.driver.OnDeviceInstrumentationDriver.TimeoutScale') | 47 'org.chromium.test.driver.OnDeviceInstrumentationDriver.TimeoutScale') |
| 48 | 48 |
| 49 _PARAMETERIZED_TEST_ANNOTATION = 'ParameterizedTest' | 49 _PARAMETERIZED_TEST_ANNOTATION = 'ParameterizedTest' |
| 50 _PARAMETERIZED_TEST_SET_ANNOTATION = 'ParameterizedTest$Set' | 50 _PARAMETERIZED_TEST_SET_ANNOTATION = 'ParameterizedTest$Set' |
| 51 _NATIVE_CRASH_RE = re.compile('(process|native) crash', re.IGNORECASE) | 51 _NATIVE_CRASH_RE = re.compile('(process|native) crash', re.IGNORECASE) |
| 52 _CMDLINE_NAME_SEGMENT_RE = re.compile( | 52 _CMDLINE_NAME_SEGMENT_RE = re.compile( |
| 53 r' with(?:out)? \{[^\}]*\}') | 53 r' with(?:out)? \{[^\}]*\}') |
| 54 _PICKLE_FORMAT_VERSION = 10 | 54 _PICKLE_FORMAT_VERSION = 11 |
| 55 | 55 |
| 56 | 56 |
| 57 class MissingSizeAnnotationError(test_exception.TestException): | 57 class MissingSizeAnnotationError(test_exception.TestException): |
| 58 def __init__(self, class_name): | 58 def __init__(self, class_name): |
| 59 super(MissingSizeAnnotationError, self).__init__(class_name + | 59 super(MissingSizeAnnotationError, self).__init__(class_name + |
| 60 ': Test method is missing required size annotation. Add one of: ' + | 60 ': Test method is missing required size annotation. Add one of: ' + |
| 61 ', '.join('@' + a for a in _VALID_ANNOTATIONS)) | 61 ', '.join('@' + a for a in _VALID_ANNOTATIONS)) |
| 62 | 62 |
| 63 | 63 |
| 64 class TestListPickleException(test_exception.TestException): | 64 class TestListPickleException(test_exception.TestException): |
| (...skipping 283 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 348 else: | 348 else: |
| 349 a = {} | 349 a = {} |
| 350 a.update(c['annotations']) | 350 a.update(c['annotations']) |
| 351 return a | 351 return a |
| 352 | 352 |
| 353 def stripped_test_class(c): | 353 def stripped_test_class(c): |
| 354 return { | 354 return { |
| 355 'class': c['class'], | 355 'class': c['class'], |
| 356 'annotations': recursive_class_annotations(c), | 356 'annotations': recursive_class_annotations(c), |
| 357 'methods': [m for m in c['methods'] if is_test_method(m)], | 357 'methods': [m for m in c['methods'] if is_test_method(m)], |
| 358 'superclass': c['superclass'], |
| 358 } | 359 } |
| 359 | 360 |
| 360 return [stripped_test_class(c) for c in p['classes'] | 361 return [stripped_test_class(c) for c in p['classes'] |
| 361 if is_test_class(c)] | 362 if is_test_class(c)] |
| 362 | 363 |
| 363 | 364 |
| 364 def _GetTestsFromDexdump(test_apk): | 365 def _GetTestsFromDexdump(test_apk): |
| 365 d = dexdump.Dump(test_apk) | 366 dump = dexdump.Dump(test_apk) |
| 366 tests = [] | 367 tests = [] |
| 367 | 368 |
| 368 def get_test_methods(methods): | 369 def get_test_methods(methods): |
| 369 return [ | 370 return [ |
| 370 { | 371 { |
| 371 'method': m, | 372 'method': m, |
| 372 # No annotation info is available from dexdump. | 373 # No annotation info is available from dexdump. |
| 373 # Set MediumTest annotation for default. | 374 # Set MediumTest annotation for default. |
| 374 'annotations': {'MediumTest': None}, | 375 'annotations': {'MediumTest': None}, |
| 375 } for m in methods if m.startswith('test')] | 376 } for m in methods if m.startswith('test')] |
| 376 | 377 |
| 377 for package_name, package_info in d.iteritems(): | 378 for package_name, package_info in dump.iteritems(): |
| 378 for class_name, class_info in package_info['classes'].iteritems(): | 379 for class_name, class_info in package_info['classes'].iteritems(): |
| 379 if class_name.endswith('Test'): | 380 if class_name.endswith('Test'): |
| 380 tests.append({ | 381 tests.append({ |
| 381 'class': '%s.%s' % (package_name, class_name), | 382 'class': '%s.%s' % (package_name, class_name), |
| 382 'annotations': {}, | 383 'annotations': {}, |
| 383 'methods': get_test_methods(class_info['methods']), | 384 'methods': get_test_methods(class_info['methods']), |
| 385 'superclass': class_info['superclass'], |
| 384 }) | 386 }) |
| 385 return tests | 387 return tests |
| 386 | 388 |
| 387 | 389 |
| 388 def _SaveTestsToPickle(pickle_path, jar_path, tests): | 390 def _SaveTestsToPickle(pickle_path, jar_path, tests): |
| 389 jar_md5 = md5sum.CalculateHostMd5Sums(jar_path)[jar_path] | 391 jar_md5 = md5sum.CalculateHostMd5Sums(jar_path)[jar_path] |
| 390 pickle_data = { | 392 pickle_data = { |
| 391 'VERSION': _PICKLE_FORMAT_VERSION, | 393 'VERSION': _PICKLE_FORMAT_VERSION, |
| 392 'JAR_MD5SUM': jar_md5, | 394 'JAR_MD5SUM': jar_md5, |
| 393 'TEST_METHODS': tests, | 395 'TEST_METHODS': tests, |
| 394 } | 396 } |
| 395 with open(pickle_path, 'w') as pickle_file: | 397 with open(pickle_path, 'w') as pickle_file: |
| 396 pickle.dump(pickle_data, pickle_file) | 398 pickle.dump(pickle_data, pickle_file) |
| 397 | 399 |
| 398 | 400 |
| 401 class MissingJUnit4RunnerException(test_exception.TestException): |
| 402 """Raised when JUnit4 runner is not provided or specified in apk manifest""" |
| 403 |
| 404 def __init__(self): |
| 405 super(MissingJUnit4RunnerException, self).__init__( |
| 406 'JUnit4 runner is not provided or specified in test apk manifest.') |
| 407 |
| 408 |
| 399 class UnmatchedFilterException(test_exception.TestException): | 409 class UnmatchedFilterException(test_exception.TestException): |
| 400 """Raised when a user specifies a filter that doesn't match any tests.""" | 410 """Raised when a user specifies a filter that doesn't match any tests.""" |
| 401 | 411 |
| 402 def __init__(self, test_filter): | 412 def __init__(self, test_filter): |
| 403 super(UnmatchedFilterException, self).__init__( | 413 super(UnmatchedFilterException, self).__init__( |
| 404 'Test filter "%s" matched no tests.' % test_filter) | 414 'Test filter "%s" matched no tests.' % test_filter) |
| 405 | 415 |
| 406 | 416 |
| 407 def GetTestName(test, sep='#'): | 417 def GetTestName(test, sep='#'): |
| 408 """Gets the name of the given test. | 418 """Gets the name of the given test. |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 449 self._additional_apks = [] | 459 self._additional_apks = [] |
| 450 self._apk_under_test = None | 460 self._apk_under_test = None |
| 451 self._apk_under_test_incremental_install_script = None | 461 self._apk_under_test_incremental_install_script = None |
| 452 self._package_info = None | 462 self._package_info = None |
| 453 self._suite = None | 463 self._suite = None |
| 454 self._test_apk = None | 464 self._test_apk = None |
| 455 self._test_apk_incremental_install_script = None | 465 self._test_apk_incremental_install_script = None |
| 456 self._test_jar = None | 466 self._test_jar = None |
| 457 self._test_package = None | 467 self._test_package = None |
| 458 self._test_runner = None | 468 self._test_runner = None |
| 469 self._test_runner_junit4 = None |
| 459 self._test_support_apk = None | 470 self._test_support_apk = None |
| 460 self._initializeApkAttributes(args, error_func) | 471 self._initializeApkAttributes(args, error_func) |
| 461 | 472 |
| 462 self._data_deps = None | 473 self._data_deps = None |
| 463 self._data_deps_delegate = None | 474 self._data_deps_delegate = None |
| 464 self._runtime_deps_path = None | 475 self._runtime_deps_path = None |
| 465 self._initializeDataDependencyAttributes(args, data_deps_delegate) | 476 self._initializeDataDependencyAttributes(args, data_deps_delegate) |
| 466 | 477 |
| 467 self._annotations = None | 478 self._annotations = None |
| 468 self._excluded_annotations = None | 479 self._excluded_annotations = None |
| (...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 542 if not os.path.exists(self._test_apk.path): | 553 if not os.path.exists(self._test_apk.path): |
| 543 error_func('Unable to find test APK: %s' % self._test_apk.path) | 554 error_func('Unable to find test APK: %s' % self._test_apk.path) |
| 544 if not self._test_jar: | 555 if not self._test_jar: |
| 545 logging.warning('Test jar not specified. Test runner will not have ' | 556 logging.warning('Test jar not specified. Test runner will not have ' |
| 546 'Java annotation info available. May not handle test ' | 557 'Java annotation info available. May not handle test ' |
| 547 'timeouts correctly.') | 558 'timeouts correctly.') |
| 548 elif not os.path.exists(self._test_jar): | 559 elif not os.path.exists(self._test_jar): |
| 549 error_func('Unable to find test JAR: %s' % self._test_jar) | 560 error_func('Unable to find test JAR: %s' % self._test_jar) |
| 550 | 561 |
| 551 self._test_package = self._test_apk.GetPackageName() | 562 self._test_package = self._test_apk.GetPackageName() |
| 552 self._test_runner = self._test_apk.GetInstrumentationName() | 563 all_instrumentations = self._test_apk.GetAllInstrumentations() |
| 564 junit3_runners = [ |
| 565 x for x in all_instrumentations if ('true' not in x.get( |
| 566 'chromium-junit4', ''))] |
| 567 junit4_runners = [ |
| 568 x for x in all_instrumentations if ('true' in x.get( |
| 569 'chromium-junit4', ''))] |
| 570 |
| 571 if len(junit3_runners) > 1: |
| 572 logging.warning('This test apk has more than one JUnit3 instrumentation') |
| 573 if len(junit4_runners) > 1: |
| 574 logging.warning('This test apk has more than one JUnit4 instrumentation') |
| 575 |
| 576 self._test_runner = ( |
| 577 junit3_runners[0]['android:name'] if junit3_runners else |
| 578 self.test_apk.GetInstrumentationName()) |
| 579 self._test_runner_junit4 = ( |
| 580 junit4_runners[0]['android:name'] if junit4_runners else None) |
| 553 | 581 |
| 554 self._package_info = None | 582 self._package_info = None |
| 555 if self._apk_under_test: | 583 if self._apk_under_test: |
| 556 package_under_test = self._apk_under_test.GetPackageName() | 584 package_under_test = self._apk_under_test.GetPackageName() |
| 557 for package_info in constants.PACKAGE_INFO.itervalues(): | 585 for package_info in constants.PACKAGE_INFO.itervalues(): |
| 558 if package_under_test == package_info.package: | 586 if package_under_test == package_info.package: |
| 559 self._package_info = package_info | 587 self._package_info = package_info |
| 560 break | 588 break |
| 561 if not self._package_info: | 589 if not self._package_info: |
| 562 logging.warning('Unable to find package info for %s', self._test_package) | 590 logging.warning('Unable to find package info for %s', self._test_package) |
| (...skipping 154 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 717 | 745 |
| 718 @property | 746 @property |
| 719 def test_package(self): | 747 def test_package(self): |
| 720 return self._test_package | 748 return self._test_package |
| 721 | 749 |
| 722 @property | 750 @property |
| 723 def test_runner(self): | 751 def test_runner(self): |
| 724 return self._test_runner | 752 return self._test_runner |
| 725 | 753 |
| 726 @property | 754 @property |
| 755 def test_runner_junit4(self): |
| 756 return self._test_runner_junit4 |
| 757 |
| 758 @property |
| 727 def timeout_scale(self): | 759 def timeout_scale(self): |
| 728 return self._timeout_scale | 760 return self._timeout_scale |
| 729 | 761 |
| 730 #override | 762 #override |
| 731 def TestType(self): | 763 def TestType(self): |
| 732 return 'instrumentation' | 764 return 'instrumentation' |
| 733 | 765 |
| 734 #override | 766 #override |
| 735 def SetUp(self): | 767 def SetUp(self): |
| 736 self._data_deps.extend( | 768 self._data_deps.extend( |
| 737 self._data_deps_delegate(self._runtime_deps_path)) | 769 self._data_deps_delegate(self._runtime_deps_path)) |
| 738 | 770 |
| 739 def GetDataDependencies(self): | 771 def GetDataDependencies(self): |
| 740 return self._data_deps | 772 return self._data_deps |
| 741 | 773 |
| 742 def GetTests(self): | 774 def GetTests(self): |
| 743 if self.test_jar: | 775 if self.test_jar: |
| 744 tests = GetAllTestsFromJar(self.test_jar) | 776 tests = GetAllTestsFromJar(self.test_jar) |
| 745 else: | 777 else: |
| 746 tests = GetAllTestsFromApk(self.test_apk.path) | 778 tests = GetAllTestsFromApk(self.test_apk.path) |
| 747 inflated_tests = self._ParametrizeTestsWithFlags(self._InflateTests(tests)) | 779 inflated_tests = self._ParametrizeTestsWithFlags(self._InflateTests(tests)) |
| 780 if self._test_runner_junit4 is None and any( |
| 781 t['is_junit4'] for t in inflated_tests): |
| 782 raise MissingJUnit4RunnerException() |
| 748 filtered_tests = FilterTests( | 783 filtered_tests = FilterTests( |
| 749 inflated_tests, self._test_filter, self._annotations, | 784 inflated_tests, self._test_filter, self._annotations, |
| 750 self._excluded_annotations) | 785 self._excluded_annotations) |
| 751 if self._test_filter and not filtered_tests: | 786 if self._test_filter and not filtered_tests: |
| 752 for t in inflated_tests: | 787 for t in inflated_tests: |
| 753 logging.debug(' %s', GetUniqueTestName(t)) | 788 logging.debug(' %s', GetUniqueTestName(t)) |
| 754 raise UnmatchedFilterException(self._test_filter) | 789 raise UnmatchedFilterException(self._test_filter) |
| 755 return filtered_tests | 790 return filtered_tests |
| 756 | 791 |
| 757 # pylint: disable=no-self-use | 792 # pylint: disable=no-self-use |
| 758 def _InflateTests(self, tests): | 793 def _InflateTests(self, tests): |
| 759 inflated_tests = [] | 794 inflated_tests = [] |
| 760 for c in tests: | 795 for c in tests: |
| 761 for m in c['methods']: | 796 for m in c['methods']: |
| 762 a = dict(c['annotations']) | 797 a = dict(c['annotations']) |
| 763 a.update(m['annotations']) | 798 a.update(m['annotations']) |
| 764 inflated_tests.append({ | 799 inflated_tests.append({ |
| 765 'class': c['class'], | 800 'class': c['class'], |
| 766 'method': m['method'], | 801 'method': m['method'], |
| 767 'annotations': a, | 802 'annotations': a, |
| 803 'is_junit4': c['superclass'] == 'java.lang.Object' |
| 768 }) | 804 }) |
| 769 return inflated_tests | 805 return inflated_tests |
| 770 | 806 |
| 771 def _ParametrizeTestsWithFlags(self, tests): | 807 def _ParametrizeTestsWithFlags(self, tests): |
| 772 new_tests = [] | 808 new_tests = [] |
| 773 for t in tests: | 809 for t in tests: |
| 774 parameters = ParseCommandLineFlagParameters(t['annotations']) | 810 parameters = ParseCommandLineFlagParameters(t['annotations']) |
| 775 if parameters: | 811 if parameters: |
| 776 t['flags'] = parameters[0] | 812 t['flags'] = parameters[0] |
| 777 for p in parameters[1:]: | 813 for p in parameters[1:]: |
| (...skipping 25 matching lines...) Expand all Loading... |
| 803 | 839 |
| 804 @staticmethod | 840 @staticmethod |
| 805 def GenerateTestResults( | 841 def GenerateTestResults( |
| 806 result_code, result_bundle, statuses, start_ms, duration_ms): | 842 result_code, result_bundle, statuses, start_ms, duration_ms): |
| 807 return GenerateTestResults(result_code, result_bundle, statuses, | 843 return GenerateTestResults(result_code, result_bundle, statuses, |
| 808 start_ms, duration_ms) | 844 start_ms, duration_ms) |
| 809 | 845 |
| 810 #override | 846 #override |
| 811 def TearDown(self): | 847 def TearDown(self): |
| 812 pass | 848 pass |
| OLD | NEW |