diff options
Diffstat (limited to 't')
-rw-r--r-- | t/active-unix-socket.t | 13 | ||||
-rw-r--r-- | t/broken-app.ru | 13 | ||||
-rw-r--r-- | t/client_body_buffer_size.t | 5 | ||||
-rw-r--r-- | t/heartbeat-timeout.t | 4 | ||||
-rw-r--r-- | t/integration.ru | 11 | ||||
-rw-r--r-- | t/integration.t | 128 | ||||
-rw-r--r-- | t/lib.perl | 6 | ||||
-rw-r--r-- | t/reload-bad-config.t | 17 | ||||
-rw-r--r-- | t/reopen-logs.t | 5 | ||||
-rwxr-xr-x | t/t0009-broken-app.sh | 56 | ||||
-rw-r--r-- | t/winch_ttin.t | 7 | ||||
-rw-r--r-- | t/working_directory.t | 16 |
12 files changed, 155 insertions, 126 deletions
diff --git a/t/active-unix-socket.t b/t/active-unix-socket.t index ff731b5..ab3c973 100644 --- a/t/active-unix-socket.t +++ b/t/active-unix-socket.t @@ -11,29 +11,22 @@ END { kill('TERM', values(%to_kill)) if keys %to_kill } my $u1 = "$tmpdir/u1.sock"; my $u2 = "$tmpdir/u2.sock"; { - open my $fh, '>', "$tmpdir/u1.conf.rb"; - print $fh <<EOM; + write_file '>', "$tmpdir/u1.conf.rb", <<EOM; pid "$tmpdir/u.pid" listen "$u1" stderr_path "$err_log" EOM - close $fh; - - open $fh, '>', "$tmpdir/u2.conf.rb"; - print $fh <<EOM; + write_file '>', "$tmpdir/u2.conf.rb", <<EOM; pid "$tmpdir/u.pid" listen "$u2" stderr_path "$tmpdir/err2.log" EOM - close $fh; - open $fh, '>', "$tmpdir/u3.conf.rb"; - print $fh <<EOM; + write_file '>', "$tmpdir/u3.conf.rb", <<EOM; pid "$tmpdir/u3.pid" listen "$u1" stderr_path "$tmpdir/err3.log" EOM - close $fh; } my @uarg = qw(-D -E none t/integration.ru); diff --git a/t/broken-app.ru b/t/broken-app.ru deleted file mode 100644 index 5966bff..0000000 --- a/t/broken-app.ru +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: false -# we do not want Rack::Lint or anything to protect us -use Rack::ContentLength -use Rack::ContentType, "text/plain" -map "/" do - run lambda { |env| [ 200, {}, [ "OK\n" ] ] } -end -map "/raise" do - run lambda { |env| raise "BAD" } -end -map "/nil" do - run lambda { |env| nil } -end diff --git a/t/client_body_buffer_size.t b/t/client_body_buffer_size.t index d479901..c8e871d 100644 --- a/t/client_body_buffer_size.t +++ b/t/client_body_buffer_size.t @@ -4,11 +4,10 @@ use v5.14; BEGIN { require './t/lib.perl' }; use autodie; -open my $conf_fh, '>', $u_conf; -$conf_fh->autoflush(1); -print $conf_fh <<EOM; +my $conf_fh = write_file '>', $u_conf, <<EOM; client_body_buffer_size 0 EOM +$conf_fh->autoflush(1); my $srv = tcp_server(); my $host_port = tcp_host_port($srv); my @uarg = (qw(-E none t/client_body_buffer_size.ru -c), $u_conf); diff --git a/t/heartbeat-timeout.t b/t/heartbeat-timeout.t index 694867a..0ae0764 100644 --- a/t/heartbeat-timeout.t +++ b/t/heartbeat-timeout.t @@ -6,14 +6,12 @@ use autodie; use Time::HiRes qw(clock_gettime CLOCK_MONOTONIC); mkdir "$tmpdir/alt"; my $srv = tcp_server(); -open my $fh, '>', $u_conf; -print $fh <<EOM; +write_file '>', $u_conf, <<EOM; pid "$tmpdir/pid" preload_app true stderr_path "$err_log" timeout 3 # WORST FEATURE EVER EOM -close $fh; my $ar = unicorn(qw(-E none t/heartbeat-timeout.ru -c), $u_conf, { 3 => $srv }); diff --git a/t/integration.ru b/t/integration.ru index 6df481c..aaed608 100644 --- a/t/integration.ru +++ b/t/integration.ru @@ -47,6 +47,7 @@ def env_dump(env) else case k when 'rack.version', 'rack.after_reply'; h[k] = v + when 'rack.input'; h[k] = v.class.to_s end end end @@ -86,6 +87,7 @@ def rack_input_tests(env) [ 200, h, [ dig.hexdigest ] ] end +$nr_aborts = 0 run(lambda do |env| case env['REQUEST_METHOD'] when 'GET' @@ -100,6 +102,10 @@ run(lambda do |env| when '/early_hints_rack2'; early_hints(env, "r\n2") when '/early_hints_rack3'; early_hints(env, %w(r 3)) when '/broken_app'; raise RuntimeError, 'hello' + when '/aborted'; $nr_aborts += 1; [ 200, {}, [] ] + when '/nr_aborts'; [ 200, { 'nr-aborts' => "#$nr_aborts" }, [] ] + when '/nil'; nil + when '/read_fifo'; [ 200, {}, [ File.read(env['HTTP_READ_FIFO']) ] ] else '/'; [ 200, {}, [ env_dump(env) ] ] end # case PATH_INFO (GET) when 'POST' @@ -111,6 +117,11 @@ run(lambda do |env| when 'PUT' case env['PATH_INFO'] when %r{\A/rack_input}; rack_input_tests(env) + when '/env_dump'; [ 200, {}, [ env_dump(env) ] ] + end + when 'OPTIONS' + case env['REQUEST_URI'] + when '*'; [ 200, {}, [ env_dump(env) ] ] end end # case REQUEST_METHOD end) # run diff --git a/t/integration.t b/t/integration.t index d17ace0..2d448cd 100644 --- a/t/integration.t +++ b/t/integration.t @@ -18,13 +18,12 @@ if ('ensure Perl does not set SO_KEEPALIVE by default') { $val = getsockopt($srv, SOL_SOCKET, SO_KEEPALIVE); } my $t0 = time; -open my $conf_fh, '>', $u_conf; -$conf_fh->autoflush(1); my $u1 = "$tmpdir/u1"; -print $conf_fh <<EOM; +my $conf_fh = write_file '>', $u_conf, <<EOM; early_hints true listen "$u1" EOM +$conf_fh->autoflush(1); my $ar = unicorn(qw(-E none t/integration.ru -c), $u_conf, { 3 => $srv }); my $curl = which('curl'); local $ENV{NO_PROXY} = '*'; # for curl @@ -104,14 +103,75 @@ is_deeply([ grep(/^x-r3: /, @$hdr) ], SKIP: { eval { require JSON::PP } or skip "JSON::PP missing: $@", 1; - ($status, $hdr, my $json) = do_req $srv, 'GET /env_dump'; + my $get_json = sub { + my (@req) = @_; + my @r = do_req $srv, @req; + my $env = eval { JSON::PP->new->decode($r[2]) }; + diag "$@ (r[2]=$r[2])" if $@; + is ref($env), 'HASH', "@req response body is JSON"; + (@r, $env) + }; + ($status, $hdr, my $json, my $env) = $get_json->('GET /env_dump'); is($status, undef, 'no status for HTTP/0.9'); is($hdr, undef, 'no header for HTTP/0.9'); unlike($json, qr/^Connection: /smi, 'no connection header for 0.9'); unlike($json, qr!\AHTTP/!s, 'no HTTP/1.x prefix for 0.9'); - my $env = JSON::PP->new->decode($json); - is(ref($env), 'HASH', 'JSON decoded body to hashref'); is($env->{SERVER_PROTOCOL}, 'HTTP/0.9', 'SERVER_PROTOCOL is 0.9'); + is $env->{'rack.url_scheme'}, 'http', 'rack.url_scheme default'; + is $env->{'rack.input'}, 'StringIO', 'StringIO for no content'; + + my $req = 'OPTIONS *'; + ($status, $hdr, $json, $env) = $get_json->("$req HTTP/1.0"); + is $env->{REQUEST_PATH}, '', "$req => REQUEST_PATH"; + is $env->{PATH_INFO}, '', "$req => PATH_INFO"; + is $env->{REQUEST_URI}, '*', "$req => REQUEST_URI"; + + $req = 'GET http://e:3/env_dump?y=z'; + ($status, $hdr, $json, $env) = $get_json->("$req HTTP/1.0"); + is $env->{REQUEST_PATH}, '/env_dump', "$req => REQUEST_PATH"; + is $env->{PATH_INFO}, '/env_dump', "$req => PATH_INFO"; + is $env->{QUERY_STRING}, 'y=z', "$req => QUERY_STRING"; + + $req = 'GET http://e:3/env_dump#frag'; + ($status, $hdr, $json, $env) = $get_json->("$req HTTP/1.0"); + is $env->{REQUEST_PATH}, '/env_dump', "$req => REQUEST_PATH"; + is $env->{PATH_INFO}, '/env_dump', "$req => PATH_INFO"; + is $env->{QUERY_STRING}, '', "$req => QUERY_STRING"; + is $env->{FRAGMENT}, 'frag', "$req => FRAGMENT"; + + $req = 'GET http://e:3/env_dump?a=b#frag'; + ($status, $hdr, $json, $env) = $get_json->("$req HTTP/1.0"); + is $env->{REQUEST_PATH}, '/env_dump', "$req => REQUEST_PATH"; + is $env->{PATH_INFO}, '/env_dump', "$req => PATH_INFO"; + is $env->{QUERY_STRING}, 'a=b', "$req => QUERY_STRING"; + is $env->{FRAGMENT}, 'frag', "$req => FRAGMENT"; + + for my $proto (qw(https http)) { + $req = "X-Forwarded-Proto: $proto"; + ($status, $hdr, $json, $env) = $get_json->( + "GET /env_dump HTTP/1.0\r\n". + "X-Forwarded-Proto: $proto"); + is $env->{REQUEST_PATH}, '/env_dump', "$req => REQUEST_PATH"; + is $env->{PATH_INFO}, '/env_dump', "$req => PATH_INFO"; + is $env->{'rack.url_scheme'}, $proto, "$req => rack.url_scheme"; + } + + $req = 'X-Forwarded-Proto: ftp'; # invalid proto + ($status, $hdr, $json, $env) = $get_json->( + "GET /env_dump HTTP/1.0\r\n". + "X-Forwarded-Proto: ftp"); + is $env->{REQUEST_PATH}, '/env_dump', "$req => REQUEST_PATH"; + is $env->{PATH_INFO}, '/env_dump', "$req => PATH_INFO"; + is $env->{'rack.url_scheme'}, 'http', "$req => rack.url_scheme"; + + ($status, $hdr, $json, $env) = $get_json->("PUT /env_dump HTTP/1.0\r\n". + 'Content-Length: 0'); + is $env->{'rack.input'}, 'StringIO', 'content-length: 0 uses StringIO'; + + ($status, $hdr, $json, $env) = $get_json->("PUT /env_dump HTTP/1.0\r\n". + 'Content-Length: 1'); + is $env->{'rack.input'}, 'Unicorn::TeeInput', + 'content-length: 1 uses TeeInput'; } # cf. <CAO47=rJa=zRcLn_Xm4v2cHPr6c0UswaFC_omYFEH+baSxHOWKQ@mail.gmail.com> @@ -123,7 +183,23 @@ check_stderr; ($status, $hdr, $bdy) = do_req($srv, 'GET /broken_app HTTP/1.0'); like($status, qr!\AHTTP/1\.[0-1] 500\b!, 'got 500 error on broken endpoint'); is($bdy, undef, 'no response body after exception'); -truncate($errfh, 0); +seek $errfh, 0, SEEK_SET; +{ + my $nxt; + while (!defined($nxt) && defined($_ = <$errfh>)) { + $nxt = <$errfh> if /app error/; + } + ok $nxt, 'got app error' and + like $nxt, qr/\bintegration\.ru/, 'got backtrace'; +} +seek $errfh, 0, SEEK_SET; +truncate $errfh, 0; + +($status, $hdr, $bdy) = do_req($srv, 'GET /nil HTTP/1.0'); +like($status, qr!\AHTTP/1\.[0-1] 500\b!, 'got 500 error on nil endpoint'); +like slurp($err_log), qr/app error/, 'exception logged for nil'; +seek $errfh, 0, SEEK_SET; +truncate $errfh, 0; my $ck_early_hints = sub { my ($note) = @_; @@ -164,6 +240,11 @@ if ('bad requests') { ($status, $hdr) = do_req $srv, 'GET /env_dump HTTP/1/1'; like($status, qr!\AHTTP/1\.[01] 400 \b!, 'got 400 on bad request'); + for my $abs_uri (qw(ssh+http://e/ ftp://e/x http+ssh://e/x)) { + ($status, $hdr) = do_req $srv, "GET $abs_uri HTTP/1.0"; + like $status, qr!\AHTTP/1\.[01] 400 \b!, "400 on $abs_uri"; + } + $c = tcp_start($srv); print $c 'GET /'; my $buf = join('', (0..9), 'ab'); @@ -324,6 +405,39 @@ EOM my $wpid = readline($fifo_fh); like($wpid, qr/\Apid=\d+\z/a , 'new worker ready'); $ck_early_hints->('ccc on'); + + $c = tcp_start $srv, 'GET /env_dump HTTP/1.0'; + vec(my $rvec = '', fileno($c), 1) = 1; + select($rvec, undef, undef, 10) or BAIL_OUT 'timed out env_dump'; + ($status, $hdr) = slurp_hdr($c); + like $status, qr!\AHTTP/1\.[01] 200!, 'got part of first response'; + ok $hdr, 'got all headers'; + + # start a slow TCP request + my $rfifo = "$tmpdir/rfifo"; + mkfifo_die $rfifo; + $c = tcp_start $srv, "GET /read_fifo HTTP/1.0\r\nRead-FIFO: $rfifo"; + tcp_start $srv, 'GET /aborted HTTP/1.0' for (1..100); + write_file '>', $rfifo, 'TFIN'; + ($status, $hdr) = slurp_hdr($c); + like $status, qr!\AHTTP/1\.[01] 200!, 'got part of first response'; + $bdy = <$c>; + is $bdy, 'TFIN', 'got slow response from TCP socket'; + + # slow Unix socket request + $c = unix_start $u1, "GET /read_fifo HTTP/1.0\r\nRead-FIFO: $rfifo"; + vec($rvec = '', fileno($c), 1) = 1; + select($rvec, undef, undef, 10) or BAIL_OUT 'timed out Unix CCC'; + unix_start $u1, 'GET /aborted HTTP/1.0' for (1..100); + write_file '>', $rfifo, 'UFIN'; + ($status, $hdr) = slurp_hdr($c); + like $status, qr!\AHTTP/1\.[01] 200!, 'got part of first response'; + $bdy = <$c>; + is $bdy, 'UFIN', 'got slow response from Unix socket'; + + ($status, $hdr, $bdy) = do_req $srv, 'GET /nr_aborts HTTP/1.0'; + like "@$hdr", qr/nr-aborts: 0\b/, + 'aborted connections unseen by Rack app'; } if ('max_header_len internal API') { @@ -30,7 +30,7 @@ $pid_file = "$tmpdir/pid"; $fifo = "$tmpdir/fifo"; $u_sock = "$tmpdir/u.sock"; $u_conf = "$tmpdir/u.conf.rb"; -open($errfh, '>>', $err_log); +open($errfh, '+>>', $err_log); if (my $t = $ENV{TAIL}) { my @tail = $t =~ /tail/ ? split(/\s+/, $t) : (qw(tail -F)); @@ -240,7 +240,9 @@ sub unicorn { state $eng = $ENV{TEST_RUBY_ENGINE} // `$ruby -e 'print RUBY_ENGINE'`; state $ext = File::Spec->rel2abs("test/$eng-$ver/ext/unicorn_http"); state $exe = File::Spec->rel2abs("test/$eng-$ver/bin/unicorn"); - my $pid = spawn(\%env, $ruby, '-I', $lib, '-I', $ext, $exe, @args); + state $rl = $ENV{RUBYLIB} ? "$lib:$ext:$ENV{RUBYLIB}" : "$lib:$ext"; + $env{RUBYLIB} = $rl; + my $pid = spawn(\%env, $ruby, $exe, @args); UnicornTest::AutoReap->new($pid); } diff --git a/t/reload-bad-config.t b/t/reload-bad-config.t index c023b88..4c17968 100644 --- a/t/reload-bad-config.t +++ b/t/reload-bad-config.t @@ -6,32 +6,27 @@ use autodie; my $srv = tcp_server(); my $host_port = tcp_host_port($srv); my $ru = "$tmpdir/config.ru"; -my $u_conf = "$tmpdir/u.conf.rb"; -open my $fh, '>', $ru; -print $fh <<'EOM'; +write_file '>', $ru, <<'EOM'; use Rack::ContentLength use Rack::ContentType, 'text/plain' config = ru = "hello world\n" # check for config variable conflicts, too run lambda { |env| [ 200, {}, [ ru.to_s ] ] } EOM -close $fh; -open $fh, '>', $u_conf; -print $fh <<EOM; +write_file '>', $u_conf, <<EOM; preload_app true stderr_path "$err_log" EOM -close $fh; my $ar = unicorn(qw(-E none -c), $u_conf, $ru, { 3 => $srv }); my ($status, $hdr, $bdy) = do_req($srv, 'GET / HTTP/1.0'); like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid at start'); is($bdy, "hello world\n", 'body matches expected'); -open $fh, '>>', $ru; -say $fh '....this better be a syntax error in any version of ruby...'; -close $fh; +write_file '>>', $ru, <<'EOM'; +....this better be a syntax error in any version of ruby... +EOM $ar->do_kill('HUP'); # reload my @l; @@ -42,7 +37,7 @@ for (1..1000) { } diag slurp($err_log) if $ENV{V}; ok(grep(/error reloading/, @l), 'got error reloading'); -open $fh, '>', $err_log; +open my $fh, '>', $err_log; # truncate close $fh; ($status, $hdr, $bdy) = do_req($srv, 'GET / HTTP/1.0'); diff --git a/t/reopen-logs.t b/t/reopen-logs.t index 76a4dbd..14bc6ef 100644 --- a/t/reopen-logs.t +++ b/t/reopen-logs.t @@ -4,14 +4,11 @@ use v5.14; BEGIN { require './t/lib.perl' }; use autodie; my $srv = tcp_server(); -my $u_conf = "$tmpdir/u.conf.rb"; my $out_log = "$tmpdir/out.log"; -open my $fh, '>', $u_conf; -print $fh <<EOM; +write_file '>', $u_conf, <<EOM; stderr_path "$err_log" stdout_path "$out_log" EOM -close $fh; my $auto_reap = unicorn('-c', $u_conf, 't/reopen-logs.ru', { 3 => $srv } ); my ($status, $hdr, $bdy) = do_req($srv, 'GET / HTTP/1.0'); diff --git a/t/t0009-broken-app.sh b/t/t0009-broken-app.sh deleted file mode 100755 index 895b178..0000000 --- a/t/t0009-broken-app.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/sh -. ./test-lib.sh - -t_plan 9 "graceful handling of broken apps" - -t_begin "setup and start" && { - unicorn_setup - unicorn -E none -D broken-app.ru -c $unicorn_config - unicorn_wait_start -} - -t_begin "normal response is alright" && { - test xOK = x"$(curl -sSf http://$listen/)" -} - -t_begin "app raised exception" && { - curl -sSf http://$listen/raise 2> $tmp || : - grep -F 500 $tmp - > $tmp -} - -t_begin "app exception logged and backtrace not swallowed" && { - grep -F 'app error' $r_err - grep -A1 -F 'app error' $r_err | tail -1 | grep broken-app.ru: - dbgcat r_err - > $r_err -} - -t_begin "trigger bad response" && { - curl -sSf http://$listen/nil 2> $tmp || : - grep -F 500 $tmp - > $tmp -} - -t_begin "app exception logged" && { - grep -F 'app error' $r_err - > $r_err -} - -t_begin "normal responses alright afterwards" && { - > $tmp - curl -sSf http://$listen/ >> $tmp & - curl -sSf http://$listen/ >> $tmp & - curl -sSf http://$listen/ >> $tmp & - curl -sSf http://$listen/ >> $tmp & - wait - test xOK = x$(sort < $tmp | uniq) -} - -t_begin "teardown" && { - kill $unicorn_pid -} - -t_begin "check stderr" && check_stderr - -t_done diff --git a/t/winch_ttin.t b/t/winch_ttin.t index c507959..3a3d430 100644 --- a/t/winch_ttin.t +++ b/t/winch_ttin.t @@ -4,13 +4,11 @@ use v5.14; BEGIN { require './t/lib.perl' }; use autodie; use POSIX qw(mkfifo); -my $u_conf = "$tmpdir/u.conf.rb"; my $u_sock = "$tmpdir/u.sock"; my $fifo = "$tmpdir/fifo"; mkfifo($fifo, 0666) or die "mkfifo($fifo): $!"; -open my $fh, '>', $u_conf; -print $fh <<EOM; +write_file '>', $u_conf, <<EOM; pid "$tmpdir/pid" listen "$u_sock" stderr_path "$err_log" @@ -19,11 +17,10 @@ after_fork do |server, worker| File.open("$fifo", "wb") { |fp| fp.syswrite worker.nr.to_s } end EOM -close $fh; unicorn('-D', '-c', $u_conf, 't/integration.ru')->join; is($?, 0, 'daemonized properly'); -open $fh, '<', "$tmpdir/pid"; +open my $fh, '<', "$tmpdir/pid"; chomp(my $pid = <$fh>); ok(kill(0, $pid), 'daemonized PID works'); my $quit = sub { kill('QUIT', $pid) if $pid; $pid = undef }; diff --git a/t/working_directory.t b/t/working_directory.t index f9254eb..f1c0a35 100644 --- a/t/working_directory.t +++ b/t/working_directory.t @@ -5,15 +5,13 @@ use v5.14; BEGIN { require './t/lib.perl' }; use autodie; mkdir "$tmpdir/alt"; my $ru = "$tmpdir/alt/config.ru"; -open my $fh, '>', $u_conf; -print $fh <<EOM; +write_file '>', $u_conf, <<EOM; pid "$pid_file" preload_app true stderr_path "$err_log" working_directory "$tmpdir/alt" # the whole point of this test before_fork { |_,_| \$master_ppid = Process.ppid } EOM -close $fh; my $common_ru = <<'EOM'; use Rack::ContentLength @@ -21,12 +19,10 @@ use Rack::ContentType, 'text/plain' run lambda { |env| [ 200, {}, [ "#{$master_ppid}\n" ] ] } EOM -open $fh, '>', $ru; -print $fh <<EOM; +write_file '>', $ru, <<EOM; #\\--daemonize --listen $u_sock $common_ru EOM -close $fh; unicorn('-c', $u_conf)->join; # will daemonize chomp($daemon_pid = slurp($pid_file)); @@ -39,9 +35,7 @@ check_stderr; if ('test without CLI switches in config.ru') { truncate $err_log, 0; - open $fh, '>', $ru; - print $fh $common_ru; - close $fh; + write_file '>', $ru, $common_ru; unicorn('-D', '-l', $u_sock, '-c', $u_conf)->join; # will daemonize chomp($daemon_pid = slurp($pid_file)); @@ -68,8 +62,7 @@ if ('ensures broken working_directory (missing config.ru) is OK') { if ('fooapp.rb (not config.ru) works with working_directory') { truncate $err_log, 0; my $fooapp = "$tmpdir/alt/fooapp.rb"; - open $fh, '>', $fooapp; - print $fh <<EOM; + write_file '>', $fooapp, <<EOM; class Fooapp def self.call(env) b = "dir=#{Dir.pwd}" @@ -78,7 +71,6 @@ class Fooapp end end EOM - close $fh; my $srv = tcp_server; my $auto_reap = unicorn(qw(-c), $u_conf, qw(-I. fooapp.rb), { -C => '/', 3 => $srv }); |