Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2017 The Chromium Authors. All rights reserved. | 1 # Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Classes for merging layout tests results directories together. | 5 """Classes for merging layout tests results directories together. |
| 6 | 6 |
| 7 This is split into three parts: | 7 This is split into three parts: |
| 8 | 8 |
| 9 * Generic code to merge JSON data together. | 9 * Generic code to merge JSON data together. |
| 10 * Generic code to merge directories together. | 10 * Generic code to merge directories together. |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 21 * Helper functions can be provided to deal with merging specific file objects. | 21 * Helper functions can be provided to deal with merging specific file objects. |
| 22 * Helper functions are called when a given Match object returns true for the | 22 * Helper functions are called when a given Match object returns true for the |
| 23 filenames. | 23 filenames. |
| 24 * The default helper functions only merge if file contents match or the file | 24 * The default helper functions only merge if file contents match or the file |
| 25 only exists in one input directory. | 25 only exists in one input directory. |
| 26 | 26 |
| 27 The quickest way to understand how the mergers, helper functions and match | 27 The quickest way to understand how the mergers, helper functions and match |
| 28 objects work together is to look at the unit tests. | 28 objects work together is to look at the unit tests. |
| 29 """ | 29 """ |
| 30 | 30 |
| 31 import collections | |
| 32 import itertools | |
| 31 import json | 33 import json |
| 32 import logging | 34 import logging |
| 33 import pprint | 35 import pprint |
| 34 import re | 36 import re |
| 35 import types | 37 import types |
| 36 | 38 |
| 37 from webkitpy.common.system.filesystem import FileSystem | 39 from webkitpy.common.system.filesystem import FileSystem |
| 38 | 40 |
| 39 | 41 |
| 40 _log = logging.getLogger(__name__) | 42 _log = logging.getLogger(__name__) |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 87 def __init__(self, value): | 89 def __init__(self, value): |
| 88 self.value = value | 90 self.value = value |
| 89 | 91 |
| 90 def __call__(self, obj, name=None): | 92 def __call__(self, obj, name=None): |
| 91 return obj == self.value | 93 return obj == self.value |
| 92 | 94 |
| 93 | 95 |
| 94 class MergeFailure(Exception): | 96 class MergeFailure(Exception): |
| 95 """Base exception for merge failing.""" | 97 """Base exception for merge failing.""" |
| 96 | 98 |
| 97 def __init__(self, msg, name, obj_a, obj_b): | 99 def __init__(self, msg, name, objs): |
| 98 emsg = ( | 100 emsg = ( |
| 99 "Failure merging {name}: " | 101 "Failure merging {name}: " |
| 100 " {msg}\nTrying to merge {a} and {b}." | 102 " {msg}\nTrying to merge {objs}." |
| 101 ).format( | 103 ).format( |
| 102 name=name, | 104 name=name, |
| 103 msg=msg, | 105 msg=msg, |
| 104 a=obj_a, | 106 objs=objs, |
| 105 b=obj_b, | |
| 106 ) | 107 ) |
| 107 Exception.__init__(self, emsg) | 108 Exception.__init__(self, emsg) |
| 108 | 109 |
| 109 @classmethod | 110 @classmethod |
| 110 def assert_type_eq(cls, name, obj_a, obj_b): | 111 def assert_type_eq(cls, name, objs): |
| 111 if type(obj_a) != type(obj_b): | 112 obj_0 = objs[0] |
| 112 raise cls("Types don't match", name, obj_a, obj_b) | 113 for obj_n in objs[1:]: |
| 114 if type(obj_0) != type(obj_n): | |
| 115 raise cls("Types don't match", name, (obj_0, obj_n)) | |
| 113 | 116 |
| 114 | 117 |
| 115 class Merger(object): | 118 class Merger(object): |
| 116 """Base class for merger objects.""" | 119 """Base class for merger objects.""" |
| 117 | 120 |
| 118 def __init__(self): | 121 def __init__(self): |
| 119 self.helpers = [] | 122 self.helpers = [] |
| 120 | 123 |
| 121 def add_helper(self, match_func, merge_func): | 124 def add_helper(self, match_func, merge_func): |
| 122 """Add function which merges values. | 125 """Add function which merges values. |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 133 class JSONMerger(Merger): | 136 class JSONMerger(Merger): |
| 134 """Merge two JSON-like objects. | 137 """Merge two JSON-like objects. |
| 135 | 138 |
| 136 For adding helpers; | 139 For adding helpers; |
| 137 | 140 |
| 138 match_func is a function of form | 141 match_func is a function of form |
| 139 def f(obj, name=None) -> bool | 142 def f(obj, name=None) -> bool |
| 140 When the function returns true, the merge_func will be called. | 143 When the function returns true, the merge_func will be called. |
| 141 | 144 |
| 142 merge_func is a function of the form | 145 merge_func is a function of the form |
| 143 def f(obj_a, obj_b, name=None) -> obj_merged | 146 def f(list_of_objs, name=None) -> obj_merged |
| 144 Merge functions should *never* modify the input arguments. | 147 Merge functions should *never* modify the input arguments. |
| 145 """ | 148 """ |
| 146 | 149 |
| 147 def __init__(self): | 150 def __init__(self): |
| 148 Merger.__init__(self) | 151 Merger.__init__(self) |
| 149 | 152 |
| 150 self.add_helper( | 153 self.add_helper( |
| 151 TypeMatch(types.ListType, types.TupleType), self.merge_listlike) | 154 TypeMatch(types.ListType, types.TupleType), self.merge_listlike) |
| 152 self.add_helper( | 155 self.add_helper( |
| 153 TypeMatch(types.DictType), self.merge_dictlike) | 156 TypeMatch(types.DictType), self.merge_dictlike) |
| 154 | 157 |
| 155 def fallback_matcher(self, obj_a, obj_b, name=None): | 158 def fallback_matcher(self, objs, name=None): |
| 156 raise MergeFailure( | 159 raise MergeFailure( |
| 157 "No merge helper found!", name, obj_a, obj_b) | 160 "No merge helper found!", name, objs) |
| 158 | 161 |
| 159 def merge_equal(self, obj_a, obj_b, name=None): | 162 def merge_equal(self, objs, name=None): |
| 160 """Merge two equal objects together.""" | 163 """Merge two equal objects together.""" |
|
alancutter (OOO until 2018)
2017/04/19 07:43:43
Remove "two".
mithro
2017/04/19 08:38:39
Done.
| |
| 161 if obj_a != obj_b: | 164 obj_0 = objs[0] |
| 162 raise MergeFailure( | 165 for obj_n in objs[1:]: |
| 163 "Unable to merge!", name, obj_a, obj_b) | 166 if obj_0 != obj_n: |
| 164 return obj_a | 167 raise MergeFailure( |
| 168 "Unable to merge!", name, (obj_0, obj_n)) | |
| 169 return obj_0 | |
| 165 | 170 |
| 166 def merge_listlike(self, list_a, list_b, name=None): # pylint: disable=unus ed-argument | 171 def merge_listlike(self, lists, name=None): # pylint: disable=unused-argume nt |
| 167 """Merge two things which are "list like" (tuples, lists, sets).""" | 172 """Merge two things which are "list like" (tuples, lists, sets).""" |
|
alancutter (OOO until 2018)
2017/04/19 07:43:43
Remove "two".
mithro
2017/04/19 08:38:39
Done.
| |
| 168 assert type(list_a) == type(list_b), ( | 173 list_0 = lists[0] |
| 169 "Types of %r and %r don't match, refusing to merge." % ( | 174 for list_n in lists[1:]: |
| 170 list_a, list_b)) | 175 assert type(list_0) == type(list_n), ( |
| 171 output = list(list_a) | 176 "Types of %r and %r don't match, refusing to merge." % ( |
| 172 output.extend(list_b) | 177 list_0, list_n)) |
| 173 return list_a.__class__(output) | 178 output = list(list_0) |
| 179 for list_n in lists[1:]: | |
| 180 output.extend(list_n) | |
| 181 return list_0.__class__(output) | |
| 174 | 182 |
| 175 def merge_dictlike(self, dict_a, dict_b, name=None): | 183 def merge_dictlike(self, dicts, name=None): |
| 176 """Merge two things which are dictionaries.""" | 184 """Merge two things which are dictionaries.""" |
|
alancutter (OOO until 2018)
2017/04/19 07:43:43
Remove "two".
mithro
2017/04/19 08:38:39
Done.
| |
| 177 assert type(dict_a) == type(dict_b), ( | 185 dict_0 = dicts[0] |
| 178 "Types of %r and %r don't match, refusing to merge." % ( | 186 for dict_n in dicts[1:]: |
| 179 dict_a, dict_b)) | 187 assert type(dict_0) == type(dict_n), ( |
| 180 dict_out = dict_a.__class__({}) | 188 "Types of %r and %r don't match, refusing to merge." % ( |
| 181 for key in dict_a.keys() + dict_b.keys(): | 189 dict_0, dict_n)) |
|
alancutter (OOO until 2018)
2017/04/19 07:43:43
I think this is worth pulling out as a helper asse
mithro
2017/04/19 08:38:39
Done.
| |
| 182 if key in dict_a and key in dict_b: | 190 |
| 191 ordered_keys = collections.OrderedDict.fromkeys( | |
|
mcgreevy
2017/04/19 06:57:20
Why do you need an OrderedDict here? Can't you ju
mithro
2017/04/19 08:38:39
Set doesn't preserve the order and OrderedSet does
mcgreevy
2017/04/20 00:08:25
Right, but why do you need to preserve the order?
mithro
2017/04/20 02:02:44
I've added documentation on this.
| |
| 192 itertools.chain(*(d.iterkeys() for d in dicts))) | |
| 193 | |
| 194 dict_out = dict_0.__class__({}) | |
| 195 for key in ordered_keys: | |
| 196 values_to_merge = [] | |
| 197 for dobj in dicts: | |
|
mcgreevy
2017/04/19 06:57:20
This nested for loop looks up every key in every d
mithro
2017/04/19 08:38:39
I tried implementing this and it made no differenc
| |
| 198 if key in dobj: | |
| 199 values_to_merge.append(dobj[key]) | |
| 200 | |
| 201 if len(values_to_merge) == 1: | |
| 202 dict_out[key] = values_to_merge[0] | |
| 203 elif len(values_to_merge) > 1: | |
| 183 dict_out[key] = self.merge( | 204 dict_out[key] = self.merge( |
| 184 dict_a[key], dict_b[key], | 205 values_to_merge, |
| 185 name=join_name(name, key)) | 206 name=join_name(name, key)) |
| 186 elif key in dict_a: | |
| 187 dict_out[key] = dict_a[key] | |
| 188 elif key in dict_b: | |
| 189 dict_out[key] = dict_b[key] | |
| 190 else: | 207 else: |
| 191 assert False | 208 assert False, "Key %s not found in any inputs!" % (key,) |
| 192 return dict_out | 209 return dict_out |
| 193 | 210 |
| 194 def merge(self, obj_a, obj_b, name=""): | 211 def merge(self, objs, name=""): |
| 195 """Generic merge function. | 212 """Generic merge function. |
| 196 | 213 |
| 197 name is a string representing the current key value separated by | 214 name is a string representing the current key value separated by |
| 198 semicolons. For example, if file.json had the following; | 215 semicolons. For example, if file.json had the following; |
| 199 | 216 |
| 200 {'key1': {'key2': 3}} | 217 {'key1': {'key2': 3}} |
| 201 | 218 |
| 202 Then the name of the value 3 is 'file.json:key1:key2' | 219 Then the name of the value 3 is 'file.json:key1:key2' |
| 203 """ | 220 """ |
| 204 if obj_a is None and obj_b is None: | 221 objs = [o for o in objs if o is not None] |
| 222 | |
| 223 if not objs: | |
| 205 return None | 224 return None |
| 206 elif obj_b is None: | |
| 207 return obj_a | |
| 208 elif obj_a is None: | |
| 209 return obj_b | |
| 210 | 225 |
| 211 MergeFailure.assert_type_eq(name, obj_a, obj_b) | 226 obj_0 = objs[0] |
| 227 for obj_n in objs[1:]: | |
| 228 MergeFailure.assert_type_eq(name, (obj_0, obj_n)) | |
| 212 | 229 |
| 213 # Try the merge helpers. | 230 # Try the merge helpers. |
| 214 for match_func, merge_func in reversed(self.helpers): | 231 for match_func, merge_func in reversed(self.helpers): |
| 215 if match_func(obj_a, name): | 232 for obj in objs: |
|
alancutter (OOO until 2018)
2017/04/19 07:43:43
Seems strange that it needs to test against every
| |
| 216 return merge_func(obj_a, obj_b, name=name) | 233 if match_func(obj, name): |
| 217 if match_func(obj_b, name): | 234 return merge_func(objs, name=name) |
| 218 return merge_func(obj_a, obj_b, name=name) | |
| 219 | 235 |
| 220 return self.fallback_matcher(obj_a, obj_b, name=name) | 236 return self.fallback_matcher(objs, name=name) |
| 221 | 237 |
| 222 | 238 |
| 223 # Classes for recursively merging a directory together. | 239 # Classes for recursively merging a directory together. |
| 224 # ------------------------------------------------------------------------ | 240 # ------------------------------------------------------------------------ |
| 225 | 241 |
| 226 | 242 |
| 227 class FilenameMatch(object): | 243 class FilenameMatch(object): |
| 228 """Match based on name matching a regex.""" | 244 """Match based on name matching a regex.""" |
| 229 | 245 |
| 230 def __init__(self, regex): | 246 def __init__(self, regex): |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 268 for filename in to_merge[1:]: | 284 for filename in to_merge[1:]: |
| 269 other_data = self.filesystem.read_binary_file(filename) | 285 other_data = self.filesystem.read_binary_file(filename) |
| 270 if data != other_data: | 286 if data != other_data: |
| 271 nonmatching.append(filename) | 287 nonmatching.append(filename) |
| 272 | 288 |
| 273 if nonmatching: | 289 if nonmatching: |
| 274 raise MergeFailure( | 290 raise MergeFailure( |
| 275 '\n'.join( | 291 '\n'.join( |
| 276 ['File contents don\'t match:'] + nonmatching), | 292 ['File contents don\'t match:'] + nonmatching), |
| 277 out_filename, | 293 out_filename, |
| 278 to_merge[0], to_merge[1:]) | 294 to_merge) |
| 279 | 295 |
| 280 self.filesystem.write_binary_file(out_filename, data) | 296 self.filesystem.write_binary_file(out_filename, data) |
| 281 | 297 |
| 282 | 298 |
| 283 class MergeFilesLinesSorted(MergeFiles): | 299 class MergeFilesLinesSorted(MergeFiles): |
| 284 """Merge and sort the files of the given files.""" | 300 """Merge and sort the files of the given files.""" |
| 285 | 301 |
| 286 def __call__(self, out_filename, to_merge): | 302 def __call__(self, out_filename, to_merge): |
| 287 lines = [] | 303 lines = [] |
| 288 for filename in to_merge: | 304 for filename in to_merge: |
| (...skipping 26 matching lines...) Expand all Loading... | |
| 315 output. | 331 output. |
| 316 """ | 332 """ |
| 317 | 333 |
| 318 def __init__(self, filesystem, json_data_merger=None, json_data_value_overri des=None): | 334 def __init__(self, filesystem, json_data_merger=None, json_data_value_overri des=None): |
| 319 MergeFiles.__init__(self, filesystem) | 335 MergeFiles.__init__(self, filesystem) |
| 320 self._json_data_merger = json_data_merger or JSONMerger() | 336 self._json_data_merger = json_data_merger or JSONMerger() |
| 321 self._json_data_value_overrides = json_data_value_overrides or {} | 337 self._json_data_value_overrides = json_data_value_overrides or {} |
| 322 | 338 |
| 323 def __call__(self, out_filename, to_merge): | 339 def __call__(self, out_filename, to_merge): |
| 324 try: | 340 try: |
| 325 before_a, output_data, after_a = self.load_jsonp( | 341 before_0, new_json_data_0, after_0 = self.load_jsonp( |
| 326 self.filesystem.open_binary_file_for_reading(to_merge[0])) | 342 self.filesystem.open_binary_file_for_reading(to_merge[0])) |
| 327 except ValueError as e: | 343 except ValueError as e: |
| 328 raise MergeFailure(e.message, to_merge[0], None, None) | 344 raise MergeFailure(e.message, to_merge[0], None) |
| 329 | 345 |
| 330 for filename in to_merge[1:]: | 346 input_data = [new_json_data_0] |
| 347 for filename_n in to_merge[1:]: | |
| 331 try: | 348 try: |
| 332 before_b, new_json_data, after_b = self.load_jsonp( | 349 beforen, new_json_data_n, aftern = self.load_jsonp( |
| 333 self.filesystem.open_binary_file_for_reading(filename)) | 350 self.filesystem.open_binary_file_for_reading(filename_n)) |
| 334 except ValueError as e: | 351 except ValueError as e: |
| 335 raise MergeFailure(e.message, filename, None, None) | 352 raise MergeFailure(e.message, filename_n, None) |
| 336 | 353 |
| 337 if before_a != before_b: | 354 if before_0 != beforen: |
| 338 raise MergeFailure( | 355 raise MergeFailure( |
| 339 "jsonp starting data from %s doesn't match." % filename, | 356 "jsonp starting data from %s doesn't match." % filename_n, |
| 340 out_filename, | 357 out_filename, |
| 341 before_a, before_b) | 358 [before_0, beforen]) |
| 342 | 359 |
| 343 if after_a != after_b: | 360 if after_0 != aftern: |
| 344 raise MergeFailure( | 361 raise MergeFailure( |
| 345 "jsonp ending data from %s doesn't match." % filename, | 362 "jsonp ending data from %s doesn't match." % filename_n, |
| 346 out_filename, | 363 out_filename, |
| 347 after_a, after_b) | 364 [after_0, aftern]) |
| 348 | 365 |
| 349 output_data = self._json_data_merger.merge(output_data, new_json_dat a, filename) | 366 input_data.append(new_json_data_n) |
| 350 | 367 |
| 368 output_data = self._json_data_merger.merge(input_data, name=out_filename ) | |
| 351 output_data.update(self._json_data_value_overrides) | 369 output_data.update(self._json_data_value_overrides) |
| 352 | 370 |
| 353 self.dump_jsonp( | 371 self.dump_jsonp( |
| 354 self.filesystem.open_binary_file_for_writing(out_filename), | 372 self.filesystem.open_binary_file_for_writing(out_filename), |
| 355 before_a, output_data, after_a) | 373 before_0, output_data, after_0) |
| 356 | 374 |
| 357 @staticmethod | 375 @staticmethod |
| 358 def load_jsonp(fd): | 376 def load_jsonp(fd): |
| 359 """Load a JSONP file and return the JSON data parsed. | 377 """Load a JSONP file and return the JSON data parsed. |
| 360 | 378 |
| 361 JSONP files have a JSON data structure wrapped in a function call or | 379 JSONP files have a JSON data structure wrapped in a function call or |
| 362 other non-JSON data. | 380 other non-JSON data. |
| 363 """ | 381 """ |
| 364 in_data = fd.read() | 382 in_data = fd.read() |
| 365 | 383 |
| (...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 458 # rel_file is the path of f relative to the base directory | 476 # rel_file is the path of f relative to the base directory |
| 459 rel_file = self.filesystem.join(dir_path, f)[len(base_dir) + 1:] | 477 rel_file = self.filesystem.join(dir_path, f)[len(base_dir) + 1:] |
| 460 files.setdefault(rel_file, []).append(base_dir) | 478 files.setdefault(rel_file, []).append(base_dir) |
| 461 | 479 |
| 462 # Go through each file and try to merge it. | 480 # Go through each file and try to merge it. |
| 463 # partial_file_path is the file relative to the directories. | 481 # partial_file_path is the file relative to the directories. |
| 464 for partial_file_path, in_dirs in sorted(files.iteritems()): | 482 for partial_file_path, in_dirs in sorted(files.iteritems()): |
| 465 out_path = self.filesystem.join(output_dir, partial_file_path) | 483 out_path = self.filesystem.join(output_dir, partial_file_path) |
| 466 if self.filesystem.exists(out_path): | 484 if self.filesystem.exists(out_path): |
| 467 raise MergeFailure( | 485 raise MergeFailure( |
| 468 'File %s already exist in output.', out_path, None, None) | 486 'File %s already exist in output.', out_path, None) |
| 469 | 487 |
| 470 dirname = self.filesystem.dirname(out_path) | 488 dirname = self.filesystem.dirname(out_path) |
| 471 if not self.filesystem.exists(dirname): | 489 if not self.filesystem.exists(dirname): |
| 472 self.filesystem.maybe_make_directory(dirname) | 490 self.filesystem.maybe_make_directory(dirname) |
| 473 | 491 |
| 474 to_merge = [self.filesystem.join(d, partial_file_path) for d in in_d irs] | 492 to_merge = [self.filesystem.join(d, partial_file_path) for d in in_d irs] |
| 475 | 493 |
| 476 _log.debug("Creating merged %s from %s", out_path, to_merge) | 494 _log.debug("Creating merged %s from %s", out_path, to_merge) |
| 477 | 495 |
| 478 for match_func, merge_func in reversed(self.helpers): | 496 for match_func, merge_func in reversed(self.helpers): |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 529 ':num_passes$', | 547 ':num_passes$', |
| 530 ':num_regressions$', | 548 ':num_regressions$', |
| 531 ':skipped$', | 549 ':skipped$', |
| 532 ':skips$', | 550 ':skips$', |
| 533 # All keys inside the num_failures_by_type entry. | 551 # All keys inside the num_failures_by_type entry. |
| 534 ':num_failures_by_type:', | 552 ':num_failures_by_type:', |
| 535 ] | 553 ] |
| 536 for match_name in addable: | 554 for match_name in addable: |
| 537 self.add_helper( | 555 self.add_helper( |
| 538 NameMatch(match_name), | 556 NameMatch(match_name), |
| 539 lambda a, b, name=None: a + b) | 557 lambda o, name=None: sum(o)) |
| 540 | 558 |
| 541 # If any shard is interrupted, mark the whole thing as interrupted. | 559 # If any shard is interrupted, mark the whole thing as interrupted. |
| 542 self.add_helper( | 560 self.add_helper( |
| 543 NameMatch(':interrupted$'), | 561 NameMatch(':interrupted$'), |
| 544 lambda a, b, name=None: a or b) | 562 lambda o, name=None: bool(sum(o))) |
| 545 | 563 |
| 546 # Layout test directory value is randomly created on each shard, so | 564 # Layout test directory value is randomly created on each shard, so |
| 547 # clear it. | 565 # clear it. |
| 548 self.add_helper( | 566 self.add_helper( |
| 549 NameMatch(':layout_tests_dir$'), | 567 NameMatch(':layout_tests_dir$'), |
| 550 lambda a, b, name=None: None) | 568 lambda o, name=None: None) |
| 551 | 569 |
| 552 # seconds_since_epoch is the start time, so we just take the earliest. | 570 # seconds_since_epoch is the start time, so we just take the earliest. |
| 553 self.add_helper( | 571 self.add_helper( |
| 554 NameMatch(':seconds_since_epoch$'), | 572 NameMatch(':seconds_since_epoch$'), |
| 555 lambda a, b, name=None: min(a, b)) | 573 lambda o, name=None: min(*o)) |
| 556 | 574 |
| 557 def fallback_matcher(self, obj_a, obj_b, name=None): | 575 def fallback_matcher(self, objs, name=None): |
| 558 if self.allow_unknown_if_matching: | 576 if self.allow_unknown_if_matching: |
| 559 result = self.merge_equal(obj_a, obj_b, name) | 577 result = self.merge_equal(objs, name) |
| 560 _log.warning('Unknown value %s, accepting anyway as it matches.', na me) | 578 _log.warning('Unknown value %s, accepting anyway as it matches.', na me) |
| 561 return result | 579 return result |
| 562 return JSONMerger.fallback_matcher(self, obj_a, obj_b, name) | 580 return JSONMerger.fallback_matcher(self, objs, name) |
| 563 | 581 |
| 564 | 582 |
| 565 class LayoutTestDirMerger(DirMerger): | 583 class LayoutTestDirMerger(DirMerger): |
| 566 """Merge layout test result directory.""" | 584 """Merge layout test result directory.""" |
| 567 | 585 |
| 568 def __init__(self, filesystem=None, | 586 def __init__(self, filesystem=None, |
| 569 results_json_value_overrides=None, | 587 results_json_value_overrides=None, |
| 570 results_json_allow_unknown_if_matching=False): | 588 results_json_allow_unknown_if_matching=False): |
| 571 DirMerger.__init__(self, filesystem) | 589 DirMerger.__init__(self, filesystem) |
| 572 | 590 |
| 573 # JSON merger for non-"result style" JSON files. | 591 # JSON merger for non-"result style" JSON files. |
| 574 basic_json_data_merger = JSONMerger() | 592 basic_json_data_merger = JSONMerger() |
| 575 basic_json_data_merger.fallback_matcher = basic_json_data_merger.merge_e qual | 593 basic_json_data_merger.fallback_matcher = basic_json_data_merger.merge_e qual |
| 576 self.add_helper( | 594 self.add_helper( |
| 577 FilenameMatch('\\.json'), | 595 FilenameMatch('\\.json$'), |
| 578 MergeFilesJSONP(self.filesystem, basic_json_data_merger)) | 596 MergeFilesJSONP(self.filesystem, basic_json_data_merger)) |
| 579 | 597 |
| 580 # access_log and error_log are httpd log files which are sortable. | 598 # access_log and error_log are httpd log files which are sortable. |
| 581 self.add_helper( | 599 self.add_helper( |
| 582 FilenameMatch('access_log\\.txt'), | 600 FilenameMatch('access_log\\.txt$'), |
| 583 MergeFilesLinesSorted(self.filesystem)) | 601 MergeFilesLinesSorted(self.filesystem)) |
| 584 self.add_helper( | 602 self.add_helper( |
| 585 FilenameMatch('error_log\\.txt'), | 603 FilenameMatch('error_log\\.txt$'), |
| 586 MergeFilesLinesSorted(self.filesystem)) | 604 MergeFilesLinesSorted(self.filesystem)) |
| 587 | 605 |
| 588 # pywebsocket files aren't particularly useful, so just save them. | 606 # pywebsocket files aren't particularly useful, so just save them. |
| 589 self.add_helper( | 607 self.add_helper( |
| 590 FilenameMatch('pywebsocket\\.ws\\.log-.*-err.txt'), | 608 FilenameMatch('pywebsocket\\.ws\\.log-.*-err\\.txt$'), |
| 591 MergeFilesKeepFiles(self.filesystem)) | 609 MergeFilesKeepFiles(self.filesystem)) |
| 592 | 610 |
| 593 # These JSON files have "result style" JSON in them. | 611 # These JSON files have "result style" JSON in them. |
| 594 results_json_file_merger = MergeFilesJSONP( | 612 results_json_file_merger = MergeFilesJSONP( |
| 595 self.filesystem, | 613 self.filesystem, |
| 596 JSONTestResultsMerger( | 614 JSONTestResultsMerger( |
| 597 allow_unknown_if_matching=results_json_allow_unknown_if_matching ), | 615 allow_unknown_if_matching=results_json_allow_unknown_if_matching ), |
| 598 json_data_value_overrides=results_json_value_overrides or {}) | 616 json_data_value_overrides=results_json_value_overrides or {}) |
| 599 | 617 |
| 600 self.add_helper( | 618 self.add_helper( |
| 601 FilenameMatch('failing_results.json'), | 619 FilenameMatch('failing_results\\.json$'), |
| 602 results_json_file_merger) | 620 results_json_file_merger) |
| 603 self.add_helper( | 621 self.add_helper( |
| 604 FilenameMatch('full_results.json'), | 622 FilenameMatch('full_results\\.json$'), |
| 605 results_json_file_merger) | 623 results_json_file_merger) |
| 606 self.add_helper( | 624 self.add_helper( |
| 607 FilenameMatch('output.json'), | 625 FilenameMatch('output\\.json$'), |
| 608 results_json_file_merger) | 626 results_json_file_merger) |
| OLD | NEW |