diff options
Diffstat (limited to 'spec/bundler/install/gemfile/sources_spec.rb')
-rw-r--r-- | spec/bundler/install/gemfile/sources_spec.rb | 742 |
1 files changed, 609 insertions, 133 deletions
diff --git a/spec/bundler/install/gemfile/sources_spec.rb b/spec/bundler/install/gemfile/sources_spec.rb index 7a0c148254..a5ba76f4d9 100644 --- a/spec/bundler/install/gemfile/sources_spec.rb +++ b/spec/bundler/install/gemfile/sources_spec.rb @@ -27,18 +27,72 @@ RSpec.describe "bundle install with gems on multiple sources" do G end - it "warns about ambiguous gems, but installs anyway, prioritizing sources last to first", :bundler => "< 3" do - bundle :install, :artifice => "compact_index" + it "refuses to install mismatched checksum because one gem has been tampered with", bundler: "< 3" do + lockfile <<~L + GEM + remote: https://gem.repo3/ + remote: https://gem.repo1/ + specs: + rack (1.0.0) - expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") - expect(err).to include("Installed from: https://gem.repo1") - expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", :source => "remote1") + PLATFORMS + #{local_platform} + + DEPENDENCIES + depends_on_rack! + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle :install, artifice: "compact_index", raise_on_error: false + + expect(exitstatus).to eq(37) + expect(err).to eq <<~E.strip + [DEPRECATED] Your Gemfile contains multiple global sources. Using `source` more than once without a block is a security risk, and may result in installing unexpected gems. To resolve this warning, use a block to indicate which gems should come from the secondary source. + Bundler found mismatched checksums. This is a potential security risk. + #{checksum_to_lock(gem_repo1, "rack", "1.0.0")} + from the API at https://gem.repo1/ + #{checksum_to_lock(gem_repo3, "rack", "1.0.0")} + from the API at https://gem.repo3/ + + Mismatched checksums each have an authoritative source: + 1. the API at https://gem.repo1/ + 2. the API at https://gem.repo3/ + You may need to alter your Gemfile sources to resolve this issue. + + To ignore checksum security warnings, disable checksum validation with + `bundle config set --local disable_checksum_validation true` + E end - it "fails", :bundler => "3" do - bundle :instal, :artifice => "compact_index", :raise_on_error => false - expect(err).to include("Each source after the first must include a block") - expect(exitstatus).to eq(4) + context "when checksum validation is disabled" do + before do + bundle "config set --local disable_checksum_validation true" + end + + it "warns about ambiguous gems, but installs anyway, prioritizing sources last to first", bundler: "< 3" do + bundle :install, artifice: "compact_index" + + expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") + expect(err).to include("Installed from: https://gem.repo1") + expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", source: "remote1") + end + + it "does not use the full index unnecessarily", bundler: "< 3" do + bundle :install, artifice: "compact_index", verbose: true + + expect(out).to include("https://gem.repo1/versions") + expect(out).to include("https://gem.repo3/versions") + expect(out).not_to include("https://gem.repo1/quick/Marshal.4.8/") + expect(out).not_to include("https://gem.repo3/quick/Marshal.4.8/") + end + + it "fails", bundler: "3" do + bundle :install, artifice: "compact_index", raise_on_error: false + expect(err).to include("Each source after the first must include a block") + expect(exitstatus).to eq(4) + end end end @@ -54,21 +108,49 @@ RSpec.describe "bundle install with gems on multiple sources" do G end - it "warns about ambiguous gems, but installs anyway", :bundler => "< 3" do - bundle :install, :artifice => "compact_index" + it "warns about ambiguous gems, but installs anyway", bundler: "< 3" do + bundle :install, artifice: "compact_index" expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") expect(err).to include("Installed from: https://gem.repo1") - expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", :source => "remote1") + expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", source: "remote1") end - it "fails", :bundler => "3" do - bundle :install, :artifice => "compact_index", :raise_on_error => false + it "fails", bundler: "3" do + bundle :install, artifice: "compact_index", raise_on_error: false expect(err).to include("Each source after the first must include a block") expect(exitstatus).to eq(4) end end end + context "without source affinity, and a stdlib gem present in one of the sources", :ruby_repo do + let(:default_json_version) { ruby "gem 'json'; require 'json'; puts JSON::VERSION" } + + before do + build_repo2 do + build_gem "json", default_json_version + end + + build_repo4 do + build_gem "foo" do |s| + s.add_dependency "json", default_json_version + end + end + + gemfile <<-G + source "https://gem.repo2" + source "https://gem.repo4" + + gem "foo" + G + end + + it "works in standalone mode", bundler: "< 3" do + gem_checksum = checksum_digest(gem_repo4, "foo", "1.0") + bundle "install --standalone", artifice: "compact_index", env: { "BUNDLER_SPEC_FOO_CHECKSUM" => gem_checksum } + end + end + context "with source affinity" do context "with sources given by a block" do before do @@ -95,20 +177,20 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs the gems without any warning" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).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") + expect(the_bundle).to include_gems("rack 1.0.0", source: "remote1") end it "can cache and deploy" do - bundle :cache, :artifice => "compact_index" + bundle :cache, artifice: "compact_index" 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 "config set --local deployment true" - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0") end @@ -128,7 +210,7 @@ RSpec.describe "bundle install with gems on multiple sources" do end end - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo3" gem "rack-obama" # should come from repo3! gem "rack", :source => "https://gem.repo1" @@ -168,9 +250,9 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs from the same source without any warning" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).not_to include("Warning") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", source: "remote3") end end @@ -185,18 +267,18 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs from the same source without any warning" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" 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", :source => "remote3") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", source: "remote3") # 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, :artifice => "compact_index" + bundle :install, artifice: "compact_index" 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", :source => "remote3") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", source: "remote3") end end end @@ -218,7 +300,7 @@ RSpec.describe "bundle install with gems on multiple sources" do context "and not in any other sources" do before do - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo2" source "https://gem.repo3" do gem "depends_on_rack" @@ -243,11 +325,63 @@ RSpec.describe "bundle install with gems on multiple sources" do G end - it "installs from the other source and warns about ambiguous gems", :bundler => "< 3" do - bundle :install, :artifice => "compact_index" + it "fails when the two sources don't have the same checksum", bundler: "< 3" do + bundle :install, artifice: "compact_index", raise_on_error: false + + expect(err).to eq(<<~E.strip) + [DEPRECATED] Your Gemfile contains multiple global sources. Using `source` more than once without a block is a security risk, and may result in installing unexpected gems. To resolve this warning, use a block to indicate which gems should come from the secondary source. + Bundler found mismatched checksums. This is a potential security risk. + #{checksum_to_lock(gem_repo2, "rack", "1.0.0")} + from the API at https://gem.repo2/ + #{checksum_to_lock(gem_repo1, "rack", "1.0.0")} + from the API at https://gem.repo1/ + + Mismatched checksums each have an authoritative source: + 1. the API at https://gem.repo2/ + 2. the API at https://gem.repo1/ + You may need to alter your Gemfile sources to resolve this issue. + + To ignore checksum security warnings, disable checksum validation with + `bundle config set --local disable_checksum_validation true` + E + expect(exitstatus).to eq(37) + end + + it "fails when the two sources agree, but the local gem calculates a different checksum", bundler: "< 3" do + rack_checksum = "c0ffee11" * 8 + bundle :install, artifice: "compact_index", env: { "BUNDLER_SPEC_RACK_CHECKSUM" => rack_checksum }, raise_on_error: false + + expect(err).to eq(<<~E.strip) + [DEPRECATED] Your Gemfile contains multiple global sources. Using `source` more than once without a block is a security risk, and may result in installing unexpected gems. To resolve this warning, use a block to indicate which gems should come from the secondary source. + Bundler found mismatched checksums. This is a potential security risk. + rack (1.0.0) sha256=#{rack_checksum} + from the API at https://gem.repo2/ + and the API at https://gem.repo1/ + #{checksum_to_lock(gem_repo2, "rack", "1.0.0")} + from the gem at #{default_bundle_path("cache", "rack-1.0.0.gem")} + + If you trust the API at https://gem.repo2/, to resolve this issue you can: + 1. remove the gem at #{default_bundle_path("cache", "rack-1.0.0.gem")} + 2. run `bundle install` + + To ignore checksum security warnings, disable checksum validation with + `bundle config set --local disable_checksum_validation true` + E + expect(exitstatus).to eq(37) + end + + it "installs from the other source and warns about ambiguous gems when the sources have the same checksum", bundler: "< 3" do + gem_checksum = checksum_digest(gem_repo2, "rack", "1.0.0") + bundle :install, artifice: "compact_index", env: { "BUNDLER_SPEC_RACK_CHECKSUM" => gem_checksum, "DEBUG" => "1" } + expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") expect(err).to include("Installed from: https://gem.repo2") + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo3, "depends_on_rack", "1.0.1" + c.checksum gem_repo2, "rack", "1.0.0" + end + expect(lockfile).to eq <<~L GEM remote: https://gem.repo1/ @@ -262,11 +396,51 @@ RSpec.describe "bundle install with gems on multiple sources" do rack PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES depends_on_rack! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + previous_lockfile = lockfile + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") + expect(lockfile).to eq(previous_lockfile) + end + + it "installs from the other source and warns about ambiguous gems when checksum validation is disabled", bundler: "< 3" do + bundle "config set --local disable_checksum_validation true" + bundle :install, artifice: "compact_index" + + expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") + expect(err).to include("Installed from: https://gem.repo2") + + checksums = checksums_section_when_existing do |c| + c.no_checksum "depends_on_rack", "1.0.1" + c.no_checksum "rack", "1.0.0" + end + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo1/ + remote: https://gem.repo2/ + specs: + rack (1.0.0) + + GEM + remote: https://gem.repo3/ + specs: + depends_on_rack (1.0.1) + rack + + PLATFORMS + #{lockfile_platforms} + DEPENDENCIES + depends_on_rack! + #{checksums} BUNDLED WITH #{Bundler::VERSION} L @@ -276,8 +450,8 @@ RSpec.describe "bundle install with gems on multiple sources" do expect(lockfile).to eq(previous_lockfile) end - it "fails", :bundler => "3" do - bundle :install, :artifice => "compact_index", :raise_on_error => false + it "fails", bundler: "3" do + bundle :install, artifice: "compact_index", raise_on_error: false expect(err).to include("Each source after the first must include a block") expect(exitstatus).to eq(4) end @@ -301,8 +475,8 @@ RSpec.describe "bundle install with gems on multiple sources" do G end - it "installs the dependency from the pinned source without warning", :bundler => "< 3" do - bundle :install, :artifice => "compact_index" + it "installs the dependency from the pinned source without warning", bundler: "< 3" do + bundle :install, artifice: "compact_index" 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") @@ -310,14 +484,14 @@ RSpec.describe "bundle install with gems on multiple sources" do # In https://github.com/rubygems/bundler/issues/3585 this failed # when there is already a lock file, and the gems are missing, so try again system_gems [] - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" 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 - it "fails", :bundler => "3" do - bundle :install, :artifice => "compact_index", :raise_on_error => false + it "fails", bundler: "3" do + bundle :install, artifice: "compact_index", raise_on_error: false expect(err).to include("Each source after the first must include a block") expect(exitstatus).to eq(4) end @@ -345,12 +519,12 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "fails" do - bundle :install, :artifice => "compact_index", :raise_on_error => false - expect(err).to include("Could not find gem 'private_gem_1' in rubygems repository https://gem.repo2/ or installed locally.") + bundle :install, artifice: "compact_index", raise_on_error: false + expect(err).to include("Could not find gem 'private_gem_1' in rubygems repository https://gem.repo2/, cached gems or installed locally.") end end - context "when an indirect dependency can't be found in the aggregate rubygems source", :bundler => "< 3" do + context "when an indirect dependency can't be found in the aggregate rubygems source", bundler: "< 3" do before do build_repo2 @@ -370,8 +544,16 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "fails" do - bundle :install, :artifice => "compact_index", :raise_on_error => false - expect(err).to include("Could not find gem 'missing', which is required by gem 'depends_on_missing', in any of the sources.") + bundle :install, artifice: "compact_index", raise_on_error: false + expect(err).to end_with <<~E.strip + Could not find compatible versions + + Because every version of depends_on_missing depends on missing >= 0 + and missing >= 0 could not be found in any of the sources, + depends_on_missing cannot be used. + So, because Gemfile depends on depends_on_missing >= 0, + version solving has failed. + E end end @@ -406,11 +588,11 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs the dependency from the top-level source without warning" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).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") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote2") - expect(the_bundle).to include_gems("unrelated_gem 1.0.0", :source => "remote3") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", source: "remote2") + expect(the_bundle).to include_gems("unrelated_gem 1.0.0", source: "remote3") end end @@ -424,10 +606,16 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "does not find the dependency" do - bundle :install, :artifice => "compact_index", :raise_on_error => false - expect(err).to include( - "Could not find gem 'rack', which is required by gem 'depends_on_rack', in rubygems repository https://gem.repo2/ or installed locally." - ) + bundle :install, artifice: "compact_index", raise_on_error: false + expect(err).to end_with <<~E.strip + Could not find compatible versions + + Because every version of depends_on_rack depends on rack >= 0 + and rack >= 0 could not be found in rubygems repository https://gem.repo2/, cached gems or installed locally, + depends_on_rack cannot be used. + So, because Gemfile depends on depends_on_rack >= 0, + version solving has failed. + E end end @@ -445,12 +633,12 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs the dependency from the top-level source without warning" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).not_to include("Warning") expect(run("require 'rack'; puts RACK")).to eq("1.0.0") expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote2") - expect(the_bundle).to include_gems("unrelated_gem 1.0.0", :source => "remote3") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", source: "remote2") + expect(the_bundle).to include_gems("unrelated_gem 1.0.0", source: "remote3") end end end @@ -484,10 +672,10 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs the dependency from the top-level source" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0") - expect(the_bundle).to include_gems("rack 1.0.0", :source => "remote2") - expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", :source => "remote3") + expect(the_bundle).to include_gems("rack 1.0.0", source: "remote2") + expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", source: "remote3") end end @@ -501,8 +689,8 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs the dependency from the pinned source" do - bundle :install, :artifice => "compact_index" - expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3") + bundle :install, artifice: "compact_index" + expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0", source: "remote3") end end @@ -520,8 +708,8 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs the dependency from the pinned source without warning" do - bundle :install, :artifice => "compact_index" - expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3") + bundle :install, artifice: "compact_index" + expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0", source: "remote3") end end end @@ -595,6 +783,21 @@ RSpec.describe "bundle install with gems on multiple sources" do end G + @locked_checksums = checksums_section_when_existing do |c| + c.checksum gem_repo2, "activesupport", "6.0.3.4" + c.checksum gem_repo2, "concurrent-ruby", "1.1.8" + c.checksum gem_repo2, "connection_pool", "2.2.3" + c.checksum gem_repo2, "i18n", "1.8.9" + c.checksum gem_repo2, "minitest", "5.14.3" + c.checksum gem_repo2, "rack", "2.2.3" + c.checksum gem_repo2, "redis", "4.2.5" + c.checksum gem_repo2, "sidekiq", "6.1.3" + c.checksum gem_repo3, "sidekiq-pro", "5.2.1" + c.checksum gem_repo2, "thread_safe", "0.3.6" + c.checksum gem_repo2, "tzinfo", "1.2.9" + c.checksum gem_repo2, "zeitwerk", "2.4.2" + end + lockfile <<~L GEM remote: https://gem.repo2/ @@ -626,19 +829,19 @@ RSpec.describe "bundle install with gems on multiple sources" do zeitwerk (2.4.2) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES activesupport sidekiq-pro! - + #{@locked_checksums} BUNDLED WITH #{Bundler::VERSION} L end it "does not install newer versions but updates the lockfile format when running bundle install in non frozen mode, and doesn't warn" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).to be_empty expect(the_bundle).to include_gems("activesupport 6.0.3.4") @@ -682,22 +885,22 @@ RSpec.describe "bundle install with gems on multiple sources" do sidekiq (>= 6.1.0) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES activesupport sidekiq-pro! - + #{@locked_checksums} BUNDLED WITH #{Bundler::VERSION} L end - it "does not install newer versions or generate lockfile changes when running bundle install in frozen mode, and warns", :bundler => "< 3" do + it "does not install newer versions or generate lockfile changes when running bundle install in frozen mode, and warns", bundler: "< 3" do initial_lockfile = lockfile bundle "config set --local frozen true" - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") @@ -711,11 +914,11 @@ RSpec.describe "bundle install with gems on multiple sources" do expect(lockfile).to eq(initial_lockfile) end - it "fails when running bundle install in frozen mode", :bundler => "3" do + it "fails when running bundle install in frozen mode", bundler: "3" do initial_lockfile = lockfile bundle "config set --local frozen true" - bundle :install, :artifice => "compact_index", :raise_on_error => false + bundle :install, artifice: "compact_index", raise_on_error: false expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") @@ -723,15 +926,21 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "splits sections and upgrades gems when running bundle update, and doesn't warn" do - bundle "update --all", :artifice => "compact_index" + bundle "update --all", artifice: "compact_index" expect(err).to be_empty expect(the_bundle).not_to include_gems("activesupport 6.0.3.4") expect(the_bundle).to include_gems("activesupport 6.1.2.1") + @locked_checksums.checksum gem_repo2, "activesupport", "6.1.2.1" + expect(the_bundle).not_to include_gems("tzinfo 1.2.9") expect(the_bundle).to include_gems("tzinfo 2.0.4") + @locked_checksums.checksum gem_repo2, "tzinfo", "2.0.4" + @locked_checksums.delete "thread_safe" + expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.8") expect(the_bundle).to include_gems("concurrent-ruby 1.1.9") + @locked_checksums.checksum gem_repo2, "concurrent-ruby", "1.1.9" expect(lockfile).to eq <<~L GEM @@ -766,19 +975,19 @@ RSpec.describe "bundle install with gems on multiple sources" do sidekiq (>= 6.1.0) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES activesupport sidekiq-pro! - + #{@locked_checksums} BUNDLED WITH #{Bundler::VERSION} L end it "upgrades the lockfile format and upgrades the requested gem when running bundle update with an argument" do - bundle "update concurrent-ruby", :artifice => "compact_index" + bundle "update concurrent-ruby", artifice: "compact_index" expect(err).to be_empty expect(the_bundle).to include_gems("activesupport 6.0.3.4") @@ -788,6 +997,8 @@ RSpec.describe "bundle install with gems on multiple sources" do expect(the_bundle).to include_gems("concurrent-ruby 1.1.9") expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.8") + @locked_checksums.checksum gem_repo2, "concurrent-ruby", "1.1.9" + expect(lockfile).to eq <<~L GEM remote: https://gem.repo2/ @@ -822,12 +1033,12 @@ RSpec.describe "bundle install with gems on multiple sources" do sidekiq (>= 6.1.0) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES activesupport sidekiq-pro! - + #{@locked_checksums} BUNDLED WITH #{Bundler::VERSION} L @@ -836,8 +1047,8 @@ RSpec.describe "bundle install with gems on multiple sources" do context "when a top-level gem has an indirect dependency present in the default source, but with a different version from the one resolved" do before do - build_lib "activesupport", "7.0.0.alpha", :path => lib_path("rails/activesupport") - build_lib "rails", "7.0.0.alpha", :path => lib_path("rails") do |s| + build_lib "activesupport", "7.0.0.alpha", path: lib_path("rails/activesupport") + build_lib "rails", "7.0.0.alpha", path: lib_path("rails") do |s| s.add_dependency "activesupport", "= 7.0.0.alpha" end @@ -859,11 +1070,11 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs all gems without warning" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).not_to include("Warning") expect(the_bundle).to include_gems("activesupport 7.0.0.alpha", "rails 7.0.0.alpha") - expect(the_bundle).to include_gems("activesupport 7.0.0.alpha", :source => "path@#{lib_path("rails/activesupport")}") - expect(the_bundle).to include_gems("rails 7.0.0.alpha", :source => "path@#{lib_path("rails")}") + expect(the_bundle).to include_gems("activesupport 7.0.0.alpha", source: "path@#{lib_path("rails/activesupport")}") + expect(the_bundle).to include_gems("rails 7.0.0.alpha", source: "path@#{lib_path("rails")}") end end @@ -895,6 +1106,12 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs from the default source without any warnings or errors and generates a proper lockfile" do + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo3, "handsoap", "0.2.5.5" + c.checksum gem_repo2, "nokogiri", "1.11.1" + c.checksum gem_repo2, "racca", "1.5.2" + end + expected_lockfile = <<~L GEM remote: https://gem.repo2/ @@ -910,30 +1127,30 @@ RSpec.describe "bundle install with gems on multiple sources" do nokogiri (>= 1.2.3) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES handsoap! nokogiri - + #{checksums} BUNDLED WITH #{Bundler::VERSION} L - bundle "install --verbose", :artifice => "compact_index" + bundle "install --verbose", artifice: "compact_index" expect(err).not_to include("Warning") expect(the_bundle).to include_gems("handsoap 0.2.5.5", "nokogiri 1.11.1", "racca 1.5.2") - expect(the_bundle).to include_gems("handsoap 0.2.5.5", :source => "remote3") - expect(the_bundle).to include_gems("nokogiri 1.11.1", "racca 1.5.2", :source => "remote2") + expect(the_bundle).to include_gems("handsoap 0.2.5.5", source: "remote3") + expect(the_bundle).to include_gems("nokogiri 1.11.1", "racca 1.5.2", source: "remote2") expect(lockfile).to eq(expected_lockfile) # Even if the gems are already installed FileUtils.rm bundled_app_lock - bundle "install --verbose", :artifice => "compact_index" + bundle "install --verbose", artifice: "compact_index" expect(err).not_to include("Warning") expect(the_bundle).to include_gems("handsoap 0.2.5.5", "nokogiri 1.11.1", "racca 1.5.2") - expect(the_bundle).to include_gems("handsoap 0.2.5.5", :source => "remote3") - expect(the_bundle).to include_gems("nokogiri 1.11.1", "racca 1.5.2", :source => "remote2") + expect(the_bundle).to include_gems("handsoap 0.2.5.5", source: "remote3") + expect(the_bundle).to include_gems("nokogiri 1.11.1", "racca 1.5.2", source: "remote2") expect(lockfile).to eq(expected_lockfile) end end @@ -944,7 +1161,7 @@ RSpec.describe "bundle install with gems on multiple sources" do build_gem "not_in_repo1", "1.0.0" end - install_gemfile <<-G, :artifice => "compact_index", :raise_on_error => false + install_gemfile <<-G, artifice: "compact_index", raise_on_error: false source "https://gem.repo3" gem "not_in_repo1", :source => "https://gem.repo1" G @@ -957,7 +1174,7 @@ RSpec.describe "bundle install with gems on multiple sources" do context "with an existing lockfile" do before do - system_gems "rack-0.9.1", "rack-1.0.0", :path => default_bundle_path + system_gems "rack-0.9.1", "rack-1.0.0", path: default_bundle_path lockfile <<-L GEM @@ -970,7 +1187,7 @@ RSpec.describe "bundle install with gems on multiple sources" do rack (0.9.1) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES rack! @@ -1000,11 +1217,11 @@ RSpec.describe "bundle install with gems on multiple sources" do rack (0.9.1) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES rack! - + #{checksums_section} BUNDLED WITH #{Bundler::VERSION} L @@ -1022,7 +1239,7 @@ RSpec.describe "bundle install with gems on multiple sources" do rack (0.9.1) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES rack! @@ -1047,20 +1264,48 @@ RSpec.describe "bundle install with gems on multiple sources" do lockfile aggregate_gem_section_lockfile end - it "installs the existing lockfile but prints a warning", :bundler => "< 3" do + it "installs the existing lockfile but prints a warning when checksum validation is disabled", bundler: "< 3" do bundle "config set --local deployment true" + bundle "config set --local disable_checksum_validation true" - bundle "install", :artifice => "compact_index" + bundle "install", artifice: "compact_index" expect(lockfile).to eq(aggregate_gem_section_lockfile) expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") - expect(the_bundle).to include_gems("rack 0.9.1", :source => "remote3") + expect(the_bundle).to include_gems("rack 0.9.1", source: "remote3") + end + + it "prints a checksum warning when the checksums from both sources do not match", bundler: "< 3" do + bundle "config set --local deployment true" + + bundle "install", artifice: "compact_index", raise_on_error: false + + api_checksum1 = checksum_digest(gem_repo1, "rack", "0.9.1") + api_checksum3 = checksum_digest(gem_repo3, "rack", "0.9.1") + + expect(exitstatus).to eq(37) + expect(err).to eq(<<~E.strip) + [DEPRECATED] Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure. + Bundler found mismatched checksums. This is a potential security risk. + rack (0.9.1) sha256=#{api_checksum3} + from the API at https://gem.repo3/ + rack (0.9.1) sha256=#{api_checksum1} + from the API at https://gem.repo1/ + + Mismatched checksums each have an authoritative source: + 1. the API at https://gem.repo3/ + 2. the API at https://gem.repo1/ + You may need to alter your Gemfile sources to resolve this issue. + + To ignore checksum security warnings, disable checksum validation with + `bundle config set --local disable_checksum_validation true` + E end - it "refuses to install the existing lockfile and prints an error", :bundler => "3" do + it "refuses to install the existing lockfile and prints an error", bundler: "3" do bundle "config set --local deployment true" - bundle "install", :artifice => "compact_index", :raise_on_error =>false + bundle "install", artifice: "compact_index", raise_on_error: false expect(lockfile).to eq(aggregate_gem_section_lockfile) expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") @@ -1080,7 +1325,7 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "does not unlock the non-path gem after install" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" bundle %(exec ruby -e 'puts "OK"') @@ -1093,7 +1338,7 @@ RSpec.describe "bundle install with gems on multiple sources" do before do system_gems "rack-0.9.1" - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo1" gem "rack" # should come from repo1! G @@ -1122,7 +1367,7 @@ RSpec.describe "bundle install with gems on multiple sources" do G bundle "config set --local path ../gems/system" - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" # And then we add some new versions... update_repo4 do @@ -1133,7 +1378,7 @@ RSpec.describe "bundle install with gems on multiple sources" do it "allows them to be unlocked separately" do # And install this gemfile, updating only foo. - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source 'https://gem.repo1' gem 'rack' gem 'foo', '~> 0.2', :source => 'https://gem.repo4' @@ -1157,7 +1402,7 @@ RSpec.describe "bundle install with gems on multiple sources" do build_git "git1" build_git "git2" - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo1" gem "rails" @@ -1173,7 +1418,7 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "does not re-resolve" do - bundle :install, :artifice => "compact_index", :verbose => true + bundle :install, artifice: "compact_index", verbose: true expect(out).to include("using resolution from the lockfile") expect(out).not_to include("re-resolving dependencies") end @@ -1182,7 +1427,7 @@ RSpec.describe "bundle install with gems on multiple sources" do context "when a gem is installed to system gems" do before do - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo1" gem "rack" G @@ -1196,7 +1441,7 @@ RSpec.describe "bundle install with gems on multiple sources" do end # When this gemfile is installed... - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo1" source "https://gem.repo4" do @@ -1218,14 +1463,14 @@ RSpec.describe "bundle install with gems on multiple sources" do G # But we should still be able to find rack 2.0.1.1.forked and install it - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" end end end describe "source changed to one containing a higher version of a dependency" do before do - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo1" gem "rack" @@ -1239,11 +1484,11 @@ RSpec.describe "bundle install with gems on multiple sources" do build_gem "bar" end - build_lib("gemspec_test", :path => tmp.join("gemspec_test")) do |s| + build_lib("gemspec_test", path: tmp.join("gemspec_test")) do |s| s.add_dependency "bar", "=1.0.0" end - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo2" gem "rack" gemspec :path => "#{tmp.join("gemspec_test")}" @@ -1255,6 +1500,32 @@ RSpec.describe "bundle install with gems on multiple sources" do end end + context "when Gemfile overrides a gemspec development dependency to change the default source" do + before do + build_repo4 do + build_gem "bar" + end + + build_lib("gemspec_test", path: tmp.join("gemspec_test")) do |s| + s.add_development_dependency "bar" + end + + install_gemfile <<-G, artifice: "compact_index" + source "https://gem.repo1" + + source "https://gem.repo4" do + gem "bar" + end + + gemspec :path => "#{tmp.join("gemspec_test")}" + G + end + + it "does not print warnings" do + expect(err).to be_empty + end + end + it "doesn't update version when a gem uses a source block but a higher version from another source is already installed locally" do build_repo2 do build_gem "example", "0.1.0" @@ -1264,7 +1535,7 @@ RSpec.describe "bundle install with gems on multiple sources" do build_gem "example", "1.0.2" end - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo4" gem "example", :source => "https://gem.repo2" @@ -1273,14 +1544,14 @@ RSpec.describe "bundle install with gems on multiple sources" do bundle "info example" expect(out).to include("example (0.1.0)") - system_gems "example-1.0.2", :path => default_bundle_path, :gem_repo => gem_repo4 + system_gems "example-1.0.2", path: default_bundle_path, gem_repo: gem_repo4 - bundle "update example --verbose", :artifice => "compact_index" + bundle "update example --verbose", artifice: "compact_index" expect(out).not_to include("Using example 1.0.2") expect(out).to include("Using example 0.1.0") end - it "fails inmmediately with a helpful error when a rubygems source does not exist and bundler/setup is required" do + it "fails immediately with a helpful error when a rubygems source does not exist and bundler/setup is required" do gemfile <<-G source "https://gem.repo1" @@ -1289,17 +1560,15 @@ RSpec.describe "bundle install with gems on multiple sources" do end G - simulate_bundler_version_when_missing_prerelease_default_gem_activation do - ruby <<~R, :raise_on_error => false - require 'bundler/setup' - R - end + ruby <<~R, raise_on_error: false + require 'bundler/setup' + R expect(last_command).to be_failure expect(err).to include("Could not find gem 'example' in locally installed gems.") end - it "fails inmmediately with a helpful error when a non retriable network error happens while resolving sources" do + it "fails immediately with a helpful error when a non retriable network error happens while resolving sources" do gemfile <<-G source "https://gem.repo1" @@ -1308,13 +1577,13 @@ RSpec.describe "bundle install with gems on multiple sources" do end G - bundle "install", :artifice => nil, :raise_on_error => false + bundle "install", artifice: nil, raise_on_error: false expect(last_command).to be_failure expect(err).to include("Could not reach host gem.repo4. Check your network connection and try again.") end - context "when an indirect dependency is available from multiple ambiguous sources", :bundler => "< 3" do + context "when an indirect dependency is available from multiple ambiguous sources", bundler: "< 3" do it "succeeds but warns, suggesting a source block" do build_repo4 do build_gem "depends_on_rack" do |s| @@ -1323,7 +1592,7 @@ RSpec.describe "bundle install with gems on multiple sources" do build_gem "rack" end - install_gemfile <<-G, :artifice => "compact_index", :raise_on_error => false + install_gemfile <<-G, artifice: "compact_index", raise_on_error: false source "#{file_uri_for(gem_repo1)}" source "https://gem.repo4" do @@ -1334,10 +1603,10 @@ RSpec.describe "bundle install with gems on multiple sources" do gem "thin" end G - expect(err).to eq strip_whitespace(<<-EOS).strip + expect(err).to eq <<~EOS.strip Warning: The gem 'rack' was found in multiple relevant sources. - * rubygems repository https://gem.repo1/ or installed locally - * rubygems repository https://gem.repo4/ or installed locally + * rubygems repository https://gem.repo1/ + * rubygems repository https://gem.repo4/ You should add this gem to the source block for the source you wish it to be installed from. EOS expect(last_command).to be_success @@ -1345,7 +1614,7 @@ RSpec.describe "bundle install with gems on multiple sources" do end end - context "when an indirect dependency is available from multiple ambiguous sources", :bundler => "3" do + context "when an indirect dependency is available from multiple ambiguous sources", bundler: "3" do it "raises, suggesting a source block" do build_repo4 do build_gem "depends_on_rack" do |s| @@ -1354,7 +1623,7 @@ RSpec.describe "bundle install with gems on multiple sources" do build_gem "rack" end - install_gemfile <<-G, :artifice => "compact_index", :raise_on_error => false + install_gemfile <<-G, artifice: "compact_index", raise_on_error: false source "#{file_uri_for(gem_repo1)}" source "https://gem.repo4" do gem "depends_on_rack" @@ -1364,10 +1633,10 @@ RSpec.describe "bundle install with gems on multiple sources" do end G expect(last_command).to be_failure - expect(err).to eq strip_whitespace(<<-EOS).strip + expect(err).to eq <<~EOS.strip The gem 'rack' was found in multiple relevant sources. - * rubygems repository https://gem.repo1/ or installed locally - * rubygems repository https://gem.repo4/ or installed locally + * rubygems repository https://gem.repo1/ + * rubygems repository https://gem.repo4/ You must add this gem to the source block for the source you wish it to be installed from. EOS expect(the_bundle).not_to be_locked @@ -1408,16 +1677,23 @@ RSpec.describe "bundle install with gems on multiple sources" do mime-types (3.3.1) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES capybara (~> 2.5.0) mime-types (~> 3.0)! + + CHECKSUMS L end it "upgrades the lockfile correctly" do - bundle "lock --update", :artifice => "compact_index" + bundle "lock --update", artifice: "compact_index" + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo2, "capybara", "2.5.0" + c.checksum gem_repo4, "mime-types", "3.0.0" + end expect(lockfile).to eq <<~L GEM @@ -1432,15 +1708,215 @@ RSpec.describe "bundle install with gems on multiple sources" do mime-types (3.0.0) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES capybara (~> 2.5.0) mime-types (~> 3.0)! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "when default source includes old gems with nil required_ruby_version" do + before do + build_repo2 do + build_gem "ruport", "1.7.0.3" do |s| + s.add_dependency "pdf-writer", "1.1.8" + end + end + + build_repo gem_repo4 do + build_gem "pdf-writer", "1.1.8" + end + + path = "#{gem_repo4}/#{Gem::MARSHAL_SPEC_DIR}/pdf-writer-1.1.8.gemspec.rz" + spec = Marshal.load(Bundler.rubygems.inflate(File.binread(path))) + spec.instance_variable_set(:@required_ruby_version, nil) + File.open(path, "wb") do |f| + f.write Gem.deflate(Marshal.dump(spec)) + end + + gemfile <<~G + source "https://localgemserver.test" + + gem "ruport", "= 1.7.0.3", :source => "https://localgemserver.test/extra" + G + end + + it "handles that fine" do + bundle "install", artifice: "compact_index_extra", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "pdf-writer", "1.1.8" + c.checksum gem_repo2, "ruport", "1.7.0.3" + end + expect(lockfile).to eq <<~L + GEM + remote: https://localgemserver.test/ + specs: + pdf-writer (1.1.8) + + GEM + remote: https://localgemserver.test/extra/ + specs: + ruport (1.7.0.3) + pdf-writer (= 1.1.8) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ruport (= 1.7.0.3)! + #{checksums} BUNDLED WITH #{Bundler::VERSION} L end end + + context "when default source includes old gems with nil required_rubygems_version" do + before do + build_repo2 do + build_gem "ruport", "1.7.0.3" do |s| + s.add_dependency "pdf-writer", "1.1.8" + end + end + + build_repo gem_repo4 do + build_gem "pdf-writer", "1.1.8" + end + + path = "#{gem_repo4}/#{Gem::MARSHAL_SPEC_DIR}/pdf-writer-1.1.8.gemspec.rz" + spec = Marshal.load(Bundler.rubygems.inflate(File.binread(path))) + spec.instance_variable_set(:@required_rubygems_version, nil) + File.open(path, "wb") do |f| + f.write Gem.deflate(Marshal.dump(spec)) + end + + gemfile <<~G + source "https://localgemserver.test" + + gem "ruport", "= 1.7.0.3", :source => "https://localgemserver.test/extra" + G + end + + it "handles that fine" do + bundle "install", artifice: "compact_index_extra", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "pdf-writer", "1.1.8" + c.checksum gem_repo2, "ruport", "1.7.0.3" + end + + expect(lockfile).to eq <<~L + GEM + remote: https://localgemserver.test/ + specs: + pdf-writer (1.1.8) + + GEM + remote: https://localgemserver.test/extra/ + specs: + ruport (1.7.0.3) + pdf-writer (= 1.1.8) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ruport (= 1.7.0.3)! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "when default source uses the old API and includes old gems with nil required_rubygems_version" do + before do + build_repo4 do + build_gem "pdf-writer", "1.1.8" + end + + path = "#{gem_repo4}/#{Gem::MARSHAL_SPEC_DIR}/pdf-writer-1.1.8.gemspec.rz" + spec = Marshal.load(Bundler.rubygems.inflate(File.binread(path))) + spec.instance_variable_set(:@required_rubygems_version, nil) + File.open(path, "wb") do |f| + f.write Gem.deflate(Marshal.dump(spec)) + end + + gemfile <<~G + source "https://localgemserver.test" + + gem "pdf-writer", "= 1.1.8" + G + end + + it "handles that fine" do + bundle "install --verbose", artifice: "endpoint", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "pdf-writer", "1.1.8" + end + + expect(lockfile).to eq <<~L + GEM + remote: https://localgemserver.test/ + specs: + pdf-writer (1.1.8) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + pdf-writer (= 1.1.8) + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "when mistakenly adding a top level gem already depended on and cached under the wrong source" do + before do + build_repo4 do + build_gem "some_private_gem", "0.1.0" do |s| + s.add_dependency "example", "~> 1.0" + end + end + + build_repo2 do + build_gem "example", "1.0.0" + end + + install_gemfile <<~G, artifice: "compact_index" + source "https://gem.repo2" + + source "https://gem.repo4" do + gem "some_private_gem" + end + G + + gemfile <<~G + source "https://gem.repo2" + + source "https://gem.repo4" do + gem "some_private_gem" + gem "example" # MISTAKE, example is not available at gem.repo4 + end + G + end + + it "shows a proper error message and does not generate a corrupted lockfile" do + expect do + bundle :install, artifice: "compact_index", raise_on_error: false, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + end.not_to change { lockfile } + + expect(err).to include("Could not find gem 'example' in rubygems repository https://gem.repo4/") + end + end end |