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   3906989 use strict;
  22         153  
  22         722  
4 22     22   125 use warnings;
  22         50  
  22         625  
5 22     22   505 use 5.010;
  22         107  
6 22     22   11470 use Moo;
  22         197563  
  22         129  
7 22     22   41222 use URI;
  22         111178  
  22         870  
8 22     22   21716 use AnyEvent;
  22         111481  
  22         943  
9 22     22   207 use Test2::API qw( context );
  22         47  
  22         1634  
10 22     22   12444 use Path::Class qw( tempdir );
  22         814945  
  22         67917  
11              
12             extends 'AnyEvent::FTP::Server';
13              
14             # ABSTRACT: Test (non-blocking) ftp clients against a real FTP server
15             our $VERSION = '0.17'; # 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 19735 my($context, $message) = @_;
71              
72 26         142 my $ctx = context();
73              
74 26         102182 my $uri = URI->new("ftp://127.0.0.1");
75              
76 26   100     192322 $context //= 'Memory';
77 26 50       191 $context = "AnyEvent::FTP::Server::Context::$context"
78             unless $context =~ /::/;
79 26         178 my $name = (split /::/, $context)[-1];
80              
81 26         130 my $user = join '', map { chr(ord('a') + int rand(26)) } (1..10);
  260         762  
82 26         119 my $pass = join '', map { chr(ord('a') + int rand(26)) } (1..10);
  260         540  
