Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(21)

Side by Side Diff: build/android/pylib/instrumentation/instrumentation_test_instance.py

Issue 1851143002: Find annotated tests by exposing API in instrumentation_test_instance (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Change after John's comments Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
52 _PICKLE_FORMAT_VERSION = 10 52 _PICKLE_FORMAT_VERSION = 10
53 53
54 54
55 class MissingSizeAnnotationError(Exception): 55 class MissingSizeAnnotationError(Exception):
56 def __init__(self, class_name): 56 def __init__(self, class_name):
57 super(MissingSizeAnnotationError, self).__init__(class_name + 57 super(MissingSizeAnnotationError, self).__init__(class_name +
58 ': Test method is missing required size annotation. Add one of: ' + 58 ': Test method is missing required size annotation. Add one of: ' +
59 ', '.join('@' + a for a in _VALID_ANNOTATIONS)) 59 ', '.join('@' + a for a in _VALID_ANNOTATIONS))
60 60
61 61
62 class ProguardPickleException(Exception):
63 pass
64
65
62 # TODO(jbudorick): Make these private class methods of 66 # TODO(jbudorick): Make these private class methods of
63 # InstrumentationTestInstance once the instrumentation test_runner is 67 # InstrumentationTestInstance once the instrumentation test_runner is
64 # deprecated. 68 # deprecated.
65 def ParseAmInstrumentRawOutput(raw_output): 69 def ParseAmInstrumentRawOutput(raw_output):
66 """Parses the output of an |am instrument -r| call. 70 """Parses the output of an |am instrument -r| call.
67 71
68 Args: 72 Args:
69 raw_output: the output of an |am instrument -r| call as a list of lines 73 raw_output: the output of an |am instrument -r| call as a list of lines
70 Returns: 74 Returns:
71 A 3-tuple containing: 75 A 3-tuple containing:
(...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after
191 to_remove = [] 195 to_remove = []
192 for a in p.get('arguments', []): 196 for a in p.get('arguments', []):
193 if a['name'] == 'add': 197 if a['name'] == 'add':
194 to_add = ['--%s' % f for f in a['stringArray']] 198 to_add = ['--%s' % f for f in a['stringArray']]
195 elif a['name'] == 'remove': 199 elif a['name'] == 'remove':
196 to_remove = ['--%s' % f for f in a['stringArray']] 200 to_remove = ['--%s' % f for f in a['stringArray']]
197 result.append(ParamsTuple(to_add, to_remove)) 201 result.append(ParamsTuple(to_add, to_remove))
198 return result if result else None 202 return result if result else None
199 203
200 204
205 def FilterTests(tests, test_filter=None, annotations=None,
206 excluded_annotations=None):
207 """Filter a list of tests
208
209 Args:
210 tests: a list of tests. e.g. [
211 {'annotations": {}, 'class': 'com.example.TestA', 'methods':[]},
212 {'annotations": {}, 'class': 'com.example.TestB', 'methods':[]}]
213 test_filter: googletest-style filter string.
214 annotations: a dict of wanted annotations for test methods.
215 exclude_annotations: a dict of annotations to exclude.
216
217 Return:
218 A list of filtered tests
219 """
220 def gtest_filter(c, m):
221 if not test_filter:
222 return True
223 # Allow fully-qualified name as well as an omitted package.
224 names = ['%s.%s' % (c['class'], m['method']),
225 '%s.%s' % (c['class'].split('.')[-1], m['method'])]
226 return unittest_util.FilterTestNames(names, test_filter)
227
228 def annotation_filter(all_annotations):
229 if not annotations:
230 return True
231 return any_annotation_matches(annotations, all_annotations)
232
233 def excluded_annotation_filter(all_annotations):
234 if not excluded_annotations:
235 return True
236 return not any_annotation_matches(excluded_annotations,
237 all_annotations)
238
239 def any_annotation_matches(annotations, all_annotations):
240 return any(
241 ak in all_annotations and (av is None or av == all_annotations[ak])
242 for ak, av in annotations.iteritems())
243
244 filtered_classes = []
245 for c in tests:
246 filtered_methods = []
247 for m in c['methods']:
248 # Gtest filtering
249 if not gtest_filter(c, m):
250 continue
251
252 all_annotations = dict(c['annotations'])
253 all_annotations.update(m['annotations'])
254
255 # Enforce that all tests declare their size.
256 if not any(a in _VALID_ANNOTATIONS for a in all_annotations):
257 raise MissingSizeAnnotationError('%s.%s' % (c['class'], m['method']))
258
259 if (not annotation_filter(all_annotations)
260 or not excluded_annotation_filter(all_annotations)):
261 continue
262
263 filtered_methods.append(m)
264
265 if filtered_methods:
266 filtered_class = dict(c)
267 filtered_class['methods'] = filtered_methods
268 filtered_classes.append(filtered_class)
269
270 return filtered_classes
271
272
273 def GetAllTests(test_jar):
274 pickle_path = '%s-proguard.pickle' % test_jar
275 try:
276 tests = _GetTestsFromPickle(pickle_path, test_jar)
277 except ProguardPickleException as e:
278 logging.info('Could not get tests from pickle: %s', e)
279 logging.info('Getting tests from JAR via proguard.')
280 tests = _GetTestsFromProguard(test_jar)
281 _SaveTestsToPickle(pickle_path, test_jar, tests)
282 return tests
283
284
285 def _GetTestsFromPickle(pickle_path, jar_path):
286 if not os.path.exists(pickle_path):
287 raise ProguardPickleException('%s does not exist.' % pickle_path)
288 if os.path.getmtime(pickle_path) <= os.path.getmtime(jar_path):
289 raise ProguardPickleException(
290 '%s newer than %s.' % (jar_path, pickle_path))
291
292 with open(pickle_path, 'r') as pickle_file:
293 pickle_data = pickle.loads(pickle_file.read())
294 jar_md5 = md5sum.CalculateHostMd5Sums(jar_path)[jar_path]
295
296 if pickle_data['VERSION'] != _PICKLE_FORMAT_VERSION:
297 raise ProguardPickleException('PICKLE_FORMAT_VERSION has changed.')
298 if pickle_data['JAR_MD5SUM'] != jar_md5:
299 raise ProguardPickleException('JAR file MD5 sum differs.')
300 return pickle_data['TEST_METHODS']
301
302
303 def _GetTestsFromProguard(jar_path):
304 p = proguard.Dump(jar_path)
305 class_lookup = dict((c['class'], c) for c in p['classes'])
306
307 def is_test_class(c):
308 return c['class'].endswith('Test')
309
310 def is_test_method(m):
311 return m['method'].startswith('test')
312
313 def recursive_class_annotations(c):
314 s = c['superclass']
315 if s in class_lookup:
316 a = recursive_class_annotations(class_lookup[s])
317 else:
318 a = {}
319 a.update(c['annotations'])
320 return a
321
322 def stripped_test_class(c):
323 return {
324 'class': c['class'],
325 'annotations': recursive_class_annotations(c),
326 'methods': [m for m in c['methods'] if is_test_method(m)],
327 }
328
329 return [stripped_test_class(c) for c in p['classes']
330 if is_test_class(c)]
331
332
333 def _SaveTestsToPickle(pickle_path, jar_path, tests):
334 jar_md5 = md5sum.CalculateHostMd5Sums(jar_path)[jar_path]
335 pickle_data = {
336 'VERSION': _PICKLE_FORMAT_VERSION,
337 'JAR_MD5SUM': jar_md5,
338 'TEST_METHODS': tests,
339 }
340 with open(pickle_path, 'w') as pickle_file:
341 pickle.dump(pickle_data, pickle_file)
342
343
201 class InstrumentationTestInstance(test_instance.TestInstance): 344 class InstrumentationTestInstance(test_instance.TestInstance):
202 345
203 def __init__(self, args, isolate_delegate, error_func): 346 def __init__(self, args, isolate_delegate, error_func):
204 super(InstrumentationTestInstance, self).__init__() 347 super(InstrumentationTestInstance, self).__init__()
205 348
206 self._additional_apks = [] 349 self._additional_apks = []
207 self._apk_under_test = None 350 self._apk_under_test = None
208 self._apk_under_test_incremental_install_script = None 351 self._apk_under_test_incremental_install_script = None
209 self._package_info = None 352 self._package_info = None
210 self._suite = None 353 self._suite = None
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after
287 430
288 self._test_package = self._test_apk.GetPackageName() 431 self._test_package = self._test_apk.GetPackageName()
289 self._test_runner = self._test_apk.GetInstrumentationName() 432 self._test_runner = self._test_apk.GetInstrumentationName()
290 433
291 self._package_info = None 434 self._package_info = None
292 if self._apk_under_test: 435 if self._apk_under_test:
293 package_under_test = self._apk_under_test.GetPackageName() 436 package_under_test = self._apk_under_test.GetPackageName()
294 for package_info in constants.PACKAGE_INFO.itervalues(): 437 for package_info in constants.PACKAGE_INFO.itervalues():
295 if package_under_test == package_info.package: 438 if package_under_test == package_info.package:
296 self._package_info = package_info 439 self._package_info = package_info
440 break
297 if not self._package_info: 441 if not self._package_info:
298 logging.warning('Unable to find package info for %s', self._test_package) 442 logging.warning('Unable to find package info for %s', self._test_package)
299 443
300 for apk in args.additional_apks: 444 for apk in args.additional_apks:
301 if not os.path.exists(apk): 445 if not os.path.exists(apk):
302 error_func('Unable to find additional APK: %s' % apk) 446 error_func('Unable to find additional APK: %s' % apk)
303 self._additional_apks = ( 447 self._additional_apks = (
304 [apk_helper.ToHelper(x) for x in args.additional_apks]) 448 [apk_helper.ToHelper(x) for x in args.additional_apks])
305 449
306 def _initializeDataDependencyAttributes(self, args, isolate_delegate): 450 def _initializeDataDependencyAttributes(self, args, isolate_delegate):
(...skipping 179 matching lines...) Expand 10 before | Expand all | Expand 10 after
486 device_rel_path, host_rel_path = t.split(':') 630 device_rel_path, host_rel_path = t.split(':')
487 host_abs_path = os.path.join(host_paths.DIR_SOURCE_ROOT, host_rel_path) 631 host_abs_path = os.path.join(host_paths.DIR_SOURCE_ROOT, host_rel_path)
488 self._data_deps.extend( 632 self._data_deps.extend(
489 [(host_abs_path, 633 [(host_abs_path,
490 [None, 'chrome', 'test', 'data', device_rel_path])]) 634 [None, 'chrome', 'test', 'data', device_rel_path])])
491 635
492 def GetDataDependencies(self): 636 def GetDataDependencies(self):
493 return self._data_deps 637 return self._data_deps
494 638
495 def GetTests(self): 639 def GetTests(self):
496 pickle_path = '%s-proguard.pickle' % self.test_jar 640 tests = GetAllTests(self.test_jar)
497 try: 641 filtered_tests = FilterTests(
498 tests = self._GetTestsFromPickle(pickle_path, self.test_jar) 642 tests, self._test_filter, self._annotations, self._excluded_annotations)
499 except self.ProguardPickleException as e: 643 return self._ParametrizeTestsWithFlags(self._InflateTests(filtered_tests))
500 logging.info('Getting tests from JAR via proguard. (%s)', str(e))
501 tests = self._GetTestsFromProguard(self.test_jar)
502 self._SaveTestsToPickle(pickle_path, self.test_jar, tests)
503 return self._ParametrizeTestsWithFlags(
504 self._InflateTests(self._FilterTests(tests)))
505
506 class ProguardPickleException(Exception):
507 pass
508
509 def _GetTestsFromPickle(self, pickle_path, jar_path):
510 if not os.path.exists(pickle_path):
511 raise self.ProguardPickleException('%s does not exist.' % pickle_path)
512 if os.path.getmtime(pickle_path) <= os.path.getmtime(jar_path):
513 raise self.ProguardPickleException(
514 '%s newer than %s.' % (jar_path, pickle_path))
515
516 with open(pickle_path, 'r') as pickle_file:
517 pickle_data = pickle.loads(pickle_file.read())
518 jar_md5 = md5sum.CalculateHostMd5Sums(jar_path)[jar_path]
519
520 try:
521 if pickle_data['VERSION'] != _PICKLE_FORMAT_VERSION:
522 raise self.ProguardPickleException('PICKLE_FORMAT_VERSION has changed.')
523 if pickle_data['JAR_MD5SUM'] != jar_md5:
524 raise self.ProguardPickleException('JAR file MD5 sum differs.')
525 return pickle_data['TEST_METHODS']
526 except TypeError as e:
527 logging.error(pickle_data)
528 raise self.ProguardPickleException(str(e))
529 644
530 # pylint: disable=no-self-use 645 # pylint: disable=no-self-use
531 def _GetTestsFromProguard(self, jar_path):
532 p = proguard.Dump(jar_path)
533
534 def is_test_class(c):
535 return c['class'].endswith('Test')
536
537 def is_test_method(m):
538 return m['method'].startswith('test')
539
540 class_lookup = dict((c['class'], c) for c in p['classes'])
541 def recursive_get_class_annotations(c):
542 s = c['superclass']
543 if s in class_lookup:
544 a = recursive_get_class_annotations(class_lookup[s])
545 else:
546 a = {}
547 a.update(c['annotations'])
548 return a
549
550 def stripped_test_class(c):
551 return {
552 'class': c['class'],
553 'annotations': recursive_get_class_annotations(c),
554 'methods': [m for m in c['methods'] if is_test_method(m)],
555 }
556
557 return [stripped_test_class(c) for c in p['classes']
558 if is_test_class(c)]
559
560 def _SaveTestsToPickle(self, pickle_path, jar_path, tests):
561 jar_md5 = md5sum.CalculateHostMd5Sums(jar_path)[jar_path]
562 pickle_data = {
563 'VERSION': _PICKLE_FORMAT_VERSION,
564 'JAR_MD5SUM': jar_md5,
565 'TEST_METHODS': tests,
566 }
567 with open(pickle_path, 'w') as pickle_file:
568 pickle.dump(pickle_data, pickle_file)
569
570 def _FilterTests(self, tests):
571
572 def gtest_filter(c, m):
573 if not self._test_filter:
574 return True
575 # Allow fully-qualified name as well as an omitted package.
576 names = ['%s.%s' % (c['class'], m['method']),
577 '%s.%s' % (c['class'].split('.')[-1], m['method'])]
578 return unittest_util.FilterTestNames(names, self._test_filter)
579
580 def annotation_filter(all_annotations):
581 if not self._annotations:
582 return True
583 return any_annotation_matches(self._annotations, all_annotations)
584
585 def excluded_annotation_filter(all_annotations):
586 if not self._excluded_annotations:
587 return True
588 return not any_annotation_matches(self._excluded_annotations,
589 all_annotations)
590
591 def any_annotation_matches(annotations, all_annotations):
592 return any(
593 ak in all_annotations and (av is None or av == all_annotations[ak])
594 for ak, av in annotations.iteritems())
595
596 filtered_classes = []
597 for c in tests:
598 filtered_methods = []
599 for m in c['methods']:
600 # Gtest filtering
601 if not gtest_filter(c, m):
602 continue
603
604 all_annotations = dict(c['annotations'])
605 all_annotations.update(m['annotations'])
606
607 # Enforce that all tests declare their size.
608 if not any(a in _VALID_ANNOTATIONS for a in all_annotations):
609 raise MissingSizeAnnotationError('%s.%s' % (c['class'], m['method']))
610
611 if (not annotation_filter(all_annotations)
612 or not excluded_annotation_filter(all_annotations)):
613 continue
614
615 filtered_methods.append(m)
616
617 if filtered_methods:
618 filtered_class = dict(c)
619 filtered_class['methods'] = filtered_methods
620 filtered_classes.append(filtered_class)
621
622 return filtered_classes
623
624 def _InflateTests(self, tests): 646 def _InflateTests(self, tests):
625 inflated_tests = [] 647 inflated_tests = []
626 for c in tests: 648 for c in tests:
627 for m in c['methods']: 649 for m in c['methods']:
628 a = dict(c['annotations']) 650 a = dict(c['annotations'])
629 a.update(m['annotations']) 651 a.update(m['annotations'])
630 inflated_tests.append({ 652 inflated_tests.append({
631 'class': c['class'], 653 'class': c['class'],
632 'method': m['method'], 654 'method': m['method'],
633 'annotations': a, 655 'annotations': a,
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
670 @staticmethod 692 @staticmethod
671 def GenerateTestResults( 693 def GenerateTestResults(
672 result_code, result_bundle, statuses, start_ms, duration_ms): 694 result_code, result_bundle, statuses, start_ms, duration_ms):
673 return GenerateTestResults(result_code, result_bundle, statuses, 695 return GenerateTestResults(result_code, result_bundle, statuses,
674 start_ms, duration_ms) 696 start_ms, duration_ms)
675 697
676 #override 698 #override
677 def TearDown(self): 699 def TearDown(self):
678 if self._isolate_delegate: 700 if self._isolate_delegate:
679 self._isolate_delegate.Clear() 701 self._isolate_delegate.Clear()
680
OLDNEW
« no previous file with comments | « no previous file | tools/android/find_annotated_tests.py » ('j') | tools/android/find_annotated_tests.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698