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.
Files changed (205) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/push.yml +36 -0
  3. data/.github/workflows/specs.yml +49 -44
  4. data/.github/workflows/verify-action-pins.yml +16 -0
  5. data/.gitignore +4 -1
  6. data/.rspec +3 -1
  7. data/.rubocop.yml +6 -1
  8. data/.ruby-version +1 -0
  9. data/.yard-lint.yml +279 -0
  10. data/CHANGELOG.md +308 -139
  11. data/Gemfile +1 -8
  12. data/Gemfile.lint +9 -0
  13. data/Gemfile.lint.lock +69 -0
  14. data/README.md +16 -33
  15. data/Rakefile +6 -10
  16. data/bin/clean_sqs +52 -0
  17. data/bin/cli/base.rb +22 -2
  18. data/bin/cli/sqs.rb +74 -7
  19. data/bin/integrations +275 -0
  20. data/bin/scenario +154 -0
  21. data/bin/shoryuken +3 -2
  22. data/docker-compose.yml +6 -0
  23. data/lib/{shoryuken/extensions/active_job_extensions.rb → active_job/extensions.rb} +20 -6
  24. data/lib/active_job/queue_adapters/shoryuken_adapter.rb +208 -0
  25. data/lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter.rb +78 -0
  26. data/lib/shoryuken/active_job/current_attributes.rb +139 -0
  27. data/lib/shoryuken/active_job/job_wrapper.rb +28 -0
  28. data/lib/shoryuken/body_parser.rb +11 -1
  29. data/lib/shoryuken/client.rb +16 -0
  30. data/lib/shoryuken/default_exception_handler.rb +11 -0
  31. data/lib/shoryuken/default_worker_registry.rb +39 -11
  32. data/lib/shoryuken/environment_loader.rb +85 -15
  33. data/lib/shoryuken/errors.rb +36 -0
  34. data/lib/shoryuken/fetcher.rb +41 -3
  35. data/lib/shoryuken/helpers/atomic_boolean.rb +58 -0
  36. data/lib/shoryuken/helpers/atomic_counter.rb +104 -0
  37. data/lib/shoryuken/helpers/atomic_hash.rb +182 -0
  38. data/lib/shoryuken/helpers/hash_utils.rb +56 -0
  39. data/lib/shoryuken/helpers/string_utils.rb +65 -0
  40. data/lib/shoryuken/helpers/timer_task.rb +80 -0
  41. data/lib/shoryuken/inline_message.rb +22 -0
  42. data/lib/shoryuken/launcher.rb +55 -0
  43. data/lib/shoryuken/logging/base.rb +26 -0
  44. data/lib/shoryuken/logging/pretty.rb +25 -0
  45. data/lib/shoryuken/logging/without_timestamp.rb +25 -0
  46. data/lib/shoryuken/logging.rb +43 -15
  47. data/lib/shoryuken/manager.rb +84 -5
  48. data/lib/shoryuken/message.rb +116 -1
  49. data/lib/shoryuken/middleware/chain.rb +141 -43
  50. data/lib/shoryuken/middleware/entry.rb +30 -0
  51. data/lib/shoryuken/middleware/server/active_record.rb +10 -0
  52. data/lib/shoryuken/middleware/server/auto_delete.rb +12 -0
  53. data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +37 -11
  54. data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +34 -3
  55. data/lib/shoryuken/middleware/server/non_retryable_exception.rb +95 -0
  56. data/lib/shoryuken/middleware/server/timing.rb +13 -0
  57. data/lib/shoryuken/options.rb +154 -13
  58. data/lib/shoryuken/polling/base_strategy.rb +127 -0
  59. data/lib/shoryuken/polling/queue_configuration.rb +103 -0
  60. data/lib/shoryuken/polling/strict_priority.rb +41 -0
  61. data/lib/shoryuken/polling/weighted_round_robin.rb +44 -0
  62. data/lib/shoryuken/processor.rb +37 -3
  63. data/lib/shoryuken/queue.rb +99 -8
  64. data/lib/shoryuken/runner.rb +54 -16
  65. data/lib/shoryuken/util.rb +32 -7
  66. data/lib/shoryuken/version.rb +4 -1
  67. data/lib/shoryuken/worker/default_executor.rb +23 -1
  68. data/lib/shoryuken/worker/inline_executor.rb +33 -2
  69. data/lib/shoryuken/worker.rb +224 -0
  70. data/lib/shoryuken/worker_registry.rb +35 -0
  71. data/lib/shoryuken.rb +27 -38
  72. data/renovate.json +62 -0
  73. data/shoryuken.gemspec +8 -4
  74. data/spec/integration/.rspec +1 -0
  75. data/spec/integration/active_job/adapter_configuration/configuration_spec.rb +26 -0
  76. data/spec/integration/active_job/bulk_enqueue/bulk_enqueue_spec.rb +53 -0
  77. data/spec/integration/active_job/current_attributes/bulk_enqueue_spec.rb +50 -0
  78. data/spec/integration/active_job/current_attributes/complex_types_spec.rb +55 -0
  79. data/spec/integration/active_job/current_attributes/empty_context_spec.rb +41 -0
  80. data/spec/integration/active_job/current_attributes/full_context_spec.rb +63 -0
  81. data/spec/integration/active_job/current_attributes/partial_context_spec.rb +57 -0
  82. data/spec/integration/active_job/custom_attributes/number_attributes_spec.rb +37 -0
  83. data/spec/integration/active_job/custom_attributes/string_attributes_spec.rb +39 -0
  84. data/spec/integration/active_job/error_handling/job_wrapper_spec.rb +53 -0
  85. data/spec/integration/active_job/fifo_and_attributes/deduplication_spec.rb +86 -0
  86. data/spec/integration/active_job/keyword_arguments/keyword_arguments_spec.rb +63 -0
  87. data/spec/integration/active_job/retry/discard_on_spec.rb +43 -0
  88. data/spec/integration/active_job/retry/retry_on_spec.rb +36 -0
  89. data/spec/integration/active_job/roundtrip/roundtrip_spec.rb +52 -0
  90. data/spec/integration/active_job/scheduled/scheduled_spec.rb +76 -0
  91. data/spec/integration/active_record_middleware/active_record_middleware_spec.rb +84 -0
  92. data/spec/integration/auto_delete/auto_delete_spec.rb +53 -0
  93. data/spec/integration/auto_extend_visibility/auto_extend_visibility_spec.rb +57 -0
  94. data/spec/integration/aws_config/aws_config_spec.rb +59 -0
  95. data/spec/integration/batch_processing/batch_processing_spec.rb +37 -0
  96. data/spec/integration/body_parser/json_parser_spec.rb +45 -0
  97. data/spec/integration/body_parser/proc_parser_spec.rb +54 -0
  98. data/spec/integration/body_parser/text_parser_spec.rb +43 -0
  99. data/spec/integration/concurrent_processing/concurrent_processing_spec.rb +45 -0
  100. data/spec/integration/custom_group_polling_strategy/custom_group_polling_strategy_spec.rb +87 -0
  101. data/spec/integration/dead_letter_queue/dead_letter_queue_spec.rb +91 -0
  102. data/spec/integration/exception_handlers/exception_handlers_spec.rb +69 -0
  103. data/spec/integration/exponential_backoff/exponential_backoff_spec.rb +67 -0
  104. data/spec/integration/fifo_ordering/fifo_ordering_spec.rb +44 -0
  105. data/spec/integration/large_payloads/large_payloads_spec.rb +30 -0
  106. data/spec/integration/launcher/launcher_spec.rb +40 -0
  107. data/spec/integration/message_attributes/message_attributes_spec.rb +54 -0
  108. data/spec/integration/message_operations/message_operations_spec.rb +59 -0
  109. data/spec/integration/middleware_chain/empty_chain_spec.rb +11 -0
  110. data/spec/integration/middleware_chain/execution_order_spec.rb +33 -0
  111. data/spec/integration/middleware_chain/removal_spec.rb +31 -0
  112. data/spec/integration/middleware_chain/short_circuit_spec.rb +40 -0
  113. data/spec/integration/non_retryable_exception/non_retryable_exception_spec.rb +149 -0
  114. data/spec/integration/polling_strategies/polling_strategies_spec.rb +46 -0
  115. data/spec/integration/queue_operations/queue_operations_spec.rb +84 -0
  116. data/spec/integration/rails/rails_72/Gemfile +6 -0
  117. data/spec/integration/rails/rails_72/activejob_adapter_spec.rb +98 -0
  118. data/spec/integration/rails/rails_80/Gemfile +6 -0
  119. data/spec/integration/rails/rails_80/activejob_adapter_spec.rb +98 -0
  120. data/spec/integration/rails/rails_80/continuation_spec.rb +79 -0
  121. data/spec/integration/rails/rails_81/Gemfile +6 -0
  122. data/spec/integration/rails/rails_81/activejob_adapter_spec.rb +98 -0
  123. data/spec/integration/rails/rails_81/continuation_spec.rb +79 -0
  124. data/spec/integration/retry_behavior/retry_behavior_spec.rb +45 -0
  125. data/spec/integration/spec_helper.rb +7 -0
  126. data/spec/integration/strict_priority_polling/strict_priority_polling_spec.rb +58 -0
  127. data/spec/integration/visibility_timeout/visibility_timeout_spec.rb +37 -0
  128. data/spec/integration/worker_enqueueing/worker_enqueueing_spec.rb +60 -0
  129. data/spec/integration/worker_groups/worker_groups_spec.rb +79 -0
  130. data/spec/integration/worker_lifecycle/worker_lifecycle_spec.rb +33 -0
  131. data/spec/integrations_helper.rb +243 -0
  132. data/spec/lib/active_job/extensions_spec.rb +225 -0
  133. data/spec/lib/active_job/queue_adapters/shoryuken_adapter_spec.rb +29 -0
  134. data/spec/{shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb → lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter_spec.rb} +5 -4
  135. data/spec/{shoryuken/extensions/active_job_wrapper_spec.rb → lib/shoryuken/active_job/job_wrapper_spec.rb} +6 -5
  136. data/spec/{shoryuken → lib/shoryuken}/body_parser_spec.rb +2 -4
  137. data/spec/{shoryuken → lib/shoryuken}/client_spec.rb +1 -1
  138. data/spec/{shoryuken → lib/shoryuken}/default_exception_handler_spec.rb +9 -10
  139. data/spec/{shoryuken → lib/shoryuken}/default_worker_registry_spec.rb +1 -2
  140. data/spec/{shoryuken → lib/shoryuken}/environment_loader_spec.rb +10 -9
  141. data/spec/{shoryuken → lib/shoryuken}/fetcher_spec.rb +23 -26
  142. data/spec/lib/shoryuken/helpers/atomic_boolean_spec.rb +196 -0
  143. data/spec/lib/shoryuken/helpers/atomic_counter_spec.rb +177 -0
  144. data/spec/lib/shoryuken/helpers/atomic_hash_spec.rb +307 -0
  145. data/spec/lib/shoryuken/helpers/hash_utils_spec.rb +145 -0
  146. data/spec/lib/shoryuken/helpers/string_utils_spec.rb +124 -0
  147. data/spec/lib/shoryuken/helpers/timer_task_spec.rb +298 -0
  148. data/spec/lib/shoryuken/helpers_integration_spec.rb +96 -0
  149. data/spec/lib/shoryuken/inline_message_spec.rb +196 -0
  150. data/spec/{shoryuken → lib/shoryuken}/launcher_spec.rb +23 -2
  151. data/spec/lib/shoryuken/logging_spec.rb +242 -0
  152. data/spec/{shoryuken → lib/shoryuken}/manager_spec.rb +1 -2
  153. data/spec/lib/shoryuken/message_spec.rb +109 -0
  154. data/spec/{shoryuken → lib/shoryuken}/middleware/chain_spec.rb +1 -1
  155. data/spec/lib/shoryuken/middleware/entry_spec.rb +68 -0
  156. data/spec/lib/shoryuken/middleware/server/active_record_spec.rb +133 -0
  157. data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_delete_spec.rb +1 -1
  158. data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_extend_visibility_spec.rb +51 -1
  159. data/spec/{shoryuken → lib/shoryuken}/middleware/server/exponential_backoff_retry_spec.rb +1 -1
  160. data/spec/lib/shoryuken/middleware/server/non_retryable_exception_spec.rb +214 -0
  161. data/spec/{shoryuken → lib/shoryuken}/middleware/server/timing_spec.rb +1 -1
  162. data/spec/{shoryuken → lib/shoryuken}/options_spec.rb +49 -6
  163. data/spec/lib/shoryuken/polling/base_strategy_spec.rb +280 -0
  164. data/spec/lib/shoryuken/polling/queue_configuration_spec.rb +195 -0
  165. data/spec/{shoryuken → lib/shoryuken}/polling/strict_priority_spec.rb +1 -1
  166. data/spec/{shoryuken → lib/shoryuken}/polling/weighted_round_robin_spec.rb +1 -1
  167. data/spec/{shoryuken → lib/shoryuken}/processor_spec.rb +1 -1
  168. data/spec/{shoryuken → lib/shoryuken}/queue_spec.rb +2 -3
  169. data/spec/{shoryuken → lib/shoryuken}/runner_spec.rb +1 -3
  170. data/spec/{shoryuken → lib/shoryuken}/util_spec.rb +2 -2
  171. data/spec/lib/shoryuken/version_spec.rb +17 -0
  172. data/spec/{shoryuken → lib/shoryuken}/worker/default_executor_spec.rb +1 -1
  173. data/spec/lib/shoryuken/worker/inline_executor_spec.rb +105 -0
  174. data/spec/lib/shoryuken/worker_registry_spec.rb +63 -0
  175. data/spec/{shoryuken → lib/shoryuken}/worker_spec.rb +15 -11
  176. data/spec/{shoryuken_spec.rb → lib/shoryuken_spec.rb} +1 -1
  177. data/spec/shared_examples_for_active_job.rb +40 -15
  178. data/spec/spec_helper.rb +48 -2
  179. metadata +295 -101
  180. data/.codeclimate.yml +0 -20
  181. data/.devcontainer/Dockerfile +0 -17
  182. data/.devcontainer/base.Dockerfile +0 -43
  183. data/.devcontainer/devcontainer.json +0 -35
  184. data/.github/FUNDING.yml +0 -12
  185. data/.github/dependabot.yml +0 -6
  186. data/.github/workflows/stale.yml +0 -20
  187. data/.reek.yml +0 -5
  188. data/Appraisals +0 -42
  189. data/gemfiles/.gitignore +0 -1
  190. data/gemfiles/aws_sdk_core_2.gemfile +0 -21
  191. data/gemfiles/rails_4_2.gemfile +0 -20
  192. data/gemfiles/rails_5_2.gemfile +0 -21
  193. data/gemfiles/rails_6_0.gemfile +0 -21
  194. data/gemfiles/rails_6_1.gemfile +0 -21
  195. data/gemfiles/rails_7_0.gemfile +0 -22
  196. data/lib/shoryuken/core_ext.rb +0 -69
  197. data/lib/shoryuken/extensions/active_job_adapter.rb +0 -103
  198. data/lib/shoryuken/extensions/active_job_concurrent_send_adapter.rb +0 -50
  199. data/lib/shoryuken/polling/base.rb +0 -67
  200. data/shoryuken.jpg +0 -0
  201. data/spec/integration/launcher_spec.rb +0 -128
  202. data/spec/shoryuken/core_ext_spec.rb +0 -40
  203. data/spec/shoryuken/extensions/active_job_adapter_spec.rb +0 -7
  204. data/spec/shoryuken/extensions/active_job_base_spec.rb +0 -84
  205. data/spec/shoryuken/worker/inline_executor_spec.rb +0 -49
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Shoryuken::InlineMessage do
4
+ let(:body) { '{"message": "test"}' }
5
+ let(:attributes) { { 'SentTimestamp' => '1234567890' } }
6
+ let(:md5_of_body) { 'abc123def456' }
7
+ let(:md5_of_message_attributes) { 'def456abc123' }
8
+ let(:message_attributes) { { 'CustomAttribute' => { string_value: 'value', data_type: 'String' } } }
9
+ let(:message_id) { 'msg-12345' }
10
+ let(:receipt_handle) { 'receipt-handle-12345' }
11
+ let(:delete) { nil }
12
+ let(:queue_name) { 'test-queue' }
13
+
14
+ describe '#new' do
15
+ context 'with positional arguments' do
16
+ subject do
17
+ described_class.new(
18
+ body: body,
19
+ attributes: attributes,
20
+ md5_of_body: md5_of_body,
21
+ md5_of_message_attributes: md5_of_message_attributes,
22
+ message_attributes: message_attributes,
23
+ message_id: message_id,
24
+ receipt_handle: receipt_handle,
25
+ delete: delete,
26
+ queue_name: queue_name
27
+ )
28
+ end
29
+
30
+ it 'initializes with all attributes' do
31
+ expect(subject.body).to eq(body)
32
+ expect(subject.attributes).to eq(attributes)
33
+ expect(subject.md5_of_body).to eq(md5_of_body)
34
+ expect(subject.md5_of_message_attributes).to eq(md5_of_message_attributes)
35
+ expect(subject.message_attributes).to eq(message_attributes)
36
+ expect(subject.message_id).to eq(message_id)
37
+ expect(subject.receipt_handle).to eq(receipt_handle)
38
+ expect(subject.delete).to eq(delete)
39
+ expect(subject.queue_name).to eq(queue_name)
40
+ end
41
+ end
42
+
43
+ context 'with keyword arguments' do
44
+ subject do
45
+ described_class.new(
46
+ body: body,
47
+ attributes: attributes,
48
+ md5_of_body: md5_of_body,
49
+ md5_of_message_attributes: md5_of_message_attributes,
50
+ message_attributes: message_attributes,
51
+ message_id: message_id,
52
+ receipt_handle: receipt_handle,
53
+ delete: delete,
54
+ queue_name: queue_name
55
+ )
56
+ end
57
+
58
+ it 'initializes with all attributes' do
59
+ expect(subject.body).to eq(body)
60
+ expect(subject.attributes).to eq(attributes)
61
+ expect(subject.md5_of_body).to eq(md5_of_body)
62
+ expect(subject.md5_of_message_attributes).to eq(md5_of_message_attributes)
63
+ expect(subject.message_attributes).to eq(message_attributes)
64
+ expect(subject.message_id).to eq(message_id)
65
+ expect(subject.receipt_handle).to eq(receipt_handle)
66
+ expect(subject.delete).to eq(delete)
67
+ expect(subject.queue_name).to eq(queue_name)
68
+ end
69
+ end
70
+
71
+ context 'with nil values' do
72
+ subject do
73
+ described_class.new(
74
+ body: body,
75
+ attributes: nil,
76
+ md5_of_body: nil,
77
+ md5_of_message_attributes: nil,
78
+ message_attributes: message_attributes,
79
+ message_id: nil,
80
+ receipt_handle: nil,
81
+ delete: nil,
82
+ queue_name: queue_name
83
+ )
84
+ end
85
+
86
+ it 'handles nil values correctly' do
87
+ expect(subject.body).to eq(body)
88
+ expect(subject.attributes).to be_nil
89
+ expect(subject.md5_of_body).to be_nil
90
+ expect(subject.md5_of_message_attributes).to be_nil
91
+ expect(subject.message_attributes).to eq(message_attributes)
92
+ expect(subject.message_id).to be_nil
93
+ expect(subject.receipt_handle).to be_nil
94
+ expect(subject.delete).to be_nil
95
+ expect(subject.queue_name).to eq(queue_name)
96
+ end
97
+ end
98
+
99
+ context 'with minimal required attributes' do
100
+ subject { described_class.new(body: body, queue_name: queue_name) }
101
+
102
+ it 'initializes with only required attributes' do
103
+ expect(subject.body).to eq(body)
104
+ expect(subject.queue_name).to eq(queue_name)
105
+ expect(subject.attributes).to be_nil
106
+ expect(subject.md5_of_body).to be_nil
107
+ expect(subject.md5_of_message_attributes).to be_nil
108
+ expect(subject.message_attributes).to be_nil
109
+ expect(subject.message_id).to be_nil
110
+ expect(subject.receipt_handle).to be_nil
111
+ expect(subject.delete).to be_nil
112
+ end
113
+ end
114
+ end
115
+
116
+ describe 'attribute accessors' do
117
+ subject do
118
+ described_class.new(
119
+ body: body,
120
+ attributes: attributes,
121
+ md5_of_body: md5_of_body,
122
+ md5_of_message_attributes: md5_of_message_attributes,
123
+ message_attributes: message_attributes,
124
+ message_id: message_id,
125
+ receipt_handle: receipt_handle,
126
+ delete: delete,
127
+ queue_name: queue_name
128
+ )
129
+ end
130
+
131
+ it 'provides read access to all attributes' do
132
+ expect(subject.body).to eq(body)
133
+ expect(subject.attributes).to eq(attributes)
134
+ expect(subject.md5_of_body).to eq(md5_of_body)
135
+ expect(subject.md5_of_message_attributes).to eq(md5_of_message_attributes)
136
+ expect(subject.message_attributes).to eq(message_attributes)
137
+ expect(subject.message_id).to eq(message_id)
138
+ expect(subject.receipt_handle).to eq(receipt_handle)
139
+ expect(subject.delete).to eq(delete)
140
+ expect(subject.queue_name).to eq(queue_name)
141
+ end
142
+
143
+ it 'provides write access to all attributes' do
144
+ new_body = '{"updated": "message"}'
145
+ new_queue_name = 'updated-queue'
146
+
147
+ subject.body = new_body
148
+ subject.queue_name = new_queue_name
149
+
150
+ expect(subject.body).to eq(new_body)
151
+ expect(subject.queue_name).to eq(new_queue_name)
152
+ end
153
+ end
154
+
155
+ describe 'struct behavior' do
156
+ subject { described_class.new(body: body, queue_name: queue_name) }
157
+
158
+ it 'behaves like a struct' do
159
+ expect(subject).to be_a(Struct)
160
+ expect(subject.class.superclass).to eq(Struct)
161
+ end
162
+
163
+ it 'supports array-like access' do
164
+ expect(subject[0]).to eq(body) # body is first attribute
165
+ expect(subject[-1]).to eq(queue_name) # queue_name is last attribute
166
+ end
167
+
168
+ it 'supports enumeration' do
169
+ values = subject.to_a
170
+ expect(values.first).to eq(body)
171
+ expect(values.last).to eq(queue_name)
172
+ expect(values.length).to eq(9) # 9 attributes total
173
+ end
174
+
175
+ it 'supports hash conversion' do
176
+ hash = subject.to_h
177
+ expect(hash[:body]).to eq(body)
178
+ expect(hash[:queue_name]).to eq(queue_name)
179
+ expect(hash.keys).to contain_exactly(
180
+ :body, :attributes, :md5_of_body, :md5_of_message_attributes,
181
+ :message_attributes, :message_id, :receipt_handle, :delete, :queue_name
182
+ )
183
+ end
184
+ end
185
+
186
+ describe 'equality' do
187
+ let(:message1) { described_class.new(body: body, queue_name: queue_name) }
188
+ let(:message2) { described_class.new(body: body, queue_name: queue_name) }
189
+ let(:message3) { described_class.new(body: 'different', queue_name: queue_name) }
190
+
191
+ it 'compares messages by attribute values' do
192
+ expect(message1).to eq(message2)
193
+ expect(message1).not_to eq(message3)
194
+ end
195
+ end
196
+ end
@@ -1,5 +1,4 @@
1
- require 'spec_helper'
2
- require 'shoryuken/launcher'
1
+ # frozen_string_literal: true
3
2
 
4
3
  RSpec.describe Shoryuken::Launcher do
5
4
  let(:executor) do
@@ -102,4 +101,26 @@ RSpec.describe Shoryuken::Launcher do
102
101
  expect(second_group_manager).to have_received(:stop_new_dispatching)
103
102
  end
104
103
  end
104
+
105
+ describe '#stopping?' do
106
+ it 'returns false by default' do
107
+ expect(subject.stopping?).to be false
108
+ end
109
+
110
+ it 'returns true after stop is called' do
111
+ allow(first_group_manager).to receive(:stop_new_dispatching)
112
+ allow(first_group_manager).to receive(:await_dispatching_in_progress)
113
+ allow(second_group_manager).to receive(:stop_new_dispatching)
114
+ allow(second_group_manager).to receive(:await_dispatching_in_progress)
115
+
116
+ expect { subject.stop }.to change { subject.stopping? }.from(false).to(true)
117
+ end
118
+
119
+ it 'returns true after stop! is called' do
120
+ allow(first_group_manager).to receive(:stop_new_dispatching)
121
+ allow(second_group_manager).to receive(:stop_new_dispatching)
122
+
123
+ expect { subject.stop! }.to change { subject.stopping? }.from(false).to(true)
124
+ end
125
+ end
105
126
  end
@@ -0,0 +1,242 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Shoryuken::Logging do
6
+ describe Shoryuken::Logging::Base do
7
+ let(:formatter) { described_class.new }
8
+
9
+ describe '#tid' do
10
+ it 'returns a string representing the thread ID' do
11
+ expect(formatter.tid).to be_a(String)
12
+ end
13
+
14
+ it 'returns the same value for the same thread' do
15
+ tid1 = formatter.tid
16
+ tid2 = formatter.tid
17
+ expect(tid1).to eq(tid2)
18
+ end
19
+
20
+ it 'caches the thread ID in thread-local storage' do
21
+ tid = formatter.tid
22
+ expect(Thread.current['shoryuken_tid']).to eq(tid)
23
+ end
24
+ end
25
+
26
+ describe '#context' do
27
+ after do
28
+ Shoryuken::Logging.context_storage[:shoryuken_context] = nil
29
+ end
30
+
31
+ it 'returns empty string when no context is set' do
32
+ Shoryuken::Logging.context_storage[:shoryuken_context] = nil
33
+ expect(formatter.context).to eq('')
34
+ end
35
+
36
+ it 'returns formatted context when context is set' do
37
+ Shoryuken::Logging.context_storage[:shoryuken_context] = 'test_context'
38
+ expect(formatter.context).to eq(' test_context')
39
+ end
40
+ end
41
+ end
42
+
43
+ describe Shoryuken::Logging::Pretty do
44
+ let(:formatter) { described_class.new }
45
+ let(:time) { Time.new(2023, 8, 15, 10, 30, 45, '+00:00') }
46
+
47
+ describe '#call' do
48
+ after do
49
+ Shoryuken::Logging.context_storage[:shoryuken_context] = nil
50
+ end
51
+
52
+ it 'formats log messages with timestamp' do
53
+ allow(formatter).to receive(:tid).and_return('abc123')
54
+ Shoryuken::Logging.context_storage[:shoryuken_context] = nil
55
+
56
+ result = formatter.call('INFO', time, 'program', 'test message')
57
+ expect(result).to eq("2023-08-15T10:30:45Z #{Process.pid} TID-abc123 INFO: test message\n")
58
+ end
59
+
60
+ it 'includes context when present' do
61
+ allow(formatter).to receive(:tid).and_return('abc123')
62
+ Shoryuken::Logging.context_storage[:shoryuken_context] = 'worker-1'
63
+
64
+ result = formatter.call('ERROR', time, 'program', 'error message')
65
+ expect(result).to eq("2023-08-15T10:30:45Z #{Process.pid} TID-abc123 worker-1 ERROR: error message\n")
66
+ end
67
+ end
68
+ end
69
+
70
+ describe Shoryuken::Logging::WithoutTimestamp do
71
+ let(:formatter) { described_class.new }
72
+
73
+ describe '#call' do
74
+ after do
75
+ Shoryuken::Logging.context_storage[:shoryuken_context] = nil
76
+ end
77
+
78
+ it 'formats log messages without timestamp' do
79
+ allow(formatter).to receive(:tid).and_return('xyz789')
80
+ Shoryuken::Logging.context_storage[:shoryuken_context] = nil
81
+
82
+ result = formatter.call('DEBUG', Time.now, 'program', 'debug message')
83
+ expect(result).to eq("pid=#{Process.pid} tid=xyz789 DEBUG: debug message\n")
84
+ end
85
+
86
+ it 'includes context when present' do
87
+ allow(formatter).to receive(:tid).and_return('xyz789')
88
+ Shoryuken::Logging.context_storage[:shoryuken_context] = 'queue-processor'
89
+
90
+ result = formatter.call('WARN', Time.now, 'program', 'warning message')
91
+ expect(result).to eq("pid=#{Process.pid} tid=xyz789 queue-processor WARN: warning message\n")
92
+ end
93
+ end
94
+ end
95
+
96
+ describe '.with_context' do
97
+ after do
98
+ described_class.context_storage[:shoryuken_context] = nil
99
+ end
100
+
101
+ it 'sets context for the duration of the block' do
102
+ described_class.with_context('test_context') do
103
+ expect(described_class.current_context).to eq('test_context')
104
+ end
105
+ end
106
+
107
+ it 'clears context after the block completes' do
108
+ described_class.with_context('test_context') do
109
+ # context is set
110
+ end
111
+ expect(described_class.current_context).to be_nil
112
+ end
113
+
114
+ it 'clears context even when an exception is raised' do
115
+ expect do
116
+ described_class.with_context('test_context') do
117
+ raise StandardError, 'test error'
118
+ end
119
+ end.to raise_error(StandardError, 'test error')
120
+
121
+ expect(described_class.current_context).to be_nil
122
+ end
123
+
124
+ it 'returns the value of the block' do
125
+ result = described_class.with_context('test_context') do
126
+ 'block_result'
127
+ end
128
+ expect(result).to eq('block_result')
129
+ end
130
+
131
+ it 'preserves outer context in nested calls' do
132
+ described_class.with_context('outer') do
133
+ expect(described_class.current_context).to eq('outer')
134
+
135
+ described_class.with_context('inner') do
136
+ expect(described_class.current_context).to eq('inner')
137
+ end
138
+
139
+ expect(described_class.current_context).to eq('outer')
140
+ end
141
+ expect(described_class.current_context).to be_nil
142
+ end
143
+
144
+ it 'restores outer context even when inner block raises' do
145
+ described_class.with_context('outer') do
146
+ expect do
147
+ described_class.with_context('inner') do
148
+ raise StandardError, 'inner error'
149
+ end
150
+ end.to raise_error(StandardError, 'inner error')
151
+
152
+ expect(described_class.current_context).to eq('outer')
153
+ end
154
+ end
155
+ end
156
+
157
+ describe '.current_context' do
158
+ after do
159
+ described_class.context_storage[:shoryuken_context] = nil
160
+ end
161
+
162
+ it 'returns nil when no context is set' do
163
+ expect(described_class.current_context).to be_nil
164
+ end
165
+
166
+ it 'returns the current context value' do
167
+ described_class.context_storage[:shoryuken_context] = 'test_value'
168
+ expect(described_class.current_context).to eq('test_value')
169
+ end
170
+ end
171
+
172
+ describe '.context_storage' do
173
+ it 'returns Fiber for fiber-local storage' do
174
+ expect(described_class.context_storage).to eq(Fiber)
175
+ end
176
+ end
177
+
178
+ describe '.initialize_logger' do
179
+ it 'creates a new Logger instance' do
180
+ logger = described_class.initialize_logger
181
+ expect(logger).to be_a(Logger)
182
+ end
183
+
184
+ it 'sets default log level to INFO' do
185
+ logger = described_class.initialize_logger
186
+ expect(logger.level).to eq(Logger::INFO)
187
+ end
188
+
189
+ it 'uses Pretty formatter by default' do
190
+ logger = described_class.initialize_logger
191
+ expect(logger.formatter).to be_a(Shoryuken::Logging::Pretty)
192
+ end
193
+
194
+ it 'accepts custom log target' do
195
+ log_target = StringIO.new
196
+ logger = described_class.initialize_logger(log_target)
197
+ expect(logger.instance_variable_get(:@logdev).dev).to eq(log_target)
198
+ end
199
+ end
200
+
201
+ describe '.logger' do
202
+ after do
203
+ # Reset the instance variable to avoid affecting other tests
204
+ described_class.instance_variable_set(:@logger, nil)
205
+ end
206
+
207
+ it 'returns a logger instance' do
208
+ expect(described_class.logger).to be_a(Logger)
209
+ end
210
+
211
+ it 'memoizes the logger instance' do
212
+ logger1 = described_class.logger
213
+ logger2 = described_class.logger
214
+ expect(logger1).to be(logger2)
215
+ end
216
+
217
+ it 'initializes logger if not already set' do
218
+ expect(described_class).to receive(:initialize_logger).and_call_original
219
+ described_class.logger
220
+ end
221
+ end
222
+
223
+ describe '.logger=' do
224
+ after do
225
+ # Reset the instance variable to avoid affecting other tests
226
+ described_class.instance_variable_set(:@logger, nil)
227
+ end
228
+
229
+ it 'sets the logger instance' do
230
+ custom_logger = Logger.new('/dev/null')
231
+ described_class.logger = custom_logger
232
+ expect(described_class.logger).to be(custom_logger)
233
+ end
234
+
235
+ it 'sets null logger when passed nil' do
236
+ described_class.logger = nil
237
+ logger = described_class.logger
238
+ # The logger should be configured to output to /dev/null
239
+ expect(logger).to be_a(Logger)
240
+ end
241
+ end
242
+ end
@@ -1,5 +1,4 @@
1
- require 'spec_helper'
2
- require 'shoryuken/manager'
1
+ # frozen_string_literal: true
3
2
 
