diff --git a/lib/apps/fabro-server/src/run_files.rs b/lib/apps/fabro-server/src/run_files.rs index 4a9f645ca..4387a3017 100644 --- a/lib/apps/fabro-server/src/run_files.rs +++ b/lib/apps/fabro-server/src/run_files.rs @@ -1781,7 +1781,7 @@ diff --git a/src/live.rs b/src/live.rs assert_eq!(body.meta.source, RunFilesMetaSource::Sandbox); assert_eq!(body.meta.scope, RunFilesMetaScope::Uncommitted); assert_eq!(body.data.len(), 1); - let commands = sandbox.captured_commands(); + let commands = sandbox.driver().scripted_exec().commands(); assert_eq!(commands.len(), 2); assert!(commands[0].contains(" show -s --format=")); assert!(commands[1].contains(" diff --patch --find-renames=50% HEAD")); diff --git a/lib/components/fabro-agent/src/profiles/kimi_tools.rs b/lib/components/fabro-agent/src/profiles/kimi_tools.rs index 119a92453..75edb2583 100644 --- a/lib/components/fabro-agent/src/profiles/kimi_tools.rs +++ b/lib/components/fabro-agent/src/profiles/kimi_tools.rs @@ -706,7 +706,15 @@ mod tests { assert!(output.starts_with("Command timed out.\n"), "{output}"); assert_eq!(env.captured_timeout(), Some(7_000)); - assert_eq!(env.captured_working_dirs(), vec![Some("/repo".to_string())]); + assert_eq!( + env.driver() + .scripted_exec() + .recorded() + .iter() + .map(|spec| spec.working_dir.clone()) + .collect::>(), + vec![Some("/repo".to_string())] + ); assert_eq!(env.captured_env_vars(), Some(tool_env)); assert_eq!(env.captured_command().as_deref(), Some("echo $TOKEN")); } diff --git a/lib/components/fabro-sandbox/src/test_support.rs b/lib/components/fabro-sandbox/src/test_support.rs index b2a389169..2f3e3f670 100644 --- a/lib/components/fabro-sandbox/src/test_support.rs +++ b/lib/components/fabro-sandbox/src/test_support.rs @@ -1,9 +1,11 @@ //! Test doubles for fabro's sandbox layer. //! -//! [`MockSandbox`] is a configuration and a recorder over the sandbox -//! driver's scripted double: a test writes down the files, the command -//! answer, and the failures it wants, takes a [`RunSandbox`] from it, and -//! reads back what the code under test ran or wrote. Nothing here fakes +//! [`MockSandbox`] is a configuration over the sandbox driver's scripted +//! double: a test writes down the files, the command answer, and the +//! failures it wants, and takes a [`RunSandbox`] from it. What the code +//! under test ran or wrote is read back from the driver double itself, +//! through [`MockSandbox::driver`]; the few accessors here convert what a +//! spec records into the shape fabro's tests assert on. Nothing here fakes //! fabro's own logic; every call goes through the real `RunSandbox` and //! fabro's exec policy, down to the scripted driver. @@ -171,13 +173,6 @@ impl MockSandbox { self } - /// Queues the result for the next command, ahead of `exec_result`. - /// Results answer in the order they were pushed. - pub fn push_exec_result(&self, result: ExecResult) -> &Self { - self.driver().scripted_exec().push_result(result); - self - } - fn built(&self) -> &Built { self.built.get_or_init(|| { let driver = Arc::new(self.build_driver()); @@ -259,17 +254,12 @@ impl MockSandbox { .unwrap_or_default() } - /// The Bash source of every command run so far, in order. - pub fn captured_commands(&self) -> Vec { - self.recorded() - .iter() - .map(|spec| spec.args.last().cloned().unwrap_or_default()) - .collect() - } - - /// The last command's Bash source. + /// The last command's Bash source. Every command, in order, is + /// `driver().scripted_exec().commands()`. pub fn captured_command(&self) -> Option { - self.captured_commands().pop() + self.recorded() + .last() + .and_then(|spec| spec.args.last().cloned()) } /// The last command's timeout in milliseconds. @@ -289,23 +279,6 @@ impl MockSandbox { .collect() } - /// Whether each command was given the run's cancellation to stop on, - /// in order. - pub fn captured_term_stops(&self) -> Vec { - self.built - .get() - .map(|built| built.driver.scripted_exec().term_stops()) - .unwrap_or_default() - } - - /// The working directory of every command, in order. - pub fn captured_working_dirs(&self) -> Vec> { - self.recorded() - .iter() - .map(|spec| spec.working_dir.clone()) - .collect() - } - /// The explicit variables of the last command as the caller passed them. /// The exec policy's own `BASH_ENV` blank is not the caller's. pub fn captured_env_vars(&self) -> Option> { @@ -318,13 +291,6 @@ impl MockSandbox { }) } - /// The bytes the last streaming command was fed on standard input. - pub fn captured_stdin(&self) -> Option> { - self.built - .get() - .and_then(|built| built.driver.scripted_exec().captured_stdin().pop()) - } - /// Every file written so far as `(path, content)`, in order. pub fn written_files(&self) -> Vec<(String, String)> { self.built @@ -340,46 +306,6 @@ impl MockSandbox { }) .unwrap_or_default() } - - /// Every file deleted so far by absolute path, in order. - pub fn deleted_files(&self) -> Vec { - self.built - .get() - .map(|built| built.driver.memory_fs().deletes()) - .unwrap_or_default() - } - - /// How many times the code under test asked whether a path exists. - pub fn exists_calls(&self) -> usize { - self.built - .get() - .map_or(0, |built| built.driver.memory_fs().exists_calls()) - } - - pub fn start_count(&self) -> u32 { - self.built - .get() - .map_or(0, |built| built.driver.start_count()) - } - - pub fn stop_count(&self) -> u32 { - self.built - .get() - .map_or(0, |built| built.driver.stop_count()) - } - - pub fn delete_count(&self) -> u32 { - self.built - .get() - .map_or(0, |built| built.driver.delete_count()) - } - - /// How many walks the code under test ran. - pub fn walk_files_was_called(&self) -> bool { - self.built - .get() - .is_some_and(|built| built.driver.scripted_search().walk_calls() > 0) - } } // --- MockStdioProcess --- diff --git a/lib/components/fabro-workflow/src/artifact.rs b/lib/components/fabro-workflow/src/artifact.rs index 0aaabb027..c7fe4eb18 100644 --- a/lib/components/fabro-workflow/src/artifact.rs +++ b/lib/components/fabro-workflow/src/artifact.rs @@ -1239,7 +1239,7 @@ mod tests { .unwrap(); assert_eq!( - env.exists_calls(), + env.driver().memory_fs().exists_calls(), 1, "sandbox locality should be probed once per resolution pass" ); diff --git a/lib/components/fabro-workflow/src/handler/command.rs b/lib/components/fabro-workflow/src/handler/command.rs index 59fa28f9a..567cf9889 100644 --- a/lib/components/fabro-workflow/src/handler/command.rs +++ b/lib/components/fabro-workflow/src/handler/command.rs @@ -1466,7 +1466,7 @@ mod tests { assert_eq!(outcome.status, StageOutcome::Succeeded); assert_eq!( - mock.captured_stdin(), + mock.driver().scripted_exec().captured_stdin().pop(), Some(serde_json::to_vec(¶llel_results).unwrap()) ); assert!( @@ -1502,7 +1502,11 @@ mod tests { assert_eq!(outcome.status, StageOutcome::Succeeded); assert_eq!( - mock.captured_stdin().as_deref(), + mock.driver() + .scripted_exec() + .captured_stdin() + .pop() + .as_deref(), Some(b"first\nlast".as_slice()) ); } @@ -1776,7 +1780,7 @@ mod tests { .await .unwrap(); - assert_eq!(spy.captured_term_stops(), vec![true]); + assert_eq!(spy.driver().scripted_exec().term_stops(), vec![true]); } #[tokio::test] diff --git a/lib/components/fabro-workflow/src/pipeline/execute/tests.rs b/lib/components/fabro-workflow/src/pipeline/execute/tests.rs index d72e60c38..a8d49ed46 100644 --- a/lib/components/fabro-workflow/src/pipeline/execute/tests.rs +++ b/lib/components/fabro-workflow/src/pipeline/execute/tests.rs @@ -942,10 +942,10 @@ async fn execute_reactivates_sandbox_after_a_stage_can_leave_it_stopped() { .unwrap(); assert_eq!(outcome.status, StageOutcome::Succeeded); - assert_eq!(sandbox.stop_count(), 1); - assert!(sandbox.walk_files_was_called()); + assert_eq!(sandbox.driver().stop_count(), 1); + assert!(sandbox.driver().scripted_search().walk_calls() > 0); assert_eq!( - sandbox.start_count(), + sandbox.driver().start_count(), 1, "the stopped sandbox is started again before the walk" ); diff --git a/lib/components/fabro-workflow/src/pipeline/finalize.rs b/lib/components/fabro-workflow/src/pipeline/finalize.rs index 9195014d0..6c37ef0e4 100644 --- a/lib/components/fabro-workflow/src/pipeline/finalize.rs +++ b/lib/components/fabro-workflow/src/pipeline/finalize.rs @@ -1644,8 +1644,8 @@ mod tests { .await .unwrap(); - assert_eq!(sandbox.stop_count(), 1); - assert_eq!(sandbox.delete_count(), 0); + assert_eq!(sandbox.driver().stop_count(), 1); + assert_eq!(sandbox.driver().delete_count(), 0); } #[tokio::test] @@ -1678,8 +1678,8 @@ mod tests { .await .unwrap(); - assert_eq!(sandbox.stop_count(), 0); - assert_eq!(sandbox.delete_count(), 0); + assert_eq!(sandbox.driver().stop_count(), 0); + assert_eq!(sandbox.driver().delete_count(), 0); } #[tokio::test] diff --git a/lib/components/fabro-workflow/src/pipeline/initialize.rs b/lib/components/fabro-workflow/src/pipeline/initialize.rs index b5c2b8fb9..0d5989bb0 100644 --- a/lib/components/fabro-workflow/src/pipeline/initialize.rs +++ b/lib/components/fabro-workflow/src/pipeline/initialize.rs @@ -1072,7 +1072,7 @@ mod tests { .await .expect("git identity should configure"); - let commands = sandbox.captured_commands(); + let commands = sandbox.driver().scripted_exec().commands(); assert_eq!(commands, vec![ "git config --local user.name 'Fabro Bot' && git config --local user.email \ fabro-bot@example.com" diff --git a/lib/components/fabro-workflow/src/sandbox_git.rs b/lib/components/fabro-workflow/src/sandbox_git.rs index 6889d6c13..0b86b0161 100644 --- a/lib/components/fabro-workflow/src/sandbox_git.rs +++ b/lib/components/fabro-workflow/src/sandbox_git.rs @@ -820,7 +820,7 @@ mod tests { fn scripted(exec_results: &[ExecResult]) -> MockSandbox { let sandbox = MockSandbox::default(); for result in exec_results { - sandbox.push_exec_result(result.clone()); + sandbox.driver().scripted_exec().push_result(result.clone()); } sandbox } @@ -993,10 +993,10 @@ mod tests { ); assert_ne!(write_paths[0], write_paths[1]); - let delete_paths = sandbox.deleted_files(); + let delete_paths = sandbox.driver().memory_fs().deletes(); assert_eq!(delete_paths, write_paths); - let commands = sandbox.captured_commands(); + let commands = sandbox.driver().scripted_exec().commands(); let commit_commands = commands .iter() .filter(|command| command.contains(" commit ")) @@ -1078,7 +1078,7 @@ mod tests { .await .expect("checkpoint should succeed"); - let commands = sandbox.captured_commands(); + let commands = sandbox.driver().scripted_exec().commands(); let commit_cmd = commands .iter() .find(|c| c.contains(" commit ")) @@ -1105,7 +1105,7 @@ mod tests { .await .expect("checkpoint should succeed"); - let commands = sandbox.captured_commands(); + let commands = sandbox.driver().scripted_exec().commands(); let commit_cmd = commands .iter() .find(|c| c.contains(" commit "))