diff options
Diffstat (limited to 'js/src/tests/lib/results.py')
-rw-r--r-- | js/src/tests/lib/results.py | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/js/src/tests/lib/results.py b/js/src/tests/lib/results.py new file mode 100644 index 000000000..451fe5b65 --- /dev/null +++ b/js/src/tests/lib/results.py @@ -0,0 +1,278 @@ +from __future__ import print_function + +import re +from progressbar import NullProgressBar, ProgressBar +import pipes + +# subprocess.list2cmdline does not properly escape for sh-like shells +def escape_cmdline(args): + return ' '.join([pipes.quote(a) for a in args]) + +class TestOutput: + """Output from a test run.""" + def __init__(self, test, cmd, out, err, rc, dt, timed_out): + self.test = test # Test + self.cmd = cmd # str: command line of test + self.out = out # str: stdout + self.err = err # str: stderr + self.rc = rc # int: return code + self.dt = dt # float: run time + self.timed_out = timed_out # bool: did the test time out + + def describe_failure(self): + if self.timed_out: + return "Timeout" + lines = self.err.splitlines() + for line in lines: + # Skip the asm.js compilation success message. + if "Successfully compiled asm.js code" not in line: + return line + return "Unknown" + +class NullTestOutput: + """Variant of TestOutput that indicates a test was not run.""" + def __init__(self, test): + self.test = test + self.cmd = '' + self.out = '' + self.err = '' + self.rc = 0 + self.dt = 0.0 + self.timed_out = False + +class TestResult: + PASS = 'PASS' + FAIL = 'FAIL' + CRASH = 'CRASH' + + """Classified result from a test run.""" + def __init__(self, test, result, results): + self.test = test + self.result = result + self.results = results + + @classmethod + def from_output(cls, output): + test = output.test + result = None # str: overall result, see class-level variables + results = [] # (str,str) list: subtest results (pass/fail, message) + + out, rc = output.out, output.rc + + failures = 0 + passes = 0 + + expected_rcs = [] + if test.path.endswith('-n.js'): + expected_rcs.append(3) + + for line in out.split('\n'): + if line.startswith(' FAILED!'): + failures += 1 + msg = line[len(' FAILED! '):] + results.append((cls.FAIL, msg)) + elif line.startswith(' PASSED!'): + passes += 1 + msg = line[len(' PASSED! '):] + results.append((cls.PASS, msg)) + else: + m = re.match('--- NOTE: IN THIS TESTCASE, WE EXPECT EXIT CODE' + ' ((?:-|\\d)+) ---', line) + if m: + expected_rcs.append(int(m.group(1))) + + if rc and not rc in expected_rcs: + if rc == 3: + result = cls.FAIL + else: + result = cls.CRASH + else: + if (rc or passes > 0) and failures == 0: + result = cls.PASS + else: + result = cls.FAIL + + return cls(test, result, results) + +class ResultsSink: + def __init__(self, options, testcount): + self.options = options + self.fp = options.output_fp + + self.groups = {} + self.output_dict = {} + self.counts = {'PASS': 0, 'FAIL': 0, 'TIMEOUT': 0, 'SKIP': 0} + self.n = 0 + + if options.hide_progress: + self.pb = NullProgressBar() + else: + fmt = [ + {'value': 'PASS', 'color': 'green'}, + {'value': 'FAIL', 'color': 'red'}, + {'value': 'TIMEOUT', 'color': 'blue'}, + {'value': 'SKIP', 'color': 'brightgray'}, + ] + self.pb = ProgressBar(testcount, fmt) + + def push(self, output): + if output.timed_out: + self.counts['TIMEOUT'] += 1 + if isinstance(output, NullTestOutput): + if self.options.format == 'automation': + self.print_automation_result( + 'TEST-KNOWN-FAIL', output.test, time=output.dt, + skip=True) + self.counts['SKIP'] += 1 + self.n += 1 + else: + result = TestResult.from_output(output) + tup = (result.result, result.test.expect, result.test.random) + dev_label = self.LABELS[tup][1] + + if self.options.check_output: + if output.test.path in self.output_dict.keys(): + if self.output_dict[output.test.path] != output: + self.counts['FAIL'] += 1 + self.print_automation_result( + "TEST-UNEXPECTED-FAIL", result.test, time=output.dt, + message="Same test with different flag producing different output") + else: + self.output_dict[output.test.path] = output + + if output.timed_out: + dev_label = 'TIMEOUTS' + self.groups.setdefault(dev_label, []).append(result) + + if dev_label == 'REGRESSIONS': + show_output = self.options.show_output \ + or not self.options.no_show_failed + elif dev_label == 'TIMEOUTS': + show_output = self.options.show_output + else: + show_output = self.options.show_output \ + and not self.options.failed_only + + if dev_label in ('REGRESSIONS', 'TIMEOUTS'): + show_cmd = self.options.show_cmd + else: + show_cmd = self.options.show_cmd \ + and not self.options.failed_only + + if show_output or show_cmd: + self.pb.beginline() + + if show_output: + print('## {}: rc = {:d}, run time = {}'.format( + output.test.path, output.rc, output.dt), file=self.fp) + + if show_cmd: + print(escape_cmdline(output.cmd), file=self.fp) + + if show_output: + self.fp.write(output.out) + self.fp.write(output.err) + + self.n += 1 + + if result.result == TestResult.PASS and not result.test.random: + self.counts['PASS'] += 1 + elif result.test.expect and not result.test.random: + self.counts['FAIL'] += 1 + else: + self.counts['SKIP'] += 1 + + if self.options.format == 'automation': + if result.result != TestResult.PASS and len(result.results) > 1: + for sub_ok, msg in result.results: + tup = (sub_ok, result.test.expect, result.test.random) + label = self.LABELS[tup][0] + if label == 'TEST-UNEXPECTED-PASS': + label = 'TEST-PASS (EXPECTED RANDOM)' + self.print_automation_result( + label, result.test, time=output.dt, + message=msg) + tup = (result.result, result.test.expect, result.test.random) + self.print_automation_result( + self.LABELS[tup][0], result.test, time=output.dt) + return + + if dev_label: + def singular(label): + return "FIXED" if label == "FIXES" else label[:-1] + self.pb.message("{} - {}".format(singular(dev_label), + output.test.path)) + + self.pb.update(self.n, self.counts) + + def finish(self, completed): + self.pb.finish(completed) + if not self.options.format == 'automation': + self.list(completed) + + # Conceptually, this maps (test result x test expection) to text labels. + # key is (result, expect, random) + # value is (automation label, dev test category) + LABELS = { + (TestResult.CRASH, False, False): ('TEST-UNEXPECTED-FAIL', 'REGRESSIONS'), + (TestResult.CRASH, False, True): ('TEST-UNEXPECTED-FAIL', 'REGRESSIONS'), + (TestResult.CRASH, True, False): ('TEST-UNEXPECTED-FAIL', 'REGRESSIONS'), + (TestResult.CRASH, True, True): ('TEST-UNEXPECTED-FAIL', 'REGRESSIONS'), + + (TestResult.FAIL, False, False): ('TEST-KNOWN-FAIL', ''), + (TestResult.FAIL, False, True): ('TEST-KNOWN-FAIL (EXPECTED RANDOM)', ''), + (TestResult.FAIL, True, False): ('TEST-UNEXPECTED-FAIL', 'REGRESSIONS'), + (TestResult.FAIL, True, True): ('TEST-KNOWN-FAIL (EXPECTED RANDOM)', ''), + + (TestResult.PASS, False, False): ('TEST-UNEXPECTED-PASS', 'FIXES'), + (TestResult.PASS, False, True): ('TEST-PASS (EXPECTED RANDOM)', ''), + (TestResult.PASS, True, False): ('TEST-PASS', ''), + (TestResult.PASS, True, True): ('TEST-PASS (EXPECTED RANDOM)', ''), + } + + def list(self, completed): + for label, results in sorted(self.groups.items()): + if label == '': + continue + + print(label) + for result in results: + print(' {}'.format(' '.join(result.test.jitflags + + [result.test.path]))) + + if self.options.failure_file: + failure_file = open(self.options.failure_file, 'w') + if not self.all_passed(): + if 'REGRESSIONS' in self.groups: + for result in self.groups['REGRESSIONS']: + print(result.test.path, file=failure_file) + if 'TIMEOUTS' in self.groups: + for result in self.groups['TIMEOUTS']: + print(result.test.path, file=failure_file) + failure_file.close() + + suffix = '' if completed else ' (partial run -- interrupted by user)' + if self.all_passed(): + print('PASS' + suffix) + else: + print('FAIL' + suffix) + + def all_passed(self): + return 'REGRESSIONS' not in self.groups and 'TIMEOUTS' not in self.groups + + def print_automation_result(self, label, test, message=None, skip=False, + time=None): + result = label + result += " | " + test.path + args = [] + if self.options.shell_args: + args.append(self.options.shell_args) + args += test.jitflags + result += ' | (args: "{}")'.format(' '.join(args)) + if message: + result += " | " + message + if skip: + result += ' | (SKIP)' + if time > self.options.timeout: + result += ' | (TIMEOUT)' + print(result) |