The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Nginx::Test - testing framework for nginx-perl and nginx

SYNOPSIS

    use Nginx::Test;
     
    my $nginx = find_nginx_perl;
    my $dir   = make_path 'tmp/test';
    
    my ($child, $peer) = 
        fork_nginx_handler_die  $nginx, $dir, '', <<'END';
        
        sub handler {
            my $r = shift;
            ...
             
            return OK;
        }
        
    END
    
    wait_for_peer $peer, 2
        or die "peer never started\n";
    
    my ($body, $headers) = http_get $peer, "/", 2;
    ...
    

DESCRIPTION

Making sure testing isn't a nightmare.

This module provides some basic functions to find nginx-perl, prepare configuration, generate handler, start in a child process, query it and get something back. And it comes with Nginx::Perl. You can simply add it as a dependency for you module and use.

EXPORT

    find_nginx_perl
    get_nginx_conf_args_die
    get_unused_port 
    wait_for_peer 
    prepare_nginx_dir_die
    cat_nginx_logs
    fork_nginx_die
    fork_child_die
    http_get
    get_nginx_incs
    fork_nginx_handler_die
    eval_wait_sub
    connect_peer
    send_data
    parse_http_request
    parse_http_response
    inject_content_length
    read_http_response
    make_path
    cat_logs

FUNCTIONS

find_nginx_perl

Finds executable binary for nginx-perl. Returns executable path or undef if not found.

    my $nginx = find_nginx_perl
        or die "Cannot find nginx-perl\n";
    
    # $nginx = './objs/nginx-perl'

get_unused_port

Returns available port number to bind to. Tries to use it first and returns undef if fails.

    $port = get_unused_port
        or die "No unused ports\n";

wait_for_peer "$host:$port", $timeout

Tries to connect to $host:$port within $timeout seconds. Returns 1 on success and undef on error.

    wait_for_peer "127.0.0.1:1234", 2
        or die "Failed to connect to 127.0.0.1:1234 within 2 seconds";

prepare_nginx_dir_die $dir, $conf, @pkgs

Creates directory tree suitable to run nginx-perl from. Puts there config and packages specified as string scalars. Dies on errors.

    prepare_nginx_dir_die "tmp/foo", <<'ENDCONF', <<'ENDONETWO';
    
        worker_processes  1;
        events {  
            worker_connections  1024;  
        }
        http {
            server {
                location / {
                    ...
                }
            }
        }
     
    ENDCONF
    
        package One::Two;
        
        sub handler {
            ...
        }
        
        1;
    
    ENDONETWO

cat_nginx_logs $dir

Returns all logs from $dir.'/logs' as a single scalar. Useful for diagnostics.

    diag cat_nginx_logs $dir;

fork_nginx_die $nginx, $dir

Forks nginx-perl using executable binary from $nginx and prepared directory path from $dir and returns guard object. Dies on errors. Internally does something like this: "$nginx -p $dir"

    my $child = fork_nginx_die $nginx, $dir;
    ...
     
    undef $child;

fork_child_die sub {}

Forks sub in a child process and returns its guard object. Dies on errors.

    my $child = fork_child_die sub {
        ...
        sleep 5;  
    };
     
    undef $child;

get_nginx_conf_args_dir $nginx

Runs nginx-perl -V, parses its output and returns a set of keys out of the list of configure arguments.

    my %CONFARGS = get_nginx_conf_args_dir;
    
    # %CONFARGS = ( '--with-http_ssl_module' => 1,
    #               '--with-...'             => 1  )

http_get $peer, $uri, $timeout

Connects to $peer, sends GET request and return its $body and parsed $headers.

    my ($body, $headers) = http_get '127.0.0.1:1234', '/', 2;
    
    $headers = {  _status          => 200,
                  _message         => 'OK',
                  _version         => 'HTTP/1.0',
                  'content-type'   => ['text/html'],
                  'content-length' => [1234],
                  ...                               }

get_nginx_incs $nginx, $dir

Returns proper @INC to use in nginx-perl.conf during tests.

    my @incs = get_nginx_incs $nginx, $dir;

fork_nginx_handler_dir $nginx, $dir, $conf, $code

