File Coverage

blib/lib/Test/HexDump/Range.pm
Criterion Covered Total %
statement 20 22 90.9
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 28 30 93.3


line stmt bran cond sub pod time code
1              
2             package Test::HexDump::Range ;
3              
4 1     1   51985 use strict;
  1         3  
  1         29  
5 1     1   5 use warnings ;
  1         1  
  1         26  
6 1     1   5 use Carp qw(carp croak confess) ;
  1         6  
  1         97  
7              
8             BEGIN
9             {
10 1         10 use Sub::Exporter -setup =>
11             {
12             exports => [ qw(diff_range) ],
13             groups =>
14             {
15             all => [ qw() ],
16             }
17 1     1   898 };
  1         16216  
18            
19 1     1   379 use vars qw ($VERSION);
  1         2  
  1         44  
20 1     1   19 $VERSION = '0.01_1';
21             }
22              
23             #-------------------------------------------------------------------------------
24              
25             #~ use English qw( -no_match_vars ) ;
26              
27 1     1   1339 use Readonly ;
  1         3866  
  1         47  
28             #~ Readonly my $EMPTY_STRING => q{} ;
29              
30 1     1   491 use Data::HexDump::Range ;
  0            
  0            
31              
32             #-------------------------------------------------------------------------------
33              
34             =head1 NAME
35              
36             Test::HexDump::Range - Compare binary data and displays a diff of two range dumps if they differ
37              
38             =head1 SYNOPSIS
39              
40             use Test::HexDump::Range qw(diff_range) ;
41            
42             my $range_description = 'magic cookie,5, bright_cyan:type,12: bf, x5b5 :meta_data,15: size,2: offset,7' ;
43            
44             my $expected_binary = '01234' . '567890123456' . '789012345678901' . '23' . '4567890' ;
45             my $got_binary = '01234' . '5XY890123456' . '789012345678901' . 'Z3' . '4567890' ;
46            
47             print diff_range($range_description, $expected_binary, $got_binary) ; # use default configuration
48            
49             # below is not implemented yet !
50            
51             my $dr = Test::HexDump::Range->new
52             (
53             DISPLAY_COLUMN_NAMES => 1,
54             DISPLAY_RULER => 1,
55             INTER_LINE => 0,
56             COLORS => ['bright_green', 'bright_yellow','bright_cyan', 'bright_red', 'bright_white'],
57             ...
58             ) ;
59            
60             print $dr->diff($range_description, $expected_binary, $got_binary) ;
61            
62             is_range_ok($range; $expected_binary, $got_binary) ;
63              
64             =head1 DESCRIPTION
65              
66             Takes a range description and two data chunks and displayes a binary diff highlighted according to the range description. The
67             dump is always in horizontal orientation.
68              
69             =head1 DOCUMENTATION
70              
71             This is a developer relase. the only thing working is the L subroutine (which may be the only thing that you need) and only
72             in the static configuration this module was built with, ANSI format, ...
73              
74             Example of output:
75              
76             =begin html
77              
78            
 
