Skip to content

Commit 7bd0eca

Browse files
test(run): synchronize PTY observation fixtures
agent-identity: mbp2025.direct.omp.5tek2r48 agent-persona: generalist agent-supervisor: unavailable agent-tool: OMP agent-tool-version: 18.0.11 agent-runtime: OMP 18.0.11 tooling-profile: dotfiles@4cc7b25
1 parent d4af275 commit 7bd0eca

1 file changed

Lines changed: 131 additions & 11 deletions

File tree

src/run.rs

Lines changed: 131 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,15 @@ pub(crate) fn output_full_stdout_with_timeout(
236236
run_captured(command, timeout, None, |_| {}, true)
237237
}
238238

239+
#[cfg(test)]
240+
fn output_full_stdout_with_timeout_observed(
241+
command: &mut Command,
242+
timeout: Duration,
243+
on_spawn: impl FnOnce(i32),
244+
) -> anyhow::Result<Output> {
245+
run_captured(command, timeout, None, on_spawn, true)
246+
}
247+
239248
/// Shared spawn/wait/read-back core. The child is `setsid`, so its pid is also its process group
240249
/// id — the group this function signals on every failure path.
241250
fn run_captured(
@@ -371,13 +380,17 @@ pub struct PtyCli {
371380
bin: String,
372381
/// The catalog root — the value of `$CATALOG` during `$`-expansion (spec.md §2 / R11).
373382
catalog_root: PathBuf,
383+
#[cfg(test)]
384+
on_command_spawn: Option<std::sync::Arc<dyn Fn(i32)>>,
374385
}
375386

376387
impl Default for PtyCli {
377388
fn default() -> Self {
378389
Self {
379390
bin: "pty".to_string(),
380391
catalog_root: PathBuf::from("."),
392+
#[cfg(test)]
393+
on_command_spawn: None,
381394
}
382395
}
383396
}
@@ -509,6 +522,8 @@ impl PtyCli {
509522
Self {
510523
bin: "pty".to_string(),
511524
catalog_root,
525+
#[cfg(test)]
526+
on_command_spawn: None,
512527
}
513528
}
514529

@@ -883,14 +898,25 @@ impl PtyCli {
883898
display_name: presentation.display_name.as_ref(),
884899
tags: &presentation.tags,
885900
})?;
886-
let out = output_with_input_timeout(
887-
Command::new(&self.bin)
888-
.args(["metadata", "patch", "--id", &presentation.pty_id])
889-
.env("PTY_ROOT", effective_pty_root(&self.catalog_root)),
901+
let mut command = Command::new(&self.bin);
902+
command
903+
.args(["metadata", "patch", "--id", &presentation.pty_id])
904+
.env("PTY_ROOT", effective_pty_root(&self.catalog_root));
905+
#[cfg(test)]
906+
let out = output_with_input_timeout_observed(
907+
&mut command,
890908
PTY_LIST_TIMEOUT,
891909
Some(payload),
892-
)
893-
.map_err(|error| anyhow::anyhow!("`pty metadata patch --id` failed: {error}"))?;
910+
|pid| {
911+
if let Some(on_spawn) = &self.on_command_spawn {
912+
on_spawn(pid);
913+
}
914+
},
915+
);
916+
#[cfg(not(test))]
917+
let out = output_with_input_timeout(&mut command, PTY_LIST_TIMEOUT, Some(payload));
918+
let out =
919+
out.map_err(|error| anyhow::anyhow!("`pty metadata patch --id` failed: {error}"))?;
894920
if !out.status.success() {
895921
anyhow::bail!(
896922
"`pty metadata patch --id {}` failed: {}",
@@ -906,13 +932,26 @@ impl PtyCli {
906932
}
907933

908934
fn list_entries_at(&self, root: &Path) -> anyhow::Result<Vec<PtyListEntry>> {
935+
#[cfg(test)]
936+
let out = output_full_stdout_with_timeout_observed(
937+
Command::new(&self.bin)
938+
.args(["list", "--json"])
939+
.env("PTY_ROOT", root),
940+
PTY_LIST_TIMEOUT,
941+
|pid| {
942+
if let Some(on_spawn) = &self.on_command_spawn {
943+
on_spawn(pid);
944+
}
945+
},
946+
);
947+
#[cfg(not(test))]
909948
let out = output_full_stdout_with_timeout(
910949
Command::new(&self.bin)
911950
.args(["list", "--json"])
912951
.env("PTY_ROOT", root),
913952
PTY_LIST_TIMEOUT,
914-
)
915-
.map_err(|error| anyhow::anyhow!("`pty list --json` failed: {error}"))?;
953+
);
954+
let out = out.map_err(|error| anyhow::anyhow!("`pty list --json` failed: {error}"))?;
916955
if !out.status.success() {
917956
anyhow::bail!(
918957
"`pty list --json` failed: {}",
@@ -3385,6 +3424,27 @@ mod tests {
33853424
}
33863425
}
33873426

3427+
fn fixture_barrier_path(executable: &Path, suffix: &str) -> PathBuf {
3428+
PathBuf::from(format!("{}.{suffix}", executable.display()))
3429+
}
3430+
3431+
fn reset_fixture_barrier(executable: &Path) {
3432+
for suffix in ["ready", "release"] {
3433+
let marker = fixture_barrier_path(executable, suffix);
3434+
match std::fs::remove_file(&marker) {
3435+
Ok(()) => {}
3436+
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
3437+
Err(error) => panic!("remove stale fixture marker {}: {error}", marker.display()),
3438+
}
3439+
}
3440+
}
3441+
3442+
fn release_ready_fixture(pid: i32, executable: &Path, what: &str) {
3443+
let ready = fixture_barrier_path(executable, "ready");
3444+
await_fixture_ready(pid, &ready, what);
3445+
std::fs::write(fixture_barrier_path(executable, "release"), b"go\n").unwrap();
3446+
}
3447+
33883448
fn process_can_retain_cleanup_resources(pid: i32) -> bool {
33893449
#[cfg(target_os = "linux")]
33903450
if linux_process_state(pid) == Some('Z') {
@@ -5758,13 +5818,28 @@ mod tests {
57585818
let executable = temporary.path().join("pty-capture");
57595819
std::fs::write(
57605820
&executable,
5761-
"#!/bin/sh\nprintf '%s\\n' \"$@\" > \"$0.args\"\ncat > \"$0.stdin\"\n",
5821+
r#"#!/bin/sh
5822+
printf '%s\n' "$@" > "$0.args"
5823+
printf '' > "$0.ready.tmp"
5824+
mv "$0.ready.tmp" "$0.ready"
5825+
while [ ! -e "$0.release" ]; do sleep 0.01; done
5826+
cat > "$0.stdin"
5827+
"#,
57625828
)
57635829
.unwrap();
57645830
std::fs::set_permissions(&executable, std::fs::Permissions::from_mode(0o755)).unwrap();
5831+
reset_fixture_barrier(&executable);
5832+
let observed_executable = executable.clone();
57655833
let cli = PtyCli {
57665834
bin: executable.display().to_string(),
57675835
catalog_root: temporary.path().to_path_buf(),
5836+
on_command_spawn: Some(std::sync::Arc::new(move |pid| {
5837+
release_ready_fixture(
5838+
pid,
5839+
&observed_executable,
5840+
"fake PTY metadata command was not ready",
5841+
);
5842+
})),
57685843
};
57695844
let presentation = PtyPresentation {
57705845
pty_id: "stable.agent.id".to_owned(),
@@ -6680,16 +6755,32 @@ mod tests {
66806755
let fake = tmp.path().join("pty-bin");
66816756
std::fs::write(
66826757
&fake,
6683-
"#!/bin/sh\nrmdir \"$PTY_ROOT\"\nmkdir \"$PTY_ROOT\"\nprintf '%s\\n' '[]'\n",
6758+
r#"#!/bin/sh
6759+
rmdir "$PTY_ROOT"
6760+
mkdir "$PTY_ROOT"
6761+
printf '%s\n' '[]'
6762+
printf '' > "$0.ready.tmp"
6763+
mv "$0.ready.tmp" "$0.ready"
6764+
while [ ! -e "$0.release" ]; do sleep 0.01; done
6765+
"#,
66846766
)
66856767
.unwrap();
66866768
let mut permissions = std::fs::metadata(&fake).unwrap().permissions();
66876769
permissions.set_mode(0o755);
66886770
std::fs::set_permissions(&fake, permissions).unwrap();
66896771

6772+
reset_fixture_barrier(&fake);
6773+
let observed_fake = fake.clone();
66906774
let batch = PtyCli {
66916775
bin: fake.display().to_string(),
66926776
catalog_root: tmp.path().join("catalog"),
6777+
on_command_spawn: Some(std::sync::Arc::new(move |pid| {
6778+
release_ready_fixture(
6779+
pid,
6780+
&observed_fake,
6781+
"fake PTY inventory was not published",
6782+
);
6783+
})),
66936784
}
66946785
.task_observations_at_root(&HashSet::from(["h.worker"]), &root);
66956786
assert!(!batch.complete);
@@ -6778,6 +6869,7 @@ esac
67786869
let cli = PtyCli {
67796870
bin: fake.display().to_string(),
67806871
catalog_root: tmp.path().join("catalog"),
6872+
on_command_spawn: None,
67816873
};
67826874
let desired_ids = HashSet::from(["h.a", "h.b"]);
67836875
let unavailable = cli.task_observations_at_root(&desired_ids, &root);
@@ -6837,19 +6929,32 @@ esac
68376929
&fake,
68386930
r#"#!/bin/sh
68396931
printf '%s\n' '[{"name":"h.live","status":"running","pid":41,"createdAt":"2026-07-31T10:00:00.000Z","displayName":"Build owner","tags":{"agent.presentation.schema":"1","unrelated":"preserved"}},{"name":"h.exit","status":"exited","exitCode":0,"pid":42,"createdAt":"2026-07-31T09:00:00.000Z"},{"name":"h.gone","status":"vanished","pid":43,"createdAt":"2026-07-31T08:00:00.000Z"}]'
6932+
printf '' > "$0.ready.tmp"
6933+
mv "$0.ready.tmp" "$0.ready"
6934+
while [ ! -e "$0.release" ]; do sleep 0.01; done
68406935
"#,
68416936
)
68426937
.unwrap();
68436938
let mut permissions = std::fs::metadata(&fake).unwrap().permissions();
68446939
permissions.set_mode(0o755);
68456940
std::fs::set_permissions(&fake, permissions).unwrap();
68466941

6942+
let observed_fake = fake.clone();
68476943
let cli = PtyCli {
68486944
bin: fake.display().to_string(),
68496945
catalog_root: catalog,
6946+
on_command_spawn: Some(std::sync::Arc::new(move |pid| {
6947+
release_ready_fixture(
6948+
pid,
6949+
&observed_fake,
6950+
"fake PTY inventory was not published",
6951+
);
6952+
})),
68506953
};
68516954
let desired = HashSet::from(["h.live", "h.exit", "h.gone"]);
6955+
reset_fixture_barrier(&fake);
68526956
let first = cli.task_observations(&desired);
6957+
reset_fixture_barrier(&fake);
68536958
let second = cli.task_observations(&desired);
68546959
assert!(first.complete, "{:?}", first.errors);
68556960
assert_eq!(first, second, "same PTY evidence changed generation");
@@ -6862,6 +6967,7 @@ printf '%s\n' '[{"name":"h.live","status":"running","pid":41,"createdAt":"2026-0
68626967
assert_eq!(first.observations[1].state, ObservedState::Exited);
68636968
assert_eq!(first.observations[2].state, ObservedState::Vanished);
68646969

6970+
reset_fixture_barrier(&fake);
68656971
let sessions = cli.list_sessions().unwrap();
68666972
let presentation = sessions[0].presentation.as_ref().unwrap();
68676973
assert_eq!(presentation.display_name.as_deref(), Some("Build owner"));
@@ -6898,15 +7004,29 @@ printf '%s\n' '[{"name":"h.live","status":"running","pid":41,"createdAt":"2026-0
68987004
let fake = tmp.path().join("pty-bin");
68997005
std::fs::write(
69007006
&fake,
6901-
"#!/bin/sh\nprintf '%s\\n' '[{\"name\":\"h.live\",\"status\":\"running\"}]'\n",
7007+
r#"#!/bin/sh
7008+
printf '%s\n' '[{"name":"h.live","status":"running"}]'
7009+
printf '' > "$0.ready.tmp"
7010+
mv "$0.ready.tmp" "$0.ready"
7011+
while [ ! -e "$0.release" ]; do sleep 0.01; done
7012+
"#,
69027013
)
69037014
.unwrap();
69047015
let mut permissions = std::fs::metadata(&fake).unwrap().permissions();
69057016
permissions.set_mode(0o755);
69067017
std::fs::set_permissions(&fake, permissions).unwrap();
7018+
reset_fixture_barrier(&fake);
7019+
let observed_fake = fake.clone();
69077020
let batch = PtyCli {
69087021
bin: fake.display().to_string(),
69097022
catalog_root: catalog,
7023+
on_command_spawn: Some(std::sync::Arc::new(move |pid| {
7024+
release_ready_fixture(
7025+
pid,
7026+
&observed_fake,
7027+
"fake PTY inventory was not published",
7028+
);
7029+
})),
69107030
}
69117031
.task_observations(&HashSet::from(["h.live"]));
69127032
assert!(!batch.complete);

0 commit comments

Comments
 (0)