File Coverage

blib/lib/Test/LongString.pm
Criterion Covered Total %
statement 146 156 93.5
branch 64 74 86.4
condition 10 17 58.8
subroutine 16 16 100.0
pod 6 6 100.0
total 242 269 89.9


line stmt bran cond sub pod time code
1             package Test::LongString;
2              
3 6     6   152100 use strict;
  6         13  
  6         265  
4 6     6   30 use vars qw($VERSION @ISA @EXPORT $Max $Context $EOL $LCSS);
  6         9  
  6         686  
5              
6             $VERSION = '0.16';
7              
8 6     6   31 use Test::Builder;
  6         14  
  6         196  
9             my $Tester = new Test::Builder();
10              
11 6     6   24 use Exporter;
  6         11  
  6         4762  
12             @ISA = ('Exporter');
13             @EXPORT = qw( is_string is_string_nows like_string unlike_string
14             contains_string lacks_string );
15              
16             # Maximum string length displayed in diagnostics
17             $Max = 50;
18              
19             # Amount of context provided when starting displaying a string in the middle
20             $Context = 10;
21              
22             # Boolean: should we show LCSS context ?
23             $LCSS = 1;
24              
25             # Regular expression that decides what a end of line is
26             $EOL = "\n";
27              
28             sub import {
29 6     6   49 (undef, my %args) = @_;
30 6 100       32 $Max = $args{max} if defined $args{max};
31 6 100       30 $LCSS = $args{lcss} if defined $args{lcss};
32 6 50       104 $EOL = $args{eol} if defined $args{eol};
33 6         17 @_ = $_[0];
34 6         7038 goto &Exporter::import;
35             }
36              
37             # _display($string, [$offset = 0])
38             # Formats a string for display. Begins at $offset minus $Context.
39             # This function ought to be configurable, à la od(1).
40              
41             sub _display {
42 40     40   51 my $s = shift;
43 40 100       74 if (!defined $s) { return 'undef'; }
  3         7  
44 37 100       70 if (length($s) > $Max) {
45 7   100     22 my $offset = shift || 0;
46 7 50       17 if (defined $Context) {
47 7         9 $offset -= $Context;
48 7 100       21 $offset < 0 and $offset = 0;
49             }
50             else {
51 0         0 $offset = 0;
52             }
53 7         57 $s = sprintf(qq("%.${Max}s"...), substr($s, $offset));
54 7 100       22 $s = "...$s" if $offset;
55             }
56             else {
57 30         51 $s = qq("$s");
58             }
59 37         61 $s =~ s/([\0-\037\200-\377])/sprintf('\x{%02x}',ord $1)/eg;
  8         30  
60 37         77 return $s;
61             }
62              
63             sub _common_prefix_length {
64 11     11   17 my ($str1, $str2) = @_;
65 11         29 my $diff = "$str1" ^ "$str2";
66 11         33 my ($pre) = $diff =~ /^(\000*)/;
67 11         22 return length $pre;
68             }
69              
70             sub contains_string($$;$) {
71 5     5 1 3249 my ($str,$sub,$name) = @_;
72              
73 5         5 my $ok;
74 5 100       15 if (!defined $str) {
    100          
75 1         3 $Tester->ok($ok = 0, $name);
76 1         489 $Tester->diag("String to look in is undef");
77             } elsif (!defined $sub) {
78 1         3 $Tester->ok($ok = 0, $name);
79 1         427 $Tester->diag("String to look for is undef");
80             } else {
81 3         7 my $index = index($str, $sub);
82 3 100       7 $ok = ($index >= 0) ? 1 : 0;
83 3         10 $Tester->ok($ok, $name);
84 3 100       1212 if (!$ok) {
85 2         5 my ($g, $e) = (_display($str), _display($sub));
86              
87 2         9 $Tester->diag(<
88             searched: $g
89             can't find: $e
90             DIAG
91              
92 2 100       134 if ($LCSS) {
93             # if _lcss() returned the actual substring,
94             # all we'd have to do is:
95             # my $l = _display( _lcss($str, $sub) );
96              
97 1         4 my ($off, $len) = _lcss($str, $sub);
98 1         3 my $l = _display( substr($str, $off, $len) );
99              
100 1         4 $Tester->diag(<
101             LCSS: $l
102             DIAG
103             # if there's room left, show some surrounding context
104 1 50       73 if ($len < $Max) {
105 1         3 my $available = int( ($Max - $len) / 2 );
106 1 50       6 my $begin = ($off - ($available*2) > 0) ? $off - ($available*2)
    50          
107             : ($off - $available > 0) ? $off - $available : 0;
108 1         3 my $c = _display( substr($str, $begin, $Max) );
109              
110 1         4 $Tester->diag("LCSS context: $c");
111             }
112             }
113             }
114             }
115 5         185 return $ok;
116             }
117              
118             sub _lcss($$) {
119 11     11   8245 my ($S, $T) = (@_);
120 11         17 my @L;
121 11         24 my ($offset, $length) = (0,0);
122              
123             # prevent us from having to zero a $ix$j matrix
124 6     6   40 no warnings 'uninitialized';
  6         14  
  6         7225  
125              
126             # now the actual LCSS algorithm
127 11         32 foreach my $i (0 .. length($S) ) {
128 232         361 foreach my $j (0 .. length($T)) {
129 9833 100       20323 if (substr($S, $i, 1) eq substr($T, $j, 1)) {
130 696 100 100     2336 if ($i == 0 or $j == 0) {
131 22         46 $L[$i][$j] = 1;
132             }
133             else {
134 674         1896 $L[$i][$j] = $L[$i-1][$j-1] + 1;
135             }
136 696 100       1553 if ($L[$i][$j] > $length) {
137 79         91 $length = $L[$i][$j];
138 79         134 $offset = $i - $length + 1;
139             }
140             }
141             }
142             }
143              
144             # if you want to display just the lcss:
145             # return substr($S, $offset, $length);
146              
147             # but to display the surroundings, we need to:
148 11         162 return ($offset, $length);
149             }
150              
151              
152             sub lacks_string($$;$) {
153 5     5 1 3719 my ($str,$sub,$name) = @_;
154              
155 5         7 my $ok;
156 5 100       13 if (!defined $str) {
    100          
157 1         3 $Tester->ok($ok = 0, $name);
158 1         424 $Tester->diag("String to look in is undef");
159             } elsif (!defined $sub) {
160 1         5 $Tester->ok($ok = 0, $name);
161 1         444 $Tester->diag("String to look for is undef");
162             } else {
163 3         7 my $index = index($str, $sub);
164 3 100       6 $ok = ($index < 0) ? 1 : 0;
165 3         9 $Tester->ok($ok, $name);
166 3 100       1183 if (!$ok) {
167 2         6 my ($g, $e) = (_display($str), _display($sub));
168 2         16 my $line = () = substr($str,0,$index-1) =~ /$EOL/g;
169 2 100       9 my $column = $line ? $index - $+[0] + 1: $index + 1;
170 2         3 $line++;
171 2         12 $Tester->diag(<
172             searched: $g
173             and found: $e
174             at position: $index (line $line column $column)
175             DIAG
176             }
177             }
178 5         271 return $ok;
179             }
180              
181             sub is_string ($$;$) {
182 13     13 1 6319 my ($got, $expected, $name) = @_;
183 13 100 66     56 if (!defined $got || !defined $expected) {
184 2   66     558 my $ok = !defined $got && !defined $expected;
185 2         6 $Tester->ok($ok, $name);
186 2 50       728 if (!$ok) {
187 2         5 my ($g, $e) = (_display($got), _display($expected));
188 2         8 $Tester->diag(<
189             got: $g
190             expected: $e
191             DIAG
192             }
193 2         115 return $ok;
194             }
195 11 100       23 if ($got eq $expected) {
196 1         6 $Tester->ok(1, $name);
197 1         312 return 1;
198             }
199             else {
200 10         31 $Tester->ok(0, $name);
201 10         3858 my $common_prefix = _common_prefix_length($got,$expected);
202 10         21 my ($g, $e) = (
203             _display($got, $common_prefix),
204             _display($expected, $common_prefix),
205             );
206 10         71 my $line = () = substr($expected,0,$common_prefix) =~ /$EOL/g;
207 10 100       26 my $column = $line ? $common_prefix - $+[0] + 1 : $common_prefix + 1;
208 10         11 $line++;
209 10         15 $Tester->diag(<
210 10         23 got: $g
211 10         19 length: ${\(length $got)}
212             expected: $e
213 10         43 length: ${\(length $expected)}
214             strings begin to differ at char ${\($common_prefix + 1)} (line $line column $column)
215             DIAG
216 10         662 return 0;
217             }
218             }
219              
220             sub is_string_nows ($$;$) {
221 2     2 1 1105 my ($got, $expected, $name) = @_;
222 2 50 33     10 if (!defined $got || !defined $expected) {
223 0   0     0 my $ok = !defined $got && !defined $expected;
224 0         0 $Tester->ok($ok, $name);
225 0 0       0 if (!$ok) {
226 0         0 my ($g, $e) = (_display($got), _display($expected));
227 0         0 $Tester->diag(<
228             got: $g
229             expected: $e
230             DIAG
231             }
232 0         0 return $ok;
233             }
234 2         27 s/\s+//g for (my $got_nows = $got), (my $expected_nows = $expected);
235 2 100       6 if ($got_nows eq $expected_nows) {
236 1         3 $Tester->ok(1, $name);
237 1         203 return 1;
238             }
239             else {
240 1         4 $Tester->ok(0, $name);
241 1         427 my $common_prefix = _common_prefix_length($got_nows,$expected_nows);
242 1         3 my ($g, $e) = (
243             _display($got_nows, $common_prefix),
244             _display($expected_nows, $common_prefix),
245             );
246 1         3 $Tester->diag(<
247             after whitespace removal:
248 1         4 got: $g
249 1         3 length: ${\(length $got_nows)}
250             expected: $e
251 1         5 length: ${\(length $expected_nows)}
252             strings begin to differ at char ${\($common_prefix + 1)}
253             DIAG
254 1         67 return 0;
255             }
256             }
257              
258             sub like_string ($$;$) {
259 4     4 1 2201 _like($_[0],$_[1],'=~',$_[2]);
260             }
261              
262             sub unlike_string ($$;$) {
263 1     1 1 625 _like($_[0],$_[1],'!~',$_[2]);
264             }
265              
266             # mostly from Test::Builder::_regex_ok
267             sub _like {
268 5     5   9 local $Test::Builder::Level = $Test::Builder::Level + 1;
269 5         7 my ($got, $regex, $cmp, $name) = @_;
270 5         6 my $ok = 0;
271 5         12 my $usable_regex = $Tester->maybe_regex($regex);
272 5 50       54 unless (defined $usable_regex) {
273 0         0 $ok = $Tester->ok( 0, $name );
274 0         0 $Tester->diag(" '$regex' doesn't look much like a regex to me.");
275 0         0 return $ok;
276             }
277             {
278 5         5 local $^W = 0;
  5         14  
279 5 100       23 my $test = $got =~ /$usable_regex/ ? 1 : 0;
280 5 100       12 $test = !$test if $cmp eq '!~';
281 5         14 $ok = $Tester->ok( $test, $name );
282             }
283 5 100       1924 unless( $ok ) {
284 4         9 my $g = _display($got);
285 4 100       9 my $match = $cmp eq '=~' ? "doesn't match" : "matches";
286 4 100       7 my $l = defined $got ? length $got : '-';
287 4         24 $Tester->diag(sprintf(<
288             got: %s
289             length: $l
290             %13s '%s'
291             DIAGNOSTIC
292             }
293 5         258 return $ok;
294             }
295              
296             1;
297              
298             __END__