diff options
| author | Sean Christopherson <seanjc@google.com> | 2026-09-23 09:37:21 -0700 |
|---|---|---|
| committer | Paolo Bonzini <pbonzini@redhat.com> | 2026-09-26 00:39:55 -0400 |
| commit | 93de2a6a4b91b72607136dd656edf03fb399d27f (patch) | |
| tree | 9220c76eb23cb39a146fac9692ee69afa4aa3135 /tools/testing/ktest | |
| download | linux-stable-93de2a6a4b91b72607136dd656edf03fb399d27f.tar.gz linux-stable-93de2a6a4b91b72607136dd656edf03fb399d27f.zip | |
KVM: SEV: Do cache maintenance on the source VM during intra-host migrationgrafted
Manually perform cache maintenance on the source VM during intra-host
migration to ensure no stale data is left in CPU caches after the VM is
destroyed. Because the source VM is "converted" to a non-SEV VM, KVM's
memory reclaim flows won't trigger cache maintenance, e.g. when all guest
memory is reclaimed in response to detaching from the mmu_notifier.
Note, relying on the destination VM to do cache maintenance isn't an option
as KVM doesn't require identical guest memory configurations, i.e. the
source VM may have access to memory that the destination VM does not.
Enforcing equivalent memory configurations is infeasible, as it would
require a *deep* comparison of memslots, e.g. to verify that not only are
the memslot identical, but what the memslots point at is also identical.
Fixes: b56639318bb2 ("KVM: SEV: Add support for SEV intra host migration")
Cc: stable@vger.kernel.org
Reported-by: Stefan Teodorescu <fane@google.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
Message-ID: <20260923163721.1584779-3-seanjc@google.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Diffstat (limited to 'tools/testing/ktest')
23 files changed, 8480 insertions, 0 deletions
diff --git a/tools/testing/ktest/compare-ktest-sample.pl b/tools/testing/ktest/compare-ktest-sample.pl new file mode 100755 index 000000000..ebea21d0a --- /dev/null +++ b/tools/testing/ktest/compare-ktest-sample.pl @@ -0,0 +1,33 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0 + +open (IN,"ktest.pl"); +while (<IN>) { + # hashes are now used + if (/\$opt\{"?([A-Z].*?)(\[.*\])?"?\}/ || + /^\s*"?([A-Z].*?)"?\s*=>\s*/ || + /set_test_option\("(.*?)"/) { + $opt{$1} = 1; + } +} +close IN; + +open (IN, "sample.conf"); +while (<IN>) { + if (/^\s*#?\s*([A-Z]\S*)\s*=/) { + $samp{$1} = 1; + } +} +close IN; + +foreach $opt (keys %opt) { + if (!defined($samp{$opt})) { + print "opt = $opt\n"; + } +} + +foreach $samp (keys %samp) { + if (!defined($opt{$samp})) { + print "samp = $samp\n"; + } +} diff --git a/tools/testing/ktest/config-bisect.pl b/tools/testing/ktest/config-bisect.pl new file mode 100755 index 000000000..bee7cb34a --- /dev/null +++ b/tools/testing/ktest/config-bisect.pl @@ -0,0 +1,769 @@ +#!/usr/bin/perl -w +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright 2015 - Steven Rostedt, Red Hat Inc. +# Copyright 2017 - Steven Rostedt, VMware, Inc. +# + +# usage: +# config-bisect.pl [options] good-config bad-config [good|bad] +# + +# Compares a good config to a bad config, then takes half of the diffs +# and produces a config that is somewhere between the good config and +# the bad config. That is, the resulting config will start with the +# good config and will try to make half of the differences of between +# the good and bad configs match the bad config. It tries because of +# dependencies between the two configs it may not be able to change +# exactly half of the configs that are different between the two config +# files. + +# Here's a normal way to use it: +# +# $ cd /path/to/linux/kernel +# $ config-bisect.pl /path/to/good/config /path/to/bad/config + +# This will now pull in good config (blowing away .config in that directory +# so do not make that be one of the good or bad configs), and then +# build the config with "make oldconfig" to make sure it matches the +# current kernel. It will then store the configs in that result for +# the good config. It does the same for the bad config as well. +# The algorithm will run, merging half of the differences between +# the two configs and building them with "make oldconfig" to make sure +# the result changes (dependencies may reset changes the tool had made). +# It then copies the result of its good config to /path/to/good/config.tmp +# and the bad config to /path/to/bad/config.tmp (just appends ".tmp" to the +# files passed in). And the ".config" that you should test will be in +# directory + +# After the first run, determine if the result is good or bad then +# run the same command appending the result + +# For good results: +# $ config-bisect.pl /path/to/good/config /path/to/bad/config good + +# For bad results: +# $ config-bisect.pl /path/to/good/config /path/to/bad/config bad + +# Do not change the good-config or bad-config, config-bisect.pl will +# copy the good-config to a temp file with the same name as good-config +# but with a ".tmp" after it. It will do the same with the bad-config. + +# If "good" or "bad" is not stated at the end, it will copy the good and +# bad configs to the .tmp versions. If a .tmp version already exists, it will +# warn before writing over them (-r will not warn, and just write over them). +# If the last config is labeled "good", then it will copy it to the good .tmp +# version. If the last config is labeled "bad", it will copy it to the bad +# .tmp version. It will continue this until it can not merge the two any more +# without the result being equal to either the good or bad .tmp configs. + +my $start = 0; +my $val = ""; + +my $pwd = `pwd`; +chomp $pwd; +my $tree = $pwd; +my $build; + +my $output_config; +my $reset_bisect; + +sub usage { + print << "EOF" + +usage: config-bisect.pl [-l linux-tree][-b build-dir] good-config bad-config [good|bad] + -l [optional] define location of linux-tree (default is current directory) + -b [optional] define location to build (O=build-dir) (default is linux-tree) + good-config the config that is considered good + bad-config the config that does not work + "good" add this if the last run produced a good config + "bad" add this if the last run produced a bad config + If "good" or "bad" is not specified, then it is the start of a new bisect + + Note, each run will create copy of good and bad configs with ".tmp" appended. + +EOF +; + + exit(-1); +} + +sub doprint { + print @_; +} + +sub dodie { + doprint "CRITICAL FAILURE... ", @_, "\n"; + + die @_, "\n"; +} + +sub expand_path { + my ($file) = @_; + + if ($file =~ m,^/,) { + return $file; + } + return "$pwd/$file"; +} + +sub read_prompt { + my ($cancel, $prompt) = @_; + + my $ans; + + for (;;) { + if ($cancel) { + print "$prompt [y/n/C] "; + } else { + print "$prompt [y/N] "; + } + $ans = <STDIN>; + chomp $ans; + if ($ans =~ /^\s*$/) { + if ($cancel) { + $ans = "c"; + } else { + $ans = "n"; + } + } + last if ($ans =~ /^y$/i || $ans =~ /^n$/i); + 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; + } + return 1; +} + +sub read_yn { + my ($prompt) = @_; + + return read_prompt 0, $prompt; +} + +sub read_ync { + my ($prompt) = @_; + + return read_prompt 1, $prompt; +} + +sub run_command { + my ($command, $redirect) = @_; + my $start_time; + my $end_time; + my $dord = 0; + my $pid; + + $start_time = time; + + doprint("$command ... "); + + $pid = open(CMD, "$command 2>&1 |") or + dodie "unable to exec $command"; + + if (defined($redirect)) { + open (RD, ">$redirect") or + dodie "failed to write to redirect $redirect"; + $dord = 1; + } + + while (<CMD>) { + print RD if ($dord); + } + + waitpid($pid, 0); + my $failed = $?; + + close(CMD); + close(RD) if ($dord); + + $end_time = time; + my $delta = $end_time - $start_time; + + if ($delta == 1) { + doprint "[1 second] "; + } else { + doprint "[$delta seconds] "; + } + + if ($failed) { + doprint "FAILED!\n"; + } else { + doprint "SUCCESS\n"; + } + + return !$failed; +} + +###### CONFIG BISECT ###### + +# 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; + +my $make; + +sub make_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"; + } + } +} + +sub assign_configs { + my ($hash, $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; + } + } + + close(IN); +} + +sub process_config_ignore { + my ($config) = @_; + + assign_configs \%config_ignore, $config; +} + +sub get_dependencies { + my ($config) = @_; + + my $arr = $dependency{$config}; + if (!defined($arr)) { + return (); + } + + my @deps = @{$arr}; + + foreach my $dep (@{$arr}) { + print "ADD DEP $dep\n"; + @deps = (@deps, get_dependencies $dep); + } + + return @deps; +} + +sub save_config { + my ($pc, $file) = @_; + + my %configs = %{$pc}; + + doprint "Saving configs into $file\n"; + + open(OUT, ">$file") or dodie "Can not write to $file"; + + foreach my $config (keys %configs) { + print OUT "$configs{$config}\n"; + } + close(OUT); +} + +sub create_config { + my ($name, $pc) = @_; + + doprint "Creating old config from $name configs\n"; + + save_config $pc, $output_config; + + make_oldconfig; +} + +# compare two config hashes, and return configs with different vals. +# It returns B's config values, but you can use A to see what A was. +sub diff_config_vals { + my ($pa, $pb) = @_; + + # crappy Perl way to pass in hashes. + my %a = %{$pa}; + my %b = %{$pb}; + + my %ret; + + foreach my $item (keys %a) { + if (defined($b{$item}) && $b{$item} ne $a{$item}) { + $ret{$item} = $b{$item}; + } + } + + return %ret; +} + +# compare two config hashes and return the configs in B but not A +sub diff_configs { + my ($pa, $pb) = @_; + + my %ret; + + # crappy Perl way to pass in hashes. + my %a = %{$pa}; + my %b = %{$pb}; + + foreach my $item (keys %b) { + if (!defined($a{$item})) { + $ret{$item} = $b{$item}; + } + } + + return %ret; +} + +# return if two configs are equal or not +# 0 is equal +1 b has something a does not +# +1 if a and b have a different item. +# -1 if a has something b does not +sub compare_configs { + my ($pa, $pb) = @_; + + my %ret; + + # crappy Perl way to pass in hashes. + my %a = %{$pa}; + my %b = %{$pb}; + + foreach my $item (keys %b) { + if (!defined($a{$item})) { + return 1; + } + if ($a{$item} ne $b{$item}) { + return 1; + } + } + + foreach my $item (keys %a) { + if (!defined($b{$item})) { + return -1; + } + } + + return 0; +} + +sub process_failed { + my ($config) = @_; + + doprint "\n\n***************************************\n"; + doprint "Found bad config: $config\n"; + doprint "***************************************\n\n"; +} + +sub process_new_config { + my ($tc, $nc, $gc, $bc) = @_; + + my %tmp_config = %{$tc}; + my %good_configs = %{$gc}; + my %bad_configs = %{$bc}; + + my %new_configs; + + my $runtest = 1; + my $ret; + + create_config "tmp_configs", \%tmp_config; + assign_configs \%new_configs, $output_config; + + $ret = compare_configs \%new_configs, \%bad_configs; + if (!$ret) { + doprint "New config equals bad config, try next test\n"; + $runtest = 0; + } + + if ($runtest) { + $ret = compare_configs \%new_configs, \%good_configs; + if (!$ret) { + doprint "New config equals good config, try next test\n"; + $runtest = 0; + } + } + + %{$nc} = %new_configs; + + return $runtest; +} + +sub convert_config { + my ($config) = @_; + + if ($config =~ /^# (.*) is not set/) { + $config = "$1=n"; + } + + $config =~ s/^CONFIG_//; + return $config; +} + +sub print_config { + my ($sym, $config) = @_; + + $config = convert_config $config; + doprint "$sym$config\n"; +} + +sub print_config_compare { + my ($good_config, $bad_config) = @_; + + $good_config = convert_config $good_config; + $bad_config = convert_config $bad_config; + + my $good_value = $good_config; + my $bad_value = $bad_config; + $good_value =~ s/(.*)=//; + my $config = $1; + + $bad_value =~ s/.*=//; + + doprint " $config $good_value -> $bad_value\n"; +} + +# Pass in: +# $phalf: half of the configs names you want to add +# $oconfigs: The orginial configs to start with +# $sconfigs: The source to update $oconfigs with (from $phalf) +# $which: The name of which half that is updating (top / bottom) +# $type: The name of the source type (good / bad) +sub make_half { + my ($phalf, $oconfigs, $sconfigs, $which, $type) = @_; + + my @half = @{$phalf}; + my %orig_configs = %{$oconfigs}; + my %source_configs = %{$sconfigs}; + + my %tmp_config = %orig_configs; + + doprint "Settings bisect with $which half of $type configs:\n"; + foreach my $item (@half) { + doprint "Updating $item to $source_configs{$item}\n"; + $tmp_config{$item} = $source_configs{$item}; + } + + return %tmp_config; +} + +sub run_config_bisect { + my ($pgood, $pbad) = @_; + + my %good_configs = %{$pgood}; + my %bad_configs = %{$pbad}; + + my %diff_configs = diff_config_vals \%good_configs, \%bad_configs; + my %b_configs = diff_configs \%good_configs, \%bad_configs; + my %g_configs = diff_configs \%bad_configs, \%good_configs; + + # diff_arr is what is in both good and bad but are different (y->n) + my @diff_arr = keys %diff_configs; + my $len_diff = $#diff_arr + 1; + + # b_arr is what is in bad but not in good (has depends) + my @b_arr = keys %b_configs; + my $len_b = $#b_arr + 1; + + # g_arr is what is in good but not in bad + my @g_arr = keys %g_configs; + my $len_g = $#g_arr + 1; + + my $runtest = 0; + my %new_configs; + my $ret; + + # Look at the configs that are different between good and bad. + # This does not include those that depend on other configs + # (configs depending on other configs that are not set would + # not show up even as a "# CONFIG_FOO is not set" + + + doprint "# of configs to check: $len_diff\n"; + doprint "# of configs showing only in good: $len_g\n"; + doprint "# of configs showing only in bad: $len_b\n"; + + if ($len_diff > 0) { + # Now test for different values + + doprint "Configs left to check:\n"; + doprint " Good Config\t\t\tBad Config\n"; + doprint " -----------\t\t\t----------\n"; + foreach my $item (@diff_arr) { + doprint " $good_configs{$item}\t$bad_configs{$item}\n"; + } + + my $half = int($#diff_arr / 2); + my @tophalf = @diff_arr[0 .. $half]; + + doprint "Set tmp config to be good config with some bad config values\n"; + + my %tmp_config = make_half \@tophalf, \%good_configs, + \%bad_configs, "top", "bad"; + + $runtest = process_new_config \%tmp_config, \%new_configs, + \%good_configs, \%bad_configs; + + if (!$runtest) { + doprint "Set tmp config to be bad config with some good config values\n"; + + my %tmp_config = make_half \@tophalf, \%bad_configs, + \%good_configs, "top", "good"; + + $runtest = process_new_config \%tmp_config, \%new_configs, + \%good_configs, \%bad_configs; + } + } + + if (!$runtest && $len_diff > 0) { + # do the same thing, but this time with bottom half + + my $half = int($#diff_arr / 2); + my @bottomhalf = @diff_arr[$half+1 .. $#diff_arr]; + + doprint "Set tmp config to be good config with some bad config values\n"; + + my %tmp_config = make_half \@bottomhalf, \%good_configs, + \%bad_configs, "bottom", "bad"; + + $runtest = process_new_config \%tmp_config, \%new_configs, + \%good_configs, \%bad_configs; + + if (!$runtest) { + doprint "Set tmp config to be bad config with some good config values\n"; + + my %tmp_config = make_half \@bottomhalf, \%bad_configs, + \%good_configs, "bottom", "good"; + + $runtest = process_new_config \%tmp_config, \%new_configs, + \%good_configs, \%bad_configs; + } + } + + if ($runtest) { + make_oldconfig; + doprint "READY TO TEST .config IN $build\n"; + return 0; + } + + doprint "\n%%%%%%%% FAILED TO FIND SINGLE BAD CONFIG %%%%%%%%\n"; + doprint "Hmm, can't make any more changes without making good == bad?\n"; + doprint "Difference between good (+) and bad (-)\n"; + + foreach my $item (keys %bad_configs) { + if (!defined($good_configs{$item})) { + print_config "-", $bad_configs{$item}; + } + } + + foreach my $item (keys %good_configs) { + next if (!defined($bad_configs{$item})); + if ($good_configs{$item} ne $bad_configs{$item}) { + print_config_compare $good_configs{$item}, $bad_configs{$item}; + } + } + + foreach my $item (keys %good_configs) { + if (!defined($bad_configs{$item})) { + print_config "+", $good_configs{$item}; + } + } + return -1; +} + +sub config_bisect { + my ($good_config, $bad_config) = @_; + my $ret; + + my %good_configs; + my %bad_configs; + my %tmp_configs; + + doprint "Run good configs through make oldconfig\n"; + assign_configs \%tmp_configs, $good_config; + create_config "$good_config", \%tmp_configs; + assign_configs \%good_configs, $output_config; + + doprint "Run bad configs through make oldconfig\n"; + assign_configs \%tmp_configs, $bad_config; + create_config "$bad_config", \%tmp_configs; + assign_configs \%bad_configs, $output_config; + + save_config \%good_configs, $good_config; + save_config \%bad_configs, $bad_config; + + return run_config_bisect \%good_configs, \%bad_configs; +} + +while ($#ARGV >= 0) { + if ($ARGV[0] !~ m/^-/) { + last; + } + my $opt = shift @ARGV; + + if ($opt eq "-b") { + $val = shift @ARGV; + if (!defined($val)) { + die "-b requires value\n"; + } + $build = $val; + } + + elsif ($opt eq "-l") { + $val = shift @ARGV; + if (!defined($val)) { + die "-l requires value\n"; + } + $tree = $val; + } + + elsif ($opt eq "-r") { + $reset_bisect = 1; + } + + elsif ($opt eq "-h") { + usage; + } + + else { + die "Unknown option $opt\n"; + } +} + +$build = $tree if (!defined($build)); + +$tree = expand_path $tree; +$build = expand_path $build; + +if ( ! -d $tree ) { + die "$tree not a directory\n"; +} + +if ( ! -d $build ) { + die "$build not a directory\n"; +} + +usage if $#ARGV < 1; + +if ($#ARGV == 1) { + $start = 1; +} elsif ($#ARGV == 2) { + $val = $ARGV[2]; + if ($val ne "good" && $val ne "bad") { + die "Unknown command '$val', bust be either \"good\" or \"bad\"\n"; + } +} else { + usage; +} + +my $good_start = expand_path $ARGV[0]; +my $bad_start = expand_path $ARGV[1]; + +my $good = "$good_start.tmp"; +my $bad = "$bad_start.tmp"; + +$make = "make"; + +if ($build ne $tree) { + $make = "make O=$build" +} + +$output_config = "$build/.config"; + +if ($start) { + if ( ! -f $good_start ) { + die "$good_start not found\n"; + } + if ( ! -f $bad_start ) { + die "$bad_start not found\n"; + } + if ( -f $good || -f $bad ) { + my $p = ""; + + if ( -f $good ) { + $p = "$good exists\n"; + } + + if ( -f $bad ) { + $p = "$p$bad exists\n"; + } + + if (!defined($reset_bisect)) { + if (!read_yn "${p}Overwrite and start new bisect anyway?") { + exit (-1); + } + } + } + run_command "cp $good_start $good" or die "failed to copy to $good\n"; + run_command "cp $bad_start $bad" or die "failed to copy to $bad\n"; +} else { + if ( ! -f $good ) { + die "Can not find file $good\n"; + } + if ( ! -f $bad ) { + die "Can not find file $bad\n"; + } + if ($val eq "good") { + run_command "cp $output_config $good" or die "failed to copy $output_config to $good\n"; + } elsif ($val eq "bad") { + run_command "cp $output_config $bad" or die "failed to copy $output_config to $bad\n"; + } +} + +chdir $tree || die "can't change directory to $tree"; + +my $ret = config_bisect $good, $bad; + +if (!$ret) { + exit(0); +} + +if ($ret > 0) { + doprint "Cleaning temp files\n"; + run_command "rm $good"; + run_command "rm $bad"; + exit(1); +} else { + doprint "See good and bad configs for details:\n"; + doprint "good: $good\n"; + doprint "bad: $bad\n"; + doprint "%%%%%%%% FAILED TO FIND SINGLE BAD CONFIG %%%%%%%%\n"; +} +exit(2); diff --git a/tools/testing/ktest/examples/README b/tools/testing/ktest/examples/README new file mode 100644 index 000000000..4f048789b --- /dev/null +++ b/tools/testing/ktest/examples/README @@ -0,0 +1,32 @@ +This directory contains example configs to use ktest for various tasks. +The configs still need to be customized for your environment, but it +is broken up by task which makes it easier to understand how to set up +ktest. + +The configs are based off of real working configs but have been modified +and commented to show more generic use cases that are more helpful for +developers. + +crosstests.conf - this config shows an example of testing a git repo against + lots of different architectures. It only does build tests, but makes + it easy to compile test different archs. You can download the arch + cross compilers from: + https://kernel.org/pub/tools/crosstool/files/bin/x86_64/ + +test.conf - A generic example of a config. This is based on an actual config + used to perform real testing. + +kvm.conf - A example of a config that is used to test a virtual guest running + on a host. + +snowball.conf - An example config that was used to demo ktest.pl against + a snowball ARM board. + +include/ - The include directory holds default configs that can be + included into other configs. This is a real use example that shows how + to reuse configs for various machines or set ups. The files here + are included by other config files, where the other config files define + options and variables that will make the included config work for the + given environment. + + diff --git a/tools/testing/ktest/examples/bootconfigs/boottrace.bconf b/tools/testing/ktest/examples/bootconfigs/boottrace.bconf new file mode 100644 index 000000000..7aa706ccc --- /dev/null +++ b/tools/testing/ktest/examples/bootconfigs/boottrace.bconf @@ -0,0 +1,59 @@ +ftrace.event { + task.task_newtask { + filter = "pid < 128" + enable + } + kprobes.vfs_read { + probes = "vfs_read $arg1 $arg2" + filter = "common_pid < 200" + enable + } + synthetic.initcall_latency { + fields = "unsigned long func", "u64 lat" + hist { + keys = func.sym,lat + values = lat + sort = lat + } + } + initcall.initcall_start.hist { + keys = func; + var.ts0 = common_timestamp.usecs + } + initcall.initcall_finish.hist { + keys = func + var.lat = common_timestamp.usecs - $ts0 + onmatch { + event = initcall.initcall_start + trace = initcall_latency, func, $lat + } + } +} + +ftrace.instance { + foo { + tracer = "function" + ftrace.filters = "user_*" + cpumask = 1 + options = nosym-addr + buffer_size = 512KB + trace_clock = mono + event.signal.signal_deliver.actions=snapshot + } + bar { + tracer = "function" + ftrace.filters = "kernel_*" + cpumask = 2 + trace_clock = x86-tsc + } +} + +ftrace.alloc_snapshot + +kernel { + trace_options = sym-addr + trace_event = "initcall:*" + trace_buf_size = 1M + ftrace = function + ftrace_filter = "vfs*" +} diff --git a/tools/testing/ktest/examples/bootconfigs/config-bootconfig b/tools/testing/ktest/examples/bootconfigs/config-bootconfig new file mode 100644 index 000000000..0685b6811 --- /dev/null +++ b/tools/testing/ktest/examples/bootconfigs/config-bootconfig @@ -0,0 +1 @@ +CONFIG_CMDLINE="bootconfig" diff --git a/tools/testing/ktest/examples/bootconfigs/functiongraph.bconf b/tools/testing/ktest/examples/bootconfigs/functiongraph.bconf new file mode 100644 index 000000000..68debfcbd --- /dev/null +++ b/tools/testing/ktest/examples/bootconfigs/functiongraph.bconf @@ -0,0 +1,15 @@ +ftrace { + tracing_on = 0 # off by default + tracer = function_graph + event.kprobes { + start_event { + probes = "pci_proc_init" + actions = "traceon" + } + end_event { + probes = "pci_proc_init%return" + actions = "traceoff" + } + } +} + diff --git a/tools/testing/ktest/examples/bootconfigs/tracing.bconf b/tools/testing/ktest/examples/bootconfigs/tracing.bconf new file mode 100644 index 000000000..bf117c781 --- /dev/null +++ b/tools/testing/ktest/examples/bootconfigs/tracing.bconf @@ -0,0 +1,33 @@ +ftrace { + tracer = function_graph; + options = event-fork, sym-addr, stacktrace; + buffer_size = 1M; + alloc_snapshot; + trace_clock = global; + events = "task:task_newtask", "initcall:*"; + event.sched.sched_process_exec { + filter = "pid < 128"; + } + instance.bar { + event.kprobes { + myevent { + probes = "vfs_read $arg2 $arg3"; + } + myevent2 { + probes = "vfs_write $arg2 +0($arg2):ustring $arg3"; + } + myevent3 { + probes = "initrd_load"; + } + enable + } + } + instance.foo { + tracer = function; + tracing_on = false; + }; +} +kernel { + ftrace_dump_on_oops = "orig_cpu" + traceoff_on_warning +} diff --git a/tools/testing/ktest/examples/bootconfigs/verify-boottrace.sh b/tools/testing/ktest/examples/bootconfigs/verify-boottrace.sh new file mode 100755 index 000000000..233e95cfc --- /dev/null +++ b/tools/testing/ktest/examples/bootconfigs/verify-boottrace.sh @@ -0,0 +1,84 @@ +#!/bin/sh + +cd /sys/kernel/tracing + +compare_file() { + file="$1" + val="$2" + content=`cat $file` + if [ "$content" != "$val" ]; then + echo "FAILED: $file has '$content', expected '$val'" + exit 1 + fi +} + +compare_file_partial() { + file="$1" + val="$2" + content=`cat $file | sed -ne "/^$val/p"` + if [ -z "$content" ]; then + echo "FAILED: $file does not contain '$val'" + cat $file + exit 1 + fi +} + +file_contains() { + file=$1 + val="$2" + + if ! grep -q "$val" $file ; then + echo "FAILED: $file does not contain $val" + cat $file + exit 1 + fi +} + +compare_mask() { + file=$1 + val="$2" + + content=`cat $file | sed -ne "/^[0 ]*$val/p"` + if [ -z "$content" ]; then + echo "FAILED: $file does not have mask '$val'" + cat $file + exit 1 + fi +} + +compare_file "events/task/task_newtask/filter" "pid < 128" +compare_file "events/task/task_newtask/enable" "1" + +compare_file "events/kprobes/vfs_read/filter" "common_pid < 200" +compare_file "events/kprobes/vfs_read/enable" "1" + +compare_file_partial "events/synthetic/initcall_latency/trigger" "hist:keys=func.sym,lat:vals=hitcount,lat:sort=lat" +compare_file_partial "events/synthetic/initcall_latency/enable" "0" + +compare_file_partial "events/initcall/initcall_start/trigger" "hist:keys=func:vals=hitcount:ts0=common_timestamp.usecs" +compare_file_partial "events/initcall/initcall_start/enable" "1" + +compare_file_partial "events/initcall/initcall_finish/trigger" 'hist:keys=func:vals=hitcount:lat=common_timestamp.usecs-\$ts0:sort=hitcount:size=2048:clock=global:onmatch(initcall.initcall_start).trace(initcall_latency,func,\$lat)' +compare_file_partial "events/initcall/initcall_finish/enable" "1" + +compare_file "instances/foo/current_tracer" "function" +file_contains "instances/foo/set_ftrace_filter" "^user" +compare_file "instances/foo/buffer_size_kb" "512" +compare_mask "instances/foo/tracing_cpumask" "1" +compare_file "instances/foo/options/sym-addr" "0" +file_contains "instances/foo/trace_clock" '\[mono\]' +compare_file_partial "instances/foo/events/signal/signal_deliver/trigger" "snapshot" + +compare_file "instances/bar/current_tracer" "function" +file_contains "instances/bar/set_ftrace_filter" "^kernel" +compare_mask "instances/bar/tracing_cpumask" "2" +file_contains "instances/bar/trace_clock" '\[x86-tsc\]' + +file_contains "snapshot" "Snapshot is allocated" +compare_file "options/sym-addr" "1" +compare_file "events/initcall/enable" "1" +compare_file "buffer_size_kb" "1027" +compare_file "current_tracer" "function" +file_contains "set_ftrace_filter" '^vfs' + +exit 0 diff --git a/tools/testing/ktest/examples/bootconfigs/verify-functiongraph.sh b/tools/testing/ktest/examples/bootconfigs/verify-functiongraph.sh new file mode 100755 index 000000000..b50baa10f --- /dev/null +++ b/tools/testing/ktest/examples/bootconfigs/verify-functiongraph.sh @@ -0,0 +1,61 @@ +#!/bin/sh + +cd /sys/kernel/tracing + +compare_file() { + file="$1" + val="$2" + content=`cat $file` + if [ "$content" != "$val" ]; then + echo "FAILED: $file has '$content', expected '$val'" + exit 1 + fi +} + +compare_file_partial() { + file="$1" + val="$2" + content=`cat $file | sed -ne "/^$val/p"` + if [ -z "$content" ]; then + echo "FAILED: $file does not contain '$val'" + cat $file + exit 1 + fi +} + +file_contains() { + file=$1 + val="$2" + + if ! grep -q "$val" $file ; then + echo "FAILED: $file does not contain $val" + cat $file + exit 1 + fi +} + +compare_mask() { + file=$1 + val="$2" + + content=`cat $file | sed -ne "/^[0 ]*$val/p"` + if [ -z "$content" ]; then + echo "FAILED: $file does not have mask '$val'" + cat $file + exit 1 + fi +} + + +compare_file "tracing_on" "0" +compare_file "current_tracer" "function_graph" + +compare_file_partial "events/kprobes/start_event/enable" "1" +compare_file_partial "events/kprobes/start_event/trigger" "traceon" +file_contains "kprobe_events" 'start_event.*pci_proc_init' + +compare_file_partial "events/kprobes/end_event/enable" "1" +compare_file_partial "events/kprobes/end_event/trigger" "traceoff" +file_contains "kprobe_events" '^r.*end_event.*pci_proc_init' + +exit 0 diff --git a/tools/testing/ktest/examples/bootconfigs/verify-tracing.sh b/tools/testing/ktest/examples/bootconfigs/verify-tracing.sh new file mode 100755 index 000000000..01e111e36 --- /dev/null +++ b/tools/testing/ktest/examples/bootconfigs/verify-tracing.sh @@ -0,0 +1,72 @@ +#!/bin/sh + +cd /sys/kernel/tracing + +compare_file() { + file="$1" + val="$2" + content=`cat $file` + if [ "$content" != "$val" ]; then + echo "FAILED: $file has '$content', expected '$val'" + exit 1 + fi +} + +compare_file_partial() { + file="$1" + val="$2" + content=`cat $file | sed -ne "/^$val/p"` + if [ -z "$content" ]; then + echo "FAILED: $file does not contain '$val'" + cat $file + exit 1 + fi +} + +file_contains() { + file=$1 + val="$2" + + if ! grep -q "$val" $file ; then + echo "FAILED: $file does not contain $val" + cat $file + exit 1 + fi +} + +compare_mask() { + file=$1 + val="$2" + + content=`cat $file | sed -ne "/^[0 ]*$val/p"` + if [ -z "$content" ]; then + echo "FAILED: $file does not have mask '$val'" + cat $file + exit 1 + fi +} + +compare_file "current_tracer" "function_graph" +compare_file "options/event-fork" "1" +compare_file "options/sym-addr" "1" +compare_file "options/stacktrace" "1" +compare_file "buffer_size_kb" "1024" +file_contains "snapshot" "Snapshot is allocated" +file_contains "trace_clock" '\[global\]' + +compare_file "events/initcall/enable" "1" +compare_file "events/task/task_newtask/enable" "1" +compare_file "events/sched/sched_process_exec/filter" "pid < 128" +compare_file "events/kprobes/enable" "1" + +compare_file "instances/bar/events/kprobes/myevent/enable" "1" +compare_file "instances/bar/events/kprobes/myevent2/enable" "1" +compare_file "instances/bar/events/kprobes/myevent3/enable" "1" + +compare_file "instances/foo/current_tracer" "function" +compare_file "instances/foo/tracing_on" "0" + +compare_file "/proc/sys/kernel/ftrace_dump_on_oops" "2" +compare_file "/proc/sys/kernel/traceoff_on_warning" "1" + +exit 0 diff --git a/tools/testing/ktest/examples/crosstests.conf b/tools/testing/ktest/examples/crosstests.conf new file mode 100644 index 000000000..3b15e85f2 --- /dev/null +++ b/tools/testing/ktest/examples/crosstests.conf @@ -0,0 +1,225 @@ +# +# Example config for cross compiling +# +# In this config, it is expected that the tool chains from: +# +# https://kernel.org/pub/tools/crosstool/files/bin/x86_64/ +# +# running on a x86_64 system have been downloaded and installed into: +# +# /usr/local/ +# +# such that the compiler binaries are something like: +# +# /usr/local/gcc-4.5.2-nolibc/mips-linux/bin/mips-linux-gcc +# +# Some of the archs will use gcc-4.5.1 instead of gcc-4.5.2 +# this config uses variables to differentiate them. +# +# Comments describe some of the options, but full descriptions of +# options are described in the samples.conf file. + +# ${PWD} is defined by ktest.pl to be the directory that the user +# was in when they executed ktest.pl. It may be better to hardcode the +# path name here. THIS_DIR is the variable used through out the config file +# in case you want to change it. + +THIS_DIR := ${PWD} + +# Update the BUILD_DIR option to the location of your git repo you want to test. +BUILD_DIR = ${THIS_DIR}/linux.git + +# The build will go into this directory. It will be created when you run the test. +OUTPUT_DIR = ${THIS_DIR}/cross-compile + +# The build will be compiled with -j8 +BUILD_OPTIONS = -j8 + +# The test will not stop when it hits a failure. +DIE_ON_FAILURE = 0 + +# If you want to have ktest.pl store the failure somewhere, uncomment this option +# and change the directory where ktest should store the failures. +#STORE_FAILURES = ${THIS_DIR}/failures + +# The log file is stored in the OUTPUT_DIR called cross.log +# If you enable this, you need to create the OUTPUT_DIR. It wont be created for you. +LOG_FILE = ${OUTPUT_DIR}/cross.log + +# The log file will be cleared each time you run ktest. +CLEAR_LOG = 1 + +# As some archs do not build with the defconfig, they have been marked +# to be ignored. If you want to test them anyway, change DO_FAILED to 1. +# If a test that has been marked as DO_FAILED passes, then you should change +# that test to be DO_DEFAULT + +DO_FAILED := 0 +DO_DEFAULT := 1 + +# By setting both DO_FAILED and DO_DEFAULT to zero, you can pick a single +# arch that you want to test. (uncomment RUN and chose your arch) +#RUN := arm + +# At the bottom of the config file exists a bisect test. You can update that +# test and set DO_FAILED and DO_DEFAULT to zero, and uncomment this variable +# to run the bisect on the arch. +#RUN := bisect + +# By default all tests will be running gcc 4.5.2. Some tests are using 4.5.1 +# and they select that in the test. +# Note: GCC_VER is declared as on option and not a variable ('=' instead of ':=') +# This is important. A variable is used only in the config file and if it is set +# it stays that way for the rest of the config file until it is change again. +# Here we want GCC_VER to remain persistent and change for each test, as it is used in +# the MAKE_CMD. By using '=' instead of ':=' we achieve our goal. + +GCC_VER = 4.5.2 +MAKE_CMD = PATH=/usr/local/gcc-${GCC_VER}-nolibc/${CROSS}/bin:$PATH CROSS_COMPILE=${CROSS}- make ARCH=${ARCH} + +# all tests are only doing builds. +TEST_TYPE = build + +# If you want to add configs on top of the defconfig, you can add those configs into +# the add-config file and uncomment this option. This is useful if you want to test +# all cross compiles with PREEMPT set, or TRACING on, etc. +#ADD_CONFIG = ${THIS_DIR}/add-config + +# All tests are using defconfig +BUILD_TYPE = defconfig + +# The test names will have the arch and cross compiler used. This will be shown in +# the results. +TEST_NAME = ${ARCH} ${CROSS} + +# alpha +TEST_START IF ${RUN} == alpha || ${DO_DEFAULT} +# Notice that CROSS and ARCH are also options and not variables (again '=' instead +# of ':='). This is because TEST_NAME and MAKE_CMD wil use them for each test. +# Only options are available during runs. Variables are only present in parsing the +# config file. +CROSS = alpha-linux +ARCH = alpha + +# arm +TEST_START IF ${RUN} == arm || ${DO_DEFAULT} +CROSS = arm-unknown-linux-gnueabi +ARCH = arm + +# ia64 +TEST_START IF ${RUN} == ia64 || ${DO_DEFAULT} +CROSS = ia64-linux +ARCH = ia64 + +# m68k fails with error? +TEST_START IF ${RUN} == m68k || ${DO_DEFAULT} +CROSS = m68k-linux +ARCH = m68k + +# mips64 +TEST_START IF ${RUN} == mips || ${RUN} == mips64 || ${DO_DEFAULT} +CROSS = mips64-linux +ARCH = mips + +# mips32 +TEST_START IF ${RUN} == mips || ${RUN} == mips32 || ${DO_DEFAULT} +CROSS = mips-linux +ARCH = mips + +# parisc64 failed? +TEST_START IF ${RUN} == hppa || ${RUN} == hppa64 || ${DO_FAILED} +CROSS = hppa64-linux +ARCH = parisc + +# parisc +TEST_START IF ${RUN} == hppa || ${RUN} == hppa32 || ${DO_FAILED} +CROSS = hppa-linux +ARCH = parisc + +# ppc +TEST_START IF ${RUN} == ppc || ${RUN} == ppc32 || ${DO_DEFAULT} +CROSS = powerpc-linux +ARCH = powerpc + +# ppc64 +TEST_START IF ${RUN} == ppc || ${RUN} == ppc64 || ${DO_DEFAULT} +CROSS = powerpc64-linux +ARCH = powerpc + +# s390 +TEST_START IF ${RUN} == s390 || ${DO_DEFAULT} +CROSS = s390x-linux +ARCH = s390 + +# sh +TEST_START IF ${RUN} == sh || ${DO_DEFAULT} +CROSS = sh4-linux +ARCH = sh + +# sparc64 +TEST_START IF ${RUN} == sparc || ${RUN} == sparc64 || ${DO_DEFAULT} +CROSS = sparc64-linux +ARCH = sparc64 + +# sparc +TEST_START IF ${RUN} == sparc || ${RUN} == sparc32 || ${DO_DEFAULT} +CROSS = sparc-linux +ARCH = sparc + +# xtensa failed +TEST_START IF ${RUN} == xtensa || ${DO_FAILED} +CROSS = xtensa-linux +ARCH = xtensa + +# UML +TEST_START IF ${RUN} == uml || ${DO_DEFAULT} +MAKE_CMD = make ARCH=um SUBARCH=x86_64 +ARCH = uml +CROSS = + +TEST_START IF ${RUN} == x86 || ${RUN} == i386 || ${DO_DEFAULT} +MAKE_CMD = make ARCH=i386 +ARCH = i386 +CROSS = + +TEST_START IF ${RUN} == x86 || ${RUN} == x86_64 || ${DO_DEFAULT} +MAKE_CMD = make ARCH=x86_64 +ARCH = x86_64 +CROSS = + +################################# + +# This is a bisect if needed. You need to give it a MIN_CONFIG that +# will be the config file it uses. Basically, just copy the created defconfig +# for the arch someplace and point MIN_CONFIG to it. +TEST_START IF ${RUN} == bisect +MIN_CONFIG = ${THIS_DIR}/min-config +CROSS = s390x-linux +ARCH = s390 +TEST_TYPE = bisect +BISECT_TYPE = build +BISECT_GOOD = v3.1 +BISECT_BAD = v3.2 +CHECKOUT = v3.2 + +################################# + +# These defaults are needed to keep ktest.pl from complaining. They are +# ignored because the test does not go pass the build. No install or +# booting of the target images. + +DEFAULTS +MACHINE = crosstest +SSH_USER = root +BUILD_TARGET = cross +TARGET_IMAGE = image +POWER_CYCLE = cycle +CONSOLE = console +LOCALVERSION = version +GRUB_MENU = grub + +REBOOT_ON_ERROR = 0 +POWEROFF_ON_ERROR = 0 +POWEROFF_ON_SUCCESS = 0 +REBOOT_ON_SUCCESS = 0 + diff --git a/tools/testing/ktest/examples/include/bisect.conf b/tools/testing/ktest/examples/include/bisect.conf new file mode 100644 index 000000000..009bea65b --- /dev/null +++ b/tools/testing/ktest/examples/include/bisect.conf @@ -0,0 +1,90 @@ +# +# This example shows the bisect tests (git bisect and config bisect) +# + + +# The config that includes this file may define a RUN_TEST +# variable that will tell this config what test to run. +# (what to set the TEST option to). +# +DEFAULTS IF NOT DEFINED RUN_TEST +# Requires that hackbench is in the PATH +RUN_TEST := ${SSH} hackbench 50 + + +# Set TEST to 'bisect' to do a normal git bisect. You need +# to modify the options below to make it bisect the exact +# commits you are interested in. +# +TEST_START IF ${TEST} == bisect +TEST_TYPE = bisect +# You must set the commit that was considered good (git bisect good) +BISECT_GOOD = v3.3 +# You must set the commit that was considered bad (git bisect bad) +BISECT_BAD = HEAD +# It's best to specify the branch to checkout before starting the bisect. +CHECKOUT = origin/master +# This can be build, boot, or test. Here we are doing a bisect +# that requires to run a test to know if the bisect was good or bad. +# The test should exit with 0 on good, non-zero for bad. But see +# the BISECT_RET_* options in samples.conf to override this. +BISECT_TYPE = test +TEST = ${RUN_TEST} +# It is usually a good idea to confirm that the GOOD and the BAD +# commits are truly good and bad respectively. Having BISECT_CHECK +# set to 1 will check both that the good commit works and the bad +# commit fails. If you only want to check one or the other, +# set BISECT_CHECK to 'good' or to 'bad'. +BISECT_CHECK = 1 +#BISECT_CHECK = good +#BISECT_CHECK = bad + +# Usually it's a good idea to specify the exact config you +# want to use throughout the entire bisect. Here we placed +# it in the directory we called ktest.pl from and named it +# 'config-bisect'. +MIN_CONFIG = ${THIS_DIR}/config-bisect +# By default, if we are doing a BISECT_TYPE = test run but the +# build or boot fails, ktest.pl will do a 'git bisect skip'. +# Uncomment the below option to make ktest stop testing on such +# an error. +#BISECT_SKIP = 0 +# Now if you had BISECT_SKIP = 0 and the test fails, you can +# examine what happened and then do 'git bisect log > /tmp/replay' +# Set BISECT_REPLAY to /tmp/replay and ktest.pl will run the +# 'git bisect replay /tmp/replay' before continuing the bisect test. +#BISECT_REPLAY = /tmp/replay +# If you used BISECT_REPLAY after the bisect test failed, you may +# not want to continue the bisect on that commit that failed. +# By setting BISECT_START to a new commit. ktest.pl will checkout +# that commit after it has performed the 'git bisect replay' but +# before it continues running the bisect test. +#BISECT_START = 2545eb6198e7e1ec50daa0cfc64a4cdfecf24ec9 + +# Now if you don't trust ktest.pl to make the decisions for you, then +# set BISECT_MANUAL to 1. This will cause ktest.pl not to decide +# if the commit was good or bad. Instead, it will ask you to tell +# it if the current commit was good. In the mean time, you could +# take the result, load it on any machine you want. Run several tests, +# or whatever you feel like. Then, when you are happy, you can tell +# ktest if you think it was good or not and ktest.pl will continue +# the git bisect. You can even change what commit it is currently at. +#BISECT_MANUAL = 1 + + +# One of the unique tests that ktest does is the config bisect. +# Currently (which hopefully will be fixed soon), the bad config +# must be a superset of the good config. This is because it only +# searches for a config that causes the target to fail. If the +# good config is not a subset of the bad config, or if the target +# fails because of a lack of a config, then it will not find +# the config for you. +TEST_START IF ${TEST} == config-bisect +TEST_TYPE = config_bisect +# set to build, boot, test +CONFIG_BISECT_TYPE = boot +# Set the config that is considered bad. +CONFIG_BISECT = ${THIS_DIR}/config-bad +# This config is optional. By default it uses the +# MIN_CONFIG as the good config. +CONFIG_BISECT_GOOD = ${THIS_DIR}/config-good diff --git a/tools/testing/ktest/examples/include/bootconfig.conf b/tools/testing/ktest/examples/include/bootconfig.conf new file mode 100644 index 000000000..3b885de08 --- /dev/null +++ b/tools/testing/ktest/examples/include/bootconfig.conf @@ -0,0 +1,69 @@ +# bootconfig.conf +# +# Tests to test some bootconfig scripts + +# List where on the target machine the initrd is used +INITRD := /boot/initramfs-test.img + +# Install bootconfig on the target machine and define the path here. +BOOTCONFIG := /usr/bin/bootconfig + +# Currenty we just build the .config in the BUILD_DIR +BUILD_TYPE := oldconfig + +# Helper macro to run bootconfig on the target +# SSH is defined in include/defaults.conf +ADD_BOOTCONFIG := ${SSH} "${BOOTCONFIG} -d ${INITRD} && ${BOOTCONFIG} -a /tmp/${BOOTCONFIG_FILE} ${INITRD}" + +# This copies a bootconfig script to the target and then will +# add it to the initrd. SSH_USER is defined in include/defaults.conf +# and MACHINE is defined in the example configs. +BOOTCONFIG_TEST_PREP = scp ${BOOTCONFIG_PATH}${BOOTCONFIG_FILE} ${SSH_USER}@${MACHINE}:/tmp && ${ADD_BOOTCONFIG} + +# When a test is complete, remove the bootconfig from the initrd. +CLEAR_BOOTCONFIG := ${SSH} "${BOOTCONFIG} -d ${INITRD}" + +# Run a verifier on the target after it had booted, to make sure that the +# bootconfig script did what it was expected to do +DO_TEST = scp ${BOOTCONFIG_PATH}${BOOTCONFIG_VERIFY} ${SSH_USER}@${MACHINE}:/tmp && ${SSH} /tmp/${BOOTCONFIG_VERIFY} + +# Comment this out to not run the boot configs +RUN_BOOTCONFIG := 1 + +TEST_START IF DEFINED RUN_BOOTCONFIG +TEST_TYPE = test +TEST_NAME = bootconfig boottrace +# Just testing the bootconfig on initrd, no need to build the kernel +BUILD_TYPE = nobuild +BOOTCONFIG_FILE = boottrace.bconf +BOOTCONFIG_VERIFY = verify-boottrace.sh +ADD_CONFIG = ${ADD_CONFIG} ${BOOTCONFIG_PATH}/config-bootconfig +PRE_TEST = ${BOOTCONFIG_TEST_PREP} +PRE_TEST_DIE = 1 +TEST = ${DO_TEST} +POST_TEST = ${CLEAR_BOOTCONFIG} + +TEST_START IF DEFINED RUN_BOOTCONFIG +TEST_TYPE = test +TEST_NAME = bootconfig function graph +BUILD_TYPE = nobuild +BOOTCONFIG_FILE = functiongraph.bconf +BOOTCONFIG_VERIFY = verify-functiongraph.sh +ADD_CONFIG = ${ADD_CONFIG} ${BOOTCONFIG_PATH}/config-bootconfig +PRE_TEST = ${BOOTCONFIG_TEST_PREP} +PRE_TEST_DIE = 1 +TEST = ${DO_TEST} +POST_TEST = ${CLEAR_BOOTCONFIG} + +TEST_START IF DEFINED RUN_BOOTCONFIG +TEST_TYPE = test +TEST_NAME = bootconfig tracing +BUILD_TYPE = nobuild +BOOTCONFIG_FILE = tracing.bconf +BOOTCONFIG_VERIFY = verify-tracing.sh +ADD_CONFIG = ${ADD_CONFIG} ${BOOTCONFIG_PATH}/config-bootconfig +PRE_TEST = ${BOOTCONFIG_TEST_PREP} +PRE_TEST_DIE = 1 +TEST = ${DO_TEST} +POST_TEST = ${CLEAR_BOOTCONFIG} + diff --git a/tools/testing/ktest/examples/include/defaults.conf b/tools/testing/ktest/examples/include/defaults.conf new file mode 100644 index 000000000..f6d8517a4 --- /dev/null +++ b/tools/testing/ktest/examples/include/defaults.conf @@ -0,0 +1,157 @@ +# This file holds defaults for most the tests. It defines the options that +# are most common to tests that are likely to be shared. +# +# Note, after including this file, a config file may override any option +# with a DEFAULTS OVERRIDE section. +# + +# For those cases that use the same machine to boot a 64 bit +# and a 32 bit version. The MACHINE is the DNS name to get to the +# box (usually different if it was 64 bit or 32 bit) but the +# BOX here is defined as a variable that will be the name of the box +# itself. It is useful for calling scripts that will power cycle +# the box, as only one script needs to be created to power cycle +# even though the box itself has multiple operating systems on it. +# By default, BOX and MACHINE are the same. + +DEFAULTS IF NOT DEFINED BOX +BOX := ${MACHINE} + + +# Consider each box as 64 bit box, unless the config including this file +# has defined BITS = 32 + +DEFAULTS IF NOT DEFINED BITS +BITS := 64 + + +DEFAULTS + +# THIS_DIR is used through out the configs and defaults to ${PWD} which +# is the directory that ktest.pl was called from. + +THIS_DIR := ${PWD} + + +# to organize your configs, having each machine save their configs +# into a separate directly is useful. +CONFIG_DIR := ${THIS_DIR}/configs/${MACHINE} + +# Reset the log before running each test. +CLEAR_LOG = 1 + +# As installing kernels usually requires root privilege, default the +# user on the target as root. It is also required that the target +# allows ssh to root from the host without asking for a password. + +SSH_USER = root + +# For accessing the machine, we will ssh to root@machine. +SSH := ssh ${SSH_USER}@${MACHINE} + +# Update this. The default here is ktest will ssh to the target box +# and run a script called 'run-test' located on that box. +TEST = ${SSH} run-test + +# Point build dir to the git repo you use +BUILD_DIR = ${THIS_DIR}/linux.git + +# Each machine will have its own output build directory. +OUTPUT_DIR = ${THIS_DIR}/build/${MACHINE} + +# Yes this config is focused on x86 (but ktest works for other archs too) +BUILD_TARGET = arch/x86/boot/bzImage +TARGET_IMAGE = /boot/vmlinuz-test + +# have directory for the scripts to reboot and power cycle the boxes +SCRIPTS_DIR := ${THIS_DIR}/scripts + +# You can have each box/machine have a script to power cycle it. +# Name your script <box>-cycle. +POWER_CYCLE = ${SCRIPTS_DIR}/${BOX}-cycle + +# This script is used to power off the box. +POWER_OFF = ${SCRIPTS_DIR}/${BOX}-poweroff + +# Keep your test kernels separate from your other kernels. +LOCALVERSION = -test + +# The /boot/grub/menu.lst is searched for the line: +# title Test Kernel +# and ktest will use that kernel to reboot into. +# For grub2 or other boot loaders, you need to set BOOT_TYPE +# to 'script' and define other ways to load the kernel. +# See snowball.conf example. +# +GRUB_MENU = Test Kernel + +# The kernel build will use this option. +BUILD_OPTIONS = -j8 + +# Keeping the log file with the output dir is convenient. +LOG_FILE = ${OUTPUT_DIR}/${MACHINE}.log + +# Each box should have their own minum configuration +# See min-config.conf +MIN_CONFIG = ${CONFIG_DIR}/config-min + +# For things like randconfigs, there may be configs you find that +# are already broken, or there may be some configs that you always +# want set. Uncomment ADD_CONFIG and point it to the make config files +# that set the configs you want to keep on (or off) in your build. +# ADD_CONFIG is usually something to add configs to all machines, +# where as, MIN_CONFIG is specific per machine. +#ADD_CONFIG = ${THIS_DIR}/config-broken ${THIS_DIR}/config-general + +# To speed up reboots for bisects and patchcheck, instead of +# waiting 60 seconds for the console to be idle, if this line is +# seen in the console output, ktest will know the good kernel has +# finished rebooting and it will be able to continue the tests. +REBOOT_SUCCESS_LINE = ${MACHINE} login: + +# The following is different ways to end the test. +# by setting the variable REBOOT to: none, error, fail or +# something else, ktest will power cycle or reboot the target box +# at the end of the tests. +# +# REBOOT := none +# Don't do anything at the end of the test. +# +# REBOOT := error +# Reboot the box if ktest detects an error +# +# REBOOT := fail +# Do not stop on failure, and after all tests are complete +# power off the box (for both success and error) +# This is good to run over a weekend and you don't want to waste +# electricity. +# + +DEFAULTS IF ${REBOOT} == none +REBOOT_ON_SUCCESS = 0 +REBOOT_ON_ERROR = 0 +POWEROFF_ON_ERROR = 0 +POWEROFF_ON_SUCCESS = 0 + +DEFAULTS ELSE IF ${REBOOT} == error +REBOOT_ON_SUCCESS = 0 +REBOOT_ON_ERROR = 1 +POWEROFF_ON_ERROR = 0 +POWEROFF_ON_SUCCESS = 0 + +DEFAULTS ELSE IF ${REBOOT} == fail +REBOOT_ON_SUCCESS = 0 +POWEROFF_ON_ERROR = 1 +POWEROFF_ON_SUCCESS = 1 +POWEROFF_AFTER_HALT = 120 +DIE_ON_FAILURE = 0 + +# Store the failure information into this directory +# such as the .config, dmesg, and build log. +STORE_FAILURES = ${THIS_DIR}/failures + +DEFAULTS ELSE +REBOOT_ON_SUCCESS = 1 +REBOOT_ON_ERROR = 1 +POWEROFF_ON_ERROR = 0 +POWEROFF_ON_SUCCESS = 0 diff --git a/tools/testing/ktest/examples/include/min-config.conf b/tools/testing/ktest/examples/include/min-config.conf new file mode 100644 index 000000000..c703cc46d --- /dev/null +++ b/tools/testing/ktest/examples/include/min-config.conf @@ -0,0 +1,60 @@ +# +# This file has some examples for creating a MIN_CONFIG. +# (A .config file that is the minimum for a machine to boot, or +# to boot and make a network connection.) +# +# A MIN_CONFIG is very useful as it is the minimum configuration +# needed to boot a given machine. You can debug someone else's +# .config by only setting the configs in your MIN_CONFIG. The closer +# your MIN_CONFIG is to the true minimum set of configs needed to +# boot your machine, the closer the config you test with will be +# to the users config that had the failure. +# +# The make_min_config test allows you to create a MIN_CONFIG that +# is truly the minimum set of configs needed to boot a box. +# +# In this example, the final config will reside in +# ${CONFIG_DIR}/config-new-min and ${CONFIG_DIR}/config-new-min-net. +# Just move one to the location you have set for MIN_CONFIG. +# +# The first test creates a MIN_CONFIG that will be the minimum +# configuration to boot ${MACHINE} and be able to ssh to it. +# +# The second test creates a MIN_CONFIG that will only boot +# the target and most likely will not let you ssh to it. (Notice +# how the second test uses the first test's result to continue with. +# This is because the second test config is a subset of the first). +# +# The ${CONFIG_DIR}/config-skip (and -net) will hold the configs +# that ktest.pl found would not boot the target without them set. +# The config-new-min holds configs that ktest.pl could not test +# directly because another config that was needed to boot the box +# selected them. Sometimes it is possible that this file will hold +# the true minimum configuration. You can test to see if this is +# the case by running the boot test with BOOT_TYPE = allnoconfig and +# setting setting the MIN_CONFIG to ${CONFIG_DIR}/config-skip. If the +# machine still boots, then you can use the config-skip as your MIN_CONFIG. +# +# These tests can run for several hours (and perhaps days). +# It's OK to kill the test with a Ctrl^C. By restarting without +# modifying this config, ktest.pl will notice that the config-new-min(-net) +# exists, and will use that instead as the starting point. +# The USE_OUTPUT_MIN_CONFIG is set to 1 to keep ktest.pl from asking +# you if you want to use the OUTPUT_MIN_CONFIG as the starting point. +# By using the OUTPUT_MIN_CONFIG as the starting point will allow ktest.pl to +# start almost where it left off. +# +TEST_START IF ${TEST} == min-config +TEST_TYPE = make_min_config +OUTPUT_MIN_CONFIG = ${CONFIG_DIR}/config-new-min-net +IGNORE_CONFIG = ${CONFIG_DIR}/config-skip-net +MIN_CONFIG_TYPE = test +TEST = ${SSH} echo hi +USE_OUTPUT_MIN_CONFIG = 1 + +TEST_START IF ${TEST} == min-config && ${MULTI} +TEST_TYPE = make_min_config +OUTPUT_MIN_CONFIG = ${CONFIG_DIR}/config-new-min +IGNORE_CONFIG = ${CONFIG_DIR}/config-skip +MIN_CONFIG = ${CONFIG_DIR}/config-new-min-net +USE_OUTPUT_MIN_CONFIG = 1 diff --git a/tools/testing/ktest/examples/include/patchcheck.conf b/tools/testing/ktest/examples/include/patchcheck.conf new file mode 100644 index 000000000..0eb0a5ac7 --- /dev/null +++ b/tools/testing/ktest/examples/include/patchcheck.conf @@ -0,0 +1,111 @@ +# patchcheck.conf +# +# This contains a test that takes two git commits and will test each +# commit between the two. The build test will look at what files the +# commit has touched, and if any of those files produce a warning, then +# the build will fail. + + +# PATCH_START is the commit to begin with and PATCH_END is the commit +# to end with (inclusive). This is similar to doing a git rebase -i PATCH_START~1 +# and then testing each commit and doing a git rebase --continue. +# You can use a SHA1, a git tag, or anything that git will accept for a checkout + +PATCH_START := HEAD~3 +PATCH_END := HEAD + +# Use the oldconfig if build_type wasn't defined +DEFAULTS IF NOT DEFINED BUILD_TYPE +DO_BUILD_TYPE := oldconfig + +DEFAULTS ELSE +DO_BUILD_TYPE := ${BUILD_TYPE} + +DEFAULTS + + +# Change PATCH_CHECKOUT to be the branch you want to test. The test will +# do a git checkout of this branch before starting. Obviously both +# PATCH_START and PATCH_END must be in this branch (and PATCH_START must +# be contained by PATCH_END). + +PATCH_CHECKOUT := test/branch + +# Usually it's a good idea to have a set config to use for testing individual +# patches. +PATCH_CONFIG := ${CONFIG_DIR}/config-patchcheck + +# Change PATCH_TEST to run some test for each patch. Each commit that is +# tested, after it is built and installed on the test machine, this command +# will be executed. Usually what is done is to ssh to the target box and +# run some test scripts. If you just want to boot test your patches +# comment PATCH_TEST out. +PATCH_TEST := ${SSH} "/usr/local/bin/ktest-test-script" + +DEFAULTS IF DEFINED PATCH_TEST +PATCH_TEST_TYPE := test + +DEFAULTS ELSE +PATCH_TEST_TYPE := boot + +# If for some reason a file has a warning that one of your patches touch +# but you do not care about it, set IGNORE_WARNINGS to that commit(s) +# (space delimited) +#IGNORE_WARNINGS = 39eaf7ef884dcc44f7ff1bac803ca2a1dcf43544 6edb2a8a385f0cdef51dae37ff23e74d76d8a6ce + +# Instead of just checking for warnings to files that are changed +# it can be advantageous to check for any new warnings. If a +# header file is changed, it could cause a warning in a file not +# touched by the commit. To detect these kinds of warnings, you +# can use the WARNINGS_FILE option. +# +# If the variable CREATE_WARNINGS_FILE is set, this config will +# enable the WARNINGS_FILE during the patchcheck test. Also, +# before running the patchcheck test, it will create the +# warnings file. +# +DEFAULTS IF DEFINED CREATE_WARNINGS_FILE +WARNINGS_FILE = ${OUTPUT_DIR}/warnings_file + +TEST_START IF DEFINED CREATE_WARNINGS_FILE +# WARNINGS_FILE is already set by the DEFAULTS above +TEST_TYPE = make_warnings_file +# Checkout the commit before the patches to test, +# and record all the warnings that exist before the patches +# to test are added +CHECKOUT = ${PATCHCHECK_START}~1 +# Force a full build +BUILD_NOCLEAN = 0 +BUILD_TYPE = ${DO_BUILD_TYPE} + +# If you are running a multi test, and the test failed on the first +# test but on, say the 5th patch. If you want to restart on the +# fifth patch, set PATCH_START1. This will make the first test start +# from this commit instead of the PATCH_START commit. +# Note, do not change this option. Just define PATCH_START1 in the +# top config (the one you pass to ktest.pl), and this will use it, +# otherwise it will just use PATCH_START if PATCH_START1 is not defined. +DEFAULTS IF NOT DEFINED PATCH_START1 +PATCH_START1 := ${PATCH_START} + +TEST_START IF ${TEST} == patchcheck +TEST_TYPE = patchcheck +MIN_CONFIG = ${PATCH_CONFIG} +TEST = ${PATCH_TEST} +PATCHCHECK_TYPE = ${PATCH_TEST_TYPE} +PATCHCHECK_START = ${PATCH_START1} +PATCHCHECK_END = ${PATCH_END} +CHECKOUT = ${PATCH_CHECKOUT} +BUILD_TYPE = ${DO_BUILD_TYPE} + +TEST_START IF ${TEST} == patchcheck && ${MULTI} +TEST_TYPE = patchcheck +MIN_CONFIG = ${PATCH_CONFIG} +TEST = ${PATCH_TEST} +PATCHCHECK_TYPE = ${PATCH_TEST_TYPE} +PATCHCHECK_START = ${PATCH_START} +PATCHCHECK_END = ${PATCH_END} +CHECKOUT = ${PATCH_CHECKOUT} +# Use multi to test different compilers? +MAKE_CMD = CC=gcc-4.5.1 make +BUILD_TYPE = ${DO_BUILD_TYPE} diff --git a/tools/testing/ktest/examples/include/tests.conf b/tools/testing/ktest/examples/include/tests.conf new file mode 100644 index 000000000..60cedb1a1 --- /dev/null +++ b/tools/testing/ktest/examples/include/tests.conf @@ -0,0 +1,74 @@ +# +# This is an example of various tests that you can run +# +# The variable TEST can be of boot, build, randconfig, or test. +# +# Note that TEST is a variable created with ':=' and only exists +# throughout the config processing (not during the tests itself). +# +# The TEST option (defined with '=') is used to tell ktest.pl +# what test to run after a successful boot. The TEST option is +# persistent into the test runs. +# + +# The config that includes this file may define a BOOT_TYPE +# variable that tells this config what type of boot test to run. +# If it's not defined, the below DEFAULTS will set the default +# to 'oldconfig'. +# +DEFAULTS IF NOT DEFINED BOOT_TYPE +BOOT_TYPE := oldconfig + +# The config that includes this file may define a RUN_TEST +# variable that will tell this config what test to run. +# (what to set the TEST option to). +# +DEFAULTS IF NOT DEFINED RUN_TEST +# Requires that hackbench is in the PATH +RUN_TEST := ${SSH} hackbench 50 + + +# If TEST is set to 'boot' then just build a kernel and boot +# the target. +TEST_START IF ${TEST} == boot +TEST_TYPE = boot +# Notice how we set the BUILD_TYPE option to the BOOT_TYPE variable. +BUILD_TYPE = ${BOOT_TYPE} +# Do not do a make mrproper. +BUILD_NOCLEAN = 1 + +# If you only want to build the kernel, and perhaps install +# and test it yourself, then just set TEST to build. +TEST_START IF ${TEST} == build +TEST_TYPE = build +BUILD_TYPE = ${BOOT_TYPE} +BUILD_NOCLEAN = 1 + +# Build, install, boot and test with a randconfg 10 times. +# It is important that you have set MIN_CONFIG in the config +# that includes this file otherwise it is likely that the +# randconfig will not have the necessary configs needed to +# boot your box. This version of the test requires a min +# config that has enough to make sure the target has network +# working. +TEST_START ITERATE 10 IF ${TEST} == randconfig +MIN_CONFIG = ${CONFIG_DIR}/config-min-net +TEST_TYPE = test +BUILD_TYPE = randconfig +TEST = ${RUN_TEST} + +# This is the same as above, but only tests to a boot prompt. +# The MIN_CONFIG used here does not need to have networking +# working. +TEST_START ITERATE 10 IF ${TEST} == randconfig && ${MULTI} +TEST_TYPE = boot +BUILD_TYPE = randconfig +MIN_CONFIG = ${CONFIG_DIR}/config-min +MAKE_CMD = make + +# This builds, installs, boots and tests the target. +TEST_START IF ${TEST} == test +TEST_TYPE = test +BUILD_TYPE = ${BOOT_TYPE} +TEST = ${RUN_TEST} +BUILD_NOCLEAN = 1 diff --git a/tools/testing/ktest/examples/kvm.conf b/tools/testing/ktest/examples/kvm.conf new file mode 100644 index 000000000..c700e8bb7 --- /dev/null +++ b/tools/testing/ktest/examples/kvm.conf @@ -0,0 +1,93 @@ +# +# This config is an example usage of ktest.pl with a kvm guest +# +# The guest is called 'Guest' and this would be something that +# could be run on the host to test a virtual machine target. + +MACHINE = Guest + + +# Use virsh to read the serial console of the guest +CONSOLE = virsh console ${MACHINE} + +# Use SIGKILL to terminate virsh console. We can't kill virsh console +# by the default signal, SIGINT. +CLOSE_CONSOLE_SIGNAL = KILL + +#*************************************# +# This part is the same as test.conf # +#*************************************# + +# The include files will set up the type of test to run. Just set TEST to +# which test you want to run. +# +# TESTS = patchcheck, randconfig, boot, test, config-bisect, bisect, min-config +# +# See the include/*.conf files that define these tests +# +TEST := patchcheck + +# Some tests may have more than one test to run. Define MULTI := 1 to run +# the extra tests. +MULTI := 0 + +# In case you want to differentiate which type of system you are testing +BITS := 64 + +# REBOOT = none, error, fail, empty +# See include/defaults.conf +REBOOT := empty + + +# The defaults file will set up various settings that can be used by all +# machine configs. +INCLUDE include/defaults.conf + + +#*************************************# +# Now we are different from test.conf # +#*************************************# + + +# The example here assumes that Guest is running a Fedora release +# that uses dracut for its initfs. The POST_INSTALL will be executed +# after the install of the kernel and modules are complete. +# +POST_INSTALL = ${SSH} /sbin/dracut -f /boot/initramfs-test.img $KERNEL_VERSION + +# Guests sometimes get stuck on reboot. We wait 3 seconds after running +# the reboot command and then do a full power-cycle of the guest. +# This forces the guest to restart. +# +POWERCYCLE_AFTER_REBOOT = 3 + +# We do the same after the halt command, but this time we wait 20 seconds. +POWEROFF_AFTER_HALT = 20 + + +# As the defaults.conf file has a POWER_CYCLE option already defined, +# and options can not be defined in the same section more than once +# (all DEFAULTS sections are considered the same). We use the +# DEFAULTS OVERRIDE to tell ktest.pl to ignore the previous defined +# options, for the options set in the OVERRIDE section. +# +DEFAULTS OVERRIDE + +# Instead of using the default POWER_CYCLE option defined in +# defaults.conf, we use virsh to cycle it. To do so, we destroy +# the guest, wait 5 seconds, and then start it up again. +# Crude, but effective. +# +POWER_CYCLE = virsh destroy ${MACHINE}; sleep 5; virsh start ${MACHINE} + + +DEFAULTS + +# The following files each handle a different test case. +# Having them included allows you to set up more than one machine and share +# the same tests. +INCLUDE include/patchcheck.conf +INCLUDE include/tests.conf +INCLUDE include/bisect.conf +INCLUDE include/min-config.conf +INCLUDE include/bootconfig.conf
\ No newline at end of file diff --git a/tools/testing/ktest/examples/snowball.conf b/tools/testing/ktest/examples/snowball.conf new file mode 100644 index 000000000..a82a3c5bc --- /dev/null +++ b/tools/testing/ktest/examples/snowball.conf @@ -0,0 +1,53 @@ +# This example was used to boot the snowball ARM board. +# See http://people.redhat.com/srostedt/ktest-embedded-2012/ + +# 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 := /home/rostedt/work/demo/ktest-embed +LOG_FILE = ${OUTPUT_DIR}/snowball.log +CLEAR_LOG = 1 +MAKE_CMD = PATH=/usr/local/gcc-4.5.2-nolibc/arm-unknown-linux-gnueabi/bin:$PATH CROSS_COMPILE=arm-unknown-linux-gnueabi- make ARCH=arm +ADD_CONFIG = ${THIS_DIR}/addconfig + +SCP_TO_TARGET = echo "don't do scp" + +TFTPBOOT := /var/lib/tftpboot +TFTPDEF := ${TFTPBOOT}/snowball-default +TFTPTEST := ${OUTPUT_DIR}/${BUILD_TARGET} + +SWITCH_TO_GOOD = cp ${TFTPDEF} ${TARGET_IMAGE} +SWITCH_TO_TEST = cp ${TFTPTEST} ${TARGET_IMAGE} + +# Define each test with TEST_START +# The config options below it will override the defaults +TEST_START SKIP +TEST_TYPE = boot +BUILD_TYPE = u8500_defconfig +BUILD_NOCLEAN = 1 + +TEST_START +TEST_TYPE = make_min_config +OUTPUT_MIN_CONFIG = ${THIS_DIR}/config.newmin +START_MIN_CONFIG = ${THIS_DIR}/config.orig +IGNORE_CONFIG = ${THIS_DIR}/config.ignore +BUILD_NOCLEAN = 1 + + +DEFAULTS +LOCALVERSION = -test +POWER_CYCLE = echo use the thumb luke; read a +CONSOLE = cat ${THIS_DIR}/snowball-cat +REBOOT_TYPE = script +SSH_USER = root +BUILD_OPTIONS = -j8 uImage +BUILD_DIR = ${THIS_DIR}/linux.git +OUTPUT_DIR = ${THIS_DIR}/snowball-build +MACHINE = snowball +TARGET_IMAGE = /var/lib/tftpboot/snowball-image +BUILD_TARGET = arch/arm/boot/uImage diff --git a/tools/testing/ktest/examples/test.conf b/tools/testing/ktest/examples/test.conf new file mode 100644 index 000000000..b725210ef --- /dev/null +++ b/tools/testing/ktest/examples/test.conf @@ -0,0 +1,62 @@ +# +# Generic config for a machine +# + +# Name your machine (the DNS name, what you ssh to) +MACHINE = foo + +# BOX can be different than foo, if the machine BOX has +# multiple partitions with different systems installed. For example, +# you may have a i386 and x86_64 installation on a test box. +# If this is the case, MACHINE defines the way to connect to the +# machine, which may be different between which system the machine +# is booting into. BOX is used for the scripts to reboot and power cycle +# the machine, where it does not matter which system the machine boots into. +# +#BOX := bar + +# Define a way to read the console +CONSOLE = stty -F /dev/ttyS0 115200 parodd; cat /dev/ttyS0 + +# The include files will set up the type of test to run. Just set TEST to +# which test you want to run. +# +# TESTS = patchcheck, randconfig, boot, test, config-bisect, bisect, min-config +# +# See the include/*.conf files that define these tests +# +TEST := patchcheck + +# Some tests may have more than one test to run. Define MULTI := 1 to run +# the extra tests. +MULTI := 0 + +# In case you want to differentiate which type of system you are testing +BITS := 64 + +# REBOOT = none, error, fail, empty +# See include/defaults.conf +REBOOT := empty + +# The defaults file will set up various settings that can be used by all +# machine configs. +INCLUDE include/defaults.conf + +# In case you need to add a patch for a bisect or something +#PRE_BUILD = patch -p1 < ${THIS_DIR}/fix.patch + +# Reset the repo after the build and remove all 'test' modules from the target +# Notice that DO_POST_BUILD is a variable (defined by ':=') and POST_BUILD +# is the option (defined by '=') + +DO_POST_BUILD := git reset --hard +POST_BUILD = ${SSH} 'rm -rf /lib/modules/*-test*'; ${DO_POST_BUILD} + +# The following files each handle a different test case. +# Having them included allows you to set up more than one machine and share +# the same tests. +INCLUDE include/patchcheck.conf +INCLUDE include/tests.conf +INCLUDE include/bisect.conf +INCLUDE include/min-config.conf + diff --git a/tools/testing/ktest/examples/vmware.conf b/tools/testing/ktest/examples/vmware.conf new file mode 100644 index 000000000..61958163d --- /dev/null +++ b/tools/testing/ktest/examples/vmware.conf @@ -0,0 +1,137 @@ +# +# This config is an example usage of ktest.pl with a vmware guest +# +# VMware Setup: +# ------------- +# - Edit the Virtual Machine ("Edit virtual machine settings") +# - Add a Serial Port +# - You almost certainly want it set "Connect at power on" +# - Select "Use socket (named pipe)" +# - Select a name that you'll recognize, like 'ktestserialpipe' +# - From: Server +# - To: A Virtual Machine +# - Save +# - Make sure you note the name, it will be in the base directory of the +# virtual machine (where the "disks" are stored. The default +# is /var/lib/vmware/<virtual machine name>/<the name you entered above> +# +# - Make note of the path to the VM +# </End VMware setup> +# +# The guest is called 'Guest' and this would be something that +# could be run on the host to test a virtual machine target. + +MACHINE = Guest + +# Name of the serial pipe you set in the VMware settings +VMWARE_SERIAL_NAME = <the name you entered above> + +# Define a variable of the name of the VM +# Noting this needs to be the name of the kmx file, and usually, the +# name of the directory that it's in. If the directory and name +# differ change the VMWARE_VM_DIR accordingly. +# Please ommit the .kmx extension +VMWARE_VM_NAME = <virtual machine name> + +# VM dir name. This is usually the same as the virtual machine's name, +# but not always the case. Change if they differ +VMWARE_VM_DIR = ${VMWARE_VM_NAME} + +# Base directory that the Virtual machine is contained in +# /var/lib/vmware is the default on Linux +VMWARE_VM_BASE_DIR = /var/lib/vmware/${VMWARE_VM_DIR} + +# Use ncat to read the unix pipe. Anything that can read the Unix Pipe +# and output it's contents to stdout will work +CONSOLE = /usr/bin/ncat -U ${VMWARE_VM_BASE_DIR}/${VMWARE_SERIAL_NAME} + +# Define what version of Workstation you are using +# This is used by vmrun to use the appropriate appripriate pieces to +# test this. In all likelihood you want 'ws' or 'player' +# Valid options: +# ws - Workstation (Windows or Linux host) +# fusion - Fusion (Mac host) +# player - Using VMware Player (Windows or Linux host) +# Note: vmrun has to run directly on the host machine +VMWARE_HOST_TYPE = ws + +# VMware provides `vmrun` to allow you to do certain things to the virtual machine +# This should hard reset the VM and force a boot +VMWARE_POWER_CYCLE = /usr/bin/vmrun -T ${VMWARE_HOST_TYPE} reset ${VMWARE_VM_BASE_DIR}/${VMWARE_VM_NAME}.kmx nogui + +#*************************************# +# This part is the same as test.conf # +#*************************************# + +# The include files will set up the type of test to run. Just set TEST to +# which test you want to run. +# +# TESTS = patchcheck, randconfig, boot, test, config-bisect, bisect, min-config +# +# See the include/*.conf files that define these tests +# +TEST := patchcheck + +# Some tests may have more than one test to run. Define MULTI := 1 to run +# the extra tests. +MULTI := 0 + +# In case you want to differentiate which type of system you are testing +BITS := 64 + +# REBOOT = none, error, fail, empty +# See include/defaults.conf +REBOOT := empty + + +# The defaults file will set up various settings that can be used by all +# machine configs. +INCLUDE include/defaults.conf + + +#*************************************# +# Now we are different from test.conf # +#*************************************# + + +# The example here assumes that Guest is running a Fedora release +# that uses dracut for its initfs. The POST_INSTALL will be executed +# after the install of the kernel and modules are complete. +# +POST_INSTALL = ${SSH} /sbin/dracut -f /boot/initramfs-test.img $KERNEL_VERSION + +# Guests sometimes get stuck on reboot. We wait 3 seconds after running +# the reboot command and then do a full power-cycle of the guest. +# This forces the guest to restart. +# +POWERCYCLE_AFTER_REBOOT = 3 + +# We do the same after the halt command, but this time we wait 20 seconds. +POWEROFF_AFTER_HALT = 20 + + +# As the defaults.conf file has a POWER_CYCLE option already defined, +# and options can not be defined in the same section more than once +# (all DEFAULTS sections are considered the same). We use the +# DEFAULTS OVERRIDE to tell ktest.pl to ignore the previous defined +# options, for the options set in the OVERRIDE section. +# +DEFAULTS OVERRIDE + +# Instead of using the default POWER_CYCLE option defined in +# defaults.conf, we use virsh to cycle it. To do so, we destroy +# the guest, wait 5 seconds, and then start it up again. +# Crude, but effective. +# +POWER_CYCLE = ${VMWARE_POWER_CYCLE} + + +DEFAULTS + +# The following files each handle a different test case. +# Having them included allows you to set up more than one machine and share +# the same tests. +INCLUDE include/patchcheck.conf +INCLUDE include/tests.conf +INCLUDE include/bisect.conf +INCLUDE include/min-config.conf diff --git a/tools/testing/ktest/ktest.pl b/tools/testing/ktest/ktest.pl new file mode 100755 index 000000000..f94ed2e98 --- /dev/null +++ b/tools/testing/ktest/ktest.pl @@ -0,0 +1,4783 @@ +#!/usr/bin/perl -w +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright 2010 - Steven Rostedt <srostedt@redhat.com>, Red Hat Inc. +# + +use strict; +use IPC::Open2; +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"; + +$| = 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" => "oldconfig", + "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", + "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. + "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 $test_log_start = 0; +my $dry_run = 0; + +my $ktest_config = "ktest.conf"; +my $version; +my $have_version = 0; +my $machine; +my $last_machine; +my $ssh_user; +my $tmpdir; +my $builddir; +my $outputdir; +my $output_config; +my $test_type; +my $build_type; +my $build_options; +my $final_post_ktest; +my $post_ktest_done = 0; +my $pre_ktest; +my $pre_ktest_die; +my $post_ktest; +my $pre_test; +my $pre_test_die; +my $post_test; +my $pre_build; +my $post_build; +my $pre_build_die; +my $post_build_die; +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; +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 $grub_bls_get; +my $syslinux; +my $syslinux_path; +my $syslinux_label; +my $target; +my $make; +my $pre_install; +my $post_install; +my $no_install; +my $noclean; +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_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 $buildlog; +my $testlog; +my $dmesg; +my $monitor_fp; +my $monitor_pid; +my $monitor_cnt = 0; +my $sleep_time; +my $bisect_sleep_time; +my $patchcheck_sleep_time; +my $ignore_warnings; +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; +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 $stty_orig; +my $run_command_status = 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_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. +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 = ( + "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, + "OUTPUT_DIR" => \$outputdir, + "BUILD_DIR" => \$builddir, + "TEST_TYPE" => \$test_type, + "PRE_KTEST" => \$pre_ktest, + "PRE_KTEST_DIE" => \$pre_ktest_die, + "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, + "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, + "REBOOT_RETURN_CODE" => \$reboot_return_code, + "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, + "GRUB_BLS_GET" => \$grub_bls_get, + "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, + "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, + "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_CHERRY" => \$patchcheck_cherry, + "PATCHCHECK_END" => \$patchcheck_end, + "PATCHCHECK_SKIP" => \$patchcheck_skip, +); + +# Options may be used by other options, record them. +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. + For build only tests, it is still needed to differentiate log files. +EOF + ; +$config_help{"SSH_USER"} = << "EOF" + The box is expected to have ssh on normal bootup, provide the user + (most likely root, since you need privileged operations) +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" + The location of the compiled file to copy to the target. + (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 + ; +$config_help{"POWER_CYCLE"} = << "EOF" + A script or command to reboot the box. + + Here is a digital loggers power switch example + POWER_CYCLE = wget --no-proxy -O /dev/null -q --auth-no-challenge 'http://admin:admin\@power/outlet?5=CCL' + + Here is an example to reboot a virtual box on the current host + with the name "Guest". + POWER_CYCLE = virsh destroy Guest; sleep 5; virsh start Guest +EOF + ; +$config_help{"CONSOLE"} = << "EOF" + The script or command that reads the console + + If you use ttywatch server, something like the following would work. +CONSOLE = nc -d localhost 3001 + + For a virtual machine with guest name "Guest". +CONSOLE = virsh console Guest +EOF + ; +$config_help{"LOCALVERSION"} = << "EOF" + Required version ending to differentiate the test + from other linux builds on the system. +EOF + ; +$config_help{"REBOOT_TYPE"} = << "EOF" + Way to reboot the box to the test kernel. + 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 + and select that target to reboot to the kernel. If this is not + your setup, then specify "script" and have a command or script + specified in REBOOT_SCRIPT to boot to the target. + + 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 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 + 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 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 + the grub menu.lst for this option to find what kernel to + reboot into. + + For example, if in the /boot/grub/menu.lst the test kernel title has: + 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. + + 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" + 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" + A script to reboot the target into the test kernel + (Only mandatory if REBOOT_TYPE = script) +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"}) && defined(fileno(LOG))) { + 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) { + print "$prompt [y/n/C] "; + } else { + print "$prompt [Y/n] "; + } + $ans = <STDIN>; + chomp $ans; + if ($ans =~ /^\s*$/) { + if ($cancel) { + $ans = "c"; + } else { + $ans = "y"; + } + } + last if ($ans =~ /^y$/i || $ans =~ /^n$/i); + 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; + } + return 1; +} + +sub read_yn { + my ($prompt) = @_; + + return read_prompt 0, $prompt; +} + +sub read_ync { + my ($prompt) = @_; + + return read_prompt 1, $prompt; +} + +sub get_mandatory_config { + my ($config) = @_; + my $ans; + + return if (defined($opt{$config})); + + if (defined($config_help{$config})) { + print "\n"; + print $config_help{$config}; + } + + for (;;) { + print "$config = "; + if (defined($default{$config}) && length($default{$config})) { + print "\[$default{$config}\] "; + } + $ans = <STDIN>; + $ans =~ s/^\s*(.*\S)\s*$/$1/; + if ($ans =~ /^\s*$/) { + if ($default{$config}) { + $ans = $default{$config}; + } else { + print "Your answer can not be blank\n"; + next; + } + } + $entered_configs{$config} = ${ans}; + last; + } +} + +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_mandatory_config("BUILD_OPTIONS"); + } + + # options required for other than just building a kernel + if (!$buildonly) { + get_mandatory_config("POWER_CYCLE"); + get_mandatory_config("CONSOLE"); + } + + # options required for install and more + if ($buildonly != 1) { + get_mandatory_config("SSH_USER"); + get_mandatory_config("BUILD_TARGET"); + get_mandatory_config("TARGET_IMAGE"); + } + + get_mandatory_config("LOCALVERSION"); + + return if ($buildonly); + + my $rtype = $opt{"REBOOT_TYPE"}; + + if (!defined($rtype)) { + if (!defined($opt{"GRUB_MENU"})) { + get_mandatory_config("REBOOT_TYPE"); + $rtype = $entered_configs{"REBOOT_TYPE"}; + } else { + $rtype = "grub"; + } + } + + if (($rtype eq "grub") or ($rtype eq "grub2bls")) { + get_mandatory_config("GRUB_MENU"); + } + + if ($rtype eq "grub2") { + get_mandatory_config("GRUB_MENU"); + get_mandatory_config("GRUB_FILE"); + } + + if ($rtype eq "syslinux") { + get_mandatory_config("SYSLINUX_LABEL"); + } +} + +sub process_variables { + my ($value, $remove_undef) = @_; + my $retval = ""; + + # We want to check for '\', and it is just easier + # 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 =~ /(.*?[^\\])\$\{([^\{]*?)\}(.*)/) { + my $begin = $1; + my $var = $2; + my $end = $3; + # append beginning of value to retval + $retval = "$retval$begin"; + 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, 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 = "$retval$end"; + $retval = ""; + } + $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"; +} + +sub set_value { + my ($lvalue, $rvalue, $override, $overrides, $name) = @_; + + my $prvalue = process_variables($rvalue); + + if ($lvalue =~ /^(TEST|BISECT|CONFIG_BISECT)_TYPE(\[.*\])?$/ && + $prvalue !~ /^(config_|)bisect$/ && + $prvalue !~ /^build$/ && + $prvalue !~ /^make_warnings_file$/ && + $buildonly) { + + # 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 mandatory options. + $buildonly = 2; + } + } + + if (defined($opt{$lvalue})) { + if (!$override || defined(${$overrides}{$lvalue})) { + my $extra = ""; + if ($override) { + $extra = "In the same override section!\n"; + } + die "$name: $.: Option $lvalue defined more than once!\n$extra"; + } + ${$overrides}{$lvalue} = $prvalue; + } + + $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, $command) = @_; + + # Command line variables override all others + if (defined($command_tmp_vars{$lvalue})) { + return; + } + + # If a variable is undefined, treat an unescaped self-reference as empty. + if (!defined($variable{$lvalue})) { + $rvalue =~ s/(?<!\\)\$\{\Q$lvalue\E\}//g; + $rvalue =~ s/^\s+//; + $rvalue =~ s/\s+$//; + } + + 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 { + my ($lval, $cmp, $rval) = @_; + + # remove whitespace + + $lval =~ s/^\s*//; + $lval =~ s/\s*$//; + + $rval =~ s/^\s*//; + $rval =~ s/\s*$//; + + if ($cmp eq "==") { + return $lval eq $rval; + } elsif ($cmp eq "!=") { + return $lval ne $rval; + } elsif ($cmp eq "=~") { + return $lval =~ m/$rval/; + } elsif ($cmp eq "!~") { + return $lval !~ m/$rval/; + } + + my $statement = "$lval $cmp $rval"; + my $ret = eval $statement; + + # $@ stores error of eval + if ($@) { + return -1; + } + + return $ret; +} + +sub value_defined { + my ($val) = @_; + + return defined($variable{$2}) || + defined($opt{$2}); +} + +sub process_expression { + my ($name, $val) = @_; + + my $c = $d++; + + while ($val =~ s/\(([^\(]*?)\)/\&\&\&\&VAL\&\&\&\&/) { + my $express = $1; + + if (process_expression($name, $express)) { + $val =~ s/\&\&\&\&VAL\&\&\&\&/ 1 /; + } else { + $val =~ s/\&\&\&\&VAL\&\&\&\&/ 0 /; + } + } + + $d--; + my $OR = "\\|\\|"; + my $AND = "\\&\\&"; + + while ($val =~ s/^(.*?)($OR|$AND)//) { + my $express = $1; + my $op = $2; + + if (process_expression($name, $express)) { + if ($op eq "||") { + return 1; + } + } else { + if ($op eq "&&") { + return 0; + } + } + } + + if ($val =~ /(.*)(==|\!=|>=|<=|>|<|=~|\!~)(.*)/) { + my $ret = process_compare($1, $2, $3); + if ($ret < 0) { + die "$name: $.: Unable to process comparison\n"; + } + return $ret; + } + + if ($val =~ /^\s*(NOT\s*)?DEFINED\s+(\S+)\s*$/) { + if (defined $1) { + return !value_defined($2); + } else { + return value_defined($2); + } + } + + if ($val =~ s/^\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*$/) { + return 1; + } + + die ("$name: $.: Undefined content $val in if statement\n"); +} + +sub process_if { + my ($name, $value) = @_; + + # Convert variables and replace undefined ones with 0 + my $val = process_variables($value, 1); + my $ret = process_expression $name, $val; + + return $ret; +} + +sub __read_config { + my ($config, $current_test_num) = @_; + + my $in; + open($in, $config) || die "can't read file $config"; + + my $name = $config; + $name =~ s,.*/(.*),$1,; + + my $test_num = $$current_test_num; + my $default = 1; + my $repeat = 1; + my $num_tests_set = 0; + my $skip = 0; + my $rest; + my $line; + my $test_case = 0; + my $if = 0; + my $if_set = 0; + my $override = 0; + + my %overrides; + + while (<$in>) { + + # ignore blank lines and comments + next if (/^\s*$/ || /\s*\#/); + + if (/^\s*(TEST_START|DEFAULTS)\b(.*)/) { + + my $type = $1; + $rest = $2; + $line = $2; + + my $old_test_num; + my $old_repeat; + $override = 0; + + if ($type eq "TEST_START") { + if ($num_tests_set) { + die "$name: $.: Can not specify both NUM_TESTS and TEST_START\n"; + } + + $old_test_num = $test_num; + $old_repeat = $repeat; + + $test_num += $repeat; + $default = 0; + $repeat = 1; + } else { + $default = 1; + } + + # If SKIP is anywhere in the line, the command will be skipped + if ($rest =~ s/\s+SKIP\b//) { + $skip = 1; + } else { + $test_case = 1; + $skip = 0; + } + + if ($rest =~ s/\sELSE\b//) { + if (!$if) { + die "$name: $.: ELSE found with out matching IF section\n$_"; + } + $if = 0; + + if ($if_set) { + $skip = 1; + } else { + $skip = 0; + } + } + + if ($rest =~ s/\sIF\s+(.*)//) { + if (process_if($name, $1)) { + $if_set = 1; + } else { + $skip = 1; + } + $if = 1; + } else { + $if = 0; + $if_set = 0; + } + + if (!$skip) { + if ($type eq "TEST_START") { + if ($rest =~ s/\s+ITERATE\s+(\d+)//) { + $repeat = $1; + $repeat_tests{"$test_num"} = $repeat; + } + } elsif ($rest =~ s/\sOVERRIDE\b//) { + # DEFAULT only + $override = 1; + # Clear previous overrides + %overrides = (); + } + } + + if (!$skip && $rest !~ /^\s*$/) { + 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$_"; + } + $rest = $1; + if ($if_set) { + $skip = 1; + $rest = ""; + } else { + $skip = 0; + + if ($rest =~ /\sIF\s+(.*)/) { + # May be a ELSE IF section. + if (process_if($name, $1)) { + $if_set = 1; + } else { + $skip = 1; + } + $rest = ""; + } else { + $if = 0; + } + } + + if ($rest !~ /^\s*$/) { + die "$name: $.: Garbage found after DEFAULTS\n$_"; + } + + } elsif (/^\s*INCLUDE\s+(\S+)/) { + + next if ($skip); + + if (!$default) { + die "$name: $.: INCLUDE can only be done in default sections\n$_"; + } + + my $file = process_variables($1); + + if ($file !~ m,^/,) { + # check the path of the config file first + if ($config =~ m,(.*)/,) { + if (-f "$1/$file") { + $file = "$1/$file"; + } + } + } + + if ( ! -r $file ) { + die "$name: $.: Can't read file $file\n$_"; + } + + if (__read_config($file, \$test_num)) { + $test_case = 1; + } + + } elsif (/^\s*([A-Z_\[\]\d]+)\s*=~\s*(.*?)\s*$/) { + + next if ($skip); + + 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); + + my $lvalue = $1; + my $rvalue = $2; + + if (!$default && + ($lvalue eq "NUM_TESTS" || + $lvalue eq "LOG_FILE" || + $lvalue eq "CLEAR_LOG")) { + die "$name: $.: $lvalue must be set in DEFAULTS section\n"; + } + + if ($lvalue eq "NUM_TESTS") { + if ($test_num) { + die "$name: $.: Can not specify both NUM_TESTS and TEST_START\n"; + } + if (!$default) { + die "$name: $.: NUM_TESTS must be set in default section\n"; + } + $num_tests_set = 1; + } + + if ($default || $lvalue =~ /\[\d+\]$/) { + set_value($lvalue, $rvalue, $override, \%overrides, $name); + } else { + my $val = "$lvalue\[$test_num\]"; + set_value($val, $rvalue, $override, \%overrides, $name); + + if ($repeat > 1) { + $repeats{$val} = $repeat; + } + } + } elsif (/^\s*([A-Z_\[\]\d]+)\s*:=\s*(.*?)\s*$/) { + next if ($skip); + + my $lvalue = $1; + my $rvalue = $2; + + # process config variables. + # 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 + # one of these sections that have SKIP defined. + # The save variable can be + # defined multiple times and the new one simply overrides + # the previous one. + set_variable($lvalue, $rvalue); + + } else { + die "$name: $.: Garbage found in config\n$_"; + } + } + + if ($test_num) { + $test_num += $repeat - 1; + $opt{"NUM_TESTS"} = $test_num; + } + + close($in); + + $$current_test_num = $test_num; + + return $test_case; +} + +sub get_test_case { + print "What test case would you like to run?\n"; + print " (build, install or boot)\n"; + print " Other tests are available but require editing ktest.conf\n"; + print " (see tools/testing/ktest/sample.conf)\n"; + my $ans = <STDIN>; + chomp $ans; + $default{"TEST_TYPE"} = $ans; +} + +sub read_config { + my ($config) = @_; + + my $test_case; + my $test_num = 0; + + $test_case = __read_config $config, \$test_num; + + 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_mandatory_configs; + + # was a test specified? + if (!$test_case) { + print "No test case specified.\n"; + get_test_case; + } + + # set any defaults + + foreach my $default (keys %default) { + if (!defined($opt{$default})) { + $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 IGNORE_UNUSED = 1 to have ktest ignore unused variables\n"; + if ($dry_run) { + return; + } + if (!read_yn "Do you want to continue?") { + exit -1; + } + } +} + +sub __eval_option { + my ($name, $option, $i) = @_; + + # Add space to evaluate the character before $ + $option = " $option"; + my $retval = ""; + my $repeated = 0; + my $parent = 0; + + foreach my $test (keys %repeat_tests) { + if ($i >= $test && + $i < $test + $repeat_tests{$test}) { + + $repeated = 1; + $parent = $test; + last; + } + } + + while ($option =~ /(.*?[^\\])\$\{(.*?)\}(.*)/) { + my $start = $1; + my $var = $2; + my $end = $3; + + # Append beginning of line + $retval = "$retval$start"; + + # If the iteration option OPT[$i] exists, then use that. + # otherwise see if the default OPT (without [$i]) exists. + + my $o = "$var\[$i\]"; + my $parento = "$var\[$parent\]"; + + # If a variable contains itself, use the default var + if (($var eq $name) && defined($opt{$var})) { + $o = $opt{$var}; + # 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"; + } elsif ($repeated && defined($opt{$parento})) { + $o = $opt{$parento}; + $retval = "$retval$o"; + } 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\}"; + } + + $option = $end; + } + + $retval = "$retval$option"; + + $retval =~ s/^ //; + + 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) = @_; + + my $prev = ""; + + # Since an option can evaluate to another option, + # keep iterating until we do not evaluate any more + # options. + my $r = 0; + while ($prev ne $option) { + # Check for recursive evaluations. + # 100 deep should be more than enough. + if ($r++ > 100) { + die "Over 100 evaluations occurred with $option\n" . + "Check for recursive variables\n"; + } + $prev = $option; + $option = __eval_option($name, $option, $i); + } + + $option = process_evals($name, $option, $i); + + return $option; +} + +sub reboot { + my ($time) = @_; + my $powercycle = 0; + + # 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) { + start_monitor; + if (defined($time)) { + # Flush stale console output from the old kernel before power-cycling. + wait_for_monitor 1; + } + + run_command "$power_cycle"; + + } 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"; + } + } + + 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); + } + if ($powercycle || $time) { + 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; + + 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 "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 run_post_ktest { + my $cmd; + + return if ($post_ktest_done); + + if (defined($final_post_ktest)) { + $cmd = $final_post_ktest; + } elsif (defined($post_ktest)) { + $cmd = $post_ktest; + } else { + return; + } + + my $cp_post_ktest = eval_kernel_version($cmd); + run_command $cp_post_ktest; + $post_ktest_done = 1; +} + +sub dodie { + # avoid recursion + return if ($in_die); + $in_die = 1; + + if ($monitor_cnt) { + # restore terminal settings + system("stty $stty_orig"); + } + + my $i = $iteration; + + 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`; + } + + if (defined($opt{"LOG_FILE"})) { + print " See $opt{LOG_FILE} for more info.\n"; + } + + # Fatal paths bypass fail(), so STORE_FAILURES needs to be handled here. + if (defined($store_failures)) { + save_logs("fail", $store_failures); + } + + 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; + } + run_post_ktest; + + die @_, "\n"; +} + +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"; + + # unlockpt() + $tmp = pack("i", 0); + ioctl($ptm, $TIOCSPTLCK, $tmp) or + dodie "ioctl TIOCSPTLCK for /dev/ptmx failed"; + + # 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; + + # 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 $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 { + if ($monitor_cnt++) { + return; + } + $monitor_fp = \*MONFD; + $monitor_pid = open_console $monitor_fp; + + return; + + open(MONFD, "Stop perl from warning about single use of MONFD"); +} + +sub end_monitor { + return if (!defined $console); + if (--$monitor_cnt) { + return; + } + close_console($monitor_fp, $monitor_pid); +} + +sub wait_for_monitor { + my ($time, $stop) = @_; + 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"; + + # read the monitor and wait for the system to calm down + while (!$booted) { + $line = wait_for_input($monitor_fp, $time); + last if (!defined($line)); + print "$line"; + $full_line .= $line; + + if (defined($stop) && $full_line =~ /$stop/) { + doprint "wait for monitor detected $stop\n"; + $booted = 1; + } + + if ($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"; + + # 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]+1,$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 + dodie "can't create $dir"; + } + + my %files = ( + "config" => $output_config, + "buildlog" => $buildlog, + "dmesg" => $dmesg, + "testlog" => $testlog, + ); + + if (defined($opt{"LOG_FILE"})) { + if (-f $opt{"LOG_FILE"}) { + cp $opt{"LOG_FILE"}, "$dir/logfile"; + } + } + + 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"; +} + +sub fail { + + if ($die_on_failure) { + dodie @_; + } + + doprint "FAILED\n"; + + my $i = $iteration; + + # no need to reboot for just building. + if (!do_not_reboot) { + doprint "REBOOTING\n"; + reboot_to_good $sleep_time; + } + + my $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"; + + if (defined($store_failures)) { + save_logs "fail", $store_failures; + } + + if (defined($post_test)) { + run_command $post_test; + } + + return 1; +} + +sub run_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, "-|", + "sh", "-c", + 'command=$1; shift; exec 2>&1; eval "$command"', + "sh", $command) or + (fail "unable to exec $command" and return 0); + + if (defined($opt{"LOG_FILE"})) { + $dolog = 1; + } + + if (defined($redirect)) { + 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; + } + } + + 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); + # 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(RD) if ($dord); + + $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 !$run_command_status; +} + +sub run_ssh { + my ($cmd, $timeout) = @_; + my $cp_exec = $ssh_exec; + + $cp_exec =~ s/\$SSH_COMMAND/$cmd/g; + return run_command "$cp_exec", undef , $timeout; +} + +sub run_scp { + my ($src, $dst, $cp_scp) = @_; + + $cp_scp =~ s/\$SRC_FILE/$src/g; + $cp_scp =~ s/\$DST_FILE/$dst/g; + + 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_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); + + doprint "Find $reboot_type menu ... "; + $grub_number = -1; + + my $ssh_grub = $ssh_exec; + $ssh_grub =~ s,\$SSH_COMMAND,$command,g; + + open(IN, "$ssh_grub |") or + dodie "unable to execute $command"; + + my $found = 0; + + my $submenu_number = 0; + + while (<IN>) { + if (/$target/) { + $grub_number++; + $found = 1; + last; + } elsif (defined($submenu) && /$submenu/) { + $submenu_number++; + $grub_number = -1; + } elsif (/$skip/) { + $grub_number++; + } + } + close(IN); + + 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; +} + +sub get_grub_index { + + my $command; + my $target; + my $skip; + my $submenu; + my $grub_menu_qt; + + if ($reboot_type !~ /^grub/) { + return; + } + + $grub_menu_qt = quotemeta($grub_menu); + + 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; + } + + _get_grub_index($command, $target, $skip, $submenu); +} + +sub wait_for_input { + my ($fp, $time) = @_; + my $start_time; + my $rin; + my $rout; + my $nr; + my $buf; + my $line; + my $ch; + + if (!defined($time)) { + $time = $timeout; + } + + if ($time < 0) { + # Negative number means wait indefinitely + undef $time; + } + + $rin = ''; + vec($rin, fileno($fp), 1) = 1; + vec($rin, fileno(\*STDIN), 1) = 1; + + $start_time = time; + + while (1) { + $nr = select($rout=$rin, undef, undef, $time); + + last if ($nr <= 0); + + # 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 { + 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)'"; + } 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) { + run_command "$reboot_script"; + } + reboot; +} + +sub get_sha1 { + my ($commit) = @_; + + doprint "git rev-list --max-count=1 $commit ... "; + my $sha1 = `git rev-list --max-count=1 $commit`; + my $ret = $?; + + logit $sha1; + + if ($ret) { + doprint "FAILED\n"; + dodie "Failed to get git $commit"; + } + + print "SUCCESS\n"; + + chomp $sha1; + + return $sha1; +} + +sub monitor { + my $booted = 0; + my $bug = 0; + my $bug_ignored = 0; + my $skip_call_trace = 0; + my $loops; + + my $start_time = time; + + wait_for_monitor 5; + + my $line; + my $full_line = ""; + + open(DMESG, "> $dmesg") or + dodie "unable to write to $dmesg"; + + reboot_to; + + my $success_start; + my $failure_start; + my $monitor_start = time; + my $done = 0; + my $version_found = 0; + + while (!$done) { + if ($bug && defined($stop_after_failure) && + $stop_after_failure >= 0) { + my $time = $stop_after_failure - (time - $failure_start); + $line = wait_for_input($monitor_fp, $time); + if (!defined($line)) { + doprint "bug timed out after $booted_timeout seconds\n"; + doprint "Test forced to stop after $stop_after_failure seconds after failure\n"; + last; + } + } elsif ($booted) { + $line = wait_for_input($monitor_fp, $booted_timeout); + if (!defined($line)) { + my $s = $booted_timeout == 1 ? "" : "s"; + doprint "Successful boot found: break after $booted_timeout second$s\n"; + last; + } + } else { + $line = wait_for_input($monitor_fp); + if (!defined($line)) { + my $s = $timeout == 1 ? "" : "s"; + doprint "Timed out after $timeout second$s\n"; + last; + } + } + + doprint $line; + print DMESG $line; + + # we are not guaranteed to get a full line + $full_line .= $line; + + if ($full_line =~ /$success_line/) { + $booted = 1; + $success_start = time; + } + + if ($booted && defined($stop_after_success) && + $stop_after_success >= 0) { + my $now = time; + if ($now - $success_start >= $stop_after_success) { + doprint "Test forced to stop after $stop_after_success seconds after success\n"; + last; + } + } + + 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; + $failure_start = time; + } + } + } + + if ($bug && defined($stop_after_failure) && + $stop_after_failure >= 0) { + my $now = time; + if ($now - $failure_start >= $stop_after_failure) { + doprint "Test forced to stop after $stop_after_failure seconds after failure\n"; + last; + } + } + + if ($full_line =~ /\[ end of backtrace testing \]/) { + $skip_call_trace = 0; + } + + if ($full_line =~ /Kernel panic -/) { + $failure_start = time; + $bug = 1; + } + + # Detect triple faults by testing the banner + if ($full_line =~ /\bLinux version (\S+).*\n/) { + if ($1 eq $version) { + $version_found = 1; + } elsif ($version_found && $detect_triplefault) { + # We already booted into the kernel we are testing, + # but now we booted into another kernel? + # Consider this a triple fault. + 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"; + last; + } + } + + if ($line =~ /\n/) { + $full_line = ""; + } + + if ($stop_test_after > 0 && !$booted && !$bug) { + if (time - $monitor_start > $stop_test_after) { + doprint "STOP_TEST_AFTER ($stop_test_after seconds) timed out\n"; + $done = 1; + } + } + } + + my $end_time = time; + $reboot_time = $end_time - $start_time; + + close(DMESG); + + if ($bug) { + return 0 if ($in_bisect); + fail "failed - got a bug report" and return 0; + } + + if (!$booted) { + return 0 if ($in_bisect); + 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 = 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); + + my $start_time = time; + + 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; + + # should we process modules? + $install_mods = 0; + open(IN, "$output_config") or dodie("Can't read config file"); + while (<IN>) { + if (/CONFIG_MODULES(=y)?/) { + if (defined($1)) { + $install_mods = 1; + last; + } + } + } + close(IN); + + if (!$install_mods) { + do_post_install; + doprint "No modules needed\n"; + my $end_time = time; + $install_time = $end_time - $start_time; + return; + } + + run_command "$make INSTALL_MOD_STRIP=1 INSTALL_MOD_PATH=$tmpdir modules_install" or + dodie "Failed to install modules"; + + my $modlib = "/lib/modules/$version"; + my $modtar = "ktest-mods.tar.bz2"; + + run_ssh "rm -rf $modlib" or + dodie "failed to remove old mods: $modlib"; + + # would be nice if scp -r did not follow symbolic links + run_command "cd $tmpdir && tar -cjf $modtar lib/modules/$version" or + dodie "making tarball"; + + run_scp_mod "$tmpdir/$modtar", "/tmp" or + dodie "failed to copy modules"; + + unlink "$tmpdir/$modtar"; + + run_ssh "'(cd / && tar xjf /tmp/$modtar)'" or + dodie "failed to tar modules"; + + 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 -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_install { + # Make sure the stable kernel has finished booting + + # 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 if (defined $console); + return monitor; +} + +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 { + my %warnings_list; + + # Failed builds should not reboot the target + my $save_no_reboot = $no_reboot; + $no_reboot = 1; + + if (defined($warnings_file) && -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); + } + + 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}) { + $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; + } + } + } + } + $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>) { + if (m,^--- a/(.*),) { + chomp $1; + $files[$#files] = $1; + } + } + close(IN); + + open(IN, $buildlog) or dodie "Can't open $buildlog"; + while (<IN>) { + if (/^\s*(.*?):.*(warning|error)/) { + my $err = $1; + foreach my $file (@files) { + my $fullpath = "$builddir/$file"; + if ($file eq $err || $fullpath eq $err) { + fail "$file built with warnings" and return 0; + } + } + } + } + close(IN); + + return 1; +} + +sub apply_min_config { + my $outconfig = "$output_config.new"; + + # Read the config file and remove anything that + # is in the force_config hash (from minconfig and others) + # then add the force config back. + + doprint "Applying minimum configurations into $output_config.new\n"; + + open (OUT, ">$outconfig") or + dodie "Can't create $outconfig"; + + if (-f $output_config) { + open (IN, $output_config) or + dodie "Failed to open $output_config"; + while (<IN>) { + if (/^(# )?(CONFIG_[^\s=]*)/) { + next if (defined($force_config{$2})); + } + print OUT; + } + close IN; + } + foreach my $config (keys %force_config) { + print OUT "$force_config{$config}\n"; + } + close OUT; + + run_command "mv $outconfig $output_config"; +} + +sub make_oldconfig { + + my @force_list = keys %force_config; + + if ($#force_list >= 0) { + apply_min_config; + } + + 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"; + } + } +} + +# read a config file and use this to force new configs. +sub load_force_config { + my ($config) = @_; + + doprint "Loading force configs from $config\n"; + open(IN, $config) or + dodie "failed to read $config"; + while (<IN>) { + chomp; + if (/^(CONFIG[^\s=]*)(\s*=.*)/) { + $force_config{$1} = $_; + } elsif (/^# (CONFIG_\S*) is not set/) { + $force_config{$1} = $_; + } + } + close IN; +} + +sub build { + my ($type) = @_; + + unlink $buildlog; + + my $start_time = time; + + # Failed builds should not reboot the target + 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) && + $pre_build_die) { + dodie "failed to pre_build\n"; + } + } + + if ($type =~ /^useconfig:(.*)/) { + run_command "cp $1 $output_config" or + dodie "could not copy $1 to .config"; + + $type = "oldconfig"; + } + + # old config can ask questions + if ($type eq "oldconfig") { + $type = "olddefconfig"; + + # allow for empty configs + run_command "touch $output_config"; + + if (!$noclean) { + run_command "mv $output_config $outputdir/config_temp" or + dodie "moving .config"; + + run_command "$make mrproper" or dodie "make mrproper"; + + run_command "mv $outputdir/config_temp $output_config" or + dodie "moving config_temp"; + } + } elsif (!$noclean) { + unlink "$output_config"; + run_command "$make mrproper" or + dodie "make mrproper"; + } + + # add something to distinguish this build + open(OUT, "> $outputdir/localversion") or dodie("Can't make localversion file"); + print OUT "$localversion\n"; + close(OUT); + + if (defined($minconfig)) { + load_force_config($minconfig); + } + + if ($type ne "olddefconfig") { + run_command "$make $type" or + dodie "failed make config"; + } + # Run old config regardless, to enforce min configurations + make_oldconfig; + + 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 + # do it now. + get_version; + my $ret = run_command $post_build; + if (!$ret && defined($post_build_die) && + $post_build_die) { + dodie "failed to post_build\n"; + } + } + + if (!$build_ret) { + # bisect may need this to pass + if ($in_bisect) { + $no_reboot = $save_no_reboot; + return 0; + } + fail "failed build" and return 0; + } + + $no_reboot = $save_no_reboot; + + my $end_time = time; + $build_time = $end_time - $start_time; + + return 1; +} + +sub halt { + if (!run_ssh "halt" or defined($power_off)) { + if (defined($poweroff_after_halt)) { + sleep $poweroff_after_halt; + run_command "$power_off"; + } + } else { + # nope? the zap it! + run_command "$power_off"; + } +} + +sub success { + my ($i) = @_; + + $successes++; + + my $name = ""; + + if (defined($test_name)) { + $name = " ($test_name)"; + } + + 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; + } + + 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, 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', 'f', or 's'\n"; + } + } +} + +sub child_run_test { + + # child should have no power + $reboot_on_error = 0; + $poweroff_on_error = 0; + $die_on_failure = 1; + + run_command $run_test, $testlog; + + exit $run_command_status; +} + +sub child_finished { + $child_done = 1; +} + +sub do_run_test { + my $child_pid; + my $child_exit; + my $line; + my $full_line; + my $bug = 0; + my $bug_ignored = 0; + + my $start_time = time; + + wait_for_monitor 1; + + doprint "run test $run_test\n"; + + $child_done = 0; + + $SIG{CHLD} = qw(child_finished); + + $child_pid = fork; + + child_run_test if (!$child_pid); + + $full_line = ""; + + do { + $line = wait_for_input($monitor_fp, 1); + if (defined($line)) { + + # we are not guaranteed to get a full line + $full_line .= $line; + doprint $line; + + if ($full_line =~ /call trace:/i) { + if ($ignore_errors) { + $bug_ignored = 1; + } else { + $bug = 1; + } + } + + if ($full_line =~ /Kernel panic -/) { + $bug = 1; + } + + if ($line =~ /\n/) { + $full_line = ""; + } + } + } 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; + do { + $line = wait_for_input($monitor_fp, 1); + if (defined($line)) { + doprint $line; + } + $now = time; + if ($now - $failure_start >= $stop_after_failure) { + last; + } + } while (defined($line)); + + doprint "Detected kernel crash!\n"; + # kill the child with extreme prejudice + kill 9, $child_pid; + } + + waitpid $child_pid, 0; + $child_exit = $? >> 8; + + my $end_time = time; + $test_time = $end_time - $start_time; + + 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; + } + return 1; +} + +sub run_git_bisect { + my ($command) = @_; + + doprint "$command ... "; + + my $output = `$command 2>&1`; + my $ret = $?; + + logit $output; + + if ($ret) { + doprint "FAILED\n"; + dodie "Failed to git bisect"; + } + + doprint "SUCCESS\n"; + if ($output =~ m/^(Bisecting: .*\(roughly \d+ steps?\))\s+\[([[:xdigit:]]+)\]/) { + doprint "$1 [$2]\n"; + } elsif ($output =~ m/^([[:xdigit:]]+) is the first bad commit/) { + $bisect_bad_commit = $1; + doprint "Found bad commit... $1\n"; + return 0; + } else { + # we already logged it, just print it now. + print $output; + } + + return 1; +} + +sub bisect_reboot { + doprint "Reboot and sleep $bisect_sleep_time seconds\n"; + reboot_to_good $bisect_sleep_time; +} + +# returns 1 on success, 0 on failure, -1 on skip +sub run_bisect_test { + my ($type, $buildtype) = @_; + + my $failed = 0; + my $result; + + $in_bisect = 1; + + build $buildtype or $failed = 1; + + if ($type ne "build") { + if ($failed && $bisect_skip) { + $in_bisect = 0; + return -1; + } + dodie "Failed on build" if $failed; + + # Now boot the box + start_monitor_and_install or $failed = 1; + + if ($type ne "boot") { + if ($failed && $bisect_skip) { + end_monitor; + bisect_reboot; + $in_bisect = 0; + return -1; + } + dodie "Failed on boot" if $failed; + + do_run_test or $failed = 1; + } + end_monitor; + } + + if ($failed) { + $result = 0; + } else { + $result = 1; + } + + # reboot the box to a kernel we can ssh to + if ($type ne "build") { + bisect_reboot; + } + $in_bisect = 0; + + return $result; +} + +sub run_bisect { + my ($type) = @_; + my $buildtype = "oldconfig"; + + # We should have a minconfig to use? + if (defined($minconfig)) { + $buildtype = "useconfig:$minconfig"; + } + + # If the user sets bisect_tries to less than 1, then no tries + # is a success. + my $ret = 1; + + # 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; + } + + if ($ret > 0) { + return "good"; + } elsif ($ret == 0) { + return "bad"; + } elsif ($bisect_skip) { + doprint "HIT A BAD COMMIT ... SKIPPING\n"; + return "skip"; + } +} + +sub update_bisect_replay { + my $tmp_log = "$tmpdir/ktest_bisect_log"; + run_command "git bisect log > $tmp_log" or + dodie "can't create bisect log"; + return $tmp_log; +} + +sub bisect { + my ($i) = @_; + + my $result; + + 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; + 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; + } else { + $start_files = ""; + } + + # convert to true sha1's + $good = get_sha1($good); + $bad = get_sha1($bad); + + if (defined($bisect_reverse) && $bisect_reverse == 1) { + doprint "Performing a reverse bisect (bad is good, good is bad!)\n"; + $reverse_bisect = 1; + } else { + $reverse_bisect = 0; + } + + # Can't have a test without having a test to run + if ($type eq "test" && !defined($run_test)) { + $type = "boot"; + } + + # 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"); + + if ($check ne "good") { + doprint "TESTING BISECT BAD [$bad]\n"; + run_command "git checkout $bad" or + dodie "Failed to checkout $bad"; + + $result = run_bisect $type; + + if ($result ne "bad") { + fail "Tested BISECT_BAD [$bad] and it succeeded" and return 0; + } + } + + if ($check ne "bad") { + doprint "TESTING BISECT GOOD [$good]\n"; + run_command "git checkout $good" or + dodie "Failed to checkout $good"; + + $result = run_bisect $type; + + if ($result ne "good") { + fail "Tested BISECT_GOOD [$good] and it failed" and return 0; + } + } + + # checkout where we started + run_command "git checkout $head" or + dodie "Failed to checkout $head"; + } + + run_command "git bisect start$start_files" or + dodie "could not start bisect"; + + 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)) { + run_command "git checkout $start" or + dodie "failed to checkout $start"; + } + + my $test; + do { + $result = run_bisect $type; + $test = run_git_bisect "git bisect $result"; + print_times; + } while ($test); + + run_command "git bisect log" or + dodie "could not capture git bisect log"; + + run_command "git bisect reset" or + dodie "could not reset git bisect"; + + doprint "Bad commit was [$bisect_bad_commit]\n"; + + success $i; +} + +sub assign_configs { + my ($hash, $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; + } + } + + close(IN); +} + +sub process_config_ignore { + my ($config) = @_; + + assign_configs \%config_ignore, $config; +} + +sub get_dependencies { + my ($config) = @_; + + my $arr = $dependency{$config}; + if (!defined($arr)) { + return (); + } + + my @deps = @{$arr}; + + foreach my $dep (@{$arr}) { + print "ADD DEP $dep\n"; + @deps = (@deps, get_dependencies $dep); + } + + return @deps; +} + +sub save_config { + my ($pc, $file) = @_; + + my %configs = %{$pc}; + + doprint "Saving configs into $file\n"; + + open(OUT, ">$file") or dodie "Can not write to $file"; + + foreach my $config (keys %configs) { + print OUT "$configs{$config}\n"; + } + close(OUT); +} + +sub create_config { + my ($name, $pc) = @_; + + doprint "Creating old config from $name configs\n"; + + save_config $pc, $output_config; + + make_oldconfig; +} + +sub run_config_bisect_test { + my ($type) = @_; + + my $ret = run_bisect_test $type, "oldconfig"; + + if ($bisect_manual) { + $ret = answer_bisect; + } + + return $ret; +} + +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 "No more config bisecting possible.\n"; + run_command "$diffexec $good $bad", 1; + doprint "***************************************\n\n"; +} + +sub run_config_bisect { + my ($good, $bad, $last_result) = @_; + my $reset = ""; + my $cmd; + my $ret; + + if (!length($last_result)) { + $reset = "-r"; + } + run_command "$config_bisect_exec $reset -b $outputdir $good $bad $last_result", 1; + + # 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 $config_bisect_type; + if ($ret) { + 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; + } +} + +sub config_bisect { + my ($i) = @_; + + my $good_config; + my $bad_config; + + my $type = $config_bisect_type; + my $ret; + + $bad_config = $config_bisect; + + if (defined($config_bisect_good)) { + $good_config = $config_bisect_good; + } elsif (defined($minconfig)) { + $good_config = $minconfig; + } else { + 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 (!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; + } + } + + # we don't want min configs to cause issues here. + doprint "Disabling 'MIN_CONFIG' for this test\n"; + undef $minconfig; + + my %good_configs; + my %bad_configs; + my %tmp_configs; + + 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"; + } + } + } + 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"; + + 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"; + + if (defined($config_bisect_check) && $config_bisect_check ne "0") { + if ($config_bisect_check ne "good") { + doprint "Testing bad config\n"; + + $ret = run_bisect_test $type, "useconfig:$bad_config"; + if ($ret) { + fail "Bad config succeeded when expected to fail!"; + return 0; + } + } + if ($config_bisect_check ne "bad") { + doprint "Testing good config\n"; + + $ret = run_bisect_test $type, "useconfig:$good_config"; + if (!$ret) { + fail "Good config failed when expected to succeed!"; + return 0; + } + } + } + + my $last_run = ""; + + do { + $ret = run_config_bisect $good_config, $bad_config, $last_run; + if ($ret == 3) { + $last_run = "good"; + } elsif ($ret == 4) { + $last_run = "bad"; + } + print_times; + } while ($ret == 3 || $ret == 4); + + if ($ret == 2) { + config_bisect_end "$good_config.tmp", "$bad_config.tmp"; + } + + return $ret if ($ret < 0); + + success $i; +} + +sub patchcheck_reboot { + doprint "Reboot and sleep $patchcheck_sleep_time seconds\n"; + reboot_to_good $patchcheck_sleep_time; +} + +sub patchcheck { + my ($i) = @_; + + dodie "PATCHCHECK_START[$i] not defined\n" + if (!defined($patchcheck_start)); + 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 + $start = get_sha1($start); + $end = get_sha1($end); + + my $type = $patchcheck_type; + + # Can't have a test without having a test to run + if ($type eq "test" && !defined($run_test)) { + $type = "boot"; + } + + 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 (!$cherry) { + if ($list[$#list] !~ /^$start/) { + fail "SHA1 $start not found"; + } + + # 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; + + if (defined($ignore_warnings)) { + foreach my $sha1 (split /\s+/, $ignore_warnings) { + $ignored_warnings{$sha1} = 1; + } + } + + $in_patchcheck = 1; + foreach my $item (@list) { + my $sha1 = $item; + $sha1 =~ s/^([[:xdigit:]]+).*/$1/; + + if (defined($skip_list{$sha1})) { + doprint "\nSkipping \"$item\"\n\n"; + next; + } + + doprint "\nProcessing commit \"$item\"\n\n"; + + run_command "git checkout $sha1" or + dodie "Failed to checkout $sha1"; + + # only clean on the first and last patch + if ($item eq $list[0] || + $item eq $list[$#list]) { + $noclean = $save_clean; + } else { + $noclean = 1; + } + + if (defined($minconfig)) { + build "useconfig:$minconfig" or return 0; + } else { + # ?? no config to use? + build "oldconfig" 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; + + start_monitor_and_install or $failed = 1; + + if (!$failed && $type ne "boot"){ + do_run_test or $failed = 1; + } + end_monitor; + if ($failed) { + print_times; + return 0; + } + patchcheck_reboot; + print_times; + } + $in_patchcheck = 0; + success $i; + + return 1; +} + +sub add_dep { + # $config depends on $dep + my ($config, $dep) = @_; + + if (defined($depends{$config})) { + $depends{$config} .= " " . $dep; + } else { + $depends{$config} = $dep; + } + + # record the number of configs depending on $dep + if (defined $depcount{$dep}) { + $depcount{$dep}++; + } else { + $depcount{$dep} = 1; + } +} + +# taken from streamline_config.pl +sub read_kconfig { + my ($kconfig) = @_; + + my $state = "NONE"; + my $config; + my @kconfigs; + + my $cont = 0; + my $line; + + if (! -f $kconfig) { + doprint "file $kconfig does not exist, skipping\n"; + return; + } + + open(KIN, "$kconfig") + or dodie "Can't open $kconfig"; + while (<KIN>) { + chomp; + + # Make sure that lines ending with \ continue + if ($cont) { + $_ = $line . " " . $_; + } + + if (s/\\$//) { + $cont = 1; + $line = $_; + next; + } + + $cont = 0; + + # collect any Kconfig sources + if (/^source\s*"(.*)"/) { + $kconfigs[$#kconfigs+1] = $1; + } + + # configs found + if (/^\s*(menu)?config\s+(\S+)\s*$/) { + $state = "NEW"; + $config = $2; + + for (my $i = 0; $i < $iflevel; $i++) { + add_dep $config, $ifdeps[$i]; + } + + # collect the depends for the config + } elsif ($state eq "NEW" && /^\s*depends\s+on\s+(.*)$/) { + + add_dep $config, $1; + + # Get the configs that select this config + } elsif ($state eq "NEW" && /^\s*select\s+(\S+)/) { + + # selected by depends on config + add_dep $1, $config; + + # Check for if statements + } elsif (/^if\s+(.*\S)\s*$/) { + my $deps = $1; + # remove beginning and ending non text + $deps =~ s/^[^a-zA-Z0-9_]*//; + $deps =~ s/[^a-zA-Z0-9_]*$//; + + my @deps = split /[^a-zA-Z0-9_]+/, $deps; + + $ifdeps[$iflevel++] = join ':', @deps; + + } elsif (/^endif/) { + + $iflevel-- if ($iflevel); + + # stop on "help" + } elsif (/^\s*help\s*$/) { + $state = "NONE"; + } + } + close(KIN); + + # read in any configs that were found. + foreach $kconfig (@kconfigs) { + if (!defined($read_kconfigs{$kconfig})) { + $read_kconfigs{$kconfig} = 1; + read_kconfig("$builddir/$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"; + my $arch; + while (<IN>) { + if (m,Linux/(\S+)\s+\S+\s+Kernel Configuration,) { + $arch = $1; + last; + } + } + close IN; + + if (!defined($arch)) { + doprint "Could not find arch from config file\n"; + doprint "no dependencies used\n"; + return; + } + + # arch is really the subarch, we need to know + # what directory to look at. + if ($arch eq "i386" || $arch eq "x86_64") { + $arch = "x86"; + } + + my $kconfig = "$builddir/arch/$arch/Kconfig"; + + if (! -f $kconfig && $arch =~ /\d$/) { + my $orig = $arch; + # some subarchs have numbers, truncate them + $arch =~ s/\d*$//; + $kconfig = "$builddir/arch/$arch/Kconfig"; + if (! -f $kconfig) { + doprint "No idea what arch dir $orig is for\n"; + doprint "no dependencies used\n"; + return; + } + } + + read_kconfig($kconfig); +} + +sub make_new_config { + my @configs = @_; + + open (OUT, ">$output_config") + or dodie "Failed to write $output_config"; + + foreach my $config (@configs) { + print OUT "$config\n"; + } + close OUT; +} + +sub chomp_config { + my ($config) = @_; + + $config =~ s/CONFIG_//; + + return $config; +} + +sub get_depends { + my ($dep) = @_; + + my $kconfig = chomp_config $dep; + + $dep = $depends{"$kconfig"}; + + # the dep string we have saves the dependencies as they + # were found, including expressions like ! && ||. We + # want to split this out into just an array of configs. + + my $valid = "A-Za-z_0-9"; + + my @configs; + + while ($dep =~ /[$valid]/) { + if ($dep =~ /^[^$valid]*([$valid]+)/) { + my $conf = "CONFIG_" . $1; + + $configs[$#configs + 1] = $conf; + + $dep =~ s/^[^$valid]*[$valid]+//; + } else { + dodie "this should never happen"; + } + } + + return @configs; +} + +sub test_this_config { + my ($config) = @_; + + my $found; + + # if we already processed this config, skip it + if (defined($processed_configs{$config})) { + return undef; + } + $processed_configs{$config} = 1; + + # if this config failed during this round, skip it + if (defined($nochange_config{$config})) { + return undef; + } + + my $kconfig = chomp_config $config; + + # Test dependencies first + if (defined($depends{"$kconfig"})) { + my @parents = get_depends $config; + foreach my $parent (@parents) { + # if the parent is in the min config, check it first + next if (!defined($min_configs{$parent})); + $found = test_this_config($parent); + if (defined($found)) { + return $found; + } + } + } + + # Remove this config from the list of configs + # 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; + $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; + + if (!defined($configs{$config}) || $configs{$config} =~ /^#/) { + return $config; + } + + doprint "disabling config $config did not change .config\n"; + + $nochange_config{$config} = 1; + + return undef; +} + +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; + } + + # If output_minconfig exists, and the start_minconfig + # came from min_config, than ask if we should use + # that instead. + if (-f $output_minconfig && !$start_minconfig_defined) { + print "$output_minconfig exists\n"; + 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"; + } + } + + if (!defined($start_minconfig)) { + fail "START_MIN_CONFIG or MIN_CONFIG not defined" and return; + } + + my $temp_config = "$tmpdir/temp_config"; + + # First things first. We build an allnoconfig to find + # out what the defaults are that we can't touch. + # Some are selections, but we really can't handle selections. + + my $save_minconfig = $minconfig; + undef $minconfig; + + run_command "$make allnoconfig" or return 0; + + read_depends; + + process_config_ignore $output_config; + + undef %save_configs; + undef %min_configs; + + if (defined($ignore_config)) { + # make sure the file exists + `touch $ignore_config`; + assign_configs \%save_configs, $ignore_config; + } + + %keep_configs = %save_configs; + + doprint "Load initial configs from $start_minconfig\n"; + + # Look at the current min configs, and save off all the + # ones that were set via the allnoconfig + assign_configs \%min_configs, $start_minconfig; + + my @config_keys = keys %min_configs; + + # All configs need a depcount + foreach my $config (@config_keys) { + my $kconfig = chomp_config $config; + if (!defined $depcount{$kconfig}) { + $depcount{$kconfig} = 0; + } + } + + # Remove anything that was set by the make allnoconfig + # we shouldn't need them as they get set for us anyway. + foreach my $config (@config_keys) { + # Remove anything in the ignore_config + if (defined($keep_configs{$config})) { + my $file = $ignore_config; + $file =~ s,.*/(.*?)$,$1,; + doprint "$config set by $file ... ignored\n"; + delete $min_configs{$config}; + next; + } + # But make sure the settings are the same. If a min config + # sets a selection, we do not want to get rid of it if + # it is not the same as what we have. Just move it into + # the keep configs. + if (defined($config_ignore{$config})) { + if ($config_ignore{$config} ne $min_configs{$config}) { + doprint "$config is in allnoconfig as '$config_ignore{$config}'"; + doprint " but it is '$min_configs{$config}' in minconfig .. keeping\n"; + $keep_configs{$config} = $min_configs{$config}; + } else { + doprint "$config set by allnoconfig ... ignored\n"; + } + delete $min_configs{$config}; + } + } + + my $done = 0; + my $take_two = 0; + + while (!$done) { + my $config; + my $found; + + # Now disable each config one by one and do a make oldconfig + # till we find a config that changes our list. + + my @test_configs = keys %min_configs; + + # Sort keys by who is most dependent on + @test_configs = sort { $depcount{chomp_config($b)} <=> $depcount{chomp_config($a)} } + @test_configs ; + + # Put configs that did not modify the config at the end. + my $reset = 1; + for (my $i = 0; $i < $#test_configs; $i++) { + if (!defined($nochange_config{$test_configs[0]})) { + $reset = 0; + last; + } + # This config didn't change the .config last time. + # Place it at the end + my $config = shift @test_configs; + push @test_configs, $config; + } + + # if every test config has failed to modify the .config file + # in the past, then reset and start over. + if ($reset) { + undef %nochange_config; + } + + undef %processed_configs; + + foreach my $config (@test_configs) { + + $found = test_this_config $config; + + last if (defined($found)); + + # oh well, try another config + } + + if (!defined($found)) { + # we could have failed due to the nochange_config hash + # reset and try again + if (!$take_two) { + undef %nochange_config; + $take_two = 1; + next; + } + doprint "No more configs found that we can disable\n"; + $done = 1; + last; + } + $take_two = 0; + + $config = $found; + + doprint "Test with $config disabled\n"; + + # set in_bisect to keep build and monitor from dieing + $in_bisect = 1; + + my $failed = 0; + build "oldconfig" or $failed = 1; + if (!$failed) { + start_monitor_and_install or $failed = 1; + + if ($type eq "test" && !$failed) { + do_run_test or $failed = 1; + } + + end_monitor; + } + + $in_bisect = 0; + + if ($failed) { + doprint "$min_configs{$config} is needed to boot the box... keeping\n"; + # this config is needed, add it to the ignore list. + $keep_configs{$config} = $min_configs{$config}; + $save_configs{$config} = $min_configs{$config}; + delete $min_configs{$config}; + + # update new ignore configs + if (defined($ignore_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"; + } + close OUT; + run_command "mv $temp_config $ignore_config" or + dodie "failed to copy update to $ignore_config"; + } + + } else { + # We booted without this config, remove it from the minconfigs. + doprint "$config is not needed, disabling\n"; + + delete $min_configs{$config}; + + # Also disable anything that is not enabled in this config + my %configs; + assign_configs \%configs, $output_config; + my @config_keys = keys %min_configs; + foreach my $config (@config_keys) { + if (!defined($configs{$config})) { + doprint "$config is not set, disabling\n"; + delete $min_configs{$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"; + } + foreach my $config (keys %min_configs) { + print OUT "$min_configs{$config}\n"; + } + close OUT; + + run_command "mv $temp_config $output_minconfig" or + dodie "failed to copy update to $output_minconfig"; + } + + doprint "Reboot and wait $sleep_time seconds\n"; + 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; +} + +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 (exists($opt{$option})) { + return undef 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 (exists($opt{$option})) { + return undef 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 print_test_preamble { + my ($resolved) = @_; + + doprint "\n\nSTARTING AUTOMATED TESTS\n\n"; + + for (my $i = 0, my $repeat = 1; $i <= $opt{"NUM_TESTS"}; $i += $repeat) { + + if (!$i) { + doprint "DEFAULT OPTIONS:\n"; + } else { + doprint "\nTEST $i OPTIONS"; + if (defined($repeat_tests{$i})) { + $repeat = $repeat_tests{$i}; + doprint " ITERATE $repeat"; + } + doprint "\n"; + } + + foreach my $option (sort keys %opt) { + my $value; + + if ($option =~ /\[(\d+)\]$/) { + next if ($i != $1); + + if ($resolved) { + my $name = $option; + $name =~ s/\[\d+\]$//; + $value = set_test_option($name, $i); + } else { + $value = $opt{$option}; + } + } else { + next if ($i); + + if ($resolved) { + $value = set_test_option($option, 0); + } else { + $value = $opt{$option}; + } + } + + $value = "" if (!defined($value)); + doprint "$option = $value\n"; + } + } +} + +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"); + } + run_post_ktest; + 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" + --dry-run + Print resolved test options and exit without running tests. + + 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 "--dry-run" ) { + $dry_run = 1; + shift; + } 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) { + print "$ktest_config does not exist.\n"; + if (!read_yn "Create it?") { + exit 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 +; + close(OUT); +} +read_config $ktest_config; + +if ($dry_run) { + print_test_preamble 1; + exit 0; +} + +if (defined($opt{"LOG_FILE"})) { + $opt{"LOG_FILE"} = set_test_option("LOG_FILE", 1); +} + +# Append any configs entered in manually to the config file. +my @new_configs = keys %entered_configs; +if ($#new_configs >= 0) { + print "\nAppending entered in configs to $ktest_config\n"; + 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} = process_variables($entered_configs{$config}); + } +} + +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); +} + +print_test_preamble 0; + +$SIG{INT} = qw(cancel_test); + +# First we need to do is the builds +for (my $i = 1; $i <= $opt{"NUM_TESTS"}; $i++) { + + # Do not reboot on failing test options + $no_reboot = 1; + $reboot_success = 0; + + $have_version = 0; + + $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); + } + + $start_minconfig_defined = 1; + + # The first test may override the PRE_KTEST option + if ($i == 1) { + if (defined($pre_ktest)) { + doprint "\n"; + my $ret = run_command $pre_ktest; + if (!$ret && defined($pre_ktest_die) && + $pre_ktest_die) { + dodie "failed to pre_ktest\n"; + } + } + 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 + # 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; + } + + if (!-d $tmpdir) { + mkpath($tmpdir) or + dodie "can't create $tmpdir"; + } + + $ENV{"SSH_USER"} = $ssh_user; + $ENV{"MACHINE"} = $machine; + + $buildlog = "$tmpdir/buildlog-$machine"; + $testlog = "$tmpdir/testlog-$machine"; + $dmesg = "$tmpdir/dmesg-$machine"; + $output_config = "$outputdir/.config"; + + if (!$buildonly) { + $target = "$ssh_user\@$machine"; + 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)); + 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 = $patchcheck_type; + } elsif ($test_type eq "bisect") { + $run_type = $bisect_type; + } elsif ($test_type eq "config_bisect") { + $run_type = $config_bisect_type; + } elsif ($test_type eq "make_min_config") { + $run_type = ""; + } elsif ($test_type eq "make_warnings_file") { + $run_type = ""; + } + + # mistake in config file? + if (!defined($run_type)) { + $run_type = "ERROR"; + } + + my $installme = ""; + $installme = " no_install" if ($no_install); + + my $name = ""; + + if (defined($test_name)) { + $name = " ($test_name)"; + } + + doprint "\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)) { + my $ret = run_command $pre_test; + if (!$ret && defined($pre_test_die) && + $pre_test_die) { + dodie "failed to pre_test\n"; + } + } + + unlink $dmesg; + unlink $buildlog; + unlink $testlog; + + if (defined($addconfig)) { + my $min = $minconfig; + if (!defined($minconfig)) { + $min = ""; + } + run_command "cat $addconfig $min > $tmpdir/add_config" or + dodie "Failed to create temp config"; + $minconfig = "$tmpdir/add_config"; + } + + if (defined($checkout)) { + run_command "git checkout $checkout" or + dodie "failed to checkout $checkout"; + } + + $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; + next; + } elsif ($test_type eq "config_bisect") { + config_bisect $i; + next; + } elsif ($test_type eq "patchcheck") { + patchcheck $i; + next; + } 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") { + get_version; + install; + success $i; + next; + } + + if ($test_type ne "build") { + my $failed = 0; + start_monitor_and_install or $failed = 1; + + if (!$failed && $test_type ne "boot" && defined($run_test)) { + do_run_test or $failed = 1; + } + end_monitor; + if ($failed) { + print_times; + next; + } + } + + print_times; + + success $i; +} + +run_post_ktest; + +if ($opt{"POWEROFF_ON_SUCCESS"}) { + halt; +} 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"; + +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 diff --git a/tools/testing/ktest/sample.conf b/tools/testing/ktest/sample.conf new file mode 100644 index 000000000..b6e439ef5 --- /dev/null +++ b/tools/testing/ktest/sample.conf @@ -0,0 +1,1407 @@ +# +# Config file for ktest.pl +# +# Place your customized version of this, in the working directory that +# ktest.pl is run from. By default, ktest.pl will look for a file +# called "ktest.conf", but you can name it anything you like and specify +# the name of your config file as the first argument of ktest.pl. +# +# Note, all paths must be absolute +# + +# Options set in the beginning of the file are considered to be +# default options. These options can be overridden by test specific +# options, with the following exceptions: +# +# LOG_FILE +# CLEAR_LOG +# POWEROFF_ON_SUCCESS +# REBOOT_ON_SUCCESS +# +# Test specific options are set after the label: +# +# TEST_START +# +# The options after a TEST_START label are specific to that test. +# Each TEST_START label will set up a new test. If you want to +# perform a test more than once, you can add the ITERATE label +# to it followed by the number of times you want that test +# to iterate. If the ITERATE is left off, the test will only +# be performed once. +# +# TEST_START ITERATE 10 +# +# You can skip a test by adding SKIP (before or after the ITERATE +# and number) +# +# TEST_START SKIP +# +# TEST_START SKIP ITERATE 10 +# +# TEST_START ITERATE 10 SKIP +# +# The SKIP label causes the options and the test itself to be ignored. +# This is useful to set up several different tests in one config file, and +# only enabling the ones you want to use for a current test run. +# +# You can add default options anywhere in the file as well +# with the DEFAULTS tag. This allows you to have default options +# after the test options to keep the test options at the top +# of the file. You can even place the DEFAULTS tag between +# test cases (but not in the middle of a single test case) +# +# TEST_START +# MIN_CONFIG = /home/test/config-test1 +# +# DEFAULTS +# MIN_CONFIG = /home/test/config-default +# +# TEST_START ITERATE 10 +# +# The above will run the first test with MIN_CONFIG set to +# /home/test/config-test-1. Then 10 tests will be executed +# with MIN_CONFIG with /home/test/config-default. +# +# You can also disable defaults with the SKIP option +# +# DEFAULTS SKIP +# MIN_CONFIG = /home/test/config-use-sometimes +# +# DEFAULTS +# MIN_CONFIG = /home/test/config-most-times +# +# The above will ignore the first MIN_CONFIG. If you want to +# use the first MIN_CONFIG, remove the SKIP from the first +# DEFAULTS tag and add it to the second. Be careful, options +# may only be declared once per test or default. If you have +# the same option name under the same test or as default +# ktest will fail to execute, and no tests will run. +# +# DEFAULTS OVERRIDE +# +# Options defined in the DEFAULTS section can not be duplicated +# even if they are defined in two different DEFAULT sections. +# This is done to catch mistakes where an option is added but +# the previous option was forgotten about and not commented. +# +# The OVERRIDE keyword can be added to a section to allow this +# section to override other DEFAULT sections values that have +# been defined previously. It will only override options that +# have been defined before its use. Options defined later +# in a non override section will still error. The same option +# can not be defined in the same section even if that section +# is marked OVERRIDE. +# +# +# +# Both TEST_START and DEFAULTS sections can also have the IF keyword +# The value after the IF must evaluate into a 0 or non 0 positive +# integer, and can use the config variables (explained below). +# +# DEFAULTS IF ${IS_X86_32} +# +# The above will process the DEFAULTS section if the config +# variable IS_X86_32 evaluates to a non zero positive integer +# otherwise if it evaluates to zero, it will act the same +# as if the SKIP keyword was used. +# +# The ELSE keyword can be used directly after a section with +# a IF statement. +# +# TEST_START IF ${RUN_NET_TESTS} +# BUILD_TYPE = useconfig:${CONFIG_DIR}/config-network +# +# ELSE +# +# BUILD_TYPE = useconfig:${CONFIG_DIR}/config-normal +# +# +# The ELSE keyword can also contain an IF statement to allow multiple +# if then else sections. But all the sections must be either +# DEFAULT or TEST_START, they can not be a mixture. +# +# TEST_START IF ${RUN_NET_TESTS} +# BUILD_TYPE = useconfig:${CONFIG_DIR}/config-network +# +# ELSE IF ${RUN_DISK_TESTS} +# BUILD_TYPE = useconfig:${CONFIG_DIR}/config-tests +# +# ELSE IF ${RUN_CPU_TESTS} +# BUILD_TYPE = useconfig:${CONFIG_DIR}/config-cpu +# +# ELSE +# BUILD_TYPE = useconfig:${CONFIG_DIR}/config-network +# +# The if statement may also have comparisons that will and for +# == and !=, strings may be used for both sides. +# +# BOX_TYPE := x86_32 +# +# DEFAULTS IF ${BOX_TYPE} == x86_32 +# BUILD_TYPE = useconfig:${CONFIG_DIR}/config-32 +# ELSE +# BUILD_TYPE = useconfig:${CONFIG_DIR}/config-64 +# +# The DEFINED keyword can be used by the IF statements too. +# It returns true if the given config variable or option has been defined +# or false otherwise. +# +# +# DEFAULTS IF DEFINED USE_CC +# CC := ${USE_CC} +# ELSE +# CC := gcc +# +# +# As well as NOT DEFINED. +# +# DEFAULTS IF NOT DEFINED MAKE_CMD +# MAKE_CMD := make ARCH=x86 +# +# +# And/or ops (&&,||) may also be used to make complex conditionals. +# +# TEST_START IF (DEFINED ALL_TESTS || ${MYTEST} == boottest) && ${MACHINE} == gandalf +# +# Notice the use of parentheses. Without any parentheses the above would be +# processed the same as: +# +# TEST_START IF DEFINED ALL_TESTS || (${MYTEST} == boottest && ${MACHINE} == gandalf) +# +# +# +# INCLUDE file +# +# The INCLUDE keyword may be used in DEFAULT sections. This will +# read another config file and process that file as well. The included +# file can include other files, add new test cases or default +# statements. Config variables will be passed to these files and changes +# to config variables will be seen by top level config files. Including +# a file is processed just like the contents of the file was cut and pasted +# into the top level file, except, that include files that end with +# TEST_START sections will have that section ended at the end of +# the include file. That is, an included file is included followed +# by another DEFAULT keyword. +# +# Unlike other files referenced in this config, the file path does not need +# to be absolute. If the file does not start with '/', then the directory +# that the current config file was located in is used. If no config by the +# given name is found there, then the current directory is searched. +# +# INCLUDE myfile +# DEFAULT +# +# is the same as: +# +# INCLUDE myfile +# +# Note, if the include file does not contain a full path, the file is +# searched first by the location of the original include file, and then +# by the location that ktest.pl was executed in. +# + +#### Config variables #### +# +# This config file can also contain "config variables". +# These are assigned with ":=" instead of the ktest option +# assignment "=". +# +# The difference between ktest options and config variables +# is that config variables can be used multiple times, +# where each instance will override the previous instance. +# And that they only live at time of processing this config. +# +# The advantage to config variables are that they can be used +# by any option or any other config variables to define thing +# that you may use over and over again in the options. +# +# For example: +# +# USER := root +# TARGET := mybox +# TEST_CASE := ssh ${USER}@${TARGET} /path/to/my/test +# +# TEST_START +# MIN_CONFIG = config1 +# TEST = ${TEST_CASE} +# +# TEST_START +# MIN_CONFIG = config2 +# TEST = ${TEST_CASE} +# +# TEST_CASE := ssh ${USER}@${TARGET} /path/to/my/test2 +# +# TEST_START +# MIN_CONFIG = config1 +# TEST = ${TEST_CASE} +# +# TEST_START +# MIN_CONFIG = config2 +# TEST = ${TEST_CASE} +# +# TEST_DIR := /home/me/test +# +# BUILD_DIR = ${TEST_DIR}/linux.git +# OUTPUT_DIR = ${TEST_DIR}/test +# +# Note, the config variables are evaluated immediately, thus +# updating TARGET after TEST_CASE has been assigned does nothing +# to TEST_CASE. +# +# As shown in the example, to evaluate a config variable, you +# use the ${X} convention. Simple $X will not work. +# +# If the config variable does not exist, the ${X} will not +# be evaluated. Thus: +# +# MAKE_CMD = PATH=/mypath:${PATH} make +# +# If PATH is not a config variable, then the ${PATH} in +# the MAKE_CMD option will be evaluated by the shell when +# the MAKE_CMD option is passed into shell processing. +# +# Shell commands can also be inserted with the ${shell <command>} +# expression. Note, this is case sensitive, thus ${SHELL <command>} +# will not work. +# +# HOSTNAME := ${shell hostname} +# DEFAULTS IF "${HOSTNAME}" == "frodo" +# + +#### Using options in other options #### +# +# Options that are defined in the config file may also be used +# by other options. All options are evaluated at time of +# use (except that config variables are evaluated at config +# processing time). +# +# If an ktest option is used within another option, instead of +# typing it again in that option you can simply use the option +# just like you can config variables. +# +# MACHINE = mybox +# +# TEST = ssh root@${MACHINE} /path/to/test +# +# The option will be used per test case. Thus: +# +# TEST_TYPE = test +# TEST = ssh root@{MACHINE} +# +# TEST_START +# MACHINE = box1 +# +# TEST_START +# MACHINE = box2 +# +# For both test cases, MACHINE will be evaluated at the time +# of the test case. The first test will run ssh root@box1 +# and the second will run ssh root@box2. + +#### Mandatory Default Options #### + +# These options must be in the default section, although most +# may be overridden by test options. + +# The machine hostname that you will test +#MACHINE = target + +# The box is expected to have ssh on normal bootup, provide the user +# (most likely root, since you need privileged operations) +#SSH_USER = root + +# The directory that contains the Linux source code +#BUILD_DIR = /home/test/linux.git + +# The directory that the objects will be built +# (can not be same as BUILD_DIR) +#OUTPUT_DIR = /home/test/build/target + +# The location of the compiled file to copy to the target +# (relative to OUTPUT_DIR) +#BUILD_TARGET = arch/x86/boot/bzImage + +# The place to put your image on the test machine +#TARGET_IMAGE = /boot/vmlinuz-test + +# A script or command to reboot the box +# +# Here is a digital loggers power switch example +#POWER_CYCLE = wget --no-proxy -O /dev/null -q --auth-no-challenge 'http://admin:admin@power/outlet?5=CCL' +# +# Here is an example to reboot a virtual box on the current host +# with the name "Guest". +#POWER_CYCLE = virsh destroy Guest; sleep 5; virsh start Guest + +# The script or command that reads the console +# +# If you use ttywatch server, something like the following would work. +#CONSOLE = nc -d localhost 3001 +# +# For a virtual machine with guest name "Guest". +#CONSOLE = virsh console Guest + +# Signal to send to kill console. +# ktest.pl will create a child process to monitor the console. +# When the console is finished, ktest will kill the child process +# with this signal. +# (default INT) +#CLOSE_CONSOLE_SIGNAL = HUP + +# Required version ending to differentiate the test +# from other linux builds on the system. +#LOCALVERSION = -test + +# For REBOOT_TYPE = grub2, you must specify where the grub.cfg +# file is. This is the file that is searched to find the menu +# option to boot to with GRUB_REBOOT +#GRUB_FILE = /boot/grub2/grub.cfg + +# The tool for REBOOT_TYPE = grub2 or grub2bls to set the next reboot kernel +# to boot into (one shot mode). +# (default grub2_reboot) +#GRUB_REBOOT = grub2_reboot + +# The grub title name for the test kernel to boot +# (Only mandatory if REBOOT_TYPE = grub or grub2 or grub2bls) +# +# Note, ktest.pl will not update the grub menu.lst, you need to +# manually add an option for the test. ktest.pl will search +# the grub menu.lst for this option to find what kernel to +# reboot into. +# +# For example, if in the /boot/grub/menu.lst the test kernel title has: +# title Test Kernel +# kernel vmlinuz-test +# +# For grub2, a search of top level "menuentry"s are done. No +# submenu is searched. The menu is found by searching for the +# contents of GRUB_MENU in the line that starts with "menuentry". +# You may want to include the quotes around the option. For example: +# for: menuentry 'Test Kernel' +# do a: GRUB_MENU = 'Test Kernel' +# For customizing, add your entry in /etc/grub.d/40_custom. +# +# For grub2bls, a search of "title"s are done. The menu is found +# by searching for the contents of GRUB_MENU in the line that starts +# with "title". +# +#GRUB_MENU = Test Kernel + +# For REBOOT_TYPE = syslinux, the name of the syslinux executable +# (on the target) to use to set up the next reboot to boot the +# test kernel. +# (default extlinux) +#SYSLINUX = syslinux + +# For REBOOT_TYPE = syslinux, the path that is passed to to the +# syslinux command where syslinux is installed. +# (default /boot/extlinux) +#SYSLINUX_PATH = /boot/syslinux + +# For REBOOT_TYPE = syslinux, the syslinux label that references the +# test kernel in the syslinux config file. +# (default undefined) +#SYSLINUX_LABEL = "test-kernel" + +# A script to reboot the target into the test kernel +# This and SWITCH_TO_TEST are about the same, except +# SWITCH_TO_TEST is run even for REBOOT_TYPE = grub. +# This may be left undefined. +# (default undefined) +#REBOOT_SCRIPT = + +#### Optional Config Options (all have defaults) #### + +# Email options for receiving notifications. Users must setup +# the specified mailer prior to using this feature. +# +# (default undefined) +#MAILTO = +# +# Supported mailers: sendmail, mail, mailx +# (default sendmail) +#MAILER = sendmail +# +# The executable to run +# (default: for sendmail "/usr/sbin/sendmail", otherwise equals ${MAILER}) +#MAIL_EXEC = /usr/sbin/sendmail +# +# The command used to send mail, which uses the above options +# can be modified. By default if the mailer is "sendmail" then +# MAIL_COMMAND = echo \'Subject: $SUBJECT\n\n$MESSAGE\' | $MAIL_PATH/$MAILER -t $MAILTO +# For mail or mailx: +# MAIL_COMMAND = "$MAIL_PATH/$MAILER -s \'$SUBJECT\' $MAILTO <<< \'$MESSAGE\' +# ktest.pl will do the substitution for MAIL_PATH, MAILER, MAILTO at the time +# it sends the mail if "$FOO" format is used. If "${FOO}" format is used, +# then the substitutions will occur at the time the config file is read. +# But note, MAIL_PATH and MAILER require being set by the config file if +# ${MAIL_PATH} or ${MAILER} are used, but not if $MAIL_PATH or $MAILER are. +#MAIL_COMMAND = echo \'Subject: $SUBJECT\n\n$MESSAGE\' | $MAIL_PATH/$MAILER -t $MAILTO +# +# Errors are defined as those would terminate the script +# (default 1) +#EMAIL_ON_ERROR = 1 +# (default 1) +#EMAIL_WHEN_FINISHED = 1 +# (default 0) +#EMAIL_WHEN_STARTED = 1 +# +# Users can cancel the test by Ctrl^C +# (default 0) +#EMAIL_WHEN_CANCELED = 1 +# +# If a test ends with an error and EMAIL_ON_ERROR is set as well +# as a LOG_FILE is defined, then the log of the failing test will +# be included in the email that is sent. +# It is possible that the log may be very large, in which case, +# only the last amount of the log should be sent. To limit how +# much of the log is sent, set MAIL_MAX_SIZE. This will be the +# size in bytes of the last portion of the log of the failed +# test file. That is, if this is set to 100000, then only the +# last 100 thousand bytes of the log file will be included in +# the email. +# (default undef) +#MAIL_MAX_SIZE = 1000000 + +# Start a test setup. If you leave this off, all options +# will be default and the test will run once. +# This is a label and not really an option (it takes no value). +# You can append ITERATE and a number after it to iterate the +# test a number of times, or SKIP to ignore this test. +# +#TEST_START +#TEST_START ITERATE 5 +#TEST_START SKIP + +# Have the following options as default again. Used after tests +# have already been defined by TEST_START. Optionally, you can +# just define all default options before the first TEST_START +# and you do not need this option. +# +# This is a label and not really an option (it takes no value). +# You can append SKIP to this label and the options within this +# section will be ignored. +# +# DEFAULTS +# DEFAULTS SKIP + +# If you want to execute some command before the first test runs +# you can set this option. Note, it can be set as a default option +# or an option in the first test case. All other test cases will +# ignore it. If both the default and first test have this option +# set, then the first test will take precedence. +# +# default (undefined) +#PRE_KTEST = ${SSH} ~/set_up_test +# +# To specify if the test should fail if PRE_KTEST fails, +# PRE_KTEST_DIE needs to be set to 1. Otherwise the PRE_KTEST +# result is ignored. +# (default 0) +#PRE_KTEST_DIE = 1 + +# If you want to execute some command after all the tests have +# completed, you can set this option. Note, it can be set as a +# default or any test case can override it. If multiple test cases +# set this option, then the last test case that set it will take +# precedence +# +# default (undefined) +#POST_KTEST = ${SSH} ~/dismantle_test + +# If you want to remove the kernel entry in Boot Loader Specification (BLS) +# environment, use kernel-install command. +# Here's the example: +#POST_KTEST = ssh root@Test "/usr/bin/kernel-install remove $KERNEL_VERSION" + +# The default test type (default test) +# The test types may be: +# build - only build the kernel, do nothing else +# install - build and install, but do nothing else (does not reboot) +# boot - build, install, and boot the kernel +# test - build, boot and if TEST is set, run the test script +# (If TEST is not set, it defaults back to boot) +# bisect - Perform a bisect on the kernel (see BISECT_TYPE below) +# patchcheck - Do a test on a series of commits in git (see PATCHCHECK below) +#TEST_TYPE = test + +# Test to run if there is a successful boot and TEST_TYPE is test. +# Must exit with 0 on success and non zero on error +# default (undefined) +#TEST = ssh user@machine /root/run_test + +# The build type is any make config type or special command +# (default oldconfig) +# nobuild - skip the clean and build step +# useconfig:/path/to/config - use the given config and run +# oldconfig on it. +# This option is ignored if TEST_TYPE is patchcheck or bisect +#BUILD_TYPE = randconfig + +# The make command (default make) +# If you are building a 32bit x86 on a 64 bit host +#MAKE_CMD = CC=i386-gcc AS=i386-as make ARCH=i386 + +# Any build options for the make of the kernel (not for other makes, like configs) +# (default "") +#BUILD_OPTIONS = -j20 + +# If you need to do some special handling before installing +# you can add a script with this option. +# The environment variable KERNEL_VERSION will be set to the +# kernel version that is used. +# +# default (undefined) +#PRE_INSTALL = ssh user@target rm -rf '/lib/modules/*-test*' + +# If you need an initrd, you can add a script or code here to install +# it. The environment variable KERNEL_VERSION will be set to the +# kernel version that is used. Remember to add the initrd line +# to your grub menu.lst file. +# +# Here's a couple of examples to use: +#POST_INSTALL = ssh user@target /sbin/mkinitrd --allow-missing -f /boot/initramfs-test.img $KERNEL_VERSION +# +# or on some systems: +#POST_INSTALL = ssh user@target /sbin/dracut -f /boot/initramfs-test.img $KERNEL_VERSION + +# If you want to add the kernel entry in Boot Loader Specification (BLS) +# environment, use kernel-install command. +# Here's the example: +#POST_INSTALL = ssh root@Test "/usr/bin/kernel-install add $KERNEL_VERSION /boot/vmlinuz-$KERNEL_VERSION" + +# If for some reason you just want to boot the kernel and you do not +# want the test to install anything new. For example, you may just want +# to boot test the same kernel over and over and do not want to go through +# the hassle of installing anything, you can set this option to 1 +# (default 0) +#NO_INSTALL = 1 + +# If there is a command that you want to run before the individual test +# case executes, then you can set this option +# +# default (undefined) +#PRE_TEST = ${SSH} reboot_to_special_kernel + +# To kill the entire test if PRE_TEST is defined but fails set this +# to 1. +# (default 0) +#PRE_TEST_DIE = 1 + +# If there is a command you want to run after the individual test case +# completes, then you can set this option. +# +# default (undefined) +#POST_TEST = cd ${BUILD_DIR}; git reset --hard + +# If there is a script that you require to run before the build is done +# you can specify it with PRE_BUILD. +# +# One example may be if you must add a temporary patch to the build to +# fix a unrelated bug to perform a patchcheck test. This will apply the +# patch before each build that is made. Use the POST_BUILD to do a git reset --hard +# to remove the patch. +# +# (default undef) +#PRE_BUILD = cd ${BUILD_DIR} && patch -p1 < /tmp/temp.patch + +# To specify if the test should fail if the PRE_BUILD fails, +# PRE_BUILD_DIE needs to be set to 1. Otherwise the PRE_BUILD +# result is ignored. +# (default 0) +# PRE_BUILD_DIE = 1 + +# If there is a script that should run after the build is done +# you can specify it with POST_BUILD. +# +# As the example in PRE_BUILD, POST_BUILD can be used to reset modifications +# made by the PRE_BUILD. +# +# (default undef) +#POST_BUILD = cd ${BUILD_DIR} && git reset --hard + +# To specify if the test should fail if the POST_BUILD fails, +# POST_BUILD_DIE needs to be set to 1. Otherwise the POST_BUILD +# result is ignored. +# (default 0) +#POST_BUILD_DIE = 1 + +# Way to reboot the box to the test kernel. +# Only valid options so far are "grub", "grub2", "syslinux" and "script" +# (default grub) +# If you specify grub, it will assume grub version 1 +# and will search in /boot/grub/menu.lst for the title $GRUB_MENU +# and select that target to reboot to the kernel. If this is not +# your setup, then specify "script" and have a command or script +# specified in REBOOT_SCRIPT to boot to the target. +# +# For REBOOT_TYPE = grub2, you must define both GRUB_MENU and +# GRUB_FILE. +# +# For REBOOT_TYPE = grub2bls, you must define GRUB_MENU. +# +# For REBOOT_TYPE = syslinux, you must define SYSLINUX_LABEL, and +# perhaps modify SYSLINUX (default extlinux) and SYSLINUX_PATH +# (default /boot/extlinux) +# +# The entry in /boot/grub/menu.lst must be entered in manually. +# The test will not modify that file. +#REBOOT_TYPE = grub + +# If you are using a machine that doesn't boot with grub, and +# perhaps gets its kernel from a remote server (tftp), then +# you can use this option to update the target image with the +# test image. +# +# You could also do the same with POST_INSTALL, but the difference +# between that option and this option is that POST_INSTALL runs +# after the install, where this one runs just before a reboot. +# (default undefined) +#SWITCH_TO_TEST = cp ${OUTPUT_DIR}/${BUILD_TARGET} ${TARGET_IMAGE} + +# If you are using a machine that doesn't boot with grub, and +# perhaps gets its kernel from a remote server (tftp), then +# you can use this option to update the target image with the +# the known good image to reboot safely back into. +# +# This option holds a command that will execute before needing +# to reboot to a good known image. +# (default undefined) +#SWITCH_TO_GOOD = ssh ${SSH_USER}/${MACHINE} cp good_image ${TARGET_IMAGE} + +# The min config that is needed to build for the machine +# A nice way to create this is with the following: +# +# $ ssh target +# $ lsmod > mymods +# $ scp mymods host:/tmp +# $ exit +# $ cd linux.git +# $ rm .config +# $ make LSMOD=mymods localyesconfig +# $ grep '^CONFIG' .config > /home/test/config-min +# +# If you want even less configs: +# +# log in directly to target (do not ssh) +# +# $ su +# # lsmod | cut -d' ' -f1 | xargs rmmod +# +# repeat the above several times +# +# # lsmod > mymods +# # reboot +# +# May need to reboot to get your network back to copy the mymods +# to the host, and then remove the previous .config and run the +# localyesconfig again. The CONFIG_MIN generated like this will +# not guarantee network activity to the box so the TEST_TYPE of +# test may fail. +# +# You might also want to set: +# CONFIG_CMDLINE="<your options here>" +# randconfig may set the above and override your real command +# line options. +# (default undefined) +#MIN_CONFIG = /home/test/config-min + +# Sometimes there's options that just break the boot and +# you do not care about. Here are a few: +# # CONFIG_STAGING is not set +# Staging drivers are horrible, and can break the build. +# # CONFIG_SCSI_DEBUG is not set +# SCSI_DEBUG may change your root partition +# # CONFIG_KGDB_SERIAL_CONSOLE is not set +# KGDB may cause oops waiting for a connection that's not there. +# This option points to the file containing config options that will be prepended +# to the MIN_CONFIG (or be the MIN_CONFIG if it is not set) +# +# Note, config options in MIN_CONFIG will override these options. +# +# (default undefined) +#ADD_CONFIG = /home/test/config-broken + +# The location on the host where to write temp files +# (default /tmp/ktest/${MACHINE}) +#TMP_DIR = /tmp/ktest/${MACHINE} + +# Optional log file to write the status (recommended) +# Note, this is a DEFAULT section only option. +# (default undefined) +#LOG_FILE = /home/test/logfiles/target.log + +# Remove old logfile if it exists before starting all tests. +# Note, this is a DEFAULT section only option. +# (default 0) +#CLEAR_LOG = 0 + +# Line to define a successful boot up in console output. +# This is what the line contains, not the entire line. If you need +# the entire line to match, then use regular expression syntax like: +# (do not add any quotes around it) +# +# SUCCESS_LINE = ^MyBox Login:$ +# +# (default "login:") +#SUCCESS_LINE = login: + +# To speed up between reboots, defining a line that the +# default kernel produces that represents that the default +# kernel has successfully booted and can be used to pass +# a new test kernel to it. Otherwise ktest.pl will wait till +# SLEEP_TIME to continue. +# (default undefined) +#REBOOT_SUCCESS_LINE = login: + +# In case the console constantly fills the screen, having +# a specified time to stop the test after success is recommended. +# (in seconds) +# (default 10) +#STOP_AFTER_SUCCESS = 10 + +# In case the console constantly fills the screen, having +# a specified time to stop the test after failure is recommended. +# (in seconds) +# (default 60) +#STOP_AFTER_FAILURE = 60 + +# In case the console constantly fills the screen, having +# a specified time to stop the test if it never succeeds nor fails +# is recommended. +# Note: this is ignored if a success or failure is detected. +# (in seconds) +# (default 600, -1 is to never stop) +#STOP_TEST_AFTER = 600 + +# Stop testing if a build fails. If set, the script will end if +# a failure is detected, otherwise it will save off the .config, +# dmesg and bootlog in a directory called +# MACHINE-TEST_TYPE_BUILD_TYPE-fail-yyyymmddhhmmss +# if the STORE_FAILURES directory is set. +# (default 1) +# Note, even if this is set to zero, there are some errors that still +# stop the tests. +#DIE_ON_FAILURE = 1 + +# Directory to store failure directories on failure. If this is not +# set, DIE_ON_FAILURE=0 will not save off the .config, dmesg and +# bootlog. This option is ignored if DIE_ON_FAILURE is not set. +# (default undefined) +#STORE_FAILURES = /home/test/failures + +# Directory to store success directories on success. If this is not +# set, the .config, dmesg and bootlog will not be saved if a +# test succeeds. +# (default undefined) +#STORE_SUCCESSES = /home/test/successes + +# Build without doing a make mrproper, or removing .config +# (default 0) +#BUILD_NOCLEAN = 0 + +# As the test reads the console, after it hits the SUCCESS_LINE +# the time it waits for the monitor to settle down between reads +# can usually be lowered. +# (in seconds) (default 1) +#BOOTED_TIMEOUT = 1 + +# The timeout in seconds when we consider the box hung after +# the console stop producing output. Be sure to leave enough +# time here to get pass a reboot. Some machines may not produce +# any console output for a long time during a reboot. You do +# not want the test to fail just because the system was in +# the process of rebooting to the test kernel. +# (default 120) +#TIMEOUT = 120 + +# The timeout in seconds when to test if the box can be rebooted +# or not. Before issuing the reboot command, a ssh connection +# is attempted to see if the target machine is still active. +# If the target does not connect within this timeout, a power cycle +# is issued instead of a reboot. +# CONNECT_TIMEOUT = 25 + +# The timeout in seconds for how long to wait for any running command +# to timeout. If not defined, it will let it go indefinitely. +# (default undefined) +#RUN_TIMEOUT = 600 + +# In between tests, a reboot of the box may occur, and this +# is the time to wait for the console after it stops producing +# output. Some machines may not produce a large lag on reboot +# so this should accommodate it. +# The difference between this and TIMEOUT, is that TIMEOUT happens +# when rebooting to the test kernel. This sleep time happens +# after a test has completed and we are about to start running +# another test. If a reboot to the reliable kernel happens, +# we wait SLEEP_TIME for the console to stop producing output +# before starting the next test. +# +# You can speed up reboot times even more by setting REBOOT_SUCCESS_LINE. +# (default 60) +#SLEEP_TIME = 60 + +# The time in between bisects to sleep (in seconds) +# (default 60) +#BISECT_SLEEP_TIME = 60 + +# The max wait time (in seconds) for waiting for the console to finish. +# If for some reason, the console is outputting content without +# ever finishing, this will cause ktest to get stuck. This +# option is the max time ktest will wait for the monitor (console) +# to settle down before continuing. +# (default 1800) +#MAX_MONITOR_WAIT + +# The time in between patch checks to sleep (in seconds) +# (default 60) +#PATCHCHECK_SLEEP_TIME = 60 + +# Reboot the target box on error (default 0) +#REBOOT_ON_ERROR = 0 + +# Power off the target on error (ignored if REBOOT_ON_ERROR is set) +# Note, this is a DEFAULT section only option. +# (default 0) +#POWEROFF_ON_ERROR = 0 + +# Power off the target after all tests have completed successfully +# Note, this is a DEFAULT section only option. +# (default 0) +#POWEROFF_ON_SUCCESS = 0 + +# Reboot the target after all test completed successfully (default 1) +# (ignored if POWEROFF_ON_SUCCESS is set) +#REBOOT_ON_SUCCESS = 1 + +# In case there are issues with rebooting, you can specify this +# to always powercycle after this amount of time after calling +# reboot. +# Note, POWERCYCLE_AFTER_REBOOT = 0 does NOT disable it. It just +# makes it powercycle immediately after rebooting. Do not define +# it if you do not want it. +# (default undefined) +#POWERCYCLE_AFTER_REBOOT = 5 + +# In case there's issues with halting, you can specify this +# to always poweroff after this amount of time after calling +# halt. +# Note, POWEROFF_AFTER_HALT = 0 does NOT disable it. It just +# makes it poweroff immediately after halting. Do not define +# it if you do not want it. +# (default undefined) +#POWEROFF_AFTER_HALT = 20 + +# A script or command to power off the box (default undefined) +# Needed for POWEROFF_ON_ERROR and SUCCESS +# +# Example for digital loggers power switch: +#POWER_OFF = wget --no-proxy -O /dev/null -q --auth-no-challenge 'http://admin:admin@power/outlet?5=OFF' +# +# Example for a virtual guest call "Guest". +#POWER_OFF = virsh destroy Guest + +# To have the build fail on "new" warnings, create a file that +# contains a list of all known warnings (they must match exactly +# to the line with 'warning:', 'error:' or 'Error:'. If the option +# WARNINGS_FILE is set, then that file will be read, and if the +# build detects a warning, it will examine this file and if the +# warning does not exist in it, it will fail the build. +# +# Note, if this option is defined to a file that does not exist +# then any warning will fail the build. +# (see make_warnings_file below) +# +# (optional, default undefined) +#WARNINGS_FILE = ${OUTPUT_DIR}/warnings_file + +# The way to execute a command on the target +# (default ssh $SSH_USER@$MACHINE $SSH_COMMAND";) +# The variables SSH_USER, MACHINE and SSH_COMMAND are defined +#SSH_EXEC = ssh $SSH_USER@$MACHINE $SSH_COMMAND"; + +# The way to copy a file to the target (install and modules) +# (default scp $SRC_FILE $SSH_USER@$MACHINE:$DST_FILE) +# The variables SSH_USER, MACHINE are defined by the config +# SRC_FILE and DST_FILE are ktest internal variables and +# should only have '$' and not the '${}' notation. +# (default scp $SRC_FILE ${SSH_USER}@${MACHINE}:$DST_FILE) +#SCP_TO_TARGET = echo skip scp for $SRC_FILE $DST_FILE + +# If install needs to be different than modules, then this +# option will override the SCP_TO_TARGET for installation. +# (default ${SCP_TO_TARGET} ) +#SCP_TO_TARGET_INSTALL = scp $SRC_FILE tftp@tftpserver:$DST_FILE + +# The nice way to reboot the target +# (default ssh $SSH_USER@$MACHINE reboot) +# The variables SSH_USER and MACHINE are defined. +#REBOOT = ssh $SSH_USER@$MACHINE reboot + +# The return code of REBOOT +# (default 255) +#REBOOT_RETURN_CODE = 255 + +# The way triple faults are detected is by testing the kernel +# banner. If the kernel banner for the kernel we are testing is +# found, and then later a kernel banner for another kernel version +# is found, it is considered that we encountered a triple fault, +# and there is no panic or callback, but simply a reboot. +# To disable this (because it did a false positive) set the following +# to 0. +# (default 1) +#DETECT_TRIPLE_FAULT = 0 + +# All options in the config file should be either used by ktest +# or could be used within a value of another option. If an option +# in the config file is not used, ktest will warn about it and ask +# if you want to continue. +# +# If you don't care if there are non-used options, enable this +# option. Be careful though, a non-used option is usually a sign +# of an option name being typed incorrectly. +# (default 0) +#IGNORE_UNUSED = 1 + +# When testing a kernel that happens to have WARNINGs, and call +# traces, ktest.pl will detect these and fail a boot or test run +# due to warnings. By setting this option, ktest will ignore +# call traces, and will not fail a test if the kernel produces +# an oops. Use this option with care. +# (default 0) +#IGNORE_ERRORS = 1 + +#### Per test run options #### +# The following options are only allowed in TEST_START sections. +# They are ignored in the DEFAULTS sections. +# +# All of these are optional and undefined by default, although +# some of these options are required for TEST_TYPE of patchcheck +# and bisect. +# +# +# CHECKOUT = branch +# +# If the BUILD_DIR is a git repository, then you can set this option +# to checkout the given branch before running the TEST. If you +# specify this for the first run, that branch will be used for +# all preceding tests until a new CHECKOUT is set. +# +# +# TEST_NAME = name +# +# If you want the test to have a name that is displayed in +# the test result banner at the end of the test, then use this +# option. This is useful to search for the RESULT keyword and +# not have to translate a test number to a test in the config. +# +# For TEST_TYPE = patchcheck +# +# This expects the BUILD_DIR to be a git repository, and +# will checkout the PATCHCHECK_START commit. +# +# The option BUILD_TYPE will be ignored. +# +# The MIN_CONFIG will be used for all builds of the patchcheck. The build type +# used for patchcheck is oldconfig. +# +# PATCHCHECK_START is required and is the first patch to +# test (the SHA1 of the commit). You may also specify anything +# that git checkout allows (branch name, tag, HEAD~3). +# +# PATCHCHECK_END is the last patch to check (default HEAD) +# +# PATCHCHECK_CHERRY if set to non zero, then git cherry will be +# performed against PATCHCHECK_START and PATCHCHECK_END. That is +# +# git cherry ${PATCHCHECK_START} ${PATCHCHECK_END} +# +# Then the changes found will be tested. +# +# Note, PATCHCHECK_CHERRY requires PATCHCHECK_END to be defined. +# (default 0) +# +# PATCHCHECK_SKIP is an optional list of shas to skip testing +# +# PATCHCHECK_TYPE is required and is the type of test to run: +# build, boot, test. +# +# Note, the build test will look for warnings, if a warning occurred +# in a file that a commit touches, the build will fail, unless +# IGNORE_WARNINGS is set for the given commit's sha1 +# +# IGNORE_WARNINGS can be used to disable the failure of patchcheck +# on a particular commit (SHA1). You can add more than one commit +# by adding a list of SHA1s that are space delimited. +# +# If BUILD_NOCLEAN is set, then make mrproper will not be run on +# any of the builds, just like all other TEST_TYPE tests. But +# what makes patchcheck different from the other tests, is if +# BUILD_NOCLEAN is not set, only the first and last patch run +# make mrproper. This helps speed up the test. +# +# Example: +# TEST_START +# TEST_TYPE = patchcheck +# CHECKOUT = mybranch +# PATCHCHECK_TYPE = boot +# PATCHCHECK_START = 747e94ae3d1b4c9bf5380e569f614eb9040b79e7 +# PATCHCHECK_END = HEAD~2 +# IGNORE_WARNINGS = 42f9c6b69b54946ffc0515f57d01dc7f5c0e4712 0c17ca2c7187f431d8ffc79e81addc730f33d128 +# +# +# +# For TEST_TYPE = bisect +# +# You can specify a git bisect if the BUILD_DIR is a git repository. +# The MIN_CONFIG will be used for all builds of the bisect. The build type +# used for bisecting is oldconfig. +# +# The option BUILD_TYPE will be ignored. +# +# BISECT_TYPE is the type of test to perform: +# build - bad fails to build +# boot - bad builds but fails to boot +# test - bad boots but fails a test +# +# BISECT_GOOD is the commit (SHA1) to label as good (accepts all git good commit types) +# BISECT_BAD is the commit to label as bad (accepts all git bad commit types) +# +# The above three options are required for a bisect operation. +# +# BISECT_REPLAY = /path/to/replay/file (optional, default undefined) +# +# If an operation failed in the bisect that was not expected to +# fail. Then the test ends. The state of the BUILD_DIR will be +# left off at where the failure occurred. You can examine the +# reason for the failure, and perhaps even find a git commit +# that would work to continue with. You can run: +# +# git bisect log > /path/to/replay/file +# +# The adding: +# +# BISECT_REPLAY= /path/to/replay/file +# +# And running the test again. The test will perform the initial +# git bisect start, git bisect good, and git bisect bad, and +# then it will run git bisect replay on this file, before +# continuing with the bisect. +# +# BISECT_START = commit (optional, default undefined) +# +# As with BISECT_REPLAY, if the test failed on a commit that +# just happen to have a bad commit in the middle of the bisect, +# and you need to skip it. If BISECT_START is defined, it +# will checkout that commit after doing the initial git bisect start, +# git bisect good, git bisect bad, and running the git bisect replay +# if the BISECT_REPLAY is set. +# +# BISECT_SKIP = 1 (optional, default 0) +# +# If BISECT_TYPE is set to test but the build fails, ktest will +# simply fail the test and end their. You could use BISECT_REPLAY +# and BISECT_START to resume after you found a new starting point, +# or you could set BISECT_SKIP to 1. If BISECT_SKIP is set to 1, +# when something other than the BISECT_TYPE fails, ktest.pl will +# run "git bisect skip" and try again. +# +# BISECT_FILES = <path> (optional, default undefined) +# +# To just run the git bisect on a specific path, set BISECT_FILES. +# For example: +# +# BISECT_FILES = arch/x86 kernel/time +# +# Will run the bisect with "git bisect start -- arch/x86 kernel/time" +# +# BISECT_REVERSE = 1 (optional, default 0) +# +# In those strange instances where it was broken forever +# and you are trying to find where it started to work! +# Set BISECT_GOOD to the commit that was last known to fail +# Set BISECT_BAD to the commit that is known to start working. +# With BISECT_REVERSE = 1, The test will consider failures as +# good, and success as bad. +# +# BISECT_MANUAL = 1 (optional, default 0) +# +# In case there's a problem with automating the bisect for +# whatever reason. (Can't reboot, want to inspect each iteration) +# Doing a BISECT_MANUAL will have the test wait for you to +# tell it if the test passed or failed after each iteration. +# This is basically the same as running git bisect yourself +# but ktest will rebuild and install the kernel for you. +# +# BISECT_CHECK = 1 (optional, default 0) +# +# Just to be sure the good is good and bad is bad, setting +# BISECT_CHECK to 1 will start the bisect by first checking +# out BISECT_BAD and makes sure it fails, then it will check +# out BISECT_GOOD and makes sure it succeeds before starting +# the bisect (it works for BISECT_REVERSE too). +# +# You can limit the test to just check BISECT_GOOD or +# BISECT_BAD with BISECT_CHECK = good or +# BISECT_CHECK = bad, respectively. +# +# BISECT_TRIES = 5 (optional, default 1) +# +# For those cases that it takes several tries to hit a bug, +# the BISECT_TRIES is useful. It is the number of times the +# test is ran before it says the kernel is good. The first failure +# will stop trying and mark the current SHA1 as bad. +# +# Note, as with all race bugs, there's no guarantee that if +# it succeeds, it is really a good bisect. But it helps in case +# the bug is some what reliable. +# +# You can set BISECT_TRIES to zero, and all tests will be considered +# good, unless you also set BISECT_MANUAL. +# +# BISECT_RET_GOOD = 0 (optional, default undefined) +# +# In case the specificed test returns something other than just +# 0 for good, and non-zero for bad, you can override 0 being +# good by defining BISECT_RET_GOOD. +# +# BISECT_RET_BAD = 1 (optional, default undefined) +# +# In case the specificed test returns something other than just +# 0 for good, and non-zero for bad, you can override non-zero being +# bad by defining BISECT_RET_BAD. +# +# BISECT_RET_ABORT = 255 (optional, default undefined) +# +# If you need to abort the bisect if the test discovers something +# that was wrong, you can define BISECT_RET_ABORT to be the error +# code returned by the test in order to abort the bisect. +# +# BISECT_RET_SKIP = 2 (optional, default undefined) +# +# If the test detects that the current commit is neither good +# nor bad, but something else happened (another bug detected) +# you can specify BISECT_RET_SKIP to an error code that the +# test returns when it should skip the current commit. +# +# BISECT_RET_DEFAULT = good (optional, default undefined) +# +# You can override the default of what to do when the above +# options are not hit. This may be one of, "good", "bad", +# "abort" or "skip" (without the quotes). +# +# Note, if you do not define any of the previous BISECT_RET_* +# and define BISECT_RET_DEFAULT, all bisects results will do +# what the BISECT_RET_DEFAULT has. +# +# +# Example: +# TEST_START +# TEST_TYPE = bisect +# BISECT_GOOD = v2.6.36 +# BISECT_BAD = b5153163ed580e00c67bdfecb02b2e3843817b3e +# BISECT_TYPE = build +# MIN_CONFIG = /home/test/config-bisect +# +# +# +# For TEST_TYPE = config_bisect +# +# In those cases that you have two different configs. One of them +# work, the other does not, and you do not know what config causes +# the problem. +# The TEST_TYPE config_bisect will bisect the bad config looking for +# what config causes the failure. +# +# The way it works is this: +# +# You can specify a good config with CONFIG_BISECT_GOOD, otherwise it +# will use the MIN_CONFIG, and if that's not specified, it will use +# the config that comes with "make defconfig". +# +# It runs both the good and bad configs through a make oldconfig to +# make sure that they are set up for the kernel that is checked out. +# +# It then reads the configs that are set, as well as the ones that are +# not set for both the good and bad configs, and then compares them. +# It will set half of the good configs within the bad config (note, +# "set" means to make the bad config match the good config, a config +# in the good config that is off, will be turned off in the bad +# config. That is considered a "set"). +# +# It tests this new config and if it works, it becomes the new good +# config, otherwise it becomes the new bad config. It continues this +# process until there's only one config left and it will report that +# config. +# +# The "bad config" can also be a config that is needed to boot but was +# disabled because it depended on something that wasn't set. +# +# During this process, it saves the current good and bad configs in +# ${TMP_DIR}/good_config and ${TMP_DIR}/bad_config respectively. +# If you stop the test, you can copy them to a new location to +# reuse them again. +# +# Although the MIN_CONFIG may be the config it starts with, the +# MIN_CONFIG is ignored. +# +# The option BUILD_TYPE will be ignored. +# +# CONFIG_BISECT_TYPE is the type of test to perform: +# build - bad fails to build +# boot - bad builds but fails to boot +# test - bad boots but fails a test +# +# CONFIG_BISECT is the config that failed to boot +# +# If BISECT_MANUAL is set, it will pause between iterations. +# This is useful to use just ktest.pl just for the config bisect. +# If you set it to build, it will run the bisect and you can +# control what happens in between iterations. It will ask you if +# the test succeeded or not and continue the config bisect. +# +# CONFIG_BISECT_GOOD (optional) +# If you have a good config to start with, then you +# can specify it with CONFIG_BISECT_GOOD. Otherwise +# the MIN_CONFIG is the base, if MIN_CONFIG is not set +# It will build a config with "make defconfig" +# +# CONFIG_BISECT_CHECK (optional) +# Set this to 1 if you want to confirm that the config ktest +# generates (the bad config with the min config) is still bad. +# It may be that the min config fixes what broke the bad config +# and the test will not return a result. +# Set it to "good" to test only the good config and set it +# to "bad" to only test the bad config. +# +# CONFIG_BISECT_EXEC (optional) +# The config bisect is a separate program that comes with ktest.pl. +# By default, it will look for: +# `pwd`/config-bisect.pl # the location ktest.pl was executed from. +# If it does not find it there, it will look for: +# `dirname <ktest.pl>`/config-bisect.pl # The directory that holds ktest.pl +# If it does not find it there, it will look for: +# ${BUILD_DIR}/tools/testing/ktest/config-bisect.pl +# Setting CONFIG_BISECT_EXEC will override where it looks. +# +# Example: +# TEST_START +# TEST_TYPE = config_bisect +# CONFIG_BISECT_TYPE = build +# CONFIG_BISECT = /home/test/config-bad +# MIN_CONFIG = /home/test/config-min +# BISECT_MANUAL = 1 +# +# +# +# For TEST_TYPE = make_min_config +# +# After doing a make localyesconfig, your kernel configuration may +# not be the most useful minimum configuration. Having a true minimum +# config that you can use against other configs is very useful if +# someone else has a config that breaks on your code. By only forcing +# those configurations that are truly required to boot your machine +# will give you less of a chance that one of your set configurations +# will make the bug go away. This will give you a better chance to +# be able to reproduce the reported bug matching the broken config. +# +# Note, this does take some time, and may require you to run the +# test over night, or perhaps over the weekend. But it also allows +# you to interrupt it, and gives you the current minimum config +# that was found till that time. +# +# Note, this test automatically assumes a BUILD_TYPE of oldconfig +# and its test type acts like boot. +# TODO: add a test version that makes the config do more than just +# boot, like having network access. +# +# To save time, the test does not just grab any option and test +# it. The Kconfig files are examined to determine the dependencies +# of the configs. If a config is chosen that depends on another +# config, that config will be checked first. By checking the +# parents first, we can eliminate whole groups of configs that +# may have been enabled. +# +# For example, if a USB device config is chosen and depends on CONFIG_USB, +# the CONFIG_USB will be tested before the device. If CONFIG_USB is +# found not to be needed, it, as well as all configs that depend on +# it, will be disabled and removed from the current min_config. +# +# OUTPUT_MIN_CONFIG is the path and filename of the file that will +# be created from the MIN_CONFIG. If you interrupt the test, set +# this file as your new min config, and use it to continue the test. +# This file does not need to exist on start of test. +# This file is not created until a config is found that can be removed. +# If this file exists, you will be prompted if you want to use it +# as the min_config (overriding MIN_CONFIG) if START_MIN_CONFIG +# is not defined. +# (required field) +# +# START_MIN_CONFIG is the config to use to start the test with. +# you can set this as the same OUTPUT_MIN_CONFIG, but if you do +# the OUTPUT_MIN_CONFIG file must exist. +# (default MIN_CONFIG) +# +# IGNORE_CONFIG is used to specify a config file that has configs that +# you already know must be set. Configs are written here that have +# been tested and proved to be required. It is best to define this +# file if you intend on interrupting the test and running it where +# it left off. New configs that it finds will be written to this file +# and will not be tested again in later runs. +# (optional) +# +# MIN_CONFIG_TYPE can be either 'boot' or 'test'. With 'boot' it will +# test if the created config can just boot the machine. If this is +# set to 'test', then the TEST option must be defined and the created +# config will not only boot the target, but also make sure that the +# config lets the test succeed. This is useful to make sure the final +# config that is generated allows network activity (ssh). +# (optional) +# +# USE_OUTPUT_MIN_CONFIG set this to 1 if you do not want to be prompted +# about using the OUTPUT_MIN_CONFIG as the MIN_CONFIG as the starting +# point. Set it to 0 if you want to always just use the given MIN_CONFIG. +# If it is not defined, it will prompt you to pick which config +# to start with (MIN_CONFIG or OUTPUT_MIN_CONFIG). +# +# Example: +# +# TEST_TYPE = make_min_config +# OUTPUT_MIN_CONFIG = /path/to/config-new-min +# START_MIN_CONFIG = /path/to/config-min +# IGNORE_CONFIG = /path/to/config-tested +# MIN_CONFIG_TYPE = test +# TEST = ssh ${USER}@${MACHINE} echo hi +# +# +# +# +# For TEST_TYPE = make_warnings_file +# +# If you want the build to fail when a new warning is discovered +# you set the WARNINGS_FILE to point to a file of known warnings. +# +# The test "make_warnings_file" will let you create a new warnings +# file before you run other tests, like patchcheck. +# +# What this test does is to run just a build, you still need to +# specify BUILD_TYPE to tell the test what type of config to use. +# A BUILD_TYPE of nobuild will fail this test. +# +# The test will do the build and scan for all warnings. Any warning +# it discovers will be saved in the WARNINGS_FILE (required) option. +# +# It is recommended (but not necessary) to make sure BUILD_NOCLEAN is +# off, so that a full build is done (make mrproper is performed). +# That way, all warnings will be captured. +# +# Example: +# +# TEST_TYPE = make_warnings_file +# WARNINGS_FILE = ${OUTPUT_DIR} +# BUILD_TYPE = useconfig:oldconfig +# CHECKOUT = v3.8 +# BUILD_NOCLEAN = 0 +# |
