diff options
Diffstat (limited to 'spec/bundler/runtime')
| -rw-r--r-- | spec/bundler/runtime/env_helpers_spec.rb | 236 | ||||
| -rw-r--r-- | spec/bundler/runtime/executable_spec.rb | 141 | ||||
| -rw-r--r-- | spec/bundler/runtime/gem_tasks_spec.rb | 158 | ||||
| -rw-r--r-- | spec/bundler/runtime/inline_spec.rb | 751 | ||||
| -rw-r--r-- | spec/bundler/runtime/load_spec.rb | 113 | ||||
| -rw-r--r-- | spec/bundler/runtime/platform_spec.rb | 468 | ||||
| -rw-r--r-- | spec/bundler/runtime/require_spec.rb | 480 | ||||
| -rw-r--r-- | spec/bundler/runtime/requiring_spec.rb | 15 | ||||
| -rw-r--r-- | spec/bundler/runtime/self_management_spec.rb | 279 | ||||
| -rw-r--r-- | spec/bundler/runtime/setup_spec.rb | 1710 |
10 files changed, 4351 insertions, 0 deletions
diff --git a/spec/bundler/runtime/env_helpers_spec.rb b/spec/bundler/runtime/env_helpers_spec.rb new file mode 100644 index 0000000000..c4ebdd1fd2 --- /dev/null +++ b/spec/bundler/runtime/env_helpers_spec.rb @@ -0,0 +1,236 @@ +# frozen_string_literal: true + +RSpec.describe "env helpers" do + def bundle_exec_ruby(args, options = {}) + build_bundler_context options.dup + bundle "exec '#{Gem.ruby}' #{args}", options + end + + def build_bundler_context(options = {}) + bundle "config set path vendor/bundle", options.dup + gemfile "source 'https://gem.repo1'" + bundle "install", options + end + + def run_bundler_script(env, script) + system(env, "ruby", "-I#{lib_dir}", "-rbundler", script.to_s) + end + + describe "Bundler.original_env" do + it "should return the PATH present before bundle was activated" do + create_file("source.rb", <<-RUBY) + print Bundler.original_env["PATH"] + RUBY + path = `getconf PATH`.strip + "#{File::PATH_SEPARATOR}/foo" + with_path_as(path) do + bundle_exec_ruby(bundled_app("source.rb").to_s) + expect(stdboth).to eq(path) + end + end + + it "should return the GEM_PATH present before bundle was activated" do + create_file("source.rb", <<-RUBY) + print Bundler.original_env['GEM_PATH'] + RUBY + gem_path = ENV["GEM_PATH"] + "#{File::PATH_SEPARATOR}/foo" + with_gem_path_as(gem_path) do + bundle_exec_ruby(bundled_app("source.rb").to_s) + expect(stdboth).to eq(gem_path) + end + end + + it "works with nested bundle exec invocations", :ruby_repo do + create_file("exe.rb", <<-'RUBY') + count = ARGV.first.to_i + exit if count < 0 + STDERR.puts "#{count} #{ENV["PATH"].end_with?("#{File::PATH_SEPARATOR}/foo")}" + if count == 2 + ENV["PATH"] = "#{ENV["PATH"]}#{File::PATH_SEPARATOR}/foo" + end + exec(Gem.ruby, __FILE__, (count - 1).to_s) + RUBY + path = `getconf PATH`.strip + File::PATH_SEPARATOR + File.dirname(Gem.ruby) + with_path_as(path) do + build_bundler_context + bundle_exec_ruby("#{bundled_app("exe.rb")} 2") + end + expect(err).to eq <<-EOS.strip +2 false +1 true +0 true + EOS + end + + it "removes variables that bundler added", :ruby_repo do + original = ruby('puts ENV.to_a.map {|e| e.join("=") }.sort.join("\n")', artifice: "fail") + create_file("source.rb", <<-RUBY) + puts Bundler.original_env.to_a.map {|e| e.join("=") }.sort.join("\n") + RUBY + bundle_exec_ruby bundled_app("source.rb"), artifice: "fail" + expect(out).to eq original + end + end + + describe "Bundler.unbundled_env" do + it "should delete BUNDLE_PATH" do + create_file("source.rb", <<-RUBY) + print Bundler.unbundled_env.has_key?('BUNDLE_PATH') + RUBY + ENV["BUNDLE_PATH"] = "./foo" + bundle_exec_ruby bundled_app("source.rb") + expect(stdboth).to include "false" + end + + it "should remove absolute path to 'bundler/setup' from RUBYOPT even if it was present in original env" do + create_file("source.rb", <<-RUBY) + print Bundler.unbundled_env['RUBYOPT'] + RUBY + setup_require = "-r#{lib_dir}/bundler/setup" + ENV["BUNDLER_ORIG_RUBYOPT"] = "-W2 #{setup_require} #{ENV["RUBYOPT"]}" + bundle_exec_ruby bundled_app("source.rb") + expect(stdboth).not_to include(setup_require) + end + + it "should remove relative path to 'bundler/setup' from RUBYOPT even if it was present in original env" do + create_file("source.rb", <<-RUBY) + print Bundler.unbundled_env['RUBYOPT'] + RUBY + ENV["BUNDLER_ORIG_RUBYOPT"] = "-W2 -rbundler/setup #{ENV["RUBYOPT"]}" + bundle_exec_ruby bundled_app("source.rb") + expect(stdboth).not_to include("-rbundler/setup") + end + + it "should delete BUNDLER_SETUP even if it was present in original env" do + create_file("source.rb", <<-RUBY) + print Bundler.unbundled_env.has_key?('BUNDLER_SETUP') + RUBY + ENV["BUNDLER_ORIG_BUNDLER_SETUP"] = system_gem_path("gems/bundler-#{Bundler::VERSION}/lib/bundler/setup").to_s + bundle_exec_ruby bundled_app("source.rb") + expect(stdboth).to include "false" + end + + it "should restore RUBYLIB", :ruby_repo do + create_file("source.rb", <<-RUBY) + print Bundler.unbundled_env['RUBYLIB'] + RUBY + ENV["RUBYLIB"] = lib_dir.to_s + File::PATH_SEPARATOR + "/foo" + ENV["BUNDLER_ORIG_RUBYLIB"] = lib_dir.to_s + File::PATH_SEPARATOR + "/foo-original" + bundle_exec_ruby bundled_app("source.rb") + expect(stdboth).to include("/foo-original") + end + + it "should restore the original MANPATH" do + create_file("source.rb", <<-RUBY) + print Bundler.unbundled_env['MANPATH'] + RUBY + ENV["MANPATH"] = "/foo" + ENV["BUNDLER_ORIG_MANPATH"] = "/foo-original" + bundle_exec_ruby bundled_app("source.rb") + expect(stdboth).to include("/foo-original") + end + end + + describe "Bundler.with_original_env" do + it "should set ENV to original_env in the block" do + expected = Bundler.original_env + actual = Bundler.with_original_env { ENV.to_hash } + expect(actual).to eq(expected) + end + + it "should restore the environment after execution" do + Bundler.with_original_env do + ENV["FOO"] = "hello" + end + + expect(ENV).not_to have_key("FOO") + end + end + + describe "Bundler.with_unbundled_env" do + it "should set ENV to unbundled_env in the block" do + expected = Bundler.unbundled_env + actual = Bundler.with_unbundled_env { ENV.to_hash } + expect(actual).to eq(expected) + end + + it "should restore the environment after execution" do + Bundler.with_unbundled_env do + ENV["FOO"] = "hello" + end + + expect(ENV).not_to have_key("FOO") + end + end + + describe "Bundler.original_system" do + before do + create_file("source.rb", <<-'RUBY') + Bundler.original_system("ruby", "-e", "exit(42) if ENV['BUNDLE_FOO'] == 'bar'") + + exit $?.exitstatus + RUBY + end + + it "runs system inside with_original_env" do + run_bundler_script({ "BUNDLE_FOO" => "bar" }, bundled_app("source.rb")) + expect($?.exitstatus).to eq(42) + end + end + + describe "Bundler.unbundled_system" do + before do + create_file("source.rb", <<-'RUBY') + Bundler.unbundled_system("ruby", "-e", "exit(42) unless ENV['BUNDLE_FOO'] == 'bar'") + + exit $?.exitstatus + RUBY + end + + it "runs system inside with_unbundled_env" do + run_bundler_script({ "BUNDLE_FOO" => "bar" }, bundled_app("source.rb")) + expect($?.exitstatus).to eq(42) + end + end + + describe "Bundler.original_exec" do + before do + create_file("source.rb", <<-'RUBY') + Process.fork do + exit Bundler.original_exec(%(test "\$BUNDLE_FOO" = "bar")) + end + + _, status = Process.wait2 + + exit(status.exitstatus) + RUBY + end + + it "runs exec inside with_original_env" do + skip "Fork not implemented" if Gem.win_platform? + + run_bundler_script({ "BUNDLE_FOO" => "bar" }, bundled_app("source.rb")) + expect($?.exitstatus).to eq(0) + end + end + + describe "Bundler.unbundled_exec" do + before do + create_file("source.rb", <<-'RUBY') + Process.fork do + exit Bundler.unbundled_exec(%(test "\$BUNDLE_FOO" = "bar")) + end + + _, status = Process.wait2 + + exit(status.exitstatus) + RUBY + end + + it "runs exec inside with_unbundled_env" do + skip "Fork not implemented" if Gem.win_platform? + + run_bundler_script({ "BUNDLE_FOO" => "bar" }, bundled_app("source.rb")) + expect($?.exitstatus).to eq(1) + end + end +end diff --git a/spec/bundler/runtime/executable_spec.rb b/spec/bundler/runtime/executable_spec.rb new file mode 100644 index 0000000000..89cee21b00 --- /dev/null +++ b/spec/bundler/runtime/executable_spec.rb @@ -0,0 +1,141 @@ +# frozen_string_literal: true + +RSpec.describe "Running bin/* commands" do + before :each do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + end + + it "runs the bundled command when in the bundle" do + bundle "binstubs myrack" + + build_gem "myrack", "2.0", to_system: true do |s| + s.executables = "myrackup" + end + + gembin "myrackup" + expect(out).to eq("1.0.0") + end + + it "allows the location of the gem stubs to be configured" do + bundle_config "bin gbin" + bundle "binstubs myrack" + + expect(bundled_app("bin")).not_to exist + expect(bundled_app("gbin/myrackup")).to exist + + gembin bundled_app("gbin/myrackup") + expect(out).to eq("1.0.0") + end + + it "allows absolute paths as a specification of where to install bin stubs" do + bundle_config "bin #{tmp("bin")}" + bundle "binstubs myrack" + + gembin tmp("bin/myrackup") + expect(out).to eq("1.0.0") + end + + it "uses the default ruby install name when shebang is not specified" do + bundle "binstubs myrack" + expect(File.readlines(bundled_app("bin/myrackup")).first).to eq("#!/usr/bin/env #{RbConfig::CONFIG["ruby_install_name"]}\n") + end + + it "allows the name of the shebang executable to be specified" do + bundle "binstubs myrack", shebang: "ruby-foo" + expect(File.readlines(bundled_app("bin/myrackup")).first).to eq("#!/usr/bin/env ruby-foo\n") + end + + it "runs the bundled command when out of the bundle" do + bundle "binstubs myrack" + + build_gem "myrack", "2.0", to_system: true do |s| + s.executables = "myrackup" + end + + gembin "myrackup", dir: tmp + expect(out).to eq("1.0.0") + end + + it "works with gems in path" do + build_lib "myrack", path: lib_path("myrack") do |s| + s.executables = "myrackup" + end + + gemfile <<-G + source "https://gem.repo1" + gem "myrack", :path => "#{lib_path("myrack")}" + G + + bundle "binstubs myrack" + + build_gem "myrack", "2.0", to_system: true do |s| + s.executables = "myrackup" + end + + gembin "myrackup" + expect(out).to eq("1.0") + end + + it "does not create a bundle binstub" do + gemfile <<-G + source "https://gem.repo1" + gem "bundler" + G + + bundle "binstubs bundler" + + expect(bundled_app("bin/bundle")).not_to exist + + expect(err).to include("Bundler itself does not use binstubs because its version is selected by RubyGems") + end + + it "does not generate bin stubs if the option was not specified" do + bundle "install" + + expect(bundled_app("bin/myrackup")).not_to exist + end + + it "rewrites bins on binstubs with --force option" do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + create_file("bin/myrackup", "OMG") + + bundle "binstubs myrack", { force: true } + + expect(bundled_app("bin/myrackup").read.strip).to_not eq("OMG") + end + + it "use BUNDLE_GEMFILE gemfile for binstub" do + # context with bin/bundler w/ default Gemfile + bundle "binstubs bundler" + + # generate other Gemfile with executable gem + build_repo2 do + build_gem("bindir") {|s| s.executables = "foo" } + end + + gemfile("OtherGemfile", <<-G) + source "https://gem.repo2" + gem 'bindir' + G + + # generate binstub for executable from non default Gemfile (other then bin/bundler version) + ENV["BUNDLE_GEMFILE"] = "OtherGemfile" + bundle "install" + bundle "binstubs bindir" + + # remove user settings + ENV["BUNDLE_GEMFILE"] = nil + + # run binstub for non default Gemfile + gembin "foo" + + expect(out).to eq("1.0") + end +end diff --git a/spec/bundler/runtime/gem_tasks_spec.rb b/spec/bundler/runtime/gem_tasks_spec.rb new file mode 100644 index 0000000000..b855142e60 --- /dev/null +++ b/spec/bundler/runtime/gem_tasks_spec.rb @@ -0,0 +1,158 @@ +# frozen_string_literal: true + +RSpec.describe "require 'bundler/gem_tasks'" do + let(:define_local_gem_using_gem_tasks) do + bundled_app("foo.gemspec").open("w") do |f| + f.write <<-GEMSPEC + Gem::Specification.new do |s| + s.name = "foo" + s.version = "1.0" + s.summary = "dummy" + s.author = "Perry Mason" + end + GEMSPEC + end + + bundled_app("Rakefile").open("w") do |f| + f.write <<-RAKEFILE + require "bundler/gem_tasks" + RAKEFILE + end + + install_gemfile <<-G + source "https://gem.repo1" + + gem "rake" + G + end + + let(:define_local_gem_with_extensions_using_gem_tasks_and_gemspec_dsl) do + bundled_app("foo.gemspec").open("w") do |f| + f.write <<-GEMSPEC + Gem::Specification.new do |s| + s.name = "foo" + s.version = "1.0" + s.summary = "dummy" + s.author = "Perry Mason" + s.extensions = "ext/extconf.rb" + end + GEMSPEC + end + + bundled_app("Rakefile").open("w") do |f| + f.write <<-RAKEFILE + require "bundler/gem_tasks" + RAKEFILE + end + + Dir.mkdir bundled_app("ext") + + bundled_app("ext/extconf.rb").open("w") do |f| + f.write <<-EXTCONF + require "mkmf" + File.write("Makefile", dummy_makefile($srcdir).join) + EXTCONF + end + + install_gemfile <<-G + source "https://gem.repo1" + + gemspec + + gem "rake" + G + end + + it "includes the relevant tasks" do + define_local_gem_using_gem_tasks + + in_bundled_app "rake -T" + + expect(err).to be_empty + expected_tasks = [ + "rake build", + "rake clean", + "rake clobber", + "rake install", + "rake release[remote]", + ] + tasks = out.lines.to_a.map {|s| s.split("#").first.strip } + expect(tasks & expected_tasks).to eq(expected_tasks) + end + + it "defines a working `rake install` task", :ruby_repo do + define_local_gem_using_gem_tasks + + in_bundled_app "rake install" + + expect(err).to be_empty + + bundle "exec rake install" + + expect(err).to be_empty + end + + it "defines a working `rake install` task for local gems with extensions", :ruby_repo do + define_local_gem_with_extensions_using_gem_tasks_and_gemspec_dsl + + bundle "exec rake install" + + expect(err).to be_empty + end + + context "rake build when path has spaces", :ruby_repo do + before do + define_local_gem_using_gem_tasks + + spaced_bundled_app = tmp("bundled app") + FileUtils.cp_r bundled_app, spaced_bundled_app + bundle "exec rake build", dir: spaced_bundled_app + end + + it "still runs successfully" do + expect(err).to be_empty + end + end + + context "rake build when path has brackets", :ruby_repo do + before do + define_local_gem_using_gem_tasks + + bracketed_bundled_app = tmp("bundled[app") + FileUtils.cp_r bundled_app, bracketed_bundled_app + bundle "exec rake build", dir: bracketed_bundled_app + end + + it "still runs successfully" do + expect(err).to be_empty + end + end + + context "bundle path configured locally" do + before do + define_local_gem_using_gem_tasks + + bundle_config "path vendor/bundle" + end + + it "works", :ruby_repo do + install_gemfile <<-G + source "https://gem.repo1" + + gem "rake" + G + + bundle "exec rake -T" + + expect(err).to be_empty + end + end + + it "adds 'pkg' to rake/clean's CLOBBER" do + define_local_gem_using_gem_tasks + + in_bundled_app %(rake -e 'load "Rakefile"; puts CLOBBER.inspect') + + expect(out).to eq '["pkg"]' + end +end diff --git a/spec/bundler/runtime/inline_spec.rb b/spec/bundler/runtime/inline_spec.rb new file mode 100644 index 0000000000..c6f9bbdbd7 --- /dev/null +++ b/spec/bundler/runtime/inline_spec.rb @@ -0,0 +1,751 @@ +# frozen_string_literal: true + +RSpec.describe "bundler/inline#gemfile" do + def script(code, options = {}) + options[:artifice] ||= "compact_index" + options[:env] ||= { "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s } + ruby("require 'bundler/inline'\n\n" + code, options) + end + + before :each do + build_lib "one", "1.0.0" do |s| + s.write "lib/baz.rb", "puts 'baz'" + s.write "lib/qux.rb", "puts 'qux'" + end + + build_lib "two", "1.0.0" do |s| + s.write "lib/two.rb", "puts 'two'" + s.add_dependency "three", "= 1.0.0" + end + + build_lib "three", "1.0.0" do |s| + s.write "lib/three.rb", "puts 'three'" + s.add_dependency "seven", "= 1.0.0" + end + + build_lib "four", "1.0.0" do |s| + s.write "lib/four.rb", "puts 'four'" + end + + build_lib "five", "1.0.0", no_default: true do |s| + s.write "lib/mofive.rb", "puts 'five'" + end + + build_lib "six", "1.0.0" do |s| + s.write "lib/six.rb", "puts 'six'" + end + + build_lib "seven", "1.0.0" do |s| + s.write "lib/seven.rb", "puts 'seven'" + end + + build_lib "eight", "1.0.0" do |s| + s.write "lib/eight.rb", "puts 'eight'" + end + end + + it "requires the gems" do + script <<-RUBY + gemfile do + source "https://gem.repo1" + path "#{lib_path}" do + gem "two" + end + end + RUBY + + expect(out).to eq("two") + + script <<-RUBY, raise_on_error: false + gemfile do + source "https://gem.repo1" + path "#{lib_path}" do + gem "eleven" + end + end + + puts "success" + RUBY + + expect(err).to include "Could not find gem 'eleven'" + expect(out).not_to include "success" + + script <<-RUBY + gemfile(true) do + source "https://gem.repo1" + gem "myrack" + end + RUBY + + expect(out).to include("Myrack's post install message") + + script <<-RUBY, artifice: "endpoint" + gemfile(true) do + source "https://notaserver.test" + gem "activesupport", :require => true + end + RUBY + + expect(out).to include("Installing activesupport") + err_lines = err.split("\n") + err_lines.reject! {|line| line =~ /\.rb:\d+: warning: / } + expect(err_lines).to be_empty + end + + it "lets me use my own ui object" do + script <<-RUBY, artifice: "endpoint" + require 'bundler' + class MyBundlerUI < Bundler::UI::Shell + def confirm(msg, newline = nil) + puts "CONFIRMED!" + end + end + my_ui = MyBundlerUI.new + my_ui.level = "confirm" + gemfile(true, :ui => my_ui) do + source "https://notaserver.test" + gem "activesupport", :require => true + end + RUBY + + expect(out).to eq("CONFIRMED!\nCONFIRMED!") + end + + it "has an option for quiet installation" do + script <<-RUBY, artifice: "endpoint" + require 'bundler/inline' + + gemfile(true, :quiet => true) do + source "https://notaserver.test" + gem "activesupport", :require => true + end + RUBY + + expect(out).to be_empty + end + + it "raises an exception if passed unknown arguments" do + script <<-RUBY, raise_on_error: false + gemfile(true, :arglebargle => true) do + path "#{lib_path}" + gem "two" + end + + puts "success" + RUBY + expect(err).to include "Unknown options: arglebargle" + expect(out).not_to include "success" + end + + it "does not mutate the option argument" do + script <<-RUBY + require 'bundler' + options = { :ui => Bundler::UI::Shell.new } + gemfile(false, options) do + source "https://gem.repo1" + path "#{lib_path}" do + gem "two" + end + end + puts "OKAY" if options.key?(:ui) + RUBY + + expect(out).to match("OKAY") + end + + it "installs quietly if necessary when the install option is not set" do + script <<-RUBY + gemfile do + source "https://gem.repo1" + gem "myrack" + end + + puts MYRACK + RUBY + + expect(out).to eq("1.0.0") + expect(err).to be_empty + end + + it "installs subdependencies quietly if necessary when the install option is not set" do + build_repo4 do + build_gem "myrack" do |s| + s.add_dependency "myrackdep" + end + + build_gem "myrackdep", "1.0.0" + end + + script <<-RUBY, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + gemfile do + source "https://gem.repo4" + gem "myrack" + end + + require "myrackdep" + puts MYRACKDEP + RUBY + + expect(out).to eq("1.0.0") + expect(err).to be_empty + end + + it "installs subdependencies quietly if necessary when the install option is not set, and multiple sources used" do + build_repo4 do + build_gem "myrack" do |s| + s.add_dependency "myrackdep" + end + + build_gem "myrackdep", "1.0.0" + end + + script <<-RUBY, artifice: "compact_index_extra_api" + gemfile do + source "https://test.repo" + source "https://test.repo/extra" do + gem "myrack" + end + end + + require "myrackdep" + puts MYRACKDEP + RUBY + + expect(out).to eq("1.0.0") + expect(err).to be_empty + end + + it "installs quietly from git if necessary when the install option is not set" do + build_git "foo", "1.0.0" + baz_ref = build_git("baz", "2.0.0").ref_for("HEAD") + script <<-RUBY + gemfile do + source "https://gem.repo1" + gem "foo", :git => #{lib_path("foo-1.0.0").to_s.dump} + gem "baz", :git => #{lib_path("baz-2.0.0").to_s.dump}, :ref => #{baz_ref.dump} + end + + puts FOO + puts BAZ + RUBY + + expect(out).to eq("1.0.0\n2.0.0") + expect(err).to be_empty + end + + it "allows calling gemfile twice" do + script <<-RUBY + gemfile do + path "#{lib_path}" do + source "https://gem.repo1" + gem "two" + end + end + + gemfile do + path "#{lib_path}" do + source "https://gem.repo1" + gem "four" + end + end + RUBY + + expect(out).to eq("two\nfour") + expect(err).to be_empty + end + + it "doesn't reinstall already installed gems" do + system_gems "myrack-1.0.0" + + script <<-RUBY + require 'bundler' + ui = Bundler::UI::Shell.new + ui.level = "confirm" + + gemfile(true, ui: ui) do + source "https://gem.repo1" + gem "activesupport" + gem "myrack" + end + RUBY + + expect(out).to include("Installing activesupport") + expect(out).not_to include("Installing myrack") + expect(err).to be_empty + end + + it "installs gems in later gemfile calls" do + system_gems "myrack-1.0.0" + + script <<-RUBY + require 'bundler' + ui = Bundler::UI::Shell.new + ui.level = "confirm" + gemfile(true, ui: ui) do + source "https://gem.repo1" + gem "myrack" + end + + gemfile(true, ui: ui) do + source "https://gem.repo1" + gem "activesupport" + end + RUBY + + expect(out).to include("Installing activesupport") + expect(out).not_to include("Installing myrack") + expect(err).to be_empty + end + + it "doesn't reinstall already installed gems in later gemfile calls" do + system_gems "myrack-1.0.0" + + script <<-RUBY + require 'bundler' + ui = Bundler::UI::Shell.new + ui.level = "confirm" + gemfile(true, ui: ui) do + source "https://gem.repo1" + gem "activesupport" + end + + gemfile(true, ui: ui) do + source "https://gem.repo1" + gem "myrack" + end + RUBY + + expect(out).to include("Installing activesupport") + expect(out).not_to include("Installing myrack") + expect(err).to be_empty + end + + it "installs gems with native extensions in later gemfile calls" do + system_gems "myrack-1.0.0" + + build_git "foo" do |s| + s.add_dependency "rake" + s.extensions << "Rakefile" + s.write "Rakefile", <<-RUBY + task :default do + path = File.expand_path("lib", __dir__) + FileUtils.mkdir_p(path) + File.open("\#{path}/foo.rb", "w") do |f| + f.puts "FOO = 'YES'" + end + end + RUBY + end + + script <<-RUBY + require 'bundler' + ui = Bundler::UI::Shell.new + ui.level = "confirm" + gemfile(true, ui: ui) do + source "https://gem.repo1" + gem "myrack" + end + + gemfile(true, ui: ui) do + source "https://gem.repo1" + gem "foo", :git => "#{lib_path("foo-1.0")}" + end + + require 'foo' + puts FOO + puts $:.grep(/ext/) + RUBY + + expect(out).to include("YES") + expect(out).to include(Pathname.glob(default_bundle_path("bundler/gems/extensions/**/foo-1.0-*")).first.to_s) + expect(err).to be_empty + end + + it "installs inline gems when a Gemfile.lock is present" do + gemfile <<-G + source "https://notaserver.test" + gem "rake" + G + + lockfile <<-G + GEM + remote: https://rubygems.org/ + specs: + rake (11.3.0) + + PLATFORMS + ruby + + DEPENDENCIES + rake + + BUNDLED WITH + #{Bundler::VERSION} + G + + script <<-RUBY + gemfile do + source "https://gem.repo1" + gem "myrack" + end + + puts MYRACK + RUBY + + expect(err).to be_empty + end + + it "does not leak Gemfile.lock versions to the installation output" do + gemfile <<-G + source "https://notaserver.test" + gem "rake" + G + + lockfile <<-G + GEM + remote: https://rubygems.org/ + specs: + rake (11.3.0) + + PLATFORMS + ruby + + DEPENDENCIES + rake + + BUNDLED WITH + #{Bundler::VERSION} + G + + script <<-RUBY + gemfile(true) do + source "https://gem.repo1" + gem "rake", "#{rake_version}" + end + RUBY + + expect(out).to include("Installing rake #{rake_version}") + expect(out).not_to include("was 11.3.0") + expect(err).to be_empty + end + + it "installs inline gems when frozen is set" do + script <<-RUBY, env: { "BUNDLE_FROZEN" => "true", "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s } + gemfile do + source "https://gem.repo1" + gem "myrack" + end + + puts MYRACK + RUBY + + expect(last_command.stderr).to be_empty + end + + it "installs inline gems when deployment is set" do + script <<-RUBY, env: { "BUNDLE_DEPLOYMENT" => "true", "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s } + gemfile do + source "https://gem.repo1" + gem "myrack" + end + + puts MYRACK + RUBY + + expect(last_command.stderr).to be_empty + end + + it "installs inline gems when BUNDLE_GEMFILE is set to an empty string" do + ENV["BUNDLE_GEMFILE"] = "" + + script <<-RUBY + gemfile do + source "https://gem.repo1" + gem "myrack" + end + + puts MYRACK + RUBY + + expect(err).to be_empty + end + + it "installs inline gems when BUNDLE_BIN is set" do + ENV["BUNDLE_BIN"] = "/usr/local/bundle/bin" + + script <<-RUBY + gemfile do + source "https://gem.repo1" + gem "myrack" # has the myrackup executable + end + + puts MYRACK + RUBY + expect(last_command).to be_success + expect(out).to eq "1.0.0" + end + + context "when BUNDLE_PATH is set" do + it "installs inline gems to the system path regardless" do + script <<-RUBY, env: { "BUNDLE_PATH" => "./vendor/inline", "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s } + gemfile(true) do + source "https://gem.repo1" + gem "myrack" + end + RUBY + expect(last_command).to be_success + expect(system_gem_path("gems/myrack-1.0.0")).to exist + end + end + + it "skips platform warnings" do + bundle_config "force_ruby_platform true" + + script <<-RUBY + gemfile(true) do + source "https://gem.repo1" + gem "myrack", platform: :jruby + end + RUBY + + expect(err).to be_empty + end + + it "still installs if the application has `bundle package` no_install config set" do + bundle_config "no_install true" + + script <<-RUBY + gemfile do + source "https://gem.repo1" + gem "myrack" + end + RUBY + + expect(last_command).to be_success + expect(system_gem_path("gems/myrack-1.0.0")).to exist + end + + it "preserves previous BUNDLE_GEMFILE value" do + ENV["BUNDLE_GEMFILE"] = "" + script <<-RUBY + gemfile do + source "https://gem.repo1" + gem "myrack" + end + + puts "BUNDLE_GEMFILE is empty" if ENV["BUNDLE_GEMFILE"].empty? + system("#{Gem.ruby} -w -e '42'") # this should see original value of BUNDLE_GEMFILE + exit $?.exitstatus + RUBY + + expect(last_command).to be_success + expect(out).to include("BUNDLE_GEMFILE is empty") + end + + it "resets BUNDLE_GEMFILE to the empty string if it wasn't set previously" do + ENV["BUNDLE_GEMFILE"] = nil + script <<-RUBY + gemfile do + source "https://gem.repo1" + gem "myrack" + end + + puts "BUNDLE_GEMFILE is empty" if ENV["BUNDLE_GEMFILE"].empty? + system("#{Gem.ruby} -w -e '42'") # this should see original value of BUNDLE_GEMFILE + exit $?.exitstatus + RUBY + + expect(last_command).to be_success + expect(out).to include("BUNDLE_GEMFILE is empty") + end + + it "does not error out if library requires optional dependencies" do + Dir.mkdir tmp("path_without_gemfile") + + foo_code = <<~RUBY + begin + gem "bar" + rescue LoadError + end + + puts "WIN" + RUBY + + build_lib "foo", "1.0.0" do |s| + s.write "lib/foo.rb", foo_code + end + + script <<-RUBY, dir: tmp("path_without_gemfile"), env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + gemfile do + source "https://gem.repo2" + path "#{lib_path}" do + gem "foo", require: false + end + end + + require "foo" + RUBY + + expect(out).to eq("WIN") + expect(err).to be_empty + end + + it "does not load default timeout", rubygems: ">= 3.5.0" do + default_timeout_version = ruby "gem 'timeout', '< 999999'; require 'timeout'; puts Timeout::VERSION", raise_on_error: false + skip "timeout isn't a default gem" if default_timeout_version.empty? + + build_repo4 do + build_gem "timeout", "999" + end + + script <<-RUBY, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + require "bundler/inline" + + gemfile(true) do + source "https://gem.repo4" + + gem "timeout" + end + RUBY + + expect(out).to include("Installing timeout 999") + end + + it "does not upcase ENV" do + script <<-RUBY + require 'bundler/inline' + + ENV['Test_Variable'] = 'value string' + puts("before: \#{ENV.each_key.select { |key| key.match?(/test_variable/i) }}") + + gemfile do + source "https://gem.repo1" + end + + puts("after: \#{ENV.each_key.select { |key| key.match?(/test_variable/i) }}") + RUBY + + expect(out).to include("before: [\"Test_Variable\"]") + expect(out).to include("after: [\"Test_Variable\"]") + end + + it "does not create a lockfile" do + script <<-RUBY + require 'bundler/inline' + + gemfile do + source "https://gem.repo1" + end + + puts Dir.glob("Gemfile.lock") + RUBY + + expect(out).to be_empty + end + + it "does not reset ENV" do + script <<-RUBY + require 'bundler/inline' + + gemfile do + source "https://gem.repo1" + + ENV['FOO'] = 'bar' + end + + puts ENV['FOO'] + RUBY + + expect(out).to eq("bar") + end + + it "does not load specified version of psych and stringio", :ruby_repo do + build_repo4 do + build_gem "psych", "999" + build_gem "stringio", "999" + end + + script <<-RUBY, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + require "bundler/inline" + + gemfile(true) do + source "https://gem.repo4" + + gem "psych" + gem "stringio" + end + RUBY + + expect(out).to include("Installing psych 999") + expect(out).to include("Installing stringio 999") + if Gem.respond_to?(:use_psych?) && Gem.use_psych? + expect(out).to include("The psych gem was resolved to 999") + expect(out).to include("The stringio gem was resolved to 999") + end + end + + it "installs a conflicting default gem and non-default gems together" do + build_repo4 do + build_gem "securerandom", "999" + build_gem "myrack", "1.0.0" + end + + script <<-RUBY, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + gemfile(true) do + source "https://gem.repo4" + gem "securerandom" + gem "myrack" + end + + puts MYRACK + RUBY + + expect(out).to include("Installing securerandom 999") + expect(out).to include("Installing myrack 1.0.0") + expect(out).to include("1.0.0") + expect(err).to be_empty + end + + it "installs a conflicting default gem alongside git sources" do + build_repo4 do + build_gem "securerandom", "999" + end + + build_git "foo", "1.0.0" + + script <<-RUBY, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + gemfile(true) do + source "https://gem.repo4" + gem "securerandom" + gem "foo", :git => #{lib_path("foo-1.0.0").to_s.dump} + end + + puts FOO + RUBY + + expect(out).to include("Installing securerandom 999") + expect(out).to include("1.0.0") + expect(err).to be_empty + end + + it "leaves a lockfile in the same directory as the inline script alone" do + install_gemfile <<~G + source "https://gem.repo1" + gem "foo" + G + + original_lockfile = lockfile + + script <<-RUBY, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s } + require "bundler/inline" + + gemfile(true) do + source "https://gem.repo1" + + gem "myrack" + end + RUBY + + expect(lockfile).to eq(original_lockfile) + end +end diff --git a/spec/bundler/runtime/load_spec.rb b/spec/bundler/runtime/load_spec.rb new file mode 100644 index 0000000000..472cde87c5 --- /dev/null +++ b/spec/bundler/runtime/load_spec.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +RSpec.describe "Bundler.load" do + describe "with a gemfile" do + before(:each) do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + allow(Bundler::SharedHelpers).to receive(:pwd).and_return(bundled_app) + end + + it "provides a list of the env dependencies" do + expect(Bundler.load.dependencies).to have_dep("myrack", ">= 0") + end + + it "provides a list of the resolved gems" do + expect(Bundler.load.gems).to have_gem("myrack-1.0.0", "bundler-#{Bundler::VERSION}") + end + + it "ignores blank BUNDLE_GEMFILEs" do + expect do + ENV["BUNDLE_GEMFILE"] = "" + Bundler.load + end.not_to raise_error + end + end + + describe "with a gems.rb file" do + before(:each) do + gemfile "gems.rb", <<-G + source "https://gem.repo1" + gem "myrack" + G + bundle :install + allow(Bundler::SharedHelpers).to receive(:pwd).and_return(bundled_app) + end + + it "provides a list of the env dependencies" do + expect(Bundler.load.dependencies).to have_dep("myrack", ">= 0") + end + + it "provides a list of the resolved gems" do + expect(Bundler.load.gems).to have_gem("myrack-1.0.0", "bundler-#{Bundler::VERSION}") + end + end + + describe "without a gemfile" do + it "raises an exception if the default gemfile is not found" do + expect do + Bundler.load + end.to raise_error(Bundler::GemfileNotFound, /could not locate gemfile/i) + end + + it "raises an exception if a specified gemfile is not found" do + expect do + ENV["BUNDLE_GEMFILE"] = "omg.rb" + Bundler.load + end.to raise_error(Bundler::GemfileNotFound, /omg\.rb/) + end + + it "does not find a Gemfile above the testing directory" do + bundler_gemfile = Pathname.new(__dir__).join("../../Gemfile") + unless File.exist?(bundler_gemfile) + FileUtils.touch(bundler_gemfile) + @remove_bundler_gemfile = true + end + begin + expect { Bundler.load }.to raise_error(Bundler::GemfileNotFound) + ensure + FileUtils.rm_rf bundler_gemfile if @remove_bundler_gemfile + end + end + end + + describe "when called twice" do + it "doesn't try to load the runtime twice" do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + gem "activesupport", :group => :test + G + + ruby <<-RUBY + require "bundler" + Bundler.setup :default + Bundler.require :default + puts MYRACK + begin + require "activesupport" + rescue LoadError + puts "no activesupport" + end + RUBY + + expect(out.split("\n")).to eq(["1.0.0", "no activesupport"]) + end + end + + describe "not hurting brittle rubygems" do + it "does not inject #source into the generated YAML of the gem specs" do + install_gemfile <<-G + source "https://gem.repo1" + gem "activerecord" + G + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + Bundler.load.specs.each do |spec| + expect(spec.to_yaml).not_to match(/^\s+source:/) + expect(spec.to_yaml).not_to match(/^\s+groups:/) + end + end + end +end diff --git a/spec/bundler/runtime/platform_spec.rb b/spec/bundler/runtime/platform_spec.rb new file mode 100644 index 0000000000..6d96758956 --- /dev/null +++ b/spec/bundler/runtime/platform_spec.rb @@ -0,0 +1,468 @@ +# frozen_string_literal: true + +RSpec.describe "Bundler.setup with multi platform stuff" do + it "raises a friendly error when gems are missing locally" do + gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + lockfile <<-G + GEM + remote: https://gem.repo1/ + specs: + myrack (1.0) + + PLATFORMS + #{local_tag} + + DEPENDENCIES + myrack + G + + ruby <<-R + begin + require 'bundler' + Bundler.ui.silence { Bundler.setup } + rescue Bundler::GemNotFound => e + puts "WIN" + end + R + + expect(out).to eq("WIN") + end + + it "will resolve correctly on the current platform when the lockfile was targeted for a different one" do + lockfile <<-G + GEM + remote: https://gem.repo1/ + specs: + nokogiri (1.4.2-java) + weakling (= 0.0.3) + weakling (0.0.3) + + PLATFORMS + java + + DEPENDENCIES + nokogiri + G + + simulate_platform "x86-darwin-10" do + install_gemfile <<-G + source "https://gem.repo1" + gem "nokogiri" + G + + expect(the_bundle).to include_gems "nokogiri 1.4.2" + end + end + + it "will keep both platforms when both ruby and a specific ruby platform are locked and the bundle is unlocked" do + build_repo4 do + build_gem "nokogiri", "1.11.1" do |s| + s.add_dependency "mini_portile2", "~> 2.5.0" + s.add_dependency "racca", "~> 1.5.2" + end + + build_gem "nokogiri", "1.11.1" do |s| + s.platform = Bundler.local_platform + s.add_dependency "racca", "~> 1.4" + end + + build_gem "mini_portile2", "2.5.0" + build_gem "racca", "1.5.2" + end + + checksums = checksums_section do |c| + c.checksum gem_repo4, "mini_portile2", "2.5.0" + c.checksum gem_repo4, "nokogiri", "1.11.1" + c.checksum gem_repo4, "nokogiri", "1.11.1", Bundler.local_platform + c.checksum gem_repo4, "racca", "1.5.2" + end + + good_lockfile = <<~L + GEM + remote: https://gem.repo4/ + specs: + mini_portile2 (2.5.0) + nokogiri (1.11.1) + mini_portile2 (~> 2.5.0) + racca (~> 1.5.2) + nokogiri (1.11.1-#{Bundler.local_platform}) + racca (~> 1.4) + racca (1.5.2) + + PLATFORMS + #{lockfile_platforms("ruby")} + + DEPENDENCIES + nokogiri (~> 1.11) + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + gemfile <<-G + source "https://gem.repo4" + gem "nokogiri", "~> 1.11" + G + + lockfile good_lockfile + + bundle "update nokogiri" + + expect(lockfile).to eq(good_lockfile) + end + + it "will not try to install platform specific gems when they don't match the current ruby if locked only to ruby" do + build_repo4 do + build_gem "nokogiri", "1.11.1" + + build_gem "nokogiri", "1.11.1" do |s| + s.platform = Bundler.local_platform + s.required_ruby_version = "< #{Gem.ruby_version}" + end + end + + gemfile <<-G + source "https://gem.repo4" + gem "nokogiri" + G + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + nokogiri (1.11.1) + + PLATFORMS + ruby + + DEPENDENCIES + nokogiri + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" + + expect(out).to include("Fetching nokogiri 1.11.1") + expect(the_bundle).to include_gems "nokogiri 1.11.1" + expect(the_bundle).not_to include_gems "nokogiri 1.11.1 #{Bundler.local_platform}" + end + + it "will use the java platform if both generic java and generic ruby platforms are locked", :jruby_only do + gemfile <<-G + source "https://gem.repo1" + gem "nokogiri" + G + + lockfile <<-G + GEM + remote: https://gem.repo1/ + specs: + nokogiri (1.4.2) + nokogiri (1.4.2-java) + weakling (>= 0.0.3) + weakling (0.0.3) + + PLATFORMS + java + ruby + + DEPENDENCIES + nokogiri + + BUNDLED WITH + #{Bundler::VERSION} + G + + bundle "install" + + expect(out).to include("Fetching nokogiri 1.4.2 (java)") + expect(the_bundle).to include_gems "nokogiri 1.4.2 java" + end + + it "will add the resolve for the current platform" do + lockfile <<-G + GEM + remote: https://gem.repo1/ + specs: + nokogiri (1.4.2-java) + weakling (= 0.0.3) + weakling (0.0.3) + + PLATFORMS + java + + DEPENDENCIES + nokogiri + G + + simulate_platform "x86-darwin-100" do + install_gemfile <<-G + source "https://gem.repo1" + gem "nokogiri" + gem "platform_specific" + G + + expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 x86-darwin-100" + end + end + + it "allows specifying only-ruby-platform on jruby", :jruby_only do + install_gemfile <<-G + source "https://gem.repo1" + gem "nokogiri" + gem "platform_specific" + G + + bundle_config "force_ruby_platform true" + + bundle "install" + + expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 ruby" + end + + it "allows specifying only-ruby-platform" do + gemfile <<-G + source "https://gem.repo1" + gem "nokogiri" + gem "platform_specific" + G + + bundle_config "force_ruby_platform true" + + bundle "install" + + expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 ruby" + end + + it "allows specifying only-ruby-platform even if the lockfile is locked to a specific compatible platform" do + install_gemfile <<-G + source "https://gem.repo1" + gem "nokogiri" + gem "platform_specific" + G + + bundle_config "force_ruby_platform true" + + bundle "install" + + expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 ruby" + end + + it "doesn't pull platform specific gems on truffleruby", :truffleruby_only do + install_gemfile <<-G + source "https://gem.repo1" + gem "platform_specific" + G + + expect(the_bundle).to include_gems "platform_specific 1.0 ruby" + end + + it "doesn't pull platform specific gems on truffleruby (except when whitelisted) even if lockfile was generated with an older version that declared ruby as platform", :truffleruby_only do + gemfile <<-G + source "https://gem.repo1" + gem "platform_specific" + G + + lockfile <<-L + GEM + remote: https://gem.repo1/ + specs: + platform_specific (1.0) + + PLATFORMS + ruby + + DEPENDENCIES + platform_specific + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" + + expect(the_bundle).to include_gems "platform_specific 1.0 ruby" + + simulate_platform "x86_64-linux" do + build_repo4 do + build_gem "libv8" + + build_gem "libv8" do |s| + s.platform = "x86_64-linux" + end + end + + gemfile <<-G + source "https://gem.repo4" + gem "libv8" + G + + lockfile <<-L + GEM + remote: https://gem.repo4/ + specs: + libv8 (1.0) + + PLATFORMS + ruby + + DEPENDENCIES + libv8 + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" + + expect(the_bundle).to include_gems "libv8 1.0 x86_64-linux" + end + end + + it "doesn't pull platform specific gems on truffleruby, even if lockfile only includes those", :truffleruby_only do + gemfile <<-G + source "https://gem.repo1" + gem "platform_specific" + G + + lockfile <<-L + GEM + remote: https://gem.repo1/ + specs: + platform_specific (1.0-x86-darwin-100) + + PLATFORMS + x86-darwin-100 + + DEPENDENCIES + platform_specific + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" + + expect(the_bundle).to include_gems "platform_specific 1.0 ruby" + end + + it "pulls platform specific gems correctly on musl" do + build_repo4 do + build_gem "nokogiri", "1.13.8" do |s| + s.platform = "aarch64-linux" + end + end + + simulate_platform "aarch64-linux-musl" do + install_gemfile <<-G, verbose: true + source "https://gem.repo4" + gem "nokogiri" + G + end + + expect(out).to include("Fetching nokogiri 1.13.8 (aarch64-linux)") + end + + it "allows specifying only-ruby-platform on windows with dependency platforms" do + simulate_platform "x86-mswin32" do + install_gemfile <<-G + source "https://gem.repo1" + gem "nokogiri", :platforms => [:windows, :jruby] + gem "platform_specific" + G + + bundle_config "force_ruby_platform true" + + bundle "install" + + expect(the_bundle).to include_gems "platform_specific 1.0 ruby" + expect(the_bundle).to not_include_gems "nokogiri" + end + end + + it "allows specifying only-ruby-platform on windows with gemspec dependency" do + build_lib("foo", "1.0", path: bundled_app) do |s| + s.add_dependency "myrack" + end + + gemfile <<-G + source "https://gem.repo1" + gemspec + G + bundle :lock + + simulate_platform "x86-mswin32" do + bundle_config "force_ruby_platform true" + bundle "install" + + expect(the_bundle).to include_gems "myrack 1.0" + end + end + + it "recovers when the lockfile is missing a platform-specific gem" do + build_repo2 do + build_gem "requires_platform_specific" do |s| + s.add_dependency "platform_specific" + end + end + simulate_platform "x64-mingw-ucrt" do + lockfile <<-L + GEM + remote: https://gem.repo2/ + specs: + platform_specific (1.0-x86-mingw32) + requires_platform_specific (1.0) + platform_specific + + PLATFORMS + x64-mingw-ucrt + x86-mingw32 + + DEPENDENCIES + requires_platform_specific + L + + install_gemfile <<-G, verbose: true + source "https://gem.repo2" + gem "requires_platform_specific" + G + + expect(out).to include("lockfile does not have all gems needed for the current platform") + expect(the_bundle).to include_gem "platform_specific 1.0 x64-mingw-ucrt" + end + end + + %w[x86-mswin32 x64-mswin64 x86-mingw32 x64-mingw-ucrt aarch64-mingw-ucrt].each do |platform| + it "allows specifying platform windows on #{platform} platform" do + simulate_platform platform do + lockfile <<-L + GEM + remote: https://gem.repo1/ + specs: + platform_specific (1.0-#{platform}) + requires_platform_specific (1.0) + platform_specific + + PLATFORMS + #{platform} + + DEPENDENCIES + requires_platform_specific + L + + install_gemfile <<-G + source "https://gem.repo1" + gem "platform_specific", :platforms => [:windows] + G + + expect(the_bundle).to include_gems "platform_specific 1.0 #{platform}" + end + end + end +end diff --git a/spec/bundler/runtime/require_spec.rb b/spec/bundler/runtime/require_spec.rb new file mode 100644 index 0000000000..46613286d2 --- /dev/null +++ b/spec/bundler/runtime/require_spec.rb @@ -0,0 +1,480 @@ +# frozen_string_literal: true + +RSpec.describe "Bundler.require" do + before :each do + build_lib "one", "1.0.0" do |s| + s.write "lib/baz.rb", "puts 'baz'" + s.write "lib/qux.rb", "puts 'qux'" + end + + build_lib "two", "1.0.0" do |s| + s.write "lib/two.rb", "puts 'two'" + s.add_dependency "three", "= 1.0.0" + end + + build_lib "three", "1.0.0" do |s| + s.write "lib/three.rb", "puts 'three'" + s.add_dependency "seven", "= 1.0.0" + end + + build_lib "four", "1.0.0" do |s| + s.write "lib/four.rb", "puts 'four'" + end + + build_lib "five", "1.0.0", no_default: true do |s| + s.write "lib/mofive.rb", "puts 'five'" + end + + build_lib "six", "1.0.0" do |s| + s.write "lib/six.rb", "puts 'six'" + end + + build_lib "seven", "1.0.0" do |s| + s.write "lib/seven.rb", "puts 'seven'" + end + + build_lib "eight", "1.0.0" do |s| + s.write "lib/eight.rb", "puts 'eight'" + end + + build_lib "nine", "1.0.0" do |s| + s.write "lib/nine.rb", "puts 'nine'" + end + + build_lib "ten", "1.0.0" do |s| + s.write "lib/ten.rb", "puts 'ten'" + end + + gemfile <<-G + source "https://gem.repo1" + path "#{lib_path}" do + gem "one", :group => :bar, :require => %w[baz qux] + gem "two" + gem "three", :group => :not + gem "four", :require => false + gem "five" + gem "six", :group => "string" + gem "seven", :group => :not + gem "eight", :require => true, :group => :require_true + env "BUNDLER_TEST" => "nine" do + gem "nine", :require => true + end + gem "ten", :install_if => lambda { ENV["BUNDLER_TEST"] == "ten" } + end + G + end + + it "requires the gems" do + # default group + run "Bundler.require" + expect(out).to eq("two") + + # specific group + run "Bundler.require(:bar)" + expect(out).to eq("baz\nqux") + + # default and specific group + run "Bundler.require(:default, :bar)" + expect(out).to eq("baz\nqux\ntwo") + + # specific group given as a string + run "Bundler.require('bar')" + expect(out).to eq("baz\nqux") + + # specific group declared as a string + run "Bundler.require(:string)" + expect(out).to eq("six") + + # required in resolver order instead of gemfile order + run("Bundler.require(:not)") + expect(out.split("\n").sort).to eq(%w[seven three]) + + # test require: true + run "Bundler.require(:require_true)" + expect(out).to eq("eight") + end + + it "allows requiring gems with non standard names explicitly" do + run "Bundler.require ; require 'mofive'" + expect(out).to eq("two\nfive") + end + + it "allows requiring gems which are scoped by env" do + ENV["BUNDLER_TEST"] = "nine" + run "Bundler.require" + expect(out).to eq("two\nnine") + end + + it "allows requiring gems which are scoped by install_if" do + ENV["BUNDLER_TEST"] = "ten" + run "Bundler.require" + expect(out).to eq("two\nten") + end + + it "raises an exception if a require is specified but the file does not exist" do + gemfile <<-G + source "https://gem.repo1" + path "#{lib_path}" do + gem "two", :require => 'fail' + end + G + + run "Bundler.require", raise_on_error: false + + expect(err_without_deprecations).to include("cannot load such file -- fail") + end + + it "displays a helpful message if the required gem throws an error" do + build_lib "faulty", "1.0.0" do |s| + s.write "lib/faulty.rb", "raise RuntimeError.new(\"Gem Internal Error Message\")" + end + + gemfile <<-G + source "https://gem.repo1" + path "#{lib_path}" do + gem "faulty" + end + G + + run "Bundler.require", raise_on_error: false + expect(err).to match("error while trying to load the gem 'faulty'") + expect(err).to match("Gem Internal Error Message") + end + + it "doesn't swallow the error when the library has an unrelated error" do + build_lib "loadfuuu", "1.0.0" do |s| + s.write "lib/loadfuuu.rb", "raise LoadError.new(\"cannot load such file -- load-bar\")" + end + + gemfile <<-G + source "https://gem.repo1" + path "#{lib_path}" do + gem "loadfuuu" + end + G + + run "Bundler.require", raise_on_error: false + + expect(err_without_deprecations).to include("cannot load such file -- load-bar") + end + + describe "with namespaced gems" do + before :each do + build_lib "jquery-rails", "1.0.0" do |s| + s.write "lib/jquery/rails.rb", "puts 'jquery/rails'" + end + end + + it "requires gem names that are namespaced" do + gemfile <<-G + source "https://gem.repo1" + path '#{lib_path}' do + gem 'jquery-rails' + end + G + + run "Bundler.require" + expect(out).to eq("jquery/rails") + end + + it "silently passes if the require fails" do + build_lib "bcrypt-ruby", "1.0.0", no_default: true do |s| + s.write "lib/brcrypt.rb", "BCrypt = '1.0.0'" + end + gemfile <<-G + source "https://gem.repo1" + + path "#{lib_path}" do + gem "bcrypt-ruby" + end + G + + cmd = <<-RUBY + require 'bundler' + Bundler.require + RUBY + ruby(cmd) + + expect(err).to be_empty + end + + it "does not mangle explicitly given requires" do + gemfile <<-G + source "https://gem.repo1" + path "#{lib_path}" do + gem 'jquery-rails', :require => 'jquery-rails' + end + G + + run "Bundler.require", raise_on_error: false + + expect(err_without_deprecations).to include("cannot load such file -- jquery-rails") + end + + it "handles the case where regex fails" do + build_lib "load-fuuu", "1.0.0" do |s| + s.write "lib/load-fuuu.rb", "raise LoadError.new(\"Could not open library 'libfuuu-1.0': libfuuu-1.0: cannot open shared object file: No such file or directory.\")" + end + + gemfile <<-G + source "https://gem.repo1" + path "#{lib_path}" do + gem "load-fuuu" + end + G + + run "Bundler.require", raise_on_error: false + + expect(err_without_deprecations).to include("libfuuu-1.0").and include("cannot open shared object file") + end + + it "doesn't swallow the error when the library has an unrelated error" do + build_lib "load-fuuu", "1.0.0" do |s| + s.write "lib/load/fuuu.rb", "raise LoadError.new(\"cannot load such file -- load-bar\")" + end + + gemfile <<-G + source "https://gem.repo1" + path "#{lib_path}" do + gem "load-fuuu" + end + G + + run "Bundler.require", raise_on_error: false + + expect(err_without_deprecations).to include("cannot load such file -- load-bar") + end + end + + describe "using bundle exec" do + it "requires the locked gems" do + bundle "exec ruby -e 'Bundler.require'" + expect(out).to eq("two") + + bundle "exec ruby -e 'Bundler.require(:bar)'" + expect(out).to eq("baz\nqux") + + bundle "exec ruby -e 'Bundler.require(:default, :bar)'" + expect(out).to eq("baz\nqux\ntwo") + end + end + + describe "order" do + before(:each) do + build_lib "one", "1.0.0" do |s| + s.write "lib/one.rb", <<-ONE + if defined?(Two) + Two.two + else + puts "two_not_loaded" + end + puts 'one' + ONE + end + + build_lib "two", "1.0.0" do |s| + s.write "lib/two.rb", <<-TWO + module Two + def self.two + puts 'module_two' + end + end + puts 'two' + TWO + end + end + + it "works when the gems are in the Gemfile in the correct order" do + gemfile <<-G + source "https://gem.repo1" + path "#{lib_path}" do + gem "two" + gem "one" + end + G + + run "Bundler.require" + expect(out).to eq("two\nmodule_two\none") + end + + describe "a gem with different requires for different envs" do + before(:each) do + build_gem "multi_gem", to_bundle: true do |s| + s.write "lib/one.rb", "puts 'ONE'" + s.write "lib/two.rb", "puts 'TWO'" + end + + install_gemfile <<-G + source "https://gem.repo1" + gem "multi_gem", :require => "one", :group => :one + gem "multi_gem", :require => "two", :group => :two + G + end + + it "requires both with Bundler.require(both)" do + run "Bundler.require(:one, :two)" + expect(out).to eq("ONE\nTWO") + end + + it "requires one with Bundler.require(:one)" do + run "Bundler.require(:one)" + expect(out).to eq("ONE") + end + + it "requires :two with Bundler.require(:two)" do + run "Bundler.require(:two)" + expect(out).to eq("TWO") + end + end + + it "fails when the gems are in the Gemfile in the wrong order" do + gemfile <<-G + source "https://gem.repo1" + path "#{lib_path}" do + gem "one" + gem "two" + end + G + + run "Bundler.require" + expect(out).to eq("two_not_loaded\none\ntwo") + end + + describe "with busted gems" do + it "should be busted" do + build_gem "busted_require", to_bundle: true do |s| + s.write "lib/busted_require.rb", "require 'no_such_file_omg'" + end + + install_gemfile <<-G + source "https://gem.repo1" + gem "busted_require" + G + + run "Bundler.require", raise_on_error: false + + expect(err_without_deprecations).to include("cannot load such file -- no_such_file_omg") + end + end + end + + it "does not load rubygems gemspecs that are used" do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + run <<-R + path = File.join(Gem.dir, "specifications", "myrack-1.0.0.gemspec") + contents = File.read(path) + contents = contents.lines.to_a.insert(-2, "\n raise 'broken gemspec'\n").join + File.open(path, "w") do |f| + f.write contents + end + R + + run <<-R + Bundler.require + puts "WIN" + R + + expect(out).to eq("WIN") + end + + it "does not load git gemspecs that are used" do + build_git "foo" + + install_gemfile <<-G + source "https://gem.repo1" + gem "foo", :git => "#{lib_path("foo-1.0")}" + G + + run <<-R + path = Gem.loaded_specs["foo"].loaded_from + contents = File.read(path) + contents = contents.lines.to_a.insert(-2, "\n raise 'broken gemspec'\n").join + File.open(path, "w") do |f| + f.write contents + end + R + + run <<-R + Bundler.require + puts "WIN" + R + + expect(out).to eq("WIN") + end + + it "does not load plugins" do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + create_file "plugins/rubygems_plugin.rb", "puts 'FAIL'" + + run <<~R, env: { "RUBYLIB" => rubylib.unshift(bundled_app("plugins").to_s).join(File::PATH_SEPARATOR) } + Bundler.require + puts "WIN" + R + + expect(out).to eq("WIN") + end + + it "does not extract gemspecs from application cache packages" do + gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + bundle :cache + + path = cached_gem("myrack-1.0.0") + + run <<-R + File.open("#{path}", "w") do |f| + f.write "broken package" + end + R + + run <<-R + Bundler.require + puts "WIN" + R + + expect(out).to eq("WIN") + end +end + +RSpec.describe "Bundler.require with platform specific dependencies" do + it "does not require the gems that are pinned to other platforms" do + install_gemfile <<-G + source "https://gem.repo1" + + platforms :#{not_local_tag} do + gem "platform_specific", :require => "omgomg" + end + + gem "myrack", "1.0.0" + G + + run "Bundler.require" + expect(err).to be_empty + end + + it "requires gems pinned to multiple platforms, including the current one" do + install_gemfile <<-G + source "https://gem.repo1" + + platforms :#{not_local_tag}, :#{local_tag} do + gem "myrack", :require => "myrack" + end + G + + run "Bundler.require; puts MYRACK" + + expect(out).to eq("1.0.0") + expect(err).to be_empty + end +end diff --git a/spec/bundler/runtime/requiring_spec.rb b/spec/bundler/runtime/requiring_spec.rb new file mode 100644 index 0000000000..f0e0aeacaf --- /dev/null +++ b/spec/bundler/runtime/requiring_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +RSpec.describe "Requiring bundler" do + it "takes care of requiring rubygems when entrypoint is bundler/setup" do + sys_exec("#{Gem.ruby} -I#{lib_dir} -rbundler/setup -e'puts true'", env: { "RUBYOPT" => "--disable=gems" }) + + expect(stdboth).to eq("true") + end + + it "takes care of requiring rubygems when requiring just bundler" do + sys_exec("#{Gem.ruby} -I#{lib_dir} -rbundler -e'puts true'", env: { "RUBYOPT" => "--disable=gems" }) + + expect(stdboth).to eq("true") + end +end diff --git a/spec/bundler/runtime/self_management_spec.rb b/spec/bundler/runtime/self_management_spec.rb new file mode 100644 index 0000000000..176c2a3121 --- /dev/null +++ b/spec/bundler/runtime/self_management_spec.rb @@ -0,0 +1,279 @@ +# frozen_string_literal: true + +RSpec.describe "Self management" do + describe "auto switching" do + let(:previous_minor) do + "9.3.0" + end + + let(:current_version) do + "9.4.0" + end + + before do + build_repo4 do + build_bundler previous_minor + + build_bundler current_version + + build_gem "myrack", "1.0.0" + end + + gemfile <<-G + source "https://gem.repo4" + + gem "myrack" + G + + pristine_system_gems "bundler-#{current_version}" + end + + it "installs locked version when using system path and uses it" do + lockfile_bundled_with(previous_minor) + + bundle_config "path.system true" + bundle "install" + expect(out).to include("Bundler #{current_version} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") + + # It uninstalls the older system bundler + bundle "clean --force", artifice: nil + expect(out).to eq("Removing bundler (#{current_version})") + + # App now uses locked version + bundle "-v", artifice: nil + expect(out).to eq(previous_minor) + + # ruby-core test setup has always "lib" in $LOAD_PATH so `require "bundler/setup"` always activate the local version rather than using RubyGems gem activation stuff + unless ruby_core? + # App now uses locked version, even when not using the CLI directly + file = bundled_app("bin/bundle_version.rb") + create_file file, <<-RUBY + #!#{Gem.ruby} + require 'bundler/setup' + puts '#{previous_minor}' + RUBY + file.chmod(0o777) + cmd = Gem.win_platform? ? "#{Gem.ruby} bin/bundle_version.rb" : "bin/bundle_version.rb" + in_bundled_app cmd + expect(out).to eq(previous_minor) + end + + # Subsequent installs use the locked version without reinstalling + bundle "install --verbose", artifice: nil + expect(out).to include("Using bundler #{previous_minor}") + expect(out).not_to include("Bundler #{current_version} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") + end + + it "installs locked version when using local path and uses it" do + lockfile_bundled_with(previous_minor) + + bundle_config "path vendor/bundle" + bundle "install" + expect(out).to include("Bundler #{current_version} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") + expect(vendored_gems("gems/bundler-#{previous_minor}")).to exist + + # It does not uninstall the locked bundler + bundle "clean" + expect(out).to be_empty + + # App now uses locked version + bundle "-v" + expect(out).to eq(previous_minor) + + # Preserves original gem home when auto-switching + bundle "exec ruby -e 'puts Bundler.original_env[\"GEM_HOME\"]'" + expect(out).to eq(ENV["GEM_HOME"]) + + # ruby-core test setup has always "lib" in $LOAD_PATH so `require "bundler/setup"` always activate the local version rather than using RubyGems gem activation stuff + unless ruby_core? + # App now uses locked version, even when not using the CLI directly + file = bundled_app("bin/bundle_version.rb") + create_file file, <<-RUBY + #!#{Gem.ruby} + require 'bundler/setup' + puts '#{previous_minor}' + RUBY + file.chmod(0o777) + cmd = Gem.win_platform? ? "#{Gem.ruby} bin/bundle_version.rb" : "bin/bundle_version.rb" + in_bundled_app cmd + expect(out).to eq(previous_minor) + end + + # Subsequent installs use the locked version without reinstalling + bundle "install --verbose" + expect(out).to include("Using bundler #{previous_minor}") + expect(out).not_to include("Bundler #{current_version} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") + end + + it "installs locked version when using deployment option and uses it" do + lockfile_bundled_with(previous_minor) + + bundle_config "deployment true" + bundle "install" + expect(out).to include("Bundler #{current_version} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") + expect(vendored_gems("gems/bundler-#{previous_minor}")).to exist + + # It does not uninstall the locked bundler + bundle "clean" + expect(out).to be_empty + + # App now uses locked version + bundle "-v" + expect(out).to eq(previous_minor) + + # Subsequent installs use the locked version without reinstalling + bundle "install --verbose" + expect(out).to include("Using bundler #{previous_minor}") + expect(out).not_to include("Bundler #{current_version} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") + end + + it "does not try to install a development version" do + lockfile_bundled_with("#{previous_minor}.dev") + + bundle "install --verbose" + expect(out).not_to match(/restarting using that version/) + + bundle "-v" + expect(out).to eq(current_version) + end + + it "does not try to install when --local is passed" do + lockfile_bundled_with(previous_minor) + system_gems "myrack-1.0.0", path: local_gem_path + + bundle "install --local" + expect(out).not_to match(/Installing Bundler/) + + bundle "-v" + expect(out).to eq(current_version) + end + + it "shows a discrete message if locked bundler does not exist" do + missing_minor = "#{current_version[0]}.999.999" + + lockfile_bundled_with(missing_minor) + + bundle "install" + expect(err).to eq("Your lockfile is locked to a version of bundler (#{missing_minor}) that doesn't exist at https://rubygems.org/. Going on using #{current_version}") + + bundle "-v" + expect(out).to eq(current_version) + end + + it "installs BUNDLE_VERSION version when using bundle config version x.y.z" do + lockfile_bundled_with(current_version) + + bundle_config "version #{previous_minor}" + bundle "install" + expect(out).to include("Bundler #{current_version} is running, but your configuration was #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") + + bundle "-v" + expect(out).to eq(previous_minor) + end + + it "requires the right bundler version from the config and run bundle CLI without re-exec" do + unless Bundler.rubygems.provides?(">= 4.1.0.dev") + skip "This spec can only run when Gem::BundlerVersionFinder.bundler_versions reads bundler configs" + end + + lockfile_bundled_with(current_version) + + bundle_config "version #{previous_minor}" + bundle_config "path.system true" + bundle "install" + + script = bundled_app("script.rb") + create_file(script, "p 'executed once'") + + bundle "-v", env: { "RUBYOPT" => "-r#{script}" } + expect(out).to eq(%("executed once"\n9.3.0)) + end + + it "does not try to install when using bundle config version global" do + lockfile_bundled_with(previous_minor) + + bundle_config "version system" + bundle "install" + expect(out).not_to match(/restarting using that version/) + + bundle "-v" + expect(out).to eq(current_version) + end + + it "does not try to install when using bundle config version <dev-version>" do + lockfile_bundled_with(previous_minor) + + bundle_config "version #{previous_minor}.dev" + bundle "install" + expect(out).not_to match(/restarting using that version/) + + bundle "-v" + expect(out).to eq(current_version) + end + + it "ignores malformed lockfile version" do + lockfile_bundled_with("2.3.") + + bundle "install --verbose" + expect(out).to include("Using bundler #{current_version}") + end + + it "uses the right original script when re-execing, if `$0` has been changed to something that's not a script", :ruby_repo do + system_gems "bundler-9.9.9", path: local_gem_path + + test = bundled_app("test.rb") + + create_file test, <<~RUBY + $0 = "this is the program name" + require "bundler/setup" + RUBY + + lockfile_bundled_with("9.9.9") + + in_bundled_app "#{Gem.ruby} #{test}", raise_on_error: false + expect(err).to include("Could not find myrack-1.0.0") + expect(err).not_to include("this is the program name") + end + + it "uses modified $0 when re-execing, if `$0` has been changed to a script", :ruby_repo do + system_gems "bundler-9.9.9", path: local_gem_path + + runner = bundled_app("runner.rb") + + create_file runner, <<~RUBY + $0 = ARGV.shift + load $0 + RUBY + + script = bundled_app("script.rb") + create_file script, <<~RUBY + require "bundler/setup" + RUBY + + lockfile_bundled_with("9.9.9") + + in_bundled_app "#{Gem.ruby} #{runner} #{script}", raise_on_error: false + expect(err).to include("Could not find myrack-1.0.0") + end + + private + + def lockfile_bundled_with(version) + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + myrack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack + + BUNDLED WITH + #{version} + L + end + end +end diff --git a/spec/bundler/runtime/setup_spec.rb b/spec/bundler/runtime/setup_spec.rb new file mode 100644 index 0000000000..ceb6fcf66a --- /dev/null +++ b/spec/bundler/runtime/setup_spec.rb @@ -0,0 +1,1710 @@ +# frozen_string_literal: true + +require "tmpdir" + +RSpec.describe "Bundler.setup" do + describe "with no arguments" do + it "makes all groups available" do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack", :group => :test + G + + ruby <<-RUBY + require 'bundler' + Bundler.setup + + require 'myrack' + puts MYRACK + RUBY + expect(err).to be_empty + expect(out).to eq("1.0.0") + end + end + + describe "when called with groups" do + before(:each) do + install_gemfile <<-G + source "https://gem.repo1" + gem "yard" + gem "myrack", :group => :test + G + end + + it "doesn't make all groups available" do + ruby <<-RUBY + require 'bundler' + Bundler.setup(:default) + + begin + require 'myrack' + rescue LoadError + puts "WIN" + end + RUBY + expect(err).to be_empty + expect(out).to eq("WIN") + end + + it "accepts string for group name" do + ruby <<-RUBY + require 'bundler' + Bundler.setup(:default, 'test') + + require 'myrack' + puts MYRACK + RUBY + expect(err).to be_empty + expect(out).to eq("1.0.0") + end + + it "leaves all groups available if they were already" do + ruby <<-RUBY + require 'bundler' + Bundler.setup + Bundler.setup(:default) + + require 'myrack' + puts MYRACK + RUBY + expect(err).to be_empty + expect(out).to eq("1.0.0") + end + + it "leaves :default available if setup is called twice" do + ruby <<-RUBY + require 'bundler' + Bundler.setup(:default) + Bundler.setup(:default, :test) + + begin + require 'yard' + puts "WIN" + rescue LoadError + puts "FAIL" + end + RUBY + expect(err).to be_empty + expect(out).to match("WIN") + end + + it "handles multiple non-additive invocations" do + ruby <<-RUBY, raise_on_error: false + require 'bundler' + Bundler.setup(:default, :test) + Bundler.setup(:default) + require 'myrack' + + puts "FAIL" + RUBY + + expect(err).to match("myrack") + expect(err).to match("LoadError") + expect(out).not_to match("FAIL") + end + end + + context "load order" do + def clean_load_path(lp) + without_bundler_load_path = ruby("puts $LOAD_PATH").split("\n") + lp -= [*without_bundler_load_path, lib_dir.to_s] + lp.map! {|p| p.sub(system_gem_path.to_s, "") } + end + + it "puts loaded gems after -I and RUBYLIB", :ruby_repo do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + ENV["RUBYOPT"] = "#{ENV["RUBYOPT"]} -Idash_i_dir" + ENV["RUBYLIB"] = "rubylib_dir" + + ruby <<-RUBY + require 'bundler' + Bundler.setup + puts $LOAD_PATH + RUBY + + load_path = out.split("\n") + myrack_load_order = load_path.index {|path| path.include?("myrack") } + + expect(err).to be_empty + expect(load_path).to include(a_string_ending_with("dash_i_dir"), "rubylib_dir") + expect(myrack_load_order).to be > 0 + end + + it "orders the load path correctly when there are dependencies" do + bundle_config "path.system true" + + install_gemfile <<-G + source "https://gem.repo1" + gem "rails" + G + + ruby <<-RUBY + require 'bundler' + gem "bundler", "#{Bundler::VERSION}" if #{ruby_core?} + Bundler.setup + puts $LOAD_PATH + RUBY + + load_path = clean_load_path(out.split("\n")) + + expect(load_path).to start_with( + "/gems/rails-2.3.2/lib", + "/gems/activeresource-2.3.2/lib", + "/gems/activerecord-2.3.2/lib", + "/gems/actionpack-2.3.2/lib", + "/gems/actionmailer-2.3.2/lib", + "/gems/activesupport-2.3.2/lib", + "/gems/rake-#{rake_version}/lib" + ) + end + + it "falls back to order the load path alphabetically for backwards compatibility" do + bundle_config "path.system true" + + install_gemfile <<-G + source "https://gem.repo1" + gem "weakling" + gem "duradura" + gem "terranova" + G + + ruby <<-RUBY + require 'bundler/setup' + puts $LOAD_PATH + RUBY + + load_path = clean_load_path(out.split("\n")) + + expect(load_path).to start_with( + "/gems/weakling-0.0.3/lib", + "/gems/terranova-8/lib", + "/gems/duradura-7.0/lib" + ) + end + end + + it "raises if the Gemfile was not yet installed" do + gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + ruby <<-R + require 'bundler' + + begin + Bundler.setup + puts "FAIL" + rescue Bundler::GemNotFound + puts "WIN" + end + R + + expect(out).to eq("WIN") + end + + it "doesn't create a Gemfile.lock if the setup fails" do + gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + ruby <<-R, raise_on_error: false + require 'bundler' + + Bundler.setup + R + + expect(bundled_app_lock).not_to exist + end + + it "doesn't change the Gemfile.lock if the setup fails" do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + lockfile = File.read(bundled_app_lock) + + gemfile <<-G + source "https://gem.repo1" + gem "myrack" + gem "nosuchgem", "10.0" + G + + ruby <<-R, raise_on_error: false + require 'bundler' + + Bundler.setup + R + + expect(File.read(bundled_app_lock)).to eq(lockfile) + end + + it "makes a Gemfile.lock if setup succeeds" do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + File.read(bundled_app_lock) + + FileUtils.rm(bundled_app_lock) + + run "1" + expect(bundled_app_lock).to exist + end + + describe "$BUNDLE_GEMFILE" do + context "user provides an absolute path" do + it "uses BUNDLE_GEMFILE to locate the gemfile if present" do + gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + gemfile bundled_app("4realz"), <<-G + source "https://gem.repo1" + gem "activesupport", "2.3.5" + G + + ENV["BUNDLE_GEMFILE"] = bundled_app("4realz").to_s + bundle :install + + expect(the_bundle).to include_gems "activesupport 2.3.5" + end + end + + context "an absolute path is not provided" do + it "uses BUNDLE_GEMFILE to locate the gemfile if present and doesn't fail in deployment mode" do + gemfile <<-G + source "https://gem.repo1" + G + + bundle "install" + bundle_config "deployment true" + + ENV["BUNDLE_GEMFILE"] = "Gemfile" + ruby <<-R + require 'bundler' + + begin + Bundler.setup + puts "WIN" + rescue ArgumentError => e + puts "FAIL" + end + R + + expect(out).to eq("WIN") + end + end + + context "user sets it via `config set --local gemfile`" do + it "uses the value in the config" do + gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + gemfile bundled_app("CustomGemfile"), <<-G + source "https://gem.repo1" + gem "activesupport", "2.3.5" + G + + bundle_config "gemfile #{bundled_app("CustomGemfile")}" + bundle "install" + + ruby <<-R + require 'bundler' + Bundler.setup + require 'activesupport' + puts ACTIVESUPPORT + R + + expect(out).to eq("2.3.5") + end + end + end + + it "prioritizes gems in BUNDLE_PATH over gems in GEM_HOME" do + ENV["BUNDLE_PATH"] = bundled_app(".bundle").to_s + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack", "1.0.0" + G + + build_gem "myrack", "1.0", to_system: true do |s| + s.write "lib/myrack.rb", "MYRACK = 'FAIL'" + end + + expect(the_bundle).to include_gems "myrack 1.0.0" + end + + describe "integrate with rubygems" do + describe "by replacing #gem" do + before :each do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack", "0.9.1" + G + end + + it "replaces #gem but raises when the gem is missing" do + run <<-R + begin + gem "activesupport" + puts "FAIL" + rescue LoadError + puts "WIN" + end + R + + expect(out).to eq("WIN") + end + + it "replaces #gem but raises when the version is wrong" do + run <<-R + begin + gem "myrack", "1.0.0" + puts "FAIL" + rescue LoadError + puts "WIN" + end + R + + expect(out).to eq("WIN") + end + end + + describe "by hiding system gems" do + before :each do + system_gems "activesupport-2.3.5" + install_gemfile <<-G + source "https://gem.repo1" + gem "yard" + G + end + + it "removes system gems from Gem.source_index" do + run "require 'yard'" + expect(out).to include("bundler-#{Bundler::VERSION}").and include("yard-1.0") + expect(out).not_to include("activesupport-2.3.5") + end + + context "when the ruby stdlib is a substring of Gem.path" do + it "does not reject the stdlib from $LOAD_PATH" do + substring = "/" + $LOAD_PATH.find {|p| p.include?("vendor_ruby") }.split("/")[2] + run "puts 'worked!'", env: { "GEM_PATH" => substring } + expect(out).to eq("worked!") + end + end + end + end + + describe "with paths" do + it "activates the gems in the path source" do + system_gems "myrack-1.0.0" + + build_lib "myrack", "1.0.0" do |s| + s.write "lib/myrack.rb", "puts 'WIN'" + end + + gemfile <<-G + source "https://gem.repo1" + path "#{lib_path("myrack-1.0.0")}" do + gem "myrack" + end + G + + run "require 'myrack'" + expect(out).to eq("WIN") + end + end + + describe "with git" do + before do + build_git "myrack", "1.0.0" + + gemfile <<-G + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-1.0.0")}" + G + end + + it "provides a useful exception when the git repo is not checked out yet" do + run "1", raise_on_error: false + expect(err).to match(/the git source #{lib_path("myrack-1.0.0")} is not yet checked out. Please run `bundle install`/i) + end + + it "does not hit the git binary if the lockfile is available and up to date" do + bundle "install" + + break_git! + + ruby <<-R + require 'bundler' + + begin + Bundler.setup + puts "WIN" + rescue Exception => e + puts "FAIL" + end + R + + expect(out).to eq("WIN") + end + + it "provides a good exception if the lockfile is unavailable" do + bundle "install" + + FileUtils.rm(bundled_app_lock) + + break_git! + + ruby <<-R + require "bundler" + + begin + Bundler.setup + puts "FAIL" + rescue Bundler::GitError => e + puts e.message + end + R + + run "puts 'FAIL'", raise_on_error: false + + expect(err).not_to include "This is not the git you are looking for" + end + + it "works even when the cache directory has been deleted" do + bundle :install + FileUtils.rm_r default_cache_path + expect(the_bundle).to include_gems "myrack 1.0.0" + end + + it "does not randomly change the path when specifying --path and the bundle directory becomes read only" do + bundle_config "path vendor/bundle" + bundle :install + + with_read_only("#{bundled_app}/**/*") do + expect(the_bundle).to include_gems "myrack 1.0.0" + end + end + + it "finds git gem when default bundle path becomes read only" do + bundle_config "path .bundle" + bundle "install" + + with_read_only("#{bundled_app(".bundle")}/**/*") do + expect(the_bundle).to include_gems "myrack 1.0.0" + end + end + end + + describe "when specifying local override" do + it "explodes if given path does not exist on runtime" do + build_git "myrack", "0.8" + + FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack")) + + gemfile <<-G + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main" + G + + bundle %(config set local.myrack #{lib_path("local-myrack")}) + bundle :install + + FileUtils.rm_r(lib_path("local-myrack")) + run "require 'myrack'", raise_on_error: false + expect(err).to match(/Cannot use local override for myrack-0.8 because #{Regexp.escape(lib_path("local-myrack").to_s)} does not exist/) + end + + it "explodes if branch is not given on runtime" do + build_git "myrack", "0.8" + + FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack")) + + gemfile <<-G + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main" + G + + bundle %(config set local.myrack #{lib_path("local-myrack")}) + bundle :install + + gemfile <<-G + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}" + G + + run "require 'myrack'", raise_on_error: false + expect(err).to match(/because :branch is not specified in Gemfile/) + end + + it "explodes on different branches on runtime" do + build_git "myrack", "0.8" + + FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack")) + + gemfile <<-G + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main" + G + + bundle %(config set local.myrack #{lib_path("local-myrack")}) + bundle :install + + gemfile <<-G + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "changed" + G + + run "require 'myrack'", raise_on_error: false + expect(err).to match(/is using branch main but Gemfile specifies changed/) + end + + it "explodes on refs with different branches on runtime" do + build_git "myrack", "0.8" + + FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack")) + + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}", :ref => "main", :branch => "main" + G + + gemfile <<-G + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}", :ref => "main", :branch => "nonexistent" + G + + bundle %(config set local.myrack #{lib_path("local-myrack")}) + run "require 'myrack'", raise_on_error: false + expect(err).to match(/is using branch main but Gemfile specifies nonexistent/) + end + end + + describe "when excluding groups" do + it "doesn't change the resolve if --without is used" do + bundle_config "without rails" + install_gemfile <<-G + source "https://gem.repo1" + gem "activesupport" + + group :rails do + gem "rails", "2.3.2" + end + G + + system_gems "activesupport-2.3.5" + + expect(the_bundle).to include_gems "activesupport 2.3.2", groups: :default + end + + it "remembers --without and does not bail on bare Bundler.setup" do + bundle_config "without rails" + install_gemfile <<-G + source "https://gem.repo1" + gem "activesupport" + + group :rails do + gem "rails", "2.3.2" + end + G + + system_gems "activesupport-2.3.5" + + expect(the_bundle).to include_gems "activesupport 2.3.2" + end + + it "remembers --without and does not bail on bare Bundler.setup, even in the case of path gems no longer available" do + bundle_config "without development" + + path = bundled_app(File.join("vendor", "foo")) + build_lib "foo", path: path + + install_gemfile <<-G + source "https://gem.repo1" + gem "activesupport", "2.3.2" + gem 'foo', :path => 'vendor/foo', :group => :development + G + + FileUtils.rm_r(path) + + ruby "require 'bundler'; Bundler.setup", env: { "DEBUG" => "1" } + expect(out).to include("Assuming that source at `vendor/foo` has not changed since fetching its specs errored") + expect(out).to include("Found no changes, using resolution from the lockfile") + expect(err).to be_empty + end + + it "doesn't re-resolve when a pre-release bundler is used and a dependency includes a dependency on bundler" do + system_gems "bundler-9.99.9.beta1" + + build_repo4 do + build_gem "depends_on_bundler", "1.0" do |s| + s.add_dependency "bundler", ">= 1.5.0" + end + end + + install_gemfile <<~G + source "https://gem.repo4" + gem "depends_on_bundler" + G + + ruby "require '#{system_gem_path("gems/bundler-9.99.9.beta1/lib/bundler.rb")}'; Bundler.setup", env: { "DEBUG" => "1" } + expect(out).to include("Found no changes, using resolution from the lockfile") + expect(out).not_to include("lockfile does not have all gems needed for the current platform") + expect(err).to be_empty + end + + it "doesn't fail in frozen mode when bundler is a Gemfile dependency" do + install_gemfile <<~G + source "https://gem.repo4" + gem "bundler" + G + + bundle "install --verbose", env: { "BUNDLE_FROZEN" => "true" } + expect(err).to be_empty + end + + it "doesn't re-resolve when deleting dependencies" do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + gem "actionpack" + G + + install_gemfile <<-G, verbose: true + source "https://gem.repo1" + gem "myrack" + G + + expect(out).to include("Some dependencies were deleted, using a subset of the resolution from the lockfile") + expect(err).to be_empty + end + + it "remembers --without and does not include groups passed to Bundler.setup" do + bundle_config "without rails" + install_gemfile <<-G + source "https://gem.repo1" + gem "activesupport" + + group :myrack do + gem "myrack" + end + + group :rails do + gem "rails", "2.3.2" + end + G + + expect(the_bundle).not_to include_gems "activesupport 2.3.2", groups: :myrack + expect(the_bundle).to include_gems "myrack 1.0.0", groups: :myrack + end + end + + # RubyGems returns loaded_from as a string + it "has loaded_from as a string on all specs" do + build_git "foo" + build_git "no-gemspec", gemspec: false + + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + gem "foo", :git => "#{lib_path("foo-1.0")}" + gem "no-gemspec", "1.0", :git => "#{lib_path("no-gemspec-1.0")}" + G + + run <<-R + Gem.loaded_specs.each do |n, s| + puts "FAIL" unless s.loaded_from.is_a?(String) + end + R + + expect(out).to be_empty + end + + it "has gem_dir pointing to local repo" do + build_lib "foo", "1.0", path: bundled_app + + install_gemfile <<-G + source "https://gem.repo1" + gemspec + G + + run <<-R + puts Gem.loaded_specs['foo'].gem_dir + R + + expect(out).to eq(bundled_app.to_s) + end + + it "does not load all gemspecs" do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + run <<-R + File.open(File.join(Gem.dir, "specifications", "invalid.gemspec"), "w") do |f| + f.write <<-RUBY +# -*- encoding: utf-8 -*- +# stub: invalid 1.0.0 ruby lib + +Gem::Specification.new do |s| + s.name = "invalid" + s.version = "1.0.0" + s.authors = ["Invalid Author"] + s.files = ["lib/invalid.rb"] + s.add_dependency "nonexistent-gem", "~> 999.999.999" + s.validate! +end + RUBY + end + R + + run <<-R + File.open(File.join(Gem.dir, "specifications", "invalid-ext.gemspec"), "w") do |f| + f.write <<-RUBY +# -*- encoding: utf-8 -*- +# stub: invalid-ext 1.0.0 ruby lib +# stub: a.ext\\0b.ext + +Gem::Specification.new do |s| + s.name = "invalid-ext" + s.version = "1.0.0" + s.authors = ["Invalid Author"] + s.files = ["lib/invalid.rb"] + s.required_ruby_version = "~> 0.8.0" + s.validate! +end + RUBY + end + # Need to write the gem.build_complete file, + # otherwise the full spec is loaded to check the installed_by_version + extensions_dir = Gem.default_ext_dir_for(Gem.dir) || File.join(Gem.dir, "extensions", Gem::Platform.local.to_s, Gem.extension_api_version) + Bundler::FileUtils.mkdir_p(File.join(extensions_dir, "invalid-ext-1.0.0")) + File.open(File.join(extensions_dir, "invalid-ext-1.0.0", "gem.build_complete"), "w") {} + R + + run <<-R + puts "Success" + R + + expect(out).to eq("Success") + end + + it "ignores empty gem paths" do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + ENV["GEM_HOME"] = "" + bundle %(exec ruby -e "require 'set'") + + expect(err).to be_empty + end + + it "can require rubygems without warnings, when using a local cache", :truffleruby do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + bundle "package" + bundle %(exec ruby -w -e "require 'rubygems'") + + expect(err).to be_empty + end + + context "when the user has `MANPATH` set", :man do + before { ENV["MANPATH"] = "/foo#{File::PATH_SEPARATOR}" } + + it "adds the gem's man dir to the MANPATH" do + build_repo4 do + build_gem "with_man" do |s| + s.write("man/man1/page.1", "MANPAGE") + end + end + + install_gemfile <<-G + source "https://gem.repo4" + gem "with_man" + G + + run "puts ENV['MANPATH']" + expect(out).to eq("#{default_bundle_path("gems/with_man-1.0/man")}#{File::PATH_SEPARATOR}/foo") + end + end + + context "when the user does not have `MANPATH` set", :man do + before { ENV.delete("MANPATH") } + + it "adds the gem's man dir to the MANPATH, leaving : in the end so that system man pages still work" do + build_repo4 do + build_gem "with_man" do |s| + s.write("man/man1/page.1", "MANPAGE") + end + + build_gem "with_man_overriding_system_man" do |s| + s.write("man/man1/ls.1", "LS MANPAGE") + end + end + + install_gemfile <<-G + source "https://gem.repo4" + gem "with_man" + G + + run <<~RUBY + puts ENV['MANPATH'] + require "open3" + puts Open3.capture2e("man", "ls")[1].success? + RUBY + + expect(out).to eq("#{default_bundle_path("gems/with_man-1.0/man")}#{File::PATH_SEPARATOR}\ntrue") + + install_gemfile <<-G + source "https://gem.repo4" + gem "with_man_overriding_system_man" + G + + run <<~RUBY + puts ENV['MANPATH'] + require "open3" + puts Open3.capture2e({ "LC_ALL" => "C" }, "man", "ls")[0] + RUBY + + lines = out.split("\n") + + expect(lines).to include("#{default_bundle_path("gems/with_man_overriding_system_man-1.0/man")}#{File::PATH_SEPARATOR}") + expect(lines).to include("LS MANPAGE") + end + end + + it "should prepend gemspec require paths to $LOAD_PATH in order" do + update_repo2 do + build_gem("requirepaths") do |s| + s.write("lib/rq.rb", "puts 'yay'") + s.write("src/rq.rb", "puts 'nooo'") + s.require_paths = %w[lib src] + end + end + + install_gemfile <<-G + source "https://gem.repo2" + gem "requirepaths", :require => nil + G + + run "require 'rq'" + expect(out).to eq("yay") + end + + it "should clean $LOAD_PATH properly" do + gem_name = "very_simple_binary" + full_gem_name = gem_name + "-1.0" + + system_gems full_gem_name + + install_gemfile <<-G + source "https://gem.repo1" + G + + ruby <<-R + require 'bundler' + gem '#{gem_name}' + + puts $LOAD_PATH.count {|path| path =~ /#{gem_name}/} >= 2 + + Bundler.setup + + puts $LOAD_PATH.count {|path| path =~ /#{gem_name}/} == 0 + R + + expect(out).to eq("true\ntrue") + end + + context "with bundler is located in symlinked GEM_HOME" do + let(:gem_home) { Dir.mktmpdir } + let(:symlinked_gem_home) { tmp("gem_home-symlink").to_s } + let(:full_name) { "bundler-#{Bundler::VERSION}" } + + before do + File.symlink(gem_home, symlinked_gem_home) + gems_dir = File.join(gem_home, "gems") + specifications_dir = File.join(gem_home, "specifications") + Dir.mkdir(gems_dir) + Dir.mkdir(specifications_dir) + + File.symlink(source_root, File.join(gems_dir, full_name)) + + gemspec_content = File.binread(gemspec). + sub("Bundler::VERSION", %("#{Bundler::VERSION}")). + lines.reject {|line| line.include?("lib/bundler/version") }.join + + File.open(File.join(specifications_dir, "#{full_name}.gemspec"), "wb") do |f| + f.write(gemspec_content) + end + end + + it "should not remove itself from the LOAD_PATH and require a different copy of 'bundler/setup'" do + install_gemfile "source 'https://gem.repo1'" + + ruby <<-R, env: { "GEM_PATH" => symlinked_gem_home } + TracePoint.trace(:class) do |tp| + if tp.path.include?("bundler") && !tp.path.start_with?("#{source_root}") + puts "OMG. Defining a class from another bundler at \#{tp.path}:\#{tp.lineno}" + end + end + gem 'bundler', '#{Bundler::VERSION}' + require 'bundler/setup' + R + + expect(out).to be_empty + end + end + + it "does not reveal system gems even when Gem.refresh is called" do + system_gems "myrack-1.0.0" + + install_gemfile <<-G + source "https://gem.repo1" + gem "activesupport" + G + + run <<-R + puts Bundler.rubygems.installed_specs.map(&:name) + Gem.refresh + puts Bundler.rubygems.installed_specs.map(&:name) + R + + expect(out).to eq("activesupport\nbundler\nactivesupport\nbundler") + end + + describe "when a vendored gem specification uses the :path option" do + let(:filesystem_root) do + current = Pathname.new(Dir.pwd) + current = current.parent until current == current.parent + current + end + + it "should resolve paths relative to the Gemfile" do + path = bundled_app(File.join("vendor", "foo")) + build_lib "foo", path: path + + # If the .gemspec exists, then Bundler handles the path differently. + # See Source::Path.load_spec_files for details. + FileUtils.rm(File.join(path, "foo.gemspec")) + + install_gemfile <<-G + source "https://gem.repo1" + gem 'foo', '1.2.3', :path => 'vendor/foo' + G + + run <<-R, env: { "BUNDLE_GEMFILE" => bundled_app_gemfile.to_s }, dir: bundled_app.parent + require 'foo' + R + expect(err).to be_empty + end + + it "should make sure the Bundler.root is really included in the path relative to the Gemfile" do + relative_path = File.join("vendor", Dir.pwd.gsub(/^#{filesystem_root}/, "")) + absolute_path = bundled_app(relative_path) + FileUtils.mkdir_p(absolute_path) + build_lib "foo", path: absolute_path + + # If the .gemspec exists, then Bundler handles the path differently. + # See Source::Path.load_spec_files for details. + FileUtils.rm(File.join(absolute_path, "foo.gemspec")) + + gemfile <<-G + source "https://gem.repo1" + gem 'foo', '1.2.3', :path => '#{relative_path}' + G + + bundle :install + + run <<-R, env: { "BUNDLE_GEMFILE" => bundled_app_gemfile.to_s }, dir: bundled_app.parent + require 'foo' + R + + expect(err).to be_empty + end + end + + describe "with git gems that don't have gemspecs" do + before :each do + build_git "no_gemspec", gemspec: false + + install_gemfile <<-G + source "https://gem.repo1" + gem "no_gemspec", "1.0", :git => "#{lib_path("no_gemspec-1.0")}" + G + end + + it "loads the library via a virtual spec" do + run <<-R + require 'no_gemspec' + puts NO_GEMSPEC + R + + expect(out).to eq("1.0") + end + end + + describe "with bundled and system gems" do + before :each do + system_gems "myrack-1.0.0" + + install_gemfile <<-G + source "https://gem.repo1" + + gem "activesupport", "2.3.5" + G + end + + it "does not pull in system gems" do + run <<-R + begin; + require 'myrack' + rescue LoadError + puts 'WIN' + end + R + + expect(out).to eq("WIN") + end + + it "provides a gem method" do + run <<-R + gem 'activesupport' + require 'activesupport' + puts ACTIVESUPPORT + R + + expect(out).to eq("2.3.5") + end + + it "raises an exception if gem is used to invoke a system gem not in the bundle" do + run <<-R + begin + gem 'myrack' + rescue LoadError => e + puts e.message + end + R + + expect(out).to eq("myrack is not part of the bundle. Add it to your Gemfile.") + end + + it "sets GEM_HOME appropriately" do + run "puts ENV['GEM_HOME']" + expect(out).to eq(default_bundle_path.to_s) + end + end + + describe "with system gems in the bundle" do + before :each do + bundle_config "path.system true" + system_gems "myrack-1.0.0" + + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack", "1.0.0" + gem "activesupport", "2.3.5" + G + end + + it "sets GEM_PATH appropriately" do + run "puts Gem.path" + paths = out.split("\n") + expect(paths).to include(system_gem_path.to_s) + end + end + + describe "with a gemspec that requires other files" do + before :each do + build_git "bar", gemspec: false do |s| + s.write "lib/bar/version.rb", %(BAR_VERSION = '1.0') + s.write "bar.gemspec", <<-G + require_relative 'lib/bar/version' + + Gem::Specification.new do |s| + s.name = 'bar' + s.version = BAR_VERSION + s.summary = 'Bar' + s.files = Dir["lib/**/*.rb"] + s.author = 'no one' + end + G + end + + gemfile <<-G + source "https://gem.repo1" + gem "bar", :git => "#{lib_path("bar-1.0")}" + G + end + + it "evals each gemspec in the context of its parent directory" do + bundle :install + run "require 'bar'; puts BAR" + expect(out).to eq("1.0") + end + + it "error intelligently if the gemspec has a LoadError" do + skip "whitespace issue?" if Gem.win_platform? + + ref = update_git "bar", gemspec: false do |s| + s.write "bar.gemspec", "require 'foobarbaz'" + end.ref_for("HEAD") + bundle :install, raise_on_error: false + + expect(err.lines.map(&:chomp)).to include( + a_string_starting_with("[!] There was an error while loading `bar.gemspec`:"), + " # from #{default_bundle_path "bundler", "gems", "bar-1.0-#{ref[0, 12]}", "bar.gemspec"}:1", + " > require 'foobarbaz'" + ) + end + + it "evals each gemspec with a binding from the top level" do + bundle "install" + + ruby <<-RUBY + require 'bundler' + bundler_module = class << Bundler; self; end + bundler_module.send(:remove_method, :require) + def Bundler.require(path) + raise StandardError, "didn't use binding from top level" + end + Bundler.load + RUBY + + expect(err).to be_empty + expect(out).to be_empty + end + end + + describe "when Bundler is bundled" do + it "doesn't blow up" do + install_gemfile <<-G + source "https://gem.repo1" + gem "bundler", :path => "#{root}" + G + + bundle %(exec ruby -e "require 'bundler'; Bundler.setup") + expect(err).to be_empty + end + end + + describe "when BUNDLED WITH" do + def lock_with(bundler_version = nil) + lock = <<~L + GEM + remote: https://gem.repo1/ + specs: + myrack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack + L + + if bundler_version + lock += "\nBUNDLED WITH\n #{bundler_version}\n" + end + + lock + end + + before do + bundle_config "path.system true" + + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + end + + context "is not present" do + it "does not change the lock" do + lockfile lock_with(nil) + ruby "require 'bundler/setup'" + expect(lockfile).to eq lock_with(nil) + end + end + + context "is newer" do + it "does not change the lock or warn" do + lockfile lock_with(Bundler::VERSION.succ) + ruby "require 'bundler/setup'" + expect(out).to be_empty + expect(err).to be_empty + expect(lockfile).to eq lock_with(Bundler::VERSION.succ) + end + end + + context "is older" do + it "does not change the lock" do + system_gems "bundler-1.10.1" + lockfile lock_with("1.10.1") + ruby "require 'bundler/setup'" + expect(lockfile).to eq lock_with("1.10.1") + end + end + end + + describe "when RUBY VERSION" do + let(:ruby_version) { nil } + + def lock_with(ruby_version = nil) + checksums = checksums_section do |c| + c.checksum gem_repo1, "myrack", "1.0.0" + end + + lock = <<~L + GEM + remote: https://gem.repo1/ + specs: + myrack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack + #{checksums} + L + + if ruby_version + lock += "\nRUBY VERSION\n ruby #{ruby_version}\n" + end + + lock += <<~L + + BUNDLED WITH + #{Bundler::VERSION} + L + + lock + end + + before do + install_gemfile <<-G + ruby ">= 0" + source "https://gem.repo1" + gem "myrack" + G + lockfile lock_with(ruby_version) + end + + context "is not present" do + # Skipped on ruby-core because `ruby "require 'bundler/setup'"` does not + # activate bundler as a gem there, so Source::Metadata falls back to a + # synthetic spec whose cache_file does not exist on disk and + # LockfileGenerator#bundler_checksum drops the bundler checksum, while + # the on-disk lockfile still has it. + it "does not change the lock", :ruby_repo do + expect { ruby "require 'bundler/setup'" }.not_to change { lockfile } + end + end + + context "is newer" do + let(:ruby_version) { "5.5.5" } + it "does not change the lock or warn" do + expect { ruby "require 'bundler/setup'" }.not_to change { lockfile } + expect(out).to be_empty + expect(err).to be_empty + end + end + + context "is older" do + let(:ruby_version) { "1.0.0" } + it "does not change the lock" do + expect { ruby "require 'bundler/setup'" }.not_to change { lockfile } + end + end + end + + describe "with gemified standard libraries" do + it "does not load Digest", :ruby_repo do + build_git "bar", gemspec: false do |s| + s.write "lib/bar/version.rb", %(BAR_VERSION = '1.0') + s.write "bar.gemspec", <<-G + require_relative 'lib/bar/version' + + Gem::Specification.new do |s| + s.name = 'bar' + s.version = BAR_VERSION + s.summary = 'Bar' + s.files = Dir["lib/**/*.rb"] + s.author = 'no one' + + s.add_dependency 'digest' + end + G + end + + gemfile <<-G + source "https://gem.repo1" + gem "bar", :git => "#{lib_path("bar-1.0")}" + G + + bundle :install, env: { "BUNDLE_LOCKFILE_CHECKSUMS" => "false" } + + ruby <<-RUBY, artifice: nil + require 'bundler/setup' + puts defined?(::Digest) ? "Digest defined" : "Digest undefined" + require 'digest' + RUBY + expect(out).to eq("Digest undefined") + end + + it "does not load Psych" do + gemfile "source 'https://gem.repo1'" + ruby <<-RUBY + require 'bundler/setup' + puts defined?(Psych::VERSION) ? Psych::VERSION : "undefined" + require 'psych' + puts Psych::VERSION + RUBY + pre_bundler, post_bundler = out.split("\n") + expect(pre_bundler).to eq("undefined") + expect(post_bundler).to match(/\d+\.\d+\.\d+/) + end + + it "does not load openssl" do + install_gemfile "source 'https://gem.repo1'" + ruby <<-RUBY, artifice: nil + require "bundler/setup" + puts defined?(OpenSSL) || "undefined" + require "openssl" + puts defined?(OpenSSL) || "undefined" + RUBY + expect(out).to eq("undefined\nconstant") + end + + it "does not load uri while reading gemspecs", rubygems: ">= 3.6.0.dev" do + Dir.mkdir bundled_app("test") + + create_file(bundled_app("test/test.gemspec"), <<-G) + Gem::Specification.new do |s| + s.name = "test" + s.version = "1.0.0" + s.summary = "test" + s.authors = ['John Doe'] + s.homepage = 'https://example.com' + end + G + + install_gemfile <<-G + source "https://gem.repo1" + gem "test", path: "#{bundled_app("test")}" + G + + ruby <<-RUBY, artifice: nil + require "bundler/setup" + puts defined?(URI) || "undefined" + require "uri" + puts defined?(URI) || "undefined" + RUBY + expect(out).to eq("undefined\nconstant") + end + + it "activates default gems when they are part of the bundle, but not installed explicitly", :ruby_repo do + default_delegate_version = ruby "gem 'delegate'; require 'delegate'; puts Delegator::VERSION" + + build_repo2 do + build_gem "delegate", default_delegate_version + end + + gemfile "source \"https://gem.repo2\"; gem 'delegate'" + + ruby <<-RUBY + require "bundler/setup" + require "delegate" + puts defined?(::Delegator) ? "Delegator defined" : "Delegator undefined" + RUBY + + expect(out).to eq("Delegator defined") + expect(err).to be_empty + end + + describe "default gem activation" do + let(:exemptions) do + exempts = %w[did_you_mean bundler uri pathname] + exempts << "error_highlight" # added in Ruby 3.1 as a default gem + exempts << "ruby2_keywords" # added in Ruby 3.1 as a default gem + exempts << "syntax_suggest" # added in Ruby 3.2 as a default gem + exempts + end + + let(:activation_warning_hack) { <<~RUBY } + require #{spec_dir.join("support/hax").to_s.dump} + + Gem::Specification.send(:alias_method, :bundler_spec_activate, :activate) + Gem::Specification.send(:define_method, :activate) do + unless #{exemptions.inspect}.include?(name) + warn '-' * 80 + warn "activating \#{full_name}" + warn(*caller) + warn '*' * 80 + end + bundler_spec_activate + end + RUBY + + let(:activation_warning_hack_rubyopt) do + create_file("activation_warning_hack.rb", activation_warning_hack) + "-r#{bundled_app("activation_warning_hack.rb")} #{ENV["RUBYOPT"]}" + end + + let(:code) { <<~RUBY } + require "pp" + loaded_specs = Gem.loaded_specs.dup + #{exemptions.inspect}.each {|s| loaded_specs.delete(s) } + pp loaded_specs + + # not a default gem, but harmful to have loaded + open_uri = $LOADED_FEATURES.grep(/open.uri/) + unless open_uri.empty? + warn "open_uri: \#{open_uri}" + end + RUBY + + it "activates no gems with -rbundler/setup" do + install_gemfile "source 'https://gem.repo1'" + ruby code, env: { "RUBYOPT" => activation_warning_hack_rubyopt + " -rbundler/setup" }, artifice: nil + expect(out).to eq("{}") + end + + it "activates no gems with bundle exec" do + install_gemfile "source 'https://gem.repo1'" + create_file("script.rb", code) + bundle "exec ruby ./script.rb", env: { "RUBYOPT" => activation_warning_hack_rubyopt } + expect(out).to eq("{}") + end + + it "activates no gems with bundle exec that is loaded" do + skip "not executable" if Gem.win_platform? + + install_gemfile "source 'https://gem.repo1'" + create_file("script.rb", "#!/usr/bin/env ruby\n\n#{code}") + FileUtils.chmod(0o777, bundled_app("script.rb")) + bundle "exec ./script.rb", env: { "RUBYOPT" => activation_warning_hack_rubyopt } + expect(out).to eq("{}") + end + + it "does not load net-http-pipeline too early" do + build_repo4 do + build_gem "net-http-pipeline", "1.0.1" + end + + system_gems "net-http-pipeline-1.0.1", gem_repo: gem_repo4 + + gemfile <<-G + source "https://gem.repo4" + gem "net-http-pipeline", "1.0.1" + G + + bundle_config "path vendor/bundle" + + bundle :install + + bundle :check + + expect(out).to eq("The Gemfile's dependencies are satisfied") + end + + Gem::Specification.select(&:default_gem?).map(&:name).each do |g| + it "activates newer versions of #{g}", :ruby_repo do + skip if exemptions.include?(g) + + build_repo4 do + build_gem g, "999999" + end + + install_gemfile <<-G + source "https://gem.repo4" + gem "#{g}", "999999" + G + + expect(the_bundle).to include_gem("#{g} 999999", env: { "RUBYOPT" => activation_warning_hack_rubyopt }, artifice: nil) + end + + it "activates older versions of #{g}", :ruby_repo do + skip if exemptions.include?(g) + + build_repo4 do + build_gem g, "0.0.0.a" + end + + install_gemfile <<-G + source "https://gem.repo4" + gem "#{g}", "0.0.0.a" + G + + expect(the_bundle).to include_gem("#{g} 0.0.0.a", env: { "RUBYOPT" => activation_warning_hack_rubyopt }, artifice: nil) + end + end + end + end + + describe "after setup" do + it "keeps Kernel#gem private" do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + ruby <<-RUBY, raise_on_error: false + require "bundler/setup" + Object.new.gem "myrack" + puts "FAIL" + RUBY + + expect(stdboth).not_to include "FAIL" + expect(err).to match(/private method [`']gem'/) + end + + it "keeps Kernel#require private" do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + ruby <<-RUBY, raise_on_error: false + require "bundler/setup" + Object.new.require "myrack" + puts "FAIL" + RUBY + + expect(stdboth).not_to include "FAIL" + expect(err).to match(/private method [`']require'/) + end + + it "memoizes initial set of specs when requiring bundler/setup, so that even if further code mutates dependencies, Bundler.definition.specs is not affected" do + install_gemfile <<~G + source "https://gem.repo1" + gem "yard" + gem "myrack", :group => :test + G + + ruby <<-RUBY, raise_on_error: false + require "bundler/setup" + Bundler.require(:test).select! {|d| (d.groups & [:test]).any? } + puts Bundler.definition.specs.map(&:name).join(", ") + RUBY + + expect(out).to include("myrack, yard") + end + + it "does not cause double loads when higher versions of default gems are activated before bundler" do + build_repo2 do + build_gem "json", "999.999.999" do |s| + s.write "lib/json.rb", <<~RUBY + module JSON + VERSION = "999.999.999" + end + RUBY + end + end + + system_gems "json-999.999.999", gem_repo: gem_repo2 + + install_gemfile "source 'https://gem.repo1'" + ruby <<-RUBY + require "json" + require "bundler/setup" + require "json" + RUBY + + expect(err).to be_empty + end + end + + it "does not undo the Kernel.require decorations", rubygems: ">= 3.4.6" do + install_gemfile "source 'https://gem.repo1'" + script = bundled_app("bin/script") + create_file(script, <<~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 + + require "bundler/setup" + + require "foo" + RUBY + + sys_exec "#{Gem.ruby} #{script}", raise_on_error: false + expect(out).to include("requiring foo used the monkeypatch") + end + + it "performs an automatic bundle install" do + build_repo4 do + build_gem "myrack", "1.0.0" + end + + gemfile <<-G + source "https://gem.repo1" + gem "myrack", :group => :test + G + + bundle_config "auto_install 1" + + ruby <<-RUBY, artifice: "compact_index" + require 'bundler/setup' + RUBY + expect(err).to be_empty + expect(out).to include("Installing myrack 1.0.0") + end + + context "in a read-only filesystem" do + before do + gemfile <<-G + source "https://gem.repo4" + G + + lockfile <<-L + GEM + remote: https://gem.repo4/ + + PLATFORMS + x86_64-darwin-19 + + DEPENDENCIES + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "should fail loudly if the lockfile platforms don't include the current platform" do + simulate_platform "x86_64-linux" do + ruby <<-RUBY, raise_on_error: false, env: { "BUNDLER_SPEC_READ_ONLY" => "true", "BUNDLER_FORCE_TTY" => "true" } + require "bundler/setup" + RUBY + end + + expect(err).to include("Your lockfile is missing the current platform, but can't be updated because file system is read-only") + end + end +end |
