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.
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