Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/rubygems.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ jobs:
- ruby: { name: no symlinks, value: 4.0.5 }
os: { name: Windows, value: windows-2025 }
symlink: off

- ruby: { name: head (RUBY_BOX=1), value: head }
os: { name: Ubuntu, value: ubuntu-24.04 }
ruby_box: true
env:
RUBYGEMS_USE_PSYCH: ${{ matrix.use_psych || 'false' }}

Expand All @@ -79,7 +83,10 @@ jobs:
run: bin/rake setup
- name: Run Test
run: bin/rake test
if: matrix.ruby.name != 'truffleruby' && matrix.ruby.name != 'jruby' && matrix.symlink != 'off'
if: matrix.ruby.name != 'truffleruby' && matrix.ruby.name != 'jruby' && matrix.symlink != 'off' && !matrix.ruby_box
- name: Run Test (RUBY_BOX=1)
run: RUBY_BOX=1 bin/rake test
if: matrix.ruby_box
- name: Run Test isolatedly
run: bin/rake test:isolated
if: matrix.ruby.name == '3.4' && matrix.os.name != 'Windows'
Expand Down
2 changes: 1 addition & 1 deletion lib/bundler/gem_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ def sh_with_status(cmd, &block)
Bundler.ui.debug(cmd)
SharedHelpers.chdir(base) do
outbuf = IO.popen(cmd, err: [:child, :out], &:read)
status = $?
status = Process.last_status
block&.call(outbuf) if status.success?
[outbuf, status]
end
Expand Down
3 changes: 2 additions & 1 deletion lib/rubygems/ext/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ def self.run(command, results, command_name = nil, dir = Dir.pwd, env = {})

require "open3"
# Set $SOURCE_DATE_EPOCH for the subprocess.
# Under Ruby::Box mkmf makes RbConfig.expand recurse until SystemStackError.
# Under Ruby::Box defined?($gvar) does not see assignments made inside the
# box, so mkmf have_devel? never memoizes and recurses until SystemStackError.
# Drop $RUBY_BOX last so no caller can restore it.
build_env = { "SOURCE_DATE_EPOCH" => Gem.source_date_epoch_string }.merge(env).merge("RUBY_BOX" => nil)
# A single-element command would be parsed as a shell command line,
Expand Down
3 changes: 2 additions & 1 deletion lib/rubygems/source/git.rb
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,10 @@ def rev_parse # :nodoc:
hash = Gem::Util.popen(git_command, "rev-parse", @reference).strip
end

# Process.last_status instead of $?, which Ruby::Box leaves uninitialized
raise Gem::Exception,
"unable to find reference #{@reference} in #{@repository}" unless
$?.success?
Process.last_status.success?

hash
end
Expand Down
9 changes: 7 additions & 2 deletions spec/bundler/shared_helpers_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,11 @@

before do
ENV["RUBYOPT"] = "-r#{install_path}/bundler/setup"
allow(File).to receive(:expand_path).and_return("#{install_path}/bundler/setup")
# Only fake the resolution of bundler/setup itself. A blanket stub
# breaks unrelated RubyGems path lookups triggered lazily inside the
# example, see #set_rubyopt.
allow(File).to receive(:expand_path).and_call_original
allow(File).to receive(:expand_path).with("setup", anything).and_return("#{install_path}/bundler/setup")
allow(Gem).to receive(:bin_path).and_return("#{install_path}/bundler/setup")
end

Expand All @@ -403,7 +407,8 @@
let(:install_path) { "/opt/ruby with space/lib" }

before do
allow(File).to receive(:expand_path).and_return("#{install_path}/bundler/setup")
allow(File).to receive(:expand_path).and_call_original
allow(File).to receive(:expand_path).with("setup", anything).and_return("#{install_path}/bundler/setup")
allow(Gem).to receive(:bin_path).and_return("#{install_path}/bundler/setup")
end

Expand Down
4 changes: 4 additions & 0 deletions spec/commands/install_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2063,6 +2063,10 @@ def run
end

it "preserves bundled native extensions when BUNDLE_CLEAN removes another gem" do
# The command-line/RUBYOPT -r bypasses gem activation under RUBY_BOX=1
# (https://bugs.ruby-lang.org/issues/22295)
skip "-r cannot activate gems under Ruby::Box" if defined?(Ruby::Box) && Ruby::Box.enabled?

