about summary refs log tree commit homepage
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rwxr-xr-xtest/aggregate.rb2
-rw-r--r--test/benchmark/README5
-rw-r--r--test/benchmark/big_request.rb44
-rw-r--r--test/benchmark/request.rb56
-rw-r--r--test/benchmark/response.rb30
-rw-r--r--test/exec/test_exec.rb364
-rw-r--r--test/rails/app-1.2.3/app/controllers/application.rb2
-rw-r--r--test/rails/app-1.2.3/app/controllers/foo_controller.rb2
-rw-r--r--test/rails/app-1.2.3/app/helpers/application_helper.rb2
-rw-r--r--test/rails/app-1.2.3/config/boot.rb2
-rw-r--r--test/rails/app-1.2.3/config/environment.rb2
-rw-r--r--test/rails/app-1.2.3/config/environments/development.rb2
-rw-r--r--test/rails/app-1.2.3/config/environments/production.rb2
-rw-r--r--test/rails/app-1.2.3/config/routes.rb2
-rw-r--r--test/rails/app-2.0.2/app/controllers/application.rb2
-rw-r--r--test/rails/app-2.0.2/app/controllers/foo_controller.rb2
-rw-r--r--test/rails/app-2.0.2/app/helpers/application_helper.rb2
-rw-r--r--test/rails/app-2.0.2/config/boot.rb2
-rw-r--r--test/rails/app-2.0.2/config/environment.rb2
-rw-r--r--test/rails/app-2.0.2/config/environments/development.rb2
-rw-r--r--test/rails/app-2.0.2/config/environments/production.rb2
-rw-r--r--test/rails/app-2.0.2/config/routes.rb2
-rw-r--r--test/rails/app-2.1.2/app/controllers/application.rb2
-rw-r--r--test/rails/app-2.1.2/app/controllers/foo_controller.rb2
-rw-r--r--test/rails/app-2.1.2/app/helpers/application_helper.rb2
-rw-r--r--test/rails/app-2.1.2/config/boot.rb2
-rw-r--r--test/rails/app-2.1.2/config/environment.rb2
-rw-r--r--test/rails/app-2.1.2/config/environments/development.rb2
-rw-r--r--test/rails/app-2.1.2/config/environments/production.rb2
-rw-r--r--test/rails/app-2.1.2/config/routes.rb2
-rw-r--r--test/rails/app-2.2.2/app/controllers/application.rb2
-rw-r--r--test/rails/app-2.2.2/app/controllers/foo_controller.rb2
-rw-r--r--test/rails/app-2.2.2/app/helpers/application_helper.rb2
-rw-r--r--test/rails/app-2.2.2/config/boot.rb2
-rw-r--r--test/rails/app-2.2.2/config/environment.rb2
-rw-r--r--test/rails/app-2.2.2/config/environments/development.rb2
-rw-r--r--test/rails/app-2.2.2/config/environments/production.rb2
-rw-r--r--test/rails/app-2.2.2/config/routes.rb2
-rw-r--r--test/rails/app-2.3.5/.gitignore (renamed from test/rails/app-2.3.2.1/.gitignore)0
-rw-r--r--test/rails/app-2.3.5/Rakefile (renamed from test/rails/app-2.3.2.1/Rakefile)0
-rw-r--r--test/rails/app-2.3.5/app/controllers/application_controller.rb (renamed from test/rails/app-2.3.2.1/app/controllers/application_controller.rb)2
-rw-r--r--test/rails/app-2.3.5/app/controllers/foo_controller.rb (renamed from test/rails/app-2.3.2.1/app/controllers/foo_controller.rb)2
-rw-r--r--test/rails/app-2.3.5/app/helpers/application_helper.rb (renamed from test/rails/app-2.3.2.1/app/helpers/application_helper.rb)2
-rw-r--r--test/rails/app-2.3.5/config/boot.rb (renamed from test/rails/app-2.3.2.1/config/boot.rb)2
-rw-r--r--test/rails/app-2.3.5/config/database.yml (renamed from test/rails/app-2.3.2.1/config/database.yml)0
-rw-r--r--test/rails/app-2.3.5/config/environment.rb (renamed from test/rails/app-2.3.2.1/config/environment.rb)2
-rw-r--r--test/rails/app-2.3.5/config/environments/development.rb (renamed from test/rails/app-2.3.2.1/config/environments/development.rb)2
-rw-r--r--test/rails/app-2.3.5/config/environments/production.rb (renamed from test/rails/app-2.3.2.1/config/environments/production.rb)2
-rw-r--r--test/rails/app-2.3.5/config/routes.rb (renamed from test/rails/app-2.3.2.1/config/routes.rb)2
-rw-r--r--test/rails/app-2.3.5/db/.gitignore (renamed from test/rails/app-2.3.2.1/db/.gitignore)0
-rw-r--r--test/rails/app-2.3.5/log/.gitignore (renamed from test/rails/app-2.3.2.1/log/.gitignore)0
-rw-r--r--test/rails/app-2.3.5/public/404.html (renamed from test/rails/app-2.3.2.1/public/404.html)0
-rw-r--r--test/rails/app-2.3.5/public/500.html (renamed from test/rails/app-2.3.2.1/public/500.html)0
-rw-r--r--test/rails/app-2.3.5/public/x.txt1
-rw-r--r--test/rails/test_rails.rb55
-rw-r--r--test/test_helper.rb36
-rw-r--r--test/unit/test_configurator.rb65
-rw-r--r--test/unit/test_http_parser.rb304
-rw-r--r--test/unit/test_http_parser_ng.rb420
-rw-r--r--test/unit/test_request.rb43
-rw-r--r--test/unit/test_response.rb13
-rw-r--r--test/unit/test_server.rb132
-rw-r--r--test/unit/test_signals.rb31
-rw-r--r--test/unit/test_socket_helper.rb16
-rw-r--r--test/unit/test_tee_input.rb229
-rw-r--r--test/unit/test_upload.rb236
-rw-r--r--test/unit/test_util.rb7
67 files changed, 1838 insertions, 331 deletions
diff --git a/test/aggregate.rb b/test/aggregate.rb
index 1c2cc5c..5eebbe5 100755
--- a/test/aggregate.rb
+++ b/test/aggregate.rb
@@ -1,4 +1,6 @@
 #!/usr/bin/ruby -n
+# -*- encoding: binary -*-
+
 BEGIN { $tests = $assertions = $failures = $errors = 0 }
 
 $_ =~ /(\d+) tests, (\d+) assertions, (\d+) failures, (\d+) errors/ or next
diff --git a/test/benchmark/README b/test/benchmark/README
index b63b8a3..1d3cdd0 100644
--- a/test/benchmark/README
+++ b/test/benchmark/README
@@ -42,11 +42,6 @@ The benchmark client is usually httperf.
 Another gentle reminder: performance with slow networks/clients
 is NOT our problem.  That is the job of nginx (or similar).
 
-== request.rb, response.rb, big_request.rb
-
-These are micro-benchmarks designed to test internal components
-of Unicorn.  It assumes the internal Unicorn API is mostly stable.
-
 == Contributors
 
 This directory is maintained independently in the "benchmark" branch
diff --git a/test/benchmark/big_request.rb b/test/benchmark/big_request.rb
deleted file mode 100644
index a250c62..0000000
--- a/test/benchmark/big_request.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-require 'benchmark'
-require 'tempfile'
-require 'unicorn'
-nr = ENV['nr'] ? ENV['nr'].to_i : 100
-bs = ENV['bs'] ? ENV['bs'].to_i : (1024 * 1024)
-count = ENV['count'] ? ENV['count'].to_i : 4
-length = bs * count
-slice = (' ' * bs).freeze
-
-big = Tempfile.new('')
-
-def big.unicorn_peeraddr # old versions of Unicorn used this
-  '127.0.0.1'
-end
-
-big.syswrite(
-"PUT /hello/world/puturl?abcd=efg&hi#anchor HTTP/1.0\r\n" \
-"Host: localhost\r\n" \
-"Accept: */*\r\n" \
-"Content-Length: #{length}\r\n" \
-"User-Agent: test-user-agent 0.1.0 (Mozilla compatible) 5.0 asdfadfasda\r\n" \
-"\r\n")
-count.times { big.syswrite(slice) }
-big.sysseek(0)
-big.fsync
-
-include Unicorn
-request = HttpRequest.new(Logger.new($stderr))
-unless request.respond_to?(:reset)
-  def request.reset
-    # no-op
-  end
-end
-
-Benchmark.bmbm do |x|
-  x.report("big") do
-    for i in 1..nr
-      request.read(big)
-      request.reset
-      big.sysseek(0)
-    end
-  end
-end
-
diff --git a/test/benchmark/request.rb b/test/benchmark/request.rb
deleted file mode 100644
index fc7822c..0000000
--- a/test/benchmark/request.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-require 'benchmark'
-require 'unicorn'
-nr = ENV['nr'] ? ENV['nr'].to_i : 100000
-
-class TestClient
-  def initialize(response)
-    @response = (response.join("\r\n") << "\r\n\r\n").freeze
-  end
-  def sysread(len, buf)
-    buf.replace(@response)
-  end
-
-  alias readpartial sysread
-
-  # old versions of Unicorn used this
-  def unicorn_peeraddr
-    '127.0.0.1'
-  end
-end
-
-small = TestClient.new([
-  'GET / HTTP/1.0',
-  'Host: localhost',
-  'Accept: */*',
-  'User-Agent: test-user-agent 0.1.0'
-])
-
-medium = TestClient.new([
-  'GET /hello/world/geturl?abcd=efg&hi#anchor HTTP/1.0',
-  'Host: localhost',
-  'Accept: */*',
-  'User-Agent: test-user-agent 0.1.0 (Mozilla compatible) 5.0 asdfadfasda'
-])
-
-include Unicorn
-request = HttpRequest.new(Logger.new($stderr))
-unless request.respond_to?(:reset)
-  def request.reset
-    # no-op
-  end
-end
-
-Benchmark.bmbm do |x|
-  x.report("small") do
-    for i in 1..nr
-      request.read(small)
-      request.reset
-    end
-  end
-  x.report("medium") do
-    for i in 1..nr
-      request.read(medium)
-      request.reset
-    end
-  end
-end
diff --git a/test/benchmark/response.rb b/test/benchmark/response.rb
deleted file mode 100644
index cb7397b..0000000
--- a/test/benchmark/response.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-require 'benchmark'
-require 'unicorn'
-
-class NullWriter
-  def syswrite(buf); buf.size; end
-  alias write syswrite
-  def close; end
-end
-
-include Unicorn
-
-socket = NullWriter.new
-bs = ENV['bs'] ? ENV['bs'].to_i : 4096
-count = ENV['count'] ? ENV['count'].to_i : 1
-slice = (' ' * bs).freeze
-body = (1..count).map { slice }.freeze
-hdr = {
-  'Content-Length' => (bs * count).to_s.freeze,
-  'Content-Type' => 'text/plain'.freeze
-}.freeze
-response = [ 200, hdr, body ].freeze
-
-nr = ENV['nr'] ? ENV['nr'].to_i : 100000
-Benchmark.bmbm do |x|
-  x.report do
-    for i in 1..nr
-      HttpResponse.write(socket.dup, response)
-    end
-  end
-end
diff --git a/test/exec/test_exec.rb b/test/exec/test_exec.rb
index 014b270..24ba856 100644
--- a/test/exec/test_exec.rb
+++ b/test/exec/test_exec.rb
@@ -1,4 +1,7 @@
+# -*- encoding: binary -*-
+
 # Copyright (c) 2009 Eric Wong
+FLOCK_PATH = File.expand_path(__FILE__)
 require 'test/test_helper'
 
 do_test = true
@@ -25,6 +28,13 @@ use Rack::ContentLength
 run proc { |env| [ 200, { 'Content-Type' => 'text/plain' }, [ "HI\\n" ] ] }
   EOS
 
+  SHOW_RACK_ENV = <<-EOS
+use Rack::ContentLength
+run proc { |env|
+  [ 200, { 'Content-Type' => 'text/plain' }, [ ENV['RACK_ENV'] ] ]
+}
+  EOS
+
   HELLO = <<-EOS
 class Hello
   def call(env)
@@ -72,11 +82,148 @@ end
     end
   end
 
+  def test_working_directory_rel_path_config_file
+    other = Tempfile.new('unicorn.wd')
+    File.unlink(other.path)
+    Dir.mkdir(other.path)
+    File.open("config.ru", "wb") do |fp|
+      fp.syswrite <<EOF
+use Rack::ContentLength
+run proc { |env| [ 200, { 'Content-Type' => 'text/plain' }, [ Dir.pwd ] ] }
+EOF
+    end
+    FileUtils.cp("config.ru", other.path + "/config.ru")
+    Dir.chdir(@tmpdir)
+
+    tmp = File.open('unicorn.config', 'wb')
+    tmp.syswrite <<EOF
+working_directory '#@tmpdir'
+listen '#@addr:#@port'
+EOF
+    pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } }
+    wait_workers_ready("test_stderr.#{pid}.log", 1)
+    results = hit(["http://#@addr:#@port/"])
+    assert_equal @tmpdir, results.first
+    File.truncate("test_stderr.#{pid}.log", 0)
+
+    tmp.sysseek(0)
+    tmp.truncate(0)
+    tmp.syswrite <<EOF
+working_directory '#{other.path}'
+listen '#@addr:#@port'
+EOF
+
+    Process.kill(:HUP, pid)
+    lines = []
+    re = /config_file=(.+) would not be accessible in working_directory=(.+)/
+    until lines.grep(re)
+      sleep 0.1
+      lines = File.readlines("test_stderr.#{pid}.log")
+    end
+
+    File.truncate("test_stderr.#{pid}.log", 0)
+    FileUtils.cp('unicorn.config', other.path + "/unicorn.config")
+    Process.kill(:HUP, pid)
+    wait_workers_ready("test_stderr.#{pid}.log", 1)
+    results = hit(["http://#@addr:#@port/"])
+    assert_equal other.path, results.first
+
+    Process.kill(:QUIT, pid)
+    ensure
+      FileUtils.rmtree(other.path)
+  end
+
+  def test_working_directory
+    other = Tempfile.new('unicorn.wd')
+    File.unlink(other.path)
+    Dir.mkdir(other.path)
+    File.open("config.ru", "wb") do |fp|
+      fp.syswrite <<EOF
+use Rack::ContentLength
+run proc { |env| [ 200, { 'Content-Type' => 'text/plain' }, [ Dir.pwd ] ] }
+EOF
+    end
+    FileUtils.cp("config.ru", other.path + "/config.ru")
+    tmp = Tempfile.new('unicorn.config')
+    tmp.syswrite <<EOF
+working_directory '#@tmpdir'
+listen '#@addr:#@port'
+EOF
+    pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } }
+    wait_workers_ready("test_stderr.#{pid}.log", 1)
+    results = hit(["http://#@addr:#@port/"])
+    assert_equal @tmpdir, results.first
+    File.truncate("test_stderr.#{pid}.log", 0)
+
+    tmp.sysseek(0)
+    tmp.truncate(0)
+    tmp.syswrite <<EOF
+working_directory '#{other.path}'
+listen '#@addr:#@port'
+EOF
+
+    Process.kill(:HUP, pid)
+    wait_workers_ready("test_stderr.#{pid}.log", 1)
+    results = hit(["http://#@addr:#@port/"])
+    assert_equal other.path, results.first
+
+    Process.kill(:QUIT, pid)
+    ensure
+      FileUtils.rmtree(other.path)
+  end
+
+  def test_working_directory_controls_relative_paths
+    other = Tempfile.new('unicorn.wd')
+    File.unlink(other.path)
+    Dir.mkdir(other.path)
+    File.open("config.ru", "wb") do |fp|
+      fp.syswrite <<EOF
+use Rack::ContentLength
+run proc { |env| [ 200, { 'Content-Type' => 'text/plain' }, [ Dir.pwd ] ] }
+EOF
+    end
+    FileUtils.cp("config.ru", other.path + "/config.ru")
+    system('mkfifo', "#{other.path}/fifo")
+    tmp = Tempfile.new('unicorn.config')
+    tmp.syswrite <<EOF
+pid "pid_file_here"
+stderr_path "stderr_log_here"
+stdout_path "stdout_log_here"
+working_directory '#{other.path}'
+listen '#@addr:#@port'
+after_fork do |server, worker|
+  File.open("fifo", "wb").close
+end
+EOF
+    pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } }
+    File.open("#{other.path}/fifo", "rb").close
+
+    assert ! File.exist?("stderr_log_here")
+    assert ! File.exist?("stdout_log_here")
+    assert ! File.exist?("pid_file_here")
+
+    assert ! File.exist?("#@tmpdir/stderr_log_here")
+    assert ! File.exist?("#@tmpdir/stdout_log_here")
+    assert ! File.exist?("#@tmpdir/pid_file_here")
+
+    assert File.exist?("#{other.path}/pid_file_here")
+    assert_equal "#{pid}\n", File.read("#{other.path}/pid_file_here")
+    assert File.exist?("#{other.path}/stderr_log_here")
+    assert File.exist?("#{other.path}/stdout_log_here")
+    wait_master_ready("#{other.path}/stderr_log_here")
+
+    Process.kill(:QUIT, pid)
+    ensure
+      FileUtils.rmtree(other.path)
+  end
+
+
   def test_exit_signals
     %w(INT TERM QUIT).each do |sig|
       File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
       pid = xfork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
       wait_master_ready("test_stderr.#{pid}.log")
