chasqui 0.9.3 → 1.0.0.pre.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/README.md +60 -39
  4. data/Vagrantfile +15 -0
  5. data/lib/chasqui.rb +28 -55
  6. data/lib/chasqui/broker.rb +3 -3
  7. data/lib/chasqui/{multi_broker.rb → brokers/redis_broker.rb} +11 -11
  8. data/lib/chasqui/config.rb +26 -6
  9. data/lib/chasqui/queue_adapter.rb +14 -0
  10. data/lib/chasqui/queue_adapters/redis_queue_adapter.rb +51 -0
  11. data/lib/chasqui/subscriber.rb +56 -49
  12. data/lib/chasqui/subscriptions.rb +67 -0
  13. data/lib/chasqui/version.rb +1 -1
  14. data/lib/chasqui/worker.rb +81 -0
  15. data/spec/integration/pubsub_examples.rb +20 -20
  16. data/spec/integration/resque_spec.rb +1 -1
  17. data/spec/integration/setup/subscribers.rb +25 -9
  18. data/spec/integration/sidekiq_spec.rb +1 -1
  19. data/spec/lib/chasqui/{multi_broker_spec.rb → brokers/redis_broker_spec.rb} +54 -26
  20. data/spec/lib/chasqui/cli_spec.rb +1 -1
  21. data/spec/lib/chasqui/config_spec.rb +121 -0
  22. data/spec/lib/chasqui/fake_queue_adapter_spec.rb +5 -0
  23. data/spec/lib/chasqui/queue_adapters/redis_queue_adapter_spec.rb +73 -0
  24. data/spec/lib/chasqui/subscriber_spec.rb +95 -43
  25. data/spec/lib/chasqui/subscriptions_spec.rb +60 -0
  26. data/spec/lib/chasqui/worker_spec.rb +96 -0
  27. data/spec/lib/chasqui_spec.rb +32 -191
  28. data/spec/spec_helper.rb +1 -1
  29. data/spec/support/chasqui_spec_helpers.rb +28 -0
  30. data/spec/support/fake_queue_adapter.rb +3 -0
  31. data/spec/support/fake_subscriber.rb +2 -1
  32. data/spec/support/shared_examples/queue_adapter_examples.rb +13 -0
  33. metadata +25 -18
  34. data/lib/chasqui/subscription.rb +0 -53
  35. data/lib/chasqui/workers/resque_worker.rb +0 -25
  36. data/lib/chasqui/workers/sidekiq_worker.rb +0 -45
  37. data/lib/chasqui/workers/worker.rb +0 -34
  38. data/spec/lib/chasqui/subscription_spec.rb +0 -35
  39. data/spec/lib/chasqui/workers/resque_worker_spec.rb +0 -27
  40. data/spec/lib/chasqui/workers/sidekiq_worker_spec.rb +0 -34
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe Chasqui::Subscriptions do
4
+ let(:queue_adapter) { FakeQueueAdapter.new }
5
+
6
+ subject { Chasqui::Subscriptions.new queue_adapter }
7
+
8
+ let(:a) { new_subscriber 'SubscriberA', channel: 'ch1', queue: 'foo' }
9
+ let(:b) { new_subscriber 'SubscriberB', channel: 'ch1', queue: 'bar' }
10
+ let(:c) { new_subscriber 'SubscriberC', channel: 'ch2', queue: 'foo' }
11
+ let(:d) { new_subscriber 'SubscriberD', channel: 'ch1', queue: 'foo' }
12
+
13
+ before do
14
+ reset_config
15
+
16
+ allow(Chasqui).to receive(:register)
17
+
18
+ [a, b, c, d].each do |subscriber|
19
+ expect(queue_adapter).to receive(:bind).with(subscriber)
20
+ subject.register subscriber
21
+ end
22
+ end
23
+
24
+ it 'registers subscribers' do
25
+ [a, b, c, d].each do |subscriber|
26
+ expect(subject.subscribed? subscriber).to be true
27
+ end
28
+
29
+ expect(subject.subscribers.size).to eq(4)
30
+
31
+ group1 = subject.find 'ch1', 'foo'
32
+ expect(group1).to include(a)
33
+ expect(group1).to include(d)
34
+
35
+ group2 = subject.find 'ch1', 'bar'
36
+ expect(group2.size).to eq(1)
37
+ expect(subject.find 'ch1', 'bar').to include(b)
38
+
39
+ group3 = subject.find 'ch2', 'foo'
40
+ expect(group3.size).to eq(1)
41
+ expect(subject.find 'ch2', 'foo').to include(c)
42
+ end
43
+
44
+ it 'unregisters subscribers' do
45
+ [a, b, c].each do |subscriber|
46
+ expect(queue_adapter).to receive(:unbind).with(subscriber)
47
+ subject.unregister subscriber
48
+ expect(subject.subscribed? subscriber).to be false
49
+ end
50
+
51
+ expect(subject.subscribed? d).to be true
52
+
53
+ expect(subject.find 'ch1', 'bar').to be_empty
54
+ expect(subject.find 'ch2', 'foo').to be_empty
55
+
56
+ remaining = subject.find 'ch1', 'foo'
57
+ expect(remaining.size).to eq(1)
58
+ expect(remaining).to include(d)
59
+ end
60
+ end
@@ -0,0 +1,96 @@
1
+ require 'spec_helper'
2
+
3
+ class MockSubscriber
4
+ include Chasqui::Subscriber
5
+ subscribe channel: 'foo-channel', prefix: nil, queue: 'foo-queue'
6
+
7
+ def perform(payload)
8
+ self.class.info[:event] = event
9
+ self.class.info[:payload] = payload
10
+ self.class.info[:redis] = redis
11
+ self.class.info[:logger] = logger
12
+ end
13
+
14
+ class << self
15
+ def info
16
+ @info ||= {}
17
+ end
18
+ end
19
+ end
20
+
21
+ describe Chasqui::Worker do
22
+ before do
23
+ reset_config
24
+ Chasqui::Worker.workers.clear
25
+ MockSubscriber.instance_variable_set :@info, nil
26
+ end
27
+
28
+ let(:subscriber) { MockSubscriber }
29
+
30
+ context 'no worker backend' do
31
+ describe '.create' do
32
+ it 'raises' do
33
+ Chasqui.config.worker_backend = :does_not_exist
34
+
35
+ expect(-> {
36
+ Chasqui::Worker.create subscriber
37
+ }).to raise_error(Chasqui::ConfigurationError)
38
+ end
39
+ end
40
+ end
41
+
42
+ context 'resque' do
43
+ before { Chasqui.config.worker_backend = :resque }
44
+ after { Chasqui::Workers.send :remove_const, :MockSubscriber }
45
+
46
+ describe '.create' do
47
+ let(:worker) { Chasqui::Worker.create subscriber }
48
+
49
+ it { expect(worker.name).to eq('Chasqui::Workers::MockSubscriber') }
50
+ it { expect(worker.instance_variable_get(:@queue)).to eq(subscriber.queue) }
51
+ it { expect(worker.instance_variable_get(:@subscriber)).to eq(subscriber) }
52
+
53
+ it 'delegates #perform to the subscriber' do
54
+ event = {
55
+ 'channel' => 'foo-channel',
56
+ 'payload' => { 'some' => 'data' }
57
+ }
58
+
59
+ worker.perform event
60
+
61
+ expect(subscriber.info[:event]).to eq(event)
62
+ expect(subscriber.info[:payload]).to eq(event['payload'])
63
+ end
64
+ end
65
+ end
66
+
67
+ if sidekiq_supported_ruby_version?
68
+ context 'sidekiq' do
69
+ before { Chasqui.config.worker_backend = :sidekiq }
70
+ after { Chasqui::Workers.send :remove_const, :MockSubscriber }
71
+
72
+ describe '.create' do
73
+ let(:worker) { Chasqui::Worker.create subscriber }
74
+
75
+ it { expect(worker.name).to eq('Chasqui::Workers::MockSubscriber') }
76
+ it { expect(worker.instance_variable_get(:@subscriber)).to eq(subscriber) }
77
+ it { expect(worker.sidekiq_options).to include('queue' => 'foo-queue') }
78
+ it { expect(worker.included_modules).to include(Sidekiq::Worker) }
79
+
80
+ it 'delegates #perform to the subscriber' do
81
+ event = {
82
+ 'channel' => 'foo-channel',
83
+ 'payload' => { 'some' => 'data' }
84
+ }
85
+
86
+ sidekiq_worker = worker.new
87
+ sidekiq_worker.perform event
88
+
89
+ expect(subscriber.info[:event]).to eq(event)
90
+ expect(subscriber.info[:payload]).to eq(event['payload'])
91
+ expect(subscriber.info[:logger]).to eq(sidekiq_worker.logger)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -5,90 +5,40 @@ describe Chasqui do
5
5
  expect(Chasqui::VERSION).not_to be nil
6
6
  end
7
7
 
8
- describe '.configure' do
9
- before { reset_config }
10
-
11
- context 'defaults' do
12
- it { expect(Chasqui.channel).to eq('__default') }
13
- it { expect(Chasqui.inbox_queue).to eq('inbox') }
14
- it { expect(Chasqui.redis.client.db).to eq(0) }
15
- it { expect(Chasqui.config.broker_poll_interval).to eq(3) }
16
- it { expect(Chasqui.config.worker_backend).to eq(nil) }
17
-
18
- it do
19
- # remove chasqui's test environment logger
20
- Chasqui.config[:logger] = nil
21
- expect(Chasqui.logger).to be_kind_of(Logger)
22
- end
23
-
24
- it { expect(Chasqui.logger.level).to eq(Logger::INFO) }
25
- it { expect(Chasqui.logger.progname).to eq('chasqui') }
26
- end
27
-
28
- it 'configures the channel' do
29
- Chasqui.config.channel = 'com.example.test'
30
- expect(Chasqui.channel).to eq('com.example.test')
31
- end
32
-
33
- it 'accepts a block' do
34
- Chasqui.configure { |config| config.channel = 'com.example.test' }
35
- expect(Chasqui.channel).to eq('com.example.test')
36
- end
37
-
38
- it 'configures the inbox queue' do
39
- Chasqui.config.inbox_queue = 'foo'
40
- expect(Chasqui.inbox).to eq('foo')
41
- end
42
-
43
- it 'configures the broker poll interval' do
44
- Chasqui.config.broker_poll_interval = 1
45
- expect(Chasqui.config.broker_poll_interval).to eq(1)
8
+ describe '.config' do
9
+ it 'returns a config object' do
10
+ expect(Chasqui.config).to be_instance_of(Chasqui::Config)
46
11
  end
12
+ end
47
13
 
48
- context 'redis' do
49
- it 'accepts config options' do
50
- redis_config = { host: '10.0.3.24' }
51
- Chasqui.config.redis = redis_config
52
- expect(Chasqui.redis.client.host).to eq('10.0.3.24')
53
- end
54
-
55
- it 'accepts an initialized client' do
56
- redis = Redis.new db: 2
57
- Chasqui.config.redis = redis
58
- expect(Chasqui.redis.client.db).to eq(2)
59
- end
60
-
61
- it 'accepts URLs' do
62
- Chasqui.config.redis = 'redis://10.0.1.21:12345/0'
63
- expect(Chasqui.redis.client.host).to eq('10.0.1.21')
14
+ describe '.configure' do
15
+ it 'yields a config object' do
16
+ Chasqui.configure do |c|
17
+ expect(c).to be_instance_of(Chasqui::Config)
64
18
  end
19
+ end
20
+ end
65
21
 
66
- it 'uses a namespace' do
67
- Chasqui.redis.set 'foo', 'bar'
68
- expect(Chasqui.redis.redis.get 'chasqui:foo').to eq('bar')
22
+ describe 'config delegates' do
23
+ Chasqui::CONFIG_SETTINGS.each do |setting|
24
+ it "responds to #{setting}" do
25
+ expect(Chasqui).to respond_to(setting)
69
26
  end
70
27
  end
28
+ end
71
29
 
72
- describe 'logger' do
73
- it 'accepts a log device' do
74
- logs = StringIO.new
75
- Chasqui.config.logger = logs
76
- Chasqui.logger.info "status"
77
- Chasqui.logger.warn "error"
78
-
79
- logs.rewind
80
- output = logs.read
30
+ describe '.subscriptions' do
31
+ subject { Chasqui.subscriptions }
81
32
 
82
- %w(chasqui INFO status WARN error).each do |text|
83
- expect(output).to match(text)
84
- end
85
- end
33
+ it { should be_instance_of(Chasqui::Subscriptions) }
34
+ it { expect(subject.queue_adapter).to be_instance_of(Chasqui::QueueAdapters::RedisQueueAdapter) }
35
+ end
86
36
 
87
- it 'accepts a logger-like object' do
88
- fake_logger = FakeLogger.new
89
- Chasqui.config.logger = fake_logger
90
- expect(Chasqui.logger).to eq(fake_logger)
91
- expect(Chasqui.logger.progname).to eq('chasqui')
37
+ describe 'subscription management delegates' do
38
+ [:register, :unregister].each do |m|
39
+ it "delegates :#{m} to #subscriptions" do
40
+ expect(Chasqui.subscriptions).to receive(m)
41
+ Chasqui.send m
92
42
  end
93
43
  end
94
44
  end
@@ -106,128 +56,19 @@ describe Chasqui do
106
56
  Chasqui.publish 'test.event', *args
107
57
  end
108
58
 
109
- payloads.each do |data|
59
+ payloads.each do |payload|
110
60
  event = JSON.load Chasqui.redis.rpop('inbox')
111
- expect(event['event']).to eq('test.event')
112
- expect(event['channel']).to eq('__default')
113
- expect(event['data']).to eq(data)
114
- expect(event['created_at'].to_f).to be_within(0.01).of(Time.now.to_f)
61
+ expect(event['channel']).to eq('test.event')
62
+ expect(event['payload']).to eq(payload)
63
+ expect(event['created_at'].to_f).to be_within(0.1).of(Time.now.to_f)
64
+ expect(event['retry']).to eq(true)
115
65
  end
116
66
  end
117
67
 
118
- it 'supports channels' do
119
- Chasqui.config.channel = 'my.app'
120
- Chasqui.publish 'test.event', :foo
121
- event = JSON.load Chasqui.redis.rpop('inbox')
122
- expect(event['event']).to eq('test.event')
123
- expect(event['channel']).to eq('my.app')
124
- expect(event['data']).to eq(['foo'])
125
- end
126
-
127
68
  it 'supports retries' do
128
- Chasqui.publish 'test.event', :foo, :bar, foo: 'bar', job_options: { retry: true }
69
+ Chasqui.publish 'test.event', :foo, :bar, foo: 'bar', job_options: { retry: false }
129
70
  event = JSON.load Chasqui.redis.rpop('inbox')
130
- expect(event['retry']).to eq(true)
131
- end
132
- end
133
-
134
- describe '.subscribe' do
135
- before do
136
- reset_chasqui
137
- Resque.redis.namespace = :resque
138
- Chasqui.config.worker_backend = :resque
139
- end
140
-
141
- context 'with defaults' do
142
- it 'subscribes to events on the default channel' do
143
- sub = Chasqui.subscribe queue: 'my-queue'
144
-
145
- channel = Chasqui.config.channel
146
- queues = Chasqui.redis.smembers Chasqui.subscription_key(channel)
147
- expect(queues).to eq(['resque/resque:queue:my-queue'])
148
- end
149
- end
150
-
151
- context 'resque worker subscriptions' do
152
- before { Resque.redis.namespace = 'blah' }
153
-
154
- it 'creates subscriptions using the appropriate redis namespace' do
155
- sub1 = Chasqui.subscribe channel: 'com.example.admin', queue: 'app1-queue'
156
- sub2 = Chasqui.subscribe channel: 'com.example.admin', queue: 'app2-queue'
157
- sub3 = Chasqui.subscribe channel: 'com.example.video', queue: 'app1-queue'
158
-
159
- queues = Chasqui.redis.smembers Chasqui.subscription_key("com.example.admin")
160
- expect(queues.sort).to eq(['resque/blah:queue:app1-queue', 'resque/blah:queue:app2-queue'])
161
-
162
- queues = Chasqui.redis.smembers Chasqui.subscription_key("com.example.video")
163
- expect(queues).to eq(['resque/blah:queue:app1-queue'])
164
-
165
- expect(Chasqui.subscription('app1-queue')).to eq(sub1)
166
- expect(Chasqui.subscription('app2-queue')).to eq(sub2)
167
- expect(sub1).to eq(sub3)
168
- end
169
- end
170
-
171
- if sidekiq_supported_ruby_version?
172
- context 'sidekiq worker subscriptions' do
173
- before do
174
- Chasqui.config.worker_backend = :sidekiq
175
- end
176
-
177
- it 'creates subscriptions using the appropriate redis namespace' do
178
- Chasqui.subscribe channel: 'com.example.admin', queue: 'app1-queue'
179
- queues = Chasqui.redis.smembers Chasqui.subscription_key("com.example.admin")
180
- expect(queues.sort).to eq(['sidekiq/queue:app1-queue'])
181
-
182
- Sidekiq.redis = { url: redis.client.options[:url], namespace: 'foobar' }
183
- Chasqui.subscribe channel: 'com.example.video', queue: 'app2-queue'
184
- queues = Chasqui.redis.smembers Chasqui.subscription_key("com.example.video")
185
- expect(queues.sort).to eq(['sidekiq/foobar:queue:app2-queue'])
186
- end
187
- end
188
- end
189
-
190
- it 'returns a subscription' do
191
- subscription = Chasqui.subscribe channel: 'com.example.admin', queue: 'app1-queue'
192
- expect(subscription.subscriber).to be_kind_of(Chasqui::Subscriber)
193
- end
194
-
195
- it 'yields a subscriber configuration context' do
196
- $context = nil
197
- Chasqui.subscribe channel: 'bar', queue: 'foo' do
198
- $context = self
199
- end
200
- expect($context).to be_kind_of(Chasqui::Subscriber)
201
- end
202
- end
203
-
204
- describe '.unsubscribe' do
205
- before do
206
- reset_chasqui
207
- Chasqui.config.worker_backend = :resque
208
- Resque.redis.namespace = 'ns0'
209
- Chasqui.subscribe channel: 'com.example.admin', queue: 'app1-queue'
210
- Chasqui.subscribe channel: 'com.example.admin', queue: 'app2-queue'
211
- Chasqui.subscribe channel: 'com.example.video', queue: 'app1-queue'
212
- end
213
-
214
- it 'removes the subscription' do
215
- subscription_id = Chasqui.unsubscribe 'com.example.admin', queue: 'app1-queue'
216
- expect(subscription_id).to eq('resque/ns0:queue:app1-queue')
217
- expect(redis.smembers(Chasqui.subscription_key 'com.example.admin').sort).to eq(['resque/ns0:queue:app2-queue'])
218
- expect(redis.smembers(Chasqui.subscription_key 'com.example.video').sort).to eq(['resque/ns0:queue:app1-queue'])
219
- end
220
-
221
- it 'returns nil for unknown subscriptions' do
222
- subscription_id = Chasqui.unsubscribe 'unknown', queue: 'unknown'
223
- expect(subscription_id).to be nil
224
- end
225
- end
226
-
227
- describe '.subscriber_class_name' do
228
- it 'transforms queue name into a subscribe class name' do
229
- expect(Chasqui.subscriber_class_name('my-queue')).to eq(:Subscriber__my_queue)
230
- expect(Chasqui.subscriber_class_name('queue:my-queue')).to eq(:Subscriber__my_queue)
71
+ expect(event['retry']).to eq(false)
231
72
  end
232
73
  end
233
74
  end
data/spec/spec_helper.rb CHANGED
@@ -7,7 +7,7 @@ require 'chasqui'
7
7
  require 'resque'
8
8
  require 'pp'
9
9
 
10
- Dir['spec/support/*.rb'].each { |file| require File.expand_path(file) }
10
+ Dir['spec/support/**/*.rb'].each { |file| require File.expand_path(file) }
11
11
 
12
12
  RSpec.configure do |c|
13
13
  c.include ChasquiSpecHelpers
@@ -3,13 +3,23 @@ module ChasquiSpecHelpers
3
3
  def reset_chasqui
4
4
  flush_redis
5
5
  reset_config
6
+ reset_chasqui_workers
6
7
  end
7
8
 
8
9
  def reset_config
9
10
  Chasqui.instance_variable_set(:@config, nil)
11
+ Chasqui.instance_variable_set(:@subscriptions, nil)
10
12
  Chasqui.config.logger = './tmp/test.log'
11
13
  end
12
14
 
15
+ def reset_chasqui_workers
16
+ Chasqui::Worker.workers.clear
17
+
18
+ Chasqui::Workers.constants.each do |c|
19
+ Chasqui::Workers.send :remove_const, c
20
+ end
21
+ end
22
+
13
23
  def redis
14
24
  Chasqui.redis
15
25
  end
@@ -23,4 +33,22 @@ module ChasquiSpecHelpers
23
33
  nnredis.keys('*').each { |k| nnredis.del k }
24
34
  end
25
35
 
36
+ def new_subscriber(class_name, options={})
37
+ queue = options.fetch :queue
38
+ channel = options.fetch :channel
39
+
40
+ @subscriber_registry ||= {}
41
+
42
+ if @subscriber_registry[class_name] && options[:force]
43
+ Object.send :remove_const, class_name
44
+ @subscriber_registry[class_name] = nil
45
+ end
46
+
47
+ @subscriber_registry[class_name] ||= Class.new
48
+
49
+ @subscriber_registry[class_name].tap do |sub|
50
+ sub.send :include, Chasqui::Subscriber
51
+ sub.subscribe channel: channel, queue: queue
52
+ end
53
+ end
26
54
  end