为Ruby Test::Unit测试套件设置超时机制遇问题求助
I get why this is frustrating—your current timeout wrapper only interrupts the main Rake thread, but the test suite keeps executing in the same process. Ruby's Timeout module works by raising an exception in the thread it's wrapping, but if your tests are running in their own threads or relying on native code that ignores Ruby-level interrupts, they won't stop just because the main thread throws an error.
To properly halt test execution when a timeout occurs, you need to run the test suite in a separate child process. This lets you kill the entire process (and all its children) when the timeout triggers, ensuring no leftover test activity.
Solution 1: Whole Suite Timeout Using Child Processes
Replace your existing :run_tests task with this code, which spawns an isolated process for the test task and terminates it cleanly on timeout:
require 'timeout' require 'rake/testtask' desc "Run tests for my Ruby app" Rake::TestTask.new do |t| t.libs << File.dirname(__FILE__) t.test_files = FileList['test*.rb'] t.verbose = true end desc "Run tests with a 200-second timeout" task :run_tests do TEST_TIMEOUT = 200 # Use "bundle exec rake test" instead if you're using Bundler test_command = "rake test" begin Timeout::timeout(TEST_TIMEOUT) do # Launch the test task as a separate process pid = Process.spawn(test_command) # Wait for the test process to finish Process.waitpid(pid) # Propagate test failures by checking the exit status exit_status = $?.exitstatus abort "Tests failed with exit code #{exit_status}" if exit_status != 0 end rescue Timeout::Error puts "❌ Test suite timed out after #{TEST_TIMEOUT} seconds!" # Kill the test process and any child processes it spawned system("pkill -TERM -P #{pid}") rescue nil # Stop child processes first Process.kill(:TERM, pid) rescue nil # Terminate the main test process # Clean up the process status to avoid zombies Process.waitpid(pid, Process::WNOHANG) rescue nil abort "Test suite timed out" end end
How This Works:
Process.spawnruns the test task in a completely separate process, so it's isolated from your timeout wrapper.- When the timeout hits, we explicitly kill the test process and its children to ensure all test activity stops.
- We check the exit status of the test process to properly report test failures even when there's no timeout.
Solution 2: Per-Test Timeouts (For Individual Hanging Tests)
If you want to catch individual hanging tests instead of just the whole suite, use a test framework-specific tool:
- For Minitest: Add the
minitest-timeoutgem to your Gemfile, then configure it in your test helper:require 'minitest/timeout' Minitest::Timeout.timeout = 30 # Set timeout per test (in seconds) - For RSpec: Use the
rspec-timeoutgem, or add a custom hook to your spec helper:RSpec.configure do |config| config.around(:example) do |example| Timeout::timeout(30) { example.run } end end
Combining both solutions (per-test timeouts + whole-suite timeout) gives you full coverage against both individual hanging tests and a completely unresponsive test suite.
内容的提问来源于stack exchange,提问作者Deddy

