//! Loom model for the runtime command capture join pattern. use loom::sync::{Arc, Mutex}; use loom::thread; fn join_capture( handle: loom::thread::JoinHandle, &'static str>>, stream: &'static str, ) -> Result, String> { handle .join() .map_err(|_| format!("{stream} capture thread panicked"))? .map_err(|error| format!("failed to read child {stream}: {error}")) } #[test] fn capture_threads_publish_before_join_returns() { loom::model(|| { let events = Arc::new(Mutex::new(Vec::new())); let stdout_events = Arc::clone(&events); let stderr_events = Arc::clone(&events); let stdout = thread::spawn(move || { let mut guard = stdout_events.lock().expect("stdout lock"); guard.push("stdout"); }); let stderr = thread::spawn(move || { let mut guard = stderr_events.lock().expect("stderr lock"); guard.push("stderr"); }); stdout.join().expect("stdout join"); stderr.join().expect("stderr join"); { let guard = events.lock().expect("events lock"); assert_eq!(guard.len(), 2); assert!(guard.contains(&"stdout")); assert!(guard.contains(&"stderr")); drop(guard); } }); } #[test] fn capture_join_models_success_and_read_error_paths() { loom::model(|| { let stdout = thread::spawn(|| Ok(vec![b'o', b'k'])); let stderr = thread::spawn(|| Err("broken pipe")); assert_eq!(join_capture(stdout, "stdout").expect("stdout"), b"ok"); assert_eq!( join_capture(stderr, "stderr").expect_err("stderr"), "failed to read child stderr: broken pipe" ); }); }