From 5a10ba13b2c2f56b8ffb0978ebb36ff26a7715e5 Mon Sep 17 00:00:00 2001 From: evanweaver Date: Wed, 20 Feb 2008 06:15:30 +0000 Subject: Move tests from tests/ into tests/unit/ so they parallel tests/benchmark. They are mainly unit tests anyway; we can clean them up more moving forward. git-svn-id: svn+ssh://rubyforge.org/var/svn/mongrel/branches/stable_1-1@973 19e92222-5c0b-0410-8929-a290d50e31e9 --- test/test_cgi_wrapper.rb | 26 ---- test/test_command.rb | 86 ------------ test/test_conditional.rb | 107 --------------- test/test_configurator.rb | 87 ------------- test/test_debug.rb | 25 ---- test/test_handlers.rb | 123 ----------------- test/test_helper.rb | 66 ++++++++++ test/test_http11.rb | 156 ---------------------- test/test_redirect_handler.rb | 44 ------- test/test_request_progress.rb | 99 -------------- test/test_response.rb | 127 ------------------ test/test_stats.rb | 35 ----- test/test_uriclassifier.rb | 261 ------------------------------------- test/test_ws.rb | 115 ---------------- test/testhelp.rb | 66 ---------- test/unit/test_cgi_wrapper.rb | 26 ++++ test/unit/test_command.rb | 86 ++++++++++++ test/unit/test_conditional.rb | 107 +++++++++++++++ test/unit/test_configurator.rb | 87 +++++++++++++ test/unit/test_debug.rb | 25 ++++ test/unit/test_handlers.rb | 123 +++++++++++++++++ test/unit/test_http11.rb | 156 ++++++++++++++++++++++ test/unit/test_redirect_handler.rb | 44 +++++++ test/unit/test_request_progress.rb | 99 ++++++++++++++ test/unit/test_response.rb | 127 ++++++++++++++++++ test/unit/test_stats.rb | 35 +++++ test/unit/test_uriclassifier.rb | 261 +++++++++++++++++++++++++++++++++++++ test/unit/test_ws.rb | 115 ++++++++++++++++ 28 files changed, 1357 insertions(+), 1357 deletions(-) delete mode 100644 test/test_cgi_wrapper.rb delete mode 100644 test/test_command.rb delete mode 100644 test/test_conditional.rb delete mode 100644 test/test_configurator.rb delete mode 100644 test/test_debug.rb delete mode 100644 test/test_handlers.rb create mode 100644 test/test_helper.rb delete mode 100644 test/test_http11.rb delete mode 100644 test/test_redirect_handler.rb delete mode 100644 test/test_request_progress.rb delete mode 100644 test/test_response.rb delete mode 100644 test/test_stats.rb delete mode 100644 test/test_uriclassifier.rb delete mode 100644 test/test_ws.rb delete mode 100644 test/testhelp.rb create mode 100644 test/unit/test_cgi_wrapper.rb create mode 100644 test/unit/test_command.rb create mode 100644 test/unit/test_conditional.rb create mode 100644 test/unit/test_configurator.rb create mode 100644 test/unit/test_debug.rb create mode 100644 test/unit/test_handlers.rb create mode 100644 test/unit/test_http11.rb create mode 100644 test/unit/test_redirect_handler.rb create mode 100644 test/unit/test_request_progress.rb create mode 100644 test/unit/test_response.rb create mode 100644 test/unit/test_stats.rb create mode 100644 test/unit/test_uriclassifier.rb create mode 100644 test/unit/test_ws.rb diff --git a/test/test_cgi_wrapper.rb b/test/test_cgi_wrapper.rb deleted file mode 100644 index 449f6d0..0000000 --- a/test/test_cgi_wrapper.rb +++ /dev/null @@ -1,26 +0,0 @@ - -require 'test/testhelp' - -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/test_command.rb b/test/test_command.rb deleted file mode 100644 index 3cb9643..0000000 --- a/test/test_command.rb +++ /dev/null @@ -1,86 +0,0 @@ -# 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/testhelp' - -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/test_conditional.rb b/test/test_conditional.rb deleted file mode 100644 index cd3ce06..0000000 --- a/test/test_conditional.rb +++ /dev/null @@ -1,107 +0,0 @@ -# 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/testhelp' - -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/test_configurator.rb b/test/test_configurator.rb deleted file mode 100644 index dd99f00..0000000 --- a/test/test_configurator.rb +++ /dev/null @@ -1,87 +0,0 @@ -# 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/testhelp' - -$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(File.dirname(__FILE__) + "/../test/mongrel.conf") - load_mime_map(File.dirname(__FILE__) + "/../test/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/test_debug.rb b/test/test_debug.rb deleted file mode 100644 index 2f7be24..0000000 --- a/test/test_debug.rb +++ /dev/null @@ -1,25 +0,0 @@ -# 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/testhelp' -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/test_handlers.rb b/test/test_handlers.rb deleted file mode 100644 index 1005dd0..0000000 --- a/test/test_handlers.rb +++ /dev/null @@ -1,123 +0,0 @@ -# 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/testhelp' - -class SimpleHandler < Mongrel::HttpHandler - def process(request, response) - response.start do |head,out| - head["Content-Type"] = "text/html" - results = "Your request:
#{request.params.to_yaml}
View the files." - 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/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000..42ead2c --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,66 @@ +# 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. + + +HERE = File.dirname(__FILE__) +%w(lib ext bin test).each do |dir| + $LOAD_PATH.unshift "#{HERE}/../#{dir}" +end + +require 'rubygems' +require 'test/unit' +require 'net/http' +require 'timeout' +require 'cgi/session' +require 'fileutils' +require 'benchmark' +require 'digest/sha1' +require 'uri' +require 'stringio' +require 'pp' + +require 'mongrel' +require 'mongrel/stats' + +if ENV['DEBUG'] + require 'ruby-debug' + Debugger.start +end + +def redirect_test_io + orig_err = STDERR.dup + orig_out = STDOUT.dup + STDERR.reopen("test_stderr.log") + STDOUT.reopen("test_stdout.log") + + begin + yield + ensure + STDERR.reopen(orig_err) + STDOUT.reopen(orig_out) + end +end + +# Either takes a string to do a get request against, or a tuple of [URI, HTTP] where +# HTTP is some kind of Net::HTTP request object (POST, HEAD, etc.) +def hit(uris) + results = [] + uris.each do |u| + res = nil + + if u.kind_of? String + res = Net::HTTP.get(URI.parse(u)) + else + url = URI.parse(u[0]) + res = Net::HTTP.new(url.host, url.port).start {|h| h.request(u[1]) } + end + + assert res != nil, "Didn't get a response: #{u}" + results << res + end + + return results +end diff --git a/test/test_http11.rb b/test/test_http11.rb deleted file mode 100644 index da311af..0000000 --- a/test/test_http11.rb +++ /dev/null @@ -1,156 +0,0 @@ -# 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/testhelp' - -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/test_redirect_handler.rb b/test/test_redirect_handler.rb deleted file mode 100644 index 2e03d48..0000000 --- a/test/test_redirect_handler.rb +++ /dev/null @@ -1,44 +0,0 @@ -# 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/testhelp' - -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/test_request_progress.rb b/test/test_request_progress.rb deleted file mode 100644 index ba21c27..0000000 --- a/test/test_request_progress.rb +++ /dev/null @@ -1,99 +0,0 @@ -# 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/testhelp' - -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/test_response.rb b/test/test_response.rb deleted file mode 100644 index 123ed98..0000000 --- a/test/test_response.rb +++ /dev/null @@ -1,127 +0,0 @@ -# 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/testhelp' - -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/test_stats.rb b/test/test_stats.rb deleted file mode 100644 index 404870a..0000000 --- a/test/test_stats.rb +++ /dev/null @@ -1,35 +0,0 @@ -# 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/testhelp' - -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/test_uriclassifier.rb b/test/test_uriclassifier.rb deleted file mode 100644 index 28af72c..0000000 --- a/test/test_uriclassifier.rb +++ /dev/null @@ -1,261 +0,0 @@ -# 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/testhelp' - -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/test_ws.rb b/test/test_ws.rb deleted file mode 100644 index f019122..0000000 --- a/test/test_ws.rb +++ /dev/null @@ -1,115 +0,0 @@ -# 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/testhelp' - -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 - diff --git a/test/testhelp.rb b/test/testhelp.rb deleted file mode 100644 index 42ead2c..0000000 --- a/test/testhelp.rb +++ /dev/null @@ -1,66 +0,0 @@ -# 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. - - -HERE = File.dirname(__FILE__) -%w(lib ext bin test).each do |dir| - $LOAD_PATH.unshift "#{HERE}/../#{dir}" -end - -require 'rubygems' -require 'test/unit' -require 'net/http' -require 'timeout' -require 'cgi/session' -require 'fileutils' -require 'benchmark' -require 'digest/sha1' -require 'uri' -require 'stringio' -require 'pp' - -require 'mongrel' -require 'mongrel/stats' - -if ENV['DEBUG'] - require 'ruby-debug' - Debugger.start -end - -def redirect_test_io - orig_err = STDERR.dup - orig_out = STDOUT.dup - STDERR.reopen("test_stderr.log") - STDOUT.reopen("test_stdout.log") - - begin - yield - ensure - STDERR.reopen(orig_err) - STDOUT.reopen(orig_out) - end -end - -# Either takes a string to do a get request against, or a tuple of [URI, HTTP] where -# HTTP is some kind of Net::HTTP request object (POST, HEAD, etc.) -def hit(uris) - results = [] - uris.each do |u| - res = nil - - if u.kind_of? String - res = Net::HTTP.get(URI.parse(u)) - else - url = URI.parse(u[0]) - res = Net::HTTP.new(url.host, url.port).start {|h| h.request(u[1]) } - end - - assert res != nil, "Didn't get a response: #{u}" - results << res - end - - return results -end 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 = "Your request:
#{request.params.to_yaml}
View the files." + 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 + -- cgit v1.2.3-24-ge0c7