patch: fill in the loop verbs, and test them
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.
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
//! 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}");
|
||||
}
|
||||
Reference in New Issue
Block a user