cli-test: Various improvements

* Add `stderr` decorator to assert stderr output of commands instead of
  stdout.
* Add ability to set env vars on a per-test-block basis.
* Add `fail` decorator when a failure code is expected.
* Look for binaries in cargo target dir first.
This commit is contained in:
Alexis Sellier 2023-05-10 14:55:01 +02:00
parent 1dfaab6b4d
commit ff9903bf96
No known key found for this signature in database
1 changed files with 57 additions and 7 deletions

View File

@ -36,6 +36,12 @@ pub struct Test {
context: Vec<String>, context: Vec<String>,
/// Test assertions to run. /// Test assertions to run.
assertions: Vec<Assertion>, assertions: Vec<Assertion>,
/// Whether to check stderr's output instead of stdout.
stderr: bool,
/// Whether to expect an error status code.
fail: bool,
/// Local env vars to use just for this test.
env: HashMap<String, String>,
} }
/// An assertion is a command to run with an expected output. /// An assertion is a command to run with an expected output.
@ -118,6 +124,18 @@ impl TestFormula {
if fenced { if fenced {
// End existing code block. // End existing code block.
self.tests.push(mem::take(&mut test)); self.tests.push(mem::take(&mut test));
} else {
for token in line.split_whitespace() {
if token.contains("stderr") {
test.stderr = true;
}
if token.contains("fail") {
test.fail = true;
}
if let Some((key, val)) = token.split_once('=') {
test.env.insert(key.to_owned(), val.to_owned());
}
}
} }
fenced = !fenced; fenced = !fenced;
@ -135,14 +153,18 @@ impl TestFormula {
command: cmd.to_owned(), command: cmd.to_owned(),
args: args.to_owned(), args: args.to_owned(),
expected: String::new(), expected: String::new(),
exit: ExitStatus::Success, exit: if test.fail {
ExitStatus::Failure
} else {
ExitStatus::Success
},
}); });
} else if let Some(test) = test.assertions.last_mut() { } else if let Some(a) = test.assertions.last_mut() {
if line.starts_with(ERROR_PREFIX) { if line.starts_with(ERROR_PREFIX) {
test.exit = ExitStatus::Failure; a.exit = ExitStatus::Failure;
} }
test.expected.push_str(line.as_str()); a.expected.push_str(line.as_str());
test.expected.push('\n'); a.expected.push('\n');
} else { } else {
return Err(Error::Parse); return Err(Error::Parse);
} }
@ -193,6 +215,20 @@ impl TestFormula {
fs::create_dir_all(&self.cwd)?; fs::create_dir_all(&self.cwd)?;
let mut bins = env::var("PATH").unwrap_or_default();
if let Ok(manifest) = env::var("CARGO_MANIFEST_DIR") {
let profile = if cfg!(debug_assertions) {
"debug"
} else {
"release"
};
let path = Path::new(manifest.as_str());
let path = path.parent().unwrap().join("target").join(profile);
bins = format!("{}:{bins}", path.display());
}
log::debug!(target: "test", "Using PATH={bins}");
for test in &self.tests { for test in &self.tests {
for assertion in &test.assertions { for assertion in &test.assertions {
let path = assertion let path = assertion
@ -226,10 +262,13 @@ impl TestFormula {
if !self.cwd.exists() { if !self.cwd.exists() {
log::error!(target: "test", "{path}: Directory {} does not exist..", self.cwd.display()); log::error!(target: "test", "{path}: Directory {} does not exist..", self.cwd.display());
} }
let result = Command::new(cmd.clone()) let result = Command::new(cmd.clone())
.env_clear() .env_clear()
.envs(env::vars().filter(|(k, _)| k == "PATH")) .env("PATH", &bins)
.env("RUST_BACKTRACE", "1")
.envs(self.env.clone()) .envs(self.env.clone())
.envs(test.env.clone())
.current_dir(&self.cwd) .current_dir(&self.cwd)
.args(&assertion.args) .args(&assertion.args)
.with_assert(assert.clone()) .with_assert(assert.clone())
@ -239,7 +278,12 @@ impl TestFormula {
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);
let matches = assert.stdout_matches(&expected);
let matches = if test.stderr {
assert.stderr_matches(&expected)
} else {
assert.stdout_matches(&expected)
};
match assertion.exit { match assertion.exit {
ExitStatus::Success => { ExitStatus::Success => {
matches.success(); matches.success();
@ -326,6 +370,9 @@ $ rad sync
exit: ExitStatus::Success, exit: ExitStatus::Success,
}, },
], ],
fail: false,
stderr: false,
env: HashMap::default(),
}, },
Test { Test {
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.")],
@ -336,6 +383,9 @@ $ rad sync
expected: String::new(), expected: String::new(),
exit: ExitStatus::Success, exit: ExitStatus::Success,
}], }],
fail: false,
stderr: false,
env: HashMap::default(),
}, },
], ],
}; };