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;
extern crate tokio_io;

use conch_runtime::io::Pipe;
use futures::future::{Future, lazy};
use std::borrow::Cow;
use std::env::current_dir;
use std::ffi::OsStr;
use tokio_core::reactor::Core;

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

const EXECUTABLE_WITH_IO_MSG: &str = "hello\nworld!\n";

#[test]
fn spawn_executable_with_io() {
    let mut lp = Core::new().unwrap();
    let mut env = ExecEnv::new(lp.remote());
    let mut io_env = PlatformSpecificAsyncIoEnv::new(lp.remote(), Some(3));

    let pipe_in = Pipe::new().unwrap();
    let pipe_out = Pipe::new().unwrap();
    let pipe_err = Pipe::new().unwrap();

    let bin_path = bin_path("cat-dup");

    let data = ExecutableData {
        name: Cow::Borrowed(OsStr::new(&bin_path)),
        args: vec!(),
        env_vars: vec!(),
        current_dir: Cow::Owned(current_dir().expect("failed to get current_dir")),
        stdin: Some(pipe_in.reader),
        stdout: Some(pipe_out.writer),
        stderr: Some(pipe_err.writer),
    };

    let pipe_in_writer = pipe_in.writer;
    let pipe_out_reader = pipe_out.reader;
    let pipe_err_reader = pipe_err.reader;

    let (status, (), (), ()) = lp.run(lazy(move || {
        let child = env.spawn_executable(data).expect("spawn failed");

        let stdin = io_env.write_all(pipe_in_writer, Vec::from(EXECUTABLE_WITH_IO_MSG.as_bytes()))
            .map_err(|e| panic!("stdin failed: {}", e));

        let stdout = tokio_io::io::read_to_end(io_env.read_async(pipe_out_reader), Vec::new())
            .map(|(_, msg)| assert_eq!(msg, EXECUTABLE_WITH_IO_MSG.as_bytes()))
            .map_err(|e| panic!("stdout failed: {}", e));

        let stderr = tokio_io::io::read_to_end(io_env.read_async(pipe_err_reader), Vec::new())
            .map(|(_, msg)| assert_eq!(msg, EXECUTABLE_WITH_IO_MSG.as_bytes()))
            .map_err(|e| panic!("stdout failed: {}", e));

        child.join4(stdin, stdout, stderr)
    })).expect("failed to run futures");
    assert!(status.success());
}

#[test]
fn env_vars_set_from_data_without_inheriting_from_process() {
    let mut lp = Core::new().unwrap();
    let mut env = ExecEnv::new(lp.remote());
    let mut io_env = PlatformSpecificAsyncIoEnv::new(lp.remote(), Some(1));

    let (status, ()) = lp.run(lazy(move || {
        let pipe_out = Pipe::new().unwrap();

        let bin_path = bin_path("env");
        let data = ExecutableData {
            name: Cow::Borrowed(OsStr::new(&bin_path)),
            args: vec!(),
            env_vars: vec!(
                (Cow::Borrowed(OsStr::new("foo")), Cow::Borrowed(OsStr::new("bar"))),
                (Cow::Borrowed(OsStr::new("PATH")), Cow::Borrowed(OsStr::new("qux"))),
            ),
            current_dir: Cow::Owned(current_dir().expect("failed to get current_dir")),
            stdin: None,
            stdout: Some(pipe_out.writer),
            stderr: None,
        };

        let child = env.spawn_executable(data).expect("spawn failed");

        let stdout = tokio_io::io::read_to_end(io_env.read_async(pipe_out.reader), Vec::new())
            .map(|(_, msg)| {
                if cfg!(windows) {
                    assert_eq!(msg, b"FOO=bar\nPATH=qux\n")
                } else {
                    assert_eq!(msg, b"PATH=qux\nfoo=bar\n")
                }
            })
            .map_err(|e| panic!("stdout failed: {}", e));

        child.join(stdout)
    })).expect("failed to run futures");
    assert!(status.success());
}

#[test]
fn remote_spawn_smoke() {
    let mut lp = Core::new().unwrap();
    let mut env = ExecEnv::new(lp.remote());

    let bin_path = bin_path("env");

    let data = ExecutableData {
        name: Cow::Borrowed(OsStr::new(&bin_path)),
        args: vec!(),
        env_vars: vec!(),
        current_dir: Cow::Owned(current_dir().expect("failed to get current_dir")),
        stdin: None,
        stdout: None,
        stderr: None,
    };

    // Spawning when not running in a task is the same as spawning
    // a future in a separate thread than the loop that's running.
    let child = env.spawn_executable(data).expect("spawn failed");
    let status = lp.run(child).expect("failed to run child");

    assert!(status.success());
}

#[test]
fn defines_empty_path_env_var_if_not_provided_by_caller() {
    let mut lp = Core::new().unwrap();
    let mut env = ExecEnv::new(lp.remote());
    let mut io_env = PlatformSpecificAsyncIoEnv::new(lp.remote(), Some(1));

    let (status, ()) = lp.run(lazy(move || {
        let pipe_out = Pipe::new().unwrap();

        let bin_path = bin_path("env");
        let data = ExecutableData {
            name: Cow::Borrowed(OsStr::new(&bin_path)),
            args: vec!(),
            env_vars: vec!(),
            current_dir: Cow::Owned(current_dir().expect("failed to get current_dir")),
            stdin: None,
            stdout: Some(pipe_out.writer),
            stderr: None,
        };

        let child = env.spawn_executable(data).expect("spawn failed");

        let stdout = tokio_io::io::read_to_end(io_env.read_async(pipe_out.reader), Vec::new())
            .map(|(_, msg)| assert_eq!(msg, b"PATH=\n"))
            .map_err(|e| panic!("stdout failed: {}", e));

        child.join(stdout)
    })).expect("failed to run futures");
    assert!(status.success());
}