kernel quest reads bugzilla's REST API and picks one bug through a seeded xorshift, so a pick is reproducible in a test and varied in a person's hands. kernel test builds under spawn_blocking and boots under qemu with the serial console streamed line by line, and it reports what qemu did instead of flattening a failed boot into a success. patch create writes a whole series when --range is given. git format-patch does the naming and the numbering because that is already its job, the cover letter comes from --cover-letter, and a series.toml beside the patches records the range with both ends as hashes, the revision, the files and the blurb. patch submit takes a directory or several files and sends them in one git send-email invocation, and patch update re-rolls a directory through --reroll-count, deleting the superseded revision only once the new one is complete. doctor runs its checks in the order a first run hits them, prints one line and a fix hint for each, and names the first hard failure in the summary. A missing qemu or a missing mail route warns and keeps exit 0, because --dry-run is where the loop actually stops on a machine without an SMTP route. ~/.config/spectral/config.toml holds the tree path, the patch directory, always-CC addresses and an identity, with the environment winning over the file and the file over the built-in default. spectral init verifies or clones a tree and writes it once: a second run with the same tree says so and writes nothing, and a run that would change an existing answer is refused until --force. tests/ grew a fixture for each of those. The harness controls PATH and git's configuration, and its PATH can hold a git that answers send-email either way, so a host without that support still passes rather than deciding the result. Two review lanes ran over this work and this commit carries their fixes. A re-roll regenerated base..HEAD, so a commit that landed after create would have silently joined the set and been mailed: the range is recorded now and a re-roll regenerates exactly it. A patch file that was already gone made update fail forever while the sidecar described a revision that was not on disk: a missing file is the state the re-roll wanted. An empty blurb aborted after the files were written: it is checked before anything is generated. A tree that is not a git repository was reported as a missing revision. init cloned before it refused. The bugzilla request had no timeout, a signal-ended child printed the bare word "signal", CommandSpawn dropped the io reason, and the printed send-email line quoted only arguments with spaces, so --in-reply-to <id@host> pasted as a redirection. Checked with cargo fmt --all -- --check, cargo clippy --all-targets --locked -- -D warnings, cargo test --locked (96 passed, 4 ignored) and cargo doc --no-deps. The four ignored tests want the host's own checkpatch, get_maintainer and bugzilla, and all four pass when asked for.
136 lines
4.4 KiB
Rust
136 lines
4.4 KiB
Rust
//! `kernel test`, driven through the built binary against a fixture tree.
|
|
//!
|
|
//! Nothing here boots a kernel: qemu is taken off `PATH` deliberately, so the
|
|
//! assertions are about the paths around the boot rather than about a machine
|
|
//! that can run one. The real boot is the plan's P6 manual step.
|
|
|
|
mod common;
|
|
|
|
use common::Fixture;
|
|
|
|
/// A fixture tree with something at the path qemu would be handed.
|
|
fn fixture_with_image() -> Fixture {
|
|
let fixture = Fixture::kernel_tree();
|
|
fixture.write("arch/x86/boot/bzImage", "not really a kernel\n");
|
|
fixture.commit_all("fixture: add an image");
|
|
|
|
fixture
|
|
}
|
|
|
|
#[test]
|
|
fn a_missing_qemu_is_named_rather_than_panicked_on() {
|
|
let fixture = fixture_with_image();
|
|
// A `PATH` with nothing on it: the binary itself is found by its full
|
|
// path, and qemu cannot be.
|
|
let bare = fixture.restricted_path(&[]);
|
|
|
|
let output = fixture.cli_env(
|
|
&["kernel", "test", "--no-build"],
|
|
&[("PATH", bare.to_str().expect("the restricted path"))],
|
|
);
|
|
|
|
assert_eq!(output.status.code(), Some(1));
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
assert!(
|
|
stderr.contains("could not run `qemu-system-x86_64`"),
|
|
"{stderr}"
|
|
);
|
|
assert!(
|
|
!String::from_utf8_lossy(&output.stdout).contains("exited cleanly"),
|
|
"a boot that never started was reported as a success"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn a_tree_with_no_image_is_named_rather_than_a_build_being_attempted() {
|
|
let fixture = Fixture::kernel_tree();
|
|
|
|
let output = fixture.cli(&["kernel", "test", "--no-build"]);
|
|
|
|
assert_eq!(output.status.code(), Some(1));
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
assert!(stderr.contains("no bootable image at"), "{stderr}");
|
|
assert!(stderr.contains("arch/x86/boot/bzImage"), "{stderr}");
|
|
}
|
|
|
|
#[test]
|
|
fn build_runs_make_in_the_tree_and_finds_the_image_it_wrote() {
|
|
// A Makefile that writes the image where a real build leaves it, and
|
|
// records that it ran. `sh` is the only tool it needs, so the restricted
|
|
// `PATH` can hold make and sh and still leave qemu out.
|
|
let fixture = Fixture::kernel_tree();
|
|
fixture.write("arch/x86/boot/.keep", "");
|
|
fixture.write(
|
|
"Makefile",
|
|
"all:\n\tprintf '' > arch/x86/boot/bzImage\n\tprintf 'built\\n' > .make-call\n",
|
|
);
|
|
fixture.commit_all("fixture: add a Makefile");
|
|
let bin = fixture.restricted_path(&["make", "sh"]);
|
|
|
|
let output = fixture.cli_env(
|
|
&["kernel", "test"],
|
|
&[("PATH", bin.to_str().expect("the restricted path"))],
|
|
);
|
|
|
|
assert_eq!(
|
|
fixture.read(".make-call"),
|
|
"built\n",
|
|
"make did not run in the tree"
|
|
);
|
|
// The image check passed, which is why the failure is qemu's rather than
|
|
// the image's.
|
|
assert_eq!(output.status.code(), Some(1));
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
assert!(
|
|
stderr.contains("could not run `qemu-system-x86_64`"),
|
|
"the build did not get as far as qemu: {stderr}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn qemu_runs_with_the_arguments_the_plan_describes() {
|
|
// What qemu was handed, captured without needing a real qemu: the stub is
|
|
// reached through `PATH`, so the binary under test cannot tell.
|
|
let fixture = fixture_with_image();
|
|
fixture.write_script(
|
|
"bin/qemu-system-x86_64",
|
|
"#!/bin/sh\necho \"argv: $*\"\nexit 1\n",
|
|
);
|
|
let bin = fixture.join("bin");
|
|
|
|
let output = fixture.cli_env(
|
|
&[
|
|
"kernel",
|
|
"test",
|
|
"--no-build",
|
|
"--qemu-arg",
|
|
"-m",
|
|
"--qemu-arg",
|
|
"1G",
|
|
],
|
|
&[("PATH", bin.to_str().expect("the stub bin directory"))],
|
|
);
|
|
|
|
assert_eq!(
|
|
output.status.code(),
|
|
Some(1),
|
|
"a failing qemu must not look clean: {}",
|
|
String::from_utf8_lossy(&output.stdout)
|
|
);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
assert!(
|
|
stdout.contains("-kernel ") && stdout.contains("arch/x86/boot/bzImage"),
|
|
"{stdout}"
|
|
);
|
|
assert!(
|
|
stdout.contains("-append console=ttyS0"),
|
|
"the console is what makes a boot readable: {stdout}"
|
|
);
|
|
assert!(stdout.contains("-nographic"), "{stdout}");
|
|
assert!(stdout.contains("-m 1G"), "the pass-through args: {stdout}");
|
|
assert!(
|
|
stdout.contains("qemu exited with status 1"),
|
|
"the exit status was flattened: {stdout}"
|
|
);
|
|
}
|