diff options
author | Eric Wong <normalperson@yhbt.net> | 2010-04-07 17:07:42 -0700 |
---|---|---|
committer | Eric Wong <normalperson@yhbt.net> | 2010-04-07 17:36:31 -0700 |
commit | c3e9f5ba6fc10397f55941f36da29808a105d248 (patch) | |
tree | 705970f479064931ae07cfca0cd44013c113cb8d /test | |
download | raindrops-c3e9f5ba6fc10397f55941f36da29808a105d248.tar.gz |
Diffstat (limited to 'test')
-rw-r--r-- | test/test_linux.rb | 228 | ||||
-rw-r--r-- | test/test_linux_middleware.rb | 59 | ||||
-rw-r--r-- | test/test_middleware.rb | 111 | ||||
-rw-r--r-- | test/test_raindrops.rb | 95 | ||||
-rw-r--r-- | test/test_raindrops_gc.rb | 13 | ||||
-rw-r--r-- | test/test_struct.rb | 54 |
6 files changed, 560 insertions, 0 deletions
diff --git a/test/test_linux.rb b/test/test_linux.rb new file mode 100644 index 0000000..7744c61 --- /dev/null +++ b/test/test_linux.rb @@ -0,0 +1,228 @@ +# -*- encoding: binary -*- +require 'test/unit' +require 'tempfile' +require 'raindrops' +require 'socket' +require 'pp' +$stderr.sync = $stdout.sync = true + +class TestLinux < Test::Unit::TestCase + include Raindrops::Linux + + TEST_ADDR = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1' + + def test_unix + tmp = Tempfile.new("\xde\xad\xbe\xef") # valid path, really :) + File.unlink(tmp.path) + us = UNIXServer.new(tmp.path) + stats = unix_listener_stats([tmp.path]) + assert_equal 1, stats.size + assert_equal 0, stats[tmp.path].active + assert_equal 0, stats[tmp.path].queued + + uc0 = UNIXSocket.new(tmp.path) + stats = unix_listener_stats([tmp.path]) + assert_equal 1, stats.size + assert_equal 0, stats[tmp.path].active + assert_equal 1, stats[tmp.path].queued + + uc1 = UNIXSocket.new(tmp.path) + stats = unix_listener_stats([tmp.path]) + assert_equal 1, stats.size + assert_equal 0, stats[tmp.path].active + assert_equal 2, stats[tmp.path].queued + + ua0 = us.accept + stats = unix_listener_stats([tmp.path]) + assert_equal 1, stats.size + assert_equal 1, stats[tmp.path].active + assert_equal 1, stats[tmp.path].queued + end + + def test_tcp + port = unused_port + s = TCPServer.new(TEST_ADDR, port) + addr = "#{TEST_ADDR}:#{port}" + addrs = [ addr ] + stats = tcp_listener_stats(addrs) + assert_equal 1, stats.size + assert_equal 0, stats[addr].queued + assert_equal 0, stats[addr].active + + c = TCPSocket.new(TEST_ADDR, port) + stats = tcp_listener_stats(addrs) + assert_equal 1, stats.size + assert_equal 1, stats[addr].queued + assert_equal 0, stats[addr].active + + sc = s.accept + stats = tcp_listener_stats(addrs) + assert_equal 1, stats.size + assert_equal 0, stats[addr].queued + assert_equal 1, stats[addr].active + end + + def test_tcp_multi + port1, port2 = unused_port, unused_port + s1 = TCPServer.new(TEST_ADDR, port1) + s2 = TCPServer.new(TEST_ADDR, port2) + addr1, addr2 = "#{TEST_ADDR}:#{port1}", "#{TEST_ADDR}:#{port2}" + addrs = [ addr1, addr2 ] + stats = tcp_listener_stats(addrs) + assert_equal 2, stats.size + assert_equal 0, stats[addr1].queued + assert_equal 0, stats[addr1].active + assert_equal 0, stats[addr2].queued + assert_equal 0, stats[addr2].active + + c1 = TCPSocket.new(TEST_ADDR, port1) + stats = tcp_listener_stats(addrs) + assert_equal 2, stats.size + assert_equal 1, stats[addr1].queued + assert_equal 0, stats[addr1].active + assert_equal 0, stats[addr2].queued + assert_equal 0, stats[addr2].active + + sc1 = s1.accept + stats = tcp_listener_stats(addrs) + assert_equal 2, stats.size + assert_equal 0, stats[addr1].queued + assert_equal 1, stats[addr1].active + assert_equal 0, stats[addr2].queued + assert_equal 0, stats[addr2].active + + c2 = TCPSocket.new(TEST_ADDR, port2) + stats = tcp_listener_stats(addrs) + assert_equal 2, stats.size + assert_equal 0, stats[addr1].queued + assert_equal 1, stats[addr1].active + assert_equal 1, stats[addr2].queued + assert_equal 0, stats[addr2].active + + c3 = TCPSocket.new(TEST_ADDR, port2) + stats = tcp_listener_stats(addrs) + assert_equal 2, stats.size + assert_equal 0, stats[addr1].queued + assert_equal 1, stats[addr1].active + assert_equal 2, stats[addr2].queued + assert_equal 0, stats[addr2].active + + sc2 = s2.accept + stats = tcp_listener_stats(addrs) + assert_equal 2, stats.size + assert_equal 0, stats[addr1].queued + assert_equal 1, stats[addr1].active + assert_equal 1, stats[addr2].queued + assert_equal 1, stats[addr2].active + + sc1.close + stats = tcp_listener_stats(addrs) + assert_equal 0, stats[addr1].queued + assert_equal 0, stats[addr1].active + assert_equal 1, stats[addr2].queued + assert_equal 1, stats[addr2].active + end + + # tries to overflow buffers + def test_tcp_stress_test + nr_proc = 32 + nr_sock = 500 + port = unused_port + addr = "#{TEST_ADDR}:#{port}" + addrs = [ addr ] + s = TCPServer.new(TEST_ADDR, port) + rda, wra = IO.pipe + rdb, wrb = IO.pipe + + nr_proc.times do + fork do + rda.close + wrb.close + socks = nr_sock.times.map { s.accept } + wra.syswrite('.') + wra.close + rdb.sysread(1) # wait for parent to nuke us + end + end + + nr_proc.times do + fork do + rda.close + wrb.close + socks = nr_sock.times.map { TCPSocket.new(TEST_ADDR, port) } + wra.syswrite('.') + wra.close + rdb.sysread(1) # wait for parent to nuke us + end + end + + assert_equal('.' * (nr_proc * 2), rda.read(nr_proc * 2)) + + rda.close + stats = tcp_listener_stats(addrs) + expect = { addr => Raindrops::ListenStats[nr_sock * nr_proc, 0] } + assert_equal expect, stats + + uno_mas = TCPSocket.new(TEST_ADDR, port) + stats = tcp_listener_stats(addrs) + expect = { addr => Raindrops::ListenStats[nr_sock * nr_proc, 1] } + assert_equal expect, stats + + if ENV["BENCHMARK"].to_i != 0 + require 'benchmark' + puts(Benchmark.measure{1000.times { tcp_listener_stats(addrs) }}) + end + + wrb.syswrite('.' * (nr_proc * 2)) # broadcast a wakeup + statuses = Process.waitall + statuses.each { |(pid,status)| assert status.success?, status.inspect } + end if ENV["STRESS"].to_i != 0 + +private + + # Stolen from Unicorn, also a version of this is used by the Rainbows! + # test suite. + # unused_port provides an unused port on +addr+ usable for TCP that is + # guaranteed to be unused across all compatible tests on that system. It + # prevents race conditions by using a lock file other tests + # will see. This is required if you perform several builds in parallel + # with a continuous integration system or run tests in parallel via + # gmake. This is NOT guaranteed to be race-free if you run other + # systems that bind to random ports for testing (but the window + # for a race condition is very small). You may also set UNICORN_TEST_ADDR + # to override the default test address (127.0.0.1). + def unused_port(addr = TEST_ADDR) + retries = 100 + base = 5000 + port = sock = nil + begin + begin + port = base + rand(32768 - base) + while port == 8080 + port = base + rand(32768 - base) + end + + sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) + sock.bind(Socket.pack_sockaddr_in(port, addr)) + sock.listen(5) + rescue Errno::EADDRINUSE, Errno::EACCES + sock.close rescue nil + retry if (retries -= 1) >= 0 + end + + # since we'll end up closing the random port we just got, there's a race + # condition could allow the random port we just chose to reselect itself + # when running tests in parallel with gmake. Create a lock file while + # we have the port here to ensure that does not happen . + lock_path = "#{Dir::tmpdir}/unicorn_test.#{addr}:#{port}.lock" + lock = File.open(lock_path, File::WRONLY|File::CREAT|File::EXCL, 0600) + at_exit { File.unlink(lock_path) rescue nil } + rescue Errno::EEXIST + sock.close rescue nil + retry + end + sock.close rescue nil + port + end + +end if RUBY_PLATFORM =~ /linux/ diff --git a/test/test_linux_middleware.rb b/test/test_linux_middleware.rb new file mode 100644 index 0000000..670b853 --- /dev/null +++ b/test/test_linux_middleware.rb @@ -0,0 +1,59 @@ +# -*- encoding: binary -*- +require 'test/unit' +require 'tempfile' +require 'raindrops' +require 'socket' +$stderr.sync = $stdout.sync = true + +class TestLinuxMiddleware < Test::Unit::TestCase + + def setup + @resp_headers = { 'Content-Type' => 'text/plain', 'Content-Length' => '0' } + @response = [ 200, @resp_headers, [] ] + @app = lambda { |env| @response } + end + + def test_unix_listener + tmp = Tempfile.new("") + File.unlink(tmp.path) + us = UNIXServer.new(tmp.path) + app = Raindrops::Middleware.new(@app, :listeners => [tmp.path]) + linux_extra = "#{tmp.path} active: 0\n#{tmp.path} queued: 0\n" + response = app.call("PATH_INFO" => "/_raindrops") + + expect = [ + 200, + { + "Content-Type" => "text/plain", + "Content-Length" => (22 + linux_extra.size).to_s + }, + [ + "calling: 0\nwriting: 0\n#{linux_extra}" \ + ] + ] + assert_equal expect, response + end + + def test_unix_listener_queued + tmp = Tempfile.new("") + File.unlink(tmp.path) + us = UNIXServer.new(tmp.path) + uc = UNIXSocket.new(tmp.path) + app = Raindrops::Middleware.new(@app, :listeners => [tmp.path]) + linux_extra = "#{tmp.path} active: 0\n#{tmp.path} queued: 1\n" + response = app.call("PATH_INFO" => "/_raindrops") + + expect = [ + 200, + { + "Content-Type" => "text/plain", + "Content-Length" => (22 + linux_extra.size).to_s + }, + [ + "calling: 0\nwriting: 0\n#{linux_extra}" \ + ] + ] + assert_equal expect, response + end + +end if RUBY_PLATFORM =~ /linux/ diff --git a/test/test_middleware.rb b/test/test_middleware.rb new file mode 100644 index 0000000..e2fdc38 --- /dev/null +++ b/test/test_middleware.rb @@ -0,0 +1,111 @@ +# -*- encoding: binary -*- +require 'test/unit' +require 'raindrops' + +class TestMiddleware < Test::Unit::TestCase + + def setup + @resp_headers = { 'Content-Type' => 'text/plain', 'Content-Length' => '0' } + @response = [ 200, @resp_headers, [] ] + @app = lambda { |env| @response } + end + + def test_setup + app = Raindrops::Middleware.new(@app) + response = app.call({}) + assert_equal @response[0,2], response[0,2] + assert response.last.kind_of?(Raindrops::Middleware) + assert response.last.object_id != app.object_id + tmp = [] + response.last.each { |y| tmp << y } + assert tmp.empty? + end + + def test_alt_stats + stats = Raindrops::Middleware::Stats.new + app = lambda { |env| + if (stats.writing == 0 && stats.calling == 1) + @app.call(env) + else + [ 500, @resp_headers, [] ] + end + } + app = Raindrops::Middleware.new(app, :stats => stats) + response = app.call({}) + assert_equal 0, stats.calling + assert_equal 1, stats.writing + assert_equal 200, response[0] + assert response.last.kind_of?(Raindrops::Middleware) + tmp = [] + response.last.each do |y| + assert_equal 1, stats.writing + tmp << y + end + assert tmp.empty? + end + + def test_default_endpoint + app = Raindrops::Middleware.new(@app) + response = app.call("PATH_INFO" => "/_raindrops") + expect = [ + 200, + { "Content-Type" => "text/plain", "Content-Length" => "22" }, + [ "calling: 0\nwriting: 0\n" ] + ] + assert_equal expect, response + end + + def test_alt_endpoint + app = Raindrops::Middleware.new(@app, :path => "/foo") + response = app.call("PATH_INFO" => "/foo") + expect = [ + 200, + { "Content-Type" => "text/plain", "Content-Length" => "22" }, + [ "calling: 0\nwriting: 0\n" ] + ] + assert_equal expect, response + end + + def test_concurrent + rda, wra = IO.pipe + rdb, wrb = IO.pipe + app = lambda do |env| + wrb.close + wra.syswrite('.') + wra.close + + # wait until parent has run app.call for stats endpoint + rdb.read + @app.call(env) + end + app = Raindrops::Middleware.new(app) + + pid = fork { app.call({}) } + rdb.close + + # wait til child is running in app.call + assert_equal '.', rda.sysread(1) + rda.close + + response = app.call("PATH_INFO" => "/_raindrops") + expect = [ + 200, + { "Content-Type" => "text/plain", "Content-Length" => "22" }, + [ "calling: 1\nwriting: 0\n" ] + ] + assert_equal expect, response + wrb.close # unblock child process + assert Process.waitpid2(pid).last.success? + + # we didn't call close the body in the forked child, so it'll always be + # marked as writing, a real server would close the body + response = app.call("PATH_INFO" => "/_raindrops") + expect = [ + 200, + { "Content-Type" => "text/plain", "Content-Length" => "22" }, + [ "calling: 0\nwriting: 1\n" ] + ] + assert_equal expect, response + end + +end diff --git a/test/test_raindrops.rb b/test/test_raindrops.rb new file mode 100644 index 0000000..66fc208 --- /dev/null +++ b/test/test_raindrops.rb @@ -0,0 +1,95 @@ +# -*- encoding: binary -*- +require 'test/unit' +require 'raindrops' + +class TestRaindrops < Test::Unit::TestCase + + def test_size + rd = Raindrops.new(4) + assert_equal 4, rd.size + end + + def test_ary + rd = Raindrops.new(4) + assert_equal [0, 0, 0, 0] , rd.to_ary + end + + def test_incr_no_args + rd = Raindrops.new(4) + assert_equal 1, rd.incr(0) + assert_equal [1, 0, 0, 0], rd.to_ary + end + + def test_incr_args + rd = Raindrops.new(4) + assert_equal 6, rd.incr(3, 6) + assert_equal [0, 0, 0, 6], rd.to_ary + end + + def test_decr_args + rd = Raindrops.new(4) + rd[3] = 6 + assert_equal 5, rd.decr(3, 1) + assert_equal [0, 0, 0, 5], rd.to_ary + end + + def test_incr_shared + rd = Raindrops.new(2) + 5.times do + pid = fork { rd.incr(1) } + _, status = Process.waitpid2(pid) + assert status.success? + end + assert_equal [0, 5], rd.to_ary + end + + def test_incr_decr + rd = Raindrops.new(1) + fork { 1000000.times { rd.incr(0) } } + 1000.times { rd.decr(0) } + statuses = Process.waitall + statuses.each { |pid, status| assert status.success? } + assert_equal [999000], rd.to_ary + end + + def test_bad_incr + rd = Raindrops.new(1) + assert_raises(ArgumentError) { rd.incr(-1) } + assert_raises(ArgumentError) { rd.incr(2) } + assert_raises(ArgumentError) { rd.incr(0xffffffff) } + end + + def test_dup + @rd = Raindrops.new(1) + rd = @rd.dup + assert_equal 1, @rd.incr(0) + assert_equal 1, rd.incr(0) + assert_equal 2, rd.incr(0) + assert_equal 2, rd[0] + assert_equal 1, @rd[0] + end + + def test_clone + @rd = Raindrops.new(1) + rd = @rd.clone + assert_equal 1, @rd.incr(0) + assert_equal 1, rd.incr(0) + assert_equal 2, rd.incr(0) + assert_equal 2, rd[0] + assert_equal 1, @rd[0] + end + + def test_big + expect = 256.times.map { 0 } + rd = Raindrops.new(256) + assert_equal expect, rd.to_ary + assert_nothing_raised { rd[255] = 5 } + assert_equal 5, rd[255] + assert_nothing_raised { rd[2] = 2 } + + expect[255] = 5 + expect[2] = 2 + assert_equal expect, rd.to_ary + end + +end diff --git a/test/test_raindrops_gc.rb b/test/test_raindrops_gc.rb new file mode 100644 index 0000000..b473619 --- /dev/null +++ b/test/test_raindrops_gc.rb @@ -0,0 +1,13 @@ +# -*- encoding: binary -*- +require 'test/unit' +require 'raindrops' + +class TestRaindropsGc < Test::Unit::TestCase + + def test_gc + assert_nothing_raised do + 1000000.times { Raindrops.new(24) } + end + end + +end diff --git a/test/test_struct.rb b/test/test_struct.rb new file mode 100644 index 0000000..9792d5b --- /dev/null +++ b/test/test_struct.rb @@ -0,0 +1,54 @@ +require 'test/unit' +require 'raindrops' + +class TestRaindrops < Test::Unit::TestCase + + def test_struct_new + @rw = Raindrops::Struct.new(:r, :w) + assert @rw.kind_of?(Class) + end + + TMP = Raindrops::Struct.new(:r, :w) + + def test_init_basic + tmp = TMP.new + assert_equal 0, tmp.r + assert_equal 1, tmp.incr_r + assert_equal 1, tmp.r + assert_equal({ :r => 1, :w => 0 }, tmp.to_hash) + + assert_equal 1, tmp[0] + assert_equal 0, tmp[1] + assert_equal [ :r, :w ], TMP::MEMBERS + end + + def test_init + tmp = TMP.new(5, 6) + assert_equal({ :r => 5, :w => 6 }, tmp.to_hash) + end + + def test_dup + a = TMP.new(5, 6) + b = a.dup + assert_equal({ :r => 5, :w => 6 }, b.to_hash) + assert_nothing_raised { 4.times { b.decr_r } } + assert_equal({ :r => 1, :w => 6 }, b.to_hash) + assert_equal({ :r => 5, :w => 6 }, a.to_hash) + end + + class Foo < Raindrops::Struct.new(:a, :b, :c, :d) + def to_ary + @raindrops.to_ary + end + + def hello + "world" + end + end + + def test_subclass + assert_equal [0, 0, 0, 0], Foo.new.to_ary + assert_equal "world", Foo.new.hello + end + +end |