File Coverage

blib/lib/Test/LectroTest/FailureRecorder.pm
Criterion Covered Total %
statement 41 42 97.6
branch 5 10 50.0
condition 6 12 50.0
subroutine 8 8 100.0
pod 3 3 100.0
total 63 75 84.0


line stmt bran cond sub pod time code
1             package Test::LectroTest::FailureRecorder;
2             {
3             $Test::LectroTest::FailureRecorder::VERSION = '0.5001';
4             }
5              
6 4     4   24 use strict;
  4         9  
  4         191  
7 4     4   22 use warnings;
  4         7  
  4         121  
8              
9 4     4   21 use Data::Dumper;
  4         22  
  4         3331  
10              
11              
12             =head1 NAME
13              
14             Test::LectroTest::FailureRecorder - Records/plays failures for regression testing
15              
16             =head1 VERSION
17              
18             version 0.5001
19              
20             =head1 SYNOPSIS
21              
22             use Test::LectroTest::Recorder;
23              
24             my $recorder = Test::LectroTest::Recorder->new("storage_file.txt");
25              
26             my $recorder->record_failure_for_property(
27             "property name",
28             $input_hashref_from_counterexample
29             );
30              
31             my $failures = $recorder->get_failures_for_property("property name");
32             for my $input_hashref (@$failures) {
33             # do something with hashref
34             }
35              
36              
37             =head1 DESCRIPTION
38              
39             This module provides a simple means of recording property-check
40             failures so they can be reused as regression tests. You do not need
41             to use this module yourself because the higher-level LectroTest
42             modules will use it for you when needed. (These docs are mainly
43             for LectroTest developers.)
44              
45             The basic idea is to record a failure as a pair of the form
46              
47             [ , ]
48              
49             and Dump these pairs into a text file, each record terminated by blank
50             line so that the file can be read using paragraph-slurp mode.
51              
52             The module provides methods to add such pairs to a recorder file and
53             to retrieve the recorded failures by property name. It uses a cache
54             to avoid repetitive reads.
55              
56              
57             =head1 METHODS
58              
59             =head2 new(I)
60              
61             my $recorder = Test::LectroTest::Recorder->new("/path/to/storage.txt");
62              
63             Creates a new recorder object and tells it to use I
64             for the reading and writing of failures.
65              
66             The recorder will not access the storage file until you attempt to
67             get or record a failure. Thus it is OK to specify a storage file that
68             does not yet exist, provided you record failures to it before you
69             attempt to get failures from it.
70              
71             =cut
72              
73             sub new {
74 3     3 1 6 my $class = shift;
75 3         17 return bless { file => $_[0] }, $class;
76             }
77              
78             # get failure store from cache or file
79              
80             sub _store {
81 20     20   25 my ($self) = @_;
82 20         34 my $file = $self->{file};
83 20   66     84 $self->{cache} ||= do {
84 2 50       130 open my $fh, $file or die "could not open $file: $!";
85 2         11 local $/ = ""; # paragraph slurp mode
86 2         226 my @recs = map eval($_), <$fh>;
87 2         24 close $fh;
88 2         19 \@recs;
89             };
90             }
91              
92             =pod
93              
94             =head2 get_failures_for_property(I)
95              
96             my $failures = $recorder->get_failures_for_property("property name");
97             for my $input_hashref (@$failures) {
98             # do something with hashref
99             while (my ($var, $value) = each %$input_hashref) {
100             # ...
101             }
102             }
103              
104             Returns a reference to an array that contains the recorded failures
105             for the property with the name I. In the event no
106             such failures exist, the array will be empty.
107             Each failure is represented by a hash containing the inputs that
108             caused the failure.
109              
110             If the recorder's storage file does not exist or cannot be
111             opened for reading, this method dies. Thus, you should call
112             it from within an C block.
113              
114             =cut
115              
116             sub get_failures_for_property {
117 20     20 1 36 my ($self, $property_name) = @_;
118 20         29 [ map $_->[1], grep { $_->[0] eq $property_name } @{$self->_store} ];
  200         445  
  20         439  
119             }
120              
121             =pod
122              
123             =head2 record_failure_for_property(I, I)
124              
125             my $recorder->record_failure_for_property(
126             "property name",
127             $input_hashref_from_counterexample
128             );
129              
130             Adds a failure record for the property named I. The
131             record captures the counterexample represented by the I.
132             The record is immediately appended to the recorder's storage file.
133              
134             Returns 1 upon success; dies otherwise.
135              
136             If the recorder's storage file cannot be opened for writing, this
137             method dies. Thus, you should call it from within an C block.
138              
139             =cut
140              
141             sub record_failure_for_property {
142 10     10 1 16 my ($self, $property_name, $input_hash) = @_;
143 10         17 my $file = $self->{file};
144 10         17 my $rec = [ $property_name, $input_hash ];
145 10         33 local $\ = "\n\n";
146 10         15 local $Data::Dumper::Indent = 0;
147 10         12 local $Data::Dumper::Purity = 1;
148 10         11 local $Data::Dumper::Terse = 1;
149 10         11 local $Data::Dumper::Deepcopy = 1;
150 10         10 local $Data::Dumper::Useqq = 1;
151 10 50       454 open my $fh, ">>$file" or die "could not open $file for appending: $!";
152 10         104 print $fh
153             '# ', scalar gmtime, "\n",
154             '# ', $self->_platform, "\n",
155             Dumper( $rec );
156 10         1409 close $fh;
157 10 50       34 push @{$self->{cache}}, $rec if $self->{cache};
  0         0  
158 10         117 1;
159             }
160              
161             sub _platform {
162 10   66 10   50 shift->{platform} ||= do {
163             # first try to grab version line from `perl -v`
164             eval {
165 1         7659 local $_ = `$^X -v`;
166 1 50 33     82 $_ && /^This is perl,(.*)/im && "perl$1";
167             }
168             # if that fails, build our own version line
169             ||
170             sprintf("perl v%vd on %s", $^V,
171             # if uname works, get the platform info from it
172 1 50 33     3 eval {
173             require POSIX;
174             if (my @u = POSIX::uname()) {
175             return "@{[grep defined, @u[0,4,2,3]]}";
176             }
177             }
178             # otherwise, use the less informative Perl OS-name variable
179             ||
180             $^O
181             );
182             };
183             }
184              
185             1;
186              
187              
188              
189             =head1 SEE ALSO
190              
191             L explains the internal testing apparatus,
192             which uses the failure recorders to record and play back failures for
193             regression testing.
194              
195             =head1 AUTHOR
196              
197             Tom Moertel (tom@moertel.com)
198              
199             =head1 COPYRIGHT and LICENSE
200              
201             Copyright (c) 2004-13 by Thomas G Moertel. All rights reserved.
202              
203             This program is free software; you can redistribute it and/or
204             modify it under the same terms as Perl itself.
205              
206             =cut