evrone-common-amqp 0.0.1
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 +7 -0
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/.travis.yml +8 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +6 -0
- data/bin/amqp_consumers +12 -0
- data/evrone-common-amqp.gemspec +30 -0
- data/lib/evrone/common/amqp.rb +68 -0
- data/lib/evrone/common/amqp/cli.rb +88 -0
- data/lib/evrone/common/amqp/config.rb +74 -0
- data/lib/evrone/common/amqp/consumer.rb +70 -0
- data/lib/evrone/common/amqp/consumer/ack.rb +19 -0
- data/lib/evrone/common/amqp/consumer/configuration.rb +93 -0
- data/lib/evrone/common/amqp/consumer/publish.rb +32 -0
- data/lib/evrone/common/amqp/consumer/subscribe.rb +67 -0
- data/lib/evrone/common/amqp/formatter.rb +109 -0
- data/lib/evrone/common/amqp/mixins/logger.rb +17 -0
- data/lib/evrone/common/amqp/mixins/with_middleware.rb +16 -0
- data/lib/evrone/common/amqp/session.rb +154 -0
- data/lib/evrone/common/amqp/supervisor/threaded.rb +170 -0
- data/lib/evrone/common/amqp/testing.rb +46 -0
- data/lib/evrone/common/amqp/version.rb +7 -0
- data/spec/integration/multi_threaded_spec.rb +83 -0
- data/spec/integration/threaded_supervisor_spec.rb +85 -0
- data/spec/lib/amqp/consumer_spec.rb +281 -0
- data/spec/lib/amqp/formatter_spec.rb +47 -0
- data/spec/lib/amqp/mixins/with_middleware_spec.rb +32 -0
- data/spec/lib/amqp/session_spec.rb +144 -0
- data/spec/lib/amqp/supervisor/threaded_spec.rb +123 -0
- data/spec/lib/amqp_spec.rb +9 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/support/amqp.rb +15 -0
- data/spec/support/ignore_me_error.rb +1 -0
- metadata +175 -0
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Evrone
|
4
|
+
module Common
|
5
|
+
module AMQP
|
6
|
+
class Supervisor::Threaded
|
7
|
+
|
8
|
+
include Common::AMQP::Logger
|
9
|
+
|
10
|
+
POOL_INTERVAL = 0.5
|
11
|
+
|
12
|
+
Task = Struct.new(:object, :method, :id) do
|
13
|
+
|
14
|
+
attr_accessor :thread, :attempt, :start_at
|
15
|
+
|
16
|
+
def alive?
|
17
|
+
!!(thread && thread.alive?)
|
18
|
+
end
|
19
|
+
|
20
|
+
def inspect
|
21
|
+
%{#<Task
|
22
|
+
object=#{object.to_s}
|
23
|
+
method=#{method.inspect}
|
24
|
+
id=#{id.inspect}
|
25
|
+
alive=#{alive?}
|
26
|
+
attempt=#{attempt}
|
27
|
+
start_at=#{start_at}> }.gsub("\n", ' ').gsub(/ +/, ' ').strip
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class SpawnAttemptsLimitReached < ::Exception ; end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
|
35
|
+
@@shutdown = false
|
36
|
+
|
37
|
+
def build(tasks)
|
38
|
+
supervisor = new
|
39
|
+
tasks.each_pair do |k,v|
|
40
|
+
v.times do |n|
|
41
|
+
supervisor.add k, :subscribe, n
|
42
|
+
end
|
43
|
+
end
|
44
|
+
supervisor
|
45
|
+
end
|
46
|
+
|
47
|
+
def resume
|
48
|
+
@@shutdown = false
|
49
|
+
end
|
50
|
+
|
51
|
+
def shutdown?
|
52
|
+
@@shutdown
|
53
|
+
end
|
54
|
+
|
55
|
+
def shutdown
|
56
|
+
@@shutdown = true
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
def initialize
|
62
|
+
self.class.resume
|
63
|
+
@tasks = Array.new
|
64
|
+
end
|
65
|
+
|
66
|
+
def add(object, method, id)
|
67
|
+
@tasks.push Task.new(object, method, id).freeze
|
68
|
+
end
|
69
|
+
|
70
|
+
def size
|
71
|
+
@tasks.size
|
72
|
+
end
|
73
|
+
|
74
|
+
def shutdown?
|
75
|
+
self.class.shutdown?
|
76
|
+
end
|
77
|
+
|
78
|
+
def shutdown
|
79
|
+
self.class.shutdown
|
80
|
+
end
|
81
|
+
|
82
|
+
def run_async
|
83
|
+
Thread.new { run }.tap{|t| t.abort_on_exception = true }
|
84
|
+
end
|
85
|
+
|
86
|
+
def run
|
87
|
+
start_all_threads
|
88
|
+
|
89
|
+
loop do
|
90
|
+
task = @tasks.shift
|
91
|
+
break unless task
|
92
|
+
|
93
|
+
case
|
94
|
+
when shutdown?
|
95
|
+
log_thread_error task
|
96
|
+
when task.alive?
|
97
|
+
@tasks.push task
|
98
|
+
else
|
99
|
+
process_fail task
|
100
|
+
end
|
101
|
+
|
102
|
+
sleep POOL_INTERVAL unless shutdown?
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def process_fail(task)
|
109
|
+
log_thread_error task
|
110
|
+
if check_attempt task
|
111
|
+
@tasks.push create_thread(task, task.attempt + 1)
|
112
|
+
else
|
113
|
+
raise SpawnAttemptsLimitReached
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def start_all_threads
|
118
|
+
started_tasks = Array.new
|
119
|
+
while task = @tasks.shift
|
120
|
+
started_tasks.push create_thread(task, 0)
|
121
|
+
end
|
122
|
+
while task = started_tasks.shift
|
123
|
+
@tasks.push task
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def create_thread(task, attempt)
|
128
|
+
attempt = 0 if reset_attempt?(task)
|
129
|
+
task.dup.tap do |new_task|
|
130
|
+
new_task.thread = Thread.new(new_task) do |t|
|
131
|
+
Thread.current[:consumer_id] = t.id
|
132
|
+
Thread.current[:consumer_name] = t.object.to_s
|
133
|
+
t.object.send t.method
|
134
|
+
end
|
135
|
+
new_task.thread.abort_on_exception = false
|
136
|
+
new_task.attempt = attempt
|
137
|
+
new_task.start_at = Time.now
|
138
|
+
new_task.freeze
|
139
|
+
debug "spawn #{new_task.inspect}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def log_thread_error(task)
|
144
|
+
return unless task.thread
|
145
|
+
|
146
|
+
begin
|
147
|
+
task.thread.value
|
148
|
+
nil
|
149
|
+
rescue Exception => e
|
150
|
+
STDERR.puts "#{e.inspect} in #{task.inspect}"
|
151
|
+
STDERR.puts e.backtrace.join("\n")
|
152
|
+
e
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def reset_attempt?(task)
|
157
|
+
return true unless task.start_at
|
158
|
+
|
159
|
+
interval = 60
|
160
|
+
(task.start_at + interval) < Time.now
|
161
|
+
end
|
162
|
+
|
163
|
+
def check_attempt(task)
|
164
|
+
task.attempt.to_i <= Common::AMQP.config.spawn_attempts.to_i
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require File.expand_path("../../amqp", __FILE__)
|
2
|
+
|
3
|
+
module Evrone
|
4
|
+
module Common
|
5
|
+
module AMQP
|
6
|
+
module Testing
|
7
|
+
|
8
|
+
extend self
|
9
|
+
|
10
|
+
@@messages = []
|
11
|
+
@@exchange_messages = Hash.new { |h,k| h[k] = [] }
|
12
|
+
|
13
|
+
def messages
|
14
|
+
@@messages
|
15
|
+
end
|
16
|
+
|
17
|
+
def exchange_messages
|
18
|
+
@@exchange_messages
|
19
|
+
end
|
20
|
+
|
21
|
+
def clear
|
22
|
+
messages.clear
|
23
|
+
exchange_messages.clear
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module Consumer::Publish
|
28
|
+
alias_method :real_publish, :publish
|
29
|
+
|
30
|
+
def publish(message, options = {})
|
31
|
+
Testing.exchange_messages[exchange_name] << message
|
32
|
+
Testing.messages << message
|
33
|
+
self
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module Consumer
|
38
|
+
module ClassMethods
|
39
|
+
def messages
|
40
|
+
Testing.exchange_messages[exchange_name]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'thread'
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
class Evrone::BobThread
|
6
|
+
include Evrone::Common::AMQP::Consumer
|
7
|
+
|
8
|
+
queue exclusive: true, durable: false
|
9
|
+
exchange auto_delete: true, durable: false
|
10
|
+
ack true
|
11
|
+
|
12
|
+
def perform(payload)
|
13
|
+
$mtest_mutex.synchronize do
|
14
|
+
$mtest_collected << payload
|
15
|
+
ack!
|
16
|
+
sleep 0.1
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Evrone::AliceThread
|
22
|
+
include Evrone::Common::AMQP::Consumer
|
23
|
+
|
24
|
+
queue exclusive: true, durable: false
|
25
|
+
exchange auto_delete: true, durable: false
|
26
|
+
ack true
|
27
|
+
|
28
|
+
def perform(payload)
|
29
|
+
Evrone::BobThread.publish payload, content_type: properties[:content_type]
|
30
|
+
ack!
|
31
|
+
sleep 0.1
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "Run in multithread environment", slow: true, jruby: true do
|
36
|
+
let(:num_messages) { 100 }
|
37
|
+
let(:alice) { Evrone::AliceThread }
|
38
|
+
let(:bob) { Evrone::BobThread }
|
39
|
+
let(:sess) { Evrone::Common::AMQP.open }
|
40
|
+
let(:ch) { sess.conn.create_channel }
|
41
|
+
|
42
|
+
before do
|
43
|
+
$mtest_mutex = Mutex.new
|
44
|
+
$mtest_collected = []
|
45
|
+
end
|
46
|
+
|
47
|
+
after do
|
48
|
+
sess.close
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should be successfuly" do
|
52
|
+
ths = (0..12).map do |i|
|
53
|
+
klass = (i % 2 == 0) ? alice : bob
|
54
|
+
Thread.new do
|
55
|
+
klass.subscribe
|
56
|
+
end
|
57
|
+
end
|
58
|
+
ths.each{|t| t.abort_on_exception = true }
|
59
|
+
sleep 0.5
|
60
|
+
|
61
|
+
num_messages.times do |n|
|
62
|
+
alice.publish "n#{n}", content_type: "text/plain"
|
63
|
+
end
|
64
|
+
|
65
|
+
Timeout.timeout(60) do
|
66
|
+
loop do
|
67
|
+
stop = false
|
68
|
+
$mtest_mutex.synchronize do
|
69
|
+
puts $mtest_collected.size
|
70
|
+
stop = true if $mtest_collected.size >= num_messages
|
71
|
+
end
|
72
|
+
break if stop
|
73
|
+
sleep 2
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
Evrone::Common::AMQP.shutdown
|
78
|
+
Timeout.timeout(10) { ths.map{|i| i.join } }
|
79
|
+
|
80
|
+
expect($mtest_collected.sort).to eq (0...num_messages).map{|i| "n#{i}" }.sort
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'thread'
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
class Evrone::BobThreadWithSupervisor
|
6
|
+
include Evrone::Common::AMQP::Consumer
|
7
|
+
|
8
|
+
class ErrorSimulation < ::Exception ; end
|
9
|
+
|
10
|
+
queue exclusive: true, durable: false
|
11
|
+
exchange auto_delete: true, durable: false
|
12
|
+
ack true
|
13
|
+
|
14
|
+
def perform(payload)
|
15
|
+
$mtest_mutex.synchronize do
|
16
|
+
raise IgnoreMeError if Random.new(delivery_info.delivery_tag.to_i).rand < 0.2
|
17
|
+
$mtest_collected << payload
|
18
|
+
ack!
|
19
|
+
sleep 0.1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Evrone::AliceThreadWithSupervisor
|
25
|
+
include Evrone::Common::AMQP::Consumer
|
26
|
+
|
27
|
+
queue exclusive: true, durable: false
|
28
|
+
exchange auto_delete: true, durable: false
|
29
|
+
ack true
|
30
|
+
|
31
|
+
def perform(payload)
|
32
|
+
Evrone::BobThreadWithSupervisor.publish payload, content_type: properties[:content_type]
|
33
|
+
ack!
|
34
|
+
sleep 0.1
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "Run in multithread environment", slow: true, jruby: true do
|
39
|
+
let(:num_messages) { 100 }
|
40
|
+
let(:alice) { Evrone::AliceThreadWithSupervisor }
|
41
|
+
let(:bob) { Evrone::BobThreadWithSupervisor }
|
42
|
+
let(:sess) { Evrone::Common::AMQP.open }
|
43
|
+
let(:ch) { sess.conn.create_channel }
|
44
|
+
|
45
|
+
before do
|
46
|
+
$mtest_mutex = Mutex.new
|
47
|
+
$mtest_collected = []
|
48
|
+
end
|
49
|
+
|
50
|
+
after do
|
51
|
+
sess.close
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should be successfuly" do
|
55
|
+
|
56
|
+
supervisor = Evrone::Common::AMQP::Supervisor::Threaded.build alice => 6, bob => 6
|
57
|
+
|
58
|
+
supervisor_thread = supervisor.run_async
|
59
|
+
|
60
|
+
sleep 0.5
|
61
|
+
|
62
|
+
num_messages.times do |n|
|
63
|
+
alice.publish "n#{n}", content_type: "text/plain"
|
64
|
+
end
|
65
|
+
|
66
|
+
Timeout.timeout(60) do
|
67
|
+
loop do
|
68
|
+
stop = false
|
69
|
+
$mtest_mutex.synchronize do
|
70
|
+
puts $mtest_collected.size
|
71
|
+
stop = true if $mtest_collected.size >= num_messages
|
72
|
+
end
|
73
|
+
break if stop
|
74
|
+
sleep 2
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
Evrone::Common::AMQP.shutdown
|
79
|
+
supervisor.shutdown
|
80
|
+
Timeout.timeout(10) { supervisor_thread.join }
|
81
|
+
|
82
|
+
expect($mtest_collected.sort).to eq (0...num_messages).map{|i| "n#{i}" }.sort
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -0,0 +1,281 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'timeout'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
class Evrone::TestConsumer
|
6
|
+
include Evrone::Common::AMQP::Consumer
|
7
|
+
|
8
|
+
ack true
|
9
|
+
|
10
|
+
def perform(payload)
|
11
|
+
Thread.current[:collected] ||= []
|
12
|
+
Thread.current[:collected] << payload
|
13
|
+
ack!
|
14
|
+
|
15
|
+
:shutdown if Thread.current[:collected].size == 3
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe Evrone::Common::AMQP::Consumer do
|
20
|
+
|
21
|
+
let(:consumer) { Evrone::TestConsumer.new }
|
22
|
+
let(:consumer_class) { consumer.class }
|
23
|
+
|
24
|
+
subject { consumer }
|
25
|
+
|
26
|
+
before { consumer_class.reset_consumer_configuration! }
|
27
|
+
|
28
|
+
context '(configuration)' do
|
29
|
+
|
30
|
+
subject { consumer_class }
|
31
|
+
|
32
|
+
its(:consumer_name) { should eq 'evrone.test' }
|
33
|
+
its(:config) { should be_an_instance_of(Evrone::Common::AMQP::Config) }
|
34
|
+
|
35
|
+
context "model" do
|
36
|
+
subject { consumer_class.model }
|
37
|
+
|
38
|
+
it "by default should be nil" do
|
39
|
+
expect(subject).to be_nil
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'when set model should be' do
|
43
|
+
consumer_class.model Hash
|
44
|
+
expect(subject).to eq Hash
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "content_type" do
|
49
|
+
|
50
|
+
subject { consumer_class.content_type }
|
51
|
+
|
52
|
+
it "by default should be nil" do
|
53
|
+
expect(subject).to be_nil
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'when set content type should be' do
|
57
|
+
consumer_class.content_type 'foo'
|
58
|
+
expect(subject).to eq 'foo'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "bind_options" do
|
63
|
+
subject { consumer_class.bind_options }
|
64
|
+
|
65
|
+
context "by default should eq {}" do
|
66
|
+
it { should eq ({}) }
|
67
|
+
end
|
68
|
+
|
69
|
+
context "set routing_key" do
|
70
|
+
before { consumer_class.routing_key 'key' }
|
71
|
+
it { should eq({routing_key: 'key'}) }
|
72
|
+
end
|
73
|
+
|
74
|
+
context "set headers" do
|
75
|
+
before { consumer_class.headers 'key' }
|
76
|
+
it { should eq({headers: 'key'}) }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context "ack" do
|
81
|
+
subject { consumer_class.ack }
|
82
|
+
|
83
|
+
it "by default should be false" do
|
84
|
+
expect(subject).to be_false
|
85
|
+
end
|
86
|
+
|
87
|
+
it "when set to true should be true" do
|
88
|
+
consumer_class.ack true
|
89
|
+
expect(subject).to be_true
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context "exchange_name" do
|
94
|
+
subject { consumer_class.exchange_name }
|
95
|
+
|
96
|
+
it 'by default should eq consumer_name' do
|
97
|
+
expect(subject).to eq consumer_class.consumer_name
|
98
|
+
end
|
99
|
+
|
100
|
+
it "when set name should be" do
|
101
|
+
consumer_class.exchange :foo
|
102
|
+
expect(subject).to eq :foo
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context "queue_name" do
|
107
|
+
subject{ consumer_class.queue_name }
|
108
|
+
it 'by default should eq consumer_name' do
|
109
|
+
expect(subject).to eq consumer_class.consumer_name
|
110
|
+
end
|
111
|
+
|
112
|
+
it "when set name should be" do
|
113
|
+
consumer_class.queue :bar
|
114
|
+
expect(subject).to eq :bar
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
%w{ queue exchange }.each do |m|
|
119
|
+
context "#{m}_options" do
|
120
|
+
subject { consumer_class.send "#{m}_options" }
|
121
|
+
it 'by default should eq {}' do
|
122
|
+
expect(subject).to eq({})
|
123
|
+
end
|
124
|
+
|
125
|
+
it "when set #{m} options should be" do
|
126
|
+
consumer_class.send(m, durable: true)
|
127
|
+
expect(subject).to eq(durable: true)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
%w{ routing_key headers }.each do |m|
|
133
|
+
context m do
|
134
|
+
subject { consumer_class.send m }
|
135
|
+
|
136
|
+
it 'by default should be nil' do
|
137
|
+
expect(subject).to be_nil
|
138
|
+
end
|
139
|
+
|
140
|
+
it "when set #{m} should be" do
|
141
|
+
consumer_class.send(m, key: :value)
|
142
|
+
expect(subject).to eq(key: :value)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
context "(publish)" do
|
149
|
+
|
150
|
+
context "options" do
|
151
|
+
let(:message) { {"foo" => 1, "bar" => 2} }
|
152
|
+
let(:expected_options) { {} }
|
153
|
+
let(:options) { {} }
|
154
|
+
let(:x) { OpenStruct.new name: "name" }
|
155
|
+
|
156
|
+
subject{ consumer_class.publish message, options }
|
157
|
+
|
158
|
+
before do
|
159
|
+
mock(consumer_class).declare_exchange { x }
|
160
|
+
mock(x).publish(message.to_json, expected_options)
|
161
|
+
end
|
162
|
+
|
163
|
+
context "routing_key" do
|
164
|
+
context "by default" do
|
165
|
+
it { should be }
|
166
|
+
end
|
167
|
+
|
168
|
+
context "when exists in configuration" do
|
169
|
+
let(:expected_options) { { routing_key: 'routing.key' } }
|
170
|
+
before do
|
171
|
+
consumer_class.routing_key 'routing.key'
|
172
|
+
end
|
173
|
+
it { should be }
|
174
|
+
end
|
175
|
+
|
176
|
+
context "when exists in options" do
|
177
|
+
let(:expected_options) { { routing_key: 'routing.key' } }
|
178
|
+
let(:options) { { routing_key: 'routing.key' } }
|
179
|
+
it { should be }
|
180
|
+
end
|
181
|
+
|
182
|
+
context "when exists in options and configuration" do
|
183
|
+
let(:expected_options) { { routing_key: 'options.key' } }
|
184
|
+
let(:options) { { routing_key: 'options.key' } }
|
185
|
+
before do
|
186
|
+
consumer_class.routing_key 'configuration.key'
|
187
|
+
end
|
188
|
+
it { should be }
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
context "headers" do
|
193
|
+
context "by default" do
|
194
|
+
it { should be }
|
195
|
+
end
|
196
|
+
|
197
|
+
context "when exists in configuration" do
|
198
|
+
let(:expected_options) { { headers: 'key' } }
|
199
|
+
before do
|
200
|
+
consumer_class.headers 'key'
|
201
|
+
end
|
202
|
+
it { should be }
|
203
|
+
end
|
204
|
+
|
205
|
+
context "when exists in options" do
|
206
|
+
let(:expected_options) { { headers: 'key' } }
|
207
|
+
let(:options) { { headers: 'key' } }
|
208
|
+
it { should be }
|
209
|
+
end
|
210
|
+
|
211
|
+
context "when exists in options and configuration" do
|
212
|
+
let(:expected_options) { { headers: 'options' } }
|
213
|
+
let(:options) { { headers: 'options' } }
|
214
|
+
before do
|
215
|
+
consumer_class.headers 'configuration'
|
216
|
+
end
|
217
|
+
it { should be }
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
context "real run" do
|
223
|
+
let(:x_name) { consumer_class.exchange_name }
|
224
|
+
let(:q_name) { consumer_class.queue_name }
|
225
|
+
let(:sess) { consumer_class.session.open }
|
226
|
+
let(:ch) { sess.conn.create_channel }
|
227
|
+
let(:q) { sess.declare_queue q_name, channel: ch }
|
228
|
+
let(:x) { sess.declare_exchange x_name, channel: ch }
|
229
|
+
let(:message) { { 'key' => 'value' } }
|
230
|
+
|
231
|
+
after do
|
232
|
+
delete_queue q
|
233
|
+
delete_exchange x
|
234
|
+
sess.close
|
235
|
+
end
|
236
|
+
|
237
|
+
before do
|
238
|
+
q.bind x
|
239
|
+
end
|
240
|
+
|
241
|
+
it "should publish message to exchange using settings from consumer" do
|
242
|
+
consumer_class.publish message
|
243
|
+
sleep 0.25
|
244
|
+
expect(q.message_count).to eq 1
|
245
|
+
_, _, expected = q.pop
|
246
|
+
expect(expected).to eq message.to_json
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
context '(subscribe)' do
|
252
|
+
let(:x_name) { consumer_class.exchange_name }
|
253
|
+
let(:q_name) { consumer_class.queue_name }
|
254
|
+
let(:sess) { consumer_class.session.open }
|
255
|
+
let(:ch) { sess.conn.create_channel }
|
256
|
+
let(:q) { sess.declare_queue q_name, channel: ch }
|
257
|
+
let(:x) { sess.declare_exchange x_name, channel: ch }
|
258
|
+
|
259
|
+
after do
|
260
|
+
delete_queue q
|
261
|
+
delete_exchange x
|
262
|
+
sess.close
|
263
|
+
end
|
264
|
+
|
265
|
+
before do
|
266
|
+
consumer_class.ack true
|
267
|
+
q.bind(x)
|
268
|
+
3.times { |n| x.publish({"n" => n}.to_json, content_type: "application/json") }
|
269
|
+
end
|
270
|
+
|
271
|
+
subject { Thread.current[:collected] }
|
272
|
+
|
273
|
+
it "should receive messages" do
|
274
|
+
Timeout.timeout(3) do
|
275
|
+
consumer_class.subscribe
|
276
|
+
end
|
277
|
+
expect(subject).to have(3).items
|
278
|
+
expect(subject.map(&:values).flatten).to eq [0,1,2]
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|