about summary refs log tree commit homepage
path: root/t
diff options
context:
space:
mode:
Diffstat (limited to 't')
-rw-r--r--t/active-unix-socket.t13
-rw-r--r--t/broken-app.ru13
-rw-r--r--t/client_body_buffer_size.t5
-rw-r--r--t/heartbeat-timeout.t4
-rw-r--r--t/integration.ru11
-rw-r--r--t/integration.t128
-rw-r--r--t/lib.perl6
-rw-r--r--t/reload-bad-config.t17
-rw-r--r--t/reopen-logs.t5
-rwxr-xr-xt/t0009-broken-app.sh56
-rw-r--r--t/winch_ttin.t7
-rw-r--r--t/working_directory.t16
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') {
diff --git a/t/lib.perl b/t/lib.perl
index b20a2c6..382f08c 100644
--- a/t/lib.perl
+++ b/t/lib.perl
@@ -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 });