File Coverage

lib/Test/Wiretap.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             # Copyright (c) 2001-2008, Aruba Networks, Inc.
2             # This library is free software; you can redistribute it and/or modify it
3             # under the same terms as Perl itself.
4              
5 6     6   161604 use strict;
  6         16  
  6         173  
6 6     6   29 use warnings;
  6         13  
  6         249  
7             package Test::Wiretap;
8 6     6   32 use base qw(Exporter);
  6         12  
  6         1085  
9              
10 6     6   42 use Carp;
  6         11  
  6         591  
11 6     6   7492 use Storable qw(dclone);
  6         32904  
  6         546  
12 6     6   13914 use Class::Std;
  0            
  0            
13             use Test::Resub;
14              
15             our $VERSION = '1.01';
16             our @EXPORT_OK = qw(wiretap);
17              
18             # Simple delegators: this way, we present a unified interface, instead of having
19             # the caller write garbage like $wiretap->resub->args, $wiretap->resub->reset, etc.
20             use Class::Delegator
21             send => [qw(
22             args
23             named_args
24             method_args
25             named_method_args
26             called
27             was_called
28             not_called
29             )],
30             to => => '_my_resub';
31              
32             my %resub :ATTR;
33             my %capture :ATTR( :init_arg, :default(0) );
34             my %return_values :ATTR;
35             my %return_context :ATTR;
36             my %deep_copy :ATTR( :init_arg, :default(1) );
37              
38             sub wiretap {
39             my ($name, $code, %args) = @_;
40             return Test::Wiretap->new({
41             name => $name,
42             before => $code,
43             %args,
44             });
45             }
46              
47             sub _my_resub {
48             my ($self) = @_;
49             return $resub{ident $self};
50             }
51              
52             sub BUILD {
53             my ($self, $ident, $args) = @_;
54              
55             local $Carp::Internal{'Class::Std'} = 1;
56             local $Carp::Internal{ do {__PACKAGE__} } = 1;
57              
58             my $code_before = $args->{before} || sub {};
59             my $code_after = $args->{after} || sub {};
60             my $original_function = UNIVERSAL::can(_split_package_method($args->{name}));
61              
62             my $replacement = sub {
63             my ($run_original, $capture, $run_after, @rv);
64             my $wantarray = wantarray;
65              
66             if ($wantarray) {
67             $run_original = sub { @rv = $original_function->(@_) };
68             $capture = sub {
69             push @{$return_values{$ident}}, ($deep_copy{$ident} ? dclone(\@rv) : \@rv);
70             push @{$return_context{$ident}}, 'list';
71             };
72             $run_after = sub { $code_after->(\@_, \@rv, 'list') };
73             } elsif (defined $wantarray) {
74             $run_original = sub { $rv[0] = $original_function->(@_) };
75             $capture = sub {
76             push @{$return_values{$ident}}, ($deep_copy{$ident} ? dclone(\@rv) : \@rv);
77             push @{$return_context{$ident}}, 'scalar';
78             };
79             $run_after = sub { $code_after->(\@_, $rv[0], 'scalar') };
80             } else {
81             $run_original = $original_function;
82             $capture = sub {
83             push @{$return_values{$ident}}, undef;
84             push @{$return_context{$ident}}, 'void';
85             };
86             $run_after = sub { $code_after->(\@_, undef, 'void') };
87             }
88              
89             $code_before->(@_);
90             $run_original->(@_);
91             $capture->();
92             $run_after->(@_);
93              
94             return $wantarray ? @rv : $rv[0];
95             };
96              
97             $resub{$ident} = Test::Resub->new({
98             name => $args->{name},
99             capture => $args->{capture},
100             code => $replacement,
101             _arg_slice($args, qw(deep_copy call)),
102             (exists $args->{deep_copy} ? (deep_copy => $args->{deep_copy}) : ()),
103             });
104             }
105              
106             sub _arg_slice {
107             my ($hash, @keys) = @_;
108             return map { exists $hash->{$_} ? ($_ => $hash->{$_}) : () } @keys;
109             }
110              
111             sub reset {
112             my ($self) = @_;
113             $self->_my_resub->reset;
114             delete $return_values{ident $self};
115             delete $return_context{ident $self};
116             }
117              
118             sub return_values {
119             my ($self) = @_;
120             $self->_complain_if_no_capture('return values');
121             return $return_values{ident $self} || [];
122             }
123              
124             sub return_contexts {
125             my ($self) = @_;
126             $self->_complain_if_no_capture('return contexts');
127             return $return_context{ident $self} || [];
128             }
129              
130             sub _complain_if_no_capture {
131             my ($self, $what) = @_;
132             if (not $capture{ident $self}) {
133             local $Carp::Internal{ do {__PACKAGE__} } = 1;
134             carp "Must use the 'capture' flag to capture $what\n";
135             }
136             }
137              
138             # if we let Class::Std do this, then we either have to put ourselves and
139             # Class::Std in %Carp::Internal all the time, which is a side effect that the
140             # user is almost certainly not expecting; or the 'not ok 1000' message
141             # contains Test::Wiretap in its stack trace, which the user doesn't care about.
142             sub DEMOLISH {
143             my ($self, $ident) = @_;
144             local $Carp::Internal{ do {__PACKAGE__} } = 1;
145             local $Carp::Internal{'Class::Std'} = 1;
146             delete $resub{$ident};
147             }
148              
149             # XXX copied from Test::Resub
150             sub _split_package_method {
151             my ($method) = @_;
152             my ($package, $name) = $method =~ /^(.+)::([^:]+)$/;
153             return ($package, $name);
154             }
155              
156             =head1 NAME
157              
158             Test-Wiretap - Listen in on a function
159              
160             =head1 SYNOPSIS
161              
162             use Test::More tests => 3;
163             use Test::Wiretap;
164              
165             {
166             package InsultOMatic;
167             sub insult {
168             my ($class, $what) = @_;
169             print "$what smells funny.\n";
170             return 'stinky';
171             }
172             }
173              
174             my $tap = Test::Wiretap->new({
175             name => 'InsultOMatic::insult',
176             before => sub {
177             print "Preparing for insult...\n";
178             },
179             after => sub {
180             print "Insult complete!\n";
181             },
182             });
183              
184             InsultOMatic->insult('Limburger cheese');
185             # prints:
186             # Preparing for insult...
187             # Limburger cheese smells funny.
188             # Insult complete!
189              
190             is( $tap->called, 1, "Insulted one thing" );
191             is_deeply(
192             $tap->method_args,
193             [['Limburger cheese']],
194             "Insulted cheese"
195             );
196             is_deeply(
197             $tap->return_values,
198             [['stinky']],
199             "InsultOMatic agrees with me"
200             );
201              
202             =head1 CONSTRUCTOR
203              
204             use Test::Wiretap qw(wiretap);
205             my $tap = wiretap 'package::method', sub { ... }, %args;
206              
207             is equivalent to:
208              
209             use Test::Wiretap;
210             my $rs = Test::Wiretap->new({
211             name => 'package::method',
212             before => sub { ... },
213             %args,
214             });
215              
216             C<%args> can contain any of the following named arguments:
217              
218             =over 4
219              
220             =item B (required)
221              
222             The name of the function which is to be monitored.
223              
224             =item B (optional)
225              
226             A code reference that will run before the tapped function. This function
227             receives the same @_ as the tapped function does.
228              
229             =item B (optional)
230              
231             A code reference that will run after the tapped function. This function
232             receives three arguments: a reference to the tapped function's argument list,
233             a reference to the tapped function's return-values list,
234             and a third parameter indicating the context in which the tapped function was called.
235              
236             The third parameter is one of 'list', 'scalar', or 'void'.
237              
238             That is, if you have:
239             sub foo { map { $_ + 100 } @_ }
240              
241             my $tap = Test::Wiretap->new({
242             name => 'main::foo',
243             before => sub { ... },
244             after => sub { ... },
245             });
246              
247             my @list = foo(1, 2, 3);
248              
249             then the 'before' sub's @_ is (1, 2, 3),
250             and the 'after' sub's @_ is ([1, 2, 3], [101, 102, 103], 'list').
251              
252             =item B (optional)
253              
254             If true, arguments and return values will be captured. Arguments are available
255             using the B, B, B, and B methods.
256             See the Test::Resub documentation for details on those.
257              
258             Default is not to capture arguments.
259              
260             =item B (optional)
261              
262             If true, a deep copy of all arguments and return values will be made. Otherwise,
263             a shallow copy will be kept. This is useful if the tapped function modifies
264             receives a reference to a data structure that it modifies, for example.
265              
266             Default is to deeply copy arguments and return values.
267              
268             =back
269              
270             =head1 METHODS
271              
272             =over 4
273              
274             =item B
275              
276             Returns the number of times the tapped subroutine/method was called. The
277             C method clears this data.
278              
279             =item B
280              
281             Returns the total number of times the tapped subroutine/method was called.
282             This data is B cleared by the C method.
283              
284             =item B
285              
286             Returns true if the tapped subroutine/method was never called. The C
287             method clears this data.
288              
289             =item B
290              
291             Clears the C, C, C, and C data.
292              
293             =item B, B, B, B
294              
295             Returns data on how the replaced subroutine/method was invoked.
296             See the Test::Resub documentation for details.
297              
298             =item B
299              
300             Returns a list of lists of the return values from the tapped function. Examples:
301              
302             sub foo { map { $_ + 100 } @_ }
303              
304             Invocations: C returns:
305             ---------------------------- -------------------------
306             (none) []
307             foo(1, 2, 3) [[101, 102, 103]]
308             foo(5); foo(6, 7) [[105], [106, 107]]
309              
310             =item B
311              
312             sub bar { }
313              
314             Invocations: C returns:
315             ---------------------------- -------------------------
316             foo(); ['void']
317             $x = foo(); ['scalar']
318             @a = foo(); ['list']
319             $x = foo(); @a = foo(); foo(); ['scalar', 'list', 'void']
320              
321             =back
322              
323             =head1 AUTHOR
324              
325             AirWave Wireless, C<< >>
326              
327             =head1 BUGS
328              
329             Please report any bugs or feature requests to C, or through
330             the web interface at L. I will be notified, and then you'll
331             automatically be notified of progress on your bug as I make changes.
332              
333              
334              
335              
336             =head1 SUPPORT
337              
338             You can find documentation for this module with the perldoc command.
339              
340             perldoc Test::Wiretap
341              
342              
343             You can also look for information at:
344              
345             =over 4
346              
347             =item * RT: CPAN's request tracker
348              
349             L
350              
351             =item * AnnoCPAN: Annotated CPAN documentation
352              
353             L
354              
355             =item * CPAN Ratings
356              
357             L
358              
359             =item * Search CPAN
360              
361             L
362              
363             =back
364              
365              
366             =head1 ACKNOWLEDGEMENTS
367              
368              
369             =head1 COPYRIGHT & LICENSE
370              
371             Copyright 2008 AirWave Wireless, all rights reserved.
372              
373             This program is free software; you can redistribute it and/or modify it
374             under the same terms as Perl itself.
375              
376              
377             =cut
378              
379             1; # End of Test::Wiretap