————————————# ABSTRACT: A class for testing a Pinto server
package
Pinto::Server::Tester;
use
Moose;
use
Carp;
use
Test::TCP;
use
File::Which;
use
Proc::Fork;
use
LWP::UserAgent;
#-------------------------------------------------------------------------------
# VERSION
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
=attr pintod_opts( \@args )
Passes additional C<@args> to the F<pintod> command line. Default is empty.
=cut
has
pintod_opts
=> (
is
=>
'ro'
,
isa
=> ArrayRef,
default
=>
sub
{ [] },
lazy
=> 1,
);
=attr server_port( $integer )
Sets the port that the server will listen on. If not specified during
construction, defaults to a randomly generated but open port.
=cut
has
server_port
=> (
is
=>
'ro'
,
isa
=> Int,
default
=>
sub
{ empty_port() },
lazy
=> 1,
);
=attr server_host( $hostname )
Sets the hostname that the server will bind to. Defaults to C<localhost>.
=cut
has
server_host
=> (
is
=>
'ro'
,
isa
=> Str,
init_arg
=>
undef
,
default
=>
'localhost'
,
lazy
=> 1,
);
=attr server_pid
Returns the process id for the server (if it has been started). Read-only.
=cut
has
server_pid
=> (
is
=>
'rw'
,
isa
=> Int,
init_arg
=>
undef
,
default
=> 0,
);
=attr server_url
Returns the full URL that the server will listen on. Read-only.
=cut
has
server_url
=> (
is
=>
'ro'
,
isa
=> Uri,
init_arg
=>
undef
,
);
=attr pintod_exe
Sets the path to the C<pintod> executable. If not specified, we will search
in F<./blib/script>, F<./bin>, C<PINTO_HOME>, and finally your C<PATH> An
exception is thrown if C<pintod> cannot be found.
=cut
has
pintod_exe
=> (
is
=>
'ro'
,
isa
=> File,
builder
=>
'_build_pintod_exe'
,
coerce
=> 1,
lazy
=> 1,
);
#-------------------------------------------------------------------------------
sub
_build_pintod_exe {
my
(
$self
) =
@_
;
# Look inside the dist directory
for
my
$dir
( [
qw(blib script)
], [
qw(bin)
] ) {
my
$pintod
= dir( @{
$dir
} )->file(
'pintod'
);
return
$pintod
if
-e
$pintod
;
}
# Look at PINTO_HOME
return
dir(
$ENV
{PINTO_HOME} )->file(
qw(bin pintod)
)
if
$ENV
{PINTO_HOME};
# Look anywhere in PATH
return
which(
'pintod'
)
|| croak
'Unable to find pintod anywhere'
;
}
#-------------------------------------------------------------------------------
=method start_server()
Starts the L<pintod> server. Emits a warning if the server is already started.
=cut
sub
start_server {
my
(
$self
) =
@_
;
carp
'Server already started'
and
return
if
$self
->server_pid;
local
$ENV
{PLACK_ENV} =
'testing'
;
# Suppresses startup message
local
$ENV
{PLACK_SERVER} =
'HTTP::Server::PSGI'
;
# Basic non-forking server
local
$ENV
{PINTO_LOCKFILE_TIMEOUT} = 2;
# Don't make tests wait!
local
$ENV
{PINTO_STALE_LOCKFILE_TIMEOUT} = 0;
# Don't expire stale locks
run_fork {
child {
my
$xtra_lib
=
$self
->_extra_lib;
my
$xtra_opts
=
$self
->pintod_opts;
my
%opts
= (
'--port'
=>
$self
->server_port,
'--root'
=>
$self
->root );
my
@cmd
= ( $^X,
$xtra_lib
,
$self
->pintod_exe,
%opts
, @{
$xtra_opts
} );
$self
->tb->note(
sprintf
'exec(%s)'
,
join
' '
,
@cmd
);
exec
@cmd
;
}
parent {
my
$server_pid
=
shift
;
$self
->server_pid(
$server_pid
);
sleep
15;
# Let the server warm up
}
error {
croak
"Failed to fork: $!"
;
}
};
return
$self
;
}
#-------------------------------------------------------------------------------
=method stop_server()
Stops the L<pintod> server. Emits a warning if the server is not
currently running.
=cut
sub
stop_server {
my
(
$self
) =
@_
;
my
$server_pid
=
$self
->server_pid;
carp
'Server was never started'
and
return
if
not
$server_pid
;
carp
"Server $server_pid not running"
and
return
if
not
kill
0,
$server_pid
;
# TODO: Consider using Proc::Terminator instead
kill
'TERM'
,
$server_pid
;
sleep
5 and
waitpid
$server_pid
, 0;
return
$self
;
}
#-------------------------------------------------------------------------------
=method server_running_ok()
Asserts that the server is running.
=cut
sub
server_running_ok {
my
(
$self
) =
@_
;
my
$server_pid
=
$self
->server_pid;
my
$server_port
=
$self
->server_port;
my
$ok
=
kill
0,
$server_pid
;
# Is this portable?
return
$self
->tb->ok(
$ok
,
"Server $server_pid is running on port $server_port"
);
}
#-------------------------------------------------------------------------------
=method server_not_running_ok
Asserts that the server is not running.
=cut
sub
server_not_running_ok {
my
(
$self
) =
@_
;
my
$server_pid
=
$self
->server_pid;
my
$ok
= not
kill
0,
$server_pid
;
# Is this portable?
return
$self
->tb->ok(
$ok
,
"Server is not running with pid $server_pid"
);
}
#-------------------------------------------------------------------------------
=method can_connect
Returns true if the server is able to receive and respond to a connection
request.
=cut
sub
can_connect {
my
(
$self
) =
@_
;
# LWP uses a 500 error when the connection is refused. I'm not
# sure if this will be portable to otehr user agents. Might be
# better to open a raw socket. See IO::Socket::INET for that.
return
LWP::UserAgent->new->get(
$self
->server_url)->code != 500;
}
#-------------------------------------------------------------------------------
sub
to_string {
my
$self
=
shift
;
return
$self
->server_url->as_string;
}
#-------------------------------------------------------------------------------
sub
_extra_lib {
my
(
$self
) =
@_
;
my
$blib
= dir(
qw(blib lib)
);
my
$lib
= dir(
qw( lib)
);
return
"-I$blib"
if
-e
$blib
;
return
"-I$lib"
if
-e
$lib
;
return
''
;
}
#-------------------------------------------------------------------------------
sub
DEMOLISH {
my
(
$self
) =
@_
;
$self
->stop_server
if
$self
->server_pid;
return
;
}
#-------------------------------------------------------------------------------
__PACKAGE__->meta->make_immutable;
#-------------------------------------------------------------------------------
1;
__END__
=pod
=for stopwords responder
=for Pod::Coverage DEMOLISH
=cut