File Coverage

inc/Test/TCP.pm
Criterion Covered Total %
statement 33 102 32.3
branch 0 46 0.0
condition 0 9 0.0
subroutine 11 21 52.3
pod 7 8 87.5
total 51 186 27.4


line stmt bran cond sub pod time code
1             #line 1
2 2     2   6891 package Test::TCP;
  2         5  
  2         82  
3 2     2   12 use strict;
  2         4  
  2         67  
4 2     2   130 use warnings;
  2         6  
  2         135  
5             use 5.00800;
6 2     2   11 our $VERSION = '1.13';
  2         6  
  2         255  
7 2     2   9147 use base qw/Exporter/;
  2         48577  
  2         21  
8 2     2   3866 use IO::Socket::INET;
  2         63  
  2         26  
9 2     2   16 use Test::SharedFork 0.12;
  2         4  
  2         30  
10 2     2   13 use Test::More ();
  2         5  
  2         67  
11 2     2   2350 use Config;
  2         18544  
  2         16  
12 2     2   11693 use POSIX;
  2         5189  
  2         289  
13 2     2   99 use Time::HiRes ();
  2         4  
  2         2463  
14             use Carp ();
15              
16             our @EXPORT = qw/ empty_port test_tcp wait_port /;
17              
18             # process does not die when received SIGTERM, on win32.
19             my $TERMSIG = $^O eq 'MSWin32' ? 'KILL' : 'TERM';
20              
21             # get a empty port on 49152 .. 65535
22             # http://www.iana.org/assignments/port-numbers
23 0     0 0   sub empty_port {
24 0 0         my $port = do {
25 0           if (@_) {
26 0 0 0       my $p = $_[0];
27 0           $p = 49152 unless $p =~ /^[0-9]+$/ && $p < 49152;
28             $p;
29 0           } else {
30             50000 + int(rand()*1000);
31             }
32             };
33 0            
34 0 0         while ( $port++ < 60000 ) {
35 0 0         next if _check_port($port);
36             my $sock = IO::Socket::INET->new(
37             Listen => 5,
38             LocalAddr => '127.0.0.1',
39             LocalPort => $port,
40             Proto => 'tcp',
41             (($^O eq 'MSWin32') ? () : (ReuseAddr => 1)),
42 0 0         );
43             return $port if $sock;
44 0           }
45             die "empty port not found";
46             }
47              
48 0     0 1   sub test_tcp {
49 0           my %args = @_;
50 0 0         for my $k (qw/client server/) {
51             die "missing madatory parameter $k" unless exists $args{$k};
52 0   0       }
53             my $server = Test::TCP->new(
54             code => $args{server},
55             port => $args{port} || empty_port(),
56 0           );
57 0           $args{client}->($server->port, $server->pid);
58             undef $server; # make sure
59             }
60              
61 0     0     sub _check_port {
62             my ($port) = @_;
63 0            
64             my $remote = IO::Socket::INET->new(
65             Proto => 'tcp',
66             PeerAddr => '127.0.0.1',
67             PeerPort => $port,
68 0 0         );
69 0           if ($remote) {
70 0           close $remote;
71             return 1;
72             }
73 0           else {
74             return 0;
75             }
76             }
77              
78 0     0 1   sub wait_port {
79             my $port = shift;
80 0            
81 0           my $retry = 100;
82 0 0         while ( $retry-- ) {
83 0           return if _check_port($port);
84             Time::HiRes::sleep(0.1);
85 0           }
86             die "cannot open port: $port";
87             }
88              
89             # -------------------------------------------------------------------------
90             # OO-ish interface
91              
92 0     0 1   sub new {
93 0 0         my $class = shift;
  0            
94 0 0         my %args = @_==1 ? %{$_[0]} : @_;
95 0           Carp::croak("missing mandatory parameter 'code'") unless exists $args{code};
96             my $self = bless {
97             auto_start => 1,
98             _my_pid => $$,
99             %args,
100 0 0         }, $class;
101 0 0         $self->{port} = Test::TCP::empty_port() unless exists $self->{port};
102             $self->start()
103 0           if $self->{auto_start};
104             return $self;
105             }
106 0     0 1    
107 0     0 1   sub pid { $_[0]->{pid} }
108             sub port { $_[0]->{port} }
109              
110 0     0 1   sub start {
111 0 0         my $self = shift;
    0          
112             if ( my $pid = fork() ) {
113 0           # parent.
114 0           $self->{pid} = $pid;
115 0           Test::TCP::wait_port($self->port);
116             return;
117             } elsif ($pid == 0) {
118 0           # child process
119             $self->{code}->($self->port);
120 0 0         # should not reach here
121 0           if (kill 0, $self->{_my_pid}) { # warn only parent process still exists
122             warn("[Test::TCP] Child process does not block(PID: $$, PPID: $self->{_my_pid})");
123 0           }
124             exit 0;
125 0           } else {
126             die "fork failed: $!";
127             }
128             }
129              
130 0     0 1   sub stop {
131             my $self = shift;
132 0 0          
133 0 0         return unless defined $self->{pid};
134             return unless $self->{_my_pid} == $$;
135              
136             # This is a workaround for win32 fork emulation's bug.
137             #
138             # kill is inherently unsafe for pseudo-processes in Windows
139             # and the process calling kill(9, $pid) may be destabilized
140             # The call to Sleep will decrease the frequency of this problems
141             #
142             # SEE ALSO:
143             # http://www.gossamer-threads.com/lists/perl/porters/261805
144 0 0         # https://rt.cpan.org/Ticket/Display.html?id=67292
145             Win32::Sleep(0) if $^O eq "MSWin32"; # will relinquish the remainder of its time slice
146 0            
147             kill $TERMSIG => $self->{pid};
148 0 0          
149             Win32::Sleep(0) if $^O eq "MSWin32"; # will relinquish the remainder of its time slice
150              
151 0            
152 0           local $?; # waitpid modifies original $?.
153 0           LOOP: while (1) {
154 0 0         my $kid = waitpid( $self->{pid}, 0 );
155 0 0         if ($^O ne 'MSWin32') { # i'm not in hell
156 0           if (POSIX::WIFSIGNALED($?)) {
157 0 0         my $signame = (split(' ', $Config{sig_name}))[POSIX::WTERMSIG($?)];
158 0           if ($signame =~ /^(ABRT|PIPE)$/) {
159             Test::More::diag("your server received SIG$signame");
160             }
161             }
162 0 0 0       }
163 0           if ($kid == 0 || $kid == -1) {
164             last LOOP;
165             }
166 0           }
167             undef $self->{pid};
168             }
169              
170 0     0     sub DESTROY {
171 0           my $self = shift;
172 0           local $@;
173             $self->stop();
174             }
175              
176             1;
177             __END__
178              
179             =encoding utf8
180              
181             #line 406