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.
- data/.gitignore +1 -0
- data/Gemfile +6 -1
- data/LICENSE.txt +1 -1
- data/bench/config.qs +46 -0
- data/bench/queue.rb +8 -0
- data/bench/report.rb +114 -0
- data/bench/report.txt +11 -0
- data/bin/qs +7 -0
- data/lib/qs/cli.rb +124 -0
- data/lib/qs/client.rb +121 -0
- data/lib/qs/config_file.rb +79 -0
- data/lib/qs/daemon.rb +350 -0
- data/lib/qs/daemon_data.rb +46 -0
- data/lib/qs/error_handler.rb +58 -0
- data/lib/qs/job.rb +70 -0
- data/lib/qs/job_handler.rb +90 -0
- data/lib/qs/logger.rb +23 -0
- data/lib/qs/payload_handler.rb +136 -0
- data/lib/qs/pid_file.rb +42 -0
- data/lib/qs/process.rb +136 -0
- data/lib/qs/process_signal.rb +20 -0
- data/lib/qs/qs_runner.rb +49 -0
- data/lib/qs/queue.rb +69 -0
- data/lib/qs/redis_item.rb +33 -0
- data/lib/qs/route.rb +52 -0
- data/lib/qs/runner.rb +26 -0
- data/lib/qs/test_helpers.rb +17 -0
- data/lib/qs/test_runner.rb +43 -0
- data/lib/qs/version.rb +1 -1
- data/lib/qs.rb +92 -2
- data/qs.gemspec +7 -2
- data/test/helper.rb +8 -1
- data/test/support/app_daemon.rb +74 -0
- data/test/support/config.qs +7 -0
- data/test/support/config_files/empty.qs +0 -0
- data/test/support/config_files/invalid.qs +1 -0
- data/test/support/config_files/valid.qs +7 -0
- data/test/support/config_invalid_run.qs +3 -0
- data/test/support/config_no_run.qs +0 -0
- data/test/support/factory.rb +14 -0
- data/test/support/pid_file_spy.rb +19 -0
- data/test/support/runner_spy.rb +17 -0
- data/test/system/daemon_tests.rb +226 -0
- data/test/unit/cli_tests.rb +188 -0
- data/test/unit/client_tests.rb +269 -0
- data/test/unit/config_file_tests.rb +59 -0
- data/test/unit/daemon_data_tests.rb +96 -0
- data/test/unit/daemon_tests.rb +702 -0
- data/test/unit/error_handler_tests.rb +163 -0
- data/test/unit/job_handler_tests.rb +253 -0
- data/test/unit/job_tests.rb +132 -0
- data/test/unit/logger_tests.rb +38 -0
- data/test/unit/payload_handler_tests.rb +276 -0
- data/test/unit/pid_file_tests.rb +70 -0
- data/test/unit/process_signal_tests.rb +61 -0
- data/test/unit/process_tests.rb +371 -0
- data/test/unit/qs_runner_tests.rb +166 -0
- data/test/unit/qs_tests.rb +217 -0
- data/test/unit/queue_tests.rb +132 -0
- data/test/unit/redis_item_tests.rb +49 -0
- data/test/unit/route_tests.rb +81 -0
- data/test/unit/runner_tests.rb +63 -0
- data/test/unit/test_helper_tests.rb +61 -0
- data/test/unit/test_runner_tests.rb +128 -0
- metadata +180 -15
@@ -0,0 +1,269 @@
|
|
1
|
+
require 'assert'
|
2
|
+
require 'qs/client'
|
3
|
+
|
4
|
+
require 'hella-redis/connection_spy'
|
5
|
+
require 'qs'
|
6
|
+
require 'qs/job'
|
7
|
+
require 'qs/queue'
|
8
|
+
|
9
|
+
module Qs::Client
|
10
|
+
|
11
|
+
class UnitTests < Assert::Context
|
12
|
+
desc "Qs::Client"
|
13
|
+
setup do
|
14
|
+
@current_test_mode = ENV['QS_TEST_MODE']
|
15
|
+
ENV['QS_TEST_MODE'] = 'yes'
|
16
|
+
Qs.init
|
17
|
+
|
18
|
+
@redis_config = Qs.redis_config
|
19
|
+
@queue = Qs::Queue.new{ name Factory.string }
|
20
|
+
@job_name = Factory.string
|
21
|
+
@job_params = { Factory.string => Factory.string }
|
22
|
+
end
|
23
|
+
teardown do
|
24
|
+
Qs.reset!
|
25
|
+
ENV['QS_TEST_MODE'] = @current_test_mode
|
26
|
+
end
|
27
|
+
subject{ Qs::Client }
|
28
|
+
|
29
|
+
should have_imeths :new
|
30
|
+
|
31
|
+
should "return a qs client using `new`" do
|
32
|
+
ENV.delete('QS_TEST_MODE')
|
33
|
+
client = subject.new(@redis_config)
|
34
|
+
assert_instance_of Qs::QsClient, client
|
35
|
+
end
|
36
|
+
|
37
|
+
should "return a test client using `new` in test mode" do
|
38
|
+
client = subject.new(@redis_config)
|
39
|
+
assert_instance_of Qs::TestClient, client
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
class MixinTests < UnitTests
|
45
|
+
setup do
|
46
|
+
@client = FakeClient.new(@redis_config)
|
47
|
+
end
|
48
|
+
subject{ @client }
|
49
|
+
|
50
|
+
should have_readers :redis_config, :redis
|
51
|
+
should have_imeths :enqueue, :push
|
52
|
+
should have_imeths :block_dequeue
|
53
|
+
should have_imeths :append, :prepend
|
54
|
+
should have_imeths :clear
|
55
|
+
|
56
|
+
should "know its redis config" do
|
57
|
+
assert_equal @redis_config, subject.redis_config
|
58
|
+
end
|
59
|
+
|
60
|
+
should "not have a redis connection" do
|
61
|
+
assert_nil subject.redis
|
62
|
+
end
|
63
|
+
|
64
|
+
should "build a job, enqueue it and return it using `enqueue`" do
|
65
|
+
result = subject.enqueue(@queue, @job_name, @job_params)
|
66
|
+
enqueued_job = subject.enqueued_jobs.last
|
67
|
+
assert_equal @job_name, enqueued_job.name
|
68
|
+
assert_equal @job_params, enqueued_job.params
|
69
|
+
assert_equal enqueued_job, result
|
70
|
+
end
|
71
|
+
|
72
|
+
should "default the job's params to an empty hash using `enqueue`" do
|
73
|
+
subject.enqueue(@queue, @job_name)
|
74
|
+
enqueued_job = subject.enqueued_jobs.last
|
75
|
+
assert_equal({}, enqueued_job.params)
|
76
|
+
end
|
77
|
+
|
78
|
+
should "raise a not implemented error using `push`" do
|
79
|
+
assert_raises(NotImplementedError) do
|
80
|
+
subject.push(Factory.string, Factory.string)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
class RedisCallTests < MixinTests
|
87
|
+
setup do
|
88
|
+
@connection_spy = HellaRedis::ConnectionSpy.new(@client.redis_config)
|
89
|
+
Assert.stub(@client, :redis){ @connection_spy }
|
90
|
+
|
91
|
+
@queue_redis_key = Factory.string
|
92
|
+
@serialized_payload = Factory.string
|
93
|
+
end
|
94
|
+
|
95
|
+
should "block pop from the front of a list using `block_dequeue`" do
|
96
|
+
args = (1..Factory.integer(3)).map{ Factory.string } + [Factory.integer]
|
97
|
+
subject.block_dequeue(*args)
|
98
|
+
|
99
|
+
call = @connection_spy.redis_calls.last
|
100
|
+
assert_equal :brpop, call.command
|
101
|
+
assert_equal args, call.args
|
102
|
+
end
|
103
|
+
|
104
|
+
should "add a serialized payload to the end of a list using `append`" do
|
105
|
+
subject.append(@queue_redis_key, @serialized_payload)
|
106
|
+
|
107
|
+
call = @connection_spy.redis_calls.last
|
108
|
+
assert_equal :lpush, call.command
|
109
|
+
assert_equal @queue_redis_key, call.args.first
|
110
|
+
assert_equal @serialized_payload, call.args.last
|
111
|
+
end
|
112
|
+
|
113
|
+
should "add a serialized payload to the front of a list using `prepend`" do
|
114
|
+
subject.prepend(@queue_redis_key, @serialized_payload)
|
115
|
+
|
116
|
+
call = @connection_spy.redis_calls.last
|
117
|
+
assert_equal :rpush, call.command
|
118
|
+
assert_equal @queue_redis_key, call.args.first
|
119
|
+
assert_equal @serialized_payload, call.args.last
|
120
|
+
end
|
121
|
+
|
122
|
+
should "del a list using `clear`" do
|
123
|
+
subject.clear(@queue_redis_key)
|
124
|
+
|
125
|
+
call = @connection_spy.redis_calls.last
|
126
|
+
assert_equal :del, call.command
|
127
|
+
assert_equal [@queue_redis_key], call.args
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
class QsClientTests < UnitTests
|
133
|
+
desc "QsClient"
|
134
|
+
setup do
|
135
|
+
@client_class = Qs::QsClient
|
136
|
+
end
|
137
|
+
subject{ @client_class }
|
138
|
+
|
139
|
+
should "be a qs client" do
|
140
|
+
assert_includes Qs::Client, subject
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
class QsClientInitTests < QsClientTests
|
146
|
+
desc "when init"
|
147
|
+
setup do
|
148
|
+
@connection_spy = nil
|
149
|
+
Assert.stub(HellaRedis::Connection, :new) do |*args|
|
150
|
+
@connection_spy = HellaRedis::ConnectionSpy.new(*args)
|
151
|
+
end
|
152
|
+
|
153
|
+
@client = @client_class.new(@redis_config)
|
154
|
+
end
|
155
|
+
subject{ @client }
|
156
|
+
|
157
|
+
should "build a redis connection" do
|
158
|
+
assert_not_nil @connection_spy
|
159
|
+
assert_equal @connection_spy.config, subject.redis_config
|
160
|
+
assert_equal @connection_spy, subject.redis
|
161
|
+
end
|
162
|
+
|
163
|
+
should "add jobs to the queue's redis list using `enqueue`" do
|
164
|
+
subject.enqueue(@queue, @job_name, @job_params)
|
165
|
+
|
166
|
+
call = @connection_spy.redis_calls.last
|
167
|
+
assert_equal :lpush, call.command
|
168
|
+
assert_equal @queue.redis_key, call.args.first
|
169
|
+
payload = Qs.deserialize(call.args.last)
|
170
|
+
assert_equal @job_name, payload['name']
|
171
|
+
assert_equal @job_params, payload['params']
|
172
|
+
end
|
173
|
+
|
174
|
+
should "add payloads to the queue's redis list using `push`" do
|
175
|
+
job = Qs::Job.new(@job_name, @job_params)
|
176
|
+
subject.push(@queue.name, job.to_payload)
|
177
|
+
|
178
|
+
call = @connection_spy.redis_calls.last
|
179
|
+
assert_equal :lpush, call.command
|
180
|
+
assert_equal @queue.redis_key, call.args.first
|
181
|
+
assert_equal Qs.serialize(job.to_payload), call.args.last
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
class TestClientTests < UnitTests
|
187
|
+
desc "TestClient"
|
188
|
+
setup do
|
189
|
+
@client_class = Qs::TestClient
|
190
|
+
end
|
191
|
+
subject{ @client_class }
|
192
|
+
|
193
|
+
should "be a qs client" do
|
194
|
+
assert_includes Qs::Client, subject
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
198
|
+
|
199
|
+
class TestClientInitTests < TestClientTests
|
200
|
+
desc "when init"
|
201
|
+
setup do
|
202
|
+
@serialized_payload = nil
|
203
|
+
Assert.stub(Qs, :serialize){ |payload| @serialized_payload = payload }
|
204
|
+
|
205
|
+
@job = Qs::Job.new(@job_name, @job_params)
|
206
|
+
|
207
|
+
@client = @client_class.new(@redis_config)
|
208
|
+
end
|
209
|
+
subject{ @client }
|
210
|
+
|
211
|
+
should have_readers :pushed_items
|
212
|
+
should have_imeths :reset!
|
213
|
+
|
214
|
+
should "build a redis connection spy" do
|
215
|
+
assert_instance_of HellaRedis::ConnectionSpy, subject.redis
|
216
|
+
assert_equal @redis_config, subject.redis.config
|
217
|
+
end
|
218
|
+
|
219
|
+
should "default its pushed items" do
|
220
|
+
assert_equal [], subject.pushed_items
|
221
|
+
end
|
222
|
+
|
223
|
+
should "track all the jobs it enqueues on the queue" do
|
224
|
+
assert_empty @queue.enqueued_jobs
|
225
|
+
result = subject.enqueue(@queue, @job_name, @job_params)
|
226
|
+
|
227
|
+
job = @queue.enqueued_jobs.last
|
228
|
+
assert_equal @job_name, job.name
|
229
|
+
assert_equal @job_params, job.params
|
230
|
+
assert_equal job, result
|
231
|
+
end
|
232
|
+
|
233
|
+
should "serialize the jobs when enqueueing" do
|
234
|
+
subject.enqueue(@queue, @job_name, @job_params)
|
235
|
+
|
236
|
+
job = @queue.enqueued_jobs.last
|
237
|
+
assert_equal job.to_payload, @serialized_payload
|
238
|
+
end
|
239
|
+
|
240
|
+
should "track all the payloads pushed onto a queue" do
|
241
|
+
subject.push(@queue.name, @job.to_payload)
|
242
|
+
|
243
|
+
pushed_item = subject.pushed_items.last
|
244
|
+
assert_instance_of Qs::TestClient::PushedItem, pushed_item
|
245
|
+
assert_equal @queue.name, pushed_item.queue_name
|
246
|
+
assert_equal @job.to_payload, pushed_item.payload
|
247
|
+
end
|
248
|
+
|
249
|
+
should "clear its pushed items when reset" do
|
250
|
+
subject.push(@queue.name, @job.to_payload)
|
251
|
+
assert_not_empty subject.pushed_items
|
252
|
+
subject.reset!
|
253
|
+
assert_empty subject.pushed_items
|
254
|
+
end
|
255
|
+
|
256
|
+
end
|
257
|
+
|
258
|
+
class FakeClient
|
259
|
+
include Qs::Client
|
260
|
+
|
261
|
+
attr_reader :enqueued_jobs
|
262
|
+
|
263
|
+
def enqueue!(queue, job)
|
264
|
+
@enqueued_jobs ||= []
|
265
|
+
@enqueued_jobs << job
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'assert'
|
2
|
+
require 'qs/config_file'
|
3
|
+
|
4
|
+
class Qs::ConfigFile
|
5
|
+
|
6
|
+
class UnitTests < Assert::Context
|
7
|
+
desc "Qs::ConfigFile"
|
8
|
+
setup do
|
9
|
+
@file_path = ROOT_PATH.join('test/support/config.qs')
|
10
|
+
@config_file = Qs::ConfigFile.new(@file_path)
|
11
|
+
end
|
12
|
+
subject{ @config_file }
|
13
|
+
|
14
|
+
should have_readers :daemon
|
15
|
+
should have_imeths :run
|
16
|
+
|
17
|
+
should "know its daemon" do
|
18
|
+
assert_instance_of AppDaemon, subject.daemon
|
19
|
+
end
|
20
|
+
|
21
|
+
should "define constants in the file at the top-level binding" do
|
22
|
+
assert_not_nil defined?(::TestConstant)
|
23
|
+
end
|
24
|
+
|
25
|
+
should "set its daemon using run" do
|
26
|
+
fake_daemon = Factory.string
|
27
|
+
subject.run fake_daemon
|
28
|
+
assert_equal fake_daemon, subject.daemon
|
29
|
+
end
|
30
|
+
|
31
|
+
should "allow passing a path without the extension" do
|
32
|
+
file_path = ROOT_PATH.join('test/support/config')
|
33
|
+
config_file = nil
|
34
|
+
|
35
|
+
assert_nothing_raised do
|
36
|
+
config_file = Qs::ConfigFile.new(file_path)
|
37
|
+
end
|
38
|
+
assert_instance_of AppDaemon, config_file.daemon
|
39
|
+
end
|
40
|
+
|
41
|
+
should "raise no config file error when the file doesn't exist" do
|
42
|
+
assert_raises(NoConfigFileError) do
|
43
|
+
Qs::ConfigFile.new(Factory.file_path)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
should "raise a no daemon error when the file doesn't call run" do
|
48
|
+
file_path = ROOT_PATH.join('test/support/config_no_run.qs')
|
49
|
+
assert_raises(NoDaemonError){ Qs::ConfigFile.new(file_path) }
|
50
|
+
end
|
51
|
+
|
52
|
+
should "raise a no daemon error when the file provides an invalid daemon" do
|
53
|
+
file_path = ROOT_PATH.join('test/support/config_invalid_run.qs')
|
54
|
+
assert_raises(NoDaemonError){ Qs::ConfigFile.new(file_path) }
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'assert'
|
2
|
+
require 'qs/daemon_data'
|
3
|
+
|
4
|
+
require 'qs/queue'
|
5
|
+
require 'qs/route'
|
6
|
+
|
7
|
+
class Qs::DaemonData
|
8
|
+
|
9
|
+
class UnitTests < Assert::Context
|
10
|
+
desc "Qs::DaemonData"
|
11
|
+
setup do
|
12
|
+
@name = Factory.string
|
13
|
+
@pid_file = Factory.file_path
|
14
|
+
@min_workers = Factory.integer
|
15
|
+
@max_workers = Factory.integer
|
16
|
+
@logger = Factory.string
|
17
|
+
@verbose_logging = Factory.boolean
|
18
|
+
@shutdown_timeout = Factory.integer
|
19
|
+
@error_procs = [ proc{ Factory.string } ]
|
20
|
+
@queue_redis_keys = (0..Factory.integer(3)).map{ Factory.string }
|
21
|
+
@routes = (0..Factory.integer(3)).map do
|
22
|
+
Qs::Route.new(Factory.string, TestHandler.to_s).tap(&:validate!)
|
23
|
+
end
|
24
|
+
|
25
|
+
@daemon_data = Qs::DaemonData.new({
|
26
|
+
:name => @name,
|
27
|
+
:pid_file => @pid_file,
|
28
|
+
:min_workers => @min_workers,
|
29
|
+
:max_workers => @max_workers,
|
30
|
+
:logger => @logger,
|
31
|
+
:verbose_logging => @verbose_logging,
|
32
|
+
:shutdown_timeout => @shutdown_timeout,
|
33
|
+
:error_procs => @error_procs,
|
34
|
+
:queue_redis_keys => @queue_redis_keys,
|
35
|
+
:routes => @routes
|
36
|
+
})
|
37
|
+
end
|
38
|
+
subject{ @daemon_data }
|
39
|
+
|
40
|
+
should have_readers :name
|
41
|
+
should have_readers :pid_file
|
42
|
+
should have_readers :min_workers, :max_workers
|
43
|
+
should have_readers :logger, :verbose_logging
|
44
|
+
should have_readers :shutdown_timeout
|
45
|
+
should have_readers :error_procs
|
46
|
+
should have_readers :queue_redis_keys, :routes
|
47
|
+
should have_imeths :route_for
|
48
|
+
|
49
|
+
should "know its attributes" do
|
50
|
+
assert_equal @name, subject.name
|
51
|
+
assert_equal @pid_file, subject.pid_file
|
52
|
+
assert_equal @min_workers, subject.min_workers
|
53
|
+
assert_equal @max_workers, subject.max_workers
|
54
|
+
assert_equal @logger, subject.logger
|
55
|
+
assert_equal @verbose_logging, subject.verbose_logging
|
56
|
+
assert_equal @shutdown_timeout, subject.shutdown_timeout
|
57
|
+
assert_equal @error_procs, subject.error_procs
|
58
|
+
assert_equal @queue_redis_keys, subject.queue_redis_keys
|
59
|
+
end
|
60
|
+
|
61
|
+
should "build a routes lookup hash" do
|
62
|
+
expected = @routes.inject({}){ |h, r| h.merge(r.name => r) }
|
63
|
+
assert_equal expected, subject.routes
|
64
|
+
end
|
65
|
+
|
66
|
+
should "allow looking up a route using `route_for`" do
|
67
|
+
expected = @routes.choice
|
68
|
+
route = subject.route_for(expected.name)
|
69
|
+
assert_equal expected, route
|
70
|
+
end
|
71
|
+
|
72
|
+
should "raise a not found error using `route_for` with an invalid name" do
|
73
|
+
assert_raises(Qs::NotFoundError) do
|
74
|
+
subject.route_for(Factory.string)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
should "default its attributes when they aren't provided" do
|
79
|
+
daemon_data = Qs::DaemonData.new
|
80
|
+
assert_nil daemon_data.name
|
81
|
+
assert_nil daemon_data.pid_file
|
82
|
+
assert_nil daemon_data.min_workers
|
83
|
+
assert_nil daemon_data.max_workers
|
84
|
+
assert_nil daemon_data.logger
|
85
|
+
assert_false daemon_data.verbose_logging
|
86
|
+
assert_nil daemon_data.shutdown_timeout
|
87
|
+
assert_equal [], daemon_data.error_procs
|
88
|
+
assert_equal [], daemon_data.queue_redis_keys
|
89
|
+
assert_equal({}, daemon_data.routes)
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
TestHandler = Class.new
|
95
|
+
|
96
|
+
end
|