blob: 9555b18071fccc4a914ab37d5f9320f424aa6440 [file] [log] [blame]
#!/usr/bin/perl
# -*- Mode: Perl; tab-width: 4; indent-tabs-mode: nil; -*-
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Mozilla JavaScript Testing Utilities
#
# The Initial Developer of the Original Code is
# Mozilla Corporation.
# Portions created by the Initial Developer are Copyright (C) 2007
# the Initial Developer. All Rights Reserved.
#
# Contributor(s): Bob Clary <bclary@bclary.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
use strict;
use Getopt::Mixed "nextOption";
# predeclarations
sub debug;
sub usage;
sub parse_options;
sub escape_string;
sub escape_pattern;
sub unescape_pattern;
# option arguments
my $option_desc = "b=s branch>b T=s buildtype>T R=s repo>R t=s testtype>t o=s os>o K=s kernel>K A=s arch>A M=s memory>M z=s timezone>z J=s jsoptions>J l=s rawlogfile>l f=s failurelogfile>f r=s patterns>r O=s outputprefix>O D debug>D";
my $testid;
my $branch;
my $repo;
my $buildtype;
my $testtype;
my $rawlogfile;
my $failurelogfile;
my $os;
my $patterns;
my $timezone;
my $jsoptions;
my $outputprefix;
my $arch;
my $kernel;
my $memory;
my $debug = $ENV{DEBUG};
# pattern variables
my $knownfailurebranchpattern;
my $failurebranchpattern;
my $knownfailureospattern;
my $failureospattern;
my $knownfailurerepopattern;
my $failurerepopattern;
my $knownfailurebuildtypepattern;
my $failurebuildtypepattern;
my $knownfailuretesttypepattern;
my $failuretesttypepattern;
my $knownfailuretimezonepattern;
my $failuretimezonepattern;
my $knownfailurejsoptionspattern;
my $failurejsoptionspattern;
my $knownfailurearchpattern;
my $failurearchpattern;
my $knownfailurekernelpattern;
my $failurekernelpattern;
my $knownfailurememorypattern;
my $failurememorypattern;
my @patterns;
my $pattern;
my @failures;
my @fixes;
my @excludedtests;
my $excludedtest;
my $excludedfile;
my %includedtests = {};
my $includedfile;
my @results;
my $regchars = '\[\^\-\]\|\{\}\?\*\+\.\<\>\$\(\)';
&parse_options;
my $jsdir = $ENV{TEST_JSDIR};
if (!defined($jsdir)) {
$jsdir = "/work/mozilla/mozilla.com/test.mozilla.com/www/tests/mozilla.org/js";
}
my @excludedfiles = ("excluded-$branch-$testtype-$buildtype.tests");
my @includedfiles = ("included-$branch-$testtype-$buildtype.tests");
# create working patterns file consisting of matches to users selection
# and which has the test description patterns escaped
# remove the excluded tests from the possible fixes log
foreach $excludedfile ( @excludedfiles ) {
open EXCLUDED, "<$jsdir/$excludedfile" or die "Unable to open excluded file $jsdir/$excludedfile: $!\n";
while (<EXCLUDED>) {
chomp;
next if ($_ =~ /^\#/);
s/\s+$//;
push @excludedtests, ($_);
}
close EXCLUDED;
}
@excludedtests = sort @excludedtests;
foreach $includedfile ( @includedfiles ) {
open INCLUDED, "<$jsdir/$includedfile" or die "Unable to open included file $jsdir/$includedfile: $!\n";
while (<INCLUDED>) {
chomp;
next if ($_ =~ /^\#/);
s/\s+$//;
$includedtests{$_} = 1;
}
close INCLUDED;
}
debug "loading patterns $patterns";
debug "pattern filter: ^TEST_ID=[^,]*, TEST_BRANCH=$knownfailurebranchpattern, TEST_REPO=$knownfailurerepopattern, TEST_BUILDTYPE=$knownfailurebuildtypepattern, TEST_TYPE=$knownfailuretesttypepattern, TEST_OS=$knownfailureospattern, TEST_KERNEL=$knownfailurekernelpattern, TEST_PROCESSORTYPE=$knownfailurearchpattern, TEST_MEMORY=$knownfailurememorypattern, TEST_TIMEZONE=$knownfailuretimezonepattern, TEST_OPTIONS=$knownfailurejsoptionspattern,";
open PATTERNS, "<$patterns" or die "Unable to open known failure patterns file $patterns: $!\n";
while (<PATTERNS>) {
chomp;
s/\s+$//;
($testid) = $_ =~ /^TEST_ID=([^,]*),/;
if (!$includedtests{$testid})
{
debug "test $testid was not included during this run";
}
elsif ($_ =~ /^TEST_ID=[^,]*, TEST_BRANCH=$knownfailurebranchpattern, TEST_REPO=$knownfailurerepopattern, TEST_BUILDTYPE=$knownfailurebuildtypepattern, TEST_TYPE=$knownfailuretesttypepattern, TEST_OS=$knownfailureospattern, TEST_KERNEL=$knownfailurekernelpattern, TEST_PROCESSORTYPE=$knownfailurearchpattern, TEST_MEMORY=$knownfailurememorypattern, TEST_TIMEZONE=$knownfailuretimezonepattern, TEST_OPTIONS=$knownfailurejsoptionspattern,/) {
debug "adding pattern : $_";
push @patterns, (escape_pattern($_));
}
else {
debug "skipping pattern: $_";
}
}
close PATTERNS;
# create a working copy of the current failures which match the users selection
debug "failure filter: ^TEST_ID=[^,]*, TEST_BRANCH=$failurebranchpattern, TEST_REPO=$failurerepopattern, TEST_BUILDTYPE=$failurebuildtypepattern, TEST_TYPE=$failuretesttypepattern, TEST_OS=$failureospattern, TEST_KERNEL=$failurekernelpattern, TEST_PROCESSORTYPE=$failurearchpattern, TEST_MEMORY=$failurememorypattern, TEST_TIMEZONE=$failuretimezonepattern, TEST_OPTIONS=$failurejsoptionspattern, TEST_RESULT=FAIL[^,]*,/";
if (defined($rawlogfile)) {
$failurelogfile = "$outputprefix-results-failures.log";
my $alllog = "$outputprefix-results-all.log";
debug "writing failures $failurelogfile";
open INPUTLOG, "$jsdir/post-process-logs.pl $rawlogfile |" or die "Unable to open $rawlogfile $!\n";
open ALLLOG, ">$alllog" or die "Unable to open $alllog $!\n";
open FAILURELOG, ">$failurelogfile" or die "Unable to open $failurelogfile $!\n";
while (<INPUTLOG>) {
chomp;
print ALLLOG "$_\n";
if ($_ =~ /^TEST_ID=[^,]*, TEST_BRANCH=$failurebranchpattern, TEST_REPO=$failurerepopattern, TEST_BUILDTYPE=$failurebuildtypepattern, TEST_TYPE=$failuretesttypepattern, TEST_OS=$failureospattern, TEST_KERNEL=$failurekernelpattern, TEST_PROCESSORTYPE=$failurearchpattern, TEST_MEMORY=$failurememorypattern, TEST_TIMEZONE=$failuretimezonepattern, TEST_OPTIONS=$failurejsoptionspattern, TEST_RESULT=FAIL[^,]*,/) {
debug "failure: $_";
push @failures, ($_);
print FAILURELOG "$_\n";
}
}
close INPUTLOG;
my $inputrc = $?;
close ALLLOG;
close FAILURELOG;
die "FATAL ERROR in post-process-logs.pl" if $inputrc != 0;
}
else
{
debug "loading failures $failurelogfile";
my $failurelogfilemode;
if ($failurelogfile =~ /\.bz2$/)
{
$failurelogfilemode = "bzcat $failurelogfile|";
}
elsif ($failurelogfile =~ /\.gz$/)
{
$failurelogfilemode = "zcat $failurelogfile|";
}
else
{
$failurelogfilemode = "<$failurelogfile";
}
open FAILURES, "$failurelogfilemode" or die "Unable to open current failure log $failurelogfile: $!\n";
while (<FAILURES>) {
chomp;
if ($_ =~ /^TEST_ID=[^,]*, TEST_BRANCH=$failurebranchpattern, TEST_REPO=$failurerepopattern, TEST_BUILDTYPE=$failurebuildtypepattern, TEST_TYPE=$failuretesttypepattern, TEST_OS=$failureospattern, TEST_KERNEL=$failurekernelpattern, TEST_PROCESSORTYPE=$failurearchpattern, TEST_MEMORY=$failurememorypattern, TEST_TIMEZONE=$failuretimezonepattern, TEST_OPTIONS=$failurejsoptionspattern, TEST_RESULT=FAIL[^,]*,/) {
debug "failure: $_";
push @failures, ($_);
}
}
close FAILURES;
}
debug "finding fixed bugs";
unlink "$outputprefix-results-possible-fixes.log";
foreach $pattern (@patterns) {
# look for known failure patterns that don't have matches in the
# the current failures selected by the user.
debug "searching for matches to $pattern\n";
@results = grep m@^$pattern@, @failures;
if ($debug) {
my $failure;
foreach $failure (@failures) {
if ($failure =~ $pattern) {
debug "MATCH: $pattern - $failure\n";
}
else {
debug "NOMATCH: $pattern - $failure\n";
}
}
}
if ($#results == -1) {
debug "fix: '$pattern'";
push @fixes, ($pattern)
}
}
foreach $excludedtest ( @excludedtests ) {
# remove any potential fixes which are due to the test being excluded
if ($debug) {
@results = grep m@$excludedtest@, @fixes;
if ($#results > -1) {
print "excluding: " . (join ', ', @results) . "\n";
}
}
@results = grep !m@$excludedtest@, @fixes;
@fixes = @results;
}
my $fix;
open OUTPUT, ">$outputprefix-results-possible-fixes.log" or die "Unable to open $outputprefix-results-possible-fixes.log: $!";
foreach $fix (@fixes) {
print OUTPUT unescape_pattern($fix) . "\n";
if ($debug) {
debug "fix: $fix";
}
}
close OUTPUT;
print STDOUT "log: $outputprefix-results-possible-fixes.log\n";
debug "finding regressions";
my $pass = 0;
my $changed = ($#patterns != -1);
debug "changed=$changed, \$#patterns=$#patterns, \$#failures=$#failures";
while ($changed) {
$pass = $pass + 1;
$changed = 0;
debug "pass $pass";
foreach $pattern (@patterns) {
debug "Pattern: $pattern";
my @nomatches = grep !m@^$pattern@, @failures;
my @matches = grep m@^$pattern@, @failures;
if ($debug) {
my $temp = join ', ', @nomatches;
debug "nomatches: $#nomatches $temp";
$temp = join ', ', @matches;
debug "matches: $#matches $temp";
}
@failures = @nomatches;
if ($#matches > -1) {
$changed = 1;
}
debug "*****************************************";
}
}
debug "\$#excludedtests=$#excludedtests, \$#failures=$#failures";
foreach $excludedtest ( @excludedtests ) {
if ($debug) {
@results = grep m@$excludedtest@, @failures;
if ($#results > -1) {
print "excluding: " . (join ', ', @results) . "\n";
}
}
@results = grep !m@$excludedtest@, @failures;
debug "\$#results=$#results, \$excludedtest=$excludedtest, \$#failures=$#failures";
@failures = @results;
}
debug "possible regressions: \$#failures=$#failures";
open OUTPUT, ">$outputprefix-results-possible-regressions.log" or die "Unable to open $outputprefix-results-possible-regressions.log: $!";
my $failure;
foreach $failure (@failures) {
print OUTPUT "$failure\n";
if ($debug) {
debug "regression: $failure";
}
}
close OUTPUT;
print STDOUT "log: $outputprefix-results-possible-regressions.log\n";
sub debug {
if ($debug) {
my $msg = shift;
print STDERR "DEBUG: $msg\n";
}
}
sub usage {
my $msg = shift;
print STDERR <<EOF;
usage: $msg
known-failures.pl [-b|--branch] branch
[-T|--buildtype] buildtype
[-t|--testtype] testtype
[-o|--os] os
[-K|--kernel] kernel
[-A|--arch] arch
[-M|--memory] memory
[-z|--timezone] timezone
[-J|--jsoptions] jsoptions
[-r|--patterns] patterns
([-f|--failurelogfile] failurelogfile|[-l|--logfile] rawlogfile])
[-O|--outputprefix] outputprefix
[-D]
variable description
=============== ============================================================
-b branch branch 1.8.0, 1.8.1, 1.9.0, all
-R repository CVS for 1.8.0, 1.8.1, 1.9.0 branches,
mercurial repository name for 1.9.1 and later branches
(\`basename http://hg.mozilla.org/repository\`)
-T buildtype build type opt, debug, all
-t testtype test type browser, shell, all
-o os operating system nt, darwin, linux, all
-K kernel kernel, all or a specific pattern
-A arch architecture, all or a specific pattern
-M memory memory in Gigabytes, all or a specific pattern
-z timezone -0400, -0700, etc. default to user\'s zone
-J jsoptions JavaScript options
-l rawlogfile raw logfile
-f failurelogfile failure logfile
-r patterns known failure patterns
-O outputprefix output files will be generated with this prefix
-D turn on debugging output
EOF
exit(2);
}
sub parse_options {
my ($option, $value);
Getopt::Mixed::init ($option_desc);
$Getopt::Mixed::order = $Getopt::Mixed::RETURN_IN_ORDER;
while (($option, $value) = nextOption()) {
if ($option eq "b") {
$branch = $value;
}
elsif ($option eq "R") {
$repo = $value;
}
elsif ($option eq "T") {
$buildtype = $value;
}
elsif ($option eq "t") {
$testtype = $value;
}
elsif ($option eq "o") {
$os = $value;
}
elsif ($option eq "K") {
$kernel = $value;
}
elsif ($option eq "A") {
$arch = $value;
}
elsif ($option eq "M") {
$memory = $value;
}
elsif ($option eq "z") {
$timezone = $value;
}
elsif ($option eq "J") {
my (@s, $j);
if (! $value) {
$jsoptions = 'none';
}
else {
$value =~ s/(-\w) (\w)/$1$2/g;
@s = sort split / /, $value;
$j = join(" ", @s);
$j =~ s/(-\w)(\w)/$1 $2/g;
$jsoptions = $j;
}
}
elsif ($option eq "r") {
$patterns = $value;
}
elsif ($option eq "l") {
$rawlogfile = $value;
}
elsif ($option eq "f") {
$failurelogfile = $value;
}
elsif ($option eq "O") {
$outputprefix = $value;
}
elsif ($option eq "D") {
$debug = 1;
}
}
if ($debug) {
print "branch=$branch, buildtype=$buildtype, testtype=$testtype, os=$os, kernel=$kernel, arch=$arch, memory=$memory, timezone=$timezone, jsoptions=$jsoptions, patterns=$patterns, rawlogfile=$rawlogfile failurelogfile=$failurelogfile, outputprefix=$outputprefix\n";
}
Getopt::Mixed::cleanup();
if ( !defined($branch) ) {
usage "missing branch";
}
if (!defined($buildtype)) {
usage "missing buildtype";
}
if (!defined($testtype)) {
usage "missing testtype";
}
if (!defined($os)) {
usage "missing os";
}
if (!defined($memory)) {
$memory = 'all';
}
if (!defined($timezone)) {
usage "missing timezone";
}
if (!defined($jsoptions)) {
$jsoptions = 'none';
}
if (!defined($patterns)) {
usage "missing patterns";
}
if (!defined($rawlogfile) && !defined($failurelogfile)) {
usage "missing logfile";
}
if (!defined($outputprefix)) {
usage "missing outputprefix";
}
if ($branch eq "all") {
$knownfailurebranchpattern = "[^,]*";
$failurebranchpattern = "[^,]*";
}
else {
$knownfailurebranchpattern = "($branch|.*)";
$knownfailurebranchpattern =~ s/\./\\./g;
$failurebranchpattern = "$branch";
$failurebranchpattern =~ s/\./\\./g;
}
if ($repo eq "all" || $repo eq ".*") {
$knownfailurerepopattern = "[^,]*";
$failurerepopattern = "[^,]*";
}
else {
$knownfailurerepopattern = "($repo|\\.\\*)";
$failurerepopattern = "$repo";
}
if ($buildtype eq "opt") {
$knownfailurebuildtypepattern = "(opt|\\.\\*)";
$failurebuildtypepattern = "opt";
}
elsif ($buildtype eq "debug") {
$knownfailurebuildtypepattern = "(debug|\\.\\*)";
$failurebuildtypepattern = "debug";
}
elsif ($buildtype eq "all") {
$knownfailurebuildtypepattern = "[^,]*";
$failurebuildtypepattern = "[^,]*";
}
if ($testtype eq "shell") {
$knownfailuretesttypepattern = "(shell|\\.\\*)";
$failuretesttypepattern = "shell";
}
elsif ($testtype eq "browser") {
$knownfailuretesttypepattern = "(browser|\\.\\*)";
$failuretesttypepattern = "browser";
}
elsif ($testtype eq "all") {
$knownfailuretesttypepattern = "[^,]*";
$failuretesttypepattern = "[^,]*";
}
if ($os eq "nt") {
$knownfailureospattern = "(nt|\\.\\*)";
$failureospattern = "nt";
}
elsif ($os eq "darwin") {
$knownfailureospattern = "(darwin|\\.\\*)";
$failureospattern = "darwin";
}
elsif ($os eq "linux") {
$knownfailureospattern = "(linux|\\.\\*)";
$failureospattern = "linux";
}
elsif ($os eq "all") {
$knownfailureospattern = "[^,]*";
$failureospattern = "[^,]*";
}
if ($kernel ne "all") {
$knownfailurekernelpattern = "(" . $kernel . "|\\.\\*)";
$failurekernelpattern = "$kernel";
}
else {
$knownfailurekernelpattern = "[^,]*";
$failurekernelpattern = "[^,]*";
}
if ($arch ne "all") {
$knownfailurearchpattern = "(" . $arch . "|\\.\\*)";
$failurearchpattern = "$arch";
}
else {
$knownfailurearchpattern = "[^,]*";
$failurearchpattern = "[^,]*";
}
if ($memory ne "all") {
$knownfailurememorypattern = "(" . $memory . "|\\.\\*)";
$failurememorypattern = "$memory";
}
else {
$knownfailurememorypattern = "[^,]*";
$failurememorypattern = "[^,]*";
}
if ($timezone eq "all") {
$knownfailuretimezonepattern = "[^,]*";
$failuretimezonepattern = "[^,]*";
}
else {
$knownfailuretimezonepattern = "(" . escape_string($timezone) . "|\\.\\*)";
$failuretimezonepattern = escape_string("$timezone");
}
if ($jsoptions eq "all") {
$knownfailurejsoptionspattern = "[^,]*";
$failurejsoptionspattern = "[^,]*";
}
else {
$knownfailurejsoptionspattern = "(" . escape_string($jsoptions) . "|\\.\\*)";
$failurejsoptionspattern = escape_string("$jsoptions");
}
}
sub escape_string {
my $s = shift;
# replace unescaped regular expression characters in the
# string so they are not interpreted as regexp chars
# when matching descriptions. leave the escaped regexp chars
# `regexp` alone so they can be unescaped later and used in
# pattern matching.
# see perldoc perlre
$s =~ s/\\/\\\\/g;
# escape non word chars that aren't surrounded by ``
$s =~ s/(?<!`)([$regchars])(?!`)/\\$1/g;
$s =~ s/(?<!`)([$regchars])(?=`)/\\$1/g;
$s =~ s/(?<=`)([$regchars])(?!`)/\\$1/g;
# unquote the regchars
$s =~ s/\`([^\`])\`/$1/g;
debug "escape_string : $s";
return "$s";
}
sub escape_pattern {
my $line = shift;
chomp;
my ($leading, $trailing) = $line =~ /(.*TEST_DESCRIPTION=)(.*)/;
# debug "escape_pattern: before: $leading$trailing";
$trailing = escape_string($trailing);
debug "escape_pattern : $leading$trailing";
return "$leading$trailing";
}
sub unescape_pattern {
my $line = shift;
chomp;
my ($leading, $trailing) = $line =~ /(.*TEST_DESCRIPTION=)(.*)/;
# quote the unescaped non word chars
$trailing =~ s/(?<!\\)([$regchars])/`$1`/g;
# unescape the escaped non word chars
$trailing =~ s/\\([$regchars])/$1/g;
$trailing =~ s/\\\\/\\/g;
debug "unescape_pattern: after: $leading$trailing";
return "$leading$trailing";
}
####
1;