mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-03 02:24:33 +00:00
Read a mock sandbox's recordings from the driver double
MockSandbox forwarded a dozen read-backs to the driver's scripted doubles one line each: the commands run, the term stops, the stdin fed, the files written and deleted, the lifecycle counts, whether a walk ran. Tests now ask the double through MockSandbox::driver. The accessors that convert a recorded spec into the shape a test asserts on stay: the last command, the timeouts in milliseconds, the caller's environment without the exec policy's BASH_ENV blank, and written files as text. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
054a37f830
commit
20a5500510
9 changed files with 42 additions and 104 deletions
|
|
@ -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"));
|
||||
|
|
|
|||
|
|
@ -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<_>>(),
|
||||
vec![Some("/repo".to_string())]
|
||||
);
|
||||
assert_eq!(env.captured_env_vars(), Some(tool_env));
|
||||
assert_eq!(env.captured_command().as_deref(), Some("echo $TOKEN"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String> {
|
||||
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<String> {
|
||||
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<bool> {
|
||||
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<Option<String>> {
|
||||
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<HashMap<String, String>> {
|
||||
|
|
@ -318,13 +291,6 @@ impl MockSandbox {
|
|||
})
|
||||
}
|
||||
|
||||
/// The bytes the last streaming command was fed on standard input.
|
||||
pub fn captured_stdin(&self) -> Option<Vec<u8>> {
|
||||
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<String> {
|
||||
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 ---
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 "))
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue