File Coverage

blib/lib/IPC/RunExternal.pm
Criterion Covered Total %
statement 88 105 83.8
branch 23 36 63.8
condition 6 15 40.0
subroutine 11 11 100.0
pod 1 1 100.0
total 129 168 76.7


line stmt bran cond sub pod time code
1             ## no critic (ControlStructures::ProhibitDeepNests)
2             ## no critic (ValuesAndExpressions::ProhibitMagicNumbers)
3              
4             package IPC::RunExternal;
5              
6 1     1   66341 use strict;
  1         3  
  1         29  
7 1     1   4 use warnings;
  1         2  
  1         26  
8 1     1   21 use 5.010000;
  1         3  
9              
10             # ABSTRACT: Execute an external command conveniently by hiding the details of IPC::Open3.
11              
12             our $VERSION = '0.102'; # VERSION: generated by DZP::OurPkgVersion
13              
14              
15 1     1   5 use English '-no_match_vars';
  1         2  
  1         4  
16 1     1   323 use Carp 'croak';
  1         2  
  1         62  
17 1     1   404 use IPC::Open3;
  1         3501  
  1         54  
18 1     1   444 use IO::Select; # for select
  1         1397  
  1         58  
19 1     1   6 use Symbol 'gensym'; # for gensym
  1         3  
  1         39  
20              
21 1     1   5 use Exporter 'import';
  1         2  
  1         59  
22             our @EXPORT_OK = qw(runexternal);
23             our @EXPORT = qw(runexternal); ## no critic (Modules::ProhibitAutomaticExportation)
24             our %EXPORT_TAGS = ( all => [ qw(runexternal) ] );
25              
26 1     1   466 use autodie;
  1         13860  
  1         4  
