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:
parent
1dfaab6b4d
commit
ff9903bf96
|
|
@ -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(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue