Skip to content

Commit d41de62

Browse files
committed
[GR-25601] When we hit a timeout, consider the data we have and also the tags
PullRequest: graalpython/1194
2 parents 2a8f6bb + 3c46074 commit d41de62

File tree

1 file changed

+76
-12
lines changed

1 file changed

+76
-12
lines changed

graalpython/com.oracle.graal.python.test/src/python_unittests.py

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
from multiprocessing import Pool, TimeoutError
5151
from pprint import pformat
5252

53-
53+
5454
import argparse
5555
import sys
5656
from time import gmtime, strftime
@@ -67,10 +67,12 @@
6767
HTML_RESULTS_NAME = "{}.html".format(_BASE_NAME)
6868
LATEST_HTML_NAME = "latest.html"
6969

70+
TIMEOUT_LINE = "\nTEST TIMED OUT WITH GRAAL PYTHON RUNNER"
71+
7072
HR = "".join(['-' for _ in range(120)])
7173

7274
PTRN_ERROR = re.compile(r'^(?P<error>[A-Z][a-z][a-zA-Z]+):(?P<message>.*)$')
73-
PTRN_UNITTEST = re.compile(r'^#### running: graalpython/lib-python/3/test/(?P<unittest>[\w.]+).*$', re.DOTALL)
75+
PTRN_UNITTEST = re.compile(r'^#### running: (?P<unittest_path>graalpython/lib-python/3/test/(?P<unittest>[\w.]+)).*$', re.DOTALL)
7476
PTRN_NUM_TESTS = re.compile(r'^Ran (?P<num_tests>\d+) test.*$')
7577
PTRN_FAILED = re.compile(
7678
r'^FAILED \((failures=(?P<failures>\d+))?(, )?(errors=(?P<errors>\d+))?(, )?(skipped=(?P<skipped>\d+))?\)$')
@@ -149,6 +151,8 @@ def _run_cmd(cmd, timeout=TIMEOUT, capture_on_failure=True):
149151
cmd_string = ' '.join(cmd)
150152
log("[EXEC] starting '{}' ...".format(cmd_string))
151153

