chasqui 0.1.0 → 0.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1bb216d9f86e895b3a2c48b686a9d3d92f991cf8
4
- data.tar.gz: e736ccd90ee44c1aa827c4d59e09136ea63f9958
3
+ metadata.gz: f308ee6114cf27680f344bbb770e4eeee946f467
4
+ data.tar.gz: 749dbc74254f92306c8204f799a99d54d5e83017
5
5
  SHA512:
6
- metadata.gz: d174c6cb4f929a2135f86fe7bf4c06e32d8f66088e25d18fa4048b872aaaa9430888f7cb5f901665dff430064dd24772164fb1b19aa6b48f3b3e74f7fa9d14a2
7
- data.tar.gz: e4e64b8ee8453138def77a8f229e3fb002f0e794d2c0e0013db5eaa12bdd6bae46e109d707e95b024a6dae4081dbdcfcf2ff7288ea4ea5c9f8ce4ba55eb19532
6
+ metadata.gz: a9fe9a92bba7c1be6ef419fae3f0adbb3db94010b966773b5b32d127a5e43302a6b01bb522f33b0bfd4d291fe389d2da47576a9197cd93a172fdadff875e519b
7
+ data.tar.gz: f7957a3fd5ea629fcb5ac05f5cb80a1d8231385ee7dddf1f8b8dd9a349be580a3b5a1e7908be04ae21be682b9d84af4fd3433ab92b3199b02a348059c1b47803
data/Guardfile CHANGED
@@ -1,4 +1,4 @@
1
- guard :rspec, cmd: "bundle exec rspec" do
1
+ guard :rspec, cmd: "bundle exec rspec --tag ~integration" do
2
2
  require "guard/rspec/dsl"
3
3
  dsl = Guard::RSpec::Dsl.new(self)
4
4
 
data/Rakefile CHANGED
@@ -2,22 +2,12 @@ require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
3
 
4
4
  require 'resque/tasks'
5
- task 'resque:setup' => :environment
5
+ task 'resque:setup' => :resque_integration_environment
6
6
 
7
7
  RSpec::Core::RakeTask.new(:spec)
8
8
 
9
9
  task :default => :spec
10
10
 
11
- task :environment do
12
- $LOAD_PATH.unshift './lib'
13
- require 'bundler/setup'
14
- require 'chasqui'
15
-
16
- if ENV['CHASQUI_ENV'] == 'test'
17
- require './spec/integration/subscribers'
18
- end
19
-
20
- require 'resque'
21
- Resque.redis = ENV['REDIS_URL'] if ENV['REDIS_URL']
22
- Resque.redis.namespace = 'chasqui'
11
+ task :resque_integration_environment do
12
+ require './spec/integration/setup/resque'
23
13
  end
@@ -1,19 +1,23 @@
1
1
  require 'timeout'
2
2
 
3
3
  class Chasqui::Broker
4
- attr_reader :config
4
+ attr_reader :config, :redis, :redis_namespace
5
5
 
6
6
  extend Forwardable
7
- def_delegators :@config, :redis, :inbox, :logger
7
+ def_delegators :@config, :inbox, :logger
8
8
 
9
9
  ShutdownSignals = %w(INT QUIT ABRT TERM).freeze
10
10
 
11
- # To prevent unsuspecting clients from blocking forever, the broker uses
12
- # it's own private redis connection.
13
11
  def initialize
14
12
  @shutdown_requested = nil
15
13
  @config = Chasqui.config.dup
16
- @config.redis = Redis.new @config.redis.client.options
14
+ @redis_namespace = @config.redis.namespace
15
+
16
+ # The broker uses it's own private redis connection for two reasons:
17
+ # 1. subscribers may use a different (or no) redis namespace than chasqui
18
+ # 2. sharing the connection with unsuspecting clients could result in
19
+ # the broker blocking forever
20
+ @redis = Redis.new @config.redis.client.options
17
21
  end
18
22
 
19
23
  def start
@@ -2,11 +2,11 @@ class Chasqui::MultiBroker < Chasqui::Broker
2
2
 
3
3
  def forward_event
4
4
  event = receive or return
5
- queues = subscriber_queues event
5
+ subscribers = subscribers_for(event)
6
6
 
7
7
  redis.multi do
8
- queues.each do |queue|
9
- dispatch event, queue
8
+ subscribers.each do |subscriber_id|
9
+ dispatch event, subscriber_id
10
10
  end
11
11
  redis.rpop(in_progress_queue)
12
12
  end
@@ -15,16 +15,23 @@ class Chasqui::MultiBroker < Chasqui::Broker
15
15
  end
16
16
 
17
17
  def in_progress_queue
