Skip to content

Commit 84fd829

Browse files
committed
Add support for diffing Raw output
1 parent 88d07bf commit 84fd829

File tree

1 file changed

+211
-17
lines changed

1 file changed

+211
-17
lines changed

tests/difftests/bin/src/differ.rs

Lines changed: 211 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,47 @@ impl OutputDiffer for RawDiffer {
5555
if output1 == output2 {
5656
vec![]
5757
} else {
58-
// For raw comparison, we just note that they differ
59-
vec![Difference {
60-
index: 0,
61-
value1: format!("{} bytes", output1.len()),
62-
value2: format!("{} bytes", output2.len()),
63-
absolute_diff: DiffMagnitude::Incomparable,
64-
relative_diff: DiffMagnitude::Incomparable,
65-
}]
58+
let mut differences = Vec::new();
59+
let max_len = std::cmp::max(output1.len(), output2.len());
60+
61+
// Find byte-level differences
62+
for i in 0..max_len {
63+
let byte1 = output1.get(i);
64+
let byte2 = output2.get(i);
65+
66+
match (byte1, byte2) {
67+
(Some(&b1), Some(&b2)) if b1 != b2 => {
68+
differences.push(Difference {
69+
index: i,
70+
value1: format!("{}", b1),
71+
value2: format!("{}", b2),
72+
absolute_diff: DiffMagnitude::Incomparable,
73+
relative_diff: DiffMagnitude::Incomparable,
74+
});
75+
}
76+
(Some(&b1), None) => {
77+
differences.push(Difference {
78+
index: i,
79+
value1: format!("{}", b1),
80+
value2: "".to_string(),
81+
absolute_diff: DiffMagnitude::Incomparable,
82+
relative_diff: DiffMagnitude::Incomparable,
83+
});
84+
}
85+
(None, Some(&b2)) => {
86+
differences.push(Difference {
87+
index: i,
88+
value1: "".to_string(),
89+
value2: format!("{}", b2),
90+
absolute_diff: DiffMagnitude::Incomparable,
91+
relative_diff: DiffMagnitude::Incomparable,
92+
});
93+
}
94+
_ => {} // bytes are equal
95+
}
96+
}
97+
98+
differences
6699
}
67100
}
68101

@@ -72,18 +105,118 @@ impl OutputDiffer for RawDiffer {
72105
}
73106

