-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathspector.py
executable file
·185 lines (153 loc) · 5.26 KB
/
spector.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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#!/usr/bin/env python3
import os
import sys
from itertools import chain
from functools import reduce
from collections import namedtuple
from subprocess import Popen, PIPE, DEVNULL
from datetime import datetime, timedelta
# Character Sequences
SYM_TICK = '\033[32m✓\033[0m'
SYM_INFO = '\033[33m?\033[0m'
SYM_FAIL = '\033[31m✗\033[0m'
# Type Definitions
TestCase = namedtuple('TestCase', 'options suite path src')
TestStatistics = namedtuple('TestStatistics', 'name status time errors')
def buildCompiler():
"""Build cargo before testing"""
# Spawn cargo build
proc = Popen(
["cargo", "build"],
stdout=DEVNULL,
stderr=PIPE,
close_fds=True
)
# Grab process output
(output, err) = proc.communicate()
# Wait for process to exit
exitCode = proc.wait()
# Check exit code
if exitCode != 0:
print("%s Compiler build failed:" % SYM_FAIL)
print(err)
exit(1)
print("%s Compiler build succeeded." % SYM_TICK)
def gatherTestCases():
"""Find test cases in spec/**/*"""
def getFileNames(tpl):
"""Build filenames from os.walk data"""
dirname, _, filenames = tpl
return [os.path.join(dirname, file) for file in filenames]
return sorted(chain(*map(getFileNames, os.walk("spec"))))
def parseTest(file):
"""Generate structured test cases from source files"""
# Read file
with open(file, "r") as f:
content = f.read()
def isSpectorComment(line):
"""Test whether a line contains a spector property"""
line = line.strip()
if not line.startswith('//'): return False
line = line.split('//', 1)[1].strip()
return True if line.startswith('spector:') else False
# Define property dictionary
props = dict()
# Extract properties from spector comments
for line in list(filter(isSpectorComment, content.splitlines())):
values = line.split('spector:', 1)[1].split(' ', 1)
if len(values) == 1:
props[values[0]] = ''
else:
key, val = values
props[key] = val
# Get relevant properties
name = props.get('name', os.path.basename(file))
shouldfail = 'shouldfail' in props
# Build final options
opts = {
'name': name,
'shouldfail': shouldfail,
}
# Determine test suite
suite = os.path.dirname(file).split('spec/', 1)[1]
# Build the structured test case
return TestCase(opts, suite, file, content)
def runTest(test, testCaseMaxLen):
"""Run a single test"""
# Try building the test case
proc = Popen(
["cargo", "run", "--quiet", "--", test.path],
stdout=PIPE,
stderr=PIPE
)
# Measure starting time
startTime = datetime.now()
# Wait for process to exit
(output, err) = proc.communicate()
exitCode = proc.wait()
# Measure exiting time
endTime = datetime.now()
# Get time difference
diff = endTime - startTime
diffMs = diff.seconds * 1000 + diff.microseconds / 1000
diffStr = "{}ms".format(str(int(diffMs))).ljust(6)
# Determine success
success = exitCode == 0 or (test.options['shouldfail'] and exitCode != 0)
# Print results
errors = None
if success:
print("{suite} {symbol} {time} {name}".format(
suite = test.suite.ljust(testCaseMaxLen, ' '),
symbol = SYM_TICK,
time = diffStr,
name = test.options['name']
))
else:
print("{suite} {symbol} {time} {name} (code {code})".format(
suite = test.suite.ljust(testCaseMaxLen, ' '),
symbol = SYM_FAIL,
time = diffStr,
name = test.options['name'],
code = exitCode
))
errors = err.decode('utf-8').replace("\\n", "\n")
errors = ''.join(filter(lambda line: 'panicked at' in line, errors.splitlines()))
print("{padding}\_ {symbol} {error}".format(
padding = ''.ljust(testCaseMaxLen - 2, ' '),
symbol = SYM_INFO,
error=errors
))
# print(str(output).replace("\\n", "\n"))
# Build test statistics
return TestStatistics(test.options['name'], success, diffMs, errors)
def runTestCases(tests, testCaseMaxLen):
"""Run tests and print a summary"""
print()
# Run all tests
results = [runTest(test, testCaseMaxLen) for test in tests]
# Print errors
# print("\nErrors:")
# for test in filter(lambda res: res.errors is not None, results):
# print("[%s]:\n%s" % (test.name, test.errors))
# pass
# Print summary
print("\nSummary:")
getPassed = lambda res: res.status
getFailed = lambda res: not res.status
elapsedMs = reduce(lambda acc, res: acc + res.time, results, 0)
print("Ran %d test%s in %dms (\033[32m%d\033[0m passed, \033[31m%d\033[0m failed)." % (
len(results),
"s" if len(results) != 1 else "",
elapsedMs,
len(list(filter(getPassed, results))),
len(list(filter(getFailed, results)))
))
if len(list(filter(getFailed, results))) > 0:
sys.exit(-1)
# Entry Point
if __name__ == "__main__":
buildCompiler()
files = gatherTestCases()
tests = [parseTest(file) for file in files]
maxlen = max([len(test.suite) for test in tests])
runTestCases(tests, maxlen)