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   2756430 use strict;
  22         139  
  22         738  
4 22     22   103 use warnings;
  22         42  
  22         566  
5 22     22   391 use 5.010;
  22         82  
6 22     22   7507 use Moo;
  22         139766  
  22         106  
7 22     22   29025 use URI;
  22         79622  
  22         606  
8 22     22   13898 use AnyEvent;
  22         73928  
  22         769  
9 22     22   160 use Test2::API qw( context );
  22         50  
  22         1342  
10 22     22   8526 use Path::Class qw( tempdir );
  22         594777  
  22         50681  
11              
12             extends 'AnyEvent::FTP::Server';
13              
14             # ABSTRACT: Test (non-blocking) ftp clients against a real FTP server
15             our $VERSION = '0.19'; # 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 14900 my($context, $message) = @_;
71              
72 26         99 my $ctx = context();
73              
74 26         74472 my $uri = URI->new("ftp://127.0.0.1");
75              
76 26   100     134253 $context //= 'Memory';
77 26 50       140 $context = "AnyEvent::FTP::Server::Context::$context"
78             unless $context =~ /::/;
79 26         119 my $name = (split /::/, $context)[-1];
80              
81 26         92 my $user = join '', map { chr(ord('a') + int rand(26)) } (1..10);
  260         791  
82 26         84 my $pass = join '', map { chr(ord('a') + int rand(26)) } (1..10);
  260         440  
