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. |
| 123 | 126 |
| 124 match_func and merge_func are dependent on the merger object type. | 127 match_func and merge_func are dependent on the merger object type. |
| 125 When the function returns true, the merge_func will be called. | 128 When the function returns true, the merge_func will be called. |
| 126 | 129 |
| 127 Helpers are searched in last added, first checked order. This allows | 130 Helpers are searched in last added, first checked order. This allows |
| 128 more specific helpers to be added after more generic helpers. | 131 more specific helpers to be added after more generic helpers. |
| 129 """ | 132 """ |
| 130 self.helpers.append((match_func, merge_func)) | 133 self.helpers.append((match_func, merge_func)) |
| 131 | 134 |
| 132 | 135 |
| 133 class JSONMerger(Merger): | 136 class JSONMerger(Merger): |
| 134 """Merge two JSON-like objects. | 137 """Merge 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 equal objects together.""" |
| 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 things which are "list like" (tuples, lists, sets).""" |
| 168 assert type(list_a) == type(list_b), ( | 173 MergeFailure.assert_type_eq(name, lists) |
| 169 "Types of %r and %r don't match, refusing to merge." % ( | 174 output = list(lists[0]) |
| 170 list_a, list_b)) | 175 for list_n in lists[1:]: |
| 171 output = list(list_a) | 176 output.extend(list_n) |
| 172 output.extend(list_b) | 177 return lists[0].__class__(output) |
| 173 return list_a.__class__(output) | |
| 174 | 178 |
| 175 def merge_dictlike(self, dict_a, dict_b, name=None): | 179 def merge_dictlike(self, dicts, name=None): |
| 176 """Merge two things which are dictionaries.""" | 180 """Merge things which are dictionaries.""" |
| 177 assert type(dict_a) == type(dict_b), ( | 181 MergeFailure.assert_type_eq(name, dicts) |
| 178 "Types of %r and %r don't match, refusing to merge." % ( | 182 |
| 179 dict_a, dict_b)) | 183 ordered_keys = collections.OrderedDict.fromkeys( |
| 180 dict_out = dict_a.__class__({}) | 184 itertools.chain(*(d.iterkeys() for d in dicts))) |
| 181 for key in dict_a.keys() + dict_b.keys(): | 185 |
| 182 if key in dict_a and key in dict_b: | 186 dict_out = dicts[0].__class__({}) |
| 187 for key in ordered_keys: | |
| 188 values_to_merge = [] | |
| 189 for dobj in dicts: | |
| 190 if key in dobj: | |
| 191 values_to_merge.append(dobj[key]) | |
| 192 | |
| 193 if len(values_to_merge) == 1: | |
| 194 dict_out[key] = values_to_merge[0] | |
| 195 elif len(values_to_merge) > 1: | |
| 183 dict_out[key] = self.merge( | 196 dict_out[key] = self.merge( |
| 184 dict_a[key], dict_b[key], | 197 values_to_merge, |
| 185 name=join_name(name, key)) | 198 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: | 199 else: |
| 191 assert False | 200 assert False, "Key %s not found in any inputs!" % (key,) |
| 192 return dict_out | 201 return dict_out |
| 193 | 202 |
| 194 def merge(self, obj_a, obj_b, name=""): | 203 def merge(self, objs, name=""): |
| 195 """Generic merge function. | 204 """Generic merge function. |
| 196 | 205 |
| 197 name is a string representing the current key value separated by | 206 name is a string representing the current key value separated by |
| 198 semicolons. For example, if file.json had the following; | 207 semicolons. For example, if file.json had the following; |
| 199 | 208 |
| 200 {'key1': {'key2': 3}} | 209 {'key1': {'key2': 3}} |
| 201 | 210 |
| 202 Then the name of the value 3 is 'file.json:key1:key2' | 211 Then the name of the value 3 is 'file.json:key1:key2' |
| 203 """ | 212 """ |
| 204 if obj_a is None and obj_b is None: | 213 objs = [o for o in objs if o is not None] |
| 214 | |
| 215 if not objs: | |
| 205 return None | 216 return None |
| 206 elif obj_b is None: | |
| 207 return obj_a | |
| 208 elif obj_a is None: | |
| 209 return obj_b | |
| 210 | 217 |
| 211 MergeFailure.assert_type_eq(name, obj_a, obj_b) | 218 MergeFailure.assert_type_eq(name, objs) |
| 212 | 219 |
| 213 # Try the merge helpers. | 220 # Try the merge helpers. |
| 214 for match_func, merge_func in reversed(self.helpers): | 221 for match_func, merge_func in reversed(self.helpers): |
| 215 if match_func(obj_a, name): | 222 for obj in objs: |
| 216 return merge_func(obj_a, obj_b, name=name) | 223 if match_func(obj, name): |
| 217 if match_func(obj_b, name): | 224 return merge_func(objs, name=name) |
| 218 return merge_func(obj_a, obj_b, name=name) | |
| 219 | 225 |
| 220 return self.fallback_matcher(obj_a, obj_b, name=name) | 226 return self.fallback_matcher(objs, name=name) |
| 221 | 227 |
| 222 | 228 |
| 223 # Classes for recursively merging a directory together. | 229 # Classes for recursively merging a directory together. |
| 224 # ------------------------------------------------------------------------ | 230 # ------------------------------------------------------------------------ |
| 225 | 231 |
| 226 | 232 |
| 227 class FilenameMatch(object): | 233 class FilenameMatch(object): |
| 228 """Match based on name matching a regex.""" | 234 """Match based on name matching a regex.""" |
| 229 | 235 |
| 230 def __init__(self, regex): | 236 def __init__(self, regex): |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 268 for filename in to_merge[1:]: | 274 for filename in to_merge[1:]: |
| 269 other_data = self.filesystem.read_binary_file(filename) | 275 other_data = self.filesystem.read_binary_file(filename) |
| 270 if data != other_data: | 276 if data != other_data: |
| 271 nonmatching.append(filename) | 277 nonmatching.append(filename) |
| 272 | 278 |
| 273 if nonmatching: | 279 if nonmatching: |
| 274 raise MergeFailure( | 280 raise MergeFailure( |
| 275 '\n'.join( | 281 '\n'.join( |
| 276 ['File contents don\'t match:'] + nonmatching), | 282 ['File contents don\'t match:'] + nonmatching), |
| 277 out_filename, | 283 out_filename, |
| 278 to_merge[0], to_merge[1:]) | 284 to_merge) |
| 279 | 285 |
| 280 self.filesystem.write_binary_file(out_filename, data) | 286 self.filesystem.write_binary_file(out_filename, data) |
| 281 | 287 |
| 282 | 288 |
| 283 class MergeFilesLinesSorted(MergeFiles): | 289 class MergeFilesLinesSorted(MergeFiles): |
| 284 """Merge and sort the files of the given files.""" | 290 """Merge and sort the files of the given files.""" |
| 285 | 291 |
| 286 def __call__(self, out_filename, to_merge): | 292 def __call__(self, out_filename, to_merge): |
| 287 lines = [] | 293 lines = [] |
| 288 for filename in to_merge: | 294 for filename in to_merge: |
| (...skipping 26 matching lines...) Expand all Loading... | |
| 315 output. | 321 output. |
| 316 """ | 322 """ |
| 317 | 323 |
| 318 def __init__(self, filesystem, json_data_merger=None, json_data_value_overri des=None): | 324 def __init__(self, filesystem, json_data_merger=None, json_data_value_overri des=None): |
| 319 MergeFiles.__init__(self, filesystem) | 325 MergeFiles.__init__(self, filesystem) |
| 320 self._json_data_merger = json_data_merger or JSONMerger() | 326 self._json_data_merger = json_data_merger or JSONMerger() |
| 321 self._json_data_value_overrides = json_data_value_overrides or {} | 327 self._json_data_value_overrides = json_data_value_overrides or {} |
| 322 | 328 |
| 323 def __call__(self, out_filename, to_merge): | 329 def __call__(self, out_filename, to_merge): |
| 324 try: | 330 try: |
| 325 before_a, output_data, after_a = self.load_jsonp( | 331 before_0, new_json_data_0, after_0 = self.load_jsonp( |
| 326 self.filesystem.open_binary_file_for_reading(to_merge[0])) | 332 self.filesystem.open_binary_file_for_reading(to_merge[0])) |
| 327 except ValueError as e: | 333 except ValueError as e: |
| 328 raise MergeFailure(e.message, to_merge[0], None, None) | 334 raise MergeFailure(e.message, to_merge[0], None) |
| 329 | 335 |
| 330 for filename in to_merge[1:]: | 336 input_data = [new_json_data_0] |
| 337 for filename_n in to_merge[1:]: | |
| 331 try: | 338 try: |
| 332 before_b, new_json_data, after_b = self.load_jsonp( | 339 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
| |
| 333 self.filesystem.open_binary_file_for_reading(filename)) | 340 self.filesystem.open_binary_file_for_reading(filename_n)) |
| 334 except ValueError as e: | 341 except ValueError as e: |
| 335 raise MergeFailure(e.message, filename, None, None) | 342 raise MergeFailure(e.message, filename_n, None) |
| 336 | 343 |
| 337 if before_a != before_b: | 344 if before_0 != beforen: |
| 338 raise MergeFailure( | 345 raise MergeFailure( |
| 339 "jsonp starting data from %s doesn't match." % filename, | 346 "jsonp starting data from %s doesn't match." % filename_n, |
| 340 out_filename, | 347 out_filename, |
| 341 before_a, before_b) | 348 [before_0, beforen]) |
| 342 | 349 |
| 343 if after_a != after_b: | 350 if after_0 != aftern: |
| 344 raise MergeFailure( | 351 raise MergeFailure( |
| 345 "jsonp ending data from %s doesn't match." % filename, | 352 "jsonp ending data from %s doesn't match." % filename_n, |
| 346 out_filename, | 353 out_filename, |
| 347 after_a, after_b) | 354 [after_0, aftern]) |
| 348 | 355 |
| 349 output_data = self._json_data_merger.merge(output_data, new_json_dat a, filename) | 356 input_data.append(new_json_data_n) |
| 350 | 357 |
| 358 output_data = self._json_data_merger.merge(input_data, name=out_filename ) | |
| 351 output_data.update(self._json_data_value_overrides) | 359 output_data.update(self._json_data_value_overrides) |
| 352 | 360 |
| 353 self.dump_jsonp( | 361 self.dump_jsonp( |
| 354 self.filesystem.open_binary_file_for_writing(out_filename), | 362 self.filesystem.open_binary_file_for_writing(out_filename), |
| 355 before_a, output_data, after_a) | 363 before_0, output_data, after_0) |
| 356 | 364 |
| 357 @staticmethod | 365 @staticmethod |
| 358 def load_jsonp(fd): | 366 def load_jsonp(fd): |
| 359 """Load a JSONP file and return the JSON data parsed. | 367 """Load a JSONP file and return the JSON data parsed. |
| 360 | 368 |
| 361 JSONP files have a JSON data structure wrapped in a function call or | 369 JSONP files have a JSON data structure wrapped in a function call or |
| 362 other non-JSON data. | 370 other non-JSON data. |
| 363 """ | 371 """ |
| 364 in_data = fd.read() | 372 in_data = fd.read() |
| 365 | 373 |
| (...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 | 466 # 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:] | 467 rel_file = self.filesystem.join(dir_path, f)[len(base_dir) + 1:] |
| 460 files.setdefault(rel_file, []).append(base_dir) | 468 files.setdefault(rel_file, []).append(base_dir) |
| 461 | 469 |
| 462 # Go through each file and try to merge it. | 470 # Go through each file and try to merge it. |
| 463 # partial_file_path is the file relative to the directories. | 471 # partial_file_path is the file relative to the directories. |
| 464 for partial_file_path, in_dirs in sorted(files.iteritems()): | 472 for partial_file_path, in_dirs in sorted(files.iteritems()): |
| 465 out_path = self.filesystem.join(output_dir, partial_file_path) | 473 out_path = self.filesystem.join(output_dir, partial_file_path) |
| 466 if self.filesystem.exists(out_path): | 474 if self.filesystem.exists(out_path): |
| 467 raise MergeFailure( | 475 raise MergeFailure( |
| 468 'File %s already exist in output.', out_path, None, None) | 476 'File %s already exist in output.', out_path, None) |
| 469 | 477 |
| 470 dirname = self.filesystem.dirname(out_path) | 478 dirname = self.filesystem.dirname(out_path) |
| 471 if not self.filesystem.exists(dirname): | 479 if not self.filesystem.exists(dirname): |
| 472 self.filesystem.maybe_make_directory(dirname) | 480 self.filesystem.maybe_make_directory(dirname) |
| 473 | 481 |
| 474 to_merge = [self.filesystem.join(d, partial_file_path) for d in in_d irs] | 482 to_merge = [self.filesystem.join(d, partial_file_path) for d in in_d irs] |
| 475 | 483 |
| 476 _log.debug("Creating merged %s from %s", out_path, to_merge) | 484 _log.debug("Creating merged %s from %s", out_path, to_merge) |
| 477 | 485 |
| 478 for match_func, merge_func in reversed(self.helpers): | 486 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$', | 537 ':num_passes$', |
| 530 ':num_regressions$', | 538 ':num_regressions$', |
| 531 ':skipped$', | 539 ':skipped$', |
| 532 ':skips$', | 540 ':skips$', |
| 533 # All keys inside the num_failures_by_type entry. | 541 # All keys inside the num_failures_by_type entry. |
| 534 ':num_failures_by_type:', | 542 ':num_failures_by_type:', |
| 535 ] | 543 ] |
| 536 for match_name in addable: | 544 for match_name in addable: |
| 537 self.add_helper( | 545 self.add_helper( |
| 538 NameMatch(match_name), | 546 NameMatch(match_name), |
| 539 lambda a, b, name=None: a + b) | 547 lambda o, name=None: sum(o)) |
| 540 | 548 |
| 541 # If any shard is interrupted, mark the whole thing as interrupted. | 549 # If any shard is interrupted, mark the whole thing as interrupted. |
| 542 self.add_helper( | 550 self.add_helper( |
| 543 NameMatch(':interrupted$'), | 551 NameMatch(':interrupted$'), |
| 544 lambda a, b, name=None: a or b) | 552 lambda o, name=None: bool(sum(o))) |
| 545 | 553 |
| 546 # Layout test directory value is randomly created on each shard, so | 554 # Layout test directory value is randomly created on each shard, so |
| 547 # clear it. | 555 # clear it. |
| 548 self.add_helper( | 556 self.add_helper( |
| 549 NameMatch(':layout_tests_dir$'), | 557 NameMatch(':layout_tests_dir$'), |
| 550 lambda a, b, name=None: None) | 558 lambda o, name=None: None) |
| 551 | 559 |
| 552 # seconds_since_epoch is the start time, so we just take the earliest. | 560 # seconds_since_epoch is the start time, so we just take the earliest. |
| 553 self.add_helper( | 561 self.add_helper( |
| 554 NameMatch(':seconds_since_epoch$'), | 562 NameMatch(':seconds_since_epoch$'), |
| 555 lambda a, b, name=None: min(a, b)) | 563 lambda o, name=None: min(*o)) |
| 556 | 564 |
| 557 def fallback_matcher(self, obj_a, obj_b, name=None): | 565 def fallback_matcher(self, objs, name=None): |
| 558 if self.allow_unknown_if_matching: | 566 if self.allow_unknown_if_matching: |
| 559 result = self.merge_equal(obj_a, obj_b, name) | 567 result = self.merge_equal(objs, name) |
| 560 _log.warning('Unknown value %s, accepting anyway as it matches.', na me) | 568 _log.warning('Unknown value %s, accepting anyway as it matches.', na me) |
| 561 return result | 569 return result |
| 562 return JSONMerger.fallback_matcher(self, obj_a, obj_b, name) | 570 return JSONMerger.fallback_matcher(self, objs, name) |
| 563 | 571 |
| 564 | 572 |
| 565 class LayoutTestDirMerger(DirMerger): | 573 class LayoutTestDirMerger(DirMerger): |
| 566 """Merge layout test result directory.""" | 574 """Merge layout test result directory.""" |
| 567 | 575 |
| 568 def __init__(self, filesystem=None, | 576 def __init__(self, filesystem=None, |
| 569 results_json_value_overrides=None, | 577 results_json_value_overrides=None, |
| 570 results_json_allow_unknown_if_matching=False): | 578 results_json_allow_unknown_if_matching=False): |
| 571 DirMerger.__init__(self, filesystem) | 579 DirMerger.__init__(self, filesystem) |
| 572 | 580 |
| 573 # JSON merger for non-"result style" JSON files. | 581 # JSON merger for non-"result style" JSON files. |
| 574 basic_json_data_merger = JSONMerger() | 582 basic_json_data_merger = JSONMerger() |
| 575 basic_json_data_merger.fallback_matcher = basic_json_data_merger.merge_e qual | 583 basic_json_data_merger.fallback_matcher = basic_json_data_merger.merge_e qual |
| 576 self.add_helper( | 584 self.add_helper( |
| 577 FilenameMatch('\\.json'), | 585 FilenameMatch('\\.json$'), |
| 578 MergeFilesJSONP(self.filesystem, basic_json_data_merger)) | 586 MergeFilesJSONP(self.filesystem, basic_json_data_merger)) |
| 579 | 587 |
| 580 # access_log and error_log are httpd log files which are sortable. | 588 # access_log and error_log are httpd log files which are sortable. |
| 581 self.add_helper( | 589 self.add_helper( |
| 582 FilenameMatch('access_log\\.txt'), | 590 FilenameMatch('access_log\\.txt$'), |
| 583 MergeFilesLinesSorted(self.filesystem)) | 591 MergeFilesLinesSorted(self.filesystem)) |
| 584 self.add_helper( | 592 self.add_helper( |
| 585 FilenameMatch('error_log\\.txt'), | 593 FilenameMatch('error_log\\.txt$'), |
| 586 MergeFilesLinesSorted(self.filesystem)) | 594 MergeFilesLinesSorted(self.filesystem)) |
| 587 | 595 |
| 588 # pywebsocket files aren't particularly useful, so just save them. | 596 # pywebsocket files aren't particularly useful, so just save them. |
| 589 self.add_helper( | 597 self.add_helper( |
| 590 FilenameMatch('pywebsocket\\.ws\\.log-.*-err.txt'), | 598 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
| |
| 591 MergeFilesKeepFiles(self.filesystem)) | 599 MergeFilesKeepFiles(self.filesystem)) |
| 592 | 600 |
| 593 # These JSON files have "result style" JSON in them. | 601 # These JSON files have "result style" JSON in them. |
| 594 results_json_file_merger = MergeFilesJSONP( | 602 results_json_file_merger = MergeFilesJSONP( |
| 595 self.filesystem, | 603 self.filesystem, |
| 596 JSONTestResultsMerger( | 604 JSONTestResultsMerger( |
| 597 allow_unknown_if_matching=results_json_allow_unknown_if_matching ), | 605 allow_unknown_if_matching=results_json_allow_unknown_if_matching ), |
| 598 json_data_value_overrides=results_json_value_overrides or {}) | 606 json_data_value_overrides=results_json_value_overrides or {}) |
| 599 | 607 |
| 600 self.add_helper( | 608 self.add_helper( |
| 601 FilenameMatch('failing_results.json'), | 609 FilenameMatch('failing_results\\.json$'), |
| 602 results_json_file_merger) | 610 results_json_file_merger) |
| 603 self.add_helper( | 611 self.add_helper( |
| 604 FilenameMatch('full_results.json'), | 612 FilenameMatch('full_results\\.json$'), |
| 605 results_json_file_merger) | 613 results_json_file_merger) |
| 606 self.add_helper( | 614 self.add_helper( |
| 607 FilenameMatch('output.json'), | 615 FilenameMatch('output\\.json$'), |
| 608 results_json_file_merger) | 616 results_json_file_merger) |
| OLD | NEW |