diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f38fdc3..4c8dade2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/lib/knapsack_pro/runners/queue/base_runner.rb b/lib/knapsack_pro/runners/queue/base_runner.rb index 6d7c15a2..42cb0a25 100644 --- a/lib/knapsack_pro/runners/queue/base_runner.rb +++ b/lib/knapsack_pro/runners/queue/base_runner.rb @@ -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 @@ -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 diff --git a/lib/knapsack_pro/runners/queue/rspec_runner.rb b/lib/knapsack_pro/runners/queue/rspec_runner.rb index 0175e21c..b960150f 100644 --- a/lib/knapsack_pro/runners/queue/rspec_runner.rb +++ b/lib/knapsack_pro/runners/queue/rspec_runner.rb @@ -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 @@ -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) diff --git a/spec/integration/runners/queue/rspec_runner_spec.rb b/spec/integration/runners/queue/rspec_runner_spec.rb index 3a948c8d..96af0bdf 100644 --- a/spec/integration/runners/queue/rspec_runner_spec.rb +++ b/spec/integration/runners/queue/rspec_runner_spec.rb @@ -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