83 26         181 $uri->userinfo(join(':', $user, $pass));
84              
85 26         3215 my $server;
86 26         104 eval {
87 26         328 $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       104 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     396 return $_[0] eq $user && $_[1] eq $pass;
119 29     29   765 });
120 26         295 });
121              
122 26         244 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         22682 );
127             $server->on_bind(sub {
128 26     26   368 $uri->port(shift);
129 26         1972 $cv->send;
130 26         610 });
131 26         147 $server->start;
132 26         171 $cv->recv;
133             };
134 26         324 my $error = $@;
135              
136 26   33     469 $message //= "created FTP ($name) server at $uri";
137              
138 26         323 $ctx->ok($error eq '', $message);
139 26 50       10843 $ctx->diag($error) if $error;
140 26         140 $ctx->release;
141              
142 26         880 $server;
143             }
144              
145              
146             sub connect_ftpclient_ok
147             {
148 5     5 1 732 my($self, $message) = @_;
149 5         9 my $client;
150 5         7 eval {
151 5         1218 require AnyEvent::FTP::Client;
152 5         57 $client = AnyEvent::FTP::Client->new;
153 5         133 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         58 );
158 5 50       75 if($self->auto_login)
159             {
160             $client->connect($self->test_uri)
161 5     5   26 ->cb(sub { $cv->send });
  5         46  
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         50 $cv->recv;
169             };
170 5         118 my $error = $@;
171              
172 5   33     36 $message //= "connected to FTP server at " . $self->test_uri;
173              
174 5         42 my $ctx = context();
175 5         486 $ctx->ok($error eq '', $message);
176 5 50       993 $ctx->diag($error) if $error;
177 5         20 $ctx->release;
178              
179 5         148 $client;
180             }
181              
182              
183             sub help_coverage_ok
184             {
185 10     10 1 129 my($self, $class, $message) = @_;
186              
187 10   66     60 $class //= $self->default_context;
188              
189 10         73 my @missing;
190              
191 10         19 my $client = eval { $self->_client };
  10         226  
192 10         29 my $error = $@;
193              
194 10         16 my $count = 0;
195 10 50       31 unless($error)
196             {
197 10     7   687 foreach my $cmd (map { uc $_ } grep s/^cmd_//, eval qq{ use $class; keys \%${class}::;})
  96     3   150  
  7         42  
  7         11  
  7         161  
  3         19  
  3         6  
  3         55  
198             {
199 96 50 33     133 if((eval { $client->help($cmd)->recv } || $@)->code != 214)
200 0         0 { push @missing, $cmd }
201 96         238 $count++;
202             }
203             }
204              
205 10   33     129 $message //= "help coverage for $class";
206              
207 10         48 my $ctx = context();
208 10   33     1192 $ctx->ok($error eq '' && @missing == 0, $message);
209 10 50       3443 $ctx->diag($error) if $error;
210 10 50       157 $ctx->diag("commands missing help: @missing") if @missing;
211 10 50       34 $ctx->diag("didn't find ANY commands for class: $class")
212             if $count == 0;
213 10         45 $ctx->release;
214              
215 10         354 $self;
216             }
217              
218              
219             sub command_ok
220             {
221 145     145 1 14390 my($self, $command, $args, $message) = @_;
222              
223 145         280 my $client = eval { $self->_client };
  145         2927  
224 145         943 my $error = $@;
225              
226 145 50       340 unless($error)
227             {
228 145   66     175 my $res = (eval { $client->push_command([$command, $args])->recv } || $@);
229 145 50       416 if(eval { $res->isa('AnyEvent::FTP::Client::Response') })
  145         608  
230 145         477 { $self->res($res) }
231             else
232 0         0 { $error = $res; $self->res(undef) }
  0         0  
233             }
234              
235 145   33     593 $message //= "command: $command";
236              
237 145         409 my $ctx = context();
238 145         12176 $ctx->ok($error eq '', $message);
239 145 50       32968 $ctx->diag($error) if $error;
240 145         502 $ctx->release;
241              
242 145         3319 $self;
243             }
244              
245              
246             sub code_is
247             {
248 140     140 1 1157 my($self, $code, $message) = @_;
249              
250 140   33     631 $message //= "response code is $code";
251              
252 140         314 my $ctx = context();
253 140   50     8522 my $actual = eval { $self->res->code } // 'undefined';
  140         601  
254 140         483 $ctx->ok($actual == $code, $message);
255 140 100       27470 $ctx->diag("actual code returned is $actual")
256             unless $actual == $code;
257 140         2674 $ctx->release;
258              
259 140         3528 $self;
260             }
261              
262              
263             sub code_like
264             {
265 2     2 1 19 my($self, $regex, $message) = @_;
266              
267 2   50     9 $message //= "response code matches";
268              
269 2         5 my $ctx = context();
270 2   50     119 my $actual = eval { $self->res->code } // 'undefined';
  2         9  
271 2         27 $ctx->ok($actual =~ $regex, $message);
272 2 50       227 $ctx->diag("code $actual does not match $regex")
273             unless $actual =~ $regex;
274 2         6 $ctx->release;
275              
276 2         38 $self;
277             }
278              
279              
280             sub message_like
281             {
282 80     80 1 908 my($self, $regex, $message) = @_;
283              
284 80   50     297 $message //= "response message matches";
285              
286 80         83 my $ok = 0;
287              
288 80   50     100 my @message = @{ (eval { $self->res->message }) // [] };
  80         92  
  80         288  
289 80         132 foreach my $line (@message)
290             {
291 82 100       406 $ok = 1 if $line =~ $regex;
292             }
293              
294 80         334 my $ctx = context();
295 80         4938 $ctx->ok($ok, $message);
296 80 50       13516 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         225 $ctx->release;
303              
304 80         1688 $self;
305             }
306              
307              
308             sub message_is
309             {
310 23     23 1 192 my($self, $string, $message) = @_;
311              
312 23   50     85 $message //= "response message matches";
313              
314 23         26 my $ok = 0;
315              
316 23   50     29 my @message = @{ (eval { $self->res->message }) // [] };
  23         57  
  23         88  
317              
318 23         41 foreach my $line (@message)
319             {
320 23 50       56 $ok = 1 if $line eq $string;
321             }
322              
323 23         62 my $ctx = context();
324 23         1425 $ctx->ok($ok, $message);
325 23 50       3967 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         79 $ctx->release;
332              
333 23         471 $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 1202 my($self, $location, $message) = @_;
367              
368 6 100 33     35 $message //= defined $location ? "nlst: $location" : 'nlst';
369              
370 6         9 my $client = eval { $self->_client };
  6         121  
371 6         35 my $error = $@;
372              
373 6         17 $self->content('');
374              
375 6 50       14 unless($error)
376             {
377 6         7 my $list = eval { $client->nlst($location)->recv };
  6         18  
378 6         385 $error = $@;
379 6 50       37 $self->content(join "\n", @$list, '') unless $error;
380             }
381              
382 6         28 my $ctx = context();
383 6         585 $ctx->ok($error eq '', $message);
384 6 50       1553 $ctx->diag($error) if $error;
385 6         20 $ctx->release;
386              
387 6         136 $self;
388             }
389              
390              
391             sub _display_content
392             {
393 6     6   9 state $temp;
394 6         9 state $counter = 0;
395 6         10 my $method = 'diag';
396             #$method = 'note' if $tb->todo;
397              
398 6 100       14 unless(defined $temp)
399             {
400 2         9 $temp = tempdir(CLEANUP => 1);
401             }
402              
403 6         1077 my $file = $temp->file(sprintf("data.%d", $counter++));
404 6         458 $file->spew($_[0]);
405              
406 6         1267 my $ctx = context();
407              
408 6 50       368 if(-T $file)
409             {
410 6         384 $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         3126 $ctx->release;
425              
426 6         60 $file->remove;
427             }
428              
429             sub content_is
430             {
431 9     9 1 30951 my($self, $string, $message) = @_;
432              
433 9   50     38 $message ||= 'content matches';
434              
435 9         26 my $ok = $self->content eq $string;
436              
437 9         19 my $ctx = context();
438 9         590 $ctx->ok($ok, $message);
439 9 100       2552 unless($ok)
440             {
441 3         10 $ctx->diag("content:");
442 3         610 _display_content($self->content);
443 3         362 $ctx->diag("expected:");
444 3         636 _display_content($string);
445             }
446              
447 9         320 $ctx->release;
448              
449 9         179 $self;
450             }
451              
452              
453             sub global_timeout_ok (;$$)
454             {
455 4     4 1 277 my($timeout, $message) = @_;
456              
457 4   100     24 $timeout //= 120;
458 4   33     27 $message //= "global timeout of $timeout seconds";
459              
460 4         22 my $ctx = context();
461              
462 4         16509 state $timers = [];
463              
464 4         8 eval {
465             push @$timers, AnyEvent->timer(
466             after => $timeout,
467 0     0   0 cb => sub { $ctx->diag("GLOBAL TIMEOUT"); exit },
  0         0  
468 4         45 );
469             };
470 4         4282 my $error = $@;
471              
472 4         11 my $ok = $error eq '';
473              
474 4         22 $ctx->ok($ok, $message);
475 4 50       1483 $ctx->diag($error) if $error;
476              
477 4         22 $ctx->release;
478              
479 4         119 $ok;
480             }
481              
482             sub import
483             {
484 21     21   172 my $caller = caller;
485 22     22   260 no strict 'refs';
  22         61  
  22         1576  
486 21         52 *{join '::', $caller, 'create_ftpserver_ok'} = \&create_ftpserver_ok;
  21         136  
487 21         56 *{join '::', $caller, 'global_timeout_ok'} = \&global_timeout_ok;
  21         33437  
488             }
489              
490 22     22   1440 BEGIN { eval 'use EV' }
  22     22   11004  
  22         42340  
  22         1056  
491              
492             1;
493              
494             __END__