File Coverage

blib/lib/Test/HexString.pm
Criterion Covered Total %
statement 38 39 97.4
branch 11 14 78.5
condition 2 3 66.6
subroutine 6 6 100.0
pod 1 1 100.0
total 58 63 92.0


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2008-2011 -- leonerd@leonerd.org.uk
5              
6             package Test::HexString;
7              
8             our $VERSION = '0.03';
9              
10 2     2   23423 use strict;
  2         4  
  2         78  
11 2     2   16 use warnings;
  2         5  
  2         71  
12 2     2   13 use base qw( Test::Builder::Module );
  2         11  
  2         1952  
13              
14             our $CLASS = __PACKAGE__;
15              
16             our @EXPORT = qw(
17             is_hexstr
18             );
19              
20             our $BYTES_PER_BLOCK = 16;
21              
22             =head1 NAME
23              
24             C - test binary strings with hex dump diagnostics
25              
26             =head1 SYNOPSIS
27              
28             use Test::More tests => 1;
29             use Test::HexString;
30              
31             my $data = generate_some_output;
32              
33             is_hexstr( $data, "\x01\x02\x03\x04", 'Generated output' );
34              
35             =head1 DESCRIPTION
36              
37             This testing module provides a single function, C, which asserts
38             that the given string matches what was expected. When the strings match (i.e.
39             compare equal using the C operator), the behaviour is identical to the
40             usual C function provided by C.
41              
42             When the strings are different, a hex dump is produced as diagnostic, rather
43             than the string values being printed raw. This may be beneficial if the string
44             contains largely binary data, such as may be produced by binary file or
45             network protocol modules.
46              
47             To print the hex dump when it fails, each string is broken into 16 byte
48             chunks. The first pair of chunks that fail to match are then printed, in both
49             hexadecimal and character form, in a message in the following format:
50              
51             # Failed test at -e line 1.
52             # at bytes 0-0xf (0-15)
53             # got: | 61 20 6c 6f 6e 67 20 73 74 72 69 6e 67 20 68 65 |a long string he|
54             # exp: | 61 20 6c 6f 6e 67 20 53 74 72 69 6e 67 20 68 65 |a long String he|
55             # Looks like you failed 1 test of 1.
56              
57             Only bytes in the range C<0x20-0x7e> are printed as literal characters. Any
58             other byte is rendered as C<.>:
59              
60             # Failed test at -e line 1.
61             # at bytes 0-0xf (0-15)
62             # got: | 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
63             # exp: | 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 |................|
64             # Looks like you failed 1 test of 1.
65              
66             Only the first differing line is printed; because otherwise it may result in a
67             long output because of misaligned bytes.
68              
69             If STDOUT is a terminal, then different bytes are printed in bold for
70             visibility.
71              
72             =cut
73              
74             sub _bold
75             {
76 104     104   131 my ( $str, $bold ) = @_;
77 104 50       748 return $str unless -t STDOUT;
78 0 0       0 return $bold ? "\e[1m$str\e[m" : $str;
79             }
80              
81             sub _hexline
82             {
83 6     6   13 my ( $bytes, $boldmap ) = @_;
84              
85 6         46 my @b = split( m//, $bytes );
86              
87 6         10 my $ret = "| ";
88 6         34 $ret .= _bold(sprintf( "%02x ", ord $b[$_] ), $boldmap->[$_] ) for 0 .. $#b;
89 6         17 $ret .= ".. " x ( $BYTES_PER_BLOCK - @b );
90 6         9 $ret .= "|";
91 6 100       31 $ret .= _bold($b[$_] =~ /[\x20-\x7e]/ ? $b[$_] : ".", $boldmap->[$_] ) for 0 .. $#b;
92 6         14 $ret .= " " x ( $BYTES_PER_BLOCK - @b );
93 6         7 $ret .= "|";
94              
95 6         34 return $ret;
96             }
97              
98             =head1 FUNCTIONS
99              
100             =cut
101              
102             =head2 is_hexstr( $got, $expect, $name )
103              
104             Test that the string $got is what was expected by $expect. If the strings are
105             not equal, a hex dump is printed showing the region where they first start to
106             differ.
107              
108             =cut
109              
110             sub is_hexstr($$;$)
111             {
112 5     5 1 3222 my ( $got, $expected, $name ) = @_;
113              
114 5         34 my $tb = $CLASS->builder;
115              
116 5 100       45 if( ref $got ) {
117 1         4 my $ok = $tb->ok( 0, $name );
118 1         582 $tb->diag( " expected a plain string, was given a reference to " . ref($got) );
119 1         81 return $ok;
120             }
121              
122 4         15 my $ok = $tb->ok( $got eq $expected, $name );
123              
124 4 100       1859 unless( $ok ) {
125             # Try to find where they differ
126 3         14 for( my $offs = 0; $offs < length $got; $offs += $BYTES_PER_BLOCK ) {
127 65         76 my $g = substr( $got, $offs, $BYTES_PER_BLOCK );
128 65         72 my $e = substr( $expected, $offs, $BYTES_PER_BLOCK );
129 65 100       195 next if $g eq $e;
130              
131 3 100 66     9 my @bold = map { $_ < length $g and $_ < length $e and substr( $g, $_, 1 ) ne substr( $e, $_, 1 ) }
  48         202  
132             ( 0 .. $BYTES_PER_BLOCK-1 );
133              
134 3         29 $tb->diag( sprintf( " at bytes %#x-%#x (%d-%d)\n",
135             $offs, $offs+$BYTES_PER_BLOCK-1, $offs, $offs+$BYTES_PER_BLOCK-1 ) .
136             " got: " . _hexline( $g, \@bold ) . "\n" .
137             " exp: " . _hexline( $e, \@bold )
138             );
139              
140 3         264 last;
141             }
142             }
143              
144 4         10 return $ok;
145             }
146              
147             =head1 AUTHOR
148              
149             Paul Evans
150              
151             =cut
152              
153             0x55AA;