File Coverage

blib/lib/Test/DBGp.pm
Criterion Covered Total %
statement 21 94 22.3
branch 0 40 0.0
condition 0 6 0.0
subroutine 7 20 35.0
pod 0 12 0.0
total 28 172 16.2


line stmt bran cond sub pod time code
1             package Test::DBGp;
2              
3 1     1   394 use strict;
  1         2  
  1         21  
4 1     1   3 use warnings;
  1         1  
  1         36  
5              
6             =head1 NAME
7              
8             Test::DBGp - Test helpers for debuggers using the DBGp protocol
9              
10             =head1 SYNOPSIS
11              
12             use Test::DBGp;
13              
14             dbgp_listen();
15              
16             # start program under debugger
17              
18             dbgp_wait_connection($EXPECTED_APPID);
19              
20             dbgp_command_is(['step_into'], {
21             reason => 'ok',
22             status => 'break',
23             command => 'step_into',
24             });
25              
26             =head1 DESCRIPTION
27              
28             Various helpers to write tests for modules dealing with the DBGp
29             debugger protocol.
30              
31             =cut
32              
33             our $VERSION = '0.06';
34              
35 1     1   484 use Test::Differences;
  1         11502  
  1         58  
36 1     1   436 use IO::Socket;
  1         13913  
  1         2  
37 1     1   691 use File::Spec::Functions;
  1         493  
  1         54  
38 1     1   603 use File::Temp;
  1         7082  
  1         60  
39 1     1   5 use Cwd;
  1         1  
  1         748  
