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
2 changes: 1 addition & 1 deletion bundler/spec/bundler/gem_helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ def sha512_hexdigest(path)
mock_confirm_message "#{app_name} (#{app_version}) installed."
subject.install_gem(nil, :local)
expect(app_gem_path).to exist
gem_command :list
installed_gems_list
expect(out).to include("#{app_name} (#{app_version})")
end
end
Expand Down
2 changes: 1 addition & 1 deletion bundler/spec/commands/check_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@
bundle "config set --local path vendor/bundle"
bundle :cache

gem_command "uninstall myrack", env: { "GEM_HOME" => vendored_gems.to_s }
uninstall_gem("myrack", env: { "GEM_HOME" => vendored_gems.to_s })

bundle "check", raise_on_error: false
expect(err).to include("* myrack (1.0.0)")
Expand Down
8 changes: 4 additions & 4 deletions bundler/spec/commands/clean_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ def should_not_have_gems(*gems)
gem "myrack"
G

gem_command :list
installed_gems_list
expect(out).to include("myrack (1.0.0)").and include("thin (1.0)")
end

Expand Down Expand Up @@ -498,7 +498,7 @@ def should_not_have_gems(*gems)
end
bundle :update, all: true

gem_command :list
installed_gems_list
expect(out).to include("foo (1.0.1, 1.0)")
end

Expand All @@ -522,7 +522,7 @@ def should_not_have_gems(*gems)
bundle "clean --force"

expect(out).to include("Removing foo (1.0)")
gem_command :list
installed_gems_list
expect(out).not_to include("foo (1.0)")
expect(out).to include("myrack (1.0.0)")
end
Expand Down Expand Up @@ -556,7 +556,7 @@ def should_not_have_gems(*gems)
expect(err).to include(system_gem_path.to_s)
expect(err).to include("grant write permissions")

gem_command :list
installed_gems_list
expect(out).to include("foo (1.0)")
expect(out).to include("myrack (1.0.0)")
end
Expand Down
2 changes: 1 addition & 1 deletion bundler/spec/install/gems/compact_index_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -997,7 +997,7 @@ def start
gem "activemerchant"
end
G
gem_command "uninstall activemerchant"
uninstall_gem("activemerchant")
bundle "update rails", artifice: "compact_index"
count = lockfile.match?("CHECKSUMS") ? 2 : 1 # Once in the specs, and once in CHECKSUMS
expect(lockfile.scan(/activemerchant \(/).size).to eq(count)
Expand Down
2 changes: 1 addition & 1 deletion bundler/spec/support/builders.rb
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,7 @@ def _build(opts)
Bundler.rubygems.build(@spec, opts[:skip_validation])
end
elsif opts[:skip_validation]
@context.gem_command "build --force #{@spec.name}", dir: lib_path
Dir.chdir(lib_path) { Gem::Package.build(@spec, true) }
else
Dir.chdir(lib_path) { Gem::Package.build(@spec) }
end
Expand Down
71 changes: 56 additions & 15 deletions bundler/spec/support/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -182,19 +182,6 @@ def gembin(cmd, options = {})
sys_exec(cmd.to_s, options)
end

def gem_command(command, options = {})
env = options[:env] || {}
env["RUBYOPT"] = opt_add(opt_add("-r#{hax}", env["RUBYOPT"]), ENV["RUBYOPT"])
options[:env] = env

# Sometimes `gem install` commands hang at dns resolution, which has a
# default timeout of 60 seconds. When that happens, the timeout for a
# command is expired too. So give `gem install` commands a bit more time.
options[:timeout] = 120

sys_exec("#{Path.gem_bin} #{command}", options)
end

def sys_exec(cmd, options = {}, &block)
env = options[:env] || {}
env["RUBYOPT"] = opt_add(opt_add("-r#{spec_dir}/support/switch_rubygems.rb", env["RUBYOPT"]), ENV["RUBYOPT"])
Expand Down Expand Up @@ -326,9 +313,17 @@ def self.install_dev_bundler
def install_gem(path, install_dir, default = false)
raise ArgumentError, "`#{path}` does not exist!" unless File.exist?(path)

args = "--no-document --ignore-dependencies --verbose --local --install-dir #{install_dir}"
require "rubygems/installer"

gem_command "install #{args} '#{path}'"
installer = Gem::Installer.at(
path.to_s,
install_dir: install_dir.to_s,
document: [],
ignore_dependencies: true,
wrappers: true,
force: true
)
installer.install

if default
gem = Pathname.new(path).basename.to_s.match(/(.*)\.gem/)[1]
Expand All @@ -343,6 +338,52 @@ def install_gem(path, install_dir, default = false)
end
end

def uninstall_gem(name, options = {})
require "rubygems/uninstaller"

gem_home = options.dig(:env, "GEM_HOME") || system_gem_path.to_s

uninstaller = Gem::Uninstaller.new(
name,
install_dir: gem_home,
ignore: true,
executables: true,
all: true
)
uninstaller.uninstall
Comment on lines +346 to +353
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The uninstall_gem method should temporarily set ENV["GEM_HOME"] to ensure Gem::Uninstaller operates on the correct gem installation directory. Without this, the uninstaller may not properly locate gems in the specified install_dir, especially when it differs from the current ENV["GEM_HOME"]. Consider wrapping the uninstaller logic with temporary ENV["GEM_HOME"] modification and Gem.clear_paths calls, similar to how installed_gems_list handles this.

Suggested change
uninstaller = Gem::Uninstaller.new(
name,
install_dir: gem_home,
ignore: true,
executables: true,
all: true
)
uninstaller.uninstall
old_gem_home = ENV["GEM_HOME"]
ENV["GEM_HOME"] = gem_home
Gem.clear_paths
begin
uninstaller = Gem::Uninstaller.new(
name,
install_dir: gem_home,
ignore: true,
executables: true,
all: true
)
uninstaller.uninstall
ensure
ENV["GEM_HOME"] = old_gem_home
Gem.clear_paths
end

Copilot uses AI. Check for mistakes.
end

def installed_gems_list(options = {})
gem_home = options.dig(:env, "GEM_HOME") || system_gem_path.to_s

# Temporarily set GEM_HOME for the command
old_gem_home = ENV["GEM_HOME"]
ENV["GEM_HOME"] = gem_home
Gem.clear_paths

begin
require "rubygems/commands/list_command"

# Capture output from the list command
output_io = StringIO.new
cmd = Gem::Commands::ListCommand.new
cmd.ui = Gem::StreamUI.new(StringIO.new, output_io, StringIO.new, false)
cmd.invoke
output = output_io.string.strip
ensure
ENV["GEM_HOME"] = old_gem_home
Gem.clear_paths
end
Comment on lines +359 to +376
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The installed_gems_list method manually saves and restores ENV["GEM_HOME"], but this codebase has an established pattern for temporary environment variable modifications using the without_env_side_effects helper (see lines 393-415). Using this helper would be more consistent with the codebase's patterns and provide better safety guarantees, as it ensures all ENV changes are properly restored even if an exception occurs during the begin block.

Copilot uses AI. Check for mistakes.

# Create a fake command execution so `out` helper works
command_execution = Spec::CommandExecution.new("gem list", timeout: 60)
command_execution.original_stdout << output
command_execution.exitstatus = 0
command_executions << command_execution

output
end

def with_built_bundler(version = nil, opts = {}, &block)
require_relative "builders"

Expand Down
Loading