File Coverage

blib/lib/Test/AnyEventFTPServer.pm
Criterion Covered Total %
statement 222 268 82.8
branch 32 64 50.0
condition 27 59 45.7
subroutine 29 35 82.8
pod 12 12 100.0
total 322 438 73.5


line stmt bran cond sub pod time code
1             package Test::AnyEventFTPServer;
2              
3 22     22   3778519 use strict;
  22         158  
  22         752  
4 22     22   123 use warnings;
  22         45  
  22         634  
5 22     22   565 use 5.010;
  22         105  
6 22     22   10055 use Moo;
  22         191839  
  22         129  
7 22     22   37740 use URI;
  22         106758  
  22         720  
8 22     22   18628 use AnyEvent;
  22         103859  
  22         854  
9 22     22   173 use Test2::API qw( context );
  22         45  
  22         1671  
10 22     22   10645 use Path::Class qw( tempdir );
  22         772882  
  22         66621  
11              
12             extends 'AnyEvent::FTP::Server';
13              
14             # ABSTRACT: Test (non-blocking) ftp clients against a real FTP server
15             our $VERSION = '0.18'; # VERSION
16              
17              
18             has test_uri => (
19             is => 'ro',
20             required => 1,
21             );
22              
23              
24             has res => (
25             is => 'rw',
26             );
27              
28              
29             has content => (
30             is => 'rw',
31             default => '',
32             );
33              
34              
35             has auto_login => (
36             is => 'rw',
37             default => sub { 1 },
38             );
39              
40             has _client => (
41             is => 'ro',
42             lazy => 1,
43             default => sub {
44             my $self = shift;
45             require AnyEvent::FTP::Client;
46             my $client = AnyEvent::FTP::Client->new;
47             my $cv = AnyEvent->condvar;
48             my $timer = AnyEvent->timer(
49             after => 5,
50             cb => sub { $cv->croak("timeout connecting with ftp client") },
51             );
52             if($self->auto_login)
53             {
54             $client->connect($self->test_uri)
55             ->cb(sub { $cv->send });
56             }
57             else
58             {
59             $client->connect($self->test_uri->host, $self->test_uri->port)
60             ->cb(sub { $cv->send });
61             }
62             $cv->recv;
63             $client;
64             },
65             );
66              
67              
68             sub create_ftpserver_ok (;$$)
69             {
70 26     26 1 18510 my($context, $message) = @_;
71              
72 26         122 my $ctx = context();
73              
74 26         95331 my $uri = URI->new("ftp://127.0.0.1");
75              
76 26   100     184241 $context //= 'Memory';
77 26 50       172 $context = "AnyEvent::FTP::Server::Context::$context"
78             unless $context =~ /::/;
79 26         168 my $name = (split /::/, $context)[-1];
80              
81 26         116 my $user = join '', map { chr(ord('a') + int rand(26)) } (1..10);
  260         701  
82 26         106 my $pass = join '', map { chr(ord('a') + int rand(26)) } (1..10);
  260         524  
83 26         231 $uri->userinfo(join(':', $user, $pass));
84              
85 26         4157 my $server;
86 26         138 eval {
87 26         428 $server = Test::AnyEventFTPServer->new(
88             default_context => $context,
89             hostname => '127.0.0.1',
90             port => undef,
91             test_uri => $uri,
92             );
93              
94 26 50       138 if($ENV{AEF_DEBUG})
95             {
96             $server->on_connect(sub {
97 0     0   0 my $con = shift;
98 0         0 $ctx->note("CONNECT");
99              
100             $con->on_request(sub {
101 0         0 my $raw = shift;
102 0         0 $ctx->note("CLIENT: $raw");
103 0         0 });
104              
105             $con->on_response(sub {
106 0         0 my $raw = shift;
107 0         0 $ctx->note("SERVER: $raw");
108 0         0 });
109              
110             $con->on_close(sub {
111 0         0 $ctx->note("DISCONNECT");
112 0         0 });
113 0         0 });
114             }
115              
116             $server->on_connect(sub {
117             shift->context->authenticator(sub {
118 29   66     491 return $_[0] eq $user && $_[1] eq $pass;
119 29     29   960 });
120 26         376 });
121              
122 26         335 my $cv = AnyEvent->condvar;
123             my $timer = AnyEvent->timer(
124             after => 5,
125 0     0   0 cb => sub { $cv->croak("timeout creating ftp server") },
126 26         30734 );
127             $server->on_bind(sub {
128 26     26   438 $uri->port(shift);
129 26         2413 $cv->send;
130 26         759 });
131 26         198 $server->start;
132 26         216 $cv->recv;
133             };
134 26         531 my $error = $@;
135              
136 26   33     624 $message //= "created FTP ($name) server at $uri";
137              
138 26         397 $ctx->ok($error eq '', $message);
139 26 50       11820 $ctx->diag($error) if $error;
140 26         179 $ctx->release;
141              
142 26         1045 $server;
143             }
144              
145              
146             sub connect_ftpclient_ok
147             {
148 5     5 1 1005 my($self, $message) = @_;
149 5         10 my $client;
150 5         10 eval {
151 5         1670 require AnyEvent::FTP::Client;
152 5         77 $client = AnyEvent::FTP::Client->new;
153 5         160 my $cv = AnyEvent->condvar;
154             my $timer = AnyEvent->timer(
155             after => 5,
156 0     0   0 cb => sub { $cv->croak("timeout connecting with ftp client") },
157 5         79 );
158 5 50       104 if($self->auto_login)
159             {
160             $client->connect($self->test_uri)
161 5     5   31 ->cb(sub { $cv->send });
  5         58  
162             }
163             else
164             {
165             $client->connect($self->tesT_uri->host, $self->test_uri->port)
166 0     0   0 ->cb(sub { $cv->send });
  0         0  
167             }
168 5         70 $cv->recv;
169             };
170 5         150 my $error = $@;
171              
172 5   33     79 $message //= "connected to FTP server at " . $self->test_uri;
173              
174 5         56 my $ctx = context();
175 5         653 $ctx->ok($error eq '', $message);
176 5 50       1710 $ctx->diag($error) if $error;
177 5         29 $ctx->release;
178              
179 5         169 $client;
180             }
181              
182              
183             sub help_coverage_ok
184             {
185 10     10 1 103 my($self, $class, $message) = @_;
186              
187 10   66     77 $class //= $self->default_context;
188              
189 10         83 my @missing;
190              
191 10         21 my $client = eval { $self->_client };
  10         266  
192 10         34 my $error = $@;
193              
194 10         22 my $count = 0;
195 10 50       35 unless($error)
196             {
197 10     7   922 foreach my $cmd (map { uc $_ } grep s/^cmd_//, eval qq{ use $class; keys \%${class}::;})
  96     3   207  
  7         55  
  7         16  
  7         214  
  3         23  
  3         7  
  3         85  
198             {
199 96 50 33     173 if((eval { $client->help($cmd)->recv } || $@)->code != 214)
200 0         0 { push @missing, $cmd }
201 96         339 $count++;
202             }
203             }
204              
205 10   33     157 $message //= "help coverage for $class";
206              
207 10         53 my $ctx = context();
208 10   33     1343 $ctx->ok($error eq '' && @missing == 0, $message);
209 10 50       2628 $ctx->diag($error) if $error;
210 10 50       134 $ctx->diag("commands missing help: @missing") if @missing;
211 10 50       38 $ctx->diag("didn't find ANY commands for class: $class")
212             if $count == 0;
213 10         51 $ctx->release;
214              
215 10         402 $self;
216             }
217              
218              
219             sub command_ok
220             {
221 145     145 1 19345 my($self, $command, $args, $message) = @_;
222              
223 145         332 my $client = eval { $self->_client };
  145         3886  
224 145         1217 my $error = $@;
225              
226 145 50       421 unless($error)
227             {
228 145   66     258 my $res = (eval { $client->push_command([$command, $args])->recv } || $@);
229 145 50       529 if(eval { $res->isa('AnyEvent::FTP::Client::Response') })
  145         773  
230 145         642 { $self->res($res) }
231             else
232 0         0 { $error = $res; $self->res(undef) }
  0         0  
233             }
234              
235 145   33     801 $message //= "command: $command";
236              
237 145         544 my $ctx = context();
238 145         14216 $ctx->ok($error eq '', $message);
239 145 50       38725 $ctx->diag($error) if $error;
240 145         631 $ctx->release;
241              
242 145         4494 $self;
243             }
244              
245              
246             sub code_is
247             {
248 140     140 1 1551 my($self, $code, $message) = @_;
249              
250 140   33     835 $message //= "response code is $code";
251              
252 140         429 my $ctx = context();
253 140   50     11991 my $actual = eval { $self->res->code } // 'undefined';
  140         741  
254 140         681 $ctx->ok($actual == $code, $message);
255 140 100       36165 $ctx->diag("actual code returned is $actual")
256             unless $actual == $code;
257 140         2050 $ctx->release;
258              
259 140         4080 $self;
260             }
261              
262              
263             sub code_like
264             {
265 2     2 1 26 my($self, $regex, $message) = @_;
266              
267 2   50     10 $message //= "response code matches";
268              
269 2         7 my $ctx = context();
270 2   50     165 my $actual = eval { $self->res->code } // 'undefined';
  2         11  
271 2         31 $ctx->ok($actual =~ $regex, $message);
272 2 50       290 $ctx->diag("code $actual does not match $regex")
273             unless $actual =~ $regex;
274 2         18 $ctx->release;
275              
276 2         56 $self;
277             }
278              
279              
280             sub message_like
281             {
282 80     80 1 1089 my($self, $regex, $message) = @_;
283              
284 80   50     391 $message //= "response message matches";
285              
286 80         116 my $ok = 0;
287              
288 80   50     139 my @message = @{ (eval { $self->res->message }) // [] };
  80         116  
  80         361  
289 80         191 foreach my $line (@message)
290             {
291 82 100       537 $ok = 1 if $line =~ $regex;
292             }
293              
294 80         356 my $ctx = context();
295 80         6817 $ctx->ok($ok, $message);
296 80 50       18541 unless($ok)
297             {
298 0         0 $ctx->diag("message: ");
299 0         0 $ctx->diag(" $_") for @message;
300 0         0 $ctx->diag("does not match $regex");
301             }
302 80         317 $ctx->release;
303              
304 80         2429 $self;
305             }
306              
307              
308             sub message_is
309             {
310 23     23 1 280 my($self, $string, $message) = @_;
311              
312 23   50     125 $message //= "response message matches";
313              
314 23         33 my $ok = 0;
315              
316 23   50     41 my @message = @{ (eval { $self->res->message }) // [] };
  23         45  
  23         157  
317              
318 23         57 foreach my $line (@message)
319             {
320 23 50       80 $ok = 1 if $line eq $string;
321             }
322              
323 23         75 my $ctx = context();
324 23         2013 $ctx->ok($ok, $message);
325 23 50       5611 unless($ok)
326             {
327 0         0 $ctx->diag("message: ");
328 0         0 $ctx->diag(" $_") for @message;
329 0         0 $ctx->diag("does not match $string");
330             }
331 23         105 $ctx->release;
332              
333 23         661 $self;
334             }
335              
336              
337             sub list_ok
338             {
339 0     0 1 0 my($self, $location, $message) = @_;
340              
341 0 0 0     0 $message //= defined $location ? "list: $location" : 'list';
342              
343 0         0 my $client = eval { $self->_client };
  0         0  
344 0         0 my $error = $@;
345              
346 0         0 $self->content('');
347              
348 0 0       0 unless($error)
349             {
350 0         0 my $list = eval { $client->list($location)->recv };
  0         0  
351 0         0 $error = $@;
352 0 0       0 $self->content(join "\n", @$list, '') unless $error;
353             }
354              
355 0         0 my $ctx = context();
356 0         0 $ctx->ok($error eq '', $message);
357 0 0       0 $ctx->diag($error) if $error;
358 0         0 $ctx->release;
359              
360 0         0 $self;
361             }
362              
363              
364             sub nlst_ok
365             {
366 6     6 1 1961 my($self, $location, $message) = @_;
367              
368 6 100 33     50 $message //= defined $location ? "nlst: $location" : 'nlst';
369              
370 6         12 my $client = eval { $self->_client };
  6         167  
371 6         51 my $error = $@;
372              
373 6         24 $self->content('');
374              
375 6 50       21 unless($error)
376             {
377 6         11 my $list = eval { $client->nlst($location)->recv };
  6         30  
378 6         507 $error = $@;
379 6 50       60 $self->content(join "\n", @$list, '') unless $error;
380             }
381              
382 6         40 my $ctx = context();
383 6         861 $ctx->ok($error eq '', $message);
384 6 50       1518 $ctx->diag($error) if $error;
385 6         35 $ctx->release;
386              
387 6         231 $self;
388             }
389              
390              
391             sub _display_content
392             {
393 6     6   13 state $temp;
394 6         11 state $counter = 0;
395 6         16 my $method = 'diag';
396             #$method = 'note' if $tb->todo;
397              
398 6 100       19 unless(defined $temp)
399             {
400 2         13 $temp = tempdir(CLEANUP => 1);
401             }
402              
403 6         1555 my $file = $temp->file(sprintf("data.%d", $counter++));
404 6         602 $file->spew($_[0]);
405              
406 6         1899 my $ctx = context();
407              
408 6 50       537 if(-T $file)
409             {
410 6         549 $ctx->$method(" $_") for split /\n/, $_[0];
411             }
412             else
413             {
414 0 0       0 if(eval { require Data::HexDump })
  0         0  
415             {
416 0         0 $ctx->$method(" $_") for grep !/^$/, split /\n/, Data::HexDump::HexDump($_[0]);
417             }
418             else
419             {
420 0         0 $ctx->$method(" binary content");
421             }
422             }
423              
424 6         4147 $ctx->release;
425              
426 6         85 $file->remove;
427             }
428              
429             sub content_is
430             {
431 9     9 1 45545 my($self, $string, $message) = @_;
432              
433 9   50     55 $message ||= 'content matches';
434              
435 9         35 my $ok = $self->content eq $string;
436              
437 9         31 my $ctx = context();
438 9         799 $ctx->ok($ok, $message);
439 9 100       2804 unless($ok)
440             {
441 3         13 $ctx->diag("content:");
442 3         777 _display_content($self->content);
443 3         477 $ctx->diag("expected:");
444 3         807 _display_content($string);
445             }
446              
447 9         413 $ctx->release;
448              
449 9         272 $self;
450             }
451              
452              
453             sub global_timeout_ok (;$$)
454             {
455 4     4 1 380 my($timeout, $message) = @_;
456              
457 4   100     30 $timeout //= 120;
458 4   33     36 $message //= "global timeout of $timeout seconds";
459              
460 4         27 my $ctx = context();
461              
462 4         23031 state $timers = [];
463              
464 4         11 eval {
465             push @$timers, AnyEvent->timer(
466             after => $timeout,
467 0     0   0 cb => sub { $ctx->diag("GLOBAL TIMEOUT"); exit },
  0         0  
468 4         52 );
469             };
470 4         6023 my $error = $@;
471              
472 4         13 my $ok = $error eq '';
473              
474 4         30 $ctx->ok($ok, $message);
475 4 50       1454 $ctx->diag($error) if $error;
476              
477 4         30 $ctx->release;
478              
479 4         149 $ok;
480             }
481              
482             sub import
483             {
484 21     21   218 my $caller = caller;
485 22     22   279 no strict 'refs';
  22         56  
  22         1970  
486 21         63 *{join '::', $caller, 'create_ftpserver_ok'} = \&create_ftpserver_ok;
  21         174  
487 21         67 *{join '::', $caller, 'global_timeout_ok'} = \&global_timeout_ok;
  21         12902  
488             }
489              
490 22     22   2076 BEGIN { eval 'use EV' }
  22     22   13745  
  22         55242  
  22         1231  
491              
492             1;
493              
494             __END__