shoryuken 6.2.1 → 7.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/push.yml +36 -0
- data/.github/workflows/specs.yml +49 -44
- data/.github/workflows/verify-action-pins.yml +16 -0
- data/.gitignore +4 -1
- data/.rspec +3 -1
- data/.rubocop.yml +6 -1
- data/.ruby-version +1 -0
- data/.yard-lint.yml +279 -0
- data/CHANGELOG.md +308 -139
- data/Gemfile +1 -8
- data/Gemfile.lint +9 -0
- data/Gemfile.lint.lock +69 -0
- data/README.md +16 -33
- data/Rakefile +6 -10
- data/bin/clean_sqs +52 -0
- data/bin/cli/base.rb +22 -2
- data/bin/cli/sqs.rb +74 -7
- data/bin/integrations +275 -0
- data/bin/scenario +154 -0
- data/bin/shoryuken +3 -2
- data/docker-compose.yml +6 -0
- data/lib/{shoryuken/extensions/active_job_extensions.rb → active_job/extensions.rb} +20 -6
- data/lib/active_job/queue_adapters/shoryuken_adapter.rb +208 -0
- data/lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter.rb +78 -0
- data/lib/shoryuken/active_job/current_attributes.rb +139 -0
- data/lib/shoryuken/active_job/job_wrapper.rb +28 -0
- data/lib/shoryuken/body_parser.rb +11 -1
- data/lib/shoryuken/client.rb +16 -0
- data/lib/shoryuken/default_exception_handler.rb +11 -0
- data/lib/shoryuken/default_worker_registry.rb +39 -11
- data/lib/shoryuken/environment_loader.rb +85 -15
- data/lib/shoryuken/errors.rb +36 -0
- data/lib/shoryuken/fetcher.rb +41 -3
- data/lib/shoryuken/helpers/atomic_boolean.rb +58 -0
- data/lib/shoryuken/helpers/atomic_counter.rb +104 -0
- data/lib/shoryuken/helpers/atomic_hash.rb +182 -0
- data/lib/shoryuken/helpers/hash_utils.rb +56 -0
- data/lib/shoryuken/helpers/string_utils.rb +65 -0
- data/lib/shoryuken/helpers/timer_task.rb +80 -0
- data/lib/shoryuken/inline_message.rb +22 -0
- data/lib/shoryuken/launcher.rb +55 -0
- data/lib/shoryuken/logging/base.rb +26 -0
- data/lib/shoryuken/logging/pretty.rb +25 -0
- data/lib/shoryuken/logging/without_timestamp.rb +25 -0
- data/lib/shoryuken/logging.rb +43 -15
- data/lib/shoryuken/manager.rb +84 -5
- data/lib/shoryuken/message.rb +116 -1
- data/lib/shoryuken/middleware/chain.rb +141 -43
- data/lib/shoryuken/middleware/entry.rb +30 -0
- data/lib/shoryuken/middleware/server/active_record.rb +10 -0
- data/lib/shoryuken/middleware/server/auto_delete.rb +12 -0
- data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +37 -11
- data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +34 -3
- data/lib/shoryuken/middleware/server/non_retryable_exception.rb +95 -0
- data/lib/shoryuken/middleware/server/timing.rb +13 -0
- data/lib/shoryuken/options.rb +154 -13
- data/lib/shoryuken/polling/base_strategy.rb +127 -0
- data/lib/shoryuken/polling/queue_configuration.rb +103 -0
- data/lib/shoryuken/polling/strict_priority.rb +41 -0
- data/lib/shoryuken/polling/weighted_round_robin.rb +44 -0
- data/lib/shoryuken/processor.rb +37 -3
- data/lib/shoryuken/queue.rb +99 -8
- data/lib/shoryuken/runner.rb +54 -16
- data/lib/shoryuken/util.rb +32 -7
- data/lib/shoryuken/version.rb +4 -1
- data/lib/shoryuken/worker/default_executor.rb +23 -1
- data/lib/shoryuken/worker/inline_executor.rb +33 -2
- data/lib/shoryuken/worker.rb +224 -0
- data/lib/shoryuken/worker_registry.rb +35 -0
- data/lib/shoryuken.rb +27 -38
- data/renovate.json +62 -0
- data/shoryuken.gemspec +8 -4
- data/spec/integration/.rspec +1 -0
- data/spec/integration/active_job/adapter_configuration/configuration_spec.rb +26 -0
- data/spec/integration/active_job/bulk_enqueue/bulk_enqueue_spec.rb +53 -0
- data/spec/integration/active_job/current_attributes/bulk_enqueue_spec.rb +50 -0
- data/spec/integration/active_job/current_attributes/complex_types_spec.rb +55 -0
- data/spec/integration/active_job/current_attributes/empty_context_spec.rb +41 -0
- data/spec/integration/active_job/current_attributes/full_context_spec.rb +63 -0
- data/spec/integration/active_job/current_attributes/partial_context_spec.rb +57 -0
- data/spec/integration/active_job/custom_attributes/number_attributes_spec.rb +37 -0
- data/spec/integration/active_job/custom_attributes/string_attributes_spec.rb +39 -0
- data/spec/integration/active_job/error_handling/job_wrapper_spec.rb +53 -0
- data/spec/integration/active_job/fifo_and_attributes/deduplication_spec.rb +86 -0
- data/spec/integration/active_job/keyword_arguments/keyword_arguments_spec.rb +63 -0
- data/spec/integration/active_job/retry/discard_on_spec.rb +43 -0
- data/spec/integration/active_job/retry/retry_on_spec.rb +36 -0
- data/spec/integration/active_job/roundtrip/roundtrip_spec.rb +52 -0
- data/spec/integration/active_job/scheduled/scheduled_spec.rb +76 -0
- data/spec/integration/active_record_middleware/active_record_middleware_spec.rb +84 -0
- data/spec/integration/auto_delete/auto_delete_spec.rb +53 -0
- data/spec/integration/auto_extend_visibility/auto_extend_visibility_spec.rb +57 -0
- data/spec/integration/aws_config/aws_config_spec.rb +59 -0
- data/spec/integration/batch_processing/batch_processing_spec.rb +37 -0
- data/spec/integration/body_parser/json_parser_spec.rb +45 -0
- data/spec/integration/body_parser/proc_parser_spec.rb +54 -0
- data/spec/integration/body_parser/text_parser_spec.rb +43 -0
- data/spec/integration/concurrent_processing/concurrent_processing_spec.rb +45 -0
- data/spec/integration/custom_group_polling_strategy/custom_group_polling_strategy_spec.rb +87 -0
- data/spec/integration/dead_letter_queue/dead_letter_queue_spec.rb +91 -0
- data/spec/integration/exception_handlers/exception_handlers_spec.rb +69 -0
- data/spec/integration/exponential_backoff/exponential_backoff_spec.rb +67 -0
- data/spec/integration/fifo_ordering/fifo_ordering_spec.rb +44 -0
- data/spec/integration/large_payloads/large_payloads_spec.rb +30 -0
- data/spec/integration/launcher/launcher_spec.rb +40 -0
- data/spec/integration/message_attributes/message_attributes_spec.rb +54 -0
- data/spec/integration/message_operations/message_operations_spec.rb +59 -0
- data/spec/integration/middleware_chain/empty_chain_spec.rb +11 -0
- data/spec/integration/middleware_chain/execution_order_spec.rb +33 -0
- data/spec/integration/middleware_chain/removal_spec.rb +31 -0
- data/spec/integration/middleware_chain/short_circuit_spec.rb +40 -0
- data/spec/integration/non_retryable_exception/non_retryable_exception_spec.rb +149 -0
- data/spec/integration/polling_strategies/polling_strategies_spec.rb +46 -0
- data/spec/integration/queue_operations/queue_operations_spec.rb +84 -0
- data/spec/integration/rails/rails_72/Gemfile +6 -0
- data/spec/integration/rails/rails_72/activejob_adapter_spec.rb +98 -0
- data/spec/integration/rails/rails_80/Gemfile +6 -0
- data/spec/integration/rails/rails_80/activejob_adapter_spec.rb +98 -0
- data/spec/integration/rails/rails_80/continuation_spec.rb +79 -0
- data/spec/integration/rails/rails_81/Gemfile +6 -0
- data/spec/integration/rails/rails_81/activejob_adapter_spec.rb +98 -0
- data/spec/integration/rails/rails_81/continuation_spec.rb +79 -0
- data/spec/integration/retry_behavior/retry_behavior_spec.rb +45 -0
- data/spec/integration/spec_helper.rb +7 -0
- data/spec/integration/strict_priority_polling/strict_priority_polling_spec.rb +58 -0
- data/spec/integration/visibility_timeout/visibility_timeout_spec.rb +37 -0
- data/spec/integration/worker_enqueueing/worker_enqueueing_spec.rb +60 -0
- data/spec/integration/worker_groups/worker_groups_spec.rb +79 -0
- data/spec/integration/worker_lifecycle/worker_lifecycle_spec.rb +33 -0
- data/spec/integrations_helper.rb +243 -0
- data/spec/lib/active_job/extensions_spec.rb +225 -0
- data/spec/lib/active_job/queue_adapters/shoryuken_adapter_spec.rb +29 -0
- data/spec/{shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb → lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter_spec.rb} +5 -4
- data/spec/{shoryuken/extensions/active_job_wrapper_spec.rb → lib/shoryuken/active_job/job_wrapper_spec.rb} +6 -5
- data/spec/{shoryuken → lib/shoryuken}/body_parser_spec.rb +2 -4
- data/spec/{shoryuken → lib/shoryuken}/client_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/default_exception_handler_spec.rb +9 -10
- data/spec/{shoryuken → lib/shoryuken}/default_worker_registry_spec.rb +1 -2
- data/spec/{shoryuken → lib/shoryuken}/environment_loader_spec.rb +10 -9
- data/spec/{shoryuken → lib/shoryuken}/fetcher_spec.rb +23 -26
- data/spec/lib/shoryuken/helpers/atomic_boolean_spec.rb +196 -0
- data/spec/lib/shoryuken/helpers/atomic_counter_spec.rb +177 -0
- data/spec/lib/shoryuken/helpers/atomic_hash_spec.rb +307 -0
- data/spec/lib/shoryuken/helpers/hash_utils_spec.rb +145 -0
- data/spec/lib/shoryuken/helpers/string_utils_spec.rb +124 -0
- data/spec/lib/shoryuken/helpers/timer_task_spec.rb +298 -0
- data/spec/lib/shoryuken/helpers_integration_spec.rb +96 -0
- data/spec/lib/shoryuken/inline_message_spec.rb +196 -0
- data/spec/{shoryuken → lib/shoryuken}/launcher_spec.rb +23 -2
- data/spec/lib/shoryuken/logging_spec.rb +242 -0
- data/spec/{shoryuken → lib/shoryuken}/manager_spec.rb +1 -2
- data/spec/lib/shoryuken/message_spec.rb +109 -0
- data/spec/{shoryuken → lib/shoryuken}/middleware/chain_spec.rb +1 -1
- data/spec/lib/shoryuken/middleware/entry_spec.rb +68 -0
- data/spec/lib/shoryuken/middleware/server/active_record_spec.rb +133 -0
- data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_delete_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_extend_visibility_spec.rb +51 -1
- data/spec/{shoryuken → lib/shoryuken}/middleware/server/exponential_backoff_retry_spec.rb +1 -1
- data/spec/lib/shoryuken/middleware/server/non_retryable_exception_spec.rb +214 -0
- data/spec/{shoryuken → lib/shoryuken}/middleware/server/timing_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/options_spec.rb +49 -6
- data/spec/lib/shoryuken/polling/base_strategy_spec.rb +280 -0
- data/spec/lib/shoryuken/polling/queue_configuration_spec.rb +195 -0
- data/spec/{shoryuken → lib/shoryuken}/polling/strict_priority_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/polling/weighted_round_robin_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/processor_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/queue_spec.rb +2 -3
- data/spec/{shoryuken → lib/shoryuken}/runner_spec.rb +1 -3
- data/spec/{shoryuken → lib/shoryuken}/util_spec.rb +2 -2
- data/spec/lib/shoryuken/version_spec.rb +17 -0
- data/spec/{shoryuken → lib/shoryuken}/worker/default_executor_spec.rb +1 -1
- data/spec/lib/shoryuken/worker/inline_executor_spec.rb +105 -0
- data/spec/lib/shoryuken/worker_registry_spec.rb +63 -0
- data/spec/{shoryuken → lib/shoryuken}/worker_spec.rb +15 -11
- data/spec/{shoryuken_spec.rb → lib/shoryuken_spec.rb} +1 -1
- data/spec/shared_examples_for_active_job.rb +40 -15
- data/spec/spec_helper.rb +48 -2
- metadata +295 -101
- data/.codeclimate.yml +0 -20
- data/.devcontainer/Dockerfile +0 -17
- data/.devcontainer/base.Dockerfile +0 -43
- data/.devcontainer/devcontainer.json +0 -35
- data/.github/FUNDING.yml +0 -12
- data/.github/dependabot.yml +0 -6
- data/.github/workflows/stale.yml +0 -20
- data/.reek.yml +0 -5
- data/Appraisals +0 -42
- data/gemfiles/.gitignore +0 -1
- data/gemfiles/aws_sdk_core_2.gemfile +0 -21
- data/gemfiles/rails_4_2.gemfile +0 -20
- data/gemfiles/rails_5_2.gemfile +0 -21
- data/gemfiles/rails_6_0.gemfile +0 -21
- data/gemfiles/rails_6_1.gemfile +0 -21
- data/gemfiles/rails_7_0.gemfile +0 -22
- data/lib/shoryuken/core_ext.rb +0 -69
- data/lib/shoryuken/extensions/active_job_adapter.rb +0 -103
- data/lib/shoryuken/extensions/active_job_concurrent_send_adapter.rb +0 -50
- data/lib/shoryuken/polling/base.rb +0 -67
- data/shoryuken.jpg +0 -0
- data/spec/integration/launcher_spec.rb +0 -128
- data/spec/shoryuken/core_ext_spec.rb +0 -40
- data/spec/shoryuken/extensions/active_job_adapter_spec.rb +0 -7
- data/spec/shoryuken/extensions/active_job_base_spec.rb +0 -84
- data/spec/shoryuken/worker/inline_executor_spec.rb +0 -49
data/bin/integrations
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Shoryuken integration test runner
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# bin/integrations # Run all integration tests
|
|
8
|
+
# bin/integrations fifo # Run tests with 'fifo' in path
|
|
9
|
+
# bin/integrations rails/rails_72 # Run Rails 7.2 tests
|
|
10
|
+
# bin/integrations batch retry # Run tests matching 'batch' OR 'retry'
|
|
11
|
+
# bin/integrations -v fifo # Run with verbose output
|
|
12
|
+
|
|
13
|
+
require 'bundler'
|
|
14
|
+
require 'fileutils'
|
|
15
|
+
require 'timeout'
|
|
16
|
+
|
|
17
|
+
TIMEOUT = 300 # 5 minutes per scenario
|
|
18
|
+
SPEC_DIR = File.expand_path('../spec/integration', __dir__)
|
|
19
|
+
ROOT_DIR = File.expand_path('..', __dir__)
|
|
20
|
+
|
|
21
|
+
class IntegrationRunner
|
|
22
|
+
def initialize(args)
|
|
23
|
+
@verbose = args.delete('-v') || args.delete('--verbose')
|
|
24
|
+
@filters = args.reject { |a| a.start_with?('-') }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def run
|
|
28
|
+
specs = find_specs
|
|
29
|
+
specs = filter_specs(specs) if @filters.any?
|
|
30
|
+
|
|
31
|
+
if specs.empty?
|
|
32
|
+
puts 'No specs found matching filters'
|
|
33
|
+
exit 1
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
puts "Running #{specs.size} integration specs..."
|
|
37
|
+
puts
|
|
38
|
+
|
|
39
|
+
results = run_specs(specs)
|
|
40
|
+
report_results(results)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def find_specs
|
|
46
|
+
Dir.glob(File.join(SPEC_DIR, '**/*_spec.rb')).reject do |path|
|
|
47
|
+
# Exclude vendor and .bundle directories
|
|
48
|
+
path.include?('/vendor/') || path.include?('/.bundle/')
|
|
49
|
+
end.map do |path|
|
|
50
|
+
relative_path = path.sub("#{SPEC_DIR}/", '')
|
|
51
|
+
dir = File.dirname(path)
|
|
52
|
+
gemfile = File.exist?(File.join(dir, 'Gemfile')) ? File.join(dir, 'Gemfile') : File.join(ROOT_DIR, 'Gemfile')
|
|
53
|
+
|
|
54
|
+
{
|
|
55
|
+
name: relative_path.sub('_spec.rb', '').gsub('/', ' / '),
|
|
56
|
+
path: path,
|
|
57
|
+
relative_path: relative_path,
|
|
58
|
+
directory: dir,
|
|
59
|
+
gemfile: gemfile
|
|
60
|
+
}
|
|
61
|
+
end.sort_by { |s| s[:relative_path] }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def filter_specs(specs)
|
|
65
|
+
specs.select do |spec|
|
|
66
|
+
@filters.any? { |filter| spec[:relative_path].include?(filter) }
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def run_specs(specs)
|
|
71
|
+
results = []
|
|
72
|
+
|
|
73
|
+
specs.each do |spec|
|
|
74
|
+
result = run_spec(spec)
|
|
75
|
+
results << result
|
|
76
|
+
|
|
77
|
+
if result[:skipped]
|
|
78
|
+
print 'S'
|
|
79
|
+
elsif result[:success]
|
|
80
|
+
print '.'
|
|
81
|
+
else
|
|
82
|
+
print 'F'
|
|
83
|
+
end
|
|
84
|
+
$stdout.flush
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
puts
|
|
88
|
+
results
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def run_spec(spec)
|
|
92
|
+
# Start with a clean bundler environment to prevent pollution between tests
|
|
93
|
+
env = {
|
|
94
|
+
'BUNDLE_GEMFILE' => spec[:gemfile],
|
|
95
|
+
'RAILS_ENV' => 'test',
|
|
96
|
+
'RUBYOPT' => nil # Clear any -rbundler/setup from CI or previous tests
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# Install dependencies if using a local Gemfile
|
|
100
|
+
uses_local_gemfile = spec[:gemfile] != File.join(ROOT_DIR, 'Gemfile')
|
|
101
|
+
if uses_local_gemfile
|
|
102
|
+
install_result = install_bundle(spec, env)
|
|
103
|
+
unless install_result[:success]
|
|
104
|
+
# Skip test if bundle install fails (e.g., gems not available in CI)
|
|
105
|
+
return {
|
|
106
|
+
spec: spec,
|
|
107
|
+
success: true,
|
|
108
|
+
skipped: true,
|
|
109
|
+
skip_reason: 'Bundle install failed (dependencies not available)',
|
|
110
|
+
output: install_result[:output]
|
|
111
|
+
}
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Use isolated bundle config to match install_bundle
|
|
115
|
+
bundle_path = File.join(spec[:directory], 'vendor', 'bundle')
|
|
116
|
+
bundle_config = File.join(spec[:directory], '.bundle')
|
|
117
|
+
env['BUNDLE_PATH'] = bundle_path
|
|
118
|
+
env['BUNDLE_FROZEN'] = 'false'
|
|
119
|
+
env['BUNDLE_APP_CONFIG'] = bundle_config
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Run the spec
|
|
123
|
+
# For local gemfiles, use standalone bundle setup which doesn't need bundler at runtime
|
|
124
|
+
# This avoids issues with bundle exec inheriting the wrong config
|
|
125
|
+
cmd = if uses_local_gemfile
|
|
126
|
+
standalone_setup = File.join(bundle_path, 'bundler', 'setup.rb')
|
|
127
|
+
['ruby', "-r#{standalone_setup}", File.join(ROOT_DIR, 'bin/scenario'), spec[:path]]
|
|
128
|
+
else
|
|
129
|
+
['bundle', 'exec', 'ruby', File.join(ROOT_DIR, 'bin/scenario'), spec[:path]]
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
output = []
|
|
133
|
+
start_time = Time.now
|
|
134
|
+
|
|
135
|
+
begin
|
|
136
|
+
Timeout.timeout(TIMEOUT) do
|
|
137
|
+
# Use unbundled env to prevent pollution from previous test runs
|
|
138
|
+
# This is especially important after Rails integration tests that use
|
|
139
|
+
# bundle install --standalone with different gem versions
|
|
140
|
+
Bundler.with_unbundled_env do
|
|
141
|
+
IO.popen(env, cmd, chdir: spec[:directory], err: [:child, :out]) do |io|
|
|
142
|
+
io.each_line { |line| output << line }
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
{
|
|
148
|
+
spec: spec,
|
|
149
|
+
success: $?.success?,
|
|
150
|
+
exit_code: $?.exitstatus,
|
|
151
|
+
duration: Time.now - start_time,
|
|
152
|
+
output: output.join
|
|
153
|
+
}
|
|
154
|
+
rescue Timeout::Error
|
|
155
|
+
{
|
|
156
|
+
spec: spec,
|
|
157
|
+
success: false,
|
|
158
|
+
exit_code: -1,
|
|
159
|
+
error: 'Timeout',
|
|
160
|
+
duration: Time.now - start_time,
|
|
161
|
+
output: output.join
|
|
162
|
+
}
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def install_bundle(spec, env)
|
|
167
|
+
return { success: true } if @bundle_installed&.include?(spec[:gemfile])
|
|
168
|
+
|
|
169
|
+
output = []
|
|
170
|
+
|
|
171
|
+
# Create isolated bundle environment to avoid CI cache interference
|
|
172
|
+
# Use a unique path per Gemfile to avoid conflicts
|
|
173
|
+
bundle_path = File.join(spec[:directory], 'vendor', 'bundle')
|
|
174
|
+
bundle_config = File.join(spec[:directory], '.bundle')
|
|
175
|
+
|
|
176
|
+
# Create local .bundle/config to override project-level config
|
|
177
|
+
FileUtils.mkdir_p(bundle_config)
|
|
178
|
+
File.write(File.join(bundle_config, 'config'), <<~CONFIG)
|
|
179
|
+
---
|
|
180
|
+
BUNDLE_PATH: "#{bundle_path}"
|
|
181
|
+
BUNDLE_FROZEN: "false"
|
|
182
|
+
CONFIG
|
|
183
|
+
|
|
184
|
+
clean_env = env.merge(
|
|
185
|
+
'BUNDLE_PATH' => bundle_path,
|
|
186
|
+
'BUNDLE_FROZEN' => 'false',
|
|
187
|
+
'BUNDLE_DEPLOYMENT' => nil,
|
|
188
|
+
'BUNDLE_WITHOUT' => nil,
|
|
189
|
+
'BUNDLE_CACHE_PATH' => nil,
|
|
190
|
+
'BUNDLE_BIN' => nil,
|
|
191
|
+
'BUNDLE_APP_CONFIG' => bundle_config,
|
|
192
|
+
'RUBYOPT' => nil # Clear any -rbundler/setup from CI
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Use --standalone to generate a setup.rb that doesn't need bundler at runtime
|
|
196
|
+
# Run in a completely unbundled environment using Bundler API
|
|
197
|
+
cmd_script = <<~RUBY
|
|
198
|
+
require 'bundler'
|
|
199
|
+
Bundler.with_unbundled_env do
|
|
200
|
+
system({'BUNDLE_GEMFILE' => '#{spec[:gemfile]}', 'BUNDLE_PATH' => '#{bundle_path}', 'BUNDLE_FROZEN' => 'false', 'BUNDLE_APP_CONFIG' => '#{bundle_config}'}, 'bundle', 'install', '--standalone')
|
|
201
|
+
end
|
|
202
|
+
exit($?.success? ? 0 : 1)
|
|
203
|
+
RUBY
|
|
204
|
+
|
|
205
|
+
IO.popen(['ruby', '-e', cmd_script], chdir: spec[:directory], err: [:child, :out]) do |io|
|
|
206
|
+
io.each_line { |line| output << line }
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
@bundle_installed ||= []
|
|
210
|
+
@bundle_installed << spec[:gemfile] if $?.success?
|
|
211
|
+
|
|
212
|
+
{
|
|
213
|
+
spec: spec,
|
|
214
|
+
success: $?.success?,
|
|
215
|
+
output: output.join,
|
|
216
|
+
error: $?.success? ? nil : 'Bundle install failed',
|
|
217
|
+
bundle_config: bundle_config
|
|
218
|
+
}
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def report_results(results)
|
|
222
|
+
skipped = results.select { |r| r[:skipped] }
|
|
223
|
+
failed = results.reject { |r| r[:success] || r[:skipped] }
|
|
224
|
+
passed = results.count { |r| r[:success] && !r[:skipped] }
|
|
225
|
+
total = results.size
|
|
226
|
+
|
|
227
|
+
puts
|
|
228
|
+
summary = "#{passed}/#{total} passed"
|
|
229
|
+
summary += ", #{skipped.size} skipped" if skipped.any?
|
|
230
|
+
puts summary
|
|
231
|
+
|
|
232
|
+
if skipped.any?
|
|
233
|
+
puts
|
|
234
|
+
puts 'Skipped:'
|
|
235
|
+
puts
|
|
236
|
+
skipped.each do |result|
|
|
237
|
+
puts " - #{result[:spec][:name]}"
|
|
238
|
+
puts " Reason: #{result[:skip_reason]}" if result[:skip_reason]
|
|
239
|
+
if result[:output] && !result[:output].strip.empty?
|
|
240
|
+
lines = result[:output].lines.last(15)
|
|
241
|
+
lines.each { |line| puts " #{line}" }
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
if failed.any?
|
|
247
|
+
puts
|
|
248
|
+
puts 'Failures:'
|
|
249
|
+
puts
|
|
250
|
+
|
|
251
|
+
failed.each_with_index do |result, idx|
|
|
252
|
+
puts " #{idx + 1}) #{result[:spec][:name]}"
|
|
253
|
+
if result[:error]
|
|
254
|
+
puts " Error: #{result[:error]}"
|
|
255
|
+
end
|
|
256
|
+
if result[:output] && !result[:output].strip.empty?
|
|
257
|
+
# Show last 30 lines of output for context
|
|
258
|
+
lines = result[:output].lines
|
|
259
|
+
if lines.size > 30
|
|
260
|
+
puts " ... (#{lines.size - 30} lines truncated)"
|
|
261
|
+
lines = lines.last(30)
|
|
262
|
+
end
|
|
263
|
+
lines.each { |line| puts " #{line}" }
|
|
264
|
+
end
|
|
265
|
+
puts
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
exit(failed.empty? ? 0 : 1)
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
if __FILE__ == $0
|
|
274
|
+
IntegrationRunner.new(ARGV.dup).run
|
|
275
|
+
end
|
data/bin/scenario
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Individual scenario runner for integration testing
|
|
5
|
+
# This script runs a single integration test file in complete isolation
|
|
6
|
+
|
|
7
|
+
require 'bundler/setup'
|
|
8
|
+
|
|
9
|
+
# Exit codes
|
|
10
|
+
EXIT_SUCCESS = 0
|
|
11
|
+
EXIT_FAILURE = 1
|
|
12
|
+
EXIT_TIMEOUT = 2
|
|
13
|
+
EXIT_SETUP_ERROR = 3
|
|
14
|
+
|
|
15
|
+
class ScenarioRunner
|
|
16
|
+
attr_reader :test_file
|
|
17
|
+
|
|
18
|
+
def initialize(test_file)
|
|
19
|
+
@test_file = test_file
|
|
20
|
+
@exit_code = EXIT_SUCCESS
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def run
|
|
24
|
+
puts "Running: #{File.basename(test_file)}" if ENV['VERBOSE']
|
|
25
|
+
|
|
26
|
+
# Set up the scenario-specific environment
|
|
27
|
+
setup_scenario
|
|
28
|
+
|
|
29
|
+
# Load and run the test file
|
|
30
|
+
load_and_run_test
|
|
31
|
+
|
|
32
|
+
exit EXIT_SUCCESS
|
|
33
|
+
rescue => e
|
|
34
|
+
puts "FAILED: #{File.basename(test_file)} - #{e.message}" if ENV['VERBOSE']
|
|
35
|
+
puts e.backtrace.first(5).join("\n") if ENV['VERBOSE']
|
|
36
|
+
exit EXIT_FAILURE
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def setup_scenario
|
|
42
|
+
# Each test handles its own specific requirements
|
|
43
|
+
require 'bundler/setup'
|
|
44
|
+
|
|
45
|
+
puts "Setting up isolated test environment" if ENV['VERBOSE']
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def load_and_run_test
|
|
50
|
+
# Test file might be relative to current directory
|
|
51
|
+
if File.exist?(test_file)
|
|
52
|
+
absolute_test_path = File.expand_path(test_file)
|
|
53
|
+
else
|
|
54
|
+
# Fallback to project root resolution
|
|
55
|
+
project_root = File.expand_path('..', __dir__)
|
|
56
|
+
absolute_test_path = File.join(project_root, test_file)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
puts "Current directory: #{Dir.pwd}" if ENV['VERBOSE']
|
|
60
|
+
puts "Loading test file: #{absolute_test_path}" if ENV['VERBOSE']
|
|
61
|
+
|
|
62
|
+
unless File.exist?(absolute_test_path)
|
|
63
|
+
raise "Test file not found: #{absolute_test_path}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Check if this is an RSpec file (contains RSpec.describe or describe)
|
|
67
|
+
file_content = File.read(absolute_test_path, encoding: 'UTF-8')
|
|
68
|
+
if file_content.match?(/\b(?:RSpec\.describe|describe)\b/)
|
|
69
|
+
puts "Running as RSpec test" if ENV['VERBOSE']
|
|
70
|
+
run_rspec_test(absolute_test_path)
|
|
71
|
+
else
|
|
72
|
+
puts "Running as plain Ruby test" if ENV['VERBOSE']
|
|
73
|
+
# Load integrations_helper for plain Ruby integration tests
|
|
74
|
+
if absolute_test_path.include?('spec/integration')
|
|
75
|
+
project_root = File.expand_path('..', __dir__)
|
|
76
|
+
integrations_helper = File.join(project_root, 'spec', 'integrations_helper.rb')
|
|
77
|
+
require integrations_helper
|
|
78
|
+
end
|
|
79
|
+
# Load as plain Ruby test
|
|
80
|
+
load absolute_test_path
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def run_rspec_test(test_file_path)
|
|
85
|
+
# Change to project root for RSpec to find spec_helper
|
|
86
|
+
project_root = File.expand_path('..', __dir__)
|
|
87
|
+
Dir.chdir(project_root) do
|
|
88
|
+
# Disable SimpleCov for integration tests to avoid coverage failures
|
|
89
|
+
ENV['SIMPLECOV_DISABLED'] = 'true'
|
|
90
|
+
|
|
91
|
+
# Make the test file path relative to project root for RSpec
|
|
92
|
+
relative_test_path = test_file_path.sub("#{project_root}/", '')
|
|
93
|
+
|
|
94
|
+
puts "Running RSpec with file: #{relative_test_path}" if ENV['VERBOSE']
|
|
95
|
+
puts "Working directory: #{Dir.pwd}" if ENV['VERBOSE']
|
|
96
|
+
|
|
97
|
+
# Check if this test requires Rails but Rails is not available
|
|
98
|
+
if requires_rails?(test_file_path) && !rails_available?
|
|
99
|
+
puts "Skipping #{File.basename(test_file_path)} - Rails not available"
|
|
100
|
+
return
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Run RSpec with the specific test file
|
|
104
|
+
require 'rspec/core'
|
|
105
|
+
|
|
106
|
+
# Load integration spec_helper for integration tests
|
|
107
|
+
if relative_test_path.include?('spec/integration/')
|
|
108
|
+
require_relative '../spec/integration/spec_helper'
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
result = RSpec::Core::Runner.run([relative_test_path], $stderr, $stdout)
|
|
112
|
+
|
|
113
|
+
if result != 0
|
|
114
|
+
raise "RSpec failed with exit code #{result}"
|
|
115
|
+
end
|
|
116
|
+
ensure
|
|
117
|
+
# Clean up environment
|
|
118
|
+
ENV.delete('SIMPLECOV_DISABLED')
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def requires_rails?(test_file_path)
|
|
123
|
+
# Check if the test file mentions Rails dependencies
|
|
124
|
+
content = File.read(test_file_path)
|
|
125
|
+
content.match?(/require.*rails|Rails::|ActiveJob::|ActionController::/)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def rails_available?
|
|
129
|
+
begin
|
|
130
|
+
require 'rails'
|
|
131
|
+
true
|
|
132
|
+
rescue LoadError
|
|
133
|
+
false
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Validate arguments
|
|
139
|
+
if ARGV.empty?
|
|
140
|
+
puts "Usage: bin/scenario <test_file>"
|
|
141
|
+
puts "Example: bin/scenario spec/integration/rails_integration_spec.rb"
|
|
142
|
+
exit EXIT_SETUP_ERROR
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
test_file = ARGV[0]
|
|
146
|
+
|
|
147
|
+
unless File.exist?(test_file)
|
|
148
|
+
puts "Test file not found: #{test_file}"
|
|
149
|
+
exit EXIT_SETUP_ERROR
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# Run the scenario
|
|
154
|
+
ScenarioRunner.new(test_file).run
|
data/bin/shoryuken
CHANGED
|
@@ -26,9 +26,10 @@ module Shoryuken
|
|
|
26
26
|
method_option :logfile, aliases: '-L', type: :string, desc: 'Path to logfile'
|
|
27
27
|
method_option :pidfile, aliases: '-P', type: :string, desc: 'Path to pidfile'
|
|
28
28
|
method_option :verbose, aliases: '-v', type: :boolean, desc: 'Print more verbose output'
|
|
29
|
-
method_option :delay, aliases: '-D', type: :numeric,
|
|
29
|
+
method_option :delay, aliases: '-D', type: :numeric,
|
|
30
|
+
desc: 'Number of seconds to pause fetching from an empty queue'
|
|
30
31
|
def start
|
|
31
|
-
opts = options.to_h.
|
|
32
|
+
opts = options.to_h.transform_keys(&:to_sym)
|
|
32
33
|
|
|
33
34
|
say '[DEPRECATED] Please use --config instead of --config-file', :yellow if opts[:config_file]
|
|
34
35
|
|
data/docker-compose.yml
ADDED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Shoryuken
|
|
2
|
-
module
|
|
4
|
+
module ActiveJob
|
|
3
5
|
# Adds an accessor for SQS SendMessage parameters on ActiveJob jobs
|
|
4
6
|
# (instances of ActiveJob::Base). Shoryuken ActiveJob queue adapters use
|
|
5
7
|
# these parameters when enqueueing jobs; other adapters can ignore them.
|
|
@@ -7,6 +9,7 @@ module Shoryuken
|
|
|
7
9
|
extend ActiveSupport::Concern
|
|
8
10
|
|
|
9
11
|
included do
|
|
12
|
+
# @return [Hash] the SQS send message parameters
|
|
10
13
|
attr_accessor :sqs_send_message_parameters
|
|
11
14
|
end
|
|
12
15
|
end
|
|
@@ -15,12 +18,23 @@ module Shoryuken
|
|
|
15
18
|
# to the empty hash, and populates it whenever `#enqueue` is called, such
|
|
16
19
|
# as when using ActiveJob::Base.set.
|
|
17
20
|
module SQSSendMessageParametersSupport
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
# Initializes a new ActiveJob instance with empty SQS parameters
|
|
22
|
+
#
|
|
23
|
+
# Uses argument forwarding (...) to properly pass all arguments including
|
|
24
|
+
# keyword arguments to the base class.
|
|
25
|
+
def initialize(...)
|
|
26
|
+
super(...)
|
|
20
27
|
self.sqs_send_message_parameters = {}
|
|
21
28
|
end
|
|
22
|
-
ruby2_keywords(:initialize) if respond_to?(:ruby2_keywords, true)
|
|
23
29
|
|
|
30
|
+
# Enqueues the job with optional SQS-specific parameters
|
|
31
|
+
#
|
|
32
|
+
# @param options [Hash] enqueue options
|
|
33
|
+
# @option options [Hash] :message_attributes custom SQS message attributes
|
|
34
|
+
# @option options [Hash] :message_system_attributes system attributes
|
|
35
|
+
# @option options [String] :message_deduplication_id FIFO deduplication ID
|
|
36
|
+
# @option options [String] :message_group_id FIFO message group ID
|
|
37
|
+
# @return [Object] the enqueue result
|
|
24
38
|
def enqueue(options = {})
|
|
25
39
|
sqs_options = options.extract! :message_attributes,
|
|
26
40
|
:message_system_attributes,
|
|
@@ -34,5 +48,5 @@ module Shoryuken
|
|
|
34
48
|
end
|
|
35
49
|
end
|
|
36
50
|
|
|
37
|
-
ActiveJob::Base.include Shoryuken::
|
|
38
|
-
ActiveJob::Base.prepend Shoryuken::
|
|
51
|
+
ActiveJob::Base.include Shoryuken::ActiveJob::SQSSendMessageParametersAccessor
|
|
52
|
+
ActiveJob::Base.prepend Shoryuken::ActiveJob::SQSSendMessageParametersSupport
|