+      wait_workers_ready("test_stderr.#{pid}.log", 1)
       status = nil
       assert_nothing_raised do
         Process.kill(sig, pid)
@@ -98,6 +245,46 @@ end
     assert_shutdown(pid)
   end
 
+  def test_rack_env_unset
+    File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
+    pid = fork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
+    results = retry_hit(["http://#{@addr}:#{@port}/"])
+    assert_equal "development", results.first
+    assert_shutdown(pid)
+  end
+
+  def test_rack_env_cli_set
+    File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
+    pid = fork {
+      redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port", "-Easdf") }
+    }
+    results = retry_hit(["http://#{@addr}:#{@port}/"])
+    assert_equal "asdf", results.first
+    assert_shutdown(pid)
+  end
+
+  def test_rack_env_ENV_set
+    File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
+    pid = fork {
+      ENV["RACK_ENV"] = "foobar"
+      redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") }
+    }
+    results = retry_hit(["http://#{@addr}:#{@port}/"])
+    assert_equal "foobar", results.first
+    assert_shutdown(pid)
+  end
+
+  def test_rack_env_cli_override_ENV
+    File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
+    pid = fork {
+      ENV["RACK_ENV"] = "foobar"
+      redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port", "-Easdf") }
+    }
+    results = retry_hit(["http://#{@addr}:#{@port}/"])
+    assert_equal "asdf", results.first
+    assert_shutdown(pid)
+  end
+
   def test_ttin_ttou
     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
     pid = fork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
@@ -603,6 +790,26 @@ end
     reexec_usr2_quit_test(new_pid, pid_file)
   end
 
+  def test_daemonize_redirect_fail
+    pid_file = "#{@tmpdir}/test.pid"
+    log = Tempfile.new('unicorn_test_log')
+    ucfg = Tempfile.new('unicorn_test_config')
+    ucfg.syswrite("pid #{pid_file}\"\n")
+    err = Tempfile.new('stderr')
+    out = Tempfile.new('stdout ')
+
+    File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
+    pid = xfork do
+      $stderr.reopen(err.path, "a")
+      $stdout.reopen(out.path, "a")
+      exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
+    end
+    pid, status = Process.waitpid2(pid)
+    assert ! status.success?, "original process exited successfully"
+    sleep 1 # can't waitpid on a daemonized process :<
+    assert err.stat.size > 0
+  end
+
   def test_reexec_fd_leak
     unless RUBY_PLATFORM =~ /linux/ # Solaris may work, too, but I forget...
       warn "FD leak test only works on Linux at the moment"
@@ -626,6 +833,7 @@ end
     end
 
     wait_master_ready(log.path)
+    wait_workers_ready(log.path, 1)
     File.truncate(log.path, 0)
     wait_for_file(pid_file)
     orig_pid = pid = File.read(pid_file).to_i
@@ -641,6 +849,7 @@ end
     wait_for_death(pid)
 
     wait_master_ready(log.path)
+    wait_workers_ready(log.path, 1)
     File.truncate(log.path, 0)
     wait_for_file(pid_file)
     pid = File.read(pid_file).to_i
@@ -660,6 +869,7 @@ end
     wait_for_death(pid)
 
     wait_master_ready(log.path)
+    wait_workers_ready(log.path, 1)
     File.truncate(log.path, 0)
     wait_for_file(pid_file)
     pid = File.read(pid_file).to_i
@@ -671,4 +881,158 @@ end
     wait_for_death(pid)
   end
 
+  def hup_test_common(preload)
+    File.open("config.ru", "wb") { |fp| fp.syswrite(HI.gsub("HI", '#$$')) }
+    pid_file = Tempfile.new('pid')
+    ucfg = Tempfile.new('unicorn_test_config')
+    ucfg.syswrite("listen '#@addr:#@port'\n")
+    ucfg.syswrite("pid '#{pid_file.path}'\n")
+    ucfg.syswrite("preload_app true\n") if preload
+    ucfg.syswrite("stderr_path 'test_stderr.#$$.log'\n")
+    ucfg.syswrite("stdout_path 'test_stdout.#$$.log'\n")
+    pid = xfork {
+      redirect_test_io { exec($unicorn_bin, "-D", "-c", ucfg.path) }
+    }
+    _, status = Process.waitpid2(pid)
+    assert status.success?
+    wait_master_ready("test_stderr.#$$.log")
+    wait_workers_ready("test_stderr.#$$.log", 1)
+    uri = URI.parse("http://#@addr:#@port/")
+    pids = Tempfile.new('worker_pids')
+    hitter = fork {
+      bodies = Hash.new(0)
+      at_exit { pids.syswrite(bodies.inspect) }
+      trap(:TERM) { exit(0) }
+      loop {
+        rv = Net::HTTP.get(uri)
+        pid = rv.to_i
+        exit!(1) if pid <= 0
+        bodies[pid] += 1
+      }
+    }
+    sleep 5 # racy
+    daemon_pid = File.read(pid_file.path).to_i
+    assert daemon_pid > 0
+    Process.kill(:HUP, daemon_pid)
+    sleep 5 # racy
+    assert_nothing_raised { Process.kill(:TERM, hitter) }
+    _, hitter_status = Process.waitpid2(hitter)
+    assert hitter_status.success?
+    pids.sysseek(0)
+    pids = eval(pids.read)
+    assert_kind_of(Hash, pids)
+    assert_equal 2, pids.size
+    pids.keys.each { |x|
+      assert_kind_of(Integer, x)
+      assert x > 0
+      assert pids[x] > 0
+    }
+    assert_nothing_raised { Process.kill(:QUIT, daemon_pid) }
+    wait_for_death(daemon_pid)
+  end
+
+  def test_preload_app_hup
+    hup_test_common(true)
+  end
+
+  def test_hup
+    hup_test_common(false)
+  end
+
+  def test_default_listen_hup_holds_listener
+    default_listen_lock do
+      res, pid_path = default_listen_setup
+      daemon_pid = File.read(pid_path).to_i
+      assert_nothing_raised { Process.kill(:HUP, daemon_pid) }
+      wait_workers_ready("test_stderr.#$$.log", 1)
+      res2 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
+      assert_match %r{\d+}, res2.first
+      assert res2.first != res.first
+      assert_nothing_raised { Process.kill(:QUIT, daemon_pid) }
+      wait_for_death(daemon_pid)
+    end
+  end
+
+  def test_default_listen_upgrade_holds_listener
+    default_listen_lock do
+      res, pid_path = default_listen_setup
+      daemon_pid = File.read(pid_path).to_i
+      assert_nothing_raised {
+        Process.kill(:USR2, daemon_pid)
+        wait_for_file("#{pid_path}.oldbin")
+        wait_for_file(pid_path)
+        Process.kill(:QUIT, daemon_pid)
+        wait_for_death(daemon_pid)
+      }
+      daemon_pid = File.read(pid_path).to_i
+      wait_workers_ready("test_stderr.#$$.log", 1)
+      File.truncate("test_stderr.#$$.log", 0)
+
+      res2 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
+      assert_match %r{\d+}, res2.first
+      assert res2.first != res.first
+
+      assert_nothing_raised { Process.kill(:HUP, daemon_pid) }
+      wait_workers_ready("test_stderr.#$$.log", 1)
+      File.truncate("test_stderr.#$$.log", 0)
+      res3 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
+      assert res2.first != res3.first
+
+      assert_nothing_raised { Process.kill(:QUIT, daemon_pid) }
+      wait_for_death(daemon_pid)
+    end
+  end
+
+  def default_listen_setup
+    File.open("config.ru", "wb") { |fp| fp.syswrite(HI.gsub("HI", '#$$')) }
+    pid_path = (tmp = Tempfile.new('pid')).path
+    tmp.close!
+    ucfg = Tempfile.new('unicorn_test_config')
+    ucfg.syswrite("pid '#{pid_path}'\n")
+    ucfg.syswrite("stderr_path 'test_stderr.#$$.log'\n")
+    ucfg.syswrite("stdout_path 'test_stdout.#$$.log'\n")
+    pid = xfork {
+      redirect_test_io { exec($unicorn_bin, "-D", "-c", ucfg.path) }
+    }
+    _, status = Process.waitpid2(pid)
+    assert status.success?
+    wait_master_ready("test_stderr.#$$.log")
+    wait_workers_ready("test_stderr.#$$.log", 1)
+    File.truncate("test_stderr.#$$.log", 0)
+    res = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
+    assert_match %r{\d+}, res.first
+    [ res, pid_path ]
+  end
+
+  # we need to flock() something to prevent these tests from running
+  def default_listen_lock(&block)
+    fp = File.open(FLOCK_PATH, "rb")
+    begin
+      fp.flock(File::LOCK_EX)
+      begin
+        TCPServer.new(Unicorn::Const::DEFAULT_HOST,
+                      Unicorn::Const::DEFAULT_PORT).close
+      rescue Errno::EADDRINUSE, Errno::EACCES
+        warn "can't bind to #{Unicorn::Const::DEFAULT_LISTEN}"
+        return false
+      end
+
+      # unused_port should never take this, but we may run an environment
+      # where tests are being run against older unicorns...
+      lock_path = "#{Dir::tmpdir}/unicorn_test." \
+                  "#{Unicorn::Const::DEFAULT_LISTEN}.lock"
+      begin
+        lock = File.open(lock_path, File::WRONLY|File::CREAT|File::EXCL, 0600)
+        yield
+      rescue Errno::EEXIST
+        lock_path = nil
+        return false
+      ensure
+        File.unlink(lock_path) if lock_path
+      end
+    ensure
+      fp.flock(File::LOCK_UN)
+    end
+  end
+
 end if do_test
diff --git a/test/rails/app-1.2.3/app/controllers/application.rb b/test/rails/app-1.2.3/app/controllers/application.rb
index ae8cac0..e72474f 100644
--- a/test/rails/app-1.2.3/app/controllers/application.rb
+++ b/test/rails/app-1.2.3/app/controllers/application.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 class ApplicationController < ActionController::Base
   # Pick a unique cookie name to distinguish our session data from others'
   session :session_key => "_unicorn_rails_test.#{rand}"
diff --git a/test/rails/app-1.2.3/app/controllers/foo_controller.rb b/test/rails/app-1.2.3/app/controllers/foo_controller.rb
index 8d877d1..52b7947 100644
--- a/test/rails/app-1.2.3/app/controllers/foo_controller.rb
+++ b/test/rails/app-1.2.3/app/controllers/foo_controller.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 require 'digest/sha1'
 class FooController < ApplicationController
   def index
diff --git a/test/rails/app-1.2.3/app/helpers/application_helper.rb b/test/rails/app-1.2.3/app/helpers/application_helper.rb
index de6be79..d9889b3 100644
--- a/test/rails/app-1.2.3/app/helpers/application_helper.rb
+++ b/test/rails/app-1.2.3/app/helpers/application_helper.rb
@@ -1,2 +1,4 @@
+# -*- encoding: binary -*-
+
 module ApplicationHelper
 end
diff --git a/test/rails/app-1.2.3/config/boot.rb b/test/rails/app-1.2.3/config/boot.rb
index 71c7d7c..84a5c18 100644
--- a/test/rails/app-1.2.3/config/boot.rb
+++ b/test/rails/app-1.2.3/config/boot.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 unless defined?(RAILS_ROOT)
   root_path = File.join(File.dirname(__FILE__), '..')
   RAILS_ROOT = root_path
diff --git a/test/rails/app-1.2.3/config/environment.rb b/test/rails/app-1.2.3/config/environment.rb
index 2ef6b4a..e230a66 100644
--- a/test/rails/app-1.2.3/config/environment.rb
+++ b/test/rails/app-1.2.3/config/environment.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 unless defined? RAILS_GEM_VERSION
   RAILS_GEM_VERSION = ENV['UNICORN_RAILS_VERSION'] # || '1.2.3'
 end
diff --git a/test/rails/app-1.2.3/config/environments/development.rb b/test/rails/app-1.2.3/config/environments/development.rb
index 032fb46..9d78f5e 100644
--- a/test/rails/app-1.2.3/config/environments/development.rb
+++ b/test/rails/app-1.2.3/config/environments/development.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 config.cache_classes = false
 config.whiny_nils = true
 config.breakpoint_server = true
diff --git a/test/rails/app-1.2.3/config/environments/production.rb b/test/rails/app-1.2.3/config/environments/production.rb
index c4059e3..1e049b2 100644
--- a/test/rails/app-1.2.3/config/environments/production.rb
+++ b/test/rails/app-1.2.3/config/environments/production.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 config.cache_classes = true
 config.action_controller.consider_all_requests_local = false
 config.action_controller.perform_caching             = true
diff --git a/test/rails/app-1.2.3/config/routes.rb b/test/rails/app-1.2.3/config/routes.rb
index 774028f..70816dc 100644
--- a/test/rails/app-1.2.3/config/routes.rb
+++ b/test/rails/app-1.2.3/config/routes.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 ActionController::Routing::Routes.draw do |map|
   map.connect ':controller/:action/:id.:format'
   map.connect ':controller/:action/:id'
diff --git a/test/rails/app-2.0.2/app/controllers/application.rb b/test/rails/app-2.0.2/app/controllers/application.rb
index 09705d1..e7bb740 100644
--- a/test/rails/app-2.0.2/app/controllers/application.rb
+++ b/test/rails/app-2.0.2/app/controllers/application.rb
@@ -1,2 +1,4 @@
+# -*- encoding: binary -*-
+
 class ApplicationController < ActionController::Base
 end
diff --git a/test/rails/app-2.0.2/app/controllers/foo_controller.rb b/test/rails/app-2.0.2/app/controllers/foo_controller.rb
index 8d877d1..52b7947 100644
--- a/test/rails/app-2.0.2/app/controllers/foo_controller.rb
+++ b/test/rails/app-2.0.2/app/controllers/foo_controller.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 require 'digest/sha1'
 class FooController < ApplicationController
   def index
diff --git a/test/rails/app-2.0.2/app/helpers/application_helper.rb b/test/rails/app-2.0.2/app/helpers/application_helper.rb
index de6be79..d9889b3 100644
--- a/test/rails/app-2.0.2/app/helpers/application_helper.rb
+++ b/test/rails/app-2.0.2/app/helpers/application_helper.rb
@@ -1,2 +1,4 @@
+# -*- encoding: binary -*-
+
 module ApplicationHelper
 end
diff --git a/test/rails/app-2.0.2/config/boot.rb b/test/rails/app-2.0.2/config/boot.rb
index 71c7d7c..84a5c18 100644
--- a/test/rails/app-2.0.2/config/boot.rb
+++ b/test/rails/app-2.0.2/config/boot.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 unless defined?(RAILS_ROOT)
   root_path = File.join(File.dirname(__FILE__), '..')
   RAILS_ROOT = root_path
diff --git a/test/rails/app-2.0.2/config/environment.rb b/test/rails/app-2.0.2/config/environment.rb
index 7c720f6..9961f08 100644
--- a/test/rails/app-2.0.2/config/environment.rb
+++ b/test/rails/app-2.0.2/config/environment.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 unless defined? RAILS_GEM_VERSION
   RAILS_GEM_VERSION = ENV['UNICORN_RAILS_VERSION']
 end
diff --git a/test/rails/app-2.0.2/config/environments/development.rb b/test/rails/app-2.0.2/config/environments/development.rb
index 6a613c1..5e0f1ca 100644
--- a/test/rails/app-2.0.2/config/environments/development.rb
+++ b/test/rails/app-2.0.2/config/environments/development.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 config.cache_classes = false
 config.whiny_nils = true
 config.action_controller.consider_all_requests_local = true
diff --git a/test/rails/app-2.0.2/config/environments/production.rb b/test/rails/app-2.0.2/config/environments/production.rb
index c4059e3..1e049b2 100644
--- a/test/rails/app-2.0.2/config/environments/production.rb
+++ b/test/rails/app-2.0.2/config/environments/production.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 config.cache_classes = true
 config.action_controller.consider_all_requests_local = false
 config.action_controller.perform_caching             = true
