ralph_workflow/executor/mod.rs
1//! Process execution abstraction for dependency injection.
2//!
3//! This module provides a trait-based abstraction for executing external processes,
4//! allowing production code to use real processes and test code to use mocks.
5//! This follows the same pattern as [`crate::workspace::Workspace`] for dependency injection.
6//!
7//! # Purpose
8//!
9//! - Production: [`RealProcessExecutor`] executes actual commands using `std::process::Command`
10//! - Tests: `MockProcessExecutor` captures calls and returns controlled results (with `test-utils` feature)
11//!
12//! # Benefits
13//!
14//! - Test isolation: Tests don't spawn real processes
15//! - Determinism: Tests produce consistent results
16//! - Speed: Tests run faster without subprocess overhead
17//! - Mockability: Full control over process behavior in tests
18//!
19//! # Key Types
20//!
21//! - [`ProcessExecutor`] - The trait abstraction for process execution
22//! - [`AgentSpawnConfig`] - Configuration for spawning agent processes
23//! - [`AgentChildHandle`] - Handle to a spawned agent with streaming output
24//! - [`ProcessOutput`] - Captured output from a completed process
25//!
26//! # Testing with MockProcessExecutor
27//!
28//! The `test-utils` feature enables `MockProcessExecutor` for integration tests:
29//!
30//! ```ignore
31//! use ralph_workflow::{MockProcessExecutor, ProcessExecutor};
32//!
33//! // Create a mock that returns success for 'git' commands
34//! let executor = MockProcessExecutor::new()
35//! .with_output("git", "On branch main\nnothing to commit");
36//!
37//! // Execute command (captured, returns mock result)
38//! let result = executor.execute("git", &["status"], &[], None)?;
39//! assert!(result.status.success());
40//!
41//! // Verify the call was captured
42//! assert_eq!(executor.execute_count(), 1);
43//! ```
44//!
45//! # See Also
46//!
47//! - [`crate::workspace::Workspace`] - Similar abstraction for filesystem operations
48
49mod executor_trait;
50#[cfg(any(test, feature = "test-utils"))]
51mod mock;
52mod real;
53mod types;
54
55// Re-export all public types
56pub use executor_trait::ProcessExecutor;
57pub use real::RealProcessExecutor;
58pub use types::{
59 AgentChild, AgentChildHandle, AgentCommandResult, AgentSpawnConfig, ProcessOutput,
60 RealAgentChild,
61};
62
63#[cfg(any(test, feature = "test-utils"))]
64pub use mock::{MockAgentChild, MockProcessExecutor};
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[test]
71 fn test_real_executor_can_be_created() {
72 let executor = RealProcessExecutor::new();
73 // Can't test actual execution without real commands
74 let _ = executor;
75 }
76
77 #[test]
78 fn test_real_executor_execute_basic() {
79 let executor = RealProcessExecutor::new();
80 // Use 'echo' command which should exist on all Unix systems
81 let result = executor.execute("echo", &["hello"], &[], None);
82 // Should succeed
83 assert!(result.is_ok());
84 if let Ok(output) = result {
85 assert!(output.status.success());
86 assert_eq!(output.stdout.trim(), "hello");
87 }
88 }
89}