Test::...:Socket(3pm)

๐Ÿ“ฆ
version 0.32.9.g58cf0d5 ยท 2 versions
, in ubuntu stonking

๐Ÿ’– Test::...:Socket โ€” User Contributed Perl Documentation (3pm) ๐Ÿ’–

okay bestie so this is Test::Nginx::Socket, and ngl it's kind of a big deal. It's the socket-backed test scaffold for the Nginx C modules plus all the Nginx/OpenResty-based libraries and apps out there. Buckle up, this page is long and it's giving encyclopedia, but we're gonna get through it together ๐Ÿซถ


โœจ NAME โœจ

Test::Nginx::Socket โ€” a Socket-backed test scaffold for the Nginx C modules and Nginx/OpenResty-based libraries and applications ๐Ÿ’…


๐ŸŒธ SYNOPSIS ๐ŸŒธ

okay so here's the whole vibe in one code block, no notes:

use Test::Nginx::Socket;

repeat_each(2);
plan tests => repeat_each() * 3 * blocks();

no_shuffle();
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

like... look at it. you write your test right there in the file, in a declarative block, and the scaffold handles the rest โœจ


๐Ÿฆ‹ DESCRIPTION ๐Ÿฆ‹

So like, what even is this? This module hands you a test scaffold built on non-blocking IO::Socket, made for automated testing while you're doing Nginx C module development. it's the whole point, periodt.

and here's the cute part: this class inherits from Test::Base, which means ALL of that declarative power gets poured straight into your Nginx C module testing practice. obsessed ๐Ÿ˜Œ

โš ๏ธ real talk bestie: you need to terminate or kill any Nginx processes before running the test suite โ€” but only if you've 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 is gonna create a temporary server root under t/servroot/ โ€” that's relative to the current working directory โ€” and then it starts and uses the nginx executable that lives in the PATH environment. slay.

And omg, you will often want to look into t/servroot/logs/error.log when things go wrong ;) like genuinely, that file is your bestie when everything's on fire ๐Ÿ”ฅ

๐Ÿ“– User Guide ๐Ÿ“–

You can find a comprehensive user guide on this test framework in the upcoming book "Programming OpenResty":

<https://openresty.gitbooks.io/programming-openresty/content/testing/index.html>

๐ŸŒˆ Features inherited from Test::Base ๐ŸŒˆ

All the features of Test::Base are inherited, since, queen, it's literally an ancestor class of this module anyway.

Still, we wanna highlight a few of the inherited features here for those of you who've never hung out with Test::Base before.

๐ŸŽ€ Meta sections ๐ŸŽ€

--- ONLY

Runs the surrounding test block, and only that one. iconic for one-off debugging ๐Ÿ’…

You do need to remember to remove --- ONLY before committing your changes though. But don't stress โ€” the test scaffold will warn you loudly on the console when you've left a --- ONLY chilling in some test file.

It's also super intuitive for the developer workflow, fr fr. You don't have to specify a (unique) test name on the command-line; you just find the block in your editor, drop a --- ONLY line right away, and run the current test file immediately. For Vim users, that final step is as simple as entering :!prove % โ€” where :! is the Vim way of running an external shell command, and % gets substituted with the current file being edited in Vim's buffer. genius.

This is definitely one of the most useful and frequently used features. no cap.

--- SKIP

Skips the surrounding test block, unconditionally, periodt.

You can use --- skip_nginx and --- skip_nginx2 provided by this module (see their documentation below) to conditionally skip tests based on the current NGINX server versions ๐Ÿ‘‘. You can also use --- skip_openssl (see its documentation below) to conditionally skip tests based on the current OpenSSL version.

๐Ÿงš Filters ๐Ÿงš

We can use filters to preprocess the values of our blocks, which makes specifying special values SO much easier. it's giving convenience, honestly ๐Ÿ’–

For example, we could chop off the last new-line character (if any) of the current section value by specifying the chomp filter, like this:

--- response_body chomp
Hello world!

Without the chomp filter, the value of the response_body section would take a trailing new line. sneaky little newline ๐Ÿ™ˆ

We list some of the common filters below โ€” and please keep in mind that one can define custom filters too!! sky's the limit bestie โœจ

  • chomp โ€” Remove the last character if it is a newline.
  • chop โ€” Remove the last character no matter what it is. (yes, no matter what. it does not care. it does not ask questions ๐Ÿ’€)
  • eval โ€” Treat the section value as a Perl source code snippet, evaluate it right away, and use the returned value of the Perl code snippet (usually being the value of the last expression).

This is very useful for specifying non-printable characters in section values, as in:

--- response_body eval
"I don't know what \0 is.\n"

okay that's part 1 done, don't go anywhere โ€” there's more coming and it's only getting juicier ๐Ÿ“

โœจ Exported Perl Functions โœจ

These are the Perl functions that get exported for you by default, bestie. No use gymnastics required, they're just there. ๐Ÿ’–

๐Ÿ run_tests

This is the main entry point of the whole test scaffold. Call this Perl function before __DATA__ and boom, all your tests run. ๐ŸŽ€

โš ๏ธ Every other configuration Perl function has to be called before you call run_tests. Order matters, no cap.

๐Ÿ”€ no_shuffle

By default, the test scaffold always shuffles the order of your test blocks automatically (chaotic queen behavior tbh). Call this function before calling run_tests and it will not do that โ€” shuffling disabled. โœจ

๐Ÿ“ก use_hup

Call this function before you call run_tests and the current test scaffold will act exactly as if TEST_NGINX_USE_HUP was set to 1. That's it, that's the whole vibe.

๐Ÿ“ no_long_string

By default, a failed string equality test uses the Test::LongString module to build the error message for you. Call this function before calling run_tests and that gets turned off. ๐Ÿ’…

๐Ÿ” no_diff

Here's the chain: when the no_long_string function is called, the Text::Diff module gets used to generate a diff for a failed string equality test. Call this no_diff function before calling run_tests and that diff output format gets turned off too โ€” you'll just get the raw got text and expected text, side by side, no pretty diff. ๐Ÿซถ

๐Ÿ”Œ worker_connections

Call this before run_tests to set Nginx's worker_connections configuration value. Like so:

worker_connections(1024);
run_tests();

Default is 64.

๐Ÿ” repeat_each

Call this one with an integer argument before run_tests() to ask the test scaffold to run the specified number of duplicate requests for each test block. And plot twist โ€” call it with no argument and it just returns the current setting back to you. ๐Ÿ‘‘

Default is 1.

๐Ÿ’€ shutdown_error_log

You can use this section to check the error log generated during nginx exit. Very "what did you say on your way out" of it.

For example:

--- shutdown_error_log
cleanup resolver

Or here's an example using an array value:

--- shutdown_error_log eval
["cleanup", "resolver"]
WARNING: skip the shutdown_error_log tests under the HUP reload mode. Seriously, don't do it.

๐Ÿšซ no_shutdown_error_log

Very much like the --- shutdown_error_log section, but it does the opposite test โ€” it passes only when the specified patterns of lines do not appear in the error.log file at all. โœจ

Here's an example:

--- no_shutdown_error_log
[error]

This test will fail when any of the lines in the error.log file contains the string [error].

๐ŸŒฑ env_to_nginx

Specify additional system environment variables to be passed into the nginx server. ๐ŸŒˆ

For example:

env_to_nginx("foo", "bar=123", "baz=hello world");
run_tests();

will result in the following lines being inserted into the resulting nginx.conf file generated by the test scaffold:

env foo;
env bar=123;
env 'baz=hello world';

Those latter two are examples of setting values directly to the environments. You can also set values directly on the Perl land, before calling this env_to_nginx function, like for instance:

$ENV{baz} = 'hello world';
env_to_nginx("baz");

If you just want to pass certain environments to a particular test case (or test block), you can just use the --- main_config section directly. For example:

--- main_config
env foo;
env bar=123;

You can check out nginx's official document on its env directive right here: http://nginx.org/r/env. ๐Ÿ“š

By default, only the following environments are passed:

  • MOCKEAGAIN_VERBOSE
  • MOCKEAGAIN
  • MOCKEAGAIN_WRITE_TIMEOUT_PATTERN
  • LD_PRELOAD
  • LD_LIBRARY_PATH
  • DYLD_INSERT_LIBRARIES
  • DYLD_FORCE_FLAT_NAMESPACE
  • ASAN_OPTIONS
  • MOCKNOEAGAIN_VERBOSE
  • MOCKNOEAGAIN

