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   3210510 use strict;
  22         41  
  22         812  
4 22     22   120 use warnings;
  22         52  
  22         1143  
5 22     22   363 use 5.010;
  22         1716  
6 22     22   10007 use Moo;
  22         131707  
  22         114  
7 22     22   38936 use URI;
  22         130489  
  22         837  
8 22     22   19206 use AnyEvent;
  22         111823  
  22         863  
9 22     22   151 use Test2::API qw( context );
  22         80  
  22         1578  
10 22     22   10121 use Path::Class qw( tempdir );
  22         862829  
  22         73792  
11              
12             extends 'AnyEvent::FTP::Server';
13              
14             # ABSTRACT: Test (non-blocking) ftp clients against a real FTP server
15             our $VERSION = '0.20'; # 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 1487949 my($context, $message) = @_;
71              
72 26         141 my $ctx = context();
73              
74 26         121493 my $uri = URI->new("ftp://127.0.0.1");
75              
76 26   100     241475 $context //= 'Memory';
77 26 50       196 $context = "AnyEvent::FTP::Server::Context::$context"
78             unless $context =~ /::/;
79 26         165 my $name = (split /::/, $context)[-1];
80              
81 26         121 my $user = join '', map { chr(ord('a') + int rand(26)) } (1..10);
  260         791  
82 26         122 my $pass = join '', map { chr(ord('a') + int rand(26)) } (1..10);
  260         1072  