27              
28             # CONSTANTS for this module
29             my $TRUE = 1;
30             my $FALSE = 0;
31             my $EMPTY_STR = q{};
32              
33             my $DEFAULT_PRINT_PROGRESS_INDICATOR = $FALSE;
34             my $DEFAULT_PROGRESS_INDICATOR_CHARACTER = q{.};
35             my $DEFAULT_EXECUTE_EVERY_SECOND_ROUTINE_POINTER = $FALSE;
36             my $EXIT_STATUS_OK = 1;
37             my $EXIT_STATUS_TIMEOUT = 0;
38             my $EXIT_STATUS_FAILED = -1;
39             my $SIGKILL = 9;
40              
41             # GLOBALS
42             # No global variables
43              
44              
45              
46              
47             sub runexternal { ## no critic (Subroutines::ProhibitExcessComplexity)
48              
49             # Parameters
50 7     7 1 32280 my ($command, $input, $timeout, $parameter_tags) = @_;
51              
52 7 50       44 if(!defined $command) {
53 0         0 croak('Parameter \'command\' is not initialised!');
54             }
55 7 50       20 if(!defined $input) {
56 0         0 croak('Parameter \'input\' is not initialised!');
57             }
58 7 50       22 if($timeout < 0) {
59 0         0 croak('Parameter \'timeout\' is not valid!');
60             }
61              
62 7         12 my $print_progress_indicator = $DEFAULT_PRINT_PROGRESS_INDICATOR;
63 7 50       25 if(exists $parameter_tags->{'print_progress_indicator'}) {
64 0 0 0     0 if($parameter_tags->{'print_progress_indicator'} == $FALSE ||
65             $parameter_tags->{'print_progress_indicator'} == $TRUE) {
66 0         0 $print_progress_indicator = $parameter_tags->{'print_progress_indicator'};
67             }
68             else {
69 0         0 croak('Parameter \'print_progress_indicator\' is not valid (must be 1/0)!');
70             }
71             }
72              
73 7         11 my $progress_indicator_char = $DEFAULT_PROGRESS_INDICATOR_CHARACTER;
74 7 100       26 if(exists $parameter_tags->{'progress_indicator_char'}) {
75 1         9 $progress_indicator_char = $parameter_tags->{'progress_indicator_char'};
76             }
77              
78 7         20 my $execute_every_second = $DEFAULT_EXECUTE_EVERY_SECOND_ROUTINE_POINTER;
79 7 50       23 if(exists $parameter_tags->{'execute_every_second'}) {
80 0 0       0 if(ref($parameter_tags->{'execute_every_second'}) eq 'CODE') {
81 0         0 $execute_every_second = $parameter_tags->{'execute_every_second'};
82             }
83             else {
84 0         0 croak('Parameter execute_every_second is not a code reference!');
85             }
86             }
87              
88             # Variables
89 7         12 my $command_exit_status = $EXIT_STATUS_OK;
90 7         14 my $output_std = $EMPTY_STR;
91 7         11 my $output_error = $EMPTY_STR;
92 7         10 my $output_all = $EMPTY_STR;
93              
94             # Validity check
95 7 50 33     60 if(
96             $command ne $EMPTY_STR
97             #&& defined($input)
98             && $timeout >= 0
99             )
100             {
101 7         44 local $OUTPUT_AUTOFLUSH = $TRUE; # Equals to var $|. Flushes always after writing.
102 7         17 my ($infh,$outfh,$errfh); # these are the FHs for our child
103 7         49 $errfh = gensym(); # we create a symbol for the errfh
104             # because open3 will not do that for us
105 7         177 my $pid;
106             # Read Perl::Critic::Policy::ErrorHandling::RequireCheckingReturnValueOfEval
107             # for an evil eval weakness and a dasdardly difficult eval handling.
108 7         14 my $eval_ok = 1;
109             eval {
110 7         49 $pid = open3($infh, $outfh, $errfh, $command);
111             # To cover the possiblity that an operation may correctly return a
112             # false value, end the block with "1":
113 6         23293 1;
114 7 100       16 } or do {
115 1         11382 $eval_ok = 0;
116             };
117 7 100       38 if($eval_ok) {
118 6         15 print {$infh} $input; ## no critic (InputOutput::RequireCheckedSyscalls)
  6         50  
119 6         90 close $infh;
120 6         3113 my $sel = IO::Select->new(); # create a select object to notify
121             # us on reads on our FHs
122 6         120 $sel->add($outfh, $errfh); # add the FHs we're interested in
123 6         514 my $out_handles_open = 2;
124             ## no critic (ControlStructures::ProhibitCStyleForLoops)
125 6   100     72 for(my $slept_secs = -1; $out_handles_open > 0 && $slept_secs < $timeout; $slept_secs++) {
126 13         148 while(my @ready = $sel->can_read(1)) { # read ready, timeout after 1 second.
127 16         5162966 foreach my $fh (@ready) {
128 20         442 my $line = <$fh>; # read one line from this fh
129 20 100       86 if( !(defined $line) ){ # EOF on this FH
130 8         43 $sel->remove($fh); # remove it from the list
131 8         330 $out_handles_open -= 1;
132 8         28 next; # and go handle the next FH
133             }
134 12 100       98 if($fh == $outfh) { # if we read from the outfh
    50          
135 5         38 $output_std .= $line;
136 5         42 $output_all .= $line;
137             } elsif($fh == $errfh) { # do the same for errfh
138 7         71 $output_error .= $line;
139 7         84 $output_all .= $line;
140             } else { # we read from something else?!?!
141 0         0 croak "Shouldn't be here!\n";
142             }
143             }
144             }
145 13 100       9010395 if($timeout == 0) {
146             # No timeout, so we lower the counter by one to keep it forever under 0.
147             # Only the closing of the output handles ($out_handles_open == 0) can break the loop
148 3         24 $slept_secs--;
149             }
150 13 50 33     71 if($print_progress_indicator == $TRUE && $out_handles_open > 0) {
151 0         0 print {*STDOUT} $progress_indicator_char; ## no critic (InputOutput::RequireCheckedSyscalls)
  0         0  
152             }
153 13 50 33     128 if($execute_every_second && $out_handles_open > 0) {
154 0         0 &{$execute_every_second}($slept_secs);
  0         0  
155             }
156             }
157             # It is safe to kill in all circumstances.
158             # Anyway, we must reap the child process.
159 6         94 my $killed = kill $SIGKILL, $pid;
160 6         1022 my $command_return_status = $CHILD_ERROR >> 8;
161 6 100       262 if($out_handles_open > 0) {
162 2         19 $output_error .= 'Timeout';
163 2         6 $output_all .= 'Timeout';
164 2         115 $command_exit_status = $EXIT_STATUS_TIMEOUT;
165             }
166             }
167             else {
168             # open3 failed!
169 1         12 $output_error .= 'Could not run command';
170 1         7 $output_all .= 'Could not run command';
171 1         50 $command_exit_status = $EXIT_STATUS_FAILED;
172             }
173             }
174             else {
175             # Parameter check
176 0         0 $output_error .= 'Invalid parameters';
177 0         0 $output_all .= 'Invalid parameters';
178 0         0 $command_exit_status = $EXIT_STATUS_FAILED;
179             }
180              
181 7         247 return ($command_exit_status, $output_std, $output_error, $output_all);
182             }
183              
184             1;
185              
186             __END__