shoryuken 3.1.1 → 3.2.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 +4 -4
- data/.rubocop.yml +1 -1
- data/.travis.yml +13 -3
- data/CHANGELOG.md +82 -1
- data/Gemfile +5 -2
- data/Gemfile.aws-sdk-core-v2 +13 -0
- data/bin/cli/sqs.rb +8 -4
- data/bin/shoryuken +2 -1
- data/lib/shoryuken/body_parser.rb +27 -0
- data/lib/shoryuken/environment_loader.rb +25 -11
- data/lib/shoryuken/fetcher.rb +40 -13
- data/lib/shoryuken/launcher.rb +4 -20
- data/lib/shoryuken/logging.rb +0 -5
- data/lib/shoryuken/manager.rb +31 -22
- data/lib/shoryuken/middleware/chain.rb +4 -0
- data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +2 -5
- data/lib/shoryuken/middleware/server/timing.rb +12 -14
- data/lib/shoryuken/options.rb +25 -1
- data/lib/shoryuken/processor.rb +9 -21
- data/lib/shoryuken/queue.rb +12 -5
- data/lib/shoryuken/runner.rb +3 -1
- data/lib/shoryuken/util.rb +3 -3
- data/lib/shoryuken/version.rb +1 -1
- data/lib/shoryuken/worker/default_executor.rb +33 -0
- data/lib/shoryuken/worker/inline_executor.rb +28 -0
- data/lib/shoryuken/worker.rb +68 -31
- data/lib/shoryuken.rb +14 -1
- data/shoryuken.gemspec +1 -1
- data/spec/shoryuken/body_parser_spec.rb +89 -0
- data/spec/shoryuken/environment_loader_spec.rb +32 -3
- data/spec/shoryuken/fetcher_spec.rb +61 -9
- data/spec/shoryuken/manager_spec.rb +31 -18
- data/spec/shoryuken/middleware/chain_spec.rb +16 -4
- data/spec/shoryuken/middleware/server/timing_spec.rb +5 -3
- data/spec/shoryuken/options_spec.rb +80 -0
- data/spec/shoryuken/processor_spec.rb +15 -97
- data/spec/shoryuken/queue_spec.rb +43 -32
- data/spec/shoryuken/util_spec.rb +25 -1
- data/spec/shoryuken/worker/default_executor_spec.rb +100 -0
- data/spec/shoryuken/worker/inline_executor_spec.rb +23 -0
- data/spec/shoryuken/worker_spec.rb +32 -91
- data/spec/spec_helper.rb +5 -0
- metadata +15 -5
|
@@ -13,8 +13,9 @@ RSpec.describe Shoryuken::Manager do
|
|
|
13
13
|
let(:polling_strategy) { Shoryuken::Polling::WeightedRoundRobin.new(queues) }
|
|
14
14
|
let(:fetcher) { double Shoryuken::Fetcher }
|
|
15
15
|
let(:concurrency) { 1 }
|
|
16
|
+
let(:executor) { Concurrent::ImmediateExecutor.new }
|
|
16
17
|
|
|
17
|
-
subject { Shoryuken::Manager.new(fetcher, polling_strategy, concurrency) }
|
|
18
|
+
subject { Shoryuken::Manager.new(fetcher, polling_strategy, concurrency, executor) }
|
|
18
19
|
|
|
19
20
|
before do
|
|
20
21
|
allow(fetcher).to receive(:fetch).and_return([])
|
|
@@ -27,9 +28,9 @@ RSpec.describe Shoryuken::Manager do
|
|
|
27
28
|
|
|
28
29
|
describe '#stop' do
|
|
29
30
|
specify do
|
|
30
|
-
allow(subject).to receive(:
|
|
31
|
-
expect(subject).to receive(:dispatch).
|
|
32
|
-
expect(subject).to receive(:
|
|
31
|
+
allow(subject).to receive(:running?).and_return(true, true, false)
|
|
32
|
+
expect(subject).to receive(:dispatch).once.and_call_original
|
|
33
|
+
expect(subject).to receive(:dispatch_loop).twice.and_call_original
|
|
33
34
|
subject.start
|
|
34
35
|
end
|
|
35
36
|
end
|
|
@@ -37,12 +38,12 @@ RSpec.describe Shoryuken::Manager do
|
|
|
37
38
|
describe '#start' do
|
|
38
39
|
before do
|
|
39
40
|
# prevent dispatch loop
|
|
40
|
-
allow(subject).to receive(:
|
|
41
|
+
allow(subject).to receive(:running?).and_return(true, true, false)
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
it 'pauses when there are no active queues' do
|
|
44
45
|
expect(polling_strategy).to receive(:next_queue).and_return(nil)
|
|
45
|
-
expect(subject).to receive(:
|
|
46
|
+
expect(subject).to receive(:dispatch).and_call_original
|
|
46
47
|
subject.start
|
|
47
48
|
end
|
|
48
49
|
|
|
@@ -59,24 +60,36 @@ RSpec.describe Shoryuken::Manager do
|
|
|
59
60
|
end
|
|
60
61
|
|
|
61
62
|
describe '#dispatch' do
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
before do
|
|
64
|
+
allow(subject).to receive(:running?).and_return(true, true, false)
|
|
65
|
+
end
|
|
65
66
|
|
|
66
|
-
|
|
67
|
+
specify do
|
|
68
|
+
message = ['test1']
|
|
69
|
+
messages = [message]
|
|
70
|
+
q = Shoryuken::Polling::QueueConfiguration.new(queue, {})
|
|
71
|
+
|
|
72
|
+
expect(fetcher).to receive(:fetch).with(q, concurrency).and_return(messages)
|
|
73
|
+
expect(subject).to receive(:fire_event).with(:dispatch, false, queue_name: q.name)
|
|
74
|
+
expect(Shoryuken::Processor).to receive(:process).with(q, message)
|
|
67
75
|
expect(Shoryuken.logger).to_not receive(:info)
|
|
68
76
|
|
|
69
77
|
subject.send(:dispatch)
|
|
70
78
|
end
|
|
71
|
-
end
|
|
72
79
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
context 'when batch' do
|
|
81
|
+
specify do
|
|
82
|
+
messages = %w(test1 test2 test3)
|
|
83
|
+
q = Shoryuken::Polling::QueueConfiguration.new(queue, {})
|
|
84
|
+
|
|
85
|
+
expect(fetcher).to receive(:fetch).with(q, described_class::BATCH_LIMIT).and_return(messages)
|
|
86
|
+
expect(subject).to receive(:fire_event).with(:dispatch, false, queue_name: q.name)
|
|
87
|
+
allow(subject).to receive(:batched_queue?).with(q).and_return(true)
|
|
88
|
+
expect(Shoryuken::Processor).to receive(:process).with(q, messages)
|
|
89
|
+
expect(Shoryuken.logger).to_not receive(:info)
|
|
90
|
+
|
|
91
|
+
subject.send(:dispatch)
|
|
92
|
+
end
|
|
80
93
|
end
|
|
81
94
|
end
|
|
82
95
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
|
-
describe Shoryuken::Middleware::Chain do
|
|
3
|
+
RSpec.describe Shoryuken::Middleware::Chain do
|
|
4
4
|
class CustomMiddleware
|
|
5
5
|
def initialize(name, recorder)
|
|
6
6
|
@name = name
|
|
@@ -14,20 +14,32 @@ describe Shoryuken::Middleware::Chain do
|
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
class CustomMiddlewareB < CustomMiddleware; end
|
|
18
|
+
|
|
17
19
|
it 'supports custom middleware' do
|
|
18
20
|
subject.add CustomMiddleware, 1, []
|
|
19
21
|
|
|
20
22
|
expect(CustomMiddleware).to eq subject.entries.last.klass
|
|
21
23
|
end
|
|
22
24
|
|
|
25
|
+
it 'can add middleware to the front of chain' do
|
|
26
|
+
subject.prepend CustomMiddleware, 1, []
|
|
27
|
+
|
|
28
|
+
expect([CustomMiddleware]).to eq subject.entries.map(&:klass)
|
|
29
|
+
|
|
30
|
+
subject.prepend CustomMiddlewareB, 1, []
|
|
31
|
+
|
|
32
|
+
expect([CustomMiddlewareB, CustomMiddleware]).to eq subject.entries.map(&:klass)
|
|
33
|
+
end
|
|
34
|
+
|
|
23
35
|
it 'invokes a middleware' do
|
|
24
36
|
recorder = []
|
|
25
|
-
subject.add CustomMiddleware, '
|
|
37
|
+
subject.add CustomMiddleware, 'custom', recorder
|
|
26
38
|
|
|
27
39
|
final_action = nil
|
|
28
40
|
subject.invoke { final_action = true }
|
|
29
41
|
expect(final_action).to eq true
|
|
30
|
-
expect(recorder).to eq [%w
|
|
42
|
+
expect(recorder).to eq [%w(custom before), %w(custom after)]
|
|
31
43
|
end
|
|
32
44
|
|
|
33
45
|
class NonYieldingMiddleware
|
|
@@ -37,7 +49,7 @@ describe Shoryuken::Middleware::Chain do
|
|
|
37
49
|
it 'allows middleware to abruptly stop processing rest of chain' do
|
|
38
50
|
recorder = []
|
|
39
51
|
subject.add NonYieldingMiddleware
|
|
40
|
-
subject.add CustomMiddleware, '
|
|
52
|
+
subject.add CustomMiddleware, 'custom', recorder
|
|
41
53
|
|
|
42
54
|
final_action = nil
|
|
43
55
|
subject.invoke { final_action = true }
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
|
-
describe Shoryuken::Middleware::Server::Timing do
|
|
3
|
+
RSpec.describe Shoryuken::Middleware::Server::Timing do
|
|
4
4
|
let(:queue) { 'default' }
|
|
5
5
|
let(:sqs_queue) { double Shoryuken::Queue, visibility_timeout: 60 }
|
|
6
6
|
|
|
7
7
|
let(:sqs_msg) do
|
|
8
|
-
double
|
|
8
|
+
double(
|
|
9
|
+
Shoryuken::Message,
|
|
9
10
|
queue_url: queue,
|
|
10
11
|
body: 'test',
|
|
11
12
|
message_id: 'fc754df7-9cc2-4c41-96ca-5996a44b771e'
|
|
13
|
+
)
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
before do
|
|
@@ -28,7 +30,7 @@ describe Shoryuken::Middleware::Server::Timing do
|
|
|
28
30
|
|
|
29
31
|
context 'when exceeded the `visibility_timeout`' do
|
|
30
32
|
it 'logs exceeded' do
|
|
31
|
-
allow(subject).to receive(:elapsed).and_return(
|
|
33
|
+
allow(subject).to receive(:elapsed).and_return(120_000)
|
|
32
34
|
|
|
33
35
|
expect(Shoryuken.logger).to receive(:info) do |&block|
|
|
34
36
|
expect(block.call).to match(/started at/)
|
|
@@ -97,4 +97,84 @@ RSpec.describe Shoryuken::Options do
|
|
|
97
97
|
"and Shoryuken doesn't support a batchable worker for a queue with multiple workers")
|
|
98
98
|
end
|
|
99
99
|
end
|
|
100
|
+
|
|
101
|
+
describe '.polling_strategy' do
|
|
102
|
+
context 'when not set' do
|
|
103
|
+
specify do
|
|
104
|
+
expect(Shoryuken.polling_strategy('default')).to eq Shoryuken::Polling::WeightedRoundRobin
|
|
105
|
+
expect(Shoryuken.polling_strategy('group1')).to eq Shoryuken::Polling::WeightedRoundRobin
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
context 'when set to StrictPriority string' do
|
|
110
|
+
before do
|
|
111
|
+
Shoryuken.options[:polling_strategy] = 'StrictPriority'
|
|
112
|
+
|
|
113
|
+
Shoryuken.options[:groups] = {
|
|
114
|
+
'group1' => {
|
|
115
|
+
polling_strategy: 'StrictPriority'
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
specify do
|
|
121
|
+
expect(Shoryuken.polling_strategy('default')).to eq Shoryuken::Polling::StrictPriority
|
|
122
|
+
expect(Shoryuken.polling_strategy('group1')).to eq Shoryuken::Polling::StrictPriority
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
context 'when set to WeightedRoundRobin string' do
|
|
127
|
+
before do
|
|
128
|
+
Shoryuken.options[:polling_strategy] = 'WeightedRoundRobin'
|
|
129
|
+
|
|
130
|
+
Shoryuken.options[:groups] = {
|
|
131
|
+
'group1' => {
|
|
132
|
+
polling_strategy: 'WeightedRoundRobin'
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
specify do
|
|
138
|
+
expect(Shoryuken.polling_strategy('default')).to eq Shoryuken::Polling::WeightedRoundRobin
|
|
139
|
+
expect(Shoryuken.polling_strategy('group1')).to eq Shoryuken::Polling::WeightedRoundRobin
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
context 'when set to non-existent string' do
|
|
144
|
+
before do
|
|
145
|
+
Shoryuken.options[:polling_strategy] = 'NonExistent1'
|
|
146
|
+
|
|
147
|
+
Shoryuken.options[:groups] = {
|
|
148
|
+
'group1' => {
|
|
149
|
+
polling_strategy: 'NonExistent2'
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
specify do
|
|
155
|
+
expect { Shoryuken.polling_strategy('default') }.to raise_error(ArgumentError)
|
|
156
|
+
expect { Shoryuken.polling_strategy('group1') }.to raise_error(ArgumentError)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
context 'when set to a class' do
|
|
161
|
+
before do
|
|
162
|
+
class Foo < Shoryuken::Polling::BaseStrategy; end
|
|
163
|
+
class Bar < Shoryuken::Polling::BaseStrategy; end
|
|
164
|
+
|
|
165
|
+
Shoryuken.options[:polling_strategy] = Foo
|
|
166
|
+
|
|
167
|
+
Shoryuken.options[:groups] = {
|
|
168
|
+
'group1' => {
|
|
169
|
+
polling_strategy: Bar
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
specify do
|
|
175
|
+
expect(Shoryuken.polling_strategy('default')).to eq Foo
|
|
176
|
+
expect(Shoryuken.polling_strategy('group1')).to eq Bar
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
100
180
|
end
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
|
-
require 'shoryuken/processor'
|
|
3
|
-
require 'shoryuken/manager'
|
|
4
2
|
|
|
5
3
|
RSpec.describe Shoryuken::Processor do
|
|
6
4
|
let(:manager) { double Shoryuken::Manager }
|
|
@@ -8,12 +6,14 @@ RSpec.describe Shoryuken::Processor do
|
|
|
8
6
|
let(:queue) { 'default' }
|
|
9
7
|
|
|
10
8
|
let(:sqs_msg) do
|
|
11
|
-
double
|
|
9
|
+
double(
|
|
10
|
+
Shoryuken::Message,
|
|
12
11
|
queue_url: queue,
|
|
13
12
|
body: 'test',
|
|
14
13
|
message_attributes: {},
|
|
15
14
|
message_id: SecureRandom.uuid,
|
|
16
15
|
receipt_handle: SecureRandom.uuid
|
|
16
|
+
)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
before do
|
|
@@ -25,101 +25,15 @@ RSpec.describe Shoryuken::Processor do
|
|
|
25
25
|
subject { described_class.new(queue, sqs_msg) }
|
|
26
26
|
|
|
27
27
|
describe '#process' do
|
|
28
|
-
it '
|
|
29
|
-
|
|
28
|
+
it 'sets log context' do
|
|
29
|
+
expect(Shoryuken::Logging).to receive(:with_context).with("TestWorker/#{queue}/#{sqs_msg.message_id}")
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
expect_any_instance_of(TestWorker).to receive(:perform).with(sqs_msg, body)
|
|
34
|
-
|
|
35
|
-
allow(sqs_msg).to receive(:body).and_return(JSON.dump(body))
|
|
31
|
+
allow_any_instance_of(TestWorker).to receive(:perform)
|
|
32
|
+
allow(sqs_msg).to receive(:body)
|
|
36
33
|
|
|
37
34
|
subject.process
|
|
38
35
|
end
|
|
39
36
|
|
|
40
|
-
it 'parses the body calling the proc' do
|
|
41
|
-
TestWorker.get_shoryuken_options['body_parser'] = proc { |sqs_msg| "*#{sqs_msg.body}*" }
|
|
42
|
-
|
|
43
|
-
expect_any_instance_of(TestWorker).to receive(:perform).with(sqs_msg, '*test*')
|
|
44
|
-
|
|
45
|
-
allow(sqs_msg).to receive(:body).and_return('test')
|
|
46
|
-
|
|
47
|
-
subject.process
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
it 'parses the body as text' do
|
|
51
|
-
TestWorker.get_shoryuken_options['body_parser'] = :text
|
|
52
|
-
|
|
53
|
-
body = 'test'
|
|
54
|
-
|
|
55
|
-
expect_any_instance_of(TestWorker).to receive(:perform).with(sqs_msg, body)
|
|
56
|
-
|
|
57
|
-
allow(sqs_msg).to receive(:body).and_return(body)
|
|
58
|
-
|
|
59
|
-
subject.process
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
it 'parses calling `.load`' do
|
|
63
|
-
TestWorker.get_shoryuken_options['body_parser'] = Class.new do
|
|
64
|
-
def self.load(*args)
|
|
65
|
-
JSON.load(*args)
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
body = { 'test' => 'hi' }
|
|
70
|
-
|
|
71
|
-
expect_any_instance_of(TestWorker).to receive(:perform).with(sqs_msg, body)
|
|
72
|
-
|
|
73
|
-
allow(sqs_msg).to receive(:body).and_return(JSON.dump(body))
|
|
74
|
-
|
|
75
|
-
subject.process
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
it 'parses calling `.parse`' do
|
|
79
|
-
TestWorker.get_shoryuken_options['body_parser'] = Class.new do
|
|
80
|
-
def self.parse(*args)
|
|
81
|
-
JSON.parse(*args)
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
body = { 'test' => 'hi' }
|
|
86
|
-
|
|
87
|
-
expect_any_instance_of(TestWorker).to receive(:perform).with(sqs_msg, body)
|
|
88
|
-
|
|
89
|
-
allow(sqs_msg).to receive(:body).and_return(JSON.dump(body))
|
|
90
|
-
|
|
91
|
-
subject.process
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
context 'when parse errors' do
|
|
95
|
-
before do
|
|
96
|
-
TestWorker.get_shoryuken_options['body_parser'] = :json
|
|
97
|
-
|
|
98
|
-
allow(sqs_msg).to receive(:body).and_return('invalid JSON')
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
specify do
|
|
102
|
-
expect(subject.logger).to receive(:error).twice
|
|
103
|
-
|
|
104
|
-
expect { subject.process }.
|
|
105
|
-
to raise_error(JSON::ParserError, /unexpected token at 'invalid JSON'/)
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
context 'when `object_type: nil`' do
|
|
110
|
-
it 'parses the body as text' do
|
|
111
|
-
TestWorker.get_shoryuken_options['body_parser'] = nil
|
|
112
|
-
|
|
113
|
-
body = 'test'
|
|
114
|
-
|
|
115
|
-
expect_any_instance_of(TestWorker).to receive(:perform).with(sqs_msg, body)
|
|
116
|
-
|
|
117
|
-
allow(sqs_msg).to receive(:body).and_return(body)
|
|
118
|
-
|
|
119
|
-
subject.process
|
|
120
|
-
end
|
|
121
|
-
end
|
|
122
|
-
|
|
123
37
|
context 'when custom middleware' do
|
|
124
38
|
let(:queue) { 'worker_called_middleware' }
|
|
125
39
|
|
|
@@ -220,15 +134,19 @@ RSpec.describe Shoryuken::Processor do
|
|
|
220
134
|
|
|
221
135
|
context 'when shoryuken_class header' do
|
|
222
136
|
let(:sqs_msg) do
|
|
223
|
-
double
|
|
137
|
+
double(
|
|
138
|
+
Shoryuken::Message,
|
|
224
139
|
queue_url: queue,
|
|
225
140
|
body: 'test',
|
|
226
141
|
message_attributes: {
|
|
227
142
|
'shoryuken_class' => {
|
|
228
143
|
string_value: TestWorker.to_s,
|
|
229
|
-
data_type: 'String'
|
|
230
|
-
|
|
231
|
-
|
|
144
|
+
data_type: 'String'
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
message_id: SecureRandom.uuid,
|
|
148
|
+
receipt_handle: SecureRandom.uuid
|
|
149
|
+
)
|
|
232
150
|
end
|
|
233
151
|
|
|
234
152
|
it 'performs without delete' do
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
|
+
# rubocop:disable Metrics/BlockLength
|
|
3
4
|
RSpec.describe Shoryuken::Queue do
|
|
4
5
|
let(:credentials) { Aws::Credentials.new('access_key_id', 'secret_access_key') }
|
|
5
6
|
let(:sqs) { Aws::SQS::Client.new(stub_responses: true, credentials: credentials) }
|
|
@@ -15,10 +16,12 @@ RSpec.describe Shoryuken::Queue do
|
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
describe '#new' do
|
|
18
|
-
context 'when queue
|
|
19
|
-
|
|
19
|
+
context 'when queue URL supplied' do
|
|
20
|
+
it 'instantiates by URL and validate the URL' do
|
|
21
|
+
expect_any_instance_of(described_class).to receive(:fifo?).and_return(false)
|
|
22
|
+
|
|
23
|
+
subject = described_class.new(sqs, queue_url)
|
|
20
24
|
|
|
21
|
-
specify do
|
|
22
25
|
expect(subject.name).to eq(queue_name)
|
|
23
26
|
end
|
|
24
27
|
end
|
|
@@ -51,7 +54,9 @@ RSpec.describe Shoryuken::Queue do
|
|
|
51
54
|
failure = double(id: 'id', code: 'code', message: '...', sender_fault: false)
|
|
52
55
|
logger = double 'Logger'
|
|
53
56
|
|
|
54
|
-
expect(sqs).to
|
|
57
|
+
expect(sqs).to(
|
|
58
|
+
receive(:delete_message_batch).with(entries: entries, queue_url: queue_url).and_return(double(failed: [failure]))
|
|
59
|
+
)
|
|
55
60
|
expect(subject).to receive(:logger).and_return(logger)
|
|
56
61
|
expect(logger).to receive(:error)
|
|
57
62
|
|
|
@@ -111,36 +116,30 @@ RSpec.describe Shoryuken::Queue do
|
|
|
111
116
|
end
|
|
112
117
|
|
|
113
118
|
describe '#send_messages' do
|
|
114
|
-
before {
|
|
115
|
-
|
|
116
|
-
}
|
|
119
|
+
before { allow(subject).to receive(:fifo?).and_return(false) }
|
|
120
|
+
|
|
117
121
|
it 'accepts SQS request parameters' do
|
|
118
122
|
# https://docs.aws.amazon.com/sdkforruby/api/Aws/SQS/Client.html#send_message_batch-instance_method
|
|
119
|
-
expect(sqs).to
|
|
123
|
+
expect(sqs).to(
|
|
124
|
+
receive(:send_message_batch).with(hash_including(entries: [{id: '0', message_body: 'msg1'}, {id: '1', message_body: 'msg2'}]))
|
|
125
|
+
)
|
|
120
126
|
|
|
121
127
|
subject.send_messages(entries: [{id: '0', message_body: 'msg1'}, {id: '1', message_body: 'msg2'}])
|
|
122
128
|
end
|
|
123
129
|
|
|
124
130
|
it 'accepts an array of messages' do
|
|
125
|
-
options = { entries: [
|
|
126
|
-
message_body: 'msg1',
|
|
127
|
-
delay_seconds: 1,
|
|
128
|
-
|
|
129
|
-
{ id: '1',
|
|
130
|
-
message_body: 'msg2',
|
|
131
|
-
delay_seconds: 1,
|
|
132
|
-
message_attributes: { attr: 'attr2' } }] }
|
|
133
|
-
|
|
131
|
+
options = { entries: [
|
|
132
|
+
{ id: '0', message_body: 'msg1', delay_seconds: 1, message_attributes: { attr: 'attr1' } },
|
|
133
|
+
{ id: '1', message_body: 'msg2', delay_seconds: 1, message_attributes: { attr: 'attr2' } }
|
|
134
|
+
] }
|
|
134
135
|
expect(sqs).to receive(:send_message_batch).with(hash_including(options))
|
|
135
136
|
|
|
136
|
-
subject.send_messages(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
message_attributes: { attr: 'attr2' }
|
|
143
|
-
}])
|
|
137
|
+
subject.send_messages(
|
|
138
|
+
[
|
|
139
|
+
{ message_body: 'msg1', delay_seconds: 1, message_attributes: { attr: 'attr1' } },
|
|
140
|
+
{ message_body: 'msg2', delay_seconds: 1, message_attributes: { attr: 'attr2' } }
|
|
141
|
+
]
|
|
142
|
+
)
|
|
144
143
|
end
|
|
145
144
|
|
|
146
145
|
context 'when FIFO' do
|
|
@@ -170,16 +169,24 @@ RSpec.describe Shoryuken::Queue do
|
|
|
170
169
|
expect(first_entry[:message_deduplication_id]).to eq 'my id'
|
|
171
170
|
end
|
|
172
171
|
|
|
173
|
-
subject.send_messages(
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
172
|
+
subject.send_messages(
|
|
173
|
+
[
|
|
174
|
+
{ message_body: 'msg1',
|
|
175
|
+
message_attributes: { attr: 'attr1' },
|
|
176
|
+
message_group_id: 'my group',
|
|
177
|
+
message_deduplication_id: 'my id' }
|
|
178
|
+
]
|
|
179
|
+
)
|
|
177
180
|
end
|
|
178
181
|
end
|
|
179
182
|
end
|
|
180
183
|
|
|
181
184
|
it 'accepts an array of string' do
|
|
182
|
-
expect(sqs).to
|
|
185
|
+
expect(sqs).to(
|
|
186
|
+
receive(:send_message_batch).with(
|
|
187
|
+
hash_including(entries: [{ id: '0', message_body: 'msg1' }, { id: '1', message_body: 'msg2' }])
|
|
188
|
+
)
|
|
189
|
+
)
|
|
183
190
|
|
|
184
191
|
subject.send_messages(%w(msg1 msg2))
|
|
185
192
|
end
|
|
@@ -189,9 +196,13 @@ RSpec.describe Shoryuken::Queue do
|
|
|
189
196
|
before do
|
|
190
197
|
attribute_response = double 'Aws::SQS::Types::GetQueueAttributesResponse'
|
|
191
198
|
|
|
192
|
-
allow(attribute_response).to
|
|
199
|
+
allow(attribute_response).to(
|
|
200
|
+
receive(:attributes).and_return('FifoQueue' => fifo.to_s, 'ContentBasedDeduplication' => 'true')
|
|
201
|
+
)
|
|
193
202
|
allow(subject).to receive(:url).and_return(queue_url)
|
|
194
|
-
allow(sqs).to
|
|
203
|
+
allow(sqs).to(
|
|
204
|
+
receive(:get_queue_attributes).with(queue_url: queue_url, attribute_names: ['All']).and_return(attribute_response)
|
|
205
|
+
)
|
|
195
206
|
end
|
|
196
207
|
|
|
197
208
|
context 'when queue is FIFO' do
|
data/spec/shoryuken/util_spec.rb
CHANGED
|
@@ -9,7 +9,7 @@ describe 'Shoryuken::Util' do
|
|
|
9
9
|
|
|
10
10
|
describe '#unparse_queues' do
|
|
11
11
|
it 'returns queues and weights' do
|
|
12
|
-
queues = %w
|
|
12
|
+
queues = %w(queue1 queue1 queue2 queue3 queue4 queue4 queue4)
|
|
13
13
|
|
|
14
14
|
expect(subject.unparse_queues(queues)).to eq([['queue1', 2], ['queue2', 1], ['queue3', 1], ['queue4', 3]])
|
|
15
15
|
end
|
|
@@ -26,4 +26,28 @@ describe 'Shoryuken::Util' do
|
|
|
26
26
|
|
|
27
27
|
it 'returns ActiveJob worker name'
|
|
28
28
|
end
|
|
29
|
+
|
|
30
|
+
describe '#fire_event' do
|
|
31
|
+
let(:value_holder) { Object.new }
|
|
32
|
+
let(:callback_without_options) { proc { value_holder.value = :without_options } }
|
|
33
|
+
let(:callback_with_options) { proc { |options| value_holder.value = [:with_options, options] } }
|
|
34
|
+
|
|
35
|
+
after :all do
|
|
36
|
+
Shoryuken.options[:lifecycle_events].delete(:some_event)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'triggers callbacks that do not accept arguments' do
|
|
40
|
+
Shoryuken.options[:lifecycle_events][:some_event] = [callback_without_options]
|
|
41
|
+
|
|
42
|
+
expect(value_holder).to receive(:value=).with(:without_options)
|
|
43
|
+
subject.fire_event(:some_event)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'triggers callbacks that accept an argument' do
|
|
47
|
+
Shoryuken.options[:lifecycle_events][:some_event] = [callback_with_options]
|
|
48
|
+
|
|
49
|
+
expect(value_holder).to receive(:value=).with([:with_options, { my_option: :some_option }])
|
|
50
|
+
subject.fire_event(:some_event, false, my_option: :some_option)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
29
53
|
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Shoryuken::Worker::DefaultExecutor do
|
|
4
|
+
let(:sqs_queue) { double 'SQS Queue' }
|
|
5
|
+
let(:queue) { 'default' }
|
|
6
|
+
|
|
7
|
+
before do
|
|
8
|
+
allow(Shoryuken::Client).to receive(:queues).with(queue).and_return(sqs_queue)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
describe '.perform_in' do
|
|
12
|
+
it 'delays a message' do
|
|
13
|
+
expect(sqs_queue).to receive(:send_message).with(
|
|
14
|
+
message_attributes: {
|
|
15
|
+
'shoryuken_class' => {
|
|
16
|
+
string_value: TestWorker.to_s,
|
|
17
|
+
data_type: 'String'
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
message_body: 'message',
|
|
21
|
+
delay_seconds: 60)
|
|
22
|
+
|
|
23
|
+
TestWorker.perform_in(60, 'message')
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'raises an exception' do
|
|
27
|
+
expect {
|
|
28
|
+
TestWorker.perform_in(901, 'message')
|
|
29
|
+
}.to raise_error 'The maximum allowed delay is 15 minutes'
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe '.perform_at' do
|
|
34
|
+
it 'delays a message' do
|
|
35
|
+
expect(sqs_queue).to receive(:send_message).with(
|
|
36
|
+
message_attributes: {
|
|
37
|
+
'shoryuken_class' => {
|
|
38
|
+
string_value: TestWorker.to_s,
|
|
39
|
+
data_type: 'String'
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
message_body: 'message',
|
|
43
|
+
delay_seconds: 60)
|
|
44
|
+
|
|
45
|
+
TestWorker.perform_in(Time.now + 60, 'message')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'raises an exception' do
|
|
49
|
+
expect {
|
|
50
|
+
TestWorker.perform_in(Time.now + 901, 'message')
|
|
51
|
+
}.to raise_error 'The maximum allowed delay is 15 minutes'
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
describe '.perform_async' do
|
|
56
|
+
it 'enqueues a message' do
|
|
57
|
+
expect(sqs_queue).to receive(:send_message).with(
|
|
58
|
+
message_attributes: {
|
|
59
|
+
'shoryuken_class' => {
|
|
60
|
+
string_value: TestWorker.to_s,
|
|
61
|
+
data_type: 'String'
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
message_body: 'message')
|
|
65
|
+
|
|
66
|
+
TestWorker.perform_async('message')
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'enqueues a message with options' do
|
|
70
|
+
expect(sqs_queue).to receive(:send_message).with(
|
|
71
|
+
delay_seconds: 60,
|
|
72
|
+
message_attributes: {
|
|
73
|
+
'shoryuken_class' => {
|
|
74
|
+
string_value: TestWorker.to_s,
|
|
75
|
+
data_type: 'String'
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
message_body: 'delayed message')
|
|
79
|
+
|
|
80
|
+
TestWorker.perform_async('delayed message', delay_seconds: 60)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'accepts an `queue` option' do
|
|
84
|
+
new_queue = 'some_different_queue'
|
|
85
|
+
|
|
86
|
+
expect(Shoryuken::Client).to receive(:queues).with(new_queue).and_return(sqs_queue)
|
|
87
|
+
|
|
88
|
+
expect(sqs_queue).to receive(:send_message).with(
|
|
89
|
+
message_attributes: {
|
|
90
|
+
'shoryuken_class' => {
|
|
91
|
+
string_value: TestWorker.to_s,
|
|
92
|
+
data_type: 'String'
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
message_body: 'delayed message')
|
|
96
|
+
|
|
97
|
+
TestWorker.perform_async('delayed message', queue: new_queue)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|