18
- "#{inbox}:in_progress"
18
+ to_key inbox, 'in_progress'
19
+ end
20
+
21
+ def inbox_queue
22
+ to_key inbox
19
23
  end
20
24
 
21
25
  private
22
26
 
23
27
  def receive
24
28
  event = retry_failed_event || dequeue
25
- logger.debug "received event: #{event['event']}, on channel: #{event['channel']}"
26
29
 
27
- JSON.parse event
30
+ if event
31
+ JSON.parse(event).tap do |e|
32
+ logger.debug "received event: #{e['event']}, on channel: #{e['channel']}"
33
+ end
34
+ end
28
35
  end
29
36
 
30
37
  def retry_failed_event
@@ -36,21 +43,40 @@ class Chasqui::MultiBroker < Chasqui::Broker
36
43
  end
37
44
 
38
45
  def dequeue
39
- redis.brpoplpush(inbox, in_progress_queue, timeout: config.broker_poll_interval).tap do |event|
46
+ redis.brpoplpush(inbox_queue, in_progress_queue, timeout: config.broker_poll_interval).tap do |event|
40
47
  if event.nil?
41
48
  logger.debug "reached timeout for broker poll interval: #{config.broker_poll_interval} seconds"
42
49
  end
43
50
  end
44
51
  end
45
52
 
46
- def dispatch(event, queue)
47
- job = { class: "Chasqui::Subscriber__#{queue}", args: [event] }.to_json
48
- logger.debug "dispatching event to queue: #{queue}, with job: #{job}"
49
- redis.rpush "queue:#{queue}", job
53
+ def dispatch(event, subscriber_id)
54
+ backend, queue = subscriber_id.split('/', 2)
55
+ job = build_job queue, event
56
+
57
+ logger.debug "dispatching event queue=#{queue} backend=#{backend} job=#{job}"
58
+
59
+ case backend
60
+ when 'resque'
61
+ redis.rpush queue, job
62
+ when 'sidekiq'
63
+ redis.lpush queue, job
64
+ end
65
+ end
66
+
67
+ def build_job(queue, event)
68
+ {
69
+ class: "Chasqui::#{Chasqui.subscriber_class_name(queue)}",
70
+ args: [event]
71
+ }.to_json
72
+ end
73
+
74
+ def subscribers_for(event)
75
+ redis.smembers to_key('subscribers', event['channel'])
50
76
  end
51
77
 
52
- def subscriber_queues(event)
53
- redis.smembers "subscribers:#{event['channel']}"
78
+ def to_key(*args)
79
+ ([redis_namespace] + args).join(':')
54
80
  end
55
81
 
56
82
  end
@@ -1,3 +1,3 @@
1
1
  module Chasqui
2
- VERSION = "0.1.0"
2
+ VERSION = "0.8.0"
3
3
  end
@@ -1,40 +1,25 @@
1
- class Chasqui::ResqueWorker
1
+ module Chasqui
2
+ class ResqueWorker < Worker
3
+ class << self
2
4
 
3
- class << self
4
-
5
- # Factory method to create a Resque worker class for a Chasqui::Subscriber instance.
6
- def create(subscriber)
7
- find_or_build_worker(subscriber).tap do |worker|
8
- worker.class_eval do
9
- @queue = subscriber.queue
10
- @subscriber = subscriber
11
-
12
- def self.perform(event)
13
- @subscriber.perform Resque.redis, event
14
- end
15
- end
5
+ def namespace
6
+ Resque.redis.namespace
16
7
  end
17
- end
18
8
 
19
- private
9
+ # Factory method to create a Resque worker class for a Chasqui::Subscriber instance.
10
+ def create(subscriber)
11
+ find_or_build_worker(subscriber, Chasqui::ResqueWorker).tap do |worker|
12
+ worker.class_eval do
13
+ @queue = subscriber.queue
14
+ @subscriber = subscriber
20
15
 
21
- def find_or_build_worker(subscriber)
22
- class_name = class_name_for subscriber
23
-
24
- if Chasqui.const_defined? class_name
25
- Chasqui.const_get class_name
26
- else
27
- Class.new(Chasqui::ResqueWorker).tap do |worker|
28
- Chasqui.const_set class_name, worker
16
+ def self.perform(event)
17
+ @subscriber.perform Resque.redis, event
18
+ end
19
+ end
29
20
  end
30
21
  end
31
- end
32
22
 
33
- def class_name_for(subscriber)
34
- queue_name_constant = subscriber.queue.gsub(/[^\w]/, '_')
35
- "Subscriber__#{queue_name_constant}".to_sym
36
23
  end
37
-
38
24
  end
39
-
40
25
  end
@@ -1,25 +1,33 @@
1
- class Chasqui::SidekiqWorker
1
+ module Chasqui
2
+ class SidekiqWorker < Worker
3
+ class << self
2
4
 
3
- class << self
5
+ def namespace
6
+ Sidekiq.redis { |r| r.respond_to?(:namespace) ? r.namespace : nil }
7
+ end
4
8
 
5
- def create(subscriber)
6
- Class.new(Chasqui::SidekiqWorker) do
7
- include Sidekiq::Worker
8
- sidekiq_options queue: subscriber.queue
9
- @subscriber = subscriber
9
+ def create(subscriber)
10
+ find_or_build_worker(subscriber, Chasqui::SidekiqWorker).tap do |worker|
11
+ worker.class_eval do
12
+ include Sidekiq::Worker
13
+ sidekiq_options queue: subscriber.queue
14
+ @subscriber = subscriber
10
15
 
11
- def perform(event)
12
- self.class.subscriber.perform "TODO: redis", event
13
- end
16
+ def perform(event)
17
+ Sidekiq.redis do |r|
18
+ self.class.subscriber.perform r, event
19
+ end
20
+ end
14
21
 
15
- private
22
+ private
16
23
 
17
- def self.subscriber
18
- @subscriber
24
+ def self.subscriber
25
+ @subscriber
26
+ end
27
+ end
19
28
  end
20
29
  end
21
- end
22
30
 
31
+ end
23
32
  end
24
-
25
33
  end
@@ -0,0 +1,34 @@
1
+ module Chasqui
2
+ class Worker
3
+ class << self
4
+
5
+ def namespace
6
+ raise NotImplementedError
7
+ end
8
+
9
+ def create(subscriber)
10
+ raise NotImplementedError
11
+ end
12
+
13
+ def subscriber=(subscriber)
14
+ raise NotImplementedError
15
+ end
16
+
17
+ protected
18
+
19
+ def find_or_build_worker(subscriber, superclass)
20
+ class_name = Chasqui.subscriber_class_name(subscriber.queue)
21
+
22
+ if Chasqui.const_defined? class_name
23
+ Chasqui.logger.warn "redefining subscriber class Chasqui::#{class_name}"
24
+ Chasqui.send :remove_const, class_name
25
+ end
26
+
27
+ Class.new(superclass).tap do |worker|
28
+ Chasqui.const_set class_name, worker
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+ end
data/lib/chasqui.rb CHANGED
@@ -9,6 +9,7 @@ require "chasqui/config"
9
9
  require "chasqui/broker"
10
10
  require "chasqui/multi_broker"
11
11
  require "chasqui/subscriber"
12
+ require "chasqui/workers/worker"
12
13
  require "chasqui/workers/resque_worker"
13
14
  require "chasqui/workers/sidekiq_worker"
14
15
 
@@ -37,8 +38,8 @@ module Chasqui
37
38
 
38
39
  register_subscriber(queue, channel).tap do |sub|
39
40
  sub.evaluate(&block) if block_given?
40
- Chasqui::ResqueWorker.create sub
41
- redis.sadd "subscribers:#{channel}", queue
41
+ worker = create_worker(sub)
42
+ redis.sadd "subscribers:#{channel}", subscriber_id(worker, queue)
42
43
  end
43
44
  end
44
45
 
@@ -46,6 +47,11 @@ module Chasqui
46
47
  subscribers[queue.to_s]
47
48
  end
48
49
 
50
+ def subscriber_class_name(queue)
51
+ queue_name_constant = queue.split(':').last.gsub(/[^\w]/, '_')
52
+ "Subscriber__#{queue_name_constant}".to_sym
53
+ end
54
+
49
55
  def create_worker(subscriber)
50
56
  case config.worker_backend
51
57
  when :resque
@@ -60,6 +66,11 @@ module Chasqui
60
66
 
61
67
  private
62
68
 
69
+ def subscriber_id(worker, queue)
70
+ queue_name = [worker.namespace, 'queue', queue].compact.join(':')
71
+ "#{config.worker_backend}/#{queue_name}"
72
+ end
73
+
63
74
  def register_subscriber(queue, channel)
64
75
  subscribers[queue.to_s] ||= Subscriber.new queue, channel
65
76
  end
