ktest: Do not ask for some options if the only test is build
[firefly-linux-kernel-4.4.55.git] / tools / testing / ktest / ktest.pl
index e087cb411c9b0c9483f68d37d0f3f17f0cd96f7c..7159e10ab8a44e7d246f60bbb157e7fbad423e5a 100755 (executable)
@@ -22,8 +22,7 @@ my %default;
 
 #default opts
 $default{"NUM_TESTS"}          = 1;
-$default{"REBOOT_TYPE"}                = "grub";
-$default{"TEST_TYPE"}          = "test";
+$default{"TEST_TYPE"}          = "build";
 $default{"BUILD_TYPE"}         = "randconfig";
 $default{"MAKE_CMD"}           = "make";
 $default{"TIMEOUT"}            = 120;
@@ -42,6 +41,7 @@ $default{"BISECT_MANUAL"}     = 0;
 $default{"BISECT_SKIP"}                = 1;
 $default{"SUCCESS_LINE"}       = "login:";
 $default{"DETECT_TRIPLE_FAULT"} = 1;
+$default{"NO_INSTALL"}         = 0;
 $default{"BOOTED_TIMEOUT"}     = 1;
 $default{"DIE_ON_FAILURE"}     = 1;
 $default{"SSH_EXEC"}           = "ssh \$SSH_USER\@\$MACHINE \$SSH_COMMAND";
@@ -50,7 +50,14 @@ $default{"REBOOT"}           = "ssh \$SSH_USER\@\$MACHINE reboot";
 $default{"STOP_AFTER_SUCCESS"} = 10;
 $default{"STOP_AFTER_FAILURE"} = 60;
 $default{"STOP_TEST_AFTER"}    = 600;
+
+# required, and we will ask users if they don't have them but we keep the default
+# value something that is common.
+$default{"REBOOT_TYPE"}                = "grub";
 $default{"LOCALVERSION"}       = "-test";
+$default{"SSH_USER"}           = "root";
+$default{"BUILD_TARGET"}       = "arch/x86/boot/bzImage";
+$default{"TARGET_IMAGE"}       = "/boot/vmlinuz-test";
 
 my $ktest_config;
 my $version;
@@ -84,6 +91,7 @@ my $grub_number;
 my $target;
 my $make;
 my $post_install;
+my $no_install;
 my $noclean;
 my $minconfig;
 my $start_minconfig;
@@ -101,6 +109,7 @@ my $in_patchcheck = 0;
 my $run_test;
 my $redirect;
 my $buildlog;
+my $testlog;
 my $dmesg;
 my $monitor_fp;
 my $monitor_pid;
@@ -110,11 +119,13 @@ my $bisect_sleep_time;
 my $patchcheck_sleep_time;
 my $ignore_warnings;
 my $store_failures;
+my $store_successes;
 my $test_name;
 my $timeout;
 my $booted_timeout;
 my $detect_triplefault;
 my $console;
+my $reboot_success_line;
 my $success_line;
 my $stop_after_success;
 my $stop_after_failure;
@@ -125,13 +136,24 @@ my $localversion;
 my $iteration = 0;
 my $successes = 0;
 
+# set when a test is something other that just building
+# which would require more options.
+my $buildonly = 1;
+
 my %entered_configs;
 my %config_help;
 my %variable;
 my %force_config;
 
+# do not force reboots on config problems
+my $no_reboot = 1;
+
+# default variables that can be used
+chomp ($variable{"PWD"} = `pwd`);
+
 $config_help{"MACHINE"} = << "EOF"
  The machine hostname that you will test.
+ For build only tests, it is still needed to differentiate log files.
 EOF
     ;
 $config_help{"SSH_USER"} = << "EOF"
@@ -141,11 +163,15 @@ EOF
     ;
 $config_help{"BUILD_DIR"} = << "EOF"
  The directory that contains the Linux source code (full path).
+ You can use \${PWD} that will be the path where ktest.pl is run, or use
+ \${THIS_DIR} which is assigned \${PWD} but may be changed later.
 EOF
     ;
 $config_help{"OUTPUT_DIR"} = << "EOF"
  The directory that the objects will be built (full path).
  (can not be same as BUILD_DIR)
+ You can use \${PWD} that will be the path where ktest.pl is run, or use
+ \${THIS_DIR} which is assigned \${PWD} but may be changed later.
 EOF
     ;
 $config_help{"BUILD_TARGET"} = << "EOF"
@@ -218,20 +244,36 @@ $config_help{"REBOOT_SCRIPT"} = << "EOF"
 EOF
     ;
 
