Chromium Code Reviews| 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 |