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.
- data/.document +7 -0
- data/.gitignore +13 -0
- data/COPYING +165 -0
- data/GIT-VERSION-GEN +40 -0
- data/GNUmakefile +172 -0
- data/LICENSE +16 -0
- data/README +117 -0
- data/Rakefile +156 -0
- data/TODO +2 -0
- data/examples/linux-tcp-listener-stats.rb +28 -0
- data/ext/raindrops/extconf.rb +11 -0
- data/ext/raindrops/linux_inet_diag.c +342 -0
- data/ext/raindrops/raindrops.c +192 -0
- data/lib/raindrops.rb +35 -0
- data/lib/raindrops/linux.rb +55 -0
- data/lib/raindrops/middleware.rb +75 -0
- data/lib/raindrops/struct.rb +47 -0
- data/raindrops.gemspec +38 -0
- data/setup.rb +1586 -0
- data/test/test_linux.rb +228 -0
- data/test/test_linux_middleware.rb +59 -0
- data/test/test_middleware.rb +111 -0
- data/test/test_raindrops.rb +95 -0
- data/test/test_raindrops_gc.rb +13 -0
- data/test/test_struct.rb +54 -0
- metadata +103 -0
data/test/test_linux.rb
ADDED
@@ -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
|