conch-runtime 0.1.2

A library for evaluating/executing programs written in the shell programming language.
Documentation
extern crate conch_runtime;
extern crate futures;
extern crate tokio_core;

use conch_runtime::io::FileDesc;
use conch_runtime::spawn::{BoxSpawnEnvFuture, BoxStatusFuture, function};
use futures::future::poll_fn;
use std::rc::Rc;
use tokio_core::reactor::Core;

#[macro_use]
mod support;
pub use self::support::*;

type TestEnv = Env<
    ArgsEnv<String>,
    PlatformSpecificAsyncIoEnv,
    FileDescEnv<Rc<FileDesc>>,
    LastStatusEnv,
    VarEnv<String, String>,
    ExecEnv,
    VirtualWorkingDirEnv,
    String,
    MockErr,
>;

fn new_test_env() -> (Core, TestEnv) {
    let lp = Core::new().expect("failed to create Core loop");
    let env = Env::with_config(DefaultEnvConfig::new(lp.remote(), Some(1))
        .expect("failed to create test env")
        .change_file_desc_env(FileDescEnv::new())
        .change_var_env(VarEnv::new())
        .change_fn_error::<MockErr>()
    );

    (lp, env)
}

#[test]
fn should_restore_args_after_completion() {
    let (mut lp, mut env) = new_test_env();

    let exit = ExitStatus::Code(42);
    let fn_name = "fn_name".to_owned();
    assert!(function(&fn_name, vec!(), &env).is_none());
    env.set_function(fn_name.clone(), Rc::new(mock_status(exit)));

    let args = Rc::new(vec!("foo".to_owned(), "bar".to_owned()));
    env.set_args(args.clone());

    let mut future = function(&fn_name, vec!("qux".to_owned()), &env)
        .expect("failed to find function");
    let next = lp.run(poll_fn(|| future.poll(&mut env))).expect("env future failed");
    assert_eq!(lp.run(next), Ok(exit));

    assert_eq!(env.args(), &**args);
}

#[test]
fn should_propagate_errors_and_restore_args() {
    let (mut lp, mut env) = new_test_env();

    let fn_name = "fn_name".to_owned();
    env.set_function(fn_name.clone(), Rc::new(mock_error(false)));

    let args = Rc::new(vec!("foo".to_owned(), "bar".to_owned()));
    env.set_args(args.clone());

    let mut future = function(&fn_name, vec!("qux".to_owned()), &env)
        .expect("failed to find function");
    match lp.run(poll_fn(|| future.poll(&mut env))) {
        Ok(_) => panic!("unexpected success"),
        Err(e) => assert_eq!(e, MockErr::Fatal(false)),
    }

    assert_eq!(env.args(), &**args);
}

#[test]
fn should_propagate_cancel_and_restore_args() {
    let (_lp, mut env) = new_test_env();

    let fn_name = "fn_name".to_owned();
    env.set_function(fn_name.clone(), Rc::new(mock_must_cancel()));

    let args = Rc::new(vec!("foo".to_owned(), "bar".to_owned()));
    env.set_args(args.clone());

    let future = function(&fn_name, vec!("qux".to_owned()), &env)
        .expect("failed to find function");
    test_cancel!(future, env);

    assert_eq!(env.args(), &**args);
}

struct MockFnRecursive<F> {
    callback: F,
}

impl<F> MockFnRecursive<F> {
    fn new(f: F) -> Rc<Self> where F: Fn(&TestEnv) -> BoxSpawnEnvFuture<'static, TestEnv, MockErr> {
        Rc::new(MockFnRecursive {
            callback: f
        })
    }
}

impl<'a, F> Spawn<TestEnv> for &'a MockFnRecursive<F>
    where F: Fn(&TestEnv) -> BoxSpawnEnvFuture<'static, TestEnv, MockErr>
{
    type EnvFuture = BoxSpawnEnvFuture<'static, TestEnv, Self::Error>;
    type Future = BoxStatusFuture<'static, Self::Error>;
    type Error = MockErr;

    fn spawn(self, env: &TestEnv) -> Self::EnvFuture {
        (self.callback)(env)
    }
}

#[test]
fn test_env_run_function_nested_calls_do_not_destroy_upper_args() {
    let exit = ExitStatus::Code(42);
    let fn_name = "fn name".to_owned();
    let (mut lp, mut env) = new_test_env();

    let depth = {
        let num_calls = 3usize;
        let depth = Rc::new(::std::cell::Cell::new(num_calls));
        let depth_copy = depth.clone();
        let fn_name = fn_name.clone();

        env.set_function(fn_name.clone(), MockFnRecursive::new(move |env| {
            let num_calls = depth.get().saturating_sub(1);
            depth.set(num_calls);

            if num_calls <= 0 {
                Box::new(Rc::new(mock_status(exit)).spawn(env))
            } else {
                let cur_args: Vec<_> = env.args().iter().cloned().collect();

                let mut next_args = cur_args.clone();
                next_args.reverse();
                next_args.push(format!("arg{}", num_calls));

                Box::new(function(&fn_name, next_args, env)
                    .expect("failed to find function"))
            }
        }));

        depth_copy
    };

    let args = Rc::new(vec!("foo".to_owned(), "bar".to_owned()));
    env.set_args(args.clone());

    let mut future = function(&fn_name, vec!("qux".to_owned()), &env)
        .expect("failed to find function");
    let next = lp.run(poll_fn(|| future.poll(&mut env))).expect("env future failed");
    assert_eq!(lp.run(next), Ok(exit));

    assert_eq!(env.args(), &**args);
    assert_eq!(depth.get(), 0);
}