Gets unused port, prepares directory for nginx with predefined package name, forks nginx and gives you a child object and generated peer back. Allows to inject $conf into nginx-perl.conf and $code into the package. Expects to found sub handler { ... } in $code. Dies on errors.

    my ($child, $peer) = 
        fork_nginx_handler_die $nginx, $dir, <<'ENDCONF', <<'ENDCODE';
        
        resolver 8.8.8.8;
        
    ENDCONF

        sub handler {
            my ($r) = @_;
            ...
            
            return OK;
        }
        
    ENDCODE
    ...
     
    undef $child; 

Be aware that this function is not suited for every module. It expects $dir to be relative to the current directory or any of its subdirectories, i.e. foo, foo/bar. And also expects blib/lib and blib/arch to contain your libraries, which is where ExtUtils::MakeMaker puts them.

eval_wait_sub $name, $timeout, $sub

Wraps eval block around subroutine $sub, sets alarm to $timeout and waits for sub to finish. Returns undef on alarm and if $sub dies.

    my $rv = eval_wait_sub "test1", 5, sub {
        ...
        pass "test1";
    };
    
    fail "test1"  unless $rv;

connect_peer "$host:$port", $timeout

Tries to connect to $host:$port within $timeout seconds. Returns socket handle on success or undef otherwise.

    $sock = connect_peer "127.0.0.1:55555", 5
        or ...;

send_data $sock, $buf, $timeout

Sends an entire $buf to the socket $sock in $timeout seconds. Returns amount of data sent on success or undef otherwise. This amount is guessed since print is used to send data.

    send_data $sock, $buf, 5
        or ...;

parse_http_request $buf, $r

Parses HTTP request from $buf and puts parsed data structure into $r. Returns length of the header in bytes on success or undef on error. Returns 0 if cannot find header separator "\n\n" in $buf.

Data returned in the following form:

    $r = { 'connection'    => ['close'],
           'content-type'  => ['text/html'],
           ...
           '_method'       => 'GET',
           '_request_uri'  => '/?foo=bar',
           '_version'      => 'HTTP/1.0',
           '_uri'          => '/',
           '_query_string' => 'foo=bar',
           '_keepalive'    => 0              };

Example:

    $len = parse_http_request $buf, $r;
    
    if ($len) {
        # ok
        substr $buf, 0, $len, '';
        warn Dumper $r;
    } elsif (defined $len) {
        # read more data 
        # and try again
    } else {
        # bad request
    }

parse_http_response $buf, $r

Parses HTTP response from $buf and puts parsed data structure into $r. Returns length of the header in bytes on success or undef on error. Returns 0 if cannot find header separator "\n\n" in $buf.

Data returned in the following form:

    $r = { 'connection'   => ['close'],
           'content-type' => ['text/html'],
           ...
           '_status'      => '404',
           '_message'     => 'Not Found',
           '_version'     => 'HTTP/1.0',
           '_keepalive'   => 0              };

Example:

    $len = parse_http_response $buf, $r;
    
    if ($len) {
        # ok
        substr $buf, 0, $len, '';
        warn Dumper $r;
    } elsif (defined $len) {
        # read more data 
        # and try again
    } else {
        # bad response
    }

inject_content_length $buf

Parses HTTP header and inserts Content-Length if needed, assuming that $buf contains entire request or response.

    $buf = "PUT /"          ."\x0d\x0a".
           "Host: foo.bar"  ."\x0d\x0a".
           ""               ."\x0d\x0a".
           "hello";
           
    inject_content_length $buf;

read_http_response $sock, $h, $timeout

Reads and parses HTTP response header from $sock into $h within $timeout seconds. Returns true on success or undef on error.

    read_http_response $sock, $h, 5
        or ...;

make_path $path

Creates directory tree specified by $path and returns this path or undef on error.

    $path = make_path 'tmp/foo'
        or die "Can't create tmp/foo: $!\n";

cat_logs $dir

Scans directory $dir for logs, concatenates them and returns.

    diag cat_logs $dir;

AUTHOR

Alexandr Gomoliako <zzz@zzz.org.ua>

LICENSE

Copyright 2011-2012 Alexandr Gomoliako. All rights reserved.

This module is free software. It may be used, redistributed and/or modified under the same terms as nginx itself.