Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

### 7.14.0

* Improve debugging for hanging CI nodes: show hanging spec files in the RSpec output and a command to reproduce the current batch of tests.

https://github.com/KnapsackPro/knapsack_pro-ruby/pull/287

https://github.com/KnapsackPro/knapsack_pro-ruby/compare/v7.13.1...v7.14.0

### 7.13.1

* Fix handling signals for non-RSpec test runners
Expand Down
6 changes: 6 additions & 0 deletions lib/knapsack_pro/runners/queue/base_runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ def log_threads
puts 'Use the following backtrace(s) to find the line of code that got stuck if the CI node hung and terminated your tests.'
puts 'How to read the backtrace: https://knapsackpro.com/perma/ruby/backtrace-debugging'

log_current_tests(threads)

threads.each do |thread|
puts
if thread == Thread.main
Expand All @@ -95,6 +97,10 @@ def log_threads

$stdout.flush
end

def log_current_tests(threads)
# implement in a child class if you need to log more info
end
end
end
end
Expand Down
36 changes: 36 additions & 0 deletions lib/knapsack_pro/runners/queue/rspec_runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,27 @@ def log_fail_fast_limit_met

def post_trap_signals
RSpec.world.wants_to_quit = true

log_current_batch_rspec_command
end

def log_current_tests(threads)
threads.each do |thread|
next unless thread.backtrace

spec_file_lines = thread.backtrace.select { |line| line.include?('_spec.rb') }
next if spec_file_lines.empty?

puts
if thread == Thread.main
puts "Running specs in the main thread:"
else
puts "Non-main thread inspect: #{thread.inspect}"
puts "Running specs in non-main thread:"
end
puts spec_file_lines.join("\n")
puts
end
end

def pre_run_setup
Expand Down Expand Up @@ -165,6 +186,21 @@ def pull_tests_from_queue(can_initialize_queue: false)
test_file_paths
end

def log_current_batch_rspec_command
test_file_paths = @queue.current_batch&.test_file_paths
return unless test_file_paths

puts
puts '=' * 80

order_option = @adapter_class.order_option(@cli_args)
printable_args = @rspec_pure.args_with_seed_option_added_when_viable(order_option, @rspec_runner.knapsack__seed, @cli_args)
messages = @rspec_pure.rspec_command(printable_args, test_file_paths, :batch_finished)
messages.each do |message|
puts message
end
end

def log_rspec_batch_command(test_file_paths)
order_option = @adapter_class.order_option(@cli_args)
printable_args = @rspec_pure.args_with_seed_option_added_when_viable(order_option, @rspec_runner.knapsack__seed, @cli_args)
Expand Down
8 changes: 6 additions & 2 deletions spec/integration/runners/queue/rspec_runner_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1403,12 +1403,16 @@ def when_first_matching_example_defined(type:)
OUTPUT
)

expect(actual.stdout).to include('To retry the last batch of tests fetched from the Queue API, please run the following command on your machine:')
expect(actual.stdout).to include('bundle exec rspec --format documentation --default-path spec_integration "spec_integration/b_spec.rb" "spec_integration/c_spec.rb"')

expect(actual.stdout).to include('Use the following backtrace(s) to find the line of code that got stuck if the CI node hung and terminated your tests.')
expect(actual.stdout).to include('Running specs in the main thread:')
expect(actual.stdout).to include('Running specs in non-main thread:')
expect(actual.stdout).to include('Main thread backtrace:')
expect(actual.stdout).to match(/spec_integration\/b_spec\.rb:7:in .*kill/)
expect(actual.stdout.scan(/spec_integration\/b_spec\.rb:7:in .*kill/).size).to eq 2
expect(actual.stdout).to include('Non-main thread backtrace:')
expect(actual.stdout).to match(/spec_integration\/a_spec\.rb:6:in .*sleep/)
expect(actual.stdout.scan(/spec_integration\/a_spec\.rb:6:in .*sleep/).size).to eq 2


expect(actual.exit_code).to eq 1
Expand Down