line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Test::WWW::Mechanize::Driver; |
2
|
9
|
|
|
9
|
|
117367
|
use Carp; use strict; use warnings; |
|
9
|
|
|
9
|
|
25
|
|
|
9
|
|
|
9
|
|
671
|
|
|
9
|
|
|
|
|
48
|
|
|
9
|
|
|
|
|
19
|
|
|
9
|
|
|
|
|
468
|
|
|
9
|
|
|
|
|
46
|
|
|
9
|
|
|
|
|
15
|
|
|
9
|
|
|
|
|
234
|
|
3
|
9
|
|
|
9
|
|
5656
|
use Test::WWW::Mechanize::Driver::YAMLLoader; |
|
9
|
|
|
|
|
26
|
|
|
9
|
|
|
|
|
259
|
|
4
|
9
|
|
|
9
|
|
45
|
use Test::WWW::Mechanize::Driver::Util qw/ :all /; |
|
9
|
|
|
|
|
14
|
|
|
9
|
|
|
|
|
1580
|
|
5
|
|
|
|
|
|
|
require Test::WWW::Mechanize::Driver::MagicValues; |
6
|
9
|
|
|
9
|
|
11783
|
use Test::Builder; |
|
9
|
|
|
|
|
101872
|
|
|
9
|
|
|
|
|
402
|
|
7
|
|
|
|
|
|
|
require URI; |
8
|
9
|
|
|
9
|
|
22608
|
use Storable qw/dclone/; |
|
9
|
|
|
|
|
45368
|
|
|
9
|
|
|
|
|
31343
|
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
my $Test = Test::Builder->new; |
11
|
|
|
|
|
|
|
our $VERSION = 0.6; |
12
|
|
|
|
|
|
|
our $TODO; |
13
|
|
|
|
|
|
|
our $CURRENT_GROUP; |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
=pod |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
=head1 NAME |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
Test::WWW::Mechanize::Driver - Drive Test::WWW::Mechanize Object Using YAML Configuration Files |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
=head1 SYNOPSIS |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
use strict; use warnings; |
24
|
|
|
|
|
|
|
use Test::WWW::Mechanize::Driver; |
25
|
|
|
|
|
|
|
Test::WWW::Mechanize::Driver->new( |
26
|
|
|
|
|
|
|
load => [ glob( "t/*.yaml" ) ] |
27
|
|
|
|
|
|
|
)->run; |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
use strict; use warnings; |
31
|
|
|
|
|
|
|
use Test::WWW::Mechanize::Driver; |
32
|
|
|
|
|
|
|
Test::WWW::Mechanize::Driver->new->run; # runs basename($0)*.{yaml.yml,dat} |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
=head1 DESCRIPTION |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
Write Test::WWW::Mechanize tests in YAML. This module will load the tests |
37
|
|
|
|
|
|
|
make a plan and run the tests. Supports every-page tests, SKIP, TODO, and |
38
|
|
|
|
|
|
|
any object supporting the Test::WWW::Mechanize interface. |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
This document focuses on the Test::WWW::Mechanize::Driver object and the |
41
|
|
|
|
|
|
|
options it can take. See the L |
42
|
|
|
|
|
|
|
for a full description of the test data file format. |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
=head1 USAGE |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
=head3 new |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
Test::WWW::Mechanize::Driver->new( [ OPTIONS ] ) |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
=over 4 |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
=item add_to_plan |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
Number of tests running outside of Driver control. Use this option if your |
55
|
|
|
|
|
|
|
test script perfoirms other tests such as build-up of mock objects. |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
=item after_response |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
=item after_response_tests |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
C is a callback sub triggered once per test group (is not |
62
|
|
|
|
|
|
|
triggered by submit_form_ok or other actions) immediately after the initial |
63
|
|
|
|
|
|
|
response is received. If any tests are run in the callback, the |
64
|
|
|
|
|
|
|
C option must be set to the number of tests to be run |
65
|
|
|
|
|
|
|
each time so that the driver may make the proper plan. |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
=item base |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
Base URL for any test uris which are not absolute. If not defined, all test |
70
|
|
|
|
|
|
|
uris must be absolute. |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
=item load |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
Array ref of file names which should be loaded by the Driver. These tests |
75
|
|
|
|
|
|
|
are loaded at object creation time. |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
=item loader |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
Name of loader package or object with Cload( $file )> method. |
80
|
|
|
|
|
|
|
Defaults to C. |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
=item mechanize |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
Override default mechanize object. The default object is: |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
Test::WWW::Mechanize->new(cookie_jar => {}) |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
=item no_plan |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
When true, calling C<-Erun> will not print a test plan. |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
=back |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
=cut |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
our %valid_params = map +($_,1), |
97
|
|
|
|
|
|
|
qw/ |
98
|
|
|
|
|
|
|
add_to_plan after_response after_response_tests base load loader mechanize no_plan |
99
|
|
|
|
|
|
|
/; |
100
|
|
|
|
|
|
|
sub new { |
101
|
1
|
|
|
1
|
1
|
16
|
my $class = shift; |
102
|
1
|
|
|
|
|
4
|
my %x = @_; |
103
|
1
|
|
|
|
|
8
|
my ($invalid) = grep !$valid_params{$_}, keys %x; |
104
|
1
|
50
|
|
|
|
5
|
croak "Invalid Parameter '$invalid'" if defined($invalid); |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
# Create loader so that "require YAML" happens early on |
107
|
1
|
|
33
|
|
|
15
|
$x{loader} ||= Test::WWW::Mechanize::Driver::YAMLLoader->new; |
108
|
|
|
|
|
|
|
|
109
|
1
|
|
|
|
|
4
|
my $x = bless \%x, $class; |
110
|
1
|
|
|
|
|
5
|
$x->load; |
111
|
1
|
|
|
|
|
3
|
return $x; |
112
|
|
|
|
|
|
|
} |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
=head3 load |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
num tests loaded = $tester->load( test filenames ) |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
Load tests. |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
=cut |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
sub load { |
123
|
2
|
|
|
2
|
1
|
4
|
my $x = shift; |
124
|
|
|
|
|
|
|
|
125
|
2
|
50
|
66
|
|
|
13
|
$$x{load} = [ $$x{load} ] if HAS($x, 'load') and !ref($$x{load}); |
126
|
|
|
|
|
|
|
|
127
|
2
|
|
|
|
|
3
|
push @{$$x{load}}, @_; |
|
2
|
|
|
|
|
7
|
|
128
|
|
|
|
|
|
|
|
129
|
2
|
|
|
|
|
8
|
my $t = $x->tests; |
130
|
2
|
|
|
|
|
9
|
$x->_load; |
131
|
2
|
|
|
|
|
5
|
$x->tests - $t; |
132
|
|
|
|
|
|
|
} |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
=head3 tests |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
num tests = $tester->tests() |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
Calculate number of tests currently loaded |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
=cut |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
sub tests { |
143
|
5
|
|
|
5
|
1
|
12
|
my $x = shift; |
144
|
5
|
|
50
|
|
|
21
|
my $tests = $$x{add_to_plan} || 0; |
145
|
5
|
50
|
|
|
|
22
|
return $tests unless $$x{groups}; |
146
|
0
|
|
|
|
|
0
|
$tests += $x->_tests_in_group($_) for @{$$x{groups}}; |
|
0
|
|
|
|
|
0
|
|
147
|
0
|
|
|
|
|
0
|
return $tests; |
148
|
|
|
|
|
|
|
} |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
=head3 test_groups |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
num groups = $tester->test_groups() |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
Return number of test groups currently loaded |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
=cut |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
sub test_groups { |
159
|
1
|
|
|
1
|
1
|
4
|
my $x = shift; |
160
|
1
|
50
|
|
|
|
8
|
return 0 unless $$x{groups}; |
161
|
0
|
|
|
|
|
0
|
return 0 + @{$$x{groups}}; |
|
0
|
|
|
|
|
0
|
|
162
|
|
|
|
|
|
|
} |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
=head3 run |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
$tester->run() |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
Run each group of tests |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
=cut |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
sub run { |
173
|
1
|
|
|
1
|
1
|
3
|
my $x = shift; |
174
|
1
|
50
|
|
|
|
10
|
$x->_autoload unless $$x{_loaded}; |
175
|
1
|
50
|
|
|
|
14
|
die "No test groups!" unless $$x{groups}; |
176
|
0
|
|
|
|
|
0
|
$x->_ensure_plan; |
177
|
0
|
|
|
|
|
0
|
$x->_run_group( $_ ) for @{$$x{groups}}; |
|
0
|
|
|
|
|
0
|
|
178
|
|
|
|
|
|
|
} |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
=head3 mechanize |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
mech = $tester->mechanize() |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
Return or construct mechanize object |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
=cut |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
sub mechanize { |
189
|
0
|
|
|
0
|
1
|
0
|
my $x = shift; |
190
|
0
|
0
|
|
|
|
0
|
return $$x{mechanize} if $$x{mechanize}; |
191
|
0
|
|
|
|
|
0
|
require Test::WWW::Mechanize; |
192
|
0
|
|
|
|
|
0
|
$$x{mechanize} = Test::WWW::Mechanize->new(cookie_jar => {}); |
193
|
|
|
|
|
|
|
} |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
=head1 INTERNAL METHODS |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
=head3 _ensure_plan |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
$tester->_ensure_plan() |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
Feed a plan (expected_tests) to Test::Builder if a plan has not yet been given. |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
=cut |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
sub _ensure_plan { |
206
|
0
|
|
|
0
|
|
0
|
my $x = shift; |
207
|
0
|
0
|
|
|
|
0
|
$Test->expected_tests($x->tests) unless $Test->expected_tests; |
208
|
|
|
|
|
|
|
} |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
=head3 _run_group |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
$tester->_run_group( group hash ) |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
Run a group of tests. Performs group-level actions (SKIP, TODO) and tests |
215
|
|
|
|
|
|
|
initial request. |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
=cut |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
sub _run_group { |
220
|
0
|
|
|
0
|
|
0
|
my ($x, $group) = @_; |
221
|
0
|
|
|
|
|
0
|
$CURRENT_GROUP = $group; |
222
|
|
|
|
|
|
|
|
223
|
0
|
0
|
|
|
|
0
|
if ($$group{SKIP}) { |
224
|
0
|
|
|
|
|
0
|
local $TODO = undef; |
225
|
0
|
|
|
|
|
0
|
$Test->skip($$group{SKIP}) for 1..$x->_tests_in_group($group); |
226
|
0
|
|
|
|
|
0
|
return; |
227
|
|
|
|
|
|
|
} |
228
|
|
|
|
|
|
|
|
229
|
0
|
|
|
|
|
0
|
local $TODO = $$group{TODO}; |
230
|
0
|
|
|
|
|
0
|
$x->_make_initial_request( $group ); |
231
|
0
|
|
|
|
|
0
|
$x->_run_test( $group, $_ ) for @{$$group{_actions}}; |
|
0
|
|
|
|
|
0
|
|
232
|
|
|
|
|
|
|
} |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
=head3 _make_initial_request |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
$tester->_make_initial_request( group hash ) |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
Perform initial GET, POST, ... request. Makes after_response callback if |
239
|
|
|
|
|
|
|
present. |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
=cut |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
sub _make_initial_request { |
244
|
0
|
|
|
0
|
|
0
|
my ($x, $group) = @_; |
245
|
0
|
|
0
|
|
|
0
|
my $method = ($$group{method} ||= 'GET'); |
246
|
0
|
0
|
|
|
|
0
|
my @params = ($$group{parameters} ? $$group{parameters} : ()); |
247
|
0
|
|
|
|
|
0
|
my $label = $x->_test_label($group, "$method $$group{uri}", @{$$group{_id}}); |
|
0
|
|
|
|
|
0
|
|
248
|
|
|
|
|
|
|
|
249
|
0
|
0
|
|
|
|
0
|
if (uc($method) eq 'GET') { |
|
|
0
|
|
|
|
|
|
250
|
0
|
|
|
|
|
0
|
my $uri = build_uri( $$group{uri}, @params ); |
251
|
0
|
|
|
|
|
0
|
$x->mechanize->get_ok( $uri, $label ); |
252
|
|
|
|
|
|
|
} |
253
|
|
|
|
|
|
|
elsif (uc($method) eq 'POST') { |
254
|
0
|
|
|
|
|
0
|
$x->mechanize->post_ok( $$group{uri}, @params, $label ); |
255
|
|
|
|
|
|
|
} |
256
|
0
|
|
|
|
|
0
|
else { die "Unimplemented request method: '$method'" } |
257
|
|
|
|
|
|
|
|
258
|
0
|
0
|
|
|
|
0
|
$$x{after_response}->($x->mechanize, $group) if $$x{after_response}; |
259
|
0
|
|
|
|
|
0
|
return 1; |
260
|
|
|
|
|
|
|
} |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
=head3 _run_test |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
$tester->_run_test( group hash, test hash ) |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
Run an individual test. Tests (an action) at theis stage should be in one |
267
|
|
|
|
|
|
|
of the following forms: |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
{ sub => sub { ... do stuff }, |
270
|
|
|
|
|
|
|
} |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
{ name => "mechanize method name", |
273
|
|
|
|
|
|
|
args => [ array of method arguments ], |
274
|
|
|
|
|
|
|
} |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
=cut |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
sub _run_test { |
279
|
0
|
|
|
0
|
|
0
|
my ($x, $group, $test) = @_; |
280
|
|
|
|
|
|
|
|
281
|
0
|
0
|
|
|
|
0
|
if ($$test{sub}) { |
282
|
0
|
|
|
|
|
0
|
return $$test{sub}->(); |
283
|
|
|
|
|
|
|
} |
284
|
|
|
|
|
|
|
|
285
|
0
|
|
|
|
|
0
|
my $t = $$test{name}; |
286
|
0
|
|
|
|
|
0
|
$x->mechanize->$t( @{$$test{args}} ); |
|
0
|
|
|
|
|
0
|
|
287
|
|
|
|
|
|
|
} |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
=head3 _load |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
$tester->_load() |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
Open test files (listed in C<@{$$x{load}}>) and attempt to load each |
294
|
|
|
|
|
|
|
contained document. Each testfile is loaded only once. |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
=cut |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
sub _load { |
299
|
2
|
|
|
2
|
|
4
|
my $x = shift; |
300
|
2
|
50
|
33
|
|
|
7
|
return unless HAS($x, 'load') and 'ARRAY' eq ref($$x{load}); |
301
|
|
|
|
|
|
|
|
302
|
2
|
|
|
|
|
4
|
for my $file (@{$$x{load}}) { |
|
2
|
|
|
|
|
7
|
|
303
|
0
|
0
|
|
|
|
0
|
next if $$x{_loaded}{$file}++; |
304
|
|
|
|
|
|
|
|
305
|
0
|
|
|
|
|
0
|
my @docs = eval { $$x{loader}->load( $file ) }; |
|
0
|
|
|
|
|
0
|
|
306
|
0
|
0
|
0
|
|
|
0
|
die "While parsing test file '$file':\n$@" if $@ or !@docs; |
307
|
|
|
|
|
|
|
|
308
|
0
|
|
|
|
|
0
|
my $document = 1; |
309
|
0
|
|
|
|
|
0
|
$x->_load_doc( $_, [$file, $document++] ) for @docs; |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
# local configs last only until end of file |
312
|
0
|
|
|
|
|
0
|
$x->_clear_local_config; |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
} |
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
=head3 _load_doc |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
$tester->_load_doc( any doc, id array ) |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
Determine document type and hand off to appropriate loaders. |
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
=cut |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
sub _load_doc { |
325
|
0
|
|
|
0
|
|
0
|
my ($x, $doc, $id) = @_; |
326
|
|
|
|
|
|
|
|
327
|
0
|
0
|
|
|
|
0
|
if (!ref($doc)) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
328
|
0
|
|
|
|
|
0
|
return 1; |
329
|
|
|
|
|
|
|
} |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
elsif ('HASH' eq ref($doc)) { |
332
|
0
|
|
|
|
|
0
|
$x->_push_local_config($doc); |
333
|
|
|
|
|
|
|
} |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
elsif ('ARRAY' eq ref($doc)) { |
336
|
0
|
|
|
|
|
0
|
my $test = 1; |
337
|
0
|
|
|
|
|
0
|
$x->_load_group($_, [@$id, $test++]) for @$doc; |
338
|
|
|
|
|
|
|
} |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
else { |
341
|
0
|
|
|
|
|
0
|
die "Unknown document type ".ref($doc); |
342
|
|
|
|
|
|
|
} |
343
|
|
|
|
|
|
|
} |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
=head3 _load_group |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
$tester->_load_group( non-canonical group hash, id array ) |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
Actually perform test "loading". As test groups are loaded the they are: |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
* canonicalized: |
352
|
|
|
|
|
|
|
- all tests moved to actions array with one test per entry |
353
|
|
|
|
|
|
|
- url misspelling -> uri |
354
|
|
|
|
|
|
|
- uri -> $$x{base}/uri if necessary |
355
|
|
|
|
|
|
|
* tagged: the test's location in the file is inserted into the test hash |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
=cut |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
our %config_options = map +($_,1), |
360
|
|
|
|
|
|
|
qw/ |
361
|
|
|
|
|
|
|
uri parameters method description SKIP TODO |
362
|
|
|
|
|
|
|
/; |
363
|
|
|
|
|
|
|
our %config_aliases = |
364
|
|
|
|
|
|
|
qw/ |
365
|
|
|
|
|
|
|
url uri |
366
|
|
|
|
|
|
|
parms parameters |
367
|
|
|
|
|
|
|
params parameters |
368
|
|
|
|
|
|
|
/; |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
# mech methods |
371
|
|
|
|
|
|
|
our %scalar_tests = map +($_,1), |
372
|
|
|
|
|
|
|
qw/ |
373
|
|
|
|
|
|
|
title_is title_like title_unlike |
374
|
|
|
|
|
|
|
base_is base_like base_unlike |
375
|
|
|
|
|
|
|
content_is content_contains content_lacks content_like content_unlike |
376
|
|
|
|
|
|
|
page_links_content_like page_links_content_unlike |
377
|
|
|
|
|
|
|
links_ok click_ok |
378
|
|
|
|
|
|
|
/; |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
# values are mech methods |
381
|
|
|
|
|
|
|
our %aliases = |
382
|
|
|
|
|
|
|
qw/ |
383
|
|
|
|
|
|
|
is content_is |
384
|
|
|
|
|
|
|
contains content_contains |
385
|
|
|
|
|
|
|
lacks content_lacks |
386
|
|
|
|
|
|
|
like content_like |
387
|
|
|
|
|
|
|
unlike content_unlike |
388
|
|
|
|
|
|
|
/; |
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
# mech methods |
391
|
|
|
|
|
|
|
our %bool_tests = map +($_,1), qw/ page_links_ok html_lint_ok /; |
392
|
|
|
|
|
|
|
our %kv_tests = map +($_,1), |
393
|
|
|
|
|
|
|
qw/ |
394
|
|
|
|
|
|
|
has_tag has_tag_like |
395
|
|
|
|
|
|
|
link_status_is link_status_isnt |
396
|
|
|
|
|
|
|
link_content_like link_content_unlike |
397
|
|
|
|
|
|
|
/; |
398
|
|
|
|
|
|
|
our %hash_tests = map +($_,1), qw/ submit_form_ok follow_link_ok /; |
399
|
|
|
|
|
|
|
our %mech_action = map +($_,1), |
400
|
|
|
|
|
|
|
qw/ |
401
|
|
|
|
|
|
|
get put reload back follow_link form_number form_name |
402
|
|
|
|
|
|
|
form_with_fields field select set_fields set_visible tick untick |
403
|
|
|
|
|
|
|
click click_button submit submit_form add_header delete_header |
404
|
|
|
|
|
|
|
save_content dump_links dump_images dump_forms dump_all redirect_ok |
405
|
|
|
|
|
|
|
request credentials stuff_inputs |
406
|
|
|
|
|
|
|
/; |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
sub _load_group { |
409
|
0
|
|
|
0
|
|
0
|
my ($x, $group, $id) = @_; |
410
|
0
|
|
|
|
|
0
|
$x->_apply_local_config( $group ); |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
# We're all about convenience here, For example, I want to be able to |
413
|
|
|
|
|
|
|
# perform simple contains tests without setting up an "_actions" sequence. |
414
|
|
|
|
|
|
|
# To do that, we need to munge the group hash a bit. |
415
|
0
|
|
|
|
|
0
|
my @keys = keys %$group; |
416
|
0
|
|
|
|
|
0
|
my @actions; |
417
|
0
|
|
|
|
|
0
|
for (@keys) { |
418
|
|
|
|
|
|
|
# the actual "actions" element, pushed to end of actions array so it |
419
|
|
|
|
|
|
|
# happens after the toplevel actions. |
420
|
0
|
0
|
0
|
|
|
0
|
if ($_ eq 'actions') { |
|
|
0
|
0
|
|
|
|
|
|
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
421
|
0
|
|
|
|
|
0
|
for (@{delete $$group{actions}}) { |
|
0
|
|
|
|
|
0
|
|
422
|
0
|
|
|
|
|
0
|
while (my ($k, $v) = each %$_) { |
423
|
0
|
|
|
|
|
0
|
push @actions, { name => $k, args => $v }; |
424
|
|
|
|
|
|
|
} |
425
|
|
|
|
|
|
|
} |
426
|
|
|
|
|
|
|
} |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
# leave internal configuration options where they are |
429
|
|
|
|
|
|
|
elsif (TRUE( \%config_options, $_ ) |
430
|
|
|
|
|
|
|
or TRUE( \%config_aliases, $_ ) |
431
|
|
|
|
|
|
|
) { |
432
|
0
|
0
|
0
|
|
|
0
|
$$group{$config_aliases{$_}} = $$group{$_} if TRUE( \%config_aliases, $_ ) and !HAS( $group, $config_aliases{$_} ); |
433
|
0
|
|
|
|
|
0
|
next; |
434
|
|
|
|
|
|
|
} |
435
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
# Put anything that looks like a test action on the front of the action |
437
|
|
|
|
|
|
|
# list (again, so that explicit action sequences occur after transplanted |
438
|
|
|
|
|
|
|
# initial load actions). |
439
|
|
|
|
|
|
|
elsif (TRUE( \%scalar_tests, $_ ) |
440
|
|
|
|
|
|
|
or TRUE( \%bool_tests, $_ ) |
441
|
|
|
|
|
|
|
or TRUE( \%kv_tests, $_ ) |
442
|
|
|
|
|
|
|
or TRUE( \%hash_tests, $_ ) |
443
|
|
|
|
|
|
|
or TRUE( \%mech_action, $_ ) |
444
|
|
|
|
|
|
|
or TRUE( \%aliases, $_ ) |
445
|
|
|
|
|
|
|
or $x->mechanize->can($_) |
446
|
0
|
|
|
|
|
0
|
) { unshift @actions, { name => $_, args => $$group{$_}, _transplant => 1 } } |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
# anything else is considered a custom config value and will be |
449
|
|
|
|
|
|
|
# preserved in the top level group hash. |
450
|
|
|
|
|
|
|
} |
451
|
|
|
|
|
|
|
|
452
|
0
|
0
|
|
|
|
0
|
$$group{uri} = URI->new_abs($$group{uri}, $$x{base})->as_string if $$x{base}; |
453
|
|
|
|
|
|
|
|
454
|
0
|
|
|
|
|
0
|
$$group{_id} = $id; |
455
|
0
|
|
|
|
|
0
|
$$group{_actions} = $x->_prepare_actions( $group, \@actions, $id ); |
456
|
0
|
|
|
|
|
0
|
push @{$$x{groups}}, $group; |
|
0
|
|
|
|
|
0
|
|
457
|
|
|
|
|
|
|
} |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
=head3 _prepare_actions |
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
canon-test (actions) array = $x->_prepare_actions( canon-group hash, actions array, group id array ) |
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
Prepare array of actions by: |
464
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
* expanding aliases |
466
|
|
|
|
|
|
|
* expanding tests |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
=cut |
469
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
sub _prepare_actions { |
471
|
0
|
|
|
0
|
|
0
|
my ($x, $group, $actions, $id) = @_; |
472
|
0
|
|
|
|
|
0
|
my @expanded; |
473
|
|
|
|
|
|
|
|
474
|
0
|
|
|
|
|
0
|
my $action = 1; |
475
|
0
|
|
|
|
|
0
|
for my $a (@$actions) { |
476
|
0
|
0
|
|
|
|
0
|
$$a{name} = $aliases{$$a{name}} if HAS( \%aliases, $$a{name} ); |
477
|
|
|
|
|
|
|
|
478
|
0
|
|
|
|
|
0
|
push @expanded, $x->_expand_tests($group, $a, [@$id, $action++]) |
479
|
|
|
|
|
|
|
} |
480
|
|
|
|
|
|
|
|
481
|
0
|
|
|
|
|
0
|
return \@expanded; |
482
|
|
|
|
|
|
|
} |
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
=head3 _expand_tests |
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
list of canon-tests = $tester->_expand_tests( canon-group hash, non-canon action item, id array ) |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
Expand a logical action item into possibly many explicit test items. When |
490
|
|
|
|
|
|
|
executed, each test item will increment the test count be exactly 1. |
491
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
* prepares argument list |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
=cut |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
sub _expand_tests { |
497
|
0
|
|
|
0
|
|
0
|
my ($x, $group, $action, $id) = @_; |
498
|
0
|
|
|
|
|
0
|
my $name = $$action{name}; |
499
|
0
|
|
|
|
|
0
|
my $args = $$action{args}; |
500
|
0
|
|
|
|
|
0
|
my $test = 'a'; |
501
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
# SCALAR TESTS |
503
|
0
|
0
|
|
|
|
0
|
if (TRUE( \%scalar_tests, $name )) { |
504
|
0
|
0
|
|
|
|
0
|
return map |
|
|
0
|
|
|
|
|
|
505
|
|
|
|
|
|
|
+{ %$action, args => [(($name =~ /_like$/) ? qr/$_/ : $_), $x->_test_label($group, $name, @$id, $test)], id => [@$id, $test++] }, |
506
|
|
|
|
|
|
|
('ARRAY' eq ref($args)) ? @$args : $args; |
507
|
|
|
|
|
|
|
} |
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
# KV TESTS |
510
|
0
|
0
|
|
|
|
0
|
if (TRUE( \%kv_tests, $name )) { |
511
|
0
|
|
|
|
|
0
|
my @tests; |
512
|
0
|
|
|
|
|
0
|
while (my ($k, $v) = each %$args) { |
513
|
0
|
0
|
|
|
|
0
|
push @tests, |
514
|
|
|
|
|
|
|
{ %$action, id => [@$id, $test++], |
515
|
|
|
|
|
|
|
args => [$k, (($name =~ /(?:_|_un)like$/) ? qr/$v/ : $v), |
516
|
|
|
|
|
|
|
$x->_test_label($group, $name, @$id, $test)], |
517
|
|
|
|
|
|
|
}; |
518
|
|
|
|
|
|
|
} |
519
|
0
|
|
|
|
|
0
|
return @tests; |
520
|
|
|
|
|
|
|
} |
521
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
# HASH TESTS |
523
|
0
|
0
|
|
|
|
0
|
if (TRUE( \%hash_tests, $name )) { |
524
|
0
|
|
|
|
|
0
|
my @tests; |
525
|
0
|
|
|
|
|
0
|
$$action{id} = [@$id, $test++]; |
526
|
0
|
|
|
|
|
0
|
$$action{args} = [$$action{args}, $x->_test_label($group, $name, @{$$action{id}})]; |
|
0
|
|
|
|
|
0
|
|
527
|
0
|
|
|
|
|
0
|
push @tests, $action; |
528
|
0
|
|
|
|
|
0
|
return @tests; |
529
|
|
|
|
|
|
|
} |
530
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
# BOOLEAN TESTS |
532
|
0
|
0
|
|
|
|
0
|
if (TRUE( \%bool_tests, $name )) { |
533
|
0
|
|
|
|
|
0
|
$$action{id} = $id; |
534
|
0
|
|
|
|
|
0
|
$$action{args} = [ $x->_test_label($group, $name, @$id) ]; |
535
|
0
|
|
|
|
|
0
|
return $action; |
536
|
|
|
|
|
|
|
} |
537
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
# MECHANIZE ACTIONS |
539
|
0
|
0
|
|
|
|
0
|
if (TRUE( \%mech_action, $name )) { |
540
|
0
|
|
|
|
|
0
|
$$action{id} = $id; |
541
|
|
|
|
|
|
|
$$action{sub} = sub { |
542
|
0
|
|
|
0
|
|
0
|
my $res = eval { |
543
|
0
|
0
|
|
|
|
0
|
$x->mechanize->$name( ('ARRAY' eq ref($args)) ? @$args |
|
|
0
|
|
|
|
|
|
544
|
|
|
|
|
|
|
: ('HASH' eq ref($args)) ? %$args |
545
|
|
|
|
|
|
|
: $args |
546
|
|
|
|
|
|
|
); |
547
|
0
|
|
|
|
|
0
|
1; |
548
|
|
|
|
|
|
|
}; |
549
|
|
|
|
|
|
|
# plain mechanize actions don't report "ok". Force a test based on |
550
|
|
|
|
|
|
|
# just evaluation fatality since we take an action spot. |
551
|
0
|
|
|
|
|
0
|
local $Test::Builder::Level = $Test::Builder::Level + 1; |
552
|
0
|
0
|
|
|
|
0
|
$Test->diag( "$name: $@" ) if $@; |
553
|
0
|
|
0
|
|
|
0
|
$Test->ok(($res and !$@), "$name mechanize action"); |
554
|
0
|
|
|
|
|
0
|
}; |
555
|
0
|
|
|
|
|
0
|
return $action; |
556
|
|
|
|
|
|
|
} |
557
|
|
|
|
|
|
|
|
558
|
0
|
|
|
|
|
0
|
die "Invalid action: '$name'"; |
559
|
|
|
|
|
|
|
} |
560
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
=head3 _test_label |
562
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
label = $tester->_test_label( group, name, id list ) |
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
Convert id components into something human-readable. For example: |
566
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
"[description] content_contains: file basic.yml, doc 3, group 5, test 2.b" |
568
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
=cut |
570
|
|
|
|
|
|
|
|
571
|
|
|
|
|
|
|
sub _test_label { |
572
|
0
|
|
|
0
|
|
0
|
my ($x, $group, $name, $file, $doc, $group_no, @id) = @_; |
573
|
0
|
|
|
|
|
0
|
local $" = '.'; |
574
|
|
|
|
|
|
|
|
575
|
0
|
|
|
|
|
0
|
my $desc = ""; |
576
|
0
|
0
|
|
|
|
0
|
$desc = "[$$group{description}] " if defined($$group{description}); |
577
|
|
|
|
|
|
|
|
578
|
0
|
|
|
|
|
0
|
my $test = ""; |
579
|
0
|
0
|
|
|
|
0
|
$test = ", test @id" if @id; |
580
|
|
|
|
|
|
|
|
581
|
0
|
|
|
|
|
0
|
"$desc$name: file $file, doc $doc, group $group_no$test" |
582
|
|
|
|
|
|
|
} |
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
=head3 _tests_in_group |
585
|
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
$tester->_tests_in_group($group) |
587
|
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
Calculates number of tests attributable to the given group. Accounts for |
589
|
|
|
|
|
|
|
initial requerst, explicit actions, and tests in any callbacks. |
590
|
|
|
|
|
|
|
|
591
|
|
|
|
|
|
|
=cut |
592
|
|
|
|
|
|
|
|
593
|
|
|
|
|
|
|
sub _tests_in_group { |
594
|
0
|
|
|
0
|
|
0
|
my ($x, $group) = @_; |
595
|
0
|
|
|
|
|
0
|
my $tests = 0; |
596
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
# 1 test for the initial request |
598
|
0
|
|
|
|
|
0
|
$tests += 1; |
599
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
# tests performed in callbacks |
601
|
0
|
|
|
|
|
0
|
for (qw/after_response_tests before_request_tests/) { |
602
|
0
|
|
0
|
|
|
0
|
$tests += $$x{$_} || 0; |
603
|
|
|
|
|
|
|
} |
604
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
# 1 test for each action in the group |
606
|
0
|
|
|
|
|
0
|
$tests += 0+@{$$group{_actions}}; |
|
0
|
|
|
|
|
0
|
|
607
|
|
|
|
|
|
|
|
608
|
0
|
|
|
|
|
0
|
return $tests; |
609
|
|
|
|
|
|
|
} |
610
|
|
|
|
|
|
|
|
611
|
|
|
|
|
|
|
=head3 _autoload |
612
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
$tester->_autoload() |
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
Attempt to load test files based on current script name. removes .t or .pl |
616
|
|
|
|
|
|
|
from C<$0> and globs C |
617
|
|
|
|
|
|
|
|
618
|
|
|
|
|
|
|
=cut |
619
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
sub _autoload { |
621
|
1
|
|
|
1
|
|
2
|
my $x = shift; |
622
|
1
|
|
|
|
|
4
|
my $glob = $0; |
623
|
1
|
|
|
|
|
8
|
$glob =~ s/\.(?:t|pl)$//; |
624
|
1
|
|
|
|
|
237
|
my @autoload = grep +(-r $_), glob("$glob*.{yaml,yml,dat}"); |
625
|
1
|
|
|
|
|
5
|
$x->load( @autoload ); |
626
|
|
|
|
|
|
|
} |
627
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
=head3 _clear_local_config |
629
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
$tester->_clear_local_config() |
631
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
Configs local to a series of test documents should be cleared after each |
633
|
|
|
|
|
|
|
file is loaded. |
634
|
|
|
|
|
|
|
|
635
|
|
|
|
|
|
|
=cut |
636
|
|
|
|
|
|
|
|
637
|
|
|
|
|
|
|
sub _clear_local_config { |
638
|
0
|
|
|
0
|
|
|
my $x = shift; |
639
|
0
|
|
|
|
|
|
$$x{_local_config} = {}; |
640
|
|
|
|
|
|
|
} |
641
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
=head3 _push_local_config |
643
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
Merge a new configuration into the local configuration. called for each |
645
|
|
|
|
|
|
|
hash document in a test configuration file. |
646
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
=cut |
648
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
sub _push_local_config { |
650
|
0
|
|
|
0
|
|
|
my ($x, $config) = @_; |
651
|
0
|
|
0
|
|
|
|
$$x{_local_config} ||= {}; |
652
|
0
|
|
|
|
|
|
%{$$x{_local_config}} = (%{$$x{_local_config}}, %$config); |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
} |
654
|
|
|
|
|
|
|
|
655
|
|
|
|
|
|
|
=head3 _apply_local_config |
656
|
|
|
|
|
|
|
|
657
|
|
|
|
|
|
|
Merge a new configuration into the local configuration. called for each |
658
|
|
|
|
|
|
|
hash document in a test configuration file. |
659
|
|
|
|
|
|
|
|
660
|
|
|
|
|
|
|
=cut |
661
|
|
|
|
|
|
|
|
662
|
|
|
|
|
|
|
sub _apply_local_config { |
663
|
0
|
|
|
0
|
|
|
my ($x, $group) = @_; |
664
|
0
|
|
0
|
|
|
|
$$x{_local_config} ||= {}; |
665
|
0
|
|
|
|
|
|
%$group = (%{dclone($$x{_local_config})}, %$group); |
|
0
|
|
|
|
|
|
|
666
|
|
|
|
|
|
|
} |
667
|
|
|
|
|
|
|
|
668
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
|
670
|
|
|
|
|
|
|
|
671
|
|
|
|
|
|
|
1; |
672
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
=head1 TODO |
674
|
|
|
|
|
|
|
|
675
|
|
|
|
|
|
|
=over 4 |
676
|
|
|
|
|
|
|
|
677
|
|
|
|
|
|
|
=item test and perhaps implement proper enctype="multipart/form-data" file uploads |
678
|
|
|
|
|
|
|
|
679
|
|
|
|
|
|
|
=item HEAD, PUT, DELETE requests |
680
|
|
|
|
|
|
|
|
681
|
|
|
|
|
|
|
=item Custom Request headers (probably as a "headers" top level hash item so avoid using that as a custom field) |
682
|
|
|
|
|
|
|
|
683
|
|
|
|
|
|
|
=back |
684
|
|
|
|
|
|
|
|
685
|
|
|
|
|
|
|
=cut |
686
|
|
|
|
|
|
|
|
687
|
|
|
|
|
|
|
=head1 AUTHOR |
688
|
|
|
|
|
|
|
|
689
|
|
|
|
|
|
|
The original version of this code written by Dean Serenevy while under |
690
|
|
|
|
|
|
|
contract with National Financial Management who graciously allowed me to |
691
|
|
|
|
|
|
|
release it to the public. |
692
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
Dean Serenevy |
694
|
|
|
|
|
|
|
dean@serenevy.net |
695
|
|
|
|
|
|
|
http://dean.serenevy.net/ |
696
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
=head1 LICENSE |
698
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
This software is hereby placed into the public domain. If you use this |
700
|
|
|
|
|
|
|
code, a simple comment in your code giving credit and an email letting |
701
|
|
|
|
|
|
|
me know that you find it useful would be courteous but is not required. |
702
|
|
|
|
|
|
|
|
703
|
|
|
|
|
|
|
The software is provided "as is" without warranty of any kind, either |
704
|
|
|
|
|
|
|
expressed or implied including, but not limited to, the implied warranties |
705
|
|
|
|
|
|
|
of merchantability and fitness for a particular purpose. In no event shall |
706
|
|
|
|
|
|
|
the authors or copyright holders be liable for any claim, damages or other |
707
|
|
|
|
|
|
|
liability, whether in an action of contract, tort or otherwise, arising |
708
|
|
|
|
|
|
|
from, out of or in connection with the software or the use or other |
709
|
|
|
|
|
|
|
dealings in the software. |
710
|
|
|
|
|
|
|
|
711
|
|
|
|
|
|
|
=head1 SEE ALSO |
712
|
|
|
|
|
|
|
|
713
|
|
|
|
|
|
|
L, L |
714
|
|
|
|
|
|
|
|
715
|
|
|
|
|
|
|
=cut |