File Coverage

blib/lib/Test/DBGp.pm
Criterion Covered Total %
statement 21 110 19.0
branch 0 46 0.0
condition 0 9 0.0
subroutine 7 24 29.1
pod 0 15 0.0
total 28 204 13.7


line stmt bran cond sub pod time code
1             package Test::DBGp;
2              
3 1     1   462 use strict;
  1         1  
  1         23  
4 1     1   2 use warnings;
  1         1  
  1         41  
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.07';
34              
35 1     1   481 use Test::Differences;
  1         12025  
  1         59  
36 1     1   423 use IO::Socket;
  1         13967  
  1         3  
37 1     1   731 use File::Spec::Functions;
  1         498  
  1         56  
38 1     1   586 use File::Temp;
  1         7191  
  1         54  
39 1     1   4 use Cwd;
  1         1  
  1         853  
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             dbgp_reset_output
58             dbgp_stdout_is
59             dbgp_stderr_is
60             );
61              
62             my ($LISTEN, $CLIENT, $INIT, $SEQ, $PORT, $PATH);
63             my ($STDOUT, $STDERR) = ('', '');
64              
65             sub dbgp_response_cmp {
66 0     0 0   local $Test::Builder::Level = $Test::Builder::Level + 1;
67              
68 0           require DBGp::Client::Parser;
69              
70 0           my ($xml, $expected) = @_;
71 0           my $res = DBGp::Client::Parser::parse($xml);
72 0           my $cmp = _extract_command_data($res, $expected);
73              
74 0           eq_or_diff($cmp, $expected);
75             }
76              
77             sub dbgp_parsed_response_cmp {
78 0     0 0   local $Test::Builder::Level = $Test::Builder::Level + 1;
79              
80 0           my ($res, $expected) = @_;
81 0           my $cmp = _extract_command_data($res, $expected);
82              
83 0           eq_or_diff($cmp, $expected);
84             }
85              
86             sub _extract_command_data {
87 0     0     my ($res, $expected) = @_;
88              
89 0 0         if (!ref $expected) {
    0          
    0          
90 0           return $res;
91             } elsif (ref $expected eq 'HASH') {
92 0 0         return $res if !defined $res;
93             return {
94             map {
95 0           $_ => _extract_command_data($res->$_, $expected->{$_})
  0            
96             } keys %$expected
97             };
98             } elsif (ref $expected eq 'ARRAY') {
99 0 0         return $res if ref $res ne 'ARRAY';
100             return [
101             ( map {
102 0           _extract_command_data($res->[$_], $expected->[$_])
  0            
103             } 0 .. $#$expected ),
104             ( ("") x ($#$res - $#$expected) ),
105             ];
106             } else {
107 0           die "Can't extract ", ref $expected, "value";
108             }
109             }
110              
111             sub dbgp_listen {
112 0 0   0 0   if ($^O eq 'MSWin32') {
113 0           dbgp_listen_tcp();
114             } else {
115 0           dbgp_listen_unix();
116             }
117             }
118              
119             sub dbgp_listen_tcp {
120 0 0   0 0   return if $LISTEN;
121              
122 0 0         for my $port (!$PORT ? (17000 .. 19000) : ($PORT)) {
123 0           $LISTEN = IO::Socket::INET->new(
124             Listen => 1,
125             LocalAddr => '127.0.0.1',
126             LocalPort => $port,
127             Proto => 'tcp',
128             Timeout => 2,
129             );
130 0 0         next unless $LISTEN;
131              
132 0           $PORT = $port;
133 0           $PATH = undef;
134 0           last;
135             }
136              
137 0 0         die "Unable to open a listening socket in the 17000 - 19000 port range"
138             unless $LISTEN;
139             }
140              
141             sub dbgp_listen_unix {
142 0 0   0 0   return if $LISTEN;
143              
144 0           my $path = $PATH;
145 0 0         if (!$path) {
146 0           $path = File::Spec::Functions::rel2abs('dbgp.sock', Cwd::getcwd());
147              
148 0 0         if (length($path) >= 90) { # arbitrary, should be low enough
149 0           my $tempdir = File::Temp::tempdir(CLEANUP => 1);
150 0           $path = File::Spec::Functions::rel2abs('dbgp.sock', $tempdir);
151             }
152             }
153 0 0         unlink $path if -S $path;
154 0 0         return if -e $path;
155              
156 0           $LISTEN = IO::Socket::UNIX->new(
157             Local => $path,
158             Listen => 1,
159             );
160 0           $PORT = undef;
161 0           $PATH = $path;
162              
163 0 0         die "Unable to open a listening socket on '$path'"
164             unless $LISTEN;
165             }
166              
167             sub dbgp_stop_listening {
168 0     0 0   close $LISTEN;
169 0           $LISTEN = undef;
170             }
171              
172 0     0 0   sub dbgp_listening_port { $PORT }
173 0     0 0   sub dbgp_listening_path { $PATH }
174              
175             sub _append_output {
176 0     0     my ($event) = @_;
177              
178 0 0         if ($event->type eq 'stdout') {
    0          
179 0           $STDOUT .= $event->content;
180             } elsif ($event->type eq 'stderr') {
181 0           $STDERR .= $event->content;
182             } else {
183 0           die "Unknown event type ", $event->type
184             }
185             }
186              
187             sub dbgp_wait_connection {
188 0     0 0   my ($pid, $reject) = @_;
189 0           my $conn = $LISTEN->accept;
190              
191 0 0         die "Did not receive any connection from the debugged program: ", $LISTEN->error
192             unless $conn;
193              
194 0 0         if ($reject) {
195 0           close $conn;
196 0           return;
197             }
198              
199 0           require DBGp::Client::Stream;
200 0           require DBGp::Client::Parser;
201              
202 0           $CLIENT = DBGp::Client::Stream->new(socket => $conn);
203              
204             # consume initialization line
205 0           $INIT = DBGp::Client::Parser::parse($CLIENT->get_line);
206              
207 0 0 0       die "We got connected with the wrong debugged program"
208             if $INIT->appid != $pid || $INIT->language ne 'Perl';
209             }
210              
211             sub dbgp_send_command {
212 0     0 0   my ($command, @args) = @_;
213              
214 0           $CLIENT->put_line($command, '-i', ++$SEQ, @args);
215 0           for (;;) {
216 0           my $res = DBGp::Client::Parser::parse($CLIENT->get_line);
217              
218 0 0 0       if ($res && $res->is_stream) {
219 0           _append_output($res);
220 0           next;
221             }
222              
223 0 0 0       die 'Mismatched transaction IDs: got ', $res->transaction_id,
224             ' expected ', $SEQ
225             if $res && $res->transaction_id != $SEQ;
226              
227 0           return $res;
228             }
229             }
230              
231             sub dbgp_reset_output {
232 0     0 0   $STDOUT = $STDERR = '';
233             }
234              
235             sub dbgp_stdout_is {
236 0     0 0   local $Test::Builder::Level = $Test::Builder::Level + 1;
237 0           my ($expected) = @_;
238              
239 0           eq_or_diff($STDOUT, $expected);
240             }
241              
242             sub dbgp_stderr_is {
243 0     0 0   local $Test::Builder::Level = $Test::Builder::Level + 1;
244 0           my ($expected) = @_;
245              
246 0           eq_or_diff($STDERR, $expected);
247             }
248              
249             sub dbgp_init_is {
250 0     0 0   local $Test::Builder::Level = $Test::Builder::Level + 1;
251              
252 0           my ($expected) = @_;
253 0           my $cmp = _extract_command_data($INIT, $expected);
254              
255 0           eq_or_diff($cmp, $expected);
256             }
257              
258             sub dbgp_command_is {
259 0     0 0   local $Test::Builder::Level = $Test::Builder::Level + 1;
260              
261 0           my ($command, $expected) = @_;
262 0           my $res = dbgp_send_command(@$command);
263 0           my $cmp = _extract_command_data($res, $expected);
264              
265 0           eq_or_diff($cmp, $expected);
266             }
267              
268             1;
269              
270             __END__