diff --git a/test/rails/app-2.0.2/config/routes.rb b/test/rails/app-2.0.2/config/routes.rb
index 774028f..70816dc 100644
--- a/test/rails/app-2.0.2/config/routes.rb
+++ b/test/rails/app-2.0.2/config/routes.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 ActionController::Routing::Routes.draw do |map|
   map.connect ':controller/:action/:id.:format'
   map.connect ':controller/:action/:id'
diff --git a/test/rails/app-2.1.2/app/controllers/application.rb b/test/rails/app-2.1.2/app/controllers/application.rb
index 09705d1..e7bb740 100644
--- a/test/rails/app-2.1.2/app/controllers/application.rb
+++ b/test/rails/app-2.1.2/app/controllers/application.rb
@@ -1,2 +1,4 @@
+# -*- encoding: binary -*-
+
 class ApplicationController < ActionController::Base
 end
diff --git a/test/rails/app-2.1.2/app/controllers/foo_controller.rb b/test/rails/app-2.1.2/app/controllers/foo_controller.rb
index 8d877d1..52b7947 100644
--- a/test/rails/app-2.1.2/app/controllers/foo_controller.rb
+++ b/test/rails/app-2.1.2/app/controllers/foo_controller.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 require 'digest/sha1'
 class FooController < ApplicationController
   def index
diff --git a/test/rails/app-2.1.2/app/helpers/application_helper.rb b/test/rails/app-2.1.2/app/helpers/application_helper.rb
index de6be79..d9889b3 100644
--- a/test/rails/app-2.1.2/app/helpers/application_helper.rb
+++ b/test/rails/app-2.1.2/app/helpers/application_helper.rb
@@ -1,2 +1,4 @@
+# -*- encoding: binary -*-
+
 module ApplicationHelper
 end
diff --git a/test/rails/app-2.1.2/config/boot.rb b/test/rails/app-2.1.2/config/boot.rb
index 0a51688..e357f0a 100644
--- a/test/rails/app-2.1.2/config/boot.rb
+++ b/test/rails/app-2.1.2/config/boot.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 # Don't change this file!
 # Configure your app in config/environment.rb and config/environments/*.rb
 
diff --git a/test/rails/app-2.1.2/config/environment.rb b/test/rails/app-2.1.2/config/environment.rb
index 7c720f6..9961f08 100644
--- a/test/rails/app-2.1.2/config/environment.rb
+++ b/test/rails/app-2.1.2/config/environment.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 unless defined? RAILS_GEM_VERSION
   RAILS_GEM_VERSION = ENV['UNICORN_RAILS_VERSION']
 end
diff --git a/test/rails/app-2.1.2/config/environments/development.rb b/test/rails/app-2.1.2/config/environments/development.rb
index 7f49032..37f523f 100644
--- a/test/rails/app-2.1.2/config/environments/development.rb
+++ b/test/rails/app-2.1.2/config/environments/development.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 config.cache_classes = false
 config.whiny_nils = true
 config.action_controller.consider_all_requests_local = true
diff --git a/test/rails/app-2.1.2/config/environments/production.rb b/test/rails/app-2.1.2/config/environments/production.rb
index c4059e3..1e049b2 100644
--- a/test/rails/app-2.1.2/config/environments/production.rb
+++ b/test/rails/app-2.1.2/config/environments/production.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 config.cache_classes = true
 config.action_controller.consider_all_requests_local = false
 config.action_controller.perform_caching             = true
diff --git a/test/rails/app-2.1.2/config/routes.rb b/test/rails/app-2.1.2/config/routes.rb
index 774028f..70816dc 100644
--- a/test/rails/app-2.1.2/config/routes.rb
+++ b/test/rails/app-2.1.2/config/routes.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 ActionController::Routing::Routes.draw do |map|
   map.connect ':controller/:action/:id.:format'
   map.connect ':controller/:action/:id'
diff --git a/test/rails/app-2.2.2/app/controllers/application.rb b/test/rails/app-2.2.2/app/controllers/application.rb
index 09705d1..e7bb740 100644
--- a/test/rails/app-2.2.2/app/controllers/application.rb
+++ b/test/rails/app-2.2.2/app/controllers/application.rb
@@ -1,2 +1,4 @@
+# -*- encoding: binary -*-
+
 class ApplicationController < ActionController::Base
 end
diff --git a/test/rails/app-2.2.2/app/controllers/foo_controller.rb b/test/rails/app-2.2.2/app/controllers/foo_controller.rb
index 8d877d1..52b7947 100644
--- a/test/rails/app-2.2.2/app/controllers/foo_controller.rb
+++ b/test/rails/app-2.2.2/app/controllers/foo_controller.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 require 'digest/sha1'
 class FooController < ApplicationController
   def index
diff --git a/test/rails/app-2.2.2/app/helpers/application_helper.rb b/test/rails/app-2.2.2/app/helpers/application_helper.rb
index de6be79..d9889b3 100644
--- a/test/rails/app-2.2.2/app/helpers/application_helper.rb
+++ b/test/rails/app-2.2.2/app/helpers/application_helper.rb
@@ -1,2 +1,4 @@
+# -*- encoding: binary -*-
+
 module ApplicationHelper
 end
diff --git a/test/rails/app-2.2.2/config/boot.rb b/test/rails/app-2.2.2/config/boot.rb
index 0a51688..e357f0a 100644
--- a/test/rails/app-2.2.2/config/boot.rb
+++ b/test/rails/app-2.2.2/config/boot.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 # Don't change this file!
 # Configure your app in config/environment.rb and config/environments/*.rb
 
diff --git a/test/rails/app-2.2.2/config/environment.rb b/test/rails/app-2.2.2/config/environment.rb
index 7c720f6..9961f08 100644
--- a/test/rails/app-2.2.2/config/environment.rb
+++ b/test/rails/app-2.2.2/config/environment.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 unless defined? RAILS_GEM_VERSION
   RAILS_GEM_VERSION = ENV['UNICORN_RAILS_VERSION']
 end
diff --git a/test/rails/app-2.2.2/config/environments/development.rb b/test/rails/app-2.2.2/config/environments/development.rb
index 7f49032..37f523f 100644
--- a/test/rails/app-2.2.2/config/environments/development.rb
+++ b/test/rails/app-2.2.2/config/environments/development.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 config.cache_classes = false
 config.whiny_nils = true
 config.action_controller.consider_all_requests_local = true
diff --git a/test/rails/app-2.2.2/config/environments/production.rb b/test/rails/app-2.2.2/config/environments/production.rb
index c4059e3..1e049b2 100644
--- a/test/rails/app-2.2.2/config/environments/production.rb
+++ b/test/rails/app-2.2.2/config/environments/production.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 config.cache_classes = true
 config.action_controller.consider_all_requests_local = false
 config.action_controller.perform_caching             = true
diff --git a/test/rails/app-2.2.2/config/routes.rb b/test/rails/app-2.2.2/config/routes.rb
index 774028f..70816dc 100644
--- a/test/rails/app-2.2.2/config/routes.rb
+++ b/test/rails/app-2.2.2/config/routes.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 ActionController::Routing::Routes.draw do |map|
   map.connect ':controller/:action/:id.:format'
   map.connect ':controller/:action/:id'
diff --git a/test/rails/app-2.3.2.1/.gitignore b/test/rails/app-2.3.5/.gitignore
index f451f91..f451f91 100644
--- a/test/rails/app-2.3.2.1/.gitignore
+++ b/test/rails/app-2.3.5/.gitignore
diff --git a/test/rails/app-2.3.2.1/Rakefile b/test/rails/app-2.3.5/Rakefile
index fbebfca..fbebfca 100644
--- a/test/rails/app-2.3.2.1/Rakefile
+++ b/test/rails/app-2.3.5/Rakefile
diff --git a/test/rails/app-2.3.2.1/app/controllers/application_controller.rb b/test/rails/app-2.3.5/app/controllers/application_controller.rb
index 6160f52..07c333e 100644
--- a/test/rails/app-2.3.2.1/app/controllers/application_controller.rb
+++ b/test/rails/app-2.3.5/app/controllers/application_controller.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 class ApplicationController < ActionController::Base
   helper :all
 end
diff --git a/test/rails/app-2.3.2.1/app/controllers/foo_controller.rb b/test/rails/app-2.3.5/app/controllers/foo_controller.rb
index 261669c..54ca1ed 100644
--- a/test/rails/app-2.3.2.1/app/controllers/foo_controller.rb
+++ b/test/rails/app-2.3.5/app/controllers/foo_controller.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 require 'digest/sha1'
 class FooController < ApplicationController
   def index
diff --git a/test/rails/app-2.3.2.1/app/helpers/application_helper.rb b/test/rails/app-2.3.5/app/helpers/application_helper.rb
index de6be79..d9889b3 100644
--- a/test/rails/app-2.3.2.1/app/helpers/application_helper.rb
+++ b/test/rails/app-2.3.5/app/helpers/application_helper.rb
@@ -1,2 +1,4 @@
+# -*- encoding: binary -*-
+
 module ApplicationHelper
 end
diff --git a/test/rails/app-2.3.2.1/config/boot.rb b/test/rails/app-2.3.5/config/boot.rb
index d22e6b0..b6c80d5 100644
--- a/test/rails/app-2.3.2.1/config/boot.rb
+++ b/test/rails/app-2.3.5/config/boot.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
 
 module Rails
diff --git a/test/rails/app-2.3.2.1/config/database.yml b/test/rails/app-2.3.5/config/database.yml
index 9f77843..9f77843 100644
--- a/test/rails/app-2.3.2.1/config/database.yml
+++ b/test/rails/app-2.3.5/config/database.yml
diff --git a/test/rails/app-2.3.2.1/config/environment.rb b/test/rails/app-2.3.5/config/environment.rb
index 17abdb7..6eb092c 100644
--- a/test/rails/app-2.3.2.1/config/environment.rb
+++ b/test/rails/app-2.3.5/config/environment.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 unless defined? RAILS_GEM_VERSION
   RAILS_GEM_VERSION = ENV['UNICORN_RAILS_VERSION']
 end
diff --git a/test/rails/app-2.3.2.1/config/environments/development.rb b/test/rails/app-2.3.5/config/environments/development.rb
index 55376c5..3d381d2 100644
--- a/test/rails/app-2.3.2.1/config/environments/development.rb
+++ b/test/rails/app-2.3.5/config/environments/development.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 config.cache_classes = false
 config.whiny_nils = true
 config.action_controller.consider_all_requests_local = true
diff --git a/test/rails/app-2.3.2.1/config/environments/production.rb b/test/rails/app-2.3.5/config/environments/production.rb
index 474257d..08710a4 100644
--- a/test/rails/app-2.3.2.1/config/environments/production.rb
+++ b/test/rails/app-2.3.5/config/environments/production.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 config.cache_classes = true
 config.action_controller.consider_all_requests_local = false
 config.action_controller.perform_caching             = true
diff --git a/test/rails/app-2.3.2.1/config/routes.rb b/test/rails/app-2.3.5/config/routes.rb
index 4248853..ac7877c 100644
--- a/test/rails/app-2.3.2.1/config/routes.rb
+++ b/test/rails/app-2.3.5/config/routes.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 ActionController::Routing::Routes.draw do |map|
   map.connect ':controller/:action/:id'
   map.connect ':controller/:action/:id.:format'
diff --git a/test/rails/app-2.3.2.1/db/.gitignore b/test/rails/app-2.3.5/db/.gitignore
index e69de29..e69de29 100644
--- a/test/rails/app-2.3.2.1/db/.gitignore
+++ b/test/rails/app-2.3.5/db/.gitignore
diff --git a/test/rails/app-2.3.2.1/log/.gitignore b/test/rails/app-2.3.5/log/.gitignore
index 397b4a7..397b4a7 100644
--- a/test/rails/app-2.3.2.1/log/.gitignore
+++ b/test/rails/app-2.3.5/log/.gitignore
diff --git a/test/rails/app-2.3.2.1/public/404.html b/test/rails/app-2.3.5/public/404.html
index 44d986c..44d986c 100644
--- a/test/rails/app-2.3.2.1/public/404.html
+++ b/test/rails/app-2.3.5/public/404.html
diff --git a/test/rails/app-2.3.2.1/public/500.html b/test/rails/app-2.3.5/public/500.html
index e534a49..e534a49 100644
--- a/test/rails/app-2.3.2.1/public/500.html
+++ b/test/rails/app-2.3.5/public/500.html
diff --git a/test/rails/app-2.3.5/public/x.txt b/test/rails/app-2.3.5/public/x.txt
new file mode 100644
index 0000000..e427984
--- /dev/null
+++ b/test/rails/app-2.3.5/public/x.txt
@@ -0,0 +1 @@
+HELLO
diff --git a/test/rails/test_rails.rb b/test/rails/test_rails.rb
index c7add20..9502dcb 100644
--- a/test/rails/test_rails.rb
+++ b/test/rails/test_rails.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 # Copyright (c) 2009 Eric Wong
 require 'test/test_helper'
 
@@ -142,18 +144,24 @@ logger Logger.new('#{COMMON_TMP.path}')
         end
       end
     end
-    resp = `curl -isSfN -Ffile=@#{tmp.path} http://#@addr:#@port/foo/xpost`
-    assert $?.success?
-    resp = resp.split(/\r?\n/)
-    grepped = resp.grep(/^sha1: (.{40})/)
-    assert_equal 1, grepped.size
-    assert_equal(sha1.hexdigest, /^sha1: (.{40})/.match(grepped.first)[1])
-
-    grepped = resp.grep(/^Content-Type:\s+(.+)/i)
-    assert_equal 1, grepped.size
-    assert_match %r{^text/plain}, grepped.first.split(/\s*:\s*/)[1]
 
-    assert_equal 1, resp.grep(/^Status:/i).size
+    # fixed in Rack commit 44ed4640f077504a49b7f1cabf8d6ad7a13f6441,
+    # no released version of Rails or Rack has this fix
+    if RB_V[0] >= 1 && RB_V[1] >= 9
+      warn "multipart broken with Rack 1.0.0 and Rails 2.3.2.1 under 1.9"
+    else
+      resp = `curl -isSfN -Ffile=@#{tmp.path} http://#@addr:#@port/foo/xpost`
+      assert $?.success?
+      resp = resp.split(/\r?\n/)
+      grepped = resp.grep(/^sha1: (.{40})/)
+      assert_equal 1, grepped.size
+      assert_equal(sha1.hexdigest, /^sha1: (.{40})/.match(grepped.first)[1])
+
+      grepped = resp.grep(/^Content-Type:\s+(.+)/i)
+      assert_equal 1, grepped.size
+      assert_match %r{^text/plain}, grepped.first.split(/\s*:\s*/)[1]
+      assert_equal 1, resp.grep(/^Status:/i).size
+    end
 
     # make sure we can get 403 responses, too
     uri = URI.parse("http://#@addr:#@port/foo/xpost")
@@ -223,6 +231,31 @@ logger Logger.new('#{COMMON_TMP.path}')
     assert_equal '404 Not Found', res['Status']
   end
 
+  def test_alt_url_root_config_env
+    # cbf to actually work on this since I never use this feature (ewong)
+    return unless ROR_V[0] >= 2 && ROR_V[1] >= 3
+    tmp = Tempfile.new(nil)
+    tmp.syswrite("ENV['RAILS_RELATIVE_URL_ROOT'] = '/poo'\n")
+    redirect_test_io do
+      @pid = fork { exec 'unicorn_rails', "-l#@addr:#@port", "-c", tmp.path }
+    end
+    wait_master_ready("test_stderr.#$$.log")
+    res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/poo/foo"))
+    assert_equal "200", res.code
+    assert_equal '200 OK', res['Status']
+    assert_equal "FOO\n", res.body
+    assert_match %r{^text/html\b}, res['Content-Type']
+    assert_equal "4", res['Content-Length']
+
+    res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/foo"))
+    assert_equal "404", res.code
+    assert_equal '404 Not Found', res['Status']
+
+    res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/poo/x.txt"))
+    assert_equal "200", res.code
+    assert_equal "HELLO\n", res.body
+  end
+
   def teardown
     return if @start_pid != $$
 
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 787adbf..3bdbeb1 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 # Copyright (c) 2005 Zed A. Shaw
 # You can redistribute it and/or modify it under the same terms as Ruby.
 #
@@ -27,7 +29,7 @@ require 'tempfile'
 require 'fileutils'
 require 'logger'
 require 'unicorn'
-require 'unicorn/http11'
+require 'unicorn_http'
 
 if ENV['DEBUG']
   require 'ruby-debug'
@@ -102,6 +104,10 @@ def unused_port(addr = '127.0.0.1')
   begin
     begin
       port = base + rand(32768 - base)
+      while port == Unicorn::Const::DEFAULT_PORT
+        port = base + rand(32768 - base)
+      end
+
       sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
       sock.bind(Socket.pack_sockaddr_in(port, addr))
       sock.listen(5)
