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;

use conch_runtime::RefCounted;
use conch_runtime::spawn::{for_args, for_loop, for_with_args};
use futures::future::{FutureResult, ok};
use std::rc::Rc;

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

macro_rules! run_env {
    ($future:expr, $env:expr, $lp:expr) => {{
        $lp.run($future.pin_env($env.sub_env()).flatten())
    }}
}

const MOCK_EXIT: ExitStatus = ExitStatus::Code(42);
const VAR: &str = "var name";
const RESULT_VAR: &str = "resulting var name";

#[derive(Debug, Clone)]
struct MockCmd2;

impl<'a> Spawn<DefaultEnvRc> for &'a MockCmd2 {
    type Error = MockErr;
    type EnvFuture = Self;
    type Future = FutureResult<ExitStatus, Self::Error>;

    fn spawn(self, _: &DefaultEnvRc) -> Self::EnvFuture {
        self
    }
}

impl<'a> EnvFuture<DefaultEnvRc> for &'a MockCmd2 {
    type Item = FutureResult<ExitStatus, Self::Error>;
    type Error = MockErr;

    fn poll(&mut self, env: &mut DefaultEnvRc) -> Poll<Self::Item, Self::Error> {
        let result_var = RESULT_VAR.to_owned();
        let mut result_val = env.var(&result_var)
            .cloned()
            .unwrap_or_else(|| Rc::new(String::new()));

        if let Some(val) = env.var(&VAR.to_owned()) {
            result_val.make_mut().push_str(&**val);
        }

        env.set_var(Rc::new(result_var), result_val);
        Ok(Async::Ready(ok(MOCK_EXIT)))
    }

    fn cancel(&mut self, _env: &mut DefaultEnvRc) {
        unimplemented!()
    }
}

#[test]
fn should_run_with_appropriate_args() {
    let (mut lp, mut env) = new_env();
    env.set_args(Rc::new(vec!(
        Rc::new("arg_foo".to_owned()),
        Rc::new("arg_bar".to_owned()),
    )));

    let result_var = Rc::new(RESULT_VAR.to_owned());
    let name = Rc::new(VAR.to_owned());
    let vars_raw = vec!("raw_foo".to_owned(), "raw_bar".to_owned());
    let vars = mock_word_fields(Fields::Split(vars_raw.clone()));
    let cmd = MockCmd2;

    macro_rules! run_env_and_assert_var {
        ($future:expr, $env:expr, $value:expr) => {{
            let mut env = $env;
            env.unset_var(&result_var);

            let mut future = $future;
            let mut next_future = future.poll(&mut env);
            while let Ok(Async::NotReady) = next_future {
                next_future = future.poll(&mut env);
            }

            let next_future = match next_future.expect("did not resolve successfully") {
                Async::Ready(n) => n,
                Async::NotReady => unreachable!(),
            };

            let ret = lp.run(next_future);
            assert_eq!(ret, Ok(MOCK_EXIT));
            assert_eq!(&**env.var(&result_var).unwrap(), $value);
        }}
    }

    {
        let env = env.sub_env();

        let for_cmd = for_loop(
            name.clone(),
            Some(vec!(vars.clone())),
            vec!(&cmd),
            &env
        );
        run_env_and_assert_var!(for_cmd, env, "raw_fooraw_bar");
    }

    {
        let env = env.sub_env();

        let no_word: Option<Vec<MockWord>> = None;
        let for_cmd = for_loop(
            name.clone(),
            no_word,
            vec!(&cmd),
            &env
        );
        run_env_and_assert_var!(for_cmd, env, "arg_fooarg_bar");
    }

    {
        let env = env.sub_env();
        let for_cmd = for_args(name.clone(), vec!(&cmd), &env);
        run_env_and_assert_var!(for_cmd, env, "arg_fooarg_bar");
    }

    {
        let vars_raw = vars_raw.into_iter().map(Rc::new);
        let for_cmd = for_with_args(name.clone(), vars_raw, vec!(&cmd));
        run_env_and_assert_var!(for_cmd, env, "raw_fooraw_bar");
    }
}

#[test]
fn should_swallow_non_fatal_errors_in_body() {
    let (mut lp, mut env) = new_env();
    env.set_args(Rc::new(vec!(
        Rc::new("arg_foo".to_owned()),
        Rc::new("arg_bar".to_owned()),
    )));

    let name = Rc::new("name".to_owned());
    let vars = mock_word_fields(Fields::Single((*name).clone()));

    let non_fatal = mock_error(false);
    let cmd = mock_status(MOCK_EXIT);

    let for_cmd = for_loop(
        name.clone(),
        Some(vec!(vars.clone())),
        vec!(&non_fatal, &cmd),
        &env
    );
    assert_eq!(run_env!(for_cmd, env, lp), Ok(MOCK_EXIT));

    let no_word: Option<Vec<MockWord>> = None;
    let for_cmd = for_loop(
        name.clone(),
        no_word,
        vec!(&non_fatal, &cmd),
        &env
    );
    assert_eq!(run_env!(for_cmd, env, lp), Ok(MOCK_EXIT));

    let for_cmd = for_args(name.clone(), vec!(&non_fatal, &cmd), &env);
    assert_eq!(run_env!(for_cmd, env, lp), Ok(MOCK_EXIT));

    let for_cmd = for_with_args(name.clone(), vec!(name.clone()), vec!(&non_fatal, &cmd));
    assert_eq!(run_env!(for_cmd, env, lp), Ok(MOCK_EXIT));
}