83 26         254 $uri->userinfo(join(':', $user, $pass));
84              
85 26         4300 my $server;
86 26         161 eval {
87 26         411 $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       159 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     546 return $_[0] eq $user && $_[1] eq $pass;
119 29     29   1011 });
120 26         440 });
121              
122 26         336 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         33873 );
127             $server->on_bind(sub {
128 26     26   462 $uri->port(shift);
129 26         2631 $cv->send;
130 26         872 });
131 26         205 $server->start;
132 26         236 $cv->recv;
133             };
134 26         439 my $error = $@;
135              
136 26   33     659 $message //= "created FTP ($name) server at $uri";
137              
138 26         484 $ctx->ok($error eq '', $message);
139 26 50       6931 $ctx->diag($error) if $error;
140 26         190 $ctx->release;
141              
142 26         1073 $server;
143             }
144              
145              
146             sub connect_ftpclient_ok
147             {
148 5     5 1 1033 my($self, $message) = @_;
149 5         13 my $client;
150 5         12 eval {
151 5         2157 require AnyEvent::FTP::Client;
152 5         87 $client = AnyEvent::FTP::Client->new;
153 5         182 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         89 );
158 5 50       115 if($self->auto_login)
159             {
160             $client->connect($self->test_uri)
161 5     5   36 ->cb(sub { $cv->send });
  5         82  
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         83 $cv->recv;
169             };
170 5         169 my $error = $@;
171              
172 5   33     67 $message //= "connected to FTP server at " . $self->test_uri;
173              
174 5         66 my $ctx = context();
175 5         763 $ctx->ok($error eq '', $message);
176 5 50       1136 $ctx->diag($error) if $error;
177 5         34 $ctx->release;
178              
179 5         193 $client;
180             }
181              
182              
183             sub help_coverage_ok
184             {
185 10     10 1 117 my($self, $class, $message) = @_;
186              
187 10   66     85 $class //= $self->default_context;
188              
189 10         84 my @missing;
190              
191 10         25 my $client = eval { $self->_client };
  10         315  
192 10         38 my $error = $@;
193              
194 10         52 my $count = 0;
195 10 50       51 unless($error)
196             {
197 10     7   1126 foreach my $cmd (map { uc $_ } grep s/^cmd_//, eval qq{ use $class; keys \%${class}::;})
  96     3   201  
  7         72  
  7         15  
  7         228  
  3         26  
  3         6  
  3         94  
198             {
199 96 50 33     168 if((eval { $client->help($cmd)->recv } || $@)->code != 214)
200 0         0 { push @missing, $cmd }
201 96         331 $count++;
202             }
203             }
204              
205 10   33     174 $message //= "help coverage for $class";
206              
207 10         105 my $ctx = context();
208 10   33     1519 $ctx->ok($error eq '' && @missing == 0, $message);
209 10 50       2055 $ctx->diag($error) if $error;
210 10 50       127 $ctx->diag("commands missing help: @missing") if @missing;
211 10 50       37 $ctx->diag("didn't find ANY commands for class: $class")
212             if $count == 0;
213 10         58 $ctx->release;
214              
215 10         425 $self;
216             }
217              
218              
219             sub command_ok
220             {
221 145     145 1 17724 my($self, $command, $args, $message) = @_;
222              
223 145         383 my $client = eval { $self->_client };
  145         3928  
224 145         1249 my $error = $@;
225              
226 145 50       482 unless($error)
227             {
228 145   66     254 my $res = (eval { $client->push_command([$command, $args])->recv } || $@);
229 145 50       578 if(eval { $res->isa('AnyEvent::FTP::Client::Response') })
  145         738  
230 145         643 { $self->res($res) }
231             else
232 0         0 { $error = $res; $self->res(undef) }
  0         0  
233             }
234              
235 145   33     844 $message //= "command: $command";
236              
237 145         511 my $ctx = context();
238 145         14408 $ctx->ok($error eq '', $message);
239 145 50       24804 $ctx->diag($error) if $error;
240 145         615 $ctx->release;
241              
242 145         4446 $self;
243             }
244              
245              
246             sub code_is
247             {
248 140     140 1 1657 my($self, $code, $message) = @_;
249              
250 140   33     880 $message //= "response code is $code";
251              
252 140         490 my $ctx = context();
253 140   50     12433 my $actual = eval { $self->res->code } // 'undefined';
  140         768  
254 140         723 $ctx->ok($actual == $code, $message);
255 140 100       23671 $ctx->diag("actual code returned is $actual")
256             unless $actual == $code;
257 140         1909 $ctx->release;
258              
259 140         4041 $self;
260             }
261              
262              
263             sub code_like
264             {
265 2     2 1 30 my($self, $regex, $message) = @_;
266              
267 2   50     13 $message //= "response code matches";
268              
269 2         8 my $ctx = context();
270 2   50     184 my $actual = eval { $self->res->code } // 'undefined';
  2         13  
271 2         30 $ctx->ok($actual =~ $regex, $message);
272 2 50       280 $ctx->diag("code $actual does not match $regex")
273             unless $actual =~ $regex;
274 2         8 $ctx->release;
275              
276 2         56 $self;
277             }
278              
279              
280             sub message_like
281             {
282 80     80 1 1188 my($self, $regex, $message) = @_;
283              
284 80   50     375 $message //= "response message matches";
285              
286 80         128 my $ok = 0;
287              
288 80   50     113 my @message = @{ (eval { $self->res->message }) // [] };
  80         134  
  80         411  
289 80         187 foreach my $line (@message)
290             {
291 82 100       584 $ok = 1 if $line =~ $regex;
292             }
293              
294 80         370 my $ctx = context();
295 80         6703 $ctx->ok($ok, $message);
296 80 50       11877 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         301 $ctx->release;
303              
304 80         2364 $self;
305             }
306              
307              
308             sub message_is
309             {
310 23     23 1 249 my($self, $string, $message) = @_;
311              
312 23   50     135 $message //= "response message matches";
313              
314 23         32 my $ok = 0;
315              
316 23   50     41 my @message = @{ (eval { $self->res->message }) // [] };
  23         31  
  23         115  
317              
318 23         60 foreach my $line (@message)
319             {
320 23 50       67 $ok = 1 if $line eq $string;
321             }
322              
323 23         74 my $ctx = context();
324 23         1928 $ctx->ok($ok, $message);
325 23 50       3254 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         97 $ctx->release;
332              
333 23         651 $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 3636 my($self, $location, $message) = @_;
367              
368 6 100 33     71 $message //= defined $location ? "nlst: $location" : 'nlst';
369              
370 6         17 my $client = eval { $self->_client };
  6         257  
371 6         85 my $error = $@;
372              
373 6         39 $self->content('');
374              
375 6 50       27 unless($error)
376             {
377 6         14 my $list = eval { $client->nlst($location)->recv };
  6         35  
378 6         459 $error = $@;
379 6 50       87 $self->content(join "\n", @$list, '') unless $error;
380             }
381              
382 6         53 my $ctx = context();
383 6         1328 $ctx->ok($error eq '', $message);
384 6 50       2453 $ctx->diag($error) if $error;
385 6         48 $ctx->release;
386              
387 6         359 $self;
388             }
389              
390              
391             sub _display_content
392             {
393 6     6   14 state $temp;
394 6         14 state $counter = 0;
395 6         15 my $method = 'diag';
396             #$method = 'note' if $tb->todo;
397              
398 6 100       24 unless(defined $temp)
399             {
400 2         19 $temp = tempdir(CLEANUP => 1);
401             }
402              
403 6         1964 my $file = $temp->file(sprintf("data.%d", $counter++));
404 6         713 $file->spew($_[0]);
405              
406 6         1897 my $ctx = context();
407              
408 6 50       583 if(-T $file)
409             {
410 6         675 $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         4351 $ctx->release;
425              
426 6         95 $file->remove;
427             }
428              
429             sub content_is
430             {
431 9     9 1 46917 my($self, $string, $message) = @_;
432              
433 9   50     84 $message ||= 'content matches';
434              
435 9         49 my $ok = $self->content eq $string;
436              
437 9         50 my $ctx = context();
438 9         1269 $ctx->ok($ok, $message);
439 9 100       3956 unless($ok)
440             {
441 3         19 $ctx->diag("content:");
442 3         1131 _display_content($self->content);
443 3         564 $ctx->diag("expected:");
444 3         842 _display_content($string);
445             }
446              
447 9         434 $ctx->release;
448              
449 9         409 $self;
450             }
451              
452              
453             sub global_timeout_ok (;$$)
454             {
455 4     4 1 434 my($timeout, $message) = @_;
456              
457 4   100     33 $timeout //= 120;
458 4   33     38 $message //= "global timeout of $timeout seconds";
459              
460 4         28 my $ctx = context();
461              
462 4         24419 state $timers = [];
463              
464 4         13 eval {
465             push @$timers, AnyEvent->timer(
466             after => $timeout,
467 0     0   0 cb => sub { $ctx->diag("GLOBAL TIMEOUT"); exit },
  0         0  
468 4         53 );
469             };
470 4         7095 my $error = $@;
471              
472 4         17 my $ok = $error eq '';
473              
474 4         32 $ctx->ok($ok, $message);
475 4 50       1073 $ctx->diag($error) if $error;
476              
477 4         30 $ctx->release;
478              
479 4         150 $ok;
480             }
481              
482             sub import
483             {
484 21     21   215 my $caller = caller;
485 22     22   358 no strict 'refs';
  22         79  
  22         2331  
486 21         67 *{join '::', $caller, 'create_ftpserver_ok'} = \&create_ftpserver_ok;
  21         174  
487 21         69 *{join '::', $caller, 'global_timeout_ok'} = \&global_timeout_ok;
  21         13435  
488             }
489              
490 22     22   2202 BEGIN { eval 'use EV' }
  22     22   16560  
  22         58294  
  22         1331  
491              
492             1;
493              
494             __END__