chasqui 0.1.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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