Extract Test::JobServer module

A placeholder to handle GNU make jobserver option.
spec/default.mspec didn't handle the jobserver using a FIFO.
This commit is contained in:
Nobuyoshi Nakada 2025-12-19 09:26:23 +09:00
parent 42d66b894c
commit bfba65d8c1
No known key found for this signature in database
GPG Key ID: 3582D74E1FEE4465
Notes: git 2025-12-19 05:19:39 +00:00
4 changed files with 57 additions and 78 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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