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,371 @@
1
+ require 'assert'
2
+ require 'qs/process'
3
+
4
+ require 'qs/daemon'
5
+ require 'test/support/pid_file_spy'
6
+
7
+ class Qs::Process
8
+
9
+ class UnitTests < Assert::Context
10
+ desc "Qs::Process"
11
+ setup do
12
+ @process_class = Qs::Process
13
+ end
14
+ subject{ @process_class }
15
+
16
+ end
17
+
18
+ class InitTests < UnitTests
19
+ desc "when init"
20
+ setup do
21
+ @current_env_process_name = ENV['QS_PROCESS_NAME']
22
+ @current_env_skip_daemonize = ENV['QS_SKIP_DAEMONIZE']
23
+ ENV.delete('QS_PROCESS_NAME')
24
+ ENV.delete('QS_SKIP_DAEMONIZE')
25
+
26
+ @daemon_spy = DaemonSpy.new
27
+
28
+ @pid_file_spy = PIDFileSpy.new(Factory.integer)
29
+ Assert.stub(Qs::PIDFile, :new).with(@daemon_spy.pid_file) do
30
+ @pid_file_spy
31
+ end
32
+
33
+ @restart_cmd_spy = RestartCmdSpy.new
34
+ Assert.stub(Qs::RestartCmd, :new){ @restart_cmd_spy }
35
+
36
+ @process = @process_class.new(@daemon_spy)
37
+ end
38
+ teardown do
39
+ ENV['QS_SKIP_DAEMONIZE'] = @current_env_skip_daemonize
40
+ ENV['QS_PROCESS_NAME'] = @current_env_process_name
41
+ end
42
+ subject{ @process }
43
+
44
+ should have_readers :daemon, :name, :pid_file, :restart_cmd
45
+ should have_imeths :run, :daemonize?, :restart?
46
+
47
+ should "know its daemon" do
48
+ assert_equal @daemon_spy, subject.daemon
49
+ end
50
+
51
+ should "know its name, pid file and restart cmd" do
52
+ assert_equal "qs-#{@daemon_spy.name}", subject.name
53
+ assert_equal @pid_file_spy, subject.pid_file
54
+ assert_equal @restart_cmd_spy, subject.restart_cmd
55
+ end
56
+
57
+ should "set its name using env vars" do
58
+ ENV['QS_PROCESS_NAME'] = Factory.string
59
+ process = @process_class.new(@daemon_spy)
60
+ assert_equal ENV['QS_PROCESS_NAME'], process.name
61
+ end
62
+
63
+ should "ignore blank env values for its name" do
64
+ ENV['QS_PROCESS_NAME'] = ''
65
+ process = @process_class.new(@daemon_spy)
66
+ assert_equal "qs-#{@daemon_spy.name}", process.name
67
+ end
68
+
69
+ should "not daemonize by default" do
70
+ process = @process_class.new(@daemon_spy)
71
+ assert_false process.daemonize?
72
+ end
73
+
74
+ should "daemonize if turned on" do
75
+ process = @process_class.new(@daemon_spy, :daemonize => true)
76
+ assert_true process.daemonize?
77
+ end
78
+
79
+ should "not daemonize if skipped via the env var" do
80
+ ENV['QS_SKIP_DAEMONIZE'] = 'yes'
81
+ process = @process_class.new(@daemon_spy)
82
+ assert_false process.daemonize?
83
+ process = @process_class.new(@daemon_spy, :daemonize => true)
84
+ assert_false process.daemonize?
85
+ end
86
+
87
+ should "ignore blank env values for skip daemonize" do
88
+ ENV['QS_SKIP_DAEMONIZE'] = ''
89
+ process = @process_class.new(@daemon_spy, :daemonize => true)
90
+ assert_true process.daemonize?
91
+ end
92
+
93
+ should "not restart by default" do
94
+ assert_false subject.restart?
95
+ end
96
+
97
+ end
98
+
99
+ class RunSetupTests < InitTests
100
+ setup do
101
+ @daemonize_called = false
102
+ Assert.stub(::Process, :daemon).with(true){ @daemonize_called = true }
103
+
104
+ @current_process_name = $0
105
+
106
+ @term_signal_trap_block = nil
107
+ @term_signal_trap_called = false
108
+ Assert.stub(::Signal, :trap).with("TERM") do |&block|
109
+ @term_signal_trap_block = block
110
+ @term_signal_trap_called = true
111
+ end
112
+
113
+ @int_signal_trap_block = nil
114
+ @int_signal_trap_called = false
115
+ Assert.stub(::Signal, :trap).with("INT") do |&block|
116
+ @int_signal_trap_block = block
117
+ @int_signal_trap_called = true
118
+ end
119
+
120
+ @usr2_signal_trap_block = nil
121
+ @usr2_signal_trap_called = false
122
+ Assert.stub(::Signal, :trap).with("USR2") do |&block|
123
+ @usr2_signal_trap_block = block
124
+ @usr2_signal_trap_called = true
125
+ end
126
+ end
127
+ teardown do
128
+ $0 = @current_process_name
129
+ end
130
+
131
+ end
132
+
133
+ class RunTests < RunSetupTests
134
+ desc "and run"
135
+ setup do
136
+ @process.run
137
+ end
138
+
139
+ should "not have daemonized the process" do
140
+ assert_false @daemonize_called
141
+ end
142
+
143
+ should "have set the process name" do
144
+ assert_equal $0, subject.name
145
+ end
146
+
147
+ should "have written the PID file" do
148
+ assert_true @pid_file_spy.write_called
149
+ end
150
+
151
+ should "have trapped signals" do
152
+ assert_true @term_signal_trap_called
153
+ assert_false @daemon_spy.stop_called
154
+ @term_signal_trap_block.call
155
+ assert_true @daemon_spy.stop_called
156
+
157
+ assert_true @int_signal_trap_called
158
+ assert_false @daemon_spy.halt_called
159
+ @int_signal_trap_block.call
160
+ assert_true @daemon_spy.halt_called
161
+
162
+ @daemon_spy.stop_called = false
163
+
164
+ assert_true @usr2_signal_trap_called
165
+ assert_false subject.restart?
166
+ @usr2_signal_trap_block.call
167
+ assert_true @daemon_spy.stop_called
168
+ assert_true subject.restart?
169
+ end
170
+
171
+ should "have started the daemon" do
172
+ assert_true @daemon_spy.start_called
173
+ end
174
+
175
+ should "have joined the daemon thread" do
176
+ assert_true @daemon_spy.thread.join_called
177
+ end
178
+
179
+ should "not run the restart cmd" do
180
+ assert_false @restart_cmd_spy.run_called
181
+ end
182
+
183
+ should "have removed the PID file" do
184
+ assert_true @pid_file_spy.remove_called
185
+ end
186
+
187
+ end
188
+
189
+ class RunWithDaemonizeTests < RunSetupTests
190
+ desc "that should daemonize is run"
191
+ setup do
192
+ Assert.stub(@process, :daemonize?){ true }
193
+ @process.run
194
+ end
195
+
196
+ should "have daemonized the process" do
197
+ assert_true @daemonize_called
198
+ end
199
+
200
+ end
201
+
202
+ class RunAndDaemonPausedTests < RunSetupTests
203
+ desc "then run and sent a restart signal"
204
+ setup do
205
+ # mimicing pause being called by a signal, after the thread is joined
206
+ @daemon_spy.thread.on_join{ @usr2_signal_trap_block.call }
207
+ @process.run
208
+ end
209
+
210
+ should "set env vars for restarting and run the restart cmd" do
211
+ assert_equal 'yes', ENV['QS_SKIP_DAEMONIZE']
212
+ assert_true @restart_cmd_spy.run_called
213
+ end
214
+
215
+ end
216
+
217
+ class RestartCmdTests < UnitTests
218
+ desc "RestartCmd"
219
+ setup do
220
+ @current_pwd = ENV['PWD']
221
+ ENV['PWD'] = Factory.path
222
+
223
+ @ruby_pwd_stat = File.stat(Dir.pwd)
224
+ env_pwd_stat = File.stat('/dev/null')
225
+ Assert.stub(File, :stat).with(Dir.pwd){ @ruby_pwd_stat }
226
+ Assert.stub(File, :stat).with(ENV['PWD']){ env_pwd_stat }
227
+
228
+ @chdir_called_with = nil
229
+ Assert.stub(Dir, :chdir){ |*args| @chdir_called_with = args }
230
+
231
+ @exec_called_with = false
232
+ Assert.stub(Kernel, :exec){ |*args| @exec_called_with = args }
233
+
234
+ @cmd_class = Qs::RestartCmd
235
+ end
236
+ teardown do
237
+ ENV['PWD'] = @current_pwd
238
+ end
239
+ subject{ @restart_cmd }
240
+
241
+ end
242
+
243
+ class RestartCmdInitTests < RestartCmdTests
244
+ desc "when init"
245
+ setup do
246
+ @restart_cmd = @cmd_class.new
247
+ end
248
+
249
+ should have_readers :argv, :dir
250
+ should have_imeths :run
251
+
252
+ should "know its argv" do
253
+ assert_equal [Gem.ruby, $0, ARGV].flatten, subject.argv
254
+ end
255
+
256
+ should "change the dir and run a kernel exec when run" do
257
+ subject.run
258
+ assert_equal [subject.dir], @chdir_called_with
259
+ assert_equal subject.argv, @exec_called_with
260
+ end
261
+
262
+ end
263
+
264
+ class RestartCmdWithPWDEnvNoMatchTests < RestartCmdTests
265
+ desc "when init with a PWD env variable that doesn't point to ruby working dir"
266
+ setup do
267
+ @restart_cmd = @cmd_class.new
268
+ end
269
+
270
+ should "know its dir" do
271
+ assert_equal Dir.pwd, subject.dir
272
+ end
273
+
274
+ end
275
+
276
+ class RestartCmdWithPWDEnvInitTests < RestartCmdTests
277
+ desc "when init with a PWD env variable that points to the ruby working dir"
278
+ setup do
279
+ # make ENV['PWD'] point to the same file as Dir.pwd
280
+ Assert.stub(File, :stat).with(ENV['PWD']){ @ruby_pwd_stat }
281
+ @restart_cmd = @cmd_class.new
282
+ end
283
+
284
+ should "know its dir" do
285
+ assert_equal ENV['PWD'], subject.dir
286
+ end
287
+
288
+ end
289
+
290
+ class RestartCmdWithNoPWDEnvInitTests < RestartCmdTests
291
+ desc "when init with a PWD env variable set"
292
+ setup do
293
+ ENV.delete('PWD')
294
+ @restart_cmd = @cmd_class.new
295
+ end
296
+
297
+ should "know its dir" do
298
+ assert_equal Dir.pwd, subject.dir
299
+ end
300
+
301
+ end
302
+
303
+ class DaemonSpy
304
+ include Qs::Daemon
305
+
306
+ name Factory.string
307
+ pid_file Factory.file_path
308
+
309
+ queue Qs::Queue.new{ name Factory.string }
310
+
311
+ attr_accessor :start_called, :stop_called, :halt_called
312
+ attr_reader :start_args
313
+ attr_reader :thread
314
+
315
+ def initialize(*args)
316
+ super
317
+ @start_called = false
318
+ @stop_called = false
319
+ @halt_called = false
320
+
321
+ @start_args = nil
322
+
323
+ @thread = ThreadSpy.new
324
+ end
325
+
326
+ def start(*args)
327
+ @start_args = args
328
+ @start_called = true
329
+ @thread
330
+ end
331
+
332
+ def stop(*args)
333
+ @stop_called = true
334
+ end
335
+
336
+ def halt(*args)
337
+ @halt_called = true
338
+ end
339
+ end
340
+
341
+ class ThreadSpy
342
+ attr_reader :join_called, :on_join_proc
343
+
344
+ def initialize
345
+ @join_called = false
346
+ @on_join_proc = proc{ }
347
+ end
348
+
349
+ def on_join(&block)
350
+ @on_join_proc = block
351
+ end
352
+
353
+ def join
354
+ @join_called = true
355
+ @on_join_proc.call
356
+ end
357
+ end
358
+
359
+ class RestartCmdSpy
360
+ attr_reader :run_called
361
+
362
+ def initialize
363
+ @run_called = false
364
+ end
365
+
366
+ def run
367
+ @run_called = true
368
+ end
369
+ end
370
+
371
+ end
@@ -0,0 +1,166 @@
1
+ require 'assert'
2
+ require 'qs/qs_runner'
3
+
4
+ require 'qs'
5
+ require 'qs/job_handler'
6
+
7
+ class Qs::QsRunner
8
+
9
+ class UnitTests < Assert::Context
10
+ desc "Qs::QsRunner"
11
+ setup do
12
+ Qs.config.timeout = Factory.integer
13
+ @runner_class = Qs::QsRunner
14
+ end
15
+ teardown do
16
+ Qs.reset!
17
+ Qs.init
18
+ end
19
+ subject{ @runner_class }
20
+
21
+ should "be a runner" do
22
+ assert_true subject < Qs::Runner
23
+ end
24
+
25
+ end
26
+
27
+ class InitTests < UnitTests
28
+ desc "when init"
29
+ setup do
30
+ @handler_class = TestJobHandler
31
+ @runner = @runner_class.new(@handler_class)
32
+ end
33
+ subject{ @runner }
34
+
35
+ should have_readers :timeout
36
+ should have_imeths :run
37
+
38
+ should "know its timeout" do
39
+ assert_equal TestJobHandler.timeout, subject.timeout
40
+ handler_class = Class.new{ include Qs::JobHandler }
41
+ runner = @runner_class.new(handler_class)
42
+ assert_equal Qs.config.timeout, runner.timeout
43
+ end
44
+
45
+ end
46
+
47
+ class RunSetupTests < InitTests
48
+ desc "and run"
49
+ setup do
50
+ @timeout_called_with = nil
51
+ Assert.stub(OptionalTimeout, :new) do |*args, &block|
52
+ @timeout_called_with = args
53
+ block.call
54
+ end
55
+ end
56
+
57
+ end
58
+
59
+ class RunTests < RunSetupTests
60
+ setup do
61
+ @handler = @runner.handler
62
+ @runner.run
63
+ end
64
+
65
+ should "run the handler in an optional timeout" do
66
+ assert_equal [@runner.timeout], @timeout_called_with
67
+ end
68
+
69
+ should "run the handlers before callbacks" do
70
+ assert_equal 1, @handler.first_before_call_order
71
+ assert_equal 2, @handler.second_before_call_order
72
+ end
73
+
74
+ should "call the handlers init and run methods" do
75
+ assert_equal 3, @handler.init_call_order
76
+ assert_equal 4, @handler.run_call_order
77
+ end
78
+
79
+ should "run the handlers after callbacks" do
80
+ assert_equal 5, @handler.first_after_call_order
81
+ assert_equal 6, @handler.second_after_call_order
82
+ end
83
+
84
+ end
85
+
86
+ class RunWithTimeoutErrorTests < RunSetupTests
87
+ setup do
88
+ Assert.stub(OptionalTimeout, :new){ raise Qs::TimeoutError }
89
+ end
90
+
91
+ should "raise a timeout error with a good message" do
92
+ exception = nil
93
+ begin; @runner.run; rescue StandardError => exception; end
94
+
95
+ assert_kind_of Qs::TimeoutError, exception
96
+ exp = "#{@handler_class} timed out (#{@runner.timeout}s)"
97
+ assert_equal exp, exception.message
98
+ end
99
+
100
+ end
101
+
102
+ class OptionalTimeoutTests < UnitTests
103
+ desc "OptionalTimeout"
104
+ setup do
105
+ @timeout_called_with = nil
106
+ Assert.stub(SystemTimer, :timeout_after) do |*args, &block|
107
+ @timeout_called_with = args
108
+ block.call
109
+ end
110
+ end
111
+ subject{ OptionalTimeout }
112
+
113
+ should have_imeths :new
114
+
115
+ should "use a system timer timeout when provided a non-`nil` value" do
116
+ value = Factory.integer
117
+ block_run = false
118
+
119
+ subject.new(value){ block_run = true }
120
+ assert_equal [value, Qs::TimeoutError], @timeout_called_with
121
+ assert_true block_run
122
+ end
123
+
124
+ should "call the block when provided a `nil` value" do
125
+ block_run = false
126
+
127
+ subject.new(nil){ block_run = true }
128
+ assert_nil @timeout_called_with
129
+ assert_true block_run
130
+ end
131
+
132
+ end
133
+
134
+ class TestJobHandler
135
+ include Qs::JobHandler
136
+
137
+ attr_reader :first_before_call_order, :second_before_call_order
138
+ attr_reader :first_after_call_order, :second_after_call_order
139
+ attr_reader :init_call_order, :run_call_order
140
+ attr_reader :response_data
141
+
142
+ timeout Factory.integer
143
+
144
+ before{ @first_before_call_order = next_call_order }
145
+ before{ @second_before_call_order = next_call_order }
146
+
147
+ after{ @first_after_call_order = next_call_order }
148
+ after{ @second_after_call_order = next_call_order }
149
+
150
+ def init!
151
+ @init_call_order = next_call_order
152
+ end
153
+
154
+ def run!
155
+ @run_call_order = next_call_order
156
+ end
157
+
158
+ private
159
+
160
+ def next_call_order
161
+ @order ||= 0
162
+ @order += 1
163
+ end
164
+ end
165
+
166
+ end