qs 0.0.1 → 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 +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
|