use std::time::Duration;
use silicube::isolate::IsolateBox;
use silicube::runner::{InteractiveEvent, InteractiveEventStream, Runner};
use silicube::types::ResourceLimits;
use super::{fixture_source, test_config};
#[tokio::test]
#[ignore = "requires root"]
async fn test_interactive_echo() {
let config = test_config();
let runner = Runner::new(config.clone());
let mut sandbox = IsolateBox::init(60, config.isolate_binary(), config.cgroup)
.await
.expect("Failed to create sandbox");
let source = fixture_source("echo.cpp");
let language = config.get_language("cpp17").expect("cpp17 not found");
let compile_result = runner
.compile(&sandbox, &source, language, None)
.await
.expect("Compilation failed");
assert!(compile_result.is_success());
let mut session = runner
.run_interactive(&sandbox, language, None)
.await
.expect("Failed to start interactive session");
session
.write_line("hello interactive")
.await
.expect("Failed to write");
let line = session
.read_line()
.await
.expect("Failed to read line")
.expect("Expected a line");
assert_eq!(line, "hello interactive");
session
.write_line("second line")
.await
.expect("Failed to write");
let line = session
.read_line()
.await
.expect("Failed to read line")
.expect("Expected a line");
assert_eq!(line, "second line");
session.close_stdin();
let result = session
.wait_timeout(Duration::from_secs(5))
.await
.expect("Wait failed");
assert!(result.is_success());
assert_eq!(result.exit_code, Some(0));
sandbox.cleanup().await.expect("Failed to cleanup");
}
#[tokio::test]
#[ignore = "requires root"]
async fn test_interactive_multi_turn() {
let config = test_config();
let runner = Runner::new(config.clone());
let mut sandbox = IsolateBox::init(61, config.isolate_binary(), config.cgroup)
.await
.expect("Failed to create sandbox");
let source = fixture_source("interactive_adder.cpp");
let language = config.get_language("cpp17").expect("cpp17 not found");
let compile_result = runner
.compile(&sandbox, &source, language, None)
.await
.expect("Compilation failed");
assert!(compile_result.is_success());
let mut session = runner
.run_interactive(&sandbox, language, None)
.await
.expect("Failed to start interactive session");
let test_cases = [(1, 2, 3), (10, 20, 30), (0, 0, 0), (-5, 15, 10)];
for (a, b, expected) in &test_cases {
session
.write_line(&format!("{} {}", a, b))
.await
.expect("Failed to write");
let line = session
.read_line()
.await
.expect("Failed to read line")
.expect("Expected a line");
assert_eq!(
line,
expected.to_string(),
"Expected {} + {} = {}, got {}",
a,
b,
expected,
line
);
}
session.close_stdin();
let result = session
.wait_timeout(Duration::from_secs(5))
.await
.expect("Wait failed");
assert!(result.is_success());
sandbox.cleanup().await.expect("Failed to cleanup");
}
#[tokio::test]
#[ignore = "requires root"]
async fn test_interactive_close_stdin_exits() {
let config = test_config();
let runner = Runner::new(config.clone());
let mut sandbox = IsolateBox::init(62, config.isolate_binary(), config.cgroup)
.await
.expect("Failed to create sandbox");
let source = fixture_source("echo.cpp");
let language = config.get_language("cpp17").expect("cpp17 not found");
let compile_result = runner
.compile(&sandbox, &source, language, None)
.await
.expect("Compilation failed");
assert!(compile_result.is_success());
let mut session = runner
.run_interactive(&sandbox, language, None)
.await
.expect("Failed to start interactive session");
session.close_stdin();
let result = session
.wait_timeout(Duration::from_secs(5))
.await
.expect("Wait failed");
assert!(result.is_success());
assert_eq!(result.exit_code, Some(0));
sandbox.cleanup().await.expect("Failed to cleanup");
}
#[tokio::test]
#[ignore = "requires root"]
async fn test_interactive_kill() {
let config = test_config();
let runner = Runner::new(config.clone());
let mut sandbox = IsolateBox::init(63, config.isolate_binary(), config.cgroup)
.await
.expect("Failed to create sandbox");
let source = fixture_source("infinite_loop.cpp");
let language = config.get_language("cpp17").expect("cpp17 not found");
let compile_result = runner
.compile(&sandbox, &source, language, None)
.await
.expect("Compilation failed");
assert!(compile_result.is_success());
let mut session = runner
.run_interactive(&sandbox, language, None)
.await
.expect("Failed to start interactive session");
assert!(!session.is_terminated());
session.kill().await.expect("Failed to kill session");
assert!(session.is_terminated());
sandbox.cleanup().await.expect("Failed to cleanup");
}
#[tokio::test]
#[ignore = "requires root"]
async fn test_interactive_wait_timeout_expires() {
let config = test_config();
let runner = Runner::new(config.clone());
let mut sandbox = IsolateBox::init(64, config.isolate_binary(), config.cgroup)
.await
.expect("Failed to create sandbox");
let source = fixture_source("infinite_loop.cpp");
let language = config.get_language("cpp17").expect("cpp17 not found");
let compile_result = runner
.compile(&sandbox, &source, language, None)
.await
.expect("Compilation failed");
assert!(compile_result.is_success());
let limits = ResourceLimits::new()
.with_time_limit(1.0)
.with_wall_time_limit(1.0);
let session = runner
.run_interactive(&sandbox, language, Some(&limits))
.await
.expect("Failed to start interactive session");
let result = session.wait_timeout(Duration::from_millis(100)).await;
assert!(result.is_err());
tokio::time::sleep(Duration::from_secs(3)).await;
sandbox.cleanup().await.expect("Failed to cleanup");
}
#[tokio::test]
#[ignore = "requires root"]
async fn test_interactive_write_after_terminated() {
let config = test_config();
let runner = Runner::new(config.clone());
let mut sandbox = IsolateBox::init(65, config.isolate_binary(), config.cgroup)
.await
.expect("Failed to create sandbox");
let source = fixture_source("hello.cpp");
let language = config.get_language("cpp17").expect("cpp17 not found");
let compile_result = runner
.compile(&sandbox, &source, language, None)
.await
.expect("Compilation failed");
assert!(compile_result.is_success());
let mut session = runner
.run_interactive(&sandbox, language, None)
.await
.expect("Failed to start interactive session");
tokio::time::sleep(Duration::from_millis(500)).await;
let result = session.write_line("should fail").await;
assert!(result.is_err());
sandbox.cleanup().await.expect("Failed to cleanup");
}
#[tokio::test]
#[ignore = "requires root"]
async fn test_interactive_event_stream() {
let config = test_config();
let runner = Runner::new(config.clone());
let mut sandbox = IsolateBox::init(66, config.isolate_binary(), config.cgroup)
.await
.expect("Failed to create sandbox");
let source = fixture_source("echo.cpp");
let language = config.get_language("cpp17").expect("cpp17 not found");
let compile_result = runner
.compile(&sandbox, &source, language, None)
.await
.expect("Compilation failed");
assert!(compile_result.is_success());
let limits = ResourceLimits::new()
.with_time_limit(2.0)
.with_wall_time_limit(2.0);
let session = runner
.run_interactive(&sandbox, language, Some(&limits))
.await
.expect("Failed to start interactive session");
let (mut stream, handle) = InteractiveEventStream::new(session);
handle
.write_line("event stream test")
.await
.expect("Failed to write");
let event = tokio::time::timeout(Duration::from_secs(5), stream.recv())
.await
.expect("Timeout waiting for event")
.expect("Stream closed unexpectedly");
match event {
InteractiveEvent::Stdout(data) => {
let output = String::from_utf8_lossy(&data);
assert!(
output.contains("event stream test"),
"Expected 'event stream test' in output, got: {}",
output
);
}
other => panic!("Expected Stdout event, got: {:?}", other),
}
handle
.write_line("second message")
.await
.expect("Failed to write second message");
let event = tokio::time::timeout(Duration::from_secs(5), stream.recv())
.await
.expect("Timeout waiting for second event")
.expect("Stream closed unexpectedly");
match event {
InteractiveEvent::Stdout(data) => {
let output = String::from_utf8_lossy(&data);
assert!(
output.contains("second message"),
"Expected 'second message' in output, got: {}",
output
);
}
other => panic!("Expected Stdout event, got: {:?}", other),
}
drop(handle);
drop(stream);
tokio::time::sleep(Duration::from_secs(4)).await;
sandbox.cleanup().await.expect("Failed to cleanup");
}
#[tokio::test]
#[ignore = "requires root"]
async fn test_interactive_staggered_prompt_response_cpp() {
let config = test_config();
let runner = Runner::new(config.clone());
let mut sandbox = IsolateBox::init(68, config.isolate_binary(), config.cgroup)
.await
.expect("Failed to create sandbox");
let source = fixture_source("staggered_prompt.cpp");
let language = config.get_language("cpp17").expect("cpp17 not found");
let compile_result = runner
.compile(&sandbox, &source, language, None)
.await
.expect("Compilation failed");
assert!(compile_result.is_success());
let mut session = runner
.run_interactive(&sandbox, language, None)
.await
.expect("Failed to start interactive session");
let prompt = session
.read_line()
.await
.expect("Failed to read prompt")
.expect("Expected prompt line");
assert_eq!(prompt, "What is your name?");
session
.write_line("Alice")
.await
.expect("Failed to write name");
let response = session
.read_line()
.await
.expect("Failed to read response")
.expect("Expected response line");
assert_eq!(response, "Hello, Alice!");
let prompt = session
.read_line()
.await
.expect("Failed to read prompt")
.expect("Expected prompt line");
assert_eq!(prompt, "Enter a number:");
session
.write_line("7")
.await
.expect("Failed to write number");
let line1 = session
.read_line()
.await
.expect("Failed to read response")
.expect("Expected response line");
assert_eq!(line1, "Double: 14");
let line2 = session
.read_line()
.await
.expect("Failed to read response")
.expect("Expected response line");
assert_eq!(line2, "Triple: 21");
let prompt = session
.read_line()
.await
.expect("Failed to read prompt")
.expect("Expected prompt line");
assert_eq!(prompt, "Enter a word:");
session
.write_line("Rust")
.await
.expect("Failed to write word");
let response = session
.read_line()
.await
.expect("Failed to read response")
.expect("Expected response line");
assert_eq!(response, "You said: Rust");
let done = session
.read_line()
.await
.expect("Failed to read final line")
.expect("Expected final line");
assert_eq!(done, "Done!");
let result = session
.wait_timeout(Duration::from_secs(5))
.await
.expect("Wait failed");
assert!(result.is_success());
assert_eq!(result.exit_code, Some(0));
sandbox.cleanup().await.expect("Failed to cleanup");
}
#[tokio::test]
#[ignore = "requires root"]
async fn test_interactive_multi_line_output_between_inputs() {
let config = test_config();
let runner = Runner::new(config.clone());
let mut sandbox = IsolateBox::init(69, config.isolate_binary(), config.cgroup)
.await
.expect("Failed to create sandbox");
let source = fixture_source("multi_step_quiz.cpp");
let language = config.get_language("cpp17").expect("cpp17 not found");
let compile_result = runner
.compile(&sandbox, &source, language, None)
.await
.expect("Compilation failed");
assert!(compile_result.is_success());
let mut session = runner
.run_interactive(&sandbox, language, None)
.await
.expect("Failed to start interactive session");
let line1 = session.read_line().await.unwrap().unwrap();
assert_eq!(line1, "Welcome to the quiz!");
let line2 = session.read_line().await.unwrap().unwrap();
assert_eq!(line2, "You will answer 3 questions.");
let blank = session.read_line().await.unwrap().unwrap();
assert_eq!(blank, "");
let q1 = session.read_line().await.unwrap().unwrap();
assert_eq!(q1, "Q1: What is 2+2?");
session.write_line("4").await.unwrap();
let r1 = session.read_line().await.unwrap().unwrap();
assert_eq!(r1, "Correct!");
let blank = session.read_line().await.unwrap().unwrap();
assert_eq!(blank, "");
let q2 = session.read_line().await.unwrap().unwrap();
assert_eq!(q2, "Q2: What is 3*5?");
session.write_line("10").await.unwrap(); let r2 = session.read_line().await.unwrap().unwrap();
assert_eq!(r2, "Wrong! The answer is 15.");
let blank = session.read_line().await.unwrap().unwrap();
assert_eq!(blank, "");
let q3 = session.read_line().await.unwrap().unwrap();
assert_eq!(q3, "Q3: What is 10-7?");
session.write_line("3").await.unwrap();
let r3 = session.read_line().await.unwrap().unwrap();
assert_eq!(r3, "Correct!");
let blank = session.read_line().await.unwrap().unwrap();
assert_eq!(blank, "");
let score = session.read_line().await.unwrap().unwrap();
assert_eq!(score, "Final score: 2/3");
let result = session
.wait_timeout(Duration::from_secs(5))
.await
.expect("Wait failed");
assert!(result.is_success());
assert_eq!(result.exit_code, Some(0));
sandbox.cleanup().await.expect("Failed to cleanup");
}
#[tokio::test]
#[ignore = "requires root"]
async fn test_interactive_staggered_event_stream() {
let config = test_config();
let runner = Runner::new(config.clone());
let mut sandbox = IsolateBox::init(70, config.isolate_binary(), config.cgroup)
.await
.expect("Failed to create sandbox");
let source = fixture_source("staggered_prompt.cpp");
let language = config.get_language("cpp17").expect("cpp17 not found");
let compile_result = runner
.compile(&sandbox, &source, language, None)
.await
.expect("Compilation failed");
assert!(compile_result.is_success());
let limits = ResourceLimits::new()
.with_time_limit(5.0)
.with_wall_time_limit(5.0);
let session = runner
.run_interactive(&sandbox, language, Some(&limits))
.await
.expect("Failed to start interactive session");
let (mut stream, handle) = InteractiveEventStream::new(session);
let mut accumulated = String::new();
loop {
let event = tokio::time::timeout(Duration::from_secs(5), stream.recv())
.await
.expect("Timeout waiting for prompt")
.expect("Stream closed");
match event {
InteractiveEvent::Stdout(data) => {
accumulated.push_str(&String::from_utf8_lossy(&data));
if accumulated.contains("What is your name?\n") {
break;
}
}
InteractiveEvent::Exited(_) => panic!("Process exited before prompt"),
_ => {}
}
}
assert!(accumulated.contains("What is your name?"));
accumulated.clear();
handle.write_line("Bob").await.expect("Failed to write");
loop {
let event = tokio::time::timeout(Duration::from_secs(5), stream.recv())
.await
.expect("Timeout waiting for greeting")
.expect("Stream closed");
match event {
InteractiveEvent::Stdout(data) => {
accumulated.push_str(&String::from_utf8_lossy(&data));
if accumulated.contains("Enter a number:\n") {
break;
}
}
InteractiveEvent::Exited(_) => panic!("Process exited before greeting"),
_ => {}
}
}
assert!(
accumulated.contains("Hello, Bob!"),
"Expected greeting in: {}",
accumulated
);
accumulated.clear();
handle.write_line("5").await.expect("Failed to write");
loop {
let event = tokio::time::timeout(Duration::from_secs(5), stream.recv())
.await
.expect("Timeout waiting for results")
.expect("Stream closed");
match event {
InteractiveEvent::Stdout(data) => {
accumulated.push_str(&String::from_utf8_lossy(&data));
if accumulated.contains("Enter a word:\n") {
break;
}
}
InteractiveEvent::Exited(_) => panic!("Process exited before results"),
_ => {}
}
}
assert!(
accumulated.contains("Double: 10"),
"Expected double in: {}",
accumulated
);
assert!(
accumulated.contains("Triple: 15"),
"Expected triple in: {}",
accumulated
);
accumulated.clear();
handle.write_line("hello").await.expect("Failed to write");
loop {
let event = tokio::time::timeout(Duration::from_secs(5), stream.recv())
.await
.expect("Timeout waiting for final output")
.expect("Stream closed");
match event {
InteractiveEvent::Stdout(data) => {
accumulated.push_str(&String::from_utf8_lossy(&data));
if accumulated.contains("Done!\n") {
break;
}
}
InteractiveEvent::Exited(_) => panic!("Process exited before Done"),
_ => {}
}
}
assert!(
accumulated.contains("You said: hello"),
"Expected echo in: {}",
accumulated
);
assert!(
accumulated.contains("Done!"),
"Expected Done in: {}",
accumulated
);
drop(handle);
let event = tokio::time::timeout(Duration::from_secs(5), stream.recv()).await;
match event {
Ok(Some(InteractiveEvent::Exited(result))) => {
assert!(result.is_success());
assert_eq!(result.exit_code, Some(0));
}
Ok(Some(other)) => {
debug_assert!(
matches!(other, InteractiveEvent::Stdout(_)),
"Unexpected event: {:?}",
other
);
}
Ok(None) => {
}
Err(_) => {
panic!("Timed out waiting for process to exit");
}
}
drop(stream);
sandbox.cleanup().await.expect("Failed to cleanup");
}
#[tokio::test]
#[ignore = "requires root"]
async fn test_interactive_staggered_prompt_response_python() {
let config = test_config();
let runner = Runner::new(config.clone());
let mut sandbox = IsolateBox::init(71, config.isolate_binary(), config.cgroup)
.await
.expect("Failed to create sandbox");
let source = fixture_source("staggered_prompt.py");
let language = config.get_language("python3").expect("python3 not found");
let source_name = language.source_name();
sandbox
.write_file(&source_name, &source)
.await
.expect("Failed to write source");
let mut session = runner
.run_interactive(&sandbox, language, None)
.await
.expect("Failed to start interactive session");
let prompt = session.read_line().await.unwrap().unwrap();
assert_eq!(prompt, "What is your name?");
session.write_line("Alice").await.unwrap();
let greeting = session.read_line().await.unwrap().unwrap();
assert_eq!(greeting, "Hello, Alice!");
let prompt = session.read_line().await.unwrap().unwrap();
assert_eq!(prompt, "Enter a number:");
session.write_line("7").await.unwrap();
let double = session.read_line().await.unwrap().unwrap();
assert_eq!(double, "Double: 14");
let triple = session.read_line().await.unwrap().unwrap();
assert_eq!(triple, "Triple: 21");
let prompt = session.read_line().await.unwrap().unwrap();
assert_eq!(prompt, "Enter a word:");
session.write_line("Rust").await.unwrap();
let echo = session.read_line().await.unwrap().unwrap();
assert_eq!(echo, "You said: Rust");
let done = session.read_line().await.unwrap().unwrap();
assert_eq!(done, "Done!");
let result = session
.wait_timeout(Duration::from_secs(5))
.await
.expect("Wait failed");
assert!(result.is_success());
sandbox.cleanup().await.expect("Failed to cleanup");
}
#[tokio::test]
#[ignore = "requires root"]
async fn test_interactive_strict_alternation() {
let config = test_config();
let runner = Runner::new(config.clone());
let mut sandbox = IsolateBox::init(72, config.isolate_binary(), config.cgroup)
.await
.expect("Failed to create sandbox");
let source = fixture_source("interactive_adder.cpp");
let language = config.get_language("cpp17").expect("cpp17 not found");
let compile_result = runner
.compile(&sandbox, &source, language, None)
.await
.expect("Compilation failed");
assert!(compile_result.is_success());
let mut session = runner
.run_interactive(&sandbox, language, None)
.await
.expect("Failed to start interactive session");
for i in 0..20 {
let a = i;
let b = i * 2;
session
.write_line(&format!("{} {}", a, b))
.await
.expect("Failed to write");
let line = session
.read_line()
.await
.expect("Failed to read")
.expect("Expected output line");
assert_eq!(
line,
(a + b).to_string(),
"Mismatch on iteration {}: {} + {} should be {}",
i,
a,
b,
a + b
);
}
session.close_stdin();
let result = session
.wait_timeout(Duration::from_secs(5))
.await
.expect("Wait failed");
assert!(result.is_success());
sandbox.cleanup().await.expect("Failed to cleanup");
}
#[tokio::test]
#[ignore = "requires root"]
async fn test_interactive_interpreted_python() {
let config = test_config();
let runner = Runner::new(config.clone());
let mut sandbox = IsolateBox::init(67, config.isolate_binary(), config.cgroup)
.await
.expect("Failed to create sandbox");
let source = fixture_source("interactive_echo.py");
let language = config.get_language("python3").expect("python3 not found");
let source_name = language.source_name();
sandbox
.write_file(&source_name, &source)
.await
.expect("Failed to write source");
let mut session = runner
.run_interactive(&sandbox, language, None)
.await
.expect("Failed to start interactive session");
session
.write_line("python interactive")
.await
.expect("Failed to write");
let line = session
.read_line()
.await
.expect("Failed to read line")
.expect("Expected a line");
assert_eq!(line, "python interactive");
session
.write_line("second round")
.await
.expect("Failed to write");
let line = session
.read_line()
.await
.expect("Failed to read line")
.expect("Expected a line");
assert_eq!(line, "second round");
session.close_stdin();
let result = session
.wait_timeout(Duration::from_secs(5))
.await
.expect("Wait failed");
assert!(result.is_success());
sandbox.cleanup().await.expect("Failed to cleanup");
}