Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/env python | |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 3 # Use of this source code is governed by a BSD-style license that can be | |
| 4 # found in the LICENSE file. | |
| 5 # get_swarm_results.py: Retrieves and output swarm test results for a given | |
| 6 # test request name. | |
| 7 | |
| 8 """Takes in a test name and retrives all the output that the swarm server | |
| 9 has produced for tests with that name. This is expected to be called as a | |
| 10 build step.""" | |
| 11 | |
| 12 import json | |
| 13 import optparse | |
| 14 import sys | |
| 15 import time | |
| 16 import urllib | |
| 17 import urllib2 | |
| 18 | |
| 19 | |
| 20 def _get_first_number(line): | |
| 21 for part in line.split(): | |
| 22 if part.isdigit(): | |
| 23 return int(part) | |
| 24 | |
| 25 print 'No number in :' | |
| 26 print line | |
| 27 return 0 | |
| 28 | |
|
M-A Ruel
2012/04/19 18:31:28
2 lines between file level symbols
csharp
2012/04/19 19:02:16
Done.
| |
| 29 class TestSummary(object): | |
|
M-A Ruel
2012/04/19 18:31:28
TestRunSummary would be better IMHO.
csharp
2012/04/19 19:02:16
Done.
| |
| 30 def __init__(self): | |
| 31 self.test_passed_count = 0 | |
| 32 self.failed_tests = [] | |
| 33 self.disabled_test_count = 0 | |
| 34 self.ignored_test_count = 0 | |
| 35 | |
| 36 def AddSummaryData(self, buf): | |
| 37 lines = buf.splitlines() | |
| 38 | |
| 39 for line in lines: | |
| 40 if '[ PASSED ]' in line: | |
| 41 self.test_passed_count += _get_first_number(line) | |
| 42 elif '[ FAILED ]' in line: | |
| 43 if ', listed below' not in line: | |
| 44 self.failed_tests.append(line) | |
| 45 elif 'DISABLED' in line: | |
| 46 self.disabled_test_count += _get_first_number(line) | |
| 47 elif 'failures' in line: | |
| 48 self.ignored_test_count += _get_first_number(line) | |
| 49 | |
| 50 def Output(self): | |
| 51 output = [] | |
| 52 | |
| 53 output.append('[ PASSED ] %i tests.' % self.test_passed_count) | |
| 54 if self.failed_tests: | |
| 55 output.append('[ FAILED ] failed tests listed below:') | |
| 56 output.extend(self.failed_tests) | |
| 57 output.append('%i FAILED TESTS' % len(self.failed_tests)) | |
| 58 | |
| 59 if self.disabled_test_count: | |
| 60 output.append('%i DISABLED TESTS' % self.disabled_test_count) | |
| 61 | |
| 62 if self.ignored_test_count: | |
| 63 output.append('%i tests with ignored failures (FAILS prefix)' % | |
| 64 self.ignored_test_count) | |
| 65 | |
| 66 return output | |
| 67 | |
| 68 | |
| 69 # TODO(csharp) The sharing_supervisor.py also has test parsing code, they should | |
| 70 # be shared. | |
| 71 def TestRunOutput(output): | |
| 72 """Go through the given output and only return the output from the Test Run | |
| 73 Step. | |
| 74 """ | |
| 75 test_run_output = [] | |
| 76 | |
| 77 in_step = False | |
| 78 step_name = '' | |
| 79 for line in output.splitlines(True): | |
| 80 if in_step: | |
| 81 if '[ OK ] ' + step_name in line: | |
| 82 break | |
| 83 else: | |
| 84 test_run_output.append(line) | |
| 85 elif '[ RUN ] ' in line and 'Run Test' in line: | |
| 86 in_step = True | |
| 87 i = len('[ RUN ] ') | |
| 88 step_name = line[i:].strip() | |
| 89 | |
| 90 return ''.join(test_run_output) | |
| 91 | |
| 92 | |
| 93 def GetTestKeys(hostname, port, test_name): | |
| 94 swarm_base_url = 'http://%s:%d' % (hostname, port) | |
| 95 key_data = urllib.urlencode([('name', test_name)]) | |
| 96 test_keys_url = '%s/get_matching_test_cases?%s' % (swarm_base_url, | |
| 97 key_data) | |
| 98 result = urllib2.urlopen(test_keys_url).read() | |
| 99 if 'No matching' in result: | |
| 100 print ('Error: Unable to find any tests with the name, %s, on swarm server' | |
| 101 % test_name) | |
| 102 return [] | |
| 103 | |
| 104 # TODO(csharp): return in a proper format (like json) | |
| 105 return result.split() | |
| 106 | |
| 107 | |
| 108 def GetSwarmResults(hostname, port, test_keys): | |
| 109 swarm_base_url = 'http://%s:%d' % (hostname, port) | |
| 110 summary_total = TestSummary() | |
| 111 hostnames = ['unknown'] * len(test_keys) | |
| 112 exit_codes = [1] * len(test_keys) | |
| 113 for index in range(len(test_keys)): | |
| 114 result_url = '%s/get_result?r=%s' % (swarm_base_url, test_keys[index]) | |
| 115 while True: | |
| 116 output = None | |
| 117 try: | |
| 118 output = urllib2.urlopen(result_url).read() | |
| 119 except urllib2.HTTPError, e: | |
| 120 print 'Calling %s threw %s' % (result_url, e) | |
| 121 break | |
| 122 | |
| 123 try: | |
| 124 test_outputs = json.loads(output) | |
| 125 except (ValueError, TypeError), e: | |
| 126 print 'Unable to get results for shard %d' % index | |
| 127 print e | |
| 128 break | |
| 129 | |
| 130 if test_outputs['output']: | |
| 131 test_exit_codes = test_outputs['exit_codes'].split(',') | |
| 132 exit_codes[index] = int(max(test_exit_codes)) | |
| 133 hostnames[index] = test_outputs['hostname'] | |
| 134 | |
| 135 print | |
| 136 print '================================================================' | |
| 137 print 'Begin output from shard index %s (%s)' % (index, | |
| 138 hostnames[index]) | |
| 139 print '================================================================' | |
| 140 print | |
| 141 | |
| 142 cleaned_output = TestRunOutput(test_outputs['output']) | |
| 143 summary_index = cleaned_output.rfind('[ PASSED ]') | |
| 144 summary_total.AddSummaryData(cleaned_output[summary_index:]) | |
| 145 sys.stdout.write(cleaned_output[:summary_index - 1]) | |
| 146 | |
| 147 print | |
| 148 print '================================================================' | |
| 149 print 'End output from shard index %s (%s). Return %d' % ( | |
| 150 index, hostnames[index], exit_codes[index]) | |
| 151 print '================================================================' | |
| 152 print | |
| 153 | |
| 154 if exit_codes[index] == 0: | |
| 155 # If the test passed, delete the key since it is no longer needed. | |
| 156 remove_key_url = '%s/cleanup_results' % swarm_base_url | |
| 157 key_encoding = urllib.urlencode([('r', test_keys[index])]) | |
| 158 urllib2.urlopen(remove_key_url, | |
| 159 key_encoding) | |
| 160 break | |
| 161 else: | |
| 162 # Test is not yet done, wait a bit before checking again. | |
| 163 time.sleep(0.5) | |
| 164 | |
| 165 print '\n'.join(summary_total.Output()) | |
| 166 print | |
| 167 | |
| 168 return max(exit_codes) | |
| 169 | |
| 170 | |
| 171 def main(): | |
| 172 """Retrieve the given swarm test results from the swarm server and print it | |
| 173 to stdout. | |
| 174 | |
| 175 Args: | |
| 176 test_name: The name of the test to retrieve output for. | |
| 177 """ | |
| 178 # Parses arguments | |
| 179 parser = optparse.OptionParser(usage='%prog [options] test_name', | |
| 180 description=sys.modules[__name__].__doc__) | |
| 181 parser.add_option('-n', '--hostname', default='localhost', | |
|
M-A Ruel
2012/04/19 18:31:28
why not accept an url instead? As such, it would b
csharp
2012/04/19 19:02:16
Good point. Changed
| |
| 182 help='Specify the hostname of the Swarm server. ' | |
| 183 'Defaults to %default') | |
| 184 parser.add_option('-p', '--port', type='int', default=8080, | |
| 185 help='Specify the port of the Swarm server. ' | |
| 186 'Defaults to %default') | |
| 187 parser.add_option('-v', '--verbose', action='store_true', | |
| 188 help='Print verbose logging') | |
| 189 (options, args) = parser.parse_args() | |
| 190 if not args: | |
| 191 parser.error('Must specify one test name.') | |
| 192 elif len(args) > 1: | |
| 193 parser.error('Must specify only one test name.') | |
| 194 test_name = args[0] | |
| 195 | |
| 196 test_keys = GetTestKeys(options.hostname, options.port, test_name) | |
| 197 | |
| 198 return GetSwarmResults(options.hostname, options.port, test_keys) | |
| 199 | |
| 200 if __name__ == '__main__': | |
| 201 sys.exit(main()) | |
| OLD | NEW |