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   601516 use version; $VERSION = qv('0.3.4');
  27         13535  
  27         158  
4              
5 27     27   2222 use strict;
  27         163  
  27         948  
6 27     27   133 use warnings;
  27         55  
  27         665  
7 27     27   3863 use Test::Trap::Builder;
  27         76  
  27         1371  
8 27     27   1736 use File::Temp qw( tempfile );
  27         38595  
  27         1259  
9 27     27   195 use IO::Handle;
  27         83  
  27         1321  
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   192 use constant GOTPERLIO => (eval "use PerlIO (); 1" || 0);
  27     27   65  
  27         1878  
  27         174  
  27         63  
  27         230  
18              
19             sub import {
20 64     64   577 shift; # package name
21 64 100       223 my $strategy_name = @_ ? shift : 'systemsafe';
22 64 100       162 my $strategy_option = @_ ? shift : {};
23 64         586 Test::Trap::Builder->capture_strategy( $strategy_name => $_ ) for sub {
24 159     159   289 my $self = shift;
25 159         419 my ($name, $fileno, $globref) = @_;
26 159         418 my $pid = $$;
27 159 100 66     788 if (tied *$globref or $fileno < 0) {
28 1         5 $self->Exception("SystemSafe only works with real file descriptors; aborting");
29             }
30 158         276 my ($fh, $file) = do {
31 158         882 local ($!, $^E);
32 158         712 tempfile( UNLINK => 1 ); # XXX: Test?
33             };
34 158         66563 my ($fh_keeper, $autoflush_keeper, @io_layers, @restore_io_layers);
35 158         638 my $Die = $self->ExceptionFunction;
36 158         464 for my $buffer ($self->{$name}) {
37 158         1122 $self->Teardown($_) for sub {
38 154         1174 local ($!, $^E);
39 154 100       518 if ($pid == $$) {
40             # this process opened it, so it gets to collect the contents:
41 152         570 local $/;
42 152         5780 $buffer .= $fh->getline;
43 152         10232 close $fh; # don't leak this one either!
44 152         5303 unlink $file;
45             }
46 154         3691 close *$globref;
47 154 100       633 return unless $fh_keeper;
48             # close and reopen the file to the keeper!
49 152         408 my $fno = fileno $fh_keeper;
50             _close_reopen( $Die, $globref, $fileno, ">&$fno",
51             sub {
52 2         59 close $fh_keeper;
53 2         30 sprintf "Cannot dup '%s' for %s: '%s'",
54             $fno, $name, $!;
55             },
56 152         1764 );
57 147         1585 close $fh_keeper; # another potential leak, I suppose.
58 147         935 $globref->autoflush($autoflush_keeper);
59             IO_LAYERS: {
60 147         6614 GOTPERLIO or last IO_LAYERS;
  147         236  
61 147         547 local($!, $^E);
62 147         392 binmode *$globref;
63 147         433 my @tmp = @restore_io_layers;
64 147 50       1325 $_ eq $tmp[0] ? shift @tmp : last for PerlIO::get_layers(*$globref);
65 147         905 binmode *$globref, $_ for @tmp;
66             }
67             };
68             }
69 158         512 binmode $fh; # superfluous?
70             {
71 158         256 local ($!, $^E);
  158         751  
72 158 100       4317 open $fh_keeper, ">&$fileno"
73             or $self->Exception("Cannot dup '$fileno' for $name: '$!'");
74             }
75             IO_LAYERS: {
76 156         883 GOTPERLIO or last IO_LAYERS;
  156         222  
77 156         572 local($!, $^E);
78 156         1480 @restore_io_layers = PerlIO::get_layers(*$globref, output => 1);
79 156 100       498 if ($strategy_option->{preserve_io_layers}) {
80 50         134 @io_layers = @restore_io_layers;
81             }
82 156 100       516 if ($strategy_option->{io_layers}) {
83 18         66 push @io_layers, $strategy_option->{io_layers};
84             }
85             }
86 156         644 $autoflush_keeper = $globref->autoflush;
87             _close_reopen( $self->ExceptionFunction, $globref, $fileno, ">>$file",
88             sub {
89 2         65 sprintf "Cannot open %s for %s: '%s'",
90             $file, $name, $!;
91             },
92 156         6874 );
93             IO_LAYERS: {
94 149         611 GOTPERLIO or last IO_LAYERS;
  149         239  
95 149         673 local($!, $^E);
96 149         377 for my $h (*$globref, $fh) {
97 298         1831 binmode $h;
98 298 100       1001 my @tmp = @io_layers or next;
99 124 100       782 $_ eq $tmp[0] ? shift @tmp : last for PerlIO::get_layers($h);
100 124     1   800 binmode $h, $_ for @tmp;
  1         7  
  1         2  
  1         8  
101             }
102             }
103 149         1026 $globref->autoflush(1);
104 149         5637 $self->Next;
105             };
106             }
107              
108             sub _close_reopen {
109 308     308   946 my ($Die, $glob, $fno_want, $what, $err) = @_;
110 308         1365 local ($!, $^E);
111 308         1599 close *$glob;
112 308         631 my @fh;
113 308         473 while (1) {
114 27     27   252 no warnings 'io';
  27         58  
  27         5778  
115 326 100       7571 open *$glob, $what or $Die->($err->());
116 323         1948 my $fileno = fileno *$glob;
117 323 100       999 last if $fileno == $fno_want;
118 27         165 close *$glob;
119 27 100       107 if ($fileno > $fno_want) {
120 6         39 $Die->("Cannot get the desired descriptor, '$fno_want' (could it be that it is fdopened and so still open?)");
121             }
122 21 100       119 if (grep{$fileno == fileno($_)}@fh) {
  3         31  
123 1         5 $Die->("Getting several files opened on fileno $fileno");
124             }
125 20 100       497 open my $fh, $what or $Die->($err->());
126 19 100       273 if (fileno($fh) != $fileno) {
127 1         8 $Die->("Getting fileno " . fileno($fh) . "; expecting $fileno");
128             }
129 18         61 push @fh, $fh;
130             }
131 296         1679 close $_ for @fh;
132             }
133              
134             1; # End of Test::Trap::Builder::SystemSafe
135              
136             __END__