74107
impl DifferenceDisplay for RawDiffer {
75-
fn format_table(&self, _diffs: &[Difference], _pkg1: &str, _pkg2: &str) -> String {
76-
"Binary files differ".to_string()
108+
fn format_table(&self, diffs: &[Difference], pkg1: &str, pkg2: &str) -> String {
109+
use tabled::settings::{Alignment, Modify, Span, Style, object::Rows};
110+
111+
let rows: Vec<Vec<String>> = diffs
112+
.iter()
113+
.take(10)
114+
.map(|d| {
115+
let (hex1, dec1, ascii1) = if d.value1.is_empty() {
116+
("--".to_string(), "--".to_string(), "--".to_string())
117+
} else {
118+
let byte = d.value1.parse::<u8>().unwrap();
119+
let ascii = if byte.is_ascii_graphic() || byte == b' ' {
120+
format!("{}", byte as char)
121+
} else {
122+
match byte {
123+
b'\n' => "\\n".to_string(),
124+
b'\r' => "\\r".to_string(),
125+
b'\t' => "\\t".to_string(),
126+
b'\0' => "\\0".to_string(),
127+
_ => "".to_string(), // Empty for non-printable
128+
}
129+
};
130+
(
131+
format!("{:>3}", format!("{:02x}", byte)),
132+
format!("{:3}", byte),
133+
format!("{:^5}", ascii),
134+
)
135+
};
136+
137+
let (hex2, dec2, ascii2) = if d.value2.is_empty() {
138+
("--".to_string(), "--".to_string(), "--".to_string())
139+
} else {
140+
let byte = d.value2.parse::<u8>().unwrap();
141+
let ascii = if byte.is_ascii_graphic() || byte == b' ' {
142+
format!("{}", byte as char)
143+
} else {
144+
match byte {
145+
b'\n' => "\\n".to_string(),
146+
b'\r' => "\\r".to_string(),
147+
b'\t' => "\\t".to_string(),
148+
b'\0' => "\\0".to_string(),
149+
_ => "".to_string(), // Empty for non-printable
150+
}
151+
};
152+
(
153+
format!("{:>3}", format!("{:02x}", byte)),
154+
format!("{:3}", byte),
155+
format!("{:^5}", ascii),
156+
)
157+
};
158+
159+
vec![
160+
format!("0x{:04x}", d.index),
161+
hex1,
162+
dec1,
163+
ascii1,
164+
hex2,
165+
dec2,
166+
ascii2,
167+
]
168+
})
169+
.collect();
170+
171+
let mut builder = tabled::builder::Builder::default();
172+
173+
// Header rows
174+
builder.push_record(vec!["Offset", pkg1, "", "", pkg2, "", ""]);
175+
builder.push_record(vec!["", "Hex", "Dec", "ASCII", "Hex", "Dec", "ASCII"]);
176+
177+
for row in &rows {
178+
builder.push_record(row);
179+
}
180+
181+
let mut table = builder.build();
182+
table
183+
.with(Style::modern())
184+
.with(Modify::new(Rows::new(0..)).with(Alignment::center()))
185+
// Apply column spans to merge the package names across their columns
186+
.modify((0, 1), Span::column(3))
187+
.modify((0, 4), Span::column(3))
188+
// Remove the borders between merged cells
189+
.with(tabled::settings::style::BorderSpanCorrection);
190+
191+
let mut result = table.to_string();
192+
193+
if diffs.len() > 10 {
194+
let last_line_width = result
195+
.lines()
196+
.last()
197+
.map(|l| l.chars().count())
198+
.unwrap_or(0);
199+
result.push_str(&format!(
200+
"\n{:>width$}",
201+
format!("... {} more differences", diffs.len() - 10),
202+
width = last_line_width
203+
));
204+
}
205+
206+
result
77207
}
78208

79209
fn format_report(
80210
&self,
81-
_diffs: &[Difference],
211+
diffs: &[Difference],
82212
pkg1: &str,
83213
pkg2: &str,
84214
_epsilon: Option<f32>,
85215
) -> String {
86-
format!("Binary outputs from {} and {} differ", pkg1, pkg2)
216+
let mut report = format!("Total differences: {} bytes\n\n", diffs.len());
217+
report.push_str(&self.format_table(diffs, pkg1, pkg2));
218+
219+
report
87220
}
88221

89222
fn write_human_readable(&self, output: &[u8], path: &std::path::Path) -> std::io::Result<()> {
@@ -456,11 +589,72 @@ mod tests {
456589
let bytes2 = b"world";
457590

458591
let diffs = differ.compare(bytes1, bytes2, None);
459-
assert_eq!(diffs.len(), 1);
460-
match &diffs[0].absolute_diff {
461-
DiffMagnitude::Incomparable => {}
462-
_ => panic!("Expected incomparable diff for raw bytes"),
463-
}
592+
assert_eq!(diffs.len(), 4); // 4 bytes differ (l at position 3 is same in both)
593+
594+
// Check first difference (h vs w)
595+
assert_eq!(diffs[0].index, 0);
596+
assert_eq!(diffs[0].value1, "104"); // h = 104
597+
assert_eq!(diffs[0].value2, "119"); // w = 119
598+
599+
// Check second difference (e vs o)
600+
assert_eq!(diffs[1].index, 1);
601+
assert_eq!(diffs[1].value1, "101"); // 'e' = 101
602+
assert_eq!(diffs[1].value2, "111"); // 'o' = 111
603+
604+
// Check third difference (first l vs r)
605+
assert_eq!(diffs[2].index, 2);
606+
assert_eq!(diffs[2].value1, "108"); // 'l' = 108
607+
assert_eq!(diffs[2].value2, "114"); // 'r' = 114
608+
609+
// Check fourth difference (o vs d)
610+
assert_eq!(diffs[3].index, 4);
611+
assert_eq!(diffs[3].value1, "111"); // 'o' = 111
612+
assert_eq!(diffs[3].value2, "100"); // 'd' = 100
613+
}
614+
615+
#[test]
616+
fn test_raw_differ_partial_match() {
617+
let differ = RawDiffer;
618+
let bytes1 = b"hello world";
619+
let bytes2 = b"hello earth";
620+
621+
let diffs = differ.compare(bytes1, bytes2, None);
622+
assert_eq!(diffs.len(), 4); // 4 bytes differ in "world" vs "earth" (r at position 8 is same)
623+
624+
// First difference should be at index 6 (w vs e)
625+
assert_eq!(diffs[0].index, 6);
626+
assert_eq!(diffs[0].value1, "119"); // 'w' = 119
627+
assert_eq!(diffs[0].value2, "101"); // 'e' = 101
628+
629+
// Second difference at index 7 (o vs a)
630+
assert_eq!(diffs[1].index, 7);
631+
assert_eq!(diffs[1].value1, "111"); // 'o' = 111
632+
assert_eq!(diffs[1].value2, "97"); // 'a' = 97
633+
634+
// Third difference at index 9 (l vs t)
635+
assert_eq!(diffs[2].index, 9);
636+
assert_eq!(diffs[2].value1, "108"); // 'l' = 108
637+
assert_eq!(diffs[2].value2, "116"); // 't' = 116
638+
639+
// Fourth difference at index 10 (d vs h)
640+
assert_eq!(diffs[3].index, 10);
641+
assert_eq!(diffs[3].value1, "100"); // 'd' = 100
642+
assert_eq!(diffs[3].value2, "104"); // 'h' = 104
643+
}
644+
645+
#[test]
646+
fn test_raw_differ_different_lengths() {
647+
let differ = RawDiffer;
648+
let bytes1 = b"hello";
649+
let bytes2 = b"hello world";
650+
651+
let diffs = differ.compare(bytes1, bytes2, None);
652+
assert_eq!(diffs.len(), 6); // " world" = 6 extra bytes
653+
654+
// Check that missing bytes are shown as empty string
655+
assert_eq!(diffs[0].index, 5);
656+
assert_eq!(diffs[0].value1, "");
657+
assert_eq!(diffs[0].value2, "32"); // ' ' = 32
464658
}
465659

466660
#[test]

0 commit comments

Comments
 (0)