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