diff --git a/tools/testing/ktest/ktest.pl b/tools/testing/ktest/ktest.pl index 5323c6f9bf9b..a9f2e10fc16f 100755 --- a/tools/testing/ktest/ktest.pl +++ b/tools/testing/ktest/ktest.pl @@ -2162,6 +2162,159 @@ sub patchcheck { return 1; } +my %depends; +my $iflevel = 0; +my @ifdeps; + +# prevent recursion +my %read_kconfigs; + +# 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 die "Can't open $kconfig"; + while () { + 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++) { + if ($i) { + $depends{$config} .= " " . $ifdeps[$i]; + } else { + $depends{$config} = $ifdeps[$i]; + } + $state = "DEP"; + } + + # collect the depends for the config + } elsif ($state eq "NEW" && /^\s*depends\s+on\s+(.*)$/) { + + if (defined($depends{$1})) { + $depends{$config} .= " " . $1; + } else { + $depends{$config} = $1; + } + + # Get the configs that select this config + } elsif ($state ne "NONE" && /^\s*select\s+(\S+)/) { + if (defined($depends{$1})) { + $depends{$1} .= " " . $config; + } else { + $depends{$1} = $config; + } + + # 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 () { + 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"; + } elsif ($arch =~ /^tile/) { + $arch = "tile"; + } + + my $kconfig = "$builddir/arch/$arch/Kconfig"; + + if (! -f $kconfig && $arch =~ /\d$/) { + my $orig = $arch; + # some subarchs have numbers, truncate them + $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 read_config_list { my ($config) = @_; @@ -2197,6 +2350,95 @@ sub make_new_config { close OUT; } +sub get_depends { + my ($dep) = @_; + + my $kconfig = $dep; + $kconfig =~ s/CONFIG_//; + + $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 { + die "this should never happen"; + } + } + + return @configs; +} + +my %min_configs; +my %keep_configs; +my %processed_configs; +my %nochange_config; + +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 = $config; + $kconfig =~ s/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 oldnoconfig and then read the resulting + # .config to make sure it is missing the config that + # we had before + my %configs = %min_configs; + delete $configs{$config}; + make_new_config ((values %configs), (values %keep_configs)); + make_oldconfig; + undef %configs; + assign_configs \%configs, $output_config; + + return $config if (!defined($configs{$config})); + + doprint "disabling config $config did not change .config\n"; + + $nochange_config{$config} = 1; + + return undef; +} + sub make_min_config { my ($i) = @_; @@ -2216,8 +2458,12 @@ sub make_min_config { run_command "$make allnoconfig" or return 0; + read_depends; + process_config_ignore $output_config; - my %keep_configs; + + undef %keep_configs; + undef %min_configs; if (defined($ignore_config)) { # make sure the file exists @@ -2229,7 +2475,6 @@ sub make_min_config { # Look at the current min configs, and save off all the # ones that were set via the allnoconfig - my %min_configs; assign_configs \%min_configs, $start_minconfig; my @config_keys = keys %min_configs; @@ -2261,9 +2506,8 @@ sub make_min_config { } } - my %nochange_config; - my $done = 0; + my $take_two = 0; while (!$done) { @@ -2293,35 +2537,30 @@ sub make_min_config { undef %nochange_config; } + undef %processed_configs; + foreach my $config (@test_configs) { - # Remove this config from the list of configs - # do a make oldnoconfig and then read the resulting - # .config to make sure it is missing the config that - # we had before - my %configs = %min_configs; - delete $configs{$config}; - make_new_config ((values %configs), (values %keep_configs)); - make_oldconfig; - undef %configs; - assign_configs \%configs, $output_config; + $found = test_this_config $config; - if (!defined($configs{$config})) { - $found = $config; - last; - } - - doprint "disabling config $config did not change .config\n"; + last if (defined($found)); # oh well, try another config - $nochange_config{$config} = 1; } 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; @@ -2338,7 +2577,7 @@ sub make_min_config { $in_bisect = 0; if ($failed) { - doprint "$config is needed to boot the box... keeping\n"; + 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}; delete $min_configs{$config}; diff --git a/tools/testing/ktest/sample.conf b/tools/testing/ktest/sample.conf index a83846d829bd..d096a0c80401 100644 --- a/tools/testing/ktest/sample.conf +++ b/tools/testing/ktest/sample.conf @@ -837,6 +837,18 @@ # 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.