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/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 ++++++++++++++++ 13 files changed, 1291 insertions(+) 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 (limited to 'test/unit') 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