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,217 @@
1
+ require 'assert'
2
+ require 'qs'
3
+
4
+ require 'hella-redis/connection_spy'
5
+ require 'ns-options/assert_macros'
6
+ require 'qs/job'
7
+ require 'qs/queue'
8
+
9
+ module Qs
10
+
11
+ class UnitTests < Assert::Context
12
+ desc "Qs"
13
+ setup do
14
+ Qs.reset!
15
+ @module = Qs
16
+ end
17
+ teardown do
18
+ Qs.reset!
19
+ Qs.init
20
+ end
21
+ subject{ @module }
22
+
23
+ should have_imeths :config, :configure, :init, :reset!
24
+ should have_imeths :enqueue, :push
25
+ should have_imeths :serialize, :deserialize
26
+ should have_imeths :client, :redis, :redis_config
27
+
28
+ should "know its config" do
29
+ assert_instance_of Config, subject.config
30
+ end
31
+
32
+ should "allow configuring its config" do
33
+ yielded = nil
34
+ subject.configure{ |c| yielded = c }
35
+ assert_equal subject.config, yielded
36
+ end
37
+
38
+ should "not have a client or redis connection by default" do
39
+ assert_nil subject.client
40
+ assert_nil subject.redis
41
+ end
42
+
43
+ should "know its redis config" do
44
+ expected = subject.config.redis.to_hash
45
+ assert_equal expected, subject.redis_config
46
+ end
47
+
48
+ end
49
+
50
+ class InitTests < UnitTests
51
+ desc "when init"
52
+ setup do
53
+ @module.config.serializer = proc{ |v| v.to_s }
54
+ @module.config.deserializer = proc{ |v| v.to_i }
55
+ @module.config.redis.ip = Factory.string
56
+ @module.config.redis.port = Factory.integer
57
+ @module.config.redis.db = Factory.integer
58
+
59
+ @client_spy = nil
60
+ Assert.stub(Client, :new) do |*args|
61
+ @client_spy = ClientSpy.new(*args)
62
+ end
63
+
64
+ @module.init
65
+ end
66
+
67
+ should "set its configured redis url" do
68
+ expected = RedisUrl.new(
69
+ subject.config.redis.ip,
70
+ subject.config.redis.port,
71
+ subject.config.redis.db
72
+ )
73
+ assert_equal expected, subject.config.redis.url
74
+ end
75
+
76
+ should "build a client" do
77
+ assert_equal @client_spy, subject.client
78
+ assert_equal @client_spy.redis, subject.redis
79
+ assert_equal subject.redis_config, @client_spy.redis_config
80
+ end
81
+
82
+ should "call enqueue on its client using `enqueue`" do
83
+ queue = Qs::Queue.new{ name Factory.string }
84
+ job_name = Factory.string
85
+ job_params = { Factory.string => Factory.string }
86
+ subject.enqueue(queue, job_name, job_params)
87
+
88
+ call = @client_spy.enqueue_calls.last
89
+ assert_equal queue, call.queue
90
+ assert_equal job_name, call.job_name
91
+ assert_equal job_params, call.job_params
92
+ end
93
+
94
+ should "call push on its client using `push`" do
95
+ queue_name = Factory.string
96
+ payload = { Factory.string => Factory.string }
97
+ subject.push(queue_name, payload)
98
+
99
+ call = @client_spy.push_calls.last
100
+ assert_equal queue_name, call.queue_name
101
+ assert_equal payload, call.payload
102
+ end
103
+
104
+ should "use the configured serializer using `serialize`" do
105
+ value = Factory.integer
106
+ result = subject.serialize(value)
107
+ assert_equal value.to_s, result
108
+ end
109
+
110
+ should "use the configured deserializer using `deserialize`" do
111
+ value = Factory.integer.to_s
112
+ result = subject.deserialize(value)
113
+ assert_equal value.to_i, result
114
+ end
115
+
116
+ should "not reset its client or redis connection when init again" do
117
+ client = subject.client
118
+ redis = subject.redis
119
+ subject.init
120
+ assert_same client, subject.client
121
+ assert_same redis, subject.redis
122
+ end
123
+
124
+ should "reset itself using `reset!`" do
125
+ subject.reset!
126
+ assert_nil subject.config.redis.url
127
+ assert_nil subject.client
128
+ assert_nil subject.redis
129
+ assert_raises(NoMethodError){ subject.serialize(Factory.integer) }
130
+ assert_raises(NoMethodError){ subject.deserialize(Factory.integer) }
131
+ end
132
+
133
+ end
134
+
135
+ class ConfigTests < UnitTests
136
+ include NsOptions::AssertMacros
137
+
138
+ desc "Config"
139
+ setup do
140
+ @config = Config.new
141
+ end
142
+ subject{ @config }
143
+
144
+ should have_options :serializer, :deserializer, :timeout
145
+ should have_namespace :redis
146
+
147
+ should "know its default serializer/deserializer" do
148
+ payload = { Factory.string => Factory.string }
149
+
150
+ exp = JSON.dump(payload)
151
+ serialized_payload = subject.serializer.call(payload)
152
+ assert_equal exp, serialized_payload
153
+ exp = JSON.load(exp)
154
+ assert_equal exp, subject.deserializer.call(serialized_payload)
155
+ end
156
+
157
+ should "know its default timeout" do
158
+ assert_nil subject.timeout
159
+ end
160
+
161
+ should "know its default redis options" do
162
+ assert_equal 'localhost', subject.redis.ip
163
+ assert_equal 6379, subject.redis.port
164
+ assert_equal 0, subject.redis.db
165
+ assert_equal 'qs', subject.redis.redis_ns
166
+ assert_equal 'ruby', subject.redis.driver
167
+ assert_equal 1, subject.redis.timeout
168
+ assert_equal 4, subject.redis.size
169
+ assert_nil subject.redis.url
170
+ end
171
+
172
+ end
173
+
174
+ class RedisUrlTests < UnitTests
175
+ desc "RedisUrl"
176
+ subject{ RedisUrl }
177
+
178
+ should "build a redis url when passed an ip, port and db" do
179
+ ip = Factory.string
180
+ port = Factory.integer
181
+ db = Factory.integer
182
+ expected = "redis://#{ip}:#{port}/#{db}"
183
+ assert_equal expected, subject.new(ip, port, db)
184
+ end
185
+
186
+ should "not return a url with an ip, port or db" do
187
+ assert_nil subject.new(nil, Factory.integer, Factory.integer)
188
+ assert_nil subject.new(Factory.string, nil, Factory.integer)
189
+ assert_nil subject.new(Factory.string, Factory.integer, nil)
190
+ end
191
+
192
+ end
193
+
194
+ class ClientSpy
195
+ attr_reader :redis_config, :redis
196
+ attr_reader :enqueue_calls, :push_calls
197
+
198
+ def initialize(redis_confg)
199
+ @redis_config = redis_confg
200
+ @redis = Factory.string
201
+ @enqueue_calls = []
202
+ @push_calls = []
203
+ end
204
+
205
+ def enqueue(queue, job_name, job_params = nil)
206
+ @enqueue_calls << EnqueueCall.new(queue, job_name, job_params)
207
+ end
208
+
209
+ def push(queue_name, payload)
210
+ @push_calls << PushCall.new(queue_name, payload)
211
+ end
212
+
213
+ EnqueueCall = Struct.new(:queue, :job_name, :job_params)
214
+ PushCall = Struct.new(:queue_name, :payload)
215
+ end
216
+
217
+ end
@@ -0,0 +1,132 @@
1
+ require 'assert'
2
+ require 'qs/queue'
3
+
4
+ require 'test/support/factory'
5
+
6
+ class Qs::Queue
7
+
8
+ class UnitTests < Assert::Context
9
+ desc "Qs::Queue"
10
+ setup do
11
+ @queue = Qs::Queue.new do
12
+ name Factory.string
13
+ end
14
+ end
15
+ subject{ @queue }
16
+
17
+ should have_readers :routes, :enqueued_jobs
18
+ should have_imeths :name, :redis_key, :job_handler_ns, :job
19
+ should have_imeths :enqueue, :add
20
+ should have_imeths :reset!
21
+
22
+ should "default its routes to an empty array" do
23
+ assert_equal [], subject.routes
24
+ end
25
+
26
+ should "default its enqueued jobs to an empty array" do
27
+ assert_equal [], subject.enqueued_jobs
28
+ end
29
+
30
+ should "allow setting its name" do
31
+ name = Factory.string
32
+ subject.name name
33
+ assert_equal name, subject.name
34
+ end
35
+
36
+ should "know its redis key" do
37
+ result = subject.redis_key
38
+ assert_equal RedisKey.new(subject.name), result
39
+ assert_same result, subject.redis_key
40
+ end
41
+
42
+ should "not have a job handler ns by default" do
43
+ assert_nil subject.job_handler_ns
44
+ end
45
+
46
+ should "allow setting its job handler ns" do
47
+ namespace = Factory.string
48
+ subject.job_handler_ns namespace
49
+ assert_equal namespace, subject.job_handler_ns
50
+ end
51
+
52
+ should "allow adding routes using `job`" do
53
+ job_name = Factory.string
54
+ handler_name = Factory.string
55
+ subject.job job_name, handler_name
56
+
57
+ route = subject.routes.last
58
+ assert_instance_of Qs::Route, route
59
+ assert_equal job_name, route.name
60
+ assert_equal handler_name, route.handler_class_name
61
+ end
62
+
63
+ should "use its job handler ns when adding routes" do
64
+ namespace = Factory.string
65
+ subject.job_handler_ns namespace
66
+
67
+ job_name = Factory.string
68
+ handler_name = Factory.string
69
+ subject.job job_name, handler_name
70
+
71
+ route = subject.routes.last
72
+ expected = "#{namespace}::#{handler_name}"
73
+ assert_equal expected, route.handler_class_name
74
+ end
75
+
76
+ should "know its custom inspect" do
77
+ reference = '0x0%x' % (subject.object_id << 1)
78
+ expected = "#<#{subject.class}:#{reference} " \
79
+ "@name=#{subject.name.inspect} " \
80
+ "@job_handler_ns=#{subject.job_handler_ns.inspect}>"
81
+ assert_equal expected, subject.inspect
82
+ end
83
+
84
+ should "require a name when initialized" do
85
+ assert_raises(InvalidError){ Qs::Queue.new }
86
+ end
87
+
88
+ end
89
+
90
+ class EnqueueTests < UnitTests
91
+ setup do
92
+ @enqueue_args = nil
93
+ Assert.stub(Qs, :enqueue){ |*args| @enqueue_args = args }
94
+
95
+ @job_name = Factory.string
96
+ @job_params = { Factory.string => Factory.string }
97
+ end
98
+
99
+ should "add jobs using `enqueue`" do
100
+ result = subject.enqueue(@job_name, @job_params)
101
+ exp = [subject, @job_name, @job_params]
102
+ assert_equal exp, @enqueue_args
103
+ assert_equal @enqueue_args, result
104
+ end
105
+
106
+ should "add jobs using `add`" do
107
+ result = subject.add(@job_name, @job_params)
108
+ exp = [subject, @job_name, @job_params]
109
+ assert_equal exp, @enqueue_args
110
+ assert_equal @enqueue_args, result
111
+ end
112
+
113
+ end
114
+
115
+ class RedisKeyTests < UnitTests
116
+ desc "RedisKey"
117
+ subject{ RedisKey }
118
+
119
+ should have_imeths :parse_name, :new
120
+
121
+ should "know how to build a redis key" do
122
+ assert_equal "queues:#{@queue.name}", subject.new(@queue.name)
123
+ end
124
+
125
+ should "know how to parse a queue name from a key" do
126
+ redis_key = subject.new(@queue.name)
127
+ assert_equal @queue.name, subject.parse_name(redis_key)
128
+ end
129
+
130
+ end
131
+
132
+ end
@@ -0,0 +1,49 @@
1
+ require 'assert'
2
+ require 'qs/redis_item'
3
+
4
+ class Qs::RedisItem
5
+
6
+ class UnitTests < Assert::Context
7
+ desc "Qs::RedisItem"
8
+ setup do
9
+ @queue_redis_key = Factory.string
10
+ @serialized_payload = Factory.string
11
+ @redis_item = Qs::RedisItem.new(@queue_redis_key, @serialized_payload)
12
+ end
13
+ subject{ @redis_item }
14
+
15
+ should have_readers :queue_redis_key, :serialized_payload
16
+ should have_accessors :started, :finished
17
+ should have_accessors :job, :handler_class
18
+ should have_accessors :exception, :time_taken
19
+
20
+ should "know its queue redis key and serialized payload" do
21
+ assert_equal @queue_redis_key, subject.queue_redis_key
22
+ assert_equal @serialized_payload, subject.serialized_payload
23
+ end
24
+
25
+ should "defaults its other attributes" do
26
+ assert_false subject.started
27
+ assert_false subject.finished
28
+
29
+ assert_nil subject.job
30
+ assert_nil subject.handler_class
31
+ assert_nil subject.exception
32
+ assert_nil subject.time_taken
33
+ end
34
+
35
+ should "know if it equals another item" do
36
+ exp = Qs::RedisItem.new(@queue_redis_key, @serialized_payload)
37
+ assert_equal exp, subject
38
+
39
+ redis_key = Qs::Queue::RedisKey.new(Factory.string)
40
+ exp = Qs::RedisItem.new(redis_key, @serialized_payload)
41
+ assert_not_equal exp, subject
42
+
43
+ exp = Qs::RedisItem.new(@queue_redis_key, Factory.string)
44
+ assert_not_equal exp, subject
45
+ end
46
+
47
+ end
48
+
49
+ end
@@ -0,0 +1,81 @@
1
+ require 'assert'
2
+ require 'qs/route'
3
+
4
+ require 'qs/daemon_data'
5
+ require 'qs/logger'
6
+ require 'qs/job'
7
+ require 'test/support/runner_spy'
8
+
9
+ class Qs::Route
10
+
11
+ class UnitTests < Assert::Context
12
+ desc "Qs::Route"
13
+ setup do
14
+ @name = Factory.string
15
+ @handler_class_name = TestHandler.to_s
16
+ @route = Qs::Route.new(@name, @handler_class_name)
17
+ end
18
+ subject{ @route }
19
+
20
+ should have_readers :name, :handler_class_name, :handler_class
21
+ should have_imeths :validate!, :run
22
+
23
+ should "know its name and handler class name" do
24
+ assert_equal @name, subject.name
25
+ assert_equal @handler_class_name, subject.handler_class_name
26
+ end
27
+
28
+ should "not know its handler class by default" do
29
+ assert_nil subject.handler_class
30
+ end
31
+
32
+ should "constantize its handler class after being validated" do
33
+ subject.validate!
34
+ assert_equal TestHandler, subject.handler_class
35
+ end
36
+
37
+ end
38
+
39
+ class RunTests < UnitTests
40
+ desc "when run"
41
+ setup do
42
+ @job = Qs::Job.new(Factory.string, Factory.string => Factory.string)
43
+ @daemon_data = Qs::DaemonData.new(:logger => Qs::NullLogger.new)
44
+
45
+ @runner_spy = nil
46
+ Assert.stub(Qs::QsRunner, :new) do |*args|
47
+ @runner_spy = RunnerSpy.new(*args)
48
+ end
49
+
50
+ @route.run(@job, @daemon_data)
51
+ end
52
+
53
+ should "build and run a qs runner" do
54
+ assert_not_nil @runner_spy
55
+ assert_equal @route.handler_class, @runner_spy.handler_class
56
+ expected = {
57
+ :job => @job,
58
+ :params => @job.params,
59
+ :logger => @daemon_data.logger
60
+ }
61
+ assert_equal expected, @runner_spy.args
62
+ assert_true @runner_spy.run_called
63
+ end
64
+
65
+ end
66
+
67
+ class InvalidHandlerClassNameTests < UnitTests
68
+ desc "with an invalid handler class name"
69
+ setup do
70
+ @route = Qs::Route.new(@name, Factory.string)
71
+ end
72
+
73
+ should "raise a no handler class error when validated" do
74
+ assert_raises(Qs::NoHandlerClassError){ subject.validate! }
75
+ end
76
+
77
+ end
78
+
79
+ TestHandler = Class.new
80
+
81
+ end
@@ -0,0 +1,63 @@
1
+ require 'assert'
2
+ require 'qs/runner'
3
+
4
+ require 'qs/job_handler'
5
+ require 'qs/logger'
6
+
7
+ class Qs::Runner
8
+
9
+ class UnitTests < Assert::Context
10
+ desc "Qs::Runner"
11
+ setup do
12
+ @runner_class = Qs::Runner
13
+ end
14
+ subject{ @runner_class }
15
+
16
+ end
17
+
18
+ class InitTests < UnitTests
19
+ desc "when init"
20
+ setup do
21
+ @handler_class = TestJobHandler
22
+ @runner = @runner_class.new(@handler_class)
23
+ end
24
+ subject{ @runner }
25
+
26
+ should have_readers :handler_class, :handler
27
+ should have_readers :job, :params, :logger
28
+ should have_imeths :run
29
+
30
+ should "know its handler class and handler" do
31
+ assert_equal @handler_class, subject.handler_class
32
+ assert_instance_of @handler_class, subject.handler
33
+ end
34
+
35
+ should "not set its job, params or logger by default" do
36
+ assert_nil subject.job
37
+ assert_equal({}, subject.params)
38
+ assert_instance_of Qs::NullLogger, subject.logger
39
+ end
40
+
41
+ should "allow passing a job, params and logger" do
42
+ args = {
43
+ :job => Factory.string,
44
+ :params => Factory.string,
45
+ :logger => Factory.string
46
+ }
47
+ runner = @runner_class.new(@handler_class, args)
48
+ assert_equal args[:job], runner.job
49
+ assert_equal args[:params], runner.params
50
+ assert_equal args[:logger], runner.logger
51
+ end
52
+
53
+ should "raise a not implemented error when run" do
54
+ assert_raises(NotImplementedError){ subject.run }
55
+ end
56
+
57
+ end
58
+
59
+ class TestJobHandler
60
+ include Qs::JobHandler
61
+ end
62
+
63
+ end
@@ -0,0 +1,61 @@
1
+ require 'assert'
2
+ require 'qs/test_helpers'
3
+
4
+ require 'qs/job_handler'
5
+ require 'test/support/runner_spy'
6
+
7
+ module Qs::TestHelpers
8
+
9
+ class UnitTests < Assert::Context
10
+ desc "Qs::TestHelpers"
11
+ setup do
12
+ @test_helpers = Qs::TestHelpers
13
+ end
14
+ subject{ @test_helpers }
15
+
16
+ end
17
+
18
+ class MixinTests < UnitTests
19
+ desc "as a mixin"
20
+ setup do
21
+ context_class = Class.new{ include Qs::TestHelpers }
22
+ @context = context_class.new
23
+ end
24
+ subject{ @context }
25
+
26
+ should have_imeths :test_runner, :test_handler
27
+
28
+ end
29
+
30
+ class HandlerTestRunnerTests < MixinTests
31
+ desc "for handler testing"
32
+ setup do
33
+ @handler_class = Class.new
34
+ @args = { Factory.string => Factory.string }
35
+
36
+ @runner_spy = nil
37
+ Assert.stub(Qs::TestRunner, :new) do |*args|
38
+ @runner_spy = RunnerSpy.new(*args)
39
+ end
40
+ end
41
+
42
+ should "build a test runner for a given handler" do
43
+ result = subject.test_runner(@handler_class, @args)
44
+
45
+ assert_not_nil @runner_spy
46
+ assert_equal @handler_class, @runner_spy.handler_class
47
+ assert_equal @args, @runner_spy.args
48
+ assert_equal @runner_spy, result
49
+ end
50
+
51
+ should "return an initialized handler instance" do
52
+ result = subject.test_handler(@handler_class, @args)
53
+
54
+ assert_not_nil @runner_spy
55
+ assert_equal @runner_spy.handler, result
56
+ end
57
+
58
+ end
59
+
60
+ end
61
+