about summary refs log tree commit homepage
path: root/test/unit
diff options
context:
space:
mode:
authorevanweaver <evanweaver@19e92222-5c0b-0410-8929-a290d50e31e9>2008-02-20 06:15:30 +0000
committerevanweaver <evanweaver@19e92222-5c0b-0410-8929-a290d50e31e9>2008-02-20 06:15:30 +0000
commit5a10ba13b2c2f56b8ffb0978ebb36ff26a7715e5 (patch)
treee93ee8070152cb99b3f4bfde01e776677ca6d0a4 /test/unit
parentaf1e1581afac9437e623eb54ef533f240858fd47 (diff)
downloadunicorn-5a10ba13b2c2f56b8ffb0978ebb36ff26a7715e5.tar.gz
git-svn-id: svn+ssh://rubyforge.org/var/svn/mongrel/branches/stable_1-1@973 19e92222-5c0b-0410-8929-a290d50e31e9
Diffstat (limited to 'test/unit')
-rw-r--r--test/unit/test_cgi_wrapper.rb26
-rw-r--r--test/unit/test_command.rb86
-rw-r--r--test/unit/test_conditional.rb107
-rw-r--r--test/unit/test_configurator.rb87
-rw-r--r--test/unit/test_debug.rb25
-rw-r--r--test/unit/test_handlers.rb123
-rw-r--r--test/unit/test_http11.rb156
-rw-r--r--test/unit/test_redirect_handler.rb44
-rw-r--r--test/unit/test_request_progress.rb99
-rw-r--r--test/unit/test_response.rb127
-rw-r--r--test/unit/test_stats.rb35
-rw-r--r--test/unit/test_uriclassifier.rb261
-rw-r--r--test/unit/test_ws.rb115
13 files changed, 1291 insertions, 0 deletions
diff --git a/test/unit/test_cgi_wrapper.rb b/test/unit/test_cgi_wrapper.rb
new file mode 100644
index 0000000..a494655
--- /dev/null
+++ b/test/unit/test_cgi_wrapper.rb
@@ -0,0 +1,26 @@
+
+require 'test/test_helper'
+
+class MockHttpRequest
+  attr_reader :body
+
+  def params
+    return { 'REQUEST_METHOD' => 'GET'}
+  end
+end
+
+class CGIWrapperTest < Test::Unit::TestCase
+  
+  def test_set_cookies_output_cookies
+    request = MockHttpRequest.new
+    response = nil # not needed for this test
+    output_headers = {}
+    
+    cgi = Mongrel::CGIWrapper.new(request, response)
+    session = CGI::Session.new(cgi, 'database_manager' => CGI::Session::MemoryStore)
+    cgi.send_cookies(output_headers)
+    
+    assert(output_headers.has_key?("Set-Cookie"))
+    assert_equal("_session_id="+session.session_id+"; path=", output_headers["Set-Cookie"])
+  end
+end \ No newline at end of file
diff --git a/test/unit/test_command.rb b/test/unit/test_command.rb
new file mode 100644
index 0000000..2e49ff2
--- /dev/null
+++ b/test/unit/test_command.rb
@@ -0,0 +1,86 @@
+# Copyright (c) 2005 Zed A. Shaw
+# You can redistribute it and/or modify it under the same terms as Ruby.
+#
+# Additional work donated by contributors.  See http://mongrel.rubyforge.org/attributions.html
+# for more information.
+
+require 'test/test_helper'
+
+class TestCommand < GemPlugin::Plugin "/commands"
+  include Mongrel::Command::Base
+
+  def configure
+    options [
+      ["-e", "--environment ENV", "Rails environment to run as", :@environment, ENV['RAILS_ENV'] || "development"],
+      ['', '--user USER', "User to run as", :@user, nil],
+      ["-d", "--daemonize", "Whether to run in the background or not", :@daemon, false],
+      ["-x", "--test", "Used to let the test run failures", :@test, false],
+    ]
+  end
+
+  def validate
+    valid_dir? ".", "Can't validate current directory."
+    valid_exists? "Rakefile", "Rakefile not there, test is invalid."
+    if @test
+      valid_exist? "BADFILE", "Yeah, badfile"
+      valid_file? "BADFILE", "Not even a file"
+      valid_dir? "BADDIR", "No dir here"
+      valid? false, "Total failure"
+    end
+
+    return @valid
+  end
+
+
+  def run
+    $test_command_ran = true
+  end
+end
+
+class CommandTest < Test::Unit::TestCase
+
+  def setup
+    $test_command_ran = false
+  end
+
+  def teardown
+  end
+
+  def run_cmd(args)
+    Mongrel::Command::Registry.instance.run args
+  end
+
+  def test_run_command
+    redirect_test_io do
+      run_cmd ["testcommand"]
+      assert $test_command_ran, "command didn't run"
+    end
+  end
+
+  def test_command_error
+    redirect_test_io do
+      run_cmd ["crapcommand"]
+    end
+  end
+
+  def test_command_listing
+    redirect_test_io do
+      run_cmd ["help"]
+    end
+  end
+
+  def test_options
+    redirect_test_io do
+      run_cmd ["testcommand","-h"]
+      run_cmd ["testcommand","--help"]
+      run_cmd ["testcommand","-e","test","-d","--user"]
+    end
+  end
+
+  def test_version
+    redirect_test_io do
+      run_cmd ["testcommand", "--version"]
+    end
+  end
+
+end
diff --git a/test/unit/test_conditional.rb b/test/unit/test_conditional.rb
new file mode 100644
index 0000000..769dd91
--- /dev/null
+++ b/test/unit/test_conditional.rb
@@ -0,0 +1,107 @@
+# Copyright (c) 2005 Zed A. Shaw
+# You can redistribute it and/or modify it under the same terms as Ruby.
+#
+# Additional work donated by contributors.  See http://mongrel.rubyforge.org/attributions.html
+# for more information.
+
+require 'test/test_helper'
+
+include Mongrel
+
+class ConditionalResponseTest < Test::Unit::TestCase
+  def setup
+    @server = HttpServer.new('127.0.0.1', 3501)
+    @server.register('/', Mongrel::DirHandler.new('.'))
+    @server.run
+    
+    @http = Net::HTTP.new(@server.host, @server.port)
+
+    # get the ETag and Last-Modified headers
+    @path = '/README'
+    res = @http.start { |http| http.get(@path) }
+    assert_not_nil @etag = res['ETag']
+    assert_not_nil @last_modified = res['Last-Modified']
+    assert_not_nil @content_length = res['Content-Length']
+  end
+
+  def teardown
+    @server.stop(true)
+  end
+
+  # status should be 304 Not Modified when If-None-Match is the matching ETag
+  def test_not_modified_via_if_none_match
+    assert_status_for_get_and_head Net::HTTPNotModified, 'If-None-Match' => @etag
+  end
+
+  # status should be 304 Not Modified when If-Modified-Since is the matching Last-Modified date
+  def test_not_modified_via_if_modified_since
+    assert_status_for_get_and_head Net::HTTPNotModified, 'If-Modified-Since' => @last_modified
+  end
+
+  # status should be 304 Not Modified when If-None-Match is the matching ETag
+  # and If-Modified-Since is the matching Last-Modified date
+  def test_not_modified_via_if_none_match_and_if_modified_since
+    assert_status_for_get_and_head Net::HTTPNotModified, 'If-None-Match' => @etag, 'If-Modified-Since' => @last_modified
+  end
+
+  # status should be 200 OK when If-None-Match is invalid
+  def test_invalid_if_none_match
+    assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => 'invalid'
+    assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => 'invalid', 'If-Modified-Since' => @last_modified
+  end
+
+  # status should be 200 OK when If-Modified-Since is invalid
+  def test_invalid_if_modified_since
+    assert_status_for_get_and_head Net::HTTPOK,                           'If-Modified-Since' => 'invalid'
+    assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => @etag, 'If-Modified-Since' => 'invalid'
+  end
+
+  # status should be 304 Not Modified when If-Modified-Since is greater than the Last-Modified header, but less than the system time
+  def test_if_modified_since_greater_than_last_modified
+    sleep 2
+    last_modified_plus_1 = (Time.httpdate(@last_modified) + 1).httpdate
+    assert_status_for_get_and_head Net::HTTPNotModified,                           'If-Modified-Since' => last_modified_plus_1
+    assert_status_for_get_and_head Net::HTTPNotModified, 'If-None-Match' => @etag, 'If-Modified-Since' => last_modified_plus_1
+  end
+
+  # status should be 200 OK when If-Modified-Since is less than the Last-Modified header
+  def test_if_modified_since_less_than_last_modified
+    last_modified_minus_1 = (Time.httpdate(@last_modified) - 1).httpdate
+    assert_status_for_get_and_head Net::HTTPOK,                           'If-Modified-Since' => last_modified_minus_1
+    assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => @etag, 'If-Modified-Since' => last_modified_minus_1
+  end
+
+  # status should be 200 OK when If-Modified-Since is a date in the future
+  def test_future_if_modified_since
+    the_future = Time.at(2**31-1).httpdate
+    assert_status_for_get_and_head Net::HTTPOK,                           'If-Modified-Since' => the_future
+    assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => @etag, 'If-Modified-Since' => the_future
+  end
+
+  # status should be 200 OK when If-None-Match is a wildcard
+  def test_wildcard_match
+    assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => '*'
+    assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => '*', 'If-Modified-Since' => @last_modified
+  end
+
+  private
+
+    # assert the response status is correct for GET and HEAD
+    def assert_status_for_get_and_head(response_class, headers = {})
+      %w{ get head }.each do |method|
+        res = @http.send(method, @path, headers)
+        assert_kind_of response_class, res
+        assert_equal @etag, res['ETag']
+        case response_class.to_s
+          when 'Net::HTTPNotModified' then
+            assert_nil res['Last-Modified']
+            assert_nil res['Content-Length']
+          when 'Net::HTTPOK' then
+            assert_equal @last_modified, res['Last-Modified']
+            assert_equal @content_length, res['Content-Length']
+          else
+            fail "Incorrect response class: #{response_class}"
+        end
+      end
+    end
+end
diff --git a/test/unit/test_configurator.rb b/test/unit/test_configurator.rb
new file mode 100644
index 0000000..f55a8ae
--- /dev/null
+++ b/test/unit/test_configurator.rb
@@ -0,0 +1,87 @@
+# Copyright (c) 2005 Zed A. Shaw
+# You can redistribute it and/or modify it under the same terms as Ruby.
+#
+# Additional work donated by contributors.  See http://mongrel.rubyforge.org/attributions.html
+# for more information.
+
+require 'test/test_helper'
+
+$test_plugin_fired = 0
+
+class TestPlugin < GemPlugin::Plugin "/handlers"
+  include Mongrel::HttpHandlerPlugin
+
+  def process(request, response)
+    $test_plugin_fired += 1
+  end
+end
+
+
+class Sentinel < GemPlugin::Plugin "/handlers"
+  include Mongrel::HttpHandlerPlugin
+
+  def process(request, response)
+    raise "This Sentinel plugin shouldn't run."
+  end
+end
+
+
+class ConfiguratorTest < Test::Unit::TestCase
+
+  def test_base_handler_config
+    @config = nil
+
+    redirect_test_io do
+      @config = Mongrel::Configurator.new :host => "localhost" do
+        listener :port => 4501 do
+          # 2 in front should run, but the sentinel shouldn't since dirhandler processes the request
+          uri "/", :handler => plugin("/handlers/testplugin")
+          uri "/", :handler => plugin("/handlers/testplugin")
+          uri "/", :handler => Mongrel::DirHandler.new(".")
+          uri "/", :handler => plugin("/handlers/testplugin")
+
+          uri "/test", :handler => plugin("/handlers/testplugin")
+          uri "/test", :handler => plugin("/handlers/testplugin")
+          uri "/test", :handler => Mongrel::DirHandler.new(".")
+          uri "/test", :handler => plugin("/handlers/testplugin")
+
+          debug "/"
+          setup_signals
+
+          run_config(HERE + "/mongrel.conf")
+          load_mime_map(HERE + "/mime.yaml")
+
+          run
+        end
+      end
+    end
+
+    # pp @config.listeners.values.first.classifier.routes
+
+    @config.listeners.each do |host,listener|
+      assert listener.classifier.uris.length == 3, "Wrong number of registered URIs"
+      assert listener.classifier.uris.include?("/"),  "/ not registered"
+      assert listener.classifier.uris.include?("/test"), "/test not registered"
+    end
+
+    res = Net::HTTP.get(URI.parse('http://localhost:4501/test'))
+    assert res != nil, "Didn't get a response"
+    assert $test_plugin_fired == 3, "Test filter plugin didn't run 3 times."
+
+    redirect_test_io do
+      res = Net::HTTP.get(URI.parse('http://localhost:4501/'))
+
+      assert res != nil, "Didn't get a response"
+      assert $test_plugin_fired == 6, "Test filter plugin didn't run 6 times."
+    end
+
+    redirect_test_io do
+      @config.stop(false, true)
+    end
+
+    assert_raise Errno::EBADF, Errno::ECONNREFUSED do
+      res = Net::HTTP.get(URI.parse("http://localhost:4501/"))
+    end
+  end
+
+end
diff --git a/test/unit/test_debug.rb b/test/unit/test_debug.rb
new file mode 100644
index 0000000..05d92d8
--- /dev/null
+++ b/test/unit/test_debug.rb
@@ -0,0 +1,25 @@
+# Copyright (c) 2005 Zed A. Shaw
+# You can redistribute it and/or modify it under the same terms as Ruby.
+#
+# Additional work donated by contributors.  See http://mongrel.rubyforge.org/attributions.html
+# for more information.
+
+require 'test/test_helper'
+require 'mongrel/debug'
+
+class MongrelDbgTest < Test::Unit::TestCase
+
+  def test_tracing_to_log
+    FileUtils.rm_rf "log/mongrel_debug"
+
+    MongrelDbg::configure
+    out = StringIO.new
+
+    MongrelDbg::begin_trace(:rails)
+    MongrelDbg::trace(:rails, "Good stuff")
+    MongrelDbg::end_trace(:rails)
+
+    assert File.exist?("log/mongrel_debug"), "Didn't make logging directory"
+  end
+
+end
diff --git a/test/unit/test_handlers.rb b/test/unit/test_handlers.rb
new file mode 100644
index 0000000..1d316e5
--- /dev/null
+++ b/test/unit/test_handlers.rb
@@ -0,0 +1,123 @@
+# Copyright (c) 2005 Zed A. Shaw
+# You can redistribute it and/or modify it under the same terms as Ruby.
+#
+# Additional work donated by contributors.  See http://mongrel.rubyforge.org/attributions.html
+# for more information.
+
+require 'test/test_helper'
+
+class SimpleHandler < Mongrel::HttpHandler
+  def process(request, response)
+    response.start do |head,out|
+      head["Content-Type"] = "text/html"
+      results = "<html><body>Your request:<br /><pre>#{request.params.to_yaml}</pre><a href=\"/files\">View the files.</a></body></html>"
+      out << results
+    end
+  end
+end
+
+class DumbHandler < Mongrel::HttpHandler
+  def process(request, response)
+    response.start do |head,out|
+      head["Content-Type"] = "text/html"
+      out.write("test")
+    end
+  end
+end
+
+def check_status(results, expecting)
+  results.each do |res|
+    assert(res.kind_of?(expecting), "Didn't get #{expecting}, got: #{res.class}")
+  end
+end
+
+class HandlersTest < Test::Unit::TestCase
+
+  def setup
+    stats = Mongrel::StatisticsFilter.new(:sample_rate => 1)
+
+    @config = Mongrel::Configurator.new :host => '127.0.0.1', :port => 9998 do
+      listener do
+        uri "/", :handler => SimpleHandler.new
+        uri "/", :handler => stats
+        uri "/404", :handler => Mongrel::Error404Handler.new("Not found")
+        uri "/dumb", :handler => Mongrel::DeflateFilter.new
+        uri "/dumb", :handler => DumbHandler.new, :in_front => true
+        uri "/files", :handler => Mongrel::DirHandler.new("doc")
+        uri "/files_nodir", :handler => Mongrel::DirHandler.new("doc", listing_allowed=false, index_html="none")
+        uri "/status", :handler => Mongrel::StatusHandler.new(:stats_filter => stats)
+        uri "/relative", :handler => Mongrel::DirHandler.new(nil, listing_allowed=false, index_html="none")
+      end
+    end
+    
+    File.open("/tmp/testfile", 'w') do
+      # Do nothing
+    end
+    
+    @config.run
+  end
+
+  def teardown
+    @config.stop(false, true)
+    File.delete "/tmp/testfile"
+  end
+
+  def test_more_web_server
+    res = hit([ "http://localhost:9998/test",
+          "http://localhost:9998/dumb",
+          "http://localhost:9998/404",
+          "http://localhost:9998/files/rdoc/index.html",
+          "http://localhost:9998/files/rdoc/nothere.html",
+          "http://localhost:9998/files/rdoc/",
+          "http://localhost:9998/files_nodir/rdoc/",
+          "http://localhost:9998/status",
+    ])
+    check_status res, String
+  end
+  
+  def test_nil_dirhandler
+    # Camping uses this internally
+    handler = Mongrel::DirHandler.new(nil, false)  
+    assert handler.can_serve("/tmp/testfile")
+    # Not a bug! A nil @file parameter is the only circumstance under which
+    # we are allowed to serve any existing file
+    assert handler.can_serve("../../../../../../../../../../tmp/testfile")
+  end
+  
+  def test_non_nil_dirhandler_is_not_vulnerable_to_path_traversal
+    # The famous security bug of Mongrel 1.1.2
+    handler = Mongrel::DirHandler.new("/doc", false)
+    assert_nil handler.can_serve("/tmp/testfile")
+    assert_nil handler.can_serve("../../../../../../../../../../tmp/testfile")
+  end
+
+  def test_deflate
+    Net::HTTP.start("localhost", 9998) do |h|
+      # Test that no accept-encoding returns a non-deflated response
+      req = h.get("/dumb")
+      assert(
+        !req['Content-Encoding'] ||
+        !req['Content-Encoding'].include?('deflate'))
+      assert_equal "test", req.body
+
+      req = h.get("/dumb", {"Accept-Encoding" => "deflate"})
+      # -MAX_WBITS stops zlib from looking for a zlib header
+      inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
+      assert req['Content-Encoding'].include?('deflate')
+      assert_equal "test", inflater.inflate(req.body)
+    end
+  end
+
+  # TODO: find out why this fails on win32 but nowhere else
+  #def test_posting_fails_dirhandler
+  #  req = Net::HTTP::Post.new("http://localhost:9998/files/rdoc/")
+  #  req.set_form_data({'from'=>'2005-01-01', 'to'=>'2005-03-31'}, ';')
+  #  res = hit [["http://localhost:9998/files/rdoc/",req]]
+  #  check_status res, Net::HTTPNotFound
+  #end
+
+  def test_unregister
+    @config.listeners["127.0.0.1:9998"].unregister("/")
+  end
+end
+
diff --git a/test/unit/test_http11.rb b/test/unit/test_http11.rb
new file mode 100644
index 0000000..e083abf
--- /dev/null
+++ b/test/unit/test_http11.rb
@@ -0,0 +1,156 @@
+# Copyright (c) 2005 Zed A. Shaw
+# You can redistribute it and/or modify it under the same terms as Ruby.
+#
+# Additional work donated by contributors.  See http://mongrel.rubyforge.org/attributions.html
+# for more information.
+
+require 'test/test_helper'
+
+include Mongrel
+
+class HttpParserTest < Test::Unit::TestCase
+    
+  def test_parse_simple
+    parser = HttpParser.new
+    req = {}
+    http = "GET / HTTP/1.1\r\n\r\n"
+    nread = parser.execute(req, http, 0)
+
+    assert nread == http.length, "Failed to parse the full HTTP request"
+    assert parser.finished?, "Parser didn't finish"
+    assert !parser.error?, "Parser had error"
+    assert nread == parser.nread, "Number read returned from execute does not match"
+
+    assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
+    assert_equal '/', req['REQUEST_PATH']
+    assert_equal 'HTTP/1.1', req['HTTP_VERSION']
+    assert_equal '/', req['REQUEST_URI']
+    assert_equal 'CGI/1.2', req['GATEWAY_INTERFACE']
+    assert_equal 'GET', req['REQUEST_METHOD']    
+    assert_nil req['FRAGMENT']
+    assert_nil req['QUERY_STRING']
+    
+    parser.reset
+    assert parser.nread == 0, "Number read after reset should be 0"
+  end
+
+  def test_parse_dumbfuck_headers
+    parser = HttpParser.new
+    req = {}
+    should_be_good = "GET / HTTP/1.1\r\naaaaaaaaaaaaa:++++++++++\r\n\r\n"
+    nread = parser.execute(req, should_be_good, 0)
+    assert_equal should_be_good.length, nread
+    assert parser.finished?
+    assert !parser.error?
+
+    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 = {}
+    #nread = parser.execute(req, nasty_pound_header, 0)
+    #assert_equal nasty_pound_header.length, nread
+    #assert parser.finished?
+    #assert !parser.error?
+  end
+  
+  def test_parse_error
+    parser = HttpParser.new
+    req = {}
+    bad_http = "GET / SsUTF/1.1"
+
+    error = false
+    begin
+      nread = parser.execute(req, bad_http, 0)
+    rescue => details
+      error = true
+    end
+
+    assert error, "failed to throw exception"
+    assert !parser.finished?, "Parser shouldn't be finished"
+    assert parser.error?, "Parser SHOULD have error"
+  end
+
+  def test_fragment_in_uri
+    parser = HttpParser.new
+    req = {}
+    get = "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
+    assert_nothing_raised do
+      parser.execute(req, get, 0)
+    end
+    assert parser.finished?
+    assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
+    assert_equal 'posts-17408', req['FRAGMENT']
+  end
+
+  # lame random garbage maker
+  def rand_data(min, max, readable=true)
+    count = min + ((rand(max)+1) *10).to_i
+    res = count.to_s + "/"
+    
+    if readable
+      res << Digest::SHA1.hexdigest(rand(count * 100).to_s) * (count / 40)
+    else
+      res << Digest::SHA1.digest(rand(count * 100).to_s) * (count / 20)
+    end
+
+    return res
+  end
+  
+
+  def test_horrible_queries
+    parser = HttpParser.new
+
+    # then that large header names are caught
+    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 Mongrel::HttpParserError do
+        parser.execute({}, get, 0)
+        parser.reset
+      end
+    end
+
+    # then that large mangled field values are caught
+    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 Mongrel::HttpParserError do
+        parser.execute({}, get, 0)
+        parser.reset
+      end
+    end
+
+    # then large headers are rejected too
+    get = "GET /#{rand_data(10,120)} HTTP/1.1\r\n"
+    get << "X-Test: test\r\n" * (80 * 1024)
+    assert_raises Mongrel::HttpParserError do
+      parser.execute({}, get, 0)
+      parser.reset
+    end
+
+    # finally just that random garbage gets blocked all the time
+    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 Mongrel::HttpParserError do
+        parser.execute({}, get, 0)
+        parser.reset
+      end
+    end
+
+  end
+
+
+
+  def test_query_parse
+    res = HttpRequest.query_parse("zed=1&frank=#{HttpRequest.escape('&&& ')}")
+    assert res["zed"], "didn't get the request right"
+    assert res["frank"], "no frank"
+    assert_equal "1", res["zed"], "wrong result"
+    assert_equal "&&& ", HttpRequest.unescape(res["frank"]), "wrong result"
+
+    res = HttpRequest.query_parse("zed=1&zed=2&zed=3&frank=11;zed=45")
+    assert res["zed"], "didn't get the request right"
+    assert res["frank"], "no frank"
+    assert_equal 4,res["zed"].length, "wrong number for zed"
+    assert_equal "11",res["frank"], "wrong number for frank"
+  end
+  
+end
+
diff --git a/test/unit/test_redirect_handler.rb b/test/unit/test_redirect_handler.rb
new file mode 100644
index 0000000..3207b46
--- /dev/null
+++ b/test/unit/test_redirect_handler.rb
@@ -0,0 +1,44 @@
+# Copyright (c) 2005 Zed A. Shaw
+# You can redistribute it and/or modify it under the same terms as Ruby.
+#
+# Additional work donated by contributors.  See http://mongrel.rubyforge.org/attributions.html
+# for more information.
+
+require 'test/test_helper'
+
+class RedirectHandlerTest < Test::Unit::TestCase
+
+  def setup
+    redirect_test_io do
+      @server = Mongrel::HttpServer.new('127.0.0.1', 9998)
+    end
+    @server.run
+    @client = Net::HTTP.new('127.0.0.1', 9998)
+  end
+
+  def teardown
+    @server.stop(true)
+  end
+
+  def test_simple_redirect
+    tester = Mongrel::RedirectHandler.new('/yo')
+    @server.register("/test", tester)
+
+    sleep(1)
+    res = @client.request_get('/test')
+    assert res != nil, "Didn't get a response"
+    assert_equal ['/yo'], res.get_fields('Location')
+  end
+
+  def test_rewrite
+    tester = Mongrel::RedirectHandler.new(/(\w+)/, '+\1+')
+    @server.register("/test", tester)
+
+    sleep(1)
+    res = @client.request_get('/test/something')
+    assert_equal ['/+test+/+something+'], res.get_fields('Location')
+  end
+
+end
+
+
diff --git a/test/unit/test_request_progress.rb b/test/unit/test_request_progress.rb
new file mode 100644
index 0000000..da9374e
--- /dev/null
+++ b/test/unit/test_request_progress.rb
@@ -0,0 +1,99 @@
+# Copyright (c) 2005 Zed A. Shaw
+# You can redistribute it and/or modify it under the same terms as Ruby.
+#
+# Additional work donated by contributors.  See http://mongrel.rubyforge.org/attributions.html
+# for more information.
+
+require 'test/test_helper'
+
+class UploadBeginHandler < Mongrel::HttpHandler
+  attr_reader :request_began, :request_progressed, :request_processed
+
+  def initialize
+    @request_notify = true
+  end
+
+  def reset
+    @request_began = false
+    @request_progressed = false
+    @request_processed = false
+  end
+
+  def request_begins(params)
+    @request_began = true
+  end
+
+  def request_progress(params,len,total)
+    @request_progressed = true
+  end
+
+  def process(request, response)
+    @request_processed = true
+    response.start do |head,body|
+      body.write("test")
+    end
+  end
+
+end
+
+class RequestProgressTest < Test::Unit::TestCase
+  def setup
+    redirect_test_io do
+      @server = Mongrel::HttpServer.new("127.0.0.1", 9998)
+    end
+    @handler = UploadBeginHandler.new
+    @server.register("/upload", @handler)
+    @server.run
+  end
+
+  def teardown
+    @server.stop(true)
+  end
+
+  def test_begin_end_progress
+    Net::HTTP.get("localhost", "/upload", 9998)
+    assert @handler.request_began
+    assert @handler.request_progressed
+    assert @handler.request_processed
+  end
+
+  def call_and_assert_handlers_in_turn(handlers)
+    # reset all handlers
+    handlers.each { |h| h.reset }
+
+    # make the call
+    Net::HTTP.get("localhost", "/upload", 9998)
+
+    # assert that each one was fired
+    handlers.each { |h|
+      assert h.request_began && h.request_progressed && h.request_processed,
+        "Callbacks NOT fired for #{h}"
+    }
+  end
+
+  def test_more_than_one_begin_end_progress
+    handlers = [@handler]
+
+    second = UploadBeginHandler.new
+    @server.register("/upload", second)
+    handlers << second
+    call_and_assert_handlers_in_turn(handlers)
+
+    # check three handlers
+    third = UploadBeginHandler.new
+    @server.register("/upload", third)
+    handlers << third
+    call_and_assert_handlers_in_turn(handlers)
+
+    # remove handlers to make sure they've all gone away
+    @server.unregister("/upload")
+    handlers.each { |h| h.reset }
+    Net::HTTP.get("localhost", "/upload", 9998)
+    handlers.each { |h|
+      assert !h.request_began && !h.request_progressed && !h.request_processed
+    }
+
+    # re-register upload to the state before this test
+    @server.register("/upload", @handler)
+  end
+end
diff --git a/test/unit/test_response.rb b/test/unit/test_response.rb
new file mode 100644
index 0000000..b49c9df
--- /dev/null
+++ b/test/unit/test_response.rb
@@ -0,0 +1,127 @@
+# Copyright (c) 2005 Zed A. Shaw
+# You can redistribute it and/or modify it under the same terms as Ruby.
+#
+# Additional work donated by contributors.  See http://mongrel.rubyforge.org/attributions.html
+# for more information.
+
+require 'test/test_helper'
+
+include Mongrel
+
+class ResponseTest < Test::Unit::TestCase
+  
+  def test_response_headers
+    out = StringIO.new
+    resp = HttpResponse.new(out)
+    resp.status = 200
+    resp.header["Accept"] = "text/plain"
+    resp.header["X-Whatever"] = "stuff"
+    resp.body.write("test")
+    resp.finished
+
+    assert out.length > 0, "output didn't have data"
+  end
+
+  def test_response_200
+    io = StringIO.new
+    resp = HttpResponse.new(io)
+    resp.start do |head,out|
+      head["Accept"] = "text/plain"
+      out.write("tested")
+      out.write("hello!")
+    end
+
+    resp.finished
+    assert io.length > 0, "output didn't have data"
+  end
+
+  def test_response_duplicate_header_squash
+    io = StringIO.new
+    resp = HttpResponse.new(io)
+    resp.start do |head,out|
+      head["Content-Length"] = 30
+      head["Content-Length"] = 0
+    end
+
+    resp.finished
+
+    assert_equal io.length, 95, "too much output"
+  end
+
+
+  def test_response_some_duplicates_allowed
+    allowed_duplicates = ["Set-Cookie", "Set-Cookie2", "Warning", "WWW-Authenticate"]
+    io = StringIO.new
+    resp = HttpResponse.new(io)
+    resp.start do |head,out|
+      allowed_duplicates.each do |dup|
+        10.times do |i|
+          head[dup] = i
+        end
+      end
+    end
+
+    resp.finished
+
+    assert_equal io.length, 734, "wrong amount of output"
+  end
+
+  def test_response_404
+    io = StringIO.new
+
+    resp = HttpResponse.new(io)
+    resp.start(404) do |head,out|
+      head['Accept'] = "text/plain"
+      out.write("NOT FOUND")
+    end
+
+    resp.finished
+    assert io.length > 0, "output didn't have data"
+  end
+
+  def test_response_file
+    contents = "PLAIN TEXT\r\nCONTENTS\r\n"
+    require 'tempfile'
+    tmpf = Tempfile.new("test_response_file")
+    tmpf.binmode
+    tmpf.write(contents)
+    tmpf.rewind
+
+    io = StringIO.new
+    resp = HttpResponse.new(io)
+    resp.start(200) do |head,out|
+      head['Content-Type'] = 'text/plain'
+      resp.send_header
+      resp.send_file(tmpf.path)
+    end
+    io.rewind
+    tmpf.close
+    
+    assert io.length > 0, "output didn't have data"
+    assert io.read[-contents.length..-1] == contents, "output doesn't end with file payload"
+  end
+
+  def test_response_with_custom_reason
+    reason = "You made a bad request"
+    io = StringIO.new
+    resp = HttpResponse.new(io)
+    resp.start(400, false, reason) { |head,out| }
+    resp.finished
+
+    io.rewind
+    assert_match(/.* #{reason}$/, io.readline.chomp, "wrong custom reason phrase")
+  end
+
+  def test_response_with_default_reason
+    code = 400
+    io = StringIO.new
+    resp = HttpResponse.new(io)
+    resp.start(code) { |head,out| }
+    resp.finished
+
+    io.rewind
+    assert_match(/.* #{HTTP_STATUS_CODES[code]}$/, io.readline.chomp, "wrong default reason phrase")
+  end
+
+end
+
diff --git a/test/unit/test_stats.rb b/test/unit/test_stats.rb
new file mode 100644
index 0000000..012c6a5
--- /dev/null
+++ b/test/unit/test_stats.rb
@@ -0,0 +1,35 @@
+# Copyright (c) 2005 Zed A. Shaw
+# You can redistribute it and/or modify it under the same terms as Ruby.
+#
+# Additional work donated by contributors.  See http://mongrel.rubyforge.org/attributions.html
+# for more information.
+
+require 'test/test_helper'
+
+class StatsTest < Test::Unit::TestCase
+
+  def test_sampling_speed
+    out = StringIO.new
+
+    s = Mongrel::Stats.new("test")
+    t = Mongrel::Stats.new("time")
+
+    100.times { s.sample(rand(20)); t.tick }
+
+    s.dump("FIRST", out)
+    t.dump("FIRST", out)
+    
+    old_mean = s.mean
+    old_sd = s.sd
+
+    s.reset
+    t.reset
+    100.times { s.sample(rand(30)); t.tick }
+    
+    s.dump("SECOND", out)
+    t.dump("SECOND", out)
+    assert_not_equal old_mean, s.mean
+    assert_not_equal old_mean, s.sd    
+  end
+
+end
diff --git a/test/unit/test_uriclassifier.rb b/test/unit/test_uriclassifier.rb
new file mode 100644
index 0000000..a438065
--- /dev/null
+++ b/test/unit/test_uriclassifier.rb
@@ -0,0 +1,261 @@
+# Copyright (c) 2005 Zed A. Shaw
+# You can redistribute it and/or modify it under the same terms as Ruby.
+#
+# Additional work donated by contributors.  See http://mongrel.rubyforge.org/attributions.html
+# for more information.
+
+require 'test/test_helper'
+
+include Mongrel
+
+class URIClassifierTest < Test::Unit::TestCase
+
+  def test_uri_finding
+    uri_classifier = URIClassifier.new
+    uri_classifier.register("/test", 1)
+    
+    script_name, path_info, value = uri_classifier.resolve("/test")
+    assert_equal 1, value
+    assert_equal "/test", script_name
+  end
+  
+  def test_root_handler_only
+    uri_classifier = URIClassifier.new
+    uri_classifier.register("/", 1)
+    
+    script_name, path_info, value = uri_classifier.resolve("/test")
+    assert_equal 1, value
+    assert_equal "/", script_name
+    assert_equal "/test", path_info
+  end
+
+  def test_uri_prefix_ops
+    test = "/pre/fix/test"
+    prefix = "/pre"
+
+    uri_classifier = URIClassifier.new
+    uri_classifier.register(prefix,1)
+
+    script_name, path_info, value = uri_classifier.resolve(prefix)
+    script_name, path_info, value = uri_classifier.resolve(test)
+    assert_equal 1, value
+    assert_equal prefix, script_name
+    assert_equal test[script_name.length .. -1], path_info
+
+    assert uri_classifier.inspect
+    assert_equal prefix, uri_classifier.uris[0]
+  end
+
+  def test_not_finding
+    test = "/cant/find/me"
+    uri_classifier = URIClassifier.new
+    uri_classifier.register(test, 1)
+
+    script_name, path_info, value = uri_classifier.resolve("/nope/not/here")
+    assert_nil script_name
+    assert_nil path_info
+    assert_nil value
+  end
+
+  def test_exceptions
+    uri_classifier = URIClassifier.new
+
+    uri_classifier.register("/test", 1)
+    
+    failed = false
+    begin
+      uri_classifier.register("/test", 1)
+    rescue => e
+      failed = true
+    end
+
+    assert failed
+
+    failed = false
+    begin
+      uri_classifier.register("", 1)
+    rescue => e
+      failed = true
+    end
+
+    assert failed
+  end
+
+
+  def test_register_unregister
+    uri_classifier = URIClassifier.new
+    
+    100.times do
+      uri_classifier.register("/stuff", 1)
+      value = uri_classifier.unregister("/stuff")
+      assert_equal 1, value
+    end
+
+    uri_classifier.register("/things",1)
+    script_name, path_info, value = uri_classifier.resolve("/things")
+    assert_equal 1, value
+
+    uri_classifier.unregister("/things")
+    script_name, path_info, value = uri_classifier.resolve("/things")
+    assert_nil value
+
+  end
+
+
+  def test_uri_branching
+    uri_classifier = URIClassifier.new
+    uri_classifier.register("/test", 1)
+    uri_classifier.register("/test/this",2)
+  
+    script_name, path_info, handler = uri_classifier.resolve("/test")
+    script_name, path_info, handler = uri_classifier.resolve("/test/that")
+    assert_equal "/test", script_name, "failed to properly find script off branch portion of uri"
+    assert_equal "/that", path_info
+    assert_equal 1, handler, "wrong result for branching uri"
+  end
+
+  def test_all_prefixing
+    tests = ["/test","/test/that","/test/this"]
+    uri = "/test/this/that"
+    uri_classifier = URIClassifier.new
+    
+    current = ""
+    uri.each_byte do |c|
+      current << c.chr
+      uri_classifier.register(current, c)
+    end
+    
+
+    # Try to resolve everything with no asserts as a fuzzing
+    tests.each do |prefix|
+      current = ""
+      prefix.each_byte do |c|
+        current << c.chr
+        script_name, path_info, handler = uri_classifier.resolve(current)
+        assert script_name
+        assert path_info
+        assert handler
+      end
+    end
+
+    # Assert that we find stuff
+    tests.each do |t|
+      script_name, path_info, handler = uri_classifier.resolve(t)
+      assert handler
+    end
+
+    # Assert we don't find stuff
+    script_name, path_info, handler = uri_classifier.resolve("chicken")
+    assert_nil handler
+    assert_nil script_name
+    assert_nil path_info
+  end
+
+
+  # Verifies that a root mounted ("/") handler resolves
+  # such that path info matches the original URI.
+  # This is needed to accommodate real usage of handlers.
+  def test_root_mounted
+    uri_classifier = URIClassifier.new
+    root = "/"
+    path = "/this/is/a/test"
+
+    uri_classifier.register(root, 1)
+
+    script_name, path_info, handler = uri_classifier.resolve(root)
+    assert_equal 1, handler
+    assert_equal root, path_info
+    assert_equal root, script_name
+
+    script_name, path_info, handler = uri_classifier.resolve(path)
+    assert_equal path, path_info
+    assert_equal root, script_name
+    assert_equal 1, handler
+  end
+
+  # Verifies that a root mounted ("/") handler
+  # is the default point, doesn't matter the order we use
+  # to register the URIs
+  def test_classifier_order
+    tests = ["/before", "/way_past"]
+    root = "/"
+    path = "/path"
+
+    uri_classifier = URIClassifier.new
+    uri_classifier.register(path, 1)
+    uri_classifier.register(root, 2)
+
+    tests.each do |uri|
+      script_name, path_info, handler = uri_classifier.resolve(uri)
+      assert_equal root, script_name, "#{uri} did not resolve to #{root}"
+      assert_equal uri, path_info
+      assert_equal 2, handler
+    end
+  end
+  
+  if ENV['BENCHMARK']
+    # Eventually we will have a suite of benchmarks instead of lamely installing a test
+    
+    def test_benchmark    
+
+      # This URI set should favor a TST. Both versions increase linearly until you hit 14
+      # URIs, then the TST flattens out.
+      @uris = %w(
+        /
+        /dag /dig /digbark /dog /dogbark /dog/bark /dug /dugbarking /puppy
+        /c /cat /cat/tree /cat/tree/mulberry /cats /cot /cot/tree/mulberry /kitty /kittycat
+#        /eag /eig /eigbark /eog /eogbark /eog/bark /eug /eugbarking /iuppy
+#        /f /fat /fat/tree /fat/tree/mulberry /fats /fot /fot/tree/mulberry /jitty /jittyfat
+#        /gag /gig /gigbark /gog /gogbark /gog/bark /gug /gugbarking /kuppy
+#        /h /hat /hat/tree /hat/tree/mulberry /hats /hot /hot/tree/mulberry /litty /littyhat
+#        /ceag /ceig /ceigbark /ceog /ceogbark /ceog/cbark /ceug /ceugbarking /ciuppy
+#        /cf /cfat /cfat/ctree /cfat/ctree/cmulberry /cfats /cfot /cfot/ctree/cmulberry /cjitty /cjittyfat
+#        /cgag /cgig /cgigbark /cgog /cgogbark /cgog/cbark /cgug /cgugbarking /ckuppy
+#        /ch /chat /chat/ctree /chat/ctree/cmulberry /chats /chot /chot/ctree/cmulberry /citty /cittyhat
+      )
+      
+      @requests = %w(
+        /
+        /dig
+        /digging
+        /dogging
+        /dogbarking/
+        /puppy/barking
+        /c
+        /cat
+        /cat/shrub
+        /cat/tree
+        /cat/tree/maple
+        /cat/tree/mulberry/tree
+        /cat/tree/oak
+        /cats/
+        /cats/tree
+        /cod
+        /zebra
+      )
+    
+      @classifier = URIClassifier.new
+      @uris.each do |uri|
+        @classifier.register(uri, 1)
+      end
+      
+      puts "#{@uris.size} URIs / #{@requests.size * 10000} requests"
+  
+      Benchmark.bm do |x|
+        x.report do
+  #        require 'ruby-prof'
+  #        profile = RubyProf.profile do
+            10000.times do
+              @requests.each do |request|
+                @classifier.resolve(request)
+              end
+            end
+  #        end
+  #        File.open("profile.html", 'w') { |file| RubyProf::GraphHtmlPrinter.new(profile).print(file, 0) }
+        end
+      end          
+    end
+  end
+  
+end
+
diff --git a/test/unit/test_ws.rb b/test/unit/test_ws.rb
new file mode 100644
index 0000000..3385e0a
--- /dev/null
+++ b/test/unit/test_ws.rb
@@ -0,0 +1,115 @@
+# Copyright (c) 2005 Zed A. Shaw
+# You can redistribute it and/or modify it under the same terms as Ruby.
+#
+# Additional work donated by contributors.  See http://mongrel.rubyforge.org/attributions.html
+# for more information.
+
+require 'test/test_helper'
+
+include Mongrel
+
+class TestHandler < Mongrel::HttpHandler
+  attr_reader :ran_test
+
+  def process(request, response)
+    @ran_test = true
+    response.socket.write("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nhello!\n")
+  end
+end
+
+
+class WebServerTest < Test::Unit::TestCase
+
+  def setup
+    @valid_request = "GET / HTTP/1.1\r\nHost: www.zedshaw.com\r\nContent-Type: text/plain\r\n\r\n"
+    
+    redirect_test_io do
+      # We set num_processors=1 so that we can test the reaping code
+      @server = HttpServer.new("127.0.0.1", 9998, num_processors=1)
+    end
+    
+    @tester = TestHandler.new
+    @server.register("/test", @tester)
+    redirect_test_io do
+      @server.run
+    end
+  end
+
+  def teardown
+    redirect_test_io do
+      @server.stop(true)
+    end
+  end
+
+  def test_simple_server
+    hit(['http://localhost:9998/test'])
+    assert @tester.ran_test, "Handler didn't really run"
+  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
+    socket = TCPSocket.new("127.0.0.1", 9998);
+    request = StringIO.new(string)
+    chunks_out = 0
+
+    while data = request.read(chunk)
+      chunks_out += socket.write(data)
+      socket.flush
+      sleep 0.2
+      if close_after and chunks_out > close_after
+        socket.close
+        sleep 1
+      end
+    end
+    sleep(shutdown_delay)
+    socket.write(" ") # Some platforms only raise the exception on attempted write
+    socket.flush
+  end
+
+  def test_trickle_attack
+    do_test(@valid_request, 3)
+  end
+
+  def test_close_client
+    assert_raises IOError do
+      do_test(@valid_request, 10, 20)
+    end
+  end
+
+  def test_bad_client
+    redirect_test_io do
+      do_test("GET /test HTTP/BAD", 3)
+    end
+  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"
+      assert_raises Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EINVAL, IOError do
+        do_test(long, long.length/2, 10)
+      end
+    end
+  end
+
+  def test_num_processors_overload
+    redirect_test_io do
+      assert_raises Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EINVAL do
+        tests = [
+          Thread.new { do_test(@valid_request, 1) },
+          Thread.new { do_test(@valid_request, 10) },
+        ]
+
+        tests.each {|t| t.join}
+      end
+    end
+  end
+
+  def test_file_streamed_request
+    body = "a" * (Mongrel::Const::MAX_BODY * 2)
+    long = "GET /test HTTP/1.1\r\nContent-length: #{body.length}\r\n\r\n" + body
+    do_test(long, Mongrel::Const::CHUNK_SIZE * 2 -400)
+  end
+
+end
+