diff options
Diffstat (limited to 'spec/bundler/lock')
| -rw-r--r-- | spec/bundler/lock/git_spec.rb | 258 | ||||
| -rw-r--r-- | spec/bundler/lock/lockfile_spec.rb | 2416 |
2 files changed, 2674 insertions, 0 deletions
diff --git a/spec/bundler/lock/git_spec.rb b/spec/bundler/lock/git_spec.rb new file mode 100644 index 0000000000..c9f76115dc --- /dev/null +++ b/spec/bundler/lock/git_spec.rb @@ -0,0 +1,258 @@ +# frozen_string_literal: true + +RSpec.describe "bundle lock with git gems" do + let(:install_gemfile_with_foo_as_a_git_dependency) do + build_git "foo" + + install_gemfile <<-G + source "https://gem.repo1" + gem 'foo', :git => "#{lib_path("foo-1.0")}" + G + end + + it "doesn't break right after running lock" do + install_gemfile_with_foo_as_a_git_dependency + + expect(the_bundle).to include_gems "foo 1.0.0" + end + + it "doesn't print errors even if running lock after removing the cache" do + install_gemfile_with_foo_as_a_git_dependency + + FileUtils.rm_r(Dir[default_cache_path("git/foo-1.0-*")].first) + + bundle "lock --verbose" + + expect(err).to be_empty + end + + it "prints a proper error when changing a locked Gemfile to point to a bad branch" do + install_gemfile_with_foo_as_a_git_dependency + + gemfile <<-G + source "https://gem.repo1" + gem 'foo', :git => "#{lib_path("foo-1.0")}", :branch => "bad" + G + + bundle "lock --update foo", env: { "LANG" => "en" }, raise_on_error: false + + expect(err).to include("Revision bad does not exist in the repository") + end + + it "prints a proper error when installing a Gemfile with a locked ref that does not exist" do + install_gemfile_with_foo_as_a_git_dependency + + lockfile <<~L + GIT + remote: #{lib_path("foo-1.0")} + revision: #{"a" * 40} + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install", raise_on_error: false + + expect(err).to include("Revision #{"a" * 40} does not exist in the repository") + end + + it "locks a git source to the current ref" do + install_gemfile_with_foo_as_a_git_dependency + + update_git "foo" + bundle :install + + run <<-RUBY + require 'foo' + puts "WIN" unless defined?(FOO_PREV_REF) + RUBY + + expect(out).to eq("WIN") + end + + it "properly clones a git source locked to an out of date ref" do + install_gemfile_with_foo_as_a_git_dependency + + update_git "foo" + + bundle :install, env: { "BUNDLE_PATH" => "foo" } + expect(err).to be_empty + end + + it "properly fetches a git source locked to an unreachable ref" do + install_gemfile_with_foo_as_a_git_dependency + + # Create a commit and make it unreachable + git "checkout -b foo ", lib_path("foo-1.0") + unreachable_sha = update_git("foo").ref_for("HEAD") + git "checkout main ", lib_path("foo-1.0") + git "branch -D foo ", lib_path("foo-1.0") + + gemfile <<-G + source "https://gem.repo1" + gem 'foo', :git => "#{lib_path("foo-1.0")}" + G + + lockfile <<-L + GIT + remote: #{lib_path("foo-1.0")} + revision: #{unreachable_sha} + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" + + expect(err).to be_empty + end + + it "properly fetches a git source locked to an annotated tag" do + install_gemfile_with_foo_as_a_git_dependency + + # Create an annotated tag + git("tag -a v1.0 -m 'Annotated v1.0'", lib_path("foo-1.0")) + annotated_tag = git("rev-parse v1.0", lib_path("foo-1.0")) + + gemfile <<-G + source "https://gem.repo1" + gem 'foo', :git => "#{lib_path("foo-1.0")}" + G + + lockfile <<-L + GIT + remote: #{lib_path("foo-1.0")} + revision: #{annotated_tag} + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" + + expect(err).to be_empty + end + + it "provides correct #full_gem_path" do + install_gemfile_with_foo_as_a_git_dependency + + run <<-RUBY + puts Bundler.rubygems.find_name('foo').first.full_gem_path + RUBY + expect(out).to eq(bundle("info foo --path")) + end + + it "does not lock versions that don't exist in the repository when changing a GEM transitive dep to a GIT direct dep" do + build_repo4 do + build_gem "activesupport", "8.0.0" do |s| + s.add_dependency "securerandom" + end + + build_gem "securerandom", "0.3.1" + end + + path = lib_path("securerandom") + + build_git "securerandom", "0.3.2", path: path + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + activesupport (8.0.0) + securerandom + securerandom (0.3.1) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + activesupport + + BUNDLED WITH + #{Bundler::VERSION} + L + + gemfile <<~G + source "https://gem.repo4" + + gem "activesupport" + gem "securerandom", git: "#{path}" + G + + bundle "lock" + + expect(lockfile).to include("securerandom (0.3.2)") + end + + it "does not lock versions that don't exist in the repository when changing a GIT direct dep to a GEM direct dep" do + build_repo4 do + build_gem "ruby-lsp", "0.16.1" + end + + path = lib_path("ruby-lsp") + revision = build_git("ruby-lsp", "0.16.2", path: path).ref_for("HEAD") + + lockfile <<~L + GIT + remote: #{path} + revision: #{revision} + specs: + ruby-lsp (0.16.2) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ruby-lsp! + + BUNDLED WITH + #{Bundler::VERSION} + L + + gemfile <<~G + source "https://gem.repo4" + gem "ruby-lsp" + G + + bundle "lock" + + expect(lockfile).to include("ruby-lsp (0.16.1)") + end +end diff --git a/spec/bundler/lock/lockfile_spec.rb b/spec/bundler/lock/lockfile_spec.rb new file mode 100644 index 0000000000..654ac02aa7 --- /dev/null +++ b/spec/bundler/lock/lockfile_spec.rb @@ -0,0 +1,2416 @@ +# frozen_string_literal: true + +RSpec.describe "the lockfile format" do + before do + build_repo2 + end + + it "generates a simple lockfile for a single source, gem" do + checksums = checksums_section_when_enabled do |c| + c.checksum(gem_repo2, "myrack", "1.0.0") + end + + install_gemfile <<-G + source "https://gem.repo2" + + gem "myrack" + G + + expect(lockfile).to eq <<~G + GEM + remote: https://gem.repo2/ + specs: + myrack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "updates the lockfile's bundler version if current ver. is newer, and version was forced through BUNDLER_VERSION" do + system_gems "bundler-1.8.2" + + lockfile <<-L + GIT + remote: git://github.com/nex3/haml.git + revision: 8a2271f + specs: + + GEM + remote: https://gem.repo2/ + specs: + myrack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + omg! + myrack + + BUNDLED WITH + 1.8.2 + L + + install_gemfile <<-G, verbose: true, env: { "BUNDLER_VERSION" => Bundler::VERSION } + source "https://gem.repo2" + + gem "myrack" + G + + expect(out).not_to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with 1.8.2.") + expect(out).to include("Using bundler #{Bundler::VERSION}") + + expect(lockfile).to eq <<~G + GEM + remote: https://gem.repo2/ + specs: + myrack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack + + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "does not update the lockfile's bundler version if nothing changed during bundle install, but uses the locked version" do + version = "2.3.0" + + build_repo4 do + build_gem "myrack", "1.0.0" + + build_bundler version + end + + lockfile <<-L + GEM + remote: https://gem.repo4/ + specs: + myrack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack + + BUNDLED WITH + #{version} + L + + install_gemfile <<-G, verbose: true + source "https://gem.repo4" + + gem "myrack" + G + + expect(out).to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{version}.") + expect(out).to include("Using bundler #{version}") + + expect(lockfile).to eq <<~G + GEM + remote: https://gem.repo4/ + specs: + myrack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack + + BUNDLED WITH + #{version} + G + end + + it "adds the BUNDLED WITH section if not present" do + lockfile <<-L + GEM + remote: https://gem.repo2/ + specs: + myrack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack + L + + install_gemfile <<-G + source "https://gem.repo2" + + gem "myrack", "> 0" + G + + expect(lockfile).to eq <<~G + GEM + remote: https://gem.repo2/ + specs: + myrack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack (> 0) + + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "update the bundler major version just fine" do + current_version = Bundler::VERSION + older_major = previous_major(current_version) + + system_gems "bundler-#{older_major}" + + lockfile <<-L + GEM + remote: https://gem.repo2/ + specs: + myrack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack + + BUNDLED WITH + #{older_major} + L + + install_gemfile <<-G, env: { "BUNDLER_VERSION" => Bundler::VERSION } + source "https://gem.repo2/" + + gem "myrack" + G + + expect(err).to be_empty + + expect(lockfile).to eq <<~G + GEM + remote: https://gem.repo2/ + specs: + myrack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack + + BUNDLED WITH + #{current_version} + G + end + + it "generates a simple lockfile for a single source, gem with dependencies" do + install_gemfile <<-G + source "https://gem.repo2/" + + gem "myrack-obama" + G + + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "myrack", "1.0.0" + c.checksum gem_repo2, "myrack-obama", "1.0" + end + + expect(lockfile).to eq <<~G + GEM + remote: https://gem.repo2/ + specs: + myrack (1.0.0) + myrack-obama (1.0) + myrack + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack-obama + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "generates a simple lockfile for a single source, gem with a version requirement" do + install_gemfile <<-G + source "https://gem.repo2/" + + gem "myrack-obama", ">= 1.0" + G + + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "myrack", "1.0.0" + c.checksum gem_repo2, "myrack-obama", "1.0" + end + + expect(lockfile).to eq <<~G + GEM + remote: https://gem.repo2/ + specs: + myrack (1.0.0) + myrack-obama (1.0) + myrack + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack-obama (>= 1.0) + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "generates a lockfile without credentials" do + bundle "config set https://localgemserver.test/ user:pass" + + install_gemfile(<<-G, artifice: "endpoint_strict_basic_authentication", quiet: true) + source "https://gem.repo1" + + source "https://localgemserver.test/" do + + end + + source "https://user:pass@othergemserver.test/" do + gem "myrack-obama", ">= 1.0" + end + G + + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "myrack", "1.0.0" + c.checksum gem_repo2, "myrack-obama", "1.0" + end + + expect(lockfile).to eq <<~G + GEM + remote: https://gem.repo1/ + specs: + + GEM + remote: https://localgemserver.test/ + specs: + + GEM + remote: https://othergemserver.test/ + specs: + myrack (1.0.0) + myrack-obama (1.0) + myrack + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack-obama (>= 1.0)! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "does not add credentials to lockfile when it does not have them already" do + bundle "config set http://localgemserver.test/ user:pass" + + gemfile <<~G + source "https://gem.repo1" + + source "http://localgemserver.test/" do + + end + + source "http://user:pass@othergemserver.test/" do + gem "myrack-obama", ">= 1.0" + end + G + + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "myrack", "1.0.0" + c.checksum gem_repo2, "myrack-obama", "1.0" + end + + lockfile_without_credentials = <<~L + GEM + remote: http://localgemserver.test/ + specs: + + GEM + remote: http://othergemserver.test/ + specs: + myrack (1.0.0) + myrack-obama (1.0) + myrack + + GEM + remote: https://gem.repo1/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack-obama (>= 1.0)! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + lockfile lockfile_without_credentials + + # when not re-resolving + bundle "install", artifice: "endpoint_strict_basic_authentication", quiet: true + expect(lockfile).to eq lockfile_without_credentials + + # when re-resolving with full unlock + bundle "update", artifice: "endpoint_strict_basic_authentication" + expect(lockfile).to eq lockfile_without_credentials + + # when re-resolving without ful unlocking + bundle "update myrack-obama", artifice: "endpoint_strict_basic_authentication" + expect(lockfile).to eq lockfile_without_credentials + end + + it "keeps credentials in lockfile if already there" do + bundle "config set http://localgemserver.test/ user:pass" + + gemfile <<~G + source "https://gem.repo1" + + source "http://localgemserver.test/" do + + end + + source "http://user:pass@othergemserver.test/" do + gem "myrack-obama", ">= 1.0" + end + G + + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "myrack", "1.0.0" + c.checksum gem_repo2, "myrack-obama", "1.0" + end + + lockfile_with_credentials = <<~L + GEM + remote: http://localgemserver.test/ + specs: + + GEM + remote: http://user:pass@othergemserver.test/ + specs: + myrack (1.0.0) + myrack-obama (1.0) + myrack + + GEM + remote: https://gem.repo1/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack-obama (>= 1.0)! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + lockfile lockfile_with_credentials + + bundle "install", artifice: "endpoint_strict_basic_authentication", quiet: true + + expect(lockfile).to eq lockfile_with_credentials + end + + it "generates lockfiles with multiple requirements" do + install_gemfile <<-G + source "https://gem.repo2/" + gem "net-sftp" + G + + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "net-sftp", "1.1.1" + c.checksum gem_repo2, "net-ssh", "1.0" + end + + expect(lockfile).to eq <<~G + GEM + remote: https://gem.repo2/ + specs: + net-sftp (1.1.1) + net-ssh (>= 1.0.0, < 1.99.0) + net-ssh (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + net-sftp + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + + expect(the_bundle).to include_gems "net-sftp 1.1.1", "net-ssh 1.0.0" + end + + it "generates a simple lockfile for a single pinned source, gem with a version requirement" do + git = build_git "foo" + + install_gemfile <<-G + source "https://gem.repo1" + gem "foo", :git => "#{lib_path("foo-1.0")}" + G + + checksums = checksums_section_when_enabled do |c| + c.no_checksum "foo", "1.0" + end + + expect(lockfile).to eq <<~G + GIT + remote: #{lib_path("foo-1.0")} + revision: #{git.ref_for("main")} + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "does not asplode when a platform specific dependency is present and the Gemfile has not been resolved on that platform" do + build_lib "omg", path: lib_path("omg") + + gemfile <<-G + source "https://gem.repo2/" + + platforms :#{not_local_tag} do + gem "omg", :path => "#{lib_path("omg")}" + end + + gem "myrack" + G + + lockfile <<-L + GIT + remote: git://github.com/nex3/haml.git + revision: 8a2271f + specs: + + GEM + remote: https://gem.repo2// + specs: + myrack (1.0.0) + + PLATFORMS + #{not_local} + + DEPENDENCIES + omg! + myrack + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" + expect(the_bundle).to include_gems "myrack 1.0.0" + end + + it "serializes global git sources" do + git = build_git "foo" + + checksums = checksums_section_when_enabled do |c| + c.no_checksum "foo", "1.0" + end + + install_gemfile <<-G + source "https://gem.repo1" + git "#{lib_path("foo-1.0")}" do + gem "foo" + end + G + + expect(lockfile).to eq <<~G + GIT + remote: #{lib_path("foo-1.0")} + revision: #{git.ref_for("main")} + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "generates a lockfile with a ref for a single pinned source, git gem with a branch requirement" do + git = build_git "foo" + update_git "foo", branch: "omg" + + checksums = checksums_section_when_enabled do |c| + c.no_checksum "foo", "1.0" + end + + install_gemfile <<-G + source "https://gem.repo1" + gem "foo", :git => "#{lib_path("foo-1.0")}", :branch => "omg" + G + + expect(lockfile).to eq <<~G + GIT + remote: #{lib_path("foo-1.0")} + revision: #{git.ref_for("omg")} + branch: omg + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "generates a lockfile with a ref for a single pinned source, git gem with a tag requirement" do + git = build_git "foo" + update_git "foo", tag: "omg" + + checksums = checksums_section_when_enabled do |c| + c.no_checksum "foo", "1.0" + end + + install_gemfile <<-G + source "https://gem.repo1" + gem "foo", :git => "#{lib_path("foo-1.0")}", :tag => "omg" + G + + expect(lockfile).to eq <<~G + GIT + remote: #{lib_path("foo-1.0")} + revision: #{git.ref_for("omg")} + tag: omg + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "is conservative with dependencies of git gems" do + build_repo4 do + build_gem "orm_adapter", "0.4.1" + build_gem "orm_adapter", "0.5.0" + end + + FileUtils.mkdir_p lib_path("ckeditor/lib") + + @remote = build_git("ckeditor_remote", bare: true) + + build_git "ckeditor", path: lib_path("ckeditor") do |s| + s.write "lib/ckeditor.rb", "CKEDITOR = '4.0.7'" + s.version = "4.0.7" + s.add_dependency "orm_adapter" + end + + update_git "ckeditor", path: lib_path("ckeditor"), remote: @remote.path + update_git "ckeditor", path: lib_path("ckeditor"), tag: "v4.0.7" + old_git = update_git "ckeditor", path: lib_path("ckeditor"), push: "v4.0.7" + + update_git "ckeditor", path: lib_path("ckeditor"), gemspec: true do |s| + s.write "lib/ckeditor.rb", "CKEDITOR = '4.0.8'" + s.version = "4.0.8" + s.add_dependency "orm_adapter" + end + update_git "ckeditor", path: lib_path("ckeditor"), tag: "v4.0.8" + + new_git = update_git "ckeditor", path: lib_path("ckeditor"), push: "v4.0.8" + + gemfile <<-G + source "https://gem.repo4" + gem "ckeditor", :git => "#{@remote.path}", :tag => "v4.0.8" + G + + lockfile <<~L + GIT + remote: #{@remote.path} + revision: #{old_git.ref_for("v4.0.7")} + tag: v4.0.7 + specs: + ckeditor (4.0.7) + orm_adapter + + GEM + remote: https://gem.repo4/ + specs: + orm_adapter (0.4.1) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ckeditor! + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "lock" + + # Bumps the git gem, but keeps its dependency locked + expect(lockfile).to eq <<~L + GIT + remote: #{@remote.path} + revision: #{new_git.ref_for("v4.0.8")} + tag: v4.0.8 + specs: + ckeditor (4.0.8) + orm_adapter + + GEM + remote: https://gem.repo4/ + specs: + orm_adapter (0.4.1) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ckeditor! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "serializes pinned path sources to the lockfile" do + build_lib "foo" + + checksums = checksums_section_when_enabled do |c| + c.no_checksum "foo", "1.0" + end + + install_gemfile <<-G + source "https://gem.repo1" + gem "foo", :path => "#{lib_path("foo-1.0")}" + G + + expect(lockfile).to eq <<~G + PATH + remote: #{lib_path("foo-1.0")} + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "serializes pinned path sources to the lockfile even when packaging" do + build_lib "foo" + + install_gemfile <<-G + source "https://gem.repo1" + gem "foo", :path => "#{lib_path("foo-1.0")}" + G + + checksums = checksums_section_when_enabled do |c| + c.no_checksum "foo", "1.0" + end + + bundle :cache + bundle :install, local: true + + expect(lockfile).to eq <<~G + PATH + remote: #{lib_path("foo-1.0")} + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "sorts serialized sources by type" do + build_lib "foo" + bar = build_git "bar" + + checksums = checksums_section_when_enabled do |c| + c.no_checksum "foo", "1.0" + c.no_checksum "bar", "1.0" + c.checksum gem_repo2, "myrack", "1.0.0" + end + + install_gemfile <<-G + source "https://gem.repo2/" + + gem "myrack" + gem "foo", :path => "#{lib_path("foo-1.0")}" + gem "bar", :git => "#{lib_path("bar-1.0")}" + G + + expect(lockfile).to eq <<~G + GIT + remote: #{lib_path("bar-1.0")} + revision: #{bar.ref_for("main")} + specs: + bar (1.0) + + PATH + remote: #{lib_path("foo-1.0")} + specs: + foo (1.0) + + GEM + remote: https://gem.repo2/ + specs: + myrack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + bar! + foo! + myrack + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "removes redundant sources" do + install_gemfile <<-G + source "https://gem.repo2/" + + gem "myrack", :source => "https://gem.repo2/" + G + + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "myrack", "1.0.0" + end + + expect(lockfile).to eq <<~G + GEM + remote: https://gem.repo2/ + specs: + myrack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "lists gems alphabetically" do + install_gemfile <<-G + source "https://gem.repo2/" + + gem "thin" + gem "actionpack" + gem "myrack-obama" + G + + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "actionpack", "2.3.2" + c.checksum gem_repo2, "activesupport", "2.3.2" + c.checksum gem_repo2, "myrack", "1.0.0" + c.checksum gem_repo2, "myrack-obama", "1.0" + c.checksum gem_repo2, "thin", "1.0" + end + + expect(lockfile).to eq <<~G + GEM + remote: https://gem.repo2/ + specs: + actionpack (2.3.2) + activesupport (= 2.3.2) + activesupport (2.3.2) + myrack (1.0.0) + myrack-obama (1.0) + myrack + thin (1.0) + myrack + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + actionpack + myrack-obama + thin + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "orders dependencies' dependencies in alphabetical order" do + install_gemfile <<-G + source "https://gem.repo2/" + + gem "rails" + G + + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "actionmailer", "2.3.2" + c.checksum gem_repo2, "actionpack", "2.3.2" + c.checksum gem_repo2, "activerecord", "2.3.2" + c.checksum gem_repo2, "activeresource", "2.3.2" + c.checksum gem_repo2, "activesupport", "2.3.2" + c.checksum gem_repo2, "rails", "2.3.2" + c.checksum gem_repo2, "rake", rake_version + end + + expect(lockfile).to eq <<~G + GEM + remote: https://gem.repo2/ + 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) + 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}) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rails + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "orders dependencies by version" do + update_repo2 do + # Capistrano did this (at least until version 2.5.10) + # RubyGems 2.2 doesn't allow the specifying of a dependency twice + # See https://github.com/ruby/rubygems/commit/03dbac93a3396a80db258d9bc63500333c25bd2f + build_gem "double_deps", "1.0", skip_validation: true do |s| + s.add_dependency "net-ssh", ">= 1.0.0" + s.add_dependency "net-ssh" + end + end + + install_gemfile <<-G + source "https://gem.repo2" + gem 'double_deps' + G + + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "double_deps", "1.0" + c.checksum gem_repo2, "net-ssh", "1.0" + end + + expect(lockfile).to eq <<~G + GEM + remote: https://gem.repo2/ + specs: + double_deps (1.0) + net-ssh + net-ssh (>= 1.0.0) + net-ssh (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + double_deps + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "does not add the :require option to the lockfile" do + install_gemfile <<-G + source "https://gem.repo2/" + + gem "myrack-obama", ">= 1.0", :require => "myrack/obama" + G + + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "myrack", "1.0.0" + c.checksum gem_repo2, "myrack-obama", "1.0" + end + + expect(lockfile).to eq <<~G + GEM + remote: https://gem.repo2/ + specs: + myrack (1.0.0) + myrack-obama (1.0) + myrack + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack-obama (>= 1.0) + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "does not add the :group option to the lockfile" do + install_gemfile <<-G + source "https://gem.repo2/" + + gem "myrack-obama", ">= 1.0", :group => :test + G + + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "myrack", "1.0.0" + c.checksum gem_repo2, "myrack-obama", "1.0" + end + + expect(lockfile).to eq <<~G + GEM + remote: https://gem.repo2/ + specs: + myrack (1.0.0) + myrack-obama (1.0) + myrack + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack-obama (>= 1.0) + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "stores relative paths when the path is provided in a relative fashion and in Gemfile dir" do + build_lib "foo", path: bundled_app("foo") + + checksums = checksums_section_when_enabled do |c| + c.no_checksum "foo", "1.0" + end + + install_gemfile <<-G + source "https://gem.repo1" + path "foo" do + gem "foo" + end + G + + expect(lockfile).to eq <<~G + PATH + remote: foo + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "stores relative paths when the path is provided in a relative fashion and is above Gemfile dir" do + build_lib "foo", path: bundled_app(File.join("..", "foo")) + + checksums = checksums_section_when_enabled do |c| + c.no_checksum "foo", "1.0" + end + + install_gemfile <<-G + source "https://gem.repo1" + path "../foo" do + gem "foo" + end + G + + expect(lockfile).to eq <<~G + PATH + remote: ../foo + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "stores relative paths when the path is provided in an absolute fashion but is relative" do + build_lib "foo", path: bundled_app("foo") + + install_gemfile <<-G + source "https://gem.repo1" + path File.expand_path("foo", __dir__) do + gem "foo" + end + G + + checksums = checksums_section_when_enabled do |c| + c.no_checksum "foo", "1.0" + end + + expect(lockfile).to eq <<~G + PATH + remote: foo + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "stores relative paths when the path is provided for gemspec" do + build_lib("foo", path: tmp("foo")) + + checksums = checksums_section_when_enabled do |c| + c.no_checksum "foo", "1.0" + end + + install_gemfile <<-G + source "https://gem.repo1" + gemspec :path => "../foo" + G + + expect(lockfile).to eq <<~G + PATH + remote: ../foo + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "keeps existing platforms in the lockfile" do + checksums = checksums_section_when_enabled do |c| + c.no_checksum "myrack", "1.0.0" + end + + lockfile <<-G + GEM + remote: https://gem.repo2/ + specs: + myrack (1.0.0) + + PLATFORMS + java + + DEPENDENCIES + myrack + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + + install_gemfile <<-G + source "https://gem.repo2/" + + gem "myrack" + G + + checksums.checksum(gem_repo2, "myrack", "1.0.0") + + expect(lockfile).to eq <<~G + GEM + remote: https://gem.repo2/ + specs: + myrack (1.0.0) + + PLATFORMS + #{lockfile_platforms("java", local_platform, defaults: [])} + + DEPENDENCIES + myrack + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "adds compatible platform specific variants to the lockfile, even if resolution fallback to ruby due to some other incompatible platform specific variant" do + simulate_platform "arm64-darwin-23" do + build_repo4 do + build_gem "google-protobuf", "3.25.1" + build_gem "google-protobuf", "3.25.1" do |s| + s.platform = "arm64-darwin-23" + end + build_gem "google-protobuf", "3.25.1" do |s| + s.platform = "x64-mingw-ucrt" + s.required_ruby_version = "> #{Gem.ruby_version}" + end + end + + gemfile <<-G + source "https://gem.repo4" + gem "google-protobuf" + G + bundle "lock --add-platform x64-mingw-ucrt" + + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "google-protobuf", "3.25.1" + c.checksum gem_repo4, "google-protobuf", "3.25.1", "arm64-darwin-23" + end + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + google-protobuf (3.25.1) + google-protobuf (3.25.1-arm64-darwin-23) + + PLATFORMS + arm64-darwin-23 + ruby + x64-mingw-ucrt + + DEPENDENCIES + google-protobuf + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + it "persists the spec's specific platform to the lockfile" do + build_repo2 do + build_gem "platform_specific", "1.0" do |s| + s.platform = Gem::Platform.new("universal-java-16") + end + end + + simulate_platform "universal-java-16" do + install_gemfile <<-G + source "https://gem.repo2" + gem "platform_specific" + G + + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "platform_specific", "1.0", "universal-java-16" + end + + expect(lockfile).to eq <<~G + GEM + remote: https://gem.repo2/ + specs: + platform_specific (1.0-universal-java-16) + + PLATFORMS + universal-java-16 + + DEPENDENCIES + platform_specific + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + end + + it "does not add duplicate gems" do + checksums = checksums_section_when_enabled do |c| + c.checksum(gem_repo2, "activesupport", "2.3.5") + c.checksum(gem_repo2, "myrack", "1.0.0") + end + + install_gemfile <<-G + source "https://gem.repo2/" + gem "myrack" + G + + install_gemfile <<-G + source "https://gem.repo2/" + gem "myrack" + gem "activesupport" + G + + expect(lockfile).to eq <<~G + GEM + remote: https://gem.repo2/ + specs: + activesupport (2.3.5) + myrack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + activesupport + myrack + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "does not add duplicate dependencies" do + checksums = checksums_section_when_enabled do |c| + c.checksum(gem_repo2, "myrack", "1.0.0") + end + + install_gemfile <<-G + source "https://gem.repo2/" + gem "myrack" + gem "myrack" + G + + expect(lockfile).to eq <<~G + GEM + remote: https://gem.repo2/ + specs: + myrack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "does not add duplicate dependencies with versions" do + checksums = checksums_section_when_enabled do |c| + c.checksum(gem_repo2, "myrack", "1.0.0") + end + + install_gemfile <<-G + source "https://gem.repo2/" + gem "myrack", "1.0" + gem "myrack", "1.0" + G + + expect(lockfile).to eq <<~G + GEM + remote: https://gem.repo2/ + specs: + myrack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack (= 1.0) + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "does not add duplicate dependencies in different groups" do + checksums = checksums_section_when_enabled do |c| + c.checksum(gem_repo2, "myrack", "1.0.0") + end + + install_gemfile <<-G + source "https://gem.repo2/" + gem "myrack", "1.0", :group => :one + gem "myrack", "1.0", :group => :two + G + + expect(lockfile).to eq <<~G + GEM + remote: https://gem.repo2/ + specs: + myrack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack (= 1.0) + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "raises if two different versions are used" do + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo2/" + gem "myrack", "1.0" + gem "myrack", "1.1" + G + + expect(bundled_app_lock).not_to exist + expect(err).to include "myrack (= 1.0) and myrack (= 1.1)" + end + + it "raises if two different sources are used" do + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo2/" + gem "myrack" + gem "myrack", :git => "git://hubz.com" + G + + expect(bundled_app_lock).not_to exist + expect(err).to include "myrack (>= 0) should come from an unspecified source and git://hubz.com" + end + + it "works correctly with multiple version dependencies" do + checksums = checksums_section_when_enabled do |c| + c.checksum(gem_repo2, "myrack", "0.9.1") + end + + install_gemfile <<-G + source "https://gem.repo2/" + gem "myrack", "> 0.9", "< 1.0" + G + + expect(lockfile).to eq <<~G + GEM + remote: https://gem.repo2/ + specs: + myrack (0.9.1) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack (> 0.9, < 1.0) + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "captures the Ruby version in the lockfile" do + checksums = checksums_section_when_enabled do |c| + c.checksum(gem_repo2, "myrack", "0.9.1") + end + + install_gemfile <<-G + source "https://gem.repo2/" + ruby '#{Gem.ruby_version}' + gem "myrack", "> 0.9", "< 1.0" + G + + expect(lockfile).to eq <<~G + GEM + remote: https://gem.repo2/ + specs: + myrack (0.9.1) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack (> 0.9, < 1.0) + #{checksums} + RUBY VERSION + #{Bundler::RubyVersion.system} + + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "automatically fixes the lockfile when it's missing deps" do + lockfile <<-L + GEM + remote: https://gem.repo2/ + specs: + myrack_middleware (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack_middleware + + BUNDLED WITH + #{Bundler::VERSION} + L + + install_gemfile <<-G + source "https://gem.repo2" + gem "myrack_middleware" + G + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo2/ + specs: + myrack (0.9.1) + myrack_middleware (1.0) + myrack (= 0.9.1) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack_middleware + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "raises a clear error when frozen mode is set and lockfile is missing deps, and does not install any gems" do + lockfile <<-L + GEM + remote: https://gem.repo2/ + specs: + myrack_middleware (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack_middleware + + BUNDLED WITH + #{Bundler::VERSION} + L + + install_gemfile <<-G, env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false + source "https://gem.repo2" + gem "myrack_middleware" + G + + expect(err).to include("Bundler found incorrect dependencies in the lockfile for myrack_middleware-1.0") + expect(err).to include("myrack: gemspec specifies = 0.9.1, not in lockfile") + expect(the_bundle).not_to include_gems "myrack_middleware 1.0" + end + + it "raises a clear error when frozen mode is set and lockfile is missing entries in CHECKSUMS section, and does not install any gems" do + lockfile <<-L + GEM + remote: https://gem.repo2/ + specs: + myrack_middleware (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack_middleware + + CHECKSUMS + + BUNDLED WITH + #{Bundler::VERSION} + L + + install_gemfile <<-G, env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false + source "https://gem.repo2" + gem "myrack_middleware" + G + + expect(err).to eq <<~L.strip + Your lockfile is missing a CHECKSUMS entry for \"myrack_middleware\", but can't be updated because frozen mode is set + + Run `bundle install` elsewhere and add the updated Gemfile.lock to version control. + L + + expect(the_bundle).not_to include_gems "myrack_middleware 1.0" + end + + it "raises a clear error when frozen mode is set and lockfile has empty checksums in CHECKSUMS section, and does not install any gems" do + lockfile <<-L + GEM + remote: https://gem.repo2/ + specs: + myrack (0.9.1) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack + + CHECKSUMS + myrack (0.9.1) + + BUNDLED WITH + #{Bundler::VERSION} + L + + install_gemfile <<-G, env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false + source "https://gem.repo2" + gem "myrack" + G + + expect(err).to eq <<~L.strip + Your lockfile has an empty CHECKSUMS entry for \"myrack\", but can't be updated because frozen mode is set + + Run `bundle install` elsewhere and add the updated Gemfile.lock to version control. + L + + expect(the_bundle).not_to include_gems "myrack 0.9.1" + end + + it "automatically fixes the lockfile when it's missing deps, they conflict with other locked deps, but conflicts are fixable" do + build_repo4 do + build_gem "other_dep", "0.9" + build_gem "other_dep", "1.0" + + build_gem "myrack", "0.9.1" + + build_gem "myrack_middleware", "1.0" do |s| + s.add_dependency "myrack", "= 0.9.1" + s.add_dependency "other_dep", "= 0.9" + end + end + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + myrack_middleware (1.0) + other_dep (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack_middleware + other_dep + + BUNDLED WITH + #{Bundler::VERSION} + L + + install_gemfile <<-G + source "https://gem.repo4" + gem "myrack_middleware" + gem "other_dep" + G + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + myrack (0.9.1) + myrack_middleware (1.0) + myrack (= 0.9.1) + other_dep (= 0.9) + other_dep (0.9) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack_middleware + other_dep + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "automatically fixes the lockfile when it's missing multiple deps, they conflict with other locked deps, but conflicts are fixable" do + build_repo4 do + build_gem "other_dep", "0.9" + build_gem "other_dep", "1.0" + + build_gem "myrack", "0.9.1" + + build_gem "myrack_middleware", "1.0" do |s| + s.add_dependency "myrack", "= 0.9.1" + end + + build_gem "another_dep_middleware", "1.0" do |s| + s.add_dependency "other_dep", "= 0.9" + end + end + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + myrack_middleware (1.0) + another_dep_middleware (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack_middleware + another_dep_middleware + + BUNDLED WITH + #{Bundler::VERSION} + L + + install_gemfile <<-G + source "https://gem.repo4" + gem "myrack_middleware" + gem "another_dep_middleware" + G + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + another_dep_middleware (1.0) + other_dep (= 0.9) + myrack (0.9.1) + myrack_middleware (1.0) + myrack (= 0.9.1) + other_dep (0.9) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + another_dep_middleware + myrack_middleware + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "raises a resolution error when lockfile is missing deps, they conflict with other locked deps, and conflicts are not fixable" do + build_repo4 do + build_gem "other_dep", "0.9" + build_gem "other_dep", "1.0" + + build_gem "myrack", "0.9.1" + + build_gem "myrack_middleware", "1.0" do |s| + s.add_dependency "myrack", "= 0.9.1" + s.add_dependency "other_dep", "= 0.9" + end + end + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + myrack_middleware (1.0) + other_dep (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack_middleware + other_dep + + BUNDLED WITH + #{Bundler::VERSION} + L + + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo4" + gem "myrack_middleware" + gem "other_dep", "1.0" + G + + expect(err).to eq <<~ERROR.strip + Could not find compatible versions + + Because every version of myrack_middleware depends on other_dep = 0.9 + and Gemfile depends on myrack_middleware >= 0, + other_dep = 0.9 is required. + So, because Gemfile depends on other_dep = 1.0, + version solving has failed. + ERROR + end + + it "regenerates a lockfile with no specs" do + build_repo4 do + build_gem "indirect_dependency", "1.2.3" do |s| + s.metadata["funding_uri"] = "https://example.com/donate" + end + + build_gem "direct_dependency", "4.5.6" do |s| + s.add_dependency "indirect_dependency", ">= 0" + end + end + + lockfile <<-G + GEM + remote: https://gem.repo4/ + specs: + + PLATFORMS + ruby + + DEPENDENCIES + direct_dependency + + BUNDLED WITH + #{Bundler::VERSION} + G + + install_gemfile <<-G + source "https://gem.repo4" + + gem "direct_dependency" + G + + expect(lockfile).to eq <<~G + GEM + remote: https://gem.repo4/ + specs: + direct_dependency (4.5.6) + indirect_dependency + indirect_dependency (1.2.3) + + PLATFORMS + #{lockfile_platforms(generic_default_locked_platform || local_platform, defaults: ["ruby"])} + + DEPENDENCIES + direct_dependency + + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "automatically fixes the lockfile when it's missing deps and the full index is in use" do + lockfile <<-L + GEM + remote: https://gem.repo2/ + specs: + myrack_middleware (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack_middleware + + BUNDLED WITH + #{Bundler::VERSION} + L + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "myrack_middleware" + G + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo2)}/ + specs: + myrack (0.9.1) + myrack_middleware (1.0) + myrack (= 0.9.1) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack_middleware + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "automatically fixes the lockfile when it includes a gem under the correct GIT section, but also under an incorrect GEM section, with a higher version, and with no explicit Gemfile requirement" do + git = build_git "foo" + + gemfile <<~G + source "https://gem.repo1/" + gem "foo", git: "#{lib_path("foo-1.0")}" + G + + # If the lockfile erroneously lists platform versions of the gem + # that don't match the locked version of the git repo we should remove them. + + lockfile <<~L + GIT + remote: #{lib_path("foo-1.0")} + revision: #{git.ref_for("main")} + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + foo (1.1-x86_64-linux-gnu) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" + + expect(lockfile).to eq <<~L + GIT + remote: #{lib_path("foo-1.0")} + revision: #{git.ref_for("main")} + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "automatically fixes the lockfile when it includes a gem under the correct GIT section, but also under an incorrect GEM section, with a higher version" do + git = build_git "foo" + + gemfile <<~G + source "https://gem.repo1/" + gem "foo", "= 1.0", git: "#{lib_path("foo-1.0")}" + G + + # If the lockfile erroneously lists platform versions of the gem + # that don't match the locked version of the git repo we should remove them. + + lockfile <<~L + GIT + remote: #{lib_path("foo-1.0")} + revision: #{git.ref_for("main")} + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + foo (1.1-x86_64-linux-gnu) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo (= 1.0)! + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" + + expect(lockfile).to eq <<~L + GIT + remote: #{lib_path("foo-1.0")} + revision: #{git.ref_for("main")} + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo (= 1.0)! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "automatically fixes the lockfile when it has incorrect deps, keeping the locked version" do + build_repo4 do + build_gem "net-smtp", "0.5.0" do |s| + s.add_dependency "net-protocol" + end + + build_gem "net-smtp", "0.5.1" do |s| + s.add_dependency "net-protocol" + end + + build_gem "net-protocol", "0.2.2" + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + gem "net-smtp" + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + net-protocol (0.2.2) + net-smtp (0.5.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + net-smtp + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + net-protocol (0.2.2) + net-smtp (0.5.0) + net-protocol + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + net-smtp + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "successfully updates the lockfile when a new gem is added in the Gemfile includes a gem that shouldn't be included" do + build_repo4 do + build_gem "logger", "1.7.0" + build_gem "rack", "3.2.0" + build_gem "net-smtp", "0.5.0" + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + gem "logger" + gem "net-smtp" + + install_if -> { false } do + gem 'rack', github: 'rack/rack' + end + G + + lockfile <<~L + GIT + remote: https://github.com/rack/rack.git + revision: 2fface9ac09fc582a81386becd939c987ad33f99 + specs: + rack (3.2.0) + + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + logger (1.7.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + logger + rack! + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" + + expect(lockfile).to eq <<~L + GIT + remote: https://github.com/rack/rack.git + revision: 2fface9ac09fc582a81386becd939c987ad33f99 + specs: + rack (3.2.0) + + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + logger (1.7.0) + net-smtp (0.5.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + logger + net-smtp + rack! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + shared_examples_for "a lockfile missing dependent specs" do + it "auto-heals" do + build_repo4 do + build_gem "minitest-bisect", "1.6.0" do |s| + s.add_dependency "path_expander", "~> 1.1" + end + + build_gem "path_expander", "1.1.1" + end + + gemfile <<~G + source "https://gem.repo4" + gem "minitest-bisect" + G + + # Corrupt lockfile (completely missing path_expander) + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + minitest-bisect (1.6.0) + + PLATFORMS + #{platforms} + + DEPENDENCIES + minitest-bisect + + BUNDLED WITH + #{Bundler::VERSION} + L + + cache_gems "minitest-bisect-1.6.0", "path_expander-1.1.1", gem_repo: gem_repo4 + bundle :install + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + minitest-bisect (1.6.0) + path_expander (~> 1.1) + path_expander (1.1.1) + + PLATFORMS + #{platforms} + + DEPENDENCIES + minitest-bisect + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "with just specific platform" do + let(:platforms) { lockfile_platforms } + + it_behaves_like "a lockfile missing dependent specs" + end + + context "with both ruby and specific platform" do + let(:platforms) { lockfile_platforms("ruby") } + + it_behaves_like "a lockfile missing dependent specs" + end + + it "auto-heals when the lockfile is missing specs" do + build_repo4 do + build_gem "minitest-bisect", "1.6.0" do |s| + s.add_dependency "path_expander", "~> 1.1" + end + + build_gem "path_expander", "1.1.1" + end + + gemfile <<~G + source "https://gem.repo4" + gem "minitest-bisect" + G + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + minitest-bisect (1.6.0) + path_expander (~> 1.1) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + minitest-bisect + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install --verbose" + expect(out).to include("re-resolving dependencies because your lockfile includes \"minitest-bisect\" but not some of its dependencies") + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + minitest-bisect (1.6.0) + path_expander (~> 1.1) + path_expander (1.1.1) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + minitest-bisect + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + describe "a line ending" do + def set_lockfile_mtime_to_known_value + time = Time.local(2000, 1, 1, 0, 0, 0) + File.utime(time, time, bundled_app_lock) + end + before(:each) do + build_repo2 + + install_gemfile <<-G + source "https://gem.repo2" + gem "myrack" + G + set_lockfile_mtime_to_known_value + end + + it "generates Gemfile.lock with \\n line endings" do + expect(File.read(bundled_app_lock)).not_to match("\r\n") + expect(the_bundle).to include_gems "myrack 1.0" + end + + context "during updates" do + it "preserves Gemfile.lock \\n line endings" do + update_repo2 do + build_gem "myrack", "1.2" do |s| + s.executables = "myrackup" + end + end + + expect { bundle "update", all: true }.to change { File.mtime(bundled_app_lock) } + expect(File.read(bundled_app_lock)).not_to match("\r\n") + expect(the_bundle).to include_gems "myrack 1.2" + end + + it "preserves Gemfile.lock \\n\\r line endings" do + skip "needs to be adapted" if Gem.win_platform? + + update_repo2 do + build_gem "myrack", "1.2" do |s| + s.executables = "myrackup" + end + end + + win_lock = File.read(bundled_app_lock).gsub(/\n/, "\r\n") + File.open(bundled_app_lock, "wb") {|f| f.puts(win_lock) } + set_lockfile_mtime_to_known_value + + expect { bundle "update", all: true }.to change { File.mtime(bundled_app_lock) } + expect(File.read(bundled_app_lock)).to match("\r\n") + + expect(the_bundle).to include_gems "myrack 1.2" + end + end + + context "when nothing changes" do + it "preserves Gemfile.lock \\n line endings" do + expect do + ruby <<-RUBY + require 'bundler' + Bundler.setup + RUBY + end.not_to change { File.mtime(bundled_app_lock) } + end + + it "preserves Gemfile.lock \\n\\r line endings" do + win_lock = File.read(bundled_app_lock).gsub(/\n/, "\r\n") + File.open(bundled_app_lock, "wb") {|f| f.puts(win_lock) } + set_lockfile_mtime_to_known_value + + expect do + ruby <<-RUBY + require 'bundler' + Bundler.setup + RUBY + end.not_to change { File.mtime(bundled_app_lock) } + end + end + end + + it "refuses to install if Gemfile.lock contains conflict markers" do + lockfile <<-L + GEM + remote: https://gem.repo2// + specs: + <<<<<<< + myrack (1.0.0) + ======= + myrack (1.0.1) + >>>>>>> + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack + + BUNDLED WITH + #{Bundler::VERSION} + L + + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo2/" + gem "myrack" + G + + expect(err).to match(/your Gemfile.lock contains merge conflicts/i) + expect(err).to match(/git checkout HEAD -- Gemfile.lock/i) + end + + private + + def previous_major(version) + version.split(".").map.with_index {|v, i| i == 0 ? v.to_i - 1 : v }.join(".") + end +end |
