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   271095 use 5.010;
  18         55  
4              
5             BEGIN {
6 18     18   308 binmode STDOUT, ":utf8";
7             }
8              
9 18     18   67 use strict;
  18         33  
  18         299  
10 18     18   56 use warnings;
  18         19  
  18         405  
11 18     18   7776 use charnames ":full";
  18         465457  
  18         97  
12 18     18   2954 no warnings 'syntax';
  18         28  
  18         661  
13              
14 18     18   64 use Exporter ();
  18         20  
  18         240  
15 18     18   647 use Test::Builder;
  18         8103  
  18         16550  
16              
17             our @EXPORT = qw [match no_match];
18             our @ISA = qw [Exporter Test::More];
19              
20             our $VERSION = '2017040101';
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   130 my $self = shift;
35 18         29 my $pkg = caller;
36              
37 18         36 my %arg = @_;
38              
39 18         170 $Test -> exported_to ($pkg);
40              
41 18   100     538 $arg {import} //= [qw [match no_match]];
42              
43 18         80 while (my ($key, $value) = each %arg) {
44 19 100       84 if ($key eq "tests") {
    50          
45 1         3 $Test -> plan ($value);
46             }
47             elsif ($key eq "import") {
48 18 50       1054 $self -> export_to_level (1, $self, $_) for @{$value || []};
  18         4489  
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 629 my ($str, $escape) = @_;
61 619   66     1286 $escape //= $ESCAPE_DEFAULT;
62 619 50       866 return $str if $escape == $ESCAPE_NONE;
63 619         743 $str =~ s/\n/\\n/g;
64 619         437 $str =~ s/\t/\\t/g;
65 619         448 $str =~ s/\r/\\r/g;
66 619 100       1108 if ($escape == $ESCAPE_NAMES) {
    100          
    100          
67 58         196 $str =~ s{([^\x20-\x7E])}
68 38         134 {my $name = charnames::viacode (ord $1);
69 38 50       9907 $name ? sprintf "\\N{%s}" => $name
70             : sprintf "\\x{%02X}" => ord $1}eg;
71             }
72             elsif ($escape == $ESCAPE_CODES) {
73 445         695 $str =~ s{([^\x20-\x7E])}
74 38         177 {sprintf "\\x{%02X}" => ord $1}eg;
75             }
76             elsif ($escape == $ESCAPE_NON_PRINTABLE) {
77 58         102 $str =~ s{([\x00-\x1F\xFF])}
78 0         0 {sprintf "\\x{%02X}" => ord $1}eg;
79             }
80 619         1096 $str;
81             }
82              
83             sub pretty {
84 421     421 0 361 my $str = shift;
85 421         560 my %arg = @_;
86 421 100 100     860 substr ($str, 50, -5, "...") if length $str > 55 && !$arg {full_text};
87 421         608 $str = escape $str, $arg {escape};
88 421         607 $str;
89             }
90              
91              
92             sub mess {
93 223     223 0 213 my $val = shift;
94 223 50       361 unless (defined $val) {return 'undefined'}
  0         0  
95 223         384 my %arg = @_;
96             my $pretty = pretty $val, full_text => $arg {full_text},
97 223         358 escape => $arg {escape};
98 223 100 66     796 if ($pretty eq $val && $val !~ /'/) {
    50          
99 183         1026 return "eq '$val'";
100             }
101             elsif ($pretty !~ /"/) {
102 40         219 return 'eq "' . $pretty . '"';
103             }
104             else {
105 0         0 return "eq qq {$pretty}";
106             }
107             }
108              
109              
110             sub todo {
111 198     198 1 669 my %arg = @_;
112 198         222 my $subject = $arg {subject};
113 198         192 my $comment = $arg {comment};
114 198         178 my $upgrade = $arg {upgrade};
115 198         162 my $downgrade = $arg {downgrade};
116 198 100       325 my $neg = $arg {match} ? "" : "not ";
117 198         177 my $full_text = $arg {full_text};
118 198         196 my $escape = $arg {escape};
119              
120 198         157 my $line = "";
121              
122 198 100       301 if ($arg {show_line}) {
123 18     18   1262 no warnings 'once';
  18         676  
  18         16746  
124 24   50     138 my ($file, $l_nr) = (caller ($Test::Builder::deepness // 1)) [1, 2];
125 24         56 $line = " [$file:$l_nr]";
126             }
127              
128 198         299 my $subject_pretty = pretty $subject, full_text => $full_text,
129             escape => $escape;
130 198         324 my $Comment = qq {qq {$subject_pretty}};
131 198         351 $Comment .= qq { ${neg}matched by "$comment"};
132              
133 198         409 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     2116 if ($upgrade && ($upgrade == 2 || !utf8::is_utf8 ($subject)
    100 66        
      33        
      66        
145             && $subject =~ /[\x80-\xFF]/)) {
146 24         27 my $subject_utf8 = $subject;
147 24 50       65 if (utf8::upgrade ($subject_utf8)) {
148 24         41 my $Comment_utf8 = qq {qq {$subject_pretty}};
149 24         29 $Comment_utf8 .= qq { [UTF-8]};
150 24         35 $Comment_utf8 .= qq { ${neg}matched by "$comment"};
151              
152 24         51 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         29 my $subject_non_utf8 = $subject;
159 24 50       62 if (utf8::downgrade ($subject_non_utf8)) {
160 24         42 my $Comment_non_utf8 = qq {qq {$subject_pretty}};
161 24         39 $Comment_non_utf8 .= qq { [non-UTF-8]};
162 24         92 $Comment_non_utf8 .= qq { ${neg}matched by "$comment"};
163              
164 24         52 push @todo => [$subject_non_utf8, $Comment_non_utf8, $line];
165             }
166             }
167              
168 198         521 @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 395420 my %arg = @_;
197              
198 198         290 my $name = $arg {name};
199 198         188 my $pattern = $arg {pattern};
200 198         190 my $keep_pattern = $arg {keep_pattern};
201 198         196 my $subject = $arg {subject};
202 198   100     591 my $captures = $arg {captures} // [];
203 198   100     1018 my $comment = escape $arg {comment} // $name // "";
      100        
204 198   100     517 my $upgrade = $arg {utf8_upgrade} // 1;
205 198   100     487 my $downgrade = $arg {utf8_downgrade} // 1;
206 198   100     372 my $match = $arg {match} // 1;
207             my $reason = defined $arg {reason}
208 198 100       339 ? " [Reason: " . $arg {reason} . "]"
209             : "";
210             my $test = defined $arg {test}
211 198 100       304 ? " [Test: " . $arg {test} . "]"
212             : "";
213 198         169 my $show_line = $arg {show_line};
214 198         173 my $full_text = $arg {full_text};
215 198         181 my $escape = $arg {escape};
216 198         187 my $todo = $arg {todo};
217 198 100       282 my $keep_message = $arg {no_keep_message} ? "" : " (with -Keep)";
218              
219 198         149 my $numbered_captures;
220             my $named_captures;
221              
222 198         182 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         281 foreach my $capture (@$captures) {
229 117 100       179 if (ref $capture eq 'ARRAY') {
230 70         84 my ($name, $match) = @$capture;
231 70         78 push @$numbered_captures => $match;
232 70         58 push @{$$named_captures {$name}} => $match;
  70         152  
233             }
234             else {
235 47         59 push @$numbered_captures => $capture;
236             }
237             }
238            
239 198   100     471 $numbered_captures ||= [];
240 198   100     475 $named_captures ||= {};
241              
242 198         385 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       333 $Test -> todo_start ($todo) if defined $todo;
252              
253             #
254             # Now we will do the tests.
255             #
256 198         400 foreach my $todo (@todo) {
257 246         287 my $subject = $$todo [0];
258 246         202 my $comment = $$todo [1];
259 246         187 my $line = $$todo [2];
260              
261 246 100 100     681 if ($match && defined $pattern) {
262 134         115 my $comment = $comment;
263 134 100       376 my $pat = ref $pattern ? $pattern
264             : qr /$pattern/;
265 134         561 $comment =~ s{""$}{/$pat/};
266 134         175 $comment .= "$line$test";
267             #
268             # Test match; match should also be complete, and not
269             # have any captures.
270             #
271             SKIP: {
272 134         115 my $result = $subject =~ /^$pattern/;
  134         805  
273 134 100       597 unless ($Test -> ok ($result, $comment)) {
274 3         227 $Test -> skip ("Match failed") for 1 .. 3;
275 3         311 $pass = 0;
276 3         9 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         9620 my $named_matches = 0;
284 18     18   7506 $named_matches += @$_ for values %-;
  18         7855  
  18         6044  
  131         671  
285              
286 131 100       561 unless ($Test -> is_eq ($&, $subject,
287             "${__}match is complete")) {
288 22         4958 $Test -> skip ("Match failed") for 2 .. 3;
289 22         1575 $pass = 0;
290 22         52 last SKIP;
291             }
292            
293 109 100       16280 $pass = 0 unless
294             $Test -> is_eq (scalar @+, 1,
295             "${__}no numbered captures");
296 109 100       15478 $pass = 0 unless
297             $Test -> is_eq ($named_matches, 0,
298             "${__}no named captures");
299             }
300             }
301              
302              
303 246 100 100     15364 if ($match && defined $keep_pattern) {
304 82         74 my $comment = $comment;
305 82 100       257 my $pat = ref $keep_pattern ? $keep_pattern
306             : qr /$keep_pattern/;
307 82         427 $comment =~ s{""$}{/$pat/};
308 82         121 $comment .= $keep_message;
309 82         94 $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         73 my $nr_of_tests = 0;
  82         76  
327 82         90 $nr_of_tests += 1; # For match.
328 82         62 $nr_of_tests += 1; # For match complete.
329 82         178 $nr_of_tests += @{$_} for values %$named_captures;
  98         105  
330             # Number of named captures.
331 82         106 $nr_of_tests += scalar keys %$named_captures;
332             # Number of different named captures.
333 82         59 $nr_of_tests += 1; # Right number of named captures.
334 82         73 $nr_of_tests += @$numbered_captures;
335             # Number of numbered captures.
336 82         73 $nr_of_tests += 1; # Right number of numbered captures.
337              
338 82         89 my ($amp, @numbered_matches, %minus);
339              
340 82         1234 my $result = $subject =~ /^$keep_pattern/;
341 82 100       386 unless ($Test -> ok ($result, $comment)) {
342 2         165 $Test -> skip ("Match failed") for 2 .. $nr_of_tests;
343 2         350 $pass = 0;
344 2         6 last SKIP;
345             }
346              
347              
348             #
349             # Copy $&, $N and %- before doing anything that
350             # migh override them.
351             #
352              
353 80         6268 $amp = $&;
354              
355             #
356             # Grab numbered captures.
357             #
358 80         207 for (my $i = 1; $i < @+; $i ++) {
359 18     18   91 no strict 'refs';
  18         18  
  18         13340  
360 139         395 push @numbered_matches => $$i;
361             }
362              
363             #
364             # Copy %-;
365             #
366 80         571 while (my ($key, $value) = each %-) {
367 98         533 $minus {$key} = [@$value];
368             }
369              
370             #
371             # Test to see if match is complete.
372             #
373 80 100       372 unless ($Test -> is_eq ($amp, $subject,
374             "${__}match is complete")) {
375 6         1129 $Test -> skip ("Match incomplete") for 3 .. $nr_of_tests;
376 6         1368 $pass = 0;
377 6         24 last SKIP;
378             }
379              
380             #
381             # Test named captures.
382             #
383 74         11022 while (my ($key, $value) = each %$named_captures) {
384 93         6379 for (my $i = 0; $i < @$value; $i ++) {
385             $pass = 0 unless
386             $Test -> is_eq (
387 97 100       1042 $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       13372 $Test -> is_num (scalar @{$minus {$key} || []},
  93 100       650  
    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       7457 $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         10182 for (my $i = 0; $i < @$numbered_captures; $i ++) {
414 126 100       8993 $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       8682 $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     10950 if (!$match && defined $pattern) {
434 33         36 my $comment = $comment;
435 33 100       101 my $pat = ref $pattern ? $pattern
436             : qr /$pattern/;
437 33         100 $comment =~ s{""$}{/$pat/};
438 33         44 $comment .= "$line$reason";
439 33         257 my $r = $subject =~ /^$pattern/;
440 33 100 100     222 $pass = 0 unless
441             $Test -> ok (!$r || $subject ne $&, $comment);
442             }
443 246 100 100     3253 if (!$match && defined $keep_pattern) {
444 1         2 my $comment = $comment;
445 1 50       4 my $pat = ref $keep_pattern ? $keep_pattern
446             : qr /$keep_pattern/;
447 1         3 $comment =~ s{""$}{/$pat/};
448 1         2 $comment .= $keep_message;
449 1         2 $comment .= "$line$reason";
450 1         48 my $r = $subject =~ /^$keep_pattern/;
451 1 50 33     7 $pass = 0 unless
452             $Test -> ok (!$r || $subject ne $&, $comment);
453             }
454             }
455              
456 198 100       514 $Test -> todo_end if defined $todo;
457              
458 198         957 $pass;
459             }
460              
461             sub no_match {
462 6     6 1 16 push @_ => match => 0;
463 6         16 goto &match;
464             }
465              
466             sub new {
467 19     19 0 14509 "Test::Regexp::Object" -> new
468             }
469              
470             package Test::Regexp::Object;
471              
472             sub new {
473 21     21   38 bless \do {my $var} => shift;
  21         93  
474             }
475              
476 18     18   8227 use Hash::Util::FieldHash qw [fieldhash];
  18         13510  
  18         10189  
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   31 my $self = shift;
496 19         48 my %arg = @_;
497              
498 19         122 $pattern {$self} = $arg {pattern};
499 19         51 $keep_pattern {$self} = $arg {keep_pattern};
500 19         34 $name {$self} = $arg {name};
501 19         36 $comment {$self} = $arg {comment};
502 19         36 $utf8_upgrade {$self} = $arg {utf8_upgrade};
503 19         36 $utf8_downgrade {$self} = $arg {utf8_downgrade};
504 19         34 $match {$self} = $arg {match};
505 19         44 $reason {$self} = $arg {reason};
506 19         34 $test {$self} = $arg {test};
507 19         36 $show_line {$self} = $arg {show_line};
508 19         38 $full_text {$self} = $arg {full_text};
509 19         48 $escape {$self} = $arg {escape};
510 19         44 $todo {$self} = $arg {todo};
511 19 100       52 $tags {$self} = $arg {tags} if exists $arg {tags};
512 19         33 $no_keep_message {$self} = $arg {no_keep_message};
513              
514 19         41 $self;
515             }
516              
517             sub args {
518 28     28   20 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         236 no_keep_message => $no_keep_message {$self},
534             )
535             }
536              
537             sub match {
538 22     22   30695 my $self = shift;
539 22         24 my $subject = shift;
540 22 100       48 my $captures = @_ % 2 ? shift : undef;
541              
542 22         43 Test::Regexp::match subject => $subject,
543             captures => $captures,
544             $self -> args,
545             @_;
546             }
547              
548             sub no_match {
549 6     6   4432 my $self = shift;
550 6         8 my $subject = shift;
551              
552 6         33 Test::Regexp::no_match subject => $subject,
553             $self -> args,
554             @_;
555             }
556              
557 9     9   47 sub name {$name {+shift}}
558              
559             sub set_tag {
560 2     2   1 my $self = shift;
561 2         6 $tags {$self} {$_ [0]} = $_ [1];
562             }
563             sub tag {
564 11     11   13 my $self = shift;
565 11         52 $tags {$self} {$_ [0]};
566             }
567              
568              
569              
570             1;
571              
572             __END__