| Index: scripts/slave/recipe_modules/swarming/results_merger.py
|
| diff --git a/scripts/slave/recipe_modules/swarming/results_merger.py b/scripts/slave/recipe_modules/swarming/results_merger.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..0fb21ae421990e019e4285a4ceab923ac8e5e44e
|
| --- /dev/null
|
| +++ b/scripts/slave/recipe_modules/swarming/results_merger.py
|
| @@ -0,0 +1,111 @@
|
| +# Copyright 2016 The Chromium Authors. All rights reserved.
|
| +# Use of this source code is governed by a BSD-style license that can be
|
| +# found in the LICENSE file.
|
| +
|
| +import copy
|
| +
|
| +
|
| +def merge_test_results(shard_results_list):
|
| + """ Merge list of results.
|
| +
|
| + Args:
|
| + shard_results_list: list of results to merge. All the results must have the
|
| + same format. Supported format are simplified JSON format & Chromium JSON
|
| + test results format version 3 (see
|
| + https://www.chromium.org/developers/the-json-test-results-format)
|
| +
|
| + Returns:
|
| + a dictionary that represent the merged results. Its format follow the same
|
| + format of all results in |shard_results_list|.
|
| + """
|
| + if 'seconds_since_epoch' in shard_results_list[0]:
|
| + return _merge_json_test_result_format(shard_results_list)
|
| + else:
|
| + return _merge_simplified_json_format(shard_results_list)
|
| +
|
| +
|
| +def _merge_simplified_json_format(shard_results_list):
|
| + # This code is specialized to the "simplified" JSON format that used to be
|
| + # the standard for recipes.
|
| +
|
| + # These are the only keys we pay attention to in the output JSON.
|
| + merged_results = {
|
| + 'successes': [],
|
| + 'failures': [],
|
| + 'valid': True,
|
| + }
|
| +
|
| + for result_json in shard_results_list:
|
| + successes = result_json.get('successes', [])
|
| + failures = result_json.get('failures', [])
|
| + valid = result_json.get('valid', True)
|
| +
|
| + if (not isinstance(successes, list) or not isinstance(failures, list) or
|
| + not isinstance(valid, bool)):
|
| + raise Exception(
|
| + 'Unexpected value type in %s' % result_json) # pragma: no cover
|
| +
|
| + merged_results['successes'].extend(successes)
|
| + merged_results['failures'].extend(failures)
|
| + merged_results['valid'] = merged_results['valid'] and valid
|
| + return merged_results
|
| +
|
| +
|
| +def _merge_json_test_result_format(shard_results_list):
|
| + # This code is specialized to the Chromium JSON test results format version 3:
|
| + # https://www.chromium.org/developers/the-json-test-results-format
|
| +
|
| + # These are required fields for the JSON test result format version 3.
|
| + merged_results = {
|
| + 'tests': {},
|
| + 'interrupted': False,
|
| + 'path_delimiter': '',
|
| + 'version': 3,
|
| + 'seconds_since_epoch': float('inf'),
|
| + 'num_failures_by_type': {
|
| + }
|
| + }
|
| + # To make sure that we don't mutate existing shard_results_list.
|
| + shard_results_list = copy.deepcopy(shard_results_list)
|
| + for result_json in shard_results_list:
|
| + if not ('tests' in result_json and
|
| + 'interrupted' in result_json and
|
| + 'path_delimiter' in result_json and
|
| + 'version' in result_json and
|
| + 'seconds_since_epoch' in result_json and
|
| + 'num_failures_by_type' in result_json):
|
| + raise Exception('Invalid json test results')
|
| +
|
| + # Traverse the result_json's test trie & merged_results's test tries in
|
| + # DFS order & add the n to merged['tests'].
|
| + curr_result_nodes_queue = [result_json['tests']]
|
| + merged_results_nodes_queue = [merged_results['tests']]
|
| + while curr_result_nodes_queue:
|
| + curr_node = curr_result_nodes_queue.pop()
|
| + merged_node = merged_results_nodes_queue.pop()
|
| + for k, v in curr_node.iteritems():
|
| + if k in merged_node:
|
| + curr_result_nodes_queue.append(v)
|
| + merged_results_nodes_queue.append(merged_node[k])
|
| + else:
|
| + merged_node[k] = v
|
| +
|
| + # Update the rest of the fields for merged_results.
|
| + merged_results['interrupted'] |= result_json['interrupted']
|
| + if not merged_results['path_delimiter']:
|
| + merged_results['path_delimiter'] = result_json['path_delimiter']
|
| + elif merged_results['path_delimiter'] != result_json['path_delimiter']:
|
| + raise Exception( # pragma: no cover - covered by results_merger_unittest
|
| + 'Incosistent path delimiter: %s %s' %
|
| + (merged_results['path_delimiter'],
|
| + result_json['path_delimiter']))
|
| + if result_json['version'] != 3:
|
| + raise Exception( # pragma: no cover - covered by results_merger_unittest
|
| + 'Only version 3 of json test result format is supported')
|
| + merged_results['seconds_since_epoch'] = min(
|
| + merged_results['seconds_since_epoch'],
|
| + result_json['seconds_since_epoch'])
|
| + for result_type, count in result_json['num_failures_by_type'].iteritems():
|
| + merged_results['num_failures_by_type'].setdefault(result_type, 0)
|
| + merged_results['num_failures_by_type'][result_type] += count
|
| + return merged_results
|
|
|