OLD | NEW |
---|---|
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 141 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
152 # which updated expectations should be | 152 # which updated expectations should be |
153 # written; typically the same as | 153 # written; typically the same as |
154 # expectations_input_filename, to overwrite | 154 # expectations_input_filename, to overwrite |
155 # the old content | 155 # the old content |
156 # actuals_base_url: base URL from which to read actual-result JSON files | 156 # actuals_base_url: base URL from which to read actual-result JSON files |
157 # actuals_filename: filename (under actuals_base_url) from which to read a | 157 # actuals_filename: filename (under actuals_base_url) from which to read a |
158 # summary of results; typically "actual-results.json" | 158 # summary of results; typically "actual-results.json" |
159 # exception_handler: reference to rebaseline.ExceptionHandler object | 159 # exception_handler: reference to rebaseline.ExceptionHandler object |
160 # tests: list of tests to rebaseline, or None if we should rebaseline | 160 # tests: list of tests to rebaseline, or None if we should rebaseline |
161 # whatever files the JSON results summary file tells us to | 161 # whatever files the JSON results summary file tells us to |
162 # Ignored if 'skip_pattern_matching' is set to True | |
162 # configs: which configs to run for each test, or None if we should | 163 # configs: which configs to run for each test, or None if we should |
163 # rebaseline whatever configs the JSON results summary file tells | 164 # rebaseline whatever configs the JSON results summary file tells |
164 # us to | 165 # us to |
166 # Ignored if 'skip_pattern_matching' is set to True | |
165 # add_new: if True, add expectations for tests which don't have any yet | 167 # add_new: if True, add expectations for tests which don't have any yet |
166 # bugs: optional list of bug numbers which pertain to these expectations | 168 # bugs: optional list of bug numbers which pertain to these expectations |
167 # notes: free-form text notes to add to all updated expectations | 169 # notes: free-form text notes to add to all updated expectations |
168 # mark_unreviewed: if True, mark these expectations as NOT having been | 170 # mark_unreviewed: if True, mark these expectations as NOT having been |
169 # reviewed by a human; otherwise, leave that field blank. | 171 # reviewed by a human; otherwise, leave that field blank. |
170 # Currently, there is no way to make this script mark | 172 # Currently, there is no way to make this script mark |
171 # expectations as reviewed-by-human=True. | 173 # expectations as reviewed-by-human=True. |
172 # TODO(epoger): Add that capability to a review tool. | 174 # TODO(epoger): Add that capability to a review tool. |
173 # mark_ignore_failure: if True, mark failures of a given test as being | 175 # mark_ignore_failure: if True, mark failures of a given test as being |
174 # ignored. | 176 # ignored. |
177 # skip_pattern_matching: if True, do not attempt to skip tests/configs | |
178 # matching input parameters 'tests'/'configs'. | |
175 def __init__(self, expectations_root, expectations_input_filename, | 179 def __init__(self, expectations_root, expectations_input_filename, |
176 expectations_output_filename, actuals_base_url, | 180 expectations_output_filename, actuals_base_url, |
177 actuals_filename, exception_handler, | 181 actuals_filename, exception_handler, |
178 tests=None, configs=None, add_new=False, bugs=None, notes=None, | 182 tests=None, configs=None, add_new=False, bugs=None, notes=None, |
179 mark_unreviewed=None, mark_ignore_failure=False, | 183 mark_unreviewed=None, mark_ignore_failure=False, |
180 from_trybot=False): | 184 from_trybot=False, skip_pattern_matching=False): |
epoger
2013/10/10 19:51:39
Please add from_trybot to the docstring (I know yo
scroggo
2013/10/10 20:18:58
Done.
| |
181 self._expectations_root = expectations_root | 185 self._expectations_root = expectations_root |
182 self._expectations_input_filename = expectations_input_filename | 186 self._expectations_input_filename = expectations_input_filename |
183 self._expectations_output_filename = expectations_output_filename | 187 self._expectations_output_filename = expectations_output_filename |
184 self._tests = tests | 188 self._tests = tests |
185 self._configs = configs | 189 self._configs = configs |
186 self._actuals_base_url = actuals_base_url | 190 self._actuals_base_url = actuals_base_url |
187 self._actuals_filename = actuals_filename | 191 self._actuals_filename = actuals_filename |
188 self._exception_handler = exception_handler | 192 self._exception_handler = exception_handler |
189 self._add_new = add_new | 193 self._add_new = add_new |
190 self._bugs = bugs | 194 self._bugs = bugs |
191 self._notes = notes | 195 self._notes = notes |
192 self._mark_unreviewed = mark_unreviewed | 196 self._mark_unreviewed = mark_unreviewed |
193 self._mark_ignore_failure = mark_ignore_failure; | 197 self._mark_ignore_failure = mark_ignore_failure; |
194 self._image_filename_re = re.compile(gm_json.IMAGE_FILENAME_PATTERN) | 198 # TODO(scroggo): This is a hack. Since the filenames will not match |
199 # the pattern in skimage, we skip the pattern matching entirely. | |
200 if skip_pattern_matching: | |
epoger
2013/10/10 19:51:39
Maybe, instead of adding the new parameter that ca
scroggo
2013/10/10 20:18:58
Done. I like this much better!
| |
201 self._image_filename_re = None | |
202 if tests or configs: | |
203 raise _InternalException('"skip_pattern_matching" is incompatible with ' | |
204 '"tests" and "configs"') | |
205 else: | |
206 self._image_filename_re = re.compile(gm_json.IMAGE_FILENAME_PATTERN) | |
195 self._using_svn = os.path.isdir(os.path.join(expectations_root, '.svn')) | 207 self._using_svn = os.path.isdir(os.path.join(expectations_root, '.svn')) |
196 self._from_trybot = from_trybot | 208 self._from_trybot = from_trybot |
197 | 209 |
198 # Executes subprocess.call(cmd). | 210 # Executes subprocess.call(cmd). |
199 # Raises an Exception if the command fails. | 211 # Raises an Exception if the command fails. |
200 def _Call(self, cmd): | 212 def _Call(self, cmd): |
201 if subprocess.call(cmd) != 0: | 213 if subprocess.call(cmd) != 0: |
202 raise _InternalException('error running command: ' + ' '.join(cmd)) | 214 raise _InternalException('error running command: ' + ' '.join(cmd)) |
203 | 215 |
204 # Returns the full contents of filepath, as a single string. | 216 # Returns the full contents of filepath, as a single string. |
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
286 expected_results = expectations_dict.get(gm_json.JSONKEY_EXPECTEDRESULTS) | 298 expected_results = expectations_dict.get(gm_json.JSONKEY_EXPECTEDRESULTS) |
287 if not expected_results: | 299 if not expected_results: |
288 expected_results = {} | 300 expected_results = {} |
289 expectations_dict[gm_json.JSONKEY_EXPECTEDRESULTS] = expected_results | 301 expectations_dict[gm_json.JSONKEY_EXPECTEDRESULTS] = expected_results |
290 | 302 |
291 # Update the expectations in memory, skipping any tests/configs that | 303 # Update the expectations in memory, skipping any tests/configs that |
292 # the caller asked to exclude. | 304 # the caller asked to exclude. |
293 skipped_images = [] | 305 skipped_images = [] |
294 if results_to_update: | 306 if results_to_update: |
295 for (image_name, image_results) in results_to_update.iteritems(): | 307 for (image_name, image_results) in results_to_update.iteritems(): |
296 (test, config) = self._image_filename_re.match(image_name).groups() | 308 if self._image_filename_re: |
297 if self._tests: | 309 (test, config) = self._image_filename_re.match(image_name).groups() |
298 if test not in self._tests: | 310 if self._tests: |
299 skipped_images.append(image_name) | 311 if test not in self._tests: |
300 continue | 312 skipped_images.append(image_name) |
301 if self._configs: | 313 continue |
302 if config not in self._configs: | 314 if self._configs: |
303 skipped_images.append(image_name) | 315 if config not in self._configs: |
304 continue | 316 skipped_images.append(image_name) |
317 continue | |
305 if not expected_results.get(image_name): | 318 if not expected_results.get(image_name): |
306 expected_results[image_name] = {} | 319 expected_results[image_name] = {} |
307 expected_results[image_name]\ | 320 expected_results[image_name]\ |
308 [gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS]\ | 321 [gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS]\ |
309 = [image_results] | 322 = [image_results] |
310 if self._mark_unreviewed: | 323 if self._mark_unreviewed: |
311 expected_results[image_name]\ | 324 expected_results[image_name]\ |
312 [gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED]\ | 325 [gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED]\ |
313 = False | 326 = False |
314 if self._mark_ignore_failure: | 327 if self._mark_ignore_failure: |
(...skipping 12 matching lines...) Expand all Loading... | |
327 # Write out updated expectations. | 340 # Write out updated expectations. |
328 expectations_output_filepath = os.path.join( | 341 expectations_output_filepath = os.path.join( |
329 self._expectations_root, builder, self._expectations_output_filename) | 342 self._expectations_root, builder, self._expectations_output_filename) |
330 gm_json.WriteToFile(expectations_dict, expectations_output_filepath) | 343 gm_json.WriteToFile(expectations_dict, expectations_output_filepath) |
331 | 344 |
332 # Mark the JSON file as plaintext, so text-style diffs can be applied. | 345 # Mark the JSON file as plaintext, so text-style diffs can be applied. |
333 # Fixes https://code.google.com/p/skia/issues/detail?id=1442 | 346 # Fixes https://code.google.com/p/skia/issues/detail?id=1442 |
334 if self._using_svn: | 347 if self._using_svn: |
335 self._Call(['svn', 'propset', '--quiet', 'svn:mime-type', | 348 self._Call(['svn', 'propset', '--quiet', 'svn:mime-type', |
336 'text/x-json', expectations_output_filepath]) | 349 'text/x-json', expectations_output_filepath]) |
337 | |
338 # main... | 350 # main... |
339 | 351 |
340 parser = argparse.ArgumentParser( | 352 parser = argparse.ArgumentParser( |
341 formatter_class=argparse.RawDescriptionHelpFormatter, | 353 formatter_class=argparse.RawDescriptionHelpFormatter, |
342 epilog='Here is the full set of builders we know about:' + | 354 epilog='Here is the full set of builders we know about:' + |
343 '\n '.join([''] + sorted(TEST_BUILDERS))) | 355 '\n '.join([''] + sorted(TEST_BUILDERS))) |
344 parser.add_argument('--actuals-base-url', | 356 parser.add_argument('--actuals-base-url', |
345 help=('base URL from which to read files containing JSON ' | 357 help=('base URL from which to read files containing JSON ' |
346 'summaries of actual GM results; defaults to ' | 358 'summaries of actual GM results; defaults to ' |
347 '%(default)s. To get a specific revision (useful for' | 359 '%(default)s. To get a specific revision (useful for ' |
348 'trybots) replace "svn" with "svn-history/r123".'), | 360 'trybots) replace "svn" with "svn-history/r123". ' |
361 'If SKIMAGE is True, defaults to ' + | |
362 gm_json.SKIMAGE_ACTUALS_BASE_URL), | |
349 default='http://skia-autogen.googlecode.com/svn/gm-actual') | 363 default='http://skia-autogen.googlecode.com/svn/gm-actual') |
350 parser.add_argument('--actuals-filename', | 364 parser.add_argument('--actuals-filename', |
351 help=('filename (within builder-specific subdirectories ' | 365 help=('filename (within builder-specific subdirectories ' |
352 'of ACTUALS_BASE_URL) to read a summary of results ' | 366 'of ACTUALS_BASE_URL) to read a summary of results ' |
353 'from; defaults to %(default)s'), | 367 'from; defaults to %(default)s'), |
354 default='actual-results.json') | 368 default='actual-results.json') |
355 parser.add_argument('--add-new', action='store_true', | 369 parser.add_argument('--add-new', action='store_true', |
356 help=('in addition to the standard behavior of ' | 370 help=('in addition to the standard behavior of ' |
357 'updating expectations for failing tests, add ' | 371 'updating expectations for failing tests, add ' |
358 'expectations for tests which don\'t have ' | 372 'expectations for tests which don\'t have ' |
359 'expectations yet.')) | 373 'expectations yet.')) |
360 parser.add_argument('--bugs', metavar='BUG', type=int, nargs='+', | 374 parser.add_argument('--bugs', metavar='BUG', type=int, nargs='+', |
361 help=('Skia bug numbers (under ' | 375 help=('Skia bug numbers (under ' |
362 'https://code.google.com/p/skia/issues/list ) which ' | 376 'https://code.google.com/p/skia/issues/list ) which ' |
363 'pertain to this set of rebaselines.')) | 377 'pertain to this set of rebaselines.')) |
364 parser.add_argument('--builders', metavar='BUILDER', nargs='+', | 378 parser.add_argument('--builders', metavar='BUILDER', nargs='+', |
365 help=('which platforms to rebaseline; ' | 379 help=('which platforms to rebaseline; ' |
366 'if unspecified, rebaseline all known platforms ' | 380 'if unspecified, rebaseline all known platforms ' |
367 '(see below for a list)')) | 381 '(see below for a list)')) |
368 # TODO(epoger): Add test that exercises --configs argument. | 382 # TODO(epoger): Add test that exercises --configs argument. |
369 parser.add_argument('--configs', metavar='CONFIG', nargs='+', | 383 parser.add_argument('--configs', metavar='CONFIG', nargs='+', |
370 help=('which configurations to rebaseline, e.g. ' | 384 help=('which configurations to rebaseline, e.g. ' |
371 '"--configs 565 8888", as a filter over the full set ' | 385 '"--configs 565 8888", as a filter over the full set ' |
372 'of results in ACTUALS_FILENAME; if unspecified, ' | 386 'of results in ACTUALS_FILENAME; if unspecified, ' |
373 'rebaseline *all* configs that are available.')) | 387 'rebaseline *all* configs that are available. ' |
388 'Ignored if SKIMAGE is True.')) | |
374 parser.add_argument('--expectations-filename', | 389 parser.add_argument('--expectations-filename', |
375 help=('filename (under EXPECTATIONS_ROOT) to read ' | 390 help=('filename (under EXPECTATIONS_ROOT) to read ' |
376 'current expectations from, and to write new ' | 391 'current expectations from, and to write new ' |
377 'expectations into (unless a separate ' | 392 'expectations into (unless a separate ' |
378 'EXPECTATIONS_FILENAME_OUTPUT has been specified); ' | 393 'EXPECTATIONS_FILENAME_OUTPUT has been specified); ' |
379 'defaults to %(default)s'), | 394 'defaults to %(default)s'), |
380 default='expected-results.json') | 395 default='expected-results.json') |
381 parser.add_argument('--expectations-filename-output', | 396 parser.add_argument('--expectations-filename-output', |
382 help=('filename (under EXPECTATIONS_ROOT) to write ' | 397 help=('filename (under EXPECTATIONS_ROOT) to write ' |
383 'updated expectations into; by default, overwrites ' | 398 'updated expectations into; by default, overwrites ' |
384 'the input file (EXPECTATIONS_FILENAME)'), | 399 'the input file (EXPECTATIONS_FILENAME)'), |
385 default='') | 400 default='') |
386 parser.add_argument('--expectations-root', | 401 parser.add_argument('--expectations-root', |
387 help=('root of expectations directory to update-- should ' | 402 help=('root of expectations directory to update-- should ' |
388 'contain one or more builder subdirectories. ' | 403 'contain one or more builder subdirectories. ' |
389 'Defaults to %(default)s'), | 404 'Defaults to %(default)s. If SKIMAGE is set, ' |
405 ' defaults to ' + gm_json.SKIMAGE_EXPECTATIONS_ROOT), | |
390 default=os.path.join('expectations', 'gm')) | 406 default=os.path.join('expectations', 'gm')) |
391 parser.add_argument('--keep-going-on-failure', action='store_true', | 407 parser.add_argument('--keep-going-on-failure', action='store_true', |
392 help=('instead of halting at the first error encountered, ' | 408 help=('instead of halting at the first error encountered, ' |
393 'keep going and rebaseline as many tests as ' | 409 'keep going and rebaseline as many tests as ' |
394 'possible, and then report the full set of errors ' | 410 'possible, and then report the full set of errors ' |
395 'at the end')) | 411 'at the end')) |
396 parser.add_argument('--notes', | 412 parser.add_argument('--notes', |
397 help=('free-form text notes to add to all updated ' | 413 help=('free-form text notes to add to all updated ' |
398 'expectations')) | 414 'expectations')) |
399 # TODO(epoger): Add test that exercises --tests argument. | 415 # TODO(epoger): Add test that exercises --tests argument. |
400 parser.add_argument('--tests', metavar='TEST', nargs='+', | 416 parser.add_argument('--tests', metavar='TEST', nargs='+', |
401 help=('which tests to rebaseline, e.g. ' | 417 help=('which tests to rebaseline, e.g. ' |
402 '"--tests aaclip bigmatrix", as a filter over the ' | 418 '"--tests aaclip bigmatrix", as a filter over the ' |
403 'full set of results in ACTUALS_FILENAME; if ' | 419 'full set of results in ACTUALS_FILENAME; if ' |
404 'unspecified, rebaseline *all* tests that are ' | 420 'unspecified, rebaseline *all* tests that are ' |
405 'available.')) | 421 'available. Ignored if SKIMAGE is True.')) |
406 parser.add_argument('--unreviewed', action='store_true', | 422 parser.add_argument('--unreviewed', action='store_true', |
407 help=('mark all expectations modified by this run as ' | 423 help=('mark all expectations modified by this run as ' |
408 '"%s": False' % | 424 '"%s": False' % |
409 gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED)) | 425 gm_json.JSONKEY_EXPECTEDRESULTS_REVIEWED)) |
410 parser.add_argument('--ignore-failure', action='store_true', | 426 parser.add_argument('--ignore-failure', action='store_true', |
411 help=('mark all expectations modified by this run as ' | 427 help=('mark all expectations modified by this run as ' |
412 '"%s": True' % | 428 '"%s": True' % |
413 gm_json.JSONKEY_ACTUALRESULTS_FAILUREIGNORED)) | 429 gm_json.JSONKEY_ACTUALRESULTS_FAILUREIGNORED)) |
414 parser.add_argument('--from-trybot', action='store_true', | 430 parser.add_argument('--from-trybot', action='store_true', |
415 help=('pull the actual-results.json file from the ' | 431 help=('pull the actual-results.json file from the ' |
416 'corresponding trybot, rather than the main builder')) | 432 'corresponding trybot, rather than the main builder')) |
433 parser.add_argument('--skimage', action='store_true', | |
434 help=('Rebaseline skimage results instead of gm. Defaults ' | |
435 'to False. If True, TESTS and CONFIGS are ignored, ' | |
436 'and ACTUALS_BASE_URL and EXPECTATIONS_ROOT are set ' | |
437 'to alternate defaults, specific to skimage.')) | |
417 args = parser.parse_args() | 438 args = parser.parse_args() |
418 exception_handler = ExceptionHandler( | 439 exception_handler = ExceptionHandler( |
419 keep_going_on_failure=args.keep_going_on_failure) | 440 keep_going_on_failure=args.keep_going_on_failure) |
420 if args.builders: | 441 if args.builders: |
421 builders = args.builders | 442 builders = args.builders |
422 missing_json_is_fatal = True | 443 missing_json_is_fatal = True |
423 else: | 444 else: |
424 builders = sorted(TEST_BUILDERS) | 445 builders = sorted(TEST_BUILDERS) |
425 missing_json_is_fatal = False | 446 missing_json_is_fatal = False |
447 if args.skimage: | |
448 # Use a different default if --skimage is specified. | |
449 if args.actuals_base_url == parser.get_default('actuals_base_url'): | |
450 args.actuals_base_url = gm_json.SKIMAGE_ACTUALS_BASE_URL | |
451 if args.expectations_root == parser.get_default('expectations_root'): | |
452 args.expectations_root = gm_json.SKIMAGE_EXPECTATIONS_ROOT | |
426 for builder in builders: | 453 for builder in builders: |
427 if not builder in TEST_BUILDERS: | 454 if not builder in TEST_BUILDERS: |
428 raise Exception(('unrecognized builder "%s"; ' + | 455 raise Exception(('unrecognized builder "%s"; ' + |
429 'should be one of %s') % ( | 456 'should be one of %s') % ( |
430 builder, TEST_BUILDERS)) | 457 builder, TEST_BUILDERS)) |
431 | 458 |
432 expectations_json_file = os.path.join(args.expectations_root, builder, | 459 expectations_json_file = os.path.join(args.expectations_root, builder, |
433 args.expectations_filename) | 460 args.expectations_filename) |
434 if os.path.isfile(expectations_json_file): | 461 if os.path.isfile(expectations_json_file): |
435 rebaseliner = JsonRebaseliner( | 462 rebaseliner = JsonRebaseliner( |
436 expectations_root=args.expectations_root, | 463 expectations_root=args.expectations_root, |
437 expectations_input_filename=args.expectations_filename, | 464 expectations_input_filename=args.expectations_filename, |
438 expectations_output_filename=(args.expectations_filename_output or | 465 expectations_output_filename=(args.expectations_filename_output or |
439 args.expectations_filename), | 466 args.expectations_filename), |
440 tests=args.tests, configs=args.configs, | 467 tests=args.tests, configs=args.configs, |
441 actuals_base_url=args.actuals_base_url, | 468 actuals_base_url=args.actuals_base_url, |
442 actuals_filename=args.actuals_filename, | 469 actuals_filename=args.actuals_filename, |
443 exception_handler=exception_handler, | 470 exception_handler=exception_handler, |
444 add_new=args.add_new, bugs=args.bugs, notes=args.notes, | 471 add_new=args.add_new, bugs=args.bugs, notes=args.notes, |
445 mark_unreviewed=args.unreviewed, | 472 mark_unreviewed=args.unreviewed, |
446 mark_ignore_failure=args.ignore_failure, | 473 mark_ignore_failure=args.ignore_failure, |
447 from_trybot=args.from_trybot) | 474 from_trybot=args.from_trybot, |
475 skip_pattern_matching=args.skimage) | |
448 try: | 476 try: |
449 rebaseliner.RebaselineSubdir(builder=builder) | 477 rebaseliner.RebaselineSubdir(builder=builder) |
450 except: | 478 except: |
451 exception_handler.RaiseExceptionOrContinue() | 479 exception_handler.RaiseExceptionOrContinue() |
452 else: | 480 else: |
453 try: | 481 try: |
454 raise _InternalException('expectations_json_file %s not found' % | 482 raise _InternalException('expectations_json_file %s not found' % |
455 expectations_json_file) | 483 expectations_json_file) |
456 except: | 484 except: |
457 exception_handler.RaiseExceptionOrContinue() | 485 exception_handler.RaiseExceptionOrContinue() |
458 | 486 |
459 exception_handler.ReportAllFailures() | 487 exception_handler.ReportAllFailures() |
OLD | NEW |