about summary refs log tree commit homepage
path: root/test
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2010-04-07 17:07:42 -0700
committerEric Wong <normalperson@yhbt.net>2010-04-07 17:36:31 -0700
commitc3e9f5ba6fc10397f55941f36da29808a105d248 (patch)
tree705970f479064931ae07cfca0cd44013c113cb8d /test
downloadraindrops-c3e9f5ba6fc10397f55941f36da29808a105d248.tar.gz
Diffstat (limited to 'test')
-rw-r--r--test/test_linux.rb228
-rw-r--r--test/test_linux_middleware.rb59
-rw-r--r--test/test_middleware.rb111
-rw-r--r--test/test_raindrops.rb95
-rw-r--r--test/test_raindrops_gc.rb13
-rw-r--r--test/test_struct.rb54
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