83 26         374 $uri->userinfo(join(':', $user, $pass));
84              
85 26         11052 my $server;
86 26         122 eval {
87 26         524 $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       123 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     482 return $_[0] eq $user && $_[1] eq $pass;
119 29     29   1120 });
120 26         389 });
121              
122 26         328 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         30687 );
127             $server->on_bind(sub {
128 26     26   263 $uri->port(shift);
129 26         3610 $cv->send;
130 26         943 });
131 26         185 $server->start;
132 26         224 $cv->recv;
133             };
134 26         468 my $error = $@;
135              
136 26   33     775 $message //= "created FTP ($name) server at $uri";
137              
138 26         444 $ctx->ok($error eq '', $message);
139 26 50       7007 $ctx->diag($error) if $error;
140 26         228 $ctx->release;
141              
142 26         1374 $server;
143             }
144              
145              
146             sub connect_ftpclient_ok
147             {
148 5     5 1 1361 my($self, $message) = @_;
149 5         9 my $client;
150 5         13 eval {
151 5         2225 require AnyEvent::FTP::Client;
152 5         95 $client = AnyEvent::FTP::Client->new;
153 5         216 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       154 if($self->auto_login)
159             {
160             $client->connect($self->test_uri)
161 5     5   40 ->cb(sub { $cv->send });
  5         63  
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         74 $cv->recv;
169             };
170 5         201 my $error = $@;
171              
172 5   33     112 $message //= "connected to FTP server at " . $self->test_uri;
173              
174 5         62 my $ctx = context();
175 5         687 $ctx->ok($error eq '', $message);
176 5 50       989 $ctx->diag($error) if $error;
177 5         39 $ctx->release;
178              
179 5         249 $client;
180             }
181              
182              
183             sub help_coverage_ok
184             {
185 10     10 1 124 my($self, $class, $message) = @_;
186              
187 10   66     97 $class //= $self->default_context;
188              
189 10         18 my @missing;
190              
191 10         78 my $client = eval { $self->_client };
  10         403  
192 10         36 my $error = $@;
193              
194 10         23 my $count = 0;
195 10 50       40 unless($error)
196             {
197 10     7   1303 foreach my $cmd (map { uc $_ } grep s/^cmd_//, eval qq{ use $class; keys \%${class}::;})
  96     3   229  
  7         64  
  7         15  
  7         233  
  3         26  
  3         6  
  3         102  
198             {
199 96 50 33     187 if((eval { $client->help($cmd)->recv } || $@)->code != 214)
200 0         0 { push @missing, $cmd }
201 96         465 $count++;
202             }
203             }
204              
205 10   33     196 $message //= "help coverage for $class";
206              
207 10         60 my $ctx = context();
208 10   33     1558 $ctx->ok($error eq '' && @missing == 0, $message);
209 10 50       2012 $ctx->diag($error) if $error;
210 10 50       38 $ctx->diag("commands missing help: @missing") if @missing;
211 10 50       61 $ctx->diag("didn't find ANY commands for class: $class")
212             if $count == 0;
213 10         63 $ctx->release;
214              
215 10         526 $self;
216             }
217              
218              
219             sub command_ok
220             {
221 145     145 1 19175 my($self, $command, $args, $message) = @_;
222              
223 145         296 my $client = eval { $self->_client };
  145         5043  
224 145         1296 my $error = $@;
225              
226 145 50       428 unless($error)
227             {
228 145   66     288 my $res = (eval { $client->push_command([$command, $args])->recv } || $@);
229 145 50       505 if(eval { $res->isa('AnyEvent::FTP::Client::Response') })
  145         741  
230 145         791 { $self->res($res) }
231             else
232 0         0 { $error = $res; $self->res(undef) }
  0         0  
233             }
234              
235 145   33     847 $message //= "command: $command";
236              
237 145         557 my $ctx = context();
238 145         16970 $ctx->ok($error eq '', $message);
239 145 50       33789 $ctx->diag($error) if $error;
240 145         670 $ctx->release;
241              
242 145         6137 $self;
243             }
244              
245              
246             sub code_is
247             {
248 140     140 1 1808 my($self, $code, $message) = @_;
249              
250 140   33     903 $message //= "response code is $code";
251              
252 140         378 my $ctx = context();
253 140   50     13617 my $actual = eval { $self->res->code } // 'undefined';
  140         820  
254 140         778 $ctx->ok($actual == $code, $message);
255 140 100       28454 $ctx->diag("actual code returned is $actual")
256             unless $actual == $code;
257 140         1982 $ctx->release;
258              
259 140         5447 $self;
260             }
261              
262              
263             sub code_like
264             {
265 2     2 1 36 my($self, $regex, $message) = @_;
266              
267 2   50     14 $message //= "response code matches";
268              
269 2         7 my $ctx = context();
270 2   50     242 my $actual = eval { $self->res->code } // 'undefined';
  2         14  
271 2         2149 $ctx->ok($actual =~ $regex, $message);
272 2 50       476 $ctx->diag("code $actual does not match $regex")
273             unless $actual =~ $regex;
274 2         9 $ctx->release;
275              
276 2         91 $self;
277             }
278              
279              
280             sub message_like
281             {
282 80     80 1 1327 my($self, $regex, $message) = @_;
283              
284 80   50     442 $message //= "response message matches";
285              
286 80         227 my $ok = 0;
287              
288 80   50     123 my @message = @{ (eval { $self->res->message }) // [] };
  80         158  
  80         440  
289 80         192 foreach my $line (@message)
290             {
291 82 100       728 $ok = 1 if $line =~ $regex;
292             }
293              
294 80         289 my $ctx = context();
295 80         7816 $ctx->ok($ok, $message);
296 80 50       10658 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         275 $ctx->release;
303              
304 80         3063 $self;
305             }
306              
307              
308             sub message_is
309             {
310 23     23 1 260 my($self, $string, $message) = @_;
311              
312 23   50     110 $message //= "response message matches";
313              
314 23         48 my $ok = 0;
315              
316 23   50     33 my @message = @{ (eval { $self->res->message }) // [] };
  23         37  
  23         99  
317              
318 23         63 foreach my $line (@message)
319             {
320 23 50       66 $ok = 1 if $line eq $string;
321             }
322              
323 23         56 my $ctx = context();
324 23         2085 $ctx->ok($ok, $message);
325 23 50       4499 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         80 $ctx->release;
332              
333 23         830 $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 2059 my($self, $location, $message) = @_;
367              
368 6 100 33     56 $message //= defined $location ? "nlst: $location" : 'nlst';
369              
370 6         12 my $client = eval { $self->_client };
  6         241  
371 6         79 my $error = $@;
372              
373 6         28 $self->content('');
374              
375 6 50       24 unless($error)
376             {
377 6         10 my $list = eval { $client->nlst($location)->recv };
  6         32  
378 6         257 $error = $@;
379 6 50       82 $self->content(join "\n", @$list, '') unless $error;
380             }
381              
382 6         48 my $ctx = context();
383 6         1093 $ctx->ok($error eq '', $message);
384 6 50       1965 $ctx->diag($error) if $error;
385 6         38 $ctx->release;
386              
387 6         297 $self;
388             }
389              
390              
391             sub _display_content
392             {
393 6     6   16 state $temp;
394 6         18 state $counter = 0;
395 6         17 my $method = 'diag';
396             #$method = 'note' if $tb->todo;
397              
398 6 100       27 unless(defined $temp)
399             {
400 2         18 $temp = tempdir(CLEANUP => 1);
401             }
402              
403 6         2232 my $file = $temp->file(sprintf("data.%d", $counter++));
404 6         799 $file->spew($_[0]);
405              
406 6         2664 my $ctx = context();
407              
408 6 50       742 if(-T $file)
409             {
410 6         777 $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         5568 $ctx->release;
425              
426 6         112 $file->remove;
427             }
428              
429             sub content_is
430             {
431 9     9 1 31515 my($self, $string, $message) = @_;
432              
433 9   50     67 $message ||= 'content matches';
434              
435 9         43 my $ok = $self->content eq $string;
436              
437 9         34 my $ctx = context();
438 9         927 $ctx->ok($ok, $message);
439 9 100       3482 unless($ok)
440             {
441 3         16 $ctx->diag("content:");
442 3         1098 _display_content($self->content);
443 3         838 $ctx->diag("expected:");
444 3         1094 _display_content($string);
445             }
446              
447 9         738 $ctx->release;
448              
449 9         356 $self;
450             }
451              
452              
453             sub global_timeout_ok (;$$)
454             {
455 4     4 1 840248 my($timeout, $message) = @_;
456              
457 4   100     28 $timeout //= 120;
458 4   33     34 $message //= "global timeout of $timeout seconds";
459              
460 4         24 my $ctx = context();
461              
462 4         26963 state $timers = [];
463              
464 4         10 eval {
465             push @$timers, AnyEvent->timer(
466             after => $timeout,
467 0     0   0 cb => sub { $ctx->diag("GLOBAL TIMEOUT"); exit },
  0         0  
468 4         69 );
469             };
470 4         6377 my $error = $@;
471              
472 4         42 my $ok = $error eq '';
473              
474 4         34 $ctx->ok($ok, $message);
475 4 50       984 $ctx->diag($error) if $error;
476              
477 4         25 $ctx->release;
478              
479 4         195 $ok;
480             }
481              
482             sub import
483             {
484 21     21   252 my $caller = caller;
485 22     22   291 no strict 'refs';
  22         53  
  22         2349  
486 21         93 *{join '::', $caller, 'create_ftpserver_ok'} = \&create_ftpserver_ok;
  21         175  
487 21         69 *{join '::', $caller, 'global_timeout_ok'} = \&global_timeout_ok;
  21         706093  
488             }
489              
490 22     22   2039 BEGIN { eval 'use EV' }
  22     22   15385  
  22         64524  
  22         1392  
491              
492             1;
493              
494             __END__