| Index: third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline.py
|
| diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline.py b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline.py
|
| index 699cd2955cb86792da10ec6650d7356f060ebd00..9369bfa61f581c0a24bc29f3799f91f520a644ec 100644
|
| --- a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline.py
|
| +++ b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline.py
|
| @@ -63,17 +63,88 @@ class AbstractRebaseliningCommand(Command):
|
| def __init__(self, options=None):
|
| super(AbstractRebaseliningCommand, self).__init__(options=options)
|
| self._baseline_suffix_list = BASELINE_SUFFIX_LIST
|
| - self._scm_changes = {'add': [], 'delete': [], 'remove-lines': []}
|
| + self._scm_changes = ChangeSet()
|
| self._tool = None
|
|
|
| - def _add_to_scm_later(self, path):
|
| - self._scm_changes['add'].append(path)
|
| -
|
| - def _delete_from_scm_later(self, path):
|
| - self._scm_changes['delete'].append(path)
|
| -
|
| def _print_scm_changes(self):
|
| - print(json.dumps(self._scm_changes))
|
| + print(json.dumps(self._scm_changes.to_dict()))
|
| +
|
| +
|
| +class ChangeSet(object):
|
| + """A record of added and deleted files and TestExpectation lines to remove.
|
| +
|
| + This class groups two kinds of changes together:
|
| + (1) files that have been added or removed.
|
| + (2) lines to remove from TestExpectations.
|
| +
|
| + The reason for keeping track of these changes together is that after rebaselining
|
| + many tests in parallel by subprocesses, we can update the git staged files list
|
| + and the TestExpectations all at once at the end.
|
| +
|
| + Each subprocess can communicate some set of changes by outputting JSON lines,
|
| + and another process can aggregate the changes.
|
| +
|
| + TODO(qyearsley): Refactor this so that:
|
| + - lines to remove are only tracked in one format.
|
| + - files to add and delete are always disjoint sets.
|
| + """
|
| + def __init__(self, files_to_add=None, files_to_delete=None, lines_to_remove=None):
|
| + self.files_to_add = files_to_add or []
|
| + self.files_to_delete = files_to_delete or []
|
| + self.lines_to_remove = lines_to_remove or {}
|
| +
|
| + def add_file(self, path):
|
| + self.files_to_add.append(path)
|
| +
|
| + def delete_file(self, path):
|
| + self.files_to_delete.append(path)
|
| +
|
| + def remove_line(self, test, builder):
|
| + if test not in self.lines_to_remove:
|
| + self.lines_to_remove[test] = []
|
| + self.lines_to_remove[test].append(builder)
|
| +
|
| + def to_dict(self):
|
| + remove_lines = []
|
| + for test in self.lines_to_remove:
|
| + for builder in self.lines_to_remove[test]:
|
| + remove_lines.append({'test': test, 'builder': builder})
|
| + return {
|
| + 'add': list(self.files_to_add),
|
| + 'delete': list(self.files_to_delete),
|
| + 'remove-lines': remove_lines,
|
| + }
|
| +
|
| + @staticmethod
|
| + def from_dict(change_dict):
|
| + files_to_add = set()
|
| + files_to_delete = set()
|
| + lines_to_remove = {}
|
| + if 'add' in change_dict:
|
| + files_to_add.update(change_dict['add'])
|
| + if 'delete' in change_dict:
|
| + files_to_delete.update(change_dict['delete'])
|
| + if 'remove-lines' in change_dict:
|
| + for line_to_remove in change_dict['remove-lines']:
|
| + test = line_to_remove['test']
|
| + builder = line_to_remove['builder']
|
| + if test not in lines_to_remove:
|
| + lines_to_remove[test] = []
|
| + lines_to_remove[test].append(builder)
|
| + return ChangeSet(
|
| + files_to_add=list(files_to_add),
|
| + files_to_delete=list(files_to_delete),
|
| + lines_to_remove=lines_to_remove)
|
| +
|
| + def update(self, other):
|
| + assert isinstance(other, ChangeSet)
|
| + assert type(other.lines_to_remove) is dict
|
| + self.files_to_add.extend(other.files_to_add)
|
| + self.files_to_delete.extend(other.files_to_delete)
|
| + for test in other.lines_to_remove:
|
| + if test not in self.lines_to_remove:
|
| + self.lines_to_remove[test] = []
|
| + self.lines_to_remove[test].extend(other.lines_to_remove[test])
|
|
|
|
|
| class BaseInternalRebaselineCommand(AbstractRebaseliningCommand):
|
| @@ -170,7 +241,7 @@ class CopyExistingBaselinesInternal(BaseInternalRebaselineCommand):
|
| self._tool.filesystem.maybe_make_directory(self._tool.filesystem.dirname(new_baseline))
|
| self._tool.filesystem.copyfile(old_baseline, new_baseline)
|
| if not self._tool.scm().exists(new_baseline):
|
| - self._add_to_scm_later(new_baseline)
|
| + self._scm_changes.add_file(new_baseline)
|
|
|
| def execute(self, options, args, tool):
|
| self._tool = tool
|
| @@ -192,7 +263,7 @@ class RebaselineTest(BaseInternalRebaselineCommand):
|
| filesystem.maybe_make_directory(filesystem.dirname(target_baseline))
|
| filesystem.write_binary_file(target_baseline, data)
|
| if not self._tool.scm().exists(target_baseline):
|
| - self._add_to_scm_later(target_baseline)
|
| + self._scm_changes.add_file(target_baseline)
|
|
|
| def _rebaseline_test(self, builder_name, test_name, suffix, results_url):
|
| baseline_directory = self._baseline_directory(builder_name)
|
| @@ -221,7 +292,7 @@ class RebaselineTest(BaseInternalRebaselineCommand):
|
|
|
| for suffix in self._baseline_suffix_list:
|
| self._rebaseline_test(options.builder, options.test, suffix, results_url)
|
| - self._scm_changes['remove-lines'].append({'builder': options.builder, 'test': options.test})
|
| + self._scm_changes.remove_line(test=options.test, builder=options.builder)
|
|
|
| def execute(self, options, args, tool):
|
| self._tool = tool
|
| @@ -333,26 +404,18 @@ class AbstractParallelRebaselineCommand(AbstractRebaseliningCommand):
|
|
|
| @staticmethod
|
| def _serial_commands(command_results):
|
| - files_to_add = set()
|
| - files_to_delete = set()
|
| - lines_to_remove = {}
|
| + """Parses the JSON lines from command output to extract SCM changes.
|
| +
|
| + TODO(qyearsley): Refactor and rename this function.
|
| + """
|
| + change_set = ChangeSet()
|
| for output in [result[1].split('\n') for result in command_results]:
|
| file_added = False
|
| for line in output:
|
| try:
|
| if line:
|
| parsed_line = json.loads(line)
|
| - if 'add' in parsed_line:
|
| - files_to_add.update(parsed_line['add'])
|
| - if 'delete' in parsed_line:
|
| - files_to_delete.update(parsed_line['delete'])
|
| - if 'remove-lines' in parsed_line:
|
| - for line_to_remove in parsed_line['remove-lines']:
|
| - test = line_to_remove['test']
|
| - builder = line_to_remove['builder']
|
| - if test not in lines_to_remove:
|
| - lines_to_remove[test] = []
|
| - lines_to_remove[test].append(builder)
|
| + change_set.update(ChangeSet.from_dict(parsed_line))
|
| file_added = True
|
| except ValueError:
|
| _log.debug('"%s" is not a JSON object, ignoring', line)
|
| @@ -360,7 +423,7 @@ class AbstractParallelRebaselineCommand(AbstractRebaseliningCommand):
|
| if not file_added:
|
| _log.debug('Could not add file based off output "%s"', output)
|
|
|
| - return list(files_to_add), list(files_to_delete), lines_to_remove
|
| + return change_set
|
|
|
| def _optimize_baselines(self, test_prefix_list, verbose=False):
|
| optimize_commands = []
|
| @@ -435,21 +498,24 @@ class AbstractParallelRebaselineCommand(AbstractRebaseliningCommand):
|
| if not commands:
|
| return {}
|
|
|
| + # TODO(qyearsley): Refactor this block.
|
| command_results = self._tool.executive.run_in_parallel(commands)
|
| log_output = '\n'.join(result[2] for result in command_results).replace('\n\n', '\n')
|
| for line in log_output.split('\n'):
|
| if line:
|
| _log.error(line)
|
|
|
| - files_to_add, files_to_delete, lines_to_remove = self._serial_commands(command_results)
|
| - # TODO(qyearsley): Consider removing this if possible,
|
| - # or making it work and produce reasonable results for rebaseline-cl.
|
| + change_set = self._serial_commands(command_results)
|
| +
|
| + # TODO(qyearsley): Instead of updating the SCM state here, aggregate changes
|
| + # and update once in _rebaseline. See http://crbug.com/639410.
|
| if update_scm:
|
| - if files_to_delete:
|
| - self._tool.scm().delete_list(files_to_delete)
|
| - if files_to_add:
|
| - self._tool.scm().add_list(files_to_add)
|
| - return lines_to_remove
|
| + if change_set.files_to_delete:
|
| + self._tool.scm().delete_list(change_set.files_to_delete)
|
| + if change_set.files_to_add:
|
| + self._tool.scm().add_list(change_set.files_to_add)
|
| +
|
| + return change_set.lines_to_remove
|
|
|
| def _rebaseline(self, options, test_prefix_list, update_scm=True):
|
| """Downloads new baselines in parallel, then updates expectations files
|
|
|