twirl 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/.gitignore +17 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +75 -0
- data/Rakefile +1 -0
- data/examples/server.rb +37 -0
- data/lib/twirl.rb +8 -0
- data/lib/twirl/cluster.rb +317 -0
- data/lib/twirl/instrumentation/log_subscriber.rb +35 -0
- data/lib/twirl/instrumentation/statsd.rb +6 -0
- data/lib/twirl/instrumentation/statsd_subscriber.rb +28 -0
- data/lib/twirl/instrumentation/subscriber.rb +60 -0
- data/lib/twirl/instrumenters/memory.rb +26 -0
- data/lib/twirl/instrumenters/noop.rb +9 -0
- data/lib/twirl/item.rb +49 -0
- data/lib/twirl/server.rb +253 -0
- data/lib/twirl/version.rb +3 -0
- data/script/bootstrap +21 -0
- data/script/kestrel +34 -0
- data/script/release +42 -0
- data/script/test +25 -0
- data/script/watch +29 -0
- data/test/cluster_test.rb +469 -0
- data/test/helper.rb +65 -0
- data/test/instrumentation/log_subscriber_test.rb +51 -0
- data/test/instrumentation/statsd_test.rb +173 -0
- data/test/instrumenters/memory_test.rb +22 -0
- data/test/instrumenters/noop_test.rb +16 -0
- data/test/integration/cluster_test.rb +199 -0
- data/test/item_test.rb +43 -0
- data/test/support/fake_udp_socket.rb +27 -0
- data/test/twirl_test.rb +11 -0
- data/twirl.gemspec +23 -0
- metadata +121 -0
data/script/bootstrap
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
#/ Usage: bootstrap [bundle options]
|
3
|
+
#/
|
4
|
+
#/ Bundle install the dependencies.
|
5
|
+
#/
|
6
|
+
#/ Examples:
|
7
|
+
#/
|
8
|
+
#/ bootstrap
|
9
|
+
#/ bootstrap --local
|
10
|
+
#/
|
11
|
+
|
12
|
+
set -e
|
13
|
+
cd $(dirname "$0")/..
|
14
|
+
|
15
|
+
[ "$1" = "--help" -o "$1" = "-h" -o "$1" = "help" ] && {
|
16
|
+
grep '^#/' <"$0"| cut -c4-
|
17
|
+
exit 0
|
18
|
+
}
|
19
|
+
|
20
|
+
rm -rf .bundle/{binstubs,config}
|
21
|
+
bundle install --binstubs .bundle/binstubs --path .bundle "$@"
|
data/script/kestrel
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
require "bundler/setup"
|
5
|
+
require "pathname"
|
6
|
+
root = Pathname(__FILE__).join("..", "..").expand_path
|
7
|
+
|
8
|
+
$LOAD_PATH.unshift root.join("lib")
|
9
|
+
require "twirl/server"
|
10
|
+
|
11
|
+
{
|
12
|
+
"kestrel1" => {
|
13
|
+
memcache_port: 9444,
|
14
|
+
thrift_port: 9445,
|
15
|
+
text_port: 9446,
|
16
|
+
admin_port: 9447,
|
17
|
+
},
|
18
|
+
"kestrel2" => {
|
19
|
+
memcache_port: 9544,
|
20
|
+
thrift_port: 9545,
|
21
|
+
text_port: 9546,
|
22
|
+
admin_port: 9547,
|
23
|
+
},
|
24
|
+
}.each do |dir, options|
|
25
|
+
server = Twirl::Server.new(root.join("tmp", dir), options)
|
26
|
+
puts "\n\nMaking sure kestrel is running on the following ports: #{options.inspect}"
|
27
|
+
if ARGV[0] == "stop"
|
28
|
+
server.stop
|
29
|
+
else
|
30
|
+
server.start
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
puts "\n\nYou can now run the tests."
|
data/script/release
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
#/ Usage: release
|
3
|
+
#/
|
4
|
+
#/ Tag the version in the repo and push the gem.
|
5
|
+
#/
|
6
|
+
|
7
|
+
set -e
|
8
|
+
cd $(dirname "$0")/..
|
9
|
+
|
10
|
+
[ "$1" = "--help" -o "$1" = "-h" -o "$1" = "help" ] && {
|
11
|
+
grep '^#/' <"$0"| cut -c4-
|
12
|
+
exit 0
|
13
|
+
}
|
14
|
+
|
15
|
+
gem_name=twirl
|
16
|
+
|
17
|
+
# Build a new gem archive.
|
18
|
+
rm -rf $gem_name-*.gem
|
19
|
+
gem build -q $gem_name.gemspec
|
20
|
+
|
21
|
+
# Make sure we're on the master branch.
|
22
|
+
(git branch | grep -q '* master') || {
|
23
|
+
echo "Only release from the master branch."
|
24
|
+
exit 1
|
25
|
+
}
|
26
|
+
|
27
|
+
# Figure out what version we're releasing.
|
28
|
+
tag=v`ls $gem_name-*.gem | sed "s/^$gem_name-\(.*\)\.gem$/\1/"`
|
29
|
+
|
30
|
+
echo "Releasing $tag"
|
31
|
+
|
32
|
+
# Make sure we haven't released this version before.
|
33
|
+
git fetch -t origin
|
34
|
+
|
35
|
+
(git tag -l | grep -q "$tag") && {
|
36
|
+
echo "Whoops, there's already a '${tag}' tag."
|
37
|
+
exit 1
|
38
|
+
}
|
39
|
+
|
40
|
+
# Tag it and bag it.
|
41
|
+
gem push $gem_name-*.gem && git tag "$tag" &&
|
42
|
+
git push origin master && git push origin "$tag"
|
data/script/test
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
#/ Usage: test [individual test file]
|
3
|
+
#/
|
4
|
+
#/ Bootstrap and run all tests or an individual test.
|
5
|
+
#/
|
6
|
+
#/ Examples:
|
7
|
+
#/
|
8
|
+
#/ # run all tests
|
9
|
+
#/ test
|
10
|
+
#/
|
11
|
+
#/ # run individual test
|
12
|
+
#/ test test/twirl_test.rb
|
13
|
+
#/
|
14
|
+
|
15
|
+
set -e
|
16
|
+
cd $(dirname "$0")/..
|
17
|
+
|
18
|
+
[ "$1" = "--help" -o "$1" = "-h" -o "$1" = "help" ] && {
|
19
|
+
grep '^#/' <"$0"| cut -c4-
|
20
|
+
exit 0
|
21
|
+
}
|
22
|
+
|
23
|
+
script/bootstrap && ruby -I lib -I test -r rubygems \
|
24
|
+
-e 'require "bundler/setup"' \
|
25
|
+
-e '(ARGV.empty? ? Dir["test/**/*_test.rb"] : ARGV).each { |f| load f }' -- "$@"
|
data/script/watch
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#/ Usage: watch
|
3
|
+
#/
|
4
|
+
#/ Run the tests whenever any relevant files change.
|
5
|
+
#/
|
6
|
+
|
7
|
+
require "pathname"
|
8
|
+
require "rubygems"
|
9
|
+
require "bundler/setup"
|
10
|
+
|
11
|
+
# Put us where we belong, in the root dir of the project.
|
12
|
+
Dir.chdir Pathname.new(__FILE__).realpath + "../.."
|
13
|
+
|
14
|
+
# Run the tests to start.
|
15
|
+
system "clear; script/test"
|
16
|
+
|
17
|
+
require "rb-fsevent"
|
18
|
+
|
19
|
+
IgnoreRegex = /\/fixtures/
|
20
|
+
|
21
|
+
fs = FSEvent.new
|
22
|
+
fs.watch ["app", "lib", "test"], latency: 1 do |args|
|
23
|
+
unless args.first =~ IgnoreRegex
|
24
|
+
system "clear"
|
25
|
+
puts "#{args.first} changed..."
|
26
|
+
system "script/test"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
fs.run
|
@@ -0,0 +1,469 @@
|
|
1
|
+
require "helper"
|
2
|
+
require "twirl/cluster"
|
3
|
+
|
4
|
+
class ClusterTest < Minitest::Test
|
5
|
+
def test_initialize
|
6
|
+
clients = [
|
7
|
+
KJess::Client.new(host: "localhost", port: 1),
|
8
|
+
KJess::Client.new(host: "localhost", port: 2),
|
9
|
+
]
|
10
|
+
cluster = Twirl::Cluster.new(clients)
|
11
|
+
assert_equal 0, cluster.command_count
|
12
|
+
assert_equal 0, cluster.client_index
|
13
|
+
assert_equal 100, cluster.commands_per_client
|
14
|
+
assert_equal 5, cluster.retries
|
15
|
+
assert_equal [KJess::NetworkError, KJess::ServerError],
|
16
|
+
cluster.retryable_errors
|
17
|
+
assert_equal Twirl::Instrumenters::Noop, cluster.instrumenter
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_initialize_shuffles_clients
|
21
|
+
clients = Minitest::Mock.new
|
22
|
+
clients.expect :shuffle, :shuffle_result
|
23
|
+
cluster = Twirl::Cluster.new(clients)
|
24
|
+
assert_equal :shuffle_result, cluster.instance_variable_get("@clients")
|
25
|
+
clients.verify
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_overriding_instrumenter
|
29
|
+
instrumenter = Object.new
|
30
|
+
cluster = Twirl::Cluster.new([], instrumenter: instrumenter)
|
31
|
+
assert_equal instrumenter, cluster.instrumenter
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_overriding_retryable_errors
|
35
|
+
retryable_errors = StandardError
|
36
|
+
cluster = Twirl::Cluster.new([], retryable_errors: retryable_errors)
|
37
|
+
assert_equal retryable_errors, cluster.retryable_errors
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_overriding_retries
|
41
|
+
cluster = Twirl::Cluster.new([], retries: 1)
|
42
|
+
assert_equal 1, cluster.retries
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_enumeration
|
46
|
+
cluster = build(:mock_cluster)
|
47
|
+
clients = []
|
48
|
+
cluster.each_with_index do |client, index|
|
49
|
+
client.expect :port, index
|
50
|
+
clients << client
|
51
|
+
end
|
52
|
+
assert_equal clients, cluster.to_a
|
53
|
+
assert_equal [0, 1, 2], cluster.map(&:port)
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_size
|
57
|
+
cluster = build(:mock_cluster)
|
58
|
+
assert_equal 3, cluster.size
|
59
|
+
assert_equal 3, cluster.length
|
60
|
+
assert_equal 3, cluster.count
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_bracket_access
|
64
|
+
clients = [
|
65
|
+
KJess::Client.new(host: "localhost", port: 1),
|
66
|
+
KJess::Client.new(host: "localhost", port: 2),
|
67
|
+
KJess::Client.new(host: "localhost", port: 3),
|
68
|
+
]
|
69
|
+
cluster = Twirl::Cluster.new(clients)
|
70
|
+
shuffled_clients = cluster.instance_variable_get("@clients")
|
71
|
+
assert_equal shuffled_clients[0], cluster[0]
|
72
|
+
assert_equal shuffled_clients[1], cluster[1]
|
73
|
+
assert_equal shuffled_clients[2], cluster[2]
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_set_rotates_based_on_commands_per_client
|
77
|
+
args = ["testing", "data", 0]
|
78
|
+
cluster = build(:mock_cluster, commands_per_client: 1)
|
79
|
+
|
80
|
+
cluster.each do |client|
|
81
|
+
client.expect :set, true, args
|
82
|
+
end
|
83
|
+
|
84
|
+
assert_equal true, cluster.set(*args)
|
85
|
+
assert_equal true, cluster.set(*args)
|
86
|
+
assert_equal true, cluster.set(*args)
|
87
|
+
|
88
|
+
cluster.each(&:verify)
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_get_returns_item_if_client_returns_item
|
92
|
+
args = ["testing", {}]
|
93
|
+
cluster = build(:mock_cluster)
|
94
|
+
cluster[0].expect :get, "data", args
|
95
|
+
|
96
|
+
item = cluster.get(*args)
|
97
|
+
assert_instance_of Twirl::Item, item
|
98
|
+
assert_equal "testing", item.key
|
99
|
+
assert_equal "data", item.value
|
100
|
+
|
101
|
+
cluster.each(&:verify)
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_get_returns_nil_if_client_returns_nil
|
105
|
+
args = ["testing", {}]
|
106
|
+
cluster = build(:mock_cluster)
|
107
|
+
cluster[0].expect :get, nil, args
|
108
|
+
|
109
|
+
assert_nil cluster.get(*args)
|
110
|
+
|
111
|
+
cluster.each(&:verify)
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_get_passes_options_to_client
|
115
|
+
args = ["testing", {open: true}]
|
116
|
+
cluster = build(:mock_cluster)
|
117
|
+
cluster[0].expect :get, nil, args
|
118
|
+
cluster.get(*args)
|
119
|
+
cluster.each(&:verify)
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_get_rotates_based_on_commands_per_client
|
123
|
+
args = ["testing", {}]
|
124
|
+
cluster = build(:mock_cluster, commands_per_client: 1)
|
125
|
+
|
126
|
+
cluster[0].expect :get, "result-1", args
|
127
|
+
cluster[1].expect :get, "result-2", args
|
128
|
+
cluster[2].expect :get, "result-3", args
|
129
|
+
|
130
|
+
assert_equal "result-1", cluster.get(*args).value
|
131
|
+
assert_equal "result-2", cluster.get(*args).value
|
132
|
+
assert_equal "result-3", cluster.get(*args).value
|
133
|
+
|
134
|
+
cluster.each(&:verify)
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_get_rotates_client_if_nil
|
138
|
+
args = ["testing", {}]
|
139
|
+
cluster = build(:mock_cluster, commands_per_client: 1_000)
|
140
|
+
|
141
|
+
cluster[0].expect :get, nil, args
|
142
|
+
cluster[1].expect :get, nil, args
|
143
|
+
cluster[2].expect :get, "data", args
|
144
|
+
|
145
|
+
assert_nil cluster.get(*args)
|
146
|
+
assert_nil cluster.get(*args)
|
147
|
+
assert_equal "data", cluster.get(*args).value
|
148
|
+
|
149
|
+
cluster.each(&:verify)
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_reserve
|
153
|
+
cluster = build(:mock_cluster)
|
154
|
+
cluster[0].expect :reserve, "data", ["testing", {}]
|
155
|
+
assert_equal "data", cluster.reserve("testing").value
|
156
|
+
cluster.each(&:verify)
|
157
|
+
end
|
158
|
+
|
159
|
+
def test_reserve_returns_nil_if_client_returns_nil
|
160
|
+
cluster = build(:mock_cluster)
|
161
|
+
cluster[0].expect :reserve, nil, ["testing", {}]
|
162
|
+
assert_nil cluster.reserve("testing")
|
163
|
+
cluster.each(&:verify)
|
164
|
+
end
|
165
|
+
|
166
|
+
def test_reserve_rotates_client_if_nil
|
167
|
+
args = ["testing", {}]
|
168
|
+
cluster = build(:mock_cluster, commands_per_client: 1_000)
|
169
|
+
|
170
|
+
cluster[0].expect :reserve, nil, args
|
171
|
+
cluster[1].expect :reserve, nil, args
|
172
|
+
cluster[2].expect :reserve, "data", args
|
173
|
+
|
174
|
+
assert_nil cluster.reserve(*args)
|
175
|
+
assert_nil cluster.reserve(*args)
|
176
|
+
assert_equal "data", cluster.reserve(*args).value
|
177
|
+
|
178
|
+
cluster.each(&:verify)
|
179
|
+
end
|
180
|
+
|
181
|
+
def test_peek
|
182
|
+
cluster = build(:mock_cluster)
|
183
|
+
cluster[0].expect :peek, "data", ["testing"]
|
184
|
+
assert_equal "data", cluster.peek("testing").value
|
185
|
+
cluster.each(&:verify)
|
186
|
+
end
|
187
|
+
|
188
|
+
def test_peek_returns_nil_if_client_returns_nil
|
189
|
+
cluster = build(:mock_cluster)
|
190
|
+
cluster[0].expect :peek, nil, ["testing"]
|
191
|
+
assert_nil cluster.peek("testing")
|
192
|
+
cluster.each(&:verify)
|
193
|
+
end
|
194
|
+
|
195
|
+
def test_peek_rotates_client_if_nil
|
196
|
+
args = ["testing"]
|
197
|
+
cluster = build(:mock_cluster, commands_per_client: 1_000)
|
198
|
+
|
199
|
+
cluster[0].expect :peek, nil, args
|
200
|
+
cluster[1].expect :peek, nil, args
|
201
|
+
cluster[2].expect :peek, "data", args
|
202
|
+
|
203
|
+
assert_nil cluster.peek(*args)
|
204
|
+
assert_nil cluster.peek(*args)
|
205
|
+
assert_equal "data", cluster.peek(*args).value
|
206
|
+
|
207
|
+
cluster.each(&:verify)
|
208
|
+
end
|
209
|
+
|
210
|
+
def test_flush
|
211
|
+
cluster = build(:mock_cluster)
|
212
|
+
cluster.size.times do |index|
|
213
|
+
cluster[index].expect :host, "localhost"
|
214
|
+
cluster[index].expect :port, (index + 1).to_s
|
215
|
+
cluster[index].expect :flush, index % 2 == 0, ["foo"]
|
216
|
+
end
|
217
|
+
expected = {
|
218
|
+
"localhost:1" => true,
|
219
|
+
"localhost:2" => false,
|
220
|
+
"localhost:3" => true,
|
221
|
+
}
|
222
|
+
assert_equal expected, cluster.flush("foo")
|
223
|
+
cluster.each(&:verify)
|
224
|
+
end
|
225
|
+
|
226
|
+
def test_flush_all
|
227
|
+
cluster = build(:mock_cluster)
|
228
|
+
cluster.size.times do |index|
|
229
|
+
cluster[index].expect :host, "localhost"
|
230
|
+
cluster[index].expect :port, (index + 1).to_s
|
231
|
+
cluster[index].expect :flush_all, index % 2 == 0
|
232
|
+
end
|
233
|
+
expected = {
|
234
|
+
"localhost:1" => true,
|
235
|
+
"localhost:2" => false,
|
236
|
+
"localhost:3" => true,
|
237
|
+
}
|
238
|
+
assert_equal expected, cluster.flush_all
|
239
|
+
cluster.each(&:verify)
|
240
|
+
end
|
241
|
+
|
242
|
+
def test_disconnect
|
243
|
+
cluster = build(:mock_cluster)
|
244
|
+
cluster.size.times do |index|
|
245
|
+
cluster[index].expect :disconnect, nil
|
246
|
+
end
|
247
|
+
cluster.disconnect
|
248
|
+
cluster.each(&:verify)
|
249
|
+
end
|
250
|
+
|
251
|
+
def test_version
|
252
|
+
cluster = build(:mock_cluster)
|
253
|
+
cluster.size.times do |index|
|
254
|
+
cluster[index].expect :host, "localhost"
|
255
|
+
cluster[index].expect :port, (index + 1).to_s
|
256
|
+
cluster[index].expect :version, "2.4.1"
|
257
|
+
end
|
258
|
+
expected = {
|
259
|
+
"localhost:1" => "2.4.1",
|
260
|
+
"localhost:2" => "2.4.1",
|
261
|
+
"localhost:3" => "2.4.1",
|
262
|
+
}
|
263
|
+
assert_equal expected, cluster.version
|
264
|
+
cluster.each(&:verify)
|
265
|
+
end
|
266
|
+
|
267
|
+
def test_version_with_one_raising_exception
|
268
|
+
cluster = build(:mock_cluster)
|
269
|
+
cluster.size.times do |index|
|
270
|
+
cluster[index].expect :host, "localhost"
|
271
|
+
cluster[index].expect :port, (index + 1).to_s
|
272
|
+
end
|
273
|
+
|
274
|
+
# force client1 to raise an error and other clients to return as normal
|
275
|
+
client1 = cluster[0]
|
276
|
+
def client1.version
|
277
|
+
raise KJess::ProtocolError
|
278
|
+
end
|
279
|
+
cluster[1].expect :version, "2.4.1"
|
280
|
+
cluster[2].expect :version, "2.4.1"
|
281
|
+
|
282
|
+
expected = {
|
283
|
+
"localhost:1" => "unavailable",
|
284
|
+
"localhost:2" => "2.4.1",
|
285
|
+
"localhost:3" => "2.4.1",
|
286
|
+
}
|
287
|
+
assert_equal expected, cluster.version
|
288
|
+
cluster.each(&:verify)
|
289
|
+
end
|
290
|
+
|
291
|
+
def test_delete
|
292
|
+
cluster = build(:mock_cluster)
|
293
|
+
cluster.size.times do |index|
|
294
|
+
cluster[index].expect :host, "localhost"
|
295
|
+
cluster[index].expect :port, (index + 1).to_s
|
296
|
+
cluster[index].expect :delete, index % 2 == 0, ["foo"]
|
297
|
+
end
|
298
|
+
expected = {
|
299
|
+
"localhost:1" => true,
|
300
|
+
"localhost:2" => false,
|
301
|
+
"localhost:3" => true,
|
302
|
+
}
|
303
|
+
assert_equal expected, cluster.delete("foo")
|
304
|
+
cluster.each(&:verify)
|
305
|
+
end
|
306
|
+
|
307
|
+
def test_ping
|
308
|
+
cluster = build(:mock_cluster)
|
309
|
+
cluster.size.times do |index|
|
310
|
+
cluster[index].expect :host, "localhost"
|
311
|
+
cluster[index].expect :port, (index + 1).to_s
|
312
|
+
cluster[index].expect :ping, index % 2 == 0
|
313
|
+
end
|
314
|
+
expected = {
|
315
|
+
"localhost:1" => true,
|
316
|
+
"localhost:2" => false,
|
317
|
+
"localhost:3" => true,
|
318
|
+
}
|
319
|
+
assert_equal expected, cluster.ping
|
320
|
+
cluster.each(&:verify)
|
321
|
+
end
|
322
|
+
|
323
|
+
def test_shutdown
|
324
|
+
cluster = build(:mock_cluster)
|
325
|
+
cluster.size.times do |index|
|
326
|
+
cluster[index].expect :shutdown, nil
|
327
|
+
end
|
328
|
+
cluster.shutdown
|
329
|
+
cluster.each(&:verify)
|
330
|
+
end
|
331
|
+
|
332
|
+
def test_reload
|
333
|
+
cluster = build(:mock_cluster)
|
334
|
+
cluster.size.times do |index|
|
335
|
+
cluster[index].expect :host, "localhost"
|
336
|
+
cluster[index].expect :port, (index + 1).to_s
|
337
|
+
cluster[index].expect :reload, index % 2 == 0
|
338
|
+
end
|
339
|
+
expected = {
|
340
|
+
"localhost:1" => true,
|
341
|
+
"localhost:2" => false,
|
342
|
+
"localhost:3" => true,
|
343
|
+
}
|
344
|
+
assert_equal expected, cluster.reload
|
345
|
+
cluster.each(&:verify)
|
346
|
+
end
|
347
|
+
|
348
|
+
def test_quit
|
349
|
+
cluster = build(:mock_cluster)
|
350
|
+
cluster.size.times do |index|
|
351
|
+
cluster[index].expect :host, "localhost"
|
352
|
+
cluster[index].expect :port, (index + 1).to_s
|
353
|
+
cluster[index].expect :quit, index % 2 == 0
|
354
|
+
end
|
355
|
+
expected = {
|
356
|
+
"localhost:1" => true,
|
357
|
+
"localhost:2" => false,
|
358
|
+
"localhost:3" => true,
|
359
|
+
}
|
360
|
+
assert_equal expected, cluster.quit
|
361
|
+
cluster.each(&:verify)
|
362
|
+
end
|
363
|
+
|
364
|
+
def test_stats
|
365
|
+
cluster = build(:mock_cluster)
|
366
|
+
cluster.size.times do |index|
|
367
|
+
cluster[index].expect :host, "localhost"
|
368
|
+
cluster[index].expect :port, (index + 1).to_s
|
369
|
+
cluster[index].expect :stats, {items: index + 1}
|
370
|
+
end
|
371
|
+
expected = {
|
372
|
+
"localhost:1" => {items: 1},
|
373
|
+
"localhost:2" => {items: 2},
|
374
|
+
"localhost:3" => {items: 3},
|
375
|
+
}
|
376
|
+
assert_equal expected, cluster.stats
|
377
|
+
cluster.each(&:verify)
|
378
|
+
end
|
379
|
+
|
380
|
+
def test_rotation
|
381
|
+
cluster = build(:mock_cluster, commands_per_client: 3)
|
382
|
+
assert_equal 0, cluster.command_count
|
383
|
+
|
384
|
+
counts = 7.times.map {
|
385
|
+
cluster.client
|
386
|
+
cluster.command_count
|
387
|
+
}
|
388
|
+
|
389
|
+
assert_equal [1, 2, 3, 1, 2, 3, 1], counts
|
390
|
+
end
|
391
|
+
|
392
|
+
RetriableMethods = {
|
393
|
+
get: [["testing"], "data"],
|
394
|
+
reserve: [["testing"], "data"],
|
395
|
+
peek: [["testing"], "data"],
|
396
|
+
set: [["testing", "data"], true],
|
397
|
+
}
|
398
|
+
|
399
|
+
RecoverableExceptions = [
|
400
|
+
KJess::NetworkError,
|
401
|
+
KJess::ServerError,
|
402
|
+
]
|
403
|
+
|
404
|
+
def test_get_retries_network_errors
|
405
|
+
cluster = build(:cluster)
|
406
|
+
client = cluster[0]
|
407
|
+
|
408
|
+
RetriableMethods.each do |op, info|
|
409
|
+
args, result = info
|
410
|
+
RecoverableExceptions.each do |exception|
|
411
|
+
client.instance_eval <<-EOC
|
412
|
+
def client.#{op}(*args)
|
413
|
+
@counter ||= 0
|
414
|
+
if @counter < 4
|
415
|
+
@counter += 1
|
416
|
+
raise #{exception}
|
417
|
+
else
|
418
|
+
#{result.inspect}
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
def client.reset
|
423
|
+
@counter = 0
|
424
|
+
end
|
425
|
+
EOC
|
426
|
+
|
427
|
+
begin
|
428
|
+
op_result = cluster.send(op, *args)
|
429
|
+
ensure
|
430
|
+
client.reset
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
def test_too_many_retries_raises
|
437
|
+
cluster = build(:cluster)
|
438
|
+
client = cluster[0]
|
439
|
+
|
440
|
+
RetriableMethods.each do |op, info|
|
441
|
+
args, result = info
|
442
|
+
RecoverableExceptions.each do |exception|
|
443
|
+
client.instance_eval <<-EOC
|
444
|
+
def client.#{op}(*args)
|
445
|
+
@counter ||= 0
|
446
|
+
if @counter < 5
|
447
|
+
@counter += 1
|
448
|
+
raise #{exception}
|
449
|
+
else
|
450
|
+
#{result.inspect}
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
def client.reset
|
455
|
+
@counter = 0
|
456
|
+
end
|
457
|
+
EOC
|
458
|
+
|
459
|
+
assert_raises exception, "#{op} raising #{exception}" do
|
460
|
+
begin
|
461
|
+
cluster.send(op, *args)
|
462
|
+
ensure
|
463
|
+
client.reset
|
464
|
+
end
|
465
|
+
end
|
466
|
+
end
|
467
|
+
end
|
468
|
+
end
|
469
|
+
end
|