File Coverage

inc/Test/TCP.pm
Criterion Covered Total %
statement 44 102 43.1
branch 5 46 10.8
condition 0 9 0.0
subroutine 13 21 61.9
pod 7 8 87.5
total 69 186 37.1


line stmt bran cond sub pod time code
1             #line 1
2 8     8   7885 package Test::TCP;
  8         18  
  8         284  
3 8     8   43 use strict;
  8         13  
  8         182  
4 8     8   237 use warnings;
  8         46  
  8         401  
5             use 5.00800;
6 8     8   44 our $VERSION = '1.13';
  8         23  
  8         58  
7 8     8   8210 use base qw/Exporter/;
  8         264114  
  8         71  
8 8     8   14121 use IO::Socket::INET;
  8         9479  
  8         106  
9 8     8   83 use Test::SharedFork 0.12;
  8         18  
  8         136  
10 8     8   43 use Test::More ();
  8         17  
  8         258  
11 8     8   8035 use Config;
  8         76389  
  8         66  
12 8     8   47142 use POSIX;
  8         19072  
  8         223  
13 8     8   61 use Time::HiRes ();
  8         17  
  8         8871  
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 8     8 0 5973 sub empty_port {
24 8 50       36 my $port = do {
25 0         0 if (@_) {
26 0 0 0     0 my $p = $_[0];
27 0         0 $p = 49152 unless $p =~ /^[0-9]+$/ && $p < 49152;
28             $p;
29 8         52 } else {
30             50000 + int(rand()*1000);
31             }
32             };
33 8         288  
34 8 50       42 while ( $port++ < 60000 ) {
35 8 50       102 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 8 50       2005 );
43             return $port if $sock;
44 0         0 }
45             die "empty port not found";
46             }
47              
48 0     0 1 0 sub test_tcp {
49 0         0 my %args = @_;
50 0 0       0 for my $k (qw/client server/) {
51             die "missing madatory parameter $k" unless exists $args{$k};
52 0   0     0 }
53             my $server = Test::TCP->new(
54             code => $args{server},
55             port => $args{port} || empty_port(),
56 0         0 );
57 0         0 $args{client}->($server->port, $server->pid);
58             undef $server; # make sure
59             }
60              
61 8     8   19 sub _check_port {
62             my ($port) = @_;
63 8         107  
64             my $remote = IO::Socket::INET->new(
65             Proto => 'tcp',
66             PeerAddr => '127.0.0.1',
67             PeerPort => $port,
68 8 50       5492 );
69 0         0 if ($remote) {
70 0         0 close $remote;
71             return 1;
72             }
73 8         44 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__