line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Test::LectroTest::Property; |
2
|
|
|
|
|
|
|
{ |
3
|
|
|
|
|
|
|
$Test::LectroTest::Property::VERSION = '0.5001'; |
4
|
|
|
|
|
|
|
} |
5
|
|
|
|
|
|
|
|
6
|
5
|
|
|
5
|
|
1979
|
use strict; |
|
5
|
|
|
|
|
9
|
|
|
5
|
|
|
|
|
275
|
|
7
|
5
|
|
|
5
|
|
33
|
use warnings; |
|
5
|
|
|
|
|
28
|
|
|
5
|
|
|
|
|
173
|
|
8
|
|
|
|
|
|
|
|
9
|
5
|
|
|
5
|
|
29
|
use Carp; |
|
5
|
|
|
|
|
7
|
|
|
5
|
|
|
|
|
322
|
|
10
|
5
|
|
|
5
|
|
4146
|
use Filter::Util::Call; |
|
5
|
|
|
|
|
4109
|
|
|
5
|
|
|
|
|
361
|
|
11
|
|
|
|
|
|
|
|
12
|
5
|
|
|
5
|
|
29
|
use constant NO_FILTER => 'NO_FILTER'; |
|
5
|
|
|
|
|
7
|
|
|
5
|
|
|
|
|
474
|
|
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
=head1 NAME |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
Test::LectroTest::Property - Properties that make testable claims about your software |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
=head1 VERSION |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
version 0.5001 |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
=head1 SYNOPSIS |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
use MyModule; # provides my_function_to_test |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
use Test::LectroTest::Generator qw( :common ); |
27
|
|
|
|
|
|
|
use Test::LectroTest::Property qw( Test ); |
28
|
|
|
|
|
|
|
use Test::LectroTest::TestRunner; |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
my $prop_non_neg = Property { |
31
|
|
|
|
|
|
|
##[ x <- Int, y <- Int ]## |
32
|
|
|
|
|
|
|
$tcon->label("negative") if $x < 0; |
33
|
|
|
|
|
|
|
$tcon->label("odd") if $x % 2; |
34
|
|
|
|
|
|
|
$tcon->retry if $y == 0; # 0 can't be used in test |
35
|
|
|
|
|
|
|
my_function_to_test( $x, $y ) >= 0; |
36
|
|
|
|
|
|
|
}, name => "my_function_to_test output is non-negative"; |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
my $runner = Test::LectroTest::TestRunner->new(); |
39
|
|
|
|
|
|
|
$runner->run_suite( |
40
|
|
|
|
|
|
|
$prop_non_neg, |
41
|
|
|
|
|
|
|
# ... more properties here ... |
42
|
|
|
|
|
|
|
); |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
=head1 DESCRIPTION |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
B If you're just looking for an easy way to write and run unit |
47
|
|
|
|
|
|
|
tests, see L first. Once you're comfortable with |
48
|
|
|
|
|
|
|
what is presented there and ready to delve into the full offerings of |
49
|
|
|
|
|
|
|
properties, this is the document for you. |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
This module allows you to define Properties that can be checked |
52
|
|
|
|
|
|
|
automatically by L. A Property is a specification |
53
|
|
|
|
|
|
|
of your software's required behavior over a given set of conditions. |
54
|
|
|
|
|
|
|
The set of conditions is given by a generator-binding |
55
|
|
|
|
|
|
|
specification. The required behavior is defined implicitly by a block |
56
|
|
|
|
|
|
|
of code that tests your software for a given set of generated |
57
|
|
|
|
|
|
|
conditions; if your software matches the expected behavor, the |
58
|
|
|
|
|
|
|
block of code returns true; otherwise, false. |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
This documentation serves as reference documentation for LectroTest |
61
|
|
|
|
|
|
|
Properties. If you don't understand the basics of Properties yet, |
62
|
|
|
|
|
|
|
see L before continuing. |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
=cut |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
BEGIN { |
67
|
5
|
|
|
5
|
|
25
|
use Exporter ( ); |
|
5
|
|
|
|
|
9
|
|
|
5
|
|
|
|
|
358
|
|
68
|
5
|
|
|
5
|
|
73
|
our @ISA = qw( Exporter ); |
69
|
5
|
|
|
|
|
31
|
our @EXPORT = qw( &Property ); |
70
|
5
|
|
|
|
|
8
|
our @EXPORT_OK = qw( &Property ); |
71
|
5
|
|
|
|
|
6807
|
our %EXPORT_TAGS = ( ); |
72
|
|
|
|
|
|
|
} |
73
|
|
|
|
|
|
|
our @EXPORT_OK; |
74
|
|
|
|
|
|
|
our @CARP_NOT = qw ( Test::LectroTest::TestRunner ); |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
my %defaults = ( name => 'Unnamed Test::LectroTest::Property' ); |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
=pod |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
=head2 Two ways to create Properties |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
There are two ways to create a property: |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
=over 4 |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
=item 1 |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
Use the C function to promote a block of code that contains |
90
|
|
|
|
|
|
|
both a generator-binding specification and a behavior test into a |
91
|
|
|
|
|
|
|
Test::LectroTest::Property object. B |
92
|
|
|
|
|
|
|
Example: |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
my $prop1 = Property { |
95
|
|
|
|
|
|
|
##[ x <- Int ]## |
96
|
|
|
|
|
|
|
thing_to_test($x) >= 0; |
97
|
|
|
|
|
|
|
}, name => "thing_to_test is non-negative"; |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
=cut |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
sub Property(&&@) { |
103
|
52
|
|
|
52
|
0
|
14943
|
my ($genspec_fn, $test_fn, @args) = @_; |
104
|
52
|
|
|
|
|
151
|
return Test::LectroTest::Property->new( |
105
|
|
|
|
|
|
|
inputs => $genspec_fn->(), |
106
|
|
|
|
|
|
|
test => $test_fn, |
107
|
|
|
|
|
|
|
@args |
108
|
|
|
|
|
|
|
); |
109
|
|
|
|
|
|
|
} |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
=pod |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
=item 2 |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
Use the C method of Test::LectroTest::Property and provide |
116
|
|
|
|
|
|
|
it with the necessary ingredients via named parameters: |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
my $prop2 = Test::LectroTest::Property->new( |
119
|
|
|
|
|
|
|
inputs => [ x => Int ], |
120
|
|
|
|
|
|
|
test => sub { my ($tcon,$x) = @_; |
121
|
|
|
|
|
|
|
thing_to_test($x) >= 0 }, |
122
|
|
|
|
|
|
|
name => "thing_to_test is non-negative" |
123
|
|
|
|
|
|
|
); |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
=back |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
=cut |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
my $pkg = __PACKAGE__; |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
sub new { |
132
|
59
|
|
|
59
|
0
|
3446
|
my $class = shift; |
133
|
59
|
100
|
|
|
|
401
|
croak "$pkg: invalid list of named parameters: (@_)" |
134
|
|
|
|
|
|
|
if @_ % 2; |
135
|
58
|
|
|
|
|
201
|
my %args = @_; |
136
|
58
|
100
|
|
|
|
710
|
croak "$pkg: test subroutine must be provided" |
137
|
|
|
|
|
|
|
if ref($args{test}) ne 'CODE'; |
138
|
55
|
100
|
|
|
|
335
|
croak "$pkg: did not get a set of valid input-generator bindings" |
139
|
|
|
|
|
|
|
if ref($args{inputs}) ne "ARRAY"; |
140
|
54
|
100
|
|
|
|
156
|
$args{inputs} = [$args{inputs}] unless ref $args{inputs}[0]; |
141
|
54
|
|
|
|
|
84
|
my $inputs_list = []; |
142
|
54
|
|
|
|
|
112
|
my $last_vars; |
143
|
54
|
|
|
|
|
74
|
for my $inputs (@{$args{inputs}}) { |
|
54
|
|
|
|
|
135
|
|
144
|
62
|
100
|
100
|
|
|
763
|
croak "$pkg: did not get a set of valid input-generator bindings" |
145
|
|
|
|
|
|
|
if ref($inputs) ne "ARRAY" || @$inputs % 2; |
146
|
60
|
|
|
|
|
152
|
$inputs = { @$inputs }; |
147
|
60
|
100
|
|
|
|
617
|
croak "$pkg: cannot use reserved name 'tcon' in a generator binding" |
148
|
|
|
|
|
|
|
if defined $inputs->{tcon}; |
149
|
58
|
|
|
|
|
199
|
my @vars = sort keys %$inputs; |
150
|
58
|
100
|
100
|
|
|
695
|
croak "$pkg: each set of generator bindings must bind the same " |
151
|
|
|
|
|
|
|
. "set of variables but (@vars) does not match ($last_vars)" |
152
|
|
|
|
|
|
|
if $last_vars && $last_vars ne "@vars"; |
153
|
55
|
|
|
|
|
161
|
$last_vars = "@vars"; |
154
|
55
|
|
|
|
|
157
|
push @$inputs_list, $inputs; |
155
|
|
|
|
|
|
|
} |
156
|
47
|
|
|
|
|
99
|
delete $args{inputs}; |
157
|
47
|
|
|
|
|
483
|
return bless { %defaults, inputs => $inputs_list, %args }, $class; |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
=pod |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
Both are equivalent, but the first is concise, easier to read, and |
164
|
|
|
|
|
|
|
lets LectroTest do some of the heavy lifting for you. The second is |
165
|
|
|
|
|
|
|
probably better, however, if you are constructing property |
166
|
|
|
|
|
|
|
specifications programmatically. |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
=head2 Generator-binding specification |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
The generator-binding specification declares that certain variables |
171
|
|
|
|
|
|
|
are to be bound to certain kinds of random-value generators during |
172
|
|
|
|
|
|
|
the tests of your software's behavior. The number and kind of |
173
|
|
|
|
|
|
|
generators define the "condition space" that is examined during |
174
|
|
|
|
|
|
|
property checks. |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
If you use the C function to create your properties, your |
177
|
|
|
|
|
|
|
generator-binding specification must come first in your code block, |
178
|
|
|
|
|
|
|
and you must use the following syntax: |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
##[ var1 <- gen1, var2 <- gen2, ... ]## |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
Comments are not allowed within the specification, but you may |
183
|
|
|
|
|
|
|
break it across multiple lines: |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
##[ var1 <- gen1, |
186
|
|
|
|
|
|
|
var2 <- gen2, ... |
187
|
|
|
|
|
|
|
]## |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
or |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
##[ |
192
|
|
|
|
|
|
|
var1 <- gen1, |
193
|
|
|
|
|
|
|
var2 <- gen2, ... |
194
|
|
|
|
|
|
|
]## |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
Further, for better integration with syntax-highlighting IDEs, |
197
|
|
|
|
|
|
|
the terminating C<]##> delimiter may be preceded by a hash |
198
|
|
|
|
|
|
|
symbol C<#> and optional whitespace to make it appear like |
199
|
|
|
|
|
|
|
a comment: |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
##[ |
202
|
|
|
|
|
|
|
var1 <- gen1, |
203
|
|
|
|
|
|
|
var2 <- gen2, ... |
204
|
|
|
|
|
|
|
# ]## |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
On the other hand, if you use Cnew()> |
207
|
|
|
|
|
|
|
to create your objects, the generator-binding specification takes the |
208
|
|
|
|
|
|
|
form of an array reference containing variable-generator pairs that is |
209
|
|
|
|
|
|
|
passed to C via the parameter named C: |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
inputs => [ var1 => gen1, var2 => gen2, ... ] |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
Normal Perl syntax applies here. |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
=head2 Specifying multiple sets of generator bindings |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
Sometimes you may want to repeat a property check with multiple sets |
219
|
|
|
|
|
|
|
of generator bindings. This can happen, for instance, when your |
220
|
|
|
|
|
|
|
condition space is vast and you want to ensure that a particular |
221
|
|
|
|
|
|
|
portion of it receives focused coverage while still sampling the |
222
|
|
|
|
|
|
|
overall space. For times like this, you can list multiple |
223
|
|
|
|
|
|
|
sets of bindings within the C<##[> and C<]##> delimiters, like so: |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
##[ var1 <- gen1A, ... ], |
226
|
|
|
|
|
|
|
[ var1 <- gen1B, ... ], |
227
|
|
|
|
|
|
|
... more sets of bindings ... |
228
|
|
|
|
|
|
|
[ var1 <- gen1N, ... ]## |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
Note that only the first and last set need the special delimiters. |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
The equivalent when using C is as follows: |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
inputs => [ [ var1 => gen1A, ... ], |
235
|
|
|
|
|
|
|
[ var1 => gen1B, ... ], |
236
|
|
|
|
|
|
|
... |
237
|
|
|
|
|
|
|
[ var1 => gen1N, ... ] ] |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
Regardless of how you declare the sets of bindings, each set must |
240
|
|
|
|
|
|
|
provide bindings for the exact same set of variables. (The |
241
|
|
|
|
|
|
|
generators, of course, can be different.) For example, this kind of |
242
|
|
|
|
|
|
|
thing is illegal: |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
##[ x <- Int ], [ y <- Int ]## |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
The above is illegal because both sets of bindings must use I or |
247
|
|
|
|
|
|
|
both must use I; they can't each use a different variable. |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
##[ x <- Int ], |
250
|
|
|
|
|
|
|
[ x <- Int, y <- Float ]## |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
The above is illegal because the second set has an extra variable that |
253
|
|
|
|
|
|
|
isn't present in the first. Both sets must use exactly the same |
254
|
|
|
|
|
|
|
variables. None of the variables may be extra, none may be missing, |
255
|
|
|
|
|
|
|
and all must be named identically across the sets of bindings. |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
=head2 Behavior test |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
The behavior test is a subroutine that accepts a test-controller |
262
|
|
|
|
|
|
|
object and a given set of input conditions, tests your software's |
263
|
|
|
|
|
|
|
observed behavior against the required behavior with respect to the |
264
|
|
|
|
|
|
|
input conditions, and returns true or false to indicate acceptance or |
265
|
|
|
|
|
|
|
rejection. If you are using the C function to create your |
266
|
|
|
|
|
|
|
property objects, lexically bound variables are created and loaded |
267
|
|
|
|
|
|
|
with values automatically, per your input-generator specification, so |
268
|
|
|
|
|
|
|
you can just go ahead and use the variables immediately: |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
my $prop = Property { |
271
|
|
|
|
|
|
|
##[ i <- Int, delta <- Float(range=>[0,1]) ]## |
272
|
|
|
|
|
|
|
my $lo_val = my_thing_to_test($i); |
273
|
|
|
|
|
|
|
my $hi_val = my_thing_to_test($i + $delta); |
274
|
|
|
|
|
|
|
$lo_val == $hi_val; |
275
|
|
|
|
|
|
|
}, name => "my_thing_to_test ignores fractions" ; |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
On the other hand, if you are using |
278
|
|
|
|
|
|
|
Cnew()>, you must declare and |
279
|
|
|
|
|
|
|
initialize these variables manually from Perl's C<@_> variable I
|
280
|
|
|
|
|
|
|
lexicographically increasing order> after receiving C<$tcon>, the test |
281
|
|
|
|
|
|
|
controller object. (This inconvenience, by the way, is why the former |
282
|
|
|
|
|
|
|
method is preferred.) The hard way: |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
my $prop = Test::LectroTest::Property->new( |
285
|
|
|
|
|
|
|
inputs => [ i => Int, delta => Float(range=>[0,1]) ], |
286
|
|
|
|
|
|
|
test => sub { |
287
|
|
|
|
|
|
|
my ($tcon, $delta, $i) = @_; |
288
|
|
|
|
|
|
|
my $lo_val = my_thing_to_test($i); |
289
|
|
|
|
|
|
|
my $hi_val = my_thing_to_test($i + $delta); |
290
|
|
|
|
|
|
|
$lo_val == $hi_val |
291
|
|
|
|
|
|
|
}, |
292
|
|
|
|
|
|
|
name => "my_thing_to_test ignores fractions" |
293
|
|
|
|
|
|
|
) ; |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
=head2 Control logic, retries, and labeling |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
Inside the behavior test, you have access to a special variable |
299
|
|
|
|
|
|
|
C<$tcon> that allows you to interact with the test controller. |
300
|
|
|
|
|
|
|
Through C<$tcon> you can do the following: |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
=over 4 |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
=item * |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
retry the current trial with different inputs (if you don't like the |
307
|
|
|
|
|
|
|
inputs you were given at first) |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
=item * |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
add labels to the current trial for reporting purposes |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
=item * |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
attach notes and variable dumps to the current trial for diagnostic |
316
|
|
|
|
|
|
|
purposes, should the trial fail |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
=back |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
(For the full details of what you can do with C<$tcon> see |
321
|
|
|
|
|
|
|
the "testcontroller" section of L.) |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
For example, let's say that we have written a function C that |
324
|
|
|
|
|
|
|
returns the square root of its input. In order to check whether our |
325
|
|
|
|
|
|
|
implementation fulfills the mathematical definition of square root, we |
326
|
|
|
|
|
|
|
might specify the following property: |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
my $epsilon = 0.000_001; |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
Property { |
331
|
|
|
|
|
|
|
##[ x <- Float ]## |
332
|
|
|
|
|
|
|
return $tcon->retry if $x < 0; |
333
|
|
|
|
|
|
|
$tcon->label("less than one") if $x < 1; |
334
|
|
|
|
|
|
|
my $sx = my_sqrt( $x ); |
335
|
|
|
|
|
|
|
abs($sx * $sx - $x) < $epsilon; |
336
|
|
|
|
|
|
|
}, name => "my_sqrt satisfies defn of square root"; |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
Because we don't want to deal with imaginary numbers, our square-root |
339
|
|
|
|
|
|
|
function is defined only over non-negative numbers. To make sure |
340
|
|
|
|
|
|
|
we don't accidentally check our property "at" a negative number, we |
341
|
|
|
|
|
|
|
use the following line to re-start the trial with a different |
342
|
|
|
|
|
|
|
input should the input we are given at first be negative: |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
return $tcon->retry if $x < 0; |
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
An interesting fact is that for all values I between zero and one, |
347
|
|
|
|
|
|
|
the square root of I is larger than I itself. Perhaps our |
348
|
|
|
|
|
|
|
implementation treats such values as a special case. In order to be |
349
|
|
|
|
|
|
|
confident that we are checking this case, we added the following line: |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
$tcon->label("less than one") if $x < 1; |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
In the property-check output, we can see what percentage of the |
354
|
|
|
|
|
|
|
trials checked this case: |
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
1..1 |
357
|
|
|
|
|
|
|
ok 1 - 'my_sqrt satisfies defn of square root' (1000 attempts) |
358
|
|
|
|
|
|
|
# 1% less than one |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
=head2 Trivial cases |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
Random-input generators may create some inputs that are trivial and |
363
|
|
|
|
|
|
|
don't provide much testing value. To make it easy to label such |
364
|
|
|
|
|
|
|
cases, you can use the following from within your behavior tests: |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
$tcon->trivial if ... ; |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
The above is exactly equivalent to the following: |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
$tcon->label("trivial") if ... ; |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
=cut |
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
sub import { |
378
|
10
|
|
|
|
|
673
|
Test::LectroTest::Property->export_to_level( |
379
|
6
|
|
|
6
|
|
29
|
1, grep {$_ ne NO_FILTER} @_ ); |
380
|
6
|
100
|
|
|
|
15
|
return if grep {$_ eq NO_FILTER} @_; |
|
10
|
|
|
|
|
481
|
|
381
|
2
|
|
|
|
|
6
|
filter_add( _make_code_filter() ); |
382
|
|
|
|
|
|
|
} |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
sub _make_code_filter { |
385
|
5
|
|
|
5
|
|
17
|
my $content = ""; |
386
|
|
|
|
|
|
|
sub { |
387
|
692
|
|
|
692
|
|
30461
|
my $status = shift; |
388
|
692
|
100
|
|
|
|
2573
|
if ( defined $status ? $status : ($status = filter_read()) ) { |
|
|
100
|
|
|
|
|
|
389
|
687
|
100
|
100
|
|
|
3225
|
if (s| \#\# ( \[ .*? ) \#*\s*\]\#\# | |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
390
|
42
|
|
|
|
|
86
|
"["._binding($1)."]]}"._body($1) |exs) { |
391
|
|
|
|
|
|
|
# 1-line decl |
392
|
|
|
|
|
|
|
} |
393
|
4
|
|
|
|
|
8
|
elsif (s| \#\# ( \[.* ) | "["._binding($1) |exs) { |
394
|
|
|
|
|
|
|
# opening of multi-line decl |
395
|
4
|
|
|
|
|
7
|
$content .= " $1"; |
396
|
|
|
|
|
|
|
} |
397
|
|
|
|
|
|
|
elsif ($content && |
398
|
|
|
|
|
|
|
s| ^(.*?)\#*\s*\]\#\# | |
399
|
4
|
|
|
|
|
10
|
_binding($1)."]]}"._body("$content$1") |exs) { |
400
|
|
|
|
|
|
|
# close of multi-line decl |
401
|
4
|
|
|
|
|
9
|
$content = ""; |
402
|
|
|
|
|
|
|
} |
403
|
|
|
|
|
|
|
elsif ($content) { |
404
|
4
|
|
|
|
|
20
|
s/(.*)/_binding($1)/es; |
|
4
|
|
|
|
|
7
|
|
405
|
4
|
|
|
|
|
8
|
$content .= " $1"; |
406
|
|
|
|
|
|
|
} |
407
|
|
|
|
|
|
|
} |
408
|
692
|
|
|
|
|
17989
|
return $status; |
409
|
|
|
|
|
|
|
} |
410
|
5
|
|
|
|
|
40
|
} |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
# convert bindinging operators ( <- ) into key arrows ( => ) |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
sub _binding { |
415
|
54
|
|
|
54
|
|
131
|
my $s = shift; |
416
|
54
|
|
|
|
|
137
|
$s =~ s| <- | => |gx; |
417
|
54
|
|
|
|
|
191
|
return $s; |
418
|
|
|
|
|
|
|
} |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
sub _body { |
421
|
46
|
|
|
46
|
|
86
|
my ($gen_decl_str) = @_; |
422
|
46
|
|
|
|
|
223
|
my @vars = $gen_decl_str =~ /(\w+)\s*<-/gs; |
423
|
46
|
|
|
|
|
60
|
@vars = sort keys %{{ map {($_,1)} @vars }}; # uniq | sort |
|
46
|
|
|
|
|
82
|
|
|
48
|
|
|
|
|
273
|
|
424
|
46
|
|
|
|
|
136
|
@vars = grep { 'tcon' ne $_ } @vars; # disallow reserved var 'tcon' |
|
44
|
|
|
|
|
114
|
|
425
|
46
|
|
|
|
|
72
|
' sub { my (' . join(',', map {"\$$_"} 'tcon', @vars) . ') = @_;'; |
|
89
|
|
|
|
|
355
|
|
426
|
|
|
|
|
|
|
} |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
1; |
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
=pod |
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
=head1 SEE ALSO |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
L describes the many generators and |
435
|
|
|
|
|
|
|
generator combinators that you can use to define the test or |
436
|
|
|
|
|
|
|
condition spaces that you want LectroTest to search for bugs. |
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
L describes the objects that check your |
439
|
|
|
|
|
|
|
properties and tells you how to turn their control knobs. You'll want |
440
|
|
|
|
|
|
|
to look here if you're interested in customizing the testing |
441
|
|
|
|
|
|
|
procedure. |
442
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
=head1 HERE BE SOURCE FILTERS |
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
The special syntax used to specify generator bindings relies upon a |
447
|
|
|
|
|
|
|
source filter (see L). If you don't want to use |
448
|
|
|
|
|
|
|
the syntax, you can disable the filter like so: |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
use Test::LectroTest::Property qw( NO_FILTER ); |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
=head1 AUTHOR |
453
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
Tom Moertel (tom@moertel.com) |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
=head1 INSPIRATION |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
The LectroTest project was inspired by Haskell's |
459
|
|
|
|
|
|
|
QuickCheck module by Koen Claessen and John Hughes: |
460
|
|
|
|
|
|
|
http://www.cs.chalmers.se/~rjmh/QuickCheck/. |
461
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
=head1 COPYRIGHT and LICENSE |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
Copyright (c) 2004-13 by Thomas G Moertel. All rights reserved. |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or |
467
|
|
|
|
|
|
|
modify it under the same terms as Perl itself. |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
=cut |