Skip to content

Commit 59909d2

Browse files
committed
Add support for diffing Raw output
1 parent e73af41 commit 59909d2

File tree

1 file changed

+200
-17
lines changed

1 file changed

+200
-17
lines changed

tests/difftests/bin/src/differ.rs

Lines changed: 200 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,53 @@ pub struct Difference {
5050
/// Differ for raw byte comparison
5151
pub struct RawDiffer;
5252

53+
5354
impl OutputDiffer for RawDiffer {
5455
fn compare(&self, output1: &[u8], output2: &[u8], _epsilon: Option<f32>) -> Vec<Difference> {
5556
if output1 == output2 {
5657
vec![]
5758
} 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-
}]
59+
let mut differences = Vec::new();
60+
let max_len = std::cmp::max(output1.len(), output2.len());
61+
62+
// Find byte-level differences
63+
for i in 0..max_len {
64+
let byte1 = output1.get(i);
65+
let byte2 = output2.get(i);
66+
67+
match (byte1, byte2) {
68+
(Some(&b1), Some(&b2)) if b1 != b2 => {
69+
differences.push(Difference {
70+
index: i,
71+
value1: format!("{}", b1),
72+
value2: format!("{}", b2),
73+
absolute_diff: DiffMagnitude::Incomparable,
74+
relative_diff: DiffMagnitude::Incomparable,
75+
});
76+
}
77+
(Some(&b1), None) => {
78+
differences.push(Difference {
79+
index: i,
80+
value1: format!("{}", b1),
81+
value2: "".to_string(),
82+
absolute_diff: DiffMagnitude::Incomparable,
83+
relative_diff: DiffMagnitude::Incomparable,
84+
});
85+
}
86+
(None, Some(&b2)) => {
87+
differences.push(Difference {
88+
index: i,
89+
value1: "".to_string(),
90+
value2: format!("{}", b2),
91+
absolute_diff: DiffMagnitude::Incomparable,
92+
relative_diff: DiffMagnitude::Incomparable,
93+
});
94+
}
95+
_ => {} // bytes are equal
96+
}
97+
}
98+
99+
differences
66100
}
67101
}
68102

@@ -72,18 +106,106 @@ impl OutputDiffer for RawDiffer {
72106
}
73107

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

79198
fn format_report(
80199
&self,
81-
_diffs: &[Difference],
200+
diffs: &[Difference],
82201
pkg1: &str,
83202
pkg2: &str,
84203
_epsilon: Option<f32>,
85204
) -> String {
86-
format!("Binary outputs from {} and {} differ", pkg1, pkg2)
205+
let mut report = format!("Total differences: {} bytes\n\n", diffs.len());
206+
report.push_str(&self.format_table(diffs, pkg1, pkg2));
207+
208+
report
87209
}
88210

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

458580
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-
}
581+
assert_eq!(diffs.len(), 4); // 4 bytes differ (l at position 3 is same in both)
582+
583+
// Check first difference (h vs w)
584+
assert_eq!(diffs[0].index, 0);
585+
assert_eq!(diffs[0].value1, "104"); // h = 104
586+
assert_eq!(diffs[0].value2, "119"); // w = 119
587+
588+
// Check second difference (e vs o)
589+
assert_eq!(diffs[1].index, 1);
590+
assert_eq!(diffs[1].value1, "101"); // 'e' = 101
591+
assert_eq!(diffs[1].value2, "111"); // 'o' = 111
592+
593+
// Check third difference (first l vs r)
594+
assert_eq!(diffs[2].index, 2);
595+
assert_eq!(diffs[2].value1, "108"); // 'l' = 108
596+
assert_eq!(diffs[2].value2, "114"); // 'r' = 114
597+
598+
// Check fourth difference (o vs d)
599+
assert_eq!(diffs[3].index, 4);
600+
assert_eq!(diffs[3].value1, "111"); // 'o' = 111
601+
assert_eq!(diffs[3].value2, "100"); // 'd' = 100
602+
}
603+
604+
#[test]
605+
fn test_raw_differ_partial_match() {
606+
let differ = RawDiffer;
607+
let bytes1 = b"hello world";
608+
let bytes2 = b"hello earth";
609+
610+
let diffs = differ.compare(bytes1, bytes2, None);
611+
assert_eq!(diffs.len(), 4); // 4 bytes differ in "world" vs "earth" (r at position 8 is same)
612+
613+
// First difference should be at index 6 (w vs e)
614+
assert_eq!(diffs[0].index, 6);
615+
assert_eq!(diffs[0].value1, "119"); // 'w' = 119
616+
assert_eq!(diffs[0].value2, "101"); // 'e' = 101
617+
618+
// Second difference at index 7 (o vs a)
619+
assert_eq!(diffs[1].index, 7);
620+
assert_eq!(diffs[1].value1, "111"); // 'o' = 111
621+
assert_eq!(diffs[1].value2, "97"); // 'a' = 97
622+
623+
// Third difference at index 9 (l vs t)
624+
assert_eq!(diffs[2].index, 9);
625+
assert_eq!(diffs[2].value1, "108"); // 'l' = 108
626+
assert_eq!(diffs[2].value2, "116"); // 't' = 116
627+
628+
// Fourth difference at index 10 (d vs h)
629+
assert_eq!(diffs[3].index, 10);
630+
assert_eq!(diffs[3].value1, "100"); // 'd' = 100
631+
assert_eq!(diffs[3].value2, "104"); // 'h' = 104
632+
}
633+
634+
#[test]
635+
fn test_raw_differ_different_lengths() {
636+
let differ = RawDiffer;
637+
let bytes1 = b"hello";
638+
let bytes2 = b"hello world";
639+
640+
let diffs = differ.compare(bytes1, bytes2, None);
641+
assert_eq!(diffs.len(), 6); // " world" = 6 extra bytes
642+
643+
// Check that missing bytes are shown as empty string
644+
assert_eq!(diffs[0].index, 5);
645+
assert_eq!(diffs[0].value1, "");
646+
assert_eq!(diffs[0].value2, "32"); // ' ' = 32
464647
}
465648

466649
#[test]

0 commit comments

Comments
 (0)