@@ -139,7 +145,7 @@ def retry_hit(uris = [])
   tries = DEFAULT_TRIES
   begin
     hit(uris)
-  rescue Errno::ECONNREFUSED => err
+  rescue Errno::EINVAL, Errno::ECONNREFUSED => err
     if (tries -= 1) > 0
       sleep DEFAULT_RES
       retry
@@ -262,3 +268,29 @@ def wait_for_death(pid)
   end
   raise "PID:#{pid} never died!"
 end
+
+# executes +cmd+ and chunks its STDOUT
+def chunked_spawn(stdout, *cmd)
+  fork {
+    crd, cwr = IO.pipe
+    crd.binmode
+    cwr.binmode
+    crd.sync = cwr.sync = true
+
+    pid = fork {
+      STDOUT.reopen(cwr)
+      crd.close
+      cwr.close
+      exec(*cmd)
+    }
+    cwr.close
+    begin
+      buf = crd.readpartial(16384)
+      stdout.write("#{'%x' % buf.size}\r\n#{buf}")
+    rescue EOFError
+      stdout.write("0\r\n")
+      pid, status = Process.waitpid(pid)
+      exit status.exitstatus
+    end while true
+  }
+end
diff --git a/test/unit/test_configurator.rb b/test/unit/test_configurator.rb
index 98f2db6..ac1efa8 100644
--- a/test/unit/test_configurator.rb
+++ b/test/unit/test_configurator.rb
@@ -1,7 +1,11 @@
+# -*- encoding: binary -*-
+
 require 'test/unit'
 require 'tempfile'
-require 'unicorn/configurator'
+require 'unicorn'
 
+TestStruct = Struct.new(
+  *(Unicorn::Configurator::DEFAULTS.keys + %w(listener_opts listeners)))
 class TestConfigurator < Test::Unit::TestCase
 
   def test_config_init
@@ -28,8 +32,10 @@ class TestConfigurator < Test::Unit::TestCase
     assert_equal "0.0.0.0:2007", meth.call('*:2007')
     assert_equal "0.0.0.0:2007", meth.call('2007')
     assert_equal "0.0.0.0:2007", meth.call(2007)
-    assert_match %r{\A\d+\.\d+\.\d+\.\d+:2007\z}, meth.call('1:2007')
-    assert_match %r{\A\d+\.\d+\.\d+\.\d+:2007\z}, meth.call('2:2007')
+
+    # the next two aren't portable, consider them unsupported for now
+    # assert_match %r{\A\d+\.\d+\.\d+\.\d+:2007\z}, meth.call('1:2007')
+    # assert_match %r{\A\d+\.\d+\.\d+\.\d+:2007\z}, meth.call('2:2007')
   end
 
   def test_config_invalid
@@ -51,22 +57,23 @@ class TestConfigurator < Test::Unit::TestCase
 
   def test_config_defaults
     cfg = Unicorn::Configurator.new(:use_defaults => true)
-    assert_nothing_raised { cfg.commit!(self) }
+    test_struct = TestStruct.new
+    assert_nothing_raised { cfg.commit!(test_struct) }
     Unicorn::Configurator::DEFAULTS.each do |key,value|
-      assert_equal value, instance_variable_get("@#{key.to_s}")
+      assert_equal value, test_struct.__send__(key)
     end
   end
 
   def test_config_defaults_skip
     cfg = Unicorn::Configurator.new(:use_defaults => true)
     skip = [ :logger ]
-    assert_nothing_raised { cfg.commit!(self, :skip => skip) }
-    @logger = nil
+    test_struct = TestStruct.new
+    assert_nothing_raised { cfg.commit!(test_struct, :skip => skip) }
     Unicorn::Configurator::DEFAULTS.each do |key,value|
       next if skip.include?(key)
-      assert_equal value, instance_variable_get("@#{key.to_s}")
+      assert_equal value, test_struct.__send__(key)
     end
-    assert_nil @logger
+    assert_nil test_struct.logger
   end
 
   def test_listen_options
@@ -78,8 +85,9 @@ class TestConfigurator < Test::Unit::TestCase
     assert_nothing_raised do
       cfg = Unicorn::Configurator.new(:config_file => tmp.path)
     end
-    assert_nothing_raised { cfg.commit!(self) }
-    assert(listener_opts = instance_variable_get("@listener_opts"))
+    test_struct = TestStruct.new
+    assert_nothing_raised { cfg.commit!(test_struct) }
+    assert(listener_opts = test_struct.listener_opts)
     assert_equal expect, listener_opts[listener]
   end
 
@@ -93,10 +101,41 @@ class TestConfigurator < Test::Unit::TestCase
     end
   end
 
+  def test_listen_option_bad_delay
+    tmp = Tempfile.new('unicorn_config')
+    expect = { :delay => "five" }
+    listener = "127.0.0.1:12345"
+    tmp.syswrite("listen '#{listener}', #{expect.inspect}\n")
+    assert_raises(ArgumentError) do
+      Unicorn::Configurator.new(:config_file => tmp.path)
+    end
+  end
+
+  def test_listen_option_float_delay
+    tmp = Tempfile.new('unicorn_config')
+    expect = { :delay => 0.5 }
+    listener = "127.0.0.1:12345"
+    tmp.syswrite("listen '#{listener}', #{expect.inspect}\n")
+    assert_nothing_raised do
+      Unicorn::Configurator.new(:config_file => tmp.path)
+    end
+  end
+
+  def test_listen_option_int_delay
+    tmp = Tempfile.new('unicorn_config')
+    expect = { :delay => 5 }
+    listener = "127.0.0.1:12345"
+    tmp.syswrite("listen '#{listener}', #{expect.inspect}\n")
+    assert_nothing_raised do
+      Unicorn::Configurator.new(:config_file => tmp.path)
+    end
+  end
+
   def test_after_fork_proc
+    test_struct = TestStruct.new
     [ proc { |a,b| }, Proc.new { |a,b| }, lambda { |a,b| } ].each do |my_proc|
-      Unicorn::Configurator.new(:after_fork => my_proc).commit!(self)
-      assert_equal my_proc, @after_fork
+      Unicorn::Configurator.new(:after_fork => my_proc).commit!(test_struct)
+      assert_equal my_proc, test_struct.after_fork
     end
   end
 
diff --git a/test/unit/test_http_parser.rb b/test/unit/test_http_parser.rb
index a158ebb..0443b46 100644
--- a/test/unit/test_http_parser.rb
+++ b/test/unit/test_http_parser.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 # Copyright (c) 2005 Zed A. Shaw
 # You can redistribute it and/or modify it under the same terms as Ruby.
 #
@@ -9,12 +11,13 @@ require 'test/test_helper'
 include Unicorn
 
 class HttpParserTest < Test::Unit::TestCase
-    
+
   def test_parse_simple
     parser = HttpParser.new
     req = {}
     http = "GET / HTTP/1.1\r\n\r\n"
-    assert parser.execute(req, http)
+    assert_equal req, parser.headers(req, http)
+    assert_equal '', http
 
     assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
     assert_equal '/', req['REQUEST_PATH']
@@ -24,15 +27,18 @@ class HttpParserTest < Test::Unit::TestCase
     assert_nil req['FRAGMENT']
     assert_equal '', req['QUERY_STRING']
 
+    assert parser.keepalive?
     parser.reset
     req.clear
 
-    assert ! parser.execute(req, "G")
+    http = "G"
+    assert_nil parser.headers(req, http)
+    assert_equal "G", http
     assert req.empty?
 
     # try parsing again to ensure we were reset correctly
     http = "GET /hello-world HTTP/1.1\r\n\r\n"
-    assert parser.execute(req, http)
+    assert parser.headers(req, http)
 
     assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
     assert_equal '/hello-world', req['REQUEST_PATH']
@@ -41,55 +47,184 @@ class HttpParserTest < Test::Unit::TestCase
     assert_equal 'GET', req['REQUEST_METHOD']
     assert_nil req['FRAGMENT']
     assert_equal '', req['QUERY_STRING']
+    assert_equal '', http
+    assert parser.keepalive?
+  end
+
+  def test_connection_close_no_ka
+    parser = HttpParser.new
+    req = {}
+    tmp = "GET / HTTP/1.1\r\nConnection: close\r\n\r\n"
+    assert_equal req.object_id, parser.headers(req, tmp).object_id
+    assert_equal "GET", req['REQUEST_METHOD']
+    assert ! parser.keepalive?
+  end
+
+  def test_connection_keep_alive_ka
+    parser = HttpParser.new
+    req = {}
+    tmp = "HEAD / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n"
+    assert_equal req.object_id, parser.headers(req, tmp).object_id
+    assert parser.keepalive?
+  end
+
+  def test_connection_keep_alive_ka_bad_method
+    parser = HttpParser.new
+    req = {}
+    tmp = "POST / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n"
+    assert_equal req.object_id, parser.headers(req, tmp).object_id
+    assert ! parser.keepalive?
+  end
+
+  def test_connection_keep_alive_ka_bad_version
+    parser = HttpParser.new
+    req = {}
+    tmp = "GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n"
+    assert_equal req.object_id, parser.headers(req, tmp).object_id
+    assert parser.keepalive?
   end
 
   def test_parse_server_host_default_port
     parser = HttpParser.new
     req = {}
-    assert parser.execute(req, "GET / HTTP/1.1\r\nHost: foo\r\n\r\n")
+    tmp = "GET / HTTP/1.1\r\nHost: foo\r\n\r\n"
+    assert_equal req, parser.headers(req, tmp)
     assert_equal 'foo', req['SERVER_NAME']
     assert_equal '80', req['SERVER_PORT']
+    assert_equal '', tmp
+    assert parser.keepalive?
   end
 
   def test_parse_server_host_alt_port
     parser = HttpParser.new
     req = {}
-    assert parser.execute(req, "GET / HTTP/1.1\r\nHost: foo:999\r\n\r\n")
+    tmp = "GET / HTTP/1.1\r\nHost: foo:999\r\n\r\n"
+    assert_equal req, parser.headers(req, tmp)
     assert_equal 'foo', req['SERVER_NAME']
     assert_equal '999', req['SERVER_PORT']
+    assert_equal '', tmp
+    assert parser.keepalive?
   end
 
   def test_parse_server_host_empty_port
     parser = HttpParser.new
     req = {}
-    assert parser.execute(req, "GET / HTTP/1.1\r\nHost: foo:\r\n\r\n")
+    tmp = "GET / HTTP/1.1\r\nHost: foo:\r\n\r\n"
+    assert_equal req, parser.headers(req, tmp)
     assert_equal 'foo', req['SERVER_NAME']
     assert_equal '80', req['SERVER_PORT']
+    assert_equal '', tmp
+    assert parser.keepalive?
   end
 
   def test_parse_server_host_xfp_https
     parser = HttpParser.new
     req = {}
-    assert parser.execute(req, "GET / HTTP/1.1\r\nHost: foo:\r\n" \
-                          "X-Forwarded-Proto: https\r\n\r\n")
+    tmp = "GET / HTTP/1.1\r\nHost: foo:\r\n" \
+          "X-Forwarded-Proto: https\r\n\r\n"
+    assert_equal req, parser.headers(req, tmp)
     assert_equal 'foo', req['SERVER_NAME']
     assert_equal '443', req['SERVER_PORT']
+    assert_equal '', tmp
+    assert parser.keepalive?
   end
 
   def test_parse_strange_headers
     parser = HttpParser.new
     req = {}
     should_be_good = "GET / HTTP/1.1\r\naaaaaaaaaaaaa:++++++++++\r\n\r\n"
-    assert parser.execute(req, should_be_good)
+    assert_equal req, parser.headers(req, should_be_good)
+    assert_equal '', should_be_good
+    assert parser.keepalive?
+  end
+
+  # legacy test case from Mongrel that we never supported before...
+  # I still consider Pound irrelevant, unfortunately stupid clients that
+  # send extremely big headers do exist and they've managed to find Unicorn...
+  def test_nasty_pound_header
+    parser = HttpParser.new
+    nasty_pound_header = "GET / HTTP/1.1\r\nX-SSL-Bullshit:   -----BEGIN CERTIFICATE-----\r\n\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgEBBAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n\tRA==\r\n\t-----END CERTIFICATE-----\r\n\r\n"
+    req = {}
+    buf = nasty_pound_header.dup
+
+    assert nasty_pound_header =~ /(-----BEGIN .*--END CERTIFICATE-----)/m
+    expect = $1.dup
+    expect.gsub!(/\r\n\t/, ' ')
+    assert_equal req, parser.headers(req, buf)
+    assert_equal '', buf
+    assert_equal expect, req['HTTP_X_SSL_BULLSHIT']
+  end
+
+  def test_continuation_eats_leading_spaces
+    parser = HttpParser.new
+    header = "GET / HTTP/1.1\r\n" \
+             "X-ASDF:      \r\n" \
+             "\t\r\n" \
+             "    \r\n" \
+             "  ASDF\r\n\r\n"
+    req = {}
+    assert_equal req, parser.headers(req, header)
+    assert_equal '', header
+    assert_equal 'ASDF', req['HTTP_X_ASDF']
+  end
+
+  def test_continuation_eats_scattered_leading_spaces
+    parser = HttpParser.new
+    header = "GET / HTTP/1.1\r\n" \
+             "X-ASDF:   hi\r\n" \
+             "    y\r\n" \
+             "\t\r\n" \
+             "       x\r\n" \
+             "  ASDF\r\n\r\n"
+    req = {}
+    assert_equal req, parser.headers(req, header)
+    assert_equal '', header
+    assert_equal 'hi y x ASDF', req['HTTP_X_ASDF']
+  end
+
+  def test_continuation_with_absolute_uri_and_ignored_host_header
+    parser = HttpParser.new
+    header = "GET http://example.com/ HTTP/1.1\r\n" \
+             "Host: \r\n" \
+             "    YHBT.net\r\n" \
+             "\r\n"
+    req = {}
+    assert_equal req, parser.headers(req, header)
+    assert_equal 'example.com', req['HTTP_HOST']
+  end
 
-    # ref: http://thread.gmane.org/gmane.comp.lang.ruby.mongrel.devel/37/focus=45
-    # (note we got 'pen' mixed up with 'pound' in that thread,
-    # but the gist of it is still relevant: these nasty headers are irrelevant
-    #
-    # nasty_pound_header = "GET / HTTP/1.1\r\nX-SSL-Bullshit:   -----BEGIN CERTIFICATE-----\r\n\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgEBBAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n\tRA==\r\n\t-----END CERTIFICATE-----\r\n\r\n"
-    # parser = HttpParser.new
-    # req = {}
-    # assert parser.execute(req, nasty_pound_header, 0)
+  # this may seem to be testing more of an implementation detail, but
+  # it also helps ensure we're safe in the presence of multiple parsers
+  # in case we ever go multithreaded/evented...
+  def test_resumable_continuations
+    nr = 1000
+    req = {}
+    header = "GET / HTTP/1.1\r\n" \
+             "X-ASDF:      \r\n" \
+             "  hello\r\n"
+    tmp = []
+    nr.times { |i|
+      parser = HttpParser.new
+      assert parser.headers(req, "#{header} #{i}\r\n").nil?
+      asdf = req['HTTP_X_ASDF']
+      assert_equal "hello #{i}", asdf
+      tmp << [ parser, asdf ]
+      req.clear
+    }
+    tmp.each_with_index { |(parser, asdf), i|
+      assert_equal req, parser.headers(req, "#{header} #{i}\r\n .\r\n\r\n")
+      assert_equal "hello #{i} .", asdf
+    }
+  end
+
+  def test_invalid_continuation
+    parser = HttpParser.new
+    header = "GET / HTTP/1.1\r\n" \
+             "    y\r\n" \
+             "Host: hello\r\n" \
+             "\r\n"
+    req = {}
+    assert_raises(HttpParserError) { parser.headers(req, header) }
   end
 
   def test_parse_ie6_urls
