File Coverage

blib/lib/App/SimpleScan/TestSpec.pm
Criterion Covered Total %
statement 12 102 11.7
branch 0 38 0.0
condition 0 11 0.0
subroutine 4 9 44.4
pod 4 4 100.0
total 20 164 12.2


line stmt bran cond sub pod time code
1             package App::SimpleScan::TestSpec;
2 8     8   13467 use strict;
  8         26  
  8         303  
3 8     8   44 use warnings;
  8         27  
  8         388  
4 8     8   2046 use Regexp::Common;
  8         17643  
  8         62  
5              
6 8     8   168980 use base qw(Class::Accessor::Fast);
  8         17  
  8         7559  
7             our $VERSION = 0.24;
8              
9             __PACKAGE__->mk_accessors(qw(raw uri regex delim kind comment metaquote syntax_error flags test_count));
10              
11             my $app; # Will store a reference to the parent App::Simplescan
12              
13             my %test_type =
14             (
15             'Y' => <<"EOS",
16             page_like "",
17             qr,
18             qq( [] [ should match]);
19             EOS
20             'N' => <<"EOS",
21             page_unlike "",
22             qr,
23             qq( [] [ shouldn't match]);
24             EOS
25             'TY' => <<"EOS",
26             TODO: {
27             local \$Test::WWW::Simple::TODO = "Doesn't match now but should later";
28             page_like "",
29             qr,
30             qq( [] [ should match]);
31             }
32             EOS
33             'TN' => <<"EOS",
34             TODO: {
35             local \$Test::WWW::Simple::TODO = "Matches now but shouldn't later";
36             page_unlike "",
37             qr,
38             qq( [] [ shouldn't match]);
39             }
40             EOS
41             'SY' => <<"EOS",
42             SKIP: {
43             skip 'Deliberately skipping test that should match', 1;
44             page_like "",
45             qr,
46             qq( [] [ should match]);
47             }
48             EOS
49             'SN' => <<"EOS",
50             SKIP: {
51             skip "Deliberately skipping test that shouldn't match", 1;
52             page_unlike "",
53             qr,
54             qq( [] [ shouldn't match]);
55             }
56             EOS
57             );
58              
59             sub app {
60 0     0 1   my ($class_or_object, $appref) = @_;
61 0 0         if (defined $appref) {
62 0           $app = $appref;
63             }
64 0           return $app;
65             }
66              
67             sub new {
68 0     0 1   my ($class, $spec) = @_;
69 0           my $self = {};
70 0           bless $self, $class;
71              
72             # Store the test spec.
73 0           $self->raw($spec);
74 0           $self->test_count(0);
75 0           $self->syntax_error(!$self->parse);
76              
77 0           return $self;
78             }
79              
80             sub parse {
81 0     0 1   my ($self, $line) = @_;
82 0 0         if (!defined $line) {
83 0           $line = $self->raw;
84             }
85 0           chomp $line;
86              
87             # Originally, we used Regex::Common to parse the URI and regex
88             # off the test spec line, but that's not going to work now since
89             # we've switched to keeping the text substitutions in place
90             # until we're ready to expand the spec into tests.
91             #
92             # So we'll do it like this: remove everything up to the first
93             # set of whitespace and call it the URI. *Reverse* the string,
94             # and match everything up to the whitespace before the kind of
95             # test; this grabs off the comment and the kind.
96             #
97             # We treat whatever is left at this point as the regex, in
98             # three phases. First, is it a standard slash-delimited
99             # regex? If not, is it an m-style regex (m| ...|, with
100             # arbitrary quote characters)? If not, then we treat it as
101             # a literal string to match (stripping off the slashes on
102             # either end if they are there.
103            
104             # Remove URI portion.
105 0           my ($URI, $rest) = ($line =~ /^(.*?)\s+(.*)$/mx);
106              
107 0 0         if (! defined $URI) {
108 0           return 0;
109             }
110              
111             # Pull the scheme from the URI and pass it explicitly to
112             # Regexp::Common. Otherwise Regexp::Common::URI::http
113             # assumes 'HTTP', meaning that any other scheme won't match,
114             # causing this code to ignore (for instance) https: links.
115             #
116             # We also check for messed-up schemes here: a common error is
117             # to have left off on % on a pragma, causing the line to be
118             # passed into this code.
119 0           my ($scheme) = $URI =~ /^(\w+)/mx;
120 0 0         if (!defined $scheme) {
121 0           $app->stack_test(<
122             fail "malformed pragma or URL scheme: '$URI'";
123             EOS
124 0           return 0;
125             }
126             # Not the canonical single-precent error. See if it's a good scheme.
127 0 0         return 0 if !($URI =~ /$RE{URI}{HTTP}{-scheme => $scheme }/mx);
128              
129             # Remove comment and kind.
130 0           my ($comment, undef, $kind, $maybe_regex) =
131             ((scalar reverse $rest) =~ /^(.*?)(\s+|\s*)\b(Y|N|YT|NT|YS|NS)\s+(.*)$/mx);
132 0           $self->comment(scalar reverse $comment);
133 0           $self->kind(scalar reverse $kind);
134 0           $self->uri($URI);
135              
136 0           my($clean, $delim, $flags);
137              
138             # Clean up regex if needed.
139 0           my $regex = reverse $maybe_regex;
140 0 0         if ((undef, undef, $clean, undef, $flags) =
    0          
    0          
141             ($regex =~ m|^$RE{delimited}{-delim=>'/'}{-keep}([ics]*)$|mx)) {
142             # Standard slash-delimited regex.
143 0           $self->regex($clean);
144 0           $self->delim('/');
145 0           $self->flags($flags);
146             }
147             elsif (($delim, $clean, $flags) = ($regex =~ /^m(.)(.*)\1([ics]*)$/mx)) {
148             # m-something-regex-something pattern.
149 0           $self->delim($1);
150 0           $self->regex($clean);
151 0           $self->flags($flags);
152             }
153             elsif (($clean, $flags) = ($regex =~ m|^/(.*)/([ics]*)$|mx)) {
154             # slash-delimited, with flags.
155 0           $self->delim('/');
156 0           $self->regex($clean);
157 0           $self->metaquote(1);
158 0           $self->flags($flags);
159             }
160             else {
161             # random string. We'll metaquote it and put slashes around it.
162 0           $self->delim('/');
163 0           $self->regex($regex);
164 0           $self->metaquote(1);
165             }
166              
167 0 0         if (! defined $self->flags) {
168 0           $self->flags(q{});
169             }
170              
171             # If we got this far, it's valid.
172 0           return 1;
173             }
174              
175             sub _render_regex {
176 0     0     my ($self) = shift;
177 0           my $regex = $self->regex;
178 0           my $delim = $self->delim;
179 0           my $flags = $self->flags;
180 0 0         if (!defined $flags) {
181 0           $self->flags(q{});
182 0           $flags = q{};
183             }
184              
185 0 0         if ($self->metaquote) {
186 0           $regex = "\\Q$regex\\E";
187             }
188 0 0         if ($delim ne '/') {
189 0           $regex = "m$delim$regex$delim";
190             }
191             else {
192 0           $regex = "/$regex/";
193             }
194 0 0         if ($flags) {
195 0           $regex .= $flags;
196             }
197 0 0         if ($regex =~ /\\/mx) {
198             # Have to escape backslashes.
199 0           $regex =~ s/\\/\\\\/mxg;
200             }
201              
202 0           return $regex;
203             }
204              
205             sub as_tests {
206 0     0 1   my ($self) = @_;
207 0           my @tests;
208 0           my $current = 0;
209 0   0       my $flags = $self->flags() || q{};
210 0           my $uri = $self->uri;
211              
212 0 0 0       if (defined $uri and
      0        
      0        
213             defined(my $regex = $self->regex) and
214             defined(my $delim = $self->delim) and
215             defined(my $comment = $self->comment)) { ##no critic
216 0 0         if (defined($tests[$current] = $test_type{$self->kind})) { ##no critic
217 0           $self->test_count($self->test_count()+1);
218 0           $tests[$current] =~ s//$uri/mxg;
219 0           $tests[$current] =~ s//$delim/mxg;
220 0 0         if ($self->metaquote) {
221 0           $tests[$current] =~ s//\Q$regex\E/mxg;
222             }
223             else {
224 0           $tests[$current] =~ s//$regex/mxg;
225             }
226 0           $tests[$current] =~ s//$flags/mxg;
227 0           $tests[$current] =~ s//$comment/mx;
228 0           my $qregex = $self->_render_regex();
229 0           $tests[$current] =~ s//$qregex/emx;
  0            
230             }
231             }
232              
233             # Call any plugin per_test routines.
234 0           for my $test_code (@tests) {
235 0           $app->stack_test($test_code);
236 0           for my $plugin ($app->plugins) {
237 0 0         next if ! $plugin->can('per_test');
238              
239 0           my ($added_tests, @per_test_code) = $plugin->per_test($self);
240 0 0         my $method = $added_tests ? 'stack_test' : 'stack_code';
241 0           for my $code_line (@per_test_code) {
242 0           $app->$method($code_line);
243             }
244             }
245             }
246 0           return;
247             }
248              
249             1; # Magic true value required at end of module
250             __END__