Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions configs/fragment.M14
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ CONFIG_KUNIT_ALL_TESTS=y

# The kunit_fault suite oopses on purpose to check that KUnit catches it, which PANIC_ON_OOPS turns into a dead machine. 7.2.8 skips it on its own, but 6.18 does not.
CONFIG_KUNIT_FAULT_TEST=n

# Suites that fail under TCG on a loaded host whatever the compiler: the DRM scheduler tests race their own hrtimers, and the ratelimit stress test counts on timing. KUNIT_ALL_TESTS hides their options, so the KUnit boot skips them by module instead.
# gk:kunit-skip drm_sched_tests test_ratelimit
44 changes: 36 additions & 8 deletions crates/gk/src/boot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ pub struct Boot<'a> {
pub dir: &'a Path,
/// Their file name without the extension, as `boot` for `boot.log` and `boot.json`.
pub stem: &'a str,
/// KUnit modules whose suites are skipped, from the fragment.
pub skip: &'a [String],
}

/// One `GK-CHECK` line.
Expand Down Expand Up @@ -231,20 +233,33 @@ pub fn strip_timestamp(line: &str) -> &str {
/// The kernel command line for a boot.
///
/// The fragments build every KUnit suite in, and they run before init, which under TCG takes longer than the whole boot budget. So every suite but `kunit` turns them off: `kunit.enable=0` from 6.2, and a filter that matches no suite from 5.10 to 6.1. A kernel ignores the one it does not know.
///
/// On the `kunit` boot, the suites of the modules in `skip` are reported as skipped, through the attribute filter KUnit has from 6.6.
#[must_use]
pub fn append(console: &str, suite: &str, version: &Version) -> String {
let quiet = if suite == "kunit" {
""
pub fn append(console: &str, suite: &str, version: &Version, skip: &[String]) -> String {
let quiet = if suite != "kunit" {
" kunit.enable=0 kunit.filter_glob=gk-none".to_owned()
} else if skip.is_empty() {
String::new()
} else {
" kunit.enable=0 kunit.filter_glob=gk-none"
let filter: Vec<String> = skip.iter().map(|m| format!("module!={m}")).collect();
format!(
" kunit.filter={} kunit.filter_action=skip",
filter.join(",")
)
};
format!(
"console={console} panic=-1 oops=panic gk.suite={suite} gk.kernel={version} gk.cpus={CPUS}{quiet}"
)
}

/// The QEMU command, with the kernel and the initramfs at the paths the container mounts them on.
pub fn qemu_command(p: &Platform, version: &Version, suite: &str) -> Result<Vec<String>, String> {
pub fn qemu_command(
p: &Platform,
version: &Version,
suite: &str,
skip: &[String],
) -> Result<Vec<String>, String> {
let missing = |what: &str| format!("platforms.toml has no {what} for {version} on {}", p.name);
let machine = p.machine_for(version).ok_or_else(|| missing("machine"))?;
let cpu = p.cpu_for(version).ok_or_else(|| missing("cpu"))?;
Expand All @@ -266,7 +281,7 @@ pub fn qemu_command(p: &Platform, version: &Version, suite: &str) -> Result<Vec<
"-initrd",
"/boot/initramfs.cpio",
"-append",
&append(console, suite, version),
&append(console, suite, version, skip),
"-nographic",
"-monitor",
"none",
Expand All @@ -290,7 +305,7 @@ pub fn run(repo: &Repo, b: &Boot<'_>) -> Result<Outcome, String> {
let (init, archive) = initramfs::for_platform(repo, b.platform)?;
let image = forge::image_for(repo, "gk-boot")?;
let qemu = crate::cell::image_digest(&image)?;
let command = qemu_command(b.platform, b.version, b.suite)?;
let command = qemu_command(b.platform, b.version, b.suite, b.skip)?;
let kernel = std::fs::canonicalize(b.image)
.map_err(|e| format!("reading {}: {e}", b.image.display()))?;
std::fs::create_dir_all(b.dir).map_err(|e| format!("creating {}: {e}", b.dir.display()))?;
Expand Down Expand Up @@ -457,6 +472,7 @@ pub fn command(repo: &Repo, args: &[String]) -> Result<bool, String> {
suite: &suite,
dir: &dir,
stem: "boot",
skip: &[],
},
)?;
let passed = o.checks.iter().filter(|c| c.pass).count();
Expand Down Expand Up @@ -561,11 +577,23 @@ mod tests {
assert!(o.panic.is_none());
}

#[test]
fn the_kunit_boot_skips_the_fragment_modules() {
let v: Version = "7.2.8".parse().unwrap();
let skip = ["drm_sched_tests".to_owned(), "test_ratelimit".to_owned()];
let line = append("ttyS0", "kunit", &v, &skip);
assert!(line.ends_with(
" kunit.filter=module!=drm_sched_tests,module!=test_ratelimit kunit.filter_action=skip"
));
assert!(!append("ttyS0", "kunit", &v, &[]).contains("kunit."));
assert!(!append("ttyS0", "smoke", &v, &skip).contains("filter="));
}

#[test]
fn the_command_line_names_the_suite_and_the_kernel() {
let v: Version = "7.2.8".parse().unwrap();
assert_eq!(
append("ttyS0", "smoke", &v),
append("ttyS0", "smoke", &v, &[]),
"console=ttyS0 panic=-1 oops=panic gk.suite=smoke gk.kernel=7.2.8 gk.cpus=2 kunit.enable=0 kunit.filter_glob=gk-none"
);
}
Expand Down
68 changes: 46 additions & 22 deletions crates/gk/src/cell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ pub struct Setup {
pub coordinates: Coordinates,
/// Whether the cell boots after L4.
pub boots: bool,
/// The KUnit modules the fragment skips, from its `# gk:kunit-skip` lines.
pub kunit_skip: Vec<String>,
}

impl Setup {
Expand Down Expand Up @@ -216,6 +218,7 @@ impl Setup {
image_name,
coordinates,
boots: false,
kunit_skip: kunit_skip(&String::from_utf8_lossy(&fragment_text)),
})
}

Expand Down Expand Up @@ -561,6 +564,30 @@ struct Booted {
steps: Vec<Step>,
}

/// One boot of a cell's image. Only the KUnit boot skips the modules the fragment names.
fn boot_once(
repo: &Repo,
s: &Setup,
image: &Path,
dir: &Path,
suite: &str,
stem: &str,
) -> Result<boot::Outcome, String> {
let skip: &[String] = if suite == "kunit" { &s.kunit_skip } else { &[] };
boot::run(
repo,
&boot::Boot {
platform: &s.platform,
version: &s.version,
image,
suite,
dir,
stem,
skip,
},
)
}

/// Boot a linked cell, three times once it has booted at all, into `boot-1.log` and on. A kernel with KUnit built in boots a second time in each run that passed smoke, into `kunit-1.log` and on, and its suites are graded against the era GCC's cell for L7.
fn boots(
repo: &Repo,
Expand All @@ -578,17 +605,7 @@ fn boots(
let mut kunit_seconds = 0.0;
for n in 1..=BOOTS {
let stem = format!("boot-{n}");
let o = boot::run(
repo,
&boot::Boot {
platform: &s.platform,
version: &s.version,
image,
suite: "smoke",
dir: cell_dir,
stem: &stem,
},
)?;
let o = boot_once(repo, s, image, cell_dir, "smoke", &stem)?;
steps.push(Step {
rung: Rung::Booted.to_string(),
passed: o.booted(),
Expand All @@ -612,17 +629,7 @@ fn boots(
}
if kunit && rung == Rung::Smoke {
let stem = format!("kunit-{n}");
let k = boot::run(
repo,
&boot::Boot {
platform: &s.platform,
version: &s.version,
image,
suite: "kunit",
dir: cell_dir,
stem: &stem,
},
)?;
let k = boot_once(repo, s, image, cell_dir, "kunit", &stem)?;
kunit_seconds += k.seconds;
kunit_splats.push(k.splats.iter().map(|l| boot::splat_key(l)).collect());
suites.push(if k.ended() && k.panic.is_none() {
Expand Down Expand Up @@ -1166,6 +1173,16 @@ fn hostname() -> String {
.unwrap_or_default()
}

/// The modules named on the `# gk:kunit-skip` lines of a fragment. Kconfig reads those lines as comments, and the KUnit boot skips the suites of these modules. A suite goes there when it fails under TCG on a loaded host for reasons that have nothing to do with the compiler, and when `KUNIT_ALL_TESTS` hides its option so the fragment cannot turn it off. Being in the fragment puts the list in the cell's identity.
fn kunit_skip(fragment: &str) -> Vec<String> {
fragment
.lines()
.filter_map(|l| l.strip_prefix("# gk:kunit-skip "))
.flat_map(str::split_whitespace)
.map(str::to_owned)
.collect()
}

/// Whether make reports a recipe killed by SIGKILL, which on these hosts is the OOM killer. That says how much memory the machine had left, not what the GCC did, so it must not become a verdict.
fn killed(log: &Path) -> bool {
std::fs::read_to_string(log).is_ok_and(|text| {
Expand All @@ -1178,6 +1195,13 @@ fn killed(log: &Path) -> bool {
mod tests {
use super::*;

#[test]
fn the_fragment_names_the_skipped_modules() {
let text = "CONFIG_KUNIT=y\n# gk:kunit-skip drm_sched_tests test_ratelimit\n# a comment\n";
assert_eq!(kunit_skip(text), ["drm_sched_tests", "test_ratelimit"]);
assert!(kunit_skip("CONFIG_KUNIT=y\n").is_empty());
}

#[test]
fn a_sigkill_is_not_a_verdict() {
let dir = std::env::temp_dir().join(format!("gk-killed-{}", std::process::id()));
Expand Down
2 changes: 1 addition & 1 deletion docs/spec/07-build-and-boot.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ The fragment turns two things off, `CONFIG_WERROR` and `CONFIG_GCC_PLUGINS`. Tha

`CONFIG_DEBUG_INFO_BTF=n` keeps `pahole` out of the build. BTF generation depends on the pahole version, a host tool with its own history of breaking kernels, and that is not the question here.

Era fragments drop what the era does not have: KUnit before 5.5, devtmpfs before 2.6.32, `PANIC_ON_OOPS` before 2.6.x, the PL011 console for trees without it. The M13 and M14 fragments also turn off `KUNIT_FAULT_TEST`, whose suite oopses on purpose and so panics a kernel built with `PANIC_ON_OOPS`. The first M13 cell, 6.12.111 with gcc-12.2.0, died that way in its KUnit boots, and so did the M14 era cell for 6.18.54. 7.2.8 skips the suite by itself. `configs/fragment.<era>` holds each, and `gk` records which requested options did not take effect after the configuration step. An option that did not take effect with the era GCC is expected. One that took effect with the era GCC and not with another column is a finding: it means a Kconfig compiler probe disagreed, which document 11.3 wants.
Era fragments drop what the era does not have: KUnit before 5.5, devtmpfs before 2.6.32, `PANIC_ON_OOPS` before 2.6.x, the PL011 console for trees without it. The M13 and M14 fragments also turn off `KUNIT_FAULT_TEST`, whose suite oopses on purpose and so panics a kernel built with `PANIC_ON_OOPS`. The first M13 cell, 6.12.111 with gcc-12.2.0, died that way in its KUnit boots, and so did the M14 era cell for 6.18.54. 7.2.8 skips the suite by itself. Two other suites, the DRM scheduler tests and the ratelimit stress test, fail under TCG on a loaded host with any compiler, and `KUNIT_ALL_TESTS` hides their options so a fragment cannot turn them off. A fragment line `# gk:kunit-skip <module>...` names such modules instead, and the KUnit boot passes them to `kunit.filter` with `kunit.filter_action=skip`, so they show up as skipped rather than vanish. M14 skips `drm_sched_tests` and `test_ratelimit`. `configs/fragment.<era>` holds each, and `gk` records which requested options did not take effect after the configuration step. An option that did not take effect with the era GCC is expected. One that took effect with the era GCC and not with another column is a finding: it means a Kconfig compiler probe disagreed, which document 11.3 wants.

Two kinds of configuration differences between columns are expected and not failures: `CC_VERSION_TEXT`, `GCC_VERSION`, `AS_VERSION`, `LD_VERSION` and the `CC_HAS_*` family that probes the compiler. They are what the configuration differential measures.

Expand Down
Loading