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,276 @@
|
|
1
|
+
require 'assert'
|
2
|
+
require 'qs/payload_handler'
|
3
|
+
|
4
|
+
require 'qs/daemon_data'
|
5
|
+
require 'qs/job'
|
6
|
+
require 'qs/redis_item'
|
7
|
+
|
8
|
+
class Qs::PayloadHandler
|
9
|
+
|
10
|
+
class UnitTests < Assert::Context
|
11
|
+
desc "Qs::PayloadHandler"
|
12
|
+
setup do
|
13
|
+
Qs.init
|
14
|
+
@handler_class = Qs::PayloadHandler
|
15
|
+
end
|
16
|
+
subject{ @handler_class }
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
class InitTests < UnitTests
|
21
|
+
desc "when init"
|
22
|
+
setup do
|
23
|
+
@route_spy = RouteSpy.new
|
24
|
+
@daemon_data = Qs::DaemonData.new({
|
25
|
+
:logger => Qs::NullLogger.new,
|
26
|
+
:routes => [@route_spy]
|
27
|
+
})
|
28
|
+
@job = Qs::Job.new(@route_spy.name, Factory.string => Factory.string)
|
29
|
+
serialized_payload = Qs.serialize(@job.to_payload)
|
30
|
+
@redis_item = Qs::RedisItem.new(Factory.string, serialized_payload)
|
31
|
+
|
32
|
+
Assert.stub(Qs::Logger, :new){ |*args| QsLoggerSpy.new(*args) }
|
33
|
+
|
34
|
+
@payload_handler = @handler_class.new(@daemon_data, @redis_item)
|
35
|
+
end
|
36
|
+
subject{ @payload_handler }
|
37
|
+
|
38
|
+
should have_readers :daemon_data, :redis_item
|
39
|
+
should have_readers :logger
|
40
|
+
should have_imeths :run
|
41
|
+
|
42
|
+
should "know its daemon data, redis item and logger" do
|
43
|
+
assert_equal @daemon_data, subject.daemon_data
|
44
|
+
assert_equal @redis_item, subject.redis_item
|
45
|
+
assert_equal @daemon_data.logger, subject.logger.passed_logger
|
46
|
+
assert_equal @daemon_data.verbose_logging, subject.logger.verbose_logging
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
class RunTests < InitTests
|
52
|
+
desc "and run"
|
53
|
+
setup do
|
54
|
+
@payload_handler.run
|
55
|
+
end
|
56
|
+
|
57
|
+
should "run a route for the redis item" do
|
58
|
+
assert_true @route_spy.run_called
|
59
|
+
assert_equal @job, @route_spy.job_passed_to_run
|
60
|
+
assert_equal @daemon_data, @route_spy.daemon_data_passed_to_run
|
61
|
+
end
|
62
|
+
|
63
|
+
should "build up its redis item as it processes it" do
|
64
|
+
assert_equal @job, @redis_item.job
|
65
|
+
assert_equal @route_spy.handler_class, @redis_item.handler_class
|
66
|
+
assert_nil @redis_item.exception
|
67
|
+
assert_instance_of Float, @redis_item.time_taken
|
68
|
+
end
|
69
|
+
|
70
|
+
should "log its processing of the redis item" do
|
71
|
+
logger_spy = subject.logger
|
72
|
+
expected = "[Qs] ===== Running job ====="
|
73
|
+
assert_includes expected, logger_spy.verbose.info_logged
|
74
|
+
expected = "[Qs] Job: #{@redis_item.job.name.inspect}"
|
75
|
+
assert_includes expected, logger_spy.verbose.info_logged
|
76
|
+
expected = "[Qs] Params: #{@redis_item.job.params.inspect}"
|
77
|
+
assert_includes expected, logger_spy.verbose.info_logged
|
78
|
+
expected = "[Qs] Handler: #{@redis_item.handler_class}"
|
79
|
+
assert_includes expected, logger_spy.verbose.info_logged
|
80
|
+
expected = "[Qs] ===== Completed in #{@redis_item.time_taken}ms ====="
|
81
|
+
assert_includes expected, logger_spy.verbose.info_logged
|
82
|
+
assert_empty logger_spy.verbose.error_logged
|
83
|
+
|
84
|
+
expected = SummaryLine.new({
|
85
|
+
'time' => @redis_item.time_taken,
|
86
|
+
'handler' => @redis_item.handler_class,
|
87
|
+
'job' => @redis_item.job.name,
|
88
|
+
'params' => @redis_item.job.params
|
89
|
+
})
|
90
|
+
assert_equal 1, logger_spy.summary.info_logged.size
|
91
|
+
assert_equal "[Qs] #{expected}", logger_spy.summary.info_logged.first
|
92
|
+
assert_empty logger_spy.summary.error_logged
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
class RunWithExceptionSetupTests < InitTests
|
98
|
+
setup do
|
99
|
+
@route_exception = Factory.exception
|
100
|
+
Assert.stub(@route_spy, :run){ raise @route_exception }
|
101
|
+
Assert.stub(Qs::ErrorHandler, :new) do |*args|
|
102
|
+
@error_handler_spy = ErrorHandlerSpy.new(*args)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
class RunWithExceptionTests < RunWithExceptionSetupTests
|
109
|
+
desc "and run with an exception"
|
110
|
+
setup do
|
111
|
+
@payload_handler.run
|
112
|
+
end
|
113
|
+
|
114
|
+
should "run an error handler" do
|
115
|
+
assert_equal @route_exception, @error_handler_spy.passed_exception
|
116
|
+
exp = {
|
117
|
+
:daemon_data => @daemon_data,
|
118
|
+
:queue_redis_key => @redis_item.queue_redis_key,
|
119
|
+
:serialized_payload => @redis_item.serialized_payload,
|
120
|
+
:job => @redis_item.job,
|
121
|
+
:handler_class => @redis_item.handler_class
|
122
|
+
}
|
123
|
+
assert_equal exp, @error_handler_spy.context_hash
|
124
|
+
assert_true @error_handler_spy.run_called
|
125
|
+
end
|
126
|
+
|
127
|
+
should "store the exception on the redis item" do
|
128
|
+
assert_equal @error_handler_spy.exception, @redis_item.exception
|
129
|
+
end
|
130
|
+
|
131
|
+
should "log its processing of the redis item" do
|
132
|
+
logger_spy = subject.logger
|
133
|
+
exception = @redis_item.exception
|
134
|
+
backtrace = exception.backtrace.join("\n")
|
135
|
+
exp = "[Qs] #{exception.class}: #{exception.message}\n#{backtrace}"
|
136
|
+
assert_equal exp, logger_spy.verbose.error_logged.first
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
class RunWithShutdownErrorTests < RunWithExceptionSetupTests
|
142
|
+
desc "and run with a dat worker pool shutdown error"
|
143
|
+
setup do
|
144
|
+
@shutdown_error = DatWorkerPool::ShutdownError.new(Factory.text)
|
145
|
+
Assert.stub(@route_spy, :run){ raise @shutdown_error }
|
146
|
+
end
|
147
|
+
|
148
|
+
should "run an error handler if the redis item was started" do
|
149
|
+
Assert.stub(@redis_item, :started){ true }
|
150
|
+
assert_raises{ @payload_handler.run }
|
151
|
+
|
152
|
+
passed_exception = @error_handler_spy.passed_exception
|
153
|
+
assert_instance_of Qs::ShutdownError, passed_exception
|
154
|
+
assert_equal @shutdown_error.message, passed_exception.message
|
155
|
+
assert_equal @shutdown_error.backtrace, passed_exception.backtrace
|
156
|
+
assert_true @error_handler_spy.run_called
|
157
|
+
end
|
158
|
+
|
159
|
+
should "not run an error handler if the redis item was started" do
|
160
|
+
Assert.stub(@redis_item, :started){ false }
|
161
|
+
assert_raises{ @payload_handler.run }
|
162
|
+
|
163
|
+
assert_nil @error_handler_spy
|
164
|
+
end
|
165
|
+
|
166
|
+
should "raise the shutdown error" do
|
167
|
+
assert_raises(@shutdown_error.class){ @payload_handler.run }
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
|
172
|
+
class RunWithExceptionWhileDebuggingTests < RunWithExceptionSetupTests
|
173
|
+
desc "and run with an exception"
|
174
|
+
setup do
|
175
|
+
ENV['QS_DEBUG'] = '1'
|
176
|
+
end
|
177
|
+
teardown do
|
178
|
+
ENV.delete('QS_DEBUG')
|
179
|
+
end
|
180
|
+
|
181
|
+
should "raise the exception" do
|
182
|
+
assert_raises(@route_exception.class){ @payload_handler.run }
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
|
187
|
+
class SummaryLineTests < UnitTests
|
188
|
+
desc "SummaryLine"
|
189
|
+
setup do
|
190
|
+
@attrs = {
|
191
|
+
'time' => Factory.string,
|
192
|
+
'handler' => Factory.string,
|
193
|
+
'job' => Factory.string,
|
194
|
+
'params' => Factory.string,
|
195
|
+
'error' => Factory.string
|
196
|
+
}
|
197
|
+
@summary_line = SummaryLine.new(@attrs)
|
198
|
+
end
|
199
|
+
subject{ @summary_line }
|
200
|
+
|
201
|
+
should "build a string of all the attributes ordered with their values" do
|
202
|
+
expected = "time=#{@attrs['time'].inspect} " \
|
203
|
+
"handler=#{@attrs['handler'].inspect} " \
|
204
|
+
"job=#{@attrs['job'].inspect} " \
|
205
|
+
"params=#{@attrs['params'].inspect} " \
|
206
|
+
"error=#{@attrs['error'].inspect}"
|
207
|
+
assert_equal expected, subject
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|
211
|
+
|
212
|
+
class RouteSpy
|
213
|
+
attr_reader :name
|
214
|
+
attr_reader :job_passed_to_run, :daemon_data_passed_to_run
|
215
|
+
attr_reader :run_called
|
216
|
+
|
217
|
+
def initialize
|
218
|
+
@name = Factory.string
|
219
|
+
@job_passed_to_run = nil
|
220
|
+
@daemon_data_passed_to_run = nil
|
221
|
+
@run_called = false
|
222
|
+
end
|
223
|
+
|
224
|
+
def handler_class
|
225
|
+
TestHandler
|
226
|
+
end
|
227
|
+
|
228
|
+
def run(job, daemon_data)
|
229
|
+
@job_passed_to_run = job
|
230
|
+
@daemon_data_passed_to_run = daemon_data
|
231
|
+
@run_called = true
|
232
|
+
end
|
233
|
+
|
234
|
+
TestHandler = Class.new
|
235
|
+
end
|
236
|
+
|
237
|
+
class ErrorHandlerSpy
|
238
|
+
attr_reader :passed_exception, :context_hash, :exception, :run_called
|
239
|
+
|
240
|
+
def initialize(exception, context_hash)
|
241
|
+
@passed_exception = exception
|
242
|
+
@context_hash = context_hash
|
243
|
+
@exception = Factory.exception
|
244
|
+
@run_called = false
|
245
|
+
end
|
246
|
+
|
247
|
+
def run
|
248
|
+
@run_called = true
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
class QsLoggerSpy
|
253
|
+
attr_reader :passed_logger, :verbose_logging
|
254
|
+
attr_reader :summary, :verbose
|
255
|
+
|
256
|
+
def initialize(passed_logger, verbose_logging)
|
257
|
+
@passed_logger = passed_logger
|
258
|
+
@verbose_logging = verbose_logging
|
259
|
+
@summary = LoggerSpy.new
|
260
|
+
@verbose = LoggerSpy.new
|
261
|
+
end
|
262
|
+
|
263
|
+
class LoggerSpy
|
264
|
+
attr_reader :info_logged, :error_logged
|
265
|
+
|
266
|
+
def initialize
|
267
|
+
@info_logged = []
|
268
|
+
@error_logged = []
|
269
|
+
end
|
270
|
+
|
271
|
+
def info(message); @info_logged << message; end
|
272
|
+
def error(message); @error_logged << message; end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'assert'
|
2
|
+
require 'qs/pid_file'
|
3
|
+
|
4
|
+
class Qs::PIDFile
|
5
|
+
|
6
|
+
class UnitTests < Assert::Context
|
7
|
+
desc "Qs::PIDFile"
|
8
|
+
setup do
|
9
|
+
@path = ROOT_PATH.join('tmp/pid_file_tests.pid').to_s
|
10
|
+
@pid_file = Qs::PIDFile.new(@path)
|
11
|
+
end
|
12
|
+
teardown do
|
13
|
+
FileUtils.rm_rf(@path)
|
14
|
+
end
|
15
|
+
subject{ @pid_file }
|
16
|
+
|
17
|
+
should have_readers :path
|
18
|
+
should have_imeths :pid, :write, :remove, :to_s
|
19
|
+
|
20
|
+
should "know its path" do
|
21
|
+
assert_equal @path, subject.path
|
22
|
+
end
|
23
|
+
|
24
|
+
should "default its path" do
|
25
|
+
pid_file = Qs::PIDFile.new(nil)
|
26
|
+
assert_equal '/dev/null', pid_file.path
|
27
|
+
end
|
28
|
+
|
29
|
+
should "know its string format" do
|
30
|
+
assert_equal @path, subject.to_s
|
31
|
+
end
|
32
|
+
|
33
|
+
should "read a PID from its file" do
|
34
|
+
pid = Factory.integer
|
35
|
+
File.open(@path, 'w'){ |f| f.puts pid }
|
36
|
+
assert_equal pid, subject.pid
|
37
|
+
end
|
38
|
+
|
39
|
+
should "raise an invalid error when it can't read from its file" do
|
40
|
+
FileUtils.rm_rf(@path)
|
41
|
+
assert_raises(InvalidError){ subject.pid }
|
42
|
+
end
|
43
|
+
|
44
|
+
should "raise an invalid error when the file doesn't have a PID in it" do
|
45
|
+
File.open(@path, 'w'){ |f| f.puts '' }
|
46
|
+
assert_raises(InvalidError){ subject.pid }
|
47
|
+
end
|
48
|
+
|
49
|
+
should "write the process PID to its file" do
|
50
|
+
assert_false File.exists?(@path)
|
51
|
+
subject.write
|
52
|
+
assert_true File.exists?(@path)
|
53
|
+
assert_equal "#{::Process.pid}\n", File.read(@path)
|
54
|
+
end
|
55
|
+
|
56
|
+
should "raise an invalid error when it can't write its file" do
|
57
|
+
Assert.stub(File, :open){ raise "can't open file" }
|
58
|
+
assert_raises(InvalidError){ subject.write }
|
59
|
+
end
|
60
|
+
|
61
|
+
should "remove its file" do
|
62
|
+
FileUtils.touch(@path)
|
63
|
+
assert_true File.exists?(@path)
|
64
|
+
subject.remove
|
65
|
+
assert_false File.exists?(@path)
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'assert'
|
2
|
+
require 'qs/process_signal'
|
3
|
+
|
4
|
+
require 'qs/daemon'
|
5
|
+
require 'test/support/pid_file_spy'
|
6
|
+
|
7
|
+
class Qs::ProcessSignal
|
8
|
+
|
9
|
+
class UnitTests < Assert::Context
|
10
|
+
desc "Qs::ProcessSignal"
|
11
|
+
setup do
|
12
|
+
@daemon = TestDaemon.new
|
13
|
+
@signal = Factory.string
|
14
|
+
|
15
|
+
@pid_file_spy = PIDFileSpy.new(Factory.integer)
|
16
|
+
Assert.stub(Qs::PIDFile, :new).with(@daemon.pid_file) do
|
17
|
+
@pid_file_spy
|
18
|
+
end
|
19
|
+
|
20
|
+
@process_signal = Qs::ProcessSignal.new(@daemon, @signal)
|
21
|
+
end
|
22
|
+
subject{ @process_signal }
|
23
|
+
|
24
|
+
should have_readers :signal, :pid
|
25
|
+
should have_imeths :send
|
26
|
+
|
27
|
+
should "know its signal and pid" do
|
28
|
+
assert_equal @signal, subject.signal
|
29
|
+
assert_equal @pid_file_spy.pid, subject.pid
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
class SendTests < UnitTests
|
35
|
+
desc "when sent"
|
36
|
+
setup do
|
37
|
+
@kill_called = false
|
38
|
+
Assert.stub(::Process, :kill).with(@signal, @pid_file_spy.pid) do
|
39
|
+
@kill_called = true
|
40
|
+
end
|
41
|
+
|
42
|
+
@process_signal.send
|
43
|
+
end
|
44
|
+
|
45
|
+
should "have used process kill to send the signal to the PID" do
|
46
|
+
assert_true @kill_called
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
class TestDaemon
|
52
|
+
include Qs::Daemon
|
53
|
+
|
54
|
+
name Factory.string
|
55
|
+
pid_file Factory.file_path
|
56
|
+
|
57
|
+
queue Qs::Queue.new{ name Factory.string }
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|