1pub mod fixtures;
56pub mod harness;
57pub mod logging;
58pub mod process_triage;
59pub mod test_workers;
60pub mod verification;
61
62pub use fixtures::{
64 DEFAULT_MULTI_REPO_ALIAS_ROOT, DEFAULT_MULTI_REPO_CANONICAL_ROOT,
65 DEFAULT_MULTI_REPO_FIXTURE_NAMESPACE, DaemonConfigFixture, FixtureFailureMode, FixtureLayer,
66 FixtureReadiness, HookInputFixture, MultiRepoFixtureConfig, MultiRepoFixtureError,
67 MultiRepoFixtureMetadata, MultiRepoFixtureResult, MultiRepoFixtureSet, RustProjectFixture,
68 TestCaseFixture, WorkerFixture, WorkersFixture, reset_default_multi_repo_fixtures,
69 reset_multi_repo_fixtures,
70};
71pub use harness::{
72 CommandResult, HarnessConfig, HarnessError, HarnessResult, ProcessInfo,
73 ReliabilityCommandRecord, ReliabilityFailureHook, ReliabilityFailureHookFlags,
74 ReliabilityLifecycleCommand, ReliabilityScenarioReport, ReliabilityScenarioSpec,
75 ReliabilityWorkerLifecycleHooks, TestHarness, TestHarnessBuilder, cleanup_stale_test_artifacts,
76};
77pub use logging::{
78 LogEntry, LogLevel, LogSource, LoggerConfig, RELIABILITY_EVENT_SCHEMA_VERSION,
79 ReliabilityContext, ReliabilityEventInput, ReliabilityPhase, ReliabilityPhaseEvent,
80 TestLogSummary, TestLogger, TestLoggerBuilder,
81};
82pub use process_triage::{
83 PROCESS_TRIAGE_CONTRACT_SCHEMA_VERSION, ProcessTriageActionClass, ProcessTriageActionOutcome,
84 ProcessTriageActionRequest, ProcessTriageActionResult, ProcessTriageAdapterCommand,
85 ProcessTriageAuditRecord, ProcessTriageCommandBudget, ProcessTriageContract,
86 ProcessTriageContractError, ProcessTriageEscalationLevel, ProcessTriageFailure,
87 ProcessTriageFailureKind, ProcessTriagePolicyDecision, ProcessTriageRequest,
88 ProcessTriageResponse, ProcessTriageResponseStatus, ProcessTriageRetryPolicy,
89 ProcessTriageSafeActionPolicy, ProcessTriageTimeoutPolicy, ProcessTriageTrigger,
90 evaluate_triage_action, process_triage_request_schema, process_triage_response_schema,
91};
92pub use test_workers::{
93 ENV_SKIP_WORKER_CHECK, ENV_TIMEOUT_SECS, ENV_WORKERS_CONFIG, TestConfigError, TestConfigResult,
94 TestSettings, TestWorkerEntry, TestWorkersConfig, expand_tilde_path, get_config_path,
95 is_mock_ssh_mode, should_skip_worker_check,
96};
97pub use verification::{RemoteCompilationTest, VerificationConfig, VerificationResult};
98
99#[macro_export]
110macro_rules! e2e_test {
111 ($name:ident, $body:expr) => {
112 #[test]
113 fn $name() {
114 use $crate::e2e::{HarnessResult, TestHarnessBuilder};
115
116 fn run_test() -> HarnessResult<()> {
117 let harness = TestHarnessBuilder::new(stringify!($name))
118 .cleanup_on_success(true)
119 .cleanup_on_failure(false)
120 .build()?;
121
122 let body: fn(&$crate::e2e::TestHarness) -> HarnessResult<()> = $body;
123 let result = body(&harness);
124
125 if result.is_ok() {
126 harness.mark_passed();
127 }
128
129 result
130 }
131
132 if let Err(e) = run_test() {
133 panic!("E2E test failed: {}", e);
134 }
135 }
136 };
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use std::time::Duration;
143
144 #[test]
145 fn test_e2e_infrastructure_integration() {
146 let logger = TestLoggerBuilder::new("integration_test")
148 .print_realtime(false)
149 .build();
150
151 logger.info("Starting integration test");
152
153 let harness = TestHarnessBuilder::new("integration_test")
155 .cleanup_on_success(true)
156 .build()
157 .unwrap();
158
159 let project = RustProjectFixture::minimal("test-proj");
161 let project_dir = harness.create_dir("project").unwrap();
162 project.create_in(&project_dir).unwrap();
163
164 assert!(project_dir.join("Cargo.toml").exists());
166 assert!(project_dir.join("src/main.rs").exists());
167
168 let workers = WorkersFixture::mock_local(1);
170 let workers_toml = workers.to_toml();
171 assert!(workers_toml.contains("worker1"));
172
173 let socket_path = harness.test_dir().join("rch.sock");
175 let daemon_config = DaemonConfigFixture::minimal(&socket_path);
176 let daemon_toml = daemon_config.to_toml();
177 assert!(daemon_toml.contains("confidence_threshold"));
178
179 let hook_input = HookInputFixture::cargo_build();
181 let json = hook_input.to_json();
182 assert!(json.contains("cargo build"));
183
184 let result = harness.exec("echo", ["hello"]).unwrap();
186 harness.assert_success(&result, "echo").unwrap();
187 harness
188 .assert_stdout_contains(&result, "hello", "echo output")
189 .unwrap();
190
191 logger.info("Integration test completed");
192 harness.mark_passed();
193 }
194
195 #[test]
196 fn test_logger_standalone() {
197 let logger = TestLoggerBuilder::new("logger_test")
198 .print_realtime(false)
199 .min_level(LogLevel::Debug)
200 .max_entries(100)
201 .build();
202
203 logger.debug("Debug message");
204 logger.info("Info message");
205 logger.warn("Warning message");
206 logger.error("Error message");
207
208 let entries = logger.entries();
209 assert_eq!(entries.len(), 4);
210
211 let errors = logger.entries_by_level(LogLevel::Error);
212 assert_eq!(errors.len(), 1);
213
214 let summary = logger.summary();
215 assert_eq!(summary.total_entries, 4);
216 assert_eq!(*summary.counts_by_level.get(&LogLevel::Error).unwrap(), 1);
217 }
218
219 #[test]
220 fn test_harness_wait_for() {
221 let harness = TestHarnessBuilder::new("wait_test")
222 .cleanup_on_success(true)
223 .build()
224 .unwrap();
225
226 let test_file = harness.test_dir().join("marker.txt");
228
229 let file_path = test_file.clone();
231 std::thread::spawn(move || {
232 std::thread::sleep(Duration::from_millis(100));
233 std::fs::write(&file_path, "created").unwrap();
234 });
235
236 harness
238 .wait_for_file(&test_file, Duration::from_secs(2))
239 .unwrap();
240
241 assert!(test_file.exists());
242 harness.mark_passed();
243 }
244}