@@ -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
74107impl 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