2022-12-27 20:16:18 -08:00
|
|
|
use {super::*, pretty_assertions::assert_eq};
|
2021-07-26 01:26:06 -07:00
|
|
|
|
2020-09-17 19:43:04 -07:00
|
|
|
macro_rules! test {
|
2021-08-27 17:01:50 -07:00
|
|
|
{
|
|
|
|
name: $name:ident,
|
2021-07-26 01:26:06 -07:00
|
|
|
$(justfile: $justfile:expr,)?
|
2021-08-27 17:01:50 -07:00
|
|
|
$(args: ($($arg:tt),*),)?
|
|
|
|
$(env: { $($env_key:literal : $env_value:literal,)* },)?
|
|
|
|
$(stdin: $stdin:expr,)?
|
|
|
|
$(stdout: $stdout:expr,)?
|
2022-05-04 16:18:31 -07:00
|
|
|
$(stdout_regex: $stdout_regex:expr,)?
|
2021-08-27 17:01:50 -07:00
|
|
|
$(stderr: $stderr:expr,)?
|
|
|
|
$(stderr_regex: $stderr_regex:expr,)?
|
|
|
|
$(status: $status:expr,)?
|
|
|
|
$(shell: $shell:expr,)?
|
|
|
|
} => {
|
2020-09-17 19:43:04 -07:00
|
|
|
#[test]
|
|
|
|
fn $name() {
|
2021-07-26 01:26:06 -07:00
|
|
|
let test = crate::test::Test::new();
|
|
|
|
|
|
|
|
$($(let test = test.arg($arg);)*)?
|
|
|
|
$($(let test = test.env($env_key, $env_value);)*)?
|
|
|
|
$(let test = test.justfile($justfile);)?
|
|
|
|
$(let test = test.shell($shell);)?
|
|
|
|
$(let test = test.status($status);)?
|
|
|
|
$(let test = test.stderr($stderr);)?
|
2021-08-27 17:01:50 -07:00
|
|
|
$(let test = test.stderr_regex($stderr_regex);)?
|
2021-07-26 01:26:06 -07:00
|
|
|
$(let test = test.stdin($stdin);)?
|
|
|
|
$(let test = test.stdout($stdout);)?
|
2022-05-04 16:18:31 -07:00
|
|
|
$(let test = test.stdout_regex($stdout_regex);)?
|
2021-07-26 01:26:06 -07:00
|
|
|
|
|
|
|
test.run();
|
2020-09-17 19:43:04 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-13 11:03:14 -08:00
|
|
|
pub(crate) struct Output {
|
2024-01-11 19:22:27 -08:00
|
|
|
pub(crate) pid: u32,
|
2023-01-13 11:03:14 -08:00
|
|
|
pub(crate) stdout: String,
|
|
|
|
pub(crate) tempdir: TempDir,
|
|
|
|
}
|
|
|
|
|
2023-10-27 13:07:46 -07:00
|
|
|
#[must_use]
|
2021-07-26 01:26:06 -07:00
|
|
|
pub(crate) struct Test {
|
2021-07-28 00:33:44 -07:00
|
|
|
pub(crate) args: Vec<String>,
|
2021-07-31 12:25:49 -07:00
|
|
|
pub(crate) current_dir: PathBuf,
|
2021-07-28 00:33:44 -07:00
|
|
|
pub(crate) env: BTreeMap<String, String>,
|
2021-07-31 12:25:49 -07:00
|
|
|
pub(crate) justfile: Option<String>,
|
|
|
|
pub(crate) shell: bool,
|
|
|
|
pub(crate) status: i32,
|
2021-07-28 00:33:44 -07:00
|
|
|
pub(crate) stderr: String,
|
2021-07-26 01:26:06 -07:00
|
|
|
pub(crate) stderr_regex: Option<Regex>,
|
2021-07-31 12:25:49 -07:00
|
|
|
pub(crate) stdin: String,
|
|
|
|
pub(crate) stdout: String,
|
2022-05-04 16:18:31 -07:00
|
|
|
pub(crate) stdout_regex: Option<Regex>,
|
2021-07-31 12:25:49 -07:00
|
|
|
pub(crate) tempdir: TempDir,
|
2023-01-12 19:25:28 -08:00
|
|
|
pub(crate) test_round_trip: bool,
|
2021-10-14 00:35:15 -07:00
|
|
|
pub(crate) unindent_stdout: bool,
|
2020-09-17 19:43:04 -07:00
|
|
|
}
|
|
|
|
|
2021-07-26 01:26:06 -07:00
|
|
|
impl Test {
|
|
|
|
pub(crate) fn new() -> Self {
|
|
|
|
Self::with_tempdir(tempdir())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn with_tempdir(tempdir: TempDir) -> Self {
|
|
|
|
Self {
|
|
|
|
args: Vec::new(),
|
2021-07-31 12:25:49 -07:00
|
|
|
current_dir: PathBuf::new(),
|
2021-07-26 01:26:06 -07:00
|
|
|
env: BTreeMap::new(),
|
|
|
|
justfile: Some(String::new()),
|
|
|
|
shell: true,
|
|
|
|
status: EXIT_SUCCESS,
|
|
|
|
stderr: String::new(),
|
2021-07-28 00:33:44 -07:00
|
|
|
stderr_regex: None,
|
2021-07-26 01:26:06 -07:00
|
|
|
stdin: String::new(),
|
|
|
|
stdout: String::new(),
|
2022-05-04 16:18:31 -07:00
|
|
|
stdout_regex: None,
|
2021-07-26 01:26:06 -07:00
|
|
|
tempdir,
|
2023-01-12 19:25:28 -08:00
|
|
|
test_round_trip: true,
|
2021-10-14 00:35:15 -07:00
|
|
|
unindent_stdout: true,
|
2020-09-17 19:43:04 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-26 01:26:06 -07:00
|
|
|
pub(crate) fn arg(mut self, val: &str) -> Self {
|
|
|
|
self.args.push(val.to_owned());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2023-01-03 22:31:56 -08:00
|
|
|
pub(crate) fn args<'a>(mut self, args: impl AsRef<[&'a str]>) -> Self {
|
|
|
|
for arg in args.as_ref() {
|
2021-07-26 01:26:06 -07:00
|
|
|
self = self.arg(arg);
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2024-06-13 13:21:00 -07:00
|
|
|
pub(crate) fn create_dir(self, path: impl AsRef<Path>) -> Self {
|
|
|
|
fs::create_dir_all(self.tempdir.path().join(path.as_ref())).unwrap();
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-07-31 12:25:49 -07:00
|
|
|
pub(crate) fn current_dir(mut self, path: impl AsRef<Path>) -> Self {
|
2024-05-14 18:05:55 -07:00
|
|
|
path.as_ref().clone_into(&mut self.current_dir);
|
2021-07-31 12:25:49 -07:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-07-26 01:26:06 -07:00
|
|
|
pub(crate) fn env(mut self, key: &str, val: &str) -> Self {
|
|
|
|
self.env.insert(key.to_string(), val.to_string());
|
|
|
|
self
|
|
|
|
}
|
2020-09-17 19:43:04 -07:00
|
|
|
|
2021-07-26 01:26:06 -07:00
|
|
|
pub(crate) fn justfile(mut self, justfile: impl Into<String>) -> Self {
|
|
|
|
self.justfile = Some(justfile.into());
|
|
|
|
self
|
|
|
|
}
|
2021-03-30 17:30:32 -07:00
|
|
|
|
2021-07-26 01:26:06 -07:00
|
|
|
pub(crate) fn justfile_path(&self) -> PathBuf {
|
|
|
|
self.tempdir.path().join("justfile")
|
|
|
|
}
|
2020-09-17 19:43:04 -07:00
|
|
|
|
2024-01-19 12:04:28 -08:00
|
|
|
#[cfg(unix)]
|
|
|
|
#[track_caller]
|
|
|
|
pub(crate) fn symlink(self, original: &str, link: &str) -> Self {
|
|
|
|
std::os::unix::fs::symlink(
|
|
|
|
self.tempdir.path().join(original),
|
|
|
|
self.tempdir.path().join(link),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-07-26 01:26:06 -07:00
|
|
|
pub(crate) fn no_justfile(mut self) -> Self {
|
|
|
|
self.justfile = None;
|
|
|
|
self
|
|
|
|
}
|
2020-09-17 19:43:04 -07:00
|
|
|
|
2021-07-26 01:26:06 -07:00
|
|
|
pub(crate) fn shell(mut self, shell: bool) -> Self {
|
|
|
|
self.shell = shell;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn status(mut self, exit_status: i32) -> Self {
|
|
|
|
self.status = exit_status;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn stderr(mut self, stderr: impl Into<String>) -> Self {
|
|
|
|
self.stderr = stderr.into();
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn stderr_regex(mut self, stderr_regex: impl AsRef<str>) -> Self {
|
2024-07-07 20:45:03 -07:00
|
|
|
self.stderr_regex = Some(Regex::new(&format!("^(?s){}$", stderr_regex.as_ref())).unwrap());
|
2021-07-26 01:26:06 -07:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn stdin(mut self, stdin: impl Into<String>) -> Self {
|
|
|
|
self.stdin = stdin.into();
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn stdout(mut self, stdout: impl Into<String>) -> Self {
|
|
|
|
self.stdout = stdout.into();
|
|
|
|
self
|
|
|
|
}
|
2021-07-28 00:33:44 -07:00
|
|
|
|
2022-05-04 16:18:31 -07:00
|
|
|
pub(crate) fn stdout_regex(mut self, stdout_regex: impl AsRef<str>) -> Self {
|
2023-01-12 19:25:28 -08:00
|
|
|
self.stdout_regex = Some(Regex::new(&format!("^{}$", stdout_regex.as_ref())).unwrap());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2024-07-14 14:22:03 -07:00
|
|
|
#[allow(unused)]
|
2023-01-12 19:25:28 -08:00
|
|
|
pub(crate) fn test_round_trip(mut self, test_round_trip: bool) -> Self {
|
|
|
|
self.test_round_trip = test_round_trip;
|
2022-05-04 16:18:31 -07:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-07-31 12:25:49 -07:00
|
|
|
pub(crate) fn tree(self, mut tree: Tree) -> Self {
|
|
|
|
tree.map(|_name, content| unindent(content));
|
2022-01-30 12:16:10 -08:00
|
|
|
tree.instantiate(self.tempdir.path()).unwrap();
|
2021-07-31 12:25:49 -07:00
|
|
|
self
|
|
|
|
}
|
2021-10-14 00:35:15 -07:00
|
|
|
|
|
|
|
pub(crate) fn unindent_stdout(mut self, unindent_stdout: bool) -> Self {
|
|
|
|
self.unindent_stdout = unindent_stdout;
|
|
|
|
self
|
|
|
|
}
|
2023-01-03 22:31:56 -08:00
|
|
|
|
|
|
|
pub(crate) fn write(self, path: impl AsRef<Path>, content: impl AsRef<[u8]>) -> Self {
|
|
|
|
let path = self.tempdir.path().join(path);
|
2023-06-12 09:53:55 -07:00
|
|
|
fs::create_dir_all(path.parent().unwrap()).unwrap();
|
|
|
|
fs::write(path, content).unwrap();
|
2023-01-03 22:31:56 -08:00
|
|
|
self
|
|
|
|
}
|
2021-07-26 01:26:06 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Test {
|
2024-05-14 20:29:40 -07:00
|
|
|
#[track_caller]
|
2023-01-13 11:03:14 -08:00
|
|
|
pub(crate) fn run(self) -> Output {
|
2021-07-26 01:26:06 -07:00
|
|
|
if let Some(justfile) = &self.justfile {
|
|
|
|
let justfile = unindent(justfile);
|
|
|
|
fs::write(self.justfile_path(), justfile).unwrap();
|
|
|
|
}
|
|
|
|
|
2021-10-14 00:35:15 -07:00
|
|
|
let stdout = if self.unindent_stdout {
|
|
|
|
unindent(&self.stdout)
|
|
|
|
} else {
|
2024-05-18 22:41:38 -07:00
|
|
|
self.stdout.clone()
|
2021-10-14 00:35:15 -07:00
|
|
|
};
|
2021-07-26 01:26:06 -07:00
|
|
|
|
2024-05-30 16:12:07 -07:00
|
|
|
let stderr = unindent(&self.stderr);
|
2020-09-17 19:43:04 -07:00
|
|
|
|
2022-12-15 16:53:21 -08:00
|
|
|
let mut command = Command::new(executable_path("just"));
|
2020-09-17 19:43:04 -07:00
|
|
|
|
|
|
|
if self.shell {
|
2022-11-22 16:36:23 -08:00
|
|
|
command.args(["--shell", "bash"]);
|
2020-09-17 19:43:04 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
let mut child = command
|
2024-05-18 22:41:38 -07:00
|
|
|
.args(&self.args)
|
2021-07-26 01:26:06 -07:00
|
|
|
.envs(&self.env)
|
2024-05-18 22:41:38 -07:00
|
|
|
.current_dir(self.tempdir.path().join(&self.current_dir))
|
2020-09-17 19:43:04 -07:00
|
|
|
.stdin(Stdio::piped())
|
|
|
|
.stdout(Stdio::piped())
|
|
|
|
.stderr(Stdio::piped())
|
|
|
|
.spawn()
|
|
|
|
.expect("just invocation failed");
|
|
|
|
|
2024-01-11 19:22:27 -08:00
|
|
|
let pid = child.id();
|
|
|
|
|
2020-09-17 19:43:04 -07:00
|
|
|
{
|
|
|
|
let mut stdin_handle = child.stdin.take().expect("failed to unwrap stdin handle");
|
|
|
|
|
|
|
|
stdin_handle
|
|
|
|
.write_all(self.stdin.as_bytes())
|
|
|
|
.expect("failed to write stdin to just process");
|
|
|
|
}
|
|
|
|
|
|
|
|
let output = child
|
|
|
|
.wait_with_output()
|
|
|
|
.expect("failed to wait for just process");
|
|
|
|
|
2021-07-26 01:26:06 -07:00
|
|
|
fn compare<T: PartialEq + Debug>(name: &str, have: T, want: T) -> bool {
|
|
|
|
let equal = have == want;
|
|
|
|
if !equal {
|
2023-06-12 09:53:55 -07:00
|
|
|
eprintln!("Bad {name}: {}", Comparison::new(&have, &want));
|
2021-07-26 01:26:06 -07:00
|
|
|
}
|
|
|
|
equal
|
|
|
|
}
|
|
|
|
|
2022-05-04 16:18:31 -07:00
|
|
|
let output_stdout = str::from_utf8(&output.stdout).unwrap();
|
2021-07-26 01:26:06 -07:00
|
|
|
let output_stderr = str::from_utf8(&output.stderr).unwrap();
|
2020-09-17 19:43:04 -07:00
|
|
|
|
2022-05-04 16:18:31 -07:00
|
|
|
if let Some(ref stdout_regex) = self.stdout_regex {
|
|
|
|
if !stdout_regex.is_match(output_stdout) {
|
2023-10-08 20:47:20 -07:00
|
|
|
panic!("Stdout regex mismatch:\n{output_stdout:?}\n!~=\n/{stdout_regex:?}/");
|
2022-05-04 16:18:31 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-26 01:26:06 -07:00
|
|
|
if let Some(ref stderr_regex) = self.stderr_regex {
|
|
|
|
if !stderr_regex.is_match(output_stderr) {
|
2023-01-26 18:49:03 -08:00
|
|
|
panic!("Stderr regex mismatch:\n{output_stderr:?}\n!~=\n/{stderr_regex:?}/");
|
2021-07-26 01:26:06 -07:00
|
|
|
}
|
|
|
|
}
|
2020-09-17 19:43:04 -07:00
|
|
|
|
2024-05-30 10:28:54 -07:00
|
|
|
if !compare("status", output.status.code(), Some(self.status))
|
2022-05-04 16:18:31 -07:00
|
|
|
| (self.stdout_regex.is_none() && !compare("stdout", output_stdout, &stdout))
|
2021-07-26 01:26:06 -07:00
|
|
|
| (self.stderr_regex.is_none() && !compare("stderr", output_stderr, &stderr))
|
|
|
|
{
|
|
|
|
panic!("Output mismatch.");
|
|
|
|
}
|
2020-09-17 19:43:04 -07:00
|
|
|
|
2023-01-12 19:25:28 -08:00
|
|
|
if self.test_round_trip && self.status == EXIT_SUCCESS {
|
2024-05-18 22:41:38 -07:00
|
|
|
self.round_trip();
|
2020-09-17 19:43:04 -07:00
|
|
|
}
|
2021-07-26 01:26:06 -07:00
|
|
|
|
2023-01-13 11:03:14 -08:00
|
|
|
Output {
|
2024-01-11 19:22:27 -08:00
|
|
|
pid,
|
2023-01-13 11:03:14 -08:00
|
|
|
stdout: output_stdout.into(),
|
2024-01-11 19:22:27 -08:00
|
|
|
tempdir: self.tempdir,
|
2023-01-13 11:03:14 -08:00
|
|
|
}
|
2020-09-17 19:43:04 -07:00
|
|
|
}
|
|
|
|
|
2024-05-18 22:41:38 -07:00
|
|
|
fn round_trip(&self) {
|
|
|
|
println!("Reparsing...");
|
2020-09-17 19:43:04 -07:00
|
|
|
|
2024-05-18 22:41:38 -07:00
|
|
|
let output = Command::new(executable_path("just"))
|
|
|
|
.current_dir(self.tempdir.path())
|
|
|
|
.arg("--dump")
|
|
|
|
.envs(&self.env)
|
|
|
|
.output()
|
|
|
|
.expect("just invocation failed");
|
2020-09-17 19:43:04 -07:00
|
|
|
|
2024-05-18 22:41:38 -07:00
|
|
|
if !output.status.success() {
|
|
|
|
panic!("dump failed: {} {:?}", output.status, output);
|
|
|
|
}
|
2020-09-17 19:43:04 -07:00
|
|
|
|
2024-05-18 22:41:38 -07:00
|
|
|
let dumped = String::from_utf8(output.stdout).unwrap();
|
2020-09-17 19:43:04 -07:00
|
|
|
|
2024-05-18 22:41:38 -07:00
|
|
|
let reparsed_path = self.tempdir.path().join("reparsed.just");
|
2020-09-17 19:43:04 -07:00
|
|
|
|
2024-05-18 22:41:38 -07:00
|
|
|
fs::write(&reparsed_path, &dumped).unwrap();
|
2020-09-17 19:43:04 -07:00
|
|
|
|
2024-05-18 22:41:38 -07:00
|
|
|
let output = Command::new(executable_path("just"))
|
|
|
|
.current_dir(self.tempdir.path())
|
|
|
|
.arg("--justfile")
|
|
|
|
.arg(&reparsed_path)
|
|
|
|
.arg("--dump")
|
|
|
|
.envs(&self.env)
|
|
|
|
.output()
|
|
|
|
.expect("just invocation failed");
|
2020-09-17 19:43:04 -07:00
|
|
|
|
2024-05-18 22:41:38 -07:00
|
|
|
if !output.status.success() {
|
|
|
|
panic!("reparse failed: {}", output.status);
|
|
|
|
}
|
2020-09-17 19:43:04 -07:00
|
|
|
|
2024-05-18 22:41:38 -07:00
|
|
|
let reparsed = String::from_utf8(output.stdout).unwrap();
|
2020-09-17 19:43:04 -07:00
|
|
|
|
2024-05-18 22:41:38 -07:00
|
|
|
assert_eq!(reparsed, dumped, "reparse mismatch");
|
|
|
|
}
|
2020-09-17 19:43:04 -07:00
|
|
|
}
|
2024-05-18 16:12:11 -07:00
|
|
|
|
|
|
|
pub fn assert_eval_eq(expression: &str, result: &str) {
|
|
|
|
Test::new()
|
|
|
|
.justfile(format!("x := {expression}"))
|
|
|
|
.args(["--evaluate", "x"])
|
|
|
|
.stdout(result)
|
|
|
|
.unindent_stdout(false)
|
|
|
|
.run();
|
|
|
|
}
|