79             00000000 30 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 5:magic cookie, 12:type,
80             30 31 32 33 34 35 58 59 38 39 30 31 32 33 34 35
81            
82             00000010 36 37 38 39 30 31 32 33 34 35 36 37 38 39 30 31 12:type, 15:meta_data,
83             36 37 38 39 30 31 32 33 34 35 36 37 38 39 30 31
84            
85            
86             00000020 32 33 34 35 36 37 38 39 30 2:size, 7:offset,
87             5a 33 34 35 36 37 38 39 30
88            
89              
90             =end html
91              
92             =head1 SUBROUTINES/METHODS
93              
94             =cut
95              
96             #-------------------------------------------------------------------------------
97              
98             Readonly my $NEW_ARGUMENTS =>
99             [
100             qw(
101             COLORS
102             INTER_LINE
103             DISPLAY_COLUMN_NAMES
104             DISPLAY_RULER
105            
106             NAME INTERACTION VERBOSE
107            
108             FORMAT
109             DUMP_RANGE_DESCRIPTION
110             COLOR
111             START_COLOR
112             OFFSET_FORMAT
113             OFFSET_START
114             DATA_WIDTH
115             DISPLAY_OFFSET
116             DISPLAY_ZERO_SIZE_RANGE_WARNING
117             DISPLAY_ZERO_SIZE_RANGE
118             DISPLAY_RANGE_NAME
119             MAXIMUM_RANGE_NAME_SIZE
120             DISPLAY_RANGE_SIZE
121             DISPLAY_ASCII_DUMP
122             DISPLAY_HEX_DUMP
123             DISPLAY_DEC_DUMP
124             DISPLAY_BITFIELDS
125             DISPLAY_BITFIELD_SOURCE
126             BIT_ZERO_ON_LEFT
127             COLOR_NAMES
128             )] ;
129              
130             sub new
131             {
132             my ($invocant, @setup_data) = @_ ;
133              
134             my $class = ref($invocant) || $invocant ;
135             confess 'Error: Invalid constructor call.' unless defined $class ;
136              
137             my $object = {} ;
138              
139             my ($package, $file_name, $line) = caller() ;
140             bless $object, $class ;
141              
142             $object->Setup($package, $file_name, $line, @setup_data) ;
143              
144             return($object) ;
145             }
146              
147             #-------------------------------------------------------------------------------
148              
149             sub Setup
150             {
151              
152             =head2 [P] Setup()
153              
154             Helper sub called by new.
155              
156             =cut
157              
158             my ($self, $package, $file_name, $line, @setup_data) = @_ ;
159              
160             if (@setup_data % 2)
161             {
162             croak "Invalid number of argument '$file_name, $line'!" ;
163             }
164              
165             $self->{INTERACTION}{INFO} ||= sub {print @_} ;
166             $self->{INTERACTION}{WARN} ||= \&Carp::carp ;
167             $self->{INTERACTION}{DIE} ||= \&Carp::croak ;
168             $self->{NAME} = 'Anonymous';
169             $self->{FILE} = $file_name ;
170             $self->{LINE} = $line ;
171              
172             $self->CheckOptionNames($NEW_ARGUMENTS, @setup_data) ;
173              
174             %{$self} =
175             (
176             %{$self},
177             INTER_LINE => 1,
178             @setup_data,
179             ORIENTATION => 'horizontal',
180             ) ;
181              
182             $self->{INTERACTION}{INFO} ||= sub {print @_} ;
183             $self->{INTERACTION}{WARN} ||= \&Carp::carp ;
184             $self->{INTERACTION}{DIE} ||= \&Carp::croak ;
185              
186             my $location = "$self->{FILE}:$self->{LINE}" ;
187              
188             if($self->{VERBOSE})
189             {
190             $self->{INTERACTION}{INFO}('Creating ' . ref($self) . " '$self->{NAME}' at $location.\n") ;
191             }
192              
193             return ;
194             }
195              
196             #-------------------------------------------------------------------------------
197              
198             sub CheckOptionNames
199             {
200              
201             =head2 [P] CheckOptionNames()
202              
203             Verifies the named options passed to the members of this class. Calls B<{INTERACTION}{DIE}> in case
204             of error.
205              
206             =cut
207              
208             my ($self, $valid_options, @options) = @_ ;
209              
210             if (@options % 2)
211             {
212             $self->{INTERACTION}{DIE}->('Invalid number of argument!') ;
213             }
214              
215             if('HASH' eq ref $valid_options)
216             {
217             # OK
218             }
219             elsif('ARRAY' eq ref $valid_options)
220             {
221             $valid_options = { map{$_ => 1} @{$valid_options} } ;
222             }
223             else
224             {
225             $self->{INTERACTION}{DIE}->("Invalid argument '$valid_options'!") ;
226             }
227              
228             my %options = @options ;
229              
230             for my $option_name (keys %options)
231             {
232             unless(exists $valid_options->{$option_name})
233             {
234             $self->{INTERACTION}{DIE}->
235             (
236             "$self->{NAME}: Invalid Option '$option_name' at '$self->{FILE}:$self->{LINE}'\nValid options:\n\t"
237             . join("\n\t", sort keys %{$valid_options}) . "\n"
238             );
239             }
240             }
241              
242             if
243             (
244             (defined $options{FILE} && ! defined $options{LINE})
245             || (!defined $options{FILE} && defined $options{LINE})
246             )
247             {
248             $self->{INTERACTION}{DIE}->("$self->{NAME}: Incomplete option FILE::LINE!") ;
249             }
250              
251             return(1) ;
252             }
253              
254             #-------------------------------------------------------------------------------
255              
256             sub diff_range
257             {
258              
259             =head2 diff_range($range_description, $expected_binary, $got_binary)
260              
261             Compares two binary chunks and displays a hexadecimal dump witht a line from $expected_binary followed by a line
262             from $got_binary. The output is highlighted according to the range description. If a difference occures, the bytes are displayed
263             with a different backgound color.
264              
265             I
266              
267             =over 2
268              
269             =item * $range_description - A range description according to L
270              
271             =item * $expected_binary - A String
272              
273             =item * $got_binary - A String
274              
275             =back
276              
277             I - A String containing the diff
278              
279             I - Croaks on invalid input
280              
281             =cut
282              
283             my ($range_description, $expected_binary, $got_binary) = @_ ;
284              
285             my @colors = ('bright_green', 'bright_yellow','bright_cyan', 'bright_red', 'bright_white') ;
286             my $color_index = -1 ;
287              
288             my @expected_ranges ;
289              
290             #todo, use $self with local GATHER_CHUNK
291              
292             my $hdr = Data::HexDump::Range->new
293             (
294             #~ FORMAT => 'HTML',
295             DISPLAY_ASCII_DUMP => 0,
296             DISPLAY_RANGE_NAME => 1,
297             GATHERED_CHUNK =>
298             sub
299             {
300             my ($self, $chunk) = @_ ;
301            
302             $color_index++ ;
303             $color_index = 0 if$color_index >= @colors ;
304              
305             $chunk->{COLOR} = $colors[$color_index] ;
306            
307             push @expected_ranges, $chunk ;
308            
309             return $chunk ;
310             }
311             ) ;
312              
313             my @dump1 = split /\n/, $hdr->dump($range_description, $expected_binary) ;
314              
315             $color_index = -1 ;
316             my $hdr2 = Data::HexDump::Range->new
317             (
318             #~ FORMAT => 'HTML',
319             DISPLAY_ASCII_DUMP => 0,
320             DISPLAY_RANGE_NAME => 0,
321             DISPLAY_OFFSET => 0,
322             GATHERED_CHUNK =>
323             sub
324             {
325             my ($self, $chunk) = @_ ;
326            
327             my $expected_chunk = shift @expected_ranges ;
328            
329             $color_index++ ;
330             $color_index = 0 if$color_index >= @colors ;
331            
332             if($expected_chunk->{DATA} ne $chunk->{DATA})
333             {
334             my @new_chunks ;
335             my $new_chunk = '' ;
336            
337             my $same_byte_value ;
338             my $previous_chunk_has_same_byte_value = 1 ;
339            
340             for my $byte_index (0 .. length($expected_chunk->{DATA}) - 1)
341             {
342             my $got_byte ;
343            
344             if(substr($expected_chunk->{DATA}, $byte_index, 1) eq ($got_byte = substr($chunk->{DATA}, $byte_index, 1)) )
345             {
346             $same_byte_value = 1 ;
347            
348             if($previous_chunk_has_same_byte_value == $same_byte_value) # same data again
349             {
350             $new_chunk .= $got_byte ;
351             }
352             }
353             else
354             {
355             $same_byte_value = 0 ;
356            
357             if($previous_chunk_has_same_byte_value == $same_byte_value) #different data again
358             {
359             $new_chunk .= $got_byte ;
360             }
361             }
362            
363            
364             if($previous_chunk_has_same_byte_value != $same_byte_value)
365             {
366             my $new_chunk_length = length($new_chunk) ;
367            
368             push @new_chunks,
369             {
370             NAME => $previous_chunk_has_same_byte_value ? "$expected_chunk->{NAME}'<=$new_chunk_length>'" : "$expected_chunk->{NAME}''",
371             COLOR => $previous_chunk_has_same_byte_value ? $colors[$color_index] : 'bright_white on_red',
372             OFFSET => $expected_chunk->{OFFSET},
373             DATA => $new_chunk,
374             IS_BITFIELD => $expected_chunk->{IS_BITFIELD},
375             IS_SKIP => $expected_chunk->{IS_SKIP},
376             IS_COMMENT => $expected_chunk->{IS_COMMENT},
377             USER_INFORMATION => '',
378             } if length($new_chunk) ;
379            
380             $previous_chunk_has_same_byte_value = $same_byte_value ;
381             $new_chunk = $got_byte ;
382             }
383             }
384            
385             my $new_chunk_length = length($new_chunk) ;
386             push @new_chunks,
387             {
388             NAME => $previous_chunk_has_same_byte_value ? "$expected_chunk->{NAME}'<=$new_chunk_length>'" : "$expected_chunk->{NAME}''",
389             COLOR => $previous_chunk_has_same_byte_value ? $colors[$color_index] : 'bright_white on_red',
390             OFFSET => $expected_chunk->{OFFSET},
391             DATA => $new_chunk,
392             IS_BITFIELD => $expected_chunk->{IS_BITFIELD},
393             IS_SKIP => $expected_chunk->{IS_SKIP},
394             IS_COMMENT => $expected_chunk->{IS_COMMENT},
395             USER_INFORMATION => '',
396             } ;
397            
398             return @new_chunks ;
399             }
400             else
401             {
402             $chunk->{COLOR} = $colors[$color_index] ;
403             return $chunk;
404             }
405             }
406             ) ;
407            
408             my @dump2 = split /\n/, $hdr2->dump($range_description, $got_binary) ;
409              
410             my $ruler = Data::HexDump::Range->new( DISPLAY_RULER => 1) ;
411             my @ruler_x = split /\n/, $ruler->dump('x,1', '1') ;
412              
413             my $output = $ruler_x[0] . "\n" ;
414             $output .= shift(@dump1) . "\n" . ' ' x 9 . shift(@dump2) . "\n\n" while(@dump1) ;
415              
416             return $output ;
417             }
418              
419             #-------------------------------------------------------------------------------
420              
421             1 ;
422              
423             =head1 BUGS AND LIMITATIONS
424              
425             None so far.
426              
427             =head1 AUTHOR
428              
429             Nadim ibn hamouda el Khemir
430             CPAN ID: NKH
431             mailto: nadim@cpan.org
432              
433             =head1 COPYRIGHT AND LICENSE
434              
435             Copyright Nadim Khemir 2010 .
436              
437             This program is free software; you can redistribute it and/or
438             modify it under the terms of either:
439              
440             =over 4
441              
442             =item * the GNU General Public License as published by the Free
443             Software Foundation; either version 1, or (at your option) any
444             later version, or
445              
446             =item * the Artistic License version 2.0.
447              
448             =back
449              
450             =head1 SUPPORT
451              
452             You can find documentation for this module with the perldoc command.
453              
454             perldoc Test::HexDump::Range
455              
456             You can also look for information at:
457              
458             =over 4
459              
460             =item * AnnoCPAN: Annotated CPAN documentation
461              
462             L
463              
464             =item * RT: CPAN's request tracker
465              
466             Please report any bugs or feature requests to L .
467              
468             We will be notified, and then you'll automatically be notified of progress on
469             your bug as we make changes.
470              
471             =item * Search CPAN
472              
473             L
474              
475             =back
476              
477             =head1 SEE ALSO
478              
479              
480             =cut