Skip to main content

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}