status_upd 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  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. next unless $file;
  89. $file =~ s{\s*$}{};
  90. $file =~ s{^\s*}{};
  91. $failed =~ s{^.+(Failed tests?:?)}{$1}i;
  92. $failed =~ s{^.+TODO passed:}{TODO passed:};
  93. chomp $failed;
  94. $failed =~ s/(\d)-(\d)/$1..$2/g;
  95. my $f = $failed;
  96. $f =~ s{^Failed tests?:?\s*(.+)$}{$1}i;
  97. $f =~ s{^TODO passed:\s*}{};
  98. $f =~ s/ //g;
  99. my $c = "$file\t" if $failed;
  100. $c .= "\t" if length($file) < 8;
  101. $c .= "$failed\n";
  102. $h{$prefix}->{$file} = $f;
  103. next if $skipped;
  104. print "$c" unless $quiet;
  105. $s .= $c;
  106. }
  107. print "\n" unless $quiet;
  108. [ $s, \%h ];
  109. }
  110. # split into platform, version, [feature]
  111. # debian-squeeze-amd64-5.10.1-nt => ("debian-squeeze-amd64", "5.10", "nt")
  112. sub platform_version_split {
  113. local $_ = shift;
  114. my ($p,$v,$f) = m/^(.+)-(5\.[\d\.]+)([-dnt]+)?$/;
  115. $f =~ s/^-// if $f; # d, d-nt, nt or empty
  116. $v =~ s/(\d\.\d+)\.\d+/$1/ if $v;
  117. return ($p,$v,$f);
  118. }
  119. sub h_size($) { scalar keys %{$_[0]} }
  120. sub split_tests($) {
  121. my $t = shift;
  122. map {
  123. if (/(\d+)\.\.(\d+)/) {
  124. ($1 .. $2)
  125. } else {
  126. $_
  127. }
  128. } split /,\s*/, $t;
  129. }
  130. sub in_both ($$) {
  131. # only the elements on both lists
  132. my %h1 = map { $_ => 1 } @{$_[0]};
  133. my %h2 = map { $_ => 1 } @{$_[1]};
  134. for (keys %h1) {
  135. my $e = $h1{$_};
  136. undef $h1{$_} unless $h2{$e};
  137. }
  138. sort keys %h1;
  139. }
  140. # every
  141. sub all_common {
  142. my $h = shift; # platform_version -> test_file -> test_no_failed
  143. my $result = shift; # skip already deleted results, initially empty
  144. my (%tests);
  145. if (@_ == 1) {
  146. delete $h->{$_[0]}->{''};
  147. return $h->{$_[0]};
  148. }
  149. # init with shortest list, sort hash by least number of keys
  150. my @p = sort { h_size($h->{$a}) <=> h_size($h->{$b}) } @_;
  151. my $pivot = $p[0];
  152. my $pivotset = Set::Object->new(keys %{$h->{$pivot}});
  153. for ($pivotset->members) {
  154. if (my $k = $h->{$pivot}->{$_}) {
  155. $tests{$_} = Set::Object->new(split_tests($k));
  156. }
  157. }
  158. for my $p (@_) { # check for common keys (in every)
  159. my $c = $pivotset * Set::Object->new(keys %{$h->{$p}});
  160. for ($c->members) {
  161. if ($_ and exists $tests{$_}) {
  162. $result->{$_} = $result->{$_} ? $tests{$_} * $result->{$_} : $tests{$_};
  163. $result->{$_} = $result->{$_} * Set::Object->new( split_tests($h->{$p}->{$_}) )
  164. if $result->{$_}->members;
  165. $result->{$_} = $result->{$_}->members;# status_upd -f -q -d
  166. }
  167. delete $result->{$_} unless $result->{$_};
  168. }
  169. }
  170. delete $result->{''};
  171. return $result;
  172. }
  173. # XXX FIXME does not work yet
  174. sub unify_results {
  175. my $h = shift; # platform_version -> file -> failed
  176. my $name = shift; # todo or fail
  177. # first check for common results in files, all platforms
  178. my @platforms = keys %$h;
  179. my $result = all_common($h, {}, @platforms);
  180. if (%$result) {
  181. print Data::Dumper->Dump([$result],["common_$name"]);
  182. # initialize for next round: delete already common found
  183. for my $p (@platforms) {
  184. for (keys %{$h->{$p}}) {
  185. if ($result->{$_} and $result->{$_} ne $h->{$p}->{$_}) { # strip out common tests
  186. my $both = Set::Object->new(split_tests $h->{$p}->{$_})
  187. - Set::Object->new($result->{$_});
  188. if ($both->members) {
  189. $h->{$p}->{$_} = join(",", $both->members);
  190. } else {
  191. undef $h->{$p}->{$_};
  192. }
  193. }
  194. }
  195. }
  196. }
  197. my $h_sav = $h;
  198. # ignore the platform for now. we don't have any platform issues.
  199. # check for all pairs version - feature the shortest commons
  200. # 1. sort by versions (ignore platform + features) *-v-*
  201. # ignore older devel versions (5.11), just blead
  202. my %versions;
  203. for (@platforms) {
  204. my ($p,$v,$f) = platform_version_split($_);
  205. push @{$versions{$v}}, ($_) if $v;
  206. }
  207. for my $v (sort keys %versions) {
  208. if ($v !~ /^5\.(7|9|11)$/) { # skip 5.11, 5.9, 5.7, but not blead (5.13 currently)
  209. my $v1 = all_common($h, $result, @{$versions{$v}});
  210. if (%$v1) {
  211. print Data::Dumper->Dump([$v1],["v$v $name"]);
  212. }
  213. }
  214. }
  215. # 2. sort by feature (ignore platform + version) *-*-f
  216. $h = $h_sav;
  217. my %feat;
  218. for (@platforms) {
  219. my ($p,$v,$f) = platform_version_split($_);
  220. $f = "" unless $f;
  221. push @{$feat{$f}}, ($_);
  222. }
  223. for my $f (sort keys %feat) {
  224. my $f1 = all_common($h, $result, @{$feat{$f}});
  225. if (%$f1) {
  226. print Data::Dumper->Dump([$f1],["feature $f $name"]);
  227. }
  228. }
  229. }
  230. my $dlogs = $dir eq '.' ? "$logs" : "$dir/$logs";
  231. my $cmd = 'grep -a -i "tests" ' . $dlogs . " | grep -v t/CORE";
  232. #print "$cmd\n" unless $quiet;
  233. my %h;
  234. my %h_sav = %h;
  235. if (my @g = `$cmd`) {
  236. for my $file (@g) {
  237. my $prefix;
  238. if (($prefix) = $file =~ m{log.test-(.*?):}) {
  239. ($file) = $file =~ m/(log.test-.*?):/;
  240. my $ctime = -f $file ? sprintf("%0.3f", -C $file) : 0;
  241. if ($ctime < 1.5 or $noskip) {
  242. $h{$prefix}->{''} = '';
  243. }
  244. }
  245. }
  246. } else {
  247. die "no $logs found\n";
  248. }
  249. if (!$todoonly) {
  250. my $cmd = 'grep -a -B1 -i "Failed test" ' . $dlogs . " | grep -v t/CORE";
  251. print "$cmd\n" unless $quiet;
  252. if (my @g = `$cmd`) {
  253. my $failed = status(\%h, @g);
  254. print $failed->[0] if $nodump and $quiet;
  255. my $failedu = unify_results($failed->[1], "fail") unless $nodump;
  256. }
  257. }
  258. %h = %h_sav;
  259. if (!$failonly) {
  260. my $cmd = 'grep -a -B1 -i "TODO passed" ' . $dlogs . " | grep -v t/CORE";
  261. print "\n$cmd\n" unless $quiet;
  262. if (my @g = `$cmd`) {
  263. my $todo = status(\%h, @g);
  264. print $todo->[0] if $nodump and $quiet;
  265. my $todou = unify_results($todo->[1], "todo_pass") unless $nodump;
  266. }
  267. }
  268. # XXX TODO: update the TEST STATUS section in "./STATUS"
  269. if ($update) {
  270. die "file not found $STATUS\n" unless -e $STATUS;
  271. die "-u update STATUS not yet implemented\n";
  272. # sort away platforms
  273. }
  274. # Local Variables:
  275. # mode: cperl
  276. # cperl-indent-level: 2
  277. # fill-column: 100
  278. # End:
  279. # vim: expandtab shiftwidth=2: