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.
Files changed (178) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/push.yml +2 -2
  3. data/.github/workflows/specs.yml +38 -43
  4. data/.github/workflows/verify-action-pins.yml +1 -1
  5. data/.gitignore +3 -0
  6. data/.rspec +1 -0
  7. data/.ruby-version +1 -1
  8. data/.yard-lint.yml +279 -0
  9. data/CHANGELOG.md +69 -1
  10. data/Gemfile +1 -1
  11. data/README.md +2 -7
  12. data/Rakefile +4 -10
  13. data/bin/clean_localstack +52 -0
  14. data/bin/cli/base.rb +21 -0
  15. data/bin/cli/sqs.rb +61 -2
  16. data/bin/integrations +275 -0
  17. data/bin/scenario +154 -0
  18. data/bin/shoryuken +1 -1
  19. data/lib/{shoryuken/extensions/active_job_extensions.rb → active_job/extensions.rb} +15 -4
  20. data/lib/active_job/queue_adapters/shoryuken_adapter.rb +208 -0
  21. data/lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter.rb +78 -0
  22. data/lib/shoryuken/active_job/current_attributes.rb +139 -0
  23. data/lib/shoryuken/active_job/job_wrapper.rb +28 -0
  24. data/lib/shoryuken/body_parser.rb +8 -0
  25. data/lib/shoryuken/client.rb +14 -0
  26. data/lib/shoryuken/default_exception_handler.rb +9 -0
  27. data/lib/shoryuken/default_worker_registry.rb +29 -1
  28. data/lib/shoryuken/environment_loader.rb +78 -8
  29. data/lib/shoryuken/errors.rb +33 -0
  30. data/lib/shoryuken/fetcher.rb +37 -1
  31. data/lib/shoryuken/helpers/atomic_boolean.rb +19 -5
  32. data/lib/shoryuken/helpers/timer_task.rb +80 -0
  33. data/lib/shoryuken/launcher.rb +53 -0
  34. data/lib/shoryuken/logging/base.rb +26 -0
  35. data/lib/shoryuken/logging/pretty.rb +25 -0
  36. data/lib/shoryuken/logging/without_timestamp.rb +25 -0
  37. data/lib/shoryuken/logging.rb +39 -25
  38. data/lib/shoryuken/manager.rb +70 -1
  39. data/lib/shoryuken/message.rb +114 -1
  40. data/lib/shoryuken/middleware/chain.rb +139 -43
  41. data/lib/shoryuken/middleware/entry.rb +30 -0
  42. data/lib/shoryuken/middleware/server/active_record.rb +8 -0
  43. data/lib/shoryuken/middleware/server/auto_delete.rb +10 -0
  44. data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +27 -1
  45. data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +29 -0
  46. data/lib/shoryuken/middleware/server/timing.rb +11 -0
  47. data/lib/shoryuken/options.rb +129 -6
  48. data/lib/shoryuken/polling/base_strategy.rb +1 -0
  49. data/lib/shoryuken/polling/strict_priority.rb +39 -0
  50. data/lib/shoryuken/polling/weighted_round_robin.rb +42 -0
  51. data/lib/shoryuken/processor.rb +32 -1
  52. data/lib/shoryuken/queue.rb +93 -4
  53. data/lib/shoryuken/runner.rb +45 -4
  54. data/lib/shoryuken/util.rb +26 -1
  55. data/lib/shoryuken/version.rb +2 -1
  56. data/lib/shoryuken/worker/default_executor.rb +21 -1
  57. data/lib/shoryuken/worker/inline_executor.rb +24 -0
  58. data/lib/shoryuken/worker.rb +193 -0
  59. data/lib/shoryuken/worker_registry.rb +33 -0
  60. data/lib/shoryuken.rb +18 -6
  61. data/renovate.json +29 -2
  62. data/shoryuken.gemspec +2 -1
  63. data/spec/integration/.rspec +1 -0
  64. data/spec/integration/active_job/adapter_configuration/configuration_spec.rb +26 -0
  65. data/spec/integration/active_job/bulk_enqueue/bulk_enqueue_spec.rb +53 -0
  66. data/spec/integration/active_job/current_attributes/bulk_enqueue_spec.rb +50 -0
  67. data/spec/integration/active_job/current_attributes/complex_types_spec.rb +55 -0
  68. data/spec/integration/active_job/current_attributes/empty_context_spec.rb +41 -0
  69. data/spec/integration/active_job/current_attributes/full_context_spec.rb +63 -0
  70. data/spec/integration/active_job/current_attributes/partial_context_spec.rb +57 -0
  71. data/spec/integration/active_job/custom_attributes/number_attributes_spec.rb +37 -0
  72. data/spec/integration/active_job/custom_attributes/string_attributes_spec.rb +39 -0
  73. data/spec/integration/active_job/error_handling/job_wrapper_spec.rb +53 -0
  74. data/spec/integration/active_job/fifo_and_attributes/deduplication_spec.rb +86 -0
  75. data/spec/integration/active_job/retry/discard_on_spec.rb +43 -0
  76. data/spec/integration/active_job/retry/retry_on_spec.rb +36 -0
  77. data/spec/integration/active_job/roundtrip/roundtrip_spec.rb +52 -0
  78. data/spec/integration/active_job/scheduled/scheduled_spec.rb +76 -0
  79. data/spec/integration/active_record_middleware/active_record_middleware_spec.rb +84 -0
  80. data/spec/integration/auto_delete/auto_delete_spec.rb +53 -0
  81. data/spec/integration/auto_extend_visibility/auto_extend_visibility_spec.rb +57 -0
  82. data/spec/integration/aws_config/aws_config_spec.rb +59 -0
  83. data/spec/integration/batch_processing/batch_processing_spec.rb +37 -0
  84. data/spec/integration/body_parser/json_parser_spec.rb +45 -0
  85. data/spec/integration/body_parser/proc_parser_spec.rb +54 -0
  86. data/spec/integration/body_parser/text_parser_spec.rb +43 -0
  87. data/spec/integration/concurrent_processing/concurrent_processing_spec.rb +45 -0
  88. data/spec/integration/dead_letter_queue/dead_letter_queue_spec.rb +91 -0
  89. data/spec/integration/exception_handlers/exception_handlers_spec.rb +69 -0
  90. data/spec/integration/exponential_backoff/exponential_backoff_spec.rb +67 -0
  91. data/spec/integration/fifo_ordering/fifo_ordering_spec.rb +44 -0
  92. data/spec/integration/large_payloads/large_payloads_spec.rb +30 -0
  93. data/spec/integration/launcher/launcher_spec.rb +40 -0
  94. data/spec/integration/message_attributes/message_attributes_spec.rb +54 -0
  95. data/spec/integration/message_operations/message_operations_spec.rb +59 -0
  96. data/spec/integration/middleware_chain/empty_chain_spec.rb +11 -0
  97. data/spec/integration/middleware_chain/execution_order_spec.rb +33 -0
  98. data/spec/integration/middleware_chain/removal_spec.rb +31 -0
  99. data/spec/integration/middleware_chain/short_circuit_spec.rb +40 -0
  100. data/spec/integration/polling_strategies/polling_strategies_spec.rb +46 -0
  101. data/spec/integration/queue_operations/queue_operations_spec.rb +84 -0
  102. data/spec/integration/rails/rails_72/Gemfile +6 -0
  103. data/spec/integration/rails/rails_72/activejob_adapter_spec.rb +98 -0
  104. data/spec/integration/rails/rails_80/Gemfile +6 -0
  105. data/spec/integration/rails/rails_80/activejob_adapter_spec.rb +98 -0
  106. data/spec/integration/rails/rails_80/continuation_spec.rb +79 -0
  107. data/spec/integration/rails/rails_81/Gemfile +6 -0
  108. data/spec/integration/rails/rails_81/activejob_adapter_spec.rb +98 -0
  109. data/spec/integration/rails/rails_81/continuation_spec.rb +79 -0
  110. data/spec/integration/retry_behavior/retry_behavior_spec.rb +45 -0
  111. data/spec/integration/spec_helper.rb +7 -0
  112. data/spec/integration/strict_priority_polling/strict_priority_polling_spec.rb +58 -0
  113. data/spec/integration/visibility_timeout/visibility_timeout_spec.rb +37 -0
  114. data/spec/integration/worker_enqueueing/worker_enqueueing_spec.rb +60 -0
  115. data/spec/integration/worker_groups/worker_groups_spec.rb +79 -0
  116. data/spec/integration/worker_lifecycle/worker_lifecycle_spec.rb +33 -0
  117. data/spec/integrations_helper.rb +243 -0
  118. data/spec/lib/active_job/extensions_spec.rb +149 -0
  119. data/spec/lib/active_job/queue_adapters/shoryuken_adapter_spec.rb +29 -0
  120. data/spec/{shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb → lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter_spec.rb} +3 -3
  121. data/spec/{shoryuken/extensions/active_job_wrapper_spec.rb → lib/shoryuken/active_job/job_wrapper_spec.rb} +4 -4
  122. data/spec/{shoryuken → lib/shoryuken}/environment_loader_spec.rb +1 -1
  123. data/spec/{shoryuken → lib/shoryuken}/helpers/hash_utils_spec.rb +14 -14
  124. data/spec/{shoryuken → lib/shoryuken}/helpers/string_utils_spec.rb +3 -3
  125. data/spec/lib/shoryuken/helpers/timer_task_spec.rb +298 -0
  126. data/spec/{shoryuken → lib/shoryuken}/helpers_integration_spec.rb +9 -9
  127. data/spec/{shoryuken → lib/shoryuken}/launcher_spec.rb +22 -0
  128. data/spec/lib/shoryuken/logging_spec.rb +242 -0
  129. data/spec/lib/shoryuken/message_spec.rb +109 -0
  130. data/spec/lib/shoryuken/middleware/entry_spec.rb +68 -0
  131. data/spec/lib/shoryuken/middleware/server/active_record_spec.rb +133 -0
  132. data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_extend_visibility_spec.rb +50 -0
  133. data/spec/{shoryuken → lib/shoryuken}/options_spec.rb +2 -2
  134. data/spec/{shoryuken → lib/shoryuken}/util_spec.rb +1 -1
  135. data/spec/lib/shoryuken/version_spec.rb +17 -0
  136. data/spec/lib/shoryuken/worker_registry_spec.rb +63 -0
  137. data/spec/shared_examples_for_active_job.rb +29 -9
  138. data/spec/spec_helper.rb +34 -3
  139. metadata +230 -91
  140. data/.devcontainer/Dockerfile +0 -17
  141. data/.devcontainer/base.Dockerfile +0 -43
  142. data/.devcontainer/devcontainer.json +0 -35
  143. data/Appraisals +0 -23
  144. data/gemfiles/.gitignore +0 -1
  145. data/gemfiles/rails_7_0.gemfile +0 -19
  146. data/gemfiles/rails_7_1.gemfile +0 -19
  147. data/gemfiles/rails_7_2.gemfile +0 -19
  148. data/gemfiles/rails_8_0.gemfile +0 -19
  149. data/lib/shoryuken/extensions/active_job_adapter.rb +0 -110
  150. data/lib/shoryuken/extensions/active_job_concurrent_send_adapter.rb +0 -50
  151. data/spec/integration/launcher_spec.rb +0 -127
  152. data/spec/shoryuken/extensions/active_job_adapter_spec.rb +0 -8
  153. data/spec/shoryuken/extensions/active_job_base_spec.rb +0 -85
  154. /data/spec/{shoryuken → lib/shoryuken}/body_parser_spec.rb +0 -0
  155. /data/spec/{shoryuken → lib/shoryuken}/client_spec.rb +0 -0
  156. /data/spec/{shoryuken → lib/shoryuken}/default_exception_handler_spec.rb +0 -0
  157. /data/spec/{shoryuken → lib/shoryuken}/default_worker_registry_spec.rb +0 -0
  158. /data/spec/{shoryuken → lib/shoryuken}/fetcher_spec.rb +0 -0
  159. /data/spec/{shoryuken → lib/shoryuken}/helpers/atomic_boolean_spec.rb +0 -0
  160. /data/spec/{shoryuken → lib/shoryuken}/helpers/atomic_counter_spec.rb +0 -0
  161. /data/spec/{shoryuken → lib/shoryuken}/helpers/atomic_hash_spec.rb +0 -0
  162. /data/spec/{shoryuken → lib/shoryuken}/inline_message_spec.rb +0 -0
  163. /data/spec/{shoryuken → lib/shoryuken}/manager_spec.rb +0 -0
  164. /data/spec/{shoryuken → lib/shoryuken}/middleware/chain_spec.rb +0 -0
  165. /data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_delete_spec.rb +0 -0
  166. /data/spec/{shoryuken → lib/shoryuken}/middleware/server/exponential_backoff_retry_spec.rb +0 -0
  167. /data/spec/{shoryuken → lib/shoryuken}/middleware/server/timing_spec.rb +0 -0
  168. /data/spec/{shoryuken → lib/shoryuken}/polling/base_strategy_spec.rb +0 -0
  169. /data/spec/{shoryuken → lib/shoryuken}/polling/queue_configuration_spec.rb +0 -0
  170. /data/spec/{shoryuken → lib/shoryuken}/polling/strict_priority_spec.rb +0 -0
  171. /data/spec/{shoryuken → lib/shoryuken}/polling/weighted_round_robin_spec.rb +0 -0
  172. /data/spec/{shoryuken → lib/shoryuken}/processor_spec.rb +0 -0
  173. /data/spec/{shoryuken → lib/shoryuken}/queue_spec.rb +0 -0
  174. /data/spec/{shoryuken → lib/shoryuken}/runner_spec.rb +0 -0
  175. /data/spec/{shoryuken → lib/shoryuken}/worker/default_executor_spec.rb +0 -0
  176. /data/spec/{shoryuken → lib/shoryuken}/worker/inline_executor_spec.rb +0 -0
  177. /data/spec/{shoryuken → lib/shoryuken}/worker_spec.rb +0 -0
  178. /data/spec/{shoryuken_spec.rb → lib/shoryuken_spec.rb} +0 -0
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ActiveJob adapter integration tests for Rails 8.1
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.1+
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.1)
9
+ unless defined?(ActiveJob::Continuable)
10
+ puts "Skipping continuation tests - ActiveJob::Continuable not available (requires Rails 8.1+)"
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")
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This spec tests retry behavior including ApproximateReceiveCount tracking
4
+ # across message redeliveries.
5
+
6
+ require 'concurrent'
7
+
8
+ setup_localstack
9
+
10
+ queue_name = DT.queue
11
+ create_test_queue(queue_name, attributes: { 'VisibilityTimeout' => '2' })
12
+ Shoryuken.add_group('default', 1)
13
+ Shoryuken.add_queue(queue_name, 1, 'default')
14
+
15
+ # Atomic counter for fail tracking
16
+ fail_counter = Concurrent::AtomicFixnum.new(2)
17
+
18
+ worker_class = Class.new do
19
+ include Shoryuken::Worker
20
+
21
+ shoryuken_options auto_delete: false, batch: false
22
+
23
+ define_method(:perform) do |sqs_msg, body|
24
+ receive_count = sqs_msg.attributes['ApproximateReceiveCount'].to_i
25
+ DT[:receive_counts] << receive_count
26
+
27
+ if fail_counter.value > 0
28
+ fail_counter.decrement
29
+ raise "Simulated failure"
30
+ else
31
+ sqs_msg.delete
32
+ end
33
+ end
34
+ end
35
+
36
+ worker_class.get_shoryuken_options['queue'] = queue_name
37
+ Shoryuken.register_worker(queue_name, worker_class)
38
+
39
+ Shoryuken::Client.queues(queue_name).send_message(message_body: 'retry-count-test')
40
+
41
+ poll_queues_until(timeout: 20) { DT[:receive_counts].size >= 3 }
42
+
43
+ assert(DT[:receive_counts].size >= 3)
44
+ assert_equal(DT[:receive_counts], DT[:receive_counts].sort, "Receive counts should be increasing")
45
+ assert_equal(1, DT[:receive_counts].first)
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Integration test spec helper
4
+ # This file is auto-required by RSpec for integration tests
5
+
6
+ require 'shoryuken'
7
+ require_relative '../integrations_helper'
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This spec tests the StrictPriority polling strategy.
4
+ # Higher priority queues are always processed before lower priority queues.
5
+
6
+ setup_localstack
7
+
8
+ queue_high = DT.queues[0]
9
+ queue_low = DT.queues[1]
10
+
11
+ [queue_high, queue_low].each { |q| create_test_queue(q) }
12
+
13
+ # Configure StrictPriority polling strategy
14
+ Shoryuken.options[:polling_strategy] = 'StrictPriority'
15
+
16
+ Shoryuken.add_group('default', 1)
17
+ # Higher weight = higher priority (queue_high appears 3 times, queue_low appears 1 time)
18
+ Shoryuken.add_queue(queue_high, 3, 'default')
19
+ Shoryuken.add_queue(queue_low, 1, 'default')
20
+
21
+ worker_class = Class.new do
22
+ include Shoryuken::Worker
23
+
24
+ shoryuken_options auto_delete: true, batch: false
25
+
26
+ def perform(sqs_msg, body)
27
+ queue = sqs_msg.queue_url.split('/').last
28
+ DT[:processed_order] << { queue: queue, body: body, time: Time.now }
29
+ end
30
+ end
31
+
32
+ [queue_high, queue_low].each do |queue|
33
+ worker_class.get_shoryuken_options['queue'] = queue
34
+ Shoryuken.register_worker(queue, worker_class)
35
+ end
36
+
37
+ # Send messages to low priority queue first
38
+ 3.times { |i| Shoryuken::Client.queues(queue_low).send_message(message_body: "low-#{i}") }
39
+
40
+ # Then send messages to high priority queue
41
+ 3.times { |i| Shoryuken::Client.queues(queue_high).send_message(message_body: "high-#{i}") }
42
+
43
+ sleep 1
44
+
45
+ poll_queues_until(timeout: 20) { DT[:processed_order].size >= 6 }
46
+
47
+ assert_equal(6, DT[:processed_order].size)
48
+
49
+ # With StrictPriority, high priority messages should generally be processed first
50
+ high_messages = DT[:processed_order].select { |m| m[:queue] == queue_high }
51
+ low_messages = DT[:processed_order].select { |m| m[:queue] == queue_low }
52
+
53
+ assert_equal(3, high_messages.size, "All high priority messages should be processed")
54
+ assert_equal(3, low_messages.size, "All low priority messages should be processed")
55
+
56
+ # Verify both queues were processed
57
+ queues_processed = DT[:processed_order].map { |m| m[:queue] }.uniq
58
+ assert_equal(2, queues_processed.size, "Both queues should have messages processed")
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This spec tests visibility timeout management including manual visibility
4
+ # extension during long processing.
5
+
6
+ setup_localstack
7
+
8
+ queue_name = DT.queue
9
+ create_test_queue(queue_name, attributes: { 'VisibilityTimeout' => '5' })
10
+ Shoryuken.add_group('default', 1)
11
+ Shoryuken.add_queue(queue_name, 1, 'default')
12
+
13
+ worker_class = Class.new do
14
+ include Shoryuken::Worker
15
+
16
+ def perform(sqs_msg, body)
17
+ # Extend visibility before long processing
18
+ sqs_msg.change_visibility(visibility_timeout: 30)
19
+ DT[:visibility_extended] << true
20
+
21
+ sleep 2 # Simulate slow processing
22
+
23
+ DT[:messages] << body
24
+ end
25
+ end
26
+
27
+ worker_class.get_shoryuken_options['queue'] = queue_name
28
+ worker_class.get_shoryuken_options['auto_delete'] = true
29
+ worker_class.get_shoryuken_options['batch'] = false
30
+ Shoryuken.register_worker(queue_name, worker_class)
31
+
32
+ Shoryuken::Client.queues(queue_name).send_message(message_body: 'extend-test')
33
+
34
+ poll_queues_until { DT[:messages].size >= 1 }
35
+
36
+ assert_equal(1, DT[:messages].size)
37
+ assert(DT[:visibility_extended].any?, "Expected visibility to be extended")
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This spec tests the worker enqueueing methods:
4
+ # - perform_async - enqueue a job for immediate processing
5
+ # - perform_in - enqueue a job with a delay
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 for testing enqueueing methods
15
+ enqueueing_worker = Class.new do
16
+ include Shoryuken::Worker
17
+
18
+ shoryuken_options auto_delete: true
19
+
20
+ def perform(sqs_msg, body)
21
+ DT[:processed_messages] << {
22
+ message_id: sqs_msg.message_id,
23
+ body: body,
24
+ processed_at: Time.now
25
+ }
26
+ end
27
+ end
28
+
29
+ enqueueing_worker.get_shoryuken_options['queue'] = queue_name
30
+ Shoryuken.register_worker(queue_name, enqueueing_worker)
31
+
32
+ # Test 1: perform_async - immediate enqueueing with string body
33
+ enqueueing_worker.perform_async('async string message')
34
+
35
+ # Test 2: perform_async with hash body
36
+ enqueueing_worker.perform_async('action' => 'test', 'data' => [1, 2, 3])
37
+
38
+ # Test 3: perform_in - delayed enqueueing (use short 1 second delay)
39
+ enqueueing_worker.perform_in(1, 'delayed message')
40
+
41
+ sleep 1
42
+
43
+ # Poll for all 3 messages
44
+ poll_queues_until(timeout: 15) { DT[:processed_messages].size >= 3 }
45
+
46
+ assert_equal(3, DT[:processed_messages].size)
47
+
48
+ # Verify string message was processed
49
+ string_msg = DT[:processed_messages].find { |m| m[:body] == 'async string message' }
50
+ assert(string_msg, 'String message should have been processed')
51
+
52
+ # Verify hash message was processed (bodies might be stringified depending on serialization)
53
+ hash_msg = DT[:processed_messages].find do |m|
54
+ m[:body].is_a?(Hash) || (m[:body].is_a?(String) && m[:body].include?('action'))
55
+ end
56
+ assert(hash_msg, 'Hash message should have been processed')
57
+
58
+ # Verify delayed message was processed
59
+ delayed_msg = DT[:processed_messages].find { |m| m[:body] == 'delayed message' }
60
+ assert(delayed_msg, 'Delayed message should have been processed')
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This spec tests multiple worker groups with different concurrency settings.
4
+ # Each group can have its own queues and concurrency level.
5
+
6
+ require 'concurrent'
7
+
8
+ setup_localstack
9
+
10
+ queue_group1 = DT.queues[0]
11
+ queue_group2 = DT.queues[1]
12
+
13
+ %w[queue_group1 queue_group2].each_with_index { |_, i| create_test_queue(DT.queues[i]) }
14
+
15
+ # Configure two separate groups with different concurrency
16
+ Shoryuken.add_group('group1', 3) # 3 concurrent processors
17
+ Shoryuken.add_group('group2', 1) # 1 concurrent processor
18
+
19
+ Shoryuken.add_queue(queue_group1, 1, 'group1')
20
+ Shoryuken.add_queue(queue_group2, 1, 'group2')
21
+
22
+ # Track concurrent processing per group
23
+ group1_concurrent = Concurrent::AtomicFixnum.new(0)
24
+ group1_max = Concurrent::AtomicFixnum.new(0)
25
+ group2_concurrent = Concurrent::AtomicFixnum.new(0)
26
+ group2_max = Concurrent::AtomicFixnum.new(0)
27
+
28
+ worker_class = Class.new do
29
+ include Shoryuken::Worker
30
+
31
+ shoryuken_options auto_delete: true, batch: false
32
+
33
+ define_method(:perform) do |sqs_msg, body|
34
+ queue = sqs_msg.queue_url.split('/').last
35
+
36
+ if queue == queue_group1
37
+ group1_concurrent.increment
38
+ current = group1_concurrent.value
39
+ group1_max.update { |max| [max, current].max }
40
+ sleep 0.5 # Longer sleep to increase chance of concurrent execution
41
+ group1_concurrent.decrement
42
+ else
43
+ group2_concurrent.increment
44
+ current = group2_concurrent.value
45
+ group2_max.update { |max| [max, current].max }
46
+ sleep 0.3
47
+ group2_concurrent.decrement
48
+ end
49
+
50
+ DT[:processed] << { queue: queue, body: body }
51
+ end
52
+ end
53
+
54
+ [queue_group1, queue_group2].each do |queue|
55
+ worker_class.get_shoryuken_options['queue'] = queue
56
+ Shoryuken.register_worker(queue, worker_class)
57
+ end
58
+
59
+ # Send messages to both groups
60
+ 5.times { |i| Shoryuken::Client.queues(queue_group1).send_message(message_body: "group1-#{i}") }
61
+ 5.times { |i| Shoryuken::Client.queues(queue_group2).send_message(message_body: "group2-#{i}") }
62
+
63
+ sleep 1
64
+
65
+ poll_queues_until(timeout: 20) { DT[:processed].size >= 10 }
66
+
67
+ assert_equal(10, DT[:processed].size)
68
+
69
+ # Verify messages from both groups were processed
70
+ group1_messages = DT[:processed].select { |m| m[:queue] == queue_group1 }
71
+ group2_messages = DT[:processed].select { |m| m[:queue] == queue_group2 }
72
+
73
+ assert_equal(5, group1_messages.size, 'All group1 messages should be processed')
74
+ assert_equal(5, group2_messages.size, 'All group2 messages should be processed')
75
+
76
+ # Verify concurrency was used - group1 with concurrency 3 should process concurrently
77
+ # group2 with concurrency 1 should process sequentially (max = 1)
78
+ assert(group1_max.value >= 1, "Group1 should have processed messages (max concurrent: #{group1_max.value})")
79
+ assert_equal(1, group2_max.value, 'Group2 with concurrency 1 should process sequentially')
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This spec tests worker lifecycle including worker registration and discovery.
4
+
5
+ setup_localstack
6
+
7
+ queue_name = DT.queue
8
+ create_test_queue(queue_name)
9
+ Shoryuken.add_group('default', 1)
10
+ Shoryuken.add_queue(queue_name, 1, 'default')
11
+
12
+ worker_class = Class.new do
13
+ include Shoryuken::Worker
14
+
15
+ def perform(sqs_msg, body)
16
+ DT[:messages] << body
17
+ end
18
+ end
19
+
20
+ worker_class.get_shoryuken_options['queue'] = queue_name
21
+ worker_class.get_shoryuken_options['auto_delete'] = true
22
+ worker_class.get_shoryuken_options['batch'] = false
23
+ Shoryuken.register_worker(queue_name, worker_class)
24
+
25
+ registered = Shoryuken.worker_registry.workers(queue_name)
26
+ assert_includes(registered, worker_class)
27
+
28
+ Shoryuken::Client.queues(queue_name).send_message(message_body: 'lifecycle-test')
29
+
30
+ poll_queues_until { DT[:messages].size >= 1 }
31
+
32
+ assert_equal(1, DT[:messages].size)
33
+ assert_equal('lifecycle-test', DT[:messages].first)