cloudfox-coreshift-core 1.2.37

Low-level Linux and Android systems primitives for CoreShift (CloudFox)
Documentation
use coreshift_core::inotify::decode_events;
use coreshift_core::spawn::{ExitStatus, SpawnBackend, SpawnOptions};
use std::time::Instant;

/// C1: the child keeps running after its pipes are drained (no capture at all
/// ⇒ `drain.is_done()` is immediately true). The old code called
/// `process.wait_blocking()` here and escaped the deadline entirely.
#[test]
fn test_spawn_timeout_no_capture_returns_quickly() {
    let start = Instant::now();
    let output = SpawnOptions::builder(
        vec!["/bin/sh".to_string(), "-c".to_string(), "sleep 30".to_string()],
        SpawnBackend::PosixSpawn,
    )
    .timeout_ms(200)
    .build()
    .unwrap()
    .run()
    .unwrap();

    assert!(
        output.timed_out,
        "a deadline must be reported as timed out, not block past it"
    );
    assert!(
        start.elapsed().as_millis() < 2000,
        "wait_loop escaped the deadline via wait_blocking (took {}ms)",
        start.elapsed().as_millis()
    );
    assert_eq!(output.status, Some(ExitStatus::Signaled(9)));
}

#[test]
fn test_spawn_echo_capture() {
    let output = SpawnOptions::builder(
        vec![
            "/bin/sh".to_string(),
            "-c".to_string(),
            "echo hello world".to_string(),
        ],
        SpawnBackend::PosixSpawn,
    )
    .capture_stdout()
    .build()
    .unwrap()
    .run()
    .unwrap();

    assert_eq!(output.status, Some(ExitStatus::Exited(0)));
    assert_eq!(
        String::from_utf8_lossy(&output.stdout).trim(),
        "hello world"
    );
}

#[test]
fn test_spawn_large_stdout() {
    // Generate ~100KB of output
    let script = "for i in $(seq 1 10000); do echo \"line $i\"; done";
    let output = SpawnOptions::builder(
        vec!["/bin/sh".to_string(), "-c".to_string(), script.to_string()],
        SpawnBackend::PosixSpawn,
    )
    .capture_stdout()
    .max_output(200_000)
    .build()
    .unwrap()
    .run()
    .unwrap();

    assert_eq!(output.status, Some(ExitStatus::Exited(0)));
    assert!(output.stdout.len() > 65536);
}

#[test]
fn test_inotify_decode_with_name() {
    let mut buf = Vec::new();
    let name = "test_file.txt";
    let name_bytes = name.as_bytes();
    let mut name_with_padding = name_bytes.to_vec();
    name_with_padding.push(0); // null terminator
    while !name_with_padding.len().is_multiple_of(8) {
        name_with_padding.push(0); // padding
    }
    let name_len = name_with_padding.len() as u32;

    // wd=1, mask=IN_MODIFY, cookie=0, len=name_len
    buf.extend_from_slice(&1i32.to_ne_bytes());
    buf.extend_from_slice(&libc::IN_MODIFY.to_ne_bytes());
    buf.extend_from_slice(&0u32.to_ne_bytes());
    buf.extend_from_slice(&name_len.to_ne_bytes());
    buf.extend_from_slice(&name_with_padding);

    let events = decode_events(&buf).unwrap();
    assert_eq!(events.len(), 1);
    assert_eq!(events[0].wd, 1);
    assert_eq!(events[0].name, Some(name.as_bytes().to_vec()));
}

#[test]
fn test_exec_context_many_args() {
    let mut args = vec![
        "/bin/sh".to_string(),
        "-c".to_string(),
        "echo $@".to_string(),
        "--".to_string(),
    ];
    for i in 0..100 {
        args.push(format!("arg{}", i));
    }

    let output = SpawnOptions::builder(args, SpawnBackend::PosixSpawn)
        .capture_stdout()
        .build()
        .unwrap()
        .run()
        .unwrap();

    assert_eq!(output.status, Some(ExitStatus::Exited(0)));
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("arg0"));
    assert!(stdout.contains("arg99"));
}

/// N4: the child exits but a background descendant keeps the stdout write end
/// open past the deadline ("wedged pipe"). The absolute deadline is
/// authoritative — spawn must return partial output instead of waiting for the
/// wedged pipe to close.
#[test]
fn test_spawn_timeout_wedged_pipe_partial_output() {
    let start = Instant::now();
    let output = SpawnOptions::builder(
        vec![
            "/bin/sh".to_string(),
            "-c".to_string(),
            "(sleep 1; echo late) & echo immediate; exit 0".to_string(),
        ],
        SpawnBackend::PosixSpawn,
    )
    .capture_stdout()
    .timeout_ms(200)
    .build()
    .unwrap()
    .run()
    .unwrap();

    assert!(
        output.timed_out,
        "wedged pipe with an elapsed deadline must report timed_out"
    );
    assert!(
        start.elapsed().as_millis() < 900,
        "wait_loop hung on the wedged pipe (took {}ms)",
        start.elapsed().as_millis()
    );
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(
        stdout.contains("immediate"),
        "partial output must contain the data drained before the kill"
    );
    assert!(
        !stdout.contains("late"),
        "must return partial output, not wait for the wedged pipe's late data"
    );
}