The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
Changes 056
MANIFEST 04
META.json 66
META.yml 66
lib/Test/Trap/Builder/PerlIO.pm 66
lib/Test/Trap/Builder/SystemSafe.pm 1079
lib/Test/Trap/Builder/TempFile.pm 956
lib/Test/Trap/Builder.pm 2637
lib/Test/Trap.pm 40112
t/03-files-perlio.t 22
t/03-files-systemsafe-preserve.t 016
t/03-files-systemsafe.t 22
t/03-files-tempfile-preserve.t 016
t/03-files-tempfile.t 22
t/03-files.pl 1539
t/04-exit.t 22
t/05-import.t 66
t/06-layers.t 44
t/15-tempfile-options.t 089
t/16-systemsafe-options.t 089
20 files changed (This is a version diff) 136629
@@ -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)");
+  }
+}