@@ -103,7 +238,10 @@ class HttpParserTest < Test::Unit::TestCase
       parser = HttpParser.new
       req = {}
       sorta_safe = %(GET #{path} HTTP/1.1\r\n\r\n)
-      assert parser.execute(req, sorta_safe)
+      assert_equal req, parser.headers(req, sorta_safe)
+      assert_equal path, req['REQUEST_URI']
+      assert_equal '', sorta_safe
+      assert parser.keepalive?
     end
   end
   
@@ -112,28 +250,34 @@ class HttpParserTest < Test::Unit::TestCase
     req = {}
     bad_http = "GET / SsUTF/1.1"
 
-    assert_raises(HttpParserError) { parser.execute(req, bad_http) }
+    assert_raises(HttpParserError) { parser.headers(req, bad_http) }
+
+    # make sure we can recover
     parser.reset
-    assert(parser.execute({}, "GET / HTTP/1.0\r\n\r\n"))
+    req.clear
+    assert_equal req, parser.headers(req, "GET / HTTP/1.0\r\n\r\n")
+    assert ! parser.keepalive?
   end
 
   def test_piecemeal
     parser = HttpParser.new
     req = {}
     http = "GET"
-    assert ! parser.execute(req, http)
-    assert_raises(HttpParserError) { parser.execute(req, http) }
-    assert ! parser.execute(req, http << " / HTTP/1.0")
+    assert_nil parser.headers(req, http)
+    assert_nil parser.headers(req, http)
+    assert_nil parser.headers(req, http << " / HTTP/1.0")
     assert_equal '/', req['REQUEST_PATH']
     assert_equal '/', req['REQUEST_URI']
     assert_equal 'GET', req['REQUEST_METHOD']
-    assert ! parser.execute(req, http << "\r\n")
+    assert_nil parser.headers(req, http << "\r\n")
     assert_equal 'HTTP/1.0', req['HTTP_VERSION']
-    assert ! parser.execute(req, http << "\r")
-    assert parser.execute(req, http << "\n")
-    assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
+    assert_nil parser.headers(req, http << "\r")
+    assert_equal req, parser.headers(req, http << "\n")
+    assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
     assert_nil req['FRAGMENT']
     assert_equal '', req['QUERY_STRING']
+    assert_equal "", http
+    assert ! parser.keepalive?
   end
 
   # not common, but underscores do appear in practice
@@ -141,7 +285,7 @@ class HttpParserTest < Test::Unit::TestCase
     parser = HttpParser.new
     req = {}
     http = "GET http://under_score.example.com/foo?q=bar HTTP/1.0\r\n\r\n"
-    assert parser.execute(req, http)
+    assert_equal req, parser.headers(req, http)
     assert_equal 'http', req['rack.url_scheme']
     assert_equal '/foo?q=bar', req['REQUEST_URI']
     assert_equal '/foo', req['REQUEST_PATH']
@@ -150,13 +294,54 @@ class HttpParserTest < Test::Unit::TestCase
     assert_equal 'under_score.example.com', req['HTTP_HOST']
     assert_equal 'under_score.example.com', req['SERVER_NAME']
     assert_equal '80', req['SERVER_PORT']
+    assert_equal "", http
+    assert ! parser.keepalive?
+  end
+
+  # some dumb clients add users because they're stupid
+  def test_absolute_uri_w_user
+    parser = HttpParser.new
+    req = {}
+    http = "GET http://user%20space@example.com/foo?q=bar HTTP/1.0\r\n\r\n"
+    assert_equal req, parser.headers(req, http)
+    assert_equal 'http', req['rack.url_scheme']
+    assert_equal '/foo?q=bar', req['REQUEST_URI']
+    assert_equal '/foo', req['REQUEST_PATH']
+    assert_equal 'q=bar', req['QUERY_STRING']
+
+    assert_equal 'example.com', req['HTTP_HOST']
+    assert_equal 'example.com', req['SERVER_NAME']
+    assert_equal '80', req['SERVER_PORT']
+    assert_equal "", http
+    assert ! parser.keepalive?
+  end
+
+  # since Mongrel supported anything URI.parse supported, we're stuck
+  # supporting everything URI.parse supports
+  def test_absolute_uri_uri_parse
+    "#{URI::REGEXP::PATTERN::UNRESERVED};:&=+$,".split(//).each do |char|
+      parser = HttpParser.new
+      req = {}
+      http = "GET http://#{char}@example.com/ HTTP/1.0\r\n\r\n"
+      assert_equal req, parser.headers(req, http)
+      assert_equal 'http', req['rack.url_scheme']
+      assert_equal '/', req['REQUEST_URI']
+      assert_equal '/', req['REQUEST_PATH']
+      assert_equal '', req['QUERY_STRING']
+
+      assert_equal 'example.com', req['HTTP_HOST']
+      assert_equal 'example.com', req['SERVER_NAME']
+      assert_equal '80', req['SERVER_PORT']
+      assert_equal "", http
+      assert ! parser.keepalive?
+    end
   end
 
   def test_absolute_uri
     parser = HttpParser.new
     req = {}
     http = "GET http://example.com/foo?q=bar HTTP/1.0\r\n\r\n"
-    assert parser.execute(req, http)
+    assert_equal req, parser.headers(req, http)
     assert_equal 'http', req['rack.url_scheme']
     assert_equal '/foo?q=bar', req['REQUEST_URI']
     assert_equal '/foo', req['REQUEST_PATH']
@@ -165,6 +350,8 @@ class HttpParserTest < Test::Unit::TestCase
     assert_equal 'example.com', req['HTTP_HOST']
     assert_equal 'example.com', req['SERVER_NAME']
     assert_equal '80', req['SERVER_PORT']
+    assert_equal "", http
+    assert ! parser.keepalive?
   end
 
   # X-Forwarded-Proto is not in rfc2616, absolute URIs are, however...
@@ -173,7 +360,7 @@ class HttpParserTest < Test::Unit::TestCase
     req = {}
     http = "GET https://example.com/foo?q=bar HTTP/1.1\r\n" \
            "X-Forwarded-Proto: http\r\n\r\n"
-    assert parser.execute(req, http)
+    assert_equal req, parser.headers(req, http)
     assert_equal 'https', req['rack.url_scheme']
     assert_equal '/foo?q=bar', req['REQUEST_URI']
     assert_equal '/foo', req['REQUEST_PATH']
@@ -182,6 +369,8 @@ class HttpParserTest < Test::Unit::TestCase
     assert_equal 'example.com', req['HTTP_HOST']
     assert_equal 'example.com', req['SERVER_NAME']
     assert_equal '443', req['SERVER_PORT']
+    assert_equal "", http
+    assert parser.keepalive?
   end
 
   # Host: header should be ignored for absolute URIs
@@ -190,7 +379,7 @@ class HttpParserTest < Test::Unit::TestCase
     req = {}
     http = "GET http://example.com:8080/foo?q=bar HTTP/1.2\r\n" \
            "Host: bad.example.com\r\n\r\n"
-    assert parser.execute(req, http)
+    assert_equal req, parser.headers(req, http)
     assert_equal 'http', req['rack.url_scheme']
     assert_equal '/foo?q=bar', req['REQUEST_URI']
     assert_equal '/foo', req['REQUEST_PATH']
@@ -199,6 +388,8 @@ class HttpParserTest < Test::Unit::TestCase
     assert_equal 'example.com:8080', req['HTTP_HOST']
     assert_equal 'example.com', req['SERVER_NAME']
     assert_equal '8080', req['SERVER_PORT']
+    assert_equal "", http
+    assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
   end
 
   def test_absolute_uri_with_empty_port
@@ -206,7 +397,7 @@ class HttpParserTest < Test::Unit::TestCase
     req = {}
     http = "GET https://example.com:/foo?q=bar HTTP/1.1\r\n" \
            "Host: bad.example.com\r\n\r\n"
-    assert parser.execute(req, http)
+    assert_equal req, parser.headers(req, http)
     assert_equal 'https', req['rack.url_scheme']
     assert_equal '/foo?q=bar', req['REQUEST_URI']
     assert_equal '/foo', req['REQUEST_PATH']
@@ -215,32 +406,55 @@ class HttpParserTest < Test::Unit::TestCase
     assert_equal 'example.com:', req['HTTP_HOST']
     assert_equal 'example.com', req['SERVER_NAME']
     assert_equal '443', req['SERVER_PORT']
+    assert_equal "", http
+    assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
   end
 
   def test_put_body_oneshot
     parser = HttpParser.new
     req = {}
     http = "PUT / HTTP/1.0\r\nContent-Length: 5\r\n\r\nabcde"
-    assert parser.execute(req, http)
+    assert_equal req, parser.headers(req, http)
     assert_equal '/', req['REQUEST_PATH']
     assert_equal '/', req['REQUEST_URI']
     assert_equal 'PUT', req['REQUEST_METHOD']
     assert_equal 'HTTP/1.0', req['HTTP_VERSION']
-    assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
-    assert_equal "abcde", req[:http_body]
+    assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
+    assert_equal "abcde", http
+    assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
   end
 
   def test_put_body_later
     parser = HttpParser.new
     req = {}
     http = "PUT /l HTTP/1.0\r\nContent-Length: 5\r\n\r\n"
-    assert parser.execute(req, http)
+    assert_equal req, parser.headers(req, http)
     assert_equal '/l', req['REQUEST_PATH']
     assert_equal '/l', req['REQUEST_URI']
     assert_equal 'PUT', req['REQUEST_METHOD']
     assert_equal 'HTTP/1.0', req['HTTP_VERSION']
-    assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
-    assert_equal "", req[:http_body]
+    assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
+    assert_equal "", http
+    assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
+  end
+
+  def test_unknown_methods
+    %w(GETT HEADR XGET XHEAD).each { |m|
+      parser = HttpParser.new
+      req = {}
+      s = "#{m} /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
+      ok = false
+      assert_nothing_raised do
+        ok = parser.headers(req, s)
+      end
+      assert ok
+      assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
+      assert_equal 'posts-17408', req['FRAGMENT']
+      assert_equal 'page=1', req['QUERY_STRING']
+      assert_equal "", s
+      assert_equal m, req['REQUEST_METHOD']
+      assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
+    }
   end
 
   def test_fragment_in_uri
@@ -249,12 +463,14 @@ class HttpParserTest < Test::Unit::TestCase
     get = "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
     ok = false
     assert_nothing_raised do
-      ok = parser.execute(req, get)
+      ok = parser.headers(req, get)
     end
     assert ok
     assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
     assert_equal 'posts-17408', req['FRAGMENT']
     assert_equal 'page=1', req['QUERY_STRING']
+    assert_equal '', get
+    assert parser.keepalive?
   end
 
   # lame random garbage maker
@@ -279,7 +495,7 @@ class HttpParserTest < Test::Unit::TestCase
     10.times do |c|
       get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-#{rand_data(1024, 1024+(c*1024))}: Test\r\n\r\n"
       assert_raises Unicorn::HttpParserError do
-        parser.execute({}, get)
+        parser.headers({}, get)
         parser.reset
       end
     end
@@ -288,7 +504,7 @@ class HttpParserTest < Test::Unit::TestCase
     10.times do |c|
       get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
       assert_raises Unicorn::HttpParserError do
-        parser.execute({}, get)
+        parser.headers({}, get)
         parser.reset
       end
     end
@@ -297,7 +513,7 @@ class HttpParserTest < Test::Unit::TestCase
     get = "GET /#{rand_data(10,120)} HTTP/1.1\r\n"
     get << "X-Test: test\r\n" * (80 * 1024)
     assert_raises Unicorn::HttpParserError do
-      parser.execute({}, get)
+      parser.headers({}, get)
       parser.reset
     end
 
@@ -305,7 +521,7 @@ class HttpParserTest < Test::Unit::TestCase
     10.times do |c|
       get = "GET #{rand_data(1024, 1024+(c*1024), false)} #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
       assert_raises Unicorn::HttpParserError do
-        parser.execute({}, get)
+        parser.headers({}, get)
         parser.reset
       end
     end
diff --git a/test/unit/test_http_parser_ng.rb b/test/unit/test_http_parser_ng.rb
new file mode 100644
index 0000000..bb61e7f
--- /dev/null
+++ b/test/unit/test_http_parser_ng.rb
@@ -0,0 +1,420 @@
+# -*- encoding: binary -*-
+
+# coding: binary
+require 'test/test_helper'
+require 'digest/md5'
+
+include Unicorn
+
+class HttpParserNgTest < Test::Unit::TestCase
+
+  def setup
+    @parser = HttpParser.new
+  end
+
+  def test_identity_byte_headers
+    req = {}
+    str = "PUT / HTTP/1.1\r\n"
+    str << "Content-Length: 123\r\n"
+    str << "\r"
+    hdr = ""
+    str.each_byte { |byte|
+      assert_nil @parser.headers(req, hdr << byte.chr)
+    }
+    hdr << "\n"
+    assert_equal req.object_id, @parser.headers(req, hdr).object_id
+    assert_equal '123', req['CONTENT_LENGTH']
+    assert_equal 0, hdr.size
+    assert ! @parser.keepalive?
+    assert @parser.headers?
+    assert 123, @parser.content_length
+  end
+
+  def test_identity_step_headers
+    req = {}
+    str = "PUT / HTTP/1.1\r\n"
+    assert ! @parser.headers(req, str)
+    str << "Content-Length: 123\r\n"
+    assert ! @parser.headers(req, str)
+    str << "\r\n"
+    assert_equal req.object_id, @parser.headers(req, str).object_id
+    assert_equal '123', req['CONTENT_LENGTH']
+    assert_equal 0, str.size
+    assert ! @parser.keepalive?
+    assert @parser.headers?
+  end
+
+  def test_identity_oneshot_header
+    req = {}
+    str = "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\n"
+    assert_equal req.object_id, @parser.headers(req, str).object_id
+    assert_equal '123', req['CONTENT_LENGTH']
+    assert_equal 0, str.size
+    assert ! @parser.keepalive?
+  end
+
+  def test_identity_oneshot_header_with_body
+    body = ('a' * 123).freeze
+    req = {}
+    str = "PUT / HTTP/1.1\r\n" \
+          "Content-Length: #{body.length}\r\n" \
+          "\r\n#{body}"
+    assert_equal req.object_id, @parser.headers(req, str).object_id
+    assert_equal '123', req['CONTENT_LENGTH']
+    assert_equal 123, str.size
+    assert_equal body, str
+    tmp = ''
+    assert_nil @parser.filter_body(tmp, str)
+    assert_equal 0, str.size
+    assert_equal tmp, body
+    assert_equal "", @parser.filter_body(tmp, str)
+    assert ! @parser.keepalive?
+  end
+
+  def test_identity_oneshot_header_with_body_partial
+    str = "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\na"
+    assert_equal Hash, @parser.headers({}, str).class
+    assert_equal 1, str.size
+    assert_equal 'a', str
+    tmp = ''
+    assert_nil @parser.filter_body(tmp, str)
+    assert_equal "", str
+    assert_equal "a", tmp
+    str << ' ' * 122
+    rv = @parser.filter_body(tmp, str)
+    assert_equal 122, tmp.size
+    assert_nil rv
+    assert_equal "", str
+    assert_equal str.object_id, @parser.filter_body(tmp, str).object_id
+    assert ! @parser.keepalive?
+  end
+
+  def test_identity_oneshot_header_with_body_slop
+    str = "PUT / HTTP/1.1\r\nContent-Length: 1\r\n\r\naG"
+    assert_equal Hash, @parser.headers({}, str).class
+    assert_equal 2, str.size
+    assert_equal 'aG', str
+    tmp = ''
+    assert_nil @parser.filter_body(tmp, str)
+    assert_equal "G", str
+    assert_equal "G", @parser.filter_body(tmp, str)
+    assert_equal 1, tmp.size
+    assert_equal "a", tmp
+    assert ! @parser.keepalive?
+  end
+
+  def test_chunked
+    str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
+    req = {}
+    assert_equal req, @parser.headers(req, str)
+    assert_equal 0, str.size
+    tmp = ""
+    assert_nil @parser.filter_body(tmp, "6")
+    assert_equal 0, tmp.size
+    assert_nil @parser.filter_body(tmp, rv = "\r\n")
+    assert_equal 0, rv.size
+    assert_equal 0, tmp.size
+    tmp = ""
+    assert_nil @parser.filter_body(tmp, "..")
+    assert_equal "..", tmp
+    assert_nil @parser.filter_body(tmp, "abcd\r\n0\r\n")
+    assert_equal "abcd", tmp
+    rv = "PUT"
+    assert_equal rv.object_id, @parser.filter_body(tmp, rv).object_id
+    assert_equal "PUT", rv
+    assert ! @parser.keepalive?
+  end
+
+  def test_two_chunks
+    str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
+    req = {}
+    assert_equal req, @parser.headers(req, str)
+    assert_equal 0, str.size
+    tmp = ""
+    assert_nil @parser.filter_body(tmp, "6")
+    assert_equal 0, tmp.size
+    assert_nil @parser.filter_body(tmp, rv = "\r\n")
+    assert_equal "", rv
+    assert_equal 0, tmp.size
+    tmp = ""
+    assert_nil @parser.filter_body(tmp, "..")
+    assert_equal 2, tmp.size
+    assert_equal "..", tmp
+    assert_nil @parser.filter_body(tmp, "abcd\r\n1")
+    assert_equal "abcd", tmp
+    assert_nil @parser.filter_body(tmp, "\r")
+    assert_equal "", tmp
+    assert_nil @parser.filter_body(tmp, "\n")
+    assert_equal "", tmp
+    assert_nil @parser.filter_body(tmp, "z")
+    assert_equal "z", tmp
+    assert_nil @parser.filter_body(tmp, "\r\n")
+    assert_nil @parser.filter_body(tmp, "0")
+    assert_nil @parser.filter_body(tmp, "\r")
+    rv = @parser.filter_body(tmp, buf = "\nGET")
+    assert_equal "GET", rv
+    assert_equal buf.object_id, rv.object_id
+    assert ! @parser.keepalive?
+  end
+
+  def test_big_chunk
+    str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
+          "4000\r\nabcd"
+    req = {}
+    assert_equal req, @parser.headers(req, str)
+    tmp = ''
+    assert_nil @parser.filter_body(tmp, str)
+    assert_equal '', str
+    str = ' ' * 16300
+    assert_nil @parser.filter_body(tmp, str)
+    assert_equal '', str
+    str = ' ' * 80
+    assert_nil @parser.filter_body(tmp, str)
+    assert_equal '', str
+    assert ! @parser.body_eof?
+    assert_equal "", @parser.filter_body(tmp, "\r\n0\r\n")
+    assert @parser.body_eof?
+    assert ! @parser.keepalive?
+  end
+
+  def test_two_chunks_oneshot
+    str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
+          "1\r\na\r\n2\r\n..\r\n0\r\n"
+    req = {}
+    assert_equal req, @parser.headers(req, str)
+    tmp = ''
+    assert_nil @parser.filter_body(tmp, str)
+    assert_equal 'a..', tmp
+    rv = @parser.filter_body(tmp, str)
+    assert_equal rv.object_id, str.object_id
+    assert ! @parser.keepalive?
+  end
+
+  def test_chunks_bytewise
+    chunked = "10\r\nabcdefghijklmnop\r\n11\r\n0123456789abcdefg\r\n0\r\n"
+    str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n#{chunked}"
+    req = {}
+    assert_equal req, @parser.headers(req, str)
+    assert_equal chunked, str
+    tmp = ''
+    buf = ''
+    body = ''
+    str = str[0..-2]
+    str.each_byte { |byte|
+      assert_nil @parser.filter_body(tmp, buf << byte.chr)
+      body << tmp
+    }
+    assert_equal 'abcdefghijklmnop0123456789abcdefg', body
+    rv = @parser.filter_body(tmp, buf << "\n")
+    assert_equal rv.object_id, buf.object_id
+    assert ! @parser.keepalive?
+  end
+
+  def test_trailers
+    str = "PUT / HTTP/1.1\r\n" \
+          "Trailer: Content-MD5\r\n" \
+          "transfer-Encoding: chunked\r\n\r\n" \
+          "1\r\na\r\n2\r\n..\r\n0\r\n"
+    req = {}
+    assert_equal req, @parser.headers(req, str)
+    assert_equal 'Content-MD5', req['HTTP_TRAILER']
+    assert_nil req['HTTP_CONTENT_MD5']
+    tmp = ''
+    assert_nil @parser.filter_body(tmp, str)
+    assert_equal 'a..', tmp
+    md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
+    rv = @parser.filter_body(tmp, str)
+    assert_equal rv.object_id, str.object_id
+    assert_equal '', str
+    md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
+    str << md5_hdr
+    assert_nil @parser.trailers(req, str)
+    assert_equal md5_b64, req['HTTP_CONTENT_MD5']
+    assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
+    assert_nil @parser.trailers(req, str << "\r")
+    assert_equal req, @parser.trailers(req, str << "\nGET / ")
+    assert_equal "GET / ", str
+    assert ! @parser.keepalive?
+  end
+
+  def test_trailers_slowly
+    str = "PUT / HTTP/1.1\r\n" \
+          "Trailer: Content-MD5\r\n" \
+          "transfer-Encoding: chunked\r\n\r\n" \
+          "1\r\na\r\n2\r\n..\r\n0\r\n"
+    req = {}
+    assert_equal req, @parser.headers(req, str)
+    assert_equal 'Content-MD5', req['HTTP_TRAILER']
+    assert_nil req['HTTP_CONTENT_MD5']
+    tmp = ''
+    assert_nil @parser.filter_body(tmp, str)
+    assert_equal 'a..', tmp
+    md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
+    rv = @parser.filter_body(tmp, str)
+    assert_equal rv.object_id, str.object_id
+    assert_equal '', str
+    assert_nil @parser.trailers(req, str)
+    md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
+    md5_hdr.each_byte { |byte|
+      str << byte.chr
+      assert_nil @parser.trailers(req, str)
+    }
+    assert_equal md5_b64, req['HTTP_CONTENT_MD5']
+    assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
+    assert_nil @parser.trailers(req, str << "\r")
+    assert_equal req, @parser.trailers(req, str << "\n")
+  end
+
+  def test_max_chunk
+    str = "PUT / HTTP/1.1\r\n" \
+          "transfer-Encoding: chunked\r\n\r\n" \
+          "#{HttpParser::CHUNK_MAX.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
+    req = {}
+    assert_equal req, @parser.headers(req, str)
+    assert_nil @parser.content_length
+    assert_nothing_raised { @parser.filter_body('', str) }
+    assert ! @parser.keepalive?
+  end
+
+  def test_max_body
+    n = HttpParser::LENGTH_MAX
+    str = "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
+    req = {}
+    assert_nothing_raised { @parser.headers(req, str) }
+    assert_equal n, req['CONTENT_LENGTH'].to_i
+    assert ! @parser.keepalive?
+  end
+
+  def test_overflow_chunk
+    n = HttpParser::CHUNK_MAX + 1
+    str = "PUT / HTTP/1.1\r\n" \
+          "transfer-Encoding: chunked\r\n\r\n" \
+          "#{n.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
+    req = {}
+    assert_equal req, @parser.headers(req, str)
+    assert_nil @parser.content_length
+    assert_raise(HttpParserError) { @parser.filter_body('', str) }
+    assert ! @parser.keepalive?
+  end
+
+  def test_overflow_content_length
+    n = HttpParser::LENGTH_MAX + 1
+    str = "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
+    assert_raise(HttpParserError) { @parser.headers({}, str) }
+    assert ! @parser.keepalive?
+  end
+
+  def test_bad_chunk
+    str = "PUT / HTTP/1.1\r\n" \
+          "transfer-Encoding: chunked\r\n\r\n" \
+          "#zzz\r\na\r\n2\r\n..\r\n0\r\n"
+    req = {}
+    assert_equal req, @parser.headers(req, str)
+    assert_nil @parser.content_length
+    assert_raise(HttpParserError) { @parser.filter_body('', str) }
+    assert ! @parser.keepalive?
+  end
+
+  def test_bad_content_length
+    str = "PUT / HTTP/1.1\r\nContent-Length: 7ff\r\n\r\n"
+    assert_raise(HttpParserError) { @parser.headers({}, str) }
+    assert ! @parser.keepalive?
+  end
+
+  def test_bad_trailers
+    str = "PUT / HTTP/1.1\r\n" \
+          "Trailer: Transfer-Encoding\r\n" \
+          "transfer-Encoding: chunked\r\n\r\n" \
+          "1\r\na\r\n2\r\n..\r\n0\r\n"
+    req = {}
+    assert_equal req, @parser.headers(req, str)
+    assert_equal 'Transfer-Encoding', req['HTTP_TRAILER']
+    tmp = ''
+    assert_nil @parser.filter_body(tmp, str)
+    assert_equal 'a..', tmp
+    assert_equal '', str
+    str << "Transfer-Encoding: identity\r\n\r\n"
+    assert_raise(HttpParserError) { @parser.trailers(req, str) }
+    assert ! @parser.keepalive?
+  end
+
+  def test_repeat_headers
+    str = "PUT / HTTP/1.1\r\n" \
+          "Trailer: Content-MD5\r\n" \
+          "Trailer: Content-SHA1\r\n" \
+          "transfer-Encoding: chunked\r\n\r\n" \
+          "1\r\na\r\n2\r\n..\r\n0\r\n"
+    req = {}
+    assert_equal req, @parser.headers(req, str)
+    assert_equal 'Content-MD5,Content-SHA1', req['HTTP_TRAILER']
+    assert ! @parser.keepalive?
+  end
+
+  def test_parse_simple_request
+    parser = HttpParser.new
+    req = {}
+    http = "GET /read-rfc1945-if-you-dont-believe-me\r\n"
+    assert_equal req, parser.headers(req, http)
+    assert_equal '', http
+    expect = {
+      "SERVER_NAME"=>"localhost",
+      "rack.url_scheme"=>"http",
+      "REQUEST_PATH"=>"/read-rfc1945-if-you-dont-believe-me",
+      "PATH_INFO"=>"/read-rfc1945-if-you-dont-believe-me",
+      "REQUEST_URI"=>"/read-rfc1945-if-you-dont-believe-me",
+      "SERVER_PORT"=>"80",
+      "SERVER_PROTOCOL"=>"HTTP/0.9",
+      "REQUEST_METHOD"=>"GET",
+      "QUERY_STRING"=>""
+    }
+    assert_equal expect, req
+    assert ! parser.headers?
+  end
+
+  def test_path_info_semicolon
+    qs = "QUERY_STRING"
+    pi = "PATH_INFO"
+    req = {}
+    str = "GET %s HTTP/1.1\r\nHost: example.com\r\n\r\n"
+    {
+      "/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
+      "/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
+      "/1;a=b" => { qs => "", pi => "/1;a=b" },
+      "/1;a=b?" => { qs => "", pi => "/1;a=b" },
+      "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
+      "*" => { qs => "", pi => "" },
+    }.each do |uri,expect|
+      assert_equal req, @parser.headers(req.clear, str % [ uri ])
+      @parser.reset
+      assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
+      assert_equal expect[qs], req[qs], "#{qs} mismatch"
+      assert_equal expect[pi], req[pi], "#{pi} mismatch"
+      next if uri == "*"
+      uri = URI.parse("http://example.com#{uri}")
+      assert_equal uri.query.to_s, req[qs], "#{qs} mismatch URI.parse disagrees"
+      assert_equal uri.path, req[pi], "#{pi} mismatch URI.parse disagrees"
+    end
+  end
+
+  def test_path_info_semicolon_absolute
+    qs = "QUERY_STRING"
+    pi = "PATH_INFO"
+    req = {}
+    str = "GET http://example.com%s HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
+    {
+      "/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
+      "/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
+      "/1;a=b" => { qs => "", pi => "/1;a=b" },
+      "/1;a=b?" => { qs => "", pi => "/1;a=b" },
+      "/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
+    }.each do |uri,expect|
+      assert_equal req, @parser.headers(req.clear, str % [ uri ])
+      @parser.reset
+      assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
+      assert_equal "example.com", req["HTTP_HOST"], "Host: mismatch"
+      assert_equal expect[qs], req[qs], "#{qs} mismatch"
+      assert_equal expect[pi], req[pi], "#{pi} mismatch"
+    end
+  end
+
+end
diff --git a/test/unit/test_request.rb b/test/unit/test_request.rb
index 0bfff7d..1896300 100644
--- a/test/unit/test_request.rb
+++ b/test/unit/test_request.rb
@@ -1,14 +1,9 @@
+# -*- encoding: binary -*-
+
 # Copyright (c) 2009 Eric Wong
 # You can redistribute it and/or modify it under the same terms as Ruby.
 
 require 'test/test_helper'
-begin
-  require 'rack'
-  require 'rack/lint'
-rescue LoadError
-  warn "Unable to load rack, skipping test"
-  exit 0
-end
 
 include Unicorn
 
@@ -16,10 +11,11 @@ class RequestTest < Test::Unit::TestCase
 
   class MockRequest < StringIO
     alias_method :readpartial, :sysread
+    alias_method :read_nonblock, :sysread
   end
 
   def setup
-    @request = HttpRequest.new(Logger.new($stderr))
+    @request = HttpRequest.new
     @app = lambda do |env|
       [ 200, { 'Content-Length' => '0', 'Content-Type' => 'text/plain' }, [] ]
     end
@@ -119,6 +115,31 @@ class RequestTest < Test::Unit::TestCase
     assert_nothing_raised { res = @lint.call(env) }
   end
 
+  def test_no_content_stringio
+    client = MockRequest.new("GET / HTTP/1.1\r\nHost: foo\r\n\r\n")
+    res = env = nil
+    assert_nothing_raised { env = @request.read(client) }
+    assert_equal StringIO, env['rack.input'].class
+  end
+
+  def test_zero_content_stringio
+    client = MockRequest.new("PUT / HTTP/1.1\r\n" \
+                             "Content-Length: 0\r\n" \
+                             "Host: foo\r\n\r\n")
+    res = env = nil
+    assert_nothing_raised { env = @request.read(client) }
+    assert_equal StringIO, env['rack.input'].class
+  end
+
+  def test_real_content_not_stringio
+    client = MockRequest.new("PUT / HTTP/1.1\r\n" \
+                             "Content-Length: 1\r\n" \
+                             "Host: foo\r\n\r\n")
+    res = env = nil
+    assert_nothing_raised { env = @request.read(client) }
+    assert_equal Unicorn::TeeInput, env['rack.input'].class
+  end
+
   def test_rack_lint_put
     client = MockRequest.new(
       "PUT / HTTP/1.1\r\n" \
@@ -149,7 +170,11 @@ class RequestTest < Test::Unit::TestCase
     assert_nothing_raised { env = @request.read(client) }
     assert ! env.include?(:http_body)
     assert_equal length, env['rack.input'].size
-    count.times { assert_equal buf, env['rack.input'].read(bs) }
+    count.times {
+      tmp = env['rack.input'].read(bs)
+      tmp << env['rack.input'].read(bs - tmp.size) if tmp.size != bs
+      assert_equal buf, tmp
+    }
     assert_nil env['rack.input'].read(bs)
     assert_nothing_raised { env['rack.input'].rewind }
     assert_nothing_raised { res = @lint.call(env) }
diff --git a/test/unit/test_response.rb b/test/unit/test_response.rb
index 66c2b54..f9eda8e 100644
--- a/test/unit/test_response.rb
+++ b/test/unit/test_response.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 # Copyright (c) 2005 Zed A. Shaw
 # You can redistribute it and/or modify it under the same terms as Ruby.
 #
@@ -94,4 +96,15 @@ class ResponseTest < Test::Unit::TestCase
     assert_match(expect_body, out.string.split(/\r\n/).last)
   end
 
+  def test_unknown_status_pass_through
+    out = StringIO.new
+    HttpResponse.write(out,["666 I AM THE BEAST", {}, [] ])
+    assert out.closed?
+    headers = out.string.split(/\r\n\r\n/).first.split(/\r\n/)
+    assert %r{\AHTTP/\d\.\d 666 I AM THE BEAST\z}.match(headers[0])
+    status = headers.grep(/\AStatus:/i).first
+    assert status
+    assert_equal "Status: 666 I AM THE BEAST", status
+  end
+
 end
diff --git a/test/unit/test_server.rb b/test/unit/test_server.rb
index 742b240..00705d0 100644
--- a/test/unit/test_server.rb
+++ b/test/unit/test_server.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 # Copyright (c) 2005 Zed A. Shaw
 # You can redistribute it and/or modify it under the same terms as Ruby.
 #
@@ -10,9 +12,13 @@ include Unicorn
 
 class TestHandler
 
-  def call(env)
-  #   response.socket.write("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nhello!\n")
+  def call(env)
+    while env['rack.input'].read(4096)
+    end
     [200, { 'Content-Type' => 'text/plain' }, ['hello!\n']]
+    rescue Unicorn::ClientShutdown, Unicorn::HttpParserError => e
+      $stderr.syswrite("#{e.class}: #{e.message} #{e.backtrace.empty?}\n")
+      raise e
    end
 end
 
@@ -31,6 +37,8 @@ class WebServerTest < Test::Unit::TestCase
 
   def teardown
     redirect_test_io do
+      wait_workers_ready("test_stderr.#$$.log", 1)
+      File.truncate("test_stderr.#$$.log", 0)
       @server.stop(true)
     end
   end
@@ -51,8 +59,10 @@ class WebServerTest < Test::Unit::TestCase
     end
     results = hit(["http://localhost:#@port/"])
     worker_pid = results[0].to_i
+    assert worker_pid != 0
     tmp.sysseek(0)
     loader_pid = tmp.sysread(4096).to_i
+    assert loader_pid != 0
     assert_equal worker_pid, loader_pid
     teardown
 
@@ -63,6 +73,7 @@ class WebServerTest < Test::Unit::TestCase
     end
     results = hit(["http://localhost:#@port/"])
     worker_pid = results[0].to_i
+    assert worker_pid != 0
     tmp.sysseek(0)
     loader_pid = tmp.sysread(4096).to_i
     assert_equal $$, loader_pid
@@ -94,6 +105,92 @@ class WebServerTest < Test::Unit::TestCase
     assert_equal 'hello!\n', results[0], "Handler didn't really run"
   end
 
+  def test_client_shutdown_writes
+    sock = nil
+    buf = nil
+    bs = 15609315 * rand
+    assert_nothing_raised do
+      sock = TCPSocket.new('127.0.0.1', @port)
+      sock.syswrite("PUT /hello HTTP/1.1\r\n")
+      sock.syswrite("Host: example.com\r\n")
+      sock.syswrite("Transfer-Encoding: chunked\r\n")
+      sock.syswrite("Trailer: X-Foo\r\n")
+      sock.syswrite("\r\n")
+      sock.syswrite("%x\r\n" % [ bs ])
+      sock.syswrite("F" * bs)
+      sock.syswrite("\r\n0\r\nX-")
+      "Foo: bar\r\n\r\n".each_byte do |x|
+        sock.syswrite x.chr
+        sleep 0.05
+      end
+      # we wrote the entire request before shutting down, server should
+      # continue to process our request and never hit EOFError on our sock
+      sock.shutdown(Socket::SHUT_WR)
+      buf = sock.read
+    end
+    assert_equal 'hello!\n', buf.split(/\r\n\r\n/).last
+    next_client = Net::HTTP.get(URI.parse("http://127.0.0.1:#@port/"))
+    assert_equal 'hello!\n', next_client
+    lines = File.readlines("test_stderr.#$$.log")
+    assert lines.grep(/^Unicorn::ClientShutdown: /).empty?
+    assert_nothing_raised { sock.close }
+  end
+
+  def test_client_shutdown_write_truncates
+    sock = nil
+    buf = nil
+    bs = 15609315 * rand
+    assert_nothing_raised do
+      sock = TCPSocket.new('127.0.0.1', @port)
+      sock.syswrite("PUT /hello HTTP/1.1\r\n")
+      sock.syswrite("Host: example.com\r\n")
+      sock.syswrite("Transfer-Encoding: chunked\r\n")
+      sock.syswrite("Trailer: X-Foo\r\n")
+      sock.syswrite("\r\n")
+      sock.syswrite("%x\r\n" % [ bs ])
+      sock.syswrite("F" * (bs / 2.0))
+
+      # shutdown prematurely, this will force the server to abort
+      # processing on us even during app dispatch
+      sock.shutdown(Socket::SHUT_WR)
+      IO.select([sock], nil, nil, 60) or raise "Timed out"
+      buf = sock.read
+    end
+    assert_equal "", buf
+    next_client = Net::HTTP.get(URI.parse("http://127.0.0.1:#@port/"))
+    assert_equal 'hello!\n', next_client
+    lines = File.readlines("test_stderr.#$$.log")
+    lines = lines.grep(/^Unicorn::ClientShutdown: bytes_read=\d+/)
+    assert_equal 1, lines.size
+    assert_match %r{\AUnicorn::ClientShutdown: bytes_read=\d+ true$}, lines[0]
+    assert_nothing_raised { sock.close }
+  end
+
+  def test_client_malformed_body
+    sock = nil
+    buf = nil
+    bs = 15653984
+    assert_nothing_raised do
+      sock = TCPSocket.new('127.0.0.1', @port)
+      sock.syswrite("PUT /hello HTTP/1.1\r\n")
+      sock.syswrite("Host: example.com\r\n")
+      sock.syswrite("Transfer-Encoding: chunked\r\n")
+      sock.syswrite("Trailer: X-Foo\r\n")
+      sock.syswrite("\r\n")
+      sock.syswrite("%x\r\n" % [ bs ])
+      sock.syswrite("F" * bs)
+    end
+    begin
+      File.open("/dev/urandom", "rb") { |fp| sock.syswrite(fp.sysread(16384)) }
+    rescue
+    end
+    assert_nothing_raised { sock.close }
+    next_client = Net::HTTP.get(URI.parse("http://127.0.0.1:#@port/"))
+    assert_equal 'hello!\n', next_client
+    lines = File.readlines("test_stderr.#$$.log")
+    lines = lines.grep(/^Unicorn::HttpParserError: .* true$/)
+    assert_equal 1, lines.size
+  end
 
   def do_test(string, chunk, close_after=nil, shutdown_delay=0)
     # Do not use instance variables here, because it needs to be thread safe
@@ -131,6 +228,16 @@ class WebServerTest < Test::Unit::TestCase
     end
   end
 
+  def test_logger_set
+    assert_equal @server.logger, Unicorn::HttpRequest::DEFAULTS["rack.logger"]
+  end
+
+  def test_logger_changed
+    tmp = Logger.new($stdout)
+    @server.logger = tmp
+    assert_equal tmp, Unicorn::HttpRequest::DEFAULTS["rack.logger"]
+  end
+
   def test_bad_client_400
     sock = nil
     assert_nothing_raised do
@@ -141,6 +248,16 @@ class WebServerTest < Test::Unit::TestCase
     assert_nothing_raised { sock.close }
   end
 
+  def test_http_0_9
+    sock = nil
+    assert_nothing_raised do
+      sock = TCPSocket.new('127.0.0.1', @port)
+      sock.syswrite("GET /hello\r\n")
+    end
+    assert_match 'hello!\n', sock.sysread(4096)
+    assert_nothing_raised { sock.close }
+  end
+
   def test_header_is_too_long
     redirect_test_io do
       long = "GET /test HTTP/1.1\r\n" + ("X-Big: stuff\r\n" * 15000) + "\r\n"
@@ -152,9 +269,18 @@ class WebServerTest < Test::Unit::TestCase
 
   def test_file_streamed_request
     body = "a" * (Unicorn::Const::MAX_BODY * 2)
-    long = "GET /test HTTP/1.1\r\nContent-length: #{body.length}\r\n\r\n" + body
+    long = "PUT /test HTTP/1.1\r\nContent-length: #{body.length}\r\n\r\n" + body
     do_test(long, Unicorn::Const::CHUNK_SIZE * 2 -400)
   end
 
+  def test_file_streamed_request_bad_body
+    body = "a" * (Unicorn::Const::MAX_BODY * 2)
+    long = "GET /test HTTP/1.1\r\nContent-ength: #{body.length}\r\n\r\n" + body
+    assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
+                  Errno::EBADF) {
+      do_test(long, Unicorn::Const::CHUNK_SIZE * 2 -400)
+    }
+  end
+
 end
 
diff --git a/test/unit/test_signals.rb b/test/unit/test_signals.rb
index ef66ed6..eb2af0b 100644
--- a/test/unit/test_signals.rb
+++ b/test/unit/test_signals.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 # Copyright (c) 2009 Eric Wong
 # You can redistribute it and/or modify it under the same terms as Ruby.
 #
@@ -24,14 +26,15 @@ class SignalsTest < Test::Unit::TestCase
     @bs = 1 * 1024 * 1024
     @count = 100
     @port = unused_port
-    tmp = @tmp = Tempfile.new('unicorn.sock')
+    @sock = Tempfile.new('unicorn.sock')
+    @tmp = Tempfile.new('unicorn.write')
+    @tmp.sync = true
+    File.unlink(@sock.path)
     File.unlink(@tmp.path)
-    n = 0
-    tmp.chmod(0)
     @server_opts = {
-      :listeners => [ "127.0.0.1:#@port", @tmp.path ],
+      :listeners => [ "127.0.0.1:#@port", @sock.path ],
       :after_fork => lambda { |server,worker|
-        trap(:HUP) { tmp.chmod(n += 1) }
+        trap(:HUP) { @tmp.syswrite('.') }
       },
     }
     @server = nil
@@ -53,8 +56,10 @@ class SignalsTest < Test::Unit::TestCase
       buf =~ /\bX-Pid: (\d+)\b/ or raise Exception
       child = $1.to_i
       wait_master_ready("test_stderr.#{pid}.log")
+      wait_workers_ready("test_stderr.#{pid}.log", 1)
       Process.kill(:KILL, pid)
       Process.waitpid(pid)
+      File.unlink("test_stderr.#{pid}.log", "test_stdout.#{pid}.log")
       t0 = Time.now
     end
     assert child
@@ -137,8 +142,9 @@ class SignalsTest < Test::Unit::TestCase
       pid = buf[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
       header_len = buf[/\A(.+?\r\n\r\n)/m, 1].size
     end
+    assert pid > 0, "pid not positive: #{pid.inspect}"
     read = buf.size
-    mode_before = @tmp.stat.mode
+    size_before = @tmp.stat.size
     assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
                   Errno::EBADF) do
       loop do
@@ -151,13 +157,17 @@ class SignalsTest < Test::Unit::TestCase
 
     redirect_test_io { @server.stop(true) }
     # can't check for == since pending signals get merged
-    assert mode_before < @tmp.stat.mode
-    assert_equal(read - header_len, @bs * @count)
+    assert size_before < @tmp.stat.size
+    got = read - header_len
+    expect = @bs * @count
+    assert_equal(expect, got, "expect=#{expect} got=#{got}")
     assert_nothing_raised { sock.close }
   end
 
   def test_request_read
     app = lambda { |env|
+      while env['rack.input'].read(4096)
+      end
       [ 200, {'Content-Type'=>'text/plain', 'X-Pid'=>Process.pid.to_s}, [] ]
     }
     redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
@@ -171,11 +181,12 @@ class SignalsTest < Test::Unit::TestCase
       sock.close
     end
 
+    assert pid > 0, "pid not positive: #{pid.inspect}"
     sock = TCPSocket.new('127.0.0.1', @port)
     sock.syswrite("PUT / HTTP/1.0\r\n")
     sock.syswrite("Content-Length: #{@bs * @count}\r\n\r\n")
     1000.times { Process.kill(:HUP, pid) }
-    mode_before = @tmp.stat.mode
+    size_before = @tmp.stat.size
     killer = fork { loop { Process.kill(:HUP, pid); sleep(0.0001) } }
     buf = ' ' * @bs
     @count.times { sock.syswrite(buf) }
@@ -183,7 +194,7 @@ class SignalsTest < Test::Unit::TestCase
     Process.waitpid2(killer)
     redirect_test_io { @server.stop(true) }
     # can't check for == since pending signals get merged
-    assert mode_before < @tmp.stat.mode
+    assert size_before < @tmp.stat.size
     assert_equal pid, sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
     sock.close
   end
diff --git a/test/unit/test_socket_helper.rb b/test/unit/test_socket_helper.rb
index 75d9f7b..c35b0c2 100644
--- a/test/unit/test_socket_helper.rb
+++ b/test/unit/test_socket_helper.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 require 'test/test_helper'
 require 'tempfile'
 
@@ -61,6 +63,20 @@ class TestSocketHelper < Test::Unit::TestCase
       File.umask(old_umask)
   end
 
+  def test_bind_listen_unix_umask
+    old_umask = File.umask(0777)
+    tmp = Tempfile.new 'unix.sock'
+    @unix_listener_path = tmp.path
+    File.unlink(@unix_listener_path)
+    @unix_listener = bind_listen(@unix_listener_path, :umask => 077)
+    assert UNIXServer === @unix_listener
+    assert_equal @unix_listener_path, sock_name(@unix_listener)
+    assert_equal 0140700, File.stat(@unix_listener_path).mode
+    assert_equal 0777, File.umask
+    ensure
+      File.umask(old_umask)
+  end
+
   def test_bind_listen_unix_idempotent
     test_bind_listen_unix
     a = bind_listen(@unix_listener)
diff --git a/test/unit/test_tee_input.rb b/test/unit/test_tee_input.rb
new file mode 100644
index 0000000..403f698
--- /dev/null
+++ b/test/unit/test_tee_input.rb
@@ -0,0 +1,229 @@
+# -*- encoding: binary -*-
+
+require 'test/unit'
+require 'digest/sha1'
+require 'unicorn'
+
+class TestTeeInput < Test::Unit::TestCase
+
+  def setup
+    @rs = $/
+    @env = {}
+    @rd, @wr = IO.pipe
+    @rd.sync = @wr.sync = true
+    @start_pid = $$
+  end
+
+  def teardown
+    return if $$ != @start_pid
+    $/ = @rs
+    @rd.close rescue nil
+    @wr.close rescue nil
+    begin
+      Process.wait
+    rescue Errno::ECHILD
+      break
+    end while true
+  end
+
+  def test_gets_long
+    init_parser("hello", 5 + (4096 * 4 * 3) + "#$/foo#$/".size)
+    ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf)
+    status = line = nil
+    pid = fork {
+      @rd.close
+      3.times { @wr.write("ffff" * 4096) }
+      @wr.write "#$/foo#$/"
+      @wr.close
+    }
+    @wr.close
+    assert_nothing_raised { line = ti.gets }
+    assert_equal(4096 * 4 * 3 + 5 + $/.size, line.size)
+    assert_equal("hello" << ("ffff" * 4096 * 3) << "#$/", line)
+    assert_nothing_raised { line = ti.gets }
+    assert_equal "foo#$/", line
+    assert_nil ti.gets
+    assert_nothing_raised { pid, status = Process.waitpid2(pid) }
+    assert status.success?
+  end
+
+  def test_gets_short
+    init_parser("hello", 5 + "#$/foo".size)
+    ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf)
+    status = line = nil
+    pid = fork {
+      @rd.close
+      @wr.write "#$/foo"
+      @wr.close
+    }
+    @wr.close
+    assert_nothing_raised { line = ti.gets }
+    assert_equal("hello#$/", line)
+    assert_nothing_raised { line = ti.gets }
+    assert_equal "foo", line
+    assert_nil ti.gets
+    assert_nothing_raised { pid, status = Process.waitpid2(pid) }
+    assert status.success?
+  end
+
+  def test_small_body
+    init_parser('hello')
+    ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf)
+    assert_equal 0, @parser.content_length
+    assert @parser.body_eof?
+    assert_equal StringIO, ti.instance_eval { @tmp.class }
+    assert_equal 0, ti.instance_eval { @tmp.pos }
+    assert_equal 5, ti.size
+    assert_equal 'hello', ti.read
+    assert_equal '', ti.read
+    assert_nil ti.read(4096)
+  end
+
+  def test_read_with_buffer
+    init_parser('hello')
+    ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf)
+    buf = ''
+    rv = ti.read(4, buf)
+    assert_equal 'hell', rv
+    assert_equal 'hell', buf
+    assert_equal rv.object_id, buf.object_id
+    assert_equal 'o', ti.read
+    assert_equal nil, ti.read(5, buf)
+    assert_equal 0, ti.rewind
+    assert_equal 'hello', ti.read(5, buf)
+    assert_equal 'hello', buf
+  end
+
+  def test_big_body
+    init_parser('.' * Unicorn::Const::MAX_BODY << 'a')
+    ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf)
+    assert_equal 0, @parser.content_length
+    assert @parser.body_eof?
+    assert_kind_of File, ti.instance_eval { @tmp }
+    assert_equal 0, ti.instance_eval { @tmp.pos }
+    assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
+  end
+
+  def test_read_in_full_if_content_length
+    a, b = 300, 3
+    init_parser('.' * b, 300)
+    assert_equal 300, @parser.content_length
+    ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf)
+    pid = fork {
+      @wr.write('.' * 197)
+      sleep 1 # still a *potential* race here that would make the test moot...
+      @wr.write('.' * 100)
+    }
+    assert_equal a, ti.read(a).size
+    _, status = Process.waitpid2(pid)
+    assert status.success?
+    @wr.close
+  end
+
+  def test_big_body_multi
+    init_parser('.', Unicorn::Const::MAX_BODY + 1)
+    ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf)
+    assert_equal Unicorn::Const::MAX_BODY, @parser.content_length
+    assert ! @parser.body_eof?
+    assert_kind_of File, ti.instance_eval { @tmp }
+    assert_equal 0, ti.instance_eval { @tmp.pos }
+    assert_equal 1, ti.instance_eval { @tmp.size }
+    assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
+    nr = Unicorn::Const::MAX_BODY / 4
+    pid = fork {
+      @rd.close
+      nr.times { @wr.write('....') }
+      @wr.close
+    }
+    @wr.close
+    assert_equal '.', ti.read(1)
+    assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
+    nr.times {
+      assert_equal '....', ti.read(4)
+      assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
+    }
+    assert_nil ti.read(1)
+    status = nil
+    assert_nothing_raised { pid, status = Process.waitpid2(pid) }
+    assert status.success?
+  end
+
+  def test_chunked
+    @parser = Unicorn::HttpParser.new
+    @buf = "POST / HTTP/1.1\r\n" \
+           "Host: localhost\r\n" \
+           "Transfer-Encoding: chunked\r\n" \
+           "\r\n"
+    assert_equal @env, @parser.headers(@env, @buf)
+    assert_equal "", @buf
+
+    pid = fork {
+      @rd.close
+      5.times { @wr.write("5\r\nabcde\r\n") }
+      @wr.write("0\r\n")
+    }
+    @wr.close
+    ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf)
+    assert_nil @parser.content_length
+    assert_nil ti.instance_eval { @size }
+    assert ! @parser.body_eof?
+    assert_equal 25, ti.size
+    assert @parser.body_eof?
+    assert_equal 25, ti.instance_eval { @size }
+    assert_equal 0, ti.instance_eval { @tmp.pos }
+    assert_nothing_raised { ti.rewind }
+    assert_equal 0, ti.instance_eval { @tmp.pos }
+    assert_equal 'abcdeabcdeabcdeabcde', ti.read(20)
+    assert_equal 20, ti.instance_eval { @tmp.pos }
+    assert_nothing_raised { ti.rewind }
+    assert_equal 0, ti.instance_eval { @tmp.pos }
+    assert_kind_of File, ti.instance_eval { @tmp }
+    status = nil
+    assert_nothing_raised { pid, status = Process.waitpid2(pid) }
+    assert status.success?
+  end
+
+  def test_chunked_ping_pong
+    @parser = Unicorn::HttpParser.new
+    @buf = "POST / HTTP/1.1\r\n" \
+           "Host: localhost\r\n" \
+           "Transfer-Encoding: chunked\r\n" \
+           "\r\n"
+    assert_equal @env, @parser.headers(@env, @buf)
+    assert_equal "", @buf
+    chunks = %w(aa bbb cccc dddd eeee)
+    rd, wr = IO.pipe
+
+    pid = fork {
+      chunks.each do |chunk|
+        rd.read(1) == "." and
+          @wr.write("#{'%x' % [ chunk.size]}\r\n#{chunk}\r\n")
+      end
+      @wr.write("0\r\n")
+    }
+    ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf)
+    assert_nil @parser.content_length
+    assert_nil ti.instance_eval { @size }
+    assert ! @parser.body_eof?
+    chunks.each do |chunk|
+      wr.write('.')
+      assert_equal chunk, ti.read(16384)
+    end
+    _, status = Process.waitpid2(pid)
+    assert status.success?
+  end
+
+private
+
+  def init_parser(body, size = nil)
+    @parser = Unicorn::HttpParser.new
+    body = body.to_s.freeze
+    @buf = "POST / HTTP/1.1\r\n" \
+           "Host: localhost\r\n" \
+           "Content-Length: #{size || body.size}\r\n" \
+           "\r\n#{body}"
+    assert_equal @env, @parser.headers(@env, @buf)
+    assert_equal body, @buf
+  end
+
+end
diff --git a/test/unit/test_upload.rb b/test/unit/test_upload.rb
index 9ef3ed7..7ac3c9e 100644
--- a/test/unit/test_upload.rb
+++ b/test/unit/test_upload.rb
@@ -1,5 +1,8 @@
+# -*- encoding: binary -*-
+
 # Copyright (c) 2009 Eric Wong
 require 'test/test_helper'
