+# Inspired from IPC::Cmd
+
+sub capture {
+ my @cmd = @_;
+
+ my $want = wantarray;
+
+ my $fail = sub {
+ my $err = $!;
+ my $ext_err = $^O eq 'MSWin32' ? $^E : undef;
+
+ my $syscall = shift;
+ my $args = join ', ', @_;
+
+ my $msg = "$syscall($args) failed: ";
+
+ if (defined $err) {
+ no warnings 'numeric';
+ my ($err_code, $err_str) = (int $err, "$err");
+ $msg .= "$err_str ($err_code)";
+ }
+
+ if (defined $ext_err) {
+ no warnings 'numeric';
+ my ($ext_err_code, $ext_err_str) = (int $ext_err, "$ext_err");
+ $msg .= ", $ext_err_str ($ext_err_code)";
+ }
+
+ die "$msg\n";
+ };
+
+ my ($status, $content_out, $content_err);
+
+ local $@;
+ my $ok = eval {
+ my ($pid, $out, $err);
+
+ if ($^O eq 'MSWin32') {
+ my $pipe = sub {
+ socketpair $_[0], $_[1],
+ &Socket::AF_UNIX, &Socket::SOCK_STREAM, &Socket::PF_UNSPEC
+ or $fail->(qw<socketpair reader writer>);
+ shutdown $_[0], 1 or $fail->(qw<shutdown reader>);
+ shutdown $_[1], 0 or $fail->(qw<shutdown writer>);
+ return 1;
+ };
+ local (*IN_R, *IN_W);
+ local (*OUT_R, *OUT_W);
+ local (*ERR_R, *ERR_W);
+ $pipe->(*IN_R, *IN_W);
+ $pipe->(*OUT_R, *OUT_W);
+ $pipe->(*ERR_R, *ERR_W);
+
+ $pid = IPC::Open3::open3('>&IN_R', '<&OUT_W', '<&ERR_W', @cmd);
+
+ close *IN_W or $fail->(qw<close input>);
+ $out = *OUT_R;
+ $err = *ERR_R;
+ } else {
+ my $in = IO::Handle->new;
+ $out = IO::Handle->new;
+ $out->autoflush(1);
+ $err = IO::Handle->new;
+ $err->autoflush(1);
+
+ $pid = IPC::Open3::open3($in, $out, $err, @cmd);
+
+ close $in;
+ }
+
+ # Forward signals to the child (except SIGKILL)
+ my %sig_handlers;
+ foreach my $s (keys %SIG) {
+ $sig_handlers{$s} = sub {
+ kill "$s" => $pid;
+ $SIG{$s} = $sig_handlers{$s};
+ };
+ }
+ local $SIG{$_} = $sig_handlers{$_} for keys %SIG;
+
+ unless ($want) {
+ close $out or $fail->(qw<close output>);
+ close $err or $fail->(qw<close error>);
+ waitpid $pid, 0;
+ $status = $?;
+ return 1;
+ }
+
+ my $sel = IO::Select->new();
+ $sel->add($out, $err);
+
+ my $fd_out = fileno $out;
+ my $fd_err = fileno $err;
+
+ my %contents;
+ $contents{$fd_out} = '';
+ $contents{$fd_err} = '';
+
+ while (my @ready = $sel->can_read) {
+ for my $fh (@ready) {
+ my $buf;
+ my $bytes_read = sysread $fh, $buf, 4096;
+ if (not defined $bytes_read) {
+ $fail->('sysread', 'fd(' . fileno($fh) . ')');
+ } elsif ($bytes_read) {
+ $contents{fileno($fh)} .= $buf;
+ } else {
+ $sel->remove($fh);
+ close $fh or $fail->('close', 'fd(' . fileno($fh) . ')');
+ last unless $sel->count;
+ }
+ }
+ }
+
+ waitpid $pid, 0;
+ $status = $?;
+
+ if ($^O eq 'MSWin32') {
+ # Manual CRLF translation that couldn't be done with sysread.
+ s/\x0D\x0A/\n/g for values %contents;
+ }
+
+ $content_out = $contents{$fd_out};
+ $content_err = $contents{$fd_err};
+
+ 1;
+ };
+
+ if ("$]" < 5.014 and $ok and ($status >> 8) == 255 and defined $content_err
+ and $content_err =~ /^open3/) {
+ # Before perl commit 8960aa87 (between 5.12 and 5.14), exceptions in open3
+ # could be reported to STDERR instead of being propagated, so work around
+ # this.
+ $ok = 0;
+ $@ = $content_err;
+ }
+
+ if ($ok) {
+ return ($status, $content_out, $content_err);
+ } else {
+ my $err = $@;
+ chomp $err;
+ return (undef, $err);
+ }
+}
+
+sub capture_failed_msg {
+ my $details = shift;
+
+ my $msg = 'Could not capture command output';
+ $msg .= " ($details)" if defined $details;
+
+ return $msg;
+}
+
+sub capture_perl {
+ my $code = shift;
+
+ if ($code =~ /"/) {
+ die 'Double quotes in evaluated code are not portable';
+ }
+
+ fresh_perl_env {
+ my @perl = @_;
+ capture @perl, '-e', $code;
+ };
+}
+
+sub capture_perl_failed_msg {
+ my $details = shift;
+
+ my $msg = 'Could not capture perl output';
+ $msg .= " ($details)" if defined $details;
+
+ return $msg;
+}
+
+=head2 C<threads>
+
+=over 4
+
+=item *
+
+Import :
+
+ use VPIT::TestHelpers threads => [
+ $pkg, $threadsafe_var, $force_var
+ ];
+
+where :
+
+=over 8
+
+=item -
+
+C<$pkg> is the target package name that will be exercised by this test ;
+
+=item -
+
+C<$threadsafe_var> is the name of an optional variable in C<$pkg> that evaluates to true if and only if the module claims to be thread safe (not checked if either C<$threadsafe_var> or C<$pkg> is C<undef>) ;
+
+=item -
+
+C<$force_var> is the name of the environment variable that can be used to force the thread tests (defaults to C<PERL_FORCE_TEST_THREADS>).
+
+=back
+
+=item *
+
+Dependencies :
+
+=over 8
+
+=item -
+
+C<perl> 5.13.4
+
+=item -
+
+L<POSIX>
+
+=item -
+
+L<threads> 1.67
+
+=item -
+
+L<threads::shared> 1.14
+
+=back
+
+=item *
+
+Exports :
+
+=over 8
+
+=item -
+
+C<spawn $coderef>
+
+=back
+
+=item *
+
+Notes :
+
+=over 8
+
+=item -
+
+C<< exit => 'threads_only' >> is passed to C<< threads->import >>.
+
+=back
+
+=back
+
+=cut
+