File Coverage

blib/lib/Test/SynchHaveWant.pm
Criterion Covered Total %
statement 67 103 65.0
branch 12 40 30.0
condition 3 4 75.0
subroutine 14 15 93.3
pod 3 3 100.0
total 99 165 60.0


line stmt bran cond sub pod time code
1             package Test::SynchHaveWant;
2              
3 3     3   95631 use warnings;
  3         9  
  3         156  
4 3     3   15 use strict;
  3         6  
  3         104  
5              
6 3     3   16 use Test::Builder;
  3         9  
  3         50  
7 3     3   3657 use Data::Dumper;
  3         74656  
  3         231  
8 3     3   32 use Carp 'confess';
  3         5  
  3         155  
9 3     3   18 use base 'Exporter';
  3         7  
  3         8624  
10             our @EXPORT_OK = qw(
11             have
12             want
13             synch
14             );
15              
16             my %DATA_SECTION_FOR; # this is the want() data
17             my %NEW_DATA_FOR; # data from have(), if requested
18             my %SEEK_POSITION_FOR; # where to synch the data, if requested
19             my %SYNCH_WAS_CALLED; # calling have/want after this should fail
20             my %TIMES_CALLED; # track how often have/want called
21              
22             =head1 NAME
23              
24             Test::SynchHaveWant - Synchronize volatile have/want values for tests
25              
26             =head1 VERSION
27              
28             Version 0.01
29              
30             =cut
31              
32             our $VERSION = '0.01';
33              
34             =head1 SYNOPSIS
35              
36             use Test::Most;
37             use Test::SynchHaveWant qw/
38             have
39             want
40             /;
41              
42             my $have = some_complex_data();
43              
44             eq_or_diff have($have), want(), 'have and want should be the same';
45              
46             __DATA__
47             [
48             {
49             'bar' => [ 3, 4 ],
50             'foo' => 1
51             },
52             0,
53             bless( [ 'this', 'that', 'glarble', 'fetch' ], 'Foobar' ),
54             ]
55              
56             If you wish to synch:
57              
58             use Test::Most;
59             use Test::SynchHaveWant qw/
60             have
61             want
62             synch
63             /;
64              
65             my $have = some_complex_data();
66              
67             eq_or_diff have($have), want(), 'have and want should be the same';
68             is have(0), want(), '0 is 0';
69              
70             # note that we can use normal tests
71             my $want = want();
72             isa_ok $want, 'Foobar';
73             is_deeply $have($some_object), $want, '... and the object is the same';
74             synch();
75              
76             __DATA__
77             [
78             {
79             'bar' => [ 3, 4 ],
80             'foo' => 1
81             },
82             0,
83             bless( [ 'this', 'that', 'glarble', 'fetch' ], 'Foobar' ),
84             ]
85              
86              
87             =cut
88              
89             sub _read_data_section {
90 2     2   5 my $caller = shift;
91 2         6 my $key = _get_key();
92              
93 3     3   20 my $__DATA__ = do { no strict 'refs'; \*{"${caller}::DATA"} };
  3         5  
  3         1000  
  2         4  
  2         3  
  2         13  
94 2 50       11 unless ( defined fileno $__DATA__ ) {
95 0         0 confess "__DATA__ section not found for package ($caller)";
96             }
97              
98 2         7 $SEEK_POSITION_FOR{$key} = tell $__DATA__;
99 2         21 seek $__DATA__, 0, 0;
100 2         94 my $data_section = join '', <$__DATA__>;
101 2         33 $data_section =~ s/^.*\n__DATA__\n/\n/s; # for win32
102 2         7 $data_section =~ s/\n__END__\n.*$/\n/s;
103              
104 2         204 $data_section = eval $data_section;
105 2 50       572 if ( my $error = $@ ) {
106 0         0 confess "Error reading __DATA__ for ($caller): $error";
107             }
108 2 50 50     42 unless ( 'ARRAY' eq ( ref $data_section || '' ) ) {
109 0         0 confess "__DATA__ did not contain an array reference";
110             }
111 2         10 $DATA_SECTION_FOR{$key} = $data_section;
112             }
113              
114             =head1 DO NOT USE THIS CODE WITHOUT SOURCE CONTROL
115              
116             This is C. It's very alpha code. It's dangerous code. It attempts to
117             B and if it screws up, you had better be using B
118             CONTROL> so you can revert.
119              
120             That being said, if you need this code and you really, really understand
121             what's going on, go ahead and use it at your own risk.
122              
123             =head1 DESCRIPTION
124              
125             Sometimes you have extremely volatile data/code and you I your tests are
126             correct even though they've failed because the code has changed or the
127             underlying data has been altered. Ordinarily, you never, never want your tests
128             to be so fragile. You want to figure out some way of mocking your test data or
129             isolating functional units in your code for testing.
130              
131             The first pass I had at solving this problem was to effectively compute the
132             edit distance for data structures, but even that failed as differences emerged
133             over time (see L).
134              
135             For this module, we're giving devs a chance to rewrite their test results on
136             the fly, assuming that the new results of their code is correct.
137              
138             This is generally an I. It's very stupid. Not only
139             do we attempt to rewrite your __DATA__ sections, we make it very easy for
140             you to have bogus tests because you may incorrectly assume that the new data
141             you're returning is correct. That's why this is a B
142             EXPERIMENT>.
143              
144             I've been asked a couple of times why I feel the need to experiment with
145             writing "fragile" tests but I can't tell you due to my NDA.
146              
147             =head1 WHY IS OVID BEING STUPID?
148              
149             Tests should not be as fragile as indicated here. You should mock up your test
150             data or find ways of isolating functionality to make your tests more robust.
151              
152             Not everyone has that luxury. If you insist that everyone does have that
153             luxury, be aware that the real world of "these are the constraints I have" and
154             the fantasy world of "the way I like things is the only way things should be
155             done" aren't on speaking terms to one another.
156              
157             =head1 USAGE
158              
159             To make this work, you must have a C<__DATA__> section in your code. This
160             section should contain terse L output of an array reference with each
161             value being a subsequent expected test result. Every time C is called,
162             the next value in this array ref is returned:
163              
164             is have($foo), want(); # 3
165             is_deeply have($aref), want(); # ['foo','bar']
166             is have($idiot), want(); # 'ovid'
167             __DATA__
168             [
169             3,
170             [ qw/foo bar/ ],
171             'ovid',
172             ]
173              
174             The C function must be called as often as the C function (and
175             in sequence) to track the values we have received.
176              
177             If desired, the C function may be exported and called at the end of
178             the test run. If any tests failed (C<< ! Test::Builder->new->is_passing >>),
179             then we attempt to write all values passed to C to the C<__DATA__>
180             section.
181              
182             C will fail if have/want have been called a different number of times
183             or if it has already been called. C and C will fail if
184             C has already been called.
185              
186             It goes without saying that this means you must have a deterministic order for
187             your tests. Bad:
188              
189             while ( my ( $key, $value ) = each %test ) {
190             is_deeply have( some_func( $key, $value ) ), want();
191             }
192              
193             Good:
194              
195             foreach my $key ( sort keys %test ) {
196             my $value = $test{$key};
197             is_deeply have( some_func( $key, $value ) ), want();
198             }
199              
200             =head1 EXPORT
201              
202             =head2 C
203              
204             is have($have), want(), 'have should equal want';
205              
206             Ordinarily this function is a no-op. It merely returns the value it is passed.
207             However, if synch is called at the end of the test run, the values passed to
208             this function will be written to the data in the __DATA__ section.
209              
210             =cut
211              
212             sub have {
213 6     6 1 607 my $have = shift;
214 6         20 my $key = _get_key();
215 6 50       24 if ( exists $SYNCH_WAS_CALLED{$key} ) {
216 0         0 confess "Synch was already called for ($key)";
217             }
218              
219 3     3   19 no warnings 'uninitialized';
  3         5  
  3         566  
220 6         19 $TIMES_CALLED{$key}{have}++;
221 6   100     28 $NEW_DATA_FOR{$key} ||= [];
222 6         1886 push @{ $NEW_DATA_FOR{$key} } => $have;
  6         18  
223 6         34 return $have;
224             }
225              
226             =head2 C
227              
228             is have($have), want(), 'have should equal want';
229              
230             Returns the current expected test result. Attempting to read past the end of
231             the test results will result in a fatal error.
232              
233             =cut
234              
235             sub want {
236 7     7 1 19 my $key = _get_key();
237 7 50       25 if ( exists $SYNCH_WAS_CALLED{$key} ) {
238 0         0 confess "Synch was already called for ($key)";
239             }
240              
241 7 100       23 unless ( exists $DATA_SECTION_FOR{$key} ) {
242 2         11 _read_data_section( scalar caller );
243             }
244 3     3   16 no warnings 'uninitialized';
  3         4  
  3         2173  
245 7         16 $TIMES_CALLED{$key}{want}++;
246 7         13 my $data_section = $DATA_SECTION_FOR{$key};
247 7 100       17 unless (@$data_section) {
248 1         257 confess("Attempt to read past end of __DATA__ for $0");
249             }
250 6         32 return shift @$data_section;
251             }
252              
253             =head2 C
254              
255             synch();
256              
257             This function will attempt to take all of the values passed to have() and
258             write them out to the __DATA__ section. If C and C have been
259             called an unequal number of times, this function will die.
260              
261             Will not attempt to synch the __DATA__ if the tests appear to be passing.
262              
263             If tests are not passing, will prompt the user if they really want to synch
264             tests results. Only a C<< /^\s*[Yy]/ >> is acceptable. To ensure that we don't
265             block on automated systems, we have an alarm set for 10 seconds. After that,
266             we merely return without attempting to synch.
267              
268             =cut
269              
270             sub synch {
271 2     2 1 645 my $key = _get_key();
272              
273 2         5 my ( $have, $want ) = @{ $TIMES_CALLED{$key} }{qw/have want/};
  2         8  
274              
275 2 100       10 unless ( $have == $want ) {
276 1         188 confess(
277             "have/want not in synch: have was called $have times and want was called $want times"
278             );
279             }
280              
281 1         5 my $builder = Test::Builder->new;
282 1 50       8 return if $builder->is_passing;
283              
284 0         0 print STDERR "# Really synch have/want data? (y/N) ";
285              
286 0         0 my $response;
287 0         0 eval {
288 0     0   0 local $SIG{ALRM} = sub { die "Died while bored" };
  0         0  
289              
290 0         0 alarm 10;
291 0         0 $response = ;
292 0         0 alarm 0;
293             };
294 0 0       0 if (my $error = $@) {
295 0 0       0 return if $error =~ /Died while bored/;
296 0         0 confess($error);
297             }
298 0 0       0 unless ( $response =~ /^\s*[yY]/ ) {
299 0         0 warn "# Aborting synch ...";
300 0         0 return;
301             }
302              
303 0 0       0 if ( exists $SYNCH_WAS_CALLED{$key} ) {
304 0         0 confess "Synch was already called for ($key)";
305             }
306              
307 0         0 $SYNCH_WAS_CALLED{$key} = 1;
308 0         0 local $Data::Dumper::Indent = 1;
309 0         0 local $Data::Dumper::Sortkeys = 1;
310 0         0 local $Data::Dumper::Terse = 1;
311 0 0       0 unless ( exists $SEEK_POSITION_FOR{$key} ) {
312 0         0 confess("Panic: seek position for ($key) not found");
313             }
314 0         0 my $new_data = $NEW_DATA_FOR{$key};
315 0 0       0 unless ( 'ARRAY' eq ref $new_data ) {
316 0         0 confess(
317             "PANIC: new data to write to __DATA__ is not an array reference");
318             }
319 0         0 my $position = $SEEK_POSITION_FOR{$key};
320              
321 0 0       0 open my $fh, '+<', $0 or confess "Cannot open $0 for writing: $!";
322 0 0       0 seek $fh, $position, 0
323             or confess "Cannot seek to position $position for $0: $!";
324 0 0       0 truncate $fh, tell($fh)
325             or confess "Cannot truncate $0 at position $position: $!";
326 0 0       0 print $fh Dumper($new_data) or confess "Could not print new data to $0: $!";
327 0 0       0 close $fh or confess "Could not close $0: $!";
328             }
329              
330             # XXX eventually I may have to add to this if people start using this
331             sub _get_key {
332 17     17   48 return $0;
333             }
334              
335             =head1 AUTHOR
336              
337             Curtis 'Ovid' Poe, C<< >>
338              
339             =head1 BUGS
340              
341             Please report any bugs or feature requests to C
342             rt.cpan.org>, or through the web interface at
343             L. I will
344             be notified, and then you'll automatically be notified of progress on your bug
345             as I make changes.
346              
347             =head1 SUPPORT
348              
349             You can find documentation for this module with the perldoc command.
350              
351             perldoc Test::SynchHaveWant
352              
353             You can also look for information at:
354              
355             =over 4
356              
357             =item * RT: CPAN's request tracker
358              
359             L
360              
361             =item * AnnoCPAN: Annotated CPAN documentation
362              
363             L
364              
365             =item * CPAN Ratings
366              
367             L
368              
369             =item * Search CPAN
370              
371             L
372              
373             =back
374              
375             =head1 ACKNOWLEDGEMENTS
376              
377             You don't really think I'm going to blame anyone else for this idiocy, do you?
378              
379             =head1 LICENSE AND COPYRIGHT
380              
381             Copyright 2011 Curtis 'Ovid' Poe.
382              
383             This program is free software; you can redistribute it and/or modify it
384             under the terms of either: the GNU General Public License as published
385             by the Free Software Foundation; or the Artistic License.
386              
387             See http://dev.perl.org/licenses/ for more information.
388              
389             =cut
390              
391             1;