1 # Copyright 2014 Google Inc. All rights reserved.
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 from collections import OrderedDict
20 class ResultType(object):
23 ImageOnlyFailure = 'ImageOnlyFailure'
28 values = (Pass, Failure, ImageOnlyFailure, Timeout, Crash, Skip)
32 # too many instance attributes pylint: disable=R0902
33 # too many arguments pylint: disable=R0913
35 def __init__(self, name, actual, started, took, worker,
36 expected=None, unexpected=False,
37 flaky=False, code=0, out='', err='', pid=0):
40 self.started = started
43 self.expected = expected or [ResultType.Pass]
44 self.unexpected = unexpected
52 class ResultSet(object):
57 def add(self, result):
58 self.results.append(result)
64 def make_full_results(metadata, seconds_since_epoch, all_test_names, results):
65 """Convert the typ results to the Chromium JSON test result format.
67 See http://www.chromium.org/developers/the-json-test-results-format
70 # We use OrderedDicts here so that the output is stable.
71 full_results = OrderedDict()
72 full_results['version'] = 3
73 full_results['interrupted'] = False
74 full_results['path_delimiter'] = TEST_SEPARATOR
75 full_results['seconds_since_epoch'] = seconds_since_epoch
78 key, val = md.split('=', 1)
79 full_results[key] = val
81 passing_tests = _passing_test_names(results)
82 failed_tests = failed_test_names(results)
83 skipped_tests = set(all_test_names) - passing_tests - failed_tests
85 full_results['num_failures_by_type'] = OrderedDict()
86 full_results['num_failures_by_type']['FAIL'] = len(failed_tests)
87 full_results['num_failures_by_type']['PASS'] = len(passing_tests)
88 full_results['num_failures_by_type']['SKIP'] = len(skipped_tests)
90 full_results['tests'] = OrderedDict()
92 for test_name in all_test_names:
94 if test_name in skipped_tests:
95 value['expected'] = 'SKIP'
96 value['actual'] = 'SKIP'
98 value['expected'] = 'PASS'
99 value['actual'] = _actual_results_for_test(test_name, results)
100 if value['actual'].endswith('FAIL'):
101 value['is_unexpected'] = True
102 _add_path_to_trie(full_results['tests'], test_name, value)
107 def make_upload_request(test_results_server, builder, master, testtype,
109 url = 'http://%s/testfile/upload' % test_results_server
110 attrs = [('builder', builder),
112 ('testtype', testtype)]
113 content_type, data = _encode_multipart_form_data(attrs, full_results)
114 return url, content_type, data
117 def exit_code_from_full_results(full_results):
118 return 1 if num_failures(full_results) else 0
121 def num_failures(full_results):
122 return full_results['num_failures_by_type']['FAIL']
125 def failed_test_names(results):
127 for r in results.results:
128 if r.actual == ResultType.Failure:
130 elif (r.actual == ResultType.Pass and r.name in names):
135 def _passing_test_names(results):
136 return set(r.name for r in results.results if r.actual == ResultType.Pass)
139 def _actual_results_for_test(test_name, results):
141 for r in results.results:
142 if r.name == test_name:
143 if r.actual == ResultType.Failure:
144 actuals.append('FAIL')
145 elif r.actual == ResultType.Pass:
146 actuals.append('PASS')
148 assert actuals, 'We did not find any result data for %s.' % test_name
149 return ' '.join(actuals)
152 def _add_path_to_trie(trie, path, value):
153 if TEST_SEPARATOR not in path:
156 directory, rest = path.split(TEST_SEPARATOR, 1)
157 if directory not in trie:
159 _add_path_to_trie(trie[directory], rest, value)
162 def _encode_multipart_form_data(attrs, test_results):
163 # Cloned from webkitpy/common/net/file_uploader.py
164 BOUNDARY = '-J-S-O-N-R-E-S-U-L-T-S---B-O-U-N-D-A-R-Y-'
168 for key, value in attrs:
169 lines.append('--' + BOUNDARY)
170 lines.append('Content-Disposition: form-data; name="%s"' % key)
174 lines.append('--' + BOUNDARY)
175 lines.append('Content-Disposition: form-data; name="file"; '
176 'filename="full_results.json"')
177 lines.append('Content-Type: application/json')
179 lines.append(json.dumps(test_results))
181 lines.append('--' + BOUNDARY + '--')
183 body = CRLF.join(lines)
184 content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
185 return content_type, body