yog 0.0.1

yog: a balls-oriented session manager for lernie loops (egui frontend)
Documentation
//! Construction, binary resolution, argv passing, happy-path streaming,
//! and `run_in` cwd propagation: `Cli::new`/`resolve`/`binary` and
//! `run`/`run_in` returning stdout, stderr, exit code, and a live pid.

use super::*;
use std::ffi::OsString;
use tempfile::tempdir;

#[test]
fn new_stores_binary_path() {
    let cli = Cli::new("/usr/local/bin/lernie");
    assert_eq!(cli.binary(), Path::new("/usr/local/bin/lernie"));
}

/// Resolve `binary` against an injected env lookup — no ambient-env
/// mutation, so no `unsafe std::env::set_var`. `set = Some(v)` models the
/// override var present with value `v` (an empty `v` exercises the "set but
/// empty → default" branch); `None` models the var absent.
fn resolve_injected(binary: Binary, set: Option<&str>) -> Cli {
    let value = set.map(OsString::from);
    Cli::resolve_with(binary, move |_| value.clone())
}

#[test]
fn resolve_reads_ambient_env() {
    // Covers the production `std::env`-backed lookup that `resolve` wires
    // into `resolve_with`. The resolved path depends on the ambient env, so
    // we assert only that a non-empty binary is produced; the injected-
    // lookup cases below pin each resolution branch deterministically.
    let cli = Cli::resolve(Binary::Lernie);
    assert!(!cli.binary().as_os_str().is_empty());
}

#[test]
fn resolve_lernie_uses_env_var_when_set() {
    let cli = resolve_injected(Binary::Lernie, Some("/opt/lernie-test"));
    assert_eq!(cli.binary(), Path::new("/opt/lernie-test"));
}

#[test]
fn resolve_lernie_falls_back_to_default_when_env_empty() {
    let cli = resolve_injected(Binary::Lernie, Some(""));
    assert_eq!(cli.binary(), Path::new("lernie"));
}

#[test]
fn resolve_lernie_falls_back_to_default_when_env_unset() {
    let cli = resolve_injected(Binary::Lernie, None);
    assert_eq!(cli.binary(), Path::new("lernie"));
}

#[test]
fn resolve_bl_uses_env_var_when_set() {
    let cli = resolve_injected(Binary::Bl, Some("/opt/bl-test"));
    assert_eq!(cli.binary(), Path::new("/opt/bl-test"));
}

#[test]
fn resolve_bl_falls_back_to_default_when_env_unset() {
    let cli = resolve_injected(Binary::Bl, None);
    assert_eq!(cli.binary(), Path::new("bl"));
}

#[test]
fn resolve_bz_uses_env_var_when_set() {
    let cli = resolve_injected(Binary::Bz, Some("/opt/bz-test"));
    assert_eq!(cli.binary(), Path::new("/opt/bz-test"));
}

#[test]
fn resolve_bz_falls_back_to_default_when_env_unset() {
    let cli = resolve_injected(Binary::Bz, None);
    assert_eq!(cli.binary(), Path::new("bz"));
}

#[test]
fn run_streams_stdout_and_reports_exit_zero() {
    let dir = tempdir().unwrap();
    let (bin, _spawn_guard) = write_script(
        dir.path(),
        "fake_lernie",
        "#!/bin/sh\nprintf 'hello out\\n'\nexit 0\n",
    );
    let cli = Cli::new(bin);
    let stream = cli.run(&[]).unwrap();
    let (out, err, exit) = collect(stream);
    assert_eq!(out, b"hello out\n");
    assert!(err.is_empty());
    assert_eq!(exit, ExitInfo::Code(0));
}

#[test]
fn run_streams_stderr_and_propagates_nonzero_exit() {
    let dir = tempdir().unwrap();
    let (bin, _spawn_guard) = write_script(
        dir.path(),
        "fake_lernie",
        "#!/bin/sh\nprintf 'boom\\n' 1>&2\nexit 7\n",
    );
    let cli = Cli::new(bin);
    let (out, err, exit) = collect(cli.run(&["some", "args"]).unwrap());
    assert!(out.is_empty());
    assert_eq!(err, b"boom\n");
    assert_eq!(exit, ExitInfo::Code(7));
}

#[test]
fn pid_is_available_while_running() {
    let dir = tempdir().unwrap();
    let (bin, _spawn_guard) = write_script(dir.path(), "fake_lernie", "#!/bin/sh\nexit 0\n");
    let cli = Cli::new(bin);
    let stream = cli.run(&[]).unwrap();
    let pid = stream.pid();
    assert!(pid.is_some());
    drop(stream);
}

#[test]
fn run_env_sets_child_environment_variables() {
    let dir = tempdir().unwrap();
    // Echo the two vars the config-edit drive sets (§9.3), one per line, so
    // the child's environment is observable in the stream.
    let (bin, _spawn_guard) = write_script(
        dir.path(),
        "fake_lernie",
        "#!/bin/sh\nprintf '%s\\n%s\\n' \"$EDITOR\" \"$YOG_EDIT_SRC\"\n",
    );
    let cli = Cli::new(bin);
    let env = [
        ("EDITOR", "/x/yog --editor-apply"),
        ("YOG_EDIT_SRC", "/s/n"),
    ];
    let (out, err, exit) = collect(cli.run_env(&env, &["config"]).unwrap());
    assert_eq!(exit, ExitInfo::Code(0));
    assert!(err.is_empty());
    assert_eq!(
        String::from_utf8(out).unwrap(),
        "/x/yog --editor-apply\n/s/n\n"
    );
}

#[test]
fn run_in_sets_child_working_directory() {
    let dir = tempdir().unwrap();
    // Report the physical cwd so the child's `current_dir` is observable
    // in the stream.
    let (bin, _spawn_guard) = write_script(dir.path(), "fake_lernie", "#!/bin/sh\npwd -P\n");
    let cli = Cli::new(bin);
    let (out, err, exit) = collect(cli.run_in(dir.path(), &[]).unwrap());
    assert_eq!(exit, ExitInfo::Code(0));
    assert!(err.is_empty());
    let reported = String::from_utf8(out).unwrap();
    assert_eq!(
        std::fs::canonicalize(reported.trim()).unwrap(),
        std::fs::canonicalize(dir.path()).unwrap(),
    );
}