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

Side by Side Diff: Tools/AutoSheriff/reasons.py

Issue 398823008: WIP: Add auto-sheriff.appspot.com code to Blink Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: Created 6 years, 5 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 | Annotate | Revision Log
OLDNEW
(Empty)
1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 import json
6 import logging
7 import requests
8 import sys
9 import urllib
10 import urlparse
11 import argparse
12 import re
13
14 import requests_cache
15
16 requests_cache.install_cache('reasons')
17
18
19 # This is relative to build/scripts:
20 # https://chromium.googlesource.com/chromium/tools/build/+/master/scripts
21 BUILD_SCRIPTS_PATH = "/src/build/scripts"
22 sys.path.append(BUILD_SCRIPTS_PATH)
23 from common import gtest_utils
24
25 # Python logging is stupidly verbose to configure.
26 def setup_logging():
27 logger = logging.getLogger(__name__)
28 logger.setLevel(logging.DEBUG)
29 handler = logging.StreamHandler()
30 handler.setLevel(logging.DEBUG)
31 formatter = logging.Formatter('%(levelname)s: %(message)s')
32 handler.setFormatter(formatter)
33 logger.addHandler(handler)
34 return logger, handler
35
36
37 log, logging_handler = setup_logging()
38
39
40 def build_url(master_url, builder_name, build_number):
41 quoted_name = urllib.pathname2url(builder_name)
42 args = (master_url, quoted_name, build_number)
43 return "%s/builders/%s/builds/%s" % args
44
45
46 def stdio_for_step(master_url, builder_name, build, step):
47 # FIXME: Should get this from the step in some way?
48 base_url = build_url(master_url, builder_name, build['number'])
49 stdio_url = "%s/steps/%s/logs/stdio/text" % (base_url, step['name'])
50
51 try:
52 return requests.get(stdio_url).text
53 except requests.exceptions.ConnectionError, e:
54 # Some builders don't save logs for whatever reason.
55 log.error('Failed to fetch %s: %s' % (stdio_url, e))
56 return None
57
58
59 # These are reason finders, more than splitters?
60 class GTestSplitter(object):
61 def handles_step(self, step):
62 step_name = step['name']
63 # Silly heuristic, at least we won't bother processing
64 # stdio from gclient revert, etc.
65 if step_name.endswith('tests'):
66 return True
67
68 KNOWN_STEPS = [
ojan 2014/07/22 02:01:26 I'd leave this out for now. Can always add it back
69 # There are probably other gtest steps not named 'tests'.
70 ]
71 return step_name in KNOWN_STEPS
72
73 def split_step(self, step, build, builder_name, master_url):
74 stdio_log = stdio_for_step(master_url, builder_name, build, step)
75 # Can't split if we can't get the logs.
76 if not stdio_log:
77 return None
78
79 # Lines this fails for:
80 #[ FAILED ] ExtensionApiTest.TabUpdate, where TypeParam = and GetParam() = (10907 ms)
81
82 log_parser = gtest_utils.GTestLogParser()
83 for line in stdio_log.split('\n'):
84 log_parser.ProcessLine(line)
85
86 failed_tests = log_parser.FailedTests()
87 if failed_tests:
88 return failed_tests
89 # Failed to split, just group with the general failures.
90 log.debug('First Line: %s' % stdio_log.split('\n')[0])
91 return None
92
93
94 # Our Android tests produce very gtest-like output, but not
95 # quite GTestLogParser-compatible (it parse the name of the
96 # test as org.chromium).
97
98 class JUnitSplitter(object):
99 def handles_step(self, step):
100 KNOWN_STEPS = [
101 'androidwebview_instrumentation_tests',
102 'mojotest_instrumentation_tests', # Are these always java?
103 ]
104 return step['name'] in KNOWN_STEPS
105
106 FAILED_REGEXP = re.compile('\[\s+FAILED\s+\] (?P<test_name>\S+)( \(.*\))?$')
107
108 def failed_tests_from_stdio(self, stdio):
109 failed_tests = []
110 for line in stdio.split('\n'):
111 match = self.FAILED_REGEXP.search(line)
112 if match:
113 failed_tests.append(match.group('test_name'))
114 return failed_tests
115
116 def split_step(self, step, build, builder_name, master_url):
117 stdio_log = stdio_for_step(master_url, builder_name, build, step)
118 # Can't split if we can't get the logs.
119 if not stdio_log:
120 return None
121
122 failed_tests = self.failed_tests_from_stdio(stdio_log)
123 if failed_tests:
124 return failed_tests
125 # Failed to split, just group with the general failures.
126 log.debug('First Line: %s' % stdio_log.split('\n')[0])
127 return None
128
129
130 def decode_results(results, include_expected=False):
131 tests = convert_trie_to_flat_paths(results['tests'])
132 failures = {}
133 flakes = {}
134 passes = {}
135 for (test, result) in tests.iteritems():
136 if include_expected or result.get('is_unexpected'):
137 actual_results = result['actual'].split()
138 expected_results = result['expected'].split()
139 if len(actual_results) > 1:
140 if actual_results[1] in expected_results:
141 flakes[test] = actual_results[0]
ojan 2014/07/22 02:01:26 Can we just store result['actual'] here? That way
142 else:
143 # We report the first failure type back, even if the second
144 # was more severe.
145 failures[test] = actual_results[0]
ojan 2014/07/22 02:01:25 Ditto
146 elif actual_results[0] == 'PASS':
147 passes[test] = result
148 else:
149 failures[test] = actual_results[0]
150
151 return (passes, failures, flakes)
152
153
154 def convert_trie_to_flat_paths(trie, prefix=None):
155 # Cloned from webkitpy.layout_tests.layout_package.json_results_generator
156 # so that this code can stand alone.
157 result = {}
158 for name, data in trie.iteritems():
159 if prefix:
160 name = prefix + "/" + name
161
162 if len(data) and not "actual" in data and not "expected" in data:
163 result.update(convert_trie_to_flat_paths(data, name))
164 else:
165 result[name] = data
166
167 return result
168
169
170 class LayoutTestsSplitter(object):
171 def handles_step(self, step):
172 return step['name'] == 'webkit_tests'
173
174 def split_step(self, step, build, builder_name, master_url):
175 # WTF? The android bots call it archive_webkit_results and the rest call it archive_webkit_tests_results?
176 archive_names = ['archive_webkit_results', 'archive_webkit_tests_results']
177 archive_step = next((step for step in build['steps'] if step['name'] in arch ive_names), None)
178 url_to_build = build_url(master_url, builder_name, build['number'])
179
180 if not archive_step:
181 log.warn('No archive step in %s' % url_to_build)
182 # print json.dumps(build['steps'], indent=1)
ojan 2014/07/22 02:01:26 Delete dead code?
183 return None
184
185 html_results_url = archive_step['urls'].get('layout test results')
186 # FIXME: Here again, Android is a special snowflake.
187 if not html_results_url:
188 html_results_url = archive_step['urls'].get('results')
189
190 if not html_results_url:
191 webkit_tests_step = next((step for step in build['steps'] if step['name'] == 'webkit_tests'), None)
192 # Common cause of this is an exception in the webkit_tests step.
193 if webkit_tests_step['results'][0] != 5:
194 log.warn('No results url for archive step in %s' % url_to_build)
195 # print json.dumps(archive_step, indent=1)
ojan 2014/07/22 02:01:25 Ditto
196 return None
197
198 # !@?#!$^&$% WTF HOW DO URLS HAVE \r in them!?!
ojan 2014/07/22 02:01:26 Wat
199 html_results_url = html_results_url.replace('\r', '')
200
201 jsonp_url = urlparse.urljoin(html_results_url, 'failing_results.json')
202 # FIXME: Silly that this is still JSONP.
ojan 2014/07/22 02:01:26 This is because failing_results.json is used by re
203 jsonp_string = requests.get(jsonp_url).text
204 if "The specified key does not exist" in jsonp_string:
205 log.warn('%s missing for %s' % (jsonp_url, url_to_build))
206 return None
207
208 json_string = jsonp_string[len('ADD_RESULTS('):-len(');')]
209 try:
210 results = json.loads(json_string)
211 passes, failures, flakes = decode_results(results)
212 if failures:
213 return ['%s:%s' % (name, types) for name, types in failures.items()]
214 except ValueError, e:
215 print archive_step['urls']
216 print html_results_url
217 print "Failed %s, %s at decode of: %s" % (jsonp_url, e, jsonp_string)
218
219 # Failed to split, just group with the general failures.
220 return None
221
222
223 class CompileSplitter(object):
224 def handles_step(self, step):
225 return step['name'] == 'compile'
226
227 # Compile example:
ojan 2014/07/22 02:01:26 Even better than examples in comments is examples
228 # FAILED: /mnt/data/b/build/goma/gomacc ...
229 # ../../v8/src/base/platform/time.cc:590:7: error: use of undeclared identifier 'close'
230
231 # Linker example:
232 # FAILED: /b/build/goma/gomacc ...
233 # obj/chrome/browser/extensions/interactive_ui_tests.extension_commands_global_r egistry_apitest.o:extension_commands_global_registry_apitest.cc:function extensi ons::SendNativeKeyEventToXDisplay(ui::KeyboardCode, bool, bool, bool): error: un defined reference to 'gfx::GetXDisplay()'
234
235 def split_step(self, step, build, builder_name, master_url):
236 stdio = stdio_for_step(master_url, builder_name, build, step)
237 # Can't split if we can't get the logs.
238 if not stdio:
ojan 2014/07/22 02:01:26 Should we log an error or something here?
239 return None
240
241 compile_regexp = re.compile(r'(?P<path>.*):(?P<line>\d+):(?P<column>\d+): er ror:')
242
243 # FIXME: I'm sure there is a cleaner way to do this.
244 next_line_is_failure = False
245 for line in stdio.split('\n'):
246 if not next_line_is_failure:
247 if line.startswith('FAILED: '):
248 next_line_is_failure = True
249 continue
250
251 match = compile_regexp.match(line)
252 if match:
253 return ['%s:%s' % (match.group('path'), match.group('line'))]
254 break
255
256 return None
257
258
259 # This is a hack I wrote because all the perf bots are failing with:
260 # E 0.009s Main File not found /b/build/slave/Android_GN_Perf/build/src/out/ step_results/dromaeo.jslibstyleprototype
261 # and it's nice to group them by something at least!
262 # Often just hits:
263 # 2 new files were left in c:\users\chrome~1.per\appdata\local\temp: Fix the tes ts to clean up themselves.
264 # so disabled for now.
265 class GenericRunTests(object):
266 def handles_step(self, step):
267 return True
268
269 def split_step(self, step, build, builder_name, master_url):
270 stdio = stdio_for_step(master_url, builder_name, build, step)
271 # Can't split if we can't get the logs.
272 if not stdio:
273 return None
274
275 last_line = None
276 for line in stdio.split('\n'):
277 if last_line and line.startswith('exit code (as seen by runtest.py):'):
278 return [last_line]
279 last_line = line
280
281
282 STEP_SPLITTERS = [
283 CompileSplitter(),
284 LayoutTestsSplitter(),
285 JUnitSplitter(),
286 GTestSplitter(),
287 # GenericRunTests(),
ojan 2014/07/22 02:01:26 Should we delete this if we're going to comment it
288 ]
289
290
291 # For testing:
292 def main(args):
293 parser = argparse.ArgumentParser()
294 parser.add_argument('stdio_url', action='store')
295 args = parser.parse_args(args)
296
297 # https://build.chromium.org/p/chromium.win/builders/XP%20Tests%20(1)/builds/3 1886/steps/browser_tests/logs/stdio
298 url_regexp = re.compile('(?P<master_url>.*)/builders/(?P<builder_name>.*)/buil ds/(?P<build_number>.*)/steps/(?P<step_name>.*)/logs/stdio')
299 match = url_regexp.match(args.stdio_url)
300 if not match:
301 print "Failed to parse URL: %s" % args.stdio_url
302 sys.exit(1)
303
304 step = {
305 'name': match.group('step_name'),
306 }
307 build = {
308 'number': match.group('build_number'),
309 }
310 splitter = next((splitter for splitter in STEP_SPLITTERS if splitter.handles_s tep(step)), None)
311 builder_name = urllib.unquote_plus(match.group('builder_name'))
312 master_url = match.group('master_url')
313 print splitter.split_step(step, build, builder_name, master_url)
314
315
316 if __name__ == '__main__':
317 sys.exit(main(sys.argv[1:]))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698