shoryuken 6.2.1 → 7.0.2
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/.github/workflows/push.yml +36 -0
- data/.github/workflows/specs.yml +49 -44
- data/.github/workflows/verify-action-pins.yml +16 -0
- data/.gitignore +4 -1
- data/.rspec +3 -1
- data/.rubocop.yml +6 -1
- data/.ruby-version +1 -0
- data/.yard-lint.yml +279 -0
- data/CHANGELOG.md +308 -139
- data/Gemfile +1 -8
- data/Gemfile.lint +9 -0
- data/Gemfile.lint.lock +69 -0
- data/README.md +16 -33
- data/Rakefile +6 -10
- data/bin/clean_sqs +52 -0
- data/bin/cli/base.rb +22 -2
- data/bin/cli/sqs.rb +74 -7
- data/bin/integrations +275 -0
- data/bin/scenario +154 -0
- data/bin/shoryuken +3 -2
- data/docker-compose.yml +6 -0
- data/lib/{shoryuken/extensions/active_job_extensions.rb → active_job/extensions.rb} +20 -6
- data/lib/active_job/queue_adapters/shoryuken_adapter.rb +208 -0
- data/lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter.rb +78 -0
- data/lib/shoryuken/active_job/current_attributes.rb +139 -0
- data/lib/shoryuken/active_job/job_wrapper.rb +28 -0
- data/lib/shoryuken/body_parser.rb +11 -1
- data/lib/shoryuken/client.rb +16 -0
- data/lib/shoryuken/default_exception_handler.rb +11 -0
- data/lib/shoryuken/default_worker_registry.rb +39 -11
- data/lib/shoryuken/environment_loader.rb +85 -15
- data/lib/shoryuken/errors.rb +36 -0
- data/lib/shoryuken/fetcher.rb +41 -3
- data/lib/shoryuken/helpers/atomic_boolean.rb +58 -0
- data/lib/shoryuken/helpers/atomic_counter.rb +104 -0
- data/lib/shoryuken/helpers/atomic_hash.rb +182 -0
- data/lib/shoryuken/helpers/hash_utils.rb +56 -0
- data/lib/shoryuken/helpers/string_utils.rb +65 -0
- data/lib/shoryuken/helpers/timer_task.rb +80 -0
- data/lib/shoryuken/inline_message.rb +22 -0
- data/lib/shoryuken/launcher.rb +55 -0
- data/lib/shoryuken/logging/base.rb +26 -0
- data/lib/shoryuken/logging/pretty.rb +25 -0
- data/lib/shoryuken/logging/without_timestamp.rb +25 -0
- data/lib/shoryuken/logging.rb +43 -15
- data/lib/shoryuken/manager.rb +84 -5
- data/lib/shoryuken/message.rb +116 -1
- data/lib/shoryuken/middleware/chain.rb +141 -43
- data/lib/shoryuken/middleware/entry.rb +30 -0
- data/lib/shoryuken/middleware/server/active_record.rb +10 -0
- data/lib/shoryuken/middleware/server/auto_delete.rb +12 -0
- data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +37 -11
- data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +34 -3
- data/lib/shoryuken/middleware/server/non_retryable_exception.rb +95 -0
- data/lib/shoryuken/middleware/server/timing.rb +13 -0
- data/lib/shoryuken/options.rb +154 -13
- data/lib/shoryuken/polling/base_strategy.rb +127 -0
- data/lib/shoryuken/polling/queue_configuration.rb +103 -0
- data/lib/shoryuken/polling/strict_priority.rb +41 -0
- data/lib/shoryuken/polling/weighted_round_robin.rb +44 -0
- data/lib/shoryuken/processor.rb +37 -3
- data/lib/shoryuken/queue.rb +99 -8
- data/lib/shoryuken/runner.rb +54 -16
- data/lib/shoryuken/util.rb +32 -7
- data/lib/shoryuken/version.rb +4 -1
- data/lib/shoryuken/worker/default_executor.rb +23 -1
- data/lib/shoryuken/worker/inline_executor.rb +33 -2
- data/lib/shoryuken/worker.rb +224 -0
- data/lib/shoryuken/worker_registry.rb +35 -0
- data/lib/shoryuken.rb +27 -38
- data/renovate.json +62 -0
- data/shoryuken.gemspec +8 -4
- data/spec/integration/.rspec +1 -0
- data/spec/integration/active_job/adapter_configuration/configuration_spec.rb +26 -0
- data/spec/integration/active_job/bulk_enqueue/bulk_enqueue_spec.rb +53 -0
- data/spec/integration/active_job/current_attributes/bulk_enqueue_spec.rb +50 -0
- data/spec/integration/active_job/current_attributes/complex_types_spec.rb +55 -0
- data/spec/integration/active_job/current_attributes/empty_context_spec.rb +41 -0
- data/spec/integration/active_job/current_attributes/full_context_spec.rb +63 -0
- data/spec/integration/active_job/current_attributes/partial_context_spec.rb +57 -0
- data/spec/integration/active_job/custom_attributes/number_attributes_spec.rb +37 -0
- data/spec/integration/active_job/custom_attributes/string_attributes_spec.rb +39 -0
- data/spec/integration/active_job/error_handling/job_wrapper_spec.rb +53 -0
- data/spec/integration/active_job/fifo_and_attributes/deduplication_spec.rb +86 -0
- data/spec/integration/active_job/keyword_arguments/keyword_arguments_spec.rb +63 -0
- data/spec/integration/active_job/retry/discard_on_spec.rb +43 -0
- data/spec/integration/active_job/retry/retry_on_spec.rb +36 -0
- data/spec/integration/active_job/roundtrip/roundtrip_spec.rb +52 -0
- data/spec/integration/active_job/scheduled/scheduled_spec.rb +76 -0
- data/spec/integration/active_record_middleware/active_record_middleware_spec.rb +84 -0
- data/spec/integration/auto_delete/auto_delete_spec.rb +53 -0
- data/spec/integration/auto_extend_visibility/auto_extend_visibility_spec.rb +57 -0
- data/spec/integration/aws_config/aws_config_spec.rb +59 -0
- data/spec/integration/batch_processing/batch_processing_spec.rb +37 -0
- data/spec/integration/body_parser/json_parser_spec.rb +45 -0
- data/spec/integration/body_parser/proc_parser_spec.rb +54 -0
- data/spec/integration/body_parser/text_parser_spec.rb +43 -0
- data/spec/integration/concurrent_processing/concurrent_processing_spec.rb +45 -0
- data/spec/integration/custom_group_polling_strategy/custom_group_polling_strategy_spec.rb +87 -0
- data/spec/integration/dead_letter_queue/dead_letter_queue_spec.rb +91 -0
- data/spec/integration/exception_handlers/exception_handlers_spec.rb +69 -0
- data/spec/integration/exponential_backoff/exponential_backoff_spec.rb +67 -0
- data/spec/integration/fifo_ordering/fifo_ordering_spec.rb +44 -0
- data/spec/integration/large_payloads/large_payloads_spec.rb +30 -0
- data/spec/integration/launcher/launcher_spec.rb +40 -0
- data/spec/integration/message_attributes/message_attributes_spec.rb +54 -0
- data/spec/integration/message_operations/message_operations_spec.rb +59 -0
- data/spec/integration/middleware_chain/empty_chain_spec.rb +11 -0
- data/spec/integration/middleware_chain/execution_order_spec.rb +33 -0
- data/spec/integration/middleware_chain/removal_spec.rb +31 -0
- data/spec/integration/middleware_chain/short_circuit_spec.rb +40 -0
- data/spec/integration/non_retryable_exception/non_retryable_exception_spec.rb +149 -0
- data/spec/integration/polling_strategies/polling_strategies_spec.rb +46 -0
- data/spec/integration/queue_operations/queue_operations_spec.rb +84 -0
- data/spec/integration/rails/rails_72/Gemfile +6 -0
- data/spec/integration/rails/rails_72/activejob_adapter_spec.rb +98 -0
- data/spec/integration/rails/rails_80/Gemfile +6 -0
- data/spec/integration/rails/rails_80/activejob_adapter_spec.rb +98 -0
- data/spec/integration/rails/rails_80/continuation_spec.rb +79 -0
- data/spec/integration/rails/rails_81/Gemfile +6 -0
- data/spec/integration/rails/rails_81/activejob_adapter_spec.rb +98 -0
- data/spec/integration/rails/rails_81/continuation_spec.rb +79 -0
- data/spec/integration/retry_behavior/retry_behavior_spec.rb +45 -0
- data/spec/integration/spec_helper.rb +7 -0
- data/spec/integration/strict_priority_polling/strict_priority_polling_spec.rb +58 -0
- data/spec/integration/visibility_timeout/visibility_timeout_spec.rb +37 -0
- data/spec/integration/worker_enqueueing/worker_enqueueing_spec.rb +60 -0
- data/spec/integration/worker_groups/worker_groups_spec.rb +79 -0
- data/spec/integration/worker_lifecycle/worker_lifecycle_spec.rb +33 -0
- data/spec/integrations_helper.rb +243 -0
- data/spec/lib/active_job/extensions_spec.rb +225 -0
- data/spec/lib/active_job/queue_adapters/shoryuken_adapter_spec.rb +29 -0
- data/spec/{shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb → lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter_spec.rb} +5 -4
- data/spec/{shoryuken/extensions/active_job_wrapper_spec.rb → lib/shoryuken/active_job/job_wrapper_spec.rb} +6 -5
- data/spec/{shoryuken → lib/shoryuken}/body_parser_spec.rb +2 -4
- data/spec/{shoryuken → lib/shoryuken}/client_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/default_exception_handler_spec.rb +9 -10
- data/spec/{shoryuken → lib/shoryuken}/default_worker_registry_spec.rb +1 -2
- data/spec/{shoryuken → lib/shoryuken}/environment_loader_spec.rb +10 -9
- data/spec/{shoryuken → lib/shoryuken}/fetcher_spec.rb +23 -26
- data/spec/lib/shoryuken/helpers/atomic_boolean_spec.rb +196 -0
- data/spec/lib/shoryuken/helpers/atomic_counter_spec.rb +177 -0
- data/spec/lib/shoryuken/helpers/atomic_hash_spec.rb +307 -0
- data/spec/lib/shoryuken/helpers/hash_utils_spec.rb +145 -0
- data/spec/lib/shoryuken/helpers/string_utils_spec.rb +124 -0
- data/spec/lib/shoryuken/helpers/timer_task_spec.rb +298 -0
- data/spec/lib/shoryuken/helpers_integration_spec.rb +96 -0
- data/spec/lib/shoryuken/inline_message_spec.rb +196 -0
- data/spec/{shoryuken → lib/shoryuken}/launcher_spec.rb +23 -2
- data/spec/lib/shoryuken/logging_spec.rb +242 -0
- data/spec/{shoryuken → lib/shoryuken}/manager_spec.rb +1 -2
- data/spec/lib/shoryuken/message_spec.rb +109 -0
- data/spec/{shoryuken → lib/shoryuken}/middleware/chain_spec.rb +1 -1
- data/spec/lib/shoryuken/middleware/entry_spec.rb +68 -0
- data/spec/lib/shoryuken/middleware/server/active_record_spec.rb +133 -0
- data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_delete_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_extend_visibility_spec.rb +51 -1
- data/spec/{shoryuken → lib/shoryuken}/middleware/server/exponential_backoff_retry_spec.rb +1 -1
- data/spec/lib/shoryuken/middleware/server/non_retryable_exception_spec.rb +214 -0
- data/spec/{shoryuken → lib/shoryuken}/middleware/server/timing_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/options_spec.rb +49 -6
- data/spec/lib/shoryuken/polling/base_strategy_spec.rb +280 -0
- data/spec/lib/shoryuken/polling/queue_configuration_spec.rb +195 -0
- data/spec/{shoryuken → lib/shoryuken}/polling/strict_priority_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/polling/weighted_round_robin_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/processor_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/queue_spec.rb +2 -3
- data/spec/{shoryuken → lib/shoryuken}/runner_spec.rb +1 -3
- data/spec/{shoryuken → lib/shoryuken}/util_spec.rb +2 -2
- data/spec/lib/shoryuken/version_spec.rb +17 -0
- data/spec/{shoryuken → lib/shoryuken}/worker/default_executor_spec.rb +1 -1
- data/spec/lib/shoryuken/worker/inline_executor_spec.rb +105 -0
- data/spec/lib/shoryuken/worker_registry_spec.rb +63 -0
- data/spec/{shoryuken → lib/shoryuken}/worker_spec.rb +15 -11
- data/spec/{shoryuken_spec.rb → lib/shoryuken_spec.rb} +1 -1
- data/spec/shared_examples_for_active_job.rb +40 -15
- data/spec/spec_helper.rb +48 -2
- metadata +295 -101
- data/.codeclimate.yml +0 -20
- data/.devcontainer/Dockerfile +0 -17
- data/.devcontainer/base.Dockerfile +0 -43
- data/.devcontainer/devcontainer.json +0 -35
- data/.github/FUNDING.yml +0 -12
- data/.github/dependabot.yml +0 -6
- data/.github/workflows/stale.yml +0 -20
- data/.reek.yml +0 -5
- data/Appraisals +0 -42
- data/gemfiles/.gitignore +0 -1
- data/gemfiles/aws_sdk_core_2.gemfile +0 -21
- data/gemfiles/rails_4_2.gemfile +0 -20
- data/gemfiles/rails_5_2.gemfile +0 -21
- data/gemfiles/rails_6_0.gemfile +0 -21
- data/gemfiles/rails_6_1.gemfile +0 -21
- data/gemfiles/rails_7_0.gemfile +0 -22
- data/lib/shoryuken/core_ext.rb +0 -69
- data/lib/shoryuken/extensions/active_job_adapter.rb +0 -103
- data/lib/shoryuken/extensions/active_job_concurrent_send_adapter.rb +0 -50
- data/lib/shoryuken/polling/base.rb +0 -67
- data/shoryuken.jpg +0 -0
- data/spec/integration/launcher_spec.rb +0 -128
- data/spec/shoryuken/core_ext_spec.rb +0 -40
- data/spec/shoryuken/extensions/active_job_adapter_spec.rb +0 -7
- data/spec/shoryuken/extensions/active_job_base_spec.rb +0 -84
- data/spec/shoryuken/worker/inline_executor_spec.rb +0 -49
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Shoryuken::Polling::BaseStrategy do
|
|
4
|
+
# Create a concrete implementation for testing
|
|
5
|
+
let(:test_strategy_class) do
|
|
6
|
+
Class.new(described_class) do
|
|
7
|
+
def initialize(queues, delay = nil)
|
|
8
|
+
@queues = queues
|
|
9
|
+
@delay = delay
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def next_queue
|
|
13
|
+
@queues.first
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def messages_found(queue, messages_found)
|
|
17
|
+
# Test implementation - store the call
|
|
18
|
+
@last_messages_found = { queue: queue, count: messages_found }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def active_queues
|
|
22
|
+
@queues
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
attr_reader :last_messages_found
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
let(:strategy) { test_strategy_class.new(['queue1', 'queue2']) }
|
|
30
|
+
|
|
31
|
+
describe 'abstract interface' do
|
|
32
|
+
it 'includes Util module' do
|
|
33
|
+
expect(described_class.included_modules).to include(Shoryuken::Util)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
describe '#next_queue' do
|
|
37
|
+
it 'raises NotImplementedError in base class' do
|
|
38
|
+
base_strategy = described_class.new
|
|
39
|
+
|
|
40
|
+
expect { base_strategy.next_queue }.to raise_error(NotImplementedError)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'can be implemented by subclasses' do
|
|
44
|
+
expect(strategy.next_queue).to eq('queue1')
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe '#messages_found' do
|
|
49
|
+
it 'raises NotImplementedError in base class' do
|
|
50
|
+
base_strategy = described_class.new
|
|
51
|
+
|
|
52
|
+
expect { base_strategy.messages_found('queue', 5) }.to raise_error(NotImplementedError)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'can be implemented by subclasses' do
|
|
56
|
+
strategy.messages_found('test_queue', 3)
|
|
57
|
+
|
|
58
|
+
expect(strategy.last_messages_found).to eq({ queue: 'test_queue', count: 3 })
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'accepts zero messages found' do
|
|
62
|
+
strategy.messages_found('empty_queue', 0)
|
|
63
|
+
|
|
64
|
+
expect(strategy.last_messages_found).to eq({ queue: 'empty_queue', count: 0 })
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
describe '#message_processed' do
|
|
69
|
+
it 'has default empty implementation' do
|
|
70
|
+
base_strategy = described_class.new
|
|
71
|
+
|
|
72
|
+
expect { base_strategy.message_processed('queue') }.not_to raise_error
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'can be overridden by subclasses' do
|
|
76
|
+
# Default implementation should do nothing
|
|
77
|
+
expect { strategy.message_processed('queue') }.not_to raise_error
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
describe '#active_queues' do
|
|
82
|
+
it 'raises NotImplementedError in base class' do
|
|
83
|
+
base_strategy = described_class.new
|
|
84
|
+
|
|
85
|
+
expect { base_strategy.active_queues }.to raise_error(NotImplementedError)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it 'can be implemented by subclasses' do
|
|
89
|
+
expect(strategy.active_queues).to eq(['queue1', 'queue2'])
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
describe '#==' do
|
|
95
|
+
context 'when comparing with Array' do
|
|
96
|
+
it 'compares against @queues instance variable' do
|
|
97
|
+
expect(strategy).to eq(['queue1', 'queue2'])
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it 'returns false for different arrays' do
|
|
101
|
+
expect(strategy).not_to eq(['queue3', 'queue4'])
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it 'returns false for arrays with different order' do
|
|
105
|
+
expect(strategy).not_to eq(['queue2', 'queue1'])
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
context 'when comparing with another strategy' do
|
|
110
|
+
it 'compares active_queues when other responds to active_queues' do
|
|
111
|
+
other_strategy = test_strategy_class.new(['queue1', 'queue2'])
|
|
112
|
+
|
|
113
|
+
expect(strategy).to eq(other_strategy)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it 'returns false when active_queues differ' do
|
|
117
|
+
other_strategy = test_strategy_class.new(['queue3', 'queue4'])
|
|
118
|
+
|
|
119
|
+
expect(strategy).not_to eq(other_strategy)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it 'handles strategies with different active_queues order' do
|
|
123
|
+
other_strategy = test_strategy_class.new(['queue2', 'queue1'])
|
|
124
|
+
|
|
125
|
+
expect(strategy).not_to eq(other_strategy)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
context 'when comparing with objects that do not respond to active_queues' do
|
|
130
|
+
it 'returns false for strings' do
|
|
131
|
+
expect(strategy).not_to eq('some_string')
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it 'returns false for numbers' do
|
|
135
|
+
expect(strategy).not_to eq(123)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
it 'returns false for hashes' do
|
|
139
|
+
expect(strategy).not_to eq({ queues: ['queue1', 'queue2'] })
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
it 'returns false for objects without active_queues method' do
|
|
143
|
+
plain_object = Object.new
|
|
144
|
+
|
|
145
|
+
expect(strategy).not_to eq(plain_object)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
context 'when @queues is not set' do
|
|
150
|
+
let(:strategy_without_queues) { test_strategy_class.new(nil) }
|
|
151
|
+
|
|
152
|
+
it 'handles nil @queues when comparing with nil' do
|
|
153
|
+
# nil is not an Array, so this goes to the else branch
|
|
154
|
+
# nil doesn't respond to active_queues, so it returns false
|
|
155
|
+
expect(strategy_without_queues == nil).to be false
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it 'handles nil @queues when comparing with empty array' do
|
|
159
|
+
expect(strategy_without_queues == []).to be false
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
describe '#delay' do
|
|
165
|
+
context 'when delay is set on strategy' do
|
|
166
|
+
let(:strategy_with_delay) { test_strategy_class.new(['queue1'], 5.0) }
|
|
167
|
+
|
|
168
|
+
it 'returns the strategy-specific delay' do
|
|
169
|
+
expect(strategy_with_delay.delay).to eq(5.0)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
context 'when delay is not set on strategy' do
|
|
174
|
+
before do
|
|
175
|
+
allow(Shoryuken.options).to receive(:[]).with(:delay).and_return(3.5)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it 'returns the global Shoryuken delay converted to float' do
|
|
179
|
+
expect(strategy.delay).to eq(3.5)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
context 'when global delay is a string' do
|
|
184
|
+
before do
|
|
185
|
+
allow(Shoryuken.options).to receive(:[]).with(:delay).and_return('2.5')
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it 'converts string delay to float' do
|
|
189
|
+
expect(strategy.delay).to eq(2.5)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
context 'when global delay is an integer' do
|
|
194
|
+
before do
|
|
195
|
+
allow(Shoryuken.options).to receive(:[]).with(:delay).and_return(4)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
it 'converts integer delay to float' do
|
|
199
|
+
expect(strategy.delay).to eq(4.0)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
context 'when delay is explicitly set to nil' do
|
|
204
|
+
let(:strategy_with_nil_delay) { test_strategy_class.new(['queue1'], nil) }
|
|
205
|
+
|
|
206
|
+
before do
|
|
207
|
+
allow(Shoryuken.options).to receive(:[]).with(:delay).and_return(1.5)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
it 'falls back to global delay' do
|
|
211
|
+
expect(strategy_with_nil_delay.delay).to eq(1.5)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
describe 'inheritance patterns' do
|
|
217
|
+
it 'allows subclasses to call super for implemented methods' do
|
|
218
|
+
subclass = Class.new(described_class) do
|
|
219
|
+
def next_queue
|
|
220
|
+
begin
|
|
221
|
+
super
|
|
222
|
+
rescue NotImplementedError
|
|
223
|
+
'fallback'
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def active_queues
|
|
228
|
+
[]
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def messages_found(queue, count)
|
|
232
|
+
# Implementation required
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
instance = subclass.new
|
|
237
|
+
expect(instance.next_queue).to eq('fallback')
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
it 'supports method chaining in subclasses' do
|
|
241
|
+
chainable_strategy = Class.new(described_class) do
|
|
242
|
+
def initialize(queues)
|
|
243
|
+
@queues = queues
|
|
244
|
+
@call_chain = []
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def next_queue
|
|
248
|
+
@call_chain << :next_queue
|
|
249
|
+
@queues.first
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def messages_found(queue, count)
|
|
253
|
+
@call_chain << :messages_found
|
|
254
|
+
self
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def active_queues
|
|
258
|
+
@call_chain << :active_queues
|
|
259
|
+
@queues
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
attr_reader :call_chain
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
strategy = chainable_strategy.new(['test'])
|
|
266
|
+
result = strategy.messages_found('queue', 1)
|
|
267
|
+
|
|
268
|
+
expect(result).to be(strategy)
|
|
269
|
+
expect(strategy.call_chain).to eq([:messages_found])
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
describe 'utility method access' do
|
|
274
|
+
it 'provides access to utility methods through Util module' do
|
|
275
|
+
# Verify that utility methods are available
|
|
276
|
+
expect(strategy).to respond_to(:unparse_queues)
|
|
277
|
+
expect(strategy).to respond_to(:logger)
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
end
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Shoryuken::Polling::QueueConfiguration do
|
|
4
|
+
describe '#initialize' do
|
|
5
|
+
it 'creates configuration with name and options' do
|
|
6
|
+
config = described_class.new('test_queue', { priority: :high })
|
|
7
|
+
|
|
8
|
+
expect(config.name).to eq('test_queue')
|
|
9
|
+
expect(config.options).to eq({ priority: :high })
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'creates configuration with empty options' do
|
|
13
|
+
config = described_class.new('simple_queue', {})
|
|
14
|
+
|
|
15
|
+
expect(config.name).to eq('simple_queue')
|
|
16
|
+
expect(config.options).to eq({})
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'accepts nil options' do
|
|
20
|
+
config = described_class.new('queue', nil)
|
|
21
|
+
|
|
22
|
+
expect(config.name).to eq('queue')
|
|
23
|
+
expect(config.options).to be_nil
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
describe '#hash' do
|
|
28
|
+
it 'returns hash based on queue name' do
|
|
29
|
+
config1 = described_class.new('queue', {})
|
|
30
|
+
config2 = described_class.new('queue', { weight: 5 })
|
|
31
|
+
|
|
32
|
+
expect(config1.hash).to eq(config2.hash)
|
|
33
|
+
expect(config1.hash).to eq('queue'.hash)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'returns different hash for different queue names' do
|
|
37
|
+
config1 = described_class.new('queue1', {})
|
|
38
|
+
config2 = described_class.new('queue2', {})
|
|
39
|
+
|
|
40
|
+
expect(config1.hash).not_to eq(config2.hash)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
describe '#==' do
|
|
45
|
+
context 'when comparing with another QueueConfiguration' do
|
|
46
|
+
it 'returns true for same name and options' do
|
|
47
|
+
config1 = described_class.new('queue', { weight: 5 })
|
|
48
|
+
config2 = described_class.new('queue', { weight: 5 })
|
|
49
|
+
|
|
50
|
+
expect(config1).to eq(config2)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'returns false for same name but different options' do
|
|
54
|
+
config1 = described_class.new('queue', { weight: 5 })
|
|
55
|
+
config2 = described_class.new('queue', { weight: 10 })
|
|
56
|
+
|
|
57
|
+
expect(config1).not_to eq(config2)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'returns false for different names' do
|
|
61
|
+
config1 = described_class.new('queue1', {})
|
|
62
|
+
config2 = described_class.new('queue2', {})
|
|
63
|
+
|
|
64
|
+
expect(config1).not_to eq(config2)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
context 'when comparing with a string' do
|
|
69
|
+
it 'returns true when options are empty and names match' do
|
|
70
|
+
config = described_class.new('test_queue', {})
|
|
71
|
+
|
|
72
|
+
expect(config).to eq('test_queue')
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'returns false when options are not empty' do
|
|
76
|
+
config = described_class.new('test_queue', { weight: 5 })
|
|
77
|
+
|
|
78
|
+
expect(config).not_to eq('test_queue')
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it 'returns false when names do not match' do
|
|
82
|
+
config = described_class.new('queue1', {})
|
|
83
|
+
|
|
84
|
+
expect(config).not_to eq('queue2')
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
context 'when comparing with other objects' do
|
|
89
|
+
it 'returns false for non-string, non-QueueConfiguration objects' do
|
|
90
|
+
config = described_class.new('queue', {})
|
|
91
|
+
|
|
92
|
+
expect(config).not_to eq(123)
|
|
93
|
+
expect(config).not_to eq([])
|
|
94
|
+
expect(config).not_to eq({ name: 'queue' })
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
describe '#eql?' do
|
|
100
|
+
it 'behaves the same as ==' do
|
|
101
|
+
config1 = described_class.new('queue', {})
|
|
102
|
+
config2 = described_class.new('queue', {})
|
|
103
|
+
|
|
104
|
+
expect(config1.eql?(config2)).to eq(config1 == config2)
|
|
105
|
+
expect(config1.eql?('queue')).to eq(config1 == 'queue')
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
describe '#to_s' do
|
|
110
|
+
context 'when options are empty' do
|
|
111
|
+
it 'returns just the queue name' do
|
|
112
|
+
config = described_class.new('simple_queue', {})
|
|
113
|
+
|
|
114
|
+
expect(config.to_s).to eq('simple_queue')
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
context 'when options are present' do
|
|
119
|
+
it 'returns detailed representation with options' do
|
|
120
|
+
config = described_class.new('complex_queue', { priority: :high, weight: 5 })
|
|
121
|
+
|
|
122
|
+
expect(config.to_s).to include('#<QueueConfiguration complex_queue options={')
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'handles single option' do
|
|
126
|
+
config = described_class.new('weighted_queue', { weight: 10 })
|
|
127
|
+
|
|
128
|
+
expect(config.to_s).to include('#<QueueConfiguration weighted_queue options={')
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
context 'when options are nil' do
|
|
133
|
+
it 'returns detailed representation' do
|
|
134
|
+
config = described_class.new('nil_options_queue', nil)
|
|
135
|
+
|
|
136
|
+
expect(config.to_s).to eq('#<QueueConfiguration nil_options_queue options=nil>')
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
describe 'struct behavior' do
|
|
142
|
+
it 'provides attribute accessors' do
|
|
143
|
+
config = described_class.new('queue', { weight: 5 })
|
|
144
|
+
|
|
145
|
+
expect(config.name).to eq('queue')
|
|
146
|
+
expect(config.options).to eq({ weight: 5 })
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it 'allows attribute modification' do
|
|
150
|
+
config = described_class.new('queue', {})
|
|
151
|
+
|
|
152
|
+
config.name = 'new_queue'
|
|
153
|
+
config.options = { priority: :low }
|
|
154
|
+
|
|
155
|
+
expect(config.name).to eq('new_queue')
|
|
156
|
+
expect(config.options).to eq({ priority: :low })
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it 'supports array-like access' do
|
|
160
|
+
config = described_class.new('queue', { weight: 5 })
|
|
161
|
+
|
|
162
|
+
expect(config[0]).to eq('queue')
|
|
163
|
+
expect(config[1]).to eq({ weight: 5 })
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
describe 'usage as hash key' do
|
|
168
|
+
it 'can be used as hash keys' do
|
|
169
|
+
config1 = described_class.new('queue', {})
|
|
170
|
+
config2 = described_class.new('queue', {}) # Same config
|
|
171
|
+
|
|
172
|
+
hash = {}
|
|
173
|
+
hash[config1] = 'value1'
|
|
174
|
+
hash[config2] = 'value2'
|
|
175
|
+
|
|
176
|
+
# Same queue name and options should use same hash key
|
|
177
|
+
expect(hash[config1]).to eq('value2')
|
|
178
|
+
expect(hash[config2]).to eq('value2')
|
|
179
|
+
expect(hash.size).to eq(1)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
it 'different queue names create different keys' do
|
|
183
|
+
config1 = described_class.new('queue1', {})
|
|
184
|
+
config2 = described_class.new('queue2', {})
|
|
185
|
+
|
|
186
|
+
hash = {}
|
|
187
|
+
hash[config1] = 'value1'
|
|
188
|
+
hash[config2] = 'value2'
|
|
189
|
+
|
|
190
|
+
expect(hash[config1]).to eq('value1')
|
|
191
|
+
expect(hash[config2]).to eq('value2')
|
|
192
|
+
expect(hash.size).to eq(2)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# rubocop:disable Metrics/BlockLength
|
|
4
3
|
RSpec.describe Shoryuken::Queue do
|
|
5
4
|
let(:credentials) { Aws::Credentials.new('access_key_id', 'secret_access_key') }
|
|
6
5
|
let(:sqs) { Aws::SQS::Client.new(stub_responses: true, credentials: credentials) }
|
|
@@ -30,7 +29,7 @@ RSpec.describe Shoryuken::Queue do
|
|
|
30
29
|
let(:queue_url) { "http://localhost:4576/queue/#{queue_name}" }
|
|
31
30
|
|
|
32
31
|
it 'instantiates by URL and validate the URL' do
|
|
33
|
-
# See https://github.com/
|
|
32
|
+
# See https://github.com/ruby-shoryuken/shoryuken/pull/551
|
|
34
33
|
expect_any_instance_of(described_class).to receive(:fifo?).and_return(false)
|
|
35
34
|
|
|
36
35
|
subject = described_class.new(sqs, queue_url)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
describe 'Shoryuken::Util' do
|
|
4
4
|
subject do
|
|
@@ -26,7 +26,7 @@ describe 'Shoryuken::Util' do
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
let(:message_attributes) do
|
|
29
|
-
{ 'shoryuken_class' => { string_value: ActiveJob::
|
|
29
|
+
{ 'shoryuken_class' => { string_value: 'Shoryuken::ActiveJob::JobWrapper' } }
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
let(:body) do
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Shoryuken::VERSION do
|
|
6
|
+
it 'has a version number' do
|
|
7
|
+
expect(Shoryuken::VERSION).not_to be_nil
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it 'follows semantic versioning format' do
|
|
11
|
+
expect(Shoryuken::VERSION).to match(/^\d+\.\d+\.\d+/)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it 'is a string' do
|
|
15
|
+
expect(Shoryuken::VERSION).to be_a(String)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Shoryuken::Worker::InlineExecutor do
|
|
4
|
+
before do
|
|
5
|
+
Shoryuken.worker_executor = described_class
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
describe '.perform_async' do
|
|
9
|
+
specify do
|
|
10
|
+
expect_any_instance_of(TestWorker).to receive(:perform).with(anything, 'test')
|
|
11
|
+
|
|
12
|
+
TestWorker.perform_async('test')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'properly sets message_attributes' do
|
|
16
|
+
custom_attributes = {
|
|
17
|
+
'custom_key' => { string_value: 'custom_value', data_type: 'String' }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
expect_any_instance_of(TestWorker).to receive(:perform) do |_, sqs_msg, _|
|
|
21
|
+
expect(sqs_msg.message_attributes).to include('shoryuken_class')
|
|
22
|
+
expect(sqs_msg.message_attributes).to include('custom_key')
|
|
23
|
+
expect(sqs_msg.message_attributes['custom_key'][:string_value]).to eq('custom_value')
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
TestWorker.perform_async('test', message_attributes: custom_attributes)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe '.perform_in' do
|
|
31
|
+
specify do
|
|
32
|
+
expect_any_instance_of(TestWorker).to receive(:perform).with(anything, 'test')
|
|
33
|
+
|
|
34
|
+
TestWorker.perform_in(60, 'test')
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'properly passes message_attributes to perform_async' do
|
|
38
|
+
custom_attributes = {
|
|
39
|
+
'custom_key' => { string_value: 'custom_value', data_type: 'String' }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
expect_any_instance_of(TestWorker).to receive(:perform) do |_, sqs_msg, _|
|
|
43
|
+
expect(sqs_msg.message_attributes).to include('shoryuken_class')
|
|
44
|
+
expect(sqs_msg.message_attributes).to include('custom_key')
|
|
45
|
+
expect(sqs_msg.message_attributes['custom_key'][:string_value]).to eq('custom_value')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
TestWorker.perform_in(60, 'test', message_attributes: custom_attributes)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
context 'batch' do
|
|
53
|
+
before do
|
|
54
|
+
TestWorker.get_shoryuken_options['batch'] = true
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
after do
|
|
58
|
+
TestWorker.get_shoryuken_options['batch'] = false
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
describe '.perform_async' do
|
|
62
|
+
specify do
|
|
63
|
+
expect_any_instance_of(TestWorker).to receive(:perform).with(anything, ['test'])
|
|
64
|
+
|
|
65
|
+
TestWorker.perform_async('test')
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'properly passes message_attributes with batch' do
|
|
69
|
+
custom_attributes = {
|
|
70
|
+
'custom_key' => { string_value: 'custom_value', data_type: 'String' }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
expect_any_instance_of(TestWorker).to receive(:perform) do |_, sqs_msgs, _|
|
|
74
|
+
expect(sqs_msgs.first.message_attributes).to include('shoryuken_class')
|
|
75
|
+
expect(sqs_msgs.first.message_attributes).to include('custom_key')
|
|
76
|
+
expect(sqs_msgs.first.message_attributes['custom_key'][:string_value]).to eq('custom_value')
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
TestWorker.perform_async('test', message_attributes: custom_attributes)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
describe '.perform_in' do
|
|
84
|
+
specify do
|
|
85
|
+
expect_any_instance_of(TestWorker).to receive(:perform).with(anything, ['test'])
|
|
86
|
+
|
|
87
|
+
TestWorker.perform_in(60, 'test')
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'properly passes message_attributes with batch' do
|
|
91
|
+
custom_attributes = {
|
|
92
|
+
'custom_key' => { string_value: 'custom_value', data_type: 'String' }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
expect_any_instance_of(TestWorker).to receive(:perform) do |_, sqs_msgs, _|
|
|
96
|
+
expect(sqs_msgs.first.message_attributes).to include('shoryuken_class')
|
|
97
|
+
expect(sqs_msgs.first.message_attributes).to include('custom_key')
|
|
98
|
+
expect(sqs_msgs.first.message_attributes['custom_key'][:string_value]).to eq('custom_value')
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
TestWorker.perform_in(60, 'test', message_attributes: custom_attributes)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|