diff --git a/crates/tty7-core/src/core/git/log.rs b/crates/tty7-core/src/core/git/log.rs index 538fe23d..d1218148 100644 --- a/crates/tty7-core/src/core/git/log.rs +++ b/crates/tty7-core/src/core/git/log.rs @@ -2112,6 +2112,53 @@ mod tests { assert!(run(host, repo, &["commit", "--quiet", "-m", message])); } + /// The other half of `a_stream_the_parse_cannot_finish_is_never_called_complete`, + /// which asserted only that the *parse* raises the flag. What the flag is + /// for is `load_page`, and both halves of its test are needed: git answered + /// with fewer commits than asked for, which on its own reads as the end of + /// history, *and* a record the parse had to drop. Taking the count alone + /// would call a graph missing its middle complete, and paging would stop + /// there for good — the history below it unreachable, with nothing on + /// screen to say so. + #[test] + fn a_page_the_parse_had_to_cut_is_not_the_end_of_history() { + let host = crate::host::local::LocalHost::new(); + let Some(scratch) = scratch("cut-page") else { + return; + }; + let repo = scratch.0.as_path(); + if !init_repo(&*host, repo) { + return; + } + commit_file(&*host, repo, "a.txt", "1\n", "first"); + + // A commit message past MAX_RECORD, which the splitter drops whole. + // Written through a file: an argument this size is over the exec limit + // on macOS, and would fail as a bad command rather than a big commit. + let huge = repo.join("msg.txt"); + std::fs::write(&huge, "z".repeat(super::super::MAX_RECORD + 1)).unwrap(); + std::fs::write(repo.join("b.txt"), "2\n").unwrap(); + assert!(run(&*host, repo, &["add", "--", "b.txt"])); + assert!(run( + &*host, + repo, + &["commit", "--quiet", "-F", huge.to_str().unwrap()] + )); + + // Thirty asked for and two exist, so the count alone says complete. + let page = + load_page(&*host, repo, &GraphScope::Head, 30).expect("a repository was just created"); + assert_eq!( + page.commits.len(), + 1, + "the record over the bound is dropped, the readable one survives" + ); + assert!( + !page.complete, + "a page the parse had to cut is not the end of history, however few commits came back" + ); + } + #[test] fn a_real_repository_answers_for_one_commit_and_its_files() { let host = crate::host::local::LocalHost::new(); diff --git a/src/terminal/remote.rs b/src/terminal/remote.rs index 5611d7ee..318cead0 100644 --- a/src/terminal/remote.rs +++ b/src/terminal/remote.rs @@ -3802,6 +3802,43 @@ mod tests { draining.join().unwrap(); } + /// The exemption a huge frame gets is for *one* of them. A paste larger + /// than the whole bound goes onto an empty queue and lifts the bound by its + /// own size while it is outstanding — but a second one arriving before the + /// first has moved a byte is not a paste, it is a link that has stopped + /// taking input, and letting it through too would trade the bound for a + /// heap that grows by five megabytes a go for as long as the peer is gone. + /// + /// The empty-queue half of that rule is the whole of it: without it every + /// oversize frame is exempt, and a stalled link accepts them forever. + #[test] + fn a_second_oversize_frame_onto_a_stalled_link_is_still_a_backlog() { + crate::core::config::pin_test_config_dir(); + let (client_side, daemon_side) = UnixStream::pair().unwrap(); + let term = RemoteTerminal::from_stream(client_side, TermSize::new(80, 24)).unwrap(); + + // Held open and never read from: the peer is parked, not gone. A closed + // peer would fail the write outright and report the link lost for a + // different reason than the one under test. + let frame = MAX_BACKLOG + (1 << 20); + term.write(vec![b'x'; frame]); + assert!( + !term.exited_flag.load(Ordering::SeqCst), + "the first oversize frame is a paste and goes through" + ); + + // Nothing has drained, so the first frame is still charged in full — + // the sender parks inside `write_all` and discounts a frame only once + // it is out. The bound stands at MAX_BACKLOG + this frame, and a second + // frame of the same size is over it. + term.write(vec![b'x'; frame]); + assert!( + term.exited_flag.load(Ordering::SeqCst), + "a second oversize frame with the first still outstanding is a link nothing is reading" + ); + drop(daemon_side); + } + /// Input the link refuses used to vanish: `write` threw the error away, so a /// pane whose daemon had stopped reading kept taking keystrokes into /// nothing. The refusal now marks the pane exited by the reader's own signal