use std::{
borrow::Cow,
env, fmt, fs, io,
io::prelude::*,
path::{Path, PathBuf},
process, str,
sync::atomic::{AtomicUsize, Ordering},
thread, time,
};
static TEST_ID: AtomicUsize = AtomicUsize::new(0);
pub struct TestDir {
bin: PathBuf,
dir: PathBuf,
}
#[cfg(unix)]
fn exe_name(name: &str) -> Cow<str> {
Cow::Borrowed(name)
}
#[cfg(windows)]
fn exe_name(name: &str) -> Cow<str> {
if name.ends_with(".exe") {
Cow::Borrowed(name)
} else {
Cow::Owned(format!("{}.exe", name))
}
}
impl TestDir {
pub fn new(bin_name: &str, test_name: &str) -> TestDir {
let mut bin_dir = env::current_exe()
.expect("Could not find executable")
.parent()
.expect("Could not find parent directory for executable")
.to_path_buf();
if bin_dir.ends_with("deps") {
bin_dir.pop();
}
let id = TEST_ID.fetch_add(1, Ordering::SeqCst);
let dir = bin_dir
.join("integration-tests")
.join(test_name)
.join(format!("{}", id));
if dir.exists() {
fs::remove_dir_all(&dir).expect("Could not remove test output directory");
}
let mut err = None;
for _ in 0..10 {
match fs::create_dir_all(&dir) {
Ok(_) => {
err = None;
break;
}
Err(e) => {
err = Some(e);
}
}
thread::sleep(time::Duration::from_millis(500));
}
if let Some(e) = err {
panic!("Could not create test output directory: {}", e);
}
let mut bin = bin_dir.join(&*exe_name(bin_name));
if !bin.exists() {
writeln!(
io::stderr(),
"WARNING: could not find {}, will search PATH",
bin.display()
)
.expect("could not write to stderr");
bin = Path::new(&bin_name).to_owned();
}
TestDir { bin: bin, dir: dir }
}
pub fn cmd(&self) -> process::Command {
let mut cmd = process::Command::new(&self.bin);
cmd.current_dir(&self.dir);
cmd
}
pub fn path<P: AsRef<Path>>(&self, path: P) -> PathBuf {
self.dir.join(path)
}
pub fn src_path<P: AsRef<Path>>(&self, path: P) -> PathBuf {
let cwd = env::current_dir().expect("Could not get current dir");
fs::canonicalize(cwd.join(path)).expect("Could not canonicalize path")
}
pub fn create_file<P, S>(&self, path: P, contents: S)
where
P: AsRef<Path>,
S: AsRef<[u8]>,
{
let path = self.dir.join(path);
fs::create_dir_all(path.parent().expect("expected parent"))
.expect("could not create directory");
let mut f = fs::File::create(&path).expect("can't create file");
f.write_all(contents.as_ref()).expect("can't write to file");
}
pub fn expect_path<P: AsRef<Path>>(&self, path: P) {
let path = self.dir.join(path);
assert!(path.exists(), "{} should exist", path.display());
}
pub fn expect_no_such_path<P: AsRef<Path>>(&self, path: P) {
let path = self.dir.join(path);
assert!(!path.exists(), "{} should not exist", path.display());
}
pub fn expect_file_contents<P, S>(&self, path: P, expected: S)
where
P: AsRef<Path>,
S: AsRef<[u8]>,
{
let path = self.dir.join(path);
let expected = expected.as_ref();
self.expect_path(&path);
let mut f = fs::File::open(&path).expect("could not open file");
let mut found = vec![];
f.read_to_end(&mut found).expect("could not read file");
expect_data_eq(path.display(), &found, expected);
}
fn read_file(&self, path: &Path) -> String {
self.expect_path(&path);
let mut f = fs::File::open(&path).expect("could not open file");
let mut found = vec![];
f.read_to_end(&mut found).expect("could not read file");
str::from_utf8(&found)
.expect("expected UTF-8 file")
.to_owned()
}
pub fn expect_contains<P>(&self, path: P, pattern: &str)
where
P: AsRef<Path>,
{
let path = self.dir.join(path);
let contents = self.read_file(&path);
assert!(
contents.contains(pattern),
"expected {} to match {:?}, but it contained {:?}",
path.display(),
pattern,
contents
);
}
pub fn expect_does_not_contain<P>(&self, path: P, pattern: &str)
where
P: AsRef<Path>,
{
let path = self.dir.join(path);
let contents = self.read_file(&path);
assert!(
!contents.contains(pattern),
"expected {} to not match {:?}, but it contained {:?}",
path.display(),
pattern,
contents
);
}
}
fn expect_data_eq<D>(source: D, found: &[u8], expected: &[u8])
where
D: fmt::Display,
{
if found != expected {
panic!(
"expected {} to equal {:?}, found {:?}",
source,
String::from_utf8_lossy(expected).as_ref(),
String::from_utf8_lossy(found).as_ref()
);
}
}
pub trait CommandExt {
fn output_with_stdin<S: AsRef<[u8]>>(
&mut self,
input: S,
) -> io::Result<process::Output>;
}
impl CommandExt for process::Command {
fn output_with_stdin<S>(&mut self, input: S) -> io::Result<process::Output>
where
S: AsRef<[u8]>,
{
let input = input.as_ref().to_owned();
let mut child: process::Child = self
.stdin(process::Stdio::piped())
.stdout(process::Stdio::piped())
.stderr(process::Stdio::piped())
.spawn()
.expect("error running command");
let mut stdin = child.stdin.take().expect("std in is unexpectedly missing");
let worker = thread::spawn(move || {
stdin.write_all(&input).expect("could not write to stdin");
stdin
.flush()
.expect("could not flush data to child's stdin");
});
let result = child.wait_with_output();
worker.join().expect("stdin writer failed");
result
}
}
pub trait TeeOutputExt {
fn tee_output(self) -> io::Result<process::Output>;
}
impl TeeOutputExt for &mut process::Command {
fn tee_output(self) -> io::Result<process::Output> {
self.output().tee_output()
}
}
impl TeeOutputExt for io::Result<process::Output> {
fn tee_output(self) -> io::Result<process::Output> {
let output = self?;
io::stdout().write_all(&output.stdout)?;
io::stderr().write_all(&output.stderr)?;
Ok(output)
}
}
pub trait OutputExt {
fn stdout_str(&self) -> &str;
fn stderr_str(&self) -> &str;
}
impl OutputExt for process::Output {
fn stdout_str(&self) -> &str {
str::from_utf8(&self.stdout).expect("stdout was not UTF-8 text")
}
fn stderr_str(&self) -> &str {
str::from_utf8(&self.stderr).expect("stderr was not UTF-8 text")
}
}
pub trait ExpectStatus {
fn expect_success(self) -> process::Output;
fn expect_failure(self) -> process::Output;
}
impl ExpectStatus for process::Output {
fn expect_success(self) -> process::Output {
if !self.status.success() {
io::stdout()
.write_all(&self.stdout)
.expect("could not write to stdout");
io::stderr()
.write_all(&self.stderr)
.expect("could not write to stderr");
panic!("expected command to succeed, got {}", self.status)
}
self
}
fn expect_failure(self) -> process::Output {
if self.status.success() {
io::stdout()
.write_all(&self.stdout)
.expect("could not write to stdout");
io::stderr()
.write_all(&self.stderr)
.expect("could not write to stderr");
panic!("expected command to fail, got {}", self.status)
}
self
}
}
impl<ES: ExpectStatus, E: fmt::Debug> ExpectStatus for Result<ES, E> {
fn expect_success(self) -> process::Output {
match self {
Ok(es) => es.expect_success(),
Err(err) => panic!("error running command: {:?}", err),
}
}
fn expect_failure(self) -> process::Output {
match self {
Ok(es) => es.expect_failure(),
Err(err) => panic!("error running command: {:?}", err),
}
}
}
impl<'a> ExpectStatus for &'a mut process::Command {
fn expect_success(self) -> process::Output {
self.output().expect_success()
}
fn expect_failure(self) -> process::Output {
self.output().expect_failure()
}
}
impl ExpectStatus for process::Child {
fn expect_success(self) -> process::Output {
self.wait_with_output().expect_success()
}
fn expect_failure(self) -> process::Output {
self.wait_with_output().expect_failure()
}
}