Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/env python | |
|
jbudorick
2016/04/28 14:17:14
Can you move this to a subdirectory of tools/andro
Yoland Yan(Google)
2016/04/29 19:51:43
Will move to tools/android/find_annotated_tests/fi
| |
| 2 # Copyright (c) 2016 The Chromium Authors. All rights reserved. | |
| 3 # Use of this source code is governed by a BSD-style license that can be | |
| 4 # found in the LICENSE file. | |
| 5 | |
| 6 """Finds all the tracked(disabled/flaky) tests from proguard dump""" | |
| 7 | |
| 8 import argparse | |
| 9 import datetime | |
| 10 import json | |
| 11 import linecache | |
| 12 import logging | |
| 13 import os | |
| 14 import pprint | |
| 15 import re | |
| 16 import sys | |
| 17 import time | |
| 18 | |
| 19 _SRC_DIR = os.path.abspath(os.path.join( | |
| 20 os.path.dirname(__file__), '..', '..')) | |
| 21 | |
| 22 sys.path.append(os.path.join(_SRC_DIR, 'build', 'android')) | |
| 23 | |
|
jbudorick
2016/04/28 14:17:15
nit: no blank line here
Yoland Yan(Google)
2016/04/29 19:51:42
Done.
| |
| 24 from pylib import constants | |
| 25 from pylib.instrumentation import instrumentation_test_instance | |
| 26 | |
| 27 sys.path.append(os.path.join(_SRC_DIR, 'third_party', 'catapult', 'devil')) | |
| 28 from devil.utils import cmd_helper | |
| 29 | |
| 30 _GIT_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S' | |
| 31 _EXPORT_TIME_FORMAT = '%Y%m%dT%H%M%S' | |
| 32 | |
| 33 class _JSONKeyName(object): | |
|
jbudorick
2016/04/28 14:17:14
I didn't read the earlier reviews, so perhaps this
perezju
2016/04/28 14:33:58
I've been saying the same thing for several review
Yoland Yan(Google)
2016/04/29 19:51:43
wow, what a bunch of bullies
lol
Done
| |
| 34 TEST_MASTER_KEY = 'tests' | |
| 35 REPORT_MASTER_KEY = 'metadata' | |
| 36 REVISION_KEY = 'revision' | |
| 37 COMMIT_POS_KEY = 'commit_pos' | |
| 38 ANNOTATIONS_KEY = 'annotations' | |
| 39 TEST_NAME_KEY = 'test_name' | |
| 40 TEST_APK_KEY = 'test_apk_name' | |
| 41 CRBUG_KEY = 'bug_id' | |
| 42 CLASS_PATH_KEY = 'class_path' | |
| 43 CLASS_NAME_KEY = 'class_name' | |
| 44 TRACKED_TEST_COUNT_KEY = 'tracked_test_count' | |
| 45 DISABLED_TEST_COUNT_KEY = 'disabled_test_count' | |
| 46 FLAKY_TEST_COUNT_KEY = 'flaky_test_count' | |
| 47 TOTAL_TEST_COUNT_KEY = 'total_test_count' | |
| 48 UTC_BUILDTIME_KEY = 'build_time' | |
| 49 UTC_REVISIONTIME_KEY = 'revision_time' | |
| 50 PLATFORM_KEY = 'platform' | |
| 51 PLATFORM_VALUE = 'android' | |
| 52 | |
| 53 | |
| 54 def _GetBugId(message): | |
| 55 """Validate bug message format and get bug id""" | |
| 56 result = re.search(r'crbug(?:.com)?/(\d+)', message) | |
|
jbudorick
2016/04/28 14:17:15
compile this into a module-scope constant
Yoland Yan(Google)
2016/04/29 19:51:42
Done.
| |
| 57 if result: | |
| 58 return int(result.group(1)) | |
| 59 else: | |
| 60 return None | |
| 61 | |
|
jbudorick
2016/04/28 14:17:14
nit: two lines
Yoland Yan(Google)
2016/04/29 19:51:43
Done.
| |
| 62 def _GetAnnotations(test_annotations): | |
| 63 """Store annotations in the existing anntation_dict and return bug id""" | |
| 64 bug_id = None | |
| 65 annotations_dict = {} | |
| 66 for annotation, content in test_annotations.iteritems(): | |
| 67 if content is not None and content.get('message') is not None: | |
| 68 bug_id = _GetBugId(content.get('message')) | |
| 69 annotations_dict.update({annotation: content}) | |
| 70 return bug_id, annotations_dict | |
| 71 | |
| 72 | |
| 73 def _GetTests(test_apks): | |
| 74 """All the all the tests""" | |
|
jbudorick
2016/04/28 14:17:14
nit: "All the all the tests"?
Yoland Yan(Google)
2016/04/29 19:51:43
got it s/All the all the tests/All the all the all
| |
| 75 result = [] | |
| 76 total_test_count = 0 | |
| 77 for test_apk in test_apks: | |
| 78 logging.info('Current test apk: %s', test_apk) | |
| 79 test_jar = os.path.join( | |
| 80 constants.GetOutDirectory(), constants.SDK_BUILD_TEST_JAVALIB_DIR, | |
| 81 '%s.jar' % test_apk) | |
| 82 all_tests = instrumentation_test_instance.GetAllTests(test_jar=test_jar) | |
| 83 for test_class in all_tests: | |
| 84 class_path = test_class['class'] | |
| 85 class_name = test_class['class'].split('.')[-1] | |
| 86 | |
| 87 bug_id, class_annotation = _GetAnnotations(test_class['annotations']) | |
|
perezju
2016/04/27 09:29:15
hmm.. looks like you always end up throwing away t
Yoland Yan(Google)
2016/04/29 19:51:43
Done.
Yoland Yan(Google)
2016/04/29 19:51:43
Changed to class_bug_id and test_bug_id for cases
| |
| 88 for test_method in test_class['methods']: | |
| 89 total_test_count += 1 | |
| 90 # getting annotation of each test case | |
| 91 bug_id, test_annotations = _GetAnnotations(test_method['annotations']) | |
| 92 test_annotations.update(class_annotation) | |
| 93 # getting test method name of each test | |
| 94 test_name = test_method['method'] | |
| 95 test_dict = { | |
| 96 _JSONKeyName.CRBUG_KEY: bug_id, | |
|
jbudorick
2016/04/28 14:17:15
nit: 2 space indent
Yoland Yan(Google)
2016/04/29 19:51:43
Keep it as 4 after a serious discussion
| |
| 97 _JSONKeyName.ANNOTATIONS_KEY: test_annotations, | |
| 98 _JSONKeyName.TEST_NAME_KEY: test_name, | |
| 99 _JSONKeyName.TEST_APK_KEY: test_apk, | |
| 100 _JSONKeyName.CLASS_NAME_KEY: class_name, | |
| 101 _JSONKeyName.CLASS_PATH_KEY: class_path | |
| 102 } | |
| 103 result.append(test_dict) | |
| 104 logging.info('Total count of tests in all test apks: %d', total_test_count) | |
| 105 return result, total_test_count | |
| 106 | |
| 107 | |
| 108 def _GetReportMeta(utc_buildtime_string, total_test_count): | |
| 109 """Returns a dictionary of the report's metadata""" | |
| 110 revision = cmd_helper.GetCmdOutput(['git', 'rev-parse', 'HEAD']).strip() | |
| 111 raw_string = cmd_helper.GetCmdOutput( | |
| 112 ['git', 'log', '--pretty=format:%aI', '--max-count=1', 'HEAD']) | |
|
perezju
2016/04/27 09:29:15
hmm, when I run this locally I get:
2016-04-22T
Yoland Yan(Google)
2016/04/29 19:51:42
Done.
| |
| 113 git_log_time_pattern = r'\d+-\d+-\d+T\d+:\d+:\d+' | |
|
perezju
2016/04/27 09:29:15
nit: a module level constant :)
you could also pr
jbudorick
2016/04/28 14:17:15
+1.
Yoland Yan(Google)
2016/04/29 19:51:43
Done.
| |
| 114 time_string_search = re.search(git_log_time_pattern, raw_string) | |
| 115 if time_string_search is None: | |
| 116 raise Exception('Time format incorrect') | |
|
jbudorick
2016/04/28 14:17:15
This error should be a little more verbose -- _why
Yoland Yan(Google)
2016/04/29 19:51:42
Done.
| |
| 117 | |
| 118 raw_string = cmd_helper.GetCmdOutput( | |
| 119 ['git', 'log', '--pretty=format:%b', '--max-count=1', 'HEAD']) | |
| 120 git_log_message_pattern = r'Cr-Commit-Position: (.*)' | |
|
perezju
2016/04/27 09:29:15
strip away the "refs/heads/master" blurb, and cast
jbudorick
2016/04/28 14:17:15
note that this should also be compiled into a modu
Yoland Yan(Google)
2016/04/29 19:51:43
Done.
| |
| 121 commit_pos_search = re.search(git_log_message_pattern, raw_string) | |
| 122 if commit_pos_search is None: | |
| 123 raise Exception('Cr commit position is not found, potentially running with ' | |
|
jbudorick
2016/04/28 14:17:14
nit: this should say "Cr-Commit-Position ..." rath
Yoland Yan(Google)
2016/04/29 19:51:42
Done.
| |
| 124 'uncommited HEAD') | |
| 125 commit_pos = commit_pos_search.group(1) | |
| 126 | |
| 127 revision_time = time.strptime(time_string_search.group(0), _GIT_TIME_FORMAT) | |
| 128 utc_revision_time = datetime.datetime.utcfromtimestamp( | |
| 129 time.mktime(revision_time)) | |
| 130 utc_revision_time = utc_revision_time.strftime(_EXPORT_TIME_FORMAT) | |
| 131 logging.info( | |
| 132 'revision is %s, revision time is %s', revision, utc_revision_time) | |
| 133 | |
| 134 return { | |
| 135 _JSONKeyName.REVISION_KEY: revision, | |
|
jbudorick
2016/04/28 14:17:14
nit: 2 space indent
Yoland Yan(Google)
2016/04/29 19:51:43
Keep as 4 space indent after serious discussion wi
| |
| 136 _JSONKeyName.COMMIT_POS_KEY: commit_pos, | |
| 137 _JSONKeyName.UTC_BUILDTIME_KEY: utc_buildtime_string, | |
| 138 _JSONKeyName.UTC_REVISIONTIME_KEY: utc_revision_time, | |
| 139 _JSONKeyName.PLATFORM_KEY: _JSONKeyName.PLATFORM_VALUE, | |
| 140 _JSONKeyName.TOTAL_TEST_COUNT_KEY: total_test_count} | |
|
jbudorick
2016/04/28 14:17:14
nit: drop the trailing } onto the next line
Yoland Yan(Google)
2016/04/29 19:51:43
Done.
| |
| 141 | |
| 142 | |
| 143 def _GetReport(test_apks, buildtime_string): | |
| 144 """Generate the dictionary of report data | |
| 145 | |
| 146 Args: | |
| 147 test_apks: a list of apks for search for tests | |
| 148 buildtime_string: the time when the script is run at | |
| 149 format: '%Y%m%dT%H%M%S' | |
| 150 """ | |
| 151 | |
| 152 test_data, total_test_count = _GetTests(test_apks) | |
| 153 report_meta = _GetReportMeta(buildtime_string, total_test_count) | |
| 154 report_data = { | |
| 155 _JSONKeyName.REPORT_MASTER_KEY: report_meta, | |
| 156 _JSONKeyName.TEST_MASTER_KEY: test_data} | |
| 157 return report_data | |
| 158 | |
| 159 | |
| 160 def main(): | |
| 161 default_build_type = os.environ.get('BUILDTYPE', 'Debug') | |
|
jbudorick
2016/04/28 14:17:14
please no
Yoland Yan(Google)
2016/04/29 19:51:43
but...lol (Explain: all this was added when the sc
| |
| 162 parser = argparse.ArgumentParser() | |
|
jbudorick
2016/04/28 14:17:15
Which of these are required?
Yoland Yan(Google)
2016/04/29 19:51:43
--test-apks, --json-output-dir
Done
| |
| 163 parser.add_argument('-t', '--test-apks', nargs='+', dest='test_apks', | |
|
jbudorick
2016/04/28 14:17:15
Why should this handle multiple APKs at once? Why
Yoland Yan(Google)
2016/04/29 19:51:42
Because the that would produce multiple JSON file
| |
| 164 help='List all test apks file name that the script uses ' | |
| 165 'to fetch tracked tests from') | |
| 166 parser.add_argument( | |
|
jbudorick
2016/04/28 14:17:14
nix both --debug and --release and instead support
Yoland Yan(Google)
2016/04/29 19:51:42
Done
| |
| 167 '--debug', action='store_const', const='Debug', dest='build_type', | |
| 168 default=default_build_type, | |
| 169 help=('If set, run test suites under out/Debug. ' | |
| 170 'Default is env var BUILDTYPE or Debug.')) | |
| 171 parser.add_argument( | |
| 172 '--release', action='store_const', const='Release', dest='build_type', | |
| 173 help=('If set, run test suites under out/Release. ' | |
| 174 'Default is env var BUILDTYPE or Debug.')) | |
| 175 parser.add_argument('-o', '--output-path', | |
| 176 help='JSON file output to be uploaded on to gcs') | |
|
jbudorick
2016/04/28 14:17:14
This description is wrong; this is now the output
Yoland Yan(Google)
2016/04/29 19:51:42
Done.
| |
| 177 parser.add_argument('-v', '--verbose', action='store_true', default=False, | |
|
jbudorick
2016/04/28 14:17:14
-v = INFO, -vv = DEBUG plz
Yoland Yan(Google)
2016/04/29 19:51:43
Done.
| |
| 178 help='DEBUG verbosity') | |
| 179 | |
| 180 arguments = parser.parse_args(sys.argv[1:]) | |
| 181 logging.basicConfig( | |
| 182 level=logging.DEBUG if arguments.verbose else logging.WARNING) | |
| 183 constants.SetBuildType(arguments.build_type) | |
|
jbudorick
2016/04/28 14:17:14
Get rid of these two lines.
Yoland Yan(Google)
2016/04/29 19:51:43
Done
| |
| 184 logging.info('Using jar from build type: %s', arguments.build_type) | |
| 185 | |
| 186 buildtime = datetime.datetime.utcnow() | |
|
jbudorick
2016/04/28 14:17:15
nit: buildtime is a bad name because this isn't wh
Yoland Yan(Google)
2016/04/29 19:51:42
Changed to script_run_time
(I was considering chan
| |
| 187 buildtime_string = buildtime.strftime(_EXPORT_TIME_FORMAT) | |
| 188 logging.info('Build time is %s', buildtime_string) | |
| 189 report_data = _GetReport(arguments.test_apks, buildtime_string) | |
| 190 | |
| 191 if arguments.output_path is None: | |
| 192 output_path = constants.GetOutDirectory() | |
| 193 else: | |
| 194 output_path = arguments.output_path | |
| 195 json_output_path = os.path.join(output_path, | |
| 196 '%s-android-chrome.json' % buildtime_string) | |
| 197 with open(json_output_path, 'w') as f: | |
| 198 json.dump(report_data, f, indent=2, sort_keys=True, separators=(',',': ')) | |
|
jbudorick
2016/04/28 14:17:14
This shouldn't be using indent
Yoland Yan(Google)
2016/04/29 19:51:43
Done.
| |
| 199 logging.info('Saved json output file to %s', json_output_path) | |
| 200 | |
| 201 if __name__ == '__main__': | |
| 202 main() | |
| OLD | NEW |