File Coverage

blib/lib/Test/LectroTest/Property.pm
Criterion Covered Total %
statement 74 74 100.0
branch 28 28 100.0
condition 9 9 100.0
subroutine 14 14 100.0
pod 0 2 0.0
total 125 127 98.4


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