diff options
Diffstat (limited to 'spec/bundler/commands/lock_spec.rb')
-rw-r--r-- | spec/bundler/commands/lock_spec.rb | 1143 |
1 files changed, 1087 insertions, 56 deletions
diff --git a/spec/bundler/commands/lock_spec.rb b/spec/bundler/commands/lock_spec.rb index 22709f4528..f6793d393b 100644 --- a/spec/bundler/commands/lock_spec.rb +++ b/spec/bundler/commands/lock_spec.rb @@ -1,14 +1,6 @@ # frozen_string_literal: true RSpec.describe "bundle lock" do - def strip_lockfile(lockfile) - strip_whitespace(lockfile).sub(/\n\Z/, "") - end - - def read_lockfile(file = "Gemfile.lock") - strip_lockfile bundled_app(file).read - end - let(:repo) { gem_repo1 } before :each do @@ -19,7 +11,19 @@ RSpec.describe "bundle lock" do gem "foo" G - @lockfile = strip_lockfile(<<-L) + checksums = checksums_section_when_existing do |c| + c.checksum repo, "actionmailer", "2.3.2" + c.checksum repo, "actionpack", "2.3.2" + c.checksum repo, "activerecord", "2.3.2" + c.checksum repo, "activeresource", "2.3.2" + c.checksum repo, "activesupport", "2.3.2" + c.checksum repo, "foo", "1.0" + c.checksum repo, "rails", "2.3.2" + c.checksum repo, "rake", rake_version + c.checksum repo, "weakling", "0.0.3" + end + + @lockfile = <<~L GEM remote: #{file_uri_for(repo)}/ specs: @@ -38,8 +42,8 @@ RSpec.describe "bundle lock" do actionpack (= 2.3.2) activerecord (= 2.3.2) activeresource (= 2.3.2) - rake (= 13.0.1) - rake (13.0.1) + rake (= #{rake_version}) + rake (#{rake_version}) weakling (0.0.3) PLATFORMS @@ -49,7 +53,7 @@ RSpec.describe "bundle lock" do foo rails weakling - + #{checksums} BUNDLED WITH #{Bundler::VERSION} L @@ -58,15 +62,23 @@ RSpec.describe "bundle lock" do it "prints a lockfile when there is no existing lockfile with --print" do bundle "lock --print" - expect(out).to eq(@lockfile) + expect(out).to eq(@lockfile.chomp) end it "prints a lockfile when there is an existing lockfile with --print" do + lockfile remove_checksums_section_from_lockfile(@lockfile) + + bundle "lock --print" + + expect(out).to eq(remove_checksums_section_from_lockfile(@lockfile).chomp) + end + + it "prints a lockfile when there is an existing checksums lockfile with --print" do lockfile @lockfile bundle "lock --print" - expect(out).to eq(@lockfile) + expect(out).to eq(@lockfile.chomp) end it "writes a lockfile when there is no existing lockfile" do @@ -75,7 +87,39 @@ RSpec.describe "bundle lock" do expect(read_lockfile).to eq(@lockfile) end + it "prints a lockfile without fetching new checksums if the existing lockfile had no checksums" do + lockfile remove_checksums_from_lockfile(@lockfile) + + bundle "lock --print" + + expect(out).to eq(remove_checksums_from_lockfile(@lockfile).chomp) + end + + it "touches the lockfile when there is an existing lockfile that does not need changes" do + lockfile @lockfile + + expect do + bundle "lock" + end.to change { bundled_app_lock.mtime } + end + + it "does not touch lockfile with --print" do + lockfile @lockfile + + expect do + bundle "lock --print" + end.not_to change { bundled_app_lock.mtime } + end + it "writes a lockfile when there is an outdated lockfile using --update" do + lockfile remove_checksums_from_lockfile(@lockfile.gsub("2.3.2", "2.3.1"), " (2.3.1)") + + bundle "lock --update" + + expect(read_lockfile).to eq(remove_checksums_from_lockfile(@lockfile)) + end + + it "writes a lockfile with checksums on --update when checksums exist" do lockfile @lockfile.gsub("2.3.2", "2.3.1") bundle "lock --update" @@ -83,10 +127,27 @@ RSpec.describe "bundle lock" do expect(read_lockfile).to eq(@lockfile) end + it "writes a lockfile when there is an outdated lockfile and bundle is frozen" do + lockfile @lockfile.gsub("2.3.2", "2.3.1") + + bundle "lock --update", env: { "BUNDLE_FROZEN" => "true" } + + expect(read_lockfile).to eq(@lockfile) + end + it "does not fetch remote specs when using the --local option" do - bundle "lock --update --local", :raise_on_error => false + bundle "lock --update --local", raise_on_error: false - expect(err).to match(/locally installed gems/) + expect(err).to match(/cached gems or installed locally/) + end + + it "does not fetch remote checksums with --local" do + lockfile remove_checksums_from_lockfile(@lockfile) + + bundle "lock --print --local" + + # No checksums because --local prevents fetching them + expect(out).to eq(remove_checksums_from_lockfile(@lockfile).chomp) end it "works with --gemfile flag" do @@ -94,7 +155,11 @@ RSpec.describe "bundle lock" do source "#{file_uri_for(repo)}" gem "foo" G - lockfile = strip_lockfile(<<-L) + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + end + + lockfile = <<~L GEM remote: #{file_uri_for(repo)}/ specs: @@ -105,7 +170,7 @@ RSpec.describe "bundle lock" do DEPENDENCIES foo - + #{checksums} BUNDLED WITH #{Bundler::VERSION} L @@ -120,7 +185,7 @@ RSpec.describe "bundle lock" do bundle "lock --lockfile=lock" expect(out).to match(/Writing lockfile to.+lock/) - expect(read_lockfile("lock")).to eq(@lockfile) + expect(read_lockfile("lock")).to eq(remove_checksums_from_lockfile(@lockfile)) expect { read_lockfile }.to raise_error(Errno::ENOENT) end @@ -128,22 +193,135 @@ RSpec.describe "bundle lock" do bundle "install" bundle "lock --lockfile=lock" + checksums = checksums_section_when_existing do |c| + c.checksum repo, "actionmailer", "2.3.2" + c.checksum repo, "actionpack", "2.3.2" + c.checksum repo, "activerecord", "2.3.2" + c.checksum repo, "activeresource", "2.3.2" + c.checksum repo, "activesupport", "2.3.2" + c.checksum repo, "foo", "1.0" + c.checksum repo, "rails", "2.3.2" + c.checksum repo, "rake", rake_version + c.checksum repo, "weakling", "0.0.3" + end + + lockfile = <<~L + GEM + remote: #{file_uri_for(repo)}/ + specs: + actionmailer (2.3.2) + activesupport (= 2.3.2) + actionpack (2.3.2) + activesupport (= 2.3.2) + activerecord (2.3.2) + activesupport (= 2.3.2) + activeresource (2.3.2) + activesupport (= 2.3.2) + activesupport (2.3.2) + foo (1.0) + rails (2.3.2) + actionmailer (= 2.3.2) + actionpack (= 2.3.2) + activerecord (= 2.3.2) + activeresource (= 2.3.2) + rake (= #{rake_version}) + rake (#{rake_version}) + weakling (0.0.3) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo + rails + weakling + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + expect(out).to match(/Writing lockfile to.+lock/) - expect(read_lockfile("lock")).to eq(@lockfile) + expect(read_lockfile("lock")).to eq(lockfile) end it "update specific gems using --update" do - lockfile @lockfile.gsub("2.3.2", "2.3.1").gsub("13.0.1", "10.0.1") + lockfile @lockfile.gsub("2.3.2", "2.3.1").gsub(rake_version, "10.0.1") bundle "lock --update rails rake" - expect(read_lockfile).to eq(@lockfile) + expect(read_lockfile).to eq(remove_checksums_from_lockfile(@lockfile, "(2.3.2)", "(#{rake_version})")) + end + + it "preserves unknown checksum algorithms" do + lockfile @lockfile.gsub(/(sha256=[a-f0-9]+)$/, "constant=true,\\1,xyz=123") + + previous_lockfile = read_lockfile + + bundle "lock" + + expect(read_lockfile).to eq(previous_lockfile) + end + + it "does not unlock git sources when only uri shape changes" do + build_git("foo") + + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}" + G + + # Change uri format to end with "/" and reinstall + install_gemfile <<-G, verbose: true + source "#{file_uri_for(gem_repo1)}" + gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}/" + G + + expect(out).to include("using resolution from the lockfile") + expect(out).not_to include("re-resolving dependencies because the list of sources changed") + end + + it "updates specific gems using --update using the locked revision of unrelated git gems for resolving" do + ref = build_git("foo").ref_for("HEAD") + + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "rake" + gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}", :branch => "deadbeef" + G + + lockfile <<~L + GIT + remote: #{file_uri_for(lib_path("foo-1.0"))} + revision: #{ref} + branch: deadbeef + specs: + foo (1.0) + + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: + rake (10.0.1) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + rake + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "lock --update rake --verbose" + expect(out).to match(/Writing lockfile to.+lock/) + expect(lockfile).to include("rake (#{rake_version})") end it "errors when updating a missing specific gems using --update" do lockfile @lockfile - bundle "lock --update blahblah", :raise_on_error => false + bundle "lock --update blahblah", raise_on_error: false expect(err).to eq("Could not find gem 'blahblah'.") expect(read_lockfile).to eq(@lockfile) @@ -157,9 +335,9 @@ RSpec.describe "bundle lock" do gem "rack_middleware", :group => "test" G bundle "config set without test" - bundle "config set path .bundle" - bundle "lock" - expect(bundled_app(".bundle")).not_to exist + bundle "config set path vendor/bundle" + bundle "lock", verbose: true + expect(bundled_app("vendor/bundle")).not_to exist end # see update_spec for more coverage on same options. logic is shared so it's not necessary @@ -176,7 +354,10 @@ RSpec.describe "bundle lock" do build_gem "foo", %w[1.5.1] do |s| s.add_dependency "bar", "~> 3.0" end - build_gem "bar", %w[2.0.3 2.0.4 2.0.5 2.1.0 2.1.1 3.0.0] + build_gem "foo", %w[2.0.0.pre] do |s| + s.add_dependency "bar" + end + build_gem "bar", %w[2.0.3 2.0.4 2.0.5 2.1.0 2.1.1 2.1.2.pre 3.0.0 3.1.0.pre 4.0.0.pre] build_gem "qux", %w[1.0.0 1.0.1 1.1.0 2.0.0] end @@ -210,6 +391,112 @@ RSpec.describe "bundle lock" do expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[foo-1.5.0 bar-2.1.1 qux-1.1.0].sort) end + + it "shows proper error when Gemfile changes forbid patch upgrades, and --patch --strict is given" do + # force next minor via Gemfile + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem 'foo', '1.5.0' + gem 'qux' + G + + bundle "lock --update foo --patch --strict", raise_on_error: false + + expect(err).to include( + "foo is locked to 1.4.3, while Gemfile is requesting foo (= 1.5.0). " \ + "--strict --patch was specified, but there are no patch level upgrades from 1.4.3 satisfying foo (= 1.5.0), so version solving has failed" + ) + end + + context "pre" do + it "defaults to major" do + bundle "lock --update --pre" + + expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[foo-2.0.0.pre bar-4.0.0.pre qux-2.0.0].sort) + end + + it "patch preferred" do + bundle "lock --update --patch --pre" + + expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[foo-1.4.5 bar-2.1.2.pre qux-1.0.1].sort) + end + + it "minor preferred" do + bundle "lock --update --minor --pre" + + expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[foo-1.5.1 bar-3.1.0.pre qux-1.1.0].sort) + end + + it "major preferred" do + bundle "lock --update --major --pre" + + expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[foo-2.0.0.pre bar-4.0.0.pre qux-2.0.0].sort) + end + end + end + + context "conservative updates when minor update adds a new dependency" do + before do + build_repo4 do + build_gem "sequel", "5.71.0" + build_gem "sequel", "5.72.0" do |s| + s.add_dependency "bigdecimal", ">= 0" + end + build_gem "bigdecimal", %w[1.4.4 99.1.4] + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + gem 'sequel' + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + sequel (5.71.0) + + PLATFORMS + ruby + + DEPENDENCIES + sequel + + BUNDLED WITH + #{Bundler::VERSION} + L + + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + end + + it "adds the latest version of the new dependency" do + bundle "lock --minor --update sequel" + + expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[sequel-5.72.0 bigdecimal-99.1.4].sort) + end + end + + it "updates the bundler version in the lockfile to the latest bundler version" do + build_repo4 do + build_gem "bundler", "55" + end + + system_gems "bundler-55", gem_repo: gem_repo4 + + install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + source "https://gems.repo4" + G + lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, '\11.0.0\2') + + bundle "lock --update --bundler --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + expect(lockfile).to end_with("BUNDLED WITH\n 55\n") + + update_repo4 do + build_gem "bundler", "99" + end + + bundle "lock --update --bundler --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + expect(lockfile).to end_with("BUNDLED WITH\n 99\n") end it "supports adding new platforms" do @@ -217,7 +504,7 @@ RSpec.describe "bundle lock" do allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) lockfile = Bundler::LockfileParser.new(read_lockfile) - expect(lockfile.platforms).to match_array(local_platforms.unshift(java, mingw).uniq) + expect(lockfile.platforms).to match_array(default_platform_list(java, x86_mingw32)) end it "supports adding new platforms with force_ruby_platform = true" do @@ -226,11 +513,11 @@ RSpec.describe "bundle lock" do remote: #{file_uri_for(gem_repo1)}/ specs: platform_specific (1.0) - platform_specific (1.0-x86-linux) + platform_specific (1.0-x86-64_linux) PLATFORMS ruby - x86-linux + x86_64-linux DEPENDENCIES platform_specific @@ -241,7 +528,7 @@ RSpec.describe "bundle lock" do allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) lockfile = Bundler::LockfileParser.new(read_lockfile) - expect(lockfile.platforms).to contain_exactly(rb, linux, java, mingw) + expect(lockfile.platforms).to contain_exactly(rb, linux, java, x86_mingw32) end it "supports adding the `ruby` platform" do @@ -249,7 +536,7 @@ RSpec.describe "bundle lock" do allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) lockfile = Bundler::LockfileParser.new(read_lockfile) - expect(lockfile.platforms).to match_array(local_platforms.unshift("ruby").uniq) + expect(lockfile.platforms).to match_array(default_platform_list("ruby")) end it "warns when adding an unknown platform" do @@ -262,16 +549,78 @@ RSpec.describe "bundle lock" do allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) lockfile = Bundler::LockfileParser.new(read_lockfile) - expect(lockfile.platforms).to match_array(local_platforms.unshift(java, mingw).uniq) + expect(lockfile.platforms).to match_array(default_platform_list(java, x86_mingw32)) bundle "lock --remove-platform java" lockfile = Bundler::LockfileParser.new(read_lockfile) - expect(lockfile.platforms).to match_array(local_platforms.unshift(mingw).uniq) + expect(lockfile.platforms).to match_array(default_platform_list(x86_mingw32)) + end + + it "also cleans up redundant platform gems when removing platforms" do + build_repo4 do + build_gem "nokogiri", "1.12.0" + build_gem "nokogiri", "1.12.0" do |s| + s.platform = "x86_64-darwin" + end + end + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "nokogiri", "1.12.0" + c.checksum gem_repo4, "nokogiri", "1.12.0", "x86_64-darwin" + end + + simulate_platform "x86_64-darwin-22" do + install_gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "nokogiri" + G + end + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.12.0) + nokogiri (1.12.0-x86_64-darwin) + + PLATFORMS + ruby + x86_64-darwin + + DEPENDENCIES + nokogiri + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + checksums.delete("nokogiri", Gem::Platform::RUBY) + + simulate_platform "x86_64-darwin-22" do + bundle "lock --remove-platform ruby" + end + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.12.0-x86_64-darwin) + + PLATFORMS + x86_64-darwin + + DEPENDENCIES + nokogiri + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L end it "errors when removing all platforms" do - bundle "lock --remove-platform #{local_platforms.join(" ")}", :raise_on_error => false + bundle "lock --remove-platform #{local_platform}", raise_on_error: false expect(err).to include("Removing all platforms from the bundle is not allowed") end @@ -280,7 +629,7 @@ RSpec.describe "bundle lock" do build_repo4 do build_gem "ffi", "1.9.14" build_gem "ffi", "1.9.14" do |s| - s.platform = mingw + s.platform = x86_mingw32 end build_gem "gssapi", "0.1" @@ -312,7 +661,14 @@ RSpec.describe "bundle lock" do gem "gssapi" G - simulate_platform(mingw) { bundle :lock } + checksums = checksums_section_when_existing do |c| + c.no_checksum "ffi", "1.9.14", "x86-mingw32" + c.no_checksum "gssapi", "1.2.0" + c.no_checksum "mixlib-shellout", "2.2.6", "universal-mingw32" + c.no_checksum "win32-process", "0.8.3" + end + + simulate_platform(x86_mingw32) { bundle :lock } expect(lockfile).to eq <<~G GEM @@ -332,12 +688,16 @@ RSpec.describe "bundle lock" do DEPENDENCIES gssapi mixlib-shellout - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G - simulate_platform(rb) { bundle :lock } + bundle "config set --local force_ruby_platform true" + bundle :lock + + checksums.no_checksum "ffi", "1.9.14" + checksums.no_checksum "mixlib-shellout", "2.2.6" expect(lockfile).to eq <<~G GEM @@ -360,7 +720,7 @@ RSpec.describe "bundle lock" do DEPENDENCIES gssapi mixlib-shellout - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G @@ -424,7 +784,12 @@ RSpec.describe "bundle lock" do gem "libv8" G - simulate_platform(Gem::Platform.new("x86_64-darwin")) { bundle "lock" } + simulate_platform(Gem::Platform.new("x86_64-darwin-19")) { bundle "lock" } + + checksums = checksums_section_when_existing do |c| + c.no_checksum "libv8", "8.4.255.0", "x86_64-darwin-19" + c.no_checksum "libv8", "8.4.255.0", "x86_64-darwin-20" + end expect(lockfile).to eq <<~G GEM @@ -434,11 +799,12 @@ RSpec.describe "bundle lock" do libv8 (8.4.255.0-x86_64-darwin-20) PLATFORMS - x86_64-darwin + x86_64-darwin-19 + x86_64-darwin-20 DEPENDENCIES libv8 - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G @@ -455,6 +821,11 @@ RSpec.describe "bundle lock" do end end + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "libv8", "8.4.255.0", "x86_64-darwin-19" + c.checksum gem_repo4, "libv8", "8.4.255.0", "x86_64-darwin-20" + end + gemfile <<-G source "#{file_uri_for(gem_repo4)}" @@ -473,7 +844,7 @@ RSpec.describe "bundle lock" do DEPENDENCIES libv8 - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G @@ -492,22 +863,25 @@ RSpec.describe "bundle lock" do end it "does not conflict on ruby requirements when adding new platforms" do - next_minor = Gem.ruby_version.segments[0..1].map.with_index {|s, i| i == 1 ? s + 1 : s }.join(".") - build_repo4 do build_gem "raygun-apm", "1.0.78" do |s| s.platform = "x86_64-linux" - s.required_ruby_version = "< #{next_minor}.dev" + s.required_ruby_version = "< #{next_ruby_minor}.dev" end build_gem "raygun-apm", "1.0.78" do |s| s.platform = "universal-darwin" - s.required_ruby_version = "< #{next_minor}.dev" + s.required_ruby_version = "< #{next_ruby_minor}.dev" end build_gem "raygun-apm", "1.0.78" do |s| s.platform = "x64-mingw32" - s.required_ruby_version = "< #{next_minor}.dev" + s.required_ruby_version = "< #{next_ruby_minor}.dev" + end + + build_gem "raygun-apm", "1.0.78" do |s| + s.platform = "x64-mingw-ucrt" + s.required_ruby_version = "< #{next_ruby_minor}.dev" end end @@ -533,30 +907,687 @@ RSpec.describe "bundle lock" do #{Bundler::VERSION} L - bundle "lock --add-platform x86_64-linux", :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + bundle "lock --add-platform x86_64-linux", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } end - context "when an update is available" do - let(:repo) { gem_repo2 } + it "does not crash on conflicting ruby requirements between platform versions in two different gems" do + build_repo4 do + build_gem "unf_ext", "0.0.8.2" - before do - lockfile(@lockfile) + build_gem "unf_ext", "0.0.8.2" do |s| + s.required_ruby_version = [">= 2.4", "< #{previous_ruby_minor}"] + s.platform = "x64-mingw32" + end + + build_gem "unf_ext", "0.0.8.2" do |s| + s.required_ruby_version = [">= #{previous_ruby_minor}", "< #{current_ruby_minor}"] + s.platform = "x64-mingw-ucrt" + end + + build_gem "google-protobuf", "3.21.12" + + build_gem "google-protobuf", "3.21.12" do |s| + s.required_ruby_version = [">= 2.5", "< #{previous_ruby_minor}"] + s.platform = "x64-mingw32" + end + + build_gem "google-protobuf", "3.21.12" do |s| + s.required_ruby_version = [">= #{previous_ruby_minor}", "< #{current_ruby_minor}"] + s.platform = "x64-mingw-ucrt" + end + end + + gemfile <<~G + source "https://gem.repo4" + + gem "google-protobuf" + gem "unf_ext" + G + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + google-protobuf (3.21.12) + unf_ext (0.0.8.2) + + PLATFORMS + x64-mingw-ucrt + x64-mingw32 + + DEPENDENCIES + google-protobuf + unf_ext + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s, "DEBUG_RESOLVER" => "1" } + end + + it "respects lower bound ruby requirements" do + build_repo4 do + build_gem "our_private_gem", "0.1.0" do |s| + s.required_ruby_version = ">= #{Gem.ruby_version}" + end + end + + gemfile <<-G + source "https://localgemserver.test" + + gem "our_private_gem" + G + + lockfile <<-L + GEM + remote: https://localgemserver.test/ + specs: + our_private_gem (0.1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + our_private_gem + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + end + + context "when an update is available" do + let(:repo) do build_repo2 do build_gem "foo", "2.0" end + gem_repo2 + end + + before do + lockfile(@lockfile) end it "does not implicitly update" do bundle "lock" - expect(read_lockfile).to eq(@lockfile) + checksums = checksums_section_when_existing do |c| + c.checksum repo, "actionmailer", "2.3.2" + c.checksum repo, "actionpack", "2.3.2" + c.checksum repo, "activerecord", "2.3.2" + c.checksum repo, "activeresource", "2.3.2" + c.checksum repo, "activesupport", "2.3.2" + c.checksum repo, "foo", "1.0" + c.checksum repo, "rails", "2.3.2" + c.checksum repo, "rake", rake_version + c.checksum repo, "weakling", "0.0.3" + end + + expected_lockfile = <<~L + GEM + remote: #{file_uri_for(repo)}/ + specs: + actionmailer (2.3.2) + activesupport (= 2.3.2) + actionpack (2.3.2) + activesupport (= 2.3.2) + activerecord (2.3.2) + activesupport (= 2.3.2) + activeresource (2.3.2) + activesupport (= 2.3.2) + activesupport (2.3.2) + foo (1.0) + rails (2.3.2) + actionmailer (= 2.3.2) + actionpack (= 2.3.2) + activerecord (= 2.3.2) + activeresource (= 2.3.2) + rake (= #{rake_version}) + rake (#{rake_version}) + weakling (0.0.3) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo + rails + weakling + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + expect(read_lockfile).to eq(expected_lockfile) end it "accounts for changes in the gemfile" do gemfile gemfile.gsub('"foo"', '"foo", "2.0"') bundle "lock" - expect(read_lockfile).to eq(@lockfile.sub("foo (1.0)", "foo (2.0)").sub(/foo$/, "foo (= 2.0)")) + checksums = checksums_section_when_existing do |c| + c.checksum repo, "actionmailer", "2.3.2" + c.checksum repo, "actionpack", "2.3.2" + c.checksum repo, "activerecord", "2.3.2" + c.checksum repo, "activeresource", "2.3.2" + c.checksum repo, "activesupport", "2.3.2" + c.no_checksum "foo", "2.0" + c.checksum repo, "rails", "2.3.2" + c.checksum repo, "rake", rake_version + c.checksum repo, "weakling", "0.0.3" + end + + expected_lockfile = <<~L + GEM + remote: #{file_uri_for(repo)}/ + specs: + actionmailer (2.3.2) + activesupport (= 2.3.2) + actionpack (2.3.2) + activesupport (= 2.3.2) + activerecord (2.3.2) + activesupport (= 2.3.2) + activeresource (2.3.2) + activesupport (= 2.3.2) + activesupport (2.3.2) + foo (2.0) + rails (2.3.2) + actionmailer (= 2.3.2) + actionpack (= 2.3.2) + activerecord (= 2.3.2) + activeresource (= 2.3.2) + rake (= #{rake_version}) + rake (#{rake_version}) + weakling (0.0.3) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo (= 2.0) + rails + weakling + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + expect(read_lockfile).to eq(expected_lockfile) + end + end + + context "when a system gem has incorrect dependencies, different from the lockfile" do + before do + build_repo4 do + build_gem "debug", "1.6.3" do |s| + s.add_dependency "irb", ">= 1.3.6" + end + + build_gem "irb", "1.5.0" + end + + system_gems "irb-1.5.0", gem_repo: gem_repo4 + system_gems "debug-1.6.3", gem_repo: gem_repo4 + + # simulate gemspec with wrong empty dependencies + debug_gemspec_path = system_gem_path("specifications/debug-1.6.3.gemspec") + debug_gemspec = Gem::Specification.load(debug_gemspec_path.to_s) + debug_gemspec.dependencies.clear + File.write(debug_gemspec_path, debug_gemspec.to_ruby) + end + + it "respects the existing lockfile, even when reresolving" do + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "debug" + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + debug (1.6.3) + irb (>= 1.3.6) + irb (1.5.0) + + PLATFORMS + x86_64-linux + + DEPENDENCIES + debug + #{checksums_section} + BUNDLED WITH + #{Bundler::VERSION} + L + + simulate_platform "arm64-darwin-22" do + bundle "lock" + end + + checksums = checksums_section do |c| + c.no_checksum "debug", "1.6.3" + c.no_checksum "irb", "1.5.0" + end + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + debug (1.6.3) + irb (>= 1.3.6) + irb (1.5.0) + + PLATFORMS + arm64-darwin-22 + x86_64-linux + + DEPENDENCIES + debug + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + it "properly shows resolution errors including OR requirements" do + build_repo4 do + build_gem "activeadmin", "2.13.1" do |s| + s.add_dependency "railties", ">= 6.1", "< 7.1" + end + build_gem "actionpack", "6.1.4" + build_gem "actionpack", "7.0.3.1" + build_gem "actionpack", "7.0.4" + build_gem "railties", "6.1.4" do |s| + s.add_dependency "actionpack", "6.1.4" + end + build_gem "rails", "7.0.3.1" do |s| + s.add_dependency "railties", "7.0.3.1" + end + build_gem "rails", "7.0.4" do |s| + s.add_dependency "railties", "7.0.4" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "rails", ">= 7.0.3.1" + gem "activeadmin", "2.13.1" + G + + bundle "lock", raise_on_error: false + + expect(err).to eq <<~ERR.strip + Could not find compatible versions + + Because rails >= 7.0.4 depends on railties = 7.0.4 + and rails < 7.0.4 depends on railties = 7.0.3.1, + railties = 7.0.3.1 OR = 7.0.4 is required. + So, because railties = 7.0.3.1 OR = 7.0.4 could not be found in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally, + version solving has failed. + ERR + end + + it "is able to display some explanation on crazy irresolvable cases" do + build_repo4 do + build_gem "activeadmin", "2.13.1" do |s| + s.add_dependency "ransack", "= 3.1.0" + end + + # Activemodel is missing as a dependency in lockfile + build_gem "ransack", "3.1.0" do |s| + s.add_dependency "activemodel", ">= 6.0.4" + s.add_dependency "activesupport", ">= 6.0.4" + end + + %w[6.0.4 7.0.2.3 7.0.3.1 7.0.4].each do |version| + build_gem "activesupport", version + + # Activemodel is only available on 6.0.4 + if version == "6.0.4" + build_gem "activemodel", version do |s| + s.add_dependency "activesupport", version + end + end + + build_gem "rails", version do |s| + # Depednencies of Rails 7.0.2.3 are in reverse order + if version == "7.0.2.3" + s.add_dependency "activesupport", version + s.add_dependency "activemodel", version + else + s.add_dependency "activemodel", version + s.add_dependency "activesupport", version + end + end + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "rails", ">= 7.0.2.3" + gem "activeadmin", "= 2.13.1" + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + activeadmin (2.13.1) + ransack (= 3.1.0) + ransack (3.1.0) + activemodel (>= 6.0.4) + + PLATFORMS + #{local_platform} + + DEPENDENCIES + activeadmin (= 2.13.1) + ransack (= 3.1.0) + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "lock", raise_on_error: false + + expect(err).to eq <<~ERR.strip + Could not find compatible versions + + Because every version of activemodel depends on activesupport = 6.0.4 + and rails >= 7.0.2.3, < 7.0.3.1 depends on activesupport = 7.0.2.3, + every version of activemodel is incompatible with rails >= 7.0.2.3, < 7.0.3.1. + And because rails >= 7.0.2.3, < 7.0.3.1 depends on activemodel = 7.0.2.3, + rails >= 7.0.2.3, < 7.0.3.1 cannot be used. + (1) So, because rails >= 7.0.3.1, < 7.0.4 depends on activemodel = 7.0.3.1 + and rails >= 7.0.4 depends on activemodel = 7.0.4, + rails >= 7.0.2.3 requires activemodel = 7.0.3.1 OR = 7.0.4. + + Because rails >= 7.0.2.3, < 7.0.3.1 depends on activemodel = 7.0.2.3 + and rails >= 7.0.3.1, < 7.0.4 depends on activesupport = 7.0.3.1, + rails >= 7.0.2.3, < 7.0.4 requires activemodel = 7.0.2.3 or activesupport = 7.0.3.1. + And because rails >= 7.0.4 depends on activesupport = 7.0.4 + and every version of activemodel depends on activesupport = 6.0.4, + activemodel != 7.0.2.3 is incompatible with rails >= 7.0.2.3. + And because rails >= 7.0.2.3 requires activemodel = 7.0.3.1 OR = 7.0.4 (1), + rails >= 7.0.2.3 cannot be used. + So, because Gemfile depends on rails >= 7.0.2.3, + version solving has failed. + ERR + + lockfile lockfile.gsub(/PLATFORMS\n #{local_platform}/m, "PLATFORMS\n #{lockfile_platforms("ruby")}") + + bundle "lock", raise_on_error: false + + expect(err).to eq <<~ERR.strip + Could not find compatible versions + + Because rails >= 7.0.3.1, < 7.0.4 depends on activemodel = 7.0.3.1 + and rails >= 7.0.2.3, < 7.0.3.1 depends on activemodel = 7.0.2.3, + rails >= 7.0.2.3, < 7.0.4 requires activemodel = 7.0.2.3 OR = 7.0.3.1. + And because every version of activemodel depends on activesupport = 6.0.4, + rails >= 7.0.2.3, < 7.0.4 requires activesupport = 6.0.4. + Because rails >= 7.0.3.1, < 7.0.4 depends on activesupport = 7.0.3.1 + and rails >= 7.0.2.3, < 7.0.3.1 depends on activesupport = 7.0.2.3, + rails >= 7.0.2.3, < 7.0.4 requires activesupport = 7.0.2.3 OR = 7.0.3.1. + Thus, rails >= 7.0.2.3, < 7.0.4 cannot be used. + And because rails >= 7.0.4 depends on activemodel = 7.0.4, + rails >= 7.0.2.3 requires activemodel = 7.0.4. + So, because activemodel = 7.0.4 could not be found in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally + and Gemfile depends on rails >= 7.0.2.3, + version solving has failed. + ERR + end + + it "does not accidentally resolves to prereleases" do + build_repo4 do + build_gem "autoproj", "2.0.3" do |s| + s.add_dependency "autobuild", ">= 1.10.0.a" + s.add_dependency "tty-prompt" + end + + build_gem "tty-prompt", "0.6.0" + build_gem "tty-prompt", "0.7.0" + + build_gem "autobuild", "1.10.0.b3" + build_gem "autobuild", "1.10.1" do |s| + s.add_dependency "tty-prompt", "~> 0.6.0" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + gem "autoproj", ">= 2.0.0" + G + + bundle "lock" + expect(lockfile).to_not include("autobuild (1.10.0.b3)") + expect(lockfile).to include("autobuild (1.10.1)") + end + + # Newer rails depends on Bundler, while ancient Rails does not. Bundler tries + # a first resolution pass that does not consider pre-releases. However, when + # using a pre-release Bundler (like the .dev version), that results in that + # pre-release being ignored and resolving to a version that does not depend on + # Bundler at all. We should avoid that and still consider .dev Bundler. + # + it "does not ignore prereleases with there's only one candidate" do + build_repo4 do + build_gem "rails", "7.4.0.2" do |s| + s.add_dependency "bundler", ">= 1.15.0" + end + + build_gem "rails", "2.3.18" + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + gem "rails" + G + + bundle "lock" + expect(lockfile).to_not include("rails (2.3.18)") + expect(lockfile).to include("rails (7.4.0.2)") + end + + it "deals with platform specific incompatibilities" do + build_repo4 do + build_gem "activerecord", "6.0.6" + build_gem "activerecord-jdbc-adapter", "60.4" do |s| + s.platform = "java" + s.add_dependency "activerecord", "~> 6.0.0" + end + build_gem "activerecord-jdbc-adapter", "61.0" do |s| + s.platform = "java" + s.add_dependency "activerecord", "~> 6.1.0" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + gem "activerecord", "6.0.6" + gem "activerecord-jdbc-adapter", "61.0" + G + + simulate_platform "universal-java-19" do + bundle "lock", raise_on_error: false + end + + expect(err).to include("Could not find compatible versions") + expect(err).not_to include("ERROR REPORT TEMPLATE") + end + + context "when re-resolving to include prereleases" do + before do + build_repo4 do + build_gem "tzinfo-data", "1.2022.7" + build_gem "rails", "7.1.0.alpha" do |s| + s.add_dependency "activesupport" + end + build_gem "activesupport", "7.1.0.alpha" + end + end + + it "does not end up including gems scoped to other platforms in the lockfile" do + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "rails" + gem "tzinfo-data", platform: :windows + G + + simulate_platform "x86_64-darwin-22" do + bundle "lock" + end + + expect(lockfile).not_to include("tzinfo-data (1.2022.7)") + end + end + + context "when resolving platform specific gems as indirect dependencies on truffleruby", :truffleruby_only do + before do + build_lib "foo", path: bundled_app do |s| + s.add_dependency "nokogiri" + end + + build_repo4 do + build_gem "nokogiri", "1.14.2" + build_gem "nokogiri", "1.14.2" do |s| + s.platform = "x86_64-linux" + end + end + + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gemspec + G + end + + it "locks ruby specs" do + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + c.no_checksum "nokogiri", "1.14.2" + end + + simulate_platform "x86_64-linux" do + bundle "lock" + end + + expect(lockfile).to eq <<~L + PATH + remote: . + specs: + foo (1.0) + nokogiri + + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.14.2) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "when adding a new gem that requires unlocking other transitive deps" do + before do + build_repo4 do + build_gem "govuk_app_config", "0.1.0" + + build_gem "govuk_app_config", "4.13.0" do |s| + s.add_dependency "railties", ">= 5.0" + end + + %w[7.0.4.1 7.0.4.3].each do |v| + build_gem "railties", v do |s| + s.add_dependency "actionpack", v + s.add_dependency "activesupport", v + end + + build_gem "activesupport", v + build_gem "actionpack", v + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "govuk_app_config" + gem "activesupport", "7.0.4.3" + G + + # Simulate out of sync lockfile because top level dependency on + # activesuport has just been added to the Gemfile, and locked to a higher + # version + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + actionpack (7.0.4.1) + activesupport (7.0.4.1) + govuk_app_config (4.13.0) + railties (>= 5.0) + railties (7.0.4.1) + actionpack (= 7.0.4.1) + activesupport (= 7.0.4.1) + + PLATFORMS + arm64-darwin-22 + + DEPENDENCIES + govuk_app_config + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "does not downgrade top level dependencies" do + checksums = checksums_section_when_existing do |c| + c.no_checksum "actionpack", "7.0.4.3" + c.no_checksum "activesupport", "7.0.4.3" + c.no_checksum "govuk_app_config", "4.13.0" + c.no_checksum "railties", "7.0.4.3" + end + + simulate_platform "arm64-darwin-22" do + bundle "lock" + end + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + actionpack (7.0.4.3) + activesupport (7.0.4.3) + govuk_app_config (4.13.0) + railties (>= 5.0) + railties (7.0.4.3) + actionpack (= 7.0.4.3) + activesupport (= 7.0.4.3) + + PLATFORMS + arm64-darwin-22 + + DEPENDENCIES + activesupport (= 7.0.4.3) + govuk_app_config + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L end end end |