@@ -50,19 +50,53 @@ pub struct Difference {
50
50
/// Differ for raw byte comparison
51
51
pub struct RawDiffer ;
52
52
53
+
53
54
impl OutputDiffer for RawDiffer {
54
55
fn compare ( & self , output1 : & [ u8 ] , output2 : & [ u8 ] , _epsilon : Option < f32 > ) -> Vec < Difference > {
55
56
if output1 == output2 {
56
57
vec ! [ ]
57
58
} 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
66
100
}
67
101
}
68
102
@@ -72,18 +106,106 @@ impl OutputDiffer for RawDiffer {
72
106
}
73
107
74
108
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
77
196
}
78
197
79
198
fn format_report (
80
199
& self ,
81
- _diffs : & [ Difference ] ,
200
+ diffs : & [ Difference ] ,
82
201
pkg1 : & str ,
83
202
pkg2 : & str ,
84
203
_epsilon : Option < f32 > ,
85
204
) -> 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
87
209
}
88
210
89
211
fn write_human_readable ( & self , output : & [ u8 ] , path : & std:: path:: Path ) -> std:: io:: Result < ( ) > {
@@ -456,11 +578,72 @@ mod tests {
456
578
let bytes2 = b"world" ;
457
579
458
580
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
464
647
}
465
648
466
649
#[ test]
0 commit comments