shoryuken 7.0.0.alpha2 → 7.0.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/.github/workflows/push.yml +2 -2
- data/.github/workflows/specs.yml +38 -43
- data/.github/workflows/verify-action-pins.yml +1 -1
- data/.gitignore +3 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -1
- data/.yard-lint.yml +279 -0
- data/CHANGELOG.md +69 -1
- data/Gemfile +1 -1
- data/README.md +2 -7
- data/Rakefile +4 -10
- data/bin/clean_localstack +52 -0
- data/bin/cli/base.rb +21 -0
- data/bin/cli/sqs.rb +61 -2
- data/bin/integrations +275 -0
- data/bin/scenario +154 -0
- data/bin/shoryuken +1 -1
- data/lib/{shoryuken/extensions/active_job_extensions.rb → active_job/extensions.rb} +15 -4
- 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 +8 -0
- data/lib/shoryuken/client.rb +14 -0
- data/lib/shoryuken/default_exception_handler.rb +9 -0
- data/lib/shoryuken/default_worker_registry.rb +29 -1
- data/lib/shoryuken/environment_loader.rb +78 -8
- data/lib/shoryuken/errors.rb +33 -0
- data/lib/shoryuken/fetcher.rb +37 -1
- data/lib/shoryuken/helpers/atomic_boolean.rb +19 -5
- data/lib/shoryuken/helpers/timer_task.rb +80 -0
- data/lib/shoryuken/launcher.rb +53 -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 +39 -25
- data/lib/shoryuken/manager.rb +70 -1
- data/lib/shoryuken/message.rb +114 -1
- data/lib/shoryuken/middleware/chain.rb +139 -43
- data/lib/shoryuken/middleware/entry.rb +30 -0
- data/lib/shoryuken/middleware/server/active_record.rb +8 -0
- data/lib/shoryuken/middleware/server/auto_delete.rb +10 -0
- data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +27 -1
- data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +29 -0
- data/lib/shoryuken/middleware/server/timing.rb +11 -0
- data/lib/shoryuken/options.rb +129 -6
- data/lib/shoryuken/polling/base_strategy.rb +1 -0
- data/lib/shoryuken/polling/strict_priority.rb +39 -0
- data/lib/shoryuken/polling/weighted_round_robin.rb +42 -0
- data/lib/shoryuken/processor.rb +32 -1
- data/lib/shoryuken/queue.rb +93 -4
- data/lib/shoryuken/runner.rb +45 -4
- data/lib/shoryuken/util.rb +26 -1
- data/lib/shoryuken/version.rb +2 -1
- data/lib/shoryuken/worker/default_executor.rb +21 -1
- data/lib/shoryuken/worker/inline_executor.rb +24 -0
- data/lib/shoryuken/worker.rb +193 -0
- data/lib/shoryuken/worker_registry.rb +33 -0
- data/lib/shoryuken.rb +18 -6
- data/renovate.json +29 -2
- data/shoryuken.gemspec +2 -1
- 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/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/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/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 +149 -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} +3 -3
- data/spec/{shoryuken/extensions/active_job_wrapper_spec.rb → lib/shoryuken/active_job/job_wrapper_spec.rb} +4 -4
- data/spec/{shoryuken → lib/shoryuken}/environment_loader_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/helpers/hash_utils_spec.rb +14 -14
- data/spec/{shoryuken → lib/shoryuken}/helpers/string_utils_spec.rb +3 -3
- data/spec/lib/shoryuken/helpers/timer_task_spec.rb +298 -0
- data/spec/{shoryuken → lib/shoryuken}/helpers_integration_spec.rb +9 -9
- data/spec/{shoryuken → lib/shoryuken}/launcher_spec.rb +22 -0
- data/spec/lib/shoryuken/logging_spec.rb +242 -0
- data/spec/lib/shoryuken/message_spec.rb +109 -0
- 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_extend_visibility_spec.rb +50 -0
- data/spec/{shoryuken → lib/shoryuken}/options_spec.rb +2 -2
- data/spec/{shoryuken → lib/shoryuken}/util_spec.rb +1 -1
- data/spec/lib/shoryuken/version_spec.rb +17 -0
- data/spec/lib/shoryuken/worker_registry_spec.rb +63 -0
- data/spec/shared_examples_for_active_job.rb +29 -9
- data/spec/spec_helper.rb +34 -3
- metadata +230 -91
- data/.devcontainer/Dockerfile +0 -17
- data/.devcontainer/base.Dockerfile +0 -43
- data/.devcontainer/devcontainer.json +0 -35
- data/Appraisals +0 -23
- data/gemfiles/.gitignore +0 -1
- data/gemfiles/rails_7_0.gemfile +0 -19
- data/gemfiles/rails_7_1.gemfile +0 -19
- data/gemfiles/rails_7_2.gemfile +0 -19
- data/gemfiles/rails_8_0.gemfile +0 -19
- data/lib/shoryuken/extensions/active_job_adapter.rb +0 -110
- data/lib/shoryuken/extensions/active_job_concurrent_send_adapter.rb +0 -50
- data/spec/integration/launcher_spec.rb +0 -127
- data/spec/shoryuken/extensions/active_job_adapter_spec.rb +0 -8
- data/spec/shoryuken/extensions/active_job_base_spec.rb +0 -85
- /data/spec/{shoryuken → lib/shoryuken}/body_parser_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/client_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/default_exception_handler_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/default_worker_registry_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/fetcher_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/helpers/atomic_boolean_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/helpers/atomic_counter_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/helpers/atomic_hash_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/inline_message_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/manager_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/middleware/chain_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_delete_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/middleware/server/exponential_backoff_retry_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/middleware/server/timing_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/polling/base_strategy_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/polling/queue_configuration_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/polling/strict_priority_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/polling/weighted_round_robin_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/processor_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/queue_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/runner_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/worker/default_executor_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/worker/inline_executor_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/worker_spec.rb +0 -0
- /data/spec/{shoryuken_spec.rb → lib/shoryuken_spec.rb} +0 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# This spec tests SQS message attributes including String, Number, and Binary
|
|
4
|
+
# attribute types, system attributes (ApproximateReceiveCount, SentTimestamp),
|
|
5
|
+
# and custom type suffixes.
|
|
6
|
+
|
|
7
|
+
setup_localstack
|
|
8
|
+
|
|
9
|
+
queue_name = DT.queue
|
|
10
|
+
create_test_queue(queue_name)
|
|
11
|
+
Shoryuken.add_group('default', 1)
|
|
12
|
+
Shoryuken.add_queue(queue_name, 1, 'default')
|
|
13
|
+
|
|
14
|
+
worker_class = Class.new do
|
|
15
|
+
include Shoryuken::Worker
|
|
16
|
+
|
|
17
|
+
shoryuken_options auto_delete: true, batch: false
|
|
18
|
+
|
|
19
|
+
def perform(sqs_msg, body)
|
|
20
|
+
DT[:attributes] << sqs_msg.message_attributes
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
worker_class.get_shoryuken_options['queue'] = queue_name
|
|
25
|
+
Shoryuken.register_worker(queue_name, worker_class)
|
|
26
|
+
|
|
27
|
+
queue_url = Shoryuken::Client.sqs.get_queue_url(queue_name: queue_name).queue_url
|
|
28
|
+
|
|
29
|
+
Shoryuken::Client.sqs.send_message(
|
|
30
|
+
queue_url: queue_url,
|
|
31
|
+
message_body: 'mixed-attr-test',
|
|
32
|
+
message_attributes: {
|
|
33
|
+
'StringAttr' => {
|
|
34
|
+
string_value: 'hello-world',
|
|
35
|
+
data_type: 'String'
|
|
36
|
+
},
|
|
37
|
+
'NumberAttr' => {
|
|
38
|
+
string_value: '42',
|
|
39
|
+
data_type: 'Number'
|
|
40
|
+
},
|
|
41
|
+
'BinaryAttr' => {
|
|
42
|
+
binary_value: 'binary-data'.b,
|
|
43
|
+
data_type: 'Binary'
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
poll_queues_until { DT[:attributes].size >= 1 }
|
|
49
|
+
|
|
50
|
+
attrs = DT[:attributes].first
|
|
51
|
+
assert_equal(3, attrs.keys.size)
|
|
52
|
+
assert_equal('hello-world', attrs['StringAttr']&.string_value)
|
|
53
|
+
assert_equal('42', attrs['NumberAttr']&.string_value)
|
|
54
|
+
assert_equal('binary-data'.b, attrs['BinaryAttr']&.binary_value)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# This spec tests message operations from within a worker:
|
|
4
|
+
# - sqs_msg.delete - manually delete a message
|
|
5
|
+
# - sqs_msg.change_visibility - change visibility with options
|
|
6
|
+
|
|
7
|
+
setup_localstack
|
|
8
|
+
|
|
9
|
+
queue_name = DT.queue
|
|
10
|
+
create_test_queue(queue_name, attributes: { 'VisibilityTimeout' => '5' })
|
|
11
|
+
Shoryuken.add_group('default', 1)
|
|
12
|
+
Shoryuken.add_queue(queue_name, 1, 'default')
|
|
13
|
+
|
|
14
|
+
# Worker that tests message operations
|
|
15
|
+
message_ops_worker = Class.new do
|
|
16
|
+
include Shoryuken::Worker
|
|
17
|
+
|
|
18
|
+
shoryuken_options auto_delete: false
|
|
19
|
+
|
|
20
|
+
def perform(sqs_msg, body)
|
|
21
|
+
DT[:processed] << { message_id: sqs_msg.message_id, body: body }
|
|
22
|
+
|
|
23
|
+
# Test sqs_msg.change_visibility method
|
|
24
|
+
sqs_msg.change_visibility(visibility_timeout: 60)
|
|
25
|
+
DT[:extended] << sqs_msg.message_id
|
|
26
|
+
|
|
27
|
+
# Manually delete the message
|
|
28
|
+
sqs_msg.delete
|
|
29
|
+
DT[:deleted] << sqs_msg.message_id
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
message_ops_worker.get_shoryuken_options['queue'] = queue_name
|
|
34
|
+
Shoryuken.register_worker(queue_name, message_ops_worker)
|
|
35
|
+
|
|
36
|
+
queue_url = Shoryuken::Client.sqs.get_queue_url(queue_name: queue_name).queue_url
|
|
37
|
+
|
|
38
|
+
# Test: Message operations - change_visibility and delete
|
|
39
|
+
Shoryuken::Client.sqs.send_message(
|
|
40
|
+
queue_url: queue_url,
|
|
41
|
+
message_body: 'message ops test'
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
sleep 1
|
|
45
|
+
poll_queues_until { DT[:deleted].size >= 1 }
|
|
46
|
+
|
|
47
|
+
# Verify message was processed
|
|
48
|
+
assert_equal(1, DT[:processed].size)
|
|
49
|
+
assert_equal('message ops test', DT[:processed].first[:body])
|
|
50
|
+
|
|
51
|
+
# Verify visibility was extended
|
|
52
|
+
assert_equal(1, DT[:extended].size, "Visibility should have been extended")
|
|
53
|
+
|
|
54
|
+
# Verify message was deleted
|
|
55
|
+
assert_equal(1, DT[:deleted].size, "Message should have been deleted")
|
|
56
|
+
|
|
57
|
+
# Verify message was deleted - should not be reprocessed
|
|
58
|
+
sleep 2
|
|
59
|
+
assert_equal(1, DT[:processed].size, "Deleted message should only be processed once")
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Middleware executes in onion model order (first-in wraps outermost)
|
|
4
|
+
|
|
5
|
+
def create_middleware(name)
|
|
6
|
+
Class.new do
|
|
7
|
+
define_method(:call) do |worker, queue, sqs_msg, body, &block|
|
|
8
|
+
DT[:order] << :"#{name}_before"
|
|
9
|
+
block.call
|
|
10
|
+
DT[:order] << :"#{name}_after"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
first = create_middleware(:first)
|
|
16
|
+
second = create_middleware(:second)
|
|
17
|
+
third = create_middleware(:third)
|
|
18
|
+
|
|
19
|
+
chain = Shoryuken::Middleware::Chain.new
|
|
20
|
+
chain.add first
|
|
21
|
+
chain.add second
|
|
22
|
+
chain.add third
|
|
23
|
+
|
|
24
|
+
chain.invoke(nil, 'test-queue', nil, nil) do
|
|
25
|
+
DT[:order] << :worker_perform
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
expected_order = [
|
|
29
|
+
:first_before, :second_before, :third_before,
|
|
30
|
+
:worker_perform,
|
|
31
|
+
:third_after, :second_after, :first_after
|
|
32
|
+
]
|
|
33
|
+
assert_equal(expected_order, DT[:order])
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Middleware can be removed from the chain
|
|
4
|
+
|
|
5
|
+
def create_middleware(name)
|
|
6
|
+
Class.new do
|
|
7
|
+
define_method(:call) do |worker, queue, sqs_msg, body, &block|
|
|
8
|
+
DT[:calls] << :"#{name}_before"
|
|
9
|
+
block.call
|
|
10
|
+
DT[:calls] << :"#{name}_after"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
first = create_middleware(:first)
|
|
16
|
+
second = create_middleware(:second)
|
|
17
|
+
third = create_middleware(:third)
|
|
18
|
+
|
|
19
|
+
chain = Shoryuken::Middleware::Chain.new
|
|
20
|
+
chain.add first
|
|
21
|
+
chain.add second
|
|
22
|
+
chain.add third
|
|
23
|
+
chain.remove second
|
|
24
|
+
|
|
25
|
+
chain.invoke(nil, 'test', nil, nil) do
|
|
26
|
+
DT[:calls] << :worker
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
assert_includes(DT[:calls], :first_before)
|
|
30
|
+
refute(DT[:calls].include?(:second_before), "Second should be removed")
|
|
31
|
+
assert_includes(DT[:calls], :third_before)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Middleware can short-circuit the chain by not calling the block
|
|
4
|
+
|
|
5
|
+
def create_middleware(name)
|
|
6
|
+
Class.new do
|
|
7
|
+
define_method(:call) do |worker, queue, sqs_msg, body, &block|
|
|
8
|
+
DT[:calls] << :"#{name}_before"
|
|
9
|
+
block.call
|
|
10
|
+
DT[:calls] << :"#{name}_after"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def create_short_circuit_middleware
|
|
16
|
+
Class.new do
|
|
17
|
+
define_method(:call) do |worker, queue, sqs_msg, body, &block|
|
|
18
|
+
DT[:calls] << :short_circuit
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
first = create_middleware(:first)
|
|
24
|
+
short_circuit = create_short_circuit_middleware
|
|
25
|
+
third = create_middleware(:third)
|
|
26
|
+
|
|
27
|
+
chain = Shoryuken::Middleware::Chain.new
|
|
28
|
+
chain.add first
|
|
29
|
+
chain.add short_circuit
|
|
30
|
+
chain.add third
|
|
31
|
+
|
|
32
|
+
chain.invoke(nil, 'test', nil, nil) do
|
|
33
|
+
DT[:calls] << :worker
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
assert_includes(DT[:calls], :first_before)
|
|
37
|
+
assert_includes(DT[:calls], :short_circuit)
|
|
38
|
+
refute(DT[:calls].include?(:third_before), "Third should not execute")
|
|
39
|
+
refute(DT[:calls].include?(:worker), "Worker should not execute")
|
|
40
|
+
assert_includes(DT[:calls], :first_after)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# This spec tests polling strategies including WeightedRoundRobin (default)
|
|
4
|
+
# with multi-queue worker message distribution.
|
|
5
|
+
|
|
6
|
+
setup_localstack
|
|
7
|
+
|
|
8
|
+
queue_high = DT.queues[0]
|
|
9
|
+
queue_medium = DT.queues[1]
|
|
10
|
+
queue_low = DT.queues[2]
|
|
11
|
+
|
|
12
|
+
[queue_high, queue_medium, queue_low].each { |q| create_test_queue(q) }
|
|
13
|
+
|
|
14
|
+
Shoryuken.add_group('default', 1)
|
|
15
|
+
# Higher weight = higher priority
|
|
16
|
+
Shoryuken.add_queue(queue_high, 3, 'default')
|
|
17
|
+
Shoryuken.add_queue(queue_medium, 2, 'default')
|
|
18
|
+
Shoryuken.add_queue(queue_low, 1, 'default')
|
|
19
|
+
|
|
20
|
+
worker_class = Class.new do
|
|
21
|
+
include Shoryuken::Worker
|
|
22
|
+
|
|
23
|
+
shoryuken_options auto_delete: true, batch: false
|
|
24
|
+
|
|
25
|
+
def perform(sqs_msg, body)
|
|
26
|
+
queue = sqs_msg.queue_url.split('/').last
|
|
27
|
+
DT[:by_queue] << { queue: queue, body: body }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
[queue_high, queue_medium, queue_low].each do |queue|
|
|
32
|
+
worker_class.get_shoryuken_options['queue'] = queue
|
|
33
|
+
Shoryuken.register_worker(queue, worker_class)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
Shoryuken::Client.queues(queue_high).send_message(message_body: 'high-msg')
|
|
37
|
+
Shoryuken::Client.queues(queue_medium).send_message(message_body: 'medium-msg')
|
|
38
|
+
Shoryuken::Client.queues(queue_low).send_message(message_body: 'low-msg')
|
|
39
|
+
|
|
40
|
+
sleep 1
|
|
41
|
+
|
|
42
|
+
poll_queues_until { DT[:by_queue].size >= 3 }
|
|
43
|
+
|
|
44
|
+
queues_with_messages = DT[:by_queue].map { |m| m[:queue] }.uniq
|
|
45
|
+
assert_equal(3, queues_with_messages.size)
|
|
46
|
+
assert_equal(3, DT[:by_queue].size)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# This spec tests Shoryuken::Queue operations including:
|
|
4
|
+
# - Queue initialization (by name, URL, and ARN)
|
|
5
|
+
# - Visibility timeout retrieval
|
|
6
|
+
# - FIFO queue detection
|
|
7
|
+
# - Batch message sending
|
|
8
|
+
# - Batch message deletion
|
|
9
|
+
|
|
10
|
+
setup_localstack
|
|
11
|
+
|
|
12
|
+
# Test 1: Queue initialization by name
|
|
13
|
+
queue_name = DT.uuid
|
|
14
|
+
create_test_queue(queue_name)
|
|
15
|
+
|
|
16
|
+
queue = Shoryuken::Queue.new(Shoryuken::Client.sqs, queue_name)
|
|
17
|
+
assert_equal(queue_name, queue.name)
|
|
18
|
+
assert(queue.url.include?(queue_name), "URL should contain queue name")
|
|
19
|
+
refute(queue.fifo?, "Standard queue should not be FIFO")
|
|
20
|
+
|
|
21
|
+
# Test 2: Queue initialization by URL
|
|
22
|
+
queue_url = queue.url
|
|
23
|
+
queue_by_url = Shoryuken::Queue.new(Shoryuken::Client.sqs, queue_url)
|
|
24
|
+
assert_equal(queue_name, queue_by_url.name)
|
|
25
|
+
assert_equal(queue_url, queue_by_url.url)
|
|
26
|
+
|
|
27
|
+
# Test 3: Visibility timeout retrieval
|
|
28
|
+
visibility_timeout = queue.visibility_timeout
|
|
29
|
+
assert(visibility_timeout.is_a?(Integer), "Visibility timeout should be an integer")
|
|
30
|
+
assert(visibility_timeout >= 0, "Visibility timeout should be non-negative")
|
|
31
|
+
|
|
32
|
+
# Test 4: FIFO queue detection
|
|
33
|
+
fifo_queue_name = "#{DT.uuid}.fifo"
|
|
34
|
+
create_fifo_queue(fifo_queue_name)
|
|
35
|
+
|
|
36
|
+
fifo_queue = Shoryuken::Queue.new(Shoryuken::Client.sqs, fifo_queue_name)
|
|
37
|
+
assert_equal(fifo_queue_name, fifo_queue.name)
|
|
38
|
+
assert(fifo_queue.fifo?, "FIFO queue should be detected as FIFO")
|
|
39
|
+
|
|
40
|
+
# Test 5: Send single message
|
|
41
|
+
send_result = queue.send_message(message_body: 'test message 1')
|
|
42
|
+
assert(send_result.message_id, "Send result should have message_id")
|
|
43
|
+
|
|
44
|
+
# Test 6: Send message with hash body (auto JSON serialization)
|
|
45
|
+
hash_body = { key: 'value', number: 42 }
|
|
46
|
+
send_result2 = queue.send_message(message_body: hash_body)
|
|
47
|
+
assert(send_result2.message_id, "Send result should have message_id for hash body")
|
|
48
|
+
|
|
49
|
+
# Test 7: Batch message sending
|
|
50
|
+
batch_result = queue.send_messages([
|
|
51
|
+
{ message_body: 'batch msg 1' },
|
|
52
|
+
{ message_body: 'batch msg 2' },
|
|
53
|
+
{ message_body: 'batch msg 3' }
|
|
54
|
+
])
|
|
55
|
+
assert_equal(3, batch_result.successful.size, "All 3 batch messages should succeed")
|
|
56
|
+
|
|
57
|
+
# Test 8: Receive messages
|
|
58
|
+
sleep 1 # Allow messages to become visible
|
|
59
|
+
received = queue.receive_messages(max_number_of_messages: 10)
|
|
60
|
+
assert(received.size > 0, "Should receive at least one message")
|
|
61
|
+
assert(received.first.is_a?(Shoryuken::Message), "Received items should be Message objects")
|
|
62
|
+
|
|
63
|
+
# Test 9: Batch message deletion
|
|
64
|
+
entries = received.map.with_index do |msg, idx|
|
|
65
|
+
{ id: idx.to_s, receipt_handle: msg.receipt_handle }
|
|
66
|
+
end
|
|
67
|
+
delete_result = queue.delete_messages(entries: entries)
|
|
68
|
+
refute(delete_result, "Delete should succeed without failures")
|
|
69
|
+
|
|
70
|
+
# Test 10: FIFO queue message sending with auto-generated attributes
|
|
71
|
+
fifo_send_result = fifo_queue.send_message(message_body: 'fifo test message')
|
|
72
|
+
assert(fifo_send_result.message_id, "FIFO send should have message_id")
|
|
73
|
+
assert(fifo_send_result.sequence_number, "FIFO send should have sequence_number")
|
|
74
|
+
|
|
75
|
+
# Test 11: Send message with delay
|
|
76
|
+
delayed_result = queue.send_message(
|
|
77
|
+
message_body: 'delayed message',
|
|
78
|
+
delay_seconds: 5
|
|
79
|
+
)
|
|
80
|
+
assert(delayed_result.message_id, "Delayed message should have message_id")
|
|
81
|
+
|
|
82
|
+
# Cleanup
|
|
83
|
+
delete_test_queue(queue_name)
|
|
84
|
+
delete_test_queue(fifo_queue_name)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# ActiveJob adapter integration tests for Rails 7.2
|
|
4
|
+
|
|
5
|
+
setup_active_job
|
|
6
|
+
|
|
7
|
+
class EmailJob < ActiveJob::Base
|
|
8
|
+
queue_as :default
|
|
9
|
+
|
|
10
|
+
def perform(user_id, message)
|
|
11
|
+
{ user_id: user_id, message: message, sent_at: Time.current }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class DataProcessingJob < ActiveJob::Base
|
|
16
|
+
queue_as :high_priority
|
|
17
|
+
|
|
18
|
+
def perform(data_file)
|
|
19
|
+
"Processed: #{data_file}"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class SerializationJob < ActiveJob::Base
|
|
24
|
+
queue_as :default
|
|
25
|
+
|
|
26
|
+
def perform(complex_data)
|
|
27
|
+
complex_data.transform_values(&:upcase)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Test adapter setup
|
|
32
|
+
adapter = ActiveJob::Base.queue_adapter
|
|
33
|
+
assert_equal("ActiveJob::QueueAdapters::ShoryukenAdapter", adapter.class.name)
|
|
34
|
+
|
|
35
|
+
# Test singleton pattern
|
|
36
|
+
instance1 = ActiveJob::QueueAdapters::ShoryukenAdapter.instance
|
|
37
|
+
instance2 = ActiveJob::QueueAdapters::ShoryukenAdapter.instance
|
|
38
|
+
assert_equal(instance1.object_id, instance2.object_id)
|
|
39
|
+
|
|
40
|
+
# Test transaction commit hook (Rails 7.2+)
|
|
41
|
+
adapter_instance = ActiveJob::QueueAdapters::ShoryukenAdapter.new
|
|
42
|
+
assert(adapter_instance.respond_to?(:enqueue_after_transaction_commit?))
|
|
43
|
+
assert_equal(true, adapter_instance.enqueue_after_transaction_commit?)
|
|
44
|
+
|
|
45
|
+
# Test simple job enqueue
|
|
46
|
+
job_capture = JobCapture.new
|
|
47
|
+
job_capture.start_capturing
|
|
48
|
+
|
|
49
|
+
EmailJob.perform_later(1, 'Hello World')
|
|
50
|
+
|
|
51
|
+
assert_equal(1, job_capture.job_count)
|
|
52
|
+
job = job_capture.last_job
|
|
53
|
+
message_body = job[:message_body]
|
|
54
|
+
assert_equal('EmailJob', message_body['job_class'])
|
|
55
|
+
assert_equal([1, 'Hello World'], message_body['arguments'])
|
|
56
|
+
assert_equal('default', message_body['queue_name'])
|
|
57
|
+
|
|
58
|
+
# Test different queue
|
|
59
|
+
job_capture2 = JobCapture.new
|
|
60
|
+
job_capture2.start_capturing
|
|
61
|
+
|
|
62
|
+
DataProcessingJob.perform_later('large_dataset.csv')
|
|
63
|
+
|
|
64
|
+
job2 = job_capture2.last_job
|
|
65
|
+
message_body2 = job2[:message_body]
|
|
66
|
+
assert_equal('DataProcessingJob', message_body2['job_class'])
|
|
67
|
+
assert_equal('high_priority', message_body2['queue_name'])
|
|
68
|
+
|
|
69
|
+
# Test complex data serialization
|
|
70
|
+
complex_data = {
|
|
71
|
+
'user' => { 'name' => 'John', 'age' => 30 },
|
|
72
|
+
'preferences' => ['email', 'sms']
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
job_capture3 = JobCapture.new
|
|
76
|
+
job_capture3.start_capturing
|
|
77
|
+
|
|
78
|
+
SerializationJob.perform_later(complex_data)
|
|
79
|
+
|
|
80
|
+
job3 = job_capture3.last_job
|
|
81
|
+
message_body3 = job3[:message_body]
|
|
82
|
+
args_data = message_body3['arguments'].first
|
|
83
|
+
assert_equal('John', args_data['user']['name'])
|
|
84
|
+
assert_equal(30, args_data['user']['age'])
|
|
85
|
+
|
|
86
|
+
# Test shoryuken_class message attribute
|
|
87
|
+
job_capture4 = JobCapture.new
|
|
88
|
+
job_capture4.start_capturing
|
|
89
|
+
|
|
90
|
+
EmailJob.perform_later(1, 'Attributes test')
|
|
91
|
+
|
|
92
|
+
job4 = job_capture4.last_job
|
|
93
|
+
attributes = job4[:message_attributes]
|
|
94
|
+
expected_shoryuken_class = {
|
|
95
|
+
string_value: "Shoryuken::ActiveJob::JobWrapper",
|
|
96
|
+
data_type: 'String'
|
|
97
|
+
}
|
|
98
|
+
assert_equal(expected_shoryuken_class, attributes['shoryuken_class'])
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# ActiveJob adapter integration tests for Rails 8.0
|
|
4
|
+
|
|
5
|
+
setup_active_job
|
|
6
|
+
|
|
7
|
+
class EmailJob < ActiveJob::Base
|
|
8
|
+
queue_as :default
|
|
9
|
+
|
|
10
|
+
def perform(user_id, message)
|
|
11
|
+
{ user_id: user_id, message: message, sent_at: Time.current }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class DataProcessingJob < ActiveJob::Base
|
|
16
|
+
queue_as :high_priority
|
|
17
|
+
|
|
18
|
+
def perform(data_file)
|
|
19
|
+
"Processed: #{data_file}"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class SerializationJob < ActiveJob::Base
|
|
24
|
+
queue_as :default
|
|
25
|
+
|
|
26
|
+
def perform(complex_data)
|
|
27
|
+
complex_data.transform_values(&:upcase)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Test adapter setup
|
|
32
|
+
adapter = ActiveJob::Base.queue_adapter
|
|
33
|
+
assert_equal("ActiveJob::QueueAdapters::ShoryukenAdapter", adapter.class.name)
|
|
34
|
+
|
|
35
|
+
# Test singleton pattern
|
|
36
|
+
instance1 = ActiveJob::QueueAdapters::ShoryukenAdapter.instance
|
|
37
|
+
instance2 = ActiveJob::QueueAdapters::ShoryukenAdapter.instance
|
|
38
|
+
assert_equal(instance1.object_id, instance2.object_id)
|
|
39
|
+
|
|
40
|
+
# Test transaction commit hook
|
|
41
|
+
adapter_instance = ActiveJob::QueueAdapters::ShoryukenAdapter.new
|
|
42
|
+
assert(adapter_instance.respond_to?(:enqueue_after_transaction_commit?))
|
|
43
|
+
assert_equal(true, adapter_instance.enqueue_after_transaction_commit?)
|
|
44
|
+
|
|
45
|
+
# Test simple job enqueue
|
|
46
|
+
job_capture = JobCapture.new
|
|
47
|
+
job_capture.start_capturing
|
|
48
|
+
|
|
49
|
+
EmailJob.perform_later(1, 'Hello World')
|
|
50
|
+
|
|
51
|
+
assert_equal(1, job_capture.job_count)
|
|
52
|
+
job = job_capture.last_job
|
|
53
|
+
message_body = job[:message_body]
|
|
54
|
+
assert_equal('EmailJob', message_body['job_class'])
|
|
55
|
+
assert_equal([1, 'Hello World'], message_body['arguments'])
|
|
56
|
+
assert_equal('default', message_body['queue_name'])
|
|
57
|
+
|
|
58
|
+
# Test different queue
|
|
59
|
+
job_capture2 = JobCapture.new
|
|
60
|
+
job_capture2.start_capturing
|
|
61
|
+
|
|
62
|
+
DataProcessingJob.perform_later('large_dataset.csv')
|
|
63
|
+
|
|
64
|
+
job2 = job_capture2.last_job
|
|
65
|
+
message_body2 = job2[:message_body]
|
|
66
|
+
assert_equal('DataProcessingJob', message_body2['job_class'])
|
|
67
|
+
assert_equal('high_priority', message_body2['queue_name'])
|
|
68
|
+
|
|
69
|
+
# Test complex data serialization
|
|
70
|
+
complex_data = {
|
|
71
|
+
'user' => { 'name' => 'John', 'age' => 30 },
|
|
72
|
+
'preferences' => ['email', 'sms']
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
job_capture3 = JobCapture.new
|
|
76
|
+
job_capture3.start_capturing
|
|
77
|
+
|
|
78
|
+
SerializationJob.perform_later(complex_data)
|
|
79
|
+
|
|
80
|
+
job3 = job_capture3.last_job
|
|
81
|
+
message_body3 = job3[:message_body]
|
|
82
|
+
args_data = message_body3['arguments'].first
|
|
83
|
+
assert_equal('John', args_data['user']['name'])
|
|
84
|
+
assert_equal(30, args_data['user']['age'])
|
|
85
|
+
|
|
86
|
+
# Test shoryuken_class message attribute
|
|
87
|
+
job_capture4 = JobCapture.new
|
|
88
|
+
job_capture4.start_capturing
|
|
89
|
+
|
|
90
|
+
EmailJob.perform_later(1, 'Attributes test')
|
|
91
|
+
|
|
92
|
+
job4 = job_capture4.last_job
|
|
93
|
+
attributes = job4[:message_attributes]
|
|
94
|
+
expected_shoryuken_class = {
|
|
95
|
+
string_value: "Shoryuken::ActiveJob::JobWrapper",
|
|
96
|
+
data_type: 'String'
|
|
97
|
+
}
|
|
98
|
+
assert_equal(expected_shoryuken_class, attributes['shoryuken_class'])
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# ActiveJob Continuations integration tests for Rails 8.0+
|
|
4
|
+
# Tests the stopping? method and continuation timestamp handling
|
|
5
|
+
|
|
6
|
+
setup_active_job
|
|
7
|
+
|
|
8
|
+
# Skip if ActiveJob::Continuable is not available (Rails < 8.0)
|
|
9
|
+
unless defined?(ActiveJob::Continuable)
|
|
10
|
+
puts "Skipping continuation tests - ActiveJob::Continuable not available (requires Rails 8.0+)"
|
|
11
|
+
exit 0
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Test stopping? returns false when launcher is not initialized
|
|
15
|
+
adapter = ActiveJob::QueueAdapters::ShoryukenAdapter.new
|
|
16
|
+
assert_equal(false, adapter.stopping?)
|
|
17
|
+
|
|
18
|
+
# Test stopping? returns true when launcher is stopping
|
|
19
|
+
launcher = Shoryuken::Launcher.new
|
|
20
|
+
runner = Shoryuken::Runner.instance
|
|
21
|
+
runner.instance_variable_set(:@launcher, launcher)
|
|
22
|
+
|
|
23
|
+
adapter2 = ActiveJob::QueueAdapters::ShoryukenAdapter.new
|
|
24
|
+
assert_equal(false, adapter2.stopping?)
|
|
25
|
+
|
|
26
|
+
launcher.instance_variable_set(:@stopping, true)
|
|
27
|
+
assert_equal(true, adapter2.stopping?)
|
|
28
|
+
|
|
29
|
+
# Reset launcher state
|
|
30
|
+
launcher.instance_variable_set(:@stopping, false)
|
|
31
|
+
|
|
32
|
+
# Test past timestamps for continuation retries
|
|
33
|
+
job_capture = JobCapture.new
|
|
34
|
+
job_capture.start_capturing
|
|
35
|
+
|
|
36
|
+
class ContinuableTestJob < ActiveJob::Base
|
|
37
|
+
include ActiveJob::Continuable if defined?(ActiveJob::Continuable)
|
|
38
|
+
queue_as :default
|
|
39
|
+
def perform; end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
adapter3 = ActiveJob::QueueAdapters::ShoryukenAdapter.new
|
|
43
|
+
job = ContinuableTestJob.new
|
|
44
|
+
job.sqs_send_message_parameters = {}
|
|
45
|
+
|
|
46
|
+
past_timestamp = Time.current.to_f - 60
|
|
47
|
+
adapter3.enqueue_at(job, past_timestamp)
|
|
48
|
+
|
|
49
|
+
captured_job = job_capture.last_job
|
|
50
|
+
assert(captured_job[:delay_seconds] <= 0, "Past timestamp should result in immediate delivery")
|
|
51
|
+
|
|
52
|
+
# Test current timestamp
|
|
53
|
+
job_capture2 = JobCapture.new
|
|
54
|
+
job_capture2.start_capturing
|
|
55
|
+
|
|
56
|
+
job2 = ContinuableTestJob.new
|
|
57
|
+
job2.sqs_send_message_parameters = {}
|
|
58
|
+
|
|
59
|
+
current_timestamp = Time.current.to_f
|
|
60
|
+
adapter3.enqueue_at(job2, current_timestamp)
|
|
61
|
+
|
|
62
|
+
captured_job2 = job_capture2.last_job
|
|
63
|
+
delay = captured_job2[:delay_seconds]
|
|
64
|
+
assert(delay >= -1 && delay <= 1, "Current timestamp should have minimal delay")
|
|
65
|
+
|
|
66
|
+
# Test future timestamp
|
|
67
|
+
job_capture3 = JobCapture.new
|
|
68
|
+
job_capture3.start_capturing
|
|
69
|
+
|
|
70
|
+
job3 = ContinuableTestJob.new
|
|
71
|
+
job3.sqs_send_message_parameters = {}
|
|
72
|
+
|
|
73
|
+
future_timestamp = Time.current.to_f + 30
|
|
74
|
+
adapter3.enqueue_at(job3, future_timestamp)
|
|
75
|
+
|
|
76
|
+
captured_job3 = job_capture3.last_job
|
|
77
|
+
delay3 = captured_job3[:delay_seconds]
|
|
78
|
+
assert(delay3 > 0, "Future timestamp should have positive delay")
|
|
79
|
+
assert(delay3 <= 30, "Delay should not exceed scheduled time")
|