# frozen_string_literal: true RSpec.describe "bundle install with gem sources" do describe "the simple case" do it "prints output and returns if no dependencies are specified" do gemfile <<-G source "#{file_uri_for(gem_repo1)}" G bundle :install expect(err).to match(/no dependencies/) end it "does not make a lockfile if the install fails" do install_gemfile <<-G, raise_on_error: false raise StandardError, "FAIL" G expect(err).to include('StandardError, "FAIL"') expect(bundled_app_lock).not_to exist end it "creates a Gemfile.lock" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" G expect(bundled_app_lock).to exist end it "does not create ./.bundle by default", bundler: "< 3" do gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" G bundle :install # can't use install_gemfile since it sets retry expect(bundled_app(".bundle")).not_to exist end it "does not create ./.bundle by default when installing to system gems" do gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" G bundle :install, env: { "BUNDLE_PATH__SYSTEM" => "true" } # can't use install_gemfile since it sets retry expect(bundled_app(".bundle")).not_to exist end it "creates lock files based on the Gemfile name" do gemfile bundled_app("OmgFile"), <<-G source "#{file_uri_for(gem_repo1)}" gem "rack", "1.0" G bundle "install --gemfile OmgFile" expect(bundled_app("OmgFile.lock")).to exist end it "doesn't delete the lockfile if one already exists" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem 'rack' G lockfile = File.read(bundled_app_lock) install_gemfile <<-G, raise_on_error: false raise StandardError, "FAIL" G expect(File.read(bundled_app_lock)).to eq(lockfile) end it "does not touch the lockfile if nothing changed" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" G expect { run "1" }.not_to change { File.mtime(bundled_app_lock) } end it "fetches gems" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem 'rack' G expect(default_bundle_path("gems/rack-1.0.0")).to exist expect(the_bundle).to include_gems("rack 1.0.0") end it "auto-heals missing gems" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem 'rack' G FileUtils.rm_rf(default_bundle_path("gems/rack-1.0.0")) bundle "install --verbose" expect(out).to include("Installing rack 1.0.0") expect(default_bundle_path("gems/rack-1.0.0")).to exist expect(the_bundle).to include_gems("rack 1.0.0") end it "fetches gems when multiple versions are specified" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem 'rack', "> 0.9", "< 1.0" G expect(default_bundle_path("gems/rack-0.9.1")).to exist expect(the_bundle).to include_gems("rack 0.9.1") end it "fetches gems when multiple versions are specified take 2" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem 'rack', "< 1.0", "> 0.9" G expect(default_bundle_path("gems/rack-0.9.1")).to exist expect(the_bundle).to include_gems("rack 0.9.1") end it "raises an appropriate error when gems are specified using symbols" do install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem :rack G expect(exitstatus).to eq(4) end it "pulls in dependencies" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rails" G expect(the_bundle).to include_gems "actionpack 2.3.2", "rails 2.3.2" end it "does the right version" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack", "0.9.1" G expect(the_bundle).to include_gems "rack 0.9.1" end it "does not install the development dependency" do build_repo2 do build_gem "with_development_dependency" do |s| s.add_development_dependency "activesupport", "= 2.3.5" end end install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "with_development_dependency" G expect(the_bundle).to include_gems("with_development_dependency 1.0.0"). and not_include_gems("activesupport 2.3.5") end it "resolves correctly" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "activemerchant" gem "rails" G expect(the_bundle).to include_gems "activemerchant 1.0", "activesupport 2.3.2", "actionpack 2.3.2" end it "activates gem correctly according to the resolved gems" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "activesupport", "2.3.5" G install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "activemerchant" gem "rails" G expect(the_bundle).to include_gems "activemerchant 1.0", "activesupport 2.3.2", "actionpack 2.3.2" end it "does not reinstall any gem that is already available locally" do system_gems "activesupport-2.3.2", path: default_bundle_path build_repo2 do build_gem "activesupport", "2.3.2" do |s| s.write "lib/activesupport.rb", "ACTIVESUPPORT = 'fail'" end end install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "activerecord", "2.3.2" G expect(the_bundle).to include_gems "activesupport 2.3.2" end it "works when the gemfile specifies gems that only exist in the system" do build_gem "foo", to_bundle: true install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" gem "foo" G expect(the_bundle).to include_gems "rack 1.0.0", "foo 1.0.0" end it "prioritizes local gems over remote gems" do build_gem "rack", "1.0.0", to_bundle: true do |s| s.add_dependency "activesupport", "2.3.5" end install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" G expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.5" end it "loads env plugins" do plugin_msg = "hello from an env plugin!" create_file "plugins/rubygems_plugin.rb", "puts '#{plugin_msg}'" rubylib = ENV["RUBYLIB"].to_s.split(File::PATH_SEPARATOR).unshift(bundled_app("plugins").to_s).join(File::PATH_SEPARATOR) install_gemfile <<-G, env: { "RUBYLIB" => rubylib } source "#{file_uri_for(gem_repo1)}" gem "rack" G expect(last_command.stdboth).to include(plugin_msg) end describe "with a gem that installs multiple platforms" do it "installs gems for the local platform as first choice" do skip "version is 1.0, not 1.0.0" if Gem.win_platform? install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "platform_specific" G run "require 'platform_specific' ; puts PLATFORM_SPECIFIC" expect(out).to eq("1.0.0 #{Bundler.local_platform}") end it "falls back on plain ruby" do simulate_platform "foo-bar-baz" install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "platform_specific" G run "require 'platform_specific' ; puts PLATFORM_SPECIFIC" expect(out).to eq("1.0.0 RUBY") end it "installs gems for java" do simulate_platform "java" install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "platform_specific" G run "require 'platform_specific' ; puts PLATFORM_SPECIFIC" expect(out).to eq("1.0.0 JAVA") end it "installs gems for windows" do simulate_platform x86_mswin32 install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "platform_specific" G run "require 'platform_specific' ; puts PLATFORM_SPECIFIC" expect(out).to eq("1.0 x86-mswin32") end end describe "doing bundle install foo" do before do gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" G end it "works" do bundle "config set --local path vendor" bundle "install" expect(the_bundle).to include_gems "rack 1.0" end it "allows running bundle install --system without deleting foo", bundler: "< 3" do bundle "install --path vendor" bundle "install --system" FileUtils.rm_rf(bundled_app("vendor")) expect(the_bundle).to include_gems "rack 1.0" end it "allows running bundle install --system after deleting foo", bundler: "< 3" do bundle "install --path vendor" FileUtils.rm_rf(bundled_app("vendor")) bundle "install --system" expect(the_bundle).to include_gems "rack 1.0" end end it "finds gems in multiple sources", bundler: "< 3" do build_repo2 do build_gem "rack", "1.2" do |s| s.executables = "rackup" end end install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" source "#{file_uri_for(gem_repo2)}" gem "activesupport", "1.2.3" gem "rack", "1.2" G expect(the_bundle).to include_gems "rack 1.2", "activesupport 1.2.3" end it "gives a useful error if no sources are set" do install_gemfile <<-G, raise_on_error: false gem "rack" G expect(err).to include("This Gemfile does not include an explicit global source. " \ "Not using an explicit global source may result in a different lockfile being generated depending on " \ "the gems you have installed locally before bundler is run. " \ "Instead, define a global source in your Gemfile like this: source \"https://rubygems.org\".") end it "creates a Gemfile.lock on a blank Gemfile" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" G expect(File.exist?(bundled_app_lock)).to eq(true) end it "throws a warning if a gem is added twice in Gemfile without version requirements" do build_repo2 install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "rack" gem "rack" G expect(err).to include("Your Gemfile lists the gem rack (>= 0) more than once.") expect(err).to include("Remove any duplicate entries and specify the gem only once.") expect(err).to include("While it's not a problem now, it could cause errors if you change the version of one of them later.") end it "throws a warning if a gem is added twice in Gemfile with same versions" do build_repo2 install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "rack", "1.0" gem "rack", "1.0" G expect(err).to include("Your Gemfile lists the gem rack (= 1.0) more than once.") expect(err).to include("Remove any duplicate entries and specify the gem only once.") expect(err).to include("While it's not a problem now, it could cause errors if you change the version of one of them later.") end it "throws a warning if a gem is added twice under different platforms and does not crash when using the generated lockfile" do build_repo2 install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "rack", :platform => :jruby gem "rack" G bundle "install" expect(err).to include("Your Gemfile lists the gem rack (>= 0) more than once.") expect(err).to include("Remove any duplicate entries and specify the gem only once.") expect(err).to include("While it's not a problem now, it could cause errors if you change the version of one of them later.") end it "does not throw a warning if a gem is added once in Gemfile and also inside a gemspec as a development dependency" do build_lib "my-gem", path: bundled_app do |s| s.add_development_dependency "my-private-gem" end build_repo2 do build_gem "my-private-gem" end gemfile <<~G source "#{file_uri_for(gem_repo2)}" gemspec gem "my-private-gem", :group => :development G bundle :install expect(err).to be_empty expect(the_bundle).to include_gems("my-private-gem 1.0") end it "throws a warning if a gem is added once in Gemfile and also inside a gemspec as a development dependency, with different requirements" do build_lib "my-gem", path: bundled_app do |s| s.add_development_dependency "rubocop", "~> 1.36.0" end build_repo4 do build_gem "rubocop", "1.36.0" build_gem "rubocop", "1.37.1" end gemfile <<~G source "#{file_uri_for(gem_repo4)}" gemspec gem "rubocop", group: :development G bundle :install expect(err).to include("A gemspec development dependency (rubocop, ~> 1.36.0) is being overridden by a Gemfile dependency (rubocop, >= 0).") expect(err).to include("This behaviour may change in the future. Please remove either of them, or make sure they both have the same requirement") # This is not the best behavior I believe, it would be better if both # requirements are considered if they are compatible, and a version # satisfying both is chosen. But not sure about changing it right now, so # I went with a warning for the time being. expect(the_bundle).to include_gems("rubocop 1.37.1") end it "includes the gem without warning if two gemspecs add it with the same requirement" do gem1 = tmp.join("my-gem-1") gem2 = tmp.join("my-gem-2") build_lib "my-gem", path: gem1 do |s| s.add_development_dependency "rubocop", "~> 1.36.0" end build_lib "my-gem-2", path: gem2 do |s| s.add_development_dependency "rubocop", "~> 1.36.0" end build_repo4 do build_gem "rubocop", "1.36.0" end gemfile <<~G source "#{file_uri_for(gem_repo4)}" gemspec path: "#{gem1}" gemspec path: "#{gem2}" G bundle :install expect(err).to be_empty expect(the_bundle).to include_gems("rubocop 1.36.0") end it "warns when a Gemfile dependency is overriding a gemspec development dependency, with different requirements" do build_lib "my-gem", path: bundled_app do |s| s.add_development_dependency "rails", ">= 5" end build_repo4 do build_gem "rails", "7.0.8" end gemfile <<~G source "#{file_uri_for(gem_repo4)}" gem "rails", "~> 7.0.8" gemspec G bundle :install expect(err).to include("A gemspec development dependency (rails, >= 5) is being overridden by a Gemfile dependency (rails, ~> 7.0.8).") expect(err).to include("This behaviour may change in the future. Please remove either of them, or make sure they both have the same requirement") # This is not the best behavior I believe, it would be better if both # requirements are considered if they are compatible, and a version # satisfying both is chosen. But not sure about changing it right now, so # I went with a warning for the time being. expect(the_bundle).to include_gems("rails 7.0.8") end it "does not warn if a gem is added once in Gemfile and also inside a gemspec as a development dependency, with same requirements, and different sources" do build_lib "my-gem", path: bundled_app do |s| s.add_development_dependency "activesupport" end build_repo4 do build_gem "activesupport" end build_git "activesupport", "1.0", path: lib_path("activesupport") install_gemfile <<~G source "#{file_uri_for(gem_repo4)}" gemspec gem "activesupport", :git => "#{file_uri_for(lib_path("activesupport"))}" G expect(err).to be_empty expect(the_bundle).to include_gems "activesupport 1.0", source: "git@#{lib_path("activesupport")}" # if the Gemfile dependency is specified first install_gemfile <<~G source "#{file_uri_for(gem_repo4)}" gem "activesupport", :git => "#{file_uri_for(lib_path("activesupport"))}" gemspec G expect(err).to be_empty expect(the_bundle).to include_gems "activesupport 1.0", source: "git@#{lib_path("activesupport")}" end it "considers both dependencies for resolution if a gem is added once in Gemfile and also inside a local gemspec as a runtime dependency, with different requirements" do build_lib "my-gem", path: bundled_app do |s| s.add_dependency "rubocop", "~> 1.36.0" end build_repo4 do build_gem "rubocop", "1.36.0" build_gem "rubocop", "1.37.1" end gemfile <<~G source "#{file_uri_for(gem_repo4)}" gemspec gem "rubocop" G bundle :install expect(err).to be_empty expect(the_bundle).to include_gems("rubocop 1.36.0") end it "throws an error if a gem is added twice in Gemfile when version of one dependency is not specified" do install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}" gem "rack" gem "rack", "1.0" G expect(err).to include("You cannot specify the same gem twice with different version requirements") expect(err).to include("You specified: rack (>= 0) and rack (= 1.0).") end it "throws an error if a gem is added twice in Gemfile when different versions of both dependencies are specified" do install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}" gem "rack", "1.0" gem "rack", "1.1" G expect(err).to include("You cannot specify the same gem twice with different version requirements") expect(err).to include("You specified: rack (= 1.0) and rack (= 1.1).") end it "gracefully handles error when rubygems server is unavailable" do skip "networking issue" if Gem.win_platform? install_gemfile <<-G, artifice: nil, raise_on_error: false source "#{file_uri_for(gem_repo1)}" source "http://0.0.0.0:9384" do gem 'foo' end G expect(err).to include("Could not fetch specs from http://0.0.0.0:9384/") expect(err).not_to include("file://") end it "fails gracefully when downloading an invalid specification from the full index" do build_repo2(build_compact_index: false) do build_gem "ajp-rails", "0.0.0", gemspec: false, skip_validation: true do |s| bad_deps = [["ruby-ajp", ">= 0.2.0"], ["rails", ">= 0.14"]] s. instance_variable_get(:@spec). instance_variable_set(:@dependencies, bad_deps) raise "failed to set bad deps" unless s.dependencies == bad_deps end build_gem "ruby-ajp", "1.0.0" end install_gemfile <<-G, full_index: true, raise_on_error: false source "#{file_uri_for(gem_repo2)}" gem "ajp-rails", "0.0.0" G expect(last_command.stdboth).not_to match(/Error Report/i) expect(err).to include("An error occurred while installing ajp-rails (0.0.0), and Bundler cannot continue."). and include("Bundler::APIResponseInvalidDependenciesError") end it "doesn't blow up when the local .bundle/config is empty" do FileUtils.mkdir_p(bundled_app(".bundle")) FileUtils.touch(bundled_app(".bundle/config")) install_gemfile(<<-G) source "#{file_uri_for(gem_repo1)}" gem 'foo' G end it "doesn't blow up when the global .bundle/config is empty" do FileUtils.mkdir_p("#{Bundler.rubygems.user_home}/.bundle") FileUtils.touch("#{Bundler.rubygems.user_home}/.bundle/config") install_gemfile(<<-G) source "#{file_uri_for(gem_repo1)}" gem 'foo' G end end describe "Ruby version in Gemfile.lock" do context "and using an unsupported Ruby version" do it "prints an error" do install_gemfile <<-G, raise_on_error: false ruby '~> 1.2' source "#{file_uri_for(gem_repo1)}" G expect(err).to include("Your Ruby version is #{Gem.ruby_version}, but your Gemfile specified ~> 1.2") end end context "and using a supported Ruby version" do before do install_gemfile <<-G ruby '~> #{Gem.ruby_version}' source "#{file_uri_for(gem_repo1)}" G end it "writes current Ruby version to Gemfile.lock" do checksums = checksums_section_when_existing expect(lockfile).to eq <<~L GEM remote: #{file_uri_for(gem_repo1)}/ specs: PLATFORMS #{lockfile_platforms} DEPENDENCIES #{checksums} RUBY VERSION #{Bundler::RubyVersion.system} BUNDLED WITH #{Bundler::VERSION} L end it "updates Gemfile.lock with updated yet still compatible ruby version" do install_gemfile <<-G ruby '~> #{current_ruby_minor}' source "#{file_uri_for(gem_repo1)}" G checksums = checksums_section_when_existing expect(lockfile).to eq <<~L GEM remote: #{file_uri_for(gem_repo1)}/ specs: PLATFORMS #{lockfile_platforms} DEPENDENCIES #{checksums} RUBY VERSION #{Bundler::RubyVersion.system} BUNDLED WITH #{Bundler::VERSION} L end it "does not crash when unlocking" do gemfile <<-G source "#{file_uri_for(gem_repo1)}" ruby '>= 2.1.0' G bundle "update" expect(err).not_to include("Could not find gem 'Ruby") end end end describe "when Bundler root contains regex chars" do it "doesn't blow up when using the `gem` DSL" do root_dir = tmp("foo[]bar") FileUtils.mkdir_p(root_dir) build_lib "foo" gemfile = <<-G source "#{file_uri_for(gem_repo1)}" gem 'foo', :path => "#{lib_path("foo-1.0")}" G File.open("#{root_dir}/Gemfile", "w") do |file| file.puts gemfile end bundle :install, dir: root_dir end it "doesn't blow up when using the `gemspec` DSL" do root_dir = tmp("foo[]bar") FileUtils.mkdir_p(root_dir) build_lib "foo", path: root_dir gemfile = <<-G source "#{file_uri_for(gem_repo1)}" gemspec G File.open("#{root_dir}/Gemfile", "w") do |file| file.puts gemfile end bundle :install, dir: root_dir end end describe "when requesting a quiet install via --quiet" do it "should be quiet if there are no warnings" do bundle "config set force_ruby_platform true" gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem 'rack' G bundle :install, quiet: true expect(out).to be_empty expect(err).to be_empty end it "should still display warnings and errors" do bundle "config set force_ruby_platform true" create_file("install_with_warning.rb", <<~RUBY) require "#{lib_dir}/bundler" require "#{lib_dir}/bundler/cli" require "#{lib_dir}/bundler/cli/install" module RunWithWarning def run super rescue Bundler.ui.warn "BOOOOO" raise end end Bundler::CLI::Install.prepend(RunWithWarning) RUBY gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem 'non-existing-gem' G bundle :install, quiet: true, raise_on_error: false, env: { "RUBYOPT" => "-r#{bundled_app("install_with_warning.rb")}" } expect(out).to be_empty expect(err).to include("Could not find gem 'non-existing-gem'") expect(err).to include("BOOOOO") end end describe "when bundle path does not have write access", :permissions do let(:bundle_path) { bundled_app("vendor") } before do FileUtils.mkdir_p(bundle_path) gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem 'rack' G end it "should display a proper message to explain the problem" do FileUtils.chmod(0o500, bundle_path) bundle "config set --local path vendor" bundle :install, raise_on_error: false expect(err).to include(bundle_path.to_s) expect(err).to include("grant write permissions") end end describe "when bundle gems path does not have write access", :permissions do let(:gems_path) { bundled_app("vendor/#{Bundler.ruby_scope}/gems") } before do FileUtils.mkdir_p(gems_path) gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem 'rack' G end it "should display a proper message to explain the problem" do FileUtils.chmod("-x", gems_path) bundle "config set --local path vendor" begin bundle :install, raise_on_error: false ensure FileUtils.chmod("+x", gems_path) end expect(err).not_to include("ERROR REPORT TEMPLATE") expect(err).to include( "There was an error while trying to create `#{gems_path.join("rack-1.0.0")}`. " \ "It is likely that you need to grant executable permissions for all parent directories and write permissions for `#{gems_path}`." ) end end describe "when bundle extensions path does not have write access", :permissions do let(:extensions_path) { bundled_app("vendor/#{Bundler.ruby_scope}/extensions/#{Gem::Platform.local}/#{Gem.extension_api_version}") } before do FileUtils.mkdir_p(extensions_path) gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem 'simple_binary' G end it "should display a proper message to explain the problem" do FileUtils.chmod("-x", extensions_path) bundle "config set --local path vendor" begin bundle :install, raise_on_error: false ensure FileUtils.chmod("+x", extensions_path) end expect(err).not_to include("ERROR REPORT TEMPLATE") expect(err).to include( "There was an error while trying to create `#{extensions_path.join("simple_binary-1.0")}`. " \ "It is likely that you need to grant executable permissions for all parent directories and write permissions for `#{extensions_path}`." ) end end describe "when the path of a specific gem is not writable", :permissions do let(:gems_path) { bundled_app("vendor/#{Bundler.ruby_scope}/gems") } let(:foo_path) { gems_path.join("foo-1.0.0") } before do build_repo4 do build_gem "foo", "1.0.0" do |s| s.write "CHANGELOG.md", "foo" end end gemfile <<-G source "#{file_uri_for(gem_repo4)}" gem 'foo' G end it "should display a proper message to explain the problem" do bundle "config set --local path vendor" bundle :install expect(out).to include("Bundle complete!") expect(err).to be_empty FileUtils.chmod("-x", foo_path) begin bundle "install --redownload", raise_on_error: false ensure FileUtils.chmod("+x", foo_path) end expect(err).not_to include("ERROR REPORT TEMPLATE") expect(err).to include("Could not delete previous installation of `#{foo_path}`.") expect(err).to include("The underlying error was Errno::EACCES") end end describe "when gem home does not have the writable bit set, yet it's still writable", :permissions do let(:gem_home) { bundled_app("vendor/#{Bundler.ruby_scope}") } before do build_repo4 do build_gem "foo", "1.0.0" do |s| s.write "CHANGELOG.md", "foo" end end gemfile <<-G source "#{file_uri_for(gem_repo4)}" gem 'foo' G end it "should display a proper message to explain the problem" do bundle "config set --local path vendor" bundle :install expect(out).to include("Bundle complete!") expect(err).to be_empty FileUtils.chmod("-w", gem_home) begin bundle "install --redownload" ensure FileUtils.chmod("+w", gem_home) end expect(out).to include("Bundle complete!") expect(err).to be_empty end end describe "when gems path is world writable (no sticky bit set)", :permissions do let(:gems_path) { bundled_app("vendor/#{Bundler.ruby_scope}/gems") } before do build_repo4 do build_gem "foo", "1.0.0" do |s| s.write "CHANGELOG.md", "foo" end end gemfile <<-G source "#{file_uri_for(gem_repo4)}" gem 'foo' G end it "should display a proper message to explain the problem" do bundle "config set --local path vendor" bundle :install expect(out).to include("Bundle complete!") expect(err).to be_empty FileUtils.chmod(0o777, gems_path) bundle "install --redownload", raise_on_error: false expect(err).to include("The installation path is insecure. Bundler cannot continue.") end end describe "when bundle cache path does not have write access", :permissions do let(:cache_path) { bundled_app("vendor/#{Bundler.ruby_scope}/cache") } before do FileUtils.mkdir_p(cache_path) gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem 'rack' G end it "should display a proper message to explain the problem" do FileUtils.chmod(0o500, cache_path) bundle "config set --local path vendor" bundle :install, raise_on_error: false expect(err).to include(cache_path.to_s) expect(err).to include("grant write permissions") end end context "after installing with --standalone" do before do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" G bundle "config set --local path bundle" bundle "install", standalone: true end it "includes the standalone path" do bundle "binstubs rack", standalone: true standalone_line = File.read(bundled_app("bin/rackup")).each_line.find {|line| line.include? "$:.unshift" }.strip expect(standalone_line).to eq %($:.unshift File.expand_path "../bundle", __dir__) end end describe "when bundle install is executed with unencoded authentication" do before do gemfile <<-G source 'https://rubygems.org/' gem "." G end it "should display a helpful message explaining how to fix it" do bundle :install, env: { "BUNDLE_RUBYGEMS__ORG" => "user:pass{word" }, raise_on_error: false expect(exitstatus).to eq(17) expect(err).to eq("Please CGI escape your usernames and passwords before " \ "setting them for authentication.") end end context "in a frozen bundle" do before do build_repo4 do build_gem "libv8", "8.4.255.0" do |s| s.platform = "x86_64-darwin-19" end end gemfile <<-G source "#{file_uri_for(gem_repo4)}" gem "libv8" G lockfile <<-L GEM remote: #{file_uri_for(gem_repo4)}/ specs: libv8 (8.4.255.0-x86_64-darwin-19) PLATFORMS x86_64-darwin-19 DEPENDENCIES libv8 BUNDLED WITH #{Bundler::VERSION} L bundle "config set --local deployment true" end it "should fail loudly if the lockfile platforms don't include the current platform" do simulate_platform(Gem::Platform.new("x86_64-linux")) { bundle "install", raise_on_error: false } expect(err).to eq( "Your bundle only supports platforms [\"x86_64-darwin-19\"] but your local platform is x86_64-linux. " \ "Add the current platform to the lockfile with\n`bundle lock --add-platform x86_64-linux` and try again." ) end end context "with missing platform specific gems in lockfile" do before do build_repo4 do build_gem "racca", "1.5.2" build_gem "nokogiri", "1.12.4" do |s| s.platform = "x86_64-darwin" s.add_runtime_dependency "racca", "~> 1.4" end build_gem "nokogiri", "1.12.4" do |s| s.platform = "x86_64-linux" s.add_runtime_dependency "racca", "~> 1.4" end build_gem "crass", "1.0.6" build_gem "loofah", "2.12.0" do |s| s.add_runtime_dependency "crass", "~> 1.0.2" s.add_runtime_dependency "nokogiri", ">= 1.5.9" end end gemfile <<-G source "https://gem.repo4" ruby "#{Gem.ruby_version}" gem "loofah", "~> 2.12.0" G checksums = checksums_section do |c| c.checksum gem_repo4, "crass", "1.0.6" c.checksum gem_repo4, "loofah", "2.12.0" c.checksum gem_repo4, "nokogiri", "1.12.4", "x86_64-darwin" c.checksum gem_repo4, "racca", "1.5.2" end lockfile <<-L GEM remote: https://gem.repo4/ specs: crass (1.0.6) loofah (2.12.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) nokogiri (1.12.4-x86_64-darwin) racca (~> 1.4) racca (1.5.2) PLATFORMS x86_64-darwin-20 x86_64-linux DEPENDENCIES loofah (~> 2.12.0) #{checksums} RUBY VERSION #{Bundler::RubyVersion.system} BUNDLED WITH #{Bundler::VERSION} L end it "automatically fixes the lockfile" do bundle "config set --local path vendor/bundle" simulate_platform "x86_64-linux" do bundle "install", artifice: "compact_index" end checksums = checksums_section_when_existing do |c| c.checksum gem_repo4, "crass", "1.0.6" c.checksum gem_repo4, "loofah", "2.12.0" c.checksum gem_repo4, "nokogiri", "1.12.4", "x86_64-darwin" c.checksum gem_repo4, "racca", "1.5.2" c.checksum gem_repo4, "nokogiri", "1.12.4", "x86_64-linux" end expect(lockfile).to eq <<~L GEM remote: https://gem.repo4/ specs: crass (1.0.6) loofah (2.12.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) nokogiri (1.12.4-x86_64-darwin) racca (~> 1.4) nokogiri (1.12.4-x86_64-linux) racca (~> 1.4) racca (1.5.2) PLATFORMS x86_64-darwin-20 x86_64-linux DEPENDENCIES loofah (~> 2.12.0) #{checksums} RUBY VERSION #{Bundler::RubyVersion.system} BUNDLED WITH #{Bundler::VERSION} L end end context "with --local flag" do before do system_gems "rack-1.0.0", path: default_bundle_path end it "respects installed gems without fetching any remote sources" do install_gemfile <<-G, local: true source "#{file_uri_for(gem_repo1)}" source "https://not-existing-source" do gem "rack" end G expect(last_command).to be_success end end context "with only option" do before do bundle "config set only a:b" end it "installs only gems of the specified groups" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rails" gem "rack", group: :a gem "rake", group: :b gem "yard", group: :c G expect(out).to include("Installing rack") expect(out).to include("Installing rake") expect(out).not_to include("Installing yard") end end context "with --prefer-local flag" do before do build_repo4 do build_gem "foo", "1.0.1" build_gem "foo", "1.0.0" build_gem "bar", "1.0.0" end system_gems "foo-1.0.0", path: default_bundle_path, gem_repo: gem_repo4 end it "fetches remote sources only when not available locally" do install_gemfile <<-G, "prefer-local": true, verbose: true source "#{file_uri_for(gem_repo4)}" gem "foo" gem "bar" G expect(out).to include("Using foo 1.0.0").and include("Fetching bar 1.0.0").and include("Installing bar 1.0.0") expect(last_command).to be_success end end context "with a symlinked configured as bundle path and a gem with symlinks" do before do symlinked_bundled_app = tmp("bundled_app-symlink") File.symlink(bundled_app, symlinked_bundled_app) bundle "config path #{File.join(symlinked_bundled_app, ".vendor")}" binman_path = tmp("binman") FileUtils.mkdir_p binman_path readme_path = File.join(binman_path, "README.markdown") FileUtils.touch(readme_path) man_path = File.join(binman_path, "man", "man0") FileUtils.mkdir_p man_path File.symlink("../../README.markdown", File.join(man_path, "README.markdown")) build_repo4 do build_gem "binman", path: gem_repo4("gems"), lib_path: binman_path, no_default: true do |s| s.files = ["README.markdown", "man/man0/README.markdown"] end end end it "installs fine" do install_gemfile <<~G source "#{file_uri_for(gem_repo4)}" gem "binman" G end end context "when a gem has equivalent versions with inconsistent dependencies" do before do build_repo4 do build_gem "autobuild", "1.10.rc2" do |s| s.add_dependency "utilrb", ">= 1.6.0" end build_gem "autobuild", "1.10.0.rc2" do |s| s.add_dependency "utilrb", ">= 2.0" end end end it "does not crash unexpectedly" do gemfile <<~G source "#{file_uri_for(gem_repo4)}" gem "autobuild", "1.10.rc2" G bundle "install --jobs 1", raise_on_error: false expect(err).not_to include("ERROR REPORT TEMPLATE") expect(err).to include("Could not find compatible versions") end end context "when a lockfile has unmet dependencies, and the Gemfile has no resolution" do before do build_repo4 do build_gem "aaa", "0.2.0" do |s| s.add_dependency "zzz", "< 0.2.0" end build_gem "zzz", "0.2.0" end gemfile <<~G source "#{file_uri_for(gem_repo4)}" gem "aaa" gem "zzz" G lockfile <<~L GEM remote: #{file_uri_for(gem_repo4)}/ specs: aaa (0.2.0) zzz (< 0.2.0) zzz (0.2.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES aaa! zzz! BUNDLED WITH #{Bundler::VERSION} L end it "does not install, but raises a resolution error" do bundle "install", raise_on_error: false expect(err).to include("Could not find compatible versions") end end context "when --jobs option given" do before do install_gemfile "source \"#{file_uri_for(gem_repo1)}\"", jobs: 1 end it "does not save the flag to config" do expect(bundled_app(".bundle/config")).not_to exist end end end