Files
rtk-ai__rtk/tests/diff_byte_accuracy_test.rs
Ilia Alshanetsky d4239ecb90 fix(diff): align by LCS and name the cause of an invisible difference
develop now carries the byte-equality verdict (0cb34ac, 428af61), so this
branch no longer argues for it. The alignment fix and the reporting that
depends on it remain.

compute_diff compared line N to line N, so a single inserted or deleted
line reported every line after it as modified. This aligns by Myers' greedy
edit script, so one insertion reads as one insertion.

The script folds into hunks, one per run of differing lines with no
matched line between. Both renderers group by hunk: the classic fallback
prints one NcM per region, as diff does, because the classic format cannot
express which old line became which new line, and rendering the pairing
split a region into an NcM plus a trailing NaM that diff never prints.
Inside a hunk the condensed render pairs old and new lines by best
similarity rather than by position, so an insertion at the head of a run
no longer shifts every pairing after it into a rewrite that did not happen.

Op::Keep carries a run length. One entry per matched line made the script
scale with the file rather than with the change, 45MB of separators for a
million-line pair with 700 rewrites. The aligner's trace is one flat vector
with each round's bookkeeping stored inline, so MAX_TRACE_CELLS charges
what is allocated: a per-round Vec cost 5.8x the cells being counted on a
lopsided pair.

The bool develop passes into render_diff cannot express the third case the
aligner produces, a comparison that ran and then refused to build a listing
because it would cost more than the raw text. FileComparison replaces the
bool with Identical, InvisibleDifference and Lines, and DiffResult::unaligned
carries the refusal reason. An empty change list is not a synonym for
identical, and routing a refusal through the identical branch exits 0 on
files that differ.

The invisible-difference message keeps develop's wording, so the phrase
diff_byte_accuracy_test matches on survives, and then names the measured
cause instead of stopping at "no line-content change". It is shown whenever
the case arises. A fixed token allowance above raw could not be met: the
message's shortest form is ~20 tokens and a one-line pair ~2, so the ceiling
sat under the message's floor and dropped it on 90% of one-line pairs,
printing the two indistinguishable blobs it exists to replace.

The render drops the file-pair header and the blank line, names the files
in the frame legend by argument position instead of by path, and prints
the counts line only once the listing runs to a screenful. On 120
agent-sized diffs the framing cost more than the change list saved: the
body alone is 7% smaller than diff, and each line of chrome erased that.
2026-09-05 15:56:58 -04:00

65 lines
2.2 KiB
Rust

use std::fs;
use std::path::Path;
use std::process::{Command, Output};
const RTK_BIN: &str = env!("CARGO_BIN_EXE_rtk");
const DIFF_SUBCOMMAND: &str = "diff";
const LF_FILE: &str = "lf.txt";
const CRLF_FILE: &str = "crlf.txt";
const LF_CONTENT: &str = "alpha\nbeta\n";
const CRLF_CONTENT: &str = "alpha\r\nbeta\r\n";
const ONE_LINE_LF_CONTENT: &str = "x\n";
const ONE_LINE_CRLF_CONTENT: &str = "x\r\n";
const BOTH_FILES_SEPARATOR: &str = "\n---\n";
const WHITESPACE_ONLY_MESSAGE: &str = "whitespace or line endings";
const IDENTICAL_MESSAGE: &str = "[ok] Files are identical";
const DIFF_EXIT_CODE: i32 = 1;
fn run_rtk_diff(file1: &Path, file2: &Path) -> Output {
let file1 = file1.display().to_string();
let file2 = file2.display().to_string();
Command::new(RTK_BIN)
.args([DIFF_SUBCOMMAND, &file1, &file2])
.output()
.expect("run rtk diff")
}
fn assert_explains_invisible_difference(lf_content: &str, crlf_content: &str) {
let dir = tempfile::tempdir().expect("tempdir");
let lf = dir.path().join(LF_FILE);
let crlf = dir.path().join(CRLF_FILE);
fs::write(&lf, lf_content).expect("write LF fixture");
fs::write(&crlf, crlf_content).expect("write CRLF fixture");
let output = run_rtk_diff(&lf, &crlf);
let stdout = String::from_utf8(output.stdout).expect("stdout utf8");
assert_eq!(output.status.code(), Some(DIFF_EXIT_CODE), "{stdout}");
assert!(
stdout.contains(WHITESPACE_ONLY_MESSAGE),
"CRLF-vs-LF diff should explain the byte-only difference:\n{stdout}"
);
assert!(
!stdout.contains(BOTH_FILES_SEPARATOR),
"the two indistinguishable blobs must not replace the explanation:\n{stdout}"
);
assert!(
!stdout.contains(IDENTICAL_MESSAGE),
"byte-different files must not be reported identical:\n{stdout}"
);
}
#[test]
fn small_crlf_vs_lf_diff_prints_whitespace_message() {
assert_explains_invisible_difference(LF_CONTENT, CRLF_CONTENT);
}
#[test]
fn one_line_crlf_vs_lf_diff_prints_whitespace_message() {
// The message is ~20 tokens and a one-line pair ~2, so any fixed allowance
// above raw drops it here. It is shown regardless of size.
assert_explains_invisible_difference(ONE_LINE_LF_CONTENT, ONE_LINE_CRLF_CONTENT);
}