initial
[raindrops.git] / test / test_linux.rb
blob7744c61c3cf7fb7c8435b174cd92f95d1d58a16a
1 # -*- encoding: binary -*-
2 require 'test/unit'
3 require 'tempfile'
4 require 'raindrops'
5 require 'socket'
6 require 'pp'
7 $stderr.sync = $stdout.sync = true
9 class TestLinux < Test::Unit::TestCase
10   include Raindrops::Linux
12   TEST_ADDR = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
14   def test_unix
15     tmp = Tempfile.new("\xde\xad\xbe\xef") # valid path, really :)
16     File.unlink(tmp.path)
17     us = UNIXServer.new(tmp.path)
18     stats = unix_listener_stats([tmp.path])
19     assert_equal 1, stats.size
20     assert_equal 0, stats[tmp.path].active
21     assert_equal 0, stats[tmp.path].queued
23     uc0 = UNIXSocket.new(tmp.path)
24     stats = unix_listener_stats([tmp.path])
25     assert_equal 1, stats.size
26     assert_equal 0, stats[tmp.path].active
27     assert_equal 1, stats[tmp.path].queued
29     uc1 = UNIXSocket.new(tmp.path)
30     stats = unix_listener_stats([tmp.path])
31     assert_equal 1, stats.size
32     assert_equal 0, stats[tmp.path].active
33     assert_equal 2, stats[tmp.path].queued
35     ua0 = us.accept
36     stats = unix_listener_stats([tmp.path])
37     assert_equal 1, stats.size
38     assert_equal 1, stats[tmp.path].active
39     assert_equal 1, stats[tmp.path].queued
40   end
42   def test_tcp
43     port = unused_port
44     s = TCPServer.new(TEST_ADDR, port)
45     addr = "#{TEST_ADDR}:#{port}"
46     addrs = [ addr ]
47     stats = tcp_listener_stats(addrs)
48     assert_equal 1, stats.size
49     assert_equal 0, stats[addr].queued
50     assert_equal 0, stats[addr].active
52     c = TCPSocket.new(TEST_ADDR, port)
53     stats = tcp_listener_stats(addrs)
54     assert_equal 1, stats.size
55     assert_equal 1, stats[addr].queued
56     assert_equal 0, stats[addr].active
58     sc = s.accept
59     stats = tcp_listener_stats(addrs)
60     assert_equal 1, stats.size
61     assert_equal 0, stats[addr].queued
62     assert_equal 1, stats[addr].active
63   end
65   def test_tcp_multi
66     port1, port2 = unused_port, unused_port
67     s1 = TCPServer.new(TEST_ADDR, port1)
68     s2 = TCPServer.new(TEST_ADDR, port2)
69     addr1, addr2 = "#{TEST_ADDR}:#{port1}", "#{TEST_ADDR}:#{port2}"
70     addrs = [ addr1, addr2 ]
71     stats = tcp_listener_stats(addrs)
72     assert_equal 2, stats.size
73     assert_equal 0, stats[addr1].queued
74     assert_equal 0, stats[addr1].active
75     assert_equal 0, stats[addr2].queued
76     assert_equal 0, stats[addr2].active
78     c1 = TCPSocket.new(TEST_ADDR, port1)
79     stats = tcp_listener_stats(addrs)
80     assert_equal 2, stats.size
81     assert_equal 1, stats[addr1].queued
82     assert_equal 0, stats[addr1].active
83     assert_equal 0, stats[addr2].queued
84     assert_equal 0, stats[addr2].active
86     sc1 = s1.accept
87     stats = tcp_listener_stats(addrs)
88     assert_equal 2, stats.size
89     assert_equal 0, stats[addr1].queued
90     assert_equal 1, stats[addr1].active
91     assert_equal 0, stats[addr2].queued
92     assert_equal 0, stats[addr2].active
94     c2 = TCPSocket.new(TEST_ADDR, port2)
95     stats = tcp_listener_stats(addrs)
96     assert_equal 2, stats.size
97     assert_equal 0, stats[addr1].queued
98     assert_equal 1, stats[addr1].active
99     assert_equal 1, stats[addr2].queued
100     assert_equal 0, stats[addr2].active
102     c3 = TCPSocket.new(TEST_ADDR, port2)
103     stats = tcp_listener_stats(addrs)
104     assert_equal 2, stats.size
105     assert_equal 0, stats[addr1].queued
106     assert_equal 1, stats[addr1].active
107     assert_equal 2, stats[addr2].queued
108     assert_equal 0, stats[addr2].active
110     sc2 = s2.accept
111     stats = tcp_listener_stats(addrs)
112     assert_equal 2, stats.size
113     assert_equal 0, stats[addr1].queued
114     assert_equal 1, stats[addr1].active
115     assert_equal 1, stats[addr2].queued
116     assert_equal 1, stats[addr2].active
118     sc1.close
119     stats = tcp_listener_stats(addrs)
120     assert_equal 0, stats[addr1].queued
121     assert_equal 0, stats[addr1].active
122     assert_equal 1, stats[addr2].queued
123     assert_equal 1, stats[addr2].active
124   end
126   # tries to overflow buffers
127   def test_tcp_stress_test
128     nr_proc = 32
129     nr_sock = 500
130     port = unused_port
131     addr = "#{TEST_ADDR}:#{port}"
132     addrs = [ addr ]
133     s = TCPServer.new(TEST_ADDR, port)
134     rda, wra = IO.pipe
135     rdb, wrb = IO.pipe
137     nr_proc.times do
138       fork do
139         rda.close
140         wrb.close
141         socks = nr_sock.times.map { s.accept }
142         wra.syswrite('.')
143         wra.close
144         rdb.sysread(1) # wait for parent to nuke us
145       end
146     end
148     nr_proc.times do
149       fork do
150         rda.close
151         wrb.close
152         socks = nr_sock.times.map { TCPSocket.new(TEST_ADDR, port) }
153         wra.syswrite('.')
154         wra.close
155         rdb.sysread(1) # wait for parent to nuke us
156       end
157     end
159     assert_equal('.' * (nr_proc * 2), rda.read(nr_proc * 2))
161     rda.close
162     stats = tcp_listener_stats(addrs)
163     expect = { addr => Raindrops::ListenStats[nr_sock * nr_proc, 0] }
164     assert_equal expect, stats
166     uno_mas = TCPSocket.new(TEST_ADDR, port)
167     stats = tcp_listener_stats(addrs)
168     expect = { addr => Raindrops::ListenStats[nr_sock * nr_proc, 1] }
169     assert_equal expect, stats
171     if ENV["BENCHMARK"].to_i != 0
172       require 'benchmark'
173       puts(Benchmark.measure{1000.times { tcp_listener_stats(addrs) }})
174     end
176     wrb.syswrite('.' * (nr_proc * 2)) # broadcast a wakeup
177     statuses = Process.waitall
178     statuses.each { |(pid,status)| assert status.success?, status.inspect }
179   end if ENV["STRESS"].to_i != 0
181 private
183   # Stolen from Unicorn, also a version of this is used by the Rainbows!
184   # test suite.
185   # unused_port provides an unused port on +addr+ usable for TCP that is
186   # guaranteed to be unused across all compatible tests on that system.  It
187   # prevents race conditions by using a lock file other tests
188   # will see.  This is required if you perform several builds in parallel
189   # with a continuous integration system or run tests in parallel via
190   # gmake.  This is NOT guaranteed to be race-free if you run other
191   # systems that bind to random ports for testing (but the window
192   # for a race condition is very small).  You may also set UNICORN_TEST_ADDR
193   # to override the default test address (127.0.0.1).
194   def unused_port(addr = TEST_ADDR)
195     retries = 100
196     base = 5000
197     port = sock = nil
198     begin
199       begin
200         port = base + rand(32768 - base)
201         while port == 8080
202           port = base + rand(32768 - base)
203         end
205         sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
206         sock.bind(Socket.pack_sockaddr_in(port, addr))
207         sock.listen(5)
208       rescue Errno::EADDRINUSE, Errno::EACCES
209         sock.close rescue nil
210         retry if (retries -= 1) >= 0
211       end
213       # since we'll end up closing the random port we just got, there's a race
214       # condition could allow the random port we just chose to reselect itself
215       # when running tests in parallel with gmake.  Create a lock file while
216       # we have the port here to ensure that does not happen .
217       lock_path = "#{Dir::tmpdir}/unicorn_test.#{addr}:#{port}.lock"
218       lock = File.open(lock_path, File::WRONLY|File::CREAT|File::EXCL, 0600)
219       at_exit { File.unlink(lock_path) rescue nil }
220     rescue Errno::EEXIST
221       sock.close rescue nil
222       retry
223     end
224     sock.close rescue nil
225     port
226   end
228 end if RUBY_PLATFORM =~ /linux/