about summary refs log tree commit homepage
path: root/test/test_linux.rb
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/test_linux.rb
downloadraindrops-c3e9f5ba6fc10397f55941f36da29808a105d248.tar.gz
Diffstat (limited to 'test/test_linux.rb')
-rw-r--r--test/test_linux.rb228
1 files changed, 228 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/