#![allow(clippy::unwrap_used, clippy::expect_used)]
#![cfg(feature = "localfs")]
use std::time::Duration;
use kaish_kernel::{pipe_stream_default, ExecuteOptions, Kernel, KernelConfig};
fn kernel() -> Kernel {
Kernel::new(KernelConfig::repl().with_trash(false))
.expect("failed to create kernel")
}
#[tokio::test]
async fn lazy_stdin_does_not_block_a_command_that_never_reads() {
let kernel = kernel();
let (writer, reader) = pipe_stream_default();
let fut = kernel.execute_with_pipe_stdin("echo hi", ExecuteOptions::new(), reader);
let result = tokio::time::timeout(Duration::from_secs(5), fut)
.await
.expect("echo must not block on unread, never-closed stdin")
.expect("kernel execute");
assert_eq!(result.text_out(), "hi\n");
drop(writer);
}
#[tokio::test]
async fn lazy_stdin_feeds_a_reading_command() {
let kernel = kernel();
let (writer, reader) = pipe_stream_default();
writer.write_bytes(b"3\n1\n2\n").await.unwrap();
drop(writer);
let result = kernel
.execute_with_pipe_stdin("sort", ExecuteOptions::new(), reader)
.await
.expect("kernel execute");
assert_eq!(result.text_out(), "1\n2\n3\n");
}
#[tokio::test]
async fn lazy_stdin_feeds_first_stage_of_a_pipeline() {
let kernel = kernel();
let (writer, reader) = pipe_stream_default();
writer.write_bytes(b"b\na\nc\n").await.unwrap();
drop(writer);
let result = kernel
.execute_with_pipe_stdin("sort | head -n 2", ExecuteOptions::new(), reader)
.await
.expect("kernel execute");
assert_eq!(result.text_out(), "a\nb\n");
}
#[tokio::test]
async fn redirect_beats_lazy_stdin() {
let kernel = kernel();
let (writer, reader) = pipe_stream_default();
let fut = kernel.execute_with_pipe_stdin("cat <<< wins", ExecuteOptions::new(), reader);
let result = tokio::time::timeout(Duration::from_secs(5), fut)
.await
.expect("redirect path must not touch the live pipe")
.expect("kernel execute");
assert_eq!(result.text_out(), "wins\n");
drop(writer);
}
#[cfg(all(target_os = "linux", feature = "subprocess"))]
#[tokio::test]
async fn lazy_stdin_feeds_an_external_command() {
let kernel = Kernel::new(
KernelConfig::repl()
.with_trash(false)
.with_allow_external_commands(true),
)
.expect("failed to create kernel");
let (writer, reader) = pipe_stream_default();
writer.write_bytes(b"external sees this\n").await.unwrap();
drop(writer);
let result = kernel
.execute_with_pipe_stdin("/bin/cat", ExecuteOptions::new(), reader)
.await
.expect("kernel execute");
assert_eq!(result.text_out(), "external sees this\n");
}
#[tokio::test]
async fn lazy_stdin_feeds_scatter_with_no_pre_scatter_command() {
let kernel = kernel();
let (writer, reader) = pipe_stream_default();
writer.write_bytes(b"a\nb\nc\n").await.unwrap();
drop(writer);
let result = kernel
.execute_with_pipe_stdin(
r#"scatter | echo "loud-${ITEM}" | gather"#,
ExecuteOptions::new(),
reader,
)
.await
.expect("kernel execute");
assert!(result.ok(), "scatter failed: {}", result.err);
let out = result.text_out();
assert!(out.contains("loud-a"), "missing loud-a: {out:?}");
assert!(out.contains("loud-b"), "missing loud-b: {out:?}");
assert!(out.contains("loud-c"), "missing loud-c: {out:?}");
}
#[tokio::test]
async fn lazy_stdin_reaches_a_builtin_that_reads_the_stdin_field() {
let kernel = kernel();
let (writer, reader) = pipe_stream_default();
writer.write_bytes(b"echo hi | grep x\n").await.unwrap();
drop(writer);
let result = kernel
.execute_with_pipe_stdin("kaish-validate", ExecuteOptions::new(), reader)
.await
.expect("kernel execute");
assert!(result.ok(), "validate should see piped stdin: {}", result.err);
assert!(
result.text_out().contains("valid"),
"expected validation of piped script, got: {:?}",
result.text_out()
);
}
#[tokio::test]
async fn head_rejects_non_utf8_pipe_stdin_loudly() {
let kernel = kernel();
let (writer, reader) = pipe_stream_default();
writer.write_bytes(&[0xff, 0xfe, b'\n']).await.unwrap();
drop(writer);
let result = kernel
.execute_with_pipe_stdin("head", ExecuteOptions::new(), reader)
.await
.expect("kernel execute");
assert!(!result.ok(), "binary stdin must error, got out={:?}", result.text_out());
assert!(result.text_out().is_empty(), "no lossy output: {:?}", result.text_out());
}
#[tokio::test]
async fn lazy_stdin_does_not_leak_between_calls() {
let kernel = kernel();
let (writer, reader) = pipe_stream_default();
writer.write_bytes(b"first\n").await.unwrap();
drop(writer);
let first = kernel
.execute_with_pipe_stdin("cat", ExecuteOptions::new(), reader)
.await
.expect("first execute");
assert_eq!(first.text_out(), "first\n");
let second = kernel.execute("echo done").await.expect("second execute");
assert_eq!(second.text_out(), "done\n", "prior pipe must not bleed into a later call");
}