test: harden ci flake diagnostics (#365)

This commit is contained in:
Can Celik 2026-05-31 01:11:06 +03:00 committed by GitHub
parent bdc189e32e
commit 9586ee9c3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 54 additions and 28 deletions

View File

@ -1230,7 +1230,7 @@ mod tests {
let repo = create_committed_repo("api-worktree-open-source-repo");
let event_hub = crate::api::EventHub::default();
let mut app = test_app_with_event_hub(event_hub.clone());
app.state.default_shell = "/bin/true".into();
app.state.default_shell = "/usr/bin/true".into();
let response = app.handle_api_request(Request {
id: "req".into(),
@ -1242,7 +1242,9 @@ mod tests {
}),
});
let success: SuccessResponse = serde_json::from_str(&response).unwrap();
let success: SuccessResponse = serde_json::from_str(&response).unwrap_or_else(|err| {
panic!("expected success response, got {response}: {err}");
});
let ResponseResult::WorktreeOpened {
workspace,
already_open,

View File

@ -755,8 +755,7 @@ fn live_handoff_accepts_old_pane_id_from_child_env() {
"params": {"pane_id": pane_id, "text": format!("printf '%s' \"$HERDR_PANE_ID\" > {}", pane_id_marker.display()), "keys": ["Enter"]}
}),
));
support::wait_for_file(&pane_id_marker, Duration::from_secs(5));
let old_pane_id = fs::read_to_string(&pane_id_marker).unwrap();
let old_pane_id = wait_for_file_contains(&pane_id_marker, "p_", Duration::from_secs(5));
assert!(
old_pane_id.starts_with("p_"),
"unexpected pane id from env: {old_pane_id:?}"

View File

@ -2,6 +2,7 @@
mod support;
use std::collections::VecDeque;
use std::fs;
use std::io::{self, BufRead, BufReader, Read, Write};
use std::os::unix::net::UnixStream;
@ -190,6 +191,20 @@ fn count_log_occurrences(path: &Path, needle: &str) -> usize {
.unwrap_or(0)
}
fn log_tail(path: &Path, lines: usize) -> String {
let Ok(text) = fs::read_to_string(path) else {
return format!("could not read {}", path.display());
};
let mut tail = VecDeque::with_capacity(lines);
for line in text.lines() {
if tail.len() == lines {
tail.pop_front();
}
tail.push_back(line.to_string());
}
tail.into_iter().collect::<Vec<_>>().join("\n")
}
fn wait_for_log_occurrence_count(
path: &Path,
needle: &str,
@ -673,12 +688,13 @@ fn wait_for_frame(stream: &mut UnixStream, timeout: Duration) -> bool {
false
}
fn wait_for_frame_matching(
fn wait_for_frame_matching_with_snapshots(
stream: &mut UnixStream,
timeout: Duration,
predicate: impl Fn(&FrameWire) -> bool,
) -> io::Result<bool> {
) -> io::Result<(bool, Vec<String>)> {
let deadline = Instant::now() + timeout;
let mut snapshots = VecDeque::with_capacity(5);
while Instant::now() < deadline {
let slice = deadline
.saturating_duration_since(Instant::now())
@ -686,8 +702,12 @@ fn wait_for_frame_matching(
match read_server_message_payload(stream, slice) {
Ok((1, frame_payload)) => {
let frame = decode_frame_payload(&frame_payload)?;
if snapshots.len() == 5 {
snapshots.pop_front();
}
snapshots.push_back(frame_text(&frame));
if predicate(&frame) {
return Ok(true);
return Ok((true, snapshots.into_iter().collect()));
}
}
Ok((_variant, _payload)) => {}
@ -696,12 +716,12 @@ fn wait_for_frame_matching(
}
}
Ok(false)
Ok((false, snapshots.into_iter().collect()))
}
fn frame_contains_text(frame: &FrameWire, needle: &str) -> bool {
fn frame_text(frame: &FrameWire) -> String {
if frame.cells.is_empty() {
return false;
return String::new();
}
let row_width = frame.width.max(1) as usize;
@ -720,7 +740,11 @@ fn frame_contains_text(frame: &FrameWire, needle: &str) -> bool {
let _ = (cursor.x, cursor.y, cursor.visible, cursor.shape);
}
full_text.contains(needle)
full_text
}
fn frame_contains_text(frame: &FrameWire, needle: &str) -> bool {
frame_text(frame).contains(needle)
}
#[test]
@ -791,7 +815,7 @@ fn multi_client_effective_size_shrinks_when_smaller_client_joins() {
}
#[test]
fn multi_client_broadcasts_frame_updates_to_all_clients_within_500ms() {
fn multi_client_broadcasts_frame_updates_to_all_clients() {
let _lock = test_lock();
let base = unique_test_dir();
let config_home = base.join("config");
@ -822,25 +846,26 @@ fn multi_client_broadcasts_frame_updates_to_all_clients_within_500ms() {
.unwrap_or(0)
);
let start = Instant::now();
send_client_input(&mut client_a, marker.as_bytes());
let received = wait_for_frame_matching(&mut client_b, Duration::from_millis(500), |frame| {
frame_contains_text(frame, &marker)
})
.expect("frame decoding should succeed");
send_client_input(&mut client_a, format!("echo {marker}\n").as_bytes());
if !pane_read_recent_contains(&api_socket, &pane_id, &marker, Duration::from_secs(5)) {
panic!(
"pane output should include client A marker so broadcast reflects a real state change. pane output:\n{}\nserver log tail:\n{}",
pane_read_recent(&api_socket, &pane_id, 200),
log_tail(&server_log_path(&config_home), 80)
);
}
let (received, client_b_frames) =
wait_for_frame_matching_with_snapshots(&mut client_b, Duration::from_secs(10), |frame| {
frame_contains_text(frame, &marker)
})
.expect("frame decoding should succeed");
assert!(
received,
"client B should receive a broadcast frame containing client A marker within 500ms"
);
assert!(
start.elapsed() <= Duration::from_millis(500),
"broadcast frame containing marker should arrive within 500ms, got {:?}",
start.elapsed()
);
assert!(
pane_read_recent_contains(&api_socket, &pane_id, &marker, Duration::from_secs(5)),
"pane output should include client A marker so broadcast reflects a real state change"
"client B should receive a broadcast frame containing client A marker. pane output:\n{}\nclient B frame snapshots:\n{}\nserver log tail:\n{}",
pane_read_recent(&api_socket, &pane_id, 200),
client_b_frames.join("\n--- frame ---\n"),
log_tail(&server_log_path(&config_home), 80)
);
cleanup_spawned_herdr(server, base);