blob: acf6c213c88ae5c5fef7f39bc7e44c6e4d7191e8 [file] [log] [blame]
Renato Golin94cc1042016-04-26 11:02:23 +01001#!/usr/bin/env perl
2
3# This script drives the bisection of LLVM/Clang/RT regressions
4# by receiving checkout, build and test commands.
5#
6# By default, the lines are:
7# checkout: './checkout.sh'
8# build: './run.sh'
9# test: ''
10#
11# Which means: success == builds.
12#
13# If you use a different checkout script, make sure it accepts one
14# parameter, the SVN revision.
15#
16# Since testing is the hard part, you should write your own
17# custom test script for each job, but you can reuse the checkout
18# and run scripts if you like.
19#
20# The test script should return 0 when the commit is GOOD and non-zero
21# when it is bad.
22#
23# The build script has a way to quickly test (check-all, test-suite)
24# and you can use that as well, as a shortcut for the testing.
25
26use strict;
27use warnings;
28use Getopt::Std;
29use Scalar::Util qw(looks_like_number);
30
31################################## Command line parsing and validation
32my ($prog) = ($0 =~ /\/([^\/]+)$/);
33my $syntax = "Syntax: $prog [-c checkout_script] [-b build_script] [-t test_script] good_rev bad_rev\n";
34die $syntax unless &getopts('c:b:t:');
35our($opt_c, $opt_b, $opt_t);
36# Job Statuses
37my $Good = 0;
38my $CompilerError = 1;
39my $TestError = 2;
40# Defaults
41my ($checkout, $build, $test) = ("./checkout.sh", "./run.sh", "");
42$checkout = $opt_c if defined $opt_c;
43$build = $opt_b if defined $opt_b;
44$test = $opt_t if defined $opt_t;
45# Validate
46die "Invalid checkout script '$checkout'\n" unless &validate_script(\$checkout);
47die "Invalid run script '$build'\n" unless &validate_script(\$build);
48die "Invalid test script '$test'\n" unless not $test or &validate_script(\$test);
49# Revs
50my $last = scalar @ARGV-1;
51die $syntax unless $last == 1;
52my $good_rev = $ARGV[$last-1];
53die $syntax unless looks_like_number $good_rev;
54my $bad_rev = $ARGV[$last];
55die $syntax unless looks_like_number $bad_rev;
56die "Bad rev '$bad_rev' is less than or equal good rev '$good_rev'\n"
57 unless $bad_rev > $good_rev;
58&main();
59exit;
60
61################################## Main bisect logic
62
63sub main () {
64 my ($good, $bad) = ($good_rev, $bad_rev);
65
66 # Header
67 print "\n==============================================\n".
68 " Checkout script: $checkout\n".
69 " Build script: $build\n".
70 " Test script: $test\n".
71 " Range: [ $good, $bad ]\n".
72 "\n==============================================\n";
73
74 # Iterations
75 my $step = 1;
76 my $range = $bad-$good;
77 my $forced_rev = 0;
78 while ($bad > $good and $bad != $good+1) {
79 my $rev = int($good+($bad-$good)/2);
80 # Use forced rev, from compilation error
81 if ($forced_rev) {
82 $rev = $forced_rev;
83 $forced_rev = 0;
84 }
85 print "\n---------------------------\n";
86 print "Step $step: $good -> $rev -> $bad\n";
87 my $result = &check($rev);
88 # All good
89 if ($result == $Good) {
90 print "Revision '$rev' is good\n";
91 rename "$rev.log", "$rev.good";
92 $good = $rev;
93 # Compiler error when test should run
94 } elsif ($result == $CompilerError and $test) {
95 print "Revision '$rev' has a compiler error\n".
96 "Since you're looking for a test error,\n".
97 "we'll skip a rev\n";
98 rename "$rev.log", "$rev.compile";
99 $forced_rev = $rev+1;
100 # Proper error
101 } else {
102 print "Revision '$rev' is bad\n";
103 rename "$rev.log", "$rev.bad";
104 $bad = $rev;
105 }
106 $step++;
107 }
108
109 # Final status
110 print "\n==============================================\n".
111 " First bad revision is '$bad'\n".
112 " Found in $step steps in a range of $range commits".
113 "\n==============================================\n";
114}
115
116################################## Checkout, run, test
117
118sub check ($) {
119 my ($rev) = @_;
120 unlink "$rev.log";
121 my $log = "2>&1 >> $rev.log";
122 print "Checking out rev '$rev'\n";
123 die "Error while checking out '$rev'\n" if system("$checkout $rev $log");
124 print "Building rev '$rev'\n";
125 return $CompilerError if system("$build $log");
126 if ($test) {
127 print "Testing rev '$rev'\n";
128 return $TestError if system("$test $log");
129 }
130 return $Good;
131}
132
133################################## Helpers
134
135sub validate_script($) {
136 my ($command) = @_;
137 my ($script) = split /\s+/, $$command;
138 return 0 if (! -x $script);
139 if ($script !~ /^[\/\.]/) {
140 $script = "./$script";
141 return 0 if (! -x $script);
142 $$command = "./$$command";
143 }
144 return 1;
145}