summaryrefslogtreecommitdiff
path: root/spec/bundler/commands/exec_spec.rb
diff options
context:
space:
mode:
Diffstat (limited to 'spec/bundler/commands/exec_spec.rb')
-rw-r--r--spec/bundler/commands/exec_spec.rb1010
1 files changed, 773 insertions, 237 deletions
diff --git a/spec/bundler/commands/exec_spec.rb b/spec/bundler/commands/exec_spec.rb
index 7736adefe1..aa35685be8 100644
--- a/spec/bundler/commands/exec_spec.rb
+++ b/spec/bundler/commands/exec_spec.rb
@@ -1,65 +1,127 @@
# frozen_string_literal: true
-require "spec_helper"
RSpec.describe "bundle exec" do
- let(:system_gems_to_install) { %w(rack-1.0.0 rack-0.9.1) }
- before :each do
- system_gems(system_gems_to_install)
+ 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
- gem "rack", "0.9.1"
+ source "https://gem.repo1"
+ gem "myrack", "0.9.1"
G
- bundle "exec rackup"
+ 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
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- bundle "exec rackup"
+ bundle "exec myrackup"
expect(out).to eq("1.0.0")
end
- it "works when running from a random directory", :ruby_repo do
+ it "works when running from a random directory" do
install_gemfile <<-G
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- bundle "exec 'cd #{tmp("gems")} && rackup'"
+ bundle "exec 'cd #{tmp("gems")} && myrackup'"
- expect(out).to include("1.0.0")
+ expect(out).to eq("1.0.0")
end
it "works when exec'ing something else" do
- install_gemfile 'gem "rack"'
+ 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 'gem "rack"'
+ 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/ruby/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 'gem "rack"'
+ 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 'gem "rack"'
+ 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/ruby/rubygems/issues/3351" if Gem.win_platform?
+
require "tempfile"
command = Tempfile.new("io-test")
@@ -70,38 +132,35 @@ RSpec.describe "bundle exec" do
else
require 'tempfile'
io = Tempfile.new("io-test-fd")
- args = %W[#{Gem.ruby} -I#{lib} #{bindir.join("bundle")} exec --keep-file-descriptors #{Gem.ruby} #{command.path} \#{io.to_i}]
- args << { io.to_i => io } if RUBY_VERSION >= "2.0"
+ 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 ""
- sys_exec("#{Gem.ruby} #{command.path}")
+ install_gemfile "source \"https://gem.repo1\""
+ in_bundled_app "#{Gem.ruby} #{command.path}"
- if Bundler.current_ruby.ruby_2?
- expect(out).to eq("")
- else
- expect(out).to eq("Ruby version #{RUBY_VERSION} defaults to keeping non-standard file descriptors on Kernel#exec.")
- end
-
- expect(err).to lack_errors
+ expect(out).to be_empty
+ expect(err).to be_empty
end
it "accepts --keep-file-descriptors" do
- install_gemfile ""
+ install_gemfile "source \"https://gem.repo1\""
bundle "exec --keep-file-descriptors echo foobar"
- expect(err).to lack_errors
+ expect(err).to be_empty
end
it "can run a command named --verbose" do
- install_gemfile 'gem "rack"'
- File.open("--verbose", "w") do |f|
+ skip "https://github.com/ruby/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, "--verbose")
+ File.chmod(0o744, bundled_app("--verbose"))
with_path_as(".") do
bundle "exec -- --verbose"
end
@@ -110,135 +169,225 @@ RSpec.describe "bundle exec" do
it "handles different versions in different bundles" do
build_repo2 do
- build_gem "rack_two", "1.0.0" do |s|
- s.executables = "rackup"
+ build_gem "myrack_two", "1.0.0" do |s|
+ s.executables = "myrackup"
end
end
install_gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", "0.9.1"
+ source "https://gem.repo1"
+ gem "myrack", "0.9.1"
G
- Dir.chdir bundled_app2 do
- install_gemfile bundled_app2("Gemfile"), <<-G
- source "file://#{gem_repo2}"
- gem "rack_two", "1.0.0"
- G
- end
+ install_gemfile bundled_app2("Gemfile"), <<-G, dir: bundled_app2
+ source "https://gem.repo2"
+ gem "myrack_two", "1.0.0"
+ G
- bundle! "exec rackup"
+ bundle "exec myrackup"
expect(out).to eq("0.9.1")
- Dir.chdir bundled_app2 do
- bundle! "exec rackup"
- expect(out).to eq("1.0.0")
+ 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_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
- install_gemfile <<-G, :without => :middleware
- source "file://#{gem_repo1}"
- gem "rack" # rack 0.9.1 and 1.0 exist
+ bundle_config "without middleware"
+ install_gemfile <<-G
+ source "https://gem.repo1"
+ gem "myrack" # myrack 0.9.1 and 1.0 exist
group :middleware do
- gem "rack_middleware" # rack_middleware depends on rack 0.9.1
+ gem "myrack_middleware" # myrack_middleware depends on myrack 0.9.1
end
G
- bundle "exec rackup"
+ bundle "exec myrackup"
expect(out).to eq("0.9.1")
- expect(the_bundle).not_to include_gems "rack_middleware 1.0"
+ 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
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- rubyopt = ENV["RUBYOPT"]
- rubyopt = "-rbundler/setup #{rubyopt}"
+ bundler_setup_opt = "-r#{lib_dir}/bundler/setup"
+
+ rubyopt = opt_add(bundler_setup_opt, ENV["RUBYOPT"])
- bundle "exec 'echo $RUBYOPT'"
- expect(out).to have_rubyopts(rubyopt)
+ bundle "exec echoopt"
+ expect(out.split(" ").count(bundler_setup_opt)).to eq(1)
- bundle "exec 'echo $RUBYOPT'", :env => { "RUBYOPT" => rubyopt }
- expect(out).to have_rubyopts(rubyopt)
+ 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", :ruby_repo do
+ it "does not duplicate already exec'ed RUBYLIB" do
+ create_file("echolib", "#!/usr/bin/env ruby\nprint ENV['RUBYLIB']")
install_gemfile <<-G
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
rubylib = ENV["RUBYLIB"]
- rubylib = "#{rubylib}".split(File::PATH_SEPARATOR).unshift "#{bundler_path}"
+ rubylib = rubylib.to_s.split(File::PATH_SEPARATOR).unshift lib_dir.to_s
rubylib = rubylib.uniq.join(File::PATH_SEPARATOR)
- bundle "exec 'echo $RUBYLIB'"
+ bundle "exec echolib"
expect(out).to include(rubylib)
- bundle "exec 'echo $RUBYLIB'", :env => { "RUBYLIB" => 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
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- bundle "exec foobarbaz"
- expect(exitstatus).to eq(127) if exitstatus
- expect(out).to include("bundler: command not found: foobarbaz")
- expect(out).to include("Install missing gem executables with `bundle install`")
+ 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
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- bundle "exec touch foo"
- bundle "exec ./foo"
- expect(exitstatus).to eq(126) if exitstatus
- expect(out).to include("bundler: not executable: ./foo")
+ 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
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- bundle "exec"
- expect(exitstatus).to eq(128) if exitstatus
- expect(out).to include("bundler: exec needs a command to run")
+ 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", :ruby_repo, :rubygems => ">= 2.5.2" do
- install_gemfile! <<-G
- source "file://#{gem_repo1}"
- gem "with_license"
- G
- [true, false].each do |l|
- bundle! "config disable_exec_load #{l}"
- bundle "exec rackup"
- expect(err).to include "can't find executable rackup for gem rack. rack is not currently included in the bundle, perhaps you meant to add it to your Gemfile?"
- end
- 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)
- # Different error message on old RG versions (before activate_bin_path) because they
- # called `Kernel#gem` directly
- it "raises a helpful error when exec'ing to something outside of the bundle", :rubygems => "< 2.5.2" do
- install_gemfile! <<-G
- source "file://#{gem_repo1}"
- gem "with_license"
+ bundle_config "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 disable_exec_load #{l}"
- bundle "exec rackup"
- expect(err).to include "rack is not part of the bundle. Add it to your Gemfile."
+ bundle_config "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
@@ -250,14 +399,14 @@ RSpec.describe "bundle exec" do
describe "when #{exec} is used" do
before(:each) do
install_gemfile <<-G
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
create_file("print_args", <<-'RUBY')
#!/usr/bin/env ruby
puts "args: #{ARGV.inspect}"
RUBY
- bundled_app("print_args").chmod(0o755)
end
it "shows executable's man page when --help is after the executable" do
@@ -278,6 +427,7 @@ RSpec.describe "bundle exec" do
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
@@ -292,39 +442,39 @@ RSpec.describe "bundle exec" do
expect(out).to eq('args: ["-h"]')
end
- it "shows bundle-exec's man page when --help is between exec and the executable", :ruby_repo do
+ it "shows bundle-exec's man page when --help is between exec and the executable" do
with_fake_man do
- bundle "#{exec} --help cat"
+ bundle "#{exec} --help echo"
end
- expect(out).to include(%(["#{root}/man/bundle-exec.1"]))
+ expect(out).to include(%(["#{man_dir}/bundle-exec.1"]))
end
- it "shows bundle-exec's man page when --help is before exec", :ruby_repo do
+ 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(%(["#{root}/man/bundle-exec.1"]))
+ expect(out).to include(%(["#{man_dir}/bundle-exec.1"]))
end
- it "shows bundle-exec's man page when -h is before exec", :ruby_repo do
+ 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(%(["#{root}/man/bundle-exec.1"]))
+ expect(out).to include(%(["#{man_dir}/bundle-exec.1"]))
end
- it "shows bundle-exec's man page when --help is after exec", :ruby_repo do
+ 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(%(["#{root}/man/bundle-exec.1"]))
+ expect(out).to include(%(["#{man_dir}/bundle-exec.1"]))
end
- it "shows bundle-exec's man page when -h is after exec", :ruby_repo do
+ 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(%(["#{root}/man/bundle-exec.1"]))
+ expect(out).to include(%(["#{man_dir}/bundle-exec.1"]))
end
end
end
@@ -334,30 +484,31 @@ RSpec.describe "bundle exec" do
describe "run from a random directory" do
before(:each) do
install_gemfile <<-G
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
end
- it "works when unlocked", :ruby_repo do
- bundle "exec 'cd #{tmp("gems")} && rackup'"
+ it "works when unlocked" do
+ bundle "exec 'cd #{tmp("gems")} && myrackup'"
expect(out).to eq("1.0.0")
- expect(out).to include("1.0.0")
end
- it "works when locked", :ruby_repo do
+ it "works when locked" do
expect(the_bundle).to be_locked
- bundle "exec 'cd #{tmp("gems")} && rackup'"
- expect(out).to include("1.0.0")
+ 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|
+ 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
@@ -382,6 +533,7 @@ RSpec.describe "bundle exec" do
end
install_gemfile <<-G
+ source "https://gem.repo1"
gem "fizz_git", :git => "#{lib_path("fizz_git-1.0")}"
G
end
@@ -400,11 +552,12 @@ RSpec.describe "bundle exec" do
describe "from gems bundled via :git with no gemspec" do
before(:each) do
- build_git "fizz_no_gemspec", :gemspec => false do |s|
+ 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
@@ -424,18 +577,61 @@ RSpec.describe "bundle exec" do
it "performs an automatic bundle install" do
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack", "0.9.1"
+ source "https://gem.repo1"
+ gem "myrack", "0.9.1"
gem "foo"
G
- bundle "config auto_install 1"
- bundle "exec rackup"
+ bundle_config "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 "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 "auto_install 1"
+ bundle_config "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", :rubygems => ">= 1.7.2" do
+ it "outputs the gemspec validation errors" do
build_lib "foo"
gemspec = lib_path("foo-1.0").join("foo.gemspec").to_s
@@ -446,24 +642,28 @@ RSpec.describe "bundle exec" do
s.version = '1.0'
s.summary = 'TODO: Add summary'
s.authors = 'Me'
+ s.rubygems_version = nil
end
G
end
- install_gemfile <<-G
+ gemfile <<-G
+ source "https://gem.repo1"
gem "foo", :path => "#{lib_path("foo-1.0")}"
G
- bundle "exec irb"
+ 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('"TODO" is not a summary')
+ 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
gemfile <<-G
+ source "https://gem.repo1"
+
module Monkey
def bin_path(a,b,c)
raise Gem::GemNotFoundException.new('Fail')
@@ -471,70 +671,146 @@ RSpec.describe "bundle exec" do
end
Bundler.rubygems.extend(Monkey)
G
- bundle "install --deployment"
- bundle "exec ruby -e '`#{bindir.join("bundler")} -v`; puts $?.success?'"
+ bundle_config "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.gsub(/^ */, "").strip }
+ let(:executable) { <<~RUBY.strip }
#{shebang}
- require "rack"
+ require "myrack"
puts "EXEC: \#{caller.grep(/load/).empty? ? 'exec' : 'load'}"
puts "ARGS: \#{$0} \#{ARGV.join(' ')}"
- puts "RACK: \#{RACK}"
- process_title = `ps -o args -p \#{Process.pid}`.split("\n", 2).last.strip
+ puts "MYRACK: \#{MYRACK}"
+ if Gem.win_platform?
+ process_title = "ruby"
+ else
+ process_title = `ps -o args -p \#{Process.pid}`.split("\n", 2).last.strip
+ end
puts "PROCESS: \#{process_title}"
RUBY
before do
- path.open("w") {|f| f << executable }
- path.chmod(0o755)
+ create_file(bundled_app(path), executable)
install_gemfile <<-G
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
end
let(:exec) { "EXEC: load" }
let(:args) { "ARGS: #{path} arg1 arg2" }
- let(:rack) { "RACK: 1.0.0" }
+ let(:myrack) { "MYRACK: 1.0.0" }
let(:process) do
- title = "PROCESS: #{path}"
- title += " arg1 arg2" if RUBY_VERSION >= "2.1"
- title
+ if Gem.win_platform?
+ "PROCESS: ruby"
+ else
+ "PROCESS: #{path} arg1 arg2"
+ end
end
let(:exit_code) { 0 }
- let(:expected) { [exec, args, rack, process].join("\n") }
+ let(:expected) { [exec, args, myrack, process].join("\n") }
let(:expected_err) { "" }
- subject { bundle "exec #{path} arg1 arg2" }
+ subject { bundle "exec #{path} arg1 arg2", raise_on_error: false }
- shared_examples_for "it runs" do
- it "like a normally executed executable" do
- subject
- expect(exitstatus).to eq(exit_code) if exitstatus
- expect(err).to eq(expected_err)
- expect(out).to eq(expected)
- end
+ it "runs" do
+ subject
+ expect(exitstatus).to eq(exit_code)
+ expect(err).to eq(expected_err)
+ expect(out).to eq(expected)
end
- it_behaves_like "it runs"
-
context "the executable exits explicitly" do
let(:executable) { super() << "\nexit #{exit_code}\nputs 'POST_EXIT'\n" }
context "with exit 0" do
- it_behaves_like "it runs"
+ it "runs" do
+ 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_behaves_like "it runs"
+
+ it "runs" do
+ 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/ruby/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
@@ -542,66 +818,148 @@ RSpec.describe "bundle exec" do
let(:executable) { "" }
let(:exit_code) { 0 }
- let(:expected) { "#{path} is empty" }
- let(:expected_err) { "" }
- if LessThanProc.with(RUBY_VERSION).call("1.9")
- # Kernel#exec in ruby < 1.9 will raise Errno::ENOEXEC if the command content is empty,
- # even if the command is set as an executable.
- pending "Kernel#exec is different"
- else
- it_behaves_like "it runs"
+ let(:expected_err) { "#{path} is empty" }
+ let(:expected) { "" }
+
+ it "runs" do
+ # it's empty, so `create_file` won't add executable permission and bat scripts on Windows
+ bundled_app(path).chmod(0o755)
+ path.sub_ext(".bat").write <<~SCRIPT if Gem.win_platform?
+ @ECHO OFF
+ @"ruby.exe" "%~dpn0" %*
+ SCRIPT
+
+ 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) { super() << "\nbundler: failed to load command: #{path} (#{path})" }
let(:expected_err) do
- "RuntimeError: ERROR\n #{path}:10" +
- (Bundler.current_ruby.ruby_18? ? "" : ":in `<top (required)>'")
+ /\Abundler: failed to load command: #{Regexp.quote(path.to_s)} \(#{Regexp.quote(path.to_s)}\)\n#{Regexp.quote(path.to_s)}:[0-9]+:in [`']<top \(required\)>': ERROR \(RuntimeError\)/
+ end
+
+ it "runs like a normally executed executable" do
+ 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
+ subject
+ expect(exitstatus).to eq(exit_code)
+ expect(err).to eq(expected_err)
+ expect(out).to eq(expected)
end
- it_behaves_like "it runs"
end
- context "when the file uses the current ruby shebang", :ruby_repo do
+ context "when the file uses the current ruby shebang" do
let(:shebang) { "#!#{Gem.ruby}" }
- it_behaves_like "it runs"
+
+ it "runs" do
+ 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
- gem 'rack', '2'
+ source "https://gem.repo1"
+ gem 'myrack', '2'
G
ENV["BUNDLER_FORCE_TTY"] = "true"
end
let(:exit_code) { Bundler::GemNotFound.new.status_code }
- let(:expected) { <<-EOS.strip }
-\e[31mCould not find gem 'rack (= 2)' in any of the gem sources listed in your Gemfile.\e[0m
-\e[33mRun `bundle install` to install missing gems.\e[0m
+ 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_behaves_like "it runs"
+ it "runs" do
+ 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
+ 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_behaves_like "it runs"
+ it "runs" do
+ 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" }
+ let(:process) do
+ if Gem.win_platform?
+ "PROCESS: ruby"
+ else
+ "PROCESS: ruby #{path} arg1 arg2"
+ end
+ end
before do
- bundle "config disable_exec_load true"
+ bundle_config "disable_exec_load true"
end
- it_behaves_like "it runs"
+ it "runs" do
+ 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
@@ -611,126 +969,304 @@ RSpec.describe "bundle exec" do
puts "__FILE__: #{__FILE__.inspect}"
RUBY
- let(:expected) { super() + <<-EOS.chomp }
+ context "when the path is absolute" do
+ let(:expected) { super() + <<~EOS.chomp }
-$0: #{path.to_s.inspect}
-__FILE__: #{path.to_s.inspect}
- EOS
+ $0: #{path.to_s.inspect}
+ __FILE__: #{path.to_s.inspect}
+ EOS
- it_behaves_like "it runs"
+ it "runs" do
+ 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 }
- if LessThanProc.with(RUBY_VERSION).call("1.9")
- pending "relative paths have ./ __FILE__"
- else
- it_behaves_like "it runs"
+ $0: #{path.to_s.inspect}
+ __FILE__: #{path.to_s.inspect}
+ EOS
+
+ it "runs" do
+ 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(Pathname.pwd)}") }
-
- if LessThanProc.with(RUBY_VERSION).call("< 1.9")
- pending "relative paths with ./ have absolute __FILE__"
- else
- it_behaves_like "it runs"
+ 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
+ subject
+ expect(exitstatus).to eq(exit_code)
+ expect(err).to eq(expected_err)
+ expect(out).to eq(expected)
end
end
end
- context "signals being trapped by bundler" do
- let(:executable) { strip_whitespace <<-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
+ context "signal handling" do
+ let(:test_signals) do
+ open3_reserved_signals = %w[CHLD CLD PIPE]
+ reserved_signals = %w[SEGV BUS ILL FPE ABRT IOT 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 RuntimeError, "Didn't receive expected INT"
+ end.join
+ rescue Interrupt
+ puts "foo"
+ end
+ RUBY
- it "receives the signal" do
- skip "popen3 doesn't provide a way to get pid " unless RUBY_VERSION >= "1.9.3"
+ it "receives the signal" do
+ skip "https://github.com/ruby/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
- bundle("exec #{path}") do |_, o, thr|
- o.gets # Consumes 'Started' and ensures that thread has started
- Process.kill("INT", thr.pid)
+ expect(out).to eq("foo")
end
+ end
- expect(out).to eq("foo")
+ 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/ruby/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", :ruby_repo do
- let(:system_gems_to_install) { super() << :bundler }
-
- context "with shared gems disabled" do
+ context "nested bundle exec" do
+ context "when bundle in a local path" do
before do
+ skip "https://github.com/ruby/rubygems/issues/3351" if Gem.win_platform?
+
gemfile <<-G
- source "file://#{gem_repo1}"
- gem "rack"
+ source "https://gem.repo1"
+ gem "myrack"
G
- bundle :install, :system_bundler => true, :path => "vendor/bundler"
+ bundle_config "path vendor/bundler"
+ bundle :install
end
- it "overrides disable_shared_gems so bundler can be found" do
+ it "correctly shells out" do
file = bundled_app("file_that_bundle_execs.rb")
- create_file(file, <<-RB)
+ create_file(file, <<-RUBY)
#!#{Gem.ruby}
puts `bundle exec echo foo`
- RB
+ RUBY
file.chmod(0o777)
- bundle! "exec #{file}", :system_bundler => true
+ 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/ruby/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
+ build_repo2 do
+ build_gem "rails", "6.1.0" do |s|
+ s.executables = "rails"
+ end
+ end
+
+ bundle_config "path vendor/bundle"
+ bundle_config "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" }
- let(:expected) { ruby "gem 'openssl', '< 999999'; require 'openssl'; puts OpenSSL::VERSION", :artifice => nil }
it "only leaves the default gem in the stdlib available" do
- skip "openssl isn't a default gem" if expected.empty?
+ default_openssl_version = ruby "require 'openssl'; puts OpenSSL::VERSION"
- install_gemfile! "" # must happen before installing the broken system gem
+ skip "https://github.com/ruby/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", <<-RB)
- raise "custom openssl should not be loaded, it's not in the gemfile!"
- RB
+ s.write("lib/openssl.rb", <<-RUBY)
+ raise ArgumentError, "custom openssl should not be loaded"
+ RUBY
end
end
- system_gems(:bundler, "openssl-#{openssl_version}", :gem_repo => gem_repo4)
+ system_gems("openssl-#{openssl_version}", gem_repo: gem_repo4)
file = bundled_app("require_openssl.rb")
- create_file(file, <<-RB)
+ create_file(file, <<-RUBY)
#!/usr/bin/env ruby
require "openssl"
puts OpenSSL::VERSION
warn Gem.loaded_specs.values.map(&:full_name)
- RB
+ RUBY
file.chmod(0o777)
+ env = { "PATH" => path }
aggregate_failures do
- expect(bundle!("exec #{file}", :system_bundler => true, :artifice => nil)).to eq(expected)
- expect(bundle!("exec bundle exec #{file}", :system_bundler => true, :artifice => nil)).to eq(expected)
- expect(bundle!("exec ruby #{file}", :system_bundler => true, :artifice => nil)).to eq(expected)
- expect(run!(file.read, :no_lib => true, :artifice => nil)).to eq(expected)
+ 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}")
+ 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 "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