mirror of
https://github.com/ruby/ruby.git
synced 2025-08-15 13:39:04 +02:00

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
1286 lines
36 KiB
Ruby
1286 lines
36 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
RSpec.describe "bundle exec" do
|
|
it "works with --gemfile flag" do
|
|
system_gems(%w[myrack-1.0.0 myrack-0.9.1], path: default_bundle_path)
|
|
|
|
gemfile "CustomGemfile", <<-G
|
|
source "https://gem.repo1"
|
|
gem "myrack", "1.0.0"
|
|
G
|
|
|
|
bundle "exec --gemfile CustomGemfile myrackup"
|
|
expect(out).to eq("1.0.0")
|
|
end
|
|
|
|
it "activates the correct gem" do
|
|
system_gems(%w[myrack-1.0.0 myrack-0.9.1], path: default_bundle_path)
|
|
|
|
gemfile <<-G
|
|
source "https://gem.repo1"
|
|
gem "myrack", "0.9.1"
|
|
G
|
|
|
|
bundle "exec myrackup"
|
|
expect(out).to eq("0.9.1")
|
|
end
|
|
|
|
it "works and prints no warnings when HOME is not writable" do
|
|
system_gems(%w[myrack-1.0.0 myrack-0.9.1], path: default_bundle_path)
|
|
|
|
gemfile <<-G
|
|
source "https://gem.repo1"
|
|
gem "myrack", "0.9.1"
|
|
G
|
|
|
|
bundle "exec myrackup", env: { "HOME" => "/" }
|
|
expect(out).to eq("0.9.1")
|
|
expect(err).to be_empty
|
|
end
|
|
|
|
it "works when the bins are in ~/.bundle" do
|
|
install_gemfile <<-G
|
|
source "https://gem.repo1"
|
|
gem "myrack"
|
|
G
|
|
|
|
bundle "exec myrackup"
|
|
expect(out).to eq("1.0.0")
|
|
end
|
|
|
|
it "works when running from a random directory" do
|
|
install_gemfile <<-G
|
|
source "https://gem.repo1"
|
|
gem "myrack"
|
|
G
|
|
|
|
bundle "exec 'cd #{tmp("gems")} && myrackup'"
|
|
|
|
expect(out).to eq("1.0.0")
|
|
end
|
|
|
|
it "works when exec'ing something else" do
|
|
install_gemfile "source \"https://gem.repo1\"; gem \"myrack\""
|
|
bundle "exec echo exec"
|
|
expect(out).to eq("exec")
|
|
end
|
|
|
|
it "works when exec'ing to ruby" do
|
|
install_gemfile "source \"https://gem.repo1\"; gem \"myrack\""
|
|
bundle "exec ruby -e 'puts %{hi}'"
|
|
expect(out).to eq("hi")
|
|
end
|
|
|
|
it "works when exec'ing to rubygems" do
|
|
install_gemfile "source \"https://gem.repo1\"; gem \"myrack\""
|
|
bundle "exec #{gem_cmd} --version"
|
|
expect(out).to eq(Gem::VERSION)
|
|
end
|
|
|
|
it "works when exec'ing to rubygems through sh -c" do
|
|
install_gemfile "source \"https://gem.repo1\"; gem \"myrack\""
|
|
bundle "exec sh -c '#{gem_cmd} --version'"
|
|
expect(out).to eq(Gem::VERSION)
|
|
end
|
|
|
|
it "works when exec'ing back to bundler to run a remote resolve" do
|
|
install_gemfile <<-G
|
|
source "https://gem.repo1"
|
|
gem "myrack", "0.9.1"
|
|
G
|
|
|
|
bundle "exec bundle lock", env: { "BUNDLER_VERSION" => Bundler::VERSION }
|
|
|
|
expect(out).to include("Writing lockfile")
|
|
end
|
|
|
|
it "respects custom process title when loading through ruby" do
|
|
skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
|
|
|
|
script_that_changes_its_own_title_and_checks_if_picked_up_by_ps_unix_utility = <<~'RUBY'
|
|
Process.setproctitle("1-2-3-4-5-6-7")
|
|
puts `ps -ocommand= -p#{$$}`
|
|
RUBY
|
|
gemfile "Gemfile", "source \"https://gem.repo1\""
|
|
create_file "a.rb", script_that_changes_its_own_title_and_checks_if_picked_up_by_ps_unix_utility
|
|
bundle "exec ruby a.rb"
|
|
expect(out).to eq("1-2-3-4-5-6-7")
|
|
end
|
|
|
|
it "accepts --verbose" do
|
|
install_gemfile "source \"https://gem.repo1\"; gem \"myrack\""
|
|
bundle "exec --verbose echo foobar"
|
|
expect(out).to eq("foobar")
|
|
end
|
|
|
|
it "passes --verbose to command if it is given after the command" do
|
|
install_gemfile "source \"https://gem.repo1\"; gem \"myrack\""
|
|
bundle "exec echo --verbose"
|
|
expect(out).to eq("--verbose")
|
|
end
|
|
|
|
it "handles --keep-file-descriptors" do
|
|
skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
|
|
|
|
require "tempfile"
|
|
|
|
command = Tempfile.new("io-test")
|
|
command.sync = true
|
|
command.write <<-G
|
|
if ARGV[0]
|
|
IO.for_fd(ARGV[0].to_i)
|
|
else
|
|
require 'tempfile'
|
|
io = Tempfile.new("io-test-fd")
|
|
args = %W[#{Gem.ruby} -I#{lib_dir} #{bindir.join("bundle")} exec --keep-file-descriptors #{Gem.ruby} #{command.path} \#{io.to_i}]
|
|
args << { io.to_i => io }
|
|
exec(*args)
|
|
end
|
|
G
|
|
|
|
install_gemfile "source \"https://gem.repo1\""
|
|
in_bundled_app "#{Gem.ruby} #{command.path}"
|
|
|
|
expect(out).to be_empty
|
|
expect(err).to be_empty
|
|
end
|
|
|
|
it "accepts --keep-file-descriptors" do
|
|
install_gemfile "source \"https://gem.repo1\""
|
|
bundle "exec --keep-file-descriptors echo foobar"
|
|
|
|
expect(err).to be_empty
|
|
end
|
|
|
|
it "can run a command named --verbose" do
|
|
skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
|
|
|
|
install_gemfile "source \"https://gem.repo1\"; gem \"myrack\""
|
|
File.open(bundled_app("--verbose"), "w") do |f|
|
|
f.puts "#!/bin/sh"
|
|
f.puts "echo foobar"
|
|
end
|
|
File.chmod(0o744, bundled_app("--verbose"))
|
|
with_path_as(".") do
|
|
bundle "exec -- --verbose"
|
|
end
|
|
expect(out).to eq("foobar")
|
|
end
|
|
|
|
it "handles different versions in different bundles" do
|
|
build_repo2 do
|
|
build_gem "myrack_two", "1.0.0" do |s|
|
|
s.executables = "myrackup"
|
|
end
|
|
end
|
|
|
|
install_gemfile <<-G
|
|
source "https://gem.repo1"
|
|
gem "myrack", "0.9.1"
|
|
G
|
|
|
|
install_gemfile bundled_app2("Gemfile"), <<-G, dir: bundled_app2
|
|
source "https://gem.repo2"
|
|
gem "myrack_two", "1.0.0"
|
|
G
|
|
|
|
bundle "exec myrackup"
|
|
|
|
expect(out).to eq("0.9.1")
|
|
|
|
bundle "exec myrackup", dir: bundled_app2
|
|
expect(out).to eq("1.0.0")
|
|
end
|
|
|
|
context "with default gems" do
|
|
# TODO: Switch to ERB::VERSION once Ruby 3.4 support is dropped, so all
|
|
# supported rubies include an `erb` gem version where `ERB::VERSION` is
|
|
# public
|
|
let(:default_erb_version) { ruby "require 'erb/version'; puts ERB.const_get(:VERSION)" }
|
|
|
|
context "when not specified in Gemfile" do
|
|
before do
|
|
install_gemfile "source \"https://gem.repo1\""
|
|
end
|
|
|
|
it "uses version provided by ruby" do
|
|
bundle "exec erb --version"
|
|
|
|
expect(stdboth).to eq(default_erb_version)
|
|
end
|
|
end
|
|
|
|
context "when specified in Gemfile directly" do
|
|
let(:specified_erb_version) { "2.0.0" }
|
|
|
|
before do
|
|
build_repo2 do
|
|
build_gem "erb", specified_erb_version do |s|
|
|
s.executables = "erb"
|
|
end
|
|
end
|
|
|
|
install_gemfile <<-G
|
|
source "https://gem.repo2"
|
|
gem "erb", "#{specified_erb_version}"
|
|
G
|
|
end
|
|
|
|
it "uses version specified" do
|
|
bundle "exec erb --version"
|
|
|
|
expect(stdboth).to eq(specified_erb_version)
|
|
end
|
|
end
|
|
|
|
context "when specified in Gemfile indirectly" do
|
|
let(:indirect_erb_version) { "2.0.0" }
|
|
|
|
before do
|
|
build_repo2 do
|
|
build_gem "erb", indirect_erb_version do |s|
|
|
s.executables = "erb"
|
|
end
|
|
|
|
build_gem "gem_depending_on_old_erb" do |s|
|
|
s.add_dependency "erb", indirect_erb_version
|
|
end
|
|
end
|
|
|
|
install_gemfile <<-G
|
|
source "https://gem.repo2"
|
|
gem "gem_depending_on_old_erb"
|
|
G
|
|
end
|
|
|
|
it "uses resolved version" do
|
|
bundle "exec erb --version"
|
|
|
|
expect(stdboth).to eq(indirect_erb_version)
|
|
end
|
|
end
|
|
end
|
|
|
|
it "warns about executable conflicts" do
|
|
build_repo2 do
|
|
build_gem "myrack_two", "1.0.0" do |s|
|
|
s.executables = "myrackup"
|
|
end
|
|
end
|
|
|
|
bundle "config set --global path.system true"
|
|
|
|
install_gemfile <<-G
|
|
source "https://gem.repo1"
|
|
gem "myrack", "0.9.1"
|
|
G
|
|
|
|
install_gemfile bundled_app2("Gemfile"), <<-G, dir: bundled_app2
|
|
source "https://gem.repo2"
|
|
gem "myrack_two", "1.0.0"
|
|
G
|
|
|
|
bundle "exec myrackup"
|
|
|
|
expect(last_command.stderr).to eq(
|
|
"Bundler is using a binstub that was created for a different gem (myrack).\n" \
|
|
"You should run `bundle binstub myrack_two` to work around a system/bundle conflict."
|
|
)
|
|
end
|
|
|
|
it "handles gems installed with --without" do
|
|
bundle "config set --local without middleware"
|
|
install_gemfile <<-G
|
|
source "https://gem.repo1"
|
|
gem "myrack" # myrack 0.9.1 and 1.0 exist
|
|
|
|
group :middleware do
|
|
gem "myrack_middleware" # myrack_middleware depends on myrack 0.9.1
|
|
end
|
|
G
|
|
|
|
bundle "exec myrackup"
|
|
|
|
expect(out).to eq("0.9.1")
|
|
expect(the_bundle).not_to include_gems "myrack_middleware 1.0"
|
|
end
|
|
|
|
it "does not duplicate already exec'ed RUBYOPT" do
|
|
create_file("echoopt", "#!/usr/bin/env ruby\nprint ENV['RUBYOPT']")
|
|
install_gemfile <<-G
|
|
source "https://gem.repo1"
|
|
gem "myrack"
|
|
G
|
|
|
|
bundler_setup_opt = "-r#{lib_dir}/bundler/setup"
|
|
|
|
rubyopt = opt_add(bundler_setup_opt, ENV["RUBYOPT"])
|
|
|
|
bundle "exec echoopt"
|
|
expect(out.split(" ").count(bundler_setup_opt)).to eq(1)
|
|
|
|
bundle "exec echoopt", env: { "RUBYOPT" => rubyopt }
|
|
expect(out.split(" ").count(bundler_setup_opt)).to eq(1)
|
|
end
|
|
|
|
it "does not duplicate already exec'ed RUBYLIB" do
|
|
create_file("echolib", "#!/usr/bin/env ruby\nprint ENV['RUBYLIB']")
|
|
install_gemfile <<-G
|
|
source "https://gem.repo1"
|
|
gem "myrack"
|
|
G
|
|
|
|
rubylib = ENV["RUBYLIB"]
|
|
rubylib = rubylib.to_s.split(File::PATH_SEPARATOR).unshift lib_dir.to_s
|
|
rubylib = rubylib.uniq.join(File::PATH_SEPARATOR)
|
|
|
|
bundle "exec echolib"
|
|
expect(out).to include(rubylib)
|
|
|
|
bundle "exec echolib", env: { "RUBYLIB" => rubylib }
|
|
expect(out).to include(rubylib)
|
|
end
|
|
|
|
it "errors nicely when the argument doesn't exist" do
|
|
install_gemfile <<-G
|
|
source "https://gem.repo1"
|
|
gem "myrack"
|
|
G
|
|
|
|
bundle "exec foobarbaz", raise_on_error: false
|
|
expect(exitstatus).to eq(127)
|
|
expect(err).to include("bundler: command not found: foobarbaz")
|
|
expect(err).to include("Install missing gem executables with `bundle install`")
|
|
end
|
|
|
|
it "errors nicely when the argument is not executable" do
|
|
install_gemfile <<-G
|
|
source "https://gem.repo1"
|
|
gem "myrack"
|
|
G
|
|
|
|
bundled_app("foo").write("")
|
|
bundle "exec ./foo", raise_on_error: false
|
|
expect(exitstatus).to eq(126)
|
|
expect(err).to include("bundler: not executable: ./foo")
|
|
end
|
|
|
|
it "errors nicely when no arguments are passed" do
|
|
install_gemfile <<-G
|
|
source "https://gem.repo1"
|
|
gem "myrack"
|
|
G
|
|
|
|
bundle "exec", raise_on_error: false
|
|
expect(exitstatus).to eq(128)
|
|
expect(err).to include("bundler: exec needs a command to run")
|
|
end
|
|
|
|
it "raises a helpful error when exec'ing to something outside of the bundle" do
|
|
system_gems(%w[myrack-1.0.0 myrack-0.9.1], path: default_bundle_path)
|
|
|
|
bundle "config set clean false" # want to keep the myrackup binstub
|
|
install_gemfile <<-G
|
|
source "https://gem.repo1"
|
|
gem "foo"
|
|
G
|
|
[true, false].each do |l|
|
|
bundle "config set disable_exec_load #{l}"
|
|
bundle "exec myrackup", raise_on_error: false
|
|
expect(err).to include "can't find executable myrackup for gem myrack. myrack is not currently included in the bundle, perhaps you meant to add it to your Gemfile?"
|
|
end
|
|
end
|
|
|
|
describe "with help flags" do
|
|
each_prefix = proc do |string, &blk|
|
|
1.upto(string.length) {|l| blk.call(string[0, l]) }
|
|
end
|
|
each_prefix.call("exec") do |exec|
|
|
describe "when #{exec} is used" do
|
|
before(:each) do
|
|
install_gemfile <<-G
|
|
source "https://gem.repo1"
|
|
gem "myrack"
|
|
G
|
|
|
|
create_file("print_args", <<-'RUBY')
|
|
#!/usr/bin/env ruby
|
|
puts "args: #{ARGV.inspect}"
|
|
RUBY
|
|
end
|
|
|
|
it "shows executable's man page when --help is after the executable" do
|
|
bundle "#{exec} print_args --help"
|
|
expect(out).to eq('args: ["--help"]')
|
|
end
|
|
|
|
it "shows executable's man page when --help is after the executable and an argument" do
|
|
bundle "#{exec} print_args foo --help"
|
|
expect(out).to eq('args: ["foo", "--help"]')
|
|
|
|
bundle "#{exec} print_args foo bar --help"
|
|
expect(out).to eq('args: ["foo", "bar", "--help"]')
|
|
|
|
bundle "#{exec} print_args foo --help bar"
|
|
expect(out).to eq('args: ["foo", "--help", "bar"]')
|
|
end
|
|
|
|
it "shows executable's man page when the executable has a -" do
|
|
FileUtils.mv(bundled_app("print_args"), bundled_app("docker-template"))
|
|
FileUtils.mv(bundled_app("print_args.bat"), bundled_app("docker-template.bat")) if Gem.win_platform?
|
|
bundle "#{exec} docker-template build discourse --help"
|
|
expect(out).to eq('args: ["build", "discourse", "--help"]')
|
|
end
|
|
|
|
it "shows executable's man page when --help is after another flag" do
|
|
bundle "#{exec} print_args --bar --help"
|
|
expect(out).to eq('args: ["--bar", "--help"]')
|
|
end
|
|
|
|
it "uses executable's original behavior for -h" do
|
|
bundle "#{exec} print_args -h"
|
|
expect(out).to eq('args: ["-h"]')
|
|
end
|
|
|
|
it "shows bundle-exec's man page when --help is between exec and the executable" do
|
|
with_fake_man do
|
|
bundle "#{exec} --help echo"
|
|
end
|
|
expect(out).to include(%(["#{man_dir}/bundle-exec.1"]))
|
|
end
|
|
|
|
it "shows bundle-exec's man page when --help is before exec" do
|
|
with_fake_man do
|
|
bundle "--help #{exec}"
|
|
end
|
|
expect(out).to include(%(["#{man_dir}/bundle-exec.1"]))
|
|
end
|
|
|
|
it "shows bundle-exec's man page when -h is before exec" do
|
|
with_fake_man do
|
|
bundle "-h #{exec}"
|
|
end
|
|
expect(out).to include(%(["#{man_dir}/bundle-exec.1"]))
|
|
end
|
|
|
|
it "shows bundle-exec's man page when --help is after exec" do
|
|
with_fake_man do
|
|
bundle "#{exec} --help"
|
|
end
|
|
expect(out).to include(%(["#{man_dir}/bundle-exec.1"]))
|
|
end
|
|
|
|
it "shows bundle-exec's man page when -h is after exec" do
|
|
with_fake_man do
|
|
bundle "#{exec} -h"
|
|
end
|
|
expect(out).to include(%(["#{man_dir}/bundle-exec.1"]))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "with gem executables" do
|
|
describe "run from a random directory" do
|
|
before(:each) do
|
|
install_gemfile <<-G
|
|
source "https://gem.repo1"
|
|
gem "myrack"
|
|
G
|
|
end
|
|
|
|
it "works when unlocked" do
|
|
bundle "exec 'cd #{tmp("gems")} && myrackup'"
|
|
expect(out).to eq("1.0.0")
|
|
end
|
|
|
|
it "works when locked" do
|
|
expect(the_bundle).to be_locked
|
|
bundle "exec 'cd #{tmp("gems")} && myrackup'"
|
|
expect(out).to eq("1.0.0")
|
|
end
|
|
end
|
|
|
|
describe "from gems bundled via :path" do
|
|
before(:each) do
|
|
build_lib "fizz", path: home("fizz") do |s|
|
|
s.executables = "fizz"
|
|
end
|
|
|
|
install_gemfile <<-G
|
|
source "https://gem.repo1"
|
|
gem "fizz", :path => "#{File.expand_path(home("fizz"))}"
|
|
G
|
|
end
|
|
|
|
it "works when unlocked" do
|
|
bundle "exec fizz"
|
|
expect(out).to eq("1.0")
|
|
end
|
|
|
|
it "works when locked" do
|
|
expect(the_bundle).to be_locked
|
|
|
|
bundle "exec fizz"
|
|
expect(out).to eq("1.0")
|
|
end
|
|
end
|
|
|
|
describe "from gems bundled via :git" do
|
|
before(:each) do
|
|
build_git "fizz_git" do |s|
|
|
s.executables = "fizz_git"
|
|
end
|
|
|
|
install_gemfile <<-G
|
|
source "https://gem.repo1"
|
|
gem "fizz_git", :git => "#{lib_path("fizz_git-1.0")}"
|
|
G
|
|
end
|
|
|
|
it "works when unlocked" do
|
|
bundle "exec fizz_git"
|
|
expect(out).to eq("1.0")
|
|
end
|
|
|
|
it "works when locked" do
|
|
expect(the_bundle).to be_locked
|
|
bundle "exec fizz_git"
|
|
expect(out).to eq("1.0")
|
|
end
|
|
end
|
|
|
|
describe "from gems bundled via :git with no gemspec" do
|
|
before(:each) do
|
|
build_git "fizz_no_gemspec", gemspec: false do |s|
|
|
s.executables = "fizz_no_gemspec"
|
|
end
|
|
|
|
install_gemfile <<-G
|
|
source "https://gem.repo1"
|
|
gem "fizz_no_gemspec", "1.0", :git => "#{lib_path("fizz_no_gemspec-1.0")}"
|
|
G
|
|
end
|
|
|
|
it "works when unlocked" do
|
|
bundle "exec fizz_no_gemspec"
|
|
expect(out).to eq("1.0")
|
|
end
|
|
|
|
it "works when locked" do
|
|
expect(the_bundle).to be_locked
|
|
bundle "exec fizz_no_gemspec"
|
|
expect(out).to eq("1.0")
|
|
end
|
|
end
|
|
end
|
|
|
|
it "performs an automatic bundle install" do
|
|
gemfile <<-G
|
|
source "https://gem.repo1"
|
|
gem "myrack", "0.9.1"
|
|
gem "foo"
|
|
G
|
|
|
|
bundle "config set auto_install 1"
|
|
bundle "exec myrackup", artifice: "compact_index"
|
|
expect(out).to include("Installing foo 1.0")
|
|
end
|
|
|
|
it "performs an automatic bundle install with git gems" do
|
|
build_git "foo" do |s|
|
|
s.executables = "foo"
|
|
end
|
|
gemfile <<-G
|
|
source "https://gem.repo1"
|
|
gem "myrack", "0.9.1"
|
|
gem "foo", :git => "#{lib_path("foo-1.0")}"
|
|
G
|
|
|
|
bundle "config set auto_install 1"
|
|
bundle "exec foo", artifice: "compact_index"
|
|
expect(out).to include("Fetching myrack 0.9.1")
|
|
expect(out).to include("Fetching #{lib_path("foo-1.0")}")
|
|
expect(out.lines).to end_with("1.0")
|
|
end
|
|
|
|
it "loads the correct optparse when `auto_install` is set, and optparse is a dependency" do
|
|
build_repo4 do
|
|
build_gem "fastlane", "2.192.0" do |s|
|
|
s.executables = "fastlane"
|
|
s.add_dependency "optparse", "~> 999.999.999"
|
|
end
|
|
|
|
build_gem "optparse", "999.999.998"
|
|
build_gem "optparse", "999.999.999"
|
|
end
|
|
|
|
system_gems "optparse-999.999.998", gem_repo: gem_repo4
|
|
|
|
bundle "config set auto_install 1"
|
|
bundle "config set --local path vendor/bundle"
|
|
|
|
gemfile <<~G
|
|
source "https://gem.repo4"
|
|
gem "fastlane"
|
|
G
|
|
|
|
bundle "exec fastlane", artifice: "compact_index"
|
|
expect(out).to include("Installing optparse 999.999.999")
|
|
expect(out).to include("2.192.0")
|
|
end
|
|
|
|
describe "with gems bundled via :path with invalid gemspecs" do
|
|
it "outputs the gemspec validation errors" do
|
|
build_lib "foo"
|
|
|
|
gemspec = lib_path("foo-1.0").join("foo.gemspec").to_s
|
|
File.open(gemspec, "w") do |f|
|
|
f.write <<-G
|
|
Gem::Specification.new do |s|
|
|
s.name = 'foo'
|
|
s.version = '1.0'
|
|
s.summary = 'TODO: Add summary'
|
|
s.authors = 'Me'
|
|
s.rubygems_version = nil
|
|
end
|
|
G
|
|
end
|
|
|
|
gemfile <<-G
|
|
source "https://gem.repo1"
|
|
gem "foo", :path => "#{lib_path("foo-1.0")}"
|
|
G
|
|
|
|
bundle "exec erb", raise_on_error: false
|
|
|
|
expect(err).to match("The gemspec at #{lib_path("foo-1.0").join("foo.gemspec")} is not valid")
|
|
expect(err).to match(/missing value for attribute rubygems_version|rubygems_version must not be nil/)
|
|
end
|
|
end
|
|
|
|
describe "with gems bundled for deployment" do
|
|
it "works when calling bundler from another script" do
|
|
skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
|
|
|
|
gemfile <<-G
|
|
source "https://gem.repo1"
|
|
|
|
module Monkey
|
|
def bin_path(a,b,c)
|
|
raise Gem::GemNotFoundException.new('Fail')
|
|
end
|
|
end
|
|
Bundler.rubygems.extend(Monkey)
|
|
G
|
|
bundle "config set path.system true"
|
|
bundle "install"
|
|
bundle "exec ruby -e '`bundle -v`; puts $?.success?'", env: { "BUNDLER_VERSION" => Bundler::VERSION }
|
|
expect(out).to match("true")
|
|
end
|
|
end
|
|
|
|
describe "bundle exec gem uninstall" do
|
|
before do
|
|
build_repo4 do
|
|
build_gem "foo"
|
|
end
|
|
|
|
install_gemfile <<-G
|
|
source "https://gem.repo4"
|
|
|
|
gem "foo"
|
|
G
|
|
end
|
|
|
|
it "works" do
|
|
bundle "exec #{gem_cmd} uninstall foo"
|
|
expect(out).to eq("Successfully uninstalled foo-1.0")
|
|
end
|
|
end
|
|
|
|
describe "running gem commands in presence of rubygems plugins" do
|
|
before do
|
|
build_repo4 do
|
|
build_gem "foo" do |s|
|
|
s.write "lib/rubygems_plugin.rb", "puts 'FAIL'"
|
|
end
|
|
end
|
|
|
|
system_gems "foo-1.0", path: default_bundle_path, gem_repo: gem_repo4
|
|
|
|
install_gemfile <<-G
|
|
source "https://gem.repo4"
|
|
G
|
|
end
|
|
|
|
it "does not load plugins outside of the bundle" do
|
|
bundle "exec #{gem_cmd} -v"
|
|
expect(out).not_to include("FAIL")
|
|
end
|
|
end
|
|
|
|
context "`load`ing a ruby file instead of `exec`ing" do
|
|
let(:path) { bundled_app("ruby_executable") }
|
|
let(:shebang) { "#!/usr/bin/env ruby" }
|
|
let(:executable) { <<~RUBY.strip }
|
|
#{shebang}
|
|
|
|
require "myrack"
|
|
puts "EXEC: \#{caller.grep(/load/).empty? ? 'exec' : 'load'}"
|
|
puts "ARGS: \#{$0} \#{ARGV.join(' ')}"
|
|
puts "MYRACK: \#{MYRACK}"
|
|
process_title = `ps -o args -p \#{Process.pid}`.split("\n", 2).last.strip
|
|
puts "PROCESS: \#{process_title}"
|
|
RUBY
|
|
|
|
before do
|
|
bundled_app(path).open("w") {|f| f << executable }
|
|
bundled_app(path).chmod(0o755)
|
|
|
|
install_gemfile <<-G
|
|
source "https://gem.repo1"
|
|
gem "myrack"
|
|
G
|
|
end
|
|
|
|
let(:exec) { "EXEC: load" }
|
|
let(:args) { "ARGS: #{path} arg1 arg2" }
|
|
let(:myrack) { "MYRACK: 1.0.0" }
|
|
let(:process) do
|
|
title = "PROCESS: #{path}"
|
|
title += " arg1 arg2"
|
|
title
|
|
end
|
|
let(:exit_code) { 0 }
|
|
let(:expected) { [exec, args, myrack, process].join("\n") }
|
|
let(:expected_err) { "" }
|
|
|
|
subject { bundle "exec #{path} arg1 arg2", raise_on_error: false }
|
|
|
|
it "runs" do
|
|
skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
|
|
|
|
subject
|
|
expect(exitstatus).to eq(exit_code)
|
|
expect(err).to eq(expected_err)
|
|
expect(out).to eq(expected)
|
|
end
|
|
|
|
context "the executable exits explicitly" do
|
|
let(:executable) { super() << "\nexit #{exit_code}\nputs 'POST_EXIT'\n" }
|
|
|
|
context "with exit 0" do
|
|
it "runs" do
|
|
skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
|
|
|
|
subject
|
|
expect(exitstatus).to eq(exit_code)
|
|
expect(err).to eq(expected_err)
|
|
expect(out).to eq(expected)
|
|
end
|
|
end
|
|
|
|
context "with exit 99" do
|
|
let(:exit_code) { 99 }
|
|
|
|
it "runs" do
|
|
skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
|
|
|
|
subject
|
|
expect(exitstatus).to eq(exit_code)
|
|
expect(err).to eq(expected_err)
|
|
expect(out).to eq(expected)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "the executable exits by SignalException" do
|
|
let(:executable) do
|
|
ex = super()
|
|
ex << "\n"
|
|
ex << "raise SignalException, 'SIGTERM'\n"
|
|
ex
|
|
end
|
|
let(:expected_err) { "" }
|
|
let(:exit_code) do
|
|
exit_status_for_signal(Signal.list["TERM"])
|
|
end
|
|
|
|
it "runs" do
|
|
skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
|
|
|
|
subject
|
|
expect(exitstatus).to eq(exit_code)
|
|
expect(err).to eq(expected_err)
|
|
expect(out).to eq(expected)
|
|
end
|
|
end
|
|
|
|
context "the executable is empty" do
|
|
let(:executable) { "" }
|
|
|
|
let(:exit_code) { 0 }
|
|
let(:expected_err) { "#{path} is empty" }
|
|
let(:expected) { "" }
|
|
|
|
it "runs" do
|
|
skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
|
|
|
|
subject
|
|
expect(exitstatus).to eq(exit_code)
|
|
expect(err).to eq(expected_err)
|
|
expect(out).to eq(expected)
|
|
end
|
|
end
|
|
|
|
context "the executable raises" do
|
|
let(:executable) { super() << "\nraise 'ERROR'" }
|
|
let(:exit_code) { 1 }
|
|
let(:expected_err) do
|
|
/\Abundler: failed to load command: #{Regexp.quote(path.to_s)} \(#{Regexp.quote(path.to_s)}\)\n#{Regexp.quote(path.to_s)}:10:in [`']<top \(required\)>': ERROR \(RuntimeError\)/
|
|
end
|
|
|
|
it "runs like a normally executed executable" do
|
|
skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
|
|
|
|
subject
|
|
expect(exitstatus).to eq(exit_code)
|
|
expect(err).to match(expected_err)
|
|
expect(out).to eq(expected)
|
|
end
|
|
end
|
|
|
|
context "the executable raises an error without a backtrace" do
|
|
let(:executable) { super() << "\nclass Err < Exception\ndef backtrace; end;\nend\nraise Err" }
|
|
let(:exit_code) { 1 }
|
|
let(:expected_err) { "bundler: failed to load command: #{path} (#{path})\n#{system_gem_path("bin/bundle")}: Err (Err)" }
|
|
let(:expected) { super() }
|
|
|
|
it "runs" do
|
|
skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
|
|
|
|
subject
|
|
expect(exitstatus).to eq(exit_code)
|
|
expect(err).to eq(expected_err)
|
|
expect(out).to eq(expected)
|
|
end
|
|
end
|
|
|
|
context "when the file uses the current ruby shebang" do
|
|
let(:shebang) { "#!#{Gem.ruby}" }
|
|
|
|
it "runs" do
|
|
skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
|
|
|
|
subject
|
|
expect(exitstatus).to eq(exit_code)
|
|
expect(err).to eq(expected_err)
|
|
expect(out).to eq(expected)
|
|
end
|
|
end
|
|
|
|
context "when Bundler.setup fails" do
|
|
before do
|
|
system_gems(%w[myrack-1.0.0 myrack-0.9.1], path: default_bundle_path)
|
|
|
|
gemfile <<-G
|
|
source "https://gem.repo1"
|
|
gem 'myrack', '2'
|
|
G
|
|
ENV["BUNDLER_FORCE_TTY"] = "true"
|
|
end
|
|
|
|
let(:exit_code) { Bundler::GemNotFound.new.status_code }
|
|
let(:expected) { "" }
|
|
let(:expected_err) { <<~EOS.strip }
|
|
Could not find gem 'myrack (= 2)' in locally installed gems.
|
|
|
|
The source contains the following gems matching 'myrack':
|
|
* myrack-0.9.1
|
|
* myrack-1.0.0
|
|
Run `bundle install` to install missing gems.
|
|
EOS
|
|
|
|
it "runs" do
|
|
skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
|
|
|
|
subject
|
|
expect(exitstatus).to eq(exit_code)
|
|
expect(err).to eq(expected_err)
|
|
expect(out).to eq(expected)
|
|
end
|
|
end
|
|
|
|
context "when Bundler.setup fails and Gemfile is not the default" do
|
|
before do
|
|
gemfile "CustomGemfile", <<-G
|
|
source "https://gem.repo1"
|
|
gem 'myrack', '2'
|
|
G
|
|
ENV["BUNDLER_FORCE_TTY"] = "true"
|
|
ENV["BUNDLE_GEMFILE"] = "CustomGemfile"
|
|
ENV["BUNDLER_ORIG_BUNDLE_GEMFILE"] = nil
|
|
end
|
|
|
|
let(:exit_code) { Bundler::GemNotFound.new.status_code }
|
|
let(:expected) { "" }
|
|
|
|
it "prints proper suggestion" do
|
|
skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
|
|
|
|
subject
|
|
expect(exitstatus).to eq(exit_code)
|
|
expect(err).to include("Run `bundle install --gemfile CustomGemfile` to install missing gems.")
|
|
expect(out).to eq(expected)
|
|
end
|
|
end
|
|
|
|
context "when the executable exits non-zero via at_exit" do
|
|
let(:executable) { super() + "\n\nat_exit { $! ? raise($!) : exit(1) }" }
|
|
let(:exit_code) { 1 }
|
|
|
|
it "runs" do
|
|
skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
|
|
|
|
subject
|
|
expect(exitstatus).to eq(exit_code)
|
|
expect(err).to eq(expected_err)
|
|
expect(out).to eq(expected)
|
|
end
|
|
end
|
|
|
|
context "when disable_exec_load is set" do
|
|
let(:exec) { "EXEC: exec" }
|
|
let(:process) { "PROCESS: ruby #{path} arg1 arg2" }
|
|
|
|
before do
|
|
bundle "config set disable_exec_load true"
|
|
end
|
|
|
|
it "runs" do
|
|
skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
|
|
|
|
subject
|
|
expect(exitstatus).to eq(exit_code)
|
|
expect(err).to eq(expected_err)
|
|
expect(out).to eq(expected)
|
|
end
|
|
end
|
|
|
|
context "regarding $0 and __FILE__" do
|
|
let(:executable) { super() + <<-'RUBY' }
|
|
|
|
puts "$0: #{$0.inspect}"
|
|
puts "__FILE__: #{__FILE__.inspect}"
|
|
RUBY
|
|
|
|
context "when the path is absolute" do
|
|
let(:expected) { super() + <<~EOS.chomp }
|
|
|
|
$0: #{path.to_s.inspect}
|
|
__FILE__: #{path.to_s.inspect}
|
|
EOS
|
|
|
|
it "runs" do
|
|
skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
|
|
|
|
subject
|
|
expect(exitstatus).to eq(exit_code)
|
|
expect(err).to eq(expected_err)
|
|
expect(out).to eq(expected)
|
|
end
|
|
end
|
|
|
|
context "when the path is relative" do
|
|
let(:path) { super().relative_path_from(bundled_app) }
|
|
let(:expected) { super() + <<~EOS.chomp }
|
|
|
|
$0: #{path.to_s.inspect}
|
|
__FILE__: #{path.to_s.inspect}
|
|
EOS
|
|
|
|
it "runs" do
|
|
skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
|
|
|
|
subject
|
|
expect(exitstatus).to eq(exit_code)
|
|
expect(err).to eq(expected_err)
|
|
expect(out).to eq(expected)
|
|
end
|
|
end
|
|
|
|
context "when the path is relative with a leading ./" do
|
|
let(:path) { Pathname.new("./#{super().relative_path_from(bundled_app)}") }
|
|
let(:expected) { super() + <<~EOS.chomp }
|
|
|
|
$0: #{path.to_s.inspect}
|
|
__FILE__: #{File.expand_path(path, bundled_app).inspect}
|
|
EOS
|
|
|
|
it "runs" do
|
|
skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
|
|
|
|
subject
|
|
expect(exitstatus).to eq(exit_code)
|
|
expect(err).to eq(expected_err)
|
|
expect(out).to eq(expected)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "signal handling" do
|
|
let(:test_signals) do
|
|
open3_reserved_signals = %w[CHLD CLD PIPE]
|
|
reserved_signals = %w[SEGV BUS ILL FPE VTALRM KILL STOP EXIT]
|
|
bundler_signals = %w[INT]
|
|
|
|
Signal.list.keys - (bundler_signals + reserved_signals + open3_reserved_signals)
|
|
end
|
|
|
|
context "signals being trapped by bundler" do
|
|
let(:executable) { <<~RUBY }
|
|
#{shebang}
|
|
begin
|
|
Thread.new do
|
|
puts 'Started' # For process sync
|
|
STDOUT.flush
|
|
sleep 1 # ignore quality_spec
|
|
raise "Didn't receive INT at all"
|
|
end.join
|
|
rescue Interrupt
|
|
puts "foo"
|
|
end
|
|
RUBY
|
|
|
|
it "receives the signal" do
|
|
skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
|
|
|
|
bundle("exec #{path}") do |_, o, thr|
|
|
o.gets # Consumes 'Started' and ensures that thread has started
|
|
Process.kill("INT", thr.pid)
|
|
end
|
|
|
|
expect(out).to eq("foo")
|
|
end
|
|
end
|
|
|
|
context "signals not being trapped by bunder" do
|
|
let(:executable) { <<~RUBY }
|
|
#{shebang}
|
|
|
|
signals = #{test_signals.inspect}
|
|
result = signals.map do |sig|
|
|
Signal.trap(sig, "IGNORE")
|
|
end
|
|
puts result.select { |ret| ret == "IGNORE" }.count
|
|
RUBY
|
|
|
|
it "makes sure no unexpected signals are restored to DEFAULT" do
|
|
skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
|
|
|
|
test_signals.each do |n|
|
|
Signal.trap(n, "IGNORE")
|
|
end
|
|
|
|
bundle("exec #{path}")
|
|
|
|
expect(out).to eq(test_signals.count.to_s)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "nested bundle exec" do
|
|
context "when bundle in a local path" do
|
|
before do
|
|
skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
|
|
|
|
gemfile <<-G
|
|
source "https://gem.repo1"
|
|
gem "myrack"
|
|
G
|
|
bundle "config set path vendor/bundler"
|
|
bundle :install
|
|
end
|
|
|
|
it "correctly shells out" do
|
|
file = bundled_app("file_that_bundle_execs.rb")
|
|
create_file(file, <<-RUBY)
|
|
#!#{Gem.ruby}
|
|
puts `bundle exec echo foo`
|
|
RUBY
|
|
file.chmod(0o777)
|
|
bundle "exec #{file}", env: { "PATH" => path }
|
|
expect(out).to eq("foo")
|
|
end
|
|
end
|
|
|
|
context "when Kernel.require uses extra monkeypatches" do
|
|
before do
|
|
skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
|
|
|
|
install_gemfile "source \"https://gem.repo1\""
|
|
end
|
|
|
|
it "does not undo the monkeypatches" do
|
|
karafka = bundled_app("bin/karafka")
|
|
create_file(karafka, <<~RUBY)
|
|
#!#{Gem.ruby}
|
|
|
|
module Kernel
|
|
module_function
|
|
|
|
alias_method :require_before_extra_monkeypatches, :require
|
|
|
|
def require(path)
|
|
puts "requiring \#{path} used the monkeypatch"
|
|
|
|
require_before_extra_monkeypatches(path)
|
|
end
|
|
end
|
|
|
|
Bundler.setup(:default)
|
|
|
|
require "foo"
|
|
RUBY
|
|
karafka.chmod(0o777)
|
|
|
|
foreman = bundled_app("bin/foreman")
|
|
create_file(foreman, <<~RUBY)
|
|
#!#{Gem.ruby}
|
|
|
|
puts `bundle exec bin/karafka`
|
|
RUBY
|
|
foreman.chmod(0o777)
|
|
|
|
bundle "exec #{foreman}"
|
|
expect(out).to eq("requiring foo used the monkeypatch")
|
|
end
|
|
end
|
|
|
|
context "when gemfile and path are configured", :ruby_repo do
|
|
before do
|
|
skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
|
|
|
|
build_repo2 do
|
|
build_gem "rails", "6.1.0" do |s|
|
|
s.executables = "rails"
|
|
end
|
|
end
|
|
|
|
bundle "config set path vendor/bundle"
|
|
bundle "config set gemfile gemfiles/myrack_6_1.gemfile"
|
|
|
|
gemfile(bundled_app("gemfiles/myrack_6_1.gemfile"), <<~RUBY)
|
|
source "https://gem.repo2"
|
|
|
|
gem "rails", "6.1.0"
|
|
RUBY
|
|
|
|
# A Gemfile needs to be in the root to trick bundler's root resolution
|
|
gemfile "source 'https://gem.repo1'"
|
|
|
|
bundle "install", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }
|
|
end
|
|
|
|
it "can still find gems after a nested subprocess" do
|
|
script = bundled_app("bin/myscript")
|
|
|
|
create_file(script, <<~RUBY)
|
|
#!#{Gem.ruby}
|
|
|
|
puts `bundle exec rails`
|
|
RUBY
|
|
|
|
script.chmod(0o777)
|
|
|
|
bundle "exec #{script}"
|
|
|
|
expect(err).to be_empty
|
|
expect(out).to eq("6.1.0")
|
|
end
|
|
|
|
it "can still find gems after a nested subprocess when using bundler (with a final r) executable" do
|
|
script = bundled_app("bin/myscript")
|
|
|
|
create_file(script, <<~RUBY)
|
|
#!#{Gem.ruby}
|
|
|
|
puts `bundler exec rails`
|
|
RUBY
|
|
|
|
script.chmod(0o777)
|
|
|
|
bundle "exec #{script}"
|
|
|
|
expect(err).to be_empty
|
|
expect(out).to eq("6.1.0")
|
|
end
|
|
end
|
|
|
|
context "with a system gem that shadows a default gem" do
|
|
let(:openssl_version) { "99.9.9" }
|
|
|
|
it "only leaves the default gem in the stdlib available" do
|
|
default_openssl_version = ruby "require 'openssl'; puts OpenSSL::VERSION"
|
|
|
|
skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform?
|
|
|
|
install_gemfile "source \"https://gem.repo1\"" # must happen before installing the broken system gem
|
|
|
|
build_repo4 do
|
|
build_gem "openssl", openssl_version do |s|
|
|
s.write("lib/openssl.rb", <<-RUBY)
|
|
raise "custom openssl should not be loaded, it's not in the gemfile!"
|
|
RUBY
|
|
end
|
|
end
|
|
|
|
system_gems("openssl-#{openssl_version}", gem_repo: gem_repo4)
|
|
|
|
file = bundled_app("require_openssl.rb")
|
|
create_file(file, <<-RUBY)
|
|
#!/usr/bin/env ruby
|
|
require "openssl"
|
|
puts OpenSSL::VERSION
|
|
warn Gem.loaded_specs.values.map(&:full_name)
|
|
RUBY
|
|
file.chmod(0o777)
|
|
|
|
env = { "PATH" => path }
|
|
aggregate_failures do
|
|
expect(bundle("exec #{file}", env: env)).to eq(default_openssl_version)
|
|
expect(bundle("exec bundle exec #{file}", env: env)).to eq(default_openssl_version)
|
|
expect(bundle("exec ruby #{file}", env: env)).to eq(default_openssl_version)
|
|
expect(run(file.read, artifice: nil, env: env)).to eq(default_openssl_version)
|
|
end
|
|
|
|
skip "ruby_core has openssl and rubygems in the same folder, and this test needs rubygems require but default openssl not in a directly added entry in $LOAD_PATH" if ruby_core?
|
|
# sanity check that we get the newer, custom version without bundler
|
|
sys_exec "#{Gem.ruby} #{file}", env: env, raise_on_error: false
|
|
expect(err).to include("custom openssl should not be loaded")
|
|
end
|
|
end
|
|
|
|
context "with a git gem that includes extensions", :ruby_repo do
|
|
before do
|
|
build_git "simple_git_binary", &:add_c_extension
|
|
bundle "config set --local path .bundle"
|
|
install_gemfile <<-G
|
|
source "https://gem.repo1"
|
|
gem "simple_git_binary", :git => '#{lib_path("simple_git_binary-1.0")}'
|
|
G
|
|
end
|
|
|
|
it "allows calling bundle install" do
|
|
bundle "exec bundle install"
|
|
end
|
|
|
|
it "allows calling bundle install after removing gem.build_complete" do
|
|
FileUtils.rm_r Dir[bundled_app(".bundle/**/gem.build_complete")]
|
|
bundle "exec #{Gem.ruby} -S bundle install"
|
|
end
|
|
end
|
|
end
|
|
end
|