diff options
Diffstat (limited to 'test/test_linux.rb')
-rw-r--r-- | test/test_linux.rb | 228 |
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/ |