diff options
Diffstat (limited to 'tools/testing/ktest/ktest.pl')
| -rwxr-xr-x | tools/testing/ktest/ktest.pl | 1442 |
1 files changed, 1174 insertions, 268 deletions
diff --git a/tools/testing/ktest/ktest.pl b/tools/testing/ktest/ktest.pl index 8b4c2535b26..40631569a0f 100755 --- a/tools/testing/ktest/ktest.pl +++ b/tools/testing/ktest/ktest.pl @@ -18,44 +18,65 @@ $| = 1; my %opt; my %repeat_tests; my %repeats; -my %default; +my %evals; #default opts -$default{"NUM_TESTS"} = 1; -$default{"REBOOT_TYPE"} = "grub"; -$default{"TEST_TYPE"} = "test"; -$default{"BUILD_TYPE"} = "randconfig"; -$default{"MAKE_CMD"} = "make"; -$default{"TIMEOUT"} = 120; -$default{"TMP_DIR"} = "/tmp/ktest/\${MACHINE}"; -$default{"SLEEP_TIME"} = 60; # sleep time between tests -$default{"BUILD_NOCLEAN"} = 0; -$default{"REBOOT_ON_ERROR"} = 0; -$default{"POWEROFF_ON_ERROR"} = 0; -$default{"REBOOT_ON_SUCCESS"} = 1; -$default{"POWEROFF_ON_SUCCESS"} = 0; -$default{"BUILD_OPTIONS"} = ""; -$default{"BISECT_SLEEP_TIME"} = 60; # sleep time between bisects -$default{"PATCHCHECK_SLEEP_TIME"} = 60; # sleep time between patch checks -$default{"CLEAR_LOG"} = 0; -$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"; -$default{"SCP_TO_TARGET"} = "scp \$SRC_FILE \$SSH_USER\@\$MACHINE:\$DST_FILE"; -$default{"REBOOT"} = "ssh \$SSH_USER\@\$MACHINE reboot"; -$default{"STOP_AFTER_SUCCESS"} = 10; -$default{"STOP_AFTER_FAILURE"} = 60; -$default{"STOP_TEST_AFTER"} = 600; -$default{"LOCALVERSION"} = "-test"; +my %default = ( + "NUM_TESTS" => 1, + "TEST_TYPE" => "build", + "BUILD_TYPE" => "randconfig", + "MAKE_CMD" => "make", + "CLOSE_CONSOLE_SIGNAL" => "INT", + "TIMEOUT" => 120, + "TMP_DIR" => "/tmp/ktest/\${MACHINE}", + "SLEEP_TIME" => 60, # sleep time between tests + "BUILD_NOCLEAN" => 0, + "REBOOT_ON_ERROR" => 0, + "POWEROFF_ON_ERROR" => 0, + "REBOOT_ON_SUCCESS" => 1, + "POWEROFF_ON_SUCCESS" => 0, + "BUILD_OPTIONS" => "", + "BISECT_SLEEP_TIME" => 60, # sleep time between bisects + "PATCHCHECK_SLEEP_TIME" => 60, # sleep time between patch checks + "CLEAR_LOG" => 0, + "BISECT_MANUAL" => 0, + "BISECT_SKIP" => 1, + "BISECT_TRIES" => 1, + "MIN_CONFIG_TYPE" => "boot", + "SUCCESS_LINE" => "login:", + "DETECT_TRIPLE_FAULT" => 1, + "NO_INSTALL" => 0, + "BOOTED_TIMEOUT" => 1, + "DIE_ON_FAILURE" => 1, + "SSH_EXEC" => "ssh \$SSH_USER\@\$MACHINE \$SSH_COMMAND", + "SCP_TO_TARGET" => "scp \$SRC_FILE \$SSH_USER\@\$MACHINE:\$DST_FILE", + "SCP_TO_TARGET_INSTALL" => "\${SCP_TO_TARGET}", + "REBOOT" => "ssh \$SSH_USER\@\$MACHINE reboot", + "STOP_AFTER_SUCCESS" => 10, + "STOP_AFTER_FAILURE" => 60, + "STOP_TEST_AFTER" => 600, + "MAX_MONITOR_WAIT" => 1800, + "GRUB_REBOOT" => "grub2-reboot", + "SYSLINUX" => "extlinux", + "SYSLINUX_PATH" => "/boot/extlinux", + +# required, and we will ask users if they don't have them but we keep the default +# value something that is common. + "REBOOT_TYPE" => "grub", + "LOCALVERSION" => "-test", + "SSH_USER" => "root", + "BUILD_TARGET" => "arch/x86/boot/bzImage", + "TARGET_IMAGE" => "/boot/vmlinuz-test", + + "LOG_FILE" => undef, + "IGNORE_UNUSED" => 0, +); my $ktest_config; my $version; +my $have_version = 0; my $machine; +my $last_machine; my $ssh_user; my $tmpdir; my $builddir; @@ -64,6 +85,11 @@ my $output_config; my $test_type; my $build_type; my $build_options; +my $final_post_ktest; +my $pre_ktest; +my $post_ktest; +my $pre_test; +my $post_test; my $pre_build; my $post_build; my $pre_build_die; @@ -73,17 +99,29 @@ my $reboot_script; my $power_cycle; my $reboot; my $reboot_on_error; +my $switch_to_good; +my $switch_to_test; my $poweroff_on_error; +my $reboot_on_success; my $die_on_failure; my $powercycle_after_reboot; my $poweroff_after_halt; +my $max_monitor_wait; my $ssh_exec; my $scp_to_target; +my $scp_to_target_install; my $power_off; my $grub_menu; +my $last_grub_menu; +my $grub_file; my $grub_number; +my $grub_reboot; +my $syslinux; +my $syslinux_path; +my $syslinux_label; my $target; my $make; +my $pre_install; my $post_install; my $no_install; my $noclean; @@ -91,18 +129,29 @@ my $minconfig; my $start_minconfig; my $start_minconfig_defined; my $output_minconfig; +my $minconfig_type; +my $use_output_minconfig; +my $warnings_file; my $ignore_config; +my $ignore_errors; my $addconfig; my $in_bisect = 0; -my $bisect_bad = ""; +my $bisect_bad_commit = ""; my $reverse_bisect; my $bisect_manual; my $bisect_skip; +my $bisect_tries; my $config_bisect_good; +my $bisect_ret_good; +my $bisect_ret_bad; +my $bisect_ret_skip; +my $bisect_ret_abort; +my $bisect_ret_default; my $in_patchcheck = 0; my $run_test; my $redirect; my $buildlog; +my $testlog; my $dmesg; my $monitor_fp; my $monitor_pid; @@ -112,11 +161,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 $close_console_signal; my $reboot_success_line; my $success_line; my $stop_after_success; @@ -124,23 +175,164 @@ my $stop_after_failure; my $stop_test_after; my $build_target; my $target_image; +my $checkout; my $localversion; my $iteration = 0; my $successes = 0; +my $bisect_good; +my $bisect_bad; +my $bisect_type; +my $bisect_start; +my $bisect_replay; +my $bisect_files; +my $bisect_reverse; +my $bisect_check; + +my $config_bisect; +my $config_bisect_type; +my $config_bisect_check; + +my $patchcheck_type; +my $patchcheck_start; +my $patchcheck_end; + +# set when a test is something other that just building or install +# which would require more options. +my $buildonly = 1; + +# tell build not to worry about warnings, even when WARNINGS_FILE is set +my $warnings_ok = 0; + +# set when creating a new config +my $newconfig = 0; + my %entered_configs; my %config_help; my %variable; + +# force_config is the list of configs that we force enabled (or disabled) +# in a .config file. The MIN_CONFIG and ADD_CONFIG configs. my %force_config; # do not force reboots on config problems my $no_reboot = 1; +# reboot on success +my $reboot_success = 0; + +my %option_map = ( + "MACHINE" => \$machine, + "SSH_USER" => \$ssh_user, + "TMP_DIR" => \$tmpdir, + "OUTPUT_DIR" => \$outputdir, + "BUILD_DIR" => \$builddir, + "TEST_TYPE" => \$test_type, + "PRE_KTEST" => \$pre_ktest, + "POST_KTEST" => \$post_ktest, + "PRE_TEST" => \$pre_test, + "POST_TEST" => \$post_test, + "BUILD_TYPE" => \$build_type, + "BUILD_OPTIONS" => \$build_options, + "PRE_BUILD" => \$pre_build, + "POST_BUILD" => \$post_build, + "PRE_BUILD_DIE" => \$pre_build_die, + "POST_BUILD_DIE" => \$post_build_die, + "POWER_CYCLE" => \$power_cycle, + "REBOOT" => \$reboot, + "BUILD_NOCLEAN" => \$noclean, + "MIN_CONFIG" => \$minconfig, + "OUTPUT_MIN_CONFIG" => \$output_minconfig, + "START_MIN_CONFIG" => \$start_minconfig, + "MIN_CONFIG_TYPE" => \$minconfig_type, + "USE_OUTPUT_MIN_CONFIG" => \$use_output_minconfig, + "WARNINGS_FILE" => \$warnings_file, + "IGNORE_CONFIG" => \$ignore_config, + "TEST" => \$run_test, + "ADD_CONFIG" => \$addconfig, + "REBOOT_TYPE" => \$reboot_type, + "GRUB_MENU" => \$grub_menu, + "GRUB_FILE" => \$grub_file, + "GRUB_REBOOT" => \$grub_reboot, + "SYSLINUX" => \$syslinux, + "SYSLINUX_PATH" => \$syslinux_path, + "SYSLINUX_LABEL" => \$syslinux_label, + "PRE_INSTALL" => \$pre_install, + "POST_INSTALL" => \$post_install, + "NO_INSTALL" => \$no_install, + "REBOOT_SCRIPT" => \$reboot_script, + "REBOOT_ON_ERROR" => \$reboot_on_error, + "SWITCH_TO_GOOD" => \$switch_to_good, + "SWITCH_TO_TEST" => \$switch_to_test, + "POWEROFF_ON_ERROR" => \$poweroff_on_error, + "REBOOT_ON_SUCCESS" => \$reboot_on_success, + "DIE_ON_FAILURE" => \$die_on_failure, + "POWER_OFF" => \$power_off, + "POWERCYCLE_AFTER_REBOOT" => \$powercycle_after_reboot, + "POWEROFF_AFTER_HALT" => \$poweroff_after_halt, + "MAX_MONITOR_WAIT" => \$max_monitor_wait, + "SLEEP_TIME" => \$sleep_time, + "BISECT_SLEEP_TIME" => \$bisect_sleep_time, + "PATCHCHECK_SLEEP_TIME" => \$patchcheck_sleep_time, + "IGNORE_WARNINGS" => \$ignore_warnings, + "IGNORE_ERRORS" => \$ignore_errors, + "BISECT_MANUAL" => \$bisect_manual, + "BISECT_SKIP" => \$bisect_skip, + "BISECT_TRIES" => \$bisect_tries, + "CONFIG_BISECT_GOOD" => \$config_bisect_good, + "BISECT_RET_GOOD" => \$bisect_ret_good, + "BISECT_RET_BAD" => \$bisect_ret_bad, + "BISECT_RET_SKIP" => \$bisect_ret_skip, + "BISECT_RET_ABORT" => \$bisect_ret_abort, + "BISECT_RET_DEFAULT" => \$bisect_ret_default, + "STORE_FAILURES" => \$store_failures, + "STORE_SUCCESSES" => \$store_successes, + "TEST_NAME" => \$test_name, + "TIMEOUT" => \$timeout, + "BOOTED_TIMEOUT" => \$booted_timeout, + "CONSOLE" => \$console, + "CLOSE_CONSOLE_SIGNAL" => \$close_console_signal, + "DETECT_TRIPLE_FAULT" => \$detect_triplefault, + "SUCCESS_LINE" => \$success_line, + "REBOOT_SUCCESS_LINE" => \$reboot_success_line, + "STOP_AFTER_SUCCESS" => \$stop_after_success, + "STOP_AFTER_FAILURE" => \$stop_after_failure, + "STOP_TEST_AFTER" => \$stop_test_after, + "BUILD_TARGET" => \$build_target, + "SSH_EXEC" => \$ssh_exec, + "SCP_TO_TARGET" => \$scp_to_target, + "SCP_TO_TARGET_INSTALL" => \$scp_to_target_install, + "CHECKOUT" => \$checkout, + "TARGET_IMAGE" => \$target_image, + "LOCALVERSION" => \$localversion, + + "BISECT_GOOD" => \$bisect_good, + "BISECT_BAD" => \$bisect_bad, + "BISECT_TYPE" => \$bisect_type, + "BISECT_START" => \$bisect_start, + "BISECT_REPLAY" => \$bisect_replay, + "BISECT_FILES" => \$bisect_files, + "BISECT_REVERSE" => \$bisect_reverse, + "BISECT_CHECK" => \$bisect_check, + + "CONFIG_BISECT" => \$config_bisect, + "CONFIG_BISECT_TYPE" => \$config_bisect_type, + "CONFIG_BISECT_CHECK" => \$config_bisect_check, + + "PATCHCHECK_TYPE" => \$patchcheck_type, + "PATCHCHECK_START" => \$patchcheck_start, + "PATCHCHECK_END" => \$patchcheck_end, +); + +# Options may be used by other options, record them. +my %used_options; + # 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" @@ -150,11 +342,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" @@ -162,6 +358,11 @@ $config_help{"BUILD_TARGET"} = << "EOF" (relative to OUTPUT_DIR) EOF ; +$config_help{"BUILD_OPTIONS"} = << "EOF" + Options to add to \"make\" when building. + i.e. -j20 +EOF + ; $config_help{"TARGET_IMAGE"} = << "EOF" The place to put your image on the test machine. EOF @@ -194,7 +395,7 @@ EOF ; $config_help{"REBOOT_TYPE"} = << "EOF" Way to reboot the box to the test kernel. - Only valid options so far are "grub" and "script". + Only valid options so far are "grub", "grub2", "syslinux", and "script". If you specify grub, it will assume grub version 1 and will search in /boot/grub/menu.lst for the title \$GRUB_MENU @@ -204,11 +405,19 @@ $config_help{"REBOOT_TYPE"} = << "EOF" The entry in /boot/grub/menu.lst must be entered in manually. The test will not modify that file. + + If you specify grub2, then you also need to specify both \$GRUB_MENU + and \$GRUB_FILE. + + If you specify syslinux, then you may use SYSLINUX to define the syslinux + command (defaults to extlinux), and SYSLINUX_PATH to specify the path to + the syslinux install (defaults to /boot/extlinux). But you have to specify + SYSLINUX_LABEL to define the label to boot to for the test kernel. EOF ; $config_help{"GRUB_MENU"} = << "EOF" The grub title name for the test kernel to boot - (Only mandatory if REBOOT_TYPE = grub) + (Only mandatory if REBOOT_TYPE = grub or grub2) Note, ktest.pl will not update the grub menu.lst, you need to manually add an option for the test. ktest.pl will search @@ -219,6 +428,22 @@ $config_help{"GRUB_MENU"} = << "EOF" title Test Kernel kernel vmlinuz-test GRUB_MENU = Test Kernel + + For grub2, a search of \$GRUB_FILE is performed for the lines + that begin with "menuentry". It will not detect submenus. The + menu must be a non-nested menu. Add the quotes used in the menu + to guarantee your selection, as the first menuentry with the content + of \$GRUB_MENU that is found will be used. +EOF + ; +$config_help{"GRUB_FILE"} = << "EOF" + If grub2 is used, the full path for the grub.cfg file is placed + here. Use something like /boot/grub2/grub.cfg to search. +EOF + ; +$config_help{"SYSLINUX_LABEL"} = << "EOF" + If syslinux is used, the label that boots the target kernel must + be specified with SYSLINUX_LABEL. EOF ; $config_help{"REBOOT_SCRIPT"} = << "EOF" @@ -227,20 +452,57 @@ $config_help{"REBOOT_SCRIPT"} = << "EOF" EOF ; -sub read_yn { - my ($prompt) = @_; +sub _logit { + if (defined($opt{"LOG_FILE"})) { + open(OUT, ">> $opt{LOG_FILE}") or die "Can't write to $opt{LOG_FILE}"; + print OUT @_; + close(OUT); + } +} + +sub logit { + if (defined($opt{"LOG_FILE"})) { + _logit @_; + } else { + print @_; + } +} + +sub doprint { + print @_; + _logit @_; +} + +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; @@ -248,6 +510,18 @@ 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; @@ -261,7 +535,7 @@ sub get_ktest_config { for (;;) { print "$config = "; - if (defined($default{$config})) { + if (defined($default{$config}) && length($default{$config})) { print "\[$default{$config}\] "; } $ans = <STDIN>; @@ -274,22 +548,37 @@ sub get_ktest_config { next; } } - $entered_configs{$config} = process_variables($ans); + $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"); + + if ($newconfig) { + get_ktest_config("BUILD_OPTIONS"); + } + + # options required for other than just building a kernel + if (!$buildonly) { + get_ktest_config("POWER_CYCLE"); + get_ktest_config("CONSOLE"); + } + + # options required for install and more + if ($buildonly != 1) { + get_ktest_config("SSH_USER"); + get_ktest_config("BUILD_TARGET"); + get_ktest_config("TARGET_IMAGE"); + } + get_ktest_config("LOCALVERSION"); + return if ($buildonly); + my $rtype = $opt{"REBOOT_TYPE"}; if (!defined($rtype)) { @@ -303,8 +592,15 @@ sub get_ktest_configs { if ($rtype eq "grub") { get_ktest_config("GRUB_MENU"); - } else { - get_ktest_config("REBOOT_SCRIPT"); + } + + if ($rtype eq "grub2") { + get_ktest_config("GRUB_MENU"); + get_ktest_config("GRUB_FILE"); + } + + if ($rtype eq "syslinux") { + get_ktest_config("SYSLINUX_LABEL"); } } @@ -334,6 +630,10 @@ sub process_variables { } else { # put back the origin piece. $retval = "$retval\$\{$var\}"; + # This could be an option that is used later, save + # it so we don't warn if this option is not one of + # ktests options. + $used_options{$var} = 1; } $value = $end; } @@ -348,6 +648,31 @@ sub process_variables { sub set_value { my ($lvalue, $rvalue, $override, $overrides, $name) = @_; + my $prvalue = process_variables($rvalue); + + if ($buildonly && $lvalue =~ /^TEST_TYPE(\[.*\])?$/ && $prvalue ne "build") { + # Note if a test is something other than build, then we + # will need other manditory options. + if ($prvalue ne "install") { + # for bisect, we need to check BISECT_TYPE + if ($prvalue ne "bisect") { + $buildonly = 0; + } + } else { + # install still limits some manditory options. + $buildonly = 2; + } + } + + if ($buildonly && $lvalue =~ /^BISECT_TYPE(\[.*\])?$/ && $prvalue ne "build") { + if ($prvalue ne "install") { + $buildonly = 0; + } else { + # install still limits some manditory options. + $buildonly = 2; + } + } + if (defined($opt{$lvalue})) { if (!$override || defined(${$overrides}{$lvalue})) { my $extra = ""; @@ -356,16 +681,31 @@ sub set_value { } die "$name: $.: Option $lvalue defined more than once!\n$extra"; } - ${$overrides}{$lvalue} = $rvalue; + ${$overrides}{$lvalue} = $prvalue; } if ($rvalue =~ /^\s*$/) { delete $opt{$lvalue}; } else { - $rvalue = process_variables($rvalue); - $opt{$lvalue} = $rvalue; + $opt{$lvalue} = $prvalue; } } +sub set_eval { + my ($lvalue, $rvalue, $name) = @_; + + my $prvalue = process_variables($rvalue); + my $arr; + + if (defined($evals{$lvalue})) { + $arr = $evals{$lvalue}; + } else { + $arr = []; + $evals{$lvalue} = $arr; + } + + push @{$arr}, $rvalue; +} + sub set_variable { my ($lvalue, $rvalue) = @_; @@ -392,6 +732,10 @@ sub process_compare { return $lval eq $rval; } elsif ($cmp eq "!=") { return $lval ne $rval; + } elsif ($cmp eq "=~") { + return $lval =~ m/$rval/; + } elsif ($cmp eq "!~") { + return $lval !~ m/$rval/; } my $statement = "$lval $cmp $rval"; @@ -447,7 +791,7 @@ sub process_expression { } } - if ($val =~ /(.*)(==|\!=|>=|<=|>|<)(.*)/) { + if ($val =~ /(.*)(==|\!=|>=|<=|>|<|=~|\!~)(.*)/) { my $ret = process_compare($1, $2, $3); if ($ret < 0) { die "$name: $.: Unable to process comparison\n"; @@ -605,7 +949,9 @@ sub __read_config { if ($rest =~ /\sIF\s+(.*)/) { # May be a ELSE IF section. - if (!process_if($name, $1)) { + if (process_if($name, $1)) { + $if_set = 1; + } else { $skip = 1; } $rest = ""; @@ -645,6 +991,20 @@ sub __read_config { $test_case = 1; } + } elsif (/^\s*([A-Z_\[\]\d]+)\s*=~\s*(.*?)\s*$/) { + + next if ($skip); + + my $lvalue = $1; + my $rvalue = $2; + + if ($default || $lvalue =~ /\[\d+\]$/) { + set_eval($lvalue, $rvalue, $name); + } else { + my $val = "$lvalue\[$test_num\]"; + set_eval($val, $rvalue, $name); + } + } elsif (/^\s*([A-Z_\[\]\d]+)\s*=\s*(.*?)\s*$/) { next if ($skip); @@ -712,6 +1072,15 @@ sub __read_config { 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) = @_; @@ -726,10 +1095,7 @@ sub read_config { # 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 @@ -739,10 +1105,41 @@ sub read_config { $opt{$default} = $default{$default}; } } + + if ($opt{"IGNORE_UNUSED"} == 1) { + return; + } + + my %not_used; + + # check if there are any stragglers (typos?) + foreach my $option (keys %opt) { + my $op = $option; + # remove per test labels. + $op =~ s/\[.*\]//; + if (!exists($option_map{$op}) && + !exists($default{$op}) && + !exists($used_options{$op})) { + $not_used{$op} = 1; + } + } + + if (%not_used) { + my $s = "s are"; + $s = " is" if (keys %not_used == 1); + print "The following option$s not used; could be a typo:\n"; + foreach my $option (keys %not_used) { + print "$option\n"; + } + print "Set IGRNORE_UNUSED = 1 to have ktest ignore unused variables\n"; + if (!read_yn "Do you want to continue?") { + exit -1; + } + } } sub __eval_option { - my ($option, $i) = @_; + my ($name, $option, $i) = @_; # Add space to evaluate the character before $ $option = " $option"; @@ -774,7 +1171,11 @@ sub __eval_option { my $o = "$var\[$i\]"; my $parento = "$var\[$parent\]"; - if (defined($opt{$o})) { + # If a variable contains itself, use the default var + if (($var eq $name) && defined($opt{$var})) { + $o = $opt{$var}; + $retval = "$retval$o"; + } elsif (defined($opt{$o})) { $o = $opt{$o}; $retval = "$retval$o"; } elsif ($repeated && defined($opt{$parento})) { @@ -783,6 +1184,10 @@ sub __eval_option { } elsif (defined($opt{$var})) { $o = $opt{$var}; $retval = "$retval$o"; + } elsif ($var eq "KERNEL_VERSION" && defined($make)) { + # special option KERNEL_VERSION uses kernel version + get_version(); + $retval = "$retval$version"; } else { $retval = "$retval\$\{$var\}"; } @@ -797,8 +1202,35 @@ sub __eval_option { return $retval; } +sub process_evals { + my ($name, $option, $i) = @_; + + my $option_name = "$name\[$i\]"; + my $ev; + + my $old_option = $option; + + if (defined($evals{$option_name})) { + $ev = $evals{$option_name}; + } elsif (defined($evals{$name})) { + $ev = $evals{$name}; + } else { + return $option; + } + + for my $e (@{$ev}) { + eval "\$option =~ $e"; + } + + if ($option ne $old_option) { + doprint("$name changed from '$old_option' to '$option'\n"); + } + + return $option; +} + sub eval_option { - my ($option, $i) = @_; + my ($name, $option, $i) = @_; my $prev = ""; @@ -814,31 +1246,12 @@ sub eval_option { "Check for recursive variables\n"; } $prev = $option; - $option = __eval_option($option, $i); + $option = __eval_option($name, $option, $i); } - return $option; -} - -sub _logit { - if (defined($opt{"LOG_FILE"})) { - open(OUT, ">> $opt{LOG_FILE}") or die "Can't write to $opt{LOG_FILE}"; - print OUT @_; - close(OUT); - } -} - -sub logit { - if (defined($opt{"LOG_FILE"})) { - _logit @_; - } else { - print @_; - } -} + $option = process_evals($name, $option, $i); -sub doprint { - print @_; - _logit @_; + return $option; } sub run_command; @@ -849,6 +1262,9 @@ sub wait_for_monitor; sub reboot { my ($time) = @_; + # Make sure everything has been written to disk + run_ssh("sync"); + if (defined($time)) { start_monitor; # flush out current monitor @@ -868,11 +1284,38 @@ sub reboot { } if (defined($time)) { + + # We only want to get to the new kernel, don't fail + # if we stumble over a call trace. + my $save_ignore_errors = $ignore_errors; + $ignore_errors = 1; + + # Look for the good kernel to boot + if (wait_for_monitor($time, "Linux version")) { + # reboot got stuck? + doprint "Reboot did not finish. Forcing power cycle\n"; + run_command "$power_cycle"; + } + + $ignore_errors = $save_ignore_errors; + + # Still need to wait for the reboot to finish wait_for_monitor($time, $reboot_success_line); + end_monitor; } } +sub reboot_to_good { + my ($time) = @_; + + if (defined($switch_to_good)) { + run_command $switch_to_good; + } + + reboot $time; +} + sub do_not_reboot { my $i = $iteration; @@ -889,7 +1332,7 @@ sub dodie { if ($reboot_on_error && !do_not_reboot) { doprint "REBOOTING\n"; - reboot; + reboot_to_good; } elsif ($poweroff_on_error && defined($power_off)) { doprint "POWERING OFF\n"; @@ -923,7 +1366,7 @@ sub close_console { my ($fp, $pid) = @_; doprint "kill child process $pid\n"; - kill 2, $pid; + kill $close_console_signal, $pid; print "closing!\n"; close($fp); @@ -942,6 +1385,7 @@ sub start_monitor { } sub end_monitor { + return if (!defined $console); if (--$monitor_cnt) { return; } @@ -953,6 +1397,11 @@ sub wait_for_monitor { my $full_line = ""; my $line; my $booted = 0; + my $start_time = time; + my $skip_call_trace = 0; + my $bug = 0; + my $bug_ignored = 0; + my $now; doprint "** Wait for monitor to settle down **\n"; @@ -968,15 +1417,84 @@ sub wait_for_monitor { $booted = 1; } + if ($full_line =~ /\[ backtrace testing \]/) { + $skip_call_trace = 1; + } + + if ($full_line =~ /call trace:/i) { + if (!$bug && !$skip_call_trace) { + if ($ignore_errors) { + $bug_ignored = 1; + } else { + $bug = 1; + } + } + } + + if ($full_line =~ /\[ end of backtrace testing \]/) { + $skip_call_trace = 0; + } + + if ($full_line =~ /Kernel panic -/) { + $bug = 1; + } + if ($line =~ /\n/) { $full_line = ""; } + $now = time; + if ($now - $start_time >= $max_monitor_wait) { + doprint "Exiting monitor flush due to hitting MAX_MONITOR_WAIT\n"; + return 1; + } } print "** Monitor flushed **\n"; + return $bug; +} + +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 (defined($post_test)) { + run_command $post_test; + } + if ($die_on_failure) { dodie @_; } @@ -988,7 +1506,7 @@ sub fail { # no need to reboot for just building. if (!do_not_reboot) { doprint "REBOOTING\n"; - reboot $sleep_time; + reboot_to_good $sleep_time; } my $name = ""; @@ -1003,38 +1521,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; } @@ -1095,8 +1584,7 @@ sub run_ssh { } sub run_scp { - my ($src, $dst) = @_; - my $cp_scp = $scp_to_target; + my ($src, $dst, $cp_scp) = @_; $cp_scp =~ s/\$SRC_FILE/$src/g; $cp_scp =~ s/\$DST_FILE/$dst/g; @@ -1104,12 +1592,70 @@ sub run_scp { return run_command "$cp_scp"; } +sub run_scp_install { + my ($src, $dst) = @_; + + my $cp_scp = $scp_to_target_install; + + return run_scp($src, $dst, $cp_scp); +} + +sub run_scp_mod { + my ($src, $dst) = @_; + + my $cp_scp = $scp_to_target; + + return run_scp($src, $dst, $cp_scp); +} + +sub get_grub2_index { + + return if (defined($grub_number) && defined($last_grub_menu) && + $last_grub_menu eq $grub_menu && defined($last_machine) && + $last_machine eq $machine); + + doprint "Find grub2 menu ... "; + $grub_number = -1; + + my $ssh_grub = $ssh_exec; + $ssh_grub =~ s,\$SSH_COMMAND,cat $grub_file,g; + + open(IN, "$ssh_grub |") + or die "unable to get $grub_file"; + + my $found = 0; + + while (<IN>) { + if (/^menuentry.*$grub_menu/) { + $grub_number++; + $found = 1; + last; + } elsif (/^menuentry\s/) { + $grub_number++; + } + } + close(IN); + + die "Could not find '$grub_menu' in $grub_file on $machine" + if (!$found); + doprint "$grub_number\n"; + $last_grub_menu = $grub_menu; + $last_machine = $machine; +} + sub get_grub_index { + if ($reboot_type eq "grub2") { + get_grub2_index; + return; + } + if ($reboot_type ne "grub") { return; } - return if (defined($grub_number)); + return if (defined($grub_number) && defined($last_grub_menu) && + $last_grub_menu eq $grub_menu && defined($last_machine) && + $last_machine eq $machine); doprint "Find grub menu ... "; $grub_number = -1; @@ -1136,6 +1682,8 @@ sub get_grub_index { die "Could not find '$grub_menu' in /boot/grub/menu on $machine" if (!$found); doprint "$grub_number\n"; + $last_grub_menu = $grub_menu; + $last_machine = $machine; } sub wait_for_input @@ -1152,7 +1700,7 @@ sub wait_for_input $rin = ''; vec($rin, fileno($fp), 1) = 1; - $ready = select($rin, undef, undef, $time); + ($ready, $time) = select($rin, undef, undef, $time); $line = ""; @@ -1170,13 +1718,20 @@ sub wait_for_input } sub reboot_to { + if (defined($switch_to_test)) { + run_command $switch_to_test; + } + if ($reboot_type eq "grub") { run_ssh "'(echo \"savedefault --default=$grub_number --once\" | grub --batch)'"; - reboot; - return; + } elsif ($reboot_type eq "grub2") { + run_ssh "$grub_reboot $grub_number"; + } elsif ($reboot_type eq "syslinux") { + run_ssh "$syslinux --once \\\"$syslinux_label\\\" $syslinux_path"; + } elsif (defined $reboot_script) { + run_command "$reboot_script"; } - - run_command "$reboot_script"; + reboot; } sub get_sha1 { @@ -1203,6 +1758,7 @@ sub get_sha1 { sub monitor { my $booted = 0; my $bug = 0; + my $bug_ignored = 0; my $skip_call_trace = 0; my $loops; @@ -1275,8 +1831,12 @@ sub monitor { if ($full_line =~ /call trace:/i) { if (!$bug && !$skip_call_trace) { - $bug = 1; - $failure_start = time; + if ($ignore_errors) { + $bug_ignored = 1; + } else { + $bug = 1; + $failure_start = time; + } } } @@ -1306,7 +1866,7 @@ sub monitor { # We already booted into the kernel we are testing, # but now we booted into another kernel? # Consider this a triple fault. - doprint "Aleady booted in Linux kernel $version, but now\n"; + doprint "Already booted in Linux kernel $version, but now\n"; doprint "we booted into Linux kernel $1.\n"; doprint "Assuming that this is a triple fault.\n"; doprint "To disable this: set DETECT_TRIPLE_FAULT to 0\n"; @@ -1338,24 +1898,53 @@ sub monitor { fail "failed - never got a boot prompt." and return 0; } + if ($bug_ignored) { + doprint "WARNING: Call Trace detected but ignored due to IGNORE_ERRORS=1\n"; + } + return 1; } +sub eval_kernel_version { + my ($option) = @_; + + $option =~ s/\$KERNEL_VERSION/$version/g; + + return $option; +} + sub do_post_install { return if (!defined($post_install)); - my $cp_post_install = $post_install; - $cp_post_install =~ s/\$KERNEL_VERSION/$version/g; + my $cp_post_install = eval_kernel_version $post_install; run_command "$cp_post_install" or dodie "Failed to run post install"; } +# Sometimes the reboot fails, and will hang. We try to ssh to the box +# and if we fail, we force another reboot, that should powercycle it. +sub test_booted { + if (!run_ssh "echo testing connection") { + reboot $sleep_time; + } +} + sub install { return if ($no_install); - run_scp "$outputdir/$build_target", "$target_image" or + if (defined($pre_install)) { + my $cp_pre_install = eval_kernel_version $pre_install; + run_command "$cp_pre_install" or + dodie "Failed to run pre install"; + } + + my $cp_target = eval_kernel_version $target_image; + + test_booted; + + run_scp_install "$outputdir/$build_target", "$cp_target" or dodie "failed to copy image"; my $install_mods = 0; @@ -1365,8 +1954,10 @@ sub install { open(IN, "$output_config") or dodie("Can't read config file"); while (<IN>) { if (/CONFIG_MODULES(=y)?/) { - $install_mods = 1 if (defined($1)); - last; + if (defined($1)) { + $install_mods = 1; + last; + } } } close(IN); @@ -1377,7 +1968,7 @@ sub install { return; } - run_command "$make INSTALL_MOD_PATH=$tmpdir modules_install" or + run_command "$make INSTALL_MOD_STRIP=1 INSTALL_MOD_PATH=$tmpdir modules_install" or dodie "Failed to install modules"; my $modlib = "/lib/modules/$version"; @@ -1390,7 +1981,7 @@ sub install { run_command "cd $tmpdir && tar -cjf $modtar lib/modules/$version" or dodie "making tarball"; - run_scp "$tmpdir/$modtar", "/tmp" or + run_scp_mod "$tmpdir/$modtar", "/tmp" or dodie "failed to copy modules"; unlink "$tmpdir/$modtar"; @@ -1405,31 +1996,112 @@ sub install { sub get_version { # get the release name + return if ($have_version); doprint "$make kernelrelease ... "; $version = `$make kernelrelease | tail -1`; chomp($version); doprint "$version\n"; + $have_version = 1; } sub start_monitor_and_boot { # Make sure the stable kernel has finished booting - start_monitor; - wait_for_monitor 5; - end_monitor; + + # Install bisects, don't need console + if (defined $console) { + start_monitor; + wait_for_monitor 5; + end_monitor; + } get_grub_index; get_version; install; - start_monitor; + start_monitor if (defined $console); return monitor; } +my $check_build_re = ".*:.*(warning|error|Error):.*"; +my $utf8_quote = "\\x{e2}\\x{80}(\\x{98}|\\x{99})"; + +sub process_warning_line { + my ($line) = @_; + + chomp $line; + + # for distcc heterogeneous systems, some compilers + # do things differently causing warning lines + # to be slightly different. This makes an attempt + # to fixe those issues. + + # chop off the index into the line + # using distcc, some compilers give different indexes + # depending on white space + $line =~ s/^(\s*\S+:\d+:)\d+/$1/; + + # Some compilers use UTF-8 extended for quotes and some don't. + $line =~ s/$utf8_quote/'/g; + + return $line; +} + +# Read buildlog and check against warnings file for any +# new warnings. +# +# Returns 1 if OK +# 0 otherwise sub check_buildlog { + return 1 if (!defined $warnings_file); + + my %warnings_list; + + # Failed builds should not reboot the target + my $save_no_reboot = $no_reboot; + $no_reboot = 1; + + if (-f $warnings_file) { + open(IN, $warnings_file) or + dodie "Error opening $warnings_file"; + + while (<IN>) { + if (/$check_build_re/) { + my $warning = process_warning_line $_; + + $warnings_list{$warning} = 1; + } + } + close(IN); + } + + # If warnings file didn't exist, and WARNINGS_FILE exist, + # then we fail on any warning! + + open(IN, $buildlog) or dodie "Can't open $buildlog"; + while (<IN>) { + if (/$check_build_re/) { + my $warning = process_warning_line $_; + + if (!defined $warnings_list{$warning}) { + fail "New warning found (not in $warnings_file)\n$_\n"; + $no_reboot = $save_no_reboot; + return 0; + } + } + } + $no_reboot = $save_no_reboot; + close(IN); +} + +sub check_patch_buildlog { my ($patch) = @_; my @files = `git show $patch | diffstat -l`; + foreach my $file (@files) { + chomp $file; + } + open(IN, "git show $patch |") or dodie "failed to show $patch"; while (<IN>) { @@ -1496,12 +2168,16 @@ sub make_oldconfig { apply_min_config; } - if (!run_command "$make oldnoconfig") { - # Perhaps oldnoconfig doesn't exist in this version of the kernel - # try a yes '' | oldconfig - doprint "oldnoconfig failed, trying yes '' | make oldconfig\n"; - run_command "yes '' | $make oldconfig" or - dodie "failed make config oldconfig"; + if (!run_command "$make olddefconfig") { + # Perhaps olddefconfig doesn't exist in this version of the kernel + # try oldnoconfig + doprint "olddefconfig failed, trying make oldnoconfig\n"; + if (!run_command "$make oldnoconfig") { + doprint "oldnoconfig failed, trying yes '' | make oldconfig\n"; + # try a yes '' | oldconfig + run_command "yes '' | $make oldconfig" or + dodie "failed make config oldconfig"; + } } } @@ -1509,6 +2185,7 @@ sub make_oldconfig { sub load_force_config { my ($config) = @_; + doprint "Loading force configs from $config\n"; open(IN, $config) or dodie "failed to read $config"; while (<IN>) { @@ -1531,6 +2208,9 @@ sub build { my $save_no_reboot = $no_reboot; $no_reboot = 1; + # Calculate a new version from here. + $have_version = 0; + if (defined($pre_build)) { my $ret = run_command $pre_build; if (!$ret && defined($pre_build_die) && @@ -1548,7 +2228,7 @@ sub build { # old config can ask questions if ($type eq "oldconfig") { - $type = "oldnoconfig"; + $type = "olddefconfig"; # allow for empty configs run_command "touch $output_config"; @@ -1578,7 +2258,7 @@ sub build { load_force_config($minconfig); } - if ($type ne "oldnoconfig") { + if ($type ne "olddefconfig") { run_command "$make $type" or dodie "failed make config"; } @@ -1590,6 +2270,9 @@ sub build { undef $redirect; if (defined($post_build)) { + # Because a post build may change the kernel version + # do it now. + get_version; my $ret = run_command $post_build; if (!$ret && defined($post_build_die) && $post_build_die) { @@ -1626,6 +2309,10 @@ sub halt { sub success { my ($i) = @_; + if (defined($post_test)) { + run_command $post_test; + } + $successes++; my $name = ""; @@ -1640,9 +2327,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 $sleep_time; + reboot_to_good $sleep_time; } } @@ -1669,7 +2360,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; } @@ -1685,6 +2379,7 @@ sub do_run_test { my $line; my $full_line; my $bug = 0; + my $bug_ignored = 0; wait_for_monitor 1; @@ -1709,7 +2404,11 @@ sub do_run_test { doprint $line; if ($full_line =~ /call trace:/i) { - $bug = 1; + if ($ignore_errors) { + $bug_ignored = 1; + } else { + $bug = 1; + } } if ($full_line =~ /Kernel panic -/) { @@ -1722,6 +2421,10 @@ sub do_run_test { } } while (!$child_done && !$bug); + if (!$bug && $bug_ignored) { + doprint "WARNING: Call Trace detected but ignored due to IGNORE_ERRORS=1\n"; + } + if ($bug) { my $failure_start = time; my $now; @@ -1744,6 +2447,43 @@ sub do_run_test { waitpid $child_pid, 0; $child_exit = $?; + if (!$bug && $in_bisect) { + if (defined($bisect_ret_good)) { + if ($child_exit == $bisect_ret_good) { + return 1; + } + } + if (defined($bisect_ret_skip)) { + if ($child_exit == $bisect_ret_skip) { + return -1; + } + } + if (defined($bisect_ret_abort)) { + if ($child_exit == $bisect_ret_abort) { + fail "test abort" and return -2; + } + } + if (defined($bisect_ret_bad)) { + if ($child_exit == $bisect_ret_skip) { + return 0; + } + } + if (defined($bisect_ret_default)) { + if ($bisect_ret_default eq "good") { + return 1; + } elsif ($bisect_ret_default eq "bad") { + return 0; + } elsif ($bisect_ret_default eq "skip") { + return -1; + } elsif ($bisect_ret_default eq "abort") { + return -2; + } else { + fail "unknown default action: $bisect_ret_default" + and return -2; + } + } + } + if ($bug || $child_exit) { return 0 if $in_bisect; fail "test failed" and return 0; @@ -1770,7 +2510,7 @@ sub run_git_bisect { if ($output =~ m/^(Bisecting: .*\(roughly \d+ steps?\))\s+\[([[:xdigit:]]+)\]/) { doprint "$1 [$2]\n"; } elsif ($output =~ m/^([[:xdigit:]]+) is the first bad commit/) { - $bisect_bad = $1; + $bisect_bad_commit = $1; doprint "Found bad commit... $1\n"; return 0; } else { @@ -1783,7 +2523,7 @@ sub run_git_bisect { sub bisect_reboot { doprint "Reboot and sleep $bisect_sleep_time seconds\n"; - reboot $bisect_sleep_time; + reboot_to_good $bisect_sleep_time; } # returns 1 on success, 0 on failure, -1 on skip @@ -1847,14 +2587,31 @@ sub run_bisect { $buildtype = "useconfig:$minconfig"; } - my $ret = run_bisect_test $type, $buildtype; + # If the user sets bisect_tries to less than 1, then no tries + # is a success. + my $ret = 1; - if ($bisect_manual) { + # Still let the user manually decide that though. + if ($bisect_tries < 1 && $bisect_manual) { $ret = answer_bisect; } + for (my $i = 0; $i < $bisect_tries; $i++) { + if ($bisect_tries > 1) { + my $t = $i + 1; + doprint("Running bisect trial $t of $bisect_tries:\n"); + } + $ret = run_bisect_test $type, $buildtype; + + if ($bisect_manual) { + $ret = answer_bisect; + } + + last if (!$ret); + } + # Are we looking for where it worked, not failed? - if ($reverse_bisect) { + if ($reverse_bisect && $ret >= 0) { $ret = !$ret; } @@ -1868,21 +2625,28 @@ 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) = @_; my $result; - die "BISECT_GOOD[$i] not defined\n" if (!defined($opt{"BISECT_GOOD[$i]"})); - die "BISECT_BAD[$i] not defined\n" if (!defined($opt{"BISECT_BAD[$i]"})); - die "BISECT_TYPE[$i] not defined\n" if (!defined($opt{"BISECT_TYPE[$i]"})); + die "BISECT_GOOD[$i] not defined\n" if (!defined($bisect_good)); + die "BISECT_BAD[$i] not defined\n" if (!defined($bisect_bad)); + die "BISECT_TYPE[$i] not defined\n" if (!defined($bisect_type)); - my $good = $opt{"BISECT_GOOD[$i]"}; - my $bad = $opt{"BISECT_BAD[$i]"}; - my $type = $opt{"BISECT_TYPE[$i]"}; - my $start = $opt{"BISECT_START[$i]"}; - my $replay = $opt{"BISECT_REPLAY[$i]"}; - my $start_files = $opt{"BISECT_FILES[$i]"}; + my $good = $bisect_good; + my $bad = $bisect_bad; + my $type = $bisect_type; + my $start = $bisect_start; + my $replay = $bisect_replay; + my $start_files = $bisect_files; if (defined($start_files)) { $start_files = " -- " . $start_files; @@ -1894,8 +2658,7 @@ sub bisect { $good = get_sha1($good); $bad = get_sha1($bad); - if (defined($opt{"BISECT_REVERSE[$i]"}) && - $opt{"BISECT_REVERSE[$i]"} == 1) { + if (defined($bisect_reverse) && $bisect_reverse == 1) { doprint "Performing a reverse bisect (bad is good, good is bad!)\n"; $reverse_bisect = 1; } else { @@ -1907,8 +2670,31 @@ sub bisect { $type = "boot"; } - my $check = $opt{"BISECT_CHECK[$i]"}; - if (defined($check) && $check ne "0") { + # Check if a bisect was running + my $bisect_start_file = "$builddir/.git/BISECT_START"; + + my $check = $bisect_check; + 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"); @@ -1973,14 +2759,28 @@ sub bisect { run_command "git bisect reset" or dodie "could not reset git bisect"; - doprint "Bad commit was [$bisect_bad]\n"; + doprint "Bad commit was [$bisect_bad_commit]\n"; success $i; } +# config_ignore holds the configs that were set (or unset) for +# a good config and we will ignore these configs for the rest +# of a config bisect. These configs stay as they were. my %config_ignore; + +# config_set holds what all configs were set as. my %config_set; +# config_off holds the set of configs that the bad config had disabled. +# We need to record them and set them in the .config when running +# olddefconfig, because olddefconfig keeps the defaults. +my %config_off; + +# config_off_tmp holds a set of configs to turn off for now +my @config_off_tmp; + +# config_list is the set of configs that are being tested my %config_list; my %null_config; @@ -2059,12 +2859,21 @@ sub create_config { } } + # turn off configs to keep off + foreach my $config (keys %config_off) { + print OUT "# $config is not set\n"; + } + + # turn off configs that should be off for now + foreach my $config (@config_off_tmp) { + print OUT "# $config is not set\n"; + } + foreach my $config (keys %config_ignore) { print OUT "$config_ignore{$config}\n"; } close(OUT); -# exit; make_oldconfig; } @@ -2129,7 +2938,7 @@ sub run_config_bisect { } doprint "***** RUN TEST ***\n"; - my $type = $opt{"CONFIG_BISECT_TYPE[$iteration]"}; + my $type = $config_bisect_type; my $ret; my %current_config; @@ -2141,6 +2950,13 @@ sub run_config_bisect { do { my @tophalf = @start_list[0 .. $half]; + # keep the bottom half off + if ($half < $#start_list) { + @config_off_tmp = @start_list[$half + 1 .. $#start_list]; + } else { + @config_off_tmp = (); + } + create_config @tophalf; read_current_config \%current_config; @@ -2157,7 +2973,11 @@ sub run_config_bisect { if (!$found) { # try the other half doprint "Top half produced no set configs, trying bottom half\n"; + + # keep the top half off + @config_off_tmp = @tophalf; @tophalf = @start_list[$half + 1 .. $#start_list]; + create_config @tophalf; read_current_config \%current_config; foreach my $config (@tophalf) { @@ -2233,7 +3053,7 @@ sub run_config_bisect { sub config_bisect { my ($i) = @_; - my $start_config = $opt{"CONFIG_BISECT[$i]"}; + my $start_config = $config_bisect; my $tmpconfig = "$tmpdir/use_config"; @@ -2262,7 +3082,7 @@ sub config_bisect { # read directly what we want to check my %config_check; open (IN, $output_config) - or dodie "faied to open $output_config"; + or dodie "failed to open $output_config"; while (<IN>) { if (/^((CONFIG\S*)=.*)/) { @@ -2295,6 +3115,10 @@ sub config_bisect { $added_configs{$2} = $1; $config_list{$2} = $1; } + } elsif (/^# ((CONFIG\S*).*)/) { + # Keep these configs disabled + $config_set{$2} = $1; + $config_off{$2} = $1; } } close(IN); @@ -2317,6 +3141,8 @@ sub config_bisect { my %config_test; my $once = 0; + @config_off_tmp = (); + # Sometimes kconfig does weird things. We must make sure # that the config we autocreate has everything we need # to test, otherwise we may miss testing configs, or @@ -2335,6 +3161,18 @@ sub config_bisect { } } my $ret; + + if (defined($config_bisect_check) && $config_bisect_check) { + doprint " Checking to make sure bad config with min config fails\n"; + create_config keys %config_list; + $ret = run_config_bisect_test $config_bisect_type; + if ($ret) { + doprint " FAILED! Bad config with min config boots fine\n"; + return -1; + } + doprint " Bad config with min config fails as expected\n"; + } + do { $ret = run_config_bisect; } while (!$ret); @@ -2346,29 +3184,29 @@ sub config_bisect { sub patchcheck_reboot { doprint "Reboot and sleep $patchcheck_sleep_time seconds\n"; - reboot $patchcheck_sleep_time; + reboot_to_good $patchcheck_sleep_time; } sub patchcheck { my ($i) = @_; die "PATCHCHECK_START[$i] not defined\n" - if (!defined($opt{"PATCHCHECK_START[$i]"})); + if (!defined($patchcheck_start)); die "PATCHCHECK_TYPE[$i] not defined\n" - if (!defined($opt{"PATCHCHECK_TYPE[$i]"})); + if (!defined($patchcheck_type)); - my $start = $opt{"PATCHCHECK_START[$i]"}; + my $start = $patchcheck_start; my $end = "HEAD"; - if (defined($opt{"PATCHCHECK_END[$i]"})) { - $end = $opt{"PATCHCHECK_END[$i]"}; + if (defined($patchcheck_end)) { + $end = $patchcheck_end; } # Get the true sha1's since we can use things like HEAD~3 $start = get_sha1($start); $end = get_sha1($end); - my $type = $opt{"PATCHCHECK_TYPE[$i]"}; + my $type = $patchcheck_type; # Can't have a test without having a test to run if ($type eq "test" && !defined($run_test)) { @@ -2428,11 +3266,13 @@ sub patchcheck { build "oldconfig" or return 0; } - - if (!defined($ignored_warnings{$sha1})) { - check_buildlog $sha1 or return 0; + # No need to do per patch checking if warnings file exists + if (!defined($warnings_file) && !defined($ignored_warnings{$sha1})) { + check_patch_buildlog $sha1 or return 0; } + check_buildlog or return 0; + next if ($type eq "build"); my $failed = 0; @@ -2727,7 +3567,7 @@ sub test_this_config { } # Remove this config from the list of configs - # do a make oldnoconfig and then read the resulting + # do a make olddefconfig and then read the resulting # .config to make sure it is missing the config that # we had before my %configs = %min_configs; @@ -2749,6 +3589,12 @@ sub test_this_config { sub make_min_config { my ($i) = @_; + my $type = $minconfig_type; + if ($type ne "boot" && $type ne "test") { + fail "Invalid MIN_CONFIG_TYPE '$minconfig_type'\n" . + " make_min_config works only with 'boot' and 'test'\n" and return; + } + if (!defined($output_minconfig)) { fail "OUTPUT_MIN_CONFIG not defined" and return; } @@ -2758,8 +3604,15 @@ sub make_min_config { # that instead. if (-f $output_minconfig && !$start_minconfig_defined) { print "$output_minconfig exists\n"; - if (read_yn " Use it as minconfig?") { + if (!defined($use_output_minconfig)) { + if (read_yn " Use it as minconfig?") { + $start_minconfig = $output_minconfig; + } + } elsif ($use_output_minconfig > 0) { + doprint "Using $output_minconfig as MIN_CONFIG\n"; $start_minconfig = $output_minconfig; + } else { + doprint "Set to still use MIN_CONFIG as starting point\n"; } } @@ -2905,9 +3758,16 @@ sub make_min_config { $in_bisect = 1; my $failed = 0; - build "oldconfig"; - start_monitor_and_boot or $failed = 1; - end_monitor; + build "oldconfig" or $failed = 1; + if (!$failed) { + start_monitor_and_boot or $failed = 1; + + if ($type eq "test" && !$failed) { + do_run_test or $failed = 1; + } + + end_monitor; + } $in_bisect = 0; @@ -2963,13 +3823,46 @@ sub make_min_config { } doprint "Reboot and wait $sleep_time seconds\n"; - reboot $sleep_time; + reboot_to_good $sleep_time; } success $i; return 1; } +sub make_warnings_file { + my ($i) = @_; + + if (!defined($warnings_file)) { + dodie "Must define WARNINGS_FILE for make_warnings_file test"; + } + + if ($build_type eq "nobuild") { + dodie "BUILD_TYPE can not be 'nobuild' for make_warnings_file test"; + } + + build $build_type or dodie "Failed to build"; + + open(OUT, ">$warnings_file") or dodie "Can't create $warnings_file"; + + open(IN, $buildlog) or dodie "Can't open $buildlog"; + while (<IN>) { + + # Some compilers use UTF-8 extended for quotes + # for distcc heterogeneous systems, this causes issues + s/$utf8_quote/'/g; + + if (/$check_build_re/) { + print OUT; + } + } + close(IN); + + close(OUT); + + success $i; +} + $#ARGV < 1 or die "ktest.pl version: $VERSION\n usage: ktest.pl config-file\n"; if ($#ARGV == 0) { @@ -2985,13 +3878,27 @@ if ($#ARGV == 0) { } if (! -f $ktest_config) { + $newconfig = 1; + 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 @@ -3001,7 +3908,7 @@ EOF read_config $ktest_config; if (defined($opt{"LOG_FILE"})) { - $opt{"LOG_FILE"} = eval_option($opt{"LOG_FILE"}, -1); + $opt{"LOG_FILE"} = eval_option("LOG_FILE", $opt{"LOG_FILE"}, -1); } # Append any configs entered in manually to the config file. @@ -3011,7 +3918,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}); } } @@ -3078,7 +3985,7 @@ sub set_test_option { my $option = __set_test_option($name, $i); return $option if (!defined($option)); - return eval_option($option, $i); + return eval_option($name, $option, $i); } # First we need to do is the builds @@ -3086,109 +3993,87 @@ for (my $i = 1; $i <= $opt{"NUM_TESTS"}; $i++) { # Do not reboot on failing test options $no_reboot = 1; + $reboot_success = 0; + + $have_version = 0; $iteration = $i; + undef %force_config; + my $makecmd = set_test_option("MAKE_CMD", $i); - $machine = set_test_option("MACHINE", $i); - $ssh_user = set_test_option("SSH_USER", $i); - $tmpdir = set_test_option("TMP_DIR", $i); $outputdir = set_test_option("OUTPUT_DIR", $i); $builddir = set_test_option("BUILD_DIR", $i); - $test_type = set_test_option("TEST_TYPE", $i); - $build_type = set_test_option("BUILD_TYPE", $i); - $build_options = set_test_option("BUILD_OPTIONS", $i); - $pre_build = set_test_option("PRE_BUILD", $i); - $post_build = set_test_option("POST_BUILD", $i); - $pre_build_die = set_test_option("PRE_BUILD_DIE", $i); - $post_build_die = set_test_option("POST_BUILD_DIE", $i); - $power_cycle = set_test_option("POWER_CYCLE", $i); - $reboot = set_test_option("REBOOT", $i); - $noclean = set_test_option("BUILD_NOCLEAN", $i); - $minconfig = set_test_option("MIN_CONFIG", $i); - $output_minconfig = set_test_option("OUTPUT_MIN_CONFIG", $i); - $start_minconfig = set_test_option("START_MIN_CONFIG", $i); - $ignore_config = set_test_option("IGNORE_CONFIG", $i); - $run_test = set_test_option("TEST", $i); - $addconfig = set_test_option("ADD_CONFIG", $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); - $die_on_failure = set_test_option("DIE_ON_FAILURE", $i); - $power_off = set_test_option("POWER_OFF", $i); - $powercycle_after_reboot = set_test_option("POWERCYCLE_AFTER_REBOOT", $i); - $poweroff_after_halt = set_test_option("POWEROFF_AFTER_HALT", $i); - $sleep_time = set_test_option("SLEEP_TIME", $i); - $bisect_sleep_time = set_test_option("BISECT_SLEEP_TIME", $i); - $patchcheck_sleep_time = set_test_option("PATCHCHECK_SLEEP_TIME", $i); - $ignore_warnings = set_test_option("IGNORE_WARNINGS", $i); - $bisect_manual = set_test_option("BISECT_MANUAL", $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); - $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); - $build_target = set_test_option("BUILD_TARGET", $i); - $ssh_exec = set_test_option("SSH_EXEC", $i); - $scp_to_target = set_test_option("SCP_TO_TARGET", $i); - $target_image = set_test_option("TARGET_IMAGE", $i); - $localversion = set_test_option("LOCALVERSION", $i); + + chdir $builddir || die "can't change directory to $builddir"; + + if (!-d $outputdir) { + mkpath($outputdir) or + die "can't create $outputdir"; + } + + $make = "$makecmd O=$outputdir"; + + # Load all the options into their mapped variable names + foreach my $opt (keys %option_map) { + ${$option_map{$opt}} = set_test_option($opt, $i); + } $start_minconfig_defined = 1; + # The first test may override the PRE_KTEST option + if (defined($pre_ktest) && $i == 1) { + doprint "\n"; + run_command $pre_ktest; + } + + # Any test can override the POST_KTEST option + # The last test takes precedence. + if (defined($post_ktest)) { + $final_post_ktest = $post_ktest; + } + if (!defined($start_minconfig)) { $start_minconfig_defined = 0; $start_minconfig = $minconfig; } - chdir $builddir || die "can't change directory to $builddir"; - - foreach my $dir ($tmpdir, $outputdir) { - if (!-d $dir) { - mkpath($dir) or - die "can't create $dir"; - } + if (!-d $tmpdir) { + mkpath($tmpdir) or + die "can't create $tmpdir"; } $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 ($reboot_type eq "grub2") { + dodie "GRUB_MENU not defined" if (!defined($grub_menu)); + dodie "GRUB_FILE not defined" if (!defined($grub_file)); + } elsif ($reboot_type eq "syslinux") { + dodie "SYSLINUX_LABEL not defined" if (!defined($syslinux_label)); + } } my $run_type = $build_type; if ($test_type eq "patchcheck") { - $run_type = $opt{"PATCHCHECK_TYPE[$i]"}; + $run_type = $patchcheck_type; } elsif ($test_type eq "bisect") { - $run_type = $opt{"BISECT_TYPE[$i]"}; + $run_type = $bisect_type; } elsif ($test_type eq "config_bisect") { - $run_type = $opt{"CONFIG_BISECT_TYPE[$i]"}; - } - - if ($test_type eq "make_min_config") { + $run_type = $config_bisect_type; + } elsif ($test_type eq "make_min_config") { + $run_type = ""; + } elsif ($test_type eq "make_warnings_file") { $run_type = ""; } @@ -3203,8 +4088,13 @@ for (my $i = 1; $i <= $opt{"NUM_TESTS"}; $i++) { doprint "\n\n"; doprint "RUNNING TEST $i of $opt{NUM_TESTS} with option $test_type $run_type$installme\n\n"; + if (defined($pre_test)) { + run_command $pre_test; + } + unlink $dmesg; unlink $buildlog; + unlink $testlog; if (defined($addconfig)) { my $min = $minconfig; @@ -3216,7 +4106,6 @@ for (my $i = 1; $i <= $opt{"NUM_TESTS"}; $i++) { $minconfig = "$tmpdir/add_config"; } - my $checkout = $opt{"CHECKOUT[$i]"}; if (defined($checkout)) { run_command "git checkout $checkout" or die "failed to checkout $checkout"; @@ -3224,6 +4113,10 @@ for (my $i = 1; $i <= $opt{"NUM_TESTS"}; $i++) { $no_reboot = 0; + # A test may opt to not reboot the box + if ($reboot_on_success) { + $reboot_success = 1; + } if ($test_type eq "bisect") { bisect $i; @@ -3237,10 +4130,15 @@ for (my $i = 1; $i <= $opt{"NUM_TESTS"}; $i++) { } elsif ($test_type eq "make_min_config") { make_min_config $i; next; + } elsif ($test_type eq "make_warnings_file") { + $no_reboot = 1; + make_warnings_file $i; + next; } if ($build_type ne "nobuild") { build $build_type or next; + check_buildlog or next; } if ($test_type eq "install") { @@ -3264,12 +4162,20 @@ for (my $i = 1; $i <= $opt{"NUM_TESTS"}; $i++) { success $i; } +if (defined($final_post_ktest)) { + run_command $final_post_ktest; +} + if ($opt{"POWEROFF_ON_SUCCESS"}) { halt; -} elsif ($opt{"REBOOT_ON_SUCCESS"} && !do_not_reboot) { - reboot; +} elsif ($opt{"REBOOT_ON_SUCCESS"} && !do_not_reboot && $reboot_success) { + reboot_to_good; +} elsif (defined($switch_to_good)) { + # still need to get to the good kernel + run_command $switch_to_good; } + doprint "\n $successes of $opt{NUM_TESTS} tests were successful\n\n"; exit 0; |
