File Coverage

blib/lib/Test/TCP.pm
Criterion Covered Total %
statement 95 95 100.0
branch 38 44 86.3
condition 11 17 64.7
subroutine 19 19 100.0
pod 7 7 100.0
total 170 182 93.4


line stmt bran cond sub pod time code
1             package Test::TCP;
2 18     18   901906 use strict;
  18         169  
  18         507  
3 18     18   88 use warnings;
  18         29  
  18         448  
4 18     18   419 use 5.00800;
  18         60  
5             our $VERSION = '2.20';
6 18     18   111 use base qw/Exporter/;
  18         55  
  18         2656  
7 18     18   7652 use Test::SharedFork 0.12;
  18         1087222  
  18         197  
8 18     18   4422 use Test::More ();
  18         20053  
  18         322  
9 18     18   95 use Config;
  18         35  
  18         489  
10 18     18   89 use POSIX;
  18         36  
  18         124  
11 18     18   59010 use Time::HiRes ();
  18         23057  
  18         451  
12 18     18   126 use Carp ();
  18         33  
  18         479  
13 18     18   8103 use Net::EmptyPort qw(empty_port check_port);
  18         51  
  18         18071  
14              
15             our @EXPORT = qw/ empty_port test_tcp wait_port /;
16              
17             # process does not die when received SIGTERM, on win32.
18             my $TERMSIG = $^O eq 'MSWin32' ? 'KILL' : 'TERM';
19              
20             sub test_tcp {
21 16     16 1 12676 my %args = @_;
22 16         124 for my $k (qw/client server/) {
23 31 100       232 die "missing mandatory parameter $k" unless exists $args{$k};
24             }
25 14         103 my $server_code = delete $args{server};
26 14         70 my $client_code = delete $args{client};
27              
28 14         227 my $server = Test::TCP->new(
29             code => $server_code,
30             %args,
31             );
32 12         129 $client_code->($server->port, $server->pid);
33 9         379720 undef $server; # make sure
34             }
35              
36             sub wait_port {
37 16     16 1 2515 my ($host, $port, $max_wait);
38 16 100 100     563 if (@_ && ref $_[0] eq 'HASH') {
    100          
39 13         173 $host = $_[0]->{host};
40 13         137 $port = $_[0]->{port};
41 13         93 $max_wait = $_[0]->{max_wait};
42             } elsif (@_ == 3) {
43             # backward compat
44 1         4 ($port, (my $sleep), (my $retry)) = @_;
45 1         2 $max_wait = $sleep * $retry;
46             } else {
47 2         4 ($port, $max_wait) = @_;
48             }
49 16 100       140 $host = '127.0.0.1'
50             unless defined $host;
51 16   100     109 $max_wait ||= 10;
52              
53 16 50       584 Net::EmptyPort::wait_port({ host => $host, port => $port, max_wait => $max_wait })
54             or die "cannot open port: $host:$port";
55             }
56              
57             # -------------------------------------------------------------------------
58             # OO-ish interface
59              
60             sub new {
61 20     20 1 2493 my $class = shift;
62 20 100       171 my %args = @_==1 ? %{$_[0]} : @_;
  1         7  
63 20 100       475 Carp::croak("missing mandatory parameter 'code'") unless exists $args{code};
64 19         336 my $self = bless {
65             auto_start => 1,
66             max_wait => 10,
67             host => '127.0.0.1',
68             _my_pid => $$,
69             %args,
70             }, $class;
71 19 100       222 if ($self->{listen}) {
72             $self->{socket} ||= Net::EmptyPort::listen_socket({
73             host => $self->{host},
74             proto => $self->{proto},
75 1 50 33     8 }) or die "Cannot listen: $!";
76 1         876 $self->{port} = $self->{socket}->sockport;
77             }
78             else {
79 18   66     232 $self->{port} ||= empty_port({ host => $self->{host} });
80             }
81             $self->start()
82 19 100       186 if $self->{auto_start};
83 16         303 return $self;
84             }
85              
86 13     13 1 318660 sub pid { $_[0]->{pid} }
87 42     42 1 8819 sub port { $_[0]->{port} }
88              
89             sub start {
90 17     17 1 45 my $self = shift;
91 17         16401 my $pid = fork();
92 17 50       1124 die "fork() failed: $!" unless defined $pid;
93              
94 17 100       686 if ( $pid ) { # parent process.
95 14         387 $self->{pid} = $pid;
96             Test::TCP::wait_port({ host => $self->{host}, port => $self->port, max_wait => $self->{max_wait} })
97 14 100       584 unless $self->{socket};
98 14         141 return;
99             } else { # child process
100 3   33     190 $self->{code}->($self->{socket} || $self->port);
101             # should not reach here
102 2 100       6006055 if (kill 0, $self->{_my_pid}) { # warn only parent process still exists
103 1         51 warn("[Test::TCP] Child process does not block(PID: $$, PPID: $self->{_my_pid})");
104             }
105 2         45 exit 0;
106             }
107             }
108              
109             sub stop {
110 19     19 1 110 my $self = shift;
111              
112 19 100       1174 return unless defined $self->{pid};
113 14 100       525 return unless $self->{_my_pid} == $$;
114              
115             # This is a workaround for win32 fork emulation's bug.
116             #
117             # kill is inherently unsafe for pseudo-processes in Windows
118             # and the process calling kill(9, $pid) may be destabilized
119             # The call to Sleep will decrease the frequency of this problems
120             #
121             # SEE ALSO:
122             # http://www.gossamer-threads.com/lists/perl/porters/261805
123             # https://rt.cpan.org/Ticket/Display.html?id=67292
124 13 50       97 Win32::Sleep(0) if $^O eq "MSWin32"; # will relinquish the remainder of its time slice
125              
126 13         706 kill $TERMSIG => $self->{pid};
127              
128 13 50       106 Win32::Sleep(0) if $^O eq "MSWin32"; # will relinquish the remainder of its time slice
129              
130              
131 13         95 local $?; # waitpid modifies original $?.
132 13         37 LOOP: while (1) {
133 25         338174 my $kid = waitpid( $self->{pid}, 0 );
134 25 50       224 if ($^O ne 'MSWin32') { # i'm not in hell
135 25 100       180 if (POSIX::WIFSIGNALED($?)) {
136 11         2146 my $signame = (split(' ', $Config{sig_name}))[POSIX::WTERMSIG($?)];
137 11 100       219 if ($signame =~ /^(ABRT|PIPE)$/) {
138 1         60 Test::More::diag("your server received SIG$signame");
139             }
140             }
141             }
142 25 100 66     1084 if ($kid == 0 || $kid == -1) {
143 13         53 last LOOP;
144             }
145             }
146 13         866 undef $self->{pid};
147             }
148              
149             sub DESTROY {
150 19     19   3059985 my $self = shift;
151 19         79 local $@;
152 19         145 $self->stop();
153             }
154              
155             1;
156             __END__