blob: acf6c213c88ae5c5fef7f39bc7e44c6e4d7191e8 [file] [log] [blame]
#!/usr/bin/env perl
# This script drives the bisection of LLVM/Clang/RT regressions
# by receiving checkout, build and test commands.
#
# By default, the lines are:
# checkout: './checkout.sh'
# build: './run.sh'
# test: ''
#
# Which means: success == builds.
#
# If you use a different checkout script, make sure it accepts one
# parameter, the SVN revision.
#
# Since testing is the hard part, you should write your own
# custom test script for each job, but you can reuse the checkout
# and run scripts if you like.
#
# The test script should return 0 when the commit is GOOD and non-zero
# when it is bad.
#
# The build script has a way to quickly test (check-all, test-suite)
# and you can use that as well, as a shortcut for the testing.
use strict;
use warnings;
use Getopt::Std;
use Scalar::Util qw(looks_like_number);
################################## Command line parsing and validation
my ($prog) = ($0 =~ /\/([^\/]+)$/);
my $syntax = "Syntax: $prog [-c checkout_script] [-b build_script] [-t test_script] good_rev bad_rev\n";
die $syntax unless &getopts('c:b:t:');
our($opt_c, $opt_b, $opt_t);
# Job Statuses
my $Good = 0;
my $CompilerError = 1;
my $TestError = 2;
# Defaults
my ($checkout, $build, $test) = ("./checkout.sh", "./run.sh", "");
$checkout = $opt_c if defined $opt_c;
$build = $opt_b if defined $opt_b;
$test = $opt_t if defined $opt_t;
# Validate
die "Invalid checkout script '$checkout'\n" unless &validate_script(\$checkout);
die "Invalid run script '$build'\n" unless &validate_script(\$build);
die "Invalid test script '$test'\n" unless not $test or &validate_script(\$test);
# Revs
my $last = scalar @ARGV-1;
die $syntax unless $last == 1;
my $good_rev = $ARGV[$last-1];
die $syntax unless looks_like_number $good_rev;
my $bad_rev = $ARGV[$last];
die $syntax unless looks_like_number $bad_rev;
die "Bad rev '$bad_rev' is less than or equal good rev '$good_rev'\n"
unless $bad_rev > $good_rev;
&main();
exit;
################################## Main bisect logic
sub main () {
my ($good, $bad) = ($good_rev, $bad_rev);
# Header
print "\n==============================================\n".
" Checkout script: $checkout\n".
" Build script: $build\n".
" Test script: $test\n".
" Range: [ $good, $bad ]\n".
"\n==============================================\n";
# Iterations
my $step = 1;
my $range = $bad-$good;
my $forced_rev = 0;
while ($bad > $good and $bad != $good+1) {
my $rev = int($good+($bad-$good)/2);
# Use forced rev, from compilation error
if ($forced_rev) {
$rev = $forced_rev;
$forced_rev = 0;
}
print "\n---------------------------\n";
print "Step $step: $good -> $rev -> $bad\n";
my $result = &check($rev);
# All good
if ($result == $Good) {
print "Revision '$rev' is good\n";
rename "$rev.log", "$rev.good";
$good = $rev;
# Compiler error when test should run
} elsif ($result == $CompilerError and $test) {
print "Revision '$rev' has a compiler error\n".
"Since you're looking for a test error,\n".
"we'll skip a rev\n";
rename "$rev.log", "$rev.compile";
$forced_rev = $rev+1;
# Proper error
} else {
print "Revision '$rev' is bad\n";
rename "$rev.log", "$rev.bad";
$bad = $rev;
}
$step++;
}
# Final status
print "\n==============================================\n".
" First bad revision is '$bad'\n".
" Found in $step steps in a range of $range commits".
"\n==============================================\n";
}
################################## Checkout, run, test
sub check ($) {
my ($rev) = @_;
unlink "$rev.log";
my $log = "2>&1 >> $rev.log";
print "Checking out rev '$rev'\n";
die "Error while checking out '$rev'\n" if system("$checkout $rev $log");
print "Building rev '$rev'\n";
return $CompilerError if system("$build $log");
if ($test) {
print "Testing rev '$rev'\n";
return $TestError if system("$test $log");
}
return $Good;
}
################################## Helpers
sub validate_script($) {
my ($command) = @_;
my ($script) = split /\s+/, $$command;
return 0 if (! -x $script);
if ($script !~ /^[\/\.]/) {
$script = "./$script";
return 0 if (! -x $script);
$$command = "./$$command";
}
return 1;
}