extern crate conch_runtime; extern crate futures; extern crate tempdir; extern crate tokio_core; extern crate void; use self::conch_runtime::STDOUT_FILENO; use self::conch_runtime::error::IsFatalError; use self::conch_runtime::io::{FileDesc, FileDescWrapper}; use self::futures::future::FutureResult; use self::futures::future::result as future_result; use self::tempdir::TempDir; use self::void::{unreachable, Void}; use std::borrow::Borrow; use std::fs::OpenOptions; use std::rc::Rc; use std::sync::Arc; // Convenience re-exports pub use self::conch_runtime::{ExitStatus, EXIT_SUCCESS, EXIT_ERROR, Spawn}; pub use self::conch_runtime::env::*; pub use self::conch_runtime::error::*; pub use self::conch_runtime::eval::*; pub use self::conch_runtime::future::*; pub use self::conch_runtime::path::*; pub use self::conch_runtime::spawn::*; pub use self::futures::{Async, Future, Poll}; pub use self::tokio_core::reactor::Core; /// Poor man's mktmp. A macro for creating "unique" test directories. #[macro_export] macro_rules! mktmp { () => { mktmp_impl(concat!("test-", module_path!(), "-", line!(), "-", column!())) }; } pub fn mktmp_impl(path: &str) -> TempDir { if cfg!(windows) { TempDir::new(&path.replace(":", "_")).unwrap() } else { TempDir::new(path).unwrap() } } #[macro_export] macro_rules! test_cancel { ($future:expr) => { test_cancel!($future, ()) }; ($future:expr, $env:expr) => {{ ::support::test_cancel_impl($future, &mut $env); }}; } pub fn test_cancel_impl, E: ?Sized>(mut future: F, env: &mut E) { let _ = future.poll(env); // Give a chance to init things future.cancel(env); // Cancel the operation drop(future); } #[cfg(unix)] pub const DEV_NULL: &str = "/dev/null"; #[cfg(windows)] pub const DEV_NULL: &str = "NUL"; pub fn dev_null() -> Rc { let fdes = OpenOptions::new() .read(true) .write(true) .open(DEV_NULL) .unwrap() .into(); Rc::new(fdes) } #[derive(Debug, Clone, PartialEq, Eq)] pub enum MockErr { Fatal(bool), ExpansionError(ExpansionError), RedirectionError(Arc), CommandError(Arc), } impl self::conch_runtime::error::IsFatalError for MockErr { fn is_fatal(&self) -> bool { match *self { MockErr::Fatal(fatal) => fatal, MockErr::ExpansionError(ref e) => e.is_fatal(), MockErr::RedirectionError(ref e) => e.is_fatal(), MockErr::CommandError(ref e) => e.is_fatal(), } } } impl ::std::error::Error for MockErr { fn description(&self) -> &str { "mock error" } } impl ::std::fmt::Display for MockErr { fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { write!(fmt, "mock {}fatal error", if self.is_fatal() { "non-" } else { "" }) } } impl From for MockErr { fn from(err: RuntimeError) -> Self { MockErr::Fatal(err.is_fatal()) } } impl From for MockErr { fn from(err: ExpansionError) -> Self { MockErr::ExpansionError(err) } } impl From for MockErr { fn from(err: RedirectionError) -> Self { MockErr::RedirectionError(Arc::new(err)) } } impl From for MockErr { fn from(err: CommandError) -> Self { MockErr::CommandError(Arc::new(err)) } } impl From<::std::io::Error> for MockErr { fn from(_: ::std::io::Error) -> Self { MockErr::Fatal(false) } } impl From for MockErr { fn from(void: Void) -> Self { unreachable(void) } } #[derive(Debug, Clone, PartialEq, Eq)] #[must_use = "futures do nothing unless polled"] pub struct MustCancel { /// Did we get polled at least once (i.e. did we get fully "spawned") was_polled: bool, /// Did we ever get a "cancel" signal was_canceled: bool, } impl MustCancel { pub fn new() -> Self { MustCancel { was_polled: false, was_canceled: false, } } pub fn poll(&mut self) -> Poll { assert!(!self.was_canceled, "cannot poll after canceling"); self.was_polled = true; Ok(Async::NotReady) } pub fn cancel(&mut self) { assert!(!self.was_canceled, "cannot cancel twice"); self.was_canceled = true; } } impl Drop for MustCancel { fn drop(&mut self) { if self.was_polled { assert!(self.was_canceled, "MustCancel future was not canceled!"); } } } #[must_use = "futures do nothing unless polled"] #[derive(Debug, Clone, PartialEq, Eq)] pub enum MockCmd { Status(ExitStatus), Error(MockErr), Panic(&'static str), MustCancel(MustCancel), } pub fn mock_status(status: ExitStatus) -> MockCmd { MockCmd::Status(status) } pub fn mock_error(fatal: bool) -> MockCmd { MockCmd::Error(MockErr::Fatal(fatal)) } pub fn mock_panic(msg: &'static str) -> MockCmd { MockCmd::Panic(msg) } pub fn mock_must_cancel() -> MockCmd { MockCmd::MustCancel(MustCancel::new()) } impl Spawn for MockCmd { type Error = MockErr; type EnvFuture = Self; type Future = FutureResult; fn spawn(self, _: &E) -> Self::EnvFuture { self } } impl<'a, E: ?Sized> Spawn for &'a MockCmd { type Error = MockErr; type EnvFuture = MockCmd; type Future = FutureResult; fn spawn(self, _: &E) -> Self::EnvFuture { self.clone() } } impl EnvFuture for MockCmd { type Item = FutureResult; type Error = MockErr; fn poll(&mut self, _: &mut E) -> Poll { match *self { MockCmd::Status(s) => Ok(Async::Ready(future_result(Ok(s)))), MockCmd::Error(ref e) => Err(e.clone()), MockCmd::Panic(msg) => panic!("{}", msg), MockCmd::MustCancel(ref mut mc) => mc.poll(), } } fn cancel(&mut self, _env: &mut E) { match *self { MockCmd::Status(_) | MockCmd::Error(_) | MockCmd::Panic(_) => {}, MockCmd::MustCancel(ref mut mc) => mc.cancel(), } } } pub fn mock_word_fields(fields: Fields) -> MockWord { MockWord::Fields(Some(fields)) } pub fn mock_word_error(fatal: bool) -> MockWord { MockWord::Error(MockErr::Fatal(fatal)) } pub fn mock_word_must_cancel() -> MockWord { MockWord::MustCancel(MustCancel::new()) } pub fn mock_word_assert_cfg(cfg: WordEvalConfig) -> MockWord { MockWord::AssertCfg(cfg, None) } pub fn mock_word_assert_cfg_with_fields( fields: Fields, cfg: WordEvalConfig, ) -> MockWord { MockWord::AssertCfg(cfg, Some(fields)) } pub fn mock_word_panic(msg: &'static str) -> MockWord { MockWord::Panic(msg) } #[must_use = "futures do nothing unless polled"] #[derive(Debug, Clone, PartialEq, Eq)] pub enum MockWord { Fields(Option>), Error(MockErr), MustCancel(MustCancel), AssertCfg(WordEvalConfig, Option>), Panic(&'static str), } impl WordEval for MockWord { type EvalResult = String; type Error = MockErr; type EvalFuture = Self; fn eval_with_config(self, _: &E, cfg: WordEvalConfig) -> Self::EvalFuture { if let MockWord::AssertCfg(expected, _) = self { assert_eq!(expected, cfg); } self } } impl<'a, E: ?Sized> WordEval for &'a MockWord { type EvalResult = String; type Error = MockErr; type EvalFuture = MockWord; fn eval_with_config(self, _: &E, cfg: WordEvalConfig) -> Self::EvalFuture { if let MockWord::AssertCfg(ref expected, _) = *self { assert_eq!(*expected, cfg); } self.clone() } } impl EnvFuture for MockWord { type Item = Fields; type Error = MockErr; fn poll(&mut self, _: &mut E) -> Poll { match *self { MockWord::Fields(ref mut f) => Ok(Async::Ready(f.take().expect("polled twice"))), MockWord::Error(ref mut e) => Err(e.clone()), MockWord::MustCancel(ref mut mc) => mc.poll(), MockWord::AssertCfg(_, ref mut fields) => { let ret = fields.take().unwrap_or(Fields::Zero); Ok(Async::Ready(ret)) }, MockWord::Panic(msg) => panic!("{}", msg), } } fn cancel(&mut self, _: &mut E) { match *self { MockWord::Fields(_) | MockWord::Error(_) | MockWord::AssertCfg(_, _) => {}, MockWord::MustCancel(ref mut mc) => mc.cancel(), MockWord::Panic(msg) => panic!("{}", msg), } } } #[derive(Debug, Clone)] pub enum MockParam { FieldsWithName(Option>, String), Fields(Option>), Split(bool /* expect_split */, Fields), } impl ::std::fmt::Display for MockParam { fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { write!(fmt, "MockParam") } } impl ParamEval for MockParam { type EvalResult = String; fn eval(&self, split_fields_further: bool, _: &E) -> Option> { match *self { MockParam::Fields(ref f) | MockParam::FieldsWithName(ref f, _) => f.clone(), MockParam::Split(expect_split, ref f) => { assert_eq!(expect_split, split_fields_further); Some(f.clone()) }, } } fn assig_name(&self) -> Option { match *self { MockParam::Fields(_) | MockParam::Split(..) => None, MockParam::FieldsWithName(_, ref name) => Some(name.clone()), } } } #[derive(Debug, Clone)] pub enum MockOutCmd { Out(&'static str), Cmd(MockCmd), } impl Spawn for MockOutCmd where E: AsyncIoEnvironment + FileDescEnvironment, E::FileHandle: Clone + FileDescWrapper, E::WriteAll: 'static + Send + Sync, { type Error = MockErr; type EnvFuture = Self; type Future = Box<'static + Future + Send + Sync>; fn spawn(self, _: &E) -> Self::EnvFuture { self } } impl<'a, E: ?Sized> Spawn for &'a MockOutCmd where E: AsyncIoEnvironment + FileDescEnvironment, E::FileHandle: Clone + FileDescWrapper, E::WriteAll: 'static + Send + Sync, { type Error = MockErr; type EnvFuture = MockOutCmd; type Future = Box<'static + Future + Send + Sync>; fn spawn(self, _: &E) -> Self::EnvFuture { self.clone() } } impl EnvFuture for MockOutCmd where E: AsyncIoEnvironment + FileDescEnvironment, E::FileHandle: Clone + FileDescWrapper, E::WriteAll: 'static + Send + Sync, { type Item = Box<'static + Future + Send + Sync>; type Error = MockErr; fn poll(&mut self, env: &mut E) -> Poll { let msg = match *self { MockOutCmd::Out(ref m) => m, MockOutCmd::Cmd(ref mut c) => match c.poll(env) { Ok(Async::Ready(f)) => return Ok(Async::Ready(Box::new(f))), Ok(Async::NotReady) => return Ok(Async::NotReady), Err(e) => return Err(e), }, }; let fd = env.file_desc(STDOUT_FILENO) .expect("failed to get stdout") .0 .borrow() .duplicate() .expect("failed to duplicate stdout handle"); let future = env.write_all(fd, msg.as_bytes().into()) .then(|result| { result.expect("unexpected failure"); Ok(EXIT_SUCCESS) }); Ok(Async::Ready(Box::new(future))) } fn cancel(&mut self, env: &mut E) { match *self { MockOutCmd::Out(_) => {}, MockOutCmd::Cmd(ref mut c) => c.cancel(env), }; } } #[must_use = "futures do nothing unless polled"] #[derive(Debug, Clone)] pub enum MockRedirect { Action(Option>), MustCancel(MustCancel), Error(Option), } pub fn mock_redirect(action: RedirectAction) -> MockRedirect { MockRedirect::Action(Some(action)) } pub fn mock_redirect_must_cancel() -> MockRedirect { MockRedirect::MustCancel(MustCancel::new()) } pub fn mock_redirect_error(fatal: bool) -> MockRedirect { MockRedirect::Error(Some(MockErr::Fatal(fatal))) } impl RedirectEval for MockRedirect { type Handle = T; type Error = MockErr; type EvalFuture = Self; fn eval(self, _: &E) -> Self::EvalFuture { self } } impl<'a, T, E: ?Sized> RedirectEval for &'a MockRedirect where T: Clone, { type Handle = T; type Error = MockErr; type EvalFuture = MockRedirect; fn eval(self, _: &E) -> Self::EvalFuture { self.clone() } } impl EnvFuture for MockRedirect { type Item = RedirectAction; type Error = MockErr; fn poll(&mut self, _: &mut E) -> Poll { match *self { MockRedirect::Action(ref mut a) => Ok(Async::Ready(a.take().expect("polled twice"))), MockRedirect::MustCancel(ref mut mc) => mc.poll(), MockRedirect::Error(ref mut e) => Err(e.take().expect("polled twice")), } } fn cancel(&mut self, _: &mut E) { match *self { MockRedirect::Action(_) | MockRedirect::Error(_) => {}, MockRedirect::MustCancel(ref mut mc) => mc.cancel(), } } } pub fn new_env() -> (Core, DefaultEnvRc) { new_env_with_threads(1) } pub fn new_env_with_threads(threads: usize) -> (Core, DefaultEnvRc) { let lp = Core::new().expect("failed to create Core loop"); let env = DefaultEnvRc::new(lp.remote(), Some(threads)).expect("failed to create env"); (lp, env) } pub fn new_env_with_no_fds() -> (Core, DefaultEnvRc) { let lp = Core::new().expect("failed to create Core loop"); let mut cfg = DefaultEnvConfigRc::new(lp.remote(), Some(1)).expect("failed to create env cfg"); cfg.file_desc_env = FileDescEnv::new(); let env = DefaultEnvRc::with_config(cfg); (lp, env) } #[macro_export] macro_rules! run { ($cmd:expr) => {{ let (lp, env) = ::support::new_env(); run!($cmd, lp, env) }}; ($cmd:expr, $lp:expr, $env:expr) => {{ let mut lp = $lp; let env = $env; let cmd = $cmd; #[allow(deprecated)] let ret_ref = run(&cmd, env.sub_env(), &mut lp); #[allow(deprecated)] let ret = run(cmd, env, &mut lp); assert_eq!(ret_ref, ret); ret }}; } /// Spawns and syncronously runs the provided command to completion. #[deprecated(note = "use `run!` macro instead, to cover spawning T and &T")] pub fn run, E>(cmd: T, env: E, lp: &mut Core) -> Result { let future = cmd.spawn(&env) .pin_env(env) .flatten(); lp.run(future) } #[macro_export] macro_rules! run_cancel { ($cmd:expr) => {{ let cmd = $cmd; #[allow(deprecated)] let ret_ref = run_cancel(&cmd); #[allow(deprecated)] let ret = run_cancel(cmd); assert_eq!(ret_ref, ret); ret }} } /// Spawns the provided command and polls it a single time to give it a /// chance to get initialized. Then cancels and drops the future. /// /// It is up to the caller to set up the command in a way that failure to /// propagate cancel messages results in a panic. #[deprecated(note = "use `run!` macro instead, to cover spawning T and &T")] pub fn run_cancel>(cmd: T) { let (_, mut env) = new_env(); let env_future = cmd.spawn(&env); test_cancel_impl(env_future, &mut env); } #[macro_export] macro_rules! eval { ($word:expr, $cfg:expr) => { eval_with_thread_pool!($word, $cfg, 1) } } #[macro_export] macro_rules! eval_with_thread_pool { ($word:expr, $cfg:expr, $threads:expr) => {{ let word = $word; let cfg = $cfg; #[allow(deprecated)] let ret_ref = eval_word(&word, cfg, $threads); #[allow(deprecated)] let ret = eval_word(word, cfg, $threads); assert_eq!(ret_ref, ret); ret }} } /// Evaluates a word to completion. #[deprecated(note = "use `eval!` macro instead, to cover spawning T and &T")] pub fn eval_word>>(word: W, cfg: WordEvalConfig, threads: usize) -> Result, W::Error> { let mut lp = Core::new().expect("failed to create Core loop"); let env = DefaultEnv::::new(lp.remote(), Some(threads)).expect("failed to create env"); let future = word.eval_with_config(&env, cfg) .pin_env(env); lp.run(future) } pub fn bin_path(s: &str) -> ::std::path::PathBuf { let mut me = ::std::env::current_exe().unwrap(); me.pop(); if me.ends_with("deps") { me.pop(); } me.push(s); me }