@@ -0,0 +1,119 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples 'pubsub' do |namespace, start_workers_method|
4
+
5
+ def events_to_publish
6
+ [
7
+ { event: 'user.signup', data: ['Kelly'] },
8
+ { event: 'account.credit', data: [1337, 'Kelly'] },
9
+ { event: 'account.debit', data: [10, 'Kelly'] },
10
+ { event: 'user.signup', data: ['Travis'] },
11
+ { event: 'account.debit', data: [9000, 'Kelly'] },
12
+ { event: 'user.cancel', data: ['Kelly'] },
13
+ { event: 'account.credit', data: [42, 'Travis'] },
14
+ ]
15
+ end
16
+
17
+ def expected_events
18
+ {
19
+ 'app1' => [
20
+ { event: 'user.signup', data: ['Kelly'] },
21
+ { event: 'user.signup', data: ['Travis'] },
22
+ ],
23
+ 'app2' => [
24
+ { event: 'account.credit', data: [1337, 'Kelly'] },
25
+ { event: 'account.debit', data: [10, 'Kelly'] },
26
+ { event: 'account.debit', data: [9000, 'Kelly'] },
27
+ { event: 'user.cancel', data: ['Kelly'] },
28
+ { event: 'account.credit', data: [42, 'Travis'] },
29
+ ],
30
+ }
31
+ end
32
+
33
+ before do
34
+ @subscriber_queues = %w(app1 app2)
35
+ @redis_url = 'redis://localhost:6379/13'
36
+ @redis = Redis.new url: @redis_url
37
+ @redis.keys('*').each { |k| @redis.del k }
38
+
39
+ Chasqui.configure do |c|
40
+ c.channel = 'integration'
41
+ c.redis = @redis_url
42
+ end
43
+
44
+ @pids = []
45
+ send start_workers_method
46
+
47
+ # Wait for subscribers to register before starting the broker so we don't miss events.
48
+ sleep 1
49
+
50
+ start_chasqui_broker
51
+ end
52
+
53
+ after do
54
+ terminate_child_processes
55
+ end
56
+
57
+ it 'works' do
58
+ events_to_publish.each do |event|
59
+ Chasqui.publish event[:event], *event[:data]
60
+ end
61
+
62
+ begin
63
+ Timeout::timeout(10) do
64
+ expected_events.each do |subscriber_queue, events|
65
+ events.each do |expected|
66
+ _, payload = @redis.blpop "#{namespace}:#{subscriber_queue}:event_log"
67
+ actual = JSON.parse payload
68
+ expect(actual['event']).to eq(expected[:event])
69
+ expect(actual['data']).to eq(expected[:data])
70
+ end
71
+ end
72
+ end
73
+ rescue TimeoutError
74
+ terminate_child_processes
75
+ fail "Failed to process all events in a timely manner."
76
+ end
77
+ end
78
+
79
+ def start_chasqui_broker
80
+ @pids << fork do
81
+ exec './bin/chasqui',
82
+ '--logfile', 'tmp/integration.log',
83
+ '--redis', @redis_url,
84
+ '--debug'
85
+ end
86
+ end
87
+
88
+ def start_resque_workers
89
+ @subscriber_queues.each do |queue|
90
+ @pids << fork do
91
+ ENV['CHASQUI_ENV'] = 'test'
92
+ ENV['QUEUE'] = queue
93
+ ENV['TERM_CHILD'] = '1'
94
+ ENV['INTERVAL'] = '1'
95
+ ENV['REDIS_NAMESPACE'] = "resque:#{queue}"
96
+ ENV['REDIS_URL'] = @redis_url
97
+ exec 'bundle', 'exec', 'rake', 'resque:work'
98
+ end
99
+ end
100
+ end
101
+
102
+ def terminate_child_processes
103
+ Timeout::timeout(10) do
104
+ @pids.each { |pid| kill 'TERM', pid rescue nil }
105
+ end
106
+ rescue TimeoutError
107
+ @pids.each { |pid| kill 'KILL', pid rescue nil }
108
+ fail "One or more child processes failed to terminate in a timely manner"
109
+ end
110
+
111
+ def kill(signal, pid)
112
+ Process.kill signal, pid
113
+ unless signal == 'KILL'
114
+ pid, status = Process.waitpid2 pid, 0
115
+ expect(status.exitstatus).to eq(0)
116
+ end
117
+ end
118
+
119
+ end
@@ -1,84 +1,8 @@
1
1
  require 'spec_helper'
2
+ require 'integration/pubsub_examples'
2
3
 
3
- describe "Chasqui resque integration" do
4
-
5
- PUBLISH_EVENTS = [
6
- { event: 'user.signup', data: ['Kelly'] },
7
- { event: 'account.credit', data: [1337, 'Kelly'] },
8
- { event: 'account.debit', data: [10, 'Kelly'] },
9
- { event: 'user.signup', data: ['Travis'] },
10
- { event: 'account.debit', data: [9000, 'Kelly'] },
11
- { event: 'user.cancel', data: ['Kelly'] },
12
- { event: 'account.credit', data: [42, 'Travis'] },
13
- ]
14
-
15
- EXPECTED_EVENTS = {
16
- 'app1' => [
17
- { event: 'user.signup', data: ['Kelly'] },
18
- { event: 'user.signup', data: ['Travis'] },
19
- ],
20
- 'app2' => [
21
- { event: 'account.credit', data: [1337, 'Kelly'] },
22
- { event: 'account.debit', data: [10, 'Kelly'] },
23
- { event: 'account.debit', data: [9000, 'Kelly'] },
24
- { event: 'user.cancel', data: ['Kelly'] },
25
- { event: 'account.credit', data: [42, 'Travis'] },
26
- ],
27
- }
28
-
29
- before do
30
- @subscriber_queues = %w(app1 app2)
31
- @redis_url = 'redis://localhost:6379/13'
32
- @redis = Redis.new url: @redis_url
33
- @redis.keys('*').each { |k| @redis.del k }
34
-
35
- Chasqui.configure do |c|
36
- c.channel = 'integration'
37
- c.redis = @redis_url
38
- end
39
-
40
- @pids = []
41
- start_resque_workers
42
-
43
- # Wait for subscribers to register before starting the broker so we don't miss events.
44
- sleep 1
45
-
46
- start_chasqui_broker
47
- end
48
-
49
- after do
50
- terminate_child_processes
51
- end
52
-
53
- it 'works' do
54
- PUBLISH_EVENTS.each do |event|
55
- Chasqui.publish event[:event], *event[:data]
56
- end
57
-
58
- begin
59
- Timeout::timeout(10) do
60
- EXPECTED_EVENTS.each do |subscriber_queue, events|
61
- events.each do |expected|
62
- _, payload = @redis.blpop "chasqui:#{subscriber_queue}:event_log"
63
- actual = JSON.parse payload
64
- expect(actual['event']).to eq(expected[:event])
65
- expect(actual['data']).to eq(expected[:data])
66
- end
67
- end
68
- end
69
- rescue TimeoutError
70
- fail "Failed to process all events in a timely manner."
71
- end
72
- end
73
-
74
- def start_chasqui_broker
75
- @pids << fork do
76
- exec './bin/chasqui',
77
- '--logfile', 'tmp/resque-spec.log',
78
- '--redis', @redis_url,
79
- '--debug'
80
- end
81
- end
4
+ describe "resque integration", integration: true do
5
+ include_examples 'pubsub', :resque, :start_resque_workers
82
6
 
83
7
  def start_resque_workers
84
8
  @subscriber_queues.each do |queue|
@@ -86,28 +10,11 @@ describe "Chasqui resque integration" do
86
10
  ENV['CHASQUI_ENV'] = 'test'
87
11
  ENV['QUEUE'] = queue
88
12
  ENV['TERM_CHILD'] = '1'
89
- ENV['INTERVAL'] = '0.1'
13
+ ENV['INTERVAL'] = '1'
90
14
  ENV['REDIS_NAMESPACE'] = "resque:#{queue}"
91
15
  ENV['REDIS_URL'] = @redis_url
92
16
  exec 'bundle', 'exec', 'rake', 'resque:work'
93
17
  end
94
18
  end
95
19
  end
96
-
97
- def terminate_child_processes
98
- Timeout::timeout(10) do
99
- @pids.each { |pid| kill 'TERM', pid rescue nil }
100
- end
101
- rescue TimeoutError
102
- @pids.each { |pid| kill 'KILL', pid rescue nil }
103
- fail "One or more child processes failed to terminate in a timely manner"
104
- end
105
-
106
- def kill(signal, pid)
107
- Process.kill signal, pid
108
- unless signal == 'KILL'
109
- pid, status = Process.waitpid2 pid, 0
110
- expect(status.exitstatus).to eq(0)
111
- end
112
- end
113
20
  end
@@ -0,0 +1,8 @@
1
+ require 'chasqui'
2
+ Chasqui.config.worker_backend = :resque
3
+
4
+ require 'resque'
5
+ Resque.redis = ENV['REDIS_URL'] if ENV['REDIS_URL']
6
+ Resque.redis.namespace = :resque
7
+
8
+ require './spec/integration/setup/subscribers'
@@ -0,0 +1,10 @@
1
+ require 'chasqui'
2
+ require 'sidekiq'
3
+
4
+ Chasqui.config.worker_backend = :sidekiq
5
+
6
+ Sidekiq.configure_server do |config|
7
+ config.redis = { url: ENV['REDIS_URL'], namespace: ENV['REDIS_NAMESPACE'] }
8
+ end
9
+
10
+ require './spec/integration/setup/subscribers'
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+ require 'integration/pubsub_examples'
3
+
4
+ if sidekiq_supported_ruby_version?
5
+ describe "sidekiq integration", integration: true do
6
+ include_examples 'pubsub', :sidekiq, :start_sidekiq_workers
7
+
8
+ def start_sidekiq_workers
9
+ @pids << fork do
10
+ ENV['REDIS_URL'] = @redis_url
11
+ ENV['REDIS_NAMESPACE'] = 'sidekiq'
12
+
13
+ exec 'bundle', 'exec', 'sidekiq',
14
+ '--concurrency', '1',
15
+ '--environment', 'test',
16
+ '--queue', 'app1',
17
+ '--queue', 'app2',
18
+ '--logfile', 'tmp/sidekiq-spec.log',
19
+ '--require', './spec/integration/setup/sidekiq.rb',
20
+ '--verbose'
21
+ end
22
+ end
23
+ end
24
+ end
@@ -7,4 +7,8 @@ describe Chasqui::Broker do
7
7
  it 'forwards events to subscriber queues' do
8
8
  expect(-> { broker.forward_event }).to raise_error(NotImplementedError)
9
9
  end
10
+
11
+ it 'does not use a redis namespace' do
12
+ expect(broker.redis).not_to respond_to(:namespace)
13
+ end
10
14
  end
@@ -2,7 +2,12 @@ require 'spec_helper'
2
2
 
3
3
  describe Chasqui::MultiBroker do
4
4
  let(:broker) { Chasqui::MultiBroker.new }
5
- before { reset_chasqui }
5
+
6
+ before do
7
+ reset_chasqui
8
+ Chasqui.config.worker_backend = :resque
9
+ Resque.redis.namespace = nil
10
+ end
6
11
 
7
12
  describe '#forward_event' do
8
13
  before do
@@ -16,18 +21,18 @@ describe Chasqui::MultiBroker do
16
21
  Chasqui.publish 'foo.bar', 'A'
17
22
  broker.forward_event
18
23
 
19
- expect(redis.llen('inbox')).to eq(0)
24
+ expect(nnredis.llen(broker.inbox_queue)).to eq(0)
20
25
 
21
- expect(redis.llen('queue:queue1')).to eq(1)
22
- expect(redis.llen('queue:queue2')).to eq(0)
23
- expect(redis.llen('queue:queue3')).to eq(1)
26
+ expect(nnredis.llen('queue:queue1')).to eq(1)
27
+ expect(nnredis.llen('queue:queue2')).to eq(0)
28
+ expect(nnredis.llen('queue:queue3')).to eq(1)
24
29
 
25
30
  event = { 'event' => 'foo.bar', 'channel' => 'app1', 'data' => ['A'] }
26
31
 
27
- job1 = JSON.parse redis.rpop('queue:queue1')
32
+ job1 = JSON.parse nnredis.lpop('queue:queue1')
28
33
  expect(job1['args']).to include(event)
29
34
 
30
- job3 = JSON.parse redis.rpop('queue:queue3')
35
+ job3 = JSON.parse nnredis.lpop('queue:queue3')
31
36
  expect(job3['args']).to include(event)
32
37
  end
33
38
 
@@ -40,7 +45,7 @@ describe Chasqui::MultiBroker do
40
45
  Chasqui.config.channel = 'app2'
41
46
  Chasqui.publish 'foo.bar', 'A'
42
47
 
43
- job = JSON.parse redis.brpop('queue:queue2')[1]
48
+ job = JSON.parse nnredis.blpop('queue:queue2')[1]
44
49
  expect(job).to include('class' => 'Chasqui::Subscriber__queue2')
45
50
  expect(job).to include('args' =>
46
51
  [{ 'event' => 'foo.bar', 'channel' => 'app2', 'data' => ['A'] }])
@@ -57,17 +62,22 @@ describe Chasqui::MultiBroker do
57
62
  allow(broker.redis).to receive(:smembers).and_raise(Redis::ConnectionError)
58
63
 
59
64
  expect(-> { broker.forward_event }).to raise_error(Redis::ConnectionError)
60
- expect(redis.llen('queue:queue2')).to eq(0)
61
- expect(redis.llen(broker.in_progress_queue)).to eq(1)
65
+ expect(nnredis.llen('queue:queue2')).to eq(0)
66
+ expect(nnredis.llen(broker.in_progress_queue)).to eq(1)
62
67
 
63
68
  allow(broker.redis).to receive(:smembers).and_call_original
64
69
  broker.forward_event
65
- expect(redis.llen('queue:queue2')).to eq(1)
70
+ expect(nnredis.llen('queue:queue2')).to eq(1)
66
71
 