๐Ÿ‘ท workers

Call this function before run_tests() to configure the value of Nginx's worker_processes directive. For example:

workers(2);

Default is 1.

๐Ÿง‘โœˆ๏ธ master_on

Call this function before run_tests() to turn on the Nginx master process.

By default, the master process is not enabled โ€” unless you're in the HUP reload testing mode. That's the only exception, fr fr.

๐Ÿ“ข log_level

Call this function before run_tests() to set the default error log filtering level in Nginx.

This is a global setting, and it can be overridden by the per-test-block --- log_level sections. Bestie, the per-block one wins. ๐ŸŽ€

Default is debug.

๐Ÿงฎ check_accum_error_log

Makes --- error_log and --- no_error_log check the accumulated error log across the duplicate requests controlled by repeat_each. By default, only the error logs belonging to the individual repeat_each request get tested. So this is the glow-up where you look at the whole picture instead. ๐Ÿ“ธ

๐Ÿ  no_root_location

By default, the Nginx configuration file generated by the test scaffold automatically emits a location /. Calling this function before run_tests() disables that behavior, so your test blocks can have their own root locations. Iconic, honestly. ๐Ÿ’…

๐Ÿ›‘

๐Ÿ›‘ bail_out

Aborts the whole test session (not just the current test file) with a specified message.

This function also does all the necessary cleanup work, which is why you should always use this function instead of calling Test::More::BAIL_OUT() directly. Don't be brave, just use bail_out. ๐Ÿ˜ญ

For example:

bail_out("something bad happened!");

๐Ÿงน add_test_cleanup_handler

Register a custom cleanup handler for the current perl/prove process by specifying a Perl subroutine object as the argument.

For example:

add_test_cleanup_handler(sub ($block) {
    print $block->name, " finish";
});

๐Ÿงผ add_cleanup_handler

Register a custom cleanup handler for the current perl/prove process by specifying a Perl subroutine object as the argument.

For example:

add_cleanup_handler(sub {
    kill INT => $my_own_child_pid;
    $my_own_socket->close()
});

๐Ÿช„ add_block_preprocessor

Add a custom Perl preprocessor to each test block by specifying a Perl subroutine object as the argument. ๐Ÿฆ‹

The processor subroutine is always run right before processing the test block.

This mechanism can be used to add custom sections or modify existing ones. It's giving... main character energy, honestly.

For example:

add_block_preprocessor(sub {
    my $block = shift;

    # use "--- req_headers" for "--- more_Headers":
    $block->set_value("more_headers", $block->req_headers);

    # initialize external dependencies like memcached services here...
});

We can leverage this feature to specify a default value for one or more sections in a single test file. For instance:

use Test::Nginx::Socket 'no_plan';

add_block_preprocessor(sub {
    my $block = shift;

    if (!defined $block->config) {
        $block->set_value("config", <<'_END_');
location = /t {
    echo $arg_a;
}
_END_
    }
});

run_tests();

__DATA__

=== TEST 1:
--- request
    GET /t?a=3
--- response_body
3

=== TEST 2:
--- request
    GET /t?a=blah
--- response_body
blah

=== TEST 3:
--- config
    location = /t {
        echo ok;
    }
--- request
    GET /t?a=blah
--- response_body
ok

Here all the test blocks in this file have a default --- config section configured. Some of the test blocks can still specify their own --- config section to override the default, exactly like in the TEST 3 test block above. ๐Ÿ’–

You can also make those defaults apply to all your test files. Just create a subclass of Test::Nginx::Socket (or one of its subclasses like Test::Nginx::Socket::Lua), as in:

package t::MyTester;

use Test::Nginx::Socket -Base;

add_block_preprocessor(sub {
    my $block = shift;

    if (!defined $block->config) {
        $block->set_value("config", <<'_END_');
location = /t {
    echo $arg_a;
}
_END_
    }
});

1;

Save this as the file t/MyTester.pm. And then in one of your test files:

use t::MyTester 'no_plan';

run_tests();

__DATA__

=== TEST 1:
--- request
    GET /t?a=3
--- response_body
3

=== TEST 2:
--- request
    GET /t?a=blah
--- response_body
blah

You can do the exact same thing with the --- http_config section, or even invent your own brand new sections. This is very powerful. ๐Ÿ”ฅ

๐Ÿ”Ž add_response_body_check

Add custom checks for testing response bodies by specifying a Perl subroutine object as the argument.

Below is an example for doing HTML title checks:

add_response_body_check(sub {
        my ($block, $body, $req_idx, $repeated_req_idx, $dry_run) = @_;

        my $name = $block->name;
        my $expected_title = $block->resp_title;

        if ($expected_title && !ref $expected_title) {
            $expected_title =~ s/^\s*|\s*$//gs;
        }

        if (defined $expected_title) {
            SKIP: {
                skip "$name - resp_title - tests skipped due to $dry_run", 1 if $dry_run;

                my $title;
                if ($body =~ m{<\s*title\s*>\s*(.*?)<\s*/\s*title\s*>}) {
                    $title = $1;
                    $title =~ s/\s*$//s;
                }

                is_str($title, $expected_title,
                       "$name - resp_title (req $repeated_req_idx)" );
            }
        }
    });

๐Ÿง  is_str

Performs intelligent string comparison subtests which honor both no_long_string and regular expression references in the expected argument. She's smart, she's versatile, she does it all. ๐Ÿ‘‘


๐Ÿ“‚ Sections supported

Here's the full menu of sections you can sprinkle into your test blocks, bestie:

๐Ÿ•ธ๏ธ http2

Enforces the test scaffold to use the HTTP/2 wire protocol to send the test request. โœจ

Under the hood, the test scaffold uses the curl command-line utility to do the wire communication with the NGINX server. The curl utility must be recent enough to support the --http2-prior-knowledge command-line options. (Old curl could never. ๐Ÿ’…)

WARNING: not all the sections and features are supported when this --- http2 section is specified. For example, this section cannot be used with --- pipelined_requests or --- raw_request. F around and find out, honestly.

See also the TEST_NGINX_USE_HTTP2 system environment for the http2 test mode.

๐ŸŒ curl_protocol

Set the protocol (such as http/https) when sending a request using 'curl'. ๐Ÿซถ

๐Ÿ› ๏ธ curl_options

Add extra command line options when using 'curl' to send a request.

Below is an example for sending an insecure https request using 'curl':

--- http2
--- curl_options: -k
--- curl_protocol: https
--- request
    GET /ping

๐Ÿ˜ญ curl_error

The expected curl error. Live your truth.

--- curl_error

โœจ config โœจ

Okay bestie, the content of this section gets dropped into the "server" part of the generated config file ๐Ÿ’…. This is THE spot where you put the "location" directive that switches on the module you're trying to test. Like, watch:

        location /echo {
            echo_before_body hello;
            echo world;
        }

Sometimes you genuinely cannot be bothered to copy-paste the same config ten times for the ten tests you're running against your module ๐Ÿ™ˆ. The hack: write a config section for ONLY the first test in your .t file, and every test after it just re-uses that same config. Obsessed. BUT โ€” heads up โ€” this depends on test order, so you should run prove with the variable TEST_NGINX_NO_SHUFFLE=1 (more on that variable below ๐Ÿ‘‡).

Also, the config section goes through environment variable expansion, as long as the variables start with TEST_NGINX. So this is completely legal (as long as TEST_NGINX_HTML_DIR is set right, obviously):

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

๐ŸŒธ http_config ๐ŸŒธ

Content here goes into the "http" part of the generated config file. This is where you wanna put the upstream directive you're testing. Example time:

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

And yeah, as you probably clocked from that example, this section also goes through environment variable expansion (the variables have to start with TEST_NGINX).


๐Ÿฆ‹ main_config ๐Ÿฆ‹

Content here lands in the "main" part (aka the toplevel) of the generated config file. This is used like, basically never, unless you're testing the nginx core itself ๐Ÿ‘‘. Everything in --- main_config gets put before the http {} block that the test scaffold generates automatically for you.

Yep, you guessed it: this section goes through environment variable expansion too (variables start with TEST_NGINX).


๐Ÿ’œ post_main_config ๐Ÿ’œ

Same energy as main_config, except the content gets placed after the http {} block that this module generates ๐Ÿ˜Œ.