154+
expired = False
155+
152156
start_time = time.monotonic()
153157
# os.setsid is used to create a process group, to be able to call os.killpg upon timeout
154158
proc = subprocess.Popen(cmd, preexec_fn=os.setsid, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
@@ -161,12 +165,16 @@ def _run_cmd(cmd, timeout=TIMEOUT, capture_on_failure=True):
161165
msg = "TimeoutExpired: {:.3f}s".format(delta)
162166
tail = get_tail(output.decode('utf-8', 'ignore'))
163167
log("[ERR] timeout '{}' after {:.3f}s, killing process group {}, last lines of output:\n{}\n{}", cmd_string, delta, proc.pid, tail, HR)
168+
expired = True
164169
else:
165170
delta = time.monotonic() - start_time
166171
log("[EXEC] finished '{}' with exit code {} in {:.3f}s", cmd_string, proc.returncode, delta)
167172
msg = "Finished: {:.3f}s".format(delta)
168-
169-
return proc.returncode == 0, output.decode("utf-8", "ignore"), msg
173+
174+
output = output.decode("utf-8", "ignore")
175+
if expired:
176+
output += TIMEOUT_LINE
177+
return proc.returncode == 0, output, msg
170178

171179

172180
def scp(results_file_path, destination_path, destination_name=None):
@@ -178,7 +186,12 @@ def scp(results_file_path, destination_path, destination_name=None):
178186

179187
def _run_unittest(test_path, timeout, with_cpython=False):
180188
if with_cpython:
181-
cmd = ["python3", test_path, "-v"]
189+
exe = os.environ.get("PYTHON3_HOME", None)
190+
if exe:
191+
exe = os.path.join(exe, "python")
192+
else:
193+
exe = "python3"
194+
cmd = [exe, test_path, "-v"]
182195
else:
183196
cmd = ["mx", "python3", "--python.CatchAllExceptions=true", test_path, "-v"]
184197
_, output, msg = _run_cmd(cmd, timeout)
@@ -200,16 +213,16 @@ def run_unittests(unittests, timeout, with_cpython=False):
200213

201214
start_time = time.monotonic()
202215
pool = Pool(processes=(os.cpu_count() // 4) or 1) # to account for hyperthreading and some additional overhead
203-
216+
204217
out = []
205218
def callback(result):
206219
out.append(result)
207220
log("[PROGRESS] {} / {}: \t {:.1f}%", len(out), num_unittests, len(out) * 100 / num_unittests)
208-
221+
209222
# schedule all unittest runs
210223
for ut in unittests:
211224
pool.apply_async(_run_unittest, args=(ut, timeout, with_cpython), callback=callback)
212-
225+
213226
pool.close()
214227
pool.join()
215228
pool.terminate()
@@ -336,6 +349,7 @@ def process_output(output_lines):
336349
if isinstance(output_lines, str):
337350
output_lines = output_lines.split("\n")
338351

352+
current_unittest_path = None
339353
unittests = []
340354
# stats tracked per unittest
341355
unittest_tests = defaultdict(list)
@@ -346,6 +360,7 @@ def process_output(output_lines):
346360
for line in output_lines:
347361
match = re.match(PTRN_UNITTEST, line)
348362
if match:
363+
current_unittest_path = match.group('unittest_path')
349364
unittest = match.group('unittest')
350365
unittests.append(unittest)
351366
unittest_tests.clear()
@@ -415,6 +430,55 @@ def process_output(output_lines):
415430
stats[unittests[-1]].num_skipped = int(skipped) if skipped else 0
416431
continue
417432

433+
if line.strip() == TIMEOUT_LINE.strip():
434+
if current_unittest_path is None or len(unittests) == 0:
435+
# we timed out here before even running something
436+
continue
437+
ran_tests = {}
438+
fails = 0
439+
errs = 0
440+
ok = 0
441+
skip = 0
442+
for test,status in unittest_tests.items():
443+
status = " ".join(status).lower()
444+
ran_tests[test.strip()] = status
445+
if "skipped" in status:
446+
skip += 1
447+
elif "fail" in status:
448+
fails += 1
449+
elif "ok" in status:
450+
ok += 1
451+
else:
452+
errs += 1
453+
454+
tagfile = ".".join([os.path.splitext(unittests[-1])[0], "txt"])
455+
prefix = os.path.splitext(current_unittest_path)[0].replace("/", ".")
456+
import glob
457+
candidates = glob.glob("**/" + tagfile, recursive=True)
458+
for candidate in candidates:
459+
with open(candidate) as f:
460+
for tagline in f.readlines():
461+
tagline = tagline.replace(prefix, "__main__") # account different runner for tagged and this
462+
tagline = tagline.replace("*", "").strip()
463+
tstcls, tst = tagline.rsplit(".", 1)
464+
test = "{} ({})".format(tst, tstcls)
465+
if test not in ran_tests:
466+
ran_tests[test] = "ok"
467+
# count the tagged test we didn't get to as an additional passing test
468+
ok += 1
469+
else:
470+
status = ran_tests[test]
471+
if "error" in status or "fail" in status:
472+
# interesting: it's tagged but failed here
473+
log("{} did not pass here but is tagged as passing", test)
474+
475+
stats[unittests[-1]].num_tests = ok + fails + errs + skip
476+
stats[unittests[-1]].num_fails = fails
477+
stats[unittests[-1]].num_errors = errs
478+
stats[unittests[-1]].num_skipped = skip
479+
unittest_tests.clear()
480+
continue
481+
418482
return unittests, error_messages, java_exceptions, stats
419483

420484

@@ -811,7 +875,7 @@ def format_val(row, k):
811875
'<b>{}</b>'.format(r[Col.UNITTEST])
812876
for r in rows if r[Col.NUM_ERRORS] == -1
813877
])
814-
878+
815879
usecase_scores = dict()
816880
for usecase_name, usecase_modules in USE_CASE_GROUPS.items():
817881
score_sum = 0
@@ -821,9 +885,9 @@ def format_val(row, k):
821885
if r[Col.NUM_PASSES] > 0 and r[Col.NUM_TESTS] > 0:
822886
score_sum += r[Col.NUM_PASSES] / r[Col.NUM_TESTS]
823887
usecase_scores[usecase_name] = score_sum / len(usecase_modules)
824-
825-
826-
use_case_stats_info = ul("<b>Summary per Use Case</b>",
888+
889+
890+
use_case_stats_info = ul("<b>Summary per Use Case</b>",
827891
[ grid((progress_bar(avg_score * 100, color="info"), 3), '<b>{}</b>'.format(usecase_name)) +
828892
grid(", ".join(USE_CASE_GROUPS[usecase_name])) for usecase_name, avg_score in usecase_scores.items()])
829893

0 commit comments

Comments
 (0)