1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/spec/bundler/install/gemfile/sources_spec.rb
David Rodríguez fd83b8887f [rubygems/rubygems] Skip duplicated dependency warning for gemspec dev deps
Generally this warning is skipped for gemspec development dependencies.
I think because it's common to override them in the Gemfile to change
the source, for example.

But the order of conditions was not correct and the warning was still
being printed in one case.

https://github.com/rubygems/rubygems/commit/da9d1d6a3f
2022-06-01 00:14:31 +09:00

1566 lines
47 KiB
Ruby

# 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 "https://gem.repo3"
source "https://gem.repo1"
gem "rack-obama"
gem "rack"
G
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 "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)
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 "https://gem.repo3"
source "https://gem.repo1"
gem "rack-obama"
gem "rack", "1.0.0" # force it to install the working version in repo1
G
end
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")
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
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 "https://gem.repo3"
source "https://gem.repo1" do
gem "thin" # comes first to test name sorting
gem "rack"
end
gem "rack-obama" # should come from repo3!
G
end
it "installs the gems without any warning" do
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")
end
it "can cache and deploy" do
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"
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
install_gemfile <<-G, :artifice => "compact_index"
source "https://gem.repo3"
gem "rack-obama" # should come from repo3!
gem "rack", :source => "https://gem.repo1"
G
end
it "installs the gems without any warning" do
expect(err).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 in the pinned source" do
before do
build_repo gem_repo3 do
build_gem "depends_on_rack", "1.0.1" do |s|
s.add_dependency "rack"
end
end
# we need a working rack gem in repo3
update_repo gem_repo3 do
build_gem "rack", "1.0.0"
end
gemfile <<-G
source "https://gem.repo2"
source "https://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, :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")
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
it "installs from the same source without any warning" 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", :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"
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")
end
end
end
context "when a pinned gem has an indirect dependency in a different source" do
before do
# In these tests, we need a working rack gem in repo2 and not repo3
build_repo gem_repo3 do
build_gem "depends_on_rack", "1.0.1" do |s|
s.add_dependency "rack"
end
end
build_repo gem_repo2 do
build_gem "rack", "1.0.0"
end
end
context "and not in any other sources" do
before do
install_gemfile <<-G, :artifice => "compact_index"
source "https://gem.repo2"
source "https://gem.repo3" do
gem "depends_on_rack"
end
G
end
it "installs from the other source without any warning" do
expect(err).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 "https://gem.repo1"
source "https://gem.repo2"
source "https://gem.repo3" do
gem "depends_on_rack"
end
G
end
it "installs from the other source and warns about ambiguous gems", :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.repo2")
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
#{specific_local_platform}
DEPENDENCIES
depends_on_rack!
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 "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
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 "https://gem.repo3" # contains depends_on_rack
source "https://gem.repo2" # contains broken rack
gem "depends_on_rack" # installed from gem_repo3
gem "rack", :source => "https://gem.repo1"
G
end
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")
# 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"
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
expect(err).to include("Each source after the first must include a block")
expect(exitstatus).to eq(4)
end
end
end
context "when a top-level gem can only be found in an scoped source" do
before do
build_repo2
build_repo gem_repo3 do
build_gem "private_gem_1", "1.0.0"
build_gem "private_gem_2", "1.0.0"
end
gemfile <<-G
source "https://gem.repo2"
gem "private_gem_1"
source "https://gem.repo3" do
gem "private_gem_2"
end
G
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.")
end
end
context "when an indirect dependency can't be found in the aggregate rubygems source", :bundler => "< 3" do
before do
build_repo2
build_repo gem_repo3 do
build_gem "depends_on_missing", "1.0.1" do |s|
s.add_dependency "missing"
end
end
gemfile <<-G
source "https://gem.repo2"
source "https://gem.repo3"
gem "depends_on_missing"
G
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.")
end
end
context "when a top-level gem has an indirect dependency" do
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 "https://gem.repo2"
gem "depends_on_rack"
source "https://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 the dependency from the top-level source without warning" do
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")
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, :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."
)
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, :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")
end
end
end
context "when a scoped gem has a deeply nested indirect dependency" do
before do
build_repo gem_repo3 do
build_gem "depends_on_depends_on_rack", "1.0.1" do |s|
s.add_dependency "depends_on_rack"
end
build_gem "depends_on_rack", "1.0.1" do |s|
s.add_dependency "rack"
end
end
gemfile <<-G
source "https://gem.repo2"
source "https://gem.repo3" do
gem "depends_on_depends_on_rack"
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 the dependency from the top-level 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")
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
context "and the dependency is only in a pinned source" do
before do
build_repo2
update_repo gem_repo3 do
build_gem "rack", "1.0.0"
end
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")
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" do |s|
s.write "lib/rack.rb", "RACK = 'FAIL'"
end
end
update_repo gem_repo3 do
build_gem "rack", "1.0.0"
end
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")
end
end
end
context "when the lockfile has aggregated rubygems sources and newer versions of dependencies are available" do
before do
build_repo gem_repo2 do
build_gem "activesupport", "6.0.3.4" do |s|
s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2"
s.add_dependency "i18n", ">= 0.7", "< 2"
s.add_dependency "minitest", "~> 5.1"
s.add_dependency "tzinfo", "~> 1.1"
s.add_dependency "zeitwerk", "~> 2.2", ">= 2.2.2"
end
build_gem "activesupport", "6.1.2.1" do |s|
s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2"
s.add_dependency "i18n", ">= 1.6", "< 2"
s.add_dependency "minitest", ">= 5.1"
s.add_dependency "tzinfo", "~> 2.0"
s.add_dependency "zeitwerk", "~> 2.3"
end
build_gem "concurrent-ruby", "1.1.8"
build_gem "concurrent-ruby", "1.1.9"
build_gem "connection_pool", "2.2.3"
build_gem "i18n", "1.8.9" do |s|
s.add_dependency "concurrent-ruby", "~> 1.0"
end
build_gem "minitest", "5.14.3"
build_gem "rack", "2.2.3"
build_gem "redis", "4.2.5"
build_gem "sidekiq", "6.1.3" do |s|
s.add_dependency "connection_pool", ">= 2.2.2"
s.add_dependency "rack", "~> 2.0"
s.add_dependency "redis", ">= 4.2.0"
end
build_gem "thread_safe", "0.3.6"
build_gem "tzinfo", "1.2.9" do |s|
s.add_dependency "thread_safe", "~> 0.1"
end
build_gem "tzinfo", "2.0.4" do |s|
s.add_dependency "concurrent-ruby", "~> 1.0"
end
build_gem "zeitwerk", "2.4.2"
end
build_repo gem_repo3 do
build_gem "sidekiq-pro", "5.2.1" do |s|
s.add_dependency "connection_pool", ">= 2.2.3"
s.add_dependency "sidekiq", ">= 6.1.0"
end
end
gemfile <<-G
# frozen_string_literal: true
source "https://gem.repo2"
gem "activesupport"
source "https://gem.repo3" do
gem "sidekiq-pro"
end
G
lockfile <<~L
GEM
remote: https://gem.repo2/
remote: https://gem.repo3/
specs:
activesupport (6.0.3.4)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
zeitwerk (~> 2.2, >= 2.2.2)
concurrent-ruby (1.1.8)
connection_pool (2.2.3)
i18n (1.8.9)
concurrent-ruby (~> 1.0)
minitest (5.14.3)
rack (2.2.3)
redis (4.2.5)
sidekiq (6.1.3)
connection_pool (>= 2.2.2)
rack (~> 2.0)
redis (>= 4.2.0)
sidekiq-pro (5.2.1)
connection_pool (>= 2.2.3)
sidekiq (>= 6.1.0)
thread_safe (0.3.6)
tzinfo (1.2.9)
thread_safe (~> 0.1)
zeitwerk (2.4.2)
PLATFORMS
#{specific_local_platform}
DEPENDENCIES
activesupport
sidekiq-pro!
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"
expect(err).to be_empty
expect(the_bundle).to include_gems("activesupport 6.0.3.4")
expect(the_bundle).not_to include_gems("activesupport 6.1.2.1")
expect(the_bundle).to include_gems("tzinfo 1.2.9")
expect(the_bundle).not_to include_gems("tzinfo 2.0.4")
expect(the_bundle).to include_gems("concurrent-ruby 1.1.8")
expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.9")
expect(lockfile).to eq <<~L
GEM
remote: https://gem.repo2/
specs:
activesupport (6.0.3.4)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
zeitwerk (~> 2.2, >= 2.2.2)
concurrent-ruby (1.1.8)
connection_pool (2.2.3)
i18n (1.8.9)
concurrent-ruby (~> 1.0)
minitest (5.14.3)
rack (2.2.3)
redis (4.2.5)
sidekiq (6.1.3)
connection_pool (>= 2.2.2)
rack (~> 2.0)
redis (>= 4.2.0)
thread_safe (0.3.6)
tzinfo (1.2.9)
thread_safe (~> 0.1)
zeitwerk (2.4.2)
GEM
remote: https://gem.repo3/
specs:
sidekiq-pro (5.2.1)
connection_pool (>= 2.2.3)
sidekiq (>= 6.1.0)
PLATFORMS
#{specific_local_platform}
DEPENDENCIES
activesupport
sidekiq-pro!
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
initial_lockfile = lockfile
bundle "config set --local frozen true"
bundle :install, :artifice => "compact_index"
expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.")
expect(the_bundle).to include_gems("activesupport 6.0.3.4")
expect(the_bundle).not_to include_gems("activesupport 6.1.2.1")
expect(the_bundle).to include_gems("tzinfo 1.2.9")
expect(the_bundle).not_to include_gems("tzinfo 2.0.4")
expect(the_bundle).to include_gems("concurrent-ruby 1.1.8")
expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.9")
expect(lockfile).to eq(initial_lockfile)
end
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
expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.")
expect(lockfile).to eq(initial_lockfile)
end
it "splits sections and upgrades gems when running bundle update, and doesn't warn" do
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")
expect(the_bundle).not_to include_gems("tzinfo 1.2.9")
expect(the_bundle).to include_gems("tzinfo 2.0.4")
expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.8")
expect(the_bundle).to include_gems("concurrent-ruby 1.1.9")
expect(lockfile).to eq <<~L
GEM
remote: https://gem.repo2/
specs:
activesupport (6.1.2.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
zeitwerk (~> 2.3)
concurrent-ruby (1.1.9)
connection_pool (2.2.3)
i18n (1.8.9)
concurrent-ruby (~> 1.0)
minitest (5.14.3)
rack (2.2.3)
redis (4.2.5)
sidekiq (6.1.3)
connection_pool (>= 2.2.2)
rack (~> 2.0)
redis (>= 4.2.0)
tzinfo (2.0.4)
concurrent-ruby (~> 1.0)
zeitwerk (2.4.2)
GEM
remote: https://gem.repo3/
specs:
sidekiq-pro (5.2.1)
connection_pool (>= 2.2.3)
sidekiq (>= 6.1.0)
PLATFORMS
#{specific_local_platform}
DEPENDENCIES
activesupport
sidekiq-pro!
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"
expect(err).to be_empty
expect(the_bundle).to include_gems("activesupport 6.0.3.4")
expect(the_bundle).not_to include_gems("activesupport 6.1.2.1")
expect(the_bundle).to include_gems("tzinfo 1.2.9")
expect(the_bundle).not_to include_gems("tzinfo 2.0.4")
expect(the_bundle).to include_gems("concurrent-ruby 1.1.9")
expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.8")
expect(lockfile).to eq <<~L
GEM
remote: https://gem.repo2/
specs:
activesupport (6.0.3.4)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
zeitwerk (~> 2.2, >= 2.2.2)
concurrent-ruby (1.1.9)
connection_pool (2.2.3)
i18n (1.8.9)
concurrent-ruby (~> 1.0)
minitest (5.14.3)
rack (2.2.3)
redis (4.2.5)
sidekiq (6.1.3)
connection_pool (>= 2.2.2)
rack (~> 2.0)
redis (>= 4.2.0)
thread_safe (0.3.6)
tzinfo (1.2.9)
thread_safe (~> 0.1)
zeitwerk (2.4.2)
GEM
remote: https://gem.repo3/
specs:
sidekiq-pro (5.2.1)
connection_pool (>= 2.2.3)
sidekiq (>= 6.1.0)
PLATFORMS
#{specific_local_platform}
DEPENDENCIES
activesupport
sidekiq-pro!
BUNDLED WITH
#{Bundler::VERSION}
L
end
end
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|
s.add_dependency "activesupport", "= 7.0.0.alpha"
end
build_repo gem_repo2 do
build_gem "activesupport", "6.1.2"
build_gem "webpacker", "5.2.1" do |s|
s.add_dependency "activesupport", ">= 5.2"
end
end
gemfile <<-G
source "https://gem.repo2"
gemspec :path => "#{lib_path("rails")}"
gem "webpacker", "~> 5.0"
G
end
it "installs all gems without warning" do
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")}")
end
end
context "when a pinned gem has an indirect dependency with more than one level of indirection in the default source " do
before do
build_repo gem_repo3 do
build_gem "handsoap", "0.2.5.5" do |s|
s.add_dependency "nokogiri", ">= 1.2.3"
end
end
update_repo gem_repo2 do
build_gem "nokogiri", "1.11.1" do |s|
s.add_dependency "racca", "~> 1.4"
end
build_gem "racca", "1.5.2"
end
gemfile <<-G
source "https://gem.repo2"
source "https://gem.repo3" do
gem "handsoap"
end
gem "nokogiri"
G
end
it "installs from the default source without any warnings or errors and generates a proper lockfile" do
expected_lockfile = <<~L
GEM
remote: https://gem.repo2/
specs:
nokogiri (1.11.1)
racca (~> 1.4)
racca (1.5.2)
GEM
remote: https://gem.repo3/
specs:
handsoap (0.2.5.5)
nokogiri (>= 1.2.3)
PLATFORMS
#{specific_local_platform}
DEPENDENCIES
handsoap!
nokogiri
BUNDLED WITH
#{Bundler::VERSION}
L
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(lockfile).to eq(expected_lockfile)
# Even if the gems are already installed
FileUtils.rm bundled_app_lock
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(lockfile).to eq(expected_lockfile)
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
install_gemfile <<-G, :artifice => "compact_index", :raise_on_error => false
source "https://gem.repo3"
gem "not_in_repo1", :source => "https://gem.repo1"
G
end
it "does not install the gem" do
expect(err).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 => default_bundle_path
lockfile <<-L
GEM
remote: https://gem.repo1
specs:
GEM
remote: https://gem.repo3
specs:
rack (0.9.1)
PLATFORMS
#{specific_local_platform}
DEPENDENCIES
rack!
L
gemfile <<-G
source "https://gem.repo1"
source "https://gem.repo3" do
gem 'rack'
end
G
end
# Reproduction of https://github.com/rubygems/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 lockfile with aggregated rubygems sources" do
let(:aggregate_gem_section_lockfile) do
<<~L
GEM
remote: https://gem.repo1/
remote: https://gem.repo3/
specs:
rack (0.9.1)
PLATFORMS
#{specific_local_platform}
DEPENDENCIES
rack!
BUNDLED WITH
#{Bundler::VERSION}
L
end
let(:split_gem_section_lockfile) do
<<~L
GEM
remote: https://gem.repo1/
specs:
GEM
remote: https://gem.repo3/
specs:
rack (0.9.1)
PLATFORMS
#{specific_local_platform}
DEPENDENCIES
rack!
BUNDLED WITH
#{Bundler::VERSION}
L
end
before do
build_repo gem_repo3 do
build_gem "rack", "0.9.1"
end
gemfile <<-G
source "https://gem.repo1"
source "https://gem.repo3" do
gem 'rack'
end
G
lockfile aggregate_gem_section_lockfile
end
it "installs the existing lockfile but prints a warning", :bundler => "< 3" do
bundle "config set --local deployment true"
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")
end
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
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(out).to be_empty
end
end
context "with a path gem in the same Gemfile" do
before do
build_lib "foo"
gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem "rack", :source => "https://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, :artifice => "compact_index"
bundle %(exec ruby -e 'puts "OK"')
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"
install_gemfile <<-G, :artifice => "compact_index"
source "https://gem.repo1"
gem "rack" # should come from repo1!
G
end
it "installs the gems without any warning" do
expect(err).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
# With these gems,
build_repo4 do
build_gem "foo", "0.1"
build_gem "bar", "0.1"
end
# Installing this gemfile...
gemfile <<-G
source 'https://gem.repo1'
gem 'rack'
gem 'foo', '~> 0.1', :source => 'https://gem.repo4'
gem 'bar', '~> 0.1', :source => 'https://gem.repo4'
G
bundle "config set --local path ../gems/system"
bundle :install, :artifice => "compact_index"
# And 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
# And install this gemfile, updating only foo.
install_gemfile <<-G, :artifice => "compact_index"
source 'https://gem.repo1'
gem 'rack'
gem 'foo', '~> 0.2', :source => 'https://gem.repo4'
gem 'bar', '~> 0.1', :source => 'https://gem.repo4'
G
# It 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_repo gem_repo3 do
build_gem "rack"
end
build_lib "path1"
build_lib "path2"
build_git "git1"
build_git "git2"
install_gemfile <<-G, :artifice => "compact_index"
source "https://gem.repo1"
gem "rails"
source "https://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, :artifice => "compact_index", :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, :artifice => "compact_index"
source "https://gem.repo1"
gem "rack"
G
end
context "and the gemfile changes" do
it "is still able to find that gem from remote sources" do
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...
install_gemfile <<-G, :artifice => "compact_index"
source "https://gem.repo1"
source "https://gem.repo4" do
gem "rack", "2.0.1.1.forked"
gem "thor"
end
gem "rack-obama"
G
# Then we change the Gemfile by adding a version to thor
gemfile <<-G
source "https://gem.repo1"
source "https://gem.repo4" 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, :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"
source "https://gem.repo1"
gem "rack"
G
build_repo2 do
build_gem "rack", "1.2" do |s|
s.executables = "rackup"
end
build_gem "bar"
end
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"
source "https://gem.repo2"
gem "rack"
gemspec :path => "#{tmp.join("gemspec_test")}"
G
end
it "conservatively installs the existing locked version" do
expect(the_bundle).to include_gems("rack 1.0.0")
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"
end
build_repo4 do
build_gem "example", "1.0.2"
end
install_gemfile <<-G, :artifice => "compact_index"
source "https://gem.repo4"
gem "example", :source => "https://gem.repo2"
G
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
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
gemfile <<-G
source "https://gem.repo1"
source "https://gem.repo4" do
gem "example"
end
G
simulate_bundler_version_when_missing_prerelease_default_gem_activation do
ruby <<~R, :raise_on_error => false
require 'bundler/setup'
R
end
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
gemfile <<-G
source "https://gem.repo1"
source "https://gem.repo4" do
gem "example"
end
G
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
it "succeeds but warns, suggesting a source block" do
build_repo4 do
build_gem "depends_on_rack" do |s|
s.add_dependency "rack"
end
build_gem "rack"
end
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"
end
source "https://gem.repo1" do
gem "thin"
end
G
expect(err).to eq strip_whitespace(<<-EOS).strip
Warning: The gem 'rack' was found in multiple relevant sources.
* 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
expect(the_bundle).to be_locked
end
end
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|
s.add_dependency "rack"
end
build_gem "rack"
end
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"
end
source "https://gem.repo1" do
gem "thin"
end
G
expect(last_command).to be_failure
expect(err).to eq strip_whitespace(<<-EOS).strip
The gem 'rack' was found in multiple relevant sources.
* 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
end
end
context "when upgrading a lockfile suffering from dependency confusion" do
before do
build_repo4 do
build_gem "mime-types", "3.0.0"
end
build_repo2 do
build_gem "capybara", "2.5.0" do |s|
s.add_dependency "mime-types", ">= 1.16"
end
build_gem "mime-types", "3.3.1"
end
gemfile <<~G
source "https://gem.repo2"
gem "capybara", "~> 2.5.0"
source "https://gem.repo4" do
gem "mime-types", "~> 3.0"
end
G
lockfile <<-L
GEM
remote: https://gem.repo2/
remote: https://gem.repo4/
specs:
capybara (2.5.0)
mime-types (>= 1.16)
mime-types (3.3.1)
PLATFORMS
#{specific_local_platform}
DEPENDENCIES
capybara (~> 2.5.0)
mime-types (~> 3.0)!
L
end
it "upgrades the lockfile correctly" do
bundle "lock --update", :artifice => "compact_index"
expect(lockfile).to eq <<~L
GEM
remote: https://gem.repo2/
specs:
capybara (2.5.0)
mime-types (>= 1.16)
GEM
remote: https://gem.repo4/
specs:
mime-types (3.0.0)
PLATFORMS
#{specific_local_platform}
DEPENDENCIES
capybara (~> 2.5.0)
mime-types (~> 3.0)!
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 }
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
#{specific_local_platform}
DEPENDENCIES
ruport (= 1.7.0.3)!
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 }
expect(lockfile).to eq <<~L
GEM
remote: https://localgemserver.test/
specs:
pdf-writer (1.1.8)
PLATFORMS
#{specific_local_platform}
DEPENDENCIES
pdf-writer (= 1.1.8)
BUNDLED WITH
#{Bundler::VERSION}
L
end
end
end