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 |