-sub read_yn {
-    my ($prompt) = @_;
+sub read_prompt {
+    my ($cancel, $prompt) = @_;
 
     my $ans;
 
     for (;;) {
-       print "$prompt [Y/n] ";
+       if ($cancel) {
+           print "$prompt [y/n/C] ";
+       } else {
+           print "$prompt [Y/n] ";
+       }
        $ans = <STDIN>;
        chomp $ans;
        if ($ans =~ /^\s*$/) {
-           $ans = "y";
+           if ($cancel) {
+               $ans = "c";
+           } else {
+               $ans = "y";
+           }
        }
        last if ($ans =~ /^y$/i || $ans =~ /^n$/i);
-       print "Please answer either 'y' or 'n'.\n";
+       if ($cancel) {
+           last if ($ans =~ /^c$/i);
+           print "Please answer either 'y', 'n' or 'c'.\n";
+       } else {
+           print "Please answer either 'y' or 'n'.\n";
+       }
+    }
+    if ($ans =~ /^c/i) {
+       exit;
     }
     if ($ans !~ /^y$/i) {
        return 0;
@@ -239,8 +281,21 @@ sub read_yn {
     return 1;
 }
 
+sub read_yn {
+    my ($prompt) = @_;
+
+    return read_prompt 0, $prompt;
+}
+
+sub read_ync {
+    my ($prompt) = @_;
+
+    return read_prompt 1, $prompt;
+}
+
 sub get_ktest_config {
     my ($config) = @_;
+    my $ans;
 
     return if (defined($opt{$config}));
 
@@ -254,31 +309,39 @@ sub get_ktest_config {
        if (defined($default{$config})) {
            print "\[$default{$config}\] ";
        }
-       $entered_configs{$config} = <STDIN>;
-       $entered_configs{$config} =~ s/^\s*(.*\S)\s*$/$1/;
-       if ($entered_configs{$config} =~ /^\s*$/) {
+       $ans = <STDIN>;
+       $ans =~ s/^\s*(.*\S)\s*$/$1/;
+       if ($ans =~ /^\s*$/) {
            if ($default{$config}) {
-               $entered_configs{$config} = $default{$config};
+               $ans = $default{$config};
            } else {
                print "Your answer can not be blank\n";
                next;
            }
        }
+       $entered_configs{$config} = ${ans};
        last;
     }
 }
 
 sub get_ktest_configs {
     get_ktest_config("MACHINE");
-    get_ktest_config("SSH_USER");
     get_ktest_config("BUILD_DIR");
     get_ktest_config("OUTPUT_DIR");
-    get_ktest_config("BUILD_TARGET");
-    get_ktest_config("TARGET_IMAGE");
-    get_ktest_config("POWER_CYCLE");
-    get_ktest_config("CONSOLE");
+
+    # options required for other than just building a kernel
+    if (!$buildonly) {
+       get_ktest_config("SSH_USER");
+       get_ktest_config("BUILD_TARGET");
+       get_ktest_config("TARGET_IMAGE");
+       get_ktest_config("POWER_CYCLE");
+       get_ktest_config("CONSOLE");
+    }
+
     get_ktest_config("LOCALVERSION");
 
+    return if ($buildonly);
+
     my $rtype = $opt{"REBOOT_TYPE"};
 
     if (!defined($rtype)) {
@@ -298,7 +361,7 @@ sub get_ktest_configs {
 }
 
 sub process_variables {
-    my ($value) = @_;
+    my ($value, $remove_undef) = @_;
     my $retval = "";
 
     # We want to check for '\', and it is just easier
@@ -316,6 +379,10 @@ sub process_variables {
        $retval = "$retval$begin";
        if (defined($variable{$var})) {
            $retval = "$retval$variable{$var}";
+       } elsif (defined($remove_undef) && $remove_undef) {
+           # for if statements, any variable that is not defined,
+           # we simple convert to 0
+           $retval = "${retval}0";
        } else {
            # put back the origin piece.
            $retval = "$retval\$\{$var\}";
@@ -331,10 +398,23 @@ sub process_variables {
 }
 
 sub set_value {
-    my ($lvalue, $rvalue) = @_;
+    my ($lvalue, $rvalue, $override, $overrides, $name) = @_;
+
+    if ($lvalue =~ /^TEST_TYPE(\[.*\])?$/ && $rvalue ne "build") {
+       # Note if a test is something other than build, then we
+       # will need other manditory options.
+       $buildonly = 0;
+    }
 
     if (defined($opt{$lvalue})) {
-       die "Error: Option $lvalue defined more than once!\n";
+       if (!$override || defined(${$overrides}{$lvalue})) {
+           my $extra = "";
+           if ($override) {
+               $extra = "In the same override section!\n";
+           }
+           die "$name: $.: Option $lvalue defined more than once!\n$extra";
+       }
+       ${$overrides}{$lvalue} = $rvalue;
     }
     if ($rvalue =~ /^\s*$/) {
        delete $opt{$lvalue};
@@ -355,86 +435,274 @@ sub set_variable {
     }
 }
 
-sub read_config {
-    my ($config) = @_;
+sub process_compare {
+    my ($lval, $cmp, $rval) = @_;
+
+    # remove whitespace
+
+    $lval =~ s/^\s*//;
+    $lval =~ s/\s*$//;
+
+    $rval =~ s/^\s*//;
+    $rval =~ s/\s*$//;
+
+    if ($cmp eq "==") {
+       return $lval eq $rval;
+    } elsif ($cmp eq "!=") {
+       return $lval ne $rval;
+    }
+
+    my $statement = "$lval $cmp $rval";
+    my $ret = eval $statement;
+
+    # $@ stores error of eval
+    if ($@) {
+       return -1;
+    }
+
+    return $ret;
+}
+
+sub value_defined {
+    my ($val) = @_;
+
+    return defined($variable{$2}) ||
+       defined($opt{$2});
+}
+
+my $d = 0;
+sub process_expression {
+    my ($name, $val) = @_;
+
+    my $c = $d++;
+
+    while ($val =~ s/\(([^\(]*?)\)/\&\&\&\&VAL\&\&\&\&/) {
+       my $express = $1;
 
-    open(IN, $config) || die "can't read file $config";
+       if (process_expression($name, $express)) {
+           $val =~ s/\&\&\&\&VAL\&\&\&\&/ 1 /;
+       } else {
+           $val =~ s/\&\&\&\&VAL\&\&\&\&/ 0 /;
+       }
+    }
+
+    $d--;
+    my $OR = "\\|\\|";
+    my $AND = "\\&\\&";
+
+    while ($val =~ s/^(.*?)($OR|$AND)//) {
+       my $express = $1;
+       my $op = $2;
+
+       if (process_expression($name, $express)) {
+           if ($op eq "||") {
+               return 1;
+           }
+       } else {
+           if ($op eq "&&") {
+               return 0;
+           }
+       }
+    }
+
+    if ($val =~ /(.*)(==|\!=|>=|<=|>|<)(.*)/) {
+       my $ret = process_compare($1, $2, $3);
+       if ($ret < 0) {
+           die "$name: $.: Unable to process comparison\n";
+       }
+       return $ret;
+    }
+
+    if ($val =~ /^\s*(NOT\s*)?DEFINED\s+(\S+)\s*$/) {
+       if (defined $1) {
+           return !value_defined($2);
+       } else {
+           return value_defined($2);
+       }
+    }
+
+    if ($val =~ /^\s*0\s*$/) {
+       return 0;
+    } elsif ($val =~ /^\s*\d+\s*$/) {
+       return 1;
+    }
+
+    die ("$name: $.: Undefined content $val in if statement\n");
+}
+
+sub process_if {
+    my ($name, $value) = @_;
+
+    # Convert variables and replace undefined ones with 0
+    my $val = process_variables($value, 1);
+    my $ret = process_expression $name, $val;
+
+    return $ret;
+}
+
+sub __read_config {
+    my ($config, $current_test_num) = @_;
+
+    my $in;
+    open($in, $config) || die "can't read file $config";
 
     my $name = $config;
     $name =~ s,.*/(.*),$1,;
 
-    my $test_num = 0;
+    my $test_num = $$current_test_num;
     my $default = 1;
     my $repeat = 1;
     my $num_tests_set = 0;
     my $skip = 0;
     my $rest;
+    my $line;
     my $test_case = 0;
+    my $if = 0;
+    my $if_set = 0;
+    my $override = 0;
 
-    while (<IN>) {
+    my %overrides;
+
+    while (<$in>) {
 
        # ignore blank lines and comments
        next if (/^\s*$/ || /\s*\#/);
 
-       if (/^\s*TEST_START(.*)/) {
+       if (/^\s*(TEST_START|DEFAULTS)\b(.*)/) {
 
-           $rest = $1;
+           my $type = $1;
+           $rest = $2;
+           $line = $2;
 
-           if ($num_tests_set) {
-               die "$name: $.: Can not specify both NUM_TESTS and TEST_START\n";
-           }
+           my $old_test_num;
+           my $old_repeat;
+           $override = 0;
 
-           my $old_test_num = $test_num;
-           my $old_repeat = $repeat;
+           if ($type eq "TEST_START") {
 
-           $test_num += $repeat;
-           $default = 0;
-           $repeat = 1;
+               if ($num_tests_set) {
+                   die "$name: $.: Can not specify both NUM_TESTS and TEST_START\n";
+               }
+
+               $old_test_num = $test_num;
+               $old_repeat = $repeat;
+
+               $test_num += $repeat;
+               $default = 0;
+               $repeat = 1;
+           } else {
+               $default = 1;
+           }
 
-           if ($rest =~ /\s+SKIP(.*)/) {
-               $rest = $1;
+           # If SKIP is anywhere in the line, the command will be skipped
+           if ($rest =~ s/\s+SKIP\b//) {
                $skip = 1;
            } else {
                $test_case = 1;
                $skip = 0;
            }
 
-           if ($rest =~ /\s+ITERATE\s+(\d+)(.*)$/) {
-               $repeat = $1;
-               $rest = $2;
-               $repeat_tests{"$test_num"} = $repeat;
+           if ($rest =~ s/\sELSE\b//) {
+               if (!$if) {
+                   die "$name: $.: ELSE found with out matching IF section\n$_";
+               }
+               $if = 0;
+
+               if ($if_set) {
+                   $skip = 1;
+               } else {
+                   $skip = 0;
+               }
+           }
+
+           if ($rest =~ s/\sIF\s+(.*)//) {
+               if (process_if($name, $1)) {
+                   $if_set = 1;
+               } else {
+                   $skip = 1;
+               }
+               $if = 1;
+           } else {
+               $if = 0;
+               $if_set = 0;
            }
 
-           if ($rest =~ /\s+SKIP(.*)/) {
-               $rest = $1;
-               $skip = 1;
+           if (!$skip) {
+               if ($type eq "TEST_START") {
+                   if ($rest =~ s/\s+ITERATE\s+(\d+)//) {
+                       $repeat = $1;
+                       $repeat_tests{"$test_num"} = $repeat;
+                   }
+               } elsif ($rest =~ s/\sOVERRIDE\b//) {
+                   # DEFAULT only
+                   $override = 1;
+                   # Clear previous overrides
+                   %overrides = ();
+               }
            }
 
-           if ($rest !~ /^\s*$/) {
-               die "$name: $.: Gargbage found after TEST_START\n$_";
+           if (!$skip && $rest !~ /^\s*$/) {
+               die "$name: $.: Gargbage found after $type\n$_";
            }
 
-           if ($skip) {
+           if ($skip && $type eq "TEST_START") {
                $test_num = $old_test_num;
                $repeat = $old_repeat;
            }
 
-       } elsif (/^\s*DEFAULTS(.*)$/) {
-           $default = 1;
-
+       } elsif (/^\s*ELSE\b(.*)$/) {
+           if (!$if) {
+               die "$name: $.: ELSE found with out matching IF section\n$_";
+           }
            $rest = $1;
-
-           if ($rest =~ /\s+SKIP(.*)/) {
-               $rest = $1;
+           if ($if_set) {
                $skip = 1;
+               $rest = "";
            } else {
                $skip = 0;
+
+               if ($rest =~ /\sIF\s+(.*)/) {
+                   # May be a ELSE IF section.
+                   if (!process_if($name, $1)) {
+                       $skip = 1;
+                   }
+                   $rest = "";
+               } else {
+                   $if = 0;
+               }
            }
 
            if ($rest !~ /^\s*$/) {
                die "$name: $.: Gargbage found after DEFAULTS\n$_";
            }
 
+       } elsif (/^\s*INCLUDE\s+(\S+)/) {
+
+           next if ($skip);
+
+           if (!$default) {
+               die "$name: $.: INCLUDE can only be done in default sections\n$_";
+           }
+
+           my $file = process_variables($1);
+
+           if ($file !~ m,^/,) {
+               # check the path of the config file first
+               if ($config =~ m,(.*)/,) {
+                   if (-f "$1/$file") {
+                       $file = "$1/$file";
+                   }
+               }
+           }
+               
+           if ( ! -r $file ) {
+               die "$name: $.: Can't read file $file\n$_";
+           }
+
+           if (__read_config($file, \$test_num)) {
+               $test_case = 1;
+           }
+
        } elsif (/^\s*([A-Z_\[\]\d]+)\s*=\s*(.*?)\s*$/) {
 
            next if ($skip);
@@ -460,10 +728,10 @@ sub read_config {
            }
 
            if ($default || $lvalue =~ /\[\d+\]$/) {
-               set_value($lvalue, $rvalue);
+               set_value($lvalue, $rvalue, $override, \%overrides, $name);
            } else {
                my $val = "$lvalue\[$test_num\]";
-               set_value($val, $rvalue);
+               set_value($val, $rvalue, $override, \%overrides, $name);
 
                if ($repeat > 1) {
                    $repeats{$val} = $repeat;
@@ -490,23 +758,42 @@ sub read_config {
        }
     }
 
-    close(IN);
-
     if ($test_num) {
        $test_num += $repeat - 1;
        $opt{"NUM_TESTS"} = $test_num;
     }
 
+    close($in);
+
+    $$current_test_num = $test_num;
+
+    return $test_case;
+}
+
+sub get_test_case {
+       print "What test case would you like to run?\n";
+       print " (build, install or boot)\n";
+       print " Other tests are available but require editing the config file\n";
+       my $ans = <STDIN>;
+       chomp $ans;
+       $default{"TEST_TYPE"} = $ans;
+}
+
+sub read_config {
+    my ($config) = @_;
+
+    my $test_case;
+    my $test_num = 0;
+
+    $test_case = __read_config $config, \$test_num;
+
     # make sure we have all mandatory configs
     get_ktest_configs;
 
     # was a test specified?
     if (!$test_case) {
        print "No test case specified.\n";
-       print "What test case would you like to run?\n";
-       my $ans = <STDIN>;
-       chomp $ans;
-       $default{"TEST_TYPE"} = $ans;
+       get_test_case;
     }
 
     # set any defaults
@@ -524,6 +811,18 @@ sub __eval_option {
     # Add space to evaluate the character before $
     $option = " $option";
     my $retval = "";
+    my $repeated = 0;
+    my $parent = 0;
+
+    foreach my $test (keys %repeat_tests) {
+       if ($i >= $test &&
+           $i < $test + $repeat_tests{$test}) {
+
+           $repeated = 1;
+           $parent = $test;
+           last;
+       }
+    }
 
     while ($option =~ /(.*?[^\\])\$\{(.*?)\}(.*)/) {
        my $start = $1;
@@ -537,10 +836,14 @@ sub __eval_option {
        # otherwise see if the default OPT (without [$i]) exists.
 
        my $o = "$var\[$i\]";
+       my $parento = "$var\[$parent\]";
 
        if (defined($opt{$o})) {
            $o = $opt{$o};
            $retval = "$retval$o";
+       } elsif ($repeated && defined($opt{$parento})) {
+           $o = $opt{$parento};
+           $retval = "$retval$o";
        } elsif (defined($opt{$var})) {
            $o = $opt{$var};
            $retval = "$retval$o";
@@ -603,8 +906,20 @@ sub doprint {
 }
 
 sub run_command;
+sub start_monitor;
+sub end_monitor;
+sub wait_for_monitor;
 
 sub reboot {
+    my ($time) = @_;
+
+    if (defined($time)) {
+       start_monitor;
+       # flush out current monitor
+       # May contain the reboot success line
+       wait_for_monitor 1;
+    }
+
     # try to reboot normally
     if (run_command $reboot) {
        if (defined($powercycle_after_reboot)) {
@@ -615,12 +930,17 @@ sub reboot {
        # nope? power cycle it.
        run_command "$power_cycle";
     }
+
+    if (defined($time)) {
+       wait_for_monitor($time, $reboot_success_line);
+       end_monitor;
+    }
 }
 
 sub do_not_reboot {
     my $i = $iteration;
 
-    return $test_type eq "build" ||
+    return $test_type eq "build" || $no_reboot ||
        ($test_type eq "patchcheck" && $opt{"PATCHCHECK_TYPE[$i]"} eq "build") ||
        ($test_type eq "bisect" && $opt{"BISECT_TYPE[$i]"} eq "build");
 }
@@ -693,19 +1013,69 @@ sub end_monitor {
 }
 
 sub wait_for_monitor {
-    my ($time) = @_;
+    my ($time, $stop) = @_;
+    my $full_line = "";
     my $line;
+    my $booted = 0;
 
     doprint "** Wait for monitor to settle down **\n";
 
     # read the monitor and wait for the system to calm down
-    do {
+    while (!$booted) {
        $line = wait_for_input($monitor_fp, $time);
-       print "$line" if (defined($line));
-    } while (defined($line));
+       last if (!defined($line));
+       print "$line";
+       $full_line .= $line;
+
+       if (defined($stop) && $full_line =~ /$stop/) {
+           doprint "wait for monitor detected $stop\n";
+           $booted = 1;
+       }
+
+       if ($line =~ /\n/) {
+           $full_line = "";
+       }
+    }
     print "** Monitor flushed **\n";
 }
 
+sub save_logs {
+       my ($result, $basedir) = @_;
+       my @t = localtime;
+       my $date = sprintf "%04d%02d%02d%02d%02d%02d",
+               1900+$t[5],$t[4],$t[3],$t[2],$t[1],$t[0];
+
+       my $type = $build_type;
+       if ($type =~ /useconfig/) {
+           $type = "useconfig";
+       }
+
+       my $dir = "$machine-$test_type-$type-$result-$date";
+
+       $dir = "$basedir/$dir";
+
+       if (!-d $dir) {
+           mkpath($dir) or
+               die "can't create $dir";
+       }
+
+       my %files = (
+               "config" => $output_config,
+               "buildlog" => $buildlog,
+               "dmesg" => $dmesg,
+               "testlog" => $testlog,
+       );
+
+       while (my ($name, $source) = each(%files)) {
+               if (-f "$source") {
+                       cp "$source", "$dir/$name" or
+                               die "failed to copy $source";
+               }
+       }
+
+       doprint "*** Saved info to $dir ***\n";
+}
+
 sub fail {
 
        if ($die_on_failure) {
@@ -719,10 +1089,7 @@ sub fail {
        # no need to reboot for just building.
        if (!do_not_reboot) {
            doprint "REBOOTING\n";
-           reboot;
-           start_monitor;
-           wait_for_monitor $sleep_time;
-           end_monitor;
+           reboot $sleep_time;
        }
 
        my $name = "";
@@ -737,38 +1104,9 @@ sub fail {
        doprint "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n";
        doprint "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n";
 
-       return 1 if (!defined($store_failures));
-
-       my @t = localtime;
-       my $date = sprintf "%04d%02d%02d%02d%02d%02d",
-               1900+$t[5],$t[4],$t[3],$t[2],$t[1],$t[0];
-
-       my $type = $build_type;
-       if ($type =~ /useconfig/) {
-           $type = "useconfig";
-       }
-
-       my $dir = "$machine-$test_type-$type-fail-$date";
-       my $faildir = "$store_failures/$dir";
-
-       if (!-d $faildir) {
-           mkpath($faildir) or
-               die "can't create $faildir";
-       }
-       if (-f "$output_config") {
-           cp "$output_config", "$faildir/config" or
-               die "failed to copy .config";
-       }
-       if (-f $buildlog) {
-           cp $buildlog, "$faildir/buildlog" or
-               die "failed to move $buildlog";
-       }
-       if (-f $dmesg) {
-           cp $dmesg, "$faildir/dmesg" or
-               die "failed to move $dmesg";
-       }
-
-       doprint "*** Saved info to $faildir ***\n";
+       if (defined($store_failures)) {
+           save_logs "fail", $store_failures;
+        }
 
        return 1;
 }
@@ -854,9 +1192,12 @@ sub get_grub_index {
     open(IN, "$ssh_grub |")
        or die "unable to get menu.lst";
 
+    my $found = 0;
+
     while (<IN>) {
        if (/^\s*title\s+$grub_menu\s*$/) {
            $grub_number++;
+           $found = 1;
            last;
        } elsif (/^\s*title\s/) {
            $grub_number++;
@@ -865,7 +1206,7 @@ sub get_grub_index {
     close(IN);
 
     die "Could not find '$grub_menu' in /boot/grub/menu on $machine"
-       if ($grub_number < 0);
+       if (!$found);
     doprint "$grub_number\n";
 }
 
@@ -902,7 +1243,8 @@ sub wait_for_input
 
 sub reboot_to {
     if ($reboot_type eq "grub") {
-       run_ssh "'(echo \"savedefault --default=$grub_number --once\" | grub --batch && reboot)'";
+       run_ssh "'(echo \"savedefault --default=$grub_number --once\" | grub --batch)'";
+       reboot;
        return;
     }
 
@@ -1083,6 +1425,8 @@ sub do_post_install {
 
 sub install {
 
+    return if ($no_install);
+
     run_scp "$outputdir/$build_target", "$target_image" or
        dodie "failed to copy image";
 
@@ -1140,6 +1484,11 @@ sub get_version {
 }
 
 sub start_monitor_and_boot {
+    # Make sure the stable kernel has finished booting
+    start_monitor;
+    wait_for_monitor 5;
+    end_monitor;
+
     get_grub_index;
     get_version;
     install;
@@ -1250,6 +1599,10 @@ sub build {
 
     unlink $buildlog;
 
+    # Failed builds should not reboot the target
+    my $save_no_reboot = $no_reboot;
+    $no_reboot = 1;
+
     if (defined($pre_build)) {
        my $ret = run_command $pre_build;
        if (!$ret && defined($pre_build_die) &&
@@ -1272,15 +1625,15 @@ sub build {
        # allow for empty configs
        run_command "touch $output_config";
 
-       run_command "mv $output_config $outputdir/config_temp" or
-           dodie "moving .config";
+       if (!$noclean) {
+           run_command "mv $output_config $outputdir/config_temp" or
+               dodie "moving .config";
 
-       if (!$noclean && !run_command "$make mrproper") {
-           dodie "make mrproper";
-       }
+           run_command "$make mrproper" or dodie "make mrproper";
 
-       run_command "mv $outputdir/config_temp $output_config" or
-           dodie "moving config_temp";
+           run_command "mv $outputdir/config_temp $output_config" or
+               dodie "moving config_temp";
+       }
 
     } elsif (!$noclean) {
        unlink "$output_config";
@@ -1318,10 +1671,15 @@ sub build {
 
     if (!$build_ret) {
        # bisect may need this to pass
-       return 0 if ($in_bisect);
+       if ($in_bisect) {
+           $no_reboot = $save_no_reboot;
+           return 0;
+       }
        fail "failed build" and return 0;
     }
 
+    $no_reboot = $save_no_reboot;
+
     return 1;
 }
 
@@ -1354,12 +1712,13 @@ sub success {
     doprint     "*******************************************\n";
     doprint     "*******************************************\n";
 
+    if (defined($store_successes)) {
+        save_logs "success", $store_successes;
+    }
+
     if ($i != $opt{"NUM_TESTS"} && !do_not_reboot) {
        doprint "Reboot and wait $sleep_time seconds\n";
-       reboot;
-       start_monitor;
-       wait_for_monitor $sleep_time;
-       end_monitor;
+       reboot $sleep_time;
     }
 }
 
@@ -1386,7 +1745,10 @@ sub child_run_test {
     $poweroff_on_error = 0;
     $die_on_failure = 1;
 
+    $redirect = "$testlog";
     run_command $run_test or $failed = 1;
+    undef $redirect;
+
     exit $failed;
 }
 
@@ -1500,10 +1862,7 @@ sub run_git_bisect {
 
 sub bisect_reboot {
     doprint "Reboot and sleep $bisect_sleep_time seconds\n";
-    reboot;
-    start_monitor;
-    wait_for_monitor $bisect_sleep_time;
-    end_monitor;
+    reboot $bisect_sleep_time;
 }
 
 # returns 1 on success, 0 on failure, -1 on skip
@@ -1588,6 +1947,13 @@ sub run_bisect {
     }
 }
 
+sub update_bisect_replay {
+    my $tmp_log = "$tmpdir/ktest_bisect_log";
+    run_command "git bisect log > $tmp_log" or
+       die "can't create bisect log";
+    return $tmp_log;
+}
+
 sub bisect {
     my ($i) = @_;
 
@@ -1627,8 +1993,31 @@ sub bisect {
        $type = "boot";
     }
 
+    # Check if a bisect was running
+    my $bisect_start_file = "$builddir/.git/BISECT_START";
+
     my $check = $opt{"BISECT_CHECK[$i]"};
-    if (defined($check) && $check ne "0") {
+    my $do_check = defined($check) && $check ne "0";
+
+    if ( -f $bisect_start_file ) {
+       print "Bisect in progress found\n";
+       if ($do_check) {
+           print " If you say yes, then no checks of good or bad will be done\n";
+       }
+       if (defined($replay)) {
+           print "** BISECT_REPLAY is defined in config file **";
+           print " Ignore config option and perform new git bisect log?\n";
+           if (read_ync " (yes, no, or cancel) ") {
+               $replay = update_bisect_replay;
+               $do_check = 0;
+           }
+       } elsif (read_yn "read git log and continue?") {
+           $replay = update_bisect_replay;
+           $do_check = 0;
+       }
+    }
+
+    if ($do_check) {
 
        # get current HEAD
        my $head = get_sha1("HEAD");
@@ -2066,10 +2455,7 @@ sub config_bisect {
 
 sub patchcheck_reboot {
     doprint "Reboot and sleep $patchcheck_sleep_time seconds\n";
-    reboot;
-    start_monitor;
-    wait_for_monitor $patchcheck_sleep_time;
-    end_monitor;
+    reboot $patchcheck_sleep_time;
 }
 
 sub patchcheck {
@@ -2178,12 +2564,31 @@ sub patchcheck {
 }
 
 my %depends;
+my %depcount;
 my $iflevel = 0;
 my @ifdeps;
 
 # prevent recursion
 my %read_kconfigs;
 
+sub add_dep {
+    # $config depends on $dep
+    my ($config, $dep) = @_;
+
+    if (defined($depends{$config})) {
+       $depends{$config} .= " " . $dep;
+    } else {
+       $depends{$config} = $dep;
+    }
+
+    # record the number of configs depending on $dep
+    if (defined $depcount{$dep}) {
+       $depcount{$dep}++;
+    } else {
+       $depcount{$dep} = 1;
+    } 
+}
+
 # taken from streamline_config.pl
 sub read_kconfig {
     my ($kconfig) = @_;
@@ -2230,30 +2635,19 @@ sub read_kconfig {
            $config = $2;
 
            for (my $i = 0; $i < $iflevel; $i++) {
-               if ($i) {
-                   $depends{$config} .= " " . $ifdeps[$i];
-               } else {
-                   $depends{$config} = $ifdeps[$i];
-               }
-               $state = "DEP";
+               add_dep $config, $ifdeps[$i];
            }
 
        # collect the depends for the config
        } elsif ($state eq "NEW" && /^\s*depends\s+on\s+(.*)$/) {
 
-           if (defined($depends{$1})) {
-               $depends{$config} .= " " . $1;
-           } else {
-               $depends{$config} = $1;
-           }
+           add_dep $config, $1;
 
        # Get the configs that select this config
-       } elsif ($state ne "NONE" && /^\s*select\s+(\S+)/) {
-           if (defined($depends{$1})) {
-               $depends{$1} .= " " . $config;
-           } else {
-               $depends{$1} = $config;
-           }
+       } elsif ($state eq "NEW" && /^\s*select\s+(\S+)/) {
+
+           # selected by depends on config
+           add_dep $1, $config;
 
        # Check for if statements
        } elsif (/^if\s+(.*\S)\s*$/) {
@@ -2365,11 +2759,18 @@ sub make_new_config {
     close OUT;
 }
 
+sub chomp_config {
+    my ($config) = @_;
+
+    $config =~ s/CONFIG_//;
+
+    return $config;
+}
+
 sub get_depends {
     my ($dep) = @_;
 
-    my $kconfig = $dep;
-    $kconfig =~ s/CONFIG_//;
+    my $kconfig = chomp_config $dep;
 
     $dep = $depends{"$kconfig"};
 
@@ -2419,8 +2820,7 @@ sub test_this_config {
        return undef;
     }
 
-    my $kconfig = $config;
-    $kconfig =~ s/CONFIG_//;
+    my $kconfig = chomp_config $config;
 
     # Test dependencies first
     if (defined($depends{"$kconfig"})) {
@@ -2510,6 +2910,14 @@ sub make_min_config {
 
     my @config_keys = keys %min_configs;
 
+    # All configs need a depcount
+    foreach my $config (@config_keys) {
+       my $kconfig = chomp_config $config;
+       if (!defined $depcount{$kconfig}) {
+               $depcount{$kconfig} = 0;
+       }
+    }
+
     # Remove anything that was set by the make allnoconfig
     # we shouldn't need them as they get set for us anyway.
     foreach my $config (@config_keys) {
@@ -2548,8 +2956,13 @@ sub make_min_config {
        # Now disable each config one by one and do a make oldconfig
        # till we find a config that changes our list.
 
-       # Put configs that did not modify the config at the end.
        my @test_configs = keys %min_configs;
+
+       # Sort keys by who is most dependent on
+       @test_configs = sort  { $depcount{chomp_config($b)} <=> $depcount{chomp_config($a)} }
+                         @test_configs ;
+
+       # Put configs that did not modify the config at the end.
        my $reset = 1;
        for (my $i = 0; $i < $#test_configs; $i++) {
            if (!defined($nochange_config{$test_configs[0]})) {
@@ -2659,10 +3072,7 @@ sub make_min_config {
        }
 
        doprint "Reboot and wait $sleep_time seconds\n";
-       reboot;
-       start_monitor;
-       wait_for_monitor $sleep_time;
-       end_monitor;
+       reboot $sleep_time;
     }
 
     success $i;
@@ -2684,13 +3094,26 @@ if ($#ARGV == 0) {
 }
 
 if (! -f $ktest_config) {
+    get_test_case;
     open(OUT, ">$ktest_config") or die "Can not create $ktest_config";
     print OUT << "EOF"
 # Generated by ktest.pl
 #
+
+# PWD is a ktest.pl variable that will result in the process working
+# directory that ktest.pl is executed in.
+
+# THIS_DIR is automatically assigned the PWD of the path that generated
+# the config file. It is best to use this variable when assigning other
+# directory paths within this directory. This allows you to easily
+# move the test cases to other locations or to other machines.
+#
+THIS_DIR := $variable{"PWD"}
+
 # Define each test with TEST_START
 # The config options below it will override the defaults
 TEST_START
+TEST_TYPE = $default{"TEST_TYPE"}
 
 DEFAULTS
 EOF
@@ -2710,7 +3133,7 @@ if ($#new_configs >= 0) {
     open(OUT, ">>$ktest_config") or die "Can not append to $ktest_config";
     foreach my $config (@new_configs) {
        print OUT "$config = $entered_configs{$config}\n";
-       $opt{$config} = $entered_configs{$config};
+       $opt{$config} = process_variables($entered_configs{$config});
     }
 }
 
@@ -2783,6 +3206,9 @@ sub set_test_option {
 # First we need to do is the builds
 for (my $i = 1; $i <= $opt{"NUM_TESTS"}; $i++) {
 
+    # Do not reboot on failing test options
+    $no_reboot = 1;
+
     $iteration = $i;
 
     my $makecmd = set_test_option("MAKE_CMD", $i);
@@ -2811,6 +3237,7 @@ for (my $i = 1; $i <= $opt{"NUM_TESTS"}; $i++) {
     $reboot_type = set_test_option("REBOOT_TYPE", $i);
     $grub_menu = set_test_option("GRUB_MENU", $i);
     $post_install = set_test_option("POST_INSTALL", $i);
+    $no_install = set_test_option("NO_INSTALL", $i);
     $reboot_script = set_test_option("REBOOT_SCRIPT", $i);
     $reboot_on_error = set_test_option("REBOOT_ON_ERROR", $i);
     $poweroff_on_error = set_test_option("POWEROFF_ON_ERROR", $i);
@@ -2826,12 +3253,14 @@ for (my $i = 1; $i <= $opt{"NUM_TESTS"}; $i++) {
     $bisect_skip = set_test_option("BISECT_SKIP", $i);
     $config_bisect_good = set_test_option("CONFIG_BISECT_GOOD", $i);
     $store_failures = set_test_option("STORE_FAILURES", $i);
+    $store_successes = set_test_option("STORE_SUCCESSES", $i);
     $test_name = set_test_option("TEST_NAME", $i);
     $timeout = set_test_option("TIMEOUT", $i);
     $booted_timeout = set_test_option("BOOTED_TIMEOUT", $i);
     $console = set_test_option("CONSOLE", $i);
     $detect_triplefault = set_test_option("DETECT_TRIPLE_FAULT", $i);
     $success_line = set_test_option("SUCCESS_LINE", $i);
+    $reboot_success_line = set_test_option("REBOOT_SUCCESS_LINE", $i);
     $stop_after_success = set_test_option("STOP_AFTER_SUCCESS", $i);
     $stop_after_failure = set_test_option("STOP_AFTER_FAILURE", $i);
     $stop_test_after = set_test_option("STOP_TEST_AFTER", $i);
@@ -2850,25 +3279,29 @@ for (my $i = 1; $i <= $opt{"NUM_TESTS"}; $i++) {
 
     chdir $builddir || die "can't change directory to $builddir";
 
-    if (!-d $tmpdir) {
-       mkpath($tmpdir) or
-           die "can't create $tmpdir";
+    foreach my $dir ($tmpdir, $outputdir) {
+       if (!-d $dir) {
+           mkpath($dir) or
+               die "can't create $dir";
+       }
     }
 
     $ENV{"SSH_USER"} = $ssh_user;
     $ENV{"MACHINE"} = $machine;
 
-    $target = "$ssh_user\@$machine";
-
     $buildlog = "$tmpdir/buildlog-$machine";
+    $testlog = "$tmpdir/testlog-$machine";
     $dmesg = "$tmpdir/dmesg-$machine";
     $make = "$makecmd O=$outputdir";
     $output_config = "$outputdir/.config";
 
-    if ($reboot_type eq "grub") {
-       dodie "GRUB_MENU not defined" if (!defined($grub_menu));
-    } elsif (!defined($reboot_script)) {
-       dodie "REBOOT_SCRIPT not defined"
+    if (!$buildonly) {
+       $target = "$ssh_user\@$machine";
+       if ($reboot_type eq "grub") {
+           dodie "GRUB_MENU not defined" if (!defined($grub_menu));
+       } elsif (!defined($reboot_script)) {
+           dodie "REBOOT_SCRIPT not defined"
+       }
     }
 
     my $run_type = $build_type;
@@ -2889,11 +3322,15 @@ for (my $i = 1; $i <= $opt{"NUM_TESTS"}; $i++) {
        $run_type = "ERROR";
     }
 
+    my $installme = "";
+    $installme = " no_install" if ($no_install);
+
     doprint "\n\n";
-    doprint "RUNNING TEST $i of $opt{NUM_TESTS} with option $test_type $run_type\n\n";
+    doprint "RUNNING TEST $i of $opt{NUM_TESTS} with option $test_type $run_type$installme\n\n";
 
     unlink $dmesg;
     unlink $buildlog;
+    unlink $testlog;
 
     if (defined($addconfig)) {
        my $min = $minconfig;
@@ -2911,6 +3348,9 @@ for (my $i = 1; $i <= $opt{"NUM_TESTS"}; $i++) {
            die "failed to checkout $checkout";
     }
 
+    $no_reboot = 0;
+
+
     if ($test_type eq "bisect") {
        bisect $i;
        next;