Chromium Code Reviews| 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 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 43 _EXTRA_DRIVER_TARGET_PACKAGE = ( | 43 _EXTRA_DRIVER_TARGET_PACKAGE = ( |
| 44 'org.chromium.test.driver.OnDeviceInstrumentationDriver.TargetPackage') | 44 'org.chromium.test.driver.OnDeviceInstrumentationDriver.TargetPackage') |
| 45 _EXTRA_DRIVER_TARGET_CLASS = ( | 45 _EXTRA_DRIVER_TARGET_CLASS = ( |
| 46 'org.chromium.test.driver.OnDeviceInstrumentationDriver.TargetClass') | 46 'org.chromium.test.driver.OnDeviceInstrumentationDriver.TargetClass') |
| 47 _EXTRA_TIMEOUT_SCALE = ( | 47 _EXTRA_TIMEOUT_SCALE = ( |
| 48 'org.chromium.test.driver.OnDeviceInstrumentationDriver.TimeoutScale') | 48 'org.chromium.test.driver.OnDeviceInstrumentationDriver.TimeoutScale') |
| 49 | 49 |
| 50 _PARAMETERIZED_TEST_ANNOTATION = 'ParameterizedTest' | 50 _PARAMETERIZED_TEST_ANNOTATION = 'ParameterizedTest' |
| 51 _PARAMETERIZED_TEST_SET_ANNOTATION = 'ParameterizedTest$Set' | 51 _PARAMETERIZED_TEST_SET_ANNOTATION = 'ParameterizedTest$Set' |
| 52 _NATIVE_CRASH_RE = re.compile('(process|native) crash', re.IGNORECASE) | 52 _NATIVE_CRASH_RE = re.compile('(process|native) crash', re.IGNORECASE) |
| 53 _CMDLINE_NAME_SEGMENT_RE = re.compile( | |
| 54 r' with(?:out)? \{[^\}]*\}') | |
|
mikecase (-- gone --)
2016/11/12 01:55:08
so, I could easily be wrong, but with this work on
jbudorick
2016/11/14 16:26:32
Yes, it should: https://docs.python.org/2/library/
| |
| 53 _PICKLE_FORMAT_VERSION = 10 | 55 _PICKLE_FORMAT_VERSION = 10 |
| 54 | 56 |
| 55 | 57 |
| 56 class MissingSizeAnnotationError(test_exception.TestException): | 58 class MissingSizeAnnotationError(test_exception.TestException): |
| 57 def __init__(self, class_name): | 59 def __init__(self, class_name): |
| 58 super(MissingSizeAnnotationError, self).__init__(class_name + | 60 super(MissingSizeAnnotationError, self).__init__(class_name + |
| 59 ': Test method is missing required size annotation. Add one of: ' + | 61 ': Test method is missing required size annotation. Add one of: ' + |
| 60 ', '.join('@' + a for a in _VALID_ANNOTATIONS)) | 62 ', '.join('@' + a for a in _VALID_ANNOTATIONS)) |
| 61 | 63 |
| 62 | 64 |
| (...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 213 tests: a list of tests. e.g. [ | 215 tests: a list of tests. e.g. [ |
| 214 {'annotations": {}, 'class': 'com.example.TestA', 'methods':[]}, | 216 {'annotations": {}, 'class': 'com.example.TestA', 'methods':[]}, |
| 215 {'annotations": {}, 'class': 'com.example.TestB', 'methods':[]}] | 217 {'annotations": {}, 'class': 'com.example.TestB', 'methods':[]}] |
| 216 test_filter: googletest-style filter string. | 218 test_filter: googletest-style filter string. |
| 217 annotations: a dict of wanted annotations for test methods. | 219 annotations: a dict of wanted annotations for test methods. |
| 218 exclude_annotations: a dict of annotations to exclude. | 220 exclude_annotations: a dict of annotations to exclude. |
| 219 | 221 |
| 220 Return: | 222 Return: |
| 221 A list of filtered tests | 223 A list of filtered tests |
| 222 """ | 224 """ |
| 223 def gtest_filter(c, m): | 225 def gtest_filter(t): |
| 224 if not test_filter: | 226 if not test_filter: |
| 225 return True | 227 return True |
| 226 # Allow fully-qualified name as well as an omitted package. | 228 # Allow fully-qualified name as well as an omitted package. |
| 227 names = ['%s.%s' % (c['class'], m['method']), | 229 unqualified_class_test = { |
|
mikecase (-- gone --)
2016/11/12 01:55:09
unqualified_class_test is just like package-name-l
jbudorick
2016/11/14 16:26:32
Acknowledged.
| |
| 228 '%s.%s' % (c['class'].split('.')[-1], m['method'])] | 230 'class': t['class'].split('.')[-1], |
| 231 'method': t['method'] | |
| 232 } | |
| 233 names = [ | |
| 234 GetTestName(t, sep='.'), | |
| 235 GetTestName(unqualified_class_test, sep='.'), | |
| 236 GetUniqueTestName(t, sep='.') | |
| 237 ] | |
|
mikecase (-- gone --)
2016/11/12 01:55:09
I like this solution. :D
jbudorick
2016/11/14 16:26:32
Yeah, it's a bit more readable this way.
| |
| 229 return unittest_util.FilterTestNames(names, test_filter) | 238 return unittest_util.FilterTestNames(names, test_filter) |
| 230 | 239 |
| 231 def annotation_filter(all_annotations): | 240 def annotation_filter(all_annotations): |
| 232 if not annotations: | 241 if not annotations: |
| 233 return True | 242 return True |
| 234 return any_annotation_matches(annotations, all_annotations) | 243 return any_annotation_matches(annotations, all_annotations) |
| 235 | 244 |
| 236 def excluded_annotation_filter(all_annotations): | 245 def excluded_annotation_filter(all_annotations): |
| 237 if not excluded_annotations: | 246 if not excluded_annotations: |
| 238 return True | 247 return True |
| 239 return not any_annotation_matches(excluded_annotations, | 248 return not any_annotation_matches(excluded_annotations, |
| 240 all_annotations) | 249 all_annotations) |
| 241 | 250 |
| 242 def any_annotation_matches(filter_annotations, all_annotations): | 251 def any_annotation_matches(filter_annotations, all_annotations): |
| 243 return any( | 252 return any( |
| 244 ak in all_annotations | 253 ak in all_annotations |
| 245 and annotation_value_matches(av, all_annotations[ak]) | 254 and annotation_value_matches(av, all_annotations[ak]) |
| 246 for ak, av in filter_annotations) | 255 for ak, av in filter_annotations) |
| 247 | 256 |
| 248 def annotation_value_matches(filter_av, av): | 257 def annotation_value_matches(filter_av, av): |
| 249 if filter_av is None: | 258 if filter_av is None: |
| 250 return True | 259 return True |
| 251 elif isinstance(av, dict): | 260 elif isinstance(av, dict): |
| 252 return filter_av in av['value'] | 261 return filter_av in av['value'] |
| 253 elif isinstance(av, list): | 262 elif isinstance(av, list): |
| 254 return filter_av in av | 263 return filter_av in av |
| 255 return filter_av == av | 264 return filter_av == av |
| 256 | 265 |
| 257 filtered_classes = [] | 266 filtered_tests = [] |
| 258 for c in tests: | 267 for t in tests: |
| 259 filtered_methods = [] | 268 # Gtest filtering |
| 260 for m in c['methods']: | 269 if not gtest_filter(t): |
| 261 # Gtest filtering | 270 continue |
| 262 if not gtest_filter(c, m): | |
| 263 continue | |
| 264 | 271 |
| 265 all_annotations = dict(c['annotations']) | 272 # Enforce that all tests declare their size. |
| 266 all_annotations.update(m['annotations']) | 273 if not any(a in _VALID_ANNOTATIONS for a in t['annotations']): |
| 274 raise MissingSizeAnnotationError(GetTestName(t)) | |
| 267 | 275 |
| 268 # Enforce that all tests declare their size. | 276 if (not annotation_filter(t['annotations']) |
| 269 if not any(a in _VALID_ANNOTATIONS for a in all_annotations): | 277 or not excluded_annotation_filter(t['annotations'])): |
| 270 raise MissingSizeAnnotationError('%s.%s' % (c['class'], m['method'])) | 278 continue |
| 271 | 279 |
| 272 if (not annotation_filter(all_annotations) | 280 filtered_tests.append(t) |
| 273 or not excluded_annotation_filter(all_annotations)): | |
| 274 continue | |
| 275 | 281 |
| 276 filtered_methods.append(m) | 282 return filtered_tests |
| 277 | 283 |
| 278 if filtered_methods: | |
| 279 filtered_class = dict(c) | |
| 280 filtered_class['methods'] = filtered_methods | |
| 281 filtered_classes.append(filtered_class) | |
| 282 | |
| 283 return filtered_classes | |
| 284 | 284 |
| 285 def GetAllTests(test_jar): | 285 def GetAllTests(test_jar): |
| 286 pickle_path = '%s-proguard.pickle' % test_jar | 286 pickle_path = '%s-proguard.pickle' % test_jar |
| 287 try: | 287 try: |
| 288 tests = _GetTestsFromPickle(pickle_path, test_jar) | 288 tests = _GetTestsFromPickle(pickle_path, test_jar) |
| 289 except ProguardPickleException as e: | 289 except ProguardPickleException as e: |
| 290 logging.info('Could not get tests from pickle: %s', e) | 290 logging.info('Could not get tests from pickle: %s', e) |
| 291 logging.info('Getting tests from JAR via proguard.') | 291 logging.info('Getting tests from JAR via proguard.') |
| 292 tests = _GetTestsFromProguard(test_jar) | 292 tests = _GetTestsFromProguard(test_jar) |
| 293 _SaveTestsToPickle(pickle_path, test_jar, tests) | 293 _SaveTestsToPickle(pickle_path, test_jar, tests) |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 354 | 354 |
| 355 | 355 |
| 356 class UnmatchedFilterException(test_exception.TestException): | 356 class UnmatchedFilterException(test_exception.TestException): |
| 357 """Raised when a user specifies a filter that doesn't match any tests.""" | 357 """Raised when a user specifies a filter that doesn't match any tests.""" |
| 358 | 358 |
| 359 def __init__(self, test_filter): | 359 def __init__(self, test_filter): |
| 360 super(UnmatchedFilterException, self).__init__( | 360 super(UnmatchedFilterException, self).__init__( |
| 361 'Test filter "%s" matched no tests.' % test_filter) | 361 'Test filter "%s" matched no tests.' % test_filter) |
| 362 | 362 |
| 363 | 363 |
| 364 def GetTestName(test, sep='#'): | |
| 365 """Gets the name of the given test. | |
| 366 | |
| 367 Note that this may return the same name for more than one test, e.g. if a | |
| 368 test is being run multiple times with different parameters. | |
| 369 | |
| 370 Args: | |
| 371 test: the instrumentation test dict. | |
| 372 sep: the character(s) that should join the class name and the method name. | |
| 373 Returns: | |
| 374 The test name as a string. | |
| 375 """ | |
| 376 return '%s%s%s' % (test['class'], sep, test['method']) | |
| 377 | |
| 378 | |
| 379 def GetUniqueTestName(test, sep='#'): | |
| 380 """Gets the unique name of the given test. | |
| 381 | |
| 382 This will include text to disambiguate between tests for which GetTestName | |
| 383 would return the same name. | |
| 384 | |
| 385 Args: | |
| 386 test: the instrumentation test dict. | |
| 387 sep: the character(s) that should join the class name and the method name. | |
| 388 Returns: | |
| 389 The unique test name as a string. | |
| 390 """ | |
| 391 display_name = GetTestName(test, sep=sep) | |
| 392 if 'flags' in test: | |
| 393 flags = test['flags'] | |
| 394 if flags.add: | |
| 395 display_name = '%s with {%s}' % (display_name, ' '.join(flags.add)) | |
| 396 if flags.remove: | |
| 397 display_name = '%s without {%s}' % (display_name, ' '.join(flags.remove)) | |
| 398 return display_name | |
| 399 | |
| 400 | |
| 364 class InstrumentationTestInstance(test_instance.TestInstance): | 401 class InstrumentationTestInstance(test_instance.TestInstance): |
| 365 | 402 |
| 366 def __init__(self, args, isolate_delegate, error_func): | 403 def __init__(self, args, isolate_delegate, error_func): |
| 367 super(InstrumentationTestInstance, self).__init__() | 404 super(InstrumentationTestInstance, self).__init__() |
| 368 | 405 |
| 369 self._additional_apks = [] | 406 self._additional_apks = [] |
| 370 self._apk_under_test = None | 407 self._apk_under_test = None |
| 371 self._apk_under_test_incremental_install_script = None | 408 self._apk_under_test_incremental_install_script = None |
| 372 self._package_info = None | 409 self._package_info = None |
| 373 self._suite = None | 410 self._suite = None |
| (...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 496 self._isolated_abs_path = os.path.join( | 533 self._isolated_abs_path = os.path.join( |
| 497 constants.GetOutDirectory(), '%s.isolated' % self._test_package) | 534 constants.GetOutDirectory(), '%s.isolated' % self._test_package) |
| 498 else: | 535 else: |
| 499 self._isolate_delegate = None | 536 self._isolate_delegate = None |
| 500 | 537 |
| 501 if not self._isolate_delegate: | 538 if not self._isolate_delegate: |
| 502 logging.warning('No data dependencies will be pushed.') | 539 logging.warning('No data dependencies will be pushed.') |
| 503 | 540 |
| 504 def _initializeTestFilterAttributes(self, args): | 541 def _initializeTestFilterAttributes(self, args): |
| 505 if args.test_filter: | 542 if args.test_filter: |
| 506 self._test_filter = args.test_filter.replace('#', '.') | 543 self._test_filter = _CMDLINE_NAME_SEGMENT_RE.sub( |
| 544 '', args.test_filter.replace('#', '.')) | |
| 507 | 545 |
| 508 def annotation_element(a): | 546 def annotation_element(a): |
| 509 a = a.split('=', 1) | 547 a = a.split('=', 1) |
| 510 return (a[0], a[1] if len(a) == 2 else None) | 548 return (a[0], a[1] if len(a) == 2 else None) |
| 511 | 549 |
| 512 if args.annotation_str: | 550 if args.annotation_str: |
| 513 self._annotations = [ | 551 self._annotations = [ |
| 514 annotation_element(a) for a in args.annotation_str.split(',')] | 552 annotation_element(a) for a in args.annotation_str.split(',')] |
| 515 elif not self._test_filter: | 553 elif not self._test_filter: |
| 516 self._annotations = [ | 554 self._annotations = [ |
| (...skipping 137 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 654 self._isolate_delegate.Remap( | 692 self._isolate_delegate.Remap( |
| 655 self._isolate_abs_path, self._isolated_abs_path) | 693 self._isolate_abs_path, self._isolated_abs_path) |
| 656 self._isolate_delegate.MoveOutputDeps() | 694 self._isolate_delegate.MoveOutputDeps() |
| 657 self._data_deps.extend([(self._isolate_delegate.isolate_deps_dir, None)]) | 695 self._data_deps.extend([(self._isolate_delegate.isolate_deps_dir, None)]) |
| 658 | 696 |
| 659 def GetDataDependencies(self): | 697 def GetDataDependencies(self): |
| 660 return self._data_deps | 698 return self._data_deps |
| 661 | 699 |
| 662 def GetTests(self): | 700 def GetTests(self): |
| 663 tests = GetAllTests(self.test_jar) | 701 tests = GetAllTests(self.test_jar) |
| 702 inflated_tests = self._ParametrizeTestsWithFlags(self._InflateTests(tests)) | |
| 664 filtered_tests = FilterTests( | 703 filtered_tests = FilterTests( |
| 665 tests, self._test_filter, self._annotations, self._excluded_annotations) | 704 inflated_tests, self._test_filter, self._annotations, |
| 705 self._excluded_annotations) | |
| 666 if self._test_filter and not filtered_tests: | 706 if self._test_filter and not filtered_tests: |
| 707 for t in inflated_tests: | |
| 708 logging.debug(' %s', GetUniqueTestName(t)) | |
| 667 raise UnmatchedFilterException(self._test_filter) | 709 raise UnmatchedFilterException(self._test_filter) |
| 668 return self._ParametrizeTestsWithFlags(self._InflateTests(filtered_tests)) | 710 return filtered_tests |
| 669 | 711 |
| 670 # pylint: disable=no-self-use | 712 # pylint: disable=no-self-use |
| 671 def _InflateTests(self, tests): | 713 def _InflateTests(self, tests): |
| 672 inflated_tests = [] | 714 inflated_tests = [] |
| 673 for c in tests: | 715 for c in tests: |
| 674 for m in c['methods']: | 716 for m in c['methods']: |
| 675 a = dict(c['annotations']) | 717 a = dict(c['annotations']) |
| 676 a.update(m['annotations']) | 718 a.update(m['annotations']) |
| 677 inflated_tests.append({ | 719 inflated_tests.append({ |
| 678 'class': c['class'], | 720 'class': c['class'], |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 717 @staticmethod | 759 @staticmethod |
| 718 def GenerateTestResults( | 760 def GenerateTestResults( |
| 719 result_code, result_bundle, statuses, start_ms, duration_ms): | 761 result_code, result_bundle, statuses, start_ms, duration_ms): |
| 720 return GenerateTestResults(result_code, result_bundle, statuses, | 762 return GenerateTestResults(result_code, result_bundle, statuses, |
| 721 start_ms, duration_ms) | 763 start_ms, duration_ms) |
| 722 | 764 |
| 723 #override | 765 #override |
| 724 def TearDown(self): | 766 def TearDown(self): |
| 725 if self._isolate_delegate: | 767 if self._isolate_delegate: |
| 726 self._isolate_delegate.Clear() | 768 self._isolate_delegate.Clear() |
| OLD | NEW |