4
3
  RSpec::Matchers.define :queue_config_of do |expected|
5
4
  match do |actual|
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Shoryuken::Message do
6
+ let(:client) { instance_double('Aws::SQS::Client') }
7
+ let(:queue) { instance_double('Shoryuken::Queue', name: 'test-queue', url: 'https://sqs.us-east-1.amazonaws.com/123456789/test-queue') }
8
+ let(:data) do
9
+ instance_double('Aws::SQS::Types::Message',
10
+ message_id: 'msg-123',
11
+ receipt_handle: 'handle-456',
12
+ md5_of_body: 'abcd1234',
13
+ body: '{"test": "data"}',
14
+ attributes: { 'ApproximateReceiveCount' => '1' },
15
+ md5_of_message_attributes: 'efgh5678',
16
+ message_attributes: { 'type' => 'test' })
17
+ end
18
+
19
+ subject { described_class.new(client, queue, data) }
20
+
21
+ describe '#initialize' do
22
+ it 'sets client, queue_url, queue_name, and data' do
23
+ expect(subject.client).to eq(client)
24
+ expect(subject.queue_url).to eq('https://sqs.us-east-1.amazonaws.com/123456789/test-queue')
25
+ expect(subject.queue_name).to eq('test-queue')
26
+ expect(subject.data).to eq(data)
27
+ end
28
+ end
29
+
30
+ describe 'delegated methods' do
31
+ it 'delegates message_id to data' do
32
+ expect(subject.message_id).to eq('msg-123')
33
+ end
34
+
35
+ it 'delegates receipt_handle to data' do
36
+ expect(subject.receipt_handle).to eq('handle-456')
37
+ end
38
+
39
+ it 'delegates md5_of_body to data' do
40
+ expect(subject.md5_of_body).to eq('abcd1234')
41
+ end
42
+
43
+ it 'delegates body to data' do
44
+ expect(subject.body).to eq('{"test": "data"}')
45
+ end
46
+
47
+ it 'delegates attributes to data' do
48
+ expect(subject.attributes).to eq({ 'ApproximateReceiveCount' => '1' })
49
+ end
50
+
51
+ it 'delegates md5_of_message_attributes to data' do
52
+ expect(subject.md5_of_message_attributes).to eq('efgh5678')
53
+ end
54
+
55
+ it 'delegates message_attributes to data' do
56
+ expect(subject.message_attributes).to eq({ 'type' => 'test' })
57
+ end
58
+ end
59
+
60
+ describe '#delete' do
61
+ it 'calls delete_message on the client with correct parameters' do
62
+ expect(client).to receive(:delete_message).with(
63
+ queue_url: 'https://sqs.us-east-1.amazonaws.com/123456789/test-queue',
64
+ receipt_handle: 'handle-456'
65
+ )
66
+
67
+ subject.delete
68
+ end
69
+ end
70
+
71
+ describe '#change_visibility' do
72
+ it 'calls change_message_visibility on the client with merged parameters' do
73
+ options = { visibility_timeout: 300 }
74
+
75
+ expect(client).to receive(:change_message_visibility).with(hash_including(
76
+ visibility_timeout: 300,
77
+ queue_url: 'https://sqs.us-east-1.amazonaws.com/123456789/test-queue',
78
+ receipt_handle: 'handle-456'
79
+ ))
80
+
81
+ subject.change_visibility(options)
82
+ end
83
+
84
+ it 'merges queue_url and receipt_handle into provided options' do
85
+ options = { visibility_timeout: 120, custom_param: 'value' }
86
+
87
+ expect(client).to receive(:change_message_visibility).with(hash_including(
88
+ visibility_timeout: 120,
89
+ custom_param: 'value',
90
+ queue_url: 'https://sqs.us-east-1.amazonaws.com/123456789/test-queue',
91
+ receipt_handle: 'handle-456'
92
+ ))
93
+
94
+ subject.change_visibility(options)
95
+ end
96
+ end
97
+
98
+ describe '#visibility_timeout=' do
99
+ it 'calls change_message_visibility on the client with the timeout' do
100
+ expect(client).to receive(:change_message_visibility).with(
101
+ queue_url: 'https://sqs.us-east-1.amazonaws.com/123456789/test-queue',
102
+ receipt_handle: 'handle-456',
103
+ visibility_timeout: 600
104
+ )
105
+
106
+ subject.visibility_timeout = 600
107
+ end
108
+ end
109
+ end
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
3
  RSpec.describe Shoryuken::Middleware::Chain do
