AnyEvent-FTP
view release on metacpan or search on metacpan
lib/Test/AnyEventFTPServer.pm view on Meta::CPAN
package Test::AnyEventFTPServer;
use strict;
use warnings;
use 5.010;
use Moo;
use URI;
use AnyEvent;
use Test2::API qw( context );
use Path::Class qw( tempdir );
extends 'AnyEvent::FTP::Server';
# ABSTRACT: Test (non-blocking) ftp clients against a real FTP server
our $VERSION = '0.20'; # VERSION
has test_uri => (
is => 'ro',
required => 1,
);
has res => (
is => 'rw',
);
has content => (
is => 'rw',
default => '',
);
has auto_login => (
is => 'rw',
default => sub { 1 },
);
has _client => (
is => 'ro',
lazy => 1,
default => sub {
my $self = shift;
require AnyEvent::FTP::Client;
my $client = AnyEvent::FTP::Client->new;
my $cv = AnyEvent->condvar;
my $timer = AnyEvent->timer(
after => 5,
cb => sub { $cv->croak("timeout connecting with ftp client") },
);
if($self->auto_login)
{
$client->connect($self->test_uri)
->cb(sub { $cv->send });
}
else
{
$client->connect($self->test_uri->host, $self->test_uri->port)
->cb(sub { $cv->send });
}
$cv->recv;
$client;
},
);
sub create_ftpserver_ok (;$$)
{
my($context, $message) = @_;
my $ctx = context();
my $uri = URI->new("ftp://127.0.0.1");
$context //= 'Memory';
$context = "AnyEvent::FTP::Server::Context::$context"
unless $context =~ /::/;
my $name = (split /::/, $context)[-1];
my $user = join '', map { chr(ord('a') + int rand(26)) } (1..10);
my $pass = join '', map { chr(ord('a') + int rand(26)) } (1..10);
$uri->userinfo(join(':', $user, $pass));
my $server;
eval {
$server = Test::AnyEventFTPServer->new(
default_context => $context,
hostname => '127.0.0.1',
port => undef,
test_uri => $uri,
);
if($ENV{AEF_DEBUG})
{
$server->on_connect(sub {
my $con = shift;
$ctx->note("CONNECT");
$con->on_request(sub {
my $raw = shift;
$ctx->note("CLIENT: $raw");
});
$con->on_response(sub {
my $raw = shift;
$ctx->note("SERVER: $raw");
});
$con->on_close(sub {
$ctx->note("DISCONNECT");
});
});
}
$server->on_connect(sub {
shift->context->authenticator(sub {
return $_[0] eq $user && $_[1] eq $pass;
});
});
my $cv = AnyEvent->condvar;
my $timer = AnyEvent->timer(
after => 5,
cb => sub { $cv->croak("timeout creating ftp server") },
);
$server->on_bind(sub {
$uri->port(shift);
$cv->send;
});
$server->start;
$cv->recv;
};
my $error = $@;
$message //= "created FTP ($name) server at $uri";
$ctx->ok($error eq '', $message);
$ctx->diag($error) if $error;
$ctx->release;
$server;
}
sub connect_ftpclient_ok
{
my($self, $message) = @_;
my $client;
eval {
require AnyEvent::FTP::Client;
$client = AnyEvent::FTP::Client->new;
my $cv = AnyEvent->condvar;
my $timer = AnyEvent->timer(
after => 5,
cb => sub { $cv->croak("timeout connecting with ftp client") },
);
if($self->auto_login)
{
$client->connect($self->test_uri)
->cb(sub { $cv->send });
}
else
{
$client->connect($self->tesT_uri->host, $self->test_uri->port)
->cb(sub { $cv->send });
}
$cv->recv;
};
my $error = $@;
$message //= "connected to FTP server at " . $self->test_uri;
my $ctx = context();
$ctx->ok($error eq '', $message);
$ctx->diag($error) if $error;
$ctx->release;
$client;
}
sub help_coverage_ok
{
my($self, $class, $message) = @_;
$class //= $self->default_context;
my @missing;
my $client = eval { $self->_client };
my $error = $@;
my $count = 0;
unless($error)
{
foreach my $cmd (map { uc $_ } grep s/^cmd_//, eval qq{ use $class; keys \%${class}::;})
{
if((eval { $client->help($cmd)->recv } || $@)->code != 214)
{ push @missing, $cmd }
$count++;
}
}
$message //= "help coverage for $class";
my $ctx = context();
$ctx->ok($error eq '' && @missing == 0, $message);
$ctx->diag($error) if $error;
$ctx->diag("commands missing help: @missing") if @missing;
$ctx->diag("didn't find ANY commands for class: $class")
if $count == 0;
$ctx->release;
lib/Test/AnyEventFTPServer.pm view on Meta::CPAN
my $file = $temp->file(sprintf("data.%d", $counter++));
$file->spew($_[0]);
my $ctx = context();
if(-T $file)
{
$ctx->$method(" $_") for split /\n/, $_[0];
}
else
{
if(eval { require Data::HexDump })
{
$ctx->$method(" $_") for grep !/^$/, split /\n/, Data::HexDump::HexDump($_[0]);
}
else
{
$ctx->$method(" binary content");
}
}
$ctx->release;
$file->remove;
}
sub content_is
{
my($self, $string, $message) = @_;
$message ||= 'content matches';
my $ok = $self->content eq $string;
my $ctx = context();
$ctx->ok($ok, $message);
unless($ok)
{
$ctx->diag("content:");
_display_content($self->content);
$ctx->diag("expected:");
_display_content($string);
}
$ctx->release;
$self;
}
sub global_timeout_ok (;$$)
{
my($timeout, $message) = @_;
$timeout //= 120;
$message //= "global timeout of $timeout seconds";
my $ctx = context();
state $timers = [];
eval {
push @$timers, AnyEvent->timer(
after => $timeout,
cb => sub { $ctx->diag("GLOBAL TIMEOUT"); exit },
);
};
my $error = $@;
my $ok = $error eq '';
$ctx->ok($ok, $message);
$ctx->diag($error) if $error;
$ctx->release;
$ok;
}
sub import
{
my $caller = caller;
no strict 'refs';
*{join '::', $caller, 'create_ftpserver_ok'} = \&create_ftpserver_ok;
*{join '::', $caller, 'global_timeout_ok'} = \&global_timeout_ok;
}
BEGIN { eval 'use EV' }
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Test::AnyEventFTPServer - Test (non-blocking) ftp clients against a real FTP server
=head1 VERSION
version 0.20
=head1 SYNOPSIS
use Test2:V0;
use Test::AnyEventFTPServer;
# exit this script after 30s to avoid hung test
global_timeout_ok;
# $test_server isa AnyEvent::FTP::Server
# and isa Test::AnyEventFTPServer
my $test_server = create_ftpserver_ok;
$test_server->command_ok('HELP')
->code_is(214)
->message_like(qr{the following commands are recognize});
# $res isa AnyEvent::FTP::Client::Response
# from that last HELP command
( run in 1.481 second using v1.01-cache-2.11-cpan-5b529ec07f3 )