status_upd 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. #!/usr/bin/perl -w
  2. # status_upd [-suqftad] [ 1.26 | path ]
  3. # process perlall maketest logfiles:
  4. # `perl$ver Makefile.PL && make test > log.test-$platform-$ver; make clean`
  5. # and find and sort by FAIL/TODO and platform+version
  6. use strict;
  7. use Data::Dumper;
  8. use Getopt::Long;
  9. use Set::Object qw(reftype);
  10. sub help {
  11. print <<EOF;
  12. status_upd -fqd [ 1.32 | path ]
  13. OPTIONS:
  14. -q quiet
  15. -f fail only
  16. -t todo only
  17. -d no unify dumps
  18. -a all, do not skip too old logs
  19. -s sort by test (ignored)
  20. -u update STATUS (ignored)
  21. -h help
  22. EOF
  23. exit;
  24. }
  25. my $logs = "log.test-*-5.*";
  26. my $dir = ".";
  27. my $STATUS = "./STATUS";
  28. chdir ".." if ! -d "t" and -d "../t";
  29. chdir "../.." if ! -d "t" and -d "../../t";
  30. my ($sortbytest, $update, $quiet, $failonly, $todoonly, $noskip, $nodump, $help);
  31. Getopt::Long::Configure ("bundling");
  32. GetOptions ("sort|s" => \$sortbytest, #ignored
  33. "update|u" => \$update, #ignored
  34. "quiet|q" => \$quiet,
  35. "fail|f" => \$failonly,
  36. "todo|pass|t" => \$todoonly,
  37. "all|a" => \$noskip,
  38. "dump|d" => \$nodump,
  39. "help|h" => \$help);
  40. help if $help;
  41. for (@ARGV) {
  42. -d "t/reports/$_" and $dir = "t/reports/$_";
  43. -d "$_" and $dir = $_;
  44. }
  45. # read stdout lines from a grep command and
  46. # prints and return a string of the sorted
  47. # results and a hash for further processing.
  48. sub status {
  49. my $h = shift;
  50. my @g = @_;
  51. my $s = "";
  52. my %h = %$h;
  53. my $prefix = '';
  54. my $oldprefix = '';
  55. my $skipped = 0;
  56. while (@g) {
  57. if ($g[0] =~ /^--/) {
  58. $oldprefix = $prefix if $prefix;
  59. $prefix = '';
  60. shift @g;
  61. next;
  62. }
  63. my $file = shift @g;
  64. my $failed = shift @g;
  65. my $ctime = 0;
  66. unless ($prefix) {
  67. my ($f) = $file =~ m{(log.test-.*?)-t/};
  68. ($prefix) = $file =~ m{log.test-(.*?)-t/};
  69. if ($prefix and $oldprefix ne $prefix) {
  70. #$prefix =~ s/ATGRZ.+?-/cygwin-/;
  71. $ctime = -f $f ? sprintf("%0.3f", -C $f) : 0;
  72. print "\n$prefix: age=$ctime" unless $quiet;
  73. if ($ctime > 1.5 and !$noskip) {
  74. $skipped = 1;
  75. print " skipped: too old" unless $quiet;
  76. $s .= "\n$prefix:\n" unless $quiet;
  77. } else {
  78. $s .= "\n$prefix:\n";
  79. $skipped = 0;
  80. }
  81. print "\n" unless $quiet;
  82. }
  83. }
  84. next unless $prefix;
  85. next unless $file;
  86. chomp $file;
  87. ($file) = $file =~ m{log.test-.*-(t/[\w\.]+\s?)};
  88. $file =~ s{\s*$}{};
  89. $file =~ s{^\s*}{};
  90. $failed =~ s{^.+(Failed tests?:?)}{$1}i;
  91. $failed =~ s{^.+TODO passed:}{TODO passed:};
  92. chomp $failed;
  93. $failed =~ s/(\d)-(\d)/$1..$2/g;
  94. my $f = $failed;
  95. $f =~ s{^Failed tests?:?\s*(.+)$}{$1}i;
  96. $f =~ s{^TODO passed:\s*}{};
  97. $f =~ s/ //g;
  98. my $c = "$file\t" if $failed;
  99. $c .= "\t" if length($file) < 8;
  100. $c .= "$failed\n";
  101. $h{$prefix}->{$file} = $f;
  102. next if $skipped;
  103. print "$c" unless $quiet;
  104. $s .= $c;
  105. }
  106. print "\n" unless $quiet;
  107. [ $s, \%h ];
  108. }
  109. # split into platform, version, [feature]
  110. # debian-squeeze-amd64-5.10.1-nt => ("debian-squeeze-amd64", "5.10", "nt")
  111. sub platform_version_split {
  112. local $_ = shift;
  113. my ($p,$v,$f) = m/^(.+)-(5\.[\d\.]+)([-dnt]+)?$/;
  114. $f =~ s/^-// if $f; # d, d-nt, nt or empty
  115. $v =~ s/(\d\.\d+)\.\d+/$1/ if $v;
  116. return ($p,$v,$f);
  117. }
  118. sub h_size($) { scalar keys %{$_[0]} }
  119. sub split_tests($) {
  120. my $t = shift;
  121. map {
  122. if (/(\d+)\.\.(\d+)/) {
  123. ($1 .. $2)
  124. } else {
  125. $_
  126. }
  127. } split /,\s*/, $t;
  128. }
  129. sub in_both ($$) {
  130. # only the elements on both lists
  131. my %h1 = map { $_ => 1 } @{$_[0]};
  132. my %h2 = map { $_ => 1 } @{$_[1]};
  133. for (keys %h1) {
  134. my $e = $h1{$_};
  135. undef $h1{$_} unless $h2{$e};
  136. }
  137. sort keys %h1;
  138. }
  139. # every
  140. sub all_common {
  141. my $h = shift; # platform_version -> test_file -> test_no_failed
  142. my $result = shift; # skip already deleted results, initially empty
  143. my (%tests);
  144. if (@_ == 1) {
  145. delete $h->{$_[0]}->{''};
  146. return $h->{$_[0]};
  147. }
  148. # init with shortest list, sort hash by least number of keys
  149. my @p = sort { h_size($h->{$a}) <=> h_size($h->{$b}) } @_;
  150. my $pivot = $p[0];
  151. my $pivotset = Set::Object->new(keys %{$h->{$pivot}});
  152. for ($pivotset->members) {
  153. if (my $k = $h->{$pivot}->{$_}) {
  154. $tests{$_} = Set::Object->new(split_tests($k));
  155. }
  156. }
  157. for my $p (@_) { # check for common keys (in every)
  158. my $c = $pivotset * Set::Object->new(keys %{$h->{$p}});
  159. for ($c->members) {
  160. if ($_ and exists $tests{$_}) {
  161. $result->{$_} = $result->{$_} ? $tests{$_} * $result->{$_} : $tests{$_};
  162. $result->{$_} = $result->{$_} * Set::Object->new( split_tests($h->{$p}->{$_}) )
  163. if $result->{$_}->members;
  164. $result->{$_} = $result->{$_}->members;# status_upd -f -q -d
  165. }
  166. delete $result->{$_} unless $result->{$_};
  167. }
  168. }
  169. delete $result->{''};
  170. return $result;
  171. }
  172. # XXX FIXME does not work yet
  173. sub unify_results {
  174. my $h = shift; # platform_version -> file -> failed
  175. my $name = shift; # todo or fail
  176. # first check for common results in files, all platforms
  177. my @platforms = keys %$h;
  178. my $result = all_common($h, {}, @platforms);
  179. if (%$result) {
  180. print Data::Dumper->Dump([$result],["common_$name"]);
  181. # initialize for next round: delete already common found
  182. for my $p (@platforms) {
  183. for (keys %{$h->{$p}}) {
  184. if ($result->{$_} and $result->{$_} ne $h->{$p}->{$_}) { # strip out common tests
  185. my $both = Set::Object->new(split_tests $h->{$p}->{$_})
  186. - Set::Object->new($result->{$_});
  187. if ($both->members) {
  188. $h->{$p}->{$_} = join(",", $both->members);
  189. } else {
  190. undef $h->{$p}->{$_};
  191. }
  192. }
  193. }
  194. }
  195. }
  196. my $h_sav = $h;
  197. # ignore the platform for now. we don't have any platform issues.
  198. # check for all pairs version - feature the shortest commons
  199. # 1. sort by versions (ignore platform + features) *-v-*
  200. # ignore older devel versions (5.11), just blead
  201. my %versions;
  202. for (@platforms) {
  203. my ($p,$v,$f) = platform_version_split($_);
  204. push @{$versions{$v}}, ($_) if $v;
  205. }
  206. for my $v (sort keys %versions) {
  207. if ($v !~ /^5\.(7|9|11)$/) { # skip 5.11, 5.9, 5.7, but not blead (5.13 currently)
  208. my $v1 = all_common($h, $result, @{$versions{$v}});
  209. if (%$v1) {
  210. print Data::Dumper->Dump([$v1],["v$v $name"]);
  211. }
  212. }
  213. }
  214. # 2. sort by feature (ignore platform + version) *-*-f
  215. $h = $h_sav;
  216. my %feat;
  217. for (@platforms) {
  218. my ($p,$v,$f) = platform_version_split($_);
  219. $f = "" unless $f;
  220. push @{$feat{$f}}, ($_);
  221. }
  222. for my $f (sort keys %feat) {
  223. my $f1 = all_common($h, $result, @{$feat{$f}});
  224. if (%$f1) {
  225. print Data::Dumper->Dump([$f1],["feature $f $name"]);
  226. }
  227. }
  228. }
  229. my $dlogs = $dir eq '.' ? "$logs" : "$dir/$logs";
  230. my $cmd = 'grep -a -i "tests" ' . $dlogs . " | grep -v t/CORE";
  231. #print "$cmd\n" unless $quiet;
  232. my %h;
  233. my %h_sav = %h;
  234. if (my @g = `$cmd`) {
  235. for my $file (@g) {
  236. my $prefix;
  237. if (($prefix) = $file =~ m{log.test-(.*?):}) {
  238. ($file) = $file =~ m/(log.test-.*?):/;
  239. my $ctime = -f $file ? sprintf("%0.3f", -C $file) : 0;
  240. if ($ctime < 1.5 or $noskip) {
  241. $h{$prefix}->{''} = '';
  242. }
  243. }
  244. }
  245. } else {
  246. die "no $logs found\n";
  247. }
  248. if (!$todoonly) {
  249. my $cmd = 'grep -a -B1 -i "Failed test" ' . $dlogs . " | grep -v t/CORE";
  250. print "$cmd\n" unless $quiet;
  251. if (my @g = `$cmd`) {
  252. my $failed = status(\%h, @g);
  253. print $failed->[0] if $nodump and $quiet;
  254. my $failedu = unify_results($failed->[1], "fail") unless $nodump;
  255. }
  256. }
  257. %h = %h_sav;
  258. if (!$failonly) {
  259. my $cmd = 'grep -a -B1 -i "TODO passed" ' . $dlogs . " | grep -v t/CORE";
  260. print "\n$cmd\n" unless $quiet;
  261. if (my @g = `$cmd`) {
  262. my $todo = status(\%h, @g);
  263. print $todo->[0] if $nodump and $quiet;
  264. my $todou = unify_results($todo->[1], "todo_pass") unless $nodump;
  265. }
  266. }
  267. # XXX TODO: update the TEST STATUS section in "./STATUS"
  268. if ($update) {
  269. die "file not found $STATUS\n" unless -e $STATUS;
  270. die "-u update STATUS not yet implemented\n";
  271. # sort away platforms
  272. }
  273. # Local Variables:
  274. # mode: cperl
  275. # cperl-indent-level: 2
  276. # fill-column: 100
  277. # End:
  278. # vim: expandtab shiftwidth=2: