qs 0.3.0 → 0.4.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 (63) hide show
  1. data/bench/config.qs +4 -27
  2. data/bench/dispatcher.qs +24 -0
  3. data/bench/report.rb +80 -10
  4. data/bench/report.txt +10 -3
  5. data/bench/setup.rb +55 -0
  6. data/lib/qs.rb +75 -15
  7. data/lib/qs/client.rb +73 -22
  8. data/lib/qs/daemon.rb +21 -21
  9. data/lib/qs/daemon_data.rb +4 -4
  10. data/lib/qs/dispatch_job.rb +36 -0
  11. data/lib/qs/dispatch_job_handler.rb +79 -0
  12. data/lib/qs/dispatcher_queue.rb +19 -0
  13. data/lib/qs/error_handler.rb +12 -12
  14. data/lib/qs/event.rb +82 -0
  15. data/lib/qs/event_handler.rb +34 -0
  16. data/lib/qs/event_handler_test_helpers.rb +17 -0
  17. data/lib/qs/job.rb +19 -31
  18. data/lib/qs/job_handler.rb +6 -63
  19. data/lib/qs/{test_helpers.rb → job_handler_test_helpers.rb} +2 -2
  20. data/lib/qs/message.rb +29 -0
  21. data/lib/qs/message_handler.rb +84 -0
  22. data/lib/qs/payload.rb +98 -0
  23. data/lib/qs/payload_handler.rb +106 -54
  24. data/lib/qs/queue.rb +39 -6
  25. data/lib/qs/queue_item.rb +33 -0
  26. data/lib/qs/route.rb +7 -7
  27. data/lib/qs/runner.rb +6 -5
  28. data/lib/qs/test_runner.rb +41 -13
  29. data/lib/qs/version.rb +1 -1
  30. data/qs.gemspec +1 -1
  31. data/test/helper.rb +1 -1
  32. data/test/support/app_daemon.rb +77 -11
  33. data/test/support/factory.rb +34 -0
  34. data/test/system/daemon_tests.rb +146 -77
  35. data/test/system/queue_tests.rb +87 -0
  36. data/test/unit/client_tests.rb +184 -45
  37. data/test/unit/daemon_data_tests.rb +4 -4
  38. data/test/unit/daemon_tests.rb +32 -32
  39. data/test/unit/dispatch_job_handler_tests.rb +163 -0
  40. data/test/unit/dispatch_job_tests.rb +75 -0
  41. data/test/unit/dispatcher_queue_tests.rb +42 -0
  42. data/test/unit/error_handler_tests.rb +9 -9
  43. data/test/unit/event_handler_test_helpers_tests.rb +55 -0
  44. data/test/unit/event_handler_tests.rb +63 -0
  45. data/test/unit/event_tests.rb +162 -0
  46. data/test/unit/{test_helper_tests.rb → job_handler_test_helper_tests.rb} +13 -19
  47. data/test/unit/job_handler_tests.rb +17 -210
  48. data/test/unit/job_tests.rb +49 -79
  49. data/test/unit/message_handler_tests.rb +235 -0
  50. data/test/unit/message_tests.rb +64 -0
  51. data/test/unit/payload_handler_tests.rb +285 -86
  52. data/test/unit/payload_tests.rb +139 -0
  53. data/test/unit/qs_runner_tests.rb +6 -6
  54. data/test/unit/qs_tests.rb +167 -28
  55. data/test/unit/queue_item_tests.rb +51 -0
  56. data/test/unit/queue_tests.rb +126 -18
  57. data/test/unit/route_tests.rb +12 -13
  58. data/test/unit/runner_tests.rb +10 -10
  59. data/test/unit/test_runner_tests.rb +117 -24
  60. metadata +51 -21
  61. data/bench/queue.rb +0 -8
  62. data/lib/qs/redis_item.rb +0 -33
  63. data/test/unit/redis_item_tests.rb +0 -49
@@ -0,0 +1,139 @@
1
+ require 'assert'
2
+ require 'qs/payload'
3
+
4
+ module Qs::Payload
5
+
6
+ class UnitTests < Assert::Context
7
+ desc "Qs::Payload"
8
+ setup do
9
+ # the default JSON encoder/decoder is not deterministic, the keys in the
10
+ # string can be randomly ordered
11
+ Assert.stub(Qs, :encode){ |hash| hash.to_a.sort }
12
+ Assert.stub(Qs, :decode){ |array| Hash[array] }
13
+ end
14
+ subject{ Qs::Payload }
15
+
16
+ should have_imeths :type_method_name
17
+ should have_imeths :deserialize, :serialize
18
+ should have_imeths :job, :job_hash
19
+ should have_imeths :event, :event_hash
20
+
21
+ should "convert payload types to method names using `type_method_name`" do
22
+ assert_equal 'job', subject.type_method_name(Qs::Job::PAYLOAD_TYPE)
23
+ assert_equal 'event', subject.type_method_name(Qs::Event::PAYLOAD_TYPE)
24
+ assert_raises(InvalidError){ subject.type_method_name(Factory.string) }
25
+ end
26
+
27
+ should "serialize and deserialize messages" do
28
+ message = Factory.message
29
+ encoded_payload = subject.serialize(message)
30
+ exp = Qs.encode(subject.send("#{message.payload_type}_hash", message))
31
+ assert_equal exp, encoded_payload
32
+ deserialized_job = subject.deserialize(encoded_payload)
33
+ assert_equal message, deserialized_job
34
+ end
35
+
36
+ should "build jobs and job payload hashes" do
37
+ job = Factory.job
38
+ payload_hash = {
39
+ 'type' => job.payload_type,
40
+ 'name' => job.name,
41
+ 'params' => job.params,
42
+ 'created_at' => Timestamp.new(job.created_at)
43
+ }
44
+ assert_equal job, subject.job(payload_hash)
45
+ assert_equal payload_hash, subject.job_hash(job)
46
+ end
47
+
48
+ should "sanitize its jobs attributes when building a job payload hash" do
49
+ job = Factory.job({
50
+ :name => Factory.string.to_sym,
51
+ :params => { Factory.string.to_sym => Factory.string }
52
+ })
53
+ payload_hash = subject.job_hash(job)
54
+
55
+ assert_equal job.name.to_s, payload_hash['name']
56
+ exp = StringifyParams.new(job.params)
57
+ assert_equal exp, payload_hash['params']
58
+ end
59
+
60
+ should "build events and event payload hashes" do
61
+ event = Factory.event
62
+ payload_hash = {
63
+ 'type' => event.payload_type,
64
+ 'channel' => event.channel,
65
+ 'name' => event.name,
66
+ 'params' => event.params,
67
+ 'publisher' => event.publisher,
68
+ 'published_at' => Timestamp.new(event.published_at)
69
+ }
70
+ assert_equal event, subject.event(payload_hash)
71
+ assert_equal payload_hash, subject.event_hash(event)
72
+ end
73
+
74
+ should "sanitize its events attributes when building an event payload hash" do
75
+ event = Factory.event({
76
+ :channel => Factory.string.to_sym,
77
+ :name => Factory.string.to_sym,
78
+ :params => { Factory.string.to_sym => Factory.string },
79
+ :publisher => Factory.string.to_sym
80
+ })
81
+ payload_hash = subject.event_hash(event)
82
+
83
+ assert_equal event.channel.to_s, payload_hash['channel']
84
+ assert_equal event.name.to_s, payload_hash['name']
85
+ exp = StringifyParams.new(event.params)
86
+ assert_equal exp, payload_hash['params']
87
+ assert_equal event.publisher.to_s, payload_hash['publisher']
88
+ end
89
+
90
+ should "raise errors for unknown parent types" do
91
+ message = Factory.message
92
+ Assert.stub(message, :payload_type){ Factory.string }
93
+ payload_hash = { 'type' => message.payload_type }
94
+
95
+ assert_raises(InvalidError){ subject.deserialize(payload_hash) }
96
+ assert_raises(InvalidError){ subject.serialize(message) }
97
+ end
98
+
99
+ end
100
+
101
+ class StringifyParamsTests < UnitTests
102
+ desc "StringifyParams"
103
+ subject{ StringifyParams }
104
+
105
+ should have_imeths :new
106
+
107
+ should "convert all hash keys to strings" do
108
+ key, value = Factory.string.to_sym, Factory.string
109
+ result = subject.new({
110
+ key => value,
111
+ :hash => { key => [value] },
112
+ :array => [{ key => value }]
113
+ })
114
+ exp = {
115
+ key.to_s => value,
116
+ 'hash' => { key.to_s => [value] },
117
+ 'array' => [{ key.to_s => value }]
118
+ }
119
+ assert_equal exp, result
120
+ end
121
+
122
+ end
123
+
124
+ class TimestampTests < UnitTests
125
+ desc "Timestamp"
126
+ subject{ Timestamp }
127
+
128
+ should have_imeths :to_time, :new
129
+
130
+ should "handle building timestamps and converting them to times" do
131
+ time = Factory.time
132
+ timestamp = subject.new(time)
133
+ assert_equal time.to_i, timestamp
134
+ assert_equal time, subject.to_time(timestamp)
135
+ end
136
+
137
+ end
138
+
139
+ end
@@ -2,7 +2,7 @@ require 'assert'
2
2
  require 'qs/qs_runner'
3
3
 
4
4
  require 'qs'
5
- require 'qs/job_handler'
5
+ require 'qs/message_handler'
6
6
 
7
7
  class Qs::QsRunner
8
8
 
@@ -27,7 +27,7 @@ class Qs::QsRunner
27
27
  class InitTests < UnitTests
28
28
  desc "when init"
29
29
  setup do
30
- @handler_class = TestJobHandler
30
+ @handler_class = TestMessageHandler
31
31
  @runner = @runner_class.new(@handler_class)
32
32
  end
33
33
  subject{ @runner }
@@ -36,8 +36,8 @@ class Qs::QsRunner
36
36
  should have_imeths :run
37
37
 
38
38
  should "know its timeout" do
39
- assert_equal TestJobHandler.timeout, subject.timeout
40
- handler_class = Class.new{ include Qs::JobHandler }
39
+ assert_equal TestMessageHandler.timeout, subject.timeout
40
+ handler_class = Class.new{ include Qs::MessageHandler }
41
41
  runner = @runner_class.new(handler_class)
42
42
  assert_equal Qs.config.timeout, runner.timeout
43
43
  end
@@ -131,8 +131,8 @@ class Qs::QsRunner
131
131
 
132
132
  end
133
133
 
134
- class TestJobHandler
135
- include Qs::JobHandler
134
+ class TestMessageHandler
135
+ include Qs::MessageHandler
136
136
 
137
137
  attr_reader :first_before_call_order, :second_before_call_order
138
138
  attr_reader :first_after_call_order, :second_after_call_order
@@ -3,7 +3,6 @@ require 'qs'
3
3
 
4
4
  require 'hella-redis/connection_spy'
5
5
  require 'ns-options/assert_macros'
6
- require 'qs/job'
7
6
  require 'qs/queue'
8
7
 
9
8
  module Qs
@@ -21,9 +20,14 @@ module Qs
21
20
  subject{ @module }
22
21
 
23
22
  should have_imeths :config, :configure, :init, :reset!
24
- should have_imeths :enqueue, :push
25
- should have_imeths :serialize, :deserialize
23
+ should have_imeths :enqueue, :publish, :publish_as, :push
24
+ should have_imeths :encode, :decode
25
+ should have_imeths :sync_subscriptions, :clear_subscriptions
26
+ should have_imeths :event_subscribers
26
27
  should have_imeths :client, :redis, :redis_config
28
+ should have_imeths :dispatcher_queue, :dispatcher_job_name
29
+ should have_imeths :event_publisher
30
+ should have_imeths :published_events
27
31
 
28
32
  should "know its config" do
29
33
  assert_instance_of Config, subject.config
@@ -32,7 +36,7 @@ module Qs
32
36
  should "allow configuring its config" do
33
37
  yielded = nil
34
38
  subject.configure{ |c| yielded = c }
35
- assert_equal subject.config, yielded
39
+ assert_same subject.config, yielded
36
40
  end
37
41
 
38
42
  should "not have a client or redis connection by default" do
@@ -50,11 +54,16 @@ module Qs
50
54
  class InitTests < UnitTests
51
55
  desc "when init"
52
56
  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
57
+ @module.config.encoder = proc{ |v| v.to_s }
58
+ @module.config.decoder = proc{ |v| v.to_i }
59
+ @module.config.redis.ip = Factory.string
60
+ @module.config.redis.port = Factory.integer
61
+ @module.config.redis.db = Factory.integer
62
+
63
+ @dispatcher_queue_spy = nil
64
+ Assert.stub(DispatcherQueue, :new) do |*args|
65
+ @dispatcher_queue_spy = DispatcherQueueSpy.new(*args)
66
+ end
58
67
 
59
68
  @client_spy = nil
60
69
  Assert.stub(Client, :new) do |*args|
@@ -73,6 +82,17 @@ module Qs
73
82
  assert_equal expected, subject.config.redis.url
74
83
  end
75
84
 
85
+ should "build a dispatcher queue" do
86
+ assert_equal @dispatcher_queue_spy, subject.dispatcher_queue
87
+ exp = subject.config.dispatcher_queue_class
88
+ assert_equal exp, @dispatcher_queue_spy.queue_class
89
+ dispatcher_config = subject.config.dispatcher
90
+ assert_equal dispatcher_config.queue_name, @dispatcher_queue_spy.queue_name
91
+ assert_equal dispatcher_config.job_name, @dispatcher_queue_spy.job_name
92
+ exp = dispatcher_config.job_handler_class_name
93
+ assert_equal exp, @dispatcher_queue_spy.job_handler_class_name
94
+ end
95
+
76
96
  should "build a client" do
77
97
  assert_equal @client_spy, subject.client
78
98
  assert_equal @client_spy.redis, subject.redis
@@ -91,6 +111,32 @@ module Qs
91
111
  assert_equal job_params, call.job_params
92
112
  end
93
113
 
114
+ should "call publish on its client using `publish`" do
115
+ event_channel = Factory.string
116
+ event_name = Factory.string
117
+ event_params = { Factory.string => Factory.string }
118
+ subject.publish(event_channel, event_name, event_params)
119
+
120
+ call = @client_spy.publish_calls.last
121
+ assert_equal event_channel, call.event_channel
122
+ assert_equal event_name, call.event_name
123
+ assert_equal event_params, call.event_params
124
+ end
125
+
126
+ should "call publish as on its client using `publish_as`" do
127
+ event_publisher = Factory.string
128
+ event_channel = Factory.string
129
+ event_name = Factory.string
130
+ event_params = { Factory.string => Factory.string }
131
+ subject.publish_as(event_publisher, event_channel, event_name, event_params)
132
+
133
+ call = @client_spy.publish_calls.last
134
+ assert_equal event_publisher, call.event_publisher
135
+ assert_equal event_channel, call.event_channel
136
+ assert_equal event_name, call.event_name
137
+ assert_equal event_params, call.event_params
138
+ end
139
+
94
140
  should "call push on its client using `push`" do
95
141
  queue_name = Factory.string
96
142
  payload = { Factory.string => Factory.string }
@@ -101,22 +147,58 @@ module Qs
101
147
  assert_equal payload, call.payload
102
148
  end
103
149
 
104
- should "use the configured serializer using `serialize`" do
150
+ should "use the configured encoder using `encode`" do
105
151
  value = Factory.integer
106
- result = subject.serialize(value)
152
+ result = subject.encode(value)
107
153
  assert_equal value.to_s, result
108
154
  end
109
155
 
110
- should "use the configured deserializer using `deserialize`" do
156
+ should "use the configured decoder using `decode`" do
111
157
  value = Factory.integer.to_s
112
- result = subject.deserialize(value)
158
+ result = subject.decode(value)
113
159
  assert_equal value.to_i, result
114
160
  end
115
161
 
116
- should "not reset its client or redis connection when init again" do
162
+ should "demeter its clients subscription methods" do
163
+ queue = Qs::Queue.new{ name Factory.string }
164
+
165
+ subject.sync_subscriptions(queue)
166
+ call = @client_spy.sync_subscriptions_calls.last
167
+ assert_equal queue, call.queue
168
+
169
+ subject.clear_subscriptions(queue)
170
+ call = @client_spy.clear_subscriptions_calls.last
171
+ assert_equal queue, call.queue
172
+ end
173
+
174
+ should "demeter its event publishers method to its client" do
175
+ event = Factory.event
176
+ subject.event_subscribers(event)
177
+
178
+ call = @client_spy.event_subscribers_calls.last
179
+ assert_equal event, call.event
180
+ end
181
+
182
+ should "know its dispatcher job name and event publisher" do
183
+ exp = subject.config.dispatcher.job_name
184
+ assert_equal exp, subject.dispatcher_job_name
185
+ exp = subject.config.event_publisher
186
+ assert_equal exp, subject.event_publisher
187
+ end
188
+
189
+ should "return the dispatcher queue published events using `published_events`" do
190
+ queue = subject.dispatcher_queue
191
+ published_events = Factory.integer(3).times.map{ Factory.string }
192
+ Assert.stub(queue, :published_events){ published_events }
193
+ assert_equal queue.published_events, subject.published_events
194
+ end
195
+
196
+ should "not reset its attributes when init again" do
197
+ queue = subject.dispatcher_queue
117
198
  client = subject.client
118
199
  redis = subject.redis
119
200
  subject.init
201
+ assert_same queue, subject.dispatcher_queue
120
202
  assert_same client, subject.client
121
203
  assert_same redis, subject.redis
122
204
  end
@@ -124,10 +206,11 @@ module Qs
124
206
  should "reset itself using `reset!`" do
125
207
  subject.reset!
126
208
  assert_nil subject.config.redis.url
209
+ assert_nil subject.dispatcher_queue
127
210
  assert_nil subject.client
128
211
  assert_nil subject.redis
129
- assert_raises(NoMethodError){ subject.serialize(Factory.integer) }
130
- assert_raises(NoMethodError){ subject.deserialize(Factory.integer) }
212
+ assert_raises(NoMethodError){ subject.encode(Factory.integer) }
213
+ assert_raises(NoMethodError){ subject.decode(Factory.integer) }
131
214
  end
132
215
 
133
216
  end
@@ -141,25 +224,39 @@ module Qs
141
224
  end
142
225
  subject{ @config }
143
226
 
144
- should have_options :serializer, :deserializer, :timeout
145
- should have_namespace :redis
227
+ should have_options :encoder, :decoder, :timeout
228
+ should have_options :event_publisher
229
+ should have_namespace :dispatcher, :redis
230
+ should have_accessors :dispatcher_queue_class
146
231
 
147
- should "know its default serializer/deserializer" do
232
+ should "know its default decoder/encoder" do
148
233
  payload = { Factory.string => Factory.string }
149
234
 
150
235
  exp = JSON.dump(payload)
151
- serialized_payload = subject.serializer.call(payload)
152
- assert_equal exp, serialized_payload
236
+ encoded_payload = subject.encoder.call(payload)
237
+ assert_equal exp, encoded_payload
153
238
  exp = JSON.load(exp)
154
- assert_equal exp, subject.deserializer.call(serialized_payload)
239
+ assert_equal exp, subject.decoder.call(encoded_payload)
155
240
  end
156
241
 
157
242
  should "know its default timeout" do
158
243
  assert_nil subject.timeout
159
244
  end
160
245
 
246
+ should "not have a default event publisher" do
247
+ assert_nil subject.event_publisher
248
+ end
249
+
250
+ should "know its default dispatcher options" do
251
+ assert_equal Queue, subject.dispatcher_queue_class
252
+ assert_equal 'dispatcher', subject.dispatcher.queue_name
253
+ assert_equal 'run_dispatch_job', subject.dispatcher.job_name
254
+ exp = DispatcherQueue::RunDispatchJob.to_s
255
+ assert_equal exp, subject.dispatcher.job_handler_class_name
256
+ end
257
+
161
258
  should "know its default redis options" do
162
- assert_equal 'localhost', subject.redis.ip
259
+ assert_equal '127.0.0.1', subject.redis.ip
163
260
  assert_equal 6379, subject.redis.port
164
261
  assert_equal 0, subject.redis.db
165
262
  assert_equal 'qs', subject.redis.redis_ns
@@ -191,27 +288,69 @@ module Qs
191
288
 
192
289
  end
193
290
 
291
+ class DispatcherQueueSpy
292
+ attr_reader :queue_class, :queue_name
293
+ attr_reader :job_name, :job_handler_class_name
294
+ attr_reader :published_events
295
+
296
+ def initialize(options)
297
+ @queue_class = options[:queue_class]
298
+ @queue_name = options[:queue_name]
299
+ @job_name = options[:job_name]
300
+ @job_handler_class_name = options[:job_handler_class_name]
301
+ end
302
+ end
303
+
194
304
  class ClientSpy
195
305
  attr_reader :redis_config, :redis
196
- attr_reader :enqueue_calls, :push_calls
306
+ attr_reader :enqueue_calls, :publish_calls, :push_calls
307
+ attr_reader :sync_subscriptions_calls, :clear_subscriptions_calls
308
+ attr_reader :event_subscribers_calls
197
309
 
198
310
  def initialize(redis_confg)
199
311
  @redis_config = redis_confg
200
312
  @redis = Factory.string
201
313
  @enqueue_calls = []
314
+ @publish_calls = []
202
315
  @push_calls = []
316
+ @sync_subscriptions_calls = []
317
+ @clear_subscriptions_calls = []
318
+ @event_subscribers_calls = []
319
+ end
320
+
321
+ def enqueue(queue, name, params = nil)
322
+ @enqueue_calls << EnqueueCall.new(queue, name, params)
323
+ end
324
+
325
+ def publish(channel, name, params = nil)
326
+ @publish_calls << PublishCall.new(channel, name, params)
203
327
  end
204
328
 
205
- def enqueue(queue, job_name, job_params = nil)
206
- @enqueue_calls << EnqueueCall.new(queue, job_name, job_params)
329
+ def publish_as(publisher, channel, name, params = nil)
330
+ @publish_calls << PublishCall.new(channel, name, params, publisher)
207
331
  end
208
332
 
209
333
  def push(queue_name, payload)
210
334
  @push_calls << PushCall.new(queue_name, payload)
211
335
  end
212
336
 
213
- EnqueueCall = Struct.new(:queue, :job_name, :job_params)
214
- PushCall = Struct.new(:queue_name, :payload)
337
+ def sync_subscriptions(queue)
338
+ @sync_subscriptions_calls << SubscriptionsCall.new(queue)
339
+ end
340
+
341
+ def clear_subscriptions(queue)
342
+ @clear_subscriptions_calls << SubscriptionsCall.new(queue)
343
+ end
344
+
345
+ def event_subscribers(event)
346
+ @event_subscribers_calls << SubscribersCall.new(event)
347
+ end
348
+
349
+ EnqueueCall = Struct.new(:queue, :job_name, :job_params)
350
+ PublishCall = Struct.new(:event_channel, :event_name, :event_params, :event_publisher)
351
+ PushCall = Struct.new(:queue_name, :payload)
352
+ SubscriptionsCall = Struct.new(:queue, :event_job_names)
353
+ SubscribersCall = Struct.new(:event)
215
354
  end
216
355
 
217
356
  end