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,298 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Shoryuken::Helpers::TimerTask do
|
|
6
|
+
let(:execution_interval) { 0.1 }
|
|
7
|
+
let!(:timer_task) do
|
|
8
|
+
described_class.new(execution_interval: execution_interval) do
|
|
9
|
+
@execution_count = (@execution_count || 0) + 1
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe '#initialize' do
|
|
14
|
+
it 'creates a timer task with the specified interval' do
|
|
15
|
+
timer = described_class.new(execution_interval: 5) {}
|
|
16
|
+
expect(timer).to be_a(described_class)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'requires a block' do
|
|
20
|
+
expect { described_class.new(execution_interval: 5) }.to raise_error(ArgumentError, 'A block must be provided')
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'requires a positive execution_interval' do
|
|
24
|
+
expect { described_class.new(execution_interval: 0) {} }.to raise_error(ArgumentError, 'execution_interval must be positive')
|
|
25
|
+
expect { described_class.new(execution_interval: -1) {} }.to raise_error(ArgumentError, 'execution_interval must be positive')
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'accepts string numbers as execution_interval' do
|
|
29
|
+
timer = described_class.new(execution_interval: '5.5') {}
|
|
30
|
+
expect(timer.instance_variable_get(:@execution_interval)).to eq(5.5)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'raises ArgumentError for non-numeric execution_interval' do
|
|
34
|
+
expect { described_class.new(execution_interval: 'invalid') {} }.to raise_error(ArgumentError)
|
|
35
|
+
expect { described_class.new(execution_interval: nil) {} }.to raise_error(TypeError)
|
|
36
|
+
expect { described_class.new(execution_interval: {}) {} }.to raise_error(TypeError)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'stores the task block in @task instance variable' do
|
|
40
|
+
task_proc = proc { puts 'test' }
|
|
41
|
+
timer = described_class.new(execution_interval: 1, &task_proc)
|
|
42
|
+
expect(timer.instance_variable_get(:@task)).to eq(task_proc)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'stores the execution interval' do
|
|
46
|
+
timer = described_class.new(execution_interval: 5) {}
|
|
47
|
+
expect(timer.instance_variable_get(:@execution_interval)).to eq(5)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'initializes state variables correctly' do
|
|
51
|
+
timer = described_class.new(execution_interval: 1) {}
|
|
52
|
+
expect(timer.instance_variable_get(:@running)).to be false
|
|
53
|
+
expect(timer.instance_variable_get(:@killed)).to be false
|
|
54
|
+
expect(timer.instance_variable_get(:@thread)).to be_nil
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe '#execute' do
|
|
59
|
+
it 'returns self for method chaining' do
|
|
60
|
+
result = timer_task.execute
|
|
61
|
+
expect(result).to eq(timer_task)
|
|
62
|
+
timer_task.kill
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'sets @running to true when executed' do
|
|
66
|
+
timer_task.execute
|
|
67
|
+
expect(timer_task.instance_variable_get(:@running)).to be true
|
|
68
|
+
timer_task.kill
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it 'creates a new thread' do
|
|
72
|
+
timer_task.execute
|
|
73
|
+
thread = timer_task.instance_variable_get(:@thread)
|
|
74
|
+
expect(thread).to be_a(Thread)
|
|
75
|
+
timer_task.kill
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'does not start multiple times' do
|
|
79
|
+
timer_task.execute
|
|
80
|
+
first_thread = timer_task.instance_variable_get(:@thread)
|
|
81
|
+
timer_task.execute
|
|
82
|
+
second_thread = timer_task.instance_variable_get(:@thread)
|
|
83
|
+
expect(first_thread).to eq(second_thread)
|
|
84
|
+
timer_task.kill
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it 'does not execute if already killed' do
|
|
88
|
+
timer_task.instance_variable_set(:@killed, true)
|
|
89
|
+
result = timer_task.execute
|
|
90
|
+
expect(result).to eq(timer_task)
|
|
91
|
+
expect(timer_task.instance_variable_get(:@thread)).to be_nil
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
describe '#kill' do
|
|
96
|
+
it 'returns true when successfully killed' do
|
|
97
|
+
timer_task.execute
|
|
98
|
+
expect(timer_task.kill).to be true
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it 'returns false when already killed' do
|
|
102
|
+
timer_task.execute
|
|
103
|
+
timer_task.kill
|
|
104
|
+
expect(timer_task.kill).to be false
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it 'sets @killed to true' do
|
|
108
|
+
timer_task.execute
|
|
109
|
+
timer_task.kill
|
|
110
|
+
expect(timer_task.instance_variable_get(:@killed)).to be true
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it 'sets @running to false' do
|
|
114
|
+
timer_task.execute
|
|
115
|
+
timer_task.kill
|
|
116
|
+
expect(timer_task.instance_variable_get(:@running)).to be false
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it 'kills the thread if alive' do
|
|
120
|
+
timer_task.execute
|
|
121
|
+
thread = timer_task.instance_variable_get(:@thread)
|
|
122
|
+
timer_task.kill
|
|
123
|
+
sleep(0.01) # Give time for thread to be killed
|
|
124
|
+
expect(thread.alive?).to be false
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it 'is safe to call multiple times' do
|
|
128
|
+
timer_task.execute
|
|
129
|
+
expect { timer_task.kill }.not_to raise_error
|
|
130
|
+
expect { timer_task.kill }.not_to raise_error
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
it 'handles case when thread is nil' do
|
|
134
|
+
timer = described_class.new(execution_interval: 1) {}
|
|
135
|
+
result = nil
|
|
136
|
+
expect { result = timer.kill }.not_to raise_error
|
|
137
|
+
expect(result).to be true
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
describe 'execution behavior' do
|
|
142
|
+
it 'executes the task at the specified interval' do
|
|
143
|
+
execution_count = 0
|
|
144
|
+
timer = described_class.new(execution_interval: 0.05) do
|
|
145
|
+
execution_count += 1
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
timer.execute
|
|
149
|
+
sleep(0.15) # Should allow for ~3 executions
|
|
150
|
+
timer.kill
|
|
151
|
+
|
|
152
|
+
expect(execution_count).to be >= 2
|
|
153
|
+
expect(execution_count).to be <= 4 # Allow some timing variance
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
it 'calls the task block correctly' do
|
|
157
|
+
task_called = false
|
|
158
|
+
timer = described_class.new(execution_interval: 0.05) do
|
|
159
|
+
task_called = true
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
timer.execute
|
|
163
|
+
sleep(0.1)
|
|
164
|
+
timer.kill
|
|
165
|
+
|
|
166
|
+
expect(task_called).to be true
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
it 'handles exceptions in the task gracefully' do
|
|
170
|
+
error_count = 0
|
|
171
|
+
timer = described_class.new(execution_interval: 0.05) do
|
|
172
|
+
error_count += 1
|
|
173
|
+
raise StandardError, 'Test error'
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Capture stderr to check for error messages
|
|
177
|
+
original_stderr = $stderr
|
|
178
|
+
captured_stderr = StringIO.new
|
|
179
|
+
$stderr = captured_stderr
|
|
180
|
+
|
|
181
|
+
# Mock warn method to prevent warning gem from raising exceptions
|
|
182
|
+
# but still capture the output
|
|
183
|
+
allow_any_instance_of(Object).to receive(:warn) do |*args|
|
|
184
|
+
captured_stderr.puts(*args)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
timer.execute
|
|
188
|
+
sleep(0.15)
|
|
189
|
+
timer.kill
|
|
190
|
+
|
|
191
|
+
error_output = captured_stderr.string
|
|
192
|
+
$stderr = original_stderr
|
|
193
|
+
|
|
194
|
+
expect(error_count).to be >= 2
|
|
195
|
+
expect(error_output).to include('Test error')
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
it 'continues execution after exceptions' do
|
|
199
|
+
execution_count = 0
|
|
200
|
+
timer = described_class.new(execution_interval: 0.05) do
|
|
201
|
+
execution_count += 1
|
|
202
|
+
raise StandardError, 'Test error' if execution_count == 1
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Mock warn method to prevent warning gem from raising exceptions
|
|
206
|
+
allow_any_instance_of(Object).to receive(:warn)
|
|
207
|
+
|
|
208
|
+
timer.execute
|
|
209
|
+
sleep(0.15)
|
|
210
|
+
timer.kill
|
|
211
|
+
|
|
212
|
+
expect(execution_count).to be >= 2 # Should continue after first error
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
it 'stops execution when killed' do
|
|
216
|
+
execution_count = 0
|
|
217
|
+
timer = described_class.new(execution_interval: 0.05) do
|
|
218
|
+
execution_count += 1
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
timer.execute
|
|
222
|
+
sleep(0.1)
|
|
223
|
+
initial_count = execution_count
|
|
224
|
+
timer.kill
|
|
225
|
+
sleep(0.1)
|
|
226
|
+
final_count = execution_count
|
|
227
|
+
|
|
228
|
+
expect(final_count).to eq(initial_count)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
it 'respects the execution interval' do
|
|
232
|
+
execution_times = []
|
|
233
|
+
timer = described_class.new(execution_interval: 0.1) do
|
|
234
|
+
execution_times << Time.now
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
timer.execute
|
|
238
|
+
sleep(0.35) # Allow for ~3 executions
|
|
239
|
+
timer.kill
|
|
240
|
+
|
|
241
|
+
expect(execution_times.length).to be >= 2
|
|
242
|
+
if execution_times.length >= 2
|
|
243
|
+
interval = execution_times[1] - execution_times[0]
|
|
244
|
+
expect(interval).to be_within(0.05).of(0.1)
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
describe 'thread safety' do
|
|
250
|
+
it 'can be safely accessed from multiple threads' do
|
|
251
|
+
timer = described_class.new(execution_interval: 0.1) {}
|
|
252
|
+
|
|
253
|
+
threads = 10.times.map do
|
|
254
|
+
Thread.new do
|
|
255
|
+
timer.execute
|
|
256
|
+
sleep(0.01)
|
|
257
|
+
timer.kill
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
threads.each(&:join)
|
|
262
|
+
# Timer should be stopped after all threads complete
|
|
263
|
+
expect(timer.instance_variable_get(:@killed)).to be true
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
it 'handles concurrent execute calls safely' do
|
|
267
|
+
timer = described_class.new(execution_interval: 0.1) {}
|
|
268
|
+
|
|
269
|
+
threads = 5.times.map do
|
|
270
|
+
Thread.new { timer.execute }
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
threads.each(&:join)
|
|
274
|
+
|
|
275
|
+
# Should only have one thread created
|
|
276
|
+
expect(timer.instance_variable_get(:@thread)).to be_a(Thread)
|
|
277
|
+
timer.kill
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
it 'handles concurrent kill calls safely' do
|
|
281
|
+
timer = described_class.new(execution_interval: 0.1) {}
|
|
282
|
+
timer.execute
|
|
283
|
+
|
|
284
|
+
threads = 5.times.map do
|
|
285
|
+
Thread.new { timer.kill }
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
results = threads.map(&:value)
|
|
289
|
+
|
|
290
|
+
# Only one kill should return true, others should return false
|
|
291
|
+
true_count = results.count(true)
|
|
292
|
+
false_count = results.count(false)
|
|
293
|
+
|
|
294
|
+
expect(true_count).to eq(1)
|
|
295
|
+
expect(false_count).to eq(4)
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
RSpec.describe 'Helpers Integration' do
|
|
4
4
|
# Integration tests for helper utility methods that replaced core extensions
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
describe Shoryuken::Helpers::HashUtils do
|
|
7
7
|
describe '.deep_symbolize_keys' do
|
|
8
8
|
it 'converts keys into symbols recursively' do
|
|
@@ -12,12 +12,12 @@ RSpec.describe 'Helpers Integration' do
|
|
|
12
12
|
'key31' => { 'key311' => 'value311' },
|
|
13
13
|
'key32' => 'value32'
|
|
14
14
|
} }
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
expected = { key1: 'value1',
|
|
17
17
|
key2: 'value2',
|
|
18
18
|
key3: { key31: { key311: 'value311' },
|
|
19
19
|
key32: 'value32' } }
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
expect(Shoryuken::Helpers::HashUtils.deep_symbolize_keys(input)).to eq(expected)
|
|
22
22
|
end
|
|
23
23
|
|
|
@@ -34,7 +34,7 @@ RSpec.describe 'Helpers Integration' do
|
|
|
34
34
|
it 'handles mixed value types' do
|
|
35
35
|
input = { 'key1' => 'string', 'key2' => 123, 'key3' => { 'nested' => true } }
|
|
36
36
|
expected = { key1: 'string', key2: 123, key3: { nested: true } }
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
expect(Shoryuken::Helpers::HashUtils.deep_symbolize_keys(input)).to eq(expected)
|
|
39
39
|
end
|
|
40
40
|
end
|
|
@@ -43,7 +43,7 @@ RSpec.describe 'Helpers Integration' do
|
|
|
43
43
|
describe Shoryuken::Helpers::StringUtils do
|
|
44
44
|
describe '.constantize' do
|
|
45
45
|
class HelloWorld; end
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
it 'returns a class from a string' do
|
|
48
48
|
expect(Shoryuken::Helpers::StringUtils.constantize('HelloWorld')).to eq(HelloWorld)
|
|
49
49
|
end
|
|
@@ -75,20 +75,20 @@ RSpec.describe 'Helpers Integration' do
|
|
|
75
75
|
'mailers' => { 'worker_class' => 'String' }
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
-
|
|
78
|
+
|
|
79
79
|
symbolized = Shoryuken::Helpers::HashUtils.deep_symbolize_keys(config_data)
|
|
80
|
-
|
|
80
|
+
|
|
81
81
|
expect(symbolized).to eq({
|
|
82
82
|
queues: {
|
|
83
83
|
default: { worker_class: 'Object' },
|
|
84
84
|
mailers: { worker_class: 'String' }
|
|
85
85
|
}
|
|
86
86
|
})
|
|
87
|
-
|
|
87
|
+
|
|
88
88
|
# Test constantizing the worker classes
|
|
89
89
|
default_worker = Shoryuken::Helpers::StringUtils.constantize(symbolized[:queues][:default][:worker_class])
|
|
90
90
|
mailer_worker = Shoryuken::Helpers::StringUtils.constantize(symbolized[:queues][:mailers][:worker_class])
|
|
91
|
-
|
|
91
|
+
|
|
92
92
|
expect(default_worker).to eq(Object)
|
|
93
93
|
expect(mailer_worker).to eq(String)
|
|
94
94
|
end
|
|
@@ -101,4 +101,26 @@ RSpec.describe Shoryuken::Launcher do
|
|
|
101
101
|
expect(second_group_manager).to have_received(:stop_new_dispatching)
|
|
102
102
|
end
|
|
103
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
|
|
104
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
|