qs 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/.gitignore +1 -0
  2. data/Gemfile +6 -1
  3. data/LICENSE.txt +1 -1
  4. data/bench/config.qs +46 -0
  5. data/bench/queue.rb +8 -0
  6. data/bench/report.rb +114 -0
  7. data/bench/report.txt +11 -0
  8. data/bin/qs +7 -0
  9. data/lib/qs/cli.rb +124 -0
  10. data/lib/qs/client.rb +121 -0
  11. data/lib/qs/config_file.rb +79 -0
  12. data/lib/qs/daemon.rb +350 -0
  13. data/lib/qs/daemon_data.rb +46 -0
  14. data/lib/qs/error_handler.rb +58 -0
  15. data/lib/qs/job.rb +70 -0
  16. data/lib/qs/job_handler.rb +90 -0
  17. data/lib/qs/logger.rb +23 -0
  18. data/lib/qs/payload_handler.rb +136 -0
  19. data/lib/qs/pid_file.rb +42 -0
  20. data/lib/qs/process.rb +136 -0
  21. data/lib/qs/process_signal.rb +20 -0
  22. data/lib/qs/qs_runner.rb +49 -0
  23. data/lib/qs/queue.rb +69 -0
  24. data/lib/qs/redis_item.rb +33 -0
  25. data/lib/qs/route.rb +52 -0
  26. data/lib/qs/runner.rb +26 -0
  27. data/lib/qs/test_helpers.rb +17 -0
  28. data/lib/qs/test_runner.rb +43 -0
  29. data/lib/qs/version.rb +1 -1
  30. data/lib/qs.rb +92 -2
  31. data/qs.gemspec +7 -2
  32. data/test/helper.rb +8 -1
  33. data/test/support/app_daemon.rb +74 -0
  34. data/test/support/config.qs +7 -0
  35. data/test/support/config_files/empty.qs +0 -0
  36. data/test/support/config_files/invalid.qs +1 -0
  37. data/test/support/config_files/valid.qs +7 -0
  38. data/test/support/config_invalid_run.qs +3 -0
  39. data/test/support/config_no_run.qs +0 -0
  40. data/test/support/factory.rb +14 -0
  41. data/test/support/pid_file_spy.rb +19 -0
  42. data/test/support/runner_spy.rb +17 -0
  43. data/test/system/daemon_tests.rb +226 -0
  44. data/test/unit/cli_tests.rb +188 -0
  45. data/test/unit/client_tests.rb +269 -0
  46. data/test/unit/config_file_tests.rb +59 -0
  47. data/test/unit/daemon_data_tests.rb +96 -0
  48. data/test/unit/daemon_tests.rb +702 -0
  49. data/test/unit/error_handler_tests.rb +163 -0
  50. data/test/unit/job_handler_tests.rb +253 -0
  51. data/test/unit/job_tests.rb +132 -0
  52. data/test/unit/logger_tests.rb +38 -0
  53. data/test/unit/payload_handler_tests.rb +276 -0
  54. data/test/unit/pid_file_tests.rb +70 -0
  55. data/test/unit/process_signal_tests.rb +61 -0
  56. data/test/unit/process_tests.rb +371 -0
  57. data/test/unit/qs_runner_tests.rb +166 -0
  58. data/test/unit/qs_tests.rb +217 -0
  59. data/test/unit/queue_tests.rb +132 -0
  60. data/test/unit/redis_item_tests.rb +49 -0
  61. data/test/unit/route_tests.rb +81 -0
  62. data/test/unit/runner_tests.rb +63 -0
  63. data/test/unit/test_helper_tests.rb +61 -0
  64. data/test/unit/test_runner_tests.rb +128 -0
  65. 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