+require 'digest/md5'
 
 include Unicorn
 
@@ -18,29 +21,33 @@ class UploadTest < Test::Unit::TestCase
     @sha1 = Digest::SHA1.new
     @sha1_app = lambda do |env|
       input = env['rack.input']
-      resp = { :pos => input.pos, :size => input.size, :class => input.class }
+      resp = {}
 
-      # sysread
       @sha1.reset
-      begin
-        loop { @sha1.update(input.sysread(@bs)) }
-      rescue EOFError
+      while buf = input.read(@bs)
+        @sha1.update(buf)
       end
       resp[:sha1] = @sha1.hexdigest
 
-      # read
-      input.sysseek(0) if input.respond_to?(:sysseek)
+      # rewind and read again
       input.rewind
       @sha1.reset
-      loop {
-        buf = input.read(@bs) or break
+      while buf = input.read(@bs)
         @sha1.update(buf)
-      }
+      end
 
       if resp[:sha1] == @sha1.hexdigest
         resp[:sysread_read_byte_match] = true
       end
 
+      if expect_size = env['HTTP_X_EXPECT_SIZE']
+        if expect_size.to_i == input.size
+          resp[:expect_size_match] = true
+        end
+      end
+      resp[:size] = input.size
+      resp[:content_md5] = env['HTTP_CONTENT_MD5']
+
       [ 200, @hdr.merge({'X-Resp' => resp.inspect}), [] ]
     end
   end
