diff options
author | hsbt <hsbt@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2018-11-02 23:07:56 +0000 |
---|---|---|
committer | hsbt <hsbt@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2018-11-02 23:07:56 +0000 |
commit | 59c8d50653480bef3f24517296e6ddf937fdf6bc (patch) | |
tree | df10aaf4f3307837fe3d1d129d66f6c0c7586bc5 /spec/bundler/install | |
parent | 7deb37777a230837e865e0a11fb8d7c1dc6d03ce (diff) | |
download | ruby-59c8d50653480bef3f24517296e6ddf937fdf6bc.tar.gz |
Added bundler as default gems. Revisit [Feature #12733]
* bin/*, lib/bundler/*, lib/bundler.rb, spec/bundler, man/*:
Merge from latest stable branch of bundler/bundler repository and
added workaround patches. I will backport them into upstream.
* common.mk, defs/gmake.mk: Added `test-bundler` task for test suite
of bundler.
* tool/sync_default_gems.rb: Added sync task for bundler.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@65509 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'spec/bundler/install')
38 files changed, 9888 insertions, 0 deletions
diff --git a/spec/bundler/install/allow_offline_install_spec.rb b/spec/bundler/install/allow_offline_install_spec.rb new file mode 100644 index 0000000000..d4bb595771 --- /dev/null +++ b/spec/bundler/install/allow_offline_install_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install with :allow_offline_install" do + before do + bundle "config allow_offline_install true" + end + + context "with no cached data locally" do + it "still installs" do + install_gemfile! <<-G, :artifice => "compact_index" + source "http://testgemserver.local" + gem "rack-obama" + G + expect(the_bundle).to include_gem("rack 1.0") + end + + it "still fails when the network is down" do + install_gemfile <<-G, :artifice => "fail" + source "http://testgemserver.local" + gem "rack-obama" + G + expect(out).to include("Could not reach host testgemserver.local.") + expect(the_bundle).to_not be_locked + end + end + + context "with cached data locally" do + it "will install from the compact index" do + system_gems ["rack-1.0.0"], :path => :bundle_path + + bundle! "config clean false" + install_gemfile! <<-G, :artifice => "compact_index" + source "http://testgemserver.local" + gem "rack-obama" + gem "rack", "< 1.0" + G + + expect(the_bundle).to include_gems("rack-obama 1.0", "rack 0.9.1") + + gemfile <<-G + source "http://testgemserver.local" + gem "rack-obama" + G + + bundle! :update, :artifice => "fail", :all => true + expect(last_command.stdboth).to include "Using the cached data for the new index because of a network error" + + expect(the_bundle).to include_gems("rack-obama 1.0", "rack 1.0.0") + end + + def break_git_remote_ops! + FileUtils.mkdir_p(tmp("broken_path")) + File.open(tmp("broken_path/git"), "w", 0o755) do |f| + f.puts strip_whitespace(<<-RUBY) + #!/usr/bin/env ruby + if %w(fetch --force --quiet --tags refs/heads/*:refs/heads/*).-(ARGV).empty? || %w(clone --bare --no-hardlinks --quiet).-(ARGV).empty? + warn "git remote ops have been disabled" + exit 1 + end + ENV["PATH"] = ENV["PATH"].sub(/^.*?:/, "") + exec("git", *ARGV) + RUBY + end + + old_path = ENV["PATH"] + ENV["PATH"] = "#{tmp("broken_path")}:#{ENV["PATH"]}" + yield if block_given? + ensure + ENV["PATH"] = old_path if block_given? + end + + it "will install from a cached git repo" do + git = build_git "a", "1.0.0", :path => lib_path("a") + update_git("a", :path => git.path, :branch => "new_branch") + install_gemfile! <<-G + gem "a", :git => #{git.path.to_s.dump} + G + + break_git_remote_ops! { bundle! :update, :all => true } + expect(out).to include("Using cached git data because of network errors") + expect(the_bundle).to be_locked + + break_git_remote_ops! do + install_gemfile! <<-G + gem "a", :git => #{git.path.to_s.dump}, :branch => "new_branch" + G + end + expect(out).to include("Using cached git data because of network errors") + expect(the_bundle).to be_locked + end + end +end diff --git a/spec/bundler/install/binstubs_spec.rb b/spec/bundler/install/binstubs_spec.rb new file mode 100644 index 0000000000..23eb691ab8 --- /dev/null +++ b/spec/bundler/install/binstubs_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install", :bundler => "< 2" do + describe "when system_bindir is set" do + # On OS X, Gem.bindir defaults to /usr/bin, so system_bindir is useful if + # you want to avoid sudo installs for system gems with OS X's default ruby + it "overrides Gem.bindir" do + expect(Pathname.new("/usr/bin")).not_to be_writable unless Process.euid == 0 + gemfile <<-G + require 'rubygems' + def Gem.bindir; "/usr/bin"; end + source "file://#{gem_repo1}" + gem "rack" + G + + config "BUNDLE_SYSTEM_BINDIR" => system_gem_path("altbin").to_s + bundle :install + expect(the_bundle).to include_gems "rack 1.0.0" + expect(system_gem_path("altbin/rackup")).to exist + end + end + + describe "when multiple gems contain the same exe", :bundler => "< 2" do + before do + build_repo2 do + build_gem "fake", "14" do |s| + s.executables = "rackup" + end + end + + install_gemfile <<-G, :binstubs => true + source "file://#{gem_repo2}" + gem "fake" + gem "rack" + G + end + + it "loads the correct spec's executable" do + gembin("rackup") + expect(out).to eq("1.2") + end + end +end diff --git a/spec/bundler/install/bundler_spec.rb b/spec/bundler/install/bundler_spec.rb new file mode 100644 index 0000000000..42863ed89b --- /dev/null +++ b/spec/bundler/install/bundler_spec.rb @@ -0,0 +1,177 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install" do + describe "with bundler dependencies" do + before(:each) do + build_repo2 do + build_gem "rails", "3.0" do |s| + s.add_dependency "bundler", ">= 0.9.0.pre" + end + build_gem "bundler", "0.9.1" + build_gem "bundler", Bundler::VERSION + end + end + + it "are forced to the current bundler version" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "rails", "3.0" + G + + expect(the_bundle).to include_gems "bundler #{Bundler::VERSION}" + end + + it "are not added if not already present" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + expect(the_bundle).not_to include_gems "bundler #{Bundler::VERSION}" + end + + it "causes a conflict if explicitly requesting a different version" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "rails", "3.0" + gem "bundler", "0.9.2" + G + + nice_error = <<-E.strip.gsub(/^ {8}/, "") + Bundler could not find compatible versions for gem "bundler": + In Gemfile: + bundler (= 0.9.2) + + Current Bundler version: + bundler (#{Bundler::VERSION}) + This Gemfile requires a different version of Bundler. + Perhaps you need to update Bundler by running `gem install bundler`? + + Could not find gem 'bundler (= 0.9.2)' in any + E + expect(last_command.bundler_err).to include(nice_error) + end + + it "works for gems with multiple versions in its dependencies" do + install_gemfile <<-G + source "file://#{gem_repo2}" + + gem "multiple_versioned_deps" + G + + install_gemfile <<-G + source "file://#{gem_repo2}" + + gem "multiple_versioned_deps" + gem "rack" + G + + expect(the_bundle).to include_gems "multiple_versioned_deps 1.0.0" + end + + it "includes bundler in the bundle when it's a child dependency" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "rails", "3.0" + G + + run "begin; gem 'bundler'; puts 'WIN'; rescue Gem::LoadError; puts 'FAIL'; end" + expect(out).to eq("WIN") + end + + it "allows gem 'bundler' when Bundler is not in the Gemfile or its dependencies" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "rack" + G + + run "begin; gem 'bundler'; puts 'WIN'; rescue Gem::LoadError => e; puts e.backtrace; end" + expect(out).to eq("WIN") + end + + it "causes a conflict if child dependencies conflict" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "activemerchant" + gem "rails_fail" + G + + nice_error = <<-E.strip.gsub(/^ {8}/, "") + Bundler could not find compatible versions for gem "activesupport": + In Gemfile: + activemerchant was resolved to 1.0, which depends on + activesupport (>= 2.0.0) + + rails_fail was resolved to 1.0, which depends on + activesupport (= 1.2.3) + E + expect(last_command.bundler_err).to include(nice_error) + end + + it "causes a conflict if a child dependency conflicts with the Gemfile" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "rails_fail" + gem "activesupport", "2.3.5" + G + + nice_error = <<-E.strip.gsub(/^ {8}/, "") + Bundler could not find compatible versions for gem "activesupport": + In Gemfile: + activesupport (= 2.3.5) + + rails_fail was resolved to 1.0, which depends on + activesupport (= 1.2.3) + E + expect(last_command.bundler_err).to include(nice_error) + end + + it "can install dependencies with newer bundler version with system gems", :ruby => "> 2" do + bundle! "config path.system true" + install_gemfile! <<-G + source "file://#{gem_repo2}" + gem "rails", "3.0" + G + + simulate_bundler_version "99999999.99.1" + + bundle! "check", :env => { "BUNDLER_SPEC_IGNORE_COMPATIBILITY_GUARD" => "1" } + expect(out).to include("The Gemfile's dependencies are satisfied") + end + + it "can install dependencies with newer bundler version with a local path", :ruby => "> 2" do + bundle! "config path .bundle" + bundle! "config global_path_appends_ruby_scope true" + install_gemfile! <<-G + source "file://#{gem_repo2}" + gem "rails", "3.0" + G + + simulate_bundler_version "99999999.99.1" + + bundle! "check", :env => { "BUNDLER_SPEC_IGNORE_COMPATIBILITY_GUARD" => "1" } + expect(out).to include("The Gemfile's dependencies are satisfied") + end + + context "with allow_bundler_dependency_conflicts set" do + before { bundle! "config allow_bundler_dependency_conflicts true" } + + it "are forced to the current bundler version with warnings when no compatible version is found" do + build_repo4 do + build_gem "requires_nonexistant_bundler" do |s| + s.add_runtime_dependency "bundler", "99.99.99.99" + end + end + + install_gemfile! <<-G + source "file://#{gem_repo4}" + gem "requires_nonexistant_bundler" + G + + expect(out).to include "requires_nonexistant_bundler (1.0) has dependency bundler (= 99.99.99.99), " \ + "which is unsatisfied by the current bundler version #{Bundler::VERSION}, so the dependency is being ignored" + + expect(the_bundle).to include_gems "bundler #{Bundler::VERSION}", "requires_nonexistant_bundler 1.0" + end + end + end +end diff --git a/spec/bundler/install/deploy_spec.rb b/spec/bundler/install/deploy_spec.rb new file mode 100644 index 0000000000..3b9d68982a --- /dev/null +++ b/spec/bundler/install/deploy_spec.rb @@ -0,0 +1,423 @@ +# frozen_string_literal: true + +RSpec.describe "install with --deployment or --frozen" do + before do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + end + + context "with CLI flags", :bundler => "< 2" do + it "fails without a lockfile and says that --deployment requires a lock" do + bundle "install --deployment" + expect(out).to include("The --deployment flag requires a Gemfile.lock") + end + + it "fails without a lockfile and says that --frozen requires a lock" do + bundle "install --frozen" + expect(out).to include("The --frozen flag requires a Gemfile.lock") + end + + it "disallows --deployment --system" do + bundle "install --deployment --system" + expect(out).to include("You have specified both --deployment") + expect(out).to include("Please choose only one option") + expect(exitstatus).to eq(15) if exitstatus + end + + it "disallows --deployment --path --system" do + bundle "install --deployment --path . --system" + expect(out).to include("You have specified both --path") + expect(out).to include("as well as --system") + expect(out).to include("Please choose only one option") + expect(exitstatus).to eq(15) if exitstatus + end + + it "works after you try to deploy without a lock" do + bundle "install --deployment" + bundle! :install + expect(the_bundle).to include_gems "rack 1.0" + end + end + + it "still works if you are not in the app directory and specify --gemfile" do + bundle "install" + Dir.chdir tmp do + simulate_new_machine + bundle! :install, + forgotten_command_line_options(:gemfile => "#{tmp}/bundled_app/Gemfile", + :deployment => true, + :path => "vendor/bundle") + end + expect(the_bundle).to include_gems "rack 1.0" + end + + it "works if you exclude a group with a git gem" do + build_git "foo" + gemfile <<-G + group :test do + gem "foo", :git => "#{lib_path("foo-1.0")}" + end + G + bundle :install + bundle! :install, forgotten_command_line_options(:deployment => true, :without => "test") + end + + it "works when you bundle exec bundle", :ruby_repo do + bundle :install + bundle "install --deployment" + bundle! "exec bundle check" + end + + it "works when using path gems from the same path and the version is specified" do + build_lib "foo", :path => lib_path("nested/foo") + build_lib "bar", :path => lib_path("nested/bar") + gemfile <<-G + gem "foo", "1.0", :path => "#{lib_path("nested")}" + gem "bar", :path => "#{lib_path("nested")}" + G + + bundle! :install + bundle! :install, forgotten_command_line_options(:deployment => true) + end + + it "works when there are credentials in the source URL" do + install_gemfile(<<-G, :artifice => "endpoint_strict_basic_authentication", :quiet => true) + source "http://user:pass@localgemserver.test/" + + gem "rack-obama", ">= 1.0" + G + + bundle! :install, forgotten_command_line_options(:deployment => true).merge(:artifice => "endpoint_strict_basic_authentication") + end + + it "works with sources given by a block" do + install_gemfile! <<-G + source "file://#{gem_repo1}" do + gem "rack" + end + G + + bundle! :install, forgotten_command_line_options(:deployment => true) + + expect(the_bundle).to include_gems "rack 1.0" + end + + describe "with an existing lockfile" do + before do + bundle "install" + end + + it "works with the --deployment flag if you didn't change anything", :bundler => "< 2" do + bundle! "install --deployment" + end + + it "works with the --frozen flag if you didn't change anything", :bundler => "< 2" do + bundle! "install --frozen" + end + + it "works with BUNDLE_FROZEN if you didn't change anything" do + bundle! :install, :env => { "BUNDLE_FROZEN" => "true" } + end + + it "explodes with the --deployment flag if you make a change and don't check in the lockfile" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "rack-obama" + G + + bundle :install, forgotten_command_line_options(:deployment => true) + expect(out).to include("deployment mode") + expect(out).to include("You have added to the Gemfile") + expect(out).to include("* rack-obama") + expect(out).not_to include("You have deleted from the Gemfile") + expect(out).not_to include("You have changed in the Gemfile") + end + + it "works if a path gem is missing but is in a without group" do + build_lib "path_gem" + install_gemfile! <<-G + source "file://#{gem_repo1}" + gem "rake" + gem "path_gem", :path => "#{lib_path("path_gem-1.0")}", :group => :development + G + expect(the_bundle).to include_gems "path_gem 1.0" + FileUtils.rm_r lib_path("path_gem-1.0") + + bundle! :install, forgotten_command_line_options(:path => ".bundle", :without => "development", :deployment => true).merge(:env => { :DEBUG => "1" }) + run! "puts :WIN" + expect(out).to eq("WIN") + end + + it "explodes if a path gem is missing" do + build_lib "path_gem" + install_gemfile! <<-G + source "file://#{gem_repo1}" + gem "rake" + gem "path_gem", :path => "#{lib_path("path_gem-1.0")}", :group => :development + G + expect(the_bundle).to include_gems "path_gem 1.0" + FileUtils.rm_r lib_path("path_gem-1.0") + + bundle :install, forgotten_command_line_options(:path => ".bundle", :deployment => true) + expect(out).to include("The path `#{lib_path("path_gem-1.0")}` does not exist.") + end + + it "can have --frozen set via an environment variable", :bundler => "< 2" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "rack-obama" + G + + ENV["BUNDLE_FROZEN"] = "1" + bundle "install" + expect(out).to include("deployment mode") + expect(out).to include("You have added to the Gemfile") + expect(out).to include("* rack-obama") + expect(out).not_to include("You have deleted from the Gemfile") + expect(out).not_to include("You have changed in the Gemfile") + end + + it "can have --deployment set via an environment variable" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "rack-obama" + G + + ENV["BUNDLE_DEPLOYMENT"] = "true" + bundle "install" + expect(out).to include("deployment mode") + expect(out).to include("You have added to the Gemfile") + expect(out).to include("* rack-obama") + expect(out).not_to include("You have deleted from the Gemfile") + expect(out).not_to include("You have changed in the Gemfile") + end + + it "can have --frozen set to false via an environment variable" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "rack-obama" + G + + ENV["BUNDLE_FROZEN"] = "false" + ENV["BUNDLE_DEPLOYMENT"] = "false" + bundle "install" + expect(out).not_to include("deployment mode") + expect(out).not_to include("You have added to the Gemfile") + expect(out).not_to include("* rack-obama") + end + + it "explodes with the --frozen flag if you make a change and don't check in the lockfile", :bundler => "< 2" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "rack-obama", "1.1" + G + + bundle :install, forgotten_command_line_options(:frozen => true) + expect(out).to include("deployment mode") + expect(out).to include("You have added to the Gemfile") + expect(out).to include("* rack-obama (= 1.1)") + expect(out).not_to include("You have deleted from the Gemfile") + expect(out).not_to include("You have changed in the Gemfile") + end + + it "explodes if you remove a gem and don't check in the lockfile" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "activesupport" + G + + bundle :install, forgotten_command_line_options(:deployment => true) + expect(out).to include("deployment mode") + expect(out).to include("You have added to the Gemfile:\n* activesupport\n\n") + expect(out).to include("You have deleted from the Gemfile:\n* rack") + expect(out).not_to include("You have changed in the Gemfile") + end + + it "explodes if you add a source" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", :git => "git://hubz.com" + G + + bundle :install, forgotten_command_line_options(:deployment => true) + expect(out).to include("deployment mode") + expect(out).to include("You have added to the Gemfile:\n* source: git://hubz.com (at master)") + expect(out).not_to include("You have changed in the Gemfile") + end + + it "explodes if you unpin a source" do + build_git "rack" + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", :git => "#{lib_path("rack-1.0")}" + G + + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + bundle :install, forgotten_command_line_options(:deployment => true) + expect(out).to include("deployment mode") + expect(out).to include("You have deleted from the Gemfile:\n* source: #{lib_path("rack-1.0")} (at master@#{revision_for(lib_path("rack-1.0"))[0..6]}") + expect(out).not_to include("You have added to the Gemfile") + expect(out).not_to include("You have changed in the Gemfile") + end + + it "explodes if you unpin a source, leaving it pinned somewhere else" do + build_lib "foo", :path => lib_path("rack/foo") + build_git "rack", :path => lib_path("rack") + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", :git => "#{lib_path("rack")}" + gem "foo", :git => "#{lib_path("rack")}" + G + + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "foo", :git => "#{lib_path("rack")}" + G + + bundle :install, forgotten_command_line_options(:deployment => true) + expect(out).to include("deployment mode") + expect(out).to include("You have changed in the Gemfile:\n* rack from `no specified source` to `#{lib_path("rack")} (at master@#{revision_for(lib_path("rack"))[0..6]})`") + expect(out).not_to include("You have added to the Gemfile") + expect(out).not_to include("You have deleted from the Gemfile") + end + + context "when replacing a host with the same host with credentials" do + let(:success_message) do + if Bundler::VERSION.split(".", 2).first == "1" + "Could not reach host localgemserver.test" + else + "Bundle complete!" + end + end + + before do + install_gemfile <<-G + source "http://user_name:password@localgemserver.test/" + gem "rack" + G + + lockfile <<-G + GEM + remote: http://localgemserver.test/ + specs: + rack (1.0.0) + + PLATFORMS + #{local} + + DEPENDENCIES + rack + G + end + + it "prevents the replace by default" do + bundle :install, forgotten_command_line_options(:deployment => true) + + expect(out).to match(/The list of sources changed/) + end + + context "when allow_deployment_source_credential_changes is true" do + before { bundle! "config allow_deployment_source_credential_changes true" } + + it "allows the replace" do + bundle :install, forgotten_command_line_options(:deployment => true) + + expect(out).to match(/#{success_message}/) + end + end + + context "when allow_deployment_source_credential_changes is false" do + before { bundle! "config allow_deployment_source_credential_changes false" } + + it "prevents the replace" do + bundle :install, forgotten_command_line_options(:deployment => true) + + expect(out).to match(/The list of sources changed/) + end + end + + context "when BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES env var is true" do + before { ENV["BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES"] = "true" } + + it "allows the replace" do + bundle :install, forgotten_command_line_options(:deployment => true) + + expect(out).to match(/#{success_message}/) + end + end + + context "when BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES env var is false" do + before { ENV["BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES"] = "false" } + + it "prevents the replace" do + bundle :install, forgotten_command_line_options(:deployment => true) + + expect(out).to match(/The list of sources changed/) + end + end + end + + it "remembers that the bundle is frozen at runtime" do + bundle! :lock + + bundle! "config deployment true" + + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", "1.0.0" + gem "rack-obama" + G + + expect(the_bundle).not_to include_gems "rack 1.0.0" + expect(err).to include strip_whitespace(<<-E).strip +The dependencies in your gemfile changed + +You have added to the Gemfile: +* rack (= 1.0.0) +* rack-obama + +You have deleted from the Gemfile: +* rack + E + end + end + + context "with path in Gemfile and packed" do + it "works fine after bundle package and bundle install --local" do + build_lib "foo", :path => lib_path("foo") + install_gemfile! <<-G + gem "foo", :path => "#{lib_path("foo")}" + G + + bundle! :install + expect(the_bundle).to include_gems "foo 1.0" + bundle! :package, forgotten_command_line_options([:all, :cache_all] => true) + expect(bundled_app("vendor/cache/foo")).to be_directory + + bundle! "install --local" + expect(out).to include("Updating files in vendor/cache") + + simulate_new_machine + bundle! "install --verbose", forgotten_command_line_options(:deployment => true) + expect(out).not_to include("You are trying to install in deployment mode after changing your Gemfile") + expect(out).not_to include("You have added to the Gemfile") + expect(out).not_to include("You have deleted from the Gemfile") + expect(out).to include("vendor/cache/foo") + expect(the_bundle).to include_gems "foo 1.0" + end + end +end diff --git a/spec/bundler/install/failure_spec.rb b/spec/bundler/install/failure_spec.rb new file mode 100644 index 0000000000..b4cdf13857 --- /dev/null +++ b/spec/bundler/install/failure_spec.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install" do + context "installing a gem fails" do + it "prints out why that gem was being installed" do + build_repo2 do + build_gem "activesupport", "2.3.2" do |s| + s.extensions << "Rakefile" + s.write "Rakefile", <<-RUBY + task :default do + abort "make installing activesupport-2.3.2 fail" + end + RUBY + end + end + + install_gemfile <<-G + source "file:\/\/localhost#{gem_repo2}" + gem "rails" + G + expect(last_command.bundler_err).to end_with(normalize_uri_file(<<-M.strip)) +An error occurred while installing activesupport (2.3.2), and Bundler cannot continue. +Make sure that `gem install activesupport -v '2.3.2' --source 'file://localhost#{gem_repo2}/'` succeeds before bundling. + +In Gemfile: + rails was resolved to 2.3.2, which depends on + actionmailer was resolved to 2.3.2, which depends on + activesupport + M + end + + context "when installing a git gem" do + it "does not tell the user to run 'gem install'" do + build_git "activesupport", "2.3.2", :path => lib_path("activesupport") do |s| + s.extensions << "Rakefile" + s.write "Rakefile", <<-RUBY + task :default do + abort "make installing activesupport-2.3.2 fail" + end + RUBY + end + + install_gemfile <<-G + source "file:\/\/localhost#{gem_repo1}" + gem "rails" + gem "activesupport", :git => "#{lib_path("activesupport")}" + G + + expect(last_command.bundler_err).to end_with(<<-M.strip) +An error occurred while installing activesupport (2.3.2), and Bundler cannot continue. + +In Gemfile: + rails was resolved to 2.3.2, which depends on + actionmailer was resolved to 2.3.2, which depends on + activesupport + M + end + end + + context "when installing a gem using a git block" do + it "does not tell the user to run 'gem install'" do + build_git "activesupport", "2.3.2", :path => lib_path("activesupport") do |s| + s.extensions << "Rakefile" + s.write "Rakefile", <<-RUBY + task :default do + abort "make installing activesupport-2.3.2 fail" + end + RUBY + end + + install_gemfile <<-G + source "file:\/\/localhost#{gem_repo1}" + gem "rails" + + git "#{lib_path("activesupport")}" do + gem "activesupport" + end + G + + expect(last_command.bundler_err).to end_with(<<-M.strip) +An error occurred while installing activesupport (2.3.2), and Bundler cannot continue. + + +In Gemfile: + rails was resolved to 2.3.2, which depends on + actionmailer was resolved to 2.3.2, which depends on + activesupport + M + end + end + + it "prints out the hint for the remote source when available" do + build_repo2 do + build_gem "activesupport", "2.3.2" do |s| + s.extensions << "Rakefile" + s.write "Rakefile", <<-RUBY + task :default do + abort "make installing activesupport-2.3.2 fail" + end + RUBY + end + end + + build_repo4 do + build_gem "a" + end + + install_gemfile <<-G + source "file:\/\/localhost#{gem_repo4}" + source "file:\/\/localhost#{gem_repo2}" do + gem "rails" + end + G + expect(last_command.bundler_err).to end_with(normalize_uri_file(<<-M.strip)) +An error occurred while installing activesupport (2.3.2), and Bundler cannot continue. +Make sure that `gem install activesupport -v '2.3.2' --source 'file://localhost#{gem_repo2}/'` succeeds before bundling. + +In Gemfile: + rails was resolved to 2.3.2, which depends on + actionmailer was resolved to 2.3.2, which depends on + activesupport + M + end + end +end diff --git a/spec/bundler/install/gemfile/eval_gemfile_spec.rb b/spec/bundler/install/gemfile/eval_gemfile_spec.rb new file mode 100644 index 0000000000..035d3692aa --- /dev/null +++ b/spec/bundler/install/gemfile/eval_gemfile_spec.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install with gemfile that uses eval_gemfile" do + before do + build_lib("gunks", :path => bundled_app.join("gems/gunks")) do |s| + s.name = "gunks" + s.version = "0.0.1" + end + end + + context "eval-ed Gemfile points to an internal gemspec" do + before do + create_file "Gemfile-other", <<-G + gemspec :path => 'gems/gunks' + G + end + + it "installs the gemspec specified gem" do + install_gemfile <<-G + eval_gemfile 'Gemfile-other' + G + expect(out).to include("Resolving dependencies") + expect(out).to include("Bundle complete") + + expect(the_bundle).to include_gem "gunks 0.0.1", :source => "path@#{bundled_app("gems", "gunks")}" + end + end + + context "eval-ed Gemfile has relative-path gems" do + before do + build_lib("a", :path => "gems/a") + create_file "nested/Gemfile-nested", <<-G + gem "a", :path => "../gems/a" + G + + gemfile <<-G + eval_gemfile "nested/Gemfile-nested" + G + end + + it "installs the path gem" do + bundle! :install + expect(the_bundle).to include_gem("a 1.0") + end + + # Make sure that we are properly comparing path based gems between the + # parsed lockfile and the evaluated gemfile. + it "bundles with --deployment" do + bundle! :install + bundle! :install, forgotten_command_line_options(:deployment => true) + end + end + + context "Gemfile uses gemspec paths after eval-ing a Gemfile" do + before { create_file "other/Gemfile-other" } + + it "installs the gemspec specified gem" do + install_gemfile <<-G + eval_gemfile 'other/Gemfile-other' + gemspec :path => 'gems/gunks' + G + expect(out).to include("Resolving dependencies") + expect(out).to include("Bundle complete") + + expect(the_bundle).to include_gem "gunks 0.0.1", :source => "path@#{bundled_app("gems", "gunks")}" + end + end + + context "eval-ed Gemfile references other gemfiles" do + it "works with relative paths" do + create_file "other/Gemfile-other", "gem 'rack'" + create_file "other/Gemfile", "eval_gemfile 'Gemfile-other'" + create_file "Gemfile-alt", <<-G + source "file:#{gem_repo1}" + eval_gemfile "other/Gemfile" + G + install_gemfile! "eval_gemfile File.expand_path('Gemfile-alt')" + + expect(the_bundle).to include_gem "rack 1.0.0" + end + end +end diff --git a/spec/bundler/install/gemfile/gemspec_spec.rb b/spec/bundler/install/gemfile/gemspec_spec.rb new file mode 100644 index 0000000000..7ce037730e --- /dev/null +++ b/spec/bundler/install/gemfile/gemspec_spec.rb @@ -0,0 +1,672 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install from an existing gemspec" do + before(:each) do + build_repo2 do + build_gem "bar" + build_gem "bar-dev" + end + end + + it "should install runtime and development dependencies" do + build_lib("foo", :path => tmp.join("foo")) do |s| + s.write("Gemfile", "source :rubygems\ngemspec") + s.add_dependency "bar", "=1.0.0" + s.add_development_dependency "bar-dev", "=1.0.0" + end + install_gemfile <<-G + source "file://#{gem_repo2}" + gemspec :path => '#{tmp.join("foo")}' + G + + expect(the_bundle).to include_gems "bar 1.0.0" + expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :development + end + + it "that is hidden should install runtime and development dependencies" do + build_lib("foo", :path => tmp.join("foo")) do |s| + s.write("Gemfile", "source :rubygems\ngemspec") + s.add_dependency "bar", "=1.0.0" + s.add_development_dependency "bar-dev", "=1.0.0" + end + FileUtils.mv tmp.join("foo", "foo.gemspec"), tmp.join("foo", ".gemspec") + + install_gemfile <<-G + source "file://#{gem_repo2}" + gemspec :path => '#{tmp.join("foo")}' + G + + expect(the_bundle).to include_gems "bar 1.0.0" + expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :development + end + + it "should handle a list of requirements" do + update_repo2 do + build_gem "baz", "1.0" + build_gem "baz", "1.1" + end + + build_lib("foo", :path => tmp.join("foo")) do |s| + s.write("Gemfile", "source :rubygems\ngemspec") + s.add_dependency "baz", ">= 1.0", "< 1.1" + end + install_gemfile <<-G + source "file://#{gem_repo2}" + gemspec :path => '#{tmp.join("foo")}' + G + + expect(the_bundle).to include_gems "baz 1.0" + end + + it "should raise if there are no gemspecs available" do + build_lib("foo", :path => tmp.join("foo"), :gemspec => false) + + install_gemfile(<<-G) + source "file://#{gem_repo2}" + gemspec :path => '#{tmp.join("foo")}' + G + expect(last_command.bundler_err).to match(/There are no gemspecs at #{tmp.join('foo')}/) + end + + it "should raise if there are too many gemspecs available" do + build_lib("foo", :path => tmp.join("foo")) do |s| + s.write("foo2.gemspec", build_spec("foo", "4.0").first.to_ruby) + end + + install_gemfile(<<-G) + source "file://#{gem_repo2}" + gemspec :path => '#{tmp.join("foo")}' + G + expect(last_command.bundler_err).to match(/There are multiple gemspecs at #{tmp.join('foo')}/) + end + + it "should pick a specific gemspec" do + build_lib("foo", :path => tmp.join("foo")) do |s| + s.write("foo2.gemspec", "") + s.add_dependency "bar", "=1.0.0" + s.add_development_dependency "bar-dev", "=1.0.0" + end + + install_gemfile(<<-G) + source "file://#{gem_repo2}" + gemspec :path => '#{tmp.join("foo")}', :name => 'foo' + G + + expect(the_bundle).to include_gems "bar 1.0.0" + expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :development + end + + it "should use a specific group for development dependencies" do + build_lib("foo", :path => tmp.join("foo")) do |s| + s.write("foo2.gemspec", "") + s.add_dependency "bar", "=1.0.0" + s.add_development_dependency "bar-dev", "=1.0.0" + end + + install_gemfile(<<-G) + source "file://#{gem_repo2}" + gemspec :path => '#{tmp.join("foo")}', :name => 'foo', :development_group => :dev + G + + expect(the_bundle).to include_gems "bar 1.0.0" + expect(the_bundle).not_to include_gems "bar-dev 1.0.0", :groups => :development + expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :dev + end + + it "should match a lockfile even if the gemspec defines development dependencies" do + build_lib("foo", :path => tmp.join("foo")) do |s| + s.write("Gemfile", "source 'file://#{gem_repo1}'\ngemspec") + s.add_dependency "actionpack", "=2.3.2" + s.add_development_dependency "rake", "=10.0.2" + end + + Dir.chdir(tmp.join("foo")) do + bundle "install" + # This should really be able to rely on $stderr, but, it's not written + # right, so we can't. In fact, this is a bug negation test, and so it'll + # ghost pass in future, and will only catch a regression if the message + # doesn't change. Exit codes should be used correctly (they can be more + # than just 0 and 1). + output = bundle("install --deployment") + expect(output).not_to match(/You have added to the Gemfile/) + expect(output).not_to match(/You have deleted from the Gemfile/) + expect(output).not_to match(/install in deployment mode after changing/) + end + end + + it "should match a lockfile without needing to re-resolve" do + build_lib("foo", :path => tmp.join("foo")) do |s| + s.add_dependency "rack" + end + + install_gemfile! <<-G + source "file://#{gem_repo1}" + gemspec :path => '#{tmp.join("foo")}' + G + + bundle! "install", :verbose => true + + message = "Found no changes, using resolution from the lockfile" + expect(out.scan(message).size).to eq(1) + end + + it "should match a lockfile without needing to re-resolve with development dependencies" do + simulate_platform java + + build_lib("foo", :path => tmp.join("foo")) do |s| + s.add_dependency "rack" + s.add_development_dependency "thin" + end + + install_gemfile! <<-G + source "file://#{gem_repo1}" + gemspec :path => '#{tmp.join("foo")}' + G + + bundle! "install", :verbose => true + + message = "Found no changes, using resolution from the lockfile" + expect(out.scan(message).size).to eq(1) + end + + it "should match a lockfile on non-ruby platforms with a transitive platform dependency" do + simulate_platform java + simulate_ruby_engine "jruby" + + build_lib("foo", :path => tmp.join("foo")) do |s| + s.add_dependency "platform_specific" + end + + system_gems "platform_specific-1.0-java", :path => :bundle_path, :keep_path => true + + install_gemfile! <<-G + gemspec :path => '#{tmp.join("foo")}' + G + + bundle! "update --bundler", :verbose => true + expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 JAVA" + end + + it "should evaluate the gemspec in its directory" do + build_lib("foo", :path => tmp.join("foo")) + File.open(tmp.join("foo/foo.gemspec"), "w") do |s| + s.write "raise 'ahh' unless Dir.pwd == '#{tmp.join("foo")}'" + end + + install_gemfile <<-G + gemspec :path => '#{tmp.join("foo")}' + G + expect(last_command.stdboth).not_to include("ahh") + end + + it "allows the gemspec to activate other gems" do + ENV["BUNDLE_PATH__SYSTEM"] = "true" + # see https://github.com/bundler/bundler/issues/5409 + # + # issue was caused by rubygems having an unresolved gem during a require, + # so emulate that + system_gems %w[rack-1.0.0 rack-0.9.1 rack-obama-1.0] + + build_lib("foo", :path => bundled_app) + gemspec = bundled_app("foo.gemspec").read + bundled_app("foo.gemspec").open("w") do |f| + f.write "#{gemspec.strip}.tap { gem 'rack-obama'; require 'rack-obama' }" + end + + install_gemfile! <<-G + gemspec + G + + expect(the_bundle).to include_gem "foo 1.0" + end + + it "allows conflicts" do + build_lib("foo", :path => tmp.join("foo")) do |s| + s.version = "1.0.0" + s.add_dependency "bar", "= 1.0.0" + end + build_gem "deps", :to_bundle => true do |s| + s.add_dependency "foo", "= 0.0.1" + end + build_gem "foo", "0.0.1", :to_bundle => true + + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "deps" + gemspec :path => '#{tmp.join("foo")}', :name => 'foo' + G + + expect(the_bundle).to include_gems "foo 1.0.0" + end + + it "does not break Gem.finish_resolve with conflicts", :rubygems => ">= 2" do + build_lib("foo", :path => tmp.join("foo")) do |s| + s.version = "1.0.0" + s.add_dependency "bar", "= 1.0.0" + end + update_repo2 do + build_gem "deps" do |s| + s.add_dependency "foo", "= 0.0.1" + end + build_gem "foo", "0.0.1" + end + + install_gemfile! <<-G + source "file://#{gem_repo2}" + gem "deps" + gemspec :path => '#{tmp.join("foo")}', :name => 'foo' + G + + expect(the_bundle).to include_gems "foo 1.0.0" + + run! "Gem.finish_resolve; puts 'WIN'" + expect(out).to eq("WIN") + end + + context "in deployment mode" do + context "when the lockfile was not updated after a change to the gemspec's dependencies" do + it "reports that installation failed" do + build_lib "cocoapods", :path => bundled_app do |s| + s.add_dependency "activesupport", ">= 1" + end + + install_gemfile! <<-G + source "file://#{gem_repo1}" + gemspec + G + + expect(the_bundle).to include_gems("cocoapods 1.0", "activesupport 2.3.5") + + build_lib "cocoapods", :path => bundled_app do |s| + s.add_dependency "activesupport", ">= 1.0.1" + end + + bundle :install, forgotten_command_line_options(:deployment => true) + + expect(out).to include("changed") + end + end + end + + context "when child gemspecs conflict with a released gemspec" do + before do + # build the "parent" gem that depends on another gem in the same repo + build_lib "source_conflict", :path => bundled_app do |s| + s.add_dependency "rack_middleware" + end + + # build the "child" gem that is the same version as a released gem, but + # has completely different and conflicting dependency requirements + build_lib "rack_middleware", "1.0", :path => bundled_app("rack_middleware") do |s| + s.add_dependency "rack", "1.0" # anything other than 0.9.1 + end + end + + it "should install the child gemspec's deps" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gemspec + G + + expect(the_bundle).to include_gems "rack 1.0" + end + end + + context "with a lockfile and some missing dependencies" do + let(:source_uri) { "http://localgemserver.test" } + + context "previously bundled for Ruby" do + let(:platform) { "ruby" } + let(:explicit_platform) { false } + + before do + build_lib("foo", :path => tmp.join("foo")) do |s| + s.add_dependency "rack", "=1.0.0" + end + + if explicit_platform + create_file( + tmp.join("foo", "foo-#{platform}.gemspec"), + build_spec("foo", "1.0", platform) do + dep "rack", "=1.0.0" + @spec.authors = "authors" + @spec.summary = "summary" + end.first.to_ruby + ) + end + + gemfile <<-G + source "#{source_uri}" + gemspec :path => "../foo" + G + + lockfile <<-L + PATH + remote: ../foo + specs: + foo (1.0) + rack (= 1.0.0) + + GEM + remote: #{source_uri} + specs: + rack (1.0.0) + + PLATFORMS + #{generic_local_platform} + + DEPENDENCIES + foo! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + context "using JRuby with explicit platform" do + let(:platform) { "java" } + let(:explicit_platform) { true } + + it "should install" do + simulate_ruby_engine "jruby" do + simulate_platform "java" do + results = bundle "install", :artifice => "endpoint" + expect(results).to include("Installing rack 1.0.0") + expect(the_bundle).to include_gems "rack 1.0.0" + end + end + end + end + + context "using JRuby" do + let(:platform) { "java" } + + it "should install" do + simulate_ruby_engine "jruby" do + simulate_platform "java" do + results = bundle "install", :artifice => "endpoint" + expect(results).to include("Installing rack 1.0.0") + expect(the_bundle).to include_gems "rack 1.0.0" + end + end + end + end + + context "using Windows" do + it "should install" do + simulate_windows do + results = bundle "install", :artifice => "endpoint" + expect(results).to include("Installing rack 1.0.0") + expect(the_bundle).to include_gems "rack 1.0.0" + end + end + end + end + + context "bundled for ruby and jruby" do + let(:platform_specific_type) { :runtime } + let(:dependency) { "platform_specific" } + before do + build_repo2 do + build_gem "indirect_platform_specific" do |s| + s.add_runtime_dependency "platform_specific" + end + end + + build_lib "foo", :path => "." do |s| + if platform_specific_type == :runtime + s.add_runtime_dependency dependency + elsif platform_specific_type == :development + s.add_development_dependency dependency + else + raise "wrong dependency type #{platform_specific_type}, can only be :development or :runtime" + end + end + + %w[ruby jruby].each do |platform| + simulate_platform(platform) do + install_gemfile <<-G + source "file://localhost#{gem_repo2}" + gemspec + G + end + end + end + + context "on ruby", :bundler => "< 2" do + before do + simulate_platform("ruby") + bundle :install + end + + context "as a runtime dependency" do + it "keeps java dependencies in the lockfile" do + expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 RUBY" + expect(lockfile).to eq normalize_uri_file(strip_whitespace(<<-L)) + PATH + remote: . + specs: + foo (1.0) + platform_specific + + GEM + remote: file://localhost#{gem_repo2}/ + specs: + platform_specific (1.0) + platform_specific (1.0-java) + + PLATFORMS + java + ruby + + DEPENDENCIES + foo! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "as a development dependency" do + let(:platform_specific_type) { :development } + + it "keeps java dependencies in the lockfile" do + expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 RUBY" + expect(lockfile).to eq normalize_uri_file(strip_whitespace(<<-L)) + PATH + remote: . + specs: + foo (1.0) + + GEM + remote: file://localhost#{gem_repo2}/ + specs: + platform_specific (1.0) + platform_specific (1.0-java) + + PLATFORMS + java + ruby + + DEPENDENCIES + foo! + platform_specific + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "with an indirect platform-specific development dependency" do + let(:platform_specific_type) { :development } + let(:dependency) { "indirect_platform_specific" } + + it "keeps java dependencies in the lockfile" do + expect(the_bundle).to include_gems "foo 1.0", "indirect_platform_specific 1.0", "platform_specific 1.0 RUBY" + expect(lockfile).to eq normalize_uri_file(strip_whitespace(<<-L)) + PATH + remote: . + specs: + foo (1.0) + + GEM + remote: file://localhost#{gem_repo2}/ + specs: + indirect_platform_specific (1.0) + platform_specific + platform_specific (1.0) + platform_specific (1.0-java) + + PLATFORMS + java + ruby + + DEPENDENCIES + foo! + indirect_platform_specific + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + end + + context "on ruby", :bundler => "2" do + before do + simulate_platform("ruby") + bundle :install + end + + context "as a runtime dependency" do + it "keeps java dependencies in the lockfile" do + expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 RUBY" + expect(lockfile).to eq normalize_uri_file(strip_whitespace(<<-L)) + GEM + remote: file://localhost#{gem_repo2}/ + specs: + platform_specific (1.0) + platform_specific (1.0-java) + + PATH + remote: . + specs: + foo (1.0) + platform_specific + + PLATFORMS + java + ruby + + DEPENDENCIES + foo! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "as a development dependency" do + let(:platform_specific_type) { :development } + + it "keeps java dependencies in the lockfile" do + expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 RUBY" + expect(lockfile).to eq normalize_uri_file(strip_whitespace(<<-L)) + GEM + remote: file://localhost#{gem_repo2}/ + specs: + platform_specific (1.0) + platform_specific (1.0-java) + + PATH + remote: . + specs: + foo (1.0) + + PLATFORMS + java + ruby + + DEPENDENCIES + foo! + platform_specific + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "with an indirect platform-specific development dependency" do + let(:platform_specific_type) { :development } + let(:dependency) { "indirect_platform_specific" } + + it "keeps java dependencies in the lockfile" do + expect(the_bundle).to include_gems "foo 1.0", "indirect_platform_specific 1.0", "platform_specific 1.0 RUBY" + expect(lockfile).to eq normalize_uri_file(strip_whitespace(<<-L)) + GEM + remote: file://localhost#{gem_repo2}/ + specs: + indirect_platform_specific (1.0) + platform_specific + platform_specific (1.0) + platform_specific (1.0-java) + + PATH + remote: . + specs: + foo (1.0) + + PLATFORMS + java + ruby + + DEPENDENCIES + foo! + indirect_platform_specific + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + end + end + end + + context "with multiple platforms" do + before do + build_lib("foo", :path => tmp.join("foo")) do |s| + s.version = "1.0.0" + s.add_development_dependency "rack" + s.write "foo-universal-java.gemspec", build_spec("foo", "1.0.0", "universal-java") {|sj| sj.runtime "rack", "1.0.0" }.first.to_ruby + end + end + + it "installs the ruby platform gemspec" do + simulate_platform "ruby" + + install_gemfile! <<-G + source "file://#{gem_repo1}" + gemspec :path => '#{tmp.join("foo")}', :name => 'foo' + G + + expect(the_bundle).to include_gems "foo 1.0.0", "rack 1.0.0" + end + + it "installs the ruby platform gemspec and skips dev deps with --without development" do + simulate_platform "ruby" + + install_gemfile! <<-G, forgotten_command_line_options(:without => "development") + source "file://#{gem_repo1}" + gemspec :path => '#{tmp.join("foo")}', :name => 'foo' + G + + expect(the_bundle).to include_gem "foo 1.0.0" + expect(the_bundle).not_to include_gem "rack" + end + end +end diff --git a/spec/bundler/install/gemfile/git_spec.rb b/spec/bundler/install/gemfile/git_spec.rb new file mode 100644 index 0000000000..57d83a5295 --- /dev/null +++ b/spec/bundler/install/gemfile/git_spec.rb @@ -0,0 +1,1351 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install with git sources" do + describe "when floating on master" do + before :each do + build_git "foo" do |s| + s.executables = "foobar" + end + + install_gemfile <<-G + source "file://#{gem_repo1}" + git "#{lib_path("foo-1.0")}" do + gem 'foo' + end + G + end + + it "fetches gems" do + expect(the_bundle).to include_gems("foo 1.0") + + run <<-RUBY + require 'foo' + puts "WIN" unless defined?(FOO_PREV_REF) + RUBY + + expect(out).to eq("WIN") + end + + it "caches the git repo", :bundler => "< 2" do + expect(Dir["#{default_bundle_path}/cache/bundler/git/foo-1.0-*"]).to have_attributes :size => 1 + end + + it "caches the git repo globally" do + simulate_new_machine + bundle! "config global_gem_cache true" + bundle! :install + expect(Dir["#{home}/.bundle/cache/git/foo-1.0-*"]).to have_attributes :size => 1 + end + + it "caches the evaluated gemspec" do + git = update_git "foo" do |s| + s.executables = ["foobar"] # we added this the first time, so keep it now + s.files = ["bin/foobar"] # updating git nukes the files list + foospec = s.to_ruby.gsub(/s\.files.*/, 's.files = `git ls-files -z`.split("\x0")') + s.write "foo.gemspec", foospec + end + + bundle "update foo" + + sha = git.ref_for("master", 11) + spec_file = default_bundle_path.join("bundler/gems/foo-1.0-#{sha}/foo.gemspec").to_s + ruby_code = Gem::Specification.load(spec_file).to_ruby + file_code = File.read(spec_file) + expect(file_code).to eq(ruby_code) + end + + it "does not update the git source implicitly" do + update_git "foo" + + in_app_root2 do + install_gemfile bundled_app2("Gemfile"), <<-G + git "#{lib_path("foo-1.0")}" do + gem 'foo' + end + G + end + + in_app_root do + run <<-RUBY + require 'foo' + puts "fail" if defined?(FOO_PREV_REF) + RUBY + + expect(out).to be_empty + end + end + + it "sets up git gem executables on the path" do + bundle "exec foobar" + expect(out).to eq("1.0") + end + + it "complains if pinned specs don't exist in the git repo" do + build_git "foo" + + install_gemfile <<-G + gem "foo", "1.1", :git => "#{lib_path("foo-1.0")}" + G + + expect(out).to include("The source contains 'foo' at: 1.0") + end + + it "complains with version and platform if pinned specs don't exist in the git repo" do + simulate_platform "java" + + build_git "only_java" do |s| + s.platform = "java" + end + + install_gemfile <<-G + platforms :jruby do + gem "only_java", "1.2", :git => "#{lib_path("only_java-1.0-java")}" + end + G + + expect(out).to include("The source contains 'only_java' at: 1.0 java") + end + + it "complains with multiple versions and platforms if pinned specs don't exist in the git repo" do + simulate_platform "java" + + build_git "only_java", "1.0" do |s| + s.platform = "java" + end + + build_git "only_java", "1.1" do |s| + s.platform = "java" + s.write "only_java1-0.gemspec", File.read("#{lib_path("only_java-1.0-java")}/only_java.gemspec") + end + + install_gemfile <<-G + platforms :jruby do + gem "only_java", "1.2", :git => "#{lib_path("only_java-1.1-java")}" + end + G + + expect(out).to include("The source contains 'only_java' at: 1.0 java, 1.1 java") + end + + it "still works after moving the application directory" do + bundle "install --path vendor/bundle" + FileUtils.mv bundled_app, tmp("bundled_app.bck") + + Dir.chdir tmp("bundled_app.bck") + expect(the_bundle).to include_gems "foo 1.0" + end + + it "can still install after moving the application directory" do + bundle "install --path vendor/bundle" + FileUtils.mv bundled_app, tmp("bundled_app.bck") + + update_git "foo", "1.1", :path => lib_path("foo-1.0") + + Dir.chdir tmp("bundled_app.bck") + gemfile tmp("bundled_app.bck/Gemfile"), <<-G + source "file://#{gem_repo1}" + git "#{lib_path("foo-1.0")}" do + gem 'foo' + end + + gem "rack", "1.0" + G + + bundle "update foo" + + expect(the_bundle).to include_gems "foo 1.1", "rack 1.0" + end + end + + describe "with an empty git block" do + before do + build_git "foo" + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + + git "#{lib_path("foo-1.0")}" do + # this page left intentionally blank + end + G + end + + it "does not explode" do + bundle "install" + expect(the_bundle).to include_gems "rack 1.0" + end + end + + describe "when specifying a revision" do + before(:each) do + build_git "foo" + @revision = revision_for(lib_path("foo-1.0")) + update_git "foo" + end + + it "works" do + install_gemfile <<-G + git "#{lib_path("foo-1.0")}", :ref => "#{@revision}" do + gem "foo" + end + G + + run <<-RUBY + require 'foo' + puts "WIN" unless defined?(FOO_PREV_REF) + RUBY + + expect(out).to eq("WIN") + end + + it "works when the revision is a symbol" do + install_gemfile <<-G + git "#{lib_path("foo-1.0")}", :ref => #{@revision.to_sym.inspect} do + gem "foo" + end + G + expect(err).to lack_errors + + run <<-RUBY + require 'foo' + puts "WIN" unless defined?(FOO_PREV_REF) + RUBY + + expect(out).to eq("WIN") + end + + it "works when the revision is a non-head ref" do + # want to ensure we don't fallback to master + update_git "foo", :path => lib_path("foo-1.0") do |s| + s.write("lib/foo.rb", "raise 'FAIL'") + end + + Dir.chdir(lib_path("foo-1.0")) do + `git update-ref -m 'Bundler Spec!' refs/bundler/1 master~1` + end + + # want to ensure we don't fallback to HEAD + update_git "foo", :path => lib_path("foo-1.0"), :branch => "rando" do |s| + s.write("lib/foo.rb", "raise 'FAIL'") + end + + install_gemfile! <<-G + git "#{lib_path("foo-1.0")}", :ref => "refs/bundler/1" do + gem "foo" + end + G + expect(err).to lack_errors + + run! <<-RUBY + require 'foo' + puts "WIN" if defined?(FOO) + RUBY + + expect(out).to eq("WIN") + end + + it "works when the revision is a non-head ref and it was previously downloaded" do + install_gemfile! <<-G + git "#{lib_path("foo-1.0")}" do + gem "foo" + end + G + + # want to ensure we don't fallback to master + update_git "foo", :path => lib_path("foo-1.0") do |s| + s.write("lib/foo.rb", "raise 'FAIL'") + end + + Dir.chdir(lib_path("foo-1.0")) do + `git update-ref -m 'Bundler Spec!' refs/bundler/1 master~1` + end + + # want to ensure we don't fallback to HEAD + update_git "foo", :path => lib_path("foo-1.0"), :branch => "rando" do |s| + s.write("lib/foo.rb", "raise 'FAIL'") + end + + install_gemfile! <<-G + git "#{lib_path("foo-1.0")}", :ref => "refs/bundler/1" do + gem "foo" + end + G + expect(err).to lack_errors + + run! <<-RUBY + require 'foo' + puts "WIN" if defined?(FOO) + RUBY + + expect(out).to eq("WIN") + end + + it "does not download random non-head refs" do + Dir.chdir(lib_path("foo-1.0")) do + sys_exec!("git update-ref -m 'Bundler Spec!' refs/bundler/1 master~1") + end + + bundle! "config global_gem_cache true" + + install_gemfile! <<-G + git "#{lib_path("foo-1.0")}" do + gem "foo" + end + G + + # ensure we also git fetch after cloning + bundle! :update, :all => bundle_update_requires_all? + + Dir.chdir(Dir[home(".bundle/cache/git/foo-*")].first) do + sys_exec("git ls-remote .") + end + + expect(out).not_to include("refs/bundler/1") + end + end + + describe "when specifying a branch" do + let(:branch) { "branch" } + let(:repo) { build_git("foo").path } + before(:each) do + update_git("foo", :path => repo, :branch => branch) + end + + it "works" do + install_gemfile <<-G + git "#{repo}", :branch => #{branch.dump} do + gem "foo" + end + G + + expect(the_bundle).to include_gems("foo 1.0") + end + + context "when the branch starts with a `#`" do + let(:branch) { "#149/redirect-url-fragment" } + it "works" do + install_gemfile <<-G + git "#{repo}", :branch => #{branch.dump} do + gem "foo" + end + G + + expect(the_bundle).to include_gems("foo 1.0") + end + end + + context "when the branch includes quotes" do + let(:branch) { %('") } + it "works" do + install_gemfile <<-G + git "#{repo}", :branch => #{branch.dump} do + gem "foo" + end + G + + expect(the_bundle).to include_gems("foo 1.0") + end + end + end + + describe "when specifying a tag" do + let(:tag) { "tag" } + let(:repo) { build_git("foo").path } + before(:each) do + update_git("foo", :path => repo, :tag => tag) + end + + it "works" do + install_gemfile <<-G + git "#{repo}", :tag => #{tag.dump} do + gem "foo" + end + G + + expect(the_bundle).to include_gems("foo 1.0") + end + + context "when the tag starts with a `#`" do + let(:tag) { "#149/redirect-url-fragment" } + it "works" do + install_gemfile <<-G + git "#{repo}", :tag => #{tag.dump} do + gem "foo" + end + G + + expect(the_bundle).to include_gems("foo 1.0") + end + end + + context "when the tag includes quotes" do + let(:tag) { %('") } + it "works" do + install_gemfile <<-G + git "#{repo}", :tag => #{tag.dump} do + gem "foo" + end + G + + expect(the_bundle).to include_gems("foo 1.0") + end + end + end + + describe "when specifying local override" do + it "uses the local repository instead of checking a new one out" do + # We don't generate it because we actually don't need it + # build_git "rack", "0.8" + + build_git "rack", "0.8", :path => lib_path("local-rack") do |s| + s.write "lib/rack.rb", "puts :LOCAL" + end + + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + G + + bundle! %(config local.rack #{lib_path("local-rack")}) + bundle! :install + + run "require 'rack'" + expect(out).to eq("LOCAL") + end + + it "chooses the local repository on runtime" do + build_git "rack", "0.8" + + FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) + + update_git "rack", "0.8", :path => lib_path("local-rack") do |s| + s.write "lib/rack.rb", "puts :LOCAL" + end + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + G + + bundle %(config local.rack #{lib_path("local-rack")}) + run "require 'rack'" + expect(out).to eq("LOCAL") + end + + it "unlocks the source when the dependencies have changed while switching to the local" do + build_git "rack", "0.8" + + FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) + + update_git "rack", "0.8", :path => lib_path("local-rack") do |s| + s.write "rack.gemspec", build_spec("rack", "0.8") { runtime "rspec", "> 0" }.first.to_ruby + s.write "lib/rack.rb", "puts :LOCAL" + end + + install_gemfile! <<-G + source "file://#{gem_repo1}" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + G + + bundle! %(config local.rack #{lib_path("local-rack")}) + bundle! :install + run! "require 'rack'" + expect(out).to eq("LOCAL") + end + + it "updates specs on runtime" do + system_gems "nokogiri-1.4.2" + + build_git "rack", "0.8" + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + G + + lockfile0 = File.read(bundled_app("Gemfile.lock")) + + FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) + update_git "rack", "0.8", :path => lib_path("local-rack") do |s| + s.add_dependency "nokogiri", "1.4.2" + end + + bundle %(config local.rack #{lib_path("local-rack")}) + run "require 'rack'" + + lockfile1 = File.read(bundled_app("Gemfile.lock")) + expect(lockfile1).not_to eq(lockfile0) + end + + it "updates ref on install" do + build_git "rack", "0.8" + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + G + + lockfile0 = File.read(bundled_app("Gemfile.lock")) + + FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) + update_git "rack", "0.8", :path => lib_path("local-rack") + + bundle %(config local.rack #{lib_path("local-rack")}) + bundle :install + + lockfile1 = File.read(bundled_app("Gemfile.lock")) + expect(lockfile1).not_to eq(lockfile0) + end + + it "explodes if given path does not exist on install" do + build_git "rack", "0.8" + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + G + + bundle %(config local.rack #{lib_path("local-rack")}) + bundle :install + expect(out).to match(/Cannot use local override for rack-0.8 because #{Regexp.escape(lib_path('local-rack').to_s)} does not exist/) + end + + it "explodes if branch is not given on install" do + build_git "rack", "0.8" + FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", :git => "#{lib_path("rack-0.8")}" + G + + bundle %(config local.rack #{lib_path("local-rack")}) + bundle :install + expect(out).to match(/cannot use local override/i) + end + + it "does not explode if disable_local_branch_check is given" do + build_git "rack", "0.8" + FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", :git => "#{lib_path("rack-0.8")}" + G + + bundle %(config local.rack #{lib_path("local-rack")}) + bundle %(config disable_local_branch_check true) + bundle :install + expect(out).to match(/Bundle complete!/) + end + + it "explodes on different branches on install" do + build_git "rack", "0.8" + + FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) + + update_git "rack", "0.8", :path => lib_path("local-rack"), :branch => "another" do |s| + s.write "lib/rack.rb", "puts :LOCAL" + end + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + G + + bundle %(config local.rack #{lib_path("local-rack")}) + bundle :install + expect(out).to match(/is using branch another but Gemfile specifies master/) + end + + it "explodes on invalid revision on install" do + build_git "rack", "0.8" + + build_git "rack", "0.8", :path => lib_path("local-rack") do |s| + s.write "lib/rack.rb", "puts :LOCAL" + end + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + G + + bundle %(config local.rack #{lib_path("local-rack")}) + bundle :install + expect(out).to match(/The Gemfile lock is pointing to revision \w+/) + end + end + + describe "specified inline" do + # TODO: Figure out how to write this test so that it is not flaky depending + # on the current network situation. + # it "supports private git URLs" do + # gemfile <<-G + # gem "thingy", :git => "git@notthere.fallingsnow.net:somebody/thingy.git" + # G + # + # bundle :install + # + # # p out + # # p err + # puts err unless err.empty? # This spec fails randomly every so often + # err.should include("notthere.fallingsnow.net") + # err.should include("ssh") + # end + + it "installs from git even if a newer gem is available elsewhere" do + build_git "rack", "0.8" + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", :git => "#{lib_path("rack-0.8")}" + G + + expect(the_bundle).to include_gems "rack 0.8" + end + + it "installs dependencies from git even if a newer gem is available elsewhere" do + system_gems "rack-1.0.0" + + build_lib "rack", "1.0", :path => lib_path("nested/bar") do |s| + s.write "lib/rack.rb", "puts 'WIN OVERRIDE'" + end + + build_git "foo", :path => lib_path("nested") do |s| + s.add_dependency "rack", "= 1.0" + end + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "foo", :git => "#{lib_path("nested")}" + G + + run "require 'rack'" + expect(out).to eq("WIN OVERRIDE") + end + + it "correctly unlocks when changing to a git source" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", "0.9.1" + G + + build_git "rack", :path => lib_path("rack") + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", "1.0.0", :git => "#{lib_path("rack")}" + G + + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "correctly unlocks when changing to a git source without versions" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + build_git "rack", "1.2", :path => lib_path("rack") + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", :git => "#{lib_path("rack")}" + G + + expect(the_bundle).to include_gems "rack 1.2" + end + end + + describe "block syntax" do + it "pulls all gems from a git block" do + build_lib "omg", :path => lib_path("hi2u/omg") + build_lib "hi2u", :path => lib_path("hi2u") + + install_gemfile <<-G + path "#{lib_path("hi2u")}" do + gem "omg" + gem "hi2u" + end + G + + expect(the_bundle).to include_gems "omg 1.0", "hi2u 1.0" + end + end + + it "uses a ref if specified" do + build_git "foo" + @revision = revision_for(lib_path("foo-1.0")) + update_git "foo" + + install_gemfile <<-G + gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "#{@revision}" + G + + run <<-RUBY + require 'foo' + puts "WIN" unless defined?(FOO_PREV_REF) + RUBY + + expect(out).to eq("WIN") + end + + it "correctly handles cases with invalid gemspecs" do + build_git "foo" do |s| + s.summary = nil + end + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "foo", :git => "#{lib_path("foo-1.0")}" + gem "rails", "2.3.2" + G + + expect(the_bundle).to include_gems "foo 1.0" + expect(the_bundle).to include_gems "rails 2.3.2" + end + + it "runs the gemspec in the context of its parent directory" do + build_lib "bar", :path => lib_path("foo/bar"), :gemspec => false do |s| + s.write lib_path("foo/bar/lib/version.rb"), %(BAR_VERSION = '1.0') + s.write "bar.gemspec", <<-G + $:.unshift Dir.pwd # For 1.9 + require 'lib/version' + Gem::Specification.new do |s| + s.name = 'bar' + s.author = 'no one' + s.version = BAR_VERSION + s.summary = 'Bar' + s.files = Dir["lib/**/*.rb"] + end + G + end + + build_git "foo", :path => lib_path("foo") do |s| + s.write "bin/foo", "" + end + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "bar", :git => "#{lib_path("foo")}" + gem "rails", "2.3.2" + G + + expect(the_bundle).to include_gems "bar 1.0" + expect(the_bundle).to include_gems "rails 2.3.2" + end + + it "installs from git even if a rubygems gem is present" do + build_gem "foo", "1.0", :path => lib_path("fake_foo"), :to_system => true do |s| + s.write "lib/foo.rb", "raise 'FAIL'" + end + + build_git "foo", "1.0" + + install_gemfile <<-G + gem "foo", "1.0", :git => "#{lib_path("foo-1.0")}" + G + + expect(the_bundle).to include_gems "foo 1.0" + end + + it "fakes the gem out if there is no gemspec" do + build_git "foo", :gemspec => false + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "foo", "1.0", :git => "#{lib_path("foo-1.0")}" + gem "rails", "2.3.2" + G + + expect(the_bundle).to include_gems("foo 1.0") + expect(the_bundle).to include_gems("rails 2.3.2") + end + + it "catches git errors and spits out useful output" do + gemfile <<-G + gem "foo", "1.0", :git => "omgomg" + G + + bundle :install + + expect(out).to include("Git error:") + expect(err).to include("fatal") + expect(err).to include("omgomg") + end + + it "works when the gem path has spaces in it" do + build_git "foo", :path => lib_path("foo space-1.0") + + install_gemfile <<-G + gem "foo", :git => "#{lib_path("foo space-1.0")}" + G + + expect(the_bundle).to include_gems "foo 1.0" + end + + it "handles repos that have been force-pushed" do + build_git "forced", "1.0" + + install_gemfile <<-G + git "#{lib_path("forced-1.0")}" do + gem 'forced' + end + G + expect(the_bundle).to include_gems "forced 1.0" + + update_git "forced" do |s| + s.write "lib/forced.rb", "FORCED = '1.1'" + end + + bundle "update", :all => bundle_update_requires_all? + expect(the_bundle).to include_gems "forced 1.1" + + Dir.chdir(lib_path("forced-1.0")) do + `git reset --hard HEAD^` + end + + bundle "update", :all => bundle_update_requires_all? + expect(the_bundle).to include_gems "forced 1.0" + end + + it "ignores submodules if :submodule is not passed" do + build_git "submodule", "1.0" + build_git "has_submodule", "1.0" do |s| + s.add_dependency "submodule" + end + Dir.chdir(lib_path("has_submodule-1.0")) do + sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0" + `git commit -m "submodulator"` + end + + install_gemfile <<-G + git "#{lib_path("has_submodule-1.0")}" do + gem "has_submodule" + end + G + expect(out).to match(/could not find gem 'submodule/i) + + expect(the_bundle).not_to include_gems "has_submodule 1.0" + end + + it "handles repos with submodules" do + build_git "submodule", "1.0" + build_git "has_submodule", "1.0" do |s| + s.add_dependency "submodule" + end + Dir.chdir(lib_path("has_submodule-1.0")) do + sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0" + `git commit -m "submodulator"` + end + + install_gemfile <<-G + git "#{lib_path("has_submodule-1.0")}", :submodules => true do + gem "has_submodule" + end + G + + expect(the_bundle).to include_gems "has_submodule 1.0" + end + + it "handles implicit updates when modifying the source info" do + git = build_git "foo" + + install_gemfile <<-G + git "#{lib_path("foo-1.0")}" do + gem "foo" + end + G + + update_git "foo" + update_git "foo" + + install_gemfile <<-G + git "#{lib_path("foo-1.0")}", :ref => "#{git.ref_for("HEAD^")}" do + gem "foo" + end + G + + run <<-RUBY + require 'foo' + puts "WIN" if FOO_PREV_REF == '#{git.ref_for("HEAD^^")}' + RUBY + + expect(out).to eq("WIN") + end + + it "does not to a remote fetch if the revision is cached locally" do + build_git "foo" + + install_gemfile <<-G + gem "foo", :git => "#{lib_path("foo-1.0")}" + G + + FileUtils.rm_rf(lib_path("foo-1.0")) + + bundle "install" + expect(out).not_to match(/updating/i) + end + + it "doesn't blow up if bundle install is run twice in a row" do + build_git "foo" + + gemfile <<-G + gem "foo", :git => "#{lib_path("foo-1.0")}" + G + + bundle "install" + bundle "install" + expect(exitstatus).to eq(0) if exitstatus + end + + it "prints a friendly error if a file blocks the git repo" do + build_git "foo" + + FileUtils.mkdir_p(default_bundle_path) + FileUtils.touch(default_bundle_path("bundler")) + + install_gemfile <<-G + gem "foo", :git => "#{lib_path("foo-1.0")}" + G + + expect(exitstatus).to_not eq(0) if exitstatus + expect(out).to include("Bundler could not install a gem because it " \ + "needs to create a directory, but a file exists " \ + "- #{default_bundle_path("bundler")}") + end + + it "does not duplicate git gem sources" do + build_lib "foo", :path => lib_path("nested/foo") + build_lib "bar", :path => lib_path("nested/bar") + + build_git "foo", :path => lib_path("nested") + build_git "bar", :path => lib_path("nested") + + gemfile <<-G + gem "foo", :git => "#{lib_path("nested")}" + gem "bar", :git => "#{lib_path("nested")}" + G + + bundle "install" + expect(File.read(bundled_app("Gemfile.lock")).scan("GIT").size).to eq(1) + end + + describe "switching sources" do + it "doesn't explode when switching Path to Git sources" do + build_gem "foo", "1.0", :to_system => true do |s| + s.write "lib/foo.rb", "raise 'fail'" + end + build_lib "foo", "1.0", :path => lib_path("bar/foo") + build_git "bar", "1.0", :path => lib_path("bar") do |s| + s.add_dependency "foo" + end + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "bar", :path => "#{lib_path("bar")}" + G + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "bar", :git => "#{lib_path("bar")}" + G + + expect(the_bundle).to include_gems "foo 1.0", "bar 1.0" + end + + it "doesn't explode when switching Gem to Git source" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack-obama" + gem "rack", "1.0.0" + G + + build_git "rack", "1.0" do |s| + s.write "lib/new_file.rb", "puts 'USING GIT'" + end + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack-obama" + gem "rack", "1.0.0", :git => "#{lib_path("rack-1.0")}" + G + + run "require 'new_file'" + expect(out).to eq("USING GIT") + end + end + + describe "bundle install after the remote has been updated" do + it "installs" do + build_git "valim" + + install_gemfile <<-G + gem "valim", :git => "file://#{lib_path("valim-1.0")}" + G + + old_revision = revision_for(lib_path("valim-1.0")) + update_git "valim" + new_revision = revision_for(lib_path("valim-1.0")) + + lockfile = File.read(bundled_app("Gemfile.lock")) + File.open(bundled_app("Gemfile.lock"), "w") do |file| + file.puts lockfile.gsub(/revision: #{old_revision}/, "revision: #{new_revision}") + end + + bundle "install" + + run <<-R + require "valim" + puts VALIM_PREV_REF + R + + expect(out).to eq(old_revision) + end + + it "gives a helpful error message when the remote ref no longer exists" do + build_git "foo" + revision = revision_for(lib_path("foo-1.0")) + + install_gemfile <<-G + gem "foo", :git => "file://#{lib_path("foo-1.0")}", :ref => "#{revision}" + G + bundle "install" + expect(out).to_not match(/Revision.*does not exist/) + + install_gemfile <<-G + gem "foo", :git => "file://#{lib_path("foo-1.0")}", :ref => "deadbeef" + G + bundle "install" + expect(out).to include("Revision deadbeef does not exist in the repository") + end + end + + describe "bundle install --deployment with git sources" do + it "works" do + build_git "valim", :path => lib_path("valim") + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "valim", "= 1.0", :git => "#{lib_path("valim")}" + G + + simulate_new_machine + + bundle! :install, forgotten_command_line_options(:deployment => true) + end + end + + describe "gem install hooks" do + it "runs pre-install hooks" do + build_git "foo" + gemfile <<-G + gem "foo", :git => "#{lib_path("foo-1.0")}" + G + + File.open(lib_path("install_hooks.rb"), "w") do |h| + h.write <<-H + require 'rubygems' + Gem.pre_install_hooks << lambda do |inst| + STDERR.puts "Ran pre-install hook: \#{inst.spec.full_name}" + end + H + end + + bundle :install, + :requires => [lib_path("install_hooks.rb")] + expect(err).to eq_err("Ran pre-install hook: foo-1.0") + end + + it "runs post-install hooks" do + build_git "foo" + gemfile <<-G + gem "foo", :git => "#{lib_path("foo-1.0")}" + G + + File.open(lib_path("install_hooks.rb"), "w") do |h| + h.write <<-H + require 'rubygems' + Gem.post_install_hooks << lambda do |inst| + STDERR.puts "Ran post-install hook: \#{inst.spec.full_name}" + end + H + end + + bundle :install, + :requires => [lib_path("install_hooks.rb")] + expect(err).to eq_err("Ran post-install hook: foo-1.0") + end + + it "complains if the install hook fails" do + build_git "foo" + gemfile <<-G + gem "foo", :git => "#{lib_path("foo-1.0")}" + G + + File.open(lib_path("install_hooks.rb"), "w") do |h| + h.write <<-H + require 'rubygems' + Gem.pre_install_hooks << lambda do |inst| + false + end + H + end + + bundle :install, + :requires => [lib_path("install_hooks.rb")] + expect(out).to include("failed for foo-1.0") + end + end + + context "with an extension" do + it "installs the extension", :ruby_repo do + build_git "foo" do |s| + s.add_dependency "rake" + s.extensions << "Rakefile" + s.write "Rakefile", <<-RUBY + task :default do + path = File.expand_path("../lib", __FILE__) + FileUtils.mkdir_p(path) + File.open("\#{path}/foo.rb", "w") do |f| + f.puts "FOO = 'YES'" + end + end + RUBY + end + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "foo", :git => "#{lib_path("foo-1.0")}" + G + + run <<-R + require 'foo' + puts FOO + R + expect(out).to eq("YES") + + run! <<-R + puts $:.grep(/ext/) + R + expect(out).to eq(Pathname.glob(default_bundle_path("bundler/gems/extensions/**/foo-1.0-*")).first.to_s) + end + + it "does not use old extension after ref changes", :ruby_repo do + git_reader = build_git "foo", :no_default => true do |s| + s.extensions = ["ext/extconf.rb"] + s.write "ext/extconf.rb", <<-RUBY + require "mkmf" + create_makefile("foo") + RUBY + s.write "ext/foo.c", "void Init_foo() {}" + end + + 2.times do |i| + Dir.chdir(git_reader.path) do + File.open("ext/foo.c", "w") do |file| + file.write <<-C + #include "ruby.h" + VALUE foo() { return INT2FIX(#{i}); } + void Init_foo() { rb_define_global_function("foo", &foo, 0); } + C + end + `git commit -m 'commit for iteration #{i}' ext/foo.c` + end + git_commit_sha = git_reader.ref_for("HEAD") + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "#{git_commit_sha}" + G + + run <<-R + require 'foo' + puts foo + R + + expect(out).to eq(i.to_s) + end + end + + it "does not prompt to gem install if extension fails" do + build_git "foo" do |s| + s.add_dependency "rake" + s.extensions << "Rakefile" + s.write "Rakefile", <<-RUBY + task :default do + raise + end + RUBY + end + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "foo", :git => "#{lib_path("foo-1.0")}" + G + + expect(last_command.bundler_err).to end_with(<<-M.strip) +An error occurred while installing foo (1.0), and Bundler cannot continue. + +In Gemfile: + foo + M + expect(out).not_to include("gem install foo") + end + + it "does not reinstall the extension", :ruby_repo, :rubygems => ">= 2.3.0" do + build_git "foo" do |s| + s.add_dependency "rake" + s.extensions << "Rakefile" + s.write "Rakefile", <<-RUBY + task :default do + path = File.expand_path("../lib", __FILE__) + FileUtils.mkdir_p(path) + cur_time = Time.now.to_f.to_s + File.open("\#{path}/foo.rb", "w") do |f| + f.puts "FOO = \#{cur_time}" + end + end + RUBY + end + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "foo", :git => "#{lib_path("foo-1.0")}" + G + + run! <<-R + require 'foo' + puts FOO + R + + installed_time = out + expect(installed_time).to match(/\A\d+\.\d+\z/) + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "foo", :git => "#{lib_path("foo-1.0")}" + G + + run! <<-R + require 'foo' + puts FOO + R + expect(out).to eq(installed_time) + end + end + + it "ignores git environment variables" do + build_git "xxxxxx" do |s| + s.executables = "xxxxxxbar" + end + + Bundler::SharedHelpers.with_clean_git_env do + ENV["GIT_DIR"] = "bar" + ENV["GIT_WORK_TREE"] = "bar" + + install_gemfile <<-G + source "file://#{gem_repo1}" + git "#{lib_path("xxxxxx-1.0")}" do + gem 'xxxxxx' + end + G + + expect(exitstatus).to eq(0) if exitstatus + expect(ENV["GIT_DIR"]).to eq("bar") + expect(ENV["GIT_WORK_TREE"]).to eq("bar") + end + end + + describe "without git installed" do + it "prints a better error message" do + build_git "foo" + + install_gemfile <<-G + git "#{lib_path("foo-1.0")}" do + gem 'foo' + end + G + + with_path_as("") do + bundle "update", :all => bundle_update_requires_all? + end + expect(last_command.bundler_err). + to include("You need to install git to be able to use gems from git repositories. For help installing git, please refer to GitHub's tutorial at https://help.github.com/articles/set-up-git") + end + + it "installs a packaged git gem successfully" do + build_git "foo" + + install_gemfile <<-G + git "#{lib_path("foo-1.0")}" do + gem 'foo' + end + G + bundle :package, forgotten_command_line_options([:all, :cache_all] => true) + simulate_new_machine + + bundle! "install", :env => { "PATH" => "" } + expect(out).to_not include("You need to install git to be able to use gems from git repositories.") + end + end + + describe "when the git source is overridden with a local git repo" do + before do + bundle! "config --global local.foo #{lib_path("foo")}" + end + + describe "and git output is colorized" do + before do + File.open("#{ENV["HOME"]}/.gitconfig", "w") do |f| + f.write("[color]\n\tui = always\n") + end + end + + it "installs successfully" do + build_git "foo", "1.0", :path => lib_path("foo") + + gemfile <<-G + gem "foo", :git => "#{lib_path("foo")}", :branch => "master" + G + + bundle :install + expect(the_bundle).to include_gems "foo 1.0" + end + end + end + + context "git sources that include credentials" do + context "that are username and password" do + let(:credentials) { "user1:password1" } + + it "does not display the password" do + install_gemfile <<-G + git "https://#{credentials}@github.com/company/private-repo" do + gem "foo" + end + G + + bundle :install + expect(last_command.stdboth).to_not include("password1") + expect(last_command.stdout).to include("Fetching https://user1@github.com/company/private-repo") + end + end + + context "that is an oauth token" do + let(:credentials) { "oauth_token" } + + it "displays the oauth scheme but not the oauth token" do + install_gemfile <<-G + git "https://#{credentials}:x-oauth-basic@github.com/company/private-repo" do + gem "foo" + end + G + + bundle :install + expect(last_command.stdboth).to_not include("oauth_token") + expect(last_command.stdout).to include("Fetching https://x-oauth-basic@github.com/company/private-repo") + end + end + end +end diff --git a/spec/bundler/install/gemfile/groups_spec.rb b/spec/bundler/install/gemfile/groups_spec.rb new file mode 100644 index 0000000000..19c379e188 --- /dev/null +++ b/spec/bundler/install/gemfile/groups_spec.rb @@ -0,0 +1,384 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install with groups" do + describe "installing with no options" do + before :each do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + group :emo do + gem "activesupport", "2.3.5" + end + gem "thin", :groups => [:emo] + G + end + + it "installs gems in the default group" do + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "installs gems in a group block into that group" do + expect(the_bundle).to include_gems "activesupport 2.3.5" + + load_error_run <<-R, "activesupport", :default + require 'activesupport' + puts ACTIVESUPPORT + R + + expect(err).to eq_err("ZOMG LOAD ERROR") + end + + it "installs gems with inline :groups into those groups" do + expect(the_bundle).to include_gems "thin 1.0" + + load_error_run <<-R, "thin", :default + require 'thin' + puts THIN + R + + expect(err).to eq_err("ZOMG LOAD ERROR") + end + + it "sets up everything if Bundler.setup is used with no groups" do + output = run("require 'rack'; puts RACK") + expect(output).to eq("1.0.0") + + output = run("require 'activesupport'; puts ACTIVESUPPORT") + expect(output).to eq("2.3.5") + + output = run("require 'thin'; puts THIN") + expect(output).to eq("1.0") + end + + it "removes old groups when new groups are set up" do + load_error_run <<-RUBY, "thin", :emo + Bundler.setup(:default) + require 'thin' + puts THIN + RUBY + + expect(err).to eq_err("ZOMG LOAD ERROR") + end + + it "sets up old groups when they have previously been removed" do + output = run <<-RUBY, :emo + Bundler.setup(:default) + Bundler.setup(:default, :emo) + require 'thin'; puts THIN + RUBY + expect(output).to eq("1.0") + end + end + + describe "installing --without" do + describe "with gems assigned to a single group" do + before :each do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + group :emo do + gem "activesupport", "2.3.5" + end + group :debugging, :optional => true do + gem "thin" + end + G + end + + it "installs gems in the default group" do + bundle! :install, forgotten_command_line_options(:without => "emo") + expect(the_bundle).to include_gems "rack 1.0.0", :groups => [:default] + end + + it "does not install gems from the excluded group" do + bundle :install, :without => "emo" + expect(the_bundle).not_to include_gems "activesupport 2.3.5", :groups => [:default] + end + + it "does not install gems from the previously excluded group" do + bundle :install, forgotten_command_line_options(:without => "emo") + expect(the_bundle).not_to include_gems "activesupport 2.3.5" + bundle :install + expect(the_bundle).not_to include_gems "activesupport 2.3.5" + end + + it "does not say it installed gems from the excluded group" do + bundle! :install, forgotten_command_line_options(:without => "emo") + expect(out).not_to include("activesupport") + end + + it "allows Bundler.setup for specific groups" do + bundle :install, forgotten_command_line_options(:without => "emo") + run!("require 'rack'; puts RACK", :default) + expect(out).to eq("1.0.0") + end + + it "does not effect the resolve" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "activesupport" + group :emo do + gem "rails", "2.3.2" + end + G + + bundle :install, forgotten_command_line_options(:without => "emo") + expect(the_bundle).to include_gems "activesupport 2.3.2", :groups => [:default] + end + + it "still works on a different machine and excludes gems" do + bundle :install, forgotten_command_line_options(:without => "emo") + + simulate_new_machine + bundle :install, forgotten_command_line_options(:without => "emo") + + expect(the_bundle).to include_gems "rack 1.0.0", :groups => [:default] + expect(the_bundle).not_to include_gems "activesupport 2.3.5", :groups => [:default] + end + + it "still works when BUNDLE_WITHOUT is set" do + ENV["BUNDLE_WITHOUT"] = "emo" + + bundle :install + expect(out).not_to include("activesupport") + + expect(the_bundle).to include_gems "rack 1.0.0", :groups => [:default] + expect(the_bundle).not_to include_gems "activesupport 2.3.5", :groups => [:default] + + ENV["BUNDLE_WITHOUT"] = nil + end + + it "clears without when passed an empty list" do + bundle :install, forgotten_command_line_options(:without => "emo") + + bundle :install, forgotten_command_line_options(:without => "") + expect(the_bundle).to include_gems "activesupport 2.3.5" + end + + it "doesn't clear without when nothing is passed" do + bundle :install, forgotten_command_line_options(:without => "emo") + + bundle :install + expect(the_bundle).not_to include_gems "activesupport 2.3.5" + end + + it "does not install gems from the optional group" do + bundle :install + expect(the_bundle).not_to include_gems "thin 1.0" + end + + it "does install gems from the optional group when requested" do + bundle :install, forgotten_command_line_options(:with => "debugging") + expect(the_bundle).to include_gems "thin 1.0" + end + + it "does install gems from the previously requested group" do + bundle :install, forgotten_command_line_options(:with => "debugging") + expect(the_bundle).to include_gems "thin 1.0" + bundle :install + expect(the_bundle).to include_gems "thin 1.0" + end + + it "does install gems from the optional groups requested with BUNDLE_WITH" do + ENV["BUNDLE_WITH"] = "debugging" + bundle :install + expect(the_bundle).to include_gems "thin 1.0" + ENV["BUNDLE_WITH"] = nil + end + + it "clears with when passed an empty list" do + bundle :install, forgotten_command_line_options(:with => "debugging") + bundle :install, forgotten_command_line_options(:with => "") + expect(the_bundle).not_to include_gems "thin 1.0" + end + + it "does remove groups from without when passed at --with", :bundler => "< 2" do + bundle :install, forgotten_command_line_options(:without => "emo") + bundle :install, forgotten_command_line_options(:with => "emo") + expect(the_bundle).to include_gems "activesupport 2.3.5" + end + + it "does remove groups from with when passed at --without", :bundler => "< 2" do + bundle :install, forgotten_command_line_options(:with => "debugging") + bundle :install, forgotten_command_line_options(:without => "debugging") + expect(the_bundle).not_to include_gem "thin 1.0" + end + + it "errors out when passing a group to with and without via CLI flags", :bundler => "< 2" do + bundle :install, forgotten_command_line_options(:with => "emo debugging", :without => "emo") + expect(last_command).to be_failure + expect(out).to include("The offending groups are: emo") + end + + it "allows the BUNDLE_WITH setting to override BUNDLE_WITHOUT" do + ENV["BUNDLE_WITH"] = "debugging" + + bundle! :install + expect(the_bundle).to include_gem "thin 1.0" + + ENV["BUNDLE_WITHOUT"] = "debugging" + expect(the_bundle).to include_gem "thin 1.0" + + bundle! :install + expect(the_bundle).to include_gem "thin 1.0" + end + + it "can add and remove a group at the same time" do + bundle :install, forgotten_command_line_options(:with => "debugging", :without => "emo") + expect(the_bundle).to include_gems "thin 1.0" + expect(the_bundle).not_to include_gems "activesupport 2.3.5" + end + + it "does have no effect when listing a not optional group in with" do + bundle :install, forgotten_command_line_options(:with => "emo") + expect(the_bundle).to include_gems "activesupport 2.3.5" + end + + it "does have no effect when listing an optional group in without" do + bundle :install, forgotten_command_line_options(:without => "debugging") + expect(the_bundle).not_to include_gems "thin 1.0" + end + end + + describe "with gems assigned to multiple groups" do + before :each do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + group :emo, :lolercoaster do + gem "activesupport", "2.3.5" + end + G + end + + it "installs gems in the default group" do + bundle! :install, forgotten_command_line_options(:without => "emo lolercoaster") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "installs the gem if any of its groups are installed" do + bundle! :install, forgotten_command_line_options(:without => "emo") + expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.5" + end + + describe "with a gem defined multiple times in different groups" do + before :each do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + + group :emo do + gem "activesupport", "2.3.5" + end + + group :lolercoaster do + gem "activesupport", "2.3.5" + end + G + end + + it "installs the gem w/ option --without emo" do + bundle :install, forgotten_command_line_options(:without => "emo") + expect(the_bundle).to include_gems "activesupport 2.3.5" + end + + it "installs the gem w/ option --without lolercoaster" do + bundle :install, forgotten_command_line_options(:without => "lolercoaster") + expect(the_bundle).to include_gems "activesupport 2.3.5" + end + + it "does not install the gem w/ option --without emo lolercoaster" do + bundle :install, forgotten_command_line_options(:without => "emo lolercoaster") + expect(the_bundle).not_to include_gems "activesupport 2.3.5" + end + + it "does not install the gem w/ option --without 'emo lolercoaster'" do + bundle :install, forgotten_command_line_options(:without => "'emo lolercoaster'") + expect(the_bundle).not_to include_gems "activesupport 2.3.5" + end + end + end + + describe "nesting groups" do + before :each do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + group :emo do + group :lolercoaster do + gem "activesupport", "2.3.5" + end + end + G + end + + it "installs gems in the default group" do + bundle! :install, forgotten_command_line_options(:without => "emo lolercoaster") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "installs the gem if any of its groups are installed" do + bundle! :install, forgotten_command_line_options(:without => "emo") + expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.5" + end + end + end + + describe "when loading only the default group" do + it "should not load all groups" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "activesupport", :groups => :development + G + + ruby <<-R + require "bundler" + Bundler.setup :default + Bundler.require :default + puts RACK + begin + require "activesupport" + rescue LoadError + puts "no activesupport" + end + R + + expect(out).to include("1.0") + expect(out).to include("no activesupport") + end + end + + describe "when locked and installed with --without" do + before(:each) do + build_repo2 + system_gems "rack-0.9.1" do + install_gemfile <<-G, forgotten_command_line_options(:without => "rack") + source "file://#{gem_repo2}" + gem "rack" + + group :rack do + gem "rack_middleware" + end + G + end + end + + it "uses the correct versions even if --without was used on the original" do + expect(the_bundle).to include_gems "rack 0.9.1" + expect(the_bundle).not_to include_gems "rack_middleware 1.0" + simulate_new_machine + + bundle :install + + expect(the_bundle).to include_gems "rack 0.9.1" + expect(the_bundle).to include_gems "rack_middleware 1.0" + end + + it "does not hit the remote a second time" do + FileUtils.rm_rf gem_repo2 + bundle! :install, forgotten_command_line_options(:without => "rack").merge(:verbose => true) + expect(last_command.stdboth).not_to match(/fetching/i) + end + end +end diff --git a/spec/bundler/install/gemfile/install_if.rb b/spec/bundler/install/gemfile/install_if.rb new file mode 100644 index 0000000000..1319051fdb --- /dev/null +++ b/spec/bundler/install/gemfile/install_if.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +describe "bundle install with install_if conditionals" do + it "follows the install_if DSL" do + install_gemfile <<-G + source "file://#{gem_repo1}" + install_if(lambda { true }) do + gem "activesupport", "2.3.5" + end + gem "thin", :install_if => false + install_if(lambda { false }) do + gem "foo" + end + gem "rack" + G + + expect(the_bundle).to include_gems("rack 1.0", "activesupport 2.3.5") + expect(the_bundle).not_to include_gems("thin") + expect(the_bundle).not_to include_gems("foo") + + lockfile_should_be <<-L + GEM + remote: file:#{gem_repo1}/ + specs: + activesupport (2.3.5) + foo (1.0) + rack (1.0.0) + thin (1.0) + rack + + PLATFORMS + ruby + + DEPENDENCIES + activesupport (= 2.3.5) + foo + rack + thin + + BUNDLED WITH + #{Bundler::VERSION} + L + end +end diff --git a/spec/bundler/install/gemfile/lockfile_spec.rb b/spec/bundler/install/gemfile/lockfile_spec.rb new file mode 100644 index 0000000000..dc1baca6ea --- /dev/null +++ b/spec/bundler/install/gemfile/lockfile_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install with a lockfile present" do + let(:gf) { <<-G } + source "file://#{gem_repo1}" + + gem "rack", "1.0.0" + G + + subject do + install_gemfile(gf) + end + + context "gemfile evaluation" do + let(:gf) { super() + "\n\n File.open('evals', 'a') {|f| f << %(1\n) } unless ENV['BUNDLER_SPEC_NO_APPEND']" } + + context "with plugins disabled" do + before do + bundle! "config plugins false" + subject + end + + it "does not evaluate the gemfile twice" do + bundle! :install + + with_env_vars("BUNDLER_SPEC_NO_APPEND" => "1") { expect(the_bundle).to include_gem "rack 1.0.0" } + + # The first eval is from the initial install, we're testing that the + # second install doesn't double-eval + expect(bundled_app("evals").read.lines.to_a.size).to eq(2) + end + + context "when the gem is not installed" do + before { FileUtils.rm_rf ".bundle" } + + it "does not evaluate the gemfile twice" do + bundle! :install + + with_env_vars("BUNDLER_SPEC_NO_APPEND" => "1") { expect(the_bundle).to include_gem "rack 1.0.0" } + + # The first eval is from the initial install, we're testing that the + # second install doesn't double-eval + expect(bundled_app("evals").read.lines.to_a.size).to eq(2) + end + end + end + end +end diff --git a/spec/bundler/install/gemfile/path_spec.rb b/spec/bundler/install/gemfile/path_spec.rb new file mode 100644 index 0000000000..f7789e7ea5 --- /dev/null +++ b/spec/bundler/install/gemfile/path_spec.rb @@ -0,0 +1,630 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install with explicit source paths" do + it "fetches gems with a global path source", :bundler => "< 2" do + build_lib "foo" + + install_gemfile <<-G + path "#{lib_path("foo-1.0")}" + gem 'foo' + G + + expect(the_bundle).to include_gems("foo 1.0") + end + + it "fetches gems" do + build_lib "foo" + + install_gemfile <<-G + path "#{lib_path("foo-1.0")}" do + gem 'foo' + end + G + + expect(the_bundle).to include_gems("foo 1.0") + end + + it "supports pinned paths" do + build_lib "foo" + + install_gemfile <<-G + gem 'foo', :path => "#{lib_path("foo-1.0")}" + G + + expect(the_bundle).to include_gems("foo 1.0") + end + + it "supports relative paths" do + build_lib "foo" + + relative_path = lib_path("foo-1.0").relative_path_from(Pathname.new(Dir.pwd)) + + install_gemfile <<-G + gem 'foo', :path => "#{relative_path}" + G + + expect(the_bundle).to include_gems("foo 1.0") + end + + it "expands paths" do + build_lib "foo" + + relative_path = lib_path("foo-1.0").relative_path_from(Pathname.new("~").expand_path) + + install_gemfile <<-G + gem 'foo', :path => "~/#{relative_path}" + G + + expect(the_bundle).to include_gems("foo 1.0") + end + + it "expands paths raise error with not existing user's home dir" do + build_lib "foo" + username = "some_unexisting_user" + relative_path = lib_path("foo-1.0").relative_path_from(Pathname.new("/home/#{username}").expand_path) + + install_gemfile <<-G + gem 'foo', :path => "~#{username}/#{relative_path}" + G + expect(out).to match("There was an error while trying to use the path `~#{username}/#{relative_path}`.") + expect(out).to match("user #{username} doesn't exist") + end + + it "expands paths relative to Bundler.root" do + build_lib "foo", :path => bundled_app("foo-1.0") + + install_gemfile <<-G + gem 'foo', :path => "./foo-1.0" + G + + bundled_app("subdir").mkpath + Dir.chdir(bundled_app("subdir")) do + expect(the_bundle).to include_gems("foo 1.0") + end + end + + it "expands paths when comparing locked paths to Gemfile paths" do + build_lib "foo", :path => bundled_app("foo-1.0") + + install_gemfile <<-G + gem 'foo', :path => File.expand_path("../foo-1.0", __FILE__) + G + + bundle! :install, forgotten_command_line_options(:frozen => true) + expect(exitstatus).to eq(0) if exitstatus + end + + it "installs dependencies from the path even if a newer gem is available elsewhere" do + system_gems "rack-1.0.0" + + build_lib "rack", "1.0", :path => lib_path("nested/bar") do |s| + s.write "lib/rack.rb", "puts 'WIN OVERRIDE'" + end + + build_lib "foo", :path => lib_path("nested") do |s| + s.add_dependency "rack", "= 1.0" + end + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "foo", :path => "#{lib_path("nested")}" + G + + run "require 'rack'" + expect(out).to eq("WIN OVERRIDE") + end + + it "works" do + build_gem "foo", "1.0.0", :to_system => true do |s| + s.write "lib/foo.rb", "puts 'FAIL'" + end + + build_lib "omg", "1.0", :path => lib_path("omg") do |s| + s.add_dependency "foo" + end + + build_lib "foo", "1.0.0", :path => lib_path("omg/foo") + + install_gemfile <<-G + gem "omg", :path => "#{lib_path("omg")}" + G + + expect(the_bundle).to include_gems "foo 1.0" + end + + it "prefers gemspecs closer to the path root" do + build_lib "premailer", "1.0.0", :path => lib_path("premailer") do |s| + s.write "gemfiles/ruby187.gemspec", <<-G + Gem::Specification.new do |s| + s.name = 'premailer' + s.version = '1.0.0' + s.summary = 'Hi' + s.authors = 'Me' + end + G + end + + install_gemfile <<-G + gem "premailer", :path => "#{lib_path("premailer")}" + G + + # Installation of the 'gemfiles' gemspec would fail since it will be unable + # to require 'premailer.rb' + expect(the_bundle).to include_gems "premailer 1.0.0" + end + + it "warns on invalid specs", :rubygems => "1.7" do + build_lib "foo" + + gemspec = lib_path("foo-1.0").join("foo.gemspec").to_s + File.open(gemspec, "w") do |f| + f.write <<-G + Gem::Specification.new do |s| + s.name = "foo" + end + G + end + + install_gemfile <<-G + gem "foo", :path => "#{lib_path("foo-1.0")}" + G + + expect(out).to_not include("ERROR REPORT") + expect(out).to_not include("Your Gemfile has no gem server sources.") + expect(out).to match(/is not valid. Please fix this gemspec./) + expect(out).to match(/The validation error was 'missing value for attribute version'/) + expect(out).to match(/You have one or more invalid gemspecs that need to be fixed/) + end + + it "supports gemspec syntax" do + build_lib "foo", "1.0", :path => lib_path("foo") do |s| + s.add_dependency "rack", "1.0" + end + + gemfile = <<-G + source "file://#{gem_repo1}" + gemspec + G + + File.open(lib_path("foo/Gemfile"), "w") {|f| f.puts gemfile } + + Dir.chdir(lib_path("foo")) do + bundle "install" + expect(the_bundle).to include_gems "foo 1.0" + expect(the_bundle).to include_gems "rack 1.0" + end + end + + it "supports gemspec syntax with an alternative path" do + build_lib "foo", "1.0", :path => lib_path("foo") do |s| + s.add_dependency "rack", "1.0" + end + + install_gemfile <<-G + source "file://#{gem_repo1}" + gemspec :path => "#{lib_path("foo")}" + G + + expect(the_bundle).to include_gems "foo 1.0" + expect(the_bundle).to include_gems "rack 1.0" + end + + it "doesn't automatically unlock dependencies when using the gemspec syntax" do + build_lib "foo", "1.0", :path => lib_path("foo") do |s| + s.add_dependency "rack", ">= 1.0" + end + + Dir.chdir lib_path("foo") + + install_gemfile lib_path("foo/Gemfile"), <<-G + source "file://#{gem_repo1}" + gemspec + G + + build_gem "rack", "1.0.1", :to_system => true + + bundle "install" + + expect(the_bundle).to include_gems "foo 1.0" + expect(the_bundle).to include_gems "rack 1.0" + end + + it "doesn't automatically unlock dependencies when using the gemspec syntax and the gem has development dependencies" do + build_lib "foo", "1.0", :path => lib_path("foo") do |s| + s.add_dependency "rack", ">= 1.0" + s.add_development_dependency "activesupport" + end + + Dir.chdir lib_path("foo") + + install_gemfile lib_path("foo/Gemfile"), <<-G + source "file://#{gem_repo1}" + gemspec + G + + build_gem "rack", "1.0.1", :to_system => true + + bundle "install" + + expect(the_bundle).to include_gems "foo 1.0" + expect(the_bundle).to include_gems "rack 1.0" + end + + it "raises if there are multiple gemspecs" do + build_lib "foo", "1.0", :path => lib_path("foo") do |s| + s.write "bar.gemspec", build_spec("bar", "1.0").first.to_ruby + end + + install_gemfile <<-G + gemspec :path => "#{lib_path("foo")}" + G + + expect(exitstatus).to eq(15) if exitstatus + expect(out).to match(/There are multiple gemspecs/) + end + + it "allows :name to be specified to resolve ambiguity" do + build_lib "foo", "1.0", :path => lib_path("foo") do |s| + s.write "bar.gemspec" + end + + install_gemfile <<-G + gemspec :path => "#{lib_path("foo")}", :name => "foo" + G + + expect(the_bundle).to include_gems "foo 1.0" + end + + it "sets up executables" do + build_lib "foo" do |s| + s.executables = "foobar" + end + + install_gemfile <<-G + path "#{lib_path("foo-1.0")}" do + gem 'foo' + end + G + expect(the_bundle).to include_gems "foo 1.0" + + bundle "exec foobar" + expect(out).to eq("1.0") + end + + it "handles directories in bin/" do + build_lib "foo" + lib_path("foo-1.0").join("foo.gemspec").rmtree + lib_path("foo-1.0").join("bin/performance").mkpath + + install_gemfile <<-G + gem 'foo', '1.0', :path => "#{lib_path("foo-1.0")}" + G + expect(err).to lack_errors + end + + it "removes the .gem file after installing" do + build_lib "foo" + + install_gemfile <<-G + gem 'foo', :path => "#{lib_path("foo-1.0")}" + G + + expect(lib_path("foo-1.0").join("foo-1.0.gem")).not_to exist + end + + describe "block syntax" do + it "pulls all gems from a path block" do + build_lib "omg" + build_lib "hi2u" + + install_gemfile <<-G + path "#{lib_path}" do + gem "omg" + gem "hi2u" + end + G + + expect(the_bundle).to include_gems "omg 1.0", "hi2u 1.0" + end + end + + it "keeps source pinning" do + build_lib "foo", "1.0", :path => lib_path("foo") + build_lib "omg", "1.0", :path => lib_path("omg") + build_lib "foo", "1.0", :path => lib_path("omg/foo") do |s| + s.write "lib/foo.rb", "puts 'FAIL'" + end + + install_gemfile <<-G + gem "foo", :path => "#{lib_path("foo")}" + gem "omg", :path => "#{lib_path("omg")}" + G + + expect(the_bundle).to include_gems "foo 1.0" + end + + it "works when the path does not have a gemspec" do + build_lib "foo", :gemspec => false + + gemfile <<-G + gem "foo", "1.0", :path => "#{lib_path("foo-1.0")}" + G + + expect(the_bundle).to include_gems "foo 1.0" + + expect(the_bundle).to include_gems "foo 1.0" + end + + it "works when the path does not have a gemspec but there is a lockfile" do + lockfile <<-L + PATH + remote: vendor/bar + specs: + + GEM + remote: http://rubygems.org + L + + in_app_root { FileUtils.mkdir_p("vendor/bar") } + + install_gemfile <<-G + gem "bar", "1.0.0", path: "vendor/bar", require: "bar/nyard" + G + expect(exitstatus).to eq(0) if exitstatus + end + + context "existing lockfile" do + it "rubygems gems don't re-resolve without changes" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem 'rack-obama', '1.0' + gem 'net-ssh', '1.0' + G + + bundle :check, :env => { "DEBUG" => 1 } + expect(out).to match(/using resolution from the lockfile/) + expect(the_bundle).to include_gems "rack-obama 1.0", "net-ssh 1.0" + end + + it "source path gems w/deps don't re-resolve without changes" do + build_lib "rack-obama", "1.0", :path => lib_path("omg") do |s| + s.add_dependency "yard" + end + + build_lib "net-ssh", "1.0", :path => lib_path("omg") do |s| + s.add_dependency "yard" + end + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem 'rack-obama', :path => "#{lib_path("omg")}" + gem 'net-ssh', :path => "#{lib_path("omg")}" + G + + bundle :check, :env => { "DEBUG" => 1 } + expect(out).to match(/using resolution from the lockfile/) + expect(the_bundle).to include_gems "rack-obama 1.0", "net-ssh 1.0" + end + end + + it "installs executable stubs" do + build_lib "foo" do |s| + s.executables = ["foo"] + end + + install_gemfile <<-G + gem "foo", :path => "#{lib_path("foo-1.0")}" + G + + bundle "exec foo" + expect(out).to eq("1.0") + end + + describe "when the gem version in the path is updated" do + before :each do + build_lib "foo", "1.0", :path => lib_path("foo") do |s| + s.add_dependency "bar" + end + build_lib "bar", "1.0", :path => lib_path("foo/bar") + + install_gemfile <<-G + gem "foo", :path => "#{lib_path("foo")}" + G + end + + it "unlocks all gems when the top level gem is updated" do + build_lib "foo", "2.0", :path => lib_path("foo") do |s| + s.add_dependency "bar" + end + + bundle "install" + + expect(the_bundle).to include_gems "foo 2.0", "bar 1.0" + end + + it "unlocks all gems when a child dependency gem is updated" do + build_lib "bar", "2.0", :path => lib_path("foo/bar") + + bundle "install" + + expect(the_bundle).to include_gems "foo 1.0", "bar 2.0" + end + end + + describe "when dependencies in the path are updated" do + before :each do + build_lib "foo", "1.0", :path => lib_path("foo") + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "foo", :path => "#{lib_path("foo")}" + G + end + + it "gets dependencies that are updated in the path" do + build_lib "foo", "1.0", :path => lib_path("foo") do |s| + s.add_dependency "rack" + end + + bundle "install" + + expect(the_bundle).to include_gems "rack 1.0.0" + end + end + + describe "switching sources" do + it "doesn't switch pinned git sources to rubygems when pinning the parent gem to a path source" do + build_gem "foo", "1.0", :to_system => true do |s| + s.write "lib/foo.rb", "raise 'fail'" + end + build_lib "foo", "1.0", :path => lib_path("bar/foo") + build_git "bar", "1.0", :path => lib_path("bar") do |s| + s.add_dependency "foo" + end + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "bar", :git => "#{lib_path("bar")}" + G + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "bar", :path => "#{lib_path("bar")}" + G + + expect(the_bundle).to include_gems "foo 1.0", "bar 1.0" + end + + it "switches the source when the gem existed in rubygems and the path was already being used for another gem" do + build_lib "foo", "1.0", :path => lib_path("foo") + build_gem "bar", "1.0", :to_system => true do |s| + s.write "lib/bar.rb", "raise 'fail'" + end + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "bar" + path "#{lib_path("foo")}" do + gem "foo" + end + G + + build_lib "bar", "1.0", :path => lib_path("foo/bar") + + install_gemfile <<-G + source "file://#{gem_repo1}" + path "#{lib_path("foo")}" do + gem "foo" + gem "bar" + end + G + + expect(the_bundle).to include_gems "bar 1.0" + end + end + + describe "when there are both a gemspec and remote gems" do + it "doesn't query rubygems for local gemspec name" do + build_lib "private_lib", "2.2", :path => lib_path("private_lib") + gemfile = <<-G + source "http://localgemserver.test" + gemspec + gem 'rack' + G + File.open(lib_path("private_lib/Gemfile"), "w") {|f| f.puts gemfile } + + Dir.chdir(lib_path("private_lib")) do + bundle :install, :env => { "DEBUG" => 1 }, :artifice => "endpoint" + expect(out).to match(%r{^HTTP GET http://localgemserver\.test/api/v1/dependencies\?gems=rack$}) + expect(out).not_to match(/^HTTP GET.*private_lib/) + expect(the_bundle).to include_gems "private_lib 2.2" + expect(the_bundle).to include_gems "rack 1.0" + end + end + end + + describe "gem install hooks" do + it "runs pre-install hooks" do + build_git "foo" + gemfile <<-G + gem "foo", :git => "#{lib_path("foo-1.0")}" + G + + File.open(lib_path("install_hooks.rb"), "w") do |h| + h.write <<-H + require 'rubygems' + Gem.pre_install_hooks << lambda do |inst| + STDERR.puts "Ran pre-install hook: \#{inst.spec.full_name}" + end + H + end + + bundle :install, + :requires => [lib_path("install_hooks.rb")] + expect(err).to eq_err("Ran pre-install hook: foo-1.0") + end + + it "runs post-install hooks" do + build_git "foo" + gemfile <<-G + gem "foo", :git => "#{lib_path("foo-1.0")}" + G + + File.open(lib_path("install_hooks.rb"), "w") do |h| + h.write <<-H + require 'rubygems' + Gem.post_install_hooks << lambda do |inst| + STDERR.puts "Ran post-install hook: \#{inst.spec.full_name}" + end + H + end + + bundle :install, + :requires => [lib_path("install_hooks.rb")] + expect(err).to eq_err("Ran post-install hook: foo-1.0") + end + + it "complains if the install hook fails" do + build_git "foo" + gemfile <<-G + gem "foo", :git => "#{lib_path("foo-1.0")}" + G + + File.open(lib_path("install_hooks.rb"), "w") do |h| + h.write <<-H + require 'rubygems' + Gem.pre_install_hooks << lambda do |inst| + false + end + H + end + + bundle :install, + :requires => [lib_path("install_hooks.rb")] + expect(out).to include("failed for foo-1.0") + end + + it "loads plugins from the path gem" do + foo_file = home("foo_plugin_loaded") + bar_file = home("bar_plugin_loaded") + expect(foo_file).not_to be_file + expect(bar_file).not_to be_file + + build_lib "foo" do |s| + s.write("lib/rubygems_plugin.rb", "FileUtils.touch('#{foo_file}')") + end + + build_git "bar" do |s| + s.write("lib/rubygems_plugin.rb", "FileUtils.touch('#{bar_file}')") + end + + install_gemfile! <<-G + gem "foo", :path => "#{lib_path("foo-1.0")}" + gem "bar", :path => "#{lib_path("bar-1.0")}" + G + + expect(foo_file).to be_file + expect(bar_file).to be_file + end + end +end diff --git a/spec/bundler/install/gemfile/platform_spec.rb b/spec/bundler/install/gemfile/platform_spec.rb new file mode 100644 index 0000000000..bfdf9b68c8 --- /dev/null +++ b/spec/bundler/install/gemfile/platform_spec.rb @@ -0,0 +1,426 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install across platforms" do + it "maintains the same lockfile if all gems are compatible across platforms" do + lockfile <<-G + GEM + remote: file:#{gem_repo1}/ + specs: + rack (0.9.1) + + PLATFORMS + #{not_local} + + DEPENDENCIES + rack + G + + install_gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack" + G + + expect(the_bundle).to include_gems "rack 0.9.1" + end + + it "pulls in the correct platform specific gem" do + lockfile <<-G + GEM + remote: file:#{gem_repo1} + specs: + platform_specific (1.0) + platform_specific (1.0-java) + platform_specific (1.0-x86-mswin32) + + PLATFORMS + ruby + + DEPENDENCIES + platform_specific + G + + simulate_platform "java" + install_gemfile <<-G + source "file://#{gem_repo1}" + + gem "platform_specific" + G + + expect(the_bundle).to include_gems "platform_specific 1.0 JAVA" + end + + it "works with gems that have different dependencies" do + simulate_platform "java" + install_gemfile <<-G + source "file://#{gem_repo1}" + + gem "nokogiri" + G + + expect(the_bundle).to include_gems "nokogiri 1.4.2 JAVA", "weakling 0.0.3" + + simulate_new_machine + + simulate_platform "ruby" + install_gemfile <<-G + source "file://#{gem_repo1}" + + gem "nokogiri" + G + + expect(the_bundle).to include_gems "nokogiri 1.4.2" + expect(the_bundle).not_to include_gems "weakling" + end + + it "does not keep unneeded platforms for gems that are used" do + build_repo4 do + build_gem "empyrean", "0.1.0" + build_gem "coderay", "1.1.2" + build_gem "method_source", "0.9.0" + build_gem("spoon", "0.0.6") {|s| s.add_runtime_dependency "ffi" } + build_gem "pry", "0.11.3" do |s| + s.platform = "java" + s.add_runtime_dependency "coderay", "~> 1.1.0" + s.add_runtime_dependency "method_source", "~> 0.9.0" + s.add_runtime_dependency "spoon", "~> 0.0" + end + build_gem "pry", "0.11.3" do |s| + s.add_runtime_dependency "coderay", "~> 1.1.0" + s.add_runtime_dependency "method_source", "~> 0.9.0" + end + build_gem("ffi", "1.9.23") {|s| s.platform = "java" } + build_gem("ffi", "1.9.23") + end + + simulate_platform java + + install_gemfile! <<-G + source "file://localhost/#{gem_repo4}" + + gem "empyrean", "0.1.0" + gem "pry" + G + + expect(the_bundle.lockfile).to read_as normalize_uri_file(strip_whitespace(<<-L)) + GEM + remote: file://localhost/#{gem_repo4}/ + specs: + coderay (1.1.2) + empyrean (0.1.0) + ffi (1.9.23-java) + method_source (0.9.0) + pry (0.11.3-java) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + spoon (~> 0.0) + spoon (0.0.6) + ffi + + PLATFORMS + java + + DEPENDENCIES + empyrean (= 0.1.0) + pry + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle! "lock --add-platform ruby" + + good_lockfile = strip_whitespace(<<-L) + GEM + remote: file://localhost/#{gem_repo4}/ + specs: + coderay (1.1.2) + empyrean (0.1.0) + ffi (1.9.23-java) + method_source (0.9.0) + pry (0.11.3) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + pry (0.11.3-java) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + spoon (~> 0.0) + spoon (0.0.6) + ffi + + PLATFORMS + java + ruby + + DEPENDENCIES + empyrean (= 0.1.0) + pry + + BUNDLED WITH + #{Bundler::VERSION} + L + + expect(the_bundle.lockfile).to read_as normalize_uri_file(good_lockfile) + + bad_lockfile = strip_whitespace <<-L + GEM + remote: file://localhost/#{gem_repo4}/ + specs: + coderay (1.1.2) + empyrean (0.1.0) + ffi (1.9.23) + ffi (1.9.23-java) + method_source (0.9.0) + pry (0.11.3) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + pry (0.11.3-java) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + spoon (~> 0.0) + spoon (0.0.6) + ffi + + PLATFORMS + java + ruby + + DEPENDENCIES + empyrean (= 0.1.0) + pry + + BUNDLED WITH + #{Bundler::VERSION} + L + + aggregate_failures do + lockfile bad_lockfile + bundle! :install + expect(the_bundle.lockfile).to read_as normalize_uri_file(good_lockfile) + + lockfile bad_lockfile + bundle! :update, :all => true + expect(the_bundle.lockfile).to read_as normalize_uri_file(good_lockfile) + + lockfile bad_lockfile + bundle! "update ffi" + expect(the_bundle.lockfile).to read_as normalize_uri_file(good_lockfile) + + lockfile bad_lockfile + bundle! "update empyrean" + expect(the_bundle.lockfile).to read_as normalize_uri_file(good_lockfile) + + lockfile bad_lockfile + bundle! :lock + expect(the_bundle.lockfile).to read_as normalize_uri_file(good_lockfile) + end + end + + it "works the other way with gems that have different dependencies" do + simulate_platform "ruby" + install_gemfile <<-G + source "file://#{gem_repo1}" + + gem "nokogiri" + G + + simulate_platform "java" + bundle "install" + + expect(the_bundle).to include_gems "nokogiri 1.4.2 JAVA", "weakling 0.0.3" + end + + it "works with gems that have extra platform-specific runtime dependencies", :bundler => "< 2" do + simulate_platform x64_mac + + update_repo2 do + build_gem "facter", "2.4.6" + build_gem "facter", "2.4.6" do |s| + s.platform = "universal-darwin" + s.add_runtime_dependency "CFPropertyList" + end + build_gem "CFPropertyList" + end + + install_gemfile! <<-G + source "file://#{gem_repo2}" + + gem "facter" + G + + expect(out).to include "Unable to use the platform-specific (universal-darwin) version of facter (2.4.6) " \ + "because it has different dependencies from the ruby version. " \ + "To use the platform-specific version of the gem, run `bundle config specific_platform true` and install again." + + expect(the_bundle).to include_gem "facter 2.4.6" + expect(the_bundle).not_to include_gem "CFPropertyList" + end + + it "fetches gems again after changing the version of Ruby" do + gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack", "1.0.0" + G + + bundle! :install, forgotten_command_line_options(:path => "vendor/bundle") + + new_version = Gem::ConfigMap[:ruby_version] == "1.8" ? "1.9.1" : "1.8" + FileUtils.mv(vendored_gems, bundled_app("vendor/bundle", Gem.ruby_engine, new_version)) + + bundle! :install + expect(vendored_gems("gems/rack-1.0.0")).to exist + end +end + +RSpec.describe "bundle install with platform conditionals" do + it "installs gems tagged w/ the current platforms" do + install_gemfile <<-G + source "file://#{gem_repo1}" + + platforms :#{local_tag} do + gem "nokogiri" + end + G + + expect(the_bundle).to include_gems "nokogiri 1.4.2" + end + + it "does not install gems tagged w/ another platforms" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + platforms :#{not_local_tag} do + gem "nokogiri" + end + G + + expect(the_bundle).to include_gems "rack 1.0" + expect(the_bundle).not_to include_gems "nokogiri 1.4.2" + end + + it "installs gems tagged w/ the current platforms inline" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "nokogiri", :platforms => :#{local_tag} + G + expect(the_bundle).to include_gems "nokogiri 1.4.2" + end + + it "does not install gems tagged w/ another platforms inline" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "nokogiri", :platforms => :#{not_local_tag} + G + expect(the_bundle).to include_gems "rack 1.0" + expect(the_bundle).not_to include_gems "nokogiri 1.4.2" + end + + it "installs gems tagged w/ the current platform inline" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "nokogiri", :platform => :#{local_tag} + G + expect(the_bundle).to include_gems "nokogiri 1.4.2" + end + + it "doesn't install gems tagged w/ another platform inline" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "nokogiri", :platform => :#{not_local_tag} + G + expect(the_bundle).not_to include_gems "nokogiri 1.4.2" + end + + it "does not blow up on sources with all platform-excluded specs" do + build_git "foo" + + install_gemfile <<-G + platform :#{not_local_tag} do + gem "foo", :git => "#{lib_path("foo-1.0")}" + end + G + + bundle :list + expect(exitstatus).to eq(0) if exitstatus + end + + it "does not attempt to install gems from :rbx when using --local" do + simulate_platform "ruby" + simulate_ruby_engine "ruby" + + gemfile <<-G + source "file://#{gem_repo1}" + gem "some_gem", :platform => :rbx + G + + bundle "install --local" + expect(out).not_to match(/Could not find gem 'some_gem/) + end + + it "does not attempt to install gems from other rubies when using --local" do + simulate_platform "ruby" + simulate_ruby_engine "ruby" + other_ruby_version_tag = RUBY_VERSION =~ /^1\.8/ ? :ruby_19 : :ruby_18 + + gemfile <<-G + source "file://#{gem_repo1}" + gem "some_gem", platform: :#{other_ruby_version_tag} + G + + bundle "install --local" + expect(out).not_to match(/Could not find gem 'some_gem/) + end + + it "prints a helpful warning when a dependency is unused on any platform" do + simulate_platform "ruby" + simulate_ruby_engine "ruby" + + gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack", :platform => [:mingw, :mswin, :x64_mingw, :jruby] + G + + bundle! "install" + + expect(out).to include <<-O.strip +The dependency #{Gem::Dependency.new("rack", ">= 0")} will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`. + O + end + + context "when disable_platform_warnings is true" do + before { bundle! "config disable_platform_warnings true" } + + it "does not print the warning when a dependency is unused on any platform" do + simulate_platform "ruby" + simulate_ruby_engine "ruby" + + gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack", :platform => [:mingw, :mswin, :x64_mingw, :jruby] + G + + bundle! "install" + + expect(out).not_to match(/The dependency (.*) will be unused/) + end + end +end + +RSpec.describe "when a gem has no architecture" do + it "still installs correctly" do + simulate_platform mswin + + gemfile <<-G + # Try to install gem with nil arch + source "http://localgemserver.test/" + gem "rcov" + G + + bundle :install, :artifice => "windows" + expect(the_bundle).to include_gems "rcov 1.0.0" + end +end diff --git a/spec/bundler/install/gemfile/ruby_spec.rb b/spec/bundler/install/gemfile/ruby_spec.rb new file mode 100644 index 0000000000..24fe021fa3 --- /dev/null +++ b/spec/bundler/install/gemfile/ruby_spec.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +RSpec.describe "ruby requirement" do + def locked_ruby_version + Bundler::RubyVersion.from_string(Bundler::LockfileParser.new(lockfile).ruby_version) + end + + # As discovered by https://github.com/bundler/bundler/issues/4147, there is + # no test coverage to ensure that adding a gem is possible with a ruby + # requirement. This test verifies the fix, committed in bfbad5c5. + it "allows adding gems" do + install_gemfile <<-G + source "file://#{gem_repo1}" + ruby "#{RUBY_VERSION}" + gem "rack" + G + + install_gemfile <<-G + source "file://#{gem_repo1}" + ruby "#{RUBY_VERSION}" + gem "rack" + gem "rack-obama" + G + + expect(exitstatus).to eq(0) if exitstatus + expect(the_bundle).to include_gems "rack-obama 1.0" + end + + it "allows removing the ruby version requirement" do + install_gemfile <<-G + source "file://#{gem_repo1}" + ruby "~> #{RUBY_VERSION}" + gem "rack" + G + + expect(lockfile).to include("RUBY VERSION") + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + expect(the_bundle).to include_gems "rack 1.0.0" + expect(lockfile).not_to include("RUBY VERSION") + end + + it "allows changing the ruby version requirement to something compatible" do + install_gemfile <<-G + source "file://#{gem_repo1}" + ruby ">= 1.0.0" + gem "rack" + G + + expect(locked_ruby_version).to eq(Bundler::RubyVersion.system) + + simulate_ruby_version "5100" + + install_gemfile <<-G + source "file://#{gem_repo1}" + ruby ">= 1.0.1" + gem "rack" + G + + expect(the_bundle).to include_gems "rack 1.0.0" + expect(locked_ruby_version).to eq(Bundler::RubyVersion.system) + end + + it "allows changing the ruby version requirement to something incompatible" do + install_gemfile <<-G + source "file://#{gem_repo1}" + ruby ">= 1.0.0" + gem "rack" + G + + expect(locked_ruby_version).to eq(Bundler::RubyVersion.system) + + simulate_ruby_version "5100" + + install_gemfile <<-G + source "file://#{gem_repo1}" + ruby ">= 5000.0" + gem "rack" + G + + expect(the_bundle).to include_gems "rack 1.0.0" + expect(locked_ruby_version.versions).to eq(["5100"]) + end + + it "allows requirements with trailing whitespace" do + install_gemfile! <<-G + source "file://#{gem_repo1}" + ruby "#{RUBY_VERSION}\\n \t\\n" + gem "rack" + G + + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "fails gracefully with malformed requirements" do + install_gemfile <<-G + source "file://#{gem_repo1}" + ruby ">= 0", "-.\\0" + gem "rack" + G + + expect(out).to include("There was an error parsing") # i.e. DSL error, not error template + end +end diff --git a/spec/bundler/install/gemfile/sources_spec.rb b/spec/bundler/install/gemfile/sources_spec.rb new file mode 100644 index 0000000000..c814d0de76 --- /dev/null +++ b/spec/bundler/install/gemfile/sources_spec.rb @@ -0,0 +1,619 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install with gems on multiple sources" do + # repo1 is built automatically before all of the specs run + # it contains rack-obama 1.0.0 and rack 0.9.1 & 1.0.0 amongst other gems + + context "without source affinity" do + before do + # Oh no! Someone evil is trying to hijack rack :( + # need this to be broken to check for correct source ordering + build_repo gem_repo3 do + build_gem "rack", repo3_rack_version do |s| + s.write "lib/rack.rb", "RACK = 'FAIL'" + end + end + end + + context "with multiple toplevel sources" do + let(:repo3_rack_version) { "1.0.0" } + + before do + gemfile <<-G + source "file://localhost#{gem_repo3}" + source "file://localhost#{gem_repo1}" + gem "rack-obama" + gem "rack" + G + bundle "config major_deprecations true" + end + + it "warns about ambiguous gems, but installs anyway, prioritizing sources last to first", :bundler => "< 2" do + bundle :install + + expect(out).to have_major_deprecation a_string_including("Your Gemfile contains multiple primary sources.") + expect(out).to include("Warning: the gem 'rack' was found in multiple sources.") + expect(out).to include(normalize_uri_file("Installed from: file://localhost#{gem_repo1}")) + expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", :source => "remote1") + end + + it "errors when disable_multisource is set" do + bundle "config disable_multisource true" + bundle :install + expect(out).to include("Each source after the first must include a block") + expect(exitstatus).to eq(4) if exitstatus + end + end + + context "when different versions of the same gem are in multiple sources" do + let(:repo3_rack_version) { "1.2" } + + before do + gemfile <<-G + source "file://localhost#{gem_repo3}" + source "file://localhost#{gem_repo1}" + gem "rack-obama" + gem "rack", "1.0.0" # force it to install the working version in repo1 + G + bundle "config major_deprecations true" + end + + it "warns about ambiguous gems, but installs anyway", :bundler => "< 2" do + bundle :install + + expect(out).to have_major_deprecation a_string_including("Your Gemfile contains multiple primary sources.") + expect(out).to include("Warning: the gem 'rack' was found in multiple sources.") + expect(out).to include(normalize_uri_file("Installed from: file://localhost#{gem_repo1}")) + expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", :source => "remote1") + end + end + end + + context "with source affinity" do + context "with sources given by a block" do + before do + # Oh no! Someone evil is trying to hijack rack :( + # need this to be broken to check for correct source ordering + build_repo gem_repo3 do + build_gem "rack", "1.0.0" do |s| + s.write "lib/rack.rb", "RACK = 'FAIL'" + end + + build_gem "rack-obama" do |s| + s.add_dependency "rack" + end + end + + gemfile <<-G + source "file://#{gem_repo3}" + source "file://#{gem_repo1}" do + gem "thin" # comes first to test name sorting + gem "rack" + end + gem "rack-obama" # shoud come from repo3! + G + end + + it "installs the gems without any warning" do + bundle! :install + expect(out).not_to include("Warning") + expect(the_bundle).to include_gems("rack-obama 1.0.0") + expect(the_bundle).to include_gems("rack 1.0.0", :source => "remote1") + end + + it "can cache and deploy" do + bundle! :package + + expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + expect(bundled_app("vendor/cache/rack-obama-1.0.gem")).to exist + + bundle! :install, forgotten_command_line_options(:deployment => true) + + expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0") + end + end + + context "with sources set by an option" do + before do + # Oh no! Someone evil is trying to hijack rack :( + # need this to be broken to check for correct source ordering + build_repo gem_repo3 do + build_gem "rack", "1.0.0" do |s| + s.write "lib/rack.rb", "RACK = 'FAIL'" + end + + build_gem "rack-obama" do |s| + s.add_dependency "rack" + end + end + + gemfile <<-G + source "file://#{gem_repo3}" + gem "rack-obama" # should come from repo3! + gem "rack", :source => "file://#{gem_repo1}" + G + end + + it "installs the gems without any warning" do + bundle :install + expect(out).not_to include("Warning") + expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0") + end + end + + context "when a pinned gem has an indirect dependency" do + before do + build_repo gem_repo3 do + build_gem "depends_on_rack", "1.0.1" do |s| + s.add_dependency "rack" + end + end + end + + context "when the indirect dependency is in the pinned source" do + before do + # we need a working rack gem in repo3 + update_repo gem_repo3 do + build_gem "rack", "1.0.0" + end + + gemfile <<-G + source "file://#{gem_repo2}" + source "file://#{gem_repo3}" do + gem "depends_on_rack" + end + G + end + + context "and not in any other sources" do + before do + build_repo(gem_repo2) {} + end + + it "installs from the same source without any warning" do + bundle :install + expect(out).not_to include("Warning") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") + end + end + + context "and in another source" do + before do + # need this to be broken to check for correct source ordering + build_repo gem_repo2 do + build_gem "rack", "1.0.0" do |s| + s.write "lib/rack.rb", "RACK = 'FAIL'" + end + end + end + + context "when lockfile_uses_separate_rubygems_sources is set" do + before do + bundle! "config lockfile_uses_separate_rubygems_sources true" + bundle! "config disable_multisource true" + end + + it "installs from the same source without any warning" do + bundle! :install + + expect(out).not_to include("Warning: the gem 'rack' was found in multiple sources.") + expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") + + # when there is already a lock file, and the gems are missing, so try again + system_gems [] + bundle! :install + + expect(out).not_to include("Warning: the gem 'rack' was found in multiple sources.") + expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") + end + end + end + end + + context "when the indirect dependency is in a different source" do + before do + # In these tests, we need a working rack gem in repo2 and not repo3 + build_repo gem_repo2 do + build_gem "rack", "1.0.0" + end + end + + context "and not in any other sources" do + before do + gemfile <<-G + source "file://#{gem_repo2}" + source "file://#{gem_repo3}" do + gem "depends_on_rack" + end + G + end + + it "installs from the other source without any warning" do + bundle :install + expect(out).not_to include("Warning") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") + end + end + + context "and in yet another source" do + before do + gemfile <<-G + source "file://localhost#{gem_repo1}" + source "file://localhost#{gem_repo2}" + source "file://localhost#{gem_repo3}" do + gem "depends_on_rack" + end + G + end + + it "installs from the other source and warns about ambiguous gems", :bundler => "< 2" do + bundle "config major_deprecations true" + bundle :install + expect(out).to have_major_deprecation a_string_including("Your Gemfile contains multiple primary sources.") + expect(out).to include("Warning: the gem 'rack' was found in multiple sources.") + expect(out).to include(normalize_uri_file("Installed from: file://localhost#{gem_repo2}")) + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") + end + end + + context "and only the dependency is pinned" do + before do + # need this to be broken to check for correct source ordering + build_repo gem_repo2 do + build_gem "rack", "1.0.0" do |s| + s.write "lib/rack.rb", "RACK = 'FAIL'" + end + end + + gemfile <<-G + source "file://#{gem_repo3}" # contains depends_on_rack + source "file://#{gem_repo2}" # contains broken rack + + gem "depends_on_rack" # installed from gem_repo3 + gem "rack", :source => "file://#{gem_repo1}" + G + end + + it "installs the dependency from the pinned source without warning", :bundler => "< 2" do + bundle :install + + expect(out).not_to include("Warning: the gem 'rack' was found in multiple sources.") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") + + # In https://github.com/bundler/bundler/issues/3585 this failed + # when there is already a lock file, and the gems are missing, so try again + system_gems [] + bundle :install + + expect(out).not_to include("Warning: the gem 'rack' was found in multiple sources.") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") + end + end + end + end + + context "when a top-level gem has an indirect dependency" do + context "when lockfile_uses_separate_rubygems_sources is set" do + before do + bundle! "config lockfile_uses_separate_rubygems_sources true" + bundle! "config disable_multisource true" + end + + before do + build_repo gem_repo2 do + build_gem "depends_on_rack", "1.0.1" do |s| + s.add_dependency "rack" + end + end + + build_repo gem_repo3 do + build_gem "unrelated_gem", "1.0.0" + end + + gemfile <<-G + source "file://#{gem_repo2}" + + gem "depends_on_rack" + + source "file://#{gem_repo3}" do + gem "unrelated_gem" + end + G + end + + context "and the dependency is only in the top-level source" do + before do + update_repo gem_repo2 do + build_gem "rack", "1.0.0" + end + end + + it "installs all gems without warning" do + bundle :install + expect(out).not_to include("Warning") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0") + end + end + + context "and the dependency is only in a pinned source" do + before do + update_repo gem_repo3 do + build_gem "rack", "1.0.0" do |s| + s.write "lib/rack.rb", "RACK = 'FAIL'" + end + end + end + + it "does not find the dependency" do + bundle :install + expect(out).to include("Could not find gem 'rack', which is required by gem 'depends_on_rack', in any of the relevant sources") + end + end + + context "and the dependency is in both the top-level and a pinned source" do + before do + update_repo gem_repo2 do + build_gem "rack", "1.0.0" + end + + update_repo gem_repo3 do + build_gem "rack", "1.0.0" do |s| + s.write "lib/rack.rb", "RACK = 'FAIL'" + end + end + end + + it "installs the dependency from the top-level source without warning" do + bundle :install + expect(out).not_to include("Warning") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0") + end + end + end + end + + context "with a gem that is only found in the wrong source" do + before do + build_repo gem_repo3 do + build_gem "not_in_repo1", "1.0.0" + end + + gemfile <<-G + source "file://#{gem_repo3}" + gem "not_in_repo1", :source => "file://#{gem_repo1}" + G + end + + it "does not install the gem" do + bundle :install + expect(out).to include("Could not find gem 'not_in_repo1'") + end + end + + context "with an existing lockfile" do + before do + system_gems "rack-0.9.1", "rack-1.0.0", :path => :bundle_path + + lockfile <<-L + GEM + remote: file:#{gem_repo1} + remote: file:#{gem_repo3} + specs: + rack (0.9.1) + + PLATFORMS + ruby + + DEPENDENCIES + rack! + L + + gemfile <<-G + source "file://#{gem_repo1}" + source "file://#{gem_repo3}" do + gem 'rack' + end + G + end + + # Reproduction of https://github.com/bundler/bundler/issues/3298 + it "does not unlock the installed gem on exec" do + expect(the_bundle).to include_gems("rack 0.9.1") + end + end + + context "with a path gem in the same Gemfile" do + before do + build_lib "foo" + + gemfile <<-G + gem "rack", :source => "file://#{gem_repo1}" + gem "foo", :path => "#{lib_path("foo-1.0")}" + G + end + + it "does not unlock the non-path gem after install" do + bundle! :install + + bundle! %(exec ruby -e 'puts "OK"'), :env => { :RUBYOPT => "-r#{spec_dir.join("support/hax")}" } + + expect(out).to include("OK") + end + end + end + + context "when an older version of the same gem also ships with Ruby" do + before do + system_gems "rack-0.9.1" + + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" # shoud come from repo1! + G + end + + it "installs the gems without any warning" do + bundle :install + expect(out).not_to include("Warning") + expect(the_bundle).to include_gems("rack 1.0.0") + end + end + + context "when a single source contains multiple locked gems" do + before do + # 1. With these gems, + build_repo4 do + build_gem "foo", "0.1" + build_gem "bar", "0.1" + end + + # 2. Installing this gemfile will produce... + gemfile <<-G + source 'file://#{gem_repo1}' + gem 'rack' + gem 'foo', '~> 0.1', :source => 'file://#{gem_repo4}' + gem 'bar', '~> 0.1', :source => 'file://#{gem_repo4}' + G + + # 3. this lockfile. + lockfile <<-L + GEM + remote: file:/Users/andre/src/bundler/bundler/tmp/gems/remote1/ + remote: file:/Users/andre/src/bundler/bundler/tmp/gems/remote4/ + specs: + bar (0.1) + foo (0.1) + rack (1.0.0) + + PLATFORMS + ruby + + DEPENDENCIES + bar (~> 0.1)! + foo (~> 0.1)! + rack + L + + bundle! :install, forgotten_command_line_options(:path => "../gems/system") + + # 4. Then we add some new versions... + update_repo4 do + build_gem "foo", "0.2" + build_gem "bar", "0.3" + end + end + + it "allows them to be unlocked separately" do + # 5. and install this gemfile, updating only foo. + install_gemfile <<-G + source 'file://#{gem_repo1}' + gem 'rack' + gem 'foo', '~> 0.2', :source => 'file://#{gem_repo4}' + gem 'bar', '~> 0.1', :source => 'file://#{gem_repo4}' + G + + # 6. Which should update foo to 0.2, but not the (locked) bar 0.1 + expect(the_bundle).to include_gems("foo 0.2", "bar 0.1") + end + end + + context "re-resolving" do + context "when there is a mix of sources in the gemfile" do + before do + build_repo3 + build_lib "path1" + build_lib "path2" + build_git "git1" + build_git "git2" + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + + source "file://#{gem_repo3}" do + gem "rack" + end + + gem "path1", :path => "#{lib_path("path1-1.0")}" + gem "path2", :path => "#{lib_path("path2-1.0")}" + gem "git1", :git => "#{lib_path("git1-1.0")}" + gem "git2", :git => "#{lib_path("git2-1.0")}" + G + end + + it "does not re-resolve" do + bundle :install, :verbose => true + expect(out).to include("using resolution from the lockfile") + expect(out).not_to include("re-resolving dependencies") + end + end + end + + context "when a gem is installed to system gems" do + before do + install_gemfile! <<-G + source "file://#{gem_repo1}" + gem "rack" + G + end + + context "and the gemfile changes" do + it "is still able to find that gem from remote sources" do + source_uri = "file://#{gem_repo1}" + second_uri = "file://#{gem_repo4}" + + build_repo4 do + build_gem "rack", "2.0.1.1.forked" + build_gem "thor", "0.19.1.1.forked" + end + + # When this gemfile is installed... + gemfile <<-G + source "#{source_uri}" + + source "#{second_uri}" do + gem "rack", "2.0.1.1.forked" + gem "thor" + end + gem "rack-obama" + G + + # It creates this lockfile. + lockfile <<-L + GEM + remote: #{source_uri}/ + remote: #{second_uri}/ + specs: + rack (2.0.1.1.forked) + rack-obama (1.0) + rack + thor (0.19.1.1.forked) + + PLATFORMS + ruby + + DEPENDENCIES + rack (= 2.0.1.1.forked)! + rack-obama + thor! + L + + # Then we change the Gemfile by adding a version to thor + gemfile <<-G + source "#{source_uri}" + + source "#{second_uri}" do + gem "rack", "2.0.1.1.forked" + gem "thor", "0.19.1.1.forked" + end + gem "rack-obama" + G + + # But we should still be able to find rack 2.0.1.1.forked and install it + bundle! :install + end + end + end +end diff --git a/spec/bundler/install/gemfile/specific_platform_spec.rb b/spec/bundler/install/gemfile/specific_platform_spec.rb new file mode 100644 index 0000000000..9c725416d5 --- /dev/null +++ b/spec/bundler/install/gemfile/specific_platform_spec.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install with specific_platform enabled" do + before do + bundle "config specific_platform true" + + build_repo2 do + build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") + build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "x86_64-linux" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "x86-mingw32" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "x86-linux" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "x64-mingw32" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "universal-darwin" } + + build_gem("google-protobuf", "3.0.0.alpha.5.0.5") {|s| s.platform = "x86_64-linux" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.5") {|s| s.platform = "x86-linux" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.5") {|s| s.platform = "x64-mingw32" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.5") {|s| s.platform = "x86-mingw32" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.5") + + build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "universal-darwin" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "x86_64-linux" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "x86-mingw32" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "x86-linux" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "x64-mingw32" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.4") + + build_gem("google-protobuf", "3.0.0.alpha.5.0.3") + build_gem("google-protobuf", "3.0.0.alpha.5.0.3") {|s| s.platform = "x86_64-linux" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.3") {|s| s.platform = "x86-mingw32" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.3") {|s| s.platform = "x86-linux" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.3") {|s| s.platform = "x64-mingw32" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.3") {|s| s.platform = "universal-darwin" } + + build_gem("google-protobuf", "3.0.0.alpha.4.0") + build_gem("google-protobuf", "3.0.0.alpha.3.1.pre") + build_gem("google-protobuf", "3.0.0.alpha.3") + build_gem("google-protobuf", "3.0.0.alpha.2.0") + build_gem("google-protobuf", "3.0.0.alpha.1.1") + build_gem("google-protobuf", "3.0.0.alpha.1.0") + + build_gem("facter", "2.4.6") + build_gem("facter", "2.4.6") do |s| + s.platform = "universal-darwin" + s.add_runtime_dependency "CFPropertyList" + end + build_gem("CFPropertyList") + end + end + + let(:google_protobuf) { <<-G } + source "file:#{gem_repo2}" + gem "google-protobuf" + G + + context "when on a darwin machine" do + before { simulate_platform "x86_64-darwin-15" } + + it "locks to both the specific darwin platform and ruby" do + install_gemfile!(google_protobuf) + expect(the_bundle.locked_gems.platforms).to eq([pl("ruby"), pl("x86_64-darwin-15")]) + expect(the_bundle).to include_gem("google-protobuf 3.0.0.alpha.5.0.5.1 universal-darwin") + expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[ + google-protobuf-3.0.0.alpha.5.0.5.1 + google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin + ]) + end + + it "caches both the universal-darwin and ruby gems when --all-platforms is passed" do + gemfile(google_protobuf) + bundle! "package --all-platforms" + expect([cached_gem("google-protobuf-3.0.0.alpha.5.0.5.1"), cached_gem("google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin")]). + to all(exist) + end + + it "uses the platform-specific gem with extra dependencies" do + install_gemfile! <<-G + source "file:#{gem_repo2}" + gem "facter" + G + + expect(the_bundle.locked_gems.platforms).to eq([pl("ruby"), pl("x86_64-darwin-15")]) + expect(the_bundle).to include_gems("facter 2.4.6 universal-darwin", "CFPropertyList 1.0") + expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(["CFPropertyList-1.0", + "facter-2.4.6", + "facter-2.4.6-universal-darwin"]) + end + + context "when adding a platform via lock --add_platform" do + it "adds the foreign platform" do + install_gemfile!(google_protobuf) + bundle! "lock --add-platform=#{x64_mingw}" + + expect(the_bundle.locked_gems.platforms).to eq([rb, x64_mingw, pl("x86_64-darwin-15")]) + expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[ + google-protobuf-3.0.0.alpha.5.0.5.1 + google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin + google-protobuf-3.0.0.alpha.5.0.5.1-x64-mingw32 + ]) + end + + it "falls back on plain ruby when that version doesnt have a platform-specific gem" do + install_gemfile!(google_protobuf) + bundle! "lock --add-platform=#{java}" + + expect(the_bundle.locked_gems.platforms).to eq([java, rb, pl("x86_64-darwin-15")]) + expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[ + google-protobuf-3.0.0.alpha.5.0.5.1 + google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin + ]) + end + end + end +end diff --git a/spec/bundler/install/gemfile_spec.rb b/spec/bundler/install/gemfile_spec.rb new file mode 100644 index 0000000000..e74c5ffe59 --- /dev/null +++ b/spec/bundler/install/gemfile_spec.rb @@ -0,0 +1,145 @@ +# encoding: utf-8 +# frozen_string_literal: true + +RSpec.describe "bundle install" do + context "with duplicated gems" do + it "will display a warning" do + install_gemfile <<-G + gem 'rails', '~> 4.0.0' + gem 'rails', '~> 4.0.0' + G + expect(out).to include("more than once") + end + end + + context "with --gemfile" do + it "finds the gemfile" do + gemfile bundled_app("NotGemfile"), <<-G + source "file://#{gem_repo1}" + gem 'rack' + G + + bundle :install, :gemfile => bundled_app("NotGemfile") + + # Specify BUNDLE_GEMFILE for `the_bundle` + # to retrieve the proper Gemfile + ENV["BUNDLE_GEMFILE"] = "NotGemfile" + expect(the_bundle).to include_gems "rack 1.0.0" + end + end + + context "with gemfile set via config" do + before do + gemfile bundled_app("NotGemfile"), <<-G + source "file://#{gem_repo1}" + gem 'rack' + G + + bundle "config --local gemfile #{bundled_app("NotGemfile")}" + end + it "uses the gemfile to install" do + bundle "install" + bundle "list" + + expect(out).to include("rack (1.0.0)") + end + it "uses the gemfile while in a subdirectory" do + bundled_app("subdir").mkpath + Dir.chdir(bundled_app("subdir")) do + bundle "install" + bundle "list" + + expect(out).to include("rack (1.0.0)") + end + end + end + + context "with deprecated features" do + before :each do + in_app_root + end + + it "reports that lib is an invalid option" do + gemfile <<-G + gem "rack", :lib => "rack" + G + + bundle :install + expect(out).to match(/You passed :lib as an option for gem 'rack', but it is invalid/) + end + end + + context "with prefer_gems_rb set" do + before { bundle! "config prefer_gems_rb true" } + + it "prefers gems.rb to Gemfile" do + create_file("gems.rb", "gem 'bundler'") + create_file("Gemfile", "raise 'wrong Gemfile!'") + + bundle! :install + + expect(bundled_app("gems.rb")).to be_file + expect(bundled_app("Gemfile.lock")).not_to be_file + + expect(the_bundle).to include_gem "bundler #{Bundler::VERSION}" + end + end + + context "with engine specified in symbol" do + it "does not raise any error parsing Gemfile" do + simulate_ruby_version "2.3.0" do + simulate_ruby_engine "jruby", "9.1.2.0" do + install_gemfile! <<-G + source "file://#{gem_repo1}" + ruby "2.3.0", :engine => :jruby, :engine_version => "9.1.2.0" + G + + expect(out).to match(/Bundle complete!/) + end + end + end + + it "installation succeeds" do + simulate_ruby_version "2.3.0" do + simulate_ruby_engine "jruby", "9.1.2.0" do + install_gemfile! <<-G + source "file://#{gem_repo1}" + ruby "2.3.0", :engine => :jruby, :engine_version => "9.1.2.0" + gem "rack" + G + + expect(the_bundle).to include_gems "rack 1.0.0" + end + end + end + end + + context "with a Gemfile containing non-US-ASCII characters" do + it "reads the Gemfile with the UTF-8 encoding by default" do + skip "Ruby 1.8 has no encodings" if RUBY_VERSION < "1.9" + + install_gemfile <<-G + str = "Il était une fois ..." + puts "The source encoding is: " + str.encoding.name + G + + expect(out).to include("The source encoding is: UTF-8") + expect(out).not_to include("The source encoding is: ASCII-8BIT") + expect(out).to include("Bundle complete!") + end + + it "respects the magic encoding comment" do + skip "Ruby 1.8 has no encodings" if RUBY_VERSION < "1.9" + + # NOTE: This works thanks to #eval interpreting the magic encoding comment + install_gemfile <<-G + # encoding: iso-8859-1 + str = "Il #{"\xE9".dup.force_encoding("binary")}tait une fois ..." + puts "The source encoding is: " + str.encoding.name + G + + expect(out).to include("The source encoding is: ISO-8859-1") + expect(out).to include("Bundle complete!") + end + end +end diff --git a/spec/bundler/install/gems/compact_index_spec.rb b/spec/bundler/install/gems/compact_index_spec.rb new file mode 100644 index 0000000000..02a37a77d5 --- /dev/null +++ b/spec/bundler/install/gems/compact_index_spec.rb @@ -0,0 +1,940 @@ +# frozen_string_literal: true + +RSpec.describe "compact index api" do + let(:source_hostname) { "localgemserver.test" } + let(:source_uri) { "http://#{source_hostname}" } + + it "should use the API" do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + + bundle! :install, :artifice => "compact_index" + expect(out).to include("Fetching gem metadata from #{source_uri}") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "should URI encode gem names" do + gemfile <<-G + source "#{source_uri}" + gem " sinatra" + G + + bundle :install, :artifice => "compact_index" + expect(out).to include("' sinatra' is not a valid gem name because it contains whitespace.") + end + + it "should handle nested dependencies" do + gemfile <<-G + source "#{source_uri}" + gem "rails" + G + + bundle! :install, :artifice => "compact_index" + expect(out).to include("Fetching gem metadata from #{source_uri}") + expect(the_bundle).to include_gems( + "rails 2.3.2", + "actionpack 2.3.2", + "activerecord 2.3.2", + "actionmailer 2.3.2", + "activeresource 2.3.2", + "activesupport 2.3.2" + ) + end + + it "should handle case sensitivity conflicts" do + build_repo4 do + build_gem "rack", "1.0" do |s| + s.add_runtime_dependency("Rack", "0.1") + end + build_gem "Rack", "0.1" + end + + install_gemfile! <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4 } + source "#{source_uri}" + gem "rack", "1.0" + gem "Rack", "0.1" + G + + # can't use `include_gems` here since the `require` will conflict on a + # case-insensitive FS + run! "Bundler.require; puts Gem.loaded_specs.values_at('rack', 'Rack').map(&:full_name)" + expect(last_command.stdout).to eq("rack-1.0\nRack-0.1") + end + + it "should handle multiple gem dependencies on the same gem" do + gemfile <<-G + source "#{source_uri}" + gem "net-sftp" + G + + bundle! :install, :artifice => "compact_index" + expect(the_bundle).to include_gems "net-sftp 1.1.1" + end + + it "should use the endpoint when using --deployment" do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + bundle! :install, :artifice => "compact_index" + + bundle! :install, forgotten_command_line_options(:deployment => true, :path => "vendor/bundle").merge(:artifice => "compact_index") + expect(out).to include("Fetching gem metadata from #{source_uri}") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "handles git dependencies that are in rubygems" do + build_git "foo" do |s| + s.executables = "foobar" + s.add_dependency "rails", "2.3.2" + end + + gemfile <<-G + source "#{source_uri}" + git "file:///#{lib_path("foo-1.0")}" do + gem 'foo' + end + G + + bundle! :install, :artifice => "compact_index" + + expect(the_bundle).to include_gems("rails 2.3.2") + end + + it "handles git dependencies that are in rubygems using --deployment" do + build_git "foo" do |s| + s.executables = "foobar" + s.add_dependency "rails", "2.3.2" + end + + gemfile <<-G + source "#{source_uri}" + gem 'foo', :git => "file:///#{lib_path("foo-1.0")}" + G + + bundle! :install, :artifice => "compact_index" + + bundle "install --deployment", :artifice => "compact_index" + + expect(the_bundle).to include_gems("rails 2.3.2") + end + + it "doesn't fail if you only have a git gem with no deps when using --deployment" do + build_git "foo" + gemfile <<-G + source "#{source_uri}" + gem 'foo', :git => "file:///#{lib_path("foo-1.0")}" + G + + bundle "install", :artifice => "compact_index" + bundle! :install, forgotten_command_line_options(:deployment => true).merge(:artifice => "compact_index") + + expect(the_bundle).to include_gems("foo 1.0") + end + + it "falls back when the API errors out" do + simulate_platform mswin + + gemfile <<-G + source "#{source_uri}" + gem "rcov" + G + + bundle! :install, :artifice => "windows" + expect(out).to include("Fetching source index from #{source_uri}") + expect(the_bundle).to include_gems "rcov 1.0.0" + end + + it "falls back when the API URL returns 403 Forbidden" do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + + bundle! :install, :verbose => true, :artifice => "compact_index_forbidden" + expect(out).to include("Fetching gem metadata from #{source_uri}") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "falls back when the versions endpoint has a checksum mismatch" do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + + bundle! :install, :verbose => true, :artifice => "compact_index_checksum_mismatch" + expect(out).to include("Fetching gem metadata from #{source_uri}") + expect(out).to include <<-'WARN' +The checksum of /versions does not match the checksum provided by the server! Something is wrong (local checksum is "\"d41d8cd98f00b204e9800998ecf8427e\"", was expecting "\"123\""). + WARN + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "falls back when the user's home directory does not exist or is not writable" do + ENV["HOME"] = tmp("missing_home").to_s + + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + + bundle! :install, :artifice => "compact_index" + expect(out).to include("Fetching gem metadata from #{source_uri}") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "handles host redirects" do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + + bundle! :install, :artifice => "compact_index_host_redirect" + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "handles host redirects without Net::HTTP::Persistent" do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + + FileUtils.mkdir_p lib_path + File.open(lib_path("disable_net_http_persistent.rb"), "w") do |h| + h.write <<-H + module Kernel + alias require_without_disabled_net_http require + def require(*args) + raise LoadError, 'simulated' if args.first == 'openssl' && !caller.grep(/vendored_persistent/).empty? + require_without_disabled_net_http(*args) + end + end + H + end + + bundle! :install, :artifice => "compact_index_host_redirect", :requires => [lib_path("disable_net_http_persistent.rb")] + expect(out).to_not match(/Too many redirects/) + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "times out when Bundler::Fetcher redirects too much" do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + + bundle :install, :artifice => "compact_index_redirects" + expect(out).to match(/Too many redirects/) + end + + context "when --full-index is specified" do + it "should use the modern index for install" do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + + bundle "install --full-index", :artifice => "compact_index" + expect(out).to include("Fetching source index from #{source_uri}") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "should use the modern index for update" do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + + bundle! "update --full-index", :artifice => "compact_index", :all => bundle_update_requires_all? + expect(out).to include("Fetching source index from #{source_uri}") + expect(the_bundle).to include_gems "rack 1.0.0" + end + end + + it "does not double check for gems that are only installed locally" do + system_gems %w[rack-1.0.0 thin-1.0 net_a-1.0] + bundle! "config --local path.system true" + ENV["BUNDLER_SPEC_ALL_REQUESTS"] = strip_whitespace(<<-EOS).strip + #{source_uri}/versions + #{source_uri}/info/rack + EOS + + install_gemfile! <<-G, :artifice => "compact_index", :verbose => true + source "#{source_uri}" + gem "rack" + G + + expect(last_command.stdboth).not_to include "Double checking" + end + + it "fetches again when more dependencies are found in subsequent sources", :bundler => "< 2" do + build_repo2 do + build_gem "back_deps" do |s| + s.add_dependency "foo" + end + FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] + end + + gemfile <<-G + source "#{source_uri}" + source "#{source_uri}/extra" + gem "back_deps" + G + + bundle! :install, :artifice => "compact_index_extra" + expect(the_bundle).to include_gems "back_deps 1.0", "foo 1.0" + end + + it "fetches again when more dependencies are found in subsequent sources with source blocks" do + build_repo2 do + build_gem "back_deps" do |s| + s.add_dependency "foo" + end + FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] + end + + install_gemfile! <<-G, :artifice => "compact_index_extra", :verbose => true + source "#{source_uri}" + source "#{source_uri}/extra" do + gem "back_deps" + end + G + + expect(the_bundle).to include_gems "back_deps 1.0", "foo 1.0" + end + + it "fetches gem versions even when those gems are already installed" do + gemfile <<-G + source "#{source_uri}" + gem "rack", "1.0.0" + G + bundle! :install, :artifice => "compact_index_extra_api" + expect(the_bundle).to include_gems "rack 1.0.0" + + build_repo4 do + build_gem "rack", "1.2" do |s| + s.executables = "rackup" + end + end + + gemfile <<-G + source "#{source_uri}" do; end + source "#{source_uri}/extra" + gem "rack", "1.2" + G + bundle! :install, :artifice => "compact_index_extra_api" + expect(the_bundle).to include_gems "rack 1.2" + end + + it "considers all possible versions of dependencies from all api gem sources", :bundler => "< 2" do + # In this scenario, the gem "somegem" only exists in repo4. It depends on specific version of activesupport that + # exists only in repo1. There happens also be a version of activesupport in repo4, but not the one that version 1.0.0 + # of somegem wants. This test makes sure that bundler actually finds version 1.2.3 of active support in the other + # repo and installs it. + build_repo4 do + build_gem "activesupport", "1.2.0" + build_gem "somegem", "1.0.0" do |s| + s.add_dependency "activesupport", "1.2.3" # This version exists only in repo1 + end + end + + gemfile <<-G + source "#{source_uri}" + source "#{source_uri}/extra" + gem 'somegem', '1.0.0' + G + + bundle! :install, :artifice => "compact_index_extra_api" + + expect(the_bundle).to include_gems "somegem 1.0.0" + expect(the_bundle).to include_gems "activesupport 1.2.3" + end + + it "considers all possible versions of dependencies from all api gem sources when using blocks", :bundler => "< 2" do + # In this scenario, the gem "somegem" only exists in repo4. It depends on specific version of activesupport that + # exists only in repo1. There happens also be a version of activesupport in repo4, but not the one that version 1.0.0 + # of somegem wants. This test makes sure that bundler actually finds version 1.2.3 of active support in the other + # repo and installs it. + build_repo4 do + build_gem "activesupport", "1.2.0" + build_gem "somegem", "1.0.0" do |s| + s.add_dependency "activesupport", "1.2.3" # This version exists only in repo1 + end + end + + gemfile <<-G + source "#{source_uri}" + source "#{source_uri}/extra" do + gem 'somegem', '1.0.0' + end + G + + bundle! :install, :artifice => "compact_index_extra_api" + + expect(the_bundle).to include_gems "somegem 1.0.0" + expect(the_bundle).to include_gems "activesupport 1.2.3" + end + + it "prints API output properly with back deps" do + build_repo2 do + build_gem "back_deps" do |s| + s.add_dependency "foo" + end + FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] + end + + gemfile <<-G + source "#{source_uri}" + source "#{source_uri}/extra" do + gem "back_deps" + end + G + + bundle! :install, :artifice => "compact_index_extra" + + expect(out).to include("Fetching gem metadata from http://localgemserver.test/") + expect(out).to include("Fetching source index from http://localgemserver.test/extra") + end + + it "does not fetch every spec if the index of gems is large when doing back deps" do + build_repo2 do + build_gem "back_deps" do |s| + s.add_dependency "foo" + end + build_gem "missing" + # need to hit the limit + 1.upto(Bundler::Source::Rubygems::API_REQUEST_LIMIT) do |i| + build_gem "gem#{i}" + end + + FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] + end + + gemfile <<-G + source "#{source_uri}" + source "#{source_uri}/extra" do + gem "back_deps" + end + G + + bundle! :install, :artifice => "compact_index_extra_missing" + expect(the_bundle).to include_gems "back_deps 1.0" + end + + it "does not fetch every spec if the index of gems is large when doing back deps & everything is the compact index" do + build_repo4 do + build_gem "back_deps" do |s| + s.add_dependency "foo" + end + build_gem "missing" + # need to hit the limit + 1.upto(Bundler::Source::Rubygems::API_REQUEST_LIMIT) do |i| + build_gem "gem#{i}" + end + + FileUtils.rm_rf Dir[gem_repo4("gems/foo-*.gem")] + end + + install_gemfile! <<-G, :artifice => "compact_index_extra_api_missing" + source "#{source_uri}" + source "#{source_uri}/extra" do + gem "back_deps" + end + G + + expect(the_bundle).to include_gem "back_deps 1.0" + end + + it "uses the endpoint if all sources support it" do + gemfile <<-G + source "#{source_uri}" + + gem 'foo' + G + + bundle! :install, :artifice => "compact_index_api_missing" + expect(the_bundle).to include_gems "foo 1.0" + end + + it "fetches again when more dependencies are found in subsequent sources using --deployment", :bundler => "< 2" do + build_repo2 do + build_gem "back_deps" do |s| + s.add_dependency "foo" + end + FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] + end + + gemfile <<-G + source "#{source_uri}" + source "#{source_uri}/extra" + gem "back_deps" + G + + bundle! :install, :artifice => "compact_index_extra" + + bundle "install --deployment", :artifice => "compact_index_extra" + expect(the_bundle).to include_gems "back_deps 1.0" + end + + it "fetches again when more dependencies are found in subsequent sources using --deployment with blocks" do + build_repo2 do + build_gem "back_deps" do |s| + s.add_dependency "foo" + end + FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] + end + + gemfile <<-G + source "#{source_uri}" + source "#{source_uri}/extra" do + gem "back_deps" + end + G + + bundle! :install, :artifice => "compact_index_extra" + + bundle "install --deployment", :artifice => "compact_index_extra" + expect(the_bundle).to include_gems "back_deps 1.0" + end + + it "does not refetch if the only unmet dependency is bundler" do + gemfile <<-G + source "#{source_uri}" + + gem "bundler_dep" + G + + bundle! :install, :artifice => "compact_index" + expect(out).to include("Fetching gem metadata from #{source_uri}") + end + + it "should install when EndpointSpecification has a bin dir owned by root", :sudo => true do + sudo "mkdir -p #{system_gem_path("bin")}" + sudo "chown -R root #{system_gem_path("bin")}" + + gemfile <<-G + source "#{source_uri}" + gem "rails" + G + bundle! :install, :artifice => "compact_index" + expect(the_bundle).to include_gems "rails 2.3.2" + end + + it "installs the binstubs", :bundler => "< 2" do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + + bundle "install --binstubs", :artifice => "compact_index" + + gembin "rackup" + expect(out).to eq("1.0.0") + end + + it "installs the bins when using --path and uses autoclean", :bundler => "< 2" do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + + bundle "install --path vendor/bundle", :artifice => "compact_index" + + expect(vendored_gems("bin/rackup")).to exist + end + + it "installs the bins when using --path and uses bundle clean", :bundler => "< 2" do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + + bundle "install --path vendor/bundle --no-clean", :artifice => "compact_index" + + expect(vendored_gems("bin/rackup")).to exist + end + + it "prints post_install_messages" do + gemfile <<-G + source "#{source_uri}" + gem 'rack-obama' + G + + bundle! :install, :artifice => "compact_index" + expect(out).to include("Post-install message from rack:") + end + + it "should display the post install message for a dependency" do + gemfile <<-G + source "#{source_uri}" + gem 'rack_middleware' + G + + bundle! :install, :artifice => "compact_index" + expect(out).to include("Post-install message from rack:") + expect(out).to include("Rack's post install message") + end + + context "when using basic authentication" do + let(:user) { "user" } + let(:password) { "pass" } + let(:basic_auth_source_uri) do + uri = URI.parse(source_uri) + uri.user = user + uri.password = password + + uri + end + + it "passes basic authentication details and strips out creds" do + gemfile <<-G + source "#{basic_auth_source_uri}" + gem "rack" + G + + bundle! :install, :artifice => "compact_index_basic_authentication" + expect(out).not_to include("#{user}:#{password}") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "strips http basic authentication creds for modern index" do + gemfile <<-G + source "#{basic_auth_source_uri}" + gem "rack" + G + + bundle! :install, :artifice => "endopint_marshal_fail_basic_authentication" + expect(out).not_to include("#{user}:#{password}") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "strips http basic auth creds when it can't reach the server" do + gemfile <<-G + source "#{basic_auth_source_uri}" + gem "rack" + G + + bundle :install, :artifice => "endpoint_500" + expect(out).not_to include("#{user}:#{password}") + end + + it "strips http basic auth creds when warning about ambiguous sources", :bundler => "< 2" do + gemfile <<-G + source "#{basic_auth_source_uri}" + source "file://#{gem_repo1}" + gem "rack" + G + + bundle! :install, :artifice => "compact_index_basic_authentication" + expect(out).to include("Warning: the gem 'rack' was found in multiple sources.") + expect(out).not_to include("#{user}:#{password}") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "does not pass the user / password to different hosts on redirect" do + gemfile <<-G + source "#{basic_auth_source_uri}" + gem "rack" + G + + bundle! :install, :artifice => "compact_index_creds_diff_host" + expect(the_bundle).to include_gems "rack 1.0.0" + end + + describe "with authentication details in bundle config" do + before do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + end + + it "reads authentication details by host name from bundle config" do + bundle "config #{source_hostname} #{user}:#{password}" + + bundle! :install, :artifice => "compact_index_strict_basic_authentication" + + expect(out).to include("Fetching gem metadata from #{source_uri}") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "reads authentication details by full url from bundle config" do + # The trailing slash is necessary here; Fetcher canonicalizes the URI. + bundle "config #{source_uri}/ #{user}:#{password}" + + bundle! :install, :artifice => "compact_index_strict_basic_authentication" + + expect(out).to include("Fetching gem metadata from #{source_uri}") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "should use the API" do + bundle "config #{source_hostname} #{user}:#{password}" + bundle! :install, :artifice => "compact_index_strict_basic_authentication" + expect(out).to include("Fetching gem metadata from #{source_uri}") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "prefers auth supplied in the source uri" do + gemfile <<-G + source "#{basic_auth_source_uri}" + gem "rack" + G + + bundle "config #{source_hostname} otheruser:wrong" + + bundle! :install, :artifice => "compact_index_strict_basic_authentication" + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "shows instructions if auth is not provided for the source" do + bundle :install, :artifice => "compact_index_strict_basic_authentication" + expect(out).to include("bundle config #{source_hostname} username:password") + end + + it "fails if authentication has already been provided, but failed" do + bundle "config #{source_hostname} #{user}:wrong" + + bundle :install, :artifice => "compact_index_strict_basic_authentication" + expect(out).to include("Bad username or password") + end + end + + describe "with no password" do + let(:password) { nil } + + it "passes basic authentication details" do + gemfile <<-G + source "#{basic_auth_source_uri}" + gem "rack" + G + + bundle! :install, :artifice => "compact_index_basic_authentication" + expect(the_bundle).to include_gems "rack 1.0.0" + end + end + end + + context "when ruby is compiled without openssl" do + before do + # Install a monkeypatch that reproduces the effects of openssl being + # missing when the fetcher runs, as happens in real life. The reason + # we can't just overwrite openssl.rb is that Artifice uses it. + bundled_app("broken_ssl").mkpath + bundled_app("broken_ssl/openssl.rb").open("w") do |f| + f.write <<-RUBY + raise LoadError, "cannot load such file -- openssl" + RUBY + end + end + + it "explains what to do to get it" do + gemfile <<-G + source "#{source_uri.gsub(/http/, "https")}" + gem "rack" + G + + bundle :install, :env => { "RUBYOPT" => "-I#{bundled_app("broken_ssl")}" } + expect(out).to include("OpenSSL") + end + end + + context "when SSL certificate verification fails" do + it "explains what happened" do + # Install a monkeypatch that reproduces the effects of openssl raising + # a certificate validation error when RubyGems tries to connect. + gemfile <<-G + class Net::HTTP + def start + raise OpenSSL::SSL::SSLError, "certificate verify failed" + end + end + + source "#{source_uri.gsub(/http/, "https")}" + gem "rack" + G + + bundle :install + expect(out).to match(/could not verify the SSL certificate/i) + end + end + + context ".gemrc with sources is present" do + before do + File.open(home(".gemrc"), "w") do |file| + file.puts({ :sources => ["https://rubygems.org"] }.to_yaml) + end + end + + after do + home(".gemrc").rmtree + end + + it "uses other sources declared in the Gemfile" do + gemfile <<-G + source "#{source_uri}" + gem 'rack' + G + + bundle! :install, :artifice => "compact_index_forbidden" + end + end + + it "performs partial update with a non-empty range" do + gemfile <<-G + source "#{source_uri}" + gem 'rack', '0.9.1' + G + + # Initial install creates the cached versions file + bundle! :install, :artifice => "compact_index" + + # Update the Gemfile so we can check subsequent install was successful + gemfile <<-G + source "#{source_uri}" + gem 'rack', '1.0.0' + G + + # Second install should make only a partial request to /versions + bundle! :install, :artifice => "compact_index_partial_update" + + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "performs partial update while local cache is updated by another process" do + gemfile <<-G + source "#{source_uri}" + gem 'rack' + G + + # Create an empty file to trigger a partial download + versions = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index", + "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions") + FileUtils.mkdir_p(File.dirname(versions)) + FileUtils.touch(versions) + + bundle! :install, :artifice => "compact_index_concurrent_download" + + expect(File.read(versions)).to start_with("created_at") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "performs full update of compact index info cache if range is not satisfiable" do + gemfile <<-G + source "#{source_uri}" + gem 'rack', '0.9.1' + G + + rake_info_path = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index", + "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "info", "rack") + + bundle! :install, :artifice => "compact_index" + + expected_rack_info_content = File.read(rake_info_path) + + # Modify the cache files. We expect them to be reset to the normal ones when we re-run :install + File.open(rake_info_path, "w") {|f| f << (expected_rack_info_content + "this is different") } + + # Update the Gemfile so the next install does its normal things + gemfile <<-G + source "#{source_uri}" + gem 'rack', '1.0.0' + G + + # The cache files now being longer means the requested range is going to be not satisfiable + # Bundler must end up requesting the whole file to fix things up. + bundle! :install, :artifice => "compact_index_range_not_satisfiable" + + resulting_rack_info_content = File.read(rake_info_path) + + expect(resulting_rack_info_content).to eq(expected_rack_info_content) + end + + it "fails gracefully when the source URI has an invalid scheme" do + install_gemfile <<-G + source "htps://rubygems.org" + gem "rack" + G + expect(exitstatus).to eq(15) if exitstatus + expect(out).to end_with(<<-E.strip) + The request uri `htps://index.rubygems.org/versions` has an invalid scheme (`htps`). Did you mean `http` or `https`? + E + end + + describe "checksum validation", :rubygems => ">= 2.3.0" do + it "raises when the checksum does not match" do + install_gemfile <<-G, :artifice => "compact_index_wrong_gem_checksum" + source "#{source_uri}" + gem "rack" + G + + expect(exitstatus).to eq(19) if exitstatus + expect(out). + to include("Bundler cannot continue installing rack (1.0.0)."). + and include("The checksum for the downloaded `rack-1.0.0.gem` does not match the checksum given by the server."). + and include("This means the contents of the downloaded gem is different from what was uploaded to the server, and could be a potential security issue."). + and include("To resolve this issue:"). + and include("1. delete the downloaded gem located at: `#{default_bundle_path}/gems/rack-1.0.0/rack-1.0.0.gem`"). + and include("2. run `bundle install`"). + and include("If you wish to continue installing the downloaded gem, and are certain it does not pose a security issue despite the mismatching checksum, do the following:"). + and include("1. run `bundle config disable_checksum_validation true` to turn off checksum verification"). + and include("2. run `bundle install`"). + and match(/\(More info: The expected SHA256 checksum was "#{"ab" * 22}", but the checksum for the downloaded gem was ".+?"\.\)/) + end + + it "raises when the checksum is the wrong length" do + install_gemfile <<-G, :artifice => "compact_index_wrong_gem_checksum", :env => { "BUNDLER_SPEC_RACK_CHECKSUM" => "checksum!" } + source "#{source_uri}" + gem "rack" + G + expect(exitstatus).to eq(5) if exitstatus + expect(out).to include("The given checksum for rack-1.0.0 (\"checksum!\") is not a valid SHA256 hexdigest nor base64digest") + end + + it "does not raise when disable_checksum_validation is set" do + bundle! "config disable_checksum_validation true" + install_gemfile! <<-G, :artifice => "compact_index_wrong_gem_checksum" + source "#{source_uri}" + gem "rack" + G + end + end + + it "works when cache dir is world-writable" do + install_gemfile! <<-G, :artifice => "compact_index" + File.umask(0000) + source "#{source_uri}" + gem "rack" + G + end + + it "doesn't explode when the API dependencies are wrong" do + install_gemfile <<-G, :artifice => "compact_index_wrong_dependencies", :env => { "DEBUG" => "true" } + source "#{source_uri}" + gem "rails" + G + deps = [Gem::Dependency.new("rake", "= 10.0.2"), + Gem::Dependency.new("actionpack", "= 2.3.2"), + Gem::Dependency.new("activerecord", "= 2.3.2"), + Gem::Dependency.new("actionmailer", "= 2.3.2"), + Gem::Dependency.new("activeresource", "= 2.3.2")] + expect(out).to include(<<-E.strip).and include("rails-2.3.2 from rubygems remote at #{source_uri}/ has either corrupted API or lockfile dependencies") +Bundler::APIResponseMismatchError: Downloading rails-2.3.2 revealed dependencies not in the API or the lockfile (#{deps.map(&:to_s).join(", ")}). +Either installing with `--full-index` or running `bundle update rails` should fix the problem. + E + end + + it "does not duplicate specs in the lockfile when updating and a dependency is not installed" do + install_gemfile! <<-G, :artifice => "compact_index" + source "#{source_uri}" do + gem "rails" + gem "activemerchant" + end + G + gem_command! :uninstall, "activemerchant" + bundle! "update rails", :artifice => "compact_index" + expect(lockfile.scan(/activemerchant \(/).size).to eq(1) + end +end diff --git a/spec/bundler/install/gems/dependency_api_spec.rb b/spec/bundler/install/gems/dependency_api_spec.rb new file mode 100644 index 0000000000..2ffe4b62d7 --- /dev/null +++ b/spec/bundler/install/gems/dependency_api_spec.rb @@ -0,0 +1,760 @@ +# frozen_string_literal: true + +RSpec.describe "gemcutter's dependency API" do + let(:source_hostname) { "localgemserver.test" } + let(:source_uri) { "http://#{source_hostname}" } + + it "should use the API" do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + + bundle :install, :artifice => "endpoint" + expect(out).to include("Fetching gem metadata from #{source_uri}") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "should URI encode gem names" do + gemfile <<-G + source "#{source_uri}" + gem " sinatra" + G + + bundle :install, :artifice => "endpoint" + expect(out).to include("' sinatra' is not a valid gem name because it contains whitespace.") + end + + it "should handle nested dependencies" do + gemfile <<-G + source "#{source_uri}" + gem "rails" + G + + bundle :install, :artifice => "endpoint" + expect(out).to include("Fetching gem metadata from #{source_uri}/...") + expect(the_bundle).to include_gems( + "rails 2.3.2", + "actionpack 2.3.2", + "activerecord 2.3.2", + "actionmailer 2.3.2", + "activeresource 2.3.2", + "activesupport 2.3.2" + ) + end + + it "should handle multiple gem dependencies on the same gem" do + gemfile <<-G + source "#{source_uri}" + gem "net-sftp" + G + + bundle :install, :artifice => "endpoint" + expect(the_bundle).to include_gems "net-sftp 1.1.1" + end + + it "should use the endpoint when using --deployment" do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + bundle :install, :artifice => "endpoint" + + bundle! :install, forgotten_command_line_options(:deployment => true, :path => "vendor/bundle").merge(:artifice => "endpoint") + expect(out).to include("Fetching gem metadata from #{source_uri}") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "handles git dependencies that are in rubygems" do + build_git "foo" do |s| + s.executables = "foobar" + s.add_dependency "rails", "2.3.2" + end + + gemfile <<-G + source "#{source_uri}" + git "file:///#{lib_path("foo-1.0")}" do + gem 'foo' + end + G + + bundle :install, :artifice => "endpoint" + + expect(the_bundle).to include_gems("rails 2.3.2") + end + + it "handles git dependencies that are in rubygems using --deployment" do + build_git "foo" do |s| + s.executables = "foobar" + s.add_dependency "rails", "2.3.2" + end + + gemfile <<-G + source "#{source_uri}" + gem 'foo', :git => "file:///#{lib_path("foo-1.0")}" + G + + bundle :install, :artifice => "endpoint" + + bundle "install --deployment", :artifice => "endpoint" + + expect(the_bundle).to include_gems("rails 2.3.2") + end + + it "doesn't fail if you only have a git gem with no deps when using --deployment" do + build_git "foo" + gemfile <<-G + source "#{source_uri}" + gem 'foo', :git => "file:///#{lib_path("foo-1.0")}" + G + + bundle "install", :artifice => "endpoint" + bundle! :install, forgotten_command_line_options(:deployment => true).merge(:artifice => "endpoint") + + expect(the_bundle).to include_gems("foo 1.0") + end + + it "falls back when the API errors out" do + simulate_platform mswin + + gemfile <<-G + source "#{source_uri}" + gem "rcov" + G + + bundle :install, :artifice => "windows" + expect(out).to include("Fetching source index from #{source_uri}") + expect(the_bundle).to include_gems "rcov 1.0.0" + end + + it "falls back when hitting the Gemcutter Dependency Limit" do + gemfile <<-G + source "#{source_uri}" + gem "activesupport" + gem "actionpack" + gem "actionmailer" + gem "activeresource" + gem "thin" + gem "rack" + gem "rails" + G + bundle :install, :artifice => "endpoint_fallback" + expect(out).to include("Fetching source index from #{source_uri}") + + expect(the_bundle).to include_gems( + "activesupport 2.3.2", + "actionpack 2.3.2", + "actionmailer 2.3.2", + "activeresource 2.3.2", + "activesupport 2.3.2", + "thin 1.0.0", + "rack 1.0.0", + "rails 2.3.2" + ) + end + + it "falls back when Gemcutter API doesn't return proper Marshal format" do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + + bundle :install, :verbose => true, :artifice => "endpoint_marshal_fail" + expect(out).to include("could not fetch from the dependency API, trying the full index") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "falls back when the API URL returns 403 Forbidden" do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + + bundle :install, :verbose => true, :artifice => "endpoint_api_forbidden" + expect(out).to include("Fetching source index from #{source_uri}") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "handles host redirects" do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + + bundle :install, :artifice => "endpoint_host_redirect" + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "handles host redirects without Net::HTTP::Persistent" do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + + FileUtils.mkdir_p lib_path + File.open(lib_path("disable_net_http_persistent.rb"), "w") do |h| + h.write <<-H + module Kernel + alias require_without_disabled_net_http require + def require(*args) + raise LoadError, 'simulated' if args.first == 'openssl' && !caller.grep(/vendored_persistent/).empty? + require_without_disabled_net_http(*args) + end + end + H + end + + bundle :install, :artifice => "endpoint_host_redirect", :requires => [lib_path("disable_net_http_persistent.rb")] + expect(out).to_not match(/Too many redirects/) + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "timeouts when Bundler::Fetcher redirects too much" do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + + bundle :install, :artifice => "endpoint_redirect" + expect(out).to match(/Too many redirects/) + end + + context "when --full-index is specified" do + it "should use the modern index for install" do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + + bundle "install --full-index", :artifice => "endpoint" + expect(out).to include("Fetching source index from #{source_uri}") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "should use the modern index for update" do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + + bundle! "update --full-index", :artifice => "endpoint", :all => bundle_update_requires_all? + expect(out).to include("Fetching source index from #{source_uri}") + expect(the_bundle).to include_gems "rack 1.0.0" + end + end + + it "fetches again when more dependencies are found in subsequent sources", :bundler => "< 2" do + build_repo2 do + build_gem "back_deps" do |s| + s.add_dependency "foo" + end + FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] + end + + gemfile <<-G + source "#{source_uri}" + source "#{source_uri}/extra" + gem "back_deps" + G + + bundle :install, :artifice => "endpoint_extra" + expect(the_bundle).to include_gems "back_deps 1.0", "foo 1.0" + end + + it "fetches again when more dependencies are found in subsequent sources using blocks" do + build_repo2 do + build_gem "back_deps" do |s| + s.add_dependency "foo" + end + FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] + end + + gemfile <<-G + source "#{source_uri}" + source "#{source_uri}/extra" do + gem "back_deps" + end + G + + bundle :install, :artifice => "endpoint_extra" + expect(the_bundle).to include_gems "back_deps 1.0", "foo 1.0" + end + + it "fetches gem versions even when those gems are already installed" do + gemfile <<-G + source "#{source_uri}" + gem "rack", "1.0.0" + G + bundle :install, :artifice => "endpoint_extra_api" + + build_repo4 do + build_gem "rack", "1.2" do |s| + s.executables = "rackup" + end + end + + gemfile <<-G + source "#{source_uri}" do; end + source "#{source_uri}/extra" + gem "rack", "1.2" + G + bundle :install, :artifice => "endpoint_extra_api" + expect(the_bundle).to include_gems "rack 1.2" + end + + it "considers all possible versions of dependencies from all api gem sources", :bundler => "< 2" do + # In this scenario, the gem "somegem" only exists in repo4. It depends on specific version of activesupport that + # exists only in repo1. There happens also be a version of activesupport in repo4, but not the one that version 1.0.0 + # of somegem wants. This test makes sure that bundler actually finds version 1.2.3 of active support in the other + # repo and installs it. + build_repo4 do + build_gem "activesupport", "1.2.0" + build_gem "somegem", "1.0.0" do |s| + s.add_dependency "activesupport", "1.2.3" # This version exists only in repo1 + end + end + + gemfile <<-G + source "#{source_uri}" + source "#{source_uri}/extra" + gem 'somegem', '1.0.0' + G + + bundle! :install, :artifice => "endpoint_extra_api" + + expect(the_bundle).to include_gems "somegem 1.0.0" + expect(the_bundle).to include_gems "activesupport 1.2.3" + end + + it "considers all possible versions of dependencies from all api gem sources using blocks" do + # In this scenario, the gem "somegem" only exists in repo4. It depends on specific version of activesupport that + # exists only in repo1. There happens also be a version of activesupport in repo4, but not the one that version 1.0.0 + # of somegem wants. This test makes sure that bundler actually finds version 1.2.3 of active support in the other + # repo and installs it. + build_repo4 do + build_gem "activesupport", "1.2.0" + build_gem "somegem", "1.0.0" do |s| + s.add_dependency "activesupport", "1.2.3" # This version exists only in repo1 + end + end + + gemfile <<-G + source "#{source_uri}" + source "#{source_uri}/extra" do + gem 'somegem', '1.0.0' + end + G + + bundle :install, :artifice => "endpoint_extra_api" + + expect(the_bundle).to include_gems "somegem 1.0.0" + expect(the_bundle).to include_gems "activesupport 1.2.3" + end + + it "prints API output properly with back deps" do + build_repo2 do + build_gem "back_deps" do |s| + s.add_dependency "foo" + end + FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] + end + + gemfile <<-G + source "#{source_uri}" + source "#{source_uri}/extra" do + gem "back_deps" + end + G + + bundle :install, :artifice => "endpoint_extra" + + expect(out).to include("Fetching gem metadata from http://localgemserver.test/.") + expect(out).to include("Fetching source index from http://localgemserver.test/extra") + end + + it "does not fetch every spec if the index of gems is large when doing back deps", :bundler => "< 2" do + build_repo2 do + build_gem "back_deps" do |s| + s.add_dependency "foo" + end + build_gem "missing" + # need to hit the limit + 1.upto(Bundler::Source::Rubygems::API_REQUEST_LIMIT) do |i| + build_gem "gem#{i}" + end + + FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] + end + + gemfile <<-G + source "#{source_uri}" + source "#{source_uri}/extra" + gem "back_deps" + G + + bundle :install, :artifice => "endpoint_extra_missing" + expect(the_bundle).to include_gems "back_deps 1.0" + end + + it "does not fetch every spec if the index of gems is large when doing back deps using blocks" do + build_repo2 do + build_gem "back_deps" do |s| + s.add_dependency "foo" + end + build_gem "missing" + # need to hit the limit + 1.upto(Bundler::Source::Rubygems::API_REQUEST_LIMIT) do |i| + build_gem "gem#{i}" + end + + FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] + end + + gemfile <<-G + source "#{source_uri}" + source "#{source_uri}/extra" do + gem "back_deps" + end + G + + bundle :install, :artifice => "endpoint_extra_missing" + expect(the_bundle).to include_gems "back_deps 1.0" + end + + it "uses the endpoint if all sources support it" do + gemfile <<-G + source "#{source_uri}" + + gem 'foo' + G + + bundle :install, :artifice => "endpoint_api_missing" + expect(the_bundle).to include_gems "foo 1.0" + end + + it "fetches again when more dependencies are found in subsequent sources using --deployment", :bundler => "< 2" do + build_repo2 do + build_gem "back_deps" do |s| + s.add_dependency "foo" + end + FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] + end + + gemfile <<-G + source "#{source_uri}" + source "#{source_uri}/extra" + gem "back_deps" + G + + bundle :install, :artifice => "endpoint_extra" + + bundle "install --deployment", :artifice => "endpoint_extra" + expect(the_bundle).to include_gems "back_deps 1.0" + end + + it "fetches again when more dependencies are found in subsequent sources using --deployment with blocks" do + build_repo2 do + build_gem "back_deps" do |s| + s.add_dependency "foo" + end + FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] + end + + gemfile <<-G + source "#{source_uri}" + source "#{source_uri}/extra" do + gem "back_deps" + end + G + + bundle :install, :artifice => "endpoint_extra" + + bundle "install --deployment", :artifice => "endpoint_extra" + expect(the_bundle).to include_gems "back_deps 1.0" + end + + it "does not refetch if the only unmet dependency is bundler" do + gemfile <<-G + source "#{source_uri}" + + gem "bundler_dep" + G + + bundle :install, :artifice => "endpoint" + expect(out).to include("Fetching gem metadata from #{source_uri}") + end + + it "should install when EndpointSpecification has a bin dir owned by root", :sudo => true do + sudo "mkdir -p #{system_gem_path("bin")}" + sudo "chown -R root #{system_gem_path("bin")}" + + gemfile <<-G + source "#{source_uri}" + gem "rails" + G + bundle :install, :artifice => "endpoint" + expect(the_bundle).to include_gems "rails 2.3.2" + end + + it "installs the binstubs", :bundler => "< 2" do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + + bundle "install --binstubs", :artifice => "endpoint" + + gembin "rackup" + expect(out).to eq("1.0.0") + end + + it "installs the bins when using --path and uses autoclean", :bundler => "< 2" do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + + bundle "install --path vendor/bundle", :artifice => "endpoint" + + expect(vendored_gems("bin/rackup")).to exist + end + + it "installs the bins when using --path and uses bundle clean", :bundler => "< 2" do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + + bundle "install --path vendor/bundle --no-clean", :artifice => "endpoint" + + expect(vendored_gems("bin/rackup")).to exist + end + + it "prints post_install_messages" do + gemfile <<-G + source "#{source_uri}" + gem 'rack-obama' + G + + bundle :install, :artifice => "endpoint" + expect(out).to include("Post-install message from rack:") + end + + it "should display the post install message for a dependency" do + gemfile <<-G + source "#{source_uri}" + gem 'rack_middleware' + G + + bundle :install, :artifice => "endpoint" + expect(out).to include("Post-install message from rack:") + expect(out).to include("Rack's post install message") + end + + context "when using basic authentication" do + let(:user) { "user" } + let(:password) { "pass" } + let(:basic_auth_source_uri) do + uri = URI.parse(source_uri) + uri.user = user + uri.password = password + + uri + end + + it "passes basic authentication details and strips out creds" do + gemfile <<-G + source "#{basic_auth_source_uri}" + gem "rack" + G + + bundle :install, :artifice => "endpoint_basic_authentication" + expect(out).not_to include("#{user}:#{password}") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "strips http basic authentication creds for modern index" do + gemfile <<-G + source "#{basic_auth_source_uri}" + gem "rack" + G + + bundle :install, :artifice => "endopint_marshal_fail_basic_authentication" + expect(out).not_to include("#{user}:#{password}") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "strips http basic auth creds when it can't reach the server" do + gemfile <<-G + source "#{basic_auth_source_uri}" + gem "rack" + G + + bundle :install, :artifice => "endpoint_500" + expect(out).not_to include("#{user}:#{password}") + end + + it "strips http basic auth creds when warning about ambiguous sources", :bundler => "< 2" do + gemfile <<-G + source "#{basic_auth_source_uri}" + source "file://#{gem_repo1}" + gem "rack" + G + + bundle :install, :artifice => "endpoint_basic_authentication" + expect(out).to include("Warning: the gem 'rack' was found in multiple sources.") + expect(out).not_to include("#{user}:#{password}") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "does not pass the user / password to different hosts on redirect" do + gemfile <<-G + source "#{basic_auth_source_uri}" + gem "rack" + G + + bundle :install, :artifice => "endpoint_creds_diff_host" + expect(the_bundle).to include_gems "rack 1.0.0" + end + + describe "with authentication details in bundle config" do + before do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + end + + it "reads authentication details by host name from bundle config" do + bundle "config #{source_hostname} #{user}:#{password}" + + bundle :install, :artifice => "endpoint_strict_basic_authentication" + + expect(out).to include("Fetching gem metadata from #{source_uri}") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "reads authentication details by full url from bundle config" do + # The trailing slash is necessary here; Fetcher canonicalizes the URI. + bundle "config #{source_uri}/ #{user}:#{password}" + + bundle :install, :artifice => "endpoint_strict_basic_authentication" + + expect(out).to include("Fetching gem metadata from #{source_uri}") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "should use the API" do + bundle "config #{source_hostname} #{user}:#{password}" + bundle :install, :artifice => "endpoint_strict_basic_authentication" + expect(out).to include("Fetching gem metadata from #{source_uri}") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "prefers auth supplied in the source uri" do + gemfile <<-G + source "#{basic_auth_source_uri}" + gem "rack" + G + + bundle "config #{source_hostname} otheruser:wrong" + + bundle :install, :artifice => "endpoint_strict_basic_authentication" + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "shows instructions if auth is not provided for the source" do + bundle :install, :artifice => "endpoint_strict_basic_authentication" + expect(out).to include("bundle config #{source_hostname} username:password") + end + + it "fails if authentication has already been provided, but failed" do + bundle "config #{source_hostname} #{user}:wrong" + + bundle :install, :artifice => "endpoint_strict_basic_authentication" + expect(out).to include("Bad username or password") + end + end + + describe "with no password" do + let(:password) { nil } + + it "passes basic authentication details" do + gemfile <<-G + source "#{basic_auth_source_uri}" + gem "rack" + G + + bundle :install, :artifice => "endpoint_basic_authentication" + expect(the_bundle).to include_gems "rack 1.0.0" + end + end + end + + context "when ruby is compiled without openssl" do + before do + # Install a monkeypatch that reproduces the effects of openssl being + # missing when the fetcher runs, as happens in real life. The reason + # we can't just overwrite openssl.rb is that Artifice uses it. + bundled_app("broken_ssl").mkpath + bundled_app("broken_ssl/openssl.rb").open("w") do |f| + f.write <<-RUBY + raise LoadError, "cannot load such file -- openssl" + RUBY + end + end + + it "explains what to do to get it" do + gemfile <<-G + source "#{source_uri.gsub(/http/, "https")}" + gem "rack" + G + + bundle :install, :env => { "RUBYOPT" => "-I#{bundled_app("broken_ssl")}" } + expect(out).to include("OpenSSL") + end + end + + context "when SSL certificate verification fails" do + it "explains what happened" do + # Install a monkeypatch that reproduces the effects of openssl raising + # a certificate validation error when RubyGems tries to connect. + gemfile <<-G + class Net::HTTP + def start + raise OpenSSL::SSL::SSLError, "certificate verify failed" + end + end + + source "#{source_uri.gsub(/http/, "https")}" + gem "rack" + G + + bundle :install + expect(out).to match(/could not verify the SSL certificate/i) + end + end + + context ".gemrc with sources is present" do + before do + File.open(home(".gemrc"), "w") do |file| + file.puts({ :sources => ["https://rubygems.org"] }.to_yaml) + end + end + + after do + home(".gemrc").rmtree + end + + it "uses other sources declared in the Gemfile" do + gemfile <<-G + source "#{source_uri}" + gem 'rack' + G + + bundle "install", :artifice => "endpoint_marshal_fail" + + expect(exitstatus).to eq(0) if exitstatus + end + end +end diff --git a/spec/bundler/install/gems/env_spec.rb b/spec/bundler/install/gems/env_spec.rb new file mode 100644 index 0000000000..0dccbbfd24 --- /dev/null +++ b/spec/bundler/install/gems/env_spec.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install with ENV conditionals" do + describe "when just setting an ENV key as a string" do + before :each do + gemfile <<-G + source "file://#{gem_repo1}" + + env "BUNDLER_TEST" do + gem "rack" + end + G + end + + it "excludes the gems when the ENV variable is not set" do + bundle :install + expect(the_bundle).not_to include_gems "rack" + end + + it "includes the gems when the ENV variable is set" do + ENV["BUNDLER_TEST"] = "1" + bundle :install + expect(the_bundle).to include_gems "rack 1.0" + end + end + + describe "when just setting an ENV key as a symbol" do + before :each do + gemfile <<-G + source "file://#{gem_repo1}" + + env :BUNDLER_TEST do + gem "rack" + end + G + end + + it "excludes the gems when the ENV variable is not set" do + bundle :install + expect(the_bundle).not_to include_gems "rack" + end + + it "includes the gems when the ENV variable is set" do + ENV["BUNDLER_TEST"] = "1" + bundle :install + expect(the_bundle).to include_gems "rack 1.0" + end + end + + describe "when setting a string to match the env" do + before :each do + gemfile <<-G + source "file://#{gem_repo1}" + + env "BUNDLER_TEST" => "foo" do + gem "rack" + end + G + end + + it "excludes the gems when the ENV variable is not set" do + bundle :install + expect(the_bundle).not_to include_gems "rack" + end + + it "excludes the gems when the ENV variable is set but does not match the condition" do + ENV["BUNDLER_TEST"] = "1" + bundle :install + expect(the_bundle).not_to include_gems "rack" + end + + it "includes the gems when the ENV variable is set and matches the condition" do + ENV["BUNDLER_TEST"] = "foo" + bundle :install + expect(the_bundle).to include_gems "rack 1.0" + end + end + + describe "when setting a regex to match the env" do + before :each do + gemfile <<-G + source "file://#{gem_repo1}" + + env "BUNDLER_TEST" => /foo/ do + gem "rack" + end + G + end + + it "excludes the gems when the ENV variable is not set" do + bundle :install + expect(the_bundle).not_to include_gems "rack" + end + + it "excludes the gems when the ENV variable is set but does not match the condition" do + ENV["BUNDLER_TEST"] = "fo" + bundle :install + expect(the_bundle).not_to include_gems "rack" + end + + it "includes the gems when the ENV variable is set and matches the condition" do + ENV["BUNDLER_TEST"] = "foobar" + bundle :install + expect(the_bundle).to include_gems "rack 1.0" + end + end +end diff --git a/spec/bundler/install/gems/flex_spec.rb b/spec/bundler/install/gems/flex_spec.rb new file mode 100644 index 0000000000..37d2e4958a --- /dev/null +++ b/spec/bundler/install/gems/flex_spec.rb @@ -0,0 +1,351 @@ +# frozen_string_literal: true + +RSpec.describe "bundle flex_install" do + it "installs the gems as expected" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem 'rack' + G + + expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to be_locked + end + + it "installs even when the lockfile is invalid" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem 'rack' + G + + expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to be_locked + + gemfile <<-G + source "file://#{gem_repo1}" + gem 'rack', '1.0' + G + + bundle :install + expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to be_locked + end + + it "keeps child dependencies at the same version" do + build_repo2 + + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "rack-obama" + G + + expect(the_bundle).to include_gems "rack 1.0.0", "rack-obama 1.0.0" + + update_repo2 + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "rack-obama", "1.0" + G + + expect(the_bundle).to include_gems "rack 1.0.0", "rack-obama 1.0.0" + end + + describe "adding new gems" do + it "installs added gems without updating previously installed gems" do + build_repo2 + + install_gemfile <<-G + source "file://#{gem_repo2}" + gem 'rack' + G + + update_repo2 + + install_gemfile <<-G + source "file://#{gem_repo2}" + gem 'rack' + gem 'activesupport', '2.3.5' + G + + expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.5" + end + + it "keeps child dependencies pinned" do + build_repo2 + + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "rack-obama" + G + + update_repo2 + + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "rack-obama" + gem "thin" + G + + expect(the_bundle).to include_gems "rack 1.0.0", "rack-obama 1.0", "thin 1.0" + end + end + + describe "removing gems" do + it "removes gems without changing the versions of remaining gems" do + build_repo2 + install_gemfile <<-G + source "file://#{gem_repo2}" + gem 'rack' + gem 'activesupport', '2.3.5' + G + + update_repo2 + + install_gemfile <<-G + source "file://#{gem_repo2}" + gem 'rack' + G + + expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).not_to include_gems "activesupport 2.3.5" + + install_gemfile <<-G + source "file://#{gem_repo2}" + gem 'rack' + gem 'activesupport', '2.3.2' + G + + expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.2" + end + + it "removes top level dependencies when removed from the Gemfile while leaving other dependencies intact" do + build_repo2 + install_gemfile <<-G + source "file://#{gem_repo2}" + gem 'rack' + gem 'activesupport', '2.3.5' + G + + update_repo2 + + install_gemfile <<-G + source "file://#{gem_repo2}" + gem 'rack' + G + + expect(the_bundle).not_to include_gems "activesupport 2.3.5" + end + + it "removes child dependencies" do + build_repo2 + install_gemfile <<-G + source "file://#{gem_repo2}" + gem 'rack-obama' + gem 'activesupport' + G + + expect(the_bundle).to include_gems "rack 1.0.0", "rack-obama 1.0.0", "activesupport 2.3.5" + + update_repo2 + install_gemfile <<-G + source "file://#{gem_repo2}" + gem 'activesupport' + G + + expect(the_bundle).to include_gems "activesupport 2.3.5" + expect(the_bundle).not_to include_gems "rack-obama", "rack" + end + end + + describe "when Gemfile conflicts with lockfile" do + before(:each) do + build_repo2 + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "rack_middleware" + G + + expect(the_bundle).to include_gems "rack_middleware 1.0", "rack 0.9.1" + + build_repo2 + update_repo2 do + build_gem "rack-obama", "2.0" do |s| + s.add_dependency "rack", "=1.2" + end + build_gem "rack_middleware", "2.0" do |s| + s.add_dependency "rack", ">=1.0" + end + end + + gemfile <<-G + source "file://#{gem_repo2}" + gem "rack-obama", "2.0" + gem "rack_middleware" + G + end + + it "does not install gems whose dependencies are not met" do + bundle :install + ruby <<-RUBY + require 'bundler/setup' + RUBY + expect(err).to match(/could not find gem 'rack-obama/i) + end + + it "suggests bundle update when the Gemfile requires different versions than the lock" do + nice_error = <<-E.strip.gsub(/^ {8}/, "") + Bundler could not find compatible versions for gem "rack": + In snapshot (Gemfile.lock): + rack (= 0.9.1) + + In Gemfile: + rack-obama (= 2.0) was resolved to 2.0, which depends on + rack (= 1.2) + + rack_middleware was resolved to 1.0, which depends on + rack (= 0.9.1) + + Running `bundle update` will rebuild your snapshot from scratch, using only + the gems in your Gemfile, which may resolve the conflict. + E + + bundle :install, :retry => 0 + expect(last_command.bundler_err).to end_with(nice_error) + end + end + + describe "subtler cases" do + before :each do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "rack-obama" + G + + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", "0.9.1" + gem "rack-obama" + G + end + + it "does something" do + expect do + bundle "install" + end.not_to change { File.read(bundled_app("Gemfile.lock")) } + + expect(out).to include("rack = 0.9.1") + expect(out).to include("locked at 1.0.0") + expect(out).to include("bundle update rack") + end + + it "should work when you update" do + bundle "update rack" + end + end + + describe "when adding a new source" do + it "updates the lockfile", :bundler => "< 2" do + build_repo2 + install_gemfile! <<-G + source "file://localhost#{gem_repo1}" + gem "rack" + G + install_gemfile! <<-G + source "file://localhost#{gem_repo1}" + source "file://localhost#{gem_repo2}" + gem "rack" + G + + lockfile_should_be <<-L + GEM + remote: file://localhost#{gem_repo1}/ + remote: file://localhost#{gem_repo2}/ + specs: + rack (1.0.0) + + PLATFORMS + ruby + + DEPENDENCIES + rack + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "updates the lockfile", :bundler => "2" do + build_repo2 + install_gemfile! <<-G + source "file://localhost#{gem_repo1}" + gem "rack" + G + + install_gemfile! <<-G + source "file://localhost#{gem_repo1}" + source "file://localhost#{gem_repo2}" do + end + gem "rack" + G + + lockfile_should_be <<-L + GEM + remote: file://localhost#{gem_repo1}/ + specs: + rack (1.0.0) + + GEM + remote: file://localhost#{gem_repo2}/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rack + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + # This was written to test github issue #636 + describe "when a locked child dependency conflicts" do + before(:each) do + build_repo2 do + build_gem "capybara", "0.3.9" do |s| + s.add_dependency "rack", ">= 1.0.0" + end + + build_gem "rack", "1.1.0" + build_gem "rails", "3.0.0.rc4" do |s| + s.add_dependency "rack", "~> 1.1.0" + end + + build_gem "rack", "1.2.1" + build_gem "rails", "3.0.0" do |s| + s.add_dependency "rack", "~> 1.2.1" + end + end + end + + it "prints the correct error message" do + # install Rails 3.0.0.rc + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "rails", "3.0.0.rc4" + gem "capybara", "0.3.9" + G + + # upgrade Rails to 3.0.0 and then install again + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "rails", "3.0.0" + gem "capybara", "0.3.9" + G + + expect(out).to include("Gemfile.lock") + end + end +end diff --git a/spec/bundler/install/gems/mirror_spec.rb b/spec/bundler/install/gems/mirror_spec.rb new file mode 100644 index 0000000000..4c35b8f206 --- /dev/null +++ b/spec/bundler/install/gems/mirror_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install with a mirror configured" do + describe "when the mirror does not match the gem source" do + before :each do + gemfile <<-G + source "file://localhost#{gem_repo1}" + + gem "rack" + G + bundle "config --local mirror.http://gems.example.org http://gem-mirror.example.org" + end + + it "installs from the normal location" do + bundle :install + expect(out).to include(normalize_uri_file("Fetching source index from file://localhost#{gem_repo1}")) + expect(the_bundle).to include_gems "rack 1.0" + end + end + + describe "when the gem source matches a configured mirror" do + before :each do + gemfile <<-G + # This source is bogus and doesn't have the gem we're looking for + source "file://localhost#{gem_repo2}" + + gem "rack" + G + bundle "config --local mirror.file://localhost#{gem_repo2} file://localhost#{gem_repo1}" + end + + it "installs the gem from the mirror" do + bundle :install + expect(out).to include(normalize_uri_file("Fetching source index from file://localhost#{gem_repo1}")) + expect(out).not_to include(normalize_uri_file("Fetching source index from file://localhost#{gem_repo2}")) + expect(the_bundle).to include_gems "rack 1.0" + end + end +end diff --git a/spec/bundler/install/gems/native_extensions_spec.rb b/spec/bundler/install/gems/native_extensions_spec.rb new file mode 100644 index 0000000000..ea616f60d3 --- /dev/null +++ b/spec/bundler/install/gems/native_extensions_spec.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +RSpec.describe "installing a gem with native extensions", :ruby_repo do + it "installs" do + build_repo2 do + build_gem "c_extension" do |s| + s.extensions = ["ext/extconf.rb"] + s.write "ext/extconf.rb", <<-E + require "mkmf" + name = "c_extension_bundle" + dir_config(name) + raise "OMG" unless with_config("c_extension") == "hello" + create_makefile(name) + E + + s.write "ext/c_extension.c", <<-C + #include "ruby.h" + + VALUE c_extension_true(VALUE self) { + return Qtrue; + } + + void Init_c_extension_bundle() { + VALUE c_Extension = rb_define_class("CExtension", rb_cObject); + rb_define_method(c_Extension, "its_true", c_extension_true, 0); + } + C + + s.write "lib/c_extension.rb", <<-C + require "c_extension_bundle" + C + end + end + + gemfile <<-G + source "file://#{gem_repo2}" + gem "c_extension" + G + + bundle "config build.c_extension --with-c_extension=hello" + bundle "install" + + expect(out).not_to include("extconf.rb failed") + expect(out).to include("Installing c_extension 1.0 with native extensions") + + run "Bundler.require; puts CExtension.new.its_true" + expect(out).to eq("true") + end + + it "installs from git" do + build_git "c_extension" do |s| + s.extensions = ["ext/extconf.rb"] + s.write "ext/extconf.rb", <<-E + require "mkmf" + name = "c_extension_bundle" + dir_config(name) + raise "OMG" unless with_config("c_extension") == "hello" + create_makefile(name) + E + + s.write "ext/c_extension.c", <<-C + #include "ruby.h" + + VALUE c_extension_true(VALUE self) { + return Qtrue; + } + + void Init_c_extension_bundle() { + VALUE c_Extension = rb_define_class("CExtension", rb_cObject); + rb_define_method(c_Extension, "its_true", c_extension_true, 0); + } + C + + s.write "lib/c_extension.rb", <<-C + require "c_extension_bundle" + C + end + + bundle! "config build.c_extension --with-c_extension=hello" + + install_gemfile! <<-G + gem "c_extension", :git => #{lib_path("c_extension-1.0").to_s.dump} + G + + expect(out).not_to include("extconf.rb failed") + + run! "Bundler.require; puts CExtension.new.its_true" + expect(out).to eq("true") + end +end diff --git a/spec/bundler/install/gems/post_install_spec.rb b/spec/bundler/install/gems/post_install_spec.rb new file mode 100644 index 0000000000..c6e348fb65 --- /dev/null +++ b/spec/bundler/install/gems/post_install_spec.rb @@ -0,0 +1,150 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install" do + context "with gem sources" do + context "when gems include post install messages" do + it "should display the post-install messages after installing" do + gemfile <<-G + source "file://#{gem_repo1}" + gem 'rack' + gem 'thin' + gem 'rack-obama' + G + + bundle :install + expect(out).to include("Post-install message from rack:") + expect(out).to include("Rack's post install message") + expect(out).to include("Post-install message from thin:") + expect(out).to include("Thin's post install message") + expect(out).to include("Post-install message from rack-obama:") + expect(out).to include("Rack-obama's post install message") + end + end + + context "when gems do not include post install messages" do + it "should not display any post-install messages" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "activesupport" + G + + bundle :install + expect(out).not_to include("Post-install message") + end + end + + context "when a dependecy includes a post install message" do + it "should display the post install message" do + gemfile <<-G + source "file://#{gem_repo1}" + gem 'rack_middleware' + G + + bundle :install + expect(out).to include("Post-install message from rack:") + expect(out).to include("Rack's post install message") + end + end + end + + context "with git sources" do + context "when gems include post install messages" do + it "should display the post-install messages after installing" do + build_git "foo" do |s| + s.post_install_message = "Foo's post install message" + end + gemfile <<-G + source "file://#{gem_repo1}" + gem 'foo', :git => '#{lib_path("foo-1.0")}' + G + + bundle :install + expect(out).to include("Post-install message from foo:") + expect(out).to include("Foo's post install message") + end + + it "should display the post-install messages if repo is updated" do + build_git "foo" do |s| + s.post_install_message = "Foo's post install message" + end + gemfile <<-G + source "file://#{gem_repo1}" + gem 'foo', :git => '#{lib_path("foo-1.0")}' + G + bundle :install + + build_git "foo", "1.1" do |s| + s.post_install_message = "Foo's 1.1 post install message" + end + gemfile <<-G + source "file://#{gem_repo1}" + gem 'foo', :git => '#{lib_path("foo-1.1")}' + G + bundle :install + + expect(out).to include("Post-install message from foo:") + expect(out).to include("Foo's 1.1 post install message") + end + + it "should not display the post-install messages if repo is not updated" do + build_git "foo" do |s| + s.post_install_message = "Foo's post install message" + end + gemfile <<-G + source "file://#{gem_repo1}" + gem 'foo', :git => '#{lib_path("foo-1.0")}' + G + + bundle :install + expect(out).to include("Post-install message from foo:") + expect(out).to include("Foo's post install message") + + bundle :install + expect(out).not_to include("Post-install message") + end + end + + context "when gems do not include post install messages" do + it "should not display any post-install messages" do + build_git "foo" do |s| + s.post_install_message = nil + end + gemfile <<-G + source "file://#{gem_repo1}" + gem 'foo', :git => '#{lib_path("foo-1.0")}' + G + + bundle :install + expect(out).not_to include("Post-install message") + end + end + end + + context "when ignore post-install messages for gem is set" do + it "doesn't display any post-install messages" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + bundle "config ignore_messages.rack true" + + bundle :install + expect(out).not_to include("Post-install message") + end + end + + context "when ignore post-install messages for all gems" do + it "doesn't display any post-install messages" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + bundle "config ignore_messages true" + + bundle :install + expect(out).not_to include("Post-install message") + end + end +end diff --git a/spec/bundler/install/gems/resolving_spec.rb b/spec/bundler/install/gems/resolving_spec.rb new file mode 100644 index 0000000000..01c03ac793 --- /dev/null +++ b/spec/bundler/install/gems/resolving_spec.rb @@ -0,0 +1,195 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install with install-time dependencies" do + it "installs gems with implicit rake dependencies", :ruby_repo do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "with_implicit_rake_dep" + gem "another_implicit_rake_dep" + gem "rake" + G + + run <<-R + require 'implicit_rake_dep' + require 'another_implicit_rake_dep' + puts IMPLICIT_RAKE_DEP + puts ANOTHER_IMPLICIT_RAKE_DEP + R + expect(out).to eq("YES\nYES") + end + + it "installs gems with a dependency with no type" do + build_repo2 + + path = "#{gem_repo2}/#{Gem::MARSHAL_SPEC_DIR}/actionpack-2.3.2.gemspec.rz" + spec = Marshal.load(Bundler.rubygems.inflate(File.read(path))) + spec.dependencies.each do |d| + d.instance_variable_set(:@type, :fail) + end + File.open(path, "w") do |f| + f.write Gem.deflate(Marshal.dump(spec)) + end + + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "actionpack", "2.3.2" + G + + expect(the_bundle).to include_gems "actionpack 2.3.2", "activesupport 2.3.2" + end + + describe "with crazy rubygem plugin stuff" do + it "installs plugins" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "net_b" + G + + expect(the_bundle).to include_gems "net_b 1.0" + end + + it "installs plugins depended on by other plugins", :ruby_repo do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "net_a" + G + + expect(the_bundle).to include_gems "net_a 1.0", "net_b 1.0" + end + + it "installs multiple levels of dependencies", :ruby_repo do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "net_c" + gem "net_e" + G + + expect(the_bundle).to include_gems "net_a 1.0", "net_b 1.0", "net_c 1.0", "net_d 1.0", "net_e 1.0" + end + + context "with ENV['DEBUG_RESOLVER'] set" do + it "produces debug output" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "net_c" + gem "net_e" + G + + bundle :install, :env => { "DEBUG_RESOLVER" => "1" } + + expect(err).to include("Creating possibility state for net_c") + end + end + + context "with ENV['DEBUG_RESOLVER_TREE'] set" do + it "produces debug output" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "net_c" + gem "net_e" + G + + bundle :install, :env => { "DEBUG_RESOLVER_TREE" => "1" } + + expect(err).to include(" net_b"). + and include("Starting resolution"). + and include("Finished resolution"). + and include("Attempting to activate") + end + end + end + + describe "when a required ruby version" do + context "allows only an older version" do + it "installs the older version" do + build_repo2 do + build_gem "rack", "9001.0.0" do |s| + s.required_ruby_version = "> 9000" + end + end + + install_gemfile <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2 } + ruby "#{RUBY_VERSION}" + source "http://localgemserver.test/" + gem 'rack' + G + + expect(out).to_not include("rack-9001.0.0 requires ruby version > 9000") + expect(the_bundle).to include_gems("rack 1.2") + end + end + + context "allows no gems" do + before do + build_repo2 do + build_gem "require_ruby" do |s| + s.required_ruby_version = "> 9000" + end + end + end + + let(:ruby_requirement) { %("#{RUBY_VERSION}") } + let(:error_message_requirement) { "~> #{RUBY_VERSION}.0" } + + shared_examples_for "ruby version conflicts" do + it "raises an error during resolution" do + install_gemfile <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2 } + source "http://localgemserver.test/" + ruby #{ruby_requirement} + gem 'require_ruby' + G + + expect(out).to_not include("Gem::InstallError: require_ruby requires Ruby version > 9000") + + nice_error = strip_whitespace(<<-E).strip + Bundler could not find compatible versions for gem "ruby\0": + In Gemfile: + ruby\0 (#{error_message_requirement}) + + require_ruby was resolved to 1.0, which depends on + ruby\0 (> 9000) + + Could not find gem 'ruby\0 (> 9000)', which is required by gem 'require_ruby', in any of the relevant sources: + the local ruby installation + E + expect(last_command.bundler_err).to end_with(nice_error) + end + end + + it_behaves_like "ruby version conflicts" + + describe "with a < requirement" do + let(:ruby_requirement) { %("< 5000") } + let(:error_message_requirement) { "< 5000" } + + it_behaves_like "ruby version conflicts" + end + + describe "with a compound requirement" do + let(:reqs) { ["> 0.1", "< 5000"] } + let(:ruby_requirement) { reqs.map(&:dump).join(", ") } + let(:error_message_requirement) { Gem::Requirement.new(reqs).to_s } + + it_behaves_like "ruby version conflicts" + end + end + end + + describe "when a required rubygems version disallows a gem" do + it "does not try to install those gems" do + build_repo2 do + build_gem "require_rubygems" do |s| + s.required_rubygems_version = "> 9000" + end + end + + install_gemfile <<-G + source "file://#{gem_repo2}" + gem 'require_rubygems' + G + + expect(out).to_not include("Gem::InstallError: require_rubygems requires RubyGems version > 9000") + expect(out).to include("require_rubygems-1.0 requires rubygems version > 9000, which is incompatible with the current version, #{Gem::VERSION}") + end + end +end diff --git a/spec/bundler/install/gems/standalone_spec.rb b/spec/bundler/install/gems/standalone_spec.rb new file mode 100644 index 0000000000..10ce589eef --- /dev/null +++ b/spec/bundler/install/gems/standalone_spec.rb @@ -0,0 +1,337 @@ +# frozen_string_literal: true + +RSpec.shared_examples "bundle install --standalone" do + shared_examples "common functionality" do + it "still makes the gems available to normal bundler" do + args = expected_gems.map {|k, v| "#{k} #{v}" } + expect(the_bundle).to include_gems(*args) + end + + it "generates a bundle/bundler/setup.rb" do + expect(bundled_app("bundle/bundler/setup.rb")).to exist + end + + it "makes the gems available without bundler" do + testrb = String.new <<-RUBY + $:.unshift File.expand_path("bundle") + require "bundler/setup" + + RUBY + expected_gems.each do |k, _| + testrb << "\nrequire \"#{k}\"" + testrb << "\nputs #{k.upcase}" + end + Dir.chdir(bundled_app) do + ruby testrb, :no_lib => true + end + + expect(out).to eq(expected_gems.values.join("\n")) + end + + it "works on a different system" do + FileUtils.mv(bundled_app, "#{bundled_app}2") + + testrb = String.new <<-RUBY + $:.unshift File.expand_path("bundle") + require "bundler/setup" + + RUBY + expected_gems.each do |k, _| + testrb << "\nrequire \"#{k}\"" + testrb << "\nputs #{k.upcase}" + end + Dir.chdir("#{bundled_app}2") do + ruby testrb, :no_lib => true + end + + expect(out).to eq(expected_gems.values.join("\n")) + end + end + + describe "with simple gems" do + before do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + G + bundle! :install, forgotten_command_line_options(:path => bundled_app("bundle")).merge(:standalone => true) + end + + let(:expected_gems) do + { + "actionpack" => "2.3.2", + "rails" => "2.3.2", + } + end + + include_examples "common functionality" + end + + describe "with gems with native extension", :ruby_repo do + before do + install_gemfile <<-G, forgotten_command_line_options(:path => bundled_app("bundle")).merge(:standalone => true) + source "file://#{gem_repo1}" + gem "very_simple_binary" + G + end + + it "generates a bundle/bundler/setup.rb with the proper paths", :rubygems => "2.4" do + expected_path = bundled_app("bundle/bundler/setup.rb") + extension_line = File.read(expected_path).each_line.find {|line| line.include? "/extensions/" }.strip + expect(extension_line).to start_with '$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/' + expect(extension_line).to end_with '/very_simple_binary-1.0"' + end + end + + describe "with gem that has an invalid gemspec" do + before do + build_git "bar", :gemspec => false do |s| + s.write "lib/bar/version.rb", %(BAR_VERSION = '1.0') + s.write "bar.gemspec", <<-G + lib = File.expand_path('../lib/', __FILE__) + $:.unshift lib unless $:.include?(lib) + require 'bar/version' + + Gem::Specification.new do |s| + s.name = 'bar' + s.version = BAR_VERSION + s.summary = 'Bar' + s.files = Dir["lib/**/*.rb"] + s.author = 'Anonymous' + s.require_path = [1,2] + end + G + end + install_gemfile <<-G, forgotten_command_line_options(:path => bundled_app("bundle")).merge(:standalone => true) + gem "bar", :git => "#{lib_path("bar-1.0")}" + G + end + + it "outputs a helpful error message" do + expect(out).to include("You have one or more invalid gemspecs that need to be fixed.") + expect(out).to include("bar 1.0 has an invalid gemspec") + end + end + + describe "with a combination of gems and git repos" do + before do + build_git "devise", "1.0" + + gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + gem "devise", :git => "#{lib_path("devise-1.0")}" + G + bundle! :install, forgotten_command_line_options(:path => bundled_app("bundle")).merge(:standalone => true) + end + + let(:expected_gems) do + { + "actionpack" => "2.3.2", + "devise" => "1.0", + "rails" => "2.3.2", + } + end + + include_examples "common functionality" + end + + describe "with groups" do + before do + build_git "devise", "1.0" + + gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + + group :test do + gem "rspec" + gem "rack-test" + end + G + bundle! :install, forgotten_command_line_options(:path => bundled_app("bundle")).merge(:standalone => true) + end + + let(:expected_gems) do + { + "actionpack" => "2.3.2", + "rails" => "2.3.2", + } + end + + include_examples "common functionality" + + it "allows creating a standalone file with limited groups" do + bundle! :install, forgotten_command_line_options(:path => bundled_app("bundle")).merge(:standalone => "default") + + Dir.chdir(bundled_app) do + load_error_ruby <<-RUBY, "spec", :no_lib => true + $:.unshift File.expand_path("bundle") + require "bundler/setup" + + require "actionpack" + puts ACTIONPACK + require "spec" + RUBY + end + + expect(last_command.stdout).to eq("2.3.2") + expect(last_command.stderr).to eq("ZOMG LOAD ERROR") + end + + it "allows --without to limit the groups used in a standalone" do + bundle! :install, forgotten_command_line_options(:path => bundled_app("bundle"), :without => "test").merge(:standalone => true) + + Dir.chdir(bundled_app) do + load_error_ruby <<-RUBY, "spec", :no_lib => true + $:.unshift File.expand_path("bundle") + require "bundler/setup" + + require "actionpack" + puts ACTIONPACK + require "spec" + RUBY + end + + expect(last_command.stdout).to eq("2.3.2") + expect(last_command.stderr).to eq("ZOMG LOAD ERROR") + end + + it "allows --path to change the location of the standalone bundle", :bundler => "< 2" do + bundle! "install", forgotten_command_line_options(:path => "path/to/bundle").merge(:standalone => true) + + Dir.chdir(bundled_app) do + ruby <<-RUBY, :no_lib => true + $:.unshift File.expand_path("path/to/bundle") + require "bundler/setup" + + require "actionpack" + puts ACTIONPACK + RUBY + end + + expect(last_command.stdout).to eq("2.3.2") + end + + it "allows --path to change the location of the standalone bundle", :bundler => "2" do + bundle! "install", forgotten_command_line_options(:path => "path/to/bundle").merge(:standalone => true) + path = File.expand_path("path/to/bundle") + + Dir.chdir(bundled_app) do + ruby <<-RUBY, :no_lib => true + $:.unshift File.expand_path(#{path.dump}) + require "bundler/setup" + + require "actionpack" + puts ACTIONPACK + RUBY + end + + expect(last_command.stdout).to eq("2.3.2") + end + + it "allows remembered --without to limit the groups used in a standalone" do + bundle! :install, forgotten_command_line_options(:without => "test") + bundle! :install, forgotten_command_line_options(:path => bundled_app("bundle")).merge(:standalone => true) + + Dir.chdir(bundled_app) do + load_error_ruby <<-RUBY, "spec", :no_lib => true + $:.unshift File.expand_path("bundle") + require "bundler/setup" + + require "actionpack" + puts ACTIONPACK + require "spec" + RUBY + end + + expect(last_command.stdout).to eq("2.3.2") + expect(last_command.stderr).to eq("ZOMG LOAD ERROR") + end + end + + describe "with gemcutter's dependency API" do + let(:source_uri) { "http://localgemserver.test" } + + describe "simple gems" do + before do + gemfile <<-G + source "#{source_uri}" + gem "rails" + G + bundle! :install, forgotten_command_line_options(:path => bundled_app("bundle")).merge(:standalone => true, :artifice => "endpoint") + end + + let(:expected_gems) do + { + "actionpack" => "2.3.2", + "rails" => "2.3.2", + } + end + + include_examples "common functionality" + end + end + + describe "with --binstubs", :bundler => "< 2" do + before do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + G + bundle! :install, forgotten_command_line_options(:path => bundled_app("bundle")).merge(:standalone => true, :binstubs => true) + end + + let(:expected_gems) do + { + "actionpack" => "2.3.2", + "rails" => "2.3.2", + } + end + + include_examples "common functionality" + + it "creates stubs that use the standalone load path" do + Dir.chdir(bundled_app) do + expect(`bin/rails -v`.chomp).to eql "2.3.2" + end + end + + it "creates stubs that can be executed from anywhere" do + require "tmpdir" + Dir.chdir(Dir.tmpdir) do + sys_exec!(%(#{bundled_app("bin/rails")} -v)) + expect(out).to eq("2.3.2") + end + end + + it "creates stubs that can be symlinked" do + pending "File.symlink is unsupported on Windows" if Bundler::WINDOWS + + symlink_dir = tmp("symlink") + FileUtils.mkdir_p(symlink_dir) + symlink = File.join(symlink_dir, "rails") + + File.symlink(bundled_app("bin/rails"), symlink) + sys_exec!("#{symlink} -v") + expect(out).to eq("2.3.2") + end + + it "creates stubs with the correct load path" do + extension_line = File.read(bundled_app("bin/rails")).each_line.find {|line| line.include? "$:.unshift" }.strip + expect(extension_line).to eq %($:.unshift File.expand_path "../../bundle", path.realpath) + end + end +end + +RSpec.describe "bundle install --standalone" do + include_examples("bundle install --standalone") +end + +RSpec.describe "bundle install --standalone run in a subdirectory" do + before do + Dir.chdir(bundled_app("bob").tap(&:mkpath)) + end + + include_examples("bundle install --standalone") +end diff --git a/spec/bundler/install/gems/sudo_spec.rb b/spec/bundler/install/gems/sudo_spec.rb new file mode 100644 index 0000000000..1781451c98 --- /dev/null +++ b/spec/bundler/install/gems/sudo_spec.rb @@ -0,0 +1,178 @@ +# frozen_string_literal: true + +RSpec.describe "when using sudo", :sudo => true do + describe "and BUNDLE_PATH is writable" do + context "but BUNDLE_PATH/build_info is not writable" do + before do + bundle! "config path.system true" + subdir = system_gem_path("cache") + subdir.mkpath + sudo "chmod u-w #{subdir}" + end + + it "installs" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + expect(out).to_not match(/an error occurred/i) + expect(system_gem_path("cache/rack-1.0.0.gem")).to exist + expect(the_bundle).to include_gems "rack 1.0" + end + end + end + + describe "and GEM_HOME is owned by root" do + before :each do + bundle! "config path.system true" + chown_system_gems_to_root + end + + it "installs" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", '1.0' + gem "thin" + G + + expect(system_gem_path("gems/rack-1.0.0")).to exist + expect(system_gem_path("gems/rack-1.0.0").stat.uid).to eq(0) + expect(the_bundle).to include_gems "rack 1.0" + end + + it "installs rake and a gem dependent on rake in the same session" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rake" + gem "another_implicit_rake_dep" + G + bundle "install" + expect(system_gem_path("gems/another_implicit_rake_dep-1.0")).to exist + end + + it "installs when BUNDLE_PATH is owned by root" do + bundle! "config global_path_appends_ruby_scope false" # consistency in tests between 1.x and 2.x modes + + bundle_path = tmp("owned_by_root") + FileUtils.mkdir_p bundle_path + sudo "chown -R root #{bundle_path}" + + ENV["BUNDLE_PATH"] = bundle_path.to_s + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", '1.0' + G + + expect(bundle_path.join("gems/rack-1.0.0")).to exist + expect(bundle_path.join("gems/rack-1.0.0").stat.uid).to eq(0) + expect(the_bundle).to include_gems "rack 1.0" + end + + it "installs when BUNDLE_PATH does not exist" do + bundle! "config global_path_appends_ruby_scope false" # consistency in tests between 1.x and 2.x modes + + root_path = tmp("owned_by_root") + FileUtils.mkdir_p root_path + sudo "chown -R root #{root_path}" + bundle_path = root_path.join("does_not_exist") + + ENV["BUNDLE_PATH"] = bundle_path.to_s + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", '1.0' + G + + expect(bundle_path.join("gems/rack-1.0.0")).to exist + expect(bundle_path.join("gems/rack-1.0.0").stat.uid).to eq(0) + expect(the_bundle).to include_gems "rack 1.0" + end + + it "installs extensions/ compiled by RubyGems 2.2", :rubygems => "2.2" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "very_simple_binary" + G + + expect(system_gem_path("gems/very_simple_binary-1.0")).to exist + binary_glob = system_gem_path("extensions/*/*/very_simple_binary-1.0") + expect(Dir.glob(binary_glob).first).to be + end + end + + describe "and BUNDLE_PATH is not writable" do + before do + sudo "chmod ugo-w #{default_bundle_path}" + end + + it "installs" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", '1.0' + G + + expect(default_bundle_path("gems/rack-1.0.0")).to exist + expect(the_bundle).to include_gems "rack 1.0" + end + + it "cleans up the tmpdirs generated" do + require "tmpdir" + Dir.glob("#{Dir.tmpdir}/bundler*").each do |tmpdir| + FileUtils.remove_entry_secure(tmpdir) + end + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + tmpdirs = Dir.glob("#{Dir.tmpdir}/bundler*") + + expect(tmpdirs).to be_empty + end + end + + describe "and GEM_HOME is not writable" do + it "installs" do + bundle! "config path.system true" + gem_home = tmp("sudo_gem_home") + sudo "mkdir -p #{gem_home}" + sudo "chmod ugo-w #{gem_home}" + + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", '1.0' + G + + bundle :install, :env => { "GEM_HOME" => gem_home.to_s, "GEM_PATH" => nil } + expect(gem_home.join("bin/rackup")).to exist + expect(the_bundle).to include_gems "rack 1.0", :env => { "GEM_HOME" => gem_home.to_s, "GEM_PATH" => nil } + end + end + + describe "and root runs install" do + let(:warning) { "Don't run Bundler as root." } + + before do + gemfile %(source "file://#{gem_repo1}") + end + + it "warns against that" do + bundle :install, :sudo => true + expect(out).to include(warning) + end + + context "when ENV['BUNDLE_SILENCE_ROOT_WARNING'] is set" do + it "skips the warning" do + bundle :install, :sudo => :preserve_env, :env => { "BUNDLE_SILENCE_ROOT_WARNING" => true } + expect(out).to_not include(warning) + end + end + + context "when silence_root_warning = false" do + it "warns against that" do + bundle :install, :sudo => true, :env => { "BUNDLE_SILENCE_ROOT_WARNING" => "false" } + expect(out).to include(warning) + end + end + end +end diff --git a/spec/bundler/install/gems/win32_spec.rb b/spec/bundler/install/gems/win32_spec.rb new file mode 100644 index 0000000000..ad758b94fa --- /dev/null +++ b/spec/bundler/install/gems/win32_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install with win32-generated lockfile" do + it "should read lockfile" do + File.open(bundled_app("Gemfile.lock"), "wb") do |f| + f << "GEM\r\n" + f << " remote: file:#{gem_repo1}/\r\n" + f << " specs:\r\n" + f << "\r\n" + f << " rack (1.0.0)\r\n" + f << "\r\n" + f << "PLATFORMS\r\n" + f << " ruby\r\n" + f << "\r\n" + f << "DEPENDENCIES\r\n" + f << " rack\r\n" + end + + install_gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack" + G + expect(exitstatus).to eq(0) if exitstatus + end +end diff --git a/spec/bundler/install/gemspecs_spec.rb b/spec/bundler/install/gemspecs_spec.rb new file mode 100644 index 0000000000..666707b295 --- /dev/null +++ b/spec/bundler/install/gemspecs_spec.rb @@ -0,0 +1,154 @@ +# encoding: utf-8 +# frozen_string_literal: true + +RSpec.describe "bundle install" do + describe "when a gem has a YAML gemspec" do + before :each do + build_repo2 do + build_gem "yaml_spec", :gemspec => :yaml + end + end + + it "still installs correctly" do + gemfile <<-G + source "file://#{gem_repo2}" + gem "yaml_spec" + G + bundle :install + expect(err).to lack_errors + end + + it "still installs correctly when using path" do + build_lib "yaml_spec", :gemspec => :yaml + + install_gemfile <<-G + gem 'yaml_spec', :path => "#{lib_path("yaml_spec-1.0")}" + G + expect(err).to lack_errors + end + end + + it "should use gemspecs in the system cache when available" do + gemfile <<-G + source "http://localtestserver.gem" + gem 'rack' + G + + FileUtils.mkdir_p "#{default_bundle_path}/specifications" + File.open("#{default_bundle_path}/specifications/rack-1.0.0.gemspec", "w+") do |f| + spec = Gem::Specification.new do |s| + s.name = "rack" + s.version = "1.0.0" + s.add_runtime_dependency "activesupport", "2.3.2" + end + f.write spec.to_ruby + end + bundle :install, :artifice => "endpoint_marshal_fail" # force gemspec load + expect(the_bundle).to include_gems "activesupport 2.3.2" + end + + it "does not hang when gemspec has incompatible encoding" do + create_file "foo.gemspec", <<-G + Gem::Specification.new do |gem| + gem.name = "pry-byebug" + gem.version = "3.4.2" + gem.author = "David Rodriguez" + gem.summary = "Good stuff" + end + G + + install_gemfile <<-G, :env => { "LANG" => "C" } + gemspec + G + + expect(out).to include("Bundle complete!") + end + + it "reads gemspecs respecting their encoding" do + skip "Unicode is not supported on Ruby 1.x without extra work" if RUBY_VERSION < "2.0" + + create_file "version.rb", <<-RUBY + module Persistent💎 + VERSION = "0.0.1" + end + RUBY + + create_file "persistent-dmnd.gemspec", <<-G + require_relative "version" + + Gem::Specification.new do |gem| + gem.name = "persistent-dmnd" + gem.version = Persistent💎::VERSION + gem.author = "Ivo Anjo" + gem.summary = "Unscratchable stuff" + end + G + + install_gemfile <<-G + gemspec + G + + expect(out).to include("Bundle complete!") + end + + context "when ruby version is specified in gemspec and gemfile" do + it "installs when patch level is not specified and the version matches" do + build_lib("foo", :path => bundled_app) do |s| + s.required_ruby_version = "~> #{RUBY_VERSION}.0" + end + + install_gemfile <<-G + ruby '#{RUBY_VERSION}', :engine_version => '#{RUBY_VERSION}', :engine => 'ruby' + gemspec + G + expect(the_bundle).to include_gems "foo 1.0" + end + + it "installs when patch level is specified and the version still matches the current version", + :if => RUBY_PATCHLEVEL >= 0 do + build_lib("foo", :path => bundled_app) do |s| + s.required_ruby_version = "#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}" + end + + install_gemfile <<-G + ruby '#{RUBY_VERSION}', :engine_version => '#{RUBY_VERSION}', :engine => 'ruby', :patchlevel => '#{RUBY_PATCHLEVEL}' + gemspec + G + expect(the_bundle).to include_gems "foo 1.0" + end + + it "fails and complains about patchlevel on patchlevel mismatch", + :if => RUBY_PATCHLEVEL >= 0 do + patchlevel = RUBY_PATCHLEVEL.to_i + 1 + build_lib("foo", :path => bundled_app) do |s| + s.required_ruby_version = "#{RUBY_VERSION}.#{patchlevel}" + end + + install_gemfile <<-G + ruby '#{RUBY_VERSION}', :engine_version => '#{RUBY_VERSION}', :engine => 'ruby', :patchlevel => '#{patchlevel}' + gemspec + G + + expect(out).to include("Ruby patchlevel") + expect(out).to include("but your Gemfile specified") + expect(exitstatus).to eq(18) if exitstatus + end + + it "fails and complains about version on version mismatch" do + version = Gem::Requirement.create(RUBY_VERSION).requirements.first.last.bump.version + + build_lib("foo", :path => bundled_app) do |s| + s.required_ruby_version = version + end + + install_gemfile <<-G + ruby '#{version}', :engine_version => '#{version}', :engine => 'ruby' + gemspec + G + + expect(out).to include("Ruby version") + expect(out).to include("but your Gemfile specified") + expect(exitstatus).to eq(18) if exitstatus + end + end +end diff --git a/spec/bundler/install/git_spec.rb b/spec/bundler/install/git_spec.rb new file mode 100644 index 0000000000..6ae718c2a4 --- /dev/null +++ b/spec/bundler/install/git_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install" do + context "git sources" do + it "displays the revision hash of the gem repository", :bundler => "< 2" do + build_git "foo", "1.0", :path => lib_path("foo") + + install_gemfile <<-G + gem "foo", :git => "#{lib_path("foo")}" + G + + bundle! :install + expect(out).to include("Using foo 1.0 from #{lib_path("foo")} (at master@#{revision_for(lib_path("foo"))[0..6]})") + expect(the_bundle).to include_gems "foo 1.0", :source => "git@#{lib_path("foo")}" + end + + it "displays the ref of the gem repository when using branch~num as a ref", :bundler => "< 2" do + build_git "foo", "1.0", :path => lib_path("foo") + rev = revision_for(lib_path("foo"))[0..6] + update_git "foo", "2.0", :path => lib_path("foo"), :gemspec => true + rev2 = revision_for(lib_path("foo"))[0..6] + update_git "foo", "3.0", :path => lib_path("foo"), :gemspec => true + + install_gemfile! <<-G + gem "foo", :git => "#{lib_path("foo")}", :ref => "master~2" + G + + bundle! :install + expect(out).to include("Using foo 1.0 from #{lib_path("foo")} (at master~2@#{rev})") + expect(the_bundle).to include_gems "foo 1.0", :source => "git@#{lib_path("foo")}" + + update_git "foo", "4.0", :path => lib_path("foo"), :gemspec => true + + bundle! :update, :all => bundle_update_requires_all? + expect(out).to include("Using foo 2.0 (was 1.0) from #{lib_path("foo")} (at master~2@#{rev2})") + expect(the_bundle).to include_gems "foo 2.0", :source => "git@#{lib_path("foo")}" + end + + it "should allows git repos that are missing but not being installed" do + revision = build_git("foo").ref_for("HEAD") + + gemfile <<-G + gem "foo", :git => "file://#{lib_path("foo-1.0")}", :group => :development + G + + lockfile <<-L + GIT + remote: file://#{lib_path("foo-1.0")} + revision: #{revision} + specs: + foo (1.0) + + PLATFORMS + ruby + + DEPENDENCIES + foo! + L + + bundle! :install, forgotten_command_line_options(:path => "vendor/bundle", :without => "development") + + expect(out).to include("Bundle complete!") + end + end +end diff --git a/spec/bundler/install/global_cache_spec.rb b/spec/bundler/install/global_cache_spec.rb new file mode 100644 index 0000000000..e41e7e0157 --- /dev/null +++ b/spec/bundler/install/global_cache_spec.rb @@ -0,0 +1,235 @@ +# frozen_string_literal: true + +RSpec.describe "global gem caching" do + before { bundle! "config global_gem_cache true" } + + describe "using the cross-application user cache" do + let(:source) { "http://localgemserver.test" } + let(:source2) { "http://gemserver.example.org" } + + def source_global_cache(*segments) + home(".bundle", "cache", "gems", "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", *segments) + end + + def source2_global_cache(*segments) + home(".bundle", "cache", "gems", "gemserver.example.org.80.1ae1663619ffe0a3c9d97712f44c705b", *segments) + end + + it "caches gems into the global cache on download" do + install_gemfile! <<-G, :artifice => "compact_index" + source "#{source}" + gem "rack" + G + + expect(the_bundle).to include_gems "rack 1.0.0" + expect(source_global_cache("rack-1.0.0.gem")).to exist + end + + it "uses globally cached gems if they exist" do + source_global_cache.mkpath + FileUtils.cp(gem_repo1("gems/rack-1.0.0.gem"), source_global_cache("rack-1.0.0.gem")) + + install_gemfile! <<-G, :artifice => "compact_index_no_gem" + source "#{source}" + gem "rack" + G + + expect(the_bundle).to include_gems "rack 1.0.0" + end + + describe "when the same gem from different sources is installed" do + it "should use the appropriate one from the global cache" do + install_gemfile! <<-G, :artifice => "compact_index" + source "#{source}" + gem "rack" + G + + FileUtils.rm_r(default_bundle_path) + expect(the_bundle).not_to include_gems "rack 1.0.0" + expect(source_global_cache("rack-1.0.0.gem")).to exist + # rack 1.0.0 is not installed and it is in the global cache + + install_gemfile! <<-G, :artifice => "compact_index" + source "#{source2}" + gem "rack", "0.9.1" + G + + FileUtils.rm_r(default_bundle_path) + expect(the_bundle).not_to include_gems "rack 0.9.1" + expect(source2_global_cache("rack-0.9.1.gem")).to exist + # rack 0.9.1 is not installed and it is in the global cache + + gemfile <<-G + source "#{source}" + gem "rack", "1.0.0" + G + + bundle! :install, :artifice => "compact_index_no_gem" + # rack 1.0.0 is installed and rack 0.9.1 is not + expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).not_to include_gems "rack 0.9.1" + FileUtils.rm_r(default_bundle_path) + + gemfile <<-G + source "#{source2}" + gem "rack", "0.9.1" + G + + bundle! :install, :artifice => "compact_index_no_gem" + # rack 0.9.1 is installed and rack 1.0.0 is not + expect(the_bundle).to include_gems "rack 0.9.1" + expect(the_bundle).not_to include_gems "rack 1.0.0" + end + + it "should not install if the wrong source is provided" do + gemfile <<-G + source "#{source}" + gem "rack" + G + + bundle! :install, :artifice => "compact_index" + FileUtils.rm_r(default_bundle_path) + expect(the_bundle).not_to include_gems "rack 1.0.0" + expect(source_global_cache("rack-1.0.0.gem")).to exist + # rack 1.0.0 is not installed and it is in the global cache + + gemfile <<-G + source "#{source2}" + gem "rack", "0.9.1" + G + + bundle! :install, :artifice => "compact_index" + FileUtils.rm_r(default_bundle_path) + expect(the_bundle).not_to include_gems "rack 0.9.1" + expect(source2_global_cache("rack-0.9.1.gem")).to exist + # rack 0.9.1 is not installed and it is in the global cache + + gemfile <<-G + source "#{source2}" + gem "rack", "1.0.0" + G + + expect(source_global_cache("rack-1.0.0.gem")).to exist + expect(source2_global_cache("rack-0.9.1.gem")).to exist + bundle :install, :artifice => "compact_index_no_gem" + expect(out).to include("Internal Server Error 500") + # rack 1.0.0 is not installed and rack 0.9.1 is not + expect(the_bundle).not_to include_gems "rack 1.0.0" + expect(the_bundle).not_to include_gems "rack 0.9.1" + + gemfile <<-G + source "#{source}" + gem "rack", "0.9.1" + G + + expect(source_global_cache("rack-1.0.0.gem")).to exist + expect(source2_global_cache("rack-0.9.1.gem")).to exist + bundle :install, :artifice => "compact_index_no_gem" + expect(out).to include("Internal Server Error 500") + # rack 0.9.1 is not installed and rack 1.0.0 is not + expect(the_bundle).not_to include_gems "rack 0.9.1" + expect(the_bundle).not_to include_gems "rack 1.0.0" + end + end + + describe "when installing gems from a different directory" do + it "uses the global cache as a source" do + install_gemfile! <<-G, :artifice => "compact_index" + source "#{source}" + gem "rack" + gem "activesupport" + G + + # Both gems are installed and in the global cache + expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "activesupport 2.3.5" + expect(source_global_cache("rack-1.0.0.gem")).to exist + expect(source_global_cache("activesupport-2.3.5.gem")).to exist + FileUtils.rm_r(default_bundle_path) + # Both gems are now only in the global cache + expect(the_bundle).not_to include_gems "rack 1.0.0" + expect(the_bundle).not_to include_gems "activesupport 2.3.5" + + install_gemfile! <<-G, :artifice => "compact_index_no_gem" + source "#{source}" + gem "rack" + G + + # rack is installed and both are in the global cache + expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).not_to include_gems "activesupport 2.3.5" + expect(source_global_cache("rack-1.0.0.gem")).to exist + expect(source_global_cache("activesupport-2.3.5.gem")).to exist + + Dir.chdir bundled_app2 do + create_file bundled_app2("gems.rb"), <<-G + source "#{source}" + gem "activesupport" + G + + # Neither gem is installed and both are in the global cache + expect(the_bundle).not_to include_gems "rack 1.0.0" + expect(the_bundle).not_to include_gems "activesupport 2.3.5" + expect(source_global_cache("rack-1.0.0.gem")).to exist + expect(source_global_cache("activesupport-2.3.5.gem")).to exist + + # Install using the global cache instead of by downloading the .gem + # from the server + bundle! :install, :artifice => "compact_index_no_gem" + + # activesupport is installed and both are in the global cache + expect(the_bundle).not_to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "activesupport 2.3.5" + expect(source_global_cache("rack-1.0.0.gem")).to exist + expect(source_global_cache("activesupport-2.3.5.gem")).to exist + end + end + end + end + + describe "extension caching", :ruby_repo, :rubygems => "2.2" do + it "works" do + build_git "very_simple_git_binary", &:add_c_extension + build_lib "very_simple_path_binary", &:add_c_extension + revision = revision_for(lib_path("very_simple_git_binary-1.0"))[0, 12] + + install_gemfile! <<-G + source "file:#{gem_repo1}" + + gem "very_simple_binary" + gem "very_simple_git_binary", :git => "#{lib_path("very_simple_git_binary-1.0")}" + gem "very_simple_path_binary", :path => "#{lib_path("very_simple_path_binary-1.0")}" + G + + gem_binary_cache = home(".bundle", "cache", "extensions", specific_local_platform.to_s, Bundler.ruby_scope, + Digest(:MD5).hexdigest("#{gem_repo1}/"), "very_simple_binary-1.0") + git_binary_cache = home(".bundle", "cache", "extensions", specific_local_platform.to_s, Bundler.ruby_scope, + "very_simple_git_binary-1.0-#{revision}", "very_simple_git_binary-1.0") + + cached_extensions = Pathname.glob(home(".bundle", "cache", "extensions", "*", "*", "*", "*", "*")).sort + expect(cached_extensions).to eq [gem_binary_cache, git_binary_cache].sort + + run! <<-R + require 'very_simple_binary_c'; puts ::VERY_SIMPLE_BINARY_IN_C + require 'very_simple_git_binary_c'; puts ::VERY_SIMPLE_GIT_BINARY_IN_C + R + expect(out).to eq "VERY_SIMPLE_BINARY_IN_C\nVERY_SIMPLE_GIT_BINARY_IN_C" + + FileUtils.rm Dir[home(".bundle", "cache", "extensions", "**", "*binary_c*")] + + gem_binary_cache.join("very_simple_binary_c.rb").open("w") {|f| f << "puts File.basename(__FILE__)" } + git_binary_cache.join("very_simple_git_binary_c.rb").open("w") {|f| f << "puts File.basename(__FILE__)" } + + bundle! "config --local path different_path" + bundle! :install + + expect(Dir[home(".bundle", "cache", "extensions", "**", "*binary_c*")]).to all(end_with(".rb")) + + run! <<-R + require 'very_simple_binary_c' + require 'very_simple_git_binary_c' + R + expect(out).to eq "very_simple_binary_c.rb\nvery_simple_git_binary_c.rb" + end + end +end diff --git a/spec/bundler/install/path_spec.rb b/spec/bundler/install/path_spec.rb new file mode 100644 index 0000000000..44439c275e --- /dev/null +++ b/spec/bundler/install/path_spec.rb @@ -0,0 +1,256 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install" do + describe "with --path" do + before :each do + build_gem "rack", "1.0.0", :to_system => true do |s| + s.write "lib/rack.rb", "puts 'FAIL'" + end + + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + end + + it "does not use available system gems with bundle --path vendor/bundle", :bundler => "< 2" do + bundle! :install, forgotten_command_line_options(:path => "vendor/bundle") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "handles paths with regex characters in them" do + dir = bundled_app("bun++dle") + dir.mkpath + + Dir.chdir(dir) do + bundle! :install, forgotten_command_line_options(:path => dir.join("vendor/bundle")) + expect(out).to include("installed into `./vendor/bundle`") + end + + dir.rmtree + end + + it "prints a warning to let the user know what has happened with bundle --path vendor/bundle" do + bundle! :install, forgotten_command_line_options(:path => "vendor/bundle") + expect(out).to include("gems are installed into `./vendor/bundle`") + end + + it "disallows --path vendor/bundle --system", :bundler => "< 2" do + bundle "install --path vendor/bundle --system" + expect(out).to include("Please choose only one option.") + expect(exitstatus).to eq(15) if exitstatus + end + + it "remembers to disable system gems after the first time with bundle --path vendor/bundle", :bundler => "< 2" do + bundle "install --path vendor/bundle" + FileUtils.rm_rf bundled_app("vendor") + bundle "install" + + expect(vendored_gems("gems/rack-1.0.0")).to be_directory + expect(the_bundle).to include_gems "rack 1.0.0" + end + + context "with path_relative_to_cwd set to true" do + before { bundle! "config path_relative_to_cwd true" } + + it "installs the bundle relatively to current working directory", :bundler => "< 2" do + Dir.chdir(bundled_app.parent) do + bundle! "install --gemfile='#{bundled_app}/Gemfile' --path vendor/bundle" + expect(out).to include("installed into `./vendor/bundle`") + expect(bundled_app("../vendor/bundle")).to be_directory + end + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "installs the standalone bundle relative to the cwd" do + Dir.chdir(bundled_app.parent) do + bundle! :install, :gemfile => bundled_app("Gemfile"), :standalone => true + expect(out).to include("installed into `./bundled_app/bundle`") + expect(bundled_app("bundle")).to be_directory + expect(bundled_app("bundle/ruby")).to be_directory + end + + bundle! "config unset path" + + Dir.chdir(bundled_app("subdir").tap(&:mkpath)) do + bundle! :install, :gemfile => bundled_app("Gemfile"), :standalone => true + expect(out).to include("installed into `../bundle`") + expect(bundled_app("bundle")).to be_directory + expect(bundled_app("bundle/ruby")).to be_directory + end + end + end + end + + describe "when BUNDLE_PATH or the global path config is set" do + before :each do + build_lib "rack", "1.0.0", :to_system => true do |s| + s.write "lib/rack.rb", "raise 'FAIL'" + end + + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + end + + def set_bundle_path(type, location) + if type == :env + ENV["BUNDLE_PATH"] = location + elsif type == :global + bundle! "config path #{location}", "no-color" => nil + end + end + + [:env, :global].each do |type| + context "when set via #{type}" do + it "installs gems to a path if one is specified" do + set_bundle_path(type, bundled_app("vendor2").to_s) + bundle! :install, forgotten_command_line_options(:path => "vendor/bundle") + + expect(vendored_gems("gems/rack-1.0.0")).to be_directory + expect(bundled_app("vendor2")).not_to be_directory + expect(the_bundle).to include_gems "rack 1.0.0" + end + + context "with global_path_appends_ruby_scope set", :bundler => "2" do + it "installs gems to ." do + set_bundle_path(type, ".") + bundle! "config --global disable_shared_gems true" + + bundle! :install + + paths_to_exist = %w[cache/rack-1.0.0.gem gems/rack-1.0.0 specifications/rack-1.0.0.gemspec].map {|path| bundled_app(Bundler.ruby_scope, path) } + expect(paths_to_exist).to all exist + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "installs gems to the path" do + set_bundle_path(type, bundled_app("vendor").to_s) + + bundle! :install + + expect(bundled_app("vendor", Bundler.ruby_scope, "gems/rack-1.0.0")).to be_directory + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "installs gems to the path relative to root when relative" do + set_bundle_path(type, "vendor") + + FileUtils.mkdir_p bundled_app("lol") + Dir.chdir(bundled_app("lol")) do + bundle! :install + end + + expect(bundled_app("vendor", Bundler.ruby_scope, "gems/rack-1.0.0")).to be_directory + expect(the_bundle).to include_gems "rack 1.0.0" + end + end + + context "with global_path_appends_ruby_scope unset", :bundler => "< 2" do + it "installs gems to ." do + set_bundle_path(type, ".") + bundle! "config --global disable_shared_gems true" + + bundle! :install + + expect([bundled_app("cache/rack-1.0.0.gem"), bundled_app("gems/rack-1.0.0"), bundled_app("specifications/rack-1.0.0.gemspec")]).to all exist + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "installs gems to BUNDLE_PATH with #{type}" do + set_bundle_path(type, bundled_app("vendor").to_s) + + bundle :install + + expect(bundled_app("vendor/gems/rack-1.0.0")).to be_directory + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "installs gems to BUNDLE_PATH relative to root when relative" do + set_bundle_path(type, "vendor") + + FileUtils.mkdir_p bundled_app("lol") + Dir.chdir(bundled_app("lol")) do + bundle :install + end + + expect(bundled_app("vendor/gems/rack-1.0.0")).to be_directory + expect(the_bundle).to include_gems "rack 1.0.0" + end + end + end + end + + it "installs gems to BUNDLE_PATH from .bundle/config" do + config "BUNDLE_PATH" => bundled_app("vendor/bundle").to_s + + bundle :install + + expect(vendored_gems("gems/rack-1.0.0")).to be_directory + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "sets BUNDLE_PATH as the first argument to bundle install" do + bundle! :install, forgotten_command_line_options(:path => "./vendor/bundle") + + expect(vendored_gems("gems/rack-1.0.0")).to be_directory + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "disables system gems when passing a path to install" do + # This is so that vendored gems can be distributed to others + build_gem "rack", "1.1.0", :to_system => true + bundle! :install, forgotten_command_line_options(:path => "./vendor/bundle") + + expect(vendored_gems("gems/rack-1.0.0")).to be_directory + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "re-installs gems whose extensions have been deleted", :ruby_repo, :rubygems => ">= 2.3" do + build_lib "very_simple_binary", "1.0.0", :to_system => true do |s| + s.write "lib/very_simple_binary.rb", "raise 'FAIL'" + end + + gemfile <<-G + source "file://#{gem_repo1}" + gem "very_simple_binary" + G + + bundle! :install, forgotten_command_line_options(:path => "./vendor/bundle") + + expect(vendored_gems("gems/very_simple_binary-1.0")).to be_directory + expect(vendored_gems("extensions")).to be_directory + expect(the_bundle).to include_gems "very_simple_binary 1.0", :source => "remote1" + + vendored_gems("extensions").rmtree + + run "require 'very_simple_binary_c'" + expect(err).to include("Bundler::GemNotFound") + + bundle :install, forgotten_command_line_options(:path => "./vendor/bundle") + + expect(vendored_gems("gems/very_simple_binary-1.0")).to be_directory + expect(vendored_gems("extensions")).to be_directory + expect(the_bundle).to include_gems "very_simple_binary 1.0", :source => "remote1" + end + end + + describe "to a file" do + before do + in_app_root do + FileUtils.touch "bundle" + end + end + + it "reports the file exists" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + bundle :install, forgotten_command_line_options(:path => "bundle") + expect(out).to include("file already exists") + end + end +end diff --git a/spec/bundler/install/post_bundle_message_spec.rb b/spec/bundler/install/post_bundle_message_spec.rb new file mode 100644 index 0000000000..eadc8a4d85 --- /dev/null +++ b/spec/bundler/install/post_bundle_message_spec.rb @@ -0,0 +1,206 @@ +# frozen_string_literal: true + +RSpec.describe "post bundle message" do + before :each do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "activesupport", "2.3.5", :group => [:emo, :test] + group :test do + gem "rspec" + end + gem "rack-obama", :group => :obama + G + end + + let(:bundle_path) { "./.bundle" } + let(:bundle_show_system_message) { "Use `bundle info [gemname]` to see where a bundled gem is installed." } + let(:bundle_show_path_message) { "Bundled gems are installed into `#{bundle_path}`" } + let(:bundle_complete_message) { "Bundle complete!" } + let(:bundle_updated_message) { "Bundle updated!" } + let(:installed_gems_stats) { "4 Gemfile dependencies, 5 gems now installed." } + let(:bundle_show_message) { Bundler::VERSION.split(".").first.to_i < 2 ? bundle_show_system_message : bundle_show_path_message } + + describe "for fresh bundle install" do + it "without any options" do + bundle :install + expect(out).to include(bundle_show_message) + expect(out).not_to include("Gems in the group") + expect(out).to include(bundle_complete_message) + expect(out).to include(installed_gems_stats) + end + + it "with --without one group" do + bundle! :install, forgotten_command_line_options(:without => "emo") + expect(out).to include(bundle_show_message) + expect(out).to include("Gems in the group emo were not installed") + expect(out).to include(bundle_complete_message) + expect(out).to include(installed_gems_stats) + end + + it "with --without two groups" do + bundle! :install, forgotten_command_line_options(:without => "emo test") + expect(out).to include(bundle_show_message) + expect(out).to include("Gems in the groups emo and test were not installed") + expect(out).to include(bundle_complete_message) + expect(out).to include("4 Gemfile dependencies, 3 gems now installed.") + end + + it "with --without more groups" do + bundle! :install, forgotten_command_line_options(:without => "emo obama test") + expect(out).to include(bundle_show_message) + expect(out).to include("Gems in the groups emo, obama and test were not installed") + expect(out).to include(bundle_complete_message) + expect(out).to include("4 Gemfile dependencies, 2 gems now installed.") + end + + describe "with --path and" do + let(:bundle_path) { "./vendor" } + + it "without any options" do + bundle! :install, forgotten_command_line_options(:path => "vendor") + expect(out).to include(bundle_show_path_message) + expect(out).to_not include("Gems in the group") + expect(out).to include(bundle_complete_message) + end + + it "with --without one group" do + bundle! :install, forgotten_command_line_options(:without => "emo", :path => "vendor") + expect(out).to include(bundle_show_path_message) + expect(out).to include("Gems in the group emo were not installed") + expect(out).to include(bundle_complete_message) + end + + it "with --without two groups" do + bundle! :install, forgotten_command_line_options(:without => "emo test", :path => "vendor") + expect(out).to include(bundle_show_path_message) + expect(out).to include("Gems in the groups emo and test were not installed") + expect(out).to include(bundle_complete_message) + end + + it "with --without more groups" do + bundle! :install, forgotten_command_line_options(:without => "emo obama test", :path => "vendor") + expect(out).to include(bundle_show_path_message) + expect(out).to include("Gems in the groups emo, obama and test were not installed") + expect(out).to include(bundle_complete_message) + end + + it "with an absolute --path inside the cwd" do + bundle! :install, forgotten_command_line_options(:path => bundled_app("cache")) + expect(out).to include("Bundled gems are installed into `./cache`") + expect(out).to_not include("Gems in the group") + expect(out).to include(bundle_complete_message) + end + + it "with an absolute --path outside the cwd" do + bundle! :install, forgotten_command_line_options(:path => tmp("not_bundled_app")) + expect(out).to include("Bundled gems are installed into `#{tmp("not_bundled_app")}`") + expect(out).to_not include("Gems in the group") + expect(out).to include(bundle_complete_message) + end + end + + describe "with misspelled or non-existent gem name" do + it "should report a helpful error message", :bundler => "< 2" do + install_gemfile <<-G + source "file://localhost#{gem_repo1}" + gem "rack" + gem "not-a-gem", :group => :development + G + expect(out).to include("Could not find gem 'not-a-gem' in any of the gem sources listed in your Gemfile.") + end + + it "should report a helpful error message", :bundler => "2" do + install_gemfile <<-G + source "file://localhost#{gem_repo1}" + gem "rack" + gem "not-a-gem", :group => :development + G + expect(out).to include normalize_uri_file(<<-EOS.strip) +Could not find gem 'not-a-gem' in rubygems repository file://localhost#{gem_repo1}/ or installed locally. +The source does not contain any versions of 'not-a-gem' + EOS + end + + it "should report a helpful error message with reference to cache if available" do + install_gemfile <<-G + source "file://localhost#{gem_repo1}" + gem "rack" + G + bundle :cache + expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + install_gemfile <<-G + source "file://localhost#{gem_repo1}" + gem "rack" + gem "not-a-gem", :group => :development + G + expect(out).to include("Could not find gem 'not-a-gem' in"). + and include("or in gems cached in vendor/cache.") + end + end + end + + describe "for second bundle install run" do + it "without any options" do + 2.times { bundle :install } + expect(out).to include(bundle_show_message) + expect(out).to_not include("Gems in the groups") + expect(out).to include(bundle_complete_message) + expect(out).to include(installed_gems_stats) + end + + it "with --without one group" do + bundle! :install, forgotten_command_line_options(:without => "emo") + bundle! :install + expect(out).to include(bundle_show_message) + expect(out).to include("Gems in the group emo were not installed") + expect(out).to include(bundle_complete_message) + expect(out).to include(installed_gems_stats) + end + + it "with --without two groups" do + bundle! :install, forgotten_command_line_options(:without => "emo test") + bundle! :install + expect(out).to include(bundle_show_message) + expect(out).to include("Gems in the groups emo and test were not installed") + expect(out).to include(bundle_complete_message) + end + + it "with --without more groups" do + bundle! :install, forgotten_command_line_options(:without => "emo obama test") + bundle :install + expect(out).to include(bundle_show_message) + expect(out).to include("Gems in the groups emo, obama and test were not installed") + expect(out).to include(bundle_complete_message) + end + end + + describe "for bundle update" do + it "without any options" do + bundle! :update, :all => bundle_update_requires_all? + expect(out).not_to include("Gems in the groups") + expect(out).to include(bundle_updated_message) + end + + it "with --without one group" do + bundle! :install, forgotten_command_line_options(:without => "emo") + bundle! :update, :all => bundle_update_requires_all? + expect(out).to include("Gems in the group emo were not installed") + expect(out).to include(bundle_updated_message) + end + + it "with --without two groups" do + bundle! :install, forgotten_command_line_options(:without => "emo test") + bundle! :update, :all => bundle_update_requires_all? + expect(out).to include("Gems in the groups emo and test were not installed") + expect(out).to include(bundle_updated_message) + end + + it "with --without more groups" do + bundle! :install, forgotten_command_line_options(:without => "emo obama test") + bundle! :update, :all => bundle_update_requires_all? + expect(out).to include("Gems in the groups emo, obama and test were not installed") + expect(out).to include(bundle_updated_message) + end + end +end diff --git a/spec/bundler/install/prereleases_spec.rb b/spec/bundler/install/prereleases_spec.rb new file mode 100644 index 0000000000..7af8c3b304 --- /dev/null +++ b/spec/bundler/install/prereleases_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install" do + describe "when prerelease gems are available" do + it "finds prereleases" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "not_released" + G + expect(the_bundle).to include_gems "not_released 1.0.pre" + end + + it "uses regular releases if available" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "has_prerelease" + G + expect(the_bundle).to include_gems "has_prerelease 1.0" + end + + it "uses prereleases if requested" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "has_prerelease", "1.1.pre" + G + expect(the_bundle).to include_gems "has_prerelease 1.1.pre" + end + end + + describe "when prerelease gems are not available" do + it "still works" do + build_repo3 + install_gemfile <<-G + source "file://#{gem_repo3}" + gem "rack" + G + + expect(the_bundle).to include_gems "rack 1.0" + end + end +end diff --git a/spec/bundler/install/process_lock_spec.rb b/spec/bundler/install/process_lock_spec.rb new file mode 100644 index 0000000000..be8fd04fdd --- /dev/null +++ b/spec/bundler/install/process_lock_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +RSpec.describe "process lock spec" do + describe "when an install operation is already holding a process lock" do + before { FileUtils.mkdir_p(default_bundle_path) } + + it "will not run a second concurrent bundle install until the lock is released" do + thread = Thread.new do + Bundler::ProcessLock.lock(default_bundle_path) do + sleep 1 # ignore quality_spec + expect(the_bundle).not_to include_gems "rack 1.0" + end + end + + install_gemfile! <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + thread.join + expect(the_bundle).to include_gems "rack 1.0" + end + + context "when creating a lock raises Errno::ENOTSUP", :ruby => ">= 1.9" do + before { allow(File).to receive(:open).and_raise(Errno::ENOTSUP) } + + it "skips creating the lock file and yields" do + processed = false + Bundler::ProcessLock.lock(default_bundle_path) { processed = true } + + expect(processed).to eq true + end + end + end +end diff --git a/spec/bundler/install/redownload_spec.rb b/spec/bundler/install/redownload_spec.rb new file mode 100644 index 0000000000..665c64d49a --- /dev/null +++ b/spec/bundler/install/redownload_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install", :bundler => "< 2", :ruby => ">= 2.0" do + before :each do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + end + + before { bundle "config major_deprecations yes" } + + shared_examples_for "an option to force redownloading gems" do + it "re-installs installed gems" do + rack_lib = default_bundle_path("gems/rack-1.0.0/lib/rack.rb") + + bundle! :install + rack_lib.open("w") {|f| f.write("blah blah blah") } + bundle! :install, flag => true + + expect(out).to include "Installing rack 1.0.0" + expect(rack_lib.open(&:read)).to eq("RACK = '1.0.0'\n") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "works on first bundle install" do + bundle! :install, flag => true + + expect(out).to include "Installing rack 1.0.0" + expect(the_bundle).to include_gems "rack 1.0.0" + end + + context "with a git gem" do + let!(:ref) { build_git("foo", "1.0").ref_for("HEAD", 11) } + + before do + gemfile <<-G + gem "foo", :git => "#{lib_path("foo-1.0")}" + G + end + + it "re-installs installed gems" do + foo_lib = default_bundle_path("bundler/gems/foo-1.0-#{ref}/lib/foo.rb") + + bundle! :install + foo_lib.open("w") {|f| f.write("blah blah blah") } + bundle! :install, flag => true + + expect(foo_lib.open(&:read)).to eq("FOO = '1.0'\n") + expect(the_bundle).to include_gems "foo 1.0" + end + + it "works on first bundle install" do + bundle! :install, flag => true + + expect(the_bundle).to include_gems "foo 1.0" + end + end + end + + describe "with --force" do + it_behaves_like "an option to force redownloading gems" do + let(:flag) { "force" } + end + + it "shows a deprecation when single flag passed" do + bundle! "install --force" + expect(out).to include "[DEPRECATED FOR 2.0] The `--force` option has been renamed to `--redownload`" + end + + it "shows a deprecation when multiple flags passed" do + bundle! "install --no-color --force" + expect(out).to include "[DEPRECATED FOR 2.0] The `--force` option has been renamed to `--redownload`" + end + end + + describe "with --redownload" do + it_behaves_like "an option to force redownloading gems" do + let(:flag) { "redownload" } + end + + it "does not show a deprecation when single flag passed" do + bundle! "install --redownload" + expect(out).not_to include "[DEPRECATED FOR 2.0] The `--force` option has been renamed to `--redownload`" + end + + it "does not show a deprecation when single multiple flags passed" do + bundle! "install --no-color --redownload" + expect(out).not_to include "[DEPRECATED FOR 2.0] The `--force` option has been renamed to `--redownload`" + end + end +end diff --git a/spec/bundler/install/security_policy_spec.rb b/spec/bundler/install/security_policy_spec.rb new file mode 100644 index 0000000000..7be09d6bd4 --- /dev/null +++ b/spec/bundler/install/security_policy_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require "rubygems/security" + +# unfortunately, testing signed gems with a provided CA is extremely difficult +# as 'gem cert' is currently the only way to add CAs to the system. + +RSpec.describe "policies with unsigned gems" do + before do + build_security_repo + gemfile <<-G + source "file://#{security_repo}" + gem "rack" + gem "signed_gem" + G + end + + it "will work after you try to deploy without a lock" do + bundle "install --deployment" + bundle :install + expect(exitstatus).to eq(0) if exitstatus + expect(the_bundle).to include_gems "rack 1.0", "signed_gem 1.0" + end + + it "will fail when given invalid security policy" do + bundle "install --trust-policy=InvalidPolicyName" + expect(out).to include("RubyGems doesn't know about trust policy") + end + + it "will fail with High Security setting due to presence of unsigned gem" do + bundle "install --trust-policy=HighSecurity" + expect(out).to include("security policy didn't allow") + end + + # This spec will fail on RubyGems 2 rc1 due to a bug in policy.rb. the bug is fixed in rc3. + it "will fail with Medium Security setting due to presence of unsigned gem", :unless => ENV["RGV"] == "v2.0.0.rc.1" do + bundle "install --trust-policy=MediumSecurity" + expect(out).to include("security policy didn't allow") + end + + it "will succeed with no policy" do + bundle "install" + expect(exitstatus).to eq(0) if exitstatus + end +end + +RSpec.describe "policies with signed gems and no CA" do + before do + build_security_repo + gemfile <<-G + source "file://#{security_repo}" + gem "signed_gem" + G + end + + it "will fail with High Security setting, gem is self-signed" do + bundle "install --trust-policy=HighSecurity" + expect(out).to include("security policy didn't allow") + end + + it "will fail with Medium Security setting, gem is self-signed" do + bundle "install --trust-policy=MediumSecurity" + expect(out).to include("security policy didn't allow") + end + + it "will succeed with Low Security setting, low security accepts self signed gem" do + bundle "install --trust-policy=LowSecurity" + expect(exitstatus).to eq(0) if exitstatus + expect(the_bundle).to include_gems "signed_gem 1.0" + end + + it "will succeed with no policy" do + bundle "install" + expect(exitstatus).to eq(0) if exitstatus + expect(the_bundle).to include_gems "signed_gem 1.0" + end +end diff --git a/spec/bundler/install/yanked_spec.rb b/spec/bundler/install/yanked_spec.rb new file mode 100644 index 0000000000..7c4b98bfdf --- /dev/null +++ b/spec/bundler/install/yanked_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +RSpec.context "when installing a bundle that includes yanked gems" do + before(:each) do + build_repo4 do + build_gem "foo", "9.0.0" + end + end + + it "throws an error when the original gem version is yanked" do + lockfile <<-L + GEM + remote: file://#{gem_repo4} + specs: + foo (10.0.0) + + PLATFORMS + ruby + + DEPENDENCIES + foo (= 10.0.0) + + L + + install_gemfile <<-G + source "file://#{gem_repo4}" + gem "foo", "10.0.0" + G + + expect(out).to include("Your bundle is locked to foo (10.0.0)") + end + + it "throws the original error when only the Gemfile specifies a gem version that doesn't exist" do + install_gemfile <<-G + source "file://#{gem_repo4}" + gem "foo", "10.0.0" + G + + expect(out).not_to include("Your bundle is locked to foo (10.0.0)") + expect(out).to include("Could not find gem 'foo (= 10.0.0)' in") + end +end + +RSpec.context "when using gem before installing" do + it "does not suggest the author has yanked the gem" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", "0.9.1" + G + + lockfile <<-L + GEM + remote: file://#{gem_repo1} + specs: + rack (0.9.1) + + PLATFORMS + ruby + + DEPENDENCIES + rack (= 0.9.1) + L + + bundle :list + + expect(out).to include("Could not find rack-0.9.1 in any of the sources") + expect(out).to_not include("Your bundle is locked to rack (0.9.1), but that version could not be found in any of the sources listed in your Gemfile.") + expect(out).to_not include("If you haven't changed sources, that means the author of rack (0.9.1) has removed it.") + expect(out).to_not include("You'll need to update your bundle to a different version of rack (0.9.1) that hasn't been removed in order to install.") + end +end |