diff options
Diffstat (limited to 'spec/bundler')
188 files changed, 11425 insertions, 6707 deletions
diff --git a/spec/bundler/bundler/build_metadata_spec.rb b/spec/bundler/bundler/build_metadata_spec.rb index afa2d1716f..2e69821f68 100644 --- a/spec/bundler/bundler/build_metadata_spec.rb +++ b/spec/bundler/bundler/build_metadata_spec.rb @@ -6,18 +6,20 @@ require "bundler/build_metadata" RSpec.describe Bundler::BuildMetadata do before do allow(Time).to receive(:now).and_return(Time.at(0)) - Bundler::BuildMetadata.instance_variable_set(:@built_at, nil) + Bundler::BuildMetadata.instance_variable_set(:@timestamp, nil) end - describe "#built_at" do - it "returns %Y-%m-%d formatted time" do - expect(Bundler::BuildMetadata.built_at).to eq "1970-01-01" + describe "#timestamp" do + it "returns %Y-%m-%d formatted current time if built_at not set" do + Bundler::BuildMetadata.instance_variable_set(:@built_at, nil) + expect(Bundler::BuildMetadata.timestamp).to eq "1970-01-01" end - end - describe "#release?" do - it "returns false as default" do - expect(Bundler::BuildMetadata.release?).to be_falsey + it "returns %Y-%m-%d formatted current time if built_at not set" do + Bundler::BuildMetadata.instance_variable_set(:@built_at, "2025-01-01") + expect(Bundler::BuildMetadata.timestamp).to eq "2025-01-01" + ensure + Bundler::BuildMetadata.instance_variable_set(:@built_at, nil) end end @@ -40,10 +42,9 @@ RSpec.describe Bundler::BuildMetadata do describe "#to_h" do subject { Bundler::BuildMetadata.to_h } - it "returns a hash includes Built At, Git SHA and Released Version" do - expect(subject["Built At"]).to eq "1970-01-01" + it "returns a hash includes Timestamp, and Git SHA" do + expect(subject["Timestamp"]).to eq "1970-01-01" expect(subject["Git SHA"]).to be_instance_of(String) - expect(subject["Released Version"]).to be_falsey end end end diff --git a/spec/bundler/bundler/bundler_spec.rb b/spec/bundler/bundler/bundler_spec.rb index 4db8c00e52..bddcbdaef3 100644 --- a/spec/bundler/bundler/bundler_spec.rb +++ b/spec/bundler/bundler/bundler_spec.rb @@ -52,10 +52,10 @@ RSpec.describe Bundler do 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", + s.metadata = { "bug_tracker_uri" => "https://github.com/ruby/rubygems/issues?q=is%3Aopen+is%3Aissue+label%3ABundler", + "changelog_uri" => "https://github.com/ruby/rubygems/blob/master/bundler/CHANGELOG.md", "homepage_uri" => "https://bundler.io/", - "source_code_uri" => "https://github.com/rubygems/rubygems/tree/master/bundler" } + "source_code_uri" => "https://github.com/ruby/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"]) diff --git a/spec/bundler/bundler/cli_common_spec.rb b/spec/bundler/bundler/cli_common_spec.rb new file mode 100644 index 0000000000..015894b3a1 --- /dev/null +++ b/spec/bundler/bundler/cli_common_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require "bundler/cli" + +RSpec.describe Bundler::CLI::Common do + describe "gem_not_found_message" do + it "should suggest alternate gem names" do + message = subject.gem_not_found_message("ralis", ["BOGUS"]) + expect(message).to match("Could not find gem 'ralis'.$") + message = subject.gem_not_found_message("ralis", ["rails"]) + expect(message).to match("Did you mean 'rails'?") + message = subject.gem_not_found_message("Rails", ["rails"]) + expect(message).to match("Did you mean 'rails'?") + message = subject.gem_not_found_message("meail", %w[email fail eval]) + expect(message).to match("Did you mean 'email'?") + message = subject.gem_not_found_message("nokogri", %w[nokogiri rails sidekiq dog]) + expect(message).to match("Did you mean 'nokogiri'?") + message = subject.gem_not_found_message("methosd", %w[method methods bogus]) + expect(message).to match(/Did you mean 'method(|s)' or 'method(|s)'?/) + end + end +end diff --git a/spec/bundler/bundler/cli_spec.rb b/spec/bundler/bundler/cli_spec.rb index b2cc1ccfef..56caf9937e 100644 --- a/spec/bundler/bundler/cli_spec.rb +++ b/spec/bundler/bundler/cli_spec.rb @@ -87,15 +87,25 @@ RSpec.describe "bundle executable" do end context "with no arguments" do - it "prints a concise help message", bundler: "3" do - bundle "" - expect(err).to be_empty + it "tries to installs by default but print help on missing Gemfile" do + bundle "", raise_on_error: false + expect(err).to include("Could not locate Gemfile") + expect(out).to include("In a future version of Bundler") + expect(out).to include("Bundler version #{Bundler::VERSION}"). and include("\n\nBundler commands:\n\n"). and include("\n\n Primary commands:\n"). and include("\n\n Utilities:\n"). and include("\n\nOptions:\n") end + + it "runs bundle install when default_cli_command set to install" do + bundle_config "default_cli_command install" + bundle "", raise_on_error: false + expect(out).to_not include("In a future version of Bundler") + expect(err).to include("Could not locate Gemfile") + expect(exitstatus).to_not be_zero + end end context "when ENV['BUNDLE_GEMFILE'] is set to an empty string" do @@ -112,20 +122,34 @@ RSpec.describe "bundle executable" do end context "with --verbose" do - it "prints the running command" do + before do gemfile "source 'https://gem.repo1'" + end + + it "prints the running command" do 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 'https://gem.repo1'", verbose: true + bundle "install", 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 'https://gem.repo1'", verbose: true - expect(out).to start_with("Running `bundle install --verbose` with bundler #{Bundler::VERSION}") + it "prints the simulated version too when setting is enabled" do + bundle "config set simulate_version 4", verbose: true + bundle "info bundler", verbose: true + expect(out).to start_with("Running `bundle info bundler --verbose` with bundler #{Bundler::VERSION} (simulating Bundler 4)") + end + end + + context "with verbose configuration" do + before do + bundle_config "verbose true" + end + + it "prints the running command" do + gemfile "source 'https://gem.repo1'" + bundle "info bundler" + expect(out).to start_with("Running `bundle info bundler` with bundler #{Bundler::VERSION}") end end @@ -179,14 +203,14 @@ RSpec.describe "bundle executable" do shared_examples_for "no warning" do it "prints no warning" do bundle "fail", env: { "BUNDLER_VERSION" => bundler_version }, raise_on_error: false - expect(last_command.stdboth).to eq("Could not find command \"fail\".") + expect(stdboth).to eq("Could not find command \"fail\".") end end let(:bundler_version) { "2.0" } let(:latest_version) { nil } before do - bundle "config set --global disable_version_check false" + bundle_config_global "disable_version_check false" pristine_system_gems "bundler-#{bundler_version}" if latest_version @@ -227,11 +251,13 @@ To update to the most recent version, run `bundle update --bundler` context "running a parseable command" do it "prints no warning" do + bundle "config set foo value", env: { "BUNDLER_VERSION" => bundler_version } bundle "config get --parseable foo", env: { "BUNDLER_VERSION" => bundler_version } - expect(last_command.stdboth).to eq "" + expect(out).to eq "foo=value" + expect(err).to eq "" bundle "platform --ruby", env: { "BUNDLER_VERSION" => bundler_version }, raise_on_error: false - expect(last_command.stdboth).to eq "Could not locate Gemfile" + expect(stdboth).to eq "Could not locate Gemfile" end end @@ -250,13 +276,23 @@ To update to the most recent version, run `bundle update --bundler` 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" do bundler "--version" - expect(out).to eq("Bundler version #{Bundler::VERSION}") - end + expect(out).to eq(Bundler::VERSION.to_s) - it "shows the bundler version just as the `bundle` executable does", bundler: "3" do + bundle_config "simulate_version 5" bundler "--version" - expect(out).to eq(Bundler::VERSION) + expect(out).to eq("#{Bundler::VERSION} (simulating Bundler 5)") + end + + it "shows cli_help when bundler install and no Gemfile is found" do + bundler "install", raise_on_error: false + expect(err).to include("Could not locate Gemfile") + + expect(out).to include("Bundler version #{Bundler::VERSION}"). + and include("\n\nBundler commands:\n\n"). + and include("\n\n Primary commands:\n"). + and include("\n\n Utilities:\n"). + and include("\n\nOptions:\n") end end diff --git a/spec/bundler/bundler/compact_index_client/parser_spec.rb b/spec/bundler/bundler/compact_index_client/parser_spec.rb index 1f6b9e593b..6aa867f058 100644 --- a/spec/bundler/bundler/compact_index_client/parser_spec.rb +++ b/spec/bundler/bundler/compact_index_client/parser_spec.rb @@ -47,7 +47,7 @@ RSpec.describe Bundler::CompactIndexClient::Parser do INFO let(:c_info) { <<~INFO } 3.0.0 a:= 1.0.0,b:~> 2.0|checksum:ccc1,ruby:>= 2.7.0,rubygems:>= 3.0.0 - 3.3.3 a:>= 1.1.0,b:~> 2.0|checksum:ccc3,ruby:>= 3.0.0,rubygems:>= 3.2.3 + 3.3.3 a:>= 1.1.0,b:~> 2.0|checksum:ccc3,ruby:>= 3.0.0,rubygems:>= 3.2.3,created_at:2026-05-12T10:00:00Z INFO describe "#available?" do @@ -195,7 +195,7 @@ RSpec.describe Bundler::CompactIndexClient::Parser do "3.3.3", nil, [["a", [">= 1.1.0"]], ["b", ["~> 2.0"]]], - [["checksum", ["ccc3"]], ["ruby", [">= 3.0.0"]], ["rubygems", [">= 3.2.3"]]], + [["checksum", ["ccc3"]], ["ruby", [">= 3.0.0"]], ["rubygems", [">= 3.2.3"]], ["created_at", ["2026-05-12T10:00:00Z"]]], ], ] end @@ -233,5 +233,17 @@ RSpec.describe Bundler::CompactIndexClient::Parser do VERSIONS expect(parser.info("a")).to eq(a_result) end + + it "handles lines without a checksum" do + compact_index.versions = <<~VERSIONS + created_at: 2024-05-01T00:00:04Z + --- + a 1.0.0,1.0.1,1.1.0 aaa111 + b 2.0.0,2.0.0-java + c 3.0.0,3.0.3,3.3.3 ccc333 + VERSIONS + + expect(parser.info("a")).to eq(a_result) + end end end diff --git a/spec/bundler/bundler/compact_index_client/updater_spec.rb b/spec/bundler/bundler/compact_index_client/updater_spec.rb index 87a73d993f..fd63a652a4 100644 --- a/spec/bundler/bundler/compact_index_client/updater_spec.rb +++ b/spec/bundler/bundler/compact_index_client/updater_spec.rb @@ -115,6 +115,23 @@ RSpec.describe Bundler::CompactIndexClient::Updater do 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 is blank" do + allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:baddigest:" } + allow(response).to receive(:body) { "" } + 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 diff --git a/spec/bundler/bundler/current_ruby_spec.rb b/spec/bundler/bundler/current_ruby_spec.rb new file mode 100644 index 0000000000..79eb802aa5 --- /dev/null +++ b/spec/bundler/bundler/current_ruby_spec.rb @@ -0,0 +1,157 @@ +# frozen_string_literal: true + +RSpec.describe Bundler::CurrentRuby do + 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, + ruby_40: Gem::Platform::RUBY, + ruby_41: 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, + mri_40: Gem::Platform::RUBY, + mri_41: 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, + windows_40: Gem::Platform::WINDOWS, + windows_41: 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, + mswin_40: Gem::Platform::MSWIN, + mswin_41: 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, + mswin64_40: Gem::Platform::MSWIN64, + mswin64_41: Gem::Platform::MSWIN64, + mingw: Gem::Platform::UNIVERSAL_MINGW, + mingw_18: Gem::Platform::UNIVERSAL_MINGW, + mingw_19: Gem::Platform::UNIVERSAL_MINGW, + mingw_20: Gem::Platform::UNIVERSAL_MINGW, + mingw_21: Gem::Platform::UNIVERSAL_MINGW, + mingw_22: Gem::Platform::UNIVERSAL_MINGW, + mingw_23: Gem::Platform::UNIVERSAL_MINGW, + mingw_24: Gem::Platform::UNIVERSAL_MINGW, + mingw_25: Gem::Platform::UNIVERSAL_MINGW, + mingw_26: Gem::Platform::UNIVERSAL_MINGW, + mingw_27: Gem::Platform::UNIVERSAL_MINGW, + mingw_30: Gem::Platform::UNIVERSAL_MINGW, + mingw_31: Gem::Platform::UNIVERSAL_MINGW, + mingw_32: Gem::Platform::UNIVERSAL_MINGW, + mingw_33: Gem::Platform::UNIVERSAL_MINGW, + mingw_34: Gem::Platform::UNIVERSAL_MINGW, + mingw_40: Gem::Platform::UNIVERSAL_MINGW, + mingw_41: Gem::Platform::UNIVERSAL_MINGW, + x64_mingw: Gem::Platform::UNIVERSAL_MINGW, + x64_mingw_20: Gem::Platform::UNIVERSAL_MINGW, + x64_mingw_21: Gem::Platform::UNIVERSAL_MINGW, + x64_mingw_22: Gem::Platform::UNIVERSAL_MINGW, + x64_mingw_23: Gem::Platform::UNIVERSAL_MINGW, + x64_mingw_24: Gem::Platform::UNIVERSAL_MINGW, + x64_mingw_25: Gem::Platform::UNIVERSAL_MINGW, + x64_mingw_26: Gem::Platform::UNIVERSAL_MINGW, + x64_mingw_27: Gem::Platform::UNIVERSAL_MINGW, + x64_mingw_30: Gem::Platform::UNIVERSAL_MINGW, + x64_mingw_31: Gem::Platform::UNIVERSAL_MINGW, + x64_mingw_32: Gem::Platform::UNIVERSAL_MINGW, + x64_mingw_33: Gem::Platform::UNIVERSAL_MINGW, + x64_mingw_34: Gem::Platform::UNIVERSAL_MINGW, + x64_mingw_40: Gem::Platform::UNIVERSAL_MINGW, + x64_mingw_41: Gem::Platform::UNIVERSAL_MINGW } + end + # rubocop:enable Naming/VariableNumber + + it "includes all platforms" do + expect(subject).to eq(platforms.merge(deprecated)) + end + end + + describe "Deprecated platform" do + it "outputs an error and aborts when calling maglev?" do + expect { Bundler.current_ruby.maglev? }.to raise_error(Bundler::RemovedError, /`CurrentRuby#maglev\?` was removed with no replacement./) + end + + it "outputs an error and aborts when calling maglev_31?" do + expect { Bundler.current_ruby.maglev_31? }.to raise_error(Bundler::RemovedError, /`CurrentRuby#maglev_31\?` was removed with no replacement./) + end + end +end diff --git a/spec/bundler/bundler/definition_spec.rb b/spec/bundler/bundler/definition_spec.rb index 68b170513c..8c4a5a0331 100644 --- a/spec/bundler/bundler/definition_spec.rb +++ b/spec/bundler/bundler/definition_spec.rb @@ -3,6 +3,24 @@ require "bundler/definition" RSpec.describe Bundler::Definition do + describe "#overrides" do + before do + allow(Bundler::SharedHelpers).to receive(:find_gemfile) { bundled_app_gemfile } + end + + subject { Bundler::Definition.new(bundled_app_lock, [], Bundler::SourceList.new, {}) } + + it "defaults to an empty array" do + expect(subject.overrides).to eq([]) + end + + it "is writable" do + override = Bundler::Override.new("rails", :version, ">= 8.0") + subject.overrides = [override] + expect(subject.overrides).to eq([override]) + end + end + describe "#lock" do before do allow(Bundler::SharedHelpers).to receive(:find_gemfile) { bundled_app_gemfile } @@ -33,7 +51,7 @@ RSpec.describe Bundler::Definition do before { Bundler::Definition.no_lock = true } after { Bundler::Definition.no_lock = false } - it "does not create a lock file" do + it "does not create a lockfile" do subject.lock expect(bundled_app_lock).not_to be_file end @@ -80,7 +98,7 @@ RSpec.describe Bundler::Definition do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -137,7 +155,7 @@ RSpec.describe Bundler::Definition do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G expect(lockfile).to eq(expected_lockfile) @@ -175,7 +193,7 @@ RSpec.describe Bundler::Definition do only_java #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -205,7 +223,7 @@ RSpec.describe Bundler::Definition do foo #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end end @@ -289,6 +307,57 @@ RSpec.describe Bundler::Definition do end end + describe "#precompute_source_requirements_for_indirect_dependencies?" do + before do + allow(Bundler::SharedHelpers).to receive(:find_gemfile) { Pathname.new("Gemfile") } + end + + let(:sources) { Bundler::SourceList.new } + subject { Bundler::Definition.new(nil, [], sources, []) } + + before do + allow(sources).to receive(:non_global_rubygems_sources).and_return(non_global_rubygems_sources) + end + + context "when all the scoped sources implement a dependency API" do + let(:non_global_rubygems_sources) do + [ + double("non-global-source-0", "dependency_api_available?":true, to_s:"a"), + double("non-global-source-1", "dependency_api_available?":true, to_s:"b"), + ] + end + + it "returns true without warning" do + expect(subject).not_to receive(:non_dependency_api_warning) + + expect(subject.send(:precompute_source_requirements_for_indirect_dependencies?)).to be_truthy + end + end + + context "when some scoped sources do not implement a dependency API" do + let(:non_global_rubygems_sources) do + [ + double("non-global-source-0", "dependency_api_available?":true, to_s:"a"), + double("non-global-source-1", "dependency_api_available?":false, to_s:"b"), + double("non-global-source-2", "dependency_api_available?":false, to_s:"c"), + ] + end + + it "returns false and warns about the non-API sources" do + expect(Bundler.ui).to receive(:warn).with(<<-W.strip) +Your Gemfile contains scoped sources that don't implement a dependency API, namely: + + * b + * c + +Using the above gem servers may result in installing unexpected gems. To resolve this warning, make sure you use gem servers that implement dependency APIs, such as gemstash or geminabox gem servers. + W + + expect(subject.send(:precompute_source_requirements_for_indirect_dependencies?)).to be_falsy + end + end + end + def mock_source_list Class.new do def all_sources @@ -299,10 +368,6 @@ RSpec.describe Bundler::Definition do [] end - def rubygems_remotes - [] - end - def replace_sources!(arg) nil end diff --git a/spec/bundler/bundler/dependency_spec.rb b/spec/bundler/bundler/dependency_spec.rb index 1185c4e5dd..f930459571 100644 --- a/spec/bundler/bundler/dependency_spec.rb +++ b/spec/bundler/bundler/dependency_spec.rb @@ -35,140 +35,15 @@ RSpec.describe Bundler::Dependency do end end - describe "PLATFORM_MAP" do - subject { described_class::PLATFORM_MAP } + it "is on the current platform" do + engine = Gem.win_platform? ? "windows" : RUBY_ENGINE - # 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, - ruby_35: 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, - mri_35: 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, - windows_35: 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, - mswin_35: 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, - mswin64_35: 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, - mingw_35: 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, - x64_mingw_35: Gem::Platform::X64_MINGW } - end - # rubocop:enable Naming/VariableNumber + dep = described_class.new( + "test_gem", + "1.0.0", + { "platforms" => "#{engine}_#{RbConfig::CONFIG["MAJOR"]}#{RbConfig::CONFIG["MINOR"]}" }, + ) - it "includes all platforms" do - expect(subject).to eq(platforms.merge(deprecated)) - end + expect(dep.current_platform?).to be_truthy end end diff --git a/spec/bundler/bundler/dsl_spec.rb b/spec/bundler/bundler/dsl_spec.rb index c4f9d0dbb5..b6e67a312c 100644 --- a/spec/bundler/bundler/dsl_spec.rb +++ b/spec/bundler/bundler/dsl_spec.rb @@ -103,7 +103,7 @@ RSpec.describe Bundler::Dsl do ) end - context "default hosts", bundler: "< 3" do + context "default hosts" do it "converts :github to URI using https" do subject.gem("sparks", github: "indirect/sparks") github_uri = "https://github.com/indirect/sparks.git" @@ -162,7 +162,7 @@ RSpec.describe Bundler::Dsl do describe "#method_missing" do it "raises an error for unknown DSL methods" do - expect(Bundler).to receive(:read_file).with(source_root.join("Gemfile").to_s). + expect(Bundler).to receive(:read_file).with(git_root.join("Gemfile").to_s). and_return("unknown") error_msg = "There was an error parsing `Gemfile`: Undefined local variable or method `unknown' for Gemfile. Bundler cannot continue." @@ -173,13 +173,13 @@ RSpec.describe Bundler::Dsl do describe "#eval_gemfile" do it "handles syntax errors with a useful message" do - expect(Bundler).to receive(:read_file).with(source_root.join("Gemfile").to_s).and_return("}") + expect(Bundler).to receive(:read_file).with(git_root.join("Gemfile").to_s).and_return("}") expect { subject.eval_gemfile("Gemfile") }. to raise_error(Bundler::GemfileError, /There was an error parsing `Gemfile`: (syntax error, unexpected tSTRING_DEND|(compile error - )?syntax error, unexpected '\}'|.+?unexpected '}', ignoring it\n). Bundler cannot continue./m) end it "distinguishes syntax errors from evaluation errors" do - expect(Bundler).to receive(:read_file).with(source_root.join("Gemfile").to_s).and_return( + expect(Bundler).to receive(:read_file).with(git_root.join("Gemfile").to_s).and_return( "ruby '2.1.5', :engine => 'ruby', :engine_version => '1.2.4'" ) expect { subject.eval_gemfile("Gemfile") }. @@ -187,13 +187,13 @@ RSpec.describe Bundler::Dsl do end it "populates __dir__ and __FILE__ correctly" do - abs_path = source_root.join("../fragment.rb").to_s + abs_path = git_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_dir)).to eq(git_root.dirname.to_s) expect(subject.instance_variable_get(:@fragment_file)).to eq(abs_path) end end @@ -201,8 +201,8 @@ RSpec.describe Bundler::Dsl do describe "#gem" do # 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| + :ruby_30, :ruby_31, :ruby_32, :ruby_33, :ruby_34, :ruby_40, :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, :mri_34, :mri_40, :jruby, :rbx, :truffleruby].each do |platform| it "allows #{platform} as a valid platform" do subject.gem("foo", platform: platform) end @@ -211,7 +211,9 @@ RSpec.describe Bundler::Dsl do it "allows platforms matching the running Ruby version" do platform = "ruby_#{RbConfig::CONFIG["MAJOR"]}#{RbConfig::CONFIG["MINOR"]}" - subject.gem("foo", platform: platform) + + expect { subject.gem("foo", platform: platform) }.not_to raise_error + expect(Bundler.current_ruby.respond_to?("#{platform}?")).to be_truthy end it "rejects invalid platforms" do @@ -219,6 +221,11 @@ RSpec.describe Bundler::Dsl do to raise_error(Bundler::GemfileError, /is not a valid platform/) end + it "warn for legacy windows platforms" do + expect(Bundler::SharedHelpers).to receive(:feature_deprecated!).with(/\APlatform :mswin, :x64_mingw will be removed in the future./) + subject.gem("foo", platforms: [:mswin, :jruby, :x64_mingw]) + end + it "rejects empty gem name" do expect { subject.gem("") }. to raise_error(Bundler::GemfileError, /an empty gem name is not valid/) @@ -283,6 +290,15 @@ RSpec.describe Bundler::Dsl do end end + describe "#platforms" do + it "warn for legacy windows platforms" do + expect(Bundler::SharedHelpers).to receive(:feature_deprecated!).with(/\APlatform :mswin64, :mingw will be removed in the future./) + subject.platforms(:mswin64, :jruby, :mingw) do + subject.gem("foo") + end + end + end + context "can bundle groups of gems with" do # git "https://github.com/rails/rails.git" do # gem "railties" @@ -350,4 +366,193 @@ RSpec.describe Bundler::Dsl do end end end + + describe "#source with cooldown" do + before do + allow(@rubygems).to receive(:add_remote) + end + + it "accepts a non-negative integer" do + expect do + subject.source("https://rubygems.org", cooldown: 7) + end.not_to raise_error + end + + it "accepts 0 as an explicit disable" do + expect do + subject.source("https://rubygems.org", cooldown: 0) + end.not_to raise_error + end + + it "rejects a string" do + expect do + subject.source("https://rubygems.org", cooldown: "7") + end.to raise_error(Bundler::InvalidOption, /non-negative integer/) + end + + it "rejects a float" do + expect do + subject.source("https://rubygems.org", cooldown: 7.5) + end.to raise_error(Bundler::InvalidOption, /non-negative integer/) + end + + it "rejects a negative integer" do + expect do + subject.source("https://rubygems.org", cooldown: -7) + end.to raise_error(Bundler::InvalidOption, /non-negative integer/) + end + + it "rejects an array" do + expect do + subject.source("https://rubygems.org", cooldown: [7]) + end.to raise_error(Bundler::InvalidOption, /non-negative integer/) + end + end + + describe "#override" do + it "stores an Override for a gem with a version: operation" do + subject.override("rails", version: ">= 8.0") + + expect(subject.overrides.size).to eq(1) + override = subject.overrides.first + expect(override.target).to eq("rails") + expect(override.field).to eq(:version) + expect(override.operation).to eq(">= 8.0") + end + + it "accepts :ignore_upper as the operation" do + subject.override("nokogiri", version: :ignore_upper) + expect(subject.overrides.first.operation).to eq(:ignore_upper) + end + + it "accepts nil as the operation" do + subject.override("legacy", version: nil) + expect(subject.overrides.first.operation).to be_nil + end + + it "appends to overrides across multiple statements" do + subject.override("rails", version: ">= 8.0") + subject.override("nokogiri", version: :ignore_upper) + expect(subject.overrides.map(&:target)).to eq(["rails", "nokogiri"]) + end + + it "is empty by default" do + expect(subject.overrides).to eq([]) + end + + it "raises ArgumentError when target is :all and version: is given" do + expect do + subject.override(:all, version: ">= 8.0") + end.to raise_error(ArgumentError, /`override :all, version:` is not allowed/) + end + + it "rejects :all + version: even when other fields are also given" do + expect do + subject.override(:all, required_ruby_version: :ignore_upper, version: ">= 8.0") + end.to raise_error(ArgumentError, /`override :all, version:` is not allowed/) + end + + it "does not record any override when :all + version: is rejected" do + expect do + subject.override(:all, version: ">= 8.0") + end.to raise_error(ArgumentError) + expect(subject.overrides).to eq([]) + end + + it "raises ArgumentError when target is neither :all nor a string" do + expect do + subject.override(:rails, version: ">= 8.0") + end.to raise_error(ArgumentError, /target must be :all or a gem name string/) + end + + it "raises ArgumentError for an unsupported field" do + expect do + subject.override("rails", as: "y") + end.to raise_error(ArgumentError, /unsupported override field `as:`/) + end + + it "stores an Override for a gem with a required_ruby_version: operation" do + subject.override("rails", required_ruby_version: :ignore_upper) + override = subject.overrides.first + expect(override.target).to eq("rails") + expect(override.field).to eq(:required_ruby_version) + expect(override.operation).to eq(:ignore_upper) + end + + it "stores an Override for a gem with a required_rubygems_version: operation" do + subject.override("rails", required_rubygems_version: nil) + override = subject.overrides.first + expect(override.field).to eq(:required_rubygems_version) + expect(override.operation).to be_nil + end + + it "stores an Override targeting :all with a metadata field" do + subject.override(:all, required_ruby_version: :ignore_upper) + override = subject.overrides.first + expect(override.target).to eq(:all) + expect(override.field).to eq(:required_ruby_version) + expect(override.operation).to eq(:ignore_upper) + end + + it "stores an Override targeting :all with required_rubygems_version" do + subject.override(:all, required_rubygems_version: nil) + override = subject.overrides.first + expect(override.target).to eq(:all) + expect(override.field).to eq(:required_rubygems_version) + end + + it "raises ArgumentError for a non-string, non-symbol, non-nil operation" do + expect do + subject.override("rails", version: 42) + end.to raise_error(ArgumentError, /override operation must be a String, Symbol, or nil/) + end + + it "raises ArgumentError for an unsupported symbol operation" do + expect do + subject.override("rails", version: :explode) + end.to raise_error(ArgumentError, /unsupported override operation/) + end + + it "raises ArgumentError for an unparsable version string" do + expect do + subject.override("rails", version: "not a version") + end.to raise_error(ArgumentError, /invalid override version requirement/) + end + + it "does not record an override when the version string is invalid" do + expect do + subject.override("rails", version: "not a version") + end.to raise_error(ArgumentError) + expect(subject.overrides).to eq([]) + end + + it "rejects atomically when one field in a multi-field call is invalid" do + expect do + subject.override("rails", version: ">= 8.0", as: "y") + end.to raise_error(ArgumentError, /unsupported override field/) + expect(subject.overrides).to eq([]) + end + + it "raises ArgumentError when the same target and field are overridden twice" do + subject.override("rails", version: ">= 8.0") + expect do + subject.override("rails", version: :ignore_upper) + end.to raise_error(ArgumentError, /duplicate override for "rails" `version:`/) + end + + it "keeps the original override when a duplicate is rejected" do + subject.override("rails", version: ">= 8.0") + expect do + subject.override("rails", version: :ignore_upper) + end.to raise_error(ArgumentError) + expect(subject.overrides.size).to eq(1) + expect(subject.overrides.first.operation).to eq(">= 8.0") + end + + it "allows different targets with the same field" do + subject.override("rails", version: ">= 8.0") + subject.override("nokogiri", version: :ignore_upper) + expect(subject.overrides.size).to eq(2) + end + end end diff --git a/spec/bundler/bundler/endpoint_specification_spec.rb b/spec/bundler/bundler/endpoint_specification_spec.rb index 6518f125ba..4fbd59d48f 100644 --- a/spec/bundler/bundler/endpoint_specification_spec.rb +++ b/spec/bundler/bundler/endpoint_specification_spec.rb @@ -46,6 +46,46 @@ RSpec.describe Bundler::EndpointSpecification do ) end end + + context "when the metadata has created_at" do + let(:metadata) { { "created_at" => ["2026-05-12T10:00:00Z"] } } + + it "parses created_at as a Time" do + expect(subject.created_at).to eq(Time.utc(2026, 5, 12, 10, 0, 0)) + end + end + + context "when the metadata has a string created_at (older rubygems shape)" do + let(:metadata) { { "created_at" => "2026-05-12T10:00:00Z" } } + + it "still parses created_at" do + expect(subject.created_at).to eq(Time.utc(2026, 5, 12, 10, 0, 0)) + end + end + + context "when created_at is truncated (older rubygems splits on colons)" do + let(:metadata) { { "created_at" => "2026-05-12T10" } } + + it "leaves created_at as nil instead of raising" do + expect(subject.created_at).to be_nil + end + end + + context "when the metadata has no created_at" do + let(:metadata) { { "checksum" => ["abc"] } } + let(:spec_fetcher) { double(:spec_fetcher, uri: "https://rubygems.org") } + + it "leaves created_at as nil" do + allow(Bundler::Checksum).to receive(:from_api).and_return(nil) + expect(subject.created_at).to be_nil + end + end + + context "when the metadata is nil" do + it "leaves created_at as nil" do + expect(subject.created_at).to be_nil + end + end end describe "#required_ruby_version" do diff --git a/spec/bundler/bundler/env_spec.rb b/spec/bundler/bundler/env_spec.rb index e0ab0a45e3..2b7dbde217 100644 --- a/spec/bundler/bundler/env_spec.rb +++ b/spec/bundler/bundler/env_spec.rb @@ -217,20 +217,21 @@ 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_local).with("--version"). - and_return("git version 1.2.3 (Apple Git-BS)") + status = double("success?" => true) + expect(Open3).to receive(:capture3).with("git", "--version"). + and_return(["git version 1.2.3 (Apple Git-BS)", "", status]) expect(Bundler::Source::Git::GitProxy).to receive(:new).and_return(git_proxy_stub) - expect(described_class.report).to include("Git 1.2.3 (Apple Git-BS)") + expect(described_class.report).to include("Git 1.2.3 (Apple Git-BS)") end end - end - - describe ".version_of" do - let(:parsed_version) { described_class.send(:version_of, "ruby") } - it "strips version of new line characters" do - expect(parsed_version).to_not end_with("\n") + it "no longer reports the Tools section or external tool versions" do + report = described_class.report + expect(report).not_to include("Tools") + ["rbenv", "RVM", "chruby"].each do |tool| + expect(report).not_to include(tool) + end end end end diff --git a/spec/bundler/bundler/errors_spec.rb b/spec/bundler/bundler/errors_spec.rb new file mode 100644 index 0000000000..b62d85d32b --- /dev/null +++ b/spec/bundler/bundler/errors_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +RSpec.describe Bundler::IncorrectLockfileDependencies do + describe "#message" do + let(:spec) do + double("LazySpecification", full_name: "rubocop-1.82.0") + end + + context "without dependency details" do + subject { described_class.new(spec) } + + it "provides a basic error message" do + expect(subject.message).to include("Bundler found incorrect dependencies in the lockfile for rubocop-1.82.0") + expect(subject.message).to include("Please run `bundle install` to regenerate the lockfile.") + end + end + + context "with dependency details" do + let(:actual_dependencies) do + [ + Gem::Dependency.new("json", [">= 2.3", "< 4.0"]), + Gem::Dependency.new("parallel", ["~> 1.10"]), + Gem::Dependency.new("parser", [">= 3.3.0.2"]), + ] + end + + let(:lockfile_dependencies) do + [ + Gem::Dependency.new("json", [">= 2.3", "< 3.0"]), + Gem::Dependency.new("parallel", ["~> 1.10"]), + Gem::Dependency.new("parser", [">= 3.2.0.0"]), + ] + end + + subject { described_class.new(spec, actual_dependencies, lockfile_dependencies) } + + it "shows only mismatched dependencies" do + message = subject.message + + expect(message).to include("json: gemspec specifies") + expect(message).to include("parser: gemspec specifies") + expect(message).not_to include("parallel") + end + end + + context "when gemspec has dependencies but lockfile has none" do + let(:actual_dependencies) do + [ + Gem::Dependency.new("myrack-test", ["~> 1.0"]), + ] + end + + let(:lockfile_dependencies) { [] } + + subject { described_class.new(spec, actual_dependencies, lockfile_dependencies) } + + it "shows the dependency as not in lockfile" do + message = subject.message + + expect(message).to include("myrack-test: gemspec specifies ~> 1.0, not in lockfile") + end + end + + context "when gemspec has no dependencies but lockfile has some" do + let(:actual_dependencies) { [] } + + let(:lockfile_dependencies) do + [ + Gem::Dependency.new("unexpected", ["~> 1.0"]), + ] + end + + subject { described_class.new(spec, actual_dependencies, lockfile_dependencies) } + + it "shows the dependency as not in gemspec" do + message = subject.message + + expect(message).to include("unexpected: not in gemspec, lockfile has ~> 1.0") + end + end + end + + describe "#status_code" do + let(:spec) { double("LazySpecification", full_name: "test-1.0.0") } + subject { described_class.new(spec) } + + it "returns 41" do + expect(subject.status_code).to eq(41) + end + end +end diff --git a/spec/bundler/bundler/fetcher/dependency_spec.rb b/spec/bundler/bundler/fetcher/dependency_spec.rb index c420b7c07f..501bc269a5 100644 --- a/spec/bundler/bundler/fetcher/dependency_spec.rb +++ b/spec/bundler/bundler/fetcher/dependency_spec.rb @@ -212,7 +212,7 @@ RSpec.describe Bundler::Fetcher::Dependency do let(:dep_api_uri) { double(:dep_api_uri) } let(:unmarshalled_gems) { double(:unmarshalled_gems) } let(:fetch_response) { double(:fetch_response, body: double(:body)) } - let(:rubygems_limit) { 50 } + let(:rubygems_limit) { 100 } before { allow(subject).to receive(:dependency_api_uri).with(gem_names).and_return(dep_api_uri) } @@ -222,6 +222,18 @@ RSpec.describe Bundler::Fetcher::Dependency do 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 + + it "should fetch as many dependencies as specified" do + allow(subject).to receive(:dependency_api_uri).with([%w[foo bar]]).and_return(dep_api_uri) + allow(subject).to receive(:dependency_api_uri).with([%w[bundler rubocop]]).and_return(dep_api_uri) + + expect(downloader).to receive(:fetch).twice.with(dep_api_uri).and_return(fetch_response) + expect(Bundler).to receive(:safe_load_marshal).twice.with(fetch_response.body).and_return([unmarshalled_gems]) + + Bundler.settings.temporary(api_request_size: 1) do + expect(subject.unmarshalled_dep_gems(gem_names)).to eq([unmarshalled_gems, unmarshalled_gems]) + end + end end describe "#get_formatted_specs_and_deps" do diff --git a/spec/bundler/bundler/fetcher/downloader_spec.rb b/spec/bundler/bundler/fetcher/downloader_spec.rb index 13f8cc74aa..edf426328a 100644 --- a/spec/bundler/bundler/fetcher/downloader_spec.rb +++ b/spec/bundler/bundler/fetcher/downloader_spec.rb @@ -83,12 +83,12 @@ RSpec.describe Bundler::Fetcher::Downloader do /Authentication is required for www.uri-to-fetch.com/) end - it "should raise a Bundler::Fetcher::AuthenticationRequiredError with advices" do + it "should raise a Bundler::Fetcher::AuthenticationRequiredError with advice" do expect { subject.fetch(uri, options, counter) }.to raise_error(Bundler::Fetcher::AuthenticationRequiredError, /`bundle config set --global www\.uri-to-fetch\.com username:password`.*`BUNDLE_WWW__URI___TO___FETCH__COM`/m) end - context "when the there are credentials provided in the request" do + context "when there are credentials provided in the request" do 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 @@ -116,7 +116,7 @@ RSpec.describe Bundler::Fetcher::Downloader do 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 + context "when there are credentials provided in the request" do 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 @@ -126,6 +126,38 @@ RSpec.describe Bundler::Fetcher::Downloader do end end + context "when the request response is a Gem::Net::HTTPRequestedRangeNotSatisfiable" do + let(:http_response) { Gem::Net::HTTPRequestedRangeNotSatisfiable.new("1.1", 416, "Range Not Satisfiable") } + let(:success_response) { Gem::Net::HTTPSuccess.new("1.1", 200, "Success") } + let(:options) { { "Range" => "bytes=1000-", "If-None-Match" => "some-etag" } } + + before do + # First request returns 416, retry request returns success + allow(subject).to receive(:request).with(uri, options).and_return(http_response) + allow(subject).to receive(:request).with(uri, { "If-None-Match" => "some-etag" }).and_return(success_response) + end + + # The 416 handler removes the Range header and retries without incrementing the counter. + # Importantly, it does NOT add Accept-Encoding header, which would break Ruby's + # automatic gzip decompression (see issue #9271 for details on that bug). + it "should retry the request without the Range header" do + expect(subject).to receive(:request).with(uri, options).ordered + expect(subject).to receive(:request).with(uri, hash_excluding("Range", "Accept-Encoding")).ordered + subject.fetch(uri, options, counter) + end + + it "should preserve other headers on retry" do + expect(subject).to receive(:request).with(uri, options).ordered + expect(subject).to receive(:request).with(uri, hash_including("If-None-Match" => "some-etag")).ordered + subject.fetch(uri, options, counter) + end + + it "should return the successful response" do + result = subject.fetch(uri, options, counter) + expect(result).to eq(success_response) + end + end + context "when the request response is some other type" do let(:http_response) { Gem::Net::HTTPBadGateway.new("1.1", 500, "Fatal Error") } @@ -201,39 +233,29 @@ RSpec.describe Bundler::Fetcher::Downloader do end end - context "when the request response causes an error included in HTTP_ERRORS" do - let(:message) { nil } - let(:error) { RuntimeError.new(message) } + context "when the request response causes an HTTP error" do + let(:message) { "error about network" } + let(:error) { error_class.new(message) } before do - stub_const("Bundler::Fetcher::HTTP_ERRORS", [RuntimeError]) allow(connection).to receive(:request).with(uri, net_http_get) { raise error } end - it "should trace log the error" do - allow(Bundler).to receive_message_chain(:ui, :debug) - expect(Bundler).to receive_message_chain(:ui, :trace).with(error) - expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError) - end - - context "when error message is about the host being down" do - let(:message) { "host down: http://www.uri-to-fetch.com" } + context "that it's retryable" do + let(:error_class) { Gem::Timeout::Error } - it "should raise a Bundler::Fetcher::NetworkDownError" do - expect { subject.request(uri, options) }.to raise_error(Bundler::Fetcher::NetworkDownError, - /Could not reach host www.uri-to-fetch.com/) + it "should trace log the error" do + allow(Bundler).to receive_message_chain(:ui, :debug) + expect(Bundler).to receive_message_chain(:ui, :trace).with(error) + expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError) end - end - - context "when error message is not about host down" do - let(:message) { "other error about network" } it "should raise a Bundler::HTTPError" do expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError, - "Network error while fetching http://www.uri-to-fetch.com/api/v2/endpoint (other error about network)") + "Network error while fetching http://www.uri-to-fetch.com/api/v2/endpoint (error about network)") end - context "when the there are credentials provided in the request" do + context "when there are credentials provided in the request" do 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") @@ -241,17 +263,38 @@ RSpec.describe Bundler::Fetcher::Downloader do it "should raise a Bundler::HTTPError that doesn't contain the password" do expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError, - "Network error while fetching http://username@www.uri-to-fetch.com/api/v2/endpoint (other error about network)") + "Network error while fetching http://username@www.uri-to-fetch.com/api/v2/endpoint (error about network)") end end end - context "when error message is about no route to host" do + context "when error is about the host being down" do + let(:error_class) { Gem::Net::HTTP::Persistent::Error } + let(:message) { "host down: http://www.uri-to-fetch.com" } + + it "should raise a Bundler::Fetcher::NetworkDownError" do + expect { subject.request(uri, options) }.to raise_error(Bundler::Fetcher::NetworkDownError, + /Could not reach host www.uri-to-fetch.com/) + end + end + + context "when error is about connection refused" do + let(:error_class) { Gem::Net::HTTP::Persistent::Error } + let(:message) { "connection refused down: http://www.uri-to-fetch.com" } + + it "should raise a Bundler::Fetcher::NetworkDownError" do + expect { subject.request(uri, options) }.to raise_error(Bundler::Fetcher::NetworkDownError, + /Could not reach host www.uri-to-fetch.com/) + end + end + + context "when error is about no route to host" do + let(:error_class) { SocketError } let(:message) { "Failed to open TCP connection to www.uri-to-fetch.com:443 " } - it "should raise a Bundler::Fetcher::HTTPError" do - expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError, - "Network error while fetching http://www.uri-to-fetch.com/api/v2/endpoint (#{message})") + it "should raise a Bundler::Fetcher::NetworkDownError" do + expect { subject.request(uri, options) }.to raise_error(Bundler::Fetcher::NetworkDownError, + /Could not reach host www.uri-to-fetch.com/) end end end diff --git a/spec/bundler/bundler/fetcher/gem_remote_fetcher_spec.rb b/spec/bundler/bundler/fetcher/gem_remote_fetcher_spec.rb new file mode 100644 index 0000000000..df1a58d843 --- /dev/null +++ b/spec/bundler/bundler/fetcher/gem_remote_fetcher_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require "rubygems/remote_fetcher" +require "bundler/fetcher/gem_remote_fetcher" +require_relative "../../support/artifice/helpers/artifice" +require "bundler/vendored_persistent.rb" + +RSpec.describe Bundler::Fetcher::GemRemoteFetcher do + describe "Parallel download" do + it "download using multiple connections from the pool" do + unless Bundler.rubygems.provides?(">= 4.0.0.dev") + skip "This example can only run when RubyGems supports multiple http connection pool" + end + + require_relative "../../support/artifice/helpers/endpoint" + concurrent_ruby_path = Dir[scoped_base_system_gem_path.join("gems/concurrent-ruby-*/lib/concurrent-ruby")].first + $LOAD_PATH.unshift(concurrent_ruby_path) + require "concurrent-ruby" + + require_rack_test + responses = [] + + latch1 = Concurrent::CountDownLatch.new + latch2 = Concurrent::CountDownLatch.new + previous_client = Gem::Request::ConnectionPools.client + dummy_endpoint = Class.new(Endpoint) do + get "/foo" do + latch2.count_down + latch1.wait + + responses << "foo" + end + + get "/bar" do + responses << "bar" + + latch1.count_down + end + end + + Artifice.activate_with(dummy_endpoint) + Gem::Request::ConnectionPools.client = Gem::Net::HTTP + + first_request = Thread.new do + subject.fetch_path("https://example.org/foo") + end + second_request = Thread.new do + latch2.wait + subject.fetch_path("https://example.org/bar") + end + + [first_request, second_request].each(&:join) + + expect(responses).to eq(["bar", "foo"]) + ensure + Artifice.deactivate + Gem::Request::ConnectionPools.client = previous_client + end + end +end diff --git a/spec/bundler/bundler/friendly_errors_spec.rb b/spec/bundler/bundler/friendly_errors_spec.rb index 255019f40a..426e3c856d 100644 --- a/spec/bundler/bundler/friendly_errors_spec.rb +++ b/spec/bundler/bundler/friendly_errors_spec.rb @@ -2,7 +2,8 @@ require "bundler" require "bundler/friendly_errors" -require "cgi" +require "cgi/escape" +require "cgi/util" unless defined?(CGI::EscapeExt) RSpec.describe Bundler, "friendly errors" do context "with invalid YAML in .gemrc" do @@ -130,17 +131,13 @@ RSpec.describe Bundler, "friendly errors" do # Does nothing end - context "Java::JavaLang::OutOfMemoryError" do - module Java - module JavaLang - class OutOfMemoryError < StandardError; end - end - end - + context "Java::JavaLang::OutOfMemoryError", :jruby_only do it "Bundler.ui receive error" do - error = Java::JavaLang::OutOfMemoryError.new - expect(Bundler.ui).to receive(:error).with(/JVM has run out of memory/) - Bundler::FriendlyErrors.log_error(error) + install_gemfile <<-G, raise_on_error: false, env: { "JRUBY_OPTS" => "-J-Xmx32M" }, artifice: nil + source "https://gem.repo1" + G + + expect(err).to include("JVM has run out of memory") end end @@ -200,7 +197,7 @@ RSpec.describe Bundler, "friendly errors" do it "generates a search URL for the exception message" do exception = Exception.new("Exception message") - expect(Bundler::FriendlyErrors.issues_url(exception)).to eq("https://github.com/rubygems/rubygems/search?q=Exception+message&type=Issues") + expect(Bundler::FriendlyErrors.issues_url(exception)).to eq("https://github.com/ruby/rubygems/search?q=Exception+message&type=Issues") end it "generates a search URL for only the first line of a multi-line exception message" do @@ -209,7 +206,7 @@ First line of the exception message Second line of the exception message END - expect(Bundler::FriendlyErrors.issues_url(exception)).to eq("https://github.com/rubygems/rubygems/search?q=First+line+of+the+exception+message&type=Issues") + expect(Bundler::FriendlyErrors.issues_url(exception)).to eq("https://github.com/ruby/rubygems/search?q=First+line+of+the+exception+message&type=Issues") end it "generates the url without colons" do @@ -218,7 +215,7 @@ Exception ::: with ::: colons ::: END issues_url = Bundler::FriendlyErrors.issues_url(exception) expect(issues_url).not_to include("%3A") - expect(issues_url).to eq("https://github.com/rubygems/rubygems/search?q=#{CGI.escape("Exception with colons ")}&type=Issues") + expect(issues_url).to eq("https://github.com/ruby/rubygems/search?q=#{CGI.escape("Exception with colons ")}&type=Issues") end it "removes information after - for Errono::EACCES" do @@ -228,7 +225,7 @@ END allow(exception).to receive(:is_a?).with(Errno).and_return(true) issues_url = Bundler::FriendlyErrors.issues_url(exception) expect(issues_url).not_to include("/Users/foo/bar") - expect(issues_url).to eq("https://github.com/rubygems/rubygems/search?q=#{CGI.escape("Errno EACCES Permission denied @ dir_s_mkdir ")}&type=Issues") + expect(issues_url).to eq("https://github.com/ruby/rubygems/search?q=#{CGI.escape("Errno EACCES Permission denied @ dir_s_mkdir ")}&type=Issues") end end end diff --git a/spec/bundler/bundler/gem_helper_spec.rb b/spec/bundler/bundler/gem_helper_spec.rb index 94f66537d3..b4ae2abdc5 100644 --- a/spec/bundler/bundler/gem_helper_spec.rb +++ b/spec/bundler/bundler/gem_helper_spec.rb @@ -9,8 +9,12 @@ RSpec.describe Bundler::GemHelper do let(:app_gemspec_path) { app_path.join("#{app_name}.gemspec") } 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" + bundle_config_global "gem.mit false" + bundle_config_global "gem.test false" + bundle_config_global "gem.coc false" + bundle_config_global "gem.linter false" + bundle_config_global "gem.ci false" + bundle_config_global "gem.changelog false" git("config --global init.defaultBranch main") bundle "gem #{app_name}" prepare_gemspec(app_gemspec_path) @@ -222,7 +226,7 @@ RSpec.describe Bundler::GemHelper do mock_confirm_message "#{app_name} (#{app_version}) installed." subject.install_gem(nil, :local) expect(app_gem_path).to exist - gem_command :list + installed_gems_list expect(out).to include("#{app_name} (#{app_version})") end end @@ -386,6 +390,7 @@ RSpec.describe Bundler::GemHelper do credentials = double("credentials", "file?" => true) allow(Bundler.user_home).to receive(:join). with(".gem/credentials").and_return(credentials) + allow(Bundler.user_home).to receive(:join).and_call_original end describe "success messaging" do diff --git a/spec/bundler/bundler/gem_version_promoter_spec.rb b/spec/bundler/bundler/gem_version_promoter_spec.rb index 917daba95d..0e1b7c9cc8 100644 --- a/spec/bundler/bundler/gem_version_promoter_spec.rb +++ b/spec/bundler/bundler/gem_version_promoter_spec.rb @@ -20,13 +20,13 @@ RSpec.describe Bundler::GemVersionPromoter do end end - def build_package(name, version, locked = []) - Bundler::Resolver::Package.new(name, [], locked_specs: Bundler::SpecSet.new(build_spec(name, version)), unlock: locked) + def build_package(name, version, unlock) + Bundler::Resolver::Package.new(name, [], locked_specs: Bundler::SpecSet.new(build_spec(name, version)), unlock: unlock) end - def sorted_versions(candidates:, current:, name: "foo", locked: []) + def sorted_versions(candidates:, current:, unlock: true) gvp.sort_versions( - build_package(name, current, locked), + build_package("foo", current, unlock), build_candidates(candidates) ).flatten.map(&:version).map(&:to_s) end @@ -127,7 +127,7 @@ RSpec.describe Bundler::GemVersionPromoter 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"]) + 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", unlock: []) expect(versions.first).to eq("0.3.0") end end diff --git a/spec/bundler/bundler/installer/gem_installer_spec.rb b/spec/bundler/bundler/installer/gem_installer_spec.rb index 6583bd8181..dbd4e1d2c8 100644 --- a/spec/bundler/bundler/installer/gem_installer_spec.rb +++ b/spec/bundler/bundler/installer/gem_installer_spec.rb @@ -23,9 +23,7 @@ RSpec.describe Bundler::GemInstaller do context "spec_settings is build option" do it "invokes install method with build_args" do - allow(Bundler.settings).to receive(:[]).with(:bin) - allow(Bundler.settings).to receive(:[]).with(:inline) - allow(Bundler.settings).to receive(:[]).with(:forget_cli_options) + allow(Bundler.settings).to receive(:[]) allow(Bundler.settings).to receive(:[]).with("build.dummy").and_return("--with-dummy-config=dummy") expect(spec_source).to receive(:install).with( spec, @@ -37,9 +35,7 @@ RSpec.describe Bundler::GemInstaller do context "spec_settings is build option with spaces" do it "invokes install method with build_args" do - allow(Bundler.settings).to receive(:[]).with(:bin) - allow(Bundler.settings).to receive(:[]).with(:inline) - allow(Bundler.settings).to receive(:[]).with(:forget_cli_options) + allow(Bundler.settings).to receive(:[]) 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, diff --git a/spec/bundler/bundler/installer/parallel_installer_spec.rb b/spec/bundler/bundler/installer/parallel_installer_spec.rb new file mode 100644 index 0000000000..49bcb5310b --- /dev/null +++ b/spec/bundler/bundler/installer/parallel_installer_spec.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require "bundler/installer/parallel_installer" +require "bundler/rubygems_gem_installer" +require "rubygems/remote_fetcher" +require "bundler" + +RSpec.describe Bundler::ParallelInstaller do + describe "priority queue" do + before do + require "support/artifice/compact_index" + + @previous_client = Gem::Request::ConnectionPools.client + Gem::Request::ConnectionPools.client = Gem::Net::HTTP + Gem::RemoteFetcher.fetcher.close_all + + build_repo2 do + build_gem "gem_with_extension", &:add_c_extension + build_gem "gem_without_extension" + end + + gemfile <<~G + source "https://gem.repo2" + + gem "gem_with_extension" + gem "gem_without_extension" + G + lockfile <<~L + GEM + remote: https://gem.repo2/ + specs: + gem_with_extension (1.0) + gem_without_extension (1.0) + + DEPENDENCIES + gem_with_extension + gem_without_extension + L + + @old_ui = Bundler.ui + Bundler.ui = Bundler::UI::Silent.new + end + + after do + Bundler.ui = @old_ui + Gem::Request::ConnectionPools.client = @previous_client + Artifice.deactivate + end + + let(:definition) do + allow(Bundler).to receive(:root) { bundled_app } + + definition = Bundler::Definition.build(bundled_app.join("Gemfile"), bundled_app.join("Gemfile.lock"), false) + definition.tap(&:setup_domain!) + end + let(:installer) { Bundler::Installer.new(bundled_app, definition) } + + it "queues native extensions in priority" do + parallel_installer = Bundler::ParallelInstaller.new(installer, definition.specs, 2, false, true) + worker_pool = parallel_installer.send(:worker_pool) + expected = 6 # Enqueue to download bundler and the 2 gems. Enqueue to install Bundler and the 2 gems. + + expect(worker_pool).to receive(:enq).exactly(expected).times.and_wrap_original do |original_enq, spec, opts| + unless opts.nil? # Enqueued for download, no priority + if spec.name == "gem_with_extension" + expect(opts).to eq({ priority: true }) + else + expect(opts).to eq({ priority: false }) + end + end + + opts ||= {} + original_enq.call(spec, **opts) + end + + parallel_installer.call + end + end +end diff --git a/spec/bundler/bundler/installer/spec_installation_spec.rb b/spec/bundler/bundler/installer/spec_installation_spec.rb index cbe2589b99..57868766d9 100644 --- a/spec/bundler/bundler/installer/spec_installation_spec.rb +++ b/spec/bundler/bundler/installer/spec_installation_spec.rb @@ -3,18 +3,17 @@ require "bundler/installer/parallel_installer" RSpec.describe Bundler::ParallelInstaller::SpecInstallation do - let!(:dep) do - a_spec = Object.new - def a_spec.name - "I like tests" - end - - def a_spec.full_name - "I really like tests" - end - a_spec + def build_spec(name, extensions: []) + spec = Object.new + spec.define_singleton_method(:name) { name } + spec.define_singleton_method(:full_name) { "#{name}-1.0" } + spec.define_singleton_method(:extensions) { extensions } + spec.define_singleton_method(:dependencies) { [] } + spec end + let!(:dep) { build_spec("I like tests") } + describe "#ready_to_enqueue?" do context "when in enqueued state" do it "is falsey" do @@ -39,29 +38,51 @@ RSpec.describe Bundler::ParallelInstaller::SpecInstallation do end describe "#dependencies_installed?" 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)] + it "returns true when all dependencies are installed" do + alpha = described_class.new(build_spec("alpha")) + alpha.dependencies = [] + + beta = described_class.new(build_spec("beta")) + beta.dependencies = [alpha] + + gamma = described_class.new(build_spec("gamma")) + gamma.dependencies = [beta] + + expect(gamma.dependencies_installed?({})).to be_falsey + expect(gamma.dependencies_installed?({ "beta" => true })).to be_falsey + expect(gamma.dependencies_installed?({ "alpha" => true, "beta" => true })).to be_truthy + end + end + + describe "#ready_to_install?" do + context "when spec has no extensions" do + it "returns true regardless of dependencies" do + beta = described_class.new(build_spec("beta")) + beta.dependencies = [] + spec = described_class.new(dep) - allow(spec).to receive(:all_dependencies).and_return(dependencies) - installed_specs = all_specs.select(&:installed?).map {|s| [s.name, true] }.to_h - expect(spec.dependencies_installed?(installed_specs)).to be_truthy + spec.state = :downloaded + spec.dependencies = [beta] + + expect(spec.ready_to_install?({})).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)] - spec = described_class.new(dep) - allow(spec).to receive(:all_dependencies).and_return(dependencies) - installed_specs = all_specs.select(&:installed?).map {|s| [s.name, true] }.to_h - expect(spec.dependencies_installed?(installed_specs)).to be_falsey + context "when spec has extensions" do + it "returns true when all dependencies are installed" do + alpha = described_class.new(build_spec("alpha")) + alpha.dependencies = [] + + beta = described_class.new(build_spec("beta")) + beta.dependencies = [alpha] + + gamma = described_class.new(build_spec("gamma", extensions: ["ext/Rakefile"])) + gamma.state = :downloaded + gamma.dependencies = [beta] + + expect(gamma.ready_to_install?({})).to be_falsey + expect(gamma.ready_to_install?({ "beta" => true })).to be_falsey + expect(gamma.ready_to_install?({ "alpha" => true, "beta" => true })).to be_truthy end end end diff --git a/spec/bundler/bundler/lockfile_parser_spec.rb b/spec/bundler/bundler/lockfile_parser_spec.rb index 88932bf009..7364ab98e5 100644 --- a/spec/bundler/bundler/lockfile_parser_spec.rb +++ b/spec/bundler/bundler/lockfile_parser_spec.rb @@ -111,11 +111,11 @@ RSpec.describe Bundler::LockfileParser do end let(:specs) do [ - Bundler::LazySpecification.new("peiji-san", v("1.2.0"), rb), - Bundler::LazySpecification.new("rake", v("10.3.2"), rb), + Bundler::LazySpecification.new("peiji-san", v("1.2.0"), Gem::Platform::RUBY), + Bundler::LazySpecification.new("rake", v("10.3.2"), Gem::Platform::RUBY), ] end - let(:platforms) { [rb] } + let(:platforms) { [Gem::Platform::RUBY] } 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) } @@ -129,6 +129,7 @@ RSpec.describe Bundler::LockfileParser do shared_examples_for "parsing" do it "parses correctly" do + expect(subject.valid?).to be(true) expect(subject.sources).to eq sources expect(subject.dependencies).to eq dependencies expect(subject.specs).to eq specs @@ -138,7 +139,7 @@ RSpec.describe Bundler::LockfileParser do 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(",")}") + expect(checksums).to eq("#{rake_spec.lock_name} #{rake_checksums.map(&:to_lock).sort.join(",")}") end end @@ -191,6 +192,87 @@ RSpec.describe Bundler::LockfileParser do include_examples "parsing" end + context "when the content does not contain any recognized lockfile sections" do + let(:lockfile_contents) { "hello world\nlorem ipsum\n" } + + it "does not raise, is not valid, and deprecates" do + expect(Bundler::SharedHelpers).to receive(:feature_deprecated!).with( + /does not appear to be a valid lockfile.*future version of Bundler/m + ) + parser = described_class.new(lockfile_contents) + expect(parser.valid?).to be(false) + expect(parser.specs).to eq([]) + expect(parser.dependencies).to eq({}) + end + + it "does not raise when strict: true, and still deprecates" do + expect(Bundler::SharedHelpers).to receive(:feature_deprecated!).with( + /does not appear to be a valid lockfile.*future version of Bundler/m + ) + parser = described_class.new(lockfile_contents, strict: true) + expect(parser.valid?).to be(false) + expect(parser.specs).to eq([]) + expect(parser.dependencies).to eq({}) + end + end + + context "when the content looks like a Gemfile DSL" do + let(:lockfile_contents) { <<~G } + source "https://rubygems.org" + gem "rake" + G + + it "does not raise, is not valid, and deprecates" do + expect(Bundler::SharedHelpers).to receive(:feature_deprecated!).with( + /does not appear to be a valid lockfile.*future version of Bundler/m + ) + parser = described_class.new(lockfile_contents) + expect(parser.valid?).to be(false) + expect(parser.specs).to eq([]) + expect(parser.dependencies).to eq({}) + end + + it "does not raise when strict: true, and still deprecates" do + expect(Bundler::SharedHelpers).to receive(:feature_deprecated!).with( + /does not appear to be a valid lockfile.*future version of Bundler/m + ) + parser = described_class.new(lockfile_contents, strict: true) + expect(parser.valid?).to be(false) + expect(parser.specs).to eq([]) + expect(parser.dependencies).to eq({}) + end + end + + context "when the content is empty" do + let(:lockfile_contents) { "" } + + it "does not raise and is valid" do + expect { subject }.not_to raise_error + expect(subject.valid?).to be(true) + end + end + + context "when lockfile_path is given" do + it "uses the provided path in error messages instead of looking up Bundler.default_lockfile" do + expect(Bundler::SharedHelpers).not_to receive(:relative_lockfile_path) + parser = described_class.new(lockfile_contents, lockfile_path: "custom/path.lock") + expect(parser.valid?).to be(true) + rake_spec = parser.specs.last + checksums = parser.sources.last.checksum_store.to_lock(rake_spec) + expected_checksum = Bundler::Checksum.from_lock( + "sha256=814828c34f1315d7e7b7e8295184577cc4e969bad6156ac069d02d63f58d82e8", + "custom/path.lock:20:17" + ) + expect(checksums).to eq("#{rake_spec.lock_name} #{expected_checksum.to_lock}") + end + + it "raises with the provided path when the lockfile contains merge conflicts" do + expect do + described_class.new("<<<<<<<\n", lockfile_path: "custom/path.lock") + end.to raise_error(Bundler::LockfileError, %r{custom/path\.lock contains merge conflicts}) + end + 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 } diff --git a/spec/bundler/bundler/override_spec.rb b/spec/bundler/bundler/override_spec.rb new file mode 100644 index 0000000000..ad8be75520 --- /dev/null +++ b/spec/bundler/bundler/override_spec.rb @@ -0,0 +1,175 @@ +# frozen_string_literal: true + +RSpec.describe "MatchMetadata override-aware checks" do + let(:spec_class) do + Class.new do + include Bundler::MatchMetadata + attr_accessor :name + def initialize(name, ruby_req, rubygems_req) + @name = name + @required_ruby_version = ruby_req + @required_rubygems_version = rubygems_req + end + end + end + + it "matches_current_metadata? ignores overrides (strict path)" do + spec = spec_class.new("rails", Gem::Requirement.new("< #{Gem.ruby_version}"), Gem::Requirement.default) + overrides = [Bundler::Override.new("rails", :required_ruby_version, :ignore_upper)] + # Strict method MUST NOT apply overrides; guards SelfManager and other generic callers. + expect(spec.matches_current_metadata?).to be(false) + expect(spec.matches_current_metadata_with_overrides?(overrides)).to be(true) + end + + it "matches_current_ruby_with_overrides? returns the strict result for an empty override list" do + spec = spec_class.new("rails", Gem::Requirement.new(">= #{Gem.ruby_version}"), Gem::Requirement.default) + expect(spec.matches_current_ruby_with_overrides?([])).to be(true) + expect(spec.matches_current_ruby_with_overrides?(nil)).to be(true) + end + + it "matches_current_rubygems_with_overrides? honors :all override" do + spec = spec_class.new("rails", Gem::Requirement.default, Gem::Requirement.new("< #{Gem.rubygems_version}")) + overrides = [Bundler::Override.new(:all, :required_rubygems_version, :ignore_upper)] + expect(spec.matches_current_rubygems_with_overrides?(overrides)).to be(true) + end +end + +RSpec.describe "LazySpecification override propagation" do + let(:overrides) { [Bundler::Override.new("rails", :required_ruby_version, :ignore_upper)] } + + it "carries overrides forward from a source LazySpec via from_spec" do + src = Bundler::LazySpecification.new("rails", "8.0", Gem::Platform::RUBY) + src.overrides = overrides + derived = Bundler::LazySpecification.from_spec(src) + expect(derived.overrides).to eq(overrides) + end + + it "does not call respond_to? on the source spec, avoiding gemspec lazy load" do + # If from_spec used respond_to?(:overrides), a RemoteSpec source would + # force-load the backing gemspec. Use a stand-in object whose + # respond_to? raises to prove it is never asked. + src = Object.new + src.define_singleton_method(:name) { "rails" } + src.define_singleton_method(:version) { Gem::Version.new("8.0") } + src.define_singleton_method(:platform) { Gem::Platform::RUBY } + src.define_singleton_method(:source) { nil } + src.define_singleton_method(:runtime_dependencies) { [] } + src.define_singleton_method(:required_ruby_version) { Gem::Requirement.default } + src.define_singleton_method(:required_rubygems_version) { Gem::Requirement.default } + src.define_singleton_method(:respond_to?) {|*| raise "from_spec must not call respond_to?" } + expect { Bundler::LazySpecification.from_spec(src) }.not_to raise_error + end +end + +RSpec.describe Bundler::Override do + describe ".find_for" do + it "returns the matching override by target and field" do + a = described_class.new("rails", :version, ">= 8.0") + b = described_class.new("nokogiri", :version, :ignore_upper) + expect(described_class.find_for([a, b], "rails", :version)).to be(a) + end + + it "returns nil when no override matches the target" do + a = described_class.new("rails", :version, ">= 8.0") + expect(described_class.find_for([a], "sinatra", :version)).to be_nil + end + + it "returns nil when no override matches the field" do + a = described_class.new("rails", :version, ">= 8.0") + expect(described_class.find_for([a], "rails", :required_ruby_version)).to be_nil + end + + it "returns nil for an empty overrides list" do + expect(described_class.find_for([], "rails", :version)).to be_nil + end + + it "falls back to an :all override on the same field" do + a = described_class.new(:all, :required_ruby_version, :ignore_upper) + expect(described_class.find_for([a], "rails", :required_ruby_version)).to be(a) + end + + it "prefers a per-gem override over a matching :all override" do + per_gem = described_class.new("rails", :required_ruby_version, ">= 3.4") + all_target = described_class.new(:all, :required_ruby_version, :ignore_upper) + expect(described_class.find_for([all_target, per_gem], "rails", :required_ruby_version)).to be(per_gem) + end + + it "does not fall back to :all when the field differs" do + a = described_class.new(:all, :required_ruby_version, :ignore_upper) + expect(described_class.find_for([a], "rails", :required_rubygems_version)).to be_nil + end + end + + describe "#apply_to" do + context "when operation is a version spec string" do + it "replaces the existing requirement entirely" do + override = described_class.new("rails", :version, ">= 8.0") + result = override.apply_to(Gem::Requirement.new(">= 1.0", "< 2.0")) + expect(result).to eq(Gem::Requirement.new(">= 8.0")) + end + + it "ignores the existing requirement regardless of its content" do + override = described_class.new("rails", :version, "= 1.0") + result = override.apply_to(Gem::Requirement.new(">= 99.0")) + expect(result).to eq(Gem::Requirement.new("= 1.0")) + end + end + + context "when operation is :ignore_upper" do + it "removes < and <= operators" do + override = described_class.new("rails", :version, :ignore_upper) + result = override.apply_to(Gem::Requirement.new(">= 1.0", "< 2.0")) + expect(result).to eq(Gem::Requirement.new(">= 1.0")) + end + + it "keeps >, >=, = operators" do + override = described_class.new("rails", :version, :ignore_upper) + result = override.apply_to(Gem::Requirement.new("> 1.0", "<= 2.0")) + expect(result).to eq(Gem::Requirement.new("> 1.0")) + end + + it "converts ~> to >= preserving the lower bound" do + override = described_class.new("rails", :version, :ignore_upper) + result = override.apply_to(Gem::Requirement.new("~> 1.5")) + expect(result).to eq(Gem::Requirement.new(">= 1.5")) + end + + it "preserves != exclusion constraints" do + override = described_class.new("rails", :version, :ignore_upper) + result = override.apply_to(Gem::Requirement.new(">= 1.0", "!= 1.5.0", "< 2.0")) + expect(result).to eq(Gem::Requirement.new(">= 1.0", "!= 1.5.0")) + end + + it "returns the default requirement when only upper bounds remain" do + override = described_class.new("rails", :version, :ignore_upper) + result = override.apply_to(Gem::Requirement.new("< 2.0")) + expect(result).to eq(Gem::Requirement.default) + end + + it "returns the default requirement when the input is nil" do + override = described_class.new("rails", :version, :ignore_upper) + expect(override.apply_to(nil)).to eq(Gem::Requirement.default) + end + + it "returns the default requirement when the input is already the default" do + override = described_class.new("rails", :version, :ignore_upper) + expect(override.apply_to(Gem::Requirement.default)).to eq(Gem::Requirement.default) + end + end + + context "when operation is nil" do + it "returns the default requirement" do + override = described_class.new("rails", :version, nil) + result = override.apply_to(Gem::Requirement.new(">= 1.0", "< 2.0")) + expect(result).to eq(Gem::Requirement.default) + end + end + + context "when operation is unsupported" do + it "raises ArgumentError" do + override = described_class.new("rails", :version, 42) + expect { override.apply_to(Gem::Requirement.default) }.to raise_error(ArgumentError, /unsupported override operation/) + end + end + end +end diff --git a/spec/bundler/bundler/plugin/events_spec.rb b/spec/bundler/bundler/plugin/events_spec.rb index 28d70c6fdd..77e5fdb74c 100644 --- a/spec/bundler/bundler/plugin/events_spec.rb +++ b/spec/bundler/bundler/plugin/events_spec.rb @@ -2,7 +2,17 @@ RSpec.describe Bundler::Plugin::Events do context "plugin events" do - before { Bundler::Plugin::Events.send :reset } + before do + @old_constants = Bundler::Plugin::Events.constants.map {|name| [name, Bundler::Plugin::Events.const_get(name)] } + Bundler::Plugin::Events.send :reset + end + + after do + Bundler::Plugin::Events.send(:reset) + Hash[@old_constants].each do |name, value| + Bundler::Plugin::Events.send(:define, name, value) + end + end describe "#define" do it "raises when redefining a constant" do diff --git a/spec/bundler/bundler/plugin/index_spec.rb b/spec/bundler/bundler/plugin/index_spec.rb index 565fc9b088..a28934269b 100644 --- a/spec/bundler/bundler/plugin/index_spec.rb +++ b/spec/bundler/bundler/plugin/index_spec.rb @@ -193,4 +193,83 @@ RSpec.describe Bundler::Plugin::Index do include_examples "it cleans up" end end + + describe "relative plugin paths" do + let(:plugin_name) { "relative-plugin" } + + before do + Bundler::Plugin.reset! + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + + plugin_root = Bundler::Plugin.root + FileUtils.mkdir_p(plugin_root) + + path = plugin_root.join(plugin_name) + FileUtils.mkdir_p(path.join("lib")) + + index.register_plugin(plugin_name, path.to_s, [path.join("lib").to_s], [], [], []) + end + + it "stores plugin paths relative to the plugin root" do + require "yaml" + data = YAML.load_file(index.index_file) + + expect(data["plugin_paths"][plugin_name]).to eq(plugin_name) + expect(data["load_paths"][plugin_name]).to eq([File.join(plugin_name, "lib")]) + end + + it "expands relative paths to absolute on load" do + require "bundler/yaml_serializer" + + plugin_root = Bundler::Plugin.root + + relative_index = { + "commands" => {}, + "hooks" => {}, + "load_paths" => { plugin_name => [File.join(plugin_name, "lib")] }, + "plugin_paths" => { plugin_name => plugin_name }, + "sources" => {}, + } + + File.open(index.index_file, "w") {|f| f.puts Bundler::YAMLSerializer.dump(relative_index) } + + new_index = Index.new + expect(new_index.plugin_path(plugin_name)).to eq(plugin_root.join(plugin_name)) + expect(new_index.load_paths(plugin_name)).to eq([plugin_root.join(plugin_name, "lib").to_s]) + end + + it "keeps paths outside the plugin root as absolute" do + outside_path = tmp.join("outside", "external-plugin") + FileUtils.mkdir_p(outside_path.join("lib")) + + index.register_plugin("external-plugin", outside_path.to_s, [outside_path.join("lib").to_s], [], [], []) + + require "yaml" + data = YAML.load_file(index.index_file) + + expect(data["plugin_paths"]["external-plugin"]).to eq(outside_path.to_s) + expect(data["load_paths"]["external-plugin"]).to eq([outside_path.join("lib").to_s]) + end + + it "reads legacy index files with absolute paths" do + require "bundler/yaml_serializer" + + plugin_root = Bundler::Plugin.root + absolute_path = plugin_root.join(plugin_name).to_s + + legacy_index = { + "commands" => {}, + "hooks" => {}, + "load_paths" => { plugin_name => [File.join(absolute_path, "lib")] }, + "plugin_paths" => { plugin_name => absolute_path }, + "sources" => {}, + } + + File.open(index.index_file, "w") {|f| f.puts Bundler::YAMLSerializer.dump(legacy_index) } + + new_index = Index.new + expect(new_index.plugin_path(plugin_name)).to eq(Pathname.new(absolute_path)) + expect(new_index.load_paths(plugin_name)).to eq([File.join(absolute_path, "lib")]) + end + end end diff --git a/spec/bundler/bundler/plugin/installer_spec.rb b/spec/bundler/bundler/plugin/installer_spec.rb index 8e1879395a..c200a98afa 100644 --- a/spec/bundler/bundler/plugin/installer_spec.rb +++ b/spec/bundler/bundler/plugin/installer_spec.rb @@ -47,6 +47,13 @@ RSpec.describe Bundler::Plugin::Installer do build_plugin "re-plugin" build_plugin "ma-plugin" end + + @previous_ui = Bundler.ui + Bundler.ui = Bundler::UI::Silent.new + end + + after do + Bundler.ui = @previous_ui end context "git plugins" do diff --git a/spec/bundler/bundler/plugin_spec.rb b/spec/bundler/bundler/plugin_spec.rb index fea3925000..b379594c6f 100644 --- a/spec/bundler/bundler/plugin_spec.rb +++ b/spec/bundler/bundler/plugin_spec.rb @@ -65,8 +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(index).to receive(:up_to_date?). + with(spec) allow(installer).to receive(:install).with(["new-plugin"], opts) do { "new-plugin" => spec } end.once @@ -75,8 +75,8 @@ RSpec.describe Bundler::Plugin do end it "validates the installed plugin" do - allow(index).to receive(:installed?). - with("new-plugin") + allow(index).to receive(:up_to_date?). + with(spec) allow(subject). to receive(:validate_plugin!).with(lib_path("new-plugin")).once @@ -84,8 +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(:up_to_date?). + with(spec) 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 @@ -102,7 +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(:up_to_date?).twice allow(index).to receive(:register_plugin).twice subject.install ["new-plugin", "another-plugin"], opts end @@ -138,7 +138,7 @@ RSpec.describe Bundler::Plugin do end before do - allow(index).to receive(:installed?) { nil } + allow(index).to receive(:up_to_date?) { nil } allow(definition).to receive(:dependencies) { [Bundler::Dependency.new("new-plugin", ">=0"), Bundler::Dependency.new("another-plugin", ">=0")] } allow(installer).to receive(:install_definition) { plugin_specs } end @@ -279,6 +279,7 @@ RSpec.describe Bundler::Plugin do s.write "plugins.rb", code end + @old_constants = Bundler::Plugin::Events.constants.map {|name| [name, Bundler::Plugin::Events.const_get(name)] } Bundler::Plugin::Events.send(:reset) Bundler::Plugin::Events.send(:define, :EVENT1, "event-1") Bundler::Plugin::Events.send(:define, :EVENT2, "event-2") @@ -291,6 +292,13 @@ RSpec.describe Bundler::Plugin do allow(index).to receive(:load_paths).with("foo-plugin").and_return([]) end + after do + Bundler::Plugin::Events.send(:reset) + Hash[@old_constants].each do |name, value| + Bundler::Plugin::Events.send(:define, name, value) + end + end + let(:code) { <<-RUBY } Bundler::Plugin::API.hook("event-1") { puts "hook for event 1" } RUBY diff --git a/spec/bundler/bundler/resolver/cooldown_spec.rb b/spec/bundler/bundler/resolver/cooldown_spec.rb new file mode 100644 index 0000000000..37ec158cba --- /dev/null +++ b/spec/bundler/bundler/resolver/cooldown_spec.rb @@ -0,0 +1,148 @@ +# frozen_string_literal: true + +RSpec.describe Bundler::Resolver do + let(:resolver) { described_class.allocate } + + def remote(cooldown:) + instance_double(Bundler::Source::Rubygems::Remote, effective_cooldown: cooldown) + end + + def spec(created_at:, remote:, name: "myrack", version: "1.0.0") + Struct.new(:name, :version, :created_at, :remote).new(name, Gem::Version.new(version), created_at, remote) + end + + describe "#filter_cooldown" do + let(:now) { Time.now } + + context "with a 7-day cooldown" do + let(:r) { remote(cooldown: 7) } + + it "rejects versions published within the window" do + recent = spec(version: "1.1.0", created_at: now - (2 * 86_400), remote: r) + old = spec(version: "1.0.0", created_at: now - (30 * 86_400), remote: r) + + expect(resolver.send(:filter_cooldown, [recent, old])).to eq([old]) + end + + it "keeps versions published exactly at the threshold" do + boundary = spec(created_at: now - (7 * 86_400), remote: r) + + expect(resolver.send(:filter_cooldown, [boundary])).to eq([boundary]) + end + + it "leaves rolling-delay history intact" do + # 7-day cooldown with frequent releases must still expose an older candidate. + in_cooldown = spec(version: "1.2.0", created_at: now - 86_400, remote: r) + also_in_cooldown = spec(version: "1.1.0", created_at: now - (3 * 86_400), remote: r) + eligible = spec(version: "1.0.0", created_at: now - (10 * 86_400), remote: r) + + result = resolver.send(:filter_cooldown, [in_cooldown, also_in_cooldown, eligible]) + + expect(result).to eq([eligible]) + end + + it "drops every spec sharing an excluded [name, version] tuple" do + # The cooldown check is by version, not per-spec: a StubSpecification for an + # in-cooldown release would otherwise slip through on local install paths. + endpoint = spec(version: "2.0.0", created_at: now - 86_400, remote: r) + local_stub = Struct.new(:name, :version).new("myrack", Gem::Version.new("2.0.0")) + eligible = spec(version: "1.0.0", created_at: now - (30 * 86_400), remote: r) + + result = resolver.send(:filter_cooldown, [endpoint, local_stub, eligible]) + + expect(result).to eq([eligible]) + end + + it "keeps stub-only versions that no endpoint marks as in cooldown" do + # If no remote spec carries created_at for a version, cooldown cannot judge it; + # the stub stays in. + local_only = Struct.new(:name, :version).new("myrack", Gem::Version.new("2.0.0")) + eligible = spec(version: "1.0.0", created_at: now - (30 * 86_400), remote: r) + + result = resolver.send(:filter_cooldown, [local_only, eligible]) + + expect(result).to eq([local_only, eligible]) + end + end + + context "when created_at is missing (blank metadata)" do + it "keeps the spec regardless of cooldown" do + s = spec(created_at: nil, remote: remote(cooldown: 7)) + + expect(resolver.send(:filter_cooldown, [s])).to eq([s]) + end + end + + context "when the remote has no cooldown" do + it "keeps every spec" do + s = spec(created_at: now - 3600, remote: remote(cooldown: nil)) + + expect(resolver.send(:filter_cooldown, [s])).to eq([s]) + end + end + + context "when cooldown is 0" do + it "keeps every spec (escape hatch)" do + s = spec(created_at: now - 3600, remote: remote(cooldown: 0)) + + expect(resolver.send(:filter_cooldown, [s])).to eq([s]) + end + end + + context "when the spec does not respond to created_at" do + it "keeps the spec" do + bare = Struct.new(:version).new("1.0.0") + + expect(resolver.send(:filter_cooldown, [bare])).to eq([bare]) + end + end + + context "when the spec has no remote" do + it "keeps the spec" do + s = spec(created_at: now - 86_400, remote: nil) + + expect(resolver.send(:filter_cooldown, [s])).to eq([s]) + end + end + + it "returns the same array when input is empty" do + expect(resolver.send(:filter_cooldown, [])).to eq([]) + end + end + + describe "#cooldown_hint" do + let(:now) { Time.now } + let(:r) { remote(cooldown: 7) } + + it "returns nil when no spec is excluded" do + expect(resolver.send(:cooldown_hint, [])).to be_nil + end + + it "returns nil when every spec is outside the cooldown window" do + eligible = [spec(created_at: now - (30 * 86_400), remote: r)] + + expect(resolver.send(:cooldown_hint, eligible)).to be_nil + end + + it "mentions the count and the bypass flag for one excluded version" do + excluded = [spec(created_at: now - 86_400, remote: r)] + + hint = resolver.send(:cooldown_hint, excluded) + + expect(hint).to match(/1 version excluded by the cooldown setting/) + expect(hint).to match(/--cooldown 0/) + end + + it "uses plural wording when multiple versions are excluded" do + excluded = %w[1.0.0 1.1.0 1.2.0].map {|v| spec(version: v, created_at: now - 86_400, remote: r) } + + expect(resolver.send(:cooldown_hint, excluded)).to match(/3 versions excluded/) + end + + it "counts each unique version once even when multiple spec instances share it" do + duplicates = Array.new(3) { spec(created_at: now - 86_400, remote: r) } + + expect(resolver.send(:cooldown_hint, duplicates)).to match(/1 version excluded/) + end + end +end diff --git a/spec/bundler/bundler/retry_spec.rb b/spec/bundler/bundler/retry_spec.rb index ffbc078074..5c84d0bea5 100644 --- a/spec/bundler/bundler/retry_spec.rb +++ b/spec/bundler/bundler/retry_spec.rb @@ -12,7 +12,7 @@ RSpec.describe Bundler::Retry do end it "returns the first valid result" do - jobs = [proc { raise "foo" }, proc { :bar }, proc { raise "foo" }] + jobs = [proc { raise "job 1 failed" }, proc { :bar }, proc { raise "job 2 failed" }] attempts = 0 result = Bundler::Retry.new(nil, nil, 3).attempt do attempts += 1 @@ -78,4 +78,113 @@ RSpec.describe Bundler::Retry do end end end + + context "exponential backoff" do + it "can be disabled by setting base_delay to 0" do + attempts = 0 + expect do + Bundler::Retry.new("test", [], 2, base_delay: 0).attempt do + attempts += 1 + raise "error" + end + end.to raise_error(StandardError) + + # Verify no sleep was called (implicitly - if sleep was called, timing would be different) + expect(attempts).to eq(3) + end + + it "is enabled by default with 1 second base delay" do + original_base_delay = Bundler::Retry.default_base_delay + Bundler::Retry.default_base_delay = 1.0 + + attempts = 0 + sleep_times = [] + + allow_any_instance_of(Bundler::Retry).to receive(:sleep) do |_instance, delay| + sleep_times << delay + end + + expect do + Bundler::Retry.new("test", [], 2, jitter: 0).attempt do + attempts += 1 + raise "error" + end + end.to raise_error(StandardError) + + expect(attempts).to eq(3) + expect(sleep_times.length).to eq(2) + # First retry: 1.0 * 2^0 = 1.0 + expect(sleep_times[0]).to eq(1.0) + # Second retry: 1.0 * 2^1 = 2.0 + expect(sleep_times[1]).to eq(2.0) + ensure + Bundler::Retry.default_base_delay = original_base_delay + end + + it "sleeps with exponential backoff when base_delay is set" do + attempts = 0 + sleep_times = [] + + allow_any_instance_of(Bundler::Retry).to receive(:sleep) do |_instance, delay| + sleep_times << delay + end + + expect do + Bundler::Retry.new("test", [], 2, base_delay: 1.0, jitter: 0).attempt do + attempts += 1 + raise "error" + end + end.to raise_error(StandardError) + + expect(attempts).to eq(3) + expect(sleep_times.length).to eq(2) + # First retry: 1.0 * 2^0 = 1.0 + expect(sleep_times[0]).to eq(1.0) + # Second retry: 1.0 * 2^1 = 2.0 + expect(sleep_times[1]).to eq(2.0) + end + + it "respects max_delay" do + sleep_times = [] + + allow_any_instance_of(Bundler::Retry).to receive(:sleep) do |_instance, delay| + sleep_times << delay + end + + expect do + Bundler::Retry.new("test", [], 3, base_delay: 10.0, max_delay: 15.0, jitter: 0).attempt do + raise "error" + end + end.to raise_error(StandardError) + + # First retry: 10.0 * 2^0 = 10.0 + expect(sleep_times[0]).to eq(10.0) + # Second retry: 10.0 * 2^1 = 20.0, capped at 15.0 + expect(sleep_times[1]).to eq(15.0) + # Third retry: 10.0 * 2^2 = 40.0, capped at 15.0 + expect(sleep_times[2]).to eq(15.0) + end + + it "adds jitter to delay" do + sleep_times = [] + + allow_any_instance_of(Bundler::Retry).to receive(:sleep) do |_instance, delay| + sleep_times << delay + end + + expect do + Bundler::Retry.new("test", [], 2, base_delay: 1.0, jitter: 0.5).attempt do + raise "error" + end + end.to raise_error(StandardError) + + expect(sleep_times.length).to eq(2) + # First retry should be between 1.0 and 1.5 (base + jitter) + expect(sleep_times[0]).to be >= 1.0 + expect(sleep_times[0]).to be <= 1.5 + # Second retry should be between 2.0 and 2.5 + expect(sleep_times[1]).to be >= 2.0 + expect(sleep_times[1]).to be <= 2.5 + end + end end diff --git a/spec/bundler/bundler/ruby_dsl_spec.rb b/spec/bundler/bundler/ruby_dsl_spec.rb index 2607f746e7..45a37c5795 100644 --- a/spec/bundler/bundler/ruby_dsl_spec.rb +++ b/spec/bundler/bundler/ruby_dsl_spec.rb @@ -178,11 +178,34 @@ RSpec.describe Bundler::RubyDsl do let(:file_content) do <<~TOML [tools] - ruby = "#{version}" + ruby = #{quote}#{version}#{quote} TOML end - it_behaves_like "it stores the ruby version" + context "with double quotes" do + let(:quote) { '"' } + + it_behaves_like "it stores the ruby version" + end + + context "with single quotes" do + let(:quote) { "'" } + + it_behaves_like "it stores the ruby version" + end + + context "with mismatched quotes" do + let(:file_content) do + <<~TOML + [tools] + ruby = "#{version}' + TOML + end + + it "raises an error" do + expect { subject }.to raise_error(Bundler::InvalidArgumentError, "= is not a valid requirement on the Ruby version") + end + end end context "with a .tool-versions file format" do @@ -210,6 +233,16 @@ RSpec.describe Bundler::RubyDsl do it_behaves_like "it stores the ruby version" end end + + context "when the file does not exist" do + let(:ruby_version_file_path) { nil } + let(:ruby_version_arg) { nil } + let(:file) { "nonexistent.txt" } + + it "raises an error" do + expect { subject }.to raise_error(Bundler::GemfileError, /Could not find version file nonexistent.txt/) + end + end end end end diff --git a/spec/bundler/bundler/ruby_version_spec.rb b/spec/bundler/bundler/ruby_version_spec.rb index 39d0571361..0d41ec9901 100644 --- a/spec/bundler/bundler/ruby_version_spec.rb +++ b/spec/bundler/bundler/ruby_version_spec.rb @@ -100,7 +100,7 @@ RSpec.describe "Bundler::RubyVersion and its subclasses" do describe "#to_s" do it "should return info string with the ruby version, patchlevel, engine, and engine version" do - expect(subject.to_s).to eq("ruby 2.0.0p645 (jruby 2.0.1)") + expect(subject.to_s).to eq("ruby 2.0.0 (jruby 2.0.1)") end context "no patchlevel" do @@ -115,7 +115,7 @@ RSpec.describe "Bundler::RubyVersion and its subclasses" do let(:engine) { "ruby" } it "should return info string with the ruby version and patchlevel" do - expect(subject.to_s).to eq("ruby 2.0.0p645") + expect(subject.to_s).to eq("ruby 2.0.0") end end @@ -137,7 +137,7 @@ RSpec.describe "Bundler::RubyVersion and its subclasses" do end end - context "the versions, pathlevels, engines, and engine_versions match" do + shared_examples_for "the versions, engines, and engine_versions match" do it "should return true" do expect(subject).to eq(other_ruby_version) end @@ -152,7 +152,7 @@ RSpec.describe "Bundler::RubyVersion and its subclasses" do context "the patchlevels do not match" do let(:other_patchlevel) { "21" } - it_behaves_like "two ruby versions are not equal" + it_behaves_like "the versions, engines, and engine_versions match" end context "the engines do not match" do @@ -228,9 +228,9 @@ RSpec.describe "Bundler::RubyVersion and its subclasses" do end end - shared_examples_for "there is a difference in the patchlevels" do - it "should return a tuple with :patchlevel and the two different patchlevels" do - expect(ruby_version.diff(other_ruby_version)).to eq([:patchlevel, patchlevel, other_patchlevel]) + shared_examples_for "even there is a difference in the patchlevels" do + it "should return nil" do + expect(ruby_version.diff(other_ruby_version)).to be_nil end end @@ -287,10 +287,10 @@ RSpec.describe "Bundler::RubyVersion and its subclasses" do it_behaves_like "there is a difference in the engine versions" end - context "detects patchlevel discrepancies last" do + context "ignores patchlevel discrepancies last" do let(:other_patchlevel) { "643" } - it_behaves_like "there is a difference in the patchlevels" + it_behaves_like "even there is a difference in the patchlevels" end context "successfully matches gem requirements" do @@ -355,7 +355,7 @@ RSpec.describe "Bundler::RubyVersion and its subclasses" do let(:other_engine) { "ruby" } let(:other_engine_version) { "2.0.5" } - it_behaves_like "there is a difference in the patchlevels" + it_behaves_like "even there is a difference in the patchlevels" end context "successfully detects bad gem requirements with engine versions" do @@ -389,7 +389,7 @@ RSpec.describe "Bundler::RubyVersion and its subclasses" do context "and comparing with a patchlevel that is not -1" do let(:other_patchlevel) { "642" } - it_behaves_like "there is a difference in the patchlevels" + it_behaves_like "even there is a difference in the patchlevels" end end end diff --git a/spec/bundler/bundler/rubygems_ext_spec.rb b/spec/bundler/bundler/rubygems_ext_spec.rb new file mode 100644 index 0000000000..0fc528f78c --- /dev/null +++ b/spec/bundler/bundler/rubygems_ext_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require "bundler/rubygems_ext" + +RSpec.describe Gem::SplitCompactIndexEntryOnFirstColon do + # Reproduces the RubyGems < 4.0.13 `Gem::Resolver::APISet::GemParser` that + # split each compact index entry on every colon, corrupting metadata values + # that themselves contain colons. + let(:legacy_parser_class) do + Class.new do + def parse_dependency(string) + dependency = string.split(":") + dependency[-1] = dependency[-1].split("&") if dependency.size > 1 + dependency[0] = -dependency[0] + dependency + end + end + end + + before { legacy_parser_class.prepend(described_class) } + + it "preserves colon-bearing metadata values such as created_at timestamps" do + parser = legacy_parser_class.new + + expect(parser.send(:parse_dependency, "created_at:2026-05-12T10:00:00Z")).to eq(["created_at", ["2026-05-12T10:00:00Z"]]) + end + + it "still parses ordinary name:requirement entries" do + parser = legacy_parser_class.new + + expect(parser.send(:parse_dependency, "myrack:>= 1.0")).to eq(["myrack", [">= 1.0"]]) + end + + it "keeps parse_dependency private" do + parser = legacy_parser_class.new + + expect { parser.parse_dependency("created_at:x") }.to raise_error(NoMethodError, /private method/) + end +end diff --git a/spec/bundler/bundler/settings_spec.rb b/spec/bundler/bundler/settings_spec.rb index 7c64c1c907..5e1aaaa555 100644 --- a/spec/bundler/bundler/settings_spec.rb +++ b/spec/bundler/bundler/settings_spec.rb @@ -119,6 +119,11 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow settings.set_local :ssl_verify_mode, "1" expect(settings[:ssl_verify_mode]).to be 1 end + + it "coerces cooldown to integer" do + settings.set_local :cooldown, "7" + expect(settings[:cooldown]).to be 7 + end end context "when it's not possible to create the settings directory" do @@ -200,7 +205,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) { Gem::URI("https://rubygems-mirror.org/") } + let(:mirror_uri) { Gem::URI("https://example-mirror.rubygems.org/") } before { settings.set_local "mirror.https://rubygems.org/", mirror_uri.to_s } @@ -277,12 +282,12 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow end it "normalizes HTTP URIs in mirror configuration" do - settings.set_local "mirror.http://rubygems.org", "http://rubygems-mirror.org" + settings.set_local "mirror.http://rubygems.org", "http://example-mirror.rubygems.org" expect(settings.all).to include("mirror.http://rubygems.org/") end it "normalizes HTTPS URIs in mirror configuration" do - settings.set_local "mirror.https://rubygems.org", "http://rubygems-mirror.org" + settings.set_local "mirror.https://rubygems.org", "http://example-mirror.rubygems.org" expect(settings.all).to include("mirror.https://rubygems.org/") end @@ -297,9 +302,9 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow end it "reads older keys without trailing slashes" do - settings.set_local "mirror.https://rubygems.org", "http://rubygems-mirror.org" + settings.set_local "mirror.https://rubygems.org", "http://example-mirror.rubygems.org" expect(settings.mirror_for("https://rubygems.org/")).to eq( - Gem::URI("http://rubygems-mirror.org/") + Gem::URI("http://example-mirror.rubygems.org/") ) end @@ -318,13 +323,13 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow let(:settings) { described_class.new(bundled_app(".bundle")) } it "converts older keys without double underscore" do - config("BUNDLE_MY__PERSONAL.MYRACK" => "~/Work/git/myrack") + bundle_config("BUNDLE_MY__PERSONAL.MYRACK" => "~/Work/git/myrack") expect(settings["my.personal.myrack"]).to eq("~/Work/git/myrack") end it "converts older keys without trailing slashes and double underscore" do - config("BUNDLE_MIRROR__HTTPS://RUBYGEMS.ORG" => "http://rubygems-mirror.org") - expect(settings["mirror.https://rubygems.org/"]).to eq("http://rubygems-mirror.org") + bundle_config("BUNDLE_MIRROR__HTTPS://RUBYGEMS.ORG" => "http://example-mirror.rubygems.org") + expect(settings["mirror.https://rubygems.org/"]).to eq("http://example-mirror.rubygems.org") end it "ignores commented out keys" do @@ -337,7 +342,7 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow end it "converts older keys with dashes" do - config("BUNDLE_MY-PERSONAL-SERVER__ORG" => "my-personal-server.org") + bundle_config("BUNDLE_MY-PERSONAL-SERVER__ORG" => "my-personal-server.org") expect(Bundler.ui).to receive(:warn).with( "Your #{bundled_app(".bundle/config")} config includes `BUNDLE_MY-PERSONAL-SERVER__ORG`, which contains the dash character (`-`).\n" \ "This is deprecated, because configuration through `ENV` should be possible, but `ENV` keys cannot include dashes.\n" \ @@ -347,8 +352,29 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow end it "reads newer keys format properly" do - config("BUNDLE_MIRROR__HTTPS://RUBYGEMS__ORG/" => "http://rubygems-mirror.org") - expect(settings["mirror.https://rubygems.org/"]).to eq("http://rubygems-mirror.org") + bundle_config("BUNDLE_MIRROR__HTTPS://RUBYGEMS__ORG/" => "http://example-mirror.rubygems.org") + expect(settings["mirror.https://rubygems.org/"]).to eq("http://example-mirror.rubygems.org") + end + end + + describe "default_cli_command validation" do + it "accepts 'install' as a valid value" do + expect { settings.set_local("default_cli_command", "install") }.not_to raise_error + end + + it "accepts 'cli_help' as a valid value" do + expect { settings.set_local("default_cli_command", "cli_help") }.not_to raise_error + end + + it "rejects invalid values" do + expect { settings.set_local("default_cli_command", "invalid") }.to raise_error( + Bundler::InvalidOption, + /Setting `default_cli_command` to "invalid" failed:\n - default_cli_command must be either 'install' or 'cli_help'\n - must be one of: install, cli_help/ + ) + end + + it "accepts nil values" do + expect { settings.set_local("default_cli_command", nil) }.not_to raise_error end end end diff --git a/spec/bundler/bundler/shared_helpers_spec.rb b/spec/bundler/bundler/shared_helpers_spec.rb index 6db8cd6783..41115aa667 100644 --- a/spec/bundler/bundler/shared_helpers_spec.rb +++ b/spec/bundler/bundler/shared_helpers_spec.rb @@ -59,7 +59,7 @@ RSpec.describe Bundler::SharedHelpers do before { allow(subject).to receive(:default_gemfile).and_return(gemfile_path) } - it "returns the lock file path" do + it "returns the lockfile path" do expect(subject.default_lockfile).to eq(expected_lockfile_path) end end @@ -159,7 +159,7 @@ RSpec.describe Bundler::SharedHelpers do let(:pwd_stub) { nil } it "returns the current absolute path" do - expect(subject.pwd).to eq(source_root) + expect(subject.pwd).to eq(git_root.to_s) end end @@ -354,7 +354,7 @@ RSpec.describe Bundler::SharedHelpers do it "ENV['PATH'] should only contain one instance of bundle bin path" do subject.set_bundle_environment - paths = (ENV["PATH"]).split(File::PATH_SEPARATOR) + paths = ENV["PATH"].split(File::PATH_SEPARATOR) expect(paths.count(bundle_path)).to eq(1) end end @@ -423,7 +423,7 @@ RSpec.describe Bundler::SharedHelpers do it "sets BUNDLE_BIN_PATH to the bundle executable file" do subject.set_bundle_environment bin_path = ENV["BUNDLE_BIN_PATH"] - expect(bin_path).to eq(bindir.join("bundle").to_s) + expect(bin_path).to eq(exedir.join("bundle").to_s) expect(File.exist?(bin_path)).to be true end end @@ -515,34 +515,4 @@ 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 f7c883eed4..1f10ca4b07 100644 --- a/spec/bundler/bundler/source/git/git_proxy_spec.rb +++ b/spec/bundler/bundler/source/git/git_proxy_spec.rb @@ -2,7 +2,7 @@ RSpec.describe Bundler::Source::Git::GitProxy do let(:path) { Pathname("path") } - let(:uri) { "https://github.com/rubygems/rubygems.git" } + let(:uri) { "https://github.com/ruby/rubygems.git" } let(:ref) { nil } let(:branch) { nil } let(:tag) { nil } @@ -10,7 +10,9 @@ RSpec.describe Bundler::Source::Git::GitProxy do let(:revision) { nil } let(:git_source) { nil } let(:clone_result) { double(Process::Status, success?: true) } + let(:fail_result) { double(Process::Status, success?: false) } let(:base_clone_args) { ["clone", "--bare", "--no-hardlinks", "--quiet", "--no-tags", "--depth", "1", "--single-branch"] } + let(:base_fetch_args) { ["fetch", "--force", "--quiet", "--no-tags", "--depth", "1"] } subject(:git_proxy) { described_class.new(path, uri, options, revision, git_source) } context "with explicit ref" do @@ -64,7 +66,7 @@ RSpec.describe Bundler::Source::Git::GitProxy do it "adds username and password to URI" do Bundler.settings.temporary(uri => "u:p") do 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]) + expect(git_proxy).to receive(:capture).with([*base_clone_args, "--", "https://u:p@github.com/ruby/rubygems.git", path.to_s], nil).and_return(["", "", clone_result]) subject.checkout end end @@ -72,13 +74,13 @@ RSpec.describe Bundler::Source::Git::GitProxy do it "adds username and password to URI for host" do Bundler.settings.temporary("github.com" => "u:p") do 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]) + expect(git_proxy).to receive(:capture).with([*base_clone_args, "--", "https://u:p@github.com/ruby/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 + Bundler.settings.temporary("https://u:p@github.com/ruby/rubygems-mismatch.git" => "u:p") do 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 @@ -87,7 +89,7 @@ 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" + original = "https://orig:info@github.com/ruby/rubygems.git" 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]) @@ -99,7 +101,7 @@ RSpec.describe Bundler::Source::Git::GitProxy do describe "#version" do context "with a normal version number" do before do - expect(git_proxy).to receive(:git_local).with("--version"). + expect(described_class).to receive(:full_version). and_return("git version 1.2.3") end @@ -114,7 +116,7 @@ RSpec.describe Bundler::Source::Git::GitProxy do context "with a OSX version number" do before do - expect(git_proxy).to receive(:git_local).with("--version"). + expect(described_class).to receive(:full_version). and_return("git version 1.2.3 (Apple Git-BS)") end @@ -129,7 +131,7 @@ RSpec.describe Bundler::Source::Git::GitProxy do context "with a msysgit version number" do before do - expect(git_proxy).to receive(:git_local).with("--version"). + expect(described_class).to receive(:full_version). and_return("git version 1.2.3.msysgit.0") end @@ -146,8 +148,9 @@ RSpec.describe Bundler::Source::Git::GitProxy do describe "#full_version" do context "with a normal version number" do before do - expect(git_proxy).to receive(:git_local).with("--version"). - and_return("git version 1.2.3") + status = double("success?" => true) + expect(Open3).to receive(:capture3).with("git", "--version"). + and_return(["git version 1.2.3", "", status]) end it "returns the git version number" do @@ -157,8 +160,9 @@ RSpec.describe Bundler::Source::Git::GitProxy do context "with a OSX version number" do before do - expect(git_proxy).to receive(:git_local).with("--version"). - and_return("git version 1.2.3 (Apple Git-BS)") + status = double("success?" => true) + expect(Open3).to receive(:capture3).with("git", "--version"). + and_return(["git version 1.2.3 (Apple Git-BS)", "", status]) end it "does not strip out OSX specific additions in the version string" do @@ -168,8 +172,9 @@ RSpec.describe Bundler::Source::Git::GitProxy do context "with a msysgit version number" do before do - expect(git_proxy).to receive(:git_local).with("--version"). - and_return("git version 1.2.3.msysgit.0") + status = double("success?" => true) + expect(Open3).to receive(:capture3).with("git", "--version"). + and_return(["git version 1.2.3.msysgit.0", "", status]) end it "does not strip out msysgit specific additions in the version string" do @@ -199,16 +204,151 @@ RSpec.describe Bundler::Source::Git::GitProxy do end context "URI is HTTP" do - let(:uri) { "http://github.com/rubygems/rubygems.git" } - let(:without_depth_arguments) { ["clone", "--bare", "--no-hardlinks", "--quiet", "--no-tags", "--single-branch"] } - let(:fail_clone_result) { double(Process::Status, success?: false) } + let(:uri) { "http://github.com/ruby/rubygems.git" } + let(:clone_args_without_depth) { ["clone", "--bare", "--no-hardlinks", "--quiet", "--no-tags", "--single-branch"] } - it "retries without --depth when git url is http and fails" do + it "retries clone without --depth when dumb http transport fails" do allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0") - allow(git_proxy).to receive(:capture).with([*base_clone_args, "--", uri, path.to_s], nil).and_return(["", "dumb http transport does not support shallow capabilities", fail_clone_result]) - expect(git_proxy).to receive(:capture).with([*without_depth_arguments, "--", uri, path.to_s], nil).and_return(["", "", clone_result]) + expect(git_proxy).to receive(:capture).with([*base_clone_args, "--", uri, path.to_s], nil).and_return(["", "dumb http transport does not support shallow capabilities", fail_result]) + expect(git_proxy).to receive(:capture).with([*clone_args_without_depth, "--", uri, path.to_s], nil).and_return(["", "", clone_result]) subject.checkout end end + + describe "#installed_to?" do + let(:destination) { "install/dir" } + let(:destination_dir_exists) { true } + let(:children) { ["gem.gemspec", "README.me", ".git", "Rakefile"] } + + before do + allow(Dir).to receive(:exist?).with(destination).and_return(destination_dir_exists) + allow(Dir).to receive(:children).with(destination).and_return(children) + end + + context "when destination dir exists with children other than just .git" do + it "returns true" do + expect(git_proxy.installed_to?(destination)).to be true + end + end + + context "when destination dir does not exist" do + let(:destination_dir_exists) { false } + + it "returns false" do + expect(git_proxy.installed_to?(destination)).to be false + end + end + + context "when destination dir is empty" do + let(:children) { [] } + + it "returns false" do + expect(git_proxy.installed_to?(destination)).to be false + end + end + + context "when destination dir has only .git directory" do + let(:children) { [".git"] } + + it "returns false" do + expect(git_proxy.installed_to?(destination)).to be false + end + end + end + + describe "#checkout" do + context "when the repository isn't cloned" do + before do + allow(path).to receive(:exist?).and_return(false) + end + + it "clones the repository" do + 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 + + context "when the repository is cloned" do + before do + allow(path).to receive(:exist?).and_return(true) + end + + context "with a locked revision" do + let(:revision) { Digest::SHA1.hexdigest("ruby") } + + context "when the revision exists locally" do + it "uses the cached revision" do + allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0") + expect(git_proxy).to receive(:git).with("cat-file", "-e", revision, dir: path).and_return(true) + subject.checkout + end + end + + context "when the revision doesn't exist locally" do + it "fetches the specific revision" do + allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0") + expect(git_proxy).to receive(:git).with("cat-file", "-e", revision, dir: path).and_raise(Bundler::GitError) + expect(git_proxy).to receive(:capture).with(["fetch", "--force", "--quiet", "--no-tags", "--depth", "1", "--", uri, "#{revision}:refs/#{revision}-sha"], path).and_return(["", "", clone_result]) + subject.checkout + end + end + end + + context "with no explicit ref" do + it "fetches the HEAD revision" do + parsed_revision = Digest::SHA1.hexdigest("ruby") + allow(git_proxy).to receive(:git_local).with("rev-parse", "--abbrev-ref", "HEAD", dir: path).and_return(parsed_revision) + allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0") + expect(git_proxy).to receive(:capture).with(["fetch", "--force", "--quiet", "--no-tags", "--depth", "1", "--", uri, "refs/heads/#{parsed_revision}:refs/heads/#{parsed_revision}"], path).and_return(["", "", clone_result]) + subject.checkout + end + end + + context "with a commit ref" do + let(:ref) { Digest::SHA1.hexdigest("ruby") } + + context "when the revision exists locally" do + it "uses the cached revision" do + allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0") + expect(git_proxy).to receive(:git).with("cat-file", "-e", ref, dir: path).and_return(true) + subject.checkout + end + end + + context "when the revision doesn't exist locally" do + it "fetches the specific revision" do + allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0") + expect(git_proxy).to receive(:git).with("cat-file", "-e", ref, dir: path).and_raise(Bundler::GitError) + expect(git_proxy).to receive(:capture).with(["fetch", "--force", "--quiet", "--no-tags", "--depth", "1", "--", uri, "#{ref}:refs/#{ref}-sha"], path).and_return(["", "", clone_result]) + subject.checkout + end + end + end + + context "with a non-commit ref" do + let(:ref) { "HEAD" } + + it "fetches all revisions" do + allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0") + expect(git_proxy).to receive(:capture).with(["fetch", "--force", "--quiet", "--no-tags", "--", uri, "refs/*:refs/*"], path).and_return(["", "", clone_result]) + subject.checkout + end + end + + context "URI is HTTP" do + let(:uri) { "http://github.com/ruby/rubygems.git" } + + it "retries fetch without --depth when dumb http transport fails" do + parsed_revision = Digest::SHA1.hexdigest("ruby") + allow(git_proxy).to receive(:git_local).with("rev-parse", "--abbrev-ref", "HEAD", dir: path).and_return(parsed_revision) + allow(git_proxy).to receive(:git_local).with("--version").and_return("git version 2.14.0") + expect(git_proxy).to receive(:capture).with([*base_fetch_args, "--", uri, "refs/heads/#{parsed_revision}:refs/heads/#{parsed_revision}"], path).and_return(["", "dumb http transport does not support shallow capabilities", fail_result]) + expect(git_proxy).to receive(:capture).with(["fetch", "--force", "--quiet", "--no-tags", "--", uri, "refs/heads/#{parsed_revision}:refs/heads/#{parsed_revision}"], path).and_return(["", "", clone_result]) + subject.checkout + end + end + end + end end diff --git a/spec/bundler/bundler/source/git_spec.rb b/spec/bundler/bundler/source/git_spec.rb index feef54bbf4..14e91c6bdc 100644 --- a/spec/bundler/bundler/source/git_spec.rb +++ b/spec/bundler/bundler/source/git_spec.rb @@ -70,4 +70,54 @@ RSpec.describe Bundler::Source::Git do end end end + + describe "#locked_revision_checked_out?" do + let(:revision) { "abc" } + let(:git_proxy_revision) { revision } + let(:git_proxy_installed) { true } + let(:git_proxy) { subject.send(:git_proxy) } + let(:options) do + { + "uri" => uri, + "revision" => revision, + } + end + + before do + allow(git_proxy).to receive(:revision).and_return(git_proxy_revision) + allow(git_proxy).to receive(:installed_to?).with(subject.install_path).and_return(git_proxy_installed) + end + + context "when the locked revision is checked out" do + it "returns true" do + expect(subject.send(:locked_revision_checked_out?)).to be true + end + end + + context "when no revision is provided" do + let(:options) do + { "uri" => uri } + end + + it "returns falsey value" do + expect(subject.send(:locked_revision_checked_out?)).to be_falsey + end + end + + context "when the git proxy revision is different than the git revision" do + let(:git_proxy_revision) { revision.next } + + it "returns falsey value" do + expect(subject.send(:locked_revision_checked_out?)).to be_falsey + end + end + + context "when the gem hasn't been installed" do + let(:git_proxy_installed) { false } + + it "returns falsey value" do + expect(subject.send(:locked_revision_checked_out?)).to be_falsey + 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 56f3bee459..27430d4a3b 100644 --- a/spec/bundler/bundler/source/rubygems/remote_spec.rb +++ b/spec/bundler/bundler/source/rubygems/remote_spec.rb @@ -106,8 +106,8 @@ RSpec.describe Bundler::Source::Rubygems::Remote do context "when a mirror with inline credentials is configured for the URI" do 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/") } + let(:mirror_uri_with_auth) { Gem::URI("https://username:password@example-mirror.rubygems.org/") } + let(:mirror_uri_no_auth) { Gem::URI("https://example-mirror.rubygems.org/") } before { Bundler.settings.temporary("mirror.https://rubygems.org/" => mirror_uri_with_auth.to_s) } @@ -132,8 +132,8 @@ RSpec.describe Bundler::Source::Rubygems::Remote do context "when a mirror with configured credentials is configured for the URI" do 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/") } + let(:mirror_uri_with_auth) { Gem::URI("https://#{credentials}@example-mirror.rubygems.org/") } + let(:mirror_uri_no_auth) { Gem::URI("https://example-mirror.rubygems.org/") } before do Bundler.settings.temporary("mirror.https://rubygems.org/" => mirror_uri_no_auth.to_s) @@ -169,4 +169,39 @@ RSpec.describe Bundler::Source::Rubygems::Remote do end end end + + describe "#cooldown" do + it "is nil by default" do + expect(remote(uri_no_auth).cooldown).to be_nil + end + + it "returns the value passed to the constructor" do + r = Bundler::Source::Rubygems::Remote.new(uri_no_auth, cooldown: 7) + expect(r.cooldown).to eq(7) + end + end + + describe "#effective_cooldown" do + it "returns the per-remote value when no override is set" do + r = Bundler::Source::Rubygems::Remote.new(uri_no_auth, cooldown: 7) + expect(r.effective_cooldown).to eq(7) + end + + it "returns nil when neither override nor per-remote value is set" do + expect(remote(uri_no_auth).effective_cooldown).to be_nil + end + + it "settings override per-remote value" do + r = Bundler::Source::Rubygems::Remote.new(uri_no_auth, cooldown: 7) + Bundler.settings.temporary(cooldown: 14) do + expect(r.effective_cooldown).to eq(14) + end + end + + it "settings override even when per-remote value is absent" do + Bundler.settings.temporary(cooldown: 14) do + expect(remote(uri_no_auth).effective_cooldown).to eq(14) + end + end + end end diff --git a/spec/bundler/bundler/source/rubygems_spec.rb b/spec/bundler/bundler/source/rubygems_spec.rb index 884fa81046..feb787498e 100644 --- a/spec/bundler/bundler/source/rubygems_spec.rb +++ b/spec/bundler/bundler/source/rubygems_spec.rb @@ -44,4 +44,61 @@ RSpec.describe Bundler::Source::Rubygems do end end end + + describe "#clear_cache" do + it "invalidates memoized indexes so subsequent reads rebuild them" do + source = described_class.new + + first_specs = source.specs + first_installed = source.send(:installed_specs) + first_default = source.send(:default_specs) + first_cached = source.send(:cached_specs) + + expect(source.specs).to equal(first_specs) + expect(source.send(:installed_specs)).to equal(first_installed) + expect(source.send(:default_specs)).to equal(first_default) + expect(source.send(:cached_specs)).to equal(first_cached) + + source.clear_cache + + expect(source.specs).not_to equal(first_specs) + expect(source.send(:installed_specs)).not_to equal(first_installed) + expect(source.send(:default_specs)).not_to equal(first_default) + expect(source.send(:cached_specs)).not_to equal(first_cached) + end + + it "reflects newly-discovered installed gems after clear_cache" do + source = described_class.new + foo = Gem::Specification.new("foo", "1.0.0") + bar = Gem::Specification.new("bar", "1.0.0") + + allow(Bundler.rubygems).to receive(:installed_specs).and_return([foo]) + expect(source.send(:installed_specs).search("bar")).to be_empty + + allow(Bundler.rubygems).to receive(:installed_specs).and_return([foo, bar]) + expect(source.send(:installed_specs).search("bar")).to be_empty + + source.clear_cache + + expect(source.send(:installed_specs).search("bar")).not_to be_empty + end + end + + describe "log debug information" do + it "log the time spent downloading and installing a gem" do + build_repo2 do + build_gem "warning" + end + + gemfile_content = <<~G + source "https://gem.repo2" + gem "warning" + G + + stdout = install_gemfile(gemfile_content, env: { "DEBUG" => "1" }) + + expect(stdout).to match(/Downloaded warning in: \d+\.\d+s/) + expect(stdout).to match(/Installed warning in: \d+\.\d+s/) + end + end end diff --git a/spec/bundler/bundler/source_list_spec.rb b/spec/bundler/bundler/source_list_spec.rb index 13453cb2a3..61bd99b063 100644 --- a/spec/bundler/bundler/source_list_spec.rb +++ b/spec/bundler/bundler/source_list_spec.rb @@ -11,7 +11,7 @@ RSpec.describe Bundler::SourceList do subject(:source_list) { Bundler::SourceList.new } - let(:rubygems_aggregate) { Bundler::Source::Rubygems.new } + let(:global_rubygems_source) { Bundler::Source::Rubygems.new } let(:metadata_source) { Bundler::Source::Metadata.new } describe "adding sources" do @@ -118,17 +118,23 @@ RSpec.describe Bundler::SourceList do describe "#add_global_rubygems_remote" do let!(:returned_source) { source_list.add_global_rubygems_remote("https://rubygems.org/") } - it "returns the aggregate rubygems source" do + it "returns the global rubygems source" do expect(returned_source).to be_instance_of(Bundler::Source::Rubygems) end - it "adds the provided remote to the beginning of the aggregate source" do + it "adds the provided remote to the beginning of the global source" do source_list.add_global_rubygems_remote("https://othersource.org") expect(returned_source.remotes).to eq [ Gem::URI("https://othersource.org/"), Gem::URI("https://rubygems.org/"), ] end + + it "records the per-remote cooldown when supplied" do + source_list.add_global_rubygems_remote("https://othersource.org", cooldown: 7) + expect(returned_source.cooldown_for(Gem::URI("https://othersource.org/"))).to eq(7) + expect(returned_source.cooldown_for(Gem::URI("https://rubygems.org/"))).to be_nil + end end describe "#add_plugin_source" do @@ -156,21 +162,21 @@ RSpec.describe Bundler::SourceList do end describe "#all_sources" do - it "includes the aggregate rubygems source when rubygems sources have been added" do + it "includes the global rubygems source when rubygems sources have been added" do source_list.add_git_source("uri" => "git://host/path.git") source_list.add_rubygems_source("remotes" => ["https://rubygems.org"]) source_list.add_path_source("path" => "/path/to/gem") source_list.add_plugin_source("new_source", "uri" => "https://some.url/a") - expect(source_list.all_sources).to include rubygems_aggregate + expect(source_list.all_sources).to include global_rubygems_source end - it "includes the aggregate rubygems source when no rubygems sources have been added" do + it "includes the global rubygems source when no rubygems sources have been added" do source_list.add_git_source("uri" => "git://host/path.git") source_list.add_path_source("path" => "/path/to/gem") source_list.add_plugin_source("new_source", "uri" => "https://some.url/a") - expect(source_list.all_sources).to include rubygems_aggregate + expect(source_list.all_sources).to include global_rubygems_source end it "returns sources of the same type in the reverse order that they were added" do @@ -204,7 +210,7 @@ RSpec.describe Bundler::SourceList do Bundler::Source::Rubygems.new("remotes" => ["https://third-rubygems.org"]), Bundler::Source::Rubygems.new("remotes" => ["https://fourth-rubygems.org"]), Bundler::Source::Rubygems.new("remotes" => ["https://fifth-rubygems.org"]), - rubygems_aggregate, + global_rubygems_source, metadata_source, ] end @@ -297,19 +303,19 @@ RSpec.describe Bundler::SourceList do end describe "#rubygems_sources" do - it "includes the aggregate rubygems source when rubygems sources have been added" do + it "includes the global rubygems source when rubygems sources have been added" do source_list.add_git_source("uri" => "git://host/path.git") source_list.add_rubygems_source("remotes" => ["https://rubygems.org"]) source_list.add_path_source("path" => "/path/to/gem") - expect(source_list.rubygems_sources).to include rubygems_aggregate + expect(source_list.rubygems_sources).to include global_rubygems_source end - it "returns only the aggregate rubygems source when no rubygems sources have been added" do + it "returns only the global rubygems source when no rubygems sources have been added" do source_list.add_git_source("uri" => "git://host/path.git") source_list.add_path_source("path" => "/path/to/gem") - expect(source_list.rubygems_sources).to eq [rubygems_aggregate] + expect(source_list.rubygems_sources).to eq [global_rubygems_source] end it "returns rubygems sources in the reverse order that they were added" do @@ -331,7 +337,7 @@ RSpec.describe Bundler::SourceList do Bundler::Source::Rubygems.new("remotes" => ["https://third-rubygems.org"]), Bundler::Source::Rubygems.new("remotes" => ["https://fourth-rubygems.org"]), Bundler::Source::Rubygems.new("remotes" => ["https://fifth-rubygems.org"]), - rubygems_aggregate, + global_rubygems_source, ] end end @@ -442,6 +448,16 @@ RSpec.describe Bundler::SourceList do end end + describe "#clear_cache" do + let(:rubygems_source) { source_list.add_rubygems_source("remotes" => ["https://rubygems.org"]) } + + it "calls #clear_cache on all rubygems sources" do + expect(rubygems_source).to receive(:clear_cache) + expect(source_list.global_rubygems_source).to receive(:clear_cache) + source_list.clear_cache + end + end + describe "implicit_global_source?" do context "when a global rubygem source provided" do it "returns a falsy value" do diff --git a/spec/bundler/bundler/source_spec.rb b/spec/bundler/bundler/source_spec.rb index 3b49c37431..01b57ce9e8 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: Gem::Platform::RUBY) } shared_examples_for "the lockfile specs are not relevant" do it "should return a string with the spec name and version" do @@ -70,7 +70,7 @@ RSpec.describe Bundler::Source do end context "with a more recent version" do - let(:spec) { double(:spec, name: "nokogiri", version: "1.6.1", platform: rb) } + let(:spec) { double(:spec, name: "nokogiri", version: "1.6.1", platform: Gem::Platform::RUBY) } let(:locked_gem) { double(:locked_gem, name: "nokogiri", version: "1.7.0") } context "with color", :no_color_tty do @@ -97,7 +97,7 @@ RSpec.describe Bundler::Source do end context "with an older version" do - let(:spec) { double(:spec, name: "nokogiri", version: "1.7.1", platform: rb) } + let(:spec) { double(:spec, name: "nokogiri", version: "1.7.1", platform: Gem::Platform::RUBY) } let(:locked_gem) { double(:locked_gem, name: "nokogiri", version: "1.7.0") } context "with color", :no_color_tty do diff --git a/spec/bundler/bundler/spec_set_spec.rb b/spec/bundler/bundler/spec_set_spec.rb index c4b6676223..1e1ceadf26 100644 --- a/spec/bundler/bundler/spec_set_spec.rb +++ b/spec/bundler/bundler/spec_set_spec.rb @@ -43,6 +43,30 @@ RSpec.describe Bundler::SpecSet do spec = described_class.new(specs).find_by_name_and_platform("b", platform) expect(spec).to eq platform_spec end + + it "returns nil when the name is not present" do + spec = described_class.new(specs).find_by_name_and_platform("missing", platform) + expect(spec).to be_nil + end + + it "returns nil when the name exists but no spec is installable on the requested platform" do + incompatible_platform = Gem::Platform.new("java") + incompatible_spec = build_spec("a", "1.0", incompatible_platform).first + + spec = described_class.new([incompatible_spec]).find_by_name_and_platform("a", platform) + expect(spec).to be_nil + end + + it "returns the first installable spec for the given name in insertion order" do + later_platform_spec = build_spec("b", "3.0", platform).first + specs = [ + platform_spec, + later_platform_spec, + ] + + spec = described_class.new(specs).find_by_name_and_platform("b", platform) + expect(spec).to eq platform_spec + end end describe "#to_a" do @@ -55,5 +79,70 @@ RSpec.describe Bundler::SpecSet do d-2.0 ] end + + it "puts rake first when present" do + specs = [ + build_spec("a", "1.0") {|s| s.dep "rake", ">= 0" }, + build_spec("rake", "13.0"), + ].flatten + + expect(described_class.new(specs).to_a.map(&:full_name)).to eq %w[ + rake-13.0 + a-1.0 + ] + end + end + + describe "#complete_platform" do + let(:platform) { Gem::Platform.new("x86_64-linux") } + + let(:platform_variant) do + build_spec("needs_old_ruby", "1.0", platform).first.tap do |s| + s.required_ruby_version = Gem::Requirement.new("< #{Gem.ruby_version}") + end + end + + let(:lazy_spec) do + lazy = Bundler::LazySpecification.new("needs_old_ruby", Gem::Version.new("1.0"), Gem::Platform::RUBY) + lazy.required_ruby_version = Gem::Requirement.new("< #{Gem.ruby_version}") + source = double("source") + source_specs = double("source_specs") + allow(source).to receive(:specs).and_return(source_specs) + allow(source_specs).to receive(:search). + with(["needs_old_ruby", Gem::Version.new("1.0")]).and_return([platform_variant]) + lazy.source = source + lazy + end + + it "rejects a platform variant whose strict metadata is incompatible when no override is attached" do + set = described_class.new([lazy_spec]) + expect(set.send(:complete_platform, platform)).to be(false) + end + + it "accepts a platform variant when the LazySpec carries an override that allows it" do + lazy_spec.overrides = [Bundler::Override.new("needs_old_ruby", :required_ruby_version, :ignore_upper)] + set = described_class.new([lazy_spec]) + expect(set.send(:complete_platform, platform)).to be(true) + end + + it "carries overrides onto a synthesized LazySpec so a follow-up complete_platform still honors them" do + override = Bundler::Override.new("needs_old_ruby", :required_ruby_version, :ignore_upper) + lazy_spec.overrides = [override] + second_platform = Gem::Platform.new("aarch64-linux") + second_variant = build_spec("needs_old_ruby", "1.0", second_platform).first.tap do |s| + s.required_ruby_version = Gem::Requirement.new("< #{Gem.ruby_version}") + end + allow(lazy_spec.source.specs).to receive(:search). + with(["needs_old_ruby", Gem::Version.new("1.0")]).and_return([platform_variant, second_variant]) + + set = described_class.new([lazy_spec]) + expect(set.send(:complete_platform, platform)).to be(true) + # The synthesized x86_64-linux variant is now in the set. If lookup + # picks it as exemplar for the next platform check, the override list + # must still be reachable via its overrides accessor. + synthesized = set.to_a.find {|s| s.platform == platform } + expect(synthesized.overrides).to eq([override]) + expect(set.send(:complete_platform, second_platform)).to be(true) + end end end diff --git a/spec/bundler/bundler/specifications/foo.gemspec b/spec/bundler/bundler/specifications/foo.gemspec index 81b77d0c86..19b7724e81 100644 --- a/spec/bundler/bundler/specifications/foo.gemspec +++ b/spec/bundler/bundler/specifications/foo.gemspec @@ -8,6 +8,6 @@ Gem::Specification.new do |s| s.version = "1.0.0" s.loaded_from = __FILE__ s.extensions = "ext/foo" - s.required_ruby_version = ">= 3.1.0" + s.required_ruby_version = ">= 3.2.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 beb966b3ce..f2faa2ea64 100644 --- a/spec/bundler/bundler/stub_specification_spec.rb +++ b/spec/bundler/bundler/stub_specification_spec.rb @@ -49,10 +49,14 @@ RSpec.describe Bundler::StubSpecification do expect(stub.missing_extensions?).to be false end - it "returns true if not manually_installed?" do + it "returns #{RUBY_ENGINE == "jruby" ? "false" : "true"} if not manually_installed?" do stub = described_class.from_stub(with_bundler_stub_spec) stub.installed_by_version = Gem::Version.new(1) - expect(stub.missing_extensions?).to be true + if RUBY_ENGINE == "jruby" + expect(stub.missing_extensions?).to be false + else + expect(stub.missing_extensions?).to be true + end end end diff --git a/spec/bundler/bundler/ui/shell_spec.rb b/spec/bundler/bundler/ui/shell_spec.rb index 422c850a65..83f147191e 100644 --- a/spec/bundler/bundler/ui/shell_spec.rb +++ b/spec/bundler/bundler/ui/shell_spec.rb @@ -81,4 +81,32 @@ RSpec.describe Bundler::UI::Shell do end end end + + describe "threads" do + it "is thread safe when using with_level" do + stop_thr1 = false + stop_thr2 = false + + expect(subject.level).to eq("debug") + + thr1 = Thread.new do + subject.silence do + sleep(0.1) until stop_thr1 + end + + stop_thr2 = true + end + + thr2 = Thread.new do + subject.silence do + stop_thr1 = true + sleep(0.1) until stop_thr2 + end + end + + [thr1, thr2].each(&:join) + + expect(subject.level).to eq("debug") + end + end end diff --git a/spec/bundler/bundler/uri_normalizer_spec.rb b/spec/bundler/bundler/uri_normalizer_spec.rb new file mode 100644 index 0000000000..1308e86014 --- /dev/null +++ b/spec/bundler/bundler/uri_normalizer_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +RSpec.describe Bundler::URINormalizer do + describe ".normalize_suffix" do + context "when trailing_slash is true" do + it "adds a trailing slash when missing" do + expect(described_class.normalize_suffix("https://example.com", trailing_slash: true)).to eq("https://example.com/") + end + + it "keeps the trailing slash when present" do + expect(described_class.normalize_suffix("https://example.com/", trailing_slash: true)).to eq("https://example.com/") + end + end + + context "when trailing_slash is false" do + it "removes a trailing slash when present" do + expect(described_class.normalize_suffix("https://example.com/", trailing_slash: false)).to eq("https://example.com") + end + + it "keeps the value unchanged when no trailing slash exists" do + expect(described_class.normalize_suffix("https://example.com", trailing_slash: false)).to eq("https://example.com") + end + end + end +end diff --git a/spec/bundler/bundler/worker_spec.rb b/spec/bundler/bundler/worker_spec.rb index e4ebbd2932..2ad2845e37 100644 --- a/spec/bundler/bundler/worker_spec.rb +++ b/spec/bundler/bundler/worker_spec.rb @@ -20,6 +20,26 @@ RSpec.describe Bundler::Worker do end end + describe "priority queue" do + it "process elements from the priority queue first" do + processed_elements = [] + + function = proc do |element, _| + processed_elements << element + end + + worker = described_class.new(1, "Spec Worker", function) + worker.instance_variable_set(:@threads, []) # Prevent the enqueueing from starting work. + worker.enq("Normal element") + worker.enq("Priority element", priority: true) + worker.send(:create_threads) + + worker.stop + + expect(processed_elements).to eq(["Priority element", "Normal element"]) + end + end + describe "handling interrupts" do let(:status) do pid = Process.fork do diff --git a/spec/bundler/bundler/yaml_serializer_spec.rb b/spec/bundler/bundler/yaml_serializer_spec.rb index de437f764a..9ff1579b76 100644 --- a/spec/bundler/bundler/yaml_serializer_spec.rb +++ b/spec/bundler/bundler/yaml_serializer_spec.rb @@ -112,10 +112,10 @@ RSpec.describe Bundler::YAMLSerializer do it "handles colon in key/value" do yaml = <<~YAML - BUNDLE_MIRROR__HTTPS://RUBYGEMS__ORG/: http://rubygems-mirror.org + BUNDLE_MIRROR__HTTPS://RUBYGEMS__ORG/: http://example-mirror.rubygems.org YAML - expect(serializer.load(yaml)).to eq("BUNDLE_MIRROR__HTTPS://RUBYGEMS__ORG/" => "http://rubygems-mirror.org") + expect(serializer.load(yaml)).to eq("BUNDLE_MIRROR__HTTPS://RUBYGEMS__ORG/" => "http://example-mirror.rubygems.org") end it "handles arrays inside hashes" do diff --git a/spec/bundler/cache/cache_path_spec.rb b/spec/bundler/cache/cache_path_spec.rb index d5bd14965b..2a280ea858 100644 --- a/spec/bundler/cache/cache_path_spec.rb +++ b/spec/bundler/cache/cache_path_spec.rb @@ -17,7 +17,7 @@ RSpec.describe "bundle package" do context "with config cache_path" do it "caches gems at given path" do - bundle "config set cache_path vendor/cache-foo" + bundle_config "cache_path vendor/cache-foo" bundle :cache expect(bundled_app("vendor/cache-foo/myrack-1.0.0.gem")).to exist end diff --git a/spec/bundler/cache/gems_spec.rb b/spec/bundler/cache/gems_spec.rb index c68b20225a..198279d84c 100644 --- a/spec/bundler/cache/gems_spec.rb +++ b/spec/bundler/cache/gems_spec.rb @@ -78,13 +78,13 @@ RSpec.describe "bundle cache" do end context "using system gems" do - before { bundle "config set path.system true" } + before { bundle_config "path.system true" } let(:path) { system_gem_path } it_behaves_like "when there are only gemsources" end context "installing into a local path" do - before { bundle "config set path ./.bundle" } + before { bundle_config "path ./.bundle" } let(:path) { local_gem_path } it_behaves_like "when there are only gemsources" end @@ -136,7 +136,7 @@ RSpec.describe "bundle cache" do gem "json" G - bundle "config set cache_all_platforms true" + bundle_config "cache_all_platforms true" bundle :cache expect(bundled_app("vendor/cache/json-#{default_json_version}.gem")).to exist @@ -157,14 +157,14 @@ RSpec.describe "bundle cache" do context "when a remote gem is not available for caching" do it "warns, but uses builtin gems when installing to system gems" do - bundle "config set path.system true" + bundle_config "path.system true" install_gemfile %(source "https://gem.repo1"; gem 'json', '#{default_json_version}'), verbose: true expect(err).to include("json-#{default_json_version} is built in to Ruby, and can't be cached") expect(out).to include("Using json #{default_json_version}") end it "errors when explicitly caching" do - bundle "config set path.system true" + bundle_config "path.system true" install_gemfile <<-G source "https://gem.repo1" @@ -226,7 +226,7 @@ RSpec.describe "bundle cache" do it "re-caches during install" do setup_main_repo - cached_gem("myrack-1.0.0").rmtree + FileUtils.rm_rf cached_gem("myrack-1.0.0") bundle :install expect(out).to include("Updating files in vendor/cache") expect(cached_gem("myrack-1.0.0")).to exist @@ -291,7 +291,7 @@ RSpec.describe "bundle cache" do expect(cached_gem("platform_specific-1.0-java")).to exist end - simulate_new_machine + pristine_system_gems simulate_platform "x86-darwin-100" do install_gemfile <<-G @@ -307,12 +307,13 @@ RSpec.describe "bundle cache" do it "doesn't remove gems cached gems that don't match their remote counterparts, but also refuses to install and prints an error" do setup_main_repo cached_myrack = cached_gem("myrack-1.0.0") - cached_myrack.rmtree + FileUtils.rm_rf cached_myrack build_gem "myrack", "1.0.0", path: cached_myrack.parent, rubygems_version: "1.3.2" - simulate_new_machine + FileUtils.rm_r default_bundle_path + default_system_gems FileUtils.rm bundled_app_lock bundle :install, raise_on_error: false @@ -337,14 +338,15 @@ RSpec.describe "bundle cache" do it "raises an error when a cached gem is altered and produces a different checksum than the remote gem" do setup_main_repo - cached_gem("myrack-1.0.0").rmtree + FileUtils.rm_rf cached_gem("myrack-1.0.0") build_gem "myrack", "1.0.0", path: bundled_app("vendor/cache") checksums = checksums_section do |c| c.checksum gem_repo1, "myrack", "1.0.0" end - simulate_new_machine + FileUtils.rm_r default_bundle_path + default_system_gems lockfile <<-L GEM @@ -360,16 +362,16 @@ RSpec.describe "bundle cache" do expect(err).to include("1. remove the gem at #{cached_gem("myrack-1.0.0")}") expect(cached_gem("myrack-1.0.0")).to exist - cached_gem("myrack-1.0.0").rmtree + FileUtils.rm_rf cached_gem("myrack-1.0.0") bundle :install expect(cached_gem("myrack-1.0.0")).to exist end it "installs a modified gem with a non-matching checksum when the API implementation does not provide checksums" do setup_main_repo - cached_gem("myrack-1.0.0").rmtree + FileUtils.rm_rf cached_gem("myrack-1.0.0") build_gem "myrack", "1.0.0", path: bundled_app("vendor/cache") - simulate_new_machine + pristine_system_gems lockfile <<-L GEM diff --git a/spec/bundler/cache/git_spec.rb b/spec/bundler/cache/git_spec.rb index 38333c0219..f0976ecac7 100644 --- a/spec/bundler/cache/git_spec.rb +++ b/spec/bundler/cache/git_spec.rb @@ -13,6 +13,22 @@ RSpec.describe "git base name" do end RSpec.describe "bundle cache with git" do + it "does not copy repository to vendor cache when cache_all set to false" do + git = build_git "foo" + ref = git.ref_for("main", 11) + + install_gemfile <<-G + source "https://gem.repo1" + gem "foo", :git => '#{lib_path("foo-1.0")}' + G + + bundle_config "cache_all false" + bundle :cache + expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).not_to exist + + expect(the_bundle).to include_gems "foo 1.0" + end + it "copies repository to vendor cache and uses it" do git = build_git "foo" ref = git.ref_for("main", 11) @@ -22,13 +38,12 @@ RSpec.describe "bundle cache with git" do gem "foo", :git => '#{lib_path("foo-1.0")}' G - bundle "config set cache_all true" bundle :cache expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist expect(bundled_app("vendor/cache/foo-1.0-#{ref}/.git")).not_to exist expect(bundled_app("vendor/cache/foo-1.0-#{ref}/.bundlecache")).to be_file - FileUtils.rm_rf lib_path("foo-1.0") + FileUtils.rm_r lib_path("foo-1.0") expect(the_bundle).to include_gems "foo 1.0" end @@ -41,15 +56,14 @@ RSpec.describe "bundle cache with git" do gem "foo", :git => '#{lib_path("foo-1.0")}' G - bundle "config set --local path vendor/bundle" + bundle_config "path vendor/bundle" bundle "install" - bundle "config set cache_all true" bundle :cache expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist expect(bundled_app("vendor/cache/foo-1.0-#{ref}/.git")).not_to exist - FileUtils.rm_rf lib_path("foo-1.0") + FileUtils.rm_r lib_path("foo-1.0") expect(the_bundle).to include_gems "foo 1.0" end @@ -61,12 +75,11 @@ RSpec.describe "bundle cache with git" do gem "foo", :git => '#{lib_path("foo-1.0")}' G - bundle "config set cache_all true" bundle :cache bundle :cache expect(out).to include "Updating files in vendor/cache" - FileUtils.rm_rf lib_path("foo-1.0") + FileUtils.rm_r lib_path("foo-1.0") expect(the_bundle).to include_gems "foo 1.0" end @@ -79,7 +92,6 @@ RSpec.describe "bundle cache with git" do gem "foo", :git => '#{lib_path("foo-1.0")}' G - bundle "config set cache_all true" bundle :cache update_git "foo" do |s| @@ -95,7 +107,7 @@ RSpec.describe "bundle cache with git" do expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist expect(bundled_app("vendor/cache/foo-1.0-#{old_ref}")).not_to exist - FileUtils.rm_rf lib_path("foo-1.0") + FileUtils.rm_r lib_path("foo-1.0") run "require 'foo'" expect(out).to eq("CACHE") end @@ -109,7 +121,6 @@ RSpec.describe "bundle cache with git" do gem "foo", :git => '#{lib_path("foo-1.0")}' G - bundle "config set cache_all true" bundle :cache update_git "foo" do |s| @@ -124,7 +135,7 @@ RSpec.describe "bundle cache with git" do expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist expect(bundled_app("vendor/cache/foo-1.0-#{old_ref}")).not_to exist - FileUtils.rm_rf lib_path("foo-1.0") + FileUtils.rm_r lib_path("foo-1.0") run "require 'foo'" expect(out).to eq("CACHE") end @@ -140,7 +151,6 @@ RSpec.describe "bundle cache with git" do bundle %(config set local.foo #{lib_path("foo-1.0")}) bundle "install" - bundle "config set cache_all true" bundle :cache expect(bundled_app("vendor/cache/foo-invalid-#{ref}")).to exist @@ -161,12 +171,12 @@ RSpec.describe "bundle cache with git" do source "https://gem.repo1" gem "foo", :git => '#{lib_path("foo-1.0")}' G - bundle "config set path vendor/bundle" + bundle_config "path vendor/bundle" bundle :install - simulate_new_machine + pristine_system_gems with_path_as "" do - bundle "config set deployment true" + bundle_config "deployment true" bundle "install --local" expect(the_bundle).to include_gem "foo 1.0" end @@ -179,13 +189,10 @@ RSpec.describe "bundle cache with git" do source "https://gem.repo1" gem "foo", :git => '#{lib_path("foo-1.0")}' G - bundle "config set cache_all true" bundle :cache, "all-platforms" => true - FileUtils.rm_rf Dir.glob(default_bundle_path("bundler/gems/extensions/**/foo-1.0-*")).first.to_s - FileUtils.rm_rf Dir.glob(default_bundle_path("bundler/gems/foo-1.0-*")).first.to_s - simulate_new_machine - bundle "config set frozen true" + pristine_system_gems + bundle_config "frozen true" bundle "install --local --verbose" expect(out).to_not include("Fetching") expect(the_bundle).to include_gem "foo 1.0" @@ -198,14 +205,10 @@ RSpec.describe "bundle cache with git" do source "https://gem.repo1" gem "foo", :git => '#{lib_path("foo-1.0")}' G - bundle "config set cache_all true" bundle :cache, "all-platforms" => true - FileUtils.rm_rf Dir.glob(default_bundle_path("bundler/gems/extensions/**/foo-1.0-*")).first.to_s - FileUtils.rm_rf Dir.glob(default_bundle_path("bundler/gems/foo-1.0-*")).first.to_s - simulate_new_machine - bundle "config set frozen true" - FileUtils.rm_rf "#{default_bundle_path}/cache/bundler/git/foo-1.0-*" + pristine_system_gems + bundle_config "frozen true" bundle "install --local --verbose" expect(out).to_not include("Fetching") expect(the_bundle).to include_gem "foo 1.0" @@ -218,14 +221,10 @@ RSpec.describe "bundle cache with git" do source "https://gem.repo1" gem "foo", :git => '#{lib_path("foo-1.0")}' G - bundle "config set cache_all true" bundle :cache, "all-platforms" => true - FileUtils.rm_rf Dir.glob(default_bundle_path("bundler/gems/extensions/**/foo-1.0-*")).first.to_s - FileUtils.rm_rf Dir.glob(default_bundle_path("bundler/gems/foo-1.0-*")).first.to_s - simulate_new_machine - bundle "config set frozen true" - FileUtils.rm_rf "#{default_bundle_path}/cache/bundler/git/foo-1.0-*" + pristine_system_gems + bundle_config "frozen true" # Remove untracked files (including the empty refs dir in the cache) Dir.chdir(bundled_app) do @@ -249,15 +248,14 @@ RSpec.describe "bundle cache with git" do source "https://gem.repo1" gem "foo", :git => '#{lib_path("foo-1.0")}' G - bundle "config set global_gem_cache false" - bundle "config set cache_all true" - bundle "config path vendor/bundle" + bundle_config "global_gem_cache false" + bundle_config "path vendor/bundle" bundle :install # Simulate old cache by copying the real cache folder to vendor/cache FileUtils.mkdir_p bundled_app("vendor/cache") FileUtils.cp_r "#{Dir.glob(vendored_gems("cache/bundler/git/foo-1.0-*")).first}/.", cache_dir - FileUtils.rm_rf bundled_app("vendor/bundle") + FileUtils.rm_r bundled_app("vendor/bundle") bundle "install --local --verbose" expect(err).to include("Installing from cache in old \"bare repository\" format for compatibility") @@ -281,15 +279,14 @@ RSpec.describe "bundle cache with git" do source "https://gem.repo1" gem "foo", :git => '#{lib_path("foo-1.0")}' G - bundle "config set global_gem_cache false" - bundle "config set cache_all true" - bundle "config path vendor/bundle" + bundle_config "global_gem_cache false" + bundle_config "path vendor/bundle" bundle :install # Simulate old cache by copying the real cache folder to vendor/cache FileUtils.mkdir_p bundled_app("vendor/cache") FileUtils.cp_r "#{Dir.glob(vendored_gems("cache/bundler/git/foo-1.0-*")).first}/.", cache_dir - FileUtils.rm_rf bundled_app("vendor/bundle") + FileUtils.rm_r bundled_app("vendor/bundle") bundle "install --verbose" expect(out).to include("Fetching") @@ -311,9 +308,8 @@ RSpec.describe "bundle cache with git" do source "https://gem.repo1" gem "foo", :git => '#{lib_path("foo-1.0")}' G - bundle "config set global_gem_cache false" - bundle "config set cache_all true" - bundle "config path vendor/bundle" + bundle_config "global_gem_cache false" + bundle_config "path vendor/bundle" bundle :install # Simulate old cache by copying the real cache folder to vendor/cache @@ -350,7 +346,6 @@ RSpec.describe "bundle cache with git" do G ref = git.ref_for("main", 11) - bundle "config set cache_all true" bundle :cache expect(bundled_app("vendor/cache/has_submodule-1.0-#{ref}")).to exist @@ -370,7 +365,6 @@ RSpec.describe "bundle cache with git" do source "https://gem.repo1" gem "foo", :git => '#{lib_path("foo-1.0")}' G - bundle "config set cache_all true" bundle :cache ref = git.ref_for("main", 11) @@ -385,12 +379,11 @@ RSpec.describe "bundle cache with git" do source "https://gem.repo1" gem "foo", :git => '#{lib_path("foo-1.0")}' G - bundle "config set cache_all true" bundle :cache, "all-platforms" => true, :install => false - simulate_new_machine + pristine_system_gems with_path_as "" do - bundle "config set deployment true" + bundle_config "deployment true" bundle :install, local: true expect(the_bundle).to include_gem "foo 1.0" end @@ -425,13 +418,13 @@ RSpec.describe "bundle cache with git" do foo! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L # Simulate an old incorrect situation where vendor/cache would be the install location of git gems FileUtils.mkdir_p bundled_app("vendor/cache") FileUtils.cp_r git_path, bundled_app("vendor/cache/foo-1.0-#{path_revision}") - FileUtils.rm_rf bundled_app("vendor/cache/foo-1.0-#{path_revision}/.git") + FileUtils.rm_r bundled_app("vendor/cache/foo-1.0-#{path_revision}/.git") bundle :install, env: { "BUNDLE_DEPLOYMENT" => "true", "BUNDLE_CACHE_ALL" => "true" } end @@ -444,7 +437,6 @@ RSpec.describe "bundle cache with git" do source "https://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 @@ -460,7 +452,7 @@ RSpec.describe "bundle cache with git" 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) + # nor in .bundle (bundler 4 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 @@ -506,7 +498,6 @@ RSpec.describe "bundle cache with git" do end FileUtils.mkdir_p(bundled_app("vendor/cache")) - bundle "config set cache_all all" install_gemfile <<-G source "https://gem.repo1" diff --git a/spec/bundler/cache/path_spec.rb b/spec/bundler/cache/path_spec.rb index 966cb6f531..42648aea1f 100644 --- a/spec/bundler/cache/path_spec.rb +++ b/spec/bundler/cache/path_spec.rb @@ -9,7 +9,6 @@ RSpec.describe "bundle cache with path" do gem "foo", :path => '#{bundled_app("lib/foo")}' G - bundle "config set cache_all true" bundle :cache expect(bundled_app("vendor/cache/foo-1.0")).not_to exist expect(the_bundle).to include_gems "foo 1.0" @@ -23,7 +22,6 @@ RSpec.describe "bundle cache with path" do gem "foo", :path => '#{lib_path("foo-1.0")}' G - bundle "config set cache_all true" bundle :cache expect(bundled_app("vendor/cache/foo-1.0")).to exist expect(bundled_app("vendor/cache/foo-1.0/.bundlecache")).to be_file @@ -42,7 +40,6 @@ RSpec.describe "bundle cache with path" do gem "#{libname}", :path => '#{libpath}' G - bundle "config set cache_all true" bundle :cache expect(bundled_app("vendor/cache/#{libname}")).to exist expect(bundled_app("vendor/cache/#{libname}/.bundlecache")).to be_file @@ -58,7 +55,6 @@ RSpec.describe "bundle cache with path" do gem "foo", :path => '#{lib_path("foo-1.0")}' G - bundle "config set cache_all true" bundle :cache build_lib "foo" do |s| @@ -81,7 +77,6 @@ RSpec.describe "bundle cache with path" do gem "foo", :path => '#{lib_path("foo-1.0")}' G - bundle "config set cache_all true" bundle :cache expect(bundled_app("vendor/cache/foo-1.0")).to exist @@ -97,20 +92,21 @@ 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 if cache_all is set to false" do build_lib "foo" install_gemfile <<-G source "https://gem.repo1" gem "foo", :path => '#{lib_path("foo-1.0")}' G + bundle_config "cache_all false" bundle :cache expect(err).to be_empty 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" do build_lib "foo" install_gemfile <<-G @@ -122,48 +118,4 @@ RSpec.describe "bundle cache with path" do expect(err).to be_empty expect(bundled_app("vendor/cache/foo-1.0")).to exist end - - it "stores the given flag" do - build_lib "foo" - - install_gemfile <<-G - source "https://gem.repo1" - gem "foo", :path => '#{lib_path("foo-1.0")}' - G - - bundle "config set cache_all true" - bundle :cache - build_lib "bar" - - install_gemfile <<-G - source "https://gem.repo1" - gem "foo", :path => '#{lib_path("foo-1.0")}' - gem "bar", :path => '#{lib_path("bar-1.0")}' - G - - bundle :cache - expect(bundled_app("vendor/cache/bar-1.0")).to exist - end - - it "can rewind chosen configuration" do - build_lib "foo" - - install_gemfile <<-G - source "https://gem.repo1" - gem "foo", :path => '#{lib_path("foo-1.0")}' - G - - bundle "config set cache_all true" - bundle :cache - build_lib "baz" - - gemfile <<-G - source "https://gem.repo1" - gem "foo", :path => '#{lib_path("foo-1.0")}' - gem "baz", :path => '#{lib_path("baz-1.0")}' - G - - 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/commands/add_spec.rb b/spec/bundler/commands/add_spec.rb index e63b0b9658..162650f2e5 100644 --- a/spec/bundler/commands/add_spec.rb +++ b/spec/bundler/commands/add_spec.rb @@ -38,34 +38,34 @@ RSpec.describe "bundle add" do end describe "without version specified" do - it "version requirement becomes ~> major.minor.patch when resolved version is < 1.0" do + it "version requirement becomes >= major.minor.patch when resolved version is < 1.0" do bundle "add 'bar'" - expect(bundled_app_gemfile.read).to match(/gem "bar", "~> 0.12.3"/) + expect(bundled_app_gemfile.read).to match(/gem "bar", ">= 0.12.3"/) expect(the_bundle).to include_gems "bar 0.12.3" end - it "version requirement becomes ~> major.minor when resolved version is > 1.0" do + it "version requirement becomes >= major.minor when resolved version is > 1.0" do bundle "add 'baz'" - expect(bundled_app_gemfile.read).to match(/gem "baz", "~> 1.2"/) + expect(bundled_app_gemfile.read).to match(/gem "baz", ">= 1.2"/) expect(the_bundle).to include_gems "baz 1.2.3" end - it "version requirement becomes ~> major.minor.patch.pre when resolved version is < 1.0" do + it "version requirement becomes >= major.minor.patch.pre when resolved version is < 1.0" do bundle "add 'cat'" - expect(bundled_app_gemfile.read).to match(/gem "cat", "~> 0.12.3.pre"/) + expect(bundled_app_gemfile.read).to match(/gem "cat", ">= 0.12.3.pre"/) expect(the_bundle).to include_gems "cat 0.12.3.pre" end - it "version requirement becomes ~> major.minor.pre when resolved version is > 1.0.pre" do + it "version requirement becomes >= major.minor.pre when resolved version is >= 1.0.pre" do bundle "add 'dog'" - expect(bundled_app_gemfile.read).to match(/gem "dog", "~> 1.1.pre"/) + 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 + 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(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 @@ -88,25 +88,25 @@ RSpec.describe "bundle add" do 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"}) + 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/) + 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'" - expect(bundled_app_gemfile.read).to match(/gem "foo", "~> 2.0", :group => :development/) + expect(bundled_app_gemfile.read).to match(/gem "foo", ">= 2.0", group: :development/) expect(the_bundle).to include_gems "foo 2.0" end it "adds dependency to more than one group" do bundle "add 'foo' --group='development, test'" - expect(bundled_app_gemfile.read).to match(/gem "foo", "~> 2.0", :groups => \[:development, :test\]/) + expect(bundled_app_gemfile.read).to match(/gem "foo", ">= 2.0", groups: \[:development, :test\]/) expect(the_bundle).to include_gems "foo 2.0" end end @@ -115,7 +115,7 @@ RSpec.describe "bundle add" do it "adds dependency with specified source" do bundle "add 'foo' --source='https://gem.repo2'" - expect(bundled_app_gemfile.read).to match(%r{gem "foo", "~> 2.0", :source => "https://gem.repo2"}) + expect(bundled_app_gemfile.read).to match(%r{gem "foo", ">= 2.0", source: "https://gem.repo2"}) expect(the_bundle).to include_gems "foo 2.0" end end @@ -124,7 +124,7 @@ RSpec.describe "bundle add" 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(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 @@ -133,7 +133,7 @@ RSpec.describe "bundle add" 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")}"/) + expect(bundled_app_gemfile.read).to match(/gem "foo", ">= 2.0", git: "#{lib_path("foo-2.0")}"/) expect(the_bundle).to include_gems "foo 2.0" end end @@ -146,7 +146,7 @@ RSpec.describe "bundle add" 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"/) + expect(bundled_app_gemfile.read).to match(/gem "foo", ">= 2.0", git: "#{lib_path("foo-2.0")}", branch: "test"/) expect(the_bundle).to include_gems "foo 2.0" end end @@ -155,32 +155,53 @@ RSpec.describe "bundle add" 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(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 + before do + build_git "rake", "13.0" + git("config --global url.file://#{lib_path("rake-13.0")}.insteadOf https://github.com/ruby/rake.git") + end + it "adds dependency with specified github source" do bundle "add rake --github=ruby/rake" - expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", :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" do - bundle "add rake --github=ruby/rake --branch=master" + bundle "add rake --github=ruby/rake --branch=main" - expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", :github => "ruby\/rake", :branch => "master"}) + expect(bundled_app_gemfile.read).to match(%r{gem "rake", ">= 13\.\d+", github: "ruby\/rake", branch: "main"}) end - end - describe "with --github and --ref" do it "adds dependency with specified github source and ref" do - bundle "add rake --github=ruby/rake --ref=5c60da8" + ref = revision_for(lib_path("rake-13.0")) + bundle "add rake --github=ruby/rake --ref=#{ref}" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", ">= 13\.\d+", github: "ruby\/rake", ref: "#{ref}"}) + end + + it "adds dependency with specified github source and glob" do + bundle "add rake --github=ruby/rake --glob='./*.gemspec'" - expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", :github => "ruby\/rake", :ref => "5c60da8"}) + expect(bundled_app_gemfile.read).to match(%r{gem "rake", ">= 13\.\d+", github: "ruby\/rake", glob: "\.\/\*\.gemspec"}) + end + + it "adds dependency with specified github source, branch and glob" do + bundle "add rake --github=ruby/rake --branch=main --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", ">= 13\.\d+", github: "ruby\/rake", branch: "main", glob: "\.\/\*\.gemspec"}) + end + + it "adds dependency with specified github source, ref and glob" do + ref = revision_for(lib_path("rake-13.0")) + bundle "add rake --github=ruby/rake --ref=#{ref} --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", ">= 13\.\d+", github: "ruby\/rake", ref: "#{ref}", glob: "\.\/\*\.gemspec"}) end end @@ -188,7 +209,7 @@ RSpec.describe "bundle add" 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(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 @@ -201,7 +222,7 @@ RSpec.describe "bundle add" do 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(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 @@ -210,32 +231,42 @@ RSpec.describe "bundle add" 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(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" do - bundle "add rake --github=ruby/rake --glob='./*.gemspec'" + describe "with mismatched pair in --git/--github, --branch/--ref" do + describe "with --git and --github" do + it "throws error" do + bundle "add 'foo' --git x --github y", raise_on_error: false - expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", :github => "ruby\/rake", :glob => "\.\/\*\.gemspec"}) + expect(err).to include("You cannot specify `--git` and `--github` at the same time.") + end end - end - describe "with --github and --branch --and glob" do - it "adds dependency with specified github source and branch" do - bundle "add rake --github=ruby/rake --branch=master --glob='./*.gemspec'" + describe "with --branch and --ref with --git" do + it "throws error" do + bundle "add 'foo' --branch x --ref y --git file://git", raise_on_error: false - expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", :github => "ruby\/rake", :branch => "master", :glob => "\.\/\*\.gemspec"}) + expect(err).to include("You cannot specify `--branch` and `--ref` at the same time.") + end end - end - describe "with --github and --ref and --glob" do - it "adds dependency with specified github source and ref" do - bundle "add rake --github=ruby/rake --ref=5c60da8 --glob='./*.gemspec'" + describe "with --branch but without --git or --github" do + it "throws error" do + bundle "add 'foo' --branch x", raise_on_error: false - expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", :github => "ruby\/rake", :ref => "5c60da8", :glob => "\.\/\*\.gemspec"}) + expect(err).to include("You cannot specify `--branch` unless `--git` or `--github` is specified.") + end + end + + describe "with --ref but without --git or --github" do + it "throws error" do + bundle "add 'foo' --ref y", raise_on_error: false + + expect(err).to include("You cannot specify `--ref` unless `--git` or `--github` is specified.") + end end end @@ -250,7 +281,7 @@ RSpec.describe "bundle add" do it "using combination of short form options works like long form" do bundle "add 'foo' -s='https://gem.repo2' -g='development' -v='~>1.0'" - expect(bundled_app_gemfile.read).to include %(gem "foo", "~> 1.0", :group => :development, :source => "https://gem.repo2") + expect(bundled_app_gemfile.read).to include %(gem "foo", "~> 1.0", group: :development, source: "https://gem.repo2") expect(the_bundle).to include_gems "foo 1.1" end @@ -260,7 +291,7 @@ RSpec.describe "bundle add" do end it "shows error message when gem cannot be found" do - bundle "config set force_ruby_platform true" + bundle_config "force_ruby_platform true" bundle "add 'werk_it'", raise_on_error: false expect(err).to match("Could not find gem 'werk_it' in") @@ -277,13 +308,21 @@ RSpec.describe "bundle add" do end describe "with --optimistic" do - it "adds optimistic version" do + it "ignores option" do bundle "add 'foo' --optimistic" expect(bundled_app_gemfile.read).to include %(gem "foo", ">= 2.0") expect(the_bundle).to include_gems "foo 2.0" end end + describe "with --pessimistic" do + it "adds pessimistic version" do + bundle "add 'foo' --pessimistic" + expect(bundled_app_gemfile.read).to include %(gem "foo", "~> 2.0") + expect(the_bundle).to include_gems "foo 2.0" + end + end + describe "with --quiet option" do it "is quiet when there are no warnings" do bundle "add 'foo' --quiet" @@ -325,18 +364,18 @@ RSpec.describe "bundle add" do end describe "with no option" do - it "adds pessimistic version" do + it "adds optimistic version" do bundle "add 'foo'" - expect(bundled_app_gemfile.read).to include %(gem "foo", "~> 2.0") + expect(bundled_app_gemfile.read).to include %(gem "foo", ">= 2.0") expect(the_bundle).to include_gems "foo 2.0" end end - describe "with --optimistic and --strict" do + describe "with --pessimistic and --strict" do it "throws error" do - bundle "add 'foo' --strict --optimistic", raise_on_error: false + bundle "add 'foo' --strict --pessimistic", raise_on_error: false - expect(err).to include("You cannot specify `--strict` and `--optimistic` at the same time") + expect(err).to include("You cannot specify `--strict` and `--pessimistic` at the same time") end end @@ -344,8 +383,8 @@ RSpec.describe "bundle add" do it "adds multiple gems to gemfile" do bundle "add bar baz" - expect(bundled_app_gemfile.read).to match(/gem "bar", "~> 0.12.3"/) - expect(bundled_app_gemfile.read).to match(/gem "baz", "~> 1.2"/) + expect(bundled_app_gemfile.read).to match(/gem "bar", ">= 0.12.3"/) + expect(bundled_app_gemfile.read).to match(/gem "baz", ">= 1.2"/) end it "throws error if any of the specified gems are present in the gemfile with different version" do diff --git a/spec/bundler/commands/binstubs_spec.rb b/spec/bundler/commands/binstubs_spec.rb index 4da6dea16e..af4d24a9e8 100644 --- a/spec/bundler/commands/binstubs_spec.rb +++ b/spec/bundler/commands/binstubs_spec.rb @@ -85,220 +85,6 @@ RSpec.describe "bundle binstubs <gem>" do expect(err).to include("Cannot specify --all with specific gems") end - context "when generating bundle binstub outside bundler" do - it "should abort" do - install_gemfile <<-G - source "https://gem.repo1" - gem "myrack" - G - - bundle "binstubs myrack" - - File.open(bundled_app("bin/bundle"), "wb") do |file| - file.print "OMG" - end - - sys_exec "bin/myrackup", raise_on_error: false - - expect(err).to include("was not generated by Bundler") - end - end - - context "the bundle binstub" do - before do - pristine_system_gems "bundler-#{system_bundler_version}" - build_repo2 do - build_gem "myrack", "1.2" do |s| - s.executables = "myrackup" - end - - build_gem "prints_loaded_gems", "1.0" do |s| - s.executables = "print_loaded_gems" - s.bindir = "exe" - s.write "exe/print_loaded_gems", <<~R - #!/usr/bin/env ruby - specs = Gem.loaded_specs.values.reject {|s| s.default_gem? } - puts specs.map(&:full_name).sort.inspect - R - end - - build_bundler locked_bundler_version - end - install_gemfile <<-G - source "https://gem.repo2" - gem "myrack" - gem "prints_loaded_gems" - G - bundle "binstubs bundler myrack prints_loaded_gems" - end - - let(:system_bundler_version) { Bundler::VERSION } - let(:locked_bundler_version) { nil } - let(:lockfile_content) { lockfile.gsub(system_bundler_version, locked_bundler_version) } - - it "runs bundler" do - bundle "install --verbose", bundle_bin: "bin/bundle" - 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 - bundle "install", env: { "BUNDLER_VERSION" => "999.999.999" }, raise_on_error: false, bundle_bin: "bin/bundle" - 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'`") - end - - 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" - - bundle "install --verbose", env: { "BUNDLER_VERSION" => "999.999.998" }, raise_on_error: false, bundle_bin: "bin/bundle" - expect(out).to include %(Using bundler 999.999.998\n) - end - end - - context "when a lockfile exists with a locked bundler version" do - let(:locked_bundler_version) { "999.999" } - - context "and the version is newer" do - before do - lockfile lockfile_content - end - - it "runs the correct version of bundler" do - bundle "install", raise_on_error: false, bundle_bin: "bin/bundle" - 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_content - end - - it "runs the correct version of bundler" do - bundle "install", env: { "BUNDLE_GEMFILE" => "gems.rb" }, raise_on_error: false, bundle_bin: "bin/bundle" - - 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 older and a different major" do - let(:system_bundler_version) { "55" } - let(:locked_bundler_version) { "44" } - - before do - lockfile lockfile_content - end - - it "runs the correct version of bundler" do - bundle "install", raise_on_error: false, bundle_bin: "bin/bundle" - 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" } - let(:locked_bundler_version) { "44" } - - before do - gemfile bundled_app("gems.rb"), gemfile - lockfile bundled_app("gems.locked"), lockfile_content - end - - it "runs the correct version of bundler" do - bundle "install", env: { "BUNDLE_GEMFILE" => "gems.rb" }, raise_on_error: false, bundle_bin: "bin/bundle" - 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 the same major" do - let(:system_bundler_version) { "2.999.999" } - let(:locked_bundler_version) { "2.3.0" } - - before do - lockfile lockfile_content - end - - it "installs and runs the exact version of bundler" do - bundle "install --verbose", bundle_bin: "bin/bundle" - expect(exitstatus).not_to eq(42) - 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 - end - - context "and the version is a pre-releaser" do - let(:system_bundler_version) { "55" } - let(:locked_bundler_version) { "2.12.0.a" } - - before do - lockfile lockfile_content - end - - it "runs the correct version of bundler when the version is a pre-release" do - bundle "install", raise_on_error: false, bundle_bin: "bin/bundle" - 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'`") - end - end - end - - context "when update --bundler is called" do - it "calls through to the latest bundler version" do - bundle "update --bundler --verbose", bundle_bin: "bin/bundle" - 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 - bundle "update --bundler=999.999.999", raise_on_error: false, bundle_bin: "bin/bundle" - 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'`") - end - end - - context "without a lockfile" do - it "falls back to the latest installed bundler" do - FileUtils.rm bundled_app_lock - bundle "install --verbose", bundle_bin: "bin/bundle" - expect(out).to include "Using bundler #{system_bundler_version}\n" - end - end - - context "using another binstub" do - it "loads all gems" do - sys_exec bundled_app("bin/print_loaded_gems").to_s - expect(out).to eq %(["bundler-#{Bundler::VERSION}", "myrack-1.2", "prints_loaded_gems-1.0"]) - end - - context "when requesting a different bundler version" do - before { lockfile lockfile.gsub(Bundler::VERSION, "999.999.999") } - - it "attempts to load that version" do - sys_exec bundled_app("bin/myrackup").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'`") - end - end - end - end - it "installs binstubs from git gems" do FileUtils.mkdir_p(lib_path("foo/bin")) FileUtils.touch(lib_path("foo/bin/foo")) @@ -370,29 +156,20 @@ RSpec.describe "bundle binstubs <gem>" do end end - context "--path" do - it "sets the binstubs dir" do - install_gemfile <<-G - source "https://gem.repo1" - gem "myrack" - G - - bundle "binstubs myrack --path exec" - - expect(bundled_app("exec/myrackup")).to exist + context "with the binstubs dir configured" do + before do + bundle_config "bin exec" end - it "setting is saved for bundle install", bundler: "< 3" do + it "creates the binstubs in the configured dir" do install_gemfile <<-G source "https://gem.repo1" gem "myrack" - gem "rails" G - bundle "binstubs myrack", path: "exec" - bundle :install + bundle "binstubs myrack" - expect(bundled_app("exec/rails")).to exist + expect(bundled_app("exec/myrackup")).to exist end end @@ -415,11 +192,10 @@ RSpec.describe "bundle binstubs <gem>" do expect(File.read(bundled_app("bin/myrackup"))).to_not include("Gem.bin_path") end - context "when specified --path option" do - it "generates a standalone binstub at the given path" do - bundle "binstubs myrack --standalone --path foo" - expect(bundled_app("foo/myrackup")).to exist - end + it "generates a standalone binstub at the given path when configured" do + bundle_config "bin foo" + bundle "binstubs myrack --standalone" + expect(bundled_app("foo/myrackup")).to exist end context "when specified --all-platforms option" do @@ -537,7 +313,7 @@ RSpec.describe "bundle binstubs <gem>" do gem "myrack" G - bundle "config set auto_install 1" + bundle_config "auto_install 1" bundle "binstubs myrack" expect(out).to include("Installing myrack 1.0.0") expect(the_bundle).to include_gems "myrack 1.0.0" @@ -549,7 +325,7 @@ RSpec.describe "bundle binstubs <gem>" do gem "myrack" G - bundle "config set auto_install 1" + bundle_config "auto_install 1" bundle "binstubs myrack", env: { "BUNDLE_INSTALL" => "1" } expect(out).not_to include("Installing myrack 1.0.0") end diff --git a/spec/bundler/commands/cache_spec.rb b/spec/bundler/commands/cache_spec.rb index ab8eb06838..e223d07f7f 100644 --- a/spec/bundler/commands/cache_spec.rb +++ b/spec/bundler/commands/cache_spec.rb @@ -28,7 +28,7 @@ RSpec.describe "bundle cache" do end end - context "with --all" do + context "with cache_all configured" do context "without a gemspec" do it "caches all dependencies except bundler itself" do gemfile <<-D @@ -37,7 +37,7 @@ RSpec.describe "bundle cache" do gem 'bundler' D - bundle "config set cache_all true" + bundle_config "cache_all true" bundle :cache expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist @@ -68,7 +68,7 @@ RSpec.describe "bundle cache" do gemspec D - bundle "config set cache_all true" + bundle_config "cache_all true" bundle :cache expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist @@ -100,7 +100,7 @@ RSpec.describe "bundle cache" do gemspec D - bundle "config set cache_all true" + bundle_config "cache_all true" bundle :cache expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist @@ -145,7 +145,7 @@ RSpec.describe "bundle cache" do gemspec :name => 'mygem_test' D - bundle "config set cache_all true" + bundle_config "cache_all true" bundle :cache expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist @@ -158,20 +158,6 @@ RSpec.describe "bundle cache" do end end - context "with --path", bundler: "< 3" do - it "sets root directory for gems" do - gemfile <<-D - source "https://gem.repo1" - gem 'myrack' - D - - bundle "cache --path #{bundled_app("test")}" - - expect(the_bundle).to include_gems "myrack 1.0.0" - expect(bundled_app("test/vendor/cache/")).to exist - end - end - context "with --no-install" do it "puts the gems in vendor/cache but does not install them" do gemfile <<-D @@ -211,7 +197,7 @@ RSpec.describe "bundle cache" do end context "with --all-platforms" do - it "puts the gems in vendor/cache even for other rubies", bundler: ">= 2.4.0" do + it "puts the gems in vendor/cache even for other rubies" do gemfile <<-D source "https://gem.repo1" gem 'myrack', :platforms => [:ruby_20, :windows_20] @@ -221,13 +207,14 @@ RSpec.describe "bundle cache" do expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist end - it "puts the gems in vendor/cache even for legacy windows rubies", bundler: ">= 2.4.0" do + it "prints a warn when using legacy windows rubies" do gemfile <<-D source "https://gem.repo1" gem 'myrack', :platforms => [:ruby_20, :x64_mingw_20] D - bundle "cache --all-platforms" + bundle "cache --all-platforms", raise_on_error: false + expect(err).to include("will be removed in the future") expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist end @@ -240,7 +227,7 @@ RSpec.describe "bundle cache" do end end - bundle "config set --local without wo" + bundle_config "without wo" install_gemfile <<-G, artifice: "compact_index_extra_api" source "https://main.repo" gem "myrack" @@ -256,14 +243,14 @@ RSpec.describe "bundle cache" do expect(the_bundle).to include_gem "myrack 1.0" expect(the_bundle).not_to include_gems "weakling", "uninstallable" - bundle "config set --local without wo" + bundle_config "without wo" bundle :install, artifice: "compact_index_extra_api" expect(the_bundle).to include_gem "myrack 1.0" expect(the_bundle).not_to include_gems "weakling" end it "does not fail to cache gems in excluded groups when there's a lockfile but gems not previously installed" do - bundle "config set --local without wo" + bundle_config "without wo" gemfile <<-G source "https://gem.repo1" gem "myrack" @@ -279,33 +266,107 @@ RSpec.describe "bundle cache" do end context "with frozen configured" do + let(:app_cache) { bundled_app("vendor/cache") } + before do + bundle_config "frozen true" + end + + it "tries to install but fails when the lockfile is out of sync" do gemfile <<-G source "https://gem.repo1" gem "myrack" G - bundle "install" - end + lockfile <<-L + GEM + remote: https://gem.repo1/ + specs: + myrack (1.0.0) + myrack-obama (1.0) + myrack + + PLATFORMS + #{lockfile_platforms} - subject do - bundle "config set --local frozen true" + DEPENDENCIES + myrack + myrack-obama + + BUNDLED WITH + #{Bundler::VERSION} + L bundle :cache, raise_on_error: false + expect(exitstatus).to eq(16) + expect(err).to include("frozen mode") + expect(err).to include("You have deleted from the Gemfile") + expect(err).to include("* myrack-obama") + bundle "env" + expect(out).to include("frozen") end - it "tries to install with frozen" do - bundle "config set deployment true" + it "caches gems without installing when lockfile is in sync, and --no-install is passed, even if vendor/cache directory is initially empty" do gemfile <<-G source "https://gem.repo1" gem "myrack" - gem "myrack-obama" G - subject - expect(exitstatus).to eq(16) - expect(err).to include("frozen mode") - expect(err).to include("You have added to the Gemfile") - expect(err).to include("* myrack-obama") - bundle "env" - expect(out).to include("frozen").or include("deployment") + lockfile <<-L + GEM + remote: https://gem.repo1/ + specs: + myrack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack + + BUNDLED WITH + #{Bundler::VERSION} + L + FileUtils.mkdir_p app_cache + + bundle "cache --no-install" + expect(out).not_to include("Installing myrack 1.0.0") + expect(out).to include("Fetching myrack 1.0.0") + expect(app_cache.join("myrack-1.0.0.gem")).to exist + end + + it "completes a partial cache when lockfile is in sync, even if the already cached gem is no longer available remotely" do + build_repo4 do + build_gem "foo", "1.0.0" + end + + build_gem "bar", "1.0.0", path: bundled_app("vendor/cache") + + gemfile <<-G + source "https://gem.repo4" + gem "foo" + gem "bar" + G + lockfile <<-L + GEM + remote: https://gem.repo4/ + specs: + foo (1.0.0) + bar (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo + bar + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "cache --no-install" + expect(out).to include("Fetching foo 1.0.0") + expect(out).not_to include("Fetching bar 1.0.0") + expect(app_cache.join("foo-1.0.0.gem")).to exist + expect(app_cache.join("bar-1.0.0.gem")).to exist end end @@ -328,7 +389,7 @@ RSpec.describe "bundle cache" do it "installs them properly from cache to a different path" do bundle "cache" - bundle "config set --local path vendor/bundle" + bundle_config "path vendor/bundle" bundle "install --local" end end @@ -344,8 +405,8 @@ RSpec.describe "bundle install with gem sources" do G bundle :cache - simulate_new_machine - FileUtils.rm_rf gem_repo2 + pristine_system_gems + FileUtils.rm_r gem_repo2 bundle "install --local" expect(the_bundle).to include_gems "myrack 1.0.0" @@ -359,11 +420,27 @@ RSpec.describe "bundle install with gem sources" do G bundle :cache - simulate_new_machine - FileUtils.rm_rf gem_repo2 + pristine_system_gems + FileUtils.rm_r gem_repo2 - bundle "config set --local deployment true" - bundle "config set --local path vendor/bundle" + bundle_config "deployment true" + bundle_config "path vendor/bundle" + bundle :install + expect(the_bundle).to include_gems "myrack 1.0.0" + end + + it "does not hit the remote at all in non frozen mode either" do + build_repo2 + install_gemfile <<-G + source "https://gem.repo2" + gem "myrack" + G + + bundle :cache + pristine_system_gems + FileUtils.rm_r gem_repo2 + + bundle_config "path vendor/bundle" bundle :install expect(the_bundle).to include_gems "myrack 1.0.0" end @@ -376,11 +453,11 @@ RSpec.describe "bundle install with gem sources" do G bundle :cache - simulate_new_machine - FileUtils.rm_rf gem_repo2 + pristine_system_gems + FileUtils.rm_r gem_repo2 - bundle "config set --local cache_all_platforms true" - bundle "config set --local path vendor/bundle" + bundle_config "cache_all_platforms true" + bundle_config "path vendor/bundle" bundle "install --local" expect(out).not_to include("Fetching gem metadata") expect(the_bundle).to include_gems "myrack 1.0.0" @@ -425,20 +502,19 @@ RSpec.describe "bundle install with gem sources" do foo BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L simulate_platform "x86_64-linux" do - bundle "config set cache_all_platforms true" - bundle "config set path vendor/bundle" + bundle_config "cache_all_platforms true" + bundle_config "path vendor/bundle" bundle :cache, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } - build_repo4 do - # simulate removal of all remote gems - end + # simulate removal of all remote gems + empty_repo4 # delete compact index cache - FileUtils.rm_rf home(".bundle/cache/compact_index") + FileUtils.rm_r home(".bundle/cache/compact_index") bundle "install", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } @@ -471,9 +547,9 @@ RSpec.describe "bundle install with gem sources" do bundle :cache end - simulate_new_machine + pristine_system_gems - bundle "config set --local force_ruby_platform true" + bundle_config "force_ruby_platform true" install_gemfile <<-G source "https://gem.repo1" @@ -482,6 +558,53 @@ RSpec.describe "bundle install with gem sources" do expect(the_bundle).to include_gems("platform_specific 1.0 ruby") end + it "keeps gems that are locked and cached for the current platform, even if incompatible with the current ruby" do + build_repo4 do + build_gem "bcrypt_pbkdf", "1.1.1" + build_gem "bcrypt_pbkdf", "1.1.1" do |s| + s.platform = "arm64-darwin" + s.required_ruby_version = "< #{current_ruby_minor}" + end + end + + app_cache = bundled_app("vendor/cache") + FileUtils.mkdir_p app_cache + FileUtils.cp gem_repo4("gems/bcrypt_pbkdf-1.1.1-arm64-darwin.gem"), app_cache + FileUtils.cp gem_repo4("gems/bcrypt_pbkdf-1.1.1.gem"), app_cache + + bundle_config "cache_all_platforms true" + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + bcrypt_pbkdf (1.1.1) + bcrypt_pbkdf (1.1.1-arm64-darwin) + + PLATFORMS + arm64-darwin + ruby + + DEPENDENCIES + bcrypt_pbkdf + + BUNDLED WITH + #{Bundler::VERSION} + L + + simulate_platform "arm64-darwin-23" do + install_gemfile <<~G, verbose: true + source "https://gem.repo4" + gem "bcrypt_pbkdf" + G + + expect(out).to include("Updating files in vendor/cache") + expect(err).to be_empty + expect(app_cache.join("bcrypt_pbkdf-1.1.1-arm64-darwin.gem")).to exist + expect(app_cache.join("bcrypt_pbkdf-1.1.1.gem")).to exist + end + end + it "does not update the cache if --no-cache is passed" do gemfile <<-G source "https://gem.repo1" diff --git a/spec/bundler/commands/check_spec.rb b/spec/bundler/commands/check_spec.rb index 78c2aef58b..7fe6897ae3 100644 --- a/spec/bundler/commands/check_spec.rb +++ b/spec/bundler/commands/check_spec.rb @@ -57,7 +57,7 @@ RSpec.describe "bundle check" do bundle :check, raise_on_error: false expect(err).to include("The following gems are missing") - expect(err).to include(" * rake (13.2.1)") + expect(err).to include(" * rake (#{rake_version})") expect(err).to include(" * actionpack (2.3.2)") expect(err).to include(" * activerecord (2.3.2)") expect(err).to include(" * actionmailer (2.3.2)") @@ -76,7 +76,7 @@ RSpec.describe "bundle check" do expect(exitstatus).to be > 0 expect(err).to include("The following gems are missing") expect(err).to include(" * rails (2.3.2)") - expect(err).to include(" * rake (13.2.1)") + expect(err).to include(" * rake (#{rake_version})") expect(err).to include(" * actionpack (2.3.2)") expect(err).to include(" * activerecord (2.3.2)") expect(err).to include(" * actionmailer (2.3.2)") @@ -88,14 +88,14 @@ RSpec.describe "bundle check" do it "prints a generic error if gem git source is not checked out" do build_git "foo", path: lib_path("foo") - bundle "config path vendor/bundle" + bundle_config "path vendor/bundle" install_gemfile <<-G source "https://gem.repo1" gem "foo", git: "#{lib_path("foo")}" G - FileUtils.rm_rf bundled_app("vendor/bundle") + FileUtils.rm_r bundled_app("vendor/bundle") bundle :check, raise_on_error: false expect(exitstatus).to eq 1 expect(err).to include("Bundler can't satisfy your Gemfile's dependencies.") @@ -123,21 +123,8 @@ RSpec.describe "bundle check" do expect(err).to include("Bundler can't satisfy your Gemfile's dependencies.") end - it "remembers --without option from install", bundler: "< 3" do - gemfile <<-G - source "https://gem.repo1" - group :foo do - gem "myrack" - end - G - - bundle "install --without foo" - bundle "check" - expect(out).to include("The Gemfile's dependencies are satisfied") - end - it "uses the without setting" do - bundle "config set without foo" + bundle_config "without foo" install_gemfile <<-G source "https://gem.repo1" group :foo do @@ -155,7 +142,7 @@ RSpec.describe "bundle check" do gem "myrack", :group => :foo G - bundle "config set --local without foo" + bundle_config "without foo" bundle :install gemfile <<-G @@ -174,10 +161,10 @@ RSpec.describe "bundle check" do gem "myrack" G - bundle "config set --local path vendor/bundle" + bundle_config "path vendor/bundle" bundle :cache - gem_command "uninstall myrack", env: { "GEM_HOME" => vendored_gems.to_s } + uninstall_gem("myrack", env: { "GEM_HOME" => vendored_gems.to_s }) bundle "check", raise_on_error: false expect(err).to include("* myrack (1.0.0)") @@ -258,13 +245,13 @@ RSpec.describe "bundle check" do expect(err).not_to include("Unfortunately, a fatal error has occurred. ") end - it "fails when there's no lock file and frozen is set" do + it "fails when there's no lockfile and frozen is set" do install_gemfile <<-G source "https://gem.repo1" gem "foo" G - bundle "config set --local deployment true" + bundle_config "deployment true" bundle "install" FileUtils.rm(bundled_app_lock) @@ -272,46 +259,6 @@ RSpec.describe "bundle check" do expect(last_command).to be_failure end - context "--path", bundler: "< 3" do - context "after installing gems in the proper directory" do - before do - gemfile <<-G - source "https://gem.repo1" - gem "rails" - G - bundle "install --path vendor/bundle" - - FileUtils.rm_rf(bundled_app(".bundle")) - end - - it "returns success" do - bundle "check --path vendor/bundle" - expect(out).to include("The Gemfile's dependencies are satisfied") - end - - it "should write to .bundle/config" do - bundle "check --path vendor/bundle" - bundle "check" - end - end - - context "after installing gems on a different directory" do - before do - install_gemfile <<-G - source "https://gem.repo1" - gem "rails" - G - - bundle "check --path vendor/bundle", raise_on_error: false - end - - it "returns false" do - expect(exitstatus).to eq(1) - expect(err).to match(/The following gems are missing/) - end - end - end - describe "when locked" do before :each do system_gems "myrack-1.0.0" @@ -328,7 +275,8 @@ RSpec.describe "bundle check" do end it "shows what is missing with the current Gemfile if it is not satisfied" do - simulate_new_machine + FileUtils.rm_r default_bundle_path + default_system_gems bundle :check, raise_on_error: false expect(err).to match(/The following gems are missing/) expect(err).to include("* myrack (1.0") @@ -388,7 +336,7 @@ RSpec.describe "bundle check" do myrack BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -463,7 +411,7 @@ RSpec.describe "bundle check" do depends_on_myrack! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -534,7 +482,7 @@ RSpec.describe "bundle check" do dex-dispatch-engine! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -551,7 +499,7 @@ RSpec.describe "bundle check" do build_gem "bar" end - bundle "config set path.system true" + bundle_config "path.system true" # Add all gems to ensure all gems are installed so that a bundle check # would be successful @@ -616,7 +564,7 @@ RSpec.describe "bundle check" do end before do - bundle "config set --local path vendor/bundle" + bundle_config "path vendor/bundle" install_gemfile <<-G source "https://gem.repo1" diff --git a/spec/bundler/commands/clean_spec.rb b/spec/bundler/commands/clean_spec.rb index c27766835e..c77859d378 100644 --- a/spec/bundler/commands/clean_spec.rb +++ b/spec/bundler/commands/clean_spec.rb @@ -25,8 +25,8 @@ RSpec.describe "bundle clean" do gem "foo" G - bundle "config set path vendor/bundle" - bundle "config set clean false" + bundle_config "path vendor/bundle" + bundle_config "clean false" bundle "install" gemfile <<-G @@ -54,8 +54,8 @@ RSpec.describe "bundle clean" do gem "foo" G - bundle "config set path vendor/bundle" - bundle "config set clean false" + bundle_config "path vendor/bundle" + bundle_config "clean false" bundle "install" gemfile <<-G @@ -84,8 +84,8 @@ RSpec.describe "bundle clean" do gem "foo" G - bundle "config set path vendor/bundle" - bundle "config set clean false" + bundle_config "path vendor/bundle" + bundle_config "clean false" bundle "install" gemfile <<-G @@ -117,9 +117,9 @@ RSpec.describe "bundle clean" do end G - bundle "config set path vendor/bundle" + bundle_config "path vendor/bundle" bundle "install" - bundle "config set without test_group" + bundle_config "without test_group" bundle "install" bundle :clean @@ -145,13 +145,13 @@ RSpec.describe "bundle clean" do end G - bundle "config set path vendor/bundle" + bundle_config "path vendor/bundle" bundle "install" bundle :clean digest = Digest(:SHA1).hexdigest(git_path.to_s) - cache_path = Bundler::VERSION.start_with?("2.") ? vendored_gems("cache/bundler/git/foo-1.0-#{digest}") : home(".bundle/cache/git/foo-1.0-#{digest}") + cache_path = vendored_gems("cache/bundler/git/foo-1.0-#{digest}") expect(cache_path).to exist end @@ -169,7 +169,7 @@ RSpec.describe "bundle clean" do end G - bundle "config set path vendor/bundle" + bundle_config "path vendor/bundle" bundle "install" gemfile <<-G @@ -210,7 +210,7 @@ RSpec.describe "bundle clean" do FileUtils.mkdir_p(bundled_app("real-path")) File.symlink(bundled_app("real-path"), bundled_app("symlink-path")) - bundle "config set path #{bundled_app("symlink-path")}" + bundle_config "path #{bundled_app("symlink-path")}" bundle "install" bundle :clean @@ -220,7 +220,7 @@ RSpec.describe "bundle clean" do expect(bundled_app("symlink-path/#{Bundler.ruby_scope}/bundler/gems/foo-#{revision[0..11]}")).to exist end - it "removes old git gems" do + it "removes old git gems on bundle update" do build_git "foo-bar", path: lib_path("foo-bar") revision = revision_for(lib_path("foo-bar")) @@ -233,7 +233,7 @@ RSpec.describe "bundle clean" do end G - bundle "config set path vendor/bundle" + bundle_config "path vendor/bundle" bundle "install" update_git "foo-bar", path: lib_path("foo-bar") @@ -265,7 +265,7 @@ RSpec.describe "bundle clean" do gem "activesupport", :git => "#{lib_path("rails")}", :ref => '#{revision}' G - bundle "config set path vendor/bundle" + bundle_config "path vendor/bundle" bundle "install" bundle :clean expect(out).to include("") @@ -288,8 +288,8 @@ RSpec.describe "bundle clean" do end end G - bundle "config set path vendor/bundle" - bundle "config set without test" + bundle_config "path vendor/bundle" + bundle_config "without test" bundle "install" bundle :clean @@ -311,15 +311,15 @@ RSpec.describe "bundle clean" do end G - bundle "config set path vendor/bundle" - bundle "config set without development" + bundle_config "path vendor/bundle" + bundle_config "without development" bundle "install" bundle :clean end it "displays an error when used without --path" do - bundle "config set path.system true" + bundle_config "path.system true" install_gemfile <<-G source "https://gem.repo1" @@ -341,7 +341,7 @@ RSpec.describe "bundle clean" do gem "foo" G - bundle "config set path vendor/bundle" + bundle_config "path vendor/bundle" bundle "install" gemfile <<-G @@ -352,8 +352,8 @@ RSpec.describe "bundle clean" do bundle "install" FileUtils.rm(vendored_gems("bin/myrackup")) - FileUtils.rm_rf(vendored_gems("gems/thin-1.0")) - FileUtils.rm_rf(vendored_gems("gems/myrack-1.0.0")) + FileUtils.rm_r(vendored_gems("gems/thin-1.0")) + FileUtils.rm_r(vendored_gems("gems/myrack-1.0.0")) bundle :clean @@ -364,7 +364,7 @@ RSpec.describe "bundle clean" do end it "does not call clean automatically when using system gems" do - bundle "config set path.system true" + bundle_config "path.system true" install_gemfile <<-G source "https://gem.repo1" @@ -379,43 +379,18 @@ RSpec.describe "bundle clean" do gem "myrack" G - gem_command :list + installed_gems_list expect(out).to include("myrack (1.0.0)").and include("thin (1.0)") end - it "--clean should override the bundle setting on install", bundler: "< 3" do - gemfile <<-G - source "https://gem.repo1" - - gem "thin" - gem "myrack" - G - bundle "config set path vendor/bundle" - bundle "config set clean false" - bundle "install --clean true" - - gemfile <<-G - source "https://gem.repo1" - - gem "myrack" - G - bundle "install" - - should_have_gems "myrack-1.0.0" - should_not_have_gems "thin-1.0" - end - - it "--clean should override the bundle setting on update", bundler: "< 3" do + it "does not clean on bundle update when path has not been set" do build_repo2 - gemfile <<-G + install_gemfile <<-G source "https://gem.repo2" gem "foo" G - bundle "config set path vendor/bundle" - bundle "config set clean false" - bundle "install --clean true" update_repo2 do build_gem "foo", "1.0.1" @@ -423,11 +398,27 @@ RSpec.describe "bundle clean" do bundle "update", all: true - should_have_gems "foo-1.0.1" - should_not_have_gems "foo-1.0" + files = Pathname.glob(default_bundle_path("*", "*")) + files.map! {|f| f.to_s.sub(default_bundle_path.to_s, "") } + expected_files = %W[ + /bin/bundle + /bin/bundler + /cache/bundler-#{Bundler::VERSION}.gem + /cache/foo-1.0.1.gem + /cache/foo-1.0.gem + /gems/bundler-#{Bundler::VERSION} + /gems/foo-1.0 + /gems/foo-1.0.1 + /specifications/bundler-#{Bundler::VERSION}.gemspec + /specifications/foo-1.0.1.gemspec + /specifications/foo-1.0.gemspec + ] + expected_files += ["/bin/bundle.bat", "/bin/bundler.bat"] if Gem.win_platform? + + expect(files.sort).to eq(expected_files.sort) end - it "automatically cleans when path has not been set", bundler: "3" do + it "will automatically clean on bundle update when path has not been set", bundler: "5" do build_repo2 install_gemfile <<-G @@ -442,8 +433,8 @@ RSpec.describe "bundle clean" do 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, "") } + files = Pathname.glob(local_gem_path("*", "*")) + files.map! {|f| f.to_s.sub(local_gem_path.to_s, "") } expect(files.sort).to eq %w[ /cache/foo-1.0.1.gem /gems/foo-1.0.1 @@ -451,14 +442,14 @@ RSpec.describe "bundle clean" do ] end - it "does not clean automatically on --path" do + it "does not clean automatically when path configured" do gemfile <<-G source "https://gem.repo1" gem "thin" gem "myrack" G - bundle "config set path vendor/bundle" + bundle_config "path vendor/bundle" bundle "install" gemfile <<-G @@ -471,7 +462,7 @@ RSpec.describe "bundle clean" do should_have_gems "myrack-1.0.0", "thin-1.0" end - it "does not clean on bundle update with --path" do + it "does not clean on bundle update when path configured" do build_repo2 gemfile <<-G @@ -479,7 +470,7 @@ RSpec.describe "bundle clean" do gem "foo" G - bundle "config set path vendor/bundle" + bundle_config "path vendor/bundle" bundle "install" update_repo2 do @@ -490,8 +481,8 @@ RSpec.describe "bundle clean" do should_have_gems "foo-1.0", "foo-1.0.1" end - it "does not clean on bundle update when using --system" do - bundle "config set path.system true" + it "does not clean on bundle update when installing to system gems" do + bundle_config "path.system true" build_repo2 @@ -507,12 +498,12 @@ RSpec.describe "bundle clean" do end bundle :update, all: true - gem_command :list + installed_gems_list expect(out).to include("foo (1.0.1, 1.0)") end it "cleans system gems when --force is used" do - bundle "config set path.system true" + bundle_config "path.system true" gemfile <<-G source "https://gem.repo1" @@ -531,7 +522,7 @@ RSpec.describe "bundle clean" do bundle "clean --force" expect(out).to include("Removing foo (1.0)") - gem_command :list + installed_gems_list expect(out).not_to include("foo (1.0)") expect(out).to include("myrack (1.0.0)") end @@ -565,7 +556,7 @@ RSpec.describe "bundle clean" do expect(err).to include(system_gem_path.to_s) expect(err).to include("grant write permissions") - gem_command :list + installed_gems_list expect(out).to include("foo (1.0)") expect(out).to include("myrack (1.0.0)") end @@ -581,7 +572,7 @@ RSpec.describe "bundle clean" do gem "foo", :git => "#{lib_path("foo-1.0")}" G - bundle "config set path vendor/bundle" + bundle_config "path vendor/bundle" bundle "install" # mimic 7 length git revisions in Gemfile.lock @@ -591,7 +582,7 @@ RSpec.describe "bundle clean" do end lockfile(bundled_app_lock, gemfile_lock.join("\n")) - bundle "config set path vendor/bundle" + bundle_config "path vendor/bundle" bundle "install" bundle :clean @@ -602,7 +593,7 @@ RSpec.describe "bundle clean" do end it "when using --force on system gems, it doesn't remove binaries" do - bundle "config set path.system true" + bundle_config "path.system true" build_repo2 do build_gem "bindir" do |s| @@ -625,7 +616,7 @@ RSpec.describe "bundle clean" do expect(out).to eq("1.0") end - it "when using --force, it doesn't remove default gem binaries", :realworld do + it "when using --force, it doesn't remove default gem binaries" 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? @@ -634,8 +625,6 @@ RSpec.describe "bundle clean" do s.executables = "irb" end - realworld_system_gems "tsort --version 0.1.0", "pathname --version 0.1.0", "set --version 1.0.1" - install_gemfile <<-G source "https://gem.repo2" G @@ -660,7 +649,7 @@ RSpec.describe "bundle clean" do gem "bar", "1.0", :path => "#{relative_path}" G - bundle "config set path vendor/bundle" + bundle_config "path vendor/bundle" bundle "install" bundle :clean end @@ -673,8 +662,8 @@ RSpec.describe "bundle clean" do gem "foo" G - bundle "config set path vendor/bundle" - bundle "config set clean false" + bundle_config "path vendor/bundle" + bundle_config "clean false" bundle "install" gemfile <<-G @@ -703,8 +692,8 @@ RSpec.describe "bundle clean" do gem "foo" G - bundle "config set path vendor/bundle" - bundle "config set clean false" + bundle_config "path vendor/bundle" + bundle_config "clean false" bundle "install" gemfile <<-G @@ -733,10 +722,10 @@ RSpec.describe "bundle clean" do gem "foo" G - bundle "config set path vendor/bundle" - bundle "config set clean false" + bundle_config "path vendor/bundle" + bundle_config "clean false" bundle "install" - bundle "config set dry_run false" + bundle_config "dry_run false" gemfile <<-G source "https://gem.repo1" @@ -765,8 +754,8 @@ RSpec.describe "bundle clean" do gem "foo" G - bundle "config set path vendor/bundle" - bundle "config set clean false" + bundle_config "path vendor/bundle" + bundle_config "clean false" bundle "install" gemfile <<-G @@ -776,7 +765,7 @@ RSpec.describe "bundle clean" do gem "weakling" G - bundle "config set auto_install 1" + bundle_config "auto_install 1" bundle :clean expect(out).to include("Installing weakling 0.0.3") should_have_gems "thin-1.0", "myrack-1.0.0", "weakling-0.0.3" @@ -794,7 +783,7 @@ RSpec.describe "bundle clean" do gem "very_simple_git_binary", :git => "#{lib_path("very_simple_git_binary-1.0")}", :ref => "#{revision}" G - bundle "config set path vendor/bundle" + bundle_config "path vendor/bundle" bundle "install" expect(vendored_gems("bundler/gems/extensions")).to exist expect(vendored_gems("bundler/gems/very_simple_git_binary-1.0-#{revision[0..11]}")).to exist @@ -815,7 +804,7 @@ RSpec.describe "bundle clean" do gem "simple_binary" G - bundle "config set path vendor/bundle" + bundle_config "path vendor/bundle" bundle "install" very_simple_binary_extensions_dir = @@ -855,7 +844,7 @@ RSpec.describe "bundle clean" do gem "very_simple_git_binary", :git => "#{lib_path("very_simple_git_binary-1.0")}", :ref => "#{revision}" G - bundle "config set path vendor/bundle" + bundle_config "path vendor/bundle" bundle "install" very_simple_binary_extensions_dir = @@ -899,8 +888,8 @@ RSpec.describe "bundle clean" do G bundle :lock - bundle "config set without development" - bundle "config set path vendor/bundle" + bundle_config "without development" + bundle_config "path vendor/bundle" bundle "install", verbose: true bundle :clean @@ -909,4 +898,39 @@ RSpec.describe "bundle clean" do expect(very_simple_binary_extensions_dir).to be_nil end + + it "does not remove the bundler version currently running" do + gemfile <<-G + source "https://gem.repo1" + + gem "myrack" + G + + bundle_config "path vendor/bundle" + bundle "install" + + version = Bundler.gem_version.to_s + # Simulate that the locked bundler version is installed in the bundle path + # by creating the gem directory and gemspec (as would happen after bundle install with that version) + Pathname(vendored_gems("cache/bundler-#{version}.gem")).tap do |path| + FileUtils.touch(path) + end + FileUtils.touch(vendored_gems("gems/bundler-#{version}")) + Pathname(vendored_gems("specifications/bundler-#{version}.gemspec")).tap do |path| + path.write(<<~GEMSPEC) + Gem::Specification.new do |s| + s.name = "bundler" + s.version = "#{version}" + s.authors = ["bundler team"] + s.summary = "The best way to manage your application's dependencies" + end + GEMSPEC + end + + should_have_gems "bundler-#{version}" + + bundle :clean + + should_have_gems "bundler-#{version}" + end end diff --git a/spec/bundler/commands/config_spec.rb b/spec/bundler/commands/config_spec.rb index 1392b17315..e8ab32ca5d 100644 --- a/spec/bundler/commands/config_spec.rb +++ b/spec/bundler/commands/config_spec.rb @@ -313,9 +313,10 @@ RSpec.describe ".bundle/config" do describe "parseable option" do it "prints an empty string" do - bundle "config get foo --parseable" + bundle "config get foo --parseable", raise_on_error: false expect(out).to eq "" + expect(last_command).to be_failure end it "only prints the value of the config" do @@ -501,8 +502,9 @@ E it "get" do ENV["BUNDLE_BAR"] = "bar_val" - bundle "config get foo" + bundle "config get foo", raise_on_error: false expect(out).to eq "Settings for `foo` in order of priority. The top value will be used\nYou have not configured a value for `foo`" + expect(last_command).to be_failure ENV["BUNDLE_FOO"] = "foo_val" @@ -547,7 +549,8 @@ E bundle "config unset foo" 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`" + expect(bundle("config get foo", raise_on_error: false)).to eq "Settings for `foo` in order of priority. The top value will be used\nYou have not configured a value for `foo`" + expect(last_command).to be_failure bundle "config set --local foo 1" bundle "config set --global foo 2" @@ -557,7 +560,8 @@ E expect(bundle("config get foo")).to eq "Settings for `foo` in order of priority. The top value will be used\nSet for the current user (#{home(".bundle/config")}): \"2\"" bundle "config unset foo --global" 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`" + expect(bundle("config get foo", raise_on_error: false)).to eq "Settings for `foo` in order of priority. The top value will be used\nYou have not configured a value for `foo`" + expect(last_command).to be_failure bundle "config set --local foo 1" bundle "config set --global foo 2" @@ -567,7 +571,8 @@ E expect(bundle("config get foo")).to eq "Settings for `foo` in order of priority. The top value will be used\nSet for your local app (#{bundled_app(".bundle/config")}): \"1\"" bundle "config unset foo --local" 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`" + expect(bundle("config get foo", raise_on_error: false)).to eq "Settings for `foo` in order of priority. The top value will be used\nYou have not configured a value for `foo`" + expect(last_command).to be_failure bundle "config unset foo --local --global", raise_on_error: false expect(last_command).to be_failure @@ -577,6 +582,36 @@ E end RSpec.describe "setting gemfile via config" do + context "when a default Gemfile exists" do + before do + gemfile <<-G + source "https://gem.repo1" + G + + gemfile bundled_app("foo/bar_gemfile"), <<-G + source "https://gem.repo1" + G + end + + it "reports the local gemfile setting without promoting it to the environment" do + bundle "config set gemfile foo/bar_gemfile" + + bundle "config list" + expect(out).to include("Set for your local app (#{bundled_app(".bundle/config")}): \"foo/bar_gemfile\"") + expect(out).not_to include("Set via BUNDLE_GEMFILE") + end + + it "unsets the local gemfile setting from the app config" do + bundle "config set gemfile foo/bar_gemfile" + + bundle "config unset gemfile" + bundle "config get gemfile", raise_on_error: false + + expect(out).to include("You have not configured a value for `gemfile`") + expect(File.read(bundled_app(".bundle/config"))).not_to include("BUNDLE_GEMFILE") + end + end + context "when only the non-default Gemfile exists" do it "persists the gemfile location to .bundle/config" do gemfile bundled_app("NotGemfile"), <<-G @@ -592,3 +627,20 @@ RSpec.describe "setting gemfile via config" do end end end + +RSpec.describe "setting lockfile via config" do + it "persists the lockfile location to .bundle/config" do + gemfile bundled_app("NotGemfile"), <<-G + source "https://gem.repo1" + gem 'myrack' + G + + bundle "config set --local gemfile #{bundled_app("NotGemfile")}" + bundle "config set --local lockfile #{bundled_app("ReallyNotGemfile.lock")}" + expect(File.exist?(bundled_app(".bundle/config"))).to eq(true) + + bundle "config list" + expect(out).to include("NotGemfile") + expect(out).to include("ReallyNotGemfile.lock") + end +end diff --git a/spec/bundler/commands/console_spec.rb b/spec/bundler/commands/console_spec.rb index ba5f5f514b..a44f607546 100644 --- a/spec/bundler/commands/console_spec.rb +++ b/spec/bundler/commands/console_spec.rb @@ -35,107 +35,180 @@ RSpec.describe "bundle console", readline: true do end RUBY end - end - install_gemfile <<-G - source "https://gem.repo2" - gem "myrack" - gem "activesupport", :group => :test - gem "myrack_middleware", :group => :development - G + build_dummy_irb + end end - it "starts IRB with the default group loaded" do - bundle "console" do |input, _, _| - input.puts("puts MYRACK") - input.puts("exit") + context "when the library requires a non-existent file" do + before do + build_lib "loadfuuu", "1.0.0" do |s| + s.write "lib/loadfuuu.rb", "require_relative 'loadfuuu/bar'" + s.write "lib/loadfuuu/bar.rb", "require 'not-in-bundle'" + end + + install_gemfile <<-G + source "https://gem.repo2" + gem "irb" + path "#{lib_path}" do + gem "loadfuuu", require: true + end + G end - expect(out).to include("0.9.1") - end - it "uses IRB as default console" do - bundle "console" do |input, _, _| - input.puts("__FILE__") - input.puts("exit") + it "does not show the bug report template" do + bundle("console", raise_on_error: false) do |input, _, _| + input.puts("exit") + end + + expect(err).not_to include("ERROR REPORT TEMPLATE") end - expect(out).to include("(irb)") end - it "starts another REPL if configured as such" do - install_gemfile <<-G - source "https://gem.repo2" - gem "pry" - G - bundle "config set console pry" + context "when the library references a non-existent constant" do + before do + build_lib "loadfuuu", "1.0.0" do |s| + s.write "lib/loadfuuu.rb", "Some::NonExistent::Constant" + end - bundle "console" do |input, _, _| - input.puts("__method__") - input.puts("exit") + install_gemfile <<-G + source "https://gem.repo2" + gem "irb" + path "#{lib_path}" do + gem "loadfuuu", require: true + end + G end - expect(out).to include(":__pry__") - end - it "falls back to IRB if the other REPL isn't available" do - bundle "config set console pry" - # make sure pry isn't there + it "does not show the bug report template" do + bundle("console", raise_on_error: false) do |input, _, _| + input.puts("exit") + end - bundle "console" do |input, _, _| - input.puts("__FILE__") - input.puts("exit") + expect(err).not_to include("ERROR REPORT TEMPLATE") end - expect(out).to include("(irb)") end - it "doesn't load any other groups" do - bundle "console" do |input, _, _| - input.puts("puts ACTIVESUPPORT") - input.puts("exit") + context "when the library does not have any errors" do + before do + install_gemfile <<-G + source "https://gem.repo2" + gem "irb" + gem "myrack" + gem "activesupport", :group => :test + gem "myrack_middleware", :group => :development + G end - expect(out).to include("NameError") - end - describe "when given a group" do - it "loads the given group" do - bundle "console test" do |input, _, _| - input.puts("puts ACTIVESUPPORT") + it "starts IRB with the default group loaded" do + bundle "console" do |input, _, _| + input.puts("puts MYRACK") input.puts("exit") end - expect(out).to include("2.3.5") + expect(out).to include("0.9.1") end - it "loads the default group" do - bundle "console test" do |input, _, _| - input.puts("puts MYRACK") + it "uses IRB as default console" do + skip "Does not work in a ruby-core context if irb is in the default $LOAD_PATH because it enables the real IRB, not our dummy one" if ruby_core? && Gem.ruby_version < Gem::Version.new("3.5.0.a") + + bundle "console" do |input, _, _| + input.puts("__method__") input.puts("exit") end - expect(out).to include("0.9.1") + expect(out).to include("__irb__") + end + + it "starts another REPL if configured as such" do + install_gemfile <<-G + source "https://gem.repo2" + gem "irb" + gem "pry" + G + bundle_config "console pry" + + bundle "console" do |input, _, _| + input.puts("__method__") + input.puts("exit") + end + expect(out).to include(":__pry__") end - it "doesn't load other groups" do - bundle "console test" do |input, _, _| - input.puts("puts MYRACK_MIDDLEWARE") + it "falls back to IRB if the other REPL isn't available" do + skip "Does not work in a ruby-core context if irb is in the default $LOAD_PATH because it enables the real IRB, not our dummy one" if ruby_core? && Gem.ruby_version < Gem::Version.new("3.5.0.a") + + bundle_config "console pry" + # make sure pry isn't there + + bundle "console" do |input, _, _| + input.puts("__method__") + input.puts("exit") + end + expect(out).to include("__irb__") + end + + it "does not try IRB twice if no console is configured and IRB is not available" do + create_file("irb.rb", "raise LoadError, 'irb is not available'") + + bundle("console", env: { "RUBYOPT" => "-I#{bundled_app} #{ENV["RUBYOPT"]}" }, raise_on_error: false) do |input, _, _| + input.puts("puts ACTIVESUPPORT") + input.puts("exit") + end + expect(err).not_to include("falling back to irb") + expect(err).to include("irb is not available") + end + + it "doesn't load any other groups" do + bundle "console" do |input, _, _| + input.puts("puts ACTIVESUPPORT") input.puts("exit") end expect(out).to include("NameError") end - end - it "performs an automatic bundle install" do - gemfile <<-G - source "https://gem.repo2" - gem "myrack" - gem "activesupport", :group => :test - gem "myrack_middleware", :group => :development - gem "foo" - G - - bundle "config set auto_install 1" - bundle :console do |input, _, _| - input.puts("puts 'hello'") - input.puts("exit") + describe "when given a group" do + it "loads the given group" do + bundle "console test" do |input, _, _| + input.puts("puts ACTIVESUPPORT") + input.puts("exit") + end + expect(out).to include("2.3.5") + end + + it "loads the default group" do + bundle "console test" do |input, _, _| + input.puts("puts MYRACK") + input.puts("exit") + end + expect(out).to include("0.9.1") + end + + it "doesn't load other groups" do + bundle "console test" do |input, _, _| + input.puts("puts MYRACK_MIDDLEWARE") + input.puts("exit") + end + expect(out).to include("NameError") + end + end + + it "performs an automatic bundle install" do + gemfile <<-G + source "https://gem.repo2" + gem "irb" + gem "myrack" + gem "activesupport", :group => :test + gem "myrack_middleware", :group => :development + gem "foo" + G + + bundle_config "auto_install 1" + bundle :console do |input, _, _| + input.puts("puts 'hello'") + input.puts("exit") + end + expect(out).to include("Installing foo 1.0") + expect(out).to include("hello") + expect(the_bundle).to include_gems "foo 1.0" end - expect(out).to include("Installing foo 1.0") - expect(out).to include("hello") - expect(the_bundle).to include_gems "foo 1.0" end end diff --git a/spec/bundler/commands/doctor_spec.rb b/spec/bundler/commands/doctor_spec.rb index 744510cd40..d350b4b3d1 100644 --- a/spec/bundler/commands/doctor_spec.rb +++ b/spec/bundler/commands/doctor_spec.rb @@ -4,6 +4,7 @@ require "find" require "stringio" require "bundler/cli" require "bundler/cli/doctor" +require "bundler/cli/doctor/diagnose" RSpec.describe "bundle doctor" do before(:each) do @@ -14,7 +15,7 @@ RSpec.describe "bundle doctor" do @stdout = StringIO.new - [:error, :warn].each do |method| + [:error, :warn, :info].each do |method| allow(Bundler.ui).to receive(method).and_wrap_original do |m, message| m.call message @stdout.puts message @@ -33,6 +34,8 @@ RSpec.describe "bundle doctor" do allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) allow(Find).to receive(:find).with(Bundler.bundle_path.to_s) { [unwritable_file] } allow(File).to receive(:exist?).and_call_original + allow(File).to receive(:writable?).and_call_original + allow(File).to receive(:readable?).and_call_original allow(File).to receive(:exist?).with(unwritable_file).and_return(true) allow(File).to receive(:stat).with(unwritable_file) { stat } allow(stat).to receive(:uid) { Process.uid } @@ -45,25 +48,33 @@ RSpec.describe "bundle doctor" do 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 - expect { Bundler::CLI::Doctor.new({}).run }.not_to raise_error - expect(@stdout.string).to be_empty + it "exits with a success message if the installed gem has no C extensions" do + doctor = Bundler::CLI::Doctor::Diagnose.new({}) + allow(doctor).to receive(:lookup_with_fiddle).and_return(false) + expect { doctor.run }.not_to raise_error + expect(@stdout.string).to include("No issues") end - it "exits with no message if the installed gem's C extension dylib breakage is fine" do - doctor = Bundler::CLI::Doctor.new({}) + it "exits with a success message if the installed gem's C extension dylib breakage is fine" do + doctor = Bundler::CLI::Doctor::Diagnose.new({}) expect(doctor).to receive(:bundles_for_gem).exactly(2).times.and_return ["/path/to/myrack/myrack.bundle"] expect(doctor).to receive(:dylibs).exactly(2).times.and_return ["/usr/lib/libSystem.dylib"] - allow(Fiddle).to receive(:dlopen).with("/usr/lib/libSystem.dylib").and_return(true) + allow(doctor).to receive(:lookup_with_fiddle).with("/usr/lib/libSystem.dylib").and_return(false) expect { doctor.run }.not_to raise_error - expect(@stdout.string).to be_empty + expect(@stdout.string).to include("No issues") + end + + it "parses otool output correctly" do + doctor = Bundler::CLI::Doctor::Diagnose.new({}) + expect(doctor).to receive(:`).with("/usr/bin/otool -L fake").and_return("/home/gem/ruby/3.4.3/gems/blake3-rb-1.5.4.4/lib/digest/blake3/blake3_ext.bundle:\n\t (compatibility version 0.0.0, current version 0.0.0)\n\t/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1351.0.0)") + expect(doctor.dylibs_darwin("fake")).to eq(["/usr/lib/libSystem.B.dylib"]) end it "exits with a message if one of the linked libraries is missing" do - doctor = Bundler::CLI::Doctor.new({}) + doctor = Bundler::CLI::Doctor::Diagnose.new({}) expect(doctor).to receive(:bundles_for_gem).exactly(2).times.and_return ["/path/to/myrack/myrack.bundle"] expect(doctor).to receive(:dylibs).exactly(2).times.and_return ["/usr/local/opt/icu4c/lib/libicui18n.57.1.dylib"] - allow(Fiddle).to receive(:dlopen).with("/usr/local/opt/icu4c/lib/libicui18n.57.1.dylib").and_raise(Fiddle::DLError) + allow(doctor).to receive(:lookup_with_fiddle).with("/usr/local/opt/icu4c/lib/libicui18n.57.1.dylib").and_return(true) 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 @@ -82,7 +93,9 @@ RSpec.describe "bundle doctor" do end 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 + doctor = Bundler::CLI::Doctor::Diagnose.new({}) + allow(doctor).to receive(:lookup_with_fiddle).and_return(false) + expect { doctor.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 - #{@broken_symlink}" ) @@ -97,42 +110,63 @@ RSpec.describe "bundle doctor" do allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) allow(Find).to receive(:find).with(Bundler.bundle_path.to_s) { [@unwritable_file] } allow(File).to receive(:exist?).and_call_original + allow(File).to receive(:writable?).and_call_original + allow(File).to receive(:readable?).and_call_original allow(File).to receive(:exist?).with(@unwritable_file) { true } allow(File).to receive(:stat).with(@unwritable_file) { @stat } end - it "exits with an error if home contains files that are not readable/writable" do + it "exits with an error if home contains files that are not readable" do + doctor = Bundler::CLI::Doctor::Diagnose.new({}) + allow(doctor).to receive(:lookup_with_fiddle).and_return(false) allow(@stat).to receive(:uid) { Process.uid } allow(File).to receive(:writable?).with(@unwritable_file) { false } allow(File).to receive(:readable?).with(@unwritable_file) { false } - expect { Bundler::CLI::Doctor.new({}).run }.not_to raise_error + expect { doctor.run }.not_to raise_error expect(@stdout.string).to include( - "Files exist in the Bundler home that are not readable/writable by the current user. These files are:\n - #{@unwritable_file}" + "Files exist in the Bundler home that are not readable by the current user. These files are:\n - #{@unwritable_file}" ) expect(@stdout.string).not_to include("No issues") end + it "exits without an error if home contains files that are not writable" do + doctor = Bundler::CLI::Doctor::Diagnose.new({}) + allow(doctor).to receive(:lookup_with_fiddle).and_return(false) + allow(@stat).to receive(:uid) { Process.uid } + allow(File).to receive(:writable?).with(@unwritable_file) { false } + allow(File).to receive(:readable?).with(@unwritable_file) { true } + expect { doctor.run }.not_to raise_error + expect(@stdout.string).not_to include( + "Files exist in the Bundler home that are not readable by the current user. These files are:\n - #{@unwritable_file}" + ) + expect(@stdout.string).to include("No issues") + end + context "when home contains files that are not owned by the current process", :permissions do before(:each) do allow(@stat).to receive(:uid) { 0o0000 } end it "exits with an error if home contains files that are not readable/writable and are not owned by the current user" do + doctor = Bundler::CLI::Doctor::Diagnose.new({}) + allow(doctor).to receive(:lookup_with_fiddle).and_return(false) allow(File).to receive(:writable?).with(@unwritable_file) { false } allow(File).to receive(:readable?).with(@unwritable_file) { false } - expect { Bundler::CLI::Doctor.new({}).run }.not_to raise_error + expect { doctor.run }.not_to raise_error expect(@stdout.string).to include( - "Files exist in the Bundler home that are owned by another user, and are not readable/writable. These files are:\n - #{@unwritable_file}" + "Files exist in the Bundler home that are owned by another user, and are not readable. These files are:\n - #{@unwritable_file}" ) expect(@stdout.string).not_to include("No issues") end it "exits with a warning if home contains files that are read/write but not owned by current user" do + doctor = Bundler::CLI::Doctor::Diagnose.new({}) + allow(doctor).to receive(:lookup_with_fiddle).and_return(false) allow(File).to receive(:writable?).with(@unwritable_file) { true } allow(File).to receive(:readable?).with(@unwritable_file) { true } - expect { Bundler::CLI::Doctor.new({}).run }.not_to raise_error + expect { doctor.run }.not_to raise_error expect(@stdout.string).to include( - "Files exist in the Bundler home that are owned by another user, but are still readable/writable. These files are:\n - #{@unwritable_file}" + "Files exist in the Bundler home that are owned by another user, but are still readable. These files are:\n - #{@unwritable_file}" ) expect(@stdout.string).not_to include("No issues") end @@ -141,7 +175,7 @@ RSpec.describe "bundle doctor" do context "when home contains filenames with special characters" do it "escape filename before command execute" do - doctor = Bundler::CLI::Doctor.new({}) + doctor = Bundler::CLI::Doctor::Diagnose.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") diff --git a/spec/bundler/commands/exec_spec.rb b/spec/bundler/commands/exec_spec.rb index e2b965c34c..aa35685be8 100644 --- a/spec/bundler/commands/exec_spec.rb +++ b/spec/bundler/commands/exec_spec.rb @@ -95,7 +95,7 @@ RSpec.describe "bundle exec" do end it "respects custom process title when loading through ruby" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + skip "https://github.com/ruby/rubygems/issues/3351" if Gem.win_platform? script_that_changes_its_own_title_and_checks_if_picked_up_by_ps_unix_utility = <<~'RUBY' Process.setproctitle("1-2-3-4-5-6-7") @@ -120,7 +120,7 @@ RSpec.describe "bundle exec" do end it "handles --keep-file-descriptors" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + skip "https://github.com/ruby/rubygems/issues/3351" if Gem.win_platform? require "tempfile" @@ -139,7 +139,7 @@ RSpec.describe "bundle exec" do G install_gemfile "source \"https://gem.repo1\"" - sys_exec "#{Gem.ruby} #{command.path}" + in_bundled_app "#{Gem.ruby} #{command.path}" expect(out).to be_empty expect(err).to be_empty @@ -153,7 +153,7 @@ RSpec.describe "bundle exec" do end it "can run a command named --verbose" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + skip "https://github.com/ruby/rubygems/issues/3351" if Gem.win_platform? install_gemfile "source \"https://gem.repo1\"; gem \"myrack\"" File.open(bundled_app("--verbose"), "w") do |f| @@ -193,75 +193,70 @@ RSpec.describe "bundle exec" do end context "with default gems" do - let(:default_irb_version) { ruby "gem 'irb', '< 999999'; require 'irb'; puts IRB::VERSION", raise_on_error: false } + # TODO: Switch to ERB::VERSION once Ruby 3.4 support is dropped, so all + # supported rubies include an `erb` gem version where `ERB::VERSION` is + # public + let(:default_erb_version) { ruby "require 'erb/version'; puts ERB.const_get(:VERSION)" } context "when not specified in Gemfile" do before do - skip "irb isn't a default gem" if default_irb_version.empty? - install_gemfile "source \"https://gem.repo1\"" end it "uses version provided by ruby" do - bundle "exec irb --version" + bundle "exec erb --version" - expect(out).to include(default_irb_version) + expect(stdboth).to eq(default_erb_version) end end context "when specified in Gemfile directly" do - let(:specified_irb_version) { "0.9.6" } + let(:specified_erb_version) { "2.0.0" } before do - skip "irb isn't a default gem" if default_irb_version.empty? - build_repo2 do - build_gem "irb", specified_irb_version do |s| - s.executables = "irb" + build_gem "erb", specified_erb_version do |s| + s.executables = "erb" end end install_gemfile <<-G source "https://gem.repo2" - gem "irb", "#{specified_irb_version}" + gem "erb", "#{specified_erb_version}" G end it "uses version specified" do - bundle "exec irb --version" + bundle "exec erb --version" - expect(out).to eq(specified_irb_version) - expect(err).to be_empty + expect(stdboth).to eq(specified_erb_version) end end context "when specified in Gemfile indirectly" do - let(:indirect_irb_version) { "0.9.6" } + let(:indirect_erb_version) { "2.0.0" } before do - skip "irb isn't a default gem" if default_irb_version.empty? - build_repo2 do - build_gem "irb", indirect_irb_version do |s| - s.executables = "irb" + build_gem "erb", indirect_erb_version do |s| + s.executables = "erb" end - build_gem "gem_depending_on_old_irb" do |s| - s.add_dependency "irb", indirect_irb_version + build_gem "gem_depending_on_old_erb" do |s| + s.add_dependency "erb", indirect_erb_version end end install_gemfile <<-G source "https://gem.repo2" - gem "gem_depending_on_old_irb" + gem "gem_depending_on_old_erb" G - - bundle "exec irb --version" end it "uses resolved version" do - expect(out).to eq(indirect_irb_version) - expect(err).to be_empty + bundle "exec erb --version" + + expect(stdboth).to eq(indirect_erb_version) end end end @@ -273,7 +268,7 @@ RSpec.describe "bundle exec" do end end - bundle "config set --global path.system true" + bundle_config_global "path.system true" install_gemfile <<-G source "https://gem.repo1" @@ -294,7 +289,7 @@ RSpec.describe "bundle exec" do end it "handles gems installed with --without" do - bundle "config set --local without middleware" + bundle_config "without middleware" install_gemfile <<-G source "https://gem.repo1" gem "myrack" # myrack 0.9.1 and 1.0 exist @@ -384,13 +379,13 @@ RSpec.describe "bundle exec" do it "raises a helpful error when exec'ing to something outside of the bundle" do system_gems(%w[myrack-1.0.0 myrack-0.9.1], path: default_bundle_path) - bundle "config set clean false" # want to keep the myrackup binstub + bundle_config "clean false" # want to keep the myrackup binstub install_gemfile <<-G source "https://gem.repo1" gem "foo" G [true, false].each do |l| - bundle "config set disable_exec_load #{l}" + bundle_config "disable_exec_load #{l}" bundle "exec myrackup", raise_on_error: false expect(err).to include "can't find executable myrackup for gem myrack. myrack is not currently included in the bundle, perhaps you meant to add it to your Gemfile?" end @@ -587,8 +582,8 @@ RSpec.describe "bundle exec" do gem "foo" G - bundle "config set auto_install 1" - bundle "exec myrackup" + bundle_config "auto_install 1" + bundle "exec myrackup", artifice: "compact_index" expect(out).to include("Installing foo 1.0") end @@ -602,8 +597,8 @@ RSpec.describe "bundle exec" do gem "foo", :git => "#{lib_path("foo-1.0")}" G - bundle "config set auto_install 1" - bundle "exec foo" + bundle_config "auto_install 1" + bundle "exec foo", artifice: "compact_index" expect(out).to include("Fetching myrack 0.9.1") expect(out).to include("Fetching #{lib_path("foo-1.0")}") expect(out.lines).to end_with("1.0") @@ -622,15 +617,15 @@ RSpec.describe "bundle exec" do system_gems "optparse-999.999.998", gem_repo: gem_repo4 - bundle "config set auto_install 1" - bundle "config set --local path vendor/bundle" + bundle_config "auto_install 1" + bundle_config "path vendor/bundle" gemfile <<~G source "https://gem.repo4" gem "fastlane" G - bundle "exec fastlane" + bundle "exec fastlane", artifice: "compact_index" expect(out).to include("Installing optparse 999.999.999") expect(out).to include("2.192.0") end @@ -657,7 +652,7 @@ RSpec.describe "bundle exec" do gem "foo", :path => "#{lib_path("foo-1.0")}" G - bundle "exec irb", raise_on_error: false + bundle "exec erb", 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(/missing value for attribute rubygems_version|rubygems_version must not be nil/) @@ -666,8 +661,6 @@ RSpec.describe "bundle exec" do describe "with gems bundled for deployment" do it "works when calling bundler from another script" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? - gemfile <<-G source "https://gem.repo1" @@ -678,7 +671,7 @@ RSpec.describe "bundle exec" do end Bundler.rubygems.extend(Monkey) G - bundle "config set path.system true" + bundle_config "path.system true" bundle "install" bundle "exec ruby -e '`bundle -v`; puts $?.success?'", env: { "BUNDLER_VERSION" => Bundler::VERSION } expect(out).to match("true") @@ -704,6 +697,27 @@ RSpec.describe "bundle exec" do end end + describe "running gem commands in presence of rubygems plugins" do + before do + build_repo4 do + build_gem "foo" do |s| + s.write "lib/rubygems_plugin.rb", "puts 'FAIL'" + end + end + + system_gems "foo-1.0", path: default_bundle_path, gem_repo: gem_repo4 + + install_gemfile <<-G + source "https://gem.repo4" + G + end + + it "does not load plugins outside of the bundle" do + bundle "exec #{gem_cmd} -v" + expect(out).not_to include("FAIL") + end + end + context "`load`ing a ruby file instead of `exec`ing" do let(:path) { bundled_app("ruby_executable") } let(:shebang) { "#!/usr/bin/env ruby" } @@ -714,13 +728,16 @@ RSpec.describe "bundle exec" do puts "EXEC: \#{caller.grep(/load/).empty? ? 'exec' : 'load'}" puts "ARGS: \#{$0} \#{ARGV.join(' ')}" puts "MYRACK: \#{MYRACK}" - process_title = `ps -o args -p \#{Process.pid}`.split("\n", 2).last.strip + if Gem.win_platform? + process_title = "ruby" + else + process_title = `ps -o args -p \#{Process.pid}`.split("\n", 2).last.strip + end puts "PROCESS: \#{process_title}" RUBY before do - bundled_app(path).open("w") {|f| f << executable } - bundled_app(path).chmod(0o755) + create_file(bundled_app(path), executable) install_gemfile <<-G source "https://gem.repo1" @@ -732,9 +749,11 @@ RSpec.describe "bundle exec" do let(:args) { "ARGS: #{path} arg1 arg2" } let(:myrack) { "MYRACK: 1.0.0" } let(:process) do - title = "PROCESS: #{path}" - title += " arg1 arg2" - title + if Gem.win_platform? + "PROCESS: ruby" + else + "PROCESS: #{path} arg1 arg2" + end end let(:exit_code) { 0 } let(:expected) { [exec, args, myrack, process].join("\n") } @@ -743,8 +762,6 @@ RSpec.describe "bundle exec" do 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? - subject expect(exitstatus).to eq(exit_code) expect(err).to eq(expected_err) @@ -756,8 +773,6 @@ RSpec.describe "bundle exec" do context "with exit 0" do it "runs" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? - subject expect(exitstatus).to eq(exit_code) expect(err).to eq(expected_err) @@ -769,8 +784,6 @@ RSpec.describe "bundle exec" do let(:exit_code) { 99 } it "runs" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? - subject expect(exitstatus).to eq(exit_code) expect(err).to eq(expected_err) @@ -792,7 +805,7 @@ RSpec.describe "bundle exec" do end it "runs" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + skip "https://github.com/ruby/rubygems/issues/3351" if Gem.win_platform? subject expect(exitstatus).to eq(exit_code) @@ -809,7 +822,12 @@ RSpec.describe "bundle exec" do let(:expected) { "" } it "runs" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + # it's empty, so `create_file` won't add executable permission and bat scripts on Windows + bundled_app(path).chmod(0o755) + path.sub_ext(".bat").write <<~SCRIPT if Gem.win_platform? + @ECHO OFF + @"ruby.exe" "%~dpn0" %* + SCRIPT subject expect(exitstatus).to eq(exit_code) @@ -822,12 +840,10 @@ RSpec.describe "bundle exec" do let(:executable) { super() << "\nraise 'ERROR'" } let(:exit_code) { 1 } let(:expected_err) do - /\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\)/ + /\Abundler: failed to load command: #{Regexp.quote(path.to_s)} \(#{Regexp.quote(path.to_s)}\)\n#{Regexp.quote(path.to_s)}:[0-9]+:in [`']<top \(required\)>': ERROR \(RuntimeError\)/ end it "runs like a normally executed executable" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? - subject expect(exitstatus).to eq(exit_code) expect(err).to match(expected_err) @@ -842,8 +858,6 @@ RSpec.describe "bundle exec" do let(:expected) { super() } it "runs" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? - subject expect(exitstatus).to eq(exit_code) expect(err).to eq(expected_err) @@ -855,8 +869,6 @@ RSpec.describe "bundle exec" do let(:shebang) { "#!#{Gem.ruby}" } it "runs" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? - subject expect(exitstatus).to eq(exit_code) expect(err).to eq(expected_err) @@ -887,8 +899,6 @@ RSpec.describe "bundle exec" do EOS it "runs" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? - subject expect(exitstatus).to eq(exit_code) expect(err).to eq(expected_err) @@ -911,8 +921,6 @@ RSpec.describe "bundle exec" do 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.") @@ -925,8 +933,6 @@ RSpec.describe "bundle exec" do let(:exit_code) { 1 } it "runs" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? - subject expect(exitstatus).to eq(exit_code) expect(err).to eq(expected_err) @@ -936,15 +942,19 @@ RSpec.describe "bundle exec" do context "when disable_exec_load is set" do let(:exec) { "EXEC: exec" } - let(:process) { "PROCESS: ruby #{path} arg1 arg2" } + let(:process) do + if Gem.win_platform? + "PROCESS: ruby" + else + "PROCESS: ruby #{path} arg1 arg2" + end + end before do - bundle "config set disable_exec_load true" + bundle_config "disable_exec_load true" end it "runs" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? - subject expect(exitstatus).to eq(exit_code) expect(err).to eq(expected_err) @@ -967,8 +977,6 @@ RSpec.describe "bundle exec" do EOS it "runs" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? - subject expect(exitstatus).to eq(exit_code) expect(err).to eq(expected_err) @@ -985,8 +993,6 @@ RSpec.describe "bundle exec" do EOS it "runs" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? - subject expect(exitstatus).to eq(exit_code) expect(err).to eq(expected_err) @@ -1003,8 +1009,6 @@ RSpec.describe "bundle exec" do EOS it "runs" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? - subject expect(exitstatus).to eq(exit_code) expect(err).to eq(expected_err) @@ -1016,7 +1020,7 @@ RSpec.describe "bundle exec" do context "signal handling" do let(:test_signals) do open3_reserved_signals = %w[CHLD CLD PIPE] - reserved_signals = %w[SEGV BUS ILL FPE VTALRM KILL STOP EXIT] + reserved_signals = %w[SEGV BUS ILL FPE ABRT IOT VTALRM KILL STOP EXIT] bundler_signals = %w[INT] Signal.list.keys - (bundler_signals + reserved_signals + open3_reserved_signals) @@ -1030,7 +1034,7 @@ RSpec.describe "bundle exec" do puts 'Started' # For process sync STDOUT.flush sleep 1 # ignore quality_spec - raise "Didn't receive INT at all" + raise RuntimeError, "Didn't receive expected INT" end.join rescue Interrupt puts "foo" @@ -1038,7 +1042,7 @@ RSpec.describe "bundle exec" do RUBY it "receives the signal" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + skip "https://github.com/ruby/rubygems/issues/3351" if Gem.win_platform? bundle("exec #{path}") do |_, o, thr| o.gets # Consumes 'Started' and ensures that thread has started @@ -1061,7 +1065,7 @@ RSpec.describe "bundle exec" do RUBY it "makes sure no unexpected signals are restored to DEFAULT" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + skip "https://github.com/ruby/rubygems/issues/3351" if Gem.win_platform? test_signals.each do |n| Signal.trap(n, "IGNORE") @@ -1078,13 +1082,13 @@ RSpec.describe "bundle exec" do context "nested bundle exec" do context "when bundle in a local path" do before do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + skip "https://github.com/ruby/rubygems/issues/3351" if Gem.win_platform? gemfile <<-G source "https://gem.repo1" gem "myrack" G - bundle "config set path vendor/bundler" + bundle_config "path vendor/bundler" bundle :install end @@ -1102,7 +1106,7 @@ RSpec.describe "bundle exec" do context "when Kernel.require uses extra monkeypatches" do before do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + skip "https://github.com/ruby/rubygems/issues/3351" if Gem.win_platform? install_gemfile "source \"https://gem.repo1\"" end @@ -1145,16 +1149,14 @@ RSpec.describe "bundle exec" do context "when gemfile and path are configured", :ruby_repo do before do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? - build_repo2 do build_gem "rails", "6.1.0" do |s| s.executables = "rails" end end - bundle "config set path vendor/bundle" - bundle "config set gemfile gemfiles/myrack_6_1.gemfile" + bundle_config "path vendor/bundle" + bundle_config "gemfile gemfiles/myrack_6_1.gemfile" gemfile(bundled_app("gemfiles/myrack_6_1.gemfile"), <<~RUBY) source "https://gem.repo2" @@ -1205,18 +1207,18 @@ RSpec.describe "bundle exec" do 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 } it "only leaves the default gem in the stdlib available" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? - skip "openssl isn't a default gem" if expected.empty? + default_openssl_version = ruby "require 'openssl'; puts OpenSSL::VERSION" + + skip "https://github.com/ruby/rubygems/issues/3351" if Gem.win_platform? install_gemfile "source \"https://gem.repo1\"" # must happen before installing the broken system gem build_repo4 do build_gem "openssl", openssl_version do |s| s.write("lib/openssl.rb", <<-RUBY) - raise "custom openssl should not be loaded, it's not in the gemfile!" + raise ArgumentError, "custom openssl should not be loaded" RUBY end end @@ -1234,10 +1236,10 @@ RSpec.describe "bundle exec" do 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}", env: env)).to eq(default_openssl_version) + expect(bundle("exec bundle exec #{file}", env: env)).to eq(default_openssl_version) + expect(bundle("exec ruby #{file}", env: env)).to eq(default_openssl_version) + expect(run(file.read, artifice: nil, env: env)).to eq(default_openssl_version) 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? @@ -1250,7 +1252,7 @@ RSpec.describe "bundle exec" do context "with a git gem that includes extensions", :ruby_repo do before do build_git "simple_git_binary", &:add_c_extension - bundle "config set --local path .bundle" + bundle_config "path .bundle" install_gemfile <<-G source "https://gem.repo1" gem "simple_git_binary", :git => '#{lib_path("simple_git_binary-1.0")}' @@ -1262,7 +1264,7 @@ RSpec.describe "bundle exec" do end it "allows calling bundle install after removing gem.build_complete" do - FileUtils.rm_rf Dir[bundled_app(".bundle/**/gem.build_complete")] + FileUtils.rm_r Dir[bundled_app(".bundle/**/gem.build_complete")] bundle "exec #{Gem.ruby} -S bundle install" end end diff --git a/spec/bundler/commands/fund_spec.rb b/spec/bundler/commands/fund_spec.rb index 6f4e61da30..5883b8a63a 100644 --- a/spec/bundler/commands/fund_spec.rb +++ b/spec/bundler/commands/fund_spec.rb @@ -73,7 +73,7 @@ RSpec.describe "bundle fund" do end it "considers fund information for installed optional dependencies" do - bundle "config set with whatever" + bundle_config "with whatever" install_gemfile <<-G source "https://gem.repo2" diff --git a/spec/bundler/commands/info_spec.rb b/spec/bundler/commands/info_spec.rb index 42f288a1d8..a26b1696fb 100644 --- a/spec/bundler/commands/info_spec.rb +++ b/spec/bundler/commands/info_spec.rb @@ -56,12 +56,12 @@ RSpec.describe "bundle info" do 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" + it "doesn't claim that bundler is missing, even if using a custom path without bundler there" do + bundle_config "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) + expect(err).not_to match(/The gem bundler is missing/i) end it "complains if gem not in bundle" do @@ -69,27 +69,27 @@ RSpec.describe "bundle info" do expect(err).to eq("Could not find gem 'missing'.") end - it "warns if path no longer exists on disk" do - FileUtils.rm_rf(default_bundle_path("gems", "rails-2.3.2")) + it "warns if path does not exist on disk, but specification is there" do + FileUtils.rm_r(default_bundle_path("gems", "rails-2.3.2")) bundle "info rails --path" - expect(err).to include("The gem rails has been deleted.") + expect(err).to include("The gem rails is missing.") 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("The gem rails is missing.") 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("The gem rails is missing.") 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 + context "given a default gem shipped in ruby", :ruby_repo do it "prints information about the default gem" do - bundle "info rdoc" - expect(out).to include("* rdoc") + bundle "info json" + expect(out).to include("* json") expect(out).to include("Default Gem: yes") end end @@ -235,7 +235,7 @@ RSpec.describe "bundle info" do context "with without configured" do it "does not find the gem, but gives a helpful error" do - bundle "config without test" + bundle_config "without test" install_gemfile <<-G source "https://gem.repo1" diff --git a/spec/bundler/commands/init_spec.rb b/spec/bundler/commands/init_spec.rb index 538e61fd47..989d6fa812 100644 --- a/spec/bundler/commands/init_spec.rb +++ b/spec/bundler/commands/init_spec.rb @@ -119,7 +119,7 @@ RSpec.describe "bundle init" do end context "when init_gems_rb setting is enabled" do - before { bundle "config set init_gems_rb true" } + before { bundle_config "init_gems_rb true" } it "generates a gems.rb" do bundle :init diff --git a/spec/bundler/commands/inject_spec.rb b/spec/bundler/commands/inject_spec.rb deleted file mode 100644 index 193806a02a..0000000000 --- a/spec/bundler/commands/inject_spec.rb +++ /dev/null @@ -1,117 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe "bundle inject", bundler: "< 3" do - before :each do - gemfile <<-G - source "https://gem.repo1" - gem "myrack" - G - end - - context "without a lockfile" do - it "locks with the injected gems" do - expect(bundled_app_lock).not_to exist - bundle "inject 'myrack-obama' '> 0'" - expect(bundled_app_lock.read).to match(/myrack-obama/) - end - end - - context "with a lockfile" do - before do - bundle "install" - end - - it "adds the injected gems to the Gemfile" do - expect(bundled_app_gemfile.read).not_to match(/myrack-obama/) - bundle "inject 'myrack-obama' '> 0'" - expect(bundled_app_gemfile.read).to match(/myrack-obama/) - end - - it "locks with the injected gems" do - expect(bundled_app_lock.read).not_to match(/myrack-obama/) - bundle "inject 'myrack-obama' '> 0'" - expect(bundled_app_lock.read).to match(/myrack-obama/) - end - end - - context "with injected gems already in the Gemfile" do - it "doesn't add existing gems" do - bundle "inject 'myrack' '> 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 - expect(err).to eq(<<-E.strip) -ERROR: "bundle inject" was called with arguments ["gem_name", "1", "v"] -Usage: "bundle inject GEM VERSION" - E - end - end - - context "with source option" do - it "add gem with source option in gemfile" do - bundle "inject 'foo' '>0' --source https://gem.repo1" - gemfile = bundled_app_gemfile.read - str = "gem \"foo\", \"> 0\", :source => \"https://gem.repo1\"" - expect(gemfile).to include str - end - end - - context "with group option" do - it "add gem with group option in gemfile" do - bundle "inject 'myrack-obama' '>0' --group=development" - gemfile = bundled_app_gemfile.read - str = "gem \"myrack-obama\", \"> 0\", :group => :development" - expect(gemfile).to include str - end - - it "add gem with multiple groups in gemfile" do - bundle "inject 'myrack-obama' '>0' --group=development,test" - gemfile = bundled_app_gemfile.read - str = "gem \"myrack-obama\", \"> 0\", :groups => [:development, :test]" - expect(gemfile).to include str - end - end - - context "when frozen" do - before do - bundle "install" - if Bundler.feature_flag.bundler_3_mode? - bundle "config set --local deployment true" - else - bundle "config set --local frozen true" - end - end - - it "injects anyway" do - bundle "inject 'myrack-obama' '> 0'" - expect(bundled_app_gemfile.read).to match(/myrack-obama/) - end - - it "locks with the injected gems" do - expect(bundled_app_lock.read).not_to match(/myrack-obama/) - bundle "inject 'myrack-obama' '> 0'" - expect(bundled_app_lock.read).to match(/myrack-obama/) - end - - it "restores frozen afterwards" do - bundle "inject 'myrack-obama' '> 0'" - config = Psych.load(bundled_app(".bundle/config").read) - expect(config["BUNDLE_DEPLOYMENT"] || config["BUNDLE_FROZEN"]).to eq("true") - end - - it "doesn't allow Gemfile changes" do - gemfile <<-G - source "https://gem.repo1" - gem "myrack-obama" - G - bundle "inject 'myrack' '> 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(/myrack-obama/) - end - end -end diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb index 992777722c..3b24434dc7 100644 --- a/spec/bundler/commands/install_spec.rb +++ b/spec/bundler/commands/install_spec.rb @@ -29,27 +29,81 @@ 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 - gemfile <<-G + it "creates lockfile based on the lockfile method in Gemfile" do + install_gemfile <<-G + lockfile "OmgFile.lock" + source "https://gem.repo1" + gem "myrack", "1.0" + G + + bundle "install" + + expect(bundled_app("OmgFile.lock")).to exist + end + + it "creates lockfile using BUNDLE_LOCKFILE instead of lockfile method" do + ENV["BUNDLE_LOCKFILE"] = "ReallyOmgFile.lock" + install_gemfile <<-G + lockfile "OmgFile.lock" + source "https://gem.repo1" + gem "myrack", "1.0" + G + + expect(bundled_app("ReallyOmgFile.lock")).to exist + expect(bundled_app("OmgFile.lock")).not_to exist + ensure + ENV.delete("BUNDLE_LOCKFILE") + end + + it "creates lockfile based on --lockfile option is given" do + gemfile bundled_app("OmgFile"), <<-G + source "https://gem.repo1" + gem "myrack", "1.0" + G + + bundle "install --gemfile OmgFile --lockfile ReallyOmgFile.lock" + + expect(bundled_app("ReallyOmgFile.lock")).to exist + end + + it "does not make a lockfile if lockfile false is used in Gemfile" do + install_gemfile <<-G + lockfile false + source "https://gem.repo1" + gem 'myrack' + G + + expect(bundled_app_lock).not_to exist + end + + it "does not create ./.bundle by default" do + install_gemfile <<-G source "https://gem.repo1" gem "myrack" G - bundle :install # can't use install_gemfile since it sets retry expect(bundled_app(".bundle")).not_to exist end + it "will create a ./.bundle by default", bundler: "5" do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + expect(bundled_app(".bundle")).to exist + end + it "does not create ./.bundle by default when installing to system gems" do - gemfile <<-G + install_gemfile <<-G, env: { "BUNDLE_PATH__SYSTEM" => "true" } source "https://gem.repo1" gem "myrack" G - bundle :install, env: { "BUNDLE_PATH__SYSTEM" => "true" } # can't use install_gemfile since it sets retry expect(bundled_app(".bundle")).not_to exist end - it "creates lock files based on the Gemfile name" do + it "creates lockfiles based on the Gemfile name" do gemfile bundled_app("OmgFile"), <<-G source "https://gem.repo1" gem "myrack", "1.0" @@ -60,6 +114,29 @@ RSpec.describe "bundle install with gem sources" do expect(bundled_app("OmgFile.lock")).to exist end + it "doesn't create a lockfile if --no-lock option is given" do + gemfile bundled_app("OmgFile"), <<-G + source "https://gem.repo1" + gem "myrack", "1.0" + G + + bundle "install --gemfile OmgFile --no-lock" + + expect(bundled_app("OmgFile.lock")).not_to exist + end + + it "doesn't create a lockfile if --no-lock and --lockfile options are given" do + gemfile bundled_app("OmgFile"), <<-G + source "https://gem.repo1" + gem "myrack", "1.0" + G + + bundle "install --gemfile OmgFile --no-lock --lockfile ReallyOmgFile.lock" + + expect(bundled_app("OmgFile.lock")).not_to exist + expect(bundled_app("ReallyOmgFile.lock")).not_to exist + end + it "doesn't delete the lockfile if one already exists" do install_gemfile <<-G source "https://gem.repo1" @@ -100,7 +177,7 @@ RSpec.describe "bundle install with gem sources" do gem 'myrack' G - FileUtils.rm_rf(default_bundle_path("gems/myrack-1.0.0")) + FileUtils.rm_r(default_bundle_path("gems/myrack-1.0.0")) bundle "install --verbose" @@ -109,6 +186,23 @@ RSpec.describe "bundle install with gem sources" do expect(the_bundle).to include_gems("myrack 1.0.0") end + it "does not state that it's constantly reinstalling empty gems" do + build_repo4 do + build_gem "empty", "1.0.0", no_default: true + end + + install_gemfile <<~G + source "https://gem.repo4" + + gem "empty" + G + gem_dir = default_bundle_path("gems/empty-1.0.0") + expect(gem_dir).to be_empty + + bundle "install --verbose" + expect(out).not_to include("Installing empty") + end + it "fetches gems when multiple versions are specified" do install_gemfile <<-G source "https://gem.repo1" @@ -243,7 +337,7 @@ RSpec.describe "bundle install with gem sources" do gem "myrack" G - expect(last_command.stdboth).to include(plugin_msg) + expect(stdboth).to include(plugin_msg) end describe "with a gem that installs multiple platforms" do @@ -281,7 +375,7 @@ RSpec.describe "bundle install with gem sources" do end it "installs gems for windows" do - simulate_platform x86_mswin32 do + simulate_platform "x86-mswin32" do install_gemfile <<-G source "https://gem.repo1" gem "platform_specific" @@ -290,53 +384,17 @@ RSpec.describe "bundle install with gem sources" do expect(the_bundle).to include_gems("platform_specific 1.0 x86-mswin32") end end - end - describe "doing bundle install foo" do - before do - gemfile <<-G - source "https://gem.repo1" - gem "myrack" - G - end - - it "works" do - bundle "config set --local path vendor" - bundle "install" - expect(the_bundle).to include_gems "myrack 1.0" - end - - 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 "myrack 1.0" - end - - 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" - expect(the_bundle).to include_gems "myrack 1.0" - end - end - - it "finds gems in multiple sources", bundler: "< 3" do - build_repo2 do - build_gem "myrack", "1.2" do |s| - s.executables = "myrackup" + it "installs gems for aarch64-mingw-ucrt" do + simulate_platform "aarch64-mingw-ucrt" do + install_gemfile <<-G + source "https://gem.repo1" + gem "platform_specific" + G end - end - - install_gemfile <<-G, artifice: "compact_index_extra" - source "https://gemserver.test" - source "https://gemserver.test/extra" - - gem "activesupport", "1.2.3" - gem "myrack", "1.2" - G - expect(the_bundle).to include_gems "myrack 1.2", "activesupport 1.2.3" + expect(out).to include("Installing platform_specific 1.0 (aarch64-mingw-ucrt)") + end end it "gives useful errors if no global sources are set, and gems not installed locally, with and without a lockfile" do @@ -358,7 +416,7 @@ RSpec.describe "bundle install with gem sources" do myrack BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install", raise_on_error: false @@ -446,7 +504,7 @@ 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 + it "does not warn if a gem is added once in Gemfile and also inside a gemspec as a development dependency, with compatible requirements" do build_lib "my-gem", path: bundled_app do |s| s.add_development_dependency "rubocop", "~> 1.36.0" end @@ -466,14 +524,32 @@ RSpec.describe "bundle install with gem sources" do 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") + expect(err).to be_empty + + expect(the_bundle).to include_gems("rubocop 1.36.0") + end + + it "raises an error if a gem is added once in Gemfile and also inside a gemspec as a development dependency, with incompatible 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 "https://gem.repo4" + + gemspec + + gem "rubocop", "~> 1.37.0", group: :development + G + + bundle :install, raise_on_error: false - # 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") + expect(err).to include("The rubocop dependency has conflicting requirements in Gemfile (~> 1.37.0) and gemspec (~> 1.36.0)") end it "includes the gem without warning if two gemspecs add it with the same requirement" do @@ -559,36 +635,31 @@ RSpec.describe "bundle install with gem sources" do bundle :install, raise_on_error: false - expect(err).to include("Two gemspecs have conflicting requirements on the same gem: rubocop (~> 1.36.0, development) and rubocop (~> 2.0, development). Bundler cannot continue.") + expect(err).to include("Two gemspec development dependencies have conflicting requirements on the same gem: rubocop (~> 1.36.0) and rubocop (~> 2.0). Bundler cannot continue.") 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" + it "errors out if a gem is specified in a gemspec and in the Gemfile" do + gem = tmp("my-gem-1") + + build_lib "rubocop", path: gem do |s| + s.add_development_dependency "rubocop", "~> 1.0" end build_repo4 do - build_gem "rails", "7.0.8" + build_gem "rubocop" end gemfile <<~G source "https://gem.repo4" - gem "rails", "~> 7.0.8" - - gemspec + gem "rubocop", :path => "#{gem}" + gemspec path: "#{gem}" 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") + bundle :install, raise_on_error: false - # 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") + expect(err).to include("There was an error parsing `Gemfile`: You cannot specify the same gem twice coming from different sources.") + expect(err).to include("You specified that rubocop (>= 0) should come from source at `#{gem}` and gemspec at `#{gem}`") 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 @@ -673,8 +744,6 @@ RSpec.describe "bundle install with gem sources" do end 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 source "https://gem.repo1" source "http://0.0.0.0:9384" do @@ -682,20 +751,19 @@ RSpec.describe "bundle install with gem sources" do end G - expect(err).to include("Could not fetch specs from http://0.0.0.0:9384/") + expect(err).to eq("Could not reach host 0.0.0.0:9384. Check your network connection and try again.") expect(err).not_to include("file://") end it "fails gracefully when downloading an invalid specification from the full index" do 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"]] + invalid_deps = [["ruby-ajp", ">= 0.2.0"], ["rails", ">= 0.14"]] s. instance_variable_get(:@spec). - instance_variable_set(:@dependencies, bad_deps) - - raise "failed to set bad deps" unless s.dependencies == bad_deps + instance_variable_set(:@dependencies, invalid_deps) end + build_gem "ruby-ajp", "1.0.0" end @@ -705,7 +773,7 @@ RSpec.describe "bundle install with gem sources" do gem "ajp-rails", "0.0.0" G - expect(last_command.stdboth).not_to match(/Error Report/i) + expect(stdboth).not_to match(/Error Report/i) expect(err).to include("An error occurred while installing ajp-rails (0.0.0), and Bundler cannot continue."). and include("Bundler::APIResponseInvalidDependenciesError") end @@ -765,10 +833,10 @@ RSpec.describe "bundle install with gem sources" do DEPENDENCIES #{checksums} RUBY VERSION - #{Bundler::RubyVersion.system} + #{Bundler::RubyVersion.system} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -791,10 +859,10 @@ RSpec.describe "bundle install with gem sources" do DEPENDENCIES #{checksums} RUBY VERSION - #{Bundler::RubyVersion.system} + #{Bundler::RubyVersion.system} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -849,7 +917,7 @@ RSpec.describe "bundle install with gem sources" do describe "when requesting a quiet install via --quiet" do it "should be quiet if there are no warnings" do - bundle "config set force_ruby_platform true" + bundle_config "force_ruby_platform true" gemfile <<-G source "https://gem.repo1" @@ -862,7 +930,7 @@ RSpec.describe "bundle install with gem sources" do end it "should still display warnings and errors" do - bundle "config set force_ruby_platform true" + bundle_config "force_ruby_platform true" create_file("install_with_warning.rb", <<~RUBY) require "#{lib_dir}/bundler" @@ -907,7 +975,7 @@ RSpec.describe "bundle install with gem sources" do it "should display a proper message to explain the problem" do FileUtils.chmod(0o500, bundle_path) - bundle "config set --local path vendor" + bundle_config "path vendor" bundle :install, raise_on_error: false expect(err).to include(bundle_path.to_s) expect(err).to include("grant executable permissions") @@ -927,7 +995,7 @@ RSpec.describe "bundle install with gem sources" do it "should display a proper message to explain the problem" do FileUtils.chmod("-x", gems_path) - bundle "config set --local path vendor" + bundle_config "path vendor" begin bundle :install, raise_on_error: false @@ -957,7 +1025,7 @@ RSpec.describe "bundle install with gem sources" do it "should display a proper message to explain the problem" do FileUtils.chmod("-x", full_gem_path) - bundle "config set --local path vendor" + bundle_config "path vendor" begin bundle :install, raise_on_error: false @@ -987,7 +1055,7 @@ RSpec.describe "bundle install with gem sources" do it "should display a proper message to explain the problem" do FileUtils.chmod("-x", bin_dir) - bundle "config set --local path vendor" + bundle_config "path vendor" begin bundle :install, raise_on_error: false @@ -1017,7 +1085,7 @@ RSpec.describe "bundle install with gem sources" do it "should display a proper message to explain the problem" do FileUtils.chmod("-w", bin_dir) - bundle "config set --local path vendor" + bundle_config "path vendor" begin bundle :install, raise_on_error: false @@ -1047,7 +1115,7 @@ RSpec.describe "bundle install with gem sources" do it "should display a proper message to explain the problem" do FileUtils.chmod("-x", extensions_path) - bundle "config set --local path vendor" + bundle_config "path vendor" begin bundle :install, raise_on_error: false @@ -1082,7 +1150,7 @@ RSpec.describe "bundle install with gem sources" do end it "should display a proper message to explain the problem" do - bundle "config set --local path vendor" + bundle_config "path vendor" bundle :install expect(out).to include("Bundle complete!") expect(err).to be_empty @@ -1090,7 +1158,7 @@ RSpec.describe "bundle install with gem sources" do FileUtils.chmod("-x", foo_path) begin - bundle "install --redownload", raise_on_error: false + bundle "install --force", raise_on_error: false ensure FileUtils.chmod("+x", foo_path) end @@ -1118,7 +1186,7 @@ RSpec.describe "bundle install with gem sources" do end it "should still work" do - bundle "config set --local path vendor" + bundle_config "path vendor" bundle :install expect(out).to include("Bundle complete!") expect(err).to be_empty @@ -1126,7 +1194,7 @@ RSpec.describe "bundle install with gem sources" do FileUtils.chmod("-w", gem_home) begin - bundle "install --redownload" + bundle "install --force" ensure FileUtils.chmod("+w", gem_home) end @@ -1153,14 +1221,14 @@ RSpec.describe "bundle install with gem sources" do end it "should display a proper message to explain the problem" do - bundle "config set --local path vendor" + bundle_config "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 + bundle "install --force", raise_on_error: false expect(err).to include("Bundler cannot reinstall foo-1.0.0 because there's a previous installation of it at #{gems_path}/foo-1.0.0 that is unsafe to remove") end @@ -1184,7 +1252,7 @@ RSpec.describe "bundle install with gem sources" do end it "does not try to remove the directory and thus don't abort with an error about unsafe directory removal" do - bundle "config set --local path vendor" + bundle_config "path vendor" FileUtils.mkdir_p(gems_path) FileUtils.chmod(0o777, gems_path) @@ -1208,7 +1276,7 @@ RSpec.describe "bundle install with gem sources" do it "should display a proper message to explain the problem" do FileUtils.chmod(0o500, cache_path) - bundle "config set --local path vendor" + bundle_config "path vendor" bundle :install, raise_on_error: false expect(err).to include(cache_path.to_s) expect(err).to include("grant write permissions") @@ -1223,7 +1291,7 @@ RSpec.describe "bundle install with gem sources" do source "https://gem.repo1" gem 'myrack' G - bundle "config path vendor/bundle" + bundle_config "path vendor/bundle" bundle :install expect(out).to include("Bundle complete!") expect(err).to be_empty @@ -1238,6 +1306,158 @@ RSpec.describe "bundle install with gem sources" do end end + describe "when using umask 002 and setgid bit", :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 "https://gem.repo4" + gem 'foo' + G + + FileUtils.mkdir_p(gems_path) + FileUtils.chmod("g+s", gems_path) + end + + it "should create the gem directory with proper permissions" do + with_umask(0o002) do + bundle_config "path vendor" + bundle :install + expect(out).to include("Bundle complete!") + expect(err).to be_empty + # Linux's SysV-derived mkdir(2) propagates the set-group-ID bit + # from the parent directory to newly created subdirectories. BSD + # (including macOS) inherits the parent's group via mkdir(2) but + # does not copy the set-group-ID bit itself, so the expected + # mode differs by platform. + expected = RUBY_PLATFORM.include?("darwin") ? 0o0775 : 0o2775 + expect(File.stat(foo_path).mode & 0o7777).to eq(expected) + end + end + end + + describe "parallel make" do + before do + unless Gem::Installer.private_method_defined?(:build_jobs) + skip "This example is runnable when RubyGems::Installer implements `build_jobs`" + end + + @old_makeflags = ENV["MAKEFLAGS"] + @gemspec = nil + + extconf_code = <<~CODE + require "mkmf" + create_makefile("foo") + CODE + + build_repo4 do + build_gem "mypsych", "4.0.6" do |s| + @gemspec = s + extension = "ext/mypsych/extconf.rb" + s.extensions = extension + + s.write(extension, extconf_code) + end + end + end + + after do + if @old_makeflags + ENV["MAKEFLAGS"] = @old_makeflags + else + ENV.delete("MAKEFLAGS") + end + end + + it "doesn't pass down -j to make when MAKEFLAGS is set" do + ENV["MAKEFLAGS"] = "-j1" + + install_gemfile(<<~G, env: { "BUNDLE_JOBS" => "8" }) + source "https://gem.repo4" + gem "mypsych" + G + + gem_make_out = File.read(File.join(@gemspec.extension_dir, "gem_make.out")) + + expect(gem_make_out).not_to include("make -j8") + end + + it "pass down the BUNDLE_JOBS to RubyGems when running the compilation of an extension" do + ENV.delete("MAKEFLAGS") + + install_gemfile(<<~G, env: { "BUNDLE_JOBS" => "8" }) + source "https://gem.repo4" + gem "mypsych" + G + + gem_make_out = File.read(File.join(@gemspec.extension_dir, "gem_make.out")) + + expect(gem_make_out).to include("make -j8") + end + + it "uses nprocessors by default" do + ENV.delete("MAKEFLAGS") + + install_gemfile(<<~G) + source "https://gem.repo4" + gem "mypsych" + G + + gem_make_out = File.read(File.join(@gemspec.extension_dir, "gem_make.out")) + + expect(gem_make_out).to include("make -j#{Etc.nprocessors + 1}") + end + end + + describe "when a native extension requires a transitive dependency at build time" do + before do + build_repo4 do + build_gem "alpha", "1.0.0" do |s| + extension = "ext/alpha/extconf.rb" + s.extensions = extension + s.write(extension, <<~CODE) + require "mkmf" + sleep 1 + create_makefile("alpha") + CODE + s.write "lib/alpha.rb", "ALPHA = '1.0.0'" + end + + build_gem "beta", "1.0.0" do |s| + s.add_dependency "alpha" + s.write "lib/beta.rb", "require 'alpha'\nBETA = '1.0.0'" + end + + build_gem "gamma", "1.0.0" do |s| + s.add_dependency "beta" + extension = "ext/gamma/extconf.rb" + s.extensions = extension + s.write(extension, <<~EXTCONF) + require "beta" + require "mkmf" + create_makefile("gamma") + EXTCONF + end + end + end + + it "installs successfully" do + install_gemfile <<~G + source "https://gem.repo4" + gem "gamma" + G + + expect(the_bundle).to include_gems "alpha 1.0.0", "beta 1.0.0", "gamma 1.0.0" + end + end + describe "when configured path is UTF-8 and a file inside a gem package too" do let(:app_path) do path = tmp("♥") @@ -1258,7 +1478,7 @@ RSpec.describe "bundle install with gem sources" do end it "works" do - bundle "config path #{app_path}/vendor/bundle", dir: app_path + bundle "config set path #{app_path}/vendor/bundle", dir: app_path install_gemfile app_path.join("Gemfile"),<<~G, dir: app_path source "https://gem.repo4" @@ -1273,7 +1493,7 @@ RSpec.describe "bundle install with gem sources" do source "https://gem.repo1" gem "myrack" G - bundle "config set --local path bundle" + bundle_config "path bundle" bundle "install", standalone: true end @@ -1331,7 +1551,7 @@ RSpec.describe "bundle install with gem sources" do libv8 BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L simulate_platform("x86_64-linux", &example) @@ -1340,7 +1560,8 @@ RSpec.describe "bundle install with gem sources" do it "adds the current platform to the lockfile" do bundle "install --verbose" - expect(out).to include("re-resolving dependencies because your lockfile does not include the current platform") + expect(out).to include("re-resolving dependencies because your lockfile is missing the current platform") + expect(out).not_to include("you are adding a new platform to your lockfile") expect(lockfile).to eq <<~L GEM @@ -1357,12 +1578,12 @@ RSpec.describe "bundle install with gem sources" do libv8 BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end it "fails loudly if frozen mode set" do - bundle "config set --local deployment true" + bundle_config "deployment true" bundle "install", raise_on_error: false expect(err).to eq( @@ -1433,12 +1654,12 @@ RSpec.describe "bundle install with gem sources" do #{Bundler::RubyVersion.system} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end it "automatically fixes the lockfile" do - bundle "config set --local path vendor/bundle" + bundle_config "path vendor/bundle" simulate_platform "x86_64-linux" do bundle "install", artifice: "compact_index" @@ -1474,14 +1695,64 @@ RSpec.describe "bundle install with gem sources" do loofah (~> 2.12.0) #{checksums} RUBY VERSION - #{Bundler::RubyVersion.system} + #{Bundler::RubyVersion.system} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end + context "when lockfile has incorrect dependencies" do + before do + build_repo2 + + gemfile <<-G + source "https://gem.repo2" + gem "myrack_middleware" + G + + system_gems "myrack_middleware-1.0", path: default_bundle_path + + # we want to raise when the 1.0 line should be followed by " myrack (= 0.9.1)" but isn't + lockfile <<-L + GEM + remote: https://gem.repo2/ + specs: + myrack_middleware (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack_middleware + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "raises a clear error message when frozen" do + bundle_config "frozen true" + bundle "install", raise_on_error: false + + expect(exitstatus).to eq(41) + expect(err).to include("Bundler found incorrect dependencies in the lockfile for myrack_middleware-1.0") + expect(err).to include("myrack: gemspec specifies = 0.9.1, not in lockfile") + end + + it "updates the lockfile when not frozen" do + missing_dep = "myrack (0.9.1)" + expect(lockfile).not_to include(missing_dep) + + bundle_config "frozen false" + bundle :install + + expect(lockfile).to include(missing_dep) + expect(out).to include("now installed") + end + end + context "with --local flag" do before do system_gems "myrack-1.0.0", path: default_bundle_path @@ -1502,7 +1773,7 @@ RSpec.describe "bundle install with gem sources" do context "with only option" do before do - bundle "config set only a:b" + bundle_config "only a:b" end it "installs only gems of the specified groups" do @@ -1521,68 +1792,94 @@ RSpec.describe "bundle install with gem sources" do 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" + context "and gems available locally" do + before do + build_repo4 do + build_gem "foo", "1.0.1" + build_gem "foo", "1.0.0" + build_gem "bar", "1.0.0" - build_gem "a", "1.0.0" do |s| - s.add_dependency "foo", "~> 1.0.0" - end + build_gem "a", "1.0.0" do |s| + s.add_dependency "foo", "~> 1.0.0" + end - build_gem "b", "1.0.0" do |s| - s.add_dependency "foo", "~> 1.0.1" + build_gem "b", "1.0.0" do |s| + s.add_dependency "foo", "~> 1.0.1" + end end + + system_gems "foo-1.0.0", path: default_bundle_path, gem_repo: gem_repo4 end - system_gems "foo-1.0.0", path: default_bundle_path, gem_repo: gem_repo4 - end + it "fetches remote sources when not available locally" do + install_gemfile <<-G, "prefer-local": true, verbose: true + source "https://gem.repo4" - it "fetches remote sources when not available locally" do - install_gemfile <<-G, "prefer-local": true, verbose: true - source "https://gem.repo4" + gem "foo" + gem "bar" + G - 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 - 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 + it "fetches remote sources when local version does not match requirements" do + install_gemfile <<-G, "prefer-local": true, verbose: true + source "https://gem.repo4" - it "fetches remote sources when local version does not match requirements" do - install_gemfile <<-G, "prefer-local": true, verbose: true - source "https://gem.repo4" + gem "foo", "1.0.1" + gem "bar" + G - gem "foo", "1.0.1" - gem "bar" - G + expect(out).to include("Fetching foo 1.0.1").and include("Installing foo 1.0.1").and include("Fetching bar 1.0.0").and include("Installing bar 1.0.0") + expect(last_command).to be_success + end - expect(out).to include("Fetching foo 1.0.1").and include("Installing foo 1.0.1").and include("Fetching bar 1.0.0").and include("Installing bar 1.0.0") - expect(last_command).to be_success - end + it "uses the locally available version for sub-dependencies when possible" do + install_gemfile <<-G, "prefer-local": true, verbose: true + source "https://gem.repo4" - it "uses the locally available version for sub-dependencies when possible" do - install_gemfile <<-G, "prefer-local": true, verbose: true - source "https://gem.repo4" + gem "a" + G - gem "a" - G + expect(out).to include("Using foo 1.0.0").and include("Fetching a 1.0.0").and include("Installing a 1.0.0") + expect(last_command).to be_success + end - expect(out).to include("Using foo 1.0.0").and include("Fetching a 1.0.0").and include("Installing a 1.0.0") - expect(last_command).to be_success + it "fetches remote sources for sub-dependencies when the locally available version does not satisfy the requirement" do + install_gemfile <<-G, "prefer-local": true, verbose: true + source "https://gem.repo4" + + gem "b" + G + + expect(out).to include("Fetching foo 1.0.1").and include("Installing foo 1.0.1").and include("Fetching b 1.0.0").and include("Installing b 1.0.0") + expect(last_command).to be_success + end end - it "fetches remote sources for sub-dependencies when the locally available version does not satisfy the requirement" do - install_gemfile <<-G, "prefer-local": true, verbose: true - source "https://gem.repo4" + context "and no gems available locally" do + before do + build_repo4 do + build_gem "myreline", "0.3.8" + build_gem "debug", "0.2.1" - gem "b" - G + build_gem "debug", "1.10.0" do |s| + s.add_dependency "myreline" + end + end + end - expect(out).to include("Fetching foo 1.0.1").and include("Installing foo 1.0.1").and include("Fetching b 1.0.0").and include("Installing b 1.0.0") - expect(last_command).to be_success + it "resolves to the latest version if no gems are available locally" do + install_gemfile <<~G, "prefer-local": true, verbose: true + source "https://gem.repo4" + + gem "debug" + G + + expect(out).to include("Fetching debug 1.10.0").and include("Installing debug 1.10.0").and include("Fetching myreline 0.3.8").and include("Installing myreline 0.3.8") + expect(last_command).to be_success + end end end @@ -1590,7 +1887,7 @@ RSpec.describe "bundle install with gem sources" 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")}" + bundle_config "path #{File.join(symlinked_bundled_app, ".vendor")}" binman_path = tmp("binman") FileUtils.mkdir_p binman_path @@ -1679,7 +1976,7 @@ RSpec.describe "bundle install with gem sources" do zzz! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -1729,7 +2026,7 @@ RSpec.describe "bundle install with gem sources" do end it "only installs executable files in bin" do - bundle "config set --local path vendor/bundle" + bundle_config "path vendor/bundle" install_gemfile <<~G source "https://gem.repo1" @@ -1741,6 +2038,25 @@ RSpec.describe "bundle install with gem sources" do expect(Dir.glob(vendored_gems("bin/*"))).to eq(expected_executables) end + it "prevents removing binstubs when BUNDLE_CLEAN is set" do + build_repo4 do + build_gem "kamal", "4.0.6" do |s| + s.executables = ["kamal"] + end + end + + gemfile = <<~G + source "https://gem.repo4" + gem "kamal" + G + + install_gemfile(gemfile, env: { "BUNDLE_CLEAN" => "true", "BUNDLE_PATH" => "vendor/bundle" }) + + expected_executables = [vendored_gems("bin/kamal").to_s] + expected_executables << vendored_gems("bin/kamal.bat").to_s if Gem.win_platform? + expect(Dir.glob(vendored_gems("bin/*"))).to eq(expected_executables) + end + it "preserves lockfile versions conservatively" do build_repo4 do build_gem "mypsych", "4.0.6" do |s| @@ -1770,7 +2086,7 @@ RSpec.describe "bundle install with gem sources" do mypsych (~> 4.0) BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L install_gemfile <<~G @@ -1793,7 +2109,7 @@ RSpec.describe "bundle install with gem sources" do mypsych (~> 5.0) BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end diff --git a/spec/bundler/commands/licenses_spec.rb b/spec/bundler/commands/licenses_spec.rb index bfec938efd..ebfad5ed4a 100644 --- a/spec/bundler/commands/licenses_spec.rb +++ b/spec/bundler/commands/licenses_spec.rb @@ -30,7 +30,7 @@ RSpec.describe "bundle licenses" do gem "foo" G - bundle "config set auto_install 1" + bundle_config "auto_install 1" bundle :licenses expect(out).to include("Installing foo 1.0") end diff --git a/spec/bundler/commands/list_spec.rb b/spec/bundler/commands/list_spec.rb index cc0db9169d..c890646a81 100644 --- a/spec/bundler/commands/list_spec.rb +++ b/spec/bundler/commands/list_spec.rb @@ -1,6 +1,28 @@ # frozen_string_literal: true +require "json" + RSpec.describe "bundle list" do + def find_gem_name(json:, name:) + parse_json(json)["gems"].detect {|h| h["name"] == name } + end + + def parse_json(json) + JSON.parse(json) + end + + context "in verbose mode" do + it "logs the actual flags passed to the command" do + install_gemfile <<-G + source "https://gem.repo1" + G + + bundle "list --verbose" + + expect(out).to include("Running `bundle list --verbose`") + end + end + context "with name-only and paths option" do it "raises an error" do bundle "list --name-only --paths", raise_on_error: false @@ -17,6 +39,20 @@ RSpec.describe "bundle list" do end end + context "with invalid format option" do + before do + install_gemfile <<-G + source "https://gem.repo1" + G + end + + it "raises an error" do + bundle "list --format=nope", raise_on_error: false + + expect(err).to eq "Unknown option`--format=nope`. Supported formats: `json`" + end + end + describe "with without-group option" do before do install_gemfile <<-G @@ -36,6 +72,17 @@ RSpec.describe "bundle list" do expect(out).to include(" * rails (2.3.2)") expect(out).not_to include(" * rspec (1.2.7)") end + + it "prints the gems not in the specified group with json" do + bundle "list --without-group test --format=json" + + gem = find_gem_name(json: out, name: "myrack") + expect(gem["version"]).to eq("1.0.0") + gem = find_gem_name(json: out, name: "rails") + expect(gem["version"]).to eq("2.3.2") + gem = find_gem_name(json: out, name: "rspec") + expect(gem).to be_nil + end end context "when group is not found" do @@ -54,6 +101,17 @@ RSpec.describe "bundle list" do expect(out).not_to include(" * rails (2.3.2)") expect(out).not_to include(" * rspec (1.2.7)") end + + it "prints the gems not in the specified groups with json" do + bundle "list --without-group test production --format=json" + + gem = find_gem_name(json: out, name: "myrack") + expect(gem["version"]).to eq("1.0.0") + gem = find_gem_name(json: out, name: "rails") + expect(gem).to be_nil + gem = find_gem_name(json: out, name: "rspec") + expect(gem).to be_nil + end end end @@ -75,6 +133,15 @@ RSpec.describe "bundle list" do expect(out).to include(" * myrack (1.0.0)") expect(out).not_to include(" * rspec (1.2.7)") end + + it "prints the gems in the specified group with json" do + bundle "list --only-group default --format=json" + + gem = find_gem_name(json: out, name: "myrack") + expect(gem["version"]).to eq("1.0.0") + gem = find_gem_name(json: out, name: "rspec") + expect(gem).to be_nil + end end context "when group is not found" do @@ -93,6 +160,17 @@ RSpec.describe "bundle list" do expect(out).to include(" * rails (2.3.2)") expect(out).not_to include(" * rspec (1.2.7)") end + + it "prints the gems in the specified groups with json" do + bundle "list --only-group default production --format=json" + + gem = find_gem_name(json: out, name: "myrack") + expect(gem["version"]).to eq("1.0.0") + gem = find_gem_name(json: out, name: "rails") + expect(gem["version"]).to eq("2.3.2") + gem = find_gem_name(json: out, name: "rspec") + expect(gem).to be_nil + end end end @@ -112,6 +190,15 @@ RSpec.describe "bundle list" do expect(out).to include("myrack") expect(out).to include("rspec") end + + it "prints only the name of the gems in the bundle with json" do + bundle "list --name-only --format=json" + + gem = find_gem_name(json: out, name: "myrack") + expect(gem.keys).to eq(["name"]) + gem = find_gem_name(json: out, name: "rspec") + expect(gem.keys).to eq(["name"]) + end end context "with paths option" do @@ -146,6 +233,27 @@ RSpec.describe "bundle list" do expect(out).to match(%r{.*\/git_test\-\w}) expect(out).to match(%r{.*\/gemspec_test}) end + + it "prints the path of each gem in the bundle with json" do + bundle "list --paths --format=json" + + gem = find_gem_name(json: out, name: "rails") + expect(gem["path"]).to match(%r{.*\/rails\-2\.3\.2}) + expect(gem["git_version"]).to be_nil + + gem = find_gem_name(json: out, name: "myrack") + expect(gem["path"]).to match(%r{.*\/myrack\-1\.2}) + expect(gem["git_version"]).to be_nil + + gem = find_gem_name(json: out, name: "git_test") + expect(gem["path"]).to match(%r{.*\/git_test\-\w}) + expect(gem["git_version"]).to be_truthy + expect(gem["git_version"].strip).to eq(gem["git_version"]) + + gem = find_gem_name(json: out, name: "gemspec_test") + expect(gem["path"]).to match(%r{.*\/gemspec_test}) + expect(gem["git_version"]).to be_nil + end end context "when no gems are in the gemfile" do @@ -159,6 +267,11 @@ RSpec.describe "bundle list" do bundle "list" expect(out).to include("No gems in the Gemfile") end + + it "prints empty json" do + bundle "list --format=json" + expect(parse_json(out)["gems"]).to eq([]) + end end context "without options" do @@ -175,6 +288,13 @@ RSpec.describe "bundle list" do bundle "list" expect(out).to include(" * myrack (1.0.0)") end + + it "lists gems installed in the bundle with json" do + bundle "list --format=json" + + gem = find_gem_name(json: out, name: "myrack") + expect(gem["version"]).to eq("1.0.0") + end end context "when using the ls alias" do diff --git a/spec/bundler/commands/lock_spec.rb b/spec/bundler/commands/lock_spec.rb index db600e356f..8ab3cc7e8d 100644 --- a/spec/bundler/commands/lock_spec.rb +++ b/spec/bundler/commands/lock_spec.rb @@ -46,7 +46,7 @@ RSpec.describe "bundle lock" do weakling #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -95,15 +95,14 @@ RSpec.describe "bundle lock" do weakling #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end - before :each do + let(:gemfile_with_rails_weakling_and_foo_from_repo4) do build_repo4 do - FileUtils.cp rake_path, "#{gem_repo4}/gems/" - build_gem "rake", "10.0.1" + build_gem "rake", rake_version %w[2.3.1 2.3.2].each do |version| build_gem "rails", version do |s| @@ -143,12 +142,16 @@ RSpec.describe "bundle lock" do end it "prints a lockfile when there is no existing lockfile with --print" do + gemfile_with_rails_weakling_and_foo_from_repo4 + bundle "lock --print" expect(out).to eq(expected_lockfile.chomp) end it "prints a lockfile when there is an existing lockfile with --print" do + gemfile_with_rails_weakling_and_foo_from_repo4 + lockfile expected_lockfile bundle "lock --print" @@ -157,6 +160,8 @@ RSpec.describe "bundle lock" do end it "prints a lockfile when there is an existing checksums lockfile with --print" do + gemfile_with_rails_weakling_and_foo_from_repo4 + lockfile expected_lockfile bundle "lock --print" @@ -165,12 +170,16 @@ RSpec.describe "bundle lock" do end it "writes a lockfile when there is no existing lockfile" do + gemfile_with_rails_weakling_and_foo_from_repo4 + bundle "lock" expect(read_lockfile).to eq(expected_lockfile) end it "prints a lockfile without fetching new checksums if the existing lockfile had no checksums" do + gemfile_with_rails_weakling_and_foo_from_repo4 + lockfile expected_lockfile bundle "lock --print" @@ -179,6 +188,8 @@ RSpec.describe "bundle lock" do end it "touches the lockfile when there is an existing lockfile that does not need changes" do + gemfile_with_rails_weakling_and_foo_from_repo4 + lockfile expected_lockfile expect do @@ -187,6 +198,8 @@ RSpec.describe "bundle lock" do end it "does not touch lockfile with --print" do + gemfile_with_rails_weakling_and_foo_from_repo4 + lockfile expected_lockfile expect do @@ -195,6 +208,8 @@ RSpec.describe "bundle lock" do end it "writes a lockfile when there is an outdated lockfile using --update" do + gemfile_with_rails_weakling_and_foo_from_repo4 + lockfile outdated_lockfile bundle "lock --update" @@ -203,6 +218,8 @@ RSpec.describe "bundle lock" do end it "prints an updated lockfile when there is an outdated lockfile using --print --update" do + gemfile_with_rails_weakling_and_foo_from_repo4 + lockfile outdated_lockfile bundle "lock --print --update" @@ -211,6 +228,8 @@ RSpec.describe "bundle lock" do end it "emits info messages to stderr when updating an outdated lockfile using --print --update" do + gemfile_with_rails_weakling_and_foo_from_repo4 + lockfile outdated_lockfile bundle "lock --print --update" @@ -222,6 +241,8 @@ RSpec.describe "bundle lock" do end it "writes a lockfile when there is an outdated lockfile and bundle is frozen" do + gemfile_with_rails_weakling_and_foo_from_repo4 + lockfile outdated_lockfile bundle "lock --update", env: { "BUNDLE_FROZEN" => "true" } @@ -230,12 +251,16 @@ RSpec.describe "bundle lock" do end it "does not fetch remote specs when using the --local option" do + gemfile_with_rails_weakling_and_foo_from_repo4 + bundle "lock --update --local", raise_on_error: false expect(err).to match(/locally installed gems/) end it "does not fetch remote checksums with --local" do + gemfile_with_rails_weakling_and_foo_from_repo4 + lockfile expected_lockfile bundle "lock --print --local" @@ -244,6 +269,8 @@ RSpec.describe "bundle lock" do end it "works with --gemfile flag" do + gemfile_with_rails_weakling_and_foo_from_repo4 + gemfile "CustomGemfile", <<-G source "https://gem.repo4" gem "foo" @@ -267,7 +294,7 @@ RSpec.describe "bundle lock" do foo #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L expect(out).to match(/Writing lockfile to.+CustomGemfile\.lock/) expect(read_lockfile("CustomGemfile.lock")).to eq(lockfile) @@ -275,6 +302,8 @@ RSpec.describe "bundle lock" do end it "writes to a custom location using --lockfile" do + gemfile_with_rails_weakling_and_foo_from_repo4 + bundle "lock --lockfile=lock" expect(out).to match(/Writing lockfile to.+lock/) @@ -282,7 +311,47 @@ RSpec.describe "bundle lock" do expect { read_lockfile }.to raise_error(Errno::ENOENT) end + it "updates a specific gem and write to a custom location" do + build_repo4 do + build_gem "foo", %w[1.0.2 1.0.3] + build_gem "warning", %w[1.4.0 1.5.0] + end + + gemfile <<~G + source "https://gem.repo4" + + gem "foo" + gem "warning" + G + + lockfile <<~L + GEM + remote: https://gem.repo4 + specs: + foo (1.0.2) + warning (1.4.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + uri + warning + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "lock --update foo --lockfile=lock" + + lockfile_content = read_lockfile("lock") + expect(lockfile_content).to include("foo (1.0.3)") + expect(lockfile_content).to include("warning (1.4.0)") + end + it "writes to custom location using --lockfile when a default lockfile is present" do + gemfile_with_rails_weakling_and_foo_from_repo4 + bundle "install" bundle "lock --lockfile=lock" @@ -330,7 +399,7 @@ RSpec.describe "bundle lock" do weakling #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L expect(out).to match(/Writing lockfile to.+lock/) @@ -338,6 +407,8 @@ RSpec.describe "bundle lock" do end it "update specific gems using --update" do + gemfile_with_rails_weakling_and_foo_from_repo4 + checksums = checksums_section_when_enabled do |c| c.checksum gem_repo4, "actionmailer", "2.3.1" c.checksum gem_repo4, "actionpack", "2.3.1" @@ -382,7 +453,7 @@ RSpec.describe "bundle lock" do weakling #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L lockfile lockfile_with_outdated_rails_and_rake @@ -439,7 +510,7 @@ RSpec.describe "bundle lock" do tapioca BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "lock --update tapioca --verbose" @@ -505,7 +576,7 @@ RSpec.describe "bundle lock" do tapioca BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "lock --update tapioca" @@ -515,6 +586,8 @@ RSpec.describe "bundle lock" do end it "preserves unknown checksum algorithms" do + gemfile_with_rails_weakling_and_foo_from_repo4 + lockfile expected_lockfile.gsub(/(sha256=[a-f0-9]+)$/, "constant=true,\\1,xyz=123") previous_lockfile = read_lockfile @@ -525,6 +598,8 @@ RSpec.describe "bundle lock" do end it "does not unlock git sources when only uri shape changes" do + gemfile_with_rails_weakling_and_foo_from_repo4 + build_git("foo") install_gemfile <<-G @@ -543,6 +618,8 @@ RSpec.describe "bundle lock" do end it "updates specific gems using --update using the locked revision of unrelated git gems for resolving" do + gemfile_with_rails_weakling_and_foo_from_repo4 + ref = build_git("foo").ref_for("HEAD") gemfile <<-G @@ -572,7 +649,7 @@ RSpec.describe "bundle lock" do rake BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "lock --update rake --verbose" @@ -581,6 +658,8 @@ RSpec.describe "bundle lock" do end it "errors when updating a missing specific gems using --update" do + gemfile_with_rails_weakling_and_foo_from_repo4 + lockfile expected_lockfile bundle "lock --update blahblah", raise_on_error: false @@ -590,14 +669,16 @@ RSpec.describe "bundle lock" do end it "can lock without downloading gems" do + gemfile_with_rails_weakling_and_foo_from_repo4 + gemfile <<-G source "https://gem.repo1" gem "thin" gem "myrack_middleware", :group => "test" G - bundle "config set without test" - bundle "config set path vendor/bundle" + bundle_config "without test" + bundle_config "path vendor/bundle" bundle "lock", verbose: true expect(bundled_app("vendor/bundle")).not_to exist end @@ -645,13 +726,13 @@ RSpec.describe "bundle lock" do it "single gem updates dependent gem to minor" do bundle "lock --update foo --patch" - expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[foo-1.4.5 bar-2.1.1 qux-1.0.0].sort) + expect(the_bundle.locked_specs).to eq(%w[foo-1.4.5 bar-2.1.1 qux-1.0.0].sort) end it "minor preferred with strict" do bundle "lock --update --minor --strict" - 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) + expect(the_bundle.locked_specs).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 @@ -674,25 +755,25 @@ RSpec.describe "bundle lock" 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) + expect(the_bundle.locked_specs).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) + expect(the_bundle.locked_specs).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) + expect(the_bundle.locked_specs).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) + expect(the_bundle.locked_specs).to eq(%w[foo-2.0.0.pre bar-4.0.0.pre qux-2.0.0].sort) end end end @@ -725,7 +806,7 @@ RSpec.describe "bundle lock" do sequel BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) @@ -734,7 +815,7 @@ RSpec.describe "bundle lock" do 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) + expect(the_bundle.locked_specs).to eq(%w[sequel-5.72.0 bigdecimal-99.1.4].sort) end end @@ -751,22 +832,35 @@ RSpec.describe "bundle lock" do 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") + expect(lockfile).to end_with("BUNDLED WITH\n 55\n") - update_repo4 do + build_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") + expect(lockfile).to end_with("BUNDLED WITH\n 99\n") end - it "supports adding new platforms" do - bundle "lock --add-platform java x86-mingw32" + it "supports adding new platforms when there's no previous lockfile" do + gemfile_with_rails_weakling_and_foo_from_repo4 + + bundle "lock --add-platform java x86-mingw32 --verbose" + expect(out).to include("Resolving dependencies because there's no lockfile") + + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + expect(the_bundle.locked_platforms).to match_array(default_platform_list("java", "x86-mingw32")) + end + + it "supports adding new platforms when a previous lockfile exists" do + gemfile_with_rails_weakling_and_foo_from_repo4 + + bundle "lock" + bundle "lock --add-platform java x86-mingw32 --verbose" + expect(out).to include("Found changes from the lockfile, re-resolving dependencies because you are adding a new platform to your lockfile") allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) - lockfile = Bundler::LockfileParser.new(read_lockfile) - expect(lockfile.platforms).to match_array(default_platform_list(java, x86_mingw32)) + expect(the_bundle.locked_platforms).to match_array(default_platform_list("java", "x86-mingw32")) end it "supports adding new platforms, when most specific locked platform is not the current platform, and current resolve is not compatible with the target platform" do @@ -800,7 +894,7 @@ RSpec.describe "bundle lock" do foo BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "lock --add-platform java" @@ -820,12 +914,14 @@ RSpec.describe "bundle lock" do foo BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end it "supports adding new platforms with force_ruby_platform = true" do + gemfile_with_rails_weakling_and_foo_from_repo4 + lockfile <<-L GEM remote: https://gem.repo1/ @@ -841,39 +937,41 @@ RSpec.describe "bundle lock" do platform_specific L - bundle "config set force_ruby_platform true" + bundle_config "force_ruby_platform true" bundle "lock --add-platform java x86-mingw32" 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, x86_mingw32) + expect(the_bundle.locked_platforms).to contain_exactly(Gem::Platform::RUBY, "x86_64-linux", "java", "x86-mingw32") end it "supports adding the `ruby` platform" do + gemfile_with_rails_weakling_and_foo_from_repo4 + bundle "lock --add-platform ruby" allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) - lockfile = Bundler::LockfileParser.new(read_lockfile) - expect(lockfile.platforms).to match_array(default_platform_list("ruby")) + expect(the_bundle.locked_platforms).to match_array(default_platform_list("ruby")) end it "fails when adding an unknown platform" do + gemfile_with_rails_weakling_and_foo_from_repo4 + bundle "lock --add-platform foobarbaz", raise_on_error: false expect(err).to include("The platform `foobarbaz` is unknown to RubyGems and can't be added to the lockfile") expect(last_command).to be_failure end it "allows removing platforms" do + gemfile_with_rails_weakling_and_foo_from_repo4 + bundle "lock --add-platform java x86-mingw32" allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) - lockfile = Bundler::LockfileParser.new(read_lockfile) - expect(lockfile.platforms).to match_array(default_platform_list(java, x86_mingw32)) + expect(the_bundle.locked_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(default_platform_list(x86_mingw32)) + expect(the_bundle.locked_platforms).to match_array(default_platform_list("x86-mingw32")) end it "also cleans up redundant platform gems when removing platforms" do @@ -912,7 +1010,7 @@ RSpec.describe "bundle lock" do nokogiri #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L checksums.delete("nokogiri", Gem::Platform::RUBY) @@ -934,11 +1032,13 @@ RSpec.describe "bundle lock" do nokogiri #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end it "errors when removing all platforms" do + gemfile_with_rails_weakling_and_foo_from_repo4 + bundle "lock --remove-platform #{local_platform}", raise_on_error: false expect(err).to include("Removing all platforms from the bundle is not allowed") end @@ -948,7 +1048,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 = x86_mingw32 + s.platform = "x86-mingw32" end build_gem "gssapi", "0.1" @@ -980,7 +1080,7 @@ RSpec.describe "bundle lock" do gem "gssapi" G - simulate_platform(x86_mingw32) { bundle :lock } + simulate_platform("x86-mingw32") { bundle :lock } checksums = checksums_section_when_enabled do |c| c.checksum gem_repo4, "ffi", "1.9.14", "x86-mingw32" @@ -1009,10 +1109,10 @@ RSpec.describe "bundle lock" do mixlib-shellout #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G - bundle "config set --local force_ruby_platform true" + bundle_config "force_ruby_platform true" bundle :lock checksums.checksum gem_repo4, "ffi", "1.9.14" @@ -1041,7 +1141,7 @@ RSpec.describe "bundle lock" do mixlib-shellout #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -1078,7 +1178,7 @@ RSpec.describe "bundle lock" do libv8 BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G simulate_platform("x86_64-darwin-19") { bundle "lock --update" } @@ -1125,7 +1225,7 @@ RSpec.describe "bundle lock" do libv8 #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -1165,7 +1265,7 @@ RSpec.describe "bundle lock" do libv8 #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G previous_lockfile = lockfile @@ -1194,11 +1294,6 @@ RSpec.describe "bundle lock" do end build_gem "raygun-apm", "1.0.78" do |s| - s.platform = "x64-mingw32" - 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 @@ -1223,7 +1318,7 @@ RSpec.describe "bundle lock" do raygun-apm BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "lock --add-platform x86_64-linux" @@ -1257,7 +1352,7 @@ RSpec.describe "bundle lock" do nokogiri BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L simulate_platform "x86_64-linux" do @@ -1279,64 +1374,50 @@ RSpec.describe "bundle lock" do nokogiri BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end - it "does not crash on conflicting ruby requirements between platform versions in two different gems" do + it "refuses to add platforms incompatible with the lockfile" do build_repo4 do - build_gem "unf_ext", "0.0.8.2" - - 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" + build_gem "sorbet-static", "0.5.11989" do |s| + s.platform = "x86_64-linux" end end gemfile <<~G source "https://gem.repo4" - gem "google-protobuf" - gem "unf_ext" + gem "sorbet-static" G lockfile <<~L GEM remote: https://gem.repo4/ specs: - google-protobuf (3.21.12) - unf_ext (0.0.8.2) + sorbet-static (0.5.11989-x86_64-linux) PLATFORMS - x64-mingw-ucrt - x64-mingw32 + x86_64-linux DEPENDENCIES - google-protobuf - unf_ext + sorbet-static BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L - bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s, "DEBUG_RESOLVER" => "1" } + simulate_platform "x86_64-linux" do + bundle "lock --add-platform ruby", raise_on_error: false + end + + nice_error = <<~E.strip + Could not find gems matching 'sorbet-static' valid for all resolution platforms (x86_64-linux, ruby) in rubygems repository https://gem.repo4/ or installed locally. + + The source contains the following gems matching 'sorbet-static': + * sorbet-static-0.5.11989-x86_64-linux + E + expect(err).to include(nice_error) end it "respects lower bound ruby requirements" do @@ -1365,7 +1446,7 @@ RSpec.describe "bundle lock" do our_private_gem BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } @@ -1373,7 +1454,9 @@ RSpec.describe "bundle lock" do context "when an update is available" do before do - update_repo4 do + gemfile_with_rails_weakling_and_foo_from_repo4 + + build_repo4 do build_gem "foo", "2.0" end @@ -1427,7 +1510,7 @@ RSpec.describe "bundle lock" do weakling #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L expect(read_lockfile).to eq(expected_lockfile) @@ -1481,7 +1564,7 @@ RSpec.describe "bundle lock" do weakling #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L expect(read_lockfile).to eq(expected_lockfile) @@ -1535,7 +1618,7 @@ RSpec.describe "bundle lock" do debug #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L simulate_platform "arm64-darwin-22" do @@ -1558,7 +1641,65 @@ RSpec.describe "bundle lock" do debug #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} + L + end + end + + context "when a system gem has incorrect dependencies, different from remote gems" do + before do + build_repo4 do + build_gem "foo", "1.0.0" do |s| + s.add_dependency "bar" + end + + build_gem "bar", "1.0.0" + end + + system_gems "foo-1.0.0", gem_repo: gem_repo4, path: default_bundle_path + + # simulate gemspec with wrong empty dependencies + foo_gemspec_path = default_bundle_path("specifications/foo-1.0.0.gemspec") + foo_gemspec = Gem::Specification.load(foo_gemspec_path.to_s) + foo_gemspec.dependencies.clear + File.write(foo_gemspec_path, foo_gemspec.to_ruby) + end + + it "generates a lockfile using remote dependencies, and prints a warning" do + gemfile <<~G + source "https://gem.repo4" + + gem "foo" + G + + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "foo", "1.0.0" + c.checksum gem_repo4, "bar", "1.0.0" + end + + simulate_platform "x86_64-linux" do + bundle "lock --verbose" + end + + expect(err).to eq("Local specification for foo-1.0.0 has different dependencies than the remote gem, ignoring it") + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + bar (1.0.0) + foo (1.0.0) + bar + + PLATFORMS + ruby + x86_64-linux + + DEPENDENCIES + foo + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} L end end @@ -1661,7 +1802,7 @@ RSpec.describe "bundle lock" do ransack (= 3.1.0) BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L expected_error = <<~ERR.strip @@ -1803,7 +1944,7 @@ RSpec.describe "bundle lock" do nogokiri BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L simulate_platform "x86_64-linux" do @@ -1830,7 +1971,7 @@ RSpec.describe "bundle lock" do nokogiri #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -1863,7 +2004,7 @@ RSpec.describe "bundle lock" do nokogiri BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L simulate_platform "x86_64-linux" do @@ -1890,7 +2031,57 @@ RSpec.describe "bundle lock" do nokogiri #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} + L + end + + it "adds checksums when source is not specified" do + system_gems(%w[myrack-1.0.0], path: default_bundle_path) + + gemfile <<-G + gem "myrack" + G + + lockfile <<~L + GEM + specs: + myrack (1.0.0) + + PLATFORMS + ruby + x86_64-linux + + DEPENDENCIES + myrack + + BUNDLED WITH + #{Bundler::VERSION} + L + + simulate_platform "x86_64-linux" do + bundle "lock --add-checksums" + end + + # myrack is coming from gem_repo1 + # but it's simulated to install in the system gems path + checksums = checksums_section do |c| + c.checksum gem_repo1, "myrack", "1.0.0" + end + + expect(lockfile).to eq <<~L + GEM + specs: + myrack (1.0.0) + + PLATFORMS + ruby + x86_64-linux + + DEPENDENCIES + myrack + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} L end @@ -1923,7 +2114,7 @@ RSpec.describe "bundle lock" do nokogiri BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L simulate_platform "x86_64-linux" do @@ -1952,11 +2143,11 @@ RSpec.describe "bundle lock" do nokogiri #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end - it "generates checksums by default if configured to do so" do + it "generates checksums by default" do build_repo4 do build_gem "nokogiri", "1.14.2" build_gem "nokogiri", "1.14.2" do |s| @@ -1964,8 +2155,6 @@ RSpec.describe "bundle lock" do end end - bundle "config lockfile_checksums true" - simulate_platform "x86_64-linux" do install_gemfile <<-G source "https://gem.repo4" @@ -1994,7 +2183,136 @@ RSpec.describe "bundle lock" do nokogiri #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} + L + end + + it "disables checksums if configured to do so" do + build_repo4 do + build_gem "nokogiri", "1.14.2" + build_gem "nokogiri", "1.14.2" do |s| + s.platform = "x86_64-linux" + end + end + + bundle_config "lockfile_checksums false" + + simulate_platform "x86_64-linux" do + install_gemfile <<-G + source "https://gem.repo4" + + gem "nokogiri" + G + end + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + nokogiri (1.14.2) + nokogiri (1.14.2-x86_64-linux) + + PLATFORMS + ruby + x86_64-linux + + DEPENDENCIES + nokogiri + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "add checksums for gems installed on disk" do + build_repo4 do + build_gem "warning", "18.0.0" + end + + bundle_config "lockfile_checksums false" + + simulate_platform "x86_64-linux" do + install_gemfile(<<-G, artifice: "endpoint") + source "https://gem.repo4" + + gem "warning" + G + + bundle "config --delete lockfile_checksums" + bundle("lock --add-checksums", artifice: "endpoint") + end + + checksums = checksums_section do |c| + c.checksum gem_repo4, "warning", "18.0.0" + end + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + warning (18.0.0) + + PLATFORMS + ruby + x86_64-linux + + DEPENDENCIES + warning + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "doesn't add checksum for gems not installed on disk" do + lockfile(<<~L) + GEM + remote: https://gem.repo4/ + specs: + warning (18.0.0) + + PLATFORMS + #{local_platform} + + DEPENDENCIES + warning + + BUNDLED WITH + #{Bundler::VERSION} + L + + gemfile(<<~G) + source "https://gem.repo4" + + gem "warning" + G + + build_repo4 do + build_gem "warning", "18.0.0" + end + + FileUtils.rm_rf("#{gem_repo4}/gems") + + bundle("lock --add-checksums", artifice: "endpoint") + + checksums = checksums_section_when_enabled do |c| + c.no_checksum "warning", "18.0.0" + end + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + warning (18.0.0) + + PLATFORMS + #{local_platform} + + DEPENDENCIES + warning + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} L end @@ -2075,7 +2393,7 @@ RSpec.describe "bundle lock" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -2105,7 +2423,7 @@ RSpec.describe "bundle lock" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -2134,14 +2452,13 @@ RSpec.describe "bundle lock" do nokogiri (1.14.2-x86_64-linux) PLATFORMS - ruby x86_64-linux DEPENDENCIES foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -2196,7 +2513,7 @@ RSpec.describe "bundle lock" do govuk_app_config BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -2232,7 +2549,7 @@ RSpec.describe "bundle lock" do govuk_app_config #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -2268,7 +2585,7 @@ RSpec.describe "bundle lock" do ffi BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -2292,8 +2609,269 @@ RSpec.describe "bundle lock" do ffi BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} + L + end + end + + describe "--normalize-platforms on linux" do + let(:normalized_lockfile) do + <<~L + GEM + remote: https://gem.repo4/ + specs: + irb (1.0.0) + irb (1.0.0-x86_64-linux) + + PLATFORMS + ruby + x86_64-linux + + DEPENDENCIES + irb + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + before do + build_repo4 do + build_gem "irb", "1.0.0" + + build_gem "irb", "1.0.0" do |s| + s.platform = "x86_64-linux" + end + end + + gemfile <<~G + source "https://gem.repo4" + + gem "irb" + G + end + + context "when already normalized" do + before do + lockfile normalized_lockfile + end + + it "is a noop" do + simulate_platform "x86_64-linux" do + bundle "lock --normalize-platforms" + end + + expect(lockfile).to eq(normalized_lockfile) + end + end + + context "when not already normalized" do + before do + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + irb (1.0.0) + + PLATFORMS + ruby + + DEPENDENCIES + irb + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "normalizes the list of platforms and native gems in the lockfile" do + simulate_platform "x86_64-linux" do + bundle "lock --normalize-platforms" + end + + expect(lockfile).to eq(normalized_lockfile) + end + end + end + + describe "--normalize-platforms on darwin" do + let(:normalized_lockfile) do + <<~L + GEM + remote: https://gem.repo4/ + specs: + irb (1.0.0) + irb (1.0.0-arm64-darwin) + + PLATFORMS + arm64-darwin + ruby + + DEPENDENCIES + irb + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + before do + build_repo4 do + build_gem "irb", "1.0.0" + + build_gem "irb", "1.0.0" do |s| + s.platform = "arm64-darwin" + end + end + + gemfile <<~G + source "https://gem.repo4" + + gem "irb" + G + end + + context "when already normalized" do + before do + lockfile normalized_lockfile + end + + it "is a noop" do + simulate_platform "arm64-darwin-23" do + bundle "lock --normalize-platforms" + end + + expect(lockfile).to eq(normalized_lockfile) + end + end + + context "when having only ruby" do + before do + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + irb (1.0.0) + + PLATFORMS + ruby + + DEPENDENCIES + irb + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "normalizes the list of platforms and native gems in the lockfile" do + simulate_platform "arm64-darwin-23" do + bundle "lock --normalize-platforms" + end + + expect(lockfile).to eq(normalized_lockfile) + end + end + + context "when having only the current platform with version" do + before do + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + irb (1.0.0-arm64-darwin) + + PLATFORMS + arm64-darwin-23 + + DEPENDENCIES + irb + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "normalizes the list of platforms by removing version" do + simulate_platform "arm64-darwin-23" do + bundle "lock --normalize-platforms" + end + + expect(lockfile).to eq(normalized_lockfile) + end + end + + context "when having other platforms with version" do + before do + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + irb (1.0.0-arm64-darwin) + + PLATFORMS + arm64-darwin-22 + + DEPENDENCIES + irb + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "normalizes the list of platforms by removing version" do + simulate_platform "arm64-darwin-23" do + bundle "lock --normalize-platforms" + end + + expect(lockfile).to eq(normalized_lockfile) + end + end + end + + describe "--normalize-platforms with gems without generic variant" do + let(:original_lockfile) do + <<~L + GEM + remote: https://gem.repo4/ + specs: + sorbet-static (1.0-x86_64-linux) + + PLATFORMS + ruby + x86_64-linux + + DEPENDENCIES + sorbet-static + + BUNDLED WITH + #{Bundler::VERSION} L end + + before do + build_repo4 do + build_gem "sorbet-static" do |s| + s.platform = "x86_64-linux" + end + end + + gemfile <<~G + source "https://gem.repo4" + + gem "sorbet-static" + G + + lockfile original_lockfile + end + + it "removes invalid platforms" do + simulate_platform "x86_64-linux" do + bundle "lock --normalize-platforms" + end + + expect(lockfile).to eq(original_lockfile.gsub(/^ ruby\n/m, "")) + end end end diff --git a/spec/bundler/commands/newgem_spec.rb b/spec/bundler/commands/newgem_spec.rb index 2389eda521..65fbad05aa 100644 --- a/spec/bundler/commands/newgem_spec.rb +++ b/spec/bundler/commands/newgem_spec.rb @@ -6,39 +6,54 @@ RSpec.describe "bundle gem" do expect(bundled_app("#{gem_name}/README.md")).to exist expect(bundled_app("#{gem_name}/Gemfile")).to exist 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}/lib/#{gem_name}.rb")).to exist + expect(bundled_app("#{gem_name}/lib/#{gem_name}/version.rb")).to exist + + expect(ignore_paths).to include("bin/") + expect(ignore_paths).to include("Gemfile") end 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 "config set path #{rubocop_gem_path}", 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 "config set path #{standard_gem_path}", 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")) } - - let(:gem_name) { "mygem" } + def ignore_paths + generated = bundled_app("#{gem_name}/#{gem_name}.gemspec").read + matched = generated.match(/^\s+f\.start_with\?\(\*%w\[(?<ignored>.*)\]\)$/) + matched[:ignored]&.split(" ") + end - let(:require_path) { "mygem" } + def installed_go? + sys_exec("go version", raise_on_error: true) + true + rescue StandardError + false + end - let(:minitest_test_file_path) { "test/test_mygem.rb" } + let(:generated_gemspec) { Bundler.load_gemspec_uncached(bundled_app(gem_name).join("#{gem_name}.gemspec")) } - let(:minitest_test_class_name) { "class TestMygem < Minitest::Test" } + let(:gem_name) { "mygem" } before do git("config --global user.name 'Bundler User'") git("config --global user.email user@example.com") git("config --global github.user bundleuser") - 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" + bundle_config_global "gem.mit false" + bundle_config_global "gem.test false" + bundle_config_global "gem.coc false" + bundle_config_global "gem.linter false" + bundle_config_global "gem.ci false" + bundle_config_global "gem.changelog false" + bundle_config_global "gem.bundle false" end describe "git repo initialization" do @@ -152,64 +167,23 @@ RSpec.describe "bundle gem" do end end - shared_examples_for "--rubocop flag" do - context "is deprecated", bundler: "< 3" do - before do - global_config "BUNDLE_GEM__LINTER" => nil - bundle "gem #{gem_name} --rubocop" - end - - it "generates a gem skeleton with rubocop" do - gem_skeleton_assertions - expect(bundled_app("test-gem/Rakefile")).to read_as( - include("# frozen_string_literal: true"). - and(include('require "rubocop/rake_task"'). - and(include("RuboCop::RakeTask.new"). - and(match(/default:.+:rubocop/)))) - ) - end - - it "includes rubocop in generated Gemfile" do - allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) - builder = Bundler::Dsl.new - builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile")) - builder.dependencies - rubocop_dep = builder.dependencies.find {|d| d.name == "rubocop" } - expect(rubocop_dep).not_to be_nil - end - - it "generates a default .rubocop.yml" do - expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist - end + shared_examples_for "--bundle flag" do + before do + bundle "gem #{gem_name} --bundle" + end + it "generates a gem skeleton with bundle install" do + gem_skeleton_assertions + expect(out).to include("Running bundle install in the new gem directory.") end end - shared_examples_for "--no-rubocop flag" do - context "is deprecated", bundler: "< 3" do - define_negated_matcher :exclude, :include - - before do - bundle "gem #{gem_name} --no-rubocop" - end - - it "generates a gem skeleton without rubocop" do - gem_skeleton_assertions - expect(bundled_app("test-gem/Rakefile")).to read_as(exclude("rubocop")) - expect(bundled_app("test-gem/#{gem_name}.gemspec")).to read_as(exclude("rubocop")) - end - - it "does not include rubocop in generated Gemfile" do - allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) - builder = Bundler::Dsl.new - builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile")) - builder.dependencies - rubocop_dep = builder.dependencies.find {|d| d.name == "rubocop" } - expect(rubocop_dep).to be_nil - end - - it "doesn't generate a default .rubocop.yml" do - expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist - end + shared_examples_for "--no-bundle flag" do + before do + bundle "gem #{gem_name} --no-bundle" + end + it "generates a gem skeleton without bundle install" do + gem_skeleton_assertions + expect(out).to_not include("Running bundle install in the new gem directory.") end end @@ -220,7 +194,7 @@ RSpec.describe "bundle gem" do it "generates a gem skeleton with rubocop" do gem_skeleton_assertions - expect(bundled_app("test-gem/Rakefile")).to read_as( + expect(bundled_app("#{gem_name}/Rakefile")).to read_as( include("# frozen_string_literal: true"). and(include('require "rubocop/rake_task"'). and(include("RuboCop::RakeTask.new"). @@ -234,12 +208,17 @@ RSpec.describe "bundle gem" do builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile")) builder.dependencies rubocop_dep = builder.dependencies.find {|d| d.name == "rubocop" } - expect(rubocop_dep).not_to be_nil + expect(rubocop_dep).not_to be_specific + expect(rubocop_dep.requirement).to eq(Gem::Requirement.new([">= 0"])) end it "generates a default .rubocop.yml" do expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist end + + it "includes .rubocop.yml into ignore list" do + expect(ignore_paths).to include(".rubocop.yml") + end end shared_examples_for "--linter=standard flag" do @@ -249,7 +228,7 @@ RSpec.describe "bundle gem" do it "generates a gem skeleton with standard" do gem_skeleton_assertions - expect(bundled_app("test-gem/Rakefile")).to read_as( + expect(bundled_app("#{gem_name}/Rakefile")).to read_as( include('require "standard/rake"'). and(match(/default:.+:standard/)) ) @@ -261,12 +240,17 @@ RSpec.describe "bundle gem" do builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile")) builder.dependencies standard_dep = builder.dependencies.find {|d| d.name == "standard" } - expect(standard_dep).not_to be_nil + expect(standard_dep).not_to be_specific + expect(standard_dep.requirement).to eq(Gem::Requirement.new([">= 0"])) end it "generates a default .standard.yml" do expect(bundled_app("#{gem_name}/.standard.yml")).to exist end + + it "includes .standard.yml into ignore list" do + expect(ignore_paths).to include(".standard.yml") + end end shared_examples_for "--no-linter flag" do @@ -278,8 +262,8 @@ RSpec.describe "bundle gem" do it "generates a gem skeleton without rubocop" do gem_skeleton_assertions - expect(bundled_app("test-gem/Rakefile")).to read_as(exclude("rubocop")) - expect(bundled_app("test-gem/#{gem_name}.gemspec")).to read_as(exclude("rubocop")) + expect(bundled_app("#{gem_name}/Rakefile")).to read_as(exclude("rubocop")) + expect(bundled_app("#{gem_name}/#{gem_name}.gemspec")).to read_as(exclude("rubocop")) end it "does not include rubocop in generated Gemfile" do @@ -304,9 +288,17 @@ RSpec.describe "bundle gem" do expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist end + it "does not add .rubocop.yml into ignore list" do + expect(ignore_paths).not_to include(".rubocop.yml") + end + it "doesn't generate a default .standard.yml" do expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist end + + it "does not add .standard.yml into ignore list" do + expect(ignore_paths).not_to include(".standard.yml") + end end it "has no rubocop offenses when using --linter=rubocop flag" do @@ -353,7 +345,6 @@ RSpec.describe "bundle gem" do it "has no rubocop offenses when using --ext=rust and --linter=rubocop flag" 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 @@ -362,7 +353,6 @@ RSpec.describe "bundle gem" do it "has no rubocop offenses when using --ext=rust, --test=minitest, and --linter=rubocop flag" 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 @@ -371,7 +361,6 @@ RSpec.describe "bundle gem" do it "has no rubocop offenses when using --ext=rust, --test=rspec, and --linter=rubocop flag" 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 @@ -380,7 +369,6 @@ RSpec.describe "bundle gem" do it "has no rubocop offenses when using --ext=rust, --test=test-unit, and --linter=rubocop flag" 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 @@ -398,11 +386,17 @@ RSpec.describe "bundle gem" do shared_examples_for "test framework is absent" do it "does not create any test framework files" do expect(bundled_app("#{gem_name}/.rspec")).to_not exist - expect(bundled_app("#{gem_name}/spec/#{require_path}_spec.rb")).to_not exist + expect(bundled_app("#{gem_name}/spec/#{gem_name}_spec.rb")).to_not exist expect(bundled_app("#{gem_name}/spec/spec_helper.rb")).to_not exist - expect(bundled_app("#{gem_name}/test/#{require_path}.rb")).to_not exist + expect(bundled_app("#{gem_name}/test/#{gem_name}.rb")).to_not exist expect(bundled_app("#{gem_name}/test/test_helper.rb")).to_not exist end + + it "does not add any test framework files into ignore list" do + expect(ignore_paths).not_to include("test/") + expect(ignore_paths).not_to include(".rspec") + expect(ignore_paths).not_to include("spec/") + end end context "README.md" do @@ -454,10 +448,7 @@ RSpec.describe "bundle gem" do context "when git is not available" do # This spec cannot have `git` available in the test env before 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" => "" } + bundle "gem #{gem_name}", env: { "PATH" => "" } end it "creates the gem without the need for git" do @@ -471,19 +462,25 @@ RSpec.describe "bundle gem" do it "doesn't create a .gitignore file" do expect(bundled_app("#{gem_name}/.gitignore")).to_not exist end + + it "does not add .gitignore into ignore list" do + expect(ignore_paths).not_to include(".gitignore") + end end - it "generates a valid gemspec", :ruby_repo do + it "generates a valid gemspec" do bundle "gem newgem --bin" prepare_gemspec(bundled_app("newgem", "newgem.gemspec")) - 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 + build_repo2 do + build_dummy_irb "9.9.9" + end + gems = ["rake-#{rake_version}", "irb-9.9.9"] + system_gems gems, path: system_gem_path, gem_repo: gem_repo2 bundle "exec rake build", dir: bundled_app("newgem") - expect(last_command.stdboth).not_to include("ERROR") + expect(stdboth).not_to include("ERROR") end context "gem naming with relative paths" do @@ -534,7 +531,7 @@ RSpec.describe "bundle gem" do shared_examples_for "github_username configuration" do context "with github_username setting set to some value" do before do - global_config "BUNDLE_GEM__GITHUB_USERNAME" => "different_username" + bundle_config_global "gem.github_username different_username" bundle "gem #{gem_name}" end @@ -550,7 +547,7 @@ RSpec.describe "bundle gem" do context "with github_username setting set to false" do before do - global_config "BUNDLE_GEM__GITHUB_USERNAME" => "false" + bundle_config_global "gem.github_username false" bundle "gem #{gem_name}" end @@ -565,914 +562,1108 @@ RSpec.describe "bundle gem" do end end - shared_examples_for "generating a gem" do - it "generates a gem skeleton" do - bundle "gem #{gem_name}" + it "generates a gem skeleton" do + bundle "gem #{gem_name}" - expect(bundled_app("#{gem_name}/#{gem_name}.gemspec")).to exist - expect(bundled_app("#{gem_name}/Gemfile")).to exist - 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}/#{gem_name}.gemspec")).to exist + expect(bundled_app("#{gem_name}/Gemfile")).to exist + expect(bundled_app("#{gem_name}/Rakefile")).to exist + expect(bundled_app("#{gem_name}/lib/#{gem_name}.rb")).to exist + expect(bundled_app("#{gem_name}/lib/#{gem_name}/version.rb")).to exist + expect(bundled_app("#{gem_name}/sig/#{gem_name}.rbs")).to exist + expect(bundled_app("#{gem_name}/.gitignore")).to exist - expect(bundled_app("#{gem_name}/bin/setup")).to exist - expect(bundled_app("#{gem_name}/bin/console")).to exist + expect(bundled_app("#{gem_name}/bin/setup")).to exist + expect(bundled_app("#{gem_name}/bin/console")).to exist - unless Gem.win_platform? - expect(bundled_app("#{gem_name}/bin/setup")).to be_executable - expect(bundled_app("#{gem_name}/bin/console")).to be_executable - end - - expect(bundled_app("#{gem_name}/bin/setup").read).to start_with("#!") - expect(bundled_app("#{gem_name}/bin/console").read).to start_with("#!") + unless Gem.win_platform? + expect(bundled_app("#{gem_name}/bin/setup")).to be_executable + expect(bundled_app("#{gem_name}/bin/console")).to be_executable end - it "starts with version 0.1.0" do + expect(bundled_app("#{gem_name}/bin/setup").read).to start_with("#!") + expect(bundled_app("#{gem_name}/bin/console").read).to start_with("#!") + end + + it "includes bin/ into ignore list" do + bundle "gem #{gem_name}" + + expect(ignore_paths).to include("bin/") + end + + it "includes Gemfile into ignore list" do + bundle "gem #{gem_name}" + + expect(ignore_paths).to include("Gemfile") + end + + it "includes .gitignore into ignore list" do + bundle "gem #{gem_name}" + + expect(ignore_paths).to include(".gitignore") + end + + it "starts with version 0.1.0" do + bundle "gem #{gem_name}" + + expect(bundled_app("#{gem_name}/lib/#{gem_name}/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/#{gem_name}.rbs").read).to match(/VERSION: String/) + end + + context "git config user.{name,email} is set" do + before do bundle "gem #{gem_name}" + end + + it "sets gemspec author to git user.name if available" do + expect(generated_gemspec.authors.first).to eq("Bundler User") + end - expect(bundled_app("#{gem_name}/lib/#{require_path}/version.rb").read).to match(/VERSION = "0.1.0"/) + it "sets gemspec email to git user.email if available" do + expect(generated_gemspec.email.first).to eq("user@example.com") end + end - it "declare String type for VERSION constant" do + context "git config user.{name,email} is not set" do + before do + git("config --global --unset user.name") + git("config --global --unset user.email") bundle "gem #{gem_name}" + end - expect(bundled_app("#{gem_name}/sig/#{require_path}.rbs").read).to match(/VERSION: String/) + it "sets gemspec author to default message if git user.name is not set or empty" do + expect(generated_gemspec.authors.first).to eq("TODO: Write your name") end - context "git config user.{name,email} is set" do - before do - bundle "gem #{gem_name}" - end + it "sets gemspec email to default message if git user.email is not set or empty" do + expect(generated_gemspec.email.first).to eq("TODO: Write your email address") + end + end - it "sets gemspec author to git user.name if available" do - expect(generated_gemspec.authors.first).to eq("Bundler User") - end + it "sets gemspec metadata['allowed_push_host']" do + bundle "gem #{gem_name}" + + expect(generated_gemspec.metadata["allowed_push_host"]). + to match(/example\.com/) + end + + it "includes a commented-out rubygems_mfa_required metadata hint" do + bundle "gem #{gem_name}" + + gemspec_contents = bundled_app("#{gem_name}/#{gem_name}.gemspec").read + + expect(gemspec_contents).to include('# spec.metadata["rubygems_mfa_required"] = "true"') + expect(gemspec_contents).to include("https://guides.rubygems.org/mfa-requirement-opt-in/") + end + + it "sets a minimum ruby version" do + bundle "gem #{gem_name}" + + expect(generated_gemspec.required_ruby_version.to_s).to start_with(">=") + 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 "sets gemspec email to git user.email if available" do - expect(generated_gemspec.email.first).to eq("user@example.com") + it "runs rake without problems" do + bundle "gem #{gem_name}" + + system_gems ["rake-#{rake_version}"] + + rakefile = <<~RAKEFILE + task :default do + puts 'SUCCESS' end + RAKEFILE + File.open(bundled_app("#{gem_name}/Rakefile"), "w") do |file| + file.puts rakefile end - context "git config user.{name,email} is not set" do - before do - git("config --global --unset user.name") - git("config --global --unset user.email") - bundle "gem #{gem_name}" - end + sys_exec("rake", dir: bundled_app(gem_name)) + expect(out).to include("SUCCESS") + end - it "sets gemspec author to default message if git user.name is not set or empty" do - expect(generated_gemspec.authors.first).to eq("TODO: Write your name") - end + context "--exe parameter set" do + before do + bundle "gem #{gem_name} --exe" + end - it "sets gemspec email to default message if git user.email is not set or empty" do - expect(generated_gemspec.email.first).to eq("TODO: Write your email address") + it "builds exe skeleton" do + expect(bundled_app("#{gem_name}/exe/#{gem_name}")).to exist + unless Gem.win_platform? + expect(bundled_app("#{gem_name}/exe/#{gem_name}")).to be_executable end end + end - it "sets gemspec metadata['allowed_push_host']" do - bundle "gem #{gem_name}" + context "--bin parameter set" do + before do + bundle "gem #{gem_name} --bin" + end - expect(generated_gemspec.metadata["allowed_push_host"]). - to match(/example\.com/) + it "builds exe skeleton" do + expect(bundled_app("#{gem_name}/exe/#{gem_name}")).to exist end + end - it "sets a minimum ruby version" do + context "no --test parameter" do + before do bundle "gem #{gem_name}" - - expect(generated_gemspec.required_ruby_version.to_s).to start_with(">=") end - it "requires the version file" do - bundle "gem #{gem_name}" + it_behaves_like "test framework is absent" + end - expect(bundled_app("#{gem_name}/lib/#{require_path}.rb").read).to match(%r{require_relative "#{require_relative_path}/version"}) + context "--test parameter set to rspec" do + before do + bundle "gem #{gem_name} --test=rspec" end - it "creates a base error class" do - bundle "gem #{gem_name}" + it "builds spec skeleton" do + expect(bundled_app("#{gem_name}/.rspec")).to exist + expect(bundled_app("#{gem_name}/spec/#{gem_name}_spec.rb")).to exist + expect(bundled_app("#{gem_name}/spec/spec_helper.rb")).to exist + end - expect(bundled_app("#{gem_name}/lib/#{require_path}.rb").read).to match(/class Error < StandardError; end$/) + it "includes .rspec and spec/ into ignore list" do + expect(ignore_paths).to include(".rspec") + expect(ignore_paths).to include("spec/") + end + + it "depends on a non-specific version of rspec in generated Gemfile" do + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + builder = Bundler::Dsl.new + builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile")) + builder.dependencies + rspec_dep = builder.dependencies.find {|d| d.name == "rspec" } + expect(rspec_dep).not_to be_specific + expect(rspec_dep.requirement).to eq(Gem::Requirement.new([">= 0"])) end + end - it "does not include the gemspec file in files" do + context "init_gems_rb setting to true" do + before do + bundle_config "init_gems_rb true" bundle "gem #{gem_name}" + end - bundler_gemspec = Bundler::GemHelper.new(bundled_app(gem_name), gem_name).gemspec + it "generates gems.rb instead of Gemfile" do + expect(bundled_app("#{gem_name}/gems.rb")).to exist + expect(bundled_app("#{gem_name}/Gemfile")).to_not exist + end - expect(bundler_gemspec.files).not_to include("#{gem_name}.gemspec") + it "includes gems.rb and gems.locked into ignore list" do + expect(ignore_paths).to include("gems.rb") + expect(ignore_paths).to include("gems.locked") + expect(ignore_paths).not_to include("Gemfile") end + end - it "does not include the Gemfile file in files" do + context "init_gems_rb setting to false" do + before do + bundle_config "init_gems_rb false" bundle "gem #{gem_name}" + end - bundler_gemspec = Bundler::GemHelper.new(bundled_app(gem_name), gem_name).gemspec + it "generates Gemfile instead of gems.rb" do + expect(bundled_app("#{gem_name}/gems.rb")).to_not exist + expect(bundled_app("#{gem_name}/Gemfile")).to exist + end - expect(bundler_gemspec.files).not_to include("Gemfile") + it "includes Gemfile into ignore list" do + expect(ignore_paths).to include("Gemfile") + expect(ignore_paths).not_to include("gems.rb") + expect(ignore_paths).not_to include("gems.locked") end + end - it "runs rake without problems" do + context "gem.test setting set to rspec" do + before do + bundle_config "gem.test rspec" bundle "gem #{gem_name}" + end - system_gems ["rake-#{rake_version}"] - - rakefile = <<~RAKEFILE - task :default do - puts 'SUCCESS' - end - RAKEFILE - File.open(bundled_app("#{gem_name}/Rakefile"), "w") do |file| - file.puts rakefile - end - - sys_exec(rake, dir: bundled_app(gem_name)) - expect(out).to include("SUCCESS") + it "builds spec skeleton" do + expect(bundled_app("#{gem_name}/.rspec")).to exist + expect(bundled_app("#{gem_name}/spec/#{gem_name}_spec.rb")).to exist + expect(bundled_app("#{gem_name}/spec/spec_helper.rb")).to exist end - context "--exe parameter set" do - before do - bundle "gem #{gem_name} --exe" - end + it "includes .rspec and spec/ into ignore list" do + expect(ignore_paths).to include(".rspec") + expect(ignore_paths).to include("spec/") + end + end - it "builds exe skeleton" do - expect(bundled_app("#{gem_name}/exe/#{gem_name}")).to exist - unless Gem.win_platform? - expect(bundled_app("#{gem_name}/exe/#{gem_name}")).to be_executable - end - end + context "gem.test setting set to rspec and --test is set to minitest" do + before do + bundle_config "gem.test rspec" + bundle "gem #{gem_name} --test=minitest" + end - it "requires the main file" do - expect(bundled_app("#{gem_name}/exe/#{gem_name}").read).to match(/require "#{require_path}"/) - end + it "builds spec skeleton" do + expect(bundled_app("#{gem_name}/test/test_#{gem_name}.rb")).to exist + expect(bundled_app("#{gem_name}/test/test_helper.rb")).to exist end - context "--bin parameter set" do - before do - bundle "gem #{gem_name} --bin" - end + it "includes test/ into ignore list" do + expect(ignore_paths).to include("test/") + end + end - it "builds exe skeleton" do - expect(bundled_app("#{gem_name}/exe/#{gem_name}")).to exist - end + context "--test parameter set to minitest" do + before do + bundle "gem #{gem_name} --test=minitest" + end - it "requires the main file" do - expect(bundled_app("#{gem_name}/exe/#{gem_name}").read).to match(/require "#{require_path}"/) - end + it "depends on a non-specific version of minitest" do + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + builder = Bundler::Dsl.new + builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile")) + builder.dependencies + minitest_dep = builder.dependencies.find {|d| d.name == "minitest" } + expect(minitest_dep).not_to be_specific + expect(minitest_dep.requirement).to eq(Gem::Requirement.new([">= 0"])) end - context "no --test parameter" do - before do - bundle "gem #{gem_name}" - end + it "builds spec skeleton" do + expect(bundled_app("#{gem_name}/test/test_#{gem_name}.rb")).to exist + expect(bundled_app("#{gem_name}/test/test_helper.rb")).to exist + end - it_behaves_like "test framework is absent" + it "includes test/ into ignore list" do + expect(ignore_paths).to include("test/") end - context "--test parameter set to rspec" do - before do - bundle "gem #{gem_name} --test=rspec" - end + it "creates a default rake task to run the test suite" do + rakefile = <<~RAKEFILE + # frozen_string_literal: true - it "builds spec skeleton" do - expect(bundled_app("#{gem_name}/.rspec")).to exist - expect(bundled_app("#{gem_name}/spec/#{require_path}_spec.rb")).to exist - expect(bundled_app("#{gem_name}/spec/spec_helper.rb")).to exist - end + require "bundler/gem_tasks" + require "minitest/test_task" - it "depends on a specific version of rspec in generated Gemfile" do - allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) - builder = Bundler::Dsl.new - builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile")) - builder.dependencies - rspec_dep = builder.dependencies.find {|d| d.name == "rspec" } - expect(rspec_dep).to be_specific - end + Minitest::TestTask.create - it "requires the main file" do - expect(bundled_app("#{gem_name}/spec/spec_helper.rb").read).to include(%(require "#{require_path}")) - end + task default: :test + RAKEFILE - it "creates a default test which fails" do - expect(bundled_app("#{gem_name}/spec/#{require_path}_spec.rb").read).to include("expect(false).to eq(true)") - end + expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) end + end - context "init_gems_rb setting to true" do - before do - bundle "config set init_gems_rb true" - bundle "gem #{gem_name}" - end - - it "generates gems.rb instead of Gemfile" do - expect(bundled_app("#{gem_name}/gems.rb")).to exist - expect(bundled_app("#{gem_name}/Gemfile")).to_not exist - end + context "gem.test setting set to minitest" do + before do + bundle_config "gem.test minitest" + bundle "gem #{gem_name}" end - context "init_gems_rb setting to false" do - before do - bundle "config set init_gems_rb false" - bundle "gem #{gem_name}" - end + it "creates a default rake task to run the test suite" do + rakefile = <<~RAKEFILE + # frozen_string_literal: true - it "generates Gemfile instead of gems.rb" do - expect(bundled_app("#{gem_name}/gems.rb")).to_not exist - expect(bundled_app("#{gem_name}/Gemfile")).to exist - end - end + require "bundler/gem_tasks" + require "minitest/test_task" - context "gem.test setting set to rspec" do - before do - bundle "config set gem.test rspec" - bundle "gem #{gem_name}" - end + Minitest::TestTask.create - it "builds spec skeleton" do - expect(bundled_app("#{gem_name}/.rspec")).to exist - expect(bundled_app("#{gem_name}/spec/#{require_path}_spec.rb")).to exist - expect(bundled_app("#{gem_name}/spec/spec_helper.rb")).to exist - end + task default: :test + RAKEFILE + + expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) end + end - context "gem.test setting set to rspec and --test is set to minitest" do - before do - bundle "config set gem.test rspec" - bundle "gem #{gem_name} --test=minitest" - end + context "--test parameter set to test-unit" do + before do + bundle "gem #{gem_name} --test=test-unit" + end - it "builds spec skeleton" do - expect(bundled_app("#{gem_name}/#{minitest_test_file_path}")).to exist - expect(bundled_app("#{gem_name}/test/test_helper.rb")).to exist - end + it "depends on a non-specific version of test-unit" do + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + builder = Bundler::Dsl.new + builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile")) + builder.dependencies + test_unit_dep = builder.dependencies.find {|d| d.name == "test-unit" } + expect(test_unit_dep).not_to be_specific + expect(test_unit_dep.requirement).to eq(Gem::Requirement.new([">= 0"])) end - context "--test parameter set to minitest" do - before do - bundle "gem #{gem_name} --test=minitest" - end + it "builds spec skeleton" do + expect(bundled_app("#{gem_name}/test/#{gem_name}_test.rb")).to exist + expect(bundled_app("#{gem_name}/test/test_helper.rb")).to exist + end - it "depends on a specific version of minitest" do - allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) - builder = Bundler::Dsl.new - builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile")) - builder.dependencies - minitest_dep = builder.dependencies.find {|d| d.name == "minitest" } - expect(minitest_dep).to be_specific - end + it "includes test/ into ignore list" do + expect(ignore_paths).to include("test/") + end - it "builds spec skeleton" do - expect(bundled_app("#{gem_name}/#{minitest_test_file_path}")).to exist - expect(bundled_app("#{gem_name}/test/test_helper.rb")).to exist - end + it "creates a default rake task to run the test suite" do + rakefile = <<~RAKEFILE + # frozen_string_literal: true - it "requires the main file" do - expect(bundled_app("#{gem_name}/test/test_helper.rb").read).to include(%(require "#{require_path}")) - end + require "bundler/gem_tasks" + require "rake/testtask" - it "requires 'test_helper'" do - expect(bundled_app("#{gem_name}/#{minitest_test_file_path}").read).to include(%(require "test_helper")) - end + Rake::TestTask.new(:test) do |t| + t.libs << "test" + t.libs << "lib" + t.test_files = FileList["test/**/*_test.rb"] + 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 + task default: :test + RAKEFILE - it "creates a default test which fails" do - expect(bundled_app("#{gem_name}/#{minitest_test_file_path}").read).to include("assert false") - end + expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) end + end - context "gem.test setting set to minitest" do - before do - bundle "config set gem.test minitest" - bundle "gem #{gem_name}" - end - - it "creates a default rake task to run the test suite" do - rakefile = <<~RAKEFILE - # frozen_string_literal: true + context "--test parameter set to an invalid value" do + before do + bundle "gem #{gem_name} --test=foo", raise_on_error: false + end - require "bundler/gem_tasks" - require "minitest/test_task" + it "fails loudly" do + expect(last_command).to be_failure + expect(err).to match(/Expected '--test' to be one of .*; got foo/) + end + end - Minitest::TestTask.create + context "gem.test set to rspec and --test with no arguments" do + before do + bundle_config "gem.test rspec" + bundle "gem #{gem_name} --test" + end - task default: :test - RAKEFILE + it "builds spec skeleton" do + expect(bundled_app("#{gem_name}/.rspec")).to exist + expect(bundled_app("#{gem_name}/spec/#{gem_name}_spec.rb")).to exist + expect(bundled_app("#{gem_name}/spec/spec_helper.rb")).to exist + end - expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) - end + it "includes .rspec and spec/ into ignore list" do + expect(ignore_paths).to include(".rspec") + expect(ignore_paths).to include("spec/") end - context "--test parameter set to test-unit" do - before do - bundle "gem #{gem_name} --test=test-unit" - end + it "hints that --test is already configured" do + expect(out).to match("rspec is already configured, ignoring --test flag.") + end + end - it "depends on a specific version of test-unit" do - allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) - builder = Bundler::Dsl.new - builder.eval_gemfile(bundled_app("#{gem_name}/Gemfile")) - builder.dependencies - test_unit_dep = builder.dependencies.find {|d| d.name == "test-unit" } - expect(test_unit_dep).to be_specific + context "gem.test setting set to false and --test with no arguments", :readline do + before do + bundle_config "gem.test false" + bundle "gem #{gem_name} --test" do |input, _, _| + input.puts end + end - it "builds spec skeleton" do - expect(bundled_app("#{gem_name}/test/#{require_path}_test.rb")).to exist - expect(bundled_app("#{gem_name}/test/test_helper.rb")).to exist - end + it "asks to generate test files" do + expect(out).to match("Do you want to generate tests with your gem?") + end - it "requires the main file" do - expect(bundled_app("#{gem_name}/test/test_helper.rb").read).to include(%(require "#{require_path}")) - end + it "hints that the choice will only be applied to the current gem" do + expect(out).to match("Your choice will only be applied to this gem.") + end - it "requires 'test_helper'" do - expect(bundled_app("#{gem_name}/test/#{require_path}_test.rb").read).to include(%(require "test_helper")) - end + it_behaves_like "test framework is absent" + end - it "creates a default test which fails" do - expect(bundled_app("#{gem_name}/test/#{require_path}_test.rb").read).to include("assert_equal(\"expected\", \"actual\")") + context "gem.test setting not set and --test with no arguments", :readline do + before do + bundle_config_global "BUNDLE_GEM__TEST" => nil + bundle "gem #{gem_name} --test" do |input, _, _| + input.puts end end - context "--test parameter set to an invalid value" do - before do - bundle "gem #{gem_name} --test=foo", raise_on_error: false - end + it "asks to generate test files" do + expect(out).to match("Do you want to generate tests with your gem?") + end - it "fails loudly" do - expect(last_command).to be_failure - expect(err).to match(/Expected '--test' to be one of .*; got foo/) - end + it "hints that the choice will be applied to future bundle gem calls" do + hint = "Future `bundle gem` calls will use your choice. " \ + "This setting can be changed anytime with `bundle config gem.test`." + expect(out).to match(hint) end - context "gem.test setting set to test-unit" do - before do - bundle "config set gem.test test-unit" - bundle "gem #{gem_name}" - end + it_behaves_like "test framework is absent" + end - it "creates a default rake task to run the test suite" do - rakefile = <<~RAKEFILE - # frozen_string_literal: true + context "gem.test setting set to a test framework and --no-test" do + before do + bundle_config "gem.test rspec" + bundle "gem #{gem_name} --no-test" + end - require "bundler/gem_tasks" - require "rake/testtask" + it_behaves_like "test framework is absent" + end - Rake::TestTask.new(:test) do |t| - t.libs << "test" - t.libs << "lib" - t.test_files = FileList["test/**/*_test.rb"] - end + context "--ci with no argument" do + before do + bundle "gem #{gem_name}" + end - task default: :test - RAKEFILE + it "does not generate any CI config" do + expect(bundled_app("#{gem_name}/.github/workflows/main.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 - expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) - end + it "does not add any CI config files into ignore list" do + expect(ignore_paths).not_to include(".github/") + expect(ignore_paths).not_to include(".gitlab-ci.yml") + expect(ignore_paths).not_to include(".circleci/") end + end - context "gem.test set to rspec and --test with no arguments" do - before do - bundle "config set gem.test rspec" - bundle "gem #{gem_name} --test" - end + context "--ci set to github" do + before do + bundle "gem #{gem_name} --ci=github" + end - it "builds spec skeleton" do - expect(bundled_app("#{gem_name}/.rspec")).to exist - expect(bundled_app("#{gem_name}/spec/#{require_path}_spec.rb")).to exist - expect(bundled_app("#{gem_name}/spec/spec_helper.rb")).to exist - end + it "generates a GitHub Actions config file" do + expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist + end - it "hints that --test is already configured" do - expect(out).to match("rspec is already configured, ignoring --test flag.") - end + it "includes .github/ into ignore list" do + expect(ignore_paths).to include(".github/") end + end - context "gem.test setting set to false and --test with no arguments", :readline do - before do - bundle "config set gem.test false" - bundle "gem #{gem_name} --test" do |input, _, _| - input.puts - end - end + context "--ci set to gitlab" do + before do + bundle "gem #{gem_name} --ci=gitlab" + end - it "asks to generate test files" do - expect(out).to match("Do you want to generate tests with your gem?") - end + it "generates a GitLab CI config file" do + expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to exist + end - it "hints that the choice will only be applied to the current gem" do - expect(out).to match("Your choice will only be applied to this gem.") - end + it "includes .gitlab-ci.yml into ignore list" do + expect(ignore_paths).to include(".gitlab-ci.yml") + end + end - it_behaves_like "test framework is absent" + context "--ci set to circle" do + before do + bundle "gem #{gem_name} --ci=circle" end - context "gem.test setting not set and --test with no arguments", :readline do - before do - global_config "BUNDLE_GEM__TEST" => nil - bundle "gem #{gem_name} --test" do |input, _, _| - input.puts - end - end + it "generates a CircleCI config file" do + expect(bundled_app("#{gem_name}/.circleci/config.yml")).to exist + end - it "asks to generate test files" do - expect(out).to match("Do you want to generate tests with your gem?") - end + it "includes .circleci/ into ignore list" do + expect(ignore_paths).to include(".circleci/") + end + end - it "hints that the choice will be applied to future bundle gem calls" do - hint = "Future `bundle gem` calls will use your choice. " \ - "This setting can be changed anytime with `bundle config gem.test`." - expect(out).to match(hint) - end + context "--ci set to an invalid value" do + before do + bundle "gem #{gem_name} --ci=foo", raise_on_error: false + end - it_behaves_like "test framework is absent" + it "fails loudly" do + expect(last_command).to be_failure + expect(err).to match(/Expected '--ci' to be one of .*; got foo/) end + end - context "gem.test setting set to a test framework and --no-test" do - before do - bundle "config set gem.test rspec" - bundle "gem #{gem_name} --no-test" - 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}/.gitlab-ci.yml")).to_not exist + expect(bundled_app("#{gem_name}/.circleci/config.yml")).to_not exist + end + end - it_behaves_like "test framework is absent" + context "gem.ci setting set to github" do + it "generates a GitHub Actions config file" do + bundle_config "gem.ci github" + bundle "gem #{gem_name}" + + expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist end + end - context "--ci with no argument" do - it "does not generate any CI config" do - bundle "gem #{gem_name}" + context "gem.ci setting set to gitlab" do + it "generates a GitLab CI config file" do + bundle_config "gem.ci gitlab" + bundle "gem #{gem_name}" - expect(bundled_app("#{gem_name}/.github/workflows/main.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 + expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to exist end + end - context "--ci set to github" do - it "generates a GitHub Actions config file" do - bundle "gem #{gem_name} --ci=github" + context "gem.ci setting set to circle" do + it "generates a CircleCI config file" do + bundle_config "gem.ci circle" + bundle "gem #{gem_name}" - expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist - end + expect(bundled_app("#{gem_name}/.circleci/config.yml")).to exist + end + end - it "contained .gitlab-ci.yml into ignore list" do - bundle "gem #{gem_name} --ci=github" + context "gem.ci set to github and --ci with no arguments" do + before do + bundle_config "gem.ci github" + bundle "gem #{gem_name} --ci" + end - expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include(".git .github appveyor") - end + it "generates a GitHub Actions config file" do + expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist end - context "--ci set to gitlab" do - it "generates a GitLab CI config file" do - bundle "gem #{gem_name} --ci=gitlab" + it "hints that --ci is already configured" do + expect(out).to match("github is already configured, ignoring --ci flag.") + end + end - expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to exist + context "gem.ci setting set to false and --ci with no arguments", :readline do + before do + bundle_config "gem.ci false" + bundle "gem #{gem_name} --ci" do |input, _, _| + input.puts "github" end + end + + it "asks to setup CI" do + expect(out).to match("Do you want to set up continuous integration for your gem?") + end - it "contained .gitlab-ci.yml into ignore list" do - bundle "gem #{gem_name} --ci=gitlab" + it "hints that the choice will only be applied to the current gem" do + expect(out).to match("Your choice will only be applied to this gem.") + end + end - expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include(".git .gitlab-ci.yml appveyor") + context "gem.ci setting not set and --ci with no arguments", :readline do + before do + bundle_config_global "BUNDLE_GEM__CI" => nil + bundle "gem #{gem_name} --ci" do |input, _, _| + input.puts "github" end end - context "--ci set to circle" do - it "generates a CircleCI config file" do - bundle "gem #{gem_name} --ci=circle" + it "asks to setup CI" do + expect(out).to match("Do you want to set up continuous integration for your gem?") + end - expect(bundled_app("#{gem_name}/.circleci/config.yml")).to exist - end + it "hints that the choice will be applied to future bundle gem calls" do + hint = "Future `bundle gem` calls will use your choice. " \ + "This setting can be changed anytime with `bundle config gem.ci`." + expect(out).to match(hint) + end + end - it "contained .circleci into ignore list" do - bundle "gem #{gem_name} --ci=circle" + context "gem.ci setting set to a CI service and --no-ci" do + before do + bundle_config "gem.ci github" + bundle "gem #{gem_name} --no-ci" + end - expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include(".git .circleci appveyor") - end + it "does not generate any CI config" do + expect(bundled_app("#{gem_name}/.github/workflows/main.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 + end - context "--ci set to an invalid value" do - before do - bundle "gem #{gem_name} --ci=foo", raise_on_error: false - end + context "--linter with no argument" do + before do + bundle "gem #{gem_name}" + end - it "fails loudly" do - expect(last_command).to be_failure - expect(err).to match(/Expected '--ci' to be one of .*; got foo/) - end + it "does not generate any linter config" do + expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist + expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist 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}/.gitlab-ci.yml")).to_not exist - expect(bundled_app("#{gem_name}/.circleci/config.yml")).to_not exist - end + it "does not add any linter config files into ignore list" do + expect(ignore_paths).not_to include(".rubocop.yml") + expect(ignore_paths).not_to include(".standard.yml") end + end - context "gem.ci setting set to github" do - it "generates a GitHub Actions config file" do - bundle "config set gem.ci github" - bundle "gem #{gem_name}" + context "--linter set to rubocop" do + before do + bundle "gem #{gem_name} --linter=rubocop" + end - expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist - end + it "generates a RuboCop config" do + expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist + expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist end - context "gem.ci setting set to gitlab" do - it "generates a GitLab CI config file" do - bundle "config set gem.ci gitlab" - bundle "gem #{gem_name}" + it "includes .rubocop.yml into ignore list" do + expect(ignore_paths).to include(".rubocop.yml") + expect(ignore_paths).not_to include(".standard.yml") + end + end - expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to exist - end + context "--linter set to standard" do + before do + bundle "gem #{gem_name} --linter=standard" end - context "gem.ci setting set to circle" do - it "generates a CircleCI config file" do - bundle "config set gem.ci circle" - bundle "gem #{gem_name}" + it "generates a Standard config" do + expect(bundled_app("#{gem_name}/.standard.yml")).to exist + expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist + end - expect(bundled_app("#{gem_name}/.circleci/config.yml")).to exist - end + it "includes .standard.yml into ignore list" do + expect(ignore_paths).to include(".standard.yml") + expect(ignore_paths).not_to include(".rubocop.yml") end + end - context "gem.ci set to github and --ci with no arguments" do - before do - bundle "config set gem.ci github" - bundle "gem #{gem_name} --ci" - end + context "--linter set to an invalid value" do + before do + bundle "gem #{gem_name} --linter=foo", raise_on_error: false + end - it "generates a GitHub Actions config file" do - expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist - end + it "fails loudly" do + expect(last_command).to be_failure + expect(err).to match(/Expected '--linter' to be one of .*; got foo/) + end + end - it "hints that --ci is already configured" do - expect(out).to match("github is already configured, ignoring --ci flag.") - end + context "gem.linter setting set to none" do + before do + bundle "gem #{gem_name}" end - context "gem.ci setting set to false and --ci with no arguments", :readline do - before do - bundle "config set gem.ci false" - bundle "gem #{gem_name} --ci" do |input, _, _| - input.puts "github" - end - end + it "doesn't generate any linter config" do + expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist + expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist + end - it "asks to setup CI" do - expect(out).to match("Do you want to set up continuous integration for your gem?") - end + it "does not add any linter config files into ignore list" do + expect(ignore_paths).not_to include(".rubocop.yml") + expect(ignore_paths).not_to include(".standard.yml") + end + end - it "hints that the choice will only be applied to the current gem" do - expect(out).to match("Your choice will only be applied to this gem.") - end + context "gem.linter setting set to rubocop" do + before do + bundle_config "gem.linter rubocop" + bundle "gem #{gem_name}" end - context "gem.ci setting not set and --ci with no arguments", :readline do - before do - global_config "BUNDLE_GEM__CI" => nil - bundle "gem #{gem_name} --ci" do |input, _, _| - input.puts "github" - end - end + it "generates a RuboCop config file" do + expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist + end - it "asks to setup CI" do - expect(out).to match("Do you want to set up continuous integration for your gem?") - end + it "includes .rubocop.yml into ignore list" do + expect(ignore_paths).to include(".rubocop.yml") + end + end - it "hints that the choice will be applied to future bundle gem calls" do - hint = "Future `bundle gem` calls will use your choice. " \ - "This setting can be changed anytime with `bundle config gem.ci`." - expect(out).to match(hint) - end + context "gem.linter setting set to standard" do + before do + bundle_config "gem.linter standard" + bundle "gem #{gem_name}" end - context "gem.ci setting set to a CI service and --no-ci" do - before do - bundle "config set gem.ci github" - bundle "gem #{gem_name} --no-ci" - end + it "generates a Standard config file" do + expect(bundled_app("#{gem_name}/.standard.yml")).to exist + end - it "does not generate any CI config" do - expect(bundled_app("#{gem_name}/.github/workflows/main.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 + it "includes .standard.yml into ignore list" do + expect(ignore_paths).to include(".standard.yml") end + end - context "--linter with no argument" do - it "does not generate any linter config" do - bundle "gem #{gem_name}" + context "gem.linter set to rubocop and --linter with no arguments" do + before do + bundle_config "gem.linter rubocop" + bundle "gem #{gem_name} --linter" + end - expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist - expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist - end + it "generates a RuboCop config file" do + expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist end - context "--linter set to rubocop" do - it "generates a RuboCop config" do - bundle "gem #{gem_name} --linter=rubocop" + it "includes .rubocop.yml into ignore list" do + expect(ignore_paths).to include(".rubocop.yml") + end - expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist - expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist + it "hints that --linter is already configured" do + expect(out).to match("rubocop is already configured, ignoring --linter flag.") + end + end + + context "gem.linter setting set to false and --linter with no arguments", :readline do + before do + bundle_config "gem.linter false" + bundle "gem #{gem_name} --linter" do |input, _, _| + input.puts "rubocop" end end - context "--linter set to standard" do - it "generates a Standard config" do - bundle "gem #{gem_name} --linter=standard" + it "asks to setup a linter" do + expect(out).to match("Do you want to add a code linter and formatter to your gem?") + end - expect(bundled_app("#{gem_name}/.standard.yml")).to exist - expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist - end + it "hints that the choice will only be applied to the current gem" do + expect(out).to match("Your choice will only be applied to this gem.") end + end - context "--linter set to an invalid value" do - before do - bundle "gem #{gem_name} --linter=foo", raise_on_error: false + context "gem.linter setting not set and --linter with no arguments", :readline do + before do + bundle_config_global "BUNDLE_GEM__LINTER" => nil + bundle "gem #{gem_name} --linter" do |input, _, _| + input.puts "rubocop" end + end - it "fails loudly" do - expect(last_command).to be_failure - expect(err).to match(/Expected '--linter' to be one of .*; got foo/) - end + it "asks to setup a linter" do + expect(out).to match("Do you want to add a code linter and formatter to your gem?") end - context "gem.linter setting set to none" do - it "doesn't generate any linter config" do - bundle "gem #{gem_name}" + it "hints that the choice will be applied to future bundle gem calls" do + hint = "Future `bundle gem` calls will use your choice. " \ + "This setting can be changed anytime with `bundle config gem.linter`." + expect(out).to match(hint) + end + end - expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist - expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist - end + context "gem.linter setting set to a linter and --no-linter" do + before do + bundle_config "gem.linter rubocop" + bundle "gem #{gem_name} --no-linter" end - context "gem.linter setting set to rubocop" do - it "generates a RuboCop config file" do - bundle "config set gem.linter rubocop" - bundle "gem #{gem_name}" + it "does not generate any linter config" do + expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist + expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist + end - expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist - end + it "does not add any linter config files into ignore list" do + expect(ignore_paths).not_to include(".rubocop.yml") + expect(ignore_paths).not_to include(".standard.yml") end + end - context "gem.linter setting set to standard" do - it "generates a Standard config file" do - bundle "config set gem.linter standard" - bundle "gem #{gem_name}" + context "--edit option" do + it "opens the generated gemspec in the user's text editor" do + output = bundle "gem #{gem_name} --edit=echo" + gemspec_path = File.join(bundled_app, gem_name, "#{gem_name}.gemspec") + expect(output).to include("echo \"#{gemspec_path}\"") + end + end - expect(bundled_app("#{gem_name}/.standard.yml")).to exist - end + shared_examples_for "paths that depend on gem name" do + it "generates entrypoint, version file and signatures file at the proper path, with the proper content" do + bundle "gem #{gem_name}" + + expect(bundled_app("#{gem_name}/lib/#{require_path}.rb")).to exist + expect(bundled_app("#{gem_name}/lib/#{require_path}.rb").read).to match(%r{require_relative "#{require_relative_path}/version"}) + expect(bundled_app("#{gem_name}/lib/#{require_path}.rb").read).to match(/class Error < StandardError; end$/) + + expect(bundled_app("#{gem_name}/lib/#{require_path}/version.rb")).to exist + expect(bundled_app("#{gem_name}/sig/#{require_path}.rbs")).to exist end - context "gem.rubocop setting set to true", bundler: "< 3" do + context "--exe parameter set" do before do - global_config "BUNDLE_GEM__LINTER" => nil - bundle "config set gem.rubocop true" - bundle "gem #{gem_name}" + bundle "gem #{gem_name} --exe" end - it "generates rubocop config" do - expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist + it "builds an exe file that requires the proper entrypoint" do + expect(bundled_app("#{gem_name}/exe/#{gem_name}")).to exist + expect(bundled_app("#{gem_name}/exe/#{gem_name}").read).to match(/require "#{require_path}"/) end + end - it "unsets gem.rubocop" do - bundle "config gem.rubocop" - expect(out).to include("You have not configured a value for `gem.rubocop`") + context "--bin parameter set" do + before do + bundle "gem #{gem_name} --bin" end - it "sets gem.linter=rubocop instead" do - bundle "config gem.linter" - expect(out).to match(/Set for the current user .*: "rubocop"/) + it "builds an exe file that requires the proper entrypoint" do + expect(bundled_app("#{gem_name}/exe/#{gem_name}")).to exist + expect(bundled_app("#{gem_name}/exe/#{gem_name}").read).to match(/require "#{require_path}"/) end end - context "gem.linter set to rubocop and --linter with no arguments" do + context "--test parameter set to rspec" do before do - bundle "config set gem.linter rubocop" - bundle "gem #{gem_name} --linter" - end - - it "generates a RuboCop config file" do - expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist + bundle "gem #{gem_name} --test=rspec" end - it "hints that --linter is already configured" do - expect(out).to match("rubocop is already configured, ignoring --linter flag.") + it "builds a spec helper that requires the proper entrypoint, and a default test in the proper path which fails" do + expect(bundled_app("#{gem_name}/spec/spec_helper.rb")).to exist + expect(bundled_app("#{gem_name}/spec/spec_helper.rb").read).to include(%(require "#{require_path}")) + expect(bundled_app("#{gem_name}/spec/#{require_path}_spec.rb")).to exist + expect(bundled_app("#{gem_name}/spec/#{require_path}_spec.rb").read).to include("expect(false).to eq(true)") end end - context "gem.linter setting set to false and --linter with no arguments", :readline do + context "--test parameter set to minitest" do before do - bundle "config set gem.linter false" - bundle "gem #{gem_name} --linter" do |input, _, _| - input.puts "rubocop" - end + bundle "gem #{gem_name} --test=minitest" end - it "asks to setup a linter" do - expect(out).to match("Do you want to add a code linter and formatter to your gem?") - end + it "builds a test helper that requires the proper entrypoint, and default test file in the proper path that defines the proper test class name, requires helper, and fails" do + expect(bundled_app("#{gem_name}/test/test_helper.rb")).to exist + expect(bundled_app("#{gem_name}/test/test_helper.rb").read).to include(%(require "#{require_path}")) - it "hints that the choice will only be applied to the current gem" do - expect(out).to match("Your choice will only be applied to this gem.") + expect(bundled_app("#{gem_name}/#{minitest_test_file_path}")).to exist + expect(bundled_app("#{gem_name}/#{minitest_test_file_path}").read).to include(minitest_test_class_name) + expect(bundled_app("#{gem_name}/#{minitest_test_file_path}").read).to include(%(require "test_helper")) + expect(bundled_app("#{gem_name}/#{minitest_test_file_path}").read).to include("assert false") end end - context "gem.linter setting not set and --linter with no arguments", :readline do + context "--test parameter set to test-unit" do before do - global_config "BUNDLE_GEM__LINTER" => nil - bundle "gem #{gem_name} --linter" do |input, _, _| - input.puts "rubocop" - end + bundle "gem #{gem_name} --test=test-unit" end - it "asks to setup a linter" do - expect(out).to match("Do you want to add a code linter and formatter to your gem?") + it "builds a test helper that requires the proper entrypoint, and default test file in the proper path which requires helper and fails" do + expect(bundled_app("#{gem_name}/test/test_helper.rb")).to exist + expect(bundled_app("#{gem_name}/test/test_helper.rb").read).to include(%(require "#{require_path}")) + expect(bundled_app("#{gem_name}/test/#{require_path}_test.rb")).to exist + expect(bundled_app("#{gem_name}/test/#{require_path}_test.rb").read).to include(%(require "test_helper")) + expect(bundled_app("#{gem_name}/test/#{require_path}_test.rb").read).to include("assert_equal(\"expected\", \"actual\")") end + end + end - it "hints that the choice will be applied to future bundle gem calls" do - hint = "Future `bundle gem` calls will use your choice. " \ - "This setting can be changed anytime with `bundle config gem.linter`." - expect(out).to match(hint) - end + context "with mit option in bundle config settings set to true" do + before do + bundle_config_global "gem.mit true" end + it_behaves_like "--mit flag" + it_behaves_like "--no-mit flag" + end - context "gem.linter setting set to a linter and --no-linter" do - before do - bundle "config set gem.linter rubocop" - bundle "gem #{gem_name} --no-linter" - end + context "with mit option in bundle config settings set to false" do + before do + bundle_config_global "gem.mit false" + end + it_behaves_like "--mit flag" + it_behaves_like "--no-mit flag" + end - it "does not generate any linter config" do - expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist - expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist - end + context "with coc option in bundle config settings set to true" do + before do + bundle_config_global "gem.coc true" end + it_behaves_like "--coc flag" + it_behaves_like "--no-coc flag" + end - context "--edit option" do - it "opens the generated gemspec in the user's text editor" do - output = bundle "gem #{gem_name} --edit=echo" - gemspec_path = File.join(bundled_app, gem_name, "#{gem_name}.gemspec") - expect(output).to include("echo \"#{gemspec_path}\"") - end + context "with coc option in bundle config settings set to false" do + before do + bundle_config_global "gem.coc false" end + it_behaves_like "--coc flag" + it_behaves_like "--no-coc flag" end - context "testing --mit and --coc options against bundle config settings" do - let(:gem_name) { "test-gem" } + context "with rubocop option in bundle config settings set to true" do + before do + bundle_config_global "gem.rubocop true" + end + it_behaves_like "--linter=rubocop flag" + it_behaves_like "--linter=standard flag" + it_behaves_like "--no-linter flag" + end - let(:require_path) { "test/gem" } + context "with rubocop option in bundle config settings set to false" do + before do + bundle_config_global "gem.rubocop false" + end + it_behaves_like "--linter=rubocop flag" + it_behaves_like "--linter=standard flag" + it_behaves_like "--no-linter flag" + end - context "with mit option in bundle config settings set to true" do - before do - global_config "BUNDLE_GEM__MIT" => "true" - end - it_behaves_like "--mit flag" - it_behaves_like "--no-mit flag" + context "with linter option in bundle config settings set to rubocop" do + before do + bundle_config_global "gem.linter rubocop" end + it_behaves_like "--linter=rubocop flag" + it_behaves_like "--linter=standard flag" + it_behaves_like "--no-linter flag" + end - context "with mit option in bundle config settings set to false" do - before do - global_config "BUNDLE_GEM__MIT" => "false" - end - it_behaves_like "--mit flag" - it_behaves_like "--no-mit flag" + context "with linter option in bundle config settings set to standard" do + before do + bundle_config_global "gem.linter standard" end + it_behaves_like "--linter=rubocop flag" + it_behaves_like "--linter=standard flag" + it_behaves_like "--no-linter flag" + end - context "with coc option in bundle config settings set to true" do - before do - global_config "BUNDLE_GEM__COC" => "true" - end - it_behaves_like "--coc flag" - it_behaves_like "--no-coc flag" + context "with linter option in bundle config settings set to false" do + before do + bundle_config_global "gem.linter false" end + it_behaves_like "--linter=rubocop flag" + it_behaves_like "--linter=standard flag" + it_behaves_like "--no-linter flag" + end - context "with coc option in bundle config settings set to false" do - before do - global_config "BUNDLE_GEM__COC" => "false" - end - it_behaves_like "--coc flag" - it_behaves_like "--no-coc flag" + context "with changelog option in bundle config settings set to true" do + before do + bundle_config_global "gem.changelog true" end + it_behaves_like "--changelog flag" + it_behaves_like "--no-changelog flag" + end - context "with rubocop option in bundle config settings set to true" do - before do - global_config "BUNDLE_GEM__RUBOCOP" => "true" - end - it_behaves_like "--linter=rubocop flag" - it_behaves_like "--linter=standard flag" - it_behaves_like "--no-linter flag" - it_behaves_like "--rubocop flag" - it_behaves_like "--no-rubocop flag" + context "with changelog option in bundle config settings set to false" do + before do + bundle_config_global "gem.changelog false" end + it_behaves_like "--changelog flag" + it_behaves_like "--no-changelog flag" + end - context "with rubocop option in bundle config settings set to false" do - before do - global_config "BUNDLE_GEM__RUBOCOP" => "false" - end - it_behaves_like "--linter=rubocop flag" - it_behaves_like "--linter=standard flag" - it_behaves_like "--no-linter flag" - it_behaves_like "--rubocop flag" - it_behaves_like "--no-rubocop flag" + context "with bundle option in bundle config settings set to true" do + before do + bundle_config_global "gem.bundle true" end + it_behaves_like "--bundle flag" + it_behaves_like "--no-bundle flag" - context "with linter option in bundle config settings set to rubocop" do - before do - global_config "BUNDLE_GEM__LINTER" => "rubocop" - end - it_behaves_like "--linter=rubocop flag" - it_behaves_like "--linter=standard flag" - it_behaves_like "--no-linter flag" + it "runs bundle install" do + bundle "gem #{gem_name}" + expect(out).to include("Running bundle install in the new gem directory.") + end + end + + context "with bundle option in bundle config settings set to false" do + before do + bundle_config_global "gem.bundle false" + end + it_behaves_like "--bundle flag" + it_behaves_like "--no-bundle flag" + + it "does not run bundle install" do + bundle "gem #{gem_name}" + expect(out).to_not include("Running bundle install in the new gem directory.") end + end - context "with linter option in bundle config settings set to standard" do + context "without git config github.user set" do + before do + git("config --global --unset github.user") + end + context "with github-username option in bundle config settings set to some value" do before do - global_config "BUNDLE_GEM__LINTER" => "standard" + bundle_config_global "gem.github_username different_username" end - it_behaves_like "--linter=rubocop flag" - it_behaves_like "--linter=standard flag" - it_behaves_like "--no-linter flag" + it_behaves_like "--github-username option", "gh_user" end - context "with linter option in bundle config settings set to false" do + it_behaves_like "github_username configuration" + + context "with github-username option in bundle config settings set to false" do before do - global_config "BUNDLE_GEM__LINTER" => "false" + bundle_config_global "gem.github_username false" end - it_behaves_like "--linter=rubocop flag" - it_behaves_like "--linter=standard flag" - it_behaves_like "--no-linter flag" + it_behaves_like "--github-username option", "gh_user" end - context "with changelog option in bundle config settings set to true" do - before do - global_config "BUNDLE_GEM__CHANGELOG" => "true" + context "when changelog is enabled" do + it "sets gemspec changelog_uri, homepage, homepage_uri, source_code_uri to TODOs" do + bundle "gem #{gem_name} --changelog" + + expect(generated_gemspec.metadata["changelog_uri"]). + to eq("TODO: Put your gem's CHANGELOG.md URL here.") + expect(generated_gemspec.homepage).to eq("TODO: Put your gem's website or public repo URL here.") + expect(generated_gemspec.metadata["homepage_uri"]).to eq("TODO: Put your gem's website or public repo URL here.") + expect(generated_gemspec.metadata["source_code_uri"]).to eq("TODO: Put your gem's public repo URL here.") end - it_behaves_like "--changelog flag" - it_behaves_like "--no-changelog flag" end - context "with changelog option in bundle config settings set to false" do - before do - global_config "BUNDLE_GEM__CHANGELOG" => "false" + context "when changelog is not enabled" do + it "sets gemspec homepage, homepage_uri, source_code_uri to TODOs and changelog_uri to nil" do + bundle "gem #{gem_name}" + + expect(generated_gemspec.metadata["changelog_uri"]).to be_nil + expect(generated_gemspec.homepage).to eq("TODO: Put your gem's website or public repo URL here.") + expect(generated_gemspec.metadata["homepage_uri"]).to eq("TODO: Put your gem's website or public repo URL here.") + expect(generated_gemspec.metadata["source_code_uri"]).to eq("TODO: Put your gem's public repo URL here.") end - it_behaves_like "--changelog flag" - it_behaves_like "--no-changelog flag" end end - context "testing --github-username option against git and bundle config settings" do - context "without git config set" do + context "with git config github.user set" do + context "with github-username option in bundle config settings set to some value" do before do - git("config --global --unset github.user") - end - context "with github-username option in bundle config settings set to some value" do - before do - global_config "BUNDLE_GEM__GITHUB_USERNAME" => "different_username" - end - it_behaves_like "--github-username option", "gh_user" + bundle_config_global "gem.github_username different_username" end + it_behaves_like "--github-username option", "gh_user" + end - context "with github-username option in bundle config settings set to false" do - before do - global_config "BUNDLE_GEM__GITHUB_USERNAME" => "false" - end - it_behaves_like "--github-username option", "gh_user" + it_behaves_like "github_username configuration" + + context "with github-username option in bundle config settings set to false" do + before do + bundle_config_global "gem.github_username false" end + it_behaves_like "--github-username option", "gh_user" end - context "with git config set" do - context "with github-username option in bundle config settings set to some value" do - before do - global_config "BUNDLE_GEM__GITHUB_USERNAME" => "different_username" - end - it_behaves_like "--github-username option", "gh_user" + context "when changelog is enabled" do + it "sets gemspec changelog_uri, homepage, homepage_uri, source_code_uri based on git username" do + bundle "gem #{gem_name} --changelog" + + expect(generated_gemspec.metadata["changelog_uri"]). + to eq("https://github.com/bundleuser/#{gem_name}/blob/main/CHANGELOG.md") + expect(generated_gemspec.homepage).to eq("https://github.com/bundleuser/#{gem_name}") + expect(generated_gemspec.metadata["homepage_uri"]).to eq("https://github.com/bundleuser/#{gem_name}") + expect(generated_gemspec.metadata["source_code_uri"]).to eq("https://github.com/bundleuser/#{gem_name}") end + end - context "with github-username option in bundle config settings set to false" do - before do - global_config "BUNDLE_GEM__GITHUB_USERNAME" => "false" - end - it_behaves_like "--github-username option", "gh_user" + context "when changelog is not enabled" do + it "sets gemspec source_code_uri, homepage, homepage_uri but not changelog_uri" do + bundle "gem #{gem_name}" + + expect(generated_gemspec.metadata["changelog_uri"]).to be_nil + expect(generated_gemspec.homepage).to eq("https://github.com/bundleuser/#{gem_name}") + expect(generated_gemspec.metadata["homepage_uri"]).to eq("https://github.com/bundleuser/#{gem_name}") + expect(generated_gemspec.metadata["source_code_uri"]).to eq("https://github.com/bundleuser/#{gem_name}") end end end - context "testing github_username bundle config against git config settings" do - context "without git config set" do - before do - git("config --global --unset github.user") - end + context "standard gem naming" do + let(:require_path) { gem_name } - it_behaves_like "github_username configuration" - end + let(:require_relative_path) { gem_name } - context "with git config set" do - it_behaves_like "github_username configuration" - end + let(:minitest_test_file_path) { "test/test_#{gem_name}.rb" } + + let(:minitest_test_class_name) { "class TestMygem < Minitest::Test" } + + include_examples "paths that depend on gem name" end context "gem naming with underscore" do @@ -1494,25 +1685,7 @@ RSpec.describe "bundle gem" do expect(bundled_app("#{gem_name}/lib/#{require_path}.rb").read).to match(/module TestGem/) end - include_examples "generating a gem" - - 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 + include_examples "paths that depend on gem name" context "--ext parameter set with C" do let(:flags) { "--ext=c" } @@ -1521,16 +1694,19 @@ RSpec.describe "bundle gem" 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 "generates native extension loading code" do + expect(bundled_app("#{gem_name}/lib/#{gem_name}.rb").read).to include(<<~RUBY) + require_relative "test_gem/version" + require "#{gem_name}/#{gem_name}" + RUBY + end + it "includes rake-compiler, but no Rust related changes" do expect(bundled_app("#{gem_name}/Gemfile").read).to include('gem "rake-compiler"') @@ -1560,24 +1736,10 @@ RSpec.describe "bundle gem" do 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 @@ -1590,12 +1752,12 @@ RSpec.describe "bundle gem" do 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 + expect(bundled_app("#{gem_name}/ext/#{gem_name}/build.rs")).to exist end - it "includes rake-compiler, rb_sys gems and required_rubygems_version constraint" do + it "includes rake-compiler and rb_sys gems constraint" do expect(bundled_app("#{gem_name}/Gemfile").read).to include('gem "rake-compiler"') expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include('spec.add_dependency "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 @@ -1618,6 +1780,182 @@ RSpec.describe "bundle gem" do expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) end + + it "configures the crate such that `cargo test` works", :ruby_repo, :mri_only do + env = setup_rust_env + gem_path = bundled_app(gem_name) + result = sys_exec("cargo test", env: env, dir: gem_path, timeout: 300) + + expect(result).to include("1 passed") + end + + def setup_rust_env + skip "rust toolchain of mingw is broken" if RUBY_PLATFORM.match?("mingw") + + env = { + "CARGO_HOME" => ENV.fetch("CARGO_HOME", File.join(ENV["HOME"], ".cargo")), + "RUSTUP_HOME" => ENV.fetch("RUSTUP_HOME", File.join(ENV["HOME"], ".rustup")), + "RUSTUP_TOOLCHAIN" => ENV.fetch("RUSTUP_TOOLCHAIN", "stable"), + } + + system(env, "cargo", "-V", out: IO::NULL, err: [:child, :out]) + skip "cargo not present" unless $?.success? + # Hermetic Cargo setup + RbConfig::CONFIG.each {|k, v| env["RBCONFIG_#{k}"] = v } + env + end + end + + context "--ext parameter set with go" do + let(:flags) { "--ext=go" } + + before do + bundle ["gem", gem_name, flags].compact.join(" ") + end + + after do + sys_exec("go clean -modcache", raise_on_error: true) if installed_go? + 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}/#{gem_name}.c")).to exist + expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.go")).to exist + expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.h")).to exist + expect(bundled_app("#{gem_name}/ext/#{gem_name}/extconf.rb")).to exist + expect(bundled_app("#{gem_name}/ext/#{gem_name}/go.mod")).to exist + end + + it "includes extconf.rb in gem_name.gemspec" do + expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include(%(spec.extensions = ["ext/#{gem_name}/extconf.rb"])) + end + + it "includes go_gem in gem_name.gemspec" do + expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include('spec.add_dependency "go_gem", ">= 0.2"') + end + + it "includes go_gem extension in extconf.rb" do + expect(bundled_app("#{gem_name}/ext/#{gem_name}/extconf.rb").read).to include(<<~RUBY) + require "mkmf" + require "go_gem/mkmf" + RUBY + + expect(bundled_app("#{gem_name}/ext/#{gem_name}/extconf.rb").read).to include(%(create_go_makefile("#{gem_name}/#{gem_name}"))) + expect(bundled_app("#{gem_name}/ext/#{gem_name}/extconf.rb").read).not_to include("create_makefile") + end + + it "includes go_gem extension in gem_name.c" do + expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.c").read).to eq(<<~C) + #include "#{gem_name}.h" + #include "_cgo_export.h" + C + end + + it "includes skeleton code in gem_name.go" do + expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.go").read).to include(<<~GO) + /* + #include "#{gem_name}.h" + + VALUE rb_#{gem_name}_sum(VALUE self, VALUE a, VALUE b); + */ + import "C" + GO + + expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.go").read).to include(<<~GO) + //export rb_#{gem_name}_sum + func rb_#{gem_name}_sum(_ C.VALUE, a C.VALUE, b C.VALUE) C.VALUE { + GO + + expect(bundled_app("#{gem_name}/ext/#{gem_name}/#{gem_name}.go").read).to include(<<~GO) + //export Init_#{gem_name} + func Init_#{gem_name}() { + GO + end + + it "includes valid module name in go.mod" do + expect(bundled_app("#{gem_name}/ext/#{gem_name}/go.mod").read).to include("module github.com/bundleuser/#{gem_name}") + end + + it "includes go_gem extension in Rakefile" do + expect(bundled_app("#{gem_name}/Rakefile").read).to include(<<~RUBY) + require "go_gem/rake_task" + + GoGem::RakeTask.new("#{gem_name}") + RUBY + end + + context "with --no-ci" do + let(:flags) { "--ext=go --no-ci" } + + it_behaves_like "CI config is absent" + end + + context "--ci set to github" do + let(:flags) { "--ext=go --ci=github" } + + it "generates .github/workflows/main.yml" do + expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist + expect(bundled_app("#{gem_name}/.github/workflows/main.yml").read).to include("go-version-file: ext/#{gem_name}/go.mod") + end + end + + context "--ci set to circle" do + let(:flags) { "--ext=go --ci=circle" } + + it "generates a .circleci/config.yml" do + expect(bundled_app("#{gem_name}/.circleci/config.yml")).to exist + + expect(bundled_app("#{gem_name}/.circleci/config.yml").read).to include(<<-YAML.strip) + environment: + GO_VERSION: + YAML + + expect(bundled_app("#{gem_name}/.circleci/config.yml").read).to include(<<-YAML) + - run: + name: Install Go + command: | + wget https://go.dev/dl/go$GO_VERSION.linux-amd64.tar.gz -O /tmp/go.tar.gz + tar -C /usr/local -xzf /tmp/go.tar.gz + echo 'export PATH=/usr/local/go/bin:"$PATH"' >> "$BASH_ENV" + YAML + end + end + + context "--ci set to gitlab" do + let(:flags) { "--ext=go --ci=gitlab" } + + it "generates a .gitlab-ci.yml" do + expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to exist + + expect(bundled_app("#{gem_name}/.gitlab-ci.yml").read).to include(<<-YAML) + - wget https://go.dev/dl/go$GO_VERSION.linux-amd64.tar.gz -O /tmp/go.tar.gz + - tar -C /usr/local -xzf /tmp/go.tar.gz + - export PATH=/usr/local/go/bin:$PATH + YAML + + expect(bundled_app("#{gem_name}/.gitlab-ci.yml").read).to include(<<-YAML.strip) + variables: + GO_VERSION: + YAML + end + end + + context "without github.user" do + before do + # FIXME: GitHub Actions Windows Runner hang up here for some reason... + skip "Workaround for hung up" if Gem.win_platform? + + git("config --global --unset github.user") + bundle ["gem", gem_name, flags].compact.join(" ") + end + + it "includes valid module name in go.mod" do + expect(bundled_app("#{gem_name}/ext/#{gem_name}/go.mod").read).to include("module github.com/username/#{gem_name}") + end + end end end @@ -1638,7 +1976,7 @@ RSpec.describe "bundle gem" do expect(bundled_app("#{gem_name}/lib/#{require_path}.rb").read).to match(/module Test\n module Gem/) end - include_examples "generating a gem" + include_examples "paths that depend on gem name" end describe "uncommon gem names" do @@ -1687,6 +2025,19 @@ Usage: "bundle gem NAME [OPTIONS]" it { expect(err).to include("Invalid gem name #{subject}") } end + context "starting with a number" do + subject { "1gem" } + it { expect(err).to include("Invalid gem name #{subject}") } + end + + context "including capital letter" do + subject { "CAPITAL" } + it "should warn but not error" do + expect(err).to include("Gem names with capital letters are not recommended") + expect(bundled_app("#{subject}/#{subject}.gemspec")).to exist + end + end + context "starting with an existing const name" do subject { "gem-somenewconstantname" } it { expect(err).not_to include("Invalid gem name #{subject}") } @@ -1700,7 +2051,7 @@ Usage: "bundle gem NAME [OPTIONS]" context "on first run", :readline do it "asks about test framework" do - global_config "BUNDLE_GEM__TEST" => nil + bundle_config_global "BUNDLE_GEM__TEST" => nil bundle "gem foobar" do |input, _, _| input.puts "rspec" @@ -1723,7 +2074,7 @@ Usage: "bundle gem NAME [OPTIONS]" end it "asks about CI service" do - global_config "BUNDLE_GEM__CI" => nil + bundle_config_global "BUNDLE_GEM__CI" => nil bundle "gem foobar" do |input, _, _| input.puts "github" @@ -1732,8 +2083,8 @@ Usage: "bundle gem NAME [OPTIONS]" expect(bundled_app("foobar/.github/workflows/main.yml")).to exist end - it "asks about MIT license" do - global_config "BUNDLE_GEM__MIT" => nil + it "asks about MIT license just once" do + bundle_config_global "BUNDLE_GEM__MIT" => nil bundle "config list" @@ -1742,26 +2093,29 @@ Usage: "bundle gem NAME [OPTIONS]" end expect(bundled_app("foobar/LICENSE.txt")).to exist + expect(out).to include("Using a MIT license means").once end - it "asks about CoC" do - global_config "BUNDLE_GEM__COC" => nil + it "asks about CoC just once" do + bundle_config_global "BUNDLE_GEM__COC" => nil bundle "gem foobar" do |input, _, _| input.puts "yes" end expect(bundled_app("foobar/CODE_OF_CONDUCT.md")).to exist + expect(out).to include("Codes of conduct can increase contributions to your project").once end - it "asks about CHANGELOG" do - global_config "BUNDLE_GEM__CHANGELOG" => nil + it "asks about CHANGELOG just once" do + bundle_config_global "BUNDLE_GEM__CHANGELOG" => nil bundle "gem foobar" do |input, _, _| input.puts "yes" end expect(bundled_app("foobar/CHANGELOG.md")).to exist + expect(out).to include("A changelog is a file which contains").once end end diff --git a/spec/bundler/commands/open_spec.rb b/spec/bundler/commands/open_spec.rb index e0c79aa407..664dc58919 100644 --- a/spec/bundler/commands/open_spec.rb +++ b/spec/bundler/commands/open_spec.rb @@ -49,7 +49,7 @@ RSpec.describe "bundle open" do it "suggests alternatives for similar-sounding gems" do bundle "open Rails", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, raise_on_error: false - expect(err).to match(/did you mean rails\?/i) + expect(err).to match(/did you mean 'rails'\?/i) end it "opens the gem with short words" do @@ -80,12 +80,12 @@ RSpec.describe "bundle open" do 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) + 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) + expect(err).to match(/did you mean 'rails'\?/i) end it "opens subpath of the short worded gem" do @@ -139,7 +139,7 @@ RSpec.describe "bundle open" do gem "foo" G - bundle "config set auto_install 1" + bundle_config "auto_install 1" bundle "open rails", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } expect(out).to include("Installing foo 1.0") end diff --git a/spec/bundler/commands/outdated_spec.rb b/spec/bundler/commands/outdated_spec.rb index 38121ce50e..28ed51d61e 100644 --- a/spec/bundler/commands/outdated_spec.rb +++ b/spec/bundler/commands/outdated_spec.rb @@ -30,7 +30,7 @@ RSpec.describe "bundle outdated" do bundle "outdated", raise_on_error: false expected_output = <<~TABLE.gsub("x", "\\\h").tr(".", "\.").strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date activesupport 2.3.5 3.0 = 2.3.5 default foo 1.0 xxxxxxx 1.0 xxxxxxx >= 0 default weakling 0.0.3 0.2 ~> 0.0.1 default @@ -53,7 +53,7 @@ RSpec.describe "bundle outdated" do bundle "outdated", raise_on_error: false expected_output = <<~TABLE - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date AAA 1.0.0 2.0.0 = 1.0.0 default TABLE @@ -92,7 +92,7 @@ RSpec.describe "bundle outdated" do bundle "outdated", raise_on_error: false expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date activesupport 2.3.5 3.0 = 2.3.5 development, test terranova 8 9 = 8 default TABLE @@ -120,7 +120,7 @@ RSpec.describe "bundle outdated" do end it "shows the location of the latest version's gemspec if installed" do - bundle "config set clean false" + bundle_config "clean false" update_repo2 { build_gem "activesupport", "3.0" } update_repo2 { build_gem "terranova", "9" } @@ -142,81 +142,15 @@ RSpec.describe "bundle outdated" do bundle "outdated --verbose", raise_on_error: false expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups Path + Gem Current Latest Requested Groups Release Date Path activesupport 2.3.5 3.0 = 2.3.5 default - terranova 8 9 = 8 default #{default_bundle_path("specifications/terranova-9.gemspec")} + terranova 8 9 = 8 default #{default_bundle_path("specifications/terranova-9.gemspec")} TABLE expect(out).to end_with(expected_output) end end - describe "with multiple, duplicated sources, with lockfile in old format", bundler: "< 3" do - before do - build_repo2 do - build_gem "dotenv", "2.7.6" - - build_gem "oj", "3.11.3" - build_gem "oj", "3.11.5" - - build_gem "vcr", "6.0.0" - end - - build_repo3 do - build_gem "pkg-gem-flowbyte-with-dep", "1.0.0" do |s| - s.add_dependency "oj" - end - end - - gemfile <<~G - source "https://gem.repo2" - - gem "dotenv" - - source "https://gem.repo3" do - gem 'pkg-gem-flowbyte-with-dep' - end - - gem "vcr",source: "https://gem.repo2" - G - - lockfile <<~L - GEM - remote: https://gem.repo2/ - remote: https://gem.repo3/ - specs: - dotenv (2.7.6) - oj (3.11.3) - pkg-gem-flowbyte-with-dep (1.0.0) - oj - vcr (6.0.0) - - PLATFORMS - #{local_platform} - - DEPENDENCIES - dotenv - pkg-gem-flowbyte-with-dep! - vcr! - - BUNDLED WITH - #{Bundler::VERSION} - L - end - - it "works" do - bundle :install, artifice: "compact_index" - bundle :outdated, artifice: "compact_index", raise_on_error: false - - expected_output = <<~TABLE - Gem Current Latest Requested Groups - oj 3.11.3 3.11.5 - TABLE - - expect(out).to include(expected_output.strip) - end - end - describe "with --group option" do before do build_repo2 do @@ -263,7 +197,7 @@ RSpec.describe "bundle outdated" do test_group_option("default") expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date terranova 8 9 = 8 default TABLE @@ -274,7 +208,7 @@ RSpec.describe "bundle outdated" do test_group_option("development") expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date activesupport 2.3.5 3.0 = 2.3.5 development, test duradura 7.0 8.0 = 7.0 development, test TABLE @@ -286,7 +220,7 @@ RSpec.describe "bundle outdated" do test_group_option("test") expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date activesupport 2.3.5 3.0 = 2.3.5 development, test duradura 7.0 8.0 = 7.0 development, test TABLE @@ -323,7 +257,7 @@ RSpec.describe "bundle outdated" do bundle "outdated --groups", raise_on_error: false expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date bar 2.0.0 3.0.0 TABLE @@ -365,7 +299,7 @@ RSpec.describe "bundle outdated" do bundle "outdated --groups", raise_on_error: false expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date activesupport 2.3.5 3.0 = 2.3.5 development, test duradura 7.0 8.0 = 7.0 development, test terranova 8 9 = 8 default @@ -399,7 +333,7 @@ RSpec.describe "bundle outdated" do build_gem "activesupport", "2.3.4" end - bundle "config set clean false" + bundle_config "clean false" install_gemfile <<-G source "https://gem.repo2" @@ -409,7 +343,7 @@ RSpec.describe "bundle outdated" do bundle "outdated --local", raise_on_error: false expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date activesupport 2.3.4 2.3.5 = 2.3.4 default TABLE @@ -417,7 +351,7 @@ RSpec.describe "bundle outdated" do end it "doesn't hit repo2" do - FileUtils.rm_rf(gem_repo2) + FileUtils.rm_r(gem_repo2) bundle "outdated --local" expect(out).not_to match(/Fetching (gem|version|dependency) metadata from/) @@ -520,7 +454,7 @@ RSpec.describe "bundle outdated" do bundle "outdated foo", raise_on_error: false expected_output = <<~TABLE.gsub("x", "\\\h").tr(".", "\.").strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date foo 1.0 xxxxxxx 1.0 xxxxxxx >= 0 default TABLE @@ -551,13 +485,13 @@ RSpec.describe "bundle outdated" do zeitwerk BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "outdated zeitwerk", raise_on_error: false expected_output = <<~TABLE.tr(".", "\.").strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date zeitwerk 1.0.0 2.0.0 >= 0 default TABLE @@ -605,7 +539,7 @@ RSpec.describe "bundle outdated" do bundle "outdated --pre", raise_on_error: false expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date activesupport 2.3.5 3.0.0.beta = 2.3.5 default TABLE @@ -628,7 +562,7 @@ RSpec.describe "bundle outdated" do bundle "outdated", raise_on_error: false expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date activesupport 3.0.0.beta.1 3.0.0.beta.2 = 3.0.0.beta.1 default TABLE @@ -664,7 +598,7 @@ RSpec.describe "bundle outdated" do bundle :outdated, "filter-strict": true, raise_on_error: false expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date weakling 0.0.3 0.0.5 ~> 0.0.1 default TABLE @@ -680,7 +614,7 @@ RSpec.describe "bundle outdated" do bundle :outdated, strict: true, raise_on_error: false expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date weakling 0.0.3 0.0.5 ~> 0.0.1 default TABLE @@ -725,7 +659,7 @@ RSpec.describe "bundle outdated" do bundle :outdated, :"filter-strict" => true, "filter-patch" => true, :raise_on_error => false expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date weakling 0.0.3 0.0.5 >= 0.0.1 default TABLE @@ -747,7 +681,7 @@ RSpec.describe "bundle outdated" do bundle :outdated, :"filter-strict" => true, "filter-minor" => true, :raise_on_error => false expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date weakling 0.0.3 0.1.5 >= 0.0.1 default TABLE @@ -769,7 +703,7 @@ RSpec.describe "bundle outdated" do bundle :outdated, :"filter-strict" => true, "filter-major" => true, :raise_on_error => false expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date weakling 0.0.3 1.1.5 >= 0.0.1 default TABLE @@ -814,12 +748,12 @@ RSpec.describe "bundle outdated" do gem "foo" G - bundle "config set auto_install 1" + bundle_config "auto_install 1" bundle :outdated, raise_on_error: false expect(out).to include("Installing foo 1.0") end - context "after bundle install --deployment", bundler: "< 3" do + context "in deployment mode" do before do build_repo2 @@ -830,7 +764,7 @@ RSpec.describe "bundle outdated" do gem "foo" G bundle :lock - bundle :install, deployment: true + bundle_config "deployment true" end it "outputs a helpful message about being in deployment mode" do @@ -858,7 +792,7 @@ RSpec.describe "bundle outdated" do gem "myrack" gem "foo" G - bundle "config set --local deployment true" + bundle_config "deployment true" end it "outputs a helpful message about being in deployment mode" do @@ -913,7 +847,7 @@ RSpec.describe "bundle outdated" do bundle "outdated", raise_on_error: false expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date laduradura 5.15.2 5.15.3 = 5.15.2 default TABLE @@ -974,7 +908,7 @@ RSpec.describe "bundle outdated" do gem "terranova", '8' G - simulate_new_machine + pristine_system_gems update_git "foo", path: lib_path("foo") update_repo2 do @@ -1215,7 +1149,7 @@ RSpec.describe "bundle outdated" do bundle "outdated --patch --filter-patch", raise_on_error: false expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date 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 @@ -1228,7 +1162,7 @@ RSpec.describe "bundle outdated" do bundle "outdated --minor --filter-minor", raise_on_error: false expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date major 1.0.0 1.1.0 >= 0 default minor 1.0.0 1.1.0 >= 0 default TABLE @@ -1282,7 +1216,7 @@ RSpec.describe "bundle outdated" do bundle "outdated --patch --filter-patch", raise_on_error: false, env: { "DEBUG_RESOLVER" => "1" } expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date bar 2.0.3 2.0.5 foo 1.4.3 1.4.4 >= 0 default TABLE @@ -1294,7 +1228,7 @@ RSpec.describe "bundle outdated" do bundle "outdated --patch --filter-patch", raise_on_error: false, env: { "DEBUG" => "1" } expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups Path + Gem Current Latest Requested Groups Release Date Path bar 2.0.3 2.0.5 foo 1.4.3 1.4.4 >= 0 default TABLE @@ -1326,7 +1260,7 @@ RSpec.describe "bundle outdated" do bundle "outdated --only-explicit", raise_on_error: false expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date weakling 0.2 0.3 >= 0 default TABLE @@ -1363,7 +1297,7 @@ RSpec.describe "bundle outdated" do nokogiri BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L gemfile <<-G @@ -1376,7 +1310,7 @@ RSpec.describe "bundle outdated" do bundle "outdated", raise_on_error: false expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date nokogiri 1.11.1 1.11.2 >= 0 default TABLE @@ -1417,7 +1351,7 @@ RSpec.describe "bundle outdated" do mini_portile2 BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -1425,7 +1359,7 @@ RSpec.describe "bundle outdated" do bundle "outdated", raise_on_error: false expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date mini_portile2 2.5.2 2.5.3 >= 0 default TABLE diff --git a/spec/bundler/commands/platform_spec.rb b/spec/bundler/commands/platform_spec.rb index 6e0a02bcf0..9d7354c54f 100644 --- a/spec/bundler/commands/platform_spec.rb +++ b/spec/bundler/commands/platform_spec.rb @@ -227,7 +227,7 @@ G ruby 1.0.0p127 BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "platform --ruby" @@ -250,7 +250,7 @@ G DEPENDENCIES BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "platform --ruby" @@ -302,9 +302,9 @@ G expect(err).to be_include("Your #{local_ruby_engine} version is #{local_engine_version}, but your Gemfile specified #{local_ruby_engine} #{not_local_engine_version}") end - def should_be_patchlevel_incorrect - expect(exitstatus).to eq(18) - expect(err).to be_include("Your Ruby patchlevel is #{RUBY_PATCHLEVEL}, but your Gemfile specified #{not_local_patchlevel}") + def should_ignore_patchlevel + expect(exitstatus).to eq(0) + expect(err).to eq("") end def should_be_patchlevel_fixnum @@ -382,7 +382,7 @@ G should_be_engine_version_incorrect end - it "doesn't install when patchlevel doesn't match" do + it "does install even when patchlevel doesn't match" do install_gemfile <<-G, raise_on_error: false source "https://gem.repo1" gem "myrack" @@ -390,8 +390,8 @@ G #{patchlevel_incorrect} G - expect(bundled_app_lock).not_to exist - should_be_patchlevel_incorrect + expect(bundled_app_lock).to exist + should_ignore_patchlevel end end @@ -481,7 +481,7 @@ G should_be_engine_version_incorrect end - it "fails when patchlevel doesn't match" do + it "checks fine even when patchlevel doesn't match" do install_gemfile <<-G source "https://gem.repo1" gem "myrack" @@ -494,8 +494,8 @@ G #{patchlevel_incorrect} G - bundle :check, raise_on_error: false - should_be_patchlevel_incorrect + bundle :check + should_ignore_patchlevel end end @@ -598,10 +598,10 @@ G should_be_engine_version_incorrect end - it "fails when patchlevel doesn't match" do + it "updates fine even when patchlevel doesn't match" do gemfile <<-G - source "https://gem.repo1" - gem "myrack" + source "https://gem.repo2" + gem "activesupport" #{patchlevel_incorrect} G @@ -609,8 +609,9 @@ G build_gem "activesupport", "3.0" end - bundle :update, all: true, raise_on_error: false - should_be_patchlevel_incorrect + bundle :update, all: true + should_ignore_patchlevel + expect(the_bundle).to include_gems "activesupport 3.0" end end @@ -646,7 +647,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" do gemfile <<-G source "https://gem.repo1" gem "rails" @@ -658,7 +659,7 @@ G should_be_ruby_version_incorrect end - it "fails if engine doesn't match", bundler: "< 3" do + it "fails if engine doesn't match" do gemfile <<-G source "https://gem.repo1" gem "rails" @@ -670,7 +671,7 @@ G should_be_engine_incorrect end - it "fails if engine version doesn't match", bundler: "< 3", jruby_only: true do + it "fails if engine version doesn't match", jruby_only: true do gemfile <<-G source "https://gem.repo1" gem "rails" @@ -682,19 +683,17 @@ G should_be_engine_version_incorrect end - it "fails when patchlevel doesn't match", bundler: "< 3" do + it "prints path even when patchlevel doesn't match" do gemfile <<-G source "https://gem.repo1" - gem "myrack" + gem "rails" #{patchlevel_incorrect} G - update_repo2 do - build_gem "activesupport", "3.0" - end - bundle "show rails", raise_on_error: false - should_be_patchlevel_incorrect + bundle "show rails" + should_ignore_patchlevel + expect(out).to eq(default_bundle_path("gems", "rails-2.3.2").to_s) end end @@ -766,7 +765,7 @@ G should_be_engine_version_incorrect end - it "fails when patchlevel doesn't match" do + it "copies the .gem file to vendor/cache even when patchlevel doesn't match" do gemfile <<-G source "https://gem.repo1" gem "myrack" @@ -774,8 +773,9 @@ G #{patchlevel_incorrect} G - bundle :cache, raise_on_error: false - should_be_patchlevel_incorrect + bundle :cache + should_ignore_patchlevel + expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist end end @@ -847,7 +847,7 @@ G should_be_engine_version_incorrect end - it "fails when patchlevel doesn't match" do + it "copies the .gem file to vendor/cache even when patchlevel doesn't match" do gemfile <<-G source "https://gem.repo1" gem "myrack" @@ -855,8 +855,9 @@ G #{patchlevel_incorrect} G - bundle :cache, raise_on_error: false - should_be_patchlevel_incorrect + bundle :cache + should_ignore_patchlevel + expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist end end @@ -926,7 +927,7 @@ G should_be_engine_version_incorrect end - it "fails when patchlevel doesn't match" do + it "activates the correct gem even when patchlevel doesn't match" do gemfile <<-G source "https://gem.repo1" gem "myrack" @@ -934,15 +935,21 @@ G #{patchlevel_incorrect} G - bundle "exec myrackup", raise_on_error: false - should_be_patchlevel_incorrect + bundle "exec myrackup" + should_ignore_patchlevel + expect(out).to include("1.0.0") end end - context "bundle console", bundler: "< 3" do + context "bundle console" do before do + build_repo2 do + build_dummy_irb + end + install_gemfile <<-G - source "https://gem.repo1" + source "https://gem.repo2" + gem "irb" gem "myrack" gem "activesupport", :group => :test gem "myrack_middleware", :group => :development @@ -950,14 +957,7 @@ G end it "starts IRB with the default group loaded when ruby version matches", :readline do - gemfile <<-G - source "https://gem.repo1" - gem "myrack" - gem "activesupport", :group => :test - gem "myrack_middleware", :group => :development - - #{ruby_version_correct} - G + gemfile gemfile + "\n\n#{ruby_version_correct}\n" bundle "console" do |input, _, _| input.puts("puts MYRACK") @@ -967,14 +967,7 @@ G end it "starts IRB with the default group loaded when ruby version matches", :readline, :jruby_only do - gemfile <<-G - source "https://gem.repo1" - gem "myrack" - gem "activesupport", :group => :test - gem "myrack_middleware", :group => :development - - #{ruby_version_correct_engineless} - G + gemfile gemfile + "\n\n#{ruby_version_correct_engineless}\n" bundle "console" do |input, _, _| input.puts("puts MYRACK") @@ -984,59 +977,35 @@ G end it "fails when ruby version doesn't match" do - gemfile <<-G - source "https://gem.repo1" - gem "myrack" - gem "activesupport", :group => :test - gem "myrack_middleware", :group => :development - - #{ruby_version_incorrect} - G + gemfile gemfile + "\n\n#{ruby_version_incorrect}\n" bundle "console", raise_on_error: false should_be_ruby_version_incorrect end it "fails when engine doesn't match" do - gemfile <<-G - source "https://gem.repo1" - gem "myrack" - gem "activesupport", :group => :test - gem "myrack_middleware", :group => :development - - #{engine_incorrect} - G + gemfile gemfile + "\n\n#{engine_incorrect}\n" bundle "console", raise_on_error: false should_be_engine_incorrect end it "fails when engine version doesn't match", :jruby_only do - gemfile <<-G - source "https://gem.repo1" - gem "myrack" - gem "activesupport", :group => :test - gem "myrack_middleware", :group => :development - - #{engine_version_incorrect} - G + gemfile gemfile + "\n\n#{engine_version_incorrect}\n" bundle "console", raise_on_error: false should_be_engine_version_incorrect end - it "fails when patchlevel doesn't match" do - gemfile <<-G - source "https://gem.repo1" - gem "myrack" - gem "activesupport", :group => :test - gem "myrack_middleware", :group => :development + it "starts IRB with the default group loaded even when patchlevel doesn't match", :readline do + gemfile gemfile + "\n\n#{patchlevel_incorrect}\n" - #{patchlevel_incorrect} - G - - bundle "console", raise_on_error: false - should_be_patchlevel_incorrect + bundle "console" do |input, _, _| + input.puts("puts MYRACK") + input.puts("exit") + end + should_ignore_patchlevel + expect(out).to include("0.9.1") end end @@ -1132,7 +1101,7 @@ G should_be_engine_version_incorrect end - it "fails when patchlevel doesn't match" do + it "makes a Gemfile.lock even when patchlevel doesn't match" do install_gemfile <<-G, raise_on_error: false source "https://gem.repo1" gem "yard" @@ -1143,10 +1112,10 @@ 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 } - expect(bundled_app_lock).not_to exist - should_be_patchlevel_incorrect + should_ignore_patchlevel + expect(bundled_app_lock).to exist end end @@ -1180,7 +1149,7 @@ G bundle "outdated", raise_on_error: false expected_output = <<~TABLE.gsub("x", "\\\h").tr(".", "\.").strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date activesupport 2.3.5 3.0 = 2.3.5 default foo 1.0 xxxxxxx 1.0 xxxxxxx >= 0 default TABLE @@ -1206,7 +1175,7 @@ G bundle "outdated", raise_on_error: false expected_output = <<~TABLE.gsub("x", "\\\h").tr(".", "\.").strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date activesupport 2.3.5 3.0 = 2.3.5 default foo 1.0 xxxxxxx 1.0 xxxxxxx >= 0 default TABLE @@ -1268,7 +1237,7 @@ G should_be_engine_version_incorrect end - it "fails when the patchlevel doesn't match", :jruby_only do + it "reports outdated gems even when patchlevel doesn't match" do update_repo2 do build_gem "activesupport", "3.0" update_git "foo", path: lib_path("foo") @@ -1283,25 +1252,9 @@ G G bundle "outdated", raise_on_error: false - should_be_patchlevel_incorrect - end - - 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") - end - - gemfile <<-G - source "https://gem.repo2" - gem "activesupport", "2.3.5" - gem "foo", :git => "#{lib_path("foo")}" - - #{patchlevel_fixnum} - G - - bundle "outdated", raise_on_error: false - should_be_patchlevel_fixnum + expect(err).not_to include("patchlevel") + expect(out).to include("activesupport") + expect(out).to include("foo") end end end diff --git a/spec/bundler/commands/post_bundle_message_spec.rb b/spec/bundler/commands/post_bundle_message_spec.rb index 8671504b25..088fc29fe1 100644 --- a/spec/bundler/commands/post_bundle_message_spec.rb +++ b/spec/bundler/commands/post_bundle_message_spec.rb @@ -18,162 +18,140 @@ RSpec.describe "post bundle message" do let(:bundle_show_path_message) { "Bundled gems are installed into `#{bundle_path}`" } let(:bundle_complete_message) { "Bundle complete!" } let(:bundle_updated_message) { "Bundle updated!" } - let(:installed_gems_stats) { "4 Gemfile dependencies, 5 gems now installed." } - let(:bundle_show_message) { Bundler::VERSION.split(".").first.to_i < 3 ? bundle_show_system_message : bundle_show_path_message } + let(:installed_gems_stats) { "4 Gemfile dependencies, 4 gems now installed." } + + describe "when installing to system gems" do + before do + bundle_config "path.system true" + end - describe "for fresh bundle install" do it "shows proper messages according to the configured groups" do bundle :install - expect(out).to include(bundle_show_message) + expect(out).to include(bundle_show_system_message) expect(out).not_to include("Gems in the group") expect(out).to include(bundle_complete_message) expect(out).to include(installed_gems_stats) - bundle "config set --local without emo" + bundle_config "without emo" bundle :install - expect(out).to include(bundle_show_message) + expect(out).to include(bundle_show_system_message) expect(out).to include("Gems in the group 'emo' were not installed") expect(out).to include(bundle_complete_message) expect(out).to include(installed_gems_stats) - bundle "config set --local without emo test" + bundle_config "without emo test" bundle :install - expect(out).to include(bundle_show_message) + expect(out).to include(bundle_show_system_message) expect(out).to include("Gems in the groups 'emo' and 'test' were not installed") expect(out).to include(bundle_complete_message) - expect(out).to include("4 Gemfile dependencies, 3 gems now installed.") + expect(out).to include("4 Gemfile dependencies, 2 gems now installed.") - bundle "config set --local without emo obama test" + bundle_config "without emo obama test" bundle :install - expect(out).to include(bundle_show_message) + expect(out).to include(bundle_show_system_message) expect(out).to include("Gems in the groups 'emo', 'obama' and 'test' were not installed") expect(out).to include(bundle_complete_message) - expect(out).to include("4 Gemfile dependencies, 2 gems now installed.") + expect(out).to include("4 Gemfile dependencies, 1 gem now installed.") end - describe "with `path` configured" do - let(:bundle_path) { "./vendor" } - - it "shows proper messages according to the configured groups" do - bundle "config set --local path vendor" - bundle :install - expect(out).to include(bundle_show_path_message) - expect(out).to_not include("Gems in the group") - expect(out).to include(bundle_complete_message) - - bundle "config set --local path vendor" - bundle "config set --local without emo" - bundle :install - expect(out).to include(bundle_show_path_message) - expect(out).to include("Gems in the group 'emo' were not installed") - expect(out).to include(bundle_complete_message) - - bundle "config set --local path vendor" - bundle "config set --local without emo test" - bundle :install - expect(out).to include(bundle_show_path_message) - expect(out).to include("Gems in the groups 'emo' and 'test' were not installed") - expect(out).to include(bundle_complete_message) - - bundle "config set --local path vendor" - bundle "config set --local without emo obama test" - bundle :install - expect(out).to include(bundle_show_path_message) - expect(out).to include("Gems in the groups 'emo', 'obama' and 'test' were not installed") - expect(out).to include(bundle_complete_message) - end - end - - describe "with an absolute `path` inside the cwd configured" do - let(:bundle_path) { bundled_app("cache") } - - it "shows proper messages according to the configured groups" do - bundle "config set --local path #{bundle_path}" - bundle :install - expect(out).to include("Bundled gems are installed into `./cache`") - expect(out).to_not include("Gems in the group") + describe "for second bundle install run" do + it "without any options" do + 2.times { bundle :install } + expect(out).to include(bundle_show_system_message) + expect(out).to_not include("Gems in the groups") expect(out).to include(bundle_complete_message) + expect(out).to include(installed_gems_stats) end end + end - describe "with `path` configured to an absolute path outside the cwd" do - let(:bundle_path) { tmp("not_bundled_app") } + describe "with `path` configured" do + let(:bundle_path) { "./vendor" } - it "shows proper messages according to the configured groups" do - bundle "config set --local path #{bundle_path}" - bundle :install - expect(out).to include("Bundled gems are installed into `#{tmp("not_bundled_app")}`") - expect(out).to_not include("Gems in the group") - expect(out).to include(bundle_complete_message) - end - end + it "shows proper messages according to the configured groups" do + bundle_config "path vendor" + bundle :install + expect(out).to include(bundle_show_path_message) + expect(out).to_not include("Gems in the group") + expect(out).to include(bundle_complete_message) - describe "with misspelled or non-existent gem name" do - before do - bundle "config set force_ruby_platform true" - end + bundle_config "path vendor" + bundle_config "without emo" + bundle :install + expect(out).to include(bundle_show_path_message) + expect(out).to include("Gems in the group 'emo' were not installed") + expect(out).to include(bundle_complete_message) - it "should report a helpful error message" do - install_gemfile <<-G, raise_on_error: false - source "https://gem.repo1" - gem "myrack" - gem "not-a-gem", :group => :development - G - expect(err).to include <<-EOS.strip -Could not find gem 'not-a-gem' in rubygems repository https://gem.repo1/ or installed locally. - EOS - end + bundle_config "path vendor" + bundle_config "without emo test" + bundle :install + expect(out).to include(bundle_show_path_message) + expect(out).to include("Gems in the groups 'emo' and 'test' were not installed") + expect(out).to include(bundle_complete_message) - it "should report a helpful error message with reference to cache if available" do - install_gemfile <<-G - source "https://gem.repo1" - gem "myrack" - G - bundle :cache - expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist - install_gemfile <<-G, raise_on_error: false - source "https://gem.repo1" - gem "myrack" - gem "not-a-gem", :group => :development - G - expect(err).to include("Could not find gem 'not-a-gem' in"). - and include("or in gems cached in vendor/cache.") - end + bundle_config "path vendor" + bundle_config "without emo obama test" + bundle :install + expect(out).to include(bundle_show_path_message) + expect(out).to include("Gems in the groups 'emo', 'obama' and 'test' were not installed") + expect(out).to include(bundle_complete_message) end end - 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) - expect(out).to_not include("Gems in the groups") - expect(out).to include(bundle_complete_message) - expect(out).to include(installed_gems_stats) - end + describe "with an absolute `path` inside the cwd configured" do + let(:bundle_path) { bundled_app("cache") } - it "with --without one group" do - bundle "install --without emo" + it "shows proper messages according to the configured groups" do + bundle_config "path #{bundle_path}" bundle :install - expect(out).to include(bundle_show_message) - expect(out).to include("Gems in the group 'emo' were not installed") + expect(out).to include("Bundled gems are installed into `./cache`") + expect(out).to_not include("Gems in the group") expect(out).to include(bundle_complete_message) - expect(out).to include(installed_gems_stats) end + end - it "with --without two groups" do - bundle "install --without emo test" + describe "with `path` configured to an absolute path outside the cwd" do + let(:bundle_path) { tmp("not_bundled_app") } + + it "shows proper messages according to the configured groups" do + bundle_config "path #{bundle_path}" bundle :install - expect(out).to include(bundle_show_message) - expect(out).to include("Gems in the groups 'emo' and 'test' were not installed") + expect(out).to include("Bundled gems are installed into `#{tmp("not_bundled_app")}`") + expect(out).to_not include("Gems in the group") expect(out).to include(bundle_complete_message) end + end - it "with --without more groups" do - bundle "install --without emo obama test" - bundle :install - expect(out).to include(bundle_show_message) - expect(out).to include("Gems in the groups 'emo', 'obama' and 'test' were not installed") - expect(out).to include(bundle_complete_message) + describe "with misspelled or non-existent gem name" do + before do + bundle_config "force_ruby_platform true" + end + + it "should report a helpful error message" do + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo1" + gem "myrack" + gem "not-a-gem", :group => :development + G + expect(err).to include <<~EOS.strip + Could not find gem 'not-a-gem' in rubygems repository https://gem.repo1/ or installed locally. + EOS + end + + it "should report a helpful error message with reference to cache if available" do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + bundle :cache + expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo1" + gem "myrack" + gem "not-a-gem", :group => :development + G + expect(err).to include("Could not find gem 'not-a-gem' in"). + and include("or in gems cached in vendor/cache.") end end @@ -183,19 +161,19 @@ Could not find gem 'not-a-gem' in rubygems repository https://gem.repo1/ or inst expect(out).not_to include("Gems in the groups") expect(out).to include(bundle_updated_message) - bundle "config set --local without emo" + bundle_config "without emo" bundle :install 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_config "without emo test" bundle :install 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_config "without emo obama test" bundle :install bundle :update, all: true expect(out).to include("Gems in the groups 'emo', 'obama' and 'test' were not updated") diff --git a/spec/bundler/commands/pristine_spec.rb b/spec/bundler/commands/pristine_spec.rb index 547aa12b6c..5f80b9e534 100644 --- a/spec/bundler/commands/pristine_spec.rb +++ b/spec/bundler/commands/pristine_spec.rb @@ -49,13 +49,7 @@ RSpec.describe "bundle pristine" do bundle "pristine" bundle "-v" - expected = if Bundler::VERSION < "3.0" - "Bundler version" - else - Bundler::VERSION - end - - expect(out).to start_with(expected) + expect(out).to end_with(Bundler::VERSION) end end @@ -85,7 +79,7 @@ RSpec.describe "bundle pristine" do it "displays warning and ignores changes when a local config exists" do spec = find_spec("foo") - bundle "config set local.#{spec.name} #{lib_path(spec.name)}" + bundle_config "local.#{spec.name} #{lib_path(spec.name)}" changes_txt = Pathname.new(spec.full_gem_path).join("lib/changes.txt") FileUtils.touch(changes_txt) @@ -95,6 +89,66 @@ RSpec.describe "bundle pristine" do expect(changes_txt).to be_file expect(err).to include("Cannot pristine #{spec.name} (#{spec.version}#{spec.git_version}). Gem is locally overridden.") end + + it "doesn't run multiple git processes for the same repository" do + nested_gems = [ + "actioncable", + "actionmailer", + "actionpack", + "actionview", + "activejob", + "activemodel", + "activerecord", + "activestorage", + "activesupport", + "railties", + ] + + build_repo2 do + nested_gems.each do |gem| + build_lib gem, path: lib_path("rails/#{gem}") + end + + build_git "rails", path: lib_path("rails") do |s| + nested_gems.each do |gem| + s.add_dependency gem + end + end + end + + install_gemfile <<-G + source 'https://rubygems.org' + + git "#{lib_path("rails")}" do + gem "rails" + gem "actioncable" + gem "actionmailer" + gem "actionpack" + gem "actionview" + gem "activejob" + gem "activemodel" + gem "activerecord" + gem "activestorage" + gem "activesupport" + gem "railties" + end + G + + changed_files = [] + diff = "#Pristine spec changes" + + nested_gems.each do |gem| + spec = find_spec(gem) + changed_files << Pathname.new(spec.full_gem_path).join("lib/#{gem}.rb") + File.open(changed_files.last, "a") {|f| f.puts diff } + end + + bundle "pristine" + + changed_files.each do |changed_file| + expect(File.read(changed_file)).to_not include(diff) + end + end end context "when sourced from gemspec" do @@ -173,7 +227,7 @@ RSpec.describe "bundle pristine" do let(:very_simple_binary) { find_spec("very_simple_binary") } let(:c_ext_dir) { Pathname.new(very_simple_binary.full_gem_path).join("ext") } let(:build_opt) { "--with-ext-lib=#{c_ext_dir}" } - before { bundle "config set build.very_simple_binary -- #{build_opt}" } + before { bundle_config "build.very_simple_binary -- #{build_opt}" } # This just verifies that the generated Makefile from the c_ext gem makes # use of the build_args from the bundle config @@ -190,7 +244,7 @@ RSpec.describe "bundle pristine" do let(:git_with_ext) { find_spec("git_with_ext") } let(:c_ext_dir) { Pathname.new(git_with_ext.full_gem_path).join("ext") } let(:build_opt) { "--with-ext-lib=#{c_ext_dir}" } - before { bundle "config set build.git_with_ext -- #{build_opt}" } + before { bundle_config "build.git_with_ext -- #{build_opt}" } # This just verifies that the generated Makefile from the c_ext gem makes # use of the build_args from the bundle config diff --git a/spec/bundler/commands/remove_spec.rb b/spec/bundler/commands/remove_spec.rb index 84505169ca..8a2e6778ea 100644 --- a/spec/bundler/commands/remove_spec.rb +++ b/spec/bundler/commands/remove_spec.rb @@ -43,21 +43,6 @@ RSpec.describe "bundle remove" do end end - context "when --install flag is specified", bundler: "< 3" do - it "removes gems from .bundle" do - gemfile <<-G - source "https://gem.repo1" - - gem "myrack" - G - - bundle "remove myrack --install" - - expect(out).to include("myrack was removed.") - expect(the_bundle).to_not include_gems "myrack" - end - end - describe "remove single gem from gemfile" do context "when gem is present in gemfile" do it "shows success for removed gem" do diff --git a/spec/bundler/commands/show_spec.rb b/spec/bundler/commands/show_spec.rb index 7f32d0563b..d0d55ffbb9 100644 --- a/spec/bundler/commands/show_spec.rb +++ b/spec/bundler/commands/show_spec.rb @@ -1,10 +1,12 @@ # frozen_string_literal: true -RSpec.describe "bundle show", bundler: "< 3" do +RSpec.describe "bundle show" do context "with a standard Gemfile" do before :each do + build_repo2 + install_gemfile <<-G - source "https://gem.repo1" + source "https://gem.repo2" gem "rails" G end @@ -35,12 +37,12 @@ RSpec.describe "bundle show", bundler: "< 3" do expect(out).to eq(default_bundle_path("gems", "rails-2.3.2").to_s) end - it "warns if path no longer exists on disk" do - FileUtils.rm_rf(default_bundle_path("gems", "rails-2.3.2")) + it "warns if specification is installed, but path does not exist on disk" do + FileUtils.rm_r(default_bundle_path("gems", "rails-2.3.2")) bundle "show rails" - expect(err).to match(/has been deleted/i) + expect(err).to match(/is missing/i) expect(err).to match(default_bundle_path("gems", "rails-2.3.2").to_s) end @@ -86,6 +88,24 @@ RSpec.describe "bundle show", bundler: "< 3" do \tStatus: Up to date MSG end + + it "includes up to date status in summary of gems" do + update_repo2 do + build_gem "rails", "3.0.0" + end + + bundle "show --verbose" + + expect(out).to include <<~MSG + * rails (2.3.2) + \tSummary: This is just a fake gem for testing + \tHomepage: http://example.com + \tStatus: Outdated - 2.3.2 < 3.0.0 + MSG + + # check lockfile is not accidentally updated + expect(lockfile).to include("actionmailer (2.3.2)") + end end context "with a git repo in the Gemfile" do @@ -159,7 +179,7 @@ RSpec.describe "bundle show", bundler: "< 3" do gem "foo" G - bundle "config set auto_install 1" + bundle_config "auto_install 1" bundle :show expect(out).to include("Installing foo 1.0") end @@ -190,35 +210,8 @@ RSpec.describe "bundle show", bundler: "< 3" do expect(err).to include("Could not find gem '#{invalid_regexp}'.") end end - - context "--outdated option" do - # Regression test for https://github.com/rubygems/bundler/issues/5375 - before do - build_repo2 - end - - it "doesn't update gems to newer versions" do - install_gemfile <<-G - source "https://gem.repo2" - gem "rails" - G - - expect(the_bundle).to include_gem("rails 2.3.2") - - update_repo2 do - build_gem "rails", "3.0.0" do |s| - s.executables = "rails" - end - end - - bundle "show --outdated" - - bundle "install" - expect(the_bundle).to include_gem("rails 2.3.2") - end - end end -RSpec.describe "bundle show", bundler: "3" do +RSpec.describe "bundle show", bundler: "5" do pending "shows a friendly error about the command removal" end diff --git a/spec/bundler/commands/ssl_spec.rb b/spec/bundler/commands/ssl_spec.rb new file mode 100644 index 0000000000..4220731b69 --- /dev/null +++ b/spec/bundler/commands/ssl_spec.rb @@ -0,0 +1,373 @@ +# frozen_string_literal: true + +require "bundler/cli" +require "bundler/cli/doctor" +require "bundler/cli/doctor/ssl" +require_relative "../support/artifice/helpers/artifice" +require "bundler/vendored_persistent.rb" + +RSpec.describe "bundle doctor ssl" do + before(:each) do + require_rack_test + require_relative "../support/artifice/helpers/endpoint" + + @dummy_endpoint = Class.new(Endpoint) do + get "/" do + end + end + + @previous_ui = Bundler.ui + Bundler.ui = Bundler::UI::Shell.new + Bundler.ui.level = "info" + + @previous_client = Gem::Request::ConnectionPools.client + Artifice.activate_with(@dummy_endpoint) + Gem::Request::ConnectionPools.client = Gem::Net::HTTP + end + + after(:each) do + Bundler.ui = @previous_ui + Artifice.deactivate + Gem::Request::ConnectionPools.client = @previous_client + end + + context "when a diagnostic fails" do + it "prints the diagnostic when openssl can't be loaded" do + subject = Bundler::CLI::Doctor::SSL.new({}) + allow(subject).to receive(:require).with("openssl").and_raise(LoadError) + + expected_err = <<~MSG + Oh no! Your Ruby doesn't have OpenSSL, so it can't connect to rubygems.org. + You'll need to recompile or reinstall Ruby with OpenSSL support and try again. + MSG + + expect { subject.run }.to output("").to_stdout.and output(expected_err).to_stderr + end + + it "fails due to certificate verification", :ruby_repo do + net_http = Class.new(Artifice::Net::HTTP) do + def connect + raise OpenSSL::SSL::SSLError, "certificate verify failed" + end + end + + Artifice.replace_net_http(net_http) + Gem::Request::ConnectionPools.client = net_http + Gem::RemoteFetcher.fetcher.close_all + + expected_out = <<~MSG + Here's your OpenSSL environment: + + OpenSSL: #{OpenSSL::VERSION} + Compiled with: #{OpenSSL::OPENSSL_VERSION} + Loaded with: #{OpenSSL::OPENSSL_LIBRARY_VERSION} + + Trying connections to https://rubygems.org: + MSG + + expected_err = <<~MSG + Bundler: failed (certificate verification) + RubyGems: failed (certificate verification) + Ruby net/http: failed + + Unfortunately, this Ruby can't connect to rubygems.org. + + Below affect only Ruby net/http connections: + SSL_CERT_FILE: exists #{OpenSSL::X509::DEFAULT_CERT_FILE} + SSL_CERT_DIR: exists #{OpenSSL::X509::DEFAULT_CERT_DIR} + + Your Ruby can't connect to rubygems.org because you are missing the certificate files OpenSSL needs to verify you are connecting to the genuine rubygems.org servers. + + MSG + + subject = Bundler::CLI::Doctor::SSL.new({}) + expect { subject.run }.to output(expected_out).to_stdout.and output(expected_err).to_stderr + end + + it "fails due to a too old tls version" do + subject = Bundler::CLI::Doctor::SSL.new({}) + + net_http = Class.new(Artifice::Net::HTTP) do + def connect + raise OpenSSL::SSL::SSLError, "read server hello A" + end + end + + Artifice.replace_net_http(net_http) + Gem::Request::ConnectionPools.client = Gem::Net::HTTP + Gem::RemoteFetcher.fetcher.close_all + + expected_out = <<~MSG + Here's your OpenSSL environment: + + OpenSSL: #{OpenSSL::VERSION} + Compiled with: #{OpenSSL::OPENSSL_VERSION} + Loaded with: #{OpenSSL::OPENSSL_LIBRARY_VERSION} + + Trying connections to https://rubygems.org: + MSG + + expected_err = <<~MSG + Bundler: failed (SSL/TLS protocol version mismatch) + RubyGems: failed (SSL/TLS protocol version mismatch) + Ruby net/http: failed + + Unfortunately, this Ruby can't connect to rubygems.org. + + Your Ruby can't connect to rubygems.org because your version of OpenSSL is too old. + You'll need to upgrade your OpenSSL install and/or recompile Ruby to use a newer OpenSSL. + + MSG + + expect { subject.run }.to output(expected_out).to_stdout.and output(expected_err).to_stderr + end + + it "fails due to unsupported tls 1.3 version" do + net_http = Class.new(Artifice::Net::HTTP) do + def connect + raise OpenSSL::SSL::SSLError, "read server hello A" + end + end + + Artifice.replace_net_http(net_http) + Gem::Request::ConnectionPools.client = net_http + Gem::RemoteFetcher.fetcher.close_all + + expected_out = <<~MSG + Here's your OpenSSL environment: + + OpenSSL: #{OpenSSL::VERSION} + Compiled with: #{OpenSSL::OPENSSL_VERSION} + Loaded with: #{OpenSSL::OPENSSL_LIBRARY_VERSION} + + Trying connections to https://rubygems.org: + MSG + + expected_err = <<~MSG + Bundler: failed (SSL/TLS protocol version mismatch) + RubyGems: failed (SSL/TLS protocol version mismatch) + Ruby net/http: failed + + Unfortunately, this Ruby can't connect to rubygems.org. + + Your Ruby can't connect to rubygems.org because TLS1_3 isn't supported yet. + + MSG + + subject = Bundler::CLI::Doctor::SSL.new("tls-version": "1.3") + expect { subject.run }.to output(expected_out).to_stdout.and output(expected_err).to_stderr + end + + it "fails due to a bundler and rubygems connection error" do + endpoint = Class.new(Endpoint) do + get "/" do + raise OpenSSL::SSL::SSLError, "read server hello A" + end + end + + Artifice.activate_with(endpoint) + Gem::Request::ConnectionPools.client = Gem::Net::HTTP + + expected_out = <<~MSG + Here's your OpenSSL environment: + + OpenSSL: #{OpenSSL::VERSION} + Compiled with: #{OpenSSL::OPENSSL_VERSION} + Loaded with: #{OpenSSL::OPENSSL_LIBRARY_VERSION} + + Trying connections to https://rubygems.org: + Ruby net/http: success + + For some reason, your Ruby installation can connect to rubygems.org, but neither RubyGems nor Bundler can. + The most likely fix is to manually upgrade RubyGems by following the instructions at http://ruby.to/ssl-check-failed. + After you've done that, run `gem install bundler` to upgrade Bundler, and then run this script again to make sure everything worked. ❣ + + MSG + + expected_err = <<~MSG + Bundler: failed (SSL/TLS protocol version mismatch) + RubyGems: failed (SSL/TLS protocol version mismatch) + MSG + + subject = Bundler::CLI::Doctor::SSL.new({}) + expect { subject.run }.to output(expected_out).to_stdout.and output(expected_err).to_stderr + end + + it "fails due to a bundler connection error" do + endpoint = Class.new(Endpoint) do + get "/" do + if request.user_agent.include?("bundler") + raise OpenSSL::SSL::SSLError, "read server hello A" + end + end + end + + Artifice.activate_with(endpoint) + Gem::Request::ConnectionPools.client = Gem::Net::HTTP + + expected_out = <<~MSG + Here's your OpenSSL environment: + + OpenSSL: #{OpenSSL::VERSION} + Compiled with: #{OpenSSL::OPENSSL_VERSION} + Loaded with: #{OpenSSL::OPENSSL_LIBRARY_VERSION} + + Trying connections to https://rubygems.org: + RubyGems: success + Ruby net/http: success + + Although your Ruby installation and RubyGems can both connect to rubygems.org, Bundler is having trouble. + The most likely way to fix this is to upgrade Bundler by running `gem install bundler`. + Run this script again after doing that to make sure everything is all set. + If you're still having trouble, check out the troubleshooting guide at http://ruby.to/ssl-check-failed. + + MSG + + expected_err = <<~MSG + Bundler: failed (SSL/TLS protocol version mismatch) + MSG + + subject = Bundler::CLI::Doctor::SSL.new({}) + expect { subject.run }.to output(expected_out).to_stdout.and output(expected_err).to_stderr + end + + it "fails due to a RubyGems connection error" do + endpoint = Class.new(Endpoint) do + get "/" do + if request.user_agent.include?("Ruby, RubyGems") + raise OpenSSL::SSL::SSLError, "read server hello A" + end + end + end + + Artifice.activate_with(endpoint) + Gem::Request::ConnectionPools.client = Gem::Net::HTTP + + expected_out = <<~MSG + Here's your OpenSSL environment: + + OpenSSL: #{OpenSSL::VERSION} + Compiled with: #{OpenSSL::OPENSSL_VERSION} + Loaded with: #{OpenSSL::OPENSSL_LIBRARY_VERSION} + + Trying connections to https://rubygems.org: + Bundler: success + Ruby net/http: success + + It looks like Ruby and Bundler can connect to rubygems.org, but RubyGems itself cannot. + You can likely solve this by manually downloading and installing a RubyGems update. + Visit http://ruby.to/ssl-check-failed for instructions on how to manually upgrade RubyGems. + + MSG + + expected_err = <<~MSG + RubyGems: failed (SSL/TLS protocol version mismatch) + MSG + + subject = Bundler::CLI::Doctor::SSL.new({}) + expect { subject.run }.to output(expected_out).to_stdout.and output(expected_err).to_stderr + end + end + + context "when no diagnostic fails" do + it "prints the SSL environment" do + expected_out = <<~MSG + Here's your OpenSSL environment: + + OpenSSL: #{OpenSSL::VERSION} + Compiled with: #{OpenSSL::OPENSSL_VERSION} + Loaded with: #{OpenSSL::OPENSSL_LIBRARY_VERSION} + + Trying connections to https://rubygems.org: + Bundler: success + RubyGems: success + Ruby net/http: success + + Hooray! This Ruby can connect to rubygems.org. + You are all set to use Bundler and RubyGems. + + MSG + + subject = Bundler::CLI::Doctor::SSL.new({}) + expect { subject.run }.to output(expected_out).to_stdout.and output("").to_stderr + end + + it "uses the tls_version verify mode and host when given as option" do + net_http = Class.new(Artifice::Net::HTTP) do + class << self + attr_accessor :verify_mode, :min_version, :max_version + end + + def connect + self.class.verify_mode = verify_mode + self.class.min_version = min_version + self.class.max_version = max_version + + super + end + end + + net_http.endpoint = @dummy_endpoint + Artifice.replace_net_http(net_http) + Gem::Request::ConnectionPools.client = net_http + Gem::RemoteFetcher.fetcher.close_all + + expected_out = <<~MSG + Here's your OpenSSL environment: + + OpenSSL: #{OpenSSL::VERSION} + Compiled with: #{OpenSSL::OPENSSL_VERSION} + Loaded with: #{OpenSSL::OPENSSL_LIBRARY_VERSION} + + Trying connections to https://example.org: + Bundler: success + RubyGems: success + Ruby net/http: success + + Hooray! This Ruby can connect to example.org. + You are all set to use Bundler and RubyGems. + + MSG + + subject = Bundler::CLI::Doctor::SSL.new("tls-version": "1.3", "verify-mode": :none, host: "example.org") + expect { subject.run }.to output(expected_out).to_stdout.and output("").to_stderr + expect(net_http.verify_mode).to eq(0) + expect(net_http.min_version.to_s).to eq("TLS1_3") + expect(net_http.max_version.to_s).to eq("TLS1_3") + end + + it "warns when TLS1.2 is not supported" do + expected_out = <<~MSG + Here's your OpenSSL environment: + + OpenSSL: #{OpenSSL::VERSION} + Compiled with: #{OpenSSL::OPENSSL_VERSION} + Loaded with: #{OpenSSL::OPENSSL_LIBRARY_VERSION} + + Trying connections to https://rubygems.org: + Bundler: success + RubyGems: success + Ruby net/http: success + + Hooray! This Ruby can connect to rubygems.org. + You are all set to use Bundler and RubyGems. + + MSG + + expected_err = <<~MSG + + WARNING: Although your Ruby can connect to rubygems.org today, your OpenSSL is very old! + WARNING: You will need to upgrade OpenSSL to use rubygems.org. + + MSG + + previous_version = OpenSSL::SSL::TLS1_2_VERSION + OpenSSL::SSL.send(:remove_const, :TLS1_2_VERSION) + + subject = Bundler::CLI::Doctor::SSL.new({}) + expect { subject.run }.to output(expected_out).to_stdout.and output(expected_err).to_stderr + ensure + OpenSSL::SSL.const_set(:TLS1_2_VERSION, previous_version) + end + end +end diff --git a/spec/bundler/commands/update_spec.rb b/spec/bundler/commands/update_spec.rb index d28c74ed62..03a3786d80 100644 --- a/spec/bundler/commands/update_spec.rb +++ b/spec/bundler/commands/update_spec.rb @@ -39,6 +39,23 @@ RSpec.describe "bundle update" do end end + describe "with --verbose" do + before do + build_repo2 + + install_gemfile <<~G + source "https://gem.repo2" + gem "myrack" + G + end + + it "logs the reason for re-resolving" do + bundle "update --verbose" + expect(out).not_to include("Found changes from the lockfile") + expect(out).to include("Re-resolving dependencies because bundler is unlocking") + end + end + describe "with --all" do before do build_repo2 @@ -80,7 +97,7 @@ RSpec.describe "bundle update" do end describe "with --gemfile" do - it "creates lock files based on the Gemfile name" do + it "creates lockfiles based on the Gemfile name" do gemfile bundled_app("OmgFile"), <<-G source "https://gem.repo1" gem "myrack", "1.0" @@ -93,7 +110,7 @@ RSpec.describe "bundle update" do end context "when update_requires_all_flag is set" do - before { bundle "config set update_requires_all_flag true" } + before { bundle_config "update_requires_all_flag true" } it "errors when passed nothing" do install_gemfile "source 'https://gem.repo1'" @@ -176,7 +193,7 @@ RSpec.describe "bundle update" do end it "should suggest alternatives" do bundle "update platformspecific", raise_on_error: false - expect(err).to include "Did you mean platform_specific?" + expect(err).to include "Did you mean 'platform_specific'?" end end @@ -230,7 +247,7 @@ RSpec.describe "bundle update" do expect(the_bundle).to include_gems("slim 3.0.9", "slim-rails 3.1.3", "slim_lint 0.16.1") - update_repo4 do + build_repo4 do build_gem "slim", "4.0.0" do |s| s.add_dependency "tilt", [">= 2.0.6", "< 2.1"] end @@ -296,7 +313,7 @@ RSpec.describe "bundle update" do country_select #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L previous_lockfile = lockfile @@ -357,7 +374,7 @@ RSpec.describe "bundle update" do quickbooks-ruby BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "update --conservative --verbose" @@ -418,7 +435,7 @@ RSpec.describe "bundle update" do sneakers BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "update --verbose" @@ -428,6 +445,115 @@ RSpec.describe "bundle update" do expect(out).to include("Installing sneakers 2.11.0").and include("Installing rake 13.0.6") end + it "downgrades indirect dependencies if required to fulfill an explicit upgrade request" do + build_repo4 do + build_gem "rbs", "3.6.1" + build_gem "rbs", "3.9.4" + + build_gem "solargraph", "0.56.0" do |s| + s.add_dependency "rbs", "~> 3.3" + end + + build_gem "solargraph", "0.56.2" do |s| + s.add_dependency "rbs", "~> 3.6.1" + end + end + + gemfile <<~G + source "https://gem.repo4" + + gem 'solargraph', '~> 0.56.0' + G + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + rbs (3.9.4) + solargraph (0.56.0) + rbs (~> 3.3) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + solargraph (~> 0.56.0) + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "lock --update solargraph" + + expect(lockfile).to include("solargraph (0.56.2)") + end + + it "does not downgrade direct dependencies unnecessarily" do + build_repo4 do + build_gem "redis", "4.8.1" + build_gem "redis", "5.3.0" + + build_gem "sidekiq", "6.5.5" do |s| + s.add_dependency "redis", ">= 4.5.0" + end + + build_gem "sidekiq", "6.5.12" do |s| + s.add_dependency "redis", ">= 4.5.0", "< 5" + end + + # one version of sidekiq above Gemfile's range is needed to make the + # resolver choose `redis` first and trying to upgrade it, reproducing + # the accidental sidekiq downgrade as a result + build_gem "sidekiq", "7.0.0 " do |s| + s.add_dependency "redis", ">= 4.2.0" + end + + build_gem "sentry-sidekiq", "5.22.0" do |s| + s.add_dependency "sidekiq", ">= 3.0" + end + + build_gem "sentry-sidekiq", "5.22.4" do |s| + s.add_dependency "sidekiq", ">= 3.0" + end + end + + gemfile <<~G + source "https://gem.repo4" + + gem "redis" + gem "sidekiq", "~> 6.5" + gem "sentry-sidekiq" + G + + original_lockfile = <<~L + GEM + remote: https://gem.repo4/ + specs: + redis (4.8.1) + sentry-sidekiq (5.22.0) + sidekiq (>= 3.0) + sidekiq (6.5.12) + redis (>= 4.5.0, < 5) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + redis + sentry-sidekiq + sidekiq (~> 6.5) + + BUNDLED WITH + #{Bundler::VERSION} + L + + lockfile original_lockfile + + bundle "lock --update sentry-sidekiq" + + expect(lockfile).to eq(original_lockfile.sub("sentry-sidekiq (5.22.0)", "sentry-sidekiq (5.22.4)")) + end + it "does not downgrade indirect dependencies unnecessarily" do build_repo4 do build_gem "a" do |s| @@ -446,7 +572,7 @@ RSpec.describe "bundle update" do expect(the_bundle).to include_gems("a 1.0", "b 1.0", "c 2.0") - update_repo4 do + build_repo4 do build_gem "b", "2.0" do |s| s.add_dependency "c", "< 2" end @@ -530,7 +656,7 @@ RSpec.describe "bundle update" do activesupport (~> 6.0.0) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "update activesupport" @@ -574,7 +700,7 @@ RSpec.describe "bundle update" do myrack PLATFORMS - #{local_platform} + x86-darwin-100 DEPENDENCIES activesupport @@ -582,12 +708,12 @@ RSpec.describe "bundle update" do myrack-obama BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install" - FileUtils.rm_rf(gem_repo2) + FileUtils.rm_r(gem_repo2) bundle "update --local --all" expect(out).not_to include("Fetching source index") @@ -689,33 +815,41 @@ RSpec.describe "bundle update" do G end - it "should fail loudly", bundler: "< 3" do - bundle "install --deployment" + it "should fail loudly" do + bundle_config "deployment true" bundle "update", all: true, raise_on_error: false expect(last_command).to be_failure - 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`./) + expect(err).to eq <<~ERROR.strip + Bundler is unlocking, but the lockfile can't be updated because frozen mode is set + + If this is a development machine, remove the Gemfile.lock freeze by running `bundle config set frozen false`. + ERROR end it "should fail loudly when frozen is set globally" do - bundle "config set --global frozen 1" + bundle_config_global "frozen 1" 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`./) + expect(err).to eq <<~ERROR.strip + Bundler is unlocking, but the lockfile can't be updated because frozen mode is set + + If this is a development machine, remove the Gemfile.lock freeze by running `bundle config set frozen false`. + ERROR end it "should fail loudly when deployment is set globally" do - bundle "config set --global deployment true" + bundle_config_global "deployment true" 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`./) + expect(err).to eq <<~ERROR.strip + Bundler is unlocking, but the lockfile can't be updated because frozen mode is set + + If this is a development machine, remove the Gemfile.lock freeze by running `bundle config set frozen false`. + ERROR 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/) + expect(err).to eq("Bundler is unlocking, but the lockfile can't be updated because frozen mode is set") end end @@ -842,7 +976,7 @@ RSpec.describe "bundle update" do bundle "update", all: true expect(out).to match(/Resolving dependencies\.\.\.\.*\nBundle updated!/) - update_repo4 do + build_repo4 do build_gem "foo", "2.0" end @@ -903,7 +1037,7 @@ RSpec.describe "bundle update" do request_store BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -940,71 +1074,9 @@ RSpec.describe "bundle update" do 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" - - build_gem "oj", "3.11.3" - build_gem "oj", "3.11.5" - - build_gem "vcr", "6.0.0" - end - - build_repo3 do - build_gem "pkg-gem-flowbyte-with-dep", "1.0.0" do |s| - s.add_dependency "oj" - end - end - - gemfile <<~G - source "https://gem.repo2" - - gem "dotenv" - - source "https://gem.repo3" do - gem 'pkg-gem-flowbyte-with-dep' - end - - gem "vcr",source: "https://gem.repo2" - G - - lockfile <<~L - GEM - remote: https://gem.repo2/ - remote: https://gem.repo3/ - specs: - dotenv (2.7.6) - oj (3.11.3) - pkg-gem-flowbyte-with-dep (1.0.0) - oj - vcr (6.0.0) - - PLATFORMS - #{local_platform} - - DEPENDENCIES - dotenv - pkg-gem-flowbyte-with-dep! - vcr! - - BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end - - it "works" do - 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" - end end end @@ -1049,7 +1121,7 @@ RSpec.describe "bundle update in more complicated situations" do end bundle "update thin myrack-obama" - expect(last_command.stdboth).to include "Bundler attempted to update myrack-obama but its version stayed the same" + expect(stdboth).to include "Bundler attempted to update myrack-obama but its version stayed the same" expect(the_bundle).to include_gems "thin 2.0", "myrack 10.0", "myrack-obama 1.0" end @@ -1067,7 +1139,7 @@ RSpec.describe "bundle update in more complicated situations" do bundle "update foo" - expect(last_command.stdboth).not_to include "attempted to update" + expect(stdboth).not_to include "attempted to update" end it "will not warn when changing gem sources but not versions" do @@ -1085,7 +1157,7 @@ RSpec.describe "bundle update in more complicated situations" do bundle "update myrack" - expect(last_command.stdboth).not_to include "attempted to update" + expect(stdboth).not_to include "attempted to update" end it "will update only from pinned source" do @@ -1132,7 +1204,7 @@ RSpec.describe "bundle update in more complicated situations" do a L - simulate_platform linux, &example + simulate_platform "x86_64-linux", &example end it "allows updating" do @@ -1173,9 +1245,9 @@ RSpec.describe "bundle update in more complicated situations" do end it "is not updated because it is not actually included in the bundle" do - simulate_platform linux do + simulate_platform "x86_64-linux" do bundle "update a" - expect(last_command.stdboth).to include "Bundler attempted to update a but it was not considered because it is for a different platform from the current one" + expect(stdboth).to include "Bundler attempted to update a but it was not considered because it is for a different platform from the current one" expect(the_bundle).to_not include_gem "a" end end @@ -1216,7 +1288,7 @@ RSpec.describe "bundle update when a gem depends on a newer version of bundler" it "should explain that bundler conflicted and how to resolve the conflict" do bundle "update", all: true, raise_on_error: false - expect(last_command.stdboth).not_to match(/in snapshot/i) + expect(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:9\.9\.9`/i) end @@ -1249,7 +1321,7 @@ RSpec.describe "bundle update --ruby" do DEPENDENCIES #{checksums_section_when_enabled} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -1281,10 +1353,10 @@ RSpec.describe "bundle update --ruby" do DEPENDENCIES #{checksums_section_when_enabled} RUBY VERSION - #{Bundler::RubyVersion.system} + #{Bundler::RubyVersion.system} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -1326,7 +1398,7 @@ RSpec.describe "bundle update --ruby" do ruby 2.1.4p222 BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L gemfile <<-G @@ -1347,14 +1419,12 @@ RSpec.describe "bundle update --ruby" do #{lockfile_platforms} DEPENDENCIES - - CHECKSUMS - + #{checksums_section_when_enabled} RUBY VERSION - #{Bundler::RubyVersion.system} + #{Bundler::RubyVersion.system} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -1388,7 +1458,7 @@ RSpec.describe "bundle update --bundler" do myrack #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, '\11.0.0\2') @@ -1408,7 +1478,7 @@ RSpec.describe "bundle update --bundler" do myrack #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L expect(the_bundle).to include_gem "myrack 1.0" @@ -1446,14 +1516,16 @@ RSpec.describe "bundle update --bundler" do myrack #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L expect(the_bundle).to include_gem "myrack 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.99.9" + bundle_config "path.system true" + + pristine_system_gems "bundler-9.0.0" build_repo4 do build_gem "myrack", "1.0" @@ -1461,13 +1533,17 @@ RSpec.describe "bundle update --bundler" do build_bundler "999.0.0" end + checksums = checksums_section do |c| + c.checksum(gem_repo4, "myrack", "1.0") + c.checksum(gem_repo4, "bundler", "999.0.0") + end + install_gemfile <<-G source "https://gem.repo4" gem "myrack" G - lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, "2.99.9") - bundle :update, bundler: true, verbose: true, preserve_ruby_flags: true + bundle :update, bundler: true, verbose: true expect(out).to include("Updating bundler to 999.0.0") expect(out).to include("Running `bundle update --bundler \"> 0.a\" --verbose` with bundler 999.0.0") @@ -1484,9 +1560,9 @@ RSpec.describe "bundle update --bundler" do DEPENDENCIES myrack - + #{checksums} BUNDLED WITH - 999.0.0 + 999.0.0 L expect(the_bundle).to include_gems "bundler 999.0.0" @@ -1494,12 +1570,12 @@ RSpec.describe "bundle update --bundler" do 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" + pristine_system_gems "bundler-4.99.0" build_repo4 do build_gem "myrack", "3.0.9.1" - build_bundler "2.99.0" + build_bundler "4.99.0" end gemfile <<~G @@ -1544,6 +1620,7 @@ RSpec.describe "bundle update --bundler" do checksums = checksums_section do |c| c.checksum(gem_repo4, "myrack", "1.0") + c.checksum(gem_repo4, "bundler", "9.9.9") end install_gemfile <<-G @@ -1568,7 +1645,7 @@ RSpec.describe "bundle update --bundler" do myrack #{checksums} BUNDLED WITH - 9.9.9 + 9.9.9 L expect(the_bundle).to include_gems "bundler 9.9.9" @@ -1593,8 +1670,26 @@ RSpec.describe "bundle update --bundler" do expect(err).to eq("The `bundle update --bundler` target version (999.999.999) does not exist") end + it "errors if the explicit target version does not exist, even if auto switching is disabled" do + pristine_system_gems "bundler-9.9.9" + + build_repo4 do + build_gem "myrack", "1.0" + end + + install_gemfile <<-G + source "https://gem.repo4" + gem "myrack" + G + + bundle :update, bundler: "999.999.999", raise_on_error: false, env: { "BUNDLER_VERSION" => "9.9.9" } + + expect(last_command).to be_failure + expect(err).to eq("The `bundle update --bundler` target version (999.999.999) does not exist") + end + it "allows updating to development versions if already installed locally" do - system_gems "bundler-2.3.0.dev" + system_gems "bundler-9.9.9" build_repo4 do build_gem "myrack", "1.0" @@ -1605,11 +1700,13 @@ RSpec.describe "bundle update --bundler" do gem "myrack" G - bundle :update, bundler: "2.3.0.dev", verbose: "true" + system_gems "bundler-9.0.0.dev", path: local_gem_path + bundle :update, bundler: "9.0.0.dev", verbose: "true" checksums = checksums_section_when_enabled do |c| c.checksum(gem_repo4, "myrack", "1.0") end + checksums.delete("bundler") expect(lockfile).to eq <<~L GEM @@ -1624,14 +1721,14 @@ RSpec.describe "bundle update --bundler" do myrack #{checksums} BUNDLED WITH - 2.3.0.dev + 9.0.0.dev L - expect(out).to include("Using bundler 2.3.0.dev") + expect(out).to include("Using bundler 9.0.0.dev") end it "does not touch the network if not necessary" do - system_gems "bundler-2.3.9" + system_gems "bundler-9.9.9" build_repo4 do build_gem "myrack", "1.0" @@ -1641,14 +1738,15 @@ RSpec.describe "bundle update --bundler" do source "https://gem.repo4" gem "myrack" G - - bundle :update, bundler: "2.3.9", verbose: true + system_gems "bundler-9.0.0", path: local_gem_path + bundle :update, bundler: "9.0.0", verbose: true expect(out).not_to include("Fetching gem metadata from https://rubygems.org/") # Only updates properly on modern RubyGems. checksums = checksums_section_when_enabled do |c| c.checksum(gem_repo4, "myrack", "1.0") + c.checksum(local_gem_path, "bundler", "9.0.0", Gem::Platform::RUBY, "cache") end expect(lockfile).to eq <<~L @@ -1664,14 +1762,14 @@ RSpec.describe "bundle update --bundler" do myrack #{checksums} BUNDLED WITH - 2.3.9 + 9.0.0 L - expect(out).to include("Using bundler 2.3.9") + expect(out).to include("Using bundler 9.0.0") end it "prints an error when trying to update bundler in frozen mode" do - system_gems "bundler-2.3.9" + system_gems "bundler-9.0.0" gemfile <<~G source "https://gem.repo2" @@ -1688,11 +1786,13 @@ RSpec.describe "bundle update --bundler" do DEPENDENCIES BUNDLED WITH - 2.1.4 + 9.0.0 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") + system_gems "bundler-9.9.9", path: local_gem_path + + bundle "update --bundler=9.9.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 @@ -1736,7 +1836,7 @@ RSpec.describe "bundle update conservative" do context "with patch set as default update level in config" do it "should do a patch level update" do - bundle "config set --local prefer_patch true" + bundle_config "prefer_patch true" bundle "update foo" expect(the_bundle).to include_gems "foo 1.4.5", "bar 2.1.1", "qux 1.0.0" @@ -1855,7 +1955,7 @@ RSpec.describe "bundle update conservative" do CHECKSUMS BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -1916,7 +2016,7 @@ RSpec.describe "bundle update conservative" do shared_owner_b #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -1970,7 +2070,7 @@ RSpec.describe "bundle update conservative" do nokogiri (>= 1.16.4) BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end diff --git a/spec/bundler/commands/version_spec.rb b/spec/bundler/commands/version_spec.rb index 307058a5dd..4320ad0611 100644 --- a/spec/bundler/commands/version_spec.rb +++ b/spec/bundler/commands/version_spec.rb @@ -10,38 +10,56 @@ RSpec.describe "bundle version" do end context "with -v" do - it "outputs the version", bundler: "< 3" do + it "outputs the version and virtual version if set" do bundle "-v" - expect(out).to eq("Bundler version #{Bundler::VERSION}") - end + expect(out).to eq(Bundler::VERSION.to_s) - it "outputs the version", bundler: "3" do + bundle_config "simulate_version 5" bundle "-v" - expect(out).to eq(Bundler::VERSION) + expect(out).to eq("#{Bundler::VERSION} (simulating Bundler 5)") end end context "with --version" do - it "outputs the version", bundler: "< 3" do + it "outputs the version and virtual version if set" do bundle "--version" - expect(out).to eq("Bundler version #{Bundler::VERSION}") - end + expect(out).to eq(Bundler::VERSION.to_s) - it "outputs the version", bundler: "3" do + bundle_config "simulate_version 5" bundle "--version" - expect(out).to eq(Bundler::VERSION) + expect(out).to eq("#{Bundler::VERSION} (simulating Bundler 5)") end end context "with version" 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/) + context "when released", :ruby_repo do + before do + system_gems "bundler-4.9.9", released: true + end + + it "outputs the version, virtual version if set, and build metadata" do + bundle "version" + expect(out).to match(/\A4\.9\.9 \(2100-01-01 commit #{COMMIT_HASH}\)\z/) + + bundle_config "simulate_version 5" + bundle "version" + expect(out).to match(/\A4\.9\.9 \(simulating Bundler 5\) \(2100-01-01 commit #{COMMIT_HASH}\)\z/) + end end - 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/) + context "when not released" do + before do + system_gems "bundler-4.9.9", released: false + end + + it "outputs the version, virtual version if set, and build metadata" do + bundle "version" + expect(out).to match(/\A4\.9\.9 \(20\d{2}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/) + + bundle_config "simulate_version 5" + bundle "version" + expect(out).to match(/\A4\.9\.9 \(simulating Bundler 5\) \(20\d{2}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/) + end end end end diff --git a/spec/bundler/commands/viz_spec.rb b/spec/bundler/commands/viz_spec.rb deleted file mode 100644 index 712ded4bc4..0000000000 --- a/spec/bundler/commands/viz_spec.rb +++ /dev/null @@ -1,144 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe "bundle viz", bundler: "< 3", if: Bundler.which("dot") do - before do - realworld_system_gems "ruby-graphviz --version 1.2.5" - end - - it "graphs gems from the Gemfile" do - install_gemfile <<-G - source "https://gem.repo1" - gem "myrack" - gem "myrack-obama" - G - - bundle "viz" - expect(out).to include("gem_graph.png") - - bundle "viz", format: "debug" - expect(out).to eq(<<~DOT.strip) - digraph Gemfile { - concentrate = "true"; - normalize = "true"; - nodesep = "0.55"; - edge[ weight = "2"]; - node[ fontname = "Arial, Helvetica, SansSerif"]; - edge[ fontname = "Arial, Helvetica, SansSerif" , fontsize = "12"]; - default [style = "filled", fillcolor = "#B9B9D5", shape = "box3d", fontsize = "16", label = "default"]; - myrack [style = "filled", fillcolor = "#B9B9D5", label = "myrack"]; - default -> myrack [constraint = "false"]; - "myrack-obama" [style = "filled", fillcolor = "#B9B9D5", label = "myrack-obama"]; - default -> "myrack-obama" [constraint = "false"]; - "myrack-obama" -> myrack; - } - debugging bundle viz... - DOT - end - - it "graphs gems that are prereleases" do - build_repo2 do - build_gem "myrack", "1.3.pre" - end - - install_gemfile <<-G - source "https://gem.repo2" - gem "myrack", "= 1.3.pre" - gem "myrack-obama" - G - - bundle "viz" - expect(out).to include("gem_graph.png") - - bundle "viz", format: :debug, version: true - expect(out).to eq(<<~EOS.strip) - digraph Gemfile { - concentrate = "true"; - normalize = "true"; - nodesep = "0.55"; - edge[ weight = "2"]; - node[ fontname = "Arial, Helvetica, SansSerif"]; - edge[ fontname = "Arial, Helvetica, SansSerif" , fontsize = "12"]; - default [style = "filled", fillcolor = "#B9B9D5", shape = "box3d", fontsize = "16", label = "default"]; - myrack [style = "filled", fillcolor = "#B9B9D5", label = "myrack\\n1.3.pre"]; - default -> myrack [constraint = "false"]; - "myrack-obama" [style = "filled", fillcolor = "#B9B9D5", label = "myrack-obama\\n1.0"]; - default -> "myrack-obama" [constraint = "false"]; - "myrack-obama" -> myrack; - } - debugging bundle viz... - EOS - end - - context "with another gem that has a graphviz file" do - before do - build_repo4 do - build_gem "graphviz", "999" do |s| - s.write("lib/graphviz.rb", "abort 'wrong graphviz gem loaded'") - end - end - - system_gems "graphviz-999", gem_repo: gem_repo4 - end - - it "loads the correct ruby-graphviz gem" do - install_gemfile <<-G - source "https://gem.repo1" - gem "myrack" - gem "myrack-obama" - G - - bundle "viz", format: "debug" - expect(out).to eq(<<~DOT.strip) - digraph Gemfile { - concentrate = "true"; - normalize = "true"; - nodesep = "0.55"; - edge[ weight = "2"]; - node[ fontname = "Arial, Helvetica, SansSerif"]; - edge[ fontname = "Arial, Helvetica, SansSerif" , fontsize = "12"]; - default [style = "filled", fillcolor = "#B9B9D5", shape = "box3d", fontsize = "16", label = "default"]; - myrack [style = "filled", fillcolor = "#B9B9D5", label = "myrack"]; - default -> myrack [constraint = "false"]; - "myrack-obama" [style = "filled", fillcolor = "#B9B9D5", label = "myrack-obama"]; - default -> "myrack-obama" [constraint = "false"]; - "myrack-obama" -> myrack; - } - debugging bundle viz... - DOT - end - end - - context "--without option" do - it "one group" do - install_gemfile <<-G - source "https://gem.repo1" - gem "activesupport" - - group :rails do - gem "rails" - end - G - - bundle "viz --without=rails" - expect(out).to include("gem_graph.png") - end - - it "two groups" do - install_gemfile <<-G - source "https://gem.repo1" - gem "activesupport" - - group :myrack do - gem "myrack" - end - - group :rails do - gem "rails" - end - G - - bundle "viz --without=rails:myrack" - expect(out).to include("gem_graph.png") - end - end -end diff --git a/spec/bundler/install/allow_offline_install_spec.rb b/spec/bundler/install/allow_offline_install_spec.rb index 21b0568f7d..c7ab7c3d7e 100644 --- a/spec/bundler/install/allow_offline_install_spec.rb +++ b/spec/bundler/install/allow_offline_install_spec.rb @@ -1,10 +1,6 @@ # frozen_string_literal: true -RSpec.describe "bundle install with :allow_offline_install" do - before do - bundle "config set allow_offline_install true" - end - +RSpec.describe "bundle install allows offline install" do context "with no cached data locally" do it "still installs" do install_gemfile <<-G, artifice: "compact_index" @@ -28,7 +24,7 @@ RSpec.describe "bundle install with :allow_offline_install" do it "will install from the compact index" do system_gems ["myrack-1.0.0"], path: default_bundle_path - bundle "config set clean false" + bundle_config "clean false" install_gemfile <<-G, artifice: "compact_index" source "http://testgemserver.local" gem "myrack-obama" @@ -43,7 +39,7 @@ RSpec.describe "bundle install with :allow_offline_install" do G 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(stdboth).to include "Using the cached data for the new index because of a network error" expect(the_bundle).to include_gems("myrack-obama 1.0", "myrack 1.0.0") end diff --git a/spec/bundler/install/binstubs_spec.rb b/spec/bundler/install/binstubs_spec.rb index 00765ac6dd..c2eccb3ef2 100644 --- a/spec/bundler/install/binstubs_spec.rb +++ b/spec/bundler/install/binstubs_spec.rb @@ -10,7 +10,7 @@ RSpec.describe "bundle install" do gem "myrack" G - config "BUNDLE_SYSTEM_BINDIR" => system_gem_path("altbin").to_s + bundle_config "BUNDLE_SYSTEM_BINDIR" => system_gem_path("altbin").to_s bundle :install expect(the_bundle).to include_gems "myrack 1.0.0" expect(system_gem_path("altbin/myrackup")).to exist diff --git a/spec/bundler/install/bundler_spec.rb b/spec/bundler/install/bundler_spec.rb index 56f8431181..86c22dad55 100644 --- a/spec/bundler/install/bundler_spec.rb +++ b/spec/bundler/install/bundler_spec.rb @@ -135,7 +135,7 @@ RSpec.describe "bundle install" do end it "causes a conflict if child dependencies conflict" do - bundle "config set force_ruby_platform true" + bundle_config "force_ruby_platform true" update_repo2 do build_gem "rails_pinned_to_old_activesupport" do |s| @@ -163,7 +163,7 @@ RSpec.describe "bundle install" do end it "causes a conflict if a child dependency conflicts with the Gemfile" do - bundle "config set force_ruby_platform true" + bundle_config "force_ruby_platform true" update_repo2 do build_gem "rails_pinned_to_old_activesupport" do |s| @@ -216,7 +216,7 @@ RSpec.describe "bundle install" do build_gem "rails", "7.0.4" end - bundle "config set path.system true" + bundle_config "path.system true" install_gemfile <<-G source "https://gem.repo4" @@ -238,7 +238,7 @@ RSpec.describe "bundle install" do end it "can install dependencies with newer bundler version with system gems" do - bundle "config set path.system true" + bundle_config "path.system true" system_gems "bundler-99999999.99.1" @@ -252,7 +252,7 @@ RSpec.describe "bundle install" do end it "can install dependencies with newer bundler version with a local path" do - bundle "config set path .bundle" + bundle_config "path .bundle" system_gems "bundler-99999999.99.1" diff --git a/spec/bundler/install/cooldown_spec.rb b/spec/bundler/install/cooldown_spec.rb new file mode 100644 index 0000000000..bad7b7cf34 --- /dev/null +++ b/spec/bundler/install/cooldown_spec.rb @@ -0,0 +1,433 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install with the cooldown setting" do + before do + build_repo2 + end + + context "Gemfile DSL" do + it "accepts `source ..., cooldown: N` without error" do + install_gemfile <<-G, artifice: "compact_index" + source "https://gem.repo2", cooldown: 5 + gem "myrack" + G + + expect(the_bundle).to include_gems("myrack 1.0.0") + end + + it "accepts `cooldown: 0` to disable cooldown for a source" do + install_gemfile <<-G, artifice: "compact_index" + source "https://gem.repo2", cooldown: 0 + gem "myrack" + G + + expect(the_bundle).to include_gems("myrack 1.0.0") + end + end + + context "CLI flag" do + before do + gemfile <<-G + source "https://gem.repo2" + gem "myrack" + G + end + + it "accepts --cooldown N on install" do + bundle "install --cooldown 7", artifice: "compact_index" + + expect(the_bundle).to include_gems("myrack 1.0.0") + end + + it "accepts --cooldown 0 as an escape hatch" do + bundle "install --cooldown 0", artifice: "compact_index" + + expect(the_bundle).to include_gems("myrack 1.0.0") + end + + it "rejects a negative --cooldown value" do + bundle "install --cooldown=-7", artifice: "compact_index", raise_on_error: false + + expect(err).to match(/non-negative integer/) + end + end + + context "configuration" do + it "reads BUNDLE_COOLDOWN as an integer" do + gemfile <<-G + source "https://gem.repo2" + gem "myrack" + G + + bundle "install", env: { "BUNDLE_COOLDOWN" => "7" }, artifice: "compact_index" + + expect(the_bundle).to include_gems("myrack 1.0.0") + end + + it "reads `bundle config set cooldown N`" do + gemfile <<-G + source "https://gem.repo2" + gem "myrack" + G + + bundle "config set cooldown 7" + bundle "install", artifice: "compact_index" + + expect(the_bundle).to include_gems("myrack 1.0.0") + end + end + + context "end-to-end with v2 compact index" do + before do + now = Time.now.utc + build_repo3 do + build_gem "ripe_gem", "1.0.0" do |s| + s.date = now - (30 * 86_400) + end + build_gem "ripe_gem", "2.0.0" do |s| + s.date = now - (1 * 86_400) + end + + # parent only resolves with the in-cooldown child 2.0.0 + build_gem "child", "1.0.0" do |s| + s.date = now - (30 * 86_400) + end + build_gem "child", "2.0.0" do |s| + s.date = now - (1 * 86_400) + end + build_gem "parent", "1.0.0" do |s| + s.add_dependency "child", ">= 2.0.0" + s.date = now - (30 * 86_400) + end + + # a cooldown-eligible version exists above the in-cooldown locked one + build_gem "upgradable", "2.0.0" do |s| + s.date = now - (1 * 86_400) + end + build_gem "upgradable", "3.0.0" do |s| + s.date = now - (30 * 86_400) + end + end + end + + it "excludes versions within the cooldown window" do + gemfile <<-G + source "https://gem.repo3" + gem "ripe_gem" + G + + bundle "install --cooldown 7", artifice: "compact_index_cooldown" + + expect(the_bundle).to include_gems("ripe_gem 1.0.0") + end + + it "selects the latest version when --cooldown 0 is passed" do + gemfile <<-G + source "https://gem.repo3" + gem "ripe_gem" + G + + bundle "install --cooldown 0", artifice: "compact_index_cooldown" + + expect(the_bundle).to include_gems("ripe_gem 2.0.0") + end + + it "applies cooldown declared per-source in the Gemfile" do + gemfile <<-G + source "https://gem.repo3", cooldown: 7 + gem "ripe_gem" + G + + bundle "install", artifice: "compact_index_cooldown" + + expect(the_bundle).to include_gems("ripe_gem 1.0.0") + end + + it "is overridden by CLI --cooldown when Gemfile sets a different per-source value" do + gemfile <<-G + source "https://gem.repo3", cooldown: 0 + gem "ripe_gem" + G + + bundle "install --cooldown 7", artifice: "compact_index_cooldown" + + expect(the_bundle).to include_gems("ripe_gem 1.0.0") + end + + it "bypasses cooldown when bundle install uses an existing lockfile" do + gemfile <<-G + source "https://gem.repo3" + gem "ripe_gem" + G + + lockfile <<-L + GEM + remote: https://gem.repo3/ + specs: + ripe_gem (2.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ripe_gem + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install --cooldown 7", artifice: "compact_index_cooldown" + + expect(the_bundle).to include_gems("ripe_gem 2.0.0") + end + + it "annotates in-cooldown versions in bundle outdated table output" do + gemfile <<-G + source "https://gem.repo3" + gem "ripe_gem", "1.0.0" + G + + lockfile <<-L + GEM + remote: https://gem.repo3/ + specs: + ripe_gem (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ripe_gem (= 1.0.0) + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "outdated --cooldown 7", artifice: "compact_index_cooldown", raise_on_error: false + + expect(out).to match(/ripe_gem.*\(cooldown \d+d\)/) + end + + it "annotates in-cooldown versions in bundle outdated --parseable output" do + gemfile <<-G + source "https://gem.repo3" + gem "ripe_gem", "1.0.0" + G + + lockfile <<-L + GEM + remote: https://gem.repo3/ + specs: + ripe_gem (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ripe_gem (= 1.0.0) + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "outdated --cooldown 7 --parseable", artifice: "compact_index_cooldown", raise_on_error: false + + expect(out).to match(/ripe_gem.*in cooldown for \d+ more day/) + end + + it "excludes a locally-installed version that is still within the cooldown window" do + system_gems "ripe_gem-2.0.0", gem_repo: gem_repo3 + + gemfile <<-G + source "https://gem.repo3" + gem "ripe_gem" + G + + bundle "install --cooldown 7", artifice: "compact_index_cooldown" + + expect(the_bundle).to include_gems("ripe_gem 1.0.0") + end + + it "selects a locally-installed in-cooldown version when --cooldown 0 bypasses the filter" do + system_gems "ripe_gem-2.0.0", gem_repo: gem_repo3 + + gemfile <<-G + source "https://gem.repo3" + gem "ripe_gem" + G + + bundle "install --cooldown 0", artifice: "compact_index_cooldown" + + expect(the_bundle).to include_gems("ripe_gem 2.0.0") + end + + it "surfaces a cooldown hint when bundle update filters every candidate" do + gemfile <<-G + source "https://gem.repo3" + gem "ripe_gem" + G + + lockfile <<-L + GEM + remote: https://gem.repo3/ + specs: + ripe_gem (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ripe_gem + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "update ripe_gem --cooldown 99999", artifice: "compact_index_cooldown", raise_on_error: false + + expect(err).to match(/excluded by the cooldown setting/) + expect(err).to match(/--cooldown 0/) + end + + it "keeps an in-cooldown locked version on bundle update --all instead of failing" do + # Lockfile written before cooldown was enabled pins the now-in-cooldown + # latest version. A full update must not downgrade below it, and cooldown + # must not filter it out, otherwise resolution becomes impossible (#9598). + gemfile <<-G + source "https://gem.repo3" + gem "ripe_gem" + G + + lockfile <<-L + GEM + remote: https://gem.repo3/ + specs: + ripe_gem (2.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ripe_gem + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "update --all --cooldown 7", artifice: "compact_index_cooldown" + + expect(the_bundle).to include_gems("ripe_gem 2.0.0") + end + + it "does not fail bundle outdated when the locked version is in cooldown" do + gemfile <<-G + source "https://gem.repo3" + gem "ripe_gem" + G + + lockfile <<-L + GEM + remote: https://gem.repo3/ + specs: + ripe_gem (2.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ripe_gem + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "outdated --cooldown 7", artifice: "compact_index_cooldown", raise_on_error: false + + # exit 0 means no outdated gems and, crucially, no resolution failure (exit 7) + expect(exitstatus).to eq(0) + end + + it "still applies cooldown and downgrades a gem that is updated explicitly" do + gemfile <<-G + source "https://gem.repo3" + gem "ripe_gem" + G + + lockfile <<-L + GEM + remote: https://gem.repo3/ + specs: + ripe_gem (2.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ripe_gem + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "update ripe_gem --cooldown 7", artifice: "compact_index_cooldown" + + expect(the_bundle).to include_gems("ripe_gem 1.0.0") + end + + it "keeps an in-cooldown transitive dependency on bundle update --all" do + gemfile <<-G + source "https://gem.repo3" + gem "parent" + G + + lockfile <<-L + GEM + remote: https://gem.repo3/ + specs: + child (2.0.0) + parent (1.0.0) + child (>= 2.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + parent + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "update --all --cooldown 7", artifice: "compact_index_cooldown" + + expect(the_bundle).to include_gems("parent 1.0.0", "child 2.0.0") + end + + it "still upgrades to a cooldown-eligible version above the locked one" do + gemfile <<-G + source "https://gem.repo3" + gem "upgradable" + G + + lockfile <<-L + GEM + remote: https://gem.repo3/ + specs: + upgradable (2.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + upgradable + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "update --all --cooldown 7", artifice: "compact_index_cooldown" + + expect(the_bundle).to include_gems("upgradable 3.0.0") + end + end +end diff --git a/spec/bundler/install/deploy_spec.rb b/spec/bundler/install/deploy_spec.rb index bd39ac5cc1..a3b4a87ecf 100644 --- a/spec/bundler/install/deploy_spec.rb +++ b/spec/bundler/install/deploy_spec.rb @@ -8,89 +8,23 @@ RSpec.describe "install in deployment or frozen mode" do G end - 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 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 lockfile") - end - - it "disallows --deployment --system" do - 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 - 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") - expect(exitstatus).to eq(15) - 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 - expect(the_bundle).to include_gems "myrack 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 "https://gem.repo1" - gem "myrack" - gem "myrack-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("* myrack-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 "fails without a lockfile and says that deployment requires a lock" do - bundle "config deployment true" + bundle_config "deployment true" bundle "install", raise_on_error: false expect(err).to include("The deployment setting requires a lockfile") end it "fails without a lockfile and says that frozen requires a lock" do - bundle "config frozen true" + bundle_config "frozen true" bundle "install", raise_on_error: false expect(err).to include("The frozen setting requires a lockfile") end it "still works if you are not in the app directory and specify --gemfile" do bundle "install" - simulate_new_machine - bundle "config set --local deployment true" - bundle "config set --local path vendor/bundle" + pristine_system_gems + bundle_config "deployment true" + bundle_config "path vendor/bundle" bundle "install --gemfile #{tmp}/bundled_app/Gemfile", dir: tmp expect(the_bundle).to include_gems "myrack 1.0" end @@ -104,8 +38,8 @@ RSpec.describe "install in deployment or frozen mode" do end G bundle :install - bundle "config set --local deployment true" - bundle "config set --local without test" + bundle_config "deployment true" + bundle_config "without test" bundle :install end @@ -113,7 +47,7 @@ RSpec.describe "install in deployment or frozen mode" do skip "doesn't find bundle" if Gem.win_platform? bundle :install - bundle "config set --local deployment true" + bundle_config "deployment true" bundle :install bundle "exec bundle check", env: { "PATH" => path } end @@ -128,7 +62,7 @@ RSpec.describe "install in deployment or frozen mode" do G bundle :install - bundle "config set --local deployment true" + bundle_config "deployment true" bundle :install end @@ -141,7 +75,7 @@ RSpec.describe "install in deployment or frozen mode" do G bundle :install - bundle "config set --local deployment true" + bundle_config "deployment true" bundle :install end @@ -152,7 +86,7 @@ RSpec.describe "install in deployment or frozen mode" do gem "myrack-obama", ">= 1.0" G - bundle "config set --local deployment true" + bundle_config "deployment true" bundle :install, artifice: "endpoint_strict_basic_authentication" end @@ -164,7 +98,7 @@ RSpec.describe "install in deployment or frozen mode" do end G - bundle "config set --local deployment true" + bundle_config "deployment true" bundle :install expect(the_bundle).to include_gems "myrack 1.0" @@ -172,7 +106,7 @@ RSpec.describe "install in deployment or frozen mode" do context "when replacing a host with the same host with credentials" do before do - bundle "config set --local path vendor/bundle" + bundle_config "path vendor/bundle" bundle "install" gemfile <<-G source "http://user_name:password@localgemserver.test/" @@ -192,7 +126,7 @@ RSpec.describe "install in deployment or frozen mode" do myrack G - bundle "config set --local deployment true" + bundle_config "deployment true" end it "allows the replace" do @@ -208,7 +142,7 @@ RSpec.describe "install in deployment or frozen mode" do end it "installs gems by default to vendor/bundle" do - bundle "config set deployment true" + bundle_config "deployment true" expect do bundle "install" end.not_to change { bundled_app_lock.mtime } @@ -216,20 +150,20 @@ RSpec.describe "install in deployment or frozen mode" do end it "installs gems to custom path if specified" do - bundle "config set path vendor/bundle2" - bundle "config set deployment true" + bundle_config "path vendor/bundle2" + bundle_config "deployment true" bundle "install" expect(out).to include("vendor/bundle2") end it "installs gems to custom path if specified, even when configured through ENV" do - bundle "config set deployment true" + bundle_config "deployment true" bundle "install", env: { "BUNDLE_PATH" => "vendor/bundle2" } expect(out).to include("vendor/bundle2") end it "works with the `frozen` setting" do - bundle "config set frozen true" + bundle_config "frozen true" expect do bundle "install" end.not_to change { bundled_app_lock.mtime } @@ -248,7 +182,7 @@ RSpec.describe "install in deployment or frozen mode" do gem "myrack-obama" G - bundle "config set --local deployment true" + bundle_config "deployment true" bundle :install, raise_on_error: false expect(err).to include("frozen mode") expect(err).to include("You have added to the Gemfile") @@ -267,9 +201,9 @@ RSpec.describe "install in deployment or frozen mode" do expect(the_bundle).to include_gems "path_gem 1.0" FileUtils.rm_r lib_path("path_gem-1.0") - bundle "config set --local path .bundle" - bundle "config set --local without development" - bundle "config set --local deployment true" + bundle_config "path .bundle" + bundle_config "without development" + bundle_config "deployment true" bundle :install, env: { "DEBUG" => "1" } run "puts :WIN" expect(out).to eq("WIN") @@ -317,11 +251,11 @@ RSpec.describe "install in deployment or frozen mode" do bar BUNDLED WITH - #{Bundler::VERSION} + #{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") + expect(err).to include("Your lockfile is missing \"bar\", but can't be updated because frozen mode is set") end it "explodes if a path gem is missing" do @@ -334,8 +268,8 @@ RSpec.describe "install in deployment or frozen mode" do expect(the_bundle).to include_gems "path_gem 1.0" FileUtils.rm_r lib_path("path_gem-1.0") - bundle "config set --local path .bundle" - bundle "config set --local deployment true" + bundle_config "path .bundle" + bundle_config "deployment true" bundle :install, raise_on_error: false expect(err).to include("The path `#{lib_path("path_gem-1.0")}` does not exist.") end @@ -406,7 +340,7 @@ RSpec.describe "install in deployment or frozen mode" do gem "activesupport" G - bundle "config set --local deployment true" + bundle_config "deployment true" 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") @@ -417,7 +351,7 @@ RSpec.describe "install in deployment or frozen mode" do it "explodes if you remove a gem and don't check in the lockfile" do gemfile 'source "https://gem.repo1"' - bundle "config set --local deployment true" + bundle_config "deployment true" bundle :install, raise_on_error: false expect(err).to include("Some dependencies were deleted") expect(err).to include("frozen mode") @@ -431,7 +365,7 @@ RSpec.describe "install in deployment or frozen mode" do gem "myrack", :git => "git://hubz.com" G - bundle "config set --local deployment true" + bundle_config "deployment true" bundle :install, raise_on_error: false expect(err).to include("frozen mode") expect(err).not_to include("You have added to the Gemfile") @@ -451,7 +385,7 @@ RSpec.describe "install in deployment or frozen mode" do gem "myrack" G - bundle "config set --local deployment true" + bundle_config "deployment true" bundle :install, raise_on_error: false expect(err).to include("frozen mode") expect(err).not_to include("You have deleted from the Gemfile") @@ -475,7 +409,7 @@ RSpec.describe "install in deployment or frozen mode" do gem "foo", :git => "#{lib_path("myrack")}" G - bundle "config set --local deployment true" + bundle_config "deployment true" bundle :install, raise_on_error: false expect(err).to include("frozen mode") expect(err).to include("You have changed in the Gemfile:\n* myrack from `#{lib_path("myrack")}` to `no specified source`") @@ -496,7 +430,7 @@ RSpec.describe "install in deployment or frozen mode" do gem "myrack", :git => "https:/my-git-repo-for-myrack" G - bundle "config set --local frozen true" + bundle_config "frozen true" bundle :install, raise_on_error: false expect(err).to include("frozen mode") expect(err).to include("You have changed in the Gemfile:\n* myrack from `#{lib_path("myrack")}` to `https:/my-git-repo-for-myrack`") @@ -507,7 +441,7 @@ RSpec.describe "install in deployment or frozen mode" do it "remembers that the bundle is frozen at runtime" do bundle :lock - bundle "config set --local deployment true" + bundle_config "deployment true" gemfile <<-G source "https://gem.repo1" @@ -540,17 +474,16 @@ RSpec.describe "install in deployment or frozen mode" do bundle :install expect(the_bundle).to include_gems "foo 1.0" - bundle "config set cache_all true" bundle :cache expect(bundled_app("vendor/cache/foo")).to be_directory bundle "install --local" expect(out).to include("Updating files in vendor/cache") - simulate_new_machine - bundle "config set --local deployment true" + pristine_system_gems + bundle_config "deployment true" bundle "install --verbose" - expect(out).not_to include("but the lockfile can't be updated because frozen mode is set") + expect(out).not_to include("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 2c2773e849..32ca455439 100644 --- a/spec/bundler/install/failure_spec.rb +++ b/spec/bundler/install/failure_spec.rb @@ -48,4 +48,38 @@ In Gemfile: end end end + + context "when lockfile dependencies don't match the gemspec" do + before do + build_repo4 do + build_gem "myrack", "1.0.0" do |s| + s.add_dependency "myrack-test", "~> 1.0" + end + + build_gem "myrack-test", "1.0.0" + end + + gemfile <<-G + source "https://gem.repo4" + gem "myrack" + G + + # First install to generate lockfile + bundle :install + + # Manually edit lockfile to have incorrect dependencies + lockfile_content = File.read(bundled_app_lock) + # Remove the myrack-test dependency from myrack + lockfile_content.gsub!(/^ myrack \(1\.0\.0\)\n myrack-test \(~> 1\.0\)\n/, " myrack (1.0.0)\n") + File.write(bundled_app_lock, lockfile_content) + end + + it "reports the mismatch with detailed information" do + bundle :install, raise_on_error: false, env: { "BUNDLE_FROZEN" => "true" } + + expect(err).to include("Bundler found incorrect dependencies in the lockfile for myrack-1.0.0") + expect(err).to include("myrack-test: gemspec specifies ~> 1.0, not in lockfile") + expect(err).to include("Please run `bundle install` to regenerate the lockfile.") + end + end end diff --git a/spec/bundler/install/redownload_spec.rb b/spec/bundler/install/force_spec.rb index b522e22bd5..e0f6fb6364 100644 --- a/spec/bundler/install/redownload_spec.rb +++ b/spec/bundler/install/force_spec.rb @@ -8,7 +8,7 @@ RSpec.describe "bundle install" do G end - shared_examples_for "an option to force redownloading gems" do + shared_examples_for "an option to force reinstalling gems" do it "re-installs installed gems" do myrack_lib = default_bundle_path("gems/myrack-1.0.0/lib/myrack.rb") @@ -57,35 +57,15 @@ RSpec.describe "bundle install" do end end - describe "with --force", bundler: 2 do - it_behaves_like "an option to force redownloading gems" do + describe "with --force" do + it_behaves_like "an option to force reinstalling gems" do let(:flag) { "force" } end - - it "shows a deprecation when single flag passed" do - bundle "install --force" - expect(err).to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" - end - - it "shows a deprecation when multiple flags passed" do - bundle "install --no-color --force" - expect(err).to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" - end end describe "with --redownload" do - it_behaves_like "an option to force redownloading gems" do + it_behaves_like "an option to force reinstalling gems" do let(:flag) { "redownload" } end - - it "does not show a deprecation when single flag passed" do - bundle "install --redownload" - expect(err).not_to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" - end - - it "does not show a deprecation when single multiple flags passed" do - bundle "install --no-color --redownload" - expect(err).not_to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" - end end end diff --git a/spec/bundler/install/gemfile/eval_gemfile_spec.rb b/spec/bundler/install/gemfile/eval_gemfile_spec.rb index a507e52485..3afa4f5daa 100644 --- a/spec/bundler/install/gemfile/eval_gemfile_spec.rb +++ b/spec/bundler/install/gemfile/eval_gemfile_spec.rb @@ -85,7 +85,7 @@ RSpec.describe "bundle install with gemfile that uses eval_gemfile" do # parsed lockfile and the evaluated gemfile. it "bundles with deployment mode configured" do bundle :install - bundle "config set --local deployment true" + bundle_config "deployment true" bundle :install end end diff --git a/spec/bundler/install/gemfile/force_ruby_platform_spec.rb b/spec/bundler/install/gemfile/force_ruby_platform_spec.rb index 926e7527e6..bcc1f36823 100644 --- a/spec/bundler/install/gemfile/force_ruby_platform_spec.rb +++ b/spec/bundler/install/gemfile/force_ruby_platform_spec.rb @@ -117,7 +117,7 @@ RSpec.describe "bundle install with force_ruby_platform DSL option", :jruby do platform_specific BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L simulate_platform "x86-darwin-100" do diff --git a/spec/bundler/install/gemfile/gemspec_spec.rb b/spec/bundler/install/gemfile/gemspec_spec.rb index be0694cb55..e51fc9247d 100644 --- a/spec/bundler/install/gemfile/gemspec_spec.rb +++ b/spec/bundler/install/gemfile/gemspec_spec.rb @@ -8,34 +8,6 @@ RSpec.describe "bundle install from an existing gemspec" do end end - let(:x64_mingw_archs) do - if RUBY_PLATFORM == "x64-mingw-ucrt" - - ["x64-mingw-ucrt", "x64-mingw32"] - - 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("foo")) do |s| s.write("Gemfile", "source :rubygems\ngemspec") @@ -154,7 +126,7 @@ RSpec.describe "bundle install from an existing gemspec" do # 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" + bundle_config "deployment true" output = bundle("install", dir: tmp("foo"), artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s }) expect(output).not_to match(/You have added to the Gemfile/) expect(output).not_to match(/You have deleted from the Gemfile/) @@ -178,7 +150,7 @@ RSpec.describe "bundle install from an existing gemspec" do end it "should match a lockfile without needing to re-resolve with development dependencies" do - simulate_platform java do + simulate_platform "java" do build_lib("foo", path: tmp("foo")) do |s| s.add_dependency "myrack" s.add_development_dependency "thin" @@ -220,7 +192,7 @@ RSpec.describe "bundle install from an existing gemspec" do install_gemfile <<-G, raise_on_error: false gemspec :path => '#{tmp("foo")}' G - expect(last_command.stdboth).not_to include("ahh") + expect(stdboth).not_to include("ahh") end it "allows the gemspec to activate other gems" do @@ -288,6 +260,25 @@ RSpec.describe "bundle install from an existing gemspec" do expect(out).to eq("WIN") end + it "does not make Gem.try_activate warn when local gem has extensions" do + build_lib("foo", path: tmp("foo")) do |s| + s.version = "1.0.0" + s.add_c_extension + end + build_repo2 + + install_gemfile <<-G + source "https://gem.repo2" + gemspec :path => '#{tmp("foo")}' + G + + expect(the_bundle).to include_gems "foo 1.0.0" + + run "Gem.try_activate('irb/lc/es/error.rb'); puts 'WIN'" + expect(out).to eq("WIN") + expect(err).to be_empty + end + it "handles downgrades" do build_lib "omg", "2.0", path: lib_path("omg") @@ -321,7 +312,7 @@ RSpec.describe "bundle install from an existing gemspec" do s.add_dependency "activesupport", ">= 1.0.1" end - bundle "config set --local deployment true" + bundle_config "deployment true" bundle :install, raise_on_error: false expect(err).to include("changed") @@ -383,13 +374,13 @@ RSpec.describe "bundle install from an existing gemspec" do myrack (1.0.0) PLATFORMS - #{generic_local_platform} + ruby DEPENDENCIES foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -429,12 +420,13 @@ RSpec.describe "bundle install from an existing gemspec" do end build_lib "foo", path: bundled_app do |s| - if platform_specific_type == :runtime + case platform_specific_type + when :runtime s.add_runtime_dependency dependency - elsif platform_specific_type == :development + when :development s.add_development_dependency dependency else - raise "wrong dependency type #{platform_specific_type}, can only be :development or :runtime" + raise ArgumentError, "wrong dependency type #{platform_specific_type}, can only be :development or :runtime" end end @@ -443,17 +435,18 @@ RSpec.describe "bundle install from an existing gemspec" do gemspec G - bundle "config set --local force_ruby_platform true" + bundle_config "force_ruby_platform true" bundle "install" simulate_new_machine simulate_platform("jruby") { bundle "install" } - simulate_platform(x64_mingw32) { bundle "install" } + expect(lockfile).to include("platform_specific (1.0-java)") + simulate_platform("x64-mingw-ucrt") { bundle "install" } end context "on ruby" do before do - bundle "config set --local force_ruby_platform true" + bundle_config "force_ruby_platform true" bundle :install end @@ -465,7 +458,7 @@ RSpec.describe "bundle install from an existing gemspec" do 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) + c.checksum gem_repo2, "platform_specific", "1.0", "x64-mingw-ucrt" end expect(lockfile).to eq <<~L @@ -480,18 +473,18 @@ RSpec.describe "bundle install from an existing gemspec" do specs: platform_specific (1.0) platform_specific (1.0-java) - #{x64_mingw_gems} + platform_specific (1.0-x64-mingw-ucrt) PLATFORMS java ruby - #{x64_mingw_platforms} + x64-mingw-ucrt DEPENDENCIES foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -506,7 +499,7 @@ RSpec.describe "bundle install from an existing gemspec" do 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) + c.checksum gem_repo2, "platform_specific", "1.0", "x64-mingw-ucrt" end expect(lockfile).to eq <<~L @@ -520,19 +513,19 @@ RSpec.describe "bundle install from an existing gemspec" do specs: platform_specific (1.0) platform_specific (1.0-java) - #{x64_mingw_gems} + platform_specific (1.0-x64-mingw-ucrt) PLATFORMS java ruby - #{x64_mingw_platforms} + x64-mingw-ucrt DEPENDENCIES foo! platform_specific #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -549,7 +542,7 @@ RSpec.describe "bundle install from an existing gemspec" do 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) + c.checksum gem_repo2, "platform_specific", "1.0", "x64-mingw-ucrt" end expect(lockfile).to eq <<~L @@ -565,19 +558,19 @@ RSpec.describe "bundle install from an existing gemspec" do platform_specific platform_specific (1.0) platform_specific (1.0-java) - #{x64_mingw_gems} + platform_specific (1.0-x64-mingw-ucrt) PLATFORMS java ruby - #{x64_mingw_platforms} + x64-mingw-ucrt DEPENDENCIES foo! indirect_platform_specific #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -595,7 +588,7 @@ RSpec.describe "bundle install from an existing gemspec" do end it "installs the ruby platform gemspec" do - bundle "config set --local force_ruby_platform true" + bundle_config "force_ruby_platform true" install_gemfile <<-G source "https://gem.repo1" @@ -606,9 +599,9 @@ 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 - bundle "config set --local force_ruby_platform true" + bundle_config "force_ruby_platform true" - bundle "config set --local without development" + bundle_config "without development" install_gemfile <<-G source "https://gem.repo1" gemspec :path => '#{tmp("foo")}', :name => 'foo' @@ -623,14 +616,14 @@ RSpec.describe "bundle install from an existing gemspec" do before do build_lib("chef", path: tmp("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 + s.write "chef-universal-mingw-ucrt.gemspec", build_spec("chef", "17.1.17", "universal-mingw-ucrt") {|sw| sw.runtime "win32-api", "~> 1.5.3" }.first.to_ruby end end it "does not remove the platform specific specs from the lockfile when updating" do build_repo4 do build_gem "win32-api", "1.5.3" do |s| - s.platform = "universal-mingw32" + s.platform = "universal-mingw-ucrt" end end @@ -641,8 +634,8 @@ RSpec.describe "bundle install from an existing gemspec" do checksums = checksums_section_when_enabled 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" + c.no_checksum "chef", "17.1.17", "universal-mingw-ucrt" + c.checksum gem_repo4, "win32-api", "1.5.3", "universal-mingw-ucrt" end initial_lockfile = <<~L @@ -650,24 +643,22 @@ RSpec.describe "bundle install from an existing gemspec" do remote: ../chef specs: chef (17.1.17) - chef (17.1.17-universal-mingw32) + chef (17.1.17-universal-mingw-ucrt) win32-api (~> 1.5.3) GEM remote: https://gem.repo4/ specs: - win32-api (1.5.3-universal-mingw32) + win32-api (1.5.3-universal-mingw-ucrt) PLATFORMS - ruby - #{x64_mingw_platforms} - x86-mingw32 + #{lockfile_platforms("ruby", "x64-mingw-ucrt", "x86-mingw32")} DEPENDENCIES chef! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L lockfile initial_lockfile @@ -730,7 +721,7 @@ RSpec.describe "bundle install from an existing gemspec" do jruby-openssl #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L gemspec = tmp("activeadmin/activeadmin.gemspec") diff --git a/spec/bundler/install/gemfile/git_spec.rb b/spec/bundler/install/gemfile/git_spec.rb index ada74c311c..b2a82caf01 100644 --- a/spec/bundler/install/gemfile/git_spec.rb +++ b/spec/bundler/install/gemfile/git_spec.rb @@ -2,12 +2,8 @@ RSpec.describe "bundle install with git sources" do describe "when floating on main" do - before :each do - build_git "foo" do |s| - s.executables = "foobar" - end - - install_gemfile <<-G + let(:base_gemfile) do + <<-G source "https://gem.repo1" git "#{lib_path("foo-1.0")}" do gem 'foo' @@ -15,7 +11,16 @@ RSpec.describe "bundle install with git sources" do G end + let(:install_base_gemfile) do + build_git "foo" do |s| + s.executables = "foobar" + end + + install_gemfile base_gemfile + end + it "fetches gems" do + install_base_gemfile expect(the_bundle).to include_gems("foo 1.0") run <<-RUBY @@ -26,20 +31,59 @@ 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 "does not (yet?) enforce CHECKSUMS" do + build_git "foo" + revision = revision_for(lib_path("foo-1.0")) + + bundle_config "lockfile_checksums true" + gemfile base_gemfile + + lockfile <<~L + GIT + remote: #{lib_path("foo-1.0")} + revision: #{revision} + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + + CHECKSUMS + foo (1.0) + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle_config "frozen true" + + bundle "install" + expect(the_bundle).to include_gems("foo 1.0") + end + + it "caches the git repo" do + install_base_gemfile + expect(Dir["#{default_cache_path}/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("cache") - FileUtils.rm_rf(cache_path) + install_base_gemfile + FileUtils.rm_r(default_cache_path) ruby "require 'bundler/setup'" - expect(cache_path).not_to exist + expect(default_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" + install_base_gemfile + pristine_system_gems + bundle_config "global_gem_cache true" bundle :install expect(Dir["#{home}/.bundle/cache/git/foo-1.0-*"]).to have_attributes size: 1 @@ -49,6 +93,7 @@ RSpec.describe "bundle install with git sources" do end it "caches the evaluated gemspec" do + install_base_gemfile git = update_git "foo" do |s| s.executables = ["foobar"] # we added this the first time, so keep it now s.files = ["bin/foobar"] # updating git nukes the files list @@ -67,6 +112,7 @@ RSpec.describe "bundle install with git sources" do end it "does not update the git source implicitly" do + install_base_gemfile update_git "foo" install_gemfile bundled_app2("Gemfile"), <<-G, dir: bundled_app2 @@ -85,6 +131,7 @@ RSpec.describe "bundle install with git sources" do end it "sets up git gem executables on the path" do + install_base_gemfile bundle "exec foobar" expect(out).to eq("1.0") end @@ -136,8 +183,8 @@ RSpec.describe "bundle install with git sources" do end it "still works after moving the application directory" do - bundle "config set --local path vendor/bundle" - bundle "install" + bundle_config "path vendor/bundle" + install_base_gemfile FileUtils.mv bundled_app, tmp("bundled_app.bck") @@ -145,8 +192,8 @@ RSpec.describe "bundle install with git sources" do end it "can still install after moving the application directory" do - bundle "config set --local path vendor/bundle" - bundle "install" + bundle_config "path vendor/bundle" + install_base_gemfile FileUtils.mv bundled_app, tmp("bundled_app.bck") @@ -334,7 +381,7 @@ RSpec.describe "bundle install with git sources" do it "does not download random non-head refs" do git("update-ref -m \"Bundler Spec!\" refs/bundler/1 main~1", lib_path("foo-1.0")) - bundle "config set global_gem_cache true" + bundle_config "global_gem_cache true" install_gemfile <<-G source "https://gem.repo1" @@ -431,7 +478,7 @@ RSpec.describe "bundle install with git sources" do update_git("foo", path: repo, tag: tag) install_gemfile <<-G - source "https://gem.repo1" + source "https://gem.repo1" git "#{repo}", :tag => #{tag.dump} do gem "foo" end @@ -1031,7 +1078,7 @@ RSpec.describe "bundle install with git sources" do expect(out).to eq("WIN") end - it "does not to a remote fetch if the revision is cached locally" do + it "does not do a remote fetch if the revision is cached locally" do build_git "foo" install_gemfile <<-G @@ -1039,7 +1086,7 @@ RSpec.describe "bundle install with git sources" do gem "foo", :git => "#{lib_path("foo-1.0")}" G - FileUtils.rm_rf(lib_path("foo-1.0")) + FileUtils.rm_r(lib_path("foo-1.0")) bundle "install" expect(out).not_to match(/updating/i) @@ -1152,6 +1199,30 @@ RSpec.describe "bundle install with git sources" do expect(the_bundle).to include_gem "rails 7.1.4", "activesupport 7.1.4" end + + it "doesn't explode when adding an explicit ref to a git gem with dependencies" do + lib_root = lib_path("rails") + + build_lib "activesupport", "7.1.4", path: lib_root.join("activesupport") + build_git "rails", "7.1.4", path: lib_root do |s| + s.add_dependency "activesupport", "= 7.1.4" + end + + old_revision = revision_for(lib_root) + update_git "rails", "7.1.4", path: lib_root + + install_gemfile <<-G + source "https://gem.repo1" + gem "rails", "7.1.4", :git => "#{lib_root}" + G + + install_gemfile <<-G + source "https://gem.repo1" + gem "rails", :git => "#{lib_root}", :ref => "#{old_revision}" + G + + expect(the_bundle).to include_gem "rails 7.1.4", "activesupport 7.1.4" + end end describe "bundle install after the remote has been updated" do @@ -1218,9 +1289,9 @@ RSpec.describe "bundle install with git sources" do gem "valim", "= 1.0", :git => "#{lib_path("valim")}" G - simulate_new_machine + pristine_system_gems - bundle "config set --local deployment true" + bundle_config "deployment true" bundle :install end end @@ -1333,7 +1404,7 @@ RSpec.describe "bundle install with git sources" do File.open(git_reader.path.join("ext/foo.c"), "w") do |file| file.write <<-C #include "ruby.h" - VALUE foo() { return INT2FIX(#{i}); } + VALUE foo(VALUE self) { return INT2FIX(#{i}); } void Init_foo() { rb_define_global_function("foo", &foo, 0); } C end @@ -1567,7 +1638,7 @@ In Gemfile: rake! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L with_path_as("") do @@ -1603,9 +1674,9 @@ In Gemfile: gem 'foo' end G - bundle "config set --global path vendor/bundle" + bundle_config_global "path vendor/bundle" bundle :install - simulate_new_machine + pristine_system_gems bundle "install", env: { "PATH" => "" } expect(out).to_not include("You need to install git to be able to use gems from git repositories.") @@ -1614,7 +1685,7 @@ In Gemfile: describe "when the git source is overridden with a local git repo" do before do - bundle "config set --global local.foo #{lib_path("foo")}" + bundle_config_global "local.foo #{lib_path("foo")}" end describe "and git output is colorized" do @@ -1650,7 +1721,7 @@ In Gemfile: end G - expect(last_command.stdboth).to_not include("password1") + expect(stdboth).to_not include("password1") expect(out).to include("Fetching https://user1@github.com/company/private-repo") end end @@ -1666,7 +1737,7 @@ In Gemfile: end G - expect(last_command.stdboth).to_not include("oauth_token") + expect(stdboth).to_not include("oauth_token") expect(out).to include("Fetching https://x-oauth-basic@github.com/company/private-repo") end end diff --git a/spec/bundler/install/gemfile/groups_spec.rb b/spec/bundler/install/gemfile/groups_spec.rb index 71871899a2..4013b112ec 100644 --- a/spec/bundler/install/gemfile/groups_spec.rb +++ b/spec/bundler/install/gemfile/groups_spec.rb @@ -25,7 +25,7 @@ RSpec.describe "bundle install with groups" do puts ACTIVESUPPORT R - expect(err_without_deprecations).to eq("ZOMG LOAD ERROR") + expect(err_without_deprecations).to match(/cannot load such file -- activesupport/) end it "installs gems with inline :groups into those groups" do @@ -36,7 +36,7 @@ RSpec.describe "bundle install with groups" do puts THIN R - expect(err_without_deprecations).to eq("ZOMG LOAD ERROR") + expect(err_without_deprecations).to match(/cannot load such file -- thin/) end it "sets up everything if Bundler.setup is used with no groups" do @@ -57,7 +57,7 @@ RSpec.describe "bundle install with groups" do puts THIN RUBY - expect(err_without_deprecations).to eq("ZOMG LOAD ERROR") + expect(err_without_deprecations).to match(/cannot load such file -- thin/) end it "sets up old groups when they have previously been removed" do @@ -86,13 +86,13 @@ RSpec.describe "bundle install with groups" do end it "installs gems in the default group" do - bundle "config set --local without emo" + bundle_config "without emo" bundle :install expect(the_bundle).to include_gems "myrack 1.0.0", groups: [:default] end it "respects global `without` configuration, but does not save it locally" do - bundle "config set --global without emo" + bundle_config_global "without emo" bundle :install expect(the_bundle).to include_gems "myrack 1.0.0", groups: [:default] bundle "config list" @@ -100,34 +100,27 @@ RSpec.describe "bundle install with groups" do 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 - bundle "config set without emo" + it "allows running application where groups where configured by a different user" do + bundle_config "without emo" bundle :install 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_config "without emo" bundle :install expect(the_bundle).not_to include_gems "activesupport 2.3.5", groups: [:default] end - 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 - expect(the_bundle).not_to include_gems "activesupport 2.3.5" - end - it "does not say it installed gems from the excluded group" do - bundle "config set --local without emo" + bundle_config "without emo" bundle :install expect(out).not_to include("activesupport") end it "allows Bundler.setup for specific groups" do - bundle "config set --local without emo" + bundle_config "without emo" bundle :install run("require 'myrack'; puts MYRACK", :default) expect(out).to eq("1.0.0") @@ -142,7 +135,7 @@ RSpec.describe "bundle install with groups" do end G - bundle "config set --local without emo" + bundle_config "without emo" bundle :install expect(the_bundle).to include_gems "activesupport 2.3.2", groups: [:default] end @@ -159,34 +152,13 @@ RSpec.describe "bundle install with groups" do ENV["BUNDLE_WITHOUT"] = nil end - 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 - bundle "install --without emo" - - bundle :install - expect(the_bundle).not_to include_gems "activesupport 2.3.5" - end - it "does not install gems from the optional group" do bundle :install expect(the_bundle).not_to include_gems "thin 1.0" end it "installs gems from the optional group when requested" do - bundle "config set --local with debugging" - bundle :install - expect(the_bundle).to include_gems "thin 1.0" - end - - 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_config "with debugging" bundle :install expect(the_bundle).to include_gems "thin 1.0" end @@ -198,30 +170,6 @@ RSpec.describe "bundle install with groups" do ENV["BUNDLE_WITH"] = nil end - 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 - 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 - bundle "config set --local with debugging" - 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 - expect(last_command).to be_failure - expect(err).to include("The offending groups are: emo") - end - it "allows the BUNDLE_WITH setting to override BUNDLE_WITHOUT" do ENV["BUNDLE_WITH"] = "debugging" @@ -235,20 +183,14 @@ 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 - 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" - end - it "has no effect when listing a not optional group in with" do - bundle "config set --local with emo" + bundle_config "with emo" bundle :install expect(the_bundle).to include_gems "activesupport 2.3.5" end it "has no effect when listing an optional group in without" do - bundle "config set --local without debugging" + bundle_config "without debugging" bundle :install expect(the_bundle).not_to include_gems "thin 1.0" end @@ -266,13 +208,13 @@ RSpec.describe "bundle install with groups" do end it "installs gems in the default group" do - bundle "config set --local without emo lolercoaster" + bundle_config "without emo lolercoaster" bundle :install expect(the_bundle).to include_gems "myrack 1.0.0" end it "installs the gem if any of its groups are installed" do - bundle "config set --local without emo" + bundle_config "without emo" bundle :install expect(the_bundle).to include_gems "myrack 1.0.0", "activesupport 2.3.5" end @@ -294,15 +236,15 @@ RSpec.describe "bundle install with groups" do end it "installs the gem unless all groups are excluded" do - bundle "config set --local without emo" + bundle_config "without emo" bundle :install expect(the_bundle).to include_gems "activesupport 2.3.5" - bundle "config set --local without lolercoaster" + bundle_config "without lolercoaster" bundle :install expect(the_bundle).to include_gems "activesupport 2.3.5" - bundle "config set --local without emo lolercoaster" + bundle_config "without emo lolercoaster" bundle :install expect(the_bundle).not_to include_gems "activesupport 2.3.5" @@ -327,13 +269,13 @@ RSpec.describe "bundle install with groups" do end it "installs gems in the default group" do - bundle "config set --local without emo lolercoaster" + bundle_config "without emo lolercoaster" bundle :install expect(the_bundle).to include_gems "myrack 1.0.0" end it "installs the gem if any of its groups are installed" do - bundle "config set --local without emo" + bundle_config "without emo" bundle :install expect(the_bundle).to include_gems "myrack 1.0.0", "activesupport 2.3.5" end @@ -365,13 +307,13 @@ RSpec.describe "bundle install with groups" do end end - describe "when locked and installed with `without` option" do + describe "when locked and installed with `without` setting" do before(:each) do build_repo2 system_gems "myrack-0.9.1" - bundle "config set --local without myrack" + bundle_config "without myrack" install_gemfile <<-G source "https://gem.repo2" gem "myrack" @@ -382,7 +324,7 @@ RSpec.describe "bundle install with groups" do G end - it "uses the correct versions even if --without was used on the original" do + it "uses versions from excluded gems in a machine without the without configuration" do expect(the_bundle).to include_gems "myrack 0.9.1" expect(the_bundle).not_to include_gems "myrack_middleware 1.0" simulate_new_machine @@ -394,10 +336,10 @@ RSpec.describe "bundle install with groups" do end it "does not hit the remote a second time" do - FileUtils.rm_rf gem_repo2 - bundle "config set --local without myrack" + FileUtils.rm_r gem_repo2 + bundle_config "without myrack" bundle :install, verbose: true - expect(last_command.stdboth).not_to match(/fetching/i) + expect(stdboth).not_to match(/fetching/i) end end end diff --git a/spec/bundler/install/gemfile/install_if_spec.rb b/spec/bundler/install/gemfile/install_if_spec.rb index 170b58c4c0..05a6d15129 100644 --- a/spec/bundler/install/gemfile/install_if_spec.rb +++ b/spec/bundler/install/gemfile/install_if_spec.rb @@ -45,7 +45,7 @@ RSpec.describe "bundle install with install_if conditionals" do thin #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end diff --git a/spec/bundler/install/gemfile/lockfile_spec.rb b/spec/bundler/install/gemfile/lockfile_spec.rb index f80b21e562..19bd7074b2 100644 --- a/spec/bundler/install/gemfile/lockfile_spec.rb +++ b/spec/bundler/install/gemfile/lockfile_spec.rb @@ -7,12 +7,8 @@ RSpec.describe "bundle install with a lockfile present" do gem "myrack", "1.0.0" G - subject do - install_gemfile(gf) - end - it "touches the lockfile on install even when nothing has changed" do - subject + install_gemfile(gf) expect { bundle :install }.to change { bundled_app_lock.mtime } end @@ -21,32 +17,25 @@ RSpec.describe "bundle install with a lockfile present" do context "with plugins disabled" do before do - bundle "config set plugins false" - subject + bundle_config "plugins false" end - it "does not evaluate the gemfile twice" do + it "does not evaluate the gemfile twice when the gem is already installed" do + install_gemfile(gf) bundle :install with_env_vars("BUNDLER_SPEC_NO_APPEND" => "1") { expect(the_bundle).to include_gem "myrack 1.0.0" } - # The first eval is from the initial install, we're testing that the - # second install doesn't double-eval expect(bundled_app("evals").read.lines.to_a.size).to eq(2) end - context "when the gem is not installed" do - before { FileUtils.rm_rf bundled_app(".bundle") } - - it "does not evaluate the gemfile twice" do - bundle :install + it "does not evaluate the gemfile twice when the gem is not installed" do + gemfile(gf) + bundle :install - with_env_vars("BUNDLER_SPEC_NO_APPEND" => "1") { expect(the_bundle).to include_gem "myrack 1.0.0" } + with_env_vars("BUNDLER_SPEC_NO_APPEND" => "1") { expect(the_bundle).to include_gem "myrack 1.0.0" } - # The first eval is from the initial install, we're testing that the - # second install doesn't double-eval - expect(bundled_app("evals").read.lines.to_a.size).to eq(2) - end + expect(bundled_app("evals").read.lines.to_a.size).to eq(1) end end end diff --git a/spec/bundler/install/gemfile/override_spec.rb b/spec/bundler/install/gemfile/override_spec.rb new file mode 100644 index 0000000000..02b0e7d772 --- /dev/null +++ b/spec/bundler/install/gemfile/override_spec.rb @@ -0,0 +1,401 @@ +# frozen_string_literal: true + +RSpec.describe "override DSL" do + context "with a version: string operation" do + it "replaces a direct dependency requirement with the override version spec" do + install_gemfile <<-G + source "https://gem.repo1" + override "myrack", version: "= 0.9.1" + gem "myrack" + G + + expect(the_bundle).to include_gems "myrack 0.9.1" + end + + it "replaces a transitive dependency requirement" do + install_gemfile <<-G + source "https://gem.repo1" + override "myrack", version: "= 1.0.0" + gem "myrack_middleware" + G + + expect(the_bundle).to include_gems "myrack 1.0.0", "myrack_middleware 1.0" + end + + it "replaces the requirement even when the Gemfile pins a different version" do + install_gemfile <<-G + source "https://gem.repo1" + override "myrack", version: "= 0.9.1" + gem "myrack", "= 1.0.0" + G + + expect(the_bundle).to include_gems "myrack 0.9.1" + end + + it "applies the override against an existing lockfile" do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + expect(the_bundle).to include_gems "myrack 1.0.0" + + gemfile <<-G + source "https://gem.repo1" + override "myrack", version: "= 0.9.1" + gem "myrack" + G + + bundle :install + + expect(the_bundle).to include_gems "myrack 0.9.1" + end + + it "pins a prerelease version that the Gemfile dependency would otherwise filter out" do + build_repo2 do + build_gem "has_prerelease", "1.0" + build_gem "has_prerelease", "1.1.pre" + end + + install_gemfile <<-G + source "https://gem.repo2" + override "has_prerelease", version: "= 1.1.pre" + gem "has_prerelease" + G + + expect(the_bundle).to include_gems "has_prerelease 1.1.pre" + end + end + + context "with a version: :ignore_upper operation" do + it "strips a < upper bound on a direct dependency" do + install_gemfile <<-G + source "https://gem.repo1" + override "myrack", version: :ignore_upper + gem "myrack", "< 1.0" + G + + expect(the_bundle).to include_gems "myrack 1.0.0" + end + + it "folds ~> into >= so newer versions become reachable" do + install_gemfile <<-G + source "https://gem.repo1" + override "myrack", version: :ignore_upper + gem "myrack", "~> 0.9.1" + G + + expect(the_bundle).to include_gems "myrack 1.0.0" + end + end + + context "with a version: nil operation" do + it "drops a direct dependency's pin entirely" do + install_gemfile <<-G + source "https://gem.repo1" + override "myrack", version: nil + gem "myrack", "= 0.9.1" + G + + expect(the_bundle).to include_gems "myrack 1.0.0" + end + + it "drops a transitive dependency's pin entirely" do + install_gemfile <<-G + source "https://gem.repo1" + override "myrack", version: nil + gem "myrack_middleware" + G + + expect(the_bundle).to include_gems "myrack 1.0.0", "myrack_middleware 1.0" + end + + it "applies a transitive-only override against an existing lockfile" do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack_middleware" + G + + expect(the_bundle).to include_gems "myrack 0.9.1", "myrack_middleware 1.0" + + gemfile <<-G + source "https://gem.repo1" + override "myrack", version: "= 1.0.0" + gem "myrack_middleware" + G + + bundle :install + + expect(the_bundle).to include_gems "myrack 1.0.0", "myrack_middleware 1.0" + end + end + + context "lockfile contents" do + it "does not record the override directive in Gemfile.lock" do + install_gemfile <<-G + source "https://gem.repo1" + override "myrack", version: "= 0.9.1" + gem "myrack" + G + + expect(lockfile).not_to match(/override/i) + end + end + + context "with a required_ruby_version: operation" do + it "lets the resolver pick a gem whose required_ruby_version excludes the current Ruby with :ignore_upper" do + build_repo2 do + build_gem "needs_old_ruby", "1.0" do |s| + s.required_ruby_version = "< #{Gem.ruby_version}" + end + end + + gemfile <<-G + source "https://gem.repo2" + override "needs_old_ruby", required_ruby_version: :ignore_upper + gem "needs_old_ruby" + G + + bundle :lock + expect(lockfile).to include("needs_old_ruby (1.0)") + end + + it "lets the resolver pick the gem with required_ruby_version: nil" do + build_repo2 do + build_gem "needs_old_ruby", "1.0" do |s| + s.required_ruby_version = "< #{Gem.ruby_version}" + end + end + + gemfile <<-G + source "https://gem.repo2" + override "needs_old_ruby", required_ruby_version: nil + gem "needs_old_ruby" + G + + bundle :lock + expect(lockfile).to include("needs_old_ruby (1.0)") + end + + it "applies to a transitive dependency's required_ruby_version" do + build_repo2 do + build_gem "needs_old_ruby", "1.0" do |s| + s.required_ruby_version = "< #{Gem.ruby_version}" + end + build_gem "wraps_old", "1.0" do |s| + s.add_dependency "needs_old_ruby" + end + end + + gemfile <<-G + source "https://gem.repo2" + override "needs_old_ruby", required_ruby_version: :ignore_upper + gem "wraps_old" + G + + bundle :lock + expect(lockfile).to include("needs_old_ruby (1.0)") + expect(lockfile).to include("wraps_old (1.0)") + end + + it "re-resolves a direct dep when a metadata override is added against an existing lockfile" do + build_repo2 do + build_gem "selectable", "1.0" + build_gem "selectable", "2.0" do |s| + s.required_ruby_version = "< #{Gem.ruby_version}" + end + end + + gemfile <<-G + source "https://gem.repo2" + gem "selectable" + G + + bundle :lock + expect(lockfile).to include("selectable (1.0)") + + gemfile <<-G + source "https://gem.repo2" + override "selectable", required_ruby_version: :ignore_upper + gem "selectable" + G + + bundle :lock + expect(lockfile).to include("selectable (2.0)") + end + end + + context "with a required_rubygems_version: operation" do + it "lets the resolver pick a gem whose required_rubygems_version excludes the current RubyGems with :ignore_upper" do + build_repo2 do + build_gem "needs_old_rubygems", "1.0" do |s| + s.required_rubygems_version = "< #{Gem.rubygems_version}" + end + end + + gemfile <<-G + source "https://gem.repo2" + override "needs_old_rubygems", required_rubygems_version: :ignore_upper + gem "needs_old_rubygems" + G + + bundle :lock + expect(lockfile).to include("needs_old_rubygems (1.0)") + end + end + + context "with an :all target" do + it "applies required_ruby_version: :ignore_upper to every gem" do + build_repo2 do + build_gem "needs_old_ruby_a", "1.0" do |s| + s.required_ruby_version = "< #{Gem.ruby_version}" + end + build_gem "needs_old_ruby_b", "1.0" do |s| + s.required_ruby_version = "< #{Gem.ruby_version}" + end + end + + gemfile <<-G + source "https://gem.repo2" + override :all, required_ruby_version: :ignore_upper + gem "needs_old_ruby_a" + gem "needs_old_ruby_b" + G + + bundle :lock + expect(lockfile).to include("needs_old_ruby_a (1.0)") + expect(lockfile).to include("needs_old_ruby_b (1.0)") + end + + it "is overridden by a per-gem override on the same field" do + build_repo2 do + build_gem "permissive", "1.0" do |s| + s.required_ruby_version = "< #{Gem.ruby_version}" + end + build_gem "still_blocked", "1.0" do |s| + s.required_ruby_version = "= #{Gem.ruby_version}.999" + end + end + + # :all says ignore_upper (would unblock both), but per-gem on + # still_blocked nails it to a hard requirement that still fails. + gemfile <<-G + source "https://gem.repo2" + override :all, required_ruby_version: :ignore_upper + override "still_blocked", required_ruby_version: "= #{Gem.ruby_version}.999" + gem "permissive" + gem "still_blocked" + G + + bundle :lock, raise_on_error: false + expect(err).to include("still_blocked") + end + + it "preserves locked versions when an :all metadata override is added without bundle update" do + build_repo2 do + build_gem "selectable", "1.0" + build_gem "selectable", "2.0" do |s| + s.required_ruby_version = "< #{Gem.ruby_version}" + end + end + + gemfile <<-G + source "https://gem.repo2" + gem "selectable" + G + + bundle :lock + expect(lockfile).to include("selectable (1.0)") + + gemfile <<-G + source "https://gem.repo2" + override :all, required_ruby_version: :ignore_upper + gem "selectable" + G + + # :all override alone does not pre-unlock locked specs; narrow change + # should not trigger unrelated lockfile churn. + bundle :lock + expect(lockfile).to include("selectable (1.0)") + + # bundle update opts the user into re-resolution under the override. + bundle "update selectable" + expect(lockfile).to include("selectable (2.0)") + end + end + + context "diagnostic on resolve failure" do + it "lists active overrides with their Gemfile location" do + build_repo2 do + build_gem "needs_old_ruby", "1.0" do |s| + s.required_ruby_version = "= #{Gem.ruby_version}.999" + end + end + + gemfile <<-G + source "https://gem.repo2" + override "needs_old_ruby", required_ruby_version: "= #{Gem.ruby_version}.999" + gem "needs_old_ruby" + G + + bundle :lock, raise_on_error: false + expect(err).to include("Bundler applied the following overrides") + expect(err).to include("override \"needs_old_ruby\", required_ruby_version:") + expect(err).to match(/declared at Gemfile:\d+/) + end + end + + context "install-time compatibility" do + it "installs a gem whose required_ruby_version excludes the current Ruby when an override removes the constraint" do + build_repo2 do + build_gem "needs_old_ruby", "1.0" do |s| + s.required_ruby_version = "< #{Gem.ruby_version}" + end + end + + install_gemfile <<-G + source "https://gem.repo2" + override "needs_old_ruby", required_ruby_version: nil + gem "needs_old_ruby" + G + + expect(the_bundle).to include_gems "needs_old_ruby 1.0" + end + + it "installs a gem whose required_rubygems_version excludes the current RubyGems when an override removes it" do + build_repo2 do + build_gem "needs_old_rubygems", "1.0" do |s| + s.required_rubygems_version = "< #{Gem.rubygems_version}" + end + end + + install_gemfile <<-G + source "https://gem.repo2" + override "needs_old_rubygems", required_rubygems_version: nil + gem "needs_old_rubygems" + G + + expect(the_bundle).to include_gems "needs_old_rubygems 1.0" + end + + it "installs every gem when :all required_ruby_version override is in effect" do + build_repo2 do + build_gem "needs_old_ruby_a", "1.0" do |s| + s.required_ruby_version = "< #{Gem.ruby_version}" + end + build_gem "needs_old_ruby_b", "1.0" do |s| + s.required_ruby_version = "< #{Gem.ruby_version}" + end + end + + install_gemfile <<-G + source "https://gem.repo2" + override :all, required_ruby_version: :ignore_upper + gem "needs_old_ruby_a" + gem "needs_old_ruby_b" + G + + expect(the_bundle).to include_gems "needs_old_ruby_a 1.0", "needs_old_ruby_b 1.0" + end + end +end diff --git a/spec/bundler/install/gemfile/path_spec.rb b/spec/bundler/install/gemfile/path_spec.rb index 7525404b24..b069488531 100644 --- a/spec/bundler/install/gemfile/path_spec.rb +++ b/spec/bundler/install/gemfile/path_spec.rb @@ -1,22 +1,10 @@ # frozen_string_literal: true RSpec.describe "bundle install with explicit source paths" do - it "fetches gems with a global path source", bundler: "< 3" do - build_lib "foo" - - install_gemfile <<-G - path "#{lib_path("foo-1.0")}" - gem 'foo' - G - - expect(the_bundle).to include_gems("foo 1.0") - end - it "fetches gems" do build_lib "foo" install_gemfile <<-G - source "https://gem.repo1" path "#{lib_path("foo-1.0")}" do gem 'foo' end @@ -29,7 +17,6 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "foo" install_gemfile <<-G - source "https://gem.repo1" gem 'foo', :path => "#{lib_path("foo-1.0")}" G @@ -42,7 +29,6 @@ RSpec.describe "bundle install with explicit source paths" do relative_path = lib_path("foo-1.0").relative_path_from(bundled_app) install_gemfile <<-G - source "https://gem.repo1" gem 'foo', :path => "#{relative_path}" G @@ -55,7 +41,6 @@ RSpec.describe "bundle install with explicit source paths" do relative_path = lib_path("foo-1.0").relative_path_from(Pathname.new("~").expand_path) install_gemfile <<-G - source "https://gem.repo1" gem 'foo', :path => "~/#{relative_path}" G @@ -70,7 +55,6 @@ RSpec.describe "bundle install with explicit source paths" do relative_path = lib_path("foo-1.0").relative_path_from(Pathname.new("/home/#{username}").expand_path) install_gemfile <<-G, raise_on_error: false - source "https://gem.repo1" gem 'foo', :path => "~#{username}/#{relative_path}" G expect(err).to match("There was an error while trying to use the path `~#{username}/#{relative_path}`.") @@ -81,7 +65,6 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "foo", path: bundled_app("foo-1.0") install_gemfile <<-G - source "https://gem.repo1" gem 'foo', :path => "./foo-1.0" G @@ -126,7 +109,7 @@ RSpec.describe "bundle install with explicit source paths" do demo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle :install, dir: lib_path("demo") @@ -139,11 +122,10 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "foo", path: bundled_app("foo-1.0") install_gemfile <<-G - source "https://gem.repo1" gem 'foo', :path => File.expand_path("foo-1.0", __dir__) G - bundle "config set --local frozen true" + bundle_config "frozen true" bundle :install end @@ -159,7 +141,6 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G - source "https://gem.repo1" gem "foo", :path => "#{lib_path("nested")}" G @@ -179,7 +160,6 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "foo", "1.0.0", path: lib_path("omg/foo") install_gemfile <<-G - source "https://gem.repo1" gem "omg", :path => "#{lib_path("omg")}" G @@ -256,7 +236,6 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "omg", "2.0", path: lib_path("omg") install_gemfile <<-G - source "https://gem.repo1" gem "omg", :path => "#{lib_path("omg")}" G @@ -280,7 +259,6 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G - source "https://gem.repo1" gem "premailer", :path => "#{lib_path("premailer")}" G @@ -302,11 +280,9 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G, raise_on_error: false - source "https://gem.repo1" gem "foo", :path => "#{lib_path("foo-1.0")}" G - expect(err).to_not include("Your Gemfile has no gem server sources.") expect(err).to match(/is not valid. Please fix this gemspec./) expect(err).to match(/The validation error was 'missing value for attribute version'/) expect(err).to match(/You have one or more invalid gemspecs that need to be fixed/) @@ -370,7 +346,7 @@ RSpec.describe "bundle install with explicit source paths" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L lockfile lockfile_path, original_lockfile @@ -440,7 +416,6 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G, raise_on_error: false - source "https://gem.repo1" gemspec :path => "#{lib_path("foo")}" G @@ -454,7 +429,6 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G - source "https://gem.repo1" gemspec :path => "#{lib_path("foo")}", :name => "foo" G @@ -467,7 +441,6 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G, verbose: true - source "https://gem.repo1" path "#{lib_path("foo-1.0")}" do gem 'foo' end @@ -481,11 +454,10 @@ RSpec.describe "bundle install with explicit source paths" do it "handles directories in bin/" do build_lib "foo" - lib_path("foo-1.0").join("foo.gemspec").rmtree + FileUtils.rm_rf lib_path("foo-1.0").join("foo.gemspec") lib_path("foo-1.0").join("bin/performance").mkpath install_gemfile <<-G - source "https://gem.repo1" gem 'foo', '1.0', :path => "#{lib_path("foo-1.0")}" G expect(err).to be_empty @@ -495,7 +467,6 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "foo" install_gemfile <<-G - source "https://gem.repo1" gem 'foo', :path => "#{lib_path("foo-1.0")}" G @@ -508,7 +479,6 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "hi2u" install_gemfile <<-G - source "https://gem.repo1" path "#{lib_path}" do gem "omg" gem "hi2u" @@ -527,7 +497,6 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G - source "https://gem.repo1" gem "foo", :path => "#{lib_path("foo")}" gem "omg", :path => "#{lib_path("omg")}" G @@ -539,7 +508,6 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "foo", gemspec: false gemfile <<-G - source "https://gem.repo1" gem "foo", "1.0", :path => "#{lib_path("foo-1.0")}" G @@ -553,15 +521,11 @@ RSpec.describe "bundle install with explicit source paths" do PATH remote: vendor/bar specs: - - GEM - remote: http://rubygems.org/ L FileUtils.mkdir_p(bundled_app("vendor/bar")) install_gemfile <<-G - source "http://rubygems.org" gem "bar", "1.0.0", path: "vendor/bar", require: "bar/nyard" G end @@ -606,7 +570,6 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G - source "https://gem.repo1" gem "foo", :path => "#{lib_path("foo-1.0")}" G @@ -622,7 +585,6 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "bar", "1.0", path: lib_path("foo/bar") install_gemfile <<-G - source "https://gem.repo1" gem "foo", :path => "#{lib_path("foo")}" G end @@ -699,7 +661,7 @@ RSpec.describe "bundle install with explicit source paths" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G build_lib "foo", "1.0", path: lib_path("foo") do |s| @@ -727,7 +689,7 @@ RSpec.describe "bundle install with explicit source paths" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G expect(the_bundle).to include_gems "myrack 0.9.1" @@ -766,7 +728,7 @@ RSpec.describe "bundle install with explicit source paths" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G build_lib "foo", "1.0", path: lib_path("foo") do |s| @@ -799,7 +761,7 @@ RSpec.describe "bundle install with explicit source paths" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G expect(the_bundle).to include_gems "myrack 0.9.1" @@ -827,7 +789,7 @@ RSpec.describe "bundle install with explicit source paths" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "lock" @@ -853,11 +815,60 @@ RSpec.describe "bundle install with explicit source paths" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end end + context "when platform specific version locked, and having less dependencies that the generic version that's actually installed" do + before do + build_repo4 do + build_gem "racc", "1.8.1" + build_gem "mini_portile2", "2.8.2" + end + + build_lib "nokogiri", "1.18.9", path: lib_path("nokogiri") do |s| + s.add_dependency "mini_portile2", "~> 2.8.2" + s.add_dependency "racc", "~> 1.4" + end + + gemfile <<~G + source "https://gem.repo4" + + gem "nokogiri", path: "#{lib_path("nokogiri")}" + G + + lockfile <<~L + PATH + remote: #{lib_path("nokogiri")} + specs: + nokogiri (1.18.9) + mini_portile2 (~> 2.8.2) + racc (~> 1.4) + nokogiri (1.18.9-arm64-darwin) + racc (~> 1.4) + + GEM + remote: https://rubygems.org/ + specs: + racc (1.8.1) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + nokogiri! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "works" do + bundle "install" + 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| @@ -869,12 +880,10 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G - source "https://gem.repo1" gem "bar", :git => "#{lib_path("bar")}" G install_gemfile <<-G - source "https://gem.repo1" gem "bar", :path => "#{lib_path("bar")}" G @@ -929,7 +938,6 @@ RSpec.describe "bundle install with explicit source paths" do it "runs pre-install hooks" do build_git "foo" gemfile <<-G - source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -949,7 +957,6 @@ RSpec.describe "bundle install with explicit source paths" do it "runs post-install hooks" do build_git "foo" gemfile <<-G - source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -969,7 +976,6 @@ RSpec.describe "bundle install with explicit source paths" do it "complains if the install hook fails" do build_git "foo" gemfile <<-G - source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -1000,7 +1006,6 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G - source "https://gem.repo1" gem "foo", :path => "#{lib_path("foo-1.0")}" gem "bar", :path => "#{lib_path("bar-1.0")}" G diff --git a/spec/bundler/install/gemfile/platform_spec.rb b/spec/bundler/install/gemfile/platform_spec.rb index 1e536e6052..e12933ebcf 100644 --- a/spec/bundler/install/gemfile/platform_spec.rb +++ b/spec/bundler/install/gemfile/platform_spec.rb @@ -65,7 +65,7 @@ RSpec.describe "bundle install across platforms" do platform_specific G - bundle "config set --local frozen true" + bundle_config "frozen true" install_gemfile <<-G source "https://gem.repo1" @@ -161,8 +161,8 @@ 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 - bundle "config set --local force_ruby_platform true" + pristine_system_gems + bundle_config "force_ruby_platform true" bundle "install" expect(the_bundle).to include_gems "nokogiri 1.4.2" @@ -195,7 +195,7 @@ RSpec.describe "bundle install across platforms" do build_gem("ffi", "1.9.23") end - simulate_platform java do + simulate_platform "java" do install_gemfile <<-G source "https://gem.repo4" @@ -235,7 +235,7 @@ RSpec.describe "bundle install across platforms" do pry #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "lock --add-platform ruby" @@ -269,7 +269,7 @@ RSpec.describe "bundle install across platforms" do pry #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L expect(lockfile).to eq good_lockfile @@ -330,7 +330,7 @@ RSpec.describe "bundle install across platforms" do end it "works with gems with platform-specific dependency having different requirements order" do - simulate_platform x64_mac do + simulate_platform "x86_64-darwin-15" do update_repo2 do build_gem "fspath", "3" build_gem "image_optim_pack", "1.2.3" do |s| @@ -365,7 +365,7 @@ RSpec.describe "bundle install across platforms" do gem "myrack", "1.0.0" G - bundle "config set --local path vendor/bundle" + bundle_config "path vendor/bundle" bundle :install FileUtils.mv(vendored_gems, bundled_app("vendor/bundle", Gem.ruby_engine, "1.8")) @@ -394,7 +394,7 @@ RSpec.describe "bundle install across platforms" do #{checksums} G - bundle "config set --local force_ruby_platform true" + bundle_config "force_ruby_platform true" install_gemfile <<-G source "https://gem.repo1" @@ -420,7 +420,7 @@ RSpec.describe "bundle install across platforms" do platform_specific #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end end @@ -486,7 +486,7 @@ RSpec.describe "bundle install with platform conditionals" do tzinfo (~> 1.2) BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install --verbose" @@ -542,7 +542,7 @@ RSpec.describe "bundle install with platform conditionals" do end it "does not attempt to install gems from :rbx when using --local" do - bundle "config set --local force_ruby_platform true" + bundle_config "force_ruby_platform true" gemfile <<-G source "https://gem.repo1" @@ -554,7 +554,7 @@ RSpec.describe "bundle install with platform conditionals" do end it "does not attempt to install gems from other rubies when using --local" do - bundle "config set --local force_ruby_platform true" + bundle_config "force_ruby_platform true" gemfile <<-G source "https://gem.repo1" gem "some_gem", platform: :ruby_22 @@ -565,12 +565,12 @@ 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 - bundle "config set --local force_ruby_platform true" + bundle_config "force_ruby_platform true" gemfile <<-G source "https://gem.repo1" - gem "myrack", :platform => [:windows, :mswin, :mswin64, :mingw, :x64_mingw, :jruby] + gem "myrack", :platform => [:windows, :jruby] G bundle "install" @@ -589,12 +589,12 @@ RSpec.describe "bundle install with platform conditionals" do myrack #{checksums_section_when_enabled} BUNDLED WITH - #{Bundler::VERSION} + #{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" + bundle_config "force_ruby_platform true" build_repo4 do build_gem "listen", "3.7.1" do |s| @@ -616,7 +616,7 @@ end RSpec.describe "when a gem has no architecture" do it "still installs correctly" do - simulate_platform x86_mswin32 do + simulate_platform "x86-mswin32" do build_repo2 do # The rcov gem is platform mswin32, but has no arch build_gem "rcov" do |s| diff --git a/spec/bundler/install/gemfile/ruby_spec.rb b/spec/bundler/install/gemfile/ruby_spec.rb index 3e15d82bbe..d937abd714 100644 --- a/spec/bundler/install/gemfile/ruby_spec.rb +++ b/spec/bundler/install/gemfile/ruby_spec.rb @@ -83,10 +83,10 @@ RSpec.describe "ruby requirement" do myrack RUBY VERSION - ruby 2.1.4p422 + ruby 2.1.4 BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) diff --git a/spec/bundler/install/gemfile/sources_spec.rb b/spec/bundler/install/gemfile/sources_spec.rb index 84af5c0d06..654d638e1f 100644 --- a/spec/bundler/install/gemfile/sources_spec.rb +++ b/spec/bundler/install/gemfile/sources_spec.rb @@ -4,153 +4,6 @@ RSpec.describe "bundle install with gems on multiple sources" do # repo1 is built automatically before all of the specs run # it contains myrack-obama 1.0.0 and myrack 0.9.1 & 1.0.0 amongst other gems - context "without source affinity" do - before do - # Oh no! Someone evil is trying to hijack myrack :( - # need this to be broken to check for correct source ordering - build_repo3 do - build_gem "myrack", repo3_myrack_version do |s| - s.write "lib/myrack.rb", "MYRACK = 'FAIL'" - end - end - end - - context "with multiple toplevel sources" do - let(:repo3_myrack_version) { "1.0.0" } - - before do - gemfile <<-G - source "https://gem.repo3" - source "https://gem.repo1" - gem "myrack-obama" - gem "myrack" - G - end - - 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: - myrack (1.0.0) - - PLATFORMS - #{local_platform} - - DEPENDENCIES - depends_on_myrack! - - 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, "myrack", "1.0.0")} - from the API at https://gem.repo1/ - #{checksum_to_lock(gem_repo3, "myrack", "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 - - 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 'myrack' was found in multiple sources.") - expect(err).to include("Installed from: https://gem.repo1") - expect(the_bundle).to include_gems("myrack-obama 1.0.0", "myrack 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 - - context "when different versions of the same gem are in multiple sources" do - let(:repo3_myrack_version) { "1.2" } - - before do - gemfile <<-G - source "https://gem.repo3" - source "https://gem.repo1" - gem "myrack-obama" - gem "myrack", "1.0.0" # force it to install the working version in repo1 - G - end - - it "warns about ambiguous gems, but installs anyway", bundler: "< 3" do - bundle :install, artifice: "compact_index" - expect(err).to include("Warning: the gem 'myrack' was found in multiple sources.") - expect(err).to include("Installed from: https://gem.repo1") - expect(the_bundle).to include_gems("myrack-obama 1.0.0", "myrack 1.0.0", source: "remote1") - end - - it "fails", bundler: "3" do - bundle :install, artifice: "compact_index", raise_on_error: false - expect(err).to include("Each source after the first must include a block") - expect(exitstatus).to eq(4) - end - end - end - - context "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 @@ -189,7 +42,7 @@ RSpec.describe "bundle install with gems on multiple sources" do expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist expect(bundled_app("vendor/cache/myrack-obama-1.0.gem")).to exist - bundle "config set --local deployment true" + bundle_config "deployment true" bundle :install, artifice: "compact_index" expect(the_bundle).to include_gems("myrack-obama 1.0.0", "myrack 1.0.0") @@ -273,7 +126,7 @@ RSpec.describe "bundle install with gems on multiple sources" do expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 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 + # when there is already a lockfile, and the gems are missing, so try again system_gems [] bundle :install, artifice: "compact_index" @@ -313,189 +166,6 @@ RSpec.describe "bundle install with gems on multiple sources" do expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 1.0.0") end end - - context "and in yet another source" do - before do - gemfile <<-G - source "https://gem.repo1" - source "https://gem.repo2" - source "https://gem.repo3" do - gem "depends_on_myrack" - end - G - end - - 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, "myrack", "1.0.0")} - from the API at https://gem.repo2/ - #{checksum_to_lock(gem_repo1, "myrack", "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 - myrack_checksum = "c0ffee11" * 8 - bundle :install, artifice: "compact_index", env: { "BUNDLER_SPEC_MYRACK_CHECKSUM" => myrack_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. - myrack (1.0.0) sha256=#{myrack_checksum} - from the API at https://gem.repo2/ - and the API at https://gem.repo1/ - #{checksum_to_lock(gem_repo2, "myrack", "1.0.0")} - from the gem at #{default_bundle_path("cache", "myrack-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", "myrack-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, "myrack", "1.0.0") - bundle :install, artifice: "compact_index", env: { "BUNDLER_SPEC_MYRACK_CHECKSUM" => gem_checksum, "DEBUG" => "1" } - - expect(err).to include("Warning: the gem 'myrack' was found in multiple sources.") - expect(err).to include("Installed from: https://gem.repo2") - - checksums = checksums_section_when_enabled do |c| - c.checksum gem_repo3, "depends_on_myrack", "1.0.1" - c.checksum gem_repo2, "myrack", "1.0.0" - end - - expect(lockfile).to eq <<~L - GEM - remote: https://gem.repo1/ - remote: https://gem.repo2/ - specs: - myrack (1.0.0) - - GEM - remote: https://gem.repo3/ - specs: - depends_on_myrack (1.0.1) - myrack - - PLATFORMS - #{lockfile_platforms} - - DEPENDENCIES - depends_on_myrack! - #{checksums} - BUNDLED WITH - #{Bundler::VERSION} - L - - previous_lockfile = lockfile - expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 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 'myrack' was found in multiple sources.") - expect(err).to include("Installed from: https://gem.repo2") - - checksums = checksums_section_when_enabled do |c| - c.no_checksum "depends_on_myrack", "1.0.1" - c.no_checksum "myrack", "1.0.0" - end - - expect(lockfile).to eq <<~L - GEM - remote: https://gem.repo1/ - remote: https://gem.repo2/ - specs: - myrack (1.0.0) - - GEM - remote: https://gem.repo3/ - specs: - depends_on_myrack (1.0.1) - myrack - - PLATFORMS - #{lockfile_platforms} - - DEPENDENCIES - depends_on_myrack! - #{checksums} - BUNDLED WITH - #{Bundler::VERSION} - L - - previous_lockfile = lockfile - expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 1.0.0") - expect(lockfile).to eq(previous_lockfile) - end - - it "fails", bundler: "3" do - bundle :install, artifice: "compact_index", raise_on_error: false - expect(err).to include("Each source after the first must include a block") - expect(exitstatus).to eq(4) - end - end - - context "and only the dependency is pinned" do - before do - # need this to be broken to check for correct source ordering - build_repo gem_repo2 do - build_gem "myrack", "1.0.0" do |s| - s.write "lib/myrack.rb", "MYRACK = 'FAIL'" - end - end - - gemfile <<-G - source "https://gem.repo3" # contains depends_on_myrack - source "https://gem.repo2" # contains broken myrack - - gem "depends_on_myrack" # installed from gem_repo3 - gem "myrack", :source => "https://gem.repo1" - G - end - - it "installs the dependency from the pinned source without warning", bundler: "< 3" do - bundle :install, artifice: "compact_index" - - expect(err).not_to include("Warning: the gem 'myrack' was found in multiple sources.") - expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 1.0.0") - - # In https://github.com/rubygems/bundler/issues/3585 this failed - # when there is already a lock file, and the gems are missing, so try again - system_gems [] - bundle :install, artifice: "compact_index" - - expect(err).not_to include("Warning: the gem 'myrack' was found in multiple sources.") - expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 1.0.0") - end - - it "fails", bundler: "3" do - bundle :install, artifice: "compact_index", raise_on_error: false - expect(err).to include("Each source after the first must include a block") - expect(exitstatus).to eq(4) - end - end end context "when a top-level gem can only be found in an scoped source" do @@ -524,39 +194,6 @@ RSpec.describe "bundle install with gems on multiple sources" do end end - context "when an indirect dependency can't be found in the aggregate rubygems source", bundler: "< 3" do - before do - build_repo2 - - build_repo3 do - build_gem "depends_on_missing", "1.0.1" do |s| - s.add_dependency "missing" - end - end - - gemfile <<-G - source "https://gem.repo2" - - source "https://gem.repo3" - - gem "depends_on_missing" - G - end - - it "fails" do - bundle :install, artifice: "compact_index", raise_on_error: false - expect(err).to 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 - context "when a top-level gem has an indirect dependency" do before do build_repo gem_repo2 do @@ -714,337 +351,6 @@ RSpec.describe "bundle install with gems on multiple sources" do end end - context "when the lockfile has aggregated rubygems sources and newer versions of dependencies are available" do - before do - build_repo gem_repo2 do - build_gem "activesupport", "6.0.3.4" do |s| - s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2" - s.add_dependency "i18n", ">= 0.7", "< 2" - s.add_dependency "minitest", "~> 5.1" - s.add_dependency "tzinfo", "~> 1.1" - s.add_dependency "zeitwerk", "~> 2.2", ">= 2.2.2" - end - - build_gem "activesupport", "6.1.2.1" do |s| - s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2" - s.add_dependency "i18n", ">= 1.6", "< 2" - s.add_dependency "minitest", ">= 5.1" - s.add_dependency "tzinfo", "~> 2.0" - s.add_dependency "zeitwerk", "~> 2.3" - end - - build_gem "concurrent-ruby", "1.1.8" - build_gem "concurrent-ruby", "1.1.9" - build_gem "connection_pool", "2.2.3" - - build_gem "i18n", "1.8.9" do |s| - s.add_dependency "concurrent-ruby", "~> 1.0" - end - - build_gem "minitest", "5.14.3" - build_gem "myrack", "2.2.3" - build_gem "redis", "4.2.5" - - build_gem "sidekiq", "6.1.3" do |s| - s.add_dependency "connection_pool", ">= 2.2.2" - s.add_dependency "myrack", "~> 2.0" - s.add_dependency "redis", ">= 4.2.0" - end - - build_gem "thread_safe", "0.3.6" - - build_gem "tzinfo", "1.2.9" do |s| - s.add_dependency "thread_safe", "~> 0.1" - end - - build_gem "tzinfo", "2.0.4" do |s| - s.add_dependency "concurrent-ruby", "~> 1.0" - end - - build_gem "zeitwerk", "2.4.2" - end - - build_repo3 do - build_gem "sidekiq-pro", "5.2.1" do |s| - s.add_dependency "connection_pool", ">= 2.2.3" - s.add_dependency "sidekiq", ">= 6.1.0" - end - end - - gemfile <<-G - # frozen_string_literal: true - - source "https://gem.repo2" - - gem "activesupport" - - source "https://gem.repo3" do - gem "sidekiq-pro" - end - G - - @locked_checksums = checksums_section_when_enabled 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, "myrack", "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/ - remote: https://gem.repo3/ - specs: - activesupport (6.0.3.4) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) - concurrent-ruby (1.1.8) - connection_pool (2.2.3) - i18n (1.8.9) - concurrent-ruby (~> 1.0) - minitest (5.14.3) - myrack (2.2.3) - redis (4.2.5) - sidekiq (6.1.3) - connection_pool (>= 2.2.2) - myrack (~> 2.0) - redis (>= 4.2.0) - sidekiq-pro (5.2.1) - connection_pool (>= 2.2.3) - sidekiq (>= 6.1.0) - thread_safe (0.3.6) - tzinfo (1.2.9) - thread_safe (~> 0.1) - zeitwerk (2.4.2) - - PLATFORMS - #{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" - expect(err).to be_empty - - expect(the_bundle).to include_gems("activesupport 6.0.3.4") - expect(the_bundle).not_to include_gems("activesupport 6.1.2.1") - expect(the_bundle).to include_gems("tzinfo 1.2.9") - expect(the_bundle).not_to include_gems("tzinfo 2.0.4") - expect(the_bundle).to include_gems("concurrent-ruby 1.1.8") - expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.9") - - expect(lockfile).to eq <<~L - GEM - remote: https://gem.repo2/ - specs: - activesupport (6.0.3.4) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) - concurrent-ruby (1.1.8) - connection_pool (2.2.3) - i18n (1.8.9) - concurrent-ruby (~> 1.0) - minitest (5.14.3) - myrack (2.2.3) - redis (4.2.5) - sidekiq (6.1.3) - connection_pool (>= 2.2.2) - myrack (~> 2.0) - redis (>= 4.2.0) - thread_safe (0.3.6) - tzinfo (1.2.9) - thread_safe (~> 0.1) - zeitwerk (2.4.2) - - GEM - remote: https://gem.repo3/ - specs: - sidekiq-pro (5.2.1) - connection_pool (>= 2.2.3) - sidekiq (>= 6.1.0) - - PLATFORMS - #{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 - initial_lockfile = lockfile - - bundle "config set --local frozen true" - bundle :install, artifice: "compact_index" - - expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") - - expect(the_bundle).to include_gems("activesupport 6.0.3.4") - expect(the_bundle).not_to include_gems("activesupport 6.1.2.1") - expect(the_bundle).to include_gems("tzinfo 1.2.9") - expect(the_bundle).not_to include_gems("tzinfo 2.0.4") - expect(the_bundle).to include_gems("concurrent-ruby 1.1.8") - expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.9") - - expect(lockfile).to eq(initial_lockfile) - end - - it "fails when running bundle install in frozen mode", bundler: "3" do - initial_lockfile = lockfile - - bundle "config set --local frozen true" - bundle :install, artifice: "compact_index", raise_on_error: false - - expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") - - expect(lockfile).to eq(initial_lockfile) - end - - it "splits sections and upgrades gems when running bundle update, and doesn't warn" do - bundle "update --all", artifice: "compact_index" - expect(err).to be_empty - - expect(the_bundle).not_to include_gems("activesupport 6.0.3.4") - expect(the_bundle).to include_gems("activesupport 6.1.2.1") - @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 - remote: https://gem.repo2/ - specs: - activesupport (6.1.2.1) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 1.6, < 2) - minitest (>= 5.1) - tzinfo (~> 2.0) - zeitwerk (~> 2.3) - concurrent-ruby (1.1.9) - connection_pool (2.2.3) - i18n (1.8.9) - concurrent-ruby (~> 1.0) - minitest (5.14.3) - myrack (2.2.3) - redis (4.2.5) - sidekiq (6.1.3) - connection_pool (>= 2.2.2) - myrack (~> 2.0) - redis (>= 4.2.0) - tzinfo (2.0.4) - concurrent-ruby (~> 1.0) - zeitwerk (2.4.2) - - GEM - remote: https://gem.repo3/ - specs: - sidekiq-pro (5.2.1) - connection_pool (>= 2.2.3) - sidekiq (>= 6.1.0) - - PLATFORMS - #{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" - expect(err).to be_empty - - expect(the_bundle).to include_gems("activesupport 6.0.3.4") - expect(the_bundle).not_to include_gems("activesupport 6.1.2.1") - expect(the_bundle).to include_gems("tzinfo 1.2.9") - expect(the_bundle).not_to include_gems("tzinfo 2.0.4") - expect(the_bundle).to include_gems("concurrent-ruby 1.1.9") - expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.8") - - @locked_checksums.checksum gem_repo2, "concurrent-ruby", "1.1.9" - - expect(lockfile).to eq <<~L - GEM - remote: https://gem.repo2/ - specs: - activesupport (6.0.3.4) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) - concurrent-ruby (1.1.9) - connection_pool (2.2.3) - i18n (1.8.9) - concurrent-ruby (~> 1.0) - minitest (5.14.3) - myrack (2.2.3) - redis (4.2.5) - sidekiq (6.1.3) - connection_pool (>= 2.2.2) - myrack (~> 2.0) - redis (>= 4.2.0) - thread_safe (0.3.6) - tzinfo (1.2.9) - thread_safe (~> 0.1) - zeitwerk (2.4.2) - - GEM - remote: https://gem.repo3/ - specs: - sidekiq-pro (5.2.1) - connection_pool (>= 2.2.3) - sidekiq (>= 6.1.0) - - PLATFORMS - #{lockfile_platforms} - - DEPENDENCIES - activesupport - sidekiq-pro! - #{@locked_checksums} - BUNDLED WITH - #{Bundler::VERSION} - L - end - end - context "when a top-level gem has an indirect dependency present in the default source, but with a different version from the one resolved" do before do build_lib "activesupport", "7.0.0.alpha", path: lib_path("rails/activesupport") @@ -1134,7 +440,7 @@ RSpec.describe "bundle install with gems on multiple sources" do nokogiri #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install --verbose", artifice: "compact_index" @@ -1207,112 +513,6 @@ RSpec.describe "bundle install with gems on multiple sources" do end end - context "with a lockfile with aggregated rubygems sources" do - let(:aggregate_gem_section_lockfile) do - <<~L - GEM - remote: https://gem.repo1/ - remote: https://gem.repo3/ - specs: - myrack (0.9.1) - - PLATFORMS - #{lockfile_platforms} - - DEPENDENCIES - myrack! - #{checksums_section} - BUNDLED WITH - #{Bundler::VERSION} - L - end - - let(:split_gem_section_lockfile) do - <<~L - GEM - remote: https://gem.repo1/ - specs: - - GEM - remote: https://gem.repo3/ - specs: - myrack (0.9.1) - - PLATFORMS - #{lockfile_platforms} - - DEPENDENCIES - myrack! - - BUNDLED WITH - #{Bundler::VERSION} - L - end - - before do - build_repo3 do - build_gem "myrack", "0.9.1" - end - - gemfile <<-G - source "https://gem.repo1" - source "https://gem.repo3" do - gem 'myrack' - end - G - - lockfile aggregate_gem_section_lockfile - end - - 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" - - 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("myrack 0.9.1", source: "remote3") - end - - it "prints a checksum warning when the checksums from both sources do not match", bundler: "< 3" do - bundle "config set --local deployment true" - - bundle "install", artifice: "compact_index", raise_on_error: false - - api_checksum1 = checksum_digest(gem_repo1, "myrack", "0.9.1") - api_checksum3 = checksum_digest(gem_repo3, "myrack", "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. - myrack (0.9.1) sha256=#{api_checksum3} - from the API at https://gem.repo3/ - myrack (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.") - expect(out).to be_empty - end - end - context "with a path gem in the same Gemfile" do before do build_lib "foo" @@ -1366,11 +566,11 @@ RSpec.describe "bundle install with gems on multiple sources" do gem 'bar', '~> 0.1', :source => 'https://gem.repo4' G - bundle "config set --local path ../gems/system" + bundle_config "path ../gems/system" bundle :install, artifice: "compact_index" # And then we add some new versions... - update_repo4 do + build_repo4 do build_gem "foo", "0.2" build_gem "bar", "0.3" end @@ -1583,38 +783,7 @@ RSpec.describe "bundle install with gems on multiple sources" do expect(err).to include("Could not reach host gem.repo4. Check your network connection and try again.") end - context "when an indirect dependency is available from multiple ambiguous sources", bundler: "< 3" do - it "succeeds but warns, suggesting a source block" do - build_repo4 do - build_gem "depends_on_myrack" do |s| - s.add_dependency "myrack" - end - build_gem "myrack" - end - - install_gemfile <<-G, artifice: "compact_index_extra_api", raise_on_error: false - source "https://global.source" - - source "https://scoped.source/extra" do - gem "depends_on_myrack" - end - - source "https://scoped.source" do - gem "thin" - end - G - expect(err).to eq <<~EOS.strip - Warning: The gem 'myrack' was found in multiple relevant sources. - * rubygems repository https://scoped.source/ - * rubygems repository https://scoped.source/extra/ - You should add this gem to the source block for the source you wish it to be installed from. - EOS - expect(last_command).to be_success - expect(the_bundle).to be_locked - end - end - - context "when an indirect dependency is available from multiple ambiguous sources", bundler: "3" do + context "when an indirect dependency is available from multiple ambiguous sources" do it "raises, suggesting a source block" do build_repo4 do build_gem "depends_on_myrack" do |s| @@ -1645,83 +814,6 @@ RSpec.describe "bundle install with gems on multiple sources" do end end - context "when upgrading a lockfile suffering from dependency confusion" do - before do - build_repo4 do - build_gem "mime-types", "3.0.0" - end - - build_repo2 do - build_gem "capybara", "2.5.0" do |s| - s.add_dependency "mime-types", ">= 1.16" - end - - build_gem "mime-types", "3.3.1" - end - - gemfile <<~G - source "https://gem.repo2" - - gem "capybara", "~> 2.5.0" - - source "https://gem.repo4" do - gem "mime-types", "~> 3.0" - end - G - - lockfile <<-L - GEM - remote: https://gem.repo2/ - remote: https://gem.repo4/ - specs: - capybara (2.5.0) - mime-types (>= 1.16) - mime-types (3.3.1) - - PLATFORMS - #{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" - - checksums = checksums_section_when_enabled 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 - remote: https://gem.repo2/ - specs: - capybara (2.5.0) - mime-types (>= 1.16) - - GEM - remote: https://gem.repo4/ - specs: - mime-types (3.0.0) - - PLATFORMS - #{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 @@ -1775,7 +867,7 @@ RSpec.describe "bundle install with gems on multiple sources" do ruport (= 1.7.0.3)! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -1833,7 +925,7 @@ RSpec.describe "bundle install with gems on multiple sources" do ruport (= 1.7.0.3)! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -1878,7 +970,7 @@ RSpec.describe "bundle install with gems on multiple sources" do pdf-writer (= 1.1.8) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -1946,7 +1038,7 @@ RSpec.describe "bundle install with gems on multiple sources" do foo! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -1987,4 +1079,235 @@ RSpec.describe "bundle install with gems on multiple sources" do expect(lockfile).to eq original_lockfile.gsub("bigdecimal (1.0.0)", "bigdecimal (3.3.1)") end end + + context "when switching a gem with components from rubygems to git source" do + before do + build_repo2 do + build_gem "rails", "7.0.0" do |s| + s.add_dependency "actionpack", "7.0.0" + s.add_dependency "activerecord", "7.0.0" + end + build_gem "actionpack", "7.0.0" + build_gem "activerecord", "7.0.0" + # propshaft also depends on actionpack, creating the conflict + build_gem "propshaft", "1.0.0" do |s| + s.add_dependency "actionpack", ">= 7.0.0" + end + end + + build_git "rails", "7.0.0", path: lib_path("rails") do |s| + s.add_dependency "actionpack", "7.0.0" + s.add_dependency "activerecord", "7.0.0" + end + + build_git "actionpack", "7.0.0", path: lib_path("rails") + build_git "activerecord", "7.0.0", path: lib_path("rails") + + install_gemfile <<-G + source "https://gem.repo2" + gem "rails", "7.0.0" + gem "propshaft" + G + end + + it "moves component gems to the git source in the lockfile" do + expect(lockfile).to include("remote: https://gem.repo2") + expect(lockfile).to include("rails (7.0.0)") + expect(lockfile).to include("actionpack (7.0.0)") + expect(lockfile).to include("activerecord (7.0.0)") + expect(lockfile).to include("propshaft (1.0.0)") + + gemfile <<-G + source "https://gem.repo2" + gem "rails", git: "#{lib_path("rails")}" + gem "propshaft" + G + + bundle "install" + + expect(lockfile).to include("remote: #{lib_path("rails")}") + expect(lockfile).to include("rails (7.0.0)") + expect(lockfile).to include("actionpack (7.0.0)") + expect(lockfile).to include("activerecord (7.0.0)") + + # Component gems should NOT remain in the GEM section + # Extract just the GEM section by splitting on GIT first, then GEM + gem_section = lockfile.split("GEM\n").last.split(/\n(PLATFORMS|DEPENDENCIES)/)[0] + expect(gem_section).not_to include("actionpack (7.0.0)") + expect(gem_section).not_to include("activerecord (7.0.0)") + end + end + + context "when switching a gem with components from rubygems to path source" do + before do + build_repo2 do + build_gem "rails", "7.0.0" do |s| + s.add_dependency "actionpack", "7.0.0" + s.add_dependency "activerecord", "7.0.0" + end + build_gem "actionpack", "7.0.0" + build_gem "activerecord", "7.0.0" + # propshaft also depends on actionpack, creating the conflict + build_gem "propshaft", "1.0.0" do |s| + s.add_dependency "actionpack", ">= 7.0.0" + end + end + + build_lib "rails", "7.0.0", path: lib_path("rails") do |s| + s.add_dependency "actionpack", "7.0.0" + s.add_dependency "activerecord", "7.0.0" + end + + build_lib "actionpack", "7.0.0", path: lib_path("rails") + build_lib "activerecord", "7.0.0", path: lib_path("rails") + + install_gemfile <<-G + source "https://gem.repo2" + gem "rails", "7.0.0" + gem "propshaft" + G + end + + it "moves component gems to the path source in the lockfile" do + expect(lockfile).to include("remote: https://gem.repo2") + expect(lockfile).to include("rails (7.0.0)") + expect(lockfile).to include("actionpack (7.0.0)") + expect(lockfile).to include("activerecord (7.0.0)") + expect(lockfile).to include("propshaft (1.0.0)") + + gemfile <<-G + source "https://gem.repo2" + gem "rails", path: "#{lib_path("rails")}" + gem "propshaft" + G + + bundle "install" + + expect(lockfile).to include("remote: #{lib_path("rails")}") + expect(lockfile).to include("rails (7.0.0)") + expect(lockfile).to include("actionpack (7.0.0)") + expect(lockfile).to include("activerecord (7.0.0)") + + # Component gems should NOT remain in the GEM section + # Extract just the GEM section by splitting appropriately + gem_section = lockfile.split("GEM\n").last.split(/\n(PLATFORMS|DEPENDENCIES)/)[0] + expect(gem_section).not_to include("actionpack (7.0.0)") + expect(gem_section).not_to include("activerecord (7.0.0)") + end + end + + context "when a scoped rubygems source is missing a transitive dependency" do + before do + build_repo2 do + build_gem "fallback_dep", "1.0.0" + build_gem "foo", "1.0.0" + end + + build_repo3 do + build_gem "private_parent", "1.0.0" do |s| + s.add_dependency "fallback_dep" + end + end + + gemfile <<-G + source "https://gem.repo2" + + gem "foo" + + source "https://gem.repo3" do + gem "private_parent", "1.0.0" + end + G + + bundle :install, artifice: "compact_index" + end + + it "falls back to the default rubygems source for that dependency" do + build_repo2 do + build_gem "foo", "2.0.0" + end + + system_gems [] + + bundle "update foo", artifice: "compact_index" + + expect(the_bundle).to include_gems("private_parent 1.0.0", "fallback_dep 1.0.0", "foo 2.0.0") + expect(the_bundle).to include_gems("private_parent 1.0.0", source: "remote3") + expect(the_bundle).to include_gems("fallback_dep 1.0.0", source: "remote2") + end + end + + context "when a path gem has a transitive dependency that does not exist in the path source" do + before do + build_repo2 do + build_gem "missing_dep", "1.0.0" + build_gem "foo", "1.0.0" + end + + build_lib "parent_gem", "1.0.0", path: lib_path("parent_gem") do |s| + s.add_dependency "missing_dep" + end + + gemfile <<-G + source "https://gem.repo2" + + gem "foo" + + gem "parent_gem", path: "#{lib_path("parent_gem")}" + G + + bundle :install, artifice: "compact_index" + end + + it "falls back to the default rubygems source for that dependency when updating" do + build_repo2 do + build_gem "foo", "2.0.0" + end + + system_gems [] + + bundle "update foo", artifice: "compact_index" + + expect(the_bundle).to include_gems("parent_gem 1.0.0", "missing_dep 1.0.0", "foo 2.0.0") + expect(the_bundle).to include_gems("parent_gem 1.0.0", source: "path@#{lib_path("parent_gem")}") + expect(the_bundle).to include_gems("missing_dep 1.0.0", source: "remote2") + end + end + + context "when a git gem has a transitive dependency that does not exist in the git source" do + before do + build_repo2 do + build_gem "missing_dep", "1.0.0" + build_gem "foo", "1.0.0" + end + + build_git "parent_gem", "1.0.0", path: lib_path("parent_gem") do |s| + s.add_dependency "missing_dep" + end + + gemfile <<-G + source "https://gem.repo2" + + gem "foo" + + gem "parent_gem", git: "#{lib_path("parent_gem")}" + G + + bundle :install, artifice: "compact_index" + end + + it "falls back to the default rubygems source for that dependency when updating" do + build_repo2 do + build_gem "foo", "2.0.0" + end + + system_gems [] + + bundle "update foo", artifice: "compact_index" + + expect(the_bundle).to include_gems("parent_gem 1.0.0", "missing_dep 1.0.0", "foo 2.0.0") + expect(the_bundle).to include_gems("parent_gem 1.0.0", source: "git@#{lib_path("parent_gem")}") + expect(the_bundle).to include_gems("missing_dep 1.0.0", source: "remote2") + end + end end diff --git a/spec/bundler/install/gemfile/specific_platform_spec.rb b/spec/bundler/install/gemfile/specific_platform_spec.rb index 04495e2b72..97b1d233bf 100644 --- a/spec/bundler/install/gemfile/specific_platform_spec.rb +++ b/spec/bundler/install/gemfile/specific_platform_spec.rb @@ -11,7 +11,7 @@ RSpec.describe "bundle install with specific platforms" 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 include(pl("x86_64-darwin-15")) + expect(the_bundle.locked_platforms).to include("universal-darwin") 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 include( "google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin" @@ -54,7 +54,7 @@ RSpec.describe "bundle install with specific platforms" do sass-embedded BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install --verbose" @@ -70,7 +70,7 @@ RSpec.describe "bundle install with specific platforms" do setup_multiplatform_gem # Consistent location to install and look for gems - bundle "config set --local path vendor/bundle" + bundle_config "path vendor/bundle" install_gemfile(google_protobuf) @@ -88,11 +88,11 @@ RSpec.describe "bundle install with specific platforms" do google-protobuf BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L - # force strict usage of the lock file by setting frozen mode - bundle "config set --local frozen true" + # force strict usage of the lockfile by setting frozen mode + bundle_config "frozen true" # 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") @@ -104,7 +104,7 @@ RSpec.describe "bundle install with specific platforms" do setup_multiplatform_gem # Consistent location to install and look for gems - bundle "config set --local path vendor/bundle" + bundle_config "path vendor/bundle" gemfile google_protobuf @@ -126,7 +126,7 @@ RSpec.describe "bundle install with specific platforms" do google-protobuf #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "update" @@ -151,12 +151,17 @@ RSpec.describe "bundle install with specific platforms" do google-protobuf #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end context "when running on a legacy lockfile locked only to ruby" do + # Exercises the legacy lockfile path (use_exact_resolved_specifications? = false) + # because most_specific_locked_platform is ruby, matching the generic platform. + # Key insight: when target (arm64-darwin-22) != platform (ruby), the code tries + # both platforms before falling back, preserving lockfile integrity. + around do |example| build_repo4 do build_gem "nokogiri", "1.3.10" @@ -185,20 +190,76 @@ RSpec.describe "bundle install with specific platforms" do nokogiri BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L simulate_platform "arm64-darwin-22", &example end it "still installs the generic ruby variant if necessary" do - bundle "install --verbose" - expect(out).to include("Installing nokogiri 1.3.10") + bundle "install" + expect(the_bundle).to include_gem("nokogiri 1.3.10") + expect(the_bundle).not_to include_gem("nokogiri 1.3.10 arm64-darwin") end it "still installs the generic ruby variant if necessary, even in frozen mode" do - bundle "install --verbose", env: { "BUNDLE_FROZEN" => "true" } - expect(out).to include("Installing nokogiri 1.3.10") + bundle "install", env: { "BUNDLE_FROZEN" => "true" } + expect(the_bundle).to include_gem("nokogiri 1.3.10") + expect(the_bundle).not_to include_gem("nokogiri 1.3.10 arm64-darwin") + end + end + + context "when platform-specific gem has incompatible required_ruby_version" do + # Key insight: candidate_platforms tries [target, platform, ruby] in order. + # Ruby platform is last since it requires compilation, but works when + # precompiled gems are incompatible with the current Ruby version. + # + # Note: This fix requires the lockfile to include both ruby and platform- + # specific variants (typical after `bundle lock --add-platform`). If the + # lockfile only has platform-specific gems, frozen mode cannot help because + # Bundler.setup would still expect the locked (incompatible) gem. + + # Exercises the exact spec path (use_exact_resolved_specifications? = true) + # because lockfile has platform-specific entry as most_specific_locked_platform + it "falls back to ruby platform in frozen mode when lockfile includes both variants" do + build_repo4 do + build_gem "nokogiri", "1.18.10" + build_gem "nokogiri", "1.18.10" do |s| + s.platform = "x86_64-linux" + s.required_ruby_version = "< #{Gem.ruby_version}" + end + end + + gemfile <<~G + source "https://gem.repo4" + + gem "nokogiri" + G + + # Lockfile has both ruby and platform-specific gem (typical after `bundle lock --add-platform`) + lockfile <<-L + GEM + remote: https://gem.repo4/ + specs: + nokogiri (1.18.10) + nokogiri (1.18.10-x86_64-linux) + + PLATFORMS + ruby + x86_64-linux + + DEPENDENCIES + nokogiri + + BUNDLED WITH + #{Bundler::VERSION} + L + + simulate_platform "x86_64-linux" do + bundle "install", env: { "BUNDLE_FROZEN" => "true" } + expect(the_bundle).to include_gem("nokogiri 1.18.10") + expect(the_bundle).not_to include_gem("nokogiri 1.18.10 x86_64-linux") + end end end @@ -214,7 +275,7 @@ RSpec.describe "bundle install with specific platforms" do end # Consistent location to install and look for gems - bundle "config set --local path vendor/bundle" + bundle_config "path vendor/bundle" gemfile <<-G source "https://gem.repo2" @@ -235,7 +296,7 @@ RSpec.describe "bundle install with specific platforms" do libv8 BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install --verbose" @@ -273,7 +334,7 @@ RSpec.describe "bundle install with specific platforms" do grpc BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install --verbose", artifice: "compact_index_precompiled_before" @@ -298,7 +359,7 @@ RSpec.describe "bundle install with specific platforms" do simulate_platform "x86_64-darwin-15" do setup_multiplatform_gem gemfile(google_protobuf) - bundle "config set --local cache_all_platforms true" + bundle_config "cache_all_platforms true" bundle "cache" expect(cached_gem("google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin")).to exist @@ -333,10 +394,9 @@ RSpec.describe "bundle install with specific platforms" do pg_array_parser! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L - bundle "config set --local cache_all true" bundle "cache --all-platforms" expect(err).to be_empty @@ -351,7 +411,7 @@ 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 include(pl("x86_64-darwin-15")) + expect(the_bundle.locked_platforms).to include("universal-darwin") 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 include("CFPropertyList-1.0", "facter-2.4.6-universal-darwin") @@ -367,12 +427,12 @@ RSpec.describe "bundle install with specific platforms" do simulate_platform "x86_64-darwin-15" do setup_multiplatform_gem install_gemfile(google_protobuf) - bundle "lock --add-platform=#{x64_mingw32}" + bundle "lock --add-platform=x64-mingw-ucrt" - expect(the_bundle.locked_gems.platforms).to include(x64_mingw32, pl("x86_64-darwin-15")) + expect(the_bundle.locked_platforms).to include("x64-mingw-ucrt", "universal-darwin") 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 + google-protobuf-3.0.0.alpha.5.0.5.1-x64-mingw-ucrt ]) end end @@ -381,9 +441,9 @@ RSpec.describe "bundle install with specific platforms" do simulate_platform "x86_64-darwin-15" do setup_multiplatform_gem install_gemfile(google_protobuf) - bundle "lock --add-platform=#{java}" + bundle "lock --add-platform=java" - expect(the_bundle.locked_gems.platforms).to include(java, pl("x86_64-darwin-15")) + expect(the_bundle.locked_platforms).to include("java", "universal-darwin") 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" @@ -392,7 +452,23 @@ RSpec.describe "bundle install with specific platforms" do end end - it "installs sorbet-static, which does not provide a pure ruby variant, just fine", :truffleruby do + it "installs sorbet-static, which does not provide a pure ruby variant, in absence of a lockfile, 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 = Bundler.local_platform } + end + + gemfile <<~G + source "https://gem.repo2" + + gem "sorbet-static", "0.5.6403" + G + + bundle "install --verbose" + end + + it "installs sorbet-static, which does not provide a pure ruby variant, in presence of a lockfile, just fine", :truffleruby do skip "does not apply to Windows" if Gem.win_platform? build_repo2 do @@ -418,7 +494,7 @@ RSpec.describe "bundle install with specific platforms" do sorbet-static (= 0.5.6403) BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install --verbose" @@ -459,6 +535,41 @@ RSpec.describe "bundle install with specific platforms" do expect(err).to include(error_message).once end + it "shows a platform mismatch hint when the current platform is not in the lockfile's platforms" do + build_repo4 do + build_gem("sorbet-static", "0.5.6433") {|s| s.platform = "x86_64-linux-musl" } + end + + gemfile <<~G + source "https://gem.repo4" + + gem "sorbet-static", "0.5.6433" + G + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + sorbet-static (0.5.6433-x86_64-linux-musl) + + PLATFORMS + x86_64-linux-musl + + DEPENDENCIES + sorbet-static (= 0.5.6433) + + BUNDLED WITH + #{Bundler::VERSION} + L + + simulate_platform "x86_64-linux" do + bundle "install", raise_on_error: false + end + + expect(err).to include("Your current platform (x86_64-linux) is not included in the lockfile's platforms (x86_64-linux-musl)") + expect(err).to include("bundle lock --add-platform x86_64-linux") + 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" } @@ -565,7 +676,7 @@ RSpec.describe "bundle install with specific platforms" do sorbet-static-and-runtime BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "update" @@ -596,7 +707,7 @@ RSpec.describe "bundle install with specific platforms" do sorbet-static-and-runtime #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -648,7 +759,7 @@ RSpec.describe "bundle install with specific platforms" do sorbet-static #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L simulate_platform "x86_64-darwin-22" do @@ -670,7 +781,7 @@ RSpec.describe "bundle install with specific platforms" do sorbet-static #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -717,7 +828,7 @@ RSpec.describe "bundle install with specific platforms" do sorbet-static-and-runtime BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "update" @@ -748,10 +859,84 @@ RSpec.describe "bundle install with specific platforms" do sorbet-static-and-runtime #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end + it "automatically fixes the lockfile when adding a gem that introduces dependencies with no ruby platform variants transitively" do + simulate_platform "x86_64-linux" do + build_repo4 do + build_gem "nokogiri", "1.18.2" + + build_gem "nokogiri", "1.18.2" do |s| + s.platform = "x86_64-linux" + end + + build_gem("sorbet", "0.5.11835") do |s| + s.add_dependency "sorbet-static", "= 0.5.11835" + end + + build_gem "sorbet-static", "0.5.11835" do |s| + s.platform = "x86_64-linux" + end + end + + gemfile <<~G + source "https://gem.repo4" + + gem "nokogiri" + gem "sorbet" + G + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + nokogiri (1.18.2) + nokogiri (1.18.2-x86_64-linux) + + PLATFORMS + ruby + x86_64-linux + + DEPENDENCIES + nokogiri + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "lock" + + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "nokogiri", "1.18.2", "x86_64-linux" + c.checksum gem_repo4, "sorbet", "0.5.11835" + c.checksum gem_repo4, "sorbet-static", "0.5.11835", "x86_64-linux" + end + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + nokogiri (1.18.2) + nokogiri (1.18.2-x86_64-linux) + sorbet (0.5.11835) + sorbet-static (= 0.5.11835) + sorbet-static (0.5.11835-x86_64-linux) + + PLATFORMS + x86_64-linux + + DEPENDENCIES + nokogiri + sorbet + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + 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 @@ -792,7 +977,7 @@ RSpec.describe "bundle install with specific platforms" do sorbet-static BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "update" @@ -817,7 +1002,7 @@ RSpec.describe "bundle install with specific platforms" do sorbet-static #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -860,7 +1045,7 @@ RSpec.describe "bundle install with specific platforms" do sorbet-static (= 0.5.10549) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install" @@ -879,7 +1064,7 @@ RSpec.describe "bundle install with specific platforms" do sorbet-static (= 0.5.10549) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -916,7 +1101,7 @@ RSpec.describe "bundle install with specific platforms" do ibandit (~> 0.7.0) BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "lock --update i18n" @@ -936,7 +1121,7 @@ RSpec.describe "bundle install with specific platforms" do ibandit (~> 0.7.0) BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -977,7 +1162,7 @@ RSpec.describe "bundle install with specific platforms" do tzinfo (~> 1.2) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L lockfile original_lockfile @@ -1000,7 +1185,7 @@ RSpec.describe "bundle install with specific platforms" do gem "nokogiri" - gem "tzinfo", "~> 1.2", platforms: %i[mingw mswin x64_mingw jruby] + gem "tzinfo", "~> 1.2", platforms: %i[windows jruby] G checksums = checksums_section_when_enabled do |c| @@ -1021,7 +1206,7 @@ RSpec.describe "bundle install with specific platforms" do tzinfo (~> 1.2) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L lockfile original_lockfile @@ -1064,7 +1249,7 @@ RSpec.describe "bundle install with specific platforms" do concurrent-ruby #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "lock" @@ -1077,14 +1262,14 @@ RSpec.describe "bundle install with specific platforms" do myrack (3.0.7) PLATFORMS - #{lockfile_platforms("ruby", generic_local_platform, defaults: [])} + #{lockfile_platforms(generic_default_locked_platform || local_platform, defaults: ["ruby"])} DEPENDENCIES concurrent-ruby myrack #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -1115,7 +1300,7 @@ RSpec.describe "bundle install with specific platforms" do my-precompiled-gem BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle :install @@ -1160,7 +1345,7 @@ RSpec.describe "bundle install with specific platforms" do nokogiri (= 1.14.0) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle :install @@ -1184,7 +1369,7 @@ RSpec.describe "bundle install with specific platforms" do nokogiri (= 1.14.0) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -1218,7 +1403,7 @@ RSpec.describe "bundle install with specific platforms" do DEPENDENCIES BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "lock" @@ -1238,7 +1423,7 @@ RSpec.describe "bundle install with specific platforms" do sorbet BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -1254,7 +1439,7 @@ RSpec.describe "bundle install with specific platforms" do s.platform = "arm-linux" end build_gem "nokogiri", "1.14.0" do |s| - s.platform = "x64-mingw32" + s.platform = "x64-mingw-ucrt" end build_gem "nokogiri", "1.14.0" do |s| s.platform = "java" @@ -1300,7 +1485,7 @@ RSpec.describe "bundle install with specific platforms" do nokogiri #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L gemfile <<~G @@ -1337,12 +1522,12 @@ RSpec.describe "bundle install with specific platforms" do sorbet-static #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{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 + 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" do build_repo4 do build_gem "nokogiri", "1.15.5" @@ -1392,7 +1577,7 @@ RSpec.describe "bundle install with specific platforms" do sass-embedded #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -1434,7 +1619,7 @@ RSpec.describe "bundle install with specific platforms" do nokogiri #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -1481,14 +1666,14 @@ RSpec.describe "bundle install with specific platforms" do rcee_precompiled (= 0.5.0) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end end end - it "adds current musl platform, when there are also gnu variants", rubygems: ">= 3.3.21" do + it "adds current musl platform, when there are also gnu variants" do build_repo4 do build_gem "rcee_precompiled", "0.5.0" do |s| s.platform = "x86_64-linux-gnu" @@ -1528,7 +1713,7 @@ RSpec.describe "bundle install with specific platforms" do rcee_precompiled (= 0.5.0) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -1566,7 +1751,7 @@ RSpec.describe "bundle install with specific platforms" do rcee_precompiled (= 0.5.0) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -1595,7 +1780,7 @@ RSpec.describe "bundle install with specific platforms" do nokogiri! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L simulate_platform "arm64-darwin-23" do @@ -1638,7 +1823,7 @@ RSpec.describe "bundle install with specific platforms" do nokogiri BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L lockfile original_lockfile @@ -1689,7 +1874,7 @@ RSpec.describe "bundle install with specific platforms" do nokogiri BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L lockfile original_lockfile @@ -1731,7 +1916,7 @@ RSpec.describe "bundle install with specific platforms" do google-protobuf (~> 3.0) BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L lockfile original_lockfile @@ -1749,11 +1934,11 @@ RSpec.describe "bundle install with specific platforms" do build_repo2 do build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "x86_64-linux" } - build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "x64-mingw32" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "x64-mingw-ucrt" } build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "universal-darwin" } build_gem("google-protobuf", "3.0.0.alpha.5.0.5") {|s| s.platform = "x86_64-linux" } - build_gem("google-protobuf", "3.0.0.alpha.5.0.5") {|s| s.platform = "x64-mingw32" } + build_gem("google-protobuf", "3.0.0.alpha.5.0.5") {|s| s.platform = "x64-mingw-ucrt" } build_gem("google-protobuf", "3.0.0.alpha.5.0.5") build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "universal-darwin" } diff --git a/spec/bundler/install/gemfile_spec.rb b/spec/bundler/install/gemfile_spec.rb index 96ed174e9b..83875a3d0e 100644 --- a/spec/bundler/install/gemfile_spec.rb +++ b/spec/bundler/install/gemfile_spec.rb @@ -27,6 +27,35 @@ RSpec.describe "bundle install" do ENV["BUNDLE_GEMFILE"] = "NotGemfile" expect(the_bundle).to include_gems "myrack 1.0.0" end + + it "respects lockfile and BUNDLE_LOCKFILE" do + gemfile bundled_app("NotGemfile"), <<-G + lockfile "ReallyNotGemfile.lock" + source "https://gem.repo1" + gem 'myrack' + G + + bundle :install, gemfile: bundled_app("NotGemfile") + + ENV["BUNDLE_GEMFILE"] = "NotGemfile" + ENV["BUNDLE_LOCKFILE"] = "ReallyNotGemfile.lock" + expect(the_bundle).to include_gems "myrack 1.0.0" + end + + it "respects BUNDLE_LOCKFILE during bundle install" do + ENV["BUNDLE_LOCKFILE"] = "ReallyNotGemfile.lock" + + gemfile bundled_app("NotGemfile"), <<-G + source "https://gem.repo1" + gem 'myrack' + G + + bundle :install, gemfile: bundled_app("NotGemfile") + expect(bundled_app("ReallyNotGemfile.lock")).to exist + + ENV["BUNDLE_GEMFILE"] = "NotGemfile" + expect(the_bundle).to include_gems "myrack 1.0.0" + end end context "with gemfile set via config" do @@ -36,7 +65,7 @@ RSpec.describe "bundle install" do gem 'myrack' G - bundle "config set --local gemfile #{bundled_app("NotGemfile")}" + bundle_config "gemfile #{bundled_app("NotGemfile")}" end it "uses the gemfile to install" do bundle "install" @@ -53,17 +82,37 @@ RSpec.describe "bundle install" do end end - context "with deprecated features" do - it "reports that lib is an invalid option" do - gemfile <<-G - source "https://gem.repo1" + it "reports that lib is an invalid option" do + gemfile <<-G + source "https://gem.repo1" - gem "myrack", :lib => "myrack" - G + gem "myrack", :lib => "myrack" + G - bundle :install, raise_on_error: false - expect(err).to match(/You passed :lib as an option for gem 'myrack', but it is invalid/) - end + bundle :install, raise_on_error: false + expect(err).to match(/You passed :lib as an option for gem 'myrack', but it is invalid/) + end + + it "reports that type is an invalid option" do + gemfile <<-G + source "https://gem.repo1" + + gem "myrack", :type => "development" + G + + bundle :install, raise_on_error: false + expect(err).to match(/You passed :type as an option for gem 'myrack', but it is invalid/) + end + + it "reports that gemfile is an invalid option" do + gemfile <<-G + source "https://gem.repo1" + + gem "myrack", :gemfile => "foo" + G + + bundle :install, raise_on_error: false + expect(err).to match(/You passed :gemfile as an option for gem 'myrack', but it is invalid/) end context "when an internal error happens" do diff --git a/spec/bundler/install/gems/compact_index_spec.rb b/spec/bundler/install/gems/compact_index_spec.rb index cf661ee284..9db73b84b5 100644 --- a/spec/bundler/install/gems/compact_index_spec.rb +++ b/spec/bundler/install/gems/compact_index_spec.rb @@ -95,8 +95,7 @@ RSpec.describe "compact index api" do G bundle :install, artifice: "compact_index" - bundle "config set --local deployment true" - bundle "config set --local path vendor/bundle" + bundle_config "deployment true" bundle :install, artifice: "compact_index" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "myrack 1.0.0" @@ -133,7 +132,7 @@ RSpec.describe "compact index api" do bundle :install, artifice: "compact_index" - bundle "config set --local deployment true" + bundle_config "deployment true" bundle :install, artifice: "compact_index" expect(the_bundle).to include_gems("rails 2.3.2") @@ -147,7 +146,7 @@ RSpec.describe "compact index api" do G bundle "install", artifice: "compact_index" - bundle "config set --local deployment true" + bundle_config "deployment true" bundle :install, artifice: "compact_index" expect(the_bundle).to include_gems("foo 1.0") @@ -183,8 +182,7 @@ RSpec.describe "compact index api" do gem "myrack" G - versions = Pathname.new(Bundler.rubygems.user_home).join( - ".bundle", "cache", "compact_index", + versions = compact_index_cache_path.join( "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions" ) versions.dirname.mkpath @@ -304,7 +302,7 @@ RSpec.describe "compact index api" do end system_gems %w[myrack-1.0.0 thin-1.0 net_a-1.0], gem_repo: gem_repo2 - bundle "config set --local path.system true" + bundle_config "path.system true" ENV["BUNDLER_SPEC_ALL_REQUESTS"] = <<~EOS.strip #{source_uri}/versions #{source_uri}/info/myrack @@ -315,33 +313,15 @@ RSpec.describe "compact index api" do gem "myrack" G - expect(last_command.stdboth).not_to include "Double checking" + expect(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" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" end - FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] - end - - gemfile <<-G - source "#{source_uri}" - source "#{source_uri}/extra" - gem "back_deps" - G - - bundle :install, artifice: "compact_index_extra" - expect(the_bundle).to include_gems "back_deps 1.0", "foo 1.0" - end - - it "fetches again when more dependencies are found in subsequent sources with source blocks" do - build_repo2 do - build_gem "back_deps" do |s| - s.add_dependency "foo" - end - FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] + FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")] end install_gemfile <<-G, artifice: "compact_index_extra", verbose: true @@ -377,11 +357,13 @@ RSpec.describe "compact index api" do expect(the_bundle).to include_gems "myrack 1.2" end - 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 - # repo and installs it. + it "resolves indirect dependencies to the most scoped source that includes them" 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 tries to + # use the version in the most scoped source, even if not compatible, and + # gives a resolution error build_repo4 do build_gem "activesupport", "1.2.0" build_gem "somegem", "1.0.0" do |s| @@ -391,14 +373,14 @@ RSpec.describe "compact index api" do gemfile <<-G source "#{source_uri}" - source "#{source_uri}/extra" - gem 'somegem', '1.0.0' + source "#{source_uri}/extra" do + gem 'somegem', '1.0.0' + end G - bundle :install, artifice: "compact_index_extra_api" + bundle :install, artifice: "compact_index_extra_api", raise_on_error: false - expect(the_bundle).to include_gems "somegem 1.0.0" - expect(the_bundle).to include_gems "activesupport 1.2.3" + expect(err).to include("Could not find compatible versions") end it "prints API output properly with back deps" do @@ -406,7 +388,7 @@ RSpec.describe "compact index api" do build_gem "back_deps" do |s| s.add_dependency "foo" end - FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] + FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")] end gemfile <<-G @@ -429,7 +411,7 @@ RSpec.describe "compact index api" do end build_gem "missing" - FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] + FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")] end install_gemfile <<-G, artifice: "compact_index_extra_missing" @@ -449,7 +431,7 @@ RSpec.describe "compact index api" do end build_gem "missing" - FileUtils.rm_rf Dir[gem_repo4("gems/foo-*.gem")] + FileUtils.rm_r Dir[gem_repo4("gems/foo-*.gem")] end install_gemfile <<-G, artifice: "compact_index_extra_api_missing" @@ -473,32 +455,12 @@ RSpec.describe "compact index api" do 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" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" end - FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] - end - - gemfile <<-G - source "#{source_uri}" - source "#{source_uri}/extra" - gem "back_deps" - G - - bundle :install, artifice: "compact_index_extra" - bundle "config --set local deployment true" - bundle :install, artifice: "compact_index_extra" - expect(the_bundle).to include_gems "back_deps 1.0" - end - - it "fetches again when more dependencies are found in subsequent sources using deployment mode with blocks" do - build_repo2 do - build_gem "back_deps" do |s| - s.add_dependency "foo" - end - FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] + FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")] end gemfile <<-G @@ -509,7 +471,7 @@ RSpec.describe "compact index api" do G bundle :install, artifice: "compact_index_extra" - bundle "config set --local deployment true" + bundle_config "deployment true" bundle :install, artifice: "compact_index_extra" expect(the_bundle).to include_gems "back_deps 1.0" end @@ -531,40 +493,6 @@ RSpec.describe "compact index api" do expect(out).to include("Fetching gem metadata from #{source_uri}") end - it "installs the binstubs", bundler: "< 3" do - gemfile <<-G - source "#{source_uri}" - gem "myrack" - G - - bundle "install --binstubs", artifice: "compact_index" - - gembin "myrackup" - expect(out).to eq("1.0.0") - end - - it "installs the bins when using --path and uses autoclean", bundler: "< 3" do - gemfile <<-G - source "#{source_uri}" - gem "myrack" - G - - bundle "install --path vendor/bundle", artifice: "compact_index" - - expect(vendored_gems("bin/myrackup")).to exist - end - - it "installs the bins when using --path and uses bundle clean", bundler: "< 3" do - gemfile <<-G - source "#{source_uri}" - gem "myrack" - G - - bundle "install --path vendor/bundle --no-clean", artifice: "compact_index" - - expect(vendored_gems("bin/myrackup")).to exist - end - it "prints post_install_messages" do gemfile <<-G source "#{source_uri}" @@ -619,19 +547,6 @@ RSpec.describe "compact index api" do expect(the_bundle).to include_gems "myrack 1.0.0" end - 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 "myrack" - G - - bundle :install, artifice: "compact_index_basic_authentication" - expect(err).to include("Warning: the gem 'myrack' was found in multiple sources.") - expect(err).not_to include("#{user}:#{password}") - expect(the_bundle).to include_gems "myrack 1.0.0" - end - it "does not pass the user / password to different hosts on redirect" do gemfile <<-G source "#{basic_auth_source_uri}" @@ -744,7 +659,7 @@ RSpec.describe "compact index api" do gem "myrack" G - bundle :install, env: { "RUBYOPT" => opt_add("-I#{bundled_app("broken_ssl")}", ENV["RUBYOPT"]) }, raise_on_error: false, artifice: nil + bundle :install, env: { "RUBYOPT" => "-I#{bundled_app("broken_ssl")}" }, raise_on_error: false, artifice: nil expect(err).to include("recompile Ruby").and include("cannot load such file") end end @@ -783,14 +698,13 @@ RSpec.describe "compact index api" do bundle :install, artifice: "compact_index_forbidden" ensure - home(".gemrc").rmtree + FileUtils.rm_rf home(".gemrc") end end end it "performs update with etag not-modified" do - versions_etag = Pathname.new(Bundler.rubygems.user_home).join( - ".bundle", "cache", "compact_index", + versions_etag = compact_index_cache_path.join( "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions.etag" ) expect(versions_etag.file?).to eq(false) @@ -833,8 +747,7 @@ RSpec.describe "compact index api" do gem 'myrack', '1.0.0' G - versions = Pathname.new(Bundler.rubygems.user_home).join( - ".bundle", "cache", "compact_index", + versions = compact_index_cache_path.join( "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions" ) # Modify the cached file. The ranged request will be based on this but, @@ -857,7 +770,7 @@ RSpec.describe "compact index api" do gem 'myrack', '0.9.1' G - update_repo4 do + build_repo4 do build_gem "myrack", "1.0.0" end @@ -876,8 +789,7 @@ RSpec.describe "compact index api" do G # Create a partial cache versions file - versions = Pathname.new(Bundler.rubygems.user_home).join( - ".bundle", "cache", "compact_index", + versions = compact_index_cache_path.join( "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions" ) versions.dirname.mkpath @@ -899,7 +811,7 @@ RSpec.describe "compact index api" do gem 'myrack', '0.9.1' G - update_repo4 do + build_repo4 do build_gem "myrack", "1.0.0" end @@ -921,7 +833,7 @@ RSpec.describe "compact index api" do gem 'myrack', '0.9.1' G - update_repo4 do + build_repo4 do build_gem "myrack", "1.0.0" end @@ -941,7 +853,7 @@ RSpec.describe "compact index api" do bundle :install, artifice: "compact_index" - cache_path = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index", "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5") + cache_path = compact_index_cache_path.join("localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5") # We must remove the etag so that we don't ignore the range and get a 304 Not Modified. myrack_info_etag_path = File.join(cache_path, "info-etags", "myrack-92f3313ce5721296f14445c3a6b9c073") @@ -993,7 +905,7 @@ RSpec.describe "compact index api" do DEPENDENCIES #{checksums_section} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -1015,11 +927,7 @@ RSpec.describe "compact index api" do gem "myrack" G - gem_path = if Bundler.feature_flag.global_gem_cache? - default_cache_path.dirname.join("cache", "gems", "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "myrack-1.0.0.gem") - else - default_cache_path.dirname.join("myrack-1.0.0.gem") - end + gem_path = default_cache_path.dirname.join("myrack-1.0.0.gem") expect(exitstatus).to eq(37) expect(err).to eq <<~E.strip @@ -1048,7 +956,7 @@ RSpec.describe "compact index api" do end it "does not raise when disable_checksum_validation is set" do - bundle "config set disable_checksum_validation true" + bundle_config "disable_checksum_validation true" install_gemfile <<-G, artifice: "compact_index_wrong_gem_checksum" source "#{source_uri}" gem "myrack" @@ -1089,9 +997,16 @@ Running `bundle update rails` should fix the problem. gem "activemerchant" end G - gem_command "uninstall activemerchant" + uninstall_gem("activemerchant") 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 + + it "handles an API that does not provide checksums info (undocumented, support may get removed)" do + install_gemfile <<-G, artifice: "compact_index_no_checksums" + source "https://gem.repo1" + gem "rake" + G + end end diff --git a/spec/bundler/install/gems/dependency_api_fallback_spec.rb b/spec/bundler/install/gems/dependency_api_fallback_spec.rb index 42239311e2..c7b0c537e4 100644 --- a/spec/bundler/install/gems/dependency_api_fallback_spec.rb +++ b/spec/bundler/install/gems/dependency_api_fallback_spec.rb @@ -3,44 +3,16 @@ RSpec.describe "gemcutter's dependency API" do context "when Gemcutter API takes too long to respond" do before do - require_rack - - port = find_unused_port - @server_uri = "http://127.0.0.1:#{port}" - - require_relative "../../support/artifice/endpoint_timeout" - require_relative "../../support/silent_logger" - - require "rackup/server" - - @t = Thread.new do - server = Rackup::Server.start(app: EndpointTimeout, - Host: "0.0.0.0", - Port: port, - server: "webrick", - AccessLog: [], - Logger: Spec::SilentLogger.new) - server.start - end - @t.run - - wait_for_server("127.0.0.1", port) - bundle "config set timeout 1" - end - - after do - Artifice.deactivate - @t.kill - @t.join + bundle_config "timeout 1" end it "times out and falls back on the modern index" do - install_gemfile <<-G, artifice: nil, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s } - source "#{@server_uri}" + install_gemfile <<-G, artifice: "endpoint_timeout" + source "https://gem.repo1" gem "myrack" G - expect(out).to include("Fetching source index from #{@server_uri}/") + expect(out).to include("Fetching source index from https://gem.repo1/") expect(the_bundle).to include_gems "myrack 1.0.0" end end diff --git a/spec/bundler/install/gems/dependency_api_spec.rb b/spec/bundler/install/gems/dependency_api_spec.rb index 4b561064d9..32a1b98b6d 100644 --- a/spec/bundler/install/gems/dependency_api_spec.rb +++ b/spec/bundler/install/gems/dependency_api_spec.rb @@ -60,8 +60,7 @@ RSpec.describe "gemcutter's dependency API" do G bundle :install, artifice: "endpoint" - bundle "config set --local deployment true" - bundle "config set --local path vendor/bundle" + bundle_config "deployment true" bundle :install, artifice: "endpoint" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems "myrack 1.0.0" @@ -98,7 +97,7 @@ RSpec.describe "gemcutter's dependency API" do bundle :install, artifice: "endpoint" - bundle "config set --local deployment true" + bundle_config "deployment true" bundle :install, artifice: "endpoint" expect(the_bundle).to include_gems("rails 2.3.2") @@ -112,14 +111,14 @@ RSpec.describe "gemcutter's dependency API" do G bundle "install", artifice: "endpoint" - bundle "config set --local deployment true" + bundle_config "deployment true" 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 x86_mswin32 do + simulate_platform "x86-mswin32" do build_repo2 do # The rcov gem is platform mswin32, but has no arch build_gem "rcov" do |s| @@ -255,30 +254,12 @@ RSpec.describe "gemcutter's dependency API" do 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" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" end - FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] - end - - gemfile <<-G - source "#{source_uri}" - source "#{source_uri}/extra" - gem "back_deps" - G - - bundle :install, artifice: "endpoint_extra" - expect(the_bundle).to include_gems "back_deps 1.0", "foo 1.0" - end - - it "fetches again when more dependencies are found in subsequent sources using blocks" do - build_repo2 do - build_gem "back_deps" do |s| - s.add_dependency "foo" - end - FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] + FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")] end gemfile <<-G @@ -314,11 +295,13 @@ RSpec.describe "gemcutter's dependency API" do expect(the_bundle).to include_gems "myrack 1.2" end - 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 - # repo and installs it. + it "resolves indirect dependencies to the most scoped source that includes them" 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 tries to + # use the version in the most scoped source, even if not compatible, and + # gives a resolution error build_repo4 do build_gem "activesupport", "1.2.0" build_gem "somegem", "1.0.0" do |s| @@ -328,14 +311,14 @@ RSpec.describe "gemcutter's dependency API" do gemfile <<-G source "#{source_uri}" - source "#{source_uri}/extra" - gem 'somegem', '1.0.0' + source "#{source_uri}/extra" do + gem 'somegem', '1.0.0' + end G - bundle :install, artifice: "endpoint_extra_api" + bundle :install, artifice: "compact_index_extra_api", raise_on_error: false - expect(the_bundle).to include_gems "somegem 1.0.0" - expect(the_bundle).to include_gems "activesupport 1.2.3" + expect(err).to include("Could not find compatible versions") end it "prints API output properly with back deps" do @@ -343,7 +326,7 @@ RSpec.describe "gemcutter's dependency API" do build_gem "back_deps" do |s| s.add_dependency "foo" end - FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] + FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")] end gemfile <<-G @@ -359,33 +342,14 @@ RSpec.describe "gemcutter's dependency API" do expect(out).to include("Fetching source index from http://localgemserver.test/extra") end - 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" - end - build_gem "missing" - - FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] - end - - install_gemfile <<-G, artifice: "endpoint_extra_missing" - source "#{source_uri}" - source "#{source_uri}/extra" - gem "back_deps" - G - - expect(the_bundle).to include_gems "back_deps 1.0" - end - - it "does not fetch every spec when doing back deps using blocks" 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" end build_gem "missing" - FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] + FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")] end install_gemfile <<-G, artifice: "endpoint_extra_missing" @@ -398,32 +362,12 @@ RSpec.describe "gemcutter's dependency API" do expect(the_bundle).to include_gems "back_deps 1.0" end - 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" - end - FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] - end - - gemfile <<-G - source "#{source_uri}" - source "#{source_uri}/extra" - gem "back_deps" - G - - bundle :install, artifice: "endpoint_extra" - bundle "config set --local deployment true" - bundle :install, artifice: "endpoint_extra" - expect(the_bundle).to include_gems "back_deps 1.0" - end - - it "fetches again when more dependencies are found in subsequent sources using deployment mode with blocks" do + it "fetches again when more dependencies are found in subsequent sources using deployment mode" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" end - FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] + FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")] end gemfile <<-G @@ -434,7 +378,7 @@ RSpec.describe "gemcutter's dependency API" do G bundle :install, artifice: "endpoint_extra" - bundle "config set --local deployment true" + bundle_config "deployment true" bundle "install", artifice: "endpoint_extra" expect(the_bundle).to include_gems "back_deps 1.0" end @@ -472,40 +416,6 @@ RSpec.describe "gemcutter's dependency API" do expect(out).to include("Fetching gem metadata from #{source_uri}") end - it "installs the binstubs", bundler: "< 3" do - gemfile <<-G - source "#{source_uri}" - gem "myrack" - G - - bundle "install --binstubs", artifice: "endpoint" - - gembin "myrackup" - expect(out).to eq("1.0.0") - end - - it "installs the bins when using --path and uses autoclean", bundler: "< 3" do - gemfile <<-G - source "#{source_uri}" - gem "myrack" - G - - bundle "install --path vendor/bundle", artifice: "endpoint" - - expect(vendored_gems("bin/myrackup")).to exist - end - - it "installs the bins when using --path and uses bundle clean", bundler: "< 3" do - gemfile <<-G - source "#{source_uri}" - gem "myrack" - G - - bundle "install --path vendor/bundle --no-clean", artifice: "endpoint" - - expect(vendored_gems("bin/myrackup")).to exist - end - it "prints post_install_messages" do gemfile <<-G source "#{source_uri}" @@ -581,19 +491,6 @@ RSpec.describe "gemcutter's dependency API" do expect(out).not_to include("#{user}:#{password}") end - 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 "myrack" - G - - bundle :install, artifice: "endpoint_basic_authentication" - expect(err).to include("Warning: the gem 'myrack' was found in multiple sources.") - expect(err).not_to include("#{user}:#{password}") - expect(the_bundle).to include_gems "myrack 1.0.0" - end - it "does not pass the user / password to different hosts on redirect" do gemfile <<-G source "#{basic_auth_source_uri}" @@ -713,7 +610,7 @@ RSpec.describe "gemcutter's dependency API" do gem "myrack" G - bundle :install, artifice: "fail", env: { "RUBYOPT" => opt_add("-I#{bundled_app("broken_ssl")}", ENV["RUBYOPT"]) }, raise_on_error: false + bundle :install, artifice: "fail", env: { "RUBYOPT" => "-I#{bundled_app("broken_ssl")}" }, raise_on_error: false expect(err).to include("recompile Ruby").and include("cannot load such file") end end @@ -752,7 +649,7 @@ RSpec.describe "gemcutter's dependency API" do bundle "install", artifice: "endpoint_marshal_fail" ensure - home(".gemrc").rmtree + FileUtils.rm_rf home(".gemrc") end end end diff --git a/spec/bundler/install/gems/flex_spec.rb b/spec/bundler/install/gems/flex_spec.rb index b7e1b5f1bd..a30b53d6ad 100644 --- a/spec/bundler/install/gems/flex_spec.rb +++ b/spec/bundler/install/gems/flex_spec.rb @@ -191,7 +191,7 @@ RSpec.describe "bundle flex_install" do end it "discards the locked gems when the Gemfile requires different versions than the lock" do - bundle "config set force_ruby_platform true" + bundle_config "force_ruby_platform true" nice_error = <<~E.strip Could not find compatible versions @@ -208,7 +208,7 @@ RSpec.describe "bundle flex_install" do end 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" + bundle_config "force_ruby_platform true" bad_error = <<~E.strip Bundler could not find compatible versions for gem "myrack-obama": @@ -289,12 +289,36 @@ RSpec.describe "bundle flex_install" do myrack-obama #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end it "should work when you update" do bundle "update myrack" + + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo1, "myrack", "0.9.1" + c.checksum gem_repo1, "myrack-obama", "1.0" + end + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo1/ + specs: + myrack (0.9.1) + myrack-obama (1.0) + myrack + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack (= 0.9.1) + myrack-obama + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L end end @@ -334,7 +358,7 @@ RSpec.describe "bundle flex_install" do myrack #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end diff --git a/spec/bundler/install/gems/fund_spec.rb b/spec/bundler/install/gems/fund_spec.rb index 0855a62b86..8a3a51270a 100644 --- a/spec/bundler/install/gems/fund_spec.rb +++ b/spec/bundler/install/gems/fund_spec.rb @@ -54,7 +54,7 @@ RSpec.describe "bundle install" do context "when gems include a fund URI but `ignore_funding_requests` is configured" do before do - bundle "config set ignore_funding_requests true" + bundle_config "ignore_funding_requests true" end it "does not display the plural fund message after installing" do diff --git a/spec/bundler/install/gems/gemfile_source_header_spec.rb b/spec/bundler/install/gems/gemfile_source_header_spec.rb new file mode 100644 index 0000000000..dc35c8d741 --- /dev/null +++ b/spec/bundler/install/gems/gemfile_source_header_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +RSpec.describe "fetching dependencies with a mirrored source" do + let(:mirror) { "https://server.example.org" } + + before do + build_repo2 + + gemfile <<-G + source "#{mirror}" + gem 'weakling' + G + + bundle_config "mirror.#{mirror} https://gem.repo2" + end + + it "sets the 'X-Gemfile-Source' and 'User-Agent' headers and bundles successfully" do + bundle :install, artifice: "endpoint_mirror_source" + + expect(out).to include("Installing weakling") + expect(out).to include("Bundle complete") + expect(the_bundle).to include_gems "weakling 0.0.3" + end +end diff --git a/spec/bundler/install/gems/mirror_probe_spec.rb b/spec/bundler/install/gems/mirror_probe_spec.rb new file mode 100644 index 0000000000..564062ccf6 --- /dev/null +++ b/spec/bundler/install/gems/mirror_probe_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +RSpec.describe "fetching dependencies with a not available mirror" do + before do + build_repo2 + + gemfile <<-G + source "https://gem.repo2" + gem 'weakling' + G + end + + context "with a specific fallback timeout" do + before do + bundle_config_global("BUNDLE_MIRROR__HTTPS://GEM__REPO2/__FALLBACK_TIMEOUT/" => "true", + "BUNDLE_MIRROR__HTTPS://GEM__REPO2/" => "https://gem.mirror") + end + + it "install a gem using the original uri when the mirror is not responding" do + bundle :install, env: { "BUNDLER_SPEC_FAKE_RESOLVE" => "gem.mirror" }, verbose: true + + expect(out).to include("Installing weakling") + expect(out).to include("Bundle complete") + expect(the_bundle).to include_gems "weakling 0.0.3" + end + end + + context "with a global fallback timeout" do + before do + bundle_config_global("BUNDLE_MIRROR__ALL__FALLBACK_TIMEOUT/" => "1", + "BUNDLE_MIRROR__ALL" => "https://gem.mirror") + end + + it "install a gem using the original uri when the mirror is not responding" do + bundle :install, env: { "BUNDLER_SPEC_FAKE_RESOLVE" => "gem.mirror" } + + expect(out).to include("Installing weakling") + expect(out).to include("Bundle complete") + expect(the_bundle).to include_gems "weakling 0.0.3" + end + end + + context "with a specific mirror without a fallback timeout" do + before do + bundle_config_global("BUNDLE_MIRROR__HTTPS://GEM__REPO2/" => "https://gem.mirror") + end + + it "fails to install the gem with a timeout error when the mirror is not responding" do + bundle :install, artifice: "compact_index_mirror_down", raise_on_error: false + + expect(out).to be_empty + expect(err).to eq("Could not reach host gem.mirror. Check your network connection and try again.") + end + end + + context "with a global mirror without a fallback timeout" do + before do + bundle_config_global("BUNDLE_MIRROR__ALL" => "https://gem.mirror") + end + + it "fails to install the gem with a timeout error when the mirror is not responding" do + bundle :install, artifice: "compact_index_mirror_down", raise_on_error: false + + expect(out).to be_empty + expect(err).to eq("Could not reach host gem.mirror. Check your network connection and try again.") + end + end +end diff --git a/spec/bundler/install/gems/mirror_spec.rb b/spec/bundler/install/gems/mirror_spec.rb index 70c0da50ef..e1fbeac454 100644 --- a/spec/bundler/install/gems/mirror_spec.rb +++ b/spec/bundler/install/gems/mirror_spec.rb @@ -8,7 +8,7 @@ RSpec.describe "bundle install with a mirror configured" do gem "myrack" G - bundle "config set --local mirror.http://gems.example.org http://gem-mirror.example.org" + bundle_config "mirror.http://gems.example.org http://gem-mirror.example.org" end it "installs from the normal location" do @@ -26,7 +26,7 @@ RSpec.describe "bundle install with a mirror configured" do gem "myrack" G - bundle "config set --local mirror.https://gem.repo2 https://gem.repo1" + bundle_config "mirror.https://gem.repo2 https://gem.repo1" end it "installs the gem from the mirror" do diff --git a/spec/bundler/install/gems/native_extensions_spec.rb b/spec/bundler/install/gems/native_extensions_spec.rb index 874818fa87..d5b10d2c8f 100644 --- a/spec/bundler/install/gems/native_extensions_spec.rb +++ b/spec/bundler/install/gems/native_extensions_spec.rb @@ -9,7 +9,7 @@ RSpec.describe "installing a gem with native extensions" do require "mkmf" name = "c_extension_bundle" dir_config(name) - raise "OMG" unless with_config("c_extension") == "hello" + raise ArgumentError unless with_config("c_extension") == "hello" create_makefile(name) E @@ -37,7 +37,7 @@ RSpec.describe "installing a gem with native extensions" do gem "c_extension" G - bundle "config set build.c_extension --with-c_extension=hello" + bundle_config "build.c_extension --with-c_extension=hello" bundle "install" expect(out).to include("Installing c_extension 1.0 with native extensions") @@ -53,7 +53,7 @@ RSpec.describe "installing a gem with native extensions" do require "mkmf" name = "c_extension_bundle" dir_config(name) - raise "OMG" unless with_config("c_extension") == "hello" + raise ArgumentError unless with_config("c_extension") == "hello" create_makefile(name) E @@ -75,7 +75,7 @@ RSpec.describe "installing a gem with native extensions" do C end - bundle "config set build.c_extension --with-c_extension=hello" + bundle_config "build.c_extension --with-c_extension=hello" install_gemfile <<-G source "https://gem.repo1" @@ -97,7 +97,7 @@ RSpec.describe "installing a gem with native extensions" do require "mkmf" name = "c_extension_bundle_#{n}" dir_config(name) - raise "OMG" unless with_config("c_extension_#{n}") == "#{n}" + raise ArgumentError unless with_config("c_extension_#{n}") == "#{n}" create_makefile(name) E @@ -122,8 +122,8 @@ RSpec.describe "installing a gem with native extensions" do build_git "gems", path: lib_path("gems"), gemspec: false end - bundle "config set build.c_extension_one --with-c_extension_one=one" - bundle "config set build.c_extension_two --with-c_extension_two=two" + bundle_config "build.c_extension_one --with-c_extension_one=one" + bundle_config "build.c_extension_two --with-c_extension_two=two" # 1st time, require only one gem -- only one of the extensions gets built. install_gemfile <<-G @@ -149,7 +149,7 @@ RSpec.describe "installing a gem with native extensions" do require "mkmf" name = "c_extension_bundle" dir_config(name) - raise "OMG" unless with_config("c_extension") == "hello" && with_config("c_extension_bundle-dir") == "hola" + raise ArgumentError unless with_config("c_extension") == "hello" && with_config("c_extension_bundle-dir") == "hola" create_makefile(name) E @@ -171,7 +171,7 @@ RSpec.describe "installing a gem with native extensions" do C end - bundle "config set build.c_extension --with-c_extension=hello --with-c_extension_bundle-dir=hola" + bundle_config "build.c_extension --with-c_extension=hello --with-c_extension_bundle-dir=hola" install_gemfile <<-G source "https://gem.repo1" diff --git a/spec/bundler/install/gems/no_build_extension_spec.rb b/spec/bundler/install/gems/no_build_extension_spec.rb new file mode 100644 index 0000000000..31f0170433 --- /dev/null +++ b/spec/bundler/install/gems/no_build_extension_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install with --no-build-extension" do + before do + build_repo2 do + build_gem "with_extension" do |s| + s.extensions << "Rakefile" + s.write "Rakefile", <<-RUBY + task :default do + path = File.expand_path("lib", __dir__) + FileUtils.mkdir_p(path) + File.open("\#{path}/with_extension.rb", "w") do |f| + f.puts "WITH_EXTENSION = 'YES'" + end + end + RUBY + end + end + end + + it "skips building native extensions and warns when no_build_extension is set" do + bundle_config "no_build_extension true" + + gemfile <<-G + source "https://gem.repo2" + gem "with_extension" + gem "rake" + G + + bundle :install + + build_complete = default_bundle_path("extensions").join( + Gem::Platform.local.to_s, + Gem.extension_api_version.to_s, + "with_extension-1.0", + "gem.build_complete" + ) + expect(build_complete).not_to exist + expect(err).to include("with_extension-1.0 contains native extensions that were not built") + expect(err).to include("unset no_build_extension and run `bundle pristine with_extension`") + end + + it "builds native extensions by default" do + gemfile <<-G + source "https://gem.repo2" + gem "with_extension" + gem "rake" + G + + bundle :install + + expect(out).to include("Installing with_extension 1.0 with native extensions") + end +end diff --git a/spec/bundler/install/gems/no_install_plugin_spec.rb b/spec/bundler/install/gems/no_install_plugin_spec.rb new file mode 100644 index 0000000000..e040e6b813 --- /dev/null +++ b/spec/bundler/install/gems/no_install_plugin_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install with --no-install-plugin" do + before do + build_repo2 do + build_gem "with_plugin", "1.0" do |s| + s.write "lib/rubygems_plugin.rb", "# plugin code" + end + + build_gem "with_plugin", "2.0" + end + end + + let(:plugin_path) { default_bundle_path("plugins", "with_plugin_plugin.rb") } + + it "does not generate the plugin wrapper and warns when no_install_plugin is set" do + bundle_config "no_install_plugin true" + + install_gemfile <<-G + source "https://gem.repo2" + gem "with_plugin", "1.0" + G + + expect(plugin_path).not_to exist + expect(err).to include("with_plugin-1.0 contains plugins that were not installed") + expect(err).to include("unset no_install_plugin and run `bundle pristine with_plugin`") + end + + it "removes a stale plugin wrapper from a prior version when no_install_plugin is set" do + install_gemfile <<-G + source "https://gem.repo2" + gem "with_plugin", "1.0" + G + expect(plugin_path).to exist + + bundle_config "no_install_plugin true" + install_gemfile <<-G + source "https://gem.repo2" + gem "with_plugin", "2.0" + G + + expect(plugin_path).not_to exist + end + + it "generates the plugin wrapper by default" do + install_gemfile <<-G + source "https://gem.repo2" + gem "with_plugin", "1.0" + G + + expect(plugin_path).to exist + end +end diff --git a/spec/bundler/install/gems/post_install_spec.rb b/spec/bundler/install/gems/post_install_spec.rb index af753dba3e..e49fd2a9a3 100644 --- a/spec/bundler/install/gems/post_install_spec.rb +++ b/spec/bundler/install/gems/post_install_spec.rb @@ -127,7 +127,7 @@ RSpec.describe "bundle install" do gem "myrack" G - bundle "config set ignore_messages.myrack true" + bundle_config "ignore_messages.myrack true" bundle :install expect(out).not_to include("Post-install message") @@ -141,7 +141,7 @@ RSpec.describe "bundle install" do gem "myrack" G - bundle "config set ignore_messages true" + bundle_config "ignore_messages true" bundle :install expect(out).not_to include("Post-install message") diff --git a/spec/bundler/install/gems/resolving_spec.rb b/spec/bundler/install/gems/resolving_spec.rb index b4af8ab6d4..111d361aab 100644 --- a/spec/bundler/install/gems/resolving_spec.rb +++ b/spec/bundler/install/gems/resolving_spec.rb @@ -275,7 +275,7 @@ RSpec.describe "bundle install with install-time dependencies" do parallel_tests #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -299,17 +299,16 @@ RSpec.describe "bundle install with install-time dependencies" do parallel_tests #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end it "gives a meaningful error if we're in frozen mode" do expect do - bundle "install --verbose", env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false + bundle "install", env: { "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.") + expect(err).to eq("parallel_tests-3.8.0 requires ruby version >= #{next_ruby_minor}, which is incompatible with the current version, #{Gem.ruby_version}") end end @@ -359,10 +358,10 @@ RSpec.describe "bundle install with install-time dependencies" do #{lockfile_platforms} DEPENDENCIES - parallel_tests + rubocop #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -389,12 +388,12 @@ RSpec.describe "bundle install with install-time dependencies" do rubocop #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end - context "with a Gemfile and lock file that don't resolve under the current platform" do + context "with a Gemfile and lockfile that don't resolve under the current platform" do before do build_repo4 do build_gem "sorbet", "0.5.10554" do |s| @@ -426,7 +425,7 @@ RSpec.describe "bundle install with install-time dependencies" do sorbet (= 0.5.10554) BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -441,7 +440,9 @@ RSpec.describe "bundle install with install-time dependencies" do 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) + expect(err).to include(nice_error) + expect(err).to include("Your current platform (aarch64-linux) is not included in the lockfile's platforms (arm64-darwin-21)") + expect(err).to include("bundle lock --add-platform aarch64-linux") end end @@ -476,7 +477,7 @@ RSpec.describe "bundle install with install-time dependencies" do nokogiri BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L gemfile <<~G @@ -518,7 +519,7 @@ RSpec.describe "bundle install with install-time dependencies" do nokogiri BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -540,7 +541,7 @@ RSpec.describe "bundle install with install-time dependencies" do lockfile original_lockfile end - it "keeps both variants in the lockfile, and uses the generic one since it's compatible" do + it "keeps both variants in the lockfile when installing, and uses the generic one since it's compatible" do simulate_platform "x86_64-linux" do bundle "install --verbose" @@ -548,6 +549,15 @@ RSpec.describe "bundle install with install-time dependencies" do expect(the_bundle).to include_gems("nokogiri 1.16.3") end end + + it "keeps both variants in the lockfile when updating, and uses the generic one since it's compatible" do + simulate_platform "x86_64-linux" do + bundle "update --verbose" + + expect(lockfile).to eq(original_lockfile) + expect(the_bundle).to include_gems("nokogiri 1.16.3") + end + end end it "gives a meaningful error on ruby version mismatches between dependencies" do @@ -606,13 +616,13 @@ RSpec.describe "bundle install with install-time dependencies" do s.required_ruby_version = "> 9000" end build_gem "myrack", "1.2" do |s| - s.platform = x86_mingw32 + s.platform = "x86-mingw32" s.required_ruby_version = "> 9000" end build_gem "myrack", "1.2" end - simulate_platform x86_mingw32 do + simulate_platform "x86-mingw32" do install_gemfile <<-G, artifice: "compact_index" ruby "#{Gem.ruby_version}" source "https://gem.repo4" @@ -768,7 +778,7 @@ RSpec.describe "bundle install with install-time dependencies" do foo #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end diff --git a/spec/bundler/install/gems/standalone_spec.rb b/spec/bundler/install/gems/standalone_spec.rb index abd77d33de..96a305bb76 100644 --- a/spec/bundler/install/gems/standalone_spec.rb +++ b/spec/bundler/install/gems/standalone_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.shared_examples "bundle install --standalone" do +RSpec.describe "bundle install --standalone" do shared_examples "common functionality" do it "still makes the gems available to normal bundler" do args = expected_gems.map {|k, v| "#{k} #{v}" } @@ -42,7 +42,7 @@ RSpec.shared_examples "bundle install --standalone" do testrb << "\nrequire \"#{k}\"" testrb << "\nputs #{k.upcase}" end - sys_exec %(#{Gem.ruby} --disable-gems -w -e #{testrb.shellescape}) + in_bundled_app %(#{Gem.ruby} --disable-gems -w -e #{testrb.shellescape}) expect(out).to eq(expected_gems.values.join("\n")) end @@ -113,7 +113,7 @@ RSpec.shared_examples "bundle install --standalone" do testrb << "\nrequire \"#{k}\"" testrb << "\nputs #{k.upcase}" end - sys_exec %(#{Gem.ruby} -w -e #{testrb.shellescape}) + in_bundled_app %(#{Gem.ruby} -w -e #{testrb.shellescape}) expect(out).to eq(expected_gems.values.join("\n")) end @@ -125,7 +125,7 @@ RSpec.shared_examples "bundle install --standalone" do source "https://gem.repo1" gem "rails" G - bundle "config set --local path #{bundled_app("bundle")}" + bundle_config "path #{bundled_app("bundle")}" bundle :install, standalone: true, dir: cwd end @@ -140,15 +140,8 @@ RSpec.shared_examples "bundle install --standalone" do end describe "with default gems and a lockfile", :ruby_repo do - before do - necessary_system_gems = ["tsort --version 0.1.0"] - realworld_system_gems(*necessary_system_gems) - end - it "works and points to the vendored copies, not to the default copies" do - necessary_gems_in_bundle_path = ["optparse --version 0.1.1", "psych --version 3.3.2", "logger --version 1.4.3", "etc --version 1.4.3", "stringio --version 3.1.0"] - necessary_gems_in_bundle_path += ["yaml --version 0.1.1"] if Gem.rubygems_version < Gem::Version.new("3.4.a") - realworld_system_gems(*necessary_gems_in_bundle_path, path: scoped_gem_path(bundled_app("bundle"))) + base_system_gems "stringio", "psych", "etc", path: scoped_gem_path(bundled_app("bundle")) build_gem "foo", "1.0.0", to_system: true, default: true do |s| s.add_dependency "bar" @@ -171,17 +164,9 @@ RSpec.shared_examples "bundle install --standalone" do bundle "lock", dir: cwd - bundle "config set --local path #{bundled_app("bundle")}" + bundle_config "path #{bundled_app("bundle")}" - # Make sure rubyinstaller2 does not activate the etc gem in its - # `operating_system.rb` file, but completely disable that since it's not - # really needed here - if Gem.win_platform? - FileUtils.mkdir_p bundled_app("rubygems/defaults") - FileUtils.touch bundled_app("rubygems/defaults/operating_system.rb") - end - - bundle :install, standalone: true, dir: cwd, env: { "BUNDLER_GEM_DEFAULT_DIR" => system_gem_path.to_s }, load_path: bundled_app + bundle :install, standalone: true, dir: cwd, 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") } @@ -193,9 +178,7 @@ RSpec.shared_examples "bundle install --standalone" do it "works for gems with extensions and points to the vendored copies, not to the default copies" do simulate_platform "arm64-darwin-23" do - necessary_gems_in_bundle_path = ["optparse --version 0.1.1", "psych --version 3.3.2", "logger --version 1.4.3", "etc --version 1.4.3", "stringio --version 3.1.0", "shellwords --version 0.2.0", "open3 --version 0.2.1"] - necessary_gems_in_bundle_path += ["yaml --version 0.1.1"] if Gem.rubygems_version < Gem::Version.new("3.4.a") - realworld_system_gems(*necessary_gems_in_bundle_path, path: scoped_gem_path(bundled_app("bundle"))) + base_system_gems "stringio", "psych", "etc", "shellwords", "open3", path: scoped_gem_path(bundled_app("bundle")) build_gem "baz", "1.0.0", to_system: true, default: true, &:add_c_extension @@ -208,19 +191,11 @@ RSpec.shared_examples "bundle install --standalone" do gem "baz" G - bundle "config set --local path #{bundled_app("bundle")}" + bundle_config "path #{bundled_app("bundle")}" bundle "lock", dir: cwd - # Make sure rubyinstaller2 does not activate the etc gem in its - # `operating_system.rb` file, but completely disable that since it's not - # really needed here - if Gem.win_platform? - FileUtils.mkdir_p bundled_app("rubygems/defaults") - FileUtils.touch bundled_app("rubygems/defaults/operating_system.rb") - end - - bundle :install, standalone: true, dir: cwd, env: { "BUNDLER_GEM_DEFAULT_DIR" => system_gem_path.to_s }, load_path: bundled_app + bundle :install, standalone: true, dir: cwd, env: { "BUNDLER_GEM_DEFAULT_DIR" => system_gem_path.to_s } end load_path_lines = bundled_app("bundle/bundler/setup.rb").read.split("\n").select {|line| line.start_with?("$:.unshift") } @@ -262,6 +237,8 @@ RSpec.shared_examples "bundle install --standalone" do end end + let(:cwd) { bundled_app } + describe "with Gemfiles using relative path sources and app moved to a different root" do before do FileUtils.mkdir_p bundled_app("app/vendor") @@ -293,7 +270,7 @@ RSpec.shared_examples "bundle install --standalone" do describe "with gems with native extension" do before do - bundle "config set --local path #{bundled_app("bundle")}" + bundle_config "path #{bundled_app("bundle")}" install_gemfile <<-G, standalone: true, dir: cwd source "https://gem.repo1" gem "very_simple_binary" @@ -331,7 +308,7 @@ RSpec.shared_examples "bundle install --standalone" do end G end - bundle "config set --local path #{bundled_app("bundle")}" + bundle_config "path #{bundled_app("bundle")}" install_gemfile <<-G, standalone: true, dir: cwd, raise_on_error: false source "https://gem.repo1" gem "bar", :git => "#{lib_path("bar-1.0")}" @@ -353,7 +330,7 @@ RSpec.shared_examples "bundle install --standalone" do gem "rails" gem "devise", :git => "#{lib_path("devise-1.0")}" G - bundle "config set --local path #{bundled_app("bundle")}" + bundle_config "path #{bundled_app("bundle")}" bundle :install, standalone: true, dir: cwd end @@ -381,7 +358,7 @@ RSpec.shared_examples "bundle install --standalone" do gem "myrack-test" end G - bundle "config set --local path #{bundled_app("bundle")}" + bundle_config "path #{bundled_app("bundle")}" bundle :install, standalone: true, dir: cwd end @@ -395,7 +372,7 @@ RSpec.shared_examples "bundle install --standalone" do include_examples "common functionality" it "allows creating a standalone file with limited groups" do - bundle "config set --local path #{bundled_app("bundle")}" + bundle_config "path #{bundled_app("bundle")}" bundle :install, standalone: "default", dir: cwd load_error_ruby <<-RUBY, "spec" @@ -408,12 +385,12 @@ RSpec.shared_examples "bundle install --standalone" do RUBY expect(out).to eq("2.3.2") - expect(err).to eq("ZOMG LOAD ERROR") + expect(err_without_deprecations).to match(/cannot load such file -- spec/) end 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_config "path #{bundled_app("bundle")}" + bundle_config "without test" bundle :install, standalone: true, dir: cwd load_error_ruby <<-RUBY, "spec" @@ -426,11 +403,11 @@ RSpec.shared_examples "bundle install --standalone" do RUBY expect(out).to eq("2.3.2") - expect(err).to eq("ZOMG LOAD ERROR") + expect(err_without_deprecations).to match(/cannot load such file -- spec/) end it "allows `path` configuration to change the location of the standalone bundle" do - bundle "config set --local path path/to/bundle" + bundle_config "path path/to/bundle" bundle "install", standalone: true, dir: cwd ruby <<-RUBY @@ -445,9 +422,9 @@ RSpec.shared_examples "bundle install --standalone" do end it "allows `without` to limit the groups used in a standalone" do - bundle "config set --local without test" + bundle_config "without test" bundle :install, dir: cwd - bundle "config set --local path #{bundled_app("bundle")}" + bundle_config "path #{bundled_app("bundle")}" bundle :install, standalone: true, dir: cwd load_error_ruby <<-RUBY, "spec" @@ -460,7 +437,7 @@ RSpec.shared_examples "bundle install --standalone" do RUBY expect(out).to eq("2.3.2") - expect(err).to eq("ZOMG LOAD ERROR") + expect(err_without_deprecations).to match(/cannot load such file -- spec/) end end @@ -473,7 +450,7 @@ RSpec.shared_examples "bundle install --standalone" do source "#{source_uri}" gem "rails" G - bundle "config set --local path #{bundled_app("bundle")}" + bundle_config "path #{bundled_app("bundle")}" bundle :install, standalone: true, artifice: "endpoint", dir: cwd end @@ -487,67 +464,37 @@ RSpec.shared_examples "bundle install --standalone" do include_examples "common functionality" end end +end - describe "with --binstubs", bundler: "< 3" do - before do - gemfile <<-G - source "https://gem.repo1" - gem "rails" - G - bundle "config set --local path #{bundled_app("bundle")}" - bundle :install, standalone: true, binstubs: true, dir: cwd - end +RSpec.describe "bundle install --standalone run in a subdirectory" do + let(:cwd) { bundled_app("bob").tap(&:mkpath) } - let(:expected_gems) do - { - "actionpack" => "2.3.2", - "rails" => "2.3.2", - } - end + before do + gemfile <<-G + source "https://gem.repo1" + gem "rails" + G + end - include_examples "common functionality" + it "generates the script in the proper place" do + bundle :install, standalone: true, dir: cwd - it "creates stubs that use the standalone load path" do - expect(sys_exec("bin/rails -v").chomp).to eql "2.3.2" - end + expect(bundled_app("bundle/bundler/setup.rb")).to exist + end - it "creates stubs that can be executed from anywhere" do - require "tmpdir" - sys_exec(%(#{bundled_app("bin/rails")} -v), dir: Dir.tmpdir) - expect(out).to eq("2.3.2") + context "when path set to a relative path" do + before do + bundle_config "path bundle" end - it "creates stubs that can be symlinked" do - skip "symlinks unsupported" if Gem.win_platform? - - symlink_dir = tmp("symlink") - FileUtils.mkdir_p(symlink_dir) - symlink = File.join(symlink_dir, "rails") - - File.symlink(bundled_app("bin/rails"), symlink) - sys_exec("#{symlink} -v") - expect(out).to eq("2.3.2") - end + it "generates the script in the proper place" do + bundle :install, standalone: true, dir: cwd - 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", __dir__) + expect(bundled_app("bundle/bundler/setup.rb")).to exist end end end -RSpec.describe "bundle install --standalone" do - let(:cwd) { bundled_app } - - include_examples("bundle install --standalone") -end - -RSpec.describe "bundle install --standalone run in a subdirectory" do - let(:cwd) { bundled_app("bob").tap(&:mkpath) } - - include_examples("bundle install --standalone") -end - RSpec.describe "bundle install --standalone --local" do before do gemfile <<-G @@ -572,6 +519,6 @@ RSpec.describe "bundle install --standalone --local" do RUBY expect(out).to eq("1.0.0") - expect(err).to eq("ZOMG LOAD ERROR") + expect(err_without_deprecations).to match(/cannot load such file -- spec/) end end diff --git a/spec/bundler/install/gemspecs_spec.rb b/spec/bundler/install/gemspecs_spec.rb index dee8e547e4..fb2271c830 100644 --- a/spec/bundler/install/gemspecs_spec.rb +++ b/spec/bundler/install/gemspecs_spec.rb @@ -122,7 +122,7 @@ RSpec.describe "bundle install" do expect(the_bundle).to include_gems "foo 1.0" end - it "fails and complains about patchlevel on patchlevel mismatch", + it "installs gems ignoring the mismatch even when patchlevel is mismatch", if: RUBY_PATCHLEVEL >= 0 do patchlevel = RUBY_PATCHLEVEL.to_i + 1 build_lib("foo", path: bundled_app) do |s| @@ -135,9 +135,7 @@ RSpec.describe "bundle install" do gemspec G - expect(err).to include("Ruby patchlevel") - expect(err).to include("but your Gemfile specified") - expect(exitstatus).to eq(18) + expect(the_bundle).to include_gems "foo 1.0" end it "fails and complains about version on version mismatch" do diff --git a/spec/bundler/install/git_spec.rb b/spec/bundler/install/git_spec.rb index d1f6b7a7ca..1172d661ae 100644 --- a/spec/bundler/install/git_spec.rb +++ b/spec/bundler/install/git_spec.rb @@ -28,14 +28,14 @@ RSpec.describe "bundle install" do end it "displays the correct default branch", git: ">= 2.28.0" do - build_git "foo", "1.0", path: lib_path("foo"), default_branch: "main" + build_git "foo", "1.0", path: lib_path("foo"), default_branch: "non-standard" install_gemfile <<-G, verbose: true source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo")}" G - expect(out).to include("Using foo 1.0 from #{lib_path("foo")} (at main@#{revision_for(lib_path("foo"))[0..6]})") + expect(out).to include("Using foo 1.0 from #{lib_path("foo")} (at non-standard@#{revision_for(lib_path("foo"))[0..6]})") expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}" end @@ -85,8 +85,8 @@ RSpec.describe "bundle install" do foo! L - bundle "config set --local path vendor/bundle" - bundle "config set --local without development" + bundle_config "path vendor/bundle" + bundle_config "without development" bundle :install expect(out).to include("Bundle complete!") @@ -157,7 +157,7 @@ RSpec.describe "bundle install" do test! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L # If GH#6743 is present, the first `bundle install` will change the @@ -188,8 +188,8 @@ RSpec.describe "bundle install" 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" + bundle_config "path vendor/bundle" + bundle_config "clean true" install_gemfile <<-G, verbose: true source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo")}" @@ -214,5 +214,156 @@ RSpec.describe "bundle install" do expect(out).to include("Using foo 1.0 from #{lib_path("foo")} (at main@#{rev[0..6]})") expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}" end + + context "when install directory exists" do + let(:checkout_confirmation_log_message) { "Checking out revision" } + let(:using_foo_confirmation_log_message) { "Using foo 1.0 from #{lib_path("foo")} (at main@#{revision_for(lib_path("foo"))[0..6]})" } + + context "and no contents besides .git directory are present" do + it "reinstalls gem" do + build_git "foo", "1.0", path: lib_path("foo") + + gemfile = <<-G + source "https://gem.repo1" + gem "foo", :git => "#{lib_path("foo")}" + G + + install_gemfile gemfile, verbose: true + + expect(out).to include(checkout_confirmation_log_message) + expect(out).to include(using_foo_confirmation_log_message) + expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}" + + # validate that the installed directory exists and has some expected contents + install_directory = default_bundle_path("bundler/gems/foo-#{revision_for(lib_path("foo"))[0..11]}") + dot_git_directory = install_directory.join(".git") + lib_directory = install_directory.join("lib") + gemspec = install_directory.join("foo.gemspec") + expect([install_directory, dot_git_directory, lib_directory, gemspec]).to all exist + + # remove all elements in the install directory except .git directory + FileUtils.rm_r(lib_directory) + gemspec.delete + + expect(dot_git_directory).to exist + expect(lib_directory).not_to exist + expect(gemspec).not_to exist + + # rerun bundle install + install_gemfile gemfile, verbose: true + + expect(out).to include(checkout_confirmation_log_message) + expect(out).to include(using_foo_confirmation_log_message) + expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}" + + # validate that it reinstalls all components + expect([install_directory, dot_git_directory, lib_directory, gemspec]).to all exist + end + end + + context "and contents besides .git directory are present" do + # we want to confirm that the change to try to detect partial installs and reinstall does not + # result in repeatedly reinstalling the gem when it is fully installed + it "does not reinstall gem" do + build_git "foo", "1.0", path: lib_path("foo") + + gemfile = <<-G + source "https://gem.repo1" + gem "foo", :git => "#{lib_path("foo")}" + G + + install_gemfile gemfile, verbose: true + + expect(out).to include(checkout_confirmation_log_message) + expect(out).to include(using_foo_confirmation_log_message) + expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}" + + # rerun bundle install + install_gemfile gemfile, verbose: true + + # it isn't altogether straight-forward to validate that bundle didn't do soething on the second run, however, + # the presence of the 2nd log message confirms install got past the point that it would have logged the above if + # it was going to + expect(out).not_to include(checkout_confirmation_log_message) + expect(out).to include(using_foo_confirmation_log_message) + end + end + end + end + + describe "with excluded groups" do + it "works if you exclude a group with a git gem", ruby: ">= 3.3" do + build_git "production_gem", "1.0" + build_git "development_gem", "1.0" + + gemfile <<-G + source "https://gem.repo1" + + gem "production_gem", :git => "#{lib_path("production_gem-1.0")}" + + group :development do + gem "development_gem", :git => "#{lib_path("development_gem-1.0")}" + end + G + + # First install all groups to create lockfile + bundle :install + + # Set without and reinstall + bundle_config "without development" + bundle :install + + # Verify only production gem is available + expect(the_bundle).to include_gems("production_gem 1.0") + expect(the_bundle).not_to include_gems("development_gem 1.0") + end + + it "resolves indirect dependencies from a git source not in the requested groups" do + build_lib "activesupport", "1.0", path: lib_path("rails/activesupport") + build_git "activerecord", "1.0", path: lib_path("rails") do |s| + s.add_dependency "activesupport", "= 1.0" + end + + gemfile <<-G + source "https://gem.repo1" + + gem "activerecord", :git => "#{lib_path("rails")}" + + group :ci do + gem "myrack" + end + G + + bundle_config "only ci" + bundle :install + + expect(the_bundle).to include_gems("myrack 1.0.0") + expect(the_bundle).not_to include_gems("activerecord 1.0") + end + + it "resolves indirect dependencies from a git source not in the requested groups (without compact_index dependency API)" do + build_lib "activesupport", "1.0", path: lib_path("rails/activesupport") + build_git "activerecord", "1.0", path: lib_path("rails") do |s| + s.add_dependency "activesupport", "= 1.0" + end + + gemfile <<-G + source "https://gem.repo1" + + gem "activerecord", :git => "#{lib_path("rails")}" + + group :ci do + gem "myrack" + end + G + + # Force the RubygemsAggregate code path in find_source_requirements by + # making the dependency API unavailable. + bundle_config "only ci" + bundle :install, artifice: "endpoint_api_forbidden" + + expect(the_bundle).to include_gems("myrack 1.0.0") + expect(the_bundle).not_to include_gems("activerecord 1.0") + end end end diff --git a/spec/bundler/install/global_cache_spec.rb b/spec/bundler/install/global_cache_spec.rb index df4559c42e..4cffa65b2a 100644 --- a/spec/bundler/install/global_cache_spec.rb +++ b/spec/bundler/install/global_cache_spec.rb @@ -1,18 +1,29 @@ # frozen_string_literal: true RSpec.describe "global gem caching" do + # Uses subprocess because this setting must apply across multiple app directories (bundled_app and bundled_app2) before { bundle "config set global_gem_cache true" } describe "using the cross-application user cache" do let(:source) { "http://localgemserver.test" } let(:source2) { "http://gemserver.example.org" } + def cache_base + # Use the unified global gem cache path if available (from RubyGems), + # otherwise fall back to the Bundler-specific cache location + if Gem.respond_to?(:global_gem_cache_path) + Pathname.new(Gem.global_gem_cache_path) + else + home(".bundle", "cache", "gems") + end + end + def source_global_cache(*segments) - home(".bundle", "cache", "gems", "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", *segments) + cache_base.join("localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", *segments) end def source2_global_cache(*segments) - home(".bundle", "cache", "gems", "gemserver.example.org.80.1ae1663619ffe0a3c9d97712f44c705b", *segments) + cache_base.join("gemserver.example.org.80.1ae1663619ffe0a3c9d97712f44c705b", *segments) end it "caches gems into the global cache on download" do @@ -38,6 +49,8 @@ RSpec.describe "global gem caching" do end it "shows a proper error message if a cached gem is corrupted" do + skip "This example is not working on ruby/ruby repo" if ruby_core? + source_global_cache.mkpath FileUtils.touch(source_global_cache("myrack-1.0.0.gem")) @@ -49,14 +62,49 @@ RSpec.describe "global gem caching" do expect(err).to include("Gem::Package::FormatError: package metadata is missing in #{source_global_cache("myrack-1.0.0.gem")}") end + it "uses a shorter path for the cache to not hit filesystem limits" do + install_gemfile <<-G, artifice: "compact_index", verbose: true + source "http://#{"a" * 255}.test" + gem "myrack" + G + + expect(the_bundle).to include_gems "myrack 1.0.0" + source_segment = "a" * 222 + ".a3cb26de2edfce9f509a65c611d99c4b" + source_cache = cache_base.join(source_segment) + cached_gem = source_cache.join("myrack-1.0.0.gem") + expect(cached_gem).to exist + ensure + # We cleanup dummy files created by this spec manually because due to a + # Ruby on Windows bug, `FileUtils.rm_rf` (run in our global after hook) + # cannot traverse directories with such long names. So we delete + # everything explicitly to workaround the bug. An alternative workaround + # would be to shell out to `rm -rf`. That also works fine, but I went with + # the more verbose and explicit approach. This whole ensure block can be + # removed once/if https://bugs.ruby-lang.org/issues/21177 is fixed, and + # once the fix propagates to all supported rubies. + File.delete cached_gem + Dir.rmdir source_cache + + File.delete compact_index_cache_path.join(source_segment, "info", "myrack") + Dir.rmdir compact_index_cache_path.join(source_segment, "info") + File.delete compact_index_cache_path.join(source_segment, "info-etags", "myrack-92f3313ce5721296f14445c3a6b9c073") + Dir.rmdir compact_index_cache_path.join(source_segment, "info-etags") + Dir.rmdir compact_index_cache_path.join(source_segment, "info-special-characters") + File.delete compact_index_cache_path.join(source_segment, "versions") + File.delete compact_index_cache_path.join(source_segment, "versions.etag") + Dir.rmdir compact_index_cache_path.join(source_segment) + end + describe "when the same gem from different sources is installed" do it "should use the appropriate one from the global cache" do + bundle_config "path.system true" + install_gemfile <<-G, artifice: "compact_index" source "#{source}" gem "myrack" G - simulate_new_machine + pristine_system_gems expect(the_bundle).not_to include_gems "myrack 1.0.0" expect(source_global_cache("myrack-1.0.0.gem")).to exist # myrack 1.0.0 is not installed and it is in the global cache @@ -66,7 +114,7 @@ RSpec.describe "global gem caching" do gem "myrack", "0.9.1" G - simulate_new_machine + pristine_system_gems expect(the_bundle).not_to include_gems "myrack 0.9.1" expect(source2_global_cache("myrack-0.9.1.gem")).to exist # myrack 0.9.1 is not installed and it is in the global cache @@ -80,7 +128,7 @@ RSpec.describe "global gem caching" do # myrack 1.0.0 is installed and myrack 0.9.1 is not expect(the_bundle).to include_gems "myrack 1.0.0" expect(the_bundle).not_to include_gems "myrack 0.9.1" - simulate_new_machine + pristine_system_gems gemfile <<-G source "#{source2}" @@ -94,13 +142,15 @@ RSpec.describe "global gem caching" do end it "should not install if the wrong source is provided" do + bundle_config "path.system true" + gemfile <<-G source "#{source}" gem "myrack" G bundle :install, artifice: "compact_index" - simulate_new_machine + pristine_system_gems expect(the_bundle).not_to include_gems "myrack 1.0.0" expect(source_global_cache("myrack-1.0.0.gem")).to exist # myrack 1.0.0 is not installed and it is in the global cache @@ -111,7 +161,7 @@ RSpec.describe "global gem caching" do G bundle :install, artifice: "compact_index" - simulate_new_machine + pristine_system_gems expect(the_bundle).not_to include_gems "myrack 0.9.1" expect(source2_global_cache("myrack-0.9.1.gem")).to exist # myrack 0.9.1 is not installed and it is in the global cache @@ -150,6 +200,8 @@ RSpec.describe "global gem caching" do describe "when installing gems from a different directory" do it "uses the global cache as a source" do + bundle_config "path.system true" + install_gemfile <<-G, artifice: "compact_index" source "#{source}" gem "myrack" @@ -161,7 +213,7 @@ RSpec.describe "global gem caching" do expect(the_bundle).to include_gems "activesupport 2.3.5" expect(source_global_cache("myrack-1.0.0.gem")).to exist expect(source_global_cache("activesupport-2.3.5.gem")).to exist - simulate_new_machine + pristine_system_gems # Both gems are now only in the global cache expect(the_bundle).not_to include_gems "myrack 1.0.0" expect(the_bundle).not_to include_gems "activesupport 2.3.5" @@ -205,6 +257,7 @@ RSpec.describe "global gem caching" do describe "extension caching" do it "works" do skip "gets incorrect ref in path" if Gem.win_platform? + skip "fails for unknown reason when run by ruby-core" if ruby_core? build_git "very_simple_git_binary", &:add_c_extension build_lib "very_simple_path_binary", &:add_c_extension @@ -232,12 +285,12 @@ RSpec.describe "global gem caching" do R expect(out).to eq "VERY_SIMPLE_BINARY_IN_C\nVERY_SIMPLE_GIT_BINARY_IN_C" - FileUtils.rm_rf Dir[home(".bundle", "cache", "extensions", "**", "*binary_c*")] + FileUtils.rm_r 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__)" } - bundle "config set --local path different_path" + bundle_config "path different_path" bundle :install expect(Dir[home(".bundle", "cache", "extensions", "**", "*binary_c*")]).to all(end_with(".rb")) diff --git a/spec/bundler/install/path_spec.rb b/spec/bundler/install/path_spec.rb index 8d32e033d6..49360e511e 100644 --- a/spec/bundler/install/path_spec.rb +++ b/spec/bundler/install/path_spec.rb @@ -14,14 +14,14 @@ RSpec.describe "bundle install" do end it "does not use available system gems with `vendor/bundle" do - bundle "config set --local path vendor/bundle" + bundle_config "path vendor/bundle" bundle :install expect(the_bundle).to include_gems "myrack 1.0.0" end it "uses system gems with `path.system` configured with more priority than `path`" do - bundle "config set --local path.system true" - bundle "config set --global path vendor/bundle" + bundle_config "path.system true" + bundle_config_global "path vendor/bundle" bundle :install run "require 'myrack'", raise_on_error: false expect(out).to include("FAIL") @@ -31,57 +31,45 @@ RSpec.describe "bundle install" do dir = bundled_app("bun++dle") dir.mkpath - bundle "config set --local path #{dir.join("vendor/bundle")}" + bundle_config "path #{dir.join("vendor/bundle")}" bundle :install, dir: dir expect(out).to include("installed into `./vendor/bundle`") - dir.rmtree + FileUtils.rm_rf dir end it "prints a message to let the user know where gems where installed" do - bundle "config set --local path vendor/bundle" + bundle_config "path vendor/bundle" bundle :install 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 - expect(err).to include("Please choose only one option.") - expect(exitstatus).to eq(15) + it "installs the bundle relatively to repository root, when Bundler run from the same directory" do + bundle "config set path vendor/bundle", dir: bundled_app.parent + bundle "install --gemfile='#{bundled_app}/Gemfile'", dir: bundled_app.parent + expect(out).to include("installed into `./bundled_app/vendor/bundle`") + expect(bundled_app("vendor/bundle")).to be_directory + expect(the_bundle).to include_gems "myrack 1.0.0" end - 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" - - expect(vendored_gems("gems/myrack-1.0.0")).to be_directory + it "installs the bundle relatively to repository root, when Bundler run from a different directory" do + bundle "config set path vendor/bundle", dir: bundled_app + bundle "install --gemfile='#{bundled_app}/Gemfile'", dir: bundled_app.parent + expect(out).to include("installed into `./bundled_app/vendor/bundle`") + expect(bundled_app("vendor/bundle")).to be_directory expect(the_bundle).to include_gems "myrack 1.0.0" end - 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 - expect(out).to include("installed into `./vendor/bundle`") - expect(bundled_app("../vendor/bundle")).to be_directory - expect(the_bundle).to include_gems "myrack 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 - expect(out).to include("installed into `./bundled_app/bundle`") - expect(bundled_app("bundle")).to be_directory - expect(bundled_app("bundle/ruby")).to be_directory + it "installs the standalone bundle relative to the cwd" do + 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) - expect(out).to include("installed into `../bundle`") - expect(bundled_app("bundle")).to be_directory - expect(bundled_app("bundle/ruby")).to be_directory - end + 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 end end @@ -109,7 +97,7 @@ RSpec.describe "bundle install" do context "when set via #{type}" do it "installs gems to a path if one is specified" do set_bundle_path(type, bundled_app("vendor2").to_s) - bundle "config set --local path vendor/bundle" + bundle_config "path vendor/bundle" bundle :install expect(vendored_gems("gems/myrack-1.0.0")).to be_directory @@ -119,7 +107,7 @@ RSpec.describe "bundle install" do it "installs gems to ." do set_bundle_path(type, ".") - bundle "config set --global disable_shared_gems true" + bundle_config_global "disable_shared_gems true" bundle :install @@ -150,7 +138,7 @@ RSpec.describe "bundle install" do end it "installs gems to BUNDLE_PATH from .bundle/config" do - config "BUNDLE_PATH" => bundled_app("vendor/bundle").to_s + bundle_config "BUNDLE_PATH" => bundled_app("vendor/bundle").to_s bundle :install @@ -159,7 +147,7 @@ RSpec.describe "bundle install" do end it "sets BUNDLE_PATH as the first argument to bundle install" do - bundle "config set --local path ./vendor/bundle" + bundle_config "path ./vendor/bundle" bundle :install expect(vendored_gems("gems/myrack-1.0.0")).to be_directory @@ -169,7 +157,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 "myrack", "1.1.0", to_system: true - bundle "config set --local path ./vendor/bundle" + bundle_config "path ./vendor/bundle" bundle :install expect(vendored_gems("gems/myrack-1.0.0")).to be_directory @@ -186,19 +174,19 @@ RSpec.describe "bundle install" do gem "very_simple_binary" G - bundle "config set --local path ./vendor/bundle" + bundle_config "path ./vendor/bundle" bundle :install 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" - vendored_gems("extensions").rmtree + FileUtils.rm_rf vendored_gems("extensions") run "require 'very_simple_binary_c'", raise_on_error: false expect(err).to include("Bundler::GemNotFound") - bundle "config set --local path ./vendor/bundle" + bundle_config "path ./vendor/bundle" bundle :install expect(vendored_gems("gems/very_simple_binary-1.0")).to be_directory @@ -218,7 +206,7 @@ RSpec.describe "bundle install" do gem "myrack" G - bundle "config set --local path bundle" + bundle_config "path bundle" bundle :install, raise_on_error: false expect(err).to include("file already exists") end diff --git a/spec/bundler/install/prereleases_spec.rb b/spec/bundler/install/prereleases_spec.rb index 57764ce722..9f764d127c 100644 --- a/spec/bundler/install/prereleases_spec.rb +++ b/spec/bundler/install/prereleases_spec.rb @@ -41,7 +41,7 @@ RSpec.describe "bundle install" do build_repo3 do build_gem "myrack" end - FileUtils.rm_rf Dir[gem_repo3("prerelease*")] + FileUtils.rm_r Dir[gem_repo3("prerelease*")] install_gemfile <<-G source "https://gem.repo3" diff --git a/spec/bundler/install/process_lock_spec.rb b/spec/bundler/install/process_lock_spec.rb index 707d2fde5e..b096291d1a 100644 --- a/spec/bundler/install/process_lock_spec.rb +++ b/spec/bundler/install/process_lock_spec.rb @@ -21,20 +21,93 @@ RSpec.describe "process lock spec" do expect(the_bundle).to include_gems "myrack 1.0" end + context "when creating a lock raises Errno::ENOTSUP" do + before { allow(File).to receive(:open).and_raise(Errno::ENOTSUP) } + + it "skips creating the lockfile 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::EPERM" do before { allow(File).to receive(:open).and_raise(Errno::EPERM) } - it "raises a friendly error" do - expect { Bundler::ProcessLock.lock(default_bundle_path) }.to raise_error(Bundler::GenericSystemCallError) + it "skips creating the lockfile 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 "raises a friendly error" do - expect { Bundler::ProcessLock.lock(default_bundle_path) }.to raise_error(Bundler::GenericSystemCallError) + it "skips creating the lockfile and yields" do + processed = false + Bundler::ProcessLock.lock(default_bundle_path) { processed = true } + + expect(processed).to eq true end end + + it "refreshes gem specification cache after waiting for lock" do + build_repo2 do + build_gem "myrack", "1.0.0" + end + + gemfile <<-G + source "https://gem.repo2" + gem "myrack" + G + + # First, install the gem so it's available + bundle "install" + expect(out).to include("Installing myrack") + + # Queue for thread-safe communication + lock_acquired = Queue.new + can_release_lock = Queue.new + install_output = Queue.new + + # Thread holds lock (simulating another bundle process that just finished installing) + thread = Thread.new do + Bundler::ProcessLock.lock(default_bundle_path) do + # Signal that we have the lock + lock_acquired << true + # Wait until main thread signals we can release + can_release_lock.pop + end + end + + # Wait for thread to acquire lock + lock_acquired.pop + + # Start another install in a thread - it will wait for the lock + install_thread = Thread.new do + bundle "install", verbose: true + install_output << out + end + + # Give subprocess time to start and begin waiting for lock + sleep 0.5 + + # Signal thread to release the lock + can_release_lock << true + + # Wait for both threads to complete + thread.join + install_thread.join + + second_install_out = install_output.pop + + expect(the_bundle).to include_gems "myrack 1.0.0" + # The second install should have refreshed its cache after acquiring + # the lock and seen that myrack was already installed + expect(second_install_out).to include("Using myrack") + end end end diff --git a/spec/bundler/install/yanked_spec.rb b/spec/bundler/install/yanked_spec.rb index 6919662671..c92af7bfb0 100644 --- a/spec/bundler/install/yanked_spec.rb +++ b/spec/bundler/install/yanked_spec.rb @@ -1,13 +1,11 @@ # frozen_string_literal: true RSpec.context "when installing a bundle that includes yanked gems" do - before(:each) do + it "throws an error when the original gem version is yanked" do build_repo4 do build_gem "foo", "9.0.0" end - end - it "throws an error when the original gem version is yanked" do lockfile <<-L GEM remote: https://gem.repo4 @@ -49,7 +47,7 @@ RSpec.context "when installing a bundle that includes yanked gems" do foo (= 1.0.0) BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -116,7 +114,11 @@ RSpec.context "when installing a bundle that includes yanked gems" do 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" + build_repo4 do + build_gem "foo", "9.0.0" + end + + bundle_config "force_ruby_platform true" install_gemfile <<-G, raise_on_error: false source "https://gem.repo4" @@ -152,7 +154,7 @@ RSpec.context "when resolving a bundle that includes yanked gems, but unlocking bar BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L gemfile <<-G @@ -180,7 +182,7 @@ RSpec.context "when resolving a bundle that includes yanked gems, but unlocking foo BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end diff --git a/spec/bundler/lock/git_spec.rb b/spec/bundler/lock/git_spec.rb index 0e08b7ee30..c9f76115dc 100644 --- a/spec/bundler/lock/git_spec.rb +++ b/spec/bundler/lock/git_spec.rb @@ -19,7 +19,7 @@ RSpec.describe "bundle lock with git gems" do it "doesn't print errors even if running lock after removing the cache" do install_gemfile_with_foo_as_a_git_dependency - FileUtils.rm_rf(Dir[default_cache_path("git/foo-1.0-*")].first) + FileUtils.rm_r(Dir[default_cache_path("git/foo-1.0-*")].first) bundle "lock --verbose" @@ -60,7 +60,7 @@ RSpec.describe "bundle lock with git gems" do foo! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install", raise_on_error: false @@ -123,7 +123,7 @@ RSpec.describe "bundle lock with git gems" do foo! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install" @@ -161,7 +161,7 @@ RSpec.describe "bundle lock with git gems" do foo! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install" @@ -206,7 +206,7 @@ RSpec.describe "bundle lock with git gems" do activesupport BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L gemfile <<~G @@ -220,4 +220,39 @@ RSpec.describe "bundle lock with git gems" do expect(lockfile).to include("securerandom (0.3.2)") end + + it "does not lock versions that don't exist in the repository when changing a GIT direct dep to a GEM direct dep" do + build_repo4 do + build_gem "ruby-lsp", "0.16.1" + end + + path = lib_path("ruby-lsp") + revision = build_git("ruby-lsp", "0.16.2", path: path).ref_for("HEAD") + + lockfile <<~L + GIT + remote: #{path} + revision: #{revision} + specs: + ruby-lsp (0.16.2) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ruby-lsp! + + BUNDLED WITH + #{Bundler::VERSION} + L + + gemfile <<~G + source "https://gem.repo4" + gem "ruby-lsp" + G + + bundle "lock" + + expect(lockfile).to include("ruby-lsp (0.16.1)") + end end diff --git a/spec/bundler/lock/lockfile_spec.rb b/spec/bundler/lock/lockfile_spec.rb index 2fa9425914..654ac02aa7 100644 --- a/spec/bundler/lock/lockfile_spec.rb +++ b/spec/bundler/lock/lockfile_spec.rb @@ -29,7 +29,7 @@ RSpec.describe "the lockfile format" do myrack #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -80,7 +80,7 @@ RSpec.describe "the lockfile format" do myrack BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -109,7 +109,7 @@ RSpec.describe "the lockfile format" do #{version} L - install_gemfile <<-G, verbose: true, preserve_ruby_flags: true + install_gemfile <<-G, verbose: true source "https://gem.repo4" gem "myrack" @@ -168,7 +168,7 @@ RSpec.describe "the lockfile format" do myrack (> 0) BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -215,7 +215,7 @@ RSpec.describe "the lockfile format" do myrack BUNDLED WITH - #{current_version} + #{current_version} G end @@ -246,7 +246,7 @@ RSpec.describe "the lockfile format" do myrack-obama #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -277,7 +277,7 @@ RSpec.describe "the lockfile format" do myrack-obama (>= 1.0) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -324,7 +324,7 @@ RSpec.describe "the lockfile format" do myrack-obama (>= 1.0)! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -371,7 +371,7 @@ RSpec.describe "the lockfile format" do myrack-obama (>= 1.0)! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L lockfile lockfile_without_credentials @@ -432,7 +432,7 @@ RSpec.describe "the lockfile format" do myrack-obama (>= 1.0)! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L lockfile lockfile_with_credentials @@ -468,7 +468,7 @@ RSpec.describe "the lockfile format" do net-sftp #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G expect(the_bundle).to include_gems "net-sftp 1.1.1", "net-ssh 1.0.0" @@ -504,7 +504,7 @@ RSpec.describe "the lockfile format" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -540,7 +540,7 @@ RSpec.describe "the lockfile format" do myrack BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install" @@ -579,7 +579,7 @@ RSpec.describe "the lockfile format" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -615,7 +615,7 @@ RSpec.describe "the lockfile format" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -651,7 +651,7 @@ RSpec.describe "the lockfile format" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -710,7 +710,7 @@ RSpec.describe "the lockfile format" do ckeditor! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "lock" @@ -737,7 +737,7 @@ RSpec.describe "the lockfile format" do ckeditor! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -770,7 +770,7 @@ RSpec.describe "the lockfile format" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -786,7 +786,6 @@ RSpec.describe "the lockfile format" do c.no_checksum "foo", "1.0" end - bundle "config set cache_all true" bundle :cache bundle :install, local: true @@ -807,7 +806,7 @@ RSpec.describe "the lockfile format" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -855,7 +854,7 @@ RSpec.describe "the lockfile format" do myrack #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -883,7 +882,7 @@ RSpec.describe "the lockfile format" do myrack! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -926,7 +925,7 @@ RSpec.describe "the lockfile format" do thin #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -975,7 +974,7 @@ RSpec.describe "the lockfile format" do rails #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -983,7 +982,7 @@ RSpec.describe "the lockfile format" 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 + # See https://github.com/ruby/rubygems/commit/03dbac93a3396a80db258d9bc63500333c25bd2f build_gem "double_deps", "1.0", skip_validation: true do |s| s.add_dependency "net-ssh", ">= 1.0.0" s.add_dependency "net-ssh" @@ -1016,7 +1015,7 @@ RSpec.describe "the lockfile format" do double_deps #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -1047,7 +1046,7 @@ RSpec.describe "the lockfile format" do myrack-obama (>= 1.0) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -1078,7 +1077,7 @@ RSpec.describe "the lockfile format" do myrack-obama (>= 1.0) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -1113,7 +1112,7 @@ RSpec.describe "the lockfile format" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -1148,7 +1147,7 @@ RSpec.describe "the lockfile format" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -1183,7 +1182,7 @@ RSpec.describe "the lockfile format" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -1216,7 +1215,7 @@ RSpec.describe "the lockfile format" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -1238,7 +1237,7 @@ RSpec.describe "the lockfile format" do myrack #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G install_gemfile <<-G @@ -1262,7 +1261,7 @@ RSpec.describe "the lockfile format" do myrack #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -1306,7 +1305,7 @@ RSpec.describe "the lockfile format" do google-protobuf #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -1341,7 +1340,7 @@ RSpec.describe "the lockfile format" do platform_specific #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end end @@ -1378,7 +1377,7 @@ RSpec.describe "the lockfile format" do myrack #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -1406,7 +1405,7 @@ RSpec.describe "the lockfile format" do myrack #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -1434,7 +1433,7 @@ RSpec.describe "the lockfile format" do myrack (= 1.0) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -1462,7 +1461,7 @@ RSpec.describe "the lockfile format" do myrack (= 1.0) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -1511,7 +1510,7 @@ RSpec.describe "the lockfile format" do myrack (> 0.9, < 1.0) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -1539,10 +1538,10 @@ RSpec.describe "the lockfile format" do myrack (> 0.9, < 1.0) #{checksums} RUBY VERSION - #{Bundler::RubyVersion.system} + #{Bundler::RubyVersion.system} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -1560,7 +1559,7 @@ RSpec.describe "the lockfile format" do myrack_middleware BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L install_gemfile <<-G @@ -1583,10 +1582,104 @@ RSpec.describe "the lockfile format" do myrack_middleware BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end + it "raises a clear error when frozen mode is set and lockfile is missing deps, and does not install any gems" do + lockfile <<-L + GEM + remote: https://gem.repo2/ + specs: + myrack_middleware (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack_middleware + + BUNDLED WITH + #{Bundler::VERSION} + L + + install_gemfile <<-G, env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false + source "https://gem.repo2" + gem "myrack_middleware" + G + + expect(err).to include("Bundler found incorrect dependencies in the lockfile for myrack_middleware-1.0") + expect(err).to include("myrack: gemspec specifies = 0.9.1, not in lockfile") + expect(the_bundle).not_to include_gems "myrack_middleware 1.0" + end + + it "raises a clear error when frozen mode is set and lockfile is missing entries in CHECKSUMS section, and does not install any gems" do + lockfile <<-L + GEM + remote: https://gem.repo2/ + specs: + myrack_middleware (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack_middleware + + CHECKSUMS + + BUNDLED WITH + #{Bundler::VERSION} + L + + install_gemfile <<-G, env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false + source "https://gem.repo2" + gem "myrack_middleware" + G + + expect(err).to eq <<~L.strip + Your lockfile is missing a CHECKSUMS entry for \"myrack_middleware\", but can't be updated because frozen mode is set + + Run `bundle install` elsewhere and add the updated Gemfile.lock to version control. + L + + expect(the_bundle).not_to include_gems "myrack_middleware 1.0" + end + + it "raises a clear error when frozen mode is set and lockfile has empty checksums in CHECKSUMS section, and does not install any gems" do + lockfile <<-L + GEM + remote: https://gem.repo2/ + specs: + myrack (0.9.1) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack + + CHECKSUMS + myrack (0.9.1) + + BUNDLED WITH + #{Bundler::VERSION} + L + + install_gemfile <<-G, env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false + source "https://gem.repo2" + gem "myrack" + G + + expect(err).to eq <<~L.strip + Your lockfile has an empty CHECKSUMS entry for \"myrack\", but can't be updated because frozen mode is set + + Run `bundle install` elsewhere and add the updated Gemfile.lock to version control. + L + + expect(the_bundle).not_to include_gems "myrack 0.9.1" + end + it "automatically fixes the lockfile when it's missing deps, they conflict with other locked deps, but conflicts are fixable" do build_repo4 do build_gem "other_dep", "0.9" @@ -1615,7 +1708,7 @@ RSpec.describe "the lockfile format" do other_dep BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L install_gemfile <<-G @@ -1642,7 +1735,7 @@ RSpec.describe "the lockfile format" do other_dep BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -1677,7 +1770,7 @@ RSpec.describe "the lockfile format" do another_dep_middleware BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L install_gemfile <<-G @@ -1705,7 +1798,7 @@ RSpec.describe "the lockfile format" do myrack_middleware BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -1737,7 +1830,7 @@ RSpec.describe "the lockfile format" do other_dep BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L install_gemfile <<-G, raise_on_error: false @@ -1780,7 +1873,7 @@ RSpec.describe "the lockfile format" do direct_dependency BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G install_gemfile <<-G @@ -1798,13 +1891,13 @@ RSpec.describe "the lockfile format" do indirect_dependency (1.2.3) PLATFORMS - #{lockfile_platforms("ruby", generic_local_platform, defaults: [])} + #{lockfile_platforms(generic_default_locked_platform || local_platform, defaults: ["ruby"])} DEPENDENCIES direct_dependency BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -1822,7 +1915,7 @@ RSpec.describe "the lockfile format" do myrack_middleware BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L install_gemfile <<-G @@ -1845,7 +1938,245 @@ RSpec.describe "the lockfile format" do myrack_middleware BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} + L + end + + it "automatically fixes the lockfile when it includes a gem under the correct GIT section, but also under an incorrect GEM section, with a higher version, and with no explicit Gemfile requirement" do + git = build_git "foo" + + gemfile <<~G + source "https://gem.repo1/" + gem "foo", git: "#{lib_path("foo-1.0")}" + G + + # If the lockfile erroneously lists platform versions of the gem + # that don't match the locked version of the git repo we should remove them. + + lockfile <<~L + GIT + remote: #{lib_path("foo-1.0")} + revision: #{git.ref_for("main")} + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + foo (1.1-x86_64-linux-gnu) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" + + expect(lockfile).to eq <<~L + GIT + remote: #{lib_path("foo-1.0")} + revision: #{git.ref_for("main")} + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "automatically fixes the lockfile when it includes a gem under the correct GIT section, but also under an incorrect GEM section, with a higher version" do + git = build_git "foo" + + gemfile <<~G + source "https://gem.repo1/" + gem "foo", "= 1.0", git: "#{lib_path("foo-1.0")}" + G + + # If the lockfile erroneously lists platform versions of the gem + # that don't match the locked version of the git repo we should remove them. + + lockfile <<~L + GIT + remote: #{lib_path("foo-1.0")} + revision: #{git.ref_for("main")} + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + foo (1.1-x86_64-linux-gnu) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo (= 1.0)! + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" + + expect(lockfile).to eq <<~L + GIT + remote: #{lib_path("foo-1.0")} + revision: #{git.ref_for("main")} + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo (= 1.0)! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "automatically fixes the lockfile when it has incorrect deps, keeping the locked version" do + build_repo4 do + build_gem "net-smtp", "0.5.0" do |s| + s.add_dependency "net-protocol" + end + + build_gem "net-smtp", "0.5.1" do |s| + s.add_dependency "net-protocol" + end + + build_gem "net-protocol", "0.2.2" + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + gem "net-smtp" + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + net-protocol (0.2.2) + net-smtp (0.5.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + net-smtp + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + net-protocol (0.2.2) + net-smtp (0.5.0) + net-protocol + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + net-smtp + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "successfully updates the lockfile when a new gem is added in the Gemfile includes a gem that shouldn't be included" do + build_repo4 do + build_gem "logger", "1.7.0" + build_gem "rack", "3.2.0" + build_gem "net-smtp", "0.5.0" + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + gem "logger" + gem "net-smtp" + + install_if -> { false } do + gem 'rack', github: 'rack/rack' + end + G + + lockfile <<~L + GIT + remote: https://github.com/rack/rack.git + revision: 2fface9ac09fc582a81386becd939c987ad33f99 + specs: + rack (3.2.0) + + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + logger (1.7.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + logger + rack! + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" + + expect(lockfile).to eq <<~L + GIT + remote: https://github.com/rack/rack.git + revision: 2fface9ac09fc582a81386becd939c987ad33f99 + specs: + rack (3.2.0) + + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + logger (1.7.0) + net-smtp (0.5.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + logger + net-smtp + rack! + + BUNDLED WITH + #{Bundler::VERSION} L end @@ -1878,7 +2209,7 @@ RSpec.describe "the lockfile format" do minitest-bisect BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L cache_gems "minitest-bisect-1.6.0", "path_expander-1.1.1", gem_repo: gem_repo4 @@ -1899,7 +2230,7 @@ RSpec.describe "the lockfile format" do minitest-bisect BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -1944,11 +2275,11 @@ RSpec.describe "the lockfile format" do minitest-bisect BUNDLED WITH - #{Bundler::VERSION} + #{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(out).to include("re-resolving dependencies because your lockfile includes \"minitest-bisect\" but not some of its dependencies") expect(lockfile).to eq <<~L GEM @@ -1965,7 +2296,7 @@ RSpec.describe "the lockfile format" do minitest-bisect BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -2065,7 +2396,7 @@ RSpec.describe "the lockfile format" do myrack BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L install_gemfile <<-G, raise_on_error: false @@ -2079,19 +2410,7 @@ RSpec.describe "the lockfile format" do private - def prerelease?(version) - Gem::Version.new(version).prerelease? - end - def previous_major(version) version.split(".").map.with_index {|v, i| i == 0 ? v.to_i - 1 : v }.join(".") end - - def bump_minor(version) - bump(version, 1) - end - - def bump(version, segment) - version.split(".").map.with_index {|v, i| i == segment ? v.to_i + 1 : v }.join(".") - end end diff --git a/spec/bundler/other/cli_dispatch_spec.rb b/spec/bundler/other/cli_dispatch_spec.rb index 48b285045a..a2c745b070 100644 --- a/spec/bundler/other/cli_dispatch_spec.rb +++ b/spec/bundler/other/cli_dispatch_spec.rb @@ -4,17 +4,17 @@ RSpec.describe "bundle command names" do it "work when given fully" do bundle "install", raise_on_error: false expect(err).to eq("Could not locate Gemfile") - expect(last_command.stdboth).not_to include("Ambiguous command") + expect(stdboth).not_to include("Ambiguous command") end it "work when not ambiguous" do bundle "ins", raise_on_error: false expect(err).to eq("Could not locate Gemfile") - expect(last_command.stdboth).not_to include("Ambiguous command") + expect(stdboth).not_to include("Ambiguous command") end it "print a friendly error when ambiguous" do bundle "in", raise_on_error: false - expect(err).to eq("Ambiguous command in matches [info, init, inject, install]") + expect(err).to eq("Ambiguous command in matches [info, init, install]") end end diff --git a/spec/bundler/other/cli_man_pages_spec.rb b/spec/bundler/other/cli_man_pages_spec.rb index 84ffca14e6..4e8f155309 100644 --- a/spec/bundler/other/cli_man_pages_spec.rb +++ b/spec/bundler/other/cli_man_pages_spec.rb @@ -1,49 +1,78 @@ # frozen_string_literal: true RSpec.describe "bundle commands" do - it "expects all commands to have a man page" do - Bundler::CLI.all_commands.each_key do |command_name| - next if command_name == "cli_help" + it "expects all commands to have all options and subcommands documented" do + check_commands!(Bundler::CLI) - expect(man_page(command_name)).to exist + Bundler::CLI.subcommand_classes.each_value do |klass| + check_commands!(klass) end end - it "expects all commands to have all options documented" do - Bundler::CLI.all_commands.each do |command_name, command| - next if command_name == "cli_help" + private - man_page_content = man_page(command_name).read + def check_commands!(command_class) + command_class.commands.each do |command_name, command| + if command.is_a?(Bundler::Thor::HiddenCommand) + man_page = man_page(command_name) + expect(man_page).not_to exist + expect(main_man_page.read).not_to include("bundle #{command_name}") + elsif command_class == Bundler::CLI + man_page = man_page(command_name) + expect(man_page).to exist - command.options.each do |_, option| - aliases = option.aliases - formatted_aliases = aliases.sort.map {|name| "`#{name}`" }.join(", ") if aliases + check_options!(command, man_page) + else + man_page = man_page(command.ancestor_name) + expect(man_page).to exist - help = if option.type == :boolean - "* #{append_aliases("`#{option.switch_name}`", formatted_aliases)}:" - elsif option.enum - formatted_aliases = "`#{option.switch_name}`" if aliases.empty? && option.lazy_default - "* #{prepend_aliases(option.enum.sort.map {|enum| "`#{option.switch_name}=#{enum}`" }.join(", "), formatted_aliases)}:" - else - names = [option.switch_name, *aliases] - value = - case option.type - when :array then "<list>" - when :numeric then "<number>" - else option.name.upcase - end + check_options!(command, man_page) + check_subcommand!(command_name, man_page) + end + end + end - value = option.type != :numeric && option.lazy_default ? "[=#{value}]" : "=#{value}" + def check_options!(command, man_page) + command.options.each do |_, option| + check_option!(option, man_page) + end + end + + def check_option!(option, man_page) + man_page_content = man_page.read - "* #{names.map {|name| "`#{name}#{value}`" }.join(", ")}:" + aliases = option.aliases + formatted_aliases = aliases.sort.map {|name| "`#{name}`" }.join(", ") if aliases + + help = if option.type == :boolean + "* #{append_aliases("`#{option.switch_name}`", formatted_aliases)}:" + elsif option.enum + formatted_aliases = "`#{option.switch_name}`" if aliases.empty? && option.lazy_default + "* #{prepend_aliases(option.enum.sort.map {|enum| "`#{option.switch_name}=#{enum}`" }.join(", "), formatted_aliases)}:" + else + names = [option.switch_name, *aliases] + value = + case option.type + when :array then "<list>" + when :numeric then "<number>" + else option.name.upcase end - expect(man_page_content).to include(help) - end + value = option.type != :numeric && option.lazy_default ? "[=#{value}]" : "=#{value}" + + "* #{names.map {|name| "`#{name}#{value}`" }.join(", ")}:" + end + + if option.banner.include?("(removed)") + expect(man_page_content).not_to include(help) + else + expect(man_page_content).to include(help) end end - private + def check_subcommand!(name, man_page) + expect(man_page.read).to match(name) + end def append_aliases(text, aliases) return text if aliases.empty? @@ -57,7 +86,15 @@ RSpec.describe "bundle commands" do "#{aliases}, #{text}" end + def man_page_content(command_name) + man_page(command_name).read + end + def man_page(command_name) source_root.join("lib/bundler/man/bundle-#{command_name}.1.ronn") end + + def main_man_page + source_root.join("lib/bundler/man/bundle.1.ronn") + end end diff --git a/spec/bundler/other/ext_spec.rb b/spec/bundler/other/ext_spec.rb index 9fc0414b4d..a883eefe06 100644 --- a/spec/bundler/other/ext_spec.rb +++ b/spec/bundler/other/ext_spec.rb @@ -1,55 +1,20 @@ # frozen_string_literal: true -RSpec.describe "Gem::Specification#match_platform" do +RSpec.describe "Gem::Specification#installable_on_platform?" do it "does not match platforms other than the gem platform" do darwin = gem "lol", "1.0", "platform_specific-1.0-x86-darwin-10" - expect(darwin.match_platform(pl("java"))).to eq(false) + expect(darwin.installable_on_platform?(pl("java"))).to eq(false) end context "when platform is a string" do it "matches when platform is a string" do lazy_spec = Bundler::LazySpecification.new("lol", "1.0", "universal-mingw32") - expect(lazy_spec.match_platform(pl("x86-mingw32"))).to eq(true) - expect(lazy_spec.match_platform(pl("x64-mingw32"))).to eq(true) + expect(lazy_spec.installable_on_platform?(pl("x86-mingw32"))).to eq(true) + expect(lazy_spec.installable_on_platform?(pl("x64-mingw32"))).to eq(true) end end end -RSpec.describe "Bundler::GemHelpers#generic" do - include Bundler::GemHelpers - - it "converts non-windows platforms into ruby" do - expect(generic(pl("x86-darwin-10"))).to eq(pl("ruby")) - expect(generic(pl("ruby"))).to eq(pl("ruby")) - end - - it "converts java platform variants into java" do - expect(generic(pl("universal-java-17"))).to eq(pl("java")) - expect(generic(pl("java"))).to eq(pl("java")) - end - - it "converts mswin platform variants into x86-mswin32" do - expect(generic(pl("mswin32"))).to eq(pl("x86-mswin32")) - expect(generic(pl("i386-mswin32"))).to eq(pl("x86-mswin32")) - expect(generic(pl("x86-mswin32"))).to eq(pl("x86-mswin32")) - end - - it "converts 32-bit mingw platform variants into x86-mingw32" do - expect(generic(pl("mingw32"))).to eq(pl("x86-mingw32")) - expect(generic(pl("i386-mingw32"))).to eq(pl("x86-mingw32")) - expect(generic(pl("x86-mingw32"))).to eq(pl("x86-mingw32")) - end - - it "converts 64-bit mingw platform variants into x64-mingw32" 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 before do install_gemfile <<-G diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb index 036c855c4e..ab7589d698 100644 --- a/spec/bundler/other/major_deprecation_spec.rb +++ b/spec/bundler/other/major_deprecation_spec.rb @@ -14,81 +14,67 @@ RSpec.describe "major deprecations" do describe ".clean_env" do before do source = "Bundler.clean_env" - bundle "exec ruby -e #{source.dump}" + bundle "exec ruby -e #{source.dump}", raise_on_error: false end - 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)" + it "is removed in favor of .unbundled_env and shows a helpful error message about it" do + expect(err).to include \ + "`Bundler.clean_env` has been removed in favor of `Bundler.unbundled_env`. " \ + "If you instead want the environment before bundler was originally loaded, use `Bundler.original_env`" \ end - - pending "is removed and shows a helpful error message about it", bundler: "3" end describe ".with_clean_env" do before do source = "Bundler.with_clean_env {}" - bundle "exec ruby -e #{source.dump}" + bundle "exec ruby -e #{source.dump}", raise_on_error: false end - 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` " \ - "(called at -e:1)" + it "is removed in favor of .unbundled_env and shows a helpful error message about it" do + expect(err).to include( + "`Bundler.with_clean_env` has been removed in favor of `Bundler.with_unbundled_env`. " \ + "If you instead want the environment before bundler was originally loaded, use `Bundler.with_original_env`" ) end - - pending "is removed and shows a helpful error message about it", bundler: "3" end describe ".clean_system" do before do source = "Bundler.clean_system('ls')" - bundle "exec ruby -e #{source.dump}" + bundle "exec ruby -e #{source.dump}", raise_on_error: false end - 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` " \ - "(called at -e:1)" + it "is removed in favor of .unbundled_system and shows a helpful error message about it" do + expect(err).to include( + "`Bundler.clean_system` has been removed 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`" \ ) end - - pending "is removed and shows a helpful error message about it", bundler: "3" end describe ".clean_exec" do before do source = "Bundler.clean_exec('ls')" - bundle "exec ruby -e #{source.dump}" + bundle "exec ruby -e #{source.dump}", raise_on_error: false end - 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` " \ - "(called at -e:1)" + it "is removed in favor of .unbundled_exec and shows a helpful error message about it" do + expect(err).to include( + "`Bundler.clean_exec` has been removed 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`" \ ) end - - pending "is removed and shows a helpful error message about it", bundler: "3" end describe ".environment" do before do source = "Bundler.environment" - bundle "exec ruby -e #{source.dump}" + bundle "exec ruby -e #{source.dump}", raise_on_error: false end - 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)" + it "is removed in favor of .load and shows a helpful error message about it" do + expect(err).to include "Bundler.environment has been removed in favor of Bundler.load" end - - pending "is removed and shows a helpful error message about it", bundler: "3" end end @@ -97,11 +83,9 @@ RSpec.describe "major deprecations" 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" + it "is removed and shows a helpful error message about it" do + expect(err).to include "The `--no-keep-file-descriptors` has been removed. `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 @@ -121,16 +105,14 @@ RSpec.describe "major deprecations" do bundle "check --path vendor/bundle", raise_on_error: false end - 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 " \ - "path 'vendor/bundle'`, and stop using this flag" + it "fails with a helpful error" do + expect(err).to include( + "The `--path` flag has been removed because it relied on being " \ + "remembered across bundler invocations, which bundler no longer " \ + "does. Instead please use `bundle config set path 'vendor/bundle'`, " \ + "and stop using this flag" ) end - - pending "fails with a helpful error", bundler: "3" end context "bundle check --path=" do @@ -143,16 +125,34 @@ RSpec.describe "major deprecations" do bundle "check --path=vendor/bundle", raise_on_error: false end - 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 " \ - "path 'vendor/bundle'`, and stop using this flag" + it "fails with a helpful error" do + expect(err).to include( + "The `--path` flag has been removed because it relied on being " \ + "remembered across bundler invocations, which bundler no longer " \ + "does. Instead please use `bundle config set path 'vendor/bundle'`, " \ + "and stop using this flag" ) end + end - pending "fails with a helpful error", bundler: "3" + context "bundle binstubs --path=" do + before do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + bundle "binstubs myrack --path=binpath", raise_on_error: false + end + + it "fails with a helpful error" do + expect(err).to include( + "The `--path` flag has been removed because it relied on being " \ + "remembered across bundler invocations, which bundler no longer " \ + "does. Instead please use `bundle config set bin 'binpath'`, " \ + "and stop using this flag" + ) + end end context "bundle cache --all" do @@ -162,19 +162,37 @@ RSpec.describe "major deprecations" do gem "myrack" G - bundle "cache --all", raise_on_error: false + bundle "cache --all --verbose", raise_on_error: false end - 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 " \ - "longer do in future versions. Instead please use `bundle config set " \ - "cache_all true`, and stop using this flag" + it "fails with a helpful error" do + expect(err).to include( + "The `--all` flag has been removed because it relied on being " \ + "remembered across bundler invocations, which bundler no longer " \ + "does. Instead please use `bundle config set cache_all true`, " \ + "and stop using this flag" ) end + end + + context "bundle cache --no-all" do + before do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G - pending "fails with a helpful error", bundler: "3" + bundle "cache --no-all", raise_on_error: false + end + + it "fails with a helpful error" do + expect(err).to include( + "The `--no-all` flag has been removed because it relied on being " \ + "remembered across bundler invocations, which bundler no longer " \ + "does. Instead please use `bundle config set cache_all false`, " \ + "and stop using this flag" + ) + end end context "bundle cache --path" do @@ -187,16 +205,74 @@ RSpec.describe "major deprecations" do bundle "cache --path foo", raise_on_error: false end - it "should print a deprecation warning", bundler: "< 3" do - expect(deprecations).to include( - "The `--path` flag is deprecated because its semantics are unclear. " \ + it "should print a removal error" do + expect(err).to include( + "The `--path` flag has been removed because its semantics were unclear. " \ + "Use `bundle config cache_path` to configure the path of your cache of gems, " \ + "and `bundle config path` to configure the path where your gems are installed, " \ + "and stop using this flag" + ) + end + end + + context "bundle cache --path=" do + before do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + bundle "cache --path=foo", raise_on_error: false + end + + it "should print a deprecation warning" do + expect(err).to include( + "The `--path` flag has been removed because its semantics were unclear. " \ "Use `bundle config cache_path` to configure the path of your cache of gems, " \ "and `bundle config path` to configure the path where your gems are installed, " \ "and stop using this flag" ) end + end + + context "bundle cache --frozen" do + before do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + bundle "cache --frozen", raise_on_error: false + end + + it "fails with a helpful error" do + expect(err).to include( + "The `--frozen` flag has been removed because it relied on being " \ + "remembered across bundler invocations, which bundler no longer " \ + "does. Instead please use `bundle config set frozen true`, " \ + "and stop using this flag" + ) + end + end - pending "fails with a helpful error", bundler: "3" + context "bundle cache --no-prune" do + before do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + bundle "cache --no-prune", raise_on_error: false + end + + it "fails with a helpful error" do + expect(err).to include( + "The `--no-prune` flag has been removed because it relied on being " \ + "remembered across bundler invocations, which bundler no longer " \ + "does. Instead please use `bundle config set no_prune true`, " \ + "and stop using this flag" + ) + end end describe "bundle config" do @@ -205,23 +281,23 @@ RSpec.describe "major deprecations" do bundle "config" end - it "warns", bundler: "3" do + it "warns", bundler: "4" 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: "5" end describe "old get interface" do before do - bundle "config waka" + bundle "config waka", raise_on_error: false end - it "warns", bundler: "3" do + it "warns", bundler: "4" 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: "5" end describe "old set interface" do @@ -229,11 +305,11 @@ RSpec.describe "major deprecations" do bundle "config waka wakapun" end - it "warns", bundler: "3" do + it "warns", bundler: "4" 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: "5" end describe "old set interface with --local" do @@ -241,11 +317,11 @@ RSpec.describe "major deprecations" do bundle "config --local waka wakapun" end - it "warns", bundler: "3" do + it "warns", bundler: "4" 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: "5" end describe "old set interface with --global" do @@ -253,11 +329,11 @@ RSpec.describe "major deprecations" do bundle "config --global waka wakapun" end - it "warns", bundler: "3" do + it "warns", bundler: "4" 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: "5" end describe "old unset interface" do @@ -265,11 +341,11 @@ RSpec.describe "major deprecations" do bundle "config --delete waka" end - it "warns", bundler: "3" do + it "warns", bundler: "4" 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: "5" end describe "old unset interface with --local" do @@ -277,11 +353,11 @@ RSpec.describe "major deprecations" do bundle "config --delete --local waka" end - it "warns", bundler: "3" do + it "warns", bundler: "4" 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: "5" end describe "old unset interface with --global" do @@ -289,11 +365,11 @@ RSpec.describe "major deprecations" do bundle "config --delete --global waka" end - it "warns", bundler: "3" do + it "warns", bundler: "4" 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: "5" end end @@ -305,12 +381,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: "4" 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: "5" it "does not warn when --all is passed" do bundle "update --all" @@ -320,17 +396,15 @@ RSpec.describe "major deprecations" do describe "bundle install --binstubs" do before do - install_gemfile <<-G, binstubs: true + install_gemfile <<-G, binstubs: true, raise_on_error: false source "https://gem.repo1" gem "myrack" G end - 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`") + it "fails with a helpful error" do + expect(err).to include("The --binstubs option has been removed in favor of `bundle binstubs --all`") end - - pending "fails with a helpful error", bundler: "3" end context "bundle install with both gems.rb and Gemfile present" do @@ -361,7 +435,7 @@ RSpec.describe "major deprecations" do context "bundle install with flags" do before do - bundle "config set --local path vendor/bundle" + bundle_config "path vendor/bundle" install_gemfile <<-G source "https://gem.repo1" @@ -383,76 +457,85 @@ RSpec.describe "major deprecations" do }.each do |name, expectations| option_name, value = *expectations flag_name = "--#{name}" + args = %w[true false].include?(value) ? flag_name : "#{flag_name} #{value}" context "with the #{flag_name} flag" do before do bundle "install" # to create a lockfile, which deployment or frozen need - bundle "install #{flag_name} #{value}" + + bundle "install #{args}", raise_on_error: false end - 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 #{option_name} #{value}`, and stop using this flag" + it "fails with a helpful error" do + expect(err).to include( + "The `#{flag_name}` flag has been removed because it relied on " \ + "being remembered across bundler invocations, which bundler no " \ + "longer does. Instead please use `bundle config set " \ + "#{option_name} #{value}`, and stop using this flag" ) end - - pending "fails with a helpful error", bundler: "3" end end end context "bundle install with multiple sources" do before do - install_gemfile <<-G + install_gemfile <<-G, raise_on_error: false source "https://gem.repo3" source "https://gem.repo1" G end - it "shows a deprecation", bundler: "< 3" do - expect(deprecations).to include( - "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." + it "fails with a helpful error" do + expect(err).to include( + "This Gemfile contains multiple global sources. " \ + "Each source after the first must include a block to indicate which gems " \ + "should come from that source" ) end - it "doesn't show lockfile deprecations if there's a lockfile", bundler: "< 3" do - bundle "install" + it "doesn't show lockfile deprecations if there's a lockfile" do + lockfile <<~L + GEM + remote: https://gem.repo3/ + remote: https://gem.repo1/ + specs: + + PLATFORMS + #{lockfile_platforms} - expect(deprecations).to include( - "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." + DEPENDENCIES + + BUNDLED WITH + #{Bundler::VERSION} + L + bundle "install", raise_on_error: false + + expect(err).to include( + "This Gemfile contains multiple global sources. " \ + "Each source after the first must include a block to indicate which gems " \ + "should come from that source" ) - expect(deprecations).not_to include( + expect(err).not_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." ) - bundle "config set --local frozen true" - bundle "install" - - expect(deprecations).to include( - "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." + bundle_config "frozen true" + bundle "install", raise_on_error: false + + expect(err).to include( + "This Gemfile contains multiple global sources. " \ + "Each source after the first must include a block to indicate which gems " \ + "should come from that source" ) - expect(deprecations).not_to include( + expect(err).not_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" end - context "bundle install in frozen mode with a lockfile with a single rubygems section with multiple remotes" do + context "bundle install with a lockfile with a single rubygems section with multiple remotes" do before do build_repo3 do build_gem "myrack", "0.9.1" @@ -479,19 +562,62 @@ RSpec.describe "major deprecations" do myrack! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L + end - bundle "config set --local frozen true" + it "shows an error" do + bundle "install", raise_on_error: false + + expect(err).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 + end - it "shows a deprecation", bundler: "< 3" do - bundle "install" + context "bundle install with a lockfile including X64_MINGW_LEGACY platform" do + before do + gemfile <<~G + source "https://gem.repo1" + gem "rake" + G + + lockfile <<~L + GEM + remote: https://rubygems.org/ + specs: + rake (10.3.2) + + PLATFORMS + ruby + x64-mingw32 - 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.") + DEPENDENCIES + rake + + BUNDLED WITH + #{Bundler::VERSION} + L end - pending "fails with a helpful error", bundler: "3" + it "warns a helpful error" do + bundle "install", raise_on_error: false + + expect(err).to include("Found x64-mingw32 in lockfile, which is deprecated and will be removed in the future.") + end + end + + context "with a global path source" do + before do + build_lib "foo" + + install_gemfile <<-G, raise_on_error: false + path "#{lib_path("foo-1.0")}" + gem 'foo' + G + end + + it "shows an error" do + expect(err).to include("You can no longer specify a path source by itself") + end end context "when Bundler.setup is run in a ruby script" do @@ -519,19 +645,41 @@ RSpec.describe "major deprecations" do context "when `bundler/deployment` is required in a ruby script" do before do - ruby <<-RUBY + ruby <<-RUBY, raise_on_error: false require 'bundler/deployment' RUBY end - it "should print a capistrano deprecation warning", bundler: "< 3" do - expect(deprecations).to include("Bundler no longer integrates " \ + it "should print a capistrano deprecation warning" do + expect(err).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 + end - pending "fails with a helpful error", bundler: "3" + context "when `bundler/capistrano` is required in a ruby script" do + before do + ruby <<-RUBY, raise_on_error: false + require 'bundler/capistrano' + RUBY + end + + it "fails with a helpful error" do + expect(err).to include("[REMOVED] The Bundler task for Capistrano. Please use https://github.com/capistrano/bundler") + end + end + + context "when `bundler/vlad` is required in a ruby script" do + before do + ruby <<-RUBY, raise_on_error: false + require 'bundler/vlad' + RUBY + end + + it "fails with a helpful error" do + expect(err).to include("[REMOVED] The Bundler task for Vlad") + end end context "bundle show" do @@ -544,14 +692,12 @@ RSpec.describe "major deprecations" do context "with --outdated flag" do before do - bundle "show --outdated" + bundle "show --outdated", raise_on_error: false end - 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") + it "fails with a helpful message" do + expect(err).to include("the `--outdated` flag to `bundle show` has been removed in favor of `bundle show --verbose`") end - - pending "fails with a helpful message", bundler: "3" end end @@ -564,28 +710,32 @@ RSpec.describe "major deprecations" do end context "with --install" do - it "shows a deprecation warning", bundler: "< 3" do - bundle "remove myrack --install" + it "fails with a helpful message" do + bundle "remove myrack --install", raise_on_error: false - expect(err).to include "[DEPRECATED] The `--install` flag has been deprecated. `bundle install` is triggered by default." + expect(err).to include "The `--install` flag has been removed. `bundle install` is triggered by default." end - - pending "fails with a helpful message", bundler: "3" end end - context "bundle viz", :realworld do + context "bundle viz" do before do - realworld_system_gems "ruby-graphviz --version 1.2.5" - create_file "gems.rb", "source 'https://gem.repo1'" - bundle "viz" + bundle "viz", raise_on_error: false end - 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" + it "fails with a helpful message" do + expect(err).to include "The `viz` command has been renamed to `graph` and moved to a plugin. See https://github.com/rubygems/bundler-graph" end + end - pending "fails with a helpful message", bundler: "3" + context "bundle inject" do + before do + bundle "inject", raise_on_error: false + end + + it "fails with a helpful message" do + expect(err).to include "The `inject` command has been replaced by the `add` command" + end end context "bundle plugin install --local_git" do @@ -595,20 +745,20 @@ RSpec.describe "major deprecations" do end end - it "prints a deprecation warning", bundler: "< 3" do - bundle "plugin install foo --local_git #{lib_path("foo-1.0")}" + it "fails with a helpful message" do + bundle "plugin install foo --local_git #{lib_path("foo-1.0")}", raise_on_error: false - expect(out).to include("Installed plugin foo") - expect(deprecations).to include "--local_git is deprecated, use --git" + expect(err).to include "--local_git has been removed, use --git" end - - pending "fails with a helpful message", bundler: "3" end - describe "deprecating rubocop" do + describe "removing rubocop" do before do - global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__COC" => "false", - "BUNDLE_GEM__CI" => "false", "BUNDLE_GEM__CHANGELOG" => "false" + bundle_config_global "gem.mit false" + bundle_config_global "gem.test false" + bundle_config_global "gem.coc false" + bundle_config_global "gem.ci false" + bundle_config_global "gem.changelog false" end context "bundle gem --rubocop" do @@ -616,9 +766,9 @@ RSpec.describe "major deprecations" do bundle "gem my_new_gem --rubocop", raise_on_error: false end - it "prints a deprecation warning", bundler: "< 3" do - expect(deprecations).to include \ - "--rubocop is deprecated, use --linter=rubocop" + it "prints an error" do + expect(err).to include \ + "--rubocop has been removed, use --linter=rubocop" end end @@ -627,32 +777,22 @@ RSpec.describe "major deprecations" do bundle "gem my_new_gem --no-rubocop", raise_on_error: false end - it "prints a deprecation warning", bundler: "< 3" do - expect(deprecations).to include \ - "--no-rubocop is deprecated, use --linter" + it "prints an error" do + expect(err).to include \ + "--no-rubocop has been removed, use --no-linter" end end + end - 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 - end - - 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 + context " bundle gem --ext parameter with no value" do + it "prints error when used before gem name" do + bundle "gem --ext foo", raise_on_error: false + expect(err).to include "Extensions can now be generated using C or Rust, so `--ext` with no arguments has been removed. Please select a language, e.g. `--ext=rust` to generate a Rust extension." end - 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 - end - - 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 + it "prints error when used after gem name" do + bundle "gem foo --ext", raise_on_error: false + expect(err).to include "Extensions can now be generated using C or Rust, so `--ext` with no arguments has been removed. Please select a language, e.g. `--ext=rust` to generate a Rust extension." end end end diff --git a/spec/bundler/plugins/command_spec.rb b/spec/bundler/plugins/command_spec.rb index f8dacb0e51..05d535a70c 100644 --- a/spec/bundler/plugins/command_spec.rb +++ b/spec/bundler/plugins/command_spec.rb @@ -53,6 +53,40 @@ RSpec.describe "command plugins" do expect(out).to eq("You gave me tacos, tofu, lasange") end + it "passes help flag to plugin" do + update_repo2 do + build_plugin "helpful" do |s| + s.write "plugins.rb", <<-RUBY + module Helpful + class Command + Bundler::Plugin::API.command "greet", self + + def exec(command, args) + if args.include?("--help") || args.include?("-h") + puts "Usage: bundle greet [NAME]" + else + puts "Hello" + end + end + end + end + RUBY + end + end + + bundle "plugin install helpful --source https://gem.repo2" + expect(out).to include("Installed plugin helpful") + + bundle "greet --help" + expect(out).to eq("Usage: bundle greet [NAME]") + + bundle "greet -h" + expect(out).to eq("Usage: bundle greet [NAME]") + + bundle "help greet" + expect(out).to eq("Usage: bundle greet [NAME]") + end + it "raises error on redeclaration of command" do update_repo2 do build_plugin "copycat" do |s| diff --git a/spec/bundler/plugins/hook_spec.rb b/spec/bundler/plugins/hook_spec.rb index 3f9053bbc8..ad8a4daeff 100644 --- a/spec/bundler/plugins/hook_spec.rb +++ b/spec/bundler/plugins/hook_spec.rb @@ -193,6 +193,129 @@ RSpec.describe "hook plugins" do end end + context "before-eval hook" do + before do + build_repo2 do + build_plugin "before-eval-plugin" do |s| + s.write "plugins.rb", <<-RUBY + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_BEFORE_EVAL do |gemfile, lockfile| + puts "hooked eval start of \#{File.basename(gemfile)} to \#{File.basename(lockfile)}" + end + RUBY + end + end + + bundle "plugin install before-eval-plugin --source https://gem.repo2" + end + + it "runs before the Gemfile is evaluated" do + install_gemfile <<-G + source "https://gem.repo1" + gem "rake" + G + + expect(out).to include "hooked eval start of Gemfile to Gemfile.lock" + end + end + + context "after-eval hook" do + before do + build_repo2 do + build_plugin "after-eval-plugin" do |s| + s.write "plugins.rb", <<-RUBY + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_AFTER_EVAL do |defn| + puts "hooked eval after with gems \#{defn.dependencies.map(&:name).join(", ")}" + end + RUBY + end + end + + bundle "plugin install after-eval-plugin --source https://gem.repo2" + end + + it "runs after the Gemfile is evaluated" do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + gem "rake" + G + + expect(out).to include "hooked eval after with gems myrack, rake" + end + end + + context "before-fetch and after-fetch hooks" do + before do + build_repo2 do + build_plugin "fetch-timing-plugin" do |s| + s.write "plugins.rb", <<-RUBY + @timing_start = nil + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_BEFORE_FETCH do |spec| + @timing_start = Process.clock_gettime(Process::CLOCK_MONOTONIC) + puts "gem \#{spec.name} started fetch at \#{@timing_start}" + end + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_AFTER_FETCH do |spec| + timing_end = Process.clock_gettime(Process::CLOCK_MONOTONIC) + puts "gem \#{spec.name} took \#{timing_end - @timing_start} to fetch" + @timing_start = nil + end + RUBY + end + end + + bundle "plugin install fetch-timing-plugin --source https://gem.repo2" + end + + it "runs around each gem download" do + install_gemfile <<-G + source "https://gem.repo1" + gem "rake" + gem "myrack" + G + + expect(out).to include "gem rake started fetch at" + expect(out).to match(/gem rake took \d+\.\d+ to fetch/) + expect(out).to include "gem myrack started fetch at" + expect(out).to match(/gem myrack took \d+\.\d+ to fetch/) + end + end + + context "before-git-fetch and after-git-fetch hooks" do + before do + build_repo2 do + build_plugin "git-fetch-timing-plugin" do |s| + s.write "plugins.rb", <<-RUBY + @timing_start = nil + Bundler::Plugin::API.hook Bundler::Plugin::Events::GIT_BEFORE_FETCH do |source| + @timing_start = Process.clock_gettime(Process::CLOCK_MONOTONIC) + puts "git source \#{source.name} started fetch at \#{@timing_start}" + end + Bundler::Plugin::API.hook Bundler::Plugin::Events::GIT_AFTER_FETCH do |source| + timing_end = Process.clock_gettime(Process::CLOCK_MONOTONIC) + puts "git source \#{source.name} took \#{timing_end - @timing_start} to fetch" + @timing_start = nil + end + RUBY + end + end + + bundle "plugin install git-fetch-timing-plugin --source https://gem.repo2" + end + + it "runs around each git source fetch" do + build_git "foo", "1.0", path: lib_path("foo") + + relative_path = lib_path("foo").relative_path_from(bundled_app) + install_gemfile <<-G, verbose: true + source "https://gem.repo1" + gem "foo", :git => "#{relative_path}" + G + + expect(out).to include "git source foo started fetch at" + expect(out).to match(/git source foo took \d+\.\d+ to fetch/) + end + end + def install_gemfile_and_bundler_require install_gemfile <<-G source "https://gem.repo1" diff --git a/spec/bundler/plugins/install_spec.rb b/spec/bundler/plugins/install_spec.rb index d0de607e6c..dcacf764be 100644 --- a/spec/bundler/plugins/install_spec.rb +++ b/spec/bundler/plugins/install_spec.rb @@ -168,7 +168,7 @@ RSpec.describe "bundler plugin install" do build_repo2 do build_plugin "chaplin" do |s| s.write "plugins.rb", <<-RUBY - raise "I got you man" + raise RuntimeError, "threw exception on load" RUBY end end @@ -203,13 +203,6 @@ RSpec.describe "bundler plugin install" do 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", 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 @@ -272,6 +265,43 @@ RSpec.describe "bundler plugin install" do plugin_should_be_installed("foo") end + it "overrides the index with the new plugin version" do + gemfile <<-G + source 'https://gem.repo2' + plugin 'foo', "1.0" + gem 'myrack', "1.0.0" + G + + bundle "install" + + update_repo2 do + build_plugin "foo", "2.0.0" + end + + gemfile <<-G + source 'https://gem.repo2' + plugin 'foo', "2.0" + gem 'myrack', "1.0.0" + G + + bundle "install" + + expected = local_plugin_gem("foo-2.0.0", "lib").to_s + expect(Bundler::Plugin.index.load_paths("foo")).to eq([expected]) + end + + it "respects bundler groups" do + gemfile <<-G + source 'https://gem.repo2' + plugin 'foo' + gem 'myrack', "1.0.0" + G + + bundle "install", env: { "BUNDLE_WITHOUT" => "default" } + + expect(out).to include("Bundle complete! 1 Gemfile dependency, 0 gems now installed.") + end + it "accepts plugin version" do update_repo2 do build_plugin "foo", "1.1.0" @@ -341,7 +371,7 @@ RSpec.describe "bundler plugin install" do gem 'myrack', "1.0.0" G - bundle "config set --local deployment true" + bundle_config "deployment true" install_gemfile <<-G source 'https://gem.repo2' plugin 'foo' diff --git a/spec/bundler/plugins/source/example_spec.rb b/spec/bundler/plugins/source/example_spec.rb index 32940bf849..4cd4a1a931 100644 --- a/spec/bundler/plugins/source/example_spec.rb +++ b/spec/bundler/plugins/source/example_spec.rb @@ -67,7 +67,7 @@ RSpec.describe "real source plugins" do expect(the_bundle).to include_gems("a-path-gem 1.0") end - it "writes to lock file" do + it "writes to lockfile" do bundle "install" checksums = checksums_section_when_enabled do |c| @@ -92,7 +92,7 @@ RSpec.describe "real source plugins" do a-path-gem! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -124,38 +124,35 @@ RSpec.describe "real source plugins" do let(:uri_hash) { Digest(:SHA1).hexdigest(lib_path("a-path-gem-1.0").to_s) } it "copies repository to vendor cache and uses it" do bundle "install" - bundle "config set cache_all true" bundle :cache expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}")).to exist expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}/.git")).not_to exist expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}/.bundlecache")).to be_file - FileUtils.rm_rf lib_path("a-path-gem-1.0") + FileUtils.rm_r lib_path("a-path-gem-1.0") expect(the_bundle).to include_gems("a-path-gem 1.0") end it "copies repository to vendor cache and uses it even when installed with `path` configured" do - bundle "config set --local path vendor/bundle" + bundle_config "path vendor/bundle" bundle :install - bundle "config set cache_all true" bundle :cache expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}")).to exist - FileUtils.rm_rf lib_path("a-path-gem-1.0") + FileUtils.rm_r lib_path("a-path-gem-1.0") expect(the_bundle).to include_gems("a-path-gem 1.0") end it "bundler package copies repository to vendor cache" do - bundle "config set --local path vendor/bundle" + bundle_config "path vendor/bundle" bundle :install - bundle "config set cache_all true" bundle :cache expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}")).to exist - FileUtils.rm_rf lib_path("a-path-gem-1.0") + FileUtils.rm_r lib_path("a-path-gem-1.0") expect(the_bundle).to include_gems("a-path-gem 1.0") end end @@ -180,7 +177,7 @@ RSpec.describe "real source plugins" do a-path-gem! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -336,7 +333,7 @@ RSpec.describe "real source plugins" do expect(the_bundle).to include_gems("ma-gitp-gem 1.0") end - it "writes to lock file" do + it "writes to lockfile" do revision = revision_for(lib_path("ma-gitp-gem-1.0")) bundle "install" @@ -363,7 +360,7 @@ RSpec.describe "real source plugins" do ma-gitp-gem! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -389,7 +386,7 @@ RSpec.describe "real source plugins" do ma-gitp-gem! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -446,13 +443,12 @@ RSpec.describe "real source plugins" do end G - bundle "config set cache_all true" bundle :cache expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist expect(bundled_app("vendor/cache/foo-1.0-#{ref}/.git")).not_to exist expect(bundled_app("vendor/cache/foo-1.0-#{ref}/.bundlecache")).to be_file - FileUtils.rm_rf lib_path("foo-1.0") + FileUtils.rm_r lib_path("foo-1.0") expect(the_bundle).to include_gems "foo 1.0" end end diff --git a/spec/bundler/quality_es_spec.rb b/spec/bundler/quality_es_spec.rb index 0dbd77e451..e68674c030 100644 --- a/spec/bundler/quality_es_spec.rb +++ b/spec/bundler/quality_es_spec.rb @@ -17,7 +17,7 @@ RSpec.describe "La biblioteca si misma" do ] pattern = /\b#{Regexp.union(useless_words)}\b/i - File.readlines(filename).each_with_index do |line, number| + File.readlines(File.expand_path(filename, source_root)).each_with_index do |line, number| next unless word_found = pattern.match(line) failing_line_message << "#{filename}:#{number.succ} contiene '#{word_found}'. Esta palabra tiene un significado subjetivo y es mejor obviarla en textos técnicos." end @@ -29,7 +29,7 @@ RSpec.describe "La biblioteca si misma" do failing_line_message = [] specific_pronouns = /\b(él|ella|ellos|ellas)\b/i - File.readlines(filename).each_with_index do |line, number| + File.readlines(File.expand_path(filename, source_root)).each_with_index do |line, number| next unless word_found = specific_pronouns.match(line) failing_line_message << "#{filename}:#{number.succ} contiene '#{word_found}'. Use pronombres más genéricos en la documentación." end diff --git a/spec/bundler/quality_spec.rb b/spec/bundler/quality_spec.rb index c7fce17b62..16b7f18788 100644 --- a/spec/bundler/quality_spec.rb +++ b/spec/bundler/quality_spec.rb @@ -20,6 +20,9 @@ RSpec.describe "The library itself" do end def check_for_tab_characters(filename) + # Because Go uses hard tabs + return if filename.end_with?(".go.tt") + failing_lines = [] each_line(filename) do |line, number| failing_lines << number + 1 if line.include?("\t") @@ -106,7 +109,7 @@ RSpec.describe "The library itself" do it "does not include any unresolved merge conflicts" do error_messages = [] - exempt = %r{lock/lockfile_spec|quality_spec|vcr_cassettes|\.ronn|lockfile_parser\.rb} + exempt = %r{lock/lockfile_spec|quality_spec|vcr_cassettes|\.ronn|lockfile_parser} tracked_files.each do |filename| next if filename&.match?(exempt) error_messages << check_for_git_merge_conflicts(filename) @@ -115,10 +118,8 @@ RSpec.describe "The library itself" do end it "maintains language quality of the documentation" do - included = /ronn/ error_messages = [] man_tracked_files.each do |filename| - next unless filename&.match?(included) error_messages << check_for_expendable_words(filename) error_messages << check_for_specific_pronouns(filename) end @@ -138,12 +139,12 @@ RSpec.describe "The library itself" do it "documents all used settings" do exemptions = %w[ - forget_cli_options gem.changelog gem.ci gem.coc gem.linter gem.mit + gem.bundle gem.rubocop gem.test git.allow_insecure @@ -165,7 +166,8 @@ RSpec.describe "The library itself" do line.scan(/Bundler\.settings\[:#{key_pattern}\]/).flatten.each {|s| all_settings[s] << "referenced at `#{filename}:#{number.succ}`" } end end - documented_settings = File.read("lib/bundler/man/bundle-config.1.ronn")[/LIST OF AVAILABLE KEYS.*/m].scan(/^\* `#{key_pattern}`/).flatten + settings_section = File.read(source_root.join("lib/bundler/man/bundle-config.1.ronn")).split(/^## /).find {|section| section.start_with?("LIST OF AVAILABLE KEYS") } + documented_settings = settings_section.scan(/^\* `#{key_pattern}`/).flatten documented_settings.each do |s| all_settings.delete(s) @@ -185,8 +187,8 @@ RSpec.describe "The library itself" do end it "can still be built" do - with_built_bundler do |_gem_path| - expect(err).to be_empty, "bundler should build as a gem without warnings, but\n#{err}" + with_built_bundler do |gem_path| + expect(File.exist?(gem_path)).to be true end end @@ -216,7 +218,7 @@ RSpec.describe "The library itself" do end end - warnings = last_command.stdboth.split("\n") + warnings = stdboth.split("\n") # ignore warnings around deprecated Object#=~ method in RubyGems warnings.reject! {|w| w =~ %r{rubygems\/version.rb.*deprecated\ Object#=~} } @@ -254,6 +256,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(File.expand_path(filename, source_root), encoding: "UTF-8").each_with_index(&block) end end diff --git a/spec/bundler/realworld/edgecases_spec.rb b/spec/bundler/realworld/edgecases_spec.rb index fb434aba70..391aa0cef6 100644 --- a/spec/bundler/realworld/edgecases_spec.rb +++ b/spec/bundler/realworld/edgecases_spec.rb @@ -16,7 +16,7 @@ RSpec.describe "real world edgecases", realworld: true do 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" \ + raise ArgumentError, "Could not find #{name} (#{requirement}) on rubygems.org!\n" \ "Found specs:\n\#{index.send(:specs).inspect}" end puts "#{name} (\#{rubygem.version})" @@ -63,141 +63,6 @@ RSpec.describe "real world edgecases", realworld: true do expect(lockfile).to include(rubygems_version("activesupport", "~> 3.0")) end - 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 - - pristine_system_gems "bundler-1.99.0" - - gemfile <<-G - source "https://rubygems.org" - gem 'rails', '~> 4.2.7.1' - gem 'paperclip', '~> 5.1.0' - G - - lockfile <<-L - GEM - remote: https://rubygems.org/ - specs: - actionmailer (4.2.7.1) - actionpack (= 4.2.7.1) - actionview (= 4.2.7.1) - activejob (= 4.2.7.1) - mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (4.2.7.1) - actionview (= 4.2.7.1) - activesupport (= 4.2.7.1) - rack (~> 1.6) - rack-test (~> 0.6.2) - rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (4.2.7.1) - activesupport (= 4.2.7.1) - builder (~> 3.1) - erubis (~> 2.7.0) - rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - activejob (4.2.7.1) - activesupport (= 4.2.7.1) - globalid (>= 0.3.0) - activemodel (4.2.7.1) - activesupport (= 4.2.7.1) - builder (~> 3.1) - activerecord (4.2.7.1) - activemodel (= 4.2.7.1) - activesupport (= 4.2.7.1) - arel (~> 6.0) - activesupport (4.2.7.1) - i18n (~> 0.7) - json (~> 1.7, >= 1.7.7) - minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) - tzinfo (~> 1.1) - arel (6.0.3) - builder (3.2.2) - climate_control (0.0.3) - activesupport (>= 3.0) - cocaine (0.5.8) - climate_control (>= 0.0.3, < 1.0) - concurrent-ruby (1.0.2) - erubis (2.7.0) - globalid (0.3.7) - activesupport (>= 4.1.0) - i18n (0.7.0) - json (1.8.3) - loofah (2.0.3) - nokogiri (>= 1.5.9) - mail (2.6.4) - mime-types (>= 1.16, < 4) - mime-types (3.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2016.0521) - mimemagic (0.3.2) - mini_portile2 (2.1.0) - minitest (5.9.1) - nokogiri (1.6.8) - mini_portile2 (~> 2.1.0) - pkg-config (~> 1.1.7) - paperclip (5.1.0) - activemodel (>= 4.2.0) - activesupport (>= 4.2.0) - cocaine (~> 0.5.5) - mime-types - mimemagic (~> 0.3.0) - pkg-config (1.1.7) - rack (1.6.4) - rack-test (0.6.3) - rack (>= 1.0) - rails (4.2.7.1) - actionmailer (= 4.2.7.1) - actionpack (= 4.2.7.1) - actionview (= 4.2.7.1) - activejob (= 4.2.7.1) - activemodel (= 4.2.7.1) - activerecord (= 4.2.7.1) - activesupport (= 4.2.7.1) - bundler (>= 1.3.0, < 2.0) - railties (= 4.2.7.1) - sprockets-rails - rails-deprecated_sanitizer (1.0.3) - activesupport (>= 4.2.0.alpha) - rails-dom-testing (1.0.7) - activesupport (>= 4.2.0.beta, < 5.0) - nokogiri (~> 1.6.0) - rails-deprecated_sanitizer (>= 1.0.1) - rails-html-sanitizer (1.0.3) - loofah (~> 2.0) - railties (4.2.7.1) - actionpack (= 4.2.7.1) - activesupport (= 4.2.7.1) - rake (>= 0.8.7) - thor (>= 0.18.1, < 2.0) - rake (11.3.0) - sprockets (3.7.0) - concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-rails (3.2.0) - actionpack (>= 4.0) - activesupport (>= 4.0) - sprockets (>= 3.0.0) - thor (0.19.1) - thread_safe (0.3.5) - tzinfo (1.2.2) - thread_safe (~> 0.1) - - PLATFORMS - ruby - - DEPENDENCIES - paperclip (~> 5.1.0) - rails (~> 4.2.7.1) - L - - 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 warning when gems have a gemspec with invalid `require_paths`" do install_gemfile <<-G, standalone: true, env: { "BUNDLE_FORCE_RUBY_PLATFORM" => "1" } source 'https://rubygems.org' diff --git a/spec/bundler/realworld/ffi_spec.rb b/spec/bundler/realworld/ffi_spec.rb index 5f40b43a0f..bede372b41 100644 --- a/spec/bundler/realworld/ffi_spec.rb +++ b/spec/bundler/realworld/ffi_spec.rb @@ -42,12 +42,12 @@ RSpec.describe "loading dynamically linked library on a bundle exec context", re } C - sys_exec "gcc -g -o libfoo.so -shared -fpic libfoo.c" + in_bundled_app "gcc -g -o libfoo.so -shared -fpic libfoo.c" install_gemfile <<-G source "https://rubygems.org" - gem 'ffi' + gem 'ffi', force_ruby_platform: true G bundle "exec ruby foo.rb" diff --git a/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock b/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock index c9ded0fd78..c2df2f9229 100644 --- a/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock +++ b/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock @@ -31,8 +31,8 @@ GEM spoom (>= 1.2.0) thor (>= 1.2.0) yard-sorbet - thor (1.3.2) - yard (0.9.37) + thor (1.4.0) + yard (0.9.42) yard-sorbet (0.9.0) sorbet-runtime yard @@ -46,4 +46,4 @@ DEPENDENCIES tapioca BUNDLED WITH - 2.7.0.dev + 4.1.0.dev diff --git a/spec/bundler/realworld/fixtures/warbler/Gemfile b/spec/bundler/realworld/fixtures/warbler/Gemfile index e4d3e8ea96..5687bbd975 100644 --- a/spec/bundler/realworld/fixtures/warbler/Gemfile +++ b/spec/bundler/realworld/fixtures/warbler/Gemfile @@ -3,5 +3,5 @@ source "https://rubygems.org" gem "demo", path: "./demo" -gem "jruby-jars", "~> 9.4" -gem "warbler", "~> 2.0" +gem "jruby-jars", "~> 10.0" +gem "warbler", "~> 2.1" diff --git a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock index 497726eba2..05f3bc4e3f 100644 --- a/spec/bundler/realworld/fixtures/warbler/Gemfile.lock +++ b/spec/bundler/realworld/fixtures/warbler/Gemfile.lock @@ -6,25 +6,30 @@ PATH GEM remote: https://rubygems.org/ specs: - jruby-jars (9.4.9.0) - jruby-rack (1.1.21) - rake (13.0.1) - rubyzip (1.3.0) - warbler (2.0.5) - jruby-jars (>= 9.0.0.0) - jruby-rack (>= 1.1.1, < 1.3) - rake (>= 10.1.0) - rubyzip (~> 1.0, < 1.4) + jruby-jars (10.0.0.1) + jruby-rack (1.2.7) + ostruct (0.6.3) + rake (13.3.0) + rexml (3.4.2) + rubyzip (3.3.0) + warbler (2.1.0) + jruby-jars (>= 9.4, < 10.1) + jruby-rack (>= 1.2.3, < 1.3) + ostruct (~> 0.6.2) + rake (~> 13.0, >= 13.0.3) + rexml (~> 3.0) + rubyzip (>= 3.0.0) PLATFORMS + arm64-darwin java ruby - universal-java-11 + universal-java DEPENDENCIES demo! - jruby-jars (~> 9.4) - warbler (~> 2.0) + jruby-jars (~> 10.0) + warbler (~> 2.1) BUNDLED WITH - 2.7.0.dev + 4.1.0.dev diff --git a/spec/bundler/realworld/gemfile_source_header_spec.rb b/spec/bundler/realworld/gemfile_source_header_spec.rb deleted file mode 100644 index c532c6a867..0000000000 --- a/spec/bundler/realworld/gemfile_source_header_spec.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -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}" } - - before do - setup_server - bundle "config set --local mirror.#{mirror} #{original}" - end - - after do - Artifice.deactivate - @t.kill - @t.join - end - - 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 - - expect(out).to include("Installing weakling") - expect(out).to include("Bundle complete") - expect(the_bundle).to include_gems "weakling 0.0.3" - end - - private - - def setup_server - require_rack - @port = find_unused_port - @server_uri = "http://127.0.0.1:#{@port}" - - require_relative "../support/artifice/endpoint_mirror_source" - require_relative "../support/silent_logger" - - require "rackup/server" - - @t = Thread.new do - Rackup::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) - end -end diff --git a/spec/bundler/realworld/mirror_probe_spec.rb b/spec/bundler/realworld/mirror_probe_spec.rb deleted file mode 100644 index 66a553da28..0000000000 --- a/spec/bundler/realworld/mirror_probe_spec.rb +++ /dev/null @@ -1,132 +0,0 @@ -# frozen_string_literal: true - -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 } - let(:host) { "127.0.0.1" } - - before do - require_rack - setup_server - setup_mirror - end - - after do - Artifice.deactivate - @server_thread.kill - @server_thread.join - end - - context "with a specific fallback timeout" do - before do - global_config("BUNDLE_MIRROR__HTTP://127__0__0__1:#{server_port}/__FALLBACK_TIMEOUT/" => "true", - "BUNDLE_MIRROR__HTTP://127__0__0__1:#{server_port}/" => mirror) - end - - it "install a gem using the original uri when the mirror is not responding" do - gemfile <<-G - source "#{original}" - gem 'weakling' - G - - bundle :install, artifice: nil - - expect(out).to include("Installing weakling") - expect(out).to include("Bundle complete") - expect(the_bundle).to include_gems "weakling 0.0.3" - end - end - - context "with a global fallback timeout" do - before do - global_config("BUNDLE_MIRROR__ALL__FALLBACK_TIMEOUT/" => "1", - "BUNDLE_MIRROR__ALL" => mirror) - end - - it "install a gem using the original uri when the mirror is not responding" do - gemfile <<-G - source "#{original}" - gem 'weakling' - G - - bundle :install, artifice: nil - - expect(out).to include("Installing weakling") - expect(out).to include("Bundle complete") - expect(the_bundle).to include_gems "weakling 0.0.3" - end - end - - context "with a specific mirror without a fallback timeout" do - before do - global_config("BUNDLE_MIRROR__HTTP://127__0__0__1:#{server_port}/" => mirror) - end - - it "fails to install the gem with a timeout error" 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}") - - 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 - - context "with a global mirror without a fallback timeout" do - before do - global_config("BUNDLE_MIRROR__ALL" => mirror) - end - - it "fails to install the gem with a timeout error" 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}") - - 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 - - def setup_server - @server_port = find_unused_port - @server_uri = "http://#{host}:#{@server_port}" - - require_relative "../support/artifice/endpoint" - require_relative "../support/silent_logger" - - require "rackup/server" - - @server_thread = Thread.new do - Rackup::Server.start(app: Endpoint, - Host: host, - Port: @server_port, - server: "webrick", - AccessLog: [], - Logger: Spec::SilentLogger.new) - end.run - - wait_for_server(host, @server_port) - end - - def setup_mirror - @mirror_port = find_unused_port - @mirror_uri = "http://#{host}:#{@mirror_port}" - end -end diff --git a/spec/bundler/realworld/slow_perf_spec.rb b/spec/bundler/realworld/slow_perf_spec.rb index 32e266ff1b..5d36ba7455 100644 --- a/spec/bundler/realworld/slow_perf_spec.rb +++ b/spec/bundler/realworld/slow_perf_spec.rb @@ -30,115 +30,6 @@ RSpec.describe "bundle install with complex dependencies", realworld: true do 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 - - if Bundler.feature_flag.bundler_3_mode? - bundle "lock", env: { "DEBUG_RESOLVER" => "1" }, raise_on_error: false - - expect(out).to include("backtracking").exactly(26).times - else - bundle "lock", env: { "DEBUG_RESOLVER" => "1" } - - expect(out).to include("Solution found after 10 attempts") - end + expect(out).to include("Solution found after 2 attempts") end end diff --git a/spec/bundler/resolver/basic_spec.rb b/spec/bundler/resolver/basic_spec.rb index 0d3eadbaf8..185df1b1c7 100644 --- a/spec/bundler/resolver/basic_spec.rb +++ b/spec/bundler/resolver/basic_spec.rb @@ -211,12 +211,12 @@ RSpec.describe "Resolving" do it "resolves all gems to latest patch" do # strict is not set, so bar goes up a minor version due to dependency from foo 1.4.5 - should_conservative_resolve_and_include :patch, [], %w[foo-1.4.5 bar-2.1.1] + should_conservative_resolve_and_include :patch, true, %w[foo-1.4.5 bar-2.1.1] end it "resolves all gems to latest patch strict" do # strict is set, so foo can only go up to 1.4.4 to avoid bar going up a minor version, and bar can go up to 2.0.5 - should_conservative_resolve_and_include [:patch, :strict], [], %w[foo-1.4.4 bar-2.0.5] + should_conservative_resolve_and_include [:patch, :strict], true, %w[foo-1.4.4 bar-2.0.5] end it "resolves foo only to latest patch - same dependency case" do @@ -238,7 +238,7 @@ RSpec.describe "Resolving" do it "resolves foo only to latest patch - changing dependency declared case" do # bar is locked AND a declared dependency in the Gemfile, so it will not move, and therefore # foo can only move up to 1.4.4. - @base << Bundler::LazySpecification.new("bar", Gem::Version.new("2.0.3"), nil) + @base = Bundler::SpecSet.new([Bundler::LazySpecification.new("bar", Gem::Version.new("2.0.3"), nil)]) should_conservative_resolve_and_include :patch, ["foo"], %w[foo-1.4.4 bar-2.0.3] end @@ -256,20 +256,20 @@ RSpec.describe "Resolving" do it "resolves all gems to latest minor" do # strict is not set, so bar goes up a major version due to dependency from foo 1.4.5 - should_conservative_resolve_and_include :minor, [], %w[foo-1.5.1 bar-3.0.0] + should_conservative_resolve_and_include :minor, true, %w[foo-1.5.1 bar-3.0.0] end it "resolves all gems to latest minor strict" do # strict is set, so foo can only go up to 1.5.0 to avoid bar going up a major version - should_conservative_resolve_and_include [:minor, :strict], [], %w[foo-1.5.0 bar-2.1.1] + should_conservative_resolve_and_include [:minor, :strict], true, %w[foo-1.5.0 bar-2.1.1] end it "resolves all gems to latest major" do - should_conservative_resolve_and_include :major, [], %w[foo-2.0.0 bar-3.0.0] + should_conservative_resolve_and_include :major, true, %w[foo-2.0.0 bar-3.0.0] end it "resolves all gems to latest major strict" do - should_conservative_resolve_and_include [:major, :strict], [], %w[foo-2.0.0 bar-3.0.0] + should_conservative_resolve_and_include [:major, :strict], true, %w[foo-2.0.0 bar-3.0.0] end # Why would this happen in real life? If bar 2.2 has a bug that the author of foo wants to bypass @@ -292,21 +292,21 @@ RSpec.describe "Resolving" do end it "could revert to a previous version level patch" do - should_conservative_resolve_and_include :patch, [], %w[foo-1.4.4 bar-2.1.1] + should_conservative_resolve_and_include :patch, true, %w[foo-1.4.4 bar-2.1.1] end it "cannot revert to a previous version in strict mode level patch" do # fall back to the locked resolution since strict means we can't regress either version - should_conservative_resolve_and_include [:patch, :strict], [], %w[foo-1.4.3 bar-2.2.3] + should_conservative_resolve_and_include [:patch, :strict], true, %w[foo-1.4.3 bar-2.2.3] end it "could revert to a previous version level minor" do - should_conservative_resolve_and_include :minor, [], %w[foo-1.5.0 bar-2.0.5] + should_conservative_resolve_and_include :minor, true, %w[foo-1.5.0 bar-2.0.5] end it "cannot revert to a previous version in strict mode level minor" do # fall back to the locked resolution since strict means we can't regress either version - should_conservative_resolve_and_include [:minor, :strict], [], %w[foo-1.4.3 bar-2.2.3] + should_conservative_resolve_and_include [:minor, :strict], true, %w[foo-1.4.3 bar-2.2.3] end end end @@ -379,4 +379,44 @@ RSpec.describe "Resolving" do should_resolve_without_dependency_api %w[myrack-3.0.0 standalone_migrations-2.0.4] end + + it "resolves fine cases that need joining unbounded disjoint ranges" do + @index = build_index do + gem "inspec", "5.22.3" do + dep "ruby", ">= 3.2.2" + dep "train-kubernetes", ">= 0.1.7" + end + + gem "ruby", "3.2.2" + + gem "train-kubernetes", "0.1.12" do + dep "k8s-ruby", ">= 0.14.0" + end + + gem "train-kubernetes", "0.1.10" do + dep "k8s-ruby", "= 0.10.5" + end + + gem "train-kubernetes", "0.1.7" do + dep "k8s-ruby", ">= 0.10.5" + end + + gem "k8s-ruby", "0.10.5" do + dep "ruby","< 3.2.2" + end + + gem "k8s-ruby", "0.11.0" do + dep "ruby", ">= 3.2.2" + end + + gem "k8s-ruby", "0.14.0" do + dep "ruby", "< 3.2.2" + end + end + + dep "inspec", "5.22.3" + dep "ruby", "3.2.2" + + should_resolve_as %w[inspec-5.22.3 ruby-3.2.2 train-kubernetes-0.1.7 k8s-ruby-0.11.0] + end end diff --git a/spec/bundler/resolver/platform_spec.rb b/spec/bundler/resolver/platform_spec.rb index 8e51911bbd..a1d095d024 100644 --- a/spec/bundler/resolver/platform_spec.rb +++ b/spec/bundler/resolver/platform_spec.rb @@ -48,11 +48,11 @@ RSpec.describe "Resolving platform craziness" do it "takes the latest ruby gem, even if an older platform specific version is available" do @index = build_index do gem "foo", "1.0.0" - gem "foo", "1.0.0", "x64-mingw32" + gem "foo", "1.0.0", "x64-mingw-ucrt" gem "foo", "1.1.0" end dep "foo" - platforms "x64-mingw32" + platforms "x64-mingw-ucrt" should_resolve_as %w[foo-1.1.0] end @@ -61,12 +61,12 @@ RSpec.describe "Resolving platform craziness" do @index = build_index do gem "bar", "1.0.0" gem "foo", "1.0.0" - gem "foo", "1.0.0", "x64-mingw32" do + gem "foo", "1.0.0", "x64-mingw-ucrt" do dep "bar", "< 1" end end dep "foo" - platforms "x64-mingw32" + platforms "x64-mingw-ucrt" should_resolve_as %w[foo-1.0.0] end @@ -74,12 +74,12 @@ RSpec.describe "Resolving platform craziness" do it "prefers the platform specific gem to the ruby version" do @index = build_index do gem "foo", "1.0.0" - gem "foo", "1.0.0", "x64-mingw32" + gem "foo", "1.0.0", "x64-mingw-ucrt" end dep "foo" - platforms "x64-mingw32" + platforms "x64-mingw-ucrt" - should_resolve_as %w[foo-1.0.0-x64-mingw32] + should_resolve_as %w[foo-1.0.0-x64-mingw-ucrt] end describe "on a linux platform" do @@ -159,15 +159,15 @@ RSpec.describe "Resolving platform craziness" do before do @index = build_index do gem "foo", "1.0.0" - gem "foo", "1.0.0", "x64-mingw32" + gem "foo", "1.0.0", "x64-mingw-ucrt" gem "foo", "1.1.0" - gem "foo", "1.1.0", "x64-mingw32" do |s| + gem "foo", "1.1.0", "x64-mingw-ucrt" 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" + platforms "x64-mingw-ucrt" end it "takes the latest ruby gem" do @@ -186,18 +186,18 @@ RSpec.describe "Resolving platform craziness" do it "takes the latest ruby gem with required_ruby_version 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.0.0", "x64-mingw-ucrt" gem "foo", "1.1.0" do |s| s.required_ruby_version = [">= 2.0"] end - gem "foo", "1.1.0", "x64-mingw32" do |s| + gem "foo", "1.1.0", "x64-mingw-ucrt" do |s| s.required_ruby_version = [">= 2.0", "< 2.4"] end gem "Ruby\0", "2.5.1" end dep "foo" dep "Ruby\0", "2.5.1" - platforms "x64-mingw32" + platforms "x64-mingw-ucrt" should_resolve_as %w[foo-1.1.0] end @@ -205,18 +205,18 @@ RSpec.describe "Resolving platform craziness" do it "takes the latest ruby gem if the platform specific gem doesn't match the required_ruby_version with multiple platforms" do @index = build_index do gem "foo", "1.0.0" - gem "foo", "1.0.0", "x64-mingw32" + gem "foo", "1.0.0", "x64-mingw-ucrt" gem "foo", "1.1.0" do |s| s.required_ruby_version = [">= 2.0"] end - gem "foo", "1.1.0", "x64-mingw32" do |s| + gem "foo", "1.1.0", "x64-mingw-ucrt" do |s| s.required_ruby_version = [">= 2.0", "< 2.4"] end gem "Ruby\0", "2.5.1" end dep "foo" dep "Ruby\0", "2.5.1" - platforms "x86_64-linux", "x64-mingw32" + platforms "x86_64-linux", "x64-mingw-ucrt" should_resolve_as %w[foo-1.1.0] end @@ -342,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 x64-mingw-ucrt" do |platform| + platforms "mingw32 mswin32 x64-mingw-ucrt" do |platform| gem "thin", "1.2.7", platform end gem "win32-api", "1.5.1", "universal-mingw32" @@ -363,10 +363,10 @@ RSpec.describe "Resolving platform craziness" do should_resolve_as %w[thin-1.2.7-mingw32] end - it "finds x64-mingw32 gems" do - platforms "x64-mingw32" + it "finds x64-mingw-ucrt gems" do + platforms "x64-mingw-ucrt" dep "thin" - should_resolve_as %w[thin-1.2.7-x64-mingw32] + should_resolve_as %w[thin-1.2.7-x64-mingw-ucrt] end it "finds universal-mingw gems on x86-mingw" do @@ -376,7 +376,7 @@ RSpec.describe "Resolving platform craziness" do end it "finds universal-mingw gems on x64-mingw" do - platform "x64-mingw32" + platform "x64-mingw-ucrt" dep "win32-api" should_resolve_as %w[win32-api-1.5.1-universal-mingw32] end @@ -387,7 +387,7 @@ RSpec.describe "Resolving platform craziness" do should_resolve_as %w[thin-1.2.7-x64-mingw-ucrt] end - it "finds universal-mingw gems on x64-mingw-ucrt", rubygems: ">= 3.3.18" do + 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] diff --git a/spec/bundler/runtime/env_helpers_spec.rb b/spec/bundler/runtime/env_helpers_spec.rb index a1607cd057..c4ebdd1fd2 100644 --- a/spec/bundler/runtime/env_helpers_spec.rb +++ b/spec/bundler/runtime/env_helpers_spec.rb @@ -24,7 +24,7 @@ RSpec.describe "env helpers" do path = `getconf PATH`.strip + "#{File::PATH_SEPARATOR}/foo" with_path_as(path) do bundle_exec_ruby(bundled_app("source.rb").to_s) - expect(last_command.stdboth).to eq(path) + expect(stdboth).to eq(path) end end @@ -35,7 +35,7 @@ RSpec.describe "env helpers" do gem_path = ENV["GEM_PATH"] + "#{File::PATH_SEPARATOR}/foo" with_gem_path_as(gem_path) do bundle_exec_ruby(bundled_app("source.rb").to_s) - expect(last_command.stdboth).to eq(gem_path) + expect(stdboth).to eq(gem_path) end end @@ -62,9 +62,6 @@ RSpec.describe "env helpers" do end it "removes variables that bundler added", :ruby_repo do - # Simulate bundler has not yet been loaded - ENV.replace(ENV.to_hash.delete_if {|k, _v| k.start_with?(Bundler::EnvironmentPreserver::BUNDLER_PREFIX) }) - original = ruby('puts ENV.to_a.map {|e| e.join("=") }.sort.join("\n")', artifice: "fail") create_file("source.rb", <<-RUBY) puts Bundler.original_env.to_a.map {|e| e.join("=") }.sort.join("\n") @@ -74,77 +71,65 @@ RSpec.describe "env helpers" do end end - shared_examples_for "an unbundling helper" do + describe "Bundler.unbundled_env" do it "should delete BUNDLE_PATH" do create_file("source.rb", <<-RUBY) - print #{modified_env}.has_key?('BUNDLE_PATH') + print Bundler.unbundled_env.has_key?('BUNDLE_PATH') RUBY ENV["BUNDLE_PATH"] = "./foo" bundle_exec_ruby bundled_app("source.rb") - expect(last_command.stdboth).to include "false" + expect(stdboth).to include "false" end it "should remove absolute path to 'bundler/setup' from RUBYOPT even if it was present in original env" do create_file("source.rb", <<-RUBY) - print #{modified_env}['RUBYOPT'] + print Bundler.unbundled_env['RUBYOPT'] RUBY setup_require = "-r#{lib_dir}/bundler/setup" ENV["BUNDLER_ORIG_RUBYOPT"] = "-W2 #{setup_require} #{ENV["RUBYOPT"]}" bundle_exec_ruby bundled_app("source.rb") - expect(last_command.stdboth).not_to include(setup_require) + expect(stdboth).not_to include(setup_require) end it "should remove relative path to 'bundler/setup' from RUBYOPT even if it was present in original env" do create_file("source.rb", <<-RUBY) - print #{modified_env}['RUBYOPT'] + print Bundler.unbundled_env['RUBYOPT'] RUBY ENV["BUNDLER_ORIG_RUBYOPT"] = "-W2 -rbundler/setup #{ENV["RUBYOPT"]}" bundle_exec_ruby bundled_app("source.rb") - expect(last_command.stdboth).not_to include("-rbundler/setup") + expect(stdboth).not_to include("-rbundler/setup") end it "should delete BUNDLER_SETUP even if it was present in original env" do create_file("source.rb", <<-RUBY) - print #{modified_env}.has_key?('BUNDLER_SETUP') + print Bundler.unbundled_env.has_key?('BUNDLER_SETUP') RUBY ENV["BUNDLER_ORIG_BUNDLER_SETUP"] = system_gem_path("gems/bundler-#{Bundler::VERSION}/lib/bundler/setup").to_s bundle_exec_ruby bundled_app("source.rb") - expect(last_command.stdboth).to include "false" + expect(stdboth).to include "false" end it "should restore RUBYLIB", :ruby_repo do create_file("source.rb", <<-RUBY) - print #{modified_env}['RUBYLIB'] + print Bundler.unbundled_env['RUBYLIB'] RUBY ENV["RUBYLIB"] = lib_dir.to_s + File::PATH_SEPARATOR + "/foo" ENV["BUNDLER_ORIG_RUBYLIB"] = lib_dir.to_s + File::PATH_SEPARATOR + "/foo-original" bundle_exec_ruby bundled_app("source.rb") - expect(last_command.stdboth).to include("/foo-original") + expect(stdboth).to include("/foo-original") end it "should restore the original MANPATH" do create_file("source.rb", <<-RUBY) - print #{modified_env}['MANPATH'] + print Bundler.unbundled_env['MANPATH'] RUBY ENV["MANPATH"] = "/foo" ENV["BUNDLER_ORIG_MANPATH"] = "/foo-original" bundle_exec_ruby bundled_app("source.rb") - expect(last_command.stdboth).to include("/foo-original") + expect(stdboth).to include("/foo-original") end end - describe "Bundler.unbundled_env" do - let(:modified_env) { "Bundler.unbundled_env" } - - it_behaves_like "an unbundling helper" - end - - describe "Bundler.clean_env", bundler: 2 do - let(:modified_env) { "Bundler.clean_env" } - - it_behaves_like "an unbundling helper" - end - describe "Bundler.with_original_env" do it "should set ENV to original_env in the block" do expected = Bundler.original_env @@ -161,26 +146,6 @@ RSpec.describe "env helpers" do end end - 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 { ENV.to_hash } - end - - expect(actual).to eq(expected) - end - - it "should restore the environment after execution" do - Bundler.ui.silence do - Bundler.with_clean_env { ENV["FOO"] = "hello" } - end - - expect(ENV).not_to have_key("FOO") - end - end - describe "Bundler.with_unbundled_env" do it "should set ENV to unbundled_env in the block" do expected = Bundler.unbundled_env @@ -212,21 +177,6 @@ RSpec.describe "env helpers" do end end - 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'") } - - exit $?.exitstatus - RUBY - end - - it "runs system inside with_clean_env" do - run_bundler_script({ "BUNDLE_FOO" => "bar" }, bundled_app("source.rb")) - expect($?.exitstatus).to eq(42) - end - end - describe "Bundler.unbundled_system" do before do create_file("source.rb", <<-'RUBY') @@ -263,27 +213,6 @@ RSpec.describe "env helpers" do end end - describe "Bundler.clean_exec", bundler: 2 do - before do - create_file("source.rb", <<-'RUBY') - Process.fork do - exit Bundler.ui.silence { Bundler.clean_exec(%(test "\$BUNDLE_FOO" = "bar")) } - end - - _, status = Process.wait2 - - exit(status.exitstatus) - RUBY - end - - it "runs exec inside with_clean_env" do - skip "Fork not implemented" if Gem.win_platform? - - run_bundler_script({ "BUNDLE_FOO" => "bar" }, bundled_app("source.rb")) - expect($?.exitstatus).to eq(1) - end - end - describe "Bundler.unbundled_exec" do before do create_file("source.rb", <<-'RUBY') @@ -297,7 +226,7 @@ RSpec.describe "env helpers" do RUBY end - it "runs exec inside with_clean_env" do + it "runs exec inside with_unbundled_env" do skip "Fork not implemented" if Gem.win_platform? run_bundler_script({ "BUNDLE_FOO" => "bar" }, bundled_app("source.rb")) diff --git a/spec/bundler/runtime/executable_spec.rb b/spec/bundler/runtime/executable_spec.rb index cf57805708..89cee21b00 100644 --- a/spec/bundler/runtime/executable_spec.rb +++ b/spec/bundler/runtime/executable_spec.rb @@ -19,8 +19,9 @@ RSpec.describe "Running bin/* commands" do expect(out).to eq("1.0.0") end - it "allows the location of the gem stubs to be specified" do - bundle "binstubs myrack", path: "gbin" + it "allows the location of the gem stubs to be configured" do + bundle_config "bin gbin" + bundle "binstubs myrack" expect(bundled_app("bin")).not_to exist expect(bundled_app("gbin/myrackup")).to exist @@ -30,7 +31,8 @@ RSpec.describe "Running bin/* commands" do end it "allows absolute paths as a specification of where to install bin stubs" do - bundle "binstubs myrack", path: tmp("bin") + bundle_config "bin #{tmp("bin")}" + bundle "binstubs myrack" gembin tmp("bin/myrackup") expect(out).to eq("1.0.0") @@ -77,7 +79,7 @@ RSpec.describe "Running bin/* commands" do expect(out).to eq("1.0") end - it "creates a bundle binstub" do + it "does not create a bundle binstub" do gemfile <<-G source "https://gem.repo1" gem "bundler" @@ -85,7 +87,9 @@ RSpec.describe "Running bin/* commands" do bundle "binstubs bundler" - expect(bundled_app("bin/bundle")).to exist + expect(bundled_app("bin/bundle")).not_to exist + + expect(err).to include("Bundler itself does not use binstubs because its version is selected by RubyGems") end it "does not generate bin stubs if the option was not specified" do @@ -94,38 +98,6 @@ RSpec.describe "Running bin/* commands" do expect(bundled_app("bin/myrackup")).not_to exist end - it "allows you to stop installing binstubs", bundler: "< 3" do - skip "delete permission error" if Gem.win_platform? - - bundle "install --binstubs bin/" - bundled_app("bin/myrackup").rmtree - bundle "install --binstubs \"\"" - - expect(bundled_app("bin/myrackup")).not_to exist - - bundle "config bin" - expect(out).to include("You have not configured a value for `bin`") - end - - it "remembers that the option was specified", bundler: "< 3" do - gemfile <<-G - source "https://gem.repo1" - gem "activesupport" - G - - bundle :install, binstubs: "bin" - - gemfile <<-G - source "https://gem.repo1" - gem "activesupport" - gem "myrack" - G - - bundle "install" - - expect(bundled_app("bin/myrackup")).to exist - end - it "rewrites bins on binstubs with --force option" do install_gemfile <<-G source "https://gem.repo1" diff --git a/spec/bundler/runtime/gem_tasks_spec.rb b/spec/bundler/runtime/gem_tasks_spec.rb index 046300391b..b855142e60 100644 --- a/spec/bundler/runtime/gem_tasks_spec.rb +++ b/spec/bundler/runtime/gem_tasks_spec.rb @@ -66,9 +66,7 @@ RSpec.describe "require 'bundler/gem_tasks'" do it "includes the relevant tasks" do define_local_gem_using_gem_tasks - with_gem_path_as(base_system_gem_path.to_s) do - sys_exec "#{rake} -T", env: { "GEM_HOME" => system_gem_path.to_s } - end + in_bundled_app "rake -T" expect(err).to be_empty expected_tasks = [ @@ -85,9 +83,7 @@ RSpec.describe "require 'bundler/gem_tasks'" do it "defines a working `rake install` task", :ruby_repo do define_local_gem_using_gem_tasks - with_gem_path_as(base_system_gem_path.to_s) do - sys_exec "#{rake} install", env: { "GEM_HOME" => system_gem_path.to_s } - end + in_bundled_app "rake install" expect(err).to be_empty @@ -136,7 +132,7 @@ RSpec.describe "require 'bundler/gem_tasks'" do before do define_local_gem_using_gem_tasks - bundle "config set path vendor/bundle" + bundle_config "path vendor/bundle" end it "works", :ruby_repo do @@ -155,9 +151,8 @@ RSpec.describe "require 'bundler/gem_tasks'" do it "adds 'pkg' to rake/clean's CLOBBER" do define_local_gem_using_gem_tasks - 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 + in_bundled_app %(rake -e 'load "Rakefile"; puts CLOBBER.inspect') + expect(out).to eq '["pkg"]' end end diff --git a/spec/bundler/runtime/inline_spec.rb b/spec/bundler/runtime/inline_spec.rb index 48385b452d..c6f9bbdbd7 100644 --- a/spec/bundler/runtime/inline_spec.rb +++ b/spec/bundler/runtime/inline_spec.rb @@ -380,7 +380,7 @@ RSpec.describe "bundler/inline#gemfile" do rake BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G script <<-RUBY @@ -414,7 +414,7 @@ RSpec.describe "bundler/inline#gemfile" do rake BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G script <<-RUBY @@ -499,7 +499,7 @@ RSpec.describe "bundler/inline#gemfile" do end it "skips platform warnings" do - bundle "config set --local force_ruby_platform true" + bundle_config "force_ruby_platform true" script <<-RUBY gemfile(true) do @@ -512,7 +512,7 @@ RSpec.describe "bundler/inline#gemfile" do end it "still installs if the application has `bundle package` no_install config set" do - bundle "config set --local no_install true" + bundle_config "no_install true" script <<-RUBY gemfile do @@ -590,37 +590,10 @@ RSpec.describe "bundler/inline#gemfile" do expect(err).to be_empty end - it "when requiring fileutils after does not show redefinition warnings" do - Dir.mkdir tmp("path_without_gemfile") - - 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 "pathname --version 0.2.0" - - script <<-RUBY, dir: tmp("path_without_gemfile"), env: { "BUNDLER_GEM_DEFAULT_DIR" => system_gem_path.to_s, "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } - require "bundler/inline" - - gemfile(true) do - source "https://gem.repo2" - end - - require "fileutils" - RUBY - - expect(err).to eq("The Gemfile specifies no dependencies") - end - - it "does not load default timeout" do + it "does not load default timeout", rubygems: ">= 3.5.0" 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 @@ -705,8 +678,54 @@ RSpec.describe "bundler/inline#gemfile" do expect(out).to include("Installing psych 999") expect(out).to include("Installing stringio 999") - expect(out).to include("The psych gem was resolved to 999") - expect(out).to include("The stringio gem was resolved to 999") + if Gem.respond_to?(:use_psych?) && Gem.use_psych? + expect(out).to include("The psych gem was resolved to 999") + expect(out).to include("The stringio gem was resolved to 999") + end + end + + it "installs a conflicting default gem and non-default gems together" do + build_repo4 do + build_gem "securerandom", "999" + build_gem "myrack", "1.0.0" + end + + script <<-RUBY, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + gemfile(true) do + source "https://gem.repo4" + gem "securerandom" + gem "myrack" + end + + puts MYRACK + RUBY + + expect(out).to include("Installing securerandom 999") + expect(out).to include("Installing myrack 1.0.0") + expect(out).to include("1.0.0") + expect(err).to be_empty + end + + it "installs a conflicting default gem alongside git sources" do + build_repo4 do + build_gem "securerandom", "999" + end + + build_git "foo", "1.0.0" + + script <<-RUBY, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + gemfile(true) do + source "https://gem.repo4" + gem "securerandom" + gem "foo", :git => #{lib_path("foo-1.0.0").to_s.dump} + end + + puts FOO + RUBY + + expect(out).to include("Installing securerandom 999") + expect(out).to include("1.0.0") + expect(err).to be_empty end it "leaves a lockfile in the same directory as the inline script alone" do diff --git a/spec/bundler/runtime/load_spec.rb b/spec/bundler/runtime/load_spec.rb index 15f3d0eb5b..472cde87c5 100644 --- a/spec/bundler/runtime/load_spec.rb +++ b/spec/bundler/runtime/load_spec.rb @@ -68,7 +68,7 @@ RSpec.describe "Bundler.load" do begin expect { Bundler.load }.to raise_error(Bundler::GemfileNotFound) ensure - bundler_gemfile.rmtree if @remove_bundler_gemfile + FileUtils.rm_rf bundler_gemfile if @remove_bundler_gemfile end end end diff --git a/spec/bundler/runtime/platform_spec.rb b/spec/bundler/runtime/platform_spec.rb index 007733d3de..6d96758956 100644 --- a/spec/bundler/runtime/platform_spec.rb +++ b/spec/bundler/runtime/platform_spec.rb @@ -100,7 +100,7 @@ RSpec.describe "Bundler.setup with multi platform stuff" do nokogiri (~> 1.11) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L gemfile <<-G @@ -143,7 +143,7 @@ RSpec.describe "Bundler.setup with multi platform stuff" do nokogiri BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install" @@ -219,7 +219,7 @@ RSpec.describe "Bundler.setup with multi platform stuff" do gem "platform_specific" G - bundle "config set force_ruby_platform true" + bundle_config "force_ruby_platform true" bundle "install" @@ -233,7 +233,7 @@ RSpec.describe "Bundler.setup with multi platform stuff" do gem "platform_specific" G - bundle "config set force_ruby_platform true" + bundle_config "force_ruby_platform true" bundle "install" @@ -247,7 +247,7 @@ RSpec.describe "Bundler.setup with multi platform stuff" do gem "platform_specific" G - bundle "config set force_ruby_platform true" + bundle_config "force_ruby_platform true" bundle "install" @@ -282,7 +282,7 @@ RSpec.describe "Bundler.setup with multi platform stuff" do platform_specific BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install" @@ -316,7 +316,7 @@ RSpec.describe "Bundler.setup with multi platform stuff" do libv8 BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install" @@ -344,7 +344,7 @@ RSpec.describe "Bundler.setup with multi platform stuff" do platform_specific BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install" @@ -370,14 +370,14 @@ RSpec.describe "Bundler.setup with multi platform stuff" do end it "allows specifying only-ruby-platform on windows with dependency platforms" do - simulate_windows do + simulate_platform "x86-mswin32" do install_gemfile <<-G source "https://gem.repo1" - gem "nokogiri", :platforms => [:windows, :mswin, :mswin64, :mingw, :x64_mingw, :jruby] + gem "nokogiri", :platforms => [:windows, :jruby] gem "platform_specific" G - bundle "config set force_ruby_platform true" + bundle_config "force_ruby_platform true" bundle "install" @@ -397,8 +397,8 @@ RSpec.describe "Bundler.setup with multi platform stuff" do G bundle :lock - simulate_windows do - bundle "config set force_ruby_platform true" + simulate_platform "x86-mswin32" do + bundle_config "force_ruby_platform true" bundle "install" expect(the_bundle).to include_gems "myrack 1.0" @@ -411,7 +411,7 @@ RSpec.describe "Bundler.setup with multi platform stuff" do s.add_dependency "platform_specific" end end - simulate_windows x64_mingw32 do + simulate_platform "x64-mingw-ucrt" do lockfile <<-L GEM remote: https://gem.repo2/ @@ -421,7 +421,7 @@ RSpec.describe "Bundler.setup with multi platform stuff" do platform_specific PLATFORMS - x64-mingw32 + x64-mingw-ucrt x86-mingw32 DEPENDENCIES @@ -434,15 +434,13 @@ RSpec.describe "Bundler.setup with multi platform stuff" do 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" + expect(the_bundle).to include_gem "platform_specific 1.0 x64-mingw-ucrt" 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 + %w[x86-mswin32 x64-mswin64 x86-mingw32 x64-mingw-ucrt aarch64-mingw-ucrt].each do |platform| + it "allows specifying platform windows on #{platform} platform" do + simulate_platform platform do lockfile <<-L GEM remote: https://gem.repo1/ @@ -463,8 +461,6 @@ RSpec.describe "Bundler.setup with multi platform stuff" do gem "platform_specific", :platforms => [:windows] G - bundle "install" - expect(the_bundle).to include_gems "platform_specific 1.0 #{platform}" end end diff --git a/spec/bundler/runtime/require_spec.rb b/spec/bundler/runtime/require_spec.rb index ece6679eb2..46613286d2 100644 --- a/spec/bundler/runtime/require_spec.rb +++ b/spec/bundler/runtime/require_spec.rb @@ -119,11 +119,9 @@ RSpec.describe "Bundler.require" do end G - load_error_run <<-R, "fail" - Bundler.require - R + run "Bundler.require", raise_on_error: false - expect(err_without_deprecations).to eq("ZOMG LOAD ERROR") + expect(err_without_deprecations).to include("cannot load such file -- fail") end it "displays a helpful message if the required gem throws an error" do @@ -155,16 +153,9 @@ RSpec.describe "Bundler.require" do end G - cmd = <<-RUBY - begin - Bundler.require - rescue LoadError => e - warn "ZOMG LOAD ERROR: \#{e.message}" - end - RUBY - run(cmd) + run "Bundler.require", raise_on_error: false - expect(err_without_deprecations).to eq("ZOMG LOAD ERROR: cannot load such file -- load-bar") + expect(err_without_deprecations).to include("cannot load such file -- load-bar") end describe "with namespaced gems" do @@ -215,10 +206,9 @@ RSpec.describe "Bundler.require" do end G - load_error_run <<-R, "jquery-rails" - Bundler.require - R - expect(err_without_deprecations).to eq("ZOMG LOAD ERROR") + run "Bundler.require", raise_on_error: false + + expect(err_without_deprecations).to include("cannot load such file -- jquery-rails") end it "handles the case where regex fails" do @@ -233,16 +223,9 @@ RSpec.describe "Bundler.require" do end G - cmd = <<-RUBY - begin - Bundler.require - rescue LoadError => e - warn "ZOMG LOAD ERROR" if e.message.include?("Could not open library 'libfuuu-1.0'") - end - RUBY - run(cmd) + run "Bundler.require", raise_on_error: false - expect(err_without_deprecations).to eq("ZOMG LOAD ERROR") + expect(err_without_deprecations).to include("libfuuu-1.0").and include("cannot open shared object file") end it "doesn't swallow the error when the library has an unrelated error" do @@ -257,16 +240,9 @@ RSpec.describe "Bundler.require" do end G - cmd = <<-RUBY - begin - Bundler.require - rescue LoadError => e - warn "ZOMG LOAD ERROR: \#{e.message}" - end - RUBY - run(cmd) + run "Bundler.require", raise_on_error: false - expect(err_without_deprecations).to eq("ZOMG LOAD ERROR: cannot load such file -- load-bar") + expect(err_without_deprecations).to include("cannot load such file -- load-bar") end end @@ -375,10 +351,9 @@ RSpec.describe "Bundler.require" do gem "busted_require" G - load_error_run <<-R, "no_such_file_omg" - Bundler.require - R - expect(err_without_deprecations).to eq("ZOMG LOAD ERROR") + run "Bundler.require", raise_on_error: false + + expect(err_without_deprecations).to include("cannot load such file -- no_such_file_omg") end end end diff --git a/spec/bundler/runtime/requiring_spec.rb b/spec/bundler/runtime/requiring_spec.rb index 1f32269622..f0e0aeacaf 100644 --- a/spec/bundler/runtime/requiring_spec.rb +++ b/spec/bundler/runtime/requiring_spec.rb @@ -2,14 +2,14 @@ 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"]) }) + sys_exec("#{Gem.ruby} -I#{lib_dir} -rbundler/setup -e'puts true'", env: { "RUBYOPT" => "--disable=gems" }) - expect(last_command.stdboth).to eq("true") + expect(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"]) }) + sys_exec("#{Gem.ruby} -I#{lib_dir} -rbundler -e'puts true'", env: { "RUBYOPT" => "--disable=gems" }) - expect(last_command.stdboth).to eq("true") + expect(stdboth).to eq("true") end end diff --git a/spec/bundler/runtime/self_management_spec.rb b/spec/bundler/runtime/self_management_spec.rb index a0b2d83d0e..176c2a3121 100644 --- a/spec/bundler/runtime/self_management_spec.rb +++ b/spec/bundler/runtime/self_management_spec.rb @@ -3,11 +3,11 @@ RSpec.describe "Self management" do describe "auto switching" do let(:previous_minor) do - "2.3.0" + "9.3.0" end let(:current_version) do - "2.4.0" + "9.4.0" end before do @@ -24,22 +24,24 @@ RSpec.describe "Self management" do gem "myrack" G + + pristine_system_gems "bundler-#{current_version}" 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", preserve_ruby_flags: true - 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.") + bundle_config "path.system true" + bundle "install" + expect(out).to include("Bundler #{current_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})") + expect(out).to eq("Removing bundler (#{current_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) + expect(out).to eq(previous_minor) # ruby-core test setup has always "lib" in $LOAD_PATH so `require "bundler/setup"` always activate the local version rather than using RubyGems gem activation stuff unless ruby_core? @@ -48,26 +50,26 @@ RSpec.describe "Self management" do create_file file, <<-RUBY #!#{Gem.ruby} require 'bundler/setup' - puts Bundler::VERSION + puts '#{previous_minor}' RUBY file.chmod(0o777) cmd = Gem.win_platform? ? "#{Gem.ruby} bin/bundle_version.rb" : "bin/bundle_version.rb" - sys_exec cmd, artifice: nil + in_bundled_app cmd expect(out).to eq(previous_minor) end # 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.") + expect(out).not_to include("Bundler #{current_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", preserve_ruby_flags: true - 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.") + bundle_config "path vendor/bundle" + bundle "install" + expect(out).to include("Bundler #{current_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 @@ -76,7 +78,11 @@ RSpec.describe "Self management" do # App now uses locked version bundle "-v" - expect(out).to end_with(previous_minor[0] == "2" ? "Bundler version #{previous_minor}" : previous_minor) + expect(out).to eq(previous_minor) + + # Preserves original gem home when auto-switching + bundle "exec ruby -e 'puts Bundler.original_env[\"GEM_HOME\"]'" + expect(out).to eq(ENV["GEM_HOME"]) # ruby-core test setup has always "lib" in $LOAD_PATH so `require "bundler/setup"` always activate the local version rather than using RubyGems gem activation stuff unless ruby_core? @@ -85,26 +91,26 @@ RSpec.describe "Self management" do create_file file, <<-RUBY #!#{Gem.ruby} require 'bundler/setup' - puts Bundler::VERSION + puts '#{previous_minor}' RUBY file.chmod(0o777) cmd = Gem.win_platform? ? "#{Gem.ruby} bin/bundle_version.rb" : "bin/bundle_version.rb" - sys_exec cmd, artifice: nil + in_bundled_app cmd expect(out).to eq(previous_minor) end # 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.") + expect(out).not_to include("Bundler #{current_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", preserve_ruby_flags: true - 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.") + bundle_config "deployment true" + bundle "install" + expect(out).to include("Bundler #{current_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 @@ -113,12 +119,12 @@ RSpec.describe "Self management" do # App now uses locked version bundle "-v" - expect(out).to end_with(previous_minor[0] == "2" ? "Bundler version #{previous_minor}" : previous_minor) + expect(out).to eq(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.") + expect(out).not_to include("Bundler #{current_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 @@ -128,76 +134,92 @@ RSpec.describe "Self management" do 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) + expect(out).to eq(current_version) end it "does not try to install when --local is passed" do lockfile_bundled_with(previous_minor) - system_gems "myrack-1.0.0", path: default_bundle_path + system_gems "myrack-1.0.0", path: local_gem_path bundle "install --local" expect(out).not_to match(/Installing Bundler/) bundle "-v" - expect(out).to eq(Bundler::VERSION[0] == "2" ? "Bundler version #{Bundler::VERSION}" : Bundler::VERSION) + expect(out).to eq(current_version) end it "shows a discrete message if locked bundler does not exist" do - missing_minor = "#{Bundler::VERSION[0]}.999.999" + missing_minor = "#{current_version[0]}.999.999" lockfile_bundled_with(missing_minor) bundle "install" - 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}") + 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 #{current_version}") bundle "-v" - expect(out).to eq(Bundler::VERSION[0] == "2" ? "Bundler version #{Bundler::VERSION}" : Bundler::VERSION) + expect(out).to eq(current_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", preserve_ruby_flags: true - 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_config "version #{previous_minor}" + bundle "install" + expect(out).to include("Bundler #{current_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) + expect(out).to eq(previous_minor) + end + + it "requires the right bundler version from the config and run bundle CLI without re-exec" do + unless Bundler.rubygems.provides?(">= 4.1.0.dev") + skip "This spec can only run when Gem::BundlerVersionFinder.bundler_versions reads bundler configs" + end + + lockfile_bundled_with(current_version) + + bundle_config "version #{previous_minor}" + bundle_config "path.system true" + bundle "install" + + script = bundled_app("script.rb") + create_file(script, "p 'executed once'") + + bundle "-v", env: { "RUBYOPT" => "-r#{script}" } + expect(out).to eq(%("executed once"\n9.3.0)) 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_config "version system" bundle "install" 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) + expect(out).to eq(current_version) end it "does not try to install when using bundle config version <dev-version>" do lockfile_bundled_with(previous_minor) - bundle "config set version #{previous_minor}.dev" + bundle_config "version #{previous_minor}.dev" bundle "install" 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) + expect(out).to eq(current_version) end it "ignores malformed lockfile version" do lockfile_bundled_with("2.3.") bundle "install --verbose" - expect(out).to include("Using bundler #{Bundler::VERSION}") + expect(out).to include("Using bundler #{current_version}") end it "uses the right original script when re-execing, if `$0` has been changed to something that's not a script", :ruby_repo do - bundle "config path vendor/bundle" - - system_gems "bundler-9.9.9", path: vendored_gems + system_gems "bundler-9.9.9", path: local_gem_path test = bundled_app("test.rb") @@ -208,15 +230,13 @@ RSpec.describe "Self management" do lockfile_bundled_with("9.9.9") - sys_exec "#{Gem.ruby} #{test}", artifice: nil, raise_on_error: false + in_bundled_app "#{Gem.ruby} #{test}", raise_on_error: false expect(err).to include("Could not find myrack-1.0.0") expect(err).not_to include("this is the program name") end it "uses modified $0 when re-execing, if `$0` has been changed to a script", :ruby_repo do - bundle "config path vendor/bundle" - - system_gems "bundler-9.9.9", path: vendored_gems + system_gems "bundler-9.9.9", path: local_gem_path runner = bundled_app("runner.rb") @@ -232,7 +252,7 @@ RSpec.describe "Self management" do lockfile_bundled_with("9.9.9") - sys_exec "#{Gem.ruby} #{runner} #{script}", artifice: nil, raise_on_error: false + in_bundled_app "#{Gem.ruby} #{runner} #{script}", raise_on_error: false expect(err).to include("Could not find myrack-1.0.0") end diff --git a/spec/bundler/runtime/setup_spec.rb b/spec/bundler/runtime/setup_spec.rb index 106f1edc57..ceb6fcf66a 100644 --- a/spec/bundler/runtime/setup_spec.rb +++ b/spec/bundler/runtime/setup_spec.rb @@ -135,7 +135,7 @@ RSpec.describe "Bundler.setup" do end it "orders the load path correctly when there are dependencies" do - bundle "config set path.system true" + bundle_config "path.system true" install_gemfile <<-G source "https://gem.repo1" @@ -163,7 +163,7 @@ RSpec.describe "Bundler.setup" do end it "falls back to order the load path alphabetically for backwards compatibility" do - bundle "config set path.system true" + bundle_config "path.system true" install_gemfile <<-G source "https://gem.repo1" @@ -286,7 +286,7 @@ RSpec.describe "Bundler.setup" do G bundle "install" - bundle "config set --local deployment true" + bundle_config "deployment true" ENV["BUNDLE_GEMFILE"] = "Gemfile" ruby <<-R @@ -303,6 +303,32 @@ RSpec.describe "Bundler.setup" do expect(out).to eq("WIN") end end + + context "user sets it via `config set --local gemfile`" do + it "uses the value in the config" do + gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + gemfile bundled_app("CustomGemfile"), <<-G + source "https://gem.repo1" + gem "activesupport", "2.3.5" + G + + bundle_config "gemfile #{bundled_app("CustomGemfile")}" + bundle "install" + + ruby <<-R + require 'bundler' + Bundler.setup + require 'activesupport' + puts ACTIVESUPPORT + R + + expect(out).to eq("2.3.5") + end + end end it "prioritizes gems in BUNDLE_PATH over gems in GEM_HOME" do @@ -458,14 +484,13 @@ RSpec.describe "Bundler.setup" do end it "works even when the cache directory has been deleted" do - bundle "config set --local path vendor/bundle" bundle :install - FileUtils.rm_rf vendored_gems("cache") + FileUtils.rm_r default_cache_path expect(the_bundle).to include_gems "myrack 1.0.0" end it "does not randomly change the path when specifying --path and the bundle directory becomes read only" do - bundle "config set --local path vendor/bundle" + bundle_config "path vendor/bundle" bundle :install with_read_only("#{bundled_app}/**/*") do @@ -474,7 +499,7 @@ RSpec.describe "Bundler.setup" do end it "finds git gem when default bundle path becomes read only" do - bundle "config set --local path .bundle" + bundle_config "path .bundle" bundle "install" with_read_only("#{bundled_app(".bundle")}/**/*") do @@ -497,7 +522,7 @@ RSpec.describe "Bundler.setup" do bundle %(config set local.myrack #{lib_path("local-myrack")}) bundle :install - FileUtils.rm_rf(lib_path("local-myrack")) + FileUtils.rm_r(lib_path("local-myrack")) run "require 'myrack'", raise_on_error: false expect(err).to match(/Cannot use local override for myrack-0.8 because #{Regexp.escape(lib_path("local-myrack").to_s)} does not exist/) end @@ -569,7 +594,7 @@ RSpec.describe "Bundler.setup" do describe "when excluding groups" do it "doesn't change the resolve if --without is used" do - bundle "config set --local without rails" + bundle_config "without rails" install_gemfile <<-G source "https://gem.repo1" gem "activesupport" @@ -585,7 +610,7 @@ RSpec.describe "Bundler.setup" do end it "remembers --without and does not bail on bare Bundler.setup" do - bundle "config set --local without rails" + bundle_config "without rails" install_gemfile <<-G source "https://gem.repo1" gem "activesupport" @@ -601,7 +626,7 @@ RSpec.describe "Bundler.setup" do end it "remembers --without and does not bail on bare Bundler.setup, even in the case of path gems no longer available" do - bundle "config set --local without development" + bundle_config "without development" path = bundled_app(File.join("vendor", "foo")) build_lib "foo", path: path @@ -612,7 +637,7 @@ RSpec.describe "Bundler.setup" do gem 'foo', :path => 'vendor/foo', :group => :development G - FileUtils.rm_rf(path) + FileUtils.rm_r(path) 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") @@ -667,7 +692,7 @@ RSpec.describe "Bundler.setup" do end it "remembers --without and does not include groups passed to Bundler.setup" do - bundle "config set --local without rails" + bundle_config "without rails" install_gemfile <<-G source "https://gem.repo1" gem "activesupport" @@ -729,46 +754,52 @@ RSpec.describe "Bundler.setup" do G run <<-R - File.open(File.join(Gem.dir, "specifications", "broken.gemspec"), "w") do |f| + File.open(File.join(Gem.dir, "specifications", "invalid.gemspec"), "w") do |f| f.write <<-RUBY # -*- encoding: utf-8 -*- -# stub: broken 1.0.0 ruby lib +# stub: invalid 1.0.0 ruby lib Gem::Specification.new do |s| - s.name = "broken" + s.name = "invalid" s.version = "1.0.0" - raise "BROKEN GEMSPEC" + s.authors = ["Invalid Author"] + s.files = ["lib/invalid.rb"] + s.add_dependency "nonexistent-gem", "~> 999.999.999" + s.validate! end RUBY end R run <<-R - File.open(File.join(Gem.dir, "specifications", "broken-ext.gemspec"), "w") do |f| + File.open(File.join(Gem.dir, "specifications", "invalid-ext.gemspec"), "w") do |f| f.write <<-RUBY # -*- encoding: utf-8 -*- -# stub: broken-ext 1.0.0 ruby lib +# stub: invalid-ext 1.0.0 ruby lib # stub: a.ext\\0b.ext Gem::Specification.new do |s| - s.name = "broken-ext" + s.name = "invalid-ext" s.version = "1.0.0" - raise "BROKEN GEMSPEC EXT" + s.authors = ["Invalid Author"] + s.files = ["lib/invalid.rb"] + s.required_ruby_version = "~> 0.8.0" + s.validate! 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") {} + Bundler::FileUtils.mkdir_p(File.join(extensions_dir, "invalid-ext-1.0.0")) + File.open(File.join(extensions_dir, "invalid-ext-1.0.0", "gem.build_complete"), "w") {} R run <<-R - puts "WIN" + puts "Success" R - expect(out).to eq("WIN") + expect(out).to eq("Success") end it "ignores empty gem paths" do @@ -1083,7 +1114,7 @@ end describe "with system gems in the bundle" do before :each do - bundle "config set path.system true" + bundle_config "path.system true" system_gems "myrack-1.0.0" install_gemfile <<-G @@ -1152,7 +1183,7 @@ end bundler_module = class << Bundler; self; end bundler_module.send(:remove_method, :require) def Bundler.require(path) - raise "LOSE" + raise StandardError, "didn't use binding from top level" end Bundler.load RUBY @@ -1197,7 +1228,7 @@ end end before do - bundle "config set --local path.system true" + bundle_config "path.system true" install_gemfile <<-G source "https://gem.repo1" @@ -1262,7 +1293,7 @@ end lock += <<~L BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L lock @@ -1278,7 +1309,12 @@ end end context "is not present" do - it "does not change the lock" do + # Skipped on ruby-core because `ruby "require 'bundler/setup'"` does not + # activate bundler as a gem there, so Source::Metadata falls back to a + # synthetic spec whose cache_file does not exist on disk and + # LockfileGenerator#bundler_checksum drops the bundler checksum, while + # the on-disk lockfile still has it. + it "does not change the lock", :ruby_repo do expect { ruby "require 'bundler/setup'" }.not_to change { lockfile } end end @@ -1324,7 +1360,7 @@ end gem "bar", :git => "#{lib_path("bar-1.0")}" G - bundle :install + bundle :install, env: { "BUNDLE_LOCKFILE_CHECKSUMS" => "false" } ruby <<-RUBY, artifice: nil require 'bundler/setup' @@ -1407,7 +1443,6 @@ end describe "default gem activation" do let(:exemptions) do exempts = %w[did_you_mean bundler uri pathname] - exempts << "etc" if (Gem.ruby_version < Gem::Version.new("3.2") || Gem.ruby_version >= Gem::Version.new("3.3.2")) && Gem.win_platform? 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 @@ -1466,7 +1501,7 @@ end install_gemfile "source 'https://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", env: { "RUBYOPT" => activation_warning_hack_rubyopt } expect(out).to eq("{}") end @@ -1482,7 +1517,7 @@ end gem "net-http-pipeline", "1.0.1" G - bundle "config set --local path vendor/bundle" + bundle_config "path vendor/bundle" bundle :install @@ -1526,22 +1561,7 @@ end end describe "after setup" do - it "allows calling #gem on random objects", bundler: "< 3" do - install_gemfile <<-G - source "https://gem.repo1" - gem "myrack" - G - - ruby <<-RUBY - require "bundler/setup" - Object.new.gem "myrack" - puts Gem.loaded_specs["myrack"].full_name - RUBY - - expect(out).to eq("myrack-1.0.0") - end - - it "keeps Kernel#gem private", bundler: "3" do + it "keeps Kernel#gem private" do install_gemfile <<-G source "https://gem.repo1" gem "myrack" @@ -1553,7 +1573,7 @@ end puts "FAIL" RUBY - expect(last_command.stdboth).not_to include "FAIL" + expect(stdboth).not_to include "FAIL" expect(err).to match(/private method [`']gem'/) end @@ -1569,7 +1589,7 @@ end puts "FAIL" RUBY - expect(last_command.stdboth).not_to include "FAIL" + expect(stdboth).not_to include "FAIL" expect(err).to match(/private method [`']require'/) end @@ -1648,7 +1668,7 @@ end gem "myrack", :group => :test G - bundle "config set auto_install 1" + bundle_config "auto_install 1" ruby <<-RUBY, artifice: "compact_index" require 'bundler/setup' @@ -1656,4 +1676,35 @@ end expect(err).to be_empty expect(out).to include("Installing myrack 1.0.0") end + + context "in a read-only filesystem" do + before do + gemfile <<-G + source "https://gem.repo4" + G + + lockfile <<-L + GEM + remote: https://gem.repo4/ + + PLATFORMS + x86_64-darwin-19 + + DEPENDENCIES + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "should fail loudly if the lockfile platforms don't include the current platform" do + simulate_platform "x86_64-linux" do + ruby <<-RUBY, raise_on_error: false, env: { "BUNDLER_SPEC_READ_ONLY" => "true", "BUNDLER_FORCE_TTY" => "true" } + require "bundler/setup" + RUBY + end + + expect(err).to include("Your lockfile is missing the current platform, but can't be updated because file system is read-only") + end + end end diff --git a/spec/bundler/spec_helper.rb b/spec/bundler/spec_helper.rb index bf7604659c..27ddc6a771 100644 --- a/spec/bundler/spec_helper.rb +++ b/spec/bundler/spec_helper.rb @@ -9,18 +9,26 @@ if File.expand_path(__FILE__) =~ %r{([^\w/\.:\-])} abort "The bundler specs cannot be run from a path that contains special characters (particularly #{$1.inspect})" end -# Bundler CLI will have different help text depending on whether this variable -# is set, since the `-e` flag `bundle gem` with require an explicit value if -# `EDITOR` is not set, but will use `EDITOR` by default is set. So make sure -# it's `nil` before loading bundler to get a consistent help text, since some -# tests rely on that. +# Bundler CLI will have different help text depending on whether any of these +# variables is set, since the `-e` flag `bundle gem` with require an explicit +# value if they are not set, but will use their value by default if set. So make +# sure they are `nil` before loading bundler to get a consistent help text, +# since some tests rely on that. ENV["EDITOR"] = nil +ENV["VISUAL"] = nil +ENV["BUNDLER_EDITOR"] = nil require "bundler" +# If we use shared GEM_HOME and install multiple versions, it may cause +# unexpected test failures. +gem "diff-lcs", "< 2.0" + require "rspec/core" require "rspec/expectations" require "rspec/mocks" require "rspec/support/differ" +gem "rubygems-generate_index" +require "rubygems/indexer" require_relative "support/builders" require_relative "support/checksums" @@ -30,6 +38,38 @@ require_relative "support/indexes" require_relative "support/matchers" require_relative "support/permissions" require_relative "support/platforms" +require_relative "support/shards" + +begin + raise LoadError if File.exist?(File.expand_path("../../lib/bundler/bundler.gemspec", __dir__)) + + gem "simplecov_json_formatter" + require "simplecov" + + SimpleCov.start do + command_name "bundler:#{Process.pid}" + root File.expand_path("../bundler", __dir__) + coverage_dir File.expand_path("../coverage", __dir__) + + add_filter "/spec/" + add_filter "/test/" + add_filter "/lib/rubygems/" + add_filter "/lib/bundler/vendor/" + add_filter "/tool/" + add_filter "/tmp/" + add_filter ".gemspec" + end + + SimpleCov.print_error_status = false + SimpleCov.at_exit do + $stdout = File.open(File::NULL, "w") + SimpleCov.result.format! + ensure + $stdout = STDOUT + end +rescue LoadError + # SimpleCov is not installed +end $debug = false @@ -48,6 +88,7 @@ RSpec.configure do |config| config.include Spec::Path config.include Spec::Platforms config.include Spec::Permissions + config.include Spec::Shards # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = ".rspec_status" @@ -82,28 +123,42 @@ RSpec.configure do |config| require_relative "support/rubygems_ext" Spec::Rubygems.test_setup + + # Disable retry delays in tests to speed them up + Bundler::Retry.default_base_delay = 0 + + # Simulate bundler has not yet been loaded + ENV.replace(ENV.to_hash.delete_if {|k, _v| k.start_with?(Bundler::EnvironmentPreserver::BUNDLER_PREFIX) }) + 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["XDG_CACHE_HOME"] = nil ENV["GEMRC"] = nil + # Prevent tests from modifying the user's global git config. + # GIT_CONFIG_GLOBAL and GIT_CONFIG_NOSYSTEM are available since Git 2.32. + git_version = `git --version`[/(\d+\.\d+\.\d+)/, 1] + if Gem::Version.new(git_version) >= Gem::Version.new("2.32") + ENV["GIT_CONFIG_GLOBAL"] = File.join(ENV["HOME"], ".gitconfig") + ENV["GIT_CONFIG_NOSYSTEM"] = "1" + end + # Don't wrap output in tests ENV["THOR_COLUMNS"] = "10000" extend(Spec::Builders) - check_test_gems! - build_repo1 - reset_paths! + reset! end config.around :each do |example| - FileUtils.cp_r pristine_system_gem_path, system_gem_path + default_system_gems with_gem_path_as(system_gem_path) do Bundler.ui.silence { example.run } @@ -120,7 +175,18 @@ RSpec.configure do |config| reset! end - config.after :suite do - FileUtils.rm_rf Spec::Path.pristine_system_gem_path + Spec::Shards::EXAMPLE_MAPPINGS.each do |tag, file_paths| + file_pattern = Regexp.union(file_paths.map {|path| Regexp.new(Regexp.escape(path) + "$") }) + + config.define_derived_metadata(file_path: file_pattern) do |metadata| + metadata[tag] = true + end end + + config.before(:context) do |example| + metadata = example.class.metadata + if metadata[:type] != :aruba && !metadata[:realworld] && metadata.keys.none? {|k| Spec::Shards::EXAMPLE_MAPPINGS.keys.include?(k) } + warn "#{metadata[:file_path]} is not assigned to any shard. see spec/support/shards.rb for details." + end + end unless Spec::Path.ruby_core? end diff --git a/spec/bundler/support/artifice/compact_index_cooldown.rb b/spec/bundler/support/artifice/compact_index_cooldown.rb new file mode 100644 index 0000000000..85e3173c98 --- /dev/null +++ b/spec/bundler/support/artifice/compact_index_cooldown.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +require_relative "helpers/compact_index_cooldown" +require_relative "helpers/artifice" + +Artifice.activate_with(CompactIndexCooldownAPI) diff --git a/spec/bundler/support/artifice/compact_index_etag_match.rb b/spec/bundler/support/artifice/compact_index_etag_match.rb index 08d7b5ec53..6c62166051 100644 --- a/spec/bundler/support/artifice/compact_index_etag_match.rb +++ b/spec/bundler/support/artifice/compact_index_etag_match.rb @@ -4,7 +4,7 @@ require_relative "helpers/compact_index" class CompactIndexEtagMatch < CompactIndexAPI get "/versions" do - raise "ETag header should be present" unless env["HTTP_IF_NONE_MATCH"] + raise ArgumentError, "ETag header should be present" unless env["HTTP_IF_NONE_MATCH"] headers "ETag" => env["HTTP_IF_NONE_MATCH"] status 304 body "" diff --git a/spec/bundler/support/artifice/compact_index_mirror_down.rb b/spec/bundler/support/artifice/compact_index_mirror_down.rb new file mode 100644 index 0000000000..88983c715d --- /dev/null +++ b/spec/bundler/support/artifice/compact_index_mirror_down.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require_relative "helpers/compact_index" +require_relative "helpers/artifice" +require_relative "helpers/rack_request" + +module Artifice + module Net + class HTTPMirrorDown < HTTP + def connect + raise SocketError if address == "gem.mirror" + + super + end + end + + HTTP.endpoint = CompactIndexAPI + end + + replace_net_http(Net::HTTPMirrorDown) +end diff --git a/spec/bundler/support/artifice/compact_index_no_checksums.rb b/spec/bundler/support/artifice/compact_index_no_checksums.rb new file mode 100644 index 0000000000..ecb7fc7d7c --- /dev/null +++ b/spec/bundler/support/artifice/compact_index_no_checksums.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require_relative "helpers/compact_index" + +class CompactIndexNoChecksums < CompactIndexAPI + get "/info/:name" do + etag_response do + gem = gems.find {|g| g.name == params[:name] } + gem.versions.map(&:number).join("\n") + end + end +end + +require_relative "helpers/artifice" + +Artifice.activate_with(CompactIndexNoChecksums) diff --git a/spec/bundler/support/artifice/helpers/compact_index.rb b/spec/bundler/support/artifice/helpers/compact_index.rb index ba331e483f..e684aa8628 100644 --- a/spec/bundler/support/artifice/helpers/compact_index.rb +++ b/spec/bundler/support/artifice/helpers/compact_index.rb @@ -2,7 +2,7 @@ require_relative "endpoint" -$LOAD_PATH.unshift Dir[Spec::Path.base_system_gem_path.join("gems/compact_index*/lib")].first.to_s +$LOAD_PATH.unshift Spec::Path.tmp_root.join("compact_index/lib").to_s require "compact_index" require "digest" @@ -90,13 +90,17 @@ class CompactIndexAPI < Endpoint 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) + build_gem_version(spec, deps, checksum) end CompactIndex::Gem.new(name, gem_versions) end end end + + def build_gem_version(spec, deps, checksum) + 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 end get "/names" do diff --git a/spec/bundler/support/artifice/helpers/compact_index_cooldown.rb b/spec/bundler/support/artifice/helpers/compact_index_cooldown.rb new file mode 100644 index 0000000000..9920fd2c95 --- /dev/null +++ b/spec/bundler/support/artifice/helpers/compact_index_cooldown.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require_relative "compact_index" + +class CompactIndexCooldownAPI < CompactIndexAPI + helpers do + def build_gem_version(spec, deps, checksum) + created_at = spec.date&.utc&.iso8601 + CompactIndex::GemVersionV2.new(spec.version.version, spec.platform.to_s, checksum, nil, + deps, spec.required_ruby_version.to_s, spec.required_rubygems_version.to_s, created_at) + end + end +end diff --git a/spec/bundler/support/artifice/helpers/endpoint.rb b/spec/bundler/support/artifice/helpers/endpoint.rb index 1ceadb5900..9590611dfe 100644 --- a/spec/bundler/support/artifice/helpers/endpoint.rb +++ b/spec/bundler/support/artifice/helpers/endpoint.rb @@ -27,7 +27,7 @@ class Endpoint < Sinatra::Base set :raise_errors, true set :show_exceptions, false - set :host_authorization, permitted_hosts: [".example.org", ".local", ".repo", ".repo1", ".repo2", ".repo3", ".repo4", ".rubygems.org", ".security", ".source", ".test", "127.0.0.1"] + set :host_authorization, permitted_hosts: [".example.org", ".local", ".mirror", ".repo", ".repo1", ".repo2", ".repo3", ".repo4", ".rubygems.org", ".security", ".source", ".test", "127.0.0.1"] def call!(*) super.tap do diff --git a/spec/bundler/support/artifice/vcr.rb b/spec/bundler/support/artifice/vcr.rb index 2386a4c6b7..0bf5ade8f6 100644 --- a/spec/bundler/support/artifice/vcr.rb +++ b/spec/bundler/support/artifice/vcr.rb @@ -24,7 +24,7 @@ class BundlerVCRHTTP < Gem::Net::HTTP 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") + f.puts request_pair_paths.map {|path| Pathname.new(path).relative_path_from(Spec::Path.git_root).to_s }.join("\n") end if recorded_response? diff --git a/spec/bundler/support/build_metadata.rb b/spec/bundler/support/build_metadata.rb index 189100edb7..2eade4137b 100644 --- a/spec/bundler/support/build_metadata.rb +++ b/spec/bundler/support/build_metadata.rb @@ -8,11 +8,10 @@ module Spec include Spec::Path include Spec::Helpers - def write_build_metadata(dir: source_root) + def write_build_metadata(dir: source_root, version: Bundler::VERSION) build_metadata = { git_commit_sha: git_commit_sha, - built_at: loaded_gemspec.date.utc.strftime("%Y-%m-%d"), - release: true, + built_at: release_date_for(version, dir: dir), } replace_build_metadata(build_metadata, dir: dir) @@ -20,7 +19,7 @@ module Spec def reset_build_metadata(dir: source_root) build_metadata = { - release: false, + built_at: nil, } replace_build_metadata(build_metadata, dir: dir) @@ -44,6 +43,11 @@ module Spec ruby_core_tarball? ? "unknown" : git("rev-parse --short HEAD", source_root).strip end + def release_date_for(version, dir:) + changelog = File.expand_path("CHANGELOG.md", dir) + File.readlines(changelog)[2].scan(/^## #{Regexp.escape(version)} \((.*)\)/).first&.first if File.exist?(changelog) + end + extend self end end diff --git a/spec/bundler/support/builders.rb b/spec/bundler/support/builders.rb index 340b3f5dd9..43ab7e053d 100644 --- a/spec/bundler/support/builders.rb +++ b/spec/bundler/support/builders.rb @@ -2,6 +2,8 @@ require "bundler/shared_helpers" require "shellwords" +require "fileutils" +require "rubygems/package" require_relative "build_metadata" @@ -24,10 +26,6 @@ module Spec Gem::Platform.new(platform) end - def rake_version - "13.2.1" - end - def build_repo1 build_repo gem_repo1 do FileUtils.cp rake_path, "#{gem_repo1}/gems/" @@ -111,11 +109,11 @@ module Spec end build_gem "platform_specific" do |s| - s.platform = "x64-mingw32" + s.platform = "x64-mingw-ucrt" end build_gem "platform_specific" do |s| - s.platform = "x64-mingw-ucrt" + s.platform = "aarch64-mingw-ucrt" end build_gem "platform_specific" do |s| @@ -183,32 +181,41 @@ module Spec end def build_repo2(**kwargs, &blk) - FileUtils.rm_rf gem_repo2 - FileUtils.cp_r gem_repo1, gem_repo2 + FileUtils.cp_r gem_repo1, gem_repo2, remove_destination: true 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.) + # + # If the repo already exists, `#update_repo` will be called. def build_repo3(**kwargs, &blk) - build_empty_repo gem_repo3, **kwargs, &blk + if File.exist?(gem_repo3) + update_repo(gem_repo3, &blk) + else + build_repo gem_repo3, **kwargs, &blk + end end # Like build_repo3, this is a repo that has no pre-installed gems included. - # We have two different methods for situations where two different empty - # sources are needed. + # + # If the repo already exists, `#udpate_repo` will be called def build_repo4(**kwargs, &blk) - build_empty_repo gem_repo4, **kwargs, &blk - end - - def update_repo4(&blk) - update_repo(gem_repo4, &blk) + if File.exist?(gem_repo4) + update_repo gem_repo4, &blk + else + build_repo gem_repo4, **kwargs, &blk + end end def update_repo2(**kwargs, &blk) update_repo(gem_repo2, **kwargs, &blk) end + def update_repo3(&blk) + update_repo(gem_repo3, &blk) + end + def build_security_repo build_repo security_repo do build_gem "myrack" @@ -224,6 +231,41 @@ module Spec end end + # A minimal fake irb console + def build_dummy_irb(version = "9.9.9") + build_gem "irb", version do |s| + s.write "lib/irb.rb", <<-RUBY + class IRB + class << self + def toplevel_binding + unless defined?(@toplevel_binding) && @toplevel_binding + TOPLEVEL_BINDING.eval %{ + def self.__irb__; binding; end + IRB.instance_variable_set(:@toplevel_binding, __irb__) + class << self; undef __irb__; end + } + end + @toplevel_binding.eval('private') + @toplevel_binding + end + + def __irb__ + while line = gets + begin + puts eval(line, toplevel_binding).inspect.sub(/^"(.*)"$/, '=> \\1') + rescue Exception => e + puts "\#{e.class}: \#{e.message}" + puts e.backtrace.first + end + end + end + alias start __irb__ + end + end + RUBY + end + end + def build_repo(path, **kwargs, &blk) return if File.directory?(path) @@ -232,17 +274,8 @@ module Spec update_repo(path,**kwargs, &blk) end - def check_test_gems! - if rake_path.nil? - FileUtils.rm_rf(base_system_gems) - Spec::Rubygems.install_test_deps - end - - Helpers.install_dev_bundler unless pristine_system_gem_path.exist? - end - def update_repo(path, build_compact_index: true) - exempted_caller = Gem.ruby_version >= Gem::Version.new("3.4.0.dev") ? "#{Module.nesting.first}#build_repo" : "build_repo" + exempted_caller = Gem.ruby_version >= Gem::Version.new("3.4.0.dev") && RUBY_ENGINE != "jruby" ? "#{Module.nesting.first}#build_repo" : "build_repo" if path == gem_repo1 && caller_locations(1, 1).first.label != exempted_caller raise "Updating gem_repo1 is unsupported -- use gem_repo2 instead" end @@ -250,14 +283,8 @@ module Spec @_build_path = "#{path}/gems" @_build_repo = File.basename(path) yield - with_gem_path_as base_system_gem_path do - Dir[base_system_gem_path.join("gems/rubygems-generate_index*/lib")].first || - raise("Could not find rubygems-generate_index lib directory in #{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 + options = { build_compact: build_compact_index } + Gem::Indexer.new(path, options).generate_index ensure @_build_path = nil @_build_repo = nil @@ -313,11 +340,6 @@ module Spec private - def build_empty_repo(gem_repo, **kwargs, &blk) - FileUtils.rm_rf gem_repo - build_repo(gem_repo, **kwargs, &blk) - end - def build_with(builder, name, args, &blk) @_build_path ||= nil @_build_repo ||= nil @@ -401,18 +423,23 @@ module Spec 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 + @spec = Spec::Path.loaded_gemspec.dup + @spec.version = version || Bundler::VERSION + end + + def required_ruby_version + @spec.required_ruby_version + end + + def required_ruby_version=(x) + @spec.required_ruby_version = x end def _build(options = {}) - full_name = "bundler-#{@version}" - build_path = @context.tmp + full_name + full_name = "bundler-#{@spec.version}" + build_path = (options[:build_path] || @context.tmp) + full_name bundler_path = build_path + "#{full_name}.gem" FileUtils.mkdir_p build_path @@ -423,15 +450,19 @@ module Spec 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 + FileUtils.cp File.expand_path(shipped_file, @context.source_root), 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 + @context.replace_version_file(@spec.version, dir: build_path) + @context.replace_changelog(@spec.version, dir: build_path) if options[:released] - Spec::BuildMetadata.write_build_metadata(dir: build_path) + Spec::BuildMetadata.write_build_metadata(dir: build_path, version: @spec.version.to_s) - @context.gem_command "build #{@context.relative_gemspec}", dir: build_path + Dir.chdir build_path do + Gem::DefaultUserInteraction.use_ui(Gem::SilentUI.new) do + Gem::Package.build(@spec) + end + end if block_given? yield(bundler_path) @@ -439,7 +470,7 @@ module Spec FileUtils.mv bundler_path, options[:path] end ensure - build_path.rmtree + FileUtils.rm_rf build_path end end @@ -526,10 +557,8 @@ module Spec when false # do nothing when :yaml - @spec.files << "#{name}.gemspec" @files["#{name}.gemspec"] = @spec.to_yaml else - @spec.files << "#{name}.gemspec" @files["#{name}.gemspec"] = @spec.to_ruby end @@ -630,14 +659,14 @@ module Spec destination = opts[:path] || _default_path FileUtils.mkdir_p(lib_path.join(destination)) - if opts[:gemspec] == :yaml || opts[:gemspec] == false + if [:yaml, false].include?(opts[:gemspec]) Dir.chdir(lib_path) do Bundler.rubygems.build(@spec, opts[:skip_validation]) end elsif opts[:skip_validation] - @context.gem_command "build --force #{@spec.name}", dir: lib_path + Dir.chdir(lib_path) { Gem::Package.build(@spec, true) } else - @context.gem_command "build #{@spec.name}", dir: lib_path + Dir.chdir(lib_path) { Gem::Package.build(@spec) } end gem_path = File.expand_path("#{@spec.full_name}.gem", lib_path) @@ -666,54 +695,54 @@ module Spec TEST_CERT = <<~CERT -----BEGIN CERTIFICATE----- - MIIDMjCCAhqgAwIBAgIBATANBgkqhkiG9w0BAQUFADAnMQwwCgYDVQQDDAN5b3Ux + MIIDNTCCAh2gAwIBAgIBATANBgkqhkiG9w0BAQsFADAnMQwwCgYDVQQDDAN5b3Ux FzAVBgoJkiaJk/IsZAEZFgdleGFtcGxlMB4XDTE1MDIwODAwMTIyM1oXDTQyMDYy NTAwMTIyM1owJzEMMAoGA1UEAwwDeW91MRcwFQYKCZImiZPyLGQBGRYHZXhhbXBs - ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANlvFdpN43c4DMS9Jo06 - m0a7k3bQ3HWQ1yrYhZMi77F1F73NpBknYHIzDktQpGn6hs/4QFJT4m4zNEBF47UL - jHU5nTK5rjkS3niGYUjvh3ZEzVeo9zHUlD/UwflDo4ALl3TSo2KY/KdPS/UTdLXL - ajkQvaVJtEDgBPE3DPhlj5whp+Ik3mDHej7qpV6F502leAwYaFyOtlEG/ZGNG+nZ - L0clH0j77HpP42AylHDi+vakEM3xcjo9BeWQ6Vkboic93c9RTt6CWBWxMQP7Nol1 - MOebz9XOSQclxpxWteXNfPRtMdAhmRl76SMI8ywzThNPpa4EH/yz34ftebVOgKyM - nd0CAwEAAaNpMGcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0OBBYEFA7D - n9qo0np23qi3aOYuAAPn/5IdMBYGA1UdEQQPMA2BC3lvdUBleGFtcGxlMBYGA1Ud - EgQPMA2BC3lvdUBleGFtcGxlMA0GCSqGSIb3DQEBBQUAA4IBAQA7Gyk62sWOUX/N - vk4tJrgKESph6Ns8+E36A7n3jt8zCep8ldzMvwTWquf9iqhsC68FilEoaDnUlWw7 - d6oNuaFkv7zfrWGLlvqQJC+cu2X5EpcCksg5oRp8VNbwJysJ6JgwosxzROII8eXc - R+j1j6mDvQYqig2QOnzf480pjaqbP+tspfDFZbhKPrgM3Blrb3ZYuFpv4zkqI7aB - 6fuk2DUhNO1CuwrJA84TqC+jGo73bDKaT5hrIDiaJRrN5+zcWja2uEWrj5jSbep4 - oXdEdyH73hOHMBP40uds3PqnUsxEJhzjB2sCCe1geV24kw9J4m7EQXPVkUKDgKrt - LlpDmOoo + ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMkupYkg3Nd1oXM3fo0d + mVJBWNrni88lKDuIIQXwcKe6XCgiloZG708ecLTOws9+o9MkTl9Wtpf/WGXT98NK + EPUYakd2Fv1SuD1jWYlP7iDR6hB3RkWBm5ziujYftVJ4ZrPD42PLjDASvlh75Tvr + MeM7yq/qkcgNsd9dQyUvMNPks3tla9je7Dt7Auli2IN3CNXys7gIOfwJH0Bb/M6t + y7oUfpoUKAfLzwe61abztgDu1lSNgdFBM1kcxYflyh/FkX5TlAcWeAXzLrnxAXGR + UxXrxW4oPC+kZi/pDRBd7X4zQDx7bCmr1+FsS3M05i3w5E08Tt9iKRk4V8nCmE4i + k6UCAwEAAaNsMGowCQYDVR0TBAIwADAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYE + FOOOFw5TNAqt/TcRRZEU3Dg/58XuMBYGA1UdEQQPMA2BC3lvdUBleGFtcGxlMBYG + A1UdEgQPMA2BC3lvdUBleGFtcGxlMA0GCSqGSIb3DQEBCwUAA4IBAQAy3xnmobxU + 1SyhHvoIXTJmG0wt1DQ/Dqwjy362LpEf1UHt29wtg1Mph58eVtl93z5Vd2t4/O77 + E2BHpSu9ujc6/Br4+2uA/Qk/xRyLBtZAwty6J4uFvOOg985HonN+RCUZbKSUTmtA + TZvNtIDAZFQ8Tu75K4gIBxDcz7biGi4i1VJ3F3GNCNeossr9IQwKvb+UWFq14U5R + IzUnGgMIzcjUG2kKQvddRD1CjS+egtcLvShbOfm5bs4w4rfQ2FPF+Aaf9v7fxa/c + Jrf3K+cB19eAy7O4nlPG1xurvnZd0QpqRk++werrBuKe1Pgga7YBLePfJhzwqcZv + wVOSsB870yeO -----END CERTIFICATE----- CERT TEST_PKEY = <<~PKEY -----BEGIN RSA PRIVATE KEY----- - MIIEowIBAAKCAQEA2W8V2k3jdzgMxL0mjTqbRruTdtDcdZDXKtiFkyLvsXUXvc2k - GSdgcjMOS1CkafqGz/hAUlPibjM0QEXjtQuMdTmdMrmuORLeeIZhSO+HdkTNV6j3 - MdSUP9TB+UOjgAuXdNKjYpj8p09L9RN0tctqORC9pUm0QOAE8TcM+GWPnCGn4iTe - YMd6PuqlXoXnTaV4DBhoXI62UQb9kY0b6dkvRyUfSPvsek/jYDKUcOL69qQQzfFy - Oj0F5ZDpWRuiJz3dz1FO3oJYFbExA/s2iXUw55vP1c5JByXGnFa15c189G0x0CGZ - GXvpIwjzLDNOE0+lrgQf/LPfh+15tU6ArIyd3QIDAQABAoIBACbDqz20TS1gDMa2 - gj0DidNedbflHKjJHdNBru7Ad8NHgOgR1YO2hXdWquG6itVqGMbTF4SV9/R1pIcg - 7qvEV1I+50u31tvOBWOvcYCzU48+TO2n7gowQA3xPHPYHzog1uu48fAOHl0lwgD7 - av9OOK3b0jO5pC08wyTOD73pPWU0NrkTh2+N364leIi1pNuI1z4V+nEuIIm7XpVd - 5V4sXidMTiEMJwE6baEDfTjHKaoRndXrrPo3ryIXmcX7Ag1SwAQwF5fBCRToCgIx - dszEZB1bJD5gA6r+eGnJLB/F60nK607az5o3EdguoB2LKa6q6krpaRCmZU5svvoF - J7xgBPECgYEA8RIzHAQ3zbaibKdnllBLIgsqGdSzebTLKheFuigRotEV3Or/z5Lg - k/nVnThWVkTOSRqXTNpJAME6a4KTdcVSxYP+SdZVO1esazHrGb7xPVb7MWSE1cqp - WEk3Yy8OUOPoPQMc4dyGzd30Mi8IBB6gnFIYOTrpUo0XtkBv8rGGhfsCgYEA5uYn - 6QgL4NqNT84IXylmMb5ia3iBt6lhxI/A28CDtQvfScl4eYK0IjBwdfG6E1vJgyzg - nJzv3xEVo9bz+Kq7CcThWpK5JQaPnsV0Q74Wjk0ShHet15txOdJuKImnh5F6lylC - GTLR9gnptytfMH/uuw4ws0Q2kcg4l5NHKOWOnAcCgYEAvAwIVkhsB0n59Wu4gCZu - FUZENxYWUk/XUyQ6KnZrG2ih90xQ8+iMyqFOIm/52R2fFKNrdoWoALC6E3ct8+ZS - pMRLrelFXx8K3it4SwMJR2H8XBEfFW4bH0UtsW7Zafv+AunUs9LETP5gKG1LgXsq - qgXX43yy2LQ61O365YPZfdUCgYBVbTvA3MhARbvYldrFEnUL3GtfZbNgdxuD9Mee - xig0eJMBIrgfBLuOlqtVB70XYnM4xAbKCso4loKSHnofO1N99siFkRlM2JOUY2tz - kMWZmmxKdFjuF0WZ5f/5oYxI/QsFGC+rUQEbbWl56mMKd5qkvEhKWudxoklF0yiV - ufC8SwKBgDWb8iWqWN5a/kfvKoxFcDM74UHk/SeKMGAL+ujKLf58F+CbweM5pX9C - EUsxeoUEraVWTiyFVNqD81rCdceus9TdBj0ZIK1vUttaRZyrMAwF0uQSfjtxsOpd - l69BkyvzjgDPkmOHVGiSZDLi3YDvypbUpo6LOy4v5rVg5U2F/A0v + MIIEowIBAAKCAQEAyS6liSDc13Whczd+jR2ZUkFY2ueLzyUoO4ghBfBwp7pcKCKW + hkbvTx5wtM7Cz36j0yROX1a2l/9YZdP3w0oQ9RhqR3YW/VK4PWNZiU/uINHqEHdG + RYGbnOK6Nh+1Unhms8PjY8uMMBK+WHvlO+sx4zvKr+qRyA2x311DJS8w0+Sze2Vr + 2N7sO3sC6WLYg3cI1fKzuAg5/AkfQFv8zq3LuhR+mhQoB8vPB7rVpvO2AO7WVI2B + 0UEzWRzFh+XKH8WRflOUBxZ4BfMuufEBcZFTFevFbig8L6RmL+kNEF3tfjNAPHts + KavX4WxLczTmLfDkTTxO32IpGThXycKYTiKTpQIDAQABAoIBABpyrHEWRed5X7aN + kXCBzKSN/LLChT8VNnB6bppLnV501yVbmV2hDlg2EJZkfCMvwIptwnPcKs2uqZ4G + u2gMC6X9Bgkg/YK4u4nZJBiIzoMNYEUL48wYGYS1dcokaapO3nQ8M1+XjyAexrFL + 5btL1IIisScRTQWiGe6FtzcN43sSNkBISyDF5zG4Kodynqi0ekITmMl2q5XLWcsM + KBnmZcRFEmFae2YYczVy8SXNApkZEvN69znvAX1iDNnZ3sJFchXo1nRPt4stOOKw + mydgIYqaNQ22aF3OkblvoA4Y4m+X2Qt1sfkryKa5xTT7DSE81GmmazNI64EWqtES + 6Xde6P0CgYEA+V1vuSnE5fWX188abWMbVwNMC71WfHbntFmI+qwWYPEpickm+RGX + DDfXs5unlVX4KUmjfplgavO29op1GZTuD9TlRnUAV0+0aJnNq4DY6XsHfD84qsBr + gQGEHeJ1cMGNDnZR/EV3eudMalj9Qjpx9NoXNzMykb0/SUYZQemiqwcCgYEAzokC + s0GoHVJqan4dfU0h0G5QPncrajW9DGG1ySxK/A2eqbVB8W2ZQx39OS26/Gydb31p + cR7zm8PZpNbzLqlIMEbD4F6q22xxvYVtDx/HHPjxHMi87yxwQ9uLDUHoMa/LciTO + djv3D1xTDDGxbpjmsdmINetunAs3htxku7JY5PMCgYBs3/TVvXzwgmhHm28Ib4sS + VKgxP/uw4CGORsFd4SDsNp9SP3c6rAltFjyheMaUlzKApFwz/DdyuvIZdp5mCvZe + BzALsS3y8SPtv6lixiDu3/6GqvvM4bKOYuESQzvPfVJfDB4DrTjben2MuUnqTqZO + p6IXQc1EgIJPNcH1W1LgpQKBgAKZlPAevngIBpDqn4JpSyititMOevxuSr/yJvCu + Xw9HOJ0YTAk3APvoT7y9h6IP1/eEU6R56EUotP+vOQZ4WRFKgsK7TllOxyvElzfe + hYom1BoxqLc2Dv+7rsdu8fZWKTB5qCOy44xM9DquEXa79AN/IojTOuQ5++v1sErw + ls/jAoGBANneGe9ogN51mYkrLyg1fhU1i24gFRq+sPGEvsCUoE6Vjw/lawQQ80T8 + v45TFqvhoGpgznqy3qxDJyguquZg6HN2yW6HE2Dvk7uk3XogcjdXgNDmWqb2j0eE + z9pKzHCqfwNVPuYf44Znyo2YeyZ2kHn42MU73oXuFshUs3QHcH+P -----END RSA PRIVATE KEY----- PKEY end diff --git a/spec/bundler/support/bundle b/spec/bundler/support/bundle new file mode 100755 index 0000000000..8f8b535295 --- /dev/null +++ b/spec/bundler/support/bundle @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative "../bundler/support/activate" + +load File.expand_path("bundle", Spec::Path.exedir) diff --git a/spec/bundler/support/bundle.rb b/spec/bundler/support/bundle.rb index 5d6d658040..aa7b121706 100644 --- a/spec/bundler/support/bundle.rb +++ b/spec/bundler/support/bundle.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true -require_relative "activate" +require_relative "path" -load File.expand_path("bundle", Spec::Path.bindir) +warn "#{__FILE__} is deprecated. Please use #{Spec::Path.dev_binstub} instead" + +load Spec::Path.dev_binstub diff --git a/spec/bundler/support/checksums.rb b/spec/bundler/support/checksums.rb index f3aa13ca9f..7b69bba668 100644 --- a/spec/bundler/support/checksums.rb +++ b/spec/bundler/support/checksums.rb @@ -3,6 +3,8 @@ module Spec module Checksums class ChecksumsBuilder + attr_reader :bundler_registered + def initialize(enabled = true, &block) @enabled = enabled @checksums = {} @@ -14,9 +16,11 @@ module Spec @checksums = @checksums.dup end - def checksum(repo, name, version, platform = Gem::Platform::RUBY) + def checksum(repo, name, version, platform = Gem::Platform::RUBY, folder = "gems") + @bundler_registered = true if name == "bundler" + name_tuple = Gem::NameTuple.new(name, version, platform) - gem_file = File.join(repo, "gems", "#{name_tuple.full_name}.gem") + gem_file = File.join(repo, folder, "#{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 @@ -50,21 +54,26 @@ module Spec end end - def checksums_section(enabled = true, &block) - ChecksumsBuilder.new(enabled, &block) + def checksums_section(enabled = true, bundler_checksum: true, &block) + ChecksumsBuilder.new(enabled, &block).tap do |builder| + next if builder.bundler_registered || !bundler_checksum + + next if Bundler::VERSION.to_s.end_with?(".dev") + builder.checksum(system_gem_path, "bundler", Bundler::VERSION, Gem::Platform::RUBY, "cache") + end end def checksums_section_when_enabled(target_lockfile = nil, &block) begin enabled = (target_lockfile || lockfile).match?(/^CHECKSUMS$/) rescue Errno::ENOENT - enabled = Bundler.feature_flag.bundler_3_mode? + enabled = true end checksums_section(enabled, &block) end def checksum_to_lock(*args) - checksums_section do |c| + checksums_section(true, bundler_checksum: false) do |c| c.checksum(*args) end.to_s.sub(/^CHECKSUMS\n/, "").strip end diff --git a/spec/bundler/support/command_execution.rb b/spec/bundler/support/command_execution.rb index 02726744d3..e2915b996d 100644 --- a/spec/bundler/support/command_execution.rb +++ b/spec/bundler/support/command_execution.rb @@ -2,9 +2,8 @@ module Spec class CommandExecution - def initialize(command, working_directory:, timeout:) + def initialize(command, timeout:) @command = command - @working_directory = working_directory @timeout = timeout @original_stdout = String.new @original_stderr = String.new @@ -73,7 +72,7 @@ module Spec attr_reader :failure_reason def normalize(string) - string.force_encoding(Encoding::UTF_8).strip.gsub("\r\n", "\n") + string.dup.force_encoding(Encoding::UTF_8).scrub.strip.gsub("\r\n", "\n") end end end diff --git a/spec/bundler/support/filters.rb b/spec/bundler/support/filters.rb index f52cb02588..2be25b4a78 100644 --- a/spec/bundler/support/filters.rb +++ b/spec/bundler/support/filters.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true class RequirementChecker < Proc - def self.against(present) - provided = Gem::Version.new(present) - + def self.against(provided) new do |required| - !Gem::Requirement.new(required).satisfied_by?(provided) + requirement = Gem::Requirement.new(required) + + !requirement.satisfied_by?(provided) end.tap do |checker| checker.provided = provided end @@ -18,11 +18,13 @@ class RequirementChecker < Proc end end +git_version = Gem::Version.new(`git --version`[/(\d+\.\d+\.\d+)/, 1]) + RSpec.configure do |config| config.filter_run_excluding realworld: true - config.filter_run_excluding bundler: RequirementChecker.against(Bundler::VERSION.split(".")[0]) - config.filter_run_excluding rubygems: RequirementChecker.against(Gem::VERSION) + config.filter_run_excluding rubygems: RequirementChecker.against(Gem.rubygems_version) + config.filter_run_excluding git: RequirementChecker.against(git_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? @@ -30,6 +32,11 @@ RSpec.configure do |config| 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_excluding mri_only: RUBY_ENGINE != "ruby" config.filter_run_when_matching :focus unless ENV["CI"] + + config.before(:each, :bundler) do |example| + bundle_config "simulate_version #{example.metadata[:bundler]}" + end end diff --git a/spec/bundler/support/hax.rb b/spec/bundler/support/hax.rb index 492c6ca8ed..46718f5fa4 100644 --- a/spec/bundler/support/hax.rb +++ b/spec/bundler/support/hax.rb @@ -19,21 +19,56 @@ module Gem @default_specifications_dir = nil end - if ENV["BUNDLER_SPEC_WINDOWS"] - @@win_platform = true # rubocop:disable Style/ClassVars - end + spec_platform = ENV["BUNDLER_SPEC_PLATFORM"] + if spec_platform + if /mingw|mswin/.match?(spec_platform) + @@win_platform = nil # rubocop:disable Style/ClassVars + RbConfig::CONFIG["host_os"] = spec_platform.gsub(/^[^-]+-/, "").tr("-", "_") + end - if ENV["BUNDLER_SPEC_PLATFORM"] - previous_platforms = @platforms - previous_local = Platform.local + RbConfig::CONFIG["arch"] = spec_platform class Platform - @local = new(ENV["BUNDLER_SPEC_PLATFORM"]) + @local = nil end - @platforms = previous_platforms.map {|platform| platform == previous_local ? Platform.local : platform } + @platforms = [] end if ENV["BUNDLER_SPEC_GEM_SOURCES"] self.sources = [ENV["BUNDLER_SPEC_GEM_SOURCES"]] end + + if ENV["BUNDLER_SPEC_READ_ONLY"] + module ReadOnly + def open(file, mode) + if file != IO::NULL && mode == "wb" + raise Errno::EROFS + else + super + end + end + end + + File.singleton_class.prepend ReadOnly + end + + if ENV["BUNDLER_SPEC_FAKE_RESOLVE"] + module FakeResolv + def getaddrinfo(host, port) + if host == ENV["BUNDLER_SPEC_FAKE_RESOLVE"] + [["AF_INET", port, "127.0.0.1", "127.0.0.1", 2, 2, 17]] + else + super + end + end + end + + Socket.singleton_class.prepend FakeResolv + end +end + +# mise installed rubygems_plugin.rb to system wide `site_ruby` directory. +# This empty module avoid to call `mise` command. +module ReshimInstaller + def self.reshim; end end diff --git a/spec/bundler/support/helpers.rb b/spec/bundler/support/helpers.rb index 294b932ea1..b0d4b5008b 100644 --- a/spec/bundler/support/helpers.rb +++ b/spec/bundler/support/helpers.rb @@ -20,20 +20,17 @@ module Spec def reset! Dir.glob("#{tmp}/{gems/*,*}", File::FNM_DOTMATCH).each do |dir| next if %w[base base_system remote1 rubocop standard gems rubygems . ..].include?(File.basename(dir)) - FileUtils.rm_rf(dir) + FileUtils.rm_r(dir) end FileUtils.mkdir_p(home) FileUtils.mkdir_p(tmpdir) - reset_paths! - end - - def reset_paths! Bundler.reset! + Bundler::Source::Git::GitProxy.reset Gem.clear_paths end - def the_bundle(*args) - TheBundle.new(*args) + def the_bundle + TheBundle.new end MAJOR_DEPRECATION = /^\[DEPRECATED\]\s*/ @@ -43,7 +40,7 @@ module Spec end def deprecations - err.split("\n").select {|l| l =~ MAJOR_DEPRECATION }.join("\n").split(MAJOR_DEPRECATION) + err.split("\n").filter_map {|l| l.sub(MAJOR_DEPRECATION, "") if l.match?(MAJOR_DEPRECATION) } end def run(cmd, *args) @@ -58,7 +55,7 @@ module Spec begin #{ruby} rescue LoadError => e - warn "ZOMG LOAD ERROR" if e.message.include?("-- #{name}") + warn e.message if e.message.include?("-- #{name}") end RUBY opts = args.last.is_a?(Hash) ? args.pop : {} @@ -66,12 +63,15 @@ module Spec run(cmd, *args) end + def in_bundled_app(cmd, options = {}) + sys_exec(cmd, dir: bundled_app, raise_on_error: options[:raise_on_error]) + end + def bundle(cmd, options = {}, &block) bundle_bin = options.delete(:bundle_bin) bundle_bin ||= installed_bindir.join("bundle") env = options.delete(:env) || {} - preserve_ruby_flags = options.delete(:preserve_ruby_flags) requires = options.delete(:requires) || [] @@ -79,15 +79,14 @@ module Spec custom_load_path = options.delete(:load_path) load_path = [] - load_path << spec_dir load_path << custom_load_path if custom_load_path - build_ruby_options = { load_path: load_path, requires: requires, env: env } - build_ruby_options.merge!(artifice: options.delete(:artifice)) if options.key?(:artifice) + build_env_options = { load_path: load_path, requires: requires, env: env } + build_env_options.merge!(artifice: options.delete(:artifice)) if options.key?(:artifice) || cmd.start_with?("exec") match_source(cmd) - env, ruby_cmd = build_ruby_cmd(build_ruby_options) + env = build_env(build_env_options) raise_on_error = options.delete(:raise_on_error) @@ -102,8 +101,7 @@ module Spec end end.join - cmd = "#{ruby_cmd} #{bundle_bin} #{cmd}#{args}" - env["BUNDLER_SPEC_ORIGINAL_CMD"] = "#{ruby_cmd} #{bundle_bin}" if preserve_ruby_flags + cmd = "#{Gem.ruby} #{bundle_bin} #{cmd}#{args}" sys_exec(cmd, { env: env, dir: dir, raise_on_error: raise_on_error }, &block) end @@ -123,10 +121,11 @@ module Spec end def ruby(ruby, options = {}) - env, ruby_cmd = build_ruby_cmd({ artifice: nil }.merge(options)) + env = build_env({ artifice: nil }.merge(options)) escaped_ruby = ruby.shellescape options[:env] = env if env - sys_exec(%(#{ruby_cmd} -w -e #{escaped_ruby}), options) + options[:dir] ||= bundled_app + sys_exec(%(#{Gem.ruby} -w -e #{escaped_ruby}), options) end def load_error_ruby(ruby, name, opts = {}) @@ -134,22 +133,24 @@ module Spec begin #{ruby} rescue LoadError => e - warn "ZOMG LOAD ERROR" if e.message.include?("-- #{name}") + warn e.message if e.message.include?("-- #{name}") end R end - def build_ruby_cmd(options = {}) - libs = options.delete(:load_path) - lib_option = libs ? "-I#{libs.join(File::PATH_SEPARATOR)}" : [] - + def build_env(options = {}) env = options.delete(:env) || {} + libs = options.delete(:load_path) || [] + env["RUBYOPT"] = opt_add("-I#{libs.join(File::PATH_SEPARATOR)}", env["RUBYOPT"]) if libs.any? + current_example = RSpec.current_example main_source = @gemfile_source if defined?(@gemfile_source) compact_index_main_source = main_source&.start_with?("https://gem.repo", "https://gems.security") requires = options.delete(:requires) || [] + requires << hax + artifice = options.delete(:artifice) do if current_example && current_example.metadata[:realworld] "vcr" @@ -172,11 +173,9 @@ module Spec requires << "#{Path.spec_dir}/support/artifice/#{artifice}.rb" end - requires << "#{Path.spec_dir}/support/hax.rb" + requires.each {|r| env["RUBYOPT"] = opt_add("-r#{r}", env["RUBYOPT"]) } - require_option = requires.map {|r| "-r#{r}" } - - [env, [Gem.ruby, *lib_option, *require_option].compact.join(" ")] + env end def gembin(cmd, options = {}) @@ -184,36 +183,20 @@ module Spec sys_exec(cmd.to_s, options) end - def gem_command(command, options = {}) - env = options[:env] || {} - env["RUBYOPT"] = opt_add(opt_add("-r#{spec_dir}/support/hax.rb", env["RUBYOPT"]), ENV["RUBYOPT"]) - options[:env] = env - - # Sometimes `gem install` commands hang at dns resolution, which has a - # default timeout of 60 seconds. When that happens, the timeout for a - # command is expired too. So give `gem install` commands a bit more time. - options[:timeout] = 120 - - output = sys_exec("#{Path.gem_bin} #{command}", options) - stderr = last_command.stderr - raise stderr if stderr.include?("WARNING") && !allowed_rubygems_warning?(stderr) - output - end - - def rake - "#{Gem.ruby} -S #{ENV["GEM_PATH"]}/bin/rake" - end - def sys_exec(cmd, options = {}, &block) env = options[:env] || {} env["RUBYOPT"] = opt_add(opt_add("-r#{spec_dir}/support/switch_rubygems.rb", env["RUBYOPT"]), ENV["RUBYOPT"]) options[:env] = env - options[:dir] ||= bundled_app sh(cmd, options, &block) end - def config(config = nil, path = bundled_app(".bundle/config")) + def bundle_config(config = nil, path = bundled_app(".bundle/config")) + if config.is_a?(String) + key, value = config.split(" ", 2) + config = { Bundler::Settings.key_for(key) => value } + end + current = File.exist?(path) ? Psych.load_file(path) : {} return current unless config @@ -230,8 +213,8 @@ module Spec new_config end - def global_config(config = nil) - config(config, home(".bundle/config")) + def bundle_config_global(config = nil) + bundle_config(config, home(".bundle/config")) end def create_file(path, contents = "") @@ -303,6 +286,10 @@ module Spec bundle :lock, opts end + def base_system_gems(*names, **options) + system_gems names.map {|name| find_base_path(name) }, **options + end + def system_gems(*gems) gems = gems.flatten options = gems.last.is_a?(Hash) ? gems.pop : {} @@ -310,9 +297,10 @@ module Spec default = options.fetch(:default, false) 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, install_dir, default) } + bundler = gem_name.match(/\Abundler-(?<version>.*)\z/) + + if bundler + with_built_bundler(bundler[:version], released: options.fetch(:released, false)) {|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 @@ -325,22 +313,96 @@ module Spec def self.install_dev_bundler extend self - system_gems :bundler, path: pristine_system_gem_path + with_built_bundler(nil, build_path: tmp_root) {|gem_path| install_gem(gem_path, pristine_system_gem_path) } end def install_gem(path, install_dir, default = false) - raise "OMG `#{path}` does not exist!" unless File.exist?(path) + raise ArgumentError, "`#{path}` does not exist!" unless File.exist?(path) + + require "rubygems/installer" + + with_simulated_platform do + installer = Gem::Installer.at( + path.to_s, + install_dir: install_dir.to_s, + document: [], + ignore_dependencies: true, + wrappers: true, + env_shebang: true, + force: true + ) + installer.install + end + + if default + gem = Pathname.new(path).basename.to_s.match(/(.*)\.gem/)[1] + + # Revert Gem::Installer#write_spec and apply Gem::Installer#write_default_spec + FileUtils.mkdir_p File.join(install_dir, "specifications", "default") + File.rename File.join(install_dir, "specifications", gem + ".gemspec"), + File.join(install_dir, "specifications", "default", gem + ".gemspec") + + # Revert Gem::Installer#write_cache_file + File.delete File.join(install_dir, "cache", gem + ".gem") + end + end - args = "--no-document --ignore-dependencies --verbose --local --install-dir #{install_dir}" - args += " --default" if default + def uninstall_gem(name, options = {}) + require "rubygems/uninstaller" - gem_command "install #{args} '#{path}'" + gem_home = options.dig(:env, "GEM_HOME") || system_gem_path.to_s + + with_env_vars("GEM_HOME" => gem_home) do + Gem.clear_paths + + uninstaller = Gem::Uninstaller.new( + name, + ignore: true, + executables: true, + all: true + ) + uninstaller.uninstall + ensure + Gem.clear_paths + end end - def with_built_bundler(version = nil, &block) + def installed_gems_list(options = {}) + gem_home = options.dig(:env, "GEM_HOME") || system_gem_path.to_s + + # Temporarily set GEM_HOME for the command + old_gem_home = ENV["GEM_HOME"] + ENV["GEM_HOME"] = gem_home + Gem.clear_paths + + begin + require "rubygems/commands/list_command" + + # Capture output from the list command + require "stringio" + output_io = StringIO.new + cmd = Gem::Commands::ListCommand.new + cmd.ui = Gem::StreamUI.new(StringIO.new, output_io, StringIO.new, false) + cmd.invoke + output = output_io.string.strip + ensure + ENV["GEM_HOME"] = old_gem_home + Gem.clear_paths + end + + # Create a fake command execution so `out` helper works + command_execution = Spec::CommandExecution.new("gem list", timeout: 60) + command_execution.original_stdout << output + command_execution.exitstatus = 0 + command_executions << command_execution + + output + end + + def with_built_bundler(version = nil, opts = {}, &block) require_relative "builders" - Builders::BundlerBuilder.new(self, "bundler", version)._build(&block) + Builders::BundlerBuilder.new(self, "bundler", version)._build(opts, &block) end def with_gem_path_as(path) @@ -368,6 +430,36 @@ module Spec ENV.replace(backup) end + # Simulate the platform set by BUNDLER_SPEC_PLATFORM for in-process + # operations, mirroring what hax.rb does for subprocesses. + def with_simulated_platform + spec_platform = ENV["BUNDLER_SPEC_PLATFORM"] + unless spec_platform + return yield + end + + old_arch = RbConfig::CONFIG["arch"] + old_host_os = RbConfig::CONFIG["host_os"] + + if /mingw|mswin/.match?(spec_platform) + Gem.class_variable_set(:@@win_platform, nil) # rubocop:disable Style/ClassVars + RbConfig::CONFIG["host_os"] = spec_platform.gsub(/^[^-]+-/, "").tr("-", "_") + end + + RbConfig::CONFIG["arch"] = spec_platform + Gem::Platform.instance_variable_set(:@local, nil) + Gem.instance_variable_set(:@platforms, []) + + yield + ensure + if spec_platform + RbConfig::CONFIG["arch"] = old_arch + RbConfig::CONFIG["host_os"] = old_host_os + Gem::Platform.instance_variable_set(:@local, nil) + Gem.instance_variable_set(:@platforms, []) + end + end + def with_path_added(path) with_path_as([path.to_s, ENV["PATH"]].join(File::PATH_SEPARATOR)) do yield @@ -393,37 +485,34 @@ module Spec end def pristine_system_gems(*gems) - FileUtils.rm_rf(system_gem_path) + FileUtils.rm_r(system_gem_path) - system_gems(*gems) - end - - def realworld_system_gems(*gems) - gems = gems.flatten - opts = gems.last.is_a?(Hash) ? gems.pop : {} - path = opts.fetch(:path, system_gem_path) - - gems.each do |gem| - gem_command "install --no-document --verbose --install-dir #{path} #{gem}" + if gems.any? + system_gems(*gems) + else + default_system_gems end end 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_repo}/gems/#{g}.gem" - raise "OMG `#{path}` does not exist!" unless File.exist?(path) + raise ArgumentError, "`#{path}` does not exist!" unless File.exist?(path) FileUtils.cp(path, "#{bundled_app}/vendor/cache") end end def simulate_new_machine - FileUtils.rm_rf bundled_app(".bundle") - pristine_system_gems :bundler + FileUtils.rm_r bundled_app(".bundle") + pristine_system_gems + end + + def default_system_gems + FileUtils.cp_r pristine_system_gem_path, system_gem_path end def simulate_ruby_platform(ruby_platform) @@ -442,16 +531,6 @@ module Spec ENV["BUNDLER_SPEC_PLATFORM"] = old if block_given? end - def simulate_windows(platform = x86_mswin32) - old = ENV["BUNDLER_SPEC_WINDOWS"] - ENV["BUNDLER_SPEC_WINDOWS"] = "true" - simulate_platform platform do - yield - end - ensure - ENV["BUNDLER_SPEC_WINDOWS"] = old - end - def current_ruby_minor Gem.ruby_version.segments.tap {|s| s.delete_at(2) }.join(".") end @@ -460,12 +539,6 @@ module Spec ruby_major_minor.map.with_index {|s, i| i == 1 ? s + 1 : s }.join(".") 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 - def ruby_major_minor Gem.ruby_version.segments[0..1] end @@ -523,45 +596,27 @@ module Spec end end - def require_rack - # need to hack, so we can require rack + def require_rack_test + # need to hack, so we can require rack for testing old_gem_home = ENV["GEM_HOME"] - ENV["GEM_HOME"] = Spec::Path.base_system_gem_path.to_s - require "rack" + ENV["GEM_HOME"] = Spec::Path.scoped_base_system_gem_path.to_s + require "rack/test" ENV["GEM_HOME"] = old_gem_home end - def wait_for_server(host, port, seconds = 15) - tries = 0 - sleep 0.5 - TCPSocket.new(host, port) - rescue StandardError => e - raise(e) if tries > (seconds * 2) - tries += 1 - retry - end - - def find_unused_port - port = 21_453 - begin - port += 1 while TCPSocket.new("127.0.0.1", port) - rescue StandardError - false - end - 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 empty_repo4 + FileUtils.rm_r gem_repo4 - def allowed_rubygems_warning?(text) - text.include?("open-ended") || text.include?("is a symlink") || text.include?("rake based") || text.include?("expected RubyGems version") + build_repo4 {} end + private + def match_source(contents) match = /source ["']?(?<source>http[^"']+)["']?/.match(contents) return unless match diff --git a/spec/bundler/support/indexes.rb b/spec/bundler/support/indexes.rb index 7960bae59f..1fbdd49abe 100644 --- a/spec/bundler/support/indexes.rb +++ b/spec/bundler/support/indexes.rb @@ -66,7 +66,6 @@ module Spec end def should_conservative_resolve_and_include(opts, unlock, specs) - # empty unlock means unlock all opts = Array(opts) search = Bundler::GemVersionPromoter.new.tap do |s| s.level = opts.first @@ -123,7 +122,7 @@ module Spec end versions "1.0 1.2 1.2.1 1.2.2 1.3 1.3.0.1 1.3.5 1.4.0 1.4.2 1.4.2.1" do |version| - platforms "ruby java mswin32 mingw32 x64-mingw32" do |platform| + platforms "ruby java mswin32 mingw32 x64-mingw-ucrt" do |platform| 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 diff --git a/spec/bundler/support/matchers.rb b/spec/bundler/support/matchers.rb index 9f311fc0d7..5a3c38a4db 100644 --- a/spec/bundler/support/matchers.rb +++ b/spec/bundler/support/matchers.rb @@ -52,7 +52,7 @@ module Spec end def self.define_compound_matcher(matcher, preconditions, &declarations) - raise "Must have preconditions to define a compound matcher" if preconditions.empty? + raise ArgumentError, "Must have preconditions to define a compound matcher" if preconditions.empty? define_method(matcher) do |*expected, &block_arg| Precondition.new( RSpec::Matchers::DSL::Matcher.new(matcher, declarations, self, *expected, &block_arg), diff --git a/spec/bundler/support/path.rb b/spec/bundler/support/path.rb index 76e28db10b..2e6486412f 100644 --- a/spec/bundler/support/path.rb +++ b/spec/bundler/support/path.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "pathname" +require "pathname" unless defined?(Pathname) require "rbconfig" require_relative "env" @@ -10,7 +10,7 @@ module Spec include Spec::Env def source_root - @source_root ||= Pathname.new(ruby_core? ? "../../.." : "../..").expand_path(__dir__) + @source_root ||= Pathname.new(ruby_core? ? "../../.." : "../../bundler").expand_path(__dir__) end def root @@ -25,12 +25,8 @@ module Spec @relative_gemspec ||= ruby_core? ? "lib/bundler/bundler.gemspec" : "bundler.gemspec" end - def gemspec_dir - @gemspec_dir ||= gemspec.parent - end - def loaded_gemspec - @loaded_gemspec ||= Gem::Specification.load(gemspec.to_s) + @loaded_gemspec ||= Dir.chdir(source_root) { Gem::Specification.load(gemspec.to_s) } end def test_gemfile @@ -49,8 +45,16 @@ module Spec @dev_gemfile ||= tool_dir.join("dev_gems.rb") end + def dev_binstub + @dev_binstub ||= bindir.join("bundle") + end + def bindir - @bindir ||= source_root.join(ruby_core? ? "libexec" : "exe") + @bindir ||= source_root.join(ruby_core? ? "spec/bin" : "../bin") + end + + def exedir + @exedir ||= source_root.join(ruby_core? ? "libexec" : "exe") end def installed_bindir @@ -67,18 +71,22 @@ module Spec def path env_path = ENV["PATH"] - env_path = env_path.split(File::PATH_SEPARATOR).reject {|path| path == bindir.to_s }.join(File::PATH_SEPARATOR) if ruby_core? + env_path = env_path.split(File::PATH_SEPARATOR).reject {|path| path == exedir.to_s }.join(File::PATH_SEPARATOR) if ruby_core? env_path end def spec_dir - @spec_dir ||= source_root.join(ruby_core? ? "spec/bundler" : "spec") + @spec_dir ||= source_root.join(ruby_core? ? "spec/bundler" : "../spec") end def man_dir @man_dir ||= lib_dir.join("bundler/man") end + def hax + @hax ||= spec_dir.join("support/hax.rb") + end + def tracked_files @tracked_files ||= git_ls_files(tracked_files_glob) end @@ -102,18 +110,28 @@ module Spec end def tmp(*path) - tmp_root(scope).join(*path) - end - - def tmp_root(scope) - source_root.join("tmp", "#{test_env_version}.#{scope}") + tmp_root.join("#{test_env_version}.#{scope}").join(*path) + end + + def tmp_root + if ruby_core? && (tmpdir = ENV["TMPDIR"]) + # Use realpath to resolve any symlinks in TMPDIR (e.g., on macOS /var -> /private/var) + real = begin + File.realpath(tmpdir) + rescue Errno::ENOENT, Errno::EACCES + tmpdir + end + Pathname(real) + else + (ruby_core? ? source_root : source_root.parent).join("tmp") + end end # Bump this version whenever you make a breaking change to the spec setup # that requires regenerating tmp/. def test_env_version - 1 + 2 end def scope @@ -128,19 +146,15 @@ module Spec end def default_bundle_path(*path) - if Bundler.feature_flag.default_install_uses_path? - local_gem_path(*path) - else - system_gem_path(*path) - end + system_gem_path(*path) 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 + default_bundle_path("cache/bundler", *path) + end + + def compact_index_cache_path + home(".bundle/cache/compact_index") end def bundled_app(*path) @@ -171,20 +185,20 @@ module Spec bundled_app("Gemfile.lock") end - def base_system_gem_path - scoped_gem_path(base_system_gems) + def scoped_base_system_gem_path + scoped_gem_path(base_system_gem_path) end - def base_system_gems - tmp("gems/base") + def base_system_gem_path + tmp_root.join("gems/base") end - def rubocop_gems - tmp("gems/rubocop") + def rubocop_gem_path + tmp_root.join("gems/rubocop") end - def standard_gems - tmp("gems/standard") + def standard_gem_path + tmp_root.join("gems/standard") end def file_uri_for(path) @@ -195,35 +209,35 @@ module Spec end def gem_repo1(*args) - tmp("gems/remote1", *args) + gem_path("remote1", *args) end def gem_repo_missing(*args) - tmp("gems/missing", *args) + gem_path("missing", *args) end def gem_repo2(*args) - tmp("gems/remote2", *args) + gem_path("remote2", *args) end def gem_repo3(*args) - tmp("gems/remote3", *args) + gem_path("remote3", *args) end def gem_repo4(*args) - tmp("gems/remote4", *args) + gem_path("remote4", *args) end def security_repo(*args) - tmp("gems/security_repo", *args) + gem_path("security_repo", *args) end def system_gem_path(*path) - tmp("gems/system", *path) + gem_path("system", *path) end def pristine_system_gem_path - tmp("gems/base_system") + tmp_root.join("gems/pristine_system") end def local_gem_path(*path, base: bundled_app) @@ -234,6 +248,10 @@ module Spec base.join(Gem.ruby_engine, RbConfig::CONFIG["ruby_version"]) end + def gem_path(*args) + tmp("gems", *args) + end + def lib_path(*args) tmp("libs", *args) end @@ -261,7 +279,7 @@ module Spec def replace_version_file(version, dir: source_root) version_file = File.expand_path("lib/bundler/version.rb", dir) contents = File.read(version_file) - contents.sub!(/(^\s+VERSION\s*=\s*)"#{Gem::Version::VERSION_PATTERN}"/, %(\\1"#{version}")) + contents.sub!(/(^\s+VERSION\s*=\s*).*$/, %(\\1"#{version}")) File.open(version_file, "w") {|f| f << contents } end @@ -272,29 +290,54 @@ module Spec File.open(gemspec_file, "w") {|f| f << contents } end + def replace_changelog(version, dir:) + changelog = File.expand_path("CHANGELOG.md", dir) + contents = File.readlines(changelog) + contents = [contents[0], contents[1], "## #{version} (2100-01-01)\n", *contents[3..-1]].join + File.open(changelog, "w") {|f| f << contents } + end + def git_root ruby_core? ? source_root : source_root.parent end def rake_path - Dir["#{base_system_gems}/#{Bundler.ruby_scope}/**/rake*.gem"].first + find_base_path("rake") + end + + def rake_version + File.basename(rake_path).delete_prefix("rake-").delete_suffix(".gem") end def sinatra_dependency_paths deps = %w[ mustermann rack + rack-protection + rack-session tilt sinatra - ruby2_keywords base64 logger + compact_index ] - Dir[base_system_gem_path.join("gems/{#{deps.join(",")}}-*/lib")].map(&:to_s) + path = if deps.all? {|dep| !Dir[scoped_base_system_gem_path.join("gems/#{dep}-*")].empty? } + scoped_base_system_gem_path + elsif ruby_core? && deps.all? {|dep| !Dir[source_root.join(".bundle/gems/#{dep}-*")].empty? } + source_root.join(".bundle") + else + scoped_base_system_gem_path + end + + Dir[path.join("gems/{#{deps.join(",")}}-*/lib")].map(&:to_s) end private + def find_base_path(name) + Dir["#{scoped_base_system_gem_path}/**/#{name}-*.gem"].first + end + def git_ls_files(glob) skip "Not running on a git context, since running tests from a tarball" if ruby_core_tarball? @@ -302,7 +345,7 @@ module Spec end def tracked_files_glob - ruby_core? ? "libexec/bundle* lib/bundler lib/bundler.rb spec/bundler man/bundle*" : "lib exe spec CHANGELOG.md LICENSE.md README.md bundler.gemspec" + ruby_core? ? "libexec/bundle* lib/bundler lib/bundler.rb spec/bundler man/bundle*" : "lib exe CHANGELOG.md LICENSE.md README.md bundler.gemspec" end def lib_tracked_files_glob @@ -310,7 +353,7 @@ module Spec end def man_tracked_files_glob - ruby_core? ? "man/bundle* man/gemfile*" : "lib/bundler/man/bundle*.1 lib/bundler/man/gemfile*.5" + "lib/bundler/man/bundle*.1.ronn lib/bundler/man/gemfile*.5.ronn" end def ruby_core_tarball? diff --git a/spec/bundler/support/platforms.rb b/spec/bundler/support/platforms.rb index 526e1c09a9..56a0843005 100644 --- a/spec/bundler/support/platforms.rb +++ b/spec/bundler/support/platforms.rb @@ -2,64 +2,22 @@ module Spec module Platforms - include Bundler::GemHelpers - - def rb - Gem::Platform::RUBY - end - - def mac - Gem::Platform.new("x86-darwin-10") - end - - def x64_mac - Gem::Platform.new("x86_64-darwin-15") - end - - def java - Gem::Platform.new([nil, "java", nil]) - end - - def linux - Gem::Platform.new("x86_64-linux") - end - - def x86_mswin32 - Gem::Platform.new(["x86", "mswin32", nil]) - end - - def x64_mswin64 - Gem::Platform.new(["x64", "mswin64", nil]) - end - - def x86_mingw32 - Gem::Platform.new(["x86", "mingw32", nil]) - end - - def x64_mingw32 - Gem::Platform.new(["x64", "mingw32", nil]) - end - - def x64_mingw_ucrt - Gem::Platform.new(["x64", "mingw", "ucrt"]) - end - - def windows_platforms - [x86_mswin32, x64_mswin64, x86_mingw32, x64_mingw32, x64_mingw_ucrt] + def not_local + generic_local_platform == Gem::Platform::RUBY ? "java" : Gem::Platform::RUBY end - def all_platforms - [rb, java, linux, windows_platforms].flatten + def local_platform + Bundler.local_platform end - def not_local - all_platforms.find {|p| p != generic_local_platform } + def generic_local_platform + Gem::Platform.generic(local_platform) end def local_tag - if RUBY_PLATFORM == "java" + if Gem.java_platform? :jruby - elsif ["x64-mingw32", "x64-mingw-ucrt"].include?(RUBY_PLATFORM) + elsif Gem.win_platform? :windows else :ruby @@ -96,16 +54,22 @@ module Spec end def default_platform_list(*extra, defaults: default_locked_platforms) - defaults.concat(extra).uniq + defaults.concat(extra).map(&:to_s).uniq end def lockfile_platforms(*extra, defaults: default_locked_platforms) platforms = default_platform_list(*extra, defaults: defaults) - platforms.map(&:to_s).sort.join("\n ") + platforms.sort.join("\n ") end def default_locked_platforms - [local_platform, generic_local_platform] + [local_platform, generic_default_locked_platform].compact + end + + def generic_default_locked_platform + return unless Bundler::MatchPlatform.generic_local_platform_is_ruby? + + Gem::Platform::RUBY end end end diff --git a/spec/bundler/support/rubygems_ext.rb b/spec/bundler/support/rubygems_ext.rb index 82b2819858..812dc4deaa 100644 --- a/spec/bundler/support/rubygems_ext.rb +++ b/spec/bundler/support/rubygems_ext.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -abort "RubyGems only supports Ruby 3.1 or higher" if RUBY_VERSION < "3.1.0" +abort "RubyGems only supports Ruby 3.2 or higher" if RUBY_VERSION < "3.2.0" require_relative "path" @@ -10,10 +10,6 @@ module Spec module Rubygems extend self - def dev_setup - install_gems(dev_gemfile) - end - def gem_load(gem_name, bin_container) require_relative "switch_rubygems" @@ -32,6 +28,9 @@ module Spec end def test_setup + # Install test dependencies unless parallel-rspec is being used, since in that case they should be setup already + install_test_deps unless ENV["RSPEC_FORMATTER_OUTPUT_ID"] + setup_test_paths require "fileutils" @@ -44,48 +43,78 @@ module Spec # 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 + if Path.ruby_core? + if (tmpdir = ENV["TMPDIR"]) + tmpdir_real = begin + File.realpath(tmpdir) + rescue Errno::ENOENT, Errno::EACCES + tmpdir + end + ENV["TMPDIR"] = tmpdir_real if tmpdir_real != tmpdir + end + else + ENV["TMPDIR"] = Path.tmpdir.to_s + end require "rubygems/user_interaction" Gem::DefaultUserInteraction.ui = Gem::SilentUI.new end - def install_parallel_test_deps - Gem.clear_paths - - require "parallel" - require "fileutils" - - install_test_deps - - (2..Parallel.processor_count).each do |n| - source = Path.tmp_root("1") - destination = Path.tmp_root(n.to_s) - - FileUtils.rm_rf destination - FileUtils.cp_r source, destination - end - end - def setup_test_paths - Gem.clear_paths - ENV["BUNDLE_PATH"] = nil - ENV["GEM_HOME"] = ENV["GEM_PATH"] = Path.base_system_gem_path.to_s ENV["PATH"] = [Path.system_gem_path("bin"), ENV["PATH"]].join(File::PATH_SEPARATOR) - ENV["PATH"] = [Path.bindir, ENV["PATH"]].join(File::PATH_SEPARATOR) if Path.ruby_core? + ENV["PATH"] = [Path.exedir, ENV["PATH"]].join(File::PATH_SEPARATOR) if Path.ruby_core? end def install_test_deps - Gem.clear_paths + dev_bundle("install", gemfile: test_gemfile, path: Path.base_system_gem_path.to_s) + dev_bundle("install", gemfile: rubocop_gemfile, path: Path.rubocop_gem_path.to_s) + dev_bundle("install", gemfile: standard_gemfile, path: Path.standard_gem_path.to_s) - 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) + require_relative "helpers" + Helpers.install_dev_bundler - # For some reason, doing this here crashes on JRuby + Windows. So defer to - # when the test suite is running in that case. - Helpers.install_dev_bundler unless Gem.win_platform? && RUBY_ENGINE == "jruby" + install_vendored_compact_index + end + + # Vendor `rubygems/rubygems.org#lib/compact_index/` under `tmp/compact_index/` + # so the artifice can serve compact-index responses without a runtime gem + # dependency. Pinned to a reviewed commit; override with COMPACT_INDEX_REF + # to refresh against another ref (the existing vendor copy is discarded). + def install_vendored_compact_index + target_root = Path.tmp_root.join("compact_index") + require "fileutils" + FileUtils.mkdir_p(Path.tmp_root) + + files = %w[ + lib/compact_index.rb + lib/compact_index/dependency.rb + lib/compact_index/gem.rb + lib/compact_index/gem_version.rb + lib/compact_index/versions_file.rb + ] + + # Serialize installs so parallel test setups don't race on the same + # vendor tree, and only skip the download when every file is present so + # an interrupted run can't leave a partial copy behind. + File.open(Path.tmp_root.join("compact_index.lock"), File::CREAT | File::RDWR) do |lock| + lock.flock(File::LOCK_EX) + + FileUtils.rm_rf(target_root) if ENV["COMPACT_INDEX_REF"] + + next if files.all? {|path| File.exist?(target_root.join(path)) } + + require "open-uri" + ref = ENV["COMPACT_INDEX_REF"] || "7c68a7b39761c61a66f9299f85b889ec39afc02c" + files.each do |path| + url = "https://raw.githubusercontent.com/rubygems/rubygems.org/#{ref}/#{path}" + target = target_root.join(path) + FileUtils.mkdir_p(File.dirname(target)) + tmp = "#{target}.tmp" + File.write(tmp, URI.parse(url).open(&:read)) + File.rename(tmp, target) + end + end end def check_source_control_changes(success_message:, error_message:) @@ -108,6 +137,36 @@ module Spec end end + def dev_bundle(*args, gemfile: dev_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"] + ENV["BUNDLE_PATH"] = path + else + old_path__system = ENV["BUNDLE_PATH__SYSTEM"] + ENV["BUNDLE_PATH__SYSTEM"] = "true" + end + + require "shellwords" + # We don't use `Open3` here because it does not work on JRuby + Windows + output = `ruby #{Path.dev_binstub} #{args.shelljoin}` + raise output unless $?.success? + output + ensure + if path + ENV["BUNDLE_PATH"] = old_path + else + ENV["BUNDLE_PATH__SYSTEM"] = old_path__system + end + + ENV["BUNDLER_ORIG_BUNDLE_GEMFILE"] = old_orig_gemfile + ENV["BUNDLE_GEMFILE"] = old_gemfile + end + private def gem_load_and_activate(gem_name, bin_container) @@ -136,34 +195,6 @@ module Spec 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"] - ENV["BUNDLE_PATH"] = path - else - old_path__system = ENV["BUNDLE_PATH__SYSTEM"] - ENV["BUNDLE_PATH__SYSTEM"] = "true" - end - - # We don't use `Open3` here because it does not work on JRuby + Windows - output = `#{Gem.ruby} #{File.expand_path("support/bundle.rb", Path.spec_dir)} install` - raise output unless $?.success? - ensure - if path - ENV["BUNDLE_PATH"] = old_path - else - ENV["BUNDLE_PATH__SYSTEM"] = old_path__system - end - - ENV["BUNDLER_ORIG_BUNDLE_GEMFILE"] = old_orig_gemfile - ENV["BUNDLE_GEMFILE"] = old_gemfile - end - def test_gemfile Path.test_gemfile end diff --git a/spec/bundler/support/setup.rb b/spec/bundler/support/setup.rb new file mode 100644 index 0000000000..4ac2e5b472 --- /dev/null +++ b/spec/bundler/support/setup.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require_relative "switch_rubygems" + +require_relative "rubygems_ext" +Spec::Rubygems.install_test_deps + +require_relative "path" +$LOAD_PATH.unshift(File.expand_path("../../lib", __dir__)) if Spec::Path.ruby_core? diff --git a/spec/bundler/support/shards.rb b/spec/bundler/support/shards.rb new file mode 100644 index 0000000000..ce33896539 --- /dev/null +++ b/spec/bundler/support/shards.rb @@ -0,0 +1,200 @@ +# frozen_string_literal: true + +# This classifies test files into 4 shards by running `bin/rspec --profile 10000` +# to ensure balanced execution times. When adding new test files, it is recommended to +# re-aggregate and adjust the shards to keep them balanced. +# For now, please add new files to shard 'shard_d'. + +module Spec + module Shards + EXAMPLE_MAPPINGS = { + shard_a: [ + "spec/runtime/setup_spec.rb", + "spec/commands/install_spec.rb", + "spec/commands/add_spec.rb", + "spec/install/gems/compact_index_spec.rb", + "spec/commands/config_spec.rb", + "spec/commands/pristine_spec.rb", + "spec/install/gemfile/path_spec.rb", + "spec/update/git_spec.rb", + "spec/commands/open_spec.rb", + "spec/commands/remove_spec.rb", + "spec/commands/show_spec.rb", + "spec/plugins/source/example_spec.rb", + "spec/commands/console_spec.rb", + "spec/runtime/require_spec.rb", + "spec/runtime/env_helpers_spec.rb", + "spec/runtime/gem_tasks_spec.rb", + "spec/install/gemfile_spec.rb", + "spec/commands/fund_spec.rb", + "spec/commands/init_spec.rb", + "spec/bundler/ruby_dsl_spec.rb", + "spec/bundler/mirror_spec.rb", + "spec/bundler/source/git/git_proxy_spec.rb", + "spec/bundler/source_list_spec.rb", + "spec/bundler/plugin/installer_spec.rb", + "spec/bundler/errors_spec.rb", + "spec/bundler/friendly_errors_spec.rb", + "spec/resolver/platform_spec.rb", + "spec/bundler/fetcher/downloader_spec.rb", + "spec/update/force_spec.rb", + "spec/bundler/env_spec.rb", + "spec/install/gems/mirror_spec.rb", + "spec/install/failure_spec.rb", + "spec/bundler/yaml_serializer_spec.rb", + "spec/bundler/environment_preserver_spec.rb", + "spec/install/gemfile/install_if_spec.rb", + "spec/install/gems/gemfile_source_header_spec.rb", + "spec/bundler/fetcher/base_spec.rb", + "spec/bundler/rubygems_integration_spec.rb", + "spec/bundler/worker_spec.rb", + "spec/bundler/dependency_spec.rb", + "spec/bundler/ui_spec.rb", + "spec/bundler/plugin/source_list_spec.rb", + "spec/bundler/source/path_spec.rb", + ], + shard_b: [ + "spec/install/gemfile/git_spec.rb", + "spec/install/gems/standalone_spec.rb", + "spec/commands/lock_spec.rb", + "spec/cache/gems_spec.rb", + "spec/other/major_deprecation_spec.rb", + "spec/install/gems/dependency_api_spec.rb", + "spec/install/gemfile/gemspec_spec.rb", + "spec/plugins/install_spec.rb", + "spec/commands/binstubs_spec.rb", + "spec/install/gems/flex_spec.rb", + "spec/runtime/inline_spec.rb", + "spec/commands/post_bundle_message_spec.rb", + "spec/runtime/executable_spec.rb", + "spec/lock/git_spec.rb", + "spec/plugins/hook_spec.rb", + "spec/install/allow_offline_install_spec.rb", + "spec/install/gems/post_install_spec.rb", + "spec/install/gemfile/ruby_spec.rb", + "spec/install/security_policy_spec.rb", + "spec/install/yanked_spec.rb", + "spec/update/gemfile_spec.rb", + "spec/runtime/load_spec.rb", + "spec/plugins/command_spec.rb", + "spec/commands/version_spec.rb", + "spec/install/prereleases_spec.rb", + "spec/bundler/uri_credentials_filter_spec.rb", + "spec/bundler/plugin_spec.rb", + "spec/install/gems/mirror_probe_spec.rb", + "spec/plugins/list_spec.rb", + "spec/bundler/compact_index_client/parser_spec.rb", + "spec/bundler/gem_version_promoter_spec.rb", + "spec/other/cli_dispatch_spec.rb", + "spec/bundler/source/rubygems_spec.rb", + "spec/cache/platform_spec.rb", + "spec/update/gems/fund_spec.rb", + "spec/bundler/stub_specification_spec.rb", + "spec/bundler/retry_spec.rb", + "spec/bundler/installer/spec_installation_spec.rb", + "spec/bundler/spec_set_spec.rb", + "spec/quality_es_spec.rb", + "spec/bundler/index_spec.rb", + "spec/other/cli_man_pages_spec.rb", + ], + shard_c: [ + "spec/commands/newgem_spec.rb", + "spec/commands/exec_spec.rb", + "spec/commands/clean_spec.rb", + "spec/commands/platform_spec.rb", + "spec/cache/git_spec.rb", + "spec/install/gemfile/groups_spec.rb", + "spec/commands/cache_spec.rb", + "spec/commands/check_spec.rb", + "spec/commands/list_spec.rb", + "spec/install/path_spec.rb", + "spec/bundler/cli_spec.rb", + "spec/install/bundler_spec.rb", + "spec/install/git_spec.rb", + "spec/commands/doctor_spec.rb", + "spec/bundler/dsl_spec.rb", + "spec/install/gems/fund_spec.rb", + "spec/install/gems/env_spec.rb", + "spec/bundler/ruby_version_spec.rb", + "spec/bundler/definition_spec.rb", + "spec/install/gemfile/eval_gemfile_spec.rb", + "spec/plugins/source_spec.rb", + "spec/install/gems/dependency_api_fallback_spec.rb", + "spec/plugins/uninstall_spec.rb", + "spec/bundler/plugin/index_spec.rb", + "spec/bundler/bundler_spec.rb", + "spec/bundler/fetcher_spec.rb", + "spec/bundler/source/rubygems/remote_spec.rb", + "spec/bundler/lockfile_parser_spec.rb", + "spec/cache/cache_path_spec.rb", + "spec/bundler/source/git_spec.rb", + "spec/bundler/source_spec.rb", + "spec/commands/ssl_spec.rb", + "spec/bundler/fetcher/compact_index_spec.rb", + "spec/bundler/plugin/api_spec.rb", + "spec/bundler/endpoint_specification_spec.rb", + "spec/bundler/fetcher/index_spec.rb", + "spec/bundler/settings/validator_spec.rb", + "spec/bundler/build_metadata_spec.rb", + "spec/bundler/current_ruby_spec.rb", + "spec/bundler/installer/gem_installer_spec.rb", + "spec/bundler/installer/parallel_installer_spec.rb", + "spec/bundler/cli_common_spec.rb", + "spec/bundler/ci_detector_spec.rb", + ], + shard_d: [ + "spec/bundler/rubygems_ext_spec.rb", + "spec/bundler/resolver/cooldown_spec.rb", + "spec/install/cooldown_spec.rb", + "spec/commands/outdated_spec.rb", + "spec/commands/update_spec.rb", + "spec/lock/lockfile_spec.rb", + "spec/install/deploy_spec.rb", + "spec/install/gemfile/sources_spec.rb", + "spec/runtime/self_management_spec.rb", + "spec/install/gemfile/specific_platform_spec.rb", + "spec/commands/info_spec.rb", + "spec/install/gems/resolving_spec.rb", + "spec/install/gemfile/platform_spec.rb", + "spec/bundler/gem_helper_spec.rb", + "spec/install/global_cache_spec.rb", + "spec/runtime/platform_spec.rb", + "spec/update/gems/post_install_spec.rb", + "spec/install/gems/native_extensions_spec.rb", + "spec/install/force_spec.rb", + "spec/cache/path_spec.rb", + "spec/install/gemspecs_spec.rb", + "spec/commands/help_spec.rb", + "spec/bundler/shared_helpers_spec.rb", + "spec/bundler/settings_spec.rb", + "spec/resolver/basic_spec.rb", + "spec/install/gemfile/force_ruby_platform_spec.rb", + "spec/commands/licenses_spec.rb", + "spec/install/gemfile/lockfile_spec.rb", + "spec/bundler/fetcher/dependency_spec.rb", + "spec/quality_spec.rb", + "spec/bundler/remote_specification_spec.rb", + "spec/install/process_lock_spec.rb", + "spec/install/binstubs_spec.rb", + "spec/bundler/compact_index_client/updater_spec.rb", + "spec/bundler/ui/shell_spec.rb", + "spec/other/ext_spec.rb", + "spec/commands/issue_spec.rb", + "spec/update/path_spec.rb", + "spec/bundler/plugin/api/source_spec.rb", + "spec/install/gems/win32_spec.rb", + "spec/bundler/plugin/dsl_spec.rb", + "spec/runtime/requiring_spec.rb", + "spec/bundler/plugin/events_spec.rb", + "spec/bundler/resolver/candidate_spec.rb", + "spec/bundler/digest_spec.rb", + "spec/bundler/fetcher/gem_remote_fetcher_spec.rb", + "spec/bundler/uri_normalizer_spec.rb", + "spec/install/gems/no_build_extension_spec.rb", + "spec/install/gems/no_install_plugin_spec.rb", + "spec/bundler/override_spec.rb", + "spec/install/gemfile/override_spec.rb", + ], + }.freeze + end +end diff --git a/spec/bundler/support/silent_logger.rb b/spec/bundler/support/silent_logger.rb deleted file mode 100644 index 4b270330fd..0000000000 --- a/spec/bundler/support/silent_logger.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -require "webrick" -module Spec - class SilentLogger < WEBrick::BasicLog - def initialize(log_file = nil, level = nil) - super(log_file, level || FATAL) - end - end -end diff --git a/spec/bundler/support/subprocess.rb b/spec/bundler/support/subprocess.rb index a4842166b9..91db80da48 100644 --- a/spec/bundler/support/subprocess.rb +++ b/spec/bundler/support/subprocess.rb @@ -22,6 +22,10 @@ module Spec last_command.stderr end + def stdboth + last_command.stdboth + end + def exitstatus last_command.exitstatus end @@ -34,11 +38,14 @@ module Spec dir = options[:dir] env = options[:env] || {} - command_execution = CommandExecution.new(cmd.to_s, working_directory: dir, timeout: options[:timeout] || 60) + command_execution = CommandExecution.new(cmd.to_s, timeout: options[:timeout] || 60) + + open3_opts = {} + open3_opts[:chdir] = dir if dir require "open3" require "shellwords" - Open3.popen3(env, *cmd.shellsplit, chdir: dir) do |stdin, stdout, stderr, wait_thr| + Open3.popen3(env, *cmd.shellsplit, **open3_opts) do |stdin, stdout, stderr, wait_thr| yield stdin, stdout, wait_thr if block_given? stdin.close diff --git a/spec/bundler/support/switch_rubygems.rb b/spec/bundler/support/switch_rubygems.rb index a138d22333..640b9f83b7 100644 --- a/spec/bundler/support/switch_rubygems.rb +++ b/spec/bundler/support/switch_rubygems.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true require_relative "rubygems_version_manager" +ENV["RGV"] ||= "." RubygemsVersionManager.new(ENV["RGV"]).switch diff --git a/spec/bundler/support/the_bundle.rb b/spec/bundler/support/the_bundle.rb index f252a4515b..452abd7d41 100644 --- a/spec/bundler/support/the_bundle.rb +++ b/spec/bundler/support/the_bundle.rb @@ -8,10 +8,8 @@ module Spec attr_accessor :bundle_dir - def initialize(opts = {}) - opts = opts.dup - @bundle_dir = Pathname.new(opts.delete(:bundle_dir) { bundled_app }) - raise "Too many options! #{opts}" unless opts.empty? + def initialize + @bundle_dir = Pathname.new(bundled_app) end def to_s @@ -28,8 +26,16 @@ module Spec end def locked_gems - raise "Cannot read lockfile if it doesn't exist" unless locked? + raise ArgumentError, "Cannot read lockfile if it doesn't exist" unless locked? Bundler::LockfileParser.new(lockfile.read) end + + def locked_specs + locked_gems.specs.map(&:full_name) + end + + def locked_platforms + locked_gems.platforms.map(&:to_s) + end end end diff --git a/spec/bundler/update/force_spec.rb b/spec/bundler/update/force_spec.rb new file mode 100644 index 0000000000..325f58088a --- /dev/null +++ b/spec/bundler/update/force_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +RSpec.describe "bundle update" do + before :each do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + end + + it "re-installs installed gems with --force" do + myrack_lib = default_bundle_path("gems/myrack-1.0.0/lib/myrack.rb") + myrack_lib.open("w") {|f| f.write("blah blah blah") } + bundle :update, force: true + + expect(out).to include "Installing myrack 1.0.0" + expect(myrack_lib.open(&:read)).to eq("MYRACK = '1.0.0'\n") + expect(the_bundle).to include_gems "myrack 1.0.0" + end + + it "re-installs installed gems with --redownload" do + myrack_lib = default_bundle_path("gems/myrack-1.0.0/lib/myrack.rb") + myrack_lib.open("w") {|f| f.write("blah blah blah") } + bundle :update, redownload: true + + expect(out).to include "Installing myrack 1.0.0" + expect(myrack_lib.open(&:read)).to eq("MYRACK = '1.0.0'\n") + expect(the_bundle).to include_gems "myrack 1.0.0" + end +end diff --git a/spec/bundler/update/gemfile_spec.rb b/spec/bundler/update/gemfile_spec.rb index cb94799061..f8849640b6 100644 --- a/spec/bundler/update/gemfile_spec.rb +++ b/spec/bundler/update/gemfile_spec.rb @@ -25,7 +25,7 @@ RSpec.describe "bundle update" do gem 'myrack' G - bundle "config set --local gemfile #{bundled_app("NotGemfile")}" + bundle_config "gemfile #{bundled_app("NotGemfile")}" bundle :install end diff --git a/spec/bundler/update/git_spec.rb b/spec/bundler/update/git_spec.rb index 64124e0920..526e988ab7 100644 --- a/spec/bundler/update/git_spec.rb +++ b/spec/bundler/update/git_spec.rb @@ -87,7 +87,7 @@ RSpec.describe "bundle update" do gem "foo", "1.0", :git => "#{lib_path("foo_one")}" G - FileUtils.rm_rf lib_path("foo_one") + FileUtils.rm_r lib_path("foo_one") install_gemfile <<-G source "https://gem.repo1" @@ -199,7 +199,7 @@ RSpec.describe "bundle update" do gem "foo", :git => "#{lib_path("foo-1.0")}" G - lib_path("foo-1.0").join(".git").rmtree + FileUtils.rm_rf lib_path("foo-1.0").join(".git") bundle :update, all: true, raise_on_error: false expect(err).to include(lib_path("foo-1.0").to_s). @@ -334,7 +334,7 @@ RSpec.describe "bundle update" do myrack #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end end diff --git a/spec/bundler/update/redownload_spec.rb b/spec/bundler/update/redownload_spec.rb deleted file mode 100644 index 66437fb938..0000000000 --- a/spec/bundler/update/redownload_spec.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe "bundle update" do - before :each do - install_gemfile <<-G - source "https://gem.repo1" - gem "myrack" - G - end - - describe "with --force" do - it "shows a deprecation when single flag passed", bundler: 2 do - bundle "update myrack --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 - bundle "update myrack --no-color --force" - expect(err).to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" - end - end - - describe "with --redownload" do - it "does not show a deprecation when single flag passed" do - bundle "update myrack --redownload" - expect(err).not_to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" - end - - it "does not show a deprecation when single multiple flags passed" do - bundle "update myrack --no-color --redownload" - expect(err).not_to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" - end - - it "re-installs installed gems" do - myrack_lib = default_bundle_path("gems/myrack-1.0.0/lib/myrack.rb") - myrack_lib.open("w") {|f| f.write("blah blah blah") } - bundle :update, redownload: true - - expect(out).to include "Installing myrack 1.0.0" - expect(myrack_lib.open(&:read)).to eq("MYRACK = '1.0.0'\n") - expect(the_bundle).to include_gems "myrack 1.0.0" - end - end -end |
