Expect checkpoint commits from a retried local run

Retry now starts the workflow over, and a local-folder run commits its
checkpoints, so the retry scenario checks that every stage commits again
under the retry's run id. The scenario for retrying without Git
checkpoints goes: local runs have them now, and the retry scenario
covers starting over.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Scott Werner 2026-10-01 13:10:13 -04:00
parent 6e0ef2c402
commit 5f22936437
2 changed files with 26 additions and 42 deletions

View file

@ -1792,43 +1792,3 @@ async fn built_in_host_runs_and_prunes_without_plugins() {
assert!(!scope.exists(), "prune removed the managed Host workspace");
server.shutdown();
}
/// Retry works without a Git checkpoint and starts the workflow again.
#[tokio::test(flavor = "multi_thread")]
async fn a_retry_without_git_checkpoints_starts_the_workflow_again() {
let context = test_context!();
let server = RunningServer::start().await;
let counter = context.temp_dir.join("retry-runs.txt");
let workspace = write_petri_workflow(
&context,
&format!(
r#"digraph Retry {{
graph [goal="Retry from the start", default_max_retries=0]
start [shape=Mdiamond]
write [shape=parallelogram, script="echo run >> '{}'", goal_gate=true]
exit [shape=Msquare]
start -> write -> exit
}}"#,
counter.display()
),
);
let original = run_detached(&context, &server, &workspace);
wait_for_success(&server, &original).await;
assert!(server.checkpoints(&original).await.is_empty());
let response = fabro_test::test_http_client()
.post(format!(
"{}/api/v1/runs/{original}/retry",
server.api_base_url
))
.bearer_auth(TEST_DEV_TOKEN)
.send()
.await
.unwrap();
let retried = expect_reqwest_json(response, fabro_http::StatusCode::CREATED, "retry").await;
let run_id = retried["id"].as_str().expect("the retry run id");
assert_ne!(run_id, original);
wait_for_success(&server, run_id).await;
assert_eq!(std::fs::read_to_string(counter).unwrap(), "run\nrun\n");
assert!(server.checkpoints(run_id).await.is_empty());
server.shutdown();
}

View file

@ -13,7 +13,7 @@
)]
use std::path::{Path, PathBuf};
use std::process::Output;
use std::process::{Command, Output};
use fabro_test::test_context;
@ -133,6 +133,24 @@ fn workspace(server: &RunningServer, run_id: &str) -> PathBuf {
workspaces.remove(0)
}
/// The subjects of the commits on the workspace's branch, oldest first.
fn commit_subjects(workspace: &Path) -> Vec<String> {
let output = Command::new("git")
.args(["log", "--reverse", "--format=%s"])
.current_dir(workspace)
.output()
.expect("git runs");
assert!(
output.status.success(),
"git log failed: {}",
String::from_utf8_lossy(&output.stderr)
);
String::from_utf8_lossy(&output.stdout)
.lines()
.map(ToOwned::to_owned)
.collect()
}
fn read(workspace: &Path, name: &str) -> String {
std::fs::read_to_string(workspace.join(name))
.unwrap_or_else(|err| panic!("{name} in {}: {err}", workspace.display()))
@ -188,7 +206,13 @@ async fn a_retry_starts_over_and_succeeds_when_the_failure_was_transient() {
assert_eq!(read(&retry_workspace, "one.txt"), "one\n");
assert_eq!(read(&retry_workspace, "flaky.txt"), "flaky\n");
assert_eq!(read(&retry_workspace, "three.txt"), "three\n");
assert!(!retry_workspace.join(".git").exists());
assert_eq!(commit_subjects(&retry_workspace), [
format!("fabro({retry}): start (success)"),
format!("fabro({retry}): one (success)"),
format!("fabro({retry}): flaky (success)"),
format!("fabro({retry}): three (success)"),
format!("fabro({retry}): exit (success)"),
]);
let state = run_json(&server, &format!("runs/{retry}/state")).await;
assert_eq!(state["retried_from"], source);
assert!(state["spec"]["fork_source_ref"].is_null());