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

NAME

Test::CGI::External - run tests on an external CGI program

SYNOPSIS

    use utf8;
    use FindBin '$Bin';
    use Test::More;
    use Test::CGI::External;
    my $tester = Test::CGI::External->new ();
    $tester->set_cgi_executable ("$Bin/x.cgi");
    my %options;
    note ("Automatically tests");
    $tester->run (\%options);
    note ("Test with a query");
    $options{REQUEST_METHOD} = 'GET';
    $options{QUERY_STRING} = 'text="alcohol"';
    $tester->run (\%options);
    note ("Test compression of output");
    $tester->do_compression_test (1);
    note ("Test that you're getting the right kind of character output.");
    $tester->expect_charset ('UTF-8');
    note ("Test the mime type");
    $tester->expect_mime_type ('text/html');
    $tester->run (\%options);
    note ("Run your own tests on the results");
    like ($options{body}, qr/私/, "IT'S ALRIGHT!");
    done_testing ();

produces output

    ok 1 - found executable /usr/home/ben/projects/test-cgi-external/examples/x.cgi
    ok 2 - /usr/home/ben/projects/test-cgi-external/examples/x.cgi is executable
    # Automatically tests
    You have not set the request method, so I am setting it to the default, 'GET' at /usr/home/ben/projects/test-cgi-external/examples/synopsis.pl line 12.
    ok 3 - The CGI executable exited with zero status
    ok 4 - The CGI executable produced some output
    ok 5 - The CGI executable did not produce any output on the error stream
    ok 6 - Output contains a blank line
    ok 7 - The header on line 1, 'Content-Type: text/html;charset=UTF-8', appears to be a correctly-formed HTTP header
    ok 8 - There is a Content-Type header
    ok 9 - The Content-Type header is well-formed
    # Test with a query
    ok 10 - The CGI executable exited with zero status
    ok 11 - The CGI executable produced some output
    ok 12 - The CGI executable did not produce any output on the error stream
    ok 13 - Output contains a blank line
    ok 14 - The header on line 1, 'Content-Type: text/html;charset=UTF-8', appears to be a correctly-formed HTTP header
    ok 15 - There is a Content-Type header
    ok 16 - The Content-Type header is well-formed
    # Test compression of output
    # Test that you're getting the right kind of character output.
    # Test the mime type
    ok 17 - The CGI executable exited with zero status
    ok 18 - The CGI executable produced some output
    ok 19 - The CGI executable did not produce any output on the error stream
    ok 20 - Output contains a blank line
    ok 21 - The header on line 1, 'Content-Type: text/html;charset=UTF-8', appears to be a correctly-formed HTTP header
    ok 22 - The header on line 2, 'Content-Encoding: gzip', appears to be a correctly-formed HTTP header
    ok 23 - There is a Content-Type header
    ok 24 - The Content-Type header is well-formed
    ok 25 - Got expected mime type text/html = text/html
    ok 26 - Specifies a charset
    ok 27 - Got expected charset UTF-8 = UTF-8
    ok 28 - The header claims that the output is compressed
    ok 29 - The body of the CGI output was able to be decompressed using 'gunzip'. The uncompressed size is 56. The compressed output is 137.5% of the uncompressed size.
    # Run your own tests on the results
    ok 30 - IT'S ALRIGHT!
    1..30

(This example is included as synopsis.pl in the distribution.)

After the test has run, the uncompressed output is in $options{body}.

VERSION

This documents Test::CGI::External version 0.23 corresponding to git commit 4597dfaadaf6015964e45fed18a5f1f8d62f57f9 released on Fri Oct 6 14:17:09 2017 +0900.

DESCRIPTION

Test::CGI::External is a tool for mocking a CGI (common gateway interface) environment and running basic checks of the operation of a CGI program. For example,

    use Test::More;
    my $tester = Test::CGI::External->new ();
    $tester->set_cgi_executable ('example.cgi');
    $tester->run ({});
    done_testing ();

tests whether

  • there is a program called example.cgi,

  • the program example.cgi is executable (except on Windows),

and when run, example.cgi

  • produces something on standard output;

  • produces a correct Content-Type header;

  • does not print out ill-formed headers (for example, print debugging messages on standard output);

  • exits with a zero status;

  • does not print any error messages.

Test::CGI::External tests for mistakes such as forgetting to install the program, forgetting to make it executable, executing print statements before the headers are printed, exiting without any printing output, or printing useless error messages during running.

Test::CGI::External runs CGI programs as stand-alone programs, under a faked CGI-like environment created by manipulating environment variables. Thus it does not require a web server. The tested CGI program can be in any language, not just Perl; the "external" in Test::CGI::External means it is meant to test external programs which are completely independent of itself. Test::CGI::External was originally created to check the operation of CGI programs written in C.

Test::CGI::External is meant for the testing stage before the program is put onto a web server. For example, if a program with stray printf statements is uploaded to the web server and run as a CGI program, the browser will show only a 500 Server Error message. If the program is tested with this module before being uploaded, it will be much easier to find the error. Another typical mistake is forgetting to make the CGI program executable; again, this results in a difficult-to-understand server error. This module nips these sorts of problems in the bud by checking for careless mistakes before the uploading.

Test::CGI::External is TAP (Test Anything Protocol)-compliant and works with the standard Perl testing modules like Test::More.

METHODS

These are the main methods you need to run tests.

new

    my $tester = Test::CGI::External->new ();

Create a new testing object.

plan

    $tester->plan ();

Print the TAP (Test Anything Protocol) plan. This has to be done at the end of the execution. As shown in "SYNOPSIS", you usually don't have to print this but can use "done_testing" in Test::More, since this module uses the Test::Builder singleton internally.

run

    $tester->run (\%options);

Run the cgi executable specified using "set_cgi_executable" with the inputs specified in %options. The argument must be a hash reference. See "Possible options" for possible options. See "Outputs" for outputs. Here is an example:

    use FindBin '$Bin';
    use Test::More;
    use Test::CGI::External;
    my %options;
    $options{REQUEST_METHOD} = 'GET';
    $options{QUERY_STRING} = "q=rupert+the+bear";
    my $tester = Test::CGI::External->new ();
    $tester->set_cgi_executable ("$Bin/rupert.cgi");
    $tester->run (\%options);
    like ($options{body}, qr/everyone/i);
    done_testing ();

produces output

    ok 1 - found executable /usr/home/ben/projects/test-cgi-external/examples/rupert.cgi
    ok 2 - /usr/home/ben/projects/test-cgi-external/examples/rupert.cgi is executable
    ok 3 - The CGI executable exited with zero status
    ok 4 - The CGI executable produced some output
    ok 5 - The CGI executable did not produce any output on the error stream
    ok 6 - Output contains a blank line
    ok 7 - The header on line 1, 'Content-Type: text/plain', appears to be a correctly-formed HTTP header
    ok 8 - There is a Content-Type header
    ok 9 - The Content-Type header is well-formed
    ok 10
    1..10

(This example is included as rupert.pl in the distribution.)

You can also use run without an argument to do basic tests, but this module will print warnings. Use "set_no_warnings" to stop it printing these.

set_cgi_executable

    $tester->set_cgi_executable ('my.cgi');

Set the CGI program to be tested to my.cgi. This runs tests which check whether the file exists and is executable.

To send command-line options to the program, give arguments after the name of the executable:

    $tester->set_cgi_executable ('my.cgi', '-o', 'xyz');

test_not_implemented

    $tester->test_not_implemented ();

Test how your CGI program responds to an unknown request method. This sends a request with a method consisting of gibberish to the CGI and checks that the response contains the HTTP status 501 Not Implemented.

This tests compliance with 6.3.3. Status of RFC 3875:

    Status code 501 'Not Implemented' may be returned by a script if it receives an unsupported REQUEST_METHOD.

If you need to, you can supply your own request method as the first argument:

    $tester->test_not_implemented ('SUFFER');

The request method probably should not be something like POST or HEAD. If you need to test how your script handles a known but not supported request method, for example if you want to reject POST requests, use "test_method_not_allowed".

All the script outputs and headers are discarded after this method has completed.

This method was added in version 0.19.

test_method_not_allowed

    $tester->test_method_not_allowed ('POST');

Test how your CGI program responds to a known but unsupported request method, such as POST, HEAD, or OPTIONS. This sends a request with the tested method to your CGI program and captures the response. It then checks that the captured response contains the HTTP status 405 Method Not Allowed, and an Allow: header. This tests compliance with 6.3.3. Status of RFC 3875:

    The script MAY reject with error 405 'Method Not Allowed' HTTP/1.1 requests made using a method it does not support.

and 10.4.6 405 Method Not Allowed of RFC 2616:

    The response MUST include an Allow header containing a list of valid methods for the requested resource.

This test further checks the validity of the allow header by making requests for each of the request methods it defines. For example, if your allow header contains GET and POST, this makes GET and POST requests to your script to see whether it handles them or not. If a POST request is made, a very simple request with the content type set to application/x-www-form-urlencoded and the content a=b is made.

If you need to check how your script responds to completely unexpected request methods, use "test_not_implemented" rather than this method.

All the CGI script outputs, including headers, are discarded after this method has completed.

For example, given this script:

    my $rm = $ENV{REQUEST_METHOD};
    if (! $rm) {
        die "No request method";
    }
    if ($rm eq 'POST') {
        print <<EOF;
    Status: 405
    Allow: GET, HEAD
    
    EOF
    }
    elsif ($rm eq 'GET') {
        print <<EOF;
    Content-Type: text/plain
    
    Greetings
    EOF
    }
    elsif ($rm eq 'HEAD') {
        print <<EOF;
    Content-Type: text/plain
    
    EOF
    }
    else {
        print <<EOF;
    Status: 501
    
    EOF
    }
    

this test:

    use FindBin '$Bin';
    use Test::More;
    use Test::CGI::External;
    my $tester = Test::CGI::External->new ();
    $tester->set_cgi_executable ("$Bin/../examples/bad-method.cgi");
    $tester->test_not_implemented ();
    $tester->test_method_not_allowed ('POST');
    done_testing ();

produces output

    ok 1 - found executable /usr/home/ben/projects/test-cgi-external/examples/../examples/bad-method.cgi
    ok 2 - /usr/home/ben/projects/test-cgi-external/examples/../examples/bad-method.cgi is executable
    ok 3 - The CGI executable exited with zero status
    ok 4 - The CGI executable produced some output
    ok 5 - The CGI executable did not produce any output on the error stream
    ok 6 - Output contains a blank line
    ok 7 - The header on line 1, 'Status: 501', appears to be a correctly-formed HTTP header
    ok 8 - Got status header
    ok 9 - Got 501 status
    ok 10 - The CGI executable exited with zero status
    ok 11 - The CGI executable produced some output
    ok 12 - The CGI executable did not produce any output on the error stream
    ok 13 - Output contains a blank line
    ok 14 - The header on line 1, 'Status: 405', appears to be a correctly-formed HTTP header
    ok 15 - The header on line 2, 'Allow: GET, HEAD', appears to be a correctly-formed HTTP header
    ok 16 - Got Allow header
    ok 17 - Got method not allowed status
    ok 18 - The CGI executable exited with zero status
    ok 19 - The CGI executable produced some output
    ok 20 - The CGI executable did not produce any output on the error stream
    ok 21 - Output contains a blank line
    ok 22 - The header on line 1, 'Content-Type: text/plain', appears to be a correctly-formed HTTP header
    ok 23 - Method GET specified by Allow: header was allowed
    ok 24 - The CGI executable exited with zero status
    ok 25 - The CGI executable produced some output
    ok 26 - The CGI executable did not produce any output on the error stream
    ok 27 - Output contains a blank line
    ok 28 - The header on line 1, 'Content-Type: text/plain', appears to be a correctly-formed HTTP header
    ok 29 - Method HEAD specified by Allow: header was allowed
    1..29

(This example is included as bad-request.pl in the distribution.)

This method was added in version 0.19.

test_status

    $tester->run (\%options);
    $tester->test_status (501);

Call this method with a numerical HTTP status. It provides a convenient way to test whether you got an expected Status: header in the output after using "run". It runs two tests. First it runs a test that there is a status header in the output, and second it runs a test that the output status header matches your required status. If there is no output available for $tester to look at, a warning is printed and no tests are run. The argument must be a three-digit integer number. If not, a warning is printed and no tests are run.

Note that you do not need to produce a 200 status header from a CGI script. From RFC 3875

    Status code 200 'OK' indicates success, and is the default value assumed for a document response.

Thus this test is not part of the tests done by "run". It is only necessary for when you expect the status to not be 200. See the "Specification for HTTP headers" and related documents, or the Perl module HTTP::Status (part of the HTTP::Message distribution), for a list of valid HTTP statuses.

This method was added in version 0.21.

test_411

    $tester->test_411 ();

Test whether your CGI program responds to a POST request with a CONTENT_LENGTH of zero with a 411 Length Required header.

Since this response is not compulsory, and not every program needs to respond to POST requests, this test is optional. From 4.4 Message Length of RFC 2616:

    If a request contains a message-body and a Content-Length is not given, the server SHOULD respond with 400 (bad request) if it cannot determine the length of the message, or with 411 (length required) if it wishes to insist on receiving a valid Content-Length.

It's possible to send your own message, as in

    $tester->test_411 (\%options);

You need to set "REQUEST_METHOD" to POST. The output is not checked for a correct Content-Type: header.

This method was added in version 0.22. See also this discussion for motivation.

test_options

    $tester->test_options ();

Test the response of the CGI executable to an OPTIONS request. See section 9.2 of RFC 2616. This method does not take any arguments. It tests that the CGI executable correctly produces an Allow: header containing the allowed methods. It does not test whether or not the allowed methods returned are valid. There is no return value.

This method was added in version 0.23.

TEST OPTION METHODS

These methods control test options.

do_caching_test

    $tester->do_caching_test (1);

Turn on or off testing of caching. If this test is "on", it will be tested whether the CGI executable produces a "Last-Modified: " header with a correctly-formatted date, and is able to respond with a 304 "Not modified" response when sent an identical query and a date later than its "Last-Modified" date. This requires HTTP::Date to be installed.

The test of the "Not modified" part is sandboxed away from the normal testing, so at the completion of "run", the outputs are from the first run, where the program was not responding to "If Modified Since", rather than the second run. Warnings are also switched off during the "If Modified Since" part of the testing.

Here is an example, using a CGI program which always claims not to be modified when asked:

    use utf8;
    use FindBin '$Bin';
    use Test::More;
    use Test::CGI::External;
    my $tester = Test::CGI::External->new ();
    $tester->set_cgi_executable ("$Bin/never-modified.cgi");
    $tester->do_caching_test (1);
    my %options = (
        REQUEST_METHOD => 'GET',
    );
    $tester->run (\%options);
    note ("The output is the output from the non-cached version.");
    like ($options{body}, qr/Columbus/, "Body is from non-cached version");
    done_testing ();
    

produces output

    ok 1 - found executable /usr/home/ben/projects/test-cgi-external/examples/never-modified.cgi
    ok 2 - /usr/home/ben/projects/test-cgi-external/examples/never-modified.cgi is executable
    ok 3 - The CGI executable exited with zero status
    ok 4 - The CGI executable produced some output
    ok 5 - The CGI executable did not produce any output on the error stream
    ok 6 - Output contains a blank line
    ok 7 - The header on line 1, 'Content-Type: tura/satana', appears to be a correctly-formed HTTP header
    ok 8 - The header on line 2, 'Last-Modified: Thu, 1 Jan 1970 00:00:00 GMT', appears to be a correctly-formed HTTP header
    ok 9 - There is a Content-Type header
    ok 10 - The Content-Type header is well-formed
    ok 11 - Has last modified header
    ok 12 - Last modified time can be parsed by HTTP::Date
    ok 13 - The CGI executable exited with zero status
    ok 14 - The CGI executable produced some output
    ok 15 - The CGI executable did not produce any output on the error stream
    ok 16 - Output contains a blank line
    ok 17 - The header on line 1, 'Status: 304', appears to be a correctly-formed HTTP header
    ok 18 - Got status header
    ok 19 - Got 304 status
    ok 20 - No body returned with 304 response
    # The output is the output from the non-cached version.
    ok 21 - Body is from non-cached version
    1..21

(This example is included as never-modified.pl in the distribution.)

This method was added in version 0.10.

do_compression_test

    $tester->do_compression_test (1);

Turn on or off testing of compression of the output of the CGI program which is being tested. Give any true value as the first argument to turn on compression testing. Give any false value to turn off compression testing. This requires either Gzip::Faster or IO::Uncompress::Gunzip to be installed.

expect_charset

    $tester->expect_charset ('UTF-8');

Tell the tester to test whether the header declares the output character set correctly.

If you set an expected character set with expect_charset, then the body of the output is upgraded from that encoding into Perl's Unicode encoding, utf8. Unless the encoding is UTF-8, that upgrading is also added as a pass or fail test. For example,

    $tester->expect_charset ('EUC-JP');

adds a test for decoding from the EUC-JP encoding.

The behaviour of the module was changed in version 0.08 of this module. Prior to version 0.08, $output{body} was not upgraded to utf8. If you do not set an expected character set, no upgrading is done.

expect_mime_type

    $tester->expect_mime_type ('text/html');

Tell $tester what mime type to expect on the Content-Type line of the header. A test is run that the mime type is what you said.

This method was added in version 0.11.

set_html_validator

    $tester->set_html_validator ('./my-favourite-validator.pl');

Set an HTML validator. The validator should be a standalone program. It should take take arguments of file names to validate, and print out to standard output the errors it finds. It should print nothing if it doesn't find any errors. Empty output from the HTML validator program is regarded as successful completion. The error output of the HTML validator is discarded. See also "HISTORY" and "BUGS" for why this is done this way.

This method was added in version 0.10.

set_no_check_content

    $tester->set_no_check_content (1);

This turns off testing of the "Content-Type" HTTP header. For example if you want to send redirects or "not modified" responses, you usually will not send any content, but just the HTTP headers, so you don't need a "Content-Type" header.

set_no_warnings

    $tester->set_no_warnings (1);

Setting this to any true value turns off all the warnings. Setting this to a false value restores the warnings. Although the warnings are annoying, I suggest being cautious about turning these off.

This method was added in version 0.12.

set_verbosity

    $tester->set_verbosity (1);

This turns on or off messages from the module informing you of what it is doing. The default is "off".

INPUT AND OUTPUT

Possible options

The following values may be set in the argument to "run", %options.

CONTENT_TYPE
     $options{REQUEST_METHOD} = 'POST';
     $options{CONTENT_TYPE} = 'application/x-www-form-urlencoded';
     $test->run (\%options);

