#!/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; }