67
- job = JSON.parse redis.rpop('queue:queue2')
72
+ job = JSON.parse nnredis.lpop('queue:queue2')
68
73
  expect(job['args']).to include(
69
74
  'event' => 'foo', 'channel' => 'app2', 'data' => ['process'])
70
- expect(redis.llen(broker.in_progress_queue)).to eq(0)
75
+ expect(nnredis.llen(broker.in_progress_queue)).to eq(0)
76
+ end
77
+
78
+ it 'works when queue is empty' do
79
+ Chasqui.config.broker_poll_interval = 1
80
+ expect(-> { broker.forward_event }).not_to raise_error
71
81
  end
72
82
  end
73
83
  end
@@ -8,8 +8,9 @@ if sidekiq_supported_ruby_version?
8
8
  describe '.create' do
9
9
  it 'configures a new worker' do
10
10
  worker_class = Chasqui::SidekiqWorker.create(subscriber)
11
- expect(worker_class.included_modules).to include(Sidekiq::Worker)
11
+ expect(worker_class.name).to eq('Chasqui::Subscriber__my_queue')
12
12
  expect(worker_class.sidekiq_options).to include('queue' => 'my-queue')
13
+ expect(worker_class.included_modules).to include(Sidekiq::Worker)
13
14
  expect(worker_class.new).to be_kind_of(Chasqui::SidekiqWorker)
14
15
  end
15
16
  end
@@ -124,39 +124,6 @@ describe Chasqui do
124
124
  end
125
125
  end
126
126
 
127
- describe '.subscribe' do
128
- before { reset_chasqui }
129
-
130
- it 'saves subscriptions' do
131
- sub1 = Chasqui.subscribe queue: 'app1-queue', channel: 'com.example.admin'
132
- sub2 = Chasqui.subscribe queue: 'app2-queue', channel: 'com.example.admin'
133
- sub3 = Chasqui.subscribe queue: 'app1-queue', channel: 'com.example.video'
134
-
135
- queues = Chasqui.redis.smembers "subscribers:com.example.admin"
136
- expect(queues.sort).to eq(['app1-queue', 'app2-queue'])
137
-
138
- queues = Chasqui.redis.smembers "subscribers:com.example.video"
139
- expect(queues).to eq(['app1-queue'])
140
-
141
- expect(Chasqui.subscriber('app1-queue')).to eq(sub1)
142
- expect(Chasqui.subscriber('app2-queue')).to eq(sub2)
143
- expect(sub1).to eq(sub3)
144
- end
145
-
146
- it 'returns a subscriber' do
147
- subscriber = Chasqui.subscribe queue: 'app1-queue', channel: 'com.example.admin'
148
- expect(subscriber).to be_kind_of(Chasqui::Subscriber)
149
- end
150
-
151
- it 'yields a subscriber configuration context' do
152
- $context = nil
153
- Chasqui.subscribe queue: 'foo', channel: 'bar' do
154
- $context = self
155
- end
156
- expect($context).to be_kind_of(Chasqui::Subscriber)
157
- end
158
- end
159
-
160
127
  describe '.create_worker' do
161
128
  let(:subscriber) { j }
162
129
 
@@ -182,4 +149,70 @@ describe Chasqui do
182
149
  end
183
150
  end
184
151
  end
