File Coverage

blib/lib/HTTP/WebTest/Parser.pm
Criterion Covered Total %
statement 150 151 99.3
branch 56 64 87.5
condition 10 13 76.9
subroutine 15 15 100.0
pod 2 2 100.0
total 233 245 95.1


line stmt bran cond sub pod time code
1             # $Id: Parser.pm,v 1.21 2003/03/02 11:52:10 m_ilya Exp $
2              
3             package HTTP::WebTest::Parser;
4              
5             =head1 NAME
6              
7             HTTP::WebTest::Parser - Parse wtscript files.
8              
9             =head1 SYNOPSIS
10              
11             use HTTP::WebTest::Parser;
12              
13             my $tests = HTTP::WebTest::Parser->parse($data);
14              
15             =head1 DESCRIPTION
16              
17             Parses a wtscript file and converts it to a set of test objects.
18              
19             =head1 CLASS METHODS
20              
21             =cut
22              
23 3     3   13917 use strict;
  3         10  
  3         391  
24              
25 3     3   149529 use Text::Balanced qw(extract_codeblock extract_delimited);
  3         3255023  
  3         1199  
26              
27 3     3   2043 use HTTP::WebTest::Utils qw(eval_in_playground make_sub_in_playground);
  3         9  
  3         414  
28              
29 3     3   30 use constant ST_FILE => 0;
  3         7  
  3         504  
30 3     3   17 use constant ST_TEST_BLOCK => 1;
  3         6  
  3         10078  
