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

Side by Side Diff: tools/rebaseline.py

Issue 23478011: rebaseline.py: add --bugs and --unreviewed flags (Closed) Base URL: http://skia.googlecode.com/svn/trunk/
Patch Set: add_notes_flag Created 7 years, 3 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
1 #!/usr/bin/python 1 #!/usr/bin/python
2 2
3 ''' 3 '''
4 Copyright 2012 Google Inc. 4 Copyright 2012 Google Inc.
5 5
6 Use of this source code is governed by a BSD-style license that can be 6 Use of this source code is governed by a BSD-style license that can be
7 found in the LICENSE file. 7 found in the LICENSE file.
8 ''' 8 '''
9 9
10 ''' 10 '''
(...skipping 142 matching lines...) Expand 10 before | Expand all | Expand 10 after
153 # actuals_base_url: base URL from which to read actual-result JSON files 153 # actuals_base_url: base URL from which to read actual-result JSON files
154 # actuals_filename: filename (under actuals_base_url) from which to read a 154 # actuals_filename: filename (under actuals_base_url) from which to read a
155 # summary of results; typically "actual-results.json" 155 # summary of results; typically "actual-results.json"
156 # exception_handler: reference to rebaseline.ExceptionHandler object 156 # exception_handler: reference to rebaseline.ExceptionHandler object
157 # tests: list of tests to rebaseline, or None if we should rebaseline 157 # tests: list of tests to rebaseline, or None if we should rebaseline
158 # whatever files the JSON results summary file tells us to 158 # whatever files the JSON results summary file tells us to
159 # configs: which configs to run for each test, or None if we should 159 # configs: which configs to run for each test, or None if we should
160 # rebaseline whatever configs the JSON results summary file tells 160 # rebaseline whatever configs the JSON results summary file tells
161 # us to 161 # us to
162 # add_new: if True, add expectations for tests which don't have any yet 162 # add_new: if True, add expectations for tests which don't have any yet
163 # bugs: optional list of bug numbers which pertain to these expectations
164 # notes: free-form text notes to add to all updated expectations
165 # mark_unreviewed: if True, mark these expectations as NOT having been
166 # reviewed by a human; otherwise, leave that field blank.
167 # Currently, there is no way to make this script mark
168 # expectations as reviewed-by-human=True.
169 # TODO(epoger): Add that capability to a review tool.
163 def __init__(self, expectations_root, expectations_input_filename, 170 def __init__(self, expectations_root, expectations_input_filename,
164 expectations_output_filename, actuals_base_url, 171 expectations_output_filename, actuals_base_url,
165 actuals_filename, exception_handler, 172 actuals_filename, exception_handler,
166 tests=None, configs=None, add_new=False): 173 tests=None, configs=None, add_new=False, bugs=None, notes=None,
174 mark_unreviewed=None):
167 self._expectations_root = expectations_root 175 self._expectations_root = expectations_root
168 self._expectations_input_filename = expectations_input_filename 176 self._expectations_input_filename = expectations_input_filename
169 self._expectations_output_filename = expectations_output_filename 177 self._expectations_output_filename = expectations_output_filename
170 self._tests = tests 178 self._tests = tests
171 self._configs = configs 179 self._configs = configs
172 self._actuals_base_url = actuals_base_url 180 self._actuals_base_url = actuals_base_url
173 self._actuals_filename = actuals_filename 181 self._actuals_filename = actuals_filename
174 self._exception_handler = exception_handler 182 self._exception_handler = exception_handler
175 self._add_new = add_new 183 self._add_new = add_new
184 self._bugs = bugs
185 self._notes = notes
186 self._mark_unreviewed = mark_unreviewed
176 self._image_filename_re = re.compile(gm_json.IMAGE_FILENAME_PATTERN) 187 self._image_filename_re = re.compile(gm_json.IMAGE_FILENAME_PATTERN)
177 self._using_svn = os.path.isdir(os.path.join(expectations_root, '.svn')) 188 self._using_svn = os.path.isdir(os.path.join(expectations_root, '.svn'))
178 189
179 # Executes subprocess.call(cmd). 190 # Executes subprocess.call(cmd).
180 # Raises an Exception if the command fails. 191 # Raises an Exception if the command fails.
181 def _Call(self, cmd): 192 def _Call(self, cmd):
182 if subprocess.call(cmd) != 0: 193 if subprocess.call(cmd) != 0:
183 raise _InternalException('error running command: ' + ' '.join(cmd)) 194 raise _InternalException('error running command: ' + ' '.join(cmd))
184 195
185 # Returns the full contents of filepath, as a single string. 196 # Returns the full contents of filepath, as a single string.
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
239 # Rebaseline all tests/types we specified in the constructor, 250 # Rebaseline all tests/types we specified in the constructor,
240 # within this builder's subdirectory in expectations/gm . 251 # within this builder's subdirectory in expectations/gm .
241 # 252 #
242 # params: 253 # params:
243 # builder : e.g. 'Test-Win7-ShuttleA-HD2000-x86-Release' 254 # builder : e.g. 'Test-Win7-ShuttleA-HD2000-x86-Release'
244 def RebaselineSubdir(self, builder): 255 def RebaselineSubdir(self, builder):
245 # Read in the actual result summary, and extract all the tests whose 256 # Read in the actual result summary, and extract all the tests whose
246 # results we need to update. 257 # results we need to update.
247 actuals_url = '/'.join([self._actuals_base_url, 258 actuals_url = '/'.join([self._actuals_base_url,
248 builder, self._actuals_filename]) 259 builder, self._actuals_filename])
249 # In most cases, we won't need to re-record results that are already 260 # Only update results for tests that are currently failing.
250 # succeeding, but including the SUCCEEDED results will allow us to 261 # We don't want to rewrite results for tests that are already succeeding,
251 # re-record expectations if they somehow get out of sync. 262 # because we don't want to add annotation fields (such as
252 sections = [gm_json.JSONKEY_ACTUALRESULTS_FAILED, 263 # JSONKEY_EXPECTEDRESULTS_BUGS) except for tests whose expectations we
253 gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED] 264 # are actually modifying.
265 sections = [gm_json.JSONKEY_ACTUALRESULTS_FAILED]
254 if self._add_new: 266 if self._add_new:
255 sections.append(gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON) 267 sections.append(gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON)
256 results_to_update = self._GetActualResults(json_url=actuals_url, 268 results_to_update = self._GetActualResults(json_url=actuals_url,
257 sections=sections) 269 sections=sections)
258 270
259 # Read in current expectations. 271 # Read in current expectations.
260 expectations_input_filepath = os.path.join( 272 expectations_input_filepath = os.path.join(
261 self._expectations_root, builder, self._expectations_input_filename) 273 self._expectations_root, builder, self._expectations_input_filename)
262 expectations_dict = gm_json.LoadFromFile(expectations_input_filepath) 274 expectations_dict = gm_json.LoadFromFile(expectations_input_filepath)
263 expected_results = expectations_dict[gm_json.JSONKEY_EXPECTEDRESULTS] 275 expected_results = expectations_dict[gm_json.JSONKEY_EXPECTEDRESULTS]
264 276
265 # Update the expectations in memory, skipping any tests/configs that 277 # Update the expectations in memory, skipping any tests/configs that
266 # the caller asked to exclude. 278 # the caller asked to exclude.
267 skipped_images = [] 279 skipped_images = []
268 if results_to_update: 280 if results_to_update:
269 for (image_name, image_results) in results_to_update.iteritems(): 281 for (image_name, image_results) in results_to_update.iteritems():
270 (test, config) = self._image_filename_re.match(image_name).groups() 282 (test, config) = self._image_filename_re.match(image_name).groups()
271 if self._tests: 283 if self._tests:
272 if test not in self._tests: 284 if test not in self._tests:
273 skipped_images.append(image_name) 285 skipped_images.append(image_name)
274 continue 286 continue
275 if self._configs: 287 if self._configs:
276 if config not in self._configs: 288 if config not in self._configs:
277 skipped_images.append(image_name) 289 skipped_images.append(image_name)
278 continue 290 continue
279 if not expected_results.get(image_name): 291 if not expected_results.get(image_name):
280 expected_results[image_name] = {} 292 expected_results[image_name] = {}
281 expected_results[image_name][gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGE STS] = \ 293 expected_results[image_name][gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGE STS] = \
282 [image_results] 294 [image_results]
295 if self._mark_unreviewed:
296 expected_results[image_name][gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED] = \
borenet 2013/08/30 13:26:55 Nit: 80 chars
epoger 2013/08/30 14:50:15 Yes, but for these lines (as well as line 293 abov
borenet 2013/09/03 15:42:53 Maybe something like this? if self._mark_unreview
epoger 2013/09/03 17:28:14 I took that idea and ran with it. What do you thi
borenet 2013/09/03 17:30:57 LGTM. Are the backslashes required?
297 False
298 if self._bugs:
299 expected_results[image_name][gm_json.JSONKEY_EXPECTEDRESULTS_BUGS] = \
300 self._bugs
301 if self._notes:
epoger 2013/08/30 09:18:25 Patchset 4 adds --notes flag, and records those no
302 expected_results[image_name][gm_json.JSONKEY_EXPECTEDRESULTS_NOTES] = \
borenet 2013/08/30 13:26:55 80 chars
303 self._notes
283 304
284 # Write out updated expectations. 305 # Write out updated expectations.
285 expectations_output_filepath = os.path.join( 306 expectations_output_filepath = os.path.join(
286 self._expectations_root, builder, self._expectations_output_filename) 307 self._expectations_root, builder, self._expectations_output_filename)
287 gm_json.WriteToFile(expectations_dict, expectations_output_filepath) 308 gm_json.WriteToFile(expectations_dict, expectations_output_filepath)
288 309
289 # Mark the JSON file as plaintext, so text-style diffs can be applied. 310 # Mark the JSON file as plaintext, so text-style diffs can be applied.
290 # Fixes https://code.google.com/p/skia/issues/detail?id=1442 311 # Fixes https://code.google.com/p/skia/issues/detail?id=1442
291 if self._using_svn: 312 if self._using_svn:
292 self._Call(['svn', 'propset', '--quiet', 'svn:mime-type', 313 self._Call(['svn', 'propset', '--quiet', 'svn:mime-type',
(...skipping 10 matching lines...) Expand all
303 help='filename (within builder-specific subdirectories ' + 324 help='filename (within builder-specific subdirectories ' +
304 'of ACTUALS_BASE_URL) to read a summary of results from; ' + 325 'of ACTUALS_BASE_URL) to read a summary of results from; ' +
305 'defaults to %(default)s', 326 'defaults to %(default)s',
306 default='actual-results.json') 327 default='actual-results.json')
307 # TODO(epoger): Add test that exercises --add-new argument. 328 # TODO(epoger): Add test that exercises --add-new argument.
308 parser.add_argument('--add-new', action='store_true', 329 parser.add_argument('--add-new', action='store_true',
309 help='in addition to the standard behavior of ' + 330 help='in addition to the standard behavior of ' +
310 'updating expectations for failing tests, add ' + 331 'updating expectations for failing tests, add ' +
311 'expectations for tests which don\'t have expectations ' + 332 'expectations for tests which don\'t have expectations ' +
312 'yet.') 333 'yet.')
334 parser.add_argument('--bugs', metavar='BUG', type=int, nargs='+',
335 help='Skia bug numbers (under ' +
336 'https://code.google.com/p/skia/issues/list ) which '+
337 'pertain to this set of rebaselines.')
borenet 2013/09/03 15:42:53 I'd prefer this syntax for multi-line strings: pa
epoger 2013/09/03 17:28:14 Done.
313 parser.add_argument('--builders', metavar='BUILDER', nargs='+', 338 parser.add_argument('--builders', metavar='BUILDER', nargs='+',
314 help='which platforms to rebaseline; ' + 339 help='which platforms to rebaseline; ' +
315 'if unspecified, rebaseline all platforms, same as ' + 340 'if unspecified, rebaseline all platforms, same as ' +
316 '"--builders %s"' % ' '.join(sorted(TEST_BUILDERS))) 341 '"--builders %s"' % ' '.join(sorted(TEST_BUILDERS)))
317 # TODO(epoger): Add test that exercises --configs argument. 342 # TODO(epoger): Add test that exercises --configs argument.
318 parser.add_argument('--configs', metavar='CONFIG', nargs='+', 343 parser.add_argument('--configs', metavar='CONFIG', nargs='+',
319 help='which configurations to rebaseline, e.g. ' + 344 help='which configurations to rebaseline, e.g. ' +
320 '"--configs 565 8888", as a filter over the full set of ' + 345 '"--configs 565 8888", as a filter over the full set of ' +
321 'results in ACTUALS_FILENAME; if unspecified, rebaseline ' + 346 'results in ACTUALS_FILENAME; if unspecified, rebaseline ' +
322 '*all* configs that are available.') 347 '*all* configs that are available.')
(...skipping 11 matching lines...) Expand all
334 default='') 359 default='')
335 parser.add_argument('--expectations-root', 360 parser.add_argument('--expectations-root',
336 help='root of expectations directory to update-- should ' + 361 help='root of expectations directory to update-- should ' +
337 'contain one or more builder subdirectories. Defaults to ' + 362 'contain one or more builder subdirectories. Defaults to ' +
338 '%(default)s', 363 '%(default)s',
339 default=os.path.join('expectations', 'gm')) 364 default=os.path.join('expectations', 'gm'))
340 parser.add_argument('--keep-going-on-failure', action='store_true', 365 parser.add_argument('--keep-going-on-failure', action='store_true',
341 help='instead of halting at the first error encountered, ' + 366 help='instead of halting at the first error encountered, ' +
342 'keep going and rebaseline as many tests as possible, ' + 367 'keep going and rebaseline as many tests as possible, ' +
343 'and then report the full set of errors at the end') 368 'and then report the full set of errors at the end')
369 parser.add_argument('--notes',
370 help='free-form text notes to add to all updated ' +
371 'expectations')
344 # TODO(epoger): Add test that exercises --tests argument. 372 # TODO(epoger): Add test that exercises --tests argument.
345 parser.add_argument('--tests', metavar='TEST', nargs='+', 373 parser.add_argument('--tests', metavar='TEST', nargs='+',
346 help='which tests to rebaseline, e.g. ' + 374 help='which tests to rebaseline, e.g. ' +
347 '"--tests aaclip bigmatrix", as a filter over the full ' + 375 '"--tests aaclip bigmatrix", as a filter over the full ' +
348 'set of results in ACTUALS_FILENAME; if unspecified, ' + 376 'set of results in ACTUALS_FILENAME; if unspecified, ' +
349 'rebaseline *all* tests that are available.') 377 'rebaseline *all* tests that are available.')
378 parser.add_argument('--unreviewed', action='store_true',
379 help='mark all expectations modified by this run as ' +
380 '"%s": False' % gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED)
350 args = parser.parse_args() 381 args = parser.parse_args()
351 exception_handler = ExceptionHandler( 382 exception_handler = ExceptionHandler(
352 keep_going_on_failure=args.keep_going_on_failure) 383 keep_going_on_failure=args.keep_going_on_failure)
353 if args.builders: 384 if args.builders:
354 builders = args.builders 385 builders = args.builders
355 missing_json_is_fatal = True 386 missing_json_is_fatal = True
356 else: 387 else:
357 builders = sorted(TEST_BUILDERS) 388 builders = sorted(TEST_BUILDERS)
358 missing_json_is_fatal = False 389 missing_json_is_fatal = False
359 for builder in builders: 390 for builder in builders:
360 if not builder in TEST_BUILDERS: 391 if not builder in TEST_BUILDERS:
361 raise Exception(('unrecognized builder "%s"; ' + 392 raise Exception(('unrecognized builder "%s"; ' +
362 'should be one of %s') % ( 393 'should be one of %s') % (
363 builder, TEST_BUILDERS)) 394 builder, TEST_BUILDERS))
364 395
365 expectations_json_file = os.path.join(args.expectations_root, builder, 396 expectations_json_file = os.path.join(args.expectations_root, builder,
366 args.expectations_filename) 397 args.expectations_filename)
367 if os.path.isfile(expectations_json_file): 398 if os.path.isfile(expectations_json_file):
368 rebaseliner = JsonRebaseliner( 399 rebaseliner = JsonRebaseliner(
369 expectations_root=args.expectations_root, 400 expectations_root=args.expectations_root,
370 expectations_input_filename=args.expectations_filename, 401 expectations_input_filename=args.expectations_filename,
371 expectations_output_filename=(args.expectations_filename_output or 402 expectations_output_filename=(args.expectations_filename_output or
372 args.expectations_filename), 403 args.expectations_filename),
373 tests=args.tests, configs=args.configs, 404 tests=args.tests, configs=args.configs,
374 actuals_base_url=args.actuals_base_url, 405 actuals_base_url=args.actuals_base_url,
375 actuals_filename=args.actuals_filename, 406 actuals_filename=args.actuals_filename,
376 exception_handler=exception_handler, 407 exception_handler=exception_handler,
377 add_new=args.add_new) 408 add_new=args.add_new, bugs=args.bugs, notes=args.notes,
409 mark_unreviewed=args.unreviewed)
378 try: 410 try:
379 rebaseliner.RebaselineSubdir(builder=builder) 411 rebaseliner.RebaselineSubdir(builder=builder)
380 except BaseException as e: 412 except BaseException as e:
381 exception_handler.RaiseExceptionOrContinue(e) 413 exception_handler.RaiseExceptionOrContinue(e)
382 else: 414 else:
383 exception_handler.RaiseExceptionOrContinue(_InternalException( 415 exception_handler.RaiseExceptionOrContinue(_InternalException(
384 'expectations_json_file %s not found' % expectations_json_file)) 416 'expectations_json_file %s not found' % expectations_json_file))
385 417
386 exception_handler.ReportAllFailures() 418 exception_handler.ReportAllFailures()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698