4
4
#
5
5
6
6
import collections as co
7
+ import csv
7
8
import errno
8
9
import glob
9
10
import itertools as it
26
27
27
28
def openio (path , mode = 'r' , buffering = - 1 , nb = False ):
28
29
if path == '-' :
29
- if 'r' in mode :
30
+ if mode == 'r' :
30
31
return os .fdopen (os .dup (sys .stdin .fileno ()), 'r' , buffering )
31
32
else :
32
33
return os .fdopen (os .dup (sys .stdout .fileno ()), 'w' , buffering )
@@ -475,9 +476,8 @@ def write_case_functions(f, suite, case):
475
476
f .writeln ('#endif' )
476
477
f .writeln ()
477
478
478
- def find_runner (runner , test_ids , ** args ):
479
+ def find_runner (runner , ** args ):
479
480
cmd = runner .copy ()
480
- cmd .extend (test_ids )
481
481
482
482
# run under some external command?
483
483
cmd [:0 ] = args .get ('exec' , [])
@@ -514,8 +514,8 @@ def find_runner(runner, test_ids, **args):
514
514
515
515
return cmd
516
516
517
- def list_ (runner , test_ids , ** args ):
518
- cmd = find_runner (runner , test_ids , ** args )
517
+ def list_ (runner , test_ids = [] , ** args ):
518
+ cmd = find_runner (runner , ** args ) + test_ids
519
519
if args .get ('summary' ): cmd .append ('--summary' )
520
520
if args .get ('list_suites' ): cmd .append ('--list-suites' )
521
521
if args .get ('list_cases' ): cmd .append ('--list-cases' )
@@ -534,9 +534,9 @@ def list_(runner, test_ids, **args):
534
534
return sp .call (cmd )
535
535
536
536
537
- def find_cases (runner_ , ** args ):
537
+ def find_cases (runner_ , ids = [], ** args ):
538
538
# query from runner
539
- cmd = runner_ + ['--list-cases' ]
539
+ cmd = runner_ + ['--list-cases' ] + ids
540
540
if args .get ('verbose' ):
541
541
print (' ' .join (shlex .quote (c ) for c in cmd ))
542
542
proc = sp .Popen (cmd ,
@@ -635,17 +635,52 @@ def find_defines(runner_, id, **args):
635
635
return defines
636
636
637
637
638
+ # Thread-safe CSV writer
639
+ class TestOutput :
640
+ def __init__ (self , path , head = None , tail = None ):
641
+ self .f = openio (path , 'w+' , 1 )
642
+ self .lock = th .Lock ()
643
+ self .head = head or []
644
+ self .tail = tail or []
645
+ self .writer = csv .DictWriter (self .f , self .head + self .tail )
646
+ self .rows = []
647
+
648
+ def close (self ):
649
+ self .f .close ()
650
+
651
+ def __enter__ (self ):
652
+ return self
653
+
654
+ def __exit__ (self , * _ ):
655
+ self .f .close ()
656
+
657
+ def writerow (self , row ):
658
+ with self .lock :
659
+ self .rows .append (row )
660
+ if all (k in self .head or k in self .tail for k in row .keys ()):
661
+ # can simply append
662
+ self .writer .writerow (row )
663
+ else :
664
+ # need to rewrite the file
665
+ self .head .extend (row .keys () - (self .head + self .tail ))
666
+ self .f .truncate ()
667
+ self .writer = csv .DictWriter (self .f , self .head + self .tail )
668
+ self .writer .writeheader ()
669
+ for row in self .rows :
670
+ self .writer .writerow (row )
671
+
672
+ # A test failure
638
673
class TestFailure (Exception ):
639
674
def __init__ (self , id , returncode , stdout , assert_ = None ):
640
675
self .id = id
641
676
self .returncode = returncode
642
677
self .stdout = stdout
643
678
self .assert_ = assert_
644
679
645
- def run_stage (name , runner_ , ** args ):
680
+ def run_stage (name , runner_ , ids , output_ , ** args ):
646
681
# get expected suite/case/perm counts
647
682
expected_suite_perms , expected_case_perms , expected_perms , total_perms = (
648
- find_cases (runner_ , ** args ))
683
+ find_cases (runner_ , ids , ** args ))
649
684
650
685
passed_suite_perms = co .defaultdict (lambda : 0 )
651
686
passed_case_perms = co .defaultdict (lambda : 0 )
@@ -662,15 +697,15 @@ def run_stage(name, runner_, **args):
662
697
locals = th .local ()
663
698
children = set ()
664
699
665
- def run_runner (runner_ ):
700
+ def run_runner (runner_ , ids = [] ):
666
701
nonlocal passed_suite_perms
667
702
nonlocal passed_case_perms
668
703
nonlocal passed_perms
669
704
nonlocal powerlosses
670
705
nonlocal locals
671
706
672
707
# run the tests!
673
- cmd = runner_ . copy ()
708
+ cmd = runner_ + ids
674
709
if args .get ('verbose' ):
675
710
print (' ' .join (shlex .quote (c ) for c in cmd ))
676
711
@@ -726,6 +761,14 @@ def run_runner(runner_):
726
761
passed_suite_perms [m .group ('suite' )] += 1
727
762
passed_case_perms [m .group ('case' )] += 1
728
763
passed_perms += 1
764
+ if output_ :
765
+ # get defines and write to csv
766
+ defines = find_defines (
767
+ runner_ , m .group ('id' ), ** args )
768
+ output_ .writerow ({
769
+ 'case' : m .group ('case' ),
770
+ 'test_pass' : 1 ,
771
+ ** defines })
729
772
elif op == 'skipped' :
730
773
locals .seen_perms += 1
731
774
elif op == 'assert' :
@@ -750,28 +793,38 @@ def run_runner(runner_):
750
793
last_stdout ,
751
794
last_assert )
752
795
753
- def run_job (runner , start = None , step = None ):
796
+ def run_job (runner_ , ids = [] , start = None , step = None ):
754
797
nonlocal failures
755
798
nonlocal killed
756
799
nonlocal locals
757
800
758
801
start = start or 0
759
802
step = step or 1
760
803
while start < total_perms :
761
- runner_ = runner .copy ()
804
+ job_runner = runner_ .copy ()
762
805
if args .get ('isolate' ) or args .get ('valgrind' ):
763
- runner_ .append ('-s%s,%s,%s' % (start , start + step , step ))
806
+ job_runner .append ('-s%s,%s,%s' % (start , start + step , step ))
764
807
else :
765
- runner_ .append ('-s%s,,%s' % (start , step ))
808
+ job_runner .append ('-s%s,,%s' % (start , step ))
766
809
767
810
try :
768
811
# run the tests
769
812
locals .seen_perms = 0
770
- run_runner (runner_ )
813
+ run_runner (job_runner , ids )
771
814
assert locals .seen_perms > 0
772
815
start += locals .seen_perms * step
773
816
774
817
except TestFailure as failure :
818
+ # keep track of failures
819
+ if output_ :
820
+ suite , case , _ = failure .id .split (':' , 2 )
821
+ # get defines and write to csv
822
+ defines = find_defines (runner_ , failure .id , ** args )
823
+ output_ .writerow ({
824
+ 'case' : ':' .join ([suite , case ]),
825
+ 'test_pass' : 0 ,
826
+ ** defines })
827
+
775
828
# race condition for multiple failures?
776
829
if failures and not args .get ('keep_going' ):
777
830
break
@@ -796,11 +849,11 @@ def run_job(runner, start=None, step=None):
796
849
if 'jobs' in args :
797
850
for job in range (args ['jobs' ]):
798
851
runners .append (th .Thread (
799
- target = run_job , args = (runner_ , job , args ['jobs' ]),
852
+ target = run_job , args = (runner_ , ids , job , args ['jobs' ]),
800
853
daemon = True ))
801
854
else :
802
855
runners .append (th .Thread (
803
- target = run_job , args = (runner_ , None , None ),
856
+ target = run_job , args = (runner_ , ids , None , None ),
804
857
daemon = True ))
805
858
806
859
def print_update (done ):
@@ -861,13 +914,12 @@ def print_update(done):
861
914
killed )
862
915
863
916
864
- def run (runner , test_ids , ** args ):
917
+ def run (runner , test_ids = [] , ** args ):
865
918
# query runner for tests
866
- runner_ = find_runner (runner , test_ids , ** args )
867
- print ('using runner: %s'
868
- % ' ' .join (shlex .quote (c ) for c in runner_ ))
919
+ runner_ = find_runner (runner , ** args )
920
+ print ('using runner: %s' % ' ' .join (shlex .quote (c ) for c in runner_ ))
869
921
expected_suite_perms , expected_case_perms , expected_perms , total_perms = (
870
- find_cases (runner_ , ** args ))
922
+ find_cases (runner_ , test_ids , ** args ))
871
923
print ('found %d suites, %d cases, %d/%d permutations'
872
924
% (len (expected_suite_perms ),
873
925
len (expected_case_perms ),
@@ -882,6 +934,9 @@ def run(runner, test_ids, **args):
882
934
trace = None
883
935
if args .get ('trace' ):
884
936
trace = openio (args ['trace' ], 'w' , 1 )
937
+ output = None
938
+ if args .get ('output' ):
939
+ output = TestOutput (args ['output' ], ['case' ], ['test_pass' ])
885
940
886
941
# measure runtime
887
942
start = time .time ()
@@ -894,14 +949,12 @@ def run(runner, test_ids, **args):
894
949
for by in (expected_case_perms .keys () if args .get ('by_cases' )
895
950
else expected_suite_perms .keys () if args .get ('by_suites' )
896
951
else [None ]):
897
- # rebuild runner for each stage to override test identifier if needed
898
- stage_runner = find_runner (runner ,
899
- [by ] if by is not None else test_ids , ** args )
900
-
901
952
# spawn jobs for stage
902
953
expected_ , passed_ , powerlosses_ , failures_ , killed = run_stage (
903
954
by or 'tests' ,
904
- stage_runner ,
955
+ runner_ ,
956
+ [by ] if by is not None else test_ids ,
957
+ output ,
905
958
** args )
906
959
expected += expected_
907
960
passed += passed_
@@ -916,6 +969,8 @@ def run(runner, test_ids, **args):
916
969
stdout .close ()
917
970
if trace :
918
971
trace .close ()
972
+ if output :
973
+ output .close ()
919
974
920
975
# show summary
921
976
print ()
@@ -975,29 +1030,29 @@ def run(runner, test_ids, **args):
975
1030
or args .get ('gdb_case' )
976
1031
or args .get ('gdb_main' )):
977
1032
failure = failures [0 ]
978
- runner_ = find_runner ( runner , [failure .id ], ** args )
1033
+ cmd = runner_ + [failure .id ]
979
1034
980
1035
if args .get ('gdb_main' ):
981
- cmd = ['gdb' ,
1036
+ cmd [: 0 ] = ['gdb' ,
982
1037
'-ex' , 'break main' ,
983
1038
'-ex' , 'run' ,
984
- '--args' ] + runner_
1039
+ '--args' ]
985
1040
elif args .get ('gdb_case' ):
986
1041
path , lineno = find_path (runner_ , failure .id , ** args )
987
- cmd = ['gdb' ,
1042
+ cmd [: 0 ] = ['gdb' ,
988
1043
'-ex' , 'break %s:%d' % (path , lineno ),
989
1044
'-ex' , 'run' ,
990
- '--args' ] + runner_
1045
+ '--args' ]
991
1046
elif failure .assert_ is not None :
992
- cmd = ['gdb' ,
1047
+ cmd [: 0 ] = ['gdb' ,
993
1048
'-ex' , 'run' ,
994
1049
'-ex' , 'frame function raise' ,
995
1050
'-ex' , 'up 2' ,
996
- '--args' ] + runner_
1051
+ '--args' ]
997
1052
else :
998
- cmd = ['gdb' ,
1053
+ cmd [: 0 ] = ['gdb' ,
999
1054
'-ex' , 'run' ,
1000
- '--args' ] + runner_
1055
+ '--args' ]
1001
1056
1002
1057
# exec gdb interactively
1003
1058
if args .get ('verbose' ):
@@ -1088,6 +1143,8 @@ def main(**args):
1088
1143
help = "Direct trace output to this file." )
1089
1144
test_parser .add_argument ('-O' , '--stdout' ,
1090
1145
help = "Direct stdout to this file. Note stderr is already merged here." )
1146
+ test_parser .add_argument ('-o' , '--output' ,
1147
+ help = "CSV file to store results." )
1091
1148
test_parser .add_argument ('--read-sleep' ,
1092
1149
help = "Artificial read delay in seconds." )
1093
1150
test_parser .add_argument ('--prog-sleep' ,
0 commit comments