From ef14fca166a8e8ddb72b61090d1131f71ab3b57c Mon Sep 17 00:00:00 2001 From: Gannon McGibbon Date: Tue, 19 Aug 2025 13:19:01 -0500 Subject: [PATCH 1/2] Make the CI Queue reporters work in parallel Make Minitest::Queue::OrderReporter parallel-friendly. This is essentially done by appending and truncating to the log file instead of writing to it. The object also doesn't depend on the start hook anymore to initialize the log file, because that isn't called in forked workers. --- ruby/lib/minitest/queue/order_reporter.rb | 16 +++++++++---- .../minitest/queue/order_reporter_test.rb | 23 +++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/ruby/lib/minitest/queue/order_reporter.rb b/ruby/lib/minitest/queue/order_reporter.rb index 4f8c426f..9e409576 100644 --- a/ruby/lib/minitest/queue/order_reporter.rb +++ b/ruby/lib/minitest/queue/order_reporter.rb @@ -4,21 +4,29 @@ class Minitest::Queue::OrderReporter < Minitest::Reporters::BaseReporter def initialize(options = {}) @path = options.delete(:path) + @file = nil super end def start - @file = File.open(@path, 'w+') super + file.truncate(0) end def before_test(test) super - @file.puts("#{test.class.name}##{test.name}") - @file.flush + file.puts("#{test.class.name}##{test.name}") + file.flush end def report - @file.close + file.close + end + + private + + def file + @file ||= File.open(@path, 'a+') end end + diff --git a/ruby/test/minitest/queue/order_reporter_test.rb b/ruby/test/minitest/queue/order_reporter_test.rb index ee8e4d8c..96699a8d 100644 --- a/ruby/test/minitest/queue/order_reporter_test.rb +++ b/ruby/test/minitest/queue/order_reporter_test.rb @@ -7,16 +7,39 @@ class OrderReporterTest < Minitest::Test def setup @reporter = OrderReporter.new(path: log_path) + end + + def test_start @reporter.start + @reporter.report + assert_equal [], File.readlines(log_path).map(&:chomp) end def test_before_test + @reporter.start @reporter.before_test(runnable('a')) @reporter.before_test(runnable('b')) @reporter.report assert_equal ['Minitest::Test#a', 'Minitest::Test#b'], File.readlines(log_path).map(&:chomp) end + def test_forking + pid = fork do + @reporter.start + end + pids = 5.times.map do + fork do + @reporter.before_test(runnable(Process.pid)) + @reporter.report + end + end + (pids + [pid]).map do |pid| + Process.waitpid(pid) + end + + assert_equal pids.map { |pid| "Minitest::Test##{pid}" }.sort, File.readlines(log_path).map(&:chomp).sort + end + private def delete_log From 52229d60e433d817e1f86eeffed3bc9dd08d171e Mon Sep 17 00:00:00 2001 From: Gannon McGibbon Date: Wed, 20 Aug 2025 16:54:35 -0500 Subject: [PATCH 2/2] Don't run fork test in truffleruby --- ruby/test/integration/grind_redis_test.rb | 2 +- ruby/test/integration/minitest_redis_test.rb | 2 +- .../minitest/queue/order_reporter_test.rb | 28 ++++++++++--------- ruby/test/test_helper.rb | 9 ++++++ 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/ruby/test/integration/grind_redis_test.rb b/ruby/test/integration/grind_redis_test.rb index 700a9813..b99033aa 100644 --- a/ruby/test/integration/grind_redis_test.rb +++ b/ruby/test/integration/grind_redis_test.rb @@ -97,7 +97,7 @@ def test_grind_command_runs_tests def test_grind_max_time grind_count = 1000000 - timeout = RUBY_ENGINE == "truffleruby" ? 10 : 1 + timeout = self.class.truffleruby? ? 10 : 1 start = Process.clock_gettime(Process::CLOCK_MONOTONIC) _, err = capture_subprocess_io do diff --git a/ruby/test/integration/minitest_redis_test.rb b/ruby/test/integration/minitest_redis_test.rb index a0ab6c2c..8e76264b 100644 --- a/ruby/test/integration/minitest_redis_test.rb +++ b/ruby/test/integration/minitest_redis_test.rb @@ -673,7 +673,7 @@ def test_test_data_time_reporter .sort_by { |h| "#{h[:test_id]}##{h[:test_index]}" } .first - start_delta = RUBY_ENGINE == "truffleruby" ? 15 : 5 + start_delta = self.class.truffleruby? ? 15 : 5 assert_in_delta start_time.to_i, failure[:test_start_timestamp], start_delta, "start time" assert_in_delta end_time.to_i, failure[:test_finish_timestamp], 5 assert failure[:test_finish_timestamp] > failure[:test_start_timestamp] diff --git a/ruby/test/minitest/queue/order_reporter_test.rb b/ruby/test/minitest/queue/order_reporter_test.rb index 96699a8d..3ba2952d 100644 --- a/ruby/test/minitest/queue/order_reporter_test.rb +++ b/ruby/test/minitest/queue/order_reporter_test.rb @@ -23,21 +23,23 @@ def test_before_test assert_equal ['Minitest::Test#a', 'Minitest::Test#b'], File.readlines(log_path).map(&:chomp) end - def test_forking - pid = fork do - @reporter.start - end - pids = 5.times.map do - fork do - @reporter.before_test(runnable(Process.pid)) - @reporter.report + unless truffleruby? + def test_forking + pid = fork do + @reporter.start + end + pids = 5.times.map do + fork do + @reporter.before_test(runnable(Process.pid)) + @reporter.report + end + end + (pids + [pid]).map do |pid| + Process.waitpid(pid) end - end - (pids + [pid]).map do |pid| - Process.waitpid(pid) - end - assert_equal pids.map { |pid| "Minitest::Test##{pid}" }.sort, File.readlines(log_path).map(&:chomp).sort + assert_equal pids.map { |pid| "Minitest::Test##{pid}" }.sort, File.readlines(log_path).map(&:chomp).sort + end end private diff --git a/ruby/test/test_helper.rb b/ruby/test/test_helper.rb index e8e49bbd..1988b7de 100644 --- a/ruby/test/test_helper.rb +++ b/ruby/test/test_helper.rb @@ -19,3 +19,12 @@ Dir[File.expand_path('../support/**/*.rb', __FILE__)].sort.each do |file| require file end + + +class Minitest::Test + class << self + def truffleruby? + RUBY_ENGINE == "truffleruby" + end + end +end