File Coverage

blib/lib/Test/Trap/Builder/SystemSafe.pm
Criterion Covered Total %
statement 111 111 100.0
branch 33 34 97.0
condition 3 5 80.0
subroutine 13 13 100.0
pod n/a
total 160 163 98.7


line stmt bran cond sub pod time code
1             package Test::Trap::Builder::SystemSafe;
2              
3 27     27   553925 use version; $VERSION = qv('0.3.3');
  27         11794  
  27         188  
4              
5 27     27   2600 use strict;
  27         79  
  27         703  
6 27     27   173 use warnings;
  27         67  
  27         921  
7 27     27   2950 use Test::Trap::Builder;
  27         80  
  27         1458  
8 27     27   1772 use File::Temp qw( tempfile );
  27         47307  
  27         1633  
9 27     27   226 use IO::Handle;
  27         84  
  27         1552  
10              
11             ########
12             #
13             # I can no longer (easily?) install Devel::Cover on 5.6.2, so silence the coverage report:
14             #
15             # uncoverable condition right
16             # uncoverable condition false
17 27   50 27   213 use constant GOTPERLIO => (eval "use PerlIO (); 1" || 0);
  27     27   82  
  27         2009  
  27         224  
  27         83  
  27         307  
18              
19             sub import {
20 64     64   233 shift; # package name
21 64 100       284 my $strategy_name = @_ ? shift : 'systemsafe';
22 64 100       271 my $strategy_option = @_ ? shift : {};
23 64         862 Test::Trap::Builder->capture_strategy( $strategy_name => $_ ) for sub {
24 159     159   369 my $self = shift;
25 159         524 my ($name, $fileno, $globref) = @_;
26 159         491 my $pid = $$;
27 159 100 66     828 if (tied *$globref or $fileno < 0) {
28 1         12 $self->Exception("SystemSafe only works with real file descriptors; aborting");
29             }
30 158         330 my ($fh, $file) = do {
31 158         8176 local ($!, $^E);
32 158         905 tempfile( UNLINK => 1 ); # XXX: Test?
33             };
34 158         74896 my ($fh_keeper, $autoflush_keeper, @io_layers, @restore_io_layers);
35 158         829 my $Die = $self->ExceptionFunction;
36 158         673 for my $buffer ($self->{$name}) {
37 158         1547 $self->Teardown($_) for sub {
38 154         1066 local ($!, $^E);
39 154 100       602 if ($pid == $$) {
40             # this process opened it, so it gets to collect the contents:
41 152         602 local $/;
42 152         5838 $buffer .= $fh->getline;
43 152         10061 close $fh; # don't leak this one either!
44 152         131826 unlink $file;
45             }
46 154         3552 close *$globref;
47 154 100       632 return unless $fh_keeper;
48             # close and reopen the file to the keeper!
49 152         483 my $fno = fileno $fh_keeper;
50             _close_reopen( $Die, $globref, $fileno, ">&$fno",
51             sub {
52 2         62 close $fh_keeper;
53 2         44 sprintf "Cannot dup '%s' for %s: '%s'",
54             $fno, $name, $!;
55             },
56 152         1713 );
57 147         967 close $fh_keeper; # another potential leak, I suppose.
58 147         876 $globref->autoflush($autoflush_keeper);
59             IO_LAYERS: {
60 147         7189 GOTPERLIO or last IO_LAYERS;
  147         268  
61 147         611 local($!, $^E);
62 147         439 binmode *$globref;
63 147         530 my @tmp = @restore_io_layers;
64 147 50       1796 $_ eq $tmp[0] ? shift @tmp : last for PerlIO::get_layers(*$globref);
65 147         1071 binmode *$globref, $_ for @tmp;
66             }
67             };
68             }
69 158         606 binmode $fh; # superfluous?
70             {
71 158         302 local ($!, $^E);
  158         892  
72 158 100       3152 open $fh_keeper, ">&$fileno"
73             or $self->Exception("Cannot dup '$fileno' for $name: '$!'");
74             }
75             IO_LAYERS: {
76 156         880 GOTPERLIO or last IO_LAYERS;
  156         301  
77 156         614 local($!, $^E);
78 156         1277 @restore_io_layers = PerlIO::get_layers(*$globref, output => 1);
79 156 100       611 if ($strategy_option->{preserve_io_layers}) {
80 50         168 @io_layers = @restore_io_layers;
81             }
82 156 100       664 if ($strategy_option->{io_layers}) {
83 18         74 push @io_layers, $strategy_option->{io_layers};
84             }
85             }
86 156         762 $autoflush_keeper = $globref->autoflush;
87             _close_reopen( $self->ExceptionFunction, $globref, $fileno, ">>$file",
88             sub {
89 2         92 sprintf "Cannot open %s for %s: '%s'",
90             $file, $name, $!;
91             },
92 156         8416 );
93             IO_LAYERS: {
94 149         931 GOTPERLIO or last IO_LAYERS;
  149         310  
95 149         825 local($!, $^E);
96 149         8605 for my $h (*$globref, $fh) {
97 298         1817 binmode $h;
98 298 100       1306 my @tmp = @io_layers or next;
99 124 100       1037 $_ eq $tmp[0] ? shift @tmp : last for PerlIO::get_layers($h);
100 124     1   793 binmode $h, $_ for @tmp;
  1         7  
  1         2  
  1         8  
101             }
102             }
103 149         924 $globref->autoflush(1);
104 149         7325 $self->Next;
105             };
106             }
107              
108             sub _close_reopen {
109 308     308   1121 my ($Die, $glob, $fno_want, $what, $err) = @_;
110 308         1687 local ($!, $^E);
111 308         1404 close *$glob;
112 308         635 my @fh;
113 308         567 while (1) {
114 27     27   274 no warnings 'io';
  27         86  
  27         6114  
115 326 100       5461 open *$glob, $what or $Die->($err->());
116 323         1756 my $fileno = fileno *$glob;
117 323 100       1347 last if $fileno == $fno_want;
118 27         119 close *$glob;
119 27 100       115 if ($fileno > $fno_want) {
120 6         45 $Die->("Cannot get the desired descriptor, '$fno_want' (could it be that it is fdopened and so still open?)");
121             }
122 21 100       114 if (grep{$fileno == fileno($_)}@fh) {
  3         30  
123 1         7 $Die->("Getting several files opened on fileno $fileno");
124             }
125 20 100       764 open my $fh, $what or $Die->($err->());
126 19 100       250 if (fileno($fh) != $fileno) {
127 1         14 $Die->("Getting fileno " . fileno($fh) . "; expecting $fileno");
128             }
129 18         91 push @fh, $fh;
130             }
131 296         1963 close $_ for @fh;
132             }
133              
134             1; # End of Test::Trap::Builder::SystemSafe
135              
136             __END__