mirror of
https://github.com/rtk-ai/rtk.git
synced 2026-09-19 07:33:17 +08:00
d4239ecb90
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.
65 lines
2.2 KiB
Rust
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);
|
|
}
|