@@ -54,7 +61,7 @@ class UploadTest < Test::Unit::TestCase
     start_server(@sha1_app)
     sock = TCPSocket.new(@addr, @port)
     sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n")
-    @count.times do
+    @count.times do |i|
       buf = @random.sysread(@bs)
       @sha1.update(buf)
       sock.syswrite(buf)
@@ -63,10 +70,34 @@ class UploadTest < Test::Unit::TestCase
     assert_equal "HTTP/1.1 200 OK", read[0]
     resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
     assert_equal length, resp[:size]
-    assert_equal 0, resp[:pos]
     assert_equal @sha1.hexdigest, resp[:sha1]
   end
 
+  def test_put_content_md5
+    md5 = Digest::MD5.new
+    start_server(@sha1_app)
+    sock = TCPSocket.new(@addr, @port)
+    sock.syswrite("PUT / HTTP/1.0\r\nTransfer-Encoding: chunked\r\n" \
+                  "Trailer: Content-MD5\r\n\r\n")
+    @count.times do |i|
+      buf = @random.sysread(@bs)
+      @sha1.update(buf)
+      md5.update(buf)
+      sock.syswrite("#{'%x' % buf.size}\r\n")
+      sock.syswrite(buf << "\r\n")
+    end
+    sock.syswrite("0\r\n")
+
+    content_md5 = [ md5.digest! ].pack('m').strip.freeze
+    sock.syswrite("Content-MD5: #{content_md5}\r\n\r\n")
+    read = sock.read.split(/\r\n/)
+    assert_equal "HTTP/1.1 200 OK", read[0]
+    resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
+    assert_equal length, resp[:size]
+    assert_equal @sha1.hexdigest, resp[:sha1]
+    assert_equal content_md5, resp[:content_md5]
+  end
+
   def test_put_trickle_small
     @count, @bs = 2, 128
     start_server(@sha1_app)
