Skip to content

Commit 685b72e

Browse files
committed
Import the latest version of envutil.rb
1 parent 4f1267d commit 685b72e

File tree

2 files changed

+380
-287
lines changed

2 files changed

+380
-287
lines changed

test/lib/envutil.rb

Lines changed: 380 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,380 @@
1+
# -*- coding: us-ascii -*-
2+
# frozen_string_literal: true
3+
require "open3"
4+
require "timeout"
5+
require_relative "find_executable"
6+
begin
7+
require 'rbconfig'
8+
rescue LoadError
9+
end
10+
begin
11+
require "rbconfig/sizeof"
12+
rescue LoadError
13+
end
14+
15+
module EnvUtil
16+
def rubybin
17+
if ruby = ENV["RUBY"]
18+
return ruby
19+
end
20+
ruby = "ruby"
21+
exeext = RbConfig::CONFIG["EXEEXT"]
22+
rubyexe = (ruby + exeext if exeext and !exeext.empty?)
23+
3.times do
24+
if File.exist? ruby and File.executable? ruby and !File.directory? ruby
25+
return File.expand_path(ruby)
26+
end
27+
if rubyexe and File.exist? rubyexe and File.executable? rubyexe
28+
return File.expand_path(rubyexe)
29+
end
30+
ruby = File.join("..", ruby)
31+
end
32+
if defined?(RbConfig.ruby)
33+
RbConfig.ruby
34+
else
35+
"ruby"
36+
end
37+
end
38+
module_function :rubybin
39+
40+
LANG_ENVS = %w"LANG LC_ALL LC_CTYPE"
41+
42+
DEFAULT_SIGNALS = Signal.list
43+
DEFAULT_SIGNALS.delete("TERM") if /mswin|mingw/ =~ RUBY_PLATFORM
44+
45+
RUBYLIB = ENV["RUBYLIB"]
46+
47+
class << self
48+
attr_accessor :timeout_scale
49+
attr_reader :original_internal_encoding, :original_external_encoding,
50+
:original_verbose, :original_warning
51+
52+
def capture_global_values
53+
@original_internal_encoding = Encoding.default_internal
54+
@original_external_encoding = Encoding.default_external
55+
@original_verbose = $VERBOSE
56+
@original_warning = defined?(Warning.[]) ? %i[deprecated experimental].to_h {|i| [i, Warning[i]]} : nil
57+
end
58+
end
59+
60+
def apply_timeout_scale(t)
61+
if scale = EnvUtil.timeout_scale
62+
t * scale
63+
else
64+
t
65+
end
66+
end
67+
module_function :apply_timeout_scale
68+
69+
def timeout(sec, klass = nil, message = nil, &blk)
70+
return yield(sec) if sec == nil or sec.zero?
71+
sec = apply_timeout_scale(sec)
72+
Timeout.timeout(sec, klass, message, &blk)
73+
end
74+
module_function :timeout
75+
76+
def terminate(pid, signal = :TERM, pgroup = nil, reprieve = 1)
77+
reprieve = apply_timeout_scale(reprieve) if reprieve
78+
79+
signals = Array(signal).select do |sig|
80+
DEFAULT_SIGNALS[sig.to_s] or
81+
DEFAULT_SIGNALS[Signal.signame(sig)] rescue false
82+
end
83+
signals |= [:ABRT, :KILL]
84+
case pgroup
85+
when 0, true
86+
pgroup = -pid
87+
when nil, false
88+
pgroup = pid
89+
end
90+
91+
lldb = true if /darwin/ =~ RUBY_PLATFORM
92+
93+
while signal = signals.shift
94+
95+
if lldb and [:ABRT, :KILL].include?(signal)
96+
lldb = false
97+
# sudo -n: --non-interactive
98+
# lldb -p: attach
99+
# -o: run command
100+
system(*%W[sudo -n lldb -p #{pid} --batch -o bt\ all -o call\ rb_vmdebug_stack_dump_all_threads() -o quit])
101+
true
102+
end
103+
104+
begin
105+
Process.kill signal, pgroup
106+
rescue Errno::EINVAL
107+
next
108+
rescue Errno::ESRCH
109+
break
110+
end
111+
if signals.empty? or !reprieve
112+
Process.wait(pid)
113+
else
114+
begin
115+
Timeout.timeout(reprieve) {Process.wait(pid)}
116+
rescue Timeout::Error
117+
else
118+
break
119+
end
120+
end
121+
end
122+
$?
123+
end
124+
module_function :terminate
125+
126+
def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = false,
127+
encoding: nil, timeout: 10, reprieve: 1, timeout_error: Timeout::Error,
128+
stdout_filter: nil, stderr_filter: nil, ios: nil,
129+
signal: :TERM,
130+
rubybin: EnvUtil.rubybin, precommand: nil,
131+
**opt)
132+
timeout = apply_timeout_scale(timeout)
133+
134+
in_c, in_p = IO.pipe
135+
out_p, out_c = IO.pipe if capture_stdout
136+
err_p, err_c = IO.pipe if capture_stderr && capture_stderr != :merge_to_stdout
137+
opt[:in] = in_c
138+
opt[:out] = out_c if capture_stdout
139+
opt[:err] = capture_stderr == :merge_to_stdout ? out_c : err_c if capture_stderr
140+
if encoding
141+
out_p.set_encoding(encoding) if out_p
142+
err_p.set_encoding(encoding) if err_p
143+
end
144+
ios.each {|i, o = i|opt[i] = o} if ios
145+
146+
c = "C"
147+
child_env = {}
148+
LANG_ENVS.each {|lc| child_env[lc] = c}
149+
if Array === args and Hash === args.first
150+
child_env.update(args.shift)
151+
end
152+
if RUBYLIB and lib = child_env["RUBYLIB"]
153+
child_env["RUBYLIB"] = [lib, RUBYLIB].join(File::PATH_SEPARATOR)
154+
end
155+
156+
# remain env
157+
%w(ASAN_OPTIONS RUBY_ON_BUG).each{|name|
158+
child_env[name] = ENV[name] if ENV[name]
159+
}
160+
161+
args = [args] if args.kind_of?(String)
162+
pid = spawn(child_env, *precommand, rubybin, *args, opt)
163+
in_c.close
164+
out_c&.close
165+
out_c = nil
166+
err_c&.close
167+
err_c = nil
168+
if block_given?
169+
return yield in_p, out_p, err_p, pid
170+
else
171+
th_stdout = Thread.new { out_p.read } if capture_stdout
172+
th_stderr = Thread.new { err_p.read } if capture_stderr && capture_stderr != :merge_to_stdout
173+
in_p.write stdin_data.to_str unless stdin_data.empty?
174+
in_p.close
175+
if (!th_stdout || th_stdout.join(timeout)) && (!th_stderr || th_stderr.join(timeout))
176+
timeout_error = nil
177+
else
178+
status = terminate(pid, signal, opt[:pgroup], reprieve)
179+
terminated = Time.now
180+
end
181+
stdout = th_stdout.value if capture_stdout
182+
stderr = th_stderr.value if capture_stderr && capture_stderr != :merge_to_stdout
183+
out_p.close if capture_stdout
184+
err_p.close if capture_stderr && capture_stderr != :merge_to_stdout
185+
status ||= Process.wait2(pid)[1]
186+
stdout = stdout_filter.call(stdout) if stdout_filter
187+
stderr = stderr_filter.call(stderr) if stderr_filter
188+
if timeout_error
189+
bt = caller_locations
190+
msg = "execution of #{bt.shift.label} expired timeout (#{timeout} sec)"
191+
msg = failure_description(status, terminated, msg, [stdout, stderr].join("\n"))
192+
raise timeout_error, msg, bt.map(&:to_s)
193+
end
194+
return stdout, stderr, status
195+
end
196+
ensure
197+
[th_stdout, th_stderr].each do |th|
198+
th.kill if th
199+
end
200+
[in_c, in_p, out_c, out_p, err_c, err_p].each do |io|
201+
io&.close
202+
end
203+
[th_stdout, th_stderr].each do |th|
204+
th.join if th
205+
end
206+
end
207+
module_function :invoke_ruby
208+
209+
def verbose_warning
210+
class << (stderr = "".dup)
211+
alias write concat
212+
def flush; end
213+
end
214+
stderr, $stderr = $stderr, stderr
215+
$VERBOSE = true
216+
yield stderr
217+
return $stderr
218+
ensure
219+
stderr, $stderr = $stderr, stderr
220+
$VERBOSE = EnvUtil.original_verbose
221+
EnvUtil.original_warning&.each {|i, v| Warning[i] = v}
222+
end
223+
module_function :verbose_warning
224+
225+
def default_warning
226+
$VERBOSE = false
227+
yield
228+
ensure
229+
$VERBOSE = EnvUtil.original_verbose
230+
end
231+
module_function :default_warning
232+
233+
def suppress_warning
234+
$VERBOSE = nil
235+
yield
236+
ensure
237+
$VERBOSE = EnvUtil.original_verbose
238+
end
239+
module_function :suppress_warning
240+
241+
def under_gc_stress(stress = true)
242+
stress, GC.stress = GC.stress, stress
243+
yield
244+
ensure
245+
GC.stress = stress
246+
end
247+
module_function :under_gc_stress
248+
249+
def with_default_external(enc)
250+
suppress_warning { Encoding.default_external = enc }
251+
yield
252+
ensure
253+
suppress_warning { Encoding.default_external = EnvUtil.original_external_encoding }
254+
end
255+
module_function :with_default_external
256+
257+
def with_default_internal(enc)
258+
suppress_warning { Encoding.default_internal = enc }
259+
yield
260+
ensure
261+
suppress_warning { Encoding.default_internal = EnvUtil.original_internal_encoding }
262+
end
263+
module_function :with_default_internal
264+
265+
def labeled_module(name, &block)
266+
Module.new do
267+
singleton_class.class_eval {
268+
define_method(:to_s) {name}
269+
alias inspect to_s
270+
alias name to_s
271+
}
272+
class_eval(&block) if block
273+
end
274+
end
275+
module_function :labeled_module
276+
277+
def labeled_class(name, superclass = Object, &block)
278+
Class.new(superclass) do
279+
singleton_class.class_eval {
280+
define_method(:to_s) {name}
281+
alias inspect to_s
282+
alias name to_s
283+
}
284+
class_eval(&block) if block
285+
end
286+
end
287+
module_function :labeled_class
288+
289+
if /darwin/ =~ RUBY_PLATFORM
290+
DIAGNOSTIC_REPORTS_PATH = File.expand_path("~/Library/Logs/DiagnosticReports")
291+
DIAGNOSTIC_REPORTS_TIMEFORMAT = '%Y-%m-%d-%H%M%S'
292+
@ruby_install_name = RbConfig::CONFIG['RUBY_INSTALL_NAME']
293+
294+
def self.diagnostic_reports(signame, pid, now)
295+
return unless %w[ABRT QUIT SEGV ILL TRAP].include?(signame)
296+
cmd = File.basename(rubybin)
297+
cmd = @ruby_install_name if "ruby-runner#{RbConfig::CONFIG["EXEEXT"]}" == cmd
298+
path = DIAGNOSTIC_REPORTS_PATH
299+
timeformat = DIAGNOSTIC_REPORTS_TIMEFORMAT
300+
pat = "#{path}/#{cmd}_#{now.strftime(timeformat)}[-_]*.{crash,ips}"
301+
first = true
302+
30.times do
303+
first ? (first = false) : sleep(0.1)
304+
Dir.glob(pat) do |name|
305+
log = File.read(name) rescue next
306+
case name
307+
when /\.crash\z/
308+
if /\AProcess:\s+#{cmd} \[#{pid}\]$/ =~ log
309+
File.unlink(name)
310+
File.unlink("#{path}/.#{File.basename(name)}.plist") rescue nil
311+
return log
312+
end
313+
when /\.ips\z/
314+
if /^ *"pid" *: *#{pid},/ =~ log
315+
File.unlink(name)
316+
return log
317+
end
318+
end
319+
end
320+
end
321+
nil
322+
end
323+
else
324+
def self.diagnostic_reports(signame, pid, now)
325+
end
326+
end
327+
328+
def self.failure_description(status, now, message = "", out = "")
329+
pid = status.pid
330+
if signo = status.termsig
331+
signame = Signal.signame(signo)
332+
sigdesc = "signal #{signo}"
333+
end
334+
log = diagnostic_reports(signame, pid, now)
335+
if signame
336+
sigdesc = "SIG#{signame} (#{sigdesc})"
337+
end
338+
if status.coredump?
339+
sigdesc = "#{sigdesc} (core dumped)"
340+
end
341+
full_message = ''.dup
342+
message = message.call if Proc === message
343+
if message and !message.empty?
344+
full_message << message << "\n"
345+
end
346+
full_message << "pid #{pid}"
347+
full_message << " exit #{status.exitstatus}" if status.exited?
348+
full_message << " killed by #{sigdesc}" if sigdesc
349+
if out and !out.empty?
350+
full_message << "\n" << out.b.gsub(/^/, '| ')
351+
full_message.sub!(/(?<!\n)\z/, "\n")
352+
end
353+
if log
354+
full_message << "Diagnostic reports:\n" << log.b.gsub(/^/, '| ')
355+
end
356+
full_message
357+
end
358+
359+
def self.gc_stress_to_class?
360+
unless defined?(@gc_stress_to_class)
361+
_, _, status = invoke_ruby(["-e""exit GC.respond_to?(:add_stress_to_class)"])
362+
@gc_stress_to_class = status.success?
363+
end
364+
@gc_stress_to_class
365+
end
366+
end
367+
368+
if defined?(RbConfig)
369+
module RbConfig
370+
@ruby = EnvUtil.rubybin
371+
class << self
372+
undef ruby if method_defined?(:ruby)
373+
attr_reader :ruby
374+
end
375+
dir = File.dirname(ruby)
376+
CONFIG['bindir'] = dir
377+
end
378+
end
379+
380+
EnvUtil.capture_global_values

0 commit comments

Comments
 (0)