From 7d5cea48cccecfffea7c4fda85f8e3702694645f Mon Sep 17 00:00:00 2001 From: tamnd <1218621+tamnd@users.noreply.github.com> Date: Sat, 3 Oct 2026 08:57:29 +0700 Subject: [PATCH] Skip the drm_sched and ratelimit KUnit suites in M14 --- configs/fragment.M14 | 3 ++ crates/gk/src/boot.rs | 44 ++++++++++++++++++---- crates/gk/src/cell.rs | 68 +++++++++++++++++++++++----------- docs/spec/07-build-and-boot.md | 2 +- 4 files changed, 86 insertions(+), 31 deletions(-) diff --git a/configs/fragment.M14 b/configs/fragment.M14 index 33b161d..a4a702c 100644 --- a/configs/fragment.M14 +++ b/configs/fragment.M14 @@ -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 diff --git a/crates/gk/src/boot.rs b/crates/gk/src/boot.rs index 605ca40..d845fa1 100644 --- a/crates/gk/src/boot.rs +++ b/crates/gk/src/boot.rs @@ -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. @@ -231,12 +233,20 @@ 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 = 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}" @@ -244,7 +254,12 @@ pub fn append(console: &str, suite: &str, version: &Version) -> String { } /// 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, String> { +pub fn qemu_command( + p: &Platform, + version: &Version, + suite: &str, + skip: &[String], +) -> Result, 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"))?; @@ -266,7 +281,7 @@ pub fn qemu_command(p: &Platform, version: &Version, suite: &str) -> Result) -> Result { 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()))?; @@ -457,6 +472,7 @@ pub fn command(repo: &Repo, args: &[String]) -> Result { suite: &suite, dir: &dir, stem: "boot", + skip: &[], }, )?; let passed = o.checks.iter().filter(|c| c.pass).count(); @@ -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" ); } diff --git a/crates/gk/src/cell.rs b/crates/gk/src/cell.rs index c0266b7..144f8e8 100644 --- a/crates/gk/src/cell.rs +++ b/crates/gk/src/cell.rs @@ -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, } impl Setup { @@ -216,6 +218,7 @@ impl Setup { image_name, coordinates, boots: false, + kunit_skip: kunit_skip(&String::from_utf8_lossy(&fragment_text)), }) } @@ -561,6 +564,30 @@ struct Booted { steps: Vec, } +/// 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 { + 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, @@ -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(), @@ -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() { @@ -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 { + 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| { @@ -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())); diff --git a/docs/spec/07-build-and-boot.md b/docs/spec/07-build-and-boot.md index cba1e1f..91439a0 100644 --- a/docs/spec/07-build-and-boot.md +++ b/docs/spec/07-build-and-boot.md @@ -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.` 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 ...` 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.` 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.