raindrops 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,228 @@
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
8
+
9
+ class TestLinux < Test::Unit::TestCase
10
+ include Raindrops::Linux
11
+
12
+ TEST_ADDR = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
13
+
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
22
+
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
28
+
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
34
+
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
41
+
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
51
+
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
57
+
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
64
+
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
77
+
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
85
+
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
93
+
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
101
+
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
109
+
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
117
+
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
125
+
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
136
+
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
147
+
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
158
+
159
+ assert_equal('.' * (nr_proc * 2), rda.read(nr_proc * 2))
160
+
161
+ rda.close
162
+ stats = tcp_listener_stats(addrs)
163
+ expect = { addr => Raindrops::ListenStats[nr_sock * nr_proc, 0] }
164
+ assert_equal expect, stats
165
+
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
170
+
171
+ if ENV["BENCHMARK"].to_i != 0
172
+ require 'benchmark'
173
+ puts(Benchmark.measure{1000.times { tcp_listener_stats(addrs) }})
174
+ end
175
+
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
180
+
181
+ private
182
+
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
204
+
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
212
+
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
227
+
228
+ end if RUBY_PLATFORM =~ /linux/
@@ -0,0 +1,59 @@
1
+ # -*- encoding: binary -*-
2
+ require 'test/unit'
3
+ require 'tempfile'
4
+ require 'raindrops'
5
+ require 'socket'
6
+ $stderr.sync = $stdout.sync = true
7
+
8
+ class TestLinuxMiddleware < Test::Unit::TestCase
9
+
10
+ def setup
11
+ @resp_headers = { 'Content-Type' => 'text/plain', 'Content-Length' => '0' }
12
+ @response = [ 200, @resp_headers, [] ]
13
+ @app = lambda { |env| @response }
14
+ end
15
+
16
+ def test_unix_listener
17
+ tmp = Tempfile.new("")
18
+ File.unlink(tmp.path)
19
+ us = UNIXServer.new(tmp.path)
20
+ app = Raindrops::Middleware.new(@app, :listeners => [tmp.path])
21
+ linux_extra = "#{tmp.path} active: 0\n#{tmp.path} queued: 0\n"
22
+ response = app.call("PATH_INFO" => "/_raindrops")
23
+
24
+ expect = [
25
+ 200,
26
+ {
27
+ "Content-Type" => "text/plain",
28
+ "Content-Length" => (22 + linux_extra.size).to_s
29
+ },
30
+ [
31
+ "calling: 0\nwriting: 0\n#{linux_extra}" \
32
+ ]
33
+ ]
34
+ assert_equal expect, response
35
+ end
36
+
37
+ def test_unix_listener_queued
38
+ tmp = Tempfile.new("")
39
+ File.unlink(tmp.path)
40
+ us = UNIXServer.new(tmp.path)
41
+ uc = UNIXSocket.new(tmp.path)
42
+ app = Raindrops::Middleware.new(@app, :listeners => [tmp.path])
43
+ linux_extra = "#{tmp.path} active: 0\n#{tmp.path} queued: 1\n"
44
+ response = app.call("PATH_INFO" => "/_raindrops")
45
+
46
+ expect = [
47
+ 200,
48
+ {
49
+ "Content-Type" => "text/plain",
50
+ "Content-Length" => (22 + linux_extra.size).to_s
51
+ },
52
+ [
53
+ "calling: 0\nwriting: 0\n#{linux_extra}" \
54
+ ]
55
+ ]
56
+ assert_equal expect, response
57
+ end
58
+
59
+ end if RUBY_PLATFORM =~ /linux/
@@ -0,0 +1,111 @@
1
+ # -*- encoding: binary -*-
2
+ require 'test/unit'
3
+ require 'raindrops'
4
+
5
+ class TestMiddleware < Test::Unit::TestCase
6
+
7
+ def setup
8
+ @resp_headers = { 'Content-Type' => 'text/plain', 'Content-Length' => '0' }
9
+ @response = [ 200, @resp_headers, [] ]
10
+ @app = lambda { |env| @response }
11
+ end
12
+
13
+ def test_setup
14
+ app = Raindrops::Middleware.new(@app)
15
+ response = app.call({})
16
+ assert_equal @response[0,2], response[0,2]
17
+ assert response.last.kind_of?(Raindrops::Middleware)
18
+ assert response.last.object_id != app.object_id
19
+ tmp = []
20
+ response.last.each { |y| tmp << y }
21
+ assert tmp.empty?
22
+ end
23
+
24
+ def test_alt_stats
25
+ stats = Raindrops::Middleware::Stats.new
26
+ app = lambda { |env|
27
+ if (stats.writing == 0 && stats.calling == 1)
28
+ @app.call(env)
29
+ else
30
+ [ 500, @resp_headers, [] ]
31
+ end
32
+ }
33
+ app = Raindrops::Middleware.new(app, :stats => stats)
34
+ response = app.call({})
35
+ assert_equal 0, stats.calling
36
+ assert_equal 1, stats.writing
37
+ assert_equal 200, response[0]
38
+ assert response.last.kind_of?(Raindrops::Middleware)
39
+ tmp = []
40
+ response.last.each do |y|
41
+ assert_equal 1, stats.writing
42
+ tmp << y
43
+ end
44
+ assert tmp.empty?
45
+ end
46
+
47
+ def test_default_endpoint
48
+ app = Raindrops::Middleware.new(@app)
49
+ response = app.call("PATH_INFO" => "/_raindrops")
50
+ expect = [
51
+ 200,
52
+ { "Content-Type" => "text/plain", "Content-Length" => "22" },
53
+ [ "calling: 0\nwriting: 0\n" ]
54
+ ]
55
+ assert_equal expect, response
56
+ end
57
+
58
+ def test_alt_endpoint
59
+ app = Raindrops::Middleware.new(@app, :path => "/foo")
60
+ response = app.call("PATH_INFO" => "/foo")
61
+ expect = [
62
+ 200,
63
+ { "Content-Type" => "text/plain", "Content-Length" => "22" },
64
+ [ "calling: 0\nwriting: 0\n" ]
65
+ ]
66
+ assert_equal expect, response
67
+ end
68
+
69
+ def test_concurrent
70
+ rda, wra = IO.pipe
71
+ rdb, wrb = IO.pipe
72
+ app = lambda do |env|
73
+ wrb.close
74
+ wra.syswrite('.')
75
+ wra.close
76
+
77
+ # wait until parent has run app.call for stats endpoint
78
+ rdb.read
79
+ @app.call(env)
80
+ end
81
+ app = Raindrops::Middleware.new(app)
82
+
83
+ pid = fork { app.call({}) }
84
+ rdb.close
85
+
86
+ # wait til child is running in app.call
87
+ assert_equal '.', rda.sysread(1)
88
+ rda.close
89
+
90
+ response = app.call("PATH_INFO" => "/_raindrops")
91
+ expect = [
92
+ 200,
93
+ { "Content-Type" => "text/plain", "Content-Length" => "22" },
94
+ [ "calling: 1\nwriting: 0\n" ]
95
+ ]
96
+ assert_equal expect, response
97
+ wrb.close # unblock child process
98
+ assert Process.waitpid2(pid).last.success?
99
+
100
+ # we didn't call close the body in the forked child, so it'll always be
101
+ # marked as writing, a real server would close the body
102
+ response = app.call("PATH_INFO" => "/_raindrops")
103
+ expect = [
104
+ 200,
105
+ { "Content-Type" => "text/plain", "Content-Length" => "22" },
106
+ [ "calling: 0\nwriting: 1\n" ]
107
+ ]
108
+ assert_equal expect, response
109
+ end
110
+
111
+ end
@@ -0,0 +1,95 @@
1
+ # -*- encoding: binary -*-
2
+ require 'test/unit'
3
+ require 'raindrops'
4
+
5
+ class TestRaindrops < Test::Unit::TestCase
6
+
7
+ def test_size
8
+ rd = Raindrops.new(4)
9
+ assert_equal 4, rd.size
10
+ end
11
+
12
+ def test_ary
13
+ rd = Raindrops.new(4)
14
+ assert_equal [0, 0, 0, 0] , rd.to_ary
15
+ end
16
+
17
+ def test_incr_no_args
18
+ rd = Raindrops.new(4)
19
+ assert_equal 1, rd.incr(0)
20
+ assert_equal [1, 0, 0, 0], rd.to_ary
21
+ end
22
+
23
+ def test_incr_args
24
+ rd = Raindrops.new(4)
25
+ assert_equal 6, rd.incr(3, 6)
26
+ assert_equal [0, 0, 0, 6], rd.to_ary
27
+ end
28
+
29
+ def test_decr_args
30
+ rd = Raindrops.new(4)
31
+ rd[3] = 6
32
+ assert_equal 5, rd.decr(3, 1)
33
+ assert_equal [0, 0, 0, 5], rd.to_ary
34
+ end
35
+
36
+ def test_incr_shared
37
+ rd = Raindrops.new(2)
38
+ 5.times do
39
+ pid = fork { rd.incr(1) }
40
+ _, status = Process.waitpid2(pid)
41
+ assert status.success?
42
+ end
43
+ assert_equal [0, 5], rd.to_ary
44
+ end
45
+
46
+ def test_incr_decr
47
+ rd = Raindrops.new(1)
48
+ fork { 1000000.times { rd.incr(0) } }
49
+ 1000.times { rd.decr(0) }
50
+ statuses = Process.waitall
51
+ statuses.each { |pid, status| assert status.success? }
52
+ assert_equal [999000], rd.to_ary
53
+ end
54
+
55
+ def test_bad_incr
56
+ rd = Raindrops.new(1)
57
+ assert_raises(ArgumentError) { rd.incr(-1) }
58
+ assert_raises(ArgumentError) { rd.incr(2) }
59
+ assert_raises(ArgumentError) { rd.incr(0xffffffff) }
60
+ end
61
+
62
+ def test_dup
63
+ @rd = Raindrops.new(1)
64
+ rd = @rd.dup
65
+ assert_equal 1, @rd.incr(0)
66
+ assert_equal 1, rd.incr(0)
67
+ assert_equal 2, rd.incr(0)
68
+ assert_equal 2, rd[0]
69
+ assert_equal 1, @rd[0]
70
+ end
71
+
72
+ def test_clone
73
+ @rd = Raindrops.new(1)
74
+ rd = @rd.clone
75
+ assert_equal 1, @rd.incr(0)
76
+ assert_equal 1, rd.incr(0)
77
+ assert_equal 2, rd.incr(0)
78
+ assert_equal 2, rd[0]
79
+ assert_equal 1, @rd[0]
80
+ end
81
+
82
+ def test_big
83
+ expect = 256.times.map { 0 }
84
+ rd = Raindrops.new(256)
85
+ assert_equal expect, rd.to_ary
86
+ assert_nothing_raised { rd[255] = 5 }
87
+ assert_equal 5, rd[255]
88
+ assert_nothing_raised { rd[2] = 2 }
89
+
90
+ expect[255] = 5
91
+ expect[2] = 2
92
+ assert_equal expect, rd.to_ary
93
+ end
94
+
95
+ end