NAME

Test::Nginx::Socket - Socket-backed test scaffold for the Nginx C modules

SYNOPSIS

use Test::Nginx::Socket;

plan tests => $Test::Nginx::Socket::RepeatEach * 2 * blocks();

run_tests();

__DATA__

=== TEST 1: sanity
--- config
    location /echo {
        echo_before_body hello;
        echo world;
    }
--- request
    GET /echo
--- response_body
hello
world
--- error_code: 200


=== TEST 2: set Server
--- config
    location /foo {
        echo hi;
        more_set_headers 'Server: Foo';
    }
--- request
    GET /foo
--- response_headers
Server: Foo
--- response_body
hi


=== TEST 3: clear Server
--- config
    location /foo {
        echo hi;
        more_clear_headers 'Server: ';
    }
--- request
    GET /foo
--- response_headers_like
Server: nginx.*
--- response_body
hi


=== TEST 3: chunk size too small
--- config
    chunkin on;
    location /main {
        echo_request_body;
    }
--- more_headers
Transfer-Encoding: chunked
--- request eval
"POST /main
4\r
hello\r
0\r
\r
"
--- error_code: 400
--- response_body_like: 400 Bad Request

DESCRIPTION

This module provides a test scaffold based on non-blocking IO::Socket for automated testing in Nginx C module development.

This class inherits from Test::Base, thus bringing all its declarative power to the Nginx C module testing practices.

You need to terminate or kill any Nginx processes before running the test suite if you have changed the Nginx server binary. Normally it's as simple as

killall nginx
PATH=/path/to/your/nginx-with-memc-module:$PATH prove -r t

This module will create a temporary server root under t/servroot/ of the current working directory and starts and uses the nginx executable in the PATH environment.

You will often want to look into t/servroot/logs/error.log when things go wrong ;)

Sections supported

The following sections are supported:

config

Content of this section will be included in the "server" part of the generated config file. This is the place where you want to put the "location" directive enabling the module you want to test. Example:

location /echo {
    echo_before_body hello;
    echo world;
}

Sometimes you simply don't want to bother copying ten times the same configuration for the ten tests you want to run against your module. One way to do this is to write a config section only for the first test in your .t file. All subsequent tests will re-use the same config. Please note that this depends on the order of test, so you should run prove with variable TEST_NGINX_NO_SHUFFLE=1 (see below for more on this variable).

Please note that config section goes through environment variable expansion provided the variables to expand start with TEST_NGINX. So, the following is a perfectly legal (provided TEST_NGINX_HTML_DIR is set correctly):

location /main {
    echo_subrequest POST /sub -f $TEST_NGINX_HTML_DIR/blah.txt;
}

http_config

Content of this section will be included in the "http" part of the generated config file. This is the place where you want to put the "upstream" directive you might want to test. Example:

upstream database {
    postgres_server     127.0.0.1:$TEST_NGINX_POSTGRESQL_PORT
                        dbname=ngx_test user=ngx_test
                        password=wrong_pass;
}

As you guessed from the example above, this section goes through environment variable expansion (variables have to start with TEST_NGINX).

main_config

Content of this section will be included in the "main" part of the generated config file. This is very rarely used, except if you are testing nginx core itself.

This section goes through environment variable expansion (variables have to start with TEST_NGINX).

request

This is probably the most important section. It defines the request(s) you are going to send to the nginx server. It offers a pretty powerful grammar which we are going to walk through one example at a time.

In its most basic form, this section looks like that:

--- request
GET

This will just do a GET request on the root (i.e. /) of the server using HTTP/1.1.

Of course, you might want to test something else than the root of your web server and even use a different version of HTTP. This is possible:

--- request
GET /foo HTTP/1.0

Please note that specifying HTTP/1.0 will not prevent Test::Nginx from sending the Host header. Actually Test::Nginx always sends 2 headers: Host (with value localhost) and Connection (with value Close for simple requests and keep-alive for all but the last pipelined_requests).