#[test]
fn should_not_run_body_args_are_empty() {
    let (mut lp, mut env) = new_env();
    env.set_args(Rc::new(vec!()));

    let should_not_run = mock_panic("must not run");
    let name = Rc::new("name".to_owned());
    let vars = mock_word_fields(Fields::Zero);

    let for_cmd = for_loop(
        name.clone(),
        Some(vec!(vars.clone())),
        vec!(&should_not_run),
        &env
    );
    assert_eq!(run_env!(for_cmd, env, lp), Ok(EXIT_SUCCESS));

    let no_word: Option<Vec<MockWord>> = None;
    let for_cmd = for_loop(
        name.clone(),
        no_word,
        vec!(&should_not_run),
        &env
    );
    assert_eq!(run_env!(for_cmd, env, lp), Ok(EXIT_SUCCESS));

    let for_cmd = for_args(name.clone(), vec!(&should_not_run), &env);
    assert_eq!(run_env!(for_cmd, env, lp), Ok(EXIT_SUCCESS));

    let for_cmd = for_with_args(name.clone(), vec!(), vec!(&should_not_run));
    assert_eq!(run_env!(for_cmd, env, lp), Ok(EXIT_SUCCESS));
}

#[test]
fn should_propagate_all_word_errors() {
    let (mut lp, env) = new_env();

    let should_not_run = mock_panic("must not run");
    let name = Rc::new("name".to_owned());

    let for_cmd = for_loop(
        name.clone(),
        Some(vec!(mock_word_error(true))),
        vec!(&should_not_run),
        &env
    );
    assert_eq!(run_env!(for_cmd, env, lp), Err(MockErr::Fatal(true)));

    let for_cmd = for_loop(
        name.clone(),
        Some(vec!(mock_word_error(false))),
        vec!(&should_not_run),
        &env
    );
    assert_eq!(run_env!(for_cmd, env, lp), Err(MockErr::Fatal(false)));
}

#[test]
fn should_propagate_fatal_errors_in_body() {
    let (mut lp, mut env) = new_env();
    env.set_args(Rc::new(vec!(
        Rc::new("foo".to_owned()),
        Rc::new("bar".to_owned()),
    )));

    let name = Rc::new("name".to_owned());
    let vars_raw = vec!("foo".to_owned(), "bar".to_owned());
    let vars = mock_word_fields(Fields::Split(vars_raw.clone()));
    let fatal = mock_error(true);

    let for_cmd = for_loop(
        name.clone(),
        Some(vec!(vars.clone())),
        vec!(&fatal),
        &env
    );
    assert_eq!(run_env!(for_cmd, env, lp), Err(MockErr::Fatal(true)));

    let no_word: Option<Vec<MockWord>> = None;
    let for_cmd = for_loop(
        name.clone(),
        no_word,
        vec!(&fatal),
        &env
    );
    assert_eq!(run_env!(for_cmd, env, lp), Err(MockErr::Fatal(true)));

    let for_cmd = for_args(name.clone(), vec!(&fatal), &env);
    assert_eq!(run_env!(for_cmd, env, lp), Err(MockErr::Fatal(true)));

    let vars_raw = vars_raw.into_iter().map(Rc::new);
    let for_cmd = for_with_args(name.clone(), vars_raw, vec!(&fatal));
    assert_eq!(run_env!(for_cmd, env, lp), Err(MockErr::Fatal(true)));
}

#[test]
fn should_propagate_cancel() {
    let (_lp, mut env) = new_env();
    env.set_args(Rc::new(vec!(
        Rc::new("foo".to_owned()),
        Rc::new("bar".to_owned()),
    )));

    let name = Rc::new("name".to_owned());
    let vars_raw = vec!("foo".to_owned(), "bar".to_owned());
    let vars = mock_word_fields(Fields::Split(vars_raw.clone()));
    let should_not_run = mock_panic("must not run");
    let must_cancel = mock_must_cancel();

    let for_cmd = for_loop(
        name.clone(),
        Some(vec!(mock_word_must_cancel())),
        vec!(&should_not_run),
        &env
    );
    test_cancel!(for_cmd, env);

    let for_cmd = for_loop(
        name.clone(),
        Some(vec!(vars.clone())),
        vec!(&must_cancel),
        &env
    );
    test_cancel!(for_cmd, env);

    let for_cmd = for_args(name.clone(), vec!(&must_cancel), &env);
    test_cancel!(for_cmd, env);

    let vars_raw = vars_raw.into_iter().map(Rc::new);
    let for_cmd = for_with_args(name.clone(), vars_raw, vec!(&must_cancel));
    test_cancel!(for_cmd, env);
}