1package TAP::Parser::Iterator::Process; 2 3use strict; 4use warnings; 5 6use Config; 7use IO::Handle; 8 9use base 'TAP::Parser::Iterator'; 10 11my $IS_WIN32 = ( $^O =~ /^(MS)?Win32$/ ); 12 13=head1 NAME 14 15TAP::Parser::Iterator::Process - Iterator for process-based TAP sources 16 17=head1 VERSION 18 19Version 3.48 20 21=cut 22 23our $VERSION = '3.48'; 24 25=head1 SYNOPSIS 26 27 use TAP::Parser::Iterator::Process; 28 my %args = ( 29 command => ['python', 'setup.py', 'test'], 30 merge => 1, 31 setup => sub { ... }, 32 teardown => sub { ... }, 33 ); 34 my $it = TAP::Parser::Iterator::Process->new(\%args); 35 my $line = $it->next; 36 37=head1 DESCRIPTION 38 39This is a simple iterator wrapper for executing external processes, used by 40L<TAP::Parser>. Unless you're writing a plugin or subclassing, you probably 41won't need to use this module directly. 42 43=head1 METHODS 44 45=head2 Class Methods 46 47=head3 C<new> 48 49Create an iterator. Expects one argument containing a hashref of the form: 50 51 command => \@command_to_execute 52 merge => $attempt_merge_stderr_and_stdout? 53 setup => $callback_to_setup_command 54 teardown => $callback_to_teardown_command 55 56Tries to uses L<IPC::Open3> & L<IO::Select> to communicate with the spawned 57process if they are available. Falls back onto C<open()>. 58 59=head2 Instance Methods 60 61=head3 C<next> 62 63Iterate through the process output, of course. 64 65=head3 C<next_raw> 66 67Iterate raw input without applying any fixes for quirky input syntax. 68 69=head3 C<wait> 70 71Get the wait status for this iterator's process. 72 73=head3 C<exit> 74 75Get the exit status for this iterator's process. 76 77=cut 78 79{ 80 81 no warnings 'uninitialized'; 82 # get around a catch22 in the test suite that causes failures on Win32: 83 local $SIG{__DIE__} = undef; 84 eval { require POSIX; &POSIX::WEXITSTATUS(0) }; 85 if ($@) { 86 *_wait2exit = sub { $_[1] >> 8 }; 87 } 88 else { 89 *_wait2exit = sub { POSIX::WEXITSTATUS( $_[1] ) } 90 } 91} 92 93sub _use_open3 { 94 my $self = shift; 95 return unless $Config{d_fork} || $IS_WIN32; 96 for my $module (qw( IPC::Open3 IO::Select )) { 97 eval "use $module"; 98 return if $@; 99 } 100 return 1; 101} 102 103{ 104 my $got_unicode; 105 106 sub _get_unicode { 107 return $got_unicode if defined $got_unicode; 108 eval 'use Encode qw(decode_utf8);'; 109 $got_unicode = $@ ? 0 : 1; 110 111 } 112} 113 114# new() implementation supplied by TAP::Object 115 116sub _initialize { 117 my ( $self, $args ) = @_; 118 119 my @command = @{ delete $args->{command} || [] } 120 or die "Must supply a command to execute"; 121 122 $self->{command} = [@command]; 123 124 # Private. Used to frig with chunk size during testing. 125 my $chunk_size = delete $args->{_chunk_size} || 65536; 126 127 my $merge = delete $args->{merge}; 128 my ( $pid, $err, $sel ); 129 130 if ( my $setup = delete $args->{setup} ) { 131 $setup->(@command); 132 } 133 134 my $out = IO::Handle->new; 135 136 if ( $self->_use_open3 ) { 137 138 # HOTPATCH {{{ 139 my $xclose = \&IPC::Open3::xclose; 140 no warnings; 141 local *IPC::Open3::xclose = sub { 142 my $fh = shift; 143 no strict 'refs'; 144 return if ( fileno($fh) == fileno(STDIN) ); 145 $xclose->($fh); 146 }; 147 148 # }}} 149 150 if ($IS_WIN32) { 151 $err = $merge ? '' : '>&STDERR'; 152 eval { 153 $pid = open3( 154 '<&STDIN', $out, $merge ? '' : $err, 155 @command 156 ); 157 }; 158 die "Could not execute (@command): $@" if $@; 159 if ( $] >= 5.006 ) { 160 binmode($out, ":crlf"); 161 } 162 } 163 else { 164 $err = $merge ? '' : IO::Handle->new; 165 eval { $pid = open3( '<&STDIN', $out, $err, @command ); }; 166 die "Could not execute (@command): $@" if $@; 167 $sel = $merge ? undef : IO::Select->new( $out, $err ); 168 } 169 } 170 else { 171 $err = ''; 172 my $exec = shift @command; 173 $exec = qq{"$exec"} if $exec =~ /\s/ and -x $exec; 174 my $command 175 = join( ' ', $exec, map { $_ =~ /\s/ ? qq{"$_"} : $_ } @command ); 176 open( $out, "$command|" ) 177 or die "Could not execute ($command): $!"; 178 } 179 180 $self->{out} = $out; 181 $self->{err} = $err; 182 $self->{sel} = $sel; 183 $self->{pid} = $pid; 184 $self->{exit} = undef; 185 $self->{chunk_size} = $chunk_size; 186 187 if ( my $teardown = delete $args->{teardown} ) { 188 $self->{teardown} = sub { 189 $teardown->(@command); 190 }; 191 } 192 193 return $self; 194} 195 196=head3 C<handle_unicode> 197 198Upgrade the input stream to handle UTF8. 199 200=cut 201 202sub handle_unicode { 203 my $self = shift; 204 205 if ( $self->{sel} ) { 206 if ( _get_unicode() ) { 207 208 # Make sure our iterator has been constructed and... 209 my $next = $self->{_next} ||= $self->_next; 210 211 # ...wrap it to do UTF8 casting 212 $self->{_next} = sub { 213 my $line = $next->(); 214 return decode_utf8($line) if defined $line; 215 return; 216 }; 217 } 218 } 219 else { 220 if ( $] >= 5.008 ) { 221 eval 'binmode($self->{out}, ":utf8")'; 222 } 223 } 224 225} 226 227############################################################################## 228 229sub wait { shift->{wait} } 230sub exit { shift->{exit} } 231 232sub _next { 233 my $self = shift; 234 235 if ( my $out = $self->{out} ) { 236 if ( my $sel = $self->{sel} ) { 237 my $err = $self->{err}; 238 my @buf = (); 239 my $partial = ''; # Partial line 240 my $chunk_size = $self->{chunk_size}; 241 return sub { 242 return shift @buf if @buf; 243 244 READ: 245 while ( my @ready = $sel->can_read ) { 246 for my $fh (@ready) { 247 my $got = sysread $fh, my ($chunk), $chunk_size; 248 249 if ( $got == 0 ) { 250 $sel->remove($fh); 251 } 252 elsif ( $fh == $err ) { 253 print STDERR $chunk; # echo STDERR 254 } 255 else { 256 $chunk = $partial . $chunk; 257 $partial = ''; 258 259 # Make sure we have a complete line 260 unless ( substr( $chunk, -1, 1 ) eq "\n" ) { 261 my $nl = rindex $chunk, "\n"; 262 if ( $nl == -1 ) { 263 $partial = $chunk; 264 redo READ; 265 } 266 else { 267 $partial = substr( $chunk, $nl + 1 ); 268 $chunk = substr( $chunk, 0, $nl ); 269 } 270 } 271 272 push @buf, split /\n/, $chunk; 273 return shift @buf if @buf; 274 } 275 } 276 } 277 278 # Return partial last line 279 if ( length $partial ) { 280 my $last = $partial; 281 $partial = ''; 282 return $last; 283 } 284 285 $self->_finish; 286 return; 287 }; 288 } 289 else { 290 return sub { 291 local $/ = "\n"; # to ensure lines 292 if ( defined( my $line = <$out> ) ) { 293 chomp $line; 294 return $line; 295 } 296 $self->_finish; 297 return; 298 }; 299 } 300 } 301 else { 302 return sub { 303 $self->_finish; 304 return; 305 }; 306 } 307} 308 309sub next_raw { 310 my $self = shift; 311 return ( $self->{_next} ||= $self->_next )->(); 312} 313 314sub _finish { 315 my $self = shift; 316 317 my $status = $?; 318 319 # Avoid circular refs 320 $self->{_next} = sub {return} 321 if $] >= 5.006; 322 323 # If we have a subprocess we need to wait for it to terminate 324 if ( defined $self->{pid} ) { 325 if ( $self->{pid} == waitpid( $self->{pid}, 0 ) ) { 326 $status = $?; 327 } 328 } 329 330 ( delete $self->{out} )->close if $self->{out}; 331 332 # If we have an IO::Select we also have an error handle to close. 333 if ( $self->{sel} ) { 334 ( delete $self->{err} )->close; 335 delete $self->{sel}; 336 } 337 else { 338 $status = $?; 339 } 340 341 # Sometimes we get -1 on Windows. Presumably that means status not 342 # available. 343 $status = 0 if $IS_WIN32 && $status == -1; 344 345 $self->{wait} = $status; 346 $self->{exit} = $self->_wait2exit($status); 347 348 if ( my $teardown = $self->{teardown} ) { 349 $teardown->(); 350 } 351 352 return $self; 353} 354 355=head3 C<get_select_handles> 356 357Return a list of filehandles that may be used upstream in a select() 358call to signal that this Iterator is ready. Iterators that are not 359handle based should return an empty list. 360 361=cut 362 363sub get_select_handles { 364 my $self = shift; 365 return grep $_, ( $self->{out}, $self->{err} ); 366} 367 3681; 369 370=head1 ATTRIBUTION 371 372Originally ripped off from L<Test::Harness>. 373 374=head1 SEE ALSO 375 376L<TAP::Object>, 377L<TAP::Parser>, 378L<TAP::Parser::Iterator>, 379 380=cut 381 382