Roger Meier | 41ad434 | 2015-03-24 22:30:40 +0100 | [diff] [blame] | 1 | # |
| 2 | # Licensed to the Apache Software Foundation (ASF) under one |
| 3 | # or more contributor license agreements. See the NOTICE file |
| 4 | # distributed with this work for additional information |
| 5 | # regarding copyright ownership. The ASF licenses this file |
| 6 | # to you under the Apache License, Version 2.0 (the |
| 7 | # "License"); you may not use this file except in compliance |
| 8 | # with the License. You may obtain a copy of the License at |
| 9 | # |
| 10 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | # |
| 12 | # Unless required by applicable law or agreed to in writing, |
| 13 | # software distributed under the License is distributed on an |
| 14 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 15 | # KIND, either express or implied. See the License for the |
| 16 | # specific language governing permissions and limitations |
| 17 | # under the License. |
| 18 | # |
| 19 | |
| 20 | import datetime |
| 21 | import json |
| 22 | import multiprocessing |
| 23 | import os |
| 24 | import platform |
| 25 | import re |
| 26 | import subprocess |
| 27 | import sys |
| 28 | import time |
| 29 | import traceback |
| 30 | |
Nobuaki Sukegawa | 2de2700 | 2015-11-22 01:13:48 +0900 | [diff] [blame] | 31 | from .test import TestEntry |
Roger Meier | 41ad434 | 2015-03-24 22:30:40 +0100 | [diff] [blame] | 32 | |
| 33 | LOG_DIR = 'log' |
Nobuaki Sukegawa | bd16530 | 2016-01-19 11:10:07 +0900 | [diff] [blame] | 34 | RESULT_HTML = 'index.html' |
Roger Meier | 41ad434 | 2015-03-24 22:30:40 +0100 | [diff] [blame] | 35 | RESULT_JSON = 'results.json' |
| 36 | FAIL_JSON = 'known_failures_%s.json' |
| 37 | |
| 38 | |
Alexandre Detiste | 24df0a5 | 2025-01-16 00:39:20 +0100 | [diff] [blame] | 39 | def logfile_open(*args): |
| 40 | return open(*args, errors='replace') |
| 41 | |
| 42 | |
Roger Meier | 41ad434 | 2015-03-24 22:30:40 +0100 | [diff] [blame] | 43 | def generate_known_failures(testdir, overwrite, save, out): |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 44 | def collect_failures(results): |
| 45 | success_index = 5 |
| 46 | for r in results: |
| 47 | if not r[success_index]: |
| 48 | yield TestEntry.get_name(*r) |
| 49 | try: |
Alexandre Detiste | 24df0a5 | 2025-01-16 00:39:20 +0100 | [diff] [blame] | 50 | with logfile_open(os.path.join(testdir, RESULT_JSON), 'r') as fp: |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 51 | results = json.load(fp) |
| 52 | except IOError: |
| 53 | sys.stderr.write('Unable to load last result. Did you run tests ?\n') |
| 54 | return False |
| 55 | fails = collect_failures(results['results']) |
| 56 | if not overwrite: |
| 57 | known = load_known_failures(testdir) |
| 58 | known.extend(fails) |
| 59 | fails = known |
| 60 | fails_json = json.dumps(sorted(set(fails)), indent=2, separators=(',', ': ')) |
| 61 | if save: |
| 62 | with logfile_open(os.path.join(testdir, FAIL_JSON % platform.system()), 'w+') as fp: |
| 63 | fp.write(fails_json) |
| 64 | sys.stdout.write('Successfully updated known failures.\n') |
| 65 | if out: |
| 66 | sys.stdout.write(fails_json) |
| 67 | sys.stdout.write('\n') |
| 68 | return True |
Roger Meier | 41ad434 | 2015-03-24 22:30:40 +0100 | [diff] [blame] | 69 | |
| 70 | |
| 71 | def load_known_failures(testdir): |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 72 | try: |
Alexandre Detiste | 24df0a5 | 2025-01-16 00:39:20 +0100 | [diff] [blame] | 73 | with logfile_open(os.path.join(testdir, FAIL_JSON % platform.system()), 'r') as fp: |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 74 | return json.load(fp) |
| 75 | except IOError: |
| 76 | return [] |
Roger Meier | 41ad434 | 2015-03-24 22:30:40 +0100 | [diff] [blame] | 77 | |
| 78 | |
| 79 | class TestReporter(object): |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 80 | # Unfortunately, standard library doesn't handle timezone well |
| 81 | # DATETIME_FORMAT = '%a %b %d %H:%M:%S %Z %Y' |
| 82 | DATETIME_FORMAT = '%a %b %d %H:%M:%S %Y' |
Roger Meier | 41ad434 | 2015-03-24 22:30:40 +0100 | [diff] [blame] | 83 | |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 84 | def __init__(self): |
| 85 | self._log = multiprocessing.get_logger() |
| 86 | self._lock = multiprocessing.Lock() |
Roger Meier | 41ad434 | 2015-03-24 22:30:40 +0100 | [diff] [blame] | 87 | |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 88 | @classmethod |
| 89 | def test_logfile(cls, test_name, prog_kind, dir=None): |
Alexandre Detiste | 24df0a5 | 2025-01-16 00:39:20 +0100 | [diff] [blame] | 90 | relpath = os.path.join('log', '%s_%s.log' % (test_name, prog_kind)) |
| 91 | return relpath if not dir else os.path.realpath(os.path.join(dir, relpath)) |
Roger Meier | 41ad434 | 2015-03-24 22:30:40 +0100 | [diff] [blame] | 92 | |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 93 | def _start(self): |
| 94 | self._start_time = time.time() |
Roger Meier | 41ad434 | 2015-03-24 22:30:40 +0100 | [diff] [blame] | 95 | |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 96 | @property |
| 97 | def _elapsed(self): |
| 98 | return time.time() - self._start_time |
Roger Meier | 41ad434 | 2015-03-24 22:30:40 +0100 | [diff] [blame] | 99 | |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 100 | @classmethod |
| 101 | def _format_date(cls): |
| 102 | return '%s' % datetime.datetime.now().strftime(cls.DATETIME_FORMAT) |
Roger Meier | 41ad434 | 2015-03-24 22:30:40 +0100 | [diff] [blame] | 103 | |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 104 | def _print_date(self): |
| 105 | print(self._format_date(), file=self.out) |
Roger Meier | 41ad434 | 2015-03-24 22:30:40 +0100 | [diff] [blame] | 106 | |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 107 | def _print_bar(self, out=None): |
| 108 | print( |
Gonzalo Aguilar Delgado | bc0082e | 2016-03-04 13:16:22 +0100 | [diff] [blame] | 109 | '===============================================================================', |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 110 | file=(out or self.out)) |
Roger Meier | 41ad434 | 2015-03-24 22:30:40 +0100 | [diff] [blame] | 111 | |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 112 | def _print_exec_time(self): |
| 113 | print('Test execution took {:.1f} seconds.'.format(self._elapsed), file=self.out) |
Roger Meier | 41ad434 | 2015-03-24 22:30:40 +0100 | [diff] [blame] | 114 | |
| 115 | |
| 116 | class ExecReporter(TestReporter): |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 117 | def __init__(self, testdir, test, prog): |
| 118 | super(ExecReporter, self).__init__() |
| 119 | self._test = test |
| 120 | self._prog = prog |
| 121 | self.logpath = self.test_logfile(test.name, prog.kind, testdir) |
Roger Meier | 41ad434 | 2015-03-24 22:30:40 +0100 | [diff] [blame] | 122 | self.out = None |
Roger Meier | 41ad434 | 2015-03-24 22:30:40 +0100 | [diff] [blame] | 123 | |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 124 | def begin(self): |
| 125 | self._start() |
| 126 | self._open() |
| 127 | if self.out and not self.out.closed: |
| 128 | self._print_header() |
| 129 | else: |
| 130 | self._log.debug('Output stream is not available.') |
Nobuaki Sukegawa | 2ba7944 | 2016-01-12 19:37:55 +0900 | [diff] [blame] | 131 | |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 132 | def end(self, returncode): |
| 133 | self._lock.acquire() |
| 134 | try: |
| 135 | if self.out and not self.out.closed: |
| 136 | self._print_footer(returncode) |
| 137 | self._close() |
| 138 | self.out = None |
| 139 | else: |
| 140 | self._log.debug('Output stream is not available.') |
| 141 | finally: |
| 142 | self._lock.release() |
Roger Meier | 41ad434 | 2015-03-24 22:30:40 +0100 | [diff] [blame] | 143 | |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 144 | def killed(self): |
| 145 | print(file=self.out) |
| 146 | print('Server process is successfully killed.', file=self.out) |
| 147 | self.end(None) |
Roger Meier | 41ad434 | 2015-03-24 22:30:40 +0100 | [diff] [blame] | 148 | |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 149 | def died(self): |
| 150 | print(file=self.out) |
| 151 | print('*** Server process has died unexpectedly ***', file=self.out) |
| 152 | self.end(None) |
| 153 | |
| 154 | _init_failure_exprs = { |
| 155 | 'server': list(map(re.compile, [ |
| 156 | '[Aa]ddress already in use', |
| 157 | 'Could not bind', |
| 158 | 'EADDRINUSE', |
| 159 | ])), |
| 160 | 'client': list(map(re.compile, [ |
| 161 | '[Cc]onnection refused', |
James E. King III | 9bea32f | 2018-03-16 16:07:42 -0400 | [diff] [blame] | 162 | 'Could not connect to', |
James E. King III | 9aaf295 | 2018-03-20 15:06:08 -0400 | [diff] [blame] | 163 | 'Could not open UNIX ', # domain socket (rb) |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 164 | 'ECONNREFUSED', |
James E. King III | 9bea32f | 2018-03-16 16:07:42 -0400 | [diff] [blame] | 165 | 'econnrefused', # erl |
| 166 | 'CONNECTION-REFUSED-ERROR', # cl |
James E. King III | 714c77c | 2018-03-20 19:58:38 -0400 | [diff] [blame] | 167 | 'connect ENOENT', # nodejs domain socket |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 168 | 'No such file or directory', # domain socket |
James E. King III | 714c77c | 2018-03-20 19:58:38 -0400 | [diff] [blame] | 169 | 'Sockets.TcpClient.Connect', # csharp |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 170 | ])), |
| 171 | } |
| 172 | |
| 173 | def maybe_false_positive(self): |
| 174 | """Searches through log file for socket bind error. |
| 175 | Returns True if suspicious expression is found, otherwise False""" |
| 176 | try: |
| 177 | if self.out and not self.out.closed: |
| 178 | self.out.flush() |
| 179 | exprs = self._init_failure_exprs[self._prog.kind] |
| 180 | |
| 181 | def match(line): |
| 182 | for expr in exprs: |
| 183 | if expr.search(line): |
James E. King III | 9bea32f | 2018-03-16 16:07:42 -0400 | [diff] [blame] | 184 | self._log.info("maybe false positive: %s" % line) |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 185 | return True |
| 186 | |
| 187 | with logfile_open(self.logpath, 'r') as fp: |
| 188 | if any(map(match, fp)): |
| 189 | return True |
| 190 | except (KeyboardInterrupt, SystemExit): |
| 191 | raise |
| 192 | except Exception as ex: |
| 193 | self._log.warn('[%s]: Error while detecting false positive: %s' % (self._test.name, str(ex))) |
| 194 | self._log.info(traceback.print_exc()) |
| 195 | return False |
| 196 | |
| 197 | def _open(self): |
| 198 | self.out = logfile_open(self.logpath, 'w+') |
| 199 | |
| 200 | def _close(self): |
| 201 | self.out.close() |
| 202 | |
| 203 | def _print_header(self): |
| 204 | self._print_date() |
Alexandre Detiste | 24df0a5 | 2025-01-16 00:39:20 +0100 | [diff] [blame] | 205 | print('Executing: %s' % ' '.join(self._prog.command), file=self.out) |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 206 | print('Directory: %s' % self._prog.workdir, file=self.out) |
| 207 | print('config:delay: %s' % self._test.delay, file=self.out) |
| 208 | print('config:timeout: %s' % self._test.timeout, file=self.out) |
| 209 | self._print_bar() |
Roger Meier | 41ad434 | 2015-03-24 22:30:40 +0100 | [diff] [blame] | 210 | self.out.flush() |
Roger Meier | 41ad434 | 2015-03-24 22:30:40 +0100 | [diff] [blame] | 211 | |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 212 | def _print_footer(self, returncode=None): |
| 213 | self._print_bar() |
| 214 | if returncode is not None: |
James E. King III | 9bea32f | 2018-03-16 16:07:42 -0400 | [diff] [blame] | 215 | print('Return code: %d (negative values indicate kill by signal)' % returncode, file=self.out) |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 216 | else: |
| 217 | print('Process is killed.', file=self.out) |
| 218 | self._print_exec_time() |
| 219 | self._print_date() |
Roger Meier | 41ad434 | 2015-03-24 22:30:40 +0100 | [diff] [blame] | 220 | |
| 221 | |
| 222 | class SummaryReporter(TestReporter): |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 223 | def __init__(self, basedir, testdir_relative, concurrent=True): |
| 224 | super(SummaryReporter, self).__init__() |
| 225 | self._basedir = basedir |
| 226 | self._testdir_rel = testdir_relative |
Alexandre Detiste | 24df0a5 | 2025-01-16 00:39:20 +0100 | [diff] [blame] | 227 | self.logdir = os.path.join(self.testdir, LOG_DIR) |
| 228 | self.out_path = os.path.join(self.testdir, RESULT_JSON) |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 229 | self.concurrent = concurrent |
| 230 | self.out = sys.stdout |
| 231 | self._platform = platform.system() |
| 232 | self._revision = self._get_revision() |
| 233 | self._tests = [] |
| 234 | if not os.path.exists(self.logdir): |
| 235 | os.mkdir(self.logdir) |
| 236 | self._known_failures = load_known_failures(self.testdir) |
| 237 | self._unexpected_success = [] |
| 238 | self._flaky_success = [] |
| 239 | self._unexpected_failure = [] |
| 240 | self._expected_failure = [] |
| 241 | self._print_header() |
Roger Meier | 41ad434 | 2015-03-24 22:30:40 +0100 | [diff] [blame] | 242 | |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 243 | @property |
| 244 | def testdir(self): |
Alexandre Detiste | 24df0a5 | 2025-01-16 00:39:20 +0100 | [diff] [blame] | 245 | return os.path.join(self._basedir, self._testdir_rel) |
Nobuaki Sukegawa | bd16530 | 2016-01-19 11:10:07 +0900 | [diff] [blame] | 246 | |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 247 | def _result_string(self, test): |
| 248 | if test.success: |
| 249 | if test.retry_count == 0: |
| 250 | return 'success' |
| 251 | elif test.retry_count == 1: |
| 252 | return 'flaky(1 retry)' |
| 253 | else: |
| 254 | return 'flaky(%d retries)' % test.retry_count |
| 255 | elif test.expired: |
| 256 | return 'failure(timeout)' |
Roger Meier | 41ad434 | 2015-03-24 22:30:40 +0100 | [diff] [blame] | 257 | else: |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 258 | return 'failure(%d)' % test.returncode |
| 259 | |
| 260 | def _get_revision(self): |
| 261 | p = subprocess.Popen(['git', 'rev-parse', '--short', 'HEAD'], |
| 262 | cwd=self.testdir, stdout=subprocess.PIPE) |
| 263 | out, _ = p.communicate() |
| 264 | return out.strip() |
| 265 | |
| 266 | def _format_test(self, test, with_result=True): |
| 267 | name = '%s-%s' % (test.server.name, test.client.name) |
| 268 | trans = '%s-%s' % (test.transport, test.socket) |
| 269 | if not with_result: |
Gonzalo Aguilar Delgado | bc0082e | 2016-03-04 13:16:22 +0100 | [diff] [blame] | 270 | return '{:24s}{:18s}{:25s}'.format(name[:23], test.protocol[:17], trans[:24]) |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 271 | else: |
James E. King III | 9bea32f | 2018-03-16 16:07:42 -0400 | [diff] [blame] | 272 | return '{:24s}{:18s}{:25s}{:s}\n'.format(name[:23], test.protocol[:17], |
| 273 | trans[:24], self._result_string(test)) |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 274 | |
| 275 | def _print_test_header(self): |
| 276 | self._print_bar() |
| 277 | print( |
Gonzalo Aguilar Delgado | bc0082e | 2016-03-04 13:16:22 +0100 | [diff] [blame] | 278 | '{:24s}{:18s}{:25s}{:s}'.format('server-client:', 'protocol:', 'transport:', 'result:'), |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 279 | file=self.out) |
| 280 | |
| 281 | def _print_header(self): |
| 282 | self._start() |
| 283 | print('Apache Thrift - Integration Test Suite', file=self.out) |
| 284 | self._print_date() |
| 285 | self._print_test_header() |
| 286 | |
| 287 | def _print_unexpected_failure(self): |
| 288 | if len(self._unexpected_failure) > 0: |
| 289 | self.out.writelines([ |
| 290 | '*** Following %d failures were unexpected ***:\n' % len(self._unexpected_failure), |
| 291 | 'If it is introduced by you, please fix it before submitting the code.\n', |
| 292 | # 'If not, please report at https://issues.apache.org/jira/browse/THRIFT\n', |
| 293 | ]) |
| 294 | self._print_test_header() |
| 295 | for i in self._unexpected_failure: |
| 296 | self.out.write(self._format_test(self._tests[i])) |
| 297 | self._print_bar() |
| 298 | else: |
| 299 | print('No unexpected failures.', file=self.out) |
| 300 | |
| 301 | def _print_flaky_success(self): |
| 302 | if len(self._flaky_success) > 0: |
| 303 | print( |
| 304 | 'Following %d tests were expected to cleanly succeed but needed retry:' % len(self._flaky_success), |
| 305 | file=self.out) |
| 306 | self._print_test_header() |
| 307 | for i in self._flaky_success: |
| 308 | self.out.write(self._format_test(self._tests[i])) |
| 309 | self._print_bar() |
| 310 | |
| 311 | def _print_unexpected_success(self): |
| 312 | if len(self._unexpected_success) > 0: |
| 313 | print( |
| 314 | 'Following %d tests were known to fail but succeeded (maybe flaky):' % len(self._unexpected_success), |
| 315 | file=self.out) |
| 316 | self._print_test_header() |
| 317 | for i in self._unexpected_success: |
| 318 | self.out.write(self._format_test(self._tests[i])) |
| 319 | self._print_bar() |
| 320 | |
| 321 | def _http_server_command(self, port): |
Alexandre Detiste | d078721 | 2024-12-26 02:44:13 +0100 | [diff] [blame] | 322 | return 'python -m http.server %d' % port |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 323 | |
| 324 | def _print_footer(self): |
| 325 | fail_count = len(self._expected_failure) + len(self._unexpected_failure) |
| 326 | self._print_bar() |
| 327 | self._print_unexpected_success() |
| 328 | self._print_flaky_success() |
| 329 | self._print_unexpected_failure() |
| 330 | self._write_html_data() |
| 331 | self._assemble_log('unexpected failures', self._unexpected_failure) |
| 332 | self._assemble_log('known failures', self._expected_failure) |
| 333 | self.out.writelines([ |
| 334 | 'You can browse results at:\n', |
| 335 | '\tfile://%s/%s\n' % (self.testdir, RESULT_HTML), |
| 336 | '# If you use Chrome, run:\n', |
| 337 | '# \tcd %s\n#\t%s\n' % (self._basedir, self._http_server_command(8001)), |
| 338 | '# then browse:\n', |
| 339 | '# \thttp://localhost:%d/%s/\n' % (8001, self._testdir_rel), |
| 340 | 'Full log for each test is here:\n', |
James E. King, III | 375bfee | 2017-10-26 00:09:34 -0400 | [diff] [blame] | 341 | '\ttest/log/server_client_protocol_transport_client.log\n', |
| 342 | '\ttest/log/server_client_protocol_transport_server.log\n', |
Nobuaki Sukegawa | 10308cb | 2016-02-03 01:57:03 +0900 | [diff] [blame] | 343 | '%d failed of %d tests in total.\n' % (fail_count, len(self._tests)), |
| 344 | ]) |
| 345 | self._print_exec_time() |
| 346 | self._print_date() |
| 347 | |
| 348 | def _render_result(self, test): |
| 349 | return [ |
| 350 | test.server.name, |
| 351 | test.client.name, |
| 352 | test.protocol, |
| 353 | test.transport, |
| 354 | test.socket, |
| 355 | test.success, |
| 356 | test.as_expected, |
| 357 | test.returncode, |
| 358 | { |
| 359 | 'server': self.test_logfile(test.name, test.server.kind), |
| 360 | 'client': self.test_logfile(test.name, test.client.kind), |
| 361 | }, |
| 362 | ] |
| 363 | |
| 364 | def _write_html_data(self): |
| 365 | """Writes JSON data to be read by result html""" |
| 366 | results = [self._render_result(r) for r in self._tests] |
| 367 | with logfile_open(self.out_path, 'w+') as fp: |
| 368 | fp.write(json.dumps({ |
| 369 | 'date': self._format_date(), |
| 370 | 'revision': str(self._revision), |
| 371 | 'platform': self._platform, |
| 372 | 'duration': '{:.1f}'.format(self._elapsed), |
| 373 | 'results': results, |
| 374 | }, indent=2)) |
| 375 | |
| 376 | def _assemble_log(self, title, indexes): |
| 377 | if len(indexes) > 0: |
| 378 | def add_prog_log(fp, test, prog_kind): |
| 379 | print('*************************** %s message ***************************' % prog_kind, |
| 380 | file=fp) |
| 381 | path = self.test_logfile(test.name, prog_kind, self.testdir) |
| 382 | if os.path.exists(path): |
| 383 | with logfile_open(path, 'r') as prog_fp: |
| 384 | print(prog_fp.read(), file=fp) |
| 385 | filename = title.replace(' ', '_') + '.log' |
| 386 | with logfile_open(os.path.join(self.logdir, filename), 'w+') as fp: |
| 387 | for test in map(self._tests.__getitem__, indexes): |
| 388 | fp.write('TEST: [%s]\n' % test.name) |
| 389 | add_prog_log(fp, test, test.server.kind) |
| 390 | add_prog_log(fp, test, test.client.kind) |
| 391 | fp.write('**********************************************************************\n\n') |
| 392 | print('%s are logged to %s/%s/%s' % (title.capitalize(), self._testdir_rel, LOG_DIR, filename)) |
| 393 | |
| 394 | def end(self): |
| 395 | self._print_footer() |
| 396 | return len(self._unexpected_failure) == 0 |
| 397 | |
| 398 | def add_test(self, test_dict): |
| 399 | test = TestEntry(self.testdir, **test_dict) |
| 400 | self._lock.acquire() |
| 401 | try: |
| 402 | if not self.concurrent: |
| 403 | self.out.write(self._format_test(test, False)) |
| 404 | self.out.flush() |
| 405 | self._tests.append(test) |
| 406 | return len(self._tests) - 1 |
| 407 | finally: |
| 408 | self._lock.release() |
| 409 | |
| 410 | def add_result(self, index, returncode, expired, retry_count): |
| 411 | self._lock.acquire() |
| 412 | try: |
| 413 | failed = returncode is None or returncode != 0 |
| 414 | flaky = not failed and retry_count != 0 |
| 415 | test = self._tests[index] |
| 416 | known = test.name in self._known_failures |
| 417 | if failed: |
| 418 | if known: |
| 419 | self._log.debug('%s failed as expected' % test.name) |
| 420 | self._expected_failure.append(index) |
| 421 | else: |
| 422 | self._log.info('unexpected failure: %s' % test.name) |
| 423 | self._unexpected_failure.append(index) |
| 424 | elif flaky and not known: |
| 425 | self._log.info('unexpected flaky success: %s' % test.name) |
| 426 | self._flaky_success.append(index) |
| 427 | elif not flaky and known: |
| 428 | self._log.info('unexpected success: %s' % test.name) |
| 429 | self._unexpected_success.append(index) |
| 430 | test.success = not failed |
| 431 | test.returncode = returncode |
| 432 | test.retry_count = retry_count |
| 433 | test.expired = expired |
| 434 | test.as_expected = known == failed |
| 435 | if not self.concurrent: |
| 436 | self.out.write(self._result_string(test) + '\n') |
| 437 | else: |
| 438 | self.out.write(self._format_test(test)) |
| 439 | finally: |
| 440 | self._lock.release() |