From 020198fb991665a4fc273ee8f82661b6f272fe87 Mon Sep 17 00:00:00 2001 From: Jeet Dekivadia Date: Mon, 8 Jun 2026 17:02:43 -0700 Subject: [PATCH 1/2] Stabilize buffered file2 diff output --- src/diff.rs | 14 +++++++++++++- tests/data/buffered_file2_base.vcd | 14 ++++++++++++++ tests/data/buffered_file2_extra.vcd | 18 ++++++++++++++++++ tests/data/expected/buffered_file2_base.cat | 4 ++++ tests/data/expected/buffered_file2_base.names | 3 +++ .../expected/buffered_file2_base.vcd.attrs | 3 +++ tests/data/expected/buffered_file2_extra.cat | 7 +++++++ tests/data/expected/buffered_file2_extra.names | 3 +++ .../expected/buffered_file2_extra.vcd.attrs | 3 +++ tests/diff_test.rs | 16 ++++++++++++++++ 10 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 tests/data/buffered_file2_base.vcd create mode 100644 tests/data/buffered_file2_extra.vcd create mode 100644 tests/data/expected/buffered_file2_base.cat create mode 100644 tests/data/expected/buffered_file2_base.names create mode 100644 tests/data/expected/buffered_file2_base.vcd.attrs create mode 100644 tests/data/expected/buffered_file2_extra.cat create mode 100644 tests/data/expected/buffered_file2_extra.names create mode 100644 tests/data/expected/buffered_file2_extra.vcd.attrs diff --git a/src/diff.rs b/src/diff.rs index 7ccdc4a..a1b6bda 100644 --- a/src/diff.rs +++ b/src/diff.rs @@ -370,12 +370,24 @@ fn compare_signal_channels( } // Anything still in the buffer was in file2 but never matched by file1. + // Sort rows before printing so diff output does not depend on HashMap + // iteration order. + let mut file2_only_rows = Vec::new(); for ((time, handle), value) in &buffered2 { has_differences = true; + let value = value.to_string(); for name in format_handle_names(*handle, &hier2.signal_map, &hier2.names) { - writeln!(writer, "{} {} {} (only in file2)", time, name, value)?; + file2_only_rows.push((*time, name, value.clone())); } } + file2_only_rows.sort_by(|a, b| { + a.0.cmp(&b.0) + .then_with(|| a.1.cmp(&b.1)) + .then_with(|| a.2.cmp(&b.2)) + }); + for (time, name, value) in file2_only_rows { + writeln!(writer, "{} {} {} (only in file2)", time, name, value)?; + } // Drain any remaining file2 batches we never read from the channel. if !source2_ended { diff --git a/tests/data/buffered_file2_base.vcd b/tests/data/buffered_file2_base.vcd new file mode 100644 index 0000000..c64ffc4 --- /dev/null +++ b/tests/data/buffered_file2_base.vcd @@ -0,0 +1,14 @@ +$timescale 1 ns $end +$scope module t $end +$var wire 1 ! a $end +$var wire 1 " b $end +$var wire 1 # c $end +$upscope $end +$enddefinitions $end +$dumpvars +0! +0" +0# +$end +#20 +1! diff --git a/tests/data/buffered_file2_extra.vcd b/tests/data/buffered_file2_extra.vcd new file mode 100644 index 0000000..860aa60 --- /dev/null +++ b/tests/data/buffered_file2_extra.vcd @@ -0,0 +1,18 @@ +$timescale 1 ns $end +$scope module t $end +$var wire 1 ! a $end +$var wire 1 " b $end +$var wire 1 # c $end +$upscope $end +$enddefinitions $end +$dumpvars +0! +0" +0# +$end +#10 +1! +1" +1# +#20 +1! diff --git a/tests/data/expected/buffered_file2_base.cat b/tests/data/expected/buffered_file2_base.cat new file mode 100644 index 0000000..5235bd4 --- /dev/null +++ b/tests/data/expected/buffered_file2_base.cat @@ -0,0 +1,4 @@ +0 t.a 0 +0 t.b 0 +0 t.c 0 +20 t.a 1 diff --git a/tests/data/expected/buffered_file2_base.names b/tests/data/expected/buffered_file2_base.names new file mode 100644 index 0000000..9c2198d --- /dev/null +++ b/tests/data/expected/buffered_file2_base.names @@ -0,0 +1,3 @@ +t.a +t.b +t.c diff --git a/tests/data/expected/buffered_file2_base.vcd.attrs b/tests/data/expected/buffered_file2_base.vcd.attrs new file mode 100644 index 0000000..bfb800d --- /dev/null +++ b/tests/data/expected/buffered_file2_base.vcd.attrs @@ -0,0 +1,3 @@ +t.a wire 1 +t.b wire 1 +t.c wire 1 diff --git a/tests/data/expected/buffered_file2_extra.cat b/tests/data/expected/buffered_file2_extra.cat new file mode 100644 index 0000000..eb446c5 --- /dev/null +++ b/tests/data/expected/buffered_file2_extra.cat @@ -0,0 +1,7 @@ +0 t.a 0 +0 t.b 0 +0 t.c 0 +10 t.a 1 +10 t.b 1 +10 t.c 1 +20 t.a 1 diff --git a/tests/data/expected/buffered_file2_extra.names b/tests/data/expected/buffered_file2_extra.names new file mode 100644 index 0000000..9c2198d --- /dev/null +++ b/tests/data/expected/buffered_file2_extra.names @@ -0,0 +1,3 @@ +t.a +t.b +t.c diff --git a/tests/data/expected/buffered_file2_extra.vcd.attrs b/tests/data/expected/buffered_file2_extra.vcd.attrs new file mode 100644 index 0000000..bfb800d --- /dev/null +++ b/tests/data/expected/buffered_file2_extra.vcd.attrs @@ -0,0 +1,3 @@ +t.a wire 1 +t.b wire 1 +t.c wire 1 diff --git a/tests/diff_test.rs b/tests/diff_test.rs index 4c83610..571d2ac 100644 --- a/tests/diff_test.rs +++ b/tests/diff_test.rs @@ -137,6 +137,22 @@ fn test_edge_time_diff() { assert_eq!(output, expected, "Expected exact diff output"); } +#[test] +fn test_buffered_file2_only_diffs_are_sorted() { + let (has_diff, output) = run_wave_diff_test( + "tests/data/buffered_file2_base.vcd", + "tests/data/buffered_file2_extra.vcd", + ); + assert!(has_diff, "file2-only intermediate changes should differ"); + + let expected = "\ +10 t.a 1 (only in file2) +10 t.b 1 (only in file2) +10 t.c 1 (only in file2) +"; + assert_eq!(output, expected, "Expected deterministic file2-only diff output"); +} + #[test] fn test_new_sig_diff() { let (has_name_diff, msg) = check_signal_names( From 211e4627672b88f6ff5151b0bd33dc2781f9651b Mon Sep 17 00:00:00 2001 From: Jeet Dekivadia Date: Tue, 9 Jun 2026 12:03:53 -0700 Subject: [PATCH 2/2] Avoid cloning buffered diff values --- src/diff.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/diff.rs b/src/diff.rs index a1b6bda..1e9aed3 100644 --- a/src/diff.rs +++ b/src/diff.rs @@ -372,21 +372,19 @@ fn compare_signal_channels( // Anything still in the buffer was in file2 but never matched by file1. // Sort rows before printing so diff output does not depend on HashMap // iteration order. - let mut file2_only_rows = Vec::new(); - for ((time, handle), value) in &buffered2 { + let owned: Vec<((u64, usize), OwnedSignalValue)> = buffered2.drain().collect(); + if !owned.is_empty() { has_differences = true; - let value = value.to_string(); + } + let mut file2_only_rows: Vec<(u64, String, usize)> = Vec::new(); + for (idx, ((time, handle), _)) in owned.iter().enumerate() { for name in format_handle_names(*handle, &hier2.signal_map, &hier2.names) { - file2_only_rows.push((*time, name, value.clone())); + file2_only_rows.push((*time, name, idx)); } } - file2_only_rows.sort_by(|a, b| { - a.0.cmp(&b.0) - .then_with(|| a.1.cmp(&b.1)) - .then_with(|| a.2.cmp(&b.2)) - }); - for (time, name, value) in file2_only_rows { - writeln!(writer, "{} {} {} (only in file2)", time, name, value)?; + file2_only_rows.sort_by(|a, b| a.0.cmp(&b.0).then_with(|| a.1.cmp(&b.1))); + for (time, name, idx) in file2_only_rows { + writeln!(writer, "{} {} {} (only in file2)", time, name, owned[idx].1)?; } // Drain any remaining file2 batches we never read from the channel.