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 119 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 130 crashed = (result_code == _ACTIVITY_RESULT_CANCELED | 130 crashed = (result_code == _ACTIVITY_RESULT_CANCELED |
| 131 and any(_NATIVE_CRASH_RE.search(l) | 131 and any(_NATIVE_CRASH_RE.search(l) |
| 132 for l in result_bundle.itervalues())) | 132 for l in result_bundle.itervalues())) |
| 133 if crashed: | 133 if crashed: |
| 134 current_result.SetType(base_test_result.ResultType.CRASH) | 134 current_result.SetType(base_test_result.ResultType.CRASH) |
| 135 | 135 |
| 136 results.append(current_result) | 136 results.append(current_result) |
| 137 | 137 |
| 138 return results | 138 return results |
| 139 | 139 |
| 140 | |
|
perezju
2016/04/12 10:29:02
nit: bring that blank line back
Yoland Yan(Google)
2016/04/13 01:03:10
Done.
| |
| 141 def ParseCommandLineFlagParameters(annotations): | 140 def ParseCommandLineFlagParameters(annotations): |
| 142 """Determines whether the test is parameterized to be run with different | 141 """Determines whether the test is parameterized to be run with different |
| 143 command-line flags. | 142 command-line flags. |
| 144 | 143 |
| 145 Args: | 144 Args: |
| 146 annotations: The annotations of the test. | 145 annotations: The annotations of the test. |
| 147 | 146 |
| 148 Returns: | 147 Returns: |
| 149 If the test is parameterized, returns a list of named tuples | 148 If the test is parameterized, returns a list of named tuples |
| 150 with lists of flags, e.g.: | 149 with lists of flags, e.g.: |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 179 if p['tag'] == _COMMAND_LINE_PARAMETER: | 178 if p['tag'] == _COMMAND_LINE_PARAMETER: |
| 180 to_add = [] | 179 to_add = [] |
| 181 to_remove = [] | 180 to_remove = [] |
| 182 for a in p.get('arguments', []): | 181 for a in p.get('arguments', []): |
| 183 if a['name'] == 'add': | 182 if a['name'] == 'add': |
| 184 to_add = ['--%s' % f for f in a['stringArray']] | 183 to_add = ['--%s' % f for f in a['stringArray']] |
| 185 elif a['name'] == 'remove': | 184 elif a['name'] == 'remove': |
| 186 to_remove = ['--%s' % f for f in a['stringArray']] | 185 to_remove = ['--%s' % f for f in a['stringArray']] |
| 187 result.append(ParamsTuple(to_add, to_remove)) | 186 result.append(ParamsTuple(to_add, to_remove)) |
| 188 return result if result else None | 187 return result if result else None |
| 189 | 188 |
|
perezju
2016/04/12 10:29:03
nit: two blank lines between module level elements
Yoland Yan(Google)
2016/04/13 01:03:11
Done.
| |
| 189 def TotalTestCount(test_jar): | |
| 190 """Return the total amount of tests in a test apk base on proguard dump""" | |
| 191 total_test_count = 0 | |
| 192 tests = _GetAllTests(test_jar) | |
| 193 for c in tests: | |
| 194 total_test_count += len(c['methods']) | |
| 195 return total_test_count | |
|
perezju
2016/04/12 10:29:03
nit: return sum(len(c['methods']) for c in _GetAll
Yoland Yan(Google)
2016/04/13 01:03:10
Done.
| |
| 196 | |
| 197 def GetFilteredTests(test_jar, test_filter, annotations, exclude_annotations): | |
|
perezju
2016/04/12 10:29:03
make test_filter, annotations, and exclude_annotat
Yoland Yan(Google)
2016/04/13 01:03:10
No longer an issue, but changed the now public Fil
| |
| 198 """Get a list of filtered tests in a test apk jar file. | |
| 199 | |
| 200 Args: | |
| 201 test_jar: the full path to the test apk jar file. | |
| 202 test_filter: googletest-style filter string. | |
| 203 annotations: a dict of wanted annotations for test methods. | |
| 204 exclude_annotations: a dict of annotations to exclude. | |
|
perezju
2016/04/12 10:29:03
Why are these dicts? How do they look like?
Yoland Yan(Google)
2016/04/13 01:03:11
No longer an issue here in the next patch because
perezju
2016/04/25 12:45:07
I think that's a bit awkward to use. Probably the
| |
| 205 | |
| 206 Return: | |
| 207 A list of filtered tests | |
|
perezju
2016/04/12 10:29:03
Add an example of what each such "test" looks like
Yoland Yan(Google)
2016/04/13 01:03:11
Done.
| |
| 208 """ | |
|
perezju
2016/04/12 10:29:03
nit: move those quotes three spaces to the left.
Yoland Yan(Google)
2016/04/13 01:03:10
Done.
| |
| 209 tests = _GetAllTests(test_jar) | |
| 210 filtered_tests = _FilterTests(tests, test_filter, annotations, | |
| 211 exclude_annotations) | |
|
perezju
2016/04/12 10:29:03
nit: one space to the right
Yoland Yan(Google)
2016/04/13 01:03:10
Done.
| |
| 212 return filtered_tests | |
| 213 | |
| 214 def _GetTestsFromPickle(pickle_path, jar_path): | |
| 215 if not os.path.exists(pickle_path): | |
| 216 raise ProguardPickleException('%s does not exist.' % pickle_path) | |
| 217 if os.path.getmtime(pickle_path) <= os.path.getmtime(jar_path): | |
| 218 raise ProguardPickleException( | |
| 219 '%s newer than %s.' % (jar_path, pickle_path)) | |
| 220 | |
| 221 with open(pickle_path, 'r') as pickle_file: | |
| 222 pickle_data = pickle.loads(pickle_file.read()) | |
| 223 jar_md5 = md5sum.CalculateHostMd5Sums(jar_path)[jar_path] | |
| 224 | |
| 225 try: | |
| 226 if pickle_data['VERSION'] != _PICKLE_FORMAT_VERSION: | |
| 227 raise ProguardPickleException('PICKLE_FORMAT_VERSION has changed.') | |
| 228 if pickle_data['JAR_MD5SUM'] != jar_md5: | |
| 229 raise ProguardPickleException('JAR file MD5 sum differs.') | |
| 230 return pickle_data['TEST_METHODS'] | |
| 231 except TypeError as e: | |
|
perezju
2016/04/12 10:29:03
what are you hoping to catch with TypeError?
Yoland Yan(Google)
2016/04/13 01:03:10
If from before, I think might have been some calcu
| |
| 232 logging.error(pickle_data) | |
| 233 raise ProguardPickleException(str(e)) | |
| 234 | |
| 235 # pylint: disable=no-self-use | |
|
perezju
2016/04/12 10:29:03
not needed anymore
Yoland Yan(Google)
2016/04/13 01:03:11
Done.
| |
| 236 def _GetTestsFromProguard(jar_path): | |
| 237 p = proguard.Dump(jar_path) | |
| 238 | |
| 239 def is_test_class(c): | |
| 240 return c['class'].endswith('Test') | |
| 241 | |
| 242 def is_test_method(m): | |
| 243 return m['method'].startswith('test') | |
| 244 | |
| 245 class_lookup = dict((c['class'], c) for c in p['classes']) | |
|
perezju
2016/04/12 10:29:03
nit: {c['class']: c for c in p['classes']}
and mo
Yoland Yan(Google)
2016/04/13 01:03:11
Done.
| |
| 246 def recursive_get_class_annotations(c): | |
|
perezju
2016/04/12 10:29:03
nit: recursive_class_annotations should be a fine
Yoland Yan(Google)
2016/04/13 01:03:10
Done.
| |
| 247 s = c['superclass'] | |
| 248 if s in class_lookup: | |
| 249 a = recursive_get_class_annotations(class_lookup[s]) | |
| 250 else: | |
| 251 a = {} | |
| 252 a.update(c['annotations']) | |
|
perezju
2016/04/12 10:29:02
you could write this non-recursively:
a = {}
Yoland Yan(Google)
2016/04/13 02:18:43
I am not sure whether order is important, will loo
perezju
2016/04/25 12:45:07
It's fine, leave it as is.
| |
| 253 return a | |
| 254 | |
| 255 def stripped_test_class(c): | |
| 256 return { | |
| 257 'class': c['class'], | |
| 258 'annotations': recursive_get_class_annotations(c), | |
| 259 'methods': [m for m in c['methods'] if is_test_method(m)], | |
| 260 } | |
| 261 | |
| 262 return [stripped_test_class(c) for c in p['classes'] | |
| 263 if is_test_class(c)] | |
| 264 | |
| 265 def _SaveTestsToPickle(pickle_path, jar_path, tests): | |
| 266 jar_md5 = md5sum.CalculateHostMd5Sums(jar_path)[jar_path] | |
| 267 pickle_data = { | |
| 268 'VERSION': _PICKLE_FORMAT_VERSION, | |
| 269 'JAR_MD5SUM': jar_md5, | |
| 270 'TEST_METHODS': tests, | |
| 271 } | |
| 272 with open(pickle_path, 'w') as pickle_file: | |
| 273 pickle.dump(pickle_data, pickle_file) | |
| 274 | |
| 275 def _FilterTests(tests, test_filter, annotations, excluded_annotations): | |
| 276 | |
| 277 def gtest_filter(c, m): | |
| 278 t = ['%s.%s' % (c['class'].split('.')[-1], m['method'])] | |
| 279 return (not test_filter | |
| 280 or unittest_util.FilterTestNames(t, test_filter)) | |
| 281 | |
| 282 def annotation_filter(all_annotations): | |
| 283 if not annotations: | |
| 284 return True | |
| 285 return any_annotation_matches(annotations, all_annotations) | |
|
perezju
2016/04/12 10:29:02
Not particularly proud of this suggestion, but you
Yoland Yan(Google)
2016/04/13 01:03:11
I did a timeit speed run, and the difference isn't
perezju
2016/04/13 09:53:46
Acknowledged.
Yoland Yan(Google)
2016/04/22 17:50:26
Done.
| |
| 286 | |
| 287 def excluded_annotation_filter(all_annotations): | |
| 288 if not excluded_annotations: | |
| 289 return True | |
| 290 return not any_annotation_matches(excluded_annotations, | |
| 291 all_annotations) | |
| 292 | |
| 293 def any_annotation_matches(annotations, all_annotations): | |
| 294 return any( | |
| 295 ak in all_annotations and (av is None or av == all_annotations[ak]) | |
| 296 for ak, av in annotations.iteritems()) | |
| 297 | |
| 298 filtered_classes = [] | |
| 299 for c in tests: | |
| 300 filtered_methods = [] | |
| 301 for m in c['methods']: | |
| 302 # Gtest filtering | |
| 303 if not gtest_filter(c, m): | |
| 304 continue | |
| 305 | |
| 306 all_annotations = dict(c['annotations']) | |
| 307 all_annotations.update(m['annotations']) | |
| 308 if (not annotation_filter(all_annotations) | |
| 309 or not excluded_annotation_filter(all_annotations)): | |
| 310 continue | |
| 311 | |
| 312 filtered_methods.append(m) | |
| 313 | |
| 314 if filtered_methods: | |
| 315 filtered_class = dict(c) | |
| 316 filtered_class['methods'] = filtered_methods | |
| 317 filtered_classes.append(filtered_class) | |
| 318 | |
| 319 return filtered_classes | |
| 320 | |
| 321 def _GetAllTests(test_jar): | |
| 322 pickle_path = '%s-proguard.pickle' % test_jar | |
| 323 try: | |
| 324 tests = _GetTestsFromPickle(pickle_path, test_jar) | |
| 325 except ProguardPickleException as e: | |
| 326 logging.info('Getting tests from JAR via proguard. (%s)', str(e)) | |
|
perezju
2016/04/12 10:29:03
maybe:
logging.info('Could not get tests from
Yoland Yan(Google)
2016/04/13 01:03:10
Done.
| |
| 327 tests = _GetTestsFromProguard(test_jar) | |
| 328 _SaveTestsToPickle(pickle_path, test_jar, tests) | |
| 329 return tests | |
| 330 | |
| 331 class ProguardPickleException(Exception): | |
|
perezju
2016/04/12 10:29:03
Move declarations of exceptions near the top of th
Yoland Yan(Google)
2016/04/13 01:03:10
Done.
| |
| 332 pass | |
| 190 | 333 |
| 191 class InstrumentationTestInstance(test_instance.TestInstance): | 334 class InstrumentationTestInstance(test_instance.TestInstance): |
| 192 | 335 |
| 193 def __init__(self, args, isolate_delegate, error_func): | 336 def __init__(self, args, isolate_delegate, error_func): |
| 194 super(InstrumentationTestInstance, self).__init__() | 337 super(InstrumentationTestInstance, self).__init__() |
| 195 | 338 |
| 196 self._additional_apks = [] | 339 self._additional_apks = [] |
| 197 self._apk_under_test = None | 340 self._apk_under_test = None |
| 198 self._apk_under_test_incremental_install_script = None | 341 self._apk_under_test_incremental_install_script = None |
| 199 self._package_info = None | 342 self._package_info = None |
| (...skipping 258 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 458 device_rel_path, host_rel_path = t.split(':') | 601 device_rel_path, host_rel_path = t.split(':') |
| 459 host_abs_path = os.path.join(host_paths.DIR_SOURCE_ROOT, host_rel_path) | 602 host_abs_path = os.path.join(host_paths.DIR_SOURCE_ROOT, host_rel_path) |
| 460 self._data_deps.extend( | 603 self._data_deps.extend( |
| 461 [(host_abs_path, | 604 [(host_abs_path, |
| 462 [None, 'chrome', 'test', 'data', device_rel_path])]) | 605 [None, 'chrome', 'test', 'data', device_rel_path])]) |
| 463 | 606 |
| 464 def GetDataDependencies(self): | 607 def GetDataDependencies(self): |
| 465 return self._data_deps | 608 return self._data_deps |
| 466 | 609 |
| 467 def GetTests(self): | 610 def GetTests(self): |
| 468 pickle_path = '%s-proguard.pickle' % self.test_jar | 611 filtered_tests = GetFilteredTests( |
| 469 try: | 612 self.test_jar, |
| 470 tests = self._GetTestsFromPickle(pickle_path, self.test_jar) | 613 self._test_filter, |
| 471 except self.ProguardPickleException as e: | 614 self._annotations, |
| 472 logging.info('Getting tests from JAR via proguard. (%s)', str(e)) | 615 self._excluded_annotations) |
| 473 tests = self._GetTestsFromProguard(self.test_jar) | 616 return self._ParametrizeTestsWithFlags(self._InflateTests(filtered_tests)) |
| 474 self._SaveTestsToPickle(pickle_path, self.test_jar, tests) | |
| 475 return self._ParametrizeTestsWithFlags( | |
| 476 self._InflateTests(self._FilterTests(tests))) | |
| 477 | |
| 478 class ProguardPickleException(Exception): | |
| 479 pass | |
| 480 | |
| 481 def _GetTestsFromPickle(self, pickle_path, jar_path): | |
| 482 if not os.path.exists(pickle_path): | |
| 483 raise self.ProguardPickleException('%s does not exist.' % pickle_path) | |
| 484 if os.path.getmtime(pickle_path) <= os.path.getmtime(jar_path): | |
| 485 raise self.ProguardPickleException( | |
| 486 '%s newer than %s.' % (jar_path, pickle_path)) | |
| 487 | |
| 488 with open(pickle_path, 'r') as pickle_file: | |
| 489 pickle_data = pickle.loads(pickle_file.read()) | |
| 490 jar_md5 = md5sum.CalculateHostMd5Sums(jar_path)[jar_path] | |
| 491 | |
| 492 try: | |
| 493 if pickle_data['VERSION'] != _PICKLE_FORMAT_VERSION: | |
| 494 raise self.ProguardPickleException('PICKLE_FORMAT_VERSION has changed.') | |
| 495 if pickle_data['JAR_MD5SUM'] != jar_md5: | |
| 496 raise self.ProguardPickleException('JAR file MD5 sum differs.') | |
| 497 return pickle_data['TEST_METHODS'] | |
| 498 except TypeError as e: | |
| 499 logging.error(pickle_data) | |
| 500 raise self.ProguardPickleException(str(e)) | |
| 501 | |
| 502 # pylint: disable=no-self-use | |
| 503 def _GetTestsFromProguard(self, jar_path): | |
| 504 p = proguard.Dump(jar_path) | |
| 505 | |
| 506 def is_test_class(c): | |
| 507 return c['class'].endswith('Test') | |
| 508 | |
| 509 def is_test_method(m): | |
| 510 return m['method'].startswith('test') | |
| 511 | |
| 512 class_lookup = dict((c['class'], c) for c in p['classes']) | |
| 513 def recursive_get_class_annotations(c): | |
| 514 s = c['superclass'] | |
| 515 if s in class_lookup: | |
| 516 a = recursive_get_class_annotations(class_lookup[s]) | |
| 517 else: | |
| 518 a = {} | |
| 519 a.update(c['annotations']) | |
| 520 return a | |
| 521 | |
| 522 def stripped_test_class(c): | |
| 523 return { | |
| 524 'class': c['class'], | |
| 525 'annotations': recursive_get_class_annotations(c), | |
| 526 'methods': [m for m in c['methods'] if is_test_method(m)], | |
| 527 } | |
| 528 | |
| 529 return [stripped_test_class(c) for c in p['classes'] | |
| 530 if is_test_class(c)] | |
| 531 | |
| 532 def _SaveTestsToPickle(self, pickle_path, jar_path, tests): | |
| 533 jar_md5 = md5sum.CalculateHostMd5Sums(jar_path)[jar_path] | |
| 534 pickle_data = { | |
| 535 'VERSION': _PICKLE_FORMAT_VERSION, | |
| 536 'JAR_MD5SUM': jar_md5, | |
| 537 'TEST_METHODS': tests, | |
| 538 } | |
| 539 with open(pickle_path, 'w') as pickle_file: | |
| 540 pickle.dump(pickle_data, pickle_file) | |
| 541 | |
| 542 def _FilterTests(self, tests): | |
| 543 | |
| 544 def gtest_filter(c, m): | |
| 545 t = ['%s.%s' % (c['class'].split('.')[-1], m['method'])] | |
| 546 return (not self._test_filter | |
| 547 or unittest_util.FilterTestNames(t, self._test_filter)) | |
| 548 | |
| 549 def annotation_filter(all_annotations): | |
| 550 if not self._annotations: | |
| 551 return True | |
| 552 return any_annotation_matches(self._annotations, all_annotations) | |
| 553 | |
| 554 def excluded_annotation_filter(all_annotations): | |
| 555 if not self._excluded_annotations: | |
| 556 return True | |
| 557 return not any_annotation_matches(self._excluded_annotations, | |
| 558 all_annotations) | |
| 559 | |
| 560 def any_annotation_matches(annotations, all_annotations): | |
| 561 return any( | |
| 562 ak in all_annotations and (av is None or av == all_annotations[ak]) | |
| 563 for ak, av in annotations.iteritems()) | |
| 564 | |
| 565 filtered_classes = [] | |
| 566 for c in tests: | |
| 567 filtered_methods = [] | |
| 568 for m in c['methods']: | |
| 569 # Gtest filtering | |
| 570 if not gtest_filter(c, m): | |
| 571 continue | |
| 572 | |
| 573 all_annotations = dict(c['annotations']) | |
| 574 all_annotations.update(m['annotations']) | |
| 575 if (not annotation_filter(all_annotations) | |
| 576 or not excluded_annotation_filter(all_annotations)): | |
| 577 continue | |
| 578 | |
| 579 filtered_methods.append(m) | |
| 580 | |
| 581 if filtered_methods: | |
| 582 filtered_class = dict(c) | |
| 583 filtered_class['methods'] = filtered_methods | |
| 584 filtered_classes.append(filtered_class) | |
| 585 | |
| 586 return filtered_classes | |
| 587 | 617 |
| 588 def _InflateTests(self, tests): | 618 def _InflateTests(self, tests): |
| 589 inflated_tests = [] | 619 inflated_tests = [] |
| 590 for c in tests: | 620 for c in tests: |
| 591 for m in c['methods']: | 621 for m in c['methods']: |
| 592 a = dict(c['annotations']) | 622 a = dict(c['annotations']) |
| 593 a.update(m['annotations']) | 623 a.update(m['annotations']) |
| 594 inflated_tests.append({ | 624 inflated_tests.append({ |
| 595 'class': c['class'], | 625 'class': c['class'], |
| 596 'method': m['method'], | 626 'method': m['method'], |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 634 @staticmethod | 664 @staticmethod |
| 635 def GenerateTestResults( | 665 def GenerateTestResults( |
| 636 result_code, result_bundle, statuses, start_ms, duration_ms): | 666 result_code, result_bundle, statuses, start_ms, duration_ms): |
| 637 return GenerateTestResults(result_code, result_bundle, statuses, | 667 return GenerateTestResults(result_code, result_bundle, statuses, |
| 638 start_ms, duration_ms) | 668 start_ms, duration_ms) |
| 639 | 669 |
| 640 #override | 670 #override |
| 641 def TearDown(self): | 671 def TearDown(self): |
| 642 if self._isolate_delegate: | 672 if self._isolate_delegate: |
| 643 self._isolate_delegate.Clear() | 673 self._isolate_delegate.Clear() |
| 644 | |
| OLD | NEW |