use std::fmt::Write;
use std::io::{BufRead, BufReader, Error, Write as _};
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::thread::spawn;
use log::*;
use tempfile::{tempdir, TempDir};
#[derive(Default)]
pub struct Fixture {
fixture: String,
epilogue: String,
}
impl Fixture {
pub fn new() -> Fixture {
Fixture::default()
}
fn append_fixture(&self, log_level: &str, appended: &str) -> Fixture {
let mut fixture = String::new();
writeln!(fixture, "{}", self.fixture).unwrap();
writeln!(fixture, "echo ::set-level::{} >&2", log_level).unwrap();
writeln!(fixture, "{}", textwrap::dedent(appended)).unwrap();
Fixture {
fixture,
epilogue: self.epilogue.clone(),
}
}
pub fn append_fixture_none(&self, appended: &str) -> Fixture {
self.append_fixture("none", appended)
}
pub fn append_fixture_trace(&self, appended: &str) -> Fixture {
self.append_fixture("trace", appended)
}
pub fn append_fixture_debug(&self, appended: &str) -> Fixture {
self.append_fixture("debug", appended)
}
fn append_epilogue(&self, appended: &str) -> Fixture {
let mut epilogue = String::new();
writeln!(epilogue, "{}", self.epilogue).unwrap();
writeln!(epilogue, "{}", textwrap::dedent(appended)).unwrap();
Fixture {
fixture: self.fixture.clone(),
epilogue,
}
}
pub fn prepare(
&self,
working_directory: &str,
last_fixture: &str,
) -> std::io::Result<FixtureGuard> {
let _ = env_logger::builder().is_test(true).try_init();
let tempdir = tempdir()?;
println!("{:?}", tempdir.path());
let mut bash = Command::new("bash")
.args(&["--noprofile", "--norc", "-xeo", "pipefail"])
.current_dir(tempdir.path())
.env_clear()
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
let mut stdin = bash.stdin.take().unwrap();
let merged_fixture = self
.append_fixture_debug(&textwrap::dedent(last_fixture))
.append_fixture_debug(&self.epilogue);
writeln!(stdin, "{}", &merged_fixture.fixture).unwrap();
drop(stdin);
let stdout_thread = spawn({
let stdout = bash.stdout.take().unwrap();
move || {
for line in BufReader::new(stdout).lines() {
match line {
Ok(line) if line.starts_with('+') => trace!("{}", line),
Ok(line) => info!("{}", line),
Err(err) => error!("{}", err),
}
}
}
});
let stderr_thread = spawn({
let stderr = bash.stderr.take().unwrap();
move || {
let mut level = Some(Level::Debug);
for line in BufReader::new(stderr).lines() {
match line {
Ok(line) if line.starts_with('+') && level.is_none() => {}
Ok(line) if line.starts_with('+') => {
log!(target: "stderr", level.unwrap(), "{}", line)
}
Ok(line) if line.starts_with("::set-level::") => {
if line.starts_with("::set-level::none") {
level = None
} else if line.starts_with("::set-level::trace") {
level = Some(Level::Trace)
} else if line.starts_with("::set-level::debug") {
level = Some(Level::Debug)
}
if let Some(level) = level {
log!(target: "stderr-set-level", level, "{}", line);
}
}
Ok(line) => info!(target: "stderr", "stderr: {}", line),
Err(err) => error!(target: "stderr", "stderr: {}", err),
}
}
}
});
stdout_thread.join().unwrap();
stderr_thread.join().unwrap();
let exit_status = bash.wait()?;
if !exit_status.success() {
return Err(Error::from_raw_os_error(exit_status.code().unwrap_or(-1)));
}
let previous_pwd = std::env::current_dir()?;
std::env::set_current_dir(tempdir.path().join(working_directory))?;
Ok(FixtureGuard {
_tempdir: tempdir,
previous_pwd,
})
}
}
#[must_use]
pub struct FixtureGuard {
_tempdir: TempDir,
previous_pwd: PathBuf,
}
impl<'a> Drop for FixtureGuard {
fn drop(&mut self) {
std::env::set_current_dir(&self.previous_pwd).unwrap();
}
}
pub fn rc() -> Fixture {
Fixture::new()
.append_fixture_none(
r#"
shopt -s expand_aliases
within() {
pushd $1 > /dev/null
source /dev/stdin
popd > /dev/null
}
alias upstream='within upstream'
alias origin='within origin'
alias local='within local'
"#,
)
.append_epilogue(
r#"
local <<EOF
pwd
git remote update --prune
git branch -vv --all
git log --oneline --oneline --decorate --graph --all
EOF
"#,
)
}
#[macro_export]
macro_rules! set {
{$($x:expr),*} => ({
use ::std::collections::HashSet;
use ::std::convert::From;
let mut tmp = HashSet::new();
$(tmp.insert(From::from($x));)*
tmp
});
{$($x:expr,)*} => ($crate::set!{$($x),*})
}
#[test]
#[ignore]
fn test() -> std::io::Result<()> {
let _guard = Fixture::new()
.append_epilogue("echo 'epilogue'")
.append_fixture("debug", "echo 'fixture'")
.prepare("", "");
Ok(())
}