From bfba65d8c1fcdc75ea3fc0f78d8bc7514e7afabd Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 19 Dec 2025 09:26:23 +0900 Subject: [PATCH] Extract `Test::JobServer` module A placeholder to handle GNU make jobserver option. spec/default.mspec didn't handle the jobserver using a FIFO. --- bootstraptest/runner.rb | 31 ++----------------------- spec/default.mspec | 33 ++++---------------------- tool/lib/test/jobserver.rb | 47 ++++++++++++++++++++++++++++++++++++++ tool/lib/test/unit.rb | 24 +++---------------- 4 files changed, 57 insertions(+), 78 deletions(-) create mode 100644 tool/lib/test/jobserver.rb diff --git a/bootstraptest/runner.rb b/bootstraptest/runner.rb index 8988ac20ce..04de0c93b9 100755 --- a/bootstraptest/runner.rb +++ b/bootstraptest/runner.rb @@ -16,6 +16,7 @@ rescue LoadError $:.unshift File.join(File.dirname(__FILE__), '../lib') retry end +require_relative '../tool/lib/test/jobserver' if !Dir.respond_to?(:mktmpdir) # copied from lib/tmpdir.rb @@ -110,35 +111,7 @@ BT = Class.new(bt) do def wn=(wn) unless wn == 1 - if /(?:\A|\s)--jobserver-(?:auth|fds)=(?:(\d+),(\d+)|fifo:((?:\\.|\S)+))/ =~ ENV.delete("MAKEFLAGS") - begin - if fifo = $3 - fifo.gsub!(/\\(?=.)/, '') - r = File.open(fifo, IO::RDONLY|IO::NONBLOCK|IO::BINARY) - w = File.open(fifo, IO::WRONLY|IO::NONBLOCK|IO::BINARY) - else - r = IO.for_fd($1.to_i(10), "rb", autoclose: false) - w = IO.for_fd($2.to_i(10), "wb", autoclose: false) - end - rescue - r.close if r - else - r.close_on_exec = true - w.close_on_exec = true - tokens = r.read_nonblock(wn > 0 ? wn : 1024, exception: false) - r.close - if String === tokens - tokens.freeze - auth = w - w = nil - at_exit {auth << tokens; auth.close} - wn = tokens.size + 1 - else - w.close - wn = 1 - end - end - end + wn = Test::JobServer.max_jobs(wn > 0 ? wn : 1024, ENV.delete("MAKEFLAGS")) || wn if wn <= 0 require 'etc' wn = [Etc.nprocessors / 2, 1].max diff --git a/spec/default.mspec b/spec/default.mspec index 058835cd10..d756dc31ff 100644 --- a/spec/default.mspec +++ b/spec/default.mspec @@ -9,6 +9,7 @@ ENV["CHECK_CONSTANT_LEAKS"] ||= "true" require "./rbconfig" unless defined?(RbConfig) require_relative "../tool/test-coverage" if ENV.key?("COVERAGE") +require_relative "../tool/lib/test/jobserver" load File.dirname(__FILE__) + '/ruby/default.mspec' OBJDIR = File.expand_path("spec/ruby/optional/capi/ext") unless defined?(OBJDIR) class MSpecScript @@ -50,34 +51,10 @@ end module MSpecScript::JobServer def cores(max = 1) - if max > 1 and /(?:\A|\s)--jobserver-(?:auth|fds)=(\d+),(\d+)/ =~ ENV["MAKEFLAGS"] - cores = 1 - begin - r = IO.for_fd($1.to_i(10), "rb", autoclose: false) - w = IO.for_fd($2.to_i(10), "wb", autoclose: false) - jobtokens = r.read_nonblock(max - 1) - cores = jobtokens.size - if cores > 0 - cores += 1 - jobserver = w - w = nil - at_exit { - jobserver.print(jobtokens) - jobserver.close - } - MSpecScript::JobServer.module_eval do - remove_method :cores - define_method(:cores) do - cores - end - end - return cores - end - rescue Errno::EBADF - ensure - r&.close - w&.close - end + MSpecScript::JobServer.remove_method :cores + if cores = Test::JobServer.max_jobs(max) + MSpecScript::JobServer.define_method(:cores) { cores } + return cores end super end diff --git a/tool/lib/test/jobserver.rb b/tool/lib/test/jobserver.rb new file mode 100644 index 0000000000..7b889163b0 --- /dev/null +++ b/tool/lib/test/jobserver.rb @@ -0,0 +1,47 @@ +module Test + module JobServer + end +end + +class << Test::JobServer + def connect(makeflags = ENV["MAKEFLAGS"]) + return unless /(?:\A|\s)--jobserver-(?:auth|fds)=(?:(\d+),(\d+)|fifo:((?:\\.|\S)+))/ =~ makeflags + begin + if fifo = $3 + fifo.gsub!(/\\(?=.)/, '') + r = File.open(fifo, IO::RDONLY|IO::NONBLOCK|IO::BINARY) + w = File.open(fifo, IO::WRONLY|IO::NONBLOCK|IO::BINARY) + else + r = IO.for_fd($1.to_i(10), "rb", autoclose: false) + w = IO.for_fd($2.to_i(10), "wb", autoclose: false) + end + rescue + r&.close + nil + else + return r, w + end + end + + def acquire_possible(r, w, max) + return unless tokens = r.read_nonblock(max - 1, exception: false) + if (jobs = tokens.size) > 0 + jobserver, w = w, nil + at_exit do + jobserver.print(tokens) + jobserver.close + end + end + return jobs + 1 + rescue Errno::EBADF + ensure + r&.close + w&.close + end + + def max_jobs(max = 2, makeflags = ENV["MAKEFLAGS"]) + if max > 1 and (r, w = connect(makeflags)) + acquire_possible(r, w, max) + end + end +end diff --git a/tool/lib/test/unit.rb b/tool/lib/test/unit.rb index 7d43e825e1..2663b7b76a 100644 --- a/tool/lib/test/unit.rb +++ b/tool/lib/test/unit.rb @@ -19,6 +19,7 @@ require_relative '../envutil' require_relative '../colorize' require_relative '../leakchecker' require_relative '../test/unit/testcase' +require_relative '../test/jobserver' require 'optparse' # See Test::Unit @@ -262,27 +263,8 @@ module Test def non_options(files, options) @jobserver = nil - makeflags = ENV.delete("MAKEFLAGS") - if !options[:parallel] and - /(?:\A|\s)--jobserver-(?:auth|fds)=(?:(\d+),(\d+)|fifo:((?:\\.|\S)+))/ =~ makeflags - begin - if fifo = $3 - fifo.gsub!(/\\(?=.)/, '') - r = File.open(fifo, IO::RDONLY|IO::NONBLOCK|IO::BINARY) - w = File.open(fifo, IO::WRONLY|IO::NONBLOCK|IO::BINARY) - else - r = IO.for_fd($1.to_i(10), "rb", autoclose: false) - w = IO.for_fd($2.to_i(10), "wb", autoclose: false) - end - rescue - r.close if r - nil - else - r.close_on_exec = true - w.close_on_exec = true - @jobserver = [r, w] - options[:parallel] ||= 256 # number of tokens to acquire first - end + if !options[:parallel] and @jobserver = Test::JobServer.connect(ENV.delete("MAKEFLAGS")) + options[:parallel] ||= 256 # number of tokens to acquire first end @worker_timeout = EnvUtil.apply_timeout_scale(options[:worker_timeout] || 1200) super