fusible 0.14.1

Electronic linter: a fuse that blows to block firmware code generation
use std::process::Command;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};

use super::run_exec::{execute, MAX_LINE};

fn shell(script: &str) -> Command {
    let mut command = Command::new("sh");
    command.args(["-c", script]);
    command
}

fn run(script: &str, timeout: Duration) -> (Option<i32>, Vec<String>, Duration) {
    let lines = Arc::new(Mutex::new(Vec::new()));
    let sink = lines.clone();
    let start = Instant::now();
    let code = execute(shell(script), timeout, move |line| {
        sink.lock().unwrap().push(line.to_string());
    });
    let lines = lines.lock().unwrap().clone();
    (code, lines, start.elapsed())
}

#[test]
fn non_utf8_output_is_kept_lossily_and_does_not_stop_the_drain() {
    let script = r"printf '\377\376 bad\n'; yes filler | head -n 60000; echo done";
    let (code, lines, took) = run(script, Duration::from_secs(20));
    assert_eq!(code, Some(0));
    assert!(took < Duration::from_secs(15), "took {took:?}");
    assert_eq!(lines.first().unwrap(), "\u{fffd}\u{fffd} bad");
    assert_eq!(lines.last().unwrap(), "done");
    assert_eq!(lines.len(), 60_002);
}

#[test]
fn a_line_without_a_newline_is_cut_and_the_stream_goes_on() {
    let script = "head -c 100000 /dev/zero | tr '\\0' a; echo; echo next";
    let (code, lines, _) = run(script, Duration::from_secs(20));
    assert_eq!(code, Some(0));
    assert_eq!(lines[0].len(), MAX_LINE);
    assert_eq!(lines[1], "next");
    assert_eq!(lines.len(), 2);
}

#[test]
fn a_command_that_runs_too_long_is_killed_and_reported() {
    let (code, lines, took) = run("sleep 30", Duration::from_millis(300));
    assert_eq!(code, None);
    assert!(took < Duration::from_secs(5), "took {took:?}");
    assert!(lines.last().unwrap().starts_with("stopped after"));
}

#[test]
fn a_grandchild_holding_the_pipe_cannot_wedge_the_run() {
    let (code, lines, took) = run("sleep 4 & echo started", Duration::from_secs(20));
    assert_eq!(code, Some(0));
    assert!(took < Duration::from_secs(3), "took {took:?}");
    assert_eq!(lines, ["started"]);
}

#[test]
fn a_missing_program_is_reported_without_a_code() {
    let lines = Arc::new(Mutex::new(Vec::new()));
    let sink = lines.clone();
    let code = execute(
        Command::new("/nonexistent/fusible"),
        Duration::from_secs(1),
        move |line| sink.lock().unwrap().push(line.to_string()),
    );
    assert_eq!(code, None);
    assert!(lines.lock().unwrap()[0].contains("cannot start"));
}