31              
32             # horizontal space regexp
33             my $reHS = qr/[\t ]/;
34             # sequence of any chars which doesn't contain ')', space chars and '=>'
35             my $reWORD = qr/(?: (?: [^=)\s] | [^)\s] (?!>) )+ )/x;
36             # eat comments regexp
37             my $reCOMMENT = qr/(?: \s*? ^ \s* \# .* )+/mx;
38              
39             =head2 parse ($data)
40              
41             Parses wtscript text data passed in a scalar variable C<$data>.
42              
43             =head3 Returns
44              
45             A list of two elements - a reference to an array that contains test
46             objects and a reference to a hash that contains test parameters.
47              
48             =cut
49              
50             sub parse {
51 17     17 1 176 my $class = shift;
52 17         30 my $data = shift;
53              
54 17         27 my($tests, $opts) = eval { _parse($data) };
  17         52  
55              
56 17 100       65 if($@) {
57 8         11 my $exc = $@;
58 8         15 chomp $exc;
59              
60 8   50     21 my $parse_pos = pos($data) || 0;
61              
62             # find reminder of string near error (without surrounding
63             # whitespace)
64 8         101 $data =~ /\G $reHS* (.*?) $reHS* $/gmx;
65 8         19 my $near = $1;
66 8 100       20 if($near eq '') {
67 4         6 $near = 'at the end of line';
68             } else {
69 4         9 $near = "near '$near'";
70             }
71              
72             # count lines
73 8         78 my $line_num = () = substr($data, 0, $parse_pos) =~ m|$|gmx;
74 8         23 pos($data) = $parse_pos;
75 8 100       33 $line_num-- if $data =~ /\G \z/gx;
76              
77 8         51 die <
78             HTTP::WebTest: wtscript parsing error
79             Line $line_num $near: $exc
80             MSG
81             }
82              
83              
84 9         41 return ($tests, $opts);
85             }
86              
87             sub _parse {
88 17     17   36 my $state = ST_FILE;
89 17         36 my $opts = {};
90 17         35 my $tests = [];
91 17         29 my $test = undef;
92              
93             PARSER:
94 17         23 while(1) {
95             # eat whitespace and comments
96 117         860 $_[0] =~ /\G $reCOMMENT /gcx;
97              
98             # eat whitespace
99 117         282 $_[0] =~ /\G \s+/gcx;
100              
101 117 100       290 if($state == ST_FILE) {
    50          
102 45 100       191 if($_[0] =~ /\G \z/gcx) {
    100          
103             # end of file
104 9         29 last PARSER;
105             } elsif($_[0] =~ /\G test_name (?=\W)/gcx) {
106             # found new test block start
107 19         50 $test = {};
108 19         29 $state = ST_TEST_BLOCK;
109              
110             # find test block name
111 19 50       264 if($_[0] =~ /\G $reHS* = $reHS* (?: \n $reHS*)?/gcx) {
112 19         66 $test->{test_name} = _parse_scalar($_[0]);
113              
114 19 50       88 die "Test name is missing\n"
115             unless defined $test->{test_name};
116             }
117             } else {
118             # expect global test parameter
119 17         51 my($name, $value) = _parse_param($_[0]);
120              
121 12 100       36 if(defined $name) {
122 11         33 _set_test_param($opts, $name, $value);
123             } else {
124 1         8 die "Global test parameter or test block is expected\n";
125             }
126             }
127             } elsif($state == ST_TEST_BLOCK) {
128 72 100       195 if($_[0] =~ /\G end_test (?=\W)/gcx) {
129 17         36 push @$tests, $test;
130 17         31 $state = ST_FILE;
131             } else {
132             # expect test parameter
133 55         135 my($name, $value) = _parse_param($_[0]);
134              
135 55 100       139 if(defined $name) {
136 53         116 _set_test_param($test, $name, $value);
137             } else {
138 2         17 die "Test parameter or end_test is expected\n";
139             }
140             }
141             } else {
142 0         0 die "Unknown state\n";
143             }
144             }
145              
146 9         32 return($tests, $opts);
147             }
148              
149             sub _set_test_param {
150 64     64   84 my $href = shift;
151 64         94 my $name = shift;
152 64         85 my $value = shift;
153              
154 64 100       676 if(exists $href->{$name}) {
155 2 50 33     36 $href->{$name} = [ $href->{$name} ]
156             if ref($href->{$name}) and ref($href->{$name}) eq 'ARRAY';
157 2         5 push @{$href->{$name}}, $value;
  2         10  
158             } else {
159 62         245 $href->{$name} = $value;
160             }
161             }
162              
163             sub _parse_param {
164 72     72   81 my $name;
165              
166 72 100       1385 if($_[0] =~ /\G ([a-zA-Z_]+) # param name
167             $reHS* = $reHS* (?: \n $reHS*)? # = (and optional space chars)
168             /gcx) {
169 69         303 $name = $1;
170             } else {
171 3         7 return;
172             }
173              
174 69         151 my $value = _parse_value($_[0]);
175 64 50       375 return unless defined $value;
176              
177 64         226 return ($name, $value);
178             }
179              
180             sub _parse_value {
181 244 100   244   778 if($_[0] =~ /\G \(/gcx) {
182             # list elem
183             #
184             # ( scalar
185             # ...
186             # scalar )
187             #
188             # ( scalar => scalar
189             # ...
190             # scalar => scalar )
191              
192 41         74 my @list = ();
193              
194 41         53 while(1) {
195             # eat whitespace and comments
196 194         1029 $_[0] =~ /\G $reCOMMENT /gcx;
197              
198             # eat whitespace
199 194         444 $_[0] =~ /\G \s+/gcx;
200              
201             # exit loop on closing bracket
202 194 100       504 last if $_[0] =~ /\G \)/gcx;
203              
204 154         319 my $value = _parse_value($_[0]);
205              
206 154 100       384 die "Missing right bracket\n"
207             unless defined $value;
208              
209 153         273 push @list, $value;
210              
211 153 100       2351 if($_[0] =~ /\G $reHS* => $reHS* /gcx) {
212             # handles second part of scalar => scalar syntax
213 21         105 my $value = _parse_value($_[0]);
214              
215 21 50       62 die "Missing right bracket\n"
216             unless defined $value;
217              
218 21         1110 push @list, $value;
219             }
220             }
221              
222 40         333 return \@list;
223             } else {
224             # may return undef
225 203         473 return _parse_scalar($_[0]);
226             }
227             }
228              
229             sub _parse_scalar {
230 222     222   322 my $parse_pos = pos $_[0];
231              
232 222 100       653 if($_[0] =~ /\G (['"])/gcx) {
    100          
233 64         118 my $delim = $1;
234              
235 64         132 pos($_[0]) = $parse_pos;
236 64         224 my($extracted) = extract_delimited($_[0]);
237 64 100       5389 die "Can't find string terminator \"$delim\"\n"
238             if $extracted eq '';
239              
240 62 100 100     254 if($delim eq "'" or $extracted !~ /[\$\@\%]/) {
241             # variable interpolation impossible - just evalute string
242             # to get rid of escape chars
243 50         157 my $ret = eval_in_playground($extracted);
244              
245 50         85 chomp $@;
246 50 50       128 die "Eval error\n$@\n" if $@;
247              
248 50         156 return $ret;
249             } else {
250             # variable interpolation possible - evaluate as subroutine
251             # which will be used as callback
252 12         85 my $ret = make_sub_in_playground($extracted);
253              
254 12         28 chomp $@;
255 12 50       101 die "Eval error\n$@\n" if $@;
256              
257 12         39 return $ret;
258             }
259             } elsif($_[0] =~ /\G \{/gcx) {
260 12         36 pos($_[0]) = $parse_pos;
261 12         66 my($extracted) = extract_codeblock($_[0]);
262 12 100       5935127 die "Missing right curly bracket\n"
263             if $extracted eq '';
264              
265 11         45 my $ret = make_sub_in_playground($extracted);
266              
267 11         29 chomp $@;
268 11 100       41 die "Eval error\n$@\n" if $@;
269              
270 10         39 return $ret;
271             } else {
272 146         1306 $_[0] =~ /\G ((?: $reWORD $reHS+ )* $reWORD )/gcxo;
273 146         383 my $extracted = $1;
274              
275             # may return undef
276 146         470 return $extracted;
277             }
278             }
279              
280             =head2 write_test ($params_aref)
281              
282             Given a set of test parameters generates text representation of the
283             test.
284              
285             =head3 Returns
286              
287             The test text.
288              
289             =cut
290              
291             sub write_test {
292 2     2 1 26 my $class = shift;
293 2         5 my($params_aref) = @_;
294 2         9 my %params = @$params_aref;
295              
296 2         3 my $wtscript = '';
297              
298 2   100     12 $wtscript .= _write_param_value('test_name',
299             $params{test_name} || 'N/A',
300             '');
301              
302 2         12 for(my $i = 0; $i < @$params_aref/2; $i ++) {
303 9         16 my $param = $params_aref->[2 * $i];
304 9         13 my $value = $params_aref->[2 * $i + 1];
305 9 100       22 next if $param eq 'test_name';
306 8         21 $wtscript .= _write_param_value($params_aref->[2 * $i],
307             $params_aref->[2 * $i + 1],
308             ' ' x 4);
309             }
310              
311 2         4 $wtscript .= "end_test\n";
312              
313 2         10 return $wtscript;
314             }
315              
316             sub _write_param_value {
317 10     10   15 my($param, $value, $indent) = @_;
318              
319 10         22 my $wtscript = "$indent$param = ";
320 10         17 my $value_indent = ' ' x length($wtscript);
321 10         18 $wtscript .= _write_value($value, $value_indent) . "\n";
322              
323 10         41 return $wtscript;
324             }
325              
326             sub _write_value {
327 38     38   59 my($value, $indent) = @_;
328              
329 38         42 my $wtscript = '';
330 38 100       197 if(UNIVERSAL::isa($value, 'ARRAY')) {
331 8         10 $wtscript .= "(\n";
332 8         11 for my $subvalue (@$value) {
333 28         36 my $subindent = "$indent ";
334 28         31 $wtscript .= $subindent;
335 28         42 $wtscript .= _write_value($subvalue, $subindent);
336 28         54 $wtscript .= "\n";
337             }
338 8         30 $wtscript .= "$indent)";
339             } else {
340 30         43 $wtscript .= _write_scalar($value);
341             }
342              
343 38         80 return $wtscript;
344             }
345              
346             sub _write_scalar {
347 30     30   36 my($scalar) = @_;
348              
349 30 100 100     120 if($scalar =~ /[()'"{}]/ or $scalar =~ /=>/) {
350 16         18 my $q_scalar = $scalar;
351 16         37 $q_scalar =~ s/(['\\])/\\$1/g;
352 16         159 return "'" . $q_scalar . "'";
353             } else {
354 14         28 return $scalar;
355             }
356             }
357              
358             =head1 COPYRIGHT
359              
360             Copyright (c) 2001-2003 Ilya Martynov. All rights reserved.
361              
362             This program is free software; you can redistribute it and/or modify
363             it under the same terms as Perl itself.
364              
365             =head1 SEE ALSO
366              
367             L
368              
369             L
370              
371             =cut
372              
373             1;