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 ):
@@ -178,7 +186,12 @@ def scp(results_file_path, destination_path, destination_name=None):
178
186
179
187
def _run_unittest (test_path , timeout , with_cpython = False ):
180
188
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" ]
182
195
else :
183
196
cmd = ["mx" , "python3" , "--python.CatchAllExceptions=true" , test_path , "-v" ]
184
197
_ , output , msg = _run_cmd (cmd , timeout )
@@ -200,16 +213,16 @@ def run_unittests(unittests, timeout, with_cpython=False):
200
213
201
214
start_time = time .monotonic ()
202
215
pool = Pool (processes = (os .cpu_count () // 4 ) or 1 ) # to account for hyperthreading and some additional overhead
203
-
216
+
204
217
out = []
205
218
def callback (result ):
206
219
out .append (result )
207
220
log ("[PROGRESS] {} / {}: \t {:.1f}%" , len (out ), num_unittests , len (out ) * 100 / num_unittests )
208
-
221
+
209
222
# schedule all unittest runs
210
223
for ut in unittests :
211
224
pool .apply_async (_run_unittest , args = (ut , timeout , with_cpython ), callback = callback )
212
-
225
+
213
226
pool .close ()
214
227
pool .join ()
215
228
pool .terminate ()
@@ -336,6 +349,7 @@ def process_output(output_lines):
336
349
if isinstance (output_lines , str ):
337
350
output_lines = output_lines .split ("\n " )
338
351
352
+ current_unittest_path = None
339
353
unittests = []
340
354
# stats tracked per unittest
341
355
unittest_tests = defaultdict (list )
@@ -346,6 +360,7 @@ def process_output(output_lines):
346
360
for line in output_lines :
347
361
match = re .match (PTRN_UNITTEST , line )
348
362
if match :
363
+ current_unittest_path = match .group ('unittest_path' )
349
364
unittest = match .group ('unittest' )
350
365
unittests .append (unittest )
351
366
unittest_tests .clear ()
@@ -415,6 +430,55 @@ def process_output(output_lines):
415
430
stats [unittests [- 1 ]].num_skipped = int (skipped ) if skipped else 0
416
431
continue
417
432
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
+
418
482
return unittests , error_messages , java_exceptions , stats
419
483
420
484
@@ -811,7 +875,7 @@ def format_val(row, k):
811
875
'<b>{}</b>' .format (r [Col .UNITTEST ])
812
876
for r in rows if r [Col .NUM_ERRORS ] == - 1
813
877
])
814
-
878
+
815
879
usecase_scores = dict ()
816
880
for usecase_name , usecase_modules in USE_CASE_GROUPS .items ():
817
881
score_sum = 0
@@ -821,9 +885,9 @@ def format_val(row, k):
821
885
if r [Col .NUM_PASSES ] > 0 and r [Col .NUM_TESTS ] > 0 :
822
886
score_sum += r [Col .NUM_PASSES ] / r [Col .NUM_TESTS ]
823
887
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>" ,
827
891
[ grid ((progress_bar (avg_score * 100 , color = "info" ), 3 ), '<b>{}</b>' .format (usecase_name )) +
828
892
grid (", " .join (USE_CASE_GROUPS [usecase_name ])) for usecase_name , avg_score in usecase_scores .items ()])
829
893
0 commit comments