Every patch verb outside the series work is real. check reads the working tree through checkpatch's stdin, which is the only way it can read one, since --git with no revision dies; format puts --fix-inplace's output back into the tree after setting the uncommitted change aside, and restores it if the fix does not apply; commit and create sit on git; create writes a mail-formatted patch, because git send-email refuses a bare diff with "No subject line"; submit splits get_maintainer.pl's roles into To and Cc, prints the command line it is about to run, and only sends when --dry-run is off; update re-rolls a patch by renaming it and does nothing at the revision it is already at. git.rs grows the plumbing those verbs stand on: diff_unstaged, format_patch, apply, commit, rev_parse. apply feeds the patch in on stdin, so a fixed patch stays in memory until it is known to apply. tests/ holds a fixture kernel tree, so the suite needs no kernel tree, no network, and no configured git send-email. The two tests that do want the host's own checkpatch and get_maintainer are ignored unless asked for, and one of them earned its place immediately: checkpatch's --file means "this argument is source code", so checking a patch with it reported a dirty patch as clean, 8004 lines and no errors. Patchfile mode is checkpatch's default and the path now stands alone. patch check also takes --rev, which is how a committed range gets checked. Checked with cargo fmt --check, cargo clippy --all-targets -- -D warnings, cargo test, and cargo doc --no-deps.
125 lines
4.8 KiB
Rust
125 lines
4.8 KiB
Rust
//! The real tools, when the machine has them.
|
|
//!
|
|
//! Everything else in `tests/` drives stubs, because checkpatch's output moves
|
|
//! with the kernel version and a test cannot assert on that. These tests are the
|
|
//! other half: they run the host's own scripts so a kernel-version change to a
|
|
//! summary line, an argument, or an exit code shows up here rather than in
|
|
//! someone's patch workflow.
|
|
//!
|
|
//! They are ignored by default and need both variables:
|
|
//!
|
|
//! ```console
|
|
//! $ SPECTRAL_REAL_CHECKPATCH=1 SPECTRAL_REAL_TREE=/usr/src/linux \
|
|
//! cargo test --locked -- --ignored real_
|
|
//! ```
|
|
|
|
mod common;
|
|
|
|
use common::Fixture;
|
|
|
|
/// A change checkpatch has opinions about, whatever kernel it comes from.
|
|
const BAD: &str =
|
|
"static int foo(int a) {\n\tint b = a + 1;\n\tif (a>0)\n\t\tb += 2;\n\treturn b;\n}\n";
|
|
|
|
#[test]
|
|
#[ignore = "needs SPECTRAL_REAL_CHECKPATCH=1 and SPECTRAL_REAL_TREE=/path/to/linux"]
|
|
fn real_checkpatch_reads_all_three_kinds_of_target() {
|
|
let fixture = real_fixture();
|
|
fixture.write("drivers/foo/bar.c", BAD);
|
|
|
|
// The working tree goes in on stdin, because `--git` with no revision
|
|
// dies with "no git commits after extraction".
|
|
let working_tree = fixture.cli(&["patch", "check"]);
|
|
let stdout = String::from_utf8_lossy(&working_tree.stdout).into_owned();
|
|
assert_eq!(working_tree.status.code(), Some(1), "{stdout}");
|
|
assert!(stdout.contains("total:"), "{stdout}");
|
|
assert!(stdout.contains("ERROR"), "{stdout}");
|
|
|
|
// A revision goes through --git, and checkpatch names the commit it read.
|
|
let by_rev = fixture.cli(&["patch", "check", "--rev", "HEAD"]);
|
|
let stdout = String::from_utf8_lossy(&by_rev.stdout).into_owned();
|
|
assert!(stdout.contains("fixture: add bar"), "{stdout}");
|
|
|
|
// A patch file goes through --file.
|
|
let patch = fixture.home().join("bad.patch");
|
|
std::fs::write(&patch, format!("{}\n", fixture.git(&["diff"]))).expect("write the patch");
|
|
let by_file = fixture.cli(&["patch", "check", patch.to_str().expect("the patch path")]);
|
|
let stdout = String::from_utf8_lossy(&by_file.stdout).into_owned();
|
|
assert_eq!(by_file.status.code(), Some(1), "{stdout}");
|
|
assert!(stdout.contains("ERROR"), "{stdout}");
|
|
}
|
|
|
|
/// A fixture whose checkpatch is the host's, copied in.
|
|
fn real_fixture() -> Fixture {
|
|
assert_eq!(
|
|
std::env::var("SPECTRAL_REAL_CHECKPATCH").as_deref(),
|
|
Ok("1"),
|
|
"run this with SPECTRAL_REAL_CHECKPATCH=1"
|
|
);
|
|
let tree = std::env::var("SPECTRAL_REAL_TREE")
|
|
.expect("set SPECTRAL_REAL_TREE to a kernel tree holding scripts/checkpatch.pl");
|
|
|
|
let fixture = Fixture::kernel_tree();
|
|
let script = std::path::Path::new(&tree).join("scripts/checkpatch.pl");
|
|
let body = std::fs::read_to_string(&script)
|
|
.unwrap_or_else(|error| panic!("read {}: {error}", script.display()));
|
|
fixture.write_script("scripts/checkpatch.pl", &body);
|
|
|
|
fixture
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "needs SPECTRAL_REAL_GETMAINTAINER=1 and SPECTRAL_REAL_TREE=/path/to/linux"]
|
|
fn real_get_maintainer_fills_the_to_and_cc_lines() {
|
|
assert_eq!(
|
|
std::env::var("SPECTRAL_REAL_GETMAINTAINER").as_deref(),
|
|
Ok("1"),
|
|
"run this with SPECTRAL_REAL_GETMAINTAINER=1"
|
|
);
|
|
let tree = std::env::var("SPECTRAL_REAL_TREE")
|
|
.expect("set SPECTRAL_REAL_TREE to a kernel tree holding scripts/get_maintainer.pl");
|
|
|
|
let fixture = Fixture::kernel_tree();
|
|
let script = std::path::Path::new(&tree).join("scripts/get_maintainer.pl");
|
|
let body = std::fs::read_to_string(&script)
|
|
.unwrap_or_else(|error| panic!("read {}: {error}", script.display()));
|
|
fixture.write_script("scripts/get_maintainer.pl", &body);
|
|
fixture.commit_all("fixture: use the real get_maintainer");
|
|
|
|
fixture.work_on_a_branch();
|
|
fixture.write("drivers/foo/bar.c", common::EDITED);
|
|
fixture.commit_all("foo: return a + 1");
|
|
let created = fixture.cli(&["patch", "create", "000-foo"]);
|
|
assert!(
|
|
created.status.success(),
|
|
"{}",
|
|
String::from_utf8_lossy(&created.stderr)
|
|
);
|
|
let patch = fixture.home().join(".spectral/patches/000-foo.patch");
|
|
|
|
let output = fixture.cli(&[
|
|
"patch",
|
|
"submit",
|
|
patch.to_str().expect("the patch path"),
|
|
"--dry-run",
|
|
]);
|
|
|
|
assert!(
|
|
output.status.success(),
|
|
"{}",
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
// The script's own words decide the split, so the assertion is about the
|
|
// shape rather than about which kernel's MAINTAINERS matched.
|
|
assert!(
|
|
stdout.contains("To: Fixture Maintainer <[email protected]>"),
|
|
"{stdout}"
|
|
);
|
|
assert!(
|
|
stdout.lines().any(|line| line.starts_with("Cc: ")),
|
|
"a reviewer and a second list belong on the Cc line: {stdout}"
|
|
);
|
|
assert!(stdout.contains("Dry-OK"), "{stdout}");
|
|
}
|