4
4
  class CustomMiddleware
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shoryuken/middleware/entry'
4
+
5
+ RSpec.describe Shoryuken::Middleware::Entry do
6
+ describe '#initialize' do
7
+ it 'stores the middleware class' do
8
+ entry = described_class.new(String)
9
+ expect(entry.klass).to eq String
10
+ end
11
+
12
+ it 'stores initialization arguments' do
13
+ entry = described_class.new(String, 'arg1', 'arg2')
14
+ expect(entry.instance_variable_get(:@args)).to eq ['arg1', 'arg2']
15
+ end
16
+ end
17
+
18
+ describe '#make_new' do
19
+ let(:test_class) do
20
+ Class.new do
21
+ attr_reader :args
22
+
23
+ def initialize(*args)
24
+ @args = args
25
+ end
26
+ end
27
+ end
28
+
29
+ it 'creates a new instance of the stored class without arguments' do
30
+ entry = described_class.new(test_class)
31
+ instance = entry.make_new
32
+
33
+ expect(instance).to be_a test_class
34
+ expect(instance.args).to eq []
35
+ end
36
+
37
+ it 'creates a new instance with stored arguments' do
38
+ entry = described_class.new(test_class, 'arg1', 42, { key: 'value' })
39
+ instance = entry.make_new
40
+
41
+ expect(instance).to be_a test_class
42
+ expect(instance.args).to eq ['arg1', 42, { key: 'value' }]
43
+ end
44
+
45
+ it 'creates a new instance each time it is called' do
46
+ entry = described_class.new(test_class, 'shared_arg')
47
+ instance1 = entry.make_new
48
+ instance2 = entry.make_new
49
+
50
+ expect(instance1).to be_a test_class
51
+ expect(instance2).to be_a test_class
52
+ expect(instance1).not_to be instance2
53
+ expect(instance1.args).to eq instance2.args
54
+ end
55
+ end
56
+
57
+ describe '#klass' do
58
+ it 'returns the stored class' do
59
+ entry = described_class.new(Array)
60
+ expect(entry.klass).to eq Array
61
+ end
62
+
63
+ it 'is readable' do
64
+ entry = described_class.new(Hash)
65
+ expect(entry.klass).to eq Hash
66
+ end
67
+ end
68
+ end