๐ŸŽ€ server_name ๐ŸŽ€

Set a custom server name (via the nginx config directive server_name) for the current test block. It defaults to localhost.


โญ init โญ

Runs a piece of Perl code โ€” whatever you put in the --- init section โ€” before the tests run for the blocks. And bestie, listen: it only runs once before all the repeated requests for this test block. Not per-request. Just once. Periodt.


๐Ÿงš post_setup_server_root ๐Ÿงš

Runs a piece of Perl code that you specify as the content of the --- post_setup_server_root section, so you can customize the test nginx instance for testing ๐Ÿ’…. Unlike the --- init section, --- post_setup_server_root preserves the changes in servroot/conf ๐Ÿซถ.

In its most basic form, it looks like this:

    --- post_setup_server_root
    use Cwd qw(cwd);
    my $root_dir = cwd();
    `ln -sf ${root_dir}/t/data/mime.types ${root_dir}/t/servroot/conf/mime.types`;

That creates a symbolic link for the file mime.types, which lets nginx load it via include mime.types; โœจ.


๐Ÿ”ฅ request ๐Ÿ”ฅ

Okay so this is lowkey the MOST important section. It defines the request(s) you're sending to the nginx server. And it offers a genuinely pretty powerful grammar that we're gonna walk through one example at a time, no cap.

Most basic form, ever:

    --- request
    GET

That just does a GET request on the root (i.e. /) of the server, using HTTP/1.1 ๐Ÿ˜Œ.

But obviously you probably wanna test something other than the root of your web server, and maybe even use a different HTTP version. So, possible:

    --- request
    GET /foo HTTP/1.0

Heads up though: specifying HTTP/1.0 will not stop 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 content to your request:

    --- request
    POST /foo
    Hello world

Test::Nginx is gonna auto-calculate the content length and add the corresponding header for you ๐Ÿซถ. No manual math required.

That said โ€” the second you wanna POST real data, you're gonna be interested in the more_headers section and in flexing the power of Test::Base filters to urlencode the content you're 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. Like, classic move: you POST some data, then you GET to make sure the data actually got taken into account. You can do exactly that with arrays:

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

And just like that, REST-like interfaces become honestly so easy to test ๐Ÿ’….

Now, when you start developing nifty nginx modules, you're eventually gonna wanna test stuff with buffers and "weird" network conditions. That's where you split your request into network packets:

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

