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,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
+