@@ -1,5 +1,61 @@
Revision history for Test-Trap
+0.3.0 Thu Dec 18 21:57:51 CET 2014
+ This release, in brief:
+ - improves clarity through nomenclature:
+ * renames "(output layer) backend implementation" to
+ "(capture) strategy", for more standard nomenclature
+ (and less of a mouthful);
+ * renames "pseudo-layer" to "multi-layer" (if so
+ declared) or "non-trapping layer" (neither is a direct
+ analogue to PerlIO pseudo layers, so the nomenclature
+ was misleading);
+ - adds (import) options to the TempFile and SystemSafe capture
+ strategy factories, allowing for different ways to handle
+ PerlIO layers;
+ - using these for two new standard capture strategies,
+ "tempfile-preserve" and "systemsafe-preserve"; and
+ - fixes a bug in SystemSafe.
+ And, in more detail ...
+ Test::Trap::Builder:
+ - Changes method names per the nomenclature changes, leaving
+ back-compat aliases behind.
+ - Changes error message per the nomenclature changes. (No
+ back-compat possible, sorry.)
+ - Updates the documentation.
+ Test::Trap::Builder::{TempFile,SystemSafe}:
+ - Import now takes arguments:
+ strategy name (default {"tempfile","systemsafe"}); and
+ strategy options (default empty hash).
+ - The following options are supported:
+ preserve_io_layers (boolean; default false); and
+ io_layers (colon-separated string; default unset).
+ Test::Trap::Builder::SystemSafe:
+ - Fixes a bug where the original perlio layers were not
+ restored after the trap was sprung.
+ Test::Trap::Builder::{PerlIO,TempFile,SystemSafe}:
+ - Updates the code in accordance with Test::Trap::Builder
+ changes; see above.
+ - Updates the documentation.
+ Test::Trap:
+ - Imports new capture strategy, "tempfile-preserve", from
+ TempFile with option preserve_io_layers.
+ - Imports new capture strategy, "systemsafe-preserve", from
+ SystemSafe with option preserve_io_layers.
+ - Updates the code in accordance with Test::Trap::Builder
+ changes; see above.
+ - Updates the documentation.
+ Tests:
+ - changes variable names and comments in accordance with
+ nomenclature changes;
+ - adds tests for the no-restore bug in t/03-files.pl (hence
+ for each tested strategy);
+ - adds t/03-files-{tempfile,systemsafe}-preserve.t to run the
+ basic tests against the new capture strategies;
+ - accounts for the changed error message in t/06-layers.t;
+ - adds t/{15-tempfile,16-systemsafe}-options.t to check
+ capture strategies of varying options.
+
0.2.5 Sun Nov 16 18:31:42 CET 2014
This release localizes $! (ERRNO) for internal operations that
change it, as suggested by Felipe Gasper. For the same
@@ -14,7 +14,9 @@ t/00-load.t
t/01-basic.t
t/02-reentrant.t
t/03-files-perlio.t
+t/03-files-systemsafe-preserve.t
t/03-files-systemsafe.t
+t/03-files-tempfile-preserve.t
t/03-files-tempfile.t
t/03-files.pl
t/04-exit.t
@@ -28,6 +30,8 @@ t/11-systemsafe-basic.PL
t/12-systemsafe-errors.t
t/13-regressions.t
t/14-leaks.t
+t/15-tempfile-options.t
+t/16-systemsafe-options.t
t/99-coverage.t
xt/author/pod-coverage.t
xt/author/pod.t
@@ -42,23 +42,23 @@
"provides" : {
"Test::Trap" : {
"file" : "lib/Test/Trap.pm",
- "version" : "v0.2.5"
+ "version" : "v0.3.0"
},
"Test::Trap::Builder" : {
"file" : "lib/Test/Trap/Builder.pm",
- "version" : "v0.2.5"
+ "version" : "v0.3.0"
},
"Test::Trap::Builder::PerlIO" : {
"file" : "lib/Test/Trap/Builder/PerlIO.pm",
- "version" : "v0.2.5"
+ "version" : "v0.3.0"
},
"Test::Trap::Builder::SystemSafe" : {
"file" : "lib/Test/Trap/Builder/SystemSafe.pm",
- "version" : "v0.2.5"
+ "version" : "v0.3.0"
},
"Test::Trap::Builder::TempFile" : {
"file" : "lib/Test/Trap/Builder/TempFile.pm",
- "version" : "v0.2.5"
+ "version" : "v0.3.0"
}
},
"release_status" : "stable",
@@ -67,5 +67,5 @@
"http://dev.perl.org/licenses/"
]
},
- "version" : "v0.2.5"
+ "version" : "v0.3.0"
}
@@ -15,19 +15,19 @@ name: Test-Trap
provides:
Test::Trap:
file: lib/Test/Trap.pm
- version: v0.2.5
+ version: v0.3.0
Test::Trap::Builder:
file: lib/Test/Trap/Builder.pm
- version: v0.2.5
+ version: v0.3.0
Test::Trap::Builder::PerlIO:
file: lib/Test/Trap/Builder/PerlIO.pm
- version: v0.2.5
+ version: v0.3.0
Test::Trap::Builder::SystemSafe:
file: lib/Test/Trap/Builder/SystemSafe.pm
- version: v0.2.5
+ version: v0.3.0
Test::Trap::Builder::TempFile:
file: lib/Test/Trap/Builder/TempFile.pm
- version: v0.2.5
+ version: v0.3.0
requires:
Carp: 0
Data::Dump: 0
@@ -46,4 +46,4 @@ requires:
warnings: 0
resources:
license: http://dev.perl.org/licenses/
-version: v0.2.5
+version: v0.3.0
@@ -1,6 +1,6 @@
package Test::Trap::Builder::PerlIO;
-use version; $VERSION = qv('0.2.5');
+use version; $VERSION = qv('0.3.0');
use strict;
use warnings;
@@ -8,7 +8,7 @@ use Test::Trap::Builder;
use PerlIO 'scalar';
sub import {
- Test::Trap::Builder->output_layer_backend( perlio => $_ ) for sub {
+ Test::Trap::Builder->capture_strategy( perlio => $_ ) for sub {
my $self = shift;
my ($name, $fileno, $globref) = @_;
local *$globref;
@@ -26,17 +26,17 @@ __END__
=head1 NAME
-Test::Trap::Builder::PerlIO - Output layer backend using PerlIO::scalar
+Test::Trap::Builder::PerlIO - Capture strategy using PerlIO::scalar
=head1 VERSION
-Version 0.2.5
+Version 0.3.0
=head1 DESCRIPTION
-This module provides an implementation I<perlio>, based on
+This module provides a capture strategy I<perlio>, based on
PerlIO::scalar, for the trap's output layers. Note that you may
-specify different implementations for each output layer on the trap.
+specify different strategies for each output layer on the trap.
See also L<Test::Trap> (:stdout and :stderr) and
L<Test::Trap::Builder> (output_layer).
@@ -1,6 +1,6 @@
package Test::Trap::Builder::SystemSafe;
-use version; $VERSION = qv('0.2.5');
+use version; $VERSION = qv('0.3.0');
use strict;
use warnings;
@@ -8,8 +8,19 @@ use Test::Trap::Builder;
use File::Temp qw( tempfile );
use IO::Handle;
+########
+#
+# I can no longer (easily?) install Devel::Cover on 5.6.2, so silence the coverage report:
+#
+# uncoverable condition right
+# uncoverable condition false
+use constant GOTPERLIO => (eval "use PerlIO (); 1" || 0);
+
sub import {
- Test::Trap::Builder->output_layer_backend( systemsafe => $_ ) for sub {
+ shift; # package name
+ my $strategy_name = @_ ? shift : 'systemsafe';
+ my $strategy_option = @_ ? shift : {};
+ Test::Trap::Builder->capture_strategy( $strategy_name => $_ ) for sub {
my $self = shift;
my ($name, $fileno, $globref) = @_;
my $pid = $$;
@@ -20,7 +31,7 @@ sub import {
local ($!, $^E);
tempfile( UNLINK => 1 ); # XXX: Test?
};
- my ($fh_keeper, $autoflush_keeper);
+ my ($fh_keeper, $autoflush_keeper, @io_layers, @restore_io_layers);
my $Die = $self->ExceptionFunction;
for my $buffer ($self->{$name}) {
$self->Teardown($_) for sub {
@@ -45,6 +56,14 @@ sub import {
);
close $fh_keeper; # another potential leak, I suppose.
$globref->autoflush($autoflush_keeper);
+ IO_LAYERS: {
+ GOTPERLIO or last IO_LAYERS;
+ local($!, $^E);
+ binmode *$globref;
+ my @tmp = @restore_io_layers;
+ $_ eq $tmp[0] ? shift @tmp : last for PerlIO::get_layers(*$globref);
+ binmode *$globref, $_ for @tmp;
+ }
};
}
binmode $fh; # superfluous?
@@ -53,6 +72,17 @@ sub import {
open $fh_keeper, ">&$fileno"
or $self->Exception("Cannot dup '$fileno' for $name: '$!'");
}
+ IO_LAYERS: {
+ GOTPERLIO or last IO_LAYERS;
+ local($!, $^E);
+ @restore_io_layers = PerlIO::get_layers(*$globref, output => 1);
+ if ($strategy_option->{preserve_io_layers}) {
+ @io_layers = @restore_io_layers;
+ }
+ if ($strategy_option->{io_layers}) {
+ push @io_layers, $strategy_option->{io_layers};
+ }
+ }
$autoflush_keeper = $globref->autoflush;
_close_reopen( $self->ExceptionFunction, $globref, $fileno, ">>$file",
sub {
@@ -60,7 +90,16 @@ sub import {
$file, $name, $!;
},
);
- binmode *$globref; # must write with the same mode as we read.
+ IO_LAYERS: {
+ GOTPERLIO or last IO_LAYERS;
+ local($!, $^E);
+ for my $h (*$globref, $fh) {
+ binmode $h;
+ my @tmp = @io_layers or next;
+ $_ eq $tmp[0] ? shift @tmp : last for PerlIO::get_layers($h);
+ binmode $h, $_ for @tmp;
+ }
+ }
$globref->autoflush(1);
$self->Next;
};
@@ -98,24 +137,50 @@ __END__
=head1 NAME
-Test::Trap::Builder::SystemSafe - "Safe" output layer backend using File::Temp
+Test::Trap::Builder::SystemSafe - "Safe" capture strategies using File::Temp
=head1 VERSION
-Version 0.2.5
+Version 0.3.0
=head1 DESCRIPTION
-This module provides an implementation I<systemsafe>, based on
-File::Temp, for the trap's output layers. This implementation insists
-on reopening the output file handles with the same descriptors, and
+This module provides capture strategies I<systemsafe>, based on
+File::Temp, for the trap's output layers. These strategies insists on
+reopening the output file handles with the same descriptors, and
therefore, unlike L<Test::Trap::Builder::TempFile> and
L<Test::Trap::Builder::PerlIO>, is able to trap output from forked-off
processes, including system().
+The import accepts a name (as a string; default I<systemsafe>) and
+options (as a hashref; by default empty), and registers a capture
+strategy with that name and a variant implementation based on the
+options.
+
+Note that you may specify different strategies for each output layer
+on the trap.
+
See also L<Test::Trap> (:stdout and :stderr) and
L<Test::Trap::Builder> (output_layer).
+=head1 OPTIONS
+
+The following options are recognized:
+
+=head2 preserve_io_layers
+
+A boolean, indicating whether to apply to the handles writing to and
+reading from the tempfile, the same perlio layers as are found on the
+to-be-trapped output handle.
+
+=head2 io_layers
+
+A colon-separated string representing perlio layers to be applied to
+the handles writing to and reading from the tempfile.
+
+If the I<preserve_io_layers> option is set, these perlio layers will
+be applied on top of the original (preserved) perlio layers.
+
=head1 CAVEATS
Using File::Temp, we need privileges to create tempfiles.
@@ -126,7 +191,7 @@ after the trap is sprung).
Disk access may be slow -- certainly compared to the in-memory files
of PerlIO.
-If the file handle we try to trap using this backend is on an
+If the file handle we try to trap using this strategy is on an
in-memory file, it would not be available to other processes in any
case. Rather than change the semantics of the trapped code or
silently fail to trap output from forked-off processes, we just raise
@@ -136,6 +201,10 @@ If there is another file handle with the same descriptor (f ex after
an C<< open OTHER, '>&=', THIS >>), we can't get that file descriptor.
Rather than silently fail, we again raise an exception.
+If the options specify (explicitly or via preserve on handles with)
+perlio custom layers, they may (or may not) fail to apply to the
+tempfile read and write handles.
+
Threads? No idea. It might even work correctly.
=head1 BUGS
@@ -1,6 +1,6 @@
package Test::Trap::Builder::TempFile;
-use version; $VERSION = qv('0.2.5');
+use version; $VERSION = qv('0.3.0');
use strict;
use warnings;
@@ -9,7 +9,10 @@ use File::Temp qw( tempfile );
use Test::Trap::Builder;
sub import {
- Test::Trap::Builder->output_layer_backend( tempfile => $_ ) for sub {
+ shift; # package name
+ my $strategy_name = @_ ? shift : 'tempfile';
+ my $strategy_option = @_ ? shift : {};
+ Test::Trap::Builder->capture_strategy( $strategy_name => $_ ) for sub {
my $self = shift;
my ($name, $fileno, $globref) = @_;
my $pid = $$;
@@ -29,14 +32,29 @@ sub import {
unlink $file;
};
}
- binmode $fh; # superfluous?
+ my @io_layers;
+ IO_LAYERS: {
+ local($!, $^E);
+ if ($strategy_option->{preserve_io_layers}) {
+ @io_layers = PerlIO::get_layers(*$globref, output => 1);
+ }
+ if ($strategy_option->{io_layers}) {
+ push @io_layers, $strategy_option->{io_layers};
+ }
+ binmode $fh; # set the perlio layers for reading:
+ binmode $fh, $_ for @io_layers;
+ }
local *$globref;
{
no warnings 'io';
local ($!, $^E);
open *$globref, '>>', $file;
}
- binmode *$globref; # must write as we read.
+ IO_LAYERS: {
+ local($!, $^E);
+ binmode *$globref; # set the perlio layers for writing:
+ binmode *$globref, $_ for @io_layers;
+ }
*$globref->autoflush(1);
$self->Next;
};
@@ -48,21 +66,46 @@ __END__
=head1 NAME
-Test::Trap::Builder::TempFile - Output layer backend using File::Temp
+Test::Trap::Builder::TempFile - Capture strategies using File::Temp
=head1 VERSION
-Version 0.2.5
+Version 0.3.0
=head1 DESCRIPTION
-This module provides an implementation I<tempfile>, based on
-File::Temp, for the trap's output layers. Note that you may specify
-different implementations for each output layer on the trap.
+This module by default provides a capture strategy based on File::Temp
+for the trap's output layers.
+
+The import accepts a name (as a string; default I<tempfile>) and
+options (as a hashref; by default empty), and registers a capture
+strategy with that name and a variant implementation based on the
+options.
+
+Note that you may specify different strategies for each output layer
+on the trap.
See also L<Test::Trap> (:stdout and :stderr) and
L<Test::Trap::Builder> (output_layer).
+=head1 OPTIONS
+
+The following options are recognized:
+
+=head2 preserve_io_layers
+
+A boolean, indicating whether to apply to the handles writing to and
+reading from the tempfile, the same perlio layers as are found on the
+to-be-trapped output handle.
+
+=head2 io_layers
+
+A colon-separated string representing perlio layers to be applied to
+the handles writing to and reading from the tempfile.
+
+If the I<preserve_io_layers> option is set, these perlio layers will
+be applied on top of the original (preserved) perlio layers.
+
=head1 CAVEATS
Using File::Temp, we need privileges to create tempfiles.
@@ -73,6 +116,10 @@ after the trap is sprung).
Disk access may be slow -- certainly compared to the in-memory files
of PerlIO.
+If the options specify (explicitly or via preserve on handles with)
+perlio custom layers, they may (or may not) fail to apply to the
+tempfile read and write handles.
+
Threads? No idea. It might even work correctly.
=head1 BUGS
@@ -1,6 +1,6 @@
package Test::Trap::Builder;
-use version; $VERSION = qv('0.2.5');
+use version; $VERSION = qv('0.3.0');
use strict;
use warnings;
@@ -301,7 +301,7 @@ BEGIN { # Layer registration:
my $code = sub {
my $class = shift;
my ($arg) = @_;
- my $implementation = $self->first_output_layer_backend($arg);
+ my $strategy = $self->first_capture_strategy($arg);
return sub {
my $trap = shift;
$trap->{$name} = ''; # XXX: Encapsulation violation!
@@ -310,9 +310,9 @@ BEGIN { # Layer registration:
unless (tied *$globref or defined($fileno = fileno *$globref)) {
return $trap->Next;
}
- my $m = $implementation; # placate Devel::Cover:
- $m = $trap->Prop->{output_backend} unless $m;
- $m = $self->output_layer_backend('tempfile') unless $m;
+ my $m = $strategy; # placate Devel::Cover:
+ $m = $trap->Prop->{capture_strategy} unless $m;
+ $m = $self->capture_strategy('tempfile') unless $m;
$trap->$m($name, $fileno, $globref);
};
};
@@ -321,23 +321,26 @@ BEGIN { # Layer registration:
}
BEGIN {
- my %backend;
- sub output_layer_backend {
+ my %strategy;
+ # Backwards compatibility aliases; don't use:
+ *output_layer_backend = \&capture_strategy;
+ *first_output_layer_backend = \&first_capture_strategy;
+ sub capture_strategy {
my $this = shift;
- my ($name, $backend) = @_;
- $backend{$name} = $backend if $backend;
- return $backend{$name};
+ my ($name, $strategy) = @_;
+ $strategy{$name} = $strategy if $strategy;
+ return $strategy{$name};
}
- sub first_output_layer_backend {
+ sub first_capture_strategy {
my $self = shift;
my ($arg) = @_;
return unless $arg;
- my @backend = split /[,;]/, $arg;
- for (@backend) {
- my $implementation = $self->output_layer_backend($_);
- return $implementation if $implementation;
+ my @strategy = split /[,;]/, $arg;
+ for (@strategy) {
+ my $strategy = $self->capture_strategy($_);
+ return $strategy if $strategy;
}
- croak "No output layer implementation found for " . dump(@backend);
+ croak "No capture strategy found for " . dump(@strategy);
}
}
@@ -377,7 +380,7 @@ Test::Trap::Builder - Backend for building test traps
=head1 VERSION
-Version 0.2.5
+Version 0.3.0
=head1 SYNOPSIS
@@ -607,23 +610,31 @@ Registers (by I<NAME> and to the calling trapper) a layer for trapping
output on the file handle of the I<GLOBREF>, using I<NAME> also as the
attribute name.
-=head2 output_layer_backend NAME, [CODE]
+=head2 capture_strategy NAME, [CODE]
When called with two arguments, registers (by I<NAME> and globally) a
-backend for output trap layers. When called with a single argument,
-looks up and returns the backend registered by I<NAME> (or undef).
+strategy for output trap layers. When called with a single argument,
+looks up and returns the strategy registered by I<NAME> (or undef).
-When a layer using this backend is applied, the I<CODE> will be called
+When a layer using this strategy is applied, the I<CODE> will be called
on the trap object, with the layer name and the output handle's fileno
and globref as arguments.
-=head2 first_output_layer_backend SPEC
+=head2 output_layer_backend SPEC
+
+Back-compat alias of the above.
+
+=head2 first_capture_strategy SPEC
Where I<SPEC> is empty, just returns.
-Where I<SPEC> is a string of comma-or-semicolon separated backend
-names, runs through the names, returning the first implementation it
-finds. Dies if no implementation is found by any of these names.
+Where I<SPEC> is a string of comma-or-semicolon separated names, runs
+through the names, returning the first strategy it finds. Dies if no
+strategy is found by any of these names.
+
+=head2 first_output_layer_backend SPEC
+
+Back-compat alias of the above.
=head2 multi_layer NAME, LAYERS
@@ -788,7 +799,7 @@ sprung), and a I<cmp_ok> test method template:
The interface of this module is likely to remain somewhat in flux for
a while yet.
-The different implementations of output trap layers have their own
+The different strategies for output trap layers have their own
caveats; see L<Test::Trap::Builder::Tempfile>,
L<Test::Trap::Builder::PerlIO>, L<Test::Trap::Builder::SystemSafe>.
@@ -1,6 +1,6 @@
package Test::Trap;
-use version; $VERSION = qv('0.2.5');
+use version; $VERSION = qv('0.3.0');
use strict;
use warnings;
@@ -115,10 +115,13 @@ $B->layer(die => $_) for sub {
$B->output_layer( stdout => \*STDOUT );
$B->output_layer( stderr => \*STDERR );
BEGIN {
- # Make available some backends:
+ # Make available some capture strategies:
use Test::Trap::Builder::TempFile;
- eval q{ use Test::Trap::Builder::PerlIO }; # optional
- eval q{ use Test::Trap::Builder::SystemSafe }; # optional
+ use Test::Trap::Builder::TempFile 'tempfile-preserve' => { preserve_io_layers => 1 };
+ # optional capture strategies:
+ eval q{ use Test::Trap::Builder::PerlIO };
+ eval q{ use Test::Trap::Builder::SystemSafe };
+ eval q{ use Test::Trap::Builder::SystemSafe 'systemsafe-preserve' => { preserve_io_layers => 1 } };
}
# A simple layer for warnings:
@@ -146,11 +149,11 @@ $B->layer(warn => $_) for sub {
$self->Next;
};
-# Pseudo-layers:
+# Multi-layers:
$B->multi_layer(flow => qw/ raw die exit /);
$B->multi_layer(default => qw/ flow stdout stderr warn /);
-# Non-default pseudo-layers:
+# Non-default non-trapping layers:
$B->layer( void => $_ ) for sub {
my $self = shift;
undef $self->{wantarray};
@@ -174,9 +177,9 @@ $B->layer( on_fail => $_ ) for sub {
};
$B->layer( output => $_ ) for sub {
my $self = shift;
- my $implementation = eval { $B->first_output_layer_backend(@_) };
+ my $strategy = eval { $B->first_capture_strategy(@_) };
$self->Exception($@) if $@;
- $self->Prop('Test::Trap::Builder')->{output_backend} = $implementation;
+ $self->Prop('Test::Trap::Builder')->{capture_strategy} = $strategy;
$self->Next;
};
@@ -301,7 +304,7 @@ Test::Trap - Trap exit codes, exceptions, output, etc.
=head1 VERSION
-Version 0.2.5
+Version 0.3.0
=head1 SYNOPSIS
@@ -368,14 +371,15 @@ following layers are pre-defined by this module:
=head2 :raw
-The terminating layer, at which the processing of the layers stops,
-and the actual call to the user code is performed. On success, it
-collects the return value(s) in the appropriate context. Pushing the
-:raw layer on a trap will for most purposes remove all layers below.
+The only built-in terminating layer, at which the processing of the
+layers stops, and the actual call to the user code is performed. On
+success, it collects the return value(s) in the appropriate context.
+Pushing the :raw layer on a trap will for most purposes remove all
+layers below.
=head2 :die
-The layer emulating block eval, capturing normal exceptions.
+The layer emulating block eval, trapping normal exceptions.
=head2 :exit
@@ -385,8 +389,9 @@ CAVEATS below for more.)
=head2 :flow
-A pseudo-layer shortcut for :raw:die:exit. Since this includes :raw,
-pushing :flow on a trap will remove all layers below.
+A shortcut for :raw:die:exit (effectively pushing all three layers on
+the trap). Since this includes :raw, it is also terminating: Pushing
+:flow on a trap will effectively remove all layers below.
=head2 :stdout, :stderr
@@ -394,22 +399,22 @@ Layers trapping Perl output on STDOUT and STDERR, respectively.
=head2 :stdout(perlio), :stderr(perlio)
-As above, but specifying a backend implemented using PerlIO::scalar.
-If this backend is not available (typically if PerlIO is not), this is
-an error.
+As above, but specifying a capture strategy using PerlIO::scalar. If
+this strategy is not available (typically if PerlIO is not), this is
+an error. See L</"CAPTURE STRATEGIES">.
=head2 :stdout(tempfile), :stderr(tempfile)
-As above, but specifying a backend implemented using File::Temp. Note
-that this is the default implementation, unless the C<:output()> layer
-is used to set another default.
+As above, but specifying a capture strategy using File::Temp. Note
+that this is the default strategy, unless the C<:output()> layer is
+used to set another default. See L</"CAPTURE STRATEGIES">.
=head2 :stdout(a;b;c), :stderr(a,b,c)
(Either syntax, commas or semicolons, is permitted, as is any number
-of names in the list.) As above, but specifying the backend
-implementation by the first existing name among I<a>, I<b>, and I<c>.
-If no such implementation is available, this is an error.
+of names in the list.) As above, but specifying the capture strategy
+by the first existing name among I<a>, I<b>, and I<c>. If no such
+strategy is found, this is an error. See L</"CAPTURE STRATEGIES">.
=head2 :warn
@@ -419,26 +424,30 @@ the :stderr layer, be it above or below the :warn layer.)
=head2 :default
-A pseudo-layer short-cut for :raw:die:exit:stdout:stderr:warn. Since
-this includes :raw, pushing :default on a trap will remove all layers
-below. The other interesting property of :default is that it is what
-every trap starts with: In order not to include any of the six layers
-that make up :default, you need to push a terminating layer (such as
-:raw or :flow) on the trap.
+A short-cut for :raw:die:exit:stdout:stderr:warn (effectively pushing
+all six layers on the trap). Since this includes :raw, it is also
+terminating: Pushing :default on a trap will effectively remove all
+layers below.
+
+The other interesting property of :default is that it is what every
+trap starts with: In order not to include the six layers that make up
+:default, you need to push a terminating layer (such as :raw or :flow)
+on the trap.
=head2 :on_fail(m)
-A (non-default) pseudo-layer that installs a callback method (by name)
-I<m> to be run on test failures. To run the L</"diag_all"> method
-every time a test fails:
+A (non-default, non-trapping) layer that installs a callback method
+(by name) I<m> to be run on test failures. To run the L</"diag_all">
+method every time a test fails:
use Test::Trap qw/ :on_fail(diag_all) /;
=head2 :void, :scalar, :list
-Runs the trapped user code in void, scalar, or list context,
-respectively. (By default, the code is run in whatever context the
-trap itself is in.)
+These (non-default, non-trapping) layers will cause the trapped user
+code to be run in void, scalar, or list context, respectively. (By
+default, the trap will propagate context, that is, it will run the
+code in whatever context the trap itself is in.)
If more than one of these layers are pushed on the trap, the deepest
(that is, leftmost) takes precedence:
@@ -449,13 +458,76 @@ If more than one of these layers are pushed on the trap, the deepest
=head2 :output(a;b;c)
-A (non-default) pseudo-layers that sets the default backend layer
-implementation for any output trapping (C<:stdout>, C<:stderr>, or
-other similarly defined) layers already on the trap.
+A (non-default, non-trapping) layer that sets the default capture
+strategy for any output trapping (C<:stdout>, C<:stderr>, or other
+similarly defined) layers below iton the trap.
use Test::Trap qw/ :output(systemsafe) /;
trap { system echo => 'Hello Unix!' }; # trapped!
+ use Test::Trap qw/ :flow:stderr:output(systemsafe):stdout /;
+ trap { system echo => 'Hello Unix!' }; # *not* trapped!
+ trap { system q/ echo 'Hello Unix!' >&2 / }; # trapped!
+
+See L</"CAPTURE STRATEGIES">.
+
+=head1 CAPTURE STRATEGIES
+
+How output is trapped, depends on the capture strategy used. It is
+possible to register more (see L<Test::Trap::Builder>), but the
+following strategies are pre-defined by this module:
+
+=head2 tempfile
+
+The default capture strategy, provided by
+L<Test::Trap::Builder::TempFile>, in which output is temporarily
+redirected to (and read back from) a tempfile.
+
+=head2 tempfile-preserve
+
+A variant of the capture strategy provided by
+L<Test::Trap::Builder::TempFile>, in which the handles used to write
+to and read from the tempfile are both binmoded with the same perlio
+layers as the trapped output handle originally had.
+
+Caveat emptor: If the handle has perlio custom layers, they may (or
+may not) fail to apply to the tempfile read and write handles.
+
+=head2 systemsafe
+
+A capture strategy provided by L<Test::Trap::Builder::SystemSafe>,
+like the default strategy, except it outputs on file handles with the
+same file descriptors as the trapped output handle originally had, and
+so can be used to trap output from forked-off processes, including
+system().
+
+This strategy may be "safe" in relation to forked-off processes, but
+it is fragile. For one, it only works with handles that have "real"
+file descriptors. For another, it depends on the original file
+descriptors being available after closing. (If signal handlers or
+threads open files, they may well not be.) And it may fail in other
+ways. But in relation to forked-off processes, the other pre-defined
+strategies will silently fail to trap, as will similarly simple
+strategies. This one, when not crashing, will trap that output.
+
+=head2 systemsafe-preserve
+
+A variant of the capture strategy provided by
+L<Test::Trap::Builder::SystemSafe>, in which the handles used to write
+to and read from the tempfile are both binmoded with the same perlio
+layers as the trapped output handle originally had.
+
+Caveat emptor: If the handle has perlio custom layers, they may (or
+may not) fail to apply to the tempfile read and write handles.
+
+=head2 perlio
+
+A capture strategy provided by L<Test::Trap::Builder::PerlIO>, in
+which output is temporarily redirected to an in-memory file via
+PerlIO::scalar.
+
+If PerlIO::scalar is not available, neither is this strategy.
+
=head1 RESULT ACCESSORS
The following methods may be called on the trap objects after any trap
@@ -4,8 +4,8 @@
use strict;
use warnings;
-our $backend;
-$backend = 'PerlIO';
+our $strategy;
+$strategy = 'PerlIO';
use lib '.';
require 't/03-files.pl';
@@ -0,0 +1,16 @@
+#!perl -T
+# -*- mode: cperl ; compile-command: "cd .. ; ./Build ; prove -vb t/03-*systemsafe-preserve.t" -*-
+
+use strict;
+use warnings;
+
+our $strategy;
+$strategy = 'systemsafe-preserve';
+# Pull a fast one to run the horrible legacy tests for this layer as well, as long as PerlIO is available:
+if (eval qq{ use PerlIO (); 1 }) {
+ no strict 'refs';
+ *{"Test::Trap::Builder::$strategy\::import"} = sub {};
+}
+
+use lib '.';
+require 't/03-files.pl';
@@ -4,8 +4,8 @@
use strict;
use warnings;
-our $backend;
-$backend = 'SystemSafe';
+our $strategy;
+$strategy = 'SystemSafe';
use lib '.';
require 't/03-files.pl';
@@ -0,0 +1,16 @@
+#!perl -T
+# -*- mode: cperl ; compile-command: "cd .. ; ./Build ; prove -vb t/03-*tempfile-preserve.t" -*-
+
+use strict;
+use warnings;
+
+our $strategy;
+$strategy = 'tempfile-preserve';
+# Pull a fast one to run the horrible legacy tests for this layer as well, as long as PerlIO is available:
+if (eval qq{ use PerlIO (); 1 }) {
+ no strict 'refs';
+ *{"Test::Trap::Builder::$strategy\::import"} = sub {};
+}
+
+use lib '.';
+require 't/03-files.pl';
@@ -4,8 +4,8 @@
use strict;
use warnings;
-our $backend;
-$backend = 'TempFile';
+our $strategy;
+$strategy = 'TempFile';
use lib '.';
require 't/03-files.pl';
@@ -8,16 +8,16 @@ use Data::Dump qw(dump);
use strict;
use warnings;
-our $backend; # to be set in the requiring test script ...
+our $strategy; # to be set in the requiring test script ...
BEGIN {
- my $pkg = "Test::Trap::Builder::$backend";
+ my $pkg = "Test::Trap::Builder::$strategy";
local $@;
eval qq{ use $pkg };
if (exists &{"$pkg\::import"}) {
- plan tests => 1 + 6*10 + 5*3 + 13; # 10 runtests; 3 inner_tests; another bunch ...
+ plan tests => 1 + 6*10 + 5*3 + 17; # 10 runtests; 3 inner_tests; another bunch ...
}
else {
- plan skip_all => "$backend backend not supported; skipping";
+ plan skip_all => "$strategy strategy not supported; skipping";
}
}
@@ -31,7 +31,7 @@ BEGIN {
# properly restored on exit from a trap. Ouch.
BEGIN {
- use_ok( 'Test::Trap', '$T', lc ":flow:stdout($backend):stderr($backend):warn" );
+ use_ok( 'Test::Trap', '$T', lc ":flow:stdout($strategy):stderr($strategy):warn" );
}
STDERR: {
@@ -165,19 +165,43 @@ trap {
};
unlike $T->stderr, qr/, \S+ line 1\./, 'No "<$f> line ..." stuff, please';
+# regression test for preservation of PerlIO layers:
+SKIP: {
+ skip 'Lacking PerlIO', 4 unless eval "use PerlIO; 1";
+ my @io = PerlIO::get_layers(*STDOUT);
+ trap { binmode STDOUT, ':utf8' }; # or whatever, really
+ is_deeply( [PerlIO::get_layers(*STDOUT)], \@io, 'STDOUT still has the original layers.')
+ or diag(dump(\@io));
+ binmode STDOUT;
+ my @raw = PerlIO::get_layers(*STDOUT);
+ trap { binmode STDOUT, ':utf8' }; # or whatever, really
+ is_deeply( [PerlIO::get_layers(*STDOUT)], \@raw, 'STDOUT is still binmoded.')
+ or diag(dump([PerlIO::get_layers(*STDOUT)], \@raw));
+ binmode STDOUT, ':crlf';
+ my @crlf = PerlIO::get_layers(*STDOUT);
+ trap { binmode STDOUT, ':utf8' }; # or whatever, really
+ is_deeply( [PerlIO::get_layers(*STDOUT)], \@crlf, 'STDOUT still has the crlf layer(s).')
+ or diag(dump([PerlIO::get_layers(*STDOUT)], \@crlf));
+ binmode STDOUT;
+ my @tmp = @io;
+ $_ eq $tmp[0] ? shift @tmp : last for PerlIO::get_layers(*STDOUT);
+ binmode STDOUT, $_ for @tmp;
+ is_deeply( [PerlIO::get_layers(*STDOUT)], \@io, 'Sanity check: STDOUT now again has the original layers.')
+ or diag(dump([PerlIO::get_layers(*STDOUT)], \@io));
+}
+
# test the $! handling:
my $errnum = 11; # "Resource temporarily unavailable" locally -- sounds good :-P
my $errstring = do { local $! = $errnum; "$!" };
my $erros = do { local $! = $errnum; $^E };
-my ($errsym) = do { local $! = $errnum; grep { $!{$_} } keys %! };
+my ($errsym) = do { local $! = $errnum; grep { $!{$_} } keys(%!) };
for my $case ([Bare => sub { return 42 }], [Dying => sub { die 42 }], [Exiting => sub { exit 42 }]) {
- my ($type, $code);
local $! = $errnum;
- trap { $code->() };
- my ($sym) = grep { $!{$_} } keys %!;
- is $!+0, $errnum, "$type trap doesn't change errno (remains $errnum/$errstring)";
- is $^E, $erros, "$type trap doesn't change extended OS error (remains $erros)";
- is $sym, $errsym, "$type trap doesn't change the error symbol (remains $errsym)";
+ trap {};
+ my ($sym) = grep { $!{$_} } keys(%!);
+ is $!+0, $errnum, "$strategy trap doesn't change errno (remains $errnum/$errstring)";
+ is $^E, $erros, "$strategy trap doesn't change extended OS error (remains $erros)";
+ is $sym, $errsym, "$strategy trap doesn't change the error symbol (remains $errsym)";
}
{
@@ -186,9 +210,9 @@ for my $case ([Bare => sub { return 42 }], [Dying => sub { die 42 }], [Exiting =
$! = 0;
$^E = '';
};
- my ($sym) = grep { $!{$_} } keys %!;
- is $!+0, 0, "Errno-unsetting trap unsets errno (it's not localized)";
- is $^E, '', "Errno-unsetting trap unsets extended OS error (it's not localized)";
+ my ($sym) = grep { $!{$_} } keys(%!);
+ is $!+0, 0, "Errno-unsetting trap unsets errno (it's not localized)";
+ is $^E, '', "Errno-unsetting trap unsets extended OS error (it's not localized)";
is $sym, undef, "Errno-unsetting trap unsets the error symbol (it's not localized)";
}
@@ -26,7 +26,7 @@ BEGIN {
my $errnum = 11; # "Resource temporarily unavailable" locally -- sounds good :-P
my $errstring = do { local $! = $errnum; "$!" };
my $erros = do { local $! = $errnum; $^E };
-my ($errsym) = do { local $! = $errnum; grep { $!{$_} } keys %! };
+my ($errsym) = do { local $! = $errnum; grep { $!{$_} } keys(%!) };
$! = $errnum;
@@ -42,7 +42,7 @@ trap {
};
like( $trap->stderr, qr/^Subroutine (?:CORE::GLOBAL::)?exit redefined at \Q${\__FILE__} line/, 'Override warning' );
-my ($sym) = grep { $!{$_} } keys %!;
+my ($sym) = grep { $!{$_} } keys(%!);
is $!+0, $errnum, "These traps don't change errno (remains $errnum/$errstring)";
is $^E, $erros, "These traps don't change extended OS error (remains $erros)";
is $sym, $errsym, "These traps don't change the error symbol (remains $errsym)";
@@ -40,21 +40,21 @@ eval { Test::Trap->import(qw( test1 $T1 :stdout(perlio) )) };
like( $@,
$got{perlio} ?
qr/\A\z/ :
- qr/^\QNo output layer implementation found for "perlio" at ${\__FILE__} line/,
- 'Export of PerlIO implementation :stdout(perlio)',
+ qr/^\QNo capture strategy found for "perlio" at ${\__FILE__} line/,
+ 'Export of capture strategy :stdout(perlio)',
);
eval { Test::Trap->import(qw( test2 $T2 :stdout(nosuch;tempfile) )) };
like( $@,
$got{tempfile} ?
qr/\A\z/ :
- qr/^\QNo output layer implementation found for ("nosuch", "tempfile") at ${\__FILE__} line/,
- 'Export of PerlIO implementation :stdout(nosuch;tempfile)',
+ qr/^\QNo capture strategy found for ("nosuch", "tempfile") at ${\__FILE__} line/,
+ 'Export of capture strategy :stdout(nosuch;tempfile)',
);
eval { Test::Trap->import(qw( test2 $T2 :stdout(nosuch1;nosuch2) )) };
like( $@,
- qr/^\QNo output layer implementation found for ("nosuch1", "nosuch2") at ${\__FILE__} line/,
- 'Export of PerlIO implementation :stdout(nosuch1;nosuch2)',
+ qr/^\QNo capture strategy found for ("nosuch1", "nosuch2") at ${\__FILE__} line/,
+ 'Export of capture strategy:stdout(nosuch1;nosuch2)',
);
@@ -2,7 +2,7 @@
# -*- mode: cperl ; compile-command: "cd .. ; ./Build ; prove -vb t/06-*.t" -*-
BEGIN { $_ = defined && /(.*)/ && $1 for @ENV{qw/ TMPDIR TEMP TMP /} } # taint vs tempfile
-use Test::More tests => 4*15 + 4*5 + 3*6 + 5*13; # non-default standard layers + output backend + internal exceptions + exits
+use Test::More tests => 4*15 + 4*5 + 3*6 + 5*13; # non-default standard layers + capture strategies + internal exceptions + exits
use IO::Handle;
use File::Temp qw( tempfile );
use Data::Dump qw( dump );
@@ -111,13 +111,13 @@ TEST
}
# Test the new :output() layer:
-for my $case # layers backendlist useable
+for my $case # layers strategies useable
( [ Tempfile => [ ':output(tempfile)' ], '"tempfile"', 1 ],
[ Perlio => [ ':output(perlio)' ], '"perlio"', !!eval q{ use PerlIO 'scalar'; 1 } ],
[ Mixed => [ ':output(nosuch;perlio)' ], '("nosuch", "perlio")', !!eval q{ use PerlIO 'scalar'; 1 } ],
[ Badout => [ ':output(nosuch)' ], '"nosuch"', 0 ],
) {
- my ($name, $layer, $backendlist, $usable) = @$case;
+ my ($name, $layer, $strategies, $usable) = @$case;
eval sprintf <<'TEST', ($name) x 2 or diag "Error in $name eval: $@";
#line 1 (%s)
BEGIN {
@@ -131,7 +131,7 @@ for my $case # layers backendlist useable
$trap->return_is_deeply( ['foo'], "Trapped the STDOUT with $name" );
}
else {
- $trap->die_like( qr/^No output layer implementation found for \Q$backendlist/, "Died with $name" );
+ $trap->die_like( qr/^No capture strategy found for \Q$strategies/, "Died with $name" );
}
$trap->warn_is_deeply( [], 'No warnings' );
1;
@@ -0,0 +1,89 @@
+#!perl -T
+# -*- mode: cperl ; compile-command: "cd .. ; ./Build ; prove -vb t/15-*.t" -*-
+
+BEGIN {
+ $_ = defined && /(.*)/ && $1 for @ENV{qw/ TMPDIR TEMP TMP /}; # taint vs tempfile
+ use Test::More;
+ eval "use PerlIO ()";
+ plan skip_all => "PerlIO required for tempfile-preserve and other options" if $@;
+ eval "use Encode::Byte ()";
+ plan skip_all => "Encode::Byte required to test at least Latin-2" if $@;
+}
+
+use Test::More tests => 3*2*5;
+
+use strict;
+use warnings;
+
+# For compatibility with perl <= 5.8.8, :crlf must be applied before :utf8.
+use Test::Trap::Builder::TempFile utf8 => { io_layers => ':crlf:utf8' };
+use Test::Trap::Builder::TempFile both => { io_layers => ':crlf:utf8', preserve_io_layers => 1 };
+use Test::Trap::Builder::TempFile latin2 => { io_layers => ':encoding(iso-8859-2)' };
+use Test::Trap qw/ $basic basic :output(tempfile) /;
+use Test::Trap qw/ $preserve preserve :output(tempfile-preserve) /;
+use Test::Trap qw/ $utf8 utf8 :output(utf8) /;
+use Test::Trap qw/ $both both :output(both) /;
+use Test::Trap qw/ $latin2 latin2 :output(latin2) /;
+
+my @layers = qw(basic preserve utf8 both latin2);
+
+our($trap);
+sub trap(&);
+
+# Test 1: ł (l stroke); no messing with STDOUT
+for my $glob (@layers) {
+ no strict 'refs';
+ local *trap = *$glob;
+ trap { print "\x{142}" };
+ if ($glob =~ /utf8|both|latin2/) {
+ # it should work
+ $trap->stdout_is("\x{142}", "TempFile '$glob' strategy handles l stroke");
+ $trap->stderr_is('', "\t(no warning)");
+ }
+ else {
+ $trap->stdout_is("\xC5\x82", "TempFile '$glob' strategy doesn't handle l stroke");
+ $trap->stderr_like(qr/^Wide character in print.*$/, "\t(and warns)");
+ }
+}
+
+# Test 2: π (pi); STDOUT binmoded to utf8
+binmode STDOUT, ':raw:utf8';
+for my $glob (@layers) {
+ no strict 'refs';
+ local *trap = *$glob;
+ trap { print "\x{3C0}" };
+ if ($glob =~ /utf8|preserve|both/) {
+ # it should work
+ $trap->stdout_is("\x{3C0}", "TempFile '$glob' strategy handles pi");
+ $trap->stderr_is('', "\t(no warning)");
+ }
+ elsif ($glob eq 'latin2') {
+ $trap->stdout_like(qr/^\\x\{0?3c0\}\z/, "TempFile '$glob' strategy doesn't handle pi; falls back to \\x notation");
+ $trap->stderr_like(qr/^"\\x\{0?3c0\}" does not map to iso-8859-2 .*$/, "\t(and warns)");
+ }
+ else {
+ $trap->stdout_is("\xCF\x80", "TempFile '$glob' strategy doesn't handle pi");
+ $trap->stderr_like(qr/^Wide character in print.*$/, "\t(and warns)");
+ }
+}
+
+# Test 3: ‰\n% (per mille, newline, per cent); STDOUT binmoded to latin2
+binmode STDOUT, ':raw:encoding(iso-8859-2)';
+for my $glob (@layers) {
+ no strict 'refs';
+ local *trap = *$glob;
+ trap { print "\x{2030}\n%" };
+ if ($glob =~ /utf8/) {
+ # it should work
+ $trap->stdout_is("\x{2030}\n%", "TempFile '$glob' strategy handles per mille");
+ $trap->stderr_is('', "\t(no warning)");
+ }
+ elsif ($glob =~ /preserve|both|latin2/) {
+ $trap->stdout_is("\\x{2030}\n%", "TempFile '$glob' strategy doesn't handle per mille; falls back to \\x notation");
+ $trap->stderr_like(qr/^\Q"\x{2030}"\E does not map to iso-8859-2 .*$/, "\t(and warns)");
+ }
+ else {
+ $trap->stdout_is("\xE2\x80\xB0\n%", "TempFile '$glob' strategy doesn't handle per mille");
+ $trap->stderr_like(qr/^Wide character in print.*$/, "\t(and warns)");
+ }
+}
@@ -0,0 +1,89 @@
+#!perl -T
+# -*- mode: cperl ; compile-command: "cd .. ; ./Build ; prove -vb t/16-*.t" -*-
+
+BEGIN {
+ $_ = defined && /(.*)/ && $1 for @ENV{qw/ TMPDIR TEMP TMP /}; # taint vs tempfile
+ use Test::More;
+ eval "use PerlIO ()";
+ plan skip_all => "PerlIO required for systemsafe-preserve and other options" if $@;
+ eval "use Encode::Byte ()";
+ plan skip_all => "Encode::Byte required to test at least Latin-2" if $@;
+}
+
+use Test::More tests => 3*2*5;
+
+use strict;
+use warnings;
+
+# For compatibility with perl <= 5.8.8, :crlf must be applied before :utf8.
+use Test::Trap::Builder::SystemSafe utf8 => { io_layers => ':crlf:utf8' };
+use Test::Trap::Builder::SystemSafe both => { io_layers => ':crlf:utf8', preserve_io_layers => 1 };
+use Test::Trap::Builder::SystemSafe latin2 => { io_layers => ':encoding(iso-8859-2)' };
+use Test::Trap qw/ $basic basic :output(systemsafe) /;
+use Test::Trap qw/ $preserve preserve :output(systemsafe-preserve) /;
+use Test::Trap qw/ $utf8 utf8 :output(utf8) /;
+use Test::Trap qw/ $both both :output(both) /;
+use Test::Trap qw/ $latin2 latin2 :output(latin2) /;
+
+my @layers = qw(basic preserve utf8 both latin2);
+
+our($trap);
+sub trap(&);
+
+# Test 1: ł (l stroke); no messing with STDOUT
+for my $glob (@layers) {
+ no strict 'refs';
+ local *trap = *$glob;
+ trap { print "\x{142}" };
+ if ($glob =~ /utf8|both|latin2/) {
+ # it should work
+ $trap->stdout_is("\x{142}", "SystemSafe '$glob' strategy handles l stroke");
+ $trap->stderr_is('', "\t(no warning)");
+ }
+ else {
+ $trap->stdout_is("\xC5\x82", "SystemSafe '$glob' strategy doesn't handle l stroke");
+ $trap->stderr_like(qr/^Wide character in print.*$/, "\t(and warns)");
+ }
+}
+
+# Test 2: π (pi); STDOUT binmoded to utf8
+binmode STDOUT, ':raw:utf8';
+for my $glob (@layers) {
+ no strict 'refs';
+ local *trap = *$glob;
+ trap { print "\x{3C0}" };
+ if ($glob =~ /utf8|preserve|both/) {
+ # it should work
+ $trap->stdout_is("\x{3C0}", "SystemSafe '$glob' strategy handles pi");
+ $trap->stderr_is('', "\t(no warning)");
+ }
+ elsif ($glob eq 'latin2') {
+ $trap->stdout_like(qr/^\\x\{0?3c0\}\z/, "SystemSafe '$glob' strategy doesn't handle pi; falls back to \\x notation");
+ $trap->stderr_like(qr/^"\\x\{0?3c0\}" does not map to iso-8859-2 .*$/, "\t(and warns)");
+ }
+ else {
+ $trap->stdout_is("\xCF\x80", "SystemSafe '$glob' strategy doesn't handle pi");
+ $trap->stderr_like(qr/^Wide character in print.*$/, "\t(and warns)");
+ }
+}
+
+# Test 3: ‰\n% (per mille, newline, per cent); STDOUT binmoded to latin2
+binmode STDOUT, ':raw:encoding(iso-8859-2)';
+for my $glob (@layers) {
+ no strict 'refs';
+ local *trap = *$glob;
+ trap { print "\x{2030}\n%" };
+ if ($glob =~ /utf8/) {
+ # it should work
+ $trap->stdout_is("\x{2030}\n%", "SystemSafe '$glob' strategy handles per mille");
+ $trap->stderr_is('', "\t(no warning)");
+ }
+ elsif ($glob =~ /preserve|both|latin2/) {
+ $trap->stdout_is("\\x{2030}\n%", "SystemSafe '$glob' strategy doesn't handle per mille; falls back to \\x notation");
+ $trap->stderr_like(qr/^\Q"\x{2030}"\E does not map to iso-8859-2 .*$/, "\t(and warns)");
+ }
+ else {
+ $trap->stdout_is("\xE2\x80\xB0\n%", "SystemSafe '$glob' strategy doesn't handle per mille");
+ $trap->stderr_like(qr/^Wide character in print.*$/, "\t(and warns)");
+ }
+}