]> git.vpit.fr Git - perl/modules/Variable-Magic.git/commitdiff
Update VPIT::TestHelpers to f24eb57f
authorVincent Pit <vince@profvince.com>
Thu, 9 Apr 2015 16:57:47 +0000 (13:57 -0300)
committerVincent Pit <vince@profvince.com>
Thu, 9 Apr 2015 17:27:51 +0000 (14:27 -0300)
And port t/17-ctl.t to the new 'capture' feature. Capture::Tiny is no
longer needed for the optional external tests.

t/09-load-threads.t
t/17-ctl.t
t/lib/VPIT/TestHelpers.pm

index 557258b38af1f51fb6081634135f96deb8bd0116..91092b0889df9b4804c11cb96772d21609ca976d 100644 (file)
@@ -4,7 +4,7 @@ use strict;
 use warnings;
 
 use lib 't/lib';
 use warnings;
 
 use lib 't/lib';
-use VPIT::TestHelpers;
+use VPIT::TestHelpers 'run_perl';
 
 my ($module, $thread_safe_var);
 BEGIN {
 
 my ($module, $thread_safe_var);
 BEGIN {
index c9dd4ca6426c7149d400e3550689c5156756fcd8..88345409e64d6c50d6e6fd54ac507f745a3e2089 100644 (file)
@@ -6,7 +6,7 @@ use warnings;
 use Test::More tests => 4 * 8 + 4 * (2 * 6 + 1) + 10 + 1 + 1;
 
 use lib 't/lib';
 use Test::More tests => 4 * 8 + 4 * (2 * 6 + 1) + 10 + 1 + 1;
 
 use lib 't/lib';
-use VPIT::TestHelpers;
+use VPIT::TestHelpers 'capture';
 
 use Variable::Magic qw<wizard cast VMG_UVAR>;
 
 
 use Variable::Magic qw<wizard cast VMG_UVAR>;
 
@@ -345,41 +345,15 @@ eval q{BEGIN {
 like $@, expect('tomato', undef, "\nBEGIN.*"),
                           'die in BEGIN in eval triggers hints hash destructor';
 
 like $@, expect('tomato', undef, "\nBEGIN.*"),
                           'die in BEGIN in eval triggers hints hash destructor';
 
-my $has_capture_tiny = do {
- local $@;
- eval {
-  require Capture::Tiny;
-  Capture::Tiny->VERSION('0.08');
- }
-};
-if ($has_capture_tiny) {
- local $@;
- my $output = eval {
-  Capture::Tiny::capture_merged(sub { run_perl <<'  CODE' });
-print STDOUT "pants\n";
-print STDERR "trousers\n";
-  CODE
- };
- unless (defined $output and $output =~ /pants/ and $output =~ /trousers/) {
-  $has_capture_tiny = 0;
- }
-}
-if ($has_capture_tiny) {
- defined and diag "Using Capture::Tiny $_" for $Capture::Tiny::VERSION;
-}
-
-SKIP:
-{
+SKIP: {
  my $count = 1;
 
  my $count = 1;
 
- skip 'No working Capture::Tiny is installed'=> $count unless $has_capture_tiny;
-
- my $output = Capture::Tiny::capture_merged(sub { run_perl <<' CODE' });
+ my ($stat, $out, $err) = capture_perl <<' CODE';
 use Variable::Magic qw<wizard cast>; { BEGIN { $^H |= 0x020000; cast %^H, wizard free => sub { die q[cucumber] } } }
  CODE
 use Variable::Magic qw<wizard cast>; { BEGIN { $^H |= 0x020000; cast %^H, wizard free => sub { die q[cucumber] } } }
  CODE
- skip 'Test code didn\'t run properly' => $count unless defined $output;
- like $output, expect('cucumber', '-e', "\nExecution(?s:.*)"),
-                  'die in free callback at compile time and not in eval string';
+ skip CAPTURE_PERL_FAILED($out) => $count unless defined $stat;
+ like $err, expect('cucumber', '-e', "\nExecution(?s:.*)"),
+            'die in free callback at compile time and not in eval string';
  --$count;
 }
 
  --$count;
 }
 
@@ -389,14 +363,13 @@ SKIP:
 {
  my $count = 1;
 
 {
  my $count = 1;
 
- skip 'No nice uvar magic for this perl'     => $count unless VMG_UVAR;
- skip 'No working Capture::Tiny is installed'=> $count unless $has_capture_tiny;
+ skip 'No nice uvar magic for this perl' => $count unless VMG_UVAR;
 
 
- my $output = Capture::Tiny::capture_merged(sub { run_perl <<' CODE' });
+ my ($stat, $out, $err) = capture_perl <<' CODE';
 use Variable::Magic qw<wizard cast>; BEGIN { cast %derp::, wizard fetch => sub { die q[raddish] } } derp::hlagh()
  CODE
 use Variable::Magic qw<wizard cast>; BEGIN { cast %derp::, wizard fetch => sub { die q[raddish] } } derp::hlagh()
  CODE
- skip 'Test code didn\'t run properly' => $count unless defined $output;
- like $output, expect('raddish', '-e', "\nExecution(?s:.*)"),
-               'die in free callback at compile time and not in eval string';
+ skip CAPTURE_PERL_FAILED($out) => $count unless defined $stat;
+ like $err, expect('raddish', '-e', "\nExecution(?s:.*)"),
+            'die in free callback at compile time and not in eval string';
  --$count;
 }
  --$count;
 }
index b8623c536b65a652df5b5d01fdb6e980f95e6ee5..0f37b40b6e3026eda9cb99dfb747276a4080aa71 100644 (file)
@@ -5,6 +5,19 @@ use warnings;
 
 use Config ();
 
 
 use Config ();
 
+=head1 NAME
+
+VPIT::TestHelpers
+
+=head1 SYNTAX
+
+    use VPIT::TestHelpers (
+     feature1 => \@feature1_args,
+     feature2 => \@feature2_args,
+    );
+
+=cut
+
 sub export_to_pkg {
  my ($subs, $pkg) = @_;
 
 sub export_to_pkg {
  my ($subs, $pkg) = @_;
 
@@ -16,16 +29,31 @@ sub export_to_pkg {
  return 1;
 }
 
  return 1;
 }
 
+sub sanitize_prefix {
+ my $prefix = shift;
+
+ if (defined $prefix) {
+  if (length $prefix and $prefix !~ /_$/) {
+   $prefix .= '_';
+  }
+ } else {
+  $prefix = '';
+ }
+
+ return $prefix;
+}
+
 my %default_exports = (
  load_or_skip     => \&load_or_skip,
  load_or_skip_all => \&load_or_skip_all,
 my %default_exports = (
  load_or_skip     => \&load_or_skip,
  load_or_skip_all => \&load_or_skip_all,
- run_perl         => \&run_perl,
  skip_all         => \&skip_all,
 );
 
 my %features = (
  skip_all         => \&skip_all,
 );
 
 my %features = (
- threads => \&init_threads,
- usleep  => \&init_usleep,
+ threads  => \&init_threads,
+ usleep   => \&init_usleep,
+ run_perl => \&init_run_perl,
+ capture  => \&init_capture,
 );
 
 sub import {
 );
 
 sub import {
@@ -141,12 +169,54 @@ sub load_or_skip_all {
  return $loaded;
 }
 
  return $loaded;
 }
 
-sub run_perl {
- my $code = shift;
+=head1 FEATURES
 
 
- if ($code =~ /"/) {
-  die 'Double quotes in evaluated code are not portable';
- }
+=head2 C<run_perl>
+
+=over 4
+
+=item *
+
+Import :
+
+    use VPIT::TestHelpers run_perl => [ $p ]
+
+where :
+
+=over 8
+
+=item -
+
+C<$p> is prefixed to the constants exported by this feature (defaults to C<''>).
+
+=back
+
+=item *
+
+Dependencies : none
+
+=item *
+
+Exports :
+
+=over 8
+
+=item -
+
+C<run_perl $code>
+
+=item -
+
+C<RUN_PERL_FAILED> (possibly prefixed by C<$p>)
+
+=back
+
+=back
+
+=cut
+
+sub fresh_perl_env (&) {
+ my $handler = shift;
 
  my ($SystemRoot, $PATH) = @ENV{qw<SystemRoot PATH>};
  my $ld_name  = $Config::Config{ldlibpthname};
 
  my ($SystemRoot, $PATH) = @ENV{qw<SystemRoot PATH>};
  my $ld_name  = $Config::Config{ldlibpthname};
@@ -165,9 +235,357 @@ sub run_perl {
   }
  }
 
   }
  }
 
- system { $perl } $perl, '-T', map("-I$_", @INC), '-e', $code;
+ return $handler->($perl, '-T', map("-I$_", @INC));
+}
+
+sub init_run_perl {
+ my $p = sanitize_prefix(shift);
+
+ return (
+  run_perl              => \&run_perl,
+  "${p}RUN_PERL_FAILED" => sub () { 'Could not execute perl subprocess' },
+ );
+}
+
+sub run_perl {
+ my $code = shift;
+
+ if ($code =~ /"/) {
+  die 'Double quotes in evaluated code are not portable';
+ }
+
+ fresh_perl_env {
+  my ($perl, @perl_args) = @_;
+  system { $perl } $perl, @perl_args, '-e', $code;
+ };
 }
 
 }
 
+=head2 C<capture>
+
+=over 4
+
+=item *
+
+Import :
+
+    use VPIT::TestHelpers capture => [ $p ];
+
+where :
+
+=over 8
+
+=item -
+
+C<$p> is prefixed to the constants exported by this feature (defaults to C<''>).
+
+=back
+
+=item *
+
+Dependencies :
+
+=over 8
+
+=item -
+
+Not VMS
+
+=item -
+
+L<IO::Handle>, L<IO::Select>, L<IPC::Open3>
+
+=item -
+
+On MSWin32 : L<Socket>
+
+=back
+
+=item *
+
+Exports :
+
+=over 8
+
+=item -
+
+C<capture @command>
+
+=item -
+
+C<CAPTURE_FAILED $details> (possibly prefixed by C<$p>)
+
+=item -
+
+C<capture_perl $code>
+
+=item -
+
+C<CAPTURE_PERL_FAILED $details> (possibly prefixed by C<$p>)
+
+=back
+
+=back
+
+=cut
+
+sub init_capture {
+ my $p = sanitize_prefix(shift);
+
+ skip_all 'Cannot capture output on VMS' if $^O eq 'VMS';
+
+ load_or_skip_all 'IO::Handle', '0', [ ];
+ load_or_skip_all 'IO::Select', '0', [ ];
+ load_or_skip_all 'IPC::Open3', '0', [ ];
+ if ($^O eq 'MSWin32') {
+  load_or_skip_all 'Socket', '0', [ ];
+ }
+
+ return (
+  capture                   => \&capture,
+  "${p}CAPTURE_FAILED"      => \&capture_failed_msg,
+  capture_perl              => \&capture_perl,
+  "${p}CAPTURE_PERL_FAILED" => \&capture_perl_failed_msg,
+ );
+}
+
+# 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 ($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, $is_threadsafe, $force_var
+    ];
+
+where :
+
+=over 8
+
+=item -
+
+C<$pkg> is the target package name to be used in error messages (defaults to C<'package'>) ;
+
+=item -
+
+C<$is_threadsafe> is a boolean telling whether the target module is thread-safe (not tested if 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<threads> 1.67
+
+=item -
+
+L<threads::shared> 1.14
+
+=item -
+
+L<Test::Leaner>
+
+=back
+
+=item *
+
+Exports :
+
+=over 8
+
+=item -
+
+C<spawn $coderef>
+
+=back
+
+=back
+
+=cut
+
 sub init_threads {
  my ($pkg, $threadsafe, $force_var) = @_;
 
 sub init_threads {
  my ($pkg, $threadsafe, $force_var) = @_;
 
@@ -196,6 +614,48 @@ sub init_threads {
  return spawn => \&spawn;
 }
 
  return spawn => \&spawn;
 }
 
+sub spawn {
+ local $@;
+ my @diag;
+ my $thread = eval {
+  local $SIG{__WARN__} = sub { push @diag, "Thread creation warning: @_" };
+  threads->create(@_);
+ };
+ push @diag, "Thread creation error: $@" if $@;
+ diag @diag;
+ return $thread ? $thread : ();
+}
+
+=head2 C<usleep>
+
+=over 4
+
+=item *
+
+Import :
+
+    use VPIT::TestHelpers 'usleep'
+
+=item *
+
+Dependencies : none
+
+=item *
+
+Exports :
+
+=over 8
+
+=item -
+
+C<usleep $microseconds>
+
+=back
+
+=back
+
+=cut
+
 sub init_usleep {
  my $usleep;
 
 sub init_usleep {
  my $usleep;
 
@@ -206,7 +666,7 @@ sub init_usleep {
  } else {
   diag 'Using fallback usleep()';
   $usleep = sub {
  } else {
   diag 'Using fallback usleep()';
   $usleep = sub {
-   my $s = int($_[0] / 2.5e5);
+   my $s = int($_[0] / 1e6);
    sleep $s if $s;
   };
  }
    sleep $s if $s;
   };
  }
@@ -214,17 +674,18 @@ sub init_usleep {
  return usleep => $usleep;
 }
 
  return usleep => $usleep;
 }
 
-sub spawn {
- local $@;
- my @diag;
- my $thread = eval {
-  local $SIG{__WARN__} = sub { push @diag, "Thread creation warning: @_" };
-  threads->create(@_);
- };
- push @diag, "Thread creation error: $@" if $@;
- diag @diag;
- return $thread ? $thread : ();
-}
+=head1 CLASSES
+
+=head2 C<VPIT::TestHelpers::Guard>
+
+Syntax :
+
+    {
+     my $guard = VPIT::TestHelpers::Guard->new($coderef);
+     ...
+    } # $codref called here
+
+=cut
 
 package VPIT::TestHelpers::Guard;
 
 
 package VPIT::TestHelpers::Guard;
 
@@ -236,4 +697,16 @@ sub new {
 
 sub DESTROY { $_[0]->{code}->() }
 
 
 sub DESTROY { $_[0]->{code}->() }
 
+=head1 AUTHOR
+
+Vincent Pit, C<< <perl at profvince.com> >>, L<http://www.profvince.com>.
+
+=head1 COPYRIGHT & LICENSE
+
+Copyright 2012,2013,2014,2015 Vincent Pit, all rights reserved.
+
+This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
+
+=cut
+
 1;
 1;