1# -*- coding: us-ascii -*- 2# frozen_string_literal: true 3require "open3" 4require "timeout" 5require_relative "find_executable" 6begin 7 require 'rbconfig' 8rescue LoadError 9end 10begin 11 require "rbconfig/sizeof" 12rescue LoadError 13end 14 15module 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 :subprocess_timeout_scale 49 attr_reader :original_internal_encoding, :original_external_encoding, 50 :original_verbose 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 end 57 end 58 59 def apply_timeout_scale(t) 60 if scale = EnvUtil.subprocess_timeout_scale 61 t * scale 62 else 63 t 64 end 65 end 66 module_function :apply_timeout_scale 67 68 def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = false, 69 encoding: nil, timeout: 10, reprieve: 1, timeout_error: Timeout::Error, 70 stdout_filter: nil, stderr_filter: nil, 71 signal: :TERM, 72 rubybin: EnvUtil.rubybin, precommand: nil, 73 **opt) 74 timeout = apply_timeout_scale(timeout) 75 reprieve = apply_timeout_scale(reprieve) if reprieve 76 77 in_c, in_p = IO.pipe 78 out_p, out_c = IO.pipe if capture_stdout 79 err_p, err_c = IO.pipe if capture_stderr && capture_stderr != :merge_to_stdout 80 opt[:in] = in_c 81 opt[:out] = out_c if capture_stdout 82 opt[:err] = capture_stderr == :merge_to_stdout ? out_c : err_c if capture_stderr 83 if encoding 84 out_p.set_encoding(encoding) if out_p 85 err_p.set_encoding(encoding) if err_p 86 end 87 c = "C" 88 child_env = {} 89 LANG_ENVS.each {|lc| child_env[lc] = c} 90 if Array === args and Hash === args.first 91 child_env.update(args.shift) 92 end 93 if RUBYLIB and lib = child_env["RUBYLIB"] 94 child_env["RUBYLIB"] = [lib, RUBYLIB].join(File::PATH_SEPARATOR) 95 end 96 args = [args] if args.kind_of?(String) 97 pid = spawn(child_env, *precommand, rubybin, *args, **opt) 98 in_c.close 99 out_c.close if capture_stdout 100 err_c.close if capture_stderr && capture_stderr != :merge_to_stdout 101 if block_given? 102 return yield in_p, out_p, err_p, pid 103 else 104 th_stdout = Thread.new { out_p.read } if capture_stdout 105 th_stderr = Thread.new { err_p.read } if capture_stderr && capture_stderr != :merge_to_stdout 106 in_p.write stdin_data.to_str unless stdin_data.empty? 107 in_p.close 108 if (!th_stdout || th_stdout.join(timeout)) && (!th_stderr || th_stderr.join(timeout)) 109 timeout_error = nil 110 else 111 signals = Array(signal).select do |sig| 112 DEFAULT_SIGNALS[sig.to_s] or 113 DEFAULT_SIGNALS[Signal.signame(sig)] rescue false 114 end 115 signals |= [:ABRT, :KILL] 116 case pgroup = opt[:pgroup] 117 when 0, true 118 pgroup = -pid 119 when nil, false 120 pgroup = pid 121 end 122 while signal = signals.shift 123 begin 124 Process.kill signal, pgroup 125 rescue Errno::EINVAL 126 next 127 rescue Errno::ESRCH 128 break 129 end 130 if signals.empty? or !reprieve 131 Process.wait(pid) 132 else 133 begin 134 Timeout.timeout(reprieve) {Process.wait(pid)} 135 rescue Timeout::Error 136 end 137 end 138 end 139 status = $? 140 end 141 stdout = th_stdout.value if capture_stdout 142 stderr = th_stderr.value if capture_stderr && capture_stderr != :merge_to_stdout 143 out_p.close if capture_stdout 144 err_p.close if capture_stderr && capture_stderr != :merge_to_stdout 145 status ||= Process.wait2(pid)[1] 146 stdout = stdout_filter.call(stdout) if stdout_filter 147 stderr = stderr_filter.call(stderr) if stderr_filter 148 if timeout_error 149 bt = caller_locations 150 msg = "execution of #{bt.shift.label} expired timeout (#{timeout} sec)" 151 msg = Test::Unit::Assertions::FailDesc[status, msg, [stdout, stderr].join("\n")].() 152 raise timeout_error, msg, bt.map(&:to_s) 153 end 154 return stdout, stderr, status 155 end 156 ensure 157 [th_stdout, th_stderr].each do |th| 158 th.kill if th 159 end 160 [in_c, in_p, out_c, out_p, err_c, err_p].each do |io| 161 io&.close 162 end 163 [th_stdout, th_stderr].each do |th| 164 th.join if th 165 end 166 end 167 module_function :invoke_ruby 168 169 alias rubyexec invoke_ruby 170 class << self 171 alias rubyexec invoke_ruby 172 end 173 174 def verbose_warning 175 class << (stderr = "".dup) 176 alias write concat 177 def flush; end 178 end 179 stderr, $stderr = $stderr, stderr 180 $VERBOSE = true 181 yield stderr 182 return $stderr 183 ensure 184 stderr, $stderr = $stderr, stderr 185 $VERBOSE = EnvUtil.original_verbose 186 end 187 module_function :verbose_warning 188 189 def default_warning 190 $VERBOSE = false 191 yield 192 ensure 193 $VERBOSE = EnvUtil.original_verbose 194 end 195 module_function :default_warning 196 197 def suppress_warning 198 $VERBOSE = nil 199 yield 200 ensure 201 $VERBOSE = EnvUtil.original_verbose 202 end 203 module_function :suppress_warning 204 205 def under_gc_stress(stress = true) 206 stress, GC.stress = GC.stress, stress 207 yield 208 ensure 209 GC.stress = stress 210 end 211 module_function :under_gc_stress 212 213 def with_default_external(enc) 214 suppress_warning { Encoding.default_external = enc } 215 yield 216 ensure 217 suppress_warning { Encoding.default_external = EnvUtil.original_external_encoding } 218 end 219 module_function :with_default_external 220 221 def with_default_internal(enc) 222 suppress_warning { Encoding.default_internal = enc } 223 yield 224 ensure 225 suppress_warning { Encoding.default_internal = EnvUtil.original_internal_encoding } 226 end 227 module_function :with_default_internal 228 229 def labeled_module(name, &block) 230 Module.new do 231 singleton_class.class_eval {define_method(:to_s) {name}; alias inspect to_s} 232 class_eval(&block) if block 233 end 234 end 235 module_function :labeled_module 236 237 def labeled_class(name, superclass = Object, &block) 238 Class.new(superclass) do 239 singleton_class.class_eval {define_method(:to_s) {name}; alias inspect to_s} 240 class_eval(&block) if block 241 end 242 end 243 module_function :labeled_class 244 245 if /darwin/ =~ RUBY_PLATFORM 246 DIAGNOSTIC_REPORTS_PATH = File.expand_path("~/Library/Logs/DiagnosticReports") 247 DIAGNOSTIC_REPORTS_TIMEFORMAT = '%Y-%m-%d-%H%M%S' 248 @ruby_install_name = RbConfig::CONFIG['RUBY_INSTALL_NAME'] 249 250 def self.diagnostic_reports(signame, pid, now) 251 return unless %w[ABRT QUIT SEGV ILL TRAP].include?(signame) 252 cmd = File.basename(rubybin) 253 cmd = @ruby_install_name if "ruby-runner#{RbConfig::CONFIG["EXEEXT"]}" == cmd 254 path = DIAGNOSTIC_REPORTS_PATH 255 timeformat = DIAGNOSTIC_REPORTS_TIMEFORMAT 256 pat = "#{path}/#{cmd}_#{now.strftime(timeformat)}[-_]*.crash" 257 first = true 258 30.times do 259 first ? (first = false) : sleep(0.1) 260 Dir.glob(pat) do |name| 261 log = File.read(name) rescue next 262 if /\AProcess:\s+#{cmd} \[#{pid}\]$/ =~ log 263 File.unlink(name) 264 File.unlink("#{path}/.#{File.basename(name)}.plist") rescue nil 265 return log 266 end 267 end 268 end 269 nil 270 end 271 else 272 def self.diagnostic_reports(signame, pid, now) 273 end 274 end 275 276 def self.gc_stress_to_class? 277 unless defined?(@gc_stress_to_class) 278 _, _, status = invoke_ruby(["-e""exit GC.respond_to?(:add_stress_to_class)"]) 279 @gc_stress_to_class = status.success? 280 end 281 @gc_stress_to_class 282 end 283end 284 285if defined?(RbConfig) 286 module RbConfig 287 @ruby = EnvUtil.rubybin 288 class << self 289 undef ruby if method_defined?(:ruby) 290 attr_reader :ruby 291 end 292 dir = File.dirname(ruby) 293 CONFIG['bindir'] = dir 294 Gem::ConfigMap[:bindir] = dir if defined?(Gem::ConfigMap) 295 end 296end 297 298EnvUtil.capture_global_values 299