You can also add a content to your request:

--- request
POST /foo
Hello world

Test::Nginx will automatically calculate the content length and add the corresponding header for you.

This being said, as soon as you want to POST real data, you will be interested in using the more_headers section and using the power of Test::Base filters to urlencode the content you are sending. Which gives us a slightly more realistic example:

--- more_headers
Content-type: application/x-www-form-urlencoded
--- request eval
use URI::Escape;
"POST /rrd/foo
value=".uri_escape("N:12345")

Sometimes a test is more than one request. Typically you want to POST some data and make sure the data has been taken into account with a GET. You can do it using arrays:

--- request eval
["POST /users
name=foo", "GET /users/foo"]

This way, REST-like interfaces are pretty easy to test.

When you develop nifty nginx modules you will eventually want to test things with buffers and "weird" network conditions. This is where you split your request into network packets:

--- request eval
[["POST /users\nna", "me=foo"]]

Here, Test::Nginx will first send the request line, the headers it automatically added for you and the first two letters of the body ("na" in our example) in ONE network packet. Then, it will send the next packet (here it's "me=foo"). When we talk about packets here, this is nto exactly correct as there is no way to guarantee the behavior of the TCP/IP stack. What Test::Nginx can guarantee is that this will result in two calls to syswrite.

A good way to make almost sure the two calls result in two packets is to introduce a delay (let's say 2 seconds)before sending the second packet:

--- request eval
[["POST /users\nna", {value => "me=foo", delay_before => 2}]]

Of course, everything can be combined till your brain starts boiling ;) :

--- request eval
use URI::Escape;
my $val="value=".uri_escape("N:12346");
[["POST /rrd/foo
".substr($val, 0, 6),
{value => substr($val, 6, 5), delay_before=>5},
substr($val, 11)],  "GET /rrd/foo"]

request_eval

Use of this section is deprecated and tests using it should replace it with a request section with an eval filter. More explicitly:

--- request_eval
"POST /echo_body
hello\x00\x01\x02
world\x03\x04\xff"

should be replaced by:

--- request eval
"POST /echo_body
hello\x00\x01\x02
world\x03\x04\xff"

pipelined_requests

Specify pipelined requests that use a single keep-alive connection to the server.

Here is an example from ngx_lua's test suite:

=== TEST 7: discard body
--- config
    location = /foo {
        content_by_lua '
            ngx.req.discard_body()
            ngx.say("body: ", ngx.var.request_body)
        ';
    }
    location = /bar {
        content_by_lua '
            ngx.req.read_body()
            ngx.say("body: ", ngx.var.request_body)
        ';
    }
--- pipelined_requests eval
["POST /foo
hello, world",
"POST /bar
hiya, world"]
--- response_body eval
["body: nil\n",
"body: hiya, world\n"]

more_headers

Adds the content of this section as headers to the request being sent. Example:

--- more_headers
X-Foo: blah

This will add X-Foo: blah to the request (on top of the automatically generated headers like Host, Connection and potentially Content-Length).

response_body

The expected value for the body of the submitted request.

--- response_body
hello

If the test is made of multiple requests, then the response_body MUST be an array and each request MUST return the corresponding expected body:

--- request eval
["GET /hello", "GET /world"]
--- response_body eval
["hello", "world"]

response_body_eval

Use of this section is deprecated and tests using it should replace it with a request section with an eval filter. Therefore:

--- response_body_eval
"hello\x00\x01\x02
world\x03\x04\xff"

should be replaced by:

--- response_body eval
"hello\x00\x01\x02
world\x03\x04\xff"

response_body_like

The body returned by the request MUST match the pattern provided by this section. Example:

--- response_body_like
^elapsed 0\.00[0-5] sec\.$

If the test is made of multiple requests, then response_body_like MUST be an array and each request MUST match the corresponding pattern.

response_headers

The headers specified in this section are in the response sent by nginx.

--- response_headers
Content-Type: application/x-resty-dbd-stream

Of course, you can specify many headers in this section:

--- response_headers
X-Resty-DBD-Module:
Content-Type: application/x-resty-dbd-stream

The test will be successful only if all headers are found in the response with the appropriate values.

If the test is made of multiple requests, then response_headers MUST be an array and each element of the array is checked against the response to the corresponding request.

response_headers_like

The value of the headers returned by nginx match the patterns.

--- response_headers_like
X-Resty-DBD-Module: ngx_drizzle \d+\.\d+\.\d+
Content-Type: application/x-resty-dbd-stream

This will check that the response's Content-Type is application/x-resty-dbd-stream and that the X-Resty-DBD-Module matches ngx_drizzle \d+\.\d+\.\d+.

The test will be successful only if all headers are found in the response and if the values match the patterns.

If the test is made of multiple requests, then response_headers_like MUST be an array and each element of the array is checked against the response to the corresponding request.

raw_response_headers_like

Checks the headers part of the response against this pattern. This is particularly useful when you want to write tests of redirect functions that are not bound to the value of the port your nginx server (under test) is listening to:

--- raw_response_headers_like: Location: http://localhost(?::\d+)?/foo\r\n

As usual, if the test is made of multiple requests, then raw_response_headers_like MUST be an array.

error_code

The expected value of the HTTP response code. If not set, this is assumed to be 200. But you can expect other things such as a redirect:

--- error_code: 302

If the test is made of multiple requests, then error_code MUST be an array with the expected value for the response status of each request in the test.

raw_request

The exact request to send to nginx. This is useful when you want to test soem behaviors that are not available with "request" such as an erroneous Content-Length header or splitting packets right in the middle of headers:

--- raw_request eval
["POST /rrd/taratata HTTP/1.1\r
Host: localhost\r
Connection: Close\r
Content-Type: application/",
"x-www-form-urlencoded\r
Content-Length:15\r\n\r\nvalue=N%3A12345"]

This can also be useful to tests "invalid" request lines:

--- raw_request
GET /foo HTTP/2.0 THE_FUTURE_IS_NOW

user_files

With this section you can create a file that will be copied in the html directory of the nginx server under test. For example:

--- user_files
>>> blah.txt
Hello, world

will create a file named blah.txt in the html directory of the nginx server tested. The file will contain the text "Hello, world".

skip_nginx

skip_nginx2

Both string scalar and string arrays are supported as values.

raw_request_middle_delay

Delay in sec between sending successive packets in the "raw_request" array value. Also used when a request is split in packets.

Environment variables

All environment variables starting with TEST_NGINX_ are expanded in the sections used to build the configuration of the server that tests automatically starts. The following environment variables are supported by this module:

TEST_NGINX_NO_NGINX_MANAGER

Defaults to 0. If set to 1, Test::Nginx module will not manage (configure/start/stop) the nginx process. Can be useful to run tests against an already configured (and running) nginx server.

TEST_NGINX_NO_SHUFFLE

Dafaults to 0. If set to 1, will make sure the tests are run in the order they appear in the test file (and not in random order).

TEST_NGINX_USE_VALGRIND

If set to 1, will start nginx with valgrind. nginx is actually started with valgrind -q --leak-check=full --gen-suppressions=all --suppressions=valgrind.suppress, the suppressions option being used only if there is actually a valgrind.suppress file.

TEST_NGINX_BINARY

The command to start nginx. Defaults to nginx. Can be used as an alternative to setting PATH to run a specific nginx instance.

TEST_NGINX_LOG_LEVEL

Value of the last argument of the error_log configuration directive. Defaults to debug.

TEST_NGINX_MASTER_PROCESS

Value of the master_process configuration directive. Defaults to off.

TEST_NGINX_SERVER_PORT

Value of the port the server started by Test::Nginx will listen to. If not set, TEST_NGINX_PORT is used. If TEST_NGINX_PORT is not set, then 1984 is used. See below for typical use.

TEST_NGINX_CLIENT_PORT

Value of the port Test::Nginx will diirect requests to. If not set, TEST_NGINX_PORT is used. If TEST_NGINX_PORT is not set, then 1984 is used. A typical use of this feature is to test extreme network conditions by adding a "proxy" between Test::Nginx and nginx itself. This is described in the etcproxy integration section of this module README.

TEST_NGINX_PORT

A shortcut for setting both TEST_NGINX_CLIENT_PORT and TEST_NGINX_SERVER_PORT.

TEST_NGINX_SLEEP

How much time (in seconds) should Test::Nginx sleep between two calls to syswrite when sending request data. Defaults to 0.

TEST_NGINX_FORCE_RESTART_ON_TEST

Defaults to 1. If set to 0, Test::Nginx will not restart the nginx server when the config does not change between two tests.

TEST_NGINX_SERVROOT

The root of the nginx "hierarchy" (where you find the conf, *_tmp and logs directories). This value will be used with the -p option of nginx. Defaults to appending t/servroot to the current directory.

TEST_NGINX_IGNORE_MISSING_DIRECTIVES

If set to 1 will SKIP all tests which config sections resulted in a unknown directive when trying to start nginx. Useful when you want to run tests on a build of nginx that does not include all modules it should. By default, these tests will FAIL.

TEST_NGINX_ERROR_LOG

Error log files from all tests will be appended to the file specified with this variable. There is no default value which disables the feature. This is very useful when debugging. By default, each test triggers a start/stop cycle for nginx. All logs are removed before each restart, so you can only see the logs for the last test run (which you usually do not control except if you set TEST_NGINX_NO_SHUFFLE=1). With this, you accumulate all logs into a single file that is never cleaned up by Test::Nginx.

Samples

You'll find live samples in the following Nginx 3rd-party modules:

ngx_echo

http://github.com/agentzh/echo-nginx-module

ngx_chunkin

http://wiki.nginx.org/NginxHttpChunkinModule

ngx_memc

http://wiki.nginx.org/NginxHttpMemcModule

ngx_drizzle

http://github.com/chaoslawful/drizzle-nginx-module

ngx_rds_json

http://github.com/agentzh/rds-json-nginx-module

ngx_xss

http://github.com/agentzh/xss-nginx-module

ngx_srcache

http://github.com/agentzh/srcache-nginx-module

ngx_lua

http://github.com/chaoslawful/lua-nginx-module

ngx_set_misc

http://github.com/agentzh/set-misc-nginx-module

ngx_array_var

http://github.com/agentzh/array-var-nginx-module

ngx_form_input

http://github.com/calio/form-input-nginx-module

ngx_iconv

http://github.com/calio/iconv-nginx-module

ngx_set_cconv

http://github.com/liseen/set-cconv-nginx-module

ngx_postgres

http://github.com/FRiCKLE/ngx_postgres

ngx_coolkit

http://github.com/FRiCKLE/ngx_coolkit

SOURCE REPOSITORY

This module has a Git repository on Github, which has access for all.

http://github.com/agentzh/test-nginx

If you want a commit bit, feel free to drop me a line.

AUTHORS

agentzh (章亦春) <agentzh@gmail.com>

Antoine BONAVITA <antoine.bonavita@gmail.com>

COPYRIGHT & LICENSE

Copyright (c) 2009-2011, Taobao Inc., Alibaba Group (http://www.taobao.com).

Copyright (c) 2009-2011, agentzh <agentzh@gmail.com>.

Copyright (c) 2011, Antoine BONAVITA <antoine.bonavita@gmail.com>.

This module is licensed under the terms of the BSD license.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  • Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  • Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

  • Neither the name of the Taobao Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

SEE ALSO

Test::Nginx::LWP, Test::Base.