-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathscheme_test.py
92 lines (80 loc) · 3.25 KB
/
scheme_test.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
"""Unit testing framework for the Scheme interpreter.
Usage: python3 scheme_test.py FILE
Interprets FILE as interactive Scheme source code, and compares each line
of printed output from the read-eval-print loop and from any output functions
to an expected output described in a comment. For example,
(display (+ 2 3))
; expect 5
Differences between printed and expected outputs are printed with line numbers.
"""
import io
import sys
from buffer import Buffer
from scheme import read_eval_print_loop, create_global_frame
from scheme_tokens import tokenize_lines
from ucb import main
def summarize(output, expected_output):
"""Summarize results of running tests."""
num_failed, num_expected = 0, len(expected_output)
def failed(expected, actual, line):
nonlocal num_failed
num_failed += 1
print('test failed at line', line)
print(' expected', expected)
print(' printed', actual)
for (actual, (expected, line_number)) in zip(output, expected_output):
if expected.startswith("Error"):
if not actual.startswith("Error"):
failed('an error indication', actual, line_number)
elif actual != expected:
failed(expected, actual, line_number)
print('{0} tested; {1} failed.'.format(num_expected, num_failed))
EXPECT_STRING = '; expect'
class TestReader:
"""A TestReader is an iterable that collects test case expected results."""
def __init__(self, lines, stdout):
self.lines = lines
self.stdout = stdout
self.last_out_len = 0
self.output = []
self.expected_output = []
self.line_number = 0
def __iter__(self):
for line in self.lines:
line = line.rstrip('\n')
self.line_number += 1
if line.lstrip().startswith(EXPECT_STRING):
expected = line.split(EXPECT_STRING, 1)[1][1:].split(' ; ')
for exp in expected:
self.expected_output.append((exp, self.line_number))
out_lines = self.stdout.getvalue().split('\n')
if len(out_lines) > self.last_out_len:
self.output.extend(out_lines[-1-len(expected):-1])
else:
self.output.extend([''] * len(expected))
self.last_out_len = len(out_lines)
yield line
raise EOFError
@main
def run_tests(src_file='tests.scm'):
"""Run a read-eval loop that reads from src_file and collects outputs."""
sys.stderr = sys.stdout = io.StringIO() # Collect output to stdout and stderr
reader = None
try:
reader = TestReader(open(src_file).readlines(), sys.stdout)
src = Buffer(tokenize_lines(reader))
def next_line():
src.current()
return src
read_eval_print_loop(next_line, create_global_frame())
except BaseException as exc:
sys.stderr = sys.__stderr__
if reader:
print("Tests terminated due to unhandled exception "
"after line {0}:\n>>>".format(reader.line_number),
file=sys.stderr)
raise
finally:
sys.stdout = sys.__stdout__ # Revert stdout
sys.stderr = sys.__stderr__ # Revert stderr
summarize(reader.output, reader.expected_output)