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