40              
41             require Exporter; *import = \&Exporter::import;
42              
43             our @EXPORT = qw(
44             dbgp_response_cmp
45             dbgp_parsed_response_cmp
46             dbgp_init_is
47             dbgp_command_is
48              
49             dbgp_listen
50             dbgp_listening_port
51             dbgp_listening_path
52             dbgp_stop_listening
53             dbgp_wait_connection
54              
55             dbgp_send_command
56             );
57              
58             my ($LISTEN, $CLIENT, $INIT, $SEQ, $PORT, $PATH);
59              
60             sub dbgp_response_cmp {
61 0     0 0   local $Test::Builder::Level = $Test::Builder::Level + 1;
62              
63 0           require DBGp::Client::Parser;
64              
65 0           my ($xml, $expected) = @_;
66 0           my $res = DBGp::Client::Parser::parse($xml);
67 0           my $cmp = _extract_command_data($res, $expected);
68              
69 0           eq_or_diff($cmp, $expected);
70             }
71              
72             sub dbgp_parsed_response_cmp {
73 0     0 0   local $Test::Builder::Level = $Test::Builder::Level + 1;
74              
75 0           my ($res, $expected) = @_;
76 0           my $cmp = _extract_command_data($res, $expected);
77              
78 0           eq_or_diff($cmp, $expected);
79             }
80              
81             sub _extract_command_data {
82 0     0     my ($res, $expected) = @_;
83              
84 0 0         if (!ref $expected) {
    0          
    0          
85 0           return $res;
86             } elsif (ref $expected eq 'HASH') {
87 0 0         return $res if !defined $res;
88             return {
89             map {
90 0           $_ => _extract_command_data($res->$_, $expected->{$_})
  0            
91             } keys %$expected
92             };
93             } elsif (ref $expected eq 'ARRAY') {
94 0 0         return $res if ref $res ne 'ARRAY';
95             return [
96             ( map {
97 0           _extract_command_data($res->[$_], $expected->[$_])
  0            
98             } 0 .. $#$expected ),
99             ( ("") x ($#$res - $#$expected) ),
100             ];
101             } else {
102 0           die "Can't extract ", ref $expected, "value";
103             }
104             }
105              
106             sub dbgp_listen {
107 0 0   0 0   if ($^O eq 'MSWin32') {
108 0           dbgp_listen_tcp();
109             } else {
110 0           dbgp_listen_unix();
111             }
112             }
113              
114             sub dbgp_listen_tcp {
115 0 0   0 0   return if $LISTEN;
116              
117 0 0         for my $port (!$PORT ? (17000 .. 19000) : ($PORT)) {
118 0           $LISTEN = IO::Socket::INET->new(
119             Listen => 1,
120             LocalAddr => '127.0.0.1',
121             LocalPort => $port,
122             Proto => 'tcp',
123             Timeout => 2,
124             );
125 0 0         next unless $LISTEN;
126              
127 0           $PORT = $port;
128 0           $PATH = undef;
129 0           last;
130             }
131              
132 0 0         die "Unable to open a listening socket in the 17000 - 19000 port range"
133             unless $LISTEN;
134             }
135              
136             sub dbgp_listen_unix {
137 0 0   0 0   return if $LISTEN;
138              
139 0           my $path = $PATH;
140 0 0         if (!$path) {
141 0           $path = File::Spec::Functions::rel2abs('dbgp.sock', Cwd::getcwd());
142              
143 0 0         if (length($path) >= 90) { # arbitrary, should be low enough
144 0           my $tempdir = File::Temp::tempdir(CLEANUP => 1);
145 0           $path = File::Spec::Functions::rel2abs('dbgp.sock', $tempdir);
146             }
147             }
148 0 0         unlink $path if -S $path;
149 0 0         return if -e $path;
150              
151 0           $LISTEN = IO::Socket::UNIX->new(
152             Local => $path,
153             Listen => 1,
154             );
155 0           $PORT = undef;
156 0           $PATH = $path;
157              
158 0 0         die "Unable to open a listening socket on '$path'"
159             unless $LISTEN;
160             }
161              
162             sub dbgp_stop_listening {
163 0     0 0   close $LISTEN;
164 0           $LISTEN = undef;
165             }
166              
167 0     0 0   sub dbgp_listening_port { $PORT }
168 0     0 0   sub dbgp_listening_path { $PATH }
169              
170             sub dbgp_wait_connection {
171 0     0 0   my ($pid, $reject) = @_;
172 0           my $conn = $LISTEN->accept;
173              
174 0 0         die "Did not receive any connection from the debugged program: ", $LISTEN->error
175             unless $conn;
176              
177 0 0         if ($reject) {
178 0           close $conn;
179 0           return;
180             }
181              
182 0           require DBGp::Client::Stream;
183 0           require DBGp::Client::Parser;
184              
185 0           $CLIENT = DBGp::Client::Stream->new(socket => $conn);
186              
187             # consume initialization line
188 0           $INIT = DBGp::Client::Parser::parse($CLIENT->get_line);
189              
190 0 0 0       die "We got connected with the wrong debugged program"
191             if $INIT->appid != $pid || $INIT->language ne 'Perl';
192             }
193              
194             sub dbgp_send_command {
195 0     0 0   my ($command, @args) = @_;
196              
197 0           $CLIENT->put_line($command, '-i', ++$SEQ, @args);
198 0           my $res = DBGp::Client::Parser::parse($CLIENT->get_line);
199              
200 0 0 0       die 'Mismatched transaction IDs: got ', $res->transaction_id,
201             ' expected ', $SEQ
202             if $res && $res->transaction_id != $SEQ;
203              
204 0           return $res;
205             }
206              
207             sub dbgp_init_is {
208 0     0 0   local $Test::Builder::Level = $Test::Builder::Level + 1;
209              
210 0           my ($expected) = @_;
211 0           my $cmp = _extract_command_data($INIT, $expected);
212              
213 0           eq_or_diff($cmp, $expected);
214             }
215              
216             sub dbgp_command_is {
217 0     0 0   local $Test::Builder::Level = $Test::Builder::Level + 1;
218              
219 0           my ($command, $expected) = @_;
220 0           my $res = dbgp_send_command(@$command);
221 0           my $cmp = _extract_command_data($res, $expected);
222              
223 0           eq_or_diff($cmp, $expected);
224             }
225              
226             1;
227              
228             __END__