Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / tools / telemetry / telemetry / unittest / json_results.py
1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 import json
6 import time
7 import unittest
8 import urllib2
9
10
11 # TODO(dpranke): This code is largely cloned from, and redundant with,
12 # src/mojo/tools/run_mojo_python_tests.py, and also duplicates logic
13 # in test-webkitpy and run-webkit-tests. We should consolidate the
14 # python TestResult parsing/converting/uploading code as much as possible.
15
16
17 def AddOptions(parser):
18   parser.add_option('--metadata', action='append', default=[],
19                     help=('optional key=value metadata that will be stored '
20                           'in the results files (can be used for revision '
21                           'numbers, etc.)'))
22   parser.add_option('--write-full-results-to', metavar='FILENAME',
23                     action='store',
24                     help='The path to write the list of full results to.')
25   parser.add_option('--builder-name',
26                     help='The name of the builder as shown on the waterfall.')
27   parser.add_option('--master-name',
28                     help='The name of the buildbot master.')
29   parser.add_option("--test-results-server", default="",
30                     help=('If specified, upload full_results.json file to '
31                           'this server.'))
32   parser.add_option('--test-type',
33                     help=('Name of test type / step on the waterfall '
34                          '(e.g., "telemetry_unittests").'))
35
36
37 def ValidateArgs(parser, args):
38   for val in args.metadata:
39     if '=' not in val:
40       parser.error('Error: malformed metadata "%s"' % val)
41
42   if (args.test_results_server and
43       (not args.builder_name or not args.master_name or not args.test_type)):
44     parser.error('Error: --builder-name, --master-name, and --test-type '
45                  'must be specified along with --test-result-server.')
46
47
48 def WriteFullResultsIfNecessary(args, full_results):
49   if not args.write_full_results_to:
50     return
51
52   with open(args.write_full_results_to, 'w') as fp:
53     json.dump(full_results, fp, indent=2)
54     fp.write("\n")
55
56
57 def UploadFullResultsIfNecessary(args, _full_results):
58   if not args.test_results_server:
59     return False, ''
60
61   # TODO(dpranke) crbug.com/403663 disable this temporarily.
62   return False, ''
63
64   #url = 'http://%s/testfile/upload' % args.test_results_server
65   #attrs = [('builder', args.builder_name),
66   #         ('master', args.master_name),
67   #         ('testtype', args.test_type)]
68   #content_type, data = _EncodeMultiPartFormData(attrs,  full_results)
69   #return _UploadData(url, data, content_type)
70
71
72 TEST_SEPARATOR = '.'
73
74
75 def FullResults(args, suite, results):
76   """Convert the unittest results to the Chromium JSON test result format.
77
78   This matches run-webkit-tests (the layout tests) and the flakiness dashboard.
79   """
80
81   full_results = {}
82   full_results['interrupted'] = False
83   full_results['path_delimiter'] = TEST_SEPARATOR
84   full_results['version'] = 3
85   full_results['seconds_since_epoch'] = time.time()
86   full_results['builder_name'] = args.builder_name or ''
87   for md in args.metadata:
88     key, val = md.split('=', 1)
89     full_results[key] = val
90
91   # TODO(dpranke): Handle skipped tests as well.
92
93   all_test_names = AllTestNames(suite)
94   num_failures = NumFailuresAfterRetries(results)
95   full_results['num_failures_by_type'] = {
96       'FAIL': num_failures,
97       'PASS': len(all_test_names) - num_failures,
98   }
99
100   sets_of_passing_test_names = map(PassingTestNames, results)
101   sets_of_failing_test_names = map(FailedTestNames, results)
102
103   full_results['tests'] = {}
104
105   for test_name in all_test_names:
106     value = {
107         'expected': 'PASS',
108         'actual': ActualResultsForTest(test_name, sets_of_failing_test_names,
109                                        sets_of_passing_test_names)
110     }
111     if value['actual'].endswith('FAIL'):
112       value['is_unexpected'] = True
113     _AddPathToTrie(full_results['tests'], test_name, value)
114
115   return full_results
116
117
118 def ActualResultsForTest(test_name, sets_of_failing_test_names,
119                          sets_of_passing_test_names):
120   actuals = []
121   for retry_num in range(len(sets_of_failing_test_names)):
122     if test_name in sets_of_failing_test_names[retry_num]:
123       actuals.append('FAIL')
124     elif test_name in sets_of_passing_test_names[retry_num]:
125       assert ((retry_num == 0) or
126               (test_name in sets_of_failing_test_names[retry_num - 1])), (
127               'We should not have run a test that did not fail '
128               'on the previous run.')
129       actuals.append('PASS')
130
131   assert actuals, 'We did not find any result data for %s.' % test_name
132   return ' '.join(actuals)
133
134
135 def ExitCodeFromFullResults(full_results):
136   return 1 if full_results['num_failures_by_type']['FAIL'] else 0
137
138
139 def AllTestNames(suite):
140   test_names = []
141   # _tests is protected  pylint: disable=W0212
142   for test in suite._tests:
143     if isinstance(test, unittest.suite.TestSuite):
144       test_names.extend(AllTestNames(test))
145     else:
146       test_names.append(test.id())
147   return test_names
148
149
150 def NumFailuresAfterRetries(results):
151   return len(FailedTestNames(results[-1]))
152
153
154 def FailedTestNames(result):
155   return set(test.id() for test, _ in result.failures + result.errors)
156
157
158 def PassingTestNames(result):
159   return set(test.id() for test in result.successes)
160
161
162 def _AddPathToTrie(trie, path, value):
163   if TEST_SEPARATOR not in path:
164     trie[path] = value
165     return
166   directory, rest = path.split(TEST_SEPARATOR, 1)
167   if directory not in trie:
168     trie[directory] = {}
169   _AddPathToTrie(trie[directory], rest, value)
170
171
172 def _EncodeMultiPartFormData(attrs, full_results):
173   # Cloned from webkitpy/common/net/file_uploader.py
174   BOUNDARY = '-M-A-G-I-C---B-O-U-N-D-A-R-Y-'
175   CRLF = '\r\n'
176   lines = []
177
178   for key, value in attrs:
179     lines.append('--' + BOUNDARY)
180     lines.append('Content-Disposition: form-data; name="%s"' % key)
181     lines.append('')
182     lines.append(value)
183
184   lines.append('--' + BOUNDARY)
185   lines.append('Content-Disposition: form-data; name="file"; '
186                'filename="full_results.json"')
187   lines.append('Content-Type: application/json')
188   lines.append('')
189   lines.append(json.dumps(full_results))
190
191   lines.append('--' + BOUNDARY + '--')
192   lines.append('')
193   body = CRLF.join(lines)
194   content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
195   return content_type, body
196
197
198 def _UploadData(url, data, content_type):
199   request = urllib2.Request(url, data, {'Content-Type': content_type})
200   try:
201     response = urllib2.urlopen(request)
202     if response.code == 200:
203       return False, ''
204     return True, ('Uploading the JSON results failed with %d: "%s"' %
205                   (response.code, response.read()))
206   except Exception as e:
207     return True, 'Uploading the JSON results raised "%s"\n' % str(e)