status_upd 8.7 KB

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