File Coverage

blib/lib/Test/Regexp.pm
Criterion Covered Total %
statement 233 236 98.7
branch 84 92 91.3
condition 47 57 82.4
subroutine 27 27 100.0
pod 3 7 42.8
total 394 419 94.0


line stmt bran cond sub pod time code
1             package Test::Regexp;
2              
3 18     18   400659 use 5.010;
  18         72  
4              
5 18     18   99 use strict;
  18         35  
  18         401  
6 18     18   94 use warnings;
  18         36  
  18         514  
7 18     18   86 no warnings 'syntax';
  18         29  
  18         603  
8              
9 18     18   89 use Exporter ();
  18         77  
  18         440  
10 18     18   1203 use Test::Builder;
  18         11397  
  18         1460  
11              
12             our @EXPORT = qw [match no_match];
13             our @ISA = qw [Exporter Test::More];
14              
15             our $VERSION = '2015110201';
16              
17             BEGIN {
18 18     18   11247 binmode STDOUT, ":utf8";
19             }
20              
21              
22             my $Test = Test::Builder -> new;
23              
24             sub import {
25 18     18   146 my $self = shift;
26 18         109 my $pkg = caller;
27              
28 18         53 my %arg = @_;
29              
30 18         180 $Test -> exported_to ($pkg);
31              
32 18   100     652 $arg {import} //= [qw [match no_match]];
33              
34 18         110 while (my ($key, $value) = each %arg) {
35 19 100       130 if ($key eq "tests") {
    50          
36 1         10 $Test -> plan ($value);
37             }
38             elsif ($key eq "import") {
39 18 50       35 $self -> export_to_level (1, $self, $_) for @{$value || []};
  18         5851  
40             }
41             else {
42 0         0 die "Unknown option '$key'\n";
43             }
44             }
45             }
46              
47              
48             my $__ = " ";
49              
50             sub escape {
51 391     391 0 565 my $str = shift;
52 391         627 $str =~ s/\n/\\n/g;
53 391         552 $str =~ s/\t/\\t/g;
54 391         514 $str =~ s/\r/\\r/g;
55 391         833 $str =~ s/([^\x20-\x7E])/sprintf "\\x{%02X}" => ord $1/eg;
  38         174  
56 391         837 $str;
57             }
58              
59             sub pretty {
60 247     247 0 360 my $str = shift;
61 247         477 my %arg = @_;
62 247 100 100     739 substr ($str, 50, -5, "...") if length $str > 55 && !$arg {full_text};
63 247         456 $str = escape $str;
64 247         647 $str;
65             }
66              
67              
68             sub mess {
69 103     103 0 157 my $val = shift;
70 103 50       275 unless (defined $val) {return 'undefined'}
  0         0  
71 103         254 my %arg = @_;
72 103         243 my $pretty = pretty $val, full_text => $arg {full_text};
73 103 100 66     598 if ($pretty eq $val && $val !~ /'/) {
    50          
74 83         624 return "eq '$val'";
75             }
76             elsif ($pretty !~ /"/) {
77 20         136 return 'eq "' . $pretty . '"';
78             }
79             else {
80 0         0 return "eq qq {$pretty}";
81             }
82             }
83              
84              
85             sub todo {
86 144     144 1 699 my %arg = @_;
87 144         254 my $subject = $arg {subject};
88 144         207 my $comment = $arg {comment};
89 144         208 my $upgrade = $arg {upgrade};
90 144         204 my $downgrade = $arg {downgrade};
91 144 100       319 my $neg = $arg {match} ? "" : "not ";
92 144         248 my $full_text = $arg {full_text};
93              
94 144         222 my $line = "";
95              
96 144 100       355 if ($arg {show_line}) {
97 18     18   108 no warnings 'once';
  18         30  
  18         15952  
98 24   50     198 my ($file, $l_nr) = (caller ($Test::Builder::deepness // 1)) [1, 2];
99 24         80 $line = " [$file:$l_nr]";
100             }
101              
102 144         332 my $subject_pretty = pretty $subject, full_text => $full_text;
103 144         330 my $Comment = qq {qq {$subject_pretty}};
104 144         400 $Comment .= qq { ${neg}matched by "$comment"};
105              
106 144         426 my @todo = [$subject, $Comment, $line];
107              
108             #
109             # If the subject isn't already UTF-8, and there are characters in
110             # the range "\x{80}" .. "\x{FF}", we do the test a second time,
111             # with the subject upgraded to UTF-8.
112             #
113             # Otherwise, if the subject is in UTF-8 format, and there are *no*
114             # characters with code point > 0xFF, but with characters in the
115             # range 0x80 .. 0xFF, we downgrade and test again.
116             #
117 144 100 33     2132 if ($upgrade && ($upgrade == 2 || !utf8::is_utf8 ($subject)
    100 66        
      33        
      66        
118             && $subject =~ /[\x80-\xFF]/)) {
119 6         11 my $subject_utf8 = $subject;
120 6 50       20 if (utf8::upgrade ($subject_utf8)) {
121 6         10 my $Comment_utf8 = qq {qq {$subject_pretty}};
122 6         13 $Comment_utf8 .= qq { [UTF-8]};
123 6         13 $Comment_utf8 .= qq { ${neg}matched by "$comment"};
124              
125 6         17 push @todo => [$subject_utf8, $Comment_utf8, $line];
126             }
127             }
128             elsif ($downgrade && ($downgrade == 2 || utf8::is_utf8 ($subject)
129             && $subject =~ /[\x80-\xFF]/
130             && $subject !~ /[^\x00-\xFF]/)) {
131 6         8 my $subject_non_utf8 = $subject;
132 6 50       20 if (utf8::downgrade ($subject_non_utf8)) {
133 6         17 my $Comment_non_utf8 = qq {qq {$subject_pretty}};
134 6         13 $Comment_non_utf8 .= qq { [non-UTF-8]};
135 6         13 $Comment_non_utf8 .= qq { ${neg}matched by "$comment"};
136              
137 6         20 push @todo => [$subject_non_utf8, $Comment_non_utf8, $line];
138             }
139             }
140              
141 144         637 @todo;
142             }
143            
144              
145              
146             #
147             # Arguments:
148             # name: 'Name' of the pattern.
149             # pattern: Pattern to be tested, without captures.
150             # keep_pattern: Pattern to be tested, with captures.
151             # subject: String to match.
152             # captures: Array of captures; elements are either strings
153             # (match for the corresponding numbered capture),
154             # or an array, where the first element is the name
155             # of the capture and the second its value.
156             # comment: Comment to use, defaults to name or "".
157             # utf8_upgrade: If set, upgrade the string if applicable. Defaults to 1.
158             # utf8_downgrade If set, downgrade the string if applicable. Defaults to 1.
159             # match If true, pattern(s) should match, otherwise, should fail
160             # to match. Defaults to 1.
161             # reason The reason a match should fail.
162             # test What is tested.
163             # todo This test is a todo test; argument is the reason.
164             # show_line Show file name/line number of call to 'match'.
165             # full_text Don't shorten long messages.
166             #
167              
168             sub match {
169 144     144 1 410877 my %arg = @_;
170              
171 144         301 my $name = $arg {name};
172 144         248 my $pattern = $arg {pattern};
173 144         224 my $keep_pattern = $arg {keep_pattern};
174 144         245 my $subject = $arg {subject};
175 144   100     644 my $captures = $arg {captures} // [];
176 144   100     986 my $comment = escape $arg {comment} // $name // "";
      100        
177 144   100     612 my $upgrade = $arg {utf8_upgrade} // 1;
178 144   100     516 my $downgrade = $arg {utf8_downgrade} // 1;
179 144   100     430 my $match = $arg {match} // 1;
180             my $reason = defined $arg {reason}
181 144 100       415 ? " [Reason: " . $arg {reason} . "]"
182             : "";
183             my $test = defined $arg {test}
184 144 100       344 ? " [Test: " . $arg {test} . "]"
185             : "";
186 144         222 my $show_line = $arg {show_line};
187 144         215 my $full_text = $arg {full_text};
188 144         210 my $todo = $arg {todo};
189 144 100       310 my $keep_message = $arg {no_keep_message} ? "" : " (with -Keep)";
190              
191 144         184 my $numbered_captures;
192             my $named_captures;
193              
194 144         179 my $pass = 1;
195              
196             #
197             # First split the captures into a hash (for named captures) and
198             # an array (for numbered captures) so we can check $1 and friends, and %-.
199             #
200 144         347 foreach my $capture (@$captures) {
201 81 100       199 if (ref $capture eq 'ARRAY') {
202 34         83 my ($name, $match) = @$capture;
203 34         86 push @$numbered_captures => $match;
204 34         45 push @{$$named_captures {$name}} => $match;
  34         134  
205             }
206             else {
207 47         118 push @$numbered_captures => $capture;
208             }
209             }
210            
211 144   100     524 $numbered_captures ||= [];
212 144   100     569 $named_captures ||= {};
213              
214 144         400 my @todo = todo subject => $subject,
215             comment => $comment,
216             upgrade => $upgrade,
217             downgrade => $downgrade,
218             match => $match,
219             show_line => $show_line,
220             full_text => $full_text;
221              
222 144 100       403 $Test -> todo_start ($todo) if defined $todo;
223              
224             #
225             # Now we will do the tests.
226             #
227 144         504 foreach my $todo (@todo) {
228 156         269 my $subject = $$todo [0];
229 156         237 my $comment = $$todo [1];
230 156         230 my $line = $$todo [2];
231              
232 156 100 100     699 if ($match && defined $pattern) {
233 74         104 my $comment = $comment;
234 74 100       367 my $pat = ref $pattern ? $pattern
235             : qr /$pattern/;
236 74         324 $comment =~ s{""$}{/$pat/};
237 74         168 $comment .= "$line$test";
238             #
239             # Test match; match should also be complete, and not
240             # have any captures.
241             #
242             SKIP: {
243 74         107 my $result = $subject =~ /^$pattern/;
  74         643  
244 74 100       465 unless ($Test -> ok ($result, $comment)) {
245 3         360 $Test -> skip ("Match failed") for 1 .. 3;
246 3         556 $pass = 0;
247 3         11 last SKIP;
248             }
249              
250             #
251             # %- contains an entry for *each* named group, regardless
252             # whether it's a capture or not.
253             #
254 71         7862 my $named_matches = 0;
255 18     18   15894 $named_matches += @$_ for values %-;
  18         9516  
  18         7509  
  71         446  
256              
257 71 100       478 unless ($Test -> is_eq ($&, $subject,
258             "${__}match is complete")) {
259 7         2280 $Test -> skip ("Match failed") for 2 .. 3;
260 7         804 $pass = 0;
261 7         24 last SKIP;
262             }
263            
264 64 100       13260 $pass = 0 unless
265             $Test -> is_eq (scalar @+, 1,
266             "${__}no numbered captures");
267 64 100       13015 $pass = 0 unless
268             $Test -> is_eq ($named_matches, 0,
269             "${__}no named captures");
270             }
271             }
272              
273              
274 156 100 100     12916 if ($match && defined $keep_pattern) {
275 52         85 my $comment = $comment;
276 52 100       254 my $pat = ref $keep_pattern ? $keep_pattern
277             : qr /$keep_pattern/;
278 52         396 $comment =~ s{""$}{/$pat/};
279 52         120 $comment .= $keep_message;
280 52         94 $comment .= "$line$test";
281             #
282             # Test keep. Should match, and the parts as well.
283             #
284             # Total number of tests:
285             # - 1 for match.
286             # - 1 for match complete.
287             # - 1 for each named capture.
288             # - 1 for each capture name.
289             # - 1 for number of different capture names.
290             # - 1 for each capture.
291             # - 1 for number of captures.
292             # So, if you only have named captures, and all the names
293             # are different, you have 4 + 3 * N tests.
294             # If you only have numbered captures, you have 4 + N tests.
295             #
296             SKIP: {
297 52         74 my $nr_of_tests = 0;
  52         73  
298 52         81 $nr_of_tests += 1; # For match.
299 52         74 $nr_of_tests += 1; # For match complete.
300 52         171 $nr_of_tests += @{$_} for values %$named_captures;
  38         83  
301             # Number of named captures.
302 52         89 $nr_of_tests += scalar keys %$named_captures;
303             # Number of different named captures.
304 52         70 $nr_of_tests += 1; # Right number of named captures.
305 52         79 $nr_of_tests += @$numbered_captures;
306             # Number of numbered captures.
307 52         65 $nr_of_tests += 1; # Right number of numbered captures.
308              
309 52         70 my ($amp, @numbered_matches, %minus);
310              
311 52         1304 my $result = $subject =~ /^$keep_pattern/;
312 52 100       372 unless ($Test -> ok ($result, $comment)) {
313 2         263 $Test -> skip ("Match failed") for 2 .. $nr_of_tests;
314 2         652 $pass = 0;
315 2         11 last SKIP;
316             }
317              
318              
319             #
320             # Copy $&, $N and %- before doing anything that
321             # migh override them.
322             #
323              
324 50         6649 $amp = $&;
325              
326             #
327             # Grab numbered captures.
328             #
329 50         189 for (my $i = 1; $i < @+; $i ++) {
330 18     18   153 no strict 'refs';
  18         34  
  18         18169  
331 79         370 push @numbered_matches => $$i;
332             }
333              
334             #
335             # Copy %-;
336             #
337 50         494 while (my ($key, $value) = each %-) {
338 38         364 $minus {$key} = [@$value];
339             }
340              
341             #
342             # Test to see if match is complete.
343             #
344 50 100       347 unless ($Test -> is_eq ($amp, $subject,
345             "${__}match is complete")) {
346 6         1752 $Test -> skip ("Match incomplete") for 3 .. $nr_of_tests;
347 6         2461 $pass = 0;
348 6         31 last SKIP;
349             }
350              
351             #
352             # Test named captures.
353             #
354 44         11337 while (my ($key, $value) = each %$named_captures) {
355 33         3216 for (my $i = 0; $i < @$value; $i ++) {
356             $pass = 0 unless
357             $Test -> is_eq (
358 37 100       1270 $minus {$key} ? $minus {$key} [$i] : undef,
    100          
359             $$value [$i],
360             "${__}\$- {$key} [$i] " .
361             mess ($$value [$i], full_text => $full_text));
362             }
363             $pass = 0 unless
364 33 100       7028 $Test -> is_num (scalar @{$minus {$key} || []},
  33 100       367  
    100          
365             scalar @$value, "$__${__}capture '$key' has " .
366             (@$value == 1 ? "1 match" :
367             @$value . " matches"));
368             }
369             #
370             # Test for the right number of captures.
371             #
372 44 100       5103 $pass = 0 unless
373             $Test -> is_num (scalar keys %minus,
374             scalar keys %$named_captures,
375             $__ . scalar (keys %$named_captures)
376             . " named capture groups"
377             );
378              
379              
380             #
381             # Test numbered captures.
382             #
383 44         10158 for (my $i = 0; $i < @$numbered_captures; $i ++) {
384 66 100       6695 $pass = 0 unless
385             $Test -> is_eq ($numbered_matches [$i],
386             $$numbered_captures [$i],
387             "${__}\$" . ($i + 1) . " " .
388             mess ($$numbered_captures [$i],
389             full_text => $full_text));
390             }
391 44 100       7731 $pass = 0 unless
    100          
392             $Test -> is_num (scalar @numbered_matches,
393             scalar @$numbered_captures,
394             $__ .
395             (@$numbered_captures == 1 ?
396             "1 numbered capture group" :
397             @$numbered_captures .
398             " numbered capture groups"));
399             }
400             }
401              
402 156 100 100     11140 if (!$match && defined $pattern) {
403 33         52 my $comment = $comment;
404 33 100       131 my $pat = ref $pattern ? $pattern
405             : qr /$pattern/;
406 33         125 $comment =~ s{""$}{/$pat/};
407 33         75 $comment .= "$line$reason";
408 33         248 my $r = $subject =~ /^$pattern/;
409 33 100 100     283 $pass = 0 unless
410             $Test -> ok (!$r || $subject ne $&, $comment);
411             }
412 156 100 100     4340 if (!$match && defined $keep_pattern) {
413 1         4 my $comment = $comment;
414 1 50       6 my $pat = ref $keep_pattern ? $keep_pattern
415             : qr /$keep_pattern/;
416 1         3 $comment =~ s{""$}{/$pat/};
417 1         4 $comment .= $keep_message;
418 1         3 $comment .= "$line$reason";
419 1         76 my $r = $subject =~ /^$keep_pattern/;
420 1 50 33     13 $pass = 0 unless
421             $Test -> ok (!$r || $subject ne $&, $comment);
422             }
423             }
424              
425 144 100       1072 $Test -> todo_end if defined $todo;
426              
427 144         1139 $pass;
428             }
429              
430             sub no_match {
431 6     6 1 19 push @_ => match => 0;
432 6         23 goto &match;
433             }
434              
435             sub new {
436 19     19 0 28966 "Test::Regexp::Object" -> new
437             }
438              
439             package Test::Regexp::Object;
440              
441             sub new {
442 21     21   34 bless \do {my $var} => shift;
  21         120  
443             }
444              
445 18     18   18242 use Hash::Util::FieldHash qw [fieldhash];
  18         28081  
  18         12820  
446              
447             fieldhash my %pattern;
448             fieldhash my %keep_pattern;
449             fieldhash my %name;
450             fieldhash my %comment;
451             fieldhash my %utf8_upgrade;
452             fieldhash my %utf8_downgrade;
453             fieldhash my %match;
454             fieldhash my %reason;
455             fieldhash my %test;
456             fieldhash my %show_line;
457             fieldhash my %full_text;
458             fieldhash my %todo;
459             fieldhash my %tags;
460             fieldhash my %no_keep_message;
461              
462             sub init {
463 19     19   28 my $self = shift;
464 19         69 my %arg = @_;
465              
466 19         156 $pattern {$self} = $arg {pattern};
467 19         77 $keep_pattern {$self} = $arg {keep_pattern};
468 19         66 $name {$self} = $arg {name};
469 19         63 $comment {$self} = $arg {comment};
470 19         64 $utf8_upgrade {$self} = $arg {utf8_upgrade};
471 19         55 $utf8_downgrade {$self} = $arg {utf8_downgrade};
472 19         62 $match {$self} = $arg {match};
473 19         77 $reason {$self} = $arg {reason};
474 19         63 $test {$self} = $arg {test};
475 19         67 $show_line {$self} = $arg {show_line};
476 19         62 $full_text {$self} = $arg {full_text};
477 19         70 $todo {$self} = $arg {todo};
478 19 100       70 $tags {$self} = $arg {tags} if exists $arg {tags};
479 19         65 $no_keep_message {$self} = $arg {no_keep_message};
480              
481 19         64 $self;
482             }
483              
484             sub args {
485 28     28   37 my $self = shift;
486             (
487             pattern => $pattern {$self},
488             keep_pattern => $keep_pattern {$self},
489             name => $name {$self},
490             comment => $comment {$self},
491             utf8_upgrade => $utf8_upgrade {$self},
492             utf8_downgrade => $utf8_downgrade {$self},
493             match => $match {$self},
494             reason => $reason {$self},
495             test => $test {$self},
496             show_line => $show_line {$self},
497             full_text => $full_text {$self},
498             todo => $todo {$self},
499 28         356 no_keep_message => $no_keep_message {$self},
500             )
501             }
502              
503             sub match {
504 22     22   61073 my $self = shift;
505 22         40 my $subject = shift;
506 22 100       69 my $captures = @_ % 2 ? shift : undef;
507              
508 22         66 Test::Regexp::match subject => $subject,
509             captures => $captures,
510             $self -> args,
511             @_;
512             }
513              
514             sub no_match {
515 6     6   4073 my $self = shift;
516 6         14 my $subject = shift;
517              
518 6         20 Test::Regexp::no_match subject => $subject,
519             $self -> args,
520             @_;
521             }
522              
523 9     9   62 sub name {$name {+shift}}
524              
525             sub set_tag {
526 2     2   4 my $self = shift;
527 2         9 $tags {$self} {$_ [0]} = $_ [1];
528             }
529             sub tag {
530 11     11   21 my $self = shift;
531 11         73 $tags {$self} {$_ [0]};
532             }
533              
534              
535              
536             1;
537              
538             __END__