twirl 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ module Twirl
2
+ VERSION = "0.1.0"
3
+ end
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