blob: a1e7e7f38aa814b987178206c7a4a3dcfa994f25 [file] [log] [blame]
//! Support code for e2e tests, which run n2 as a binary.
mod basic;
mod directories;
mod discovered;
mod missing;
mod regen;
mod validations;
use anyhow::anyhow;
pub fn n2_binary() -> std::path::PathBuf {
std::env::current_exe()
.expect("test binary path")
.parent()
.expect("test binary directory")
.parent()
.expect("binary directory")
.join("n2")
}
pub fn n2_command(args: Vec<&str>) -> std::process::Command {
let mut cmd = std::process::Command::new(n2_binary());
cmd.args(args);
cmd
}
fn print_output(out: &std::process::Output) {
// Gross: use print! instead of writing to stdout so Rust test
// framework can capture it.
print!("{}", std::str::from_utf8(&out.stdout).unwrap());
print!("{}", std::str::from_utf8(&out.stderr).unwrap());
}
pub fn assert_output_contains(out: &std::process::Output, text: &str) {
let out = std::str::from_utf8(&out.stdout).unwrap();
if !out.contains(text) {
panic!(
"assertion failed; expected output to contain {:?} but got:\n{}",
text, out
);
}
}
pub fn assert_output_not_contains(out: &std::process::Output, text: &str) {
let out = std::str::from_utf8(&out.stdout).unwrap();
if out.contains(text) {
panic!(
"assertion failed; expected output to not contain {:?} but got:\n{}",
text, out
);
}
}
/// Manages a temporary directory for invoking n2.
pub struct TestSpace {
dir: tempfile::TempDir,
}
impl TestSpace {
pub fn new() -> anyhow::Result<Self> {
let dir = tempfile::tempdir()?;
Ok(TestSpace { dir })
}
/// Write a file into the working space.
pub fn write(&self, path: &str, content: &str) -> std::io::Result<()> {
std::fs::write(self.dir.path().join(path), content)
}
/// Read a file from the working space.
pub fn read(&self, path: &str) -> anyhow::Result<Vec<u8>> {
let path = self.dir.path().join(path);
std::fs::read(&path).map_err(|err| anyhow!("read {}: {}", path.display(), err))
}
pub fn metadata(&self, path: &str) -> std::io::Result<std::fs::Metadata> {
std::fs::metadata(self.dir.path().join(path))
}
pub fn sub_mtime(&self, path: &str, dur: std::time::Duration) -> anyhow::Result<()> {
let path = self.dir.path().join(path);
let t = std::time::SystemTime::now() - dur;
filetime::set_file_mtime(path, filetime::FileTime::from_system_time(t))?;
Ok(())
}
/// Invoke n2, returning process output.
pub fn run(&self, cmd: &mut std::process::Command) -> std::io::Result<std::process::Output> {
cmd.current_dir(self.dir.path()).output()
}
/// Like run, but also print output if the build failed.
pub fn run_expect(
&self,
cmd: &mut std::process::Command,
) -> anyhow::Result<std::process::Output> {
let out = self.run(cmd)?;
if !out.status.success() {
print_output(&out);
anyhow::bail!("build failed, status {}", out.status);
}
Ok(out)
}
/// Persist the temp dir locally and abort the test. Debugging helper.
#[allow(dead_code)]
pub fn eject(self) -> ! {
panic!("ejected at {:?}", self.dir.into_path());
}
}
// Ensure TOUCH_RULE has the same description and number of lines of text
// on Windows/non-Windows to make tests agnostic to platform.
#[cfg(unix)]
pub const TOUCH_RULE: &str = "
rule touch
command = touch $out
description = touch $out
";
#[cfg(windows)]
pub const TOUCH_RULE: &str = "
rule touch
command = cmd /c type nul > $out
description = touch $out
";
#[cfg(unix)]
pub const ECHO_RULE: &str = "
rule echo
command = echo $text
description = echo $out
";
#[cfg(windows)]
pub const ECHO_RULE: &str = "
rule echo
command = cmd /c echo $text
description = echo $out
";