mxsh 0.1.0

Embeddable POSIX-style shell parser and runtime
Documentation
#![cfg(all(feature = "runtime", feature = "test-support"))]

use std::io;
use std::path::{Path, PathBuf};

use mxsh::runtime::Runtime;
use mxsh::runtime::fd::FileDescriptor;
use mxsh::runtime::process::{
    ExternalCommand, ProcessEvent, ProcessHandle, RuntimeSignal, SpawnMode, SpawnStdio,
};
use mxsh::runtime::testing::{DeterministicRuntime, InMemoryRuntime};

fn fork_runtime<R: Runtime + Sized>(runtime: &R) -> R {
    runtime.fork().expect("runtime fork should succeed")
}

fn resolve_program<R: Runtime>(runtime: &R, program: &str) -> PathBuf {
    runtime
        .resolve_command_path(program, "/usr/bin:/bin")
        .expect("runtime path resolution should succeed")
}

fn run_command<R: Runtime>(runtime: &mut R, command: ExternalCommand) -> i32 {
    let child = runtime
        .spawn_external_command(&command, SpawnStdio::default(), &[], SpawnMode::Foreground)
        .expect("runtime spawn should succeed");
    runtime.wait_child(child.handle)
}

fn signal_process<R: Runtime>(runtime: &mut R) {
    runtime
        .signal_process_group(ProcessHandle::new(1), RuntimeSignal::Continue)
        .expect("runtime signaling should succeed");
}

fn claim_and_release_foreground<R: Runtime>(runtime: &mut R) {
    let guard = runtime
        .claim_foreground(ProcessHandle::new(1), FileDescriptor::STDIN)
        .expect("foreground claim should succeed");
    runtime
        .release_foreground(guard)
        .expect("foreground release should succeed");
}

fn exec_is_explicit<R: Runtime>(runtime: &R) -> io::ErrorKind {
    runtime
        .exec_replace("echo", &["echo".to_string()], &[], Path::new("/"))
        .expect_err("in-memory runtime should not support exec")
        .kind()
}

#[test]
fn public_runtime_capabilities_are_composable() {
    let mut runtime = InMemoryRuntime::new();
    runtime.register_command("emit", |_argv, _env, _cwd, _stdio| 7);

    let forked = fork_runtime(&runtime);
    assert_eq!(resolve_program(&forked, "emit"), PathBuf::from("emit"));

    let status = run_command(
        &mut runtime,
        ExternalCommand {
            program: "emit".to_string(),
            argv: vec!["emit".to_string()],
            env: Vec::new(),
            cwd: PathBuf::from("/"),
            create_process_group: false,
            passed_fds: Vec::new(),
        },
    );
    assert_eq!(status, 7);

    signal_process(&mut runtime);
    claim_and_release_foreground(&mut runtime);
    assert_eq!(exec_is_explicit(&runtime), io::ErrorKind::Unsupported);
}

#[test]
fn deterministic_runtime_fork_does_not_clone_queued_spawns() {
    let mut runtime = DeterministicRuntime::new();
    runtime.push_spawn(Some(42), [], [ProcessEvent::Exited(0)]);

    let mut forked = runtime.fork().expect("runtime fork should succeed");
    let err = forked
        .spawn_external_command(
            &ExternalCommand {
                program: "echo".to_string(),
                argv: vec!["echo".to_string()],
                env: Vec::new(),
                cwd: PathBuf::from("/"),
                create_process_group: false,
                passed_fds: Vec::new(),
            },
            SpawnStdio::default(),
            &[],
            SpawnMode::Foreground,
        )
        .expect_err("forked runtime should start with no queued spawns");
    assert_eq!(err.kind(), io::ErrorKind::NotFound);
}