ruby/spec/bundler/support/subprocess.rb
David Rodríguez 63c4223775 [rubygems/rubygems] Don't create an empty bundled_app when setting up deps
Running everything in `bundled_app` by default causes the `bundled_app`
helper to be used everytime, and that will create a scoped bundled_app
folder if it does not exist. That causes `bin/rake spec:deps` to create
an empty `tmp/2.1/bundled_app` folder which is a bit weird.

This commit changes specs to not switch to a (possibly empty)
bundled_app directory when not necessary (for example, when running
`gem` commands in order to setup test dependencies).

4bf89c0705
2025-07-25 11:10:36 +09:00

115 lines
3.2 KiB
Ruby

# frozen_string_literal: true
require_relative "command_execution"
module Spec
module Subprocess
class TimeoutExceeded < StandardError; end
def command_executions
@command_executions ||= []
end
def last_command
command_executions.last || raise("There is no last command")
end
def out
last_command.stdout
end
def err
last_command.stderr
end
def stdboth
last_command.stdboth
end
def exitstatus
last_command.exitstatus
end
def git(cmd, path = Dir.pwd, options = {})
sh("git #{cmd}", options.merge(dir: path))
end
def sh(cmd, options = {})
dir = options[:dir]
env = options[:env] || {}
command_execution = CommandExecution.new(cmd.to_s, timeout: options[:timeout] || 60)
open3_opts = {}
open3_opts[:chdir] = dir if dir
require "open3"
require "shellwords"
Open3.popen3(env, *cmd.shellsplit, **open3_opts) do |stdin, stdout, stderr, wait_thr|
yield stdin, stdout, wait_thr if block_given?
stdin.close
stdout_handler = ->(data) { command_execution.original_stdout << data }
stderr_handler = ->(data) { command_execution.original_stderr << data }
stdout_thread = read_stream(stdout, stdout_handler, timeout: command_execution.timeout)
stderr_thread = read_stream(stderr, stderr_handler, timeout: command_execution.timeout)
stdout_thread.join
stderr_thread.join
status = wait_thr.value
command_execution.exitstatus = if status.exited?
status.exitstatus
elsif status.signaled?
exit_status_for_signal(status.termsig)
end
rescue TimeoutExceeded
command_execution.failure_reason = :timeout
command_execution.exitstatus = exit_status_for_signal(Signal.list["INT"])
end
unless options[:raise_on_error] == false || command_execution.success?
command_execution.raise_error!
end
command_executions << command_execution
command_execution.stdout
end
# Mostly copied from https://github.com/piotrmurach/tty-command/blob/49c37a895ccea107e8b78d20e4cb29de6a1a53c8/lib/tty/command/process_runner.rb#L165-L193
def read_stream(stream, handler, timeout:)
Thread.new do
Thread.current.report_on_exception = false
cmd_start = Time.now
readers = [stream]
while readers.any?
ready = IO.select(readers, nil, readers, timeout)
raise TimeoutExceeded if ready.nil?
ready[0].each do |reader|
chunk = reader.readpartial(16 * 1024)
handler.call(chunk)
# control total time spent reading
runtime = Time.now - cmd_start
time_left = timeout - runtime
raise TimeoutExceeded if time_left < 0.0
rescue Errno::EAGAIN, Errno::EINTR
rescue EOFError, Errno::EPIPE, Errno::EIO
readers.delete(reader)
reader.close
end
end
end
end
def all_commands_output
return "" if command_executions.empty?
"\n\nCommands:\n#{command_executions.map(&:to_s_verbose).join("\n\n")}"
end
end
end