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