freddy-jruby 0.4.3
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 +5 -0
- data/.npmignore +8 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +10 -0
- data/Gemfile +8 -0
- data/LICENCE.txt +22 -0
- data/README.md +170 -0
- data/Rakefile +7 -0
- data/freddy.gemspec +35 -0
- data/lib/freddy/adaptive_queue.rb +34 -0
- data/lib/freddy/consumer.rb +77 -0
- data/lib/freddy/delivery.rb +14 -0
- data/lib/freddy/message_handler.rb +19 -0
- data/lib/freddy/message_handlers.rb +68 -0
- data/lib/freddy/producer.rb +42 -0
- data/lib/freddy/request.rb +124 -0
- data/lib/freddy/request_manager.rb +45 -0
- data/lib/freddy/responder_handler.rb +21 -0
- data/lib/freddy/sync_response_container.rb +34 -0
- data/lib/freddy.rb +124 -0
- data/spec/freddy/consumer_spec.rb +25 -0
- data/spec/freddy/error_response_spec.rb +59 -0
- data/spec/freddy/freddy_spec.rb +225 -0
- data/spec/freddy/message_handler_spec.rb +27 -0
- data/spec/freddy/request_spec.rb +41 -0
- data/spec/freddy/responder_handler_spec.rb +22 -0
- data/spec/integration/concurrency_spec.rb +65 -0
- data/spec/integration/logging_spec.rb +35 -0
- data/spec/spec_helper.rb +40 -0
- metadata +168 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Freddy::Consumer do
|
4
|
+
let(:freddy) { Freddy.build(logger, config) }
|
5
|
+
|
6
|
+
let(:destination) { random_destination }
|
7
|
+
let(:payload) { {pay: 'load'} }
|
8
|
+
|
9
|
+
let(:consumer) { freddy.consumer }
|
10
|
+
|
11
|
+
after { freddy.close }
|
12
|
+
|
13
|
+
it 'raises exception when no consumer is provided' do
|
14
|
+
expect { consumer.consume destination }.to raise_error described_class::EmptyConsumer
|
15
|
+
end
|
16
|
+
|
17
|
+
it "doesn't call passed block without any messages" do
|
18
|
+
consumer.consume destination do
|
19
|
+
@message_received = true
|
20
|
+
end
|
21
|
+
default_sleep
|
22
|
+
|
23
|
+
expect(@message_received).to be_falsy
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Freddy::ErrorResponse do
|
4
|
+
subject(:error) { described_class.new(input) }
|
5
|
+
|
6
|
+
context 'with an error type' do
|
7
|
+
let(:input) { {error: 'SomeError'} }
|
8
|
+
|
9
|
+
describe '#response' do
|
10
|
+
subject { error.response }
|
11
|
+
|
12
|
+
it { should eq(input) }
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#message' do
|
16
|
+
subject { error.message }
|
17
|
+
|
18
|
+
it 'uses error type as a message' do
|
19
|
+
should eq('SomeError')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'with an error type and message' do
|
25
|
+
let(:input) { {error: 'SomeError', message: 'extra info'} }
|
26
|
+
|
27
|
+
describe '#response' do
|
28
|
+
subject { error.response }
|
29
|
+
|
30
|
+
it { should eq(input) }
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#message' do
|
34
|
+
subject { error.message }
|
35
|
+
|
36
|
+
it 'uses error type as a message' do
|
37
|
+
should eq('SomeError: extra info')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'without an error type' do
|
43
|
+
let(:input) { {something: 'else'} }
|
44
|
+
|
45
|
+
describe '#response' do
|
46
|
+
subject { error.response }
|
47
|
+
|
48
|
+
it { should eq(input) }
|
49
|
+
end
|
50
|
+
|
51
|
+
describe '#message' do
|
52
|
+
subject { error.message }
|
53
|
+
|
54
|
+
it 'uses default error message as a message' do
|
55
|
+
should eq('Use #response to get the error response')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,225 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Freddy do
|
4
|
+
let(:freddy) { described_class.build(logger, config) }
|
5
|
+
|
6
|
+
let(:destination) { random_destination }
|
7
|
+
let(:destination2) { random_destination }
|
8
|
+
let(:payload) { {pay: 'load'} }
|
9
|
+
|
10
|
+
after { freddy.close }
|
11
|
+
|
12
|
+
def respond_to(&block)
|
13
|
+
freddy.respond_to(destination, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'when making a send-and-forget request' do
|
17
|
+
context 'with timeout' do
|
18
|
+
it 'removes the message from the queue after the timeout' do
|
19
|
+
# Assume that there already is a queue. Otherwise will get an early
|
20
|
+
# return.
|
21
|
+
freddy.channel.queue(destination)
|
22
|
+
|
23
|
+
freddy.deliver(destination, {}, timeout: 0.1)
|
24
|
+
sleep 0.2
|
25
|
+
|
26
|
+
processed_after_timeout = false
|
27
|
+
respond_to { processed_after_timeout = true }
|
28
|
+
default_sleep
|
29
|
+
|
30
|
+
expect(processed_after_timeout).to be(false)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'without timeout' do
|
35
|
+
it 'keeps the message in the queue' do
|
36
|
+
# Assume that there already is a queue. Otherwise will get an early
|
37
|
+
# return.
|
38
|
+
freddy.channel.queue(destination)
|
39
|
+
|
40
|
+
freddy.deliver(destination, {})
|
41
|
+
default_sleep # to ensure everything is properly cleaned
|
42
|
+
|
43
|
+
processed_after_timeout = false
|
44
|
+
respond_to { processed_after_timeout = true }
|
45
|
+
default_sleep
|
46
|
+
|
47
|
+
expect(processed_after_timeout).to be(true)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'when making a synchronized request' do
|
53
|
+
it 'returns response as soon as possible' do
|
54
|
+
respond_to { |payload, msg_handler| msg_handler.success(res: 'yey') }
|
55
|
+
response = freddy.deliver_with_response(destination, {a: 'b'})
|
56
|
+
|
57
|
+
expect(response).to eq(res: 'yey')
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'raises an error if the message was errored' do
|
61
|
+
respond_to { |payload, msg_handler| msg_handler.error(error: 'not today') }
|
62
|
+
|
63
|
+
expect {
|
64
|
+
freddy.deliver_with_response(destination, payload)
|
65
|
+
}.to raise_error(Freddy::InvalidRequestError) {|error|
|
66
|
+
expect(error.response).to eq(error: 'not today')
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'does not leak consumers' do
|
71
|
+
respond_to { |payload, msg_handler| msg_handler.success(res: 'yey') }
|
72
|
+
|
73
|
+
old_count = freddy.channel.consumers.keys.count
|
74
|
+
|
75
|
+
response1 = freddy.deliver_with_response(destination, {a: 'b'})
|
76
|
+
response2 = freddy.deliver_with_response(destination, {a: 'b'})
|
77
|
+
|
78
|
+
expect(response1).to eq(res: 'yey')
|
79
|
+
expect(response2).to eq(res: 'yey')
|
80
|
+
|
81
|
+
new_count = freddy.channel.consumers.keys.count
|
82
|
+
expect(new_count).to be(old_count + 1)
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'responds to the correct requester' do
|
86
|
+
respond_to { |payload, msg_handler| msg_handler.success(res: 'yey') }
|
87
|
+
|
88
|
+
response = freddy.deliver_with_response(destination, payload)
|
89
|
+
expect(response).to eq(res: 'yey')
|
90
|
+
|
91
|
+
expect {
|
92
|
+
freddy.deliver_with_response(destination2, payload)
|
93
|
+
}.to raise_error(Freddy::InvalidRequestError)
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'when queue does not exist' do
|
97
|
+
it 'gives a no route error' do
|
98
|
+
begin
|
99
|
+
Timeout::timeout(0.5) do
|
100
|
+
expect {
|
101
|
+
freddy.deliver_with_response(destination, {a: 'b'}, timeout: 3)
|
102
|
+
}.to raise_error(Freddy::InvalidRequestError) {|error|
|
103
|
+
expect(error.response).to eq(error: 'Specified queue does not exist')
|
104
|
+
}
|
105
|
+
end
|
106
|
+
rescue Timeout::Error
|
107
|
+
fail('Received a timeout error instead of the no route error')
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'on timeout' do
|
113
|
+
it 'gives timeout error' do
|
114
|
+
respond_to { |payload, msg_handler| sleep 0.2 }
|
115
|
+
|
116
|
+
expect {
|
117
|
+
freddy.deliver_with_response(destination, {a: 'b'}, timeout: 0.1)
|
118
|
+
}.to raise_error(Freddy::TimeoutError) {|error|
|
119
|
+
expect(error.response).to eq(error: 'RequestTimeout', message: 'Timed out waiting for response')
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
123
|
+
context 'with delete_on_timeout is set to true' do
|
124
|
+
it 'removes the message from the queue' do
|
125
|
+
# Assume that there already is a queue. Otherwise will get an early
|
126
|
+
# return.
|
127
|
+
freddy.channel.queue(destination)
|
128
|
+
|
129
|
+
expect {
|
130
|
+
freddy.deliver_with_response(destination, {}, timeout: 0.1)
|
131
|
+
}.to raise_error(Freddy::TimeoutError)
|
132
|
+
default_sleep # to ensure everything is properly cleaned
|
133
|
+
|
134
|
+
processed_after_timeout = false
|
135
|
+
respond_to { processed_after_timeout = true }
|
136
|
+
default_sleep
|
137
|
+
|
138
|
+
expect(processed_after_timeout).to be(false)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
context 'with delete_on_timeout is set to false' do
|
143
|
+
it 'removes the message from the queue' do
|
144
|
+
# Assume that there already is a queue. Otherwise will get an early
|
145
|
+
# return.
|
146
|
+
freddy.channel.queue(destination)
|
147
|
+
|
148
|
+
expect {
|
149
|
+
freddy.deliver_with_response(destination, {}, timeout: 0.1, delete_on_timeout: false)
|
150
|
+
}.to raise_error(Freddy::TimeoutError)
|
151
|
+
default_sleep # to ensure everything is properly cleaned
|
152
|
+
|
153
|
+
processed_after_timeout = false
|
154
|
+
respond_to { processed_after_timeout = true }
|
155
|
+
default_sleep
|
156
|
+
|
157
|
+
expect(processed_after_timeout).to be(true)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
describe 'when tapping' do
|
164
|
+
def tap(custom_destination = destination, &block)
|
165
|
+
freddy.tap_into(custom_destination, &block)
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'receives messages' do
|
169
|
+
tap {|msg| @tapped_message = msg }
|
170
|
+
deliver
|
171
|
+
|
172
|
+
wait_for { @tapped_message }
|
173
|
+
expect(@tapped_message).to eq(payload)
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'has the destination' do
|
177
|
+
tap "somebody.*.love" do |message, destination|
|
178
|
+
@destination = destination
|
179
|
+
end
|
180
|
+
deliver "somebody.to.love"
|
181
|
+
|
182
|
+
wait_for { @destination }
|
183
|
+
expect(@destination).to eq("somebody.to.love")
|
184
|
+
end
|
185
|
+
|
186
|
+
it "doesn't consume the message" do
|
187
|
+
tap { @tapped = true }
|
188
|
+
respond_to { @message_received = true }
|
189
|
+
|
190
|
+
deliver
|
191
|
+
|
192
|
+
wait_for { @tapped }
|
193
|
+
wait_for { @message_received }
|
194
|
+
expect(@tapped).to be(true)
|
195
|
+
expect(@message_received).to be(true)
|
196
|
+
end
|
197
|
+
|
198
|
+
it "allows * wildcard" do
|
199
|
+
tap("somebody.*.love") { @tapped = true }
|
200
|
+
|
201
|
+
deliver "somebody.to.love"
|
202
|
+
|
203
|
+
wait_for { @tapped }
|
204
|
+
expect(@tapped).to be(true)
|
205
|
+
end
|
206
|
+
|
207
|
+
it "* matches only one word" do
|
208
|
+
tap("somebody.*.love") { @tapped = true }
|
209
|
+
|
210
|
+
deliver "somebody.not.to.love"
|
211
|
+
|
212
|
+
default_sleep
|
213
|
+
expect(@tapped).to be_falsy
|
214
|
+
end
|
215
|
+
|
216
|
+
it "allows # wildcard" do
|
217
|
+
tap("i.#.free") { @tapped = true }
|
218
|
+
|
219
|
+
deliver "i.want.to.break.free"
|
220
|
+
|
221
|
+
wait_for { @tapped }
|
222
|
+
expect(@tapped).to be(true)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Freddy::MessageHandler do
|
4
|
+
subject(:handler) { described_class.new(adapter, delivery) }
|
5
|
+
|
6
|
+
let(:adapter) { double }
|
7
|
+
let(:delivery) { double(metadata: metadata) }
|
8
|
+
let(:metadata) { double(reply_to: reply_to, correlation_id: 'abc') }
|
9
|
+
|
10
|
+
let(:reply_to) { double }
|
11
|
+
|
12
|
+
describe '#success' do
|
13
|
+
it 'delegates to the adapter' do
|
14
|
+
expect(adapter).to receive(:success).with(reply_to, x: 'y')
|
15
|
+
|
16
|
+
subject.success(x: 'y')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#error' do
|
21
|
+
it 'delegates to the adapter' do
|
22
|
+
expect(adapter).to receive(:error).with(reply_to, error: 'text')
|
23
|
+
|
24
|
+
subject.error(error: 'text')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Freddy::Request do
|
4
|
+
let(:freddy) { Freddy.build(logger, config) }
|
5
|
+
|
6
|
+
let(:destination) { random_destination }
|
7
|
+
let(:payload) { {pay: 'load'} }
|
8
|
+
|
9
|
+
let(:request) { freddy.request }
|
10
|
+
|
11
|
+
after { freddy.close }
|
12
|
+
|
13
|
+
it 'raises empty responder exception when responding without callback' do
|
14
|
+
expect {@responder = request.respond_to destination }.to raise_error described_class::EmptyResponder
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'requesting from multiple threads' do
|
18
|
+
let(:nr_of_threads) { 50 }
|
19
|
+
|
20
|
+
before do
|
21
|
+
freddy.respond_to 'thread-queue' do |payload, msg_handler|
|
22
|
+
msg_handler.success(payload)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'handles multiple threads' do
|
27
|
+
require 'hamster/experimental/mutable_set'
|
28
|
+
msg_counter = Hamster.mutable_set
|
29
|
+
nr_of_threads.times.map do |index|
|
30
|
+
Thread.new do
|
31
|
+
response = freddy.deliver_with_response 'thread-queue', payload
|
32
|
+
msg_counter << index
|
33
|
+
expect(response).to eq(payload)
|
34
|
+
end
|
35
|
+
end.each(&:join)
|
36
|
+
expect(msg_counter.count).to eq(nr_of_threads)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Freddy::ResponderHandler do
|
4
|
+
let(:freddy) { Freddy.build(logger, config) }
|
5
|
+
|
6
|
+
let(:destination) { random_destination }
|
7
|
+
let(:payload) { {pay: 'load'} }
|
8
|
+
|
9
|
+
after { freddy.close }
|
10
|
+
|
11
|
+
it 'can cancel listening for messages' do
|
12
|
+
consumer_handler = freddy.respond_to destination do
|
13
|
+
@messages_count ||= 0
|
14
|
+
@messages_count += 1
|
15
|
+
end
|
16
|
+
deliver
|
17
|
+
consumer_handler.cancel
|
18
|
+
deliver
|
19
|
+
|
20
|
+
expect(@messages_count).to eq 1
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Concurrency' do
|
4
|
+
let(:freddy1) { Freddy.build(logger, config) }
|
5
|
+
let(:freddy2) { Freddy.build(logger, config) }
|
6
|
+
let(:freddy3) { Freddy.build(logger, config) }
|
7
|
+
|
8
|
+
after { [freddy1, freddy2, freddy3].each(&:close) }
|
9
|
+
|
10
|
+
it 'supports multiple requests in #respond_to' do
|
11
|
+
freddy1.respond_to 'Concurrency1' do |payload, msg_handler|
|
12
|
+
begin
|
13
|
+
freddy1.deliver_with_response 'Concurrency2', msg: 'noop'
|
14
|
+
result2 = freddy1.deliver_with_response 'Concurrency3', msg: 'noop'
|
15
|
+
msg_handler.success(result2)
|
16
|
+
rescue Freddy::InvalidRequestError => e
|
17
|
+
msg_handler.error(e.response)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
freddy2.respond_to 'Concurrency2' do |payload, msg_handler|
|
22
|
+
begin
|
23
|
+
msg_handler.success({from: 'Concurrency2'})
|
24
|
+
rescue Freddy::InvalidRequestError => e
|
25
|
+
msg_handler.error(e.response)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
freddy3.respond_to 'Concurrency3' do |payload, msg_handler|
|
30
|
+
msg_handler.success({from: 'Concurrency3'})
|
31
|
+
end
|
32
|
+
|
33
|
+
result =
|
34
|
+
begin
|
35
|
+
freddy1.deliver_with_response 'Concurrency1', msg: 'noop'
|
36
|
+
rescue Freddy::InvalidRequestError => e
|
37
|
+
e.response
|
38
|
+
end
|
39
|
+
|
40
|
+
expect(result).to eq(from: 'Concurrency3')
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'supports nested calls in #tap_into' do
|
44
|
+
received1 = false
|
45
|
+
received2 = false
|
46
|
+
|
47
|
+
freddy1.tap_into 'concurrency.*.queue1' do
|
48
|
+
result = freddy1.deliver_with_response 'TapConcurrency', msg: 'noop'
|
49
|
+
expect(result).to eq(from: 'TapConcurrency')
|
50
|
+
received1 = true
|
51
|
+
end
|
52
|
+
|
53
|
+
freddy2.respond_to 'TapConcurrency' do |payload, msg_handler|
|
54
|
+
msg_handler.success({from: 'TapConcurrency'})
|
55
|
+
received2 = true
|
56
|
+
end
|
57
|
+
|
58
|
+
freddy1.deliver 'concurrency.q.queue1', msg: 'noop'
|
59
|
+
|
60
|
+
wait_for { received1 && received2 }
|
61
|
+
|
62
|
+
expect(received1).to be(true)
|
63
|
+
expect(received2).to be(true)
|
64
|
+
end
|
65
|
+
end
|