diff options
Diffstat (limited to 'spec/bundler')
231 files changed, 14973 insertions, 5681 deletions
diff --git a/spec/bundler/bundler/bundler_spec.rb b/spec/bundler/bundler/bundler_spec.rb index d164b5d3c6..6a2e435e54 100644 --- a/spec/bundler/bundler/bundler_spec.rb +++ b/spec/bundler/bundler/bundler_spec.rb @@ -4,6 +4,71 @@ require "bundler" require "tmpdir" RSpec.describe Bundler do + describe "#load_marshal" do + it "is a private method and raises an error" do + data = Marshal.dump(Bundler) + expect { Bundler.load_marshal(data) }.to raise_error(NoMethodError, /private method [`']load_marshal' called/) + end + + it "loads any data" do + data = Marshal.dump(Bundler) + expect(Bundler.send(:load_marshal, data)).to eq(Bundler) + end + end + + describe "#safe_load_marshal" do + it "fails on unexpected class" do + data = Marshal.dump(Bundler) + expect { Bundler.safe_load_marshal(data) }.to raise_error(Bundler::MarshalError) + end + + it "loads simple structure" do + simple_structure = { "name" => [:development] } + data = Marshal.dump(simple_structure) + expect(Bundler.safe_load_marshal(data)).to eq(simple_structure) + end + + it "loads Gem::Specification" do + gem_spec = Gem::Specification.new do |s| + s.name = "bundler" + s.version = Gem::Version.new("2.4.7") + s.installed_by_version = Gem::Version.new("0") + s.authors = ["André Arko", + "Samuel Giddins", + "Colby Swandale", + "Hiroshi Shibata", + "David RodrÃguez", + "Grey Baker", + "Stephanie Morillo", + "Chris Morris", + "James Wen", + "Tim Moore", + "André Medeiros", + "Jessica Lynn Suttles", + "Terence Lee", + "Carl Lerche", + "Yehuda Katz"] + s.date = Time.utc(2023, 2, 15) + s.description = "Bundler manages an application's dependencies through its entire life, across many machines, systematically and repeatably" + s.email = ["team@bundler.io"] + s.homepage = "https://bundler.io" + s.metadata = { "bug_tracker_uri" => "https://github.com/rubygems/rubygems/issues?q=is%3Aopen+is%3Aissue+label%3ABundler", + "changelog_uri" => "https://github.com/rubygems/rubygems/blob/master/bundler/CHANGELOG.md", + "homepage_uri" => "https://bundler.io/", + "source_code_uri" => "https://github.com/rubygems/rubygems/tree/master/bundler" } + s.require_paths = ["lib"] + s.required_ruby_version = Gem::Requirement.new([">= 2.6.0"]) + s.required_rubygems_version = Gem::Requirement.new([">= 3.0.1"]) + s.rubygems_version = "3.4.7" + s.specification_version = 4 + s.summary = "The best way to manage your application's dependencies" + s.license = false + end + data = Marshal.dump(gem_spec) + expect(Bundler.safe_load_marshal(data)).to eq(gem_spec) + end + end + describe "#load_gemspec_uncached" do let(:app_gemspec_path) { tmp("test.gemspec") } subject { Bundler.load_gemspec_uncached(app_gemspec_path) } @@ -11,7 +76,7 @@ RSpec.describe Bundler do context "with incorrect YAML file" do before do File.open(app_gemspec_path, "wb") do |f| - f.write strip_whitespace(<<-GEMSPEC) + f.write <<~GEMSPEC --- {:!00 ao=gu\g1= 7~f GEMSPEC @@ -21,33 +86,9 @@ RSpec.describe Bundler do it "catches YAML syntax errors" do expect { subject }.to raise_error(Bundler::GemspecError, /error while loading `test.gemspec`/) end - - context "on Rubies with a settable YAML engine", :if => defined?(YAML::ENGINE) do - context "with Syck as YAML::Engine" do - it "raises a GemspecError after YAML load throws ArgumentError" do - orig_yamler = YAML::ENGINE.yamler - YAML::ENGINE.yamler = "syck" - - expect { subject }.to raise_error(Bundler::GemspecError) - - YAML::ENGINE.yamler = orig_yamler - end - end - - context "with Psych as YAML::Engine" do - it "raises a GemspecError after YAML load throws Psych::SyntaxError" do - orig_yamler = YAML::ENGINE.yamler - YAML::ENGINE.yamler = "psych" - - expect { subject }.to raise_error(Bundler::GemspecError) - - YAML::ENGINE.yamler = orig_yamler - end - end - end end - context "with correct YAML file", :if => defined?(Encoding) do + context "with correct YAML file", if: defined?(Encoding) do it "can load a gemspec with unicode characters with default ruby encoding" do # spec_helper forces the external encoding to UTF-8 but that's not the # default until Ruby 2.0 @@ -58,7 +99,7 @@ RSpec.describe Bundler do $VERBOSE = verbose File.open(app_gemspec_path, "wb") do |file| - file.puts <<-GEMSPEC.gsub(/^\s+/, "") + file.puts <<~GEMSPEC # -*- encoding: utf-8 -*- Gem::Specification.new do |gem| gem.author = "André the Giant" @@ -98,7 +139,7 @@ RSpec.describe Bundler do end GEMSPEC end - expect(Bundler.rubygems).to receive(:validate).with have_attributes(:name => "validated") + expect(Bundler.rubygems).to receive(:validate).with have_attributes(name: "validated") subject end end @@ -106,7 +147,7 @@ RSpec.describe Bundler do context "with gemspec containing local variables" do before do File.open(app_gemspec_path, "wb") do |f| - f.write strip_whitespace(<<-GEMSPEC) + f.write <<~GEMSPEC must_not_leak = true Gem::Specification.new do |gem| gem.name = "leak check" @@ -176,29 +217,9 @@ RSpec.describe Bundler do describe "configuration" do context "disable_shared_gems" do it "should unset GEM_PATH with empty string" do - env = {} expect(Bundler).to receive(:use_system_gems?).and_return(false) - Bundler.send(:configure_gem_path, env) - expect(env.keys).to include("GEM_PATH") - expect(env["GEM_PATH"]).to eq "" - end - end - end - - describe "#rm_rf" do - context "the directory is world writable" do - let(:bundler_ui) { Bundler.ui } - it "should raise a friendly error" do - allow(File).to receive(:exist?).and_return(true) - allow(::Bundler::FileUtils).to receive(:remove_entry_secure).and_raise(ArgumentError) - allow(File).to receive(:world_writable?).and_return(true) - message = <<EOF -It is a security vulnerability to allow your home directory to be world-writable, and bundler can not continue. -You should probably consider fixing this issue by running `chmod o-w ~` on *nix. -Please refer to https://ruby-doc.org/stdlib-2.1.2/libdoc/fileutils/rdoc/FileUtils.html#method-c-remove_entry_secure for details. -EOF - expect(bundler_ui).to receive(:warn).with(message) - expect { Bundler.send(:rm_rf, bundled_app) }.to raise_error(Bundler::PathError) + Bundler.send(:configure_gem_path) + expect(ENV["GEM_PATH"]).to eq "" end end end @@ -215,22 +236,6 @@ EOF Bundler.mkdir_p(bundled_app.join("foo", "bar")) expect(bundled_app.join("foo", "bar")).to exist end - - context "when mkdir_p requires sudo" do - it "creates a new folder using sudo" do - expect(Bundler).to receive(:requires_sudo?).and_return(true) - expect(Bundler).to receive(:sudo).and_return true - Bundler.mkdir_p(bundled_app.join("foo")) - end - end - - context "with :no_sudo option" do - it "forces mkdir_p to not use sudo" do - expect(Bundler).to receive(:requires_sudo?).and_return(true) - expect(Bundler).to_not receive(:sudo) - Bundler.mkdir_p(bundled_app.join("foo"), :no_sudo => true) - end - end end describe "#user_home" do @@ -294,118 +299,6 @@ EOF end end - describe "#requires_sudo?" do - let!(:tmpdir) { Dir.mktmpdir } - let(:bundle_path) { Pathname("#{tmpdir}/bundle") } - - def clear_cached_requires_sudo - return unless Bundler.instance_variable_defined?(:@requires_sudo_ran) - Bundler.remove_instance_variable(:@requires_sudo_ran) - Bundler.remove_instance_variable(:@requires_sudo) - end - - before do - clear_cached_requires_sudo - allow(Bundler).to receive(:which).with("sudo").and_return("/usr/bin/sudo") - allow(Bundler).to receive(:bundle_path).and_return(bundle_path) - end - - after do - FileUtils.rm_rf(tmpdir) - clear_cached_requires_sudo - end - - subject { Bundler.requires_sudo? } - - context "bundle_path doesn't exist" do - it { should be false } - - context "and parent dir can't be written" do - before do - FileUtils.chmod(0o500, tmpdir) - end - - it { should be true } - end - - context "with unwritable files in a parent dir" do - # Regression test for https://github.com/rubygems/bundler/pull/6316 - # It doesn't matter if there are other unwritable files so long as - # bundle_path can be created - before do - file = File.join(tmpdir, "unrelated_file") - FileUtils.touch(file) - FileUtils.chmod(0o400, file) - end - - it { should be false } - end - end - - context "bundle_path exists" do - before do - FileUtils.mkdir_p(bundle_path) - end - - it { should be false } - - context "and is unwritable" do - before do - FileUtils.chmod(0o500, bundle_path) - end - - it { should be true } - end - end - - context "path writability" do - before do - FileUtils.mkdir_p("tmp/vendor/bundle") - FileUtils.mkdir_p("tmp/vendor/bin_dir") - end - after do - FileUtils.rm_rf("tmp/vendor/bundle") - FileUtils.rm_rf("tmp/vendor/bin_dir") - end - context "writable paths" do - it "should return false and display nothing" do - allow(Bundler).to receive(:bundle_path).and_return(Pathname("tmp/vendor/bundle")) - expect(Bundler.ui).to_not receive(:warn) - expect(Bundler.requires_sudo?).to eq(false) - end - end - context "unwritable paths" do - before do - FileUtils.touch("tmp/vendor/bundle/unwritable1.txt") - FileUtils.touch("tmp/vendor/bundle/unwritable2.txt") - FileUtils.touch("tmp/vendor/bin_dir/unwritable3.txt") - FileUtils.chmod(0o400, "tmp/vendor/bundle/unwritable1.txt") - FileUtils.chmod(0o400, "tmp/vendor/bundle/unwritable2.txt") - FileUtils.chmod(0o400, "tmp/vendor/bin_dir/unwritable3.txt") - end - it "should return true and display warn message" do - allow(Bundler).to receive(:bundle_path).and_return(Pathname("tmp/vendor/bundle")) - bin_dir = Pathname("tmp/vendor/bin_dir/") - - # allow File#writable? to be called with args other than the stubbed on below - allow(File).to receive(:writable?).and_call_original - - # fake make the directory unwritable - allow(File).to receive(:writable?).with(bin_dir).and_return(false) - allow(Bundler).to receive(:system_bindir).and_return(Pathname("tmp/vendor/bin_dir/")) - message = <<-MESSAGE.chomp -Following files may not be writable, so sudo is needed: - tmp/vendor/bin_dir/ - tmp/vendor/bundle/unwritable1.txt - tmp/vendor/bundle/unwritable2.txt -MESSAGE - expect(Bundler.ui).to receive(:warn).with(message) - expect(Bundler.requires_sudo?).to eq(true) - end - end - end - end - context "user cache dir" do let(:home_path) { Pathname.new(ENV["HOME"]) } diff --git a/spec/bundler/bundler/ci_detector_spec.rb b/spec/bundler/bundler/ci_detector_spec.rb new file mode 100644 index 0000000000..299d8005e8 --- /dev/null +++ b/spec/bundler/bundler/ci_detector_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +RSpec.describe Bundler::CIDetector do + # This is properly tested in rubygems, under the name Gem::CIDetector + # But the test that confirms that our version _stays in sync_ with that version + # will live here. + + it "stays in sync with the rubygems implementation" do + rubygems_implementation_path = File.join(git_root, "lib", "rubygems", "ci_detector.rb") + expect(File.exist?(rubygems_implementation_path)).to be_truthy + rubygems_code = File.read(rubygems_implementation_path) + denamespaced_rubygems_code = rubygems_code.sub("Gem", "NAMESPACE") + + bundler_implementation_path = File.join(source_lib_dir, "bundler", "ci_detector.rb") + expect(File.exist?(bundler_implementation_path)).to be_truthy + bundler_code = File.read(bundler_implementation_path) + denamespaced_bundler_code = bundler_code.sub("Bundler", "NAMESPACE") + + expect(denamespaced_bundler_code).to eq(denamespaced_rubygems_code) + end +end diff --git a/spec/bundler/bundler/cli_spec.rb b/spec/bundler/bundler/cli_spec.rb index c9dd101f55..c71fc8e9e7 100644 --- a/spec/bundler/bundler/cli_spec.rb +++ b/spec/bundler/bundler/cli_spec.rb @@ -4,12 +4,12 @@ require "bundler/cli" RSpec.describe "bundle executable" do it "returns non-zero exit status when passed unrecognized options" do - bundle "--invalid_argument", :raise_on_error => false + bundle "--invalid_argument", raise_on_error: false expect(exitstatus).to_not be_zero end it "returns non-zero exit status when passed unrecognized task" do - bundle "unrecognized-task", :raise_on_error => false + bundle "unrecognized-task", raise_on_error: false expect(exitstatus).to_not be_zero end @@ -87,7 +87,7 @@ RSpec.describe "bundle executable" do end context "with no arguments" do - it "prints a concise help message", :bundler => "3" do + it "prints a concise help message", bundler: "3" do bundle "" expect(err).to be_empty expect(out).to include("Bundler version #{Bundler::VERSION}"). @@ -105,50 +105,80 @@ RSpec.describe "bundle executable" do gem 'rack' G - bundle :install, :env => { "BUNDLE_GEMFILE" => "" } + bundle :install, env: { "BUNDLE_GEMFILE" => "" } expect(the_bundle).to include_gems "rack 1.0.0" end end - context "when ENV['RUBYGEMS_GEMDEPS'] is set" do - it "displays a warning" do - gemfile bundled_app_gemfile, <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' - G - - bundle :install, :env => { "RUBYGEMS_GEMDEPS" => "foo" } - expect(err).to include("RUBYGEMS_GEMDEPS") - expect(err).to include("conflict with Bundler") - - bundle :install, :env => { "RUBYGEMS_GEMDEPS" => "" } - expect(err).not_to include("RUBYGEMS_GEMDEPS") - end - end - context "with --verbose" do it "prints the running command" do gemfile "source \"#{file_uri_for(gem_repo1)}\"" - bundle "info bundler", :verbose => true + bundle "info bundler", verbose: true expect(out).to start_with("Running `bundle info bundler --verbose` with bundler #{Bundler::VERSION}") end it "doesn't print defaults" do - install_gemfile "source \"#{file_uri_for(gem_repo1)}\"", :verbose => true + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"", verbose: true expect(out).to start_with("Running `bundle install --verbose` with bundler #{Bundler::VERSION}") end it "doesn't print defaults" do - install_gemfile "source \"#{file_uri_for(gem_repo1)}\"", :verbose => true + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"", verbose: true expect(out).to start_with("Running `bundle install --verbose` with bundler #{Bundler::VERSION}") end end + describe "bundle outdated" do + let(:run_command) do + bundle "install" + + bundle "outdated #{flags}", raise_on_error: false + end + + before do + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "rack", '0.9.1' + G + end + + context "with --groups flag" do + let(:flags) { "--groups" } + + it "prints a message when there are outdated gems" do + run_command + + expect(out).to include("Gem Current Latest Requested Groups") + expect(out).to include("rack 0.9.1 1.0.0 = 0.9.1 default") + end + end + + context "with --parseable" do + let(:flags) { "--parseable" } + + it "prints a message when there are outdated gems" do + run_command + + expect(out).to include("rack (newest 1.0.0, installed 0.9.1, requested = 0.9.1)") + end + end + + context "with --groups and --parseable" do + let(:flags) { "--groups --parseable" } + + it "prints a simplified message when there are outdated gems" do + run_command + + expect(out).to include("rack (newest 1.0.0, installed 0.9.1, requested = 0.9.1)") + end + end + end + describe "printing the outdated warning" do shared_examples_for "no warning" do it "prints no warning" do - bundle "fail", :env => { "BUNDLER_VERSION" => bundler_version }, :raise_on_error => false + bundle "fail", env: { "BUNDLER_VERSION" => bundler_version }, raise_on_error: false expect(last_command.stdboth).to eq("Could not find command \"fail\".") end end @@ -183,24 +213,24 @@ RSpec.describe "bundle executable" do context "when the latest version is greater than the current version" do let(:latest_version) { "222.0" } it "prints the version warning" do - bundle "fail", :env => { "BUNDLER_VERSION" => bundler_version }, :raise_on_error => false + bundle "fail", env: { "BUNDLER_VERSION" => bundler_version }, raise_on_error: false expect(err).to start_with(<<-EOS.strip) The latest bundler is #{latest_version}, but you are currently running #{bundler_version}. -To install the latest version, run `gem install bundler` +To update to the most recent version, run `bundle update --bundler` EOS end context "and disable_version_check is set" do - before { bundle "config set disable_version_check true", :env => { "BUNDLER_VERSION" => bundler_version } } + before { bundle "config set disable_version_check true", env: { "BUNDLER_VERSION" => bundler_version } } include_examples "no warning" end context "running a parseable command" do it "prints no warning" do - bundle "config get --parseable foo", :env => { "BUNDLER_VERSION" => bundler_version } + bundle "config get --parseable foo", env: { "BUNDLER_VERSION" => bundler_version } expect(last_command.stdboth).to eq "" - bundle "platform --ruby", :env => { "BUNDLER_VERSION" => bundler_version }, :raise_on_error => false + bundle "platform --ruby", env: { "BUNDLER_VERSION" => bundler_version }, raise_on_error: false expect(last_command.stdboth).to eq "Could not locate Gemfile" end end @@ -208,10 +238,10 @@ To install the latest version, run `gem install bundler` context "and is a pre-release" do let(:latest_version) { "222.0.0.pre.4" } it "prints the version warning" do - bundle "fail", :env => { "BUNDLER_VERSION" => bundler_version }, :raise_on_error => false + bundle "fail", env: { "BUNDLER_VERSION" => bundler_version }, raise_on_error: false expect(err).to start_with(<<-EOS.strip) The latest bundler is #{latest_version}, but you are currently running #{bundler_version}. -To install the latest version, run `gem install bundler --pre` +To update to the most recent version, run `bundle update --bundler` EOS end end @@ -220,12 +250,12 @@ To install the latest version, run `gem install bundler --pre` end RSpec.describe "bundler executable" do - it "shows the bundler version just as the `bundle` executable does", :bundler => "< 3" do + it "shows the bundler version just as the `bundle` executable does", bundler: "< 3" do bundler "--version" expect(out).to eq("Bundler version #{Bundler::VERSION}") end - it "shows the bundler version just as the `bundle` executable does", :bundler => "3" do + it "shows the bundler version just as the `bundle` executable does", bundler: "3" do bundler "--version" expect(out).to eq(Bundler::VERSION) end diff --git a/spec/bundler/bundler/compact_index_client/updater_spec.rb b/spec/bundler/bundler/compact_index_client/updater_spec.rb index 4acd7dbc63..6eed88ca9e 100644 --- a/spec/bundler/bundler/compact_index_client/updater_spec.rb +++ b/spec/bundler/bundler/compact_index_client/updater_spec.rb @@ -1,53 +1,224 @@ # frozen_string_literal: true -require "net/http" +require "bundler/vendored_net_http" require "bundler/compact_index_client" require "bundler/compact_index_client/updater" require "tmpdir" RSpec.describe Bundler::CompactIndexClient::Updater do + subject(:updater) { described_class.new(fetcher) } + let(:fetcher) { double(:fetcher) } - let(:local_path) { Pathname.new Dir.mktmpdir("localpath") } + let(:local_path) { Pathname.new(Dir.mktmpdir("localpath")).join("versions") } + let(:etag_path) { Pathname.new(Dir.mktmpdir("localpath-etags")).join("versions.etag") } let(:remote_path) { double(:remote_path) } - let!(:updater) { described_class.new(fetcher) } + let(:full_body) { "abc123" } + let(:response) { double(:response, body: full_body, is_a?: false) } + let(:digest) { Digest::SHA256.base64digest(full_body) } + + context "when the local path does not exist" do + before do + allow(response).to receive(:[]).with("Repr-Digest") { nil } + allow(response).to receive(:[]).with("Digest") { nil } + allow(response).to receive(:[]).with("ETag") { '"thisisanetag"' } + end + + it "downloads the file without attempting append" do + expect(fetcher).to receive(:call).once.with(remote_path, {}) { response } + + updater.update(remote_path, local_path, etag_path) + + expect(local_path.read).to eq(full_body) + expect(etag_path.read).to eq("thisisanetag") + end + + it "fails immediately on bad checksum" do + expect(fetcher).to receive(:call).once.with(remote_path, {}) { response } + allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:baddigest:" } + + expect do + updater.update(remote_path, local_path, etag_path) + end.to raise_error(Bundler::CompactIndexClient::Updater::MismatchedChecksumError) + end + end + + context "when the local path exists" do + let(:local_body) { "abc" } + + before do + local_path.open("w") {|f| f.write(local_body) } + end + + context "with an etag" do + before do + etag_path.open("w") {|f| f.write("LocalEtag") } + end + + let(:headers) do + { + "If-None-Match" => '"LocalEtag"', + "Range" => "bytes=2-", + } + end + + it "does nothing if etags match" do + expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response) + allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { false } + allow(response).to receive(:is_a?).with(Gem::Net::HTTPNotModified) { true } + + updater.update(remote_path, local_path, etag_path) + + expect(local_path.read).to eq("abc") + expect(etag_path.read).to eq("LocalEtag") + end + + it "appends the file if etags do not match" do + expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response) + allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" } + allow(response).to receive(:[]).with("ETag") { '"NewEtag"' } + allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { true } + allow(response).to receive(:is_a?).with(Gem::Net::HTTPNotModified) { false } + allow(response).to receive(:body) { "c123" } + + updater.update(remote_path, local_path, etag_path) + + expect(local_path.read).to eq(full_body) + expect(etag_path.read).to eq("NewEtag") + end + + it "replaces the file if response ignores range" do + expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response) + allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" } + allow(response).to receive(:[]).with("ETag") { '"NewEtag"' } + allow(response).to receive(:body) { full_body } + + updater.update(remote_path, local_path, etag_path) + + expect(local_path.read).to eq(full_body) + expect(etag_path.read).to eq("NewEtag") + end + + it "tries the request again if the partial response fails digest check" do + allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:baddigest:" } + allow(response).to receive(:body) { "the beginning of the file changed" } + allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { true } + expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response) + + full_response = double(:full_response, body: full_body, is_a?: false) + allow(full_response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" } + allow(full_response).to receive(:[]).with("ETag") { '"NewEtag"' } + expect(fetcher).to receive(:call).once.with(remote_path, { "If-None-Match" => '"LocalEtag"' }).and_return(full_response) + + updater.update(remote_path, local_path, etag_path) + + expect(local_path.read).to eq(full_body) + expect(etag_path.read).to eq("NewEtag") + end + end + + context "without an etag file" do + let(:headers) do + { + "Range" => "bytes=2-", + # This MD5 feature should be deleted after sufficient time has passed since release. + # From then on, requests that still don't have a saved etag will be made without this header. + "If-None-Match" => %("#{Digest::MD5.hexdigest(local_body)}"), + } + end + + it "saves only the etag_path if generated etag matches" do + expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response) + allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { false } + allow(response).to receive(:is_a?).with(Gem::Net::HTTPNotModified) { true } + + updater.update(remote_path, local_path, etag_path) + + expect(local_path.read).to eq("abc") + expect(%("#{etag_path.read}")).to eq(headers["If-None-Match"]) + end + + it "appends the file" do + expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response) + allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" } + allow(response).to receive(:[]).with("ETag") { '"OpaqueEtag"' } + allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { true } + allow(response).to receive(:is_a?).with(Gem::Net::HTTPNotModified) { false } + allow(response).to receive(:body) { "c123" } + + updater.update(remote_path, local_path, etag_path) + + expect(local_path.read).to eq(full_body) + expect(etag_path.read).to eq("OpaqueEtag") + end + + it "replaces the file on full file response that ignores range request" do + expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response) + allow(response).to receive(:[]).with("Repr-Digest") { nil } + allow(response).to receive(:[]).with("Digest") { nil } + allow(response).to receive(:[]).with("ETag") { '"OpaqueEtag"' } + allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { false } + allow(response).to receive(:is_a?).with(Gem::Net::HTTPNotModified) { false } + allow(response).to receive(:body) { full_body } + + updater.update(remote_path, local_path, etag_path) + + expect(local_path.read).to eq(full_body) + expect(etag_path.read).to eq("OpaqueEtag") + end + + it "tries the request again if the partial response fails digest check" do + allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:baddigest:" } + allow(response).to receive(:body) { "the beginning of the file changed" } + allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { true } + expect(fetcher).to receive(:call).once.with(remote_path, headers) do + # During the failed first request, we simulate another process writing the etag. + # This ensures the second request doesn't generate the md5 etag again but just uses whatever is written. + etag_path.open("w") {|f| f.write("LocalEtag") } + response + end + + full_response = double(:full_response, body: full_body, is_a?: false) + allow(full_response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" } + allow(full_response).to receive(:[]).with("ETag") { '"NewEtag"' } + expect(fetcher).to receive(:call).once.with(remote_path, { "If-None-Match" => '"LocalEtag"' }).and_return(full_response) + + updater.update(remote_path, local_path, etag_path) + + expect(local_path.read).to eq(full_body) + expect(etag_path.read).to eq("NewEtag") + end + end + end context "when the ETag header is missing" do # Regression test for https://github.com/rubygems/bundler/issues/5463 - let(:response) { double(:response, :body => "abc123") } + let(:response) { double(:response, body: full_body) } it "treats the response as an update" do - expect(response).to receive(:[]).with("ETag") { nil } + allow(response).to receive(:[]).with("Repr-Digest") { nil } + allow(response).to receive(:[]).with("Digest") { nil } + allow(response).to receive(:[]).with("ETag") { nil } expect(fetcher).to receive(:call) { response } - updater.update(local_path, remote_path) + updater.update(remote_path, local_path, etag_path) end end context "when the download is corrupt" do - let(:response) { double(:response, :body => "") } + let(:response) { double(:response, body: "") } it "raises HTTPError" do expect(fetcher).to receive(:call).and_raise(Zlib::GzipFile::Error) expect do - updater.update(local_path, remote_path) + updater.update(remote_path, local_path, etag_path) end.to raise_error(Bundler::HTTPError) end end - context "when bundler doesn't have permissions on Dir.tmpdir" do - it "Errno::EACCES is raised" do - allow(Bundler::Dir).to receive(:mktmpdir) { raise Errno::EACCES } - - expect do - updater.update(local_path, remote_path) - end.to raise_error(Bundler::PermissionError) - end - end - context "when receiving non UTF-8 data and default internal encoding set to ASCII" do - let(:response) { double(:response, :body => "\x8B".b) } + let(:response) { double(:response, body: "\x8B".b) } it "works just fine" do old_verbose = $VERBOSE @@ -56,10 +227,12 @@ RSpec.describe Bundler::CompactIndexClient::Updater do begin $VERBOSE = false Encoding.default_internal = "ASCII" - expect(response).to receive(:[]).with("ETag") { nil } + allow(response).to receive(:[]).with("Repr-Digest") { nil } + allow(response).to receive(:[]).with("Digest") { nil } + allow(response).to receive(:[]).with("ETag") { nil } expect(fetcher).to receive(:call) { response } - updater.update(local_path, remote_path) + updater.update(remote_path, local_path, etag_path) ensure Encoding.default_internal = previous_internal_encoding $VERBOSE = old_verbose diff --git a/spec/bundler/bundler/definition_spec.rb b/spec/bundler/bundler/definition_spec.rb index bf000c468a..28c04e0860 100644 --- a/spec/bundler/bundler/definition_spec.rb +++ b/spec/bundler/bundler/definition_spec.rb @@ -5,61 +5,63 @@ require "bundler/definition" RSpec.describe Bundler::Definition do describe "#lock" do before do - allow(Bundler).to receive(:settings) { Bundler::Settings.new(".") } - allow(Bundler::SharedHelpers).to receive(:find_gemfile) { Pathname.new("Gemfile") } - allow(Bundler).to receive(:ui) { double("UI", :info => "", :debug => "") } + allow(Bundler::SharedHelpers).to receive(:find_gemfile) { bundled_app_gemfile } + allow(Bundler).to receive(:ui) { double("UI", info: "", debug: "") } end - context "when it's not possible to write to the file" do - subject { Bundler::Definition.new(nil, [], Bundler::SourceList.new, []) } + subject { Bundler::Definition.new(bundled_app_lock, [], Bundler::SourceList.new, {}) } + + context "when it's not possible to write to the file" do it "raises an PermissionError with explanation" do allow(File).to receive(:open).and_call_original - expect(File).to receive(:open).with("Gemfile.lock", "wb"). + expect(File).to receive(:open).with(bundled_app_lock, "wb"). and_raise(Errno::EACCES) - expect { subject.lock("Gemfile.lock") }. + expect { subject.lock }. to raise_error(Bundler::PermissionError, /Gemfile\.lock/) end end context "when a temporary resource access issue occurs" do - subject { Bundler::Definition.new(nil, [], Bundler::SourceList.new, []) } - it "raises a TemporaryResourceError with explanation" do allow(File).to receive(:open).and_call_original - expect(File).to receive(:open).with("Gemfile.lock", "wb"). + expect(File).to receive(:open).with(bundled_app_lock, "wb"). and_raise(Errno::EAGAIN) - expect { subject.lock("Gemfile.lock") }. + expect { subject.lock }. to raise_error(Bundler::TemporaryResourceError, /temporarily unavailable/) end end context "when Bundler::Definition.no_lock is set to true" do - subject { Bundler::Definition.new(nil, [], Bundler::SourceList.new, []) } before { Bundler::Definition.no_lock = true } after { Bundler::Definition.no_lock = false } it "does not create a lock file" do - subject.lock("Gemfile.lock") - expect(File.file?("Gemfile.lock")).to eq false + subject.lock + expect(bundled_app_lock).not_to be_file end end end describe "detects changes" do it "for a path gem with changes" do - build_lib "foo", "1.0", :path => lib_path("foo") + build_lib "foo", "1.0", path: lib_path("foo") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("foo")}" G - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.add_dependency "rack", "1.0" end - bundle :install, :env => { "DEBUG" => "1" } + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + c.checksum gem_repo1, "rack", "1.0.0" + end + + bundle :install, env: { "DEBUG" => "1" } expect(out).to match(/re-resolving dependencies/) - lockfile_should_be <<-G + expect(lockfile).to eq <<~G PATH remote: #{lib_path("foo")} specs: @@ -76,27 +78,47 @@ RSpec.describe Bundler::Definition do DEPENDENCIES foo! - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G end + it "with an explicit update" do + build_repo4 do + build_gem("ffi", "1.9.23") {|s| s.platform = "java" } + build_gem("ffi", "1.9.23") + end + + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "ffi" + G + + bundle "lock --add-platform java" + + bundle "update ffi", env: { "DEBUG" => "1" } + + expect(out).to match(/because bundler is unlocking gems: \(ffi\)/) + end + it "for a path gem with deps and no changes" do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.add_dependency "rack", "1.0" s.add_development_dependency "net-ssh", "1.0" end + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + c.checksum gem_repo1, "rack", "1.0.0" + end + install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("foo")}" G - bundle :check, :env => { "DEBUG" => "1" } - - expect(out).to match(/using resolution from the lockfile/) - lockfile_should_be <<-G + expected_lockfile = <<~G PATH remote: #{lib_path("foo")} specs: @@ -113,50 +135,64 @@ RSpec.describe Bundler::Definition do DEPENDENCIES foo! - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G + + expect(lockfile).to eq(expected_lockfile) + + bundle :check, env: { "DEBUG" => "1" } + + expect(out).to match(/using resolution from the lockfile/) + expect(lockfile).to eq(expected_lockfile) end it "for a locked gem for another platform" do + checksums = checksums_section_when_existing do |c| + c.no_checksum "only_java", "1.1", "java" + end + install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "only_java", platform: :jruby G bundle "lock --add-platform java" - bundle :check, :env => { "DEBUG" => "1" } + bundle :check, env: { "DEBUG" => "1" } expect(out).to match(/using resolution from the lockfile/) - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo1)}/ specs: only_java (1.1-java) PLATFORMS - java - #{lockfile_platforms} + #{lockfile_platforms("java")} DEPENDENCIES only_java - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G end it "for a rubygems gem" do + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo1, "foo", "1.0" + end + install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "foo" G - bundle :check, :env => { "DEBUG" => "1" } + bundle :check, env: { "DEBUG" => "1" } expect(out).to match(/using resolution from the lockfile/) - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo1)}/ specs: @@ -167,7 +203,7 @@ RSpec.describe Bundler::Definition do DEPENDENCIES foo - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G @@ -176,31 +212,6 @@ RSpec.describe Bundler::Definition do describe "initialize" do context "gem version promoter" do - context "with lockfile" do - before do - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "foo" - G - - allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) - end - - it "should get a locked specs list when updating all" do - definition = Bundler::Definition.new(bundled_app_lock, [], Bundler::SourceList.new, true) - locked_specs = definition.gem_version_promoter.locked_specs - expect(locked_specs.to_a.map(&:name)).to eq ["foo"] - expect(definition.instance_variable_get("@locked_specs").empty?).to eq true - end - end - - context "without gemfile or lockfile" do - it "should not attempt to parse empty lockfile contents" do - definition = Bundler::Definition.new(nil, [], mock_source_list, true) - expect(definition.gem_version_promoter.locked_specs.to_a).to eq [] - end - end - context "eager unlock" do let(:source_list) do Bundler::SourceList.new.tap do |source_list| @@ -268,7 +279,7 @@ RSpec.describe Bundler::Definition do bundled_app_lock, updated_deps_in_gemfile, source_list, - :gems => ["shared_owner_a"], :conservative => true + gems: ["shared_owner_a"], conservative: true ) locked = definition.send(:converge_locked_specs).map(&:name) expect(locked).to eq %w[isolated_dep isolated_owner shared_dep shared_owner_b] diff --git a/spec/bundler/bundler/dep_proxy_spec.rb b/spec/bundler/bundler/dep_proxy_spec.rb deleted file mode 100644 index 8d02a33725..0000000000 --- a/spec/bundler/bundler/dep_proxy_spec.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe Bundler::DepProxy do - let(:dep) { Bundler::Dependency.new("rake", ">= 0") } - subject { described_class.get_proxy(dep, Gem::Platform::RUBY) } - let(:same) { subject } - let(:other) { described_class.get_proxy(dep, Gem::Platform::RUBY) } - let(:different) { described_class.get_proxy(dep, Gem::Platform::JAVA) } - - describe "#eql?" do - it { expect(subject.eql?(same)).to be true } - it { expect(subject.eql?(other)).to be true } - it { expect(subject.eql?(different)).to be false } - it { expect(subject.eql?(nil)).to be false } - it { expect(subject.eql?("foobar")).to be false } - end - - describe "must use factory methods" do - it { expect { described_class.new(dep, Gem::Platform::RUBY) }.to raise_error NoMethodError } - it { expect { subject.dup }.to raise_error NoMethodError } - it { expect { subject.clone }.to raise_error NoMethodError } - end - - describe "frozen" do - if Gem.ruby_version >= Gem::Version.new("2.5.0") - error = Object.const_get("FrozenError") - else - error = RuntimeError - end - it { expect { subject.instance_variable_set(:@__platform, {}) }.to raise_error error } - end -end diff --git a/spec/bundler/bundler/dependency_spec.rb b/spec/bundler/bundler/dependency_spec.rb new file mode 100644 index 0000000000..a953372742 --- /dev/null +++ b/spec/bundler/bundler/dependency_spec.rb @@ -0,0 +1,167 @@ +# frozen_string_literal: true + +RSpec.describe Bundler::Dependency do + let(:options) do + {} + end + let(:dependency) do + described_class.new( + "test_gem", + "1.0.0", + options + ) + end + + describe "to_lock" do + it "returns formatted string" do + expect(dependency.to_lock).to eq(" test_gem (= 1.0.0)") + end + + it "matches format of Gem::Dependency#to_lock" do + gem_dependency = Gem::Dependency.new("test_gem", "1.0.0") + expect(dependency.to_lock).to eq(gem_dependency.to_lock) + end + + context "when source is passed" do + let(:options) do + { + "source" => Bundler::Source::Git.new({}), + } + end + + it "returns formatted string with exclamation mark" do + expect(dependency.to_lock).to eq(" test_gem (= 1.0.0)!") + end + end + end + + describe "PLATFORM_MAP" do + subject { described_class::PLATFORM_MAP } + + # rubocop:disable Naming/VariableNumber + let(:platforms) do + { ruby: Gem::Platform::RUBY, + ruby_18: Gem::Platform::RUBY, + ruby_19: Gem::Platform::RUBY, + ruby_20: Gem::Platform::RUBY, + ruby_21: Gem::Platform::RUBY, + ruby_22: Gem::Platform::RUBY, + ruby_23: Gem::Platform::RUBY, + ruby_24: Gem::Platform::RUBY, + ruby_25: Gem::Platform::RUBY, + ruby_26: Gem::Platform::RUBY, + ruby_27: Gem::Platform::RUBY, + ruby_30: Gem::Platform::RUBY, + ruby_31: Gem::Platform::RUBY, + ruby_32: Gem::Platform::RUBY, + ruby_33: Gem::Platform::RUBY, + ruby_34: Gem::Platform::RUBY, + mri: Gem::Platform::RUBY, + mri_18: Gem::Platform::RUBY, + mri_19: Gem::Platform::RUBY, + mri_20: Gem::Platform::RUBY, + mri_21: Gem::Platform::RUBY, + mri_22: Gem::Platform::RUBY, + mri_23: Gem::Platform::RUBY, + mri_24: Gem::Platform::RUBY, + mri_25: Gem::Platform::RUBY, + mri_26: Gem::Platform::RUBY, + mri_27: Gem::Platform::RUBY, + mri_30: Gem::Platform::RUBY, + mri_31: Gem::Platform::RUBY, + mri_32: Gem::Platform::RUBY, + mri_33: Gem::Platform::RUBY, + mri_34: Gem::Platform::RUBY, + rbx: Gem::Platform::RUBY, + truffleruby: Gem::Platform::RUBY, + jruby: Gem::Platform::JAVA, + jruby_18: Gem::Platform::JAVA, + jruby_19: Gem::Platform::JAVA, + windows: Gem::Platform::WINDOWS, + windows_18: Gem::Platform::WINDOWS, + windows_19: Gem::Platform::WINDOWS, + windows_20: Gem::Platform::WINDOWS, + windows_21: Gem::Platform::WINDOWS, + windows_22: Gem::Platform::WINDOWS, + windows_23: Gem::Platform::WINDOWS, + windows_24: Gem::Platform::WINDOWS, + windows_25: Gem::Platform::WINDOWS, + windows_26: Gem::Platform::WINDOWS, + windows_27: Gem::Platform::WINDOWS, + windows_30: Gem::Platform::WINDOWS, + windows_31: Gem::Platform::WINDOWS, + windows_32: Gem::Platform::WINDOWS, + windows_33: Gem::Platform::WINDOWS, + windows_34: Gem::Platform::WINDOWS } + end + + let(:deprecated) do + { mswin: Gem::Platform::MSWIN, + mswin_18: Gem::Platform::MSWIN, + mswin_19: Gem::Platform::MSWIN, + mswin_20: Gem::Platform::MSWIN, + mswin_21: Gem::Platform::MSWIN, + mswin_22: Gem::Platform::MSWIN, + mswin_23: Gem::Platform::MSWIN, + mswin_24: Gem::Platform::MSWIN, + mswin_25: Gem::Platform::MSWIN, + mswin_26: Gem::Platform::MSWIN, + mswin_27: Gem::Platform::MSWIN, + mswin_30: Gem::Platform::MSWIN, + mswin_31: Gem::Platform::MSWIN, + mswin_32: Gem::Platform::MSWIN, + mswin_33: Gem::Platform::MSWIN, + mswin_34: Gem::Platform::MSWIN, + mswin64: Gem::Platform::MSWIN64, + mswin64_19: Gem::Platform::MSWIN64, + mswin64_20: Gem::Platform::MSWIN64, + mswin64_21: Gem::Platform::MSWIN64, + mswin64_22: Gem::Platform::MSWIN64, + mswin64_23: Gem::Platform::MSWIN64, + mswin64_24: Gem::Platform::MSWIN64, + mswin64_25: Gem::Platform::MSWIN64, + mswin64_26: Gem::Platform::MSWIN64, + mswin64_27: Gem::Platform::MSWIN64, + mswin64_30: Gem::Platform::MSWIN64, + mswin64_31: Gem::Platform::MSWIN64, + mswin64_32: Gem::Platform::MSWIN64, + mswin64_33: Gem::Platform::MSWIN64, + mswin64_34: Gem::Platform::MSWIN64, + mingw: Gem::Platform::MINGW, + mingw_18: Gem::Platform::MINGW, + mingw_19: Gem::Platform::MINGW, + mingw_20: Gem::Platform::MINGW, + mingw_21: Gem::Platform::MINGW, + mingw_22: Gem::Platform::MINGW, + mingw_23: Gem::Platform::MINGW, + mingw_24: Gem::Platform::MINGW, + mingw_25: Gem::Platform::MINGW, + mingw_26: Gem::Platform::MINGW, + mingw_27: Gem::Platform::MINGW, + mingw_30: Gem::Platform::MINGW, + mingw_31: Gem::Platform::MINGW, + mingw_32: Gem::Platform::MINGW, + mingw_33: Gem::Platform::MINGW, + mingw_34: Gem::Platform::MINGW, + x64_mingw: Gem::Platform::X64_MINGW, + x64_mingw_20: Gem::Platform::X64_MINGW, + x64_mingw_21: Gem::Platform::X64_MINGW, + x64_mingw_22: Gem::Platform::X64_MINGW, + x64_mingw_23: Gem::Platform::X64_MINGW, + x64_mingw_24: Gem::Platform::X64_MINGW, + x64_mingw_25: Gem::Platform::X64_MINGW, + x64_mingw_26: Gem::Platform::X64_MINGW, + x64_mingw_27: Gem::Platform::X64_MINGW, + x64_mingw_30: Gem::Platform::X64_MINGW, + x64_mingw_31: Gem::Platform::X64_MINGW, + x64_mingw_32: Gem::Platform::X64_MINGW, + x64_mingw_33: Gem::Platform::X64_MINGW, + x64_mingw_34: Gem::Platform::X64_MINGW } + end + # rubocop:enable Naming/VariableNumber + + it "includes all platforms" do + expect(subject).to eq(platforms.merge(deprecated)) + end + end +end diff --git a/spec/bundler/bundler/digest_spec.rb b/spec/bundler/bundler/digest_spec.rb new file mode 100644 index 0000000000..f876827964 --- /dev/null +++ b/spec/bundler/bundler/digest_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "openssl" +require "bundler/digest" + +RSpec.describe Bundler::Digest do + context "SHA1" do + subject { Bundler::Digest } + let(:stdlib) { OpenSSL::Digest::SHA1 } + + it "is compatible with stdlib" do + random_strings = ["foo", "skfjsdlkfjsdf", "3924m", "ldskfj"] + + # https://www.rfc-editor.org/rfc/rfc3174#section-7.3 + rfc3174_test_cases = ["abc", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "a", "01234567" * 8] + + (random_strings + rfc3174_test_cases).each do |payload| + sha1 = subject.sha1(payload) + sha1_stdlib = stdlib.hexdigest(payload) + expect(sha1).to be == sha1_stdlib, "#{payload}'s sha1 digest (#{sha1}) did not match stlib's result (#{sha1_stdlib})" + end + end + end +end diff --git a/spec/bundler/bundler/dsl_spec.rb b/spec/bundler/bundler/dsl_spec.rb index e6cd43ab59..3c3b6c26c3 100644 --- a/spec/bundler/bundler/dsl_spec.rb +++ b/spec/bundler/bundler/dsl_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Bundler::Dsl do it "registers custom hosts" do subject.git_source(:example) {|repo_name| "git@git.example.com:#{repo_name}.git" } subject.git_source(:foobar) {|repo_name| "git@foobar.com:#{repo_name}.git" } - subject.gem("dobry-pies", :example => "strzalek/dobry-pies") + subject.gem("dobry-pies", example: "strzalek/dobry-pies") example_uri = "git@git.example.com:strzalek/dobry-pies.git" expect(subject.dependencies.first.source.uri).to eq(example_uri) end @@ -25,47 +25,137 @@ RSpec.describe Bundler::Dsl do expect { subject.git_source(:example) }.to raise_error(Bundler::InvalidOption) end - context "default hosts", :bundler => "< 3" do + it "converts :github PR to URI using https" do + subject.gem("sparks", github: "https://github.com/indirect/sparks/pull/5") + github_uri = "https://github.com/indirect/sparks.git" + expect(subject.dependencies.first.source.uri).to eq(github_uri) + expect(subject.dependencies.first.source.ref).to eq("refs/pull/5/head") + end + + it "converts :gitlab PR to URI using https" do + subject.gem("sparks", gitlab: "https://gitlab.com/indirect/sparks/-/merge_requests/5") + gitlab_uri = "https://gitlab.com/indirect/sparks.git" + expect(subject.dependencies.first.source.uri).to eq(gitlab_uri) + expect(subject.dependencies.first.source.ref).to eq("refs/merge-requests/5/head") + end + + it "rejects :github PR URI with a branch, ref or tag" do + expect do + subject.gem("sparks", github: "https://github.com/indirect/sparks/pull/5", branch: "foo") + end.to raise_error( + Bundler::GemfileError, + %(The :branch option can't be used with `github: "https://github.com/indirect/sparks/pull/5"`), + ) + + expect do + subject.gem("sparks", github: "https://github.com/indirect/sparks/pull/5", ref: "foo") + end.to raise_error( + Bundler::GemfileError, + %(The :ref option can't be used with `github: "https://github.com/indirect/sparks/pull/5"`), + ) + + expect do + subject.gem("sparks", github: "https://github.com/indirect/sparks/pull/5", tag: "foo") + end.to raise_error( + Bundler::GemfileError, + %(The :tag option can't be used with `github: "https://github.com/indirect/sparks/pull/5"`), + ) + end + + it "rejects :gitlab PR URI with a branch, ref or tag" do + expect do + subject.gem("sparks", gitlab: "https://gitlab.com/indirect/sparks/-/merge_requests/5", branch: "foo") + end.to raise_error( + Bundler::GemfileError, + %(The :branch option can't be used with `gitlab: "https://gitlab.com/indirect/sparks/-/merge_requests/5"`), + ) + + expect do + subject.gem("sparks", gitlab: "https://gitlab.com/indirect/sparks/-/merge_requests/5", ref: "foo") + end.to raise_error( + Bundler::GemfileError, + %(The :ref option can't be used with `gitlab: "https://gitlab.com/indirect/sparks/-/merge_requests/5"`), + ) + + expect do + subject.gem("sparks", gitlab: "https://gitlab.com/indirect/sparks/-/merge_requests/5", tag: "foo") + end.to raise_error( + Bundler::GemfileError, + %(The :tag option can't be used with `gitlab: "https://gitlab.com/indirect/sparks/-/merge_requests/5"`), + ) + end + + it "rejects :github with :git" do + expect do + subject.gem("sparks", github: "indirect/sparks", git: "https://github.com/indirect/sparks.git") + end.to raise_error( + Bundler::GemfileError, + %(The :git option can't be used with `github: "indirect/sparks"`), + ) + end + + it "rejects :gitlab with :git" do + expect do + subject.gem("sparks", gitlab: "indirect/sparks", git: "https://gitlab.com/indirect/sparks.git") + end.to raise_error( + Bundler::GemfileError, + %(The :git option can't be used with `gitlab: "indirect/sparks"`), + ) + end + + context "default hosts", bundler: "< 3" do it "converts :github to URI using https" do - subject.gem("sparks", :github => "indirect/sparks") + subject.gem("sparks", github: "indirect/sparks") github_uri = "https://github.com/indirect/sparks.git" expect(subject.dependencies.first.source.uri).to eq(github_uri) end it "converts :github shortcut to URI using https" do - subject.gem("sparks", :github => "rails") + subject.gem("sparks", github: "rails") github_uri = "https://github.com/rails/rails.git" expect(subject.dependencies.first.source.uri).to eq(github_uri) end + it "converts :gitlab to URI using https" do + subject.gem("sparks", gitlab: "indirect/sparks") + gitlab_uri = "https://gitlab.com/indirect/sparks.git" + expect(subject.dependencies.first.source.uri).to eq(gitlab_uri) + end + + it "converts :gitlab shortcut to URI using https" do + subject.gem("sparks", gitlab: "rails") + gitlab_uri = "https://gitlab.com/rails/rails.git" + expect(subject.dependencies.first.source.uri).to eq(gitlab_uri) + end + it "converts numeric :gist to :git" do - subject.gem("not-really-a-gem", :gist => 2_859_988) + subject.gem("not-really-a-gem", gist: 2_859_988) github_uri = "https://gist.github.com/2859988.git" expect(subject.dependencies.first.source.uri).to eq(github_uri) end it "converts :gist to :git" do - subject.gem("not-really-a-gem", :gist => "2859988") + subject.gem("not-really-a-gem", gist: "2859988") github_uri = "https://gist.github.com/2859988.git" expect(subject.dependencies.first.source.uri).to eq(github_uri) end it "converts :bitbucket to :git" do - subject.gem("not-really-a-gem", :bitbucket => "mcorp/flatlab-rails") + subject.gem("not-really-a-gem", bitbucket: "mcorp/flatlab-rails") bitbucket_uri = "https://mcorp@bitbucket.org/mcorp/flatlab-rails.git" expect(subject.dependencies.first.source.uri).to eq(bitbucket_uri) end it "converts 'mcorp' to 'mcorp/mcorp'" do - subject.gem("not-really-a-gem", :bitbucket => "mcorp") + subject.gem("not-really-a-gem", bitbucket: "mcorp") bitbucket_uri = "https://mcorp@bitbucket.org/mcorp/mcorp.git" expect(subject.dependencies.first.source.uri).to eq(bitbucket_uri) end end context "default git sources" do - it "has bitbucket, gist, and github" do - expect(subject.instance_variable_get(:@git_sources).keys.sort).to eq(%w[bitbucket gist github]) + it "has bitbucket, gist, github, and gitlab" do + expect(subject.instance_variable_get(:@git_sources).keys.sort).to eq(%w[bitbucket gist github gitlab]) end end end @@ -95,18 +185,37 @@ RSpec.describe Bundler::Dsl do expect { subject.eval_gemfile("Gemfile") }. to raise_error(Bundler::GemfileError, /There was an error evaluating `Gemfile`: ruby_version must match the :engine_version for MRI/) end + + it "populates __dir__ and __FILE__ correctly" do + abs_path = source_root.join("../fragment.rb").to_s + expect(Bundler).to receive(:read_file).with(abs_path).and_return(<<~RUBY) + @fragment_dir = __dir__ + @fragment_file = __FILE__ + RUBY + subject.eval_gemfile("../fragment.rb") + expect(subject.instance_variable_get(:@fragment_dir)).to eq(source_root.dirname.to_s) + expect(subject.instance_variable_get(:@fragment_file)).to eq(abs_path) + end end describe "#gem" do - [:ruby, :ruby_18, :ruby_19, :ruby_20, :ruby_21, :ruby_22, :ruby_23, :ruby_24, :ruby_25, :ruby_26, :mri, :mri_18, :mri_19, - :mri_20, :mri_21, :mri_22, :mri_23, :mri_24, :mri_25, :mri_26, :jruby, :rbx, :truffleruby].each do |platform| + # rubocop:disable Naming/VariableNumber + [:ruby, :ruby_18, :ruby_19, :ruby_20, :ruby_21, :ruby_22, :ruby_23, :ruby_24, :ruby_25, :ruby_26, :ruby_27, + :ruby_30, :ruby_31, :ruby_32, :ruby_33, :mri, :mri_18, :mri_19, :mri_20, :mri_21, :mri_22, :mri_23, :mri_24, + :mri_25, :mri_26, :mri_27, :mri_30, :mri_31, :mri_32, :mri_33, :jruby, :rbx, :truffleruby].each do |platform| it "allows #{platform} as a valid platform" do - subject.gem("foo", :platform => platform) + subject.gem("foo", platform: platform) end end + # rubocop:enable Naming/VariableNumber + + it "allows platforms matching the running Ruby version" do + platform = "ruby_#{RbConfig::CONFIG["MAJOR"]}#{RbConfig::CONFIG["MINOR"]}" + subject.gem("foo", platform: platform) + end it "rejects invalid platforms" do - expect { subject.gem("foo", :platform => :bogus) }. + expect { subject.gem("foo", platform: :bogus) }. to raise_error(Bundler::GemfileError, /is not a valid platform/) end @@ -156,19 +265,19 @@ RSpec.describe Bundler::Dsl do end it "rejects branch option on non-git gems" do - expect { subject.gem("foo", :branch => "test") }. + expect { subject.gem("foo", branch: "test") }. to raise_error(Bundler::GemfileError, /The `branch` option for `gem 'foo'` is not allowed. Only gems with a git source can specify a branch/) end it "allows specifying a branch on git gems" do - subject.gem("foo", :branch => "test", :git => "http://mytestrepo") + subject.gem("foo", branch: "test", git: "http://mytestrepo") dep = subject.dependencies.last expect(dep.name).to eq "foo" end it "allows specifying a branch on git gems with a git_source" do subject.git_source(:test_source) {|n| "https://github.com/#{n}" } - subject.gem("foo", :branch => "test", :test_source => "bundler/bundler") + subject.gem("foo", branch: "test", test_source: "bundler/bundler") dep = subject.dependencies.last expect(dep.name).to eq "foo" end @@ -233,7 +342,7 @@ RSpec.describe Bundler::Dsl do allow(Bundler).to receive(:default_gemfile).and_return(Pathname.new("./Gemfile")) subject.source("https://other-source.org") do - subject.gem("dobry-pies", :path => "foo") + subject.gem("dobry-pies", path: "foo") subject.gem("foo") end @@ -245,12 +354,12 @@ RSpec.describe Bundler::Dsl do describe "#check_primary_source_safety" do context "when a global source is not defined implicitly" do it "will raise a major deprecation warning" do - not_a_global_source = double("not-a-global-source", :no_remotes? => true) + not_a_global_source = double("not-a-global-source", no_remotes?: true) allow(Bundler::Source::Rubygems).to receive(:new).and_return(not_a_global_source) warning = "This Gemfile does not include an explicit global source. " \ "Not using an explicit global source may result in a different lockfile being generated depending on " \ - "the gems you have installed locally before bundler is run." \ + "the gems you have installed locally before bundler is run. " \ "Instead, define a global source in your Gemfile like this: source \"https://rubygems.org\"." expect(Bundler::SharedHelpers).to receive(:major_deprecation).with(2, warning) diff --git a/spec/bundler/bundler/endpoint_specification_spec.rb b/spec/bundler/bundler/endpoint_specification_spec.rb index a9371f6617..e7e10730cf 100644 --- a/spec/bundler/bundler/endpoint_specification_spec.rb +++ b/spec/bundler/bundler/endpoint_specification_spec.rb @@ -5,9 +5,10 @@ RSpec.describe Bundler::EndpointSpecification do let(:version) { "1.0.0" } let(:platform) { Gem::Platform::RUBY } let(:dependencies) { [] } + let(:spec_fetcher) { double(:spec_fetcher) } let(:metadata) { nil } - subject(:spec) { described_class.new(name, version, platform, dependencies, metadata) } + subject(:spec) { described_class.new(name, version, platform, spec_fetcher, dependencies, metadata) } describe "#build_dependency" do let(:name) { "foo" } @@ -32,22 +33,6 @@ RSpec.describe Bundler::EndpointSpecification do ) end end - - context "when there is an ill formed requirement" do - before do - allow(Gem::Dependency).to receive(:new).with(name, [requirement1, requirement2]) { - raise ArgumentError.new("Ill-formed requirement [\"#<YAML::Syck::DefaultKey") - } - # Eliminate extra line break in rspec output due to `puts` in `#build_dependency` - allow(subject).to receive(:puts) {} - end - - it "should raise a Bundler::GemspecError with invalid gemspec message" do - expect { subject.send(:build_dependency, name, [requirement1, requirement2]) }.to raise_error( - Bundler::GemspecError, /Unfortunately, the gem foo \(1\.0\.0\) has an invalid gemspec/ - ) - end - end end describe "#parse_metadata" do @@ -63,8 +48,35 @@ RSpec.describe Bundler::EndpointSpecification do end end + describe "#required_ruby_version" do + context "required_ruby_version is already set on endpoint specification" do + existing_value = "already set value" + let(:required_ruby_version) { existing_value } + + it "should return the current value when already set on endpoint specification" do + expect(spec.required_ruby_version). eql?(existing_value) + end + end + + it "should return the remote spec value when not set on endpoint specification and remote spec has one" do + remote_value = "remote_value" + remote_spec = double(:remote_spec, required_ruby_version: remote_value, required_rubygems_version: nil) + allow(spec_fetcher).to receive(:fetch_spec).and_return(remote_spec) + + expect(spec.required_ruby_version). eql?(remote_value) + end + + it "should use the default Gem Requirement value when not set on endpoint specification and not set on remote spec" do + remote_spec = double(:remote_spec, required_ruby_version: nil, required_rubygems_version: nil) + allow(spec_fetcher).to receive(:fetch_spec).and_return(remote_spec) + expect(spec.required_ruby_version). eql?(Gem::Requirement.default) + end + end + it "supports equality comparison" do - other_spec = described_class.new("bar", version, platform, dependencies, metadata) + remote_spec = double(:remote_spec, required_ruby_version: nil, required_rubygems_version: nil) + allow(spec_fetcher).to receive(:fetch_spec).and_return(remote_spec) + other_spec = described_class.new("bar", version, platform, spec_fetcher, dependencies, metadata) expect(spec).to eql(spec) expect(spec).to_not eql(other_spec) end diff --git a/spec/bundler/bundler/env_spec.rb b/spec/bundler/bundler/env_spec.rb index 5d3aeec226..7997cb0c40 100644 --- a/spec/bundler/bundler/env_spec.rb +++ b/spec/bundler/bundler/env_spec.rb @@ -4,7 +4,7 @@ require "bundler/settings" require "openssl" RSpec.describe Bundler::Env do - let(:git_proxy_stub) { Bundler::Source::Git::GitProxy.new(nil, nil, nil) } + let(:git_proxy_stub) { Bundler::Source::Git::GitProxy.new(nil, nil) } describe "#report" do it "prints the environment" do @@ -34,8 +34,6 @@ RSpec.describe Bundler::Env do end it "prints user home" do - skip "needs to use a valid HOME" if Gem.win_platform? && RUBY_VERSION < "2.6.0" - with_clear_paths("HOME", "/a/b/c") do out = described_class.report expect(out).to include("User Home /a/b/c") @@ -43,8 +41,6 @@ RSpec.describe Bundler::Env do end it "prints user path" do - skip "needs to use a valid HOME" if Gem.win_platform? && RUBY_VERSION < "2.6.0" - with_clear_paths("HOME", "/a/b/c") do allow(File).to receive(:exist?) allow(File).to receive(:exist?).with("/a/b/c/.gem").and_return(true) @@ -92,7 +88,7 @@ RSpec.describe Bundler::Env do allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) end - let(:output) { described_class.report(:print_gemfile => true) } + let(:output) { described_class.report(print_gemfile: true) } it "prints the Gemfile" do expect(output).to include("Gemfile") @@ -106,7 +102,7 @@ RSpec.describe Bundler::Env do end context "when there no Gemfile and print_gemfile is true" do - let(:output) { described_class.report(:print_gemfile => true) } + let(:output) { described_class.report(print_gemfile: true) } it "prints the environment" do expect(output).to start_with("## Environment") @@ -118,7 +114,7 @@ RSpec.describe Bundler::Env do bundle "config set https://localgemserver.test/ user:pass" end - let(:output) { described_class.report(:print_gemfile => true) } + let(:output) { described_class.report(print_gemfile: true) } it "prints the config with redacted values" do expect(output).to include("https://localgemserver.test") @@ -127,9 +123,23 @@ RSpec.describe Bundler::Env do end end + context "when there's bundler config with OAuth token credentials" do + before do + bundle "config set https://localgemserver.test/ api_token:x-oauth-basic" + end + + let(:output) { described_class.report(print_gemfile: true) } + + it "prints the config with redacted values" do + expect(output).to include("https://localgemserver.test") + expect(output).to include("[REDACTED]:x-oauth-basic") + expect(output).to_not include("api_token:x-oauth-basic") + end + end + context "when Gemfile contains a gemspec and print_gemspecs is true" do let(:gemspec) do - strip_whitespace(<<-GEMSPEC) + <<~GEMSPEC Gem::Specification.new do |gem| gem.name = "foo" gem.author = "Fumofu" @@ -148,7 +158,7 @@ RSpec.describe Bundler::Env do end it "prints the gemspec" do - output = described_class.report(:print_gemspecs => true) + output = described_class.report(print_gemspecs: true) expect(output).to include("foo.gemspec") expect(output).to include(gemspec) @@ -167,8 +177,8 @@ RSpec.describe Bundler::Env do allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) allow(Bundler::SharedHelpers).to receive(:pwd).and_return(bundled_app) - output = described_class.report(:print_gemspecs => true) - expect(output).to include(strip_whitespace(<<-ENV)) + output = described_class.report(print_gemspecs: true) + expect(output).to include(<<~ENV) ## Gemfile ### Gemfile @@ -207,7 +217,7 @@ RSpec.describe Bundler::Env do context "when the git version is OS specific" do it "includes OS specific information with the version number" do - expect(git_proxy_stub).to receive(:git).with("--version"). + expect(git_proxy_stub).to receive(:git_local).with("--version"). and_return("git version 1.2.3 (Apple Git-BS)") expect(Bundler::Source::Git::GitProxy).to receive(:new).and_return(git_proxy_stub) diff --git a/spec/bundler/bundler/environment_preserver_spec.rb b/spec/bundler/bundler/environment_preserver_spec.rb index 530ca6f835..6c7066d0c6 100644 --- a/spec/bundler/bundler/environment_preserver_spec.rb +++ b/spec/bundler/bundler/environment_preserver_spec.rb @@ -27,8 +27,12 @@ RSpec.describe Bundler::EnvironmentPreserver do context "when a key is empty" do let(:env) { { "foo" => "" } } - it "should not create backup entries" do - expect(subject).not_to have_key "BUNDLER_ORIG_foo" + it "should keep the original entry" do + expect(subject["foo"]).to be_empty + end + + it "should still create backup entries" do + expect(subject).to have_key "BUNDLER_ORIG_foo" end end @@ -71,8 +75,12 @@ RSpec.describe Bundler::EnvironmentPreserver do context "when the original key is empty" do let(:env) { { "foo" => "my-foo", "BUNDLER_ORIG_foo" => "" } } - it "should keep the current value" do - expect(subject["foo"]).to eq("my-foo") + it "should restore the original value" do + expect(subject["foo"]).to be_empty + end + + it "should delete the backup value" do + expect(subject.key?("BUNDLER_ORIG_foo")).to eq(false) end end end diff --git a/spec/bundler/bundler/fetcher/base_spec.rb b/spec/bundler/bundler/fetcher/base_spec.rb index 02506591f3..b8c6b57b10 100644 --- a/spec/bundler/bundler/fetcher/base_spec.rb +++ b/spec/bundler/bundler/fetcher/base_spec.rb @@ -4,15 +4,16 @@ RSpec.describe Bundler::Fetcher::Base do let(:downloader) { double(:downloader) } let(:remote) { double(:remote) } let(:display_uri) { "http://sample_uri.com" } + let(:gem_remote_fetcher) { nil } class TestClass < described_class; end - subject { TestClass.new(downloader, remote, display_uri) } + subject { TestClass.new(downloader, remote, display_uri, gem_remote_fetcher) } describe "#initialize" do context "with the abstract Base class" do it "should raise an error" do - expect { described_class.new(downloader, remote, display_uri) }.to raise_error(RuntimeError, "Abstract class") + expect { described_class.new(downloader, remote, display_uri, gem_remote_fetcher) }.to raise_error(RuntimeError, "Abstract class") end end @@ -36,7 +37,7 @@ RSpec.describe Bundler::Fetcher::Base do end describe "#fetch_uri" do - let(:remote_uri_obj) { Bundler::URI("http://rubygems.org") } + let(:remote_uri_obj) { Gem::URI("http://rubygems.org") } before { allow(subject).to receive(:remote_uri).and_return(remote_uri_obj) } @@ -49,10 +50,10 @@ RSpec.describe Bundler::Fetcher::Base do end context "when the remote uri's host is not rubygems.org" do - let(:remote_uri_obj) { Bundler::URI("http://otherhost.org") } + let(:remote_uri_obj) { Gem::URI("http://otherhost.org") } it "should return the remote uri" do - expect(subject.fetch_uri).to eq(Bundler::URI("http://otherhost.org")) + expect(subject.fetch_uri).to eq(Gem::URI("http://otherhost.org")) end end diff --git a/spec/bundler/bundler/fetcher/compact_index_spec.rb b/spec/bundler/bundler/fetcher/compact_index_spec.rb index 00eb27edea..a988171f34 100644 --- a/spec/bundler/bundler/fetcher/compact_index_spec.rb +++ b/spec/bundler/bundler/fetcher/compact_index_spec.rb @@ -5,9 +5,10 @@ require "bundler/compact_index_client" RSpec.describe Bundler::Fetcher::CompactIndex do let(:downloader) { double(:downloader) } - let(:display_uri) { Bundler::URI("http://sampleuri.com") } - let(:remote) { double(:remote, :cache_slug => "lsjdf", :uri => display_uri) } - let(:compact_index) { described_class.new(downloader, remote, display_uri) } + let(:display_uri) { Gem::URI("http://sampleuri.com") } + let(:remote) { double(:remote, cache_slug: "lsjdf", uri: display_uri) } + let(:gem_remote_fetcher) { nil } + let(:compact_index) { described_class.new(downloader, remote, display_uri, gem_remote_fetcher) } before do allow(compact_index).to receive(:log_specs) {} @@ -33,7 +34,7 @@ RSpec.describe Bundler::Fetcher::CompactIndex do describe "#available?" do before do allow(compact_index).to receive(:compact_index_client). - and_return(double(:compact_index_client, :update_and_parse_checksums! => true)) + and_return(double(:compact_index_client, update_and_parse_checksums!: true)) end it "returns true" do diff --git a/spec/bundler/bundler/fetcher/dependency_spec.rb b/spec/bundler/bundler/fetcher/dependency_spec.rb index 53249116cd..c420b7c07f 100644 --- a/spec/bundler/bundler/fetcher/dependency_spec.rb +++ b/spec/bundler/bundler/fetcher/dependency_spec.rb @@ -2,10 +2,11 @@ RSpec.describe Bundler::Fetcher::Dependency do let(:downloader) { double(:downloader) } - let(:remote) { double(:remote, :uri => Bundler::URI("http://localhost:5000")) } + let(:remote) { double(:remote, uri: Gem::URI("http://localhost:5000")) } let(:display_uri) { "http://sample_uri.com" } + let(:gem_remote_fetcher) { nil } - subject { described_class.new(downloader, remote, display_uri) } + subject { described_class.new(downloader, remote, display_uri, gem_remote_fetcher) } describe "#available?" do let(:dependency_api_uri) { double(:dependency_api_uri) } @@ -155,9 +156,9 @@ RSpec.describe Bundler::Fetcher::Dependency do end end - shared_examples_for "the error suggests retrying with the full index" do - it "should log the inability to fetch from API at debug level" do - expect(Bundler).to receive_message_chain(:ui, :debug).with("could not fetch from the dependency API\nit's suggested to retry using the full index via `bundle install --full-index`") + shared_examples_for "the error is logged" do + it "should log the inability to fetch from API at debug level, and mention retrying" do + expect(Bundler).to receive_message_chain(:ui, :debug).with("could not fetch from the dependency API, trying the full index") subject.specs(gem_names, full_dependency_list, last_spec_list) end end @@ -166,25 +167,21 @@ RSpec.describe Bundler::Fetcher::Dependency do before { allow(subject).to receive(:dependency_specs) { raise Bundler::HTTPError.new } } it_behaves_like "the error is properly handled" - it_behaves_like "the error suggests retrying with the full index" + it_behaves_like "the error is logged" end context "when a GemspecError occurs" do before { allow(subject).to receive(:dependency_specs) { raise Bundler::GemspecError.new } } it_behaves_like "the error is properly handled" - it_behaves_like "the error suggests retrying with the full index" + it_behaves_like "the error is logged" end context "when a MarshalError occurs" do before { allow(subject).to receive(:dependency_specs) { raise Bundler::MarshalError.new } } it_behaves_like "the error is properly handled" - - it "should log the inability to fetch from API and mention retrying" do - expect(Bundler).to receive_message_chain(:ui, :debug).with("could not fetch from the dependency API, trying the full index") - subject.specs(gem_names, full_dependency_list, last_spec_list) - end + it_behaves_like "the error is logged" end end @@ -214,7 +211,7 @@ RSpec.describe Bundler::Fetcher::Dependency do let(:gem_names) { [%w[foo bar], %w[bundler rubocop]] } let(:dep_api_uri) { double(:dep_api_uri) } let(:unmarshalled_gems) { double(:unmarshalled_gems) } - let(:fetch_response) { double(:fetch_response, :body => double(:body)) } + let(:fetch_response) { double(:fetch_response, body: double(:body)) } let(:rubygems_limit) { 50 } before { allow(subject).to receive(:dependency_api_uri).with(gem_names).and_return(dep_api_uri) } @@ -222,7 +219,7 @@ RSpec.describe Bundler::Fetcher::Dependency do it "should fetch dependencies from RubyGems and unmarshal them" do expect(gem_names).to receive(:each_slice).with(rubygems_limit).and_call_original expect(downloader).to receive(:fetch).with(dep_api_uri).and_return(fetch_response) - expect(Bundler).to receive(:load_marshal).with(fetch_response.body).and_return([unmarshalled_gems]) + expect(Bundler).to receive(:safe_load_marshal).with(fetch_response.body).and_return([unmarshalled_gems]) expect(subject.unmarshalled_dep_gems(gem_names)).to eq([unmarshalled_gems]) end end @@ -231,20 +228,20 @@ RSpec.describe Bundler::Fetcher::Dependency do let(:gem_list) do [ { - :dependencies => { + dependencies: { "resque" => "req3,req4", }, - :name => "typhoeus", - :number => "1.0.1", - :platform => "ruby", + name: "typhoeus", + number: "1.0.1", + platform: "ruby", }, { - :dependencies => { + dependencies: { "faraday" => "req1,req2", }, - :name => "grape", - :number => "2.0.2", - :platform => "jruby", + name: "grape", + number: "2.0.2", + platform: "jruby", }, ] end @@ -258,7 +255,7 @@ RSpec.describe Bundler::Fetcher::Dependency do end describe "#dependency_api_uri" do - let(:uri) { Bundler::URI("http://gem-api.com") } + let(:uri) { Gem::URI("http://gem-api.com") } context "with gem names" do let(:gem_names) { %w[foo bar bundler rubocop] } diff --git a/spec/bundler/bundler/fetcher/downloader_spec.rb b/spec/bundler/bundler/fetcher/downloader_spec.rb index 4d3dff3a89..d5c32f4730 100644 --- a/spec/bundler/bundler/fetcher/downloader_spec.rb +++ b/spec/bundler/bundler/fetcher/downloader_spec.rb @@ -3,7 +3,7 @@ RSpec.describe Bundler::Fetcher::Downloader do let(:connection) { double(:connection) } let(:redirect_limit) { 5 } - let(:uri) { Bundler::URI("http://www.uri-to-fetch.com/api/v2/endpoint") } + let(:uri) { Gem::URI("http://www.uri-to-fetch.com/api/v2/endpoint") } let(:options) { double(:options) } subject { described_class.new(connection, redirect_limit) } @@ -27,7 +27,7 @@ RSpec.describe Bundler::Fetcher::Downloader do end context "logging" do - let(:http_response) { Net::HTTPSuccess.new("1.1", 200, "Success") } + let(:http_response) { Gem::Net::HTTPSuccess.new("1.1", 200, "Success") } it "should log the HTTP response code and message to debug" do expect(Bundler).to receive_message_chain(:ui, :debug).with("HTTP 200 Success #{uri}") @@ -35,48 +35,48 @@ RSpec.describe Bundler::Fetcher::Downloader do end end - context "when the request response is a Net::HTTPRedirection" do - let(:http_response) { Net::HTTPRedirection.new(httpv, 308, "Moved") } + context "when the request response is a Gem::Net::HTTPRedirection" do + let(:http_response) { Gem::Net::HTTPRedirection.new(httpv, 308, "Moved") } before { http_response["location"] = "http://www.redirect-uri.com/api/v2/endpoint" } it "should try to fetch the redirect uri and iterate the # requests counter" do - expect(subject).to receive(:fetch).with(Bundler::URI("http://www.uri-to-fetch.com/api/v2/endpoint"), options, 0).and_call_original - expect(subject).to receive(:fetch).with(Bundler::URI("http://www.redirect-uri.com/api/v2/endpoint"), options, 1) + expect(subject).to receive(:fetch).with(Gem::URI("http://www.uri-to-fetch.com/api/v2/endpoint"), options, 0).and_call_original + expect(subject).to receive(:fetch).with(Gem::URI("http://www.redirect-uri.com/api/v2/endpoint"), options, 1) subject.fetch(uri, options, counter) end context "when the redirect uri and original uri are the same" do - let(:uri) { Bundler::URI("ssh://username:password@www.uri-to-fetch.com/api/v2/endpoint") } + let(:uri) { Gem::URI("ssh://username:password@www.uri-to-fetch.com/api/v2/endpoint") } before { http_response["location"] = "ssh://www.uri-to-fetch.com/api/v1/endpoint" } it "should set the same user and password for the redirect uri" do - expect(subject).to receive(:fetch).with(Bundler::URI("ssh://username:password@www.uri-to-fetch.com/api/v2/endpoint"), options, 0).and_call_original - expect(subject).to receive(:fetch).with(Bundler::URI("ssh://username:password@www.uri-to-fetch.com/api/v1/endpoint"), options, 1) + expect(subject).to receive(:fetch).with(Gem::URI("ssh://username:password@www.uri-to-fetch.com/api/v2/endpoint"), options, 0).and_call_original + expect(subject).to receive(:fetch).with(Gem::URI("ssh://username:password@www.uri-to-fetch.com/api/v1/endpoint"), options, 1) subject.fetch(uri, options, counter) end end end - context "when the request response is a Net::HTTPSuccess" do - let(:http_response) { Net::HTTPSuccess.new("1.1", 200, "Success") } + context "when the request response is a Gem::Net::HTTPSuccess" do + let(:http_response) { Gem::Net::HTTPSuccess.new("1.1", 200, "Success") } it "should return the response body" do expect(subject.fetch(uri, options, counter)).to eq(http_response) end end - context "when the request response is a Net::HTTPRequestEntityTooLarge" do - let(:http_response) { Net::HTTPRequestEntityTooLarge.new("1.1", 413, "Too Big") } + context "when the request response is a Gem::Net::HTTPRequestEntityTooLarge" do + let(:http_response) { Gem::Net::HTTPRequestEntityTooLarge.new("1.1", 413, "Too Big") } it "should raise a Bundler::Fetcher::FallbackError with the response body" do expect { subject.fetch(uri, options, counter) }.to raise_error(Bundler::Fetcher::FallbackError, "Body with info") end end - context "when the request response is a Net::HTTPUnauthorized" do - let(:http_response) { Net::HTTPUnauthorized.new("1.1", 401, "Unauthorized") } + context "when the request response is a Gem::Net::HTTPUnauthorized" do + let(:http_response) { Gem::Net::HTTPUnauthorized.new("1.1", 401, "Unauthorized") } it "should raise a Bundler::Fetcher::AuthenticationRequiredError with the uri host" do expect { subject.fetch(uri, options, counter) }.to raise_error(Bundler::Fetcher::AuthenticationRequiredError, @@ -89,7 +89,7 @@ RSpec.describe Bundler::Fetcher::Downloader do end context "when the there are credentials provided in the request" do - let(:uri) { Bundler::URI("http://user:password@www.uri-to-fetch.com") } + let(:uri) { Gem::URI("http://user:password@www.uri-to-fetch.com") } it "should raise a Bundler::Fetcher::BadAuthenticationError that doesn't contain the password" do expect { subject.fetch(uri, options, counter) }. @@ -98,29 +98,39 @@ RSpec.describe Bundler::Fetcher::Downloader do end end - context "when the request response is a Net::HTTPNotFound" do - let(:http_response) { Net::HTTPNotFound.new("1.1", 404, "Not Found") } + context "when the request response is a Gem::Net::HTTPForbidden" do + let(:http_response) { Gem::Net::HTTPForbidden.new("1.1", 403, "Forbidden") } + let(:uri) { Gem::URI("http://user:password@www.uri-to-fetch.com") } - it "should raise a Bundler::Fetcher::FallbackError with Net::HTTPNotFound" do + it "should raise a Bundler::Fetcher::AuthenticationForbiddenError with the uri host" do + expect { subject.fetch(uri, options, counter) }.to raise_error(Bundler::Fetcher::AuthenticationForbiddenError, + /Access token could not be authenticated for www.uri-to-fetch.com/) + end + end + + context "when the request response is a Gem::Net::HTTPNotFound" do + let(:http_response) { Gem::Net::HTTPNotFound.new("1.1", 404, "Not Found") } + + it "should raise a Bundler::Fetcher::FallbackError with Gem::Net::HTTPNotFound" do expect { subject.fetch(uri, options, counter) }. - to raise_error(Bundler::Fetcher::FallbackError, "Net::HTTPNotFound: http://www.uri-to-fetch.com/api/v2/endpoint") + to raise_error(Bundler::Fetcher::FallbackError, "Gem::Net::HTTPNotFound: http://www.uri-to-fetch.com/api/v2/endpoint") end context "when the there are credentials provided in the request" do - let(:uri) { Bundler::URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint") } + let(:uri) { Gem::URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint") } it "should raise a Bundler::Fetcher::FallbackError that doesn't contain the password" do expect { subject.fetch(uri, options, counter) }. - to raise_error(Bundler::Fetcher::FallbackError, "Net::HTTPNotFound: http://username@www.uri-to-fetch.com/api/v2/endpoint") + to raise_error(Bundler::Fetcher::FallbackError, "Gem::Net::HTTPNotFound: http://username@www.uri-to-fetch.com/api/v2/endpoint") end end end context "when the request response is some other type" do - let(:http_response) { Net::HTTPBadGateway.new("1.1", 500, "Fatal Error") } + let(:http_response) { Gem::Net::HTTPBadGateway.new("1.1", 500, "Fatal Error") } it "should raise a Bundler::HTTPError with the response class and body" do - expect { subject.fetch(uri, options, counter) }.to raise_error(Bundler::HTTPError, "Net::HTTPBadGateway: Body with info") + expect { subject.fetch(uri, options, counter) }.to raise_error(Bundler::HTTPError, "Gem::Net::HTTPBadGateway: Body with info") end end end @@ -130,7 +140,7 @@ RSpec.describe Bundler::Fetcher::Downloader do let(:response) { double(:response) } before do - allow(Net::HTTP::Get).to receive(:new).with("/api/v2/endpoint", options).and_return(net_http_get) + allow(Gem::Net::HTTP::Get).to receive(:new).with("/api/v2/endpoint", options).and_return(net_http_get) allow(connection).to receive(:request).with(uri, net_http_get).and_return(response) end @@ -142,7 +152,7 @@ RSpec.describe Bundler::Fetcher::Downloader do context "when there is a user provided in the request" do context "and there is also a password provided" do context "that contains cgi escaped characters" do - let(:uri) { Bundler::URI("http://username:password%24@www.uri-to-fetch.com/api/v2/endpoint") } + let(:uri) { Gem::URI("http://username:password%24@www.uri-to-fetch.com/api/v2/endpoint") } it "should request basic authentication with the username and password" do expect(net_http_get).to receive(:basic_auth).with("username", "password$") @@ -151,7 +161,7 @@ RSpec.describe Bundler::Fetcher::Downloader do end context "that is all unescaped characters" do - let(:uri) { Bundler::URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint") } + let(:uri) { Gem::URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint") } it "should request basic authentication with the username and proper cgi compliant password" do expect(net_http_get).to receive(:basic_auth).with("username", "password") subject.request(uri, options) @@ -160,7 +170,7 @@ RSpec.describe Bundler::Fetcher::Downloader do end context "and there is no password provided" do - let(:uri) { Bundler::URI("http://username@www.uri-to-fetch.com/api/v2/endpoint") } + let(:uri) { Gem::URI("http://username@www.uri-to-fetch.com/api/v2/endpoint") } it "should request basic authentication with just the user" do expect(net_http_get).to receive(:basic_auth).with("username", nil) @@ -169,7 +179,7 @@ RSpec.describe Bundler::Fetcher::Downloader do end context "that contains cgi escaped characters" do - let(:uri) { Bundler::URI("http://username%24@www.uri-to-fetch.com/api/v2/endpoint") } + let(:uri) { Gem::URI("http://username%24@www.uri-to-fetch.com/api/v2/endpoint") } it "should request basic authentication with the proper cgi compliant password user" do expect(net_http_get).to receive(:basic_auth).with("username$", nil) @@ -178,26 +188,6 @@ RSpec.describe Bundler::Fetcher::Downloader do end end - context "when the request response causes a NoMethodError" do - before { allow(connection).to receive(:request).with(uri, net_http_get) { raise NoMethodError.new(message) } } - - context "and the error message is about use_ssl=" do - let(:message) { "undefined method 'use_ssl='" } - - it "should raise a LoadError about openssl" do - expect { subject.request(uri, options) }.to raise_error(LoadError, "cannot load such file -- openssl") - end - end - - context "and the error message is not about use_ssl=" do - let(:message) { "undefined method 'undefined_method_call'" } - - it "should raise the original NoMethodError" do - expect { subject.request(uri, options) }.to raise_error(NoMethodError, "undefined method 'undefined_method_call'") - end - end - end - context "when the request response causes a OpenSSL::SSL::SSLError" do before { allow(connection).to receive(:request).with(uri, net_http_get) { raise OpenSSL::SSL::SSLError.new } } @@ -240,7 +230,7 @@ RSpec.describe Bundler::Fetcher::Downloader do end context "when the there are credentials provided in the request" do - let(:uri) { Bundler::URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint") } + let(:uri) { Gem::URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint") } before do allow(net_http_get).to receive(:basic_auth).with("username", "password") end diff --git a/spec/bundler/bundler/fetcher/index_spec.rb b/spec/bundler/bundler/fetcher/index_spec.rb index f0db07583c..dff9ccc3cc 100644 --- a/spec/bundler/bundler/fetcher/index_spec.rb +++ b/spec/bundler/bundler/fetcher/index_spec.rb @@ -8,8 +8,9 @@ RSpec.describe Bundler::Fetcher::Index do let(:display_uri) { "http://sample_uri.com" } let(:rubygems) { double(:rubygems) } let(:gem_names) { %w[foo bar] } + let(:gem_remote_fetcher) { nil } - subject { described_class.new(downloader, remote, display_uri) } + subject { described_class.new(downloader, remote, display_uri, gem_remote_fetcher) } before { allow(Bundler).to receive(:rubygems).and_return(rubygems) } @@ -19,7 +20,7 @@ RSpec.describe Bundler::Fetcher::Index do end context "error handling" do - let(:remote_uri) { Bundler::URI("http://remote-uri.org") } + let(:remote_uri) { Gem::URI("http://remote-uri.org") } before do allow(rubygems).to receive(:fetch_all_remote_specs) { raise Gem::RemoteFetcher::FetchError.new(error_message, display_uri) } allow(subject).to receive(:remote_uri).and_return(remote_uri) @@ -63,33 +64,16 @@ RSpec.describe Bundler::Fetcher::Index do context "when a 403 response occurs" do let(:error_message) { "403" } - before do - allow(remote_uri).to receive(:userinfo).and_return(userinfo) - end - - context "and there was userinfo" do - let(:userinfo) { double(:userinfo) } - - it "should raise a Bundler::Fetcher::BadAuthenticationError" do - expect { subject.specs(gem_names) }.to raise_error(Bundler::Fetcher::BadAuthenticationError, - %r{Bad username or password for http://remote-uri.org}) - end - end - - context "and there was no userinfo" do - let(:userinfo) { nil } - - it "should raise a Bundler::Fetcher::AuthenticationRequiredError" do - expect { subject.specs(gem_names) }.to raise_error(Bundler::Fetcher::AuthenticationRequiredError, - %r{Authentication is required for http://remote-uri.org}) - end + it "should raise a Bundler::Fetcher::AuthenticationForbiddenError" do + expect { subject.specs(gem_names) }.to raise_error(Bundler::Fetcher::AuthenticationForbiddenError, + %r{Access token could not be authenticated for http://remote-uri.org}) end end context "any other message is returned" do let(:error_message) { "You get an error, you get an error!" } - before { allow(Bundler).to receive(:ui).and_return(double(:trace => nil)) } + before { allow(Bundler).to receive(:ui).and_return(double(trace: nil)) } it "should raise a Bundler::HTTPError" do expect { subject.specs(gem_names) }.to raise_error(Bundler::HTTPError, "Could not fetch specs from http://sample_uri.com due to underlying error <You get an error, you get an error! (http://sample_uri.com)>") diff --git a/spec/bundler/bundler/fetcher_spec.rb b/spec/bundler/bundler/fetcher_spec.rb index 256d342775..e20f7e7c48 100644 --- a/spec/bundler/bundler/fetcher_spec.rb +++ b/spec/bundler/bundler/fetcher_spec.rb @@ -3,8 +3,8 @@ require "bundler/fetcher" RSpec.describe Bundler::Fetcher do - let(:uri) { Bundler::URI("https://example.com") } - let(:remote) { double("remote", :uri => uri, :original_uri => nil) } + let(:uri) { Gem::URI("https://example.com") } + let(:remote) { double("remote", uri: uri, original_uri: nil) } subject(:fetcher) { Bundler::Fetcher.new(remote) } @@ -26,7 +26,7 @@ RSpec.describe Bundler::Fetcher do context "when Gem.configuration specifies http_proxy " do let(:proxy) { "http://proxy-example2.com" } before do - allow(Bundler.rubygems.configuration).to receive(:[]).with(:http_proxy).and_return(proxy) + allow(Gem.configuration).to receive(:[]).with(:http_proxy).and_return(proxy) end it "consider Gem.configuration when determine proxy" do expect(fetcher.http_proxy).to match("http://proxy-example2.com") @@ -45,9 +45,9 @@ RSpec.describe Bundler::Fetcher do end context "when a rubygems source mirror is set" do - let(:orig_uri) { Bundler::URI("http://zombo.com") } + let(:orig_uri) { Gem::URI("http://zombo.com") } let(:remote_with_mirror) do - double("remote", :uri => uri, :original_uri => orig_uri, :anonymized_uri => uri) + double("remote", uri: uri, original_uri: orig_uri, anonymized_uri: uri) end let(:fetcher) { Bundler::Fetcher.new(remote_with_mirror) } @@ -61,7 +61,7 @@ RSpec.describe Bundler::Fetcher do context "when there is no rubygems source mirror set" do let(:remote_no_mirror) do - double("remote", :uri => uri, :original_uri => nil, :anonymized_uri => uri) + double("remote", uri: uri, original_uri: nil, anonymized_uri: uri) end let(:fetcher) { Bundler::Fetcher.new(remote_no_mirror) } @@ -113,10 +113,10 @@ RSpec.describe Bundler::Fetcher do context "when gem ssl configuration is set" do before do - allow(Bundler.rubygems.configuration).to receive_messages( - :http_proxy => nil, - :ssl_client_cert => "cert", - :ssl_ca_cert => "ca" + allow(Gem.configuration).to receive_messages( + http_proxy: nil, + ssl_client_cert: "cert", + ssl_ca_cert: "ca" ) expect(File).to receive(:read).and_return("") expect(OpenSSL::X509::Certificate).to receive(:new).and_return("cert") @@ -143,20 +143,118 @@ RSpec.describe Bundler::Fetcher do describe "include CI information" do it "from one CI" do - with_env_vars("JENKINS_URL" => "foo") do + with_env_vars("CI" => nil, "JENKINS_URL" => "foo") do ci_part = fetcher.user_agent.split(" ").find {|x| x.start_with?("ci/") } - expect(ci_part).to match("jenkins") + cis = ci_part.split("/").last.split(",") + expect(cis).to include("jenkins") + expect(cis).not_to include("ci") end end it "from many CI" do - with_env_vars("TRAVIS" => "foo", "GITLAB_CI" => "gitlab", "CI_NAME" => "my_ci") do + with_env_vars("CI" => "true", "SEMAPHORE" => nil, "TRAVIS" => "foo", "GITLAB_CI" => "gitlab", "CI_NAME" => "MY_ci") do ci_part = fetcher.user_agent.split(" ").find {|x| x.start_with?("ci/") } - expect(ci_part).to match("travis") - expect(ci_part).to match("gitlab") - expect(ci_part).to match("my_ci") + cis = ci_part.split("/").last.split(",") + expect(cis).to include("ci", "gitlab", "my_ci", "travis") + expect(cis).not_to include("semaphore") end end end end + + describe "#fetch_spec" do + let(:name) { "name" } + let(:version) { "1.3.17" } + let(:platform) { "platform" } + let(:downloader) { double("downloader") } + let(:body) { double(Gem::Net::HTTP::Get, body: downloaded_data) } + + context "when attempting to load a Gem::Specification" do + let(:spec) { Gem::Specification.new(name, version) } + let(:downloaded_data) { Zlib::Deflate.deflate(Marshal.dump(spec)) } + + it "returns the spec" do + expect(Bundler::Fetcher::Downloader).to receive(:new).and_return(downloader) + expect(downloader).to receive(:fetch).once.and_return(body) + result = fetcher.fetch_spec([name, version, platform]) + expect(result).to eq(spec) + end + end + + context "when attempting to load an unexpected class" do + let(:downloaded_data) { Zlib::Deflate.deflate(Marshal.dump(3)) } + + it "raises a HTTPError error" do + expect(Bundler::Fetcher::Downloader).to receive(:new).and_return(downloader) + expect(downloader).to receive(:fetch).once.and_return(body) + expect { fetcher.fetch_spec([name, version, platform]) }.to raise_error(Bundler::HTTPError, /Gemspec .* contained invalid data/i) + end + end + end + + describe "#specs_with_retry" do + let(:downloader) { double(:downloader) } + let(:remote) { double(:remote, cache_slug: "slug", uri: uri, original_uri: nil, anonymized_uri: uri) } + let(:compact_index) { double(Bundler::Fetcher::CompactIndex, available?: true, api_fetcher?: true) } + let(:dependency) { double(Bundler::Fetcher::Dependency, available?: true, api_fetcher?: true) } + let(:index) { double(Bundler::Fetcher::Index, available?: true, api_fetcher?: false) } + + before do + allow(Bundler::Fetcher::CompactIndex).to receive(:new).and_return(compact_index) + allow(Bundler::Fetcher::Dependency).to receive(:new).and_return(dependency) + allow(Bundler::Fetcher::Index).to receive(:new).and_return(index) + end + + it "picks the first fetcher that works" do + expect(compact_index).to receive(:specs).with("name").and_return([["name", "1.2.3", "ruby"]]) + expect(dependency).not_to receive(:specs) + expect(index).not_to receive(:specs) + fetcher.specs_with_retry("name", double(Bundler::Source::Rubygems)) + end + + context "when APIs are not available" do + before do + allow(compact_index).to receive(:available?).and_return(false) + allow(dependency).to receive(:available?).and_return(false) + end + + it "uses the index" do + expect(compact_index).not_to receive(:specs) + expect(dependency).not_to receive(:specs) + expect(index).to receive(:specs).with("name").and_return([["name", "1.2.3", "ruby"]]) + + fetcher.specs_with_retry("name", double(Bundler::Source::Rubygems)) + end + end + end + + describe "#api_fetcher?" do + let(:downloader) { double(:downloader) } + let(:remote) { double(:remote, cache_slug: "slug", uri: uri, original_uri: nil, anonymized_uri: uri) } + let(:compact_index) { double(Bundler::Fetcher::CompactIndex, available?: false, api_fetcher?: true) } + let(:dependency) { double(Bundler::Fetcher::Dependency, available?: false, api_fetcher?: true) } + let(:index) { double(Bundler::Fetcher::Index, available?: true, api_fetcher?: false) } + + before do + allow(Bundler::Fetcher::CompactIndex).to receive(:new).and_return(compact_index) + allow(Bundler::Fetcher::Dependency).to receive(:new).and_return(dependency) + allow(Bundler::Fetcher::Index).to receive(:new).and_return(index) + end + + context "when an api fetcher is available" do + before do + allow(compact_index).to receive(:available?).and_return(true) + end + + it "is truthy" do + expect(fetcher).to be_api_fetcher + end + end + + context "when only the index fetcher is available" do + it "is falsey" do + expect(fetcher).not_to be_api_fetcher + end + end + end end diff --git a/spec/bundler/bundler/friendly_errors_spec.rb b/spec/bundler/bundler/friendly_errors_spec.rb index 496191f891..cda2ef31de 100644 --- a/spec/bundler/bundler/friendly_errors_spec.rb +++ b/spec/bundler/bundler/friendly_errors_spec.rb @@ -22,7 +22,7 @@ RSpec.describe Bundler, "friendly errors" do gem "rack" G - bundle :install, :env => { "DEBUG" => "true" } + bundle :install, env: { "DEBUG" => "true" } expect(err).to include("Failed to load #{home(".gemrc")}") end @@ -101,29 +101,15 @@ RSpec.describe Bundler, "friendly errors" do context "BundlerError" do it "Bundler.ui receive error" do error = Bundler::BundlerError.new - expect(Bundler.ui).to receive(:error).with(error.message, :wrap => true) + expect(Bundler.ui).to receive(:error).with(error.message, wrap: true) Bundler::FriendlyErrors.log_error(error) end - it_behaves_like "Bundler.ui receive trace", Bundler::BundlerError.new end context "Thor::Error" do it_behaves_like "Bundler.ui receive error", Bundler::Thor::Error.new end - context "LoadError" do - let(:error) { LoadError.new("cannot load such file -- openssl") } - - before do - allow(error).to receive(:backtrace).and_return(["backtrace"]) - end - - it "Bundler.ui receive error" do - expect(Bundler.ui).to receive(:error).with("\nCould not load OpenSSL. LoadError: cannot load such file -- openssl\nbacktrace") - Bundler::FriendlyErrors.log_error(error) - end - end - context "Interrupt" do it "Bundler.ui receive error" do expect(Bundler.ui).to receive(:error).with("\nQuitting...") @@ -135,7 +121,7 @@ RSpec.describe Bundler, "friendly errors" do context "Gem::InvalidSpecificationException" do it "Bundler.ui receive error" do error = Gem::InvalidSpecificationException.new - expect(Bundler.ui).to receive(:error).with(error.message, :wrap => true) + expect(Bundler.ui).to receive(:error).with(error.message, wrap: true) Bundler::FriendlyErrors.log_error(error) end end diff --git a/spec/bundler/bundler/gem_helper_spec.rb b/spec/bundler/bundler/gem_helper_spec.rb index 6c3ac3e035..940e5df9de 100644 --- a/spec/bundler/bundler/gem_helper_spec.rb +++ b/spec/bundler/bundler/gem_helper_spec.rb @@ -11,6 +11,7 @@ RSpec.describe Bundler::GemHelper do before(:each) do global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__COC" => "false", "BUNDLE_GEM__LINTER" => "false", "BUNDLE_GEM__CI" => "false", "BUNDLE_GEM__CHANGELOG" => "false" + sys_exec("git config --global init.defaultBranch main") bundle "gem #{app_name}" prepare_gemspec(app_gemspec_path) end @@ -66,6 +67,10 @@ RSpec.describe Bundler::GemHelper do mock_confirm_message message end + def sha512_hexdigest(path) + Digest::SHA512.file(path).hexdigest + end + subject! { Bundler::GemHelper.new(app_path) } let(:app_version) { "0.1.0" } let(:app_gem_dir) { app_path.join("pkg") } @@ -169,12 +174,21 @@ RSpec.describe Bundler::GemHelper do end describe "#build_checksum" do + it "calculates SHA512 of the content" do + FileUtils.mkdir_p(app_gem_dir) + File.write(app_gem_path, "") + mock_checksum_message app_name, app_version + subject.build_checksum(app_gem_path) + expect(File.read(app_sha_path).chomp).to eql(Digest::SHA512.hexdigest("")) + end + context "when build was successful" do it "creates .sha512 file" do mock_build_message app_name, app_version mock_checksum_message app_name, app_version subject.build_checksum expect(app_sha_path).to exist + expect(File.read(app_sha_path).chomp).to eql(sha512_hexdigest(app_gem_path)) end end context "when building in the current working directory" do @@ -185,6 +199,7 @@ RSpec.describe Bundler::GemHelper do Bundler::GemHelper.new.build_checksum end expect(app_sha_path).to exist + expect(File.read(app_sha_path).chomp).to eql(sha512_hexdigest(app_gem_path)) end end context "when building in a location relative to the current working directory" do @@ -195,6 +210,7 @@ RSpec.describe Bundler::GemHelper do Bundler::GemHelper.new(File.basename(app_path)).build_checksum end expect(app_sha_path).to exist + expect(File.read(app_sha_path).chomp).to eql(sha512_hexdigest(app_gem_path)) end end end @@ -219,7 +235,7 @@ RSpec.describe Bundler::GemHelper do FileUtils.touch app_gem_path app_gem_path end - expect { subject.install_gem }.to raise_error(/Couldn't install gem/) + expect { subject.install_gem }.to raise_error(/Running `#{gem_bin} install #{app_gem_path}` failed/) end end end @@ -237,11 +253,11 @@ RSpec.describe Bundler::GemHelper do end before do - sys_exec("git init", :dir => app_path) - sys_exec("git config user.email \"you@example.com\"", :dir => app_path) - sys_exec("git config user.name \"name\"", :dir => app_path) - sys_exec("git config commit.gpgsign false", :dir => app_path) - sys_exec("git config push.default simple", :dir => app_path) + sys_exec("git init", dir: app_path) + sys_exec("git config user.email \"you@example.com\"", dir: app_path) + sys_exec("git config user.name \"name\"", dir: app_path) + sys_exec("git config commit.gpgsign false", dir: app_path) + sys_exec("git config push.default simple", dir: app_path) # silence messages allow(Bundler.ui).to receive(:confirm) @@ -255,23 +271,23 @@ RSpec.describe Bundler::GemHelper do end it "when there are uncommitted files" do - sys_exec("git add .", :dir => app_path) + sys_exec("git add .", dir: app_path) expect { Rake.application["release"].invoke }. to raise_error("There are files that need to be committed first.") end it "when there is no git remote" do - sys_exec("git commit -a -m \"initial commit\"", :dir => app_path) + sys_exec("git commit -a -m \"initial commit\"", dir: app_path) expect { Rake.application["release"].invoke }.to raise_error(RuntimeError) end end context "succeeds" do - let(:repo) { build_git("foo", :bare => true) } + let(:repo) { build_git("foo", bare: true) } before do - sys_exec("git remote add origin #{file_uri_for(repo.path)}", :dir => app_path) - sys_exec('git commit -a -m "initial commit"', :dir => app_path) + sys_exec("git remote add origin #{file_uri_for(repo.path)}", dir: app_path) + sys_exec('git commit -a -m "initial commit"', dir: app_path) end context "on releasing" do @@ -280,7 +296,7 @@ RSpec.describe Bundler::GemHelper do mock_confirm_message "Tagged v#{app_version}." mock_confirm_message "Pushed git commits and release tag." - sys_exec("git push -u origin master", :dir => app_path) + sys_exec("git push -u origin main", dir: app_path) end it "calls rubygem_push with proper arguments" do @@ -298,8 +314,8 @@ RSpec.describe Bundler::GemHelper do it "also works when releasing from an ambiguous reference" do # Create a branch with the same name as the tag - sys_exec("git checkout -b v#{app_version}", :dir => app_path) - sys_exec("git push -u origin v#{app_version}", :dir => app_path) + sys_exec("git checkout -b v#{app_version}", dir: app_path) + sys_exec("git push -u origin v#{app_version}", dir: app_path) expect(subject).to receive(:rubygem_push).with(app_gem_path.to_s) @@ -307,7 +323,7 @@ RSpec.describe Bundler::GemHelper do end it "also works with releasing from a branch not yet pushed" do - sys_exec("git checkout -b module_function", :dir => app_path) + sys_exec("git checkout -b module_function", dir: app_path) expect(subject).to receive(:rubygem_push).with(app_gem_path.to_s) @@ -321,7 +337,7 @@ RSpec.describe Bundler::GemHelper do mock_build_message app_name, app_version mock_confirm_message "Pushed git commits and release tag." - sys_exec("git push -u origin master", :dir => app_path) + sys_exec("git push -u origin main", dir: app_path) expect(subject).to receive(:rubygem_push).with(app_gem_path.to_s) end @@ -337,7 +353,7 @@ RSpec.describe Bundler::GemHelper do mock_confirm_message "Tag v#{app_version} has already been created." expect(subject).to receive(:rubygem_push).with(app_gem_path.to_s) - sys_exec("git tag -a -m \"Version #{app_version}\" v#{app_version}", :dir => app_path) + sys_exec("git tag -a -m \"Version #{app_version}\" v#{app_version}", dir: app_path) Rake.application["release"].invoke end @@ -358,10 +374,10 @@ RSpec.describe Bundler::GemHelper do end before do - sys_exec("git init", :dir => app_path) - sys_exec("git config user.email \"you@example.com\"", :dir => app_path) - sys_exec("git config user.name \"name\"", :dir => app_path) - sys_exec("git config push.gpgsign simple", :dir => app_path) + sys_exec("git init", dir: app_path) + sys_exec("git config user.email \"you@example.com\"", dir: app_path) + sys_exec("git config user.name \"name\"", dir: app_path) + sys_exec("git config push.gpgsign simple", dir: app_path) # silence messages allow(Bundler.ui).to receive(:confirm) diff --git a/spec/bundler/bundler/gem_version_promoter_spec.rb b/spec/bundler/bundler/gem_version_promoter_spec.rb index 43a3630bbb..917daba95d 100644 --- a/spec/bundler/bundler/gem_version_promoter_spec.rb +++ b/spec/bundler/bundler/gem_version_promoter_spec.rb @@ -1,178 +1,162 @@ # frozen_string_literal: true RSpec.describe Bundler::GemVersionPromoter do - context "conservative resolver" do - def versions(result) - result.flatten.map(&:version).map(&:to_s) + let(:gvp) { described_class.new } + + # Rightmost (highest array index) in result is most preferred. + # Leftmost (lowest array index) in result is least preferred. + # `build_candidates` has all versions of gem in index. + # `build_spec` is the version currently in the .lock file. + # + # In default (not strict) mode, all versions in the index will + # be returned, allowing Bundler the best chance to resolve all + # dependencies, but sometimes resulting in upgrades that some + # would not consider conservative. + + describe "#sort_versions" do + def build_candidates(versions) + versions.map do |v| + Bundler::Resolver::Candidate.new(v) + end end - def make_instance(*args) - @gvp = Bundler::GemVersionPromoter.new(*args).tap do |gvp| - gvp.class.class_eval { public :filter_dep_specs, :sort_dep_specs } - end + def build_package(name, version, locked = []) + Bundler::Resolver::Package.new(name, [], locked_specs: Bundler::SpecSet.new(build_spec(name, version)), unlock: locked) end - def unlocking(options) - make_instance(Bundler::SpecSet.new([]), ["foo"]).tap do |p| - p.level = options[:level] if options[:level] - p.strict = options[:strict] if options[:strict] - end + def sorted_versions(candidates:, current:, name: "foo", locked: []) + gvp.sort_versions( + build_package(name, current, locked), + build_candidates(candidates) + ).flatten.map(&:version).map(&:to_s) end - def keep_locked(options) - make_instance(Bundler::SpecSet.new([]), ["bar"]).tap do |p| - p.level = options[:level] if options[:level] - p.strict = options[:strict] if options[:strict] - end + it "numerically sorts versions" do + versions = sorted_versions(candidates: %w[1.7.7 1.7.8 1.7.9 1.7.15 1.8.0], current: "1.7.8") + expect(versions).to eq %w[1.8.0 1.7.15 1.7.9 1.7.8 1.7.7] end - def build_spec_groups(name, versions) - versions.map do |v| - Bundler::Resolver::SpecGroup.create_for({ Gem::Platform::RUBY => build_spec(name, v) }, [Gem::Platform::RUBY], Gem::Platform::RUBY) + context "with no options" do + it "defaults to level=:major, strict=false, pre=false" do + versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") + expect(versions).to eq %w[2.1.0 2.0.1 1.0.0 0.9.0 0.3.1 0.3.0 0.2.0] end end - # Rightmost (highest array index) in result is most preferred. - # Leftmost (lowest array index) in result is least preferred. - # `build_spec_groups` has all versions of gem in index. - # `build_spec` is the version currently in the .lock file. - # - # In default (not strict) mode, all versions in the index will - # be returned, allowing Bundler the best chance to resolve all - # dependencies, but sometimes resulting in upgrades that some - # would not consider conservative. - context "filter specs (strict) level patch" do - it "when keeping build_spec, keep current, next release" do - keep_locked(:level => :patch) - res = @gvp.filter_dep_specs( - build_spec_groups("foo", %w[1.7.8 1.7.9 1.8.0]), - build_spec("foo", "1.7.8").first - ) - expect(versions(res)).to eq %w[1.7.9 1.7.8] - end + context "when strict" do + before { gvp.strict = true } - it "when unlocking prefer next release first" do - unlocking(:level => :patch) - res = @gvp.filter_dep_specs( - build_spec_groups("foo", %w[1.7.8 1.7.9 1.8.0]), - build_spec("foo", "1.7.8").first - ) - expect(versions(res)).to eq %w[1.7.8 1.7.9] - end + context "when level is major" do + before { gvp.level = :major } - it "when unlocking keep current when already at latest release" do - unlocking(:level => :patch) - res = @gvp.filter_dep_specs( - build_spec_groups("foo", %w[1.7.9 1.8.0 2.0.0]), - build_spec("foo", "1.7.9").first - ) - expect(versions(res)).to eq %w[1.7.9] + it "keeps downgrades" do + versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") + expect(versions).to eq %w[2.1.0 2.0.1 1.0.0 0.9.0 0.3.1 0.3.0 0.2.0] + end end - end - context "filter specs (strict) level minor" do - it "when unlocking favor next releases, remove minor and major increases" do - unlocking(:level => :minor) - res = @gvp.filter_dep_specs( - build_spec_groups("foo", %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.0 2.0.1]), - build_spec("foo", "0.2.0").first - ) - expect(versions(res)).to eq %w[0.2.0 0.3.0 0.3.1 0.9.0] + context "when level is minor" do + before { gvp.level = :minor } + + it "sorts highest minor within same major in first position" do + versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") + expect(versions).to eq %w[0.9.0 0.3.1 0.3.0 1.0.0 2.1.0 2.0.1 0.2.0] + end end - it "when keep locked, keep current, then favor next release, remove minor and major increases" do - keep_locked(:level => :minor) - res = @gvp.filter_dep_specs( - build_spec_groups("foo", %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.0 2.0.1]), - build_spec("foo", "0.2.0").first - ) - expect(versions(res)).to eq %w[0.3.0 0.3.1 0.9.0 0.2.0] + context "when level is patch" do + before { gvp.level = :patch } + + it "sorts highest patch within same minor in first position" do + versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") + expect(versions).to eq %w[0.3.1 0.3.0 0.9.0 1.0.0 2.0.1 2.1.0 0.2.0] + end end end - context "sort specs (not strict) level patch" do - it "when not unlocking, same order but make sure build_spec version is most preferred to stay put" do - keep_locked(:level => :patch) - res = @gvp.sort_dep_specs( - build_spec_groups("foo", %w[1.5.4 1.6.5 1.7.6 1.7.7 1.7.8 1.7.9 1.8.0 1.8.1 2.0.0 2.0.1]), - build_spec("foo", "1.7.7").first - ) - expect(versions(res)).to eq %w[1.5.4 1.6.5 1.7.6 2.0.0 2.0.1 1.8.0 1.8.1 1.7.8 1.7.9 1.7.7] - end + context "when not strict" do + before { gvp.strict = false } - it "when unlocking favor next release, then current over minor increase" do - unlocking(:level => :patch) - res = @gvp.sort_dep_specs( - build_spec_groups("foo", %w[1.7.7 1.7.8 1.7.9 1.8.0]), - build_spec("foo", "1.7.8").first - ) - expect(versions(res)).to eq %w[1.7.7 1.8.0 1.7.8 1.7.9] + context "when level is major" do + before { gvp.level = :major } + + it "orders by version" do + versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") + expect(versions).to eq %w[2.1.0 2.0.1 1.0.0 0.9.0 0.3.1 0.3.0 0.2.0] + end end - it "when unlocking do proper integer comparison, not string" do - unlocking(:level => :patch) - res = @gvp.sort_dep_specs( - build_spec_groups("foo", %w[1.7.7 1.7.8 1.7.9 1.7.15 1.8.0]), - build_spec("foo", "1.7.8").first - ) - expect(versions(res)).to eq %w[1.7.7 1.8.0 1.7.8 1.7.9 1.7.15] + context "when level is minor" do + before { gvp.level = :minor } + + it "favors minor upgrades, then patch upgrades, then major upgrades, then downgrades" do + versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") + expect(versions).to eq %w[0.9.0 0.3.1 0.3.0 1.0.0 2.1.0 2.0.1 0.2.0] + end end - it "leave current when unlocking but already at latest release" do - unlocking(:level => :patch) - res = @gvp.sort_dep_specs( - build_spec_groups("foo", %w[1.7.9 1.8.0 2.0.0]), - build_spec("foo", "1.7.9").first - ) - expect(versions(res)).to eq %w[2.0.0 1.8.0 1.7.9] + context "when level is patch" do + before { gvp.level = :patch } + + it "favors patch upgrades, then minor upgrades, then major upgrades, then downgrades" do + versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") + expect(versions).to eq %w[0.3.1 0.3.0 0.9.0 1.0.0 2.0.1 2.1.0 0.2.0] + end end end - context "sort specs (not strict) level minor" do - it "when unlocking favor next release, then minor increase over current" do - unlocking(:level => :minor) - res = @gvp.sort_dep_specs( - build_spec_groups("foo", %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.0 2.0.1]), - build_spec("foo", "0.2.0").first - ) - expect(versions(res)).to eq %w[2.0.0 2.0.1 1.0.0 0.2.0 0.3.0 0.3.1 0.9.0] + context "when pre" do + before { gvp.pre = true } + + it "sorts regardless of prerelease status" do + versions = sorted_versions(candidates: %w[1.7.7.pre 1.8.0 1.8.1.pre 1.8.1 2.0.0.pre 2.0.0], current: "1.8.0") + expect(versions).to eq %w[2.0.0 2.0.0.pre 1.8.1 1.8.1.pre 1.8.0 1.7.7.pre] end end - context "level error handling" do - subject { Bundler::GemVersionPromoter.new } + context "when not pre" do + before { gvp.pre = false } - it "should raise if not major, minor or patch is passed" do - expect { subject.level = :minjor }.to raise_error ArgumentError + it "deprioritizes prerelease gems" do + versions = sorted_versions(candidates: %w[1.7.7.pre 1.8.0 1.8.1.pre 1.8.1 2.0.0.pre 2.0.0], current: "1.8.0") + expect(versions).to eq %w[2.0.0 1.8.1 1.8.0 2.0.0.pre 1.8.1.pre 1.7.7.pre] end + end - it "should raise if invalid classes passed" do - [123, nil].each do |value| - expect { subject.level = value }.to raise_error ArgumentError - end + context "when locking and not major" do + before { gvp.level = :minor } + + it "keeps the current version first" do + versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.1.0 2.0.1], current: "0.3.0", locked: ["bar"]) + expect(versions.first).to eq("0.3.0") end + end + end - it "should accept major, minor patch symbols" do - [:major, :minor, :patch].each do |value| - subject.level = value - expect(subject.level).to eq value - end + describe "#level=" do + subject { described_class.new } + + it "should raise if not major, minor or patch is passed" do + expect { subject.level = :minjor }.to raise_error ArgumentError + end + + it "should raise if invalid classes passed" do + [123, nil].each do |value| + expect { subject.level = value }.to raise_error ArgumentError end + end - it "should accept major, minor patch strings" do - %w[major minor patch].each do |value| - subject.level = value - expect(subject.level).to eq value.to_sym - end + it "should accept major, minor patch symbols" do + [:major, :minor, :patch].each do |value| + subject.level = value + expect(subject.level).to eq value end end - context "debug output" do - it "should not kerblooie on its own debug output" do - gvp = unlocking(:level => :patch) - dep = Bundler::DepProxy.get_proxy(dep("foo", "1.2.0").first, "ruby") - result = gvp.send(:debug_format_result, dep, build_spec_groups("foo", %w[1.2.0 1.3.0])) - expect(result.class).to eq Array + it "should accept major, minor patch strings" do + %w[major minor patch].each do |value| + subject.level = value + expect(subject.level).to eq value.to_sym end end end diff --git a/spec/bundler/bundler/installer/gem_installer_spec.rb b/spec/bundler/bundler/installer/gem_installer_spec.rb index 8f8d1c6d15..4b6a07f344 100644 --- a/spec/bundler/bundler/installer/gem_installer_spec.rb +++ b/spec/bundler/bundler/installer/gem_installer_spec.rb @@ -3,15 +3,19 @@ require "bundler/installer/gem_installer" RSpec.describe Bundler::GemInstaller do - let(:installer) { instance_double("Installer") } + let(:definition) { instance_double("Definition", locked_gems: nil) } + let(:installer) { instance_double("Installer", definition: definition) } let(:spec_source) { instance_double("SpecSource") } - let(:spec) { instance_double("Specification", :name => "dummy", :version => "0.0.1", :loaded_from => "dummy", :source => spec_source) } + let(:spec) { instance_double("Specification", name: "dummy", version: "0.0.1", loaded_from: "dummy", source: spec_source) } subject { described_class.new(spec, installer) } context "spec_settings is nil" do it "invokes install method with empty build_args" do - allow(spec_source).to receive(:install).with(spec, :force => false, :ensure_builtin_gems_cached => false, :build_args => []) + allow(spec_source).to receive(:install).with( + spec, + { force: false, ensure_builtin_gems_cached: false, build_args: [], previous_spec: nil } + ) subject.install_from_spec end end @@ -22,7 +26,10 @@ RSpec.describe Bundler::GemInstaller do allow(Bundler.settings).to receive(:[]).with(:inline) allow(Bundler.settings).to receive(:[]).with(:forget_cli_options) allow(Bundler.settings).to receive(:[]).with("build.dummy").and_return("--with-dummy-config=dummy") - expect(spec_source).to receive(:install).with(spec, :force => false, :ensure_builtin_gems_cached => false, :build_args => ["--with-dummy-config=dummy"]) + expect(spec_source).to receive(:install).with( + spec, + { force: false, ensure_builtin_gems_cached: false, build_args: ["--with-dummy-config=dummy"], previous_spec: nil } + ) subject.install_from_spec end end @@ -33,7 +40,10 @@ RSpec.describe Bundler::GemInstaller do allow(Bundler.settings).to receive(:[]).with(:inline) allow(Bundler.settings).to receive(:[]).with(:forget_cli_options) allow(Bundler.settings).to receive(:[]).with("build.dummy").and_return("--with-dummy-config=dummy --with-another-dummy-config") - expect(spec_source).to receive(:install).with(spec, :force => false, :ensure_builtin_gems_cached => false, :build_args => ["--with-dummy-config=dummy", "--with-another-dummy-config"]) + expect(spec_source).to receive(:install).with( + spec, + { force: false, ensure_builtin_gems_cached: false, build_args: ["--with-dummy-config=dummy", "--with-another-dummy-config"], previous_spec: nil } + ) subject.install_from_spec end end diff --git a/spec/bundler/bundler/installer/parallel_installer_spec.rb b/spec/bundler/bundler/installer/parallel_installer_spec.rb deleted file mode 100644 index e680633862..0000000000 --- a/spec/bundler/bundler/installer/parallel_installer_spec.rb +++ /dev/null @@ -1,80 +0,0 @@ -# frozen_string_literal: true - -require "bundler/installer/parallel_installer" - -RSpec.describe Bundler::ParallelInstaller do - let(:installer) { instance_double("Installer") } - let(:all_specs) { [] } - let(:size) { 1 } - let(:standalone) { false } - let(:force) { false } - - subject { described_class.new(installer, all_specs, size, standalone, force) } - - context "when dependencies that are not on the overall installation list are the only ones not installed" do - let(:all_specs) do - [ - build_spec("alpha", "1.0") {|s| s.runtime "a", "1" }, - ].flatten - end - - it "prints a warning" do - expect(Bundler.ui).to receive(:warn).with(<<-W.strip) -Your lockfile was created by an old Bundler that left some things out. -You can fix this by adding the missing gems to your Gemfile, running bundle install, and then removing the gems from your Gemfile. -The missing gems are: -* a depended upon by alpha - W - subject.check_for_corrupt_lockfile - end - - context "when size > 1" do - let(:size) { 500 } - - it "prints a warning and sets size to 1" do - expect(Bundler.ui).to receive(:warn).with(<<-W.strip) -Your lockfile was created by an old Bundler that left some things out. -Because of the missing DEPENDENCIES, we can only install gems one at a time, instead of installing 500 at a time. -You can fix this by adding the missing gems to your Gemfile, running bundle install, and then removing the gems from your Gemfile. -The missing gems are: -* a depended upon by alpha - W - subject.check_for_corrupt_lockfile - expect(subject.size).to eq(1) - end - end - end - - context "when the spec set is not a valid resolution" do - let(:all_specs) do - [ - build_spec("cucumber", "4.1.0") {|s| s.runtime "diff-lcs", "< 1.4" }, - build_spec("diff-lcs", "1.4.4"), - ].flatten - end - - it "prints a warning" do - expect(Bundler.ui).to receive(:warn).with(<<-W.strip) -Your lockfile doesn't include a valid resolution. -You can fix this by regenerating your lockfile or trying to manually editing the bad locked gems to a version that satisfies all dependencies. -The unmet dependencies are: -* diff-lcs (< 1.4), depended upon cucumber-4.1.0, unsatisfied by diff-lcs-1.4.4 - W - subject.check_for_unmet_dependencies - end - end - - context "when the spec set is a valid resolution" do - let(:all_specs) do - [ - build_spec("cucumber", "4.1.0") {|s| s.runtime "diff-lcs", "< 1.4" }, - build_spec("diff-lcs", "1.3"), - ].flatten - end - - it "doesn't print a warning" do - expect(Bundler.ui).not_to receive(:warn) - subject.check_for_unmet_dependencies - end - end -end diff --git a/spec/bundler/bundler/installer/spec_installation_spec.rb b/spec/bundler/bundler/installer/spec_installation_spec.rb index e63ef26cb3..cbe2589b99 100644 --- a/spec/bundler/bundler/installer/spec_installation_spec.rb +++ b/spec/bundler/bundler/installer/spec_installation_spec.rb @@ -42,24 +42,26 @@ RSpec.describe Bundler::ParallelInstaller::SpecInstallation do context "when all dependencies are installed" do it "returns true" do dependencies = [] - dependencies << instance_double("SpecInstallation", :spec => "alpha", :name => "alpha", :installed? => true, :all_dependencies => [], :type => :production) - dependencies << instance_double("SpecInstallation", :spec => "beta", :name => "beta", :installed? => true, :all_dependencies => [], :type => :production) - all_specs = dependencies + [instance_double("SpecInstallation", :spec => "gamma", :name => "gamma", :installed? => false, :all_dependencies => [], :type => :production)] + dependencies << instance_double("SpecInstallation", spec: "alpha", name: "alpha", installed?: true, all_dependencies: [], type: :production) + dependencies << instance_double("SpecInstallation", spec: "beta", name: "beta", installed?: true, all_dependencies: [], type: :production) + all_specs = dependencies + [instance_double("SpecInstallation", spec: "gamma", name: "gamma", installed?: false, all_dependencies: [], type: :production)] spec = described_class.new(dep) allow(spec).to receive(:all_dependencies).and_return(dependencies) - expect(spec.dependencies_installed?(all_specs)).to be_truthy + installed_specs = all_specs.select(&:installed?).map {|s| [s.name, true] }.to_h + expect(spec.dependencies_installed?(installed_specs)).to be_truthy end end context "when all dependencies are not installed" do it "returns false" do dependencies = [] - dependencies << instance_double("SpecInstallation", :spec => "alpha", :name => "alpha", :installed? => false, :all_dependencies => [], :type => :production) - dependencies << instance_double("SpecInstallation", :spec => "beta", :name => "beta", :installed? => true, :all_dependencies => [], :type => :production) - all_specs = dependencies + [instance_double("SpecInstallation", :spec => "gamma", :name => "gamma", :installed? => false, :all_dependencies => [], :type => :production)] + dependencies << instance_double("SpecInstallation", spec: "alpha", name: "alpha", installed?: false, all_dependencies: [], type: :production) + dependencies << instance_double("SpecInstallation", spec: "beta", name: "beta", installed?: true, all_dependencies: [], type: :production) + all_specs = dependencies + [instance_double("SpecInstallation", spec: "gamma", name: "gamma", installed?: false, all_dependencies: [], type: :production)] spec = described_class.new(dep) allow(spec).to receive(:all_dependencies).and_return(dependencies) - expect(spec.dependencies_installed?(all_specs)).to be_falsey + installed_specs = all_specs.select(&:installed?).map {|s| [s.name, true] }.to_h + expect(spec.dependencies_installed?(installed_specs)).to be_falsey end end end diff --git a/spec/bundler/bundler/lockfile_parser_spec.rb b/spec/bundler/bundler/lockfile_parser_spec.rb index 3a6d61336f..88932bf009 100644 --- a/spec/bundler/bundler/lockfile_parser_spec.rb +++ b/spec/bundler/bundler/lockfile_parser_spec.rb @@ -3,7 +3,7 @@ require "bundler/lockfile_parser" RSpec.describe Bundler::LockfileParser do - let(:lockfile_contents) { strip_whitespace(<<-L) } + let(:lockfile_contents) { <<~L } GIT remote: https://github.com/alloy/peiji-san.git revision: eca485d8dc95f12aaec1a434b49d295c7e91844b @@ -22,6 +22,9 @@ RSpec.describe Bundler::LockfileParser do peiji-san! rake + CHECKSUMS + rake (10.3.2) sha256=814828c34f1315d7e7b7e8295184577cc4e969bad6156ac069d02d63f58d82e8 + RUBY VERSION ruby 2.1.3p242 @@ -33,13 +36,13 @@ RSpec.describe Bundler::LockfileParser do it "returns the attributes" do attributes = described_class.sections_in_lockfile(lockfile_contents) expect(attributes).to contain_exactly( - "BUNDLED WITH", "DEPENDENCIES", "GEM", "GIT", "PLATFORMS", "RUBY VERSION" + "BUNDLED WITH", "CHECKSUMS", "DEPENDENCIES", "GEM", "GIT", "PLATFORMS", "RUBY VERSION" ) end end describe ".unknown_sections_in_lockfile" do - let(:lockfile_contents) { strip_whitespace(<<-L) } + let(:lockfile_contents) { <<~L } UNKNOWN ATTR UNKNOWN ATTR 2 @@ -60,7 +63,7 @@ RSpec.describe Bundler::LockfileParser do it "returns the same as > 1.0" do expect(subject).to contain_exactly( - described_class::BUNDLED, described_class::RUBY, described_class::PLUGIN + described_class::BUNDLED, described_class::CHECKSUMS, described_class::RUBY, described_class::PLUGIN ) end end @@ -70,7 +73,7 @@ RSpec.describe Bundler::LockfileParser do it "returns the same as for the release version" do expect(subject).to contain_exactly( - described_class::RUBY, described_class::PLUGIN + described_class::CHECKSUMS, described_class::RUBY, described_class::PLUGIN ) end end @@ -115,6 +118,14 @@ RSpec.describe Bundler::LockfileParser do let(:platforms) { [rb] } let(:bundler_version) { Gem::Version.new("1.12.0.rc.2") } let(:ruby_version) { "ruby 2.1.3p242" } + let(:lockfile_path) { Bundler.default_lockfile.relative_path_from(Dir.pwd) } + let(:rake_sha256_checksum) do + Bundler::Checksum.from_lock( + "sha256=814828c34f1315d7e7b7e8295184577cc4e969bad6156ac069d02d63f58d82e8", + "#{lockfile_path}:20:17" + ) + end + let(:rake_checksums) { [rake_sha256_checksum] } shared_examples_for "parsing" do it "parses correctly" do @@ -125,6 +136,9 @@ RSpec.describe Bundler::LockfileParser do expect(subject.platforms).to eq platforms expect(subject.bundler_version).to eq bundler_version expect(subject.ruby_version).to eq ruby_version + rake_spec = specs.last + checksums = subject.sources.last.checksum_store.to_lock(specs.last) + expect(checksums).to eq("#{rake_spec.name_tuple.lock_name} #{rake_checksums.map(&:to_lock).sort.join(",")}") end end @@ -149,5 +163,59 @@ RSpec.describe Bundler::LockfileParser do let(:lockfile_contents) { super().sub("peiji-san!", "peiji-san!\n foo: bar") } include_examples "parsing" end + + context "when the checksum is urlsafe base64 encoded" do + let(:lockfile_contents) do + super().sub( + "sha256=814828c34f1315d7e7b7e8295184577cc4e969bad6156ac069d02d63f58d82e8", + "sha256=gUgow08TFdfnt-gpUYRXfMTpabrWFWrAadAtY_WNgug=" + ) + end + include_examples "parsing" + end + + context "when the checksum is of an unknown algorithm" do + let(:rake_sha512_checksum) do + Bundler::Checksum.from_lock( + "sha512=pVDn9GLmcFkz8vj1ueiVxj5uGKkAyaqYjEX8zG6L5O4BeVg3wANaKbQdpj/B82Nd/MHVszy6polHcyotUdwilQ==", + "#{lockfile_path}:20:17" + ) + end + let(:lockfile_contents) do + super().sub( + "sha256=", + "sha512=pVDn9GLmcFkz8vj1ueiVxj5uGKkAyaqYjEX8zG6L5O4BeVg3wANaKbQdpj/B82Nd/MHVszy6polHcyotUdwilQ==,sha256=" + ) + end + let(:rake_checksums) { [rake_sha256_checksum, rake_sha512_checksum] } + include_examples "parsing" + end + + context "when CHECKSUMS has duplicate checksums in the lockfile that don't match" do + let(:bad_checksum) { "sha256=c0ffee11c0ffee11c0ffee11c0ffee11c0ffee11c0ffee11c0ffee11c0ffee11" } + let(:lockfile_contents) { super().split(/(?<=CHECKSUMS\n)/m).insert(1, " rake (10.3.2) #{bad_checksum}\n").join } + + it "raises a security error" do + expect { subject }.to raise_error(Bundler::SecurityError) do |e| + expect(e.message).to match <<~MESSAGE + Bundler found mismatched checksums. This is a potential security risk. + rake (10.3.2) #{bad_checksum} + from the lockfile CHECKSUMS at #{lockfile_path}:20:17 + rake (10.3.2) #{rake_sha256_checksum.to_lock} + from the lockfile CHECKSUMS at #{lockfile_path}:21:17 + + To resolve this issue you can either: + 1. remove the matching checksum in #{lockfile_path}:21:17 + 2. run `bundle install` + or if you are sure that the new checksum from the lockfile CHECKSUMS at #{lockfile_path}:21:17 is correct: + 1. remove the matching checksum in #{lockfile_path}:20:17 + 2. run `bundle install` + + To ignore checksum security warnings, disable checksum validation with + `bundle config set --local disable_checksum_validation true` + MESSAGE + end + end + end end end diff --git a/spec/bundler/bundler/mirror_spec.rb b/spec/bundler/bundler/mirror_spec.rb index 1eaf1e9a8e..ba1c6ed413 100644 --- a/spec/bundler/bundler/mirror_spec.rb +++ b/spec/bundler/bundler/mirror_spec.rb @@ -36,12 +36,12 @@ RSpec.describe Bundler::Settings::Mirror do it "takes a string for the uri but returns an uri object" do mirror.uri = "http://localhost:9292" - expect(mirror.uri).to eq(Bundler::URI("http://localhost:9292")) + expect(mirror.uri).to eq(Gem::URI("http://localhost:9292")) end it "takes an uri object for the uri" do - mirror.uri = Bundler::URI("http://localhost:9293") - expect(mirror.uri).to eq(Bundler::URI("http://localhost:9293")) + mirror.uri = Gem::URI("http://localhost:9293") + expect(mirror.uri).to eq(Gem::URI("http://localhost:9293")) end context "without a uri" do @@ -145,7 +145,7 @@ RSpec.describe Bundler::Settings::Mirror do end RSpec.describe Bundler::Settings::Mirrors do - let(:localhost_uri) { Bundler::URI("http://localhost:9292") } + let(:localhost_uri) { Gem::URI("http://localhost:9292") } context "with a just created mirror" do let(:mirrors) do @@ -260,7 +260,7 @@ RSpec.describe Bundler::Settings::Mirrors do before { mirrors.parse("mirror.all.fallback_timeout", "true") } it "returns the source uri, not localhost" do - expect(mirrors.for("http://whatever.com").uri).to eq(Bundler::URI("http://whatever.com/")) + expect(mirrors.for("http://whatever.com").uri).to eq(Gem::URI("http://whatever.com/")) end end end @@ -270,7 +270,7 @@ RSpec.describe Bundler::Settings::Mirrors do context "without a fallback timeout" do it "returns the uri that is not mirrored" do - expect(mirrors.for("http://whatever.com").uri).to eq(Bundler::URI("http://whatever.com/")) + expect(mirrors.for("http://whatever.com").uri).to eq(Gem::URI("http://whatever.com/")) end it "returns localhost for rubygems.org" do @@ -282,11 +282,11 @@ RSpec.describe Bundler::Settings::Mirrors do before { mirrors.parse("mirror.http://rubygems.org/.fallback_timeout", "true") } it "returns the uri that is not mirrored" do - expect(mirrors.for("http://whatever.com").uri).to eq(Bundler::URI("http://whatever.com/")) + expect(mirrors.for("http://whatever.com").uri).to eq(Gem::URI("http://whatever.com/")) end it "returns rubygems.org for rubygems.org" do - expect(mirrors.for("http://rubygems.org/").uri).to eq(Bundler::URI("http://rubygems.org/")) + expect(mirrors.for("http://rubygems.org/").uri).to eq(Gem::URI("http://rubygems.org/")) end end end diff --git a/spec/bundler/bundler/plugin/api/source_spec.rb b/spec/bundler/bundler/plugin/api/source_spec.rb index 428ceb220a..ae02e08bea 100644 --- a/spec/bundler/bundler/plugin/api/source_spec.rb +++ b/spec/bundler/bundler/plugin/api/source_spec.rb @@ -51,7 +51,7 @@ RSpec.describe Bundler::Plugin::API::Source do context "to_lock" do it "returns the string with remote and type" do - expected = strip_whitespace <<-L + expected = <<~L PLUGIN SOURCE remote: #{uri} type: #{type} @@ -67,7 +67,7 @@ RSpec.describe Bundler::Plugin::API::Source do end it "includes them" do - expected = strip_whitespace <<-L + expected = <<~L PLUGIN SOURCE remote: #{uri} type: #{type} diff --git a/spec/bundler/bundler/plugin/dsl_spec.rb b/spec/bundler/bundler/plugin/dsl_spec.rb index 00e39dca69..235a549735 100644 --- a/spec/bundler/bundler/plugin/dsl_spec.rb +++ b/spec/bundler/bundler/plugin/dsl_spec.rb @@ -23,7 +23,7 @@ RSpec.describe Bundler::Plugin::DSL do it "adds #source with :type to list and also inferred_plugins list" do expect(dsl).to receive(:plugin).with("bundler-source-news").once - dsl.source("some_random_url", :type => "news") {} + dsl.source("some_random_url", type: "news") {} expect(dsl.inferred_plugins).to eq(["bundler-source-news"]) end @@ -31,8 +31,8 @@ RSpec.describe Bundler::Plugin::DSL do it "registers a source type plugin only once for multiple declarations" do expect(dsl).to receive(:plugin).with("bundler-source-news").and_call_original.once - dsl.source("some_random_url", :type => "news") {} - dsl.source("another_random_url", :type => "news") {} + dsl.source("some_random_url", type: "news") {} + dsl.source("another_random_url", type: "news") {} end end end diff --git a/spec/bundler/bundler/plugin/index_spec.rb b/spec/bundler/bundler/plugin/index_spec.rb index d34b0de342..5a7047459f 100644 --- a/spec/bundler/bundler/plugin/index_spec.rb +++ b/spec/bundler/bundler/plugin/index_spec.rb @@ -140,7 +140,7 @@ RSpec.describe Bundler::Plugin::Index do describe "after conflict" do let(:commands) { ["foo"] } let(:sources) { ["bar"] } - let(:hooks) { ["hoook"] } + let(:hooks) { ["thehook"] } shared_examples "it cleans up" do it "the path" do @@ -156,7 +156,7 @@ RSpec.describe Bundler::Plugin::Index do end it "the hook" do - expect(index.hook_plugins("xhoook")).to be_empty + expect(index.hook_plugins("xthehook")).to be_empty end end @@ -164,7 +164,7 @@ RSpec.describe Bundler::Plugin::Index do before do expect do path = lib_path("cplugin") - index.register_plugin("cplugin", path.to_s, [path.join("lib").to_s], ["foo"], ["xbar"], ["xhoook"]) + index.register_plugin("cplugin", path.to_s, [path.join("lib").to_s], ["foo"], ["xbar"], ["xthehook"]) end.to raise_error(Index::CommandConflict) end @@ -175,7 +175,7 @@ RSpec.describe Bundler::Plugin::Index do before do expect do path = lib_path("cplugin") - index.register_plugin("cplugin", path.to_s, [path.join("lib").to_s], ["xfoo"], ["bar"], ["xhoook"]) + index.register_plugin("cplugin", path.to_s, [path.join("lib").to_s], ["xfoo"], ["bar"], ["xthehook"]) end.to raise_error(Index::SourceConflict) end @@ -186,7 +186,7 @@ RSpec.describe Bundler::Plugin::Index do before do expect do path = lib_path("cplugin") - index.register_plugin("cplugin", path.to_s, [path.join("lib").to_s], ["foo"], ["bar"], ["xhoook"]) + index.register_plugin("cplugin", path.to_s, [path.join("lib").to_s], ["foo"], ["bar"], ["xthehook"]) end.to raise_error(Index::CommandConflict) end diff --git a/spec/bundler/bundler/plugin/installer_spec.rb b/spec/bundler/bundler/plugin/installer_spec.rb index e89720f6f7..ed40029f5a 100644 --- a/spec/bundler/bundler/plugin/installer_spec.rb +++ b/spec/bundler/bundler/plugin/installer_spec.rb @@ -6,8 +6,7 @@ RSpec.describe Bundler::Plugin::Installer do describe "cli install" do it "uses Gem.sources when non of the source is provided" do sources = double(:sources) - Bundler.settings # initialize it before we have to touch rubygems.ext_lock - allow(Bundler).to receive_message_chain("rubygems.sources") { sources } + allow(Gem).to receive(:sources) { sources } allow(installer).to receive(:install_rubygems). with("new-plugin", [">= 0"], sources).once @@ -21,15 +20,15 @@ RSpec.describe Bundler::Plugin::Installer do allow(installer).to receive(:install_git). and_return("new-plugin" => spec) - expect(installer.install(["new-plugin"], :git => "https://some.ran/dom")). + expect(installer.install(["new-plugin"], git: "https://some.ran/dom")). to eq("new-plugin" => spec) end it "returns the installed spec after installing local git plugins" do - allow(installer).to receive(:install_local_git). + allow(installer).to receive(:install_git). and_return("new-plugin" => spec) - expect(installer.install(["new-plugin"], :local_git => "/phony/path/repo")). + expect(installer.install(["new-plugin"], git: "/phony/path/repo")). to eq("new-plugin" => spec) end @@ -37,7 +36,7 @@ RSpec.describe Bundler::Plugin::Installer do allow(installer).to receive(:install_rubygems). and_return("new-plugin" => spec) - expect(installer.install(["new-plugin"], :source => "https://some.ran/dom")). + expect(installer.install(["new-plugin"], source: "https://some.ran/dom")). to eq("new-plugin" => spec) end end @@ -52,13 +51,13 @@ RSpec.describe Bundler::Plugin::Installer do context "git plugins" do before do - build_git "ga-plugin", :path => lib_path("ga-plugin") do |s| + build_git "ga-plugin", path: lib_path("ga-plugin") do |s| s.write "plugins.rb" end end let(:result) do - installer.install(["ga-plugin"], :git => file_uri_for(lib_path("ga-plugin"))) + installer.install(["ga-plugin"], git: file_uri_for(lib_path("ga-plugin"))) end it "returns the installed spec after installing" do @@ -75,13 +74,13 @@ RSpec.describe Bundler::Plugin::Installer do context "local git plugins" do before do - build_git "ga-plugin", :path => lib_path("ga-plugin") do |s| + build_git "ga-plugin", path: lib_path("ga-plugin") do |s| s.write "plugins.rb" end end let(:result) do - installer.install(["ga-plugin"], :local_git => lib_path("ga-plugin").to_s) + installer.install(["ga-plugin"], git: lib_path("ga-plugin").to_s) end it "returns the installed spec after installing" do @@ -98,7 +97,7 @@ RSpec.describe Bundler::Plugin::Installer do context "rubygems plugins" do let(:result) do - installer.install(["re-plugin"], :source => file_uri_for(gem_repo2)) + installer.install(["re-plugin"], source: file_uri_for(gem_repo2)) end it "returns the installed spec after installing " do @@ -113,7 +112,7 @@ RSpec.describe Bundler::Plugin::Installer do context "multiple plugins" do let(:result) do - installer.install(["re-plugin", "ma-plugin"], :source => file_uri_for(gem_repo2)) + installer.install(["re-plugin", "ma-plugin"], source: file_uri_for(gem_repo2)) end it "returns the installed spec after installing " do diff --git a/spec/bundler/bundler/plugin_spec.rb b/spec/bundler/bundler/plugin_spec.rb index d1a05bbad7..f41b4eff3a 100644 --- a/spec/bundler/bundler/plugin_spec.rb +++ b/spec/bundler/bundler/plugin_spec.rb @@ -9,11 +9,11 @@ RSpec.describe Bundler::Plugin do let(:spec2) { double(:spec2) } before do - build_lib "new-plugin", :path => lib_path("new-plugin") do |s| + build_lib "new-plugin", path: lib_path("new-plugin") do |s| s.write "plugins.rb" end - build_lib "another-plugin", :path => lib_path("another-plugin") do |s| + build_lib "another-plugin", path: lib_path("another-plugin") do |s| s.write "plugins.rb" end @@ -65,6 +65,8 @@ RSpec.describe Bundler::Plugin do end it "passes the name and options to installer" do + allow(index).to receive(:installed?). + with("new-plugin") allow(installer).to receive(:install).with(["new-plugin"], opts) do { "new-plugin" => spec } end.once @@ -73,6 +75,8 @@ RSpec.describe Bundler::Plugin do end it "validates the installed plugin" do + allow(index).to receive(:installed?). + with("new-plugin") allow(subject). to receive(:validate_plugin!).with(lib_path("new-plugin")).once @@ -80,6 +84,8 @@ RSpec.describe Bundler::Plugin do end it "registers the plugin with index" do + allow(index).to receive(:installed?). + with("new-plugin") allow(index).to receive(:register_plugin). with("new-plugin", lib_path("new-plugin").to_s, [lib_path("new-plugin").join("lib").to_s], []).once subject.install ["new-plugin"], opts @@ -96,6 +102,7 @@ RSpec.describe Bundler::Plugin do end.once allow(subject).to receive(:validate_plugin!).twice + allow(index).to receive(:installed?).twice allow(index).to receive(:register_plugin).twice subject.install ["new-plugin", "another-plugin"], opts end @@ -218,7 +225,7 @@ RSpec.describe Bundler::Plugin do end end - describe "#source_from_lock" do + describe "#from_lock" do it "returns instance of registered class initialized with locked opts" do opts = { "type" => "l_source", "remote" => "xyz", "other" => "random" } allow(index).to receive(:source_plugin).with("l_source") { "plugin_name" } @@ -229,7 +236,7 @@ RSpec.describe Bundler::Plugin do expect(SClass).to receive(:new). with(hash_including("type" => "l_source", "uri" => "xyz", "other" => "random")) { s_instance } - expect(subject.source_from_lock(opts)).to be(s_instance) + expect(subject.from_lock(opts)).to be(s_instance) end end @@ -268,17 +275,17 @@ RSpec.describe Bundler::Plugin do describe "#hook" do before do path = lib_path("foo-plugin") - build_lib "foo-plugin", :path => path do |s| + build_lib "foo-plugin", path: path do |s| s.write "plugins.rb", code end Bundler::Plugin::Events.send(:reset) - Bundler::Plugin::Events.send(:define, :EVENT_1, "event-1") - Bundler::Plugin::Events.send(:define, :EVENT_2, "event-2") + Bundler::Plugin::Events.send(:define, :EVENT1, "event-1") + Bundler::Plugin::Events.send(:define, :EVENT2, "event-2") - allow(index).to receive(:hook_plugins).with(Bundler::Plugin::Events::EVENT_1). + allow(index).to receive(:hook_plugins).with(Bundler::Plugin::Events::EVENT1). and_return(["foo-plugin", "", nil]) - allow(index).to receive(:hook_plugins).with(Bundler::Plugin::Events::EVENT_2). + allow(index).to receive(:hook_plugins).with(Bundler::Plugin::Events::EVENT2). and_return(["foo-plugin"]) allow(index).to receive(:plugin_path).with("foo-plugin").and_return(path) allow(index).to receive(:load_paths).with("foo-plugin").and_return([]) @@ -296,33 +303,33 @@ RSpec.describe Bundler::Plugin do it "executes the hook" do expect do - Plugin.hook(Bundler::Plugin::Events::EVENT_1) + Plugin.hook(Bundler::Plugin::Events::EVENT1) end.to output("hook for event 1\n").to_stdout end context "single plugin declaring more than one hook" do let(:code) { <<-RUBY } - Bundler::Plugin::API.hook(Bundler::Plugin::Events::EVENT_1) {} - Bundler::Plugin::API.hook(Bundler::Plugin::Events::EVENT_2) {} + Bundler::Plugin::API.hook(Bundler::Plugin::Events::EVENT1) {} + Bundler::Plugin::API.hook(Bundler::Plugin::Events::EVENT2) {} puts "loaded" RUBY it "evals plugins.rb once" do expect do - Plugin.hook(Bundler::Plugin::Events::EVENT_1) - Plugin.hook(Bundler::Plugin::Events::EVENT_2) + Plugin.hook(Bundler::Plugin::Events::EVENT1) + Plugin.hook(Bundler::Plugin::Events::EVENT2) end.to output("loaded\n").to_stdout end end context "a block is passed" do let(:code) { <<-RUBY } - Bundler::Plugin::API.hook(Bundler::Plugin::Events::EVENT_1) { |&blk| blk.call } + Bundler::Plugin::API.hook(Bundler::Plugin::Events::EVENT1) { |&blk| blk.call } RUBY it "is passed to the hook" do expect do - Plugin.hook(Bundler::Plugin::Events::EVENT_1) { puts "win" } + Plugin.hook(Bundler::Plugin::Events::EVENT1) { puts "win" } end.to output("win\n").to_stdout end end diff --git a/spec/bundler/bundler/psyched_yaml_spec.rb b/spec/bundler/bundler/psyched_yaml_spec.rb deleted file mode 100644 index d5d68c5cc3..0000000000 --- a/spec/bundler/bundler/psyched_yaml_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -require "bundler/psyched_yaml" - -RSpec.describe "Bundler::YamlLibrarySyntaxError" do - it "is raised on YAML parse errors" do - expect { YAML.parse "{foo" }.to raise_error(Bundler::YamlLibrarySyntaxError) - end -end diff --git a/spec/bundler/bundler/remote_specification_spec.rb b/spec/bundler/bundler/remote_specification_spec.rb index 8115e026d8..f35b231d58 100644 --- a/spec/bundler/bundler/remote_specification_spec.rb +++ b/spec/bundler/bundler/remote_specification_spec.rb @@ -17,7 +17,7 @@ RSpec.describe Bundler::RemoteSpecification do end describe "#fetch_platform" do - let(:remote_spec) { double(:remote_spec, :platform => "jruby") } + let(:remote_spec) { double(:remote_spec, platform: "jruby") } before { allow(spec_fetcher).to receive(:fetch_spec).and_return(remote_spec) } @@ -45,7 +45,7 @@ RSpec.describe Bundler::RemoteSpecification do let(:platform) { "jruby" } it "should return the spec name, version, and platform" do - expect(subject.full_name).to eq("foo-1.0.0-jruby") + expect(subject.full_name).to eq("foo-1.0.0-java") end end end @@ -113,7 +113,7 @@ RSpec.describe Bundler::RemoteSpecification do context "comparing a non sortable object" do let(:other) { Object.new } - let(:remote_spec) { double(:remote_spec, :platform => "jruby") } + let(:remote_spec) { double(:remote_spec, platform: "jruby") } before do allow(spec_fetcher).to receive(:fetch_spec).and_return(remote_spec) @@ -127,8 +127,8 @@ RSpec.describe Bundler::RemoteSpecification do end describe "#__swap__" do - let(:spec) { double(:spec, :dependencies => []) } - let(:new_spec) { double(:new_spec, :dependencies => [], :runtime_dependencies => []) } + let(:spec) { double(:spec, dependencies: []) } + let(:new_spec) { double(:new_spec, dependencies: [], runtime_dependencies: []) } before { subject.instance_variable_set(:@_remote_specification, spec) } @@ -157,7 +157,7 @@ RSpec.describe Bundler::RemoteSpecification do describe "method missing" do context "and is present in Gem::Specification" do - let(:remote_spec) { double(:remote_spec, :authors => "abcd") } + let(:remote_spec) { double(:remote_spec, authors: "abcd") } before do allow(subject).to receive(:_remote_specification).and_return(remote_spec) @@ -172,7 +172,7 @@ RSpec.describe Bundler::RemoteSpecification do describe "respond to missing?" do context "and is present in Gem::Specification" do - let(:remote_spec) { double(:remote_spec, :authors => "abcd") } + let(:remote_spec) { double(:remote_spec, authors: "abcd") } before do allow(subject).to receive(:_remote_specification).and_return(remote_spec) diff --git a/spec/bundler/bundler/resolver/candidate_spec.rb b/spec/bundler/bundler/resolver/candidate_spec.rb new file mode 100644 index 0000000000..f7b378d32b --- /dev/null +++ b/spec/bundler/bundler/resolver/candidate_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +RSpec.describe Bundler::Resolver::Candidate do + it "compares fine" do + version1 = described_class.new("1.12.5", specs: [Gem::Specification.new("foo", "1.12.5") {|s| s.platform = Gem::Platform::RUBY }]) + version2 = described_class.new("1.12.5") # passing no specs creates a platform specific candidate, so sorts higher + + expect(version2 >= version1).to be true + + expect(version1.generic! == version2.generic!).to be true + expect(version1.platform_specific! == version2.platform_specific!).to be true + + expect(version1.platform_specific! >= version2.generic!).to be true + expect(version2.platform_specific! >= version1.generic!).to be true + + version1 = described_class.new("1.12.5", specs: [Gem::Specification.new("foo", "1.12.5") {|s| s.platform = Gem::Platform::RUBY }]) + version2 = described_class.new("1.12.5", specs: [Gem::Specification.new("foo", "1.12.5") {|s| s.platform = Gem::Platform::X64_LINUX }]) + + expect(version2 >= version1).to be true + end +end diff --git a/spec/bundler/bundler/ruby_dsl_spec.rb b/spec/bundler/bundler/ruby_dsl_spec.rb index bc1ca98457..384ac4b8b2 100644 --- a/spec/bundler/bundler/ruby_dsl_spec.rb +++ b/spec/bundler/bundler/ruby_dsl_spec.rb @@ -7,28 +7,37 @@ RSpec.describe Bundler::RubyDsl do include Bundler::RubyDsl attr_reader :ruby_version + attr_accessor :gemfile end let(:dsl) { MockDSL.new } let(:ruby_version) { "2.0.0" } + let(:ruby_version_arg) { ruby_version } let(:version) { "2.0.0" } let(:engine) { "jruby" } let(:engine_version) { "9000" } let(:patchlevel) { "100" } let(:options) do - { :patchlevel => patchlevel, - :engine => engine, - :engine_version => engine_version } + { patchlevel: patchlevel, + engine: engine, + engine_version: engine_version } end + let(:project_root) { Pathname.new("/path/to/project") } + let(:gemfile) { project_root.join("Gemfile") } + before { allow(Bundler).to receive(:root).and_return(project_root) } let(:invoke) do proc do - args = Array(ruby_version) + [options] + args = [] + args << ruby_version_arg if ruby_version_arg + args << options + dsl.ruby(*args) end end subject do + dsl.gemfile = gemfile invoke.call dsl.ruby_version end @@ -59,6 +68,15 @@ RSpec.describe Bundler::RubyDsl do it_behaves_like "it stores the ruby version" end + context "with a preview version" do + let(:ruby_version) { "3.3.0-preview2" } + + it "stores the version" do + expect(subject.versions).to eq(Array("3.3.0.preview2")) + expect(subject.gem_version.version).to eq("3.3.0.preview2") + end + end + context "with two requirements in the same string" do let(:ruby_version) { ">= 2.0.0, < 3.0" } it "raises an error" do @@ -91,5 +109,94 @@ RSpec.describe Bundler::RubyDsl do it_behaves_like "it stores the ruby version" end end + + context "with a file option" do + let(:file) { ".ruby-version" } + let(:ruby_version_file_path) { gemfile.dirname.join(file) } + let(:options) do + { file: file, + patchlevel: patchlevel, + engine: engine, + engine_version: engine_version } + end + let(:ruby_version_arg) { nil } + let(:file_content) { "#{version}\n" } + + before do + allow(Bundler).to receive(:read_file) do |path| + raise Errno::ENOENT, <<~ERROR unless path == ruby_version_file_path + #{file} not found in specs: + expected: #{ruby_version_file_path} + received: #{path} + ERROR + file_content + end + end + + it_behaves_like "it stores the ruby version" + + context "with the Gemfile ruby file: path is relative to the Gemfile in a subdir" do + let(:gemfile) { project_root.join("subdir", "Gemfile") } + let(:file) { "../.ruby-version" } + let(:ruby_version_file_path) { gemfile.dirname.join(file) } + + it_behaves_like "it stores the ruby version" + end + + context "with bundler root in a subdir of the project" do + let(:project_root) { Pathname.new("/path/to/project/subdir") } + let(:gemfile) { project_root.parent.join("Gemfile") } + + it_behaves_like "it stores the ruby version" + end + + context "with the ruby- prefix in the file" do + let(:file_content) { "ruby-#{version}\n" } + + it_behaves_like "it stores the ruby version" + end + + context "and a version" do + let(:ruby_version_arg) { version } + + it "raises an error" do + expect { subject }.to raise_error(Bundler::GemfileError, "Do not pass version argument when using :file option") + end + end + + context "with a @gemset" do + let(:file_content) { "ruby-#{version}@gemset\n" } + + it "raises an error" do + expect { subject }.to raise_error(Gem::Requirement::BadRequirementError, "Illformed requirement [\"#{version}@gemset\"]") + end + end + + context "with a .tool-versions file format" do + let(:file) { ".tool-versions" } + let(:ruby_version_arg) { nil } + let(:file_content) do + <<~TOOLS + nodejs 18.16.0 + ruby #{version} # This is a comment + pnpm 8.6.12 + TOOLS + end + + it_behaves_like "it stores the ruby version" + + context "with extra spaces and a very cozy comment" do + let(:file_content) do + <<~TOOLS + nodejs 18.16.0 + ruby #{version}# This is a cozy comment + pnpm 8.6.12 + TOOLS + end + + it_behaves_like "it stores the ruby version" + end + end + end end end diff --git a/spec/bundler/bundler/ruby_version_spec.rb b/spec/bundler/bundler/ruby_version_spec.rb index 8c6c071d7f..39d0571361 100644 --- a/spec/bundler/bundler/ruby_version_spec.rb +++ b/spec/bundler/bundler/ruby_version_spec.rb @@ -400,19 +400,19 @@ RSpec.describe "Bundler::RubyVersion and its subclasses" do let(:bundler_system_ruby_version) { subject } around do |example| - if Bundler::RubyVersion.instance_variable_defined?("@ruby_version") + if Bundler::RubyVersion.instance_variable_defined?("@system") begin - old_ruby_version = Bundler::RubyVersion.instance_variable_get("@ruby_version") - Bundler::RubyVersion.remove_instance_variable("@ruby_version") + old_ruby_version = Bundler::RubyVersion.instance_variable_get("@system") + Bundler::RubyVersion.remove_instance_variable("@system") example.run ensure - Bundler::RubyVersion.instance_variable_set("@ruby_version", old_ruby_version) + Bundler::RubyVersion.instance_variable_set("@system", old_ruby_version) end else begin example.run ensure - Bundler::RubyVersion.remove_instance_variable("@ruby_version") + Bundler::RubyVersion.remove_instance_variable("@system") end end end @@ -427,9 +427,8 @@ RSpec.describe "Bundler::RubyVersion and its subclasses" do end describe "#version" do - it "should return a copy of the value of RUBY_VERSION" do - expect(subject.versions).to eq([RUBY_VERSION]) - expect(subject.versions.first).to_not be(RUBY_VERSION) + it "should return the value of Gem.ruby_version as a string" do + expect(subject.versions).to eq([Gem.ruby_version.to_s]) end end @@ -446,13 +445,12 @@ RSpec.describe "Bundler::RubyVersion and its subclasses" do describe "#engine_version" do context "engine is ruby" do before do - stub_const("RUBY_ENGINE_VERSION", "2.2.4") + allow(Gem).to receive(:ruby_version).and_return(Gem::Version.new("2.2.4")) stub_const("RUBY_ENGINE", "ruby") end - it "should return a copy of the value of RUBY_ENGINE_VERSION" do + it "should return the value of Gem.ruby_version as a string" do expect(bundler_system_ruby_version.engine_versions).to eq(["2.2.4"]) - expect(bundler_system_ruby_version.engine_versions.first).to_not be(RUBY_ENGINE_VERSION) end end @@ -498,31 +496,5 @@ RSpec.describe "Bundler::RubyVersion and its subclasses" do end end end - - describe "#to_gem_version_with_patchlevel" do - shared_examples_for "the patchlevel is omitted" do - it "does not include a patch level" do - expect(subject.to_gem_version_with_patchlevel.to_s).to eq(version) - end - end - - context "with nil patch number" do - let(:patchlevel) { nil } - - it_behaves_like "the patchlevel is omitted" - end - - context "with negative patch number" do - let(:patchlevel) { -1 } - - it_behaves_like "the patchlevel is omitted" - end - - context "with a valid patch number" do - it "uses the specified patchlevel as patchlevel" do - expect(subject.to_gem_version_with_patchlevel.to_s).to eq("#{version}.#{patchlevel}") - end - end - end end end diff --git a/spec/bundler/bundler/rubygems_integration_spec.rb b/spec/bundler/bundler/rubygems_integration_spec.rb index 11fa2f4e0d..b6bda9f43e 100644 --- a/spec/bundler/bundler/rubygems_integration_spec.rb +++ b/spec/bundler/bundler/rubygems_integration_spec.rb @@ -1,10 +1,6 @@ # frozen_string_literal: true RSpec.describe Bundler::RubygemsIntegration do - it "uses the same chdir lock as rubygems" do - expect(Bundler.rubygems.ext_lock).to eq(Gem::Ext::Builder::CHDIR_MONITOR) - end - context "#validate" do let(:spec) do Gem::Specification.new do |s| @@ -34,35 +30,24 @@ RSpec.describe Bundler::RubygemsIntegration do end end - describe "#configuration" do - it "handles Gem::SystemExitException errors" do - allow(Gem).to receive(:configuration) { raise Gem::SystemExitException.new(1) } - expect { Bundler.rubygems.configuration }.to raise_error(Gem::SystemExitException) - end - end - describe "#download_gem" do let(:bundler_retry) { double(Bundler::Retry) } - let(:retry) { double("Bundler::Retry") } - let(:uri) { Bundler::URI.parse("https://foo.bar") } - let(:path) { Gem.path.first } + let(:uri) { Gem::URI.parse("https://foo.bar") } + let(:cache_dir) { "#{Gem.path.first}/cache" } let(:spec) do - spec = Bundler::RemoteSpecification.new("Foo", Gem::Version.new("2.5.2"), - Gem::Platform::RUBY, nil) + spec = Gem::Specification.new("Foo", Gem::Version.new("2.5.2")) spec.remote = Bundler::Source::Rubygems::Remote.new(uri.to_s) spec end let(:fetcher) { double("gem_remote_fetcher") } it "successfully downloads gem with retries" do - expect(Bundler.rubygems).to receive(:gem_remote_fetcher).and_return(fetcher) - expect(fetcher).to receive(:headers=).with("X-Gemfile-Source" => "https://foo.bar") expect(Bundler::Retry).to receive(:new).with("download gem from #{uri}/"). and_return(bundler_retry) expect(bundler_retry).to receive(:attempts).and_yield - expect(fetcher).to receive(:download).with(spec, uri, path) + expect(fetcher).to receive(:cache_update_path) - Bundler.rubygems.download_gem(spec, uri, path) + Bundler.rubygems.download_gem(spec, uri, cache_dir, fetcher) end end @@ -73,30 +58,36 @@ RSpec.describe Bundler::RubygemsIntegration do let(:prerelease_specs_response) { Marshal.dump(["prerelease_specs"]) } context "when a rubygems source mirror is set" do - let(:orig_uri) { Bundler::URI("http://zombo.com") } - let(:remote_with_mirror) { double("remote", :uri => uri, :original_uri => orig_uri) } + let(:orig_uri) { Gem::URI("http://zombo.com") } + let(:remote_with_mirror) { double("remote", uri: uri, original_uri: orig_uri) } it "sets the 'X-Gemfile-Source' header containing the original source" do - expect(Bundler.rubygems).to receive(:gem_remote_fetcher).twice.and_return(fetcher) - expect(fetcher).to receive(:headers=).with("X-Gemfile-Source" => "http://zombo.com").twice expect(fetcher).to receive(:fetch_path).with(uri + "specs.4.8.gz").and_return(specs_response) expect(fetcher).to receive(:fetch_path).with(uri + "prerelease_specs.4.8.gz").and_return(prerelease_specs_response) - result = Bundler.rubygems.fetch_all_remote_specs(remote_with_mirror) + result = Bundler.rubygems.fetch_all_remote_specs(remote_with_mirror, fetcher) expect(result).to eq(%w[specs prerelease_specs]) end end context "when there is no rubygems source mirror set" do - let(:remote_no_mirror) { double("remote", :uri => uri, :original_uri => nil) } + let(:remote_no_mirror) { double("remote", uri: uri, original_uri: nil) } it "does not set the 'X-Gemfile-Source' header" do - expect(Bundler.rubygems).to receive(:gem_remote_fetcher).twice.and_return(fetcher) - expect(fetcher).to_not receive(:headers=) expect(fetcher).to receive(:fetch_path).with(uri + "specs.4.8.gz").and_return(specs_response) expect(fetcher).to receive(:fetch_path).with(uri + "prerelease_specs.4.8.gz").and_return(prerelease_specs_response) - result = Bundler.rubygems.fetch_all_remote_specs(remote_no_mirror) + result = Bundler.rubygems.fetch_all_remote_specs(remote_no_mirror, fetcher) expect(result).to eq(%w[specs prerelease_specs]) end end + + context "when loading an unexpected class" do + let(:remote_no_mirror) { double("remote", uri: uri, original_uri: nil) } + let(:unexpected_specs_response) { Marshal.dump(3) } + + it "raises a MarshalError error" do + expect(fetcher).to receive(:fetch_path).with(uri + "specs.4.8.gz").and_return(unexpected_specs_response) + expect { Bundler.rubygems.fetch_all_remote_specs(remote_no_mirror, fetcher) }.to raise_error(Bundler::MarshalError, /unexpected class/i) + end + end end end diff --git a/spec/bundler/bundler/settings/validator_spec.rb b/spec/bundler/bundler/settings/validator_spec.rb index e4ffd89435..b252ba59a0 100644 --- a/spec/bundler/bundler/settings/validator_spec.rb +++ b/spec/bundler/bundler/settings/validator_spec.rb @@ -44,14 +44,14 @@ RSpec.describe Bundler::Settings::Validator do validate!("without", "b:c", "BUNDLE_WITH" => "a") end.not_to raise_error - expect { validate!("with", "b:c", "BUNDLE_WITHOUT" => "c:d") }.to raise_error Bundler::InvalidOption, strip_whitespace(<<-EOS).strip + expect { validate!("with", "b:c", "BUNDLE_WITHOUT" => "c:d") }.to raise_error Bundler::InvalidOption, <<~EOS.strip Setting `with` to "b:c" failed: - a group cannot be in both `with` & `without` simultaneously - `without` is current set to [:c, :d] - the `c` groups conflict EOS - expect { validate!("without", "b:c", "BUNDLE_WITH" => "c:d") }.to raise_error Bundler::InvalidOption, strip_whitespace(<<-EOS).strip + expect { validate!("without", "b:c", "BUNDLE_WITH" => "c:d") }.to raise_error Bundler::InvalidOption, <<~EOS.strip Setting `without` to "b:c" failed: - a group cannot be in both `with` & `without` simultaneously - `with` is current set to [:c, :d] @@ -74,7 +74,7 @@ RSpec.describe Bundler::Settings::Validator do describe "#fail!" do it "raises with a helpful message" do - expect { subject.fail!("key", "value", "reason1", "reason2") }.to raise_error Bundler::InvalidOption, strip_whitespace(<<-EOS).strip + expect { subject.fail!("key", "value", "reason1", "reason2") }.to raise_error Bundler::InvalidOption, <<~EOS.strip Setting `key` to "value" failed: - rule description - reason1 diff --git a/spec/bundler/bundler/settings_spec.rb b/spec/bundler/bundler/settings_spec.rb index 24e3de7ba8..634e0faf91 100644 --- a/spec/bundler/bundler/settings_spec.rb +++ b/spec/bundler/bundler/settings_spec.rb @@ -27,7 +27,7 @@ RSpec.describe Bundler::Settings do "gem.mit" => "false", "gem.test" => "minitest", "thingy" => <<-EOS.tr("\n", " "), ---asdf --fdsa --ty=oh man i hope this doesnt break bundler because +--asdf --fdsa --ty=oh man i hope this doesn't break bundler because that would suck --ehhh=oh geez it looks like i might have broken bundler somehow --very-important-option=DontDeleteRoo --very-important-option=DontDeleteRoo @@ -131,7 +131,7 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow Bundler.settings.set_command_option :no_install, true - Bundler.settings.temporary(:no_install => false) do + Bundler.settings.temporary(no_install: false) do expect(Bundler.settings[:no_install]).to eq false end @@ -147,12 +147,12 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow context "when called without a block" do it "leaves the setting changed" do - Bundler.settings.temporary(:foo => :random) + Bundler.settings.temporary(foo: :random) expect(Bundler.settings[:foo]).to eq "random" end it "returns nil" do - expect(Bundler.settings.temporary(:foo => :bar)).to be_nil + expect(Bundler.settings.temporary(foo: :bar)).to be_nil end end end @@ -179,7 +179,7 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow end describe "#mirror_for" do - let(:uri) { Bundler::URI("https://rubygems.org/") } + let(:uri) { Gem::URI("https://rubygems.org/") } context "with no configured mirror" do it "returns the original URI" do @@ -192,7 +192,7 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow end context "with a configured mirror" do - let(:mirror_uri) { Bundler::URI("https://rubygems-mirror.org/") } + let(:mirror_uri) { Gem::URI("https://rubygems-mirror.org/") } before { settings.set_local "mirror.https://rubygems.org/", mirror_uri.to_s } @@ -213,7 +213,7 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow end context "with a file URI" do - let(:mirror_uri) { Bundler::URI("file:/foo/BAR/baz/qUx/") } + let(:mirror_uri) { Gem::URI("file:/foo/BAR/baz/qUx/") } it "returns the mirror URI" do expect(settings.mirror_for(uri)).to eq(mirror_uri) @@ -231,7 +231,7 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow end describe "#credentials_for" do - let(:uri) { Bundler::URI("https://gemserver.example.org/") } + let(:uri) { Gem::URI("https://gemserver.example.org/") } let(:credentials) { "username:password" } context "with no configured credentials" do @@ -291,7 +291,7 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow it "reads older keys without trailing slashes" do settings.set_local "mirror.https://rubygems.org", "http://rubygems-mirror.org" expect(settings.mirror_for("https://rubygems.org/")).to eq( - Bundler::URI("http://rubygems-mirror.org/") + Gem::URI("http://rubygems-mirror.org/") ) end @@ -319,6 +319,15 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow expect(settings["mirror.https://rubygems.org/"]).to eq("http://rubygems-mirror.org") end + it "ignores commented out keys" do + create_file bundled_app(".bundle/config"), <<~C + # BUNDLE_MY-PERSONAL-SERVER__ORG: my-personal-server.org + C + + expect(Bundler.ui).not_to receive(:warn) + expect(settings.all).to be_empty + end + it "converts older keys with dashes" do config("BUNDLE_MY-PERSONAL-SERVER__ORG" => "my-personal-server.org") expect(Bundler.ui).to receive(:warn).with( diff --git a/spec/bundler/bundler/shared_helpers_spec.rb b/spec/bundler/bundler/shared_helpers_spec.rb index 68a24be31c..918f73b337 100644 --- a/spec/bundler/bundler/shared_helpers_spec.rb +++ b/spec/bundler/bundler/shared_helpers_spec.rb @@ -1,12 +1,8 @@ # frozen_string_literal: true RSpec.describe Bundler::SharedHelpers do - let(:ext_lock_double) { double(:ext_lock) } - before do pwd_stub - allow(Bundler.rubygems).to receive(:ext_lock).and_return(ext_lock_double) - allow(ext_lock_double).to receive(:synchronize) {|&block| block.call } end let(:pwd_stub) { allow(subject).to receive(:pwd).and_return(bundled_app) } @@ -242,7 +238,14 @@ RSpec.describe Bundler::SharedHelpers do shared_examples_for "ENV['RUBYOPT'] gets set correctly" do it "ensures -rbundler/setup is at the beginning of ENV['RUBYOPT']" do subject.set_bundle_environment - expect(ENV["RUBYOPT"].split(" ")).to start_with("-r#{source_lib_dir}/bundler/setup") + expect(ENV["RUBYOPT"].split(" ")).to start_with("-r#{install_path}/bundler/setup") + end + end + + shared_examples_for "ENV['BUNDLER_SETUP'] gets set correctly" do + it "ensures bundler/setup is set in ENV['BUNDLER_SETUP']" do + subject.set_bundle_environment + expect(ENV["BUNDLER_SETUP"]).to eq("#{source_lib_dir}/bundler/setup") end end @@ -281,7 +284,7 @@ RSpec.describe Bundler::SharedHelpers do if Gem.respond_to?(:path_separator) allow(Gem).to receive(:path_separator).and_return(":") else - stub_const("File::PATH_SEPARATOR", ":".freeze) + stub_const("File::PATH_SEPARATOR", ":") end allow(Bundler).to receive(:bundle_path) { Pathname.new("so:me/dir/bin") } expect { subject.send(:validate_bundle_path) }.to raise_error( @@ -358,20 +361,41 @@ RSpec.describe Bundler::SharedHelpers do end end - context "ENV['RUBYOPT'] does not exist" do - before { ENV.delete("RUBYOPT") } + context "when bundler install path is standard" do + let(:install_path) { source_lib_dir } - it_behaves_like "ENV['RUBYOPT'] gets set correctly" - end + context "ENV['RUBYOPT'] does not exist" do + before { ENV.delete("RUBYOPT") } - context "ENV['RUBYOPT'] exists without -rbundler/setup" do - before { ENV["RUBYOPT"] = "-I/some_app_path/lib" } + it_behaves_like "ENV['RUBYOPT'] gets set correctly" + end - it_behaves_like "ENV['RUBYOPT'] gets set correctly" + context "ENV['RUBYOPT'] exists without -rbundler/setup" do + before { ENV["RUBYOPT"] = "-I/some_app_path/lib" } + + it_behaves_like "ENV['RUBYOPT'] gets set correctly" + end + + context "ENV['RUBYOPT'] exists and contains -rbundler/setup" do + before { ENV["RUBYOPT"] = "-rbundler/setup" } + + it_behaves_like "ENV['RUBYOPT'] gets set correctly" + end end - context "ENV['RUBYOPT'] exists and contains -rbundler/setup" do - before { ENV["RUBYOPT"] = "-rbundler/setup" } + context "when bundler install path contains special characters" do + let(:install_path) { "/opt/ruby3.3.0-preview2/lib/ruby/3.3.0+0" } + + before do + ENV["RUBYOPT"] = "-r#{install_path}/bundler/setup" + allow(File).to receive(:expand_path).and_return("#{install_path}/bundler/setup") + allow(Gem).to receive(:bin_path).and_return("#{install_path}/bundler/setup") + end + + it "ensures -rbundler/setup is not duplicated" do + subject.set_bundle_environment + expect(ENV["RUBYOPT"].split(" ").grep(%r{-r.*/bundler/setup}).length).to eq(1) + end it_behaves_like "ENV['RUBYOPT'] gets set correctly" end @@ -494,4 +518,34 @@ RSpec.describe Bundler::SharedHelpers do end end end + + describe "#major_deprecation" do + before { allow(Bundler).to receive(:bundler_major_version).and_return(37) } + before { allow(Bundler.ui).to receive(:warn) } + + it "prints and raises nothing below the deprecated major version" do + subject.major_deprecation(38, "Message") + subject.major_deprecation(39, "Message", removed_message: "Removal", print_caller_location: true) + expect(Bundler.ui).not_to have_received(:warn) + end + + it "prints but does not raise _at_ the deprecated major version" do + subject.major_deprecation(37, "Message") + subject.major_deprecation(37, "Message", removed_message: "Removal") + expect(Bundler.ui).to have_received(:warn).with("[DEPRECATED] Message").twice + + subject.major_deprecation(37, "Message", print_caller_location: true) + expect(Bundler.ui).to have_received(:warn). + with(a_string_matching(/^\[DEPRECATED\] Message \(called at .*:\d+\)$/)) + end + + it "raises the appropriate errors when _past_ the deprecated major version" do + expect { subject.major_deprecation(36, "Message") }. + to raise_error(Bundler::DeprecatedError, "[REMOVED] Message") + expect { subject.major_deprecation(36, "Message", removed_message: "Removal") }. + to raise_error(Bundler::DeprecatedError, "[REMOVED] Removal") + expect { subject.major_deprecation(35, "Message", removed_message: "Removal", print_caller_location: true) }. + to raise_error(Bundler::DeprecatedError, /^\[REMOVED\] Removal \(called at .*:\d+\)$/) + end + end end diff --git a/spec/bundler/bundler/source/git/git_proxy_spec.rb b/spec/bundler/bundler/source/git/git_proxy_spec.rb index 97f06973cb..1450316d59 100644 --- a/spec/bundler/bundler/source/git/git_proxy_spec.rb +++ b/spec/bundler/bundler/source/git/git_proxy_spec.rb @@ -3,29 +3,84 @@ RSpec.describe Bundler::Source::Git::GitProxy do let(:path) { Pathname("path") } let(:uri) { "https://github.com/rubygems/rubygems.git" } - let(:ref) { "HEAD" } + let(:ref) { nil } + let(:branch) { nil } + let(:tag) { nil } + let(:options) { { "ref" => ref, "branch" => branch, "tag" => tag }.compact } let(:revision) { nil } let(:git_source) { nil } - subject { described_class.new(path, uri, ref, revision, git_source) } + let(:clone_result) { double(Process::Status, success?: true) } + let(:base_clone_args) { ["clone", "--bare", "--no-hardlinks", "--quiet", "--no-tags", "--depth", "1", "--single-branch"] } + subject(:git_proxy) { described_class.new(path, uri, options, revision, git_source) } + + context "with explicit ref" do + context "with branch only" do + let(:branch) { "main" } + it "sets explicit ref to branch" do + expect(git_proxy.explicit_ref).to eq(branch) + end + end + + context "with ref only" do + let(:ref) { "HEAD" } + it "sets explicit ref to ref" do + expect(git_proxy.explicit_ref).to eq(ref) + end + end + + context "with tag only" do + let(:tag) { "v1.0" } + it "sets explicit ref to ref" do + expect(git_proxy.explicit_ref).to eq(tag) + end + end + + context "with tag and branch" do + let(:tag) { "v1.0" } + let(:branch) { "main" } + it "raises error" do + expect { git_proxy }.to raise_error(Bundler::Source::Git::AmbiguousGitReference) + end + end + + context "with tag and ref" do + let(:tag) { "v1.0" } + let(:ref) { "HEAD" } + it "raises error" do + expect { git_proxy }.to raise_error(Bundler::Source::Git::AmbiguousGitReference) + end + end + + context "with branch and ref" do + let(:branch) { "main" } + let(:ref) { "HEAD" } + it "honors ref over branch" do + expect(git_proxy.explicit_ref).to eq(ref) + end + end + end context "with configured credentials" do it "adds username and password to URI" do Bundler.settings.temporary(uri => "u:p") do - expect(subject).to receive(:git_retry).with("clone", "https://u:p@github.com/rubygems/rubygems.git", any_args) + allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0") + expect(git_proxy).to receive(:capture).with([*base_clone_args, "--", "https://u:p@github.com/rubygems/rubygems.git", path.to_s], nil).and_return(["", "", clone_result]) subject.checkout end end it "adds username and password to URI for host" do Bundler.settings.temporary("github.com" => "u:p") do - expect(subject).to receive(:git_retry).with("clone", "https://u:p@github.com/rubygems/rubygems.git", any_args) + allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0") + expect(git_proxy).to receive(:capture).with([*base_clone_args, "--", "https://u:p@github.com/rubygems/rubygems.git", path.to_s], nil).and_return(["", "", clone_result]) subject.checkout end end it "does not add username and password to mismatched URI" do Bundler.settings.temporary("https://u:p@github.com/rubygems/rubygems-mismatch.git" => "u:p") do - expect(subject).to receive(:git_retry).with("clone", uri, any_args) + allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0") + expect(git_proxy).to receive(:capture).with([*base_clone_args, "--", uri, path.to_s], nil).and_return(["", "", clone_result]) subject.checkout end end @@ -33,9 +88,10 @@ RSpec.describe Bundler::Source::Git::GitProxy do it "keeps original userinfo" do Bundler.settings.temporary("github.com" => "u:p") do original = "https://orig:info@github.com/rubygems/rubygems.git" - subject = described_class.new(Pathname("path"), original, "HEAD") - expect(subject).to receive(:git_retry).with("clone", original, any_args) - subject.checkout + git_proxy = described_class.new(Pathname("path"), original, options) + allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0") + expect(git_proxy).to receive(:capture).with([*base_clone_args, "--", original, path.to_s], nil).and_return(["", "", clone_result]) + git_proxy.checkout end end end @@ -43,46 +99,46 @@ RSpec.describe Bundler::Source::Git::GitProxy do describe "#version" do context "with a normal version number" do before do - expect(subject).to receive(:git).with("--version"). + expect(git_proxy).to receive(:git_local).with("--version"). and_return("git version 1.2.3") end it "returns the git version number" do - expect(subject.version).to eq("1.2.3") + expect(git_proxy.version).to eq("1.2.3") end it "does not raise an error when passed into Gem::Version.create" do - expect { Gem::Version.create subject.version }.not_to raise_error + expect { Gem::Version.create git_proxy.version }.not_to raise_error end end context "with a OSX version number" do before do - expect(subject).to receive(:git).with("--version"). + expect(git_proxy).to receive(:git_local).with("--version"). and_return("git version 1.2.3 (Apple Git-BS)") end it "strips out OSX specific additions in the version string" do - expect(subject.version).to eq("1.2.3") + expect(git_proxy.version).to eq("1.2.3") end it "does not raise an error when passed into Gem::Version.create" do - expect { Gem::Version.create subject.version }.not_to raise_error + expect { Gem::Version.create git_proxy.version }.not_to raise_error end end context "with a msysgit version number" do before do - expect(subject).to receive(:git).with("--version"). + expect(git_proxy).to receive(:git_local).with("--version"). and_return("git version 1.2.3.msysgit.0") end it "strips out msysgit specific additions in the version string" do - expect(subject.version).to eq("1.2.3") + expect(git_proxy.version).to eq("1.2.3") end it "does not raise an error when passed into Gem::Version.create" do - expect { Gem::Version.create subject.version }.not_to raise_error + expect { Gem::Version.create git_proxy.version }.not_to raise_error end end end @@ -90,62 +146,55 @@ RSpec.describe Bundler::Source::Git::GitProxy do describe "#full_version" do context "with a normal version number" do before do - expect(subject).to receive(:git).with("--version"). + expect(git_proxy).to receive(:git_local).with("--version"). and_return("git version 1.2.3") end it "returns the git version number" do - expect(subject.full_version).to eq("1.2.3") + expect(git_proxy.full_version).to eq("1.2.3") end end context "with a OSX version number" do before do - expect(subject).to receive(:git).with("--version"). + expect(git_proxy).to receive(:git_local).with("--version"). and_return("git version 1.2.3 (Apple Git-BS)") end it "does not strip out OSX specific additions in the version string" do - expect(subject.full_version).to eq("1.2.3 (Apple Git-BS)") + expect(git_proxy.full_version).to eq("1.2.3 (Apple Git-BS)") end end context "with a msysgit version number" do before do - expect(subject).to receive(:git).with("--version"). + expect(git_proxy).to receive(:git_local).with("--version"). and_return("git version 1.2.3.msysgit.0") end it "does not strip out msysgit specific additions in the version string" do - expect(subject.full_version).to eq("1.2.3.msysgit.0") + expect(git_proxy.full_version).to eq("1.2.3.msysgit.0") end end end - describe "#copy_to" do - let(:cache) { tmpdir("cache_path") } - let(:destination) { tmpdir("copy_to_path") } - let(:submodules) { false } + it "doesn't allow arbitrary code execution through Gemfile uris with a leading dash" do + gemfile <<~G + gem "poc", git: "-u./pay:load.sh" + G - context "when given a SHA as a revision" do - let(:revision) { "abcd" * 10 } - let(:command) { ["reset", "--hard", revision] } - let(:command_for_display) { "git #{command.shelljoin}" } + file = bundled_app("pay:load.sh") - it "fails gracefully when resetting to the revision fails" do - expect(subject).to receive(:git_retry).with("clone", any_args) { destination.mkpath } - expect(subject).to receive(:git_retry).with("fetch", any_args, :dir => destination) - expect(subject).to receive(:git).with(*command, :dir => destination).and_raise(Bundler::Source::Git::GitCommandError.new(command_for_display, destination)) - expect(subject).not_to receive(:git) + create_file file, <<~RUBY + #!/bin/sh - expect { subject.copy_to(destination, submodules) }. - to raise_error( - Bundler::Source::Git::MissingGitRevisionError, - "Git error: command `#{command_for_display}` in directory #{destination} has failed.\n" \ - "Revision #{revision} does not exist in the repository #{uri}. Maybe you misspelled it?\n" \ - "If this error persists you could try removing the cache directory '#{destination}'" - ) - end - end + touch #{bundled_app("canary")} + RUBY + + FileUtils.chmod("+x", file) + + bundle :lock, raise_on_error: false + + expect(Pathname.new(bundled_app("canary"))).not_to exist end end diff --git a/spec/bundler/bundler/source/git_spec.rb b/spec/bundler/bundler/source/git_spec.rb index 6668b6e69a..feef54bbf4 100644 --- a/spec/bundler/bundler/source/git_spec.rb +++ b/spec/bundler/bundler/source/git_spec.rb @@ -24,5 +24,50 @@ RSpec.describe Bundler::Source::Git do expect(subject.to_s).to eq "https://x-oauth-basic@github.com/foo/bar.git" end end + + context "when the source has a glob specifier" do + let(:glob) { "bar/baz/*.gemspec" } + let(:options) do + { "uri" => uri, "glob" => glob } + end + + it "includes it" do + expect(subject.to_s).to eq "https://github.com/foo/bar.git (glob: bar/baz/*.gemspec)" + end + end + + context "when the source has a reference" do + let(:git_proxy_stub) do + instance_double(Bundler::Source::Git::GitProxy, revision: "123abc", branch: "v1.0.0") + end + let(:options) do + { "uri" => uri, "ref" => "v1.0.0" } + end + + before do + allow(Bundler::Source::Git::GitProxy).to receive(:new).and_return(git_proxy_stub) + end + + it "includes it" do + expect(subject.to_s).to eq "https://github.com/foo/bar.git (at v1.0.0@123abc)" + end + end + + context "when the source has both reference and glob specifiers" do + let(:git_proxy_stub) do + instance_double(Bundler::Source::Git::GitProxy, revision: "123abc", branch: "v1.0.0") + end + let(:options) do + { "uri" => uri, "ref" => "v1.0.0", "glob" => "gems/foo/*.gemspec" } + end + + before do + allow(Bundler::Source::Git::GitProxy).to receive(:new).and_return(git_proxy_stub) + end + + it "includes both" do + expect(subject.to_s).to eq "https://github.com/foo/bar.git (at v1.0.0@123abc, glob: gems/foo/*.gemspec)" + end + end end end diff --git a/spec/bundler/bundler/source/rubygems/remote_spec.rb b/spec/bundler/bundler/source/rubygems/remote_spec.rb index 07ce4f968e..56f3bee459 100644 --- a/spec/bundler/bundler/source/rubygems/remote_spec.rb +++ b/spec/bundler/bundler/source/rubygems/remote_spec.rb @@ -11,8 +11,8 @@ RSpec.describe Bundler::Source::Rubygems::Remote do allow(Digest(:MD5)).to receive(:hexdigest).with(duck_type(:to_s)) {|string| "MD5HEX(#{string})" } end - let(:uri_no_auth) { Bundler::URI("https://gems.example.com") } - let(:uri_with_auth) { Bundler::URI("https://#{credentials}@gems.example.com") } + let(:uri_no_auth) { Gem::URI("https://gems.example.com") } + let(:uri_with_auth) { Gem::URI("https://#{credentials}@gems.example.com") } let(:credentials) { "username:password" } context "when the original URI has no credentials" do @@ -89,11 +89,11 @@ RSpec.describe Bundler::Source::Rubygems::Remote do end context "when the original URI has only a username" do - let(:uri) { Bundler::URI("https://SeCrEt-ToKeN@gem.fury.io/me/") } + let(:uri) { Gem::URI("https://SeCrEt-ToKeN@gem.fury.io/me/") } describe "#anonymized_uri" do it "returns the URI without username and password" do - expect(remote(uri).anonymized_uri).to eq(Bundler::URI("https://gem.fury.io/me/")) + expect(remote(uri).anonymized_uri).to eq(Gem::URI("https://gem.fury.io/me/")) end end @@ -105,9 +105,9 @@ RSpec.describe Bundler::Source::Rubygems::Remote do end context "when a mirror with inline credentials is configured for the URI" do - let(:uri) { Bundler::URI("https://rubygems.org/") } - let(:mirror_uri_with_auth) { Bundler::URI("https://username:password@rubygems-mirror.org/") } - let(:mirror_uri_no_auth) { Bundler::URI("https://rubygems-mirror.org/") } + let(:uri) { Gem::URI("https://rubygems.org/") } + let(:mirror_uri_with_auth) { Gem::URI("https://username:password@rubygems-mirror.org/") } + let(:mirror_uri_no_auth) { Gem::URI("https://rubygems-mirror.org/") } before { Bundler.settings.temporary("mirror.https://rubygems.org/" => mirror_uri_with_auth.to_s) } @@ -131,9 +131,9 @@ RSpec.describe Bundler::Source::Rubygems::Remote do end context "when a mirror with configured credentials is configured for the URI" do - let(:uri) { Bundler::URI("https://rubygems.org/") } - let(:mirror_uri_with_auth) { Bundler::URI("https://#{credentials}@rubygems-mirror.org/") } - let(:mirror_uri_no_auth) { Bundler::URI("https://rubygems-mirror.org/") } + let(:uri) { Gem::URI("https://rubygems.org/") } + let(:mirror_uri_with_auth) { Gem::URI("https://#{credentials}@rubygems-mirror.org/") } + let(:mirror_uri_no_auth) { Gem::URI("https://rubygems-mirror.org/") } before do Bundler.settings.temporary("mirror.https://rubygems.org/" => mirror_uri_no_auth.to_s) diff --git a/spec/bundler/bundler/source_list_spec.rb b/spec/bundler/bundler/source_list_spec.rb index f860e9ff58..13453cb2a3 100644 --- a/spec/bundler/bundler/source_list_spec.rb +++ b/spec/bundler/bundler/source_list_spec.rb @@ -85,7 +85,7 @@ RSpec.describe Bundler::SourceList do end it "ignores git protocols on request" do - Bundler.settings.temporary(:"git.allow_insecure" => true) + Bundler.settings.temporary("git.allow_insecure": true) expect(Bundler.ui).to_not receive(:warn).with(msg) source_list.add_git_source("uri" => "git://existing-git.org/path.git") end @@ -125,8 +125,8 @@ RSpec.describe Bundler::SourceList do it "adds the provided remote to the beginning of the aggregate source" do source_list.add_global_rubygems_remote("https://othersource.org") expect(returned_source.remotes).to eq [ - Bundler::URI("https://othersource.org/"), - Bundler::URI("https://rubygems.org/"), + Gem::URI("https://othersource.org/"), + Gem::URI("https://rubygems.org/"), ] end end diff --git a/spec/bundler/bundler/source_spec.rb b/spec/bundler/bundler/source_spec.rb index af370bb45c..3b49c37431 100644 --- a/spec/bundler/bundler/source_spec.rb +++ b/spec/bundler/bundler/source_spec.rb @@ -21,7 +21,7 @@ RSpec.describe Bundler::Source do end describe "#version_message" do - let(:spec) { double(:spec, :name => "nokogiri", :version => ">= 1.6", :platform => rb) } + let(:spec) { double(:spec, name: "nokogiri", version: ">= 1.6", platform: rb) } shared_examples_for "the lockfile specs are not relevant" do it "should return a string with the spec name and version" do @@ -30,31 +30,21 @@ RSpec.describe Bundler::Source do end context "when there are locked gems" do - let(:locked_gems) { double(:locked_gems) } - - before { allow(Bundler).to receive(:locked_gems).and_return(locked_gems) } - context "that contain the relevant gem spec" do - before do - specs = double(:specs) - allow(locked_gems).to receive(:specs).and_return(specs) - allow(specs).to receive(:find).and_return(locked_gem) - end - context "without a version" do - let(:locked_gem) { double(:locked_gem, :name => "nokogiri", :version => nil) } + let(:locked_gem) { double(:locked_gem, name: "nokogiri", version: nil) } it_behaves_like "the lockfile specs are not relevant" end context "with the same version" do - let(:locked_gem) { double(:locked_gem, :name => "nokogiri", :version => ">= 1.6") } + let(:locked_gem) { double(:locked_gem, name: "nokogiri", version: ">= 1.6") } it_behaves_like "the lockfile specs are not relevant" end context "with a different version" do - let(:locked_gem) { double(:locked_gem, :name => "nokogiri", :version => "< 1.5") } + let(:locked_gem) { double(:locked_gem, name: "nokogiri", version: "< 1.5") } context "with color", :no_color_tty do before do @@ -62,7 +52,7 @@ RSpec.describe Bundler::Source do end it "should return a string with the spec name and version and locked spec version" do - expect(subject.version_message(spec)).to eq("nokogiri >= 1.6\e[32m (was < 1.5)\e[0m") + expect(subject.version_message(spec, locked_gem)).to eq("nokogiri >= 1.6\e[32m (was < 1.5)\e[0m") end end @@ -74,14 +64,14 @@ RSpec.describe Bundler::Source do end it "should return a string with the spec name and version and locked spec version" do - expect(subject.version_message(spec)).to eq("nokogiri >= 1.6 (was < 1.5)") + expect(subject.version_message(spec, locked_gem)).to eq("nokogiri >= 1.6 (was < 1.5)") end end end context "with a more recent version" do - let(:spec) { double(:spec, :name => "nokogiri", :version => "1.6.1", :platform => rb) } - let(:locked_gem) { double(:locked_gem, :name => "nokogiri", :version => "1.7.0") } + let(:spec) { double(:spec, name: "nokogiri", version: "1.6.1", platform: rb) } + let(:locked_gem) { double(:locked_gem, name: "nokogiri", version: "1.7.0") } context "with color", :no_color_tty do before do @@ -89,7 +79,7 @@ RSpec.describe Bundler::Source do end it "should return a string with the locked spec version in yellow" do - expect(subject.version_message(spec)).to eq("nokogiri 1.6.1\e[33m (was 1.7.0)\e[0m") + expect(subject.version_message(spec, locked_gem)).to eq("nokogiri 1.6.1\e[33m (was 1.7.0)\e[0m") end end @@ -101,14 +91,14 @@ RSpec.describe Bundler::Source do end it "should return a string with the locked spec version in yellow" do - expect(subject.version_message(spec)).to eq("nokogiri 1.6.1 (was 1.7.0)") + expect(subject.version_message(spec, locked_gem)).to eq("nokogiri 1.6.1 (was 1.7.0)") end end end context "with an older version" do - let(:spec) { double(:spec, :name => "nokogiri", :version => "1.7.1", :platform => rb) } - let(:locked_gem) { double(:locked_gem, :name => "nokogiri", :version => "1.7.0") } + let(:spec) { double(:spec, name: "nokogiri", version: "1.7.1", platform: rb) } + let(:locked_gem) { double(:locked_gem, name: "nokogiri", version: "1.7.0") } context "with color", :no_color_tty do before do @@ -116,7 +106,7 @@ RSpec.describe Bundler::Source do end it "should return a string with the locked spec version in green" do - expect(subject.version_message(spec)).to eq("nokogiri 1.7.1\e[32m (was 1.7.0)\e[0m") + expect(subject.version_message(spec, locked_gem)).to eq("nokogiri 1.7.1\e[32m (was 1.7.0)\e[0m") end end @@ -128,33 +118,17 @@ RSpec.describe Bundler::Source do end it "should return a string with the locked spec version in yellow" do - expect(subject.version_message(spec)).to eq("nokogiri 1.7.1 (was 1.7.0)") + expect(subject.version_message(spec, locked_gem)).to eq("nokogiri 1.7.1 (was 1.7.0)") end end end end - - context "that do not contain the relevant gem spec" do - before do - specs = double(:specs) - allow(locked_gems).to receive(:specs).and_return(specs) - allow(specs).to receive(:find).and_return(nil) - end - - it_behaves_like "the lockfile specs are not relevant" - end - end - - context "when there are no locked gems" do - before { allow(Bundler).to receive(:locked_gems).and_return(nil) } - - it_behaves_like "the lockfile specs are not relevant" end end describe "#can_lock?" do context "when the passed spec's source is equivalent" do - let(:spec) { double(:spec, :source => subject) } + let(:spec) { double(:spec, source: subject) } it "should return true" do expect(subject.can_lock?(spec)).to be_truthy @@ -162,7 +136,7 @@ RSpec.describe Bundler::Source do end context "when the passed spec's source is not equivalent" do - let(:spec) { double(:spec, :source => double(:other_source)) } + let(:spec) { double(:spec, source: double(:other_source)) } it "should return false" do expect(subject.can_lock?(spec)).to be_falsey diff --git a/spec/bundler/bundler/spec_set_spec.rb b/spec/bundler/bundler/spec_set_spec.rb index 6fedd38b50..c4b6676223 100644 --- a/spec/bundler/bundler/spec_set_spec.rb +++ b/spec/bundler/bundler/spec_set_spec.rb @@ -45,24 +45,6 @@ RSpec.describe Bundler::SpecSet do end end - describe "#merge" do - let(:other_specs) do - [ - build_spec("f", "1.0"), - build_spec("g", "2.0"), - ].flatten - end - - let(:other_spec_set) { described_class.new(other_specs) } - - it "merges the items in each gemspec" do - new_spec_set = subject.merge(other_spec_set) - specs = new_spec_set.to_a.map(&:full_name) - expect(specs).to include("a-1.0") - expect(specs).to include("f-1.0") - end - end - describe "#to_a" do it "returns the specs in order" do expect(subject.to_a.map(&:full_name)).to eq %w[ diff --git a/spec/bundler/bundler/specifications/foo.gemspec b/spec/bundler/bundler/specifications/foo.gemspec new file mode 100644 index 0000000000..46eb068cd1 --- /dev/null +++ b/spec/bundler/bundler/specifications/foo.gemspec @@ -0,0 +1,13 @@ +# rubocop:disable Style/FrozenStringLiteralComment +# stub: foo 1.0.0 ruby lib + +# The first line would be '# -*- encoding: utf-8 -*-' in a real stub gemspec + +Gem::Specification.new do |s| + s.name = "foo" + s.version = "1.0.0" + s.loaded_from = __FILE__ + s.extensions = "ext/foo" + s.required_ruby_version = ">= 3.0.0" +end +# rubocop:enable Style/FrozenStringLiteralComment diff --git a/spec/bundler/bundler/stub_specification_spec.rb b/spec/bundler/bundler/stub_specification_spec.rb index fb612813c2..dae9f3cfba 100644 --- a/spec/bundler/bundler/stub_specification_spec.rb +++ b/spec/bundler/bundler/stub_specification_spec.rb @@ -19,6 +19,17 @@ RSpec.describe Bundler::StubSpecification do end end + describe "#gem_build_complete_path" do + it "StubSpecification should have equal gem_build_complete_path as Specification" do + spec_path = File.join(File.dirname(__FILE__), "specifications", "foo.gemspec") + spec = Gem::Specification.load(spec_path) + gem_stub = Gem::StubSpecification.new(spec_path, File.dirname(__FILE__),"","") + + stub = described_class.from_stub(gem_stub) + expect(stub.gem_build_complete_path).to eq spec.gem_build_complete_path + end + end + describe "#manually_installed?" do it "returns true if installed_by_version is nil or 0" do stub = described_class.from_stub(with_bundler_stub_spec) diff --git a/spec/bundler/bundler/uri_credentials_filter_spec.rb b/spec/bundler/bundler/uri_credentials_filter_spec.rb index 466c1b8594..ed24744a1c 100644 --- a/spec/bundler/bundler/uri_credentials_filter_spec.rb +++ b/spec/bundler/bundler/uri_credentials_filter_spec.rb @@ -16,7 +16,7 @@ RSpec.describe Bundler::URICredentialsFilter do let(:credentials) { "oauth_token:x-oauth-basic@" } it "returns the uri without the oauth token" do - expect(subject.credential_filtered_uri(uri).to_s).to eq(Bundler::URI("https://x-oauth-basic@github.com/company/private-repo").to_s) + expect(subject.credential_filtered_uri(uri).to_s).to eq(Gem::URI("https://x-oauth-basic@github.com/company/private-repo").to_s) end it_behaves_like "original type of uri is maintained" @@ -26,7 +26,7 @@ RSpec.describe Bundler::URICredentialsFilter do let(:credentials) { "oauth_token:x@" } it "returns the uri without the oauth token" do - expect(subject.credential_filtered_uri(uri).to_s).to eq(Bundler::URI("https://x@github.com/company/private-repo").to_s) + expect(subject.credential_filtered_uri(uri).to_s).to eq(Gem::URI("https://x@github.com/company/private-repo").to_s) end it_behaves_like "original type of uri is maintained" @@ -37,7 +37,7 @@ RSpec.describe Bundler::URICredentialsFilter do let(:credentials) { "username1:hunter3@" } it "returns the uri without the password" do - expect(subject.credential_filtered_uri(uri).to_s).to eq(Bundler::URI("https://username1@github.com/company/private-repo").to_s) + expect(subject.credential_filtered_uri(uri).to_s).to eq(Gem::URI("https://username1@github.com/company/private-repo").to_s) end it_behaves_like "original type of uri is maintained" @@ -55,7 +55,7 @@ RSpec.describe Bundler::URICredentialsFilter do end context "uri is a uri object" do - let(:uri) { Bundler::URI("https://#{credentials}github.com/company/private-repo") } + let(:uri) { Gem::URI("https://#{credentials}github.com/company/private-repo") } it_behaves_like "sensitive credentials in uri are filtered out" end @@ -90,7 +90,7 @@ RSpec.describe Bundler::URICredentialsFilter do describe "#credential_filtered_string" do let(:str_to_filter) { "This is a git message containing a uri #{uri}!" } let(:credentials) { "" } - let(:uri) { Bundler::URI("https://#{credentials}github.com/company/private-repo") } + let(:uri) { Gem::URI("https://#{credentials}github.com/company/private-repo") } context "with a uri that contains credentials" do let(:credentials) { "oauth_token:x-oauth-basic@" } diff --git a/spec/bundler/bundler/vendored_persistent_spec.rb b/spec/bundler/bundler/vendored_persistent_spec.rb deleted file mode 100644 index 3ed899dbcf..0000000000 --- a/spec/bundler/bundler/vendored_persistent_spec.rb +++ /dev/null @@ -1,77 +0,0 @@ -# frozen_string_literal: true - -require "bundler/vendored_persistent" - -RSpec.describe Bundler::PersistentHTTP do - describe "#warn_old_tls_version_rubygems_connection" do - let(:uri) { "https://index.rubygems.org" } - let(:connection) { instance_double(Bundler::Persistent::Net::HTTP::Persistent::Connection) } - let(:tls_version) { "TLSv1.2" } - let(:socket) { double("Socket") } - let(:socket_io) { double("SocketIO") } - - before do - allow(connection).to receive_message_chain(:http, :use_ssl?).and_return(!tls_version.nil?) - allow(socket).to receive(:io).and_return(socket_io) if socket - connection.instance_variable_set(:@socket, socket) - - if tls_version - allow(socket_io).to receive(:ssl_version).and_return(tls_version) - end - end - - shared_examples_for "does not warn" do - it "does not warn" do - allow(Bundler.ui).to receive(:warn).never - subject.warn_old_tls_version_rubygems_connection(Bundler::URI(uri), connection) - end - end - - shared_examples_for "does warn" do |*expected| - it "warns" do - expect(Bundler.ui).to receive(:warn).with(*expected) - subject.warn_old_tls_version_rubygems_connection(Bundler::URI(uri), connection) - end - end - - context "an HTTPS uri with TLSv1.2" do - include_examples "does not warn" - end - - context "without SSL" do - let(:tls_version) { nil } - - include_examples "does not warn" - end - - context "without a socket" do - let(:socket) { nil } - - include_examples "does not warn" - end - - context "with a different TLD" do - let(:uri) { "https://foo.bar" } - include_examples "does not warn" - - context "and an outdated TLS version" do - let(:tls_version) { "TLSv1" } - include_examples "does not warn" - end - end - - context "with a nonsense TLS version" do - let(:tls_version) { "BlahBlah2.0Blah" } - include_examples "does not warn" - end - - context "with an outdated TLS version" do - let(:tls_version) { "TLSv1" } - include_examples "does warn", - "Warning: Your Ruby version is compiled against a copy of OpenSSL that is very old. " \ - "Starting in January 2018, RubyGems.org will refuse connection requests from these very old versions of OpenSSL. " \ - "If you will need to continue installing gems after January 2018, please follow this guide to upgrade: http://ruby.to/tls-outdated.", - :wrap => true - end - end -end diff --git a/spec/bundler/bundler/version_ranges_spec.rb b/spec/bundler/bundler/version_ranges_spec.rb deleted file mode 100644 index bca044b0c0..0000000000 --- a/spec/bundler/bundler/version_ranges_spec.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -require "bundler/version_ranges" - -RSpec.describe Bundler::VersionRanges do - describe ".empty?" do - shared_examples_for "empty?" do |exp, *req| - it "returns #{exp} for #{req}" do - r = Gem::Requirement.new(*req) - ranges = described_class.for(r) - expect(described_class.empty?(*ranges)).to eq(exp), "expected `#{r}` #{exp ? "" : "not "}to be empty" - end - end - - include_examples "empty?", false - include_examples "empty?", false, "!= 1" - include_examples "empty?", false, "!= 1", "= 2" - include_examples "empty?", false, "!= 1", "> 1" - include_examples "empty?", false, "!= 1", ">= 1" - include_examples "empty?", false, "= 1", ">= 0.1", "<= 1.1" - include_examples "empty?", false, "= 1", ">= 1", "<= 1" - include_examples "empty?", false, "= 1", "~> 1" - include_examples "empty?", false, ">= 0.z", "= 0" - include_examples "empty?", false, ">= 0" - include_examples "empty?", false, ">= 1.0.0", "< 2.0.0" - include_examples "empty?", false, "~> 1" - include_examples "empty?", false, "~> 2.0", "~> 2.1" - include_examples "empty?", true, ">= 4.1.0", "< 5.0", "= 5.2.1" - include_examples "empty?", true, "< 5.0", "< 5.3", "< 6.0", "< 6", "= 5.2.0", "> 2", ">= 3.0", ">= 3.1", ">= 3.2", ">= 4.0.0", ">= 4.1.0", ">= 4.2.0", ">= 4.2", ">= 4" - include_examples "empty?", true, "!= 1", "< 2", "> 2" - include_examples "empty?", true, "!= 1", "<= 1", ">= 1" - include_examples "empty?", true, "< 2", "> 2" - include_examples "empty?", true, "< 2", "> 2", "= 2" - include_examples "empty?", true, "= 1", "!= 1" - include_examples "empty?", true, "= 1", "= 2" - include_examples "empty?", true, "= 1", "~> 2" - include_examples "empty?", true, ">= 0", "<= 0.a" - include_examples "empty?", true, "~> 2.0", "~> 3" - end -end diff --git a/spec/bundler/bundler/yaml_serializer_spec.rb b/spec/bundler/bundler/yaml_serializer_spec.rb index 1241c74bbf..de437f764a 100644 --- a/spec/bundler/bundler/yaml_serializer_spec.rb +++ b/spec/bundler/bundler/yaml_serializer_spec.rb @@ -9,7 +9,7 @@ RSpec.describe Bundler::YAMLSerializer do it "works for simple hash" do hash = { "Q" => "Where does Thursday come before Wednesday? In the dictionary. :P" } - expected = strip_whitespace <<-YAML + expected = <<~YAML --- Q: "Where does Thursday come before Wednesday? In the dictionary. :P" YAML @@ -24,7 +24,7 @@ RSpec.describe Bundler::YAMLSerializer do }, } - expected = strip_whitespace <<-YAML + expected = <<~YAML --- nice-one: read_ahead: "All generalizations are false, including this one" @@ -45,7 +45,7 @@ RSpec.describe Bundler::YAMLSerializer do }, } - expected = strip_whitespace <<-YAML + expected = <<~YAML --- nested_hash: contains_array: @@ -57,11 +57,24 @@ RSpec.describe Bundler::YAMLSerializer do expect(serializer.dump(hash)).to eq(expected) end + + it "handles empty array" do + hash = { + "empty_array" => [], + } + + expected = <<~YAML + --- + empty_array: [] + YAML + + expect(serializer.dump(hash)).to eq(expected) + end end describe "#load" do it "works for simple hash" do - yaml = strip_whitespace <<-YAML + yaml = <<~YAML --- Jon: "Air is free dude!" Jack: "Yes.. until you buy a bag of chips!" @@ -76,7 +89,7 @@ RSpec.describe Bundler::YAMLSerializer do end it "works for nested hash" do - yaml = strip_whitespace <<-YAML + yaml = <<~YAML --- baa: baa: "black sheep" @@ -98,7 +111,7 @@ RSpec.describe Bundler::YAMLSerializer do end it "handles colon in key/value" do - yaml = strip_whitespace <<-YAML + yaml = <<~YAML BUNDLE_MIRROR__HTTPS://RUBYGEMS__ORG/: http://rubygems-mirror.org YAML @@ -106,7 +119,7 @@ RSpec.describe Bundler::YAMLSerializer do end it "handles arrays inside hashes" do - yaml = strip_whitespace <<-YAML + yaml = <<~YAML --- nested_hash: contains_array: @@ -127,7 +140,7 @@ RSpec.describe Bundler::YAMLSerializer do end it "handles windows-style CRLF line endings" do - yaml = strip_whitespace(<<-YAML).gsub("\n", "\r\n") + yaml = <<~YAML.gsub("\n", "\r\n") --- nested_hash: contains_array: @@ -148,6 +161,34 @@ RSpec.describe Bundler::YAMLSerializer do expect(serializer.load(yaml)).to eq(hash) end + + it "handles empty array" do + yaml = <<~YAML + --- + empty_array: [] + YAML + + hash = { + "empty_array" => [], + } + + expect(serializer.load(yaml)).to eq(hash) + end + + it "skip commented out words" do + yaml = <<~YAML + --- + foo: bar + buzz: foo # bar + YAML + + hash = { + "foo" => "bar", + "buzz" => "foo", + } + + expect(serializer.load(yaml)).to eq(hash) + end end describe "against yaml lib" do diff --git a/spec/bundler/cache/gems_spec.rb b/spec/bundler/cache/gems_spec.rb index 72e372fb41..abbc2c3cf2 100644 --- a/spec/bundler/cache/gems_spec.rb +++ b/spec/bundler/cache/gems_spec.rb @@ -8,7 +8,7 @@ RSpec.describe "bundle cache" do gem 'rack' G - system_gems "rack-1.0.0", :path => path + system_gems "rack-1.0.0", path: path bundle :cache end @@ -17,7 +17,7 @@ RSpec.describe "bundle cache" do end it "uses the cache as a source when installing gems" do - build_gem "omg", :path => bundled_app("vendor/cache") + build_gem "omg", path: bundled_app("vendor/cache") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -28,14 +28,14 @@ RSpec.describe "bundle cache" do end it "uses the cache as a source when installing gems with --local" do - system_gems [], :path => default_bundle_path + system_gems [], path: default_bundle_path bundle "install --local" expect(the_bundle).to include_gems("rack 1.0.0") end it "does not reinstall gems from the cache if they exist on the system" do - build_gem "rack", "1.0.0", :path => bundled_app("vendor/cache") do |s| + build_gem "rack", "1.0.0", path: bundled_app("vendor/cache") do |s| s.write "lib/rack.rb", "RACK = 'FAIL'" end @@ -48,18 +48,18 @@ RSpec.describe "bundle cache" do end it "does not reinstall gems from the cache if they exist in the bundle" do - system_gems "rack-1.0.0", :path => default_bundle_path + system_gems "rack-1.0.0", path: default_bundle_path gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" G - build_gem "rack", "1.0.0", :path => bundled_app("vendor/cache") do |s| + build_gem "rack", "1.0.0", path: bundled_app("vendor/cache") do |s| s.write "lib/rack.rb", "RACK = 'FAIL'" end - bundle :install, :local => true + bundle :install, local: true expect(the_bundle).to include_gems("rack 1.0.0") end @@ -89,39 +89,49 @@ RSpec.describe "bundle cache" do it_behaves_like "when there are only gemsources" end - describe "when there is a built-in gem" do + describe "when there is a built-in gem", :ruby_repo do + let(:default_json_version) { ruby "gem 'json'; require 'json'; puts JSON::VERSION" } + before :each do build_repo2 do - build_gem "builtin_gem", "1.0.2" + build_gem "json", default_json_version end - build_gem "builtin_gem", "1.0.2", :to_system => true do |s| - s.summary = "This builtin_gem is bundled with Ruby" - end - - FileUtils.rm("#{system_gem_path}/cache/builtin_gem-1.0.2.gem") + build_gem "json", default_json_version, to_system: true, default: true end it "uses builtin gems when installing to system gems" do bundle "config set path.system true" - install_gemfile %(source "#{file_uri_for(gem_repo1)}"; gem 'builtin_gem', '1.0.2') - expect(the_bundle).to include_gems("builtin_gem 1.0.2") + install_gemfile %(source "#{file_uri_for(gem_repo1)}"; gem 'json', '#{default_json_version}'), verbose: true + expect(out).to include("Using json #{default_json_version}") end it "caches remote and builtin gems" do install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" - gem 'builtin_gem', '1.0.2' + gem 'json', '#{default_json_version}' gem 'rack', '1.0.0' G bundle :cache expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist - expect(bundled_app("vendor/cache/builtin_gem-1.0.2.gem")).to exist + expect(bundled_app("vendor/cache/json-#{default_json_version}.gem")).to exist + end + + it "caches builtin gems when cache_all_platforms is set" do + gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "json" + G + + bundle "config set cache_all_platforms true" + + bundle :cache + expect(bundled_app("vendor/cache/json-#{default_json_version}.gem")).to exist end it "doesn't make remote request after caching the gem" do - build_gem "builtin_gem_2", "1.0.2", :path => bundled_app("vendor/cache") do |s| + build_gem "builtin_gem_2", "1.0.2", path: bundled_app("vendor/cache") do |s| s.summary = "This builtin_gem is bundled with Ruby" end @@ -139,12 +149,12 @@ RSpec.describe "bundle cache" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem 'builtin_gem', '1.0.2' + gem 'json', '#{default_json_version}' G - bundle :cache, :raise_on_error => false + bundle :cache, raise_on_error: false expect(exitstatus).to_not eq(0) - expect(err).to include("builtin_gem-1.0.2 is built in to Ruby, and can't be cached") + expect(err).to include("json-#{default_json_version} is built in to Ruby, and can't be cached") end end @@ -208,7 +218,7 @@ RSpec.describe "bundle cache" do end end - bundle "update", :all => true + bundle "update", all: true expect(cached_gem("rack-1.2")).to exist expect(cached_gem("rack-1.0.0")).not_to exist end @@ -269,10 +279,57 @@ RSpec.describe "bundle cache" do it "doesn't remove gems with mismatched :rubygems_version or :date" do cached_gem("rack-1.0.0").rmtree build_gem "rack", "1.0.0", - :path => bundled_app("vendor/cache"), - :rubygems_version => "1.3.2" + path: bundled_app("vendor/cache"), + rubygems_version: "1.3.2" + # This test is only really valid if the checksum isn't saved. It otherwise can't be the same gem. Tested below. + bundled_app_lock.write remove_checksums_from_lockfile(bundled_app_lock.read, "rack (1.0.0)") + simulate_new_machine + + bundle :install + expect(cached_gem("rack-1.0.0")).to exist + end + + it "raises an error when the gem is altered and produces a different checksum" do + cached_gem("rack-1.0.0").rmtree + build_gem "rack", "1.0.0", path: bundled_app("vendor/cache") + + checksums = checksums_section do |c| + c.checksum gem_repo1, "rack", "1.0.0" + end + simulate_new_machine + lockfile <<-L + GEM + remote: #{file_uri_for(gem_repo2)}/ + specs: + rack (1.0.0) + #{checksums} + L + + bundle :install, raise_on_error: false + expect(exitstatus).to eq(37) + expect(err).to include("Bundler found mismatched checksums.") + expect(err).to include("1. remove the gem at #{cached_gem("rack-1.0.0")}") + + expect(cached_gem("rack-1.0.0")).to exist + cached_gem("rack-1.0.0").rmtree + bundle :install + expect(cached_gem("rack-1.0.0")).to exist + end + + it "installs a modified gem with a non-matching checksum when checksums is not opted in" do + cached_gem("rack-1.0.0").rmtree + build_gem "rack", "1.0.0", path: bundled_app("vendor/cache") + simulate_new_machine + + lockfile <<-L + GEM + remote: #{file_uri_for(gem_repo2)}/ + specs: + rack (1.0.0) + L + bundle :install expect(cached_gem("rack-1.0.0")).to exist end @@ -304,7 +361,7 @@ RSpec.describe "bundle cache" do it "should install gems with the name bundler in them (that aren't bundler)" do build_gem "foo-bundler", "1.0", - :path => bundled_app("vendor/cache") + path: bundled_app("vendor/cache") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" diff --git a/spec/bundler/cache/git_spec.rb b/spec/bundler/cache/git_spec.rb index b88993e9b1..4b3cd4d2eb 100644 --- a/spec/bundler/cache/git_spec.rb +++ b/spec/bundler/cache/git_spec.rb @@ -15,7 +15,7 @@ end RSpec.describe "bundle cache with git" do it "copies repository to vendor cache and uses it" do git = build_git "foo" - ref = git.ref_for("master", 11) + ref = git.ref_for("main", 11) install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -34,7 +34,7 @@ RSpec.describe "bundle cache with git" do it "copies repository to vendor cache and uses it even when configured with `path`" do git = build_git "foo" - ref = git.ref_for("master", 11) + ref = git.ref_for("main", 11) install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -72,7 +72,7 @@ RSpec.describe "bundle cache with git" do it "tracks updates" do git = build_git "foo" - old_ref = git.ref_for("master", 11) + old_ref = git.ref_for("main", 11) install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -86,11 +86,10 @@ RSpec.describe "bundle cache with git" do s.write "lib/foo.rb", "puts :CACHE" end - ref = git.ref_for("master", 11) + ref = git.ref_for("main", 11) expect(ref).not_to eq(old_ref) - bundle "update", :all => true - bundle "config set cache_all true" + bundle "update", all: true bundle :cache expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist @@ -103,7 +102,7 @@ RSpec.describe "bundle cache with git" do it "tracks updates when specifying the gem" do git = build_git "foo" - old_ref = git.ref_for("master", 11) + old_ref = git.ref_for("main", 11) install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -117,7 +116,7 @@ RSpec.describe "bundle cache with git" do s.write "lib/foo.rb", "puts :CACHE" end - ref = git.ref_for("master", 11) + ref = git.ref_for("main", 11) expect(ref).not_to eq(old_ref) bundle "update foo" @@ -132,11 +131,11 @@ RSpec.describe "bundle cache with git" do it "uses the local repository to generate the cache" do git = build_git "foo" - ref = git.ref_for("master", 11) + ref = git.ref_for("main", 11) gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "foo", :git => '#{lib_path("foo-invalid")}', :branch => :master + gem "foo", :git => '#{lib_path("foo-invalid")}', :branch => :main G bundle %(config set local.foo #{lib_path("foo-1.0")}) @@ -156,14 +155,17 @@ RSpec.describe "bundle cache with git" do end it "copies repository to vendor cache, including submodules" do + # CVE-2022-39253: https://lore.kernel.org/lkml/xmqq4jw1uku5.fsf@gitster.g/ + system(*%W[git config --global protocol.file.allow always]) + build_git "submodule", "1.0" git = build_git "has_submodule", "1.0" do |s| s.add_dependency "submodule" end - sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0", :dir => lib_path("has_submodule-1.0") - sys_exec "git commit -m \"submodulator\"", :dir => lib_path("has_submodule-1.0") + sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0", dir: lib_path("has_submodule-1.0") + sys_exec "git commit -m \"submodulator\"", dir: lib_path("has_submodule-1.0") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -172,7 +174,7 @@ RSpec.describe "bundle cache with git" do end G - ref = git.ref_for("master", 11) + ref = git.ref_for("main", 11) bundle "config set cache_all true" bundle :cache @@ -196,7 +198,7 @@ RSpec.describe "bundle cache with git" do bundle "config set cache_all true" bundle :cache - ref = git.ref_for("master", 11) + ref = git.ref_for("main", 11) gemspec = bundled_app("vendor/cache/foo-1.0-#{ref}/foo.gemspec").read expect(gemspec).to_not match("`echo bob`") end @@ -214,8 +216,61 @@ RSpec.describe "bundle cache with git" do simulate_new_machine with_path_as "" do bundle "config set deployment true" - bundle :install, :local => true + bundle :install, local: true expect(the_bundle).to include_gem "foo 1.0" end end + + it "respects the --no-install flag" do + git = build_git "foo", &:add_c_extension + ref = git.ref_for("main", 11) + + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "foo", :git => '#{lib_path("foo-1.0")}' + G + bundle "config set cache_all true" + + # The algorithm for the cache location for a git checkout is + # in Bundle::Source::Git#cache_path + cache_path_name = "foo-1.0-#{Digest(:SHA1).hexdigest(lib_path("foo-1.0").to_s)}" + + # Run this test twice. This is because materially different codepaths + # will get hit the second time around. + # The first time, Bundler::Sources::Git#install_path is set to the system + # wide cache directory bundler/gems; the second time, it's set to the + # vendor/cache directory. We don't want the native extension to appear in + # either of these places, so run the `bundle cache` command twice. + 2.times do + bundle :cache, "all-platforms" => true, :install => false + + # it did _NOT_ actually install the gem - neither in $GEM_HOME (bundler 2 mode), + # nor in .bundle (bundler 3 mode) + expect(Pathname.new(File.join(default_bundle_path, "gems/foo-1.0-#{ref}"))).to_not exist + # it _did_ cache the gem in vendor/ + expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist + # it did _NOT_ build the gems extensions in the vendor/ dir + expect(Dir[bundled_app("vendor/cache/foo-1.0-#{ref}/lib/foo_c*")]).to be_empty + # it _did_ cache the git checkout + expect(default_cache_path("git", cache_path_name)).to exist + # And the checkout is a bare checkout + expect(default_cache_path("git", cache_path_name, "HEAD")).to exist + end + + # Subsequently installing the gem should compile it. + # _currently_, the gem gets compiled in vendor/cache, and vendor/cache is added + # to the $LOAD_PATH for git extensions, so it all kind of "works". However, in the + # future we would like to stop adding vendor/cache to the $LOAD_PATH for git extensions + # and instead treat them identically to normal gems (where the gem install location, + # not the cache location, is added to $LOAD_PATH). + # Verify that the compilation worked and the result is in $LOAD_PATH by simply attempting + # to require it; that should make sure this spec does not break if the load path behaviour + # is changed. + bundle :install, local: true + ruby <<~R, raise_on_error: false + require 'bundler/setup' + require 'foo_c' + R + expect(last_command).to_not be_failure + end end diff --git a/spec/bundler/cache/path_spec.rb b/spec/bundler/cache/path_spec.rb index 2ad136a008..2c8a52617a 100644 --- a/spec/bundler/cache/path_spec.rb +++ b/spec/bundler/cache/path_spec.rb @@ -2,7 +2,7 @@ RSpec.describe "bundle cache with path" do it "is no-op when the path is within the bundle" do - build_lib "foo", :path => bundled_app("lib/foo") + build_lib "foo", path: bundled_app("lib/foo") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -35,7 +35,7 @@ RSpec.describe "bundle cache with path" do libname = File.basename(bundled_app) + "_gem" libpath = File.join(File.dirname(bundled_app), libname) - build_lib libname, :path => libpath + build_lib libname, path: libpath install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -97,7 +97,7 @@ RSpec.describe "bundle cache with path" do expect(bundled_app("vendor/cache/foo-1.0")).not_to exist end - it "does not cache path gems by default", :bundler => "< 3" do + it "does not cache path gems by default", bundler: "< 3" do build_lib "foo" install_gemfile <<-G @@ -110,7 +110,7 @@ RSpec.describe "bundle cache with path" do expect(bundled_app("vendor/cache/foo-1.0")).not_to exist end - it "caches path gems by default", :bundler => "3" do + it "caches path gems by default", bundler: "3" do build_lib "foo" install_gemfile <<-G @@ -163,7 +163,7 @@ RSpec.describe "bundle cache with path" do gem "baz", :path => '#{lib_path("baz-1.0")}' G - bundle "cache --no-all", :raise_on_error => false + bundle "cache --no-all", raise_on_error: false expect(bundled_app("vendor/cache/baz-1.0")).not_to exist end end diff --git a/spec/bundler/cache/platform_spec.rb b/spec/bundler/cache/platform_spec.rb index 128278956c..36db954c79 100644 --- a/spec/bundler/cache/platform_spec.rb +++ b/spec/bundler/cache/platform_spec.rb @@ -41,7 +41,7 @@ RSpec.describe "bundle cache with multiple platforms" do end it "ensures that a successful bundle update does not delete gems for other platforms" do - bundle "update", :all => true + bundle "update", all: true expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist expect(bundled_app("vendor/cache/activesupport-2.3.5.gem")).to exist diff --git a/spec/bundler/commands/add_spec.rb b/spec/bundler/commands/add_spec.rb index 4c533652ca..36e286793b 100644 --- a/spec/bundler/commands/add_spec.rb +++ b/spec/bundler/commands/add_spec.rb @@ -9,6 +9,7 @@ RSpec.describe "bundle add" do build_gem "bar", "0.12.3" build_gem "cat", "0.12.3.pre" build_gem "dog", "1.1.3.pre" + build_gem "lemur", "3.1.1.pre.2023.1.1" end build_git "foo", "2.0" @@ -21,7 +22,7 @@ RSpec.describe "bundle add" do context "when no gems are specified" do it "shows error" do - bundle "add", :raise_on_error => false + bundle "add", raise_on_error: false expect(err).to include("Please specify gems to add") end @@ -51,6 +52,13 @@ RSpec.describe "bundle add" do expect(bundled_app_gemfile.read).to match(/gem "dog", "~> 1.1.pre"/) expect(the_bundle).to include_gems "dog 1.1.3.pre" end + + it "version requirement becomes ~> major.minor.pre.tail when resolved version has a very long tail pre version" do + bundle "add 'lemur'" + # the trailing pre purposely matches the release version to ensure that subbing the release doesn't change the pre.version" + expect(bundled_app_gemfile.read).to match(/gem "lemur", "~> 3.1.pre.2023.1.1"/) + expect(the_bundle).to include_gems "lemur 3.1.1.pre.2023.1.1" + end end describe "with --version" do @@ -63,11 +71,23 @@ RSpec.describe "bundle add" do it "adds multiple version constraints when specified" do requirements = ["< 3.0", "> 1.0"] bundle "add 'foo' --version='#{requirements.join(", ")}'" - expect(bundled_app_gemfile.read).to match(/gem "foo", #{Gem::Requirement.new(requirements).as_list.map(&:dump).join(', ')}/) + expect(bundled_app_gemfile.read).to match(/gem "foo", #{Gem::Requirement.new(requirements).as_list.map(&:dump).join(", ")}/) expect(the_bundle).to include_gems "foo 2.0" end end + describe "with --require" do + it "adds the require param for the gem" do + bundle "add 'foo' --require=foo/engine" + expect(bundled_app_gemfile.read).to match(%r{gem "foo",(?: .*,) :require => "foo\/engine"}) + end + + it "converts false to a boolean" do + bundle "add 'foo' --require=false" + expect(bundled_app_gemfile.read).to match(/gem "foo",(?: .*,) :require => false/) + end + end + describe "with --group" do it "adds dependency for the specified group" do bundle "add 'foo' --group='development'" @@ -91,8 +111,17 @@ RSpec.describe "bundle add" do end end + describe "with --path" do + it "adds dependency with specified path" do + bundle "add 'foo' --path='#{lib_path("foo-2.0")}'" + + expect(bundled_app_gemfile.read).to match(/gem "foo", "~> 2.0", :path => "#{lib_path("foo-2.0")}"/) + expect(the_bundle).to include_gems "foo 2.0" + end + end + describe "with --git" do - it "adds dependency with specified github source" do + it "adds dependency with specified git source" do bundle "add foo --git=#{lib_path("foo-2.0")}" expect(bundled_app_gemfile.read).to match(/gem "foo", "~> 2.0", :git => "#{lib_path("foo-2.0")}"/) @@ -102,10 +131,10 @@ RSpec.describe "bundle add" do describe "with --git and --branch" do before do - update_git "foo", "2.0", :branch => "test" + update_git "foo", "2.0", branch: "test" end - it "adds dependency with specified github source and branch" do + it "adds dependency with specified git source and branch" do bundle "add foo --git=#{lib_path("foo-2.0")} --branch=test" expect(bundled_app_gemfile.read).to match(/gem "foo", "~> 2.0", :git => "#{lib_path("foo-2.0")}", :branch => "test"/) @@ -113,6 +142,94 @@ RSpec.describe "bundle add" do end end + describe "with --git and --ref" do + it "adds dependency with specified git source and branch" do + bundle "add foo --git=#{lib_path("foo-2.0")} --ref=#{revision_for(lib_path("foo-2.0"))}" + + expect(bundled_app_gemfile.read).to match(/gem "foo", "~> 2\.0", :git => "#{lib_path("foo-2.0")}", :ref => "#{revision_for(lib_path("foo-2.0"))}"/) + expect(the_bundle).to include_gems "foo 2.0" + end + end + + describe "with --github" do + it "adds dependency with specified github source", :realworld do + bundle "add rake --github=ruby/rake" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", :github => "ruby\/rake"}) + end + end + + describe "with --github and --branch" do + it "adds dependency with specified github source and branch", :realworld do + bundle "add rake --github=ruby/rake --branch=master" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", :github => "ruby\/rake", :branch => "master"}) + end + end + + describe "with --github and --ref" do + it "adds dependency with specified github source and ref", :realworld do + bundle "add rake --github=ruby/rake --ref=5c60da8" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", :github => "ruby\/rake", :ref => "5c60da8"}) + end + end + + describe "with --git and --glob" do + it "adds dependency with specified git source" do + bundle "add foo --git=#{lib_path("foo-2.0")} --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "foo", "~> 2.0", :git => "#{lib_path("foo-2.0")}", :glob => "\./\*\.gemspec"}) + expect(the_bundle).to include_gems "foo 2.0" + end + end + + describe "with --git and --branch and --glob" do + before do + update_git "foo", "2.0", branch: "test" + end + + it "adds dependency with specified git source and branch" do + bundle "add foo --git=#{lib_path("foo-2.0")} --branch=test --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "foo", "~> 2.0", :git => "#{lib_path("foo-2.0")}", :branch => "test", :glob => "\./\*\.gemspec"}) + expect(the_bundle).to include_gems "foo 2.0" + end + end + + describe "with --git and --ref and --glob" do + it "adds dependency with specified git source and branch" do + bundle "add foo --git=#{lib_path("foo-2.0")} --ref=#{revision_for(lib_path("foo-2.0"))} --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "foo", "~> 2\.0", :git => "#{lib_path("foo-2.0")}", :ref => "#{revision_for(lib_path("foo-2.0"))}", :glob => "\./\*\.gemspec"}) + expect(the_bundle).to include_gems "foo 2.0" + end + end + + describe "with --github and --glob" do + it "adds dependency with specified github source", :realworld do + bundle "add rake --github=ruby/rake --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", :github => "ruby\/rake", :glob => "\.\/\*\.gemspec"}) + end + end + + describe "with --github and --branch --and glob" do + it "adds dependency with specified github source and branch", :realworld do + bundle "add rake --github=ruby/rake --branch=master --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", :github => "ruby\/rake", :branch => "master", :glob => "\.\/\*\.gemspec"}) + end + end + + describe "with --github and --ref and --glob" do + it "adds dependency with specified github source and ref", :realworld do + bundle "add rake --github=ruby/rake --ref=5c60da8 --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", :github => "ruby\/rake", :ref => "5c60da8", :glob => "\.\/\*\.gemspec"}) + end + end + describe "with --skip-install" do it "adds gem to Gemfile but is not installed" do bundle "add foo --skip-install --version=2.0" @@ -129,24 +246,24 @@ RSpec.describe "bundle add" do end it "shows error message when version is not formatted correctly" do - bundle "add 'foo' -v='~>1 . 0'", :raise_on_error => false + bundle "add 'foo' -v='~>1 . 0'", raise_on_error: false expect(err).to match("Invalid gem requirement pattern '~>1 . 0'") end it "shows error message when gem cannot be found" do bundle "config set force_ruby_platform true" - bundle "add 'werk_it'", :raise_on_error => false + bundle "add 'werk_it'", raise_on_error: false expect(err).to match("Could not find gem 'werk_it' in") - bundle "add 'werk_it' -s='#{file_uri_for(gem_repo2)}'", :raise_on_error => false + bundle "add 'werk_it' -s='#{file_uri_for(gem_repo2)}'", raise_on_error: false expect(err).to match("Could not find gem 'werk_it' in rubygems repository") end it "shows error message when source cannot be reached" do - bundle "add 'baz' --source='http://badhostasdf'", :raise_on_error => false + bundle "add 'baz' --source='http://badhostasdf'", raise_on_error: false expect(err).to include("Could not reach host badhostasdf. Check your network connection and try again.") - bundle "add 'baz' --source='file://does/not/exist'", :raise_on_error => false + bundle "add 'baz' --source='file://does/not/exist'", raise_on_error: false expect(err).to include("Could not fetch specs from file://does/not/exist/") end @@ -176,7 +293,7 @@ RSpec.describe "bundle add" do describe "with --optimistic and --strict" do it "throws error" do - bundle "add 'foo' --strict --optimistic", :raise_on_error => false + bundle "add 'foo' --strict --optimistic", raise_on_error: false expect(err).to include("You can not specify `--strict` and `--optimistic` at the same time") end @@ -191,7 +308,7 @@ RSpec.describe "bundle add" do end it "throws error if any of the specified gems are present in the gemfile with different version" do - bundle "add weakling bar", :raise_on_error => false + bundle "add weakling bar", raise_on_error: false expect(err).to include("You cannot specify the same gem twice with different version requirements") expect(err).to include("You specified: weakling (~> 0.0.1) and weakling (>= 0).") @@ -205,7 +322,7 @@ RSpec.describe "bundle add" do gem "rack", "1.0" G - bundle "add 'rack' --version=1.1", :raise_on_error => false + bundle "add 'rack' --version=1.1", raise_on_error: false expect(err).to include("You cannot specify the same gem twice with different version requirements") expect(err).to include("If you want to update the gem version, run `bundle update rack`. You may also need to change the version requirement specified in the Gemfile if it's too restrictive") @@ -217,7 +334,7 @@ RSpec.describe "bundle add" do gem "rack", "1.0" G - bundle "add 'rack'", :raise_on_error => false + bundle "add 'rack'", raise_on_error: false expect(err).to include("Gem already added.") expect(err).to include("You cannot specify the same gem twice with different version requirements") @@ -232,7 +349,7 @@ RSpec.describe "bundle add" do gem "rack" G - bundle "add 'rack' --version=1.1", :raise_on_error => false + bundle "add 'rack' --version=1.1", raise_on_error: false expect(err).to include("You cannot specify the same gem twice with different version requirements") expect(err).to include("If you want to update the gem version, run `bundle update rack`.") diff --git a/spec/bundler/commands/binstubs_spec.rb b/spec/bundler/commands/binstubs_spec.rb index fb5da98bf3..6c3dc7bb2d 100644 --- a/spec/bundler/commands/binstubs_spec.rb +++ b/spec/bundler/commands/binstubs_spec.rb @@ -45,7 +45,7 @@ RSpec.describe "bundle binstubs <gem>" do gem "rails" G - bundle :binstubs, :all => true + bundle :binstubs, all: true expect(bundled_app("bin/rails")).to exist expect(bundled_app("bin/rake")).to exist @@ -69,7 +69,7 @@ RSpec.describe "bundle binstubs <gem>" do gem "rack" G - bundle "binstubs", :raise_on_error => false + bundle "binstubs", raise_on_error: false expect(exitstatus).to eq(1) expect(err).to include("`bundle binstubs` needs at least one gem to run.") end @@ -80,7 +80,7 @@ RSpec.describe "bundle binstubs <gem>" do gem "rack" G - bundle "binstubs rack", :all => true, :raise_on_error => false + bundle "binstubs rack", all: true, raise_on_error: false expect(last_command).to be_failure expect(err).to include("Cannot specify --all with specific gems") end @@ -98,7 +98,7 @@ RSpec.describe "bundle binstubs <gem>" do file.print "OMG" end - sys_exec "bin/rackup", :raise_on_error => false + sys_exec "bin/rackup", raise_on_error: false expect(err).to include("was not generated by Bundler") end @@ -116,7 +116,7 @@ RSpec.describe "bundle binstubs <gem>" do s.executables = "print_loaded_gems" s.bindir = "exe" s.write "exe/print_loaded_gems", <<-R - specs = Gem.loaded_specs.values.reject {|s| Bundler.rubygems.spec_default_gem?(s) } + specs = Gem.loaded_specs.values.reject {|s| s.default_gem? } puts specs.map(&:full_name).sort.inspect R end @@ -132,13 +132,13 @@ RSpec.describe "bundle binstubs <gem>" do let(:system_bundler_version) { Bundler::VERSION } it "runs bundler" do - sys_exec "bin/bundle install", :env => { "DEBUG" => "1" } + sys_exec "bin/bundle install", env: { "DEBUG" => "1" } expect(out).to include %(Using bundler #{system_bundler_version}\n) end context "when BUNDLER_VERSION is set" do it "runs the correct version of bundler" do - sys_exec "bin/bundle install", :env => { "BUNDLER_VERSION" => "999.999.999" }, :raise_on_error => false + sys_exec "bin/bundle install", env: { "BUNDLER_VERSION" => "999.999.999" }, raise_on_error: false expect(exitstatus).to eq(42) expect(err).to include("Activating bundler (999.999.999) failed:"). and include("To install the version of bundler this project requires, run `gem install bundler -v '999.999.999'`") @@ -147,7 +147,7 @@ RSpec.describe "bundle binstubs <gem>" do it "runs the correct version of bundler even if a higher version is installed" do system_gems "bundler-999.999.998", "bundler-999.999.999" - sys_exec "bin/bundle install", :env => { "BUNDLER_VERSION" => "999.999.998", "DEBUG" => "1" }, :raise_on_error => false + sys_exec "bin/bundle install", env: { "BUNDLER_VERSION" => "999.999.998", "DEBUG" => "1" }, raise_on_error: false expect(out).to include %(Using bundler 999.999.998\n) end end @@ -159,7 +159,22 @@ RSpec.describe "bundle binstubs <gem>" do end it "runs the correct version of bundler" do - sys_exec "bin/bundle install", :raise_on_error => false + sys_exec "bin/bundle install", raise_on_error: false + expect(exitstatus).to eq(42) + expect(err).to include("Activating bundler (~> 999.999) failed:"). + and include("To install the version of bundler this project requires, run `gem install bundler -v '~> 999.999'`") + end + end + + context "and the version is newer when given `gems.rb` and `gems.locked`" do + before do + gemfile bundled_app("gems.rb"), gemfile + lockfile bundled_app("gems.locked"), lockfile.gsub(system_bundler_version, "999.999") + end + + it "runs the correct version of bundler" do + sys_exec "bin/bundle install", env: { "BUNDLE_GEMFILE" => "gems.rb" }, raise_on_error: false + expect(exitstatus).to eq(42) expect(err).to include("Activating bundler (~> 999.999) failed:"). and include("To install the version of bundler this project requires, run `gem install bundler -v '~> 999.999'`") @@ -174,7 +189,23 @@ RSpec.describe "bundle binstubs <gem>" do end it "runs the correct version of bundler" do - sys_exec "bin/bundle install", :raise_on_error => false + sys_exec "bin/bundle install", raise_on_error: false + expect(exitstatus).to eq(42) + expect(err).to include("Activating bundler (~> 44.0) failed:"). + and include("To install the version of bundler this project requires, run `gem install bundler -v '~> 44.0'`") + end + end + + context "and the version is older and a different major when given `gems.rb` and `gems.locked`" do + let(:system_bundler_version) { "55" } + + before do + gemfile bundled_app("gems.rb"), gemfile + lockfile bundled_app("gems.locked"), lockfile.gsub(/BUNDLED WITH\n .*$/m, "BUNDLED WITH\n 44.0") + end + + it "runs the correct version of bundler" do + sys_exec "bin/bundle install", env: { "BUNDLE_GEMFILE" => "gems.rb" }, raise_on_error: false expect(exitstatus).to eq(42) expect(err).to include("Activating bundler (~> 44.0) failed:"). and include("To install the version of bundler this project requires, run `gem install bundler -v '~> 44.0'`") @@ -182,16 +213,26 @@ RSpec.describe "bundle binstubs <gem>" do end context "and the version is older and the same major" do - let(:system_bundler_version) { "55.1" } + let(:system_bundler_version) { "2.999.999" } before do - lockfile lockfile.gsub(/BUNDLED WITH\n .*$/m, "BUNDLED WITH\n 55.0") + lockfile lockfile.gsub(/BUNDLED WITH\n .*$/m, "BUNDLED WITH\n 2.3.0") end - it "runs the available version of bundler when the version is older and the same major" do - sys_exec "bin/bundle install" + it "installs and runs the exact version of bundler", rubygems: ">= 3.3.0.dev", realworld: true do + sys_exec "bin/bundle install --verbose", artifice: "vcr" expect(exitstatus).not_to eq(42) - expect(err).not_to include("Activating bundler (~> 55.0) failed:") + expect(out).to include("Bundler 2.999.999 is running, but your lockfile was generated with 2.3.0. Installing Bundler 2.3.0 and restarting using that version.") + expect(out).to include("Using bundler 2.3.0") + expect(err).not_to include("Activating bundler (~> 2.3.0) failed:") + end + + it "runs the available version of bundler", rubygems: "< 3.3.0.dev" do + sys_exec "bin/bundle install --verbose" + expect(exitstatus).not_to eq(42) + expect(out).not_to include("Bundler 2.999.999 is running, but your lockfile was generated with 2.3.0. Installing Bundler 2.3.0 and restarting using that version.") + expect(out).to include("Using bundler 2.999.999") + expect(err).not_to include("Activating bundler (~> 2.3.0) failed:") end end @@ -203,7 +244,7 @@ RSpec.describe "bundle binstubs <gem>" do end it "runs the correct version of bundler when the version is a pre-release" do - sys_exec "bin/bundle install", :raise_on_error => false + sys_exec "bin/bundle install", raise_on_error: false expect(exitstatus).to eq(42) expect(err).to include("Activating bundler (~> 2.12.a) failed:"). and include("To install the version of bundler this project requires, run `gem install bundler -v '~> 2.12.a'`") @@ -214,13 +255,16 @@ RSpec.describe "bundle binstubs <gem>" do context "when update --bundler is called" do before { lockfile.gsub(system_bundler_version, "1.1.1") } - it "calls through to the latest bundler version" do - sys_exec "bin/bundle update --bundler", :env => { "DEBUG" => "1" } - expect(out).to include %(Using bundler #{system_bundler_version}\n) + it "calls through to the latest bundler version", :realworld do + sys_exec "bin/bundle update --bundler", env: { "DEBUG" => "1" } + using_bundler_line = /Using bundler ([\w\.]+)\n/.match(out) + expect(using_bundler_line).to_not be_nil + latest_version = using_bundler_line[1] + expect(Gem::Version.new(latest_version)).to be >= Gem::Version.new(system_bundler_version) end it "calls through to the explicit bundler version" do - sys_exec "bin/bundle update --bundler=999.999.999", :raise_on_error => false + sys_exec "bin/bundle update --bundler=999.999.999", raise_on_error: false expect(exitstatus).to eq(42) expect(err).to include("Activating bundler (999.999.999) failed:"). and include("To install the version of bundler this project requires, run `gem install bundler -v '999.999.999'`") @@ -230,7 +274,7 @@ RSpec.describe "bundle binstubs <gem>" do context "without a lockfile" do it "falls back to the latest installed bundler" do FileUtils.rm bundled_app_lock - sys_exec "bin/bundle install", :env => { "DEBUG" => "1" } + sys_exec "bin/bundle install", env: { "DEBUG" => "1" } expect(out).to include "Using bundler #{system_bundler_version}\n" end end @@ -245,7 +289,7 @@ RSpec.describe "bundle binstubs <gem>" do before { lockfile lockfile.gsub(Bundler::VERSION, "999.999.999") } it "attempts to load that version" do - sys_exec bundled_app("bin/rackup").to_s, :raise_on_error => false + sys_exec bundled_app("bin/rackup").to_s, raise_on_error: false expect(exitstatus).to eq(42) expect(err).to include("Activating bundler (~> 999.999) failed:"). and include("To install the version of bundler this project requires, run `gem install bundler -v '~> 999.999'`") @@ -257,7 +301,7 @@ RSpec.describe "bundle binstubs <gem>" do it "installs binstubs from git gems" do FileUtils.mkdir_p(lib_path("foo/bin")) FileUtils.touch(lib_path("foo/bin/foo")) - build_git "foo", "1.0", :path => lib_path("foo") do |s| + build_git "foo", "1.0", path: lib_path("foo") do |s| s.executables = %w[foo] end install_gemfile <<-G @@ -273,7 +317,7 @@ RSpec.describe "bundle binstubs <gem>" do it "installs binstubs from path gems" do FileUtils.mkdir_p(lib_path("foo/bin")) FileUtils.touch(lib_path("foo/bin/foo")) - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.executables = %w[foo] end install_gemfile <<-G @@ -287,8 +331,6 @@ RSpec.describe "bundle binstubs <gem>" do end it "sets correct permissions for binstubs" do - skip "https://github.com/rubygems/rubygems/issues/3352" if Gem.win_platform? - with_umask(0o002) do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -297,7 +339,7 @@ RSpec.describe "bundle binstubs <gem>" do bundle "binstubs rack" binary = bundled_app("bin/rackup") - expect(File.stat(binary).mode.to_s(8)).to eq("100775") + expect(File.stat(binary).mode.to_s(8)).to eq(Gem.win_platform? ? "100644" : "100775") end end @@ -320,7 +362,7 @@ RSpec.describe "bundle binstubs <gem>" do source "#{file_uri_for(gem_repo1)}" G - bundle "binstubs doesnt_exist", :raise_on_error => false + bundle "binstubs doesnt_exist", raise_on_error: false expect(exitstatus).to eq(7) expect(err).to include("Could not find gem 'doesnt_exist'.") @@ -339,14 +381,14 @@ RSpec.describe "bundle binstubs <gem>" do expect(bundled_app("exec/rackup")).to exist end - it "setting is saved for bundle install", :bundler => "< 3" do + it "setting is saved for bundle install", bundler: "< 3" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" gem "rails" G - bundle "binstubs rack", :path => "exec" + bundle "binstubs rack", path: "exec" bundle :install expect(bundled_app("exec/rails")).to exist @@ -358,6 +400,7 @@ RSpec.describe "bundle binstubs <gem>" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" + gem "rails" G end @@ -385,6 +428,26 @@ RSpec.describe "bundle binstubs <gem>" do expect(bundled_app("bin/rackup.cmd")).to exist end end + + context "when the gem is bundler" do + it "warns without generating a standalone binstub" do + bundle "binstubs bundler --standalone" + expect(bundled_app("bin/bundle")).not_to exist + expect(bundled_app("bin/bundler")).not_to exist + expect(err).to include("Sorry, Bundler can only be run via RubyGems.") + end + end + + context "when specified --all option" do + it "generates standalone binstubs for all gems except bundler" do + bundle "binstubs --standalone --all" + expect(bundled_app("bin/rackup")).to exist + expect(bundled_app("bin/rails")).to exist + expect(bundled_app("bin/bundle")).not_to exist + expect(bundled_app("bin/bundler")).not_to exist + expect(err).not_to include("Sorry, Bundler can only be run via RubyGems.") + end + end end context "when the bin already exists" do @@ -486,7 +549,7 @@ RSpec.describe "bundle binstubs <gem>" do G bundle "config set auto_install 1" - bundle "binstubs rack", :env => { "BUNDLE_INSTALL" => "1" } + bundle "binstubs rack", env: { "BUNDLE_INSTALL" => "1" } expect(out).not_to include("Installing rack 1.0.0") end end diff --git a/spec/bundler/commands/cache_spec.rb b/spec/bundler/commands/cache_spec.rb index 4f9c1c26c4..70e2c84961 100644 --- a/spec/bundler/commands/cache_spec.rb +++ b/spec/bundler/commands/cache_spec.rb @@ -158,7 +158,7 @@ RSpec.describe "bundle cache" do end end - context "with --path", :bundler => "< 3" do + context "with --path", bundler: "< 3" do it "sets root directory for gems" do gemfile <<-D source "#{file_uri_for(gem_repo1)}" @@ -211,7 +211,17 @@ RSpec.describe "bundle cache" do end context "with --all-platforms" do - it "puts the gems in vendor/cache even for other rubies" do + it "puts the gems in vendor/cache even for other rubies", bundler: ">= 2.4.0" do + gemfile <<-D + source "#{file_uri_for(gem_repo1)}" + gem 'rack', :platforms => [:ruby_20, :windows_20] + D + + bundle "cache --all-platforms" + expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + end + + it "puts the gems in vendor/cache even for legacy windows rubies", bundler: ">= 2.4.0" do gemfile <<-D source "#{file_uri_for(gem_repo1)}" gem 'rack', :platforms => [:ruby_20, :x64_mingw_20] @@ -262,7 +272,7 @@ RSpec.describe "bundle cache" do end G - bundle :lock, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s } + bundle :lock, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s } bundle :cache, "all-platforms" => true, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s } expect(bundled_app("vendor/cache/weakling-0.0.3.gem")).to exist end @@ -279,7 +289,7 @@ RSpec.describe "bundle cache" do subject do bundle "config set --local frozen true" - bundle :cache, :raise_on_error => false + bundle :cache, raise_on_error: false end it "tries to install with frozen" do @@ -291,7 +301,7 @@ RSpec.describe "bundle cache" do G subject expect(exitstatus).to eq(16) - expect(err).to include("deployment mode") + expect(err).to include("frozen mode") expect(err).to include("You have added to the Gemfile") expect(err).to include("* rack-obama") bundle "env" @@ -383,7 +393,7 @@ RSpec.describe "bundle install with gem sources" do G bundle :cache - build_gem "rack", "1.0.0", :path => bundled_app("vendor/cache") do |s| + build_gem "rack", "1.0.0", path: bundled_app("vendor/cache") do |s| s.write "lib/rack.rb", "raise 'omg'" end @@ -403,14 +413,14 @@ RSpec.describe "bundle install with gem sources" do simulate_new_machine - simulate_platform "ruby" do - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "platform_specific" - G - run "require 'platform_specific' ; puts PLATFORM_SPECIFIC" - expect(out).to eq("1.0.0 RUBY") - end + bundle "config set --local force_ruby_platform true" + + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "platform_specific" + G + run "require 'platform_specific' ; puts PLATFORM_SPECIFIC" + expect(out).to eq("1.0.0 RUBY") end it "does not update the cache if --no-cache is passed" do diff --git a/spec/bundler/commands/check_spec.rb b/spec/bundler/commands/check_spec.rb index c48220f8df..02f9bb5b7a 100644 --- a/spec/bundler/commands/check_spec.rb +++ b/spec/bundler/commands/check_spec.rb @@ -17,7 +17,7 @@ RSpec.describe "bundle check" do gem "rails" G - bundle "check --gemfile bundled_app/Gemfile", :dir => tmp + bundle "check --gemfile bundled_app/Gemfile", dir: tmp expect(out).to include("The Gemfile's dependencies are satisfied") end @@ -55,7 +55,7 @@ RSpec.describe "bundle check" do gem "rails" G - bundle :check, :raise_on_error => false + bundle :check, raise_on_error: false expect(err).to include("Bundler can't satisfy your Gemfile's dependencies.") end @@ -65,7 +65,7 @@ RSpec.describe "bundle check" do gem "rails" G - bundle :check, :raise_on_error => false + bundle :check, raise_on_error: false expect(exitstatus).to be > 0 expect(err).to include("Bundler can't satisfy your Gemfile's dependencies.") end @@ -88,11 +88,11 @@ RSpec.describe "bundle check" do gem "rails_pinned_to_old_activesupport" G - bundle :check, :raise_on_error => false + bundle :check, raise_on_error: false expect(err).to include("Bundler can't satisfy your Gemfile's dependencies.") end - it "remembers --without option from install", :bundler => "< 3" do + it "remembers --without option from install", bundler: "< 3" do gemfile <<-G source "#{file_uri_for(gem_repo1)}" group :foo do @@ -132,7 +132,23 @@ RSpec.describe "bundle check" do gem "rack" G - bundle "check", :raise_on_error => false + bundle "check", raise_on_error: false + expect(err).to include("* rack (1.0.0)") + expect(exitstatus).to eq(1) + end + + it "ensures that gems are actually installed and not just cached in applications' cache" do + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "rack" + G + + bundle "config set --local path vendor/bundle" + bundle :cache + + gem_command "uninstall rack", env: { "GEM_HOME" => vendored_gems.to_s } + + bundle "check", raise_on_error: false expect(err).to include("* rack (1.0.0)") expect(exitstatus).to eq(1) end @@ -146,7 +162,7 @@ RSpec.describe "bundle check" do end G - system_gems "rack-1.0.0", :path => default_bundle_path + system_gems "rack-1.0.0", path: default_bundle_path lockfile <<-G GEM @@ -156,7 +172,7 @@ RSpec.describe "bundle check" do rack (1.0.0) PLATFORMS - #{local} + #{generic_local_platform} #{not_local} DEPENDENCIES @@ -177,7 +193,7 @@ RSpec.describe "bundle check" do end G - system_gems "rack-1.0.0", :path => default_bundle_path + system_gems "rack-1.0.0", path: default_bundle_path lockfile <<-G GEM @@ -187,7 +203,7 @@ RSpec.describe "bundle check" do rack (1.0.0) PLATFORMS - #{local} + #{generic_local_platform} #{not_local} DEPENDENCIES @@ -200,13 +216,13 @@ RSpec.describe "bundle check" do end it "outputs an error when the default Gemfile is not found" do - bundle :check, :raise_on_error => false + bundle :check, raise_on_error: false expect(exitstatus).to eq(10) expect(err).to include("Could not locate Gemfile") end it "does not output fatal error message" do - bundle :check, :raise_on_error => false + bundle :check, raise_on_error: false expect(exitstatus).to eq(10) expect(err).not_to include("Unfortunately, a fatal error has occurred. ") end @@ -221,11 +237,11 @@ RSpec.describe "bundle check" do bundle "install" FileUtils.rm(bundled_app_lock) - bundle :check, :raise_on_error => false + bundle :check, raise_on_error: false expect(last_command).to be_failure end - context "--path", :bundler => "< 3" do + context "--path", bundler: "< 3" do context "after installing gems in the proper directory" do before do gemfile <<-G @@ -255,7 +271,7 @@ RSpec.describe "bundle check" do gem "rails" G - bundle "check --path vendor/bundle", :raise_on_error => false + bundle "check --path vendor/bundle", raise_on_error: false end it "returns false" do @@ -282,7 +298,7 @@ RSpec.describe "bundle check" do it "shows what is missing with the current Gemfile if it is not satisfied" do simulate_new_machine - bundle :check, :raise_on_error => false + bundle :check, raise_on_error: false expect(err).to match(/The following gems are missing/) expect(err).to include("* rack (1.0") end @@ -310,7 +326,43 @@ RSpec.describe "bundle check" do end it "shows what is missing with the current Gemfile without duplications" do - bundle :check, :raise_on_error => false + bundle :check, raise_on_error: false + expect(err).to match(/The following gems are missing/) + expect(err).to include("* rack (1.0").once + end + end + + describe "when locked under multiple platforms" do + before :each do + build_repo4 do + build_gem "rack" + end + + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "rack" + G + + lockfile <<-L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + rack (1.0) + + PLATFORMS + ruby + #{local_platform} + + DEPENDENCIES + rack + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "shows what is missing with the current Gemfile without duplications" do + bundle :check, raise_on_error: false expect(err).to match(/The following gems are missing/) expect(err).to include("* rack (1.0").once end @@ -327,7 +379,7 @@ RSpec.describe "bundle check" do end it "returns success when the Gemfile is satisfied" do - system_gems "rack-1.0.0", :path => default_bundle_path + system_gems "rack-1.0.0", path: default_bundle_path bundle :check expect(out).to include("The Gemfile's dependencies are satisfied") end @@ -352,8 +404,14 @@ RSpec.describe "bundle check" do end it "returns success when the Gemfile is satisfied and generates a correct lockfile" do - system_gems "depends_on_rack-1.0", "rack-1.0", :gem_repo => gem_repo4, :path => default_bundle_path + system_gems "depends_on_rack-1.0", "rack-1.0", gem_repo: gem_repo4, path: default_bundle_path bundle :check + + checksums = checksums_section_when_existing do |c| + c.no_checksum "depends_on_rack", "1.0" + c.no_checksum "rack", "1.0" + end + expect(out).to include("The Gemfile's dependencies are satisfied") expect(lockfile).to eq <<~L GEM @@ -372,7 +430,76 @@ RSpec.describe "bundle check" do DEPENDENCIES depends_on_rack! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "with gemspec directive and scoped sources" do + before do + build_repo4 do + build_gem "awesome_print" + end + + build_repo2 do + build_gem "dex-dispatch-engine" + end + + build_lib("bundle-check-issue", path: tmp.join("bundle-check-issue")) do |s| + s.write "Gemfile", <<-G + source "https://localgemserver.test" + + gemspec + + source "https://localgemserver.test/extra" do + gem "dex-dispatch-engine" + end + G + s.add_dependency "awesome_print" + end + + bundle "install", artifice: "compact_index_extra", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }, dir: tmp.join("bundle-check-issue") + end + + it "does not corrupt lockfile when changing version" do + version_file = tmp.join("bundle-check-issue/bundle-check-issue.gemspec") + File.write(version_file, File.read(version_file).gsub(/s\.version = .+/, "s.version = '9999'")) + + bundle "check --verbose", dir: tmp.join("bundle-check-issue") + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "awesome_print", "1.0" + c.no_checksum "bundle-check-issue", "9999" + c.checksum gem_repo2, "dex-dispatch-engine", "1.0" + end + + expect(File.read(tmp.join("bundle-check-issue/Gemfile.lock"))).to eq <<~L + PATH + remote: . + specs: + bundle-check-issue (9999) + awesome_print + + GEM + remote: https://localgemserver.test/ + specs: + awesome_print (1.0) + + GEM + remote: https://localgemserver.test/extra/ + specs: + dex-dispatch-engine (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + bundle-check-issue! + dex-dispatch-engine! + #{checksums} BUNDLED WITH #{Bundler::VERSION} L @@ -381,7 +508,7 @@ RSpec.describe "bundle check" do describe "BUNDLED WITH" do def lock_with(bundler_version = nil) - lock = <<-L + lock = <<~L GEM remote: #{file_uri_for(gem_repo1)}/ specs: @@ -395,7 +522,7 @@ RSpec.describe "bundle check" do L if bundler_version - lock += "\n BUNDLED WITH\n #{bundler_version}\n" + lock += "\nBUNDLED WITH\n #{bundler_version}\n" end lock @@ -414,16 +541,16 @@ RSpec.describe "bundle check" do it "does not change the lock" do lockfile lock_with(nil) bundle :check - lockfile_should_be lock_with(nil) + expect(lockfile).to eq lock_with(nil) end end context "is newer" do - it "does not change the lock but warns" do + it "does not change the lock and does not warn" do lockfile lock_with(Bundler::VERSION.succ) bundle :check - expect(err).to include("the running version of Bundler (#{Bundler::VERSION}) is older than the version that created the lockfile (#{Bundler::VERSION.succ})") - lockfile_should_be lock_with(Bundler::VERSION.succ) + expect(err).to be_empty + expect(lockfile).to eq lock_with(Bundler::VERSION.succ) end end @@ -432,7 +559,7 @@ RSpec.describe "bundle check" do system_gems "bundler-1.18.0" lockfile lock_with("1.18.0") bundle :check - lockfile_should_be lock_with("1.18.0") + expect(lockfile).to eq lock_with("1.18.0") end end end diff --git a/spec/bundler/commands/clean_spec.rb b/spec/bundler/commands/clean_spec.rb index 4c04853bd3..0b559a87c8 100644 --- a/spec/bundler/commands/clean_spec.rb +++ b/spec/bundler/commands/clean_spec.rb @@ -156,7 +156,7 @@ RSpec.describe "bundle clean" do end it "removes unused git gems" do - build_git "foo", :path => lib_path("foo") + build_git "foo", path: lib_path("foo") git_path = lib_path("foo") revision = revision_for(git_path) @@ -194,7 +194,7 @@ RSpec.describe "bundle clean" do end it "keeps used git gems even if installed to a symlinked location" do - build_git "foo", :path => lib_path("foo") + build_git "foo", path: lib_path("foo") git_path = lib_path("foo") revision = revision_for(git_path) @@ -208,7 +208,7 @@ RSpec.describe "bundle clean" do G FileUtils.mkdir_p(bundled_app("real-path")) - FileUtils.ln_sf(bundled_app("real-path"), bundled_app("symlink-path")) + File.symlink(bundled_app("real-path"), bundled_app("symlink-path")) bundle "config set path #{bundled_app("symlink-path")}" bundle "install" @@ -221,7 +221,7 @@ RSpec.describe "bundle clean" do end it "removes old git gems" do - build_git "foo-bar", :path => lib_path("foo-bar") + build_git "foo-bar", path: lib_path("foo-bar") revision = revision_for(lib_path("foo-bar")) gemfile <<-G @@ -236,10 +236,10 @@ RSpec.describe "bundle clean" do bundle "config set path vendor/bundle" bundle "install" - update_git "foo", :path => lib_path("foo-bar") + update_git "foo-bar", path: lib_path("foo-bar") revision2 = revision_for(lib_path("foo-bar")) - bundle "update", :all => true + bundle "update", all: true bundle :clean expect(out).to include("Removing foo-bar (#{revision[0..11]})") @@ -254,8 +254,8 @@ RSpec.describe "bundle clean" do end it "does not remove nested gems in a git repo" do - build_lib "activesupport", "3.0", :path => lib_path("rails/activesupport") - build_git "rails", "3.0", :path => lib_path("rails") do |s| + build_lib "activesupport", "3.0", path: lib_path("rails/activesupport") + build_git "rails", "3.0", path: lib_path("rails") do |s| s.add_dependency "activesupport", "= 3.0" end revision = revision_for(lib_path("rails")) @@ -274,7 +274,7 @@ RSpec.describe "bundle clean" do end it "does not remove git sources that are in without groups" do - build_git "foo", :path => lib_path("foo") + build_git "foo", path: lib_path("foo") git_path = lib_path("foo") revision = revision_for(git_path) @@ -326,7 +326,7 @@ RSpec.describe "bundle clean" do gem "rack", "1.0.0" G - bundle :clean, :raise_on_error => false + bundle :clean, raise_on_error: false expect(exitstatus).to eq(15) expect(err).to include("--force") @@ -383,7 +383,7 @@ RSpec.describe "bundle clean" do expect(out).to include("rack (1.0.0)").and include("thin (1.0)") end - it "--clean should override the bundle setting on install", :bundler => "< 3" do + it "--clean should override the bundle setting on install", bundler: "< 3" do gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -405,7 +405,7 @@ RSpec.describe "bundle clean" do should_not_have_gems "thin-1.0" end - it "--clean should override the bundle setting on update", :bundler => "< 3" do + it "--clean should override the bundle setting on update", bundler: "< 3" do build_repo2 gemfile <<-G @@ -421,13 +421,13 @@ RSpec.describe "bundle clean" do build_gem "foo", "1.0.1" end - bundle "update", :all => true + bundle "update", all: true should_have_gems "foo-1.0.1" should_not_have_gems "foo-1.0" end - it "automatically cleans when path has not been set", :bundler => "3" do + it "automatically cleans when path has not been set", bundler: "3" do build_repo2 install_gemfile <<-G @@ -440,7 +440,7 @@ RSpec.describe "bundle clean" do build_gem "foo", "1.0.1" end - bundle "update", :all => true + bundle "update", all: true files = Pathname.glob(bundled_app(".bundle", Bundler.ruby_scope, "*", "*")) files.map! {|f| f.to_s.sub(bundled_app(".bundle", Bundler.ruby_scope).to_s, "") } @@ -486,7 +486,7 @@ RSpec.describe "bundle clean" do build_gem "foo", "1.0.1" end - bundle :update, :all => true + bundle :update, all: true should_have_gems "foo-1.0", "foo-1.0.1" end @@ -505,7 +505,7 @@ RSpec.describe "bundle clean" do update_repo2 do build_gem "foo", "1.0.1" end - bundle :update, :all => true + bundle :update, all: true gem_command :list expect(out).to include("foo (1.0.1, 1.0)") @@ -560,7 +560,7 @@ RSpec.describe "bundle clean" do FileUtils.chmod(0o500, system_cache_path) - bundle :clean, :force => true, :raise_on_error => false + bundle :clean, force: true, raise_on_error: false expect(err).to include(system_gem_path.to_s) expect(err).to include("grant write permissions") @@ -625,28 +625,22 @@ RSpec.describe "bundle clean" do expect(out).to eq("1.0") end - it "when using --force, it doesn't remove default gem binaries" do - skip "does not work on ruby 3.0 because it changes the path to look for default gems, tsort is a default gem there, and we can't install it either like we do with fiddle because it doesn't yet exist" unless RUBY_VERSION < "3.0.0" - - skip "does not work on rubygems versions where `--install_dir` doesn't respect --default" unless Gem::Installer.for_spec(loaded_gemspec, :install_dir => "/foo").default_spec_file == "/foo/specifications/default/bundler-#{Bundler::VERSION}.gemspec" # Since rubygems 3.2.0.rc.2 - - default_irb_version = ruby "gem 'irb', '< 999999'; require 'irb'; puts IRB::VERSION", :raise_on_error => false + it "when using --force, it doesn't remove default gem binaries", :realworld do + default_irb_version = ruby "gem 'irb', '< 999999'; require 'irb'; puts IRB::VERSION", raise_on_error: false skip "irb isn't a default gem" if default_irb_version.empty? - build_repo2 do - # simulate executable for default gem - build_gem "irb", default_irb_version, :to_system => true, :default => true do |s| - s.executables = "irb" - end + # simulate executable for default gem + build_gem "irb", default_irb_version, to_system: true, default: true do |s| + s.executables = "irb" end - realworld_system_gems "fiddle --version 1.0.0" + realworld_system_gems "tsort --version 0.1.0", "pathname --version 0.1.0", "set --version 1.0.1" install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" G - bundle "clean --force", :env => { "BUNDLER_GEM_DEFAULT_DIR" => system_gem_path.to_s } + bundle "clean --force", env: { "BUNDLER_GEM_DEFAULT_DIR" => system_gem_path.to_s } expect(out).not_to include("Removing irb") end @@ -789,7 +783,7 @@ RSpec.describe "bundle clean" do should_not_have_gems "foo-1.0" end - it "doesn't remove extensions artifacts from bundled git gems after clean", :ruby_repo do + it "doesn't remove extensions artifacts from bundled git gems after clean" do build_git "very_simple_git_binary", &:add_c_extension revision = revision_for(lib_path("very_simple_git_binary-1.0")) @@ -812,7 +806,7 @@ RSpec.describe "bundle clean" do expect(vendored_gems("bundler/gems/very_simple_git_binary-1.0-#{revision[0..11]}")).to exist end - it "removes extension directories", :ruby_repo do + it "removes extension directories" do gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -848,7 +842,7 @@ RSpec.describe "bundle clean" do expect(simple_binary_extensions_dir).to exist end - it "removes git extension directories", :ruby_repo do + it "removes git extension directories" do build_git "very_simple_git_binary", &:add_c_extension revision = revision_for(lib_path("very_simple_git_binary-1.0")) @@ -907,7 +901,7 @@ RSpec.describe "bundle clean" do bundle :lock bundle "config set without development" bundle "config set path vendor/bundle" - bundle "install" + bundle "install", verbose: true bundle :clean very_simple_binary_extensions_dir = diff --git a/spec/bundler/commands/config_spec.rb b/spec/bundler/commands/config_spec.rb index 1ef84c8195..547fd2d869 100644 --- a/spec/bundler/commands/config_spec.rb +++ b/spec/bundler/commands/config_spec.rb @@ -28,7 +28,7 @@ RSpec.describe ".bundle/config" do context "with env overwrite" do it "prints config with env" do - bundle "config list --parseable", :env => { "BUNDLE_FOO" => "bar3" } + bundle "config list --parseable", env: { "BUNDLE_FOO" => "bar3" } expect(out).to include("foo=bar3") end end @@ -43,6 +43,12 @@ RSpec.describe ".bundle/config" do G end + it "is local by default" do + bundle "config set foo bar" + expect(bundled_app(".bundle/config")).to exist + expect(home(".bundle/config")).not_to exist + end + it "can be moved with an environment variable" do ENV["BUNDLE_APP_CONFIG"] = tmp("foo/bar").to_s bundle "config set --local path vendor/bundle" @@ -58,15 +64,21 @@ RSpec.describe ".bundle/config" do ENV["BUNDLE_APP_CONFIG"] = "../foo" bundle "config set --local path vendor/bundle" - bundle "install", :dir => bundled_app("omg") + bundle "install", dir: bundled_app("omg") expect(bundled_app(".bundle")).not_to exist expect(bundled_app("../foo/config")).to exist - expect(the_bundle).to include_gems "rack 1.0.0", :dir => bundled_app("omg") + expect(the_bundle).to include_gems "rack 1.0.0", dir: bundled_app("omg") end end describe "location without a gemfile" do + it "is global by default" do + bundle "config set foo bar" + expect(bundled_app(".bundle/config")).not_to exist + expect(home(".bundle/config")).to exist + end + it "works with an absolute path" do ENV["BUNDLE_APP_CONFIG"] = tmp("foo/bar").to_s bundle "config set --local path vendor/bundle" @@ -76,6 +88,30 @@ RSpec.describe ".bundle/config" do end end + describe "config location" do + let(:bundle_user_config) { File.join(Dir.home, ".config/bundler") } + + before do + Dir.mkdir File.dirname(bundle_user_config) + end + + it "can be configured through BUNDLE_USER_CONFIG" do + bundle "config set path vendor", env: { "BUNDLE_USER_CONFIG" => bundle_user_config } + bundle "config get path", env: { "BUNDLE_USER_CONFIG" => bundle_user_config } + expect(out).to include("Set for the current user (#{bundle_user_config}): \"vendor\"") + end + + context "when not explicitly configured, but BUNDLE_USER_HOME set" do + let(:bundle_user_home) { bundled_app(".bundle").to_s } + + it "uses the right location" do + bundle "config set path vendor", env: { "BUNDLE_USER_HOME" => bundle_user_home } + bundle "config get path", env: { "BUNDLE_USER_HOME" => bundle_user_home } + expect(out).to include("Set for the current user (#{bundle_user_home}/config): \"vendor\"") + end + end + end + describe "global" do before(:each) do install_gemfile <<-G @@ -107,17 +143,15 @@ RSpec.describe ".bundle/config" do end it "has lower precedence than env" do - begin - ENV["BUNDLE_FOO"] = "env" + ENV["BUNDLE_FOO"] = "env" - bundle "config set --global foo global" - expect(out).to match(/You have a bundler environment variable for foo set to "env"/) + bundle "config set --global foo global" + expect(out).to match(/You have a bundler environment variable for foo set to "env"/) - run "puts Bundler.settings[:foo]" - expect(out).to eq("env") - ensure - ENV.delete("BUNDLE_FOO") - end + run "puts Bundler.settings[:foo]" + expect(out).to eq("env") + ensure + ENV.delete("BUNDLE_FOO") end it "can be deleted" do @@ -185,15 +219,13 @@ RSpec.describe ".bundle/config" do end it "has higher precedence than env" do - begin - ENV["BUNDLE_FOO"] = "env" - bundle "config set --local foo local" - - run "puts Bundler.settings[:foo]" - expect(out).to eq("local") - ensure - ENV.delete("BUNDLE_FOO") - end + ENV["BUNDLE_FOO"] = "env" + bundle "config set --local foo local" + + run "puts Bundler.settings[:foo]" + expect(out).to eq("local") + ensure + ENV.delete("BUNDLE_FOO") end it "can be deleted" do @@ -335,7 +367,7 @@ E it "doesn't return quotes around values" do bundle "config set foo '1'" - run "puts Bundler.settings.send(:global_config_file).read" + run "puts Bundler.settings.send(:local_config_file).read" expect(out).to include('"1"') run "puts Bundler.settings[:foo]" expect(out).to eq("1") @@ -399,21 +431,57 @@ E end end + describe "commented out settings with urls" do + before do + bundle "config set #mirror.https://rails-assets.org http://localhost:9292" + end + + it "does not make bundler crash and ignores the configuration" do + bundle "config list --parseable" + + expect(out).to eq("#mirror.https://rails-assets.org/=http://localhost:9292") + expect(err).to be_empty + + ruby(<<~RUBY) + require "bundler" + print Bundler.settings.mirror_for("https://rails-assets.org") + RUBY + expect(out).to eq("https://rails-assets.org/") + expect(err).to be_empty + + bundle "config set mirror.all http://localhost:9293" + ruby(<<~RUBY) + require "bundler" + print Bundler.settings.mirror_for("https://rails-assets.org") + RUBY + expect(out).to eq("http://localhost:9293/") + expect(err).to be_empty + end + end + describe "subcommands" do it "list" do - bundle "config list" - expect(out).to eq "Settings are listed in order of priority. The top value will be used.\nspec_run\nSet via BUNDLE_SPEC_RUN: \"true\"" + bundle "config list", env: { "BUNDLE_FOO" => "bar" } + expect(out).to eq "Settings are listed in order of priority. The top value will be used.\nfoo\nSet via BUNDLE_FOO: \"bar\"" - bundle "config list", :parseable => true - expect(out).to eq "spec_run=true" + bundle "config list", env: { "BUNDLE_FOO" => "bar" }, parseable: true + expect(out).to eq "foo=bar" end it "list with credentials" do - bundle "config list", :env => { "BUNDLE_GEMS__MYSERVER__COM" => "user:password" } - expect(out).to eq "Settings are listed in order of priority. The top value will be used.\ngems.myserver.com\nSet via BUNDLE_GEMS__MYSERVER__COM: \"user:[REDACTED]\"\n\nspec_run\nSet via BUNDLE_SPEC_RUN: \"true\"" + bundle "config list", env: { "BUNDLE_GEMS__MYSERVER__COM" => "user:password" } + expect(out).to eq "Settings are listed in order of priority. The top value will be used.\ngems.myserver.com\nSet via BUNDLE_GEMS__MYSERVER__COM: \"user:[REDACTED]\"" + + bundle "config list", parseable: true, env: { "BUNDLE_GEMS__MYSERVER__COM" => "user:password" } + expect(out).to eq "gems.myserver.com=user:password" + end + + it "list with API token credentials" do + bundle "config list", env: { "BUNDLE_GEMS__MYSERVER__COM" => "api_token:x-oauth-basic" } + expect(out).to eq "Settings are listed in order of priority. The top value will be used.\ngems.myserver.com\nSet via BUNDLE_GEMS__MYSERVER__COM: \"[REDACTED]:x-oauth-basic\"" - bundle "config list", :parseable => true, :env => { "BUNDLE_GEMS__MYSERVER__COM" => "user:password" } - expect(out).to eq "gems.myserver.com=user:password\nspec_run=true" + bundle "config list", parseable: true, env: { "BUNDLE_GEMS__MYSERVER__COM" => "api_token:x-oauth-basic" } + expect(out).to eq "gems.myserver.com=api_token:x-oauth-basic" end it "get" do @@ -447,7 +515,7 @@ E bundle "config set --local foo 4.1" expect(out).to eq "You are replacing the current local value of foo, which is currently \"4\"" - bundle "config set --global --local foo 5", :raise_on_error => false + bundle "config set --global --local foo 5", raise_on_error: false expect(last_command).to be_failure expect(err).to eq "The options global and local were specified. Please only use one of the switches at a time." end @@ -487,7 +555,7 @@ E expect(out).to eq "" expect(bundle("config get foo")).to eq "Settings for `foo` in order of priority. The top value will be used\nYou have not configured a value for `foo`" - bundle "config unset foo --local --global", :raise_on_error => false + bundle "config unset foo --local --global", raise_on_error: false expect(last_command).to be_failure expect(err).to eq "The options global and local were specified. Please only use one of the switches at a time." end diff --git a/spec/bundler/commands/console_spec.rb b/spec/bundler/commands/console_spec.rb index aa76096e3d..a41432b88a 100644 --- a/spec/bundler/commands/console_spec.rb +++ b/spec/bundler/commands/console_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe "bundle console", :bundler => "< 3", :readline => true do +RSpec.describe "bundle console", bundler: "< 3", readline: true do before :each do build_repo2 do # A minimal fake pry console diff --git a/spec/bundler/commands/doctor_spec.rb b/spec/bundler/commands/doctor_spec.rb index a59ccc540a..666b23a141 100644 --- a/spec/bundler/commands/doctor_spec.rb +++ b/spec/bundler/commands/doctor_spec.rb @@ -38,6 +38,11 @@ RSpec.describe "bundle doctor" do allow(stat).to receive(:uid) { Process.uid } allow(File).to receive(:writable?).with(unwritable_file) { true } allow(File).to receive(:readable?).with(unwritable_file) { true } + + # The following lines are for `Gem::PathSupport#initialize`. + allow(File).to receive(:exist?).with(Gem.default_dir) + allow(File).to receive(:writable?).with(Gem.default_dir) + allow(File).to receive(:writable?).with(File.expand_path("..", Gem.default_dir)) end it "exits with no message if the installed gem has no C extensions" do @@ -49,8 +54,8 @@ RSpec.describe "bundle doctor" do doctor = Bundler::CLI::Doctor.new({}) expect(doctor).to receive(:bundles_for_gem).exactly(2).times.and_return ["/path/to/rack/rack.bundle"] expect(doctor).to receive(:dylibs).exactly(2).times.and_return ["/usr/lib/libSystem.dylib"] - allow(File).to receive(:exist?).with("/usr/lib/libSystem.dylib").and_return(true) - expect { doctor.run }.not_to(raise_error, @stdout.string) + allow(Fiddle).to receive(:dlopen).with("/usr/lib/libSystem.dylib").and_return(true) + expect { doctor.run }.not_to raise_error expect(@stdout.string).to be_empty end @@ -58,8 +63,8 @@ RSpec.describe "bundle doctor" do doctor = Bundler::CLI::Doctor.new({}) expect(doctor).to receive(:bundles_for_gem).exactly(2).times.and_return ["/path/to/rack/rack.bundle"] expect(doctor).to receive(:dylibs).exactly(2).times.and_return ["/usr/local/opt/icu4c/lib/libicui18n.57.1.dylib"] - allow(File).to receive(:exist?).with("/usr/local/opt/icu4c/lib/libicui18n.57.1.dylib").and_return(false) - expect { doctor.run }.to raise_error(Bundler::ProductionError, strip_whitespace(<<-E).strip), @stdout.string + allow(Fiddle).to receive(:dlopen).with("/usr/local/opt/icu4c/lib/libicui18n.57.1.dylib").and_raise(Fiddle::DLError) + expect { doctor.run }.to raise_error(Bundler::ProductionError, <<~E.strip), @stdout.string The following gems are missing OS dependencies: * bundler: /usr/local/opt/icu4c/lib/libicui18n.57.1.dylib * rack: /usr/local/opt/icu4c/lib/libicui18n.57.1.dylib @@ -79,7 +84,7 @@ RSpec.describe "bundle doctor" do it "exits with an error if home contains files that are not readable/writable" do expect { Bundler::CLI::Doctor.new({}).run }.not_to raise_error expect(@stdout.string).to include( - "Broken links exist in the Bundler home. Please report them to the offending gem's upstream repo. These files are:\n - #{@unwritable_file}" + "Broken links exist in the Bundler home. Please report them to the offending gem's upstream repo. These files are:\n - #{@broken_symlink}" ) expect(@stdout.string).not_to include("No issues") end @@ -133,4 +138,14 @@ RSpec.describe "bundle doctor" do end end end + + context "when home contains filenames with special characters" do + it "escape filename before command execute" do + doctor = Bundler::CLI::Doctor.new({}) + expect(doctor).to receive(:`).with("/usr/bin/otool -L \\$\\(date\\)\\ \\\"\\'\\\\.bundle").and_return("dummy string") + doctor.dylibs_darwin('$(date) "\'\.bundle') + expect(doctor).to receive(:`).with("/usr/bin/ldd \\$\\(date\\)\\ \\\"\\'\\\\.bundle").and_return("dummy string") + doctor.dylibs_ldd('$(date) "\'\.bundle') + end + end end diff --git a/spec/bundler/commands/exec_spec.rb b/spec/bundler/commands/exec_spec.rb index 2c83ec8e11..d59b690d2f 100644 --- a/spec/bundler/commands/exec_spec.rb +++ b/spec/bundler/commands/exec_spec.rb @@ -2,11 +2,10 @@ RSpec.describe "bundle exec" do let(:system_gems_to_install) { %w[rack-1.0.0 rack-0.9.1] } - before :each do - system_gems(system_gems_to_install, :path => default_bundle_path) - end it "works with --gemfile flag" do + system_gems(system_gems_to_install, path: default_bundle_path) + create_file "CustomGemfile", <<-G source "#{file_uri_for(gem_repo1)}" gem "rack", "1.0.0" @@ -17,6 +16,8 @@ RSpec.describe "bundle exec" do end it "activates the correct gem" do + system_gems(system_gems_to_install, path: default_bundle_path) + gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack", "0.9.1" @@ -27,12 +28,14 @@ RSpec.describe "bundle exec" do end it "works and prints no warnings when HOME is not writable" do + system_gems(system_gems_to_install, path: default_bundle_path) + gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack", "0.9.1" G - bundle "exec rackup", :env => { "HOME" => "/" } + bundle "exec rackup", env: { "HOME" => "/" } expect(out).to eq("0.9.1") expect(err).to be_empty end @@ -105,7 +108,7 @@ RSpec.describe "bundle exec" do 2.1.4 L - bundle "exec bundle cache", :env => { "BUNDLER_VERSION" => Bundler::VERSION } + bundle "exec bundle cache", env: { "BUNDLER_VERSION" => Bundler::VERSION } expect(out).to include("Updating files in vendor/cache") end @@ -195,7 +198,7 @@ RSpec.describe "bundle exec" do gem "rack", "0.9.1" G - install_gemfile bundled_app2("Gemfile"), <<-G, :dir => bundled_app2 + install_gemfile bundled_app2("Gemfile"), <<-G, dir: bundled_app2 source "#{file_uri_for(gem_repo2)}" gem "rack_two", "1.0.0" G @@ -204,14 +207,12 @@ RSpec.describe "bundle exec" do expect(out).to eq("0.9.1") - bundle "exec rackup", :dir => bundled_app2 + bundle "exec rackup", dir: bundled_app2 expect(out).to eq("1.0.0") end context "with default gems" do - let(:system_gems_to_install) { [] } - - let(:default_irb_version) { ruby "gem 'irb', '< 999999'; require 'irb'; puts IRB::VERSION", :raise_on_error => false } + let(:default_irb_version) { ruby "gem 'irb', '< 999999'; require 'irb'; puts IRB::VERSION", raise_on_error: false } context "when not specified in Gemfile" do before do @@ -291,14 +292,14 @@ RSpec.describe "bundle exec" do end end - bundle "config set path.system true" + bundle "config set --global path.system true" install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack", "0.9.1" G - install_gemfile bundled_app2("Gemfile"), <<-G, :dir => bundled_app2 + install_gemfile bundled_app2("Gemfile"), <<-G, dir: bundled_app2 source "#{file_uri_for(gem_repo2)}" gem "rack_two", "1.0.0" G @@ -343,7 +344,7 @@ RSpec.describe "bundle exec" do bundle "exec 'echo $RUBYOPT'" expect(out.split(" ").count(bundler_setup_opt)).to eq(1) - bundle "exec 'echo $RUBYOPT'", :env => { "RUBYOPT" => rubyopt } + bundle "exec 'echo $RUBYOPT'", env: { "RUBYOPT" => rubyopt } expect(out.split(" ").count(bundler_setup_opt)).to eq(1) end @@ -362,7 +363,7 @@ RSpec.describe "bundle exec" do bundle "exec 'echo $RUBYLIB'" expect(out).to include(rubylib) - bundle "exec 'echo $RUBYLIB'", :env => { "RUBYLIB" => rubylib } + bundle "exec 'echo $RUBYLIB'", env: { "RUBYLIB" => rubylib } expect(out).to include(rubylib) end @@ -372,7 +373,7 @@ RSpec.describe "bundle exec" do gem "rack" G - bundle "exec foobarbaz", :raise_on_error => false + bundle "exec foobarbaz", raise_on_error: false expect(exitstatus).to eq(127) expect(err).to include("bundler: command not found: foobarbaz") expect(err).to include("Install missing gem executables with `bundle install`") @@ -385,7 +386,7 @@ RSpec.describe "bundle exec" do G bundle "exec touch foo" - bundle "exec ./foo", :raise_on_error => false + bundle "exec ./foo", raise_on_error: false expect(exitstatus).to eq(126) expect(err).to include("bundler: not executable: ./foo") end @@ -396,12 +397,14 @@ RSpec.describe "bundle exec" do gem "rack" G - bundle "exec", :raise_on_error => false + bundle "exec", raise_on_error: false expect(exitstatus).to eq(128) expect(err).to include("bundler: exec needs a command to run") end it "raises a helpful error when exec'ing to something outside of the bundle" do + system_gems(system_gems_to_install, path: default_bundle_path) + bundle "config set clean false" # want to keep the rackup binstub install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -409,7 +412,7 @@ RSpec.describe "bundle exec" do G [true, false].each do |l| bundle "config set disable_exec_load #{l}" - bundle "exec rackup", :raise_on_error => false + bundle "exec rackup", raise_on_error: false expect(err).to include "can't find executable rackup for gem rack. rack is not currently included in the bundle, perhaps you meant to add it to your Gemfile?" end end @@ -528,7 +531,7 @@ RSpec.describe "bundle exec" do describe "from gems bundled via :path" do before(:each) do - build_lib "fizz", :path => home("fizz") do |s| + build_lib "fizz", path: home("fizz") do |s| s.executables = "fizz" end @@ -577,7 +580,7 @@ RSpec.describe "bundle exec" do describe "from gems bundled via :git with no gemspec" do before(:each) do - build_git "fizz_no_gemspec", :gemspec => false do |s| + build_git "fizz_no_gemspec", gemspec: false do |s| s.executables = "fizz_no_gemspec" end @@ -612,6 +615,53 @@ RSpec.describe "bundle exec" do expect(out).to include("Installing foo 1.0") end + it "performs an automatic bundle install with git gems" do + build_git "foo" do |s| + s.executables = "foo" + end + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "rack", "0.9.1" + gem "foo", :git => "#{lib_path("foo-1.0")}" + G + + bundle "config set auto_install 1" + bundle "exec foo" + expect(out).to include("Fetching rack 0.9.1") + expect(out).to include("Fetching #{lib_path("foo-1.0")}") + expect(out.lines).to end_with("1.0") + end + + it "loads the correct optparse when `auto_install` is set, and optparse is a dependency" do + if Gem.rubygems_version < Gem::Version.new("3.3.0.a") + skip "optparse is a default gem, and rubygems loads it during install" + end + + build_repo4 do + build_gem "fastlane", "2.192.0" do |s| + s.executables = "fastlane" + s.add_dependency "optparse", "~> 999.999.999" + end + + build_gem "optparse", "999.999.998" + build_gem "optparse", "999.999.999" + end + + system_gems "optparse-999.999.998", gem_repo: gem_repo4 + + bundle "config set auto_install 1" + bundle "config set --local path vendor/bundle" + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + gem "fastlane" + G + + bundle "exec fastlane" + expect(out).to include("Installing optparse 999.999.999") + expect(out).to include("2.192.0") + end + describe "with gems bundled via :path with invalid gemspecs" do it "outputs the gemspec validation errors" do build_lib "foo" @@ -633,7 +683,7 @@ RSpec.describe "bundle exec" do gem "foo", :path => "#{lib_path("foo-1.0")}" G - bundle "exec irb", :raise_on_error => false + bundle "exec irb", raise_on_error: false expect(err).to match("The gemspec at #{lib_path("foo-1.0").join("foo.gemspec")} is not valid") expect(err).to match('"TODO" is not a summary') @@ -656,7 +706,7 @@ RSpec.describe "bundle exec" do G bundle "config set path.system true" bundle "install" - bundle "exec ruby -e '`bundle -v`; puts $?.success?'", :env => { "BUNDLER_VERSION" => Bundler::VERSION } + bundle "exec ruby -e '`bundle -v`; puts $?.success?'", env: { "BUNDLER_VERSION" => Bundler::VERSION } expect(out).to match("true") end end @@ -664,7 +714,7 @@ RSpec.describe "bundle exec" do context "`load`ing a ruby file instead of `exec`ing" do let(:path) { bundled_app("ruby_executable") } let(:shebang) { "#!/usr/bin/env ruby" } - let(:executable) { <<-RUBY.gsub(/^ */, "").strip } + let(:executable) { <<~RUBY.strip } #{shebang} require "rack" @@ -676,6 +726,8 @@ RSpec.describe "bundle exec" do RUBY before do + system_gems(system_gems_to_install, path: default_bundle_path) + bundled_app(path).open("w") {|f| f << executable } bundled_app(path).chmod(0o755) @@ -697,7 +749,7 @@ RSpec.describe "bundle exec" do let(:expected) { [exec, args, rack, process].join("\n") } let(:expected_err) { "" } - subject { bundle "exec #{path} arg1 arg2", :raise_on_error => false } + subject { bundle "exec #{path} arg1 arg2", raise_on_error: false } it "runs" do skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? @@ -745,9 +797,7 @@ RSpec.describe "bundle exec" do end let(:expected_err) { "" } let(:exit_code) do - # signal mask 128 + plus signal 15 -> TERM - # this is specified by C99 - 128 + 15 + exit_status_for_signal(Signal.list["TERM"]) end it "runs" do @@ -781,8 +831,7 @@ RSpec.describe "bundle exec" do let(:executable) { super() << "\nraise 'ERROR'" } let(:exit_code) { 1 } let(:expected_err) do - "bundler: failed to load command: #{path} (#{path})" \ - "\n#{path}:10:in `<top (required)>': ERROR (RuntimeError)" + /\Abundler: failed to load command: #{Regexp.quote(path.to_s)} \(#{Regexp.quote(path.to_s)}\)\n#{Regexp.quote(path.to_s)}:10:in [`']<top \(required\)>': ERROR \(RuntimeError\)/ end it "runs like a normally executed executable" do @@ -790,7 +839,7 @@ RSpec.describe "bundle exec" do subject expect(exitstatus).to eq(exit_code) - expect(err).to start_with(expected_err) + expect(err).to match(expected_err) expect(out).to eq(expected) end end @@ -824,7 +873,7 @@ RSpec.describe "bundle exec" do end end - context "when Bundler.setup fails", :bundler => "< 3" do + context "when Bundler.setup fails", bundler: "< 3" do before do gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -836,9 +885,12 @@ RSpec.describe "bundle exec" do let(:exit_code) { Bundler::GemNotFound.new.status_code } let(:expected) { "" } let(:expected_err) { <<-EOS.strip } -\e[31mCould not find gem 'rack (= 2)' in rubygems repository #{file_uri_for(gem_repo1)}/ or installed locally. -The source contains the following versions of 'rack': 0.9.1, 1.0.0\e[0m -\e[33mRun `bundle install` to install missing gems.\e[0m +Could not find gem 'rack (= 2)' in cached gems or installed locally. + +The source contains the following gems matching 'rack': + * rack-0.9.1 + * rack-1.0.0 +Run `bundle install` to install missing gems. EOS it "runs" do @@ -851,7 +903,7 @@ The source contains the following versions of 'rack': 0.9.1, 1.0.0\e[0m end end - context "when Bundler.setup fails", :bundler => "3" do + context "when Bundler.setup fails", bundler: "3" do before do gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -863,9 +915,11 @@ The source contains the following versions of 'rack': 0.9.1, 1.0.0\e[0m let(:exit_code) { Bundler::GemNotFound.new.status_code } let(:expected) { "" } let(:expected_err) { <<-EOS.strip } -\e[31mCould not find gem 'rack (= 2)' in rubygems repository #{file_uri_for(gem_repo1)}/ or installed locally. -The source contains the following versions of 'rack': 1.0.0\e[0m -\e[33mRun `bundle install` to install missing gems.\e[0m +Could not find gem 'rack (= 2)' in cached gems or installed locally. + +The source contains the following gems matching 'rack': + * rack-1.0.0 +Run `bundle install` to install missing gems. EOS it "runs" do @@ -878,6 +932,30 @@ The source contains the following versions of 'rack': 1.0.0\e[0m end end + context "when Bundler.setup fails and Gemfile is not the default" do + before do + create_file "CustomGemfile", <<-G + source "#{file_uri_for(gem_repo1)}" + gem 'rack', '2' + G + ENV["BUNDLER_FORCE_TTY"] = "true" + ENV["BUNDLE_GEMFILE"] = "CustomGemfile" + ENV["BUNDLER_ORIG_BUNDLE_GEMFILE"] = nil + end + + let(:exit_code) { Bundler::GemNotFound.new.status_code } + let(:expected) { "" } + + it "prints proper suggestion" do + skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + + subject + expect(exitstatus).to eq(exit_code) + expect(err).to include("Run `bundle install --gemfile CustomGemfile` to install missing gems.") + expect(out).to eq(expected) + end + end + context "when the executable exits non-zero via at_exit" do let(:executable) { super() + "\n\nat_exit { $! ? raise($!) : exit(1) }" } let(:exit_code) { 1 } @@ -962,7 +1040,7 @@ __FILE__: #{path.to_s.inspect} end context "signals being trapped by bundler" do - let(:executable) { strip_whitespace <<-RUBY } + let(:executable) { <<~RUBY } #{shebang} begin Thread.new do @@ -989,7 +1067,7 @@ __FILE__: #{path.to_s.inspect} end context "signals not being trapped by bunder" do - let(:executable) { strip_whitespace <<-RUBY } + let(:executable) { <<~RUBY } #{shebang} signals = #{test_signals.inspect} @@ -1034,7 +1112,7 @@ __FILE__: #{path.to_s.inspect} puts `bundle exec echo foo` RUBY file.chmod(0o777) - bundle "exec #{file}", :env => { "PATH" => path } + bundle "exec #{file}", env: { "PATH" => path } expect(out).to eq("foo") end end @@ -1127,7 +1205,7 @@ __FILE__: #{path.to_s.inspect} context "with a system gem that shadows a default gem" do let(:openssl_version) { "99.9.9" } - let(:expected) { ruby "gem 'openssl', '< 999999'; require 'openssl'; puts OpenSSL::VERSION", :artifice => nil, :raise_on_error => false } + let(:expected) { ruby "gem 'openssl', '< 999999'; require 'openssl'; puts OpenSSL::VERSION", artifice: nil, raise_on_error: false } it "only leaves the default gem in the stdlib available" do skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? @@ -1143,7 +1221,7 @@ __FILE__: #{path.to_s.inspect} end end - system_gems("openssl-#{openssl_version}", :gem_repo => gem_repo4) + system_gems("openssl-#{openssl_version}", gem_repo: gem_repo4) file = bundled_app("require_openssl.rb") create_file(file, <<-RUBY) @@ -1156,15 +1234,15 @@ __FILE__: #{path.to_s.inspect} env = { "PATH" => path } aggregate_failures do - expect(bundle("exec #{file}", :artifice => nil, :env => env)).to eq(expected) - expect(bundle("exec bundle exec #{file}", :artifice => nil, :env => env)).to eq(expected) - expect(bundle("exec ruby #{file}", :artifice => nil, :env => env)).to eq(expected) - expect(run(file.read, :artifice => nil, :env => env)).to eq(expected) + expect(bundle("exec #{file}", artifice: nil, env: env)).to eq(expected) + expect(bundle("exec bundle exec #{file}", artifice: nil, env: env)).to eq(expected) + expect(bundle("exec ruby #{file}", artifice: nil, env: env)).to eq(expected) + expect(run(file.read, artifice: nil, env: env)).to eq(expected) end skip "ruby_core has openssl and rubygems in the same folder, and this test needs rubygems require but default openssl not in a directly added entry in $LOAD_PATH" if ruby_core? # sanity check that we get the newer, custom version without bundler - sys_exec "#{Gem.ruby} #{file}", :env => env, :raise_on_error => false + sys_exec "#{Gem.ruby} #{file}", env: env, raise_on_error: false expect(err).to include("custom openssl should not be loaded") end end diff --git a/spec/bundler/commands/fund_spec.rb b/spec/bundler/commands/fund_spec.rb index 5a0c5411da..5415b88eeb 100644 --- a/spec/bundler/commands/fund_spec.rb +++ b/spec/bundler/commands/fund_spec.rb @@ -5,20 +5,20 @@ RSpec.describe "bundle fund" do build_repo2 do build_gem "has_funding_and_other_metadata" do |s| s.metadata = { - "bug_tracker_uri" => "https://example.com/user/bestgemever/issues", - "changelog_uri" => "https://example.com/user/bestgemever/CHANGELOG.md", + "bug_tracker_uri" => "https://example.com/user/bestgemever/issues", + "changelog_uri" => "https://example.com/user/bestgemever/CHANGELOG.md", "documentation_uri" => "https://www.example.info/gems/bestgemever/0.0.1", - "homepage_uri" => "https://bestgemever.example.io", - "mailing_list_uri" => "https://groups.example.com/bestgemever", - "funding_uri" => "https://example.com/has_funding_and_other_metadata/funding", - "source_code_uri" => "https://example.com/user/bestgemever", - "wiki_uri" => "https://example.com/user/bestgemever/wiki", + "homepage_uri" => "https://bestgemever.example.io", + "mailing_list_uri" => "https://groups.example.com/bestgemever", + "funding_uri" => "https://example.com/has_funding_and_other_metadata/funding", + "source_code_uri" => "https://example.com/user/bestgemever", + "wiki_uri" => "https://example.com/user/bestgemever/wiki", } end build_gem "has_funding", "1.2.3" do |s| s.metadata = { - "funding_uri" => "https://example.com/has_funding/funding", + "funding_uri" => "https://example.com/has_funding/funding", } end diff --git a/spec/bundler/commands/help_spec.rb b/spec/bundler/commands/help_spec.rb index f72763900e..0c7031e813 100644 --- a/spec/bundler/commands/help_spec.rb +++ b/spec/bundler/commands/help_spec.rb @@ -15,6 +15,13 @@ RSpec.describe "bundle help" do expect(out).to eq(%(["#{man_dir}/bundle-install.1"])) end + it "prexifes bundle commands with bundle- and resolves aliases when finding the man files" do + with_fake_man do + bundle "help package" + end + expect(out).to eq(%(["#{man_dir}/bundle-cache.1"])) + end + it "simply outputs the human readable file when there is no man on the path" do with_path_as("") do bundle "help install" @@ -23,8 +30,8 @@ RSpec.describe "bundle help" do end it "still outputs the old help for commands that do not have man pages yet" do - bundle "help version" - expect(out).to include("Prints the bundler's version information") + bundle "help fund" + expect(out).to include("Lists information about gems seeking funding assistance") end it "looks for a binary and executes it with --help option if it's named bundler-<task>" do @@ -71,7 +78,7 @@ RSpec.describe "bundle help" do it "has helpful output when using --help flag for a non-existent command" do with_fake_man do - bundle "instill -h", :raise_on_error => false + bundle "instill -h", raise_on_error: false end expect(err).to include('Could not find command "instill".') end diff --git a/spec/bundler/commands/info_spec.rb b/spec/bundler/commands/info_spec.rb index 906349cacf..a5a09bc147 100644 --- a/spec/bundler/commands/info_spec.rb +++ b/spec/bundler/commands/info_spec.rb @@ -6,13 +6,13 @@ RSpec.describe "bundle info" do build_repo2 do build_gem "has_metadata" do |s| s.metadata = { - "bug_tracker_uri" => "https://example.com/user/bestgemever/issues", - "changelog_uri" => "https://example.com/user/bestgemever/CHANGELOG.md", + "bug_tracker_uri" => "https://example.com/user/bestgemever/issues", + "changelog_uri" => "https://example.com/user/bestgemever/CHANGELOG.md", "documentation_uri" => "https://www.example.info/gems/bestgemever/0.0.1", - "homepage_uri" => "https://bestgemever.example.io", - "mailing_list_uri" => "https://groups.example.com/bestgemever", - "source_code_uri" => "https://example.com/user/bestgemever", - "wiki_uri" => "https://example.com/user/bestgemever/wiki", + "homepage_uri" => "https://bestgemever.example.io", + "mailing_list_uri" => "https://groups.example.com/bestgemever", + "source_code_uri" => "https://example.com/user/bestgemever", + "wiki_uri" => "https://example.com/user/bestgemever/wiki", } end end @@ -21,6 +21,7 @@ RSpec.describe "bundle info" do source "#{file_uri_for(gem_repo2)}" gem "rails" gem "has_metadata" + gem "thin" G end @@ -50,8 +51,21 @@ RSpec.describe "bundle info" do expect(out).to eq(root.to_s) end + it "prints gem version if exists in bundle" do + bundle "info rails --version" + expect(out).to eq("2.3.2") + end + + it "doesn't claim that bundler has been deleted, even if using a custom path without bundler there" do + bundle "config set --local path vendor/bundle" + bundle "install" + bundle "info bundler" + expect(out).to include("\tPath: #{root}") + expect(err).not_to match(/The gem bundler has been deleted/i) + end + it "complains if gem not in bundle" do - bundle "info missing", :raise_on_error => false + bundle "info missing", raise_on_error: false expect(err).to eq("Could not find gem 'missing'.") end @@ -60,8 +74,16 @@ RSpec.describe "bundle info" do bundle "info rails --path" - expect(err).to match(/has been deleted/i) - expect(err).to match(default_bundle_path("gems", "rails-2.3.2").to_s) + expect(err).to include("The gem rails has been deleted.") + expect(err).to include(default_bundle_path("gems", "rails-2.3.2").to_s) + + bundle "info rail --path" + expect(err).to include("The gem rails has been deleted.") + expect(err).to include(default_bundle_path("gems", "rails-2.3.2").to_s) + + bundle "info rails" + expect(err).to include("The gem rails has been deleted.") + expect(err).to include(default_bundle_path("gems", "rails-2.3.2").to_s) end context "given a default gem shippped in ruby", :ruby_repo do @@ -102,6 +124,30 @@ RSpec.describe "bundle info" do expect(out).to_not include("Homepage:") end end + + context "when gem has a reverse dependency on any version" do + it "prints the details" do + bundle "info rack" + + expect(out).to include("Reverse Dependencies: \n\t\tthin (1.0) depends on rack (>= 0)") + end + end + + context "when gem has a reverse dependency on a specific version" do + it "prints the details" do + bundle "info actionpack" + + expect(out).to include("Reverse Dependencies: \n\t\trails (2.3.2) depends on actionpack (= 2.3.2)") + end + end + + context "when gem has no reverse dependencies" do + it "excludes the reverse dependencies field from the output" do + bundle "info rails" + + expect(out).not_to include("Reverse Dependencies:") + end + end end context "with a git repo in the Gemfile" do @@ -117,11 +163,11 @@ RSpec.describe "bundle info" do expect(the_bundle).to include_gems "foo 1.0" bundle "info foo" - expect(out).to include("foo (1.0 #{@git.ref_for("master", 6)}") + expect(out).to include("foo (1.0 #{@git.ref_for("main", 6)}") end - it "prints out branch names other than master" do - update_git "foo", :branch => "omg" do |s| + it "prints out branch names other than main" do + update_git "foo", branch: "omg" do |s| s.write "lib/foo.rb", "FOO = '1.0.omg'" end @revision = revision_for(lib_path("foo-1.0"))[0...6] @@ -148,7 +194,7 @@ RSpec.describe "bundle info" do end it "handles when a version is a '-' prerelease" do - @git = build_git("foo", "1.0.0-beta.1", :path => lib_path("foo")) + @git = build_git("foo", "1.0.0-beta.1", path: lib_path("foo")) install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "foo", "1.0.0-beta.1", :git => "#{lib_path("foo")}" @@ -169,7 +215,7 @@ RSpec.describe "bundle info" do G bundle "info rac" - expect(out).to match(/\A1 : rack\n2 : rack-obama\n0 : - exit -(\n>)?\z/) + expect(out).to match(/\A1 : rack\n2 : rack-obama\n0 : - exit -(\n>.*)?\z/) end end @@ -182,7 +228,7 @@ RSpec.describe "bundle info" do invalid_regexp = "[]" - bundle "info #{invalid_regexp}", :raise_on_error => false + bundle "info #{invalid_regexp}", raise_on_error: false expect(err).to include("Could not find gem '#{invalid_regexp}'.") end end @@ -196,7 +242,7 @@ RSpec.describe "bundle info" do gem "rails", group: :test G - bundle "info rails", :raise_on_error => false + bundle "info rails", raise_on_error: false expect(err).to include("Could not find gem 'rails', because it's in the group 'test', configured to be ignored.") end end diff --git a/spec/bundler/commands/init_spec.rb b/spec/bundler/commands/init_spec.rb index 683a453c7d..0a1336572a 100644 --- a/spec/bundler/commands/init_spec.rb +++ b/spec/bundler/commands/init_spec.rb @@ -7,6 +7,29 @@ RSpec.describe "bundle init" do expect(bundled_app_gemfile).to be_file end + context "with a template with permission flags not matching current process umask" do + let(:template_file) do + gemfile = Bundler.preferred_gemfile_name + templates_dir.join(gemfile) + end + + let(:target_dir) { bundled_app("init_permissions_test") } + + around do |example| + old_chmod = File.stat(template_file).mode + FileUtils.chmod(old_chmod | 0o111, template_file) # chmod +x + example.run + FileUtils.chmod(old_chmod, template_file) + end + + it "honours the current process umask when generating from a template" do + FileUtils.mkdir(target_dir) + bundle :init, dir: target_dir + generated_mode = File.stat(File.join(target_dir, "Gemfile")).mode & 0o111 + expect(generated_mode).to be_zero + end + end + context "when a Gemfile already exists" do before do create_file "Gemfile", <<-G @@ -15,11 +38,11 @@ RSpec.describe "bundle init" do end it "does not change existing Gemfiles" do - expect { bundle :init, :raise_on_error => false }.not_to change { File.read(bundled_app_gemfile) } + expect { bundle :init, raise_on_error: false }.not_to change { File.read(bundled_app_gemfile) } end it "notifies the user that an existing Gemfile already exists" do - bundle :init, :raise_on_error => false + bundle :init, raise_on_error: false expect(err).to include("Gemfile already exists") end end @@ -32,7 +55,7 @@ RSpec.describe "bundle init" do FileUtils.mkdir bundled_app(subdir) - bundle :init, :dir => bundled_app(subdir) + bundle :init, dir: bundled_app(subdir) expect(out).to include("Writing new Gemfile") expect(bundled_app("#{subdir}/Gemfile")).to be_file @@ -42,13 +65,13 @@ RSpec.describe "bundle init" do context "when the dir is not writable by the current user" do let(:subdir) { "child_dir" } - it "notifies the user that it can not write to it" do + it "notifies the user that it cannot write to it" do FileUtils.mkdir bundled_app(subdir) # chmod a-w it mode = File.stat(bundled_app(subdir)).mode ^ 0o222 FileUtils.chmod mode, bundled_app(subdir) - bundle :init, :dir => bundled_app(subdir), :raise_on_error => false + bundle :init, dir: bundled_app(subdir), raise_on_error: false expect(err).to include("directory is not writable") expect(Dir[bundled_app("#{subdir}/*")]).to be_empty @@ -69,7 +92,7 @@ RSpec.describe "bundle init" do S end - bundle :init, :gemspec => spec_file + bundle :init, gemspec: spec_file gemfile = bundled_app_gemfile.read expect(gemfile).to match(%r{source 'https://rubygems.org'}) @@ -89,7 +112,7 @@ RSpec.describe "bundle init" do S end - bundle :init, :gemspec => spec_file, :raise_on_error => false + bundle :init, gemspec: spec_file, raise_on_error: false expect(err).to include("There was an error while loading `test.gemspec`") end end @@ -112,11 +135,11 @@ RSpec.describe "bundle init" do end it "does not change existing Gemfiles" do - expect { bundle :init, :raise_on_error => false }.not_to change { File.read(bundled_app("gems.rb")) } + expect { bundle :init, raise_on_error: false }.not_to change { File.read(bundled_app("gems.rb")) } end it "notifies the user that an existing gems.rb already exists" do - bundle :init, :raise_on_error => false + bundle :init, raise_on_error: false expect(err).to include("gems.rb already exists") end end @@ -129,7 +152,7 @@ RSpec.describe "bundle init" do FileUtils.mkdir bundled_app(subdir) - bundle :init, :dir => bundled_app(subdir) + bundle :init, dir: bundled_app(subdir) expect(out).to include("Writing new gems.rb") expect(bundled_app("#{subdir}/gems.rb")).to be_file @@ -152,7 +175,7 @@ RSpec.describe "bundle init" do end it "should generate from an existing gemspec" do - bundle :init, :gemspec => spec_file + bundle :init, gemspec: spec_file gemfile = bundled_app("gems.rb").read expect(gemfile).to match(%r{source 'https://rubygems.org'}) @@ -162,10 +185,23 @@ RSpec.describe "bundle init" do end it "prints message to user" do - bundle :init, :gemspec => spec_file + bundle :init, gemspec: spec_file expect(out).to include("Writing new gems.rb") end end end + + describe "using the --gemfile" do + it "should use the --gemfile value to name the gemfile" do + custom_gemfile_name = "NiceGemfileName" + + bundle :init, gemfile: custom_gemfile_name + + expect(out).to include("Writing new #{custom_gemfile_name}") + used_template = File.read("#{source_root}/lib/bundler/templates/Gemfile") + generated_gemfile = bundled_app(custom_gemfile_name).read + expect(generated_gemfile).to eq(used_template) + end + end end diff --git a/spec/bundler/commands/inject_spec.rb b/spec/bundler/commands/inject_spec.rb index 2d97bf6ff0..255a03c135 100644 --- a/spec/bundler/commands/inject_spec.rb +++ b/spec/bundler/commands/inject_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe "bundle inject", :bundler => "< 3" do +RSpec.describe "bundle inject", bundler: "< 3" do before :each do gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -36,14 +36,14 @@ RSpec.describe "bundle inject", :bundler => "< 3" do context "with injected gems already in the Gemfile" do it "doesn't add existing gems" do - bundle "inject 'rack' '> 0'", :raise_on_error => false + bundle "inject 'rack' '> 0'", raise_on_error: false expect(err).to match(/cannot specify the same gem twice/i) end end context "incorrect arguments" do it "fails when more than 2 arguments are passed" do - bundle "inject gem_name 1 v", :raise_on_error => false + bundle "inject gem_name 1 v", raise_on_error: false expect(err).to eq(<<-E.strip) ERROR: "bundle inject" was called with arguments ["gem_name", "1", "v"] Usage: "bundle inject GEM VERSION" @@ -99,7 +99,7 @@ Usage: "bundle inject GEM VERSION" it "restores frozen afterwards" do bundle "inject 'rack-obama' '> 0'" - config = YAML.load(bundled_app(".bundle/config").read) + config = Psych.load(bundled_app(".bundle/config").read) expect(config["BUNDLE_DEPLOYMENT"] || config["BUNDLE_FROZEN"]).to eq("true") end @@ -108,8 +108,8 @@ Usage: "bundle inject GEM VERSION" source "#{file_uri_for(gem_repo1)}" gem "rack-obama" G - bundle "inject 'rack' '> 0'", :raise_on_error => false - expect(err).to match(/trying to install in deployment mode after changing/) + bundle "inject 'rack' '> 0'", raise_on_error: false + expect(err).to match(/the lockfile can't be updated because frozen mode is set/) expect(bundled_app_lock.read).not_to match(/rack-obama/) end diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb index 412000341f..f0c9aaea8e 100644 --- a/spec/bundler/commands/install_spec.rb +++ b/spec/bundler/commands/install_spec.rb @@ -12,7 +12,7 @@ RSpec.describe "bundle install with gem sources" do end it "does not make a lockfile if the install fails" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false raise StandardError, "FAIL" G @@ -29,7 +29,7 @@ RSpec.describe "bundle install with gem sources" do expect(bundled_app_lock).to exist end - it "does not create ./.bundle by default", :bundler => "< 3" do + it "does not create ./.bundle by default", bundler: "< 3" do gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" @@ -45,7 +45,7 @@ RSpec.describe "bundle install with gem sources" do gem "rack" G - bundle :install, :env => { "BUNDLE_PATH__SYSTEM" => "true" } # can't use install_gemfile since it sets retry + bundle :install, env: { "BUNDLE_PATH__SYSTEM" => "true" } # can't use install_gemfile since it sets retry expect(bundled_app(".bundle")).not_to exist end @@ -68,7 +68,7 @@ RSpec.describe "bundle install with gem sources" do lockfile = File.read(bundled_app_lock) - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false raise StandardError, "FAIL" G @@ -94,6 +94,21 @@ RSpec.describe "bundle install with gem sources" do expect(the_bundle).to include_gems("rack 1.0.0") end + it "auto-heals missing gems" do + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem 'rack' + G + + FileUtils.rm_rf(default_bundle_path("gems/rack-1.0.0")) + + bundle "install --verbose" + + expect(out).to include("Installing rack 1.0.0") + expect(default_bundle_path("gems/rack-1.0.0")).to exist + expect(the_bundle).to include_gems("rack 1.0.0") + end + it "fetches gems when multiple versions are specified" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -115,7 +130,7 @@ RSpec.describe "bundle install with gem sources" do end it "raises an appropriate error when gems are specified using symbols" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem :rack G @@ -182,7 +197,7 @@ RSpec.describe "bundle install with gem sources" do end it "does not reinstall any gem that is already available locally" do - system_gems "activesupport-2.3.2", :path => default_bundle_path + system_gems "activesupport-2.3.2", path: default_bundle_path build_repo2 do build_gem "activesupport", "2.3.2" do |s| @@ -199,7 +214,7 @@ RSpec.describe "bundle install with gem sources" do end it "works when the gemfile specifies gems that only exist in the system" do - build_gem "foo", :to_bundle => true + build_gem "foo", to_bundle: true install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" @@ -210,7 +225,7 @@ RSpec.describe "bundle install with gem sources" do end it "prioritizes local gems over remote gems" do - build_gem "rack", "1.0.0", :to_bundle => true do |s| + build_gem "rack", "1.0.0", to_bundle: true do |s| s.add_dependency "activesupport", "2.3.5" end @@ -226,7 +241,7 @@ RSpec.describe "bundle install with gem sources" do plugin_msg = "hello from an env plugin!" create_file "plugins/rubygems_plugin.rb", "puts '#{plugin_msg}'" rubylib = ENV["RUBYLIB"].to_s.split(File::PATH_SEPARATOR).unshift(bundled_app("plugins").to_s).join(File::PATH_SEPARATOR) - install_gemfile <<-G, :env => { "RUBYLIB" => rubylib } + install_gemfile <<-G, env: { "RUBYLIB" => rubylib } source "#{file_uri_for(gem_repo1)}" gem "rack" G @@ -270,7 +285,7 @@ RSpec.describe "bundle install with gem sources" do end it "installs gems for windows" do - simulate_platform mswin + simulate_platform x86_mswin32 install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -278,7 +293,7 @@ RSpec.describe "bundle install with gem sources" do G run "require 'platform_specific' ; puts PLATFORM_SPECIFIC" - expect(out).to eq("1.0.0 MSWIN") + expect(out).to eq("1.0 x86-mswin32") end end @@ -296,14 +311,14 @@ RSpec.describe "bundle install with gem sources" do expect(the_bundle).to include_gems "rack 1.0" end - it "allows running bundle install --system without deleting foo", :bundler => "< 3" do + it "allows running bundle install --system without deleting foo", bundler: "< 3" do bundle "install --path vendor" bundle "install --system" FileUtils.rm_rf(bundled_app("vendor")) expect(the_bundle).to include_gems "rack 1.0" end - it "allows running bundle install --system after deleting foo", :bundler => "< 3" do + it "allows running bundle install --system after deleting foo", bundler: "< 3" do bundle "install --path vendor" FileUtils.rm_rf(bundled_app("vendor")) bundle "install --system" @@ -311,7 +326,7 @@ RSpec.describe "bundle install with gem sources" do end end - it "finds gems in multiple sources", :bundler => "< 3" do + it "finds gems in multiple sources", bundler: "< 3" do build_repo2 do build_gem "rack", "1.2" do |s| s.executables = "rackup" @@ -330,13 +345,13 @@ RSpec.describe "bundle install with gem sources" do end it "gives a useful error if no sources are set" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false gem "rack" G expect(err).to include("This Gemfile does not include an explicit global source. " \ "Not using an explicit global source may result in a different lockfile being generated depending on " \ - "the gems you have installed locally before bundler is run." \ + "the gems you have installed locally before bundler is run. " \ "Instead, define a global source in your Gemfile like this: source \"https://rubygems.org\".") end @@ -349,7 +364,9 @@ RSpec.describe "bundle install with gem sources" do end it "throws a warning if a gem is added twice in Gemfile without version requirements" do - install_gemfile <<-G, :raise_on_error => false + build_repo2 + + install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "rack" gem "rack" @@ -361,7 +378,9 @@ RSpec.describe "bundle install with gem sources" do end it "throws a warning if a gem is added twice in Gemfile with same versions" do - install_gemfile <<-G, :raise_on_error => false + build_repo2 + + install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "rack", "1.0" gem "rack", "1.0" @@ -372,8 +391,24 @@ RSpec.describe "bundle install with gem sources" do expect(err).to include("While it's not a problem now, it could cause errors if you change the version of one of them later.") end + it "throws a warning if a gem is added twice under different platforms and does not crash when using the generated lockfile" do + build_repo2 + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "rack", :platform => :jruby + gem "rack" + G + + bundle "install" + + expect(err).to include("Your Gemfile lists the gem rack (>= 0) more than once.") + expect(err).to include("Remove any duplicate entries and specify the gem only once.") + expect(err).to include("While it's not a problem now, it could cause errors if you change the version of one of them later.") + end + it "does not throw a warning if a gem is added once in Gemfile and also inside a gemspec as a development dependency" do - build_lib "my-gem", :path => bundled_app do |s| + build_lib "my-gem", path: bundled_app do |s| s.add_development_dependency "my-private-gem" end @@ -395,8 +430,155 @@ RSpec.describe "bundle install with gem sources" do expect(the_bundle).to include_gems("my-private-gem 1.0") end + it "throws a warning if a gem is added once in Gemfile and also inside a gemspec as a development dependency, with different requirements" do + build_lib "my-gem", path: bundled_app do |s| + s.add_development_dependency "rubocop", "~> 1.36.0" + end + + build_repo4 do + build_gem "rubocop", "1.36.0" + build_gem "rubocop", "1.37.1" + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gemspec + + gem "rubocop", group: :development + G + + bundle :install + + expect(err).to include("A gemspec development dependency (rubocop, ~> 1.36.0) is being overridden by a Gemfile dependency (rubocop, >= 0).") + expect(err).to include("This behaviour may change in the future. Please remove either of them, or make sure they both have the same requirement") + + # This is not the best behavior I believe, it would be better if both + # requirements are considered if they are compatible, and a version + # satisfying both is chosen. But not sure about changing it right now, so + # I went with a warning for the time being. + expect(the_bundle).to include_gems("rubocop 1.37.1") + end + + it "includes the gem without warning if two gemspecs add it with the same requirement" do + gem1 = tmp.join("my-gem-1") + gem2 = tmp.join("my-gem-2") + + build_lib "my-gem", path: gem1 do |s| + s.add_development_dependency "rubocop", "~> 1.36.0" + end + + build_lib "my-gem-2", path: gem2 do |s| + s.add_development_dependency "rubocop", "~> 1.36.0" + end + + build_repo4 do + build_gem "rubocop", "1.36.0" + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gemspec path: "#{gem1}" + gemspec path: "#{gem2}" + G + + bundle :install + + expect(err).to be_empty + expect(the_bundle).to include_gems("rubocop 1.36.0") + end + + it "warns when a Gemfile dependency is overriding a gemspec development dependency, with different requirements" do + build_lib "my-gem", path: bundled_app do |s| + s.add_development_dependency "rails", ">= 5" + end + + build_repo4 do + build_gem "rails", "7.0.8" + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "rails", "~> 7.0.8" + + gemspec + G + + bundle :install + + expect(err).to include("A gemspec development dependency (rails, >= 5) is being overridden by a Gemfile dependency (rails, ~> 7.0.8).") + expect(err).to include("This behaviour may change in the future. Please remove either of them, or make sure they both have the same requirement") + + # This is not the best behavior I believe, it would be better if both + # requirements are considered if they are compatible, and a version + # satisfying both is chosen. But not sure about changing it right now, so + # I went with a warning for the time being. + expect(the_bundle).to include_gems("rails 7.0.8") + end + + it "does not warn if a gem is added once in Gemfile and also inside a gemspec as a development dependency, with same requirements, and different sources" do + build_lib "my-gem", path: bundled_app do |s| + s.add_development_dependency "activesupport" + end + + build_repo4 do + build_gem "activesupport" + end + + build_git "activesupport", "1.0", path: lib_path("activesupport") + + install_gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gemspec + + gem "activesupport", :git => "#{file_uri_for(lib_path("activesupport"))}" + G + + expect(err).to be_empty + expect(the_bundle).to include_gems "activesupport 1.0", source: "git@#{lib_path("activesupport")}" + + # if the Gemfile dependency is specified first + install_gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "activesupport", :git => "#{file_uri_for(lib_path("activesupport"))}" + + gemspec + G + + expect(err).to be_empty + expect(the_bundle).to include_gems "activesupport 1.0", source: "git@#{lib_path("activesupport")}" + end + + it "considers both dependencies for resolution if a gem is added once in Gemfile and also inside a local gemspec as a runtime dependency, with different requirements" do + build_lib "my-gem", path: bundled_app do |s| + s.add_dependency "rubocop", "~> 1.36.0" + end + + build_repo4 do + build_gem "rubocop", "1.36.0" + build_gem "rubocop", "1.37.1" + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gemspec + + gem "rubocop" + G + + bundle :install + + expect(err).to be_empty + expect(the_bundle).to include_gems("rubocop 1.36.0") + end + it "throws an error if a gem is added twice in Gemfile when version of one dependency is not specified" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}" gem "rack" gem "rack", "1.0" @@ -407,7 +589,7 @@ RSpec.describe "bundle install with gem sources" do end it "throws an error if a gem is added twice in Gemfile when different versions of both dependencies are specified" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}" gem "rack", "1.0" gem "rack", "1.1" @@ -420,7 +602,7 @@ RSpec.describe "bundle install with gem sources" do it "gracefully handles error when rubygems server is unavailable" do skip "networking issue" if Gem.win_platform? - install_gemfile <<-G, :artifice => nil, :raise_on_error => false + install_gemfile <<-G, artifice: nil, raise_on_error: false source "#{file_uri_for(gem_repo1)}" source "http://0.0.0.0:9384" do gem 'foo' @@ -432,8 +614,8 @@ RSpec.describe "bundle install with gem sources" do end it "fails gracefully when downloading an invalid specification from the full index" do - build_repo2 do - build_gem "ajp-rails", "0.0.0", :gemspec => false, :skip_validation => true do |s| + build_repo2(build_compact_index: false) do + build_gem "ajp-rails", "0.0.0", gemspec: false, skip_validation: true do |s| bad_deps = [["ruby-ajp", ">= 0.2.0"], ["rails", ">= 0.14"]] s. instance_variable_get(:@spec). @@ -444,7 +626,7 @@ RSpec.describe "bundle install with gem sources" do build_gem "ruby-ajp", "1.0.0" end - install_gemfile <<-G, :full_index => true, :raise_on_error => false + install_gemfile <<-G, full_index: true, raise_on_error: false source "#{file_uri_for(gem_repo2)}" gem "ajp-rails", "0.0.0" @@ -479,31 +661,27 @@ RSpec.describe "bundle install with gem sources" do end describe "Ruby version in Gemfile.lock" do - include Bundler::GemHelpers - context "and using an unsupported Ruby version" do it "prints an error" do - install_gemfile <<-G, :raise_on_error => false - ::RUBY_VERSION = '2.0.1' - ruby '~> 2.2' + install_gemfile <<-G, raise_on_error: false + ruby '~> 1.2' source "#{file_uri_for(gem_repo1)}" G - expect(err).to include("Your Ruby version is 2.0.1, but your Gemfile specified ~> 2.2") + expect(err).to include("Your Ruby version is #{Gem.ruby_version}, but your Gemfile specified ~> 1.2") end end context "and using a supported Ruby version" do before do install_gemfile <<-G - ::RUBY_VERSION = '2.1.3' - ::RUBY_PATCHLEVEL = 100 - ruby '~> 2.1.0' + ruby '~> #{Gem.ruby_version}' source "#{file_uri_for(gem_repo1)}" G end it "writes current Ruby version to Gemfile.lock" do - lockfile_should_be <<-L + checksums = checksums_section_when_existing + expect(lockfile).to eq <<~L GEM remote: #{file_uri_for(gem_repo1)}/ specs: @@ -512,24 +690,24 @@ RSpec.describe "bundle install with gem sources" do #{lockfile_platforms} DEPENDENCIES - + #{checksums} RUBY VERSION - ruby 2.1.3p100 + #{Bundler::RubyVersion.system} BUNDLED WITH #{Bundler::VERSION} L end - it "updates Gemfile.lock with updated incompatible ruby version" do + it "updates Gemfile.lock with updated yet still compatible ruby version" do install_gemfile <<-G - ::RUBY_VERSION = '2.2.3' - ::RUBY_PATCHLEVEL = 100 - ruby '~> 2.2.0' + ruby '~> #{current_ruby_minor}' source "#{file_uri_for(gem_repo1)}" G - lockfile_should_be <<-L + checksums = checksums_section_when_existing + + expect(lockfile).to eq <<~L GEM remote: #{file_uri_for(gem_repo1)}/ specs: @@ -538,9 +716,9 @@ RSpec.describe "bundle install with gem sources" do #{lockfile_platforms} DEPENDENCIES - + #{checksums} RUBY VERSION - ruby 2.2.3p100 + #{Bundler::RubyVersion.system} BUNDLED WITH #{Bundler::VERSION} @@ -575,7 +753,7 @@ RSpec.describe "bundle install with gem sources" do file.puts gemfile end - bundle :install, :dir => root_dir + bundle :install, dir: root_dir end it "doesn't blow up when using the `gemspec` DSL" do @@ -583,7 +761,7 @@ RSpec.describe "bundle install with gem sources" do FileUtils.mkdir_p(root_dir) - build_lib "foo", :path => root_dir + build_lib "foo", path: root_dir gemfile = <<-G source "#{file_uri_for(gem_repo1)}" gemspec @@ -592,7 +770,7 @@ RSpec.describe "bundle install with gem sources" do file.puts gemfile end - bundle :install, :dir => root_dir + bundle :install, dir: root_dir end end @@ -605,7 +783,7 @@ RSpec.describe "bundle install with gem sources" do gem 'rack' G - bundle :install, :quiet => true + bundle :install, quiet: true expect(out).to be_empty expect(err).to be_empty end @@ -635,7 +813,7 @@ RSpec.describe "bundle install with gem sources" do gem 'non-existing-gem' G - bundle :install, :quiet => true, :raise_on_error => false, :env => { "RUBYOPT" => "-r#{bundled_app("install_with_warning.rb")}" } + bundle :install, quiet: true, raise_on_error: false, env: { "RUBYOPT" => "-r#{bundled_app("install_with_warning.rb")}" } expect(out).to be_empty expect(err).to include("Could not find gem 'non-existing-gem'") expect(err).to include("BOOOOO") @@ -657,12 +835,174 @@ RSpec.describe "bundle install with gem sources" do FileUtils.chmod(0o500, bundle_path) bundle "config set --local path vendor" - bundle :install, :raise_on_error => false + bundle :install, raise_on_error: false expect(err).to include(bundle_path.to_s) expect(err).to include("grant write permissions") end end + describe "when bundle gems path does not have write access", :permissions do + let(:gems_path) { bundled_app("vendor/#{Bundler.ruby_scope}/gems") } + + before do + FileUtils.mkdir_p(gems_path) + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem 'rack' + G + end + + it "should display a proper message to explain the problem" do + FileUtils.chmod("-x", gems_path) + bundle "config set --local path vendor" + + begin + bundle :install, raise_on_error: false + ensure + FileUtils.chmod("+x", gems_path) + end + + expect(err).not_to include("ERROR REPORT TEMPLATE") + + expect(err).to include( + "There was an error while trying to create `#{gems_path.join("rack-1.0.0")}`. " \ + "It is likely that you need to grant executable permissions for all parent directories and write permissions for `#{gems_path}`." + ) + end + end + + describe "when bundle extensions path does not have write access", :permissions do + let(:extensions_path) { bundled_app("vendor/#{Bundler.ruby_scope}/extensions/#{Gem::Platform.local}/#{Gem.extension_api_version}") } + + before do + FileUtils.mkdir_p(extensions_path) + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem 'simple_binary' + G + end + + it "should display a proper message to explain the problem" do + FileUtils.chmod("-x", extensions_path) + bundle "config set --local path vendor" + + begin + bundle :install, raise_on_error: false + ensure + FileUtils.chmod("+x", extensions_path) + end + + expect(err).not_to include("ERROR REPORT TEMPLATE") + + expect(err).to include( + "There was an error while trying to create `#{extensions_path.join("simple_binary-1.0")}`. " \ + "It is likely that you need to grant executable permissions for all parent directories and write permissions for `#{extensions_path}`." + ) + end + end + + describe "when the path of a specific gem is not writable", :permissions do + let(:gems_path) { bundled_app("vendor/#{Bundler.ruby_scope}/gems") } + let(:foo_path) { gems_path.join("foo-1.0.0") } + + before do + build_repo4 do + build_gem "foo", "1.0.0" do |s| + s.write "CHANGELOG.md", "foo" + end + end + + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem 'foo' + G + end + + it "should display a proper message to explain the problem" do + bundle "config set --local path vendor" + bundle :install + expect(out).to include("Bundle complete!") + expect(err).to be_empty + + FileUtils.chmod("-x", foo_path) + + begin + bundle "install --redownload", raise_on_error: false + ensure + FileUtils.chmod("+x", foo_path) + end + + expect(err).not_to include("ERROR REPORT TEMPLATE") + expect(err).to include("Could not delete previous installation of `#{foo_path}`.") + expect(err).to include("The underlying error was Errno::EACCES") + end + end + + describe "when gem home does not have the writable bit set, yet it's still writable", :permissions do + let(:gem_home) { bundled_app("vendor/#{Bundler.ruby_scope}") } + + before do + build_repo4 do + build_gem "foo", "1.0.0" do |s| + s.write "CHANGELOG.md", "foo" + end + end + + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem 'foo' + G + end + + it "should display a proper message to explain the problem" do + bundle "config set --local path vendor" + bundle :install + expect(out).to include("Bundle complete!") + expect(err).to be_empty + + FileUtils.chmod("-w", gem_home) + + begin + bundle "install --redownload" + ensure + FileUtils.chmod("+w", gem_home) + end + + expect(out).to include("Bundle complete!") + expect(err).to be_empty + end + end + + describe "when gems path is world writable (no sticky bit set)", :permissions do + let(:gems_path) { bundled_app("vendor/#{Bundler.ruby_scope}/gems") } + + before do + build_repo4 do + build_gem "foo", "1.0.0" do |s| + s.write "CHANGELOG.md", "foo" + end + end + + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem 'foo' + G + end + + it "should display a proper message to explain the problem" do + bundle "config set --local path vendor" + bundle :install + expect(out).to include("Bundle complete!") + expect(err).to be_empty + + FileUtils.chmod(0o777, gems_path) + + bundle "install --redownload", raise_on_error: false + + expect(err).to include("The installation path is insecure. Bundler cannot continue.") + end + end + describe "when bundle cache path does not have write access", :permissions do let(:cache_path) { bundled_app("vendor/#{Bundler.ruby_scope}/cache") } @@ -678,7 +1018,7 @@ RSpec.describe "bundle install with gem sources" do FileUtils.chmod(0o500, cache_path) bundle "config set --local path vendor" - bundle :install, :raise_on_error => false + bundle :install, raise_on_error: false expect(err).to include(cache_path.to_s) expect(err).to include("grant write permissions") end @@ -691,13 +1031,13 @@ RSpec.describe "bundle install with gem sources" do gem "rack" G bundle "config set --local path bundle" - bundle "install", :standalone => true + bundle "install", standalone: true end it "includes the standalone path" do - bundle "binstubs rack", :standalone => true + bundle "binstubs rack", standalone: true standalone_line = File.read(bundled_app("bin/rackup")).each_line.find {|line| line.include? "$:.unshift" }.strip - expect(standalone_line).to eq %($:.unshift File.expand_path "../../bundle", path.realpath) + expect(standalone_line).to eq %($:.unshift File.expand_path "../bundle", __dir__) end end @@ -710,7 +1050,7 @@ RSpec.describe "bundle install with gem sources" do end it "should display a helpful message explaining how to fix it" do - bundle :install, :env => { "BUNDLE_RUBYGEMS__ORG" => "user:pass{word" }, :raise_on_error => false + bundle :install, env: { "BUNDLE_RUBYGEMS__ORG" => "user:pass{word" }, raise_on_error: false expect(exitstatus).to eq(17) expect(err).to eq("Please CGI escape your usernames and passwords before " \ "setting them for authentication.") @@ -751,12 +1091,297 @@ RSpec.describe "bundle install with gem sources" do end it "should fail loudly if the lockfile platforms don't include the current platform" do - simulate_platform(Gem::Platform.new("x86_64-linux")) { bundle "install", :raise_on_error => false } + simulate_platform(Gem::Platform.new("x86_64-linux")) { bundle "install", raise_on_error: false } expect(err).to eq( "Your bundle only supports platforms [\"x86_64-darwin-19\"] but your local platform is x86_64-linux. " \ - "Add the current platform to the lockfile with `bundle lock --add-platform x86_64-linux` and try again." + "Add the current platform to the lockfile with\n`bundle lock --add-platform x86_64-linux` and try again." ) end end + + context "with missing platform specific gems in lockfile" do + before do + build_repo4 do + build_gem "racca", "1.5.2" + + build_gem "nokogiri", "1.12.4" do |s| + s.platform = "x86_64-darwin" + s.add_runtime_dependency "racca", "~> 1.4" + end + + build_gem "nokogiri", "1.12.4" do |s| + s.platform = "x86_64-linux" + s.add_runtime_dependency "racca", "~> 1.4" + end + + build_gem "crass", "1.0.6" + + build_gem "loofah", "2.12.0" do |s| + s.add_runtime_dependency "crass", "~> 1.0.2" + s.add_runtime_dependency "nokogiri", ">= 1.5.9" + end + end + + gemfile <<-G + source "https://gem.repo4" + + ruby "#{Gem.ruby_version}" + + gem "loofah", "~> 2.12.0" + G + + checksums = checksums_section do |c| + c.checksum gem_repo4, "crass", "1.0.6" + c.checksum gem_repo4, "loofah", "2.12.0" + c.checksum gem_repo4, "nokogiri", "1.12.4", "x86_64-darwin" + c.checksum gem_repo4, "racca", "1.5.2" + end + + lockfile <<-L + GEM + remote: https://gem.repo4/ + specs: + crass (1.0.6) + loofah (2.12.0) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + nokogiri (1.12.4-x86_64-darwin) + racca (~> 1.4) + racca (1.5.2) + + PLATFORMS + x86_64-darwin-20 + x86_64-linux + + DEPENDENCIES + loofah (~> 2.12.0) + #{checksums} + RUBY VERSION + #{Bundler::RubyVersion.system} + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "automatically fixes the lockfile" do + bundle "config set --local path vendor/bundle" + + simulate_platform "x86_64-linux" do + bundle "install", artifice: "compact_index" + end + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "crass", "1.0.6" + c.checksum gem_repo4, "loofah", "2.12.0" + c.checksum gem_repo4, "nokogiri", "1.12.4", "x86_64-darwin" + c.checksum gem_repo4, "racca", "1.5.2" + c.checksum gem_repo4, "nokogiri", "1.12.4", "x86_64-linux" + end + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + crass (1.0.6) + loofah (2.12.0) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + nokogiri (1.12.4-x86_64-darwin) + racca (~> 1.4) + nokogiri (1.12.4-x86_64-linux) + racca (~> 1.4) + racca (1.5.2) + + PLATFORMS + x86_64-darwin-20 + x86_64-linux + + DEPENDENCIES + loofah (~> 2.12.0) + #{checksums} + RUBY VERSION + #{Bundler::RubyVersion.system} + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "with --local flag" do + before do + system_gems "rack-1.0.0", path: default_bundle_path + end + + it "respects installed gems without fetching any remote sources" do + install_gemfile <<-G, local: true + source "#{file_uri_for(gem_repo1)}" + + source "https://not-existing-source" do + gem "rack" + end + G + + expect(last_command).to be_success + end + end + + context "with only option" do + before do + bundle "config set only a:b" + end + + it "installs only gems of the specified groups" do + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "rails" + gem "rack", group: :a + gem "rake", group: :b + gem "yard", group: :c + G + + expect(out).to include("Installing rack") + expect(out).to include("Installing rake") + expect(out).not_to include("Installing yard") + end + end + + context "with --prefer-local flag" do + before do + build_repo4 do + build_gem "foo", "1.0.1" + build_gem "foo", "1.0.0" + build_gem "bar", "1.0.0" + end + + system_gems "foo-1.0.0", path: default_bundle_path, gem_repo: gem_repo4 + end + + it "fetches remote sources only when not available locally" do + install_gemfile <<-G, "prefer-local": true, verbose: true + source "#{file_uri_for(gem_repo4)}" + + gem "foo" + gem "bar" + G + + expect(out).to include("Using foo 1.0.0").and include("Fetching bar 1.0.0").and include("Installing bar 1.0.0") + expect(last_command).to be_success + end + end + + context "with a symlinked configured as bundle path and a gem with symlinks" do + before do + symlinked_bundled_app = tmp("bundled_app-symlink") + File.symlink(bundled_app, symlinked_bundled_app) + bundle "config path #{File.join(symlinked_bundled_app, ".vendor")}" + + binman_path = tmp("binman") + FileUtils.mkdir_p binman_path + + readme_path = File.join(binman_path, "README.markdown") + FileUtils.touch(readme_path) + + man_path = File.join(binman_path, "man", "man0") + FileUtils.mkdir_p man_path + + File.symlink("../../README.markdown", File.join(man_path, "README.markdown")) + + build_repo4 do + build_gem "binman", path: gem_repo4("gems"), lib_path: binman_path, no_default: true do |s| + s.files = ["README.markdown", "man/man0/README.markdown"] + end + end + end + + it "installs fine" do + install_gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "binman" + G + end + end + + context "when a gem has equivalent versions with inconsistent dependencies" do + before do + build_repo4 do + build_gem "autobuild", "1.10.rc2" do |s| + s.add_dependency "utilrb", ">= 1.6.0" + end + + build_gem "autobuild", "1.10.0.rc2" do |s| + s.add_dependency "utilrb", ">= 2.0" + end + end + end + + it "does not crash unexpectedly" do + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "autobuild", "1.10.rc2" + G + + bundle "install --jobs 1", raise_on_error: false + + expect(err).not_to include("ERROR REPORT TEMPLATE") + expect(err).to include("Could not find compatible versions") + end + end + + context "when a lockfile has unmet dependencies, and the Gemfile has no resolution" do + before do + build_repo4 do + build_gem "aaa", "0.2.0" do |s| + s.add_dependency "zzz", "< 0.2.0" + end + + build_gem "zzz", "0.2.0" + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "aaa" + gem "zzz" + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + aaa (0.2.0) + zzz (< 0.2.0) + zzz (0.2.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + aaa! + zzz! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "does not install, but raises a resolution error" do + bundle "install", raise_on_error: false + expect(err).to include("Could not find compatible versions") + end + end + + context "when --jobs option given" do + before do + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"", jobs: 1 + end + + it "does not save the flag to config" do + expect(bundled_app(".bundle/config")).not_to exist + end + end end diff --git a/spec/bundler/commands/list_spec.rb b/spec/bundler/commands/list_spec.rb index 66930ded75..5ac2077d81 100644 --- a/spec/bundler/commands/list_spec.rb +++ b/spec/bundler/commands/list_spec.rb @@ -3,7 +3,7 @@ RSpec.describe "bundle list" do context "with name-only and paths option" do it "raises an error" do - bundle "list --name-only --paths", :raise_on_error => false + bundle "list --name-only --paths", raise_on_error: false expect(err).to eq "The `--name-only` and `--paths` options cannot be used together" end @@ -11,7 +11,7 @@ RSpec.describe "bundle list" do context "with without-group and only-group option" do it "raises an error" do - bundle "list --without-group dev --only-group test", :raise_on_error => false + bundle "list --without-group dev --only-group test", raise_on_error: false expect(err).to eq "The `--only-group` and `--without-group` options cannot be used together" end @@ -40,7 +40,7 @@ RSpec.describe "bundle list" do context "when group is not found" do it "raises an error" do - bundle "list --without-group random", :raise_on_error => false + bundle "list --without-group random", raise_on_error: false expect(err).to eq "`random` group could not be found." end @@ -79,7 +79,7 @@ RSpec.describe "bundle list" do context "when group is not found" do it "raises an error" do - bundle "list --only-group random", :raise_on_error => false + bundle "list --only-group random", raise_on_error: false expect(err).to eq "`random` group could not be found." end @@ -124,9 +124,9 @@ RSpec.describe "bundle list" do build_gem "bar" end - build_git "git_test", "1.0.0", :path => lib_path("git_test") + build_git "git_test", "1.0.0", path: lib_path("git_test") - build_lib("gemspec_test", :path => tmp.join("gemspec_test")) do |s| + build_lib("gemspec_test", path: tmp.join("gemspec_test")) do |s| s.add_dependency "bar", "=1.0.0" end diff --git a/spec/bundler/commands/lock_spec.rb b/spec/bundler/commands/lock_spec.rb index 21eb6e5456..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(/installed locally/) + 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,9 +661,16 @@ 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 - lockfile_should_be <<-G + simulate_platform(x86_mingw32) { bundle :lock } + + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo4)}/ specs: @@ -332,14 +688,18 @@ 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" - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo4)}/ specs: @@ -360,7 +720,7 @@ RSpec.describe "bundle lock" do DEPENDENCIES gssapi mixlib-shellout - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G @@ -424,9 +784,14 @@ 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 - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo4)}/ specs: @@ -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 diff --git a/spec/bundler/commands/newgem_spec.rb b/spec/bundler/commands/newgem_spec.rb index 56fea008ec..199340b131 100644 --- a/spec/bundler/commands/newgem_spec.rb +++ b/spec/bundler/commands/newgem_spec.rb @@ -12,14 +12,14 @@ RSpec.describe "bundle gem" do def bundle_exec_rubocop prepare_gemspec(bundled_app(gem_name, "#{gem_name}.gemspec")) - bundle "config set path #{rubocop_gems}", :dir => bundled_app(gem_name) - bundle "exec rubocop --debug --config .rubocop.yml", :dir => bundled_app(gem_name) + bundle "config set path #{rubocop_gems}", dir: bundled_app(gem_name) + bundle "exec rubocop --debug --config .rubocop.yml", dir: bundled_app(gem_name) end def bundle_exec_standardrb prepare_gemspec(bundled_app(gem_name, "#{gem_name}.gemspec")) - bundle "config set path #{standard_gems}", :dir => bundled_app(gem_name) - bundle "exec standardrb --debug", :dir => bundled_app(gem_name) + bundle "config set path #{standard_gems}", dir: bundled_app(gem_name) + bundle "exec standardrb --debug", dir: bundled_app(gem_name) end let(:generated_gemspec) { Bundler.load_gemspec_uncached(bundled_app(gem_name).join("#{gem_name}.gemspec")) } @@ -28,6 +28,10 @@ RSpec.describe "bundle gem" do let(:require_path) { "mygem" } + let(:minitest_test_file_path) { "test/test_mygem.rb" } + + let(:minitest_test_class_name) { "class TestMygem < Minitest::Test" } + before do sys_exec("git config --global user.name 'Bundler User'") sys_exec("git config --global user.email user@example.com") @@ -35,36 +39,32 @@ RSpec.describe "bundle gem" do end describe "git repo initialization" do - shared_examples_for "a gem with an initial git repo" do - before do - bundle "gem #{gem_name} #{flags}" - end - - it "generates a gem skeleton with a .git folder", :readline do - gem_skeleton_assertions - expect(bundled_app("#{gem_name}/.git")).to exist - end + it "generates a gem skeleton with a .git folder", :readline do + bundle "gem #{gem_name}" + gem_skeleton_assertions + expect(bundled_app("#{gem_name}/.git")).to exist end - context "when using the default" do - it_behaves_like "a gem with an initial git repo" do - let(:flags) { "" } - end + it "generates a gem skeleton with a .git folder when passing --git", :readline do + bundle "gem #{gem_name} --git" + gem_skeleton_assertions + expect(bundled_app("#{gem_name}/.git")).to exist end - context "when explicitly passing --git" do - it_behaves_like "a gem with an initial git repo" do - let(:flags) { "--git" } - end + it "generates a gem skeleton without a .git folder when passing --no-git", :readline do + bundle "gem #{gem_name} --no-git" + gem_skeleton_assertions + expect(bundled_app("#{gem_name}/.git")).not_to exist end - context "when passing --no-git", :readline do + context "on a path with spaces" do before do - bundle "gem #{gem_name} --no-git" + Dir.mkdir(bundled_app("path with spaces")) end - it "generates a gem skeleton without a .git folder" do - gem_skeleton_assertions - expect(bundled_app("#{gem_name}/.git")).not_to exist + + it "properly initializes git repo", :readline do + bundle "gem #{gem_name}", dir: bundled_app("path with spaces") + expect(bundled_app("path with spaces/#{gem_name}/.git")).to exist end end end @@ -103,7 +103,7 @@ RSpec.describe "bundle gem" do expect(bundled_app("#{gem_name}/README.md").read).to match(%r{https://github\.com/bundleuser/#{gem_name}/blob/.*/CODE_OF_CONDUCT.md}) end - it "generates the README with a section for the Code of Conduct, respecting the configured git default branch", :git => ">= 2.28.0" do + it "generates the README with a section for the Code of Conduct, respecting the configured git default branch", git: ">= 2.28.0" do sys_exec("git config --global init.defaultBranch main") bundle "gem #{gem_name} --coc" @@ -148,7 +148,7 @@ RSpec.describe "bundle gem" do end shared_examples_for "--rubocop flag" do - context "is deprecated", :bundler => "< 3" do + context "is deprecated", bundler: "< 3" do before do bundle "gem #{gem_name} --rubocop" end @@ -179,7 +179,7 @@ RSpec.describe "bundle gem" do end shared_examples_for "--no-rubocop flag" do - context "is deprecated", :bundler => "< 3" do + context "is deprecated", bundler: "< 3" do define_negated_matcher :exclude, :include before do @@ -310,30 +310,30 @@ RSpec.describe "bundle gem" do expect(last_command).to be_success end - it "has no rubocop offenses when using --ext and --linter=rubocop flag", :readline do + it "has no rubocop offenses when using --ext=c and --linter=rubocop flag", :readline do skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? - bundle "gem #{gem_name} --ext --linter=rubocop" + bundle "gem #{gem_name} --ext=c --linter=rubocop" bundle_exec_rubocop expect(last_command).to be_success end - it "has no rubocop offenses when using --ext, --test=minitest, and --linter=rubocop flag", :readline do + it "has no rubocop offenses when using --ext=c, --test=minitest, and --linter=rubocop flag", :readline do skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? - bundle "gem #{gem_name} --ext --test=minitest --linter=rubocop" + bundle "gem #{gem_name} --ext=c --test=minitest --linter=rubocop" bundle_exec_rubocop expect(last_command).to be_success end - it "has no rubocop offenses when using --ext, --test=rspec, and --linter=rubocop flag", :readline do + it "has no rubocop offenses when using --ext=c, --test=rspec, and --linter=rubocop flag", :readline do skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? - bundle "gem #{gem_name} --ext --test=rspec --linter=rubocop" + bundle "gem #{gem_name} --ext=c --test=rspec --linter=rubocop" bundle_exec_rubocop expect(last_command).to be_success end - it "has no rubocop offenses when using --ext, --ext=test-unit, and --linter=rubocop flag", :readline do + it "has no rubocop offenses when using --ext=c, --test=test-unit, and --linter=rubocop flag", :readline do skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? - bundle "gem #{gem_name} --ext --test=test-unit --linter=rubocop" + bundle "gem #{gem_name} --ext=c --test=test-unit --linter=rubocop" bundle_exec_rubocop expect(last_command).to be_success end @@ -345,10 +345,45 @@ RSpec.describe "bundle gem" do expect(last_command).to be_success end + it "has no rubocop offenses when using --ext=rust and --linter=rubocop flag", :readline do + skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? + skip "RubyGems incompatible with Rust builder" if ::Gem::Version.new("3.3.11") > ::Gem.rubygems_version + + bundle "gem #{gem_name} --ext=rust --linter=rubocop" + bundle_exec_rubocop + expect(last_command).to be_success + end + + it "has no rubocop offenses when using --ext=rust, --test=minitest, and --linter=rubocop flag", :readline do + skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? + skip "RubyGems incompatible with Rust builder" if ::Gem::Version.new("3.3.11") > ::Gem.rubygems_version + + bundle "gem #{gem_name} --ext=rust --test=minitest --linter=rubocop" + bundle_exec_rubocop + expect(last_command).to be_success + end + + it "has no rubocop offenses when using --ext=rust, --test=rspec, and --linter=rubocop flag", :readline do + skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? + skip "RubyGems incompatible with Rust builder" if ::Gem::Version.new("3.3.11") > ::Gem.rubygems_version + + bundle "gem #{gem_name} --ext=rust --test=rspec --linter=rubocop" + bundle_exec_rubocop + expect(last_command).to be_success + end + + it "has no rubocop offenses when using --ext=rust, --test=test-unit, and --linter=rubocop flag", :readline do + skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? + skip "RubyGems incompatible with Rust builder" if ::Gem::Version.new("3.3.11") > ::Gem.rubygems_version + + bundle "gem #{gem_name} --ext=rust --test=test-unit --linter=rubocop" + bundle_exec_rubocop + expect(last_command).to be_success + end + shared_examples_for "CI config is absent" do it "does not create any CI files" do expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to_not exist - expect(bundled_app("#{gem_name}/.travis.yml")).to_not exist expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to_not exist expect(bundled_app("#{gem_name}/.circleci/config.yml")).to_not exist end @@ -400,7 +435,7 @@ RSpec.describe "bundle gem" do load_paths = [lib_dir, spec_dir] load_path_str = "-I#{load_paths.join(File::PATH_SEPARATOR)}" - sys_exec "#{Gem.ruby} #{load_path_str} #{bindir.join("bundle")} gem #{gem_name}", :env => { "PATH" => "" } + sys_exec "#{Gem.ruby} #{load_path_str} #{bindir.join("bundle")} gem #{gem_name}", env: { "PATH" => "" } end it "creates the gem without the need for git" do @@ -421,10 +456,10 @@ RSpec.describe "bundle gem" do prepare_gemspec(bundled_app("newgem", "newgem.gemspec")) - gems = ["rake-13.0.1"] - path = Bundler.feature_flag.default_install_uses_path? ? local_gem_path(:base => bundled_app("newgem")) : system_gem_path - system_gems gems, :path => path - bundle "exec rake build", :dir => bundled_app("newgem") + gems = ["rake-#{rake_version}"] + path = Bundler.feature_flag.default_install_uses_path? ? local_gem_path(base: bundled_app("newgem")) : system_gem_path + system_gems gems, path: path + bundle "exec rake build", dir: bundled_app("newgem") expect(last_command.stdboth).not_to include("ERROR") end @@ -433,7 +468,7 @@ RSpec.describe "bundle gem" do it "resolves ." do create_temporary_dir("tmp") - bundle "gem .", :dir => bundled_app("tmp") + bundle "gem .", dir: bundled_app("tmp") expect(bundled_app("tmp/lib/tmp.rb")).to exist end @@ -441,7 +476,7 @@ RSpec.describe "bundle gem" do it "resolves .." do create_temporary_dir("temp/empty_dir") - bundle "gem ..", :dir => bundled_app("temp/empty_dir") + bundle "gem ..", dir: bundled_app("temp/empty_dir") expect(bundled_app("temp/lib/temp.rb")).to exist end @@ -449,7 +484,7 @@ RSpec.describe "bundle gem" do it "resolves relative directory" do create_temporary_dir("tmp/empty/tmp") - bundle "gem ../../empty", :dir => bundled_app("tmp/empty/tmp") + bundle "gem ../../empty", dir: bundled_app("tmp/empty/tmp") expect(bundled_app("tmp/empty/lib/empty.rb")).to exist end @@ -517,6 +552,7 @@ RSpec.describe "bundle gem" do expect(bundled_app("#{gem_name}/Rakefile")).to exist expect(bundled_app("#{gem_name}/lib/#{require_path}.rb")).to exist expect(bundled_app("#{gem_name}/lib/#{require_path}/version.rb")).to exist + expect(bundled_app("#{gem_name}/sig/#{require_path}.rbs")).to exist expect(bundled_app("#{gem_name}/.gitignore")).to exist expect(bundled_app("#{gem_name}/bin/setup")).to exist @@ -533,6 +569,12 @@ RSpec.describe "bundle gem" do expect(bundled_app("#{gem_name}/lib/#{require_path}/version.rb").read).to match(/VERSION = "0.1.0"/) end + it "declare String type for VERSION constant" do + bundle "gem #{gem_name}" + + expect(bundled_app("#{gem_name}/sig/#{require_path}.rbs").read).to match(/VERSION: String/) + end + context "git config user.{name,email} is set" do before do bundle "gem #{gem_name}" @@ -567,13 +609,13 @@ RSpec.describe "bundle gem" do bundle "gem #{gem_name}" expect(generated_gemspec.metadata["allowed_push_host"]). - to match(/mygemserver\.com/) + to match(/example\.com/) end it "sets a minimum ruby version" do bundle "gem #{gem_name}" - expect(generated_gemspec.required_ruby_version).to eq(Gem::Requirement.new(Gem.ruby_version < Gem::Version.new("2.4.a") ? ">= 2.3.0" : ">= 2.4.0")) + expect(generated_gemspec.required_ruby_version.to_s).to start_with(">=") end it "requires the version file" do @@ -588,12 +630,28 @@ RSpec.describe "bundle gem" do expect(bundled_app("#{gem_name}/lib/#{require_path}.rb").read).to match(/class Error < StandardError; end$/) end + it "does not include the gemspec file in files" do + bundle "gem #{gem_name}" + + bundler_gemspec = Bundler::GemHelper.new(bundled_app(gem_name), gem_name).gemspec + + expect(bundler_gemspec.files).not_to include("#{gem_name}.gemspec") + end + + it "does not include the Gemfile file in files" do + bundle "gem #{gem_name}" + + bundler_gemspec = Bundler::GemHelper.new(bundled_app(gem_name), gem_name).gemspec + + expect(bundler_gemspec.files).not_to include("Gemfile") + end + it "runs rake without problems" do bundle "gem #{gem_name}" - system_gems ["rake-13.0.1"] + system_gems ["rake-#{rake_version}"] - rakefile = strip_whitespace <<-RAKEFILE + rakefile = <<~RAKEFILE task :default do puts 'SUCCESS' end @@ -602,7 +660,7 @@ RSpec.describe "bundle gem" do file.puts rakefile end - sys_exec(rake, :dir => bundled_app(gem_name)) + sys_exec(rake, dir: bundled_app(gem_name)) expect(out).to include("SUCCESS") end @@ -691,7 +749,7 @@ RSpec.describe "bundle gem" do end it "builds spec skeleton" do - expect(bundled_app("#{gem_name}/test/test_#{require_path}.rb")).to exist + expect(bundled_app("#{gem_name}/#{minitest_test_file_path}")).to exist expect(bundled_app("#{gem_name}/test/test_helper.rb")).to exist end end @@ -711,7 +769,7 @@ RSpec.describe "bundle gem" do end it "builds spec skeleton" do - expect(bundled_app("#{gem_name}/test/test_#{require_path}.rb")).to exist + expect(bundled_app("#{gem_name}/#{minitest_test_file_path}")).to exist expect(bundled_app("#{gem_name}/test/test_helper.rb")).to exist end @@ -720,11 +778,15 @@ RSpec.describe "bundle gem" do end it "requires 'test_helper'" do - expect(bundled_app("#{gem_name}/test/test_#{require_path}.rb").read).to include(%(require "test_helper")) + expect(bundled_app("#{gem_name}/#{minitest_test_file_path}").read).to include(%(require "test_helper")) + end + + it "defines valid test class name" do + expect(bundled_app("#{gem_name}/#{minitest_test_file_path}").read).to include(minitest_test_class_name) end it "creates a default test which fails" do - expect(bundled_app("#{gem_name}/test/test_#{require_path}.rb").read).to include("assert false") + expect(bundled_app("#{gem_name}/#{minitest_test_file_path}").read).to include("assert false") end end @@ -735,17 +797,13 @@ RSpec.describe "bundle gem" do end it "creates a default rake task to run the test suite" do - rakefile = strip_whitespace <<-RAKEFILE + rakefile = <<~RAKEFILE # frozen_string_literal: true require "bundler/gem_tasks" - require "rake/testtask" + require "minitest/test_task" - Rake::TestTask.new(:test) do |t| - t.libs << "test" - t.libs << "lib" - t.test_files = FileList["test/**/test_*.rb"] - end + Minitest::TestTask.create task default: :test RAKEFILE @@ -793,7 +851,7 @@ RSpec.describe "bundle gem" do end it "creates a default rake task to run the test suite" do - rakefile = strip_whitespace <<-RAKEFILE + rakefile = <<~RAKEFILE # frozen_string_literal: true require "bundler/gem_tasks" @@ -869,7 +927,6 @@ RSpec.describe "bundle gem" do bundle "gem #{gem_name}" expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to_not exist - expect(bundled_app("#{gem_name}/.travis.yml")).to_not exist expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to_not exist expect(bundled_app("#{gem_name}/.circleci/config.yml")).to_not exist end @@ -881,6 +938,12 @@ RSpec.describe "bundle gem" do expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist end + + it "contained .gitlab-ci.yml into ignore list" do + bundle "gem #{gem_name} --ci=github" + + expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include(".git .github appveyor") + end end context "--ci set to gitlab" do @@ -889,6 +952,12 @@ RSpec.describe "bundle gem" do expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to exist end + + it "contained .gitlab-ci.yml into ignore list" do + bundle "gem #{gem_name} --ci=gitlab" + + expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include(".git .gitlab-ci.yml appveyor") + end end context "--ci set to circle" do @@ -897,20 +966,17 @@ RSpec.describe "bundle gem" do expect(bundled_app("#{gem_name}/.circleci/config.yml")).to exist end - end - context "--ci set to travis" do - it "generates a Travis CI config file" do - bundle "gem #{gem_name} --ci=travis" + it "contained .circleci into ignore list" do + bundle "gem #{gem_name} --ci=circle" - expect(bundled_app("#{gem_name}/.travis.yml")).to exist + expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include(".git .circleci appveyor") end end context "gem.ci setting set to none" do it "doesn't generate any CI config" do expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to_not exist - expect(bundled_app("#{gem_name}/.travis.yml")).to_not exist expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to_not exist expect(bundled_app("#{gem_name}/.circleci/config.yml")).to_not exist end @@ -925,15 +991,6 @@ RSpec.describe "bundle gem" do end end - context "gem.ci setting set to travis" do - it "generates a Travis CI config file" do - bundle "config set gem.ci travis" - bundle "gem #{gem_name}" - - expect(bundled_app("#{gem_name}/.travis.yml")).to exist - end - end - context "gem.ci setting set to gitlab" do it "generates a GitLab CI config file" do bundle "config set gem.ci gitlab" @@ -1052,7 +1109,7 @@ RSpec.describe "bundle gem" do end end - context "gem.rubocop setting set to true", :bundler => "< 3" do + context "gem.rubocop setting set to true", bundler: "< 3" do before do bundle "config set gem.rubocop true" bundle "gem #{gem_name}" @@ -1289,6 +1346,10 @@ RSpec.describe "bundle gem" do let(:require_relative_path) { "test_gem" } + let(:minitest_test_file_path) { "test/test_test_gem.rb" } + + let(:minitest_test_class_name) { "class TestTestGem < Minitest::Test" } + let(:flags) { nil } it "does not nest constants" do @@ -1299,25 +1360,50 @@ RSpec.describe "bundle gem" do include_examples "generating a gem" - context "--ext parameter set" do - let(:flags) { "--ext" } + context "--ext parameter with no value" do + context "is deprecated", bundler: "< 3" do + it "prints deprecation when used after gem name" do + bundle ["gem", "--ext", gem_name].compact.join(" ") + expect(err).to include "[DEPRECATED]" + expect(err).to include "`--ext` with no arguments has been deprecated" + expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.c")).to exist + end + + it "prints deprecation when used before gem name" do + bundle ["gem", gem_name, "--ext"].compact.join(" ") + expect(err).to include "[DEPRECATED]" + expect(err).to include "`--ext` with no arguments has been deprecated" + expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.c")).to exist + end + end + end + + context "--ext parameter set with C" do + let(:flags) { "--ext=c" } before do bundle ["gem", gem_name, flags].compact.join(" ") end + it "is not deprecated" do + expect(err).not_to include "[DEPRECATED] Option `--ext` without explicit value is deprecated." + end + it "builds ext skeleton" do expect(bundled_app("#{gem_name}/ext/#{gem_name}/extconf.rb")).to exist expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.h")).to exist expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.c")).to exist end - it "includes rake-compiler" do + it "includes rake-compiler, but no Rust related changes" do expect(bundled_app("#{gem_name}/Gemfile").read).to include('gem "rake-compiler"') + + expect(bundled_app("#{gem_name}/Gemfile").read).to_not include('gem "rb_sys"') + expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to_not include('spec.required_rubygems_version = ">= ') end it "depends on compile task for build" do - rakefile = strip_whitespace <<-RAKEFILE + rakefile = <<~RAKEFILE # frozen_string_literal: true require "bundler/gem_tasks" @@ -1325,7 +1411,9 @@ RSpec.describe "bundle gem" do task build: :compile - Rake::ExtensionTask.new("#{gem_name}") do |ext| + GEMSPEC = Gem::Specification.load("#{gem_name}.gemspec") + + Rake::ExtensionTask.new("#{gem_name}", GEMSPEC) do |ext| ext.lib_dir = "lib/#{gem_name}" end @@ -1335,6 +1423,66 @@ RSpec.describe "bundle gem" do expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) end end + + context "--ext parameter set with rust and old RubyGems" do + it "fails in friendly way" do + if ::Gem::Version.new("3.3.11") <= ::Gem.rubygems_version + skip "RubyGems compatible with Rust builder" + end + + expect do + bundle ["gem", gem_name, "--ext=rust"].compact.join(" ") + end.to raise_error(RuntimeError, /too old to build Rust extension/) + end + end + + context "--ext parameter set with rust" do + let(:flags) { "--ext=rust" } + + before do + skip "RubyGems incompatible with Rust builder" if ::Gem::Version.new("3.3.11") > ::Gem.rubygems_version + + bundle ["gem", gem_name, flags].compact.join(" ") + end + + it "is not deprecated" do + expect(err).not_to include "[DEPRECATED] Option `--ext` without explicit value is deprecated." + end + + it "builds ext skeleton" do + expect(bundled_app("#{gem_name}/Cargo.toml")).to exist + expect(bundled_app("#{gem_name}/ext/#{gem_name}/Cargo.toml")).to exist + expect(bundled_app("#{gem_name}/ext/#{gem_name}/extconf.rb")).to exist + expect(bundled_app("#{gem_name}/ext/#{gem_name}/src/lib.rs")).to exist + end + + it "includes rake-compiler, rb_sys gems and required_rubygems_version constraint" do + expect(bundled_app("#{gem_name}/Gemfile").read).to include('gem "rake-compiler"') + expect(bundled_app("#{gem_name}/Gemfile").read).to include('gem "rb_sys"') + expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include('spec.required_rubygems_version = ">= ') + end + + it "depends on compile task for build" do + rakefile = <<~RAKEFILE + # frozen_string_literal: true + + require "bundler/gem_tasks" + require "rb_sys/extensiontask" + + task build: :compile + + GEMSPEC = Gem::Specification.load("#{gem_name}.gemspec") + + RbSys::ExtensionTask.new("#{gem_name}", GEMSPEC) do |ext| + ext.lib_dir = "lib/#{gem_name}" + end + + task default: :compile + RAKEFILE + + expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) + end + end end context "gem naming with dashed", :readline do @@ -1344,6 +1492,10 @@ RSpec.describe "bundle gem" do let(:require_relative_path) { "gem" } + let(:minitest_test_file_path) { "test/test/test_gem.rb" } + + let(:minitest_test_class_name) { "class Test::TestGem < Minitest::Test" } + it "nests constants so they work" do bundle "gem #{gem_name}" expect(bundled_app("#{gem_name}/lib/#{require_path}/version.rb").read).to match(/module Test\n module Gem/) @@ -1361,22 +1513,22 @@ RSpec.describe "bundle gem" do end it "fails gracefully with a ." do - bundle "gem foo.gemspec", :raise_on_error => false + bundle "gem foo.gemspec", raise_on_error: false expect(err).to end_with("Invalid gem name foo.gemspec -- `Foo.gemspec` is an invalid constant name") end it "fails gracefully with a ^" do - bundle "gem ^", :raise_on_error => false + bundle "gem ^", raise_on_error: false expect(err).to end_with("Invalid gem name ^ -- `^` is an invalid constant name") end it "fails gracefully with a space" do - bundle "gem 'foo bar'", :raise_on_error => false + bundle "gem 'foo bar'", raise_on_error: false expect(err).to end_with("Invalid gem name foo bar -- `Foo bar` is an invalid constant name") end it "fails gracefully when multiple names are passed" do - bundle "gem foo bar baz", :raise_on_error => false + bundle "gem foo bar baz", raise_on_error: false expect(err).to eq(<<-E.strip) ERROR: "bundle gem" was called with arguments ["foo", "bar", "baz"] Usage: "bundle gem NAME [OPTIONS]" @@ -1386,7 +1538,7 @@ Usage: "bundle gem NAME [OPTIONS]" describe "#ensure_safe_gem_name", :readline do before do - bundle "gem #{subject}", :raise_on_error => false + bundle "gem #{subject}", raise_on_error: false end context "with an existing const name" do @@ -1419,7 +1571,7 @@ Usage: "bundle gem NAME [OPTIONS]" end expect(bundled_app("foobar/spec/spec_helper.rb")).to exist - rakefile = strip_whitespace <<-RAKEFILE + rakefile = <<~RAKEFILE # frozen_string_literal: true require "bundler/gem_tasks" @@ -1481,7 +1633,7 @@ Usage: "bundle gem NAME [OPTIONS]" context "on conflicts with a previously created file", :readline do it "should fail gracefully" do FileUtils.touch(bundled_app("conflict-foobar")) - bundle "gem conflict-foobar", :raise_on_error => false + bundle "gem conflict-foobar", raise_on_error: false expect(err).to eq("Couldn't create a new gem named `conflict-foobar` because there's an existing file named `conflict-foobar`.") expect(exitstatus).to eql(32) end diff --git a/spec/bundler/commands/open_spec.rb b/spec/bundler/commands/open_spec.rb index 53dc35c2c7..97374f30c3 100644 --- a/spec/bundler/commands/open_spec.rb +++ b/spec/bundler/commands/open_spec.rb @@ -10,66 +10,124 @@ RSpec.describe "bundle open" do end it "opens the gem with BUNDLER_EDITOR as highest priority" do - bundle "open rails", :env => { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" } + bundle "open rails", env: { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" } expect(out).to include("bundler_editor #{default_bundle_path("gems", "rails-2.3.2")}") end it "opens the gem with VISUAL as 2nd highest priority" do - bundle "open rails", :env => { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "" } + bundle "open rails", env: { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "" } expect(out).to include("visual #{default_bundle_path("gems", "rails-2.3.2")}") end it "opens the gem with EDITOR as 3rd highest priority" do - bundle "open rails", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } + bundle "open rails", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } expect(out).to include("editor #{default_bundle_path("gems", "rails-2.3.2")}") end it "complains if no EDITOR is set" do - bundle "open rails", :env => { "EDITOR" => "", "VISUAL" => "", "BUNDLER_EDITOR" => "" } + bundle "open rails", env: { "EDITOR" => "", "VISUAL" => "", "BUNDLER_EDITOR" => "" } expect(out).to eq("To open a bundled gem, set $EDITOR or $BUNDLER_EDITOR") end it "complains if gem not in bundle" do - bundle "open missing", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, :raise_on_error => false + bundle "open missing", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, raise_on_error: false expect(err).to match(/could not find gem 'missing'/i) end it "does not blow up if the gem to open does not have a Gemfile" do git = build_git "foo" - ref = git.ref_for("master", 11) + ref = git.ref_for("main", 11) install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem 'foo', :git => "#{lib_path("foo-1.0")}" G - bundle "open foo", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } - expect(out).to match("editor #{default_bundle_path.join("bundler/gems/foo-1.0-#{ref}")}") + bundle "open foo", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } + expect(out).to include("editor #{default_bundle_path.join("bundler", "gems", "foo-1.0-#{ref}")}") end it "suggests alternatives for similar-sounding gems" do - bundle "open Rails", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, :raise_on_error => false + bundle "open Rails", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, raise_on_error: false expect(err).to match(/did you mean rails\?/i) end it "opens the gem with short words" do - bundle "open rec", :env => { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" } + bundle "open rec", env: { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" } expect(out).to include("bundler_editor #{default_bundle_path("gems", "activerecord-2.3.2")}") end + it "opens subpath of the gem" do + bundle "open activerecord --path lib/activerecord", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } + expect(out).to include("editor #{default_bundle_path("gems", "activerecord-2.3.2")}/lib/activerecord") + end + + it "opens subpath file of the gem" do + bundle "open activerecord --path lib/version.rb", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } + expect(out).to include("editor #{default_bundle_path("gems", "activerecord-2.3.2")}/lib/version.rb") + end + + it "opens deep subpath of the gem" do + bundle "open activerecord --path lib/active_record", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } + expect(out).to include("editor #{default_bundle_path("gems", "activerecord-2.3.2")}/lib/active_record") + end + + it "requires value for --path arg" do + bundle "open activerecord --path", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, raise_on_error: false + expect(err).to eq "Cannot specify `--path` option without a value" + end + + it "suggests alternatives for similar-sounding gems when using subpath" do + bundle "open Rails --path README.md", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, raise_on_error: false + expect(err).to match(/did you mean rails\?/i) + end + + it "suggests alternatives for similar-sounding gems when using deep subpath" do + bundle "open Rails --path some/path/here", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, raise_on_error: false + expect(err).to match(/did you mean rails\?/i) + end + + it "opens subpath of the short worded gem" do + bundle "open rec --path CHANGELOG.md", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } + expect(out).to include("editor #{default_bundle_path("gems", "activerecord-2.3.2")}/CHANGELOG.md") + end + + it "opens deep subpath of the short worded gem" do + bundle "open rec --path lib/activerecord", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } + expect(out).to include("editor #{default_bundle_path("gems", "activerecord-2.3.2")}/lib/activerecord") + end + + it "opens subpath of the selected matching gem", :readline do + env = { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" } + bundle "open active --path CHANGELOG.md", env: env do |input, _, _| + input.puts "2" + end + + expect(out).to include("bundler_editor #{default_bundle_path("gems", "activerecord-2.3.2").join("CHANGELOG.md")}") + end + + it "opens deep subpath of the selected matching gem", :readline do + env = { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" } + bundle "open active --path lib/activerecord/version.rb", env: env do |input, _, _| + input.puts "2" + end + + expect(out).to include("bundler_editor #{default_bundle_path("gems", "activerecord-2.3.2").join("lib", "activerecord", "version.rb")}") + end + it "select the gem from many match gems", :readline do env = { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" } - bundle "open active", :env => env do |input, _, _| + bundle "open active", env: env do |input, _, _| input.puts "2" end - expect(out).to match(/bundler_editor #{default_bundle_path('gems', 'activerecord-2.3.2')}\z/) + expect(out).to include("bundler_editor #{default_bundle_path("gems", "activerecord-2.3.2")}") end it "allows selecting exit from many match gems", :readline do env = { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" } - bundle "open active", :env => env do |input, _, _| + bundle "open active", env: env do |input, _, _| input.puts "0" end end @@ -82,12 +140,12 @@ RSpec.describe "bundle open" do G bundle "config set auto_install 1" - bundle "open rails", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } + bundle "open rails", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } expect(out).to include("Installing foo 1.0") end it "opens the editor with a clean env" do - bundle "open", :env => { "EDITOR" => "sh -c 'env'", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, :raise_on_error => false + bundle "open", env: { "EDITOR" => "sh -c 'env'", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, raise_on_error: false expect(out).not_to include("BUNDLE_GEMFILE=") end end @@ -111,7 +169,7 @@ RSpec.describe "bundle open" do end it "throws proper error when trying to open default gem" do - bundle "open json", :env => { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" } + bundle "open json", env: { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" } expect(out).to include("Unable to open json because it's a default gem, so the directory it would normally be installed to does not exist.") end end diff --git a/spec/bundler/commands/outdated_spec.rb b/spec/bundler/commands/outdated_spec.rb index 731d67af1b..e7edc67e57 100644 --- a/spec/bundler/commands/outdated_spec.rb +++ b/spec/bundler/commands/outdated_spec.rb @@ -4,8 +4,8 @@ RSpec.describe "bundle outdated" do describe "with no arguments" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G @@ -23,11 +23,11 @@ RSpec.describe "bundle outdated" do update_repo2 do build_gem "activesupport", "3.0" build_gem "weakling", "0.2" - update_git "foo", :path => lib_path("foo") - update_git "zebra", :path => lib_path("zebra") + update_git "foo", path: lib_path("foo") + update_git "zebra", path: lib_path("zebra") end - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false expected_output = <<~TABLE.gsub("x", "\\\h").tr(".", "\.").strip Gem Current Latest Requested Groups @@ -50,7 +50,7 @@ RSpec.describe "bundle outdated" do gem "AAA", "1.0.0" G - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false expected_output = <<~TABLE Gem Current Latest Requested Groups @@ -63,10 +63,10 @@ RSpec.describe "bundle outdated" do it "returns non zero exit status if outdated gems present" do update_repo2 do build_gem "activesupport", "3.0" - update_git "foo", :path => lib_path("foo") + update_git "foo", path: lib_path("foo") end - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false expect(exitstatus).to_not be_zero end @@ -89,7 +89,7 @@ RSpec.describe "bundle outdated" do update_repo2 { build_gem "activesupport", "3.0" } update_repo2 { build_gem "terranova", "9" } - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false expected_output = <<~TABLE.strip Gem Current Latest Requested Groups @@ -104,8 +104,8 @@ RSpec.describe "bundle outdated" do describe "with --verbose option" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G @@ -139,7 +139,7 @@ RSpec.describe "bundle outdated" do gem 'activesupport', '2.3.5' G - bundle "outdated --verbose", :raise_on_error => false + bundle "outdated --verbose", raise_on_error: false expected_output = <<~TABLE.strip Gem Current Latest Requested Groups Path @@ -151,7 +151,7 @@ RSpec.describe "bundle outdated" do end end - describe "with multiple, duplicated sources, with lockfile in old format", :bundler => "< 3" do + describe "with multiple, duplicated sources, with lockfile in old format", bundler: "< 3" do before do build_repo2 do build_gem "dotenv", "2.7.6" @@ -192,7 +192,7 @@ RSpec.describe "bundle outdated" do vcr (6.0.0) PLATFORMS - #{specific_local_platform} + #{local_platform} DEPENDENCIES dotenv @@ -205,8 +205,8 @@ RSpec.describe "bundle outdated" do end it "works" do - bundle :install, :artifice => "compact_index" - bundle :outdated, :artifice => "compact_index", :raise_on_error => false + bundle :install, artifice: "compact_index" + bundle :outdated, artifice: "compact_index", raise_on_error: false expected_output = <<~TABLE Gem Current Latest Requested Groups @@ -220,8 +220,8 @@ RSpec.describe "bundle outdated" do describe "with --group option" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G @@ -243,7 +243,7 @@ RSpec.describe "bundle outdated" do build_gem "duradura", "8.0" end - bundle "outdated --group #{group}", :raise_on_error => false + bundle "outdated --group #{group}", raise_on_error: false end it "works when the bundle is up to date" do @@ -290,8 +290,8 @@ RSpec.describe "bundle outdated" do describe "with --groups option and outdated transitive dependencies" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") build_gem "bar", %w[2.0.0] @@ -312,7 +312,7 @@ RSpec.describe "bundle outdated" do end it "returns a sorted list of outdated gems" do - bundle "outdated --groups", :raise_on_error => false + bundle "outdated --groups", raise_on_error: false expected_output = <<~TABLE.strip Gem Current Latest Requested Groups @@ -326,8 +326,8 @@ RSpec.describe "bundle outdated" do describe "with --groups option" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G @@ -354,7 +354,7 @@ RSpec.describe "bundle outdated" do build_gem "duradura", "8.0" end - bundle "outdated --groups", :raise_on_error => false + bundle "outdated --groups", raise_on_error: false expected_output = <<~TABLE.strip Gem Current Latest Requested Groups @@ -370,8 +370,8 @@ RSpec.describe "bundle outdated" do describe "with --local option" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G @@ -398,7 +398,7 @@ RSpec.describe "bundle outdated" do gem "activesupport", "2.3.4" G - bundle "outdated --local", :raise_on_error => false + bundle "outdated --local", raise_on_error: false expected_output = <<~TABLE.strip Gem Current Latest Requested Groups @@ -420,8 +420,8 @@ RSpec.describe "bundle outdated" do context "and gems are outdated" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") build_gem "activesupport", "3.0" build_gem "weakling", "0.2" @@ -455,13 +455,13 @@ RSpec.describe "bundle outdated" do end describe "with --parseable option" do - subject { bundle "outdated --parseable", :raise_on_error => false } + subject { bundle "outdated --parseable", raise_on_error: false } it_behaves_like "a minimal output is desired" end describe "with aliased --porcelain option" do - subject { bundle "outdated --porcelain", :raise_on_error => false } + subject { bundle "outdated --porcelain", raise_on_error: false } it_behaves_like "a minimal output is desired" end @@ -469,8 +469,8 @@ RSpec.describe "bundle outdated" do describe "with specified gems" do it "returns list of outdated gems" do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G @@ -485,10 +485,10 @@ RSpec.describe "bundle outdated" do update_repo2 do build_gem "activesupport", "3.0" - update_git "foo", :path => lib_path("foo") + update_git "foo", path: lib_path("foo") end - bundle "outdated foo", :raise_on_error => false + bundle "outdated foo", raise_on_error: false expected_output = <<~TABLE.gsub("x", "\\\h").tr(".", "\.").strip Gem Current Latest Requested Groups @@ -502,8 +502,8 @@ RSpec.describe "bundle outdated" do describe "pre-release gems" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G @@ -535,7 +535,7 @@ RSpec.describe "bundle outdated" do build_gem "activesupport", "3.0.0.beta" end - bundle "outdated --pre", :raise_on_error => false + bundle "outdated --pre", raise_on_error: false expected_output = <<~TABLE.strip Gem Current Latest Requested Groups @@ -558,7 +558,7 @@ RSpec.describe "bundle outdated" do gem "activesupport", "3.0.0.beta.1" G - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false expected_output = <<~TABLE.strip Gem Current Latest Requested Groups @@ -570,12 +570,11 @@ RSpec.describe "bundle outdated" do end end - filter_strict_option = Bundler.feature_flag.bundler_2_mode? ? :"filter-strict" : :strict - describe "with --#{filter_strict_option} option" do + describe "with --filter-strict option" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G @@ -595,7 +594,23 @@ RSpec.describe "bundle outdated" do build_gem "weakling", "0.0.5" end - bundle :outdated, filter_strict_option => true, :raise_on_error => false + bundle :outdated, "filter-strict": true, raise_on_error: false + + expected_output = <<~TABLE.strip + Gem Current Latest Requested Groups + weakling 0.0.3 0.0.5 ~> 0.0.1 default + TABLE + + expect(out).to end_with(expected_output) + end + + it "only reports gems that have a newer version that matches the specified dependency version requirements, using --strict alias" do + update_repo2 do + build_gem "activesupport", "3.0" + build_gem "weakling", "0.0.5" + end + + bundle :outdated, strict: true, raise_on_error: false expected_output = <<~TABLE.strip Gem Current Latest Requested Groups @@ -611,7 +626,7 @@ RSpec.describe "bundle outdated" do gem "activesupport", platforms: [:ruby_22] G - bundle :outdated, filter_strict_option => true + bundle :outdated, "filter-strict": true expect(out).to end_with("Bundle up to date!") end @@ -622,7 +637,7 @@ RSpec.describe "bundle outdated" do gem "rack_middleware", "1.0" G - bundle :outdated, filter_strict_option => true + bundle :outdated, "filter-strict": true expect(out).to end_with("Bundle up to date!") end @@ -640,7 +655,7 @@ RSpec.describe "bundle outdated" do build_gem "weakling", "0.0.5" end - bundle :outdated, filter_strict_option => true, "filter-patch" => true, :raise_on_error => false + bundle :outdated, :"filter-strict" => true, "filter-patch" => true, :raise_on_error => false expected_output = <<~TABLE.strip Gem Current Latest Requested Groups @@ -662,7 +677,7 @@ RSpec.describe "bundle outdated" do build_gem "weakling", "0.1.5" end - bundle :outdated, filter_strict_option => true, "filter-minor" => true, :raise_on_error => false + bundle :outdated, :"filter-strict" => true, "filter-minor" => true, :raise_on_error => false expected_output = <<~TABLE.strip Gem Current Latest Requested Groups @@ -684,7 +699,7 @@ RSpec.describe "bundle outdated" do build_gem "weakling", "1.1.5" end - bundle :outdated, filter_strict_option => true, "filter-major" => true, :raise_on_error => false + bundle :outdated, :"filter-strict" => true, "filter-major" => true, :raise_on_error => false expected_output = <<~TABLE.strip Gem Current Latest Requested Groups @@ -699,8 +714,8 @@ RSpec.describe "bundle outdated" do describe "with invalid gem name" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G @@ -715,12 +730,12 @@ RSpec.describe "bundle outdated" do end it "returns could not find gem name" do - bundle "outdated invalid_gem_name", :raise_on_error => false + bundle "outdated invalid_gem_name", raise_on_error: false expect(err).to include("Could not find gem 'invalid_gem_name'.") end it "returns non-zero exit code" do - bundle "outdated invalid_gem_name", :raise_on_error => false + bundle "outdated invalid_gem_name", raise_on_error: false expect(exitstatus).to_not be_zero end end @@ -733,11 +748,11 @@ RSpec.describe "bundle outdated" do G bundle "config set auto_install 1" - bundle :outdated, :raise_on_error => false + bundle :outdated, raise_on_error: false expect(out).to include("Installing foo 1.0") end - context "after bundle install --deployment", :bundler => "< 3" do + context "after bundle install --deployment", bundler: "< 3" do before do build_repo2 @@ -748,13 +763,13 @@ RSpec.describe "bundle outdated" do gem "foo" G bundle :lock - bundle :install, :deployment => true + bundle :install, deployment: true end it "outputs a helpful message about being in deployment mode" do update_repo2 { build_gem "activesupport", "3.0" } - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false expect(last_command).to be_failure expect(err).to include("You are trying to check outdated gems in deployment mode.") expect(err).to include("Run `bundle outdated` elsewhere.") @@ -766,8 +781,8 @@ RSpec.describe "bundle outdated" do context "after bundle config set --local deployment true" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G @@ -782,7 +797,7 @@ RSpec.describe "bundle outdated" do it "outputs a helpful message about being in deployment mode" do update_repo2 { build_gem "activesupport", "3.0" } - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false expect(last_command).to be_failure expect(err).to include("You are trying to check outdated gems in deployment mode.") expect(err).to include("Run `bundle outdated` elsewhere.") @@ -822,13 +837,13 @@ RSpec.describe "bundle outdated" do expect(out).to end_with("Bundle up to date!") end - it "reports that updates are available if the JRuby platform is used", :jruby do + it "reports that updates are available if the JRuby platform is used", :jruby_only do install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "laduradura", '= 5.15.2', :platforms => [:ruby, :jruby] G - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false expected_output = <<~TABLE.strip Gem Current Latest Requested Groups @@ -852,8 +867,8 @@ RSpec.describe "bundle outdated" do shared_examples_for "major version updates are detected" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G @@ -878,8 +893,8 @@ RSpec.describe "bundle outdated" do context "when on a new machine" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G @@ -894,22 +909,22 @@ RSpec.describe "bundle outdated" do simulate_new_machine - update_git "foo", :path => lib_path("foo") + update_git "foo", path: lib_path("foo") update_repo2 do build_gem "activesupport", "3.3.5" build_gem "weakling", "0.8.0" end end - subject { bundle "outdated", :raise_on_error => false } + subject { bundle "outdated", raise_on_error: false } it_behaves_like "version update is detected" end shared_examples_for "minor version updates are detected" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G @@ -934,8 +949,8 @@ RSpec.describe "bundle outdated" do shared_examples_for "patch version updates are detected" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G @@ -967,8 +982,8 @@ RSpec.describe "bundle outdated" do shared_examples_for "major version is ignored" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G @@ -993,8 +1008,8 @@ RSpec.describe "bundle outdated" do shared_examples_for "minor version is ignored" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G @@ -1019,8 +1034,8 @@ RSpec.describe "bundle outdated" do shared_examples_for "patch version is ignored" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G @@ -1043,7 +1058,7 @@ RSpec.describe "bundle outdated" do end describe "with --filter-major option" do - subject { bundle "outdated --filter-major", :raise_on_error => false } + subject { bundle "outdated --filter-major", raise_on_error: false } it_behaves_like "major version updates are detected" it_behaves_like "minor version is ignored" @@ -1051,7 +1066,7 @@ RSpec.describe "bundle outdated" do end describe "with --filter-minor option" do - subject { bundle "outdated --filter-minor", :raise_on_error => false } + subject { bundle "outdated --filter-minor", raise_on_error: false } it_behaves_like "minor version updates are detected" it_behaves_like "major version is ignored" @@ -1059,7 +1074,7 @@ RSpec.describe "bundle outdated" do end describe "with --filter-patch option" do - subject { bundle "outdated --filter-patch", :raise_on_error => false } + subject { bundle "outdated --filter-patch", raise_on_error: false } it_behaves_like "patch version updates are detected" it_behaves_like "major version is ignored" @@ -1067,7 +1082,7 @@ RSpec.describe "bundle outdated" do end describe "with --filter-minor --filter-patch options" do - subject { bundle "outdated --filter-minor --filter-patch", :raise_on_error => false } + subject { bundle "outdated --filter-minor --filter-patch", raise_on_error: false } it_behaves_like "minor version updates are detected" it_behaves_like "patch version updates are detected" @@ -1075,7 +1090,7 @@ RSpec.describe "bundle outdated" do end describe "with --filter-major --filter-minor options" do - subject { bundle "outdated --filter-major --filter-minor", :raise_on_error => false } + subject { bundle "outdated --filter-major --filter-minor", raise_on_error: false } it_behaves_like "major version updates are detected" it_behaves_like "minor version updates are detected" @@ -1083,7 +1098,7 @@ RSpec.describe "bundle outdated" do end describe "with --filter-major --filter-patch options" do - subject { bundle "outdated --filter-major --filter-patch", :raise_on_error => false } + subject { bundle "outdated --filter-major --filter-patch", raise_on_error: false } it_behaves_like "major version updates are detected" it_behaves_like "patch version updates are detected" @@ -1091,7 +1106,7 @@ RSpec.describe "bundle outdated" do end describe "with --filter-major --filter-minor --filter-patch options" do - subject { bundle "outdated --filter-major --filter-minor --filter-patch", :raise_on_error => false } + subject { bundle "outdated --filter-major --filter-minor --filter-patch", raise_on_error: false } it_behaves_like "major version updates are detected" it_behaves_like "minor version updates are detected" @@ -1099,116 +1114,125 @@ RSpec.describe "bundle outdated" do end context "conservative updates" do - context "without update-strict" do - before do - build_repo4 do - build_gem "patch", %w[1.0.0 1.0.1] - build_gem "minor", %w[1.0.0 1.0.1 1.1.0] - build_gem "major", %w[1.0.0 1.0.1 1.1.0 2.0.0] - end + before do + build_repo4 do + build_gem "patch", %w[1.0.0 1.0.1] + build_gem "minor", %w[1.0.0 1.0.1 1.1.0] + build_gem "major", %w[1.0.0 1.0.1 1.1.0 2.0.0] + end - # establish a lockfile set to 1.0.0 - install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" - gem 'patch', '1.0.0' - gem 'minor', '1.0.0' - gem 'major', '1.0.0' - G + # establish a lockfile set to 1.0.0 + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem 'patch', '1.0.0' + gem 'minor', '1.0.0' + gem 'major', '1.0.0' + G - # remove 1.4.3 requirement and bar altogether - # to setup update specs below - gemfile <<-G - source "#{file_uri_for(gem_repo4)}" - gem 'patch' - gem 'minor' - gem 'major' - G - end + # remove all version requirements + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem 'patch' + gem 'minor' + gem 'major' + G + end - it "shows nothing when patching and filtering to minor" do - bundle "outdated --patch --filter-minor" + it "shows nothing when patching and filtering to minor" do + bundle "outdated --patch --filter-minor" - expect(out).to end_with("No minor updates to display.") - end + expect(out).to end_with("No minor updates to display.") + end - it "shows all gems when patching and filtering to patch" do - bundle "outdated --patch --filter-patch", :raise_on_error => false + it "shows all gems when patching and filtering to patch" do + bundle "outdated --patch --filter-patch", raise_on_error: false - expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups - major 1.0.0 1.0.1 >= 0 default - minor 1.0.0 1.0.1 >= 0 default - patch 1.0.0 1.0.1 >= 0 default - TABLE + expected_output = <<~TABLE.strip + Gem Current Latest Requested Groups + major 1.0.0 1.0.1 >= 0 default + minor 1.0.0 1.0.1 >= 0 default + patch 1.0.0 1.0.1 >= 0 default + TABLE - expect(out).to end_with(expected_output) - end + expect(out).to end_with(expected_output) + end - it "shows minor and major when updating to minor and filtering to patch and minor" do - bundle "outdated --minor --filter-minor", :raise_on_error => false + it "shows minor and major when updating to minor and filtering to patch and minor" do + bundle "outdated --minor --filter-minor", raise_on_error: false - expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups - major 1.0.0 1.1.0 >= 0 default - minor 1.0.0 1.1.0 >= 0 default - TABLE + expected_output = <<~TABLE.strip + Gem Current Latest Requested Groups + major 1.0.0 1.1.0 >= 0 default + minor 1.0.0 1.1.0 >= 0 default + TABLE - expect(out).to end_with(expected_output) - end + expect(out).to end_with(expected_output) + end - it "shows minor when updating to major and filtering to minor with parseable" do - bundle "outdated --major --filter-minor --parseable", :raise_on_error => false + it "shows minor when updating to major and filtering to minor with parseable" do + bundle "outdated --major --filter-minor --parseable", raise_on_error: false - expect(out).not_to include("patch (newest") - expect(out).to include("minor (newest") - expect(out).not_to include("major (newest") - end + expect(out).not_to include("patch (newest") + expect(out).to include("minor (newest") + expect(out).not_to include("major (newest") end + end - context "with update-strict" do - before do - build_repo4 do - build_gem "foo", %w[1.4.3 1.4.4] do |s| - s.add_dependency "bar", "~> 2.0" - end - build_gem "foo", %w[1.4.5 1.5.0] do |s| - s.add_dependency "bar", "~> 2.1" - end - 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 "qux", %w[1.0.0 1.1.0 2.0.0] + context "tricky conservative updates" do + before do + build_repo4 do + build_gem "foo", %w[1.4.3 1.4.4] do |s| + s.add_dependency "bar", "~> 2.0" + end + build_gem "foo", %w[1.4.5 1.5.0] do |s| + s.add_dependency "bar", "~> 2.1" end + 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 "qux", %w[1.0.0 1.1.0 2.0.0] + end - # establish a lockfile set to 1.4.3 - install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" - gem 'foo', '1.4.3' - gem 'bar', '2.0.3' - gem 'qux', '1.0.0' - G + # establish a lockfile set to 1.4.3 + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem 'foo', '1.4.3' + gem 'bar', '2.0.3' + gem 'qux', '1.0.0' + G - # remove 1.4.3 requirement and bar altogether - # to setup update specs below - gemfile <<-G - source "#{file_uri_for(gem_repo4)}" - gem 'foo' - gem 'qux' - G - end + # remove 1.4.3 requirement and bar altogether + # to setup update specs below + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem 'foo' + gem 'qux' + G + end - it "shows gems with update-strict updating to patch and filtering to patch" do - bundle "outdated --patch --update-strict --filter-patch", :raise_on_error => false + it "shows gems updating to patch and filtering to patch" do + bundle "outdated --patch --filter-patch", raise_on_error: false, env: { "DEBUG_RESOLVER" => "1" } - expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups - bar 2.0.3 2.0.5 - foo 1.4.3 1.4.4 >= 0 default - TABLE + expected_output = <<~TABLE.strip + Gem Current Latest Requested Groups + bar 2.0.3 2.0.5 + foo 1.4.3 1.4.4 >= 0 default + TABLE - expect(out).to end_with(expected_output) - end + expect(out).to end_with(expected_output) + end + + it "shows gems updating to patch and filtering to patch, in debug mode" do + bundle "outdated --patch --filter-patch", raise_on_error: false, env: { "DEBUG" => "1" } + + expected_output = <<~TABLE.strip + Gem Current Latest Requested Groups Path + bar 2.0.3 2.0.5 + foo 1.4.3 1.4.4 >= 0 default + TABLE + + expect(out).to end_with(expected_output) end end @@ -1232,7 +1256,7 @@ RSpec.describe "bundle outdated" do gem 'weakling' G - bundle "outdated --only-explicit", :raise_on_error => false + bundle "outdated --only-explicit", raise_on_error: false expected_output = <<~TABLE.strip Gem Current Latest Requested Groups @@ -1282,7 +1306,7 @@ RSpec.describe "bundle outdated" do end it "reports a single entry per gem" do - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false expected_output = <<~TABLE.strip Gem Current Latest Requested Groups @@ -1331,7 +1355,7 @@ RSpec.describe "bundle outdated" do end it "works" do - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false expected_output = <<~TABLE.strip Gem Current Latest Requested Groups diff --git a/spec/bundler/other/platform_spec.rb b/spec/bundler/commands/platform_spec.rb index 5693d6bce6..61e615acae 100644 --- a/spec/bundler/other/platform_spec.rb +++ b/spec/bundler/commands/platform_spec.rb @@ -2,10 +2,6 @@ RSpec.describe "bundle platform" do context "without flags" do - let(:bundle_platform_platforms_string) do - local_platforms.reverse.map {|pl| "* #{pl}" }.join("\n") - end - it "returns all the output" do gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -17,13 +13,13 @@ RSpec.describe "bundle platform" do bundle "platform" expect(out).to eq(<<-G.chomp) -Your platform is: #{RUBY_PLATFORM} +Your platform is: #{Gem::Platform.local} Your app has gems that work on these platforms: -#{bundle_platform_platforms_string} +* #{local_platform} Your Gemfile specifies a Ruby version requirement: -* ruby #{RUBY_VERSION} +* ruby #{Gem.ruby_version} Your current platform satisfies the Ruby version requirement. G @@ -40,13 +36,13 @@ G bundle "platform" expect(out).to eq(<<-G.chomp) -Your platform is: #{RUBY_PLATFORM} +Your platform is: #{Gem::Platform.local} Your app has gems that work on these platforms: -#{bundle_platform_platforms_string} +* #{local_platform} Your Gemfile specifies a Ruby version requirement: -* ruby #{RUBY_VERSION}p#{RUBY_PATCHLEVEL} +* #{Bundler::RubyVersion.system.single_version_string} Your current platform satisfies the Ruby version requirement. G @@ -61,10 +57,10 @@ G bundle "platform" expect(out).to eq(<<-G.chomp) -Your platform is: #{RUBY_PLATFORM} +Your platform is: #{Gem::Platform.local} Your app has gems that work on these platforms: -#{bundle_platform_platforms_string} +* #{local_platform} Your Gemfile does not specify a Ruby version requirement. G @@ -81,15 +77,15 @@ G bundle "platform" expect(out).to eq(<<-G.chomp) -Your platform is: #{RUBY_PLATFORM} +Your platform is: #{Gem::Platform.local} Your app has gems that work on these platforms: -#{bundle_platform_platforms_string} +* #{local_platform} Your Gemfile specifies a Ruby version requirement: * ruby #{not_local_ruby_version} -Your Ruby version is #{RUBY_VERSION}, but your Gemfile specified #{not_local_ruby_version} +Your Ruby version is #{Gem.ruby_version}, but your Gemfile specified #{not_local_ruby_version} G end end @@ -168,7 +164,7 @@ G gem "foo" G - bundle "platform", :raise_on_error => false + bundle "platform", raise_on_error: false expect(exitstatus).not_to eq(0) end @@ -181,7 +177,7 @@ G gem "foo" G - bundle "platform", :raise_on_error => false + bundle "platform", raise_on_error: false expect(exitstatus).not_to eq(0) end @@ -194,7 +190,7 @@ G gem "foo" G - bundle "platform", :raise_on_error => false + bundle "platform", raise_on_error: false expect(exitstatus).not_to eq(0) end @@ -235,7 +231,30 @@ G L bundle "platform --ruby" - expect(out).to eq("ruby 1.0.0p127") + expect(out).to eq("ruby 1.0.0") + end + + it "handles when there is a lockfile with no requirement" do + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + G + + lockfile <<-L + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: + + PLATFORMS + ruby + + DEPENDENCIES + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "platform --ruby" + expect(out).to eq("No ruby version specified") end it "handles when there is a requirement in the gemfile" do @@ -259,18 +278,18 @@ G end end - let(:ruby_version_correct) { "ruby \"#{RUBY_VERSION}\", :engine => \"#{local_ruby_engine}\", :engine_version => \"#{local_engine_version}\"" } - let(:ruby_version_correct_engineless) { "ruby \"#{RUBY_VERSION}\"" } + let(:ruby_version_correct) { "ruby \"#{Gem.ruby_version}\", :engine => \"#{local_ruby_engine}\", :engine_version => \"#{local_engine_version}\"" } + let(:ruby_version_correct_engineless) { "ruby \"#{Gem.ruby_version}\"" } let(:ruby_version_correct_patchlevel) { "#{ruby_version_correct}, :patchlevel => '#{RUBY_PATCHLEVEL}'" } let(:ruby_version_incorrect) { "ruby \"#{not_local_ruby_version}\", :engine => \"#{local_ruby_engine}\", :engine_version => \"#{not_local_ruby_version}\"" } - let(:engine_incorrect) { "ruby \"#{RUBY_VERSION}\", :engine => \"#{not_local_tag}\", :engine_version => \"#{RUBY_VERSION}\"" } - let(:engine_version_incorrect) { "ruby \"#{RUBY_VERSION}\", :engine => \"#{local_ruby_engine}\", :engine_version => \"#{not_local_engine_version}\"" } + let(:engine_incorrect) { "ruby \"#{Gem.ruby_version}\", :engine => \"#{not_local_tag}\", :engine_version => \"#{Gem.ruby_version}\"" } + let(:engine_version_incorrect) { "ruby \"#{Gem.ruby_version}\", :engine => \"#{local_ruby_engine}\", :engine_version => \"#{not_local_engine_version}\"" } let(:patchlevel_incorrect) { "#{ruby_version_correct}, :patchlevel => '#{not_local_patchlevel}'" } let(:patchlevel_fixnum) { "#{ruby_version_correct}, :patchlevel => #{RUBY_PATCHLEVEL}1" } def should_be_ruby_version_incorrect expect(exitstatus).to eq(18) - expect(err).to be_include("Your Ruby version is #{RUBY_VERSION}, but your Gemfile specified #{not_local_ruby_version}") + expect(err).to be_include("Your Ruby version is #{Gem.ruby_version}, but your Gemfile specified #{not_local_ruby_version}") end def should_be_engine_incorrect @@ -305,7 +324,7 @@ G expect(bundled_app_lock).to exist end - it "installs fine with any engine", :jruby do + it "installs fine with any engine", :jruby_only do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" @@ -328,7 +347,7 @@ G end it "doesn't install when the ruby version doesn't match" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem "rack" @@ -340,7 +359,7 @@ G end it "doesn't install when engine doesn't match" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem "rack" @@ -351,8 +370,8 @@ G should_be_engine_incorrect end - it "doesn't install when engine version doesn't match", :jruby do - install_gemfile <<-G, :raise_on_error => false + it "doesn't install when engine version doesn't match", :jruby_only do + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem "rack" @@ -364,7 +383,7 @@ G end it "doesn't install when patchlevel doesn't match" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem "rack" @@ -394,7 +413,7 @@ G expect(out).to match(/\AResolving dependencies\.\.\.\.*\nThe Gemfile's dependencies are satisfied\z/) end - it "checks fine with any engine", :jruby do + it "checks fine with any engine", :jruby_only do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" @@ -424,7 +443,7 @@ G #{ruby_version_incorrect} G - bundle :check, :raise_on_error => false + bundle :check, raise_on_error: false should_be_ruby_version_incorrect end @@ -441,11 +460,11 @@ G #{engine_incorrect} G - bundle :check, :raise_on_error => false + bundle :check, raise_on_error: false should_be_engine_incorrect end - it "fails when engine version doesn't match", :jruby do + it "fails when engine version doesn't match", :jruby_only do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" @@ -458,7 +477,7 @@ G #{engine_version_incorrect} G - bundle :check, :raise_on_error => false + bundle :check, raise_on_error: false should_be_engine_version_incorrect end @@ -475,7 +494,7 @@ G #{patchlevel_incorrect} G - bundle :check, :raise_on_error => false + bundle :check, raise_on_error: false should_be_patchlevel_incorrect end end @@ -507,11 +526,11 @@ G build_gem "activesupport", "3.0" end - bundle "update", :all => true + bundle "update", all: true expect(the_bundle).to include_gems "rack 1.2", "rack-obama 1.0", "activesupport 3.0" end - it "updates fine with any engine", :jruby do + it "updates fine with any engine", :jruby_only do gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "activesupport" @@ -527,7 +546,7 @@ G build_gem "activesupport", "3.0" end - bundle "update", :all => true + bundle "update", all: true expect(the_bundle).to include_gems "rack 1.2", "rack-obama 1.0", "activesupport 3.0" end @@ -543,11 +562,11 @@ G build_gem "activesupport", "3.0" end - bundle :update, :all => true, :raise_on_error => false + bundle :update, all: true, raise_on_error: false should_be_ruby_version_incorrect end - it "fails when ruby engine doesn't match", :jruby do + it "fails when ruby engine doesn't match", :jruby_only do gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "activesupport" @@ -559,11 +578,11 @@ G build_gem "activesupport", "3.0" end - bundle :update, :all => true, :raise_on_error => false + bundle :update, all: true, raise_on_error: false should_be_engine_incorrect end - it "fails when ruby engine version doesn't match", :jruby do + it "fails when ruby engine version doesn't match", :jruby_only do gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "activesupport" @@ -575,7 +594,7 @@ G build_gem "activesupport", "3.0" end - bundle :update, :all => true, :raise_on_error => false + bundle :update, all: true, raise_on_error: false should_be_engine_version_incorrect end @@ -590,7 +609,7 @@ G build_gem "activesupport", "3.0" end - bundle :update, :all => true, :raise_on_error => false + bundle :update, all: true, raise_on_error: false should_be_patchlevel_incorrect end end @@ -615,7 +634,7 @@ G expect(out).to eq(default_bundle_path("gems", "rails-2.3.2").to_s) end - it "prints path if ruby version is correct for any engine", :jruby do + it "prints path if ruby version is correct for any engine", :jruby_only do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rails" @@ -627,7 +646,7 @@ G expect(out).to eq(default_bundle_path("gems", "rails-2.3.2").to_s) end - it "fails if ruby version doesn't match", :bundler => "< 3" do + it "fails if ruby version doesn't match", bundler: "< 3" do gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rails" @@ -635,11 +654,11 @@ G #{ruby_version_incorrect} G - bundle "show rails", :raise_on_error => false + bundle "show rails", raise_on_error: false should_be_ruby_version_incorrect end - it "fails if engine doesn't match", :bundler => "< 3" do + it "fails if engine doesn't match", bundler: "< 3" do gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rails" @@ -647,11 +666,11 @@ G #{engine_incorrect} G - bundle "show rails", :raise_on_error => false + bundle "show rails", raise_on_error: false should_be_engine_incorrect end - it "fails if engine version doesn't match", :bundler => "< 3", :jruby => true do + it "fails if engine version doesn't match", bundler: "< 3", jruby_only: true do gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rails" @@ -659,11 +678,11 @@ G #{engine_version_incorrect} G - bundle "show rails", :raise_on_error => false + bundle "show rails", raise_on_error: false should_be_engine_version_incorrect end - it "fails when patchlevel doesn't match", :bundler => "< 3" do + it "fails when patchlevel doesn't match", bundler: "< 3" do gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" @@ -674,7 +693,7 @@ G build_gem "activesupport", "3.0" end - bundle "show rails", :raise_on_error => false + bundle "show rails", raise_on_error: false should_be_patchlevel_incorrect end end @@ -699,7 +718,7 @@ G expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist end - it "copies the .gem file to vendor/cache when ruby version matches for any engine", :jruby do + it "copies the .gem file to vendor/cache when ruby version matches for any engine", :jruby_only do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem 'rack' @@ -719,7 +738,7 @@ G #{ruby_version_incorrect} G - bundle :cache, :raise_on_error => false + bundle :cache, raise_on_error: false should_be_ruby_version_incorrect end @@ -731,11 +750,11 @@ G #{engine_incorrect} G - bundle :cache, :raise_on_error => false + bundle :cache, raise_on_error: false should_be_engine_incorrect end - it "fails if the engine version doesn't match", :jruby do + it "fails if the engine version doesn't match", :jruby_only do gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem 'rack' @@ -743,7 +762,7 @@ G #{engine_version_incorrect} G - bundle :cache, :raise_on_error => false + bundle :cache, raise_on_error: false should_be_engine_version_incorrect end @@ -755,7 +774,7 @@ G #{patchlevel_incorrect} G - bundle :cache, :raise_on_error => false + bundle :cache, raise_on_error: false should_be_patchlevel_incorrect end end @@ -780,7 +799,7 @@ G expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist end - it "copies the .gem file to vendor/cache when ruby version matches any engine", :jruby do + it "copies the .gem file to vendor/cache when ruby version matches any engine", :jruby_only do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem 'rack' @@ -800,7 +819,7 @@ G #{ruby_version_incorrect} G - bundle :cache, :raise_on_error => false + bundle :cache, raise_on_error: false should_be_ruby_version_incorrect end @@ -812,11 +831,11 @@ G #{engine_incorrect} G - bundle :cache, :raise_on_error => false + bundle :cache, raise_on_error: false should_be_engine_incorrect end - it "fails if the engine version doesn't match", :jruby do + it "fails if the engine version doesn't match", :jruby_only do gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem 'rack' @@ -824,7 +843,7 @@ G #{engine_version_incorrect} G - bundle :cache, :raise_on_error => false + bundle :cache, raise_on_error: false should_be_engine_version_incorrect end @@ -836,7 +855,7 @@ G #{patchlevel_incorrect} G - bundle :cache, :raise_on_error => false + bundle :cache, raise_on_error: false should_be_patchlevel_incorrect end end @@ -844,7 +863,7 @@ G context "bundle exec" do before do ENV["BUNDLER_FORCE_TTY"] = "true" - system_gems "rack-1.0.0", "rack-0.9.1", :path => default_bundle_path + system_gems "rack-1.0.0", "rack-0.9.1", path: default_bundle_path end it "activates the correct gem when ruby version matches" do @@ -859,8 +878,8 @@ G expect(out).to include("0.9.1") end - it "activates the correct gem when ruby version matches any engine", :jruby do - system_gems "rack-1.0.0", "rack-0.9.1", :path => default_bundle_path + it "activates the correct gem when ruby version matches any engine", :jruby_only do + system_gems "rack-1.0.0", "rack-0.9.1", path: default_bundle_path gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack", "0.9.1" @@ -880,7 +899,7 @@ G #{ruby_version_incorrect} G - bundle "exec rackup", :raise_on_error => false + bundle "exec rackup", raise_on_error: false should_be_ruby_version_incorrect end @@ -892,11 +911,11 @@ G #{engine_incorrect} G - bundle "exec rackup", :raise_on_error => false + bundle "exec rackup", raise_on_error: false should_be_engine_incorrect end - # it "fails when the engine version doesn't match", :jruby do + # it "fails when the engine version doesn't match", :jruby_only do # gemfile <<-G # gem "rack", "0.9.1" # @@ -915,12 +934,12 @@ G #{patchlevel_incorrect} G - bundle "exec rackup", :raise_on_error => false + bundle "exec rackup", raise_on_error: false should_be_patchlevel_incorrect end end - context "bundle console", :bundler => "< 3" do + context "bundle console", bundler: "< 3" do before do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -947,7 +966,7 @@ G expect(out).to include("0.9.1") end - it "starts IRB with the default group loaded when ruby version matches", :readline, :jruby do + it "starts IRB with the default group loaded when ruby version matches", :readline, :jruby_only do gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" @@ -974,7 +993,7 @@ G #{ruby_version_incorrect} G - bundle "console", :raise_on_error => false + bundle "console", raise_on_error: false should_be_ruby_version_incorrect end @@ -988,11 +1007,11 @@ G #{engine_incorrect} G - bundle "console", :raise_on_error => false + bundle "console", raise_on_error: false should_be_engine_incorrect end - it "fails when engine version doesn't match", :jruby do + it "fails when engine version doesn't match", :jruby_only do gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" @@ -1002,7 +1021,7 @@ G #{engine_version_incorrect} G - bundle "console", :raise_on_error => false + bundle "console", raise_on_error: false should_be_engine_version_incorrect end @@ -1016,7 +1035,7 @@ G #{patchlevel_incorrect} G - bundle "console", :raise_on_error => false + bundle "console", raise_on_error: false should_be_patchlevel_incorrect end end @@ -1047,7 +1066,7 @@ G expect(bundled_app_lock).to exist end - it "makes a Gemfile.lock if setup succeeds for any engine", :jruby do + it "makes a Gemfile.lock if setup succeeds for any engine", :jruby_only do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "yard" @@ -1063,7 +1082,7 @@ G end it "fails when ruby version doesn't match" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem "yard" gem "rack" @@ -1073,14 +1092,14 @@ G FileUtils.rm(bundled_app_lock) - ruby "require 'bundler/setup'", :env => { "BUNDLER_VERSION" => Bundler::VERSION }, :raise_on_error => false + ruby "require 'bundler/setup'", env: { "BUNDLER_VERSION" => Bundler::VERSION }, raise_on_error: false expect(bundled_app_lock).not_to exist should_be_ruby_version_incorrect end it "fails when engine doesn't match" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem "yard" gem "rack" @@ -1090,14 +1109,14 @@ G FileUtils.rm(bundled_app_lock) - ruby "require 'bundler/setup'", :env => { "BUNDLER_VERSION" => Bundler::VERSION }, :raise_on_error => false + ruby "require 'bundler/setup'", env: { "BUNDLER_VERSION" => Bundler::VERSION }, raise_on_error: false expect(bundled_app_lock).not_to exist should_be_engine_incorrect end - it "fails when engine version doesn't match", :jruby do - install_gemfile <<-G, :raise_on_error => false + it "fails when engine version doesn't match", :jruby_only do + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem "yard" gem "rack" @@ -1107,14 +1126,14 @@ G FileUtils.rm(bundled_app_lock) - ruby "require 'bundler/setup'", :env => { "BUNDLER_VERSION" => Bundler::VERSION }, :raise_on_error => false + ruby "require 'bundler/setup'", env: { "BUNDLER_VERSION" => Bundler::VERSION }, raise_on_error: false expect(bundled_app_lock).not_to exist should_be_engine_version_incorrect end it "fails when patchlevel doesn't match" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem "yard" gem "rack" @@ -1124,7 +1143,7 @@ G FileUtils.rm(bundled_app_lock) - ruby "require 'bundler/setup'", :env => { "BUNDLER_VERSION" => Bundler::VERSION }, :raise_on_error => false + ruby "require 'bundler/setup'", env: { "BUNDLER_VERSION" => Bundler::VERSION }, raise_on_error: false expect(bundled_app_lock).not_to exist should_be_patchlevel_incorrect @@ -1134,7 +1153,7 @@ G context "bundle outdated" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") + build_git "foo", path: lib_path("foo") end install_gemfile <<-G @@ -1147,7 +1166,7 @@ G it "returns list of outdated gems when the ruby version matches" do update_repo2 do build_gem "activesupport", "3.0" - update_git "foo", :path => lib_path("foo") + update_git "foo", path: lib_path("foo") end gemfile <<-G @@ -1158,7 +1177,7 @@ G #{ruby_version_correct} G - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false expected_output = <<~TABLE.gsub("x", "\\\h").tr(".", "\.").strip Gem Current Latest Requested Groups @@ -1169,11 +1188,11 @@ G expect(out).to match(Regexp.new(expected_output)) end - it "returns list of outdated gems when the ruby version matches for any engine", :jruby do + it "returns list of outdated gems when the ruby version matches for any engine", :jruby_only do bundle :install update_repo2 do build_gem "activesupport", "3.0" - update_git "foo", :path => lib_path("foo") + update_git "foo", path: lib_path("foo") end gemfile <<-G @@ -1184,7 +1203,7 @@ G #{ruby_version_correct_engineless} G - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false expected_output = <<~TABLE.gsub("x", "\\\h").tr(".", "\.").strip Gem Current Latest Requested Groups @@ -1198,7 +1217,7 @@ G it "fails when the ruby version doesn't match" do update_repo2 do build_gem "activesupport", "3.0" - update_git "foo", :path => lib_path("foo") + update_git "foo", path: lib_path("foo") end gemfile <<-G @@ -1209,14 +1228,14 @@ G #{ruby_version_incorrect} G - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false should_be_ruby_version_incorrect end it "fails when the engine doesn't match" do update_repo2 do build_gem "activesupport", "3.0" - update_git "foo", :path => lib_path("foo") + update_git "foo", path: lib_path("foo") end gemfile <<-G @@ -1227,14 +1246,14 @@ G #{engine_incorrect} G - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false should_be_engine_incorrect end - it "fails when the engine version doesn't match", :jruby do + it "fails when the engine version doesn't match", :jruby_only do update_repo2 do build_gem "activesupport", "3.0" - update_git "foo", :path => lib_path("foo") + update_git "foo", path: lib_path("foo") end gemfile <<-G @@ -1245,14 +1264,14 @@ G #{engine_version_incorrect} G - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false should_be_engine_version_incorrect end - it "fails when the patchlevel doesn't match", :jruby do + it "fails when the patchlevel doesn't match", :jruby_only do update_repo2 do build_gem "activesupport", "3.0" - update_git "foo", :path => lib_path("foo") + update_git "foo", path: lib_path("foo") end gemfile <<-G @@ -1263,14 +1282,14 @@ G #{patchlevel_incorrect} G - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false should_be_patchlevel_incorrect end - it "fails when the patchlevel is a fixnum", :jruby do + it "fails when the patchlevel is a fixnum", :jruby_only do update_repo2 do build_gem "activesupport", "3.0" - update_git "foo", :path => lib_path("foo") + update_git "foo", path: lib_path("foo") end gemfile <<-G @@ -1281,7 +1300,7 @@ G #{patchlevel_fixnum} G - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false should_be_patchlevel_fixnum end end diff --git a/spec/bundler/commands/post_bundle_message_spec.rb b/spec/bundler/commands/post_bundle_message_spec.rb index 72f8020b44..07fd5a79e9 100644 --- a/spec/bundler/commands/post_bundle_message_spec.rb +++ b/spec/bundler/commands/post_bundle_message_spec.rb @@ -114,14 +114,13 @@ RSpec.describe "post bundle message" do end it "should report a helpful error message" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem "rack" gem "not-a-gem", :group => :development G expect(err).to include <<-EOS.strip -Could not find gem 'not-a-gem' in rubygems repository #{file_uri_for(gem_repo1)}/ or installed locally. -The source does not contain any versions of 'not-a-gem' +Could not find gem 'not-a-gem' in rubygems repository #{file_uri_for(gem_repo1)}/, cached gems or installed locally. EOS end @@ -132,7 +131,7 @@ The source does not contain any versions of 'not-a-gem' G bundle :cache expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem "rack" gem "not-a-gem", :group => :development @@ -143,7 +142,7 @@ The source does not contain any versions of 'not-a-gem' end end - describe "for second bundle install run", :bundler => "< 3" do + describe "for second bundle install run", bundler: "< 3" do it "without any options" do 2.times { bundle :install } expect(out).to include(bundle_show_message) @@ -180,25 +179,25 @@ The source does not contain any versions of 'not-a-gem' describe "for bundle update" do it "shows proper messages according to the configured groups" do - bundle :update, :all => true + bundle :update, all: true expect(out).not_to include("Gems in the groups") expect(out).to include(bundle_updated_message) bundle "config set --local without emo" bundle :install - bundle :update, :all => true + bundle :update, all: true expect(out).to include("Gems in the group 'emo' were not updated") expect(out).to include(bundle_updated_message) bundle "config set --local without emo test" bundle :install - bundle :update, :all => true + bundle :update, all: true expect(out).to include("Gems in the groups 'emo' and 'test' were not updated") expect(out).to include(bundle_updated_message) bundle "config set --local without emo obama test" bundle :install - bundle :update, :all => true + bundle :update, all: true expect(out).to include("Gems in the groups 'emo', 'obama' and 'test' were not updated") expect(out).to include(bundle_updated_message) end diff --git a/spec/bundler/commands/pristine_spec.rb b/spec/bundler/commands/pristine_spec.rb index 2f730bd4e2..1aec37f850 100644 --- a/spec/bundler/commands/pristine_spec.rb +++ b/spec/bundler/commands/pristine_spec.rb @@ -2,9 +2,9 @@ require "bundler/vendored_fileutils" -RSpec.describe "bundle pristine", :ruby_repo do +RSpec.describe "bundle pristine" do before :each do - build_lib "baz", :path => bundled_app do |s| + build_lib "baz", path: bundled_app do |s| s.version = "1.0.0" s.add_development_dependency "baz-dev", "=1.0.0" end @@ -13,16 +13,16 @@ RSpec.describe "bundle pristine", :ruby_repo do build_gem "weakling" build_gem "baz-dev", "1.0.0" build_gem "very_simple_binary", &:add_c_extension - build_git "foo", :path => lib_path("foo") - build_git "git_with_ext", :path => lib_path("git_with_ext"), &:add_c_extension - build_lib "bar", :path => lib_path("bar") + build_git "foo", path: lib_path("foo") + build_git "git_with_ext", path: lib_path("git_with_ext"), &:add_c_extension + build_lib "bar", path: lib_path("bar") end install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "weakling" gem "very_simple_binary" - gem "foo", :git => "#{lib_path("foo")}", :branch => "master" + gem "foo", :git => "#{lib_path("foo")}", :branch => "main" gem "git_with_ext", :git => "#{lib_path("git_with_ext")}" gem "bar", :path => "#{lib_path("bar")}" @@ -164,7 +164,7 @@ RSpec.describe "bundle pristine", :ruby_repo do end it "raises when one of them is not in the lockfile" do - bundle "pristine abcabcabc", :raise_on_error => false + bundle "pristine abcabcabc", raise_on_error: false expect(err).to include("Could not find gem 'abcabcabc'.") end end @@ -181,8 +181,8 @@ RSpec.describe "bundle pristine", :ruby_repo do bundle "pristine" makefile_contents = File.read(c_ext_dir.join("Makefile").to_s) - expect(makefile_contents).to match(/libpath =.*#{c_ext_dir}/) - expect(makefile_contents).to match(/LIBPATH =.*-L#{c_ext_dir}/) + expect(makefile_contents).to match(/libpath =.*#{Regexp.escape(c_ext_dir.to_s)}/) + expect(makefile_contents).to match(/LIBPATH =.*-L#{Regexp.escape(c_ext_dir.to_s)}/) end end @@ -198,14 +198,14 @@ RSpec.describe "bundle pristine", :ruby_repo do bundle "pristine" makefile_contents = File.read(c_ext_dir.join("Makefile").to_s) - expect(makefile_contents).to match(/libpath =.*#{c_ext_dir}/) - expect(makefile_contents).to match(/LIBPATH =.*-L#{c_ext_dir}/) + expect(makefile_contents).to match(/libpath =.*#{Regexp.escape(c_ext_dir.to_s)}/) + expect(makefile_contents).to match(/LIBPATH =.*-L#{Regexp.escape(c_ext_dir.to_s)}/) end end context "when BUNDLE_GEMFILE doesn't exist" do before do - bundle "pristine", :env => { "BUNDLE_GEMFILE" => "does/not/exist" }, :raise_on_error => false + bundle "pristine", env: { "BUNDLE_GEMFILE" => "does/not/exist" }, raise_on_error: false end it "shows a meaningful error" do diff --git a/spec/bundler/commands/remove_spec.rb b/spec/bundler/commands/remove_spec.rb index 170545f80c..197fcde091 100644 --- a/spec/bundler/commands/remove_spec.rb +++ b/spec/bundler/commands/remove_spec.rb @@ -7,13 +7,43 @@ RSpec.describe "bundle remove" do source "#{file_uri_for(gem_repo1)}" G - bundle "remove", :raise_on_error => false + bundle "remove", raise_on_error: false expect(err).to include("Please specify gems to remove.") end end - context "when --install flag is specified" do + context "after 'bundle install' is run" do + describe "running 'bundle remove GEM_NAME'" do + it "removes it from the lockfile" do + rack_dep = <<~L + + DEPENDENCIES + rack + + L + + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + + gem "rack" + G + + bundle "install" + + expect(lockfile).to include(rack_dep) + + bundle "remove rack" + + expect(gemfile).to eq <<~G + source "#{file_uri_for(gem_repo1)}" + G + expect(lockfile).to_not include(rack_dep) + end + end + end + + context "when --install flag is specified", bundler: "< 3" do it "removes gems from .bundle" do gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -40,27 +70,30 @@ RSpec.describe "bundle remove" do bundle "remove rack" expect(out).to include("rack was removed.") - gemfile_should_be <<-G + expect(the_bundle).to_not include_gems "rack" + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" G end context "when gem is specified in multiple lines" do it "shows success for removed gem" do + build_git "rack" + gemfile <<-G source '#{file_uri_for(gem_repo1)}' gem 'git' gem 'rack', - git: 'https://github.com/rack/rack', - branch: 'master' + git: "#{lib_path("rack-1.0")}", + branch: 'main' gem 'nokogiri' G bundle "remove rack" expect(out).to include("rack was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source '#{file_uri_for(gem_repo1)}' gem 'git' @@ -76,7 +109,7 @@ RSpec.describe "bundle remove" do source "#{file_uri_for(gem_repo1)}" G - bundle "remove rack", :raise_on_error => false + bundle "remove rack", raise_on_error: false expect(err).to include("`rack` is not specified in #{bundled_app_gemfile} so it could not be removed.") end @@ -97,7 +130,7 @@ RSpec.describe "bundle remove" do expect(out).to include("rack was removed.") expect(out).to include("rails was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" G end @@ -113,10 +146,10 @@ RSpec.describe "bundle remove" do gem "rspec" G - bundle "remove rails rack minitest", :raise_on_error => false + bundle "remove rails rack minitest", raise_on_error: false expect(err).to include("`rack` is not specified in #{bundled_app_gemfile} so it could not be removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" gem "rails" @@ -138,7 +171,7 @@ RSpec.describe "bundle remove" do bundle "remove rack" expect(out).to include("rack was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" G end @@ -158,7 +191,7 @@ RSpec.describe "bundle remove" do bundle "remove rspec" expect(out).to include("rspec was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" G end @@ -178,7 +211,7 @@ RSpec.describe "bundle remove" do bundle "remove rack" expect(out).to include("rack was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" group :test do @@ -204,7 +237,7 @@ RSpec.describe "bundle remove" do bundle "remove rspec" expect(out).to include("rspec was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" G end @@ -223,7 +256,7 @@ RSpec.describe "bundle remove" do bundle "remove rspec" expect(out).to include("rspec was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" G end @@ -246,7 +279,7 @@ RSpec.describe "bundle remove" do bundle "remove rspec" expect(out).to include("rspec was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" G end @@ -269,7 +302,7 @@ RSpec.describe "bundle remove" do bundle "remove rspec" expect(out).to include("rspec was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" G end @@ -292,7 +325,7 @@ RSpec.describe "bundle remove" do bundle "remove rspec" expect(out).to include("rspec was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" group :test do @@ -319,7 +352,7 @@ RSpec.describe "bundle remove" do bundle "remove rspec" expect(out).to include("rspec was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" group :test do @@ -340,10 +373,10 @@ RSpec.describe "bundle remove" do gem "rack"; gem "rails" G - bundle "remove rails", :raise_on_error => false + bundle "remove rails", raise_on_error: false expect(err).to include("Gems could not be removed. rack (>= 0) would also have been removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" gem "rack"; gem "rails" G @@ -352,7 +385,7 @@ RSpec.describe "bundle remove" do context "when some gems could not be removed" do it "shows warning for gems not removed and success for those removed" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem"rack" gem"rspec" @@ -365,7 +398,7 @@ RSpec.describe "bundle remove" do expect(out).to include("rails was removed.") expect(out).to include("minitest was removed.") expect(out).to include("rack, rspec could not be removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" gem"rack" gem"rspec" @@ -397,7 +430,7 @@ RSpec.describe "bundle remove" do bundle "remove rspec" expect(out).to include("rspec was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" gem "rack" @@ -458,7 +491,7 @@ RSpec.describe "bundle remove" do eval_gemfile "Gemfile-other" G - bundle "remove rack", :raise_on_error => false + bundle "remove rack", raise_on_error: false expect(err).to include("`rack` is not specified in #{bundled_app_gemfile} so it could not be removed.") end @@ -477,11 +510,11 @@ RSpec.describe "bundle remove" do gem "rack" G - bundle "remove rack", :raise_on_error => false + bundle "remove rack", raise_on_error: false expect(out).to include("rack was removed.") expect(err).to include("`rack` is not specified in #{bundled_app("Gemfile-other")} so it could not be removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" eval_gemfile "Gemfile-other" @@ -489,7 +522,7 @@ RSpec.describe "bundle remove" do end end - context "when gems can not be removed from other gemfile" do + context "when gems cannot be removed from other gemfile" do it "shows error" do create_file "Gemfile-other", <<-G gem "rails"; gem "rack" @@ -502,11 +535,11 @@ RSpec.describe "bundle remove" do gem "rack" G - bundle "remove rack", :raise_on_error => false + bundle "remove rack", raise_on_error: false expect(out).to include("rack was removed.") expect(err).to include("Gems could not be removed. rails (>= 0) would also have been removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" eval_gemfile "Gemfile-other" @@ -527,11 +560,11 @@ RSpec.describe "bundle remove" do gem "rails"; gem "rack" G - bundle "remove rack", :raise_on_error => false + bundle "remove rack", raise_on_error: false expect(err).to include("Gems could not be removed. rails (>= 0) would also have been removed.") expect(bundled_app("Gemfile-other").read).to include("gem \"rack\"") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" eval_gemfile "Gemfile-other" @@ -541,7 +574,7 @@ RSpec.describe "bundle remove" do end context "when gem present in gemfiles but could not be removed from one from one of them" do - it "removes gem which can be removed and shows warning for file from which it can not be removed" do + it "removes gem which can be removed and shows warning for file from which it cannot be removed" do create_file "Gemfile-other", <<-G gem "rack" G @@ -574,7 +607,7 @@ RSpec.describe "bundle remove" do bundle "remove rack" expect(out).to include("rack was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" G end @@ -593,7 +626,7 @@ RSpec.describe "bundle remove" do bundle "remove rack" expect(out).to include("rack was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" G end @@ -601,7 +634,7 @@ RSpec.describe "bundle remove" do context "with gemspec" do it "should not remove the gem" do - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp.join("foo")) do |s| s.write("foo.gemspec", "") s.add_dependency "rack" end @@ -630,7 +663,7 @@ RSpec.describe "bundle remove" do bundle "remove rack" expect(out).to include("rack was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" # gem "rack" might be used in the future @@ -649,7 +682,7 @@ RSpec.describe "bundle remove" do bundle "remove rack" expect(out).to include("rack was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" G end @@ -668,7 +701,7 @@ RSpec.describe "bundle remove" do expect(out).to_not include("puma was removed.") expect(out).to include("rack was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" gem "puma" # implements interface provided by gem "rack" G @@ -688,7 +721,7 @@ RSpec.describe "bundle remove" do expect(out).to include("puma was removed.") expect(out).to_not include("rack was removed.") - gemfile_should_be <<-G + expect(gemfile).to eq <<~G source "#{file_uri_for(gem_repo1)}" gem "rack" diff --git a/spec/bundler/commands/show_spec.rb b/spec/bundler/commands/show_spec.rb index 2adb121616..2b6d4d2d00 100644 --- a/spec/bundler/commands/show_spec.rb +++ b/spec/bundler/commands/show_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe "bundle show", :bundler => "< 3" do +RSpec.describe "bundle show", bundler: "< 3" do context "with a standard Gemfile" do before :each do install_gemfile <<-G @@ -50,14 +50,14 @@ RSpec.describe "bundle show", :bundler => "< 3" do end it "complains if gem not in bundle" do - bundle "show missing", :raise_on_error => false + bundle "show missing", raise_on_error: false expect(err).to match(/could not find gem 'missing'/i) end it "prints path of all gems in bundle sorted by name" do bundle "show --paths" - expect(out).to include(default_bundle_path("gems", "rake-13.0.1").to_s) + expect(out).to include(default_bundle_path("gems", "rake-#{rake_version}").to_s) expect(out).to include(default_bundle_path("gems", "rails-2.3.2").to_s) # Gem names are the last component of their path. @@ -100,11 +100,11 @@ RSpec.describe "bundle show", :bundler => "< 3" do expect(the_bundle).to include_gems "foo 1.0" bundle :show - expect(out).to include("foo (1.0 #{@git.ref_for("master", 6)}") + expect(out).to include("foo (1.0 #{@git.ref_for("main", 6)}") end - it "prints out branch names other than master" do - update_git "foo", :branch => "omg" do |s| + it "prints out branch names other than main" do + update_git "foo", branch: "omg" do |s| s.write "lib/foo.rb", "FOO = '1.0.omg'" end @revision = revision_for(lib_path("foo-1.0"))[0...6] @@ -129,7 +129,7 @@ RSpec.describe "bundle show", :bundler => "< 3" do end it "handles when a version is a '-' prerelease" do - @git = build_git("foo", "1.0.0-beta.1", :path => lib_path("foo")) + @git = build_git("foo", "1.0.0-beta.1", path: lib_path("foo")) install_gemfile <<-G gem "foo", "1.0.0-beta.1", :git => "#{lib_path("foo")}" G @@ -142,13 +142,13 @@ RSpec.describe "bundle show", :bundler => "< 3" do context "in a fresh gem in a blank git repo" do before :each do - build_git "foo", :path => lib_path("foo") + build_git "foo", path: lib_path("foo") File.open(lib_path("foo/Gemfile"), "w") {|f| f.puts "gemspec" } - sys_exec "rm -rf .git && git init", :dir => lib_path("foo") + sys_exec "rm -rf .git && git init", dir: lib_path("foo") end it "does not output git errors" do - bundle :show, :dir => lib_path("foo") + bundle :show, dir: lib_path("foo") expect(err_without_deprecations).to be_empty end end @@ -173,7 +173,7 @@ RSpec.describe "bundle show", :bundler => "< 3" do G bundle "show rac" - expect(out).to match(/\A1 : rack\n2 : rack-obama\n0 : - exit -(\n>)?\z/) + expect(out).to match(/\A1 : rack\n2 : rack-obama\n0 : - exit -(\n>.*)?\z/) end end @@ -186,7 +186,7 @@ RSpec.describe "bundle show", :bundler => "< 3" do invalid_regexp = "[]" - bundle "show #{invalid_regexp}", :raise_on_error => false + bundle "show #{invalid_regexp}", raise_on_error: false expect(err).to include("Could not find gem '#{invalid_regexp}'.") end end @@ -219,6 +219,6 @@ RSpec.describe "bundle show", :bundler => "< 3" do end end -RSpec.describe "bundle show", :bundler => "3" do +RSpec.describe "bundle show", bundler: "3" do pending "shows a friendly error about the command removal" end diff --git a/spec/bundler/commands/update_spec.rb b/spec/bundler/commands/update_spec.rb index 14fe3e245b..cfb86ebb54 100644 --- a/spec/bundler/commands/update_spec.rb +++ b/spec/bundler/commands/update_spec.rb @@ -34,7 +34,7 @@ RSpec.describe "bundle update" do gem "rack-obama" exit! G - bundle "update", :raise_on_error => false + bundle "update", raise_on_error: false expect(bundled_app_lock).to exist end end @@ -60,7 +60,7 @@ RSpec.describe "bundle update" do build_gem "activesupport", "3.0" end - bundle "update", :all => true + bundle "update", all: true expect(out).to include("Bundle updated!") expect(the_bundle).to include_gems "rack 1.2", "rack-obama 1.0", "activesupport 3.0" end @@ -74,7 +74,7 @@ RSpec.describe "bundle update" do gem "rack-obama" exit! G - bundle "update", :all => true, :raise_on_error => false + bundle "update", all: true, raise_on_error: false expect(bundled_app_lock).to exist end end @@ -86,7 +86,7 @@ RSpec.describe "bundle update" do gem "rack", "1.0" G - bundle "update --gemfile OmgFile", :all => true + bundle "update --gemfile OmgFile", all: true expect(bundled_app("OmgFile.lock")).to exist end @@ -97,13 +97,13 @@ RSpec.describe "bundle update" do it "errors when passed nothing" do install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" - bundle :update, :raise_on_error => false + bundle :update, raise_on_error: false expect(err).to eq("To update everything, pass the `--all` flag.") end it "errors when passed --all and another option" do install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" - bundle "update --all foo", :raise_on_error => false + bundle "update --all foo", raise_on_error: false expect(err).to eq("Cannot specify --all along with specific options.") end @@ -171,11 +171,11 @@ RSpec.describe "bundle update" do end it "should inform the user" do - bundle "update halting-problem-solver", :raise_on_error => false + bundle "update halting-problem-solver", raise_on_error: false expect(err).to include "Could not find gem 'halting-problem-solver'" end it "should suggest alternatives" do - bundle "update platformspecific", :raise_on_error => false + bundle "update platformspecific", raise_on_error: false expect(err).to include "Did you mean platform_specific?" end end @@ -236,7 +236,7 @@ RSpec.describe "bundle update" do end end - bundle "update", :all => true + bundle "update", all: true expect(the_bundle).to include_gems("slim 3.0.9", "slim-rails 3.1.3", "slim_lint 0.16.1") end @@ -275,6 +275,11 @@ RSpec.describe "bundle update" do gem "countries" G + checksums = checksums_section_when_existing do |c| + c.checksum(gem_repo4, "countries", "3.1.0") + c.checksum(gem_repo4, "country_select", "5.1.0") + end + lockfile <<~L GEM remote: #{file_uri_for(gem_repo4)}/ @@ -284,23 +289,145 @@ RSpec.describe "bundle update" do countries (~> 3.0) PLATFORMS - #{specific_local_platform} + #{local_platform} DEPENDENCIES countries country_select - + #{checksums} BUNDLED WITH #{Bundler::VERSION} L previous_lockfile = lockfile - bundle "lock --update" + bundle "lock --update", env: { "DEBUG" => "1" }, verbose: true expect(lockfile).to eq(previous_lockfile) end + it "does not downgrade direct dependencies when run with --conservative" do + build_repo4 do + build_gem "oauth2", "2.0.6" do |s| + s.add_dependency "faraday", ">= 0.17.3", "< 3.0" + end + + build_gem "oauth2", "1.4.10" do |s| + s.add_dependency "faraday", ">= 0.17.3", "< 3.0" + s.add_dependency "multi_json", "~> 1.3" + end + + build_gem "faraday", "2.5.2" + + build_gem "multi_json", "1.15.0" + + build_gem "quickbooks-ruby", "1.0.19" do |s| + s.add_dependency "oauth2", "~> 1.4" + end + + build_gem "quickbooks-ruby", "0.1.9" do |s| + s.add_dependency "oauth2" + end + end + + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + + gem "oauth2" + gem "quickbooks-ruby" + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + faraday (2.5.2) + multi_json (1.15.0) + oauth2 (1.4.10) + faraday (>= 0.17.3, < 3.0) + multi_json (~> 1.3) + quickbooks-ruby (1.0.19) + oauth2 (~> 1.4) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + oauth2 + quickbooks-ruby + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "update --conservative --verbose" + + expect(out).not_to include("Installing quickbooks-ruby 0.1.9") + expect(out).to include("Installing quickbooks-ruby 1.0.19").and include("Installing oauth2 1.4.10") + end + + it "does not downgrade direct dependencies when using gemspec sources" do + create_file("rails.gemspec", <<-G) + Gem::Specification.new do |gem| + gem.name = "rails" + gem.version = "7.1.0.alpha" + gem.author = "DHH" + gem.summary = "Full-stack web application framework." + end + G + + build_repo4 do + build_gem "rake", "12.3.3" + build_gem "rake", "13.0.6" + + build_gem "sneakers", "2.11.0" do |s| + s.add_dependency "rake" + end + + build_gem "sneakers", "2.12.0" do |s| + s.add_dependency "rake", "~> 12.3" + end + end + + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + + gemspec + + gem "rake" + gem "sneakers" + G + + lockfile <<~L + PATH + remote: . + specs: + + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + rake (13.0.6) + sneakers (2.11.0) + rake + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rake + sneakers + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "update --verbose" + + expect(out).not_to include("Installing sneakers 2.12.0") + expect(out).not_to include("Installing rake 12.3.3") + expect(out).to include("Installing sneakers 2.11.0").and include("Installing rake 13.0.6") + end + it "does not downgrade indirect dependencies unnecessarily" do build_repo4 do build_gem "a" do |s| @@ -312,7 +439,7 @@ RSpec.describe "bundle update" do build_gem "c", "2.0" end - install_gemfile <<-G, :verbose => true + install_gemfile <<-G, verbose: true source "#{file_uri_for(gem_repo4)}" gem "a" G @@ -325,7 +452,7 @@ RSpec.describe "bundle update" do end end - bundle "update", :all => true, :verbose => true + bundle "update", all: true, verbose: true expect(the_bundle).to include_gems("a 1.0", "b 1.0", "c 2.0") end @@ -354,18 +481,112 @@ RSpec.describe "bundle update" do expect(the_bundle).to include_gems("a 1.0", "b 1.0") end + + it "should still downgrade if forced by the Gemfile, when transitive dependencies also need downgrade" do + build_repo4 do + build_gem "activesupport", "6.1.4.1" do |s| + s.add_dependency "tzinfo", "~> 2.0" + end + + build_gem "activesupport", "6.0.4.1" do |s| + s.add_dependency "tzinfo", "~> 1.1" + end + + build_gem "tzinfo", "2.0.4" + build_gem "tzinfo", "1.2.9" + end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "activesupport", "~> 6.1.0" + G + + expect(the_bundle).to include_gems("activesupport 6.1.4.1", "tzinfo 2.0.4") + + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "activesupport", "~> 6.0.0" + G + + original_lockfile = lockfile + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "activesupport", "6.0.4.1" + c.checksum gem_repo4, "tzinfo", "1.2.9" + end + + expected_lockfile = <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + activesupport (6.0.4.1) + tzinfo (~> 1.1) + tzinfo (1.2.9) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + activesupport (~> 6.0.0) + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "update activesupport" + expect(the_bundle).to include_gems("activesupport 6.0.4.1", "tzinfo 1.2.9") + expect(lockfile).to eq(expected_lockfile) + + # needed because regressing to versions already present on the system + # won't add a checksum + expected_lockfile = remove_checksums_from_lockfile(expected_lockfile) + + lockfile original_lockfile + bundle "update" + expect(the_bundle).to include_gems("activesupport 6.0.4.1", "tzinfo 1.2.9") + expect(lockfile).to eq(expected_lockfile) + + lockfile original_lockfile + bundle "lock --update" + expect(the_bundle).to include_gems("activesupport 6.0.4.1", "tzinfo 1.2.9") + expect(lockfile).to eq(expected_lockfile) + end end describe "with --local option" do before do build_repo2 - install_gemfile <<-G + gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "activesupport" gem "rack-obama" gem "platform_specific" G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo2)}/ + specs: + activesupport (2.3.5) + platform_specific (1.0-#{local_platform}) + rack (1.0.0) + rack-obama (1.0) + rack + + PLATFORMS + #{local_platform} + + DEPENDENCIES + activesupport + platform_specific + rack-obama + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" end it "doesn't hit repo2" do @@ -418,7 +639,7 @@ RSpec.describe "bundle update" do context "when there is a source with the same name as a gem in a group" do before do - build_git "foo", :path => lib_path("activesupport") + build_git "foo", path: lib_path("activesupport") install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "activesupport", :group => :development @@ -428,7 +649,7 @@ RSpec.describe "bundle update" do it "should not update the gems from that source" do update_repo2 { build_gem "activesupport", "3.0" } - update_git "foo", "2.0", :path => lib_path("activesupport") + update_git "foo", "2.0", path: lib_path("activesupport") bundle "update --group development" expect(the_bundle).to include_gems "activesupport 3.0" @@ -470,27 +691,33 @@ RSpec.describe "bundle update" do G end - it "should fail loudly", :bundler => "< 3" do + it "should fail loudly", bundler: "< 3" do bundle "install --deployment" - bundle "update", :all => true, :raise_on_error => false + bundle "update", all: true, raise_on_error: false expect(last_command).to be_failure - expect(err).to match(/You are trying to install in deployment mode after changing.your Gemfile/m) - expect(err).to match(/freeze \nby running `bundle config unset deployment`./m) + expect(err).to match(/Bundler is unlocking, but the lockfile can't be updated because frozen mode is set/) + expect(err).to match(/freeze by running `bundle config set frozen false`./) end - it "should suggest different command when frozen is set globally", :bundler => "< 3" do + it "should fail loudly when frozen is set globally" do bundle "config set --global frozen 1" - bundle "update", :all => true, :raise_on_error => false - expect(err).to match(/You are trying to install in deployment mode after changing.your Gemfile/m). - and match(/freeze \nby running `bundle config unset frozen`./m) + bundle "update", all: true, raise_on_error: false + expect(err).to match(/Bundler is unlocking, but the lockfile can't be updated because frozen mode is set/). + and match(/freeze by running `bundle config set frozen false`./) end - it "should suggest different command when frozen is set globally", :bundler => "3" do + it "should fail loudly when deployment is set globally" do bundle "config set --global deployment true" - bundle "update", :all => true, :raise_on_error => false - expect(err).to match(/You are trying to install in deployment mode after changing.your Gemfile/m). - and match(/freeze \nby running `bundle config unset deployment`./m) + bundle "update", all: true, raise_on_error: false + expect(err).to match(/Bundler is unlocking, but the lockfile can't be updated because frozen mode is set/). + and match(/freeze by running `bundle config set frozen false`./) + end + + it "should not suggest any command to unfreeze bundler if frozen is set through ENV" do + bundle "update", all: true, raise_on_error: false, env: { "BUNDLE_FROZEN" => "true" } + expect(err).to match(/Bundler is unlocking, but the lockfile can't be updated because frozen mode is set/) + expect(err).not_to match(/by running/) end end @@ -583,7 +810,7 @@ RSpec.describe "bundle update" do end end - it "shows the previous version of the gem when updated from rubygems source", :bundler => "< 3" do + it "shows the previous version of the gem when updated from rubygems source" do build_repo2 install_gemfile <<-G @@ -591,43 +818,39 @@ RSpec.describe "bundle update" do gem "activesupport" G - bundle "update", :all => true + bundle "update", all: true, verbose: true expect(out).to include("Using activesupport 2.3.5") update_repo2 do build_gem "activesupport", "3.0" end - bundle "update", :all => true + bundle "update", all: true expect(out).to include("Installing activesupport 3.0 (was 2.3.5)") end - context "with suppress_install_using_messages set" do - before { bundle "config set suppress_install_using_messages true" } - - it "only prints `Using` for versions that have changed" do - build_repo4 do - build_gem "bar" - build_gem "foo" - end - - install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" - gem "bar" - gem "foo" - G + it "only prints `Using` for versions that have changed" do + build_repo4 do + build_gem "bar" + build_gem "foo" + end - bundle "update", :all => true - expect(out).to match(/Resolving dependencies\.\.\.\.*\nBundle updated!/) + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "bar" + gem "foo" + G - update_repo4 do - build_gem "foo", "2.0" - end + bundle "update", all: true + expect(out).to match(/Resolving dependencies\.\.\.\.*\nBundle updated!/) - bundle "update", :all => true - out.sub!("Removing foo (1.0)\n", "") - expect(out).to match(/Resolving dependencies\.\.\.\.*\nFetching foo 2\.0 \(was 1\.0\)\nInstalling foo 2\.0 \(was 1\.0\)\nBundle updated/) + update_repo4 do + build_gem "foo", "2.0" end + + bundle "update", all: true + out.sub!("Removing foo (1.0)\n", "") + expect(out).to match(/Resolving dependencies\.\.\.\.*\nFetching foo 2\.0 \(was 1\.0\)\nInstalling foo 2\.0 \(was 1\.0\)\nBundle updated/) end it "shows error message when Gemfile.lock is not preset and gem is specified" do @@ -636,12 +859,96 @@ RSpec.describe "bundle update" do gem "activesupport" G - bundle "update nonexisting", :raise_on_error => false + bundle "update nonexisting", raise_on_error: false expect(err).to include("This Bundle hasn't been installed yet. Run `bundle install` to update and install the bundled gems.") expect(exitstatus).to eq(22) end - context "with multiple, duplicated sources, with lockfile in old format", :bundler => "< 3" do + context "with multiple sources and caching enabled" do + before do + build_repo2 do + build_gem "rack", "1.0.0" + + build_gem "request_store", "1.0.0" do |s| + s.add_dependency "rack", "1.0.0" + end + end + + build_repo4 do + # set up repo with no gems + end + + gemfile <<~G + source "#{file_uri_for(gem_repo2)}" + + gem "request_store" + + source "#{file_uri_for(gem_repo4)}" do + end + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo2)}/ + specs: + rack (1.0.0) + request_store (1.0.0) + rack (= 1.0.0) + + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + + PLATFORMS + #{local_platform} + + DEPENDENCIES + request_store + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "works" do + bundle :install + bundle :cache + + update_repo2 do + build_gem "request_store", "1.1.0" do |s| + s.add_dependency "rack", "1.0.0" + end + end + + bundle "update request_store" + + expect(out).to include("Bundle updated!") + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo2)}/ + specs: + rack (1.0.0) + request_store (1.1.0) + rack (= 1.0.0) + + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + + PLATFORMS + #{local_platform} + + DEPENDENCIES + request_store + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "with multiple, duplicated sources, with lockfile in old format", bundler: "< 3" do before do build_repo2 do build_gem "dotenv", "2.7.6" @@ -682,7 +989,7 @@ RSpec.describe "bundle update" do vcr (6.0.0) PLATFORMS - #{specific_local_platform} + #{local_platform} DEPENDENCIES dotenv @@ -695,8 +1002,8 @@ RSpec.describe "bundle update" do end it "works" do - bundle :install, :artifice => "compact_index" - bundle "update oj", :artifice => "compact_index" + bundle :install, artifice: "compact_index" + bundle "update oj", artifice: "compact_index" expect(out).to include("Bundle updated!") expect(the_bundle).to include_gems "oj 3.11.5" @@ -832,7 +1139,7 @@ RSpec.describe "bundle update in more complicated situations" do end it "allows updating" do - bundle :update, :all => true + bundle :update, all: true expect(the_bundle).to include_gem "a 1.1" end @@ -888,7 +1195,7 @@ RSpec.describe "bundle update without a Gemfile.lock" do gem "rack", "1.0" G - bundle "update", :all => true + bundle "update", all: true expect(the_bundle).to include_gems "rack 1.0.0" end @@ -911,7 +1218,7 @@ RSpec.describe "bundle update when a gem depends on a newer version of bundler" end it "should explain that bundler conflicted and how to resolve the conflict" do - bundle "update", :all => true, :raise_on_error => false + bundle "update", all: true, raise_on_error: false expect(last_command.stdboth).not_to match(/in snapshot/i) expect(err).to match(/current Bundler version/i). and match(/Install the necessary version with `gem install bundler:#{Bundler::VERSION.succ}`/i) @@ -919,27 +1226,22 @@ RSpec.describe "bundle update when a gem depends on a newer version of bundler" end RSpec.describe "bundle update --ruby" do - before do - install_gemfile <<-G - ::RUBY_VERSION = '2.1.3' - ::RUBY_PATCHLEVEL = 100 - ruby '~> 2.1.0' - source "#{file_uri_for(gem_repo1)}" - G - end - context "when the Gemfile removes the ruby" do before do + install_gemfile <<-G + ruby '~> #{Gem.ruby_version}' + source "#{file_uri_for(gem_repo1)}" + G + gemfile <<-G - ::RUBY_VERSION = '2.1.4' - ::RUBY_PATCHLEVEL = 222 - source "#{file_uri_for(gem_repo1)}" + source "#{file_uri_for(gem_repo1)}" G end + it "removes the Ruby from the Gemfile.lock" do bundle "update --ruby" - lockfile_should_be <<-L + expect(lockfile).to eq <<~L GEM remote: #{file_uri_for(gem_repo1)}/ specs: @@ -957,64 +1259,89 @@ RSpec.describe "bundle update --ruby" do context "when the Gemfile specified an updated Ruby version" do before do + install_gemfile <<-G + ruby '~> #{Gem.ruby_version}' + source "#{file_uri_for(gem_repo1)}" + G + gemfile <<-G - ::RUBY_VERSION = '2.1.4' - ::RUBY_PATCHLEVEL = 222 - ruby '~> 2.1.0' - source "#{file_uri_for(gem_repo1)}" + ruby '~> #{current_ruby_minor}' + source "#{file_uri_for(gem_repo1)}" G end + it "updates the Gemfile.lock with the latest version" do bundle "update --ruby" - lockfile_should_be <<-L - GEM - remote: #{file_uri_for(gem_repo1)}/ - specs: + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: - PLATFORMS - #{lockfile_platforms} + PLATFORMS + #{lockfile_platforms} - DEPENDENCIES + DEPENDENCIES - RUBY VERSION - ruby 2.1.4p222 + RUBY VERSION + #{Bundler::RubyVersion.system} - BUNDLED WITH - #{Bundler::VERSION} + BUNDLED WITH + #{Bundler::VERSION} L end end context "when a different Ruby is being used than has been versioned" do before do + install_gemfile <<-G + ruby '~> #{Gem.ruby_version}' + source "#{file_uri_for(gem_repo1)}" + G + gemfile <<-G - ::RUBY_VERSION = '2.2.2' - ::RUBY_PATCHLEVEL = 505 ruby '~> 2.1.0' source "#{file_uri_for(gem_repo1)}" G end it "shows a helpful error message" do - bundle "update --ruby", :raise_on_error => false + bundle "update --ruby", raise_on_error: false - expect(err).to include("Your Ruby version is 2.2.2, but your Gemfile specified ~> 2.1.0") + expect(err).to include("Your Ruby version is #{Bundler::RubyVersion.system.gem_version}, but your Gemfile specified ~> 2.1.0") end end context "when updating Ruby version and Gemfile `ruby`" do before do + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + + CHECKSUMS + + RUBY VERSION + ruby 2.1.4p222 + + BUNDLED WITH + #{Bundler::VERSION} + L + gemfile <<-G - ::RUBY_VERSION = '1.8.3' - ::RUBY_PATCHLEVEL = 55 - ruby '~> 1.8.0' + ruby '~> #{Gem.ruby_version}' source "#{file_uri_for(gem_repo1)}" G end + it "updates the Gemfile.lock with the latest version" do bundle "update --ruby" - lockfile_should_be <<-L + expect(lockfile).to eq <<~L GEM remote: #{file_uri_for(gem_repo1)}/ specs: @@ -1024,8 +1351,10 @@ RSpec.describe "bundle update --ruby" do DEPENDENCIES + CHECKSUMS + RUBY VERSION - ruby 1.8.3p55 + #{Bundler::RubyVersion.system} BUNDLED WITH #{Bundler::VERSION} @@ -1035,24 +1364,355 @@ RSpec.describe "bundle update --ruby" do end RSpec.describe "bundle update --bundler" do - it "updates the bundler version in the lockfile without re-resolving" do + it "updates the bundler version in the lockfile" do build_repo4 do build_gem "rack", "1.0" end + checksums = checksums_section_when_existing do |c| + c.checksum(gem_repo4, "rack", "1.0") + end + install_gemfile <<-G source "#{file_uri_for(gem_repo4)}" gem "rack" G - allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + rack (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rack + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, '\11.0.0\2') - FileUtils.rm_r gem_repo4 + bundle :update, bundler: true, artifice: "compact_index", verbose: true + expect(out).to include("Using bundler #{Bundler::VERSION}") + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + rack (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rack + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L - bundle :update, :bundler => true, :verbose => true expect(the_bundle).to include_gem "rack 1.0" + end + + it "updates the bundler version in the lockfile without re-resolving if the highest version is already installed" do + system_gems "bundler-2.3.9" + + build_repo4 do + build_gem "rack", "1.0" + end - expect(the_bundle.locked_gems.bundler_version).to eq v(Bundler::VERSION) + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "rack" + G + lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, "2.3.9") + + checksums = checksums_section_when_existing do |c| + c.checksum(gem_repo4, "rack", "1.0") + end + + bundle :update, bundler: true, artifice: "compact_index", verbose: true + expect(out).to include("Using bundler #{Bundler::VERSION}") + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + rack (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rack + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + expect(the_bundle).to include_gem "rack 1.0" + end + + it "updates the bundler version in the lockfile even if the latest version is not installed", :ruby_repo do + pristine_system_gems "bundler-2.3.9" + + build_repo4 do + build_gem "rack", "1.0" + + build_bundler "999.0.0" + end + + install_gemfile <<-G, artifice: nil, env: { "BUNDLER_IGNORE_DEFAULT_GEM" => "true" } + source "#{file_uri_for(gem_repo4)}" + gem "rack" + G + lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, "2.3.9") + + bundle :update, bundler: true, artifice: "compact_index", verbose: true, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + + # Only updates properly on modern RubyGems. + + if Gem.rubygems_version >= Gem::Version.new("3.3.0.dev") + expect(out).to include("Updating bundler to 999.0.0") + expect(out).to include("Using bundler 999.0.0") + expect(out).not_to include("Installing Bundler 2.3.9 and restarting using that version.") + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + rack (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rack + + BUNDLED WITH + 999.0.0 + L + + expect(the_bundle).to include_gems "bundler 999.0.0" + expect(the_bundle).to include_gems "rack 1.0" + else + # Old RubyGems versions do not trampoline but they still change BUNDLED + # WITH to the latest bundler version. This means the below check fails + # because it tries to use bundler 999.0.0 which did not get installed. + # Workaround the bug by forcing the version we know is installed. + expect(the_bundle).to include_gems "rack 1.0", env: { "BUNDLER_VERSION" => "2.3.9" } + end + end + + it "does not claim to update to Bundler version to a wrong version when cached gems are present" do + pristine_system_gems "bundler-2.99.0" + + build_repo4 do + build_gem "rack", "3.0.9.1" + + build_bundler "2.99.0" + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + gem "rack" + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + rack (3.0.9.1) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rack + + BUNDLED WITH + 2.99.0 + L + + bundle :cache, verbose: true + + bundle :update, bundler: true, artifice: "compact_index", verbose: true, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + + expect(out).not_to include("Updating bundler to") + end + + it "does not update the bundler version in the lockfile if the latest version is not compatible with current ruby", :ruby_repo do + pristine_system_gems "bundler-2.3.9" + + build_repo4 do + build_gem "rack", "1.0" + + build_bundler "2.3.9" + build_bundler "999.0.0" do |s| + s.required_ruby_version = "> #{Gem.ruby_version}" + end + end + + install_gemfile <<-G, env: { "BUNDLER_IGNORE_DEFAULT_GEM" => "true" } + source "#{file_uri_for(gem_repo4)}" + gem "rack" + G + lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, "2.3.9") + + bundle :update, bundler: true, artifice: "compact_index", verbose: true, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s, "BUNDLER_IGNORE_DEFAULT_GEM" => "true" } + + expect(out).to include("Using bundler 2.3.9") + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + rack (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rack + + BUNDLED WITH + 2.3.9 + L + + expect(the_bundle).to include_gems "bundler 2.3.9" + expect(the_bundle).to include_gems "rack 1.0" + end + + it "errors if the explicit target version does not exist" do + pristine_system_gems "bundler-2.3.9" + + build_repo4 do + build_gem "rack", "1.0" + end + + install_gemfile <<-G, env: { "BUNDLER_IGNORE_DEFAULT_GEM" => "true" } + source "#{file_uri_for(gem_repo4)}" + gem "rack" + G + lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, "2.3.9") + + bundle :update, bundler: "999.999.999", artifice: "compact_index", raise_on_error: false + + # Only gives a meaningful error message on modern RubyGems. + + if Gem.rubygems_version >= Gem::Version.new("3.3.0.dev") + expect(last_command).to be_failure + expect(err).to include("The `bundle update --bundler` target version (999.999.999) does not exist") + end + end + + it "allows updating to development versions if already installed locally" do + system_gems "bundler-2.3.0.dev" + + build_repo4 do + build_gem "rack", "1.0" + end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "rack" + G + + bundle :update, bundler: "2.3.0.dev", verbose: "true" + + # Only updates properly on modern RubyGems. + if Gem.rubygems_version >= Gem::Version.new("3.3.0.dev") + checksums = checksums_section_when_existing do |c| + c.checksum(gem_repo4, "rack", "1.0") + end + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + rack (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rack + #{checksums} + BUNDLED WITH + 2.3.0.dev + L + + expect(out).to include("Using bundler 2.3.0.dev") + end + end + + it "does not touch the network if not necessary" do + system_gems "bundler-2.3.9" + + build_repo4 do + build_gem "rack", "1.0" + end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "rack" + G + + bundle :update, bundler: "2.3.9", verbose: true + + expect(out).not_to include("Fetching gem metadata from https://rubygems.org/") + + # Only updates properly on modern RubyGems. + checksums = checksums_section_when_existing do |c| + c.checksum(gem_repo4, "rack", "1.0") + end + + if Gem.rubygems_version >= Gem::Version.new("3.3.0.dev") + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + rack (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rack + #{checksums} + BUNDLED WITH + 2.3.9 + L + + expect(out).to include("Using bundler 2.3.9") + end + end + + it "prints an error when trying to update bundler in frozen mode" do + system_gems "bundler-2.3.9" + + gemfile <<~G + source "#{file_uri_for(gem_repo2)}" + G + + lockfile <<-L + GEM + remote: #{file_uri_for(gem_repo2)}/ + specs: + + PLATFORMS + ruby + + DEPENDENCIES + + BUNDLED WITH + 2.1.4 + L + + bundle "update --bundler=2.3.9", env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false + expect(err).to include("An update to the version of bundler itself was requested, but the lockfile can't be updated because frozen mode is set") end end @@ -1070,7 +1730,10 @@ RSpec.describe "bundle update conservative" 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 @@ -1108,7 +1771,7 @@ RSpec.describe "bundle update conservative" do end it "update all" do - bundle "update --patch", :all => true + bundle "update --patch", all: true expect(the_bundle).to include_gems "foo 1.4.5", "bar 2.1.1", "qux 1.0.1" end @@ -1130,11 +1793,37 @@ RSpec.describe "bundle update conservative" do end it "minor preferred" do - bundle "update --minor --strict", :all => true + bundle "update --minor --strict", all: true expect(the_bundle).to include_gems "foo 1.5.0", "bar 2.1.1", "qux 1.1.0" end end + + context "pre" do + it "defaults to major" do + bundle "update --pre foo bar" + + expect(the_bundle).to include_gems "foo 2.0.0.pre", "bar 4.0.0.pre", "qux 1.0.0" + end + + it "patch preferred" do + bundle "update --patch --pre foo bar" + + expect(the_bundle).to include_gems "foo 1.4.5", "bar 2.1.2.pre", "qux 1.0.0" + end + + it "minor preferred" do + bundle "update --minor --pre foo bar" + + expect(the_bundle).to include_gems "foo 1.5.1", "bar 3.1.0.pre", "qux 1.0.0" + end + + it "major preferred" do + bundle "update --major --pre foo bar" + + expect(the_bundle).to include_gems "foo 2.0.0.pre", "bar 4.0.0.pre", "qux 1.0.0" + end + end end context "eager unlocking" do @@ -1176,13 +1865,15 @@ RSpec.describe "bundle update conservative" do shared_dep (~> 5.0) PLATFORMS - #{specific_local_platform} + #{local_platform} DEPENDENCIES isolated_owner shared_owner_a shared_owner_b + CHECKSUMS + BUNDLED WITH #{Bundler::VERSION} L @@ -1229,13 +1920,20 @@ RSpec.describe "bundle update conservative" do shared_dep (~> 5.0) PLATFORMS - #{specific_local_platform} + #{local_platform} DEPENDENCIES isolated_owner shared_owner_a shared_owner_b + CHECKSUMS + isolated_dep (2.0.1) + isolated_owner (1.0.2) + shared_dep (5.0.1) + shared_owner_a (3.0.2) + shared_owner_b (4.0.2) + BUNDLED WITH #{Bundler::VERSION} L @@ -1262,7 +1960,7 @@ RSpec.describe "bundle update conservative" do end it "raises if too many flags are provided" do - bundle "update --patch --minor", :all => true, :raise_on_error => false + bundle "update --patch --minor", all: true, raise_on_error: false expect(err).to eq "Provide only one of the following options: minor, patch" end diff --git a/spec/bundler/commands/version_spec.rb b/spec/bundler/commands/version_spec.rb index 53d545f5fa..307058a5dd 100644 --- a/spec/bundler/commands/version_spec.rb +++ b/spec/bundler/commands/version_spec.rb @@ -4,42 +4,42 @@ require_relative "../support/path" RSpec.describe "bundle version" do if Spec::Path.ruby_core? - COMMIT_HASH = /unknown|[a-fA-F0-9]{7,}/.freeze + COMMIT_HASH = /unknown|[a-fA-F0-9]{7,}/ else - COMMIT_HASH = /[a-fA-F0-9]{7,}/.freeze + COMMIT_HASH = /[a-fA-F0-9]{7,}/ end context "with -v" do - it "outputs the version", :bundler => "< 3" do + it "outputs the version", bundler: "< 3" do bundle "-v" expect(out).to eq("Bundler version #{Bundler::VERSION}") end - it "outputs the version", :bundler => "3" do + it "outputs the version", bundler: "3" do bundle "-v" expect(out).to eq(Bundler::VERSION) end end context "with --version" do - it "outputs the version", :bundler => "< 3" do + it "outputs the version", bundler: "< 3" do bundle "--version" expect(out).to eq("Bundler version #{Bundler::VERSION}") end - it "outputs the version", :bundler => "3" do + it "outputs the version", bundler: "3" do bundle "--version" expect(out).to eq(Bundler::VERSION) end end context "with version" do - it "outputs the version with build metadata", :bundler => "< 3" do + it "outputs the version with build metadata", bundler: "< 3" do bundle "version" expect(out).to match(/\ABundler version #{Regexp.escape(Bundler::VERSION)} \(\d{4}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/) end - it "outputs the version with build metadata", :bundler => "3" do + it "outputs the version with build metadata", bundler: "3" do bundle "version" expect(out).to match(/\A#{Regexp.escape(Bundler::VERSION)} \(\d{4}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/) end diff --git a/spec/bundler/commands/viz_spec.rb b/spec/bundler/commands/viz_spec.rb index 0efb24b504..f8b5f7836e 100644 --- a/spec/bundler/commands/viz_spec.rb +++ b/spec/bundler/commands/viz_spec.rb @@ -1,10 +1,8 @@ # frozen_string_literal: true -RSpec.describe "bundle viz", :bundler => "< 3", :if => Bundler.which("dot") do +RSpec.describe "bundle viz", bundler: "< 3", if: Bundler.which("dot"), realworld: true do before do - graphviz_version = RUBY_VERSION >= "2.4" ? "1.2.5" : "1.2.4" - - realworld_system_gems "ruby-graphviz --version #{graphviz_version}" + realworld_system_gems "ruby-graphviz --version 1.2.5" end it "graphs gems from the Gemfile" do @@ -17,8 +15,8 @@ RSpec.describe "bundle viz", :bundler => "< 3", :if => Bundler.which("dot") do bundle "viz" expect(out).to include("gem_graph.png") - bundle "viz", :format => "debug" - expect(out).to eq(strip_whitespace(<<-DOT).strip) + bundle "viz", format: "debug" + expect(out).to eq(<<~DOT.strip) digraph Gemfile { concentrate = "true"; normalize = "true"; @@ -51,8 +49,8 @@ RSpec.describe "bundle viz", :bundler => "< 3", :if => Bundler.which("dot") do bundle "viz" expect(out).to include("gem_graph.png") - bundle "viz", :format => :debug, :version => true - expect(out).to eq(strip_whitespace(<<-EOS).strip) + bundle "viz", format: :debug, version: true + expect(out).to eq(<<~EOS.strip) digraph Gemfile { concentrate = "true"; normalize = "true"; @@ -79,7 +77,7 @@ RSpec.describe "bundle viz", :bundler => "< 3", :if => Bundler.which("dot") do end end - system_gems "graphviz-999", :gem_repo => gem_repo4 + system_gems "graphviz-999", gem_repo: gem_repo4 end it "loads the correct ruby-graphviz gem" do @@ -89,8 +87,8 @@ RSpec.describe "bundle viz", :bundler => "< 3", :if => Bundler.which("dot") do gem "rack-obama" G - bundle "viz", :format => "debug" - expect(out).to eq(strip_whitespace(<<-DOT).strip) + bundle "viz", format: "debug" + expect(out).to eq(<<~DOT.strip) digraph Gemfile { concentrate = "true"; normalize = "true"; diff --git a/spec/bundler/install/allow_offline_install_spec.rb b/spec/bundler/install/allow_offline_install_spec.rb index 524363fde5..8da94718e0 100644 --- a/spec/bundler/install/allow_offline_install_spec.rb +++ b/spec/bundler/install/allow_offline_install_spec.rb @@ -7,7 +7,7 @@ RSpec.describe "bundle install with :allow_offline_install" do context "with no cached data locally" do it "still installs" do - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "http://testgemserver.local" gem "rack-obama" G @@ -15,7 +15,7 @@ RSpec.describe "bundle install with :allow_offline_install" do end it "still fails when the network is down" do - install_gemfile <<-G, :artifice => "fail", :raise_on_error => false + install_gemfile <<-G, artifice: "fail", raise_on_error: false source "http://testgemserver.local" gem "rack-obama" G @@ -26,10 +26,10 @@ RSpec.describe "bundle install with :allow_offline_install" do context "with cached data locally" do it "will install from the compact index" do - system_gems ["rack-1.0.0"], :path => default_bundle_path + system_gems ["rack-1.0.0"], path: default_bundle_path bundle "config set clean false" - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "http://testgemserver.local" gem "rack-obama" gem "rack", "< 1.0" @@ -42,7 +42,7 @@ RSpec.describe "bundle install with :allow_offline_install" do gem "rack-obama" G - bundle :update, :artifice => "fail", :all => true + bundle :update, artifice: "fail", all: true expect(last_command.stdboth).to include "Using the cached data for the new index because of a network error" expect(the_bundle).to include_gems("rack-obama 1.0", "rack 1.0.0") @@ -51,9 +51,12 @@ RSpec.describe "bundle install with :allow_offline_install" do def break_git_remote_ops! FileUtils.mkdir_p(tmp("broken_path")) File.open(tmp("broken_path/git"), "w", 0o755) do |f| - f.puts strip_whitespace(<<-RUBY) + f.puts <<~RUBY #!/usr/bin/env ruby - if %w(fetch --force --quiet --tags refs/heads/*:refs/heads/*).-(ARGV).empty? || %w(clone --bare --no-hardlinks --quiet).-(ARGV).empty? + fetch_args = %w(fetch --force --quiet) + clone_args = %w(clone --bare --no-hardlinks --quiet) + + if (fetch_args.-(ARGV).empty? || clone_args.-(ARGV).empty?) && ARGV.any? {|arg| arg.start_with?("file://") } warn "git remote ops have been disabled" exit 1 end @@ -72,14 +75,14 @@ RSpec.describe "bundle install with :allow_offline_install" do it "will install from a cached git repo" do skip "doesn't print errors" if Gem.win_platform? - git = build_git "a", "1.0.0", :path => lib_path("a") - update_git("a", :path => git.path, :branch => "new_branch") + git = build_git "a", "1.0.0", path: lib_path("a") + update_git("a", path: git.path, branch: "new_branch") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "a", :git => #{git.path.to_s.dump} G - break_git_remote_ops! { bundle :update, :all => true } + break_git_remote_ops! { bundle :update, all: true } expect(err).to include("Using cached git data because of network errors") expect(the_bundle).to be_locked diff --git a/spec/bundler/install/binstubs_spec.rb b/spec/bundler/install/binstubs_spec.rb index 6961171f4f..928ba80b15 100644 --- a/spec/bundler/install/binstubs_spec.rb +++ b/spec/bundler/install/binstubs_spec.rb @@ -2,10 +2,8 @@ RSpec.describe "bundle install" do describe "when system_bindir is set" do - # On OS X, Gem.bindir defaults to /usr/bin, so system_bindir is useful if - # you want to avoid sudo installs for system gems with OS X's default ruby it "overrides Gem.bindir" do - expect(Pathname.new("/usr/bin")).not_to be_writable unless Process.euid == 0 + expect(Pathname.new("/usr/bin")).not_to be_writable gemfile <<-G def Gem.bindir; "/usr/bin"; end source "#{file_uri_for(gem_repo1)}" diff --git a/spec/bundler/install/bundler_spec.rb b/spec/bundler/install/bundler_spec.rb index 963ce82db8..19911f1154 100644 --- a/spec/bundler/install/bundler_spec.rb +++ b/spec/bundler/install/bundler_spec.rb @@ -30,19 +30,18 @@ RSpec.describe "bundle install" do end it "causes a conflict if explicitly requesting a different version of bundler" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}" gem "rails", "3.0" gem "bundler", "0.9.1" G - nice_error = <<-E.strip.gsub(/^ {8}/, "") - Bundler could not find compatible versions for gem "bundler": - In Gemfile: - bundler (= 0.9.1) + nice_error = <<~E.strip + Could not find compatible versions - Current Bundler version: - bundler (#{Bundler::VERSION}) + Because the current Bundler version (#{Bundler::VERSION}) does not satisfy bundler = 0.9.1 + and Gemfile depends on bundler = 0.9.1, + version solving has failed. Your bundle requires a different version of Bundler than the one you're running. Install the necessary version with `gem install bundler:0.9.1` and rerun bundler using `bundle _0.9.1_ install` @@ -51,19 +50,21 @@ RSpec.describe "bundle install" do end it "causes a conflict if explicitly requesting a non matching requirement on bundler" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}" gem "rails", "3.0" gem "bundler", "~> 0.8" G - nice_error = <<-E.strip.gsub(/^ {8}/, "") - Bundler could not find compatible versions for gem "bundler": - In Gemfile: - bundler (~> 0.8) + nice_error = <<~E.strip + Could not find compatible versions - Current Bundler version: - bundler (#{Bundler::VERSION}) + Because rails >= 3.0 depends on bundler >= 0.9.0.pre + and the current Bundler version (#{Bundler::VERSION}) does not satisfy bundler >= 0.9.0.pre, < 1.A, + rails >= 3.0 requires bundler >= 1.A. + So, because Gemfile depends on rails = 3.0 + and Gemfile depends on bundler ~> 0.8, + version solving has failed. Your bundle requires a different version of Bundler than the one you're running. Install the necessary version with `gem install bundler:0.9.1` and rerun bundler using `bundle _0.9.1_ install` @@ -72,19 +73,18 @@ RSpec.describe "bundle install" do end it "causes a conflict if explicitly requesting a version of bundler that doesn't exist" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}" gem "rails", "3.0" gem "bundler", "0.9.2" G - nice_error = <<-E.strip.gsub(/^ {8}/, "") - Bundler could not find compatible versions for gem "bundler": - In Gemfile: - bundler (= 0.9.2) + nice_error = <<~E.strip + Could not find compatible versions - Current Bundler version: - bundler (#{Bundler::VERSION}) + Because the current Bundler version (#{Bundler::VERSION}) does not satisfy bundler = 0.9.2 + and Gemfile depends on bundler = 0.9.2, + version solving has failed. Your bundle requires a different version of Bundler than the one you're running, and that version could not be found. E @@ -143,20 +143,21 @@ RSpec.describe "bundle install" do end end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}" gem "activemerchant" gem "rails_pinned_to_old_activesupport" G - nice_error = <<-E.strip.gsub(/^ {8}/, "") - Bundler could not find compatible versions for gem "activesupport": - In Gemfile: - activemerchant was resolved to 1.0, which depends on - activesupport (>= 2.0.0) + nice_error = <<~E.strip + Could not find compatible versions - rails_pinned_to_old_activesupport was resolved to 1.0, which depends on - activesupport (= 1.2.3) + Because every version of rails_pinned_to_old_activesupport depends on activesupport = 1.2.3 + and every version of activemerchant depends on activesupport >= 2.0.0, + every version of rails_pinned_to_old_activesupport is incompatible with activemerchant >= 0. + So, because Gemfile depends on activemerchant >= 0 + and Gemfile depends on rails_pinned_to_old_activesupport >= 0, + version solving has failed. E expect(err).to include(nice_error) end @@ -170,19 +171,20 @@ RSpec.describe "bundle install" do end end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}" gem "rails_pinned_to_old_activesupport" gem "activesupport", "2.3.5" G - nice_error = <<-E.strip.gsub(/^ {8}/, "") - Bundler could not find compatible versions for gem "activesupport": - In Gemfile: - activesupport (= 2.3.5) + nice_error = <<~E.strip + Could not find compatible versions - rails_pinned_to_old_activesupport was resolved to 1.0, which depends on - activesupport (= 1.2.3) + Because every version of rails_pinned_to_old_activesupport depends on activesupport = 1.2.3 + and Gemfile depends on rails_pinned_to_old_activesupport >= 0, + activesupport = 1.2.3 is required. + So, because Gemfile depends on activesupport = 2.3.5, + version solving has failed. E expect(err).to include(nice_error) end @@ -208,6 +210,33 @@ RSpec.describe "bundle install" do expect(err).to be_empty end + it "prints the previous version when switching to a previously downloaded gem" do + build_repo4 do + build_gem "rails", "7.0.3" + build_gem "rails", "7.0.4" + end + + bundle "config set path.system true" + + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem 'rails', "7.0.4" + G + + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem 'rails', "7.0.3" + G + + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem 'rails', "7.0.4" + G + + expect(out).to include("Using rails 7.0.4 (was 7.0.3)") + expect(err).to be_empty + end + it "can install dependencies with newer bundler version with system gems" do bundle "config set path.system true" diff --git a/spec/bundler/install/deploy_spec.rb b/spec/bundler/install/deploy_spec.rb index 8f2650a932..d89fdea6f1 100644 --- a/spec/bundler/install/deploy_spec.rb +++ b/spec/bundler/install/deploy_spec.rb @@ -8,26 +8,26 @@ RSpec.describe "install in deployment or frozen mode" do G end - context "with CLI flags", :bundler => "< 3" do + context "with CLI flags", bundler: "< 3" do it "fails without a lockfile and says that --deployment requires a lock" do - bundle "install --deployment", :raise_on_error => false - expect(err).to include("The --deployment flag requires a Gemfile.lock") + bundle "install --deployment", raise_on_error: false + expect(err).to include("The --deployment flag requires a lockfile") end it "fails without a lockfile and says that --frozen requires a lock" do - bundle "install --frozen", :raise_on_error => false - expect(err).to include("The --frozen flag requires a Gemfile.lock") + bundle "install --frozen", raise_on_error: false + expect(err).to include("The --frozen flag requires a lockfile") end it "disallows --deployment --system" do - bundle "install --deployment --system", :raise_on_error => false + bundle "install --deployment --system", raise_on_error: false expect(err).to include("You have specified both --deployment") expect(err).to include("Please choose only one option") expect(exitstatus).to eq(15) end it "disallows --deployment --path --system" do - bundle "install --deployment --path . --system", :raise_on_error => false + bundle "install --deployment --path . --system", raise_on_error: false expect(err).to include("You have specified both --path") expect(err).to include("as well as --system") expect(err).to include("Please choose only one option") @@ -35,10 +35,43 @@ RSpec.describe "install in deployment or frozen mode" do end it "doesn't mess up a subsequent `bundle install` after you try to deploy without a lock" do - bundle "install --deployment", :raise_on_error => false + bundle "install --deployment", raise_on_error: false bundle :install expect(the_bundle).to include_gems "rack 1.0" end + + it "installs gems by default to vendor/bundle" do + bundle :lock + bundle "install --deployment" + expect(out).to include("vendor/bundle") + end + + it "installs gems to custom path if specified" do + bundle :lock + bundle "install --path vendor/bundle2 --deployment" + expect(out).to include("vendor/bundle2") + end + + it "works with the --frozen flag" do + bundle :lock + bundle "install --frozen" + end + + it "explodes with the --deployment flag if you make a change and don't check in the lockfile" do + bundle :lock + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "rack" + gem "rack-obama" + G + + bundle "install --deployment", raise_on_error: false + expect(err).to include("frozen mode") + expect(err).to include("You have added to the Gemfile") + expect(err).to include("* rack-obama") + expect(err).not_to include("You have deleted from the Gemfile") + expect(err).not_to include("You have changed in the Gemfile") + end end it "still works if you are not in the app directory and specify --gemfile" do @@ -46,7 +79,7 @@ RSpec.describe "install in deployment or frozen mode" do simulate_new_machine bundle "config set --local deployment true" bundle "config set --local path vendor/bundle" - bundle "install --gemfile #{tmp}/bundled_app/Gemfile", :dir => tmp + bundle "install --gemfile #{tmp}/bundled_app/Gemfile", dir: tmp expect(the_bundle).to include_gems "rack 1.0" end @@ -70,12 +103,12 @@ RSpec.describe "install in deployment or frozen mode" do bundle :install bundle "config set --local deployment true" bundle :install - bundle "exec bundle check", :env => { "PATH" => path } + bundle "exec bundle check", env: { "PATH" => path } end it "works when using path gems from the same path and the version is specified" do - build_lib "foo", :path => lib_path("nested/foo") - build_lib "bar", :path => lib_path("nested/bar") + build_lib "foo", path: lib_path("nested/foo") + build_lib "bar", path: lib_path("nested/bar") gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "foo", "1.0", :path => "#{lib_path("nested")}" @@ -88,7 +121,7 @@ RSpec.describe "install in deployment or frozen mode" do end it "works when path gems are specified twice" do - build_lib "foo", :path => lib_path("nested/foo") + build_lib "foo", path: lib_path("nested/foo") gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("nested/foo")}" @@ -101,14 +134,14 @@ RSpec.describe "install in deployment or frozen mode" do end it "works when there are credentials in the source URL" do - install_gemfile(<<-G, :artifice => "endpoint_strict_basic_authentication", :quiet => true) + install_gemfile(<<-G, artifice: "endpoint_strict_basic_authentication", quiet: true) source "http://user:pass@localgemserver.test/" gem "rack-obama", ">= 1.0" G bundle "config set --local deployment true" - bundle :install, :artifice => "endpoint_strict_basic_authentication" + bundle :install, artifice: "endpoint_strict_basic_authentication" end it "works with sources given by a block" do @@ -141,7 +174,7 @@ RSpec.describe "install in deployment or frozen mode" do rack (1.0.0) PLATFORMS - #{local} + #{generic_local_platform} DEPENDENCIES rack @@ -150,50 +183,10 @@ RSpec.describe "install in deployment or frozen mode" do bundle "config set --local deployment true" end - it "prevents the replace by default" do - bundle :install, :raise_on_error => false - - expect(err).to match(/The list of sources changed/) - end - - context "when allow_deployment_source_credential_changes is true" do - before { bundle "config set allow_deployment_source_credential_changes true" } - - it "allows the replace" do - bundle :install - - expect(out).to match(/Bundle complete!/) - end - end - - context "when allow_deployment_source_credential_changes is false" do - before { bundle "config set allow_deployment_source_credential_changes false" } - - it "prevents the replace" do - bundle :install, :raise_on_error => false - - expect(err).to match(/The list of sources changed/) - end - end - - context "when BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES env var is true" do - before { ENV["BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES"] = "true" } - - it "allows the replace" do - bundle :install + it "allows the replace" do + bundle :install - expect(out).to match(/Bundle complete!/) - end - end - - context "when BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES env var is false" do - before { ENV["BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES"] = "false" } - - it "prevents the replace" do - bundle :install, :raise_on_error => false - - expect(err).to match(/The list of sources changed/) - end + expect(out).to match(/Bundle complete!/) end end @@ -202,29 +195,41 @@ RSpec.describe "install in deployment or frozen mode" do bundle "install" end - it "installs gems by default to vendor/bundle", :bundler => "< 3" do - bundle "install --deployment" + it "installs gems by default to vendor/bundle" do + bundle "config set deployment true" + expect do + bundle "install" + end.not_to change { bundled_app_lock.mtime } expect(out).to include("vendor/bundle") end - it "installs gems to custom path if specified", :bundler => "< 3" do - bundle "install --path vendor/bundle2 --deployment" + it "installs gems to custom path if specified" do + bundle "config set path vendor/bundle2" + bundle "config set deployment true" + bundle "install" expect(out).to include("vendor/bundle2") end - it "works with the --deployment flag if you didn't change anything", :bundler => "< 3" do - bundle "install --deployment" + it "installs gems to custom path if specified, even when configured through ENV" do + bundle "config set deployment true" + bundle "install", env: { "BUNDLE_PATH" => "vendor/bundle2" } + expect(out).to include("vendor/bundle2") end - it "works with the --frozen flag if you didn't change anything", :bundler => "< 3" do - bundle "install --frozen" + it "works with the `frozen` setting" do + bundle "config set frozen true" + expect do + bundle "install" + end.not_to change { bundled_app_lock.mtime } end it "works with BUNDLE_FROZEN if you didn't change anything" do - bundle :install, :env => { "BUNDLE_FROZEN" => "true" } + expect do + bundle :install, env: { "BUNDLE_FROZEN" => "true" } + end.not_to change { bundled_app_lock.mtime } end - it "explodes with the --deployment flag if you make a change and don't check in the lockfile" do + it "explodes with the `deployment` setting if you make a change and don't check in the lockfile" do gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" @@ -232,8 +237,8 @@ RSpec.describe "install in deployment or frozen mode" do G bundle "config set --local deployment true" - bundle :install, :raise_on_error => false - expect(err).to include("deployment mode") + bundle :install, raise_on_error: false + expect(err).to include("frozen mode") expect(err).to include("You have added to the Gemfile") expect(err).to include("* rack-obama") expect(err).not_to include("You have deleted from the Gemfile") @@ -253,11 +258,60 @@ RSpec.describe "install in deployment or frozen mode" do bundle "config set --local path .bundle" bundle "config set --local without development" bundle "config set --local deployment true" - bundle :install, :env => { "DEBUG" => "1" } + bundle :install, env: { "DEBUG" => "1" } run "puts :WIN" expect(out).to eq("WIN") end + it "works if a gem is missing, but it's on a different platform" do + build_repo2 + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + + source "#{file_uri_for(gem_repo1)}" do + gem "rake", platform: :#{not_local_tag} + end + G + + bundle :install, env: { "BUNDLE_FROZEN" => "true" } + expect(last_command).to be_success + end + + it "shows a good error if a gem is missing from the lockfile" do + build_repo4 do + build_gem "foo" + build_gem "bar" + end + + gemfile <<-G + source "https://gem.repo4" + + gem "foo" + gem "bar" + G + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + foo (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo + bar + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle :install, env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false, artifice: "compact_index" + expect(err).to include("Your lock file is missing \"bar\", but the lockfile can't be updated because frozen mode is set") + end + it "explodes if a path gem is missing" do build_lib "path_gem" install_gemfile <<-G @@ -270,11 +324,11 @@ RSpec.describe "install in deployment or frozen mode" do bundle "config set --local path .bundle" bundle "config set --local deployment true" - bundle :install, :raise_on_error => false + bundle :install, raise_on_error: false expect(err).to include("The path `#{lib_path("path_gem-1.0")}` does not exist.") end - it "can have --frozen set via an environment variable", :bundler => "< 3" do + it "can have --frozen set via an environment variable" do gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" @@ -282,8 +336,8 @@ RSpec.describe "install in deployment or frozen mode" do G ENV["BUNDLE_FROZEN"] = "1" - bundle "install", :raise_on_error => false - expect(err).to include("deployment mode") + bundle "install", raise_on_error: false + expect(err).to include("frozen mode") expect(err).to include("You have added to the Gemfile") expect(err).to include("* rack-obama") expect(err).not_to include("You have deleted from the Gemfile") @@ -298,21 +352,21 @@ RSpec.describe "install in deployment or frozen mode" do G ENV["BUNDLE_DEPLOYMENT"] = "true" - bundle "install", :raise_on_error => false - expect(err).to include("deployment mode") + bundle "install", raise_on_error: false + expect(err).to include("frozen mode") expect(err).to include("You have added to the Gemfile") expect(err).to include("* rack-obama") expect(err).not_to include("You have deleted from the Gemfile") expect(err).not_to include("You have changed in the Gemfile") end - it "installs gems by default to vendor/bundle when deployment mode is set via an environment variable", :bundler => "< 3" do + it "installs gems by default to vendor/bundle when deployment mode is set via an environment variable" do ENV["BUNDLE_DEPLOYMENT"] = "true" bundle "install" expect(out).to include("vendor/bundle") end - it "installs gems to custom path when deployment mode is set via an environment variable ", :bundler => "< 3" do + it "installs gems to custom path when deployment mode is set via an environment variable " do ENV["BUNDLE_DEPLOYMENT"] = "true" ENV["BUNDLE_PATH"] = "vendor/bundle2" bundle "install" @@ -329,7 +383,7 @@ RSpec.describe "install in deployment or frozen mode" do ENV["BUNDLE_FROZEN"] = "false" ENV["BUNDLE_DEPLOYMENT"] = "false" bundle "install" - expect(out).not_to include("deployment mode") + expect(out).not_to include("frozen mode") expect(out).not_to include("You have added to the Gemfile") expect(out).not_to include("* rack-obama") end @@ -341,8 +395,8 @@ RSpec.describe "install in deployment or frozen mode" do G bundle "config set --local deployment true" - bundle :install, :raise_on_error => false - expect(err).to include("deployment mode") + bundle :install, raise_on_error: false + expect(err).to include("frozen mode") expect(err).to include("You have added to the Gemfile:\n* activesupport\n\n") expect(err).to include("You have deleted from the Gemfile:\n* rack") expect(err).not_to include("You have changed in the Gemfile") @@ -355,13 +409,13 @@ RSpec.describe "install in deployment or frozen mode" do G bundle "config set --local deployment true" - bundle :install, :raise_on_error => false - expect(err).to include("deployment mode") - expect(err).to include("You have added to the Gemfile:\n* source: git://hubz.com") - expect(err).not_to include("You have changed in the Gemfile") + bundle :install, raise_on_error: false + expect(err).to include("frozen mode") + expect(err).not_to include("You have added to the Gemfile") + expect(err).to include("You have changed in the Gemfile:\n* rack from `no specified source` to `git://hubz.com`") end - it "explodes if you unpin a source" do + it "explodes if you change a source" do build_git "rack" install_gemfile <<-G @@ -375,16 +429,16 @@ RSpec.describe "install in deployment or frozen mode" do G bundle "config set --local deployment true" - bundle :install, :raise_on_error => false - expect(err).to include("deployment mode") - expect(err).to include("You have deleted from the Gemfile:\n* source: #{lib_path("rack-1.0")}") + bundle :install, raise_on_error: false + expect(err).to include("frozen mode") + expect(err).not_to include("You have deleted from the Gemfile") expect(err).not_to include("You have added to the Gemfile") - expect(err).not_to include("You have changed in the Gemfile") + expect(err).to include("You have changed in the Gemfile:\n* rack from `#{lib_path("rack-1.0")}` to `no specified source`") end - it "explodes if you unpin a source, leaving it pinned somewhere else" do - build_lib "foo", :path => lib_path("rack/foo") - build_git "rack", :path => lib_path("rack") + it "explodes if you change a source" do + build_lib "foo", path: lib_path("rack/foo") + build_git "rack", path: lib_path("rack") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -399,9 +453,9 @@ RSpec.describe "install in deployment or frozen mode" do G bundle "config set --local deployment true" - bundle :install, :raise_on_error => false - expect(err).to include("deployment mode") - expect(err).to include("You have changed in the Gemfile:\n* rack from `no specified source` to `#{lib_path("rack")}`") + bundle :install, raise_on_error: false + expect(err).to include("frozen mode") + expect(err).to include("You have changed in the Gemfile:\n* rack from `#{lib_path("rack")}` to `no specified source`") expect(err).not_to include("You have added to the Gemfile") expect(err).not_to include("You have deleted from the Gemfile") end @@ -417,23 +471,23 @@ RSpec.describe "install in deployment or frozen mode" do gem "rack-obama" G - run "require 'rack'", :raise_on_error => false - expect(err).to include strip_whitespace(<<-E).strip -The dependencies in your gemfile changed + run "require 'rack'", raise_on_error: false + expect(err).to include <<~E.strip + The dependencies in your gemfile changed, but the lockfile can't be updated because frozen mode is set (Bundler::ProductionError) -You have added to the Gemfile: -* rack (= 1.0.0) -* rack-obama + You have added to the Gemfile: + * rack (= 1.0.0) + * rack-obama -You have deleted from the Gemfile: -* rack + You have deleted from the Gemfile: + * rack E end end context "with path in Gemfile and packed" do it "works fine after bundle package and bundle install --local" do - build_lib "foo", :path => lib_path("foo") + build_lib "foo", path: lib_path("foo") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("foo")}" @@ -452,7 +506,7 @@ You have deleted from the Gemfile: simulate_new_machine bundle "config set --local deployment true" bundle "install --verbose" - expect(out).not_to include("You are trying to install in deployment mode after changing your Gemfile") + expect(out).not_to include("but the lockfile can't be updated because frozen mode is set") expect(out).not_to include("You have added to the Gemfile") expect(out).not_to include("You have deleted from the Gemfile") expect(out).to include("vendor/cache/foo") diff --git a/spec/bundler/install/failure_spec.rb b/spec/bundler/install/failure_spec.rb index 4a9c33754f..f972a37bf6 100644 --- a/spec/bundler/install/failure_spec.rb +++ b/spec/bundler/install/failure_spec.rb @@ -14,7 +14,7 @@ RSpec.describe "bundle install" do end end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}" gem "rails" G @@ -39,7 +39,7 @@ In Gemfile: end it "removes the downloaded .gem" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo4)}" gem "a" G diff --git a/spec/bundler/install/gemfile/eval_gemfile_spec.rb b/spec/bundler/install/gemfile/eval_gemfile_spec.rb index 02283291b4..cfa66e5986 100644 --- a/spec/bundler/install/gemfile/eval_gemfile_spec.rb +++ b/spec/bundler/install/gemfile/eval_gemfile_spec.rb @@ -2,7 +2,7 @@ RSpec.describe "bundle install with gemfile that uses eval_gemfile" do before do - build_lib("gunks", :path => bundled_app.join("gems/gunks")) do |s| + build_lib("gunks", path: bundled_app.join("gems/gunks")) do |s| s.name = "gunks" s.version = "0.0.1" end @@ -24,7 +24,7 @@ RSpec.describe "bundle install with gemfile that uses eval_gemfile" do expect(out).to include("Resolving dependencies") expect(out).to include("Bundle complete") - expect(the_bundle).to include_gem "gunks 0.0.1", :source => "path@#{bundled_app("gems", "gunks")}" + expect(the_bundle).to include_gem "gunks 0.0.1", source: "path@#{bundled_app("gems", "gunks")}" end end @@ -64,7 +64,7 @@ RSpec.describe "bundle install with gemfile that uses eval_gemfile" do context "eval-ed Gemfile has relative-path gems" do before do - build_lib("a", :path => bundled_app("gems/a")) + build_lib("a", path: bundled_app("gems/a")) create_file bundled_app("nested/Gemfile-nested"), <<-G source "#{file_uri_for(gem_repo1)}" gem "a", :path => "../gems/a" @@ -102,7 +102,7 @@ RSpec.describe "bundle install with gemfile that uses eval_gemfile" do expect(out).to include("Resolving dependencies") expect(out).to include("Bundle complete") - expect(the_bundle).to include_gem "gunks 0.0.1", :source => "path@#{bundled_app("gems", "gunks")}" + expect(the_bundle).to include_gem "gunks 0.0.1", source: "path@#{bundled_app("gems", "gunks")}" end end diff --git a/spec/bundler/install/gemfile/force_ruby_platform_spec.rb b/spec/bundler/install/gemfile/force_ruby_platform_spec.rb new file mode 100644 index 0000000000..a29b79ad62 --- /dev/null +++ b/spec/bundler/install/gemfile/force_ruby_platform_spec.rb @@ -0,0 +1,146 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install with force_ruby_platform DSL option", :jruby do + context "when no transitive deps" do + before do + build_repo4 do + # Build a gem with platform specific versions + build_gem("platform_specific") do |s| + s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 RUBY'" + end + + build_gem("platform_specific") do |s| + s.platform = Bundler.local_platform + s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 #{Bundler.local_platform}'" + end + + # Build the exact same gem with a different name to compare using vs not using the option + build_gem("platform_specific_forced") do |s| + s.write "lib/platform_specific_forced.rb", "PLATFORM_SPECIFIC_FORCED = '1.0.0 RUBY'" + end + + build_gem("platform_specific_forced") do |s| + s.platform = Bundler.local_platform + s.write "lib/platform_specific_forced.rb", "PLATFORM_SPECIFIC_FORCED = '1.0.0 #{Bundler.local_platform}'" + end + end + end + + it "pulls the pure ruby variant of the given gem" do + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + + gem "platform_specific_forced", :force_ruby_platform => true + gem "platform_specific" + G + + expect(the_bundle).to include_gems "platform_specific_forced 1.0.0 RUBY" + expect(the_bundle).to include_gems "platform_specific 1.0.0 #{Bundler.local_platform}" + end + + it "still respects a global `force_ruby_platform` config" do + install_gemfile <<-G, env: { "BUNDLE_FORCE_RUBY_PLATFORM" => "true" } + source "#{file_uri_for(gem_repo4)}" + + gem "platform_specific_forced", :force_ruby_platform => true + gem "platform_specific" + G + + expect(the_bundle).to include_gems "platform_specific_forced 1.0.0 RUBY" + expect(the_bundle).to include_gems "platform_specific 1.0.0 RUBY" + end + end + + context "when also a transitive dependency" do + before do + build_repo4 do + build_gem("depends_on_platform_specific") {|s| s.add_runtime_dependency "platform_specific" } + + build_gem("platform_specific") do |s| + s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 RUBY'" + end + + build_gem("platform_specific") do |s| + s.platform = Bundler.local_platform + s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 #{Bundler.local_platform}'" + end + end + end + + it "still pulls the ruby variant" do + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + + gem "depends_on_platform_specific" + gem "platform_specific", :force_ruby_platform => true + G + + expect(the_bundle).to include_gems "platform_specific 1.0.0 RUBY" + end + end + + context "with transitive dependencies with platform specific versions" do + before do + build_repo4 do + build_gem("depends_on_platform_specific") do |s| + s.add_runtime_dependency "platform_specific" + s.write "lib/depends_on_platform_specific.rb", "DEPENDS_ON_PLATFORM_SPECIFIC = '1.0.0 RUBY'" + end + + build_gem("depends_on_platform_specific") do |s| + s.add_runtime_dependency "platform_specific" + s.platform = Bundler.local_platform + s.write "lib/depends_on_platform_specific.rb", "DEPENDS_ON_PLATFORM_SPECIFIC = '1.0.0 #{Bundler.local_platform}'" + end + + build_gem("platform_specific") do |s| + s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 RUBY'" + end + + build_gem("platform_specific") do |s| + s.platform = Bundler.local_platform + s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 #{Bundler.local_platform}'" + end + end + end + + it "ignores ruby variants for the transitive dependencies" do + install_gemfile <<-G, env: { "DEBUG_RESOLVER" => "true" } + source "#{file_uri_for(gem_repo4)}" + + gem "depends_on_platform_specific", :force_ruby_platform => true + G + + expect(the_bundle).to include_gems "depends_on_platform_specific 1.0.0 RUBY" + expect(the_bundle).to include_gems "platform_specific 1.0.0 #{Bundler.local_platform}" + end + + it "reinstalls the ruby variant when a platform specific variant is already installed, the lockile has only RUBY platform, and :force_ruby_platform is used in the Gemfile" do + lockfile <<-L + GEM + remote: #{file_uri_for(gem_repo4)} + specs: + platform_specific (1.0) + + PLATFORMS + ruby + + DEPENDENCIES + platform_specific + + BUNDLED WITH + #{Bundler::VERSION} + L + + system_gems "platform_specific-1.0-#{Gem::Platform.local}", path: default_bundle_path + + install_gemfile <<-G, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }, artifice: "compact_index" + source "#{file_uri_for(gem_repo4)}" + + gem "platform_specific", :force_ruby_platform => true + G + + expect(the_bundle).to include_gems "platform_specific 1.0.0 RUBY" + end + end +end diff --git a/spec/bundler/install/gemfile/gemspec_spec.rb b/spec/bundler/install/gemfile/gemspec_spec.rb index 6d9cd2daff..63778567cf 100644 --- a/spec/bundler/install/gemfile/gemspec_spec.rb +++ b/spec/bundler/install/gemfile/gemspec_spec.rb @@ -8,8 +8,38 @@ RSpec.describe "bundle install from an existing gemspec" do end end + let(:x64_mingw_archs) do + if RUBY_PLATFORM == "x64-mingw-ucrt" + if Gem.rubygems_version >= Gem::Version.new("3.2.28") + ["x64-mingw-ucrt", "x64-mingw32"] + else + ["x64-mingw32", "x64-unknown"] + end + else + ["x64-mingw32"] + end + end + + let(:x64_mingw_gems) do + x64_mingw_archs.map {|p| "platform_specific (1.0-#{p})" }.join("\n ") + end + + let(:x64_mingw_platforms) do + x64_mingw_archs.join("\n ") + end + + def x64_mingw_checksums(checksums) + x64_mingw_archs.each do |arch| + if arch == "x64-mingw-ucrt" + checksums.no_checksum "platform_specific", "1.0", arch + else + checksums.checksum gem_repo2, "platform_specific", "1.0", arch + end + end + end + it "should install runtime and development dependencies" do - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp.join("foo")) do |s| s.write("Gemfile", "source :rubygems\ngemspec") s.add_dependency "bar", "=1.0.0" s.add_development_dependency "bar-dev", "=1.0.0" @@ -20,11 +50,11 @@ RSpec.describe "bundle install from an existing gemspec" do G expect(the_bundle).to include_gems "bar 1.0.0" - expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :development + expect(the_bundle).to include_gems "bar-dev 1.0.0", groups: :development end it "that is hidden should install runtime and development dependencies" do - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp.join("foo")) do |s| s.write("Gemfile", "source :rubygems\ngemspec") s.add_dependency "bar", "=1.0.0" s.add_development_dependency "bar-dev", "=1.0.0" @@ -37,7 +67,7 @@ RSpec.describe "bundle install from an existing gemspec" do G expect(the_bundle).to include_gems "bar 1.0.0" - expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :development + expect(the_bundle).to include_gems "bar-dev 1.0.0", groups: :development end it "should handle a list of requirements" do @@ -46,7 +76,7 @@ RSpec.describe "bundle install from an existing gemspec" do build_gem "baz", "1.1" end - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp.join("foo")) do |s| s.write("Gemfile", "source :rubygems\ngemspec") s.add_dependency "baz", ">= 1.0", "< 1.1" end @@ -59,29 +89,29 @@ RSpec.describe "bundle install from an existing gemspec" do end it "should raise if there are no gemspecs available" do - build_lib("foo", :path => tmp.join("foo"), :gemspec => false) + build_lib("foo", path: tmp.join("foo"), gemspec: false) - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}" gemspec :path => '#{tmp.join("foo")}' G - expect(err).to match(/There are no gemspecs at #{tmp.join('foo')}/) + expect(err).to match(/There are no gemspecs at #{tmp.join("foo")}/) end it "should raise if there are too many gemspecs available" do - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp.join("foo")) do |s| s.write("foo2.gemspec", build_spec("foo", "4.0").first.to_ruby) end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}" gemspec :path => '#{tmp.join("foo")}' G - expect(err).to match(/There are multiple gemspecs at #{tmp.join('foo')}/) + expect(err).to match(/There are multiple gemspecs at #{tmp.join("foo")}/) end it "should pick a specific gemspec" do - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp.join("foo")) do |s| s.write("foo2.gemspec", "") s.add_dependency "bar", "=1.0.0" s.add_development_dependency "bar-dev", "=1.0.0" @@ -93,11 +123,11 @@ RSpec.describe "bundle install from an existing gemspec" do G expect(the_bundle).to include_gems "bar 1.0.0" - expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :development + expect(the_bundle).to include_gems "bar-dev 1.0.0", groups: :development end it "should use a specific group for development dependencies" do - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp.join("foo")) do |s| s.write("foo2.gemspec", "") s.add_dependency "bar", "=1.0.0" s.add_development_dependency "bar-dev", "=1.0.0" @@ -109,32 +139,32 @@ RSpec.describe "bundle install from an existing gemspec" do G expect(the_bundle).to include_gems "bar 1.0.0" - expect(the_bundle).not_to include_gems "bar-dev 1.0.0", :groups => :development - expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :dev + expect(the_bundle).not_to include_gems "bar-dev 1.0.0", groups: :development + expect(the_bundle).to include_gems "bar-dev 1.0.0", groups: :dev end it "should match a lockfile even if the gemspec defines development dependencies" do - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp.join("foo")) do |s| s.write("Gemfile", "source '#{file_uri_for(gem_repo1)}'\ngemspec") s.add_dependency "actionpack", "=2.3.2" - s.add_development_dependency "rake", "=13.0.1" + s.add_development_dependency "rake", rake_version end - bundle "install", :dir => tmp.join("foo") + bundle "install", dir: tmp.join("foo") # This should really be able to rely on $stderr, but, it's not written # right, so we can't. In fact, this is a bug negation test, and so it'll # ghost pass in future, and will only catch a regression if the message # doesn't change. Exit codes should be used correctly (they can be more # than just 0 and 1). bundle "config set --local deployment true" - output = bundle("install", :dir => tmp.join("foo")) + output = bundle("install", dir: tmp.join("foo")) expect(output).not_to match(/You have added to the Gemfile/) expect(output).not_to match(/You have deleted from the Gemfile/) - expect(output).not_to match(/install in deployment mode after changing/) + expect(output).not_to match(/the lockfile can't be updated because frozen mode is set/) end it "should match a lockfile without needing to re-resolve" do - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp.join("foo")) do |s| s.add_dependency "rack" end @@ -143,7 +173,7 @@ RSpec.describe "bundle install from an existing gemspec" do gemspec :path => '#{tmp.join("foo")}' G - bundle "install", :verbose => true + bundle "install", verbose: true message = "Found no changes, using resolution from the lockfile" expect(out.scan(message).size).to eq(1) @@ -152,7 +182,7 @@ RSpec.describe "bundle install from an existing gemspec" do it "should match a lockfile without needing to re-resolve with development dependencies" do simulate_platform java - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp.join("foo")) do |s| s.add_dependency "rack" s.add_development_dependency "thin" end @@ -162,34 +192,34 @@ RSpec.describe "bundle install from an existing gemspec" do gemspec :path => '#{tmp.join("foo")}' G - bundle "install", :verbose => true + bundle "install", verbose: true message = "Found no changes, using resolution from the lockfile" expect(out.scan(message).size).to eq(1) end - it "should match a lockfile on non-ruby platforms with a transitive platform dependency", :jruby do - build_lib("foo", :path => tmp.join("foo")) do |s| + it "should match a lockfile on non-ruby platforms with a transitive platform dependency", :jruby_only do + build_lib("foo", path: tmp.join("foo")) do |s| s.add_dependency "platform_specific" end - system_gems "platform_specific-1.0-java", :path => default_bundle_path + system_gems "platform_specific-1.0-java", path: default_bundle_path install_gemfile <<-G gemspec :path => '#{tmp.join("foo")}' G - bundle "update --bundler", :verbose => true + bundle "update --bundler", artifice: "compact_index", verbose: true expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 JAVA" end it "should evaluate the gemspec in its directory" do - build_lib("foo", :path => tmp.join("foo")) + build_lib("foo", path: tmp.join("foo")) File.open(tmp.join("foo/foo.gemspec"), "w") do |s| s.write "raise 'ahh' unless Dir.pwd == '#{tmp.join("foo")}'" end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false gemspec :path => '#{tmp.join("foo")}' G expect(last_command.stdboth).not_to include("ahh") @@ -203,7 +233,7 @@ RSpec.describe "bundle install from an existing gemspec" do # so emulate that system_gems %w[rack-1.0.0 rack-0.9.1 rack-obama-1.0] - build_lib("foo", :path => bundled_app) + build_lib("foo", path: bundled_app) gemspec = bundled_app("foo.gemspec").read bundled_app("foo.gemspec").open("w") do |f| f.write "#{gemspec.strip}.tap { gem 'rack-obama'; require 'rack/obama' }" @@ -218,14 +248,14 @@ RSpec.describe "bundle install from an existing gemspec" do end it "allows conflicts" do - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp.join("foo")) do |s| s.version = "1.0.0" s.add_dependency "bar", "= 1.0.0" end - build_gem "deps", :to_bundle => true do |s| + build_gem "deps", to_bundle: true do |s| s.add_dependency "foo", "= 0.0.1" end - build_gem "foo", "0.0.1", :to_bundle => true + build_gem "foo", "0.0.1", to_bundle: true install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" @@ -237,7 +267,7 @@ RSpec.describe "bundle install from an existing gemspec" do end it "does not break Gem.finish_resolve with conflicts" do - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp.join("foo")) do |s| s.version = "1.0.0" s.add_dependency "bar", "= 1.0.0" end @@ -261,14 +291,14 @@ RSpec.describe "bundle install from an existing gemspec" do end it "handles downgrades" do - build_lib "omg", "2.0", :path => lib_path("omg") + build_lib "omg", "2.0", path: lib_path("omg") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gemspec :path => "#{lib_path("omg")}" G - build_lib "omg", "1.0", :path => lib_path("omg") + build_lib "omg", "1.0", path: lib_path("omg") bundle :install @@ -278,7 +308,7 @@ RSpec.describe "bundle install from an existing gemspec" do context "in deployment mode" do context "when the lockfile was not updated after a change to the gemspec's dependencies" do it "reports that installation failed" do - build_lib "cocoapods", :path => bundled_app do |s| + build_lib "cocoapods", path: bundled_app do |s| s.add_dependency "activesupport", ">= 1" end @@ -289,12 +319,12 @@ RSpec.describe "bundle install from an existing gemspec" do expect(the_bundle).to include_gems("cocoapods 1.0", "activesupport 2.3.5") - build_lib "cocoapods", :path => bundled_app do |s| + build_lib "cocoapods", path: bundled_app do |s| s.add_dependency "activesupport", ">= 1.0.1" end bundle "config set --local deployment true" - bundle :install, :raise_on_error => false + bundle :install, raise_on_error: false expect(err).to include("changed") end @@ -304,13 +334,13 @@ RSpec.describe "bundle install from an existing gemspec" do context "when child gemspecs conflict with a released gemspec" do before do # build the "parent" gem that depends on another gem in the same repo - build_lib "source_conflict", :path => bundled_app do |s| + build_lib "source_conflict", path: bundled_app do |s| s.add_dependency "rack_middleware" end # build the "child" gem that is the same version as a released gem, but # has completely different and conflicting dependency requirements - build_lib "rack_middleware", "1.0", :path => bundled_app("rack_middleware") do |s| + build_lib "rack_middleware", "1.0", path: bundled_app("rack_middleware") do |s| s.add_dependency "rack", "1.0" # anything other than 0.9.1 end end @@ -328,83 +358,69 @@ RSpec.describe "bundle install from an existing gemspec" do context "with a lockfile and some missing dependencies" do let(:source_uri) { "http://localgemserver.test" } - context "previously bundled for Ruby" do - let(:platform) { "ruby" } - - before do - skip "not installing for some reason" if Gem.win_platform? - - build_lib("foo", :path => tmp.join("foo")) do |s| - s.add_dependency "rack", "=1.0.0" - end - - gemfile <<-G - source "#{source_uri}" - gemspec :path => "../foo" - G + before do + build_lib("foo", path: tmp.join("foo")) do |s| + s.add_dependency "rack", "=1.0.0" + end - lockfile <<-L - PATH - remote: ../foo - specs: - foo (1.0) - rack (= 1.0.0) + gemfile <<-G + source "#{source_uri}" + gemspec :path => "../foo" + G - GEM - remote: #{source_uri} - specs: - rack (1.0.0) + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + end - PLATFORMS - #{generic_local_platform} + lockfile <<-L + PATH + remote: ../foo + specs: + foo (1.0) + rack (= 1.0.0) - DEPENDENCIES - foo! + GEM + remote: #{source_uri} + specs: + rack (1.0.0) - BUNDLED WITH - #{Bundler::VERSION} - L - end + PLATFORMS + #{generic_local_platform} - context "using JRuby with explicit platform", :jruby do - before do - create_file( - tmp.join("foo", "foo-java.gemspec"), - build_spec("foo", "1.0", "java") do - dep "rack", "=1.0.0" - @spec.authors = "authors" - @spec.summary = "summary" - end.first.to_ruby - ) - end + DEPENDENCIES + foo! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end - it "should install" do - results = bundle "install", :artifice => "endpoint" - expect(results).to include("Installing rack 1.0.0") - expect(the_bundle).to include_gems "rack 1.0.0" - end + context "using JRuby with explicit platform", :jruby_only do + before do + create_file( + tmp.join("foo", "foo-java.gemspec"), + build_spec("foo", "1.0", "java") do + dep "rack", "=1.0.0" + @spec.authors = "authors" + @spec.summary = "summary" + end.first.to_ruby + ) end - context "using JRuby", :jruby do - it "should install" do - results = bundle "install", :artifice => "endpoint" - expect(results).to include("Installing rack 1.0.0") - expect(the_bundle).to include_gems "rack 1.0.0" - end + it "should install" do + results = bundle "install", artifice: "endpoint" + expect(results).to include("Installing rack 1.0.0") + expect(the_bundle).to include_gems "rack 1.0.0" end + end - context "using Windows" do - it "should install" do - simulate_windows do - results = bundle "install", :artifice => "endpoint" - expect(results).to include("Installing rack 1.0.0") - expect(the_bundle).to include_gems "rack 1.0.0" - end - end - end + it "should install", :jruby do + results = bundle "install", artifice: "endpoint" + expect(results).to include("Installing rack 1.0.0") + expect(the_bundle).to include_gems "rack 1.0.0" end - context "bundled for ruby and jruby" do + context "bundled for multiple platforms" do let(:platform_specific_type) { :runtime } let(:dependency) { "platform_specific" } before do @@ -414,7 +430,7 @@ RSpec.describe "bundle install from an existing gemspec" do end end - build_lib "foo", :path => bundled_app do |s| + build_lib "foo", path: bundled_app do |s| if platform_specific_type == :runtime s.add_runtime_dependency dependency elsif platform_specific_type == :development @@ -429,20 +445,32 @@ RSpec.describe "bundle install from an existing gemspec" do gemspec G - simulate_platform("ruby") { bundle "install" } + bundle "config set --local force_ruby_platform true" + bundle "install" + + simulate_new_machine simulate_platform("jruby") { bundle "install" } + simulate_platform(x64_mingw32) { bundle "install" } end context "on ruby" do before do - simulate_platform("ruby") + bundle "config set --local force_ruby_platform true" bundle :install end context "as a runtime dependency" do - it "keeps java dependencies in the lockfile" do + it "keeps all platform dependencies in the lockfile" do expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 RUBY" - expect(lockfile).to eq strip_whitespace(<<-L) + + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + c.checksum gem_repo2, "platform_specific", "1.0" + c.checksum gem_repo2, "platform_specific", "1.0", "java" + x64_mingw_checksums(c) + end + + expect(lockfile).to eq <<~L PATH remote: . specs: @@ -454,14 +482,16 @@ RSpec.describe "bundle install from an existing gemspec" do specs: platform_specific (1.0) platform_specific (1.0-java) + #{x64_mingw_gems} PLATFORMS java ruby + #{x64_mingw_platforms} DEPENDENCIES foo! - + #{checksums} BUNDLED WITH #{Bundler::VERSION} L @@ -471,9 +501,17 @@ RSpec.describe "bundle install from an existing gemspec" do context "as a development dependency" do let(:platform_specific_type) { :development } - it "keeps java dependencies in the lockfile" do + it "keeps all platform dependencies in the lockfile" do expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 RUBY" - expect(lockfile).to eq strip_whitespace(<<-L) + + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + c.checksum gem_repo2, "platform_specific", "1.0" + c.checksum gem_repo2, "platform_specific", "1.0", "java" + x64_mingw_checksums(c) + end + + expect(lockfile).to eq <<~L PATH remote: . specs: @@ -484,15 +522,17 @@ RSpec.describe "bundle install from an existing gemspec" do specs: platform_specific (1.0) platform_specific (1.0-java) + #{x64_mingw_gems} PLATFORMS java ruby + #{x64_mingw_platforms} DEPENDENCIES foo! platform_specific - + #{checksums} BUNDLED WITH #{Bundler::VERSION} L @@ -503,9 +543,18 @@ RSpec.describe "bundle install from an existing gemspec" do let(:platform_specific_type) { :development } let(:dependency) { "indirect_platform_specific" } - it "keeps java dependencies in the lockfile" do + it "keeps all platform dependencies in the lockfile" do expect(the_bundle).to include_gems "foo 1.0", "indirect_platform_specific 1.0", "platform_specific 1.0 RUBY" - expect(lockfile).to eq strip_whitespace(<<-L) + + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + c.checksum gem_repo2, "indirect_platform_specific", "1.0" + c.checksum gem_repo2, "platform_specific", "1.0" + c.checksum gem_repo2, "platform_specific", "1.0", "java" + x64_mingw_checksums(c) + end + + expect(lockfile).to eq <<~L PATH remote: . specs: @@ -518,15 +567,17 @@ RSpec.describe "bundle install from an existing gemspec" do platform_specific platform_specific (1.0) platform_specific (1.0-java) + #{x64_mingw_gems} PLATFORMS java ruby + #{x64_mingw_platforms} DEPENDENCIES foo! indirect_platform_specific - + #{checksums} BUNDLED WITH #{Bundler::VERSION} L @@ -538,7 +589,7 @@ RSpec.describe "bundle install from an existing gemspec" do context "with multiple platforms" do before do - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp.join("foo")) do |s| s.version = "1.0.0" s.add_development_dependency "rack" s.write "foo-universal-java.gemspec", build_spec("foo", "1.0.0", "universal-java") {|sj| sj.runtime "rack", "1.0.0" }.first.to_ruby @@ -546,7 +597,7 @@ RSpec.describe "bundle install from an existing gemspec" do end it "installs the ruby platform gemspec" do - simulate_platform "ruby" + bundle "config set --local force_ruby_platform true" install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -557,7 +608,7 @@ RSpec.describe "bundle install from an existing gemspec" do end it "installs the ruby platform gemspec and skips dev deps with `without development` configured" do - simulate_platform "ruby" + bundle "config set --local force_ruby_platform true" bundle "config set --local without development" install_gemfile <<-G @@ -572,7 +623,7 @@ RSpec.describe "bundle install from an existing gemspec" do context "with multiple platforms and resolving for more specific platforms" do before do - build_lib("chef", :path => tmp.join("chef")) do |s| + build_lib("chef", path: tmp.join("chef")) do |s| s.version = "17.1.17" s.write "chef-universal-mingw32.gemspec", build_spec("chef", "17.1.17", "universal-mingw32") {|sw| sw.runtime "win32-api", "~> 1.5.3" }.first.to_ruby end @@ -590,6 +641,12 @@ RSpec.describe "bundle install from an existing gemspec" do gemspec :path => "../chef" G + checksums = checksums_section_when_existing do |c| + c.no_checksum "chef", "17.1.17" + c.no_checksum "chef", "17.1.17", "universal-mingw32" + c.checksum gem_repo4, "win32-api", "1.5.3", "universal-mingw32" + end + initial_lockfile = <<~L PATH remote: ../chef @@ -605,12 +662,12 @@ RSpec.describe "bundle install from an existing gemspec" do PLATFORMS ruby - x64-mingw32 + #{x64_mingw_platforms} x86-mingw32 DEPENDENCIES chef! - + #{checksums} BUNDLED WITH #{Bundler::VERSION} L @@ -622,4 +679,70 @@ RSpec.describe "bundle install from an existing gemspec" do expect(lockfile).to eq initial_lockfile end end + + context "with multiple locked platforms" do + before do + build_lib("activeadmin", path: tmp.join("activeadmin")) do |s| + s.version = "2.9.0" + s.add_dependency "railties", ">= 5.2", "< 6.2" + end + + build_repo4 do + build_gem "railties", "6.1.4" + + build_gem "jruby-openssl", "0.10.7" do |s| + s.platform = "java" + end + end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gemspec :path => "../activeadmin" + gem "jruby-openssl", :platform => :jruby + G + + bundle "lock --add-platform java" + end + + it "does not remove the platform specific specs from the lockfile when re-resolving due to gemspec changes" do + checksums = checksums_section_when_existing do |c| + c.no_checksum "activeadmin", "2.9.0" + c.no_checksum "jruby-openssl", "0.10.7", "java" + c.checksum gem_repo4, "railties", "6.1.4" + end + + expect(lockfile).to eq <<~L + PATH + remote: ../activeadmin + specs: + activeadmin (2.9.0) + railties (>= 5.2, < 6.2) + + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + jruby-openssl (0.10.7-java) + railties (6.1.4) + + PLATFORMS + #{lockfile_platforms("java")} + + DEPENDENCIES + activeadmin! + jruby-openssl + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + gemspec = tmp.join("activeadmin/activeadmin.gemspec") + File.write(gemspec, File.read(gemspec).sub(">= 5.2", ">= 6.0")) + + previous_lockfile = lockfile + + bundle "install --local" + + expect(lockfile).to eq(previous_lockfile.sub(">= 5.2", ">= 6.0")) + end + end end diff --git a/spec/bundler/install/gemfile/git_spec.rb b/spec/bundler/install/gemfile/git_spec.rb index 5ea9eee878..45ee7b44d1 100644 --- a/spec/bundler/install/gemfile/git_spec.rb +++ b/spec/bundler/install/gemfile/git_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.describe "bundle install with git sources" do - describe "when floating on master" do + describe "when floating on main" do before :each do build_git "foo" do |s| s.executables = "foobar" @@ -26,15 +26,22 @@ RSpec.describe "bundle install with git sources" do expect(out).to eq("WIN") end - it "caches the git repo", :bundler => "< 3" do - expect(Dir["#{default_bundle_path}/cache/bundler/git/foo-1.0-*"]).to have_attributes :size => 1 + it "caches the git repo", bundler: "< 3" do + expect(Dir["#{default_bundle_path}/cache/bundler/git/foo-1.0-*"]).to have_attributes size: 1 + end + + it "does not write to cache on bundler/setup" do + cache_path = default_bundle_path.join("cache") + FileUtils.rm_rf(cache_path) + ruby "require 'bundler/setup'" + expect(cache_path).not_to exist end it "caches the git repo globally and properly uses the cached repo on the next invocation" do simulate_new_machine bundle "config set global_gem_cache true" bundle :install - expect(Dir["#{home}/.bundle/cache/git/foo-1.0-*"]).to have_attributes :size => 1 + expect(Dir["#{home}/.bundle/cache/git/foo-1.0-*"]).to have_attributes size: 1 bundle "install --verbose" expect(err).to be_empty @@ -51,9 +58,10 @@ RSpec.describe "bundle install with git sources" do bundle "update foo" - sha = git.ref_for("master", 11) - spec_file = default_bundle_path.join("bundler/gems/foo-1.0-#{sha}/foo.gemspec").to_s - ruby_code = Gem::Specification.load(spec_file).to_ruby + sha = git.ref_for("main", 11) + spec_file = default_bundle_path.join("bundler/gems/foo-1.0-#{sha}/foo.gemspec") + expect(spec_file).to exist + ruby_code = Gem::Specification.load(spec_file.to_s).to_ruby file_code = File.read(spec_file) expect(file_code).to eq(ruby_code) end @@ -61,7 +69,7 @@ RSpec.describe "bundle install with git sources" do it "does not update the git source implicitly" do update_git "foo" - install_gemfile bundled_app2("Gemfile"), <<-G, :dir => bundled_app2 + install_gemfile bundled_app2("Gemfile"), <<-G, dir: bundled_app2 source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo-1.0")}" do gem 'foo' @@ -84,34 +92,30 @@ RSpec.describe "bundle install with git sources" do it "complains if pinned specs don't exist in the git repo" do build_git "foo" - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem "foo", "1.1", :git => "#{lib_path("foo-1.0")}" G - expect(err).to include("The source contains the following versions of 'foo': 1.0") + expect(err).to include("The source contains the following gems matching 'foo':\n * foo-1.0") end - it "complains with version and platform if pinned specs don't exist in the git repo" do - simulate_platform "java" - + it "complains with version and platform if pinned specs don't exist in the git repo", :jruby_only do build_git "only_java" do |s| s.platform = "java" end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" platforms :jruby do gem "only_java", "1.2", :git => "#{lib_path("only_java-1.0-java")}" end G - expect(err).to include("The source contains the following versions of 'only_java': 1.0 java") + expect(err).to include("The source contains the following gems matching 'only_java':\n * only_java-1.0-java") end - it "complains with multiple versions and platforms if pinned specs don't exist in the git repo" do - simulate_platform "java" - + it "complains with multiple versions and platforms if pinned specs don't exist in the git repo", :jruby_only do build_git "only_java", "1.0" do |s| s.platform = "java" end @@ -121,14 +125,14 @@ RSpec.describe "bundle install with git sources" do s.write "only_java1-0.gemspec", File.read("#{lib_path("only_java-1.0-java")}/only_java.gemspec") end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" platforms :jruby do gem "only_java", "1.2", :git => "#{lib_path("only_java-1.1-java")}" end G - expect(err).to include("The source contains the following versions of 'only_java': 1.0 java, 1.1 java") + expect(err).to include("The source contains the following gems matching 'only_java':\n * only_java-1.0-java\n * only_java-1.1-java") end it "still works after moving the application directory" do @@ -137,7 +141,7 @@ RSpec.describe "bundle install with git sources" do FileUtils.mv bundled_app, tmp("bundled_app.bck") - expect(the_bundle).to include_gems "foo 1.0", :dir => tmp("bundled_app.bck") + expect(the_bundle).to include_gems "foo 1.0", dir: tmp("bundled_app.bck") end it "can still install after moving the application directory" do @@ -146,7 +150,7 @@ RSpec.describe "bundle install with git sources" do FileUtils.mv bundled_app, tmp("bundled_app.bck") - update_git "foo", "1.1", :path => lib_path("foo-1.0") + update_git "foo", "1.1", path: lib_path("foo-1.0") gemfile tmp("bundled_app.bck/Gemfile"), <<-G source "#{file_uri_for(gem_repo1)}" @@ -157,9 +161,9 @@ RSpec.describe "bundle install with git sources" do gem "rack", "1.0" G - bundle "update foo", :dir => tmp("bundled_app.bck") + bundle "update foo", dir: tmp("bundled_app.bck") - expect(the_bundle).to include_gems "foo 1.1", "rack 1.0", :dir => tmp("bundled_app.bck") + expect(the_bundle).to include_gems "foo 1.1", "rack 1.0", dir: tmp("bundled_app.bck") end end @@ -196,6 +200,7 @@ RSpec.describe "bundle install with git sources" do gem "foo" end G + expect(err).to be_empty run <<-RUBY require 'foo' @@ -222,17 +227,56 @@ RSpec.describe "bundle install with git sources" do expect(out).to eq("WIN") end + it "works when an abbreviated revision is added after an initial, potentially shallow clone" do + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + git "#{lib_path("foo-1.0")}" do + gem "foo" + end + G + + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + git "#{lib_path("foo-1.0")}", :ref => #{@revision[0..7].inspect} do + gem "foo" + end + G + end + + it "works when a tag that does not look like a commit hash is used as the value of :ref" do + build_git "foo" + @remote = build_git("bar", bare: true) + update_git "foo", remote: file_uri_for(@remote.path) + update_git "foo", push: "main" + + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem 'foo', :git => "#{@remote.path}" + G + + # Create a new tag on the remote that needs fetching + update_git "foo", tag: "v1.0.0" + update_git "foo", push: "v1.0.0" + + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem 'foo', :git => "#{@remote.path}", :ref => "v1.0.0" + G + + expect(err).to be_empty + end + it "works when the revision is a non-head ref" do - # want to ensure we don't fallback to master - update_git "foo", :path => lib_path("foo-1.0") do |s| + # want to ensure we don't fallback to main + update_git "foo", path: lib_path("foo-1.0") do |s| s.write("lib/foo.rb", "raise 'FAIL'") end - sys_exec("git update-ref -m \"Bundler Spec!\" refs/bundler/1 master~1", :dir => lib_path("foo-1.0")) + sys_exec("git update-ref -m \"Bundler Spec!\" refs/bundler/1 main~1", dir: lib_path("foo-1.0")) # want to ensure we don't fallback to HEAD - update_git "foo", :path => lib_path("foo-1.0"), :branch => "rando" do |s| - s.write("lib/foo.rb", "raise 'FAIL'") + update_git "foo", path: lib_path("foo-1.0"), branch: "rando" do |s| + s.write("lib/foo.rb", "raise 'FAIL_FROM_RANDO'") end install_gemfile <<-G @@ -259,16 +303,16 @@ RSpec.describe "bundle install with git sources" do end G - # want to ensure we don't fallback to master - update_git "foo", :path => lib_path("foo-1.0") do |s| + # want to ensure we don't fallback to main + update_git "foo", path: lib_path("foo-1.0") do |s| s.write("lib/foo.rb", "raise 'FAIL'") end - sys_exec("git update-ref -m \"Bundler Spec!\" refs/bundler/1 master~1", :dir => lib_path("foo-1.0")) + sys_exec("git update-ref -m \"Bundler Spec!\" refs/bundler/1 main~1", dir: lib_path("foo-1.0")) # want to ensure we don't fallback to HEAD - update_git "foo", :path => lib_path("foo-1.0"), :branch => "rando" do |s| - s.write("lib/foo.rb", "raise 'FAIL'") + update_git "foo", path: lib_path("foo-1.0"), branch: "rando" do |s| + s.write("lib/foo.rb", "raise 'FAIL_FROM_RANDO'") end install_gemfile <<-G @@ -288,7 +332,7 @@ RSpec.describe "bundle install with git sources" do end it "does not download random non-head refs" do - sys_exec("git update-ref -m \"Bundler Spec!\" refs/bundler/1 master~1", :dir => lib_path("foo-1.0")) + sys_exec("git update-ref -m \"Bundler Spec!\" refs/bundler/1 main~1", dir: lib_path("foo-1.0")) bundle "config set global_gem_cache true" @@ -300,9 +344,9 @@ RSpec.describe "bundle install with git sources" do G # ensure we also git fetch after cloning - bundle :update, :all => true + bundle :update, all: true - sys_exec("git ls-remote .", :dir => Dir[home(".bundle/cache/git/foo-*")].first) + sys_exec("git ls-remote .", dir: Dir[home(".bundle/cache/git/foo-*")].first) expect(out).not_to include("refs/bundler/1") end @@ -313,7 +357,7 @@ RSpec.describe "bundle install with git sources" do let(:repo) { build_git("foo").path } it "works" do - update_git("foo", :path => repo, :branch => branch) + update_git("foo", path: repo, branch: branch) install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -330,7 +374,7 @@ RSpec.describe "bundle install with git sources" do it "works" do skip "git does not accept this" if Gem.win_platform? - update_git("foo", :path => repo, :branch => branch) + update_git("foo", path: repo, branch: branch) install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -348,7 +392,7 @@ RSpec.describe "bundle install with git sources" do it "works" do skip "git does not accept this" if Gem.win_platform? - update_git("foo", :path => repo, :branch => branch) + update_git("foo", path: repo, branch: branch) install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -367,7 +411,7 @@ RSpec.describe "bundle install with git sources" do let(:repo) { build_git("foo").path } it "works" do - update_git("foo", :path => repo, :tag => tag) + update_git("foo", path: repo, tag: tag) install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -384,7 +428,7 @@ RSpec.describe "bundle install with git sources" do it "works" do skip "git does not accept this" if Gem.win_platform? - update_git("foo", :path => repo, :tag => tag) + update_git("foo", path: repo, tag: tag) install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -402,7 +446,7 @@ RSpec.describe "bundle install with git sources" do it "works" do skip "git does not accept this" if Gem.win_platform? - update_git("foo", :path => repo, :tag => tag) + update_git("foo", path: repo, tag: tag) install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -418,16 +462,13 @@ RSpec.describe "bundle install with git sources" do describe "when specifying local override" do it "uses the local repository instead of checking a new one out" do - # We don't generate it because we actually don't need it - # build_git "rack", "0.8" - - build_git "rack", "0.8", :path => lib_path("local-rack") do |s| + build_git "rack", "0.8", path: lib_path("local-rack") do |s| s.write "lib/rack.rb", "puts :LOCAL" end gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" G bundle %(config set local.rack #{lib_path("local-rack")}) @@ -442,13 +483,13 @@ RSpec.describe "bundle install with git sources" do FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) - update_git "rack", "0.8", :path => lib_path("local-rack") do |s| + update_git "rack", "0.8", path: lib_path("local-rack") do |s| s.write "lib/rack.rb", "puts :LOCAL" end install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" G bundle %(config set local.rack #{lib_path("local-rack")}) @@ -461,14 +502,14 @@ RSpec.describe "bundle install with git sources" do FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) - update_git "rack", "0.8", :path => lib_path("local-rack") do |s| + update_git "rack", "0.8", path: lib_path("local-rack") do |s| s.write "rack.gemspec", build_spec("rack", "0.8") { runtime "rspec", "> 0" }.first.to_ruby s.write "lib/rack.rb", "puts :LOCAL" end install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" G bundle %(config set local.rack #{lib_path("local-rack")}) @@ -484,13 +525,13 @@ RSpec.describe "bundle install with git sources" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" G lockfile0 = File.read(bundled_app_lock) FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) - update_git "rack", "0.8", :path => lib_path("local-rack") do |s| + update_git "rack", "0.8", path: lib_path("local-rack") do |s| s.add_dependency "nokogiri", "1.4.2" end @@ -506,13 +547,13 @@ RSpec.describe "bundle install with git sources" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" G lockfile0 = File.read(bundled_app_lock) FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) - update_git "rack", "0.8", :path => lib_path("local-rack") + update_git "rack", "0.8", path: lib_path("local-rack") bundle %(config set local.rack #{lib_path("local-rack")}) bundle :install @@ -526,12 +567,12 @@ RSpec.describe "bundle install with git sources" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" G bundle %(config set local.rack #{lib_path("local-rack")}) - bundle :install, :raise_on_error => false - expect(err).to match(/Cannot use local override for rack-0.8 because #{Regexp.escape(lib_path('local-rack').to_s)} does not exist/) + bundle :install, raise_on_error: false + expect(err).to match(/Cannot use local override for rack-0.8 because #{Regexp.escape(lib_path("local-rack").to_s)} does not exist/) solution = "config unset local.rack" expect(err).to match(/Run `bundle #{solution}` to remove the local override/) @@ -552,8 +593,8 @@ RSpec.describe "bundle install with git sources" do G bundle %(config set local.rack #{lib_path("local-rack")}) - bundle :install, :raise_on_error => false - expect(err).to match(/Cannot use local override for rack-0.8 at #{Regexp.escape(lib_path('local-rack').to_s)} because :branch is not specified in Gemfile/) + bundle :install, raise_on_error: false + expect(err).to match(/Cannot use local override for rack-0.8 at #{Regexp.escape(lib_path("local-rack").to_s)} because :branch is not specified in Gemfile/) solution = "config unset local.rack" expect(err).to match(/Specify a branch or run `bundle #{solution}` to remove the local override/) @@ -584,47 +625,47 @@ RSpec.describe "bundle install with git sources" do FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) - update_git "rack", "0.8", :path => lib_path("local-rack"), :branch => "another" do |s| + update_git "rack", "0.8", path: lib_path("local-rack"), branch: "another" do |s| s.write "lib/rack.rb", "puts :LOCAL" end install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" G bundle %(config set local.rack #{lib_path("local-rack")}) - bundle :install, :raise_on_error => false - expect(err).to match(/is using branch another but Gemfile specifies master/) + bundle :install, raise_on_error: false + expect(err).to match(/is using branch another but Gemfile specifies main/) end it "explodes on invalid revision on install" do build_git "rack", "0.8" - build_git "rack", "0.8", :path => lib_path("local-rack") do |s| + build_git "rack", "0.8", path: lib_path("local-rack") do |s| s.write "lib/rack.rb", "puts :LOCAL" end install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" G bundle %(config set local.rack #{lib_path("local-rack")}) - bundle :install, :raise_on_error => false + bundle :install, raise_on_error: false expect(err).to match(/The Gemfile lock is pointing to revision \w+/) end it "does not explode on invalid revision on install" do build_git "rack", "0.8" - build_git "rack", "0.8", :path => lib_path("local-rack") do |s| + build_git "rack", "0.8", path: lib_path("local-rack") do |s| s.write "lib/rack.rb", "puts :LOCAL" end install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" G bundle %(config set local.rack #{lib_path("local-rack")}) @@ -665,11 +706,11 @@ RSpec.describe "bundle install with git sources" do it "installs dependencies from git even if a newer gem is available elsewhere" do system_gems "rack-1.0.0" - build_lib "rack", "1.0", :path => lib_path("nested/bar") do |s| + build_lib "rack", "1.0", path: lib_path("nested/bar") do |s| s.write "lib/rack.rb", "puts 'WIN OVERRIDE'" end - build_git "foo", :path => lib_path("nested") do |s| + build_git "foo", path: lib_path("nested") do |s| s.add_dependency "rack", "= 1.0" end @@ -688,7 +729,7 @@ RSpec.describe "bundle install with git sources" do gem "rack", "0.9.1" G - build_git "rack", :path => lib_path("rack") + build_git "rack", path: lib_path("rack") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -704,7 +745,7 @@ RSpec.describe "bundle install with git sources" do gem "rack" G - build_git "rack", "1.2", :path => lib_path("rack") + build_git "rack", "1.2", path: lib_path("rack") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -717,8 +758,8 @@ RSpec.describe "bundle install with git sources" do describe "block syntax" do it "pulls all gems from a git block" do - build_lib "omg", :path => lib_path("hi2u/omg") - build_lib "hi2u", :path => lib_path("hi2u") + build_lib "omg", path: lib_path("hi2u/omg") + build_lib "hi2u", path: lib_path("hi2u") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -766,7 +807,7 @@ RSpec.describe "bundle install with git sources" do end it "runs the gemspec in the context of its parent directory" do - build_lib "bar", :path => lib_path("foo/bar"), :gemspec => false do |s| + build_lib "bar", path: lib_path("foo/bar"), gemspec: false do |s| s.write lib_path("foo/bar/lib/version.rb"), %(BAR_VERSION = '1.0') s.write "bar.gemspec", <<-G $:.unshift Dir.pwd @@ -781,7 +822,7 @@ RSpec.describe "bundle install with git sources" do G end - build_git "foo", :path => lib_path("foo") do |s| + build_git "foo", path: lib_path("foo") do |s| s.write "bin/foo", "" end @@ -796,7 +837,7 @@ RSpec.describe "bundle install with git sources" do end it "installs from git even if a rubygems gem is present" do - build_gem "foo", "1.0", :path => lib_path("fake_foo"), :to_system => true do |s| + build_gem "foo", "1.0", path: lib_path("fake_foo"), to_system: true do |s| s.write "lib/foo.rb", "raise 'FAIL'" end @@ -811,7 +852,7 @@ RSpec.describe "bundle install with git sources" do end it "fakes the gem out if there is no gemspec" do - build_git "foo", :gemspec => false + build_git "foo", gemspec: false install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -829,7 +870,7 @@ RSpec.describe "bundle install with git sources" do gem "foo", "1.0", :git => "omgomg" G - bundle :install, :raise_on_error => false + bundle :install, raise_on_error: false expect(err).to include("Git error:") expect(err).to include("fatal") @@ -837,7 +878,7 @@ RSpec.describe "bundle install with git sources" do end it "works when the gem path has spaces in it" do - build_git "foo", :path => lib_path("foo space-1.0") + build_git "foo", path: lib_path("foo space-1.0") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -862,41 +903,47 @@ RSpec.describe "bundle install with git sources" do s.write "lib/forced.rb", "FORCED = '1.1'" end - bundle "update", :all => true + bundle "update", all: true expect(the_bundle).to include_gems "forced 1.1" - sys_exec("git reset --hard HEAD^", :dir => lib_path("forced-1.0")) + sys_exec("git reset --hard HEAD^", dir: lib_path("forced-1.0")) - bundle "update", :all => true + bundle "update", all: true expect(the_bundle).to include_gems "forced 1.0" end it "ignores submodules if :submodule is not passed" do + # CVE-2022-39253: https://lore.kernel.org/lkml/xmqq4jw1uku5.fsf@gitster.g/ + system(*%W[git config --global protocol.file.allow always]) + build_git "submodule", "1.0" build_git "has_submodule", "1.0" do |s| s.add_dependency "submodule" end - sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0", :dir => lib_path("has_submodule-1.0") - sys_exec "git commit -m \"submodulator\"", :dir => lib_path("has_submodule-1.0") + sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0", dir: lib_path("has_submodule-1.0") + sys_exec "git commit -m \"submodulator\"", dir: lib_path("has_submodule-1.0") - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" git "#{lib_path("has_submodule-1.0")}" do gem "has_submodule" end G - expect(err).to match(/could not find gem 'submodule/i) + expect(err).to match(%r{submodule >= 0 could not be found in rubygems repository #{file_uri_for(gem_repo1)}/, cached gems or installed locally}) expect(the_bundle).not_to include_gems "has_submodule 1.0" end it "handles repos with submodules" do + # CVE-2022-39253: https://lore.kernel.org/lkml/xmqq4jw1uku5.fsf@gitster.g/ + system(*%W[git config --global protocol.file.allow always]) + build_git "submodule", "1.0" build_git "has_submodule", "1.0" do |s| s.add_dependency "submodule" end - sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0", :dir => lib_path("has_submodule-1.0") - sys_exec "git commit -m \"submodulator\"", :dir => lib_path("has_submodule-1.0") + sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0", dir: lib_path("has_submodule-1.0") + sys_exec "git commit -m \"submodulator\"", dir: lib_path("has_submodule-1.0") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -909,11 +956,14 @@ RSpec.describe "bundle install with git sources" do end it "does not warn when deiniting submodules" do + # CVE-2022-39253: https://lore.kernel.org/lkml/xmqq4jw1uku5.fsf@gitster.g/ + system(*%W[git config --global protocol.file.allow always]) + build_git "submodule", "1.0" build_git "has_submodule", "1.0" - sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0", :dir => lib_path("has_submodule-1.0") - sys_exec "git commit -m \"submodulator\"", :dir => lib_path("has_submodule-1.0") + sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0", dir: lib_path("has_submodule-1.0") + sys_exec "git commit -m \"submodulator\"", dir: lib_path("has_submodule-1.0") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -987,7 +1037,7 @@ RSpec.describe "bundle install with git sources" do FileUtils.mkdir_p(default_bundle_path) FileUtils.touch(default_bundle_path("bundler")) - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -999,11 +1049,11 @@ RSpec.describe "bundle install with git sources" do end it "does not duplicate git gem sources" do - build_lib "foo", :path => lib_path("nested/foo") - build_lib "bar", :path => lib_path("nested/bar") + build_lib "foo", path: lib_path("nested/foo") + build_lib "bar", path: lib_path("nested/bar") - build_git "foo", :path => lib_path("nested") - build_git "bar", :path => lib_path("nested") + build_git "foo", path: lib_path("nested") + build_git "bar", path: lib_path("nested") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -1016,11 +1066,11 @@ RSpec.describe "bundle install with git sources" do describe "switching sources" do it "doesn't explode when switching Path to Git sources" do - build_gem "foo", "1.0", :to_system => true do |s| + build_gem "foo", "1.0", to_system: true do |s| s.write "lib/foo.rb", "raise 'fail'" end - build_lib "foo", "1.0", :path => lib_path("bar/foo") - build_git "bar", "1.0", :path => lib_path("bar") do |s| + build_lib "foo", "1.0", path: lib_path("bar/foo") + build_git "bar", "1.0", path: lib_path("bar") do |s| s.add_dependency "foo" end @@ -1095,17 +1145,28 @@ RSpec.describe "bundle install with git sources" do G expect(out).to_not match(/Revision.*does not exist/) - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}", :ref => "deadbeef" G expect(err).to include("Revision deadbeef does not exist in the repository") end + + it "gives a helpful error message when the remote branch no longer exists" do + build_git "foo" + + install_gemfile <<-G, env: { "LANG" => "en" }, raise_on_error: false + source "#{file_uri_for(gem_repo1)}" + gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}", :branch => "deadbeef" + G + + expect(err).to include("Revision deadbeef does not exist in the repository") + end end describe "bundle install with deployment mode configured and git sources" do it "works" do - build_git "valim", :path => lib_path("valim") + build_git "valim", path: lib_path("valim") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -1136,7 +1197,7 @@ RSpec.describe "bundle install with git sources" do end bundle :install, - :requires => [lib_path("install_hooks.rb")] + requires: [lib_path("install_hooks.rb")] expect(err_without_deprecations).to eq("Ran pre-install hook: foo-1.0") end @@ -1156,7 +1217,7 @@ RSpec.describe "bundle install with git sources" do end bundle :install, - :requires => [lib_path("install_hooks.rb")] + requires: [lib_path("install_hooks.rb")] expect(err_without_deprecations).to eq("Ran post-install hook: foo-1.0") end @@ -1175,7 +1236,7 @@ RSpec.describe "bundle install with git sources" do H end - bundle :install, :requires => [lib_path("install_hooks.rb")], :raise_on_error => false + bundle :install, requires: [lib_path("install_hooks.rb")], raise_on_error: false expect(err).to include("failed for foo-1.0") end end @@ -1187,7 +1248,7 @@ RSpec.describe "bundle install with git sources" do s.extensions << "Rakefile" s.write "Rakefile", <<-RUBY task :default do - path = File.expand_path("../lib", __FILE__) + path = File.expand_path("lib", __dir__) FileUtils.mkdir_p(path) File.open("\#{path}/foo.rb", "w") do |f| f.puts "FOO = 'YES'" @@ -1213,8 +1274,8 @@ RSpec.describe "bundle install with git sources" do expect(out).to include(Pathname.glob(default_bundle_path("bundler/gems/extensions/**/foo-1.0-*")).first.to_s) end - it "does not use old extension after ref changes", :ruby_repo do - git_reader = build_git "foo", :no_default => true do |s| + it "does not use old extension after ref changes" do + git_reader = build_git "foo", no_default: true do |s| s.extensions = ["ext/extconf.rb"] s.write "ext/extconf.rb", <<-RUBY require "mkmf" @@ -1231,7 +1292,7 @@ RSpec.describe "bundle install with git sources" do void Init_foo() { rb_define_global_function("foo", &foo, 0); } C end - sys_exec("git commit -m \"commit for iteration #{i}\" ext/foo.c", :dir => git_reader.path) + sys_exec("git commit -m \"commit for iteration #{i}\" ext/foo.c", dir: git_reader.path) git_commit_sha = git_reader.ref_for("HEAD") @@ -1260,7 +1321,7 @@ RSpec.describe "bundle install with git sources" do RUBY end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -1280,7 +1341,7 @@ In Gemfile: s.extensions << "Rakefile" s.write "Rakefile", <<-RUBY task :default do - path = File.expand_path("../lib", __FILE__) + path = File.expand_path("lib", __dir__) FileUtils.mkdir_p(path) cur_time = Time.now.to_f.to_s File.open("\#{path}/foo.rb", "w") do |f| @@ -1321,7 +1382,7 @@ In Gemfile: s.extensions << "Rakefile" s.write "Rakefile", <<-RUBY task :default do - path = File.expand_path("../lib", __FILE__) + path = File.expand_path("lib", __dir__) FileUtils.mkdir_p(path) cur_time = Time.now.to_f.to_s File.open("\#{path}/foo.rb", "w") do |f| @@ -1364,7 +1425,7 @@ In Gemfile: s.extensions << "Rakefile" s.write "Rakefile", <<-RUBY task :default do - path = File.expand_path("../lib", __FILE__) + path = File.expand_path("lib", __dir__) FileUtils.mkdir_p(path) cur_time = Time.now.to_f.to_s File.open("\#{path}/foo.rb", "w") do |f| @@ -1386,7 +1447,7 @@ In Gemfile: installed_time = out - update_git("foo", :branch => "branch2") + update_git("foo", branch: "branch2") expect(installed_time).to match(/\A\d+\.\d+\z/) @@ -1436,7 +1497,42 @@ In Gemfile: end describe "without git installed" do - it "prints a better error message" do + it "prints a better error message when installing" do + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + + gem "rake", git: "https://github.com/ruby/rake" + G + + lockfile <<-L + GIT + remote: https://github.com/ruby/rake + revision: 5c60da8644a9e4f655e819252e3b6ca77f42b7af + specs: + rake (13.0.6) + + GEM + remote: https://rubygems.org/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rake! + + BUNDLED WITH + #{Bundler::VERSION} + L + + with_path_as("") do + bundle "install", raise_on_error: false + end + expect(err). + to include("You need to install git to be able to use gems from git repositories. For help installing git, please refer to GitHub's tutorial at https://help.github.com/articles/set-up-git") + end + + it "prints a better error message when updating" do build_git "foo" install_gemfile <<-G @@ -1447,7 +1543,7 @@ In Gemfile: G with_path_as("") do - bundle "update", :all => true, :raise_on_error => false + bundle "update", all: true, raise_on_error: false end expect(err). to include("You need to install git to be able to use gems from git repositories. For help installing git, please refer to GitHub's tutorial at https://help.github.com/articles/set-up-git") @@ -1466,7 +1562,7 @@ In Gemfile: bundle :cache simulate_new_machine - bundle "install", :env => { "PATH" => "" } + bundle "install", env: { "PATH" => "" } expect(out).to_not include("You need to install git to be able to use gems from git repositories.") end end @@ -1484,11 +1580,11 @@ In Gemfile: end it "installs successfully" do - build_git "foo", "1.0", :path => lib_path("foo") + build_git "foo", "1.0", path: lib_path("foo") gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "foo", :git => "#{lib_path("foo")}", :branch => "master" + gem "foo", :git => "#{lib_path("foo")}", :branch => "main" G bundle :install @@ -1502,7 +1598,7 @@ In Gemfile: let(:credentials) { "user1:password1" } it "does not display the password" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" git "https://#{credentials}@github.com/company/private-repo" do gem "foo" @@ -1518,7 +1614,7 @@ In Gemfile: let(:credentials) { "oauth_token" } it "displays the oauth scheme but not the oauth token" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" git "https://#{credentials}:x-oauth-basic@github.com/company/private-repo" do gem "foo" diff --git a/spec/bundler/install/gemfile/groups_spec.rb b/spec/bundler/install/gemfile/groups_spec.rb index c92b5dcc57..f7907a9cad 100644 --- a/spec/bundler/install/gemfile/groups_spec.rb +++ b/spec/bundler/install/gemfile/groups_spec.rb @@ -88,41 +88,32 @@ RSpec.describe "bundle install with groups" do it "installs gems in the default group" do bundle "config set --local without emo" bundle :install - expect(the_bundle).to include_gems "rack 1.0.0", :groups => [:default] + expect(the_bundle).to include_gems "rack 1.0.0", groups: [:default] end - it "respects global `without` configuration, and saves it locally", :bundler => "< 3" do - bundle "config set without emo" - bundle :install - expect(the_bundle).to include_gems "rack 1.0.0", :groups => [:default] - bundle "config list" - expect(out).to include("Set for your local app (#{bundled_app(".bundle/config")}): [:emo]") - expect(out).to include("Set for the current user (#{home(".bundle/config")}): [:emo]") - end - - it "respects global `without` configuration, but does not save it locally", :bundler => "3" do - bundle "config set without emo" + it "respects global `without` configuration, but does not save it locally" do + bundle "config set --global without emo" bundle :install - expect(the_bundle).to include_gems "rack 1.0.0", :groups => [:default] + expect(the_bundle).to include_gems "rack 1.0.0", groups: [:default] bundle "config list" expect(out).not_to include("Set for your local app (#{bundled_app(".bundle/config")}): [:emo]") expect(out).to include("Set for the current user (#{home(".bundle/config")}): [:emo]") end - it "allows running application where groups where configured by a different user", :bundler => "< 3" do + it "allows running application where groups where configured by a different user", bundler: "< 3" do bundle "config set without emo" bundle :install - bundle "exec ruby -e 'puts 42'", :env => { "BUNDLE_USER_HOME" => tmp("new_home").to_s } + bundle "exec ruby -e 'puts 42'", env: { "BUNDLE_USER_HOME" => tmp("new_home").to_s } expect(out).to include("42") end it "does not install gems from the excluded group" do bundle "config set --local without emo" bundle :install - expect(the_bundle).not_to include_gems "activesupport 2.3.5", :groups => [:default] + expect(the_bundle).not_to include_gems "activesupport 2.3.5", groups: [:default] end - it "remembers previous exclusion with `--without`", :bundler => "< 3" do + it "remembers previous exclusion with `--without`", bundler: "< 3" do bundle "install --without emo" expect(the_bundle).not_to include_gems "activesupport 2.3.5" bundle :install @@ -153,7 +144,7 @@ RSpec.describe "bundle install with groups" do bundle "config set --local without emo" bundle :install - expect(the_bundle).to include_gems "activesupport 2.3.2", :groups => [:default] + expect(the_bundle).to include_gems "activesupport 2.3.2", groups: [:default] end it "still works when BUNDLE_WITHOUT is set" do @@ -162,20 +153,20 @@ RSpec.describe "bundle install with groups" do bundle :install expect(out).not_to include("activesupport") - expect(the_bundle).to include_gems "rack 1.0.0", :groups => [:default] - expect(the_bundle).not_to include_gems "activesupport 2.3.5", :groups => [:default] + expect(the_bundle).to include_gems "rack 1.0.0", groups: [:default] + expect(the_bundle).not_to include_gems "activesupport 2.3.5", groups: [:default] ENV["BUNDLE_WITHOUT"] = nil end - it "clears --without when passed an empty list", :bundler => "< 3" do + it "clears --without when passed an empty list", bundler: "< 3" do bundle "install --without emo" bundle "install --without ''" expect(the_bundle).to include_gems "activesupport 2.3.5" end - it "doesn't clear without when nothing is passed", :bundler => "< 3" do + it "doesn't clear without when nothing is passed", bundler: "< 3" do bundle "install --without emo" bundle :install @@ -193,7 +184,7 @@ RSpec.describe "bundle install with groups" do expect(the_bundle).to include_gems "thin 1.0" end - it "installs gems from the previously requested group", :bundler => "< 3" do + it "installs gems from the previously requested group", bundler: "< 3" do bundle "install --with debugging" expect(the_bundle).to include_gems "thin 1.0" bundle :install @@ -207,26 +198,26 @@ RSpec.describe "bundle install with groups" do ENV["BUNDLE_WITH"] = nil end - it "clears --with when passed an empty list", :bundler => "< 3" do + it "clears --with when passed an empty list", bundler: "< 3" do bundle "install --with debugging" bundle "install --with ''" expect(the_bundle).not_to include_gems "thin 1.0" end - it "removes groups from without when passed at --with", :bundler => "< 3" do + it "removes groups from without when passed at --with", bundler: "< 3" do bundle "config set --local without emo" bundle "install --with emo" expect(the_bundle).to include_gems "activesupport 2.3.5" end - it "removes groups from with when passed at --without", :bundler => "< 3" do + it "removes groups from with when passed at --without", bundler: "< 3" do bundle "config set --local with debugging" - bundle "install --without debugging", :raise_on_error => false + bundle "install --without debugging", raise_on_error: false expect(the_bundle).not_to include_gem "thin 1.0" end - it "errors out when passing a group to with and without via CLI flags", :bundler => "< 3" do - bundle "install --with emo debugging --without emo", :raise_on_error => false + it "errors out when passing a group to with and without via CLI flags", bundler: "< 3" do + bundle "install --with emo debugging --without emo", raise_on_error: false expect(last_command).to be_failure expect(err).to include("The offending groups are: emo") end @@ -244,7 +235,7 @@ RSpec.describe "bundle install with groups" do expect(the_bundle).to include_gem "thin 1.0" end - it "can add and remove a group at the same time", :bundler => "< 3" do + it "can add and remove a group at the same time", bundler: "< 3" do bundle "install --with debugging --without emo" expect(the_bundle).to include_gems "thin 1.0" expect(the_bundle).not_to include_gems "activesupport 2.3.5" @@ -358,7 +349,7 @@ RSpec.describe "bundle install with groups" do G ruby <<-R - require "#{entrypoint}" + require "bundler" Bundler.setup :default Bundler.require :default puts RACK @@ -405,7 +396,7 @@ RSpec.describe "bundle install with groups" do it "does not hit the remote a second time" do FileUtils.rm_rf gem_repo2 bundle "config set --local without rack" - bundle :install, :verbose => true + bundle :install, verbose: true expect(last_command.stdboth).not_to match(/fetching/i) end end diff --git a/spec/bundler/install/gemfile/install_if_spec.rb b/spec/bundler/install/gemfile/install_if_spec.rb index 786e0e9258..c7640d07e1 100644 --- a/spec/bundler/install/gemfile/install_if_spec.rb +++ b/spec/bundler/install/gemfile/install_if_spec.rb @@ -18,7 +18,14 @@ RSpec.describe "bundle install with install_if conditionals" do expect(the_bundle).not_to include_gems("thin") expect(the_bundle).not_to include_gems("foo") - lockfile_should_be <<-L + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo1, "activesupport", "2.3.5" + c.no_checksum "foo", "1.0" + c.checksum gem_repo1, "rack", "1.0.0" + c.no_checksum "thin", "1.0" + end + + expect(lockfile).to eq <<~L GEM remote: #{file_uri_for(gem_repo1)}/ specs: @@ -36,7 +43,7 @@ RSpec.describe "bundle install with install_if conditionals" do foo rack thin - + #{checksums} BUNDLED WITH #{Bundler::VERSION} L diff --git a/spec/bundler/install/gemfile/lockfile_spec.rb b/spec/bundler/install/gemfile/lockfile_spec.rb index 313e99d0b8..4601d3e2a8 100644 --- a/spec/bundler/install/gemfile/lockfile_spec.rb +++ b/spec/bundler/install/gemfile/lockfile_spec.rb @@ -11,6 +11,11 @@ RSpec.describe "bundle install with a lockfile present" do install_gemfile(gf) end + it "touches the lockfile on install even when nothing has changed" do + subject + expect { bundle :install }.to change { bundled_app_lock.mtime } + end + context "gemfile evaluation" do let(:gf) { super() + "\n\n File.open('evals', 'a') {|f| f << %(1\n) } unless ENV['BUNDLER_SPEC_NO_APPEND']" } diff --git a/spec/bundler/install/gemfile/path_spec.rb b/spec/bundler/install/gemfile/path_spec.rb index 566fdcf65a..a57b7ee560 100644 --- a/spec/bundler/install/gemfile/path_spec.rb +++ b/spec/bundler/install/gemfile/path_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.describe "bundle install with explicit source paths" do - it "fetches gems with a global path source", :bundler => "< 3" do + it "fetches gems with a global path source", bundler: "< 3" do build_lib "foo" install_gemfile <<-G @@ -69,7 +69,7 @@ RSpec.describe "bundle install with explicit source paths" do username = "some_unexisting_user" relative_path = lib_path("foo-1.0").relative_path_from(Pathname.new("/home/#{username}").expand_path) - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem 'foo', :path => "~#{username}/#{relative_path}" G @@ -78,27 +78,30 @@ RSpec.describe "bundle install with explicit source paths" do end it "expands paths relative to Bundler.root" do - build_lib "foo", :path => bundled_app("foo-1.0") + build_lib "foo", path: bundled_app("foo-1.0") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem 'foo', :path => "./foo-1.0" G - expect(the_bundle).to include_gems("foo 1.0", :dir => bundled_app("subdir").mkpath) + expect(the_bundle).to include_gems("foo 1.0", dir: bundled_app("subdir").mkpath) end it "sorts paths consistently on install and update when they start with ./" do - build_lib "demo", :path => lib_path("demo") - build_lib "aaa", :path => lib_path("demo/aaa") + build_lib "demo", path: lib_path("demo") + build_lib "aaa", path: lib_path("demo/aaa") - gemfile = <<-G + gemfile lib_path("demo/Gemfile"), <<-G source "#{file_uri_for(gem_repo1)}" gemspec gem "aaa", :path => "./aaa" G - File.open(lib_path("demo/Gemfile"), "w") {|f| f.puts gemfile } + checksums = checksums_section_when_existing do |c| + c.no_checksum "aaa", "1.0" + c.no_checksum "demo", "1.0" + end lockfile = <<~L PATH @@ -121,23 +124,23 @@ RSpec.describe "bundle install with explicit source paths" do DEPENDENCIES aaa! demo! - + #{checksums} BUNDLED WITH #{Bundler::VERSION} L - bundle :install, :dir => lib_path("demo") - expect(lib_path("demo/Gemfile.lock")).to have_lockfile(lockfile) - bundle :update, :all => true, :dir => lib_path("demo") - expect(lib_path("demo/Gemfile.lock")).to have_lockfile(lockfile) + bundle :install, dir: lib_path("demo") + expect(lib_path("demo/Gemfile.lock")).to read_as(lockfile) + bundle :update, all: true, dir: lib_path("demo") + expect(lib_path("demo/Gemfile.lock")).to read_as(lockfile) end it "expands paths when comparing locked paths to Gemfile paths" do - build_lib "foo", :path => bundled_app("foo-1.0") + build_lib "foo", path: bundled_app("foo-1.0") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem 'foo', :path => File.expand_path("../foo-1.0", __FILE__) + gem 'foo', :path => File.expand_path("foo-1.0", __dir__) G bundle "config set --local frozen true" @@ -147,11 +150,11 @@ RSpec.describe "bundle install with explicit source paths" do it "installs dependencies from the path even if a newer gem is available elsewhere" do system_gems "rack-1.0.0" - build_lib "rack", "1.0", :path => lib_path("nested/bar") do |s| + build_lib "rack", "1.0", path: lib_path("nested/bar") do |s| s.write "lib/rack.rb", "puts 'WIN OVERRIDE'" end - build_lib "foo", :path => lib_path("nested") do |s| + build_lib "foo", path: lib_path("nested") do |s| s.add_dependency "rack", "= 1.0" end @@ -165,15 +168,15 @@ RSpec.describe "bundle install with explicit source paths" do end it "works" do - build_gem "foo", "1.0.0", :to_system => true do |s| + build_gem "foo", "1.0.0", to_system: true do |s| s.write "lib/foo.rb", "puts 'FAIL'" end - build_lib "omg", "1.0", :path => lib_path("omg") do |s| + build_lib "omg", "1.0", path: lib_path("omg") do |s| s.add_dependency "foo" end - build_lib "foo", "1.0.0", :path => lib_path("omg/foo") + build_lib "foo", "1.0.0", path: lib_path("omg/foo") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -183,15 +186,81 @@ RSpec.describe "bundle install with explicit source paths" do expect(the_bundle).to include_gems "foo 1.0" end + it "works when using prereleases of 0.0.0" do + build_lib "foo", "0.0.0.dev", path: lib_path("foo") + + gemfile <<~G + source "#{file_uri_for(gem_repo1)}" + gem "foo", :path => "#{lib_path("foo")}" + G + + lockfile <<~L + PATH + remote: #{lib_path("foo")} + specs: + foo (0.0.0.dev) + + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle :install + + expect(the_bundle).to include_gems "foo 0.0.0.dev" + end + + it "works when using uppercase prereleases of 0.0.0" do + build_lib "foo", "0.0.0.SNAPSHOT", path: lib_path("foo") + + gemfile <<~G + source "#{file_uri_for(gem_repo1)}" + gem "foo", :path => "#{lib_path("foo")}" + G + + lockfile <<~L + PATH + remote: #{lib_path("foo")} + specs: + foo (0.0.0.SNAPSHOT) + + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle :install + + expect(the_bundle).to include_gems "foo 0.0.0.SNAPSHOT" + end + it "handles downgrades" do - build_lib "omg", "2.0", :path => lib_path("omg") + build_lib "omg", "2.0", path: lib_path("omg") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "omg", :path => "#{lib_path("omg")}" G - build_lib "omg", "1.0", :path => lib_path("omg") + build_lib "omg", "1.0", path: lib_path("omg") bundle :install @@ -199,7 +268,7 @@ RSpec.describe "bundle install with explicit source paths" do end it "prefers gemspecs closer to the path root" do - build_lib "premailer", "1.0.0", :path => lib_path("premailer") do |s| + build_lib "premailer", "1.0.0", path: lib_path("premailer") do |s| s.write "gemfiles/ruby187.gemspec", <<-G Gem::Specification.new do |s| s.name = 'premailer' @@ -232,7 +301,7 @@ RSpec.describe "bundle install with explicit source paths" do G end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("foo-1.0")}" G @@ -244,24 +313,78 @@ RSpec.describe "bundle install with explicit source paths" do end it "supports gemspec syntax" do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.add_dependency "rack", "1.0" end - gemfile = <<-G + gemfile lib_path("foo/Gemfile"), <<-G source "#{file_uri_for(gem_repo1)}" gemspec G - File.open(lib_path("foo/Gemfile"), "w") {|f| f.puts gemfile } + bundle "install", dir: lib_path("foo") + expect(the_bundle).to include_gems "foo 1.0", dir: lib_path("foo") + expect(the_bundle).to include_gems "rack 1.0", dir: lib_path("foo") + end + + it "does not unlock dependencies of path sources" do + build_repo4 do + build_gem "graphql", "2.0.15" + build_gem "graphql", "2.0.16" + end + + build_lib "foo", "0.1.0", path: lib_path("foo") do |s| + s.add_dependency "graphql", "~> 2.0" + end + + gemfile_path = lib_path("foo/Gemfile") + + gemfile gemfile_path, <<-G + source "#{file_uri_for(gem_repo4)}" + gemspec + G + + lockfile_path = lib_path("foo/Gemfile.lock") - bundle "install", :dir => lib_path("foo") - expect(the_bundle).to include_gems "foo 1.0", :dir => lib_path("foo") - expect(the_bundle).to include_gems "rack 1.0", :dir => lib_path("foo") + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "0.1.0" + c.checksum gem_repo4, "graphql", "2.0.15" + end + + original_lockfile = <<~L + PATH + remote: . + specs: + foo (0.1.0) + graphql (~> 2.0) + + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + graphql (2.0.15) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + lockfile lockfile_path, original_lockfile + + build_lib "foo", "0.1.1", path: lib_path("foo") do |s| + s.add_dependency "graphql", "~> 2.0" + end + + bundle "install", dir: lib_path("foo") + expect(lockfile_path).to read_as(original_lockfile.gsub("foo (0.1.0)", "foo (0.1.1)")) end it "supports gemspec syntax with an alternative path" do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.add_dependency "rack", "1.0" end @@ -275,48 +398,48 @@ RSpec.describe "bundle install with explicit source paths" do end it "doesn't automatically unlock dependencies when using the gemspec syntax" do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.add_dependency "rack", ">= 1.0" end - install_gemfile lib_path("foo/Gemfile"), <<-G, :dir => lib_path("foo") + install_gemfile lib_path("foo/Gemfile"), <<-G, dir: lib_path("foo") source "#{file_uri_for(gem_repo1)}" gemspec G - build_gem "rack", "1.0.1", :to_system => true + build_gem "rack", "1.0.1", to_system: true - bundle "install", :dir => lib_path("foo") + bundle "install", dir: lib_path("foo") - expect(the_bundle).to include_gems "foo 1.0", :dir => lib_path("foo") - expect(the_bundle).to include_gems "rack 1.0", :dir => lib_path("foo") + expect(the_bundle).to include_gems "foo 1.0", dir: lib_path("foo") + expect(the_bundle).to include_gems "rack 1.0", dir: lib_path("foo") end it "doesn't automatically unlock dependencies when using the gemspec syntax and the gem has development dependencies" do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.add_dependency "rack", ">= 1.0" s.add_development_dependency "activesupport" end - install_gemfile lib_path("foo/Gemfile"), <<-G, :dir => lib_path("foo") + install_gemfile lib_path("foo/Gemfile"), <<-G, dir: lib_path("foo") source "#{file_uri_for(gem_repo1)}" gemspec G - build_gem "rack", "1.0.1", :to_system => true + build_gem "rack", "1.0.1", to_system: true - bundle "install", :dir => lib_path("foo") + bundle "install", dir: lib_path("foo") - expect(the_bundle).to include_gems "foo 1.0", :dir => lib_path("foo") - expect(the_bundle).to include_gems "rack 1.0", :dir => lib_path("foo") + expect(the_bundle).to include_gems "foo 1.0", dir: lib_path("foo") + expect(the_bundle).to include_gems "rack 1.0", dir: lib_path("foo") end it "raises if there are multiple gemspecs" do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.write "bar.gemspec", build_spec("bar", "1.0").first.to_ruby end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gemspec :path => "#{lib_path("foo")}" G @@ -326,7 +449,7 @@ RSpec.describe "bundle install with explicit source paths" do end it "allows :name to be specified to resolve ambiguity" do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.write "bar.gemspec" end @@ -343,7 +466,7 @@ RSpec.describe "bundle install with explicit source paths" do s.executables = "foobar" end - install_gemfile <<-G, :verbose => true + install_gemfile <<-G, verbose: true source "#{file_uri_for(gem_repo1)}" path "#{lib_path("foo-1.0")}" do gem 'foo' @@ -397,9 +520,9 @@ RSpec.describe "bundle install with explicit source paths" do end it "keeps source pinning" do - build_lib "foo", "1.0", :path => lib_path("foo") - build_lib "omg", "1.0", :path => lib_path("omg") - build_lib "foo", "1.0", :path => lib_path("omg/foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") + build_lib "omg", "1.0", path: lib_path("omg") + build_lib "foo", "1.0", path: lib_path("omg/foo") do |s| s.write "lib/foo.rb", "puts 'FAIL'" end @@ -413,7 +536,7 @@ RSpec.describe "bundle install with explicit source paths" do end it "works when the path does not have a gemspec" do - build_lib "foo", :gemspec => false + build_lib "foo", gemspec: false gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -451,17 +574,17 @@ RSpec.describe "bundle install with explicit source paths" do gem 'net-ssh', '1.0' G - bundle :check, :env => { "DEBUG" => "1" } + bundle :check, env: { "DEBUG" => "1" } expect(out).to match(/using resolution from the lockfile/) expect(the_bundle).to include_gems "rack-obama 1.0", "net-ssh 1.0" end it "source path gems w/deps don't re-resolve without changes" do - build_lib "rack-obama", "1.0", :path => lib_path("omg") do |s| + build_lib "rack-obama", "1.0", path: lib_path("omg") do |s| s.add_dependency "yard" end - build_lib "net-ssh", "1.0", :path => lib_path("omg") do |s| + build_lib "net-ssh", "1.0", path: lib_path("omg") do |s| s.add_dependency "yard" end @@ -471,7 +594,7 @@ RSpec.describe "bundle install with explicit source paths" do gem 'net-ssh', :path => "#{lib_path("omg")}" G - bundle :check, :env => { "DEBUG" => "1" } + bundle :check, env: { "DEBUG" => "1" } expect(out).to match(/using resolution from the lockfile/) expect(the_bundle).to include_gems "rack-obama 1.0", "net-ssh 1.0" end @@ -493,10 +616,10 @@ RSpec.describe "bundle install with explicit source paths" do describe "when the gem version in the path is updated" do before :each do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.add_dependency "bar" end - build_lib "bar", "1.0", :path => lib_path("foo/bar") + build_lib "bar", "1.0", path: lib_path("foo/bar") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -505,7 +628,7 @@ RSpec.describe "bundle install with explicit source paths" do end it "unlocks all gems when the top level gem is updated" do - build_lib "foo", "2.0", :path => lib_path("foo") do |s| + build_lib "foo", "2.0", path: lib_path("foo") do |s| s.add_dependency "bar" end @@ -515,7 +638,7 @@ RSpec.describe "bundle install with explicit source paths" do end it "unlocks all gems when a child dependency gem is updated" do - build_lib "bar", "2.0", :path => lib_path("foo/bar") + build_lib "bar", "2.0", path: lib_path("foo/bar") bundle "install" @@ -525,7 +648,7 @@ RSpec.describe "bundle install with explicit source paths" do describe "when dependencies in the path are updated" do before :each do - build_lib "foo", "1.0", :path => lib_path("foo") + build_lib "foo", "1.0", path: lib_path("foo") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -534,7 +657,7 @@ RSpec.describe "bundle install with explicit source paths" do end it "gets dependencies that are updated in the path" do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.add_dependency "rack" end @@ -544,7 +667,7 @@ RSpec.describe "bundle install with explicit source paths" do end it "keeps using the same version if it's compatible" do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.add_dependency "rack", "0.9.1" end @@ -552,7 +675,12 @@ RSpec.describe "bundle install with explicit source paths" do expect(the_bundle).to include_gems "rack 0.9.1" - lockfile_should_be <<-G + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + c.checksum gem_repo1, "rack", "0.9.1" + end + + expect(lockfile).to eq <<~G PATH remote: #{lib_path("foo")} specs: @@ -569,18 +697,18 @@ RSpec.describe "bundle install with explicit source paths" do DEPENDENCIES foo! - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.add_dependency "rack" end bundle "install" - lockfile_should_be <<-G + expect(lockfile).to eq <<~G PATH remote: #{lib_path("foo")} specs: @@ -597,7 +725,7 @@ RSpec.describe "bundle install with explicit source paths" do DEPENDENCIES foo! - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G @@ -606,7 +734,7 @@ RSpec.describe "bundle install with explicit source paths" do end it "keeps using the same version even when another dependency is added" do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.add_dependency "rack", "0.9.1" end @@ -614,7 +742,12 @@ RSpec.describe "bundle install with explicit source paths" do expect(the_bundle).to include_gems "rack 0.9.1" - lockfile_should_be <<-G + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + c.checksum gem_repo1, "rack", "0.9.1" + end + + expect(lockfile).to eq <<~G PATH remote: #{lib_path("foo")} specs: @@ -631,53 +764,107 @@ RSpec.describe "bundle install with explicit source paths" do DEPENDENCIES foo! - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.add_dependency "rack" - s.add_dependency "rake", "13.0.1" + s.add_dependency "rake", rake_version end bundle "install" - lockfile_should_be <<-G + checksums.checksum gem_repo1, "rake", rake_version + + expect(lockfile).to eq <<~G PATH remote: #{lib_path("foo")} specs: foo (1.0) rack - rake (= 13.0.1) + rake (= #{rake_version}) GEM remote: #{file_uri_for(gem_repo1)}/ specs: rack (0.9.1) - rake (13.0.1) + rake (#{rake_version}) PLATFORMS #{lockfile_platforms} DEPENDENCIES foo! - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G expect(the_bundle).to include_gems "rack 0.9.1" end + + it "does not remove existing ruby platform" do + build_lib "foo", "1.0", path: lib_path("foo") do |s| + s.add_dependency "rack", "0.9.1" + end + + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + end + + lockfile <<~L + PATH + remote: #{lib_path("foo")} + specs: + foo (1.0) + + PLATFORMS + #{lockfile_platforms("ruby")} + + DEPENDENCIES + foo! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "lock" + + checksums.no_checksum "rack", "0.9.1" + + expect(lockfile).to eq <<~G + PATH + remote: #{lib_path("foo")} + specs: + foo (1.0) + rack (= 0.9.1) + + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: + rack (0.9.1) + + PLATFORMS + #{lockfile_platforms("ruby")} + + DEPENDENCIES + foo! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end end describe "switching sources" do it "doesn't switch pinned git sources to rubygems when pinning the parent gem to a path source" do - build_gem "foo", "1.0", :to_system => true do |s| + build_gem "foo", "1.0", to_system: true do |s| s.write "lib/foo.rb", "raise 'fail'" end - build_lib "foo", "1.0", :path => lib_path("bar/foo") - build_git "bar", "1.0", :path => lib_path("bar") do |s| + build_lib "foo", "1.0", path: lib_path("bar/foo") + build_git "bar", "1.0", path: lib_path("bar") do |s| s.add_dependency "foo" end @@ -695,8 +882,8 @@ RSpec.describe "bundle install with explicit source paths" do end it "switches the source when the gem existed in rubygems and the path was already being used for another gem" do - build_lib "foo", "1.0", :path => lib_path("foo") - build_gem "bar", "1.0", :to_bundle => true do |s| + build_lib "foo", "1.0", path: lib_path("foo") + build_gem "bar", "1.0", to_bundle: true do |s| s.write "lib/bar.rb", "raise 'fail'" end @@ -708,7 +895,7 @@ RSpec.describe "bundle install with explicit source paths" do end G - build_lib "bar", "1.0", :path => lib_path("foo/bar") + build_lib "bar", "1.0", path: lib_path("foo/bar") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -724,19 +911,17 @@ RSpec.describe "bundle install with explicit source paths" do describe "when there are both a gemspec and remote gems" do it "doesn't query rubygems for local gemspec name" do - build_lib "private_lib", "2.2", :path => lib_path("private_lib") - gemfile = <<-G + build_lib "private_lib", "2.2", path: lib_path("private_lib") + gemfile lib_path("private_lib/Gemfile"), <<-G source "http://localgemserver.test" gemspec gem 'rack' G - File.open(lib_path("private_lib/Gemfile"), "w") {|f| f.puts gemfile } - - bundle :install, :env => { "DEBUG" => "1" }, :artifice => "endpoint", :dir => lib_path("private_lib") + bundle :install, env: { "DEBUG" => "1" }, artifice: "endpoint", dir: lib_path("private_lib") expect(out).to match(%r{^HTTP GET http://localgemserver\.test/api/v1/dependencies\?gems=rack$}) expect(out).not_to match(/^HTTP GET.*private_lib/) - expect(the_bundle).to include_gems "private_lib 2.2", :dir => lib_path("private_lib") - expect(the_bundle).to include_gems "rack 1.0", :dir => lib_path("private_lib") + expect(the_bundle).to include_gems "private_lib 2.2", dir: lib_path("private_lib") + expect(the_bundle).to include_gems "rack 1.0", dir: lib_path("private_lib") end end @@ -757,7 +942,7 @@ RSpec.describe "bundle install with explicit source paths" do end bundle :install, - :requires => [lib_path("install_hooks.rb")] + requires: [lib_path("install_hooks.rb")] expect(err_without_deprecations).to eq("Ran pre-install hook: foo-1.0") end @@ -777,7 +962,7 @@ RSpec.describe "bundle install with explicit source paths" do end bundle :install, - :requires => [lib_path("install_hooks.rb")] + requires: [lib_path("install_hooks.rb")] expect(err_without_deprecations).to eq("Ran post-install hook: foo-1.0") end @@ -796,7 +981,7 @@ RSpec.describe "bundle install with explicit source paths" do H end - bundle :install, :requires => [lib_path("install_hooks.rb")], :raise_on_error => false + bundle :install, requires: [lib_path("install_hooks.rb")], raise_on_error: false expect(err).to include("failed for foo-1.0") end diff --git a/spec/bundler/install/gemfile/platform_spec.rb b/spec/bundler/install/gemfile/platform_spec.rb index 7cd06b7e08..d90dacdc02 100644 --- a/spec/bundler/install/gemfile/platform_spec.rb +++ b/spec/bundler/install/gemfile/platform_spec.rb @@ -50,7 +50,7 @@ RSpec.describe "bundle install across platforms" do expect(the_bundle).to include_gems "platform_specific 1.0 JAVA" end - it "pulls the pure ruby version on jruby if the java platform is not present in the lockfile and bundler is run in frozen mode", :jruby do + it "pulls the pure ruby version on jruby if the java platform is not present in the lockfile and bundler is run in frozen mode", :jruby_only do lockfile <<-G GEM remote: #{file_uri_for(gem_repo1)} @@ -75,6 +75,81 @@ RSpec.describe "bundle install across platforms" do expect(the_bundle).to include_gems "platform_specific 1.0 RUBY" end + context "on universal Rubies" do + before do + build_repo4 do + build_gem "darwin_single_arch" do |s| + s.platform = "ruby" + s.write "lib/darwin_single_arch.rb", "DARWIN_SINGLE_ARCH = '1.0 RUBY'" + end + build_gem "darwin_single_arch" do |s| + s.platform = "arm64-darwin" + s.write "lib/darwin_single_arch.rb", "DARWIN_SINGLE_ARCH = '1.0 arm64-darwin'" + end + build_gem "darwin_single_arch" do |s| + s.platform = "x86_64-darwin" + s.write "lib/darwin_single_arch.rb", "DARWIN_SINGLE_ARCH = '1.0 x86_64-darwin'" + end + end + end + + it "pulls in the correct architecture gem" do + lockfile <<-G + GEM + remote: #{file_uri_for(gem_repo4)} + specs: + darwin_single_arch (1.0) + darwin_single_arch (1.0-arm64-darwin) + darwin_single_arch (1.0-x86_64-darwin) + + PLATFORMS + ruby + + DEPENDENCIES + darwin_single_arch + G + + simulate_platform "universal-darwin-21" + simulate_ruby_platform "universal.x86_64-darwin21" do + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + + gem "darwin_single_arch" + G + + expect(the_bundle).to include_gems "darwin_single_arch 1.0 x86_64-darwin" + end + end + + it "pulls in the correct architecture gem on arm64e macOS Ruby" do + lockfile <<-G + GEM + remote: #{file_uri_for(gem_repo4)} + specs: + darwin_single_arch (1.0) + darwin_single_arch (1.0-arm64-darwin) + darwin_single_arch (1.0-x86_64-darwin) + + PLATFORMS + ruby + + DEPENDENCIES + darwin_single_arch + G + + simulate_platform "universal-darwin-21" + simulate_ruby_platform "universal.arm64e-darwin21" do + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + + gem "darwin_single_arch" + G + + expect(the_bundle).to include_gems "darwin_single_arch 1.0 arm64-darwin" + end + end + end + it "works with gems that have different dependencies" do simulate_platform "java" install_gemfile <<-G @@ -86,13 +161,13 @@ RSpec.describe "bundle install across platforms" do expect(the_bundle).to include_gems "nokogiri 1.4.2 JAVA", "weakling 0.0.3" simulate_new_machine - - simulate_platform "ruby" + bundle "config set --local force_ruby_platform true" bundle "install" expect(the_bundle).to include_gems "nokogiri 1.4.2" expect(the_bundle).not_to include_gems "weakling" + simulate_new_machine simulate_platform "java" bundle "install" @@ -128,7 +203,16 @@ RSpec.describe "bundle install across platforms" do gem "pry" G - lockfile_should_be <<-L + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "coderay", "1.1.2" + c.checksum gem_repo4, "empyrean", "0.1.0" + c.checksum gem_repo4, "ffi", "1.9.23", "java" + c.checksum gem_repo4, "method_source", "0.9.0" + c.checksum gem_repo4, "pry", "0.11.3", "java" + c.checksum gem_repo4, "spoon", "0.0.6" + end + + expect(lockfile).to eq <<~L GEM remote: #{file_uri_for(gem_repo4)}/ specs: @@ -149,14 +233,14 @@ RSpec.describe "bundle install across platforms" do DEPENDENCIES empyrean (= 0.1.0) pry - + #{checksums} BUNDLED WITH #{Bundler::VERSION} L bundle "lock --add-platform ruby" - good_lockfile = strip_whitespace(<<-L) + good_lockfile = <<~L GEM remote: #{file_uri_for(gem_repo4)}/ specs: @@ -181,14 +265,14 @@ RSpec.describe "bundle install across platforms" do DEPENDENCIES empyrean (= 0.1.0) pry - + #{checksums} BUNDLED WITH #{Bundler::VERSION} L - lockfile_should_be good_lockfile + expect(lockfile).to eq good_lockfile - bad_lockfile = strip_whitespace <<-L + bad_lockfile = <<~L GEM remote: #{file_uri_for(gem_repo4)}/ specs: @@ -214,31 +298,31 @@ RSpec.describe "bundle install across platforms" do DEPENDENCIES empyrean (= 0.1.0) pry - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + 1.16.1 L aggregate_failures do lockfile bad_lockfile - bundle :install - lockfile_should_be good_lockfile + bundle :install, env: { "BUNDLER_VERSION" => Bundler::VERSION } + expect(lockfile).to eq good_lockfile lockfile bad_lockfile - bundle :update, :all => true - lockfile_should_be good_lockfile + bundle :update, all: true, env: { "BUNDLER_VERSION" => Bundler::VERSION } + expect(lockfile).to eq good_lockfile lockfile bad_lockfile - bundle "update ffi" - lockfile_should_be good_lockfile + bundle "update ffi", env: { "BUNDLER_VERSION" => Bundler::VERSION } + expect(lockfile).to eq good_lockfile lockfile bad_lockfile - bundle "update empyrean" - lockfile_should_be good_lockfile + bundle "update empyrean", env: { "BUNDLER_VERSION" => Bundler::VERSION } + expect(lockfile).to eq good_lockfile lockfile bad_lockfile - bundle :lock - lockfile_should_be good_lockfile + bundle :lock, env: { "BUNDLER_VERSION" => Bundler::VERSION } + expect(lockfile).to eq good_lockfile end end @@ -288,6 +372,11 @@ RSpec.describe "bundle install across platforms" do end it "keeps existing platforms when installing with force_ruby_platform" do + checksums = checksums_section do |c| + c.no_checksum "platform_specific", "1.0" + c.no_checksum "platform_specific", "1.0", "java" + end + lockfile <<-G GEM remote: #{file_uri_for(gem_repo1)}/ @@ -299,6 +388,7 @@ RSpec.describe "bundle install across platforms" do DEPENDENCIES platform_specific + #{checksums} G bundle "config set --local force_ruby_platform true" @@ -308,9 +398,11 @@ RSpec.describe "bundle install across platforms" do gem "platform_specific" G + checksums.checksum gem_repo1, "platform_specific", "1.0" + expect(the_bundle).to include_gem "platform_specific 1.0 RUBY" - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo1)}/ specs: @@ -323,7 +415,7 @@ RSpec.describe "bundle install across platforms" do DEPENDENCIES platform_specific - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G @@ -332,8 +424,6 @@ end RSpec.describe "bundle install with platform conditionals" do it "installs gems tagged w/ the current platforms" do - skip "platform issues" if Gem.win_platform? - install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -358,9 +448,50 @@ RSpec.describe "bundle install with platform conditionals" do expect(the_bundle).not_to include_gems "nokogiri 1.4.2" end - it "installs gems tagged w/ the current platforms inline" do - skip "platform issues" if Gem.win_platform? + it "installs gems tagged w/ another platform but also dependent on the current one transitively" do + build_repo4 do + build_gem "activesupport", "6.1.4.1" do |s| + s.add_dependency "tzinfo", "~> 2.0" + end + + build_gem "tzinfo", "2.0.4" + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + gem "activesupport" + + platforms :#{not_local_tag} do + gem "tzinfo", "~> 1.2" + end + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + activesupport (6.1.4.1) + tzinfo (~> 2.0) + tzinfo (2.0.4) + + PLATFORMS + #{local_platform} + + DEPENDENCIES + activesupport + tzinfo (~> 1.2) + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install --verbose" + + expect(the_bundle).to include_gems "tzinfo 2.0.4" + end + + it "installs gems tagged w/ the current platforms inline" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "nokogiri", :platforms => :#{local_tag} @@ -379,8 +510,6 @@ RSpec.describe "bundle install with platform conditionals" do end it "installs gems tagged w/ the current platform inline" do - skip "platform issues" if Gem.win_platform? - install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "nokogiri", :platform => :#{local_tag} @@ -410,7 +539,7 @@ RSpec.describe "bundle install with platform conditionals" do end it "does not attempt to install gems from :rbx when using --local" do - simulate_platform "ruby" + bundle "config set --local force_ruby_platform true" gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -422,12 +551,10 @@ RSpec.describe "bundle install with platform conditionals" do end it "does not attempt to install gems from other rubies when using --local" do - simulate_platform "ruby" - other_ruby_version_tag = RUBY_VERSION =~ /^1\.8/ ? :ruby_19 : :ruby_18 - + bundle "config set --local force_ruby_platform true" gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "some_gem", platform: :#{other_ruby_version_tag} + gem "some_gem", platform: :ruby_22 G bundle "install --local" @@ -435,19 +562,19 @@ RSpec.describe "bundle install with platform conditionals" do end it "does not print a warning when a dependency is unused on a platform different from the current one" do - simulate_platform "ruby" + bundle "config set --local force_ruby_platform true" gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "rack", :platform => [:mingw, :mswin, :x64_mingw, :jruby] + gem "rack", :platform => [:windows, :mswin, :mswin64, :mingw, :x64_mingw, :jruby] G bundle "install" expect(err).to be_empty - lockfile_should_be <<-L + expect(lockfile).to eq <<~L GEM remote: #{file_uri_for(gem_repo1)}/ specs: @@ -457,16 +584,36 @@ RSpec.describe "bundle install with platform conditionals" do DEPENDENCIES rack - + #{checksums_section_when_existing} BUNDLED WITH #{Bundler::VERSION} L end + + it "resolves fine when a dependency is unused on a platform different from the current one, but reintroduced transitively" do + bundle "config set --local force_ruby_platform true" + + build_repo4 do + build_gem "listen", "3.7.1" do |s| + s.add_dependency "ffi" + end + + build_gem "ffi", "1.15.5" + end + + install_gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "listen" + gem "ffi", :platform => :windows + G + expect(err).to be_empty + end end RSpec.describe "when a gem has no architecture" do it "still installs correctly" do - simulate_platform mswin + simulate_platform x86_mswin32 build_repo2 do # The rcov gem is platform mswin32, but has no arch @@ -482,7 +629,7 @@ RSpec.describe "when a gem has no architecture" do gem "rcov" G - bundle :install, :artifice => "windows", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + bundle :install, artifice: "windows", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } expect(the_bundle).to include_gems "rcov 1.0.0" end end diff --git a/spec/bundler/install/gemfile/ruby_spec.rb b/spec/bundler/install/gemfile/ruby_spec.rb index fd4300c042..b64d633fd3 100644 --- a/spec/bundler/install/gemfile/ruby_spec.rb +++ b/spec/bundler/install/gemfile/ruby_spec.rb @@ -11,13 +11,13 @@ RSpec.describe "ruby requirement" do it "allows adding gems" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - ruby "#{RUBY_VERSION}" + ruby "#{Gem.ruby_version}" gem "rack" G install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - ruby "#{RUBY_VERSION}" + ruby "#{Gem.ruby_version}" gem "rack" gem "rack-obama" G @@ -28,7 +28,7 @@ RSpec.describe "ruby requirement" do it "allows removing the ruby version requirement" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - ruby "~> #{RUBY_VERSION}" + ruby "~> #{Gem.ruby_version}" gem "rack" G @@ -46,18 +46,16 @@ RSpec.describe "ruby requirement" do it "allows changing the ruby version requirement to something compatible" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - ruby ">= 1.0.0" + ruby ">= #{current_ruby_minor}" gem "rack" G allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) expect(locked_ruby_version).to eq(Bundler::RubyVersion.system) - simulate_ruby_version "5100" - install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - ruby ">= 1.0.1" + ruby ">= #{Gem.ruby_version}" gem "rack" G @@ -72,25 +70,41 @@ RSpec.describe "ruby requirement" do gem "rack" G - allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) - expect(locked_ruby_version).to eq(Bundler::RubyVersion.system) + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: + rack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rack + + RUBY VERSION + ruby 2.1.4p422 + + BUNDLED WITH + #{Bundler::VERSION} + L - simulate_ruby_version "5100" + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - ruby ">= 5000.0" + ruby ">= #{current_ruby_minor}" gem "rack" G expect(the_bundle).to include_gems "rack 1.0.0" - expect(locked_ruby_version.versions).to eq(["5100"]) + expect(locked_ruby_version).to eq(Bundler::RubyVersion.system) end it "allows requirements with trailing whitespace" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - ruby "#{RUBY_VERSION}\\n \t\\n" + ruby "#{Gem.ruby_version}\\n \t\\n" gem "rack" G @@ -98,7 +112,7 @@ RSpec.describe "ruby requirement" do end it "fails gracefully with malformed requirements" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" ruby ">= 0", "-.\\0" gem "rack" @@ -106,4 +120,38 @@ RSpec.describe "ruby requirement" do expect(err).to include("There was an error parsing") # i.e. DSL error, not error template end + + it "allows picking up ruby version from a file" do + create_file ".ruby-version", Gem.ruby_version.to_s + + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + ruby file: ".ruby-version" + gem "rack" + G + + expect(lockfile).to include("RUBY VERSION") + end + + it "reads the ruby version file from the right folder when nested Gemfiles are involved" do + create_file ".ruby-version", Gem.ruby_version.to_s + + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + ruby file: ".ruby-version" + gem "rack" + G + + nested_dir = bundled_app(".ruby-lsp") + + FileUtils.mkdir nested_dir + + create_file ".ruby-lsp/Gemfile", <<-G + eval_gemfile(File.expand_path("../Gemfile", __dir__)) + G + + bundle "install", dir: nested_dir + + expect(bundled_app(".ruby-lsp/Gemfile.lock").read).to include("RUBY VERSION") + end end diff --git a/spec/bundler/install/gemfile/sources_spec.rb b/spec/bundler/install/gemfile/sources_spec.rb index 8c225afb11..a5ba76f4d9 100644 --- a/spec/bundler/install/gemfile/sources_spec.rb +++ b/spec/bundler/install/gemfile/sources_spec.rb @@ -27,18 +27,72 @@ RSpec.describe "bundle install with gems on multiple sources" do G end - it "warns about ambiguous gems, but installs anyway, prioritizing sources last to first", :bundler => "< 3" do - bundle :install, :artifice => "compact_index" + it "refuses to install mismatched checksum because one gem has been tampered with", bundler: "< 3" do + lockfile <<~L + GEM + remote: https://gem.repo3/ + remote: https://gem.repo1/ + specs: + rack (1.0.0) - expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") - expect(err).to include("Installed from: https://gem.repo1") - expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", :source => "remote1") + PLATFORMS + #{local_platform} + + DEPENDENCIES + depends_on_rack! + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle :install, artifice: "compact_index", raise_on_error: false + + expect(exitstatus).to eq(37) + expect(err).to eq <<~E.strip + [DEPRECATED] Your Gemfile contains multiple global sources. Using `source` more than once without a block is a security risk, and may result in installing unexpected gems. To resolve this warning, use a block to indicate which gems should come from the secondary source. + Bundler found mismatched checksums. This is a potential security risk. + #{checksum_to_lock(gem_repo1, "rack", "1.0.0")} + from the API at https://gem.repo1/ + #{checksum_to_lock(gem_repo3, "rack", "1.0.0")} + from the API at https://gem.repo3/ + + Mismatched checksums each have an authoritative source: + 1. the API at https://gem.repo1/ + 2. the API at https://gem.repo3/ + You may need to alter your Gemfile sources to resolve this issue. + + To ignore checksum security warnings, disable checksum validation with + `bundle config set --local disable_checksum_validation true` + E end - it "fails", :bundler => "3" do - bundle :instal, :artifice => "compact_index", :raise_on_error => false - expect(err).to include("Each source after the first must include a block") - expect(exitstatus).to eq(4) + context "when checksum validation is disabled" do + before do + bundle "config set --local disable_checksum_validation true" + end + + it "warns about ambiguous gems, but installs anyway, prioritizing sources last to first", bundler: "< 3" do + bundle :install, artifice: "compact_index" + + expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") + expect(err).to include("Installed from: https://gem.repo1") + expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", source: "remote1") + end + + it "does not use the full index unnecessarily", bundler: "< 3" do + bundle :install, artifice: "compact_index", verbose: true + + expect(out).to include("https://gem.repo1/versions") + expect(out).to include("https://gem.repo3/versions") + expect(out).not_to include("https://gem.repo1/quick/Marshal.4.8/") + expect(out).not_to include("https://gem.repo3/quick/Marshal.4.8/") + end + + it "fails", bundler: "3" do + bundle :install, artifice: "compact_index", raise_on_error: false + expect(err).to include("Each source after the first must include a block") + expect(exitstatus).to eq(4) + end end end @@ -54,21 +108,49 @@ RSpec.describe "bundle install with gems on multiple sources" do G end - it "warns about ambiguous gems, but installs anyway", :bundler => "< 3" do - bundle :install, :artifice => "compact_index" + it "warns about ambiguous gems, but installs anyway", bundler: "< 3" do + bundle :install, artifice: "compact_index" expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") expect(err).to include("Installed from: https://gem.repo1") - expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", :source => "remote1") + expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", source: "remote1") end - it "fails", :bundler => "3" do - bundle :install, :artifice => "compact_index", :raise_on_error => false + it "fails", bundler: "3" do + bundle :install, artifice: "compact_index", raise_on_error: false expect(err).to include("Each source after the first must include a block") expect(exitstatus).to eq(4) end end end + context "without source affinity, and a stdlib gem present in one of the sources", :ruby_repo do + let(:default_json_version) { ruby "gem 'json'; require 'json'; puts JSON::VERSION" } + + before do + build_repo2 do + build_gem "json", default_json_version + end + + build_repo4 do + build_gem "foo" do |s| + s.add_dependency "json", default_json_version + end + end + + gemfile <<-G + source "https://gem.repo2" + source "https://gem.repo4" + + gem "foo" + G + end + + it "works in standalone mode", bundler: "< 3" do + gem_checksum = checksum_digest(gem_repo4, "foo", "1.0") + bundle "install --standalone", artifice: "compact_index", env: { "BUNDLER_SPEC_FOO_CHECKSUM" => gem_checksum } + end + end + context "with source affinity" do context "with sources given by a block" do before do @@ -95,20 +177,20 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs the gems without any warning" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).not_to include("Warning") expect(the_bundle).to include_gems("rack-obama 1.0.0") - expect(the_bundle).to include_gems("rack 1.0.0", :source => "remote1") + expect(the_bundle).to include_gems("rack 1.0.0", source: "remote1") end it "can cache and deploy" do - bundle :cache, :artifice => "compact_index" + bundle :cache, artifice: "compact_index" expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist expect(bundled_app("vendor/cache/rack-obama-1.0.gem")).to exist bundle "config set --local deployment true" - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0") end @@ -128,7 +210,7 @@ RSpec.describe "bundle install with gems on multiple sources" do end end - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo3" gem "rack-obama" # should come from repo3! gem "rack", :source => "https://gem.repo1" @@ -168,9 +250,9 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs from the same source without any warning" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).not_to include("Warning") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", source: "remote3") end end @@ -185,18 +267,18 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs from the same source without any warning" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", source: "remote3") # In https://github.com/bundler/bundler/issues/3585 this failed # when there is already a lock file, and the gems are missing, so try again system_gems [] - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", source: "remote3") end end end @@ -218,7 +300,7 @@ RSpec.describe "bundle install with gems on multiple sources" do context "and not in any other sources" do before do - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo2" source "https://gem.repo3" do gem "depends_on_rack" @@ -243,11 +325,63 @@ RSpec.describe "bundle install with gems on multiple sources" do G end - it "installs from the other source and warns about ambiguous gems", :bundler => "< 3" do - bundle :install, :artifice => "compact_index" + it "fails when the two sources don't have the same checksum", bundler: "< 3" do + bundle :install, artifice: "compact_index", raise_on_error: false + + expect(err).to eq(<<~E.strip) + [DEPRECATED] Your Gemfile contains multiple global sources. Using `source` more than once without a block is a security risk, and may result in installing unexpected gems. To resolve this warning, use a block to indicate which gems should come from the secondary source. + Bundler found mismatched checksums. This is a potential security risk. + #{checksum_to_lock(gem_repo2, "rack", "1.0.0")} + from the API at https://gem.repo2/ + #{checksum_to_lock(gem_repo1, "rack", "1.0.0")} + from the API at https://gem.repo1/ + + Mismatched checksums each have an authoritative source: + 1. the API at https://gem.repo2/ + 2. the API at https://gem.repo1/ + You may need to alter your Gemfile sources to resolve this issue. + + To ignore checksum security warnings, disable checksum validation with + `bundle config set --local disable_checksum_validation true` + E + expect(exitstatus).to eq(37) + end + + it "fails when the two sources agree, but the local gem calculates a different checksum", bundler: "< 3" do + rack_checksum = "c0ffee11" * 8 + bundle :install, artifice: "compact_index", env: { "BUNDLER_SPEC_RACK_CHECKSUM" => rack_checksum }, raise_on_error: false + + expect(err).to eq(<<~E.strip) + [DEPRECATED] Your Gemfile contains multiple global sources. Using `source` more than once without a block is a security risk, and may result in installing unexpected gems. To resolve this warning, use a block to indicate which gems should come from the secondary source. + Bundler found mismatched checksums. This is a potential security risk. + rack (1.0.0) sha256=#{rack_checksum} + from the API at https://gem.repo2/ + and the API at https://gem.repo1/ + #{checksum_to_lock(gem_repo2, "rack", "1.0.0")} + from the gem at #{default_bundle_path("cache", "rack-1.0.0.gem")} + + If you trust the API at https://gem.repo2/, to resolve this issue you can: + 1. remove the gem at #{default_bundle_path("cache", "rack-1.0.0.gem")} + 2. run `bundle install` + + To ignore checksum security warnings, disable checksum validation with + `bundle config set --local disable_checksum_validation true` + E + expect(exitstatus).to eq(37) + end + + it "installs from the other source and warns about ambiguous gems when the sources have the same checksum", bundler: "< 3" do + gem_checksum = checksum_digest(gem_repo2, "rack", "1.0.0") + bundle :install, artifice: "compact_index", env: { "BUNDLER_SPEC_RACK_CHECKSUM" => gem_checksum, "DEBUG" => "1" } + expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") expect(err).to include("Installed from: https://gem.repo2") + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo3, "depends_on_rack", "1.0.1" + c.checksum gem_repo2, "rack", "1.0.0" + end + expect(lockfile).to eq <<~L GEM remote: https://gem.repo1/ @@ -262,11 +396,51 @@ RSpec.describe "bundle install with gems on multiple sources" do rack PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES depends_on_rack! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + previous_lockfile = lockfile + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") + expect(lockfile).to eq(previous_lockfile) + end + + it "installs from the other source and warns about ambiguous gems when checksum validation is disabled", bundler: "< 3" do + bundle "config set --local disable_checksum_validation true" + bundle :install, artifice: "compact_index" + expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") + expect(err).to include("Installed from: https://gem.repo2") + + checksums = checksums_section_when_existing do |c| + c.no_checksum "depends_on_rack", "1.0.1" + c.no_checksum "rack", "1.0.0" + end + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo1/ + remote: https://gem.repo2/ + specs: + rack (1.0.0) + + GEM + remote: https://gem.repo3/ + specs: + depends_on_rack (1.0.1) + rack + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + depends_on_rack! + #{checksums} BUNDLED WITH #{Bundler::VERSION} L @@ -276,8 +450,8 @@ RSpec.describe "bundle install with gems on multiple sources" do expect(lockfile).to eq(previous_lockfile) end - it "fails", :bundler => "3" do - bundle :install, :artifice => "compact_index", :raise_on_error => false + it "fails", bundler: "3" do + bundle :install, artifice: "compact_index", raise_on_error: false expect(err).to include("Each source after the first must include a block") expect(exitstatus).to eq(4) end @@ -301,8 +475,8 @@ RSpec.describe "bundle install with gems on multiple sources" do G end - it "installs the dependency from the pinned source without warning", :bundler => "< 3" do - bundle :install, :artifice => "compact_index" + it "installs the dependency from the pinned source without warning", bundler: "< 3" do + bundle :install, artifice: "compact_index" expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.") expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") @@ -310,14 +484,14 @@ RSpec.describe "bundle install with gems on multiple sources" do # In https://github.com/rubygems/bundler/issues/3585 this failed # when there is already a lock file, and the gems are missing, so try again system_gems [] - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.") expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") end - it "fails", :bundler => "3" do - bundle :install, :artifice => "compact_index", :raise_on_error => false + it "fails", bundler: "3" do + bundle :install, artifice: "compact_index", raise_on_error: false expect(err).to include("Each source after the first must include a block") expect(exitstatus).to eq(4) end @@ -345,13 +519,12 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "fails" do - bundle :install, :artifice => "compact_index", :raise_on_error => false - expect(err).to include("Could not find gem 'private_gem_1' in rubygems repository https://gem.repo2/ or installed locally.") - expect(err).to include("The source does not contain any versions of 'private_gem_1'") + bundle :install, artifice: "compact_index", raise_on_error: false + expect(err).to include("Could not find gem 'private_gem_1' in rubygems repository https://gem.repo2/, cached gems or installed locally.") end end - context "when an indirect dependency can't be found in the aggregate rubygems source", :bundler => "< 3" do + context "when an indirect dependency can't be found in the aggregate rubygems source", bundler: "< 3" do before do build_repo2 @@ -371,8 +544,16 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "fails" do - bundle :install, :artifice => "compact_index", :raise_on_error => false - expect(err).to include("Could not find gem 'missing', which is required by gem 'depends_on_missing', in any of the sources.") + bundle :install, artifice: "compact_index", raise_on_error: false + expect(err).to end_with <<~E.strip + Could not find compatible versions + + Because every version of depends_on_missing depends on missing >= 0 + and missing >= 0 could not be found in any of the sources, + depends_on_missing cannot be used. + So, because Gemfile depends on depends_on_missing >= 0, + version solving has failed. + E end end @@ -407,11 +588,11 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs the dependency from the top-level source without warning" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).not_to include("Warning") expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote2") - expect(the_bundle).to include_gems("unrelated_gem 1.0.0", :source => "remote3") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", source: "remote2") + expect(the_bundle).to include_gems("unrelated_gem 1.0.0", source: "remote3") end end @@ -425,10 +606,16 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "does not find the dependency" do - bundle :install, :artifice => "compact_index", :raise_on_error => false - expect(err).to include( - "Could not find gem 'rack', which is required by gem 'depends_on_rack', in rubygems repository https://gem.repo2/ or installed locally." - ) + bundle :install, artifice: "compact_index", raise_on_error: false + expect(err).to end_with <<~E.strip + Could not find compatible versions + + Because every version of depends_on_rack depends on rack >= 0 + and rack >= 0 could not be found in rubygems repository https://gem.repo2/, cached gems or installed locally, + depends_on_rack cannot be used. + So, because Gemfile depends on depends_on_rack >= 0, + version solving has failed. + E end end @@ -446,12 +633,12 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs the dependency from the top-level source without warning" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).not_to include("Warning") expect(run("require 'rack'; puts RACK")).to eq("1.0.0") expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote2") - expect(the_bundle).to include_gems("unrelated_gem 1.0.0", :source => "remote3") + expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", source: "remote2") + expect(the_bundle).to include_gems("unrelated_gem 1.0.0", source: "remote3") end end end @@ -485,10 +672,10 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs the dependency from the top-level source" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0") - expect(the_bundle).to include_gems("rack 1.0.0", :source => "remote2") - expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", :source => "remote3") + expect(the_bundle).to include_gems("rack 1.0.0", source: "remote2") + expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", source: "remote3") end end @@ -502,8 +689,8 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs the dependency from the pinned source" do - bundle :install, :artifice => "compact_index" - expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3") + bundle :install, artifice: "compact_index" + expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0", source: "remote3") end end @@ -521,8 +708,8 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs the dependency from the pinned source without warning" do - bundle :install, :artifice => "compact_index" - expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3") + bundle :install, artifice: "compact_index" + expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0", source: "remote3") end end end @@ -596,6 +783,21 @@ RSpec.describe "bundle install with gems on multiple sources" do end G + @locked_checksums = checksums_section_when_existing do |c| + c.checksum gem_repo2, "activesupport", "6.0.3.4" + c.checksum gem_repo2, "concurrent-ruby", "1.1.8" + c.checksum gem_repo2, "connection_pool", "2.2.3" + c.checksum gem_repo2, "i18n", "1.8.9" + c.checksum gem_repo2, "minitest", "5.14.3" + c.checksum gem_repo2, "rack", "2.2.3" + c.checksum gem_repo2, "redis", "4.2.5" + c.checksum gem_repo2, "sidekiq", "6.1.3" + c.checksum gem_repo3, "sidekiq-pro", "5.2.1" + c.checksum gem_repo2, "thread_safe", "0.3.6" + c.checksum gem_repo2, "tzinfo", "1.2.9" + c.checksum gem_repo2, "zeitwerk", "2.4.2" + end + lockfile <<~L GEM remote: https://gem.repo2/ @@ -627,19 +829,19 @@ RSpec.describe "bundle install with gems on multiple sources" do zeitwerk (2.4.2) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES activesupport sidekiq-pro! - + #{@locked_checksums} BUNDLED WITH #{Bundler::VERSION} L end it "does not install newer versions but updates the lockfile format when running bundle install in non frozen mode, and doesn't warn" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).to be_empty expect(the_bundle).to include_gems("activesupport 6.0.3.4") @@ -683,22 +885,22 @@ RSpec.describe "bundle install with gems on multiple sources" do sidekiq (>= 6.1.0) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES activesupport sidekiq-pro! - + #{@locked_checksums} BUNDLED WITH #{Bundler::VERSION} L end - it "does not install newer versions or generate lockfile changes when running bundle install in frozen mode, and warns", :bundler => "< 3" do + it "does not install newer versions or generate lockfile changes when running bundle install in frozen mode, and warns", bundler: "< 3" do initial_lockfile = lockfile bundle "config set --local frozen true" - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") @@ -712,11 +914,11 @@ RSpec.describe "bundle install with gems on multiple sources" do expect(lockfile).to eq(initial_lockfile) end - it "fails when running bundle install in frozen mode", :bundler => "3" do + it "fails when running bundle install in frozen mode", bundler: "3" do initial_lockfile = lockfile bundle "config set --local frozen true" - bundle :install, :artifice => "compact_index", :raise_on_error => false + bundle :install, artifice: "compact_index", raise_on_error: false expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") @@ -724,15 +926,21 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "splits sections and upgrades gems when running bundle update, and doesn't warn" do - bundle "update --all", :artifice => "compact_index" + bundle "update --all", artifice: "compact_index" expect(err).to be_empty expect(the_bundle).not_to include_gems("activesupport 6.0.3.4") expect(the_bundle).to include_gems("activesupport 6.1.2.1") + @locked_checksums.checksum gem_repo2, "activesupport", "6.1.2.1" + expect(the_bundle).not_to include_gems("tzinfo 1.2.9") expect(the_bundle).to include_gems("tzinfo 2.0.4") + @locked_checksums.checksum gem_repo2, "tzinfo", "2.0.4" + @locked_checksums.delete "thread_safe" + expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.8") expect(the_bundle).to include_gems("concurrent-ruby 1.1.9") + @locked_checksums.checksum gem_repo2, "concurrent-ruby", "1.1.9" expect(lockfile).to eq <<~L GEM @@ -767,19 +975,19 @@ RSpec.describe "bundle install with gems on multiple sources" do sidekiq (>= 6.1.0) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES activesupport sidekiq-pro! - + #{@locked_checksums} BUNDLED WITH #{Bundler::VERSION} L end it "upgrades the lockfile format and upgrades the requested gem when running bundle update with an argument" do - bundle "update concurrent-ruby", :artifice => "compact_index" + bundle "update concurrent-ruby", artifice: "compact_index" expect(err).to be_empty expect(the_bundle).to include_gems("activesupport 6.0.3.4") @@ -789,6 +997,8 @@ RSpec.describe "bundle install with gems on multiple sources" do expect(the_bundle).to include_gems("concurrent-ruby 1.1.9") expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.8") + @locked_checksums.checksum gem_repo2, "concurrent-ruby", "1.1.9" + expect(lockfile).to eq <<~L GEM remote: https://gem.repo2/ @@ -823,12 +1033,12 @@ RSpec.describe "bundle install with gems on multiple sources" do sidekiq (>= 6.1.0) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES activesupport sidekiq-pro! - + #{@locked_checksums} BUNDLED WITH #{Bundler::VERSION} L @@ -837,8 +1047,8 @@ RSpec.describe "bundle install with gems on multiple sources" do context "when a top-level gem has an indirect dependency present in the default source, but with a different version from the one resolved" do before do - build_lib "activesupport", "7.0.0.alpha", :path => lib_path("rails/activesupport") - build_lib "rails", "7.0.0.alpha", :path => lib_path("rails") do |s| + build_lib "activesupport", "7.0.0.alpha", path: lib_path("rails/activesupport") + build_lib "rails", "7.0.0.alpha", path: lib_path("rails") do |s| s.add_dependency "activesupport", "= 7.0.0.alpha" end @@ -860,11 +1070,11 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs all gems without warning" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).not_to include("Warning") expect(the_bundle).to include_gems("activesupport 7.0.0.alpha", "rails 7.0.0.alpha") - expect(the_bundle).to include_gems("activesupport 7.0.0.alpha", :source => "path@#{lib_path("rails/activesupport")}") - expect(the_bundle).to include_gems("rails 7.0.0.alpha", :source => "path@#{lib_path("rails")}") + expect(the_bundle).to include_gems("activesupport 7.0.0.alpha", source: "path@#{lib_path("rails/activesupport")}") + expect(the_bundle).to include_gems("rails 7.0.0.alpha", source: "path@#{lib_path("rails")}") end end @@ -896,6 +1106,12 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs from the default source without any warnings or errors and generates a proper lockfile" do + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo3, "handsoap", "0.2.5.5" + c.checksum gem_repo2, "nokogiri", "1.11.1" + c.checksum gem_repo2, "racca", "1.5.2" + end + expected_lockfile = <<~L GEM remote: https://gem.repo2/ @@ -911,30 +1127,30 @@ RSpec.describe "bundle install with gems on multiple sources" do nokogiri (>= 1.2.3) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES handsoap! nokogiri - + #{checksums} BUNDLED WITH #{Bundler::VERSION} L - bundle "install --verbose", :artifice => "compact_index" + bundle "install --verbose", artifice: "compact_index" expect(err).not_to include("Warning") expect(the_bundle).to include_gems("handsoap 0.2.5.5", "nokogiri 1.11.1", "racca 1.5.2") - expect(the_bundle).to include_gems("handsoap 0.2.5.5", :source => "remote3") - expect(the_bundle).to include_gems("nokogiri 1.11.1", "racca 1.5.2", :source => "remote2") + expect(the_bundle).to include_gems("handsoap 0.2.5.5", source: "remote3") + expect(the_bundle).to include_gems("nokogiri 1.11.1", "racca 1.5.2", source: "remote2") expect(lockfile).to eq(expected_lockfile) # Even if the gems are already installed FileUtils.rm bundled_app_lock - bundle "install --verbose", :artifice => "compact_index" + bundle "install --verbose", artifice: "compact_index" expect(err).not_to include("Warning") expect(the_bundle).to include_gems("handsoap 0.2.5.5", "nokogiri 1.11.1", "racca 1.5.2") - expect(the_bundle).to include_gems("handsoap 0.2.5.5", :source => "remote3") - expect(the_bundle).to include_gems("nokogiri 1.11.1", "racca 1.5.2", :source => "remote2") + expect(the_bundle).to include_gems("handsoap 0.2.5.5", source: "remote3") + expect(the_bundle).to include_gems("nokogiri 1.11.1", "racca 1.5.2", source: "remote2") expect(lockfile).to eq(expected_lockfile) end end @@ -945,7 +1161,7 @@ RSpec.describe "bundle install with gems on multiple sources" do build_gem "not_in_repo1", "1.0.0" end - install_gemfile <<-G, :artifice => "compact_index", :raise_on_error => false + install_gemfile <<-G, artifice: "compact_index", raise_on_error: false source "https://gem.repo3" gem "not_in_repo1", :source => "https://gem.repo1" G @@ -958,7 +1174,7 @@ RSpec.describe "bundle install with gems on multiple sources" do context "with an existing lockfile" do before do - system_gems "rack-0.9.1", "rack-1.0.0", :path => default_bundle_path + system_gems "rack-0.9.1", "rack-1.0.0", path: default_bundle_path lockfile <<-L GEM @@ -971,7 +1187,7 @@ RSpec.describe "bundle install with gems on multiple sources" do rack (0.9.1) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES rack! @@ -1001,11 +1217,11 @@ RSpec.describe "bundle install with gems on multiple sources" do rack (0.9.1) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES rack! - + #{checksums_section} BUNDLED WITH #{Bundler::VERSION} L @@ -1023,7 +1239,7 @@ RSpec.describe "bundle install with gems on multiple sources" do rack (0.9.1) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES rack! @@ -1048,20 +1264,48 @@ RSpec.describe "bundle install with gems on multiple sources" do lockfile aggregate_gem_section_lockfile end - it "installs the existing lockfile but prints a warning", :bundler => "< 3" do + it "installs the existing lockfile but prints a warning when checksum validation is disabled", bundler: "< 3" do bundle "config set --local deployment true" + bundle "config set --local disable_checksum_validation true" - bundle "install", :artifice => "compact_index" + bundle "install", artifice: "compact_index" expect(lockfile).to eq(aggregate_gem_section_lockfile) expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") - expect(the_bundle).to include_gems("rack 0.9.1", :source => "remote3") + expect(the_bundle).to include_gems("rack 0.9.1", source: "remote3") end - it "refuses to install the existing lockfile and prints an error", :bundler => "3" do + it "prints a checksum warning when the checksums from both sources do not match", bundler: "< 3" do bundle "config set --local deployment true" - bundle "install", :artifice => "compact_index", :raise_on_error =>false + bundle "install", artifice: "compact_index", raise_on_error: false + + api_checksum1 = checksum_digest(gem_repo1, "rack", "0.9.1") + api_checksum3 = checksum_digest(gem_repo3, "rack", "0.9.1") + + expect(exitstatus).to eq(37) + expect(err).to eq(<<~E.strip) + [DEPRECATED] Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure. + Bundler found mismatched checksums. This is a potential security risk. + rack (0.9.1) sha256=#{api_checksum3} + from the API at https://gem.repo3/ + rack (0.9.1) sha256=#{api_checksum1} + from the API at https://gem.repo1/ + + Mismatched checksums each have an authoritative source: + 1. the API at https://gem.repo3/ + 2. the API at https://gem.repo1/ + You may need to alter your Gemfile sources to resolve this issue. + + To ignore checksum security warnings, disable checksum validation with + `bundle config set --local disable_checksum_validation true` + E + end + + it "refuses to install the existing lockfile and prints an error", bundler: "3" do + 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.") @@ -1081,7 +1325,7 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "does not unlock the non-path gem after install" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" bundle %(exec ruby -e 'puts "OK"') @@ -1094,7 +1338,7 @@ RSpec.describe "bundle install with gems on multiple sources" do before do system_gems "rack-0.9.1" - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo1" gem "rack" # should come from repo1! G @@ -1123,7 +1367,7 @@ RSpec.describe "bundle install with gems on multiple sources" do G bundle "config set --local path ../gems/system" - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" # And then we add some new versions... update_repo4 do @@ -1134,7 +1378,7 @@ RSpec.describe "bundle install with gems on multiple sources" do it "allows them to be unlocked separately" do # And install this gemfile, updating only foo. - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source 'https://gem.repo1' gem 'rack' gem 'foo', '~> 0.2', :source => 'https://gem.repo4' @@ -1158,7 +1402,7 @@ RSpec.describe "bundle install with gems on multiple sources" do build_git "git1" build_git "git2" - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo1" gem "rails" @@ -1174,7 +1418,7 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "does not re-resolve" do - bundle :install, :artifice => "compact_index", :verbose => true + bundle :install, artifice: "compact_index", verbose: true expect(out).to include("using resolution from the lockfile") expect(out).not_to include("re-resolving dependencies") end @@ -1183,7 +1427,7 @@ RSpec.describe "bundle install with gems on multiple sources" do context "when a gem is installed to system gems" do before do - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo1" gem "rack" G @@ -1197,7 +1441,7 @@ RSpec.describe "bundle install with gems on multiple sources" do end # When this gemfile is installed... - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo1" source "https://gem.repo4" do @@ -1219,14 +1463,14 @@ RSpec.describe "bundle install with gems on multiple sources" do G # But we should still be able to find rack 2.0.1.1.forked and install it - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" end end end describe "source changed to one containing a higher version of a dependency" do before do - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo1" gem "rack" @@ -1240,19 +1484,45 @@ RSpec.describe "bundle install with gems on multiple sources" do build_gem "bar" end - build_lib("gemspec_test", :path => tmp.join("gemspec_test")) do |s| + build_lib("gemspec_test", path: tmp.join("gemspec_test")) do |s| s.add_dependency "bar", "=1.0.0" end - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo2" gem "rack" gemspec :path => "#{tmp.join("gemspec_test")}" G end - it "installs the higher version in the new repo" do - expect(the_bundle).to include_gems("rack 1.2") + 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 @@ -1265,7 +1535,7 @@ RSpec.describe "bundle install with gems on multiple sources" do build_gem "example", "1.0.2" end - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo4" gem "example", :source => "https://gem.repo2" @@ -1274,14 +1544,14 @@ RSpec.describe "bundle install with gems on multiple sources" do bundle "info example" expect(out).to include("example (0.1.0)") - system_gems "example-1.0.2", :path => default_bundle_path, :gem_repo => gem_repo4 + system_gems "example-1.0.2", path: default_bundle_path, gem_repo: gem_repo4 - bundle "update example --verbose", :artifice => "compact_index" + bundle "update example --verbose", artifice: "compact_index" expect(out).not_to include("Using example 1.0.2") expect(out).to include("Using example 0.1.0") end - it "fails inmmediately with a helpful error when a non retriable network error happens while resolving sources" do + it "fails immediately with a helpful error when a rubygems source does not exist and bundler/setup is required" do gemfile <<-G source "https://gem.repo1" @@ -1290,17 +1560,30 @@ RSpec.describe "bundle install with gems on multiple sources" do end G - simulate_bundler_version_when_missing_prerelease_default_gem_activation do - ruby <<~R, :raise_on_error => false - require 'bundler/setup' - R - end + ruby <<~R, raise_on_error: false + require 'bundler/setup' + R + + expect(last_command).to be_failure + expect(err).to include("Could not find gem 'example' in locally installed gems.") + end + + it "fails immediately 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 + 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| @@ -1309,7 +1592,7 @@ RSpec.describe "bundle install with gems on multiple sources" do build_gem "rack" end - install_gemfile <<-G, :artifice => "compact_index", :raise_on_error => false + install_gemfile <<-G, artifice: "compact_index", raise_on_error: false source "#{file_uri_for(gem_repo1)}" source "https://gem.repo4" do @@ -1320,10 +1603,10 @@ RSpec.describe "bundle install with gems on multiple sources" do gem "thin" end G - expect(err).to eq strip_whitespace(<<-EOS).strip + expect(err).to eq <<~EOS.strip Warning: The gem 'rack' was found in multiple relevant sources. - * rubygems repository https://gem.repo1/ or installed locally - * rubygems repository https://gem.repo4/ or installed locally + * rubygems repository https://gem.repo1/ + * rubygems repository https://gem.repo4/ You should add this gem to the source block for the source you wish it to be installed from. EOS expect(last_command).to be_success @@ -1331,7 +1614,7 @@ RSpec.describe "bundle install with gems on multiple sources" do end end - context "when an indirect dependency is available from multiple ambiguous sources", :bundler => "3" do + context "when an indirect dependency is available from multiple ambiguous sources", bundler: "3" do it "raises, suggesting a source block" do build_repo4 do build_gem "depends_on_rack" do |s| @@ -1340,7 +1623,7 @@ RSpec.describe "bundle install with gems on multiple sources" do build_gem "rack" end - install_gemfile <<-G, :artifice => "compact_index", :raise_on_error => false + install_gemfile <<-G, artifice: "compact_index", raise_on_error: false source "#{file_uri_for(gem_repo1)}" source "https://gem.repo4" do gem "depends_on_rack" @@ -1350,10 +1633,10 @@ RSpec.describe "bundle install with gems on multiple sources" do end G expect(last_command).to be_failure - expect(err).to eq strip_whitespace(<<-EOS).strip + expect(err).to eq <<~EOS.strip The gem 'rack' was found in multiple relevant sources. - * rubygems repository https://gem.repo1/ or installed locally - * rubygems repository https://gem.repo4/ or installed locally + * rubygems repository https://gem.repo1/ + * rubygems repository https://gem.repo4/ You must add this gem to the source block for the source you wish it to be installed from. EOS expect(the_bundle).not_to be_locked @@ -1394,16 +1677,23 @@ RSpec.describe "bundle install with gems on multiple sources" do mime-types (3.3.1) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES capybara (~> 2.5.0) mime-types (~> 3.0)! + + CHECKSUMS L end it "upgrades the lockfile correctly" do - bundle "lock --update", :artifice => "compact_index" + bundle "lock --update", artifice: "compact_index" + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo2, "capybara", "2.5.0" + c.checksum gem_repo4, "mime-types", "3.0.0" + end expect(lockfile).to eq <<~L GEM @@ -1418,15 +1708,215 @@ RSpec.describe "bundle install with gems on multiple sources" do mime-types (3.0.0) PLATFORMS - #{specific_local_platform} + #{lockfile_platforms} DEPENDENCIES capybara (~> 2.5.0) mime-types (~> 3.0)! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "when default source includes old gems with nil required_ruby_version" do + before do + build_repo2 do + build_gem "ruport", "1.7.0.3" do |s| + s.add_dependency "pdf-writer", "1.1.8" + end + end + + build_repo gem_repo4 do + build_gem "pdf-writer", "1.1.8" + end + + path = "#{gem_repo4}/#{Gem::MARSHAL_SPEC_DIR}/pdf-writer-1.1.8.gemspec.rz" + spec = Marshal.load(Bundler.rubygems.inflate(File.binread(path))) + spec.instance_variable_set(:@required_ruby_version, nil) + File.open(path, "wb") do |f| + f.write Gem.deflate(Marshal.dump(spec)) + end + + gemfile <<~G + source "https://localgemserver.test" + + gem "ruport", "= 1.7.0.3", :source => "https://localgemserver.test/extra" + G + end + + it "handles that fine" do + bundle "install", artifice: "compact_index_extra", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "pdf-writer", "1.1.8" + c.checksum gem_repo2, "ruport", "1.7.0.3" + end + + expect(lockfile).to eq <<~L + GEM + remote: https://localgemserver.test/ + specs: + pdf-writer (1.1.8) + + GEM + remote: https://localgemserver.test/extra/ + specs: + ruport (1.7.0.3) + pdf-writer (= 1.1.8) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ruport (= 1.7.0.3)! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "when default source includes old gems with nil required_rubygems_version" do + before do + build_repo2 do + build_gem "ruport", "1.7.0.3" do |s| + s.add_dependency "pdf-writer", "1.1.8" + end + end + + build_repo gem_repo4 do + build_gem "pdf-writer", "1.1.8" + end + + path = "#{gem_repo4}/#{Gem::MARSHAL_SPEC_DIR}/pdf-writer-1.1.8.gemspec.rz" + spec = Marshal.load(Bundler.rubygems.inflate(File.binread(path))) + spec.instance_variable_set(:@required_rubygems_version, nil) + File.open(path, "wb") do |f| + f.write Gem.deflate(Marshal.dump(spec)) + end + + gemfile <<~G + source "https://localgemserver.test" + + gem "ruport", "= 1.7.0.3", :source => "https://localgemserver.test/extra" + G + end + it "handles that fine" do + bundle "install", artifice: "compact_index_extra", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "pdf-writer", "1.1.8" + c.checksum gem_repo2, "ruport", "1.7.0.3" + end + + expect(lockfile).to eq <<~L + GEM + remote: https://localgemserver.test/ + specs: + pdf-writer (1.1.8) + + GEM + remote: https://localgemserver.test/extra/ + specs: + ruport (1.7.0.3) + pdf-writer (= 1.1.8) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ruport (= 1.7.0.3)! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "when default source uses the old API and includes old gems with nil required_rubygems_version" do + before do + build_repo4 do + build_gem "pdf-writer", "1.1.8" + end + + path = "#{gem_repo4}/#{Gem::MARSHAL_SPEC_DIR}/pdf-writer-1.1.8.gemspec.rz" + spec = Marshal.load(Bundler.rubygems.inflate(File.binread(path))) + spec.instance_variable_set(:@required_rubygems_version, nil) + File.open(path, "wb") do |f| + f.write Gem.deflate(Marshal.dump(spec)) + end + + gemfile <<~G + source "https://localgemserver.test" + + gem "pdf-writer", "= 1.1.8" + G + end + + it "handles that fine" do + bundle "install --verbose", artifice: "endpoint", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "pdf-writer", "1.1.8" + end + + expect(lockfile).to eq <<~L + GEM + remote: https://localgemserver.test/ + specs: + pdf-writer (1.1.8) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + pdf-writer (= 1.1.8) + #{checksums} BUNDLED WITH #{Bundler::VERSION} L end end + + context "when mistakenly adding a top level gem already depended on and cached under the wrong source" do + before do + build_repo4 do + build_gem "some_private_gem", "0.1.0" do |s| + s.add_dependency "example", "~> 1.0" + end + end + + build_repo2 do + build_gem "example", "1.0.0" + end + + install_gemfile <<~G, artifice: "compact_index" + source "https://gem.repo2" + + source "https://gem.repo4" do + gem "some_private_gem" + end + G + + gemfile <<~G + source "https://gem.repo2" + + source "https://gem.repo4" do + gem "some_private_gem" + gem "example" # MISTAKE, example is not available at gem.repo4 + end + G + end + + it "shows a proper error message and does not generate a corrupted lockfile" do + expect do + bundle :install, artifice: "compact_index", raise_on_error: false, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + end.not_to change { lockfile } + + expect(err).to include("Could not find gem 'example' in rubygems repository https://gem.repo4/") + end + end end diff --git a/spec/bundler/install/gemfile/specific_platform_spec.rb b/spec/bundler/install/gemfile/specific_platform_spec.rb index 4c24f09368..5f1b034bfc 100644 --- a/spec/bundler/install/gemfile/specific_platform_spec.rb +++ b/spec/bundler/install/gemfile/specific_platform_spec.rb @@ -6,29 +6,29 @@ RSpec.describe "bundle install with specific platforms" do gem "google-protobuf" G - context "when on a darwin machine" do - before { simulate_platform "x86_64-darwin-15" } - - it "locks to the specific darwin platform" do + it "locks to the specific darwin platform" do + simulate_platform "x86_64-darwin-15" do setup_multiplatform_gem install_gemfile(google_protobuf) allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) - expect(the_bundle.locked_gems.platforms).to eq([pl("x86_64-darwin-15")]) + expect(the_bundle.locked_gems.platforms).to include(pl("x86_64-darwin-15")) expect(the_bundle).to include_gem("google-protobuf 3.0.0.alpha.5.0.5.1 universal-darwin") - expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[ - google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin - ]) + expect(the_bundle.locked_gems.specs.map(&:full_name)).to include( + "google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin" + ) end + end - it "understands that a non-plaform specific gem in a old lockfile doesn't necessarily mean installing the non-specific variant" do + it "understands that a non-platform specific gem in a old lockfile doesn't necessarily mean installing the non-specific variant" do + simulate_platform "x86_64-darwin-15" do setup_multiplatform_gem system_gems "bundler-2.1.4" # Consistent location to install and look for gems - bundle "config set --local path vendor/bundle", :env => { "BUNDLER_VERSION" => "2.1.4" } + bundle "config set --local path vendor/bundle", env: { "BUNDLER_VERSION" => "2.1.4" } - install_gemfile(google_protobuf, :env => { "BUNDLER_VERSION" => "2.1.4" }) + install_gemfile(google_protobuf, env: { "BUNDLER_VERSION" => "2.1.4" }) # simulate lockfile created with old bundler, which only locks for ruby platform lockfile <<-L @@ -48,22 +48,28 @@ RSpec.describe "bundle install with specific platforms" do L # force strict usage of the lock file by setting frozen mode - bundle "config set --local frozen true", :env => { "BUNDLER_VERSION" => "2.1.4" } + bundle "config set --local frozen true", env: { "BUNDLER_VERSION" => "2.1.4" } # make sure the platform that got actually installed with the old bundler is used expect(the_bundle).to include_gem("google-protobuf 3.0.0.alpha.5.0.5.1 universal-darwin") end + end - it "understands that a non-plaform specific gem in a new lockfile locked only to RUBY doesn't necessarily mean installing the non-specific variant" do + it "understands that a non-platform specific gem in a new lockfile locked only to RUBY doesn't necessarily mean installing the non-specific variant" do + simulate_platform "x86_64-darwin-15" do setup_multiplatform_gem system_gems "bundler-2.1.4" # Consistent location to install and look for gems - bundle "config set --local path vendor/bundle", :env => { "BUNDLER_VERSION" => "2.1.4" } + bundle "config set --local path vendor/bundle", env: { "BUNDLER_VERSION" => "2.1.4" } gemfile google_protobuf + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo2, "google-protobuf", "3.0.0.alpha.4.0" + end + # simulate lockfile created with old bundler, which only locks for ruby platform lockfile <<-L GEM @@ -76,18 +82,20 @@ RSpec.describe "bundle install with specific platforms" do DEPENDENCIES google-protobuf - + #{checksums} BUNDLED WITH 2.1.4 L - bundle "update", :env => { "BUNDLER_VERSION" => Bundler::VERSION } + bundle "update", env: { "BUNDLER_VERSION" => Bundler::VERSION } + + checksums.checksum gem_repo2, "google-protobuf", "3.0.0.alpha.5.0.5.1" # make sure the platform that the platform specific dependency is used, since we're only locked to ruby expect(the_bundle).to include_gem("google-protobuf 3.0.0.alpha.5.0.5.1 universal-darwin") # make sure we're still only locked to ruby - lockfile_should_be <<-L + expect(lockfile).to eq <<~L GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -98,13 +106,61 @@ RSpec.describe "bundle install with specific platforms" do DEPENDENCIES google-protobuf + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "when running on a legacy lockfile locked only to RUBY" do + around do |example| + build_repo4 do + build_gem "nokogiri", "1.3.10" + build_gem "nokogiri", "1.3.10" do |s| + s.platform = "arm64-darwin" + s.required_ruby_version = "< #{Gem.ruby_version}" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "nokogiri" + G + + lockfile <<-L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.3.10) + + PLATFORMS + ruby + + DEPENDENCIES + nokogiri BUNDLED WITH #{Bundler::VERSION} L + + simulate_platform "arm64-darwin-22", &example end - it "doesn't discard previously installed platform specific gem and fall back to ruby on subsequent bundles" do + it "still installs the generic RUBY variant if necessary" do + bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + expect(out).to include("Installing nokogiri 1.3.10") + end + + it "still installs the generic RUBY variant if necessary, even in frozen mode" do + bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s, "BUNDLE_FROZEN" => "true" } + expect(out).to include("Installing nokogiri 1.3.10") + end + end + + it "doesn't discard previously installed platform specific gem and fall back to ruby on subsequent bundles" do + simulate_platform "x86_64-darwin-15" do build_repo2 do build_gem("libv8", "8.4.255.0") build_gem("libv8", "8.4.255.0") {|s| s.platform = "universal-darwin" } @@ -117,7 +173,7 @@ RSpec.describe "bundle install with specific platforms" do system_gems "bundler-2.1.4" # Consistent location to install and look for gems - bundle "config set --local path vendor/bundle", :env => { "BUNDLER_VERSION" => "2.1.4" } + bundle "config set --local path vendor/bundle", env: { "BUNDLER_VERSION" => "2.1.4" } gemfile <<-G source "https://localgemserver.test" @@ -141,14 +197,50 @@ RSpec.describe "bundle install with specific platforms" do 2.1.4 L - bundle "install --verbose", :artifice => "compact_index", :env => { "BUNDLER_VERSION" => "2.1.4", "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_VERSION" => "2.1.4", "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } expect(out).to include("Installing libv8 8.4.255.0 (universal-darwin)") - bundle "add mini_racer --verbose", :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + bundle "add mini_racer --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } expect(out).to include("Using libv8 8.4.255.0 (universal-darwin)") end + end - it "caches the universal-darwin gem when --all-platforms is passed and properly picks it up on further bundler invocations" do + it "chooses platform specific gems even when resolving upon materialization and the API returns more specific platforms first" do + simulate_platform "x86_64-darwin-15" do + build_repo4 do + build_gem("grpc", "1.50.0") + build_gem("grpc", "1.50.0") {|s| s.platform = "universal-darwin" } + end + + gemfile <<-G + source "https://localgemserver.test" + gem "grpc" + G + + # simulate lockfile created with old bundler, which only locks for ruby platform + lockfile <<-L + GEM + remote: https://localgemserver.test/ + specs: + grpc (1.50.0) + + PLATFORMS + ruby + + DEPENDENCIES + grpc + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install --verbose", artifice: "compact_index_precompiled_before", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + expect(out).to include("Installing grpc 1.50.0 (universal-darwin)") + end + end + + it "caches the universal-darwin gem when --all-platforms is passed and properly picks it up on further bundler invocations" do + simulate_platform "x86_64-darwin-15" do setup_multiplatform_gem gemfile(google_protobuf) bundle "cache --all-platforms" @@ -157,8 +249,10 @@ RSpec.describe "bundle install with specific platforms" do bundle "install --verbose" expect(err).to be_empty end + end - it "caches the universal-darwin gem when cache_all_platforms is configured and properly picks it up on further bundler invocations" do + it "caches the universal-darwin gem when cache_all_platforms is configured and properly picks it up on further bundler invocations" do + simulate_platform "x86_64-darwin-15" do setup_multiplatform_gem gemfile(google_protobuf) bundle "config set --local cache_all_platforms true" @@ -168,44 +262,46 @@ RSpec.describe "bundle install with specific platforms" do bundle "install --verbose" expect(err).to be_empty end + end - it "caches multiplatform git gems with a single gemspec when --all-platforms is passed" do - git = build_git "pg_array_parser", "1.0" + it "caches multiplatform git gems with a single gemspec when --all-platforms is passed" do + git = build_git "pg_array_parser", "1.0" - gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "pg_array_parser", :git => "#{lib_path("pg_array_parser-1.0")}" - G + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "pg_array_parser", :git => "#{lib_path("pg_array_parser-1.0")}" + G - lockfile <<-L - GIT - remote: #{lib_path("pg_array_parser-1.0")} - revision: #{git.ref_for("master")} - specs: - pg_array_parser (1.0-java) - pg_array_parser (1.0) + lockfile <<-L + GIT + remote: #{lib_path("pg_array_parser-1.0")} + revision: #{git.ref_for("main")} + specs: + pg_array_parser (1.0-java) + pg_array_parser (1.0) - GEM - specs: + GEM + specs: - PLATFORMS - java - #{lockfile_platforms} + PLATFORMS + java + #{lockfile_platforms} - DEPENDENCIES - pg_array_parser! + DEPENDENCIES + pg_array_parser! - BUNDLED WITH - #{Bundler::VERSION} - L + BUNDLED WITH + #{Bundler::VERSION} + L - bundle "config set --local cache_all true" - bundle "cache --all-platforms" + bundle "config set --local cache_all true" + bundle "cache --all-platforms" - expect(err).to be_empty - end + expect(err).to be_empty + end - it "uses the platform-specific gem with extra dependencies" do + it "uses the platform-specific gem with extra dependencies" do + simulate_platform "x86_64-darwin-15" do setup_multiplatform_gem_with_different_dependencies_per_platform install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" @@ -213,47 +309,52 @@ RSpec.describe "bundle install with specific platforms" do G allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) - expect(the_bundle.locked_gems.platforms).to eq([pl("x86_64-darwin-15")]) + expect(the_bundle.locked_gems.platforms).to include(pl("x86_64-darwin-15")) expect(the_bundle).to include_gems("facter 2.4.6 universal-darwin", "CFPropertyList 1.0") - expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(["CFPropertyList-1.0", - "facter-2.4.6-universal-darwin"]) + expect(the_bundle.locked_gems.specs.map(&:full_name)).to include("CFPropertyList-1.0", + "facter-2.4.6-universal-darwin") end + end - context "when adding a platform via lock --add_platform" do - before do - allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) - end + context "when adding a platform via lock --add_platform" do + before do + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + end - it "adds the foreign platform" do + it "adds the foreign platform" do + simulate_platform "x86_64-darwin-15" do setup_multiplatform_gem install_gemfile(google_protobuf) - bundle "lock --add-platform=#{x64_mingw}" + bundle "lock --add-platform=#{x64_mingw32}" - expect(the_bundle.locked_gems.platforms).to eq([x64_mingw, pl("x86_64-darwin-15")]) - expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[ + expect(the_bundle.locked_gems.platforms).to include(x64_mingw32, pl("x86_64-darwin-15")) + expect(the_bundle.locked_gems.specs.map(&:full_name)).to include(*%w[ google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin google-protobuf-3.0.0.alpha.5.0.5.1-x64-mingw32 ]) end + end - it "falls back on plain ruby when that version doesnt have a platform-specific gem" do + it "falls back on plain ruby when that version doesn't have a platform-specific gem" do + simulate_platform "x86_64-darwin-15" do setup_multiplatform_gem install_gemfile(google_protobuf) bundle "lock --add-platform=#{java}" - expect(the_bundle.locked_gems.platforms).to eq([java, pl("x86_64-darwin-15")]) - expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[ - google-protobuf-3.0.0.alpha.5.0.5.1 - google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin - ]) + expect(the_bundle.locked_gems.platforms).to include(java, pl("x86_64-darwin-15")) + expect(the_bundle.locked_gems.specs.map(&:full_name)).to include( + "google-protobuf-3.0.0.alpha.5.0.5.1", + "google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin" + ) end end end - it "installs sorbet-static, which does not provide a pure ruby variant, just fine on truffleruby", :truffleruby do + it "installs sorbet-static, which does not provide a pure ruby variant, just fine", :truffleruby do + skip "does not apply to Windows" if Gem.win_platform? + build_repo2 do - build_gem("sorbet-static", "0.5.6403") {|s| s.platform = "x86_64-linux" } - build_gem("sorbet-static", "0.5.6403") {|s| s.platform = "universal-darwin-20" } + build_gem("sorbet-static", "0.5.6403") {|s| s.platform = Bundler.local_platform } end gemfile <<~G @@ -266,8 +367,7 @@ RSpec.describe "bundle install with specific platforms" do GEM remote: #{file_uri_for(gem_repo2)}/ specs: - sorbet-static (0.5.6403-universal-darwin-20) - sorbet-static (0.5.6403-x86_64-linux) + sorbet-static (0.5.6403-#{Bundler.local_platform}) PLATFORMS ruby @@ -282,6 +382,1004 @@ RSpec.describe "bundle install with specific platforms" do bundle "install --verbose" end + it "does not resolve if the current platform does not match any of available platform specific variants for a top level dependency" do + build_repo4 do + build_gem("sorbet-static", "0.5.6433") {|s| s.platform = "x86_64-linux" } + build_gem("sorbet-static", "0.5.6433") {|s| s.platform = "universal-darwin-20" } + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "sorbet-static", "0.5.6433" + G + + error_message = <<~ERROR.strip + Could not find gem 'sorbet-static (= 0.5.6433)' with platform 'arm64-darwin-21' in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally. + + The source contains the following gems matching 'sorbet-static (= 0.5.6433)': + * sorbet-static-0.5.6433-universal-darwin-20 + * sorbet-static-0.5.6433-x86_64-linux + ERROR + + simulate_platform "arm64-darwin-21" do + bundle "lock", raise_on_error: false + end + + expect(err).to include(error_message).once + + # Make sure it doesn't print error twice in verbose mode + + simulate_platform "arm64-darwin-21" do + bundle "lock --verbose", raise_on_error: false + end + + expect(err).to include(error_message).once + end + + it "does not resolve if the current platform does not match any of available platform specific variants for a transitive dependency" do + build_repo4 do + build_gem("sorbet", "0.5.6433") {|s| s.add_dependency "sorbet-static", "= 0.5.6433" } + build_gem("sorbet-static", "0.5.6433") {|s| s.platform = "x86_64-linux" } + build_gem("sorbet-static", "0.5.6433") {|s| s.platform = "universal-darwin-20" } + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "sorbet", "0.5.6433" + G + + error_message = <<~ERROR.strip + Could not find compatible versions + + Because every version of sorbet depends on sorbet-static = 0.5.6433 + and sorbet-static = 0.5.6433 could not be found in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally for any resolution platforms (arm64-darwin-21), + sorbet cannot be used. + So, because Gemfile depends on sorbet = 0.5.6433, + version solving has failed. + + The source contains the following gems matching 'sorbet-static (= 0.5.6433)': + * sorbet-static-0.5.6433-universal-darwin-20 + * sorbet-static-0.5.6433-x86_64-linux + ERROR + + simulate_platform "arm64-darwin-21" do + bundle "lock", raise_on_error: false + end + + expect(err).to include(error_message).once + + # Make sure it doesn't print error twice in verbose mode + + simulate_platform "arm64-darwin-21" do + bundle "lock --verbose", raise_on_error: false + end + + expect(err).to include(error_message).once + end + + it "does not generate a lockfile if RUBY platform is forced and some gem has no RUBY variant available" do + build_repo4 do + build_gem("sorbet-static", "0.5.9889") {|s| s.platform = Gem::Platform.local } + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "sorbet-static", "0.5.9889" + G + + bundle "lock", raise_on_error: false, env: { "BUNDLE_FORCE_RUBY_PLATFORM" => "true" } + + expect(err).to include <<~ERROR.rstrip + Could not find gem 'sorbet-static (= 0.5.9889)' with platform 'ruby' in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally. + + The source contains the following gems matching 'sorbet-static (= 0.5.9889)': + * sorbet-static-0.5.9889-#{Gem::Platform.local} + ERROR + end + + it "automatically fixes the lockfile if RUBY platform is locked and some gem has no RUBY variant available" do + build_repo4 do + build_gem("sorbet-static-and-runtime", "0.5.10160") do |s| + s.add_runtime_dependency "sorbet", "= 0.5.10160" + s.add_runtime_dependency "sorbet-runtime", "= 0.5.10160" + end + + build_gem("sorbet", "0.5.10160") do |s| + s.add_runtime_dependency "sorbet-static", "= 0.5.10160" + end + + build_gem("sorbet-runtime", "0.5.10160") + + build_gem("sorbet-static", "0.5.10160") do |s| + s.platform = Gem::Platform.local + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "sorbet-static-and-runtime" + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + sorbet (0.5.10160) + sorbet-static (= 0.5.10160) + sorbet-runtime (0.5.10160) + sorbet-static (0.5.10160-#{Gem::Platform.local}) + sorbet-static-and-runtime (0.5.10160) + sorbet (= 0.5.10160) + sorbet-runtime (= 0.5.10160) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + sorbet-static-and-runtime + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "update" + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "sorbet", "0.5.10160" + c.checksum gem_repo4, "sorbet-runtime", "0.5.10160" + c.checksum gem_repo4, "sorbet-static", "0.5.10160", Gem::Platform.local + c.checksum gem_repo4, "sorbet-static-and-runtime", "0.5.10160" + end + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + sorbet (0.5.10160) + sorbet-static (= 0.5.10160) + sorbet-runtime (0.5.10160) + sorbet-static (0.5.10160-#{Gem::Platform.local}) + sorbet-static-and-runtime (0.5.10160) + sorbet (= 0.5.10160) + sorbet-runtime (= 0.5.10160) + + PLATFORMS + #{local_platform} + + DEPENDENCIES + sorbet-static-and-runtime + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "automatically fixes the lockfile if both RUBY platform and a more specific platform are locked, and some gem has no RUBY variant available" do + build_repo4 do + build_gem "nokogiri", "1.12.0" + build_gem "nokogiri", "1.12.0" do |s| + s.platform = "x86_64-darwin" + end + + build_gem "nokogiri", "1.13.0" + build_gem "nokogiri", "1.13.0" do |s| + s.platform = "x86_64-darwin" + end + + build_gem("sorbet-static", "0.5.10601") do |s| + s.platform = "x86_64-darwin" + end + end + + simulate_platform "x86_64-darwin-22" do + install_gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "nokogiri" + gem "sorbet-static" + G + end + + checksums = checksums_section_when_existing do |c| + c.no_checksum "nokogiri", "1.13.0", "x86_64-darwin" + c.no_checksum "sorbet-static", "0.5.10601", "x86_64-darwin" + end + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.12.0) + nokogiri (1.12.0-x86_64-darwin) + sorbet-static (0.5.10601-x86_64-darwin) + + PLATFORMS + ruby + x86_64-darwin + + DEPENDENCIES + nokogiri + sorbet-static + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + simulate_platform "x86_64-darwin-22" do + bundle "update --conservative nokogiri" + end + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.13.0-x86_64-darwin) + sorbet-static (0.5.10601-x86_64-darwin) + + PLATFORMS + x86_64-darwin + + DEPENDENCIES + nokogiri + sorbet-static + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "automatically fixes the lockfile if only RUBY platform is locked and some gem has no RUBY variant available" do + build_repo4 do + build_gem("sorbet-static-and-runtime", "0.5.10160") do |s| + s.add_runtime_dependency "sorbet", "= 0.5.10160" + s.add_runtime_dependency "sorbet-runtime", "= 0.5.10160" + end + + build_gem("sorbet", "0.5.10160") do |s| + s.add_runtime_dependency "sorbet-static", "= 0.5.10160" + end + + build_gem("sorbet-runtime", "0.5.10160") + + build_gem("sorbet-static", "0.5.10160") do |s| + s.platform = Gem::Platform.local + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "sorbet-static-and-runtime" + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + sorbet (0.5.10160) + sorbet-static (= 0.5.10160) + sorbet-runtime (0.5.10160) + sorbet-static (0.5.10160-#{Gem::Platform.local}) + sorbet-static-and-runtime (0.5.10160) + sorbet (= 0.5.10160) + sorbet-runtime (= 0.5.10160) + + PLATFORMS + ruby + + DEPENDENCIES + sorbet-static-and-runtime + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "update" + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "sorbet", "0.5.10160" + c.checksum gem_repo4, "sorbet-runtime", "0.5.10160" + c.checksum gem_repo4, "sorbet-static", "0.5.10160", Gem::Platform.local + c.checksum gem_repo4, "sorbet-static-and-runtime", "0.5.10160" + end + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + sorbet (0.5.10160) + sorbet-static (= 0.5.10160) + sorbet-runtime (0.5.10160) + sorbet-static (0.5.10160-#{Gem::Platform.local}) + sorbet-static-and-runtime (0.5.10160) + sorbet (= 0.5.10160) + sorbet-runtime (= 0.5.10160) + + PLATFORMS + #{local_platform} + + DEPENDENCIES + sorbet-static-and-runtime + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "automatically fixes the lockfile if multiple platforms locked, but no valid versions of direct dependencies for all of them" do + simulate_platform "x86_64-linux" do + build_repo4 do + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "x86_64-linux" + end + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "arm-linux" + end + + build_gem "sorbet-static", "0.5.10696" do |s| + s.platform = "x86_64-linux" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "nokogiri" + gem "sorbet-static" + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.14.0-arm-linux) + nokogiri (1.14.0-x86_64-linux) + sorbet-static (0.5.10696-x86_64-linux) + + PLATFORMS + aarch64-linux + arm-linux + x86_64-linux + + DEPENDENCIES + nokogiri + sorbet-static + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "update" + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "nokogiri", "1.14.0", "x86_64-linux" + c.checksum gem_repo4, "sorbet-static", "0.5.10696", "x86_64-linux" + end + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.14.0-x86_64-linux) + sorbet-static (0.5.10696-x86_64-linux) + + PLATFORMS + x86_64-linux + + DEPENDENCIES + nokogiri + sorbet-static + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + it "automatically fixes the lockfile without removing other variants if it's missing platform gems, but they are installed locally" do + simulate_platform "x86_64-darwin-21" do + build_repo4 do + build_gem("sorbet-static", "0.5.10549") do |s| + s.platform = "universal-darwin-20" + end + + build_gem("sorbet-static", "0.5.10549") do |s| + s.platform = "universal-darwin-21" + end + end + + # Make sure sorbet-static-0.5.10549-universal-darwin-21 is installed + install_gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "sorbet-static", "= 0.5.10549" + G + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "sorbet-static", "0.5.10549", "universal-darwin-20" + c.checksum gem_repo4, "sorbet-static", "0.5.10549", "universal-darwin-21" + end + + # Make sure the lockfile is missing sorbet-static-0.5.10549-universal-darwin-21 + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + sorbet-static (0.5.10549-universal-darwin-20) + + PLATFORMS + x86_64-darwin + + DEPENDENCIES + sorbet-static (= 0.5.10549) + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" + + checksums.no_checksum "sorbet-static", "0.5.10549", "universal-darwin-21" + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + sorbet-static (0.5.10549-universal-darwin-20) + sorbet-static (0.5.10549-universal-darwin-21) + + PLATFORMS + x86_64-darwin + + DEPENDENCIES + sorbet-static (= 0.5.10549) + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + it "does not remove ruby if gems for other platforms, and not present in the lockfile, exist in the Gemfile" do + build_repo4 do + build_gem "nokogiri", "1.13.8" + build_gem "nokogiri", "1.13.8" do |s| + s.platform = Gem::Platform.local + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "nokogiri" + + gem "tzinfo", "~> 1.2", platform: :#{not_local_tag} + G + + original_lockfile = <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.13.8) + nokogiri (1.13.8-#{Gem::Platform.local}) + + PLATFORMS + #{lockfile_platforms("ruby")} + + DEPENDENCIES + nokogiri + tzinfo (~> 1.2) + + CHECKSUMS + + BUNDLED WITH + #{Bundler::VERSION} + L + + lockfile original_lockfile + + bundle "lock --update" + + checksums = checksums_section_when_existing do |c| + c.no_checksum "nokogiri", "1.13.8" + c.no_checksum "nokogiri", "1.13.8", Gem::Platform.local + end + + updated_lockfile = <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.13.8) + nokogiri (1.13.8-#{Gem::Platform.local}) + + PLATFORMS + #{lockfile_platforms("ruby")} + + DEPENDENCIES + nokogiri + tzinfo (~> 1.2) + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + expect(lockfile).to eq(updated_lockfile) + end + + it "does not remove ruby when adding a new gem to the Gemfile" do + build_repo4 do + build_gem "concurrent-ruby", "1.2.2" + build_gem "rack", "3.0.7" + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "concurrent-ruby" + gem "rack" + G + + checksums = checksums_section_when_existing do |c| + c.no_checksum "concurrent-ruby", "1.2.2" + c.no_checksum "rack", "3.0.7" + end + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + concurrent-ruby (1.2.2) + + PLATFORMS + ruby + + DEPENDENCIES + concurrent-ruby + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "lock" + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + concurrent-ruby (1.2.2) + rack (3.0.7) + + PLATFORMS + #{lockfile_platforms("ruby", generic_local_platform, defaults: [])} + + DEPENDENCIES + concurrent-ruby + rack + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "can fallback to a source gem when platform gems are incompatible with current ruby version" do + setup_multiplatform_gem_with_source_gem + + source = file_uri_for(gem_repo2) + + gemfile <<~G + source "#{source}" + + gem "my-precompiled-gem" + G + + # simulate lockfile which includes both a precompiled gem with: + # - Gem the current platform (with incompatible ruby version) + # - A source gem with compatible ruby version + lockfile <<-L + GEM + remote: #{source}/ + specs: + my-precompiled-gem (3.0.0) + my-precompiled-gem (3.0.0-#{Bundler.local_platform}) + + PLATFORMS + ruby + #{Bundler.local_platform} + + DEPENDENCIES + my-precompiled-gem + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle :install + end + + it "automatically fixes the lockfile if the specific platform is locked and we move to a newer ruby version for which a native package is not available" do + # + # Given an existing application using native gems (e.g., nokogiri) + # And a lockfile generated with a stable ruby version + # When want test the application against ruby-head and `bundle install` + # Then bundler should fall back to the generic ruby platform gem + # + simulate_platform "x86_64-linux" do + build_repo4 do + build_gem "nokogiri", "1.14.0" + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "x86_64-linux" + s.required_ruby_version = "< #{Gem.ruby_version}" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "nokogiri", "1.14.0" + G + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "nokogiri", "1.14.0", "x86_64-linux" + end + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.14.0-x86_64-linux) + + PLATFORMS + x86_64-linux + + DEPENDENCIES + nokogiri (= 1.14.0) + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle :install + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "nokogiri", "1.14.0" + end + + expect(lockfile).to eq(<<~L) + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.14.0) + + PLATFORMS + x86_64-linux + + DEPENDENCIES + nokogiri (= 1.14.0) + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + it "locks specific platforms automatically" do + simulate_platform "x86_64-linux" do + build_repo4 do + build_gem "nokogiri", "1.14.0" + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "x86_64-linux" + end + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "arm-linux" + end + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "x64-mingw32" + end + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "java" + end + + build_gem "sorbet-static", "0.5.10696" do |s| + s.platform = "x86_64-linux" + end + build_gem "sorbet-static", "0.5.10696" do |s| + s.platform = "universal-darwin-22" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "nokogiri" + G + + bundle "lock" + + checksums = checksums_section_when_existing do |c| + c.no_checksum "nokogiri", "1.14.0" + c.no_checksum "nokogiri", "1.14.0", "arm-linux" + c.no_checksum "nokogiri", "1.14.0", "x86_64-linux" + end + + # locks all compatible platforms, excluding Java and Windows + expect(lockfile).to eq(<<~L) + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.14.0) + nokogiri (1.14.0-arm-linux) + nokogiri (1.14.0-x86_64-linux) + + PLATFORMS + arm-linux + ruby + x86_64-linux + + DEPENDENCIES + nokogiri + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "nokogiri" + gem "sorbet-static" + G + + FileUtils.rm bundled_app_lock + + bundle "lock" + + checksums.delete "nokogiri", "arm-linux" + checksums.no_checksum "sorbet-static", "0.5.10696", "universal-darwin-22" + checksums.no_checksum "sorbet-static", "0.5.10696", "x86_64-linux" + + # locks only platforms compatible with all gems in the bundle + expect(lockfile).to eq(<<~L) + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.14.0) + nokogiri (1.14.0-x86_64-linux) + sorbet-static (0.5.10696-universal-darwin-22) + sorbet-static (0.5.10696-x86_64-linux) + + PLATFORMS + universal-darwin-22 + x86_64-linux + + DEPENDENCIES + nokogiri + sorbet-static + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + it "does not fail when a platform variant is incompatible with the current ruby and another equivalent platform specific variant is part of the resolution", rubygems: ">= 3.3.21" do + build_repo4 do + build_gem "nokogiri", "1.15.5" + + build_gem "nokogiri", "1.15.5" do |s| + s.platform = "x86_64-linux" + s.required_ruby_version = "< #{current_ruby_minor}.dev" + end + + build_gem "sass-embedded", "1.69.5" + + build_gem "sass-embedded", "1.69.5" do |s| + s.platform = "x86_64-linux-gnu" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "nokogiri" + gem "sass-embedded" + G + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "nokogiri", "1.15.5" + c.no_checksum "sass-embedded", "1.69.5" + c.checksum gem_repo4, "sass-embedded", "1.69.5", "x86_64-linux-gnu" + end + + simulate_platform "x86_64-linux" do + bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + + # locks all compatible platforms, excluding Java and Windows + expect(lockfile).to eq(<<~L) + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.15.5) + sass-embedded (1.69.5) + sass-embedded (1.69.5-x86_64-linux-gnu) + + PLATFORMS + ruby + x86_64-linux + + DEPENDENCIES + nokogiri + sass-embedded + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + it "does not add ruby platform gem if it brings extra dependencies not resolved originally" do + build_repo4 do + build_gem "nokogiri", "1.15.5" do |s| + s.add_dependency "mini_portile2", "~> 2.8.2" + end + + build_gem "nokogiri", "1.15.5" do |s| + s.platform = "x86_64-linux" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "nokogiri" + G + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo4, "nokogiri", "1.15.5", "x86_64-linux" + end + + simulate_platform "x86_64-linux" do + bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + + expect(lockfile).to eq(<<~L) + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.15.5-x86_64-linux) + + PLATFORMS + x86_64-linux + + DEPENDENCIES + nokogiri + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + ["x86_64-linux", "x86_64-linux-musl"].each do |host_platform| + describe "on host platform #{host_platform}" do + it "adds current musl platform" do + build_repo4 do + build_gem "rcee_precompiled", "0.5.0" do |s| + s.platform = "x86_64-linux" + end + + build_gem "rcee_precompiled", "0.5.0" do |s| + s.platform = "x86_64-linux-musl" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "rcee_precompiled", "0.5.0" + G + + simulate_platform host_platform do + bundle "lock", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + + expect(lockfile).to eq(<<~L) + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + rcee_precompiled (0.5.0-x86_64-linux) + rcee_precompiled (0.5.0-x86_64-linux-musl) + + PLATFORMS + x86_64-linux + x86_64-linux-musl + + DEPENDENCIES + rcee_precompiled (= 0.5.0) + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + end + end + + it "adds current musl platform, when there are also gnu variants", rubygems: ">= 3.3.21" do + build_repo4 do + build_gem "rcee_precompiled", "0.5.0" do |s| + s.platform = "x86_64-linux-gnu" + end + + build_gem "rcee_precompiled", "0.5.0" do |s| + s.platform = "x86_64-linux-musl" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "rcee_precompiled", "0.5.0" + G + + simulate_platform "x86_64-linux-musl" do + bundle "lock", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + + expect(lockfile).to eq(<<~L) + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + rcee_precompiled (0.5.0-x86_64-linux-gnu) + rcee_precompiled (0.5.0-x86_64-linux-musl) + + PLATFORMS + x86_64-linux-gnu + x86_64-linux-musl + + DEPENDENCIES + rcee_precompiled (= 0.5.0) + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + it "does not add current platform if there's an equivalent less specific platform among the ones resolved" do + build_repo4 do + build_gem "rcee_precompiled", "0.5.0" do |s| + s.platform = "universal-darwin" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "rcee_precompiled", "0.5.0" + G + + simulate_platform "x86_64-darwin-15" do + bundle "lock", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + + expect(lockfile).to eq(<<~L) + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + rcee_precompiled (0.5.0-universal-darwin) + + PLATFORMS + universal-darwin + + DEPENDENCIES + rcee_precompiled (= 0.5.0) + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + private def setup_multiplatform_gem @@ -312,4 +1410,16 @@ RSpec.describe "bundle install with specific platforms" do build_gem("CFPropertyList") end end + + def setup_multiplatform_gem_with_source_gem + build_repo2 do + build_gem("my-precompiled-gem", "3.0.0") + build_gem("my-precompiled-gem", "3.0.0") do |s| + s.platform = Bundler.local_platform + + # purposely unresolvable + s.required_ruby_version = ">= 1000.0.0" + end + end + end end diff --git a/spec/bundler/install/gemfile_spec.rb b/spec/bundler/install/gemfile_spec.rb index 0f8f1ecfa8..158645d3eb 100644 --- a/spec/bundler/install/gemfile_spec.rb +++ b/spec/bundler/install/gemfile_spec.rb @@ -3,7 +3,7 @@ RSpec.describe "bundle install" do context "with duplicated gems" do it "will display a warning" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem 'rails', '~> 4.0.0' @@ -20,7 +20,7 @@ RSpec.describe "bundle install" do gem 'rack' G - bundle :install, :gemfile => bundled_app("NotGemfile") + bundle :install, gemfile: bundled_app("NotGemfile") # Specify BUNDLE_GEMFILE for `the_bundle` # to retrieve the proper Gemfile @@ -46,8 +46,8 @@ RSpec.describe "bundle install" do end it "uses the gemfile while in a subdirectory" do bundled_app("subdir").mkpath - bundle "install", :dir => bundled_app("subdir") - bundle "list", :dir => bundled_app("subdir") + bundle "install", dir: bundled_app("subdir") + bundle "list", dir: bundled_app("subdir") expect(out).to include("rack (1.0.0)") end @@ -61,12 +61,12 @@ RSpec.describe "bundle install" do gem "rack", :lib => "rack" G - bundle :install, :raise_on_error => false + bundle :install, raise_on_error: false expect(err).to match(/You passed :lib as an option for gem 'rack', but it is invalid/) end end - context "with engine specified in symbol", :jruby do + context "with engine specified in symbol", :jruby_only do it "does not raise any error parsing Gemfile" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" diff --git a/spec/bundler/install/gems/compact_index_spec.rb b/spec/bundler/install/gems/compact_index_spec.rb index 0cee69f702..50add8743b 100644 --- a/spec/bundler/install/gems/compact_index_spec.rb +++ b/spec/bundler/install/gems/compact_index_spec.rb @@ -10,7 +10,7 @@ RSpec.describe "compact index api" do gem "rack" G - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -21,7 +21,7 @@ RSpec.describe "compact index api" do gem " sinatra" G - bundle :install, :artifice => "compact_index", :raise_on_error => false + bundle :install, artifice: "compact_index", raise_on_error: false expect(err).to include("' sinatra' is not a valid gem name because it contains whitespace.") end @@ -31,7 +31,7 @@ RSpec.describe "compact index api" do gem "rails" G - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems( "rails 2.3.2", @@ -44,14 +44,14 @@ RSpec.describe "compact index api" do end it "should handle case sensitivity conflicts" do - build_repo4 do + build_repo4(build_compact_index: false) do build_gem "rack", "1.0" do |s| s.add_runtime_dependency("Rack", "0.1") end build_gem "Rack", "0.1" end - install_gemfile <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } source "#{source_uri}" gem "rack", "1.0" gem "Rack", "0.1" @@ -69,7 +69,7 @@ RSpec.describe "compact index api" do gem "net-sftp" G - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(the_bundle).to include_gems "net-sftp 1.1.1" end @@ -78,11 +78,11 @@ RSpec.describe "compact index api" do source "#{source_uri}" gem "rack" G - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" bundle "config set --local deployment true" bundle "config set --local path vendor/bundle" - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -100,7 +100,7 @@ RSpec.describe "compact index api" do end G - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(the_bundle).to include_gems("rails 2.3.2") end @@ -116,10 +116,10 @@ RSpec.describe "compact index api" do gem 'foo', :git => "#{file_uri_for(lib_path("foo-1.0"))}" G - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" bundle "config set --local deployment true" - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(the_bundle).to include_gems("rails 2.3.2") end @@ -131,9 +131,9 @@ RSpec.describe "compact index api" do gem 'foo', :git => "#{file_uri_for(lib_path("foo-1.0"))}" G - bundle "install", :artifice => "compact_index" + bundle "install", artifice: "compact_index" bundle "config set --local deployment true" - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(the_bundle).to include_gems("foo 1.0") end @@ -144,7 +144,7 @@ RSpec.describe "compact index api" do gem "rack" G - bundle :install, :verbose => true, :artifice => "compact_index_forbidden" + bundle :install, verbose: true, artifice: "compact_index_forbidden" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -155,14 +155,34 @@ RSpec.describe "compact index api" do gem "rack" G - bundle :install, :verbose => true, :artifice => "compact_index_checksum_mismatch" + bundle :install, verbose: true, artifice: "compact_index_checksum_mismatch" expect(out).to include("Fetching gem metadata from #{source_uri}") - expect(out).to include <<-'WARN' -The checksum of /versions does not match the checksum provided by the server! Something is wrong (local checksum is "\"d41d8cd98f00b204e9800998ecf8427e\"", was expecting "\"123\""). - WARN + expect(out).to include("The checksum of /versions does not match the checksum provided by the server!") + expect(out).to include('Calculated checksums {"sha-256"=>"8KfZiM/fszVkqhP/m5s9lvE6M9xKu4I1bU4Izddp5Ms="} did not match expected {"sha-256"=>"ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0="}') expect(the_bundle).to include_gems "rack 1.0.0" end + it "shows proper path when permission errors happen", :permissions do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + + versions = Pathname.new(Bundler.rubygems.user_home).join( + ".bundle", "cache", "compact_index", + "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions" + ) + versions.dirname.mkpath + versions.write("created_at") + FileUtils.chmod("-r", versions) + + bundle :install, artifice: "compact_index", raise_on_error: false + + expect(err).to include( + "There was an error while trying to read from `#{versions}`. It is likely that you need to grant read permissions for that path." + ) + end + it "falls back when the user's home directory does not exist or is not writable" do ENV["HOME"] = tmp("missing_home").to_s @@ -171,7 +191,7 @@ The checksum of /versions does not match the checksum provided by the server! So gem "rack" G - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -182,11 +202,11 @@ The checksum of /versions does not match the checksum provided by the server! So gem "rack" G - bundle :install, :artifice => "compact_index_host_redirect" + bundle :install, artifice: "compact_index_host_redirect" expect(the_bundle).to include_gems "rack 1.0.0" end - it "handles host redirects without Net::HTTP::Persistent" do + it "handles host redirects without Gem::Net::HTTP::Persistent" do gemfile <<-G source "#{source_uri}" gem "rack" @@ -205,7 +225,7 @@ The checksum of /versions does not match the checksum provided by the server! So H end - bundle :install, :artifice => "compact_index_host_redirect", :requires => [lib_path("disable_net_http_persistent.rb")] + bundle :install, artifice: "compact_index_host_redirect", requires: [lib_path("disable_net_http_persistent.rb")] expect(out).to_not match(/Too many redirects/) expect(the_bundle).to include_gems "rack 1.0.0" end @@ -216,7 +236,7 @@ The checksum of /versions does not match the checksum provided by the server! So gem "rack" G - bundle :install, :artifice => "compact_index_redirects", :raise_on_error => false + bundle :install, artifice: "compact_index_redirects", raise_on_error: false expect(err).to match(/Too many redirects/) end @@ -227,7 +247,7 @@ The checksum of /versions does not match the checksum provided by the server! So gem "rack" G - bundle "install --full-index", :artifice => "compact_index" + bundle "install --full-index", artifice: "compact_index" expect(out).to include("Fetching source index from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -238,7 +258,7 @@ The checksum of /versions does not match the checksum provided by the server! So gem "rack" G - bundle "update --full-index", :artifice => "compact_index", :all => true + bundle "update --full-index", artifice: "compact_index", all: true expect(out).to include("Fetching source index from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -258,7 +278,7 @@ The checksum of /versions does not match the checksum provided by the server! So s.extensions << "Rakefile" s.write "Rakefile", <<-RUBY task :default do - path = File.expand_path("../lib", __FILE__) + path = File.expand_path("lib", __dir__) FileUtils.mkdir_p(path) File.open("\#{path}/net_build_extensions.rb", "w") do |f| f.puts "NET_BUILD_EXTENSIONS = 'YES'" @@ -268,14 +288,14 @@ The checksum of /versions does not match the checksum provided by the server! So end end - system_gems %w[rack-1.0.0 thin-1.0 net_a-1.0], :gem_repo => gem_repo2 + system_gems %w[rack-1.0.0 thin-1.0 net_a-1.0], gem_repo: gem_repo2 bundle "config set --local path.system true" - ENV["BUNDLER_SPEC_ALL_REQUESTS"] = strip_whitespace(<<-EOS).strip + ENV["BUNDLER_SPEC_ALL_REQUESTS"] = <<~EOS.strip #{source_uri}/versions #{source_uri}/info/rack EOS - install_gemfile <<-G, :artifice => "compact_index", :verbose => true, :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + install_gemfile <<-G, artifice: "compact_index", verbose: true, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } source "#{source_uri}" gem "rack" G @@ -283,7 +303,7 @@ The checksum of /versions does not match the checksum provided by the server! So expect(last_command.stdboth).not_to include "Double checking" end - it "fetches again when more dependencies are found in subsequent sources", :bundler => "< 3" do + it "fetches again when more dependencies are found in subsequent sources", bundler: "< 3" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" @@ -297,7 +317,7 @@ The checksum of /versions does not match the checksum provided by the server! So gem "back_deps" G - bundle :install, :artifice => "compact_index_extra" + bundle :install, artifice: "compact_index_extra" expect(the_bundle).to include_gems "back_deps 1.0", "foo 1.0" end @@ -309,7 +329,7 @@ The checksum of /versions does not match the checksum provided by the server! So FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] end - install_gemfile <<-G, :artifice => "compact_index_extra", :verbose => true + install_gemfile <<-G, artifice: "compact_index_extra", verbose: true source "#{source_uri}" source "#{source_uri}/extra" do gem "back_deps" @@ -324,7 +344,7 @@ The checksum of /versions does not match the checksum provided by the server! So source "#{source_uri}" gem "rack", "1.0.0" G - bundle :install, :artifice => "compact_index_extra_api" + bundle :install, artifice: "compact_index_extra_api" expect(the_bundle).to include_gems "rack 1.0.0" build_repo4 do @@ -338,11 +358,11 @@ The checksum of /versions does not match the checksum provided by the server! So source "#{source_uri}/extra" gem "rack", "1.2" G - bundle :install, :artifice => "compact_index_extra_api" + bundle :install, artifice: "compact_index_extra_api" expect(the_bundle).to include_gems "rack 1.2" end - it "considers all possible versions of dependencies from all api gem sources", :bundler => "< 3" do + it "considers all possible versions of dependencies from all api gem sources", bundler: "< 3" do # In this scenario, the gem "somegem" only exists in repo4. It depends on specific version of activesupport that # exists only in repo1. There happens also be a version of activesupport in repo4, but not the one that version 1.0.0 # of somegem wants. This test makes sure that bundler actually finds version 1.2.3 of active support in the other @@ -360,7 +380,7 @@ The checksum of /versions does not match the checksum provided by the server! So gem 'somegem', '1.0.0' G - bundle :install, :artifice => "compact_index_extra_api" + bundle :install, artifice: "compact_index_extra_api" expect(the_bundle).to include_gems "somegem 1.0.0" expect(the_bundle).to include_gems "activesupport 1.2.3" @@ -381,13 +401,13 @@ The checksum of /versions does not match the checksum provided by the server! So end G - bundle :install, :artifice => "compact_index_extra" + bundle :install, artifice: "compact_index_extra" expect(out).to include("Fetching gem metadata from http://localgemserver.test/") expect(out).to include("Fetching source index from http://localgemserver.test/extra") end - it "does not fetch every spec if the index of gems is large when doing back deps" do + it "does not fetch every spec when doing back deps" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" @@ -397,9 +417,7 @@ The checksum of /versions does not match the checksum provided by the server! So FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] end - api_request_limit = low_api_request_limit_for(gem_repo2) - - install_gemfile <<-G, :artifice => "compact_index_extra_missing", :requires => [api_request_limit_hack_file], :env => { "BUNDLER_SPEC_API_REQUEST_LIMIT" => api_request_limit.to_s }.merge(env_for_missing_prerelease_default_gem_activation) + install_gemfile <<-G, artifice: "compact_index_extra_missing" source "#{source_uri}" source "#{source_uri}/extra" do gem "back_deps" @@ -409,7 +427,7 @@ The checksum of /versions does not match the checksum provided by the server! So expect(the_bundle).to include_gems "back_deps 1.0" end - it "does not fetch every spec if the index of gems is large when doing back deps & everything is the compact index" do + it "does not fetch every spec when doing back deps & everything is the compact index" do build_repo4 do build_gem "back_deps" do |s| s.add_dependency "foo" @@ -419,9 +437,7 @@ The checksum of /versions does not match the checksum provided by the server! So FileUtils.rm_rf Dir[gem_repo4("gems/foo-*.gem")] end - api_request_limit = low_api_request_limit_for(gem_repo4) - - install_gemfile <<-G, :artifice => "compact_index_extra_api_missing", :requires => [api_request_limit_hack_file], :env => { "BUNDLER_SPEC_API_REQUEST_LIMIT" => api_request_limit.to_s }.merge(env_for_missing_prerelease_default_gem_activation) + install_gemfile <<-G, artifice: "compact_index_extra_api_missing" source "#{source_uri}" source "#{source_uri}/extra" do gem "back_deps" @@ -438,11 +454,11 @@ The checksum of /versions does not match the checksum provided by the server! So gem 'foo' G - bundle :install, :artifice => "compact_index_api_missing" + bundle :install, artifice: "compact_index_api_missing" expect(the_bundle).to include_gems "foo 1.0" end - it "fetches again when more dependencies are found in subsequent sources using deployment mode", :bundler => "< 3" do + it "fetches again when more dependencies are found in subsequent sources using deployment mode", bundler: "< 3" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" @@ -456,9 +472,9 @@ The checksum of /versions does not match the checksum provided by the server! So gem "back_deps" G - bundle :install, :artifice => "compact_index_extra" + bundle :install, artifice: "compact_index_extra" bundle "config --set local deployment true" - bundle :install, :artifice => "compact_index_extra" + bundle :install, artifice: "compact_index_extra" expect(the_bundle).to include_gems "back_deps 1.0" end @@ -477,9 +493,9 @@ The checksum of /versions does not match the checksum provided by the server! So end G - bundle :install, :artifice => "compact_index_extra" + bundle :install, artifice: "compact_index_extra" bundle "config set --local deployment true" - bundle :install, :artifice => "compact_index_extra" + bundle :install, artifice: "compact_index_extra" expect(the_bundle).to include_gems "back_deps 1.0" end @@ -496,52 +512,40 @@ The checksum of /versions does not match the checksum provided by the server! So gem "bundler_dep" G - bundle :install, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + bundle :install, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } expect(out).to include("Fetching gem metadata from #{source_uri}") end - it "should install when EndpointSpecification has a bin dir owned by root", :sudo => true do - sudo "mkdir -p #{system_gem_path("bin")}" - sudo "chown -R root #{system_gem_path("bin")}" - - gemfile <<-G - source "#{source_uri}" - gem "rails" - G - bundle :install, :artifice => "compact_index" - expect(the_bundle).to include_gems "rails 2.3.2" - end - - it "installs the binstubs", :bundler => "< 3" do + it "installs the binstubs", bundler: "< 3" do gemfile <<-G source "#{source_uri}" gem "rack" G - bundle "install --binstubs", :artifice => "compact_index" + bundle "install --binstubs", artifice: "compact_index" gembin "rackup" expect(out).to eq("1.0.0") end - it "installs the bins when using --path and uses autoclean", :bundler => "< 3" do + it "installs the bins when using --path and uses autoclean", bundler: "< 3" do gemfile <<-G source "#{source_uri}" gem "rack" G - bundle "install --path vendor/bundle", :artifice => "compact_index" + bundle "install --path vendor/bundle", artifice: "compact_index" expect(vendored_gems("bin/rackup")).to exist end - it "installs the bins when using --path and uses bundle clean", :bundler => "< 3" do + it "installs the bins when using --path and uses bundle clean", bundler: "< 3" do gemfile <<-G source "#{source_uri}" gem "rack" G - bundle "install --path vendor/bundle --no-clean", :artifice => "compact_index" + bundle "install --path vendor/bundle --no-clean", artifice: "compact_index" expect(vendored_gems("bin/rackup")).to exist end @@ -552,7 +556,7 @@ The checksum of /versions does not match the checksum provided by the server! So gem 'rack-obama' G - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(out).to include("Post-install message from rack:") end @@ -562,7 +566,7 @@ The checksum of /versions does not match the checksum provided by the server! So gem 'rack_middleware' G - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(out).to include("Post-install message from rack:") expect(out).to include("Rack's post install message") end @@ -571,7 +575,7 @@ The checksum of /versions does not match the checksum provided by the server! So let(:user) { "user" } let(:password) { "pass" } let(:basic_auth_source_uri) do - uri = Bundler::URI.parse(source_uri) + uri = Gem::URI.parse(source_uri) uri.user = user uri.password = password @@ -584,7 +588,7 @@ The checksum of /versions does not match the checksum provided by the server! So gem "rack" G - bundle :install, :artifice => "compact_index_basic_authentication" + bundle :install, artifice: "compact_index_basic_authentication" expect(out).not_to include("#{user}:#{password}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -595,19 +599,19 @@ The checksum of /versions does not match the checksum provided by the server! So gem "rack" G - bundle :install, :verbose => true, :artifice => "compact_index_basic_authentication" + bundle :install, verbose: true, artifice: "compact_index_basic_authentication" expect(out).not_to include("#{user}:#{password}") expect(the_bundle).to include_gems "rack 1.0.0" end - it "strips http basic auth creds when warning about ambiguous sources", :bundler => "< 3" do + it "strips http basic auth creds when warning about ambiguous sources", bundler: "< 3" do gemfile <<-G source "#{basic_auth_source_uri}" source "#{file_uri_for(gem_repo1)}" gem "rack" G - bundle :install, :artifice => "compact_index_basic_authentication" + bundle :install, artifice: "compact_index_basic_authentication" expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") expect(err).not_to include("#{user}:#{password}") expect(the_bundle).to include_gems "rack 1.0.0" @@ -619,7 +623,7 @@ The checksum of /versions does not match the checksum provided by the server! So gem "rack" G - bundle :install, :artifice => "compact_index_creds_diff_host" + bundle :install, artifice: "compact_index_creds_diff_host" expect(the_bundle).to include_gems "rack 1.0.0" end @@ -634,7 +638,7 @@ The checksum of /versions does not match the checksum provided by the server! So it "reads authentication details by host name from bundle config" do bundle "config set #{source_hostname} #{user}:#{password}" - bundle :install, :artifice => "compact_index_strict_basic_authentication" + bundle :install, artifice: "compact_index_strict_basic_authentication" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" @@ -644,7 +648,7 @@ The checksum of /versions does not match the checksum provided by the server! So # The trailing slash is necessary here; Fetcher canonicalizes the URI. bundle "config set #{source_uri}/ #{user}:#{password}" - bundle :install, :artifice => "compact_index_strict_basic_authentication" + bundle :install, artifice: "compact_index_strict_basic_authentication" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" @@ -652,7 +656,7 @@ The checksum of /versions does not match the checksum provided by the server! So it "should use the API" do bundle "config set #{source_hostname} #{user}:#{password}" - bundle :install, :artifice => "compact_index_strict_basic_authentication" + bundle :install, artifice: "compact_index_strict_basic_authentication" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -665,20 +669,29 @@ The checksum of /versions does not match the checksum provided by the server! So bundle "config set #{source_hostname} otheruser:wrong" - bundle :install, :artifice => "compact_index_strict_basic_authentication" + bundle :install, artifice: "compact_index_strict_basic_authentication" expect(the_bundle).to include_gems "rack 1.0.0" end it "shows instructions if auth is not provided for the source" do - bundle :install, :artifice => "compact_index_strict_basic_authentication", :raise_on_error => false + bundle :install, artifice: "compact_index_strict_basic_authentication", raise_on_error: false expect(err).to include("bundle config set --global #{source_hostname} username:password") end it "fails if authentication has already been provided, but failed" do bundle "config set #{source_hostname} #{user}:wrong" - bundle :install, :artifice => "compact_index_strict_basic_authentication", :raise_on_error => false + bundle :install, artifice: "compact_index_strict_basic_authentication", raise_on_error: false + expect(err).to include("Bad username or password") + end + + it "does not fallback to old dependency API if bad authentication is provided" do + bundle "config set #{source_hostname} #{user}:wrong" + + bundle :install, artifice: "compact_index_strict_basic_authentication", raise_on_error: false, verbose: true expect(err).to include("Bad username or password") + expect(out).to include("HTTP 401 Unauthorized http://user@localgemserver.test/versions") + expect(out).not_to include("HTTP 401 Unauthorized http://user@localgemserver.test/api/v1/dependencies") end end @@ -691,7 +704,7 @@ The checksum of /versions does not match the checksum provided by the server! So gem "rack" G - bundle :install, :artifice => "compact_index_basic_authentication" + bundle :install, artifice: "compact_index_basic_authentication" expect(the_bundle).to include_gems "rack 1.0.0" end end @@ -716,7 +729,7 @@ The checksum of /versions does not match the checksum provided by the server! So gem "rack" G - bundle :install, :env => { "RUBYOPT" => opt_add("-I#{bundled_app("broken_ssl")}", ENV["RUBYOPT"]) }, :raise_on_error => false + bundle :install, env: { "RUBYOPT" => opt_add("-I#{bundled_app("broken_ssl")}", ENV["RUBYOPT"]) }, raise_on_error: false expect(err).to include("OpenSSL") end end @@ -726,7 +739,7 @@ The checksum of /versions does not match the checksum provided by the server! So # Install a monkeypatch that reproduces the effects of openssl raising # a certificate validation error when RubyGems tries to connect. gemfile <<-G - class Net::HTTP + class Gem::Net::HTTP def start raise OpenSSL::SSL::SSLError, "certificate verify failed" end @@ -736,7 +749,7 @@ The checksum of /versions does not match the checksum provided by the server! So gem "rack" G - bundle :install, :raise_on_error => false + bundle :install, raise_on_error: false expect(err).to match(/could not verify the SSL certificate/i) end end @@ -744,7 +757,7 @@ The checksum of /versions does not match the checksum provided by the server! So context ".gemrc with sources is present" do it "uses other sources declared in the Gemfile" do File.open(home(".gemrc"), "w") do |file| - file.puts({ :sources => ["https://rubygems.org"] }.to_yaml) + file.puts({ sources: ["https://rubygems.org"] }.to_yaml) end begin @@ -753,23 +766,30 @@ The checksum of /versions does not match the checksum provided by the server! So gem 'rack' G - bundle :install, :artifice => "compact_index_forbidden" + bundle :install, artifice: "compact_index_forbidden" ensure home(".gemrc").rmtree end end end - it "performs partial update with a non-empty range" do - skip "HTTP_RANGE not set" if Gem.win_platform? + it "performs update with etag not-modified" do + versions_etag = Pathname.new(Bundler.rubygems.user_home).join( + ".bundle", "cache", "compact_index", + "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions.etag" + ) + expect(versions_etag.file?).to eq(false) gemfile <<-G source "#{source_uri}" gem 'rack', '0.9.1' G - # Initial install creates the cached versions file - bundle :install, :artifice => "compact_index" + # Initial install creates the cached versions file and etag file + bundle :install, artifice: "compact_index" + + expect(versions_etag.file?).to eq(true) + previous_content = versions_etag.binread # Update the Gemfile so we can check subsequent install was successful gemfile <<-G @@ -777,8 +797,59 @@ The checksum of /versions does not match the checksum provided by the server! So gem 'rack', '1.0.0' G - # Second install should make only a partial request to /versions - bundle :install, :artifice => "compact_index_partial_update" + # Second install should match etag + bundle :install, artifice: "compact_index_etag_match" + + expect(versions_etag.binread).to eq(previous_content) + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "performs full update when range is ignored" do + gemfile <<-G + source "#{source_uri}" + gem 'rack', '0.9.1' + G + + # Initial install creates the cached versions file and etag file + bundle :install, artifice: "compact_index" + + gemfile <<-G + source "#{source_uri}" + gem 'rack', '1.0.0' + G + + versions = Pathname.new(Bundler.rubygems.user_home).join( + ".bundle", "cache", "compact_index", + "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions" + ) + # Modify the cached file. The ranged request will be based on this but, + # in this test, the range is ignored so this gets overwritten, allowing install. + versions.write "ruining this file" + + bundle :install, artifice: "compact_index_range_ignored" + + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "performs partial update with a non-empty range" do + build_repo4 do + build_gem "rack", "0.9.1" + end + + # Initial install creates the cached versions file + install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + source "#{source_uri}" + gem 'rack', '0.9.1' + G + + update_repo4 do + build_gem "rack", "1.0.0" + end + + install_gemfile <<-G, artifice: "compact_index_partial_update", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + source "#{source_uri}" + gem 'rack', '1.0.0' + G expect(the_bundle).to include_gems "rack 1.0.0" end @@ -789,24 +860,26 @@ The checksum of /versions does not match the checksum provided by the server! So gem 'rack' G - # Create an empty file to trigger a partial download - versions = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index", - "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions") - FileUtils.mkdir_p(File.dirname(versions)) - FileUtils.touch(versions) + # Create a partial cache versions file + versions = Pathname.new(Bundler.rubygems.user_home).join( + ".bundle", "cache", "compact_index", + "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions" + ) + versions.dirname.mkpath + versions.write("created_at") - bundle :install, :artifice => "compact_index_concurrent_download" + bundle :install, artifice: "compact_index_concurrent_download" - expect(File.read(versions)).to start_with("created_at") + expect(versions.read).to start_with("created_at") expect(the_bundle).to include_gems "rack 1.0.0" end - it "performs full update if server endpoints serve partial content responses but don't have incremental content and provide no Etag" do + it "performs a partial update that fails digest check, then a full update" do build_repo4 do build_gem "rack", "0.9.1" end - install_gemfile <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } source "#{source_uri}" gem 'rack', '0.9.1' G @@ -815,7 +888,29 @@ The checksum of /versions does not match the checksum provided by the server! So build_gem "rack", "1.0.0" end - install_gemfile <<-G, :artifice => "compact_index_partial_update_no_etag_not_incremental", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + install_gemfile <<-G, artifice: "compact_index_partial_update_bad_digest", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + source "#{source_uri}" + gem 'rack', '1.0.0' + G + + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "performs full update if server endpoints serve partial content responses but don't have incremental content and provide no digest" do + build_repo4 do + build_gem "rack", "0.9.1" + end + + install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + source "#{source_uri}" + gem 'rack', '0.9.1' + G + + update_repo4 do + build_gem "rack", "1.0.0" + end + + install_gemfile <<-G, artifice: "compact_index_partial_update_no_digest_not_incremental", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } source "#{source_uri}" gem 'rack', '1.0.0' G @@ -829,15 +924,19 @@ The checksum of /versions does not match the checksum provided by the server! So gem 'rack', '0.9.1' G - rake_info_path = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index", - "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "info", "rack") + bundle :install, artifice: "compact_index" - bundle :install, :artifice => "compact_index" + # We must remove the etag so that we don't ignore the range and get a 304 Not Modified. + rake_info_etag_path = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index", + "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "info-etags", "rack-11690b09f16021ff06a6857d784a1870") + File.unlink(rake_info_etag_path) if File.exist?(rake_info_etag_path) + rake_info_path = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index", + "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "info", "rack") expected_rack_info_content = File.read(rake_info_path) - # Modify the cache files. We expect them to be reset to the normal ones when we re-run :install - File.open(rake_info_path, "w") {|f| f << (expected_rack_info_content + "this is different") } + # Modify the cache files to make the range not satisfiable + File.open(rake_info_path, "a") {|f| f << "0.9.2 |checksum:c55b525b421fd833a93171ad3d7f04528ca8e87d99ac273f8933038942a5888c" } # Update the Gemfile so the next install does its normal things gemfile <<-G @@ -847,7 +946,7 @@ The checksum of /versions does not match the checksum provided by the server! So # The cache files now being longer means the requested range is going to be not satisfiable # Bundler must end up requesting the whole file to fix things up. - bundle :install, :artifice => "compact_index_range_not_satisfiable" + bundle :install, artifice: "compact_index_range_not_satisfiable" resulting_rack_info_content = File.read(rake_info_path) @@ -855,7 +954,7 @@ The checksum of /versions does not match the checksum provided by the server! So end it "fails gracefully when the source URI has an invalid scheme" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "htps://rubygems.org" gem "rack" G @@ -866,38 +965,76 @@ The checksum of /versions does not match the checksum provided by the server! So end describe "checksum validation" do + before do + lockfile <<-L + GEM + remote: #{source_uri} + specs: + rack (1.0.0) + + PLATFORMS + ruby + + DEPENDENCIES + #{checksums_section} + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "handles checksums from the server in base64" do + api_checksum = checksum_digest(gem_repo1, "rack", "1.0.0") + rack_checksum = [[api_checksum].pack("H*")].pack("m0") + install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_RACK_CHECKSUM" => rack_checksum } + source "#{source_uri}" + gem "rack" + G + + expect(out).to include("Fetching gem metadata from #{source_uri}") + expect(the_bundle).to include_gems("rack 1.0.0") + end + it "raises when the checksum does not match" do - install_gemfile <<-G, :artifice => "compact_index_wrong_gem_checksum", :raise_on_error => false + install_gemfile <<-G, artifice: "compact_index_wrong_gem_checksum", raise_on_error: false source "#{source_uri}" gem "rack" G - expect(exitstatus).to eq(19) - expect(err). - to include("Bundler cannot continue installing rack (1.0.0)."). - and include("The checksum for the downloaded `rack-1.0.0.gem` does not match the checksum given by the server."). - and include("This means the contents of the downloaded gem is different from what was uploaded to the server, and could be a potential security issue."). - and include("To resolve this issue:"). - and include("1. delete the downloaded gem located at: `#{default_bundle_path}/gems/rack-1.0.0/rack-1.0.0.gem`"). - and include("2. run `bundle install`"). - and include("If you wish to continue installing the downloaded gem, and are certain it does not pose a security issue despite the mismatching checksum, do the following:"). - and include("1. run `bundle config set --local disable_checksum_validation true` to turn off checksum verification"). - and include("2. run `bundle install`"). - and match(/\(More info: The expected SHA256 checksum was "#{"ab" * 22}", but the checksum for the downloaded gem was ".+?"\.\)/) + gem_path = if Bundler.feature_flag.global_gem_cache? + default_cache_path.dirname.join("cache", "gems", "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "rack-1.0.0.gem") + else + default_cache_path.dirname.join("rack-1.0.0.gem") + end + + expect(exitstatus).to eq(37) + expect(err).to eq <<~E.strip + Bundler found mismatched checksums. This is a potential security risk. + rack (1.0.0) sha256=2222222222222222222222222222222222222222222222222222222222222222 + from the API at http://localgemserver.test/ + #{checksum_to_lock(gem_repo1, "rack", "1.0.0")} + from the gem at #{gem_path} + + If you trust the API at http://localgemserver.test/, to resolve this issue you can: + 1. remove the gem at #{gem_path} + 2. run `bundle install` + + To ignore checksum security warnings, disable checksum validation with + `bundle config set --local disable_checksum_validation true` + E end it "raises when the checksum is the wrong length" do - install_gemfile <<-G, :artifice => "compact_index_wrong_gem_checksum", :env => { "BUNDLER_SPEC_RACK_CHECKSUM" => "checksum!", "DEBUG" => "1" }, :verbose => true, :raise_on_error => false + install_gemfile <<-G, artifice: "compact_index_wrong_gem_checksum", env: { "BUNDLER_SPEC_RACK_CHECKSUM" => "checksum!", "DEBUG" => "1" }, verbose: true, raise_on_error: false source "#{source_uri}" gem "rack" G - expect(exitstatus).to eq(5) - expect(err).to include("The given checksum for rack-1.0.0 (\"checksum!\") is not a valid SHA256 hexdigest nor base64digest") + expect(exitstatus).to eq(14) + expect(err).to include('Invalid checksum for rack-0.9.1: "checksum!" is not a valid SHA256 hex or base64 digest') end it "does not raise when disable_checksum_validation is set" do bundle "config set disable_checksum_validation true" - install_gemfile <<-G, :artifice => "compact_index_wrong_gem_checksum" + install_gemfile <<-G, artifice: "compact_index_wrong_gem_checksum" source "#{source_uri}" gem "rack" G @@ -905,7 +1042,7 @@ The checksum of /versions does not match the checksum provided by the server! So end it "works when cache dir is world-writable" do - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" File.umask(0000) source "#{source_uri}" gem "rack" @@ -913,23 +1050,24 @@ The checksum of /versions does not match the checksum provided by the server! So end it "doesn't explode when the API dependencies are wrong" do - install_gemfile <<-G, :artifice => "compact_index_wrong_dependencies", :env => { "DEBUG" => "true" }, :raise_on_error => false + install_gemfile <<-G, artifice: "compact_index_wrong_dependencies", env: { "DEBUG" => "true" }, raise_on_error: false source "#{source_uri}" gem "rails" G - deps = [Gem::Dependency.new("rake", "= 13.0.1"), + deps = [Gem::Dependency.new("rake", "= #{rake_version}"), Gem::Dependency.new("actionpack", "= 2.3.2"), Gem::Dependency.new("activerecord", "= 2.3.2"), Gem::Dependency.new("actionmailer", "= 2.3.2"), Gem::Dependency.new("activeresource", "= 2.3.2")] - expect(out).to include(<<-E.strip).and include("rails-2.3.2 from rubygems remote at #{source_uri}/ has either corrupted API or lockfile dependencies") + expect(out).to include("rails-2.3.2 from rubygems remote at #{source_uri}/ has either corrupted API or lockfile dependencies") + expect(err).to include(<<-E.strip) Bundler::APIResponseMismatchError: Downloading rails-2.3.2 revealed dependencies not in the API or the lockfile (#{deps.map(&:to_s).join(", ")}). -Either installing with `--full-index` or running `bundle update rails` should fix the problem. +Running `bundle update rails` should fix the problem. E end it "does not duplicate specs in the lockfile when updating and a dependency is not installed" do - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "#{file_uri_for(gem_repo1)}" source "#{source_uri}" do gem "rails" @@ -937,7 +1075,8 @@ Either installing with `--full-index` or running `bundle update rails` should fi end G gem_command "uninstall activemerchant" - bundle "update rails", :artifice => "compact_index" - expect(lockfile.scan(/activemerchant \(/).size).to eq(1) + bundle "update rails", artifice: "compact_index" + count = lockfile.match?("CHECKSUMS") ? 2 : 1 # Once in the specs, and once in CHECKSUMS + expect(lockfile.scan(/activemerchant \(/).size).to eq(count) end end diff --git a/spec/bundler/install/gems/dependency_api_spec.rb b/spec/bundler/install/gems/dependency_api_spec.rb index 9738a75474..35468b3a1c 100644 --- a/spec/bundler/install/gems/dependency_api_spec.rb +++ b/spec/bundler/install/gems/dependency_api_spec.rb @@ -10,7 +10,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle :install, :artifice => "endpoint" + bundle :install, artifice: "endpoint" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -21,7 +21,7 @@ RSpec.describe "gemcutter's dependency API" do gem " sinatra" G - bundle :install, :artifice => "endpoint", :raise_on_error => false + bundle :install, artifice: "endpoint", raise_on_error: false expect(err).to include("' sinatra' is not a valid gem name because it contains whitespace.") end @@ -31,7 +31,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rails" G - bundle :install, :artifice => "endpoint" + bundle :install, artifice: "endpoint" expect(out).to include("Fetching gem metadata from #{source_uri}/...") expect(the_bundle).to include_gems( "rails 2.3.2", @@ -49,7 +49,7 @@ RSpec.describe "gemcutter's dependency API" do gem "net-sftp" G - bundle :install, :artifice => "endpoint" + bundle :install, artifice: "endpoint" expect(the_bundle).to include_gems "net-sftp 1.1.1" end @@ -58,11 +58,11 @@ RSpec.describe "gemcutter's dependency API" do source "#{source_uri}" gem "rack" G - bundle :install, :artifice => "endpoint" + bundle :install, artifice: "endpoint" bundle "config set --local deployment true" bundle "config set --local path vendor/bundle" - bundle :install, :artifice => "endpoint" + bundle :install, artifice: "endpoint" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -80,7 +80,7 @@ RSpec.describe "gemcutter's dependency API" do end G - bundle :install, :artifice => "endpoint" + bundle :install, artifice: "endpoint" expect(the_bundle).to include_gems("rails 2.3.2") end @@ -96,10 +96,10 @@ RSpec.describe "gemcutter's dependency API" do gem 'foo', :git => "#{file_uri_for(lib_path("foo-1.0"))}" G - bundle :install, :artifice => "endpoint" + bundle :install, artifice: "endpoint" bundle "config set --local deployment true" - bundle :install, :artifice => "endpoint" + bundle :install, artifice: "endpoint" expect(the_bundle).to include_gems("rails 2.3.2") end @@ -111,15 +111,15 @@ RSpec.describe "gemcutter's dependency API" do gem 'foo', :git => "#{file_uri_for(lib_path("foo-1.0"))}" G - bundle "install", :artifice => "endpoint" + bundle "install", artifice: "endpoint" bundle "config set --local deployment true" - bundle :install, :artifice => "endpoint" + bundle :install, artifice: "endpoint" expect(the_bundle).to include_gems("foo 1.0") end it "falls back when the API errors out" do - simulate_platform mswin + simulate_platform x86_mswin32 build_repo2 do # The rcov gem is platform mswin32, but has no arch @@ -134,7 +134,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rcov" G - bundle :install, :artifice => "windows", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + bundle :install, artifice: "windows", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } expect(out).to include("Fetching source index from #{source_uri}") expect(the_bundle).to include_gems "rcov 1.0.0" end @@ -150,7 +150,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" gem "rails" G - bundle :install, :artifice => "endpoint_fallback" + bundle :install, artifice: "endpoint_fallback" expect(out).to include("Fetching source index from #{source_uri}") expect(the_bundle).to include_gems( @@ -171,7 +171,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle :install, :verbose => true, :artifice => "endpoint_marshal_fail" + bundle :install, verbose: true, artifice: "endpoint_marshal_fail" expect(out).to include("could not fetch from the dependency API, trying the full index") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -182,7 +182,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle :install, :verbose => true, :artifice => "endpoint_api_forbidden" + bundle :install, verbose: true, artifice: "endpoint_api_forbidden" expect(out).to include("Fetching source index from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -193,11 +193,11 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle :install, :artifice => "endpoint_host_redirect" + bundle :install, artifice: "endpoint_host_redirect" expect(the_bundle).to include_gems "rack 1.0.0" end - it "handles host redirects without Net::HTTP::Persistent" do + it "handles host redirects without Gem::Net::HTTP::Persistent" do gemfile <<-G source "#{source_uri}" gem "rack" @@ -216,7 +216,7 @@ RSpec.describe "gemcutter's dependency API" do H end - bundle :install, :artifice => "endpoint_host_redirect", :requires => [lib_path("disable_net_http_persistent.rb")] + bundle :install, artifice: "endpoint_host_redirect", requires: [lib_path("disable_net_http_persistent.rb")] expect(out).to_not match(/Too many redirects/) expect(the_bundle).to include_gems "rack 1.0.0" end @@ -227,7 +227,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle :install, :artifice => "endpoint_redirect", :raise_on_error => false + bundle :install, artifice: "endpoint_redirect", raise_on_error: false expect(err).to match(/Too many redirects/) end @@ -238,7 +238,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle "install --full-index", :artifice => "endpoint" + bundle "install --full-index", artifice: "endpoint" expect(out).to include("Fetching source index from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -249,13 +249,13 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle "update --full-index", :artifice => "endpoint", :all => true + bundle "update --full-index", artifice: "endpoint", all: true expect(out).to include("Fetching source index from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" end end - it "fetches again when more dependencies are found in subsequent sources", :bundler => "< 3" do + it "fetches again when more dependencies are found in subsequent sources", bundler: "< 3" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" @@ -269,7 +269,7 @@ RSpec.describe "gemcutter's dependency API" do gem "back_deps" G - bundle :install, :artifice => "endpoint_extra" + bundle :install, artifice: "endpoint_extra" expect(the_bundle).to include_gems "back_deps 1.0", "foo 1.0" end @@ -288,7 +288,7 @@ RSpec.describe "gemcutter's dependency API" do end G - bundle :install, :artifice => "endpoint_extra" + bundle :install, artifice: "endpoint_extra" expect(the_bundle).to include_gems "back_deps 1.0", "foo 1.0" end @@ -297,7 +297,7 @@ RSpec.describe "gemcutter's dependency API" do source "#{source_uri}" gem "rack", "1.0.0" G - bundle :install, :artifice => "endpoint_extra_api" + bundle :install, artifice: "endpoint_extra_api" build_repo4 do build_gem "rack", "1.2" do |s| @@ -310,11 +310,11 @@ RSpec.describe "gemcutter's dependency API" do source "#{source_uri}/extra" gem "rack", "1.2" G - bundle :install, :artifice => "endpoint_extra_api" + bundle :install, artifice: "endpoint_extra_api" expect(the_bundle).to include_gems "rack 1.2" end - it "considers all possible versions of dependencies from all api gem sources", :bundler => "< 3" do + it "considers all possible versions of dependencies from all api gem sources", bundler: "< 3" do # In this scenario, the gem "somegem" only exists in repo4. It depends on specific version of activesupport that # exists only in repo1. There happens also be a version of activesupport in repo4, but not the one that version 1.0.0 # of somegem wants. This test makes sure that bundler actually finds version 1.2.3 of active support in the other @@ -332,7 +332,7 @@ RSpec.describe "gemcutter's dependency API" do gem 'somegem', '1.0.0' G - bundle :install, :artifice => "endpoint_extra_api" + bundle :install, artifice: "endpoint_extra_api" expect(the_bundle).to include_gems "somegem 1.0.0" expect(the_bundle).to include_gems "activesupport 1.2.3" @@ -353,13 +353,13 @@ RSpec.describe "gemcutter's dependency API" do end G - bundle :install, :artifice => "endpoint_extra" + bundle :install, artifice: "endpoint_extra" expect(out).to include("Fetching gem metadata from http://localgemserver.test/.") expect(out).to include("Fetching source index from http://localgemserver.test/extra") end - it "does not fetch every spec if the index of gems is large when doing back deps", :bundler => "< 3" do + it "does not fetch every spec when doing back deps", bundler: "< 3" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" @@ -369,9 +369,7 @@ RSpec.describe "gemcutter's dependency API" do FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] end - api_request_limit = low_api_request_limit_for(gem_repo2) - - install_gemfile <<-G, :artifice => "endpoint_extra_missing", :requires => [api_request_limit_hack_file], :env => { "BUNDLER_SPEC_API_REQUEST_LIMIT" => api_request_limit.to_s }.merge(env_for_missing_prerelease_default_gem_activation) + install_gemfile <<-G, artifice: "endpoint_extra_missing" source "#{source_uri}" source "#{source_uri}/extra" gem "back_deps" @@ -380,7 +378,7 @@ RSpec.describe "gemcutter's dependency API" do expect(the_bundle).to include_gems "back_deps 1.0" end - it "does not fetch every spec if the index of gems is large when doing back deps using blocks" do + it "does not fetch every spec when doing back deps using blocks" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" @@ -390,9 +388,7 @@ RSpec.describe "gemcutter's dependency API" do FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] end - api_request_limit = low_api_request_limit_for(gem_repo2) - - install_gemfile <<-G, :artifice => "endpoint_extra_missing", :requires => [api_request_limit_hack_file], :env => { "BUNDLER_SPEC_API_REQUEST_LIMIT" => api_request_limit.to_s }.merge(env_for_missing_prerelease_default_gem_activation) + install_gemfile <<-G, artifice: "endpoint_extra_missing" source "#{source_uri}" source "#{source_uri}/extra" do gem "back_deps" @@ -402,18 +398,7 @@ RSpec.describe "gemcutter's dependency API" do expect(the_bundle).to include_gems "back_deps 1.0" end - it "uses the endpoint if all sources support it" do - gemfile <<-G - source "#{source_uri}" - - gem 'foo' - G - - bundle :install, :artifice => "endpoint_api_missing" - expect(the_bundle).to include_gems "foo 1.0" - end - - it "fetches again when more dependencies are found in subsequent sources using deployment mode", :bundler => "< 3" do + it "fetches again when more dependencies are found in subsequent sources using deployment mode", bundler: "< 3" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" @@ -427,9 +412,9 @@ RSpec.describe "gemcutter's dependency API" do gem "back_deps" G - bundle :install, :artifice => "endpoint_extra" + bundle :install, artifice: "endpoint_extra" bundle "config set --local deployment true" - bundle :install, :artifice => "endpoint_extra" + bundle :install, artifice: "endpoint_extra" expect(the_bundle).to include_gems "back_deps 1.0" end @@ -448,12 +433,28 @@ RSpec.describe "gemcutter's dependency API" do end G - bundle :install, :artifice => "endpoint_extra" + bundle :install, artifice: "endpoint_extra" bundle "config set --local deployment true" - bundle "install", :artifice => "endpoint_extra" + bundle "install", artifice: "endpoint_extra" expect(the_bundle).to include_gems "back_deps 1.0" end + it "does not fetch all marshaled specs" do + build_repo2 do + build_gem "foo", "1.0" + build_gem "foo", "2.0" + end + + install_gemfile <<-G, artifice: "endpoint", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }, verbose: true + source "#{source_uri}" + + gem "foo" + G + + expect(out).to include("foo-2.0.gemspec.rz") + expect(out).not_to include("foo-1.0.gemspec.rz") + end + it "does not refetch if the only unmet dependency is bundler" do build_repo2 do build_gem "bundler_dep" do |s| @@ -467,52 +468,40 @@ RSpec.describe "gemcutter's dependency API" do gem "bundler_dep" G - bundle :install, :artifice => "endpoint", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + bundle :install, artifice: "endpoint", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } expect(out).to include("Fetching gem metadata from #{source_uri}") end - it "should install when EndpointSpecification has a bin dir owned by root", :sudo => true do - sudo "mkdir -p #{system_gem_path("bin")}" - sudo "chown -R root #{system_gem_path("bin")}" - - gemfile <<-G - source "#{source_uri}" - gem "rails" - G - bundle :install, :artifice => "endpoint" - expect(the_bundle).to include_gems "rails 2.3.2" - end - - it "installs the binstubs", :bundler => "< 3" do + it "installs the binstubs", bundler: "< 3" do gemfile <<-G source "#{source_uri}" gem "rack" G - bundle "install --binstubs", :artifice => "endpoint" + bundle "install --binstubs", artifice: "endpoint" gembin "rackup" expect(out).to eq("1.0.0") end - it "installs the bins when using --path and uses autoclean", :bundler => "< 3" do + it "installs the bins when using --path and uses autoclean", bundler: "< 3" do gemfile <<-G source "#{source_uri}" gem "rack" G - bundle "install --path vendor/bundle", :artifice => "endpoint" + bundle "install --path vendor/bundle", artifice: "endpoint" expect(vendored_gems("bin/rackup")).to exist end - it "installs the bins when using --path and uses bundle clean", :bundler => "< 3" do + it "installs the bins when using --path and uses bundle clean", bundler: "< 3" do gemfile <<-G source "#{source_uri}" gem "rack" G - bundle "install --path vendor/bundle --no-clean", :artifice => "endpoint" + bundle "install --path vendor/bundle --no-clean", artifice: "endpoint" expect(vendored_gems("bin/rackup")).to exist end @@ -523,7 +512,7 @@ RSpec.describe "gemcutter's dependency API" do gem 'rack-obama' G - bundle :install, :artifice => "endpoint" + bundle :install, artifice: "endpoint" expect(out).to include("Post-install message from rack:") end @@ -533,7 +522,7 @@ RSpec.describe "gemcutter's dependency API" do gem 'rack_middleware' G - bundle :install, :artifice => "endpoint" + bundle :install, artifice: "endpoint" expect(out).to include("Post-install message from rack:") expect(out).to include("Rack's post install message") end @@ -542,7 +531,7 @@ RSpec.describe "gemcutter's dependency API" do let(:user) { "user" } let(:password) { "pass" } let(:basic_auth_source_uri) do - uri = Bundler::URI.parse(source_uri) + uri = Gem::URI.parse(source_uri) uri.user = user uri.password = password @@ -555,7 +544,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle :install, :artifice => "endpoint_basic_authentication" + bundle :install, artifice: "endpoint_basic_authentication" expect(out).not_to include("#{user}:#{password}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -566,7 +555,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle :install, :verbose => true, :artifice => "endpoint_basic_authentication" + bundle :install, verbose: true, artifice: "endpoint_basic_authentication" expect(out).not_to include("#{user}:#{password}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -577,7 +566,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle :install, :artifice => "endpoint_marshal_fail_basic_authentication" + bundle :install, artifice: "endpoint_marshal_fail_basic_authentication" expect(out).not_to include("#{user}:#{password}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -588,18 +577,18 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle :install, :artifice => "endpoint_500", :raise_on_error => false + bundle :install, artifice: "endpoint_500", raise_on_error: false expect(out).not_to include("#{user}:#{password}") end - it "strips http basic auth creds when warning about ambiguous sources", :bundler => "< 3" do + it "strips http basic auth creds when warning about ambiguous sources", bundler: "< 3" do gemfile <<-G source "#{basic_auth_source_uri}" source "#{file_uri_for(gem_repo1)}" gem "rack" G - bundle :install, :artifice => "endpoint_basic_authentication" + bundle :install, artifice: "endpoint_basic_authentication" expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") expect(err).not_to include("#{user}:#{password}") expect(the_bundle).to include_gems "rack 1.0.0" @@ -611,7 +600,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle :install, :artifice => "endpoint_creds_diff_host" + bundle :install, artifice: "endpoint_creds_diff_host" expect(the_bundle).to include_gems "rack 1.0.0" end @@ -624,7 +613,7 @@ RSpec.describe "gemcutter's dependency API" do end it "reads authentication details from a valid ENV variable" do - bundle :install, :artifice => "endpoint_strict_basic_authentication", :env => { "BUNDLE_LOCAL___GEMSERVER__TEST" => "#{user}:#{password}" } + bundle :install, artifice: "endpoint_strict_basic_authentication", env: { "BUNDLE_LOCAL___GEMSERVER__TEST" => "#{user}:#{password}" } expect(out).to include("Fetching gem metadata from http://local-gemserver.test") expect(the_bundle).to include_gems "rack 1.0.0" @@ -642,7 +631,7 @@ RSpec.describe "gemcutter's dependency API" do it "reads authentication details by host name from bundle config" do bundle "config set #{source_hostname} #{user}:#{password}" - bundle :install, :artifice => "endpoint_strict_basic_authentication" + bundle :install, artifice: "endpoint_strict_basic_authentication" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" @@ -652,7 +641,7 @@ RSpec.describe "gemcutter's dependency API" do # The trailing slash is necessary here; Fetcher canonicalizes the URI. bundle "config set #{source_uri}/ #{user}:#{password}" - bundle :install, :artifice => "endpoint_strict_basic_authentication" + bundle :install, artifice: "endpoint_strict_basic_authentication" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" @@ -660,7 +649,7 @@ RSpec.describe "gemcutter's dependency API" do it "should use the API" do bundle "config set #{source_hostname} #{user}:#{password}" - bundle :install, :artifice => "endpoint_strict_basic_authentication" + bundle :install, artifice: "endpoint_strict_basic_authentication" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "rack 1.0.0" end @@ -673,19 +662,19 @@ RSpec.describe "gemcutter's dependency API" do bundle "config set #{source_hostname} otheruser:wrong" - bundle :install, :artifice => "endpoint_strict_basic_authentication" + bundle :install, artifice: "endpoint_strict_basic_authentication" expect(the_bundle).to include_gems "rack 1.0.0" end it "shows instructions if auth is not provided for the source" do - bundle :install, :artifice => "endpoint_strict_basic_authentication", :raise_on_error => false + bundle :install, artifice: "endpoint_strict_basic_authentication", raise_on_error: false expect(err).to include("bundle config set --global #{source_hostname} username:password") end it "fails if authentication has already been provided, but failed" do bundle "config set #{source_hostname} #{user}:wrong" - bundle :install, :artifice => "endpoint_strict_basic_authentication", :raise_on_error => false + bundle :install, artifice: "endpoint_strict_basic_authentication", raise_on_error: false expect(err).to include("Bad username or password") end end @@ -699,7 +688,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle :install, :artifice => "endpoint_basic_authentication" + bundle :install, artifice: "endpoint_basic_authentication" expect(the_bundle).to include_gems "rack 1.0.0" end end @@ -724,7 +713,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle :install, :env => { "RUBYOPT" => opt_add("-I#{bundled_app("broken_ssl")}", ENV["RUBYOPT"]) }, :raise_on_error => false + bundle :install, env: { "RUBYOPT" => opt_add("-I#{bundled_app("broken_ssl")}", ENV["RUBYOPT"]) }, raise_on_error: false expect(err).to include("OpenSSL") end end @@ -734,7 +723,7 @@ RSpec.describe "gemcutter's dependency API" do # Install a monkeypatch that reproduces the effects of openssl raising # a certificate validation error when RubyGems tries to connect. gemfile <<-G - class Net::HTTP + class Gem::Net::HTTP def start raise OpenSSL::SSL::SSLError, "certificate verify failed" end @@ -744,7 +733,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rack" G - bundle :install, :raise_on_error => false + bundle :install, raise_on_error: false expect(err).to match(/could not verify the SSL certificate/i) end end @@ -752,7 +741,7 @@ RSpec.describe "gemcutter's dependency API" do context ".gemrc with sources is present" do it "uses other sources declared in the Gemfile" do File.open(home(".gemrc"), "w") do |file| - file.puts({ :sources => ["https://rubygems.org"] }.to_yaml) + file.puts({ sources: ["https://rubygems.org"] }.to_yaml) end begin @@ -761,7 +750,7 @@ RSpec.describe "gemcutter's dependency API" do gem 'rack' G - bundle "install", :artifice => "endpoint_marshal_fail" + bundle "install", artifice: "endpoint_marshal_fail" ensure home(".gemrc").rmtree end diff --git a/spec/bundler/install/gems/flex_spec.rb b/spec/bundler/install/gems/flex_spec.rb index 326ec51214..8ef3984975 100644 --- a/spec/bundler/install/gems/flex_spec.rb +++ b/spec/bundler/install/gems/flex_spec.rb @@ -156,7 +156,7 @@ RSpec.describe "bundle flex_install" do end end - describe "when Gemfile conflicts with lockfile" do + describe "when running bundle install and Gemfile conflicts with lockfile" do before(:each) do build_repo2 install_gemfile <<-G @@ -183,34 +183,70 @@ RSpec.describe "bundle flex_install" do end it "does not install gems whose dependencies are not met" do - bundle :install, :raise_on_error => false - ruby <<-RUBY, :raise_on_error => false + bundle :install, raise_on_error: false + ruby <<-RUBY, raise_on_error: false require 'bundler/setup' RUBY expect(err).to match(/could not find gem 'rack-obama/i) end - it "suggests bundle update when the Gemfile requires different versions than the lock" do + it "discards the locked gems when the Gemfile requires different versions than the lock" do bundle "config set force_ruby_platform true" - nice_error = <<-E.strip.gsub(/^ {8}/, "") - Bundler could not find compatible versions for gem "rack": - In snapshot (Gemfile.lock): - rack (= 0.9.1) + nice_error = <<~E.strip + Could not find compatible versions - In Gemfile: - rack-obama (= 2.0) was resolved to 2.0, which depends on - rack (= 1.2) + Because rack-obama >= 2.0 depends on rack = 1.2 + and rack = 1.2 could not be found in rubygems repository #{file_uri_for(gem_repo2)}/, cached gems or installed locally, + rack-obama >= 2.0 cannot be used. + So, because Gemfile depends on rack-obama = 2.0, + version solving has failed. + E + + bundle :install, retry: 0, raise_on_error: false + expect(err).to end_with(nice_error) + end - rack_middleware was resolved to 1.0, which depends on - rack (= 0.9.1) + it "does not include conflicts with a single requirement tree, because that can't possibly be a conflict" do + bundle "config set force_ruby_platform true" - Running `bundle update` will rebuild your snapshot from scratch, using only - the gems in your Gemfile, which may resolve the conflict. + bad_error = <<~E.strip + Bundler could not find compatible versions for gem "rack-obama": + In Gemfile: + rack-obama (= 2.0) E - bundle :install, :retry => 0, :raise_on_error => false - expect(err).to end_with(nice_error) + bundle "update rack_middleware", retry: 0, raise_on_error: false + expect(err).not_to end_with(bad_error) + end + end + + describe "when running bundle update and Gemfile conflicts with lockfile" do + before(:each) do + build_repo4 do + build_gem "jekyll-feed", "0.16.0" + build_gem "jekyll-feed", "0.15.1" + + build_gem "github-pages", "226" do |s| + s.add_dependency "jekyll-feed", "0.15.1" + end + end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "jekyll-feed", "~> 0.12" + G + + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "github-pages", "~> 226" + gem "jekyll-feed", "~> 0.12" + G + end + + it "discards the conflicting lockfile information and resolves properly" do + bundle :update, raise_on_error: false, all: true + expect(err).to be_empty end end @@ -229,14 +265,32 @@ RSpec.describe "bundle flex_install" do G end - it "does something" do - expect do - bundle "install", :raise_on_error => false - end.not_to change { File.read(bundled_app_lock) } + it "should work when you install" do + bundle "install" + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo1, "rack", "0.9.1" + c.checksum gem_repo1, "rack-obama", "1.0" + end - expect(err).to include("rack = 0.9.1") - expect(err).to include("locked at 1.0.0") - expect(err).to include("bundle update rack") + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: + rack (0.9.1) + rack-obama (1.0) + rack + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rack (= 0.9.1) + rack-obama + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L end it "should work when you update" do @@ -259,24 +313,28 @@ RSpec.describe "bundle flex_install" do gem "rack" G - lockfile_should_be <<-L - GEM - remote: #{file_uri_for(gem_repo1)}/ - specs: - rack (1.0.0) + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo1, "rack", "1.0.0" + end - GEM - remote: #{file_uri_for(gem_repo2)}/ - specs: + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: + rack (1.0.0) - PLATFORMS - #{lockfile_platforms} + GEM + remote: #{file_uri_for(gem_repo2)}/ + specs: - DEPENDENCIES - rack + PLATFORMS + #{lockfile_platforms} - BUNDLED WITH - #{Bundler::VERSION} + DEPENDENCIES + rack + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} L end end @@ -301,7 +359,7 @@ RSpec.describe "bundle flex_install" do end end - it "prints the correct error message" do + it "resolves them" do # install Rails 3.0.0.rc install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" @@ -310,13 +368,12 @@ RSpec.describe "bundle flex_install" do G # upgrade Rails to 3.0.0 and then install again - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "rails", "3.0.0" gem "capybara", "0.3.9" G - - expect(err).to include("Gemfile.lock") + expect(err).to be_empty end end end diff --git a/spec/bundler/install/gems/fund_spec.rb b/spec/bundler/install/gems/fund_spec.rb index f521b0296f..9aadc9ed25 100644 --- a/spec/bundler/install/gems/fund_spec.rb +++ b/spec/bundler/install/gems/fund_spec.rb @@ -6,20 +6,20 @@ RSpec.describe "bundle install" do build_repo2 do build_gem "has_funding_and_other_metadata" do |s| s.metadata = { - "bug_tracker_uri" => "https://example.com/user/bestgemever/issues", - "changelog_uri" => "https://example.com/user/bestgemever/CHANGELOG.md", + "bug_tracker_uri" => "https://example.com/user/bestgemever/issues", + "changelog_uri" => "https://example.com/user/bestgemever/CHANGELOG.md", "documentation_uri" => "https://www.example.info/gems/bestgemever/0.0.1", - "homepage_uri" => "https://bestgemever.example.io", - "mailing_list_uri" => "https://groups.example.com/bestgemever", - "funding_uri" => "https://example.com/has_funding_and_other_metadata/funding", - "source_code_uri" => "https://example.com/user/bestgemever", - "wiki_uri" => "https://example.com/user/bestgemever/wiki", + "homepage_uri" => "https://bestgemever.example.io", + "mailing_list_uri" => "https://groups.example.com/bestgemever", + "funding_uri" => "https://example.com/has_funding_and_other_metadata/funding", + "source_code_uri" => "https://example.com/user/bestgemever", + "wiki_uri" => "https://example.com/user/bestgemever/wiki", } end build_gem "has_funding", "1.2.3" do |s| s.metadata = { - "funding_uri" => "https://example.com/has_funding/funding", + "funding_uri" => "https://example.com/has_funding/funding", } end @@ -52,6 +52,33 @@ RSpec.describe "bundle install" do end end + context "when gems include a fund URI but `ignore_funding_requests` is configured" do + before do + bundle "config set ignore_funding_requests true" + end + + it "does not display the plural fund message after installing" do + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem 'has_funding_and_other_metadata' + gem 'has_funding' + gem 'rack-obama' + G + + expect(out).not_to include("2 installed gems you directly depend on are looking for funding.") + end + + it "does not display the singular fund message after installing" do + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem 'has_funding' + gem 'rack-obama' + G + + expect(out).not_to include("1 installed gem you directly depend on is looking for funding.") + end + end + context "when gems do not include fund messages" do it "does not display any fund messages" do install_gemfile <<-G diff --git a/spec/bundler/install/gems/native_extensions_spec.rb b/spec/bundler/install/gems/native_extensions_spec.rb index d5cafcfc2c..907778a384 100644 --- a/spec/bundler/install/gems/native_extensions_spec.rb +++ b/spec/bundler/install/gems/native_extensions_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe "installing a gem with native extensions", :ruby_repo do +RSpec.describe "installing a gem with native extensions" do it "installs" do build_repo2 do build_gem "c_extension" do |s| @@ -91,7 +91,7 @@ RSpec.describe "installing a gem with native extensions", :ruby_repo do it "installs correctly from git when multiple gems with extensions share one repository" do build_repo2 do ["one", "two"].each do |n| - build_lib "c_extension_#{n}", "1.0", :path => lib_path("gems/c_extension_#{n}") do |s| + build_lib "c_extension_#{n}", "1.0", path: lib_path("gems/c_extension_#{n}") do |s| s.extensions = ["ext/extconf.rb"] s.write "ext/extconf.rb", <<-E require "mkmf" @@ -119,7 +119,7 @@ RSpec.describe "installing a gem with native extensions", :ruby_repo do C end end - build_git "gems", :path => lib_path("gems"), :gemspec => false + build_git "gems", path: lib_path("gems"), gemspec: false end bundle "config set build.c_extension_one --with-c_extension_one=one" diff --git a/spec/bundler/install/gems/resolving_spec.rb b/spec/bundler/install/gems/resolving_spec.rb index 94fac0052c..c5f9c4a3d3 100644 --- a/spec/bundler/install/gems/resolving_spec.rb +++ b/spec/bundler/install/gems/resolving_spec.rb @@ -7,7 +7,7 @@ RSpec.describe "bundle install with install-time dependencies" do s.extensions << "Rakefile" s.write "Rakefile", <<-RUBY task :default do - path = File.expand_path("../lib", __FILE__) + path = File.expand_path("lib", __dir__) FileUtils.mkdir_p(path) File.open("\#{path}/implicit_rake_dep.rb", "w") do |f| f.puts "IMPLICIT_RAKE_DEP = 'YES'" @@ -20,7 +20,7 @@ RSpec.describe "bundle install with install-time dependencies" do s.extensions << "Rakefile" s.write "Rakefile", <<-RUBY task :default do - path = File.expand_path("../lib", __FILE__) + path = File.expand_path("lib", __dir__) FileUtils.mkdir_p(path) File.open("\#{path}/another_implicit_rake_dep.rb", "w") do |f| f.puts "ANOTHER_IMPLICIT_RAKE_DEP = 'YES'" @@ -42,7 +42,7 @@ RSpec.describe "bundle install with install-time dependencies" do s.extensions << "Rakefile" s.write "Rakefile", <<-RUBY task :default do - path = File.expand_path("../lib", __FILE__) + path = File.expand_path("lib", __dir__) FileUtils.mkdir_p(path) File.open("\#{path}/net_build_extensions.rb", "w") do |f| f.puts "NET_BUILD_EXTENSIONS = 'YES'" @@ -101,16 +101,14 @@ RSpec.describe "bundle install with install-time dependencies" do end it "installs gems with a dependency with no type" do - skip "incorrect data check error" if Gem.win_platform? - build_repo2 path = "#{gem_repo2}/#{Gem::MARSHAL_SPEC_DIR}/actionpack-2.3.2.gemspec.rz" spec = Marshal.load(Bundler.rubygems.inflate(File.binread(path))) spec.dependencies.each do |d| - d.instance_variable_set(:@type, :fail) + d.instance_variable_set(:@type, "fail") end - File.open(path, "w") do |f| + File.open(path, "wb") do |f| f.write Gem.deflate(Marshal.dump(spec)) end @@ -133,7 +131,7 @@ RSpec.describe "bundle install with install-time dependencies" do end it "installs plugins depended on by other plugins" do - install_gemfile <<-G, :env => { "DEBUG" => "1" } + install_gemfile <<-G, env: { "DEBUG" => "1" } source "#{file_uri_for(gem_repo2)}" gem "net_a" G @@ -142,7 +140,7 @@ RSpec.describe "bundle install with install-time dependencies" do end it "installs multiple levels of dependencies" do - install_gemfile <<-G, :env => { "DEBUG" => "1" } + install_gemfile <<-G, env: { "DEBUG" => "1" } source "#{file_uri_for(gem_repo2)}" gem "net_c" gem "net_e" @@ -159,9 +157,9 @@ RSpec.describe "bundle install with install-time dependencies" do gem "net_e" G - bundle :install, :env => { "BUNDLER_DEBUG_RESOLVER" => "1", "DEBUG" => "1" } + bundle :install, env: { "BUNDLER_DEBUG_RESOLVER" => "1", "DEBUG" => "1" } - expect(out).to include("BUNDLER: Starting resolution") + expect(out).to include("Resolving dependencies...") end end @@ -173,9 +171,9 @@ RSpec.describe "bundle install with install-time dependencies" do gem "net_e" G - bundle :install, :env => { "DEBUG_RESOLVER" => "1", "DEBUG" => "1" } + bundle :install, env: { "DEBUG_RESOLVER" => "1", "DEBUG" => "1" } - expect(out).to include("BUNDLER: Starting resolution") + expect(out).to include("Resolving dependencies...") end end @@ -187,18 +185,12 @@ RSpec.describe "bundle install with install-time dependencies" do gem "net_e" G - bundle :install, :env => { "DEBUG_RESOLVER_TREE" => "1", "DEBUG" => "1" } - - activated_groups = if local_platforms.any? - "net_b (1.0) (ruby), net_b (1.0) (#{local_platforms.join(", ")})" - else - "net_b (1.0) (ruby)" - end + bundle :install, env: { "DEBUG_RESOLVER_TREE" => "1", "DEBUG" => "1" } expect(out).to include(" net_b"). - and include("BUNDLER: Starting resolution"). - and include("BUNDLER: Finished resolution"). - and include("Attempting to activate [#{activated_groups}]") + and include("Resolving dependencies..."). + and include("Solution found after 1 attempts:"). + and include("selected net_b 1.0") end end end @@ -216,16 +208,327 @@ RSpec.describe "bundle install with install-time dependencies" do end end - install_gemfile <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } - ruby "#{RUBY_VERSION}" + install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + ruby "#{Gem.ruby_version}" source "http://localgemserver.test/" gem 'rack' G - expect(out).to_not include("rack-9001.0.0 requires ruby version > 9000") + expect(err).to_not include("rack-9001.0.0 requires ruby version > 9000") expect(the_bundle).to include_gems("rack 1.2") end + it "installs the older version when using servers not implementing the compact index API" do + build_repo2 do + build_gem "rack", "1.2" do |s| + s.executables = "rackup" + end + + build_gem "rack", "9001.0.0" do |s| + s.required_ruby_version = "> 9000" + end + end + + install_gemfile <<-G, artifice: "endpoint", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + ruby "#{Gem.ruby_version}" + source "http://localgemserver.test/" + gem 'rack' + G + + expect(err).to_not include("rack-9001.0.0 requires ruby version > 9000") + expect(the_bundle).to include_gems("rack 1.2") + end + + context "when there is a lockfile using the newer incompatible version" do + before do + build_repo2 do + build_gem "parallel_tests", "3.7.0" do |s| + s.required_ruby_version = ">= #{current_ruby_minor}" + end + + build_gem "parallel_tests", "3.8.0" do |s| + s.required_ruby_version = ">= #{next_ruby_minor}" + end + end + + gemfile <<-G + source "http://localgemserver.test/" + gem 'parallel_tests' + G + + checksums = checksums_section do |c| + c.checksum gem_repo2, "parallel_tests", "3.8.0" + end + + lockfile <<~L + GEM + remote: http://localgemserver.test/ + specs: + parallel_tests (3.8.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + parallel_tests + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "automatically updates lockfile to use the older version" do + bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo2, "parallel_tests", "3.7.0" + end + + expect(lockfile).to eq <<~L + GEM + remote: http://localgemserver.test/ + specs: + parallel_tests (3.7.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + parallel_tests + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "gives a meaningful error if we're in frozen mode" do + expect do + bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s, "BUNDLE_FROZEN" => "true" }, raise_on_error: false + end.not_to change { lockfile } + + expect(err).to include("parallel_tests-3.8.0 requires ruby version >= #{next_ruby_minor}") + expect(err).not_to include("That means the author of parallel_tests (3.8.0) has removed it.") + end + end + + context "with transitive dependencies in a lockfile" do + before do + build_repo2 do + build_gem "rubocop", "1.28.2" do |s| + s.required_ruby_version = ">= #{current_ruby_minor}" + + s.add_dependency "rubocop-ast", ">= 1.17.0", "< 2.0" + end + + build_gem "rubocop", "1.35.0" do |s| + s.required_ruby_version = ">= #{next_ruby_minor}" + + s.add_dependency "rubocop-ast", ">= 1.20.1", "< 2.0" + end + + build_gem "rubocop-ast", "1.17.0" do |s| + s.required_ruby_version = ">= #{current_ruby_minor}" + end + + build_gem "rubocop-ast", "1.21.0" do |s| + s.required_ruby_version = ">= #{next_ruby_minor}" + end + end + + gemfile <<-G + source "http://localgemserver.test/" + gem 'rubocop' + G + + checksums = checksums_section do |c| + c.checksum gem_repo2, "rubocop", "1.35.0" + c.checksum gem_repo2, "rubocop-ast", "1.21.0" + end + + lockfile <<~L + GEM + remote: http://localgemserver.test/ + specs: + rubocop (1.35.0) + rubocop-ast (>= 1.20.1, < 2.0) + rubocop-ast (1.21.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + parallel_tests + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "automatically updates lockfile to use the older compatible versions" do + bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo2, "rubocop", "1.28.2" + c.checksum gem_repo2, "rubocop-ast", "1.17.0" + end + + expect(lockfile).to eq <<~L + GEM + remote: http://localgemserver.test/ + specs: + rubocop (1.28.2) + rubocop-ast (>= 1.17.0, < 2.0) + rubocop-ast (1.17.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rubocop + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "with a Gemfile and lock file that don't resolve under the current platform" do + before do + build_repo4 do + build_gem "sorbet", "0.5.10554" do |s| + s.add_dependency "sorbet-static", "0.5.10554" + end + + build_gem "sorbet-static", "0.5.10554" do |s| + s.platform = "universal-darwin-21" + end + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + gem 'sorbet', '= 0.5.10554' + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + sorbet (0.5.10554) + sorbet-static (= 0.5.10554) + sorbet-static (0.5.10554-universal-darwin-21) + + PLATFORMS + arm64-darwin-21 + + DEPENDENCIES + sorbet (= 0.5.10554) + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "raises a proper error" do + simulate_platform "aarch64-linux" do + bundle "install", raise_on_error: false + end + + nice_error = <<~E.strip + Could not find gems matching 'sorbet-static (= 0.5.10554)' valid for all resolution platforms (arm64-darwin-21, aarch64-linux) in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally. + + The source contains the following gems matching 'sorbet-static (= 0.5.10554)': + * sorbet-static-0.5.10554-universal-darwin-21 + E + expect(err).to end_with(nice_error) + end + end + + context "when adding a new gem that does not resolve under all locked platforms" do + before do + simulate_platform "x86_64-linux" do + build_repo4 do + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "x86_64-linux" + end + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "arm-linux" + end + + build_gem "sorbet-static", "0.5.10696" do |s| + s.platform = "x86_64-linux" + end + end + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + nokogiri (1.14.0-arm-linux) + nokogiri (1.14.0-x86_64-linux) + + PLATFORMS + arm-linux + x86_64-linux + + DEPENDENCIES + nokogiri + + BUNDLED WITH + #{Bundler::VERSION} + L + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + + gem "nokogiri" + gem "sorbet-static" + G + + bundle "lock", raise_on_error: false + end + end + + it "raises a proper error" do + nice_error = <<~E.strip + Could not find gems matching 'sorbet-static' valid for all resolution platforms (arm-linux, x86_64-linux) in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally. + + The source contains the following gems matching 'sorbet-static': + * sorbet-static-0.5.10696-x86_64-linux + E + expect(err).to end_with(nice_error) + end + end + + it "gives a meaningful error on ruby version mismatches between dependencies" do + build_repo4 do + build_gem "requires-old-ruby" do |s| + s.required_ruby_version = "< #{Gem.ruby_version}" + end + end + + build_lib("foo", path: bundled_app) do |s| + s.required_ruby_version = ">= #{Gem.ruby_version}" + + s.add_dependency "requires-old-ruby" + end + + install_gemfile <<-G, raise_on_error: false + source "#{file_uri_for(gem_repo4)}" + gemspec + G + + expect(err).to end_with <<~E.strip + Could not find compatible versions + + Because every version of foo depends on requires-old-ruby >= 0 + and every version of requires-old-ruby depends on Ruby < #{Gem.ruby_version}, + every version of foo requires Ruby < #{Gem.ruby_version}. + So, because Gemfile depends on foo >= 0 + and current Ruby version is = #{Gem.ruby_version}, + version solving has failed. + E + end + it "installs the older version under rate limiting conditions" do build_repo4 do build_gem "rack", "9001.0.0" do |s| @@ -235,14 +538,14 @@ RSpec.describe "bundle install with install-time dependencies" do build_gem "foo1", "1.0" end - install_gemfile <<-G, :artifice => "compact_index_rate_limited", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } - ruby "#{RUBY_VERSION}" + install_gemfile <<-G, artifice: "compact_index_rate_limited", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + ruby "#{Gem.ruby_version}" source "http://localgemserver.test/" gem 'rack' gem 'foo1' G - expect(out).to_not include("rack-9001.0.0 requires ruby version > 9000") + expect(err).to_not include("rack-9001.0.0 requires ruby version > 9000") expect(the_bundle).to include_gems("rack 1.2") end @@ -252,22 +555,22 @@ RSpec.describe "bundle install with install-time dependencies" do s.required_ruby_version = "> 9000" end build_gem "rack", "1.2" do |s| - s.platform = mingw + s.platform = x86_mingw32 s.required_ruby_version = "> 9000" end build_gem "rack", "1.2" end - simulate_platform mingw do - install_gemfile <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } - ruby "#{RUBY_VERSION}" + simulate_platform x86_mingw32 do + install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + ruby "#{Gem.ruby_version}" source "http://localgemserver.test/" gem 'rack' G end - expect(out).to_not include("rack-9001.0.0 requires ruby version > 9000") - expect(out).to_not include("rack-1.2-#{Bundler.local_platform} requires ruby version > 9000") + expect(err).to_not include("rack-9001.0.0 requires ruby version > 9000") + expect(err).to_not include("rack-1.2-#{Bundler.local_platform} requires ruby version > 9000") expect(the_bundle).to include_gems("rack 1.2") end end @@ -281,14 +584,32 @@ RSpec.describe "bundle install with install-time dependencies" do end end - let(:ruby_requirement) { %("#{RUBY_VERSION}") } - let(:error_message_requirement) { "~> #{RUBY_VERSION}.0" } + let(:ruby_requirement) { %("#{Gem.ruby_version}") } + let(:error_message_requirement) { "= #{Gem.ruby_version}" } + + it "raises a proper error that mentions the current Ruby version during resolution" do + install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }, raise_on_error: false + source "http://localgemserver.test/" + gem 'require_ruby' + G + + expect(out).to_not include("Gem::InstallError: require_ruby requires Ruby version > 9000") + + nice_error = <<~E.strip + Could not find compatible versions + + Because every version of require_ruby depends on Ruby > 9000 + and Gemfile depends on require_ruby >= 0, + Ruby > 9000 is required. + So, because current Ruby version is #{error_message_requirement}, + version solving has failed. + E + expect(err).to end_with(nice_error) + end shared_examples_for "ruby version conflicts" do it "raises an error during resolution" do - skip "ruby requirement includes platform and it shouldn't" if Gem.win_platform? - - install_gemfile <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }, :raise_on_error => false + install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }, raise_on_error: false source "http://localgemserver.test/" ruby #{ruby_requirement} gem 'require_ruby' @@ -296,15 +617,14 @@ RSpec.describe "bundle install with install-time dependencies" do expect(out).to_not include("Gem::InstallError: require_ruby requires Ruby version > 9000") - nice_error = strip_whitespace(<<-E).strip - Bundler found conflicting requirements for the Ruby\0 version: - In Gemfile: - Ruby\0 (#{error_message_requirement}) - - require_ruby was resolved to 1.0, which depends on - Ruby\0 (> 9000) + nice_error = <<~E.strip + Could not find compatible versions - Ruby\0 (> 9000), which is required by gem 'require_ruby', is not available in the local ruby installation + Because every version of require_ruby depends on Ruby > 9000 + and Gemfile depends on require_ruby >= 0, + Ruby > 9000 is required. + So, because current Ruby version is #{error_message_requirement}, + version solving has failed. E expect(err).to end_with(nice_error) end @@ -314,7 +634,6 @@ RSpec.describe "bundle install with install-time dependencies" do describe "with a < requirement" do let(:ruby_requirement) { %("< 5000") } - let(:error_message_requirement) { "< 5000" } it_behaves_like "ruby version conflicts" end @@ -322,7 +641,6 @@ RSpec.describe "bundle install with install-time dependencies" do describe "with a compound requirement" do let(:reqs) { ["> 0.1", "< 5000"] } let(:ruby_requirement) { reqs.map(&:dump).join(", ") } - let(:error_message_requirement) { Gem::Requirement.new(reqs).to_s } it_behaves_like "ruby version conflicts" end @@ -337,13 +655,20 @@ RSpec.describe "bundle install with install-time dependencies" do end end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}" gem 'require_rubygems' G expect(err).to_not include("Gem::InstallError: require_rubygems requires RubyGems version > 9000") - expect(err).to include("require_rubygems-1.0 requires rubygems version > 9000, which is incompatible with the current version, #{Gem::VERSION}") + nice_error = <<~E.strip + Because every version of require_rubygems depends on RubyGems > 9000 + and Gemfile depends on require_rubygems >= 0, + RubyGems > 9000 is required. + So, because current RubyGems version is = #{Gem::VERSION}, + version solving has failed. + E + expect(err).to end_with(nice_error) end end end diff --git a/spec/bundler/install/gems/standalone_spec.rb b/spec/bundler/install/gems/standalone_spec.rb index b3b9d4d46b..46cab2dfeb 100644 --- a/spec/bundler/install/gems/standalone_spec.rb +++ b/spec/bundler/install/gems/standalone_spec.rb @@ -32,6 +32,21 @@ RSpec.shared_examples "bundle install --standalone" do expect(out).to eq(expected_gems.values.join("\n")) end + it "makes the gems available without bundler nor rubygems" do + testrb = String.new <<-RUBY + $:.unshift File.expand_path("bundle") + require "bundler/setup" + + RUBY + expected_gems.each do |k, _| + testrb << "\nrequire \"#{k}\"" + testrb << "\nputs #{k.upcase}" + end + sys_exec %(#{Gem.ruby} --disable-gems -w -e #{testrb.shellescape}) + + expect(out).to eq(expected_gems.values.join("\n")) + end + it "makes the gems available without bundler via Kernel.require" do testrb = String.new <<-RUBY $:.unshift File.expand_path("bundle") @@ -82,7 +97,23 @@ RSpec.shared_examples "bundle install --standalone" do testrb << "\nrequire \"#{k}\"" testrb << "\nputs #{k.upcase}" end - ruby testrb, :dir => "#{bundled_app}2" + ruby testrb, dir: "#{bundled_app}2" + + expect(out).to eq(expected_gems.values.join("\n")) + end + + it "skips activating gems" do + testrb = String.new <<-RUBY + $:.unshift File.expand_path("bundle") + require "bundler/setup" + + gem "do_not_activate_me" + RUBY + expected_gems.each do |k, _| + testrb << "\nrequire \"#{k}\"" + testrb << "\nputs #{k.upcase}" + end + sys_exec %(#{Gem.ruby} -w -e #{testrb.shellescape}) expect(out).to eq(expected_gems.values.join("\n")) end @@ -95,7 +126,7 @@ RSpec.shared_examples "bundle install --standalone" do gem "rails" G bundle "config set --local path #{bundled_app("bundle")}" - bundle :install, :standalone => true, :dir => cwd + bundle :install, standalone: true, dir: cwd end let(:expected_gems) do @@ -108,10 +139,113 @@ RSpec.shared_examples "bundle install --standalone" do include_examples "common functionality" end - describe "with gems with native extension", :ruby_repo do + describe "with default gems and a lockfile", :ruby_repo do before do + realworld_system_gems "tsort --version 0.1.0" + + necessary_system_gems = ["optparse --version 0.1.1", "psych --version 3.3.2", "logger --version 1.4.3", "etc --version 1.2.0", "stringio --version 3.1.0"] + necessary_system_gems += ["shellwords --version 0.1.0", "base64 --version 0.1.0", "resolv --version 0.2.1"] if Gem.rubygems_version < Gem::Version.new("3.3.a") + necessary_system_gems += ["yaml --version 0.1.1"] if Gem.rubygems_version < Gem::Version.new("3.4.a") + realworld_system_gems(*necessary_system_gems, path: scoped_gem_path(bundled_app("bundle"))) + + build_gem "foo", "1.0.0", to_system: true, default: true do |s| + s.add_dependency "bar" + end + + build_gem "bar", "1.0.0", to_system: true, default: true + + build_repo4 do + build_gem "foo", "1.0.0" do |s| + s.add_dependency "bar" + end + + build_gem "bar", "1.0.0" + end + + gemfile <<-G + source "https://gem.repo4" + gem "foo" + G + + bundle "lock", dir: cwd, artifice: "compact_index" + end + + it "works and points to the vendored copies, not to the default copies", :realworld do bundle "config set --local path #{bundled_app("bundle")}" - install_gemfile <<-G, :standalone => true, :dir => cwd + bundle :install, standalone: true, dir: cwd, artifice: "compact_index", env: { "BUNDLER_GEM_DEFAULT_DIR" => system_gem_path.to_s } + + load_path_lines = bundled_app("bundle/bundler/setup.rb").read.split("\n").select {|line| line.start_with?("$:.unshift") } + + expect(load_path_lines).to eq [ + '$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/bar-1.0.0/lib")', + '$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/foo-1.0.0/lib")', + ] + end + end + + describe "with Gemfiles using absolute path sources and resulting bundle moved to a folder hierarchy with different nesting" do + before do + build_lib "minitest", "1.0.0", path: lib_path("minitest") + + Dir.mkdir bundled_app("app") + + gemfile bundled_app("app/Gemfile"), <<-G + source "#{file_uri_for(gem_repo1)}" + gem "minitest", :path => "#{lib_path("minitest")}" + G + + bundle "install", standalone: true, dir: bundled_app("app") + + Dir.mkdir tmp("one_more_level") + FileUtils.mv bundled_app, tmp("one_more_level") + end + + it "also works" do + ruby <<-RUBY, dir: tmp("one_more_level/bundled_app/app") + require "./bundle/bundler/setup" + + require "minitest" + puts MINITEST + RUBY + + expect(out).to eq("1.0.0") + expect(err).to be_empty + end + end + + describe "with Gemfiles using relative path sources and app moved to a different root" do + before do + FileUtils.mkdir_p bundled_app("app/vendor") + + build_lib "minitest", "1.0.0", path: bundled_app("app/vendor/minitest") + + gemfile bundled_app("app/Gemfile"), <<-G + source "#{file_uri_for(gem_repo1)}" + gem "minitest", :path => "vendor/minitest" + G + + bundle "install", standalone: true, dir: bundled_app("app") + + FileUtils.mv(bundled_app("app"), bundled_app2("app")) + end + + it "also works" do + ruby <<-RUBY, dir: bundled_app2("app") + require "./bundle/bundler/setup" + + require "minitest" + puts MINITEST + RUBY + + expect(out).to eq("1.0.0") + expect(err).to be_empty + end + end + + describe "with gems with native extension" do + before do + bundle "config set --local path #{bundled_app("bundle")}" + install_gemfile <<-G, standalone: true, dir: cwd source "#{file_uri_for(gem_repo1)}" gem "very_simple_binary" G @@ -119,18 +253,22 @@ RSpec.shared_examples "bundle install --standalone" do it "generates a bundle/bundler/setup.rb with the proper paths" do expected_path = bundled_app("bundle/bundler/setup.rb") - extension_line = File.read(expected_path).each_line.find {|line| line.include? "/extensions/" }.strip - expect(extension_line).to start_with '$:.unshift File.expand_path("#{path}/../#{ruby_engine}/#{ruby_version}/extensions/' - expect(extension_line).to end_with '/very_simple_binary-1.0")' + script_content = File.read(expected_path) + expect(script_content).to include("def self.ruby_api_version") + expect(script_content).to include("def self.extension_api_version") + extension_line = script_content.each_line.find {|line| line.include? "/extensions/" }.strip + platform = Gem::Platform.local + expect(extension_line).to start_with '$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/extensions/' + expect(extension_line).to end_with platform.to_s + '/#{Gem.extension_api_version}/very_simple_binary-1.0")' end end describe "with gem that has an invalid gemspec" do before do - build_git "bar", :gemspec => false do |s| + build_git "bar", gemspec: false do |s| s.write "lib/bar/version.rb", %(BAR_VERSION = '1.0') s.write "bar.gemspec", <<-G - lib = File.expand_path('../lib/', __FILE__) + lib = File.expand_path('lib/', __dir__) $:.unshift lib unless $:.include?(lib) require 'bar/version' @@ -145,7 +283,7 @@ RSpec.shared_examples "bundle install --standalone" do G end bundle "config set --local path #{bundled_app("bundle")}" - install_gemfile <<-G, :standalone => true, :dir => cwd, :raise_on_error => false + install_gemfile <<-G, standalone: true, dir: cwd, raise_on_error: false source "#{file_uri_for(gem_repo1)}" gem "bar", :git => "#{lib_path("bar-1.0")}" G @@ -167,7 +305,7 @@ RSpec.shared_examples "bundle install --standalone" do gem "devise", :git => "#{lib_path("devise-1.0")}" G bundle "config set --local path #{bundled_app("bundle")}" - bundle :install, :standalone => true, :dir => cwd + bundle :install, standalone: true, dir: cwd end let(:expected_gems) do @@ -195,7 +333,7 @@ RSpec.shared_examples "bundle install --standalone" do end G bundle "config set --local path #{bundled_app("bundle")}" - bundle :install, :standalone => true, :dir => cwd + bundle :install, standalone: true, dir: cwd end let(:expected_gems) do @@ -209,7 +347,7 @@ RSpec.shared_examples "bundle install --standalone" do it "allows creating a standalone file with limited groups" do bundle "config set --local path #{bundled_app("bundle")}" - bundle :install, :standalone => "default", :dir => cwd + bundle :install, standalone: "default", dir: cwd load_error_ruby <<-RUBY, "spec" $:.unshift File.expand_path("bundle") @@ -227,7 +365,7 @@ RSpec.shared_examples "bundle install --standalone" do it "allows `without` configuration to limit the groups used in a standalone" do bundle "config set --local path #{bundled_app("bundle")}" bundle "config set --local without test" - bundle :install, :standalone => true, :dir => cwd + bundle :install, standalone: true, dir: cwd load_error_ruby <<-RUBY, "spec" $:.unshift File.expand_path("bundle") @@ -244,7 +382,7 @@ RSpec.shared_examples "bundle install --standalone" do it "allows `path` configuration to change the location of the standalone bundle" do bundle "config set --local path path/to/bundle" - bundle "install", :standalone => true, :dir => cwd + bundle "install", standalone: true, dir: cwd ruby <<-RUBY $:.unshift File.expand_path("path/to/bundle") @@ -259,9 +397,9 @@ RSpec.shared_examples "bundle install --standalone" do it "allows `without` to limit the groups used in a standalone" do bundle "config set --local without test" - bundle :install, :dir => cwd + bundle :install, dir: cwd bundle "config set --local path #{bundled_app("bundle")}" - bundle :install, :standalone => true, :dir => cwd + bundle :install, standalone: true, dir: cwd load_error_ruby <<-RUBY, "spec" $:.unshift File.expand_path("bundle") @@ -287,7 +425,7 @@ RSpec.shared_examples "bundle install --standalone" do gem "rails" G bundle "config set --local path #{bundled_app("bundle")}" - bundle :install, :standalone => true, :artifice => "endpoint", :dir => cwd + bundle :install, standalone: true, artifice: "endpoint", dir: cwd end let(:expected_gems) do @@ -301,14 +439,14 @@ RSpec.shared_examples "bundle install --standalone" do end end - describe "with --binstubs", :bundler => "< 3" do + describe "with --binstubs", bundler: "< 3" do before do gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rails" G bundle "config set --local path #{bundled_app("bundle")}" - bundle :install, :standalone => true, :binstubs => true, :dir => cwd + bundle :install, standalone: true, binstubs: true, dir: cwd end let(:expected_gems) do @@ -326,7 +464,7 @@ RSpec.shared_examples "bundle install --standalone" do it "creates stubs that can be executed from anywhere" do require "tmpdir" - sys_exec(%(#{bundled_app("bin/rails")} -v), :dir => Dir.tmpdir) + sys_exec(%(#{bundled_app("bin/rails")} -v), dir: Dir.tmpdir) expect(out).to eq("2.3.2") end @@ -344,7 +482,7 @@ RSpec.shared_examples "bundle install --standalone" do it "creates stubs with the correct load path" do extension_line = File.read(bundled_app("bin/rails")).each_line.find {|line| line.include? "$:.unshift" }.strip - expect(extension_line).to eq %($:.unshift File.expand_path "../../bundle", path.realpath) + expect(extension_line).to eq %($:.unshift File.expand_path "../bundle", __dir__) end end end @@ -360,3 +498,31 @@ RSpec.describe "bundle install --standalone run in a subdirectory" do include_examples("bundle install --standalone") end + +RSpec.describe "bundle install --standalone --local" do + before do + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "rack" + G + + system_gems "rack-1.0.0", path: default_bundle_path + end + + it "generates script pointing to system gems" do + bundle "install --standalone --local --verbose" + + expect(out).to include("Using rack 1.0.0") + + load_error_ruby <<-RUBY, "spec" + require "./bundler/setup" + + require "rack" + puts RACK + require "spec" + RUBY + + expect(out).to eq("1.0.0") + expect(err).to eq("ZOMG LOAD ERROR") + end +end diff --git a/spec/bundler/install/gems/sudo_spec.rb b/spec/bundler/install/gems/sudo_spec.rb deleted file mode 100644 index 3e5d38ea4c..0000000000 --- a/spec/bundler/install/gems/sudo_spec.rb +++ /dev/null @@ -1,205 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe "when using sudo", :sudo => true do - describe "and BUNDLE_PATH is writable" do - context "but BUNDLE_PATH/build_info is not writable" do - let(:subdir) do - system_gem_path("cache") - end - - before do - bundle "config set path.system true" - subdir.mkpath - sudo "chmod u-w #{subdir}" - end - - after do - sudo "chmod u+w #{subdir}" - end - - it "installs" do - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" - G - - expect(out).to_not match(/an error occurred/i) - expect(system_gem_path("cache/rack-1.0.0.gem")).to exist - expect(the_bundle).to include_gems "rack 1.0" - end - end - end - - describe "and GEM_HOME is owned by root" do - before :each do - bundle "config set path.system true" - chown_system_gems_to_root - end - - it "installs" do - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", '1.0' - gem "thin" - G - - expect(system_gem_path("gems/rack-1.0.0")).to exist - expect(system_gem_path("gems/rack-1.0.0").stat.uid).to eq(0) - expect(the_bundle).to include_gems "rack 1.0" - end - - it "installs rake and a gem dependent on rake in the same session" do - build_repo2 do - build_gem "another_implicit_rake_dep" do |s| - s.extensions << "Rakefile" - s.write "Rakefile", <<-RUBY - task :default do - path = File.expand_path("../lib", __FILE__) - FileUtils.mkdir_p(path) - File.open("\#{path}/another_implicit_rake_dep.rb", "w") do |f| - f.puts "ANOTHER_IMPLICIT_RAKE_DEP = 'YES'" - end - end - RUBY - end - end - - gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rake" - gem "another_implicit_rake_dep" - G - bundle "install" - expect(system_gem_path("gems/another_implicit_rake_dep-1.0")).to exist - end - - it "installs when BUNDLE_PATH is owned by root" do - bundle_path = tmp("owned_by_root") - FileUtils.mkdir_p bundle_path - sudo "chown -R root #{bundle_path}" - - ENV["BUNDLE_PATH"] = bundle_path.to_s - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", '1.0' - G - - expect(bundle_path.join(Bundler.ruby_scope, "gems/rack-1.0.0")).to exist - expect(bundle_path.join(Bundler.ruby_scope, "gems/rack-1.0.0").stat.uid).to eq(0) - expect(the_bundle).to include_gems "rack 1.0" - end - - it "installs when BUNDLE_PATH does not exist" do - root_path = tmp("owned_by_root") - FileUtils.mkdir_p root_path - sudo "chown -R root #{root_path}" - bundle_path = root_path.join("does_not_exist") - - ENV["BUNDLE_PATH"] = bundle_path.to_s - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", '1.0' - G - - expect(bundle_path.join(Bundler.ruby_scope, "gems/rack-1.0.0")).to exist - expect(bundle_path.join(Bundler.ruby_scope, "gems/rack-1.0.0").stat.uid).to eq(0) - expect(the_bundle).to include_gems "rack 1.0" - end - - it "installs extensions/" do - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "very_simple_binary" - G - - expect(system_gem_path("gems/very_simple_binary-1.0")).to exist - binary_glob = system_gem_path("extensions/*/*/very_simple_binary-1.0") - expect(Dir.glob(binary_glob).first).to be - end - end - - describe "and BUNDLE_PATH is not writable" do - before do - bundle "config set --local path .bundle" - sudo "chmod ugo-w .bundle" - end - - after do - sudo "chmod ugo+w .bundle" - end - - it "installs" do - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", '1.0' - G - - expect(local_gem_path("gems/rack-1.0.0")).to exist - expect(the_bundle).to include_gems "rack 1.0" - end - - it "cleans up the tmpdirs generated" do - require "tmpdir" - Dir.glob("#{Dir.tmpdir}/bundler*").each do |tmpdir| - FileUtils.remove_entry_secure(tmpdir) - end - - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" - G - tmpdirs = Dir.glob("#{Dir.tmpdir}/bundler*") - - expect(tmpdirs).to be_empty - end - end - - describe "and GEM_HOME is not writable" do - it "installs" do - bundle "config set path.system true" - gem_home = tmp("sudo_gem_home") - sudo "mkdir -p #{gem_home}" - sudo "chmod ugo-w #{gem_home}" - - system_gems :bundler, :path => gem_home - - gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", '1.0' - G - - bundle :install, :env => { "GEM_HOME" => gem_home.to_s, "GEM_PATH" => nil } - expect(gem_home.join("bin/rackup")).to exist - expect(the_bundle).to include_gems "rack 1.0", :env => { "GEM_HOME" => gem_home.to_s, "GEM_PATH" => nil } - - sudo "rm -rf #{tmp("sudo_gem_home")}" - end - end - - describe "and root runs install" do - let(:warning) { "Don't run Bundler as root." } - - before do - gemfile %(source "#{file_uri_for(gem_repo1)}") - end - - it "warns against that" do - bundle :install, :sudo => :preserve_env - expect(err).to include(warning) - end - - context "when ENV['BUNDLE_SILENCE_ROOT_WARNING'] is set" do - it "skips the warning" do - bundle :install, :sudo => :preserve_env, :env => { "BUNDLE_SILENCE_ROOT_WARNING" => "true" } - expect(err).to_not include(warning) - end - end - - context "when silence_root_warning = false" do - it "warns against that" do - bundle :install, :sudo => :preserve_env, :env => { "BUNDLE_SILENCE_ROOT_WARNING" => "false" } - expect(err).to include(warning) - end - end - end -end diff --git a/spec/bundler/install/gemspecs_spec.rb b/spec/bundler/install/gemspecs_spec.rb index 0c4518fe2b..51aa0ed14f 100644 --- a/spec/bundler/install/gemspecs_spec.rb +++ b/spec/bundler/install/gemspecs_spec.rb @@ -4,7 +4,7 @@ RSpec.describe "bundle install" do describe "when a gem has a YAML gemspec" do before :each do build_repo2 do - build_gem "yaml_spec", :gemspec => :yaml + build_gem "yaml_spec", gemspec: :yaml end end @@ -18,7 +18,7 @@ RSpec.describe "bundle install" do end it "still installs correctly when using path" do - build_lib "yaml_spec", :gemspec => :yaml + build_lib "yaml_spec", gemspec: :yaml install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -34,6 +34,8 @@ RSpec.describe "bundle install" do gem 'rack' G + system_gems "rack-1.0.0", path: default_bundle_path + FileUtils.mkdir_p "#{default_bundle_path}/specifications" File.open("#{default_bundle_path}/specifications/rack-1.0.0.gemspec", "w+") do |f| spec = Gem::Specification.new do |s| @@ -43,8 +45,8 @@ RSpec.describe "bundle install" do end f.write spec.to_ruby end - bundle :install, :artifice => "endpoint_marshal_fail" # force gemspec load - expect(the_bundle).to include_gems "activesupport 2.3.2" + bundle :install, artifice: "endpoint_marshal_fail" # force gemspec load + expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.2" end it "does not hang when gemspec has incompatible encoding" do @@ -57,7 +59,7 @@ RSpec.describe "bundle install" do end G - install_gemfile <<-G, :env => { "LANG" => "C" } + install_gemfile <<-G, env: { "LANG" => "C" } source "#{file_uri_for(gem_repo1)}" gemspec G @@ -92,8 +94,9 @@ RSpec.describe "bundle install" do end context "when ruby version is specified in gemspec and gemfile" do - it "installs when patch level is not specified and the version matches" do - build_lib("foo", :path => bundled_app) do |s| + it "installs when patch level is not specified and the version matches", + if: RUBY_PATCHLEVEL >= 0 do + build_lib("foo", path: bundled_app) do |s| s.required_ruby_version = "~> #{RUBY_VERSION}.0" end @@ -106,12 +109,12 @@ RSpec.describe "bundle install" do end it "installs when patch level is specified and the version still matches the current version", - :if => RUBY_PATCHLEVEL >= 0 do - build_lib("foo", :path => bundled_app) do |s| + if: RUBY_PATCHLEVEL >= 0 do + build_lib("foo", path: bundled_app) do |s| s.required_ruby_version = "#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}" end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false ruby '#{RUBY_VERSION}', :engine_version => '#{RUBY_VERSION}', :engine => 'ruby', :patchlevel => '#{RUBY_PATCHLEVEL}' source "#{file_uri_for(gem_repo1)}" gemspec @@ -120,13 +123,13 @@ RSpec.describe "bundle install" do end it "fails and complains about patchlevel on patchlevel mismatch", - :if => RUBY_PATCHLEVEL >= 0 do + if: RUBY_PATCHLEVEL >= 0 do patchlevel = RUBY_PATCHLEVEL.to_i + 1 - build_lib("foo", :path => bundled_app) do |s| + build_lib("foo", path: bundled_app) do |s| s.required_ruby_version = "#{RUBY_VERSION}.#{patchlevel}" end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false ruby '#{RUBY_VERSION}', :engine_version => '#{RUBY_VERSION}', :engine => 'ruby', :patchlevel => '#{patchlevel}' source "#{file_uri_for(gem_repo1)}" gemspec @@ -140,11 +143,11 @@ RSpec.describe "bundle install" do it "fails and complains about version on version mismatch" do version = Gem::Requirement.create(RUBY_VERSION).requirements.first.last.bump.version - build_lib("foo", :path => bundled_app) do |s| + build_lib("foo", path: bundled_app) do |s| s.required_ruby_version = version end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false ruby '#{version}', :engine_version => '#{version}', :engine => 'ruby' source "#{file_uri_for(gem_repo1)}" gemspec diff --git a/spec/bundler/install/git_spec.rb b/spec/bundler/install/git_spec.rb index d43aacee7e..c8d574baf3 100644 --- a/spec/bundler/install/git_spec.rb +++ b/spec/bundler/install/git_spec.rb @@ -3,51 +3,51 @@ RSpec.describe "bundle install" do context "git sources" do it "displays the revision hash of the gem repository" do - build_git "foo", "1.0", :path => lib_path("foo") + build_git "foo", "1.0", path: lib_path("foo") - install_gemfile <<-G, :verbose => true + install_gemfile <<-G, verbose: true source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{file_uri_for(lib_path("foo"))}" G - expect(out).to include("Using foo 1.0 from #{file_uri_for(lib_path("foo"))} (at master@#{revision_for(lib_path("foo"))[0..6]})") - expect(the_bundle).to include_gems "foo 1.0", :source => "git@#{lib_path("foo")}" + expect(out).to include("Using foo 1.0 from #{file_uri_for(lib_path("foo"))} (at main@#{revision_for(lib_path("foo"))[0..6]})") + expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}" end - it "displays the correct default branch", :git => ">= 2.28.0" do - build_git "foo", "1.0", :path => lib_path("foo"), :default_branch => "main" + it "displays the correct default branch", git: ">= 2.28.0" do + build_git "foo", "1.0", path: lib_path("foo"), default_branch: "main" - install_gemfile <<-G, :verbose => true + install_gemfile <<-G, verbose: true source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{file_uri_for(lib_path("foo"))}" G expect(out).to include("Using foo 1.0 from #{file_uri_for(lib_path("foo"))} (at main@#{revision_for(lib_path("foo"))[0..6]})") - expect(the_bundle).to include_gems "foo 1.0", :source => "git@#{lib_path("foo")}" + expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}" end it "displays the ref of the gem repository when using branch~num as a ref" do skip "maybe branch~num notation doesn't work on Windows' git" if Gem.win_platform? - build_git "foo", "1.0", :path => lib_path("foo") + build_git "foo", "1.0", path: lib_path("foo") rev = revision_for(lib_path("foo"))[0..6] - update_git "foo", "2.0", :path => lib_path("foo"), :gemspec => true + update_git "foo", "2.0", path: lib_path("foo"), gemspec: true rev2 = revision_for(lib_path("foo"))[0..6] - update_git "foo", "3.0", :path => lib_path("foo"), :gemspec => true + update_git "foo", "3.0", path: lib_path("foo"), gemspec: true - install_gemfile <<-G, :verbose => true + install_gemfile <<-G, verbose: true source "#{file_uri_for(gem_repo1)}" - gem "foo", :git => "#{file_uri_for(lib_path("foo"))}", :ref => "master~2" + gem "foo", :git => "#{file_uri_for(lib_path("foo"))}", :ref => "main~2" G - expect(out).to include("Using foo 1.0 from #{file_uri_for(lib_path("foo"))} (at master~2@#{rev})") - expect(the_bundle).to include_gems "foo 1.0", :source => "git@#{lib_path("foo")}" + expect(out).to include("Using foo 1.0 from #{file_uri_for(lib_path("foo"))} (at main~2@#{rev})") + expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}" - update_git "foo", "4.0", :path => lib_path("foo"), :gemspec => true + update_git "foo", "4.0", path: lib_path("foo"), gemspec: true - bundle :update, :all => true, :verbose => true - expect(out).to include("Using foo 2.0 (was 1.0) from #{file_uri_for(lib_path("foo"))} (at master~2@#{rev2})") - expect(the_bundle).to include_gems "foo 2.0", :source => "git@#{lib_path("foo")}" + bundle :update, all: true, verbose: true + expect(out).to include("Using foo 2.0 (was 1.0) from #{file_uri_for(lib_path("foo"))} (at main~2@#{rev2})") + expect(the_bundle).to include_gems "foo 2.0", source: "git@#{lib_path("foo")}" end it "should allows git repos that are missing but not being installed" do @@ -81,9 +81,9 @@ RSpec.describe "bundle install" do it "allows multiple gems from the same git source" do build_repo2 do - build_lib "foo", "1.0", :path => lib_path("gems/foo") - build_lib "zebra", "2.0", :path => lib_path("gems/zebra") - build_git "gems", :path => lib_path("gems"), :gemspec => false + build_lib "foo", "1.0", path: lib_path("gems/foo") + build_lib "zebra", "2.0", path: lib_path("gems/zebra") + build_git "gems", path: lib_path("gems"), gemspec: false end install_gemfile <<-G @@ -98,5 +98,108 @@ RSpec.describe "bundle install" do bundle "info zebra" expect(out).to include("* zebra (2.0 #{revision_for(lib_path("gems"))[0..6]})") end + + it "should always sort dependencies in the same order" do + # This Gemfile + lockfile had a problem where the first + # `bundle install` would change the order, but the second would + # change it back. + + # NOTE: both gems MUST have the same path! It has to be two gems in one repo. + + test = build_git "test", "1.0.0", path: lib_path("test-and-other") + other = build_git "other", "1.0.0", path: lib_path("test-and-other") + test_ref = test.ref_for("HEAD") + other_ref = other.ref_for("HEAD") + + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + + gem "test", git: #{test.path.to_s.inspect} + gem "other", ref: #{other_ref.inspect}, git: #{other.path.to_s.inspect} + G + + lockfile <<-L + GIT + remote: #{test.path} + revision: #{test_ref} + specs: + test (1.0.0) + + GIT + remote: #{other.path} + revision: #{other_ref} + ref: #{other_ref} + specs: + other (1.0.0) + + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: + + PLATFORMS + ruby + + DEPENDENCIES + other! + test! + + BUNDLED WITH + #{Bundler::VERSION} + L + + # If GH#6743 is present, the first `bundle install` will change the + # lockfile, by flipping the order (`other` would be moved to the top). + # + # The second `bundle install` would then change the lockfile back + # to the original. + # + # The fix makes it so it may change it once, but it will not change + # it a second time. + # + # So, we run `bundle install` once, and store the value of the + # modified lockfile. + bundle :install + modified_lockfile = lockfile + + # If GH#6743 is present, the second `bundle install` would change the + # lockfile back to what it was originally. + # + # This `expect` makes sure it doesn't change a second time. + bundle :install + expect(lockfile).to eq(modified_lockfile) + + expect(out).to include("Bundle complete!") + end + + it "allows older revisions of git source when clean true" do + build_git "foo", "1.0", path: lib_path("foo") + rev = revision_for(lib_path("foo")) + + bundle "config set path vendor/bundle" + bundle "config set clean true" + install_gemfile <<-G, verbose: true + source "#{file_uri_for(gem_repo1)}" + gem "foo", :git => "#{file_uri_for(lib_path("foo"))}" + G + + expect(out).to include("Using foo 1.0 from #{file_uri_for(lib_path("foo"))} (at main@#{rev[0..6]})") + expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}" + + old_lockfile = lockfile + + update_git "foo", "2.0", path: lib_path("foo"), gemspec: true + rev2 = revision_for(lib_path("foo")) + + bundle :update, all: true, verbose: true + expect(out).to include("Using foo 2.0 (was 1.0) from #{file_uri_for(lib_path("foo"))} (at main@#{rev2[0..6]})") + expect(out).to include("Removing foo (#{rev[0..11]})") + expect(the_bundle).to include_gems "foo 2.0", source: "git@#{lib_path("foo")}" + + lockfile(old_lockfile) + + bundle :install, verbose: true + expect(out).to include("Using foo 1.0 from #{file_uri_for(lib_path("foo"))} (at main@#{rev[0..6]})") + expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}" + end end end diff --git a/spec/bundler/install/global_cache_spec.rb b/spec/bundler/install/global_cache_spec.rb index 9bc243e7cf..0da4de05b2 100644 --- a/spec/bundler/install/global_cache_spec.rb +++ b/spec/bundler/install/global_cache_spec.rb @@ -16,7 +16,7 @@ RSpec.describe "global gem caching" do end it "caches gems into the global cache on download" do - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "#{source}" gem "rack" G @@ -29,7 +29,7 @@ RSpec.describe "global gem caching" do source_global_cache.mkpath FileUtils.cp(gem_repo1("gems/rack-1.0.0.gem"), source_global_cache("rack-1.0.0.gem")) - install_gemfile <<-G, :artifice => "compact_index_no_gem" + install_gemfile <<-G, artifice: "compact_index_no_gem" source "#{source}" gem "rack" G @@ -37,9 +37,21 @@ RSpec.describe "global gem caching" do expect(the_bundle).to include_gems "rack 1.0.0" end + it "shows a proper error message if a cached gem is corrupted" do + source_global_cache.mkpath + FileUtils.touch(source_global_cache("rack-1.0.0.gem")) + + install_gemfile <<-G, artifice: "compact_index_no_gem", raise_on_error: false + source "#{source}" + gem "rack" + G + + expect(err).to include("Gem::Package::FormatError: package metadata is missing in #{source_global_cache("rack-1.0.0.gem")}") + end + describe "when the same gem from different sources is installed" do it "should use the appropriate one from the global cache" do - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "#{source}" gem "rack" G @@ -49,7 +61,7 @@ RSpec.describe "global gem caching" do expect(source_global_cache("rack-1.0.0.gem")).to exist # rack 1.0.0 is not installed and it is in the global cache - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "#{source2}" gem "rack", "0.9.1" G @@ -64,7 +76,7 @@ RSpec.describe "global gem caching" do gem "rack", "1.0.0" G - bundle :install, :artifice => "compact_index_no_gem" + bundle :install, artifice: "compact_index_no_gem" # rack 1.0.0 is installed and rack 0.9.1 is not expect(the_bundle).to include_gems "rack 1.0.0" expect(the_bundle).not_to include_gems "rack 0.9.1" @@ -75,7 +87,7 @@ RSpec.describe "global gem caching" do gem "rack", "0.9.1" G - bundle :install, :artifice => "compact_index_no_gem" + bundle :install, artifice: "compact_index_no_gem" # rack 0.9.1 is installed and rack 1.0.0 is not expect(the_bundle).to include_gems "rack 0.9.1" expect(the_bundle).not_to include_gems "rack 1.0.0" @@ -87,7 +99,7 @@ RSpec.describe "global gem caching" do gem "rack" G - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" simulate_new_machine expect(the_bundle).not_to include_gems "rack 1.0.0" expect(source_global_cache("rack-1.0.0.gem")).to exist @@ -98,7 +110,7 @@ RSpec.describe "global gem caching" do gem "rack", "0.9.1" G - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" simulate_new_machine expect(the_bundle).not_to include_gems "rack 0.9.1" expect(source2_global_cache("rack-0.9.1.gem")).to exist @@ -111,9 +123,9 @@ RSpec.describe "global gem caching" do expect(source_global_cache("rack-1.0.0.gem")).to exist expect(source2_global_cache("rack-0.9.1.gem")).to exist - bundle :install, :artifice => "compact_index_no_gem", :raise_on_error => false + bundle :install, artifice: "compact_index_no_gem", raise_on_error: false expect(err).to include("Internal Server Error 500") - expect(err).not_to include("please copy and paste the report template above into a new issue") + expect(err).not_to include("ERROR REPORT TEMPLATE") # rack 1.0.0 is not installed and rack 0.9.1 is not expect(the_bundle).not_to include_gems "rack 1.0.0" @@ -126,9 +138,9 @@ RSpec.describe "global gem caching" do expect(source_global_cache("rack-1.0.0.gem")).to exist expect(source2_global_cache("rack-0.9.1.gem")).to exist - bundle :install, :artifice => "compact_index_no_gem", :raise_on_error => false + bundle :install, artifice: "compact_index_no_gem", raise_on_error: false expect(err).to include("Internal Server Error 500") - expect(err).not_to include("please copy and paste the report template above into a new issue") + expect(err).not_to include("ERROR REPORT TEMPLATE") # rack 0.9.1 is not installed and rack 1.0.0 is not expect(the_bundle).not_to include_gems "rack 0.9.1" @@ -138,7 +150,7 @@ RSpec.describe "global gem caching" do describe "when installing gems from a different directory" do it "uses the global cache as a source" do - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "#{source}" gem "rack" gem "activesupport" @@ -154,7 +166,7 @@ RSpec.describe "global gem caching" do expect(the_bundle).not_to include_gems "rack 1.0.0" expect(the_bundle).not_to include_gems "activesupport 2.3.5" - install_gemfile <<-G, :artifice => "compact_index_no_gem" + install_gemfile <<-G, artifice: "compact_index_no_gem" source "#{source}" gem "rack" G @@ -171,20 +183,18 @@ RSpec.describe "global gem caching" do G # Neither gem is installed and both are in the global cache - expect(the_bundle).not_to include_gems "rack 1.0.0", :dir => bundled_app2 - expect(the_bundle).not_to include_gems "activesupport 2.3.5", :dir => bundled_app2 + expect(the_bundle).not_to include_gems "rack 1.0.0", dir: bundled_app2 + expect(the_bundle).not_to include_gems "activesupport 2.3.5", dir: bundled_app2 expect(source_global_cache("rack-1.0.0.gem")).to exist expect(source_global_cache("activesupport-2.3.5.gem")).to exist # Install using the global cache instead of by downloading the .gem # from the server - bundle :install, :artifice => "compact_index_no_gem", :dir => bundled_app2 + bundle :install, artifice: "compact_index_no_gem", dir: bundled_app2 # activesupport is installed and both are in the global cache - simulate_bundler_version_when_missing_prerelease_default_gem_activation do - expect(the_bundle).not_to include_gems "rack 1.0.0", :dir => bundled_app2 - expect(the_bundle).to include_gems "activesupport 2.3.5", :dir => bundled_app2 - end + expect(the_bundle).not_to include_gems "rack 1.0.0", dir: bundled_app2 + expect(the_bundle).to include_gems "activesupport 2.3.5", dir: bundled_app2 expect(source_global_cache("rack-1.0.0.gem")).to exist expect(source_global_cache("activesupport-2.3.5.gem")).to exist @@ -193,7 +203,7 @@ RSpec.describe "global gem caching" do end describe "extension caching" do - it "works", :ruby_repo do + it "works" do skip "gets incorrect ref in path" if Gem.win_platform? build_git "very_simple_git_binary", &:add_c_extension @@ -208,9 +218,9 @@ RSpec.describe "global gem caching" do gem "very_simple_path_binary", :path => "#{lib_path("very_simple_path_binary-1.0")}" G - gem_binary_cache = home(".bundle", "cache", "extensions", specific_local_platform.to_s, Bundler.ruby_scope, + gem_binary_cache = home(".bundle", "cache", "extensions", local_platform.to_s, Bundler.ruby_scope, Digest(:MD5).hexdigest("#{gem_repo1}/"), "very_simple_binary-1.0") - git_binary_cache = home(".bundle", "cache", "extensions", specific_local_platform.to_s, Bundler.ruby_scope, + git_binary_cache = home(".bundle", "cache", "extensions", local_platform.to_s, Bundler.ruby_scope, "very_simple_git_binary-1.0-#{revision}", "very_simple_git_binary-1.0") cached_extensions = Pathname.glob(home(".bundle", "cache", "extensions", "*", "*", "*", "*", "*")).sort @@ -222,7 +232,7 @@ RSpec.describe "global gem caching" do R expect(out).to eq "VERY_SIMPLE_BINARY_IN_C\nVERY_SIMPLE_GIT_BINARY_IN_C" - FileUtils.rm Dir[home(".bundle", "cache", "extensions", "**", "*binary_c*")] + FileUtils.rm_rf Dir[home(".bundle", "cache", "extensions", "**", "*binary_c*")] gem_binary_cache.join("very_simple_binary_c.rb").open("w") {|f| f << "puts File.basename(__FILE__)" } git_binary_cache.join("very_simple_git_binary_c.rb").open("w") {|f| f << "puts File.basename(__FILE__)" } diff --git a/spec/bundler/install/path_spec.rb b/spec/bundler/install/path_spec.rb index b0392c4ed2..0a30e402b7 100644 --- a/spec/bundler/install/path_spec.rb +++ b/spec/bundler/install/path_spec.rb @@ -3,7 +3,7 @@ RSpec.describe "bundle install" do describe "with path configured" do before :each do - build_gem "rack", "1.0.0", :to_system => true do |s| + build_gem "rack", "1.0.0", to_system: true do |s| s.write "lib/rack.rb", "puts 'FAIL'" end @@ -23,7 +23,7 @@ RSpec.describe "bundle install" do bundle "config set --local path.system true" bundle "config set --global path vendor/bundle" bundle :install - run "require 'rack'", :raise_on_error => false + run "require 'rack'", raise_on_error: false expect(out).to include("FAIL") end @@ -32,7 +32,7 @@ RSpec.describe "bundle install" do dir.mkpath bundle "config set --local path #{dir.join("vendor/bundle")}" - bundle :install, :dir => dir + bundle :install, dir: dir expect(out).to include("installed into `./vendor/bundle`") dir.rmtree @@ -44,13 +44,13 @@ RSpec.describe "bundle install" do expect(out).to include("gems are installed into `./vendor/bundle`") end - it "disallows --path vendor/bundle --system", :bundler => "< 3" do - bundle "install --path vendor/bundle --system", :raise_on_error => false + it "disallows --path vendor/bundle --system", bundler: "< 3" do + bundle "install --path vendor/bundle --system", raise_on_error: false expect(err).to include("Please choose only one option.") expect(exitstatus).to eq(15) end - it "remembers to disable system gems after the first time with bundle --path vendor/bundle", :bundler => "< 3" do + it "remembers to disable system gems after the first time with bundle --path vendor/bundle", bundler: "< 3" do bundle "install --path vendor/bundle" FileUtils.rm_rf bundled_app("vendor") bundle "install" @@ -62,22 +62,22 @@ RSpec.describe "bundle install" do context "with path_relative_to_cwd set to true" do before { bundle "config set path_relative_to_cwd true" } - it "installs the bundle relatively to current working directory", :bundler => "< 3" do - bundle "install --gemfile='#{bundled_app}/Gemfile' --path vendor/bundle", :dir => bundled_app.parent + it "installs the bundle relatively to current working directory", bundler: "< 3" do + bundle "install --gemfile='#{bundled_app}/Gemfile' --path vendor/bundle", dir: bundled_app.parent expect(out).to include("installed into `./vendor/bundle`") expect(bundled_app("../vendor/bundle")).to be_directory expect(the_bundle).to include_gems "rack 1.0.0" end it "installs the standalone bundle relative to the cwd" do - bundle :install, :gemfile => bundled_app_gemfile, :standalone => true, :dir => bundled_app.parent + bundle :install, gemfile: bundled_app_gemfile, standalone: true, dir: bundled_app.parent expect(out).to include("installed into `./bundled_app/bundle`") expect(bundled_app("bundle")).to be_directory expect(bundled_app("bundle/ruby")).to be_directory bundle "config unset path" - bundle :install, :gemfile => bundled_app_gemfile, :standalone => true, :dir => bundled_app("subdir").tap(&:mkpath) + bundle :install, gemfile: bundled_app_gemfile, standalone: true, dir: bundled_app("subdir").tap(&:mkpath) expect(out).to include("installed into `../bundle`") expect(bundled_app("bundle")).to be_directory expect(bundled_app("bundle/ruby")).to be_directory @@ -87,7 +87,7 @@ RSpec.describe "bundle install" do describe "when BUNDLE_PATH or the global path config is set" do before :each do - build_lib "rack", "1.0.0", :to_system => true do |s| + build_lib "rack", "1.0.0", to_system: true do |s| s.write "lib/rack.rb", "raise 'FAIL'" end @@ -141,7 +141,7 @@ RSpec.describe "bundle install" do set_bundle_path(type, "vendor") FileUtils.mkdir_p bundled_app("lol") - bundle :install, :dir => bundled_app("lol") + bundle :install, dir: bundled_app("lol") expect(bundled_app("vendor", Bundler.ruby_scope, "gems/rack-1.0.0")).to be_directory expect(the_bundle).to include_gems "rack 1.0.0" @@ -168,7 +168,7 @@ RSpec.describe "bundle install" do it "disables system gems when passing a path to install" do # This is so that vendored gems can be distributed to others - build_gem "rack", "1.1.0", :to_system => true + build_gem "rack", "1.1.0", to_system: true bundle "config set --local path ./vendor/bundle" bundle :install @@ -176,8 +176,8 @@ RSpec.describe "bundle install" do expect(the_bundle).to include_gems "rack 1.0.0" end - it "re-installs gems whose extensions have been deleted", :ruby_repo do - build_lib "very_simple_binary", "1.0.0", :to_system => true do |s| + it "re-installs gems whose extensions have been deleted" do + build_lib "very_simple_binary", "1.0.0", to_system: true do |s| s.write "lib/very_simple_binary.rb", "raise 'FAIL'" end @@ -191,11 +191,11 @@ RSpec.describe "bundle install" do expect(vendored_gems("gems/very_simple_binary-1.0")).to be_directory expect(vendored_gems("extensions")).to be_directory - expect(the_bundle).to include_gems "very_simple_binary 1.0", :source => "remote1" + expect(the_bundle).to include_gems "very_simple_binary 1.0", source: "remote1" vendored_gems("extensions").rmtree - run "require 'very_simple_binary_c'", :raise_on_error => false + run "require 'very_simple_binary_c'", raise_on_error: false expect(err).to include("Bundler::GemNotFound") bundle "config set --local path ./vendor/bundle" @@ -203,7 +203,7 @@ RSpec.describe "bundle install" do expect(vendored_gems("gems/very_simple_binary-1.0")).to be_directory expect(vendored_gems("extensions")).to be_directory - expect(the_bundle).to include_gems "very_simple_binary 1.0", :source => "remote1" + expect(the_bundle).to include_gems "very_simple_binary 1.0", source: "remote1" end end @@ -219,7 +219,7 @@ RSpec.describe "bundle install" do G bundle "config set --local path bundle" - bundle :install, :raise_on_error => false + bundle :install, raise_on_error: false expect(err).to include("file already exists") end end diff --git a/spec/bundler/install/process_lock_spec.rb b/spec/bundler/install/process_lock_spec.rb index dac0d34bc4..1f8c62f26e 100644 --- a/spec/bundler/install/process_lock_spec.rb +++ b/spec/bundler/install/process_lock_spec.rb @@ -31,5 +31,27 @@ RSpec.describe "process lock spec" do expect(processed).to eq true end end + + context "when creating a lock raises Errno::EPERM" do + before { allow(File).to receive(:open).and_raise(Errno::EPERM) } + + it "skips creating the lock file and yields" do + processed = false + Bundler::ProcessLock.lock(default_bundle_path) { processed = true } + + expect(processed).to eq true + end + end + + context "when creating a lock raises Errno::EROFS" do + before { allow(File).to receive(:open).and_raise(Errno::EROFS) } + + it "skips creating the lock file and yields" do + processed = false + Bundler::ProcessLock.lock(default_bundle_path) { processed = true } + + expect(processed).to eq true + end + end end end diff --git a/spec/bundler/install/redownload_spec.rb b/spec/bundler/install/redownload_spec.rb index a936b2b536..3a72c356d9 100644 --- a/spec/bundler/install/redownload_spec.rb +++ b/spec/bundler/install/redownload_spec.rb @@ -57,7 +57,7 @@ RSpec.describe "bundle install" do end end - describe "with --force", :bundler => 2 do + describe "with --force", bundler: 2 do it_behaves_like "an option to force redownloading gems" do let(:flag) { "force" } end diff --git a/spec/bundler/install/security_policy_spec.rb b/spec/bundler/install/security_policy_spec.rb index 43c3069c4e..befeb81da5 100644 --- a/spec/bundler/install/security_policy_spec.rb +++ b/spec/bundler/install/security_policy_spec.rb @@ -16,23 +16,23 @@ RSpec.describe "policies with unsigned gems" do end it "will work after you try to deploy without a lock" do - bundle "install --deployment", :raise_on_error => false + bundle "install --deployment", raise_on_error: false bundle :install expect(the_bundle).to include_gems "rack 1.0", "signed_gem 1.0" end it "will fail when given invalid security policy" do - bundle "install --trust-policy=InvalidPolicyName", :raise_on_error => false + bundle "install --trust-policy=InvalidPolicyName", raise_on_error: false expect(err).to include("RubyGems doesn't know about trust policy") end it "will fail with High Security setting due to presence of unsigned gem" do - bundle "install --trust-policy=HighSecurity", :raise_on_error => false + bundle "install --trust-policy=HighSecurity", raise_on_error: false expect(err).to include("security policy didn't allow") end it "will fail with Medium Security setting due to presence of unsigned gem" do - bundle "install --trust-policy=MediumSecurity", :raise_on_error => false + bundle "install --trust-policy=MediumSecurity", raise_on_error: false expect(err).to include("security policy didn't allow") end @@ -51,12 +51,12 @@ RSpec.describe "policies with signed gems and no CA" do end it "will fail with High Security setting, gem is self-signed" do - bundle "install --trust-policy=HighSecurity", :raise_on_error => false + bundle "install --trust-policy=HighSecurity", raise_on_error: false expect(err).to include("security policy didn't allow") end it "will fail with Medium Security setting, gem is self-signed" do - bundle "install --trust-policy=MediumSecurity", :raise_on_error => false + bundle "install --trust-policy=MediumSecurity", raise_on_error: false expect(err).to include("security policy didn't allow") end diff --git a/spec/bundler/install/yanked_spec.rb b/spec/bundler/install/yanked_spec.rb index 6d3065a836..7408c24327 100644 --- a/spec/bundler/install/yanked_spec.rb +++ b/spec/bundler/install/yanked_spec.rb @@ -15,14 +15,14 @@ RSpec.context "when installing a bundle that includes yanked gems" do foo (10.0.0) PLATFORMS - ruby + #{lockfile_platforms} DEPENDENCIES foo (= 10.0.0) L - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo4)}" gem "foo", "10.0.0" G @@ -30,10 +30,76 @@ RSpec.context "when installing a bundle that includes yanked gems" do expect(err).to include("Your bundle is locked to foo (10.0.0)") end + context "when a re-resolve is necessary, and a yanked version is considered by the resolver" do + before do + skip "Materialization on Windows is not yet strict, so the example does not detect the gem has been yanked" if Gem.win_platform? + + build_repo4 do + build_gem "foo", "1.0.0", "1.0.1" + build_gem "actiontext", "6.1.7" do |s| + s.add_dependency "nokogiri", ">= 1.8" + end + build_gem "actiontext", "6.1.6" do |s| + s.add_dependency "nokogiri", ">= 1.8" + end + build_gem "actiontext", "6.1.7" do |s| + s.add_dependency "nokogiri", ">= 1.8" + end + build_gem "nokogiri", "1.13.8" + end + + gemfile <<~G + source "#{source_uri}" + gem "foo", "1.0.1" + gem "actiontext", "6.1.6" + G + + lockfile <<~L + GEM + remote: #{source_uri}/ + specs: + actiontext (6.1.6) + nokogiri (>= 1.8) + foo (1.0.0) + nokogiri (1.13.8-#{Bundler.local_platform}) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + actiontext (= 6.1.6) + foo (= 1.0.0) + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + context "and the old index is used" do + let(:source_uri) { file_uri_for(gem_repo4) } + + it "reports the yanked gem properly" do + bundle "install", raise_on_error: false + + expect(err).to include("Your bundle is locked to nokogiri (1.13.8-#{Bundler.local_platform})") + end + end + + context "and the compact index API is used" do + let(:source_uri) { "https://gem.repo4" } + + it "reports the yanked gem properly" do + bundle "install", artifice: "compact_index", raise_on_error: false + + expect(err).to include("Your bundle is locked to nokogiri (1.13.8-#{Bundler.local_platform})") + end + end + end + it "throws the original error when only the Gemfile specifies a gem version that doesn't exist" do bundle "config set force_ruby_platform true" - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo4)}" gem "foo", "10.0.0" G @@ -43,6 +109,63 @@ RSpec.context "when installing a bundle that includes yanked gems" do end end +RSpec.context "when resolving a bundle that includes yanked gems, but unlocking an unrelated gem" do + before(:each) do + build_repo4 do + build_gem "foo", "10.0.0" + + build_gem "bar", "1.0.0" + build_gem "bar", "2.0.0" + end + + lockfile <<-L + GEM + remote: #{file_uri_for(gem_repo4)} + specs: + foo (9.0.0) + bar (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo + bar + + BUNDLED WITH + #{Bundler::VERSION} + L + + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "foo" + gem "bar" + G + end + + it "does not update the yanked gem" do + bundle "lock --update bar" + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + bar (2.0.0) + foo (9.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + bar + foo + + BUNDLED WITH + #{Bundler::VERSION} + L + end +end + RSpec.context "when using gem before installing" do it "does not suggest the author has yanked the gem" do gemfile <<-G @@ -57,16 +180,53 @@ RSpec.context "when using gem before installing" do rack (0.9.1) PLATFORMS - ruby + #{lockfile_platforms} + + DEPENDENCIES + rack (= 0.9.1) + L + + bundle :list, raise_on_error: false + + expect(err).to include("Could not find rack-0.9.1 in cached gems or installed locally") + expect(err).to_not include("Your bundle is locked to rack (0.9.1) from") + expect(err).to_not include("If you haven't changed sources, that means the author of rack (0.9.1) has removed it.") + expect(err).to_not include("You'll need to update your bundle to a different version of rack (0.9.1) that hasn't been removed in order to install.") + + # Check error message is still correct when multiple platforms are locked + lockfile lockfile.gsub(/PLATFORMS\n #{lockfile_platforms}/m, "PLATFORMS\n #{lockfile_platforms("ruby")}") + + bundle :list, raise_on_error: false + expect(err).to include("Could not find rack-0.9.1 in cached gems or installed locally") + end + + it "does not suggest the author has yanked the gem when using more than one gem, but shows all gems that couldn't be found in the source" do + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "rack", "0.9.1" + gem "rack_middleware", "1.0" + G + + lockfile <<-L + GEM + remote: #{file_uri_for(gem_repo1)} + specs: + rack (0.9.1) + rack_middleware (1.0) + + PLATFORMS + #{lockfile_platforms} DEPENDENCIES rack (= 0.9.1) + rack_middleware (1.0) L - bundle :list, :raise_on_error => false + bundle :list, raise_on_error: false - expect(err).to include("Could not find rack-0.9.1 in any of the sources") - expect(err).to_not include("Your bundle is locked to rack (0.9.1), but that version could not be found in any of the sources listed in your Gemfile.") + expect(err).to include("Could not find rack-0.9.1, rack_middleware-1.0 in cached gems or installed locally") + expect(err).to include("Install missing gems with `bundle install`.") + expect(err).to_not include("Your bundle is locked to rack (0.9.1) from") expect(err).to_not include("If you haven't changed sources, that means the author of rack (0.9.1) has removed it.") expect(err).to_not include("You'll need to update your bundle to a different version of rack (0.9.1) that hasn't been removed in order to install.") end diff --git a/spec/bundler/lock/git_spec.rb b/spec/bundler/lock/git_spec.rb index 56db5d8305..ad13e8ffc6 100644 --- a/spec/bundler/lock/git_spec.rb +++ b/spec/bundler/lock/git_spec.rb @@ -14,6 +14,52 @@ RSpec.describe "bundle lock with git gems" do 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 + FileUtils.rm_rf(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 + gemfile <<-G + source "#{file_uri_for(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 + lockfile <<~L + GIT + remote: #{lib_path("foo-1.0")} + revision: #{"a" * 40} + specs: + foo (1.0) + + GEM + remote: #{file_uri_for(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 update_git "foo" bundle :install @@ -26,6 +72,87 @@ RSpec.describe "bundle lock with git gems" do expect(out).to eq("WIN") end + it "properly clones a git source locked to an out of date ref" do + 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 + # 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 "#{file_uri_for(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: #{file_uri_for(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 + # 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 "#{file_uri_for(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: #{file_uri_for(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 run <<-RUBY puts Bundler.rubygems.find_name('foo').first.full_gem_path diff --git a/spec/bundler/lock/lockfile_spec.rb b/spec/bundler/lock/lockfile_spec.rb index 0e2a3a3cd8..4fd081e7d0 100644 --- a/spec/bundler/lock/lockfile_spec.rb +++ b/spec/bundler/lock/lockfile_spec.rb @@ -1,28 +1,22 @@ # frozen_string_literal: true RSpec.describe "the lockfile format" do - include Bundler::GemHelpers - before do - build_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/rubygems/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 + build_repo2 end it "generates a simple lockfile for a single source, gem" do + checksums = checksums_section_when_existing do |c| + c.checksum(gem_repo2, "rack", "1.0.0") + end + install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" gem "rack" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -33,13 +27,13 @@ RSpec.describe "the lockfile format" do DEPENDENCIES rack - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G end - it "updates the lockfile's bundler version if current ver. is newer" do + 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 @@ -64,13 +58,16 @@ RSpec.describe "the lockfile format" do 1.8.2 L - install_gemfile <<-G, :env => { "BUNDLER_VERSION" => Bundler::VERSION } + install_gemfile <<-G, verbose: true, env: { "BUNDLER_VERSION" => Bundler::VERSION } source "#{file_uri_for(gem_repo2)}" gem "rack" G - lockfile_should_be <<-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: #{file_uri_for(gem_repo2)}/ specs: @@ -87,8 +84,8 @@ RSpec.describe "the lockfile format" do G end - it "does not update the lockfile's bundler version if nothing changed during bundle install" do - version = "#{Bundler::VERSION.split(".").first}.0.0.a" + it "does not update the lockfile's bundler version if nothing changed during bundle install, but uses the locked version", rubygems: ">= 3.3.0.a", realworld: true do + version = "2.3.0" lockfile <<-L GEM @@ -106,13 +103,16 @@ RSpec.describe "the lockfile format" do #{version} L - install_gemfile <<-G + install_gemfile <<-G, verbose: true, artifice: "vcr" source "#{file_uri_for(gem_repo2)}" gem "rack" G - lockfile_should_be <<-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: #{file_uri_for(gem_repo2)}/ specs: @@ -129,7 +129,13 @@ RSpec.describe "the lockfile format" do G end - it "updates the lockfile's bundler version if not present" do + it "does not update the lockfile's bundler version if nothing changed during bundle install, and uses the latest version", rubygems: "< 3.3.0.a" do + version = "#{Bundler::VERSION.split(".").first}.0.0.a" + + checksums = checksums_section do |c| + c.checksum(gem_repo2, "rack", "1.0.0") + end + lockfile <<-L GEM remote: #{file_uri_for(gem_repo2)}/ @@ -141,15 +147,21 @@ RSpec.describe "the lockfile format" do DEPENDENCIES rack + #{checksums} + BUNDLED WITH + #{version} L - install_gemfile <<-G + install_gemfile <<-G, verbose: true source "#{file_uri_for(gem_repo2)}" - gem "rack", "> 0" + gem "rack" G - lockfile_should_be <<-G + expect(out).not_to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{version}.") + expect(out).to include("Using bundler #{Bundler::VERSION}") + + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -159,17 +171,14 @@ RSpec.describe "the lockfile format" do #{lockfile_platforms} DEPENDENCIES - rack (> 0) - + rack + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{version} G end - it "warns if the current is older than lockfile's bundler version" do - current_version = Bundler::VERSION - newer_minor = bump_minor(current_version) - + it "adds the BUNDLED WITH section if not present" do lockfile <<-L GEM remote: #{file_uri_for(gem_repo2)}/ @@ -181,25 +190,15 @@ RSpec.describe "the lockfile format" do DEPENDENCIES rack - - BUNDLED WITH - #{newer_minor} L install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" - gem "rack" + gem "rack", "> 0" G - pre_flag = prerelease?(newer_minor) ? " --pre" : "" - warning_message = "the running version of Bundler (#{current_version}) is older " \ - "than the version that created the lockfile (#{newer_minor}). " \ - "We suggest you to upgrade to the version that created the " \ - "lockfile by running `gem install bundler:#{newer_minor}#{pre_flag}`." - expect(err).to include warning_message - - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -209,14 +208,14 @@ RSpec.describe "the lockfile format" do #{lockfile_platforms} DEPENDENCIES - rack + rack (> 0) BUNDLED WITH - #{newer_minor} + #{Bundler::VERSION} G end - it "warns when updating bundler major version" do + it "update the bundler major version just fine" do current_version = Bundler::VERSION older_major = previous_major(current_version) @@ -238,18 +237,15 @@ RSpec.describe "the lockfile format" do #{older_major} L - install_gemfile <<-G, :env => { "BUNDLER_VERSION" => Bundler::VERSION } + install_gemfile <<-G, env: { "BUNDLER_VERSION" => Bundler::VERSION } source "#{file_uri_for(gem_repo2)}/" gem "rack" G - expect(err).to include( - "Warning: the lockfile is being updated to Bundler " \ - "#{current_version.split(".").first}, after which you will be unable to return to Bundler #{older_major.split(".").first}." - ) + expect(err).to be_empty - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -273,7 +269,12 @@ RSpec.describe "the lockfile format" do gem "rack-obama" G - lockfile_should_be <<-G + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo2, "rack", "1.0.0" + c.checksum gem_repo2, "rack-obama", "1.0" + end + + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -286,7 +287,7 @@ RSpec.describe "the lockfile format" do DEPENDENCIES rack-obama - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G @@ -299,7 +300,12 @@ RSpec.describe "the lockfile format" do gem "rack-obama", ">= 1.0" G - lockfile_should_be <<-G + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo2, "rack", "1.0.0" + c.checksum gem_repo2, "rack-obama", "1.0" + end + + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -312,16 +318,16 @@ RSpec.describe "the lockfile format" do DEPENDENCIES rack-obama (>= 1.0) - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G end - it "generates a lockfile without credentials for a configured source" do + it "generates a lockfile without credentials" do bundle "config set http://localgemserver.test/ user:pass" - install_gemfile(<<-G, :artifice => "endpoint_strict_basic_authentication", :quiet => true) + install_gemfile(<<-G, artifice: "endpoint_strict_basic_authentication", quiet: true) source "#{file_uri_for(gem_repo1)}" source "http://localgemserver.test/" do @@ -333,7 +339,12 @@ RSpec.describe "the lockfile format" do end G - lockfile_should_be <<-G + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo2, "rack", "1.0.0" + c.checksum gem_repo2, "rack-obama", "1.0" + end + + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo1)}/ specs: @@ -343,7 +354,7 @@ RSpec.describe "the lockfile format" do specs: GEM - remote: http://user:pass@othergemserver.test/ + remote: http://othergemserver.test/ specs: rack (1.0.0) rack-obama (1.0) @@ -354,7 +365,7 @@ RSpec.describe "the lockfile format" do DEPENDENCIES rack-obama (>= 1.0)! - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G @@ -366,7 +377,12 @@ RSpec.describe "the lockfile format" do gem "net-sftp" G - lockfile_should_be <<-G + checksums = checksums_section_when_existing 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: #{file_uri_for(gem_repo2)}/ specs: @@ -379,7 +395,7 @@ RSpec.describe "the lockfile format" do DEPENDENCIES net-sftp - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G @@ -395,10 +411,14 @@ RSpec.describe "the lockfile format" do gem "foo", :git => "#{lib_path("foo-1.0")}" G - lockfile_should_be <<-G + checksums = checksums_section_when_existing 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("master")} + revision: #{git.ref_for("main")} specs: foo (1.0) @@ -411,14 +431,14 @@ RSpec.describe "the lockfile format" do 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") + build_lib "omg", path: lib_path("omg") gemfile <<-G source "#{file_uri_for(gem_repo2)}/" @@ -459,6 +479,10 @@ RSpec.describe "the lockfile format" do it "serializes global git sources" do git = build_git "foo" + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + end + install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" git "#{lib_path("foo-1.0")}" do @@ -466,10 +490,10 @@ RSpec.describe "the lockfile format" do end G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GIT remote: #{lib_path("foo-1.0")} - revision: #{git.ref_for("master")} + revision: #{git.ref_for("main")} specs: foo (1.0) @@ -482,7 +506,7 @@ RSpec.describe "the lockfile format" do DEPENDENCIES foo! - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G @@ -490,14 +514,18 @@ RSpec.describe "the lockfile format" do 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" + update_git "foo", branch: "omg" + + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + end install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}", :branch => "omg" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GIT remote: #{lib_path("foo-1.0")} revision: #{git.ref_for("omg")} @@ -514,7 +542,7 @@ RSpec.describe "the lockfile format" do DEPENDENCIES foo! - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G @@ -522,14 +550,18 @@ RSpec.describe "the lockfile format" do 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" + update_git "foo", tag: "omg" + + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + end install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "foo", :git => "#{lib_path("foo-1.0")}", :tag => "omg" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GIT remote: #{lib_path("foo-1.0")} revision: #{git.ref_for("omg")} @@ -546,21 +578,111 @@ RSpec.describe "the lockfile format" do 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: file_uri_for(@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 "#{file_uri_for(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: #{file_uri_for(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: #{file_uri_for(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_existing do |c| + c.no_checksum "foo", "1.0" + end + install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("foo-1.0")}" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G PATH remote: #{lib_path("foo-1.0")} specs: @@ -575,7 +697,7 @@ RSpec.describe "the lockfile format" do DEPENDENCIES foo! - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G @@ -584,6 +706,10 @@ RSpec.describe "the lockfile format" do it "serializes pinned path sources to the lockfile even when packaging" do build_lib "foo" + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + end + install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("foo-1.0")}" @@ -591,9 +717,9 @@ RSpec.describe "the lockfile format" do bundle "config set cache_all true" bundle :cache - bundle :install, :local => true + bundle :install, local: true - lockfile_should_be <<-G + expect(lockfile).to eq <<~G PATH remote: #{lib_path("foo-1.0")} specs: @@ -608,7 +734,7 @@ RSpec.describe "the lockfile format" do DEPENDENCIES foo! - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G @@ -618,6 +744,12 @@ RSpec.describe "the lockfile format" do build_lib "foo" bar = build_git "bar" + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + c.no_checksum "bar", "1.0" + c.checksum gem_repo2, "rack", "1.0.0" + end + install_gemfile <<-G source "#{file_uri_for(gem_repo2)}/" @@ -626,10 +758,10 @@ RSpec.describe "the lockfile format" do gem "bar", :git => "#{lib_path("bar-1.0")}" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GIT remote: #{lib_path("bar-1.0")} - revision: #{bar.ref_for("master")} + revision: #{bar.ref_for("main")} specs: bar (1.0) @@ -650,7 +782,7 @@ RSpec.describe "the lockfile format" do bar! foo! rack - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G @@ -663,7 +795,11 @@ RSpec.describe "the lockfile format" do gem "rack", :source => "#{file_uri_for(gem_repo2)}/" G - lockfile_should_be <<-G + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo2, "rack", "1.0.0" + end + + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -674,7 +810,7 @@ RSpec.describe "the lockfile format" do DEPENDENCIES rack! - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G @@ -689,7 +825,15 @@ RSpec.describe "the lockfile format" do gem "rack-obama" G - lockfile_should_be <<-G + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo2, "actionpack", "2.3.2" + c.checksum gem_repo2, "activesupport", "2.3.2" + c.checksum gem_repo2, "rack", "1.0.0" + c.checksum gem_repo2, "rack-obama", "1.0" + c.checksum gem_repo2, "thin", "1.0" + end + + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -709,7 +853,7 @@ RSpec.describe "the lockfile format" do actionpack rack-obama thin - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G @@ -722,7 +866,17 @@ RSpec.describe "the lockfile format" do gem "rails" G - lockfile_should_be <<-G + checksums = checksums_section_when_existing 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: #{file_uri_for(gem_repo2)}/ specs: @@ -740,27 +894,42 @@ RSpec.describe "the lockfile format" 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}) 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/rubygems/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 "#{file_uri_for(gem_repo2)}/" gem 'double_deps' G - lockfile_should_be <<-G + checksums = checksums_section_when_existing 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: #{file_uri_for(gem_repo2)}/ specs: @@ -774,7 +943,7 @@ RSpec.describe "the lockfile format" do DEPENDENCIES double_deps - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G @@ -787,7 +956,12 @@ RSpec.describe "the lockfile format" do gem "rack-obama", ">= 1.0", :require => "rack/obama" G - lockfile_should_be <<-G + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo2, "rack", "1.0.0" + c.checksum gem_repo2, "rack-obama", "1.0" + end + + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -800,7 +974,7 @@ RSpec.describe "the lockfile format" do DEPENDENCIES rack-obama (>= 1.0) - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G @@ -813,7 +987,12 @@ RSpec.describe "the lockfile format" do gem "rack-obama", ">= 1.0", :group => :test G - lockfile_should_be <<-G + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo2, "rack", "1.0.0" + c.checksum gem_repo2, "rack-obama", "1.0" + end + + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -826,14 +1005,18 @@ RSpec.describe "the lockfile format" do DEPENDENCIES rack-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") + build_lib "foo", path: bundled_app("foo") + + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + end install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -842,7 +1025,7 @@ RSpec.describe "the lockfile format" do end G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G PATH remote: foo specs: @@ -857,14 +1040,18 @@ RSpec.describe "the lockfile format" do 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")) + build_lib "foo", path: bundled_app(File.join("..", "foo")) + + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + end install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -873,7 +1060,7 @@ RSpec.describe "the lockfile format" do end G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G PATH remote: ../foo specs: @@ -888,23 +1075,27 @@ RSpec.describe "the lockfile format" do 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") + build_lib "foo", path: bundled_app("foo") + + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + end install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - path File.expand_path("../foo", __FILE__) do + path File.expand_path("foo", __dir__) do gem "foo" end G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G PATH remote: foo specs: @@ -919,21 +1110,25 @@ RSpec.describe "the lockfile format" do 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.join("foo")) + build_lib("foo", path: tmp.join("foo")) + + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "1.0" + end install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gemspec :path => "../foo" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G PATH remote: ../foo specs: @@ -948,13 +1143,17 @@ RSpec.describe "the lockfile format" do DEPENDENCIES foo! - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G end it "keeps existing platforms in the lockfile" do + checksums = checksums_section_when_existing do |c| + c.no_checksum "rack", "1.0.0" + end + lockfile <<-G GEM remote: #{file_uri_for(gem_repo2)}/ @@ -966,7 +1165,7 @@ RSpec.describe "the lockfile format" do DEPENDENCIES rack - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G @@ -977,24 +1176,65 @@ RSpec.describe "the lockfile format" do gem "rack" G - lockfile_should_be <<-G + checksums.checksum(gem_repo2, "rack", "1.0.0") + + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: rack (1.0.0) PLATFORMS - java - #{lockfile_platforms} + #{lockfile_platforms("java", local_platform, defaults: [])} DEPENDENCIES rack - + #{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 "#{file_uri_for(gem_repo4)}" + gem "google-protobuf" + G + bundle "lock --add-platform x64-mingw-ucrt" + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(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 + + 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| @@ -1009,7 +1249,11 @@ RSpec.describe "the lockfile format" do gem "platform_specific" G - lockfile_should_be <<-G + checksums = checksums_section_when_existing do |c| + c.checksum gem_repo2, "platform_specific", "1.0", "universal-java-16" + end + + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -1020,13 +1264,18 @@ RSpec.describe "the lockfile format" do DEPENDENCIES platform_specific - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G end it "does not add duplicate gems" do + checksums = checksums_section_when_existing do |c| + c.checksum(gem_repo2, "activesupport", "2.3.5") + c.checksum(gem_repo2, "rack", "1.0.0") + end + install_gemfile <<-G source "#{file_uri_for(gem_repo2)}/" gem "rack" @@ -1038,7 +1287,7 @@ RSpec.describe "the lockfile format" do gem "activesupport" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -1051,20 +1300,24 @@ RSpec.describe "the lockfile format" do DEPENDENCIES activesupport rack - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G end it "does not add duplicate dependencies" do + checksums = checksums_section_when_existing do |c| + c.checksum(gem_repo2, "rack", "1.0.0") + end + install_gemfile <<-G source "#{file_uri_for(gem_repo2)}/" gem "rack" gem "rack" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -1075,20 +1328,24 @@ RSpec.describe "the lockfile format" do DEPENDENCIES rack - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G end it "does not add duplicate dependencies with versions" do + checksums = checksums_section_when_existing do |c| + c.checksum(gem_repo2, "rack", "1.0.0") + end + install_gemfile <<-G source "#{file_uri_for(gem_repo2)}/" gem "rack", "1.0" gem "rack", "1.0" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -1099,20 +1356,24 @@ RSpec.describe "the lockfile format" do DEPENDENCIES rack (= 1.0) - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G end it "does not add duplicate dependencies in different groups" do + checksums = checksums_section_when_existing do |c| + c.checksum(gem_repo2, "rack", "1.0.0") + end + install_gemfile <<-G source "#{file_uri_for(gem_repo2)}/" gem "rack", "1.0", :group => :one gem "rack", "1.0", :group => :two G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -1123,14 +1384,14 @@ RSpec.describe "the lockfile format" do DEPENDENCIES rack (= 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 + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}/" gem "rack", "1.0" gem "rack", "1.1" @@ -1141,7 +1402,7 @@ RSpec.describe "the lockfile format" do end it "raises if two different sources are used" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}/" gem "rack" gem "rack", :git => "git://hubz.com" @@ -1152,12 +1413,16 @@ RSpec.describe "the lockfile format" do end it "works correctly with multiple version dependencies" do + checksums = checksums_section_when_existing do |c| + c.checksum(gem_repo2, "rack", "0.9.1") + end + install_gemfile <<-G source "#{file_uri_for(gem_repo2)}/" gem "rack", "> 0.9", "< 1.0" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -1168,20 +1433,24 @@ RSpec.describe "the lockfile format" do DEPENDENCIES rack (> 0.9, < 1.0) - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G end it "captures the Ruby version in the lockfile" do + checksums = checksums_section_when_existing do |c| + c.checksum(gem_repo2, "rack", "0.9.1") + end + install_gemfile <<-G source "#{file_uri_for(gem_repo2)}/" - ruby '#{RUBY_VERSION}' + ruby '#{Gem.ruby_version}' gem "rack", "> 0.9", "< 1.0" G - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GEM remote: #{file_uri_for(gem_repo2)}/ specs: @@ -1192,9 +1461,9 @@ RSpec.describe "the lockfile format" do DEPENDENCIES rack (> 0.9, < 1.0) - + #{checksums} RUBY VERSION - ruby #{RUBY_VERSION}p#{RUBY_PATCHLEVEL} + #{Bundler::RubyVersion.system} BUNDLED WITH #{Bundler::VERSION} @@ -1215,13 +1484,184 @@ RSpec.describe "the lockfile format" do rack_middleware L - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}" gem "rack_middleware" G expect(err).to include("Downloading rack_middleware-1.0 revealed dependencies not in the API or the lockfile (#{Gem::Dependency.new("rack", "= 0.9.1")})."). - and include("Either installing with `--full-index` or running `bundle update rack_middleware` should fix the problem.") + and include("Running `bundle update rack_middleware` should fix the problem.") + 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: #{file_uri_for(gem_repo4)}/ + specs: + + PLATFORMS + ruby + + DEPENDENCIES + direct_dependency + + BUNDLED WITH + #{Bundler::VERSION} + G + + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + + gem "direct_dependency" + G + + expect(lockfile).to eq <<~G + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + direct_dependency (4.5.6) + indirect_dependency + indirect_dependency (1.2.3) + + PLATFORMS + #{lockfile_platforms("ruby", generic_local_platform, defaults: [])} + + DEPENDENCIES + direct_dependency + + BUNDLED WITH + #{Bundler::VERSION} + G + 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 "#{file_uri_for(gem_repo4)}" + gem "minitest-bisect" + G + + # Corrupt lockfile (completely missing path_expander) + lockfile <<~L + GEM + remote: #{file_uri_for(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: #{file_uri_for(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 "#{file_uri_for(gem_repo4)}" + gem "minitest-bisect" + G + + lockfile <<~L + GEM + remote: #{file_uri_for(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 lock file includes \"minitest-bisect\" but not some of its dependencies") + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(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 @@ -1252,7 +1692,7 @@ RSpec.describe "the lockfile format" do end end - expect { bundle "update", :all => true }.to change { File.mtime(bundled_app_lock) } + 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 "rack 1.2" end @@ -1270,12 +1710,10 @@ RSpec.describe "the lockfile format" do 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 { bundle "update", all: true }.to change { File.mtime(bundled_app_lock) } expect(File.read(bundled_app_lock)).to match("\r\n") - simulate_bundler_version_when_missing_prerelease_default_gem_activation do - expect(the_bundle).to include_gems "rack 1.2" - end + expect(the_bundle).to include_gems "rack 1.2" end end @@ -1296,7 +1734,7 @@ RSpec.describe "the lockfile format" do expect do ruby <<-RUBY - require '#{entrypoint}' + require 'bundler' Bundler.setup RUBY end.not_to change { File.mtime(bundled_app_lock) } @@ -1325,7 +1763,7 @@ RSpec.describe "the lockfile format" do #{Bundler::VERSION} L - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false source "#{file_uri_for(gem_repo2)}/" gem "rack" G diff --git a/spec/bundler/other/cli_dispatch_spec.rb b/spec/bundler/other/cli_dispatch_spec.rb index 2d6080296f..48b285045a 100644 --- a/spec/bundler/other/cli_dispatch_spec.rb +++ b/spec/bundler/other/cli_dispatch_spec.rb @@ -2,19 +2,19 @@ RSpec.describe "bundle command names" do it "work when given fully" do - bundle "install", :raise_on_error => false + bundle "install", raise_on_error: false expect(err).to eq("Could not locate Gemfile") expect(last_command.stdboth).not_to include("Ambiguous command") end it "work when not ambiguous" do - bundle "ins", :raise_on_error => false + bundle "ins", raise_on_error: false expect(err).to eq("Could not locate Gemfile") expect(last_command.stdboth).not_to include("Ambiguous command") end it "print a friendly error when ambiguous" do - bundle "in", :raise_on_error => false + bundle "in", raise_on_error: false expect(err).to eq("Ambiguous command in matches [info, init, inject, install]") end end diff --git a/spec/bundler/other/ext_spec.rb b/spec/bundler/other/ext_spec.rb index e13f62a856..4d954b474f 100644 --- a/spec/bundler/other/ext_spec.rb +++ b/spec/bundler/other/ext_spec.rb @@ -44,6 +44,10 @@ RSpec.describe "Bundler::GemHelpers#generic" do expect(generic(pl("x64-mingw32"))).to eq(pl("x64-mingw32")) expect(generic(pl("x86_64-mingw32"))).to eq(pl("x64-mingw32")) end + + it "converts 64-bit mingw UCRT platform variants into x64-mingw-ucrt" do + expect(generic(pl("x64-mingw-ucrt"))).to eq(pl("x64-mingw-ucrt")) + end end RSpec.describe "Gem::SourceIndex#refresh!" do @@ -55,7 +59,27 @@ RSpec.describe "Gem::SourceIndex#refresh!" do end it "does not explode when called" do - run "Gem.source_index.refresh!", :raise_on_error => false - run "Gem::SourceIndex.new([]).refresh!", :raise_on_error => false + run "Gem.source_index.refresh!", raise_on_error: false + run "Gem::SourceIndex.new([]).refresh!", raise_on_error: false + end +end + +RSpec.describe "Gem::NameTuple" do + describe "#initialize" do + it "creates a Gem::NameTuple with equality regardless of platform type" do + gem_platform = Gem::NameTuple.new "a", v("1"), pl("x86_64-linux") + str_platform = Gem::NameTuple.new "a", v("1"), "x86_64-linux" + expect(gem_platform).to eq(str_platform) + expect(gem_platform.hash).to eq(str_platform.hash) + expect(gem_platform.to_a).to eq(str_platform.to_a) + end + end + + describe "#lock_name" do + it "returns the lock name" do + expect(Gem::NameTuple.new("a", v("1.0.0"), pl("x86_64-linux")).lock_name).to eq("a (1.0.0-x86_64-linux)") + expect(Gem::NameTuple.new("a", v("1.0.0"), "ruby").lock_name).to eq("a (1.0.0)") + expect(Gem::NameTuple.new("a", v("1.0.0")).lock_name).to eq("a (1.0.0)") + end end end diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb index 688efbcec8..939b68a0bb 100644 --- a/spec/bundler/other/major_deprecation_spec.rb +++ b/spec/bundler/other/major_deprecation_spec.rb @@ -17,14 +17,14 @@ RSpec.describe "major deprecations" do bundle "exec ruby -e #{source.dump}" end - it "is deprecated in favor of .unbundled_env", :bundler => "< 3" do + it "is deprecated in favor of .unbundled_env", bundler: "< 3" do expect(deprecations).to include \ "`Bundler.clean_env` has been deprecated in favor of `Bundler.unbundled_env`. " \ "If you instead want the environment before bundler was originally loaded, use `Bundler.original_env` " \ "(called at -e:1)" end - pending "is removed and shows a helpful error message about it", :bundler => "3" + pending "is removed and shows a helpful error message about it", bundler: "3" end describe ".with_clean_env" do @@ -33,7 +33,7 @@ RSpec.describe "major deprecations" do bundle "exec ruby -e #{source.dump}" end - it "is deprecated in favor of .unbundled_env", :bundler => "< 3" do + it "is deprecated in favor of .unbundled_env", bundler: "< 3" do expect(deprecations).to include( "`Bundler.with_clean_env` has been deprecated in favor of `Bundler.with_unbundled_env`. " \ "If you instead want the environment before bundler was originally loaded, use `Bundler.with_original_env` " \ @@ -41,7 +41,7 @@ RSpec.describe "major deprecations" do ) end - pending "is removed and shows a helpful error message about it", :bundler => "3" + pending "is removed and shows a helpful error message about it", bundler: "3" end describe ".clean_system" do @@ -50,7 +50,7 @@ RSpec.describe "major deprecations" do bundle "exec ruby -e #{source.dump}" end - it "is deprecated in favor of .unbundled_system", :bundler => "< 3" do + it "is deprecated in favor of .unbundled_system", bundler: "< 3" do expect(deprecations).to include( "`Bundler.clean_system` has been deprecated in favor of `Bundler.unbundled_system`. " \ "If you instead want to run the command in the environment before bundler was originally loaded, use `Bundler.original_system` " \ @@ -58,7 +58,7 @@ RSpec.describe "major deprecations" do ) end - pending "is removed and shows a helpful error message about it", :bundler => "3" + pending "is removed and shows a helpful error message about it", bundler: "3" end describe ".clean_exec" do @@ -67,7 +67,7 @@ RSpec.describe "major deprecations" do bundle "exec ruby -e #{source.dump}" end - it "is deprecated in favor of .unbundled_exec", :bundler => "< 3" do + it "is deprecated in favor of .unbundled_exec", bundler: "< 3" do expect(deprecations).to include( "`Bundler.clean_exec` has been deprecated in favor of `Bundler.unbundled_exec`. " \ "If you instead want to exec to a command in the environment before bundler was originally loaded, use `Bundler.original_exec` " \ @@ -75,7 +75,7 @@ RSpec.describe "major deprecations" do ) end - pending "is removed and shows a helpful error message about it", :bundler => "3" + pending "is removed and shows a helpful error message about it", bundler: "3" end describe ".environment" do @@ -84,17 +84,29 @@ RSpec.describe "major deprecations" do bundle "exec ruby -e #{source.dump}" end - it "is deprecated in favor of .load", :bundler => "< 3" do + it "is deprecated in favor of .load", bundler: "< 3" do expect(deprecations).to include "Bundler.environment has been removed in favor of Bundler.load (called at -e:1)" end - pending "is removed and shows a helpful error message about it", :bundler => "3" + pending "is removed and shows a helpful error message about it", bundler: "3" end end + describe "bundle exec --no-keep-file-descriptors" do + before do + bundle "exec --no-keep-file-descriptors -e 1", raise_on_error: false + end + + it "is deprecated", bundler: "< 3" do + expect(deprecations).to include "The `--no-keep-file-descriptors` has been deprecated. `bundle exec` no longer mess with your file descriptors. Close them in the exec'd script if you need to" + end + + pending "is removed and shows a helpful error message about it", bundler: "3" + end + describe "bundle update --quiet" do it "does not print any deprecations" do - bundle :update, :quiet => true, :raise_on_error => false + bundle :update, quiet: true, raise_on_error: false expect(deprecations).to be_empty end end @@ -106,19 +118,19 @@ RSpec.describe "major deprecations" do gem "rack" G - bundle "check --path vendor/bundle", :raise_on_error => false + bundle "check --path vendor/bundle", raise_on_error: false end - it "should print a deprecation warning", :bundler => "< 3" do + it "should print a deprecation warning", bundler: "< 3" do expect(deprecations).to include( "The `--path` flag is deprecated because it relies on being " \ "remembered across bundler invocations, which bundler will no " \ - "longer do in future versions. Instead please use `bundle config set --local " \ + "longer do in future versions. Instead please use `bundle config set " \ "path 'vendor/bundle'`, and stop using this flag" ) end - pending "fails with a helpful error", :bundler => "3" + pending "fails with a helpful error", bundler: "3" end context "bundle check --path=" do @@ -128,19 +140,19 @@ RSpec.describe "major deprecations" do gem "rack" G - bundle "check --path=vendor/bundle", :raise_on_error => false + bundle "check --path=vendor/bundle", raise_on_error: false end - it "should print a deprecation warning", :bundler => "< 3" do + it "should print a deprecation warning", bundler: "< 3" do expect(deprecations).to include( "The `--path` flag is deprecated because it relies on being " \ "remembered across bundler invocations, which bundler will no " \ - "longer do in future versions. Instead please use `bundle config set --local " \ + "longer do in future versions. Instead please use `bundle config set " \ "path 'vendor/bundle'`, and stop using this flag" ) end - pending "fails with a helpful error", :bundler => "3" + pending "fails with a helpful error", bundler: "3" end context "bundle cache --all" do @@ -150,10 +162,10 @@ RSpec.describe "major deprecations" do gem "rack" G - bundle "cache --all", :raise_on_error => false + bundle "cache --all", raise_on_error: false end - it "should print a deprecation warning", :bundler => "< 3" do + it "should print a deprecation warning", bundler: "< 3" do expect(deprecations).to include( "The `--all` flag is deprecated because it relies on being " \ "remembered across bundler invocations, which bundler will no " \ @@ -162,7 +174,7 @@ RSpec.describe "major deprecations" do ) end - pending "fails with a helpful error", :bundler => "3" + pending "fails with a helpful error", bundler: "3" end context "bundle cache --path" do @@ -172,10 +184,10 @@ RSpec.describe "major deprecations" do gem "rack" G - bundle "cache --path foo", :raise_on_error => false + bundle "cache --path foo", raise_on_error: false end - it "should print a deprecation warning", :bundler => "< 3" do + it "should print a deprecation warning", bundler: "< 3" do expect(deprecations).to include( "The `--path` flag is deprecated because its semantics are unclear. " \ "Use `bundle config cache_path` to configure the path of your cache of gems, " \ @@ -184,7 +196,7 @@ RSpec.describe "major deprecations" do ) end - pending "fails with a helpful error", :bundler => "3" + pending "fails with a helpful error", bundler: "3" end describe "bundle config" do @@ -193,11 +205,11 @@ RSpec.describe "major deprecations" do bundle "config" end - it "warns", :bundler => "3" do + it "warns", bundler: "3" do expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config list` instead.") end - pending "fails with a helpful error", :bundler => "3" + pending "fails with a helpful error", bundler: "3" end describe "old get interface" do @@ -205,11 +217,11 @@ RSpec.describe "major deprecations" do bundle "config waka" end - it "warns", :bundler => "3" do + it "warns", bundler: "3" do expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config get waka` instead.") end - pending "fails with a helpful error", :bundler => "3" + pending "fails with a helpful error", bundler: "3" end describe "old set interface" do @@ -217,11 +229,11 @@ RSpec.describe "major deprecations" do bundle "config waka wakapun" end - it "warns", :bundler => "3" do + it "warns", bundler: "3" do expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config set waka wakapun` instead.") end - pending "fails with a helpful error", :bundler => "3" + pending "fails with a helpful error", bundler: "3" end describe "old set interface with --local" do @@ -229,11 +241,11 @@ RSpec.describe "major deprecations" do bundle "config --local waka wakapun" end - it "warns", :bundler => "3" do + it "warns", bundler: "3" do expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config set --local waka wakapun` instead.") end - pending "fails with a helpful error", :bundler => "3" + pending "fails with a helpful error", bundler: "3" end describe "old set interface with --global" do @@ -241,11 +253,11 @@ RSpec.describe "major deprecations" do bundle "config --global waka wakapun" end - it "warns", :bundler => "3" do + it "warns", bundler: "3" do expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config set --global waka wakapun` instead.") end - pending "fails with a helpful error", :bundler => "3" + pending "fails with a helpful error", bundler: "3" end describe "old unset interface" do @@ -253,11 +265,11 @@ RSpec.describe "major deprecations" do bundle "config --delete waka" end - it "warns", :bundler => "3" do + it "warns", bundler: "3" do expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config unset waka` instead.") end - pending "fails with a helpful error", :bundler => "3" + pending "fails with a helpful error", bundler: "3" end describe "old unset interface with --local" do @@ -265,11 +277,11 @@ RSpec.describe "major deprecations" do bundle "config --delete --local waka" end - it "warns", :bundler => "3" do + it "warns", bundler: "3" do expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config unset --local waka` instead.") end - pending "fails with a helpful error", :bundler => "3" + pending "fails with a helpful error", bundler: "3" end describe "old unset interface with --global" do @@ -277,11 +289,11 @@ RSpec.describe "major deprecations" do bundle "config --delete --global waka" end - it "warns", :bundler => "3" do + it "warns", bundler: "3" do expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config unset --global waka` instead.") end - pending "fails with a helpful error", :bundler => "3" + pending "fails with a helpful error", bundler: "3" end end @@ -293,12 +305,12 @@ RSpec.describe "major deprecations" do G end - it "warns when no options are given", :bundler => "3" do + it "warns when no options are given", bundler: "3" do bundle "update" expect(deprecations).to include("Pass --all to `bundle update` to update everything") end - pending "fails with a helpful error when no options are given", :bundler => "3" + pending "fails with a helpful error when no options are given", bundler: "3" it "does not warn when --all is passed" do bundle "update --all" @@ -308,17 +320,17 @@ RSpec.describe "major deprecations" do describe "bundle install --binstubs" do before do - install_gemfile <<-G, :binstubs => true + install_gemfile <<-G, binstubs: true source "#{file_uri_for(gem_repo1)}" gem "rack" G end - it "should output a deprecation warning", :bundler => "< 3" do + it "should output a deprecation warning", bundler: "< 3" do expect(deprecations).to include("The --binstubs option will be removed in favor of `bundle binstubs --all`") end - pending "fails with a helpful error", :bundler => "3" + pending "fails with a helpful error", bundler: "3" end context "bundle install with both gems.rb and Gemfile present" do @@ -340,7 +352,7 @@ RSpec.describe "major deprecations" do G expect(warnings).to include( - "Multiple gemfiles (gems.rb and Gemfile) detected. Make sure you remove Gemfile and Gemfile.lock since bundler is ignoring them in favor of gems.rb and gems.rb.locked." + "Multiple gemfiles (gems.rb and Gemfile) detected. Make sure you remove Gemfile and Gemfile.lock since bundler is ignoring them in favor of gems.rb and gems.locked." ) expect(the_bundle).not_to include_gem "rack 1.0" @@ -358,16 +370,16 @@ RSpec.describe "major deprecations" do end { - "clean" => ["clean", true], - "deployment" => ["deployment", true], - "frozen" => ["frozen", true], - "no-deployment" => ["deployment", false], - "no-prune" => ["no_prune", true], - "path" => ["path", "vendor/bundle"], - "shebang" => ["shebang", "ruby27"], - "system" => ["system", true], - "without" => ["without", "development"], - "with" => ["with", "development"], + "clean" => ["clean", "true"], + "deployment" => ["deployment", "true"], + "frozen" => ["frozen", "true"], + "no-deployment" => ["deployment", "false"], + "no-prune" => ["no_prune", "true"], + "path" => ["path", "'vendor/bundle'"], + "shebang" => ["shebang", "'ruby27'"], + "system" => ["path.system", "true"], + "without" => ["without", "'development'"], + "with" => ["with", "'development'"], }.each do |name, expectations| option_name, value = *expectations flag_name = "--#{name}" @@ -378,16 +390,16 @@ RSpec.describe "major deprecations" do bundle "install #{flag_name} #{value}" end - it "should print a deprecation warning", :bundler => "< 3" do + it "should print a deprecation warning", bundler: "< 3" do expect(deprecations).to include( "The `#{flag_name}` flag is deprecated because it relies on " \ "being remembered across bundler invocations, which bundler " \ "will no longer do in future versions. Instead please use " \ - "`bundle config set --local #{option_name} '#{value}'`, and stop using this flag" + "`bundle config set #{option_name} #{value}`, and stop using this flag" ) end - pending "fails with a helpful error", :bundler => "3" + pending "fails with a helpful error", bundler: "3" end end end @@ -400,20 +412,20 @@ RSpec.describe "major deprecations" do G end - it "shows a deprecation", :bundler => "< 3" do + it "shows a deprecation", bundler: "< 3" do expect(deprecations).to include( - "Your Gemfile contains multiple primary sources. " \ + "Your Gemfile contains multiple global sources. " \ "Using `source` more than once without a block is a security risk, and " \ "may result in installing unexpected gems. To resolve this warning, use " \ "a block to indicate which gems should come from the secondary source." ) end - it "doesn't show lockfile deprecations if there's a lockfile", :bundler => "< 3" do + it "doesn't show lockfile deprecations if there's a lockfile", bundler: "< 3" do bundle "install" expect(deprecations).to include( - "Your Gemfile contains multiple primary sources. " \ + "Your Gemfile contains multiple global sources. " \ "Using `source` more than once without a block is a security risk, and " \ "may result in installing unexpected gems. To resolve this warning, use " \ "a block to indicate which gems should come from the secondary source." @@ -426,7 +438,7 @@ RSpec.describe "major deprecations" do bundle "install" expect(deprecations).to include( - "Your Gemfile contains multiple primary sources. " \ + "Your Gemfile contains multiple global sources. " \ "Using `source` more than once without a block is a security risk, and " \ "may result in installing unexpected gems. To resolve this warning, use " \ "a block to indicate which gems should come from the secondary source." @@ -437,7 +449,7 @@ RSpec.describe "major deprecations" do ) end - pending "fails with a helpful error", :bundler => "3" + pending "fails with a helpful error", bundler: "3" end context "bundle install in frozen mode with a lockfile with a single rubygems section with multiple remotes" do @@ -473,13 +485,13 @@ RSpec.describe "major deprecations" do bundle "config set --local frozen true" end - it "shows a deprecation", :bundler => "< 3" do + it "shows a deprecation", bundler: "< 3" do bundle "install" expect(deprecations).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure.") end - pending "fails with a helpful error", :bundler => "3" + pending "fails with a helpful error", bundler: "3" end context "when Bundler.setup is run in a ruby script" do @@ -491,7 +503,7 @@ RSpec.describe "major deprecations" do G ruby <<-RUBY - require '#{entrypoint}' + require 'bundler' Bundler.setup Bundler.setup @@ -500,153 +512,119 @@ RSpec.describe "major deprecations" do it "should print a single deprecation warning" do expect(warnings).to include( - "Multiple gemfiles (gems.rb and Gemfile) detected. Make sure you remove Gemfile and Gemfile.lock since bundler is ignoring them in favor of gems.rb and gems.rb.locked." + "Multiple gemfiles (gems.rb and Gemfile) detected. Make sure you remove Gemfile and Gemfile.lock since bundler is ignoring them in favor of gems.rb and gems.locked." ) end end context "when `bundler/deployment` is required in a ruby script" do before do - ruby(<<-RUBY, :env => env_for_missing_prerelease_default_gem_activation) + ruby <<-RUBY require 'bundler/deployment' RUBY end - it "should print a capistrano deprecation warning", :bundler => "< 3" do + it "should print a capistrano deprecation warning", bundler: "< 3" do expect(deprecations).to include("Bundler no longer integrates " \ "with Capistrano, but Capistrano provides " \ "its own integration with Bundler via the " \ "capistrano-bundler gem. Use it instead.") end - pending "fails with a helpful error", :bundler => "3" + pending "fails with a helpful error", bundler: "3" end - describe Bundler::Dsl do + context "bundle show" do before do - @rubygems = double("rubygems") - allow(Bundler::Source::Rubygems).to receive(:new) { @rubygems } - end - - context "with github gems" do - it "does not warn about removal", :bundler => "< 3" do - expect(Bundler.ui).not_to receive(:warn) - subject.gem("sparks", :github => "indirect/sparks") - github_uri = "https://github.com/indirect/sparks.git" - expect(subject.dependencies.first.source.uri).to eq(github_uri) - end - - it "warns about removal", :bundler => "3" do - msg = <<-EOS -The :github git source is deprecated, and will be removed in the future. Change any "reponame" :github sources to "username/reponame". Add this code to the top of your Gemfile to ensure it continues to work: - - git_source(:github) {|repo_name| "https://github.com/\#{repo_name}.git" } - - EOS - expect(Bundler.ui).to receive(:warn).with("[DEPRECATED] #{msg}") - subject.gem("sparks", :github => "indirect/sparks") - github_uri = "https://github.com/indirect/sparks.git" - expect(subject.dependencies.first.source.uri).to eq(github_uri) - end - end - - context "with bitbucket gems" do - it "does not warn about removal", :bundler => "< 3" do - expect(Bundler.ui).not_to receive(:warn) - subject.gem("not-really-a-gem", :bitbucket => "mcorp/flatlab-rails") - end - - it "warns about removal", :bundler => "3" do - msg = <<-EOS -The :bitbucket git source is deprecated, and will be removed in the future. Add this code to the top of your Gemfile to ensure it continues to work: - - git_source(:bitbucket) do |repo_name| - user_name, repo_name = repo_name.split("/") - repo_name ||= user_name - "https://\#{user_name}@bitbucket.org/\#{user_name}/\#{repo_name}.git" + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "rack" + G end - EOS - expect(Bundler.ui).to receive(:warn).with("[DEPRECATED] #{msg}") - subject.gem("not-really-a-gem", :bitbucket => "mcorp/flatlab-rails") + context "with --outdated flag" do + before do + bundle "show --outdated" end - end - context "with gist gems" do - it "does not warn about removal", :bundler => "< 3" do - expect(Bundler.ui).not_to receive(:warn) - subject.gem("not-really-a-gem", :gist => "1234") + it "prints a deprecation warning informing about its removal", bundler: "< 3" do + expect(deprecations).to include("the `--outdated` flag to `bundle show` was undocumented and will be removed without replacement") end - it "warns about removal", :bundler => "3" do - msg = <<-EOS -The :gist git source is deprecated, and will be removed in the future. Add this code to the top of your Gemfile to ensure it continues to work: - - git_source(:gist) {|repo_name| "https://gist.github.com/\#{repo_name}.git" } - - EOS - expect(Bundler.ui).to receive(:warn).with("[DEPRECATED] #{msg}") - subject.gem("not-really-a-gem", :gist => "1234") - end + pending "fails with a helpful message", bundler: "3" end end - context "bundle show" do + context "bundle remove" do before do - install_gemfile <<-G + gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" G end - context "with --outdated flag" do - before do - bundle "show --outdated" - end + context "with --install" do + it "shows a deprecation warning", bundler: "< 3" do + bundle "remove rack --install" - it "prints a deprecation warning informing about its removal", :bundler => "< 3" do - expect(deprecations).to include("the `--outdated` flag to `bundle show` was undocumented and will be removed without replacement") + expect(err).to include "[DEPRECATED] The `--install` flag has been deprecated. `bundle install` is triggered by default." end - pending "fails with a helpful message", :bundler => "3" + pending "fails with a helpful message", bundler: "3" end end context "bundle console" do before do - bundle "console", :raise_on_error => false + bundle "console", raise_on_error: false end - it "prints a deprecation warning", :bundler => "< 3" do + it "prints a deprecation warning", bundler: "< 3" do expect(deprecations).to include \ "bundle console will be replaced by `bin/console` generated by `bundle gem <name>`" end - pending "fails with a helpful message", :bundler => "3" + pending "fails with a helpful message", bundler: "3" end - context "bundle viz" do + context "bundle viz", :realworld do before do - graphviz_version = RUBY_VERSION >= "2.4" ? "1.2.5" : "1.2.4" - realworld_system_gems "ruby-graphviz --version #{graphviz_version}" + realworld_system_gems "ruby-graphviz --version 1.2.5" create_file "gems.rb", "source \"#{file_uri_for(gem_repo1)}\"" bundle "viz" end - it "prints a deprecation warning", :bundler => "< 3" do - expect(deprecations).to include "The `viz` command has been moved to the `bundle-viz` gem, see https://github.com/bundler/bundler-viz" + it "prints a deprecation warning", bundler: "< 3" do + expect(deprecations).to include "The `viz` command has been renamed to `graph` and moved to a plugin. See https://github.com/rubygems/bundler-graph" + end + + pending "fails with a helpful message", bundler: "3" + end + + context "bundle plugin install --local_git" do + before do + build_git "foo" do |s| + s.write "plugins.rb" + end + end + + it "prints a deprecation warning", bundler: "< 3" do + bundle "plugin install foo --local_git #{lib_path("foo-1.0")}" + + expect(out).to include("Installed plugin foo") + expect(deprecations).to include "--local_git is deprecated, use --git" end - pending "fails with a helpful message", :bundler => "3" + pending "fails with a helpful message", bundler: "3" end describe "deprecating rubocop", :readline do context "bundle gem --rubocop" do before do - bundle "gem my_new_gem --rubocop", :raise_on_error => false + bundle "gem my_new_gem --rubocop", raise_on_error: false end - it "prints a deprecation warning", :bundler => "< 3" do + it "prints a deprecation warning", bundler: "< 3" do expect(deprecations).to include \ "--rubocop is deprecated, use --linter=rubocop" end @@ -654,10 +632,10 @@ The :gist git source is deprecated, and will be removed in the future. Add this context "bundle gem --no-rubocop" do before do - bundle "gem my_new_gem --no-rubocop", :raise_on_error => false + bundle "gem my_new_gem --no-rubocop", raise_on_error: false end - it "prints a deprecation warning", :bundler => "< 3" do + it "prints a deprecation warning", bundler: "< 3" do expect(deprecations).to include \ "--no-rubocop is deprecated, use --linter" end @@ -665,10 +643,10 @@ The :gist git source is deprecated, and will be removed in the future. Add this context "bundle gem with gem.rubocop set to true" do before do - bundle "gem my_new_gem", :env => { "BUNDLE_GEM__RUBOCOP" => "true" }, :raise_on_error => false + bundle "gem my_new_gem", env: { "BUNDLE_GEM__RUBOCOP" => "true" }, raise_on_error: false end - it "prints a deprecation warning", :bundler => "< 3" do + it "prints a deprecation warning", bundler: "< 3" do expect(deprecations).to include \ "config gem.rubocop is deprecated; we've updated your config to use gem.linter instead" end @@ -676,10 +654,10 @@ The :gist git source is deprecated, and will be removed in the future. Add this context "bundle gem with gem.rubocop set to false" do before do - bundle "gem my_new_gem", :env => { "BUNDLE_GEM__RUBOCOP" => "false" }, :raise_on_error => false + bundle "gem my_new_gem", env: { "BUNDLE_GEM__RUBOCOP" => "false" }, raise_on_error: false end - it "prints a deprecation warning", :bundler => "< 3" do + it "prints a deprecation warning", bundler: "< 3" do expect(deprecations).to include \ "config gem.rubocop is deprecated; we've updated your config to use gem.linter instead" end diff --git a/spec/bundler/plugins/command_spec.rb b/spec/bundler/plugins/command_spec.rb index 4567a39081..af132d6550 100644 --- a/spec/bundler/plugins/command_spec.rb +++ b/spec/bundler/plugins/command_spec.rb @@ -69,12 +69,10 @@ RSpec.describe "command plugins" do end end - bundle "plugin install copycat --source #{file_uri_for(gem_repo2)}" + bundle "plugin install copycat --source #{file_uri_for(gem_repo2)}", raise_on_error: false expect(out).not_to include("Installed plugin copycat") - expect(err).to include("Failed to install the following plugins: `copycat`") - - expect(err).to include("Command(s) `mahcommand` declared by copycat are already registered.") + expect(err).to include("Failed to install plugin `copycat`, due to Bundler::Plugin::Index::CommandConflict (Command(s) `mahcommand` declared by copycat are already registered.)") end end diff --git a/spec/bundler/plugins/hook_spec.rb b/spec/bundler/plugins/hook_spec.rb index 72feb14d84..f6ee0ba210 100644 --- a/spec/bundler/plugins/hook_spec.rb +++ b/spec/bundler/plugins/hook_spec.rb @@ -106,4 +106,103 @@ RSpec.describe "hook plugins" do expect(out).to include "installed gem rack : installed" end end + + context "before-require-all hook" do + before do + build_repo2 do + build_plugin "before-require-all-plugin" do |s| + s.write "plugins.rb", <<-RUBY + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_BEFORE_REQUIRE_ALL do |deps| + puts "gems to be required \#{deps.map(&:name).join(", ")}" + end + RUBY + end + end + + bundle "plugin install before-require-all-plugin --source #{file_uri_for(gem_repo2)}" + end + + it "runs before all rubygems are required" do + install_gemfile_and_bundler_require + expect(out).to include "gems to be required rake, rack" + end + end + + context "before-require hook" do + before do + build_repo2 do + build_plugin "before-require-plugin" do |s| + s.write "plugins.rb", <<-RUBY + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_BEFORE_REQUIRE do |dep| + puts "requiring gem \#{dep.name}" + end + RUBY + end + end + + bundle "plugin install before-require-plugin --source #{file_uri_for(gem_repo2)}" + end + + it "runs before each rubygem is required" do + install_gemfile_and_bundler_require + expect(out).to include "requiring gem rake" + expect(out).to include "requiring gem rack" + end + end + + context "after-require-all hook" do + before do + build_repo2 do + build_plugin "after-require-all-plugin" do |s| + s.write "plugins.rb", <<-RUBY + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_AFTER_REQUIRE_ALL do |deps| + puts "required gems \#{deps.map(&:name).join(", ")}" + end + RUBY + end + end + + bundle "plugin install after-require-all-plugin --source #{file_uri_for(gem_repo2)}" + end + + it "runs after all rubygems are required" do + install_gemfile_and_bundler_require + expect(out).to include "required gems rake, rack" + end + end + + context "after-require hook" do + before do + build_repo2 do + build_plugin "after-require-plugin" do |s| + s.write "plugins.rb", <<-RUBY + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_AFTER_REQUIRE do |dep| + puts "required gem \#{dep.name}" + end + RUBY + end + end + + bundle "plugin install after-require-plugin --source #{file_uri_for(gem_repo2)}" + end + + it "runs after each rubygem is required" do + install_gemfile_and_bundler_require + expect(out).to include "required gem rake" + expect(out).to include "required gem rack" + end + end + + def install_gemfile_and_bundler_require + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "rake" + gem "rack" + G + + ruby <<-RUBY + require "bundler" + Bundler.require + RUBY + end end diff --git a/spec/bundler/plugins/install_spec.rb b/spec/bundler/plugins/install_spec.rb index c719e8533e..20c2f1fd26 100644 --- a/spec/bundler/plugins/install_spec.rb +++ b/spec/bundler/plugins/install_spec.rb @@ -9,7 +9,7 @@ RSpec.describe "bundler plugin install" do end it "shows proper message when gem in not found in the source" do - bundle "plugin install no-foo --source #{file_uri_for(gem_repo1)}", :raise_on_error => false + bundle "plugin install no-foo --source #{file_uri_for(gem_repo1)}", raise_on_error: false expect(err).to include("Could not find") plugin_should_not_be_installed("no-foo") @@ -22,8 +22,15 @@ RSpec.describe "bundler plugin install" do plugin_should_be_installed("foo") end + it "installs from rubygems source in frozen mode" do + bundle "plugin install foo --source #{file_uri_for(gem_repo2)}", env: { "BUNDLE_DEPLOYMENT" => "true" } + + expect(out).to include("Installed plugin foo") + plugin_should_be_installed("foo") + end + it "installs from sources configured as Gem.sources without any flags" do - bundle "plugin install foo", :env => { "BUNDLER_SPEC_GEM_SOURCES" => file_uri_for(gem_repo2).to_s } + bundle "plugin install foo", env: { "BUNDLER_SPEC_GEM_SOURCES" => file_uri_for(gem_repo2).to_s } expect(out).to include("Installed plugin foo") plugin_should_be_installed("foo") @@ -32,7 +39,8 @@ RSpec.describe "bundler plugin install" do it "shows help when --help flag is given" do bundle "plugin install --help" - expect(out).to include("bundle plugin install PLUGINS # Install the plugin from the source") + # The help message defined in ../../lib/bundler/man/bundle-plugin.1.ronn will be output. + expect(out).to include("You can install, uninstall, and list plugin(s)") end context "plugin is already installed" do @@ -69,6 +77,43 @@ RSpec.describe "bundler plugin install" do plugin_should_be_installed("foo", "kung-foo") end + it "installs the latest version if not installed" do + update_repo2 do + build_plugin "foo", "1.1" + end + + bundle "plugin install foo --version 1.0 --source #{file_uri_for(gem_repo2)} --verbose" + expect(out).to include("Installing foo 1.0") + + bundle "plugin install foo --source #{file_uri_for(gem_repo2)} --verbose" + expect(out).to include("Installing foo 1.1") + + bundle "plugin install foo --source #{file_uri_for(gem_repo2)} --verbose" + expect(out).to include("Using foo 1.1") + end + + it "raises an error when when --branch specified" do + bundle "plugin install foo --branch main --source #{file_uri_for(gem_repo2)}", raise_on_error: false + + expect(out).not_to include("Installed plugin foo") + + expect(err).to include("--branch can only be used with git sources") + end + + it "raises an error when --ref specified" do + bundle "plugin install foo --ref v1.2.3 --source #{file_uri_for(gem_repo2)}", raise_on_error: false + + expect(err).to include("--ref can only be used with git sources") + end + + it "raises error when both --branch and --ref options are specified" do + bundle "plugin install foo --source #{file_uri_for(gem_repo2)} --branch main --ref v1.2.3", raise_on_error: false + + expect(out).not_to include("Installed plugin foo") + + expect(err).to include("You cannot specify `--branch` and `--ref` at the same time.") + end + it "works with different load paths" do build_repo2 do build_plugin "testing" do |s| @@ -109,9 +154,9 @@ RSpec.describe "bundler plugin install" do build_gem "charlie" end - bundle "plugin install charlie --source #{file_uri_for(gem_repo2)}" + bundle "plugin install charlie --source #{file_uri_for(gem_repo2)}", raise_on_error: false - expect(err).to include("Failed to install the following plugins: `charlie`. The underlying error was: plugins.rb was not found") + expect(err).to include("Failed to install plugin `charlie`, due to Bundler::Plugin::MalformattedPlugin (plugins.rb was not found in the plugin.)") expect(global_plugin_gem("charlie-1.0")).not_to be_directory @@ -128,7 +173,7 @@ RSpec.describe "bundler plugin install" do end end - bundle "plugin install chaplin --source #{file_uri_for(gem_repo2)}" + bundle "plugin install chaplin --source #{file_uri_for(gem_repo2)}", raise_on_error: false expect(global_plugin_gem("chaplin-1.0")).not_to be_directory @@ -153,20 +198,58 @@ RSpec.describe "bundler plugin install" do s.write "plugins.rb" end - bundle "plugin install foo --local_git #{lib_path("foo-1.0")}" + bundle "plugin install foo --git #{lib_path("foo-1.0")}" expect(out).to include("Installed plugin foo") plugin_should_be_installed("foo") end - it "raises an error when both git and local git sources are specified" do - bundle "plugin install foo --local_git /phony/path/project --git git@gitphony.com:/repo/project", :raise_on_error => false + it "raises an error when both git and local git sources are specified", bundler: "< 3" do + bundle "plugin install foo --git /phony/path/project --local_git git@gitphony.com:/repo/project", raise_on_error: false expect(exitstatus).not_to eq(0) expect(err).to eq("Remote and local plugin git sources can't be both specified") end end + context "path plugins" do + it "installs from a path source" do + build_lib "path_plugin" do |s| + s.write "plugins.rb" + end + bundle "plugin install path_plugin --path #{lib_path("path_plugin-1.0")}" + + expect(out).to include("Installed plugin path_plugin") + plugin_should_be_installed("path_plugin") + end + + it "installs from a relative path source" do + build_lib "path_plugin" do |s| + s.write "plugins.rb" + end + path = lib_path("path_plugin-1.0").relative_path_from(bundled_app) + bundle "plugin install path_plugin --path #{path}" + + expect(out).to include("Installed plugin path_plugin") + plugin_should_be_installed("path_plugin") + end + + it "installs from a relative path source when inside an app" do + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + gemfile "" + + build_lib "ga-plugin" do |s| + s.write "plugins.rb" + end + + path = lib_path("ga-plugin-1.0").relative_path_from(bundled_app) + bundle "plugin install ga-plugin --path #{path}" + + plugin_should_be_installed("ga-plugin") + expect(local_plugin_gem("foo-1.0")).not_to be_directory + end + end + context "Gemfile eval" do before do allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) @@ -236,6 +319,21 @@ RSpec.describe "bundler plugin install" do plugin_should_be_installed("ga-plugin") end + it "accepts relative path sources" do + build_lib "ga-plugin" do |s| + s.write "plugins.rb" + end + + path = lib_path("ga-plugin-1.0").relative_path_from(bundled_app) + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + plugin 'ga-plugin', :path => "#{path}" + G + + expect(out).to include("Installed plugin ga-plugin") + plugin_should_be_installed("ga-plugin") + end + context "in deployment mode" do it "installs plugins" do install_gemfile <<-G @@ -271,7 +369,7 @@ RSpec.describe "bundler plugin install" do end RUBY - ruby code, :env => { "BUNDLER_VERSION" => Bundler::VERSION } + ruby code, env: { "BUNDLER_VERSION" => Bundler::VERSION } expect(local_plugin_gem("foo-1.0", "plugins.rb")).to exist end end @@ -321,7 +419,7 @@ RSpec.describe "bundler plugin install" do end # outside the app - bundle "plugin install fubar --source #{file_uri_for(gem_repo2)}", :dir => tmp + bundle "plugin install fubar --source #{file_uri_for(gem_repo2)}", dir: tmp end it "inside the app takes precedence over global plugin" do @@ -330,7 +428,7 @@ RSpec.describe "bundler plugin install" do end it "outside the app global plugin is used" do - bundle "shout", :dir => tmp + bundle "shout", dir: tmp expect(out).to eq("global_one") end end diff --git a/spec/bundler/plugins/source/example_spec.rb b/spec/bundler/plugins/source/example_spec.rb index e2bab9c199..e569f3e415 100644 --- a/spec/bundler/plugins/source/example_spec.rb +++ b/spec/bundler/plugins/source/example_spec.rb @@ -70,7 +70,11 @@ RSpec.describe "real source plugins" do it "writes to lock file" do bundle "install" - lockfile_should_be <<-G + checksums = checksums_section_when_existing do |c| + c.no_checksum "a-path-gem", "1.0" + end + + expect(lockfile).to eq <<~G PLUGIN SOURCE remote: #{lib_path("a-path-gem-1.0")} type: mpath @@ -86,7 +90,7 @@ RSpec.describe "real source plugins" do DEPENDENCIES a-path-gem! - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G @@ -203,7 +207,7 @@ RSpec.describe "real source plugins" do def initialize(opts) super - @ref = options["ref"] || options["branch"] || options["tag"] || "master" + @ref = options["ref"] || options["branch"] || options["tag"] || "main" @unlocked = false end @@ -247,7 +251,7 @@ RSpec.describe "real source plugins" do def options_to_lock opts = {"revision" => revision} - opts["ref"] = ref if ref != "master" + opts["ref"] = ref if ref != "main" opts end @@ -304,13 +308,7 @@ RSpec.describe "real source plugins" do @install_path ||= begin git_scope = "\#{base_name}-\#{shortref_for_path(revision)}" - path = gem_install_dir.join(git_scope) - - if !path.exist? && requires_sudo? - user_bundle_path.join(ruby_scope).join(git_scope) - else - path - end + gem_install_dir.join(git_scope) end end @@ -342,7 +340,11 @@ RSpec.describe "real source plugins" do revision = revision_for(lib_path("ma-gitp-gem-1.0")) bundle "install" - lockfile_should_be <<-G + checksums = checksums_section_when_existing do |c| + c.no_checksum "ma-gitp-gem", "1.0" + end + + expect(lockfile).to eq <<~G PLUGIN SOURCE remote: #{file_uri_for(lib_path("ma-gitp-gem-1.0"))} type: gitp @@ -359,7 +361,7 @@ RSpec.describe "real source plugins" do DEPENDENCIES ma-gitp-gem! - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G @@ -419,7 +421,7 @@ RSpec.describe "real source plugins" do end it "updates the deps on change in gemfile" do - update_git "ma-gitp-gem", "1.1", :path => lib_path("ma-gitp-gem-1.0"), :gemspec => true + update_git "ma-gitp-gem", "1.1", path: lib_path("ma-gitp-gem-1.0"), gemspec: true gemfile <<-G source "#{file_uri_for(gem_repo2)}" # plugin source source "#{file_uri_for(lib_path("ma-gitp-gem-1.0"))}", :type => :gitp do @@ -435,7 +437,7 @@ RSpec.describe "real source plugins" do describe "bundle cache with gitp" do it "copies repository to vendor cache and uses it" do git = build_git "foo" - ref = git.ref_for("master", 11) + ref = git.ref_for("main", 11) install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" # plugin source diff --git a/spec/bundler/plugins/uninstall_spec.rb b/spec/bundler/plugins/uninstall_spec.rb index 8180241911..555c6a7002 100644 --- a/spec/bundler/plugins/uninstall_spec.rb +++ b/spec/bundler/plugins/uninstall_spec.rb @@ -30,6 +30,31 @@ RSpec.describe "bundler plugin uninstall" do plugin_should_not_be_installed("foo") end + it "doesn't wipe out path plugins" do + build_lib "path_plugin" do |s| + s.write "plugins.rb" + end + path = lib_path("path_plugin-1.0") + expect(path).to be_a_directory + + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + + install_gemfile <<-G + source '#{file_uri_for(gem_repo2)}' + plugin 'path_plugin', :path => "#{path}" + gem 'rack', '1.0.0' + G + + plugin_should_be_installed("path_plugin") + expect(Bundler::Plugin.index.plugin_path("path_plugin")).to eq path + + bundle "plugin uninstall path_plugin" + expect(out).to include("Uninstalled plugin path_plugin") + plugin_should_not_be_installed("path_plugin") + # the actual gem still exists though + expect(path).to be_a_directory + end + describe "with --all" do it "uninstalls all installed plugins" do bundle "plugin install foo kung-foo --source #{file_uri_for(gem_repo2)}" diff --git a/spec/bundler/quality_es_spec.rb b/spec/bundler/quality_es_spec.rb index 90968e6270..0dbd77e451 100644 --- a/spec/bundler/quality_es_spec.rb +++ b/spec/bundler/quality_es_spec.rb @@ -41,7 +41,7 @@ RSpec.describe "La biblioteca si misma" do included = /ronn/ error_messages = [] man_tracked_files.each do |filename| - next unless filename =~ included + next unless filename&.match?(included) error_messages << check_for_expendable_words(filename) error_messages << check_for_specific_pronouns(filename) end @@ -52,7 +52,7 @@ RSpec.describe "La biblioteca si misma" do error_messages = [] exempt = /vendor/ lib_tracked_files.each do |filename| - next if filename =~ exempt + next if filename&.match?(exempt) error_messages << check_for_expendable_words(filename) error_messages << check_for_specific_pronouns(filename) end diff --git a/spec/bundler/quality_spec.rb b/spec/bundler/quality_spec.rb index 08ec8bed5c..7cdb993017 100644 --- a/spec/bundler/quality_spec.rb +++ b/spec/bundler/quality_spec.rb @@ -3,25 +3,6 @@ require "set" RSpec.describe "The library itself" do - def check_for_debugging_mechanisms(filename) - debugging_mechanisms_regex = / - (binding\.pry)| - (debugger)| - (sleep\s*\(?\d+)| - (fit\s*\(?("|\w)) - /x - - failing_lines = [] - each_line(filename) do |line, number| - if line =~ debugging_mechanisms_regex && !line.end_with?("# ignore quality_spec\n") - failing_lines << number + 1 - end - end - - return if failing_lines.empty? - "#{filename} has debugging mechanisms (like binding.pry, sleep, debugger, rspec focusing, etc.) on lines #{failing_lines.join(", ")}" - end - def check_for_git_merge_conflicts(filename) merge_conflicts_regex = / <<<<<<<| @@ -31,7 +12,7 @@ RSpec.describe "The library itself" do failing_lines = [] each_line(filename) do |line, number| - failing_lines << number + 1 if line =~ merge_conflicts_regex + failing_lines << number + 1 if line&.match?(merge_conflicts_regex) end return if failing_lines.empty? @@ -41,7 +22,7 @@ RSpec.describe "The library itself" do def check_for_tab_characters(filename) failing_lines = [] each_line(filename) do |line, number| - failing_lines << number + 1 if line =~ /\t/ + failing_lines << number + 1 if line.include?("\t") end return if failing_lines.empty? @@ -51,24 +32,22 @@ RSpec.describe "The library itself" do def check_for_extra_spaces(filename) failing_lines = [] each_line(filename) do |line, number| - next if line =~ /^\s+#.*\s+\n$/ - failing_lines << number + 1 if line =~ /\s+\n$/ + next if /^\s+#.*\s+\n$/.match?(line) + failing_lines << number + 1 if /\s+\n$/.match?(line) end return if failing_lines.empty? "#{filename} has spaces on the EOL on lines #{failing_lines.join(", ")}" end - def check_for_straneous_quotes(filename) - return if File.expand_path(filename) == __FILE__ - + def check_for_extraneous_quotes(filename) failing_lines = [] each_line(filename) do |line, number| - failing_lines << number + 1 if line =~ /’/ + failing_lines << number + 1 if /\u{2019}/.match?(line) end return if failing_lines.empty? - "#{filename} has an straneous quote on lines #{failing_lines.join(", ")}" + "#{filename} has an extraneous quote on lines #{failing_lines.join(", ")}" end def check_for_expendable_words(filename) @@ -108,29 +87,19 @@ RSpec.describe "The library itself" do exempt = /\.gitmodules|fixtures|vendor|LICENSE|vcr_cassettes|rbreadline\.diff|index\.txt$/ error_messages = [] tracked_files.each do |filename| - next if filename =~ exempt + next if filename&.match?(exempt) error_messages << check_for_tab_characters(filename) error_messages << check_for_extra_spaces(filename) end expect(error_messages.compact).to be_well_formed end - it "has no estraneous quotes" do + it "has no extraneous quotes" do exempt = /vendor|vcr_cassettes|LICENSE|rbreadline\.diff/ error_messages = [] tracked_files.each do |filename| - next if filename =~ exempt - error_messages << check_for_straneous_quotes(filename) - end - expect(error_messages.compact).to be_well_formed - end - - it "does not include any leftover debugging or development mechanisms" do - exempt = %r{quality_spec.rb|support/helpers|vcr_cassettes|\.md|\.ronn|index\.txt|\.5|\.1} - error_messages = [] - tracked_files.each do |filename| - next if filename =~ exempt - error_messages << check_for_debugging_mechanisms(filename) + next if filename&.match?(exempt) + error_messages << check_for_extraneous_quotes(filename) end expect(error_messages.compact).to be_well_formed end @@ -139,7 +108,7 @@ RSpec.describe "The library itself" do error_messages = [] exempt = %r{lock/lockfile_spec|quality_spec|vcr_cassettes|\.ronn|lockfile_parser\.rb} tracked_files.each do |filename| - next if filename =~ exempt + next if filename&.match?(exempt) error_messages << check_for_git_merge_conflicts(filename) end expect(error_messages.compact).to be_well_formed @@ -149,7 +118,7 @@ RSpec.describe "The library itself" do included = /ronn/ error_messages = [] man_tracked_files.each do |filename| - next unless filename =~ included + next unless filename&.match?(included) error_messages << check_for_expendable_words(filename) error_messages << check_for_specific_pronouns(filename) end @@ -160,7 +129,7 @@ RSpec.describe "The library itself" do error_messages = [] exempt = /vendor|vcr_cassettes|CODE_OF_CONDUCT/ lib_tracked_files.each do |filename| - next if filename =~ exempt + next if filename&.match?(exempt) error_messages << check_for_expendable_words(filename) error_messages << check_for_specific_pronouns(filename) end @@ -180,7 +149,6 @@ RSpec.describe "The library itself" do git.allow_insecure inline trust-policy - use_gem_version_promoter_for_major_updates ] all_settings = Hash.new {|h, k| h[k] = [] } @@ -223,11 +191,12 @@ RSpec.describe "The library itself" do end it "ships the correct set of files" do - git_list = git_ls_files(ruby_core? ? "lib/bundler lib/bundler.rb libexec/bundle*" : "lib exe CHANGELOG.md LICENSE.md README.md bundler.gemspec") + git_list = tracked_files.reject {|f| f.start_with?("spec/") } gem_list = loaded_gemspec.files + gem_list.map! {|f| f.sub(%r{\Aexe/}, "libexec/") } if ruby_core? - expect(git_list.sort).to eq(gem_list.sort) + expect(git_list).to match_array(gem_list) end it "does not contain any warnings" do @@ -236,7 +205,6 @@ RSpec.describe "The library itself" do lib/bundler/deployment.rb lib/bundler/gem_tasks.rb lib/bundler/vlad.rb - lib/bundler/templates/gems.rb ] files_to_require = lib_tracked_files.grep(/\.rb$/) - exclusions files_to_require.reject! {|f| f.start_with?("lib/bundler/vendor") } @@ -259,7 +227,7 @@ RSpec.describe "The library itself" do exempt = %r{templates/|\.5|\.1|vendor/} all_bad_requires = [] lib_tracked_files.each do |filename| - next if filename =~ exempt + next if filename&.match?(exempt) each_line(filename) do |line, number| line.scan(/^ *require "bundler/).each { all_bad_requires << "#{filename}:#{number.succ}" } end @@ -271,6 +239,6 @@ RSpec.describe "The library itself" do private def each_line(filename, &block) - File.readlines(filename, :encoding => "UTF-8").each_with_index(&block) + File.readlines(filename, encoding: "UTF-8").each_with_index(&block) end end diff --git a/spec/bundler/realworld/dependency_api_spec.rb b/spec/bundler/realworld/dependency_api_spec.rb index 08c6acf190..ee5c0e3d0a 100644 --- a/spec/bundler/realworld/dependency_api_spec.rb +++ b/spec/bundler/realworld/dependency_api_spec.rb @@ -2,7 +2,7 @@ require_relative "../support/silent_logger" -RSpec.describe "gemcutter's dependency API", :realworld => true do +RSpec.describe "gemcutter's dependency API", realworld: true do context "when Gemcutter API takes too long to respond" do before do require_rack @@ -13,12 +13,12 @@ RSpec.describe "gemcutter's dependency API", :realworld => true do require_relative "../support/artifice/endpoint_timeout" @t = Thread.new do - server = Rack::Server.start(:app => EndpointTimeout, - :Host => "0.0.0.0", - :Port => port, - :server => "webrick", - :AccessLog => [], - :Logger => Spec::SilentLogger.new) + server = Rack::Server.start(app: EndpointTimeout, + Host: "0.0.0.0", + Port: port, + server: "webrick", + AccessLog: [], + Logger: Spec::SilentLogger.new) server.start end @t.run @@ -34,7 +34,7 @@ RSpec.describe "gemcutter's dependency API", :realworld => true do end it "times out and falls back on the modern index" do - install_gemfile <<-G, :artifice => nil + install_gemfile <<-G, artifice: nil source "#{@server_uri}" gem "rack" G diff --git a/spec/bundler/realworld/double_check_spec.rb b/spec/bundler/realworld/double_check_spec.rb index d7f28d10bb..0741560395 100644 --- a/spec/bundler/realworld/double_check_spec.rb +++ b/spec/bundler/realworld/double_check_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe "double checking sources", :realworld => true do +RSpec.describe "double checking sources", realworld: true do it "finds already-installed gems" do create_file("rails.gemspec", <<-RUBY) Gem::Specification.new do |s| @@ -25,9 +25,9 @@ RSpec.describe "double checking sources", :realworld => true do RUBY cmd = <<-RUBY - require "#{entrypoint}" + require "bundler" require "#{spec_dir}/support/artifice/vcr" - require "#{entrypoint}/inline" + require "bundler/inline" gemfile(true) do source "https://rubygems.org" gem "rails", path: "." diff --git a/spec/bundler/realworld/edgecases_spec.rb b/spec/bundler/realworld/edgecases_spec.rb index f031e2f354..94ca3554b1 100644 --- a/spec/bundler/realworld/edgecases_spec.rb +++ b/spec/bundler/realworld/edgecases_spec.rb @@ -1,17 +1,19 @@ # frozen_string_literal: true -RSpec.describe "real world edgecases", :realworld => true do +RSpec.describe "real world edgecases", realworld: true do def rubygems_version(name, requirement) ruby <<-RUBY require "#{spec_dir}/support/artifice/vcr" - require "#{entrypoint}" - require "#{entrypoint}/source/rubygems/remote" - require "#{entrypoint}/fetcher" + require "bundler" + require "bundler/source/rubygems/remote" + require "bundler/fetcher" rubygem = Bundler.ui.silence do - source = Bundler::Source::Rubygems::Remote.new(Bundler::URI("https://rubygems.org")) - fetcher = Bundler::Fetcher.new(source) - index = fetcher.specs([#{name.dump}], nil) - index.search(Gem::Dependency.new(#{name.dump}, #{requirement.dump})).last + remote = Bundler::Source::Rubygems::Remote.new(Gem::URI("https://rubygems.org")) + source = Bundler::Source::Rubygems.new + fetcher = Bundler::Fetcher.new(remote) + index = fetcher.specs([#{name.dump}], source) + requirement = Gem::Requirement.create(#{requirement.dump}) + index.search(#{name.dump}).select {|spec| requirement.satisfied_by?(spec.version) }.last end if rubygem.nil? raise "Could not find #{name} (#{requirement}) on rubygems.org!\n" \ @@ -64,7 +66,7 @@ RSpec.describe "real world edgecases", :realworld => true do it "is able to update a top-level dependency when there is a conflict on a shared transitive child" do # from https://github.com/rubygems/bundler/issues/5031 - system_gems "bundler-2.99.0" + pristine_system_gems "bundler-1.99.0" gemfile <<-G source "https://rubygems.org" @@ -154,7 +156,7 @@ RSpec.describe "real world edgecases", :realworld => true do activemodel (= 4.2.7.1) activerecord (= 4.2.7.1) activesupport (= 4.2.7.1) - bundler (>= 1.3.0, < 3.0) + bundler (>= 1.3.0, < 2.0) railties (= 4.2.7.1) sprockets-rails rails-deprecated_sanitizer (1.0.3) @@ -191,336 +193,191 @@ RSpec.describe "real world edgecases", :realworld => true do rails (~> 4.2.7.1) L - bundle "lock --update paperclip", :env => { "BUNDLER_VERSION" => "2.99.0" } + bundle "lock --update paperclip", env: { "BUNDLER_VERSION" => "1.99.0" } expect(lockfile).to include(rubygems_version("paperclip", "~> 5.1.0")) end - it "outputs a helpful error message when gems have invalid gemspecs" do - install_gemfile <<-G, :standalone => true, :raise_on_error => false + it "outputs a helpful error message when gems have invalid gemspecs", rubygems: "< 3.3.16" do + install_gemfile <<-G, standalone: true, raise_on_error: false, env: { "BUNDLE_FORCE_RUBY_PLATFORM" => "1" } source 'https://rubygems.org' gem "resque-scheduler", "2.2.0" gem "redis-namespace", "1.6.0" # for a consistent resolution including ruby 2.3.0 + gem "ruby2_keywords", "0.0.5" G expect(err).to include("You have one or more invalid gemspecs that need to be fixed.") expect(err).to include("resque-scheduler 2.2.0 has an invalid gemspec") end - it "doesn't hang on big gemfile" do - skip "Only for ruby 2.7.3" if RUBY_VERSION != "2.7.3" || RUBY_PLATFORM =~ /darwin/ - - gemfile <<~G - # frozen_string_literal: true - - source "https://rubygems.org" - - ruby "2.7.3" - - gem "rails" - gem "pg", ">= 0.18", "< 2.0" - gem "goldiloader" - gem "awesome_nested_set" - gem "circuitbox" - gem "passenger" - gem "globalid" - gem "rack-cors" - gem "rails-pg-extras" - gem "linear_regression_trend" - gem "rack-protection" - gem "pundit" - gem "remote_ip_proxy_scrubber" - gem "bcrypt" - gem "searchkick" - gem "excon" - gem "faraday_middleware-aws-sigv4" - gem "typhoeus" - gem "sidekiq" - gem "sidekiq-undertaker" - gem "sidekiq-cron" - gem "storext" - gem "appsignal" - gem "fcm" - gem "business_time" - gem "tzinfo" - gem "holidays" - gem "bigdecimal" - gem "progress_bar" - gem "redis" - gem "hiredis" - gem "state_machines" - gem "state_machines-audit_trail" - gem "state_machines-activerecord" - gem "interactor" - gem "ar_transaction_changes" - gem "redis-rails" - gem "seed_migration" - gem "lograge" - gem "graphiql-rails", group: :development - gem "graphql" - gem "pusher" - gem "rbnacl" - gem "jwt" - gem "json-schema" - gem "discard" - gem "money" - gem "strip_attributes" - gem "validates_email_format_of" - gem "audited" - gem "concurrent-ruby" - gem "with_advisory_lock" - - group :test do - gem "rspec-sidekiq" - gem "simplecov", require: false - end - - group :development, :test do - gem "byebug", platform: :mri - gem "guard" - gem "guard-bundler" - gem "guard-rspec" - gem "rb-fsevent" - gem "rspec_junit_formatter" - gem "rspec-collection_matchers" - gem "rspec-rails" - gem "rspec-retry" - gem "state_machines-rspec" - gem "dotenv-rails" - gem "database_cleaner-active_record" - gem "database_cleaner-redis" - gem "timecop" - end - - gem "factory_bot_rails" - gem "faker" - - group :development do - gem "listen" - gem "sql_queries_count" - gem "rubocop" - gem "rubocop-performance" - gem "rubocop-rspec" - gem "rubocop-rails" - gem "brakeman" - gem "bundler-audit" - gem "solargraph" - gem "annotate" - end - G - - if Bundler.feature_flag.bundler_3_mode? - # Conflicts on bundler version, so fails earlier - bundle :lock, :env => { "DEBUG_RESOLVER" => "1" }, :raise_on_error => false - expect(out).to display_total_steps_of(435) - else - bundle :lock, :env => { "DEBUG_RESOLVER" => "1" } - expect(out).to display_total_steps_of(1025) - end - end - - it "doesn't hang on tricky gemfile" do - skip "Only for ruby 2.7.3" if RUBY_VERSION != "2.7.3" || RUBY_PLATFORM =~ /darwin/ - - gemfile <<~G + it "outputs a helpful warning when gems have a gemspec with invalid `require_paths`", rubygems: ">= 3.3.16" do + install_gemfile <<-G, standalone: true, env: { "BUNDLE_FORCE_RUBY_PLATFORM" => "1" } source 'https://rubygems.org' - - group :development do - gem "puppet-module-posix-default-r2.7", '~> 0.3' - gem "puppet-module-posix-dev-r2.7", '~> 0.3' - gem "beaker-rspec" - gem "beaker-puppet" - gem "beaker-docker" - gem "beaker-puppet_install_helper" - gem "beaker-module_install_helper" - end + gem "resque-scheduler", "2.2.0" + gem "redis-namespace", "1.6.0" # for a consistent resolution including ruby 2.3.0 + gem "ruby2_keywords", "0.0.5" G - - bundle :lock, :env => { "DEBUG_RESOLVER" => "1" } - - if Bundler.feature_flag.bundler_3_mode? - expect(out).to display_total_steps_of(890) - else - expect(out).to display_total_steps_of(891) - end + expect(err).to include("resque-scheduler 2.2.0 includes a gemspec with `require_paths` set to an array of arrays. Newer versions of this gem might've already fixed this").once end it "doesn't hang on nix gemfile" do - skip "Only for ruby 3.0.1" if RUBY_VERSION != "3.0.1" || RUBY_PLATFORM =~ /darwin/ + skip "Only for ruby 3.0" unless RUBY_VERSION.start_with?("3.0") gemfile <<~G - source "https://rubygems.org" do - gem "addressable" - gem "atk" - gem "awesome_print" - gem "bacon" - gem "byebug" - gem "cairo" - gem "cairo-gobject" - gem "camping" - gem "charlock_holmes" - gem "cld3" - gem "cocoapods" - gem "cocoapods-acknowledgements" - gem "cocoapods-art" - gem "cocoapods-bin" - gem "cocoapods-browser" - gem "cocoapods-bugsnag" - gem "cocoapods-check" - gem "cocoapods-clean" - gem "cocoapods-clean_build_phases_scripts" - gem "cocoapods-core" - gem "cocoapods-coverage" - gem "cocoapods-deintegrate" - gem "cocoapods-dependencies" - gem "cocoapods-deploy" - gem "cocoapods-downloader" - gem "cocoapods-expert-difficulty" - gem "cocoapods-fix-react-native" - gem "cocoapods-generate" - gem "cocoapods-git_url_rewriter" - gem "cocoapods-keys" - gem "cocoapods-no-dev-schemes" - gem "cocoapods-open" - gem "cocoapods-packager" - gem "cocoapods-playgrounds" - gem "cocoapods-plugins" - gem "cocoapods-prune-localizations" - gem "cocoapods-rome" - gem "cocoapods-search" - gem "cocoapods-sorted-search" - gem "cocoapods-static-swift-framework" - gem "cocoapods-stats" - gem "cocoapods-tdfire-binary" - gem "cocoapods-testing" - gem "cocoapods-trunk" - gem "cocoapods-try" - gem "cocoapods-try-release-fix" - gem "cocoapods-update-if-you-dare" - gem "cocoapods-whitelist" - gem "cocoapods-wholemodule" - gem "coderay" - gem "concurrent-ruby" - gem "curb" - gem "curses" - gem "daemons" - gem "dep-selector-libgecode" - gem "digest-sha3" - gem "domain_name" - gem "do_sqlite3" - gem "ethon" - gem "eventmachine" - gem "excon" - gem "faraday" - gem "ffi" - gem "ffi-rzmq-core" - gem "fog-dnsimple" - gem "gdk_pixbuf2" - gem "gio2" - gem "gitlab-markup" - gem "glib2" - gem "gpgme" - gem "gtk2" - gem "hashie" - gem "highline" - gem "hike" - gem "hitimes" - gem "hpricot" - gem "httpclient" - gem "http-cookie" - gem "iconv" - gem "idn-ruby" - gem "jbuilder" - gem "jekyll" - gem "jmespath" - gem "jwt" - gem "libv8" - gem "libxml-ruby" - gem "magic" - gem "markaby" - gem "method_source" - gem "mini_magick" - gem "msgpack" - gem "mysql2" - gem "ncursesw" - gem "netrc" - gem "net-scp" - gem "net-ssh" - gem "nokogiri" - gem "opus-ruby" - gem "ovirt-engine-sdk" - gem "pango" - gem "patron" - gem "pcaprub" - gem "pg" - gem "pry" - gem "pry-byebug" - gem "pry-doc" - gem "public_suffix" - gem "puma" - gem "rails" - gem "rainbow" - gem "rbnacl" - gem "rb-readline" - gem "re2" - gem "redis" - gem "redis-rack" - gem "rest-client" - gem "rmagick" - gem "rpam2" - gem "rspec" - gem "rubocop" - gem "rubocop-performance" - gem "ruby-libvirt" - gem "ruby-lxc" - gem "ruby-progressbar" - gem "ruby-terminfo" - gem "ruby-vips" - gem "rubyzip" - gem "rugged" - gem "sassc" - gem "scrypt" - gem "semian" - gem "sequel" - gem "sequel_pg" - gem "simplecov" - gem "sinatra" - gem "slop" - gem "snappy" - gem "sqlite3" - gem "taglib-ruby" - gem "thrift" - gem "tilt" - gem "tiny_tds" - gem "treetop" - gem "typhoeus" - gem "tzinfo" - gem "unf_ext" - gem "uuid4r" - gem "whois" - gem "zookeeper" - end - G - - bundle :lock, :env => { "DEBUG_RESOLVER" => "1" } - - if Bundler.feature_flag.bundler_3_mode? - expect(out).to display_total_steps_of(1874) - else - expect(out).to display_total_steps_of(1922) - end - end - - private + source "https://rubygems.org" - RSpec::Matchers.define :display_total_steps_of do |expected_steps| - match do |out| - out.include?("BUNDLER: Finished resolution (#{expected_steps} steps)") - end + gem "addressable" + gem "atk" + gem "awesome_print" + gem "bacon" + gem "byebug" + gem "cairo" + gem "cairo-gobject" + gem "camping" + gem "charlock_holmes" + gem "cld3" + gem "cocoapods" + gem "cocoapods-acknowledgements" + gem "cocoapods-art" + gem "cocoapods-bin" + gem "cocoapods-browser" + gem "cocoapods-bugsnag" + gem "cocoapods-check" + gem "cocoapods-clean" + gem "cocoapods-clean_build_phases_scripts" + gem "cocoapods-core" + gem "cocoapods-coverage" + gem "cocoapods-deintegrate" + gem "cocoapods-dependencies" + gem "cocoapods-deploy" + gem "cocoapods-downloader" + gem "cocoapods-expert-difficulty" + gem "cocoapods-fix-react-native" + gem "cocoapods-generate" + gem "cocoapods-git_url_rewriter" + gem "cocoapods-keys" + gem "cocoapods-no-dev-schemes" + gem "cocoapods-open" + gem "cocoapods-packager" + gem "cocoapods-playgrounds" + gem "cocoapods-plugins" + gem "cocoapods-prune-localizations" + gem "cocoapods-rome" + gem "cocoapods-search" + gem "cocoapods-sorted-search" + gem "cocoapods-static-swift-framework" + gem "cocoapods-stats" + gem "cocoapods-tdfire-binary" + gem "cocoapods-testing" + gem "cocoapods-trunk" + gem "cocoapods-try" + gem "cocoapods-try-release-fix" + gem "cocoapods-update-if-you-dare" + gem "cocoapods-whitelist" + gem "cocoapods-wholemodule" + gem "coderay" + gem "concurrent-ruby" + gem "curb" + gem "curses" + gem "daemons" + gem "dep-selector-libgecode" + gem "digest-sha3" + gem "domain_name" + gem "do_sqlite3" + gem "ethon" + gem "eventmachine" + gem "excon" + gem "faraday" + gem "ffi" + gem "ffi-rzmq-core" + gem "fog-dnsimple" + gem "gdk_pixbuf2" + gem "gio2" + gem "gitlab-markup" + gem "glib2" + gem "gpgme" + gem "gtk2" + gem "hashie" + gem "highline" + gem "hike" + gem "hitimes" + gem "hpricot" + gem "httpclient" + gem "http-cookie" + gem "iconv" + gem "idn-ruby" + gem "jbuilder" + gem "jekyll" + gem "jmespath" + gem "jwt" + gem "libv8" + gem "libxml-ruby" + gem "magic" + gem "markaby" + gem "method_source" + gem "mini_magick" + gem "msgpack" + gem "mysql2" + gem "ncursesw" + gem "netrc" + gem "net-scp" + gem "net-ssh" + gem "nokogiri" + gem "opus-ruby" + gem "ovirt-engine-sdk" + gem "pango" + gem "patron" + gem "pcaprub" + gem "pg" + gem "pry" + gem "pry-byebug" + gem "pry-doc" + gem "public_suffix" + gem "puma" + gem "rails" + gem "rainbow" + gem "rbnacl" + gem "rb-readline" + gem "re2" + gem "redis" + gem "redis-rack" + gem "rest-client" + gem "rmagick" + gem "rpam2" + gem "rspec" + gem "rubocop" + gem "rubocop-performance" + gem "ruby-libvirt" + gem "ruby-lxc" + gem "ruby-progressbar" + gem "ruby-terminfo" + gem "ruby-vips" + gem "rubyzip" + gem "rugged" + gem "sassc" + gem "scrypt" + gem "semian" + gem "sequel" + gem "sequel_pg" + gem "simplecov" + gem "sinatra" + gem "slop" + gem "snappy" + gem "sqlite3" + gem "taglib-ruby" + gem "thrift" + gem "tilt" + gem "tiny_tds" + gem "treetop" + gem "typhoeus" + gem "tzinfo" + gem "unf_ext" + gem "uuid4r" + gem "whois" + gem "zookeeper" + G - failure_message do |out| - actual_steps = out.scan(/BUNDLER: Finished resolution \((\d+) steps\)/).first.first + bundle :lock, env: { "DEBUG_RESOLVER" => "1" } - "Expected resolution to finish in #{expected_steps} steps, but took #{actual_steps}" - end + expect(out).to include("Solution found after 4 attempts") end end diff --git a/spec/bundler/realworld/ffi_spec.rb b/spec/bundler/realworld/ffi_spec.rb new file mode 100644 index 0000000000..5f40b43a0f --- /dev/null +++ b/spec/bundler/realworld/ffi_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +RSpec.describe "loading dynamically linked library on a bundle exec context", realworld: true do + it "passes ENV right after argv in memory" do + create_file "foo.rb", <<~RUBY + require 'ffi' + + module FOO + extend FFI::Library + ffi_lib './libfoo.so' + + attach_function :Hello, [], :void + end + + FOO.Hello() + RUBY + + create_file "libfoo.c", <<~'C' + #include <stdio.h> + + static int foo_init(int argc, char** argv, char** envp) { + if (argv[argc+1] == NULL) { + printf("FAIL\n"); + } else { + printf("OK\n"); + } + + return 0; + } + + #if defined(__APPLE__) && defined(__MACH__) + __attribute__((section("__DATA,__mod_init_func"), used, aligned(sizeof(void*)))) + #else + __attribute__((section(".init_array"))) + #endif + static void *ctr = &foo_init; + + extern char** environ; + + void Hello() { + return; + } + C + + sys_exec "gcc -g -o libfoo.so -shared -fpic libfoo.c" + + install_gemfile <<-G + source "https://rubygems.org" + + gem 'ffi' + G + + bundle "exec ruby foo.rb" + + expect(out).to eq("OK") + end +end diff --git a/spec/bundler/realworld/fixtures/warbler/Gemfile b/spec/bundler/realworld/fixtures/warbler/Gemfile index 4fbf2d05a7..a8dbb4911c 100644 --- a/spec/bundler/realworld/fixtures/warbler/Gemfile +++ b/spec/bundler/realworld/fixtures/warbler/Gemfile @@ -2,6 +2,6 @@ source "https://rubygems.org" -gem "demo", :path => "./demo" +gem "demo", path: "./demo" gem "jruby-jars", "~> 9.2" gem "warbler", "~> 2.0" diff --git a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock index 05bcb877db..5b476f8df2 100644 --- a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock +++ b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock @@ -27,4 +27,4 @@ DEPENDENCIES warbler (~> 2.0) BUNDLED WITH - 2.3.0.dev + 2.5.0.dev diff --git a/spec/bundler/realworld/gemfile_source_header_spec.rb b/spec/bundler/realworld/gemfile_source_header_spec.rb index ada2fc92ee..45f5d0fd22 100644 --- a/spec/bundler/realworld/gemfile_source_header_spec.rb +++ b/spec/bundler/realworld/gemfile_source_header_spec.rb @@ -2,7 +2,7 @@ require_relative "../support/silent_logger" -RSpec.describe "fetching dependencies with a mirrored source", :realworld => true do +RSpec.describe "fetching dependencies with a mirrored source", realworld: true do let(:mirror) { "https://server.example.org" } let(:original) { "http://127.0.0.1:#{@port}" } @@ -17,13 +17,13 @@ RSpec.describe "fetching dependencies with a mirrored source", :realworld => tru @t.join end - it "sets the 'X-Gemfile-Source' header and bundles successfully" do + it "sets the 'X-Gemfile-Source' and 'User-Agent' headers and bundles successfully" do gemfile <<-G source "#{mirror}" gem 'weakling' G - bundle :install, :artifice => nil + bundle :install, artifice: nil expect(out).to include("Installing weakling") expect(out).to include("Bundle complete") @@ -40,12 +40,12 @@ RSpec.describe "fetching dependencies with a mirrored source", :realworld => tru require_relative "../support/artifice/endpoint_mirror_source" @t = Thread.new do - Rack::Server.start(:app => EndpointMirrorSource, - :Host => "0.0.0.0", - :Port => @port, - :server => "webrick", - :AccessLog => [], - :Logger => Spec::SilentLogger.new) + Rack::Server.start(app: EndpointMirrorSource, + Host: "0.0.0.0", + Port: @port, + server: "webrick", + AccessLog: [], + Logger: Spec::SilentLogger.new) end.run wait_for_server("127.0.0.1", @port) diff --git a/spec/bundler/realworld/git_spec.rb b/spec/bundler/realworld/git_spec.rb new file mode 100644 index 0000000000..9eff74f1c9 --- /dev/null +++ b/spec/bundler/realworld/git_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +RSpec.describe "github source", realworld: true do + it "properly fetches PRs" do + install_gemfile <<-G + source "https://rubygems.org" + + gem "reline", github: "https://github.com/ruby/reline/pull/488" + G + end +end diff --git a/spec/bundler/realworld/mirror_probe_spec.rb b/spec/bundler/realworld/mirror_probe_spec.rb index a2b5c89150..fc97f92375 100644 --- a/spec/bundler/realworld/mirror_probe_spec.rb +++ b/spec/bundler/realworld/mirror_probe_spec.rb @@ -2,7 +2,7 @@ require_relative "../support/silent_logger" -RSpec.describe "fetching dependencies with a not available mirror", :realworld => true do +RSpec.describe "fetching dependencies with a not available mirror", realworld: true do let(:mirror) { @mirror_uri } let(:original) { @server_uri } let(:server_port) { @server_port } @@ -32,7 +32,7 @@ RSpec.describe "fetching dependencies with a not available mirror", :realworld = gem 'weakling' G - bundle :install, :artifice => nil + bundle :install, artifice: nil expect(out).to include("Installing weakling") expect(out).to include("Bundle complete") @@ -52,7 +52,7 @@ RSpec.describe "fetching dependencies with a not available mirror", :realworld = gem 'weakling' G - bundle :install, :artifice => nil + bundle :install, artifice: nil expect(out).to include("Installing weakling") expect(out).to include("Bundle complete") @@ -71,30 +71,15 @@ RSpec.describe "fetching dependencies with a not available mirror", :realworld = gem 'weakling' G - bundle :install, :artifice => nil, :raise_on_error => false + bundle :install, artifice: nil, raise_on_error: false expect(out).to include("Fetching source index from #{mirror}") - expect(err).to include("Retrying fetcher due to error (2/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2) for \"#{host}\" port #{@mirror_port}) (#{mirror}/specs.4.8.gz)>") - expect(err).to include("Retrying fetcher due to error (3/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2) for \"#{host}\" port #{@mirror_port}) (#{mirror}/specs.4.8.gz)>") - expect(err).to include("Retrying fetcher due to error (4/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2) for \"#{host}\" port #{@mirror_port}) (#{mirror}/specs.4.8.gz)>") - expect(err).to include("Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2) for \"#{host}\" port #{@mirror_port}) (#{mirror}/specs.4.8.gz)>") - end - - it "prints each error and warning on a new line" do - gemfile <<-G - source "#{original}" - gem 'weakling' - G - bundle :install, :artifice => nil, :raise_on_error => false - - expect(out).to include "Fetching source index from #{mirror}/" - expect(err).to include <<-EOS.strip -Retrying fetcher due to error (2/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2) for \"#{host}\" port #{@mirror_port}) (#{mirror}/specs.4.8.gz)> -Retrying fetcher due to error (3/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2) for \"#{host}\" port #{@mirror_port}) (#{mirror}/specs.4.8.gz)> -Retrying fetcher due to error (4/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2) for \"#{host}\" port #{@mirror_port}) (#{mirror}/specs.4.8.gz)> -Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2) for \"#{host}\" port #{@mirror_port}) (#{mirror}/specs.4.8.gz)> - EOS + err_lines = err.split("\n") + expect(err_lines).to include(%r{\ARetrying fetcher due to error \(2/4\): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <}) + expect(err_lines).to include(%r{\ARetrying fetcher due to error \(3/4\): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <}) + expect(err_lines).to include(%r{\ARetrying fetcher due to error \(4/4\): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <}) + expect(err_lines).to include(%r{\ACould not fetch specs from #{mirror}/ due to underlying error <}) end end @@ -109,13 +94,15 @@ Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUS gem 'weakling' G - bundle :install, :artifice => nil, :raise_on_error => false + bundle :install, artifice: nil, raise_on_error: false expect(out).to include("Fetching source index from #{mirror}") - expect(err).to include("Retrying fetcher due to error (2/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2) for \"#{host}\" port #{@mirror_port}) (#{mirror}/specs.4.8.gz)>") - expect(err).to include("Retrying fetcher due to error (3/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2) for \"#{host}\" port #{@mirror_port}) (#{mirror}/specs.4.8.gz)>") - expect(err).to include("Retrying fetcher due to error (4/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2) for \"#{host}\" port #{@mirror_port}) (#{mirror}/specs.4.8.gz)>") - expect(err).to include("Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUSED: Failed to open TCP connection to #{host}:#{@mirror_port} (Connection refused - connect(2) for \"#{host}\" port #{@mirror_port}) (#{mirror}/specs.4.8.gz)>") + + err_lines = err.split("\n") + expect(err_lines).to include(%r{\ARetrying fetcher due to error \(2/4\): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <}) + expect(err_lines).to include(%r{\ARetrying fetcher due to error \(3/4\): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <}) + expect(err_lines).to include(%r{\ARetrying fetcher due to error \(4/4\): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <}) + expect(err_lines).to include(%r{\ACould not fetch specs from #{mirror}/ due to underlying error <}) end end @@ -126,12 +113,12 @@ Could not fetch specs from #{mirror}/ due to underlying error <Errno::ECONNREFUS require_relative "../support/artifice/endpoint" @server_thread = Thread.new do - Rack::Server.start(:app => Endpoint, - :Host => host, - :Port => @server_port, - :server => "webrick", - :AccessLog => [], - :Logger => Spec::SilentLogger.new) + Rack::Server.start(app: Endpoint, + Host: host, + Port: @server_port, + server: "webrick", + AccessLog: [], + Logger: Spec::SilentLogger.new) end.run wait_for_server(host, @server_port) diff --git a/spec/bundler/realworld/parallel_spec.rb b/spec/bundler/realworld/parallel_spec.rb index 97c0e0cab4..b57fdfd0ee 100644 --- a/spec/bundler/realworld/parallel_spec.rb +++ b/spec/bundler/realworld/parallel_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe "parallel", :realworld => true do +RSpec.describe "parallel", realworld: true do it "installs" do gemfile <<-G source "https://rubygems.org" @@ -9,7 +9,7 @@ RSpec.describe "parallel", :realworld => true do gem 'i18n', '~> 0.6.0' # Because 0.7+ requires Ruby 1.9.3+ G - bundle :install, :jobs => 4, :env => { "DEBUG" => "1" } + bundle :install, jobs: 4, env: { "DEBUG" => "1" } expect(out).to match(/[1-3]: /) @@ -34,7 +34,7 @@ RSpec.describe "parallel", :realworld => true do gem 'i18n', '~> 0.6.0' # Because 0.7+ requires Ruby 1.9.3+ G - bundle :update, :jobs => 4, :env => { "DEBUG" => "1" }, :all => true + bundle :update, jobs: 4, env: { "DEBUG" => "1" }, all: true expect(out).to match(/[1-3]: /) @@ -46,12 +46,12 @@ RSpec.describe "parallel", :realworld => true do end it "works with --standalone" do - gemfile <<-G, :standalone => true + gemfile <<-G source "https://rubygems.org" gem "diff-lcs" G - bundle :install, :standalone => true, :jobs => 4 + bundle :install, standalone: true, jobs: 4 ruby <<-RUBY $:.unshift File.expand_path("bundle") diff --git a/spec/bundler/realworld/slow_perf_spec.rb b/spec/bundler/realworld/slow_perf_spec.rb index aced5a1641..32e266ff1b 100644 --- a/spec/bundler/realworld/slow_perf_spec.rb +++ b/spec/bundler/realworld/slow_perf_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -RSpec.describe "bundle install with complex dependencies", :realworld => true do +RSpec.describe "bundle install with complex dependencies", realworld: true do it "resolves quickly" do gemfile <<-G source 'https://rubygems.org' @@ -11,12 +11,134 @@ RSpec.describe "bundle install with complex dependencies", :realworld => true do gem "mongoid", ">= 0.10.2" G - start_time = Time.now + bundle "lock", env: { "DEBUG_RESOLVER" => "1" } + expect(out).to include("Solution found after 1 attempts") + end + + it "resolves quickly (case 2)" do + gemfile <<-G + source "https://rubygems.org" + + gem 'metasploit-erd' + gem 'rails-erd' + gem 'yard' + + gem 'coveralls' + gem 'rails' + gem 'simplecov' + gem 'rspec-rails' + G + + bundle "lock", env: { "DEBUG_RESOLVER" => "1" } + expect(out).to include("Solution found after 1 attempts") + end + + it "resolves big gemfile quickly" do + gemfile <<~G + # frozen_string_literal: true + + source "https://rubygems.org" + + gem "rails" + gem "pg", ">= 0.18", "< 2.0" + gem "goldiloader" + gem "awesome_nested_set" + gem "circuitbox" + gem "passenger" + gem "globalid" + gem "rack-cors" + gem "rails-pg-extras" + gem "linear_regression_trend" + gem "rack-protection" + gem "pundit" + gem "remote_ip_proxy_scrubber" + gem "bcrypt" + gem "searchkick" + gem "excon" + gem "faraday_middleware-aws-sigv4" + gem "typhoeus" + gem "sidekiq" + gem "sidekiq-undertaker" + gem "sidekiq-cron" + gem "storext" + gem "appsignal" + gem "fcm" + gem "business_time" + gem "tzinfo" + gem "holidays" + gem "bigdecimal" + gem "progress_bar" + gem "redis" + gem "hiredis" + gem "state_machines" + gem "state_machines-audit_trail" + gem "state_machines-activerecord" + gem "interactor" + gem "ar_transaction_changes" + gem "redis-rails" + gem "seed_migration" + gem "lograge" + gem "graphiql-rails", group: :development + gem "graphql" + gem "pusher" + gem "rbnacl" + gem "jwt" + gem "json-schema" + gem "discard" + gem "money" + gem "strip_attributes" + gem "validates_email_format_of" + gem "audited" + gem "concurrent-ruby" + gem "with_advisory_lock" + + group :test do + gem "rspec-sidekiq" + gem "simplecov", require: false + end + + group :development, :test do + gem "byebug", platform: :mri + gem "guard" + gem "guard-bundler" + gem "guard-rspec" + gem "rb-fsevent" + gem "rspec_junit_formatter" + gem "rspec-collection_matchers" + gem "rspec-rails" + gem "rspec-retry" + gem "state_machines-rspec" + gem "dotenv-rails" + gem "database_cleaner-active_record" + gem "database_cleaner-redis" + gem "timecop" + end + + gem "factory_bot_rails" + gem "faker" + + group :development do + gem "listen" + gem "sql_queries_count" + gem "rubocop" + gem "rubocop-performance" + gem "rubocop-rspec" + gem "rubocop-rails" + gem "brakeman" + gem "bundler-audit" + gem "solargraph" + gem "annotate" + end + G - bundle "lock" + if Bundler.feature_flag.bundler_3_mode? + bundle "lock", env: { "DEBUG_RESOLVER" => "1" }, raise_on_error: false - duration = Time.now - start_time + expect(out).to include("backtracking").exactly(26).times + else + bundle "lock", env: { "DEBUG_RESOLVER" => "1" } - expect(duration.to_f).to be < 12 # seconds + expect(out).to include("Solution found after 10 attempts") + end end end diff --git a/spec/bundler/resolver/basic_spec.rb b/spec/bundler/resolver/basic_spec.rb index ee62dc3577..4a0dd37bf9 100644 --- a/spec/bundler/resolver/basic_spec.rb +++ b/spec/bundler/resolver/basic_spec.rb @@ -100,11 +100,20 @@ RSpec.describe "Resolving" do end it "raises an exception if a child dependency is not resolved" do - @index = a_unresovable_child_index + @index = a_unresolvable_child_index dep "chef_app_error" expect do resolve - end.to raise_error(Bundler::VersionConflict) + end.to raise_error(Bundler::SolveFailure) + end + + it "does not try to re-resolve including prereleases if gems involved don't have prereleases" do + @index = a_unresolvable_child_index + dep "chef_app_error" + expect(Bundler.ui).not_to receive(:debug).with("Retrying resolution...", any_args) + expect do + resolve + end.to raise_error(Bundler::SolveFailure) end it "raises an exception with the minimal set of conflicting dependencies" do @@ -118,14 +127,15 @@ RSpec.describe "Resolving" do dep "c" expect do resolve - end.to raise_error(Bundler::VersionConflict, <<-E.strip) -Bundler could not find compatible versions for gem "a": - In Gemfile: - b was resolved to 1.0, which depends on - a (>= 2) - - c was resolved to 1.0, which depends on - a (< 1) + end.to raise_error(Bundler::SolveFailure, <<~E.strip) + Could not find compatible versions + + Because every version of c depends on a < 1 + and every version of b depends on a >= 2, + every version of c is incompatible with b >= 0. + So, because Gemfile depends on b >= 0 + and Gemfile depends on c >= 0, + version solving has failed. E end @@ -134,7 +144,7 @@ Bundler could not find compatible versions for gem "a": dep "circular_app" expect do - resolve + Bundler::SpecSet.new(resolve).sort end.to raise_error(Bundler::CyclicDependencyError, /please remove either gem 'bar' or gem 'foo'/i) end @@ -174,12 +184,7 @@ Bundler could not find compatible versions for gem "a": dep "foo" dep "Ruby\0", "1.8.7" - deps = [] - @deps.each do |d| - deps << Bundler::DepProxy.get_proxy(d, "ruby") - end - - should_resolve_and_include %w[foo-1.0.0 bar-1.0.0], [[]] + should_resolve_and_include %w[foo-1.0.0 bar-1.0.0] end context "conservative" do @@ -305,4 +310,73 @@ Bundler could not find compatible versions for gem "a": end end end + + it "handles versions that redundantly depend on themselves" do + @index = build_index do + gem "rack", "3.0.0" + + gem "standalone_migrations", "7.1.0" do + dep "rack", "~> 2.0" + end + + gem "standalone_migrations", "2.0.4" do + dep "standalone_migrations", ">= 0" + end + + gem "standalone_migrations", "1.0.13" do + dep "rack", ">= 0" + end + end + + dep "rack", "~> 3.0" + dep "standalone_migrations" + + should_resolve_as %w[rack-3.0.0 standalone_migrations-2.0.4] + end + + it "ignores versions that incorrectly depend on themselves" do + @index = build_index do + gem "rack", "3.0.0" + + gem "standalone_migrations", "7.1.0" do + dep "rack", "~> 2.0" + end + + gem "standalone_migrations", "2.0.4" do + dep "standalone_migrations", ">= 2.0.5" + end + + gem "standalone_migrations", "1.0.13" do + dep "rack", ">= 0" + end + end + + dep "rack", "~> 3.0" + dep "standalone_migrations" + + should_resolve_as %w[rack-3.0.0 standalone_migrations-1.0.13] + end + + it "does not ignore versions that incorrectly depend on themselves when dependency_api is not available" do + @index = build_index do + gem "rack", "3.0.0" + + gem "standalone_migrations", "7.1.0" do + dep "rack", "~> 2.0" + end + + gem "standalone_migrations", "2.0.4" do + dep "standalone_migrations", ">= 2.0.5" + end + + gem "standalone_migrations", "1.0.13" do + dep "rack", ">= 0" + end + end + + dep "rack", "~> 3.0" + dep "standalone_migrations" + + should_resolve_without_dependency_api %w[rack-3.0.0 standalone_migrations-2.0.4] + end end diff --git a/spec/bundler/resolver/platform_spec.rb b/spec/bundler/resolver/platform_spec.rb index bc4081f8b5..3e959aeb89 100644 --- a/spec/bundler/resolver/platform_spec.rb +++ b/spec/bundler/resolver/platform_spec.rb @@ -82,21 +82,105 @@ RSpec.describe "Resolving platform craziness" do should_resolve_as %w[foo-1.0.0-x64-mingw32] end - it "takes the latest ruby gem if the platform specific gem doesn't match the required_ruby_version" do - @index = build_index do - gem "foo", "1.0.0" - gem "foo", "1.0.0", "x64-mingw32" - gem "foo", "1.1.0" - gem "foo", "1.1.0", "x64-mingw32" do |s| - s.required_ruby_version = [">= 2.0", "< 2.4"] + describe "on a linux platform" do + # Ruby's platform is *-linux => platform's libc is glibc, so not musl + # Ruby's platform is *-linux-musl => platform's libc is musl, so not glibc + # Gem's platform is *-linux => gem is glibc + maybe musl compatible + # Gem's platform is *-linux-musl => gem is musl compatible but not glibc + + it "favors the platform version-specific gem on a version-specifying linux platform" do + @index = build_index do + gem "foo", "1.0.0" + gem "foo", "1.0.0", "x86_64-linux" + gem "foo", "1.0.0", "x86_64-linux-musl" end - gem "Ruby\0", "2.5.1" + dep "foo" + platforms "x86_64-linux-musl" + + should_resolve_as %w[foo-1.0.0-x86_64-linux-musl] end - dep "foo" - dep "Ruby\0", "2.5.1" - platforms "x64-mingw32" - should_resolve_as %w[foo-1.1.0] + it "favors the version-less gem over the version-specific gem on a gnu linux platform" do + @index = build_index do + gem "foo", "1.0.0" + gem "foo", "1.0.0", "x86_64-linux" + gem "foo", "1.0.0", "x86_64-linux-musl" + end + dep "foo" + platforms "x86_64-linux" + + should_resolve_as %w[foo-1.0.0-x86_64-linux] + end + + it "ignores the platform version-specific gem on a gnu linux platform" do + @index = build_index do + gem "foo", "1.0.0", "x86_64-linux-musl" + end + dep "foo" + platforms "x86_64-linux" + + should_not_resolve + end + + it "falls back to the platform version-less gem on a linux platform with a version" do + @index = build_index do + gem "foo", "1.0.0" + gem "foo", "1.0.0", "x86_64-linux" + end + dep "foo" + platforms "x86_64-linux-musl" + + should_resolve_as %w[foo-1.0.0-x86_64-linux] + end + + it "falls back to the ruby platform gem on a gnu linux platform when only a version-specifying gem is available" do + @index = build_index do + gem "foo", "1.0.0" + gem "foo", "1.0.0", "x86_64-linux-musl" + end + dep "foo" + platforms "x86_64-linux" + + should_resolve_as %w[foo-1.0.0] + end + + it "falls back to the platform version-less gem on a version-specifying linux platform and no ruby platform gem is available" do + @index = build_index do + gem "foo", "1.0.0", "x86_64-linux" + end + dep "foo" + platforms "x86_64-linux-musl" + + should_resolve_as %w[foo-1.0.0-x86_64-linux] + end + end + + context "when the platform specific gem doesn't match the required_ruby_version" do + before do + @index = build_index do + gem "foo", "1.0.0" + gem "foo", "1.0.0", "x64-mingw32" + gem "foo", "1.1.0" + gem "foo", "1.1.0", "x64-mingw32" do |s| + s.required_ruby_version = [">= 2.0", "< 2.4"] + end + gem "Ruby\0", "2.5.1" + end + dep "Ruby\0", "2.5.1" + platforms "x64-mingw32" + end + + it "takes the latest ruby gem" do + dep "foo" + + should_resolve_as %w[foo-1.1.0] + end + + it "takes the latest ruby gem, even if requirement does not match previous versions with the same ruby requirement" do + dep "foo", "1.1.0" + + should_resolve_as %w[foo-1.1.0] + end end it "takes the latest ruby gem with required_ruby_version if the platform specific gem doesn't match the required_ruby_version" do @@ -137,39 +221,6 @@ RSpec.describe "Resolving platform craziness" do should_resolve_as %w[foo-1.1.0] end - it "doesn't include gems not needed for none of the platforms" do - @index = build_index do - gem "empyrean", "0.1.0" - gem "coderay", "1.1.2" - gem "method_source", "0.9.0" - - gem "spoon", "0.0.6" do - dep "ffi", ">= 0" - end - - gem "pry", "0.11.3", "java" do - dep "coderay", "~> 1.1.0" - dep "method_source", "~> 0.9.0" - dep "spoon", "~> 0.0" - end - - gem "pry", "0.11.3" do - dep "coderay", "~> 1.1.0" - dep "method_source", "~> 0.9.0" - end - - gem "ffi", "1.9.23", "java" - gem "ffi", "1.9.23" - end - - dep "empyrean", "0.1.0" - dep "pry" - - platforms "ruby", "java" - - should_resolve_as %w[coderay-1.1.2 empyrean-0.1.0 ffi-1.9.23-java method_source-0.9.0 pry-0.11.3 pry-0.11.3-java spoon-0.0.6] - end - it "includes gems needed for at least one platform" do @index = build_index do gem "empyrean", "0.1.0" @@ -291,7 +342,7 @@ RSpec.describe "Resolving platform craziness" do describe "with mingw32" do before :each do @index = build_index do - platforms "mingw32 mswin32 x64-mingw32" do |platform| + platforms "mingw32 mswin32 x64-mingw32 x64-mingw-ucrt" do |platform| gem "thin", "1.2.7", platform end gem "win32-api", "1.5.1", "universal-mingw32" @@ -312,7 +363,7 @@ RSpec.describe "Resolving platform craziness" do should_resolve_as %w[thin-1.2.7-mingw32] end - it "finds x64-mingw gems" do + it "finds x64-mingw32 gems" do platforms "x64-mingw32" dep "thin" should_resolve_as %w[thin-1.2.7-x64-mingw32] @@ -329,6 +380,22 @@ RSpec.describe "Resolving platform craziness" do dep "win32-api" should_resolve_as %w[win32-api-1.5.1-universal-mingw32] end + + if Gem.rubygems_version >= Gem::Version.new("3.2.28") + it "finds x64-mingw-ucrt gems" do + platforms "x64-mingw-ucrt" + dep "thin" + should_resolve_as %w[thin-1.2.7-x64-mingw-ucrt] + end + end + + if Gem.rubygems_version >= Gem::Version.new("3.3.18") + it "finds universal-mingw gems on x64-mingw-ucrt" do + platform "x64-mingw-ucrt" + dep "win32-api" + should_resolve_as %w[win32-api-1.5.1-universal-mingw32] + end + end end describe "with conflicting cases" do diff --git a/spec/bundler/runtime/executable_spec.rb b/spec/bundler/runtime/executable_spec.rb index a11f547648..36ce6dcf67 100644 --- a/spec/bundler/runtime/executable_spec.rb +++ b/spec/bundler/runtime/executable_spec.rb @@ -11,7 +11,7 @@ RSpec.describe "Running bin/* commands" do it "runs the bundled command when in the bundle" do bundle "binstubs rack" - build_gem "rack", "2.0", :to_system => true do |s| + build_gem "rack", "2.0", to_system: true do |s| s.executables = "rackup" end @@ -20,7 +20,7 @@ RSpec.describe "Running bin/* commands" do end it "allows the location of the gem stubs to be specified" do - bundle "binstubs rack", :path => "gbin" + bundle "binstubs rack", path: "gbin" expect(bundled_app("bin")).not_to exist expect(bundled_app("gbin/rackup")).to exist @@ -30,7 +30,7 @@ RSpec.describe "Running bin/* commands" do end it "allows absolute paths as a specification of where to install bin stubs" do - bundle "binstubs rack", :path => tmp("bin") + bundle "binstubs rack", path: tmp("bin") gembin tmp("bin/rackup") expect(out).to eq("1.0.0") @@ -42,23 +42,23 @@ RSpec.describe "Running bin/* commands" do end it "allows the name of the shebang executable to be specified" do - bundle "binstubs rack", :shebang => "ruby-foo" + bundle "binstubs rack", shebang: "ruby-foo" expect(File.readlines(bundled_app("bin/rackup")).first).to eq("#!/usr/bin/env ruby-foo\n") end it "runs the bundled command when out of the bundle" do bundle "binstubs rack" - build_gem "rack", "2.0", :to_system => true do |s| + build_gem "rack", "2.0", to_system: true do |s| s.executables = "rackup" end - gembin "rackup", :dir => tmp + gembin "rackup", dir: tmp expect(out).to eq("1.0.0") end it "works with gems in path" do - build_lib "rack", :path => lib_path("rack") do |s| + build_lib "rack", path: lib_path("rack") do |s| s.executables = "rackup" end @@ -69,7 +69,7 @@ RSpec.describe "Running bin/* commands" do bundle "binstubs rack" - build_gem "rack", "2.0", :to_system => true do |s| + build_gem "rack", "2.0", to_system: true do |s| s.executables = "rackup" end @@ -94,7 +94,7 @@ RSpec.describe "Running bin/* commands" do expect(bundled_app("bin/rackup")).not_to exist end - it "allows you to stop installing binstubs", :bundler => "< 3" do + it "allows you to stop installing binstubs", bundler: "< 3" do skip "delete permission error" if Gem.win_platform? bundle "install --binstubs bin/" @@ -107,13 +107,13 @@ RSpec.describe "Running bin/* commands" do expect(out).to include("You have not configured a value for `bin`") end - it "remembers that the option was specified", :bundler => "< 3" do + it "remembers that the option was specified", bundler: "< 3" do gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "activesupport" G - bundle :install, :binstubs => "bin" + bundle :install, binstubs: "bin" gemfile <<-G source "#{file_uri_for(gem_repo1)}" diff --git a/spec/bundler/runtime/gem_tasks_spec.rb b/spec/bundler/runtime/gem_tasks_spec.rb index b0ef0cc144..f7afc0eb92 100644 --- a/spec/bundler/runtime/gem_tasks_spec.rb +++ b/spec/bundler/runtime/gem_tasks_spec.rb @@ -27,8 +27,8 @@ RSpec.describe "require 'bundler/gem_tasks'" do end it "includes the relevant tasks" do - with_gem_path_as(base_system_gems.to_s) do - sys_exec "#{rake} -T", :env => { "GEM_HOME" => system_gem_path.to_s } + with_gem_path_as(base_system_gem_path.to_s) do + sys_exec "#{rake} -T", env: { "GEM_HOME" => system_gem_path.to_s } end expect(err).to be_empty @@ -44,8 +44,8 @@ RSpec.describe "require 'bundler/gem_tasks'" do end it "defines a working `rake install` task", :ruby_repo do - with_gem_path_as(base_system_gems.to_s) do - sys_exec "#{rake} install", :env => { "GEM_HOME" => system_gem_path.to_s } + with_gem_path_as(base_system_gem_path.to_s) do + sys_exec "#{rake} install", env: { "GEM_HOME" => system_gem_path.to_s } end expect(err).to be_empty @@ -59,7 +59,7 @@ RSpec.describe "require 'bundler/gem_tasks'" do before do spaced_bundled_app = tmp.join("bundled app") FileUtils.cp_r bundled_app, spaced_bundled_app - bundle "exec rake build", :dir => spaced_bundled_app + bundle "exec rake build", dir: spaced_bundled_app end it "still runs successfully" do @@ -71,7 +71,7 @@ RSpec.describe "require 'bundler/gem_tasks'" do before do bracketed_bundled_app = tmp.join("bundled[app") FileUtils.cp_r bundled_app, bracketed_bundled_app - bundle "exec rake build", :dir => bracketed_bundled_app + bundle "exec rake build", dir: bracketed_bundled_app end it "still runs successfully" do @@ -98,8 +98,8 @@ RSpec.describe "require 'bundler/gem_tasks'" do end it "adds 'pkg' to rake/clean's CLOBBER" do - with_gem_path_as(base_system_gems.to_s) do - sys_exec %(#{rake} -e 'load "Rakefile"; puts CLOBBER.inspect'), :env => { "GEM_HOME" => system_gem_path.to_s } + with_gem_path_as(base_system_gem_path.to_s) do + sys_exec %(#{rake} -e 'load "Rakefile"; puts CLOBBER.inspect'), env: { "GEM_HOME" => system_gem_path.to_s } end expect(out).to eq '["pkg"]' end diff --git a/spec/bundler/runtime/inline_spec.rb b/spec/bundler/runtime/inline_spec.rb index 4f39c552e8..50a5258dc7 100644 --- a/spec/bundler/runtime/inline_spec.rb +++ b/spec/bundler/runtime/inline_spec.rb @@ -2,7 +2,7 @@ RSpec.describe "bundler/inline#gemfile" do def script(code, options = {}) - requires = ["#{entrypoint}/inline"] + requires = ["bundler/inline"] requires.unshift "#{spec_dir}/support/artifice/" + options.delete(:artifice) if options.key?(:artifice) requires = requires.map {|r| "require '#{r}'" }.join("\n") ruby("#{requires}\n\n" + code, options) @@ -28,7 +28,7 @@ RSpec.describe "bundler/inline#gemfile" do s.write "lib/four.rb", "puts 'four'" end - build_lib "five", "1.0.0", :no_default => true do |s| + build_lib "five", "1.0.0", no_default: true do |s| s.write "lib/mofive.rb", "puts 'five'" end @@ -57,7 +57,7 @@ RSpec.describe "bundler/inline#gemfile" do expect(out).to eq("two") - script <<-RUBY, :raise_on_error => false + script <<-RUBY, raise_on_error: false gemfile do source "#{file_uri_for(gem_repo1)}" path "#{lib_path}" do @@ -80,7 +80,7 @@ RSpec.describe "bundler/inline#gemfile" do expect(out).to include("Rack's post install message") - script <<-RUBY, :artifice => "endpoint" + script <<-RUBY, artifice: "endpoint" gemfile(true) do source "https://notaserver.com" gem "activesupport", :require => true @@ -89,19 +89,21 @@ RSpec.describe "bundler/inline#gemfile" do expect(out).to include("Installing activesupport") err_lines = err.split("\n") - err_lines.reject!{|line| line =~ /\.rb:\d+: warning: / } unless RUBY_VERSION < "2.7" + err_lines.reject! {|line| line =~ /\.rb:\d+: warning: / } expect(err_lines).to be_empty end it "lets me use my own ui object" do - script <<-RUBY, :artifice => "endpoint" - require '#{entrypoint}' - class MyBundlerUI < Bundler::UI::Silent + script <<-RUBY, artifice: "endpoint" + require 'bundler' + class MyBundlerUI < Bundler::UI::Shell def confirm(msg, newline = nil) puts "CONFIRMED!" end end - gemfile(true, :ui => MyBundlerUI.new) do + my_ui = MyBundlerUI.new + my_ui.level = "confirm" + gemfile(true, :ui => my_ui) do source "https://notaserver.com" gem "activesupport", :require => true end @@ -111,8 +113,8 @@ RSpec.describe "bundler/inline#gemfile" do end it "has an option for quiet installation" do - script <<-RUBY, :artifice => "endpoint" - require '#{entrypoint}/inline' + script <<-RUBY, artifice: "endpoint" + require 'bundler/inline' gemfile(true, :quiet => true) do source "https://notaserver.com" @@ -124,7 +126,7 @@ RSpec.describe "bundler/inline#gemfile" do end it "raises an exception if passed unknown arguments" do - script <<-RUBY, :raise_on_error => false + script <<-RUBY, raise_on_error: false gemfile(true, :arglebargle => true) do path "#{lib_path}" gem "two" @@ -138,7 +140,7 @@ RSpec.describe "bundler/inline#gemfile" do it "does not mutate the option argument" do script <<-RUBY - require '#{entrypoint}' + require 'bundler' options = { :ui => Bundler::UI::Shell.new } gemfile(false, options) do source "#{file_uri_for(gem_repo1)}" @@ -166,6 +168,54 @@ RSpec.describe "bundler/inline#gemfile" do expect(err).to be_empty end + it "installs subdependencies quietly if necessary when the install option is not set" do + build_repo4 do + build_gem "rack" do |s| + s.add_dependency "rackdep" + end + + build_gem "rackdep", "1.0.0" + end + + script <<-RUBY + gemfile do + source "#{file_uri_for(gem_repo4)}" + gem "rack" + end + + require "rackdep" + puts RACKDEP + RUBY + + expect(out).to eq("1.0.0") + expect(err).to be_empty + end + + it "installs subdependencies quietly if necessary when the install option is not set, and multiple sources used" do + build_repo4 do + build_gem "rack" do |s| + s.add_dependency "rackdep" + end + + build_gem "rackdep", "1.0.0" + end + + script <<-RUBY + gemfile do + source "#{file_uri_for(gem_repo1)}" + source "#{file_uri_for(gem_repo4)}" do + gem "rack" + end + end + + require "rackdep" + puts RACKDEP + RUBY + + expect(out).to eq("1.0.0") + expect(err).to be_empty + end + it "installs quietly from git if necessary when the install option is not set" do build_git "foo", "1.0.0" baz_ref = build_git("baz", "2.0.0").ref_for("HEAD") @@ -205,6 +255,113 @@ RSpec.describe "bundler/inline#gemfile" do expect(err).to be_empty end + it "doesn't reinstall already installed gems" do + system_gems "rack-1.0.0" + + script <<-RUBY + require 'bundler' + ui = Bundler::UI::Shell.new + ui.level = "confirm" + + gemfile(true, ui: ui) do + source "#{file_uri_for(gem_repo1)}" + gem "activesupport" + gem "rack" + end + RUBY + + expect(out).to include("Installing activesupport") + expect(out).not_to include("Installing rack") + expect(err).to be_empty + end + + it "installs gems in later gemfile calls" do + system_gems "rack-1.0.0" + + script <<-RUBY + require 'bundler' + ui = Bundler::UI::Shell.new + ui.level = "confirm" + gemfile(true, ui: ui) do + source "#{file_uri_for(gem_repo1)}" + gem "rack" + end + + gemfile(true, ui: ui) do + source "#{file_uri_for(gem_repo1)}" + gem "activesupport" + end + RUBY + + expect(out).to include("Installing activesupport") + expect(out).not_to include("Installing rack") + expect(err).to be_empty + end + + it "doesn't reinstall already installed gems in later gemfile calls" do + system_gems "rack-1.0.0" + + script <<-RUBY + require 'bundler' + ui = Bundler::UI::Shell.new + ui.level = "confirm" + gemfile(true, ui: ui) do + source "#{file_uri_for(gem_repo1)}" + gem "activesupport" + end + + gemfile(true, ui: ui) do + source "#{file_uri_for(gem_repo1)}" + gem "rack" + end + RUBY + + expect(out).to include("Installing activesupport") + expect(out).not_to include("Installing rack") + expect(err).to be_empty + end + + it "installs gems with native extensions in later gemfile calls" do + system_gems "rack-1.0.0" + + build_git "foo" do |s| + s.add_dependency "rake" + s.extensions << "Rakefile" + s.write "Rakefile", <<-RUBY + task :default do + path = File.expand_path("lib", __dir__) + FileUtils.mkdir_p(path) + File.open("\#{path}/foo.rb", "w") do |f| + f.puts "FOO = 'YES'" + end + end + RUBY + end + + script <<-RUBY + require 'bundler' + ui = Bundler::UI::Shell.new + ui.level = "confirm" + gemfile(true, ui: ui) do + source "#{file_uri_for(gem_repo1)}" + gem "rack" + end + + gemfile(true, ui: ui) do + source "#{file_uri_for(gem_repo1)}" + gem "foo", :git => "#{lib_path("foo-1.0")}" + end + + require 'foo' + puts FOO + puts $:.grep(/ext/) + RUBY + + expect(out).to include("YES") + expect(out).to include(Pathname.glob(default_bundle_path("bundler/gems/extensions/**/foo-1.0-*")).first.to_s) + expect(err).to be_empty + end + it "installs inline gems when a Gemfile.lock is present" do gemfile <<-G source "https://notaserver.com" @@ -239,8 +396,42 @@ RSpec.describe "bundler/inline#gemfile" do expect(err).to be_empty end + it "does not leak Gemfile.lock versions to the installation output" do + gemfile <<-G + source "https://notaserver.com" + gem "rake" + G + + lockfile <<-G + GEM + remote: https://rubygems.org/ + specs: + rake (11.3.0) + + PLATFORMS + ruby + + DEPENDENCIES + rake + + BUNDLED WITH + #{Bundler::VERSION} + G + + script <<-RUBY + gemfile(true) do + source "#{file_uri_for(gem_repo1)}" + gem "rake", "#{rake_version}" + end + RUBY + + expect(out).to include("Installing rake #{rake_version}") + expect(out).not_to include("was 11.3.0") + expect(err).to be_empty + end + it "installs inline gems when frozen is set" do - script <<-RUBY, :env => { "BUNDLE_FROZEN" => "true" } + script <<-RUBY, env: { "BUNDLE_FROZEN" => "true" } gemfile do source "#{file_uri_for(gem_repo1)}" gem "rack" @@ -253,7 +444,7 @@ RSpec.describe "bundler/inline#gemfile" do end it "installs inline gems when deployment is set" do - script <<-RUBY, :env => { "BUNDLE_DEPLOYMENT" => "true" } + script <<-RUBY, env: { "BUNDLE_DEPLOYMENT" => "true" } gemfile do source "#{file_uri_for(gem_repo1)}" gem "rack" @@ -297,7 +488,7 @@ RSpec.describe "bundler/inline#gemfile" do context "when BUNDLE_PATH is set" do it "installs inline gems to the system path regardless" do - script <<-RUBY, :env => { "BUNDLE_PATH" => "./vendor/inline" } + script <<-RUBY, env: { "BUNDLE_PATH" => "./vendor/inline" } gemfile(true) do source "#{file_uri_for(gem_repo1)}" gem "rack" @@ -309,7 +500,7 @@ RSpec.describe "bundler/inline#gemfile" do end it "skips platform warnings" do - simulate_platform "ruby" + bundle "config set --local force_ruby_platform true" script <<-RUBY gemfile(true) do @@ -321,6 +512,20 @@ RSpec.describe "bundler/inline#gemfile" do expect(err).to be_empty end + it "still installs if the application has `bundle package` no_install config set" do + bundle "config set --local no_install true" + + script <<-RUBY + gemfile do + source "#{file_uri_for(gem_repo1)}" + gem "rack" + end + RUBY + + expect(last_command).to be_success + expect(system_gem_path("gems/rack-1.0.0")).to exist + end + it "preserves previous BUNDLE_GEMFILE value" do ENV["BUNDLE_GEMFILE"] = "" script <<-RUBY @@ -371,7 +576,7 @@ RSpec.describe "bundler/inline#gemfile" do s.write "lib/foo.rb", foo_code end - script <<-RUBY, :dir => tmp("path_without_gemfile") + script <<-RUBY, dir: tmp("path_without_gemfile") gemfile do source "#{file_uri_for(gem_repo2)}" path "#{lib_path}" do @@ -386,24 +591,17 @@ RSpec.describe "bundler/inline#gemfile" do expect(err).to be_empty end - it "when requiring fileutils after does not show redefinition warnings" do - dependency_installer_loads_fileutils = ruby "require 'rubygems/dependency_installer'; puts $LOADED_FEATURES.grep(/fileutils/)", :raise_on_error => false - skip "does not work if rubygems/dependency_installer loads fileutils, which happens until rubygems 3.2.0" unless dependency_installer_loads_fileutils.empty? - - skip "does not work on ruby 3.0 because it changes the path to look for default gems, tsort is a default gem there, and we can't install it either like we do with fiddle because it doesn't yet exist" unless RUBY_VERSION < "3.0.0" - + it "when requiring fileutils after does not show redefinition warnings", :realworld do Dir.mkdir tmp("path_without_gemfile") - default_fileutils_version = ruby "gem 'fileutils', '< 999999'; require 'fileutils'; puts FileUtils::VERSION", :raise_on_error => false + default_fileutils_version = ruby "gem 'fileutils', '< 999999'; require 'fileutils'; puts FileUtils::VERSION", raise_on_error: false skip "fileutils isn't a default gem" if default_fileutils_version.empty? realworld_system_gems "fileutils --version 1.4.1" - realworld_system_gems "fiddle" # not sure why, but this is needed on Windows to boot rubygems successfully + realworld_system_gems "pathname --version 0.2.0" - realworld_system_gems "timeout uri" # this spec uses net/http which requires these default gems - - script <<-RUBY, :dir => tmp("path_without_gemfile"), :env => { "BUNDLER_GEM_DEFAULT_DIR" => system_gem_path.to_s } + script <<-RUBY, dir: tmp("path_without_gemfile"), env: { "BUNDLER_GEM_DEFAULT_DIR" => system_gem_path.to_s } require "bundler/inline" gemfile(true) do @@ -415,4 +613,47 @@ RSpec.describe "bundler/inline#gemfile" do expect(err).to eq("The Gemfile specifies no dependencies") end + + it "does not load default timeout" do + default_timeout_version = ruby "gem 'timeout', '< 999999'; require 'timeout'; puts Timeout::VERSION", raise_on_error: false + skip "timeout isn't a default gem" if default_timeout_version.empty? + + # This only works on RubyGems 3.5.0 or higher + ruby "require 'rubygems/timeout'", raise_on_error: false + skip "rubygems under test does not yet vendor timeout" unless last_command.success? + + build_repo4 do + build_gem "timeout", "999" + end + + script <<-RUBY + require "bundler/inline" + + gemfile(true) do + source "#{file_uri_for(gem_repo4)}" + + gem "timeout" + end + RUBY + + expect(out).to include("Installing timeout 999") + end + + it "does not upcase ENV" do + script <<-RUBY + require 'bundler/inline' + + ENV['Test_Variable'] = 'value string' + puts("before: \#{ENV.each_key.select { |key| key.match?(/test_variable/i) }}") + + gemfile do + source "#{file_uri_for(gem_repo1)}" + end + + puts("after: \#{ENV.each_key.select { |key| key.match?(/test_variable/i) }}") + RUBY + + expect(out).to include("before: [\"Test_Variable\"]") + expect(out).to include("after: [\"Test_Variable\"]") + end end diff --git a/spec/bundler/runtime/load_spec.rb b/spec/bundler/runtime/load_spec.rb index 96a22a46cc..f28ffd9460 100644 --- a/spec/bundler/runtime/load_spec.rb +++ b/spec/bundler/runtime/load_spec.rb @@ -82,7 +82,7 @@ RSpec.describe "Bundler.load" do G ruby <<-RUBY - require "#{entrypoint}" + require "bundler" Bundler.setup :default Bundler.require :default puts RACK diff --git a/spec/bundler/runtime/platform_spec.rb b/spec/bundler/runtime/platform_spec.rb index d81bccbdf8..1925e9bf2e 100644 --- a/spec/bundler/runtime/platform_spec.rb +++ b/spec/bundler/runtime/platform_spec.rb @@ -22,7 +22,7 @@ RSpec.describe "Bundler.setup with multi platform stuff" do ruby <<-R begin - require '#{entrypoint}' + require 'bundler' Bundler.ui.silence { Bundler.setup } rescue Bundler::GemNotFound => e puts "WIN" @@ -61,16 +61,23 @@ RSpec.describe "Bundler.setup with multi platform stuff" do build_repo4 do build_gem "nokogiri", "1.11.1" do |s| s.add_dependency "mini_portile2", "~> 2.5.0" - s.add_dependency "racc", "~> 1.4" + s.add_dependency "racca", "~> 1.5.2" end build_gem "nokogiri", "1.11.1" do |s| s.platform = Bundler.local_platform - s.add_dependency "racc", "~> 1.4" + s.add_dependency "racca", "~> 1.4" end build_gem "mini_portile2", "2.5.0" - build_gem "racc", "1.5.2" + build_gem "racca", "1.5.2" + end + + checksums = checksums_section do |c| + c.checksum gem_repo4, "mini_portile2", "2.5.0" + c.checksum gem_repo4, "nokogiri", "1.11.1" + c.checksum gem_repo4, "nokogiri", "1.11.1", Bundler.local_platform + c.checksum gem_repo4, "racca", "1.5.2" end good_lockfile = <<~L @@ -80,18 +87,17 @@ RSpec.describe "Bundler.setup with multi platform stuff" do mini_portile2 (2.5.0) nokogiri (1.11.1) mini_portile2 (~> 2.5.0) - racc (~> 1.4) + racca (~> 1.5.2) nokogiri (1.11.1-#{Bundler.local_platform}) - racc (~> 1.4) - racc (1.5.2) + racca (~> 1.4) + racca (1.5.2) PLATFORMS - ruby - #{Bundler.local_platform} + #{lockfile_platforms("ruby")} DEPENDENCIES nokogiri (~> 1.11) - + #{checksums} BUNDLED WITH #{Bundler::VERSION} L @@ -139,14 +145,14 @@ RSpec.describe "Bundler.setup with multi platform stuff" do #{Bundler::VERSION} L - bundle "install", :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + bundle "install", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } expect(out).to include("Fetching nokogiri 1.11.1") expect(the_bundle).to include_gems "nokogiri 1.11.1" expect(the_bundle).not_to include_gems "nokogiri 1.11.1 #{Bundler.local_platform}" end - it "will use the java platform if both generic java and generic ruby platforms are locked", :jruby do + it "will use the java platform if both generic java and generic ruby platforms are locked", :jruby_only do gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "nokogiri" @@ -169,7 +175,7 @@ RSpec.describe "Bundler.setup with multi platform stuff" do nokogiri BUNDLED WITH - 2.1.4 + #{Bundler::VERSION} G bundle "install" @@ -205,7 +211,7 @@ RSpec.describe "Bundler.setup with multi platform stuff" do expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 x86-darwin-100" end - it "allows specifying only-ruby-platform on jruby", :jruby do + it "allows specifying only-ruby-platform on jruby", :jruby_only do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "nokogiri" @@ -233,7 +239,21 @@ RSpec.describe "Bundler.setup with multi platform stuff" do expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 RUBY" end - it "doesn't pull platform specific gems on truffleruby", :truffleruby do + it "allows specifying only-ruby-platform even if the lockfile is locked to a specific compatible platform" do + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "nokogiri" + gem "platform_specific" + G + + bundle "config set force_ruby_platform true" + + bundle "install" + + expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 RUBY" + end + + it "doesn't pull platform specific gems on truffleruby", :truffleruby_only do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "platform_specific" @@ -242,11 +262,117 @@ RSpec.describe "Bundler.setup with multi platform stuff" do expect(the_bundle).to include_gems "platform_specific 1.0 RUBY" end + it "doesn't pull platform specific gems on truffleruby (except when whitelisted) even if lockfile was generated with an older version that declared RUBY as platform", :truffleruby_only do + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "platform_specific" + G + + lockfile <<-L + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: + platform_specific (1.0) + + PLATFORMS + ruby + + DEPENDENCIES + platform_specific + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" + + expect(the_bundle).to include_gems "platform_specific 1.0 RUBY" + + simulate_platform "x86_64-linux" do + build_repo4 do + build_gem "libv8" + + build_gem "libv8" do |s| + s.platform = "x86_64-linux" + end + end + + gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "libv8" + G + + lockfile <<-L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + libv8 (1.0) + + PLATFORMS + ruby + + DEPENDENCIES + libv8 + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" + + expect(the_bundle).to include_gems "libv8 1.0 x86_64-linux" + end + end + + it "doesn't pull platform specific gems on truffleruby, even if lockfile only includes those", :truffleruby_only do + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "platform_specific" + G + + lockfile <<-L + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: + platform_specific (1.0-x86-darwin-100) + + PLATFORMS + x86-darwin-100 + + DEPENDENCIES + platform_specific + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" + + expect(the_bundle).to include_gems "platform_specific 1.0 RUBY" + end + + it "pulls platform specific gems correctly on musl" do + build_repo4 do + build_gem "nokogiri", "1.13.8" do |s| + s.platform = "aarch64-linux" + end + end + + simulate_platform "aarch64-linux-musl" do + install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }, verbose: true + source "https://gems.repo4" + gem "nokogiri" + G + end + + expect(out).to include("Fetching nokogiri 1.13.8 (aarch64-linux)") + end + it "allows specifying only-ruby-platform on windows with dependency platforms" do simulate_windows do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "nokogiri", :platforms => [:mingw, :mswin, :x64_mingw, :jruby] + gem "nokogiri", :platforms => [:windows, :mswin, :mswin64, :mingw, :x64_mingw, :jruby] gem "platform_specific" G @@ -260,7 +386,7 @@ RSpec.describe "Bundler.setup with multi platform stuff" do end it "allows specifying only-ruby-platform on windows with gemspec dependency" do - build_lib("foo", "1.0", :path => bundled_app) do |s| + build_lib("foo", "1.0", path: bundled_app) do |s| s.add_dependency "rack" end @@ -284,7 +410,7 @@ RSpec.describe "Bundler.setup with multi platform stuff" do s.add_dependency "platform_specific" end end - simulate_windows x64_mingw do + simulate_windows x64_mingw32 do lockfile <<-L GEM remote: #{file_uri_for(gem_repo2)}/ @@ -301,12 +427,45 @@ RSpec.describe "Bundler.setup with multi platform stuff" do requires_platform_specific L - install_gemfile <<-G, :verbose => true + install_gemfile <<-G, verbose: true source "#{file_uri_for(gem_repo2)}" gem "requires_platform_specific" G + expect(out).to include("lockfile does not have all gems needed for the current platform") expect(the_bundle).to include_gem "platform_specific 1.0 x64-mingw32" end end + + %w[x86-mswin32 x64-mswin64 x86-mingw32 x64-mingw32 x64-mingw-ucrt].each do |arch| + it "allows specifying platform windows on #{arch} arch" do + platform = send(arch.tr("-", "_")) + + simulate_windows platform do + lockfile <<-L + GEM + remote: #{file_uri_for(gem_repo1)}/ + specs: + platform_specific (1.0-#{platform}) + requires_platform_specific (1.0) + platform_specific + + PLATFORMS + #{platform} + + DEPENDENCIES + requires_platform_specific + L + + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "platform_specific", :platforms => [:windows] + G + + bundle "install" + + expect(the_bundle).to include_gems "platform_specific 1.0 #{platform}" + end + end + end end diff --git a/spec/bundler/runtime/require_spec.rb b/spec/bundler/runtime/require_spec.rb index d91b5f8666..76271a5593 100644 --- a/spec/bundler/runtime/require_spec.rb +++ b/spec/bundler/runtime/require_spec.rb @@ -21,7 +21,7 @@ RSpec.describe "Bundler.require" do s.write "lib/four.rb", "puts 'four'" end - build_lib "five", "1.0.0", :no_default => true do |s| + build_lib "five", "1.0.0", no_default: true do |s| s.write "lib/mofive.rb", "puts 'five'" end @@ -138,7 +138,7 @@ RSpec.describe "Bundler.require" do end G - run "Bundler.require", :raise_on_error => false + run "Bundler.require", raise_on_error: false expect(err).to match("error while trying to load the gem 'faulty'") expect(err).to match("Gem Internal Error Message") end @@ -187,7 +187,7 @@ RSpec.describe "Bundler.require" do end it "silently passes if the require fails" do - build_lib "bcrypt-ruby", "1.0.0", :no_default => true do |s| + build_lib "bcrypt-ruby", "1.0.0", no_default: true do |s| s.write "lib/brcrypt.rb", "BCrypt = '1.0.0'" end gemfile <<-G @@ -199,7 +199,7 @@ RSpec.describe "Bundler.require" do G cmd = <<-RUBY - require '#{entrypoint}' + require 'bundler' Bundler.require RUBY ruby(cmd) @@ -323,7 +323,7 @@ RSpec.describe "Bundler.require" do describe "a gem with different requires for different envs" do before(:each) do - build_gem "multi_gem", :to_bundle => true do |s| + build_gem "multi_gem", to_bundle: true do |s| s.write "lib/one.rb", "puts 'ONE'" s.write "lib/two.rb", "puts 'TWO'" end @@ -366,7 +366,7 @@ RSpec.describe "Bundler.require" do describe "with busted gems" do it "should be busted" do - build_gem "busted_require", :to_bundle => true do |s| + build_gem "busted_require", to_bundle: true do |s| s.write "lib/busted_require.rb", "require 'no_such_file_omg'" end @@ -449,8 +449,6 @@ RSpec.describe "Bundler.require with platform specific dependencies" do end it "requires gems pinned to multiple platforms, including the current one" do - skip "platform issues" if Gem.win_platform? - install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" diff --git a/spec/bundler/runtime/requiring_spec.rb b/spec/bundler/runtime/requiring_spec.rb new file mode 100644 index 0000000000..1f32269622 --- /dev/null +++ b/spec/bundler/runtime/requiring_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +RSpec.describe "Requiring bundler" do + it "takes care of requiring rubygems when entrypoint is bundler/setup" do + sys_exec("#{Gem.ruby} -I#{lib_dir} -rbundler/setup -e'puts true'", env: { "RUBYOPT" => opt_add("--disable=gems", ENV["RUBYOPT"]) }) + + expect(last_command.stdboth).to eq("true") + end + + it "takes care of requiring rubygems when requiring just bundler" do + sys_exec("#{Gem.ruby} -I#{lib_dir} -rbundler -e'puts true'", env: { "RUBYOPT" => opt_add("--disable=gems", ENV["RUBYOPT"]) }) + + expect(last_command.stdboth).to eq("true") + end +end diff --git a/spec/bundler/runtime/self_management_spec.rb b/spec/bundler/runtime/self_management_spec.rb new file mode 100644 index 0000000000..d15ca3189e --- /dev/null +++ b/spec/bundler/runtime/self_management_spec.rb @@ -0,0 +1,159 @@ +# frozen_string_literal: true + +RSpec.describe "Self management", rubygems: ">= 3.3.0.dev", realworld: true do + describe "auto switching" do + let(:previous_minor) do + "2.3.0" + end + + let(:current_version) do + "2.4.0" + end + + before do + build_repo2 + + gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + + gem "rack" + G + end + + it "installs locked version when using system path and uses it" do + lockfile_bundled_with(previous_minor) + + bundle "config set --local path.system true" + bundle "install", artifice: "vcr" + expect(out).to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") + + # It uninstalls the older system bundler + bundle "clean --force", artifice: nil + expect(out).to eq("Removing bundler (#{Bundler::VERSION})") + + # App now uses locked version + bundle "-v", artifice: nil + expect(out).to end_with(previous_minor[0] == "2" ? "Bundler version #{previous_minor}" : previous_minor) + + # Subsequent installs use the locked version without reinstalling + bundle "install --verbose", artifice: nil + expect(out).to include("Using bundler #{previous_minor}") + expect(out).not_to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") + end + + it "installs locked version when using local path and uses it" do + lockfile_bundled_with(previous_minor) + + bundle "config set --local path vendor/bundle" + bundle "install", artifice: "vcr" + expect(out).to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") + expect(vendored_gems("gems/bundler-#{previous_minor}")).to exist + + # It does not uninstall the locked bundler + bundle "clean" + expect(out).to be_empty + + # App now uses locked version + bundle "-v" + expect(out).to end_with(previous_minor[0] == "2" ? "Bundler version #{previous_minor}" : previous_minor) + + # Subsequent installs use the locked version without reinstalling + bundle "install --verbose" + expect(out).to include("Using bundler #{previous_minor}") + expect(out).not_to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") + end + + it "installs locked version when using deployment option and uses it" do + lockfile_bundled_with(previous_minor) + + bundle "config set --local deployment true" + bundle "install", artifice: "vcr" + expect(out).to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") + expect(vendored_gems("gems/bundler-#{previous_minor}")).to exist + + # It does not uninstall the locked bundler + bundle "clean" + expect(out).to be_empty + + # App now uses locked version + bundle "-v" + expect(out).to end_with(previous_minor[0] == "2" ? "Bundler version #{previous_minor}" : previous_minor) + + # Subsequent installs use the locked version without reinstalling + bundle "install --verbose" + expect(out).to include("Using bundler #{previous_minor}") + expect(out).not_to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") + end + + it "does not try to install a development version" do + lockfile_bundled_with("#{previous_minor}.dev") + + bundle "install --verbose" + expect(out).not_to match(/restarting using that version/) + + bundle "-v" + expect(out).to eq(Bundler::VERSION[0] == "2" ? "Bundler version #{Bundler::VERSION}" : Bundler::VERSION) + end + + it "shows a discrete message if locked bundler does not exist" do + missing_minor = "#{Bundler::VERSION[0]}.999.999" + + lockfile_bundled_with(missing_minor) + + bundle "install", artifice: "vcr" + expect(err).to eq("Your lockfile is locked to a version of bundler (#{missing_minor}) that doesn't exist at https://rubygems.org/. Going on using #{Bundler::VERSION}") + + bundle "-v" + expect(out).to eq(Bundler::VERSION[0] == "2" ? "Bundler version #{Bundler::VERSION}" : Bundler::VERSION) + end + + it "installs BUNDLE_VERSION version when using bundle config version x.y.z" do + lockfile_bundled_with(current_version) + + bundle "config set --local version #{previous_minor}" + bundle "install", artifice: "vcr" + expect(out).to include("Bundler #{Bundler::VERSION} is running, but your configuration was #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") + + bundle "-v" + expect(out).to eq(previous_minor[0] == "2" ? "Bundler version #{previous_minor}" : previous_minor) + end + + it "does not try to install when using bundle config version global" do + lockfile_bundled_with(previous_minor) + + bundle "config set version system" + bundle "install", artifice: "vcr" + expect(out).not_to match(/restarting using that version/) + + bundle "-v" + expect(out).to eq(Bundler::VERSION[0] == "2" ? "Bundler version #{Bundler::VERSION}" : Bundler::VERSION) + end + + it "ignores malformed lockfile version" do + lockfile_bundled_with("2.3.") + + bundle "install --verbose" + expect(out).to include("Using bundler #{Bundler::VERSION}") + end + + private + + def lockfile_bundled_with(version) + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo2)}/ + specs: + rack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rack + + BUNDLED WITH + #{version} + L + end + end +end diff --git a/spec/bundler/runtime/setup_spec.rb b/spec/bundler/runtime/setup_spec.rb index 42bbacea0e..0344d24223 100644 --- a/spec/bundler/runtime/setup_spec.rb +++ b/spec/bundler/runtime/setup_spec.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "tmpdir" -require "tempfile" RSpec.describe "Bundler.setup" do describe "with no arguments" do @@ -90,7 +89,7 @@ RSpec.describe "Bundler.setup" do end it "handles multiple non-additive invocations" do - ruby <<-RUBY, :raise_on_error => false + ruby <<-RUBY, raise_on_error: false require 'bundler' Bundler.setup(:default, :test) Bundler.setup(:default) @@ -145,6 +144,7 @@ RSpec.describe "Bundler.setup" do ruby <<-RUBY require 'bundler' + gem "bundler", "#{Bundler::VERSION}" if #{ruby_core?} Bundler.setup puts $LOAD_PATH RUBY @@ -158,7 +158,7 @@ RSpec.describe "Bundler.setup" do "/gems/actionpack-2.3.2/lib", "/gems/actionmailer-2.3.2/lib", "/gems/activesupport-2.3.2/lib", - "/gems/rake-13.0.1/lib" + "/gems/rake-#{rake_version}/lib" ) end @@ -194,7 +194,7 @@ RSpec.describe "Bundler.setup" do G ruby <<-R - require '#{entrypoint}' + require 'bundler' begin Bundler.setup @@ -213,7 +213,7 @@ RSpec.describe "Bundler.setup" do gem "rack" G - ruby <<-R, :raise_on_error => false + ruby <<-R, raise_on_error: false require 'bundler' Bundler.setup @@ -236,7 +236,7 @@ RSpec.describe "Bundler.setup" do gem "nosuchgem", "10.0" G - ruby <<-R, :raise_on_error => false + ruby <<-R, raise_on_error: false require 'bundler' Bundler.setup @@ -312,7 +312,7 @@ RSpec.describe "Bundler.setup" do gem "rack", "1.0.0" G - build_gem "rack", "1.0", :to_system => true do |s| + build_gem "rack", "1.0", to_system: true do |s| s.write "lib/rack.rb", "RACK = 'FAIL'" end @@ -341,19 +341,6 @@ RSpec.describe "Bundler.setup" do expect(out).to eq("WIN") end - it "version_requirement is now deprecated in rubygems 1.4.0+ when gem is missing" do - run <<-R - begin - gem "activesupport" - puts "FAIL" - rescue LoadError - puts "WIN" - end - R - - expect(err).to be_empty - end - it "replaces #gem but raises when the version is wrong" do run <<-R begin @@ -366,19 +353,6 @@ RSpec.describe "Bundler.setup" do expect(out).to eq("WIN") end - - it "version_requirement is now deprecated in rubygems 1.4.0+ when the version is wrong" do - run <<-R - begin - gem "rack", "1.0.0" - puts "FAIL" - rescue LoadError - puts "WIN" - end - R - - expect(err).to be_empty - end end describe "by hiding system gems" do @@ -397,8 +371,8 @@ RSpec.describe "Bundler.setup" do context "when the ruby stdlib is a substring of Gem.path" do it "does not reject the stdlib from $LOAD_PATH" do - substring = "/" + $LOAD_PATH.find {|p| p =~ /vendor_ruby/ }.split("/")[2] - run "puts 'worked!'", :env => { "GEM_PATH" => substring } + substring = "/" + $LOAD_PATH.find {|p| p.include?("vendor_ruby") }.split("/")[2] + run "puts 'worked!'", env: { "GEM_PATH" => substring } expect(out).to eq("worked!") end end @@ -436,8 +410,8 @@ RSpec.describe "Bundler.setup" do end it "provides a useful exception when the git repo is not checked out yet" do - run "1", :raise_on_error => false - expect(err).to match(/the git source #{lib_path('rack-1.0.0')} is not yet checked out. Please run `bundle install`/i) + run "1", raise_on_error: false + expect(err).to match(/the git source #{lib_path("rack-1.0.0")} is not yet checked out. Please run `bundle install`/i) end it "does not hit the git binary if the lockfile is available and up to date" do @@ -467,7 +441,7 @@ RSpec.describe "Bundler.setup" do break_git! ruby <<-R - require "#{entrypoint}" + require "bundler" begin Bundler.setup @@ -477,7 +451,7 @@ RSpec.describe "Bundler.setup" do end R - run "puts 'FAIL'", :raise_on_error => false + run "puts 'FAIL'", raise_on_error: false expect(err).not_to include "This is not the git you are looking for" end @@ -516,15 +490,15 @@ RSpec.describe "Bundler.setup" do gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" G bundle %(config set local.rack #{lib_path("local-rack")}) bundle :install FileUtils.rm_rf(lib_path("local-rack")) - run "require 'rack'", :raise_on_error => false - expect(err).to match(/Cannot use local override for rack-0.8 because #{Regexp.escape(lib_path('local-rack').to_s)} does not exist/) + run "require 'rack'", raise_on_error: false + expect(err).to match(/Cannot use local override for rack-0.8 because #{Regexp.escape(lib_path("local-rack").to_s)} does not exist/) end it "explodes if branch is not given on runtime" do @@ -534,7 +508,7 @@ RSpec.describe "Bundler.setup" do gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" G bundle %(config set local.rack #{lib_path("local-rack")}) @@ -545,7 +519,7 @@ RSpec.describe "Bundler.setup" do gem "rack", :git => "#{lib_path("rack-0.8")}" G - run "require 'rack'", :raise_on_error => false + run "require 'rack'", raise_on_error: false expect(err).to match(/because :branch is not specified in Gemfile/) end @@ -556,7 +530,7 @@ RSpec.describe "Bundler.setup" do gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "master" + gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" G bundle %(config set local.rack #{lib_path("local-rack")}) @@ -567,8 +541,8 @@ RSpec.describe "Bundler.setup" do gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "changed" G - run "require 'rack'", :raise_on_error => false - expect(err).to match(/is using branch master but Gemfile specifies changed/) + run "require 'rack'", raise_on_error: false + expect(err).to match(/is using branch main but Gemfile specifies changed/) end it "explodes on refs with different branches on runtime" do @@ -578,17 +552,17 @@ RSpec.describe "Bundler.setup" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :ref => "master", :branch => "master" + gem "rack", :git => "#{lib_path("rack-0.8")}", :ref => "main", :branch => "main" G gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :ref => "master", :branch => "nonexistant" + gem "rack", :git => "#{lib_path("rack-0.8")}", :ref => "main", :branch => "nonexistent" G bundle %(config set local.rack #{lib_path("local-rack")}) - run "require 'rack'", :raise_on_error => false - expect(err).to match(/is using branch master but Gemfile specifies nonexistant/) + run "require 'rack'", raise_on_error: false + expect(err).to match(/is using branch main but Gemfile specifies nonexistent/) end end @@ -606,7 +580,7 @@ RSpec.describe "Bundler.setup" do system_gems "activesupport-2.3.5" - expect(the_bundle).to include_gems "activesupport 2.3.2", :groups => :default + expect(the_bundle).to include_gems "activesupport 2.3.2", groups: :default end it "remembers --without and does not bail on bare Bundler.setup" do @@ -629,7 +603,7 @@ RSpec.describe "Bundler.setup" do bundle "config set --local without development" path = bundled_app(File.join("vendor", "foo")) - build_lib "foo", :path => path + build_lib "foo", path: path install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -639,7 +613,7 @@ RSpec.describe "Bundler.setup" do FileUtils.rm_rf(path) - ruby "require 'bundler'; Bundler.setup", :env => { "DEBUG" => "1" } + ruby "require 'bundler'; Bundler.setup", env: { "DEBUG" => "1" } expect(out).to include("Assuming that source at `vendor/foo` has not changed since fetching its specs errored") expect(out).to include("Found no changes, using resolution from the lockfile") expect(err).to be_empty @@ -659,8 +633,35 @@ RSpec.describe "Bundler.setup" do gem "depends_on_bundler" G - ruby "require '#{system_gem_path("gems/bundler-9.99.9.beta1/lib/bundler.rb")}'; Bundler.setup", :env => { "DEBUG" => "1" } + ruby "require '#{system_gem_path("gems/bundler-9.99.9.beta1/lib/bundler.rb")}'; Bundler.setup", env: { "DEBUG" => "1" } expect(out).to include("Found no changes, using resolution from the lockfile") + expect(out).not_to include("lockfile does not have all gems needed for the current platform") + expect(err).to be_empty + end + + it "doesn't fail in frozen mode when bundler is a Gemfile dependency" do + install_gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + gem "bundler" + G + + bundle "install --verbose", env: { "BUNDLE_FROZEN" => "true" } + expect(err).to be_empty + end + + it "doesn't re-resolve when deleting dependencies" do + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "rack" + gem "actionpack" + G + + install_gemfile <<-G, verbose: true + source "#{file_uri_for(gem_repo1)}" + gem "rack" + G + + expect(out).to include("Some dependencies were deleted, using a subset of the resolution from the lockfile") expect(err).to be_empty end @@ -679,15 +680,15 @@ RSpec.describe "Bundler.setup" do end G - expect(the_bundle).not_to include_gems "activesupport 2.3.2", :groups => :rack - expect(the_bundle).to include_gems "rack 1.0.0", :groups => :rack + expect(the_bundle).not_to include_gems "activesupport 2.3.2", groups: :rack + expect(the_bundle).to include_gems "rack 1.0.0", groups: :rack end end # RubyGems returns loaded_from as a string it "has loaded_from as a string on all specs" do build_git "foo" - build_git "no-gemspec", :gemspec => false + build_git "no-gemspec", gemspec: false install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -727,6 +728,27 @@ end R run <<-R + File.open(File.join(Gem.dir, "specifications", "broken-ext.gemspec"), "w") do |f| + f.write <<-RUBY +# -*- encoding: utf-8 -*- +# stub: broken-ext 1.0.0 ruby lib +# stub: a.ext\\0b.ext + +Gem::Specification.new do |s| + s.name = "broken-ext" + s.version = "1.0.0" + raise "BROKEN GEMSPEC EXT" +end + RUBY + end + # Need to write the gem.build_complete file, + # otherwise the full spec is loaded to check the installed_by_version + extensions_dir = Gem.default_ext_dir_for(Gem.dir) || File.join(Gem.dir, "extensions", Gem::Platform.local.to_s, Gem.extension_api_version) + Bundler::FileUtils.mkdir_p(File.join(extensions_dir, "broken-ext-1.0.0")) + File.open(File.join(extensions_dir, "broken-ext-1.0.0", "gem.build_complete"), "w") {} + R + + run <<-R puts "WIN" R @@ -745,41 +767,68 @@ end expect(err).to be_empty end - describe "$MANPATH" do - before do + context "when the user has `MANPATH` set", :man do + before { ENV["MANPATH"] = "/foo#{File::PATH_SEPARATOR}" } + + it "adds the gem's man dir to the MANPATH" do build_repo4 do build_gem "with_man" do |s| s.write("man/man1/page.1", "MANPAGE") end end + + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "with_man" + G + + run "puts ENV['MANPATH']" + expect(out).to eq("#{default_bundle_path("gems/with_man-1.0/man")}#{File::PATH_SEPARATOR}/foo") end + end - context "when the user has one set" do - before { ENV["MANPATH"] = "/foo#{File::PATH_SEPARATOR}" } + context "when the user does not have `MANPATH` set", :man do + before { ENV.delete("MANPATH") } - it "adds the gem's man dir to the MANPATH" do - install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" - gem "with_man" - G + it "adds the gem's man dir to the MANPATH, leaving : in the end so that system man pages still work" do + build_repo4 do + build_gem "with_man" do |s| + s.write("man/man1/page.1", "MANPAGE") + end - run "puts ENV['MANPATH']" - expect(out).to eq("#{default_bundle_path("gems/with_man-1.0/man")}#{File::PATH_SEPARATOR}/foo") + build_gem "with_man_overriding_system_man" do |s| + s.write("man/man1/ls.1", "LS MANPAGE") + end end - end - context "when the user does not have one set" do - before { ENV.delete("MANPATH") } + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "with_man" + G - it "adds the gem's man dir to the MANPATH" do - install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" - gem "with_man" - G + run <<~RUBY + puts ENV['MANPATH'] + require "open3" + puts Open3.capture2e("man", "ls")[1].success? + RUBY - run "puts ENV['MANPATH']" - expect(out).to eq(default_bundle_path("gems/with_man-1.0/man").to_s) - end + expect(out).to eq("#{default_bundle_path("gems/with_man-1.0/man")}#{File::PATH_SEPARATOR}\ntrue") + + install_gemfile <<-G + source "#{file_uri_for(gem_repo4)}" + gem "with_man_overriding_system_man" + G + + run <<~RUBY + puts ENV['MANPATH'] + require "open3" + puts Open3.capture2e({ "LC_ALL" => "C" }, "man", "ls")[0] + RUBY + + lines = out.split("\n") + + expect(lines).to include("#{default_bundle_path("gems/with_man_overriding_system_man-1.0/man")}#{File::PATH_SEPARATOR}") + expect(lines).to include("LS MANPAGE") end end @@ -801,7 +850,7 @@ end expect(out).to eq("yay") end - it "should clean $LOAD_PATH properly", :ruby_repo do + it "should clean $LOAD_PATH properly" do gem_name = "very_simple_binary" full_gem_name = gem_name + "-1.0" ext_dir = File.join(tmp("extensions", full_gem_name)) @@ -834,23 +883,21 @@ end context "with bundler is located in symlinked GEM_HOME" do let(:gem_home) { Dir.mktmpdir } - let(:symlinked_gem_home) { Tempfile.new("gem_home").path } + let(:symlinked_gem_home) { tmp("gem_home-symlink").to_s } let(:full_name) { "bundler-#{Bundler::VERSION}" } before do - skip "symlink destination exists" if Gem.win_platform? - - FileUtils.ln_sf(gem_home, symlinked_gem_home) + File.symlink(gem_home, symlinked_gem_home) gems_dir = File.join(gem_home, "gems") specifications_dir = File.join(gem_home, "specifications") Dir.mkdir(gems_dir) Dir.mkdir(specifications_dir) - FileUtils.ln_s(source_root, File.join(gems_dir, full_name)) + File.symlink(source_root, File.join(gems_dir, full_name)) gemspec_content = File.binread(gemspec). sub("Bundler::VERSION", %("#{Bundler::VERSION}")). - lines.reject {|line| line =~ %r{lib/bundler/version} }.join + lines.reject {|line| line.include?("lib/bundler/version") }.join File.open(File.join(specifications_dir, "#{full_name}.gemspec"), "wb") do |f| f.write(gemspec_content) @@ -860,7 +907,7 @@ end it "should not remove itself from the LOAD_PATH and require a different copy of 'bundler/setup'" do install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" - ruby <<-R, :env => { "GEM_PATH" => symlinked_gem_home } + ruby <<-R, env: { "GEM_PATH" => symlinked_gem_home } TracePoint.trace(:class) do |tp| if tp.path.include?("bundler") && !tp.path.start_with?("#{source_root}") puts "OMG. Defining a class from another bundler at \#{tp.path}:\#{tp.lineno}" @@ -900,7 +947,7 @@ end it "should resolve paths relative to the Gemfile" do path = bundled_app(File.join("vendor", "foo")) - build_lib "foo", :path => path + build_lib "foo", path: path # If the .gemspec exists, then Bundler handles the path differently. # See Source::Path.load_spec_files for details. @@ -911,7 +958,7 @@ end gem 'foo', '1.2.3', :path => 'vendor/foo' G - run <<-R, :env => { "BUNDLE_GEMFILE" => bundled_app_gemfile.to_s }, :dir => bundled_app.parent + run <<-R, env: { "BUNDLE_GEMFILE" => bundled_app_gemfile.to_s }, dir: bundled_app.parent require 'foo' R expect(err).to be_empty @@ -921,7 +968,7 @@ end relative_path = File.join("vendor", Dir.pwd.gsub(/^#{filesystem_root}/, "")) absolute_path = bundled_app(relative_path) FileUtils.mkdir_p(absolute_path) - build_lib "foo", :path => absolute_path + build_lib "foo", path: absolute_path # If the .gemspec exists, then Bundler handles the path differently. # See Source::Path.load_spec_files for details. @@ -934,7 +981,7 @@ end bundle :install - run <<-R, :env => { "BUNDLE_GEMFILE" => bundled_app_gemfile.to_s }, :dir => bundled_app.parent + run <<-R, env: { "BUNDLE_GEMFILE" => bundled_app_gemfile.to_s }, dir: bundled_app.parent require 'foo' R @@ -944,7 +991,7 @@ end describe "with git gems that don't have gemspecs" do before :each do - build_git "no_gemspec", :gemspec => false + build_git "no_gemspec", gemspec: false install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -1034,7 +1081,7 @@ end describe "with a gemspec that requires other files" do before :each do - build_git "bar", :gemspec => false do |s| + build_git "bar", gemspec: false do |s| s.write "lib/bar/version.rb", %(BAR_VERSION = '1.0') s.write "bar.gemspec", <<-G require_relative 'lib/bar/version' @@ -1064,10 +1111,10 @@ end it "error intelligently if the gemspec has a LoadError" do skip "whitespace issue?" if Gem.win_platform? - ref = update_git "bar", :gemspec => false do |s| + ref = update_git "bar", gemspec: false do |s| s.write "bar.gemspec", "require 'foobarbaz'" end.ref_for("HEAD") - bundle :install, :raise_on_error => false + bundle :install, raise_on_error: false expect(err.lines.map(&:chomp)).to include( a_string_starting_with("[!] There was an error while loading `bar.gemspec`:"), @@ -1108,7 +1155,7 @@ end describe "when BUNDLED WITH" do def lock_with(bundler_version = nil) - lock = <<-L + lock = <<~L GEM remote: #{file_uri_for(gem_repo1)}/ specs: @@ -1122,7 +1169,7 @@ end L if bundler_version - lock += "\n BUNDLED WITH\n #{bundler_version}\n" + lock += "\nBUNDLED WITH\n #{bundler_version}\n" end lock @@ -1140,8 +1187,8 @@ end context "is not present" do it "does not change the lock" do lockfile lock_with(nil) - ruby "require '#{entrypoint}/setup'" - lockfile_should_be lock_with(nil) + ruby "require 'bundler/setup'" + expect(lockfile).to eq lock_with(nil) end end @@ -1151,7 +1198,7 @@ end ruby "require 'bundler/setup'" expect(out).to be_empty expect(err).to be_empty - lockfile_should_be lock_with(Bundler::VERSION.succ) + expect(lockfile).to eq lock_with(Bundler::VERSION.succ) end end @@ -1159,8 +1206,8 @@ end it "does not change the lock" do system_gems "bundler-1.10.1" lockfile lock_with("1.10.1") - ruby "require '#{entrypoint}/setup'" - lockfile_should_be lock_with("1.10.1") + ruby "require 'bundler/setup'" + expect(lockfile).to eq lock_with("1.10.1") end end end @@ -1169,7 +1216,11 @@ end let(:ruby_version) { nil } def lock_with(ruby_version = nil) - lock = <<-L + checksums = checksums_section do |c| + c.checksum gem_repo1, "rack", "1.0.0" + end + + lock = <<~L GEM remote: #{file_uri_for(gem_repo1)}/ specs: @@ -1180,13 +1231,14 @@ end DEPENDENCIES rack + #{checksums} L if ruby_version - lock += "\n RUBY VERSION\n ruby #{ruby_version}\n" + lock += "\nRUBY VERSION\n ruby #{ruby_version}\n" end - lock += <<-L + lock += <<~L BUNDLED WITH #{Bundler::VERSION} @@ -1228,10 +1280,43 @@ end end describe "with gemified standard libraries" do + it "does not load Digest", :ruby_repo do + build_git "bar", gemspec: false do |s| + s.write "lib/bar/version.rb", %(BAR_VERSION = '1.0') + s.write "bar.gemspec", <<-G + require_relative 'lib/bar/version' + + Gem::Specification.new do |s| + s.name = 'bar' + s.version = BAR_VERSION + s.summary = 'Bar' + s.files = Dir["lib/**/*.rb"] + s.author = 'no one' + + s.add_runtime_dependency 'digest' + end + G + end + + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "bar", :git => "#{lib_path("bar-1.0")}" + G + + bundle :install + + ruby <<-RUBY + require 'bundler/setup' + puts defined?(::Digest) ? "Digest defined" : "Digest undefined" + require 'digest' + RUBY + expect(out).to eq("Digest undefined") + end + it "does not load Psych" do gemfile "source \"#{file_uri_for(gem_repo1)}\"" ruby <<-RUBY - require '#{entrypoint}/setup' + require 'bundler/setup' puts defined?(Psych::VERSION) ? Psych::VERSION : "undefined" require 'psych' puts Psych::VERSION @@ -1252,22 +1337,46 @@ end expect(out).to eq("undefined\nconstant") end + it "does not load uri while reading gemspecs", rubygems: ">= 3.6.0.dev" do + Dir.mkdir bundled_app("test") + + create_file(bundled_app("test/test.gemspec"), <<-G) + Gem::Specification.new do |s| + s.name = "test" + s.version = "1.0.0" + s.summary = "test" + s.authors = ['John Doe'] + s.homepage = 'https://example.com' + end + G + + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "test", path: "#{bundled_app("test")}" + G + + ruby <<-RUBY + require "bundler/setup" + puts defined?(URI) || "undefined" + require "uri" + puts defined?(URI) || "undefined" + RUBY + expect(out).to eq("undefined\nconstant") + end + describe "default gem activation" do let(:exemptions) do - exempts = if Gem.rubygems_version >= Gem::Version.new("2.7") - %w[did_you_mean] - else - %w[io-console openssl] - end << "bundler" - exempts << "fiddle" if Gem.win_platform? && Gem.rubygems_version >= Gem::Version.new("2.7") - exempts << "uri" if Gem.ruby_version >= Gem::Version.new("2.7") - exempts << "pathname" if Gem.ruby_version >= Gem::Version.new("3.0") + exempts = %w[did_you_mean bundler uri pathname] + exempts << "etc" if Gem.ruby_version < Gem::Version.new("3.2") && Gem.win_platform? exempts << "set" unless Gem.rubygems_version >= Gem::Version.new("3.2.6") - exempts << "tsort" if Gem.ruby_version >= Gem::Version.new("3.0") + exempts << "tsort" unless Gem.rubygems_version >= Gem::Version.new("3.2.31") + exempts << "error_highlight" # added in Ruby 3.1 as a default gem + exempts << "ruby2_keywords" # added in Ruby 3.1 as a default gem + exempts << "syntax_suggest" # added in Ruby 3.2 as a default gem exempts end - let(:activation_warning_hack) { strip_whitespace(<<-RUBY) } + let(:activation_warning_hack) { <<~RUBY } require #{spec_dir.join("support/hax").to_s.dump} Gem::Specification.send(:alias_method, :bundler_spec_activate, :activate) @@ -1287,7 +1396,7 @@ end "-r#{bundled_app("activation_warning_hack.rb")} #{ENV["RUBYOPT"]}" end - let(:code) { strip_whitespace(<<-RUBY) } + let(:code) { <<~RUBY } require "pp" loaded_specs = Gem.loaded_specs.dup #{exemptions.inspect}.each {|s| loaded_specs.delete(s) } @@ -1302,14 +1411,14 @@ end it "activates no gems with -rbundler/setup" do install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" - ruby code, :env => { "RUBYOPT" => activation_warning_hack_rubyopt + " -rbundler/setup" } + ruby code, env: { "RUBYOPT" => activation_warning_hack_rubyopt + " -rbundler/setup" } expect(out).to eq("{}") end it "activates no gems with bundle exec" do install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" create_file("script.rb", code) - bundle "exec ruby ./script.rb", :env => { "RUBYOPT" => activation_warning_hack_rubyopt } + bundle "exec ruby ./script.rb", env: { "RUBYOPT" => activation_warning_hack_rubyopt } expect(out).to eq("{}") end @@ -1319,7 +1428,7 @@ end install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" create_file("script.rb", "#!/usr/bin/env ruby\n\n#{code}") FileUtils.chmod(0o777, bundled_app("script.rb")) - bundle "exec ./script.rb", :artifice => nil, :env => { "RUBYOPT" => activation_warning_hack_rubyopt } + bundle "exec ./script.rb", artifice: nil, env: { "RUBYOPT" => activation_warning_hack_rubyopt } expect(out).to eq("{}") end @@ -1328,7 +1437,7 @@ end build_gem "net-http-pipeline", "1.0.1" end - system_gems "net-http-pipeline-1.0.1", :gem_repo => gem_repo4 + system_gems "net-http-pipeline-1.0.1", gem_repo: gem_repo4 gemfile <<-G source "#{file_uri_for(gem_repo4)}" @@ -1357,7 +1466,7 @@ end gem "#{g}", "999999" G - expect(the_bundle).to include_gem("#{g} 999999", :env => { "RUBYOPT" => activation_warning_hack_rubyopt }) + expect(the_bundle).to include_gem("#{g} 999999", env: { "RUBYOPT" => activation_warning_hack_rubyopt }) end it "activates older versions of #{g}", :ruby_repo do @@ -1372,14 +1481,14 @@ end gem "#{g}", "0.0.0.a" G - expect(the_bundle).to include_gem("#{g} 0.0.0.a", :env => { "RUBYOPT" => activation_warning_hack_rubyopt }) + expect(the_bundle).to include_gem("#{g} 0.0.0.a", env: { "RUBYOPT" => activation_warning_hack_rubyopt }) end end end end describe "after setup" do - it "allows calling #gem on random objects", :bundler => "< 3" do + it "allows calling #gem on random objects", bundler: "< 3" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" @@ -1394,13 +1503,13 @@ end expect(out).to eq("rack-1.0.0") end - it "keeps Kernel#gem private", :bundler => "3" do + it "keeps Kernel#gem private", bundler: "3" do install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rack" G - ruby <<-RUBY, :raise_on_error => false + ruby <<-RUBY, raise_on_error: false require "bundler/setup" Object.new.gem "rack" puts "FAIL" @@ -1416,20 +1525,93 @@ end gem "rack" G - ruby <<-RUBY, :raise_on_error => false + ruby <<-RUBY, raise_on_error: false require "bundler/setup" Object.new.require "rack" puts "FAIL" RUBY expect(last_command.stdboth).not_to include "FAIL" - expect(err).to include "private method `require'" + expect(err).to match(/private method [`']require'/) end - it "takes care of requiring rubygems" do - sys_exec("#{Gem.ruby} -I#{lib_dir} -rbundler/setup -e'puts true'", :env => { "RUBYOPT" => opt_add("--disable=gems", ENV["RUBYOPT"]) }) + it "memoizes initial set of specs when requiring bundler/setup, so that even if further code mutates dependencies, Bundler.definition.specs is not affected" do + install_gemfile <<~G + source "#{file_uri_for(gem_repo1)}" + gem "yard" + gem "rack", :group => :test + G + + ruby <<-RUBY, raise_on_error: false + require "bundler/setup" + Bundler.require(:test).select! {|d| (d.groups & [:test]).any? } + puts Bundler.definition.specs.map(&:name).join(", ") + RUBY - expect(last_command.stdboth).to eq("true") + expect(out).to include("rack, yard") end + + it "does not cause double loads when higher versions of default gems are activated before bundler" do + build_repo2 do + build_gem "json", "999.999.999" do |s| + s.write "lib/json.rb", <<~RUBY + module JSON + VERSION = "999.999.999" + end + RUBY + end + end + + system_gems "json-999.999.999", gem_repo: gem_repo2 + + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" + ruby <<-RUBY + require "json" + require "bundler/setup" + require "json" + RUBY + + expect(err).to be_empty + end + end + + it "does not undo the Kernel.require decorations", rubygems: ">= 3.4.6" do + install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" + script = bundled_app("bin/script") + create_file(script, <<~RUBY) + module Kernel + module_function + + alias_method :require_before_extra_monkeypatches, :require + + def require(path) + puts "requiring \#{path} used the monkeypatch" + + require_before_extra_monkeypatches(path) + end + end + + require "bundler/setup" + + require "foo" + RUBY + + sys_exec "#{Gem.ruby} #{script}", raise_on_error: false + expect(out).to include("requiring foo used the monkeypatch") + end + + it "performs an automatic bundle install" do + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "rack", :group => :test + G + + bundle "config set auto_install 1" + + ruby <<-RUBY + require 'bundler/setup' + RUBY + expect(err).to be_empty + expect(out).to include("Installing rack 1.0.0") end end diff --git a/spec/bundler/runtime/with_unbundled_env_spec.rb b/spec/bundler/runtime/with_unbundled_env_spec.rb index 731a9921a2..135c71b0af 100644 --- a/spec/bundler/runtime/with_unbundled_env_spec.rb +++ b/spec/bundler/runtime/with_unbundled_env_spec.rb @@ -90,9 +90,7 @@ RSpec.describe "Bundler.with_env helpers" do RUBY setup_require = "-r#{lib_dir}/bundler/setup" ENV["BUNDLER_ORIG_RUBYOPT"] = "-W2 #{setup_require} #{ENV["RUBYOPT"]}" - simulate_bundler_version_when_missing_prerelease_default_gem_activation do - bundle_exec_ruby bundled_app("source.rb") - end + bundle_exec_ruby bundled_app("source.rb") expect(last_command.stdboth).not_to include(setup_require) end @@ -101,9 +99,7 @@ RSpec.describe "Bundler.with_env helpers" do print #{modified_env}['RUBYOPT'] RUBY ENV["BUNDLER_ORIG_RUBYOPT"] = "-W2 -rbundler/setup #{ENV["RUBYOPT"]}" - simulate_bundler_version_when_missing_prerelease_default_gem_activation do - bundle_exec_ruby bundled_app("source.rb") - end + bundle_exec_ruby bundled_app("source.rb") expect(last_command.stdboth).not_to include("-rbundler/setup") end @@ -134,7 +130,7 @@ RSpec.describe "Bundler.with_env helpers" do it_behaves_like "an unbundling helper" end - describe "Bundler.clean_env", :bundler => 2 do + describe "Bundler.clean_env", bundler: 2 do let(:modified_env) { "Bundler.clean_env" } it_behaves_like "an unbundling helper" @@ -143,7 +139,7 @@ RSpec.describe "Bundler.with_env helpers" do describe "Bundler.with_original_env" do it "should set ENV to original_env in the block" do expected = Bundler.original_env - actual = Bundler.with_original_env { Bundler::EnvironmentPreserver.env_to_hash(ENV) } + actual = Bundler.with_original_env { ENV.to_hash } expect(actual).to eq(expected) end @@ -156,12 +152,12 @@ RSpec.describe "Bundler.with_env helpers" do end end - describe "Bundler.with_clean_env", :bundler => 2 do + describe "Bundler.with_clean_env", bundler: 2 do it "should set ENV to unbundled_env in the block" do expected = Bundler.unbundled_env actual = Bundler.ui.silence do - Bundler.with_clean_env { Bundler::EnvironmentPreserver.env_to_hash(ENV) } + Bundler.with_clean_env { ENV.to_hash } end expect(actual).to eq(expected) @@ -179,7 +175,7 @@ RSpec.describe "Bundler.with_env helpers" do describe "Bundler.with_unbundled_env" do it "should set ENV to unbundled_env in the block" do expected = Bundler.unbundled_env - actual = Bundler.with_unbundled_env { Bundler::EnvironmentPreserver.env_to_hash(ENV) } + actual = Bundler.with_unbundled_env { ENV.to_hash } expect(actual).to eq(expected) end @@ -207,7 +203,7 @@ RSpec.describe "Bundler.with_env helpers" do end end - describe "Bundler.clean_system", :bundler => 2 do + describe "Bundler.clean_system", bundler: 2 do before do create_file("source.rb", <<-'RUBY') Bundler.ui.silence { Bundler.clean_system("ruby", "-e", "exit(42) unless ENV['BUNDLE_FOO'] == 'bar'") } @@ -258,7 +254,7 @@ RSpec.describe "Bundler.with_env helpers" do end end - describe "Bundler.clean_exec", :bundler => 2 do + describe "Bundler.clean_exec", bundler: 2 do before do create_file("source.rb", <<-'RUBY') Process.fork do diff --git a/spec/bundler/spec_helper.rb b/spec/bundler/spec_helper.rb index 80b0ccff4d..66bdcfa028 100644 --- a/spec/bundler/spec_helper.rb +++ b/spec/bundler/spec_helper.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "bundler/psyched_yaml" +require "psych" require "bundler/vendored_fileutils" require "bundler/vendored_uri" require "digest" @@ -17,13 +17,13 @@ require "rspec/support/differ" require_relative "support/builders" require_relative "support/build_metadata" +require_relative "support/checksums" require_relative "support/filters" require_relative "support/helpers" require_relative "support/indexes" require_relative "support/matchers" require_relative "support/permissions" require_relative "support/platforms" -require_relative "support/sudo" $debug = false @@ -35,12 +35,12 @@ end RSpec.configure do |config| config.include Spec::Builders + config.include Spec::Checksums config.include Spec::Helpers config.include Spec::Indexes config.include Spec::Matchers config.include Spec::Path config.include Spec::Platforms - config.include Spec::Sudo config.include Spec::Permissions # Enable flags like --only-failures and --next-failure @@ -48,6 +48,9 @@ RSpec.configure do |config| config.silence_filter_announcements = !ENV["TEST_ENV_NUMBER"].nil? + config.backtrace_exclusion_patterns << + %r{./spec/(spec_helper\.rb|support/.+)} + config.disable_monkey_patching! # Since failures cause us to keep a bunch of long strings in memory, stop @@ -60,6 +63,8 @@ RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = :expect + + c.max_formatted_output_length = 1000 end config.mock_with :rspec do |mocks| @@ -71,8 +76,11 @@ RSpec.configure do |config| require_relative "support/rubygems_ext" Spec::Rubygems.test_setup - ENV["BUNDLE_SPEC_RUN"] = "true" + ENV["BUNDLER_SPEC_RUN"] = "true" ENV["BUNDLE_USER_CONFIG"] = ENV["BUNDLE_USER_CACHE"] = ENV["BUNDLE_USER_PLUGIN"] = nil + ENV["BUNDLE_APP_CONFIG"] = nil + ENV["BUNDLE_SILENCE_ROOT_WARNING"] = nil + ENV["RUBYGEMS_GEMDEPS"] = nil ENV["XDG_CONFIG_HOME"] = nil ENV["GEMRC"] = nil @@ -80,7 +88,7 @@ RSpec.configure do |config| ENV["THOR_COLUMNS"] = "10000" extend(Spec::Helpers) - system_gems :bundler, :path => pristine_system_gem_path + system_gems :bundler, path: pristine_system_gem_path end config.before :all do @@ -92,26 +100,24 @@ RSpec.configure do |config| end config.around :each do |example| - begin - FileUtils.cp_r pristine_system_gem_path, system_gem_path - - with_gem_path_as(system_gem_path) do - Bundler.ui.silence { example.run } - - all_output = all_commands_output - if example.exception && !all_output.empty? - message = all_output + "\n" + example.exception.message - (class << example.exception; self; end).send(:define_method, :message) do - message - end + FileUtils.cp_r pristine_system_gem_path, system_gem_path + + with_gem_path_as(system_gem_path) do + Bundler.ui.silence { example.run } + + all_output = all_commands_output + if example.exception && !all_output.empty? + message = all_output + "\n" + example.exception.message + (class << example.exception; self; end).send(:define_method, :message) do + message end end - ensure - reset! end + ensure + reset! end config.after :suite do - FileUtils.rm_r Spec::Path.pristine_system_gem_path + FileUtils.rm_rf Spec::Path.pristine_system_gem_path end end diff --git a/spec/bundler/support/activate.rb b/spec/bundler/support/activate.rb new file mode 100644 index 0000000000..143b77833d --- /dev/null +++ b/spec/bundler/support/activate.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require "rubygems" +Gem.instance_variable_set(:@ruby, ENV["RUBY"]) if ENV["RUBY"] + +require_relative "path" +bundler_gemspec = Spec::Path.loaded_gemspec +bundler_gemspec.instance_variable_set(:@full_gem_path, Spec::Path.source_root.to_s) +bundler_gemspec.activate if bundler_gemspec.respond_to?(:activate) diff --git a/spec/bundler/support/api_request_limit_hax.rb b/spec/bundler/support/api_request_limit_hax.rb deleted file mode 100644 index 37ff0203b3..0000000000 --- a/spec/bundler/support/api_request_limit_hax.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -if ENV["BUNDLER_SPEC_API_REQUEST_LIMIT"] - require_relative "path" - require "bundler/source" - require "bundler/source/rubygems" - - module Bundler - class Source - class Rubygems < Source - remove_const :API_REQUEST_LIMIT - API_REQUEST_LIMIT = ENV["BUNDLER_SPEC_API_REQUEST_LIMIT"].to_i - end - end - end -end diff --git a/spec/bundler/support/artifice/compact_index.rb b/spec/bundler/support/artifice/compact_index.rb index 1b314e89ef..ebc4d0ae5b 100644 --- a/spec/bundler/support/artifice/compact_index.rb +++ b/spec/bundler/support/artifice/compact_index.rb @@ -1,120 +1,6 @@ # frozen_string_literal: true -require_relative "endpoint" - -$LOAD_PATH.unshift Dir[Spec::Path.base_system_gems.join("gems/compact_index*/lib")].first.to_s -require "compact_index" - -class CompactIndexAPI < Endpoint - helpers do - include Spec::Path - - def load_spec(name, version, platform, gem_repo) - full_name = "#{name}-#{version}" - full_name += "-#{platform}" if platform != "ruby" - Marshal.load(Bundler.rubygems.inflate(File.binread(gem_repo.join("quick/Marshal.4.8/#{full_name}.gemspec.rz")))) - end - - def etag_response - response_body = yield - checksum = Digest(:MD5).hexdigest(response_body) - return if not_modified?(checksum) - headers "ETag" => quote(checksum) - headers "Surrogate-Control" => "max-age=2592000, stale-while-revalidate=60" - content_type "text/plain" - requested_range_for(response_body) - rescue StandardError => e - puts e - puts e.backtrace - raise - end - - def not_modified?(checksum) - etags = parse_etags(request.env["HTTP_IF_NONE_MATCH"]) - - return unless etags.include?(checksum) - headers "ETag" => quote(checksum) - status 304 - body "" - end - - def requested_range_for(response_body) - ranges = Rack::Utils.byte_ranges(env, response_body.bytesize) - - if ranges - status 206 - body ranges.map! {|range| slice_body(response_body, range) }.join - else - status 200 - body response_body - end - end - - def quote(string) - %("#{string}") - end - - def parse_etags(value) - value ? value.split(/, ?/).select {|s| s.sub!(/"(.*)"/, '\1') } : [] - end - - def slice_body(body, range) - body.byteslice(range) - end - - def gems(gem_repo = default_gem_repo) - @gems ||= {} - @gems[gem_repo] ||= begin - specs = Bundler::Deprecate.skip_during do - %w[specs.4.8 prerelease_specs.4.8].map do |filename| - Marshal.load(File.open(gem_repo.join(filename)).read).map do |name, version, platform| - load_spec(name, version, platform, gem_repo) - end - end.flatten - end - - specs.group_by(&:name).map do |name, versions| - gem_versions = versions.map do |spec| - deps = spec.dependencies.select {|d| d.type == :runtime }.map do |d| - reqs = d.requirement.requirements.map {|r| r.join(" ") }.join(", ") - CompactIndex::Dependency.new(d.name, reqs) - end - checksum = begin - Digest(:SHA256).file("#{gem_repo}/gems/#{spec.original_name}.gem").base64digest - rescue StandardError - nil - end - CompactIndex::GemVersion.new(spec.version.version, spec.platform.to_s, checksum, nil, - deps, spec.required_ruby_version.to_s, spec.required_rubygems_version.to_s) - end - CompactIndex::Gem.new(name, gem_versions) - end - end - end - end - - get "/names" do - etag_response do - CompactIndex.names(gems.map(&:name)) - end - end - - get "/versions" do - etag_response do - file = tmp("versions.list") - FileUtils.rm_f(file) - file = CompactIndex::VersionsFile.new(file.to_s) - file.create(gems) - file.contents - end - end - - get "/info/:name" do - etag_response do - gem = gems.find {|g| g.name == params[:name] } - CompactIndex.info(gem ? gem.versions : []) - end - end -end +require_relative "helpers/compact_index" +require_relative "helpers/artifice" Artifice.activate_with(CompactIndexAPI) diff --git a/spec/bundler/support/artifice/compact_index_api_missing.rb b/spec/bundler/support/artifice/compact_index_api_missing.rb index 6514fde01e..f771f7d1f0 100644 --- a/spec/bundler/support/artifice/compact_index_api_missing.rb +++ b/spec/bundler/support/artifice/compact_index_api_missing.rb @@ -1,18 +1,13 @@ # frozen_string_literal: true -require_relative "compact_index" - -Artifice.deactivate +require_relative "helpers/compact_index" class CompactIndexApiMissing < CompactIndexAPI get "/fetch/actual/gem/:id" do - warn params[:id] - if params[:id] == "rack-1.0.gemspec.rz" - halt 404 - else - File.binread("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}") - end + halt 404 end end +require_relative "helpers/artifice" + Artifice.activate_with(CompactIndexApiMissing) diff --git a/spec/bundler/support/artifice/compact_index_basic_authentication.rb b/spec/bundler/support/artifice/compact_index_basic_authentication.rb index 775f1a3977..b9115cdd86 100644 --- a/spec/bundler/support/artifice/compact_index_basic_authentication.rb +++ b/spec/bundler/support/artifice/compact_index_basic_authentication.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require_relative "compact_index" - -Artifice.deactivate +require_relative "helpers/compact_index" class CompactIndexBasicAuthentication < CompactIndexAPI before do @@ -12,4 +10,6 @@ class CompactIndexBasicAuthentication < CompactIndexAPI end end +require_relative "helpers/artifice" + Artifice.activate_with(CompactIndexBasicAuthentication) diff --git a/spec/bundler/support/artifice/compact_index_checksum_mismatch.rb b/spec/bundler/support/artifice/compact_index_checksum_mismatch.rb index 1abe64236c..83b147d2ae 100644 --- a/spec/bundler/support/artifice/compact_index_checksum_mismatch.rb +++ b/spec/bundler/support/artifice/compact_index_checksum_mismatch.rb @@ -1,16 +1,16 @@ # frozen_string_literal: true -require_relative "compact_index" - -Artifice.deactivate +require_relative "helpers/compact_index" class CompactIndexChecksumMismatch < CompactIndexAPI get "/versions" do - headers "ETag" => quote("123") + headers "Repr-Digest" => "sha-256=:ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0=:" headers "Surrogate-Control" => "max-age=2592000, stale-while-revalidate=60" content_type "text/plain" - body "" + body "content does not match the checksum" end end +require_relative "helpers/artifice" + Artifice.activate_with(CompactIndexChecksumMismatch) diff --git a/spec/bundler/support/artifice/compact_index_concurrent_download.rb b/spec/bundler/support/artifice/compact_index_concurrent_download.rb index 14c31f35a4..5d55b8a72b 100644 --- a/spec/bundler/support/artifice/compact_index_concurrent_download.rb +++ b/spec/bundler/support/artifice/compact_index_concurrent_download.rb @@ -1,19 +1,18 @@ # frozen_string_literal: true -require_relative "compact_index" - -Artifice.deactivate +require_relative "helpers/compact_index" class CompactIndexConcurrentDownload < CompactIndexAPI get "/versions" do versions = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index", "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions") - # Verify the original (empty) content hasn't been deleted, e.g. on a retry - File.binread(versions) == "" || raise("Original file should be present and empty") + # Verify the original content hasn't been deleted, e.g. on a retry + data = File.binread(versions) + data == "created_at" || raise("Original file should be present with expected content") # Verify this is only requested once for a partial download - env["HTTP_RANGE"] || raise("Missing Range header for expected partial download") + env["HTTP_RANGE"] == "bytes=#{data.bytesize - 1}-" || raise("Missing Range header for expected partial download") # Overwrite the file in parallel, which should be then overwritten # after a successful download to prevent corruption @@ -29,4 +28,6 @@ class CompactIndexConcurrentDownload < CompactIndexAPI end end +require_relative "helpers/artifice" + Artifice.activate_with(CompactIndexConcurrentDownload) diff --git a/spec/bundler/support/artifice/compact_index_creds_diff_host.rb b/spec/bundler/support/artifice/compact_index_creds_diff_host.rb index cfe22c7f51..401e8a98d8 100644 --- a/spec/bundler/support/artifice/compact_index_creds_diff_host.rb +++ b/spec/bundler/support/artifice/compact_index_creds_diff_host.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require_relative "compact_index" - -Artifice.deactivate +require_relative "helpers/compact_index" class CompactIndexCredsDiffHost < CompactIndexAPI helpers do @@ -36,4 +34,6 @@ class CompactIndexCredsDiffHost < CompactIndexAPI end end +require_relative "helpers/artifice" + Artifice.activate_with(CompactIndexCredsDiffHost) diff --git a/spec/bundler/support/artifice/compact_index_etag_match.rb b/spec/bundler/support/artifice/compact_index_etag_match.rb new file mode 100644 index 0000000000..08d7b5ec53 --- /dev/null +++ b/spec/bundler/support/artifice/compact_index_etag_match.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require_relative "helpers/compact_index" + +class CompactIndexEtagMatch < CompactIndexAPI + get "/versions" do + raise "ETag header should be present" unless env["HTTP_IF_NONE_MATCH"] + headers "ETag" => env["HTTP_IF_NONE_MATCH"] + status 304 + body "" + end +end + +require_relative "helpers/artifice" + +Artifice.activate_with(CompactIndexEtagMatch) diff --git a/spec/bundler/support/artifice/compact_index_extra.rb b/spec/bundler/support/artifice/compact_index_extra.rb index cec368276a..cd41b3ecca 100644 --- a/spec/bundler/support/artifice/compact_index_extra.rb +++ b/spec/bundler/support/artifice/compact_index_extra.rb @@ -1,37 +1,6 @@ # frozen_string_literal: true -require_relative "compact_index" - -Artifice.deactivate - -class CompactIndexExtra < CompactIndexAPI - get "/extra/versions" do - halt 404 - end - - get "/extra/api/v1/dependencies" do - halt 404 - end - - get "/extra/specs.4.8.gz" do - File.binread("#{gem_repo2}/specs.4.8.gz") - end - - get "/extra/prerelease_specs.4.8.gz" do - File.binread("#{gem_repo2}/prerelease_specs.4.8.gz") - end - - get "/extra/quick/Marshal.4.8/:id" do - redirect "/extra/fetch/actual/gem/#{params[:id]}" - end - - get "/extra/fetch/actual/gem/:id" do - File.binread("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}") - end - - get "/extra/gems/:id" do - File.binread("#{gem_repo2}/gems/#{params[:id]}") - end -end +require_relative "helpers/compact_index_extra" +require_relative "helpers/artifice" Artifice.activate_with(CompactIndexExtra) diff --git a/spec/bundler/support/artifice/compact_index_extra_api.rb b/spec/bundler/support/artifice/compact_index_extra_api.rb index 5cc13421a8..8b9d304ab4 100644 --- a/spec/bundler/support/artifice/compact_index_extra_api.rb +++ b/spec/bundler/support/artifice/compact_index_extra_api.rb @@ -1,52 +1,6 @@ # frozen_string_literal: true -require_relative "compact_index" - -Artifice.deactivate - -class CompactIndexExtraApi < CompactIndexAPI - get "/extra/names" do - etag_response do - CompactIndex.names(gems(gem_repo4).map(&:name)) - end - end - - get "/extra/versions" do - etag_response do - file = tmp("versions.list") - FileUtils.rm_f(file) - file = CompactIndex::VersionsFile.new(file.to_s) - file.create(gems(gem_repo4)) - file.contents - end - end - - get "/extra/info/:name" do - etag_response do - gem = gems(gem_repo4).find {|g| g.name == params[:name] } - CompactIndex.info(gem ? gem.versions : []) - end - end - - get "/extra/specs.4.8.gz" do - File.binread("#{gem_repo4}/specs.4.8.gz") - end - - get "/extra/prerelease_specs.4.8.gz" do - File.binread("#{gem_repo4}/prerelease_specs.4.8.gz") - end - - get "/extra/quick/Marshal.4.8/:id" do - redirect "/extra/fetch/actual/gem/#{params[:id]}" - end - - get "/extra/fetch/actual/gem/:id" do - File.binread("#{gem_repo4}/quick/Marshal.4.8/#{params[:id]}") - end - - get "/extra/gems/:id" do - File.binread("#{gem_repo4}/gems/#{params[:id]}") - end -end +require_relative "helpers/compact_index_extra_api" +require_relative "helpers/artifice" Artifice.activate_with(CompactIndexExtraApi) diff --git a/spec/bundler/support/artifice/compact_index_extra_api_missing.rb b/spec/bundler/support/artifice/compact_index_extra_api_missing.rb index b9d757c266..df6ede584c 100644 --- a/spec/bundler/support/artifice/compact_index_extra_api_missing.rb +++ b/spec/bundler/support/artifice/compact_index_extra_api_missing.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require_relative "compact_index_extra_api" - -Artifice.deactivate +require_relative "helpers/compact_index_extra_api" class CompactIndexExtraAPIMissing < CompactIndexExtraApi get "/extra/fetch/actual/gem/:id" do @@ -14,4 +12,6 @@ class CompactIndexExtraAPIMissing < CompactIndexExtraApi end end +require_relative "helpers/artifice" + Artifice.activate_with(CompactIndexExtraAPIMissing) diff --git a/spec/bundler/support/artifice/compact_index_extra_missing.rb b/spec/bundler/support/artifice/compact_index_extra_missing.rb index ff1e47a1bb..255c89afdb 100644 --- a/spec/bundler/support/artifice/compact_index_extra_missing.rb +++ b/spec/bundler/support/artifice/compact_index_extra_missing.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require_relative "compact_index_extra" - -Artifice.deactivate +require_relative "helpers/compact_index_extra" class CompactIndexExtraMissing < CompactIndexExtra get "/extra/fetch/actual/gem/:id" do @@ -14,4 +12,6 @@ class CompactIndexExtraMissing < CompactIndexExtra end end +require_relative "helpers/artifice" + Artifice.activate_with(CompactIndexExtraMissing) diff --git a/spec/bundler/support/artifice/compact_index_forbidden.rb b/spec/bundler/support/artifice/compact_index_forbidden.rb index 3eebe0fbd8..18c30ed9a2 100644 --- a/spec/bundler/support/artifice/compact_index_forbidden.rb +++ b/spec/bundler/support/artifice/compact_index_forbidden.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require_relative "compact_index" - -Artifice.deactivate +require_relative "helpers/compact_index" class CompactIndexForbidden < CompactIndexAPI get "/versions" do @@ -10,4 +8,6 @@ class CompactIndexForbidden < CompactIndexAPI end end +require_relative "helpers/artifice" + Artifice.activate_with(CompactIndexForbidden) diff --git a/spec/bundler/support/artifice/compact_index_host_redirect.rb b/spec/bundler/support/artifice/compact_index_host_redirect.rb index 304c897d68..4f82bf3812 100644 --- a/spec/bundler/support/artifice/compact_index_host_redirect.rb +++ b/spec/bundler/support/artifice/compact_index_host_redirect.rb @@ -1,11 +1,9 @@ # frozen_string_literal: true -require_relative "compact_index" - -Artifice.deactivate +require_relative "helpers/compact_index" class CompactIndexHostRedirect < CompactIndexAPI - get "/fetch/actual/gem/:id", :host_name => "localgemserver.test" do + get "/fetch/actual/gem/:id", host_name: "localgemserver.test" do redirect "http://bundler.localgemserver.test#{request.path_info}" end @@ -18,4 +16,6 @@ class CompactIndexHostRedirect < CompactIndexAPI end end +require_relative "helpers/artifice" + Artifice.activate_with(CompactIndexHostRedirect) diff --git a/spec/bundler/support/artifice/compact_index_no_gem.rb b/spec/bundler/support/artifice/compact_index_no_gem.rb index 0a4be08a46..71f6629688 100644 --- a/spec/bundler/support/artifice/compact_index_no_gem.rb +++ b/spec/bundler/support/artifice/compact_index_no_gem.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require_relative "compact_index" - -Artifice.deactivate +require_relative "helpers/compact_index" class CompactIndexNoGem < CompactIndexAPI get "/gems/:id" do @@ -10,4 +8,6 @@ class CompactIndexNoGem < CompactIndexAPI end end +require_relative "helpers/artifice" + Artifice.activate_with(CompactIndexNoGem) diff --git a/spec/bundler/support/artifice/compact_index_partial_update.rb b/spec/bundler/support/artifice/compact_index_partial_update.rb index cb1c7b9481..f111d91ef9 100644 --- a/spec/bundler/support/artifice/compact_index_partial_update.rb +++ b/spec/bundler/support/artifice/compact_index_partial_update.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require_relative "compact_index" - -Artifice.deactivate +require_relative "helpers/compact_index" class CompactIndexPartialUpdate < CompactIndexAPI # Stub the server to never return 304s. This simulates the behaviour of @@ -25,7 +23,7 @@ class CompactIndexPartialUpdate < CompactIndexAPI # Verify that a partial request is made, starting from the index of the # final byte of the cached file. unless env["HTTP_RANGE"] == "bytes=#{File.binread(cached_versions_path).bytesize - 1}-" - raise("Range header should be present, and start from the index of the final byte of the cache.") + raise("Range header should be present, and start from the index of the final byte of the cache. #{env["HTTP_RANGE"].inspect}") end etag_response do @@ -35,4 +33,6 @@ class CompactIndexPartialUpdate < CompactIndexAPI end end +require_relative "helpers/artifice" + Artifice.activate_with(CompactIndexPartialUpdate) diff --git a/spec/bundler/support/artifice/compact_index_partial_update_bad_digest.rb b/spec/bundler/support/artifice/compact_index_partial_update_bad_digest.rb new file mode 100644 index 0000000000..ac04336636 --- /dev/null +++ b/spec/bundler/support/artifice/compact_index_partial_update_bad_digest.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require_relative "helpers/compact_index" + +# The purpose of this Artifice is to test that an incremental response is invalidated +# and a second request is issued for the full content. +class CompactIndexPartialUpdateBadDigest < CompactIndexAPI + def partial_update_bad_digest + response_body = yield + if request.env["HTTP_RANGE"] + headers "Repr-Digest" => "sha-256=:#{Digest::SHA256.base64digest("wrong digest on ranged request")}:" + else + headers "Repr-Digest" => "sha-256=:#{Digest::SHA256.base64digest(response_body)}:" + end + headers "Surrogate-Control" => "max-age=2592000, stale-while-revalidate=60" + content_type "text/plain" + requested_range_for(response_body) + end + + get "/versions" do + partial_update_bad_digest do + file = tmp("versions.list") + FileUtils.rm_f(file) + file = CompactIndex::VersionsFile.new(file.to_s) + file.create(gems) + file.contents([], calculate_info_checksums: true) + end + end + + get "/info/:name" do + partial_update_bad_digest do + gem = gems.find {|g| g.name == params[:name] } + CompactIndex.info(gem ? gem.versions : []) + end + end +end + +require_relative "helpers/artifice" + +Artifice.activate_with(CompactIndexPartialUpdateBadDigest) diff --git a/spec/bundler/support/artifice/compact_index_partial_update_no_etag_not_incremental.rb b/spec/bundler/support/artifice/compact_index_partial_update_no_digest_not_incremental.rb index acf76dfbf0..99bae039f0 100644 --- a/spec/bundler/support/artifice/compact_index_partial_update_no_etag_not_incremental.rb +++ b/spec/bundler/support/artifice/compact_index_partial_update_no_digest_not_incremental.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -require_relative "compact_index" +require_relative "helpers/compact_index" -Artifice.deactivate - -class CompactIndexPartialUpdateNoEtagNotIncremental < CompactIndexAPI - def partial_update_no_etag +# The purpose of this Artifice is to test that an incremental response is ignored +# when the digest is not present to verify that the partial response is valid. +class CompactIndexPartialUpdateNoDigestNotIncremental < CompactIndexAPI + def partial_update_no_digest response_body = yield headers "Surrogate-Control" => "max-age=2592000, stale-while-revalidate=60" content_type "text/plain" @@ -13,12 +13,12 @@ class CompactIndexPartialUpdateNoEtagNotIncremental < CompactIndexAPI end get "/versions" do - partial_update_no_etag do + partial_update_no_digest do file = tmp("versions.list") FileUtils.rm_f(file) file = CompactIndex::VersionsFile.new(file.to_s) file.create(gems) - lines = file.contents([], :calculate_info_checksums => true).split("\n") + lines = file.contents([], calculate_info_checksums: true).split("\n") name, versions, checksum = lines.last.split(" ") # shuffle versions so new versions are not appended to the end @@ -27,7 +27,7 @@ class CompactIndexPartialUpdateNoEtagNotIncremental < CompactIndexAPI end get "/info/:name" do - partial_update_no_etag do + partial_update_no_digest do gem = gems.find {|g| g.name == params[:name] } lines = CompactIndex.info(gem ? gem.versions : []).split("\n") @@ -37,4 +37,6 @@ class CompactIndexPartialUpdateNoEtagNotIncremental < CompactIndexAPI end end -Artifice.activate_with(CompactIndexPartialUpdateNoEtagNotIncremental) +require_relative "helpers/artifice" + +Artifice.activate_with(CompactIndexPartialUpdateNoDigestNotIncremental) diff --git a/spec/bundler/support/artifice/compact_index_precompiled_before.rb b/spec/bundler/support/artifice/compact_index_precompiled_before.rb new file mode 100644 index 0000000000..b5f72f546a --- /dev/null +++ b/spec/bundler/support/artifice/compact_index_precompiled_before.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require_relative "helpers/compact_index" + +class CompactIndexPrecompiledBefore < CompactIndexAPI + get "/info/:name" do + etag_response do + gem = gems.find {|g| g.name == params[:name] } + move_ruby_variant_to_the_end(CompactIndex.info(gem ? gem.versions : [])) + end + end + + private + + def move_ruby_variant_to_the_end(response) + lines = response.split("\n") + ruby = lines.find {|line| /\A\d+\.\d+\.\d* \|/.match(line) } + lines.delete(ruby) + lines.push(ruby).join("\n") + end +end + +require_relative "helpers/artifice" + +Artifice.activate_with(CompactIndexPrecompiledBefore) diff --git a/spec/bundler/support/artifice/compact_index_range_ignored.rb b/spec/bundler/support/artifice/compact_index_range_ignored.rb new file mode 100644 index 0000000000..2303682c1f --- /dev/null +++ b/spec/bundler/support/artifice/compact_index_range_ignored.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require_relative "helpers/compact_index" + +class CompactIndexRangeIgnored < CompactIndexAPI + # Stub the server to not return 304 so that we don't bypass all the logic + def not_modified?(_checksum) + false + end + + get "/versions" do + cached_versions_path = File.join( + Bundler.rubygems.user_home, ".bundle", "cache", "compact_index", + "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions" + ) + + # Verify a cached copy of the versions file exists + unless File.binread(cached_versions_path).size > 0 + raise("Cached versions file should be present and have content") + end + + # Verify that a partial request is made, starting from the index of the + # final byte of the cached file. + unless env.delete("HTTP_RANGE") + raise("Expected client to write the full response on the first try") + end + + etag_response do + file = tmp("versions.list") + FileUtils.rm_f(file) + file = CompactIndex::VersionsFile.new(file.to_s) + file.create(gems) + file.contents + end + end +end + +require_relative "helpers/artifice" + +Artifice.activate_with(CompactIndexRangeIgnored) diff --git a/spec/bundler/support/artifice/compact_index_range_not_satisfiable.rb b/spec/bundler/support/artifice/compact_index_range_not_satisfiable.rb index bb616125bb..8a7c4b79b0 100644 --- a/spec/bundler/support/artifice/compact_index_range_not_satisfiable.rb +++ b/spec/bundler/support/artifice/compact_index_range_not_satisfiable.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require_relative "compact_index" - -Artifice.deactivate +require_relative "helpers/compact_index" class CompactIndexRangeNotSatisfiable < CompactIndexAPI get "/versions" do @@ -31,4 +29,6 @@ class CompactIndexRangeNotSatisfiable < CompactIndexAPI end end +require_relative "helpers/artifice" + Artifice.activate_with(CompactIndexRangeNotSatisfiable) diff --git a/spec/bundler/support/artifice/compact_index_rate_limited.rb b/spec/bundler/support/artifice/compact_index_rate_limited.rb index 570105e2a0..4495491635 100644 --- a/spec/bundler/support/artifice/compact_index_rate_limited.rb +++ b/spec/bundler/support/artifice/compact_index_rate_limited.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require_relative "compact_index" - -Artifice.deactivate +require_relative "helpers/compact_index" class CompactIndexRateLimited < CompactIndexAPI class RequestCounter @@ -45,4 +43,6 @@ class CompactIndexRateLimited < CompactIndexAPI end end +require_relative "helpers/artifice" + Artifice.activate_with(CompactIndexRateLimited) diff --git a/spec/bundler/support/artifice/compact_index_redirects.rb b/spec/bundler/support/artifice/compact_index_redirects.rb index 99adc797bf..f7ba393239 100644 --- a/spec/bundler/support/artifice/compact_index_redirects.rb +++ b/spec/bundler/support/artifice/compact_index_redirects.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require_relative "compact_index" - -Artifice.deactivate +require_relative "helpers/compact_index" class CompactIndexRedirect < CompactIndexAPI get "/fetch/actual/gem/:id" do @@ -18,4 +16,6 @@ class CompactIndexRedirect < CompactIndexAPI end end +require_relative "helpers/artifice" + Artifice.activate_with(CompactIndexRedirect) diff --git a/spec/bundler/support/artifice/compact_index_strict_basic_authentication.rb b/spec/bundler/support/artifice/compact_index_strict_basic_authentication.rb index 7d427b5382..96259385e7 100644 --- a/spec/bundler/support/artifice/compact_index_strict_basic_authentication.rb +++ b/spec/bundler/support/artifice/compact_index_strict_basic_authentication.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require_relative "compact_index" - -Artifice.deactivate +require_relative "helpers/compact_index" class CompactIndexStrictBasicAuthentication < CompactIndexAPI before do @@ -12,9 +10,11 @@ class CompactIndexStrictBasicAuthentication < CompactIndexAPI # Only accepts password == "password" unless env["HTTP_AUTHORIZATION"] == "Basic dXNlcjpwYXNz" - halt 403, "Authentication failed" + halt 401, "Authentication failed" end end end +require_relative "helpers/artifice" + Artifice.activate_with(CompactIndexStrictBasicAuthentication) diff --git a/spec/bundler/support/artifice/compact_index_wrong_dependencies.rb b/spec/bundler/support/artifice/compact_index_wrong_dependencies.rb index 036fac70b3..15850599b6 100644 --- a/spec/bundler/support/artifice/compact_index_wrong_dependencies.rb +++ b/spec/bundler/support/artifice/compact_index_wrong_dependencies.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require_relative "compact_index" - -Artifice.deactivate +require_relative "helpers/compact_index" class CompactIndexWrongDependencies < CompactIndexAPI get "/info/:name" do @@ -14,4 +12,6 @@ class CompactIndexWrongDependencies < CompactIndexAPI end end +require_relative "helpers/artifice" + Artifice.activate_with(CompactIndexWrongDependencies) diff --git a/spec/bundler/support/artifice/compact_index_wrong_gem_checksum.rb b/spec/bundler/support/artifice/compact_index_wrong_gem_checksum.rb index 8add32b88f..9bd2ca0a9d 100644 --- a/spec/bundler/support/artifice/compact_index_wrong_gem_checksum.rb +++ b/spec/bundler/support/artifice/compact_index_wrong_gem_checksum.rb @@ -1,15 +1,14 @@ # frozen_string_literal: true -require_relative "compact_index" - -Artifice.deactivate +require_relative "helpers/compact_index" class CompactIndexWrongGemChecksum < CompactIndexAPI get "/info/:name" do etag_response do name = params[:name] gem = gems.find {|g| g.name == name } - checksum = ENV.fetch("BUNDLER_SPEC_#{name.upcase}_CHECKSUM") { "ab" * 22 } + # This generates the hexdigest "2222222222222222222222222222222222222222222222222222222222222222" + checksum = ENV.fetch("BUNDLER_SPEC_#{name.upcase}_CHECKSUM") { "IiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiI=" } versions = gem ? gem.versions : [] versions.each {|v| v.checksum = checksum } CompactIndex.info(versions) @@ -17,4 +16,6 @@ class CompactIndexWrongGemChecksum < CompactIndexAPI end end +require_relative "helpers/artifice" + Artifice.activate_with(CompactIndexWrongGemChecksum) diff --git a/spec/bundler/support/artifice/endpoint.rb b/spec/bundler/support/artifice/endpoint.rb index 4a820e5a3f..15242a7942 100644 --- a/spec/bundler/support/artifice/endpoint.rb +++ b/spec/bundler/support/artifice/endpoint.rb @@ -1,115 +1,6 @@ # frozen_string_literal: true -require_relative "../path" - -$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gems.join("gems/{artifice,mustermann,rack,tilt,sinatra,ruby2_keywords}-*/lib")].map(&:to_s)) - -require "artifice" -require "sinatra/base" - -ALL_REQUESTS = [] # rubocop:disable Style/MutableConstant -ALL_REQUESTS_MUTEX = Thread::Mutex.new - -at_exit do - if expected = ENV["BUNDLER_SPEC_ALL_REQUESTS"] - expected = expected.split("\n").sort - actual = ALL_REQUESTS.sort - - unless expected == actual - raise "Unexpected requests!\nExpected:\n\t#{expected.join("\n\t")}\n\nActual:\n\t#{actual.join("\n\t")}" - end - end -end - -class Endpoint < Sinatra::Base - def self.all_requests - @all_requests ||= [] - end - - set :raise_errors, true - set :show_exceptions, false - - def call!(*) - super.tap do - ALL_REQUESTS_MUTEX.synchronize do - ALL_REQUESTS << @request.url - end - end - end - - helpers do - include Spec::Path - - def default_gem_repo - if ENV["BUNDLER_SPEC_GEM_REPO"] - Pathname.new(ENV["BUNDLER_SPEC_GEM_REPO"]) - else - case request.host - when "gem.repo1" - Spec::Path.gem_repo1 - when "gem.repo2" - Spec::Path.gem_repo2 - when "gem.repo3" - Spec::Path.gem_repo3 - when "gem.repo4" - Spec::Path.gem_repo4 - else - Spec::Path.gem_repo1 - end - end - end - - def dependencies_for(gem_names, gem_repo = default_gem_repo) - return [] if gem_names.nil? || gem_names.empty? - - all_specs = %w[specs.4.8 prerelease_specs.4.8].map do |filename| - Marshal.load(File.open(gem_repo.join(filename)).read) - end.inject(:+) - - all_specs.map do |name, version, platform| - spec = load_spec(name, version, platform, gem_repo) - next unless gem_names.include?(spec.name) - { - :name => spec.name, - :number => spec.version.version, - :platform => spec.platform.to_s, - :dependencies => spec.dependencies.select {|dep| dep.type == :runtime }.map do |dep| - [dep.name, dep.requirement.requirements.map {|a| a.join(" ") }.join(", ")] - end, - } - end.compact - end - - def load_spec(name, version, platform, gem_repo) - full_name = "#{name}-#{version}" - full_name += "-#{platform}" if platform != "ruby" - Marshal.load(Bundler.rubygems.inflate(File.binread(gem_repo.join("quick/Marshal.4.8/#{full_name}.gemspec.rz")))) - end - end - - get "/quick/Marshal.4.8/:id" do - redirect "/fetch/actual/gem/#{params[:id]}" - end - - get "/fetch/actual/gem/:id" do - File.binread("#{default_gem_repo}/quick/Marshal.4.8/#{params[:id]}") - end - - get "/gems/:id" do - File.binread("#{default_gem_repo}/gems/#{params[:id]}") - end - - get "/api/v1/dependencies" do - Marshal.dump(dependencies_for(params[:gems])) - end - - get "/specs.4.8.gz" do - File.binread("#{default_gem_repo}/specs.4.8.gz") - end - - get "/prerelease_specs.4.8.gz" do - File.binread("#{default_gem_repo}/prerelease_specs.4.8.gz") - end -end +require_relative "helpers/endpoint" +require_relative "helpers/artifice" Artifice.activate_with(Endpoint) diff --git a/spec/bundler/support/artifice/endpoint_500.rb b/spec/bundler/support/artifice/endpoint_500.rb index 0ce8dfeaad..b1ed1964c8 100644 --- a/spec/bundler/support/artifice/endpoint_500.rb +++ b/spec/bundler/support/artifice/endpoint_500.rb @@ -2,17 +2,16 @@ require_relative "../path" -$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gems.join("gems/{artifice,mustermann,rack,tilt,sinatra,ruby2_keywords}-*/lib")].map(&:to_s)) +$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gem_path.join("gems/{mustermann,rack,tilt,sinatra,ruby2_keywords,base64}-*/lib")].map(&:to_s)) -require "artifice" require "sinatra/base" -Artifice.deactivate - class Endpoint500 < Sinatra::Base before do halt 500 end end +require_relative "helpers/artifice" + Artifice.activate_with(Endpoint500) diff --git a/spec/bundler/support/artifice/endpoint_api_forbidden.rb b/spec/bundler/support/artifice/endpoint_api_forbidden.rb index edc2463424..6bdc5896d6 100644 --- a/spec/bundler/support/artifice/endpoint_api_forbidden.rb +++ b/spec/bundler/support/artifice/endpoint_api_forbidden.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require_relative "endpoint" - -Artifice.deactivate +require_relative "helpers/endpoint" class EndpointApiForbidden < Endpoint get "/api/v1/dependencies" do @@ -10,4 +8,6 @@ class EndpointApiForbidden < Endpoint end end +require_relative "helpers/artifice" + Artifice.activate_with(EndpointApiForbidden) diff --git a/spec/bundler/support/artifice/endpoint_api_missing.rb b/spec/bundler/support/artifice/endpoint_api_missing.rb deleted file mode 100644 index 755c42e836..0000000000 --- a/spec/bundler/support/artifice/endpoint_api_missing.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -require_relative "endpoint" - -Artifice.deactivate - -class EndpointApiMissing < Endpoint - get "/fetch/actual/gem/:id" do - warn params[:id] - if params[:id] == "rack-1.0.gemspec.rz" - halt 404 - else - File.binread("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}") - end - end -end - -Artifice.activate_with(EndpointApiMissing) diff --git a/spec/bundler/support/artifice/endpoint_basic_authentication.rb b/spec/bundler/support/artifice/endpoint_basic_authentication.rb index ff3d1493d6..e8e3569e63 100644 --- a/spec/bundler/support/artifice/endpoint_basic_authentication.rb +++ b/spec/bundler/support/artifice/endpoint_basic_authentication.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require_relative "endpoint" - -Artifice.deactivate +require_relative "helpers/endpoint" class EndpointBasicAuthentication < Endpoint before do @@ -12,4 +10,6 @@ class EndpointBasicAuthentication < Endpoint end end +require_relative "helpers/artifice" + Artifice.activate_with(EndpointBasicAuthentication) diff --git a/spec/bundler/support/artifice/endpoint_creds_diff_host.rb b/spec/bundler/support/artifice/endpoint_creds_diff_host.rb index 8b8972cedd..ce30de0a68 100644 --- a/spec/bundler/support/artifice/endpoint_creds_diff_host.rb +++ b/spec/bundler/support/artifice/endpoint_creds_diff_host.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require_relative "endpoint" - -Artifice.deactivate +require_relative "helpers/endpoint" class EndpointCredsDiffHost < Endpoint helpers do @@ -36,4 +34,6 @@ class EndpointCredsDiffHost < Endpoint end end +require_relative "helpers/artifice" + Artifice.activate_with(EndpointCredsDiffHost) diff --git a/spec/bundler/support/artifice/endpoint_extra.rb b/spec/bundler/support/artifice/endpoint_extra.rb index 942c4352b7..021fd435fe 100644 --- a/spec/bundler/support/artifice/endpoint_extra.rb +++ b/spec/bundler/support/artifice/endpoint_extra.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require_relative "endpoint" - -Artifice.deactivate +require_relative "helpers/endpoint" class EndpointExtra < Endpoint get "/extra/api/v1/dependencies" do @@ -30,4 +28,6 @@ class EndpointExtra < Endpoint end end +require_relative "helpers/artifice" + Artifice.activate_with(EndpointExtra) diff --git a/spec/bundler/support/artifice/endpoint_extra_api.rb b/spec/bundler/support/artifice/endpoint_extra_api.rb index 1cfef7a7fc..a965af6e73 100644 --- a/spec/bundler/support/artifice/endpoint_extra_api.rb +++ b/spec/bundler/support/artifice/endpoint_extra_api.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require_relative "endpoint" - -Artifice.deactivate +require_relative "helpers/endpoint" class EndpointExtraApi < Endpoint get "/extra/api/v1/dependencies" do @@ -31,4 +29,6 @@ class EndpointExtraApi < Endpoint end end +require_relative "helpers/artifice" + Artifice.activate_with(EndpointExtraApi) diff --git a/spec/bundler/support/artifice/endpoint_extra_missing.rb b/spec/bundler/support/artifice/endpoint_extra_missing.rb index 5fd9238207..73e2defb32 100644 --- a/spec/bundler/support/artifice/endpoint_extra_missing.rb +++ b/spec/bundler/support/artifice/endpoint_extra_missing.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require_relative "endpoint_extra" - -Artifice.deactivate +require_relative "helpers/endpoint_extra" class EndpointExtraMissing < EndpointExtra get "/extra/fetch/actual/gem/:id" do @@ -14,4 +12,6 @@ class EndpointExtraMissing < EndpointExtra end end +require_relative "helpers/artifice" + Artifice.activate_with(EndpointExtraMissing) diff --git a/spec/bundler/support/artifice/endpoint_fallback.rb b/spec/bundler/support/artifice/endpoint_fallback.rb index 08edf232e3..742e563f07 100644 --- a/spec/bundler/support/artifice/endpoint_fallback.rb +++ b/spec/bundler/support/artifice/endpoint_fallback.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require_relative "endpoint" - -Artifice.deactivate +require_relative "helpers/endpoint" class EndpointFallback < Endpoint DEPENDENCY_LIMIT = 60 @@ -16,4 +14,6 @@ class EndpointFallback < Endpoint end end +require_relative "helpers/artifice" + Artifice.activate_with(EndpointFallback) diff --git a/spec/bundler/support/artifice/endpoint_host_redirect.rb b/spec/bundler/support/artifice/endpoint_host_redirect.rb index 338cbcad00..6ce51bed93 100644 --- a/spec/bundler/support/artifice/endpoint_host_redirect.rb +++ b/spec/bundler/support/artifice/endpoint_host_redirect.rb @@ -1,11 +1,9 @@ # frozen_string_literal: true -require_relative "endpoint" - -Artifice.deactivate +require_relative "helpers/endpoint" class EndpointHostRedirect < Endpoint - get "/fetch/actual/gem/:id", :host_name => "localgemserver.test" do + get "/fetch/actual/gem/:id", host_name: "localgemserver.test" do redirect "http://bundler.localgemserver.test#{request.path_info}" end @@ -14,4 +12,6 @@ class EndpointHostRedirect < Endpoint end end +require_relative "helpers/artifice" + Artifice.activate_with(EndpointHostRedirect) diff --git a/spec/bundler/support/artifice/endpoint_marshal_fail.rb b/spec/bundler/support/artifice/endpoint_marshal_fail.rb index 22c13e3e17..74ce321de6 100644 --- a/spec/bundler/support/artifice/endpoint_marshal_fail.rb +++ b/spec/bundler/support/artifice/endpoint_marshal_fail.rb @@ -1,13 +1,6 @@ # frozen_string_literal: true -require_relative "endpoint_fallback" - -Artifice.deactivate - -class EndpointMarshalFail < EndpointFallback - get "/api/v1/dependencies" do - "f0283y01hasf" - end -end +require_relative "helpers/endpoint_marshal_fail" +require_relative "helpers/artifice" Artifice.activate_with(EndpointMarshalFail) diff --git a/spec/bundler/support/artifice/endpoint_marshal_fail_basic_authentication.rb b/spec/bundler/support/artifice/endpoint_marshal_fail_basic_authentication.rb index c341c3993f..ea4cfbe965 100644 --- a/spec/bundler/support/artifice/endpoint_marshal_fail_basic_authentication.rb +++ b/spec/bundler/support/artifice/endpoint_marshal_fail_basic_authentication.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require_relative "endpoint_marshal_fail" - -Artifice.deactivate +require_relative "helpers/endpoint_marshal_fail" class EndpointMarshalFailBasicAuthentication < EndpointMarshalFail before do @@ -12,4 +10,6 @@ class EndpointMarshalFailBasicAuthentication < EndpointMarshalFail end end +require_relative "helpers/artifice" + Artifice.activate_with(EndpointMarshalFailBasicAuthentication) diff --git a/spec/bundler/support/artifice/endpoint_mirror_source.rb b/spec/bundler/support/artifice/endpoint_mirror_source.rb index 788a9027f3..fed7a746b9 100644 --- a/spec/bundler/support/artifice/endpoint_mirror_source.rb +++ b/spec/bundler/support/artifice/endpoint_mirror_source.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true -require_relative "endpoint" +require_relative "helpers/endpoint" class EndpointMirrorSource < Endpoint get "/gems/:id" do - if request.env["HTTP_X_GEMFILE_SOURCE"] == "https://server.example.org/" + if request.env["HTTP_X_GEMFILE_SOURCE"] == "https://server.example.org/" && request.env["HTTP_USER_AGENT"].start_with?("bundler") File.binread("#{gem_repo1}/gems/#{params[:id]}") else halt 500 @@ -12,4 +12,6 @@ class EndpointMirrorSource < Endpoint end end +require_relative "helpers/artifice" + Artifice.activate_with(EndpointMirrorSource) diff --git a/spec/bundler/support/artifice/endpoint_redirect.rb b/spec/bundler/support/artifice/endpoint_redirect.rb index ee97fccf64..84f546ba9d 100644 --- a/spec/bundler/support/artifice/endpoint_redirect.rb +++ b/spec/bundler/support/artifice/endpoint_redirect.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require_relative "endpoint" - -Artifice.deactivate +require_relative "helpers/endpoint" class EndpointRedirect < Endpoint get "/fetch/actual/gem/:id" do @@ -14,4 +12,6 @@ class EndpointRedirect < Endpoint end end +require_relative "helpers/artifice" + Artifice.activate_with(EndpointRedirect) diff --git a/spec/bundler/support/artifice/endpoint_strict_basic_authentication.rb b/spec/bundler/support/artifice/endpoint_strict_basic_authentication.rb index 4d4da08770..dff360c5c5 100644 --- a/spec/bundler/support/artifice/endpoint_strict_basic_authentication.rb +++ b/spec/bundler/support/artifice/endpoint_strict_basic_authentication.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require_relative "endpoint" - -Artifice.deactivate +require_relative "helpers/endpoint" class EndpointStrictBasicAuthentication < Endpoint before do @@ -12,9 +10,11 @@ class EndpointStrictBasicAuthentication < Endpoint # Only accepts password == "password" unless env["HTTP_AUTHORIZATION"] == "Basic dXNlcjpwYXNz" - halt 403, "Authentication failed" + halt 401, "Authentication failed" end end end +require_relative "helpers/artifice" + Artifice.activate_with(EndpointStrictBasicAuthentication) diff --git a/spec/bundler/support/artifice/endpoint_timeout.rb b/spec/bundler/support/artifice/endpoint_timeout.rb index c118da1893..86b793e499 100644 --- a/spec/bundler/support/artifice/endpoint_timeout.rb +++ b/spec/bundler/support/artifice/endpoint_timeout.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -require_relative "endpoint_fallback" - -Artifice.deactivate +require_relative "helpers/endpoint_fallback" class EndpointTimeout < EndpointFallback SLEEP_TIMEOUT = 3 @@ -12,4 +10,6 @@ class EndpointTimeout < EndpointFallback end end +require_relative "helpers/artifice" + Artifice.activate_with(EndpointTimeout) diff --git a/spec/bundler/support/artifice/fail.rb b/spec/bundler/support/artifice/fail.rb index f69f2eccc6..8822e5b8e2 100644 --- a/spec/bundler/support/artifice/fail.rb +++ b/spec/bundler/support/artifice/fail.rb @@ -1,15 +1,11 @@ # frozen_string_literal: true -require "net/http" +require "bundler/vendored_net_http" -# We can't use artifice here because it uses rack - -module Artifice; end # for < 2.0, Net::HTTP::Persistent::SSLReuse - -class Fail < Net::HTTP - # Net::HTTP uses a @newimpl instance variable to decide whether +class Fail < Gem::Net::HTTP + # Gem::Net::HTTP uses a @newimpl instance variable to decide whether # to use a legacy implementation. Since we are subclassing - # Net::HTTP, we must set it + # Gem::Net::HTTP, we must set it @newimpl = true def request(req, body = nil, &block) @@ -21,14 +17,11 @@ class Fail < Net::HTTP end def exception(req) - name = ENV.fetch("BUNDLER_SPEC_EXCEPTION") { "Errno::ENETUNREACH" } - const = name.split("::").reduce(Object) {|mod, sym| mod.const_get(sym) } - const.new("host down: Bundler spec artifice fail! #{req["PATH_INFO"]}") + Errno::ENETUNREACH.new("host down: Bundler spec artifice fail! #{req["PATH_INFO"]}") end end -# Replace Net::HTTP with our failing subclass -::Net.class_eval do - remove_const(:HTTP) - const_set(:HTTP, ::Fail) -end +require_relative "helpers/artifice" + +# Replace Gem::Net::HTTP with our failing subclass +Artifice.replace_net_http(::Fail) diff --git a/spec/bundler/support/artifice/helpers/artifice.rb b/spec/bundler/support/artifice/helpers/artifice.rb new file mode 100644 index 0000000000..788268295c --- /dev/null +++ b/spec/bundler/support/artifice/helpers/artifice.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +# This module was initially borrowed from https://github.com/wycats/artifice +module Artifice + # Activate Artifice with a particular Rack endpoint. + # + # Calling this method will replace the Gem::Net::HTTP system + # with a replacement that routes all requests to the + # Rack endpoint. + # + # @param [#call] endpoint A valid Rack endpoint + def self.activate_with(endpoint) + require_relative "rack_request" + + Net::HTTP.endpoint = endpoint + replace_net_http(Artifice::Net::HTTP) + end + + # Deactivate the Artifice replacement. + def self.deactivate + replace_net_http(::Gem::Net::HTTP) + end + + def self.replace_net_http(value) + ::Gem::Net.class_eval do + remove_const(:HTTP) + const_set(:HTTP, value) + end + end +end diff --git a/spec/bundler/support/artifice/helpers/compact_index.rb b/spec/bundler/support/artifice/helpers/compact_index.rb new file mode 100644 index 0000000000..a803a2d30a --- /dev/null +++ b/spec/bundler/support/artifice/helpers/compact_index.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true + +require_relative "endpoint" + +$LOAD_PATH.unshift Dir[Spec::Path.base_system_gem_path.join("gems/compact_index*/lib")].first.to_s +require "compact_index" +require "digest" + +class CompactIndexAPI < Endpoint + helpers do + include Spec::Path + + def load_spec(name, version, platform, gem_repo) + full_name = "#{name}-#{version}" + full_name += "-#{platform}" if platform != "ruby" + Marshal.load(Bundler.rubygems.inflate(File.binread(gem_repo.join("quick/Marshal.4.8/#{full_name}.gemspec.rz")))) + end + + def etag_response + response_body = yield + etag = Digest::MD5.hexdigest(response_body) + headers "ETag" => quote(etag) + return if not_modified?(etag) + headers "Repr-Digest" => "sha-256=:#{Digest::SHA256.base64digest(response_body)}:" + headers "Surrogate-Control" => "max-age=2592000, stale-while-revalidate=60" + content_type "text/plain" + requested_range_for(response_body) + rescue StandardError => e + puts e + puts e.backtrace + raise + end + + def not_modified?(etag) + etags = parse_etags(request.env["HTTP_IF_NONE_MATCH"]) + + return unless etags.include?(etag) + status 304 + body "" + end + + def requested_range_for(response_body) + ranges = Rack::Utils.byte_ranges(env, response_body.bytesize) + + if ranges + status 206 + body ranges.map! {|range| slice_body(response_body, range) }.join + else + status 200 + body response_body + end + end + + def quote(string) + %("#{string}") + end + + def parse_etags(value) + value ? value.split(/, ?/).select {|s| s.sub!(/"(.*)"/, '\1') } : [] + end + + def slice_body(body, range) + body.byteslice(range) + end + + def gems(gem_repo = default_gem_repo) + @gems ||= {} + @gems[gem_repo] ||= begin + specs = Bundler::Deprecate.skip_during do + %w[specs.4.8 prerelease_specs.4.8].map do |filename| + Marshal.load(File.open(gem_repo.join(filename)).read).map do |name, version, platform| + load_spec(name, version, platform, gem_repo) + end + end.flatten + end + + specs.group_by(&:name).map do |name, versions| + gem_versions = versions.map do |spec| + deps = spec.runtime_dependencies.map do |d| + reqs = d.requirement.requirements.map {|r| r.join(" ") }.join(", ") + CompactIndex::Dependency.new(d.name, reqs) + end + begin + checksum = ENV.fetch("BUNDLER_SPEC_#{name.upcase}_CHECKSUM") do + Digest(:SHA256).file("#{gem_repo}/gems/#{spec.original_name}.gem").hexdigest + end + rescue StandardError + checksum = nil + end + CompactIndex::GemVersion.new(spec.version.version, spec.platform.to_s, checksum, nil, + deps, spec.required_ruby_version.to_s, spec.required_rubygems_version.to_s) + end + CompactIndex::Gem.new(name, gem_versions) + end + end + end + end + + get "/names" do + etag_response do + CompactIndex.names(gems.map(&:name)) + end + end + + get "/versions" do + etag_response do + file = tmp("versions.list") + FileUtils.rm_f(file) + file = CompactIndex::VersionsFile.new(file.to_s) + file.create(gems) + file.contents + end + end + + get "/info/:name" do + etag_response do + gem = gems.find {|g| g.name == params[:name] } + CompactIndex.info(gem ? gem.versions : []) + end + end +end diff --git a/spec/bundler/support/artifice/helpers/compact_index_extra.rb b/spec/bundler/support/artifice/helpers/compact_index_extra.rb new file mode 100644 index 0000000000..9e742630dd --- /dev/null +++ b/spec/bundler/support/artifice/helpers/compact_index_extra.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require_relative "compact_index" + +class CompactIndexExtra < CompactIndexAPI + get "/extra/versions" do + halt 404 + end + + get "/extra/api/v1/dependencies" do + halt 404 + end + + get "/extra/specs.4.8.gz" do + File.binread("#{gem_repo2}/specs.4.8.gz") + end + + get "/extra/prerelease_specs.4.8.gz" do + File.binread("#{gem_repo2}/prerelease_specs.4.8.gz") + end + + get "/extra/quick/Marshal.4.8/:id" do + redirect "/extra/fetch/actual/gem/#{params[:id]}" + end + + get "/extra/fetch/actual/gem/:id" do + File.binread("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}") + end + + get "/extra/gems/:id" do + File.binread("#{gem_repo2}/gems/#{params[:id]}") + end +end diff --git a/spec/bundler/support/artifice/helpers/compact_index_extra_api.rb b/spec/bundler/support/artifice/helpers/compact_index_extra_api.rb new file mode 100644 index 0000000000..d9a7d83d23 --- /dev/null +++ b/spec/bundler/support/artifice/helpers/compact_index_extra_api.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require_relative "compact_index" + +class CompactIndexExtraApi < CompactIndexAPI + get "/extra/names" do + etag_response do + CompactIndex.names(gems(gem_repo4).map(&:name)) + end + end + + get "/extra/versions" do + etag_response do + file = tmp("versions.list") + FileUtils.rm_f(file) + file = CompactIndex::VersionsFile.new(file.to_s) + file.create(gems(gem_repo4)) + file.contents + end + end + + get "/extra/info/:name" do + etag_response do + gem = gems(gem_repo4).find {|g| g.name == params[:name] } + CompactIndex.info(gem ? gem.versions : []) + end + end + + get "/extra/specs.4.8.gz" do + File.binread("#{gem_repo4}/specs.4.8.gz") + end + + get "/extra/prerelease_specs.4.8.gz" do + File.binread("#{gem_repo4}/prerelease_specs.4.8.gz") + end + + get "/extra/quick/Marshal.4.8/:id" do + redirect "/extra/fetch/actual/gem/#{params[:id]}" + end + + get "/extra/fetch/actual/gem/:id" do + File.binread("#{gem_repo4}/quick/Marshal.4.8/#{params[:id]}") + end + + get "/extra/gems/:id" do + File.binread("#{gem_repo4}/gems/#{params[:id]}") + end +end diff --git a/spec/bundler/support/artifice/helpers/endpoint.rb b/spec/bundler/support/artifice/helpers/endpoint.rb new file mode 100644 index 0000000000..83ba1be0fc --- /dev/null +++ b/spec/bundler/support/artifice/helpers/endpoint.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +require_relative "../../path" + +$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gem_path.join("gems/{mustermann,rack,tilt,sinatra,ruby2_keywords,base64}-*/lib")].map(&:to_s)) + +require "sinatra/base" + +ALL_REQUESTS = [] # rubocop:disable Style/MutableConstant +ALL_REQUESTS_MUTEX = Thread::Mutex.new + +at_exit do + if expected = ENV["BUNDLER_SPEC_ALL_REQUESTS"] + expected = expected.split("\n").sort + actual = ALL_REQUESTS.sort + + unless expected == actual + raise "Unexpected requests!\nExpected:\n\t#{expected.join("\n\t")}\n\nActual:\n\t#{actual.join("\n\t")}" + end + end +end + +class Endpoint < Sinatra::Base + def self.all_requests + @all_requests ||= [] + end + + set :raise_errors, true + set :show_exceptions, false + + def call!(*) + super.tap do + ALL_REQUESTS_MUTEX.synchronize do + ALL_REQUESTS << @request.url + end + end + end + + helpers do + include Spec::Path + + def default_gem_repo + if ENV["BUNDLER_SPEC_GEM_REPO"] + Pathname.new(ENV["BUNDLER_SPEC_GEM_REPO"]) + else + case request.host + when "gem.repo1" + Spec::Path.gem_repo1 + when "gem.repo2" + Spec::Path.gem_repo2 + when "gem.repo3" + Spec::Path.gem_repo3 + when "gem.repo4" + Spec::Path.gem_repo4 + else + Spec::Path.gem_repo1 + end + end + end + + def dependencies_for(gem_names, gem_repo = default_gem_repo) + return [] if gem_names.nil? || gem_names.empty? + + all_specs = %w[specs.4.8 prerelease_specs.4.8].map do |filename| + Marshal.load(File.open(gem_repo.join(filename)).read) + end.inject(:+) + + all_specs.map do |name, version, platform| + spec = load_spec(name, version, platform, gem_repo) + next unless gem_names.include?(spec.name) + { + name: spec.name, + number: spec.version.version, + platform: spec.platform.to_s, + dependencies: spec.runtime_dependencies.map do |dep| + [dep.name, dep.requirement.requirements.map {|a| a.join(" ") }.join(", ")] + end, + } + end.compact + end + + def load_spec(name, version, platform, gem_repo) + full_name = "#{name}-#{version}" + full_name += "-#{platform}" if platform != "ruby" + Marshal.load(Bundler.rubygems.inflate(File.binread(gem_repo.join("quick/Marshal.4.8/#{full_name}.gemspec.rz")))) + end + end + + get "/quick/Marshal.4.8/:id" do + redirect "/fetch/actual/gem/#{params[:id]}" + end + + get "/fetch/actual/gem/:id" do + File.binread("#{default_gem_repo}/quick/Marshal.4.8/#{params[:id]}") + end + + get "/gems/:id" do + File.binread("#{default_gem_repo}/gems/#{params[:id]}") + end + + get "/api/v1/dependencies" do + Marshal.dump(dependencies_for(params[:gems])) + end + + get "/specs.4.8.gz" do + File.binread("#{default_gem_repo}/specs.4.8.gz") + end + + get "/prerelease_specs.4.8.gz" do + File.binread("#{default_gem_repo}/prerelease_specs.4.8.gz") + end +end diff --git a/spec/bundler/support/artifice/helpers/endpoint_extra.rb b/spec/bundler/support/artifice/helpers/endpoint_extra.rb new file mode 100644 index 0000000000..ad08495b50 --- /dev/null +++ b/spec/bundler/support/artifice/helpers/endpoint_extra.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require_relative "endpoint" + +class EndpointExtra < Endpoint + get "/extra/api/v1/dependencies" do + halt 404 + end + + get "/extra/specs.4.8.gz" do + File.binread("#{gem_repo2}/specs.4.8.gz") + end + + get "/extra/prerelease_specs.4.8.gz" do + File.binread("#{gem_repo2}/prerelease_specs.4.8.gz") + end + + get "/extra/quick/Marshal.4.8/:id" do + redirect "/extra/fetch/actual/gem/#{params[:id]}" + end + + get "/extra/fetch/actual/gem/:id" do + File.binread("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}") + end + + get "/extra/gems/:id" do + File.binread("#{gem_repo2}/gems/#{params[:id]}") + end +end diff --git a/spec/bundler/support/artifice/helpers/endpoint_fallback.rb b/spec/bundler/support/artifice/helpers/endpoint_fallback.rb new file mode 100644 index 0000000000..a232930b67 --- /dev/null +++ b/spec/bundler/support/artifice/helpers/endpoint_fallback.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require_relative "endpoint" + +class EndpointFallback < Endpoint + DEPENDENCY_LIMIT = 60 + + get "/api/v1/dependencies" do + if params[:gems] && params[:gems].size <= DEPENDENCY_LIMIT + Marshal.dump(dependencies_for(params[:gems])) + else + halt 413, "Too many gems to resolve, please request less than #{DEPENDENCY_LIMIT} gems" + end + end +end diff --git a/spec/bundler/support/artifice/helpers/endpoint_marshal_fail.rb b/spec/bundler/support/artifice/helpers/endpoint_marshal_fail.rb new file mode 100644 index 0000000000..c409d39d99 --- /dev/null +++ b/spec/bundler/support/artifice/helpers/endpoint_marshal_fail.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require_relative "endpoint_fallback" + +class EndpointMarshalFail < EndpointFallback + get "/api/v1/dependencies" do + "f0283y01hasf" + end +end diff --git a/spec/bundler/support/artifice/helpers/rack_request.rb b/spec/bundler/support/artifice/helpers/rack_request.rb new file mode 100644 index 0000000000..f419bacb8c --- /dev/null +++ b/spec/bundler/support/artifice/helpers/rack_request.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +require "rack/test" +require "bundler/vendored_net_http" + +module Artifice + module Net + # This is an internal object that can receive Rack requests + # to the application using the Rack::Test API + class RackRequest + include Rack::Test::Methods + attr_reader :app + + def initialize(app) + @app = app + end + end + + class HTTP < ::Gem::Net::HTTP + class << self + attr_accessor :endpoint + end + + # Gem::Net::HTTP uses a @newimpl instance variable to decide whether + # to use a legacy implementation. Since we are subclassing + # Gem::Net::HTTP, we must set it + @newimpl = true + + # We don't need to connect, so blank out this method + def connect + end + + # Replace the Gem::Net::HTTP request method with a method + # that converts the request into a Rack request and + # dispatches it to the Rack endpoint. + # + # @param [Net::HTTPRequest] req A Gem::Net::HTTPRequest + # object, or one if its subclasses + # @param [optional, String, #read] body This should + # be sent as "rack.input". If it's a String, it will + # be converted to a StringIO. + # @return [Net::HTTPResponse] + # + # @yield [Net::HTTPResponse] If a block is provided, + # this method will yield the Gem::Net::HTTPResponse to + # it after the body is read. + def request(req, body = nil, &block) + rack_request = RackRequest.new(self.class.endpoint) + + req.each_header do |header, value| + rack_request.header(header, value) + end + + scheme = use_ssl? ? "https" : "http" + prefix = "#{scheme}://#{addr_port}" + body_stream_contents = req.body_stream.read if req.body_stream + + response = rack_request.request("#{prefix}#{req.path}", + { method: req.method, input: body || req.body || body_stream_contents }) + + make_net_http_response(response, &block) + end + + private + + # This method takes a Rack response and creates a Gem::Net::HTTPResponse + # Instead of trying to mock HTTPResponse directly, we just convert + # the Rack response into a String that looks like a normal HTTP + # response and call Gem::Net::HTTPResponse.read_new + # + # @param [Array(#to_i, Hash, #each)] response a Rack response + # @return [Net::HTTPResponse] + # @yield [Net::HTTPResponse] If a block is provided, yield the + # response to it after the body is read + def make_net_http_response(response) + status = response.status + headers = response.headers + body = response.body + + response_string = [] + response_string << "HTTP/1.1 #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]}" + + headers.each do |header, value| + response_string << "#{header}: #{value}" + end + + response_string << "" << body + + response_io = ::Gem::Net::BufferedIO.new(StringIO.new(response_string.join("\n"))) + res = ::Gem::Net::HTTPResponse.read_new(response_io) + + res.reading_body(response_io, true) do + yield res if block_given? + end + + res + end + end + end +end diff --git a/spec/bundler/support/artifice/vcr.rb b/spec/bundler/support/artifice/vcr.rb index 0d51201bef..7b9a8bdeaf 100644 --- a/spec/bundler/support/artifice/vcr.rb +++ b/spec/bundler/support/artifice/vcr.rb @@ -1,12 +1,13 @@ # frozen_string_literal: true -require "net/http" +require "bundler/vendored_net_http" require_relative "../path" -CASSETTE_PATH = "#{Spec::Path.spec_dir}/support/artifice/vcr_cassettes" +CASSETTE_PATH = "#{Spec::Path.spec_dir}/support/artifice/vcr_cassettes".freeze +USED_CASSETTES_PATH = "#{Spec::Path.spec_dir}/support/artifice/used_cassettes.txt".freeze CASSETTE_NAME = ENV.fetch("BUNDLER_SPEC_VCR_CASSETTE_NAME") { "realworld" } -class BundlerVCRHTTP < Net::HTTP +class BundlerVCRHTTP < Gem::Net::HTTP class RequestHandler attr_reader :http, :request, :body, :response_block def initialize(http, request, body = nil, &response_block) @@ -22,6 +23,10 @@ class BundlerVCRHTTP < Net::HTTP @__vcr_request_handler = handler end + File.open(USED_CASSETTES_PATH, "a+") do |f| + f.puts request_pair_paths.map {|path| Pathname.new(path).relative_path_from(Spec::Path.source_root).to_s }.join("\n") + end + if recorded_response? recorded_response else @@ -36,13 +41,13 @@ class BundlerVCRHTTP < Net::HTTP def recorded_response File.open(request_pair_paths.last, "rb:ASCII-8BIT") do |response_file| - response_io = ::Net::BufferedIO.new(response_file) - ::Net::HTTPResponse.read_new(response_io).tap do |response| + response_io = ::Gem::Net::BufferedIO.new(response_file) + ::Gem::Net::HTTPResponse.read_new(response_io).tap do |response| response.decode_content = request.decode_content if request.respond_to?(:decode_content) response.uri = request.uri response.reading_body(response_io, request.response_body_permitted?) do - response_block.call(response) if response_block + response_block&.call(response) end end end @@ -69,30 +74,13 @@ class BundlerVCRHTTP < Net::HTTP end def file_name_for_key(key) - key.join("/").gsub(/[\:*?"<>|]/, "-") + File.join(*key).gsub(/[\:*?"<>|]/, "-") end def request_pair_paths %w[request response].map do |kind| - File.join(CASSETTE_PATH, CASSETTE_NAME, file_name_for_key(key + [kind])) - end - end - - def read_stored_request(path) - contents = File.binread(path) - headers = {} - method = nil - path = nil - contents.lines.grep(/^> /).each do |line| - if line =~ /^> (GET|HEAD|POST|PATCH|PUT|DELETE) (.*)/ - method = $1 - path = $2.strip - elsif line =~ /^> (.*?): (.*)/ - headers[$1] = $2 - end + File.join(CASSETTE_PATH, CASSETTE_NAME, file_name_for_key(key), kind) end - body = contents =~ /^([^>].*)/m && $1 - Net::HTTP.const_get(method.capitalize).new(path, headers).tap {|r| r.body = body if body } end def request_to_string(request) @@ -158,8 +146,7 @@ class BundlerVCRHTTP < Net::HTTP alias_method :request, :request_with_vcr end -# Replace Net::HTTP with our VCR subclass -::Net.class_eval do - remove_const(:HTTP) - const_set(:HTTP, BundlerVCRHTTP) -end +require_relative "helpers/artifice" + +# Replace Gem::Net::HTTP with our VCR subclass +Artifice.replace_net_http(BundlerVCRHTTP) diff --git a/spec/bundler/support/artifice/windows.rb b/spec/bundler/support/artifice/windows.rb index ddbbd62b96..fea991c071 100644 --- a/spec/bundler/support/artifice/windows.rb +++ b/spec/bundler/support/artifice/windows.rb @@ -2,13 +2,10 @@ require_relative "../path" -$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gems.join("gems/{artifice,mustermann,rack,tilt,sinatra,ruby2_keywords}-*/lib")].map(&:to_s)) +$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gem_path.join("gems/{mustermann,rack,tilt,sinatra,ruby2_keywords,base64}-*/lib")].map(&:to_s)) -require "artifice" require "sinatra/base" -Artifice.deactivate - class Windows < Sinatra::Base set :raise_errors, true set :show_exceptions, false @@ -43,4 +40,6 @@ class Windows < Sinatra::Base end end +require_relative "helpers/artifice" + Artifice.activate_with(Windows) diff --git a/spec/bundler/support/build_metadata.rb b/spec/bundler/support/build_metadata.rb index 98d8ac23c8..5898e7f3bd 100644 --- a/spec/bundler/support/build_metadata.rb +++ b/spec/bundler/support/build_metadata.rb @@ -10,20 +10,20 @@ module Spec def write_build_metadata(dir: source_root) build_metadata = { - :git_commit_sha => git_commit_sha, - :built_at => loaded_gemspec.date.utc.strftime("%Y-%m-%d"), - :release => true, + git_commit_sha: git_commit_sha, + built_at: loaded_gemspec.date.utc.strftime("%Y-%m-%d"), + release: true, } - replace_build_metadata(build_metadata, dir: dir) # rubocop:disable Style/HashSyntax + replace_build_metadata(build_metadata, dir: dir) end def reset_build_metadata(dir: source_root) build_metadata = { - :release => false, + release: false, } - replace_build_metadata(build_metadata, dir: dir) # rubocop:disable Style/HashSyntax + replace_build_metadata(build_metadata, dir: dir) end private @@ -41,7 +41,7 @@ module Spec end def git_commit_sha - ruby_core_tarball? ? "unknown" : sys_exec("git rev-parse --short HEAD", :dir => source_root).strip + ruby_core_tarball? ? "unknown" : sys_exec("git rev-parse --short HEAD", dir: source_root).strip end extend self diff --git a/spec/bundler/support/builders.rb b/spec/bundler/support/builders.rb index 25377d2ac2..ab2dafb0b9 100644 --- a/spec/bundler/support/builders.rb +++ b/spec/bundler/support/builders.rb @@ -17,16 +17,8 @@ module Spec Gem::Platform.new(platform) end - # Returns a number smaller than the size of the index. Useful for specs that - # need the API request limit to be reached for some reason. - def low_api_request_limit_for(gem_repo) - all_gems = Dir[gem_repo.join("gems/*.gem")] - - all_gem_names = all_gems.map do |file| - File.basename(file, ".gem").match(/\A(?<gem_name>[^-]+)-.*\z/)[:gem_name] - end.uniq - - (all_gem_names - ["bundler"]).size + def rake_version + "13.2.1" end def build_repo1 @@ -35,6 +27,11 @@ module Spec build_repo gem_repo1 do FileUtils.cp rake_path, "#{gem_repo1}/gems/" + build_gem "coffee-script-source" + build_gem "git" + build_gem "puma" + build_gem "minitest" + build_gem "rack", %w[0.9.1 1.0.0] do |s| s.executables = "rackup" s.post_install_message = "Rack's post install message" @@ -56,7 +53,7 @@ module Spec build_gem "rails", "2.3.2" do |s| s.executables = "rails" - s.add_dependency "rake", "13.0.1" + s.add_dependency "rake", rake_version s.add_dependency "actionpack", "2.3.2" s.add_dependency "activerecord", "2.3.2" s.add_dependency "actionmailer", "2.3.2" @@ -80,17 +77,17 @@ module Spec s.add_dependency "activesupport", ">= 2.0.0" end - build_gem "rspec", "1.2.7", :no_default => true do |s| + build_gem "rspec", "1.2.7", no_default: true do |s| s.write "lib/spec.rb", "SPEC = '1.2.7'" end - build_gem "rack-test", :no_default => true do |s| + build_gem "rack-test", no_default: true do |s| s.write "lib/rack/test.rb", "RACK_TEST = '1.0'" end build_gem "platform_specific" do |s| - s.platform = Bundler.local_platform - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 #{Bundler.local_platform}'" + s.platform = Gem::Platform.local + s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 #{Gem::Platform.local}'" end build_gem "platform_specific" do |s| @@ -105,15 +102,27 @@ module Spec build_gem "platform_specific" do |s| s.platform = "x86-mswin32" - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 MSWIN'" + s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0 x86-mswin32'" + end + + build_gem "platform_specific" do |s| + s.platform = "x64-mswin64" + s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0 x64-mswin64'" end build_gem "platform_specific" do |s| s.platform = "x86-mingw32" + s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0 x86-mingw32'" end build_gem "platform_specific" do |s| s.platform = "x64-mingw32" + s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0 x64-mingw32'" + end + + build_gem "platform_specific" do |s| + s.platform = "x64-mingw-ucrt" + s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0 x64-mingw-ucrt'" end build_gem "platform_specific" do |s| @@ -186,27 +195,25 @@ module Spec end end - def build_repo2(&blk) + def build_repo2(**kwargs, &blk) FileUtils.rm_rf gem_repo2 FileUtils.cp_r gem_repo1, gem_repo2 - update_repo2(&blk) if block_given? + update_repo2(**kwargs, &blk) if block_given? end # A repo that has no pre-installed gems included. (The caller completely # determines the contents with the block.) - def build_repo4(&blk) + def build_repo4(**kwargs, &blk) FileUtils.rm_rf gem_repo4 - build_repo(gem_repo4, &blk) + build_repo(gem_repo4, **kwargs, &blk) end def update_repo4(&blk) update_repo(gem_repo4, &blk) end - def update_repo2 - update_repo gem_repo2 do - yield if block_given? - end + def update_repo2(**kwargs, &blk) + update_repo(gem_repo2, **kwargs, &blk) end def build_security_repo @@ -224,12 +231,12 @@ module Spec end end - def build_repo(path, &blk) + def build_repo(path, **kwargs, &blk) return if File.directory?(path) FileUtils.mkdir_p("#{path}/gems") - update_repo(path, &blk) + update_repo(path,**kwargs, &blk) end def check_test_gems! @@ -246,7 +253,7 @@ module Spec end end - def update_repo(path) + def update_repo(path, build_compact_index: true) if path == gem_repo1 && caller.first.split(" ").last == "`build_repo`" raise "Updating gem_repo1 is unsupported -- use gem_repo2 instead" end @@ -254,8 +261,13 @@ module Spec @_build_path = "#{path}/gems" @_build_repo = File.basename(path) yield - with_gem_path_as Path.base_system_gems do - gem_command :generate_index, :dir => path + with_gem_path_as Path.base_system_gem_path do + Dir[Spec::Path.base_system_gem_path.join("gems/rubygems-generate_index*/lib")].first || + raise("Could not find rubygems-generate_index lib directory in #{Spec::Path.base_system_gem_path}") + + command = "generate_index" + command += " --no-compact" if !build_compact_index && gem_command(command + " --help").include?("--[no-]compact") + gem_command command, dir: path end ensure @_build_path = nil @@ -281,14 +293,14 @@ module Spec end end - def build_dep(name, requirements = Gem::Requirement.default, type = :runtime) - Bundler::Dependency.new(name, :version => requirements) - end - def build_lib(name, *args, &blk) build_with(LibBuilder, name, args, &blk) end + def build_bundler(*args, &blk) + build_with(BundlerBuilder, "bundler", args, &blk) + end + def build_gem(name, *args, &blk) build_with(GemBuilder, name, args, &blk) end @@ -394,6 +406,49 @@ module Spec alias_method :dep, :runtime end + class BundlerBuilder + attr_writer :required_ruby_version + + def initialize(context, name, version) + raise "can only build bundler" unless name == "bundler" + + @context = context + @version = version || Bundler::VERSION + end + + def _build(options = {}) + full_name = "bundler-#{@version}" + build_path = @context.tmp + full_name + bundler_path = build_path + "#{full_name}.gem" + + FileUtils.mkdir_p build_path + + @context.shipped_files.each do |shipped_file| + target_shipped_file = shipped_file + target_shipped_file = shipped_file.sub(/\Alibexec/, "exe") if @context.ruby_core? + target_shipped_file = build_path + target_shipped_file + target_shipped_dir = File.dirname(target_shipped_file) + FileUtils.mkdir_p target_shipped_dir unless File.directory?(target_shipped_dir) + FileUtils.cp shipped_file, target_shipped_file, preserve: true + end + + @context.replace_version_file(@version, dir: build_path) + @context.replace_required_ruby_version(@required_ruby_version, dir: build_path) if @required_ruby_version + + Spec::BuildMetadata.write_build_metadata(dir: build_path) + + @context.gem_command "build #{@context.relative_gemspec}", dir: build_path + + if block_given? + yield(bundler_path) + else + FileUtils.mv bundler_path, options[:path] + end + ensure + build_path.rmtree + end + end + class LibBuilder def initialize(context, name, version) @context = context @@ -439,9 +494,6 @@ module Spec write "ext/extconf.rb", <<-RUBY require "mkmf" - - # exit 1 unless with_config("simple") - extension_name = "#{name}_c" if extra_lib_dir = with_config("ext-lib") # add extra libpath if --with-ext-lib is @@ -479,7 +531,7 @@ module Spec end @spec.authors = ["no one"] - @spec.files = @files.keys + @spec.files += @files.keys case options[:gemspec] when false @@ -520,10 +572,10 @@ module Spec class GitBuilder < LibBuilder def _build(options) - default_branch = options[:default_branch] || "master" + default_branch = options[:default_branch] || "main" path = options[:path] || _default_path source = options[:source] || "git@#{path}" - super(options.merge(:path => path, :source => source)) + super(options.merge(path: path, source: source)) @context.git("config --global init.defaultBranch #{default_branch}", path) @context.git("init", path) @context.git("add *", path) @@ -537,7 +589,7 @@ module Spec class GitBareBuilder < LibBuilder def _build(options) path = options[:path] || _default_path - super(options.merge(:path => path)) + super(options.merge(path: path)) @context.git("init --bare", path) end end @@ -548,17 +600,8 @@ module Spec update_gemspec = options[:gemspec] || false source = options[:source] || "git@#{libpath}" - @context.git "checkout master", libpath - if branch = options[:branch] - raise "You can't specify `master` as the branch" if branch == "master" - escaped_branch = Shellwords.shellescape(branch) - - if @context.git("branch -l #{escaped_branch}", libpath).empty? - @context.git("branch #{escaped_branch}", libpath) - end - - @context.git("checkout #{escaped_branch}", libpath) + @context.git("checkout -b #{Shellwords.shellescape(branch)}", libpath) elsif tag = options[:tag] @context.git("tag #{Shellwords.shellescape(tag)}", libpath) elsif options[:remote] @@ -571,9 +614,8 @@ module Spec _default_files.keys.each do |path| _default_files[path] += "\n#{Builders.constantize(name)}_PREV_REF = '#{current_ref}'" end - super(options.merge(:path => libpath, :gemspec => update_gemspec, :source => source)) - @context.git("add *", libpath) - @context.git("commit -m BUMP", libpath, :raise_on_error => false) + super(options.merge(path: libpath, gemspec: update_gemspec, source: source)) + @context.git("commit -am BUMP", libpath) end end @@ -594,7 +636,8 @@ module Spec class GemBuilder < LibBuilder def _build(opts) - lib_path = super(opts.merge(:path => @context.tmp(".tmp/#{@spec.full_name}"), :no_default => opts[:no_default])) + lib_path = opts[:lib_path] || @context.tmp(".tmp/#{@spec.full_name}") + lib_path = super(opts.merge(path: lib_path, no_default: opts[:no_default])) destination = opts[:path] || _default_path FileUtils.mkdir_p(lib_path.join(destination)) @@ -603,16 +646,16 @@ module Spec Bundler.rubygems.build(@spec, opts[:skip_validation]) end elsif opts[:skip_validation] - @context.gem_command "build --force #{@spec.name}", :dir => lib_path + @context.gem_command "build --force #{@spec.name}", dir: lib_path else - @context.gem_command "build #{@spec.name}", :dir => lib_path + @context.gem_command "build #{@spec.name}", dir: lib_path end gem_path = File.expand_path("#{@spec.full_name}.gem", lib_path) if opts[:to_system] - @context.system_gems gem_path, :default => opts[:default] + @context.system_gems gem_path, default: opts[:default] elsif opts[:to_bundle] - @context.system_gems gem_path, :path => @context.default_bundle_path + @context.system_gems gem_path, path: @context.default_bundle_path else FileUtils.mv(gem_path, destination) end @@ -632,7 +675,7 @@ module Spec end end - TEST_CERT = <<-CERT.gsub(/^\s*/, "") + TEST_CERT = <<~CERT -----BEGIN CERTIFICATE----- MIIDMjCCAhqgAwIBAgIBATANBgkqhkiG9w0BAQUFADAnMQwwCgYDVQQDDAN5b3Ux FzAVBgoJkiaJk/IsZAEZFgdleGFtcGxlMB4XDTE1MDIwODAwMTIyM1oXDTQyMDYy @@ -655,7 +698,7 @@ module Spec -----END CERTIFICATE----- CERT - TEST_PKEY = <<-PKEY.gsub(/^\s*/, "") + TEST_PKEY = <<~PKEY -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEA2W8V2k3jdzgMxL0mjTqbRruTdtDcdZDXKtiFkyLvsXUXvc2k GSdgcjMOS1CkafqGz/hAUlPibjM0QEXjtQuMdTmdMrmuORLeeIZhSO+HdkTNV6j3 diff --git a/spec/bundler/support/bundle.rb b/spec/bundler/support/bundle.rb index bb21526d35..5d6d658040 100644 --- a/spec/bundler/support/bundle.rb +++ b/spec/bundler/support/bundle.rb @@ -1,8 +1,5 @@ # frozen_string_literal: true -require "rubygems" -require_relative "path" -bundler_gemspec = Spec::Path.loaded_gemspec -bundler_gemspec.instance_variable_set(:@full_gem_path, Spec::Path.source_root) -bundler_gemspec.activate if bundler_gemspec.respond_to?(:activate) +require_relative "activate" + load File.expand_path("bundle", Spec::Path.bindir) diff --git a/spec/bundler/support/checksums.rb b/spec/bundler/support/checksums.rb new file mode 100644 index 0000000000..f758559b3b --- /dev/null +++ b/spec/bundler/support/checksums.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +module Spec + module Checksums + class ChecksumsBuilder + def initialize(enabled = true, &block) + @enabled = enabled + @checksums = {} + yield self if block_given? + end + + def initialize_copy(original) + super + @checksums = @checksums.dup + end + + def checksum(repo, name, version, platform = Gem::Platform::RUBY) + name_tuple = Gem::NameTuple.new(name, version, platform) + gem_file = File.join(repo, "gems", "#{name_tuple.full_name}.gem") + File.open(gem_file, "rb") do |f| + register(name_tuple, Bundler::Checksum.from_gem(f, "#{gem_file} (via ChecksumsBuilder#checksum)")) + end + end + + def no_checksum(name, version, platform = Gem::Platform::RUBY) + name_tuple = Gem::NameTuple.new(name, version, platform) + register(name_tuple, nil) + end + + def delete(name, platform = nil) + @checksums.reject! {|k, _| k.name == name && (platform.nil? || k.platform == platform) } + end + + def to_s + return "" unless @enabled + + locked_checksums = @checksums.map do |name_tuple, checksum| + checksum &&= " #{checksum.to_lock}" + " #{name_tuple.lock_name}#{checksum}\n" + end + + "\nCHECKSUMS\n#{locked_checksums.sort.join}" + end + + private + + def register(name_tuple, checksum) + delete(name_tuple.name, name_tuple.platform) + @checksums[name_tuple] = checksum + end + end + + def checksums_section(enabled = true, &block) + ChecksumsBuilder.new(enabled, &block) + end + + def checksums_section_when_existing(&block) + begin + enabled = lockfile.match?(/^CHECKSUMS$/) + rescue Errno::ENOENT + enabled = false + end + checksums_section(enabled, &block) + end + + def checksum_to_lock(*args) + checksums_section do |c| + c.checksum(*args) + end.to_s.sub(/^CHECKSUMS\n/, "").strip + end + + def checksum_digest(*args) + checksum_to_lock(*args).split(Bundler::Checksum::ALGO_SEPARATOR, 2).last + end + + # if prefixes is given, removes all checksums where the line + # has any of the prefixes on the line before the checksum + # otherwise, removes all checksums from the lockfile + def remove_checksums_from_lockfile(lockfile, *prefixes) + head, remaining = lockfile.split(/^CHECKSUMS$/, 2) + return lockfile unless remaining + checksums, tail = remaining.split("\n\n", 2) + + prefixes = + if prefixes.empty? + nil + else + /(#{prefixes.map {|p| Regexp.escape(p) }.join("|")})/ + end + + checksums = checksums.each_line.map do |line| + if prefixes.nil? || line.match?(prefixes) + line.gsub(/ sha256=[a-f0-9]{64}/i, "") + else + line + end + end + + head.concat( + "CHECKSUMS", + checksums.join, + "\n\n", + tail + ) + end + + def remove_checksums_section_from_lockfile(lockfile) + head, remaining = lockfile.split(/^CHECKSUMS$/, 2) + return lockfile unless remaining + _checksums, tail = remaining.split("\n\n", 2) + head.concat(tail) + end + end +end diff --git a/spec/bundler/support/command_execution.rb b/spec/bundler/support/command_execution.rb index 68e5c56c75..5639fda3b6 100644 --- a/spec/bundler/support/command_execution.rb +++ b/spec/bundler/support/command_execution.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Spec - CommandExecution = Struct.new(:command, :working_directory, :exitstatus, :stdout, :stderr) do + CommandExecution = Struct.new(:command, :working_directory, :exitstatus, :original_stdout, :original_stderr) do def to_s "$ #{command}" end @@ -11,6 +11,19 @@ module Spec @stdboth ||= [stderr, stdout].join("\n").strip end + def stdout + original_stdout + end + + # Can be removed once/if https://github.com/oneclick/rubyinstaller2/pull/369 is resolved + def stderr + return original_stderr unless Gem.win_platform? + + original_stderr.split("\n").reject do |l| + l.include?("operating_system_defaults") + end.join("\n") + end + def to_s_verbose [ to_s, diff --git a/spec/bundler/support/filters.rb b/spec/bundler/support/filters.rb index 0c1f27e470..8e164af756 100644 --- a/spec/bundler/support/filters.rb +++ b/spec/bundler/support/filters.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative "sudo" - class RequirementChecker < Proc def self.against(present) provided = Gem::Version.new(present) @@ -21,19 +19,20 @@ class RequirementChecker < Proc end RSpec.configure do |config| - config.filter_run_excluding :sudo => true - config.filter_run_excluding :realworld => true - - git_version = Bundler::Source::Git::GitProxy.new(nil, nil, nil).version - - config.filter_run_excluding :git => RequirementChecker.against(git_version) - config.filter_run_excluding :bundler => RequirementChecker.against(Bundler::VERSION.split(".")[0]) - config.filter_run_excluding :ruby_repo => !ENV["GEM_COMMAND"].nil? - config.filter_run_excluding :no_color_tty => Gem.win_platform? || !ENV["GITHUB_ACTION"].nil? - config.filter_run_excluding :permissions => Gem.win_platform? - config.filter_run_excluding :readline => Gem.win_platform? - config.filter_run_excluding :jruby => RUBY_ENGINE != "jruby" - config.filter_run_excluding :truffleruby => RUBY_ENGINE != "truffleruby" + config.filter_run_excluding realworld: true + + git_version = Bundler::Source::Git::GitProxy.new(nil, nil).version + + config.filter_run_excluding git: RequirementChecker.against(git_version) + config.filter_run_excluding bundler: RequirementChecker.against(Bundler::VERSION.split(".")[0]) + config.filter_run_excluding rubygems: RequirementChecker.against(Gem::VERSION) + config.filter_run_excluding ruby_repo: !ENV["GEM_COMMAND"].nil? + config.filter_run_excluding no_color_tty: Gem.win_platform? || !ENV["GITHUB_ACTION"].nil? + config.filter_run_excluding permissions: Gem.win_platform? + config.filter_run_excluding readline: Gem.win_platform? + config.filter_run_excluding jruby_only: RUBY_ENGINE != "jruby" + config.filter_run_excluding truffleruby_only: RUBY_ENGINE != "truffleruby" + config.filter_run_excluding man: Gem.win_platform? config.filter_run_when_matching :focus unless ENV["CI"] end diff --git a/spec/bundler/support/hax.rb b/spec/bundler/support/hax.rb index aaf8c74894..c7fe3637cc 100644 --- a/spec/bundler/support/hax.rb +++ b/spec/bundler/support/hax.rb @@ -1,5 +1,10 @@ # frozen_string_literal: true +if ENV["BUNDLER_SPEC_RUBY_PLATFORM"] + Object.send(:remove_const, :RUBY_PLATFORM) + RUBY_PLATFORM = ENV["BUNDLER_SPEC_RUBY_PLATFORM"] +end + module Gem def self.ruby=(ruby) @ruby = ruby @@ -9,33 +14,40 @@ module Gem Gem.ruby = ENV["RUBY"] end - @default_dir = ENV["BUNDLER_GEM_DEFAULT_DIR"] if ENV["BUNDLER_GEM_DEFAULT_DIR"] + if ENV["BUNDLER_GEM_DEFAULT_DIR"] + @default_dir = ENV["BUNDLER_GEM_DEFAULT_DIR"] + @default_specifications_dir = nil + end + + if ENV["BUNDLER_SPEC_WINDOWS"] + @@win_platform = true # rubocop:disable Style/ClassVars + end if ENV["BUNDLER_SPEC_PLATFORM"] + previous_platforms = @platforms + previous_local = Platform.local + class Platform @local = new(ENV["BUNDLER_SPEC_PLATFORM"]) end - @platforms = [Gem::Platform::RUBY, Gem::Platform.local] - - if ENV["BUNDLER_SPEC_PLATFORM"] == "ruby" - class << self - remove_method :finish_resolve - - def finish_resolve - [] - end - end - end + @platforms = previous_platforms.map {|platform| platform == previous_local ? Platform.local : platform } end if ENV["BUNDLER_SPEC_GEM_SOURCES"] - @sources = [ENV["BUNDLER_SPEC_GEM_SOURCES"]] + self.sources = [ENV["BUNDLER_SPEC_GEM_SOURCES"]] end - # We only need this hack for rubygems versions without the BundlerVersionFinder - if Gem.rubygems_version < Gem::Version.new("2.7.0") - @path_to_default_spec_map.delete_if do |_path, spec| - spec.name == "bundler" + if ENV["BUNDLER_IGNORE_DEFAULT_GEM"] + module RemoveDefaultBundlerStub + def default_stubs(pattern = "*") + super.delete_if {|stub| stub.name == "bundler" } + end + end + + class Specification + class << self + prepend RemoveDefaultBundlerStub + end end end end diff --git a/spec/bundler/support/helpers.rb b/spec/bundler/support/helpers.rb index 13fa36fbc2..1ad9cc78ca 100644 --- a/spec/bundler/support/helpers.rb +++ b/spec/bundler/support/helpers.rb @@ -43,7 +43,7 @@ module Spec last_command.stderr end - MAJOR_DEPRECATION = /^\[DEPRECATED\]\s*/.freeze + MAJOR_DEPRECATION = /^\[DEPRECATED\]\s*/ def err_without_deprecations err.gsub(/#{MAJOR_DEPRECATION}.+[\n]?/, "") @@ -60,7 +60,7 @@ module Spec def run(cmd, *args) opts = args.last.is_a?(Hash) ? args.pop : {} groups = args.map(&:inspect).join(", ") - setup = "require '#{entrypoint}' ; Bundler.ui.silence { Bundler.setup(#{groups}) }" + setup = "require 'bundler' ; Bundler.ui.silence { Bundler.setup(#{groups}) }" ruby([setup, cmd].join(" ; "), opts) end @@ -78,9 +78,6 @@ module Spec end def bundle(cmd, options = {}, &block) - with_sudo = options.delete(:sudo) - sudo = with_sudo == :preserve_env ? "sudo -E --preserve-env=RUBYOPT" : "sudo" if with_sudo - bundle_bin = options.delete(:bundle_bin) bundle_bin ||= installed_bindir.join("bundle") @@ -88,7 +85,6 @@ module Spec requires = options.delete(:requires) || [] realworld = RSpec.current_example.metadata[:realworld] - options[:verbose] = true if options[:verbose].nil? && realworld artifice = options.delete(:artifice) do if realworld @@ -120,9 +116,9 @@ module Spec end end.join - ruby_cmd = build_ruby_cmd({ :sudo => sudo, :load_path => load_path, :requires => requires }) + ruby_cmd = build_ruby_cmd({ load_path: load_path, requires: requires, env: env }) cmd = "#{ruby_cmd} #{bundle_bin} #{cmd}#{args}" - sys_exec(cmd, { :env => env, :dir => dir, :raise_on_error => raise_on_error }, &block) + sys_exec(cmd, { env: env, dir: dir, raise_on_error: raise_on_error }, &block) end def bundler(cmd, options = {}) @@ -147,16 +143,20 @@ module Spec end def build_ruby_cmd(options = {}) - sudo = options.delete(:sudo) - libs = options.delete(:load_path) lib_option = libs ? "-I#{libs.join(File::PATH_SEPARATOR)}" : [] requires = options.delete(:requires) || [] - requires << "#{Path.spec_dir}/support/hax.rb" + + hax_path = "#{Path.spec_dir}/support/hax.rb" + + # For specs that need to ignore the default Bundler gem, load hax before + # anything else since other stuff may actually load bundler and not skip + # the default version + options[:env]&.include?("BUNDLER_IGNORE_DEFAULT_GEM") ? requires.prepend(hax_path) : requires.append(hax_path) require_option = requires.map {|r| "-r#{r}" } - [sudo, Gem.ruby, *lib_option, *require_option].compact.join(" ") + [Gem.ruby, *lib_option, *require_option].compact.join(" ") end def gembin(cmd, options = {}) @@ -166,7 +166,7 @@ module Spec def gem_command(command, options = {}) env = options[:env] || {} - env["RUBYOPT"] = opt_add("-r#{spec_dir}/support/hax.rb", env["RUBYOPT"] || ENV["RUBYOPT"]) + env["RUBYOPT"] = opt_add(opt_add("-r#{spec_dir}/support/hax.rb", env["RUBYOPT"]), ENV["RUBYOPT"]) options[:env] = env sys_exec("#{Path.gem_bin} #{command}", options) end @@ -176,31 +176,31 @@ module Spec end def git(cmd, path, options = {}) - sys_exec("git #{cmd}", options.merge(:dir => path)) + sys_exec("git #{cmd}", options.merge(dir: path)) end def sys_exec(cmd, options = {}) env = options[:env] || {} - env["RUBYOPT"] = opt_add("-r#{spec_dir}/support/switch_rubygems.rb", env["RUBYOPT"] || ENV["RUBYOPT"]) + env["RUBYOPT"] = opt_add(opt_add("-r#{spec_dir}/support/switch_rubygems.rb", env["RUBYOPT"]), ENV["RUBYOPT"]) dir = options[:dir] || bundled_app command_execution = CommandExecution.new(cmd.to_s, dir) require "open3" require "shellwords" - Open3.popen3(env, *cmd.shellsplit, :chdir => dir) do |stdin, stdout, stderr, wait_thr| + Open3.popen3(env, *cmd.shellsplit, chdir: dir) do |stdin, stdout, stderr, wait_thr| yield stdin, stdout, wait_thr if block_given? stdin.close stdout_read_thread = Thread.new { stdout.read } stderr_read_thread = Thread.new { stderr.read } - command_execution.stdout = stdout_read_thread.value.strip - command_execution.stderr = stderr_read_thread.value.strip + command_execution.original_stdout = stdout_read_thread.value.strip + command_execution.original_stderr = stderr_read_thread.value.strip status = wait_thr.value command_execution.exitstatus = if status.exited? status.exitstatus elsif status.signaled? - 128 + status.termsig + exit_status_for_signal(status.termsig) end end @@ -220,13 +220,13 @@ module Spec end def all_commands_output - return [] if command_executions.empty? + return "" if command_executions.empty? "\n\nCommands:\n#{command_executions.map(&:to_s_verbose).join("\n\n")}" end def config(config = nil, path = bundled_app(".bundle/config")) - return YAML.load_file(path) unless config + return Psych.load_file(path) unless config FileUtils.mkdir_p(File.dirname(path)) File.open(path, "w") do |f| f.puts config.to_yaml @@ -238,36 +238,46 @@ module Spec config(config, home(".bundle/config")) end - def create_file(*args) - path = bundled_app(args.shift) - path = args.shift if args.first.is_a?(Pathname) - str = args.shift || "" + def create_file(path, contents = "") + path = Pathname.new(path).expand_path(bundled_app) unless path.is_a?(Pathname) path.dirname.mkpath File.open(path.to_s, "w") do |f| - f.puts strip_whitespace(str) + f.puts strip_whitespace(contents) end end def gemfile(*args) - contents = args.shift + contents = args.pop if contents.nil? - File.open(bundled_app_gemfile, "r", &:read) + read_gemfile else - create_file("Gemfile", contents, *args) + create_file(args.pop || "Gemfile", contents) end end def lockfile(*args) - contents = args.shift + contents = args.pop if contents.nil? - File.open(bundled_app_lock, "r", &:read) + read_lockfile else - create_file("Gemfile.lock", contents, *args) + create_file(args.pop || "Gemfile.lock", contents) end end + def read_gemfile(file = "Gemfile") + read_bundled_app_file(file) + end + + def read_lockfile(file = "Gemfile.lock") + read_bundled_app_file(file) + end + + def read_bundled_app_file(file) + bundled_app(file).read + end + def strip_whitespace(str) # Trim the leading spaces spaces = str[/\A\s+/, 0] || "" @@ -275,8 +285,8 @@ module Spec end def install_gemfile(*args) + opts = args.last.is_a?(Hash) ? args.pop : {} gemfile(*args) - opts = args.last.is_a?(Hash) ? args.last : {} bundle :install, opts end @@ -289,65 +299,42 @@ module Spec def system_gems(*gems) gems = gems.flatten options = gems.last.is_a?(Hash) ? gems.pop : {} - path = options.fetch(:path, system_gem_path) + install_dir = options.fetch(:path, system_gem_path) default = options.fetch(:default, false) - with_gem_path_as(path) do + with_gem_path_as(install_dir) do gem_repo = options.fetch(:gem_repo, gem_repo1) gems.each do |g| gem_name = g.to_s if gem_name.start_with?("bundler") version = gem_name.match(/\Abundler-(?<version>.*)\z/)[:version] if gem_name != "bundler" - with_built_bundler(version) {|gem_path| install_gem(gem_path, default) } - elsif gem_name =~ %r{\A(?:[a-zA-Z]:)?/.*\.gem\z} - install_gem(gem_name, default) + with_built_bundler(version) {|gem_path| install_gem(gem_path, install_dir, default) } + elsif %r{\A(?:[a-zA-Z]:)?/.*\.gem\z}.match?(gem_name) + install_gem(gem_name, install_dir, default) else - install_gem("#{gem_repo}/gems/#{gem_name}.gem", default) + install_gem("#{gem_repo}/gems/#{gem_name}.gem", install_dir, default) end end end end - def install_gem(path, default = false) + def install_gem(path, install_dir, default = false) raise "OMG `#{path}` does not exist!" unless File.exist?(path) - args = "--no-document --ignore-dependencies" - args += " --default --install-dir #{system_gem_path}" if default + args = "--no-document --ignore-dependencies --verbose --local --install-dir #{install_dir}" + args += " --default" if default gem_command "install #{args} '#{path}'" end - def with_built_bundler(version = nil) - version ||= Bundler::VERSION - full_name = "bundler-#{version}" - build_path = tmp + full_name - bundler_path = build_path + "#{full_name}.gem" - - Dir.mkdir build_path - - begin - shipped_files.each do |shipped_file| - target_shipped_file = build_path + shipped_file - target_shipped_dir = File.dirname(target_shipped_file) - FileUtils.mkdir_p target_shipped_dir unless File.directory?(target_shipped_dir) - FileUtils.cp shipped_file, target_shipped_file, :preserve => true - end - - replace_version_file(version, dir: build_path) # rubocop:disable Style/HashSyntax - - Spec::BuildMetadata.write_build_metadata(dir: build_path) # rubocop:disable Style/HashSyntax - - gem_command "build #{relative_gemspec}", :dir => build_path - - yield(bundler_path) - ensure - build_path.rmtree - end + def with_built_bundler(version = nil, &block) + Builders::BundlerBuilder.new(self, "bundler", version)._build(&block) end def with_gem_path_as(path) without_env_side_effects do ENV["GEM_HOME"] = path.to_s ENV["GEM_PATH"] = path.to_s + ENV["BUNDLER_ORIG_GEM_HOME"] = nil ENV["BUNDLER_ORIG_GEM_PATH"] = nil yield end @@ -421,14 +408,14 @@ module Spec end end - def cache_gems(*gems) + def cache_gems(*gems, gem_repo: gem_repo1) gems = gems.flatten FileUtils.rm_rf("#{bundled_app}/vendor/cache") FileUtils.mkdir_p("#{bundled_app}/vendor/cache") gems.each do |g| - path = "#{gem_repo1}/gems/#{g}.gem" + path = "#{gem_repo}/gems/#{g}.gem" raise "OMG `#{path}` does not exist!" unless File.exist?(path) FileUtils.cp(path, "#{bundled_app}/vendor/cache") end @@ -439,6 +426,14 @@ module Spec pristine_system_gems :bundler end + def simulate_ruby_platform(ruby_platform) + old = ENV["BUNDLER_SPEC_RUBY_PLATFORM"] + ENV["BUNDLER_SPEC_RUBY_PLATFORM"] = ruby_platform.to_s + yield + ensure + ENV["BUNDLER_SPEC_RUBY_PLATFORM"] = old + end + def simulate_platform(platform) old = ENV["BUNDLER_SPEC_PLATFORM"] ENV["BUNDLER_SPEC_PLATFORM"] = platform.to_s @@ -447,49 +442,36 @@ module Spec ENV["BUNDLER_SPEC_PLATFORM"] = old if block_given? end - def simulate_ruby_version(version) - return if version == RUBY_VERSION - old = ENV["BUNDLER_SPEC_RUBY_VERSION"] - ENV["BUNDLER_SPEC_RUBY_VERSION"] = version - yield if block_given? - ensure - ENV["BUNDLER_SPEC_RUBY_VERSION"] = old if block_given? - end - - def simulate_windows(platform = mswin) + def simulate_windows(platform = x86_mswin32) + old = ENV["BUNDLER_SPEC_WINDOWS"] + ENV["BUNDLER_SPEC_WINDOWS"] = "true" simulate_platform platform do - simulate_bundler_version_when_missing_prerelease_default_gem_activation do - yield - end + yield end + ensure + ENV["BUNDLER_SPEC_WINDOWS"] = old end - def simulate_bundler_version_when_missing_prerelease_default_gem_activation - return yield unless rubygems_version_failing_to_activate_bundler_prereleases + def current_ruby_minor + Gem.ruby_version.segments.tap {|s| s.delete_at(2) }.join(".") + end - old = ENV["BUNDLER_VERSION"] - ENV["BUNDLER_VERSION"] = Bundler::VERSION - yield - ensure - ENV["BUNDLER_VERSION"] = old + def next_ruby_minor + ruby_major_minor.map.with_index {|s, i| i == 1 ? s + 1 : s }.join(".") end - def env_for_missing_prerelease_default_gem_activation - if rubygems_version_failing_to_activate_bundler_prereleases - { "BUNDLER_VERSION" => Bundler::VERSION } - else - {} - end + def previous_ruby_minor + return "2.7" if ruby_major_minor == [3, 0] + + ruby_major_minor.map.with_index {|s, i| i == 1 ? s - 1 : s }.join(".") end - # versions providing a bundler version finder but not including - # https://github.com/rubygems/rubygems/commit/929e92d752baad3a08f3ac92eaec162cb96aedd1 - def rubygems_version_failing_to_activate_bundler_prereleases - Gem.rubygems_version < Gem::Version.new("3.1.0.pre.1") && Gem.rubygems_version >= Gem::Version.new("2.7.0") + def ruby_major_minor + Gem.ruby_version.segments[0..1] end def revision_for(path) - sys_exec("git rev-parse HEAD", :dir => path).strip + sys_exec("git rev-parse HEAD", dir: path).strip end def with_read_only(pattern) @@ -544,7 +526,7 @@ module Spec def require_rack # need to hack, so we can require rack old_gem_home = ENV["GEM_HOME"] - ENV["GEM_HOME"] = Spec::Path.base_system_gems.to_s + ENV["GEM_HOME"] = Spec::Path.base_system_gem_path.to_s require "rack" ENV["GEM_HOME"] = old_gem_home end @@ -569,6 +551,11 @@ module Spec port end + def exit_status_for_signal(signal_number) + # For details see: https://en.wikipedia.org/wiki/Exit_status#Shell_and_scripts + 128 + signal_number + end + private def git_root_dir? diff --git a/spec/bundler/support/indexes.rb b/spec/bundler/support/indexes.rb index bf4300edb2..086a311551 100644 --- a/spec/bundler/support/indexes.rb +++ b/spec/bundler/support/indexes.rb @@ -14,22 +14,25 @@ module Spec alias_method :platforms, :platform - def resolve(args = []) + def resolve(args = [], dependency_api_available: true) @platforms ||= ["ruby"] - deps = [] - default_source = instance_double("Bundler::Source::Rubygems", :specs => @index) - source_requirements = { :default => default_source } + default_source = instance_double("Bundler::Source::Rubygems", specs: @index, to_s: "locally install gems", dependency_api_available?: dependency_api_available) + source_requirements = { default: default_source } + base = args[0] || Bundler::SpecSet.new([]) + base.each {|ls| ls.source = default_source } + gem_version_promoter = args[1] || Bundler::GemVersionPromoter.new + originally_locked = args[2] || Bundler::SpecSet.new([]) + unlock = args[3] || [] @deps.each do |d| - source_requirements[d.name] = d.source = default_source - @platforms.each do |p| - deps << Bundler::DepProxy.get_proxy(d, p) - end + name = d.name + source_requirements[name] = d.source = default_source end - args[0] ||= [] # base - args[1] ||= Bundler::GemVersionPromoter.new # gem_version_promoter - args[2] ||= [] # additional_base_requirements - args[3] ||= @platforms # platforms - Bundler::Resolver.resolve(deps, source_requirements, *args) + packages = Bundler::Resolver::Base.new(source_requirements, @deps, base, @platforms, locked_specs: originally_locked, unlock: unlock) + Bundler::Resolver.new(packages, gem_version_promoter).start + end + + def should_not_resolve + expect { resolve }.to raise_error(Bundler::GemNotFound) end def should_resolve_as(specs) @@ -38,6 +41,12 @@ module Spec expect(got).to eq(specs.sort) end + def should_resolve_without_dependency_api(specs) + got = resolve(dependency_api_available: false) + got = got.map(&:full_name).sort + expect(got).to eq(specs.sort) + end + def should_resolve_and_include(specs, args = []) got = resolve(args) got = got.map(&:full_name).sort @@ -46,13 +55,6 @@ module Spec end end - def should_conflict_on(names) - got = resolve - raise "The resolve succeeded with: #{got.map(&:full_name).sort.inspect}" - rescue Bundler::VersionConflict => e - expect(Array(names).sort).to eq(e.conflicts.sort) - end - def gem(*args, &blk) build_spec(*args, &blk).first end @@ -66,12 +68,11 @@ module Spec def should_conservative_resolve_and_include(opts, unlock, specs) # empty unlock means unlock all opts = Array(opts) - search = Bundler::GemVersionPromoter.new(@locked, unlock).tap do |s| + search = Bundler::GemVersionPromoter.new.tap do |s| s.level = opts.first s.strict = opts.include?(:strict) - s.prerelease_specified = Hash[@deps.map {|d| [d.name, d.requirement.prerelease?] }] end - should_resolve_and_include specs, [@base, search] + should_resolve_and_include specs, [@base, search, @locked, unlock] end def an_awesome_index @@ -126,7 +127,7 @@ module Spec next if version == v("1.4.2.1") && platform != pl("x86-mswin32") next if version == v("1.4.2") && platform == pl("x86-mswin32") gem "nokogiri", version, platform do - dep "weakling", ">= 0.0.3" if platform =~ pl("java") + dep "weakling", ">= 0.0.3" if platform =~ pl("java") # rubocop:disable Performance/RegexpMatch end end end @@ -303,7 +304,7 @@ module Spec end end - def a_unresovable_child_index + def a_unresolvable_child_index build_index do gem "json", %w[1.8.0] diff --git a/spec/bundler/support/matchers.rb b/spec/bundler/support/matchers.rb index 180ad54c5b..0f027dcf04 100644 --- a/spec/bundler/support/matchers.rb +++ b/spec/bundler/support/matchers.rb @@ -150,7 +150,7 @@ module Spec end if exitstatus == 65 actual_platform = out.split("\n").last - next "#{name} was expected to be of platform #{platform} but was #{actual_platform}" + next "#{name} was expected to be of platform #{platform || "ruby"} but was #{actual_platform || "ruby"}" end if exitstatus == 66 actual_source = out.split("\n").last @@ -178,7 +178,7 @@ module Spec begin require '#{name}' - name_constant = '#{Spec::Builders.constantize(name)}' + name_constant = #{Spec::Builders.constantize(name)} if #{version.nil?} || name_constant == '#{version}' exit 64 else @@ -208,10 +208,6 @@ module Spec RSpec::Matchers.define_negated_matcher :not_include_gems, :include_gems RSpec::Matchers.alias_matcher :include_gem, :include_gems - def have_lockfile(expected) - read_as(strip_whitespace(expected)) - end - def plugin_should_be_installed(*names) names.each do |name| expect(Bundler::Plugin).to be_installed(name) @@ -225,13 +221,5 @@ module Spec expect(Bundler::Plugin).not_to be_installed(name) end end - - def lockfile_should_be(expected) - expect(bundled_app_lock).to have_lockfile(expected) - end - - def gemfile_should_be(expected) - expect(bundled_app_gemfile).to read_as(strip_whitespace(expected)) - end end end diff --git a/spec/bundler/support/path.rb b/spec/bundler/support/path.rb index a98ef7c6cf..7352d5a353 100644 --- a/spec/bundler/support/path.rb +++ b/spec/bundler/support/path.rb @@ -30,7 +30,7 @@ module Spec end def test_gemfile - @test_gemfile ||= source_root.join("tool/bundler/test_gems.rb") + @test_gemfile ||= tool_dir.join("test_gems.rb") end def rubocop_gemfile @@ -42,7 +42,7 @@ module Spec end def dev_gemfile - @dev_gemfile ||= git_root.join("dev_gems.rb") + @dev_gemfile ||= tool_dir.join("dev_gems.rb") end def bindir @@ -71,10 +71,6 @@ module Spec @spec_dir ||= source_root.join(ruby_core? ? "spec/bundler" : "spec") end - def api_request_limit_hack_file - spec_dir.join("support/api_request_limit_hax.rb") - end - def man_dir @man_dir ||= lib_dir.join("bundler/man") end @@ -84,7 +80,13 @@ module Spec end def shipped_files - @shipped_files ||= loaded_gemspec.files + @shipped_files ||= if ruby_core_tarball? + loaded_gemspec.files.map {|f| f.gsub(%r{^exe/}, "libexec/") } + elsif ruby_core? + tracked_files + else + loaded_gemspec.files + end end def lib_tracked_files @@ -118,6 +120,14 @@ module Spec end end + def default_cache_path(*path) + if Bundler.feature_flag.global_gem_cache? + home(".bundle/cache", *path) + else + default_bundle_path("cache/bundler", *path) + end + end + def bundled_app(*path) root = tmp.join("bundled_app") FileUtils.mkdir_p(root) @@ -146,6 +156,10 @@ module Spec bundled_app("Gemfile.lock") end + def base_system_gem_path + scoped_gem_path(base_system_gems) + end + def base_system_gems tmp.join("gems/base") end @@ -217,13 +231,6 @@ module Spec root.join("lib") end - # Sometimes rubygems version under test does not include - # https://github.com/rubygems/rubygems/pull/2728 and will not always end up - # activating the current bundler. In that case, require bundler absolutely. - def entrypoint - Gem.rubygems_version < Gem::Version.new("3.1.a") ? "#{lib_dir}/bundler" : "bundler" - end - def global_plugin_gem(*args) home ".bundle", "plugin", "gems", *args end @@ -243,6 +250,13 @@ module Spec File.open(version_file, "w") {|f| f << contents } end + def replace_required_ruby_version(version, dir:) + gemspec_file = File.expand_path("bundler.gemspec", dir) + contents = File.read(gemspec_file) + contents.sub!(/(^\s+s\.required_ruby_version\s*=\s*)"[^"]+"/, %(\\1"#{version}")) + File.open(gemspec_file, "w") {|f| f << contents } + end + def ruby_core? # avoid to warnings @ruby_core ||= nil @@ -254,16 +268,20 @@ module Spec end end + def git_root + ruby_core? ? source_root : source_root.parent + end + private def git_ls_files(glob) skip "Not running on a git context, since running tests from a tarball" if ruby_core_tarball? - sys_exec("git ls-files -z -- #{glob}", :dir => source_root).split("\x0") + sys_exec("git ls-files -z -- #{glob}", dir: source_root).split("\x0") end def tracked_files_glob - ruby_core? ? "lib/bundler lib/bundler.rb spec/bundler man/bundle*" : "" + ruby_core? ? "libexec/bundle* lib/bundler lib/bundler.rb spec/bundler man/bundle*" : "lib exe spec CHANGELOG.md LICENSE.md README.md bundler.gemspec" end def lib_tracked_files_glob @@ -274,20 +292,24 @@ module Spec ruby_core? ? "man/bundle* man/gemfile*" : "lib/bundler/man/bundle*.1 lib/bundler/man/gemfile*.5" end - def git_root - ruby_core? ? source_root : source_root.parent - end - def ruby_core_tarball? !git_root.join(".git").directory? end def rubocop_gemfile_basename - source_root.join("tool/bundler/#{RUBY_VERSION.start_with?("2.3") ? "rubocop23_gems.rb" : "rubocop_gems.rb"}") + tool_dir.join("rubocop_gems.rb") end def standard_gemfile_basename - source_root.join("tool/bundler/#{RUBY_VERSION.start_with?("2.3") ? "standard23_gems.rb" : "standard_gems.rb"}") + tool_dir.join("standard_gems.rb") + end + + def tool_dir + ruby_core? ? source_root.join("tool/bundler") : source_root.join("../tool/bundler") + end + + def templates_dir + lib_dir.join("bundler", "templates") end extend self diff --git a/spec/bundler/support/platforms.rb b/spec/bundler/support/platforms.rb index 0cb7f7cd29..526e1c09a9 100644 --- a/spec/bundler/support/platforms.rb +++ b/spec/bundler/support/platforms.rb @@ -21,31 +21,35 @@ module Spec end def linux - Gem::Platform.new(["x86", "linux", nil]) + Gem::Platform.new("x86_64-linux") end - def mswin + def x86_mswin32 Gem::Platform.new(["x86", "mswin32", nil]) end - def mingw + def x64_mswin64 + Gem::Platform.new(["x64", "mswin64", nil]) + end + + def x86_mingw32 Gem::Platform.new(["x86", "mingw32", nil]) end - def x64_mingw + def x64_mingw32 Gem::Platform.new(["x64", "mingw32", nil]) end - def all_platforms - [rb, java, linux, mswin, mingw, x64_mingw] + def x64_mingw_ucrt + Gem::Platform.new(["x64", "mingw", "ucrt"]) end - def local - generic_local_platform + def windows_platforms + [x86_mswin32, x64_mswin64, x86_mingw32, x64_mingw32, x64_mingw_ucrt] end - def specific_local_platform - Bundler.local_platform + def all_platforms + [rb, java, linux, windows_platforms].flatten end def not_local @@ -55,13 +59,15 @@ module Spec def local_tag if RUBY_PLATFORM == "java" :jruby + elsif ["x64-mingw32", "x64-mingw-ucrt"].include?(RUBY_PLATFORM) + :windows else :ruby end end def not_local_tag - [:ruby, :jruby].find {|tag| tag != local_tag } + [:jruby, :windows, :ruby].find {|tag| tag != local_tag } end def local_ruby_engine @@ -69,12 +75,12 @@ module Spec end def local_engine_version - RUBY_ENGINE_VERSION + RUBY_ENGINE == "ruby" ? Gem.ruby_version : RUBY_ENGINE_VERSION end def not_local_engine_version case not_local_tag - when :ruby + when :ruby, :windows not_local_ruby_version when :jruby "1.6.1" @@ -89,12 +95,17 @@ module Spec 9999 end - def lockfile_platforms - local_platforms.map(&:to_s).sort.join("\n ") + def default_platform_list(*extra, defaults: default_locked_platforms) + defaults.concat(extra).uniq + end + + def lockfile_platforms(*extra, defaults: default_locked_platforms) + platforms = default_platform_list(*extra, defaults: defaults) + platforms.map(&:to_s).sort.join("\n ") end - def local_platforms - [specific_local_platform] + def default_locked_platforms + [local_platform, generic_local_platform] end end end diff --git a/spec/bundler/support/rubygems_ext.rb b/spec/bundler/support/rubygems_ext.rb index 9389543a0f..889ebc90c3 100644 --- a/spec/bundler/support/rubygems_ext.rb +++ b/spec/bundler/support/rubygems_ext.rb @@ -18,9 +18,15 @@ module Spec gem_load_and_activate(gem_name, bin_container) end - def gem_require(gem_name) + def gem_load_and_possibly_install(gem_name, bin_container) + require_relative "switch_rubygems" + + gem_load_activate_and_possibly_install(gem_name, bin_container) + end + + def gem_require(gem_name, entrypoint) gem_activate(gem_name) - require gem_name + require entrypoint end def test_setup @@ -32,6 +38,10 @@ module Spec FileUtils.mkdir_p(Path.tmpdir) ENV["HOME"] = Path.home.to_s + # Remove "RUBY_CODESIGN", which is used by mkmf-generated Makefile to + # sign extension bundles on macOS, to avoid trying to find the specified key + # from the fake $HOME/Library/Keychains directory. + ENV.delete "RUBY_CODESIGN" ENV["TMPDIR"] = Path.tmpdir.to_s require "rubygems/user_interaction" @@ -59,15 +69,13 @@ module Spec Gem.clear_paths ENV["BUNDLE_PATH"] = nil - ENV["GEM_HOME"] = ENV["GEM_PATH"] = Path.base_system_gems.to_s + ENV["GEM_HOME"] = ENV["GEM_PATH"] = Path.base_system_gem_path.to_s ENV["PATH"] = [Path.system_gem_path.join("bin"), ENV["PATH"]].join(File::PATH_SEPARATOR) ENV["PATH"] = [Path.bindir, ENV["PATH"]].join(File::PATH_SEPARATOR) if Path.ruby_core? end def install_test_deps - setup_test_paths - - install_gems(test_gemfile) + install_gems(test_gemfile, Path.base_system_gems.to_s) install_gems(rubocop_gemfile, Path.rubocop_gems.to_s) install_gems(standard_gemfile, Path.standard_gems.to_s) end @@ -82,7 +90,7 @@ module Spec puts success_message puts else - system("git status --porcelain") + system("git diff") puts puts error_message @@ -101,15 +109,30 @@ module Spec abort "We couldn't activate #{gem_name} (#{e.requirement}). Run `gem install #{gem_name}:'#{e.requirement}'`" end + def gem_load_activate_and_possibly_install(gem_name, bin_container) + gem_activate_and_possibly_install(gem_name) + load Gem.bin_path(gem_name, bin_container) + end + + def gem_activate_and_possibly_install(gem_name) + gem_activate(gem_name) + rescue Gem::LoadError => e + Gem.install(gem_name, e.requirement) + retry + end + def gem_activate(gem_name) + require_relative "activate" require "bundler" - gem_requirement = Bundler::LockfileParser.new(File.read(dev_lockfile)).dependencies[gem_name]&.requirement + gem_requirement = Bundler::LockfileParser.new(File.read(dev_lockfile)).specs.find {|spec| spec.name == gem_name }.version gem gem_name, gem_requirement end def install_gems(gemfile, path = nil) old_gemfile = ENV["BUNDLE_GEMFILE"] + old_orig_gemfile = ENV["BUNDLER_ORIG_BUNDLE_GEMFILE"] ENV["BUNDLE_GEMFILE"] = gemfile.to_s + ENV["BUNDLER_ORIG_BUNDLE_GEMFILE"] = nil if path old_path = ENV["BUNDLE_PATH"] @@ -119,8 +142,8 @@ module Spec ENV["BUNDLE_PATH__SYSTEM"] = "true" end - output = `#{Gem.ruby} #{File.expand_path("support/bundle.rb", Path.spec_dir)} install` - raise "Error when installing gems in #{gemfile}: #{output}" unless $?.success? + puts `#{Gem.ruby} #{File.expand_path("support/bundle.rb", Path.spec_dir)} install --verbose` + raise unless $?.success? ensure if path ENV["BUNDLE_PATH"] = old_path @@ -128,6 +151,7 @@ module Spec ENV["BUNDLE_PATH__SYSTEM"] = old_path__system end + ENV["BUNDLER_ORIG_BUNDLE_GEMFILE"] = old_orig_gemfile ENV["BUNDLE_GEMFILE"] = old_gemfile end diff --git a/spec/bundler/support/rubygems_version_manager.rb b/spec/bundler/support/rubygems_version_manager.rb index c2e5a5f484..88da14b67e 100644 --- a/spec/bundler/support/rubygems_version_manager.rb +++ b/spec/bundler/support/rubygems_version_manager.rb @@ -24,30 +24,23 @@ class RubygemsVersionManager def assert_system_features_not_loaded! at_exit do - errors = if $?.nil? - "" - else - all_commands_output - end - rubylibdir = RbConfig::CONFIG["rubylibdir"] rubygems_path = rubylibdir + "/rubygems" rubygems_default_path = rubygems_path + "/defaults" bundler_path = rubylibdir + "/bundler" - bundler_exemptions = Gem.rubygems_version < Gem::Version.new("3.2.0") ? [bundler_path + "/errors.rb"] : [] bad_loaded_features = $LOADED_FEATURES.select do |loaded_feature| (loaded_feature.start_with?(rubygems_path) && !loaded_feature.start_with?(rubygems_default_path)) || - (loaded_feature.start_with?(bundler_path) && !bundler_exemptions.any? {|bundler_exemption| loaded_feature.start_with?(bundler_exemption) }) + loaded_feature.start_with?(bundler_path) end - if bad_loaded_features.any? - errors += "the following features were incorrectly loaded:\n#{bad_loaded_features.join("\n")}" + errors = if bad_loaded_features.any? + all_commands_output + "the following features were incorrectly loaded:\n#{bad_loaded_features.join("\n")}" end - raise errors unless errors.empty? + raise errors if errors end end @@ -72,7 +65,7 @@ class RubygemsVersionManager def switch_local_copy_if_needed return unless local_copy_switch_needed? - sys_exec("git checkout #{target_tag}", :dir => local_copy_path) + sys_exec("git checkout #{target_tag}", dir: local_copy_path) ENV["RGV"] = local_copy_path.to_s end @@ -91,7 +84,7 @@ class RubygemsVersionManager end def local_copy_tag - sys_exec("git rev-parse --abbrev-ref HEAD", :dir => local_copy_path) + sys_exec("git rev-parse --abbrev-ref HEAD", dir: local_copy_path) end def local_copy_path @@ -104,7 +97,7 @@ class RubygemsVersionManager rubygems_path = source_root.join("tmp/rubygems") unless rubygems_path.directory? - sys_exec("git clone .. #{rubygems_path}", :dir => source_root) + sys_exec("git clone .. #{rubygems_path}", dir: source_root) end rubygems_path @@ -119,7 +112,7 @@ class RubygemsVersionManager end def resolve_target_tag - return "v#{@source}" if @source.match(/^\d/) + return "v#{@source}" if @source.match?(/^\d/) @source end diff --git a/spec/bundler/support/sudo.rb b/spec/bundler/support/sudo.rb deleted file mode 100644 index 04e9443945..0000000000 --- a/spec/bundler/support/sudo.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -module Spec - module Sudo - def self.present? - @which_sudo ||= Bundler.which("sudo") - end - - def sudo(cmd) - raise "sudo not present" unless Sudo.present? - sys_exec("sudo #{cmd}") - end - - def chown_system_gems_to_root - sudo "chown -R root #{system_gem_path}" - end - end -end diff --git a/spec/bundler/update/gemfile_spec.rb b/spec/bundler/update/gemfile_spec.rb index 1c5294101e..d32a7945b0 100644 --- a/spec/bundler/update/gemfile_spec.rb +++ b/spec/bundler/update/gemfile_spec.rb @@ -8,8 +8,8 @@ RSpec.describe "bundle update" do gem 'rack' G - bundle :install, :gemfile => bundled_app("NotGemfile") - bundle :update, :gemfile => bundled_app("NotGemfile"), :all => true + bundle :install, gemfile: bundled_app("NotGemfile") + bundle :update, gemfile: bundled_app("NotGemfile"), all: true # Specify BUNDLE_GEMFILE for `the_bundle` # to retrieve the proper Gemfile @@ -30,7 +30,7 @@ RSpec.describe "bundle update" do end it "uses the gemfile to update" do - bundle "update", :all => true + bundle "update", all: true bundle "list" expect(out).to include("rack (1.0.0)") @@ -38,8 +38,8 @@ RSpec.describe "bundle update" do it "uses the gemfile while in a subdirectory" do bundled_app("subdir").mkpath - bundle "update", :all => true, :dir => bundled_app("subdir") - bundle "list", :dir => bundled_app("subdir") + bundle "update", all: true, dir: bundled_app("subdir") + bundle "list", dir: bundled_app("subdir") expect(out).to include("rack (1.0.0)") end diff --git a/spec/bundler/update/gems/fund_spec.rb b/spec/bundler/update/gems/fund_spec.rb index 0dfe63d36d..4a87c16bf7 100644 --- a/spec/bundler/update/gems/fund_spec.rb +++ b/spec/bundler/update/gems/fund_spec.rb @@ -5,20 +5,20 @@ RSpec.describe "bundle update" do build_repo2 do build_gem "has_funding_and_other_metadata" do |s| s.metadata = { - "bug_tracker_uri" => "https://example.com/user/bestgemever/issues", - "changelog_uri" => "https://example.com/user/bestgemever/CHANGELOG.md", + "bug_tracker_uri" => "https://example.com/user/bestgemever/issues", + "changelog_uri" => "https://example.com/user/bestgemever/CHANGELOG.md", "documentation_uri" => "https://www.example.info/gems/bestgemever/0.0.1", - "homepage_uri" => "https://bestgemever.example.io", - "mailing_list_uri" => "https://groups.example.com/bestgemever", - "funding_uri" => "https://example.com/has_funding_and_other_metadata/funding", - "source_code_uri" => "https://example.com/user/bestgemever", - "wiki_uri" => "https://example.com/user/bestgemever/wiki", + "homepage_uri" => "https://bestgemever.example.io", + "mailing_list_uri" => "https://groups.example.com/bestgemever", + "funding_uri" => "https://example.com/has_funding_and_other_metadata/funding", + "source_code_uri" => "https://example.com/user/bestgemever", + "wiki_uri" => "https://example.com/user/bestgemever/wiki", } end build_gem "has_funding", "1.2.3" do |s| s.metadata = { - "funding_uri" => "https://example.com/has_funding/funding", + "funding_uri" => "https://example.com/has_funding/funding", } end end @@ -40,7 +40,7 @@ RSpec.describe "bundle update" do gem 'has_funding' G - bundle :update, :all => true + bundle :update, all: true end it "displays fund message" do diff --git a/spec/bundler/update/gems/post_install_spec.rb b/spec/bundler/update/gems/post_install_spec.rb index 3aaa659d57..e3593387d4 100644 --- a/spec/bundler/update/gems/post_install_spec.rb +++ b/spec/bundler/update/gems/post_install_spec.rb @@ -52,7 +52,7 @@ RSpec.describe "bundle update" do gem 'thin' G - bundle :update, :all => true + bundle :update, all: true end it_behaves_like "a post-install message outputter" @@ -67,7 +67,7 @@ RSpec.describe "bundle update" do gem 'thin' G - bundle :update, :all => true + bundle :update, all: true end it_behaves_like "a post-install message outputter" diff --git a/spec/bundler/update/git_spec.rb b/spec/bundler/update/git_spec.rb index 26ad1b45d3..3b7bbfd979 100644 --- a/spec/bundler/update/git_spec.rb +++ b/spec/bundler/update/git_spec.rb @@ -4,7 +4,7 @@ RSpec.describe "bundle update" do describe "git sources" do it "floats on a branch when :branch is used" do build_git "foo", "1.0" - update_git "foo", :branch => "omg" + update_git "foo", branch: "omg" install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -13,18 +13,18 @@ RSpec.describe "bundle update" do end G - update_git "foo", :branch => "omg" do |s| + update_git "foo" do |s| s.write "lib/foo.rb", "FOO = '1.1'" end - bundle "update", :all => true + bundle "update", all: true expect(the_bundle).to include_gems "foo 1.1" end it "updates correctly when you have like craziness" do - build_lib "activesupport", "3.0", :path => lib_path("rails/activesupport") - build_git "rails", "3.0", :path => lib_path("rails") do |s| + build_lib "activesupport", "3.0", path: lib_path("rails/activesupport") + build_git "rails", "3.0", path: lib_path("rails") do |s| s.add_dependency "activesupport", "= 3.0" end @@ -38,8 +38,8 @@ RSpec.describe "bundle update" do end it "floats on a branch when :branch is used and the source is specified in the update" do - build_git "foo", "1.0", :path => lib_path("foo") - update_git "foo", :branch => "omg", :path => lib_path("foo") + build_git "foo", "1.0", path: lib_path("foo") + update_git "foo", branch: "omg", path: lib_path("foo") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -48,7 +48,7 @@ RSpec.describe "bundle update" do end G - update_git "foo", :branch => "omg", :path => lib_path("foo") do |s| + update_git "foo", path: lib_path("foo") do |s| s.write "lib/foo.rb", "FOO = '1.1'" end @@ -57,9 +57,9 @@ RSpec.describe "bundle update" do expect(the_bundle).to include_gems "foo 1.1" end - it "floats on master when updating all gems that are pinned to the source even if you have child dependencies" do - build_git "foo", :path => lib_path("foo") - build_gem "bar", :to_bundle => true do |s| + it "floats on main when updating all gems that are pinned to the source even if you have child dependencies" do + build_git "foo", path: lib_path("foo") + build_gem "bar", to_bundle: true do |s| s.add_dependency "foo" end @@ -69,7 +69,7 @@ RSpec.describe "bundle update" do gem "bar" G - update_git "foo", :path => lib_path("foo") do |s| + update_git "foo", path: lib_path("foo") do |s| s.write "lib/foo.rb", "FOO = '1.1'" end @@ -79,8 +79,8 @@ RSpec.describe "bundle update" do end it "notices when you change the repo url in the Gemfile" do - build_git "foo", :path => lib_path("foo_one") - build_git "foo", :path => lib_path("foo_two") + build_git "foo", path: lib_path("foo_one") + build_git "foo", path: lib_path("foo_two") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -101,9 +101,9 @@ RSpec.describe "bundle update" do it "fetches tags from the remote" do build_git "foo" - @remote = build_git("bar", :bare => true) - update_git "foo", :remote => file_uri_for(@remote.path) - update_git "foo", :push => "master" + @remote = build_git("bar", bare: true) + update_git "foo", remote: file_uri_for(@remote.path) + update_git "foo", push: "main" install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" @@ -111,19 +111,23 @@ RSpec.describe "bundle update" do G # Create a new tag on the remote that needs fetching - update_git "foo", :tag => "fubar" - update_git "foo", :push => "fubar" + update_git "foo", tag: "fubar" + update_git "foo", push: "fubar" gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem 'foo', :git => "#{@remote.path}", :tag => "fubar" G - bundle "update", :all => true + bundle "update", all: true + expect(err).to be_empty end describe "with submodules" do before :each do + # CVE-2022-39253: https://lore.kernel.org/lkml/xmqq4jw1uku5.fsf@gitster.g/ + system(*%W[git config --global protocol.file.allow always]) + build_repo4 do build_gem "submodule" do |s| s.write "lib/submodule.rb", "puts 'GEM'" @@ -138,8 +142,8 @@ RSpec.describe "bundle update" do s.add_dependency "submodule" end - sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0", :dir => lib_path("has_submodule-1.0") - sys_exec "git commit -m \"submodulator\"", :dir => lib_path("has_submodule-1.0") + sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0", dir: lib_path("has_submodule-1.0") + sys_exec "git commit -m \"submodulator\"", dir: lib_path("has_submodule-1.0") end it "it unlocks the source when submodules are added to a git source" do @@ -164,7 +168,7 @@ RSpec.describe "bundle update" do expect(out).to eq("GIT") end - it "unlocks the source when submodules are removed from git source", :git => ">= 2.9.0" do + it "unlocks the source when submodules are removed from git source", git: ">= 2.9.0" do install_gemfile <<-G source "#{file_uri_for(gem_repo4)}" git "#{lib_path("has_submodule-1.0")}", :submodules => true do @@ -197,7 +201,7 @@ RSpec.describe "bundle update" do lib_path("foo-1.0").join(".git").rmtree - bundle :update, :all => true, :raise_on_error => false + bundle :update, all: true, raise_on_error: false expect(err).to include(lib_path("foo-1.0").to_s). and match(/Git error: command `git fetch.+has failed/) end @@ -205,13 +209,13 @@ RSpec.describe "bundle update" do it "should not explode on invalid revision on update of gem by name" do build_git "rack", "0.8" - build_git "rack", "0.8", :path => lib_path("local-rack") do |s| + build_git "rack", "0.8", path: lib_path("local-rack") do |s| s.write "lib/rack.rb", "puts :LOCAL" end install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{file_uri_for(lib_path("rack-0.8"))}", :branch => "master" + gem "rack", :git => "#{file_uri_for(lib_path("rack-0.8"))}", :branch => "main" G bundle %(config set local.rack #{lib_path("local-rack")}) @@ -220,24 +224,24 @@ RSpec.describe "bundle update" do end it "shows the previous version of the gem" do - build_git "rails", "2.3.2", :path => lib_path("rails") + build_git "rails", "2.3.2", path: lib_path("rails") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "rails", :git => "#{file_uri_for(lib_path("rails"))}" G - update_git "rails", "3.0", :path => lib_path("rails"), :gemspec => true + update_git "rails", "3.0", path: lib_path("rails"), gemspec: true - bundle "update", :all => true - expect(out).to include("Using rails 3.0 (was 2.3.2) from #{file_uri_for(lib_path("rails"))} (at master@#{revision_for(lib_path("rails"))[0..6]})") + bundle "update", all: true + expect(out).to include("Using rails 3.0 (was 2.3.2) from #{file_uri_for(lib_path("rails"))} (at main@#{revision_for(lib_path("rails"))[0..6]})") end end describe "with --source flag" do before :each do build_repo2 - @git = build_git "foo", :path => lib_path("foo") do |s| + @git = build_git "foo", path: lib_path("foo") do |s| s.executables = "foobar" end @@ -251,7 +255,7 @@ RSpec.describe "bundle update" do end it "updates the source" do - update_git "foo", :path => @git.path + update_git "foo", path: @git.path bundle "update --source foo" @@ -264,7 +268,7 @@ RSpec.describe "bundle update" do end it "unlocks gems that were originally pulled in by the source" do - update_git "foo", "2.0", :path => @git.path + update_git "foo", "2.0", path: @git.path bundle "update --source foo" expect(the_bundle).to include_gems "foo 2.0" @@ -272,7 +276,7 @@ RSpec.describe "bundle update" do it "leaves all other gems frozen" do update_repo2 - update_git "foo", :path => @git.path + update_git "foo", path: @git.path bundle "update --source foo" expect(the_bundle).to include_gems "rack 1.0" @@ -282,7 +286,7 @@ RSpec.describe "bundle update" do context "when the gem and the repository have different names" do before :each do build_repo2 - @git = build_git "foo", :path => lib_path("bar") + @git = build_git "foo", path: lib_path("bar") install_gemfile <<-G source "#{file_uri_for(gem_repo2)}" @@ -293,55 +297,24 @@ RSpec.describe "bundle update" do G end - it "the --source flag updates version of gems that were originally pulled in by the source", :bundler => "< 3" do + it "the --source flag updates version of gems that were originally pulled in by the source" do spec_lines = lib_path("bar/foo.gemspec").read.split("\n") spec_lines[5] = "s.version = '2.0'" - update_git "foo", "2.0", :path => @git.path do |s| + update_git "foo", "2.0", path: @git.path do |s| s.write "foo.gemspec", spec_lines.join("\n") end - ref = @git.ref_for "master" + ref = @git.ref_for "main" bundle "update --source bar" - lockfile_should_be <<-G - GIT - remote: #{@git.path} - revision: #{ref} - specs: - foo (2.0) - - GEM - remote: #{file_uri_for(gem_repo2)}/ - specs: - rack (1.0.0) - - PLATFORMS - #{lockfile_platforms} - - DEPENDENCIES - foo! - rack - - BUNDLED WITH - #{Bundler::VERSION} - G - end - - it "the --source flag updates version of gems that were originally pulled in by the source", :bundler => "3" do - spec_lines = lib_path("bar/foo.gemspec").read.split("\n") - spec_lines[5] = "s.version = '2.0'" - - update_git "foo", "2.0", :path => @git.path do |s| - s.write "foo.gemspec", spec_lines.join("\n") + checksums = checksums_section_when_existing do |c| + c.no_checksum "foo", "2.0" + c.checksum gem_repo2, "rack", "1.0.0" end - ref = @git.ref_for "master" - - bundle "update --source bar" - - lockfile_should_be <<-G + expect(lockfile).to eq <<~G GIT remote: #{@git.path} revision: #{ref} @@ -359,7 +332,7 @@ RSpec.describe "bundle update" do DEPENDENCIES foo! rack - + #{checksums} BUNDLED WITH #{Bundler::VERSION} G diff --git a/spec/bundler/update/path_spec.rb b/spec/bundler/update/path_spec.rb index 756770313b..1f8992b33f 100644 --- a/spec/bundler/update/path_spec.rb +++ b/spec/bundler/update/path_spec.rb @@ -3,14 +3,14 @@ RSpec.describe "path sources" do describe "bundle update --source" do it "shows the previous version of the gem when updated from path source" do - build_lib "activesupport", "2.3.5", :path => lib_path("rails/activesupport") + build_lib "activesupport", "2.3.5", path: lib_path("rails/activesupport") install_gemfile <<-G source "#{file_uri_for(gem_repo1)}" gem "activesupport", :path => "#{lib_path("rails/activesupport")}" G - build_lib "activesupport", "3.0", :path => lib_path("rails/activesupport") + build_lib "activesupport", "3.0", path: lib_path("rails/activesupport") bundle "update --source activesupport" expect(out).to include("Using activesupport 3.0 (was 2.3.5) from source at `#{lib_path("rails/activesupport")}`") diff --git a/spec/bundler/update/redownload_spec.rb b/spec/bundler/update/redownload_spec.rb index 147be823f5..4a8c711bfa 100644 --- a/spec/bundler/update/redownload_spec.rb +++ b/spec/bundler/update/redownload_spec.rb @@ -9,12 +9,12 @@ RSpec.describe "bundle update" do end describe "with --force" do - it "shows a deprecation when single flag passed", :bundler => 2 do + it "shows a deprecation when single flag passed", bundler: 2 do bundle "update rack --force" expect(err).to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" end - it "shows a deprecation when multiple flags passed", :bundler => 2 do + it "shows a deprecation when multiple flags passed", bundler: 2 do bundle "update rack --no-color --force" expect(err).to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" end @@ -30,5 +30,15 @@ RSpec.describe "bundle update" do bundle "update rack --no-color --redownload" expect(err).not_to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" end + + it "re-installs installed gems" do + rack_lib = default_bundle_path("gems/rack-1.0.0/lib/rack.rb") + rack_lib.open("w") {|f| f.write("blah blah blah") } + bundle :update, redownload: true + + expect(out).to include "Installing rack 1.0.0" + expect(rack_lib.open(&:read)).to eq("RACK = '1.0.0'\n") + expect(the_bundle).to include_gems "rack 1.0.0" + end end end |