diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-05-31 01:11:06 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-05-31 01:11:06 +0300 |
| commit | 9586ee9c3fe080efbb43e6874911fd974290d7fb (patch) | |
| tree | 6db158a0140ad2691c79c2a0d8ccbea1ebacb4c7 /tests/multi_client.rs | |
| parent | bdc189e32e7c9dd61811fe17e50ac171ca94c647 (diff) | |
test: harden ci flake diagnostics (#365)
Diffstat (limited to 'tests/multi_client.rs')
| -rw-r--r-- | tests/multi_client.rs | 73 |
1 files changed, 49 insertions, 24 deletions
diff --git a/tests/multi_client.rs b/tests/multi_client.rs index 3080b677..57e689c2 100644 --- a/tests/multi_client.rs +++ b/tests/multi_client.rs @@ -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); |
