diff options
Diffstat (limited to 'tools/testing/ktest/ktest.pl')
| -rwxr-xr-x | tools/testing/ktest/ktest.pl | 2119 |
1 files changed, 1365 insertions, 754 deletions
diff --git a/tools/testing/ktest/ktest.pl b/tools/testing/ktest/ktest.pl index 999eab1bc64f..001c4df9f7df 100755 --- a/tools/testing/ktest/ktest.pl +++ b/tools/testing/ktest/ktest.pl @@ -1,7 +1,7 @@ #!/usr/bin/perl -w +# SPDX-License-Identifier: GPL-2.0-only # # Copyright 2010 - Steven Rostedt <srostedt@redhat.com>, Red Hat Inc. -# Licensed under the terms of the GNU GPL License version 2 # use strict; @@ -10,6 +10,8 @@ use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK); use File::Path qw(mkpath); use File::Copy qw(cp); use FileHandle; +use FindBin; +use IO::Handle; my $VERSION = "0.2"; @@ -18,27 +20,37 @@ $| = 1; my %opt; my %repeat_tests; my %repeats; +my %evals; +my @command_vars; +my %command_tmp_vars; #default opts my %default = ( + "MAILER" => "sendmail", # default mailer + "EMAIL_ON_ERROR" => 1, + "EMAIL_WHEN_FINISHED" => 1, + "EMAIL_WHEN_CANCELED" => 0, + "EMAIL_WHEN_STARTED" => 0, "NUM_TESTS" => 1, "TEST_TYPE" => "build", - "BUILD_TYPE" => "randconfig", + "BUILD_TYPE" => "oldconfig", "MAKE_CMD" => "make", + "CLOSE_CONSOLE_SIGNAL" => "INT", "TIMEOUT" => 120, "TMP_DIR" => "/tmp/ktest/\${MACHINE}", - "SLEEP_TIME" => 60, # sleep time between tests + "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 + "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, @@ -49,13 +61,16 @@ my %default = ( "SCP_TO_TARGET" => "scp \$SRC_FILE \$SSH_USER\@\$MACHINE:\$DST_FILE", "SCP_TO_TARGET_INSTALL" => "\${SCP_TO_TARGET}", "REBOOT" => "ssh \$SSH_USER\@\$MACHINE reboot", + "REBOOT_RETURN_CODE" => 255, "STOP_AFTER_SUCCESS" => 10, "STOP_AFTER_FAILURE" => 60, "STOP_TEST_AFTER" => 600, "MAX_MONITOR_WAIT" => 1800, "GRUB_REBOOT" => "grub2-reboot", + "GRUB_BLS_GET" => "grubby --info=ALL", "SYSLINUX" => "extlinux", "SYSLINUX_PATH" => "/boot/extlinux", + "CONNECT_TIMEOUT" => 25, # required, and we will ask users if they don't have them but we keep the default # value something that is common. @@ -69,7 +84,9 @@ my %default = ( "IGNORE_UNUSED" => 0, ); -my $ktest_config; +my $test_log_start = 0; + +my $ktest_config = "ktest.conf"; my $version; my $have_version = 0; my $machine; @@ -86,6 +103,7 @@ my $final_post_ktest; my $pre_ktest; my $post_ktest; my $pre_test; +my $pre_test_die; my $post_test; my $pre_build; my $post_build; @@ -95,6 +113,7 @@ my $reboot_type; my $reboot_script; my $power_cycle; my $reboot; +my $reboot_return_code; my $reboot_on_error; my $switch_to_good; my $switch_to_test; @@ -113,6 +132,7 @@ my $last_grub_menu; my $grub_file; my $grub_number; my $grub_reboot; +my $grub_bls_get; my $syslinux; my $syslinux_path; my $syslinux_label; @@ -137,6 +157,7 @@ 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; @@ -145,7 +166,6 @@ my $bisect_ret_abort; my $bisect_ret_default; my $in_patchcheck = 0; my $run_test; -my $redirect; my $buildlog; my $testlog; my $dmesg; @@ -160,9 +180,13 @@ my $store_failures; my $store_successes; my $test_name; my $timeout; +my $run_timeout; +my $connect_timeout; +my $config_bisect_exec; my $booted_timeout; my $detect_triplefault; my $console; +my $close_console_signal; my $reboot_success_line; my $success_line; my $stop_after_success; @@ -174,6 +198,8 @@ my $checkout; my $localversion; my $iteration = 0; my $successes = 0; +my $stty_orig; +my $run_command_status = 0; my $bisect_good; my $bisect_bad; @@ -190,7 +216,31 @@ my $config_bisect_check; my $patchcheck_type; my $patchcheck_start; +my $patchcheck_cherry; my $patchcheck_end; +my $patchcheck_skip; + +my $build_time; +my $install_time; +my $reboot_time; +my $test_time; + +my $warning_found = 0; + +my $pwd; +my $dirname = $FindBin::Bin; + +my $mailto; +my $mailer; +my $mail_path; +my $mail_max_size; +my $mail_command; +my $email_on_error; +my $email_when_finished; +my $email_when_started; +my $email_when_canceled; + +my $script_start_time = localtime(); # set when a test is something other that just building or install # which would require more options. @@ -217,6 +267,15 @@ my $no_reboot = 1; my $reboot_success = 0; my %option_map = ( + "MAILTO" => \$mailto, + "MAILER" => \$mailer, + "MAIL_PATH" => \$mail_path, + "MAIL_MAX_SIZE" => \$mail_max_size, + "MAIL_COMMAND" => \$mail_command, + "EMAIL_ON_ERROR" => \$email_on_error, + "EMAIL_WHEN_FINISHED" => \$email_when_finished, + "EMAIL_WHEN_STARTED" => \$email_when_started, + "EMAIL_WHEN_CANCELED" => \$email_when_canceled, "MACHINE" => \$machine, "SSH_USER" => \$ssh_user, "TMP_DIR" => \$tmpdir, @@ -226,6 +285,7 @@ my %option_map = ( "PRE_KTEST" => \$pre_ktest, "POST_KTEST" => \$post_ktest, "PRE_TEST" => \$pre_test, + "PRE_TEST_DIE" => \$pre_test_die, "POST_TEST" => \$post_test, "BUILD_TYPE" => \$build_type, "BUILD_OPTIONS" => \$build_options, @@ -235,6 +295,7 @@ my %option_map = ( "POST_BUILD_DIE" => \$post_build_die, "POWER_CYCLE" => \$power_cycle, "REBOOT" => \$reboot, + "REBOOT_RETURN_CODE" => \$reboot_return_code, "BUILD_NOCLEAN" => \$noclean, "MIN_CONFIG" => \$minconfig, "OUTPUT_MIN_CONFIG" => \$output_minconfig, @@ -249,6 +310,7 @@ my %option_map = ( "GRUB_MENU" => \$grub_menu, "GRUB_FILE" => \$grub_file, "GRUB_REBOOT" => \$grub_reboot, + "GRUB_BLS_GET" => \$grub_bls_get, "SYSLINUX" => \$syslinux, "SYSLINUX_PATH" => \$syslinux_path, "SYSLINUX_LABEL" => \$syslinux_label, @@ -273,6 +335,7 @@ my %option_map = ( "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, @@ -283,8 +346,12 @@ my %option_map = ( "STORE_SUCCESSES" => \$store_successes, "TEST_NAME" => \$test_name, "TIMEOUT" => \$timeout, + "RUN_TIMEOUT" => \$run_timeout, + "CONNECT_TIMEOUT" => \$connect_timeout, + "CONFIG_BISECT_EXEC" => \$config_bisect_exec, "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, @@ -314,7 +381,9 @@ my %option_map = ( "PATCHCHECK_TYPE" => \$patchcheck_type, "PATCHCHECK_START" => \$patchcheck_start, + "PATCHCHECK_CHERRY" => \$patchcheck_cherry, "PATCHCHECK_END" => \$patchcheck_end, + "PATCHCHECK_SKIP" => \$patchcheck_skip, ); # Options may be used by other options, record them. @@ -322,6 +391,7 @@ my %used_options; # default variables that can be used chomp ($variable{"PWD"} = `pwd`); +$pwd = $variable{"PWD"}; $config_help{"MACHINE"} = << "EOF" The machine hostname that you will test. @@ -388,7 +458,7 @@ EOF ; $config_help{"REBOOT_TYPE"} = << "EOF" Way to reboot the box to the test kernel. - Only valid options so far are "grub", "grub2", "syslinux", and "script". + Only valid options so far are "grub", "grub2", "grub2bls", "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 @@ -402,6 +472,8 @@ $config_help{"REBOOT_TYPE"} = << "EOF" If you specify grub2, then you also need to specify both \$GRUB_MENU and \$GRUB_FILE. + If you specify grub2bls, then you also need to specify \$GRUB_MENU. + 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 @@ -427,6 +499,9 @@ $config_help{"GRUB_MENU"} = << "EOF" 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. + + For grub2bls, \$GRUB_MENU is searched on the result of \$GRUB_BLS_GET + command for the lines that begin with "title". EOF ; $config_help{"GRUB_FILE"} = << "EOF" @@ -445,13 +520,95 @@ $config_help{"REBOOT_SCRIPT"} = << "EOF" EOF ; +# used with process_expression() +my $d = 0; + +# defined before get_test_name() +my $in_die = 0; + +# defined before process_warning_line() +my $check_build_re = ".*:.*(warning|error|Error):.*"; +my $utf8_quote = "\\x{e2}\\x{80}(\\x{98}|\\x{99})"; + +# defined before child_finished() +my $child_done; + +# 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; + +my %dependency; + +# found above run_config_bisect() +my $pass = 1; + +# found above add_dep() + +my %depends; +my %depcount; +my $iflevel = 0; +my @ifdeps; + +# prevent recursion +my %read_kconfigs; + +# found above test_this_config() +my %min_configs; +my %keep_configs; +my %save_configs; +my %processed_configs; +my %nochange_config; + +# +# These are first defined here, main function later on +# +sub run_command; +sub start_monitor; +sub end_monitor; +sub wait_for_monitor; + +sub _logit { + if (defined($opt{"LOG_FILE"})) { + print LOG @_; + } +} + +sub logit { + if (defined($opt{"LOG_FILE"})) { + _logit @_; + } else { + print @_; + } +} + +sub doprint { + print @_; + _logit @_; +} + sub read_prompt { my ($cancel, $prompt) = @_; my $ans; for (;;) { - if ($cancel) { + if ($cancel) { print "$prompt [y/n/C] "; } else { print "$prompt [Y/n] "; @@ -494,7 +651,7 @@ sub read_ync { return read_prompt 1, $prompt; } -sub get_ktest_config { +sub get_mandatory_config { my ($config) = @_; my $ans; @@ -525,29 +682,96 @@ sub get_ktest_config { } } -sub get_ktest_configs { - get_ktest_config("MACHINE"); - get_ktest_config("BUILD_DIR"); - get_ktest_config("OUTPUT_DIR"); +sub show_time { + my ($time) = @_; + + my $hours = 0; + my $minutes = 0; + + if ($time > 3600) { + $hours = int($time / 3600); + $time -= $hours * 3600; + } + if ($time > 60) { + $minutes = int($time / 60); + $time -= $minutes * 60; + } + + if ($hours > 0) { + doprint "$hours hour"; + doprint "s" if ($hours > 1); + doprint " "; + } + + if ($minutes > 0) { + doprint "$minutes minute"; + doprint "s" if ($minutes > 1); + doprint " "; + } + + doprint "$time second"; + doprint "s" if ($time != 1); +} + +sub print_times { + doprint "\n"; + if ($build_time) { + doprint "Build time: "; + show_time($build_time); + doprint "\n"; + } + if ($install_time) { + doprint "Install time: "; + show_time($install_time); + doprint "\n"; + } + if ($reboot_time) { + doprint "Reboot time: "; + show_time($reboot_time); + doprint "\n"; + } + if ($test_time) { + doprint "Test time: "; + show_time($test_time); + doprint "\n"; + } + if ($warning_found) { + doprint "\n*** WARNING"; + doprint "S" if ($warning_found > 1); + doprint " found in build: $warning_found ***\n\n"; + } + + # reset for iterations like bisect + $build_time = 0; + $install_time = 0; + $reboot_time = 0; + $test_time = 0; + $warning_found = 0; +} + +sub get_mandatory_configs { + get_mandatory_config("MACHINE"); + get_mandatory_config("BUILD_DIR"); + get_mandatory_config("OUTPUT_DIR"); if ($newconfig) { - get_ktest_config("BUILD_OPTIONS"); + get_mandatory_config("BUILD_OPTIONS"); } # options required for other than just building a kernel if (!$buildonly) { - get_ktest_config("POWER_CYCLE"); - get_ktest_config("CONSOLE"); + get_mandatory_config("POWER_CYCLE"); + get_mandatory_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_mandatory_config("SSH_USER"); + get_mandatory_config("BUILD_TARGET"); + get_mandatory_config("TARGET_IMAGE"); } - get_ktest_config("LOCALVERSION"); + get_mandatory_config("LOCALVERSION"); return if ($buildonly); @@ -555,24 +779,24 @@ sub get_ktest_configs { if (!defined($rtype)) { if (!defined($opt{"GRUB_MENU"})) { - get_ktest_config("REBOOT_TYPE"); + get_mandatory_config("REBOOT_TYPE"); $rtype = $entered_configs{"REBOOT_TYPE"}; } else { $rtype = "grub"; } } - if ($rtype eq "grub") { - get_ktest_config("GRUB_MENU"); + if (($rtype eq "grub") or ($rtype eq "grub2bls")) { + get_mandatory_config("GRUB_MENU"); } if ($rtype eq "grub2") { - get_ktest_config("GRUB_MENU"); - get_ktest_config("GRUB_FILE"); + get_mandatory_config("GRUB_MENU"); + get_mandatory_config("GRUB_FILE"); } if ($rtype eq "syslinux") { - get_ktest_config("SYSLINUX_LABEL"); + get_mandatory_config("SYSLINUX_LABEL"); } } @@ -581,40 +805,51 @@ sub process_variables { my $retval = ""; # We want to check for '\', and it is just easier - # to check the previous characet of '$' and not need + # to check the previous character of '$' and not need # to worry if '$' is the first character. By adding # a space to $value, we can just check [^\\]\$ and # it will still work. $value = " $value"; - while ($value =~ /(.*?[^\\])\$\{(.*?)\}(.*)/) { + while ($value =~ /(.*?[^\\])\$\{([^\{]*?)\}(.*)/) { my $begin = $1; my $var = $2; my $end = $3; # append beginning of value to retval $retval = "$retval$begin"; - if (defined($variable{$var})) { + if ($var =~ s/^shell\s+//) { + $retval = `$var`; + if ($?) { + doprint "WARNING: $var returned an error\n"; + } else { + chomp $retval; + } + } elsif (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\}"; + # put back the origin piece, but with $#### to not reprocess it + $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; + $value = "$retval$end"; + $retval = ""; } - $retval = "$retval$value"; + $retval = $value; + + # Convert the saved variables with $####{var} back to ${var} + $retval =~ s/\$####/\$/g; # remove the space added in the beginning $retval =~ s/ //; - return "$retval" + return "$retval"; } sub set_value { @@ -622,25 +857,18 @@ sub set_value { 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 ($lvalue =~ /^(TEST|BISECT|CONFIG_BISECT)_TYPE(\[.*\])?$/ && + $prvalue !~ /^(config_|)bisect$/ && + $prvalue !~ /^build$/ && + $prvalue !~ /^make_warnings_file$/ && + $buildonly) { - if ($buildonly && $lvalue =~ /^BISECT_TYPE(\[.*\])?$/ && $prvalue ne "build") { + # Note if a test is something other than build, then we + # will need other mandatory options. if ($prvalue ne "install") { $buildonly = 0; } else { - # install still limits some manditory options. + # install still limits some mandatory options. $buildonly = 2; } } @@ -655,22 +883,43 @@ sub set_value { } ${$overrides}{$lvalue} = $prvalue; } - if ($rvalue =~ /^\s*$/) { - delete $opt{$lvalue}; + + $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 { - $opt{$lvalue} = $prvalue; + $arr = []; + $evals{$lvalue} = $arr; } + + push @{$arr}, $rvalue; } sub set_variable { - my ($lvalue, $rvalue) = @_; + my ($lvalue, $rvalue, $command) = @_; + # Command line variables override all others + if (defined($command_tmp_vars{$lvalue})) { + return; + } if ($rvalue =~ /^\s*$/) { delete $variable{$lvalue}; } else { $rvalue = process_variables($rvalue); $variable{$lvalue} = $rvalue; } + + if (defined($command)) { + $command_tmp_vars{$lvalue} = 1; + } } sub process_compare { @@ -712,7 +961,6 @@ sub value_defined { defined($opt{$2}); } -my $d = 0; sub process_expression { my ($name, $val) = @_; @@ -763,6 +1011,12 @@ sub process_expression { } } + if ($val =~ s/^\s*NOT\s+(.*)//) { + my $express = $1; + my $ret = process_expression($name, $express); + return !$ret; + } + if ($val =~ /^\s*0\s*$/) { return 0; } elsif ($val =~ /^\s*\d+\s*$/) { @@ -821,7 +1075,6 @@ sub __read_config { $override = 0; if ($type eq "TEST_START") { - if ($num_tests_set) { die "$name: $.: Can not specify both NUM_TESTS and TEST_START\n"; } @@ -884,14 +1137,13 @@ sub __read_config { } if (!$skip && $rest !~ /^\s*$/) { - die "$name: $.: Gargbage found after $type\n$_"; + die "$name: $.: Garbage found after $type\n$_"; } if ($skip && $type eq "TEST_START") { $test_num = $old_test_num; $repeat = $old_repeat; } - } elsif (/^\s*ELSE\b(.*)$/) { if (!$if) { die "$name: $.: ELSE found with out matching IF section\n$_"; @@ -917,7 +1169,7 @@ sub __read_config { } if ($rest !~ /^\s*$/) { - die "$name: $.: Gargbage found after DEFAULTS\n$_"; + die "$name: $.: Garbage found after DEFAULTS\n$_"; } } elsif (/^\s*INCLUDE\s+(\S+)/) { @@ -938,7 +1190,7 @@ sub __read_config { } } } - + if ( ! -r $file ) { die "$name: $.: Can't read file $file\n$_"; } @@ -947,6 +1199,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); @@ -991,10 +1257,10 @@ sub __read_config { # Config variables are only active while reading the # config and can be defined anywhere. They also ignore # TEST_START and DEFAULTS, but are skipped if they are in - # on of these sections that have SKIP defined. + # one of these sections that have SKIP defined. # The save variable can be # defined multiple times and the new one simply overrides - # the prevous one. + # the previous one. set_variable($lvalue, $rvalue); } else { @@ -1015,12 +1281,13 @@ sub __read_config { } 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; + print "What test case would you like to run?\n"; + print " (build, install or boot)\n"; + print " Other tests are available but require editing ktest.conf\n"; + print " (see tools/testing/ktest/sample.conf)\n"; + my $ans = <STDIN>; + chomp $ans; + $default{"TEST_TYPE"} = $ans; } sub read_config { @@ -1031,8 +1298,21 @@ sub read_config { $test_case = __read_config $config, \$test_num; + foreach my $val (@command_vars) { + chomp $val; + my %command_overrides; + if ($val =~ m/^\s*([A-Z_\[\]\d]+)\s*=\s*(.*?)\s*$/) { + my $lvalue = $1; + my $rvalue = $2; + + set_value($lvalue, $rvalue, 1, \%command_overrides, "COMMAND LINE"); + } else { + die "Invalid option definition '$val'\n"; + } + } + # make sure we have all mandatory configs - get_ktest_configs; + get_mandatory_configs; # was a test specified? if (!$test_case) { @@ -1073,7 +1353,7 @@ sub read_config { foreach my $option (keys %not_used) { print "$option\n"; } - print "Set IGRNORE_UNUSED = 1 to have ktest ignore unused variables\n"; + print "Set IGNORE_UNUSED = 1 to have ktest ignore unused variables\n"; if (!read_yn "Do you want to continue?") { exit -1; } @@ -1116,7 +1396,10 @@ sub __eval_option { # If a variable contains itself, use the default var if (($var eq $name) && defined($opt{$var})) { $o = $opt{$var}; - $retval = "$retval$o"; + # Only append if the default doesn't contain itself + if ($o !~ m/\$\{$var\}/) { + $retval = "$retval$o"; + } } elsif (defined($opt{$o})) { $o = $opt{$o}; $retval = "$retval$o"; @@ -1126,6 +1409,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\}"; } @@ -1140,6 +1427,33 @@ 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 ($name, $option, $i) = @_; @@ -1153,64 +1467,58 @@ sub eval_option { # Check for recursive evaluations. # 100 deep should be more than enough. if ($r++ > 100) { - die "Over 100 evaluations accurred with $option\n" . + die "Over 100 evaluations occurred with $option\n" . "Check for recursive variables\n"; } $prev = $option; $option = __eval_option($name, $option, $i); } - return $option; -} + $option = process_evals($name, $option, $i); -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 @_; + return $option; } -sub run_command; -sub start_monitor; -sub end_monitor; -sub wait_for_monitor; - sub reboot { my ($time) = @_; + my $powercycle = 0; - # Make sure everything has been written to disk - run_ssh("sync"); + # test if the machine can be connected to within a few seconds + my $stat = run_ssh("echo check machine status", $connect_timeout); + if (!$stat) { + doprint("power cycle\n"); + $powercycle = 1; + } + + if ($powercycle) { + run_command "$power_cycle"; - 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)) { - sleep $powercycle_after_reboot; + } else { + # Make sure everything has been written to disk + run_ssh("sync", 10); + + 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)) { + sleep $powercycle_after_reboot; + run_command "$power_cycle"; + } + } else { + # nope? power cycle it. run_command "$power_cycle"; } - } else { - # nope? power cycle it. - run_command "$power_cycle"; } if (defined($time)) { @@ -1231,7 +1539,8 @@ sub reboot { # Still need to wait for the reboot to finish wait_for_monitor($time, $reboot_success_line); - + } + if ($powercycle || $time) { end_monitor; } } @@ -1251,19 +1560,38 @@ sub do_not_reboot { 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"); + ($test_type eq "bisect" && $opt{"BISECT_TYPE[$i]"} eq "build") || + ($test_type eq "config_bisect" && $opt{"CONFIG_BISECT_TYPE[$i]"} eq "build"); +} + +sub get_test_name() { + my $name; + + if (defined($test_name)) { + $name = "$test_name:$test_type"; + } else { + $name = $test_type; + } + return $name; } sub dodie { - doprint "CRITICAL FAILURE... ", @_, "\n"; + # avoid recursion + return if ($in_die); + $in_die = 1; + + if ($monitor_cnt) { + # restore terminal settings + system("stty $stty_orig"); + } my $i = $iteration; - if ($reboot_on_error && !do_not_reboot) { + doprint "CRITICAL FAILURE... [TEST $i] ", @_, "\n"; + if ($reboot_on_error && !do_not_reboot) { doprint "REBOOTING\n"; reboot_to_good; - } elsif ($poweroff_on_error && defined($power_off)) { doprint "POWERING OFF\n"; `$power_off`; @@ -1273,33 +1601,128 @@ sub dodie { print " See $opt{LOG_FILE} for more info.\n"; } + if ($email_on_error) { + my $name = get_test_name; + my $log_file; + + if (defined($opt{"LOG_FILE"})) { + my $whence = 2; # End of file + my $log_size = tell LOG; + my $size = $log_size - $test_log_start; + + if (defined($mail_max_size)) { + if ($size > $mail_max_size) { + $size = $mail_max_size; + } + } + my $pos = - $size; + $log_file = "$tmpdir/log"; + open (L, "$opt{LOG_FILE}") or die "Can't open $opt{LOG_FILE} to read)"; + open (O, "> $tmpdir/log") or die "Can't open $tmpdir/log\n"; + seek(L, $pos, $whence); + while (<L>) { + print O; + } + close O; + close L; + } + + send_email("KTEST: critical failure for test $i [$name]", + "Your test started at $script_start_time has failed with:\n@_\n", $log_file); + } + + if (defined($post_test)) { + run_command $post_test; + } + die @_, "\n"; } -sub open_console { - my ($fp) = @_; +sub create_pty { + my ($ptm, $pts) = @_; + my $tmp; + my $TIOCSPTLCK = 0x40045431; + my $TIOCGPTN = 0x80045430; + + sysopen($ptm, "/dev/ptmx", O_RDWR | O_NONBLOCK) or + dodie "Can't open /dev/ptmx"; - my $flags; + # unlockpt() + $tmp = pack("i", 0); + ioctl($ptm, $TIOCSPTLCK, $tmp) or + dodie "ioctl TIOCSPTLCK for /dev/ptmx failed"; - my $pid = open($fp, "$console|") or + # ptsname() + ioctl($ptm, $TIOCGPTN, $tmp) or + dodie "ioctl TIOCGPTN for /dev/ptmx failed"; + $tmp = unpack("i", $tmp); + + sysopen($pts, "/dev/pts/$tmp", O_RDWR | O_NONBLOCK) or + dodie "Can't open /dev/pts/$tmp"; +} + +sub exec_console { + my ($ptm, $pts) = @_; + + close($ptm); + + close(\*STDIN); + close(\*STDOUT); + close(\*STDERR); + + open(\*STDIN, '<&', $pts); + open(\*STDOUT, '>&', $pts); + open(\*STDERR, '>&', $pts); + + close($pts); + + exec $console or dodie "Can't open console $console"; +} + +sub open_console { + my ($ptm) = @_; + my $pts = \*PTSFD; + my $pid; - $flags = fcntl($fp, F_GETFL, 0) or - dodie "Can't get flags for the socket: $!"; - $flags = fcntl($fp, F_SETFL, $flags | O_NONBLOCK) or - dodie "Can't set flags for the socket: $!"; + # save terminal settings + $stty_orig = `stty -g`; + + # place terminal in cbreak mode so that stdin can be read one character at + # a time without having to wait for a newline + system("stty -icanon -echo -icrnl"); + + create_pty($ptm, $pts); + + $pid = fork; + + if (!$pid) { + # child + exec_console($ptm, $pts) + } + + # parent + close($pts); return $pid; + + open(PTSFD, "Stop perl from warning about single use of PTSFD"); } sub close_console { my ($fp, $pid) = @_; doprint "kill child process $pid\n"; - kill 2, $pid; + kill $close_console_signal, $pid; + + doprint "wait for child process $pid to exit\n"; + waitpid($pid, 0); print "closing!\n"; close($fp); + + # restore terminal settings + system("stty $stty_orig"); } sub start_monitor { @@ -1379,138 +1802,195 @@ sub wait_for_monitor { } } print "** Monitor flushed **\n"; + + # if stop is defined but wasn't hit, return error + # used by reboot (which wants to see a reboot) + if (defined($stop) && !$booted) { + $bug = 1; + } 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 ($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 $type = $build_type; + if ($type =~ /useconfig/) { + $type = "useconfig"; + } - my $dir = "$machine-$test_type-$type-$result-$date"; + my $dir = "$machine-$test_type-$type-$result-$date"; - $dir = "$basedir/$dir"; + $dir = "$basedir/$dir"; - if (!-d $dir) { - mkpath($dir) or - die "can't create $dir"; - } + if (!-d $dir) { + mkpath($dir) or + dodie "can't create $dir"; + } - my %files = ( - "config" => $output_config, - "buildlog" => $buildlog, - "dmesg" => $dmesg, - "testlog" => $testlog, - ); + 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"; - } + while (my ($name, $source) = each(%files)) { + if (-f "$source") { + cp "$source", "$dir/$name" or + dodie "failed to copy $source"; } + } - doprint "*** Saved info to $dir ***\n"; + doprint "*** Saved info to $dir ***\n"; } sub fail { - if (defined($post_test)) { - run_command $post_test; - } + if ($die_on_failure) { + dodie @_; + } - if ($die_on_failure) { - dodie @_; - } + doprint "FAILED\n"; - doprint "FAILED\n"; + my $i = $iteration; - my $i = $iteration; + # no need to reboot for just building. + if (!do_not_reboot) { + doprint "REBOOTING\n"; + reboot_to_good $sleep_time; + } - # no need to reboot for just building. - if (!do_not_reboot) { - doprint "REBOOTING\n"; - reboot_to_good $sleep_time; - } + my $name = ""; - my $name = ""; + if (defined($test_name)) { + $name = " ($test_name)"; + } - if (defined($test_name)) { - $name = " ($test_name)"; - } + print_times; - doprint "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; - doprint "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; - doprint "KTEST RESULT: TEST $i$name Failed: ", @_, "\n"; - doprint "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; - doprint "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; + doprint "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; + doprint "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; + doprint "KTEST RESULT: TEST $i$name Failed: ", @_, "\n"; + doprint "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; + doprint "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; - if (defined($store_failures)) { - save_logs "fail", $store_failures; - } + if (defined($store_failures)) { + save_logs "fail", $store_failures; + } - return 1; + if (defined($post_test)) { + run_command $post_test; + } + + return 1; } sub run_command { - my ($command) = @_; + my ($command, $redirect, $timeout) = @_; + my $start_time; + my $end_time; my $dolog = 0; my $dord = 0; + my $dostdout = 0; my $pid; + my $command_orig = $command; $command =~ s/\$SSH_USER/$ssh_user/g; $command =~ s/\$MACHINE/$machine/g; + if (!defined($timeout)) { + $timeout = $run_timeout; + } + + if (!defined($timeout)) { + $timeout = -1; # tell wait_for_input to wait indefinitely + } + doprint("$command ... "); + $start_time = time; $pid = open(CMD, "$command 2>&1 |") or (fail "unable to exec $command" and return 0); if (defined($opt{"LOG_FILE"})) { - open(LOG, ">>$opt{LOG_FILE}") or - dodie "failed to write to log"; $dolog = 1; } if (defined($redirect)) { - open (RD, ">$redirect") or - dodie "failed to write to redirect $redirect"; - $dord = 1; + if ($redirect eq 1) { + $dostdout = 1; + # Have the output of the command on its own line + doprint "\n"; + } else { + open (RD, ">$redirect") or + dodie "failed to write to redirect $redirect"; + $dord = 1; + } } - while (<CMD>) { - print LOG if ($dolog); - print RD if ($dord); + my $hit_timeout = 0; + + while (1) { + my $fp = \*CMD; + my $line = wait_for_input($fp, $timeout); + if (!defined($line)) { + my $now = time; + if ($timeout >= 0 && (($now - $start_time) >= $timeout)) { + doprint "Hit timeout of $timeout, killing process\n"; + $hit_timeout = 1; + kill 9, $pid; + } + last; + } + print LOG $line if ($dolog); + print RD $line if ($dord); + print $line if ($dostdout); } waitpid($pid, 0); - my $failed = $?; + # shift 8 for real exit status + $run_command_status = $? >> 8; + + if ($command_orig eq $default{REBOOT} && + $run_command_status == $reboot_return_code) { + $run_command_status = 0; + } close(CMD); - close(LOG) if ($dolog); close(RD) if ($dord); - if ($failed) { + $end_time = time; + my $delta = $end_time - $start_time; + + if ($delta == 1) { + doprint "[1 second] "; + } else { + doprint "[$delta seconds] "; + } + + if ($hit_timeout) { + $run_command_status = 1; + } + + if ($run_command_status) { doprint "FAILED!\n"; } else { doprint "SUCCESS\n"; } - return !$failed; + return !$run_command_status; } sub run_ssh { - my ($cmd) = @_; + my ($cmd, $timeout) = @_; my $cp_exec = $ssh_exec; $cp_exec =~ s/\$SSH_COMMAND/$cmd/g; - return run_command "$cp_exec"; + return run_command "$cp_exec", undef , $timeout; } sub run_scp { @@ -1538,36 +2018,46 @@ sub run_scp_mod { return run_scp($src, $dst, $cp_scp); } -sub get_grub2_index { +sub _get_grub_index { + + my ($command, $target, $skip, $submenu) = @_; return if (defined($grub_number) && defined($last_grub_menu) && - $last_grub_menu eq $grub_menu && defined($last_machine) && - $last_machine eq $machine); + $last_grub_menu eq $grub_menu && defined($last_machine) && + $last_machine eq $machine); - doprint "Find grub2 menu ... "; + doprint "Find $reboot_type menu ... "; $grub_number = -1; my $ssh_grub = $ssh_exec; - $ssh_grub =~ s,\$SSH_COMMAND,cat $grub_file,g; + $ssh_grub =~ s,\$SSH_COMMAND,$command,g; - open(IN, "$ssh_grub |") - or die "unable to get $grub_file"; + open(IN, "$ssh_grub |") or + dodie "unable to execute $command"; my $found = 0; + my $submenu_number = 0; + while (<IN>) { - if (/^menuentry.*$grub_menu/) { + if (/$target/) { $grub_number++; $found = 1; last; - } elsif (/^menuentry\s/) { + } elsif (defined($submenu) && /$submenu/) { + $submenu_number++; + $grub_number = -1; + } elsif (/$skip/) { $grub_number++; } } close(IN); - die "Could not find '$grub_menu' in $grub_file on $machine" + dodie "Could not find '$grub_menu' through $command on $machine" if (!$found); + if ($submenu_number > 0) { + $grub_number = "$submenu_number>$grub_number"; + } doprint "$grub_number\n"; $last_grub_menu = $grub_menu; $last_machine = $machine; @@ -1575,52 +2065,45 @@ sub get_grub2_index { sub get_grub_index { - if ($reboot_type eq "grub2") { - get_grub2_index; - return; - } + my $command; + my $target; + my $skip; + my $submenu; + my $grub_menu_qt; - if ($reboot_type ne "grub") { + if ($reboot_type !~ /^grub/) { return; } - 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; - - my $ssh_grub = $ssh_exec; - $ssh_grub =~ s,\$SSH_COMMAND,cat /boot/grub/menu.lst,g; - open(IN, "$ssh_grub |") - or die "unable to get menu.lst"; + $grub_menu_qt = quotemeta($grub_menu); - my $found = 0; - - while (<IN>) { - if (/^\s*title\s+$grub_menu\s*$/) { - $grub_number++; - $found = 1; - last; - } elsif (/^\s*title\s/) { - $grub_number++; - } + if ($reboot_type eq "grub") { + $command = "cat /boot/grub/menu.lst"; + $target = '^\s*title\s+' . $grub_menu_qt . '\s*$'; + $skip = '^\s*title\s'; + } elsif ($reboot_type eq "grub2") { + $command = "cat $grub_file"; + $target = '^\s*menuentry.*' . $grub_menu_qt; + $skip = '^\s*menuentry\s'; + $submenu = '^\s*submenu\s'; + } elsif ($reboot_type eq "grub2bls") { + $command = $grub_bls_get; + $target = '^title=.*' . $grub_menu_qt; + $skip = '^title='; + } else { + return; } - close(IN); - 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; + _get_grub_index($command, $target, $skip, $submenu); } -sub wait_for_input -{ +sub wait_for_input { my ($fp, $time) = @_; + my $start_time; my $rin; - my $ready; + my $rout; + my $nr; + my $buf; my $line; my $ch; @@ -1628,23 +2111,47 @@ sub wait_for_input $time = $timeout; } + if ($time < 0) { + # Negative number means wait indefinitely + undef $time; + } + $rin = ''; vec($rin, fileno($fp), 1) = 1; - ($ready, $time) = select($rin, undef, undef, $time); + vec($rin, fileno(\*STDIN), 1) = 1; - $line = ""; + $start_time = time; - # try to read one char at a time - while (sysread $fp, $ch, 1) { - $line .= $ch; - last if ($ch eq "\n"); - } + while (1) { + $nr = select($rout=$rin, undef, undef, $time); - if (!length($line)) { - return undef; - } + last if ($nr <= 0); - return $line; + # copy data from stdin to the console + if (vec($rout, fileno(\*STDIN), 1) == 1) { + $nr = sysread(\*STDIN, $buf, 1000); + syswrite($fp, $buf, $nr) if ($nr > 0); + } + + # The timeout is based on time waiting for the fp data + if (vec($rout, fileno($fp), 1) != 1) { + last if (defined($time) && (time - $start_time > $time)); + next; + } + + $line = ""; + + # try to read one char at a time + while (sysread $fp, $ch, 1) { + $line .= $ch; + last if ($ch eq "\n"); + } + + last if (!length($line)); + + return $line; + } + return undef; } sub reboot_to { @@ -1654,8 +2161,8 @@ sub reboot_to { if ($reboot_type eq "grub") { run_ssh "'(echo \"savedefault --default=$grub_number --once\" | grub --batch)'"; - } elsif ($reboot_type eq "grub2") { - run_ssh "$grub_reboot $grub_number"; + } elsif (($reboot_type eq "grub2") or ($reboot_type eq "grub2bls")) { + run_ssh "$grub_reboot \"'$grub_number'\""; } elsif ($reboot_type eq "syslinux") { run_ssh "$syslinux --once \\\"$syslinux_label\\\" $syslinux_path"; } elsif (defined $reboot_script) { @@ -1692,13 +2199,15 @@ sub monitor { my $skip_call_trace = 0; my $loops; + my $start_time = time; + wait_for_monitor 5; my $line; my $full_line = ""; open(DMESG, "> $dmesg") or - die "unable to write to $dmesg"; + dodie "unable to write to $dmesg"; reboot_to; @@ -1709,7 +2218,6 @@ sub monitor { my $version_found = 0; while (!$done) { - if ($bug && defined($stop_after_failure) && $stop_after_failure >= 0) { my $time = $stop_after_failure - (time - $failure_start); @@ -1816,6 +2324,9 @@ sub monitor { } } + my $end_time = time; + $reboot_time = $end_time - $start_time; + close(DMESG); if ($bug) { @@ -1864,6 +2375,8 @@ sub install { return if ($no_install); + my $start_time = time; + if (defined($pre_install)) { my $cp_pre_install = eval_kernel_version $pre_install; run_command "$cp_pre_install" or @@ -1895,6 +2408,8 @@ sub install { if (!$install_mods) { do_post_install; doprint "No modules needed\n"; + my $end_time = time; + $install_time = $end_time - $start_time; return; } @@ -1922,19 +2437,27 @@ sub install { run_ssh "rm -f /tmp/$modtar"; do_post_install; + + my $end_time = time; + $install_time = $end_time - $start_time; } sub get_version { # get the release name return if ($have_version); doprint "$make kernelrelease ... "; - $version = `$make kernelrelease | tail -1`; + $version = `$make -s kernelrelease | tail -1`; + if (!length($version)) { + run_command "$make allnoconfig" or return 0; + doprint "$make kernelrelease ... "; + $version = `$make -s kernelrelease | tail -1`; + } chomp($version); doprint "$version\n"; $have_version = 1; } -sub start_monitor_and_boot { +sub start_monitor_and_install { # Make sure the stable kernel has finished booting # Install bisects, don't need console @@ -1952,9 +2475,6 @@ sub start_monitor_and_boot { 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) = @_; @@ -1982,8 +2502,6 @@ sub process_warning_line { # 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 @@ -1997,25 +2515,28 @@ sub check_buildlog { 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; + $warning_found++; + + # If warnings file didn't exist, and WARNINGS_FILE exist, + # then we fail on any warning! + if (defined $warnings_file) { + fail "New warning found (not in $warnings_file)\n$_\n"; + $no_reboot = $save_no_reboot; + return 0; + } } } } @@ -2134,6 +2655,8 @@ sub build { unlink $buildlog; + my $start_time = time; + # Failed builds should not reboot the target my $save_no_reboot = $no_reboot; $no_reboot = 1; @@ -2172,7 +2695,6 @@ sub build { run_command "mv $outputdir/config_temp $output_config" or dodie "moving config_temp"; } - } elsif (!$noclean) { unlink "$output_config"; run_command "$make mrproper" or @@ -2195,9 +2717,10 @@ sub build { # Run old config regardless, to enforce min configurations make_oldconfig; - $redirect = "$buildlog"; - my $build_ret = run_command "$make $build_options"; - undef $redirect; + if (not defined($build_options)){ + $build_options = ""; + } + my $build_ret = run_command "$make $build_options", $buildlog; if (defined($post_build)) { # Because a post build may change the kernel version @@ -2221,6 +2744,9 @@ sub build { $no_reboot = $save_no_reboot; + my $end_time = time; + $build_time = $end_time - $start_time; + return 1; } @@ -2239,10 +2765,6 @@ sub halt { sub success { my ($i) = @_; - if (defined($post_test)) { - run_command $post_test; - } - $successes++; my $name = ""; @@ -2251,54 +2773,58 @@ sub success { $name = " ($test_name)"; } - doprint "\n\n*******************************************\n"; - doprint "*******************************************\n"; - doprint "KTEST RESULT: TEST $i$name SUCCESS!!!! **\n"; - doprint "*******************************************\n"; - doprint "*******************************************\n"; + print_times; + + doprint "\n\n"; + doprint "*******************************************\n"; + doprint "*******************************************\n"; + doprint "KTEST RESULT: TEST $i$name SUCCESS!!!! **\n"; + doprint "*******************************************\n"; + doprint "*******************************************\n"; if (defined($store_successes)) { - save_logs "success", $store_successes; + save_logs "success", $store_successes; } if ($i != $opt{"NUM_TESTS"} && !do_not_reboot) { doprint "Reboot and wait $sleep_time seconds\n"; reboot_to_good $sleep_time; } + + if (defined($post_test)) { + run_command $post_test; + } } sub answer_bisect { for (;;) { - doprint "Pass or fail? [p/f]"; + doprint "Pass, fail, or skip? [p/f/s]"; my $ans = <STDIN>; chomp $ans; if ($ans eq "p" || $ans eq "P") { return 1; } elsif ($ans eq "f" || $ans eq "F") { return 0; + } elsif ($ans eq "s" || $ans eq "S") { + return -1; } else { - print "Please answer 'P' or 'F'\n"; + print "Please answer 'p', 'f', or 's'\n"; } } } sub child_run_test { - my $failed = 0; # child should have no power $reboot_on_error = 0; $poweroff_on_error = 0; $die_on_failure = 1; - $redirect = "$testlog"; - run_command $run_test or $failed = 1; - undef $redirect; + run_command $run_test, $testlog; - exit $failed; + exit $run_command_status; } -my $child_done; - sub child_finished { $child_done = 1; } @@ -2311,6 +2837,8 @@ sub do_run_test { my $bug = 0; my $bug_ignored = 0; + my $start_time = time; + wait_for_monitor 1; doprint "run test $run_test\n"; @@ -2375,7 +2903,10 @@ sub do_run_test { } waitpid $child_pid, 0; - $child_exit = $?; + $child_exit = $? >> 8; + + my $end_time = time; + $test_time = $end_time - $start_time; if (!$bug && $in_bisect) { if (defined($bisect_ret_good)) { @@ -2462,8 +2993,6 @@ sub run_bisect_test { my $failed = 0; my $result; - my $output; - my $ret; $in_bisect = 1; @@ -2477,7 +3006,7 @@ sub run_bisect_test { dodie "Failed on build" if $failed; # Now boot the box - start_monitor_and_boot or $failed = 1; + start_monitor_and_install or $failed = 1; if ($type ne "boot") { if ($failed && $bisect_skip) { @@ -2517,12 +3046,29 @@ 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 && $ret >= 0) { $ret = !$ret; @@ -2541,7 +3087,7 @@ 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"; + dodie "can't create bisect log"; return $tmp_log; } @@ -2550,9 +3096,9 @@ sub bisect { my $result; - 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)); + dodie "BISECT_GOOD[$i] not defined\n" if (!defined($bisect_good)); + dodie "BISECT_BAD[$i] not defined\n" if (!defined($bisect_bad)); + dodie "BISECT_TYPE[$i] not defined\n" if (!defined($bisect_type)); my $good = $bisect_good; my $bad = $bisect_bad; @@ -2608,14 +3154,13 @@ sub bisect { } if ($do_check) { - # get current HEAD my $head = get_sha1("HEAD"); if ($check ne "good") { doprint "TESTING BISECT BAD [$bad]\n"; run_command "git checkout $bad" or - die "Failed to checkout $bad"; + dodie "Failed to checkout $bad"; $result = run_bisect $type; @@ -2627,7 +3172,7 @@ sub bisect { if ($check ne "bad") { doprint "TESTING BISECT GOOD [$good]\n"; run_command "git checkout $good" or - die "Failed to checkout $good"; + dodie "Failed to checkout $good"; $result = run_bisect $type; @@ -2638,21 +3183,21 @@ sub bisect { # checkout where we started run_command "git checkout $head" or - die "Failed to checkout $head"; + dodie "Failed to checkout $head"; } run_command "git bisect start$start_files" or dodie "could not start bisect"; - run_command "git bisect good $good" or - dodie "could not set bisect good to $good"; - - run_git_bisect "git bisect bad $bad" or - dodie "could not set bisect bad to $bad"; - if (defined($replay)) { run_command "git bisect replay $replay" or dodie "failed to run replay"; + } else { + run_command "git bisect good $good" or + dodie "could not set bisect good to $good"; + + run_git_bisect "git bisect bad $bad" or + dodie "could not set bisect bad to $bad"; } if (defined($start)) { @@ -2664,6 +3209,7 @@ sub bisect { do { $result = run_bisect $type; $test = run_git_bisect "git bisect $result"; + print_times; } while ($test); run_command "git bisect log" or @@ -2677,37 +3223,20 @@ sub bisect { 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; - -my %dependency; - sub assign_configs { my ($hash, $config) = @_; - open (IN, $config) - or dodie "Failed to read $config"; + doprint "Reading configs from $config\n"; + + open (IN, $config) or + dodie "Failed to read $config"; while (<IN>) { + chomp; if (/^((CONFIG\S*)=.*)/) { ${$hash}{$2} = $1; + } elsif (/^(# (CONFIG\S*) is not set)/) { + ${$hash}{$2} = $1; } } @@ -2720,27 +3249,6 @@ sub process_config_ignore { assign_configs \%config_ignore, $config; } -sub read_current_config { - my ($config_ref) = @_; - - %{$config_ref} = (); - undef %{$config_ref}; - - my @key = keys %{$config_ref}; - if ($#key >= 0) { - print "did not delete!\n"; - exit; - } - open (IN, "$output_config"); - - while (<IN>) { - if (/^(CONFIG\S+)=(.*)/) { - ${$config_ref}{$1} = $2; - } - } - close(IN); -} - sub get_dependencies { my ($config) = @_; @@ -2759,337 +3267,207 @@ sub get_dependencies { return @deps; } -sub create_config { - my @configs = @_; +sub save_config { + my ($pc, $file) = @_; - open(OUT, ">$output_config") or dodie "Can not write to $output_config"; + my %configs = %{$pc}; - foreach my $config (@configs) { - print OUT "$config_set{$config}\n"; - my @deps = get_dependencies $config; - foreach my $dep (@deps) { - print OUT "$config_set{$dep}\n"; - } - } + doprint "Saving configs into $file\n"; - # turn off configs to keep off - foreach my $config (keys %config_off) { - print OUT "# $config is not set\n"; - } + open(OUT, ">$file") or dodie "Can not write to $file"; - # 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"; + foreach my $config (keys %configs) { + print OUT "$configs{$config}\n"; } close(OUT); - - make_oldconfig; } -sub compare_configs { - my (%a, %b) = @_; +sub create_config { + my ($name, $pc) = @_; - foreach my $item (keys %a) { - if (!defined($b{$item})) { - print "diff $item\n"; - return 1; - } - delete $b{$item}; - } + doprint "Creating old config from $name configs\n"; - my @keys = keys %b; - if ($#keys) { - print "diff2 $keys[0]\n"; - } - return -1 if ($#keys >= 0); + save_config $pc, $output_config; - return 0; + make_oldconfig; } sub run_config_bisect_test { my ($type) = @_; - return run_bisect_test $type, "oldconfig"; -} - -sub process_passed { - my (%configs) = @_; + my $ret = run_bisect_test $type, "oldconfig"; - doprint "These configs had no failure: (Enabling them for further compiles)\n"; - # Passed! All these configs are part of a good compile. - # Add them to the min options. - foreach my $config (keys %configs) { - if (defined($config_list{$config})) { - doprint " removing $config\n"; - $config_ignore{$config} = $config_list{$config}; - delete $config_list{$config}; - } + if ($bisect_manual) { + $ret = answer_bisect; } - doprint "config copied to $outputdir/config_good\n"; - run_command "cp -f $output_config $outputdir/config_good"; + + return $ret; } -sub process_failed { - my ($config) = @_; +sub config_bisect_end { + my ($good, $bad) = @_; + my $diffexec = "diff -u"; + if (-f "$builddir/scripts/diffconfig") { + $diffexec = "$builddir/scripts/diffconfig"; + } doprint "\n\n***************************************\n"; - doprint "Found bad config: $config\n"; + doprint "No more config bisecting possible.\n"; + run_command "$diffexec $good $bad", 1; doprint "***************************************\n\n"; } sub run_config_bisect { - - my @start_list = keys %config_list; - - if ($#start_list < 0) { - doprint "No more configs to test!!!\n"; - return -1; - } - - doprint "***** RUN TEST ***\n"; - my $type = $config_bisect_type; + my ($good, $bad, $last_result) = @_; + my $reset = ""; + my $cmd; my $ret; - my %current_config; - - my $count = $#start_list + 1; - doprint " $count configs to test\n"; - - my $half = int($#start_list / 2); - - 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; - - $count = $#tophalf + 1; - doprint "Testing $count configs\n"; - my $found = 0; - # make sure we test something - foreach my $config (@tophalf) { - if (defined($current_config{$config})) { - logit " $config\n"; - $found = 1; - } - } - 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) { - if (defined($current_config{$config})) { - logit " $config\n"; - $found = 1; - } - } - if (!$found) { - doprint "Failed: Can't make new config with current configs\n"; - foreach my $config (@start_list) { - doprint " CONFIG: $config\n"; - } - return -1; - } - $count = $#tophalf + 1; - doprint "Testing $count configs\n"; - } - - $ret = run_config_bisect_test $type; - if ($bisect_manual) { - $ret = answer_bisect; - } - if ($ret) { - process_passed %current_config; - return 0; - } - - doprint "This config had a failure.\n"; - doprint "Removing these configs that were not set in this config:\n"; - doprint "config copied to $outputdir/config_bad\n"; - run_command "cp -f $output_config $outputdir/config_bad"; - - # A config exists in this group that was bad. - foreach my $config (keys %config_list) { - if (!defined($current_config{$config})) { - doprint " removing $config\n"; - delete $config_list{$config}; - } - } - - @start_list = @tophalf; - if ($#start_list == 0) { - process_failed $start_list[0]; - return 1; - } - - # remove half the configs we are looking at and see if - # they are good. - $half = int($#start_list / 2); - } while ($#start_list > 0); - - # we found a single config, try it again unless we are running manually - - if ($bisect_manual) { - process_failed $start_list[0]; - return 1; + if (!length($last_result)) { + $reset = "-r"; } + run_command "$config_bisect_exec $reset -b $outputdir $good $bad $last_result", 1; - my @tophalf = @start_list[0 .. 0]; + # config-bisect returns: + # 0 if there is more to bisect + # 1 for finding a good config + # 2 if it can not find any more configs + # -1 (255) on error + if ($run_command_status) { + return $run_command_status; + } - $ret = run_config_bisect_test $type; + $ret = run_config_bisect_test $config_bisect_type; if ($ret) { - process_passed %current_config; - return 0; + doprint "NEW GOOD CONFIG ($pass)\n"; + system("cp $output_config $tmpdir/good_config.tmp.$pass"); + $pass++; + # Return 3 for good config + return 3; + } else { + doprint "NEW BAD CONFIG ($pass)\n"; + system("cp $output_config $tmpdir/bad_config.tmp.$pass"); + $pass++; + # Return 4 for bad config + return 4; } - - process_failed $start_list[0]; - return 1; } sub config_bisect { my ($i) = @_; - my $start_config = $config_bisect; + my $good_config; + my $bad_config; - my $tmpconfig = "$tmpdir/use_config"; + my $type = $config_bisect_type; + my $ret; - if (defined($config_bisect_good)) { - process_config_ignore $config_bisect_good; - } + $bad_config = $config_bisect; - # Make the file with the bad config and the min config - if (defined($minconfig)) { - # read the min config for things to ignore - run_command "cp $minconfig $tmpconfig" or - dodie "failed to copy $minconfig to $tmpconfig"; + if (defined($config_bisect_good)) { + $good_config = $config_bisect_good; + } elsif (defined($minconfig)) { + $good_config = $minconfig; } else { - unlink $tmpconfig; + doprint "No config specified, checking if defconfig works"; + $ret = run_bisect_test $type, "defconfig"; + if (!$ret) { + fail "Have no good config to compare with, please set CONFIG_BISECT_GOOD"; + return 1; + } + $good_config = $output_config; } - if (-f $tmpconfig) { - load_force_config($tmpconfig); - process_config_ignore $tmpconfig; + if (!defined($config_bisect_exec)) { + # First check the location that ktest.pl ran + my @locations = ( + "$pwd/config-bisect.pl", + "$dirname/config-bisect.pl", + "$builddir/tools/testing/ktest/config-bisect.pl", + undef ); + foreach my $loc (@locations) { + doprint "loc = $loc\n"; + $config_bisect_exec = $loc; + last if (defined($config_bisect_exec && -x $config_bisect_exec)); + } + if (!defined($config_bisect_exec)) { + fail "Could not find an executable config-bisect.pl\n", + " Set CONFIG_BISECT_EXEC to point to config-bisect.pl"; + return 1; + } } - # now process the start config - run_command "cp $start_config $output_config" or - dodie "failed to copy $start_config to $output_config"; + # we don't want min configs to cause issues here. + doprint "Disabling 'MIN_CONFIG' for this test\n"; + undef $minconfig; - # read directly what we want to check - my %config_check; - open (IN, $output_config) - or dodie "failed to open $output_config"; + my %good_configs; + my %bad_configs; + my %tmp_configs; - while (<IN>) { - if (/^((CONFIG\S*)=.*)/) { - $config_check{$2} = $1; + if (-f "$tmpdir/good_config.tmp" || -f "$tmpdir/bad_config.tmp") { + if (read_yn "Interrupted config-bisect. Continue (n - will start new)?") { + if (-f "$tmpdir/good_config.tmp") { + $good_config = "$tmpdir/good_config.tmp"; + } else { + $good_config = "$tmpdir/good_config"; + } + if (-f "$tmpdir/bad_config.tmp") { + $bad_config = "$tmpdir/bad_config.tmp"; + } else { + $bad_config = "$tmpdir/bad_config"; + } } } - close(IN); + doprint "Run good configs through make oldconfig\n"; + assign_configs \%tmp_configs, $good_config; + create_config "$good_config", \%tmp_configs; + $good_config = "$tmpdir/good_config"; + system("cp $output_config $good_config") == 0 or dodie "cp good config"; - # Now run oldconfig with the minconfig - make_oldconfig; - - # check to see what we lost (or gained) - open (IN, $output_config) - or dodie "Failed to read $start_config"; + doprint "Run bad configs through make oldconfig\n"; + assign_configs \%tmp_configs, $bad_config; + create_config "$bad_config", \%tmp_configs; + $bad_config = "$tmpdir/bad_config"; + system("cp $output_config $bad_config") == 0 or dodie "cp bad config"; - my %removed_configs; - my %added_configs; + if (defined($config_bisect_check) && $config_bisect_check ne "0") { + if ($config_bisect_check ne "good") { + doprint "Testing bad config\n"; - while (<IN>) { - if (/^((CONFIG\S*)=.*)/) { - # save off all options - $config_set{$2} = $1; - if (defined($config_check{$2})) { - if (defined($config_ignore{$2})) { - $removed_configs{$2} = $1; - } else { - $config_list{$2} = $1; - } - } elsif (!defined($config_ignore{$2})) { - $added_configs{$2} = $1; - $config_list{$2} = $1; + $ret = run_bisect_test $type, "useconfig:$bad_config"; + if ($ret) { + fail "Bad config succeeded when expected to fail!"; + return 0; } - } elsif (/^# ((CONFIG\S*).*)/) { - # Keep these configs disabled - $config_set{$2} = $1; - $config_off{$2} = $1; } - } - close(IN); + if ($config_bisect_check ne "bad") { + doprint "Testing good config\n"; - my @confs = keys %removed_configs; - if ($#confs >= 0) { - doprint "Configs overridden by default configs and removed from check:\n"; - foreach my $config (@confs) { - doprint " $config\n"; - } - } - @confs = keys %added_configs; - if ($#confs >= 0) { - doprint "Configs appearing in make oldconfig and added:\n"; - foreach my $config (@confs) { - doprint " $config\n"; + $ret = run_bisect_test $type, "useconfig:$good_config"; + if (!$ret) { + fail "Good config failed when expected to succeed!"; + return 0; + } } } - my %config_test; - my $once = 0; + my $last_run = ""; - @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 - # may not be able to create a new config. - # Here we create a config with everything set. - create_config (keys %config_list); - read_current_config \%config_test; - foreach my $config (keys %config_list) { - if (!defined($config_test{$config})) { - if (!$once) { - $once = 1; - doprint "Configs not produced by kconfig (will not be checked):\n"; - } - doprint " $config\n"; - delete $config_list{$config}; + do { + $ret = run_config_bisect $good_config, $bad_config, $last_run; + if ($ret == 3) { + $last_run = "good"; + } elsif ($ret == 4) { + $last_run = "bad"; } - } - my $ret; + print_times; + } while ($ret == 3 || $ret == 4); - 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"; + if ($ret == 2) { + config_bisect_end "$good_config.tmp", "$bad_config.tmp"; } - do { - $ret = run_config_bisect; - } while (!$ret); - return $ret if ($ret < 0); success $i; @@ -3103,16 +3481,23 @@ sub patchcheck_reboot { sub patchcheck { my ($i) = @_; - die "PATCHCHECK_START[$i] not defined\n" + dodie "PATCHCHECK_START[$i] not defined\n" if (!defined($patchcheck_start)); - die "PATCHCHECK_TYPE[$i] not defined\n" + dodie "PATCHCHECK_TYPE[$i] not defined\n" if (!defined($patchcheck_type)); my $start = $patchcheck_start; + my $cherry = $patchcheck_cherry; + if (!defined($cherry)) { + $cherry = 0; + } + my $end = "HEAD"; if (defined($patchcheck_end)) { $end = $patchcheck_end; + } elsif ($cherry) { + dodie "PATCHCHECK_END must be defined with PATCHCHECK_CHERRY\n"; } # Get the true sha1's since we can use things like HEAD~3 @@ -3126,24 +3511,64 @@ sub patchcheck { $type = "boot"; } - open (IN, "git log --pretty=oneline $end|") or - dodie "could not get git list"; + if ($cherry) { + open (IN, "git cherry -v $start $end|") or + dodie "could not get git list"; + } else { + open (IN, "git log --pretty=oneline $end|") or + dodie "could not get git list"; + } my @list; while (<IN>) { chomp; + # git cherry adds a '+' we want to remove + s/^\+ //; $list[$#list+1] = $_; last if (/^$start/); } close(IN); - if ($list[$#list] !~ /^$start/) { - fail "SHA1 $start not found"; + if (!$cherry) { + if ($list[$#list] !~ /^$start/) { + fail "SHA1 $start not found"; + } + + # go backwards in the list + @list = reverse @list; } - # go backwards in the list - @list = reverse @list; + my %skip_list; + my $will_skip = 0; + + if (defined($patchcheck_skip)) { + foreach my $s (split /\s+/, $patchcheck_skip) { + $s = `git log --pretty=oneline $s~1..$s`; + $s =~ s/^(\S+).*/$1/; + chomp $s; + $skip_list{$s} = 1; + $will_skip++; + } + } + + doprint("Going to test the following commits:\n"); + foreach my $l (@list) { + my $sha1 = $l; + $sha1 =~ s/^([[:xdigit:]]+).*/$1/; + next if (defined($skip_list{$sha1})); + doprint "$l\n"; + } + + if ($will_skip) { + doprint("\nSkipping the following commits:\n"); + foreach my $l (@list) { + my $sha1 = $l; + $sha1 =~ s/^([[:xdigit:]]+).*/$1/; + next if (!defined($skip_list{$sha1})); + doprint "$l\n"; + } + } my $save_clean = $noclean; my %ignored_warnings; @@ -3159,10 +3584,15 @@ sub patchcheck { my $sha1 = $item; $sha1 =~ s/^([[:xdigit:]]+).*/$1/; - doprint "\nProcessing commit $item\n\n"; + if (defined($skip_list{$sha1})) { + doprint "\nSkipping \"$item\"\n\n"; + next; + } + + doprint "\nProcessing commit \"$item\"\n\n"; run_command "git checkout $sha1" or - die "Failed to checkout $sha1"; + dodie "Failed to checkout $sha1"; # only clean on the first and last patch if ($item eq $list[0] || @@ -3190,16 +3620,18 @@ sub patchcheck { my $failed = 0; - start_monitor_and_boot or $failed = 1; + start_monitor_and_install or $failed = 1; if (!$failed && $type ne "boot"){ do_run_test or $failed = 1; } end_monitor; - return 0 if ($failed); - + if ($failed) { + print_times; + return 0; + } patchcheck_reboot; - + print_times; } $in_patchcheck = 0; success $i; @@ -3207,14 +3639,6 @@ sub patchcheck { return 1; } -my %depends; -my %depcount; -my $iflevel = 0; -my @ifdeps; - -# prevent recursion -my %read_kconfigs; - sub add_dep { # $config depends on $dep my ($config, $dep) = @_; @@ -3244,14 +3668,13 @@ sub read_kconfig { my $cont = 0; my $line; - if (! -f $kconfig) { doprint "file $kconfig does not exist, skipping\n"; return; } open(KIN, "$kconfig") - or die "Can't open $kconfig"; + or dodie "Can't open $kconfig"; while (<KIN>) { chomp; @@ -3326,8 +3749,8 @@ sub read_kconfig { sub read_depends { # find out which arch this is by the kconfig file - open (IN, $output_config) - or dodie "Failed to read $output_config"; + open (IN, $output_config) or + dodie "Failed to read $output_config"; my $arch; while (<IN>) { if (m,Linux/(\S+)\s+\S+\s+Kernel Configuration,) { @@ -3347,15 +3770,13 @@ sub read_depends { # what directory to look at. if ($arch eq "i386" || $arch eq "x86_64") { $arch = "x86"; - } elsif ($arch =~ /^tile/) { - $arch = "tile"; } my $kconfig = "$builddir/arch/$arch/Kconfig"; if (! -f $kconfig && $arch =~ /\d$/) { my $orig = $arch; - # some subarchs have numbers, truncate them + # some subarchs have numbers, truncate them $arch =~ s/\d*$//; $kconfig = "$builddir/arch/$arch/Kconfig"; if (! -f $kconfig) { @@ -3368,29 +3789,6 @@ sub read_depends { read_kconfig($kconfig); } -sub read_config_list { - my ($config) = @_; - - open (IN, $config) - or dodie "Failed to read $config"; - - while (<IN>) { - if (/^((CONFIG\S*)=.*)/) { - if (!defined($config_ignore{$2})) { - $config_list{$2} = $1; - } - } - } - - close(IN); -} - -sub read_output_config { - my ($config) = @_; - - assign_configs \%config_ignore, $config; -} - sub make_new_config { my @configs = @_; @@ -3427,7 +3825,6 @@ sub get_depends { my @configs; while ($dep =~ /[$valid]/) { - if ($dep =~ /^[^$valid]*([$valid]+)/) { my $conf = "CONFIG_" . $1; @@ -3435,19 +3832,13 @@ sub get_depends { $dep =~ s/^[^$valid]*[$valid]+//; } else { - die "this should never happen"; + dodie "this should never happen"; } } return @configs; } -my %min_configs; -my %keep_configs; -my %save_configs; -my %processed_configs; -my %nochange_config; - sub test_this_config { my ($config) = @_; @@ -3484,13 +3875,16 @@ sub test_this_config { # .config to make sure it is missing the config that # we had before my %configs = %min_configs; - delete $configs{$config}; + $configs{$config} = "# $config is not set"; make_new_config ((values %configs), (values %keep_configs)); make_oldconfig; + delete $configs{$config}; undef %configs; assign_configs \%configs, $output_config; - return $config if (!defined($configs{$config})); + if (!defined($configs{$config}) || $configs{$config} =~ /^#/) { + return $config; + } doprint "disabling config $config did not change .config\n"; @@ -3571,7 +3965,7 @@ sub make_min_config { foreach my $config (@config_keys) { my $kconfig = chomp_config $config; if (!defined $depcount{$kconfig}) { - $depcount{$kconfig} = 0; + $depcount{$kconfig} = 0; } } @@ -3606,7 +4000,6 @@ sub make_min_config { my $take_two = 0; while (!$done) { - my $config; my $found; @@ -3617,7 +4010,7 @@ sub make_min_config { # Sort keys by who is most dependent on @test_configs = sort { $depcount{chomp_config($b)} <=> $depcount{chomp_config($a)} } - @test_configs ; + @test_configs ; # Put configs that did not modify the config at the end. my $reset = 1; @@ -3673,13 +4066,13 @@ sub make_min_config { my $failed = 0; build "oldconfig" or $failed = 1; if (!$failed) { - start_monitor_and_boot or $failed = 1; + start_monitor_and_install or $failed = 1; - if ($type eq "test" && !$failed) { - do_run_test or $failed = 1; - } + if ($type eq "test" && !$failed) { + do_run_test or $failed = 1; + } - end_monitor; + end_monitor; } $in_bisect = 0; @@ -3693,8 +4086,8 @@ sub make_min_config { # update new ignore configs if (defined($ignore_config)) { - open (OUT, ">$temp_config") - or die "Can't write to $temp_config"; + open (OUT, ">$temp_config") or + dodie "Can't write to $temp_config"; foreach my $config (keys %save_configs) { print OUT "$save_configs{$config}\n"; } @@ -3720,9 +4113,9 @@ sub make_min_config { } } - # Save off all the current mandidory configs - open (OUT, ">$temp_config") - or die "Can't write to $temp_config"; + # Save off all the current mandatory configs + open (OUT, ">$temp_config") or + dodie "Can't write to $temp_config"; foreach my $config (keys %keep_configs) { print OUT "$keep_configs{$config}\n"; } @@ -3760,7 +4153,6 @@ sub make_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; @@ -3776,8 +4168,188 @@ sub make_warnings_file { success $i; } -$#ARGV < 1 or die "ktest.pl version: $VERSION\n usage: ktest.pl config-file\n"; +sub option_defined { + my ($option) = @_; + + if (defined($opt{$option}) && $opt{$option} !~ /^\s*$/) { + return 1; + } + + return 0; +} +sub __set_test_option { + my ($name, $i) = @_; + + my $option = "$name\[$i\]"; + + if (option_defined($option)) { + return $opt{$option}; + } + + foreach my $test (keys %repeat_tests) { + if ($i >= $test && + $i < $test + $repeat_tests{$test}) { + $option = "$name\[$test\]"; + if (option_defined($option)) { + return $opt{$option}; + } + } + } + + if (option_defined($name)) { + return $opt{$name}; + } + + return undef; +} + +sub set_test_option { + my ($name, $i) = @_; + + my $option = __set_test_option($name, $i); + return $option if (!defined($option)); + + return eval_option($name, $option, $i); +} + +sub find_mailer { + my ($mailer) = @_; + + my @paths = split /:/, $ENV{PATH}; + + # sendmail is usually in /usr/sbin + $paths[$#paths + 1] = "/usr/sbin"; + + foreach my $path (@paths) { + if (-x "$path/$mailer") { + return $path; + } + } + + return undef; +} + +sub do_send_mail { + my ($subject, $message, $file) = @_; + + if (!defined($mail_path)) { + # find the mailer + $mail_path = find_mailer $mailer; + if (!defined($mail_path)) { + die "\nCan not find $mailer in PATH\n"; + } + } + + my $header_file = "$tmpdir/header"; + open (HEAD, ">$header_file") or die "Can not create $header_file\n"; + print HEAD "To: $mailto\n"; + print HEAD "Subject: $subject\n\n"; + print HEAD "$message\n"; + close HEAD; + + if (!defined($mail_command)) { + if ($mailer eq "mail" || $mailer eq "mailx") { + $mail_command = "cat \$HEADER_FILE \$BODY_FILE | \$MAIL_PATH/\$MAILER -s \'\$SUBJECT\' \$MAILTO"; + } elsif ($mailer eq "sendmail" ) { + $mail_command = "cat \$HEADER_FILE \$BODY_FILE | \$MAIL_PATH/\$MAILER -t \$MAILTO"; + } else { + die "\nYour mailer: $mailer is not supported.\n"; + } + } + + if (defined($file)) { + $mail_command =~ s/\$BODY_FILE/$file/g; + } else { + $mail_command =~ s/\$BODY_FILE//g; + } + + $mail_command =~ s/\$HEADER_FILE/$header_file/g; + $mail_command =~ s/\$MAILER/$mailer/g; + $mail_command =~ s/\$MAIL_PATH/$mail_path/g; + $mail_command =~ s/\$MAILTO/$mailto/g; + $mail_command =~ s/\$SUBJECT/$subject/g; + $mail_command =~ s/\$MESSAGE/$message/g; + + my $ret = run_command $mail_command; + if (!$ret && defined($file)) { + # try again without the file + $message .= "\n\n*** FAILED TO SEND LOG ***\n\n"; + do_send_email($subject, $message); + } +} + +sub send_email { + if (defined($mailto)) { + if (!defined($mailer)) { + doprint "No email sent: email or mailer not specified in config.\n"; + return; + } + do_send_mail @_; + } +} + +sub cancel_test { + if ($monitor_cnt) { + end_monitor; + } + if ($email_when_canceled) { + my $name = get_test_name; + send_email("KTEST: Your [$name] test was cancelled", + "Your test started at $script_start_time was cancelled: sig int"); + } + die "\nCaught Sig Int, test interrupted: $!\n" +} + +sub die_usage { + die << "EOF" +ktest.pl version: $VERSION + usage: ktest.pl [options] [config-file] + [options]: + -D value: Where value can act as an option override. + -D BUILD_NOCLEAN=1 + Sets global BUILD_NOCLEAN to 1 + -D TEST_TYPE[2]=build + Sets TEST_TYPE of test 2 to "build" + + It can also override all temp variables. + -D USE_TEMP_DIR:=1 + Will override all variables that use + "USE_TEMP_DIR=" + +EOF +; +} + +while ( $#ARGV >= 0 ) { + if ( $ARGV[0] eq "-D" ) { + shift; + die_usage if ($#ARGV < 1); + my $val = shift; + + if ($val =~ m/(.*?):=(.*)$/) { + set_variable($1, $2, 1); + } else { + $command_vars[$#command_vars + 1] = $val; + } + + } elsif ( $ARGV[0] =~ m/^-D(.*)/) { + my $val = $1; + shift; + + if ($val =~ m/(.*?):=(.*)$/) { + set_variable($1, $2, 1); + } else { + $command_vars[$#command_vars + 1] = $val; + } + } elsif ( $ARGV[0] eq "-h" ) { + die_usage; + } else { + last; + } +} + +$#ARGV < 1 or die_usage; if ($#ARGV == 0) { $ktest_config = $ARGV[0]; if (! -f $ktest_config) { @@ -3786,8 +4358,6 @@ if ($#ARGV == 0) { exit 0; } } -} else { - $ktest_config = "ktest.conf"; } if (! -f $ktest_config) { @@ -3835,8 +4405,20 @@ if ($#new_configs >= 0) { } } -if ($opt{"CLEAR_LOG"} && defined($opt{"LOG_FILE"})) { - unlink $opt{"LOG_FILE"}; +if (defined($opt{"LOG_FILE"})) { + if ($opt{"CLEAR_LOG"}) { + unlink $opt{"LOG_FILE"}; + } + + if (! -e $opt{"LOG_FILE"} && $opt{"LOG_FILE"} =~ m,^(.*/),) { + my $dir = $1; + if (! -d $dir) { + mkpath($dir) or die "Failed to create directories '$dir': $!"; + print "\nThe log directory $dir did not exist, so it was created.\n"; + } + } + open(LOG, ">> $opt{LOG_FILE}") or die "Can't write to $opt{LOG_FILE}"; + LOG->autoflush(1); } doprint "\n\nSTARTING AUTOMATED TESTS\n\n"; @@ -3855,7 +4437,6 @@ for (my $i = 0, my $repeat = 1; $i <= $opt{"NUM_TESTS"}; $i += $repeat) { } foreach my $option (sort keys %opt) { - if ($option =~ /\[(\d+)\]$/) { next if ($i != $1); } else { @@ -3866,40 +4447,7 @@ for (my $i = 0, my $repeat = 1; $i <= $opt{"NUM_TESTS"}; $i += $repeat) { } } -sub __set_test_option { - my ($name, $i) = @_; - - my $option = "$name\[$i\]"; - - if (defined($opt{$option})) { - return $opt{$option}; - } - - foreach my $test (keys %repeat_tests) { - if ($i >= $test && - $i < $test + $repeat_tests{$test}) { - $option = "$name\[$test\]"; - if (defined($opt{$option})) { - return $opt{$option}; - } - } - } - - if (defined($opt{$name})) { - return $opt{$name}; - } - - return undef; -} - -sub set_test_option { - my ($name, $i) = @_; - - my $option = __set_test_option($name, $i); - return $option if (!defined($option)); - - return eval_option($name, $option, $i); -} +$SIG{INT} = qw(cancel_test); # First we need to do is the builds for (my $i = 1; $i <= $opt{"NUM_TESTS"}; $i++) { @@ -3912,10 +4460,27 @@ for (my $i = 1; $i <= $opt{"NUM_TESTS"}; $i++) { $iteration = $i; + $build_time = 0; + $install_time = 0; + $reboot_time = 0; + $test_time = 0; + undef %force_config; my $makecmd = set_test_option("MAKE_CMD", $i); + $outputdir = set_test_option("OUTPUT_DIR", $i); + $builddir = set_test_option("BUILD_DIR", $i); + + chdir $builddir || dodie "can't change directory to $builddir"; + + if (!-d $outputdir) { + mkpath($outputdir) or + dodie "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); @@ -3924,9 +4489,16 @@ for (my $i = 1; $i <= $opt{"NUM_TESTS"}; $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; + if ($i == 1) { + if (defined($pre_ktest)) { + doprint "\n"; + run_command $pre_ktest; + } + if ($email_when_started) { + my $name = get_test_name; + send_email("KTEST: Your [$name] test was started", + "Your test was started on $script_start_time"); + } } # Any test can override the POST_KTEST option @@ -3940,13 +4512,9 @@ for (my $i = 1; $i <= $opt{"NUM_TESTS"}; $i++) { $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 + dodie "can't create $tmpdir"; } $ENV{"SSH_USER"} = $ssh_user; @@ -3955,12 +4523,11 @@ for (my $i = 1; $i <= $opt{"NUM_TESTS"}; $i++) { $buildlog = "$tmpdir/buildlog-$machine"; $testlog = "$tmpdir/testlog-$machine"; $dmesg = "$tmpdir/dmesg-$machine"; - $make = "$makecmd O=$outputdir"; $output_config = "$outputdir/.config"; if (!$buildonly) { $target = "$ssh_user\@$machine"; - if ($reboot_type eq "grub") { + if (($reboot_type eq "grub") or ($reboot_type eq "grub2bls")) { dodie "GRUB_MENU not defined" if (!defined($grub_menu)); } elsif ($reboot_type eq "grub2") { dodie "GRUB_MENU not defined" if (!defined($grub_menu)); @@ -3991,11 +4558,30 @@ for (my $i = 1; $i <= $opt{"NUM_TESTS"}; $i++) { my $installme = ""; $installme = " no_install" if ($no_install); + my $name = ""; + + if (defined($test_name)) { + $name = " ($test_name)"; + } + doprint "\n\n"; - doprint "RUNNING TEST $i of $opt{NUM_TESTS} with option $test_type $run_type$installme\n\n"; + + if (defined($opt{"LOG_FILE"})) { + $test_log_start = tell(LOG); + } + + doprint "RUNNING TEST $i of $opt{NUM_TESTS}$name with option $test_type $run_type$installme\n\n"; + + # Always show which build directory and output directory is being used + doprint "BUILD_DIR=$builddir\n"; + doprint "OUTPUT_DIR=$outputdir\n\n"; if (defined($pre_test)) { - run_command $pre_test; + my $ret = run_command $pre_test; + if (!$ret && defined($pre_test_die) && + $pre_test_die) { + dodie "failed to pre_test\n"; + } } unlink $dmesg; @@ -4014,7 +4600,7 @@ for (my $i = 1; $i <= $opt{"NUM_TESTS"}; $i++) { if (defined($checkout)) { run_command "git checkout $checkout" or - die "failed to checkout $checkout"; + dodie "failed to checkout $checkout"; } $no_reboot = 0; @@ -4056,20 +4642,27 @@ for (my $i = 1; $i <= $opt{"NUM_TESTS"}; $i++) { if ($test_type ne "build") { my $failed = 0; - start_monitor_and_boot or $failed = 1; + start_monitor_and_install or $failed = 1; if (!$failed && $test_type ne "boot" && defined($run_test)) { do_run_test or $failed = 1; } end_monitor; - next if ($failed); + if ($failed) { + print_times; + next; + } } + print_times; + success $i; } if (defined($final_post_ktest)) { - run_command $final_post_ktest; + + my $cp_final_post_ktest = eval_kernel_version $final_post_ktest; + run_command $cp_final_post_ktest; } if ($opt{"POWEROFF_ON_SUCCESS"}) { @@ -4081,7 +4674,25 @@ if ($opt{"POWEROFF_ON_SUCCESS"}) { run_command $switch_to_good; } - doprint "\n $successes of $opt{NUM_TESTS} tests were successful\n\n"; +if ($email_when_finished) { + send_email("KTEST: Your test has finished!", + "$successes of $opt{NUM_TESTS} tests started at $script_start_time were successful!"); +} + +if (defined($opt{"LOG_FILE"})) { + print "\n See $opt{LOG_FILE} for the record of results.\n\n"; + close LOG; +} + exit 0; + +## +# The following are here to standardize tabs/spaces/etc across the most likely editors +### + +# Local Variables: +# mode: perl +# End: +# vim: softtabstop=4 |