The content type of the input. This is necessary when "REQUEST_METHOD" is POST. It is usually either application/x-www-form-urlencoded or multipart/form-data. application/x-www-form-urlencoded is the default value for CGI form queries.

expect_errors
     $options{expect_errors} = 1;
     $test->run (\%options);

Set to a true value if the program is expected to produce errors. This inverts the test that errors are not printed, and makes it into a test that errors are printed, so if your program doesn't print an error it will fail a test.

expect_failure
    $options{expect_failure} = 1;
    $test->run (\%options);

Expect the CGI program to exit with a non-zero error status. This switches off the test that the exit status of the executable is zero. It does not actually test what error status the CGI program exits with, so a zero error status will not cause a failed test. Note carefully that this behaviour is slightly different from that of "expect_errors" in that it does not invert the test, but skips it.

html
     $options{html} = 1;
     $test->run (\%options);

If set to a true value, validate the html using a validator you have set up with "set_html_validator". It will also print a warning message if the mime type has not been set to text/html with "expect_mime_type". The html option is incompatible with the "json" and "png" options.

     $options{HTTP_COOKIE} = 'nice=day';
     $test->run (\%options);

This option sets the environment variable HTTP_COOKIE to whatever its value is. The environment variable is then unset at the end of the test run.

input
     $options{input} = $post_input;
     $test->run (\%options);

Input to send to the CGI program with a POST request. The environment variable CONTENT_LENGTH in the CGI program is automatically set to the length of this variable. See also "content_length".

json
     $options{json} = 1;
     $test->run (\%options);

Validate the body of the output as JSON using JSON::Parse. The validation is run after decompression. It will also print a warning message if the mime type has not been set to application/json or text/plain with "expect_mime_type". The json option is incompatible with the "html" and "png" options.

no_check_content
     $options{no_check_content} = 1;
     $test->run (\%options);

If this is set to a true value, the program does not check for the "Content-Type" header line produced by the CGI. This option is for the case where the CGI produces, for example, a "Location: " response without a body. See also the "set_no_check_content" method.

no_check_request_method
    $options{no_check_request_method} = 1;

If you need to set "REQUEST_METHOD" to a method other than POST, GET, or HEAD, set this to a true value to override the check of the request method. For example, if you need to check how your CGI program responds to PROPFIND requests, use

    $options{no_check_request_method} = 1;
    $options{REQUEST_METHOD} = 'PROPFIND';

This option was added in version 0.15.

png
     $options{png} = 1;
     $test->run (\%options);

Validate the body of the output as PNG (portable network graphics, an image format) using Image::PNG::Libpng. If you choose this, the parsed PNG data is output into $options{pngdata}, where you can apply any further tests to it if you wish. The following example demonstrates how this test could be applied to a CGI binary which produces a PNG output.

    use Test::More;
    use Test::CGI::External;
    use Image::PNG::Libpng ':all';
    my $binary = "/home/ben/projects/kanjivg/www/memory.cgi";
    my $tester = Test::CGI::External->new ();
    $tester->set_cgi_executable ($binary);
    my %apple;
    $apple{REQUEST_METHOD} = 'GET';
    $apple{QUERY_STRING} = 'o=apple-touch-icon-57x57.png';
    $apple{REMOTE_ADDR} = '127.0.0.1';
    $apple{png} = 1;
    $tester->expect_mime_type ('image/png');
    $tester->run (\%apple);
    my $pngdata = $apple{pngdata};
    SKIP: {
        if (! $pngdata) {
            skip 2, "no png data";
        }
        my $ihdr = get_IHDR ($pngdata);
        ok ($ihdr->{width} == 57, "width 57 as expected");
        ok ($ihdr->{height} == 57, "height 57 as expected");
    }
    done_testing ();

produces output

    ok 1 - found executable /home/ben/projects/kanjivg/www/memory.cgi
    ok 2 - /home/ben/projects/kanjivg/www/memory.cgi is executable
    ok 3 - The CGI executable exited with zero status
    ok 4 - The CGI executable produced some output
    ok 5 - The CGI executable did not produce any output on the error stream
    ok 6 - Output contains a blank line
    ok 7 - The header on line 1, 'Content-Type: image/png', appears to be a correctly-formed HTTP header
    ok 8 - The header on line 2, 'Expires: Fri, 03 Mar 2017 01:41:48 GMT', appears to be a correctly-formed HTTP header
    ok 9 - The header on line 3, 'Content-Length: 3000', appears to be a correctly-formed HTTP header
    ok 10 - There is a Content-Type header
    ok 11 - The Content-Type header is well-formed
    ok 12 - Got expected mime type image/png = image/png
    ok 13 - Could read PNG from body
    ok 14 - Got a valid value for PNG
    ok 15 - width 57 as expected
    ok 16 - height 57 as expected
    1..16

(This example is included as png-test.pl in the distribution.)

It will also print a warning message if the mime type has not been set to image/png with "expect_mime_type". The png option is incompatible with the "html" and "json" options.

QUERY_STRING
     $options{QUERY_STRING} = "word=babies";
     $test->run (\%options);

This option sets the environment variable QUERY_STRING to whatever its value is. The environment variable is then unset at the end of the test run. If you do not set this, the environment variable QUERY_STRING is set to an empty string, as required by the CGI specification. From RFC 3875, section 4.1.7. QUERY_STRING:

    The server MUST set this variable; if the Script-URI does not include a query component, the QUERY_STRING MUST be defined as an empty string ("").

This behaviour was changed in version 0.22; prior to that, QUERY_STRING was not set in the environment if the user had not set it.

REMOTE_ADDR
     $options{REMOTE_ADDR} = "127.0.0.1";
     $test->run (\%options);

This option sets the environment variable REMOTE_ADDR to whatever its value is. The environment variable is then unset at the end of the test run.

REQUEST_METHOD
     $options{REQUEST_METHOD} = "GET";
     $test->run (\%options);

This option may be set to one of POST, GET and HEAD. The module then sets the environment variable REQUEST_METHOD to this value. If not set at all, the module sets it to a default and prints a warning message.

You can also set this to any other value you want, like OPTIONS or something, using "no_check_request_method".

Outputs

The various outputs of the CGI program are also put into %options. The entire output is available as "output", the entire error output is available as "error_output", and the parsed and processed output is available as "body", "header", and "headers".

body
    my %options;
    $test->run (\%options);
    my $body = $options{body};

The body of the CGI output, the part after the headers. If you have requested compression testing with "do_compression_test", this will be the output after uncompression. If you have specified a character set with "expect_charset", it will be parsed from that character set into Perl's internal Unicode format.

content_length

The content length of your input in bytes, if you are making a POST request. Note that this is the length in bytes, so it may differ from the return value of Perl's length function.

For example,

    use utf8;
    use FindBin '$Bin';
    use Test::More;
    my $stuff = 'ばびぶべぼ';
    note (length ($stuff));
    use Test::CGI::External;
    my %options;
    $options{input} = $stuff;
    my $tester = Test::CGI::External->new ();
    $tester->set_cgi_executable ("$Bin/../t/test.cgi");
    $tester->run (\%options);
    note ($options{content_length});
    $tester->plan ();

produces output

    # 5
    ok 1 - found executable /usr/home/ben/projects/test-cgi-external/examples/../t/test.cgi
    ok 2 - /usr/home/ben/projects/test-cgi-external/examples/../t/test.cgi is executable
    You have not set the request method, so I am setting it to the default, 'GET' at /usr/home/ben/projects/test-cgi-external/examples/length.pl line 14.
    ok 3 - The CGI executable exited with zero status
    ok 4 - The CGI executable produced some output
    ok 5 - The CGI executable did not produce any output on the error stream
    ok 6 - Output contains a blank line
    ok 7 - The header on line 1, 'Content-Type: text/html; charset=UTF-8', appears to be a correctly-formed HTTP header
    ok 8 - There is a Content-Type header
    ok 9 - The Content-Type header is well-formed
    # 15
    1..9

(This example is included as length.pl in the distribution.)

error_output
    my %options;
    $test->run (\%options);
    my $error_output = $options{error_output};

Any errors output by the CGI program. This is an empty string if there were no errors, not the undefined value. If you are expecting to get error messages, remember to set "expect_errors", otherwise any error output at all will cause a test to fail.

exit_code
    $test->run (\%options);
    my $exit_code = $options{exit_code};

The exit value of the CGI program, the value of $status in

    $status = system ('./example.cgi');
    $test->run (\%options);
    my $header = $options{header};

The header part of the CGI output as a single string. This is split from "output".

headers
    $test->run (\%options);
    my $headers = $options{headers};
    print $headers->{'content-type'};

The received headers, parsed and put into lower case, with the key/value pairs of the hash reference $headers being the keys and values from the HTTP header.

For example, the status header is $options{headers}{status}, so one could test for a 400 "Bad request" HTTP status like this:

     $test->run (\%options);
     like ($options{headers}{status}, qr/400/);

This output was added in version 0.09 of this module.

output
    $test->run (\%options);
    my $output = $options{output};

The entire output of the CGI program, unprocessed. If you have requested compression testing with "do_compression_test", this will contain binary compressed data. It is also not upgraded into Unicode.

TESTS APPLIED

The following tests are applied. "Must-pass" tests cannot be switched off. "Skippable" tests can be skipped if you choose. "Optional" tests are not run unless you request them. "Invertible" tests can be made to test the opposite result.

CGI executable exists (Must-pass)

This test is run by "set_cgi_executable".

CGI executable is an executable file (Must-pass)

This test is run by "set_cgi_executable". This test is skipped on Windows.

The exit status of the CGI program is zero (Skippable)

This test is run by "run". It may be skipped using "expect_failure".

The program has produced output (Must-pass)

This test is run by "run". This tests for the problem that the program has exited without producing any output whatsoever, not that the program has not produced correctly formatted message body or headers.

The program does not produce error output (Invertible)

This test is run by "run". It may be inverted using "expect_errors". It's meant to catch things like stray fprintf statements in the executable.

The program has printed headers (Must-pass)

This test is run by "run". This tests for some headers followed by a blank line.

Each header is correctly formatted (Must-pass)

This test is run by "run". It's meant to catch things like stray printf statements in the executable, or forgetting to print a header. The header format is as defined by "Specification for HTTP headers". Each line of the header is checked as a separate test.

The program has printed a Content-Type header (Skippable)

This test is run by "run". It may be skipped using "no_check_content".

The mime type in the Content-Type header is what you want (Optional)

This test is run by "run". It is an optional test switched on by the "expect_mime_type" method.

The charset parameter of the Content-Type header is what you want (Optional)

This test is run by "run". It is an optional test switched on by the "expect_charset" method.

There is a blank line after the headers (Must-pass)

This test is run by "run". A blank line is compulsory after the headers, even if there is no response body. From RFC 3875, section 6.2:

    The response comprises a message-header and a message-body, separated by a blank line. The message-header contains one or more header fields. The body may be NULL.

        generic-response = 1*header-field NL [ response-body ]
The body of the output is compressed (Optional)

This test is run by "run". It is an optional test switched on by the "do_compression_test" method. This only supports gzip encodings.

The body of the output is in the encoding you specified (Optional)

This test is run by "run". It is an optional test switched on by the "expect_charset" method. If the parameter set by "expect_charset" is not UTF-8, it runs a test that the encoding of the output is as expected.

Caching works correctly (Optional)

This test is run by "run". It is an optional test switched on by "do_caching_test". It tests the following:

  • The CGI is producing a Last-Modified header

    If this test fails, it assumes that the CGI cannot understand If-Modified-Since requests and does not perform the following tests.

  • The CGI's Last-Modified header contains a date which HTTP::Date can parse

  • Basic tests are all run again

    All of the tests of basic functioning, such as producing output and correctly-formatted HTTP headers, are re-run under the "If-Modified-Since" regimen.

  • The CGI is correctly producing a 304 response

    It is tested whether the program is correctly producing a 304 response when sent an "If-Modified-Since" request using the Last-Modified date which it supplied in the above test. Because this is CGI, the If-Modified-Since header is supplied to your CGI program using the environment variable HTTP_IF_MODIFIED_SINCE.

  • The CGI is not producing a body

    It is tested whether the CGI is producing a body after it produces the 304 response described above. There should not be any body, just headers, in this situation.

The output is valid HTML (Optional)

This test is run by "run". It is an optional test switched on by the "html" parameter. You also need to supply your own HTML validator using the "set_html_validator" method. You should also set the expected mime type to text/html with "expect_mime_type" if you use this.

The output is valid JSON. (Optional)

This test is run by "run". It is an optional test switched on by the "json" parameter. You should also set the expected mime type to application/json with "expect_mime_type" if you use this.

The output is valid PNG. (Optional)

This test is run by "run". It is an optional test switched on by the "png" parameter. You should also set the expected mime type to image/png with "expect_mime_type" if you use this.

BUGS

This assumes line endings are \n in some places. There may be problems with \r\n support.

Due to originally not being Test::Builder based, the tests are a little strange-looking, and the pass/fail test messages are somewhat disorganized (see also "HISTORY").

The mixture of methods (see "METHODS") and options (see "Possible options") to control the tests is messy.

There is no check for MD5 checksums. For completeness, there probably should be a check for this.

The module is not adapted for Microsoft Windows. See also https://github.com/benkasminbullock/test-cgi-external/issues/1.

I wanted to include an example validator program in this distribution but I cannot find anything useable on CPAN. For example HTML::Lint seems to be very problematic, and the W3C validator seems intent on making huge amounts of fuss about things which are almost irrelevant for practical purposes. The Go program I made, mentioned below, is barely useable to anyone except me, since it insists on a range of personal coding conventions and checks for mistakes which I commonly make but which are probably unlikely to be made by anyone else. Please get in touch if you have a good idea for HTML validation.

The program doesn't sanitize %ENV, but it probably should do.

The number of tests is not really fixed, so the older way of using Test::More with a plan won't work with this module.

The HTML validator writes into the directory of the executable file, found using "$Bin" in FindBin, rather than using File::Temp or something similar.

DEPENDENCIES

This module depends on

Carp

Carp is used to print error messages.

Encode

Encode is used for converting charset encodings into Unicode. It's not necessary unless you use "expect_charset" but it is included as a dependency since almost every Perl installation will have it.

File::Temp

File::Temp is used for the temporary files which store the input, output, and error stream of the CGI program.

FindBin

FindBin is used by the HTML validation routines. See "html".

Test::Builder

Test::Builder is used for the testing framework.

Optional

Test::CGI::External uses the following modules to do optional tests (see "TESTS APPLIED"). It can be installed without these modules, since they are only used when they are required by specific tests.

Gzip::Faster

It is used to do the compression testing, so you don't need this unless you choose "do_compression_test".

HTTP::Date

HTTP::Date is used to check the dates, if you check caching with "do_caching_test".

IO::Uncompress::Gunzip

It is used to do the compression testing, so you don't need this unless you choose "do_compression_test". This module is used as a fallback if you don't have Gzip::Faster. It is used because it's a core Perl module, so most people will have this installed.

Image::PNG::Libpng

Optionally, Image::PNG::Libpng is used if you select the "png" option to test the output is valid PNG data.

JSON::Parse

JSON::Parse is used to test JSON for validity, if you choose to check JSON with "json".

Unicode::UTF8

Optionally, Unicode::UTF8 is used for converting UTF-8 encodings into Perl's internal Unicode, if you set the charset to UTF-8 with "expect_charset". If you don't have this, it falls back to Encode.

The module also used to depend on IPC::Run3, but there were some issues with this module messing around with the global variables. It does something like binmode STDOUT, which interferes with other parts of the program, so that had to be removed.

SEE ALSO

Specifications used

Specification of the Common Gateway Interface

The current specification of the Common Gateway Interface is RFC (Request For Comments) 3875 by D. Robinson and K. Coar of The Apache Software Foundation, dated October 2004. See http://www.ietf.org/rfc/rfc3875.

Specification for HTTP headers

This module's check for HTTP headers was written against the specification on pages 15 and 16 of RFC 2616 by R. Fielding et al, dated June 1999. See http://www.ietf.org/rfc/rfc2616.txt.

The Common Gateway Interface

This is my own web page which explains some things about CGI.

Other CPAN modules

Test::More

Test::CGI::External is built on top of the Test::More framework for testing Perl programs, so you need to have some familiarity with that.

CGI::Test

This is similar in some ways to Test::CGI::External, but with the following differences

Much more comprehensive mocking of environment

CGI::Test works really hard to generate a complete mock CGI environment, including sanitizing the environment variables in %ENV. Test::CGI::External is lazy about this. (See also "BUGS".)

Throws exceptions rather than tests

CGI::Test doesn't test for the very basic problems (non-executable files, stray printf statements, etc.) that Test::CGI::External does, and if it detects problems like these, it dies (throws exceptions), rather than making them into pass and fail tests.

In contrast, Test::CGI::External's philosophy is Murphy's law, "anything which can go wrong, will go wrong", and it uses the "test" channel to check that the most basic kinds of problems are not occurring, rather than throwing exceptions. CGI::Test just runs the CGI program for you and returns its output for you to test yourself.

HISTORY

See also the file Changes in the distribution for the dates of the following changes.

This module started out as a test program for a CGI program written in C. Originally, it didn't use the Perl Test::More-style framework, but counted its pass and fail tests itself. I released the module to CPAN because I couldn't find a similar alternative, and I thought it might be useful to someone. Since the initial release I have changed it to use the Test::More framework.

After version 0.05, I thought the module was not being used and deleted it from CPAN. At this point I added some options like HTML validation using an external program (which is written in Go).

Version 0.07 marked the module's return to CPAN by popular demand.

Version 0.09 added Unicode upgrading of "body".

Version 0.10 added do_caching_test and removed die_on_failure, which was made invalid by the move to Test::Builder.

Version 0.11 added support for testing mime types with "expect_mime_type" and offered a way to sidestep the nag messages with "set_no_warnings". It also reduced the number of dependencies, most of which were only needed for optional tests.

Version 0.18 added the new test that the header contains a blank line even when the body is empty.

Version 0.19 added "test_not_implemented" and "test_method_not_allowed".

Version 0.21 added "test_status".

Version 0.22 changed to always set "QUERY_STRING" and introduced "test_411".

Version 0.23 plugged a loophole for programs reading from standard input and added the "test_options" method.

AUTHOR

Ben Bullock, <bkb@cpan.org>

COPYRIGHT & LICENCE

This package and associated files are copyright (C) 2011-2017 Ben Bullock.

You can use, copy, modify and redistribute this package and associated files under the Perl Artistic Licence or the GNU General Public Licence.