@@ -55,14 +55,47 @@ impl OutputDiffer for RawDiffer {
55
55
if output1 == output2 {
56
56
vec ! [ ]
57
57
} 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
66
99
}
67
100
}
68
101
@@ -72,18 +105,118 @@ impl OutputDiffer for RawDiffer {
72
105
}
73
106
74
107
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
77
207
}
78
208
79
209
fn format_report (
80
210
& self ,
81
- _diffs : & [ Difference ] ,
211
+ diffs : & [ Difference ] ,
82
212
pkg1 : & str ,
83
213
pkg2 : & str ,
84
214
_epsilon : Option < f32 > ,
85
215
) -> 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
87
220
}
88
221
89
222
fn write_human_readable ( & self , output : & [ u8 ] , path : & std:: path:: Path ) -> std:: io:: Result < ( ) > {
@@ -456,11 +589,72 @@ mod tests {
456
589
let bytes2 = b"world" ;
457
590
458
591
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
464
658
}
465
659
466
660
#[ test]
0 commit comments