qs 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bench/config.qs +4 -27
- data/bench/dispatcher.qs +24 -0
- data/bench/report.rb +80 -10
- data/bench/report.txt +10 -3
- data/bench/setup.rb +55 -0
- data/lib/qs.rb +75 -15
- data/lib/qs/client.rb +73 -22
- data/lib/qs/daemon.rb +21 -21
- data/lib/qs/daemon_data.rb +4 -4
- data/lib/qs/dispatch_job.rb +36 -0
- data/lib/qs/dispatch_job_handler.rb +79 -0
- data/lib/qs/dispatcher_queue.rb +19 -0
- data/lib/qs/error_handler.rb +12 -12
- data/lib/qs/event.rb +82 -0
- data/lib/qs/event_handler.rb +34 -0
- data/lib/qs/event_handler_test_helpers.rb +17 -0
- data/lib/qs/job.rb +19 -31
- data/lib/qs/job_handler.rb +6 -63
- data/lib/qs/{test_helpers.rb → job_handler_test_helpers.rb} +2 -2
- data/lib/qs/message.rb +29 -0
- data/lib/qs/message_handler.rb +84 -0
- data/lib/qs/payload.rb +98 -0
- data/lib/qs/payload_handler.rb +106 -54
- data/lib/qs/queue.rb +39 -6
- data/lib/qs/queue_item.rb +33 -0
- data/lib/qs/route.rb +7 -7
- data/lib/qs/runner.rb +6 -5
- data/lib/qs/test_runner.rb +41 -13
- data/lib/qs/version.rb +1 -1
- data/qs.gemspec +1 -1
- data/test/helper.rb +1 -1
- data/test/support/app_daemon.rb +77 -11
- data/test/support/factory.rb +34 -0
- data/test/system/daemon_tests.rb +146 -77
- data/test/system/queue_tests.rb +87 -0
- data/test/unit/client_tests.rb +184 -45
- data/test/unit/daemon_data_tests.rb +4 -4
- data/test/unit/daemon_tests.rb +32 -32
- data/test/unit/dispatch_job_handler_tests.rb +163 -0
- data/test/unit/dispatch_job_tests.rb +75 -0
- data/test/unit/dispatcher_queue_tests.rb +42 -0
- data/test/unit/error_handler_tests.rb +9 -9
- data/test/unit/event_handler_test_helpers_tests.rb +55 -0
- data/test/unit/event_handler_tests.rb +63 -0
- data/test/unit/event_tests.rb +162 -0
- data/test/unit/{test_helper_tests.rb → job_handler_test_helper_tests.rb} +13 -19
- data/test/unit/job_handler_tests.rb +17 -210
- data/test/unit/job_tests.rb +49 -79
- data/test/unit/message_handler_tests.rb +235 -0
- data/test/unit/message_tests.rb +64 -0
- data/test/unit/payload_handler_tests.rb +285 -86
- data/test/unit/payload_tests.rb +139 -0
- data/test/unit/qs_runner_tests.rb +6 -6
- data/test/unit/qs_tests.rb +167 -28
- data/test/unit/queue_item_tests.rb +51 -0
- data/test/unit/queue_tests.rb +126 -18
- data/test/unit/route_tests.rb +12 -13
- data/test/unit/runner_tests.rb +10 -10
- data/test/unit/test_runner_tests.rb +117 -24
- metadata +51 -21
- data/bench/queue.rb +0 -8
- data/lib/qs/redis_item.rb +0 -33
- data/test/unit/redis_item_tests.rb +0 -49
data/test/system/daemon_tests.rb
CHANGED
@@ -11,26 +11,39 @@ module Qs::Daemon
|
|
11
11
|
Qs.reset!
|
12
12
|
@qs_test_mode = ENV['QS_TEST_MODE']
|
13
13
|
ENV['QS_TEST_MODE'] = nil
|
14
|
+
Qs.config.dispatcher.queue_name = 'qs-app-dispatcher'
|
15
|
+
Qs.config.event_publisher = 'Daemon System Tests'
|
14
16
|
Qs.init
|
17
|
+
AppQueue.sync_subscriptions
|
15
18
|
@orig_config = AppDaemon.configuration.to_hash
|
16
19
|
end
|
17
20
|
teardown do
|
18
21
|
@daemon_runner.stop if @daemon_runner
|
19
22
|
AppDaemon.configuration.apply(@orig_config) # reset daemon config
|
20
|
-
Qs.redis.with
|
21
|
-
|
23
|
+
Qs.redis.with do |c|
|
24
|
+
keys = c.keys('*qs-app*')
|
25
|
+
c.pipelined{ keys.each{ |k| c.del(k) } }
|
26
|
+
end
|
22
27
|
Qs.client.clear(AppQueue.redis_key)
|
28
|
+
AppQueue.clear_subscriptions
|
23
29
|
Qs.reset!
|
24
30
|
ENV['QS_TEST_MODE'] = @qs_test_mode
|
25
31
|
end
|
26
32
|
|
33
|
+
private
|
34
|
+
|
35
|
+
def setup_app_and_dispatcher_daemon
|
36
|
+
@app_daemon = AppDaemon.new
|
37
|
+
@dispatcher_daemon = DispatcherDaemon.new
|
38
|
+
@daemon_runner = DaemonRunner.new(@app_daemon, @dispatcher_daemon)
|
39
|
+
@app_thread = @daemon_runner.start
|
40
|
+
end
|
41
|
+
|
27
42
|
end
|
28
43
|
|
29
44
|
class RunningDaemonSetupTests < SystemTests
|
30
45
|
setup do
|
31
|
-
|
32
|
-
@daemon_runner = DaemonRunner.new(@daemon)
|
33
|
-
@thread = @daemon_runner.start
|
46
|
+
setup_app_and_dispatcher_daemon
|
34
47
|
end
|
35
48
|
|
36
49
|
end
|
@@ -43,11 +56,11 @@ module Qs::Daemon
|
|
43
56
|
'key' => @key,
|
44
57
|
'value' => @value
|
45
58
|
})
|
46
|
-
@
|
59
|
+
@app_thread.join 0.5
|
47
60
|
end
|
48
61
|
|
49
62
|
should "run the job" do
|
50
|
-
assert_equal @value, Qs.redis.with{ |c| c.get(@key) }
|
63
|
+
assert_equal @value, Qs.redis.with{ |c| c.get("qs-app:#{@key}") }
|
51
64
|
end
|
52
65
|
|
53
66
|
end
|
@@ -57,12 +70,12 @@ module Qs::Daemon
|
|
57
70
|
setup do
|
58
71
|
@error_message = Factory.text
|
59
72
|
AppQueue.add('error', 'error_message' => @error_message)
|
60
|
-
@
|
73
|
+
@app_thread.join 0.5
|
61
74
|
end
|
62
75
|
|
63
76
|
should "run the configured error handler procs" do
|
64
77
|
exp = "RuntimeError: #{@error_message}"
|
65
|
-
assert_equal exp, Qs.redis.with{ |c| c.get('
|
78
|
+
assert_equal exp, Qs.redis.with{ |c| c.get('qs-app:last_job_error') }
|
66
79
|
end
|
67
80
|
|
68
81
|
end
|
@@ -71,14 +84,62 @@ module Qs::Daemon
|
|
71
84
|
desc "with a job that times out"
|
72
85
|
setup do
|
73
86
|
AppQueue.add('timeout')
|
74
|
-
@
|
87
|
+
@app_thread.join 1 # let the daemon have time to process the job
|
75
88
|
end
|
76
89
|
|
77
90
|
should "run the configured error handler procs" do
|
78
91
|
handler_class = AppHandlers::Timeout
|
79
92
|
exp = "Qs::TimeoutError: #{handler_class} timed out " \
|
80
93
|
"(#{handler_class.timeout}s)"
|
81
|
-
assert_equal exp, Qs.redis.with{ |c| c.get('
|
94
|
+
assert_equal exp, Qs.redis.with{ |c| c.get('qs-app:last_job_error') }
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
class BasicEventTests < RunningDaemonSetupTests
|
100
|
+
desc "with a basic event added"
|
101
|
+
setup do
|
102
|
+
@key, @value = [Factory.string, Factory.string]
|
103
|
+
Qs.publish('qs-app', 'basic', {
|
104
|
+
'key' => @key,
|
105
|
+
'value' => @value
|
106
|
+
})
|
107
|
+
@app_thread.join 0.5
|
108
|
+
end
|
109
|
+
|
110
|
+
should "run the event" do
|
111
|
+
assert_equal @value, Qs.redis.with{ |c| c.get("qs-app:#{@key}") }
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
class EventThatErrorsTests < RunningDaemonSetupTests
|
117
|
+
desc "with an event that errors"
|
118
|
+
setup do
|
119
|
+
@error_message = Factory.text
|
120
|
+
Qs.publish('qs-app', 'error', 'error_message' => @error_message)
|
121
|
+
@app_thread.join 0.5
|
122
|
+
end
|
123
|
+
|
124
|
+
should "run the configured error handler procs" do
|
125
|
+
exp = "RuntimeError: #{@error_message}"
|
126
|
+
assert_equal exp, Qs.redis.with{ |c| c.get('qs-app:last_event_error') }
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
class TimeoutEventTests < RunningDaemonSetupTests
|
132
|
+
desc "with an event that times out"
|
133
|
+
setup do
|
134
|
+
Qs.publish('qs-app', 'timeout')
|
135
|
+
@app_thread.join 1 # let the daemon have time to process the job
|
136
|
+
end
|
137
|
+
|
138
|
+
should "run the configured error handler procs" do
|
139
|
+
handler_class = AppHandlers::TimeoutEvent
|
140
|
+
exp = "Qs::TimeoutError: #{handler_class} timed out " \
|
141
|
+
"(#{handler_class.timeout}s)"
|
142
|
+
assert_equal exp, Qs.redis.with{ |c| c.get('qs-app:last_event_error') }
|
82
143
|
end
|
83
144
|
|
84
145
|
end
|
@@ -87,21 +148,19 @@ module Qs::Daemon
|
|
87
148
|
desc "when no workers are available"
|
88
149
|
setup do
|
89
150
|
AppDaemon.workers 0 # no workers available, don't do this
|
90
|
-
|
91
|
-
@daemon_runner = DaemonRunner.new(@daemon)
|
92
|
-
@thread = @daemon_runner.start
|
151
|
+
setup_app_and_dispatcher_daemon
|
93
152
|
end
|
94
153
|
|
95
154
|
should "shutdown when stopped" do
|
96
|
-
@
|
97
|
-
@
|
98
|
-
assert_false @
|
155
|
+
@app_daemon.stop
|
156
|
+
@app_thread.join 2 # give it time to shutdown, should be faster
|
157
|
+
assert_false @app_thread.alive?
|
99
158
|
end
|
100
159
|
|
101
160
|
should "shutdown when halted" do
|
102
|
-
@
|
103
|
-
@
|
104
|
-
assert_false @
|
161
|
+
@app_daemon.halt
|
162
|
+
@app_thread.join 2 # give it time to shutdown, should be faster
|
163
|
+
assert_false @app_thread.alive?
|
105
164
|
end
|
106
165
|
|
107
166
|
end
|
@@ -110,28 +169,31 @@ module Qs::Daemon
|
|
110
169
|
desc "without a shutdown timeout"
|
111
170
|
setup do
|
112
171
|
AppDaemon.shutdown_timeout nil # disable shutdown timeout
|
113
|
-
|
114
|
-
@daemon_runner = DaemonRunner.new(@daemon)
|
115
|
-
@thread = @daemon_runner.start
|
172
|
+
setup_app_and_dispatcher_daemon
|
116
173
|
|
117
174
|
AppQueue.add('slow')
|
118
|
-
|
175
|
+
Qs.publish('qs-app', 'slow')
|
176
|
+
@app_thread.join 1 # let the daemon have time to process the job and event
|
119
177
|
end
|
120
178
|
|
121
|
-
should "shutdown and let the job
|
122
|
-
@
|
123
|
-
@
|
124
|
-
assert_false @
|
125
|
-
assert_equal 'finished', Qs.redis.with{ |c| c.get('slow') }
|
179
|
+
should "shutdown and let the job and event finish" do
|
180
|
+
@app_daemon.stop
|
181
|
+
@app_thread.join 10 # give it time to shutdown, should be faster
|
182
|
+
assert_false @app_thread.alive?
|
183
|
+
assert_equal 'finished', Qs.redis.with{ |c| c.get('qs-app:slow') }
|
184
|
+
assert_equal 'finished', Qs.redis.with{ |c| c.get('qs-app:slow:event') }
|
126
185
|
end
|
127
186
|
|
128
|
-
should "shutdown and not let the job
|
129
|
-
@
|
130
|
-
@
|
131
|
-
assert_false @
|
132
|
-
assert_nil Qs.redis.with{ |c| c.get('slow') }
|
187
|
+
should "shutdown and not let the job or event finish" do
|
188
|
+
@app_daemon.halt
|
189
|
+
@app_thread.join 2 # give it time to shutdown, should be faster
|
190
|
+
assert_false @app_thread.alive?
|
191
|
+
assert_nil Qs.redis.with{ |c| c.get('qs-app:slow') }
|
192
|
+
exp = "Qs::ShutdownError"
|
193
|
+
assert_equal exp, Qs.redis.with{ |c| c.get('qs-app:last_job_error') }
|
194
|
+
assert_nil Qs.redis.with{ |c| c.get('qs-app:slow:event') }
|
133
195
|
exp = "Qs::ShutdownError"
|
134
|
-
assert_equal exp, Qs.redis.with{ |c| c.get('
|
196
|
+
assert_equal exp, Qs.redis.with{ |c| c.get('qs-app:last_event_error') }
|
135
197
|
end
|
136
198
|
|
137
199
|
end
|
@@ -140,86 +202,93 @@ module Qs::Daemon
|
|
140
202
|
desc "with a shutdown timeout"
|
141
203
|
setup do
|
142
204
|
AppDaemon.shutdown_timeout 1
|
143
|
-
|
144
|
-
@daemon_runner = DaemonRunner.new(@daemon)
|
145
|
-
@thread = @daemon_runner.start
|
205
|
+
setup_app_and_dispatcher_daemon
|
146
206
|
|
147
207
|
AppQueue.add('slow')
|
148
|
-
|
208
|
+
Qs.publish('qs-app', 'slow')
|
209
|
+
@app_thread.join 1 # let the daemon have time to process the job and event
|
149
210
|
end
|
150
211
|
|
151
|
-
should "shutdown and not let the job
|
152
|
-
@
|
153
|
-
@
|
154
|
-
assert_false @
|
155
|
-
assert_nil Qs.redis.with{ |c| c.get('slow') }
|
212
|
+
should "shutdown and not let the job or event finish" do
|
213
|
+
@app_daemon.stop
|
214
|
+
@app_thread.join 2 # give it time to shutdown, should be faster
|
215
|
+
assert_false @app_thread.alive?
|
216
|
+
assert_nil Qs.redis.with{ |c| c.get('qs-app:slow') }
|
156
217
|
exp = "Qs::ShutdownError"
|
157
|
-
assert_equal exp, Qs.redis.with{ |c| c.get('
|
218
|
+
assert_equal exp, Qs.redis.with{ |c| c.get('qs-app:last_job_error') }
|
219
|
+
assert_nil Qs.redis.with{ |c| c.get('qs-app:slow:event') }
|
220
|
+
exp = "Qs::ShutdownError"
|
221
|
+
assert_equal exp, Qs.redis.with{ |c| c.get('qs-app:last_event_error') }
|
158
222
|
end
|
159
223
|
|
160
|
-
should "shutdown and not let the job
|
161
|
-
@
|
162
|
-
@
|
163
|
-
assert_false @
|
164
|
-
assert_nil Qs.redis.with{ |c| c.get('slow') }
|
224
|
+
should "shutdown and not let the job or event finish" do
|
225
|
+
@app_daemon.halt
|
226
|
+
@app_thread.join 2 # give it time to shutdown, should be faster
|
227
|
+
assert_false @app_thread.alive?
|
228
|
+
assert_nil Qs.redis.with{ |c| c.get('qs-app:slow') }
|
229
|
+
exp = "Qs::ShutdownError"
|
230
|
+
assert_equal exp, Qs.redis.with{ |c| c.get('qs-app:last_job_error') }
|
231
|
+
assert_nil Qs.redis.with{ |c| c.get('qs-app:slow:event') }
|
165
232
|
exp = "Qs::ShutdownError"
|
166
|
-
assert_equal exp, Qs.redis.with{ |c| c.get('
|
233
|
+
assert_equal exp, Qs.redis.with{ |c| c.get('qs-app:last_event_error') }
|
167
234
|
end
|
168
235
|
|
169
236
|
end
|
170
237
|
|
171
|
-
class
|
172
|
-
desc "with a
|
238
|
+
class ShutdownWithUnprocessedQueueItemTests < SystemTests
|
239
|
+
desc "with a queue item that gets picked up but doesn't get processed"
|
173
240
|
setup do
|
174
241
|
Assert.stub(Qs::PayloadHandler, :new){ sleep 5 }
|
175
242
|
|
176
243
|
AppDaemon.shutdown_timeout 1
|
177
244
|
AppDaemon.workers 2
|
178
|
-
|
179
|
-
@daemon_runner = DaemonRunner.new(@daemon)
|
180
|
-
@thread = @daemon_runner.start
|
245
|
+
setup_app_and_dispatcher_daemon
|
181
246
|
|
182
247
|
AppQueue.add('slow')
|
183
248
|
AppQueue.add('slow')
|
184
249
|
AppQueue.add('basic')
|
185
|
-
@
|
250
|
+
@app_thread.join 1 # let the daemon have time to process jobs
|
186
251
|
end
|
187
252
|
|
188
|
-
should "shutdown and requeue the
|
189
|
-
@
|
190
|
-
@
|
191
|
-
assert_false @
|
192
|
-
|
193
|
-
|
194
|
-
names = serialized_payloads.map{ |sp| Qs::Job.parse(Qs.deserialize(sp)).name }
|
253
|
+
should "shutdown and requeue the queue item" do
|
254
|
+
@app_daemon.stop
|
255
|
+
@app_thread.join 2 # give it time to shutdown, should be faster
|
256
|
+
assert_false @app_thread.alive?
|
257
|
+
encoded_payloads = Qs.redis.with{ |c| c.lrange(AppQueue.redis_key, 0, 3) }
|
258
|
+
names = encoded_payloads.map{ |sp| Qs::Payload.deserialize(sp).name }
|
195
259
|
assert_equal ['basic', 'slow', 'slow'], names
|
196
260
|
end
|
197
261
|
|
198
|
-
should "shutdown and requeue the
|
199
|
-
@
|
200
|
-
@
|
201
|
-
assert_false @
|
202
|
-
|
203
|
-
|
204
|
-
names = serialized_payloads.map{ |sp| Qs::Job.parse(Qs.deserialize(sp)).name }
|
262
|
+
should "shutdown and requeue the queue item" do
|
263
|
+
@app_daemon.halt
|
264
|
+
@app_thread.join 2 # give it time to shutdown, should be faster
|
265
|
+
assert_false @app_thread.alive?
|
266
|
+
encoded_payloads = Qs.redis.with{ |c| c.lrange(AppQueue.redis_key, 0, 3) }
|
267
|
+
names = encoded_payloads.map{ |sp| Qs::Payload.deserialize(sp).name }
|
205
268
|
assert_equal ['basic', 'slow', 'slow'], names
|
206
269
|
end
|
207
270
|
|
208
271
|
end
|
209
272
|
|
210
273
|
class DaemonRunner
|
211
|
-
def initialize(
|
212
|
-
@
|
213
|
-
@
|
274
|
+
def initialize(app_daemon, dispatcher_daemon = nil)
|
275
|
+
@app_daemon = app_daemon
|
276
|
+
@dispatcher_daemon = dispatcher_daemon
|
277
|
+
@app_thread = nil
|
278
|
+
@dispatcher_thread = nil
|
214
279
|
end
|
215
280
|
|
216
281
|
def start
|
217
|
-
@
|
282
|
+
@app_thread = @app_daemon.start
|
283
|
+
@dispatcher_thread = @dispatcher_daemon.start if @dispatcher_daemon
|
284
|
+
@app_thread
|
218
285
|
end
|
219
286
|
|
220
287
|
def stop
|
221
|
-
@
|
222
|
-
@
|
288
|
+
@app_daemon.halt
|
289
|
+
@dispatcher_daemon.halt if @dispatcher_daemon
|
290
|
+
@app_thread.join if @app_thread
|
291
|
+
@dispatcher_thread.join if @dispatcher_thread
|
223
292
|
end
|
224
293
|
end
|
225
294
|
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'assert'
|
2
|
+
require 'qs'
|
3
|
+
|
4
|
+
require 'test/support/app_daemon'
|
5
|
+
|
6
|
+
class Qs::Queue
|
7
|
+
|
8
|
+
class SystemTests < Assert::Context
|
9
|
+
desc "Qs::Queue"
|
10
|
+
setup do
|
11
|
+
Qs.reset!
|
12
|
+
@qs_test_mode = ENV['QS_TEST_MODE']
|
13
|
+
ENV['QS_TEST_MODE'] = nil
|
14
|
+
Qs.init
|
15
|
+
|
16
|
+
@event = Qs::Event.new('qs-app', 'basic')
|
17
|
+
@other_queue = Qs::Queue.new{ name(Factory.string) }
|
18
|
+
@other_queue.event(@event.channel, @event.name, Factory.string)
|
19
|
+
end
|
20
|
+
teardown do
|
21
|
+
Qs.redis.with do |c|
|
22
|
+
keys = c.keys('*qs-app*')
|
23
|
+
c.pipelined{ keys.each{ |k| c.del(k) } }
|
24
|
+
end
|
25
|
+
Qs.reset!
|
26
|
+
ENV['QS_TEST_MODE'] = @qs_test_mode
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
class SyncSubscriptionsTests < SystemTests
|
32
|
+
desc "sync_subscriptions"
|
33
|
+
setup do
|
34
|
+
AppQueue.sync_subscriptions
|
35
|
+
end
|
36
|
+
|
37
|
+
should "store subscriptions for the queue in redis" do
|
38
|
+
AppQueue.event_route_names.each do |route_name|
|
39
|
+
redis_key = Qs::Event::SubscribersRedisKey.new(route_name)
|
40
|
+
smembers = Qs.redis.with{ |c| c.smembers(redis_key) }
|
41
|
+
assert_includes AppQueue.name, smembers
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
should "allow adding a new queues subscriptions but preserve the existing" do
|
46
|
+
@other_queue.sync_subscriptions
|
47
|
+
|
48
|
+
smembers = Qs.redis.with{ |c| c.smembers(@event.subscribers_redis_key) }
|
49
|
+
assert_equal 2, smembers.size
|
50
|
+
assert_includes AppQueue.name, smembers
|
51
|
+
assert_includes @other_queue.name, smembers
|
52
|
+
end
|
53
|
+
|
54
|
+
should "remove subscriptions if a queue no longer subscribes to the event" do
|
55
|
+
route_names = AppQueue.event_route_names.reject{ |n| n == @event.route_name }
|
56
|
+
Assert.stub(AppQueue, :event_route_names){ route_names }
|
57
|
+
AppQueue.sync_subscriptions
|
58
|
+
|
59
|
+
redis_key = Qs::Event.new('qs-app', 'basic').subscribers_redis_key
|
60
|
+
smembers = Qs.redis.with{ |c| c.smembers(redis_key) }
|
61
|
+
assert_not_includes AppQueue.name, smembers
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
class ClearSubscriptionsTests < SystemTests
|
67
|
+
desc "clear_subscriptions"
|
68
|
+
setup do
|
69
|
+
AppQueue.sync_subscriptions
|
70
|
+
@other_queue.sync_subscriptions
|
71
|
+
AppQueue.clear_subscriptions
|
72
|
+
end
|
73
|
+
|
74
|
+
should "remove the queue from all of its events subscribers" do
|
75
|
+
AppQueue.event_route_names.each do |route_name|
|
76
|
+
redis_key = Qs::Event::SubscribersRedisKey.new(route_name)
|
77
|
+
smembers = Qs.redis.with{ |c| c.smembers(redis_key) }
|
78
|
+
assert_not_includes AppQueue.name, smembers
|
79
|
+
end
|
80
|
+
|
81
|
+
smembers = Qs.redis.with{ |c| c.smembers(@event.subscribers_redis_key) }
|
82
|
+
assert_equal [@other_queue.name], smembers
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
data/test/unit/client_tests.rb
CHANGED
@@ -48,10 +48,12 @@ module Qs::Client
|
|
48
48
|
subject{ @client }
|
49
49
|
|
50
50
|
should have_readers :redis_config, :redis
|
51
|
-
should have_imeths :enqueue, :push
|
51
|
+
should have_imeths :enqueue, :publish, :publish_as, :push
|
52
52
|
should have_imeths :block_dequeue
|
53
53
|
should have_imeths :append, :prepend
|
54
54
|
should have_imeths :clear
|
55
|
+
should have_imeths :sync_subscriptions, :clear_subscriptions
|
56
|
+
should have_imeths :event_subscribers
|
55
57
|
|
56
58
|
should "know its redis config" do
|
57
59
|
assert_equal @redis_config, subject.redis_config
|
@@ -63,16 +65,51 @@ module Qs::Client
|
|
63
65
|
|
64
66
|
should "build a job, enqueue it and return it using `enqueue`" do
|
65
67
|
result = subject.enqueue(@queue, @job_name, @job_params)
|
66
|
-
|
67
|
-
assert_equal @
|
68
|
-
assert_equal @
|
69
|
-
assert_equal
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
68
|
+
call = subject.enqueue_calls.last
|
69
|
+
assert_equal @queue, call.queue
|
70
|
+
assert_equal @job_name, call.job.name
|
71
|
+
assert_equal @job_params, call.job.params
|
72
|
+
assert_equal call.job, result
|
73
|
+
end
|
74
|
+
|
75
|
+
should "enqueue a dispatch job and return its event using `publish`" do
|
76
|
+
event_channel = Factory.string
|
77
|
+
event_name = Factory.string
|
78
|
+
event_params = @job_params
|
79
|
+
result = subject.publish(event_channel, event_name, event_params)
|
80
|
+
|
81
|
+
call = subject.enqueue_calls.last
|
82
|
+
assert_equal Qs.dispatcher_queue, call.queue
|
83
|
+
|
84
|
+
dispatch_job = Factory.dispatch_job({
|
85
|
+
:event_channel => event_channel,
|
86
|
+
:event_name => event_name,
|
87
|
+
:event_params => event_params
|
88
|
+
})
|
89
|
+
assert_equal dispatch_job.name, call.job.name
|
90
|
+
assert_equal dispatch_job.params, call.job.params
|
91
|
+
assert_equal call.job.event, result
|
92
|
+
end
|
93
|
+
|
94
|
+
should "enqueue a dispatch job with a custom publisher using `publish_as`" do
|
95
|
+
publisher = Factory.string
|
96
|
+
channel = Factory.string
|
97
|
+
name = Factory.string
|
98
|
+
params = @job_params
|
99
|
+
result = subject.publish_as(publisher, channel, name, params)
|
100
|
+
|
101
|
+
call = subject.enqueue_calls.last
|
102
|
+
assert_equal Qs.dispatcher_queue, call.queue
|
103
|
+
|
104
|
+
dispatch_job = Factory.dispatch_job({
|
105
|
+
:event_channel => channel,
|
106
|
+
:event_name => name,
|
107
|
+
:event_params => params,
|
108
|
+
:event_publisher => publisher
|
109
|
+
})
|
110
|
+
assert_equal dispatch_job.name, call.job.name
|
111
|
+
assert_equal dispatch_job.params, call.job.params
|
112
|
+
assert_equal call.job.event, result
|
76
113
|
end
|
77
114
|
|
78
115
|
should "raise a not implemented error using `push`" do
|
@@ -88,8 +125,8 @@ module Qs::Client
|
|
88
125
|
@connection_spy = HellaRedis::ConnectionSpy.new(@client.redis_config)
|
89
126
|
Assert.stub(@client, :redis){ @connection_spy }
|
90
127
|
|
91
|
-
@queue_redis_key
|
92
|
-
@
|
128
|
+
@queue_redis_key = Factory.string
|
129
|
+
@encoded_payload = Factory.string
|
93
130
|
end
|
94
131
|
|
95
132
|
should "block pop from the front of a list using `block_dequeue`" do
|
@@ -101,22 +138,22 @@ module Qs::Client
|
|
101
138
|
assert_equal args, call.args
|
102
139
|
end
|
103
140
|
|
104
|
-
should "add a
|
105
|
-
subject.append(@queue_redis_key, @
|
141
|
+
should "add a encoded payload to the end of a list using `append`" do
|
142
|
+
subject.append(@queue_redis_key, @encoded_payload)
|
106
143
|
|
107
144
|
call = @connection_spy.redis_calls.last
|
108
|
-
assert_equal :lpush,
|
109
|
-
assert_equal @queue_redis_key,
|
110
|
-
assert_equal @
|
145
|
+
assert_equal :lpush, call.command
|
146
|
+
assert_equal @queue_redis_key, call.args.first
|
147
|
+
assert_equal @encoded_payload, call.args.last
|
111
148
|
end
|
112
149
|
|
113
|
-
should "add a
|
114
|
-
subject.prepend(@queue_redis_key, @
|
150
|
+
should "add a encoded payload to the front of a list using `prepend`" do
|
151
|
+
subject.prepend(@queue_redis_key, @encoded_payload)
|
115
152
|
|
116
153
|
call = @connection_spy.redis_calls.last
|
117
|
-
assert_equal :rpush,
|
118
|
-
assert_equal @queue_redis_key,
|
119
|
-
assert_equal @
|
154
|
+
assert_equal :rpush, call.command
|
155
|
+
assert_equal @queue_redis_key, call.args.first
|
156
|
+
assert_equal @encoded_payload, call.args.last
|
120
157
|
end
|
121
158
|
|
122
159
|
should "del a list using `clear`" do
|
@@ -134,6 +171,96 @@ module Qs::Client
|
|
134
171
|
assert_equal :ping, call.command
|
135
172
|
end
|
136
173
|
|
174
|
+
should "return the events subscriber set using `event_subscribers`" do
|
175
|
+
smembers_key = nil
|
176
|
+
smembers = Factory.integer(3).times.map{ Factory.string }
|
177
|
+
Assert.stub(@connection_spy.redis_spy, :smembers) do |key|
|
178
|
+
smembers_key = key
|
179
|
+
smembers
|
180
|
+
end
|
181
|
+
|
182
|
+
event = Factory.event
|
183
|
+
result = subject.event_subscribers(event)
|
184
|
+
|
185
|
+
assert_equal event.subscribers_redis_key, smembers_key
|
186
|
+
assert_equal smembers, result
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
|
191
|
+
class SubscriptionsSetupTests < RedisCallTests
|
192
|
+
setup do
|
193
|
+
@event_subs_keys = Factory.integer(3).times.map{ Factory.string }
|
194
|
+
@keys_pattern = nil
|
195
|
+
Assert.stub(@connection_spy.redis_spy, :keys) do |pattern|
|
196
|
+
@keys_pattern = pattern
|
197
|
+
@event_subs_keys
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
end
|
202
|
+
|
203
|
+
class SyncSubscriptionsTests < SubscriptionsSetupTests
|
204
|
+
desc "sync_subscriptions"
|
205
|
+
setup do
|
206
|
+
Factory.integer(3).times.map{ @queue.event_route_names << Factory.string }
|
207
|
+
subject.sync_subscriptions(@queue)
|
208
|
+
end
|
209
|
+
|
210
|
+
should "run in a pipelined transaction" do
|
211
|
+
calls = @connection_spy.redis_calls[0, 2]
|
212
|
+
assert_equal [:pipelined, :multi], calls.map(&:command)
|
213
|
+
end
|
214
|
+
|
215
|
+
should "find all event subscribers keys" do
|
216
|
+
assert_equal Qs::Event::SubscribersRedisKey.new('*'), @keys_pattern
|
217
|
+
end
|
218
|
+
|
219
|
+
should "remove the queue from all events subscribers first" do
|
220
|
+
calls = @connection_spy.redis_calls[2, @event_subs_keys.size]
|
221
|
+
assert_equal @event_subs_keys.size, calls.size
|
222
|
+
assert_equal [:srem], calls.map(&:command).uniq
|
223
|
+
exp = @event_subs_keys.map{ |key| [key, @queue.name] }
|
224
|
+
assert_equal exp, calls.map(&:args)
|
225
|
+
end
|
226
|
+
|
227
|
+
should "remove and add the queue from events subscribers" do
|
228
|
+
start_at = 2 + @event_subs_keys.size
|
229
|
+
calls = @connection_spy.redis_calls[start_at..-1]
|
230
|
+
exp = @queue.event_route_names.size
|
231
|
+
assert_equal exp, calls.size
|
232
|
+
assert_equal [:sadd], calls.map(&:command).uniq
|
233
|
+
exp = @queue.event_route_names.map do |name|
|
234
|
+
[Qs::Event::SubscribersRedisKey.new(name), @queue.name]
|
235
|
+
end
|
236
|
+
assert_equal exp, calls.map(&:args)
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
240
|
+
|
241
|
+
class ClearSubscriptionsTests < SubscriptionsSetupTests
|
242
|
+
desc "clear_subscriptions"
|
243
|
+
setup do
|
244
|
+
subject.clear_subscriptions(@queue)
|
245
|
+
end
|
246
|
+
|
247
|
+
should "run in a pipelined transaction" do
|
248
|
+
calls = @connection_spy.redis_calls[0, 2]
|
249
|
+
assert_equal [:pipelined, :multi], calls.map(&:command)
|
250
|
+
end
|
251
|
+
|
252
|
+
should "find all event subscribers keys" do
|
253
|
+
assert_equal Qs::Event::SubscribersRedisKey.new('*'), @keys_pattern
|
254
|
+
end
|
255
|
+
|
256
|
+
should "remove the queue from all events subscribers" do
|
257
|
+
calls = @connection_spy.redis_calls[2..-1]
|
258
|
+
assert_equal @event_subs_keys.size, calls.size
|
259
|
+
assert_equal [:srem], calls.map(&:command).uniq
|
260
|
+
exp = @event_subs_keys.map{ |key| [key, @queue.name] }
|
261
|
+
assert_equal exp, calls.map(&:args)
|
262
|
+
end
|
263
|
+
|
137
264
|
end
|
138
265
|
|
139
266
|
class QsClientTests < UnitTests
|
@@ -167,25 +294,25 @@ module Qs::Client
|
|
167
294
|
assert_equal @connection_spy, subject.redis
|
168
295
|
end
|
169
296
|
|
170
|
-
should "add jobs to the
|
297
|
+
should "add jobs to the queues redis list using `enqueue`" do
|
171
298
|
subject.enqueue(@queue, @job_name, @job_params)
|
172
299
|
|
173
300
|
call = @connection_spy.redis_calls.last
|
174
301
|
assert_equal :lpush, call.command
|
175
302
|
assert_equal @queue.redis_key, call.args.first
|
176
|
-
|
177
|
-
assert_equal @job_name,
|
178
|
-
assert_equal @job_params,
|
303
|
+
job = Qs::Payload.deserialize(call.args.last)
|
304
|
+
assert_equal @job_name, job.name
|
305
|
+
assert_equal @job_params, job.params
|
179
306
|
end
|
180
307
|
|
181
|
-
should "add
|
182
|
-
|
183
|
-
subject.push(@queue.name,
|
308
|
+
should "add a payload hash to the queues redis list using `push`" do
|
309
|
+
payload_hash = { Factory.string => Factory.string }
|
310
|
+
subject.push(@queue.name, payload_hash)
|
184
311
|
|
185
312
|
call = @connection_spy.redis_calls.last
|
186
313
|
assert_equal :lpush, call.command
|
187
314
|
assert_equal @queue.redis_key, call.args.first
|
188
|
-
assert_equal Qs.
|
315
|
+
assert_equal Qs.encode(payload_hash), call.args.last
|
189
316
|
end
|
190
317
|
|
191
318
|
end
|
@@ -206,10 +333,10 @@ module Qs::Client
|
|
206
333
|
class TestClientInitTests < TestClientTests
|
207
334
|
desc "when init"
|
208
335
|
setup do
|
209
|
-
@
|
210
|
-
Assert.stub(Qs, :
|
211
|
-
|
212
|
-
|
336
|
+
@encoded_hash = nil
|
337
|
+
Assert.stub(Qs, :encode){ |hash| @encoded_hash = hash }
|
338
|
+
@serialized_job = nil
|
339
|
+
Assert.stub(Qs::Payload, :serialize){ |job| @serialized_job = job }
|
213
340
|
|
214
341
|
@client = @client_class.new(@redis_config)
|
215
342
|
end
|
@@ -237,24 +364,34 @@ module Qs::Client
|
|
237
364
|
assert_equal job, result
|
238
365
|
end
|
239
366
|
|
240
|
-
should "serialize the
|
367
|
+
should "serialize the job when enqueueing" do
|
241
368
|
subject.enqueue(@queue, @job_name, @job_params)
|
242
369
|
|
243
370
|
job = @queue.enqueued_jobs.last
|
244
|
-
assert_equal job
|
371
|
+
assert_equal job, @serialized_job
|
245
372
|
end
|
246
373
|
|
247
|
-
should "track all the
|
248
|
-
|
374
|
+
should "track all the payload hashes pushed onto a queue" do
|
375
|
+
payload_hash = { Factory.string => Factory.string }
|
376
|
+
subject.push(@queue.name, payload_hash)
|
249
377
|
|
250
378
|
pushed_item = subject.pushed_items.last
|
251
379
|
assert_instance_of Qs::TestClient::PushedItem, pushed_item
|
252
|
-
assert_equal @queue.name,
|
253
|
-
assert_equal
|
380
|
+
assert_equal @queue.name, pushed_item.queue_name
|
381
|
+
assert_equal payload_hash, pushed_item.payload_hash
|
382
|
+
end
|
383
|
+
|
384
|
+
should "encode the payload hashes when pushing them onto a queue" do
|
385
|
+
payload_hash = { Factory.string => Factory.string }
|
386
|
+
subject.push(@queue.name, payload_hash)
|
387
|
+
|
388
|
+
pushed_item = subject.pushed_items.last
|
389
|
+
assert_equal pushed_item.payload_hash, @encoded_hash
|
254
390
|
end
|
255
391
|
|
256
392
|
should "clear its pushed items when reset" do
|
257
|
-
|
393
|
+
payload_hash = { Factory.string => Factory.string }
|
394
|
+
subject.push(@queue.name, payload_hash)
|
258
395
|
assert_not_empty subject.pushed_items
|
259
396
|
subject.reset!
|
260
397
|
assert_empty subject.pushed_items
|
@@ -265,12 +402,14 @@ module Qs::Client
|
|
265
402
|
class FakeClient
|
266
403
|
include Qs::Client
|
267
404
|
|
268
|
-
attr_reader :
|
405
|
+
attr_reader :enqueue_calls
|
269
406
|
|
270
407
|
def enqueue!(queue, job)
|
271
|
-
@
|
272
|
-
@
|
408
|
+
@enqueue_calls ||= []
|
409
|
+
EnqueueCall.new(queue, job).tap{ |c| @enqueue_calls << c }
|
273
410
|
end
|
411
|
+
|
412
|
+
EnqueueCall = Struct.new(:queue, :job)
|
274
413
|
end
|
275
414
|
|
276
415
|
end
|