use std::io::{BufRead, BufReader, Write}; use std::net::TcpListener; use std::time::{Duration, Instant}; use super::*; fn start_running_control_server() -> (String, thread::JoinHandle<()>) { let listener = TcpListener::bind("127.0.0.1:0").unwrap(); let coordinator = listener.local_addr().unwrap().to_string(); let server = thread::spawn(move || { let (mut stream, _) = listener.accept().unwrap(); let mut reader = BufReader::new(stream.try_clone().unwrap()); loop { let mut request = String::new(); match reader.read_line(&mut request) { Ok(0) | Err(_) => break, Ok(_) => stream .write_all(b"{\"type\":\"task_control\",\"process\":\"vp\",\"task\":\"task\",\"cancel_requested\":false,\"abort_requested\":false}\n") .unwrap(), } } }); (coordinator, server) } fn test_controlled_runner( coordinator: String, timeout: Duration, ) -> CoordinatorControlledProcessRunner { CoordinatorControlledProcessRunner { args: Args { coordinator, tenant: "tenant".to_owned(), project: "project".to_owned(), project_root: None, node: "node".to_owned(), enrollment_grant: None, public_key: None, control_poll_ms: 0, assignment_poll_ms: 1, emit_ready: false, worker: true, }, process: "vp".to_owned(), task: "task".to_owned(), node_private_key: clusterflux_core::derive_ed25519_private_key_from_seed( "controlled-runner-test", ), debug_control: Arc::new(WasmDebugControl::default()), command_status: Arc::new(Mutex::new(None)), timeout, } } #[cfg(unix)] #[test] fn controlled_process_runner_kills_running_group_when_abort_is_polled() { let listener = TcpListener::bind("127.0.0.1:0").unwrap(); let coordinator = listener.local_addr().unwrap().to_string(); let server = thread::spawn(move || { let (mut stream, _) = listener.accept().unwrap(); let mut request = String::new(); BufReader::new(stream.try_clone().unwrap()) .read_line(&mut request) .unwrap(); assert!(!request.trim().is_empty()); stream .write_all(b"{\"type\":\"task_control\",\"process\":\"vp\",\"task\":\"task\",\"cancel_requested\":false,\"abort_requested\":true}\n") .unwrap(); }); let mut runner = CoordinatorControlledProcessRunner { args: Args { coordinator, tenant: "tenant".to_owned(), project: "project".to_owned(), project_root: None, node: "node".to_owned(), enrollment_grant: None, public_key: None, control_poll_ms: 0, assignment_poll_ms: 1, emit_ready: false, worker: true, }, process: "vp".to_owned(), task: "task".to_owned(), node_private_key: clusterflux_core::derive_ed25519_private_key_from_seed( "controlled-runner-test", ), debug_control: Arc::new(WasmDebugControl::default()), command_status: Arc::new(Mutex::new(None)), timeout: Duration::from_secs(30), }; let started = Instant::now(); let error = runner .run(&PodmanCommand { program: "sh".to_owned(), args: vec!["-c".to_owned(), "sleep 30 & wait".to_owned()], }) .unwrap_err(); server.join().unwrap(); assert!(matches!(error, BackendError::Cancelled(_))); assert!(started.elapsed() < Duration::from_secs(5)); } #[cfg(unix)] #[test] fn command_timeout_is_bounded_and_a_later_command_still_runs() { let (coordinator, server) = start_running_control_server(); let mut runner = test_controlled_runner(coordinator, Duration::from_millis(120)); let started = Instant::now(); let error = runner .run(&PodmanCommand { program: "sh".to_owned(), args: vec!["-c".to_owned(), "sleep 30 & wait".to_owned()], }) .unwrap_err(); assert!(error.to_string().contains("timeout")); assert!(started.elapsed() < Duration::from_secs(5)); drop(runner); server.join().unwrap(); let (coordinator, server) = start_running_control_server(); let mut runner = test_controlled_runner(coordinator, Duration::from_secs(5)); let output = runner .run(&PodmanCommand { program: "sh".to_owned(), args: vec!["-c".to_owned(), "printf healthy".to_owned()], }) .unwrap(); assert_eq!(output.status_code, Some(0)); assert_eq!(String::from_utf8(output.stdout).unwrap(), "healthy"); drop(runner); server.join().unwrap(); } #[test] fn command_source_verification_rejects_a_changed_checkout() { let checkout = tempfile::tempdir().unwrap(); std::fs::write(checkout.path().join("source.c"), "int value = 1;\n").unwrap(); let expected = snapshot_project(checkout.path()).unwrap().digest; verify_source_snapshot(checkout.path(), &expected).unwrap(); std::fs::write(checkout.path().join("source.c"), "int value = 2;\n").unwrap(); let error = verify_source_snapshot(checkout.path(), &expected).unwrap_err(); assert!(error.contains("source snapshot mismatch")); assert!(error.contains(expected.as_str())); } #[test] fn command_environment_verification_rejects_a_changed_recipe() { let checkout = tempfile::tempdir().unwrap(); let environment = checkout.path().join("envs/linux"); std::fs::create_dir_all(&environment).unwrap(); std::fs::write( environment.join("Containerfile"), "FROM docker.io/library/alpine:3.20\n", ) .unwrap(); let expected = clusterflux_core::discover_environments(checkout.path()) .unwrap() .into_iter() .find(|environment| environment.name == "linux") .unwrap() .digest; verify_environment_digest(checkout.path(), "linux", &expected).unwrap(); std::fs::write( environment.join("Containerfile"), "FROM docker.io/library/alpine:3.21\n", ) .unwrap(); let error = verify_environment_digest(checkout.path(), "linux", &expected).unwrap_err(); assert!(error.contains("does not match the bundle")); assert!(error.contains(expected.as_str())); } #[test] fn configured_secret_values_are_redacted_before_guest_or_coordinator_logging() { assert!(is_secret_environment_name("API_TOKEN")); assert!(is_secret_environment_name("database_password")); assert!(!is_secret_environment_name("SOURCE_DATE_EPOCH")); assert_eq!( redact_configured_values( "token=correct-horse and again correct-horse".to_owned(), &["correct-horse".to_owned()], ), "token=[REDACTED] and again [REDACTED]" ); } #[test] fn native_command_requires_environment_and_network_grant() { let error = require_command_environment(None).unwrap_err(); assert!(error.contains("explicit command-capable environment")); assert_eq!(require_command_environment(Some("linux")).unwrap(), "linux"); authorize_command_network(&clusterflux_core::CommandNetworkPolicy::Disabled, false).unwrap(); let error = authorize_command_network(&clusterflux_core::CommandNetworkPolicy::Enabled, false) .unwrap_err(); assert!(error.contains("Network capability")); }