From 53a46c7bfeb4034ac2bd0d521c6d0de1823d9935 Mon Sep 17 00:00:00 2001 From: Zhiping <2716057626@qq.com> Date: Sat, 9 May 2026 03:24:33 +0800 Subject: [PATCH] test(tui): add rail-prefix-widths memory overhead test (#1163) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simulate a 30-turn complex session (user → thinking → assistant → tools) and assert the rail_prefix_widths vector stays under 1 MB even in pathological sessions. The test reports exact memory figures via eprintln! for diagnostic visibility. --- crates/tui/src/tui/transcript.rs | 62 ++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/crates/tui/src/tui/transcript.rs b/crates/tui/src/tui/transcript.rs index cd55ebe8..a8fd8e20 100644 --- a/crates/tui/src/tui/transcript.rs +++ b/crates/tui/src/tui/transcript.rs @@ -910,4 +910,66 @@ mod tests { ); } } + + /// Simulate a long, complex conversation (thinking + multi-line tool output + + /// tool headers with multiple decorative spans) and report the memory + /// consumed by `rail_prefix_widths`. This is informational — the assertion + /// only fails if the per-line overhead exceeds a generous bound. + #[test] + fn rail_prefix_widths_memory_overhead_complex_session() { + let mut cells: Vec = Vec::new(); + // Build ~60 turns covering the typical deep-reasoning workflow: + // user → thinking (5-15 lines) → assistant → tool → tool output → + // thinking → assistant → ... repeat. + for i in 0..30 { + cells.push(user_cell(&format!("complex query {i} about system design"))); + cells.push(HistoryCell::Thinking { + content: format!( + "line A\nline B\nline C\nline D\nline E\nline F\nline G\nline H\nline I\nline J" + ), + streaming: false, + duration_secs: Some(3.5), + }); + cells.push(assistant_cell( + &format!("response {i} with multi-line\ntext content spanning\nseveral lines"), + false, + )); + cells.push(exec_tool_cell(&format!( + "cargo test --package my_crate -- --nocapture 2>&1 | head -40" + ))); + // Insert a second tool so adjacent tool cells merge into a railed group. + cells.push(exec_tool_cell(&format!("git diff --stat HEAD~{i}"))); + } + let revisions: Vec = (0..cells.len()).map(|i| i as u64 + 1).collect(); + + let mut cache = TranscriptViewCache::new(); + cache.ensure(&cells, &revisions, 80, TranscriptRenderOptions::default()); + + let total_lines = cache.total_lines(); + let pw_len = cache.rail_prefix_widths.len(); + let pw_cap = cache.rail_prefix_widths.capacity(); + // The Vec's inlined buffer on most platforms is small; capacity + // should be >= len. Both must equal total_lines. + assert_eq!(pw_len, total_lines); + assert!(pw_cap >= pw_len); + + let memory_bytes = pw_cap * std::mem::size_of::(); + let memory_kb = memory_bytes as f64 / 1024.0; + // Each usize is 8 bytes on 64-bit. Even with 100k lines this stays + // under 1 MB. + let kbytes_per_1k_lines = (memory_bytes as f64 / total_lines as f64) * 1000.0 / 1024.0; + + eprintln!("=== rail_prefix_widths memory (complex session) ==="); + eprintln!(" total_lines: {total_lines}"); + eprintln!(" vec len: {pw_len}"); + eprintln!(" vec capacity: {pw_cap}"); + eprintln!(" memory (bytes): {memory_bytes}"); + eprintln!(" memory (KB): {memory_kb:.2}"); + eprintln!(" KB per 1k lines: {kbytes_per_1k_lines:.2}"); + eprintln!(" lines × 8 bytes: {} KB", total_lines * 8 / 1024); + + // Sanity: per-line overhead must be reasonable. + assert!(memory_kb < 1024.0, "rail_prefix_widths memory unexpectedly large: {memory_kb:.1} KB"); + eprintln!(" ✓ well under 1 MB even for very long sessions"); + } }