#![cfg(all(windows, feature = "blocking"))]
pub mod helpers;
use std::io::Read;
use std::time::{Duration, Instant};
use conpty_oxide::blocking::Command;
use helpers::sync::Session;
use helpers::{strip_escapes, with_timeout};
const BUDGET: Duration = Duration::from_secs(30);
const WAIT_BUDGET: Duration = Duration::from_secs(2);
fn reading_past_the_child_exit_reaches_eof_in() {
const MARKER: &str = "conpty-oxide-eof-marker";
let parts = Command::new("cmd.exe")
.args(["/c", "echo", MARKER])
.spawn()
.expect("spawning must succeed")
.into_parts();
let mut child = parts.child;
let mut reader = parts.output;
let writer = parts.input;
let controller = parts.controller;
let status = child.wait().expect("waiting must succeed");
assert!(status.success(), "unexpected status: {status}");
let mut collected = Vec::new();
let mut chunk = [0_u8; 4096];
let reads_to_eof = loop {
let read = reader.read(&mut chunk).expect("reading must not fail");
if read == 0 {
break true;
}
collected.extend_from_slice(&chunk[..read]);
};
assert!(reads_to_eof);
assert_eq!(
reader
.read(&mut chunk)
.expect("a read after EOF must not fail"),
0,
"end-of-file must be reported for every subsequent read"
);
let text = strip_escapes(&String::from_utf8_lossy(&collected));
assert!(
text.contains(MARKER),
"output written before exit was lost: {text:?}"
);
drop(writer);
drop(controller);
}
#[test]
fn reading_past_the_child_exit_reaches_end_of_file() {
with_timeout(BUDGET, || {
reading_past_the_child_exit_reaches_eof_in();
});
}
#[test]
fn waiting_for_a_short_child_returns_promptly() {
with_timeout(BUDGET, || {
let mut session = Session::start(Command::new("cmd.exe").args(["/c", "echo", "prompt"]));
let started = Instant::now();
let status = session.child.wait().expect("waiting must succeed");
let elapsed = started.elapsed();
assert!(status.success(), "unexpected status: {status}");
assert!(
elapsed < WAIT_BUDGET,
"waiting for a child that exits immediately took {elapsed:?}, \
which is over the {WAIT_BUDGET:?} budget"
);
let (_output, again) = session.finish();
assert_eq!(again, status, "the exit status must remain cached");
});
}
#[test]
fn the_output_written_before_exit_survives_the_shutdown() {
with_timeout(BUDGET, || {
const LINES: u32 = 15;
let (output, status) = Session::start(
Command::new("cmd.exe")
.raw_arg("/c for /l %i in (1,1,15) do @echo conpty-oxide-line-%i-end"),
)
.finish();
assert!(status.success(), "unexpected status: {status}");
for line in 1..=LINES {
let marker = format!("conpty-oxide-line-{line}-end");
assert!(
output.contains(&marker),
"{marker:?} is missing, so the session ended before the reader \
had drained the output: {output:?}"
);
}
});
}