#!/usr/bin/perl -w # Original coding: J.W. Lockhart ################################# # Usage: deja-summarize file1 [file2] # cat suitename.sum | deja-summarize # Where files are dejagnu test.sum output from one or two runs of the test suite. # Regression checking requires two input files. # # Intent: to summarize test output from suites such as gdb and gcc, # and possibly spot any regressions from previous runs, if any such # results are available for comparison. # # The scoring mechanism and weighting can be adjusted by # editing the hashes below -- or you can use the rest of the summarized # output to come up with your own scheme. ############################## $suiteName = ''; use vars qw($run1 $run2 $cur $res $ste $mdl $progName $fileCnt); $passCnt = $failCnt = $xfailCnt = $kfailCnt = $unSupCnt = $errCnt = 0; $unTestCnt = $unResCnt = $warnCnt = $kpassCnt = $xpassCnt = $suiteCnt = $fileCnt = 0; $totalScore = 0; $resultType = 'default'; $VERBOSE_SUM = 0; # To get a meaningful score, make the base increase # or decrease sanely per type of test result. # Note that these labels must match the %weights that follow. %baseScores = ( 'pass' => 1, 'fail' => (-2), 'xfail' => 0, 'kfail' => (-1), 'kpass' => (-1), 'xpass' => (-1), 'warning' => 0, 'brokentest' => (-10), 'ransuite' => 0, 'regression' => (-10), 'unsupported' => 0, 'untested' => 0, 'unresolved' => 0, 'default' => 0, ); # To make the differences between scores more noticeable # and meaningful, define how important each type of test # result is -- especially based on expected results, # where you're likely to have 8,000 to 11,000 PASS results. # Note that these labels must match the %baseScores above. %weights = ( 'pass' => 1, 'fail' => 2, 'xfail' => 0, 'kfail' => 1, 'kpass' => 1, 'xpass' => 1, 'warning' => 0, 'brokentest' => 5, 'ransuite' => 0, 'regression' => 10, 'unsupported' => 0, 'untested' => 0, 'unresolved' => 0, 'default' => 1, ); # Names to print for the result summary %lNames = ( pass => 'PASS', fail => 'FAIL', regression => 'REGRESSION', ransuite => 'SUITES', default => 'default', xfail => 'XFAIL', kfail => 'KFAIL', kpass => 'KPASS', xpass => 'XPASS', warning => 'WARN', unsupported => 'UNSUPPORTED', untested => 'UNTESTED', unresolved => 'UNRESOLVED', brokentest => 'ERROR', ); # For later formatting use, figure out the longest of those displayed names. $maxLen_lNames = 0; for my $i (keys(%lNames)) { my $l = length($i); $maxLen_lNames = $l if ($l > $maxLen_lNames); } # Thus the actual amount that we will vary the score # is given by (BASE * WEIGHT). # # TAG TYPES: # pass - testcase returned PASS # fail - testcase returned FAIL # xfail - testcase returned an expected failure # kfail - not sure; either known or kernel-caused failure, perhaps. # brokentest - testcase did not run correctly, generated 'ERROR' string # regression - testcase PASSed in first file, FAILed ('unexpected') in second # default - any other result (Unsupported, or misc logfile output). ## FIXME: implement the 'regression' category. This requires ## that we have 2 files as input rather than just one. $fNameA = 'none'; $fNameB = 'none'; # refs/pointers to items of interest $progName = $0; $run1 = $run2 = $cur = $res = $ste = $mdl = ''; ### [for those whose perl-fu is, well, python... ] ### One could think of the data structures this way, ### if it adds any familiarity... # struct resultsFile { # String fileName; # struct suiteList *ste; # struct resultsCount *res; # } # struct suiteList { # String suiteName; # struct moduleList *mod; # struct resultsCount *res; # } # struct moduleList { # String moduleName; # String result; // pass/fail etc # String moduleDetails; # } # struct resultsCount { # unsigned passCnt; # unsigned failCnt; # unsigned xfailCnt; # unsigned unSupCnt; # unsigned errCnt; # unsigned testCnt; # } sub newFile { my ($fn) = @_; my %r = (); my @tmpSuites = (); $r{filename} = $fn; $r{name} = $fn; $r{suites} = \@tmpSuites; $r{results} = &newResults; $r{total_score} = 0; $fileCnt++; return \%r; } sub newResults { my %tmpResCnt = (); for my $n (keys(%lNames)) { $tmpResCnt{$n} = 0; } return \%tmpResCnt; } ################################################ # isBad - return 1 if string might indicate regression, 0 otherwise. # These are results as found in %lNames that might indicate a regression. # For example if the test used to pass, but now has this kind of result # (fail or broken, or whatever). sub isBad { my ($b) = @_; for my $bad qw(fail brokentest kfail) { return 1 if ($b eq $bad); } return 0; } ################################################# # incrResult # increment the count of a given result type in a hashref # result type as found in lNames; hashref must have member 'results'. sub incrResult { my ($r, $rType) = @_; my $tmp = $r->{'results'}; $tmp->{$rType} = $tmp->{$rType} + 1; } ################################################ # newSuite - return hashref to an initialized Suite hash sub newSuite { my ($sName) = @_; my %tmpSuite = (); my @tmpModules = (); $tmpSuite{name} = $sName; $tmpSuite{modules} = \@tmpModules; $tmpSuite{results} = &newResults; return \%tmpSuite; } ########################################################################## # findMod: find hashref to module 'm' in suite 's' of run 'r', or return ''. sub findMod { my ($m, $s, $r) = @_; for my $st (@{$r->{suites}}) { if ($st->{name} eq $s->{name}) { for my $md (@{$st->{modules}}) { if (($md->{name} eq $m->{name}) and ($md->{detail} eq $m->{detail})) { return $md; } } } } return ''; } ######################################################## # printMod: print out the result, name, and detail for a given test module. sub printMod { my ($m) = @_; print $m->{result}, ": ", $m->{name}, ": ", $m->{detail}, "\n"; } ######################################################## # newModule - return ref to initialized new module hash # inputs: # n - module name (per %lNames) # r - test result # d - test detail # Example: # FAIL: gdb.base/auxv.exp: generate native core dump # $newM = newModule('fail', 'gdb.base/auxv.exp', 'generate native core dump'); sub newModule { my ($n,$r,$d) = @_; my %resRecord = ( 'name' => $n, 'result' => $r, 'detail' => $d, ); return \%resRecord; } ######################################################## # doHelp - print out the usage sub doHelp { print STDERR "Usage: $progName file1 ", '[file2]',"\n"; print STDERR " cat suitename.sum | $progName \n"; print STDERR "Where files are dejagnu test.sum output from one or two runs of the test suite.\n"; print STDERR "Regression checking requires two input files.\n"; } ######################################################## # "MAIN" STARTS HERE ######################################################## if (($#ARGV >= 0) and ($ARGV[-1] =~ m|-+help|)) { # FIXME: should really have real option # handling if we grow real options. doHelp(); exit 0; } while (defined ($ln = <>)) { chomp($ln); if ($fNameA ne 'none') { if ($ARGV ne $cur->{filename}) { ## RESET ALL POINTERS die("cannot handle more than 2 files\n") if ($fNameB ne 'none'); $fNameB = $ARGV; $run2 = newFile($ARGV); $cur = $run2; $cur->{filename} = $ARGV; # print STDERR "New Input File: ", $cur->{filename}, "\n"; } } else { $fNameA = $ARGV; $run1 = newFile($ARGV); $cur = $run1; $cur->{filename} = $ARGV; # print STDERR "New Input File: ", $cur->{filename}, "\n"; } $resultType = 'default'; # if ($ln =~ m|^PASS:\s+([^:]+):\s+(.*)|) { if ($ln =~ m|^PASS:\s+([^:[:space:]]+):*\s*(.*)|) { ### Possible New Module for GDB-style output # PASS: gdb.base/assign.exp: continuing after dummy() $testName = $1; $testDetail = $2; $passCnt++; $resultType = 'pass'; my $tempMod = newModule($testName,$resultType,$testDetail); $mdl = $tempMod; push @{$ste->{modules}}, $tempMod; print "ps: $testName with: $testDetail\n" if ($VERBOSE_SUM); incrResult($cur, $resultType); incrResult($ste, $resultType); # } elsif ($ln =~ m|^FAIL:\s+([^:]+):\s+(.*)|) { } elsif ($ln =~ m|^FAIL:\s+([^:[:space:]]+):*\s*(.*)|) { ### New Module # FAIL: gdb.base/auxv.exp: generate native core dump $failName = $1; $failDetail = $2; my ($steName, $modName) = split(m|/|, $failName, 2); print "fn: ste $steName / mod $modName with: $failDetail\n" if ($VERBOSE_SUM); $failCnt++; $resultType = 'fail'; my $tempMod = newModule($failName,$resultType,$failDetail); push @{$ste->{modules}}, $tempMod; incrResult($cur, $resultType); incrResult($ste, $resultType); # } elsif ($ln =~ m|^XFAIL:\s+([^:]+):\s+(.*)|) { } elsif ($ln =~ m|^XFAIL:\s+([^:[:space:]]+):*\s+(.*)|) { ### New Module # XFAIL: gdb.base/list.exp: list line 1 with unlimited listsize $xfailName = $1; $xfailDetail = $2; print "xf: $xfailName with: $xfailDetail\n" if ($VERBOSE_SUM); $xfailCnt++; $resultType = 'xfail'; my $tempMod = newModule($xfailName,$resultType,$xfailDetail); $mdl = $tempMod; push @{$ste->{modules}}, $tempMod; incrResult($cur, $resultType); incrResult($ste, $resultType); # } elsif ($ln =~ m|^KFAIL:\s+([^:]+):\s+(.*)|) { } elsif ($ln =~ m|^KFAIL:\s+([^:[:space:]]+):*\s+(.*)|) { ### New Module # KFAIL: gdb.threads/tls.exp: info address me (PRMS: gdb/1294) $kfailName = $1; $kfailDetail = $2; print "kf: $kfailName with: $kfailDetail\n" if ($VERBOSE_SUM); $kfailCnt++; $resultType = 'kfail'; my $tempMod = newModule($kfailName,$resultType,$kfailDetail); $mdl = $tempMod; push @{$ste->{modules}}, $tempMod; incrResult($cur, $resultType); incrResult($ste, $resultType); } elsif ($ln =~ m|^Running\s+(\.+/)*(\S+)\s+|) { ### New Suite # Running ../../../gdb/testsuite/gdb.base/bitfields.exp ... my $foo = $2; $foo =~ s|gdb/testsuite/||g; my @tmpName = split(m|/|, $foo); my ($indx1, $indx2) = ($#tmpName, ($#tmpName - 1)); $suiteName = $tmpName[$indx2]; $moduleName = $tmpName[$indx1]; print "suiteName: $suiteName :: moduleName: $moduleName\n" if ($VERBOSE_SUM); my $tempSuite = newSuite($suiteName); push @{$cur->{suites}}, $tempSuite; $ste = $tempSuite; $resultType = 'ransuite'; $suiteCnt++; incrResult($cur, $resultType); } elsif ($ln =~ m|^UNSUPPORTED: (\S+)|) { # UNSUPPORTED: gdb.base/auxv.exp: info auxv on native core dump $unSupName = $1; print "unsup: $unSupName\n" if ($VERBOSE_SUM); $resultType = 'unsupported'; $unSupCnt++; incrResult($cur, $resultType); } elsif ($ln =~ m|^UNTESTED: (\S+)|) { # UNSUPPORTED: gdb.base/auxv.exp: info auxv on native core dump $unSupName = $1; print "unsup: $unSupName\n" if ($VERBOSE_SUM); $resultType = 'untested'; $unTestCnt++; incrResult($cur, $resultType); } elsif ($ln =~ m|^UNRESOLVED: (\S+)|) { # UNSUPPORTED: gdb.base/auxv.exp: info auxv on native core dump $unSupName = $1; print "unsup: $unSupName\n" if ($VERBOSE_SUM); $resultType = 'unresolved'; $unResCnt++; incrResult($cur, $resultType); } elsif ($ln =~ m|^WARNING: (\S+)|) { # WARNING: Couldn't test self # e.g., fairly useless message. $unSupName = $1; print "warn: $unSupName\n" if ($VERBOSE_SUM); $resultType = 'warning'; $warnCnt++; incrResult($cur, $resultType); # } elsif ($ln =~ m|^KPASS:\s+([^:]+):\s+(.*)|) { } elsif ($ln =~ m|^KPASS:\s+([^:[:space:]]+):*\s+(.*)|) { ### Possible New Module # KPASS: gdb.base/sigstep.exp: continue on breakpoint, to handler entry; performing continue (PRMS gdb/1738) $testName = $1; $testDetail = $2; $kpassCnt++; $resultType = 'kpass'; my $tempMod = newModule($testName,$resultType,$testDetail); $mdl = $tempMod; push @{$ste->{modules}}, $tempMod; print "kp: $testName with: $testDetail\n" if ($VERBOSE_SUM); incrResult($cur, $resultType); incrResult($ste, $resultType); } elsif ($ln =~ m|^XPASS:\s+([^:[:space:]]+):*\s*(.*)|) { ### Possible New Module # XPASS: gcc.dg/cpp/cmdlne-dI-M.c scan-file (^|\\n)cmdlne-dI-M.*:[^\\n]*cmdlne-dI-M.c # XPASS: gdb.mi/mi-var-display.exp: eval variable anone $testName = $1; $testDetail = $2; $xpassCnt++; $resultType = 'xpass'; my $tempMod = newModule($testName,$resultType,$testDetail); $mdl = $tempMod; push @{$ste->{modules}}, $tempMod; print "xp: $testName with: $testDetail\n" if ($VERBOSE_SUM); incrResult($cur, $resultType); incrResult($ste, $resultType); } elsif (($ln =~ m|^ERROR:.*?(\S+testsuite\S+)|) || ($ln =~ m|^ERROR:\s+(.*)|)) { # ERROR: tcl error sourcing ../../../gdb/testsuite/gdb.base/attach-32.exp. # ERROR: couldn't execute "/usr/src/redhat/BUILD/gdb-6.3/build-x86_64-redhat-linux/gdb/testsuite/gdb.base/attach-32": no such file or directory $testError = $1; # $testFullError = $ln; print "test err: $testError\n" if ($VERBOSE_SUM); $errCnt++; $resultType = 'brokentest'; incrResult($cur, $resultType); } else { # miscellaneous stuff such as tcl tracebacks, blank lines, warnings, etc. $resultType = 'default'; incrResult($cur, $resultType); # print STDERR "MISC: $ln\n" if ($VERBOSE_SUM); } $totalScore += ($baseScores{$resultType} * $weights{$resultType}); $cur->{total_score} += ($baseScores{$resultType} * $weights{$resultType}); $ln = ''; } # print "SUITES: $suiteCnt\n"; # print " PASS: $passCnt\n"; # print " FAIL: $failCnt\n"; # print " XFAIL: $xfailCnt\n"; # print " ERR: $errCnt\n"; # print "UNSUPP: $unSupCnt\n"; # print " SCORE: $totalScore\n"; if ($fileCnt > 1) { my $oldMod; my $banner = 0; for $ste (@{$run2->{suites}}) { for $mdl (@{$ste->{modules}}) { if (isBad($mdl->{result})) { if ($oldMod = findMod($mdl,$ste,$run1)) { if ($oldMod->{result} =~ m|pass|i) { $resultType = 'regression'; incrResult($ste, $resultType); incrResult($run2, $resultType); $run2->{total_score} += ($baseScores{$resultType} * $weights{$resultType}); print "\nREGRESSION INFO:\n" unless (++$banner > 1); printMod($oldMod); printMod($mdl); } } } } } } for my $run ($run1, $run2) { my $x = $run->{results}; print "\nFilename: $run->{filename}\n"; for my $y (keys(%$x)) { printf "%-${maxLen_lNames}s: %7d\n", $lNames{$y}, $x->{$y}; } $x = $run->{suites}; #printf STDERR "%8s: %7d\n", "SUITEDAT", $#{@$x}; printf "%-${maxLen_lNames}s: %7d\n", "SCORE", $run->{total_score}; last if (1 >= $fileCnt); }