Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/tools/test_dcr.py
2723 views
1
#!/usr/bin/python3
2
# This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
3
4
import argparse
5
import os.path
6
import subprocess as sp
7
import sys
8
import xml.sax as x
9
10
try:
11
import colorama as c
12
except ImportError:
13
class c:
14
class Fore:
15
RED=''
16
RESET=''
17
GREEN=''
18
else:
19
c.init()
20
21
SCRIPT_PATH = os.path.split(sys.argv[0])[0]
22
FAIL_LIST_PATH = os.path.join(SCRIPT_PATH, "faillist.txt")
23
24
25
def loadFailList():
26
with open(FAIL_LIST_PATH) as f:
27
return set(map(str.strip, f.readlines()))
28
29
30
def safeParseInt(i, default=0):
31
try:
32
return int(i)
33
except ValueError:
34
return default
35
36
37
def makeDottedName(path):
38
return ".".join(path)
39
40
41
class Handler(x.ContentHandler):
42
def __init__(self, failList):
43
self.currentTest = []
44
self.failList = failList # Set of dotted test names that are expected to fail
45
46
self.results = {} # {DottedName: TrueIfTheTestPassed}
47
48
self.numSkippedTests = 0
49
50
self.pass_count = 0
51
self.fail_count = 0
52
self.test_count = 0
53
54
self.crashed_tests = []
55
56
def startElement(self, name, attrs):
57
if name == "TestSuite":
58
self.currentTest.append(attrs["name"])
59
elif name == "TestCase":
60
self.currentTest.append(attrs["name"])
61
62
elif name == "OverallResultsAsserts":
63
if self.currentTest:
64
passed = attrs["test_case_success"] == "true"
65
66
dottedName = makeDottedName(self.currentTest)
67
68
# Sometimes we get multiple XML trees for the same test. All of
69
# them must report a pass in order for us to consider the test
70
# to have passed.
71
r = self.results.get(dottedName, True)
72
self.results[dottedName] = r and passed
73
74
self.test_count += 1
75
if passed:
76
self.pass_count += 1
77
else:
78
self.fail_count += 1
79
80
elif name == "OverallResultsTestCases":
81
self.numSkippedTests = safeParseInt(attrs.get("skipped", 0))
82
83
elif name == "Exception":
84
if attrs.get("crash") == "true":
85
self.crashed_tests.append(makeDottedName(self.currentTest))
86
87
def endElement(self, name):
88
if name == "TestCase":
89
self.currentTest.pop()
90
91
elif name == "TestSuite":
92
self.currentTest.pop()
93
94
95
def print_stderr(*args, **kw):
96
print(*args, **kw, file=sys.stderr)
97
98
99
def main():
100
parser = argparse.ArgumentParser(
101
description="Run Luau.UnitTest with deferred constraint resolution enabled"
102
)
103
parser.add_argument(
104
"path", action="store", help="Path to the Luau.UnitTest executable"
105
)
106
parser.add_argument(
107
"--fflags",
108
dest="flags",
109
action="store",
110
help="Set extra FFlags",
111
)
112
parser.add_argument(
113
"--dump",
114
dest="dump",
115
action="store_true",
116
help="Instead of doing any processing, dump the raw output of the test run. Useful for debugging this tool.",
117
)
118
parser.add_argument(
119
"--write",
120
dest="write",
121
action="store_true",
122
help="Write a new faillist.txt after running tests.",
123
)
124
parser.add_argument(
125
"--ts",
126
dest="suite",
127
action="store",
128
help="Only run a specific suite."
129
)
130
131
parser.add_argument("--randomize", action="store_true", help="Pick a random seed")
132
133
parser.add_argument(
134
"--random-seed",
135
action="store",
136
dest="random_seed",
137
type=int,
138
help="Accept a specific RNG seed",
139
)
140
141
args = parser.parse_args()
142
143
failList = loadFailList()
144
145
flags = "true,LuauSolverV2"
146
if args.flags:
147
flags += "," + args.flags
148
149
commandLine = [args.path, "--reporters=xml", "--fflags=" + flags]
150
151
if args.random_seed:
152
commandLine.append("--random-seed=" + str(args.random_seed))
153
elif args.randomize:
154
commandLine.append("--randomize")
155
156
if args.suite:
157
commandLine.append(f'--ts={args.suite}')
158
159
print_stderr(">", " ".join(commandLine))
160
161
p = sp.Popen(
162
commandLine,
163
stdout=sp.PIPE,
164
)
165
166
assert p.stdout
167
168
handler = Handler(failList)
169
170
if args.dump:
171
for line in p.stdout:
172
sys.stdout.buffer.write(line)
173
return
174
else:
175
try:
176
x.parse(p.stdout, handler)
177
except x.SAXParseException as e:
178
print_stderr(
179
f"XML parsing failed during test {makeDottedName(handler.currentTest)}. That probably means that the test crashed"
180
)
181
sys.exit(1)
182
183
p.wait()
184
185
unexpected_fails = 0
186
unexpected_passes = 0
187
188
for testName, passed in handler.results.items():
189
if passed and testName in failList:
190
unexpected_passes += 1
191
print_stderr(
192
f"UNEXPECTED: {c.Fore.RED}{testName}{c.Fore.RESET} should have failed"
193
)
194
elif not passed and testName not in failList:
195
unexpected_fails += 1
196
print_stderr(
197
f"UNEXPECTED: {c.Fore.GREEN}{testName}{c.Fore.RESET} should have passed"
198
)
199
200
if unexpected_fails or unexpected_passes:
201
print_stderr("")
202
print_stderr(f"Unexpected fails: {unexpected_fails}")
203
print_stderr(f"Unexpected passes: {unexpected_passes}")
204
205
pass_percent = int(handler.pass_count / handler.test_count * 100)
206
207
print_stderr("")
208
print_stderr(
209
f"{handler.pass_count} of {handler.test_count} tests passed. ({pass_percent}%)"
210
)
211
print_stderr(f"{handler.fail_count} tests failed.")
212
213
if args.write:
214
newFailList = sorted(
215
(
216
dottedName
217
for dottedName, passed in handler.results.items()
218
if not passed
219
),
220
key=str.lower,
221
)
222
with open(FAIL_LIST_PATH, "w", newline="\n") as f:
223
for name in newFailList:
224
print(name, file=f)
225
print_stderr("Updated faillist.txt")
226
227
if handler.crashed_tests:
228
print_stderr()
229
for test in handler.crashed_tests:
230
print_stderr(
231
f"{c.Fore.RED}{test}{c.Fore.RESET} threw an exception and crashed the test process!"
232
)
233
234
if handler.numSkippedTests > 0:
235
print_stderr(f"{handler.numSkippedTests} test(s) were skipped!")
236
237
ok = (
238
not handler.crashed_tests
239
and handler.numSkippedTests == 0
240
and all(
241
not passed == (dottedName in failList)
242
for dottedName, passed in handler.results.items()
243
)
244
)
245
246
if ok:
247
print_stderr("Everything in order!")
248
249
sys.exit(0 if ok else 1)
250
251
252
if __name__ == "__main__":
253
main()
254
255