File Coverage

blib/lib/Test/Future.pm
Criterion Covered Total %
statement 49 52 94.2
branch 5 8 62.5
condition n/a
subroutine 9 10 90.0
pod 1 1 100.0
total 64 71 90.1


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2014-2015 -- leonerd@leonerd.org.uk
5              
6             package Test::Future 0.52;
7              
8 1     1   794 use v5.14;
  1         4  
9 1     1   8 use warnings;
  1         1  
  1         66  
10 1     1   7 use base qw( Test::Builder::Module );
  1         3  
  1         677  
11              
12             our @EXPORT = qw(
13             no_pending_futures
14             );
15              
16 1     1   820 use Scalar::Util qw( refaddr );
  1         2  
  1         91  
17              
18 1     1   7 use constant HAVE_DEVEL_MAT_DUMPER => defined eval { require Devel::MAT::Dumper };
  1         3  
  1         2  
  1         281  
19              
20             =head1 NAME
21              
22             C - unit test assertions for L instances
23              
24             =head1 SYNOPSIS
25              
26             =for highlighter language=perl
27              
28             use Test::More tests => 2;
29             use Test::Future;
30              
31             no_pending_futures {
32             my $f = some_function();
33              
34             is( $f->get, "result", 'Result of the some_function()' );
35             } 'some_function() leaves no pending Futures';
36              
37             =head1 DESCRIPTION
38              
39             This module provides unit testing assertions that may be useful when testing
40             code based on, or using L instances or subclasses.
41              
42             =cut
43              
44             =head1 FUNCTIONS
45              
46             =cut
47              
48             =head2 no_pending_futures
49              
50             no_pending_futures( \&code, $name );
51              
52             I
53              
54             Runs the given block of code, while keeping track of every C instance
55             constructed while doing so. After the code has returned, each of these
56             instances are inspected to check that they are not still pending. If they are
57             all either ready (by success or failure) or cancelled, the test will pass. If
58             any are still pending then the test fails.
59              
60             If L is installed, it will be used to write a memory state dump
61             after a failure. It will create a F<.pmat> file named the same as the unit
62             test, but with the trailing F<.t> suffix replaced with F<-TEST.pmat> where
63             C is the number of the test that failed (in case there was more than
64             one). A list of addresses of C instances that are still pending is
65             also printed to assist in debugging the issue.
66              
67             It is not an error if the code does not construct any C instances at
68             all. The block of code may contain other testing assertions; they will be run
69             before the assertion by C itself.
70              
71             =cut
72              
73             sub no_pending_futures(&@)
74             {
75 4     4 1 232606 my ( $code, $name ) = @_;
76              
77 4         8 my @futures;
78              
79 1     1   6 no warnings 'redefine';
  1         1  
  1         877  
80              
81 4         39 my $new = Future->can( "new" );
82             local *Future::new = sub {
83 4     4   22 my $f = $new->(@_);
84 4         9 push @futures, $f;
85             $f->on_ready( sub {
86 2         4 my $f = shift;
87 2         8 for ( 0 .. $#futures ) {
88 2 50       7 refaddr( $futures[$_] ) == refaddr( $f ) or next;
89              
90 2         5 splice @futures, $_, 1, ();
91 2         23 return;
92             }
93 4         33 });
94 4         9 return $f;
95 4         28 };
96              
97 4         18 my $done = Future->can( "done" );
98             local *Future::done = sub {
99 3     3   24 my $f = $done->(@_);
100 3 100       11 pop @futures if !ref $_[0]; # class method
101 3         22 return $f;
102 4         17 };
103              
104 4         18 my $fail = Future->can( "fail" );
105             local *Future::fail = sub {
106 0     0   0 my $f = $fail->(@_);
107 0 0       0 pop @futures if !ref $_[0]; # class method
108 0         0 return $f;
109 4         16 };
110              
111 4         26 my $tb = __PACKAGE__->builder;
112              
113 4         81 $code->();
114              
115 4         1618 my @pending = grep { !$_->is_ready } @futures;
  1         8  
116              
117 4 100       24 return $tb->ok( 1, $name ) if !@pending;
118              
119 1         5 my $ok = $tb->ok( 0, $name );
120              
121 1         1567 $tb->diag( "The following Futures are still pending:" );
122 1         593 $tb->diag( join ", ", map { sprintf "0x%x", refaddr $_ } @pending );
  1         12  
123              
124 1         604 if( HAVE_DEVEL_MAT_DUMPER ) {
125             my $file = $0;
126             my $num = $tb->current_test;
127              
128             # Trim the .t off first then append -$num.pmat, in case $0 wasn't a .t file
129             $file =~ s/\.(?:t|pm|pl)$//;
130             $file .= "-$num.pmat";
131              
132             $tb->diag( "Writing heap dump to $file" );
133             Devel::MAT::Dumper::dump( $file );
134             }
135              
136 1         13 return $ok;
137             }
138              
139             =head1 AUTHOR
140              
141             Paul Evans
142              
143             =cut
144              
145             0x55AA;