build_repo4 do
build_gem "native_child", "1.0", &:add_c_extension
build_gem "native_parent", "1.0" do |s|
Expand Down
15 changes: 14 additions & 1 deletion spec/support/command_execution.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

module Spec
class CommandExecution
# Under RUBY_BOX, every spawned ruby prints an experimental warning to
# stderr, breaking specs that assert clean stderr.
RUBY_BOX_WARNING = Regexp.union(
/^[^\n]*: warning: Ruby::Box is experimental, and the behavior may change in the future!\n?/,
%r{^See https://docs\.ruby-lang\.org/\S+ for known issues, etc\.\n?}
)

def initialize(command, timeout:)
@command = command
@timeout = timeout
Expand Down Expand Up @@ -72,7 +79,13 @@ def failure?
attr_reader :failure_reason

def normalize(string)
string.dup.force_encoding(Encoding::UTF_8).scrub.strip.gsub("\r\n", "\n")
string = string.dup.force_encoding(Encoding::UTF_8).scrub.gsub("\r\n", "\n")
string = string.gsub(RUBY_BOX_WARNING, "") if ruby_box_enabled?
string.strip
end

def ruby_box_enabled?
defined?(Ruby::Box) && Ruby::Box.enabled?
end
end
end
30 changes: 30 additions & 0 deletions test/rubygems/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,10 @@ def setup
ENV["BUNDLE_USER_HOME"] = nil
ENV["RUBYGEMS_PREVENT_UPDATE_SUGGESTION"] = "true"

# Child ruby processes inherit RUBY_BOX and print an experimental
# warning on startup, breaking assertions on subprocess stderr.
ENV["RUBYOPT"] = [ENV["RUBYOPT"], "-W:no-experimental"].compact.join(" ") if ruby_box_enabled?

@current_dir = Dir.pwd
@fetcher = nil

Expand Down Expand Up @@ -1367,6 +1371,32 @@ def ruby_repo?
!ENV["GEM_COMMAND"].nil?
end

##
# Is this test running under Ruby::Box (RUBY_BOX=1)?

def ruby_box_enabled?
defined?(Ruby::Box) && Ruby::Box.enabled?
end

##
# Ruby::Box gives each box detached copies of the stdio globals, so
# reassigning $stdout/$stderr cannot capture output written by Kernel#warn,
# Kernel#puts or subprocesses. Pends until the ruby-core fix for
# https://bugs.ruby-lang.org/issues/21867 lands.

def pend_for_ruby_box_stdio_capture
pend "Ruby::Box breaks $stdout/$stderr capture (https://bugs.ruby-lang.org/issues/21867)" if ruby_box_enabled?
end

##
# Under Ruby::Box, Marshal in the main box cannot resolve Gem:: (and other
# boxed) constants. Pends until the ruby-core fix for
# https://bugs.ruby-lang.org/issues/22090 lands.

def pend_for_ruby_box_marshal
pend "Marshal cannot resolve boxed constants under Ruby::Box (https://bugs.ruby-lang.org/issues/22090)" if ruby_box_enabled?
end

##
# Returns the make command for the current platform. For versions of Ruby
# built on MS Windows with VC++ or Borland it will return 'nmake'. On all
Expand Down
3 changes: 3 additions & 0 deletions test/rubygems/test_deprecate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ def test_deprecated_method_calls_the_old_method
end

def test_deprecated_method_outputs_a_warning
pend_for_ruby_box_stdio_capture
out, err = capture_output do
thing = Thing.new
thing.foo
Expand Down Expand Up @@ -165,6 +166,7 @@ def execute
end

def test_deprecated_method_outputs_a_warning_old_way
pend_for_ruby_box_stdio_capture
out, err = capture_output do
thing = OtherThing.new
thing.foo
Expand All @@ -180,6 +182,7 @@ def test_deprecated_method_outputs_a_warning_old_way
end

def test_deprecated_method_when_class_overrides_format
pend_for_ruby_box_stdio_capture
out, err = capture_output do
thing = ThingWithFormat.new
thing.foo
Expand Down
3 changes: 2 additions & 1 deletion test/rubygems/test_exit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
class TestGemExit < Gem::TestCase
def test_exit
system(*ruby_with_rubygems_in_load_path, "-e", "raise Gem::SystemExitException.new(2)")
assert_equal 2, $?.exitstatus
# Process.last_status instead of $?, which Ruby::Box leaves uninitialized
assert_equal 2, Process.last_status.exitstatus
end

def test_status
Expand Down
2 changes: 2 additions & 0 deletions test/rubygems/test_gem.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1291,6 +1291,7 @@ def test_self_try_activate_missing_prerelease
end

def test_self_try_activate_missing_extensions
pend_for_ruby_box_stdio_capture
spec = util_spec "ext", "1" do |s|
s.extensions = %w[ext/extconf.rb]
s.installed_by_version = v("2.2")
Expand Down Expand Up @@ -1346,6 +1347,7 @@ def test_setting_paths_does_not_mutate_parameter_object
end

def test_deprecated_paths=
pend_for_ruby_box_stdio_capture
stdout, stderr = capture_output do
Gem.paths = { "GEM_HOME" => Gem.paths.home,
"GEM_PATH" => [Gem.paths.home, "foo"] }
Expand Down
1 change: 1 addition & 0 deletions test/rubygems/test_gem_commands_build_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ def test_execute_strict_with_warnings
end

def test_execute_bad_spec
pend_for_ruby_box_stdio_capture
@gem.date = "2010-11-08"

gemspec_file = File.join(@tempdir, @gem.spec_name)
Expand Down
1 change: 1 addition & 0 deletions test/rubygems/test_gem_commands_open_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def gem(name, version = "1.0")
end

def test_execute
pend_for_ruby_box_stdio_capture
omit "JRuby on Windows spawns the editor with a different cwd" if Gem.win_platform? && Gem.java_platform?

@cmd.options[:args] = %w[foo]
Expand Down
1 change: 1 addition & 0 deletions test/rubygems/test_gem_commands_specification_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ def test_execute_file
end

def test_execute_marshal
pend_for_ruby_box_marshal
foo = util_spec "foo", "2"

install_specs foo
Expand Down
1 change: 1 addition & 0 deletions test/rubygems/test_gem_config_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ def test_handle_arguments_backtrace
end

def test_handle_arguments_debug
pend_for_ruby_box_stdio_capture
assert_equal false, $DEBUG

args = %w[--debug]
Expand Down
2 changes: 2 additions & 0 deletions test/rubygems/test_gem_dependency_installer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1103,6 +1103,7 @@ def test_install_version_default
end

def test_install_legacy_spec_with_nil_required_ruby_version
pend_for_ruby_box_marshal
path = File.expand_path "data/null-required-ruby-version.gemspec.rz", __dir__
spec = Marshal.load Gem.read_binary(path)
def spec.validate(*args); end
Expand All @@ -1128,6 +1129,7 @@ def spec.validate(*args); end
end

def test_install_legacy_spec_with_nil_required_rubygems_version
pend_for_ruby_box_marshal
path = File.expand_path "data/null-required-rubygems-version.gemspec.rz", __dir__
spec = Marshal.load Gem.read_binary(path)
def spec.validate(*args); end
Expand Down
2 changes: 1 addition & 1 deletion test/rubygems/test_gem_ext_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ def self.expand(val, config = CONFIG); val; end
system(Gem.ruby, "-rmkmf", "-e", "exit MakeMakefile::RbConfig::CONFIG['host_os'] == 'fake_os'",
"--", "--target-rbconfig=#{fake_rbconfig}")
end
unless $?.success?
unless Process.last_status.success?
assert_include(stderr, "uninitialized constant MakeMakefile::RbConfig")
pend "This version of mkmf does not support --target-rbconfig"
end
Expand Down
7 changes: 4 additions & 3 deletions test/rubygems/test_gem_ext_cargo_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ def test_full_integration
Open3.capture2e(*gem, "build", "rust_ruby_example.gemspec", "--output", built_gem)
Open3.capture2e(*gem, "install", "--verbose", "--local", built_gem, *ARGV)

stdout_and_stderr_str, status = Open3.capture2e(env_for_subprocess, *ruby_with_rubygems_in_load_path, "-rrust_ruby_example", "-e", "puts 'Result: ' + RustRubyExample.reverse('hello world')")
# Require inside -e because -r bypasses gem activation under RUBY_BOX=1
stdout_and_stderr_str, status = Open3.capture2e(env_for_subprocess, *ruby_with_rubygems_in_load_path, "-e", "require 'rust_ruby_example'; puts 'Result: ' + RustRubyExample.reverse('hello world')")
assert status.success?, stdout_and_stderr_str
assert_match "Result: #{"hello world".reverse}", stdout_and_stderr_str
end
Expand All @@ -134,7 +135,7 @@ def test_custom_name
Open3.capture2e(*gem, "install", "--verbose", "--local", built_gem, *ARGV)
end

stdout_and_stderr_str, status = Open3.capture2e(env_for_subprocess, *ruby_with_rubygems_in_load_path, "-rcustom_name", "-e", "puts 'Result: ' + CustomName.say_hello")
stdout_and_stderr_str, status = Open3.capture2e(env_for_subprocess, *ruby_with_rubygems_in_load_path, "-e", "require 'custom_name'; puts 'Result: ' + CustomName.say_hello")

assert status.success?, stdout_and_stderr_str
assert_match "Result: Hello world!", stdout_and_stderr_str
Expand Down Expand Up @@ -199,7 +200,7 @@ def skip_unsupported_platforms!
pend "jruby not supported" if Gem.java_platform?
pend "truffleruby not supported (yet)" if RUBY_ENGINE == "truffleruby"
system(@rust_envs, "cargo", "-V", out: IO::NULL, err: [:child, :out])
pend "cargo not present" unless $?.success?
pend "cargo not present" unless Process.last_status.success?
pend "ruby.h is not provided by ruby repo" if ruby_repo?
pend "rust toolchain of mingw is broken" if mingw_windows?
end
Expand Down
2 changes: 2 additions & 0 deletions test/rubygems/test_gem_package.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1077,6 +1077,7 @@ def test_verify_corrupt
end

def test_verify_corrupt_tar_metadata_entry
pend_for_ruby_box_stdio_capture
gem = tar_file_header("metadata.gz", "", 0, 999, Time.now)

File.open "corrupt.gem", "wb" do |io|
Expand Down Expand Up @@ -1113,6 +1114,7 @@ def test_verify_corrupt_tar_checksums_entry
end

def test_verify_corrupt_tar_data_entry
pend_for_ruby_box_stdio_capture
gem = tar_file_header("data.tar.gz", "", 0, 100, Time.now)

File.open "corrupt.gem", "wb" do |io|
Expand Down
5 changes: 5 additions & 0 deletions test/rubygems/test_gem_package_tar_header_ractor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
end

class TestGemPackageTarHeaderRactor < Gem::Package::TarTestCase
def setup
super
pend "Ruby::Box ignores $VERBOSE=, so assert_ractor cannot keep the Ractor experimental warning out of the child stderr (https://bugs.ruby-lang.org/issues/22282)" if ruby_box_enabled?
end

SETUP = <<~RUBY
header = {
name: "x",
Expand Down
2 changes: 2 additions & 0 deletions test/rubygems/test_gem_request_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def test_install_from_gemdeps
end

def test_install_from_gemdeps_explain
pend_for_ruby_box_stdio_capture
spec_fetcher do |fetcher|
fetcher.gem "a", 2
end
Expand All @@ -94,6 +95,7 @@ def test_install_from_gemdeps_explain
end

def test_install_from_gemdeps_explain_verbose
pend_for_ruby_box_stdio_capture
spec_fetcher do |fetcher|
fetcher.gem "a", 2
end
Expand Down
3 changes: 3 additions & 0 deletions test/rubygems/test_gem_request_set_gem_dependency_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def test_gem
end

def test_gem_duplicate
pend_for_ruby_box_stdio_capture
@gda.gem "a"

_, err = capture_output do
Expand Down Expand Up @@ -128,6 +129,7 @@ def test_gem_bitbucket_expand_path
end

def test_gem_git_branch
pend_for_ruby_box_stdio_capture
_, err = capture_output do
@gda.gem "a", git: "git/a", branch: "other", tag: "v1"
end
Expand All @@ -149,6 +151,7 @@ def test_gem_git_gist
end

def test_gem_git_ref
pend_for_ruby_box_stdio_capture
_, err = capture_output do
@gda.gem "a", git: "git/a", ref: "abcd123", branch: "other"
end
Expand Down
1 change: 1 addition & 0 deletions test/rubygems/test_gem_requirement.rb
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ def self.exploit(arg)
end

def test_marshal_load_attack
pend_for_ruby_box_marshal
wa = Gem::Net::WriteAdapter.allocate
wa.instance_variable_set(:@socket, self.class)
wa.instance_variable_set(:@method_id, :exploit)
Expand Down
Loading
Loading