use std::env;
use std::fmt;
use std::fs;
use std::io;
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::process;
use std::str;
use std::sync::atomic::{ATOMIC_USIZE_INIT, AtomicUsize, Ordering};
use std::thread;
use std::time;
static TEST_ID: AtomicUsize = ATOMIC_USIZE_INIT;
pub struct TestDir {
bin: PathBuf,
dir: PathBuf,
}
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(&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 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(), format!("{} exists", 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);
}
pub fn expect_contains<P>(&self, path: P, pattern: &str)
where P: AsRef<Path>
{
let path = self.dir.join(path);
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");
let found = str::from_utf8(&found).expect("expected UTF-8 file");
assert!(found.contains(pattern),
format!("expected {} to match {:?}, but it contained {:?}",
path.display(), pattern, found));
}
}
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 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;
}
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
}
}
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),
}
}
}
impl<'a> ExpectStatus for &'a mut process::Command {
fn expect_success(self) -> process::Output {
self.output().expect_success()
}
}
impl ExpectStatus for process::Child {
fn expect_success(self) -> process::Output {
self.wait_with_output().expect_success()
}
}