1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
|
#!/usr/bin/env ruby
# frozen_string_literal: true
# Standalone CLI script to test sentry-rails against multiple Rails versions
#
# FEATURES:
# - Dedicated lock files for each Ruby/Rails version combination
# - Prevents dependency conflicts between different Rails versions
# - Automatic lock file management and restoration
# - Clean up functionality for old lock files
#
# LOCK FILE STRATEGY:
# Each Ruby/Rails combination gets its own lock file:
# - Ruby 3.4.5 + Rails 6.1 → Gemfile-ruby-3.4.5-rails-6.1.lock
# - Ruby 3.4.5 + Rails 7.0 → Gemfile-ruby-3.4.5-rails-7.0.lock
#
# Usage:
# ./bin/test --version 5.0
# ./bin/test --all
# ./bin/test --help
require 'optparse'
require 'fileutils'
class RailsVersionTester
SUPPORTED_VERSIONS = %w[5.0 5.1 5.2 6.0 6.1 7.0 7.1 7.2 8.0].freeze
def initialize
@options = {}
@failed_versions = []
@ruby_version = RUBY_VERSION
@spec_paths = []
end
def run(args)
parse_options(args)
case
when @options[:help]
show_help
when @options[:list]
list_versions
when @options[:clean]
clean_lock_files
when @options[:all]
test_all_versions
when @options[:version]
test_single_version(@options[:version])
else
puts "Error: No action specified. Use --help for usage information."
exit(1)
end
end
private
def parse_options(args)
OptionParser.new do |opts|
opts.banner = "Usage: #{$0} [options] [spec_paths...]"
opts.on("-v", "--version VERSION", "Test specific Rails version") do |version|
unless SUPPORTED_VERSIONS.include?(version)
puts "Error: Unsupported Rails version '#{version}'"
puts "Supported versions: #{SUPPORTED_VERSIONS.join(', ')}"
exit(1)
end
@options[:version] = version
end
opts.on("-a", "--all", "Test all supported Rails versions") do
@options[:all] = true
end
opts.on("-l", "--list", "List supported Rails versions and lock file status") do
@options[:list] = true
end
opts.on("-c", "--clean", "Clean up old lock files for current Ruby version") do
@options[:clean] = true
end
opts.on("-h", "--help", "Show this help message") do
@options[:help] = true
end
end.parse!(args)
# Remaining arguments are spec paths
@spec_paths = args
end
def show_help
puts <<~HELP
Rails Version Tester for sentry-rails
This script tests sentry-rails against different Rails versions by:
1. Setting the RAILS_VERSION environment variable
2. Managing bundle dependencies with dedicated lock files per Ruby/Rails combination
3. Running the test suite in isolated processes
4. Providing proper exit codes for CI/CD integration
Each Ruby/Rails version combination gets its own Gemfile.lock to prevent conflicts:
- Ruby #{@ruby_version} + Rails 6.1 → Gemfile-ruby-#{@ruby_version}-rails-6.1.lock
- Ruby #{@ruby_version} + Rails 7.0 → Gemfile-ruby-#{@ruby_version}-rails-7.0.lock
Usage:
#{$0} --version 6.1 # Test specific Rails version (all specs)
#{$0} --version 7.0 spec/sentry/rails/log_subscribers # Test specific Rails version with specific specs
#{$0} --all # Test all supported versions
#{$0} --list # List supported versions and lock file status
#{$0} --clean # Clean up old lock files for current Ruby version
#{$0} --help # Show this help
Supported Rails versions: #{SUPPORTED_VERSIONS.join(', ')}
Examples:
#{$0} -v 7.1 # Test Rails 7.1 (all specs)
#{$0} -v 7.0 spec/sentry/rails/log_subscribers # Test Rails 7.0 log subscriber specs only
#{$0} -v 7.0 spec/sentry/rails/tracing # Test Rails 7.0 tracing specs only
#{$0} -a # Test all versions
#{$0} -c # Clean up old lock files
HELP
end
def list_versions
puts "Supported Rails versions:"
SUPPORTED_VERSIONS.each do |version|
lock_file = generate_lock_file_name(version)
status = File.exist?(lock_file) ? "(has lock file)" : "(no lock file)"
puts " - #{version} #{status}"
end
puts
puts "Current Ruby version: #{@ruby_version}"
puts "Lock files are stored as: Gemfile-ruby-X.X.X-rails-Y.Y.lock"
end
def test_all_versions
puts "Testing sentry-rails against all supported Rails versions: #{SUPPORTED_VERSIONS.join(', ')}"
puts
SUPPORTED_VERSIONS.each do |version|
puts "=" * 60
puts "Testing Rails #{version}"
puts "=" * 60
exit_code = test_rails_version(version)
if exit_code == 0
puts "✓ Rails #{version} - PASSED"
else
puts "✗ Rails #{version} - FAILED (exit code: #{exit_code})"
@failed_versions << version
end
puts
end
print_summary
end
def test_single_version(version)
puts "Testing sentry-rails against Rails #{version}..."
exit_code = test_rails_version(version)
exit(exit_code) unless exit_code == 0
end
def test_rails_version(version)
puts "Setting up environment for Rails #{version}..."
# Generate dedicated lock file name for this Ruby/Rails combination
dedicated_lock_file = generate_lock_file_name(version)
current_lock_file = "Gemfile.lock"
# Set up environment variables
env = {
"RAILS_VERSION" => version,
"BUNDLE_GEMFILE" => File.expand_path("Gemfile", Dir.pwd)
}
puts "Using dedicated lock file: #{dedicated_lock_file}"
# Manage lock file switching
setup_lock_file(dedicated_lock_file, current_lock_file)
begin
# Check if bundle update is needed
if bundle_update_needed?(env, dedicated_lock_file)
puts "Dependencies need to be updated for Rails #{version}..."
unless update_bundle(env, dedicated_lock_file)
puts "✗ Failed to update bundle for Rails #{version}"
return 1
end
end
# Run the tests in a separate process
puts "Running test suite..."
run_tests(env, @spec_paths)
ensure
# Save the current lock file back to the dedicated location
save_lock_file(dedicated_lock_file, current_lock_file)
end
end
def generate_lock_file_name(rails_version)
# Create a unique lock file name for this Ruby/Rails combination
ruby_version_clean = @ruby_version.gsub(/[^\d\.]/, '')
rails_version_clean = rails_version.gsub(/[^\d\.]/, '')
"Gemfile-ruby-#{ruby_version_clean}-rails-#{rails_version_clean}.lock"
end
def setup_lock_file(dedicated_lock_file, current_lock_file)
# If we have a dedicated lock file, copy it to the current location
if File.exist?(dedicated_lock_file)
puts "Restoring lock file from #{dedicated_lock_file}"
FileUtils.cp(dedicated_lock_file, current_lock_file)
elsif File.exist?(current_lock_file)
# If no dedicated lock file exists but current one does, remove it
# so we get a fresh resolution
puts "Removing existing lock file for fresh dependency resolution"
File.delete(current_lock_file)
end
end
def save_lock_file(dedicated_lock_file, current_lock_file)
# Save the current lock file to the dedicated location
if File.exist?(current_lock_file)
puts "Saving lock file to #{dedicated_lock_file}"
FileUtils.cp(current_lock_file, dedicated_lock_file)
end
end
def bundle_update_needed?(env, dedicated_lock_file)
# Check if current Gemfile.lock exists
current_lock_file = "Gemfile.lock"
gemfile_path = env["BUNDLE_GEMFILE"] || "Gemfile"
return true unless File.exist?(current_lock_file)
# Check if Gemfile is newer than the current lock file
return true if File.mtime(gemfile_path) > File.mtime(current_lock_file)
# For Rails version changes, check if lockfile has incompatible Rails version
if env["RAILS_VERSION"] && lockfile_has_incompatible_rails_version?(current_lock_file, env["RAILS_VERSION"])
return true
end
# Check if bundle check passes
system(env, "bundle check > /dev/null 2>&1") == false
end
def lockfile_has_incompatible_rails_version?(lockfile_path, target_rails_version)
return false unless File.exist?(lockfile_path)
lockfile_content = File.read(lockfile_path)
# Extract Rails version from lockfile
if lockfile_content =~ /^\s*rails \(([^)]+)\)/
locked_rails_version = $1
target_major_minor = target_rails_version.split('.')[0..1].join('.')
locked_major_minor = locked_rails_version.split('.')[0..1].join('.')
# If major.minor versions don't match, we need to update
return target_major_minor != locked_major_minor
end
# If we can't determine the Rails version, assume update is needed
true
end
def update_bundle(env, dedicated_lock_file)
puts "Updating bundle for Rails #{env['RAILS_VERSION']}..."
current_lock_file = "Gemfile.lock"
# Try bundle update first
if system(env, "bundle update --quiet")
puts "Bundle updated successfully"
return true
end
puts "Bundle update failed, trying clean install..."
# Remove the current lockfile and try fresh install
File.delete(current_lock_file) if File.exist?(current_lock_file)
if system(env, "bundle install --quiet")
puts "Bundle installed successfully"
return true
end
puts "Bundle install failed"
false
end
def run_tests(env, spec_paths = [])
# Determine the command to run
if spec_paths.empty?
# Run all tests via rake
command = "bundle exec rake"
else
# Run specific specs via rspec
command = "bundle exec rspec #{spec_paths.join(' ')}"
end
puts "Executing: #{command}"
# Run the tests in a separate process with proper signal handling
pid = spawn(env, command,
out: $stdout,
err: $stderr,
pgroup: true)
begin
_, status = Process.wait2(pid)
status.exitstatus
rescue Interrupt
puts "\nInterrupted! Terminating test process..."
terminate_process_group(pid)
130 # Standard exit code for SIGINT
end
end
def terminate_process_group(pid)
begin
Process.kill("TERM", -pid) # Kill the process group
sleep(2)
Process.kill("KILL", -pid) if process_running?(pid)
rescue Errno::ESRCH
# Process already terminated
end
end
def process_running?(pid)
Process.getpgid(pid)
true
rescue Errno::ESRCH
false
end
def clean_lock_files
puts "Cleaning up lock files for Ruby #{@ruby_version}..."
# Find all lock files matching our pattern
pattern = "Gemfile-ruby-#{@ruby_version.gsub(/[^\d\.]/, '')}-rails-*.lock"
lock_files = Dir.glob(pattern)
if lock_files.empty?
puts "No lock files found matching pattern: #{pattern}"
return
end
puts "Found #{lock_files.length} lock file(s):"
lock_files.each { |file| puts " - #{file}" }
print "Delete these files? [y/N]: "
response = $stdin.gets.chomp.downcase
if response == 'y' || response == 'yes'
lock_files.each do |file|
File.delete(file)
puts "Deleted: #{file}"
end
puts "Cleanup complete!"
else
puts "Cleanup cancelled."
end
end
def print_summary
puts "=" * 60
puts "SUMMARY"
puts "=" * 60
if @failed_versions.empty?
puts "✓ All Rails versions passed!"
exit(0)
else
puts "✗ Failed versions: #{@failed_versions.join(', ')}"
puts
puts "Some Rails versions failed. See output above for details."
exit(1)
end
end
end
# Run the script if called directly
if __FILE__ == $0
tester = RailsVersionTester.new
tester.run(ARGV)
end
|