| OLD | NEW |
| 1 # Copyright (c) 2010 Google Inc. All rights reserved. | 1 # Copyright (c) 2010 Google Inc. All rights reserved. |
| 2 # | 2 # |
| 3 # Redistribution and use in source and binary forms, with or without | 3 # Redistribution and use in source and binary forms, with or without |
| 4 # modification, are permitted provided that the following conditions are | 4 # modification, are permitted provided that the following conditions are |
| 5 # met: | 5 # met: |
| 6 # | 6 # |
| 7 # * Redistributions of source code must retain the above copyright | 7 # * Redistributions of source code must retain the above copyright |
| 8 # notice, this list of conditions and the following disclaimer. | 8 # notice, this list of conditions and the following disclaimer. |
| 9 # * Redistributions in binary form must reproduce the above | 9 # * Redistributions in binary form must reproduce the above |
| 10 # copyright notice, this list of conditions and the following disclaimer | 10 # copyright notice, this list of conditions and the following disclaimer |
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 80 The reason for keeping track of these changes together is that after rebasel
ining | 80 The reason for keeping track of these changes together is that after rebasel
ining |
| 81 many tests in parallel by subprocesses, we can update the git staged files l
ist | 81 many tests in parallel by subprocesses, we can update the git staged files l
ist |
| 82 and the TestExpectations all at once at the end. | 82 and the TestExpectations all at once at the end. |
| 83 | 83 |
| 84 Each subprocess can communicate some set of changes by outputting JSON lines
, | 84 Each subprocess can communicate some set of changes by outputting JSON lines
, |
| 85 and another process can aggregate the changes. | 85 and another process can aggregate the changes. |
| 86 | 86 |
| 87 TODO(qyearsley): Refactor this so that lines to remove are only tracked in o
ne format. | 87 TODO(qyearsley): Refactor this so that lines to remove are only tracked in o
ne format. |
| 88 """ | 88 """ |
| 89 def __init__(self, files_to_add=None, files_to_delete=None, lines_to_remove=
None): | 89 def __init__(self, files_to_add=None, files_to_delete=None, lines_to_remove=
None): |
| 90 self._files_to_add = set(files_to_add or []) | 90 self.files_to_add = files_to_add or [] |
| 91 self._files_to_delete = set(files_to_delete or []) | 91 self.files_to_delete = files_to_delete or [] |
| 92 lines_to_remove = lines_to_remove or {} | 92 self.lines_to_remove = lines_to_remove or {} |
| 93 self._lines_to_remove = {t: set(builders) for t, builders in lines_to_re
move.iteritems()} | |
| 94 | 93 |
| 95 def add_file(self, path): | 94 def add_file(self, path): |
| 96 self._files_to_add.add(path) | 95 self.files_to_add.append(path) |
| 97 | 96 |
| 98 def delete_file(self, path): | 97 def delete_file(self, path): |
| 99 self._files_to_delete.add(path) | 98 self.files_to_delete.append(path) |
| 100 | 99 |
| 101 def remove_line(self, test, builder): | 100 def remove_line(self, test, builder): |
| 102 if test not in self._lines_to_remove: | 101 if test not in self.lines_to_remove: |
| 103 self._lines_to_remove[test] = set() | 102 self.lines_to_remove[test] = [] |
| 104 self._lines_to_remove[test].add(builder) | 103 self.lines_to_remove[test].append(builder) |
| 105 | |
| 106 @property | |
| 107 def files_to_add(self): | |
| 108 return sorted(self._files_to_add - self._files_to_delete) | |
| 109 | |
| 110 @property | |
| 111 def files_to_delete(self): | |
| 112 return sorted(self._files_to_delete - self._files_to_add) | |
| 113 | |
| 114 @property | |
| 115 def lines_to_remove(self): | |
| 116 return {t: sorted(set(builders)) for t, builders in sorted(self._lines_t
o_remove.iteritems())} | |
| 117 | 104 |
| 118 def to_dict(self): | 105 def to_dict(self): |
| 119 remove_lines = [] | 106 remove_lines = [] |
| 120 for test in sorted(self.lines_to_remove): | 107 for test in self.lines_to_remove: |
| 121 for builder in sorted(set(self.lines_to_remove[test])): | 108 for builder in self.lines_to_remove[test]: |
| 122 remove_lines.append({'test': test, 'builder': builder}) | 109 remove_lines.append({'test': test, 'builder': builder}) |
| 123 return { | 110 return { |
| 124 'add': self.files_to_add, | 111 'add': list(self.files_to_add), |
| 125 'delete': self.files_to_delete, | 112 'delete': list(self.files_to_delete), |
| 126 'remove-lines': remove_lines, | 113 'remove-lines': remove_lines, |
| 127 } | 114 } |
| 128 | 115 |
| 129 @staticmethod | 116 @staticmethod |
| 130 def from_dict(change_dict): | 117 def from_dict(change_dict): |
| 131 files_to_add = set() | 118 files_to_add = set() |
| 132 files_to_delete = set() | 119 files_to_delete = set() |
| 133 lines_to_remove = {} | 120 lines_to_remove = {} |
| 134 if 'add' in change_dict: | 121 if 'add' in change_dict: |
| 135 files_to_add.update(change_dict['add']) | 122 files_to_add.update(change_dict['add']) |
| 136 if 'delete' in change_dict: | 123 if 'delete' in change_dict: |
| 137 files_to_delete.update(change_dict['delete']) | 124 files_to_delete.update(change_dict['delete']) |
| 138 if 'remove-lines' in change_dict: | 125 if 'remove-lines' in change_dict: |
| 139 for line_to_remove in change_dict['remove-lines']: | 126 for line_to_remove in change_dict['remove-lines']: |
| 140 test = line_to_remove['test'] | 127 test = line_to_remove['test'] |
| 141 builder = line_to_remove['builder'] | 128 builder = line_to_remove['builder'] |
| 142 if test not in lines_to_remove: | 129 if test not in lines_to_remove: |
| 143 lines_to_remove[test] = [] | 130 lines_to_remove[test] = [] |
| 144 lines_to_remove[test].append(builder) | 131 lines_to_remove[test].append(builder) |
| 145 return ChangeSet( | 132 return ChangeSet( |
| 146 files_to_add=files_to_add, | 133 files_to_add=list(files_to_add), |
| 147 files_to_delete=files_to_delete, | 134 files_to_delete=list(files_to_delete), |
| 148 lines_to_remove=lines_to_remove) | 135 lines_to_remove=lines_to_remove) |
| 149 | 136 |
| 150 def update(self, other): | 137 def update(self, other): |
| 151 assert isinstance(other, ChangeSet) | 138 assert isinstance(other, ChangeSet) |
| 152 self._files_to_add.update(other.files_to_add) | 139 assert type(other.lines_to_remove) is dict |
| 153 self._files_to_delete.update(other.files_to_delete) | 140 self.files_to_add.extend(other.files_to_add) |
| 141 self.files_to_delete.extend(other.files_to_delete) |
| 154 for test in other.lines_to_remove: | 142 for test in other.lines_to_remove: |
| 155 if test not in self._lines_to_remove: | 143 if test not in self.lines_to_remove: |
| 156 self._lines_to_remove[test] = set() | 144 self.lines_to_remove[test] = [] |
| 157 self._lines_to_remove[test].update(other.lines_to_remove[test]) | 145 self.lines_to_remove[test].extend(other.lines_to_remove[test]) |
| 158 | 146 |
| 159 | 147 |
| 160 class BaseInternalRebaselineCommand(AbstractRebaseliningCommand): | 148 class BaseInternalRebaselineCommand(AbstractRebaseliningCommand): |
| 161 """Base class for rebaseline-related commands that are intended to be used b
y other commands.""" | 149 """Base class for rebaseline-related commands that are intended to be used b
y other commands.""" |
| 162 # Not overriding execute() - pylint: disable=abstract-method | 150 # Not overriding execute() - pylint: disable=abstract-method |
| 163 | 151 |
| 164 def __init__(self): | 152 def __init__(self): |
| 165 super(BaseInternalRebaselineCommand, self).__init__(options=[ | 153 super(BaseInternalRebaselineCommand, self).__init__(options=[ |
| 166 self.results_directory_option, | 154 self.results_directory_option, |
| 167 self.suffixes_option, | 155 self.suffixes_option, |
| (...skipping 324 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 492 def _port_skips_test(port, test, generic_expectations, full_expectations): | 480 def _port_skips_test(port, test, generic_expectations, full_expectations): |
| 493 fs = port.host.filesystem | 481 fs = port.host.filesystem |
| 494 if port.default_smoke_test_only(): | 482 if port.default_smoke_test_only(): |
| 495 smoke_test_filename = fs.join(port.layout_tests_dir(), 'SmokeTests') | 483 smoke_test_filename = fs.join(port.layout_tests_dir(), 'SmokeTests') |
| 496 if fs.exists(smoke_test_filename) and test not in fs.read_text_file(
smoke_test_filename): | 484 if fs.exists(smoke_test_filename) and test not in fs.read_text_file(
smoke_test_filename): |
| 497 return True | 485 return True |
| 498 | 486 |
| 499 return (SKIP in full_expectations.get_expectations(test) and | 487 return (SKIP in full_expectations.get_expectations(test) and |
| 500 SKIP not in generic_expectations.get_expectations(test)) | 488 SKIP not in generic_expectations.get_expectations(test)) |
| 501 | 489 |
| 502 def _run_in_parallel(self, commands): | 490 def _run_in_parallel(self, commands, update_scm=True): |
| 503 if not commands: | 491 if not commands: |
| 504 return ChangeSet() | 492 return {} |
| 505 | 493 |
| 506 command_results = self._tool.executive.run_in_parallel(commands) | 494 command_results = self._tool.executive.run_in_parallel(commands) |
| 507 for _, _, stderr in command_results: | 495 for _, _, stderr in command_results: |
| 508 if stderr: | 496 if stderr: |
| 509 _log.error(stderr) | 497 _log.error(stderr) |
| 510 | 498 |
| 511 return self._extract_scm_changes(command_results) | 499 change_set = self._extract_scm_changes(command_results) |
| 512 | 500 |
| 513 def _rebaseline(self, options, test_prefix_list): | 501 # TODO(qyearsley): Instead of updating the SCM state here, aggregate cha
nges |
| 502 # and update once in _rebaseline. See http://crbug.com/639410. |
| 503 if update_scm: |
| 504 if change_set.files_to_delete: |
| 505 self._tool.scm().delete_list(change_set.files_to_delete) |
| 506 if change_set.files_to_add: |
| 507 self._tool.scm().add_list(change_set.files_to_add) |
| 508 |
| 509 return change_set.lines_to_remove |
| 510 |
| 511 def _rebaseline(self, options, test_prefix_list, update_scm=True): |
| 514 """Downloads new baselines in parallel, then updates expectations files | 512 """Downloads new baselines in parallel, then updates expectations files |
| 515 and optimizes baselines. | 513 and optimizes baselines. |
| 516 | 514 |
| 517 Args: | 515 Args: |
| 518 options: An object with the options passed to the current command. | 516 options: An object with the options passed to the current command. |
| 519 test_prefix_list: A map of test names to Build objects to file suffi
xes | 517 test_prefix_list: A map of test names to Build objects to file suffi
xes |
| 520 for new baselines. For example: | 518 for new baselines. For example: |
| 521 { | 519 { |
| 522 "some/test.html": {Build("builder-1", 412): ["txt"], Build("
builder-2", 100): ["txt"]}, | 520 "some/test.html": {Build("builder-1", 412): ["txt"], Build("
builder-2", 100): ["txt"]}, |
| 523 "some/other.html": {Build("builder-1", 412): ["txt"]} | 521 "some/other.html": {Build("builder-1", 412): ["txt"]} |
| 524 } | 522 } |
| 525 This would mean that new text baselines should be downloaded for | 523 This would mean that new text baselines should be downloaded for |
| 526 "some/test.html" on both builder-1 (build 412) and builder-2 | 524 "some/test.html" on both builder-1 (build 412) and builder-2 |
| 527 (build 100), and new text baselines should be downloaded for | 525 (build 100), and new text baselines should be downloaded for |
| 528 "some/other.html" but only from builder-1. | 526 "some/other.html" but only from builder-1. |
| 529 TODO(qyearsley): Replace test_prefix_list everywhere with some | 527 TODO(qyearsley): Replace test_prefix_list everywhere with some |
| 530 sort of class that contains the same data. | 528 sort of class that contains the same data. |
| 529 update_scm: If True, commands like `git add` and `git rm` will be ru
n. |
| 531 """ | 530 """ |
| 532 for test, builds_to_check in sorted(test_prefix_list.items()): | 531 for test, builds_to_check in sorted(test_prefix_list.items()): |
| 533 _log.info("Rebaselining %s", test) | 532 _log.info("Rebaselining %s", test) |
| 534 for build, suffixes in sorted(builds_to_check.items()): | 533 for build, suffixes in sorted(builds_to_check.items()): |
| 535 _log.debug(" %s: %s", build, ",".join(suffixes)) | 534 _log.debug(" %s: %s", build, ",".join(suffixes)) |
| 536 | 535 |
| 537 copy_baseline_commands, rebaseline_commands, extra_lines_to_remove = sel
f._rebaseline_commands( | 536 copy_baseline_commands, rebaseline_commands, extra_lines_to_remove = sel
f._rebaseline_commands( |
| 538 test_prefix_list, options) | 537 test_prefix_list, options) |
| 538 lines_to_remove = {} |
| 539 | 539 |
| 540 change_set = ChangeSet(lines_to_remove=extra_lines_to_remove) | 540 self._run_in_parallel(copy_baseline_commands, update_scm=update_scm) |
| 541 lines_to_remove = self._run_in_parallel(rebaseline_commands, update_scm=
update_scm) |
| 541 | 542 |
| 542 change_set.update(self._run_in_parallel(copy_baseline_commands)) | 543 for test in extra_lines_to_remove: |
| 543 change_set.update(self._run_in_parallel(rebaseline_commands)) | 544 if test in lines_to_remove: |
| 545 lines_to_remove[test] = lines_to_remove[test] + extra_lines_to_r
emove[test] |
| 546 else: |
| 547 lines_to_remove[test] = extra_lines_to_remove[test] |
| 544 | 548 |
| 545 if change_set.lines_to_remove: | 549 if lines_to_remove: |
| 546 self._update_expectations_files(change_set.lines_to_remove) | 550 self._update_expectations_files(lines_to_remove) |
| 547 | 551 |
| 548 if options.optimize: | 552 if options.optimize: |
| 549 # TODO(wkorman): Consider changing temporary branch to base off of H
EAD rather than | 553 # TODO(wkorman): Consider changing temporary branch to base off of H
EAD rather than |
| 550 # origin/master to ensure we run baseline optimization processes wit
h the same code as | 554 # origin/master to ensure we run baseline optimization processes wit
h the same code as |
| 551 # auto-rebaseline itself. | 555 # auto-rebaseline itself. |
| 552 change_set.update(self._run_in_parallel(self._optimize_baselines(tes
t_prefix_list, options.verbose))) | 556 self._run_in_parallel(self._optimize_baselines(test_prefix_list, opt
ions.verbose), update_scm=update_scm) |
| 553 | |
| 554 if change_set.files_to_delete: | |
| 555 self._tool.scm().delete_list(change_set.files_to_delete) | |
| 556 if change_set.files_to_add: | |
| 557 self._tool.scm().add_list(change_set.files_to_add) | |
| 558 | 557 |
| 559 def _suffixes_for_actual_failures(self, test, build, existing_suffixes): | 558 def _suffixes_for_actual_failures(self, test, build, existing_suffixes): |
| 560 """Gets the baseline suffixes for actual mismatch failures in some resul
ts. | 559 """Gets the baseline suffixes for actual mismatch failures in some resul
ts. |
| 561 | 560 |
| 562 Args: | 561 Args: |
| 563 test: A full test path string. | 562 test: A full test path string. |
| 564 build: A Build object. | 563 build: A Build object. |
| 565 existing_suffixes: A collection of all suffixes to consider. | 564 existing_suffixes: A collection of all suffixes to consider. |
| 566 | 565 |
| 567 Returns: | 566 Returns: |
| (...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 684 for test in args: | 683 for test in args: |
| 685 if test not in test_prefix_list: | 684 if test not in test_prefix_list: |
| 686 test_prefix_list[test] = {} | 685 test_prefix_list[test] = {} |
| 687 build = Build(builder) | 686 build = Build(builder) |
| 688 test_prefix_list[test][build] = suffixes_to_update | 687 test_prefix_list[test][build] = suffixes_to_update |
| 689 | 688 |
| 690 if options.verbose: | 689 if options.verbose: |
| 691 _log.debug("rebaseline-json: " + str(test_prefix_list)) | 690 _log.debug("rebaseline-json: " + str(test_prefix_list)) |
| 692 | 691 |
| 693 self._rebaseline(options, test_prefix_list) | 692 self._rebaseline(options, test_prefix_list) |
| OLD | NEW |