@@ -50,19 +50,53 @@ pub struct Difference {
5050/// Differ for raw byte comparison
5151pub struct RawDiffer ;
5252
53+
5354impl 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
74108impl 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