@@ -85,42 +116,7 @@ class UploadTest < Test::Unit::TestCase
     assert_equal "HTTP/1.1 200 OK", read[0]
     resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
     assert_equal length, resp[:size]
-    assert_equal 0, resp[:pos]
     assert_equal @sha1.hexdigest, resp[:sha1]
-    assert_equal StringIO, resp[:class]
-  end
-
-  def test_tempfile_unlinked
-    spew_path = lambda do |env|
-      if orig = env['HTTP_X_OLD_PATH']
-        assert orig != env['rack.input'].path
-      end
-      assert_equal length, env['rack.input'].size
-      [ 200, @hdr.merge('X-Tempfile-Path' => env['rack.input'].path), [] ]
-    end
-    start_server(spew_path)
-    sock = TCPSocket.new(@addr, @port)
-    sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n")
-    @count.times { sock.syswrite(' ' * @bs) }
-    path = sock.read[/^X-Tempfile-Path: (\S+)/, 1]
-    sock.close
-
-    # send another request to ensure we hit the next request
-    sock = TCPSocket.new(@addr, @port)
-    sock.syswrite("PUT / HTTP/1.0\r\nX-Old-Path: #{path}\r\n" \
-                  "Content-Length: #{length}\r\n\r\n")
-    @count.times { sock.syswrite(' ' * @bs) }
-    path2 = sock.read[/^X-Tempfile-Path: (\S+)/, 1]
-    sock.close
-    assert path != path2
-
-    # make sure the next request comes in so the unlink got processed
-    sock = TCPSocket.new(@addr, @port)
-    sock.syswrite("GET ?lasdf\r\n\r\n\r\n\r\n")
-    sock.sysread(4096) rescue nil
-    sock.close
-
-    assert ! File.exist?(path)
   end
 
   def test_put_keepalive_truncates_small_overwrite
@@ -136,75 +132,31 @@ class UploadTest < Test::Unit::TestCase
     sock.syswrite('12345') # write 4 bytes more than we expected
     @sha1.update('1')
 
-    read = sock.read.split(/\r\n/)
+    buf = sock.readpartial(4096)
+    while buf !~ /\r\n\r\n/
+      buf << sock.readpartial(4096)
+    end
+    read = buf.split(/\r\n/)
     assert_equal "HTTP/1.1 200 OK", read[0]
     resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
     assert_equal to_upload, resp[:size]
-    assert_equal 0, resp[:pos]
     assert_equal @sha1.hexdigest, resp[:sha1]
   end
 
   def test_put_excessive_overwrite_closed
-    start_server(lambda { |env| [ 200, @hdr, [] ] })
-    sock = TCPSocket.new(@addr, @port)
-    buf = ' ' * @bs
-    sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n")
-    @count.times { sock.syswrite(buf) }
-    assert_raise(Errno::ECONNRESET, Errno::EPIPE) do
-      ::Unicorn::Const::CHUNK_SIZE.times { sock.syswrite(buf) }
-    end
-  end
-
-  def test_put_handler_closed_file
-    nr = '0'
     start_server(lambda { |env|
-      env['rack.input'].close
-      resp = { :nr => nr.succ! }
-      [ 200, @hdr.merge({ 'X-Resp' => resp.inspect}), [] ]
+      while env['rack.input'].read(65536); end
+      [ 200, @hdr, [] ]
     })
     sock = TCPSocket.new(@addr, @port)
     buf = ' ' * @bs
     sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n")
-    @count.times { sock.syswrite(buf) }
-    read = sock.read.split(/\r\n/)
-    assert_equal "HTTP/1.1 200 OK", read[0]
-    resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
-    assert_equal '1', resp[:nr]
 
-    # server still alive?
-    sock = TCPSocket.new(@addr, @port)
-    sock.syswrite("GET / HTTP/1.0\r\n\r\n")
-    read = sock.read.split(/\r\n/)
-    assert_equal "HTTP/1.1 200 OK", read[0]
-    resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
-    assert_equal '2', resp[:nr]
-  end
-
-  def test_renamed_file_not_closed
-    start_server(lambda { |env|
-      new_tmp = Tempfile.new('unicorn_test')
-      input = env['rack.input']
-      File.rename(input.path, new_tmp.path)
-      resp = {
-        :inode => input.stat.ino,
-        :size => input.stat.size,
-        :new_tmp => new_tmp.path,
-        :old_tmp => input.path,
-      }
-      [ 200, @hdr.merge({ 'X-Resp' => resp.inspect}), [] ]
-    })
-    sock = TCPSocket.new(@addr, @port)
-    buf = ' ' * @bs
-    sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n")
     @count.times { sock.syswrite(buf) }
-    read = sock.read.split(/\r\n/)
-    assert_equal "HTTP/1.1 200 OK", read[0]
-    resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
-    new_tmp = File.open(resp[:new_tmp])
-    assert_equal resp[:inode], new_tmp.stat.ino
-    assert_equal length, resp[:size]
-    assert ! File.exist?(resp[:old_tmp])
-    assert_equal resp[:size], new_tmp.stat.size
+    assert_raise(Errno::ECONNRESET, Errno::EPIPE) do
+      ::Unicorn::Const::CHUNK_SIZE.times { sock.syswrite(buf) }
+    end
+    assert_equal "HTTP/1.1 200 OK\r\n", sock.gets
   end
 
   # Despite reading numerous articles and inspecting the 1.9.1-p0 C
@@ -233,7 +185,6 @@ class UploadTest < Test::Unit::TestCase
     resp = `curl -isSfN -T#{tmp.path} http://#@addr:#@port/`
     assert $?.success?, 'curl ran OK'
     assert_match(%r!\b#{sha1}\b!, resp)
-    assert_match(/Tempfile/, resp)
     assert_match(/sysread_read_byte_match/, resp)
 
     # small StringIO path
@@ -249,10 +200,87 @@ class UploadTest < Test::Unit::TestCase
     resp = `curl -isSfN -T#{tmp.path} http://#@addr:#@port/`
     assert $?.success?, 'curl ran OK'
     assert_match(%r!\b#{sha1}\b!, resp)
-    assert_match(/StringIO/, resp)
     assert_match(/sysread_read_byte_match/, resp)
   end
 
+  def test_chunked_upload_via_curl
+    # POSIX doesn't require all of these to be present on a system
+    which('curl') or return
+    which('sha1sum') or return
+    which('dd') or return
+
+    start_server(@sha1_app)
+
+    tmp = Tempfile.new('dd_dest')
+    assert(system("dd", "if=#{@random.path}", "of=#{tmp.path}",
+                        "bs=#{@bs}", "count=#{@count}"),
+           "dd #@random to #{tmp}")
+    sha1_re = %r!\b([a-f0-9]{40})\b!
+    sha1_out = `sha1sum #{tmp.path}`
+    assert $?.success?, 'sha1sum ran OK'
+
+    assert_match(sha1_re, sha1_out)
+    sha1 = sha1_re.match(sha1_out)[1]
+    cmd = "curl -H 'X-Expect-Size: #{tmp.size}' --tcp-nodelay \
+           -isSf --no-buffer -T- " \
+          "http://#@addr:#@port/"
+    resp = Tempfile.new('resp')
+    resp.sync = true
+
+    rd, wr = IO.pipe
+    wr.sync = rd.sync = true
+    pid = fork {
+      STDIN.reopen(rd)
+      rd.close
+      wr.close
+      STDOUT.reopen(resp)
+      exec cmd
+    }
+    rd.close
+
+    tmp.rewind
+    @count.times { |i|
+      wr.write(tmp.read(@bs))
+      sleep(rand / 10) if 0 == i % 8
+    }
+    wr.close
+    pid, status = Process.waitpid2(pid)
+
+    resp.rewind
+    resp = resp.read
+    assert status.success?, 'curl ran OK'
+    assert_match(%r!\b#{sha1}\b!, resp)
+    assert_match(/sysread_read_byte_match/, resp)
+    assert_match(/expect_size_match/, resp)
+  end
+
+  def test_curl_chunked_small
+    # POSIX doesn't require all of these to be present on a system
+    which('curl') or return
+    which('sha1sum') or return
+    which('dd') or return
+
+    start_server(@sha1_app)
+
+    tmp = Tempfile.new('dd_dest')
+    # small StringIO path
+    assert(system("dd", "if=#{@random.path}", "of=#{tmp.path}",
+                        "bs=1024", "count=1"),
+           "dd #@random to #{tmp}")
+    sha1_re = %r!\b([a-f0-9]{40})\b!
+    sha1_out = `sha1sum #{tmp.path}`
+    assert $?.success?, 'sha1sum ran OK'
+
+    assert_match(sha1_re, sha1_out)
+    sha1 = sha1_re.match(sha1_out)[1]
+    resp = `curl -H 'X-Expect-Size: #{tmp.size}' --tcp-nodelay \
+            -isSf --no-buffer -T- http://#@addr:#@port/ < #{tmp.path}`
+    assert $?.success?, 'curl ran OK'
+    assert_match(%r!\b#{sha1}\b!, resp)
+    assert_match(/sysread_read_byte_match/, resp)
+    assert_match(/expect_size_match/, resp)
+  end
+
   private
 
   def length
diff --git a/test/unit/test_util.rb b/test/unit/test_util.rb
index 032f0be..4a1e21f 100644
--- a/test/unit/test_util.rb
+++ b/test/unit/test_util.rb
@@ -1,3 +1,5 @@
+# -*- encoding: binary -*-
+
 require 'test/test_helper'
 require 'tempfile'
 
@@ -15,6 +17,7 @@ class TestUtil < Test::Unit::TestCase
     assert_equal before, File.stat(tmp.path).inspect
     assert_equal ext, (tmp.external_encoding rescue nil)
     assert_equal int, (tmp.internal_encoding rescue nil)
+    assert_nothing_raised { tmp.close! }
   end
 
   def test_reopen_logs_renamed
@@ -37,6 +40,8 @@ class TestUtil < Test::Unit::TestCase
     assert_equal int, (tmp.internal_encoding rescue nil)
     assert_equal(EXPECT_FLAGS, EXPECT_FLAGS & tmp.fcntl(Fcntl::F_GETFL))
     assert tmp.sync
+    assert_nothing_raised { tmp.close! }
+    assert_nothing_raised { to.close! }
   end
 
   def test_reopen_logs_renamed_with_encoding
@@ -59,6 +64,7 @@ class TestUtil < Test::Unit::TestCase
         assert fp.sync
       }
     }
+    assert_nothing_raised { tmp.close! }
   end if STDIN.respond_to?(:external_encoding)
 
   def test_reopen_logs_renamed_with_internal_encoding
@@ -84,6 +90,7 @@ class TestUtil < Test::Unit::TestCase
         }
       }
     }
+    assert_nothing_raised { tmp.close! }
   end if STDIN.respond_to?(:external_encoding)
 
 end