152
+
153
+ describe '.subscribe' do
154
+ before do
155
+ reset_chasqui
156
+ Chasqui.config.worker_backend = :resque
157
+ end
158
+
159
+ context 'resque worker subscriptions' do
160
+ before { Resque.redis.namespace = 'blah' }
161
+
162
+ it 'creates subscriptions using the appropriate redis namespace' do
163
+ sub1 = Chasqui.subscribe queue: 'app1-queue', channel: 'com.example.admin'
164
+ sub2 = Chasqui.subscribe queue: 'app2-queue', channel: 'com.example.admin'
165
+ sub3 = Chasqui.subscribe queue: 'app1-queue', channel: 'com.example.video'
166
+
167
+ queues = Chasqui.redis.smembers "subscribers:com.example.admin"
168
+ expect(queues.sort).to eq(['resque/blah:queue:app1-queue', 'resque/blah:queue:app2-queue'])
169
+
170
+ queues = Chasqui.redis.smembers "subscribers:com.example.video"
171
+ expect(queues).to eq(['resque/blah:queue:app1-queue'])
172
+
173
+ expect(Chasqui.subscriber('app1-queue')).to eq(sub1)
174
+ expect(Chasqui.subscriber('app2-queue')).to eq(sub2)
175
+ expect(sub1).to eq(sub3)
176
+ end
177
+ end
178
+
179
+ if sidekiq_supported_ruby_version?
180
+ context 'sidekiq worker subscriptions' do
181
+ before do
182
+ Chasqui.config.worker_backend = :sidekiq
183
+ end
184
+
185
+ it 'creates subscriptions using the appropriate redis namespace' do
186
+ Chasqui.subscribe queue: 'app1-queue', channel: 'com.example.admin'
187
+ queues = Chasqui.redis.smembers "subscribers:com.example.admin"
188
+ expect(queues.sort).to eq(['sidekiq/queue:app1-queue'])
189
+
190
+ Sidekiq.redis = { url: redis.client.options[:url], namespace: 'foobar' }
191
+ Chasqui.subscribe queue: 'app2-queue', channel: 'com.example.video'
192
+ queues = Chasqui.redis.smembers "subscribers:com.example.video"
193
+ expect(queues.sort).to eq(['sidekiq/foobar:queue:app2-queue'])
194
+ end
195
+ end
196
+ end
197
+
198
+ it 'returns a subscriber' do
199
+ subscriber = Chasqui.subscribe queue: 'app1-queue', channel: 'com.example.admin'
200
+ expect(subscriber).to be_kind_of(Chasqui::Subscriber)
201
+ end
202
+
203
+ it 'yields a subscriber configuration context' do
204
+ $context = nil
205
+ Chasqui.subscribe queue: 'foo', channel: 'bar' do
206
+ $context = self
207
+ end
208
+ expect($context).to be_kind_of(Chasqui::Subscriber)
209
+ end
210
+ end
211
+
212
+ describe '.subscriber_class_name' do
213
+ it 'transforms queue name into a subscribe class name' do
214
+ expect(Chasqui.subscriber_class_name('my-queue')).to eq(:Subscriber__my_queue)
215
+ expect(Chasqui.subscriber_class_name('queue:my-queue')).to eq(:Subscriber__my_queue)
216
+ end
217
+ end
185
218
  end
@@ -7,15 +7,20 @@ module ChasquiSpecHelpers
7
7
 
8
8
  def reset_config
9
9
  Chasqui.instance_variable_set(:@config, nil)
10
- Chasqui.config.logger = open('/dev/null', 'w+')
10
+ Chasqui.config.logger = './tmp/test.log'
11
11
  end
12
12
 
13
13
  def redis
14
14
  Chasqui.redis
15
15
  end
16
16
 
17
+ def redis_no_namespace
18
+ redis.redis
19
+ end
20
+ alias nnredis redis_no_namespace
21
+
17
22
  def flush_redis
18
- redis.keys('*').each { |k| redis.del k }
23
+ nnredis.keys('*').each { |k| nnredis.del k }
19
24
  end
20
25
 
21
26
  end
@@ -1,5 +1,6 @@
1
1
  class FakeLogger
2
2
  attr_accessor :progname
3
+
3
4
  def info(*args, &block)
4
5
  end
5
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chasqui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jordan Bach
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-24 00:00:00.000000000 Z
11
+ date: 2015-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -150,8 +150,13 @@ files:
150
150
  - lib/chasqui/version.rb
151
151
  - lib/chasqui/workers/resque_worker.rb
152
152
  - lib/chasqui/workers/sidekiq_worker.rb
153
+ - lib/chasqui/workers/worker.rb
154
+ - spec/integration/pubsub_examples.rb
153
155
  - spec/integration/resque_spec.rb
154
- - spec/integration/subscribers.rb
156
+ - spec/integration/setup/resque.rb
157
+ - spec/integration/setup/sidekiq.rb
158
+ - spec/integration/setup/subscribers.rb
159
+ - spec/integration/sidekiq_spec.rb
155
160
  - spec/lib/chasqui/broker_spec.rb
156
161
  - spec/lib/chasqui/cli_spec.rb
157
162
  - spec/lib/chasqui/config_spec.rb
@@ -192,8 +197,12 @@ specification_version: 4
192
197
  summary: Chasqui is a simple, lightweight, persistent implementation of the publish-subscribe
193
198
  (pub/sub) messaging pattern for service oriented architectures.
194
199
  test_files:
200
+ - spec/integration/pubsub_examples.rb
195
201
  - spec/integration/resque_spec.rb
196
- - spec/integration/subscribers.rb
202
+ - spec/integration/setup/resque.rb
203
+ - spec/integration/setup/sidekiq.rb
204
+ - spec/integration/setup/subscribers.rb
205
+ - spec/integration/sidekiq_spec.rb
197
206
  - spec/lib/chasqui/broker_spec.rb
198
207
  - spec/lib/chasqui/cli_spec.rb
199
208
  - spec/lib/chasqui/config_spec.rb