32
32
import md5
33
33
import json
34
34
import yaml
35
+ import xml .etree .ElementTree as ET
35
36
36
37
#
37
38
# Platform detection
@@ -160,6 +161,8 @@ def blue(text):
160
161
return ansi_color (text , '0;37;44' )
161
162
def yellow (text ):
162
163
return ansi_color (text , '0;33;40' )
164
+ def grey (text ):
165
+ return ansi_color (text , '6;37;40' )
163
166
164
167
# Parse lines. Split a text input into lines using LF as the line separator.
165
168
# Assume last line is terminated with a LF and ignore an "empty line" that
@@ -200,6 +203,72 @@ def clipline(x):
200
203
def remove_cr (data ):
201
204
return data .replace ('\r ' , '' )
202
205
206
+ #
207
+ # Valgrind result processing
208
+ #
209
+
210
+ def parse_massif_result (f , res ):
211
+ # Allocated bytes.
212
+ re_heap_b = re .compile (r'^mem_heap_B=(\d+)$' )
213
+
214
+ # Allocation overhead. Matters for desktop environments, for efficient
215
+ # zero overhead pool allocators this is not usually a concern (the waste
216
+ # in a pool allocator behaves very differently than a libc allocator).
217
+ re_heap_extra_b = re .compile (r'^mem_heap_extra_B=(\d+)$' )
218
+
219
+ # Stacks.
220
+ re_stacks_b = re .compile (r'^mem_stacks_B=(\d+)$' )
221
+
222
+ peak_heap = 0
223
+ peak_stack = 0
224
+
225
+ for line in f :
226
+ line = line .strip ()
227
+ #print(line)
228
+ m1 = re_heap_b .match (line )
229
+ m2 = re_heap_extra_b .match (line )
230
+ m3 = re_stacks_b .match (line )
231
+
232
+ heap = None
233
+ if m1 is not None :
234
+ heap = int (m1 .group (1 ))
235
+ stack = None
236
+ if m3 is not None :
237
+ stack = int (m3 .group (1 ))
238
+
239
+ if heap is not None :
240
+ peak_heap = max (peak_heap , heap )
241
+ if stack is not None :
242
+ peak_stack = max (peak_stack , stack )
243
+
244
+ res ['massif_peak_heap_bytes' ] = peak_heap
245
+ res ['massif_peak_stack_bytes' ] = peak_stack
246
+
247
+ def parse_memcheck_result (f , res ):
248
+ try :
249
+ tree = ET .parse (f )
250
+ except ET .ParseError :
251
+ res ['errors' ].append ('memcheck-parse-failed' )
252
+ return
253
+ root = tree .getroot ()
254
+ if root .tag != 'valgrindoutput' :
255
+ raise Exception ('invalid valgrind xml format' )
256
+
257
+ def parse_error (node ):
258
+ err = {}
259
+ for child in node .findall ('kind' ):
260
+ err ['kind' ] = child .text
261
+ for child in node .findall ('xwhat' ):
262
+ for child2 in child .findall ('text' ):
263
+ err ['text' ] = child2 .text
264
+
265
+ res ['errors' ].append (err ['kind' ])
266
+ # XXX: make res['errors'] structured rather than text list?
267
+ # 'err' is now ignored.
268
+
269
+ for child in root .findall ('error' ):
270
+ parse_error (child )
271
+
203
272
#
204
273
# Test execution and result interpretation helpers.
205
274
#
@@ -384,13 +453,13 @@ def parse_known_issue(data):
384
453
def find_known_issues ():
385
454
for dirname in [
386
455
os .path .join (os .path .dirname (testcase_filename ), '..' , 'knownissues' ),
387
- os .path .join (os . path . dirname ( script_path ) , '..' , 'tests' , 'knownissues' ),
388
- os .path .join (os . path . dirname ( entry_cwd ) , 'tests' , 'knownissues' )
456
+ os .path .join (script_path , '..' , 'tests' , 'knownissues' ),
457
+ os .path .join (entry_cwd , 'tests' , 'knownissues' )
389
458
]:
390
459
#print('Find known issues, try: %r' % dirname)
391
460
if os .path .isdir (dirname ):
392
461
return dirname
393
- raise Exception ('failed to locate testcases ' )
462
+ raise Exception ('failed to locate known issues ' )
394
463
395
464
# Check known issues against output data.
396
465
def check_known_issues (dirname , output ):
@@ -423,20 +492,35 @@ def execute_ecmascript_testcase(res, data, name):
423
492
test_fn = os .path .abspath (os .path .join (tempdir , name ))
424
493
write_file (test_fn , data )
425
494
495
+ valgrind_output = None
496
+
426
497
cmd = []
427
498
try :
428
499
start_time = time .time ()
429
500
try :
430
501
if opts .valgrind :
431
502
res ['valgrind' ] = True
432
- cmd += [ 'valgrind' , path_to_platform (opts .duk ) ]
503
+ res ['valgrind_tool' ] = opts .valgrind_tool
504
+ cmd += [ 'valgrind' ]
505
+ cmd += [ '--tool=' + opts .valgrind_tool ]
506
+
507
+ valgrind_output = os .path .abspath (os .path .join (tempdir , 'valgrind.out' ))
508
+ if opts .valgrind_tool == 'massif' :
509
+ cmd += [ '--massif-out-file=' + path_to_platform (valgrind_output ) ]
510
+ #cmd += [ '--peak-inaccuracy=0.0' ]
511
+ #cmd += [ '--stacks=yes' ]
512
+ elif opts .valgrind_tool == 'memcheck' :
513
+ cmd += [ '--xml=yes' , '--xml-file=' + path_to_platform (valgrind_output ) ]
514
+ else :
515
+ raise Exception ('invalid valgrind tool %r' % opts .valgrind_tool )
516
+ cmd += [ path_to_platform (os .path .abspath (opts .duk )) ]
433
517
else :
434
- cmd += [ opts .duk ]
435
- cmd += [ path_to_platform (test_fn ) ]
518
+ cmd += [ os . path . abspath ( opts .duk ) ]
519
+ cmd += [ path_to_platform (os . path . abspath ( test_fn ) ) ]
436
520
res ['command' ] = cmd
437
521
438
522
#print('Executing: %r' % cmd)
439
- proc = subprocess .Popen (cmd , stdin = None , stdout = subprocess .PIPE , stderr = subprocess .PIPE )
523
+ proc = subprocess .Popen (cmd , stdin = None , stdout = subprocess .PIPE , stderr = subprocess .PIPE , cwd = os . path . abspath ( tempdir ) )
440
524
441
525
timeout_sec = opts .timeout
442
526
def kill_proc (p ):
@@ -458,6 +542,16 @@ def kill_proc(p):
458
542
if opts .valgrind :
459
543
res ['valgrind_output' ] = ret [1 ]
460
544
res ['stderr' ] = '' # otherwise interpreted as an error
545
+ if valgrind_output is not None and os .path .exists (valgrind_output ):
546
+ with open (valgrind_output , 'rb' ) as f :
547
+ res ['valgrind_output' ] += f .read ()
548
+ with open (valgrind_output , 'rb' ) as f :
549
+ if opts .valgrind_tool == 'massif' :
550
+ parse_massif_result (f , res )
551
+ elif opts .valgrind_tool == 'memcheck' :
552
+ parse_memcheck_result (f , res )
553
+ else :
554
+ res ['errors' ].append ('no-valgrind-output' )
461
555
except :
462
556
exc_type , exc_value , exc_traceback = sys .exc_info ()
463
557
print ('Command execution failed for %r:\n %s' % (cmd , traceback .format_exc (exc_traceback )))
@@ -477,7 +571,7 @@ def execute_api_testcase(data):
477
571
def interpret_test_result (doc , expect ):
478
572
meta = doc ['metadata' ]
479
573
480
- errors = [ ]
574
+ errors = doc [ 'errors' ]
481
575
482
576
known_meta = check_known_issues (opts .known_issues , doc ['stdout' ])
483
577
@@ -506,6 +600,8 @@ def interpret_test_result(doc, expect):
506
600
success = False
507
601
doc ['knownissue' ] = known_meta .get ('summary' , 'no summary' )
508
602
doc ['knownissue_meta' ] = known_meta
603
+ if len (errors ) > 0 :
604
+ success = False
509
605
510
606
doc ['success' ] = success
511
607
doc ['errors' ] = errors
@@ -519,41 +615,70 @@ def interpret_test_result(doc, expect):
519
615
def print_summary (doc ):
520
616
meta = doc ['metadata' ]
521
617
522
- parts = []
523
- test_time = '[%.1f sec]' % float (doc ['duration' ])
618
+ def fmt_time (x ):
619
+ if x >= 60 :
620
+ return '%.1f m' % (float (x ) / 60.0 )
621
+ else :
622
+ return '%.1f s' % float (x )
623
+
624
+ def fmt_size (x ):
625
+ if x < 1024 * 1024 :
626
+ return '%.2f k' % (float (x ) / 1024.0 )
627
+ else :
628
+ return '%.2f M' % (float (x ) / (1024.0 * 1024.0 ))
524
629
630
+ parts = []
631
+ issues = []
632
+ test_result = '???'
633
+ test_name = doc ['testcase_name' ].ljust (50 )
525
634
print_diff = True # print diff if it is nonzero
526
635
636
+ test_time = fmt_time (doc ['duration' ])
637
+ test_time = '[%s]' % (test_time .rjust (6 ))
638
+
527
639
if doc ['skipped' ]:
528
- parts += [ 'SKIPPED' , doc [ 'testcase_name' ] ]
640
+ test_result = 'SKIPPED'
529
641
elif doc ['success' ]:
530
642
if doc ['timeout' ]:
531
- parts += [ red ('TIMEOUT' ), doc [ 'testcase_name' ] ]
643
+ test_result = red ('TIMEOUT' )
532
644
else :
533
- parts += [ green ('SUCCESS' ), doc [ 'testcase_name' ] ]
645
+ test_result = green ('SUCCESS' )
534
646
else :
535
647
if doc ['timeout' ]:
536
- parts += [ red ('TIMEOUT' ), doc [ 'testcase_name' ] ]
648
+ test_result = red ('TIMEOUT' )
537
649
elif doc ['knownissue' ] != '' :
538
- parts += [ blue ('KNOWN ' ), doc [ 'testcase_name' ] ]
650
+ test_result = blue ('KNOWN ' )
539
651
print_diff = False
540
652
else :
541
- parts += [ red ('FAILURE' ), doc [ 'testcase_name' ] ]
653
+ test_result = red ('FAILURE' )
542
654
543
- parts += [ '[%d diff lines]' % count_lines (doc ['diff_expect' ]) ]
655
+ issues += [ '[%d diff lines]' % count_lines (doc ['diff_expect' ]) ]
544
656
if doc ['knownissue' ] != '' :
545
- parts += [ '[known: ' + doc ['knownissue' ] + ']' ]
657
+ issues += [ '[known: ' + doc ['knownissue' ] + ']' ]
546
658
547
659
if len (doc ['errors' ]) > 0 :
548
- parts += [ '[errors: ' + ',' .join (doc ['errors' ]) + ']' ]
660
+ issues += [ '[errors: ' + ',' .join (doc ['errors' ]) + ']' ]
661
+
662
+ parts += [ test_result , test_name ]
549
663
550
- if doc ['duration' ] >= 30 .0 :
664
+ if doc ['duration' ] >= 60 .0 :
551
665
parts += [ blue (test_time ) ]
552
666
elif doc ['duration' ] >= 5.0 :
553
667
parts += [ yellow (test_time ) ]
554
668
else :
555
669
parts += [ test_time ]
556
670
671
+ if doc .has_key ('massif_peak_heap_bytes' ):
672
+ tmp = []
673
+ tmp += [ '%s heap' % fmt_size (doc ['massif_peak_heap_bytes' ]) ]
674
+ #tmp += [ '%s stack' % fmt_size(doc['massif_peak_stack_bytes']) ]
675
+ parts += [ '[%s]' % (', ' .join (tmp ).rjust (14 )) ]
676
+
677
+ if doc .has_key ('valgrind_tool' ):
678
+ parts += [ grey ('[%s]' % doc ['valgrind_tool' ]) ]
679
+
680
+ parts += issues
681
+
557
682
print (' ' .join (parts ))
558
683
559
684
if doc ['stderr' ] != '' and not meta .get ('intended_uncaught' , False ):
@@ -604,6 +729,9 @@ def main():
604
729
parser .add_option ('--duk' , dest = 'duk' , default = None , help = 'Path to "duk" command, default is autodetect' )
605
730
parser .add_option ('--timeout' , dest = 'timeout' , type = 'int' , default = 15 * 60 , help = 'Test execution timeout (seconds), default 15min' )
606
731
parser .add_option ('--valgrind' , dest = 'valgrind' , action = 'store_true' , default = False , help = 'Run test inside valgrind' )
732
+ parser .add_option ('--valgrind-tool' , dest = 'valgrind_tool' , default = None , help = 'Valgrind tool to use (implies --valgrind)' )
733
+ parser .add_option ('--memcheck' , dest = 'memcheck' , default = False , action = 'store_true' , help = 'Shorthand for --valgrind-tool memcheck' )
734
+ parser .add_option ('--massif' , dest = 'massif' , default = False , action = 'store_true' , help = 'Shorthand for --valgrind-tool massif' )
607
735
parser .add_option ('--prepare-only' , dest = 'prepare_only' , action = 'store_true' , default = False , help = 'Only prepare a testcase without running it' )
608
736
parser .add_option ('--clip-lines' , dest = 'clip_lines' , type = 'int' , default = 10 , help = 'Number of lines for stderr/diff summaries' )
609
737
parser .add_option ('--clip-columns' , dest = 'clip_columns' , type = 'int' , default = 80 , help = 'Number of columns for stderr/diff summaries' )
@@ -623,6 +751,16 @@ def main():
623
751
if opts .known_issues is None :
624
752
opts .known_issues = find_known_issues ()
625
753
#print('Autodetect known issues directory: %r' % opts.known_issues)
754
+ if opts .memcheck :
755
+ opts .valgrind = True
756
+ opts .valgrind_tool = 'memcheck'
757
+ if opts .massif :
758
+ opts .valgrind = True
759
+ opts .valgrind_tool = 'massif'
760
+ if opts .valgrind_tool is not None :
761
+ opts .valgrind = True
762
+ if opts .valgrind and opts .valgrind_tool is None :
763
+ opts .valgrind_tool = 'memcheck'
626
764
627
765
# Create a temporary directory for anything test related, automatic
628
766
# atexit deletion. Plumbed through globals to minimize argument passing.
0 commit comments