So here, Test::Nginx sends 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 sends the next packet (here it's me=foo). Now โ€” when we say "packets" here, that's not exactly correct, because there's literally no way to guarantee the behavior of the TCP/IP stack. What Test::Nginx can guarantee is that this results in two calls to syswrite ๐Ÿฆ‹.

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

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

And 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"]

Adding comments before the actual request spec is supported too, for example:

   --- request
   # this request contains the URI args
   # "foo" and "bar":
   GET /api?foo=1&bar=2

๐Ÿ’€ request_eval ๐Ÿ’€

Nope bestie โ€” using this section is deprecated, and tests that use it should swap it out for a request section with an eval filter instead. Please, for me.

More explicitly, this:

    --- 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. Iconic behavior, honestly.

Here's an example straight 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

That adds X-Foo: blah to the request, on top of the automatically generated headers like Host, Connection, and potentially Content-Length โœจ.


๐Ÿ“ curl ๐Ÿ“

When this section is specified, the test scaffold will try generating a curl command line for the (first) test request. So helpful, honestly.

For example:

    --- request
    GET /foo/bar?baz=3

    --- more_headers
    X-Foo: 3
    User-Agent: openresty

    --- curl

will produce the following line (to stderr) while running this test block:

    # curl -i -sS -H 'X-Foo: 3' -A openresty 'http://127.0.0.1:1984/foo/bar?baz=3'
Okay so important remember-y thing: you need to set the TEST_NGINX_NO_CLEAN environment to 1 to prevent nginx and other processes from quitting automatically when the test exits ๐Ÿ™ˆ.

๐Ÿ’– response_body_filters ๐Ÿ’–

This one transforms the value of the actual response body data through a series of filters, before it gets matched against the expected response body data that you specified in the response_body or response_body_like sections. Vibe check on your data ๐Ÿ˜Œ.

The filters can be specified either as names (for the builtin ones) or as arbitrary Perl subroutine references.

The following builtin filter names are supported, bestie โœจ:

  • md5_hex
  • sha1_hex
  • length
  • uc
  • lc
  • ucfirst
  • lcfirst

Their meanings are self-explanatory, no cap ๐Ÿ’….

Here's an example:

    === TEST 1:
    --- config
        location = /t {
            echo hello;
        }
    --- request
        GET /t
    --- response_body_filters
    uc
    --- response_body
    HELLO

So here, the actual response body data, hello, goes through the filter uc to become all upper-case, and then it gets matched against the expected pattern specified by the response_body section, HELLO.

The example above can be rewritten by using raw Perl subroutine reference values:

    === TEST 1:
    --- config
        location = /t {
            echo hello;
        }
    --- request
        GET /t
    --- response_body_filters eval
    \&CORE::uc
    --- response_body
    HELLO
To reference builtin Perl functions like \&CORE::uc and \&CORE::lc, you need at least perl 5.16. Don't @ me.

Multiple builtin filter names can be specified at the same time, and they'll be applied in order ๐Ÿซถ. For example:

    === TEST 2:
    --- config
        location = /t {
            echo hello;
        }
    --- request
        GET /hello
    --- response_body_filters
    uc lc
    --- response_body
    hello

And the response_body_filters value can also be an array reference, which is mostly useful for specifying multiple Perl subroutine references as the filters:

    === TEST 3:
    --- config
        location = /t {
            echo hello;
        }
    --- request
        GET /hello
    --- response_body_filters eval
    [\&CORE::uc, \&CORE::lc]
    --- response_body
    hello

If the response_body_filters value is ALSO a two-dimensional array ref, that means the actual response body data gets "isolatedly" run through each indexed array's filters, one array per index, no crossover. Like so:

=== TEST 4:
--- config
    location = /t {
        echo hello;
    }
--- request eval
    ['GET /t', 'GET /t']
--- response_body_filters eval
    [[\&CORE::uc, \&CORE::lc], [\&CORE::uc]]
--- response_body eval
    ['hello', 'HELLO']

response_body_json_sort

kk so lua-cjson outputs json string in an order that is genuinely not guaranteed, bestie. So to actually compare the output result, we gotta sort the response body first. sorting = the vibe check for json.

--- response_body_json_sort
--- response_body
{"a": 1, "b": 2}

response_body

This is where you put the expected value for the body of the submitted request. the moment of truth ๐Ÿ’…

--- response_body
hello

But listen โ€” if your test is made of multiple requests, then response_body MUST be an array, and each request MUST return the corresponding expected body. no skipping, no shortcuts:

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

response_body_eval

okay so using this section is deprecated ๐Ÿ˜ญ and tests that use it should swap it out for a "request" section with an "eval" filter. So like, this:

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

should get replaced by this:

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

literally just a glow-up. same content, new section name ๐ŸŽ€

response_body_like

The body returned by the request MUST match the pattern you provide here. Example:

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

And if the test is made of multiple requests, then response_body_like MUST be an array, and each request MUST match the corresponding pattern. one pattern per request, aligned, no chaos.

response_body_unlike

Just like "response_body_like" but the opposite energy โ€” this test only passes when the specified pattern does not match the actual response body data. it's giving "prove it's not there" ๐Ÿ”ฅ

response_headers

The headers you list in this section are the ones in the response sent by nginx.

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

And obviously you can flex and 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. all of them, babes. no partial credit.

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

Here the values of the headers returned by nginx have to 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 the values match the patterns. both things, together, periodt.

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

This one checks the headers part of the response against the pattern. It's especially iconic when you're writing tests for redirect functions that aren't bound to the value of the port your nginx server (the one under test) is listening on:

--- 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.

raw_response_headers_unlike

Just like "raw_response_headers_like" except the subtest only passes when the regex does not match the raw response headers string. flip the script, bestie ๐Ÿ™ˆ

error_code

The expected value of the HTTP response code. If you don't set it, it's assumed to be 200. But you can absolutely expect other things too, like 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.

error_code_like

Just like "error_code", except it accepts a Perl regex as the value. for example:

--- error_code_like: ^(?:500)?$

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

timeout

Set the timeout value (in seconds) for the HTTP client embedded into the test scaffold. This has nothing to do with the server side configuration, ok? When the timeout expires, the test scaffold will immediately close the socket that connects to the Nginx server being tested. brb, socket's gone.

Note that, just as almost all the timeout settings in the Nginx world, this timeout also specifies the maximum waiting time between two successive I/O events on the same socket handle โ€” not the total waiting time for the current socket operation. it's per-gap, not per-whole-thing. iykyk.

When the timeout setting expires, a test failure gets triggered with the message "ERROR: client socket timed out - TEST NAME", unless you've also specified "--- abort" at the same time.

Here's an example:

=== TEST 1: test timeout
--- location
    location = /t {
        echo_sleep 1;
        echo ok;
    }
--- request
    GET /t
--- response_body
ok
--- timeout: 1.5

You can also throw in an optional time unit, like:

--- timeout: 50ms

Acceptable time units are "s" (seconds) and "ms" (milliseconds). If no time unit is specified, then it defaults to seconds.

Default is 3s.

error_log_file

Specify the global error log file, but for the current test block ONLY. it's exclusive ๐Ÿ’…

Right now, it will not affect the "--- error_log" section and etc accordingly. just so you know.

error_log

Checks if the pattern โ€” or multiple patterns โ€” all appear in lines of the error.log file.

For example:

=== TEST 1: matched with j
--- config
    location /re {
        content_by_lua '
            m = ngx.re.match("hello, 1234", "([0-9]+)", "j")
            if m then
                ngx.say(m[0])
            else
                ngx.say("not matched!")
            end
        ';
    }
--- request
    GET /re
--- response_body
1234
--- error_log: pcre JIT compiling result: 1

Then the substring "pcre JIT compiling result: 1" must appear literally in a line of error.log. literally literally.

Multiple patterns are also supported, for example:

--- error_log eval
["abc", qr/blah/]

then the substring "abc" must appear literally in a line of error.log, and the regex "qr/blah" must also match a line in error.log.

By default, only the part of the error logs corresponding to the current request is checked. You can make it check accumulated error logs by calling the "check_accum_error_log" Perl function before calling "run_tests" in the boilerplate Perl code above the __DATA__ line. just a lil pre-game setup ๐Ÿซถ

access_log

Similar to the error_log section, but for asserting appearance of patterns in the nginx access log file.

Here's an example:

=== TEST 1: check access log
--- config
    location /t {
        content_by_lua_block {
            ngx.say("hello")
        }
    }

--- request
GET /t
--- response_body
hello
--- access_log
GET /t

no_access_log

Similar to the no_error_log section, but for asserting appearance of patterns in the nginx access log file.

Here's an example:

=== TEST 1: check access log
--- config
    location /t {
        content_by_lua_block {
            ngx.say("hello")
        }
    }

--- request
GET /t
--- response_body
hello
--- no_access_log
GET /p

abort

Makes the test scaffold not treat "--- timeout" expiration as a test failure. so when it times out, it's fine, no tears ๐Ÿ’€โœจ

shutdown

Performs a "shutdown"() operation on the client socket connecting to Nginx, right as soon as all the request data has been sent out. This section takes an (optional) integer value for the argument to the "shutdown" function call. For example,

--- shutdown: 1

will make the connection stop sending data, which is the default. byeee data ๐Ÿ‘‹

no_error_log

Very much like the "--- error_log" section, but it does the opposite test โ€” i.e., it passes only when the specified patterns of lines do not appear in the error.log file at all. zero. zilch. none.

Here's an example:

--- no_error_log
[error]

This test will fail when any of the lines in the error.log file contains the string "[error]".

Just like the "--- error_log" section, you can also specify multiple patterns:

--- no_error_log eval
["abc", qr/blah/]

Then if any line in error.log contains the string "abc" or matches the Perl regex "qr/blah/", then the test will fail.

grep_error_log

This section specifies the Perl regex pattern for filtering out the Nginx error logs.

You can specify a verbatim substring being matched in the error log messages, as in

--- grep_error_log chop
some thing we want to see

or you can specify a Perl regex object to match against the error log message lines, as in

--- grep_error_log eval
qr/something should be: \d+/

All the matched substrings in the error log messages will be concatenated by a newline character as a whole, and that whole thing gets compared with the value of the "--- grep_error_log_out" section.

grep_error_log_out

This section contains the expected output for the filtering operations specified by the "--- grep_error_log" section.

If the filtered output varies among the repeated requests (specified by the repeat_each function), then you can specify a Perl array as the value, like so:

--- grep_error_log_out eval
["output for req 0", "output for req 1"]

log_level

Overrides the default error log level for the current test block. it's giving main character log energy ๐Ÿ‘‘

For example:

--- log_level: debug

The default error log level can be specified in the Perl code by calling the log_level() function, as in

use Test::Nginx::Socket;

repeat_each(2);
plan tests => repeat_each() * (3 * blocks());

log_level('warn');

run_tests();

__DATA__
...

raw_request

The exact request to send to nginx. This is the one you want when you're testing some behaviors that aren't 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"]

omg and this one is also so handy when you wanna test "invalid" request lines ๐Ÿ‘€ like:

--- raw_request
GET /foo HTTP/2.0 THE_FUTURE_IS_NOW

FR fr chaos testing is a whole vibe. ๐Ÿ’…


http09 ๐ŸŒˆ

This one says "hey bestie, we're rolling with the HTTP 0.9 protocol over here" โ€” and girl, it changes how Test::Nginx::Socket parses the response. Lowkey important, don't skip it.

Here's an example straight from the ngx_headers_more module's test suite, iconic:

=== TEST 38: HTTP 0.9 (set)
--- config
    location /foo {
        more_set_input_headers 'X-Foo: howdy';
        echo "x-foo: $http_x_foo";
    }
--- raw_request eval
"GET /foo\r\n"
--- response_headers
! X-Foo
--- response_body
x-foo:
--- http09

ignore_response ๐Ÿ™ˆ

Literally just do not touch the response. No parsing, no response-related subtests, nothing. We're ignoring it, as the name suggests, bestie is so self-explanatory and I respect that.


reload_fails ๐Ÿ’€

This one lets the NGINX HUP reload fail, which means the server keeps on using the nginx config from the previous test block. Awkward? Yes. On purpose? Also yes.

Heads up: this only makes sense in the HUP reload testing mode. Anywhere else it's giving nothing. ๐Ÿซถ

user_files ๐ŸŽ€

Okay so with this section you can create a file that gets copied right into the html directory of the nginx server under test. Adorable. For example:

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

...will create a file named blah.txt in the html directory of the nginx server being tested, and that file will contain the text Hello, world. Slay. โœจ

And bestie, multiple files?? Supported. Watch:

--- user_files
>>> foo.txt
Hello, world!
>>> bar.txt
Hello, heaven!

You can also drop an optional last modified timestamp (elapsed seconds since Epoch, for the record), like so:

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

And WAIT, you can even specify a whole Perl data structure for the user files you want created, no cap:

--- user_files eval
[
    [ "foo.txt" => "Hello, world!", 199801171935.33 ],
    [ "bar.txt" => "Hello, heaven!" ],
]

Obsessed with the flexibility honestly. ๐Ÿ‘‘


skip_eval ๐ŸŒธ

Skip a specified number of subtests (in the current test block) if running some Perl code comes back true. Simple as that.

The format is:

--- skip_eval
<subtest-count>: <perl-code>

So for example, to skip 3 subtests when the current operating system isn't Linux:

--- skip_eval
3: $^O ne 'linux'

or equivalently, because we love options:

--- skip_eval: 3: $^O ne 'linux'

skip_nginx ๐Ÿฆ‹

Same energy as skip_eval but for a specific version range of nginx โ€” skip that many subtests in the current block.

Format goes:

--- skip_nginx
<subtest-count>: <op> <version>

Rules, bestie:

  • <subtest-count> must be a positive integer.
  • <op> can be >, >=, <, or <=.
  • <version> is a valid nginx version number, like 1.0.2.

Example time:

=== TEST 1: sample
--- config
    location /t { echo hello; }
--- request
    GET /t
--- response_body
--- skip_nginx
2: < 0.8.54

Translation: skipping 2 subtests in this test block for nginx versions older than 0.8.54. ๐Ÿ’–

Real talk: this skip_nginx section only lets you specify one boolean expression as the skip condition. If you want two, you gotta use the skip_nginx2 section instead. iykyk.

skip_nginx2 ๐ŸŒˆ

Basically skip_nginx's cooler sibling, except the skip condition is two boolean expressions joined by either and or or.

Format:

--- skip_nginx2
<subtest-count>: <op> <version> and|or <op> <version>

For example:

=== TEST 1: sample
--- config
    location /t { echo hello; }
--- request
    GET /t
--- response_body
--- skip_nginx2
2: < 0.8.53 and >= 0.8.41

Two conditions, one section, we ate. ๐Ÿ”ฅ


skip_openssl ๐Ÿ”

You guessed it โ€” skip a specified number of subtests (in the current test block) for a specified version range of OpenSSL. Same format as its nginx cousin:

--- skip_openssl
<subtest-count>: <op> <version>

The deets:

  • <subtest-count> must be a positive integer.
  • <op> can be >, >=, <, or <=.
  • <version> is a valid OpenSSL version number, like 1.1.1 or "1.1.0h".

Example:

=== TEST 1: sample
--- config
    location /t { echo hello; }
--- request
    GET /t
--- response_body
--- skip_openssl
2: < 1.1.1

So that's skipping 2 subtests in this test block for OpenSSL versions older than 1.1.1. ๐Ÿ’œ

And yeah, this skip_openssl section only allows one boolean expression as the skip condition. Just the one. Don't get greedy bestie.

todo ๐Ÿ“

Marks tests as todo. Currently they are not used but they should be. (Not me calling out the codebase. It said it first. ๐Ÿ˜ญ)

Format:

--- todo
<subtest-count>: <reason>
  • <subtest-count> must be a positive integer.
  • <reason> gets logged when you run tests with --directives.

stap ๐Ÿงš

This section is for specifying a user systemtap script file (a .stp file).

Here's an example, get into it:

=== TEST 1: stap sample
--- config
    location /t { echo hello; }
--- stap
probe process("nginx").function("ngx_http_finalize_request")
{
    printf("finalize %s?%s\n", ngx_http_req_uri($r),
           ngx_http_req_args($r))
}
--- stap_out
finalize /test?a=3&b=4
--- request
GET /test?a=3&b=4
--- response_body
hello

There's also some macros you can use inside the --- stap section value. These get expanded by the test scaffold automatically, no manual work required. โœจ

F(function_name)

This expands to probe process("nginx").function("function_name"). For example, the sample above could be rewritten as:

=== TEST 1: stap sample
--- config
    location /t { echo hello; }
--- stap
F(ngx_http_finalize_request)
{
    printf("finalize %s?%s\n", ngx_http_req_uri($r),
           ngx_http_req_args($r))
}
--- stap_out
finalize /test?a=3&b=4
--- request
GET /test?a=3&b=4
--- response_body
hello

T()

This macro expands to println("Fire ", pp()).

M(static-probe-name)

This macro expands to probe process("nginx").mark("static-probe-name").

So for example:

M(http-subrequest-start)
{
    ...
}

becomes:

probe process("nginx").mark("http-subrequest-start")
{
    ...
}

Macros doing the heavy lifting so you don't have to, we love to see it. ๐Ÿ’…


stap_out ๐Ÿ‘‘

This section specifies the expected literal output of the systemtap script specified by stap.


stap_out_like ๐Ÿ“

Just like stap_out, but specify a Perl regex pattern instead. Same vibe, regex edition.


stap_out_unlike ๐Ÿ’€

Just like stap_like, but the subtest only passes when the specified pattern does NOT match the output of the systemtap script. Chaos but make it a test. ๐Ÿ™ˆ


wait โณ

Takes an integer value for the seconds of time to wait right after processing the Nginx response, and before doing the error log and/or systemtap output checks. So basically a lil pause moment. Take your time bestie. ๐Ÿซถ


udp_listen ๐Ÿ“ก

This one instantiates a UDP server listening on the port you specify, running in the background for the test case to access. The server gets started and shut down at each iteration of the test case โ€” so if repeat_each is set to 3, that's 3 whole iterations. Persistence? Never heard of her.

The UDP server will first read and discard a datagram, and then send back a datagram with the content specified by the udp_reply section value. Iconic behaviour honestly.

Here's an example:

=== TEST 1: udp access
--- config
    location = /t {
        content_by_lua '
            local udp = ngx.socket.udp()
            udp:setpeername("127.0.0.1", 19232)
            udp:send("blah")
            local data, err = udp:receive()
            ngx.say("received: ", data)
        ';
    }
--- udp_listen: 19232
--- udp_reply: hello world
--- request
GET /t
--- response_body
received: hello world

And omg, a Datagram UNIX domain socket is also supported if you give it a path name that ends with .sock. For instance:

=== TEST 2: datagram unix domain socket access
--- config
    location = /t {
        content_by_lua '
            local udp = ngx.socket.udp()
            udp:setpeername("unix:a.sock")
            udp:send("blah")
            local data, err = udp:receive()
            ngx.say("received: ", data)
        ';
    }
--- udp_listen: a.sock
--- udp_reply: hello world
--- request
GET /t
--- response_body
received: hello world

Versatile queen. ๐Ÿ‘‘


udp_reply ๐Ÿ’Œ

This section specifies the datagram reply content for the UDP server created by the udp_listen section.

You can also specify a delay time before the reply gets sent out, via the udp_reply_delay section. By default, there is no delay. Instant gratification, we love that.

An array value can be specified to make the embedded UDP server send multiple replies as specified, for example:

--- udp_reply eval
[ "hello", "world" ]

And this section also accepts a Perl subroutine value that can be used to generate a dynamic response packet or packets based on the actual query. For example:

--- udp_reply eval
sub {
    my $req = shift;
    return "hello, $req";
}

The custom Perl subroutine can also return an array reference, for example:

--- udp_reply eval
sub {
    my $req = shift;
    return ["hello, $req", "hiya, $req"];
}

See the udp_listen section for more details. Go back up if you need, I'll wait. ๐ŸŽ€


udp_reply_delay โฐ

This section specifies the delay time before sending out the reply specified by the udp_reply section.

Default is 0 delay. Zero. None. Immediately. ๐Ÿ’จ

An optional time unit can be specified, for example:

--- udp_reply_delay: 50ms

Acceptable time units are s (seconds) and ms (milliseconds). And if no time unit is specified, then it defaults to seconds. Simple as. ๐ŸŒธ


udp_query ๐Ÿ”

Tests whether the UDP query sent to the embedded UDP server is equal to what is specified by this directive.

For example,

...and here's the whole vibe in action, bestie ๐Ÿ‘‡

=== TEST 1: udp access
--- config
    location = /t {
        content_by_lua '
            local udp = ngx.socket.udp()
            udp:setpeername("127.0.0.1", 19232)
            udp:send("blah")
            local data, err = udp:receive()
            ngx.say("received: ", data)
        ';
    }
--- udp_listen: 19232
--- udp_reply: hello world
--- request
GET /t
--- udp_query: hello world
--- response_body
received: hello world

tcp_listen ๐Ÿ’–

Basically udp_listen's twin, except it boots up an embedded TCP server that listens on whatever port you name. Like so:

--- tcp_listen: 12345

Stream-typed unix domain sockets? Also yes, we love range. Just hand over the path to the socket file:

--- tcp_listen: /tmp/my-socket.sock

tcp_no_close ๐Ÿ”’

Drop this section in and the embedded TCP server (if there even is one) will not close the current TCP connection. She stays open, periodt.

tcp_reply_delay โณ

Same energy as udp_reply_delay, but for the embedded TCP server.

tcp_reply ๐Ÿ’ฌ

Same energy as udp_reply, but for the embedded TCP server.

And just like udp_reply, this one can also take a Perl subroutine value so you can generate a dynamic response packet (or, like, several of them) based on the actual query. For example:

--- tcp_reply eval
sub {
    my $req = shift;
    return "hello, $req";
}

That custom Perl subroutine can also hand back an array reference, if you're feeling extra:

--- tcp_reply eval
sub {
    my $req = shift;
    return ["hello, $req", "hiya, $req"];
}

tcp_query ๐Ÿ“จ

Same energy as udp_query, but for the embedded TCP server.

tcp_query_len ๐Ÿ“

Tells the test what length of TCP query the embedded TCP server should be expecting.

If tcp_query is specified, then tcp_query_len just defaults to the length of the value of tcp_query. Effortless.

tcp_query_auto_timeout โฐ

Don't know the query data length in advance? No problem, babe โ€” pass this option with a timeout argument and you're set.

The default timeout for reading from the peer is 0.1 sec.

tcp_shutdown โœ‚๏ธ

Shuts down the reading part, the writing part, or both in the embedded TCP server the moment a new connection is established. The value says which part gets the axe:

  • 0 โ€” read part only
  • 1 โ€” write part only
  • 2 โ€” both directions, bye ๐Ÿ’€

raw_request_middle_delay ๐Ÿข

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

no_check_leak ๐Ÿ™ˆ

Skips the tests in the current test block while in the "check leak" testing mode (i.e. when TEST_NGINX_CHECK_LEAK=1).

must_die ๐Ÿ’€

For testing the cases where Nginx must die right after starting. If a value is specified, the exit code has to match that value.

Normal request-and-response cycle does not happen here. But you can still use the error_log section to check whether there's an error message to be seen. ๐Ÿ‘€

This exists to test that bogus configuration gets noticed and given a proper error message. It is normal to see stderr error messages while running these tests (you can use suppress_stderr to suppress them).

Here's an example:

=== TEST 1: bad "return" directive
--- config
    location = /t {
        return a b c;
    }
--- request
    GET /t
--- must_die
--- error_log
invalid number of arguments in "return" directive
--- no_error_log
[error]

This configuration ignores TEST_NGINX_USE_VALGRIND, TEST_NGINX_USE_STAP or TEST_NGINX_CHECK_LEAK, because honestly there's no point checking other things when nginx is expected to die right away. ๐Ÿ˜ญ

This directive is handled before checking TEST_NGINX_IGNORE_MISSING_DIRECTIVES.

suppress_stderr ๐Ÿคซ

Sends nginx's stderr straight to /dev/null. Super useful alongside must_die.

server_addr_for_client ๐Ÿ“

This section specifies the server address Test::Nginx will connect to. If server_addr_for_client isn't set, then 127.0.0.1 is used.


Environment variables ๐ŸŒˆ

Every environment variable starting with TEST_NGINX_ gets expanded in the sections used to build the configuration of the server that the test automatically starts. This module supports the following ones, bestie:

TEST_NGINX_REUSE_PORT ๐Ÿ”

When this environment variable is set to a true value, the test scaffold automatically adds the resuseport parameter to the listen directive it generates in nginx.conf.

TEST_NGINX_USE_HTTP2 ๐Ÿš€

Enables the http2 test mode by enforcing use of the (plain text) HTTP/2 protocol to send the test request.

Under the hood, the test scaffold uses the curl command-line utility to do the wire communication with the NGINX server. That curl must be recent enough to support the --http2-prior-knowledge command-line option.

โš ๏ธ WARNING: not all the sections and features are supported in the http2 test mode. For example, pipelined_requests and raw_request will still use the HTTP/1 protocols even in the http2 test mode. Similarly, test blocks that explicitly require the HTTP 1.0 protocol will still use HTTP 1.0.

You can enable HTTP/2 mode for a single test block by specifying the http2 section, like:

--- http2

And you can disable HTTP/2 mode for an individual test block by specifying the no_http2 section, like:

--- no_http2

TEST_NGINX_USE_HTTP3 ๐Ÿ›ธ

Enables the http3 test mode by enforcing use of the HTTP/3 protocol to send the test request.

Under the hood, the test scaffold uses the curl command-line utility to do the wire communication with the NGINX server. That curl must be recent enough to support both the --http3 command-line options.

โš ๏ธ WARNING: not all the sections and features are supported in the http3 test mode. For example, pipelined_requests and raw_request will still use the HTTP/1 protocols even in the http3 test mode. Similarly, test blocks that explicitly require the HTTP 1.0 protocol will still use HTTP 1.0.

You can enable HTTP/3 mode for a single test block with the http3 section:

--- http3

And disable HTTP/3 mode for an individual test block with the no_http3 section:

--- no_http3

TEST_NGINX_HTTP3_CRT ๐Ÿ“œ

When running in http3 mode, you need to specify the default certificate.

TEST_NGINX_HTTP3_KEY ๐Ÿ”‘

When running in http3 mode, you need to specify the default key.

TEST_NGINX_QUIC_IDLE_TIMEOUT โŒ›

HTTP3 connections don't get closed when the requests are done. When you reload nginx, the older nginx won't exit until the older connections' idle timeout is reached. The default idle timeout is 60 seconds, which is way too long for the test scaffold, ngl.

Change that idle timeout value with the environment variable TEST_NGINX_QUIC_IDLE_TIMEOUT. The default idle timeout value is 0.6s if it's not set.

export TEST_NGINX_QUIC_IDLE_TIMEOUT=0.1

TEST_NGINX_VERBOSE ๐Ÿ—ฃ๏ธ

Controls whether verbose debugging messages get output in Test::Nginx. Defaults to empty.

TEST_NGINX_BENCHMARK ๐Ÿƒโ€โ™€๏ธ

Set it to a non-empty and non-zero value and the test scaffold enters benchmarking testing mode โ€” it invokes weighttp (for HTTP 1.1 requests) and ab (for HTTP 1.0 requests) to run each test case with the test request over and over.

Give it a positive number as the value and that number becomes the total number of repeated requests. For example:

export TEST_NGINX_BENCHMARK=1000

...gives you 1000 repeated requests for each test block. Defaults to 100000.

Specify a second number (separated from the first by spaces) and that second number is the concurrency level for the benchmark. For example:

export TEST_NGINX_BENCHMARK='1000 10'

...gives you 1000 repeated requests over 10 concurrent connections for each test block. The default concurrency level is 2 (or 1 if the number of requests is 1).

The benchmark testing mode will also output the actual ab or weighttp command line the test scaffold used to stderr. For example:

weighttp -c2 -k -n2000 -H 'Host: foo.com' http://127.0.0.1:1984/t

See also the TEST_NGINX_BENCHMARK_WARMUP environment.

This testing mode requires the unbuffer command-line utility from the expect package. ๐Ÿ“ฆ

TEST_NGINX_BENCHMARK_WARMUP ๐Ÿ”ฅ

Specifies the number of "warm-up" requests performed before the actual benchmark requests for each test block.

The latencies of those warm-up requests never get included in the final benchmark results.

Only meaningful in the benchmark testing mode.

See also the TEST_NGINX_BENCHMARK environment.

TEST_NGINX_CHECK_LEAK ๐Ÿšจ

Set to 1 and the test scaffold performs the most general memory leak test, by calling weighttpd/ab and ps.

Specifically, it starts weighttp (for HTTP 1.1 GET requests) or ab (for HTTP 1.0 requests) to repeatedly hit Nginx for seconds in a sub-process, and then after about 1 second it starts sampling the RSS value of the Nginx process by calling the ps utility every 20 ms. Finally, it dumps all the sample point data plus the line slope of the linear regression result on the 100 sample points.

One typical output for non-leaking test cases, looking all pristine โœจ:

t/075-logby.t .. 3/17 TEST 2: log_by_lua_file
LeakTest: [2176 2176 2176 2176 2176 2176 2176
 2176 2176 2176 2176 2176 2176 2176 2176 2176
 2176 2176 2176 2176 2176 2176 2176 2176 2176
 2176 2176 2176 2176 2176 2176 2176 2176 2176
 2176 2176 2176 2176 2176 2176 2176 2176 2176
 2176 2176 2176 2176 2176 2176 2176 2176 2176
 2176 2176 2176 2176 2176 2176 2176 2176 2176
 2176 2176 2176 2176 2176 2176 2176 2176 2176
 2176 2176 2176 2176 2176 2176 2176 2176 2176
 2176 2176 2176 2176 2176 2176 2176 2176 2176
 2176 2176 2176 2176 2176 2176 2176 2176 2176
 2176 2176 2176]
LeakTest: k=0.0

And here's an example of one that's leaking, the drama ๐Ÿ˜ญ:

TEST 5: ngx.ctx available in log_by_lua (not defined yet)
LeakTest: [4396 4440 4476 4564 4620 4708 4752
 4788 4884 4944 4996 5032 5080 5132 5188 5236
 5348 5404 5464 5524 5596 5652 5700 5776 5828
 5912 5964 6040 6108 6108 6316 6316 6584 6672
 6672 6752 6820 6912 6912 6980 7064 7152 7152
 7240 7340 7340 7432 7508 7508 7600 7700 7700
 7792 7896 7896 7992 7992 8100 8100 8204 8296
 8296 8416 8416 8512 8512 8624 8624 8744 8744
 8848 8848 8968 8968 9084 9084 9204 9204 9324
 9324 9444 9444 9584 9584 9704 9704 9832 9832
 9864 9964 9964 10096 10096 10488 10488 10488
 10488 10488 11052 11052]
LeakTest: k=64.1

Even the teeniest little leaks can get amplified and caught easily by this testing mode, because their slopes will usually be far above 1.0. Iconic surveillance honestly ๐Ÿ‘‘

Okay so you can lowkey configure the number of sample points via the TEST_NGINX_CHECK_LEAK_COUNT system environment ๐Ÿ’ซ like, say you wanna sample 1000 data points, you just set this before running the test:

export TEST_NGINX_CHECK_LEAK_COUNT=1000

And bestie, real talk: for now only GET, POST, PUT, and HEAD requests are supported โ€” that's because the HTTP support in both ab and weighttp is, um, limited. ๐Ÿ˜… Any other methods you specify in your test cases will turn to GET with force. No negotiating, no vibes check, just GET. ๐Ÿ’…

Also the tests in this mode will always succeed, because this mode also enforces the dry-run mode. So likeโ€ฆ a passing run here isn't proof of anything, ok?? It's giving "I passed the assignment because I wasn't graded" ๐Ÿ˜ญ

And test blocks carrying the --- no_check_leak directive? Skipped. Entirely. Not even invited to the party in this testing mode. Bye ๐Ÿ™ˆ


TEST_NGINX_CHECK_LEAK_COUNT ๐Ÿ“

Takes a number value which controls how many data points to be sampled in the check leak test mode. See TEST_NGINX_CHECK_LEAK for more details.

Defaults to 100.


TEST_NGINX_USE_HUP ๐Ÿฆ‹

When you set this to 1, the test scaffold will try to send a HUP signal to the Nginx master process to reload the config file between successive test blocks โ€” but not between successive repeat_each sub-tests within the same test block. Note the nuance, bestie, it matters. โœจ

When this environment is set to 1, it will also enforce the master_process on config line in the nginx.conf file, because Nginx is buggy in processing the HUP signal when the master process is off. So like, we're doing it this way for a reason, don't fight it. ๐Ÿ™


TEST_NGINX_LOAD_MODULES ๐Ÿงš

This environment takes a list of dynamic NGINX module files' paths. The test scaffold then generates a series of load_module directives in the top-level scope of nginx.conf. Iconic, honestly.

For example, when this environment takes the value ../../work/nginx/modules/ngx_http_headers_more_filter_module.so ../../work/nginx/modules/ngx_http_lua_module.so , then the following snippet will be generated in nginx.conf:

load_module ../../work/nginx/modules/ngx_http_headers_more_filter_module.so;
load_module ../../work/nginx/modules/ngx_http_lua_module.so;

This requires at least NGINX 1.9.11 to work. Old nginx said no. ๐Ÿ’€


TEST_NGINX_POSTPONE_OUTPUT ๐ŸŒธ

Defaults to empty. This environment takes positive integer numbers as its value, and it will cause the auto-generated nginx.conf file to have a postpone_output setting in the http {} block.

For example, setting TEST_NGINX_POSTPONE_OUTPUT to 1 will have the following line in nginx.conf's http {} block:

postpone_output 1;

and it will effectively disable the write buffering in nginx's ngx_http_write_module.


TEST_NGINX_NO_CLEAN ๐ŸŽ€

When this environment is set to 1, it will prevent the test scaffold from quitting the Nginx server at the end of the run. This is very useful when you want to use other tools like gdb or curl to inspect the Nginx server manually afterwards. The server stays up, you go poke at it, no one stops you. ๐Ÿ‘‘


TEST_NGINX_NO_NGINX_MANAGER ๐Ÿซถ

Defaults to 0. If set to 1, the 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. Like, you bring your own nginx, we just show up and test. ๐Ÿ’–


TEST_NGINX_FAST_SHUTDOWN โšก

Defaults to 0. If set to 1, the Test::Nginx module will stop the nginx process with SIGTERM.


TEST_NGINX_NO_SHUFFLE ๐ŸŒ€

Defaults to 0. If set to 1, it will make sure the tests are run in the order they appear in the test file (and not in random order). For my control-freak besties out there. ๐Ÿซก


TEST_NGINX_USE_VALGRIND ๐Ÿ”ฅ

If set, Test::Nginx will start nginx with valgrind, with the value of this environment as the options.

Nginx is actually started with valgrind -q $TEST_NGINX_USE_VALGRIND --gen-suppressions=all --suppressions=valgrind.suppress, the suppressions option being used only if there is actually a valgrind.suppress file.

If this environment is set to the number 1 or any other non-zero numbers, then it is equivalent to taking the value --tool=memcheck --leak-check=full.


TEST_NGINX_VALGRIND_EXIT_ON_FIRST_ERR ๐Ÿ’€

If set, Test::Nginx will add --exit-on-first-error=yes --error-exitcode=1 options for the valgrind.

Nginx is actually started with valgrind -q $TEST_NGINX_USE_VALGRIND --gen-suppressions=all --suppressions=valgrind.suppress --exit-on-first-error=yes --error-exitcode=1, the suppressions option being used only if there is actually a valgrind.suppress file.

If this environment is set to the number 1 or any other non-zero numbers, then it is equivalent to taking the value --exit-on-first-error=yes --error-exitcode=1.

You would prefer to turn on this option when multiple invalid memory accesses exist. (Bestie, that's a LOT of invalid memory accesses. ๐Ÿ˜ญ)


TEST_NGINX_USE_RR ๐ŸŽฅ

Uses Mozilla rr to record the execution of the nginx server run by the test scaffold.

This feature is experimental. โš ๏ธ Don't be shocked if the vibes are unstable.

TEST_NGINX_USE_STAP ๐Ÿ”

When set to true values (like 1), the test scaffold will use systemtap to instrument the nginx process.

You can specify the stap script in the stap section.

Note that you need to use the stap-nginx script from the nginx-dtrace project.


TEST_NGINX_STAP_OUT ๐Ÿ“

You can specify the output file for the systemtap tool. By default, a random file name under the system temporary directory is generated.

It's common to specify TEST_NGINX_STAP_OUT=/dev/stderr when debugging. Chef's kiss debugging strategy. ๐Ÿ‘จโ€๐Ÿณ๐Ÿ’‹


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 direct 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. Two birds, one env var. ๐Ÿฆ๐Ÿฆ


TEST_NGINX_SLEEP ๐Ÿ˜ด

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


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. So like, pick your struggle. ๐Ÿ˜ค


TEST_NGINX_EVENT_TYPE ๐ŸŽ›๏ธ

This environment can be used to specify an event API type to be used by Nginx. Possible values are epoll, kqueue, select, rtsig, poll, and others.

For example,

$ TEST_NGINX_EVENT_TYPE=select prove -r t

TEST_NGINX_LD_PRELOAD ๐Ÿงฒ

This environment can be used to specify LD_PRELOAD to be used by Nginx. Specifying the LD_PRELOAD environment directly in the command line is undesirable because it will affect other processes other than nginx. Rude to the other processes, honestly. ๐Ÿ™…

For example,

$ TEST_NGINX_LD_PRELOAD=/usr/lib64/libasan.so prove -r t

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. All the receipts, forever. ๐Ÿงพโœจ


TEST_NGINX_RANDOMIZE ๐ŸŽฒ

When set, the test scaffold forces the use of random server listening port numbers as well as random t/servroot_XXXX/ directories. This can help a test suite run in multiple parallel jobs via prove -jN where N is an integer bigger than 1. For instance, prove -j8 -r t runs the test suite under t/ in 8 parallel jobs, utilizing up to 8 (logical) CPU cores in the same machine. Slay. ๐Ÿ’…

Note that only a test suite without external shared and writable service dependencies (like Memcached, Redis or MySQL) can run in parallel in this way, obviously. Don't share your toys, bestie. ๐Ÿงธ


TEST_NGINX_WORKER_USER ๐Ÿ‘ค

Sets the user account used to run the nginx worker processes when the master process is enabled. This requires root access to run the nginx master process. For instance,

export TEST_NGINX_WORKER_USER='agentzh'

Defaults to the root when the master is run by root also. Otherwise defaults to the current user.

One can also add an optional user group separated by spaces, as in

export TEST_NGINX_WORKER_USER='agentzh wheel'

โœจ Special Variables โœจ

TEST_NGINX_RAND_PORT_XXX ๐ŸŽฐ

Variables like TEST_NGINX_RAND_PORT_XXX are expanded to random and unused unprivileged ports numbers to build the configuration of the server, where XXX is an integer from 1985 to 65535.

For instance, TEST_NGINX_RAND_PORT_1 will be expanded to a port number, such as 1986, and TEST_NGINX_RAND_PORT_2 will be expanded to another port number, such as 65535. Surprise ports! Nobody knows which ones! ๐ŸŽ


๐Ÿ’œ Valgrind Integration ๐Ÿ’œ

Test::Nginx has integrated support for valgrind (<http://valgrind.org>) even though by default it does not bother running it with the tests because valgrind will significantly slow down the test suite. Like, significantly. We're talking glacial. ๐ŸงŠ

First ensure that your valgrind executable is visible in your PATH env. And then run your test suite with the TEST_NGINX_USE_VALGRIND env set to true:

TEST_NGINX_USE_VALGRIND=1 prove -r t

If you see false alarms, you do have a chance to skip them by defining a ./valgrind.suppress file at the root of your module source tree, as in

<https://github.com/chaoslawful/drizzle-nginx-module/blob/master/valgrind.suppress>

This is the suppression file for ngx_drizzle. Test::Nginx will automatically use it to start nginx with valgrind memcheck if this file does exist at the expected location. Autodetect? We love to see it. ๐Ÿซถ

If you do see a lot of Connection refused errors while running the tests this way, then you probably have a slow machine (or a very busy one) such that the default waiting time is not sufficient for valgrind to start. You can define the sleep time to a larger value by setting the TEST_NGINX_SLEEP env:

TEST_NGINX_SLEEP=1 prove -r t

The time unit used here is second. The default sleep setting just fits my ThinkPad (Core2Duo T9600). ๐Ÿ’ป๐Ÿ’–

Applying the no-pool patch to your nginx core is recommended while running nginx with valgrind:

...and the repo lives at <https://github.com/shrimp/no-pool-nginx> ๐Ÿ’–

So real talk: the nginx memory pool can stop valgrind from spotting lots of invalid memory reads/writes, plus certain double-free errors. But (and this is the gag) when the no-pool patch first landed in practice, we found a whole lot more memory issues in so many of our modules ๐Ÿ˜ญ worth it though.

Also bestie, there are way more advanced features hiding in Test::Nginx that have never been documented. More on those in the near future ;)


๐ŸŽ€ Etcproxy Integration ๐ŸŽ€

โš ๏ธ WARNING: use etcproxy is no longer recommended because the mockeagain is way more effective and efficient: <https://github.com/openresty/mockeagain> โ€” pls use the new one, it's giving glow-up โœจ

The default settings in etcproxy (https://github.com/chaoslawful/etcproxy) make this small TCP proxy split the TCP packets into bytes and introduce 1 ms latency among them. Iconic chaos, but chaos.

There's usually various TCP chains that we can put etcproxy into, for example:

Test::Nginx <=> nginx

$ ./etcproxy 1234 1984

Here we're telling etcproxy: hey, listen on port 1234 and delegate all the TCP traffic to port 1984 โ€” that's the default port Test::Nginx makes nginx listen to. ๐Ÿ’…

And then we tell Test::Nginx to test against port 1234, where etcproxy is listening, rather than the port 1984 that nginx directly listens on:

$ TEST_NGINX_CLIENT_PORT=1234 prove -r t/

Then the TCP chain now looks like this:

Test::Nginx <=> etcproxy (1234) <=> nginx (1984)

So etcproxy can effectively emulate extreme network conditions and exercise "unusual" code paths in your nginx server by your tests. No cap, it's doing the most (affectionate) ๐ŸŒˆ

In practice, tons of weird bugs can be captured by this setting. Even ourselves didn't expect that this simple approach is so effective. ๐Ÿซถ

nginx <=> memcached

We first start the memcached server daemon on port 11211:

memcached -p 11211 -vv

and then we another etcproxy instance to listen on port 11984 like this:

$ ./etcproxy 11984 11211

Then we tell our t/foo.t test script to connect to 11984 rather than 11211:

# foo.t
use Test::Nginx::Socket;
repeat_each(1);
plan tests => 2 * repeat_each() * blocks();
$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211;  # make this env take a default value
run_tests();

__DATA__

=== TEST 1: sanity
--- config
location /foo {
     set $memc_cmd set;
     set $memc_key foo;
     set $memc_value bar;
     memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
}
--- request
    GET /foo
--- response_body_like: STORED
--- error_code: 201

The Test::Nginx library will automatically expand the special macro $TEST_NGINX_MEMCACHED_PORT to the environment with the same name. You can define your own $TEST_NGINX_BLAH_BLAH_PORT macros as long as its prefix is TEST_NGINX_ and all in upper case letters. So flexible, so slay โœจ

And now we can run your test script against the etcproxy port 11984:

TEST_NGINX_MEMCACHED_PORT=11984 prove t/foo.t

Then the TCP chains look like this:

Test::Nginx <=> nginx (1984) <=> etcproxy (11984) <=> memcached (11211)

If TEST_NGINX_MEMCACHED_PORT is not set, then it will take the default value 11211, which is what we want when there's no etcproxy configured:

Test::Nginx <=> nginx (1984) <=> memcached (11211)

This approach also works for proxied mysql and postgres traffic. Please see the live test suite of ngx_drizzle and ngx_postgres for more details. ๐Ÿ‘‘

Usually we set both TEST_NGINX_CLIENT_PORT and TEST_NGINX_MEMCACHED_PORT (and etc) at the same time, effectively yielding the following chain:

Test::Nginx <=> etcproxy (1234) <=> nginx (1984) <=> etcproxy (11984) <=> memcached (11211)

as long as you run two separate etcproxy instances in two separate terminals. Two terminals, two vibes, one chain ๐Ÿฆ‹

It's easy to verify if the traffic actually goes through your etcproxy server. Just check if the terminal running etcproxy emits outputs. By default, etcproxy always dump out the incoming and outgoing data to stdout/stderr. ๐Ÿ’–


๐ŸŒธ SOURCE REPOSITORY ๐ŸŒธ

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

<https://github.com/openresty/test-nginx>

If you want a commit bit, feel free to drop me a line. (fr fr, just ask ๐Ÿซถ)


โญ Community โญ

๐Ÿ’ฌ English Mailing List

The openresty-en mailing list is for English speakers:

<https://groups.google.com/group/openresty-en>

๐Ÿ’ฌ Chinese Mailing List

The openresty mailing list is for Chinese speakers:

<https://groups.google.com/group/openresty>


๐Ÿ‘‘ AUTHORS ๐Ÿ‘‘

Yichun "agentzh" Zhang (็ซ ไบฆๆ˜ฅ) <agentzh@gmail.com>, OpenResty Inc.

Antoine BONAVITA <antoine.bonavita@gmail.com>


Copyright (c) 2009-2025, Yichun Zhang <agentzh@gmail.com>, OpenResty Inc.

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

This module is licensed under the terms of the BSD license. Okay so bestie, here's the legal bit โ€” read it properly, it's long but it matters ๐Ÿ“œ

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 authors 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.

(All caps legal text is its own aesthetic honestly, ๐Ÿ’€ but it means what it says โ€” no sneaky warranty, no cap.)


๐Ÿ”ฅ SEE ALSO ๐Ÿ”ฅ

  • Test::Nginx::Lua
  • Test::Nginx::Lua::Stream
  • Test::Nginx::LWP
  • Test::Base

And that's a wrap on the whole page, bestie!! ๐ŸŽ€โœจ This one was built from perl v5.40.1, dated 2026-08-10, straight out of the User Contributed Perl Documentation โ€” go forth and write tests that eat ๐Ÿ’…๐Ÿฆ‹๐ŸŒธ

๐Ÿ“ฆ where this page comes from