File: SystemSafe.pm

package info (click to toggle)
libtest-trap-perl 0.2.2-1
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 256 kB
  • sloc: perl: 2,258; makefile: 2
file content (145 lines) | stat: -rw-r--r-- 4,285 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package Test::Trap::Builder::SystemSafe;

use version; $VERSION = qv('0.2.2');

use strict;
use warnings;
use Test::Trap::Builder;
use File::Temp qw( tempfile );
use IO::Handle;

sub import {
  Test::Trap::Builder->output_layer_backend( systemsafe => $_ ) for sub {
    my $self = shift;
    my ($name, $fileno, $globref) = @_;
    my $pid = $$;
    if (tied *$globref or $fileno < 0) {
      $self->Exception("SystemSafe only works with real file descriptors; aborting");
    }
    my ($fh, $file) = tempfile( UNLINK => 1 ); # XXX: Test?
    my ($fh_keeper, $autoflush_keeper);
    $self->Teardown($_) for sub {
      if ($pid == $$) {
	# this process opened it, so it gets to collect the contents:
	local $/;
	$self->{$name} .= $fh->getline;
	close $fh; # don't leak this one either!
        unlink $file;
      }
      close *$globref;
      return unless $fh_keeper;
      # close and reopen the file to the keeper!
      my $fno = fileno $fh_keeper;
      _close_reopen( $self, $globref, $fileno, ">&$fno",
		     sub {
		       close $fh_keeper;
		       sprintf "Cannot dup '%s' for %s: '%s'",
			 $fno, $name, $!;
		     },
		   );
      close $fh_keeper; # another potential leak, I suppose.
      $globref->autoflush($autoflush_keeper);
    };
    binmode $fh; # superfluos?
    open $fh_keeper, ">&$fileno"
      or $self->Exception("Cannot dup '$fileno' for $name: '$!'");
    $autoflush_keeper = $globref->autoflush;
    _close_reopen( $self, $globref, $fileno, ">>$file",
		   sub {
		     sprintf "Cannot open %s for %s: '%s'",
		       $file, $name, $!;
		   },
		 );
    binmode *$globref; # must write with the same mode as we read.
    $globref->autoflush(1);
    $self->Next;
  };
}

sub _close_reopen {
  my ($result, $glob, $fno_want, $what, $err) = @_;
  close *$glob;
  my @fh;
  while (1) {
    no warnings 'io';
    open *$glob, $what or $result->Exception($err->());
    my $fileno = fileno *$glob;
    last if $fileno == $fno_want;
    close *$glob;
    if ($fileno > $fno_want) {
      $result->Exception("Cannot get the desired descriptor, '$fno_want' (could it be that it is fdopened and so still open?)");
    }
    if (grep{$fileno == fileno($_)}@fh) {
      $result->Exception("Getting several files opened on fileno $fileno");
    }
    open my $fh, $what or $result->Exception($err->());
    if (fileno($fh) != $fileno) {
      $result->Exception("Getting fileno " . fileno($fh) . "; expecting $fileno");
    }
    push @fh, $fh;
  }
  close $_ for @fh;
}

1; # End of Test::Trap::Builder::SystemSafe

__END__

=head1 NAME

Test::Trap::Builder::SystemSafe - "Safe" output layer backend using File::Temp

=head1 VERSION

Version 0.2.2

=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
therefore, unlike L<Test::Trap::Builder::TempFile> and
L<Test::Trap::Builder::PerlIO>, is able to trap output from forked-off
processes, including system().

See also L<Test::Trap> (:stdout and :stderr) and
L<Test::Trap::Builder> (output_layer).

=head1 CAVEATS

Using File::Temp, we need privileges to create tempfiles.

We need disk space for the output of every trap (it should clean up
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
in-memory file, it would not be availible 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
an exception in this case.

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.

Threads?  No idea.  It might even work correctly.

=head1 BUGS

Please report any bugs or feature requests directly to the author.

=head1 AUTHOR

Eirik Berg Hanssen, C<< <ebhanssen@allverden.no> >>

=head1 COPYRIGHT & LICENSE

Copyright 2006-2012 Eirik Berg Hanssen, All Rights Reserved.

This program is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=cut