diff --git a/lib/Test/Nginx/Socket.pm b/lib/Test/Nginx/Socket.pm index 39075345..b57c2b14 100644 --- a/lib/Test/Nginx/Socket.pm +++ b/lib/Test/Nginx/Socket.pm @@ -35,6 +35,9 @@ use Test::Nginx::Util qw( $RepeatEach timeout error_log_data + file_data + file_like_data + parse_files worker_connections master_process_enabled config_preamble @@ -516,6 +519,7 @@ again: } check_error_log($block, $res, $dry_run, $req_idx, $need_array); + check_files($block, $res, $dry_run, $req_idx, $need_array); $req_idx++; @@ -717,6 +721,189 @@ sub check_error_log ($$$$$) { } +sub check_files ($$$$$) { + my ($block, $res, $dry_run, $req_idx, $need_array) = @_; + my $name = $block->name; + my $lines; + + if (defined $block->output_files) { + my $raw = $block->output_files; + + open my $in, '<', \$raw; + + my @files; + my ($fname, $body, $chop); + while (<$in>) { + if (/>>> (\S+)\s?(\S*)/) { + if ($fname) { + push @files, [$fname, $body, $chop]; + } + $fname = $1; + if ($2 eq "chop") { + $chop = 1; + } else { + $chop = 0; + } + undef $body; + } else { + $body .= $_; + } + } + if ($fname) { + push @files, [$fname, $body, $chop]; + } + + for my $file (@files) { + my ($fname, $body, $chop) = @$file; + + if (!defined $body) { + $body = ''; + } + if (defined $file) { + my $lines = file_data($fname); + if ($chop == 1) { + chop $body; + } + if ($lines eq $body) { + SKIP: { + skip "$name - tests skipped due to the lack of directive $dry_run", 1 if $dry_run; + pass("$name - content \"$body\" matches file \"$fname\""); + } + undef $file; + } + } + } + + for my $file (@files) { + if (defined $file) { + my ($fname, $body, $chop) = @$file; + SKIP: { + skip "$name - tests skipped due to the lack of directive $dry_run", 1 if $dry_run; + fail("$name - content \"$body\" not matches file \"$fname\""); + } + } + } + + } + + if (defined $block->output_files_like) { + my $pats = parse_files($block->output_files_like); + + for my $pat (@$pats) { + if (defined $pat) { + my $lines = file_like_data(@$pat[0]); + for my $line (@$lines) { + my $val = @$pat[1]; + if ($line =~ /$val/ || $line =~ /\Q$val\E/) { + SKIP: { + skip "$name - tests skipped due to the lack of directive $dry_run", 1 if $dry_run; + pass("$name - files_like: content \"@$pat[1]\" exists in file \"@$pat[0]\""); + } + undef $pat; + } + } + } + } + for my $pat (@$pats) { + if (defined $pat) { + SKIP: { + skip "$name - tests skipped due to the lack of directive $dry_run", 1 if $dry_run; + fail("$name - files_like: content \"@$pat[1]\" not exists in file \"@$pat[0]\""); + } + } + } + } + + if (defined $block->output_files_unlike) { + my $pats = parse_files($block->output_files_unlike); + + for my $pat (@$pats) { + if (defined $pat) { + my $lines = file_like_data(@$pat[0]); + for my $line (@$lines) { + my $val = @$pat[1]; + if ($line =~ /$val/ || $line =~ /\Q$val\E/) { + SKIP: { + skip "$name - tests skipped due to the lack of directive $dry_run", 1 if $dry_run; + fail("$name - files_not_like: content \"@$pat[1]\" exists in file \"@$pat[0]\""); + } + undef $pat; + } + } + } + } + for my $pat (@$pats) { + if (defined $pat) { + SKIP: { + skip "$name - tests skipped due to the lack of directive $dry_run", 1 if $dry_run; + pass("$name - files_not_like: content \"@$pat[1]\" not exists in file \"@$pat[0]\""); + } + } + } + } + + if ($block->files_exist) { + my $files = $block->files_exist; + if (!ref $files) { + chomp $files; + my @lines = split /\n+/, $files; + $files = \@lines; + } else { + my @clone = @$files; + $files = \@clone; + } + + for my $file (@$files) { + if (-e $file) { + SKIP: { + skip "$name - tests skipped due to the lack of directive $dry_run", 1 if $dry_run; + pass("$name - files_exist: \"$file\" exists"); + } + undef $file; + } + } + for my $file (@$files) { + if (defined $file) { + SKIP: { + skip "$name - tests skipped due to the lack of directive $dry_run", 1 if $dry_run; + fail("$name - files_exist: \"$file\" is not existing"); + } + } + } + } + + if ($block->files_not_exist) { + my $files = $block->files_not_exist; + if (!ref $files) { + chomp $files; + my @lines = split /\n+/, $files; + $files = \@lines; + } else { + my @clone = @$files; + $files = \@clone; + } + + for my $file (@$files) { + if (-e $file) { + SKIP: { + skip "$name - tests skipped due to the lack of directive $dry_run", 1 if $dry_run; + fail("$name - files_not_exist: \"$file\" exists"); + } + undef $file; + } + } + for my $file (@$files) { + if (defined $file) { + SKIP: { + skip "$name - tests skipped due to the lack of directive $dry_run", 1 if $dry_run; + pass("$name - files_not_exist: \"$file\" is not existing"); + } + } + } + } + +} + sub fmt_str ($) { my $str = shift; chomp $str; @@ -1591,6 +1778,12 @@ test) is listening to: As usual, if the test is made of multiple requests, then raw_response_headers_like B be an array. +=head2 timeout + +Specifys the timeout value for test case running, default to 2(sec). + + --- timeout: 50 + =head2 error_code The expected value of the HTTP response code. If not set, this is assumed @@ -1665,6 +1858,120 @@ Just like the C<--- error_log> section, one can also specify multiple patterns: Then if any line in F contains the string C<"abc"> or match the Perl regex C, then the test will fail. +=head2 output_files + +Checks if content of the file is equal to specified string. Usage most likes user_files, additional directive "chop" is added to remove unnecessary "\n" for some files. + +For example, + + === TEST 0: write to file + --- config + location /write/to/file { + content_by_lua ' + io.output("/tmp/file",rw); + io.write("hello"); + io.flush(); + io.output("/tmp/file1",rw); + io.write("hello\\nworld\\n"); + io.flush(); + io.close(); + '; + } + --- request + GET /write/to/file + --- error_code: 200 + --- output_files + >>> /tmp/file chop + hello + >>> /tmp/file1 + hello + world + + +Then content of the F is "abcd", first case should be passed and second case failed. + +=head2 output_files_like + +Checks if specified pattern matches one line of the file. First section seperated by colon is file name and the second section is match pattern. + +For example, + + === TEST 1: write to file + --- config + location /write/to/file { + content_by_lua ' + io.output("/tmp/file",rw); + io.write("abcd"); + io.flush(); + io.close(); + '; + } + --- request + GET /write/to/file + --- error_code: 200 + --- output_files_like + /tmp/file: abcde + /tmp/file: ^abc + +Then content of the F is "abcd", first case should be failed and second case passed. + +=head2 output_files_unlike + +Likes C<--- output_files_like> section, but does the opposite test, i.e., +checks if specified pattern matches none line of the file. First section seperated by colon is file name and the second section is match pattern. + +For example, + + === TEST 1: write to file + --- config + location /write/to/file { + content_by_lua ' + io.output("/tmp/file",rw); + io.write("abcd"); + io.flush(); + io.close(); + '; + } + --- request + GET /write/to/file + --- error_code: 200 + --- output_files_unlike + /tmp/file: abc + /tmp/file: ^bc + +Then content of the F is "abcd", first case should be failed and second case passed. + +=head2 pre_remove_files + +Remove files before start nginx. Both multi lines and eval are supported. For example: + + --- pre_remove_files + /tmp/file + /tmp/file1 + +and: + + --- pre_remove_files eval + ["/tmp/file", "/tmp/file1"] + +=head2 files_exist + +Check if specified files exist. + + --- files_exist + /tmp/file + /tmp/file1 + +And "eval" is also supported. + + --- files_exist eval + ["/tmp/file2", "/tmp/file3"] + + +=head2 files_not_exist + +Likes C<--- files_exist> section, but does the opposite test. + =head2 raw_request The exact request to send to nginx. This is useful when you want to test diff --git a/lib/Test/Nginx/Util.pm b/lib/Test/Nginx/Util.pm index 242a98ca..4cab5663 100644 --- a/lib/Test/Nginx/Util.pm +++ b/lib/Test/Nginx/Util.pm @@ -147,6 +147,9 @@ sub master_process_enabled (@) { our @EXPORT_OK = qw( bail_out error_log_data + file_data + file_like_data + parse_files setup_server_root write_config_file get_canon_version @@ -279,6 +282,68 @@ sub error_log_data () { return \@lines; } +sub file_data ($) { + my ($file) = @_; + open my $in, $file or + return undef; + my $lines = do { local $/; <$in>; }; + close $in; + return $lines; +} + +sub file_like_data ($) { + my ($file) = @_; + open my $in, $file or + return undef; + my @lines = <$in>; + close $in; + return \@lines; +} + +sub parse_files ($) { + my $s = shift; + my @headers; + my @lines; + open my $in, '<', \$s; + while (<$in>) { + s/^\s+|\s+$//g; + my $neg = ($_ =~ s/^!\s*//); + if (!$neg) { + my ($key, $val) = split /\s*:\s*/, $_, 2; + @headers = ($key, $val); + push @lines, [ @headers ]; + #warn "neg: $key $val"; + } + } + close $in; + return \@lines; +} + +sub pre_remove_files ($) { + my $block = shift; + + my $name = $block->name; + + if ($block->pre_remove_files) { + my $files = $block->pre_remove_files; + if (!ref $files) { + chomp $files; + my @lines = split /\n+/, $files; + $files = \@lines; + } else { + my @clone = @$files; + $files = \@clone; + } + + for my $file (@$files) { + if (-e $file) { + unlink $file or die "unlink $file failed\n"; + } + } + + } +} + sub run_tests () { $NginxVersion = get_nginx_version(); @@ -800,6 +865,7 @@ start_nginx: #warn "*** Restarting the nginx server...\n"; setup_server_root(); write_user_files($block); + pre_remove_files($block); write_config_file($config, $block->http_config, $block->main_config); #warn "nginx binary: $NginxBinary"; if ( ! can_run($NginxBinary) ) {