50
50
from multiprocessing import Pool , TimeoutError
51
51
from pprint import pformat
52
52
53
-
53
+
54
54
import argparse
55
55
import sys
56
56
from time import gmtime , strftime
67
67
HTML_RESULTS_NAME = "{}.html" .format (_BASE_NAME )
68
68
LATEST_HTML_NAME = "latest.html"
69
69
70
+ TIMEOUT_LINE = "\n TEST TIMED OUT WITH GRAAL PYTHON RUNNER"
71
+
70
72
HR = "" .join (['-' for _ in range (120 )])
71
73
72
74
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 )
74
76
PTRN_NUM_TESTS = re .compile (r'^Ran (?P<num_tests>\d+) test.*$' )
75
77
PTRN_FAILED = re .compile (
76
78
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):
149
151
cmd_string = ' ' .join (cmd )
150
152
log ("[EXEC] starting '{}' ..." .format (cmd_string ))
151
153
154
+ expired = False
155
+
152
156
start_time = time .monotonic ()
153
157
# os.setsid is used to create a process group, to be able to call os.killpg upon timeout
154
158
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):
161
165
msg = "TimeoutExpired: {:.3f}s" .format (delta )
162
166
tail = get_tail (output .decode ('utf-8' , 'ignore' ))
163
167
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
164
169
else :
165
170
delta = time .monotonic () - start_time
166
171
log ("[EXEC] finished '{}' with exit code {} in {:.3f}s" , cmd_string , proc .returncode , delta )
167
172
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
170
178
171
179
172
180
def scp (results_file_path , destination_path , destination_name = None ):
@@ -200,16 +208,16 @@ def run_unittests(unittests, timeout, with_cpython=False):
200
208
201
209
start_time = time .monotonic ()
202
210
pool = Pool (processes = (os .cpu_count () // 4 ) or 1 ) # to account for hyperthreading and some additional overhead
203
-
211
+
204
212
out = []
205
213
def callback (result ):
206
214
out .append (result )
207
215
log ("[PROGRESS] {} / {}: \t {:.1f}%" , len (out ), num_unittests , len (out ) * 100 / num_unittests )
208
-
216
+
209
217
# schedule all unittest runs
210
218
for ut in unittests :
211
219
pool .apply_async (_run_unittest , args = (ut , timeout , with_cpython ), callback = callback )
212
-
220
+
213
221
pool .close ()
214
222
pool .join ()
215
223
pool .terminate ()
@@ -336,6 +344,7 @@ def process_output(output_lines):
336
344
if isinstance (output_lines , str ):
337
345
output_lines = output_lines .split ("\n " )
338
346
347
+ current_unittest_path = None
339
348
unittests = []
340
349
# stats tracked per unittest
341
350
unittest_tests = defaultdict (list )
@@ -346,6 +355,7 @@ def process_output(output_lines):
346
355
for line in output_lines :
347
356
match = re .match (PTRN_UNITTEST , line )
348
357
if match :
358
+ current_unittest_path = match .group ('unittest_path' )
349
359
unittest = match .group ('unittest' )
350
360
unittests .append (unittest )
351
361
unittest_tests .clear ()
@@ -415,6 +425,55 @@ def process_output(output_lines):
415
425
stats [unittests [- 1 ]].num_skipped = int (skipped ) if skipped else 0
416
426
continue
417
427
428
+ if line .strip () == TIMEOUT_LINE .strip ():
429
+ if current_unittest_path is None or len (unittests ) == 0 :
430
+ # we timed out here before even running something
431
+ continue
432
+ ran_tests = {}
433
+ fails = 0
434
+ errs = 0
435
+ ok = 0
436
+ skip = 0
437
+ for test ,status in unittest_tests .items ():
438
+ status = " " .join (status ).lower ()
439
+ ran_tests [test .strip ()] = status
440
+ if "skipped" in status :
441
+ skip += 1
442
+ elif "fail" in status :
443
+ fails += 1
444
+ elif "ok" in status :
445
+ ok += 1
446
+ else :
447
+ errs += 1
448
+
449
+ tagfile = "." .join ([os .path .splitext (unittests [- 1 ])[0 ], "txt" ])
450
+ prefix = os .path .splitext (current_unittest_path )[0 ].replace ("/" , "." )
451
+ import glob
452
+ candidates = glob .glob ("**/" + tagfile , recursive = True )
453
+ for candidate in candidates :
454
+ with open (candidate ) as f :
455
+ for tagline in f .readlines ():
456
+ tagline = tagline .replace (prefix , "__main__" ) # account different runner for tagged and this
457
+ tagline = tagline .replace ("*" , "" ).strip ()
458
+ tstcls , tst = tagline .rsplit ("." , 1 )
459
+ test = "{} ({})" .format (tst , tstcls )
460
+ if test not in ran_tests :
461
+ ran_tests [test ] = "ok"
462
+ # count the tagged test we didn't get to as an additional passing test
463
+ ok += 1
464
+ else :
465
+ status = ran_tests [test ]
466
+ if "error" in status or "fail" in status :
467
+ # interesting: it's tagged but failed here
468
+ log ("{} did not pass here but is tagged as passing" , test )
469
+
470
+ stats [unittests [- 1 ]].num_tests = ok + fails + errs + skip
471
+ stats [unittests [- 1 ]].num_fails = fails
472
+ stats [unittests [- 1 ]].num_errors = errs
473
+ stats [unittests [- 1 ]].num_skipped = skip
474
+ unittest_tests .clear ()
475
+ continue
476
+
418
477
return unittests , error_messages , java_exceptions , stats
419
478
420
479
@@ -811,7 +870,7 @@ def format_val(row, k):
811
870
'<b>{}</b>' .format (r [Col .UNITTEST ])
812
871
for r in rows if r [Col .NUM_ERRORS ] == - 1
813
872
])
814
-
873
+
815
874
usecase_scores = dict ()
816
875
for usecase_name , usecase_modules in USE_CASE_GROUPS .items ():
817
876
score_sum = 0
@@ -821,9 +880,9 @@ def format_val(row, k):
821
880
if r [Col .NUM_PASSES ] > 0 and r [Col .NUM_TESTS ] > 0 :
822
881
score_sum += r [Col .NUM_PASSES ] / r [Col .NUM_TESTS ]
823
882
usecase_scores [usecase_name ] = score_sum / len (usecase_modules )
824
-
825
-
826
- use_case_stats_info = ul ("<b>Summary per Use Case</b>" ,
883
+
884
+
885
+ use_case_stats_info = ul ("<b>Summary per Use Case</b>" ,
827
886
[ grid ((progress_bar (avg_score * 100 , color = "info" ), 3 ), '<b>{}</b>' .format (usecase_name )) +
828
887
grid (", " .join (USE_CASE_GROUPS [usecase_name ])) for usecase_name , avg_score in usecase_scores .items ()])
829
888
0 commit comments