qs 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/Gemfile +6 -1
- data/LICENSE.txt +1 -1
- data/bench/config.qs +46 -0
- data/bench/queue.rb +8 -0
- data/bench/report.rb +114 -0
- data/bench/report.txt +11 -0
- data/bin/qs +7 -0
- data/lib/qs/cli.rb +124 -0
- data/lib/qs/client.rb +121 -0
- data/lib/qs/config_file.rb +79 -0
- data/lib/qs/daemon.rb +350 -0
- data/lib/qs/daemon_data.rb +46 -0
- data/lib/qs/error_handler.rb +58 -0
- data/lib/qs/job.rb +70 -0
- data/lib/qs/job_handler.rb +90 -0
- data/lib/qs/logger.rb +23 -0
- data/lib/qs/payload_handler.rb +136 -0
- data/lib/qs/pid_file.rb +42 -0
- data/lib/qs/process.rb +136 -0
- data/lib/qs/process_signal.rb +20 -0
- data/lib/qs/qs_runner.rb +49 -0
- data/lib/qs/queue.rb +69 -0
- data/lib/qs/redis_item.rb +33 -0
- data/lib/qs/route.rb +52 -0
- data/lib/qs/runner.rb +26 -0
- data/lib/qs/test_helpers.rb +17 -0
- data/lib/qs/test_runner.rb +43 -0
- data/lib/qs/version.rb +1 -1
- data/lib/qs.rb +92 -2
- data/qs.gemspec +7 -2
- data/test/helper.rb +8 -1
- data/test/support/app_daemon.rb +74 -0
- data/test/support/config.qs +7 -0
- data/test/support/config_files/empty.qs +0 -0
- data/test/support/config_files/invalid.qs +1 -0
- data/test/support/config_files/valid.qs +7 -0
- data/test/support/config_invalid_run.qs +3 -0
- data/test/support/config_no_run.qs +0 -0
- data/test/support/factory.rb +14 -0
- data/test/support/pid_file_spy.rb +19 -0
- data/test/support/runner_spy.rb +17 -0
- data/test/system/daemon_tests.rb +226 -0
- data/test/unit/cli_tests.rb +188 -0
- data/test/unit/client_tests.rb +269 -0
- data/test/unit/config_file_tests.rb +59 -0
- data/test/unit/daemon_data_tests.rb +96 -0
- data/test/unit/daemon_tests.rb +702 -0
- data/test/unit/error_handler_tests.rb +163 -0
- data/test/unit/job_handler_tests.rb +253 -0
- data/test/unit/job_tests.rb +132 -0
- data/test/unit/logger_tests.rb +38 -0
- data/test/unit/payload_handler_tests.rb +276 -0
- data/test/unit/pid_file_tests.rb +70 -0
- data/test/unit/process_signal_tests.rb +61 -0
- data/test/unit/process_tests.rb +371 -0
- data/test/unit/qs_runner_tests.rb +166 -0
- data/test/unit/qs_tests.rb +217 -0
- data/test/unit/queue_tests.rb +132 -0
- data/test/unit/redis_item_tests.rb +49 -0
- data/test/unit/route_tests.rb +81 -0
- data/test/unit/runner_tests.rb +63 -0
- data/test/unit/test_helper_tests.rb +61 -0
- data/test/unit/test_runner_tests.rb +128 -0
- metadata +180 -15
@@ -0,0 +1,702 @@
|
|
1
|
+
require 'assert'
|
2
|
+
require 'qs/daemon'
|
3
|
+
|
4
|
+
require 'dat-worker-pool/worker_pool_spy'
|
5
|
+
require 'ns-options/assert_macros'
|
6
|
+
require 'thread'
|
7
|
+
require 'qs/client'
|
8
|
+
require 'qs/queue'
|
9
|
+
require 'qs/redis_item'
|
10
|
+
|
11
|
+
module Qs::Daemon
|
12
|
+
|
13
|
+
class UnitTests < Assert::Context
|
14
|
+
desc "Qs::Daemon"
|
15
|
+
setup do
|
16
|
+
@daemon_class = Class.new{ include Qs::Daemon }
|
17
|
+
end
|
18
|
+
subject{ @daemon_class }
|
19
|
+
|
20
|
+
should have_imeths :configuration
|
21
|
+
should have_imeths :name, :pid_file
|
22
|
+
should have_imeths :min_workers, :max_workers, :workers
|
23
|
+
should have_imeths :verbose_logging, :logger
|
24
|
+
should have_imeths :shutdown_timeout
|
25
|
+
should have_imeths :init, :error, :queue
|
26
|
+
|
27
|
+
should "know its configuration" do
|
28
|
+
config = subject.configuration
|
29
|
+
assert_instance_of Configuration, config
|
30
|
+
assert_same config, subject.configuration
|
31
|
+
end
|
32
|
+
|
33
|
+
should "allow reading/writing its configuration name" do
|
34
|
+
new_name = Factory.string
|
35
|
+
subject.name(new_name)
|
36
|
+
assert_equal new_name, subject.configuration.name
|
37
|
+
assert_equal new_name, subject.name
|
38
|
+
end
|
39
|
+
|
40
|
+
should "allow reading/writing its configuration pid file" do
|
41
|
+
new_pid_file = Factory.string
|
42
|
+
subject.pid_file(new_pid_file)
|
43
|
+
expected = Pathname.new(new_pid_file)
|
44
|
+
assert_equal expected, subject.configuration.pid_file
|
45
|
+
assert_equal expected, subject.pid_file
|
46
|
+
end
|
47
|
+
|
48
|
+
should "allow reading/writing its configuration min workers" do
|
49
|
+
new_min_workers = Factory.integer
|
50
|
+
subject.min_workers(new_min_workers)
|
51
|
+
assert_equal new_min_workers, subject.configuration.min_workers
|
52
|
+
assert_equal new_min_workers, subject.min_workers
|
53
|
+
end
|
54
|
+
|
55
|
+
should "allow reading/writing its configuration max workers" do
|
56
|
+
new_max_workers = Factory.integer
|
57
|
+
subject.max_workers(new_max_workers)
|
58
|
+
assert_equal new_max_workers, subject.configuration.max_workers
|
59
|
+
assert_equal new_max_workers, subject.max_workers
|
60
|
+
end
|
61
|
+
|
62
|
+
should "allow reading/writing its configuration workers" do
|
63
|
+
new_workers = Factory.integer
|
64
|
+
subject.workers(new_workers)
|
65
|
+
assert_equal new_workers, subject.configuration.min_workers
|
66
|
+
assert_equal new_workers, subject.configuration.max_workers
|
67
|
+
assert_equal new_workers, subject.min_workers
|
68
|
+
assert_equal new_workers, subject.max_workers
|
69
|
+
end
|
70
|
+
|
71
|
+
should "allow reading/writing its configuration verbose logging" do
|
72
|
+
new_verbose = Factory.boolean
|
73
|
+
subject.verbose_logging(new_verbose)
|
74
|
+
assert_equal new_verbose, subject.configuration.verbose_logging
|
75
|
+
assert_equal new_verbose, subject.verbose_logging
|
76
|
+
end
|
77
|
+
|
78
|
+
should "allow reading/writing its configuration logger" do
|
79
|
+
new_logger = Factory.string
|
80
|
+
subject.logger(new_logger)
|
81
|
+
assert_equal new_logger, subject.configuration.logger
|
82
|
+
assert_equal new_logger, subject.logger
|
83
|
+
end
|
84
|
+
|
85
|
+
should "allow reading/writing its configuration shutdown timeout" do
|
86
|
+
new_shutdown_timeout = Factory.integer
|
87
|
+
subject.shutdown_timeout(new_shutdown_timeout)
|
88
|
+
assert_equal new_shutdown_timeout, subject.configuration.shutdown_timeout
|
89
|
+
assert_equal new_shutdown_timeout, subject.shutdown_timeout
|
90
|
+
end
|
91
|
+
|
92
|
+
should "allow adding init procs to its configuration" do
|
93
|
+
new_init_proc = proc{ Factory.string }
|
94
|
+
subject.init(&new_init_proc)
|
95
|
+
assert_includes new_init_proc, subject.configuration.init_procs
|
96
|
+
end
|
97
|
+
|
98
|
+
should "allow adding error procs to its configuration" do
|
99
|
+
new_error_proc = proc{ Factory.string }
|
100
|
+
subject.error(&new_error_proc)
|
101
|
+
assert_includes new_error_proc, subject.configuration.error_procs
|
102
|
+
end
|
103
|
+
|
104
|
+
should "allow adding queues to its configuration" do
|
105
|
+
new_queue = Factory.string
|
106
|
+
subject.queue(new_queue)
|
107
|
+
assert_includes new_queue, subject.configuration.queues
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
class InitSetupTests < UnitTests
|
113
|
+
setup do
|
114
|
+
@qs_init_called = false
|
115
|
+
Assert.stub(Qs, :init){ @qs_init_called = true }
|
116
|
+
|
117
|
+
@queue = Qs::Queue.new do
|
118
|
+
name(Factory.string)
|
119
|
+
job 'test', TestHandler.to_s
|
120
|
+
end
|
121
|
+
@daemon_class.name Factory.string
|
122
|
+
@daemon_class.pid_file Factory.file_path
|
123
|
+
@daemon_class.workers Factory.integer
|
124
|
+
@daemon_class.verbose_logging Factory.boolean
|
125
|
+
@daemon_class.shutdown_timeout Factory.integer
|
126
|
+
@daemon_class.error{ Factory.string }
|
127
|
+
@daemon_class.queue @queue
|
128
|
+
|
129
|
+
@client_spy = nil
|
130
|
+
Assert.stub(Qs::QsClient, :new) do |*args|
|
131
|
+
@client_spy = ClientSpy.new(*args)
|
132
|
+
end
|
133
|
+
|
134
|
+
@worker_pool_spy = nil
|
135
|
+
@worker_available = true
|
136
|
+
Assert.stub(::DatWorkerPool, :new) do |*args, &block|
|
137
|
+
@worker_pool_spy = DatWorkerPool::WorkerPoolSpy.new(*args, &block)
|
138
|
+
@worker_pool_spy.worker_available = !!@worker_available
|
139
|
+
@worker_pool_spy
|
140
|
+
end
|
141
|
+
end
|
142
|
+
teardown do
|
143
|
+
@daemon.halt(true) rescue false
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
class InitTests < InitSetupTests
|
149
|
+
desc "when init"
|
150
|
+
setup do
|
151
|
+
@daemon = @daemon_class.new
|
152
|
+
end
|
153
|
+
subject{ @daemon }
|
154
|
+
|
155
|
+
should have_readers :daemon_data, :logger
|
156
|
+
should have_readers :signals_redis_key, :queue_redis_keys
|
157
|
+
should have_imeths :name, :pid_file
|
158
|
+
should have_imeths :running?
|
159
|
+
should have_imeths :start, :stop, :halt
|
160
|
+
|
161
|
+
should "validate its configuration" do
|
162
|
+
assert_true @daemon_class.configuration.valid?
|
163
|
+
end
|
164
|
+
|
165
|
+
should "init Qs" do
|
166
|
+
assert_true @qs_init_called
|
167
|
+
end
|
168
|
+
|
169
|
+
should "know its daemon data" do
|
170
|
+
configuration = @daemon_class.configuration
|
171
|
+
data = subject.daemon_data
|
172
|
+
|
173
|
+
assert_instance_of Qs::DaemonData, data
|
174
|
+
assert_equal configuration.name, data.name
|
175
|
+
assert_equal configuration.pid_file, data.pid_file
|
176
|
+
assert_equal configuration.min_workers, data.min_workers
|
177
|
+
assert_equal configuration.max_workers, data.max_workers
|
178
|
+
assert_equal configuration.verbose_logging, data.verbose_logging
|
179
|
+
assert_equal configuration.shutdown_timeout, data.shutdown_timeout
|
180
|
+
assert_equal configuration.error_procs, data.error_procs
|
181
|
+
assert_equal [@queue.redis_key], data.queue_redis_keys
|
182
|
+
assert_equal configuration.routes, data.routes.values
|
183
|
+
assert_instance_of configuration.logger.class, data.logger
|
184
|
+
end
|
185
|
+
|
186
|
+
should "know its signal and queues redis keys" do
|
187
|
+
data = subject.daemon_data
|
188
|
+
expected = "signals:#{data.name}-#{Socket.gethostname}-#{::Process.pid}"
|
189
|
+
assert_equal expected, subject.signals_redis_key
|
190
|
+
assert_equal data.queue_redis_keys, subject.queue_redis_keys
|
191
|
+
end
|
192
|
+
|
193
|
+
should "know its name and pid file" do
|
194
|
+
data = subject.daemon_data
|
195
|
+
assert_equal data.name, subject.name
|
196
|
+
assert_equal data.pid_file, subject.pid_file
|
197
|
+
end
|
198
|
+
|
199
|
+
should "build a client" do
|
200
|
+
assert_not_nil @client_spy
|
201
|
+
exp = Qs.redis_config.merge({
|
202
|
+
:timeout => 1,
|
203
|
+
:size => subject.daemon_data.max_workers + 1
|
204
|
+
})
|
205
|
+
assert_equal exp, @client_spy.redis_config
|
206
|
+
end
|
207
|
+
|
208
|
+
should "not be running by default" do
|
209
|
+
assert_false subject.running?
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
|
214
|
+
class StartTests < InitTests
|
215
|
+
desc "and started"
|
216
|
+
setup do
|
217
|
+
@thread = @daemon.start
|
218
|
+
@thread.join 0.1
|
219
|
+
end
|
220
|
+
|
221
|
+
should "return the thread that is running the daemon" do
|
222
|
+
assert_instance_of Thread, @thread
|
223
|
+
assert_true @thread.alive?
|
224
|
+
end
|
225
|
+
|
226
|
+
should "be running" do
|
227
|
+
assert_true subject.running?
|
228
|
+
end
|
229
|
+
|
230
|
+
should "clear the signals list in redis" do
|
231
|
+
call = @client_spy.calls.first
|
232
|
+
assert_equal :clear, call.command
|
233
|
+
assert_equal [subject.signals_redis_key], call.args
|
234
|
+
end
|
235
|
+
|
236
|
+
should "build and start a worker pool" do
|
237
|
+
assert_not_nil @worker_pool_spy
|
238
|
+
assert_equal @daemon_class.min_workers, @worker_pool_spy.min_workers
|
239
|
+
assert_equal @daemon_class.max_workers, @worker_pool_spy.max_workers
|
240
|
+
assert_equal 1, @worker_pool_spy.on_worker_error_callbacks.size
|
241
|
+
assert_equal 1, @worker_pool_spy.on_worker_sleep_callbacks.size
|
242
|
+
assert_true @worker_pool_spy.start_called
|
243
|
+
end
|
244
|
+
|
245
|
+
end
|
246
|
+
|
247
|
+
class RunningWithoutAvailableWorkerTests < InitSetupTests
|
248
|
+
desc "running without an available worker"
|
249
|
+
setup do
|
250
|
+
@worker_available = false
|
251
|
+
|
252
|
+
@daemon = @daemon_class.new
|
253
|
+
@thread = @daemon.start
|
254
|
+
end
|
255
|
+
subject{ @daemon }
|
256
|
+
|
257
|
+
should "sleep its thread and not add work to its worker pool" do
|
258
|
+
@thread.join(0.1)
|
259
|
+
assert_equal 'sleep', @thread.status
|
260
|
+
@client_spy.append(@queue.redis_key, Factory.string)
|
261
|
+
@thread.join(0.1)
|
262
|
+
assert_empty @worker_pool_spy.work_items
|
263
|
+
end
|
264
|
+
|
265
|
+
end
|
266
|
+
|
267
|
+
class RunningWithWorkerAndWorkTests < InitSetupTests
|
268
|
+
desc "running with a worker available and work"
|
269
|
+
setup do
|
270
|
+
@daemon = @daemon_class.new
|
271
|
+
@thread = @daemon.start
|
272
|
+
|
273
|
+
@serialized_payload = Factory.string
|
274
|
+
@client_spy.append(@queue.redis_key, @serialized_payload)
|
275
|
+
end
|
276
|
+
subject{ @daemon }
|
277
|
+
|
278
|
+
should "call dequeue on its client and add work to the worker pool" do
|
279
|
+
call = @client_spy.calls.last
|
280
|
+
assert_equal :block_dequeue, call.command
|
281
|
+
exp = [subject.signals_redis_key, subject.queue_redis_keys, 0].flatten
|
282
|
+
assert_equal exp, call.args
|
283
|
+
exp = Qs::RedisItem.new(@queue.redis_key, @serialized_payload)
|
284
|
+
assert_equal exp, @worker_pool_spy.work_items.first
|
285
|
+
end
|
286
|
+
|
287
|
+
end
|
288
|
+
|
289
|
+
class RunningWithMultipleQueuesTests < InitSetupTests
|
290
|
+
desc "running with multiple queues"
|
291
|
+
setup do
|
292
|
+
@other_queue = Qs::Queue.new{ name(Factory.string) }
|
293
|
+
@daemon_class.queue @other_queue
|
294
|
+
@daemon = @daemon_class.new
|
295
|
+
|
296
|
+
@shuffled_keys = @daemon.queue_redis_keys + [Factory.string]
|
297
|
+
Assert.stub(@daemon.queue_redis_keys, :shuffle){ @shuffled_keys }
|
298
|
+
|
299
|
+
@thread = @daemon.start
|
300
|
+
end
|
301
|
+
subject{ @daemon }
|
302
|
+
|
303
|
+
should "shuffle the queue keys to avoid queue starvation" do
|
304
|
+
call = @client_spy.calls.last
|
305
|
+
assert_equal :block_dequeue, call.command
|
306
|
+
exp = [subject.signals_redis_key, @shuffled_keys, 0].flatten
|
307
|
+
assert_equal exp, call.args
|
308
|
+
end
|
309
|
+
|
310
|
+
end
|
311
|
+
|
312
|
+
class WorkerPoolWorkProcTests < InitSetupTests
|
313
|
+
desc "worker pool work proc"
|
314
|
+
setup do
|
315
|
+
@ph_spy = nil
|
316
|
+
Assert.stub(Qs::PayloadHandler, :new) do |*args|
|
317
|
+
@ph_spy = PayloadHandlerSpy.new(*args)
|
318
|
+
end
|
319
|
+
|
320
|
+
@daemon = @daemon_class.new
|
321
|
+
@thread = @daemon.start
|
322
|
+
|
323
|
+
@redis_item = Qs::RedisItem.new(Factory.string, Factory.string)
|
324
|
+
@worker_pool_spy.work_proc.call(@redis_item)
|
325
|
+
end
|
326
|
+
subject{ @daemon }
|
327
|
+
|
328
|
+
should "build and run a payload handler" do
|
329
|
+
assert_not_nil @ph_spy
|
330
|
+
assert_equal subject.daemon_data, @ph_spy.daemon_data
|
331
|
+
assert_equal @redis_item, @ph_spy.redis_item
|
332
|
+
end
|
333
|
+
|
334
|
+
end
|
335
|
+
|
336
|
+
class WorkerPoolOnWorkerErrorTests < InitSetupTests
|
337
|
+
desc "worker pool on worker error proc"
|
338
|
+
setup do
|
339
|
+
@daemon = @daemon_class.new
|
340
|
+
@thread = @daemon.start
|
341
|
+
|
342
|
+
@exception = Factory.exception
|
343
|
+
@redis_item = Qs::RedisItem.new(Factory.string, Factory.string)
|
344
|
+
@callback = @worker_pool_spy.on_worker_error_callbacks.first
|
345
|
+
end
|
346
|
+
subject{ @daemon }
|
347
|
+
|
348
|
+
should "requeue the redis item if it wasn't started" do
|
349
|
+
@redis_item.started = false
|
350
|
+
@callback.call('worker', @exception, @redis_item)
|
351
|
+
call = @client_spy.calls.detect{ |c| c.command == :prepend }
|
352
|
+
assert_not_nil call
|
353
|
+
assert_equal @redis_item.queue_redis_key, call.args.first
|
354
|
+
assert_equal @redis_item.serialized_payload, call.args.last
|
355
|
+
end
|
356
|
+
|
357
|
+
should "not requeue the redis item if it was started" do
|
358
|
+
@redis_item.started = true
|
359
|
+
@callback.call('worker', @exception, @redis_item)
|
360
|
+
assert_nil @client_spy.calls.detect{ |c| c.command == :prepend }
|
361
|
+
end
|
362
|
+
|
363
|
+
should "do nothing if not passed a redis item" do
|
364
|
+
assert_nothing_raised{ @callback.call(@exception, nil) }
|
365
|
+
end
|
366
|
+
|
367
|
+
end
|
368
|
+
|
369
|
+
class StopTests < StartTests
|
370
|
+
desc "and then stopped"
|
371
|
+
setup do
|
372
|
+
@redis_item = Qs::RedisItem.new(@queue.redis_key, Factory.string)
|
373
|
+
@worker_pool_spy.add_work(@redis_item)
|
374
|
+
|
375
|
+
@daemon.stop true
|
376
|
+
end
|
377
|
+
|
378
|
+
should "shutdown the worker pool" do
|
379
|
+
assert_true @worker_pool_spy.shutdown_called
|
380
|
+
assert_equal @daemon_class.shutdown_timeout, @worker_pool_spy.shutdown_timeout
|
381
|
+
end
|
382
|
+
|
383
|
+
should "requeue any work left on the pool" do
|
384
|
+
call = @client_spy.calls.last
|
385
|
+
assert_equal :prepend, call.command
|
386
|
+
assert_equal @redis_item.queue_redis_key, call.args.first
|
387
|
+
assert_equal @redis_item.serialized_payload, call.args.last
|
388
|
+
end
|
389
|
+
|
390
|
+
should "stop the work loop thread" do
|
391
|
+
assert_false @thread.alive?
|
392
|
+
end
|
393
|
+
|
394
|
+
should "not be running" do
|
395
|
+
assert_false subject.running?
|
396
|
+
end
|
397
|
+
|
398
|
+
end
|
399
|
+
|
400
|
+
class StopWhileWaitingForWorkerTests < InitSetupTests
|
401
|
+
desc "stopped while waiting for a worker"
|
402
|
+
setup do
|
403
|
+
@worker_available = false
|
404
|
+
@daemon = @daemon_class.new
|
405
|
+
@thread = @daemon.start
|
406
|
+
@daemon.stop(true)
|
407
|
+
end
|
408
|
+
subject{ @daemon }
|
409
|
+
|
410
|
+
should "not be running" do
|
411
|
+
assert_false subject.running?
|
412
|
+
end
|
413
|
+
|
414
|
+
end
|
415
|
+
|
416
|
+
class HaltTests < StartTests
|
417
|
+
desc "and then halted"
|
418
|
+
setup do
|
419
|
+
@redis_item = Qs::RedisItem.new(@queue.redis_key, Factory.string)
|
420
|
+
@worker_pool_spy.add_work(@redis_item)
|
421
|
+
|
422
|
+
@daemon.halt true
|
423
|
+
end
|
424
|
+
|
425
|
+
should "shutdown the worker pool with a 0 timeout" do
|
426
|
+
assert_true @worker_pool_spy.shutdown_called
|
427
|
+
assert_equal 0, @worker_pool_spy.shutdown_timeout
|
428
|
+
end
|
429
|
+
|
430
|
+
should "requeue any work left on the pool" do
|
431
|
+
call = @client_spy.calls.last
|
432
|
+
assert_equal :prepend, call.command
|
433
|
+
assert_equal @redis_item.queue_redis_key, call.args.first
|
434
|
+
assert_equal @redis_item.serialized_payload, call.args.last
|
435
|
+
end
|
436
|
+
|
437
|
+
should "stop the work loop thread" do
|
438
|
+
assert_false @thread.alive?
|
439
|
+
end
|
440
|
+
|
441
|
+
should "not be running" do
|
442
|
+
assert_false subject.running?
|
443
|
+
end
|
444
|
+
|
445
|
+
end
|
446
|
+
|
447
|
+
class HaltWhileWaitingForWorkerTests < InitSetupTests
|
448
|
+
desc "halted while waiting for a worker"
|
449
|
+
setup do
|
450
|
+
@worker_available = false
|
451
|
+
@daemon = @daemon_class.new
|
452
|
+
@thread = @daemon.start
|
453
|
+
@daemon.halt(true)
|
454
|
+
end
|
455
|
+
subject{ @daemon }
|
456
|
+
|
457
|
+
should "not be running" do
|
458
|
+
assert_false subject.running?
|
459
|
+
end
|
460
|
+
|
461
|
+
end
|
462
|
+
|
463
|
+
class ConfigurationTests < UnitTests
|
464
|
+
include NsOptions::AssertMacros
|
465
|
+
|
466
|
+
desc "Configuration"
|
467
|
+
setup do
|
468
|
+
@queue = Qs::Queue.new do
|
469
|
+
name Factory.string
|
470
|
+
job_handler_ns 'Qs::Daemon'
|
471
|
+
job 'test', 'TestHandler'
|
472
|
+
end
|
473
|
+
|
474
|
+
@configuration = Configuration.new.tap do |c|
|
475
|
+
c.name Factory.string
|
476
|
+
c.queues << @queue
|
477
|
+
end
|
478
|
+
end
|
479
|
+
subject{ @configuration }
|
480
|
+
|
481
|
+
should have_options :name, :pid_file
|
482
|
+
should have_options :min_workers, :max_workers
|
483
|
+
should have_options :verbose_logging, :logger
|
484
|
+
should have_options :shutdown_timeout
|
485
|
+
should have_accessors :init_procs, :error_procs
|
486
|
+
should have_accessors :queues
|
487
|
+
should have_imeths :routes
|
488
|
+
should have_imeths :to_hash
|
489
|
+
should have_imeths :valid?, :validate!
|
490
|
+
|
491
|
+
should "be an ns-options proxy" do
|
492
|
+
assert_includes NsOptions::Proxy, subject.class
|
493
|
+
end
|
494
|
+
|
495
|
+
should "default its options" do
|
496
|
+
config = Configuration.new
|
497
|
+
assert_nil config.name
|
498
|
+
assert_nil config.pid_file
|
499
|
+
assert_equal 1, config.min_workers
|
500
|
+
assert_equal 4, config.max_workers
|
501
|
+
assert_true config.verbose_logging
|
502
|
+
assert_instance_of Qs::NullLogger, config.logger
|
503
|
+
assert_nil subject.shutdown_timeout
|
504
|
+
assert_equal [], config.init_procs
|
505
|
+
assert_equal [], config.error_procs
|
506
|
+
assert_equal [], config.queues
|
507
|
+
assert_equal [], config.routes
|
508
|
+
end
|
509
|
+
|
510
|
+
should "not be valid by default" do
|
511
|
+
assert_false subject.valid?
|
512
|
+
end
|
513
|
+
|
514
|
+
should "know its routes" do
|
515
|
+
assert_equal subject.queues.map(&:routes).flatten, subject.routes
|
516
|
+
end
|
517
|
+
|
518
|
+
should "include its error procs, queue redis keys and routes in its hash" do
|
519
|
+
config_hash = subject.to_hash
|
520
|
+
assert_equal subject.error_procs, config_hash[:error_procs]
|
521
|
+
expected = subject.queues.map(&:redis_key)
|
522
|
+
assert_equal expected, config_hash[:queue_redis_keys]
|
523
|
+
assert_equal subject.routes, config_hash[:routes]
|
524
|
+
end
|
525
|
+
|
526
|
+
should "call its init procs when validated" do
|
527
|
+
called = false
|
528
|
+
subject.init_procs << proc{ called = true }
|
529
|
+
subject.validate!
|
530
|
+
assert_true called
|
531
|
+
end
|
532
|
+
|
533
|
+
should "ensure its required options have been set when validated" do
|
534
|
+
subject.name = nil
|
535
|
+
assert_raises(InvalidError){ subject.validate! }
|
536
|
+
subject.name = Factory.string
|
537
|
+
|
538
|
+
subject.queues = []
|
539
|
+
assert_raises(InvalidError){ subject.validate! }
|
540
|
+
subject.queues << @queue
|
541
|
+
|
542
|
+
assert_nothing_raised{ subject.validate! }
|
543
|
+
end
|
544
|
+
|
545
|
+
should "validate its routes when validated" do
|
546
|
+
subject.routes.each{ |route| assert_nil route.handler_class }
|
547
|
+
subject.validate!
|
548
|
+
subject.routes.each{ |route| assert_not_nil route.handler_class }
|
549
|
+
end
|
550
|
+
|
551
|
+
should "be valid after being validated" do
|
552
|
+
assert_false subject.valid?
|
553
|
+
subject.validate!
|
554
|
+
assert_true subject.valid?
|
555
|
+
end
|
556
|
+
|
557
|
+
should "only be able to be validated once" do
|
558
|
+
called = 0
|
559
|
+
subject.init_procs << proc{ called += 1 }
|
560
|
+
subject.validate!
|
561
|
+
assert_equal 1, called
|
562
|
+
subject.validate!
|
563
|
+
assert_equal 1, called
|
564
|
+
end
|
565
|
+
|
566
|
+
end
|
567
|
+
|
568
|
+
class IOPipeTests < UnitTests
|
569
|
+
desc "IOPipe"
|
570
|
+
setup do
|
571
|
+
@io = IOPipe.new
|
572
|
+
end
|
573
|
+
subject{ @io }
|
574
|
+
|
575
|
+
should have_readers :reader, :writer
|
576
|
+
should have_imeths :wait, :signal
|
577
|
+
should have_imeths :setup, :teardown
|
578
|
+
|
579
|
+
should "default its reader and writer" do
|
580
|
+
assert_same IOPipe::NULL, subject.reader
|
581
|
+
assert_same IOPipe::NULL, subject.writer
|
582
|
+
end
|
583
|
+
|
584
|
+
should "be able to wait until signalled" do
|
585
|
+
subject.setup
|
586
|
+
|
587
|
+
thread = Thread.new{ subject.wait }
|
588
|
+
thread.join(0.1)
|
589
|
+
assert_equal 'sleep', thread.status
|
590
|
+
|
591
|
+
subject.signal
|
592
|
+
thread.join
|
593
|
+
assert_false thread.status
|
594
|
+
end
|
595
|
+
|
596
|
+
should "set its reader and writer to an IO pipe when setup" do
|
597
|
+
subject.setup
|
598
|
+
assert_instance_of ::IO, subject.reader
|
599
|
+
assert_instance_of ::IO, subject.writer
|
600
|
+
end
|
601
|
+
|
602
|
+
should "close its reader/writer and set them to defaults when torn down" do
|
603
|
+
subject.setup
|
604
|
+
reader = subject.reader
|
605
|
+
writer = subject.writer
|
606
|
+
|
607
|
+
subject.teardown
|
608
|
+
assert_true reader.closed?
|
609
|
+
assert_true writer.closed?
|
610
|
+
assert_same IOPipe::NULL, subject.reader
|
611
|
+
assert_same IOPipe::NULL, subject.writer
|
612
|
+
end
|
613
|
+
|
614
|
+
end
|
615
|
+
|
616
|
+
class SignalTests < UnitTests
|
617
|
+
desc "Signal"
|
618
|
+
setup do
|
619
|
+
@signal = Signal.new(:stop)
|
620
|
+
end
|
621
|
+
subject{ @signal }
|
622
|
+
|
623
|
+
should have_imeths :set, :start?, :stop?, :halt?
|
624
|
+
|
625
|
+
should "allow setting it to start" do
|
626
|
+
subject.set :start
|
627
|
+
assert_true subject.start?
|
628
|
+
assert_false subject.stop?
|
629
|
+
assert_false subject.halt?
|
630
|
+
end
|
631
|
+
|
632
|
+
should "allow setting it to stop" do
|
633
|
+
subject.set :stop
|
634
|
+
assert_false subject.start?
|
635
|
+
assert_true subject.stop?
|
636
|
+
assert_false subject.halt?
|
637
|
+
end
|
638
|
+
|
639
|
+
should "allow setting it to halt" do
|
640
|
+
subject.set :halt
|
641
|
+
assert_false subject.start?
|
642
|
+
assert_false subject.stop?
|
643
|
+
assert_true subject.halt?
|
644
|
+
end
|
645
|
+
|
646
|
+
end
|
647
|
+
|
648
|
+
TestHandler = Class.new
|
649
|
+
|
650
|
+
class PayloadHandlerSpy
|
651
|
+
attr_reader :daemon_data, :redis_item, :run_called
|
652
|
+
|
653
|
+
def initialize(daemon_data, redis_item)
|
654
|
+
@daemon_data = daemon_data
|
655
|
+
@redis_item = redis_item
|
656
|
+
@run_called = false
|
657
|
+
end
|
658
|
+
|
659
|
+
def run
|
660
|
+
@run_called = true
|
661
|
+
end
|
662
|
+
end
|
663
|
+
|
664
|
+
class ClientSpy < Qs::TestClient
|
665
|
+
attr_reader :calls
|
666
|
+
|
667
|
+
def initialize(*args)
|
668
|
+
super
|
669
|
+
@calls = []
|
670
|
+
@list = []
|
671
|
+
@mutex = Mutex.new
|
672
|
+
@cv = ConditionVariable.new
|
673
|
+
end
|
674
|
+
|
675
|
+
def block_dequeue(*args)
|
676
|
+
@calls << Call.new(:block_dequeue, args)
|
677
|
+
if @list.empty?
|
678
|
+
@mutex.synchronize{ @cv.wait(@mutex) }
|
679
|
+
end
|
680
|
+
@list.shift
|
681
|
+
end
|
682
|
+
|
683
|
+
def append(*args)
|
684
|
+
@calls << Call.new(:append, args)
|
685
|
+
@list << args
|
686
|
+
@cv.signal
|
687
|
+
end
|
688
|
+
|
689
|
+
def prepend(*args)
|
690
|
+
@calls << Call.new(:prepend, args)
|
691
|
+
@list << args
|
692
|
+
@cv.signal
|
693
|
+
end
|
694
|
+
|
695
|
+
def clear(*args)
|
696
|
+
@calls << Call.new(:clear, args)
|
697
|
+
end
|
698
|
+
|
699
|
+
Call = Struct.new(:command, :args)
|
700
|
+
end
|
701
|
+
|
702
|
+
end
|