| 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 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 67 'base-android-nexus-10': | 67 'base-android-nexus-10': |
| 68 'Test-Android-Nexus10-MaliT604-Arm7-Release', | 68 'Test-Android-Nexus10-MaliT604-Arm7-Release', |
| 69 'base-android-nexus-4': | 69 'base-android-nexus-4': |
| 70 'Test-Android-Nexus4-Adreno320-Arm7-Release', | 70 'Test-Android-Nexus4-Adreno320-Arm7-Release', |
| 71 } | 71 } |
| 72 | 72 |
| 73 | 73 |
| 74 class _InternalException(Exception): | 74 class _InternalException(Exception): |
| 75 pass | 75 pass |
| 76 | 76 |
| 77 # Object that handles exceptions, either raising them immediately or collecting |
| 78 # them to display later on. |
| 79 class ExceptionHandler(object): |
| 80 |
| 81 # params: |
| 82 # keep_going_on_failure: if False, report failures and quit right away; |
| 83 # if True, collect failures until |
| 84 # ReportAllFailures() is called |
| 85 def __init__(self, keep_going_on_failure=False): |
| 86 self._keep_going_on_failure = keep_going_on_failure |
| 87 self._failures_encountered = [] |
| 88 self._exiting = False |
| 89 |
| 90 # Exit the program with the given status value. |
| 91 def _Exit(self, status=1): |
| 92 self._exiting = True |
| 93 sys.exit(status) |
| 94 |
| 95 # We have encountered an exception; either collect the info and keep going, |
| 96 # or exit the program right away. |
| 97 def RaiseExceptionOrContinue(self, e): |
| 98 # If we are already quitting the program, propagate any exceptions |
| 99 # so that the proper exit status will be communicated to the shell. |
| 100 if self._exiting: |
| 101 raise e |
| 102 |
| 103 if self._keep_going_on_failure: |
| 104 print >> sys.stderr, 'WARNING: swallowing exception %s' % e |
| 105 self._failures_encountered.append(e) |
| 106 else: |
| 107 print >> sys.stderr, e |
| 108 print >> sys.stderr, ( |
| 109 'Halting at first exception; to keep going, re-run ' + |
| 110 'with the --keep-going-on-failure option set.') |
| 111 self._Exit() |
| 112 |
| 113 def ReportAllFailures(self): |
| 114 if self._failures_encountered: |
| 115 print >> sys.stderr, ('Encountered %d failures (see above).' % |
| 116 len(self._failures_encountered)) |
| 117 self._Exit() |
| 118 |
| 119 |
| 77 # Object that rebaselines a JSON expectations file (not individual image files). | 120 # Object that rebaselines a JSON expectations file (not individual image files). |
| 78 class JsonRebaseliner(object): | 121 class JsonRebaseliner(object): |
| 79 | 122 |
| 80 # params: | 123 # params: |
| 81 # expectations_root: root directory of all expectations JSON files | 124 # expectations_root: root directory of all expectations JSON files |
| 82 # expectations_filename: filename (under expectations_root) of JSON | 125 # expectations_filename: filename (under expectations_root) of JSON |
| 83 # expectations file; typically | 126 # expectations file; typically |
| 84 # "expected-results.json" | 127 # "expected-results.json" |
| 85 # actuals_base_url: base URL from which to read actual-result JSON files | 128 # actuals_base_url: base URL from which to read actual-result JSON files |
| 86 # actuals_filename: filename (under actuals_base_url) from which to read a | 129 # actuals_filename: filename (under actuals_base_url) from which to read a |
| 87 # summary of results; typically "actual-results.json" | 130 # summary of results; typically "actual-results.json" |
| 131 # exception_handler: reference to rebaseline.ExceptionHandler object |
| 88 # tests: list of tests to rebaseline, or None if we should rebaseline | 132 # tests: list of tests to rebaseline, or None if we should rebaseline |
| 89 # whatever files the JSON results summary file tells us to | 133 # whatever files the JSON results summary file tells us to |
| 90 # configs: which configs to run for each test, or None if we should | 134 # configs: which configs to run for each test, or None if we should |
| 91 # rebaseline whatever configs the JSON results summary file tells | 135 # rebaseline whatever configs the JSON results summary file tells |
| 92 # us to | 136 # us to |
| 93 # add_new: if True, add expectations for tests which don't have any yet | 137 # add_new: if True, add expectations for tests which don't have any yet |
| 94 def __init__(self, expectations_root, expectations_filename, | 138 def __init__(self, expectations_root, expectations_filename, |
| 95 actuals_base_url, actuals_filename, | 139 actuals_base_url, actuals_filename, exception_handler, |
| 96 tests=None, configs=None, add_new=False): | 140 tests=None, configs=None, add_new=False): |
| 97 self._expectations_root = expectations_root | 141 self._expectations_root = expectations_root |
| 98 self._expectations_filename = expectations_filename | 142 self._expectations_filename = expectations_filename |
| 99 self._tests = tests | 143 self._tests = tests |
| 100 self._configs = configs | 144 self._configs = configs |
| 101 self._actuals_base_url = actuals_base_url | 145 self._actuals_base_url = actuals_base_url |
| 102 self._actuals_filename = actuals_filename | 146 self._actuals_filename = actuals_filename |
| 147 self._exception_handler = exception_handler |
| 103 self._add_new = add_new | 148 self._add_new = add_new |
| 104 self._testname_pattern = re.compile('(\S+)_(\S+).png') | 149 self._testname_pattern = re.compile('(\S+)_(\S+).png') |
| 105 | 150 |
| 106 # Returns the full contents of filepath, as a single string. | 151 # Returns the full contents of filepath, as a single string. |
| 107 # If filepath looks like a URL, try to read it that way instead of as | 152 # If filepath looks like a URL, try to read it that way instead of as |
| 108 # a path on local storage. | 153 # a path on local storage. |
| 109 # | 154 # |
| 110 # Raises _InternalException if there is a problem. | 155 # Raises _InternalException if there is a problem. |
| 111 def _GetFileContents(self, filepath): | 156 def _GetFileContents(self, filepath): |
| 112 if filepath.startswith('http:') or filepath.startswith('https:'): | 157 if filepath.startswith('http:') or filepath.startswith('https:'): |
| (...skipping 123 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 236 parser.add_argument('--expectations-filename', | 281 parser.add_argument('--expectations-filename', |
| 237 help='filename (under EXPECTATIONS_ROOT) to read ' + | 282 help='filename (under EXPECTATIONS_ROOT) to read ' + |
| 238 'current expectations from, and to write new ' + | 283 'current expectations from, and to write new ' + |
| 239 'expectations into; defaults to %(default)s', | 284 'expectations into; defaults to %(default)s', |
| 240 default='expected-results.json') | 285 default='expected-results.json') |
| 241 parser.add_argument('--expectations-root', | 286 parser.add_argument('--expectations-root', |
| 242 help='root of expectations directory to update-- should ' + | 287 help='root of expectations directory to update-- should ' + |
| 243 'contain one or more base-* subdirectories. Defaults to ' + | 288 'contain one or more base-* subdirectories. Defaults to ' + |
| 244 '%(default)s', | 289 '%(default)s', |
| 245 default='.') | 290 default='.') |
| 291 parser.add_argument('--keep-going-on-failure', action='store_true', |
| 292 help='instead of halting at the first error encountered, ' + |
| 293 'keep going and rebaseline as many tests as possible, ' + |
| 294 'and then report the full set of errors at the end') |
| 246 parser.add_argument('--subdirs', metavar='SUBDIR', nargs='+', | 295 parser.add_argument('--subdirs', metavar='SUBDIR', nargs='+', |
| 247 help='which platform subdirectories to rebaseline; ' + | 296 help='which platform subdirectories to rebaseline; ' + |
| 248 'if unspecified, rebaseline all subdirs, same as ' + | 297 'if unspecified, rebaseline all subdirs, same as ' + |
| 249 '"--subdirs %s"' % ' '.join(sorted(SUBDIR_MAPPING.keys()))) | 298 '"--subdirs %s"' % ' '.join(sorted(SUBDIR_MAPPING.keys()))) |
| 250 # TODO(epoger): Add test that exercises --tests argument. | 299 # TODO(epoger): Add test that exercises --tests argument. |
| 251 parser.add_argument('--tests', metavar='TEST', nargs='+', | 300 parser.add_argument('--tests', metavar='TEST', nargs='+', |
| 252 help='which tests to rebaseline, e.g. ' + | 301 help='which tests to rebaseline, e.g. ' + |
| 253 '"--tests aaclip bigmatrix", as a filter over the full ' + | 302 '"--tests aaclip bigmatrix", as a filter over the full ' + |
| 254 'set of results in ACTUALS_FILENAME; if unspecified, ' + | 303 'set of results in ACTUALS_FILENAME; if unspecified, ' + |
| 255 'rebaseline *all* tests that are available.') | 304 'rebaseline *all* tests that are available.') |
| 256 args = parser.parse_args() | 305 args = parser.parse_args() |
| 306 exception_handler = ExceptionHandler( |
| 307 keep_going_on_failure=args.keep_going_on_failure) |
| 257 if args.subdirs: | 308 if args.subdirs: |
| 258 subdirs = args.subdirs | 309 subdirs = args.subdirs |
| 259 missing_json_is_fatal = True | 310 missing_json_is_fatal = True |
| 260 else: | 311 else: |
| 261 subdirs = sorted(SUBDIR_MAPPING.keys()) | 312 subdirs = sorted(SUBDIR_MAPPING.keys()) |
| 262 missing_json_is_fatal = False | 313 missing_json_is_fatal = False |
| 263 for subdir in subdirs: | 314 for subdir in subdirs: |
| 264 if not subdir in SUBDIR_MAPPING.keys(): | 315 if not subdir in SUBDIR_MAPPING.keys(): |
| 265 raise Exception(('unrecognized platform subdir "%s"; ' + | 316 raise Exception(('unrecognized platform subdir "%s"; ' + |
| 266 'should be one of %s') % ( | 317 'should be one of %s') % ( |
| 267 subdir, SUBDIR_MAPPING.keys())) | 318 subdir, SUBDIR_MAPPING.keys())) |
| 268 builder = SUBDIR_MAPPING[subdir] | 319 builder = SUBDIR_MAPPING[subdir] |
| 269 | 320 |
| 270 # We instantiate different Rebaseliner objects depending | 321 # We instantiate different Rebaseliner objects depending |
| 271 # on whether we are rebaselining an expected-results.json file, or | 322 # on whether we are rebaselining an expected-results.json file, or |
| 272 # individual image files. Different gm-expected subdirectories may move | 323 # individual image files. Different gm-expected subdirectories may move |
| 273 # from individual image files to JSON-format expectations at different | 324 # from individual image files to JSON-format expectations at different |
| 274 # times, so we need to make this determination per subdirectory. | 325 # times, so we need to make this determination per subdirectory. |
| 275 # | 326 # |
| 276 # See https://goto.google.com/ChecksumTransitionDetail | 327 # See https://goto.google.com/ChecksumTransitionDetail |
| 277 expectations_json_file = os.path.join(args.expectations_root, subdir, | 328 expectations_json_file = os.path.join(args.expectations_root, subdir, |
| 278 args.expectations_filename) | 329 args.expectations_filename) |
| 279 if os.path.isfile(expectations_json_file): | 330 if os.path.isfile(expectations_json_file): |
| 280 rebaseliner = JsonRebaseliner( | 331 rebaseliner = JsonRebaseliner( |
| 281 expectations_root=args.expectations_root, | 332 expectations_root=args.expectations_root, |
| 282 expectations_filename=args.expectations_filename, | 333 expectations_filename=args.expectations_filename, |
| 283 tests=args.tests, configs=args.configs, | 334 tests=args.tests, configs=args.configs, |
| 284 actuals_base_url=args.actuals_base_url, | 335 actuals_base_url=args.actuals_base_url, |
| 285 actuals_filename=args.actuals_filename, | 336 actuals_filename=args.actuals_filename, |
| 337 exception_handler=exception_handler, |
| 286 add_new=args.add_new) | 338 add_new=args.add_new) |
| 287 else: | 339 else: |
| 288 # TODO(epoger): When we get rid of the ImageRebaseliner implementation, | 340 # TODO(epoger): When we get rid of the ImageRebaseliner implementation, |
| 289 # we should raise an Exception in this case (no JSON expectations file | 341 # we should raise an Exception in this case (no JSON expectations file |
| 290 # found to update), to prevent a recurrence of | 342 # found to update), to prevent a recurrence of |
| 291 # https://code.google.com/p/skia/issues/detail?id=1403 ('rebaseline.py | 343 # https://code.google.com/p/skia/issues/detail?id=1403 ('rebaseline.py |
| 292 # script fails with misleading output when run outside of gm-expected | 344 # script fails with misleading output when run outside of gm-expected |
| 293 # dir') | 345 # dir') |
| 294 rebaseliner = rebaseline_imagefiles.ImageRebaseliner( | 346 rebaseliner = rebaseline_imagefiles.ImageRebaseliner( |
| 295 expectations_root=args.expectations_root, | 347 expectations_root=args.expectations_root, |
| 296 tests=args.tests, configs=args.configs, | 348 tests=args.tests, configs=args.configs, |
| 297 dry_run=args.dry_run, | 349 dry_run=args.dry_run, |
| 298 json_base_url=args.actuals_base_url, | 350 json_base_url=args.actuals_base_url, |
| 299 json_filename=args.actuals_filename, | 351 json_filename=args.actuals_filename, |
| 352 exception_handler=exception_handler, |
| 300 add_new=args.add_new, | 353 add_new=args.add_new, |
| 301 missing_json_is_fatal=missing_json_is_fatal) | 354 missing_json_is_fatal=missing_json_is_fatal) |
| 355 |
| 302 try: | 356 try: |
| 303 rebaseliner.RebaselineSubdir(subdir=subdir, builder=builder) | 357 rebaseliner.RebaselineSubdir(subdir=subdir, builder=builder) |
| 304 except BaseException as e: | 358 except BaseException as e: |
| 305 print >> sys.stderr, e | 359 exception_handler.RaiseExceptionOrContinue(e) |
| 306 sys.exit(1) | 360 |
| 361 exception_handler.ReportAllFailures() |
| OLD | NEW |