File Coverage

blib/lib/Test/Command/Simple.pm
Criterion Covered Total %
statement 60 63 95.2
branch 7 14 50.0
condition 8 15 53.3
subroutine 13 13 100.0
pod 6 6 100.0
total 94 111 84.6


line stmt bran cond sub pod time code
1             package Test::Command::Simple;
2              
3 2     2   35375 use warnings;
  2         6  
  2         59  
4 2     2   8 use strict;
  2         2  
  2         102  
5              
6             =head1 NAME
7              
8             Test::Command::Simple - Test external commands (nearly) as easily as loaded modules.
9              
10             =head1 VERSION
11              
12             Version 0.05
13              
14             =cut
15              
16             our $VERSION = '0.05';
17              
18 2     2   10 use base 'Test::Builder::Module';
  2         5  
  2         216  
19 2     2   1151 use IPC::Open3;
  2         8307  
  2         124  
20 2     2   1111 use IO::Select;
  2         3132  
  2         106  
21 2     2   12 use Symbol qw(gensym);
  2         3  
  2         121  
22 2     2   12 use Scalar::Util qw(looks_like_number);
  2         4  
  2         1123  
23              
24             our @EXPORT = qw(
25             run
26             stdout
27             stderr
28             rc
29             run_ok
30             exit_status
31             );
32              
33             =head1 SYNOPSIS
34              
35             use Test::Command::Simple;
36              
37             run('echo', 'has this output'); # only tests that the command can be started, not checking rc
38             is(rc,0,'Returned successfully')
39             like(stdout,qr/has this output/,'Testing stdout');
40             is(length stderr, 0,'No stderr');
41              
42             =head1 PURPOSE
43              
44             This test module is intended to simplify testing of external commands.
45             It does so by running the command under L, closing the stdin
46             immediately, and reading everything from the command's stdout and stderr.
47             It then makes the output available to be tested.
48              
49             It is not (yet?) as feature-rich as L, but I think the
50             interface to this is much simpler. Tests also plug directly into the
51             L framework, which plays nice with L.
52              
53             As compared to L, this module is simpler, relying on
54             the user to feed rc, stdout, and stderr to the appropriate other
55             tests, presumably in L, but not necessarily. This makes it
56             possible, for example, to test line 3 of the output:
57              
58             my (undef, undef, $line) = split /\r?\n/, stdout;
59             is($line, 'This is the third line', 'Test the third line');
60              
61             While this is possible to do with Test::Command's stdout_like, some regex's
62             can get very awkward, and it becomes better to do this in multiple steps.
63              
64             Also, Test::Command saves stdout and stderr to files. That has an advantage
65             when you're saving a lot of text. However, this module prefers to slurp
66             everything in using IPC::Open3, IO::Select, and sysread. Most of the time,
67             commands being tested do not produce significant amounts of output, so there
68             becomes no reason to use temporary files and involve the disk at all.
69              
70             =head1 EXPORTS
71              
72             =head2 run
73              
74             Runs the given command. It will return when the command is done.
75              
76             This will also reinitialise all of the states for stdout, stderr, and rc.
77             If you need to keep the values of a previous run() after a later one,
78             you will need to store it. This should be mostly pretty rare.
79              
80             Counts as one test: whether the IPC::Open3 call to open3 succeeded.
81             That is not returned in a meaningful way to the user, though. To check
82             if that's the case for purposes of SKIPping, rc will be set to -1.
83              
84             =cut
85              
86             my ($stdout, $stderr, $rc);
87             sub run {
88 3 50 33 3 1 36 my $opts = @_ && ref $_[0] eq 'HASH' ? shift : {};
89              
90 3         15 my @cmd = @_;
91              
92             # initialise everything each run.
93 3         5 $rc = -1;
94 3         5 $stdout = '';
95 3         4 $stderr = '';
96              
97 3         8 my ($wtr, $rdr, $err) = map { gensym() } 1..3;
  9         83  
98 3 50       36 my $pid = open3($wtr, $rdr, $err, @cmd) or do {
99 0         0 return __PACKAGE__->builder->ok(0, "Can run '@cmd'");
100             };
101 3         8768 __PACKAGE__->builder->ok(1, "Can run '@cmd'");
102              
103 3         2465 my $s = IO::Select->new();
104              
105 3 50       53 if ($opts->{stdin})
106             {
107 0         0 print $wtr $opts->{stdin};
108             }
109              
110 3         40 close $wtr;
111 3         14 $s->add($rdr);
112 3         177 $s->add($err);
113              
114 3         97 my %map = (
115             fileno($rdr) => \$stdout,
116             fileno($err) => \$stderr,
117             );
118 3         17 while ($s->count())
119             {
120 5 50       38 if (my @ready = $s->can_read())
    0          
121             {
122 5         1858 for my $fh (@ready)
123             {
124 8         9 my $buffer;
125 8         15 my $fileno = fileno($fh);
126 8         38 my $read = sysread($fh, $buffer, 1024);
127 8 100 66     43 if ($read && $map{$fileno})
128             {
129 2         2 ${$map{$fileno}} .= $buffer;
  2         16  
130             }
131             else
132             {
133             # done.
134 6         28 $s->remove($fh);
135 6         247 close $fh;
136             }
137             }
138             }
139             elsif (my @err = $s->has_exception())
140             {
141 0         0 warn "Exception on ", fileno($_) for @err;
142             }
143             }
144 3         77 waitpid $pid, 0;
145 3         16 $rc = $?;
146              
147 3         47 $rc;
148             }
149              
150             =head2 stdout
151              
152             Returns the last run's stdout
153              
154             =cut
155              
156             sub stdout() {
157 2     2 1 32 $stdout
158             }
159              
160             =head2 stderr
161              
162             Returns the last run's stderr
163              
164             =cut
165              
166             sub stderr() {
167 1     1 1 10 $stderr
168             }
169              
170             =head2 rc
171              
172             Returns the last run's full $?, suitable for passing to L's
173             :sys_wait_h macros (WIFEXITED, WEXITSTATUS, etc.)
174              
175             =cut
176              
177             sub rc() {
178 3     3 1 52 $rc
179             }
180              
181             =head2 exit_status
182              
183             Returns the exit status of the last run
184              
185             =cut
186              
187             sub exit_status()
188             {
189             #WEXITSTATUS($rc);
190 2     2 1 49 $rc >> 8;
191             }
192              
193             =head2 run_ok
194              
195             Shortcut for checking that the return from a command is 0. Will
196             still set stdout and stderr for further testing.
197              
198             If the first parameter is an integer 0-255, then that is the expected
199             return code instead. Remember: $? has both a return code (0-255) and a
200             reason for exit embedded. This function must make the assumption that
201             you want a "normal" exit only. If any signal is given, this will treat
202             that as a failure.
203              
204             Note that this becomes B tests: one that IPC::Open3 could create
205             the subprocess with the command, the next is the test that the process
206             exited normally, and the last is the test of the rc.
207              
208             =cut
209              
210             sub run_ok
211             {
212 2     2 1 3 my $wanted_rc = 0;
213 2 50 66     40 if (looks_like_number($_[0]) &&
      66        
      33        
214             0 <= $_[0] && $_[0] <= 255 &&
215             int($_[0]) == $_[0])
216             {
217 1         3 $wanted_rc = shift();
218             }
219 2         5 run(@_);
220 2         16 __PACKAGE__->builder->is_eq(rc & 0xFF, 0, "Process terminated without a signal");
221 2         1477 __PACKAGE__->builder->is_eq(exit_status, $wanted_rc, "Check return from '@_' is $wanted_rc");
222             }
223              
224             =head1 AUTHOR
225              
226             Darin McBride, C<< >>
227              
228             =head1 BUGS
229              
230             Please report any bugs or feature requests to C, or through
231             the web interface at L. I will be notified, and then you'll
232             automatically be notified of progress on your bug as I make changes.
233              
234             =head1 SUPPORT
235              
236             You can find documentation for this module with the perldoc command.
237              
238             perldoc Test::Command::Simple
239              
240              
241             You can also look for information at:
242              
243             =over 4
244              
245             =item * RT: CPAN's request tracker
246              
247             L
248              
249             =item * AnnoCPAN: Annotated CPAN documentation
250              
251             L
252              
253             =item * CPAN Ratings
254              
255             L
256              
257             =item * Search CPAN
258              
259             L
260              
261             =back
262              
263              
264             =head1 ACKNOWLEDGEMENTS
265              
266              
267             =head1 LICENSE AND COPYRIGHT
268              
269             Copyright 2010 Darin McBride.
270              
271             This program is free software; you can redistribute it and/or modify it
272             under the terms of either: the GNU General Public License as published
273             by the Free Software Foundation; or the Artistic License.
274              
275             See http://dev.perl.org/licenses/ for more information.
276              
277              
278             =cut
279              
280             1; # End of Test::Command::Simple