test/unit/daemon_tests.rb in qs-0.6.1 vs test/unit/daemon_tests.rb in qs-0.7.0
- old
+ new
@@ -1,13 +1,13 @@
require 'assert'
require 'qs/daemon'
require 'dat-worker-pool/worker_pool_spy'
require 'much-plugin'
-require 'ns-options/assert_macros'
require 'thread'
require 'qs/client'
+require 'qs/logger'
require 'qs/queue'
require 'qs/queue_item'
require 'test/support/client_spy'
module Qs::Daemon
@@ -17,114 +17,84 @@
setup do
@daemon_class = Class.new{ include Qs::Daemon }
end
subject{ @daemon_class }
- should have_imeths :configuration
- should have_imeths :name, :pid_file
- should have_imeths :worker_class, :worker_params
- should have_imeths :num_workers, :workers
- should have_imeths :verbose_logging, :logger
- should have_imeths :shutdown_timeout
- should have_imeths :init, :error, :queue, :queues
+ should have_imeths :config
+ should have_imeths :name, :pid_file, :shutdown_timeout
+ should have_imeths :worker_class, :worker_params, :num_workers, :workers
+ should have_imeths :init, :init_procs, :error, :error_procs
+ should have_imeths :logger, :queue, :queues
+ should have_imeths :verbose_logging
should "use much-plugin" do
assert_includes MuchPlugin, Qs::Daemon
end
- should "know its configuration" do
- config = subject.configuration
- assert_instance_of Configuration, config
- assert_same config, subject.configuration
- end
+ should "allow setting its config values" do
+ config = subject.config
- should "allow reading/writing its configuration name" do
- new_name = Factory.string
- subject.name(new_name)
- assert_equal new_name, subject.configuration.name
- assert_equal new_name, subject.name
- end
+ exp = Factory.string
+ subject.name exp
+ assert_equal exp, config.name
- should "allow reading/writing its configuration pid file" do
- new_pid_file = Factory.string
- subject.pid_file(new_pid_file)
- expected = Pathname.new(new_pid_file)
- assert_equal expected, subject.configuration.pid_file
- assert_equal expected, subject.pid_file
- end
+ exp = Factory.file_path
+ subject.pid_file exp
+ assert_equal exp, config.pid_file
- should "allow reading/writing its configuration worker class" do
- new_worker_class = Class.new
- subject.worker_class(new_worker_class)
- assert_equal new_worker_class, subject.configuration.worker_class
- assert_equal new_worker_class, subject.worker_class
- end
+ exp = Factory.integer
+ subject.shutdown_timeout exp
+ assert_equal exp, config.shutdown_timeout
- should "allow reading/writing its configuration worker params" do
- new_worker_params = { Factory.string => Factory.string }
- subject.worker_params(new_worker_params)
- assert_equal new_worker_params, subject.configuration.worker_params
- assert_equal new_worker_params, subject.worker_params
- end
+ exp = Class.new
+ subject.worker_class exp
+ assert_equal exp, subject.config.worker_class
- should "allow reading/writing its configuration num workers" do
- new_num_workers = Factory.integer
- subject.num_workers(new_num_workers)
- assert_equal new_num_workers, subject.configuration.num_workers
- assert_equal new_num_workers, subject.num_workers
- end
+ exp = { Factory.string => Factory.string }
+ subject.worker_params exp
+ assert_equal exp, subject.config.worker_params
- should "alias workers as num workers" do
- new_workers = Factory.integer
- subject.workers(new_workers)
- assert_equal new_workers, subject.configuration.num_workers
- assert_equal new_workers, subject.workers
- end
+ exp = Factory.integer
+ subject.num_workers(exp)
+ assert_equal exp, subject.config.num_workers
+ assert_equal exp, subject.workers
- should "allow reading/writing its configuration verbose logging" do
- new_verbose = Factory.boolean
- subject.verbose_logging(new_verbose)
- assert_equal new_verbose, subject.configuration.verbose_logging
- assert_equal new_verbose, subject.verbose_logging
- end
+ exp = proc{ Factory.string }
+ assert_equal 0, config.init_procs.size
+ subject.init(&exp)
+ assert_equal 1, config.init_procs.size
+ assert_equal exp, config.init_procs.first
- should "allow reading/writing its configuration logger" do
- new_logger = Factory.string
- subject.logger(new_logger)
- assert_equal new_logger, subject.configuration.logger
- assert_equal new_logger, subject.logger
- end
+ exp = proc{ Factory.string }
+ assert_equal 0, config.error_procs.size
+ subject.error(&exp)
+ assert_equal 1, config.error_procs.size
+ assert_equal exp, config.error_procs.first
- should "allow reading/writing its configuration shutdown timeout" do
- new_shutdown_timeout = Factory.integer
- subject.shutdown_timeout(new_shutdown_timeout)
- assert_equal new_shutdown_timeout, subject.configuration.shutdown_timeout
- assert_equal new_shutdown_timeout, subject.shutdown_timeout
- end
+ exp = Logger.new(STDOUT)
+ subject.logger exp
+ assert_equal exp, config.logger
- should "allow adding init procs to its configuration" do
- new_init_proc = proc{ Factory.string }
- subject.init(&new_init_proc)
- assert_includes new_init_proc, subject.configuration.init_procs
- end
+ exp = Factory.string
+ subject.queue(exp)
+ assert_equal [exp], subject.config.queues
- should "allow adding error procs to its configuration" do
- new_error_proc = proc{ Factory.string }
- subject.error(&new_error_proc)
- assert_includes new_error_proc, subject.configuration.error_procs
+ exp = Factory.boolean
+ subject.verbose_logging exp
+ assert_equal exp, config.verbose_logging
end
- should "allow adding queues to its configuration" do
- new_queue = Factory.string
- subject.queue(new_queue)
- assert_includes new_queue, subject.configuration.queues
+ should "demeter its config values that aren't directly set" do
+ assert_equal subject.config.init_procs, subject.init_procs
+ assert_equal subject.config.error_procs, subject.error_procs
end
- should "allow reading its configuration queues" do
- new_queue = Factory.string
- subject.queue(new_queue)
- assert_equal [new_queue], subject.queues
+ should "know its queues" do
+ assert_equal [], subject.queues
+ exp = Factory.string
+ subject.queue(exp)
+ assert_equal [exp], subject.queues
end
end
class InitSetupTests < UnitTests
@@ -132,14 +102,14 @@
@qs_init_called = false
Assert.stub(Qs, :init){ @qs_init_called = true }
@daemon_class.name Factory.string
@daemon_class.pid_file Factory.file_path
+ @daemon_class.shutdown_timeout Factory.integer
@daemon_class.worker_params(Factory.string => Factory.string)
- @daemon_class.workers Factory.integer
+ @daemon_class.num_workers Factory.integer
@daemon_class.verbose_logging Factory.boolean
- @daemon_class.shutdown_timeout Factory.integer
@daemon_class.error{ Factory.string }
@queue = Qs::Queue.new do
name(Factory.string)
job 'test', TestHandler.to_s
@@ -173,96 +143,100 @@
setup do
@daemon = @daemon_class.new
end
subject{ @daemon }
- should have_readers :daemon_data, :logger
- should have_readers :signals_redis_key, :queue_redis_keys
+ should have_readers :daemon_data, :signals_redis_key
should have_imeths :name, :process_label, :pid_file
+ should have_imeths :logger, :queue_redis_keys
should have_imeths :running?
should have_imeths :start, :stop, :halt
- should "validate its configuration" do
- assert_true @daemon_class.configuration.valid?
+ should "have validated its config" do
+ assert_true @daemon_class.config.valid?
end
- should "init Qs" do
+ should "have initialized Qs" do
assert_true @qs_init_called
end
should "know its daemon data" do
- configuration = @daemon_class.configuration
- data = subject.daemon_data
+ config = @daemon_class.config
+ data = subject.daemon_data
assert_instance_of Qs::DaemonData, data
- assert_equal configuration.name, data.name
- assert_equal configuration.pid_file, data.pid_file
- assert_equal configuration.worker_class, data.worker_class
- assert_equal configuration.worker_params, data.worker_params
- assert_equal configuration.num_workers, data.num_workers
- assert_equal configuration.verbose_logging, data.verbose_logging
- assert_equal configuration.shutdown_timeout, data.shutdown_timeout
- assert_equal configuration.error_procs, data.error_procs
+ assert_equal config.name, data.name
+ assert_equal config.pid_file, data.pid_file
+ assert_equal config.shutdown_timeout, data.shutdown_timeout
+ assert_equal config.worker_class, data.worker_class
+ assert_equal config.worker_params, data.worker_params
+ assert_equal config.num_workers, data.num_workers
+ assert_equal config.error_procs, data.error_procs
- assert_equal [@queue.redis_key], data.queue_redis_keys
- assert_equal configuration.routes, data.routes.values
+ assert_instance_of config.logger.class, data.logger
- assert_instance_of configuration.logger.class, data.logger
- end
+ assert_equal config.queues.size, data.queue_redis_keys.size
+ assert_equal config.verbose_logging, data.verbose_logging
- should "know its signal and queues redis keys" do
- data = subject.daemon_data
- expected = "signals:#{data.name}-#{Socket.gethostname}-#{::Process.pid}"
- assert_equal expected, subject.signals_redis_key
- assert_equal data.queue_redis_keys, subject.queue_redis_keys
+ assert_equal config.routes, data.routes.values
end
- should "know its name, process label and pid file" do
+ should "know its signals redis keys" do
data = subject.daemon_data
- assert_equal data.name, subject.name
- assert_equal data.process_label, subject.process_label
- assert_equal data.pid_file, subject.pid_file
+ exp = "signals:#{data.name}-#{Socket.gethostname}-#{::Process.pid}"
+ assert_equal exp, subject.signals_redis_key
end
should "build a client" do
assert_not_nil @client_spy
- exp = Qs.redis_config.merge({
+ exp = Qs.redis_connect_hash.merge({
:timeout => 1,
:size => subject.daemon_data.num_workers + 1
})
- assert_equal exp, @client_spy.redis_config
+ assert_equal exp, @client_spy.redis_connect_hash
end
- should "build a worker pool" do
+ should "build a dat-worker-pool worker pool" do
data = subject.daemon_data
assert_not_nil @wp_spy
assert_equal data.worker_class, @wp_spy.worker_class
assert_equal data.dwp_logger, @wp_spy.logger
assert_equal data.num_workers, @wp_spy.num_workers
+
exp = data.worker_params.merge({
:qs_daemon_data => data,
:qs_client => @client_spy,
:qs_worker_available => @worker_available,
:qs_logger => data.logger
})
assert_equal exp, @wp_spy.worker_params
assert_false @wp_spy.start_called
end
+ should "demeter its daemon data" do
+ data = subject.daemon_data
+
+ assert_equal data.name, subject.name
+ assert_equal data.process_label, subject.process_label
+ assert_equal data.pid_file, subject.pid_file
+ assert_equal data.logger, subject.logger
+ assert_equal data.queue_redis_keys, subject.queue_redis_keys
+ end
+
should "not be running by default" do
assert_false subject.running?
end
end
class StartTests < InitTests
desc "and started"
setup do
@thread = @daemon.start
- @thread.join 0.1
+ @thread.join(JOIN_SECONDS)
end
should "ping redis" do
call = @client_spy.calls.first
assert_equal :ping, call.command
@@ -294,67 +268,81 @@
setup do
@wp_worker_available = false
@daemon = @daemon_class.new
@thread = @daemon.start
+ @thread.join(JOIN_SECONDS)
end
subject{ @daemon }
should "sleep its thread and not add work to its worker pool" do
- @thread.join(0.1)
assert_equal 'sleep', @thread.status
@client_spy.append(@queue.redis_key, Factory.string)
- @thread.join(0.1)
+ @thread.join(JOIN_SECONDS)
assert_empty @wp_spy.work_items
end
end
class RunningWithWorkerAndWorkTests < InitSetupTests
desc "running with a worker available and work"
setup do
@daemon = @daemon_class.new
@thread = @daemon.start
+ @thread.join(JOIN_SECONDS)
@encoded_payload = Factory.string
@client_spy.append(@queue.redis_key, @encoded_payload)
+ @thread.join(JOIN_SECONDS)
end
subject{ @daemon }
should "call dequeue on its client and add work to the worker pool" do
call = @client_spy.calls.last
assert_equal :block_dequeue, call.command
+
exp = [subject.signals_redis_key, subject.queue_redis_keys, 0].flatten
assert_equal exp, call.args
+
exp = Qs::QueueItem.new(@queue.redis_key, @encoded_payload)
assert_equal exp, @wp_spy.work_items.first
end
end
class RunningWithErrorWhileDequeuingTests < InitSetupTests
desc "running with an error while dequeueing"
setup do
- @daemon = @daemon_class.new
- @thread = @daemon.start
+ @mutex = Mutex.new
+ @cond_var = ConditionVariable.new
+ @daemon = @daemon_class.new
@block_dequeue_calls = 0
Assert.stub(@client_spy, :block_dequeue) do
@block_dequeue_calls += 1
+ @mutex.synchronize{ @cond_var.wait(@mutex) }
raise RuntimeError
end
- # cause the daemon to loop, its sleeping on the original block_dequeue
- # call that happened before the stub
- @client_spy.append(@queue.redis_key, Factory.string)
- @thread.join(0.1)
+
+ @thread = @daemon.start
+ @thread.join(JOIN_SECONDS)
end
+ teardown do
+ Assert.unstub(@client_spy, :block_dequeue)
+ @mutex.synchronize{ @cond_var.broadcast }
+ end
subject{ @daemon }
should "not cause the thread to exit" do
assert_true @thread.alive?
assert_equal 1, @block_dequeue_calls
- @thread.join(1)
+
+ # the daemon is sleeping on the original block_dequeue, cause it to
+ # dequeue (and error)
+ @mutex.synchronize{ @cond_var.broadcast }
+ @thread.join(Qs::Daemon::FETCH_ERR_SLEEP_TIME + JOIN_SECONDS)
+
assert_true @thread.alive?
assert_equal 2, @block_dequeue_calls
end
end
@@ -368,10 +356,11 @@
@shuffled_keys = @daemon.queue_redis_keys + [Factory.string]
Assert.stub(@daemon.queue_redis_keys, :shuffle){ @shuffled_keys }
@thread = @daemon.start
+ @thread.join(JOIN_SECONDS)
end
subject{ @daemon }
should "shuffle the queue keys to avoid queue starvation" do
call = @client_spy.calls.last
@@ -417,10 +406,11 @@
desc "stopped while waiting for a worker"
setup do
@wp_worker_available = false
@daemon = @daemon_class.new
@thread = @daemon.start
+ @thread.join(JOIN_SECONDS)
@daemon.stop(true)
end
subject{ @daemon }
should "not be running" do
@@ -464,10 +454,11 @@
desc "halted while waiting for a worker"
setup do
@wp_worker_available = false
@daemon = @daemon_class.new
@thread = @daemon.start
+ @thread.join(JOIN_SECONDS)
@daemon.halt(true)
end
subject{ @daemon }
should "not be running" do
@@ -484,11 +475,11 @@
# cause the daemon to loop, it's sleeping on the original `block_dequeue`
# call that happened before the stub
@queue_item = Qs::QueueItem.new(@queue.redis_key, Factory.string)
@client_spy.append(@queue_item.queue_redis_key, @queue_item.encoded_payload)
- @thread.join
+ @thread.join(JOIN_SECONDS)
end
should "shutdown the worker pool" do
assert_true @wp_spy.shutdown_called
assert_equal @daemon_class.shutdown_timeout, @wp_spy.shutdown_timeout
@@ -509,100 +500,119 @@
assert_false subject.running?
end
end
- class ConfigurationTests < UnitTests
- include NsOptions::AssertMacros
-
- desc "Configuration"
+ class ConfigTests < UnitTests
+ desc "Config"
setup do
@queue = Qs::Queue.new do
name Factory.string
job_handler_ns 'Qs::Daemon'
job 'test', 'TestHandler'
end
- @configuration = Configuration.new.tap do |c|
- c.name Factory.string
- c.queues << @queue
- end
+ @config_class = Config
+ @config = Config.new
end
- subject{ @configuration }
+ subject{ @config }
- should have_options :name, :pid_file
- should have_options :num_workers
- should have_options :verbose_logging, :logger
- should have_options :shutdown_timeout
- should have_accessors :init_procs, :error_procs
- should have_accessors :worker_class, :worker_params
- should have_accessors :queues
- should have_imeths :routes
- should have_imeths :to_hash
- should have_imeths :valid?, :validate!
+ should have_accessors :name, :pid_file, :shutdown_timeout
+ should have_accessors :worker_class, :worker_params, :num_workers
+ should have_accessors :init_procs, :error_procs, :logger, :queues
+ should have_accessors :verbose_logging
+ should have_imeths :routes, :valid?, :validate!
- should "be an ns-options proxy" do
- assert_includes NsOptions::Proxy, subject.class
+ should "know its default attr values" do
+ assert_equal 4, @config_class::DEFAULT_NUM_WORKERS
end
- should "default its options and attrs" do
- config = Configuration.new
- assert_nil config.name
- assert_nil config.pid_file
- assert_equal 4, config.num_workers
- assert_true config.verbose_logging
- assert_instance_of Qs::NullLogger, config.logger
+ should "default its attrs" do
+ assert_nil subject.name
+ assert_nil subject.pid_file
assert_nil subject.shutdown_timeout
- assert_equal [], config.init_procs
- assert_equal [], config.error_procs
- assert_equal DefaultWorker, config.worker_class
- assert_nil config.worker_params
- assert_equal [], config.queues
- assert_equal [], config.routes
- end
+ assert_equal DefaultWorker, subject.worker_class
- should "not be valid by default" do
- assert_false subject.valid?
+ assert_nil subject.worker_params
+
+ exp = @config_class::DEFAULT_NUM_WORKERS
+ assert_equal exp, subject.num_workers
+
+ assert_equal [], subject.init_procs
+ assert_equal [], subject.error_procs
+
+ assert_instance_of Qs::NullLogger, subject.logger
+
+ assert_equal [], subject.queues
+ assert_equal true, subject.verbose_logging
end
should "know its routes" do
- assert_equal subject.queues.map(&:routes).flatten, subject.routes
+ exp = subject.queues.map(&:routes).flatten
+ assert_equal exp, subject.routes
end
- should "include some attrs (not just the options) in its hash" do
- config_hash = subject.to_hash
+ should "not be valid until validate! has been run" do
+ assert_false subject.valid?
- assert_equal subject.error_procs, config_hash[:error_procs]
- assert_equal subject.worker_class, config_hash[:worker_class]
- assert_equal subject.worker_params, config_hash[:worker_params]
- assert_equal subject.routes, config_hash[:routes]
+ subject.name = Factory.string
+ subject.queues << @queue
- exp = subject.queues.map(&:redis_key)
- assert_equal exp, config_hash[:queue_redis_keys]
- end
-
- should "call its init procs when validated" do
- called = false
- subject.init_procs << proc{ called = true }
subject.validate!
- assert_true called
+ assert_true subject.valid?
end
- should "ensure its required options have been set when validated" do
+ should "complain if validating and its name is nil or it has no queues" do
subject.name = nil
+ subject.queues << @queue
assert_raises(InvalidError){ subject.validate! }
- subject.name = Factory.string
- subject.queues = []
+ subject.name = Factory.string
+ subject.queues.clear
assert_raises(InvalidError){ subject.validate! }
+ end
+
+ should "complain if validating and its worker class isn't a Worker" do
+ subject.name = Factory.string
subject.queues << @queue
- assert_nothing_raised{ subject.validate! }
+ subject.worker_class = Module.new
+ assert_raises(InvalidError){ subject.validate! }
+
+ subject.worker_class = Class.new
+ assert_raises(InvalidError){ subject.validate! }
end
- should "validate its routes when validated" do
+ end
+
+ class ValidationTests < ConfigTests
+ desc "when successfully validated"
+ setup do
+ @config = Config.new.tap do |c|
+ c.name = Factory.string
+ c.queues << @queue
+ end
+
+ @initialized = false
+ @config.init_procs << proc{ @initialized = true }
+
+ @other_initialized = false
+ @config.init_procs << proc{ @other_initialized = true }
+ end
+
+ should "call its init procs" do
+ assert_equal false, @initialized
+ assert_equal false, @other_initialized
+
+ subject.validate!
+
+ assert_equal true, @initialized
+ assert_equal true, @other_initialized
+ end
+
+ should "validate its routes" do
subject.routes.each{ |route| assert_nil route.handler_class }
subject.validate!
subject.routes.each{ |route| assert_not_nil route.handler_class }
end
@@ -612,16 +622,10 @@
subject.worker_class = Class.new
assert_raises(InvalidError){ subject.validate! }
end
- should "be valid after being validated" do
- assert_false subject.valid?
- subject.validate!
- assert_true subject.valid?
- end
-
should "only be able to be validated once" do
called = 0
subject.init_procs << proc{ called += 1 }
subject.validate!
assert_equal 1, called
@@ -629,10 +633,31 @@
assert_equal 1, called
end
end
+ class WorkerAvailableTests < UnitTests
+ desc "WorkerAvailable"
+ setup do
+ @worker_available = WorkerAvailable.new
+ end
+ subject{ @worker_available }
+
+ should have_imeths :wait, :signal
+
+ should "allow waiting and signalling" do
+ thread = Thread.new{ subject.wait }
+ thread.join(JOIN_SECONDS)
+ assert_equal 'sleep', thread.status
+
+ subject.signal
+ thread.join(JOIN_SECONDS)
+ assert_equal false, thread.status # dead, done running
+ end
+
+ end
+
class StateTests < UnitTests
desc "State"
setup do
@state = State.new
end
@@ -658,27 +683,9 @@
should "know if its in the halt state" do
assert_false subject.halt?
subject.set :halt
assert_true subject.halt?
- end
-
- end
-
- class WorkerAvailableTests < UnitTests
- desc "WorkerAvailable"
- setup do
- @worker_available = WorkerAvailable.new
- end
- subject{ @worker_available }
-
- should have_imeths :wait, :signal
-
- should "allow waiting and signalling" do
- thread = Thread.new{ subject.wait }
- assert_equal 'sleep', thread.status
- subject.signal
- assert_equal false, thread.status # dead, done running
end
end
TestHandler = Class.new