File Coverage

blib/lib/Test/Stochastic.pm
Criterion Covered Total %
statement 96 105 91.4
branch 21 30 70.0
condition 8 15 53.3
subroutine 17 17 100.0
pod 0 7 0.0
total 142 174 81.6


line stmt bran cond sub pod time code
1             package Test::Stochastic;
2              
3 1     1   23574 use 5.008006;
  1         4  
  1         40  
4 1     1   6 use strict;
  1         2  
  1         37  
5 1     1   4 use warnings;
  1         7  
  1         28  
6              
7 1     1   5 use Test::More;
  1         2  
  1         5  
8 1     1   258 use Scalar::Util qw(reftype);
  1         2  
  1         118  
9 1     1   908 use English qw{-no_match_vars};
  1         5233  
  1         9  
10              
11             require Exporter;
12              
13             our @ISA = qw(Exporter);
14             our %EXPORT_TAGS = ( 'all' => [ qw(
15             stochastic_ok
16             stochastic_nok
17             stochastic_all_seen_ok
18             stochastic_all_seen_nok
19             stochastic_all_and_only_ok
20             stochastic_all_and_only_nok
21             ) ] );
22              
23             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
24              
25             our @EXPORT = qw(
26            
27             );
28              
29             our $VERSION = '0.03';
30             $VERSION = eval $VERSION; # see L
31              
32             my $TIMES = 1000;
33             my $TOLERENCE = 0.2;
34              
35             sub _check_probabilities{
36 18     18   28 my ($arg1, $arg2) = @_;
37 18         19 my ($sub, $hash);
38              
39 18 100       111 if (reftype($arg1) eq "CODE") {
40 3         5 ($sub, $hash) = ($arg1, $arg2);
41             } else {
42 15         31 ($sub, $hash) = ($arg2, $arg1);
43             }
44              
45 18         23 my %seen;
46 18         34 for (1..$TIMES) {
47 342000         1510901 $seen{ $sub->() }++;
48             }
49              
50 18         221 while (my($k, $v) = each %$hash) {
51 43         98 my ($min, $max) = _get_acceptable_range($v, $TIMES, $TOLERENCE);
52 43 100 66     318 next if (($min <= $seen{$k}) and ($seen{$k} <= $max));
53 2         13 my $msg = "Value out of range for '$k': expected to see it between $min and $max times, but instead saw it $seen{$k} times\n";
54 2         15 die $msg;
55             }
56              
57 16         64 return 1;
58              
59              
60             }
61              
62             sub _check_all_present{
63 2     2   4 my ($arg1, $arg2) = @_;
64 2         3 my ($sub, $arr);
65              
66 2 50       10 if (reftype($arg1) eq "CODE") {
67 0         0 ($sub, $arr) = ($arg1, $arg2);
68             } else {
69 2         24 ($sub, $arr) = ($arg2, $arg1);
70             }
71              
72 2         5 my %to_see = map { $_ => 1 } @$arr;
  4         10  
73 2         6 for (1..$TIMES) {
74 28         47 delete $to_see{ $sub->() };
75 28 100       145 unless (%to_see) {
76 1         3 return 1;
77             }
78             }
79              
80 1         12 die "Not all expected outputs seen: missing ". join(', ', keys %to_see);
81             }
82              
83             sub _check_all_and_only_present{
84 4     4   8 my ($arg1, $arg2) = @_;
85 4         5 my ($sub, $arr);
86              
87 4 50       17 if (reftype($arg1) eq "CODE") {
88 0         0 ($sub, $arr) = ($arg1, $arg2);
89             } else {
90 4         5 ($sub, $arr) = ($arg2, $arg1);
91             }
92              
93 4         7 my %to_see = map { $_ => 1 } @$arr;
  8         17  
94 4         10 my %still_to_see = %to_see;
95 4         6 for (1..$TIMES) {
96 33         56 my $val = $sub->();
97 33 100       111 die "unexpected value $val" unless exists $to_see{$val};
98 31         38 delete $still_to_see{ $val };
99             }
100              
101 2 50       7 unless (%still_to_see) {
102 2         5 return 1;
103             }
104 0         0 die "Not all expected outputs seen: missing ". join(', ', keys %to_see);
105             }
106              
107              
108             sub stochastic_ok {
109 15     15 0 7277 my ($arg1, $arg2, $msg) = @_;
110 15   50     81 $msg ||= "stochastic_ok";
111              
112 15         22 eval { _check_probabilities($arg1, $arg2)};
  15         35  
113 15 50       43 if ($EVAL_ERROR) {
114 0         0 ok(0, $EVAL_ERROR);
115             } else {
116 15         692 ok(1, $msg);
117             }
118             }
119              
120              
121             sub stochastic_nok{
122 3     3 0 1895 my ( $arg1, $arg2, $msg ) = @_;
123 3   50     15 $msg ||= "stochastic_nok";
124            
125 3         4 eval { _check_probabilities($arg1, $arg2)};
  3         6  
126 3 100       11 if ($EVAL_ERROR) {
127 2         7 ok(1, $msg);
128             } else {
129 1         3 ok(1, "stochastic_nok -- unexpectedly in range");
130             }
131             }
132              
133             sub stochastic_all_seen_ok{
134 1     1 0 7 my ( $arr, $sub, $msg ) = @_;
135 1         1 eval { _check_all_present($arr, $sub) };
  1         5  
136 1 50       5 if ($EVAL_ERROR) {
137 0         0 ok(0, $EVAL_ERROR);
138             } else {
139 1   50     10 ok( 1, $msg || "stochastic_all_seen_ok" );
140              
141             }
142             }
143              
144              
145             sub stochastic_all_seen_nok{
146 1     1 0 368 my ( $arr, $sub, $msg ) = @_;
147 1         1 eval { _check_all_present($arr, $sub) };
  1         4  
148 1 50       4 if ($EVAL_ERROR) {
149 1   50     7 ok(1, $msg || "stochastic_all_seen_nok");
150             } else {
151 0         0 ok( 0, "stochastic_all_seen_nok: unexpectedly saw everything" );
152              
153             }
154             }
155              
156             sub stochastic_all_and_only_ok{
157 2     2 0 688 my ( $arr, $sub, $msg ) = @_;
158 2         3 eval { _check_all_and_only_present($arr, $sub) };
  2         4  
159 2 50       6 if ($EVAL_ERROR) {
160 0         0 ok(0, $EVAL_ERROR);
161             } else {
162 2   50     10 ok( 1, $msg || "stochastic_all_and_only_ok" );
163              
164             }
165             }
166              
167             sub stochastic_all_and_only_nok{
168 2     2 0 660 my ( $arr, $sub, $msg ) = @_;
169 2         3 eval { _check_all_and_only_present($arr, $sub) };
  2         3  
170 2 50       7 if ($EVAL_ERROR) {
171 2   50     11 ok( 1, $msg || "stochastic_all_and_only_nok" );
172             } else {
173 0         0 ok(0, "stochastic_all_and_only_nok");
174             }
175             }
176              
177              
178             sub setup{
179 4     4 0 2302 my (%hash) = @_;
180 4         21 while (my($k, $v) = each %hash) {
181 6 100       17 if ($k eq "times") {
    50          
182 3         13 $TIMES = $v;
183             } elsif ($k eq "tolerence") {
184 3         13 $TOLERENCE = $v;
185             } else {
186 0         0 die "unknown option $k passed to setup";
187             }
188             }
189             }
190              
191             sub _get_acceptable_range{
192 43     43   60 my ($p, $times, $tolerence) = @_;
193 43         130 return( int(($p - $tolerence) * $times),
194             int(($p + $tolerence) * $times + 0.999)
195             );
196             }
197              
198             1;
199             __END__