use super::*;
fn run_diffless(run_fail: Option<&'static str>, pre: &[&str], post: &[&str]) -> (Result<(), OpError>, Vec<String>) {
let jrn = journal();
let anvil = FakeAnvil::new(jrn.clone(), None);
let plugins = FakePlugins::new(jrn.clone(), run_fail);
let pre: Vec<_> = pre.iter().map(|n| plugin(n)).collect();
let post: Vec<_> = post.iter().map(|n| plugin(n)).collect();
let result = Engine::new(&anvil, &plugins, &test_log()).diffless(Verb::Sync, Path::new("/op"), &pre, &post);
let log = jrn.borrow().clone();
(result, log)
}
#[test]
fn a_diffless_op_reads_the_tip_around_pre_then_runs_post() {
let (r, log) = run_diffless(None, &["a"], &["b"]);
assert!(r.is_ok());
assert_eq!(log, ["head", "run:a:pre", "head", "run:b:post"]);
}
#[test]
fn a_diffless_post_sees_the_moved_tip_while_pre_sees_none() {
let jrn = journal();
let anvil = FakeAnvil::new(jrn.clone(), None);
let plugins = FakePlugins::new(jrn.clone(), None);
let pre = [plugin("a")];
let post = [plugin("b")];
Engine::new(&anvil, &plugins, &test_log()).diffless(Verb::Sync, Path::new("/op"), &pre, &post).unwrap();
assert_eq!(*plugins.seen.borrow(), [("a".into(), None), ("b".into(), Some("T1".into()))]);
}
#[test]
fn a_diffless_abort_unwinds_only_its_plugins() {
let (r, log) = run_diffless(Some("b"), &["a", "b"], &["c"]);
assert!(matches!(r, Err(OpError::Plugin { ref name, .. }) if name == "b"));
assert_eq!(log, ["head", "run:a:pre", "run:b:pre", "rollback:a:pre"]);
}
#[test]
fn a_diffless_post_abort_hands_the_moved_facts_to_every_rollback() {
let jrn = journal();
let anvil = FakeAnvil::new(jrn.clone(), None);
let plugins = FakePlugins::new(jrn.clone(), Some("d"));
let pre = [plugin("a")];
let post = [plugin("c"), plugin("d")];
let _ = Engine::new(&anvil, &plugins, &test_log()).diffless(Verb::Sync, Path::new("/op"), &pre, &post);
let seen = plugins.seen.borrow().clone();
assert_eq!(seen.iter().find(|(k, _)| k == "rb:c").unwrap().1, Some("T1".into()));
assert_eq!(seen.iter().find(|(k, _)| k == "rb:a").unwrap().1, Some("T1".into()));
}
fn run_prime(
moved: Option<&'static str>,
step_fail: bool,
run_fail: Option<&'static str>,
pre: &[&str],
post: &[&str],
) -> (Result<(), OpError>, Vec<String>) {
let jrn = journal();
let anvil = FakeAnvil::new(jrn.clone(), None);
let plugins = FakePlugins::new(jrn.clone(), run_fail);
let pre: Vec<_> = pre.iter().map(|n| plugin(n)).collect();
let post: Vec<_> = post.iter().map(|n| plugin(n)).collect();
let mut step = || -> io::Result<Option<String>> {
if step_fail {
return Err(io::Error::other("materialize"));
}
Ok(moved.map(String::from))
};
let result = Engine::new(&anvil, &plugins, &test_log()).prime(
Path::new("/landing"),
Path::new("/store"),
&pre,
&post,
&mut step,
);
let log = jrn.borrow().clone();
(result, log)
}
#[test]
fn a_prime_pass_runs_pre_once_then_post_once() {
let (r, log) = run_prime(None, false, None, &["a"], &["b"]);
assert!(r.is_ok());
assert_eq!(log, ["run:a:pre", "run:b:post"]);
}
#[test]
fn a_prime_step_failure_is_a_substrate_abort_that_unwinds_pre() {
let (r, log) = run_prime(None, true, None, &["a"], &["b"]);
assert!(matches!(r, Err(OpError::Substrate(_))));
assert_eq!(log, ["run:a:pre", "rollback:a:pre"]);
}
#[test]
fn a_prime_whose_pre_moved_the_dial_aborts_on_the_first_pass() {
let (r, log) = run_prime(Some("balls/elsewhere"), false, None, &["a"], &["b"]);
let Err(OpError::Substrate(e)) = r else { panic!("expected a Substrate abort, got {r:?}") };
let msg = e.to_string();
assert!(msg.contains("prime.pre may not move tasks_branch"), "{msg}");
assert!(msg.contains("balls/elsewhere"), "{msg}"); assert_eq!(log, ["run:a:pre", "rollback:a:pre"]);
}
#[test]
fn a_prime_post_abort_unwinds_every_run_plugin_in_reverse() {
let (r, log) = run_prime(None, false, Some("b"), &["a"], &["b"]);
let Err(e) = r else { panic!("expected a Plugin abort") };
assert!(matches!(e, OpError::Plugin { ref name, .. } if name == "b"));
assert!(
e.to_string().starts_with("plugin failed during prime, rolled back 1 prior:"),
"{e}"
);
assert_eq!(log, ["run:a:pre", "run:b:post", "rollback:a:pre"]);
}