Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(992)

Unified Diff: third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/merge_results.py

Issue 2825253003: webkitpy: Improve performance of merge-results script. (Closed)
Patch Set: Fixing for review. Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/merge_results_unittest.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/merge_results.py
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/merge_results.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/merge_results.py
index cbfcae51dabdb5674ec2f278d9e01308c25fa3a0..c245146846a5a35be8fb7b854e8fe4d1bd5b5e9b 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/merge_results.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/merge_results.py
@@ -28,6 +28,8 @@ The quickest way to understand how the mergers, helper functions and match
objects work together is to look at the unit tests.
"""
+import collections
+import itertools
import json
import logging
import pprint
@@ -94,22 +96,23 @@ class ValueMatch(Match):
class MergeFailure(Exception):
"""Base exception for merge failing."""
- def __init__(self, msg, name, obj_a, obj_b):
+ def __init__(self, msg, name, objs):
emsg = (
"Failure merging {name}: "
- " {msg}\nTrying to merge {a} and {b}."
+ " {msg}\nTrying to merge {objs}."
).format(
name=name,
msg=msg,
- a=obj_a,
- b=obj_b,
+ objs=objs,
)
Exception.__init__(self, emsg)
@classmethod
- def assert_type_eq(cls, name, obj_a, obj_b):
- if type(obj_a) != type(obj_b):
- raise cls("Types don't match", name, obj_a, obj_b)
+ def assert_type_eq(cls, name, objs):
+ obj_0 = objs[0]
+ for obj_n in objs[1:]:
+ if type(obj_0) != type(obj_n):
+ raise cls("Types don't match", name, (obj_0, obj_n))
class Merger(object):
@@ -131,7 +134,7 @@ class Merger(object):
class JSONMerger(Merger):
- """Merge two JSON-like objects.
+ """Merge JSON-like objects.
For adding helpers;
@@ -140,7 +143,7 @@ class JSONMerger(Merger):
When the function returns true, the merge_func will be called.
merge_func is a function of the form
- def f(obj_a, obj_b, name=None) -> obj_merged
+ def f(list_of_objs, name=None) -> obj_merged
Merge functions should *never* modify the input arguments.
"""
@@ -152,46 +155,52 @@ class JSONMerger(Merger):
self.add_helper(
TypeMatch(types.DictType), self.merge_dictlike)
- def fallback_matcher(self, obj_a, obj_b, name=None):
+ def fallback_matcher(self, objs, name=None):
raise MergeFailure(
- "No merge helper found!", name, obj_a, obj_b)
+ "No merge helper found!", name, objs)
- def merge_equal(self, obj_a, obj_b, name=None):
- """Merge two equal objects together."""
- if obj_a != obj_b:
- raise MergeFailure(
- "Unable to merge!", name, obj_a, obj_b)
- return obj_a
-
- def merge_listlike(self, list_a, list_b, name=None): # pylint: disable=unused-argument
- """Merge two things which are "list like" (tuples, lists, sets)."""
- assert type(list_a) == type(list_b), (
- "Types of %r and %r don't match, refusing to merge." % (
- list_a, list_b))
- output = list(list_a)
- output.extend(list_b)
- return list_a.__class__(output)
-
- def merge_dictlike(self, dict_a, dict_b, name=None):
- """Merge two things which are dictionaries."""
- assert type(dict_a) == type(dict_b), (
- "Types of %r and %r don't match, refusing to merge." % (
- dict_a, dict_b))
- dict_out = dict_a.__class__({})
- for key in dict_a.keys() + dict_b.keys():
- if key in dict_a and key in dict_b:
+ def merge_equal(self, objs, name=None):
+ """Merge equal objects together."""
+ obj_0 = objs[0]
+ for obj_n in objs[1:]:
+ if obj_0 != obj_n:
+ raise MergeFailure(
+ "Unable to merge!", name, (obj_0, obj_n))
+ return obj_0
+
+ def merge_listlike(self, lists, name=None): # pylint: disable=unused-argument
+ """Merge things which are "list like" (tuples, lists, sets)."""
+ MergeFailure.assert_type_eq(name, lists)
+ output = list(lists[0])
+ for list_n in lists[1:]:
+ output.extend(list_n)
+ return lists[0].__class__(output)
+
+ def merge_dictlike(self, dicts, name=None):
+ """Merge things which are dictionaries."""
+ MergeFailure.assert_type_eq(name, dicts)
+
+ ordered_keys = collections.OrderedDict.fromkeys(
+ itertools.chain(*(d.iterkeys() for d in dicts)))
+
+ dict_out = dicts[0].__class__({})
+ for key in ordered_keys:
+ values_to_merge = []
+ for dobj in dicts:
+ if key in dobj:
+ values_to_merge.append(dobj[key])
+
+ if len(values_to_merge) == 1:
+ dict_out[key] = values_to_merge[0]
+ elif len(values_to_merge) > 1:
dict_out[key] = self.merge(
- dict_a[key], dict_b[key],
+ values_to_merge,
name=join_name(name, key))
- elif key in dict_a:
- dict_out[key] = dict_a[key]
- elif key in dict_b:
- dict_out[key] = dict_b[key]
else:
- assert False
+ assert False, "Key %s not found in any inputs!" % (key,)
return dict_out
- def merge(self, obj_a, obj_b, name=""):
+ def merge(self, objs, name=""):
"""Generic merge function.
name is a string representing the current key value separated by
@@ -201,23 +210,20 @@ class JSONMerger(Merger):
Then the name of the value 3 is 'file.json:key1:key2'
"""
- if obj_a is None and obj_b is None:
+ objs = [o for o in objs if o is not None]
+
+ if not objs:
return None
- elif obj_b is None:
- return obj_a
- elif obj_a is None:
- return obj_b
- MergeFailure.assert_type_eq(name, obj_a, obj_b)
+ MergeFailure.assert_type_eq(name, objs)
# Try the merge helpers.
for match_func, merge_func in reversed(self.helpers):
- if match_func(obj_a, name):
- return merge_func(obj_a, obj_b, name=name)
- if match_func(obj_b, name):
- return merge_func(obj_a, obj_b, name=name)
+ for obj in objs:
+ if match_func(obj, name):
+ return merge_func(objs, name=name)
- return self.fallback_matcher(obj_a, obj_b, name=name)
+ return self.fallback_matcher(objs, name=name)
# Classes for recursively merging a directory together.
@@ -275,7 +281,7 @@ class MergeFilesMatchingContents(MergeFiles):
'\n'.join(
['File contents don\'t match:'] + nonmatching),
out_filename,
- to_merge[0], to_merge[1:])
+ to_merge)
self.filesystem.write_binary_file(out_filename, data)
@@ -322,37 +328,39 @@ class MergeFilesJSONP(MergeFiles):
def __call__(self, out_filename, to_merge):
try:
- before_a, output_data, after_a = self.load_jsonp(
+ before_0, new_json_data_0, after_0 = self.load_jsonp(
self.filesystem.open_binary_file_for_reading(to_merge[0]))
except ValueError as e:
- raise MergeFailure(e.message, to_merge[0], None, None)
+ raise MergeFailure(e.message, to_merge[0], None)
- for filename in to_merge[1:]:
+ input_data = [new_json_data_0]
+ for filename_n in to_merge[1:]:
try:
- before_b, new_json_data, after_b = self.load_jsonp(
- self.filesystem.open_binary_file_for_reading(filename))
+ beforen, new_json_data_n, aftern = self.load_jsonp(
qyearsley 2017/04/19 22:40:20 Using an underscore (before_n/after_n) may look a
mithro 2017/04/20 02:02:45 Done now. This was actually the reason I didn't s
+ self.filesystem.open_binary_file_for_reading(filename_n))
except ValueError as e:
- raise MergeFailure(e.message, filename, None, None)
+ raise MergeFailure(e.message, filename_n, None)
- if before_a != before_b:
+ if before_0 != beforen:
raise MergeFailure(
- "jsonp starting data from %s doesn't match." % filename,
+ "jsonp starting data from %s doesn't match." % filename_n,
out_filename,
- before_a, before_b)
+ [before_0, beforen])
- if after_a != after_b:
+ if after_0 != aftern:
raise MergeFailure(
- "jsonp ending data from %s doesn't match." % filename,
+ "jsonp ending data from %s doesn't match." % filename_n,
out_filename,
- after_a, after_b)
+ [after_0, aftern])
- output_data = self._json_data_merger.merge(output_data, new_json_data, filename)
+ input_data.append(new_json_data_n)
+ output_data = self._json_data_merger.merge(input_data, name=out_filename)
output_data.update(self._json_data_value_overrides)
self.dump_jsonp(
self.filesystem.open_binary_file_for_writing(out_filename),
- before_a, output_data, after_a)
+ before_0, output_data, after_0)
@staticmethod
def load_jsonp(fd):
@@ -465,7 +473,7 @@ class DirMerger(Merger):
out_path = self.filesystem.join(output_dir, partial_file_path)
if self.filesystem.exists(out_path):
raise MergeFailure(
- 'File %s already exist in output.', out_path, None, None)
+ 'File %s already exist in output.', out_path, None)
dirname = self.filesystem.dirname(out_path)
if not self.filesystem.exists(dirname):
@@ -536,30 +544,30 @@ class JSONTestResultsMerger(JSONMerger):
for match_name in addable:
self.add_helper(
NameMatch(match_name),
- lambda a, b, name=None: a + b)
+ lambda o, name=None: sum(o))
# If any shard is interrupted, mark the whole thing as interrupted.
self.add_helper(
NameMatch(':interrupted$'),
- lambda a, b, name=None: a or b)
+ lambda o, name=None: bool(sum(o)))
# Layout test directory value is randomly created on each shard, so
# clear it.
self.add_helper(
NameMatch(':layout_tests_dir$'),
- lambda a, b, name=None: None)
+ lambda o, name=None: None)
# seconds_since_epoch is the start time, so we just take the earliest.
self.add_helper(
NameMatch(':seconds_since_epoch$'),
- lambda a, b, name=None: min(a, b))
+ lambda o, name=None: min(*o))
- def fallback_matcher(self, obj_a, obj_b, name=None):
+ def fallback_matcher(self, objs, name=None):
if self.allow_unknown_if_matching:
- result = self.merge_equal(obj_a, obj_b, name)
+ result = self.merge_equal(objs, name)
_log.warning('Unknown value %s, accepting anyway as it matches.', name)
return result
- return JSONMerger.fallback_matcher(self, obj_a, obj_b, name)
+ return JSONMerger.fallback_matcher(self, objs, name)
class LayoutTestDirMerger(DirMerger):
@@ -574,20 +582,20 @@ class LayoutTestDirMerger(DirMerger):
basic_json_data_merger = JSONMerger()
basic_json_data_merger.fallback_matcher = basic_json_data_merger.merge_equal
self.add_helper(
- FilenameMatch('\\.json'),
+ FilenameMatch('\\.json$'),
MergeFilesJSONP(self.filesystem, basic_json_data_merger))
# access_log and error_log are httpd log files which are sortable.
self.add_helper(
- FilenameMatch('access_log\\.txt'),
+ FilenameMatch('access_log\\.txt$'),
MergeFilesLinesSorted(self.filesystem))
self.add_helper(
- FilenameMatch('error_log\\.txt'),
+ FilenameMatch('error_log\\.txt$'),
MergeFilesLinesSorted(self.filesystem))
# pywebsocket files aren't particularly useful, so just save them.
self.add_helper(
- FilenameMatch('pywebsocket\\.ws\\.log-.*-err.txt'),
+ FilenameMatch('pywebsocket\\.ws\\.log-.*-err\\.txt$'),
qyearsley 2017/04/19 22:40:20 Could also use a "raw string" for strings that con
mithro 2017/04/20 02:02:45 Yeah, raw string can be a little bit dangerous to
MergeFilesKeepFiles(self.filesystem))
# These JSON files have "result style" JSON in them.
@@ -598,11 +606,11 @@ class LayoutTestDirMerger(DirMerger):
json_data_value_overrides=results_json_value_overrides or {})
self.add_helper(
- FilenameMatch('failing_results.json'),
+ FilenameMatch('failing_results\\.json$'),
results_json_file_merger)
self.add_helper(
- FilenameMatch('full_results.json'),
+ FilenameMatch('full_results\\.json$'),
results_json_file_merger)
self.add_helper(
- FilenameMatch('output.json'),
+ FilenameMatch('output\\.json$'),
results_json_file_merger)
« no previous file with comments | « no previous file | third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/merge_results_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698