cli-test: Log line of assertion being executed

This commit is contained in:
Lorenz Leutgeb 2026-02-14 17:31:34 +01:00 committed by Fintan Halpenny
parent 9055a2043c
commit ed2b36cf9c
1 changed files with 29 additions and 17 deletions

View File

@ -70,6 +70,8 @@ pub struct Assertion {
expected: String, expected: String,
/// Expected exit status. /// Expected exit status.
exit: ExitStatus, exit: ExitStatus,
/// Line number in the test file where this assertion is defined.
line: usize,
} }
#[derive(Debug, Default, PartialEq, Eq, Clone)] #[derive(Debug, Default, PartialEq, Eq, Clone)]
@ -275,7 +277,7 @@ impl TestFormula {
let mut fenced = false; // Whether we're inside a fenced code block. let mut fenced = false; // Whether we're inside a fenced code block.
let mut file: Option<(PathBuf, String)> = None; // Path and content of file created by this test block. let mut file: Option<(PathBuf, String)> = None; // Path and content of file created by this test block.
for line in r.lines() { for (row, line) in r.lines().enumerate() {
let line = line?; let line = line?;
if line.starts_with("```") { if line.starts_with("```") {
@ -343,6 +345,7 @@ impl TestFormula {
} else { } else {
ExitStatus::Success ExitStatus::Success
}, },
line: row + 1,
}); });
} else if let Some(a) = test.assertions.last_mut() { } else if let Some(a) = test.assertions.last_mut() {
a.expected.push_str(line.as_str()); a.expected.push_str(line.as_str());
@ -411,13 +414,16 @@ impl TestFormula {
*arg = arg.replace(format!("${k}").as_str(), &v); *arg = arg.replace(format!("${k}").as_str(), &v);
} }
} }
let path = assertion let location = assertion
.path .path
.file_name() .file_name()
.map(|f| f.to_string_lossy().to_string()) .map(|f| f.to_string_lossy().to_string())
.map(|f| f.strip_suffix(".md").unwrap_or(&f).to_owned())
.map(|f| f + ":" + assertion.line.to_string().as_str())
.unwrap_or(String::from("<none>")); .unwrap_or(String::from("<none>"));
let cmd = if assertion.command == "rad" {
snapbox::cmd::cargo_bin("rad") let (cmd, cmd_display) = if assertion.command == "rad" {
(snapbox::cmd::cargo_bin("rad"), "rad".to_string())
} else if assertion.command == "cd" { } else if assertion.command == "cd" {
let arg = assertion.args.first().unwrap(); let arg = assertion.args.first().unwrap();
let dir: PathBuf = arg.into(); let dir: PathBuf = arg.into();
@ -426,7 +432,7 @@ impl TestFormula {
// TODO: Add support for `..` and `/` // TODO: Add support for `..` and `/`
// TODO: Error if more than one args are given. // TODO: Error if more than one args are given.
log::debug!(target: "test", "{path}: Running `cd {}`..", dir.display()); log::debug!(target: "test", "{location}: `cd {}`..", dir.display());
if !dir.exists() { if !dir.exists() {
return Err(io::Error::new( return Err(io::Error::new(
@ -438,12 +444,11 @@ impl TestFormula {
continue; continue;
} else { } else {
PathBuf::from(&assertion.command) (PathBuf::from(&assertion.command), assertion.command.clone())
}; };
log::debug!(target: "test", "{path}: Running `{}` with {:?} in `{}`..", cmd.display(), assertion.args, run.path().display());
if !run.path().exists() { if !run.path().exists() {
log::warn!(target: "test", "{path}: Directory {} does not exist. Creating..", run.path().display()); log::warn!(target: "test", "{location}: Directory {} does not exist. Creating..", run.path().display());
fs::create_dir_all(run.path())?; fs::create_dir_all(run.path())?;
} }
@ -463,20 +468,24 @@ impl TestFormula {
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(ffi::OsStr::new(&PATH_SEPARATOR.to_string())); .join(ffi::OsStr::new(&PATH_SEPARATOR.to_string()));
log::debug!(target: "test", "Using PATH={:?}", bins); let command = Command::new(cmd.clone())
let result = Command::new(cmd.clone())
.env_clear() .env_clear()
.env("PATH", &bins) .env("PATH", &bins)
.env("RUST_BACKTRACE", "1") .env("RUST_BACKTRACE", "1")
.envs(jj_envs) .envs(jj_envs)
.envs(run.envs()) .envs(run.envs())
.current_dir(run.path()) .current_dir(run.path())
.args(args) .args(args.clone())
.with_assert(assert.clone()) .with_assert(assert.clone());
.output();
match result { log::debug!(target: "test", "{location}: `{} {}` @ {}", cmd_display, args.join(" "), run.path().display());
log::trace!(target: "test", "{location}: {}", run.envs().map(|(k, v)| format!("{}={}", k, v)).collect::<Vec<_>>().join(", "));
log::logger().flush();
// Even though it would be possible to use `Command::assert` to directly obtain
// `OutputAssert`, we use `Command::output` to be able to handle `io::ErrorKind::NotFound`
// separately and provide a more helpful error message in that case.
match command.output() {
Ok(output) => { Ok(output) => {
let assert = OutputAssert::new(output).with_assert(assert.clone()); let assert = OutputAssert::new(output).with_assert(assert.clone());
let expected = Self::map_spaced_brackets(&assertion.expected); let expected = Self::map_spaced_brackets(&assertion.expected);
@ -507,11 +516,11 @@ impl TestFormula {
} }
Err(err) => { Err(err) => {
if err.kind() == io::ErrorKind::NotFound { if err.kind() == io::ErrorKind::NotFound {
log::error!(target: "test", "{path}: Command `{}` does not exist..", cmd.display()); log::error!(target: "test", "{location}: Command `{}` does not exist..", cmd.display());
} }
return Err(io::Error::new( return Err(io::Error::new(
err.kind(), err.kind(),
format!("{path}: {err}: `{}`", cmd.display()), format!("{location}: {err}: `{}`", cmd.display()),
)); ));
} }
} }
@ -606,6 +615,7 @@ $ rad sync
home: None, home: None,
assertions: vec![ assertions: vec![
Assertion { Assertion {
line: 3,
path: path.clone(), path: path.clone(),
command: String::from("rad"), command: String::from("rad"),
args: vec![String::from("track"), String::from("@dave")], args: vec![String::from("track"), String::from("@dave")],
@ -615,6 +625,7 @@ $ rad sync
exit: ExitStatus::Success, exit: ExitStatus::Success,
}, },
Assertion { Assertion {
line: 7,
path: path.clone(), path: path.clone(),
command: String::from("rad"), command: String::from("rad"),
args: vec![String::from("track"), String::from("@sean")], args: vec![String::from("track"), String::from("@sean")],
@ -634,6 +645,7 @@ $ rad sync
context: vec![String::from("Super, now let's move on to the next step.")], context: vec![String::from("Super, now let's move on to the next step.")],
home: Some("alice".to_owned()), home: Some("alice".to_owned()),
assertions: vec![Assertion { assertions: vec![Assertion {
line: 13,
path: path.clone(), path: path.clone(),
command: String::from("rad"), command: String::from("rad"),
args: vec![String::from("sync")], args: vec![String::from("sync")],