Day 7 - Testing with an External Web Server

Adding built in tests for an external web server to your application.

The Problem

In some cases you might want your Catalyst application to be deployable in a number of different situations. To be comprehensive you need to build a test environment that will test your deployment in each environment. Here's how to do this so that you can test simultaneously with the built in server, Apache and Lighttpd, and includes how to deploy from the root of the web server or a sub directory.

Catalyst Testing

Essentially the aim of testing is to assess that your application behaves in the expected manner. Catalyst::Test and Test::WWW::Mechanize::Catalyst provides the methods to test your catalyst applications. These are based on lwp and Test::WWW::Mechanize respectively. Bu default these two Catalyst test modules use the catalyst dispatcher directly to test your application. This way you avoid having to use an server process during development. However you can also use an external server to run your tests as well by setting the CATALYST_SERVER environment variable. For example :

 $ script/myapp_server.pl
 [ snipped output]
 You can connect to your server at http://zaphod.local:3000

And then from another terminal I do the following in the application root directory:

 $ CATALYST_SERVER='http://localhost:3000/' prove -r -l lib/ t/

This will run the tests in the t/ directory using the built in server instance running on port 3000. The same can be done for an external deployment, for example the following:

 $  $ CATALYST_SERVER='http://www.example.com/myapp' prove -r -l lib/
 t/

And you run your tests on the external server.

Another approach is to automatically configure a catalyst application running on an external server your local computer. The rest of this entry explores this.

Automating testing with a local lighttpd installation

In the t/ directory of your application, you can place a script: optional_lighttpd-fastcgi-non-root.pl which contains the following code:

 #!/usr/bin/perl

 use strict;
 use warnings;

 use File::Path;
 use File::Slurp qw(write_file);
 use FindBin;
 use IO::Socket;
 use Test::More;

 eval "use File::Copy::Recursive";
 plan skip_all => 'File::Copy::Recursive required' if $@;

 my $lighttpd_bin = $ENV{LIGHTTPD_BIN} || 'lighttpd';
 plan skip_all => 'Cannot find lighttpd, please set LIGHTTPD_BIN'
     unless -x $lighttpd_bin;

 plan tests => 1;

Place any application specific configuration you need here, to for example create a version of your configuration which suits a testing environment (i.e. you can nuke the tests after the installation).

Next, set up a local configuration for lighttpd:

 my $docroot = "$FindBin::Bin/../t/tmp";
 my $port    = 8529;

 # Clean up docroot path
 $docroot =~ s{/t/..}{};

 my $conf = qq{
 # basic lighttpd config file for testing fcgi+catalyst
 server.modules = (
     "mod_access",
     "mod_fastcgi",
     "mod_accesslog"
 )

 server.document-root = "$docroot"

 server.errorlog    = "$docroot/error.log"
 accesslog.filename = "$docroot/access.log"

 server.bind = "127.0.0.1"
 server.port = $port

 # catalyst app specific fcgi setup
 fastcgi.server = (
     "" => (
         "FastCgiTest" => (
             "socket"       => "$docroot/test.socket",
             "check-local"  => "disable",
             "bin-path"     => "$docroot/lib/MyApp/myapp/myapp_fastcgi.pl",
             "min-procs"    => 1,
             "max-procs"    => 1,
             "idle-timeout" => 20
         )
     )
 )
 };

 write_file "$docroot/lighttpd.conf", $conf;

Code to start this configuration of lighttpd:

 my $pid = open my $lighttpd, "$lighttpd_bin -D -f $docroot/lighttpd.conf 2>
&1 |" 
     or die "Unable to spawn lighttpd: $!";

 # wait for it to start
 print "Waiting for server to start...\n";
 while ( check_port( 'localhost', $port ) != 1 ) {
     sleep 1;
 }




And finally code to run the tests:

  $ENV{CATALYST_SERVER} = "http://localhost:$port";
  system( 'prove -r -l t' ); 




The final part of the script shuts down the local server and cleans up the local configuration

 # shut it down
 kill 'INT', $pid;
 close $lighttpd;

 # clean up
 rmtree "$FindBin::Bin/../t/tmp" if -d "$FindBin::Bin/../t/tmp";

 ok( 'done' );




Deploying on a server subdirectory:

To make a configuration that deploys on a subdirectory, instead of this in the lighttpd config:

 fastcgi.server = (
     "" => (
         "FastCgiTest" => (

We replace it with this:

 fastcgi.server = (
     "/myapp" => (
         "FastCgiTest" => (

And replace this:

  $ENV{CATALYST_SERVER} = "http://localhost:$port";

with this:

  $ENV{CATALYST_SERVER} = "http://localhost:$port/myapp";

Of course you can do both. I have one script which is optional_lighttpd-fastcgi.pl and another which is optional_lighttpd-fastcgi-non-root.pl.

Running the tests

To run these tests just issue the following command from the application root:

 $ perl -ilib t/optional_lighttpd-fastcgi.pl

Apache

Apache is a little more complicated in some ways. This time we use the Apache::Test module to help us build a local instance of the installed apache. This is because Apache::Test automatically discovers information about your local Apache installation which is a complicated job to do manually. It requires creating a few more files and directories than the lighttpd test harness. It seems that the configuration for root and non-root deployments needs to be in the same config file due to limitations with Apache::Test (please correct me if I'm wrong here).

Here's start of the optional_apache-fastcgi.pl script:

 #!/usr/bin/perl
 use strict;
 use warnings;

 use Apache::Test;
 use Apache::TestRun ();

 use File::Path;
 use File::Copy::Recursive;
 use FindBin;
 use IO::Socket;

 # clean up
 warn "cleaning up last test run\n";
 rmtree "$FindBin::Bin/../t/tmp" if -d "$FindBin::Bin/../t/tmp";

And again here is where you want to tweak the configuration of the application to reflect the test environment.

 $ENV{CATALYST_SERVER} = 'http://localhost:8529/';

 Apache::TestRun->new->run(@ARGV);

In fact this test is based on the code from http://dev.catalyst.perl.org/repos/Catalyst/trunk/Catalyst-Runtime/t/optional_apache-fastcgi.pl which shows the complete set-up tear down implementation of this.

To configure the apache installation we need to provide a file called extra.conf in the directory t/conf. That will look something like this:

 <IfModule mod_fastcgi.c>
     FastCgiIpcDir /path/to/MyApp/t/tmp/tmp
     FastCgiServer /path/to/MyApp/script/myapp_fastcgi.pl -idle-timeout 300 -processes 1
      ScriptAlias / /path/to/MyApp/t/tmp/lib/MyApp/script/myapp_fastcgi.pl/
     ScriptAlias /fastcgi/ /path/to/MyApp/script/myapp_fastcgi.pl/
 </IfModule>

for deployment on both root and non-root locations. Tweak the value of $ENV{CATALYST_SERVER} to reflect the one you want to test, or indeed create a separate test script as for lighttpd.

We run the tests in the same way as for lighttpd.

wrap up

Here we have shown how it is fairly easy to test for a wide variety of deployment scenarios using the tools that Catalyst and CPAN provide. Of course it would be fairly easy to turn this into a Catalyst::Helper which may well be the next step for this project.

AUTHOR

Kieren Diment <diment@gmail.com>