File Coverage

blib/lib/Test/Regexp.pm
Criterion Covered Total %
statement 246 250 98.4
branch 92 102 90.2
condition 49 60 81.6
subroutine 28 28 100.0
pod 4 7 57.1
total 419 447 93.7


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