diff options
Diffstat (limited to 'spec/bundler')
220 files changed, 20586 insertions, 11734 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 6a2e435e54..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"]) @@ -164,53 +164,36 @@ RSpec.describe Bundler do end describe "#which" do - let(:executable) { "executable" } + it "can detect relative path" do + script_path = bundled_app("tmp/test_command") + create_file(script_path, "#!/usr/bin/env ruby\n") - let(:path) do - if Gem.win_platform? - %w[C:/a C:/b C:/c C:/../d C:/e] - else - %w[/a /b c ../d /e] + result = Dir.chdir script_path.dirname.dirname do + Bundler.which("test_command") end - end - - let(:expected) { "executable" } - - before do - ENV["PATH"] = path.join(File::PATH_SEPARATOR) + expect(result).to eq(nil) - allow(File).to receive(:file?).and_return(false) - allow(File).to receive(:executable?).and_return(false) - if expected - expect(File).to receive(:file?).with(expected).and_return(true) - expect(File).to receive(:executable?).with(expected).and_return(true) + result = Dir.chdir script_path.dirname do + Bundler.which("test_command") end - end - - subject { described_class.which(executable) } - shared_examples_for "it returns the correct executable" do - it "returns the expected file" do - expect(subject).to eq(expected) - end + expect(result).to eq("test_command") unless Gem.win_platform? + expect(result).to eq("test_command.bat") if Gem.win_platform? end - it_behaves_like "it returns the correct executable" + it "can detect absolute path" do + create_file("test_command", "#!/usr/bin/env ruby\n") - context "when the executable in inside a quoted path" do - let(:expected) do - if Gem.win_platform? - "C:/e/executable" - else - "/e/executable" - end - end - it_behaves_like "it returns the correct executable" + ENV["PATH"] = bundled_app("test_command").parent.to_s + + result = Bundler.which("test_command") + expect(result).to eq(bundled_app("test_command").to_s) unless Gem.win_platform? + expect(result).to eq(bundled_app("test_command.bat").to_s) if Gem.win_platform? end - context "when the executable is not found" do - let(:expected) { nil } - it_behaves_like "it returns the correct executable" + it "returns nil when not found" do + result = Bundler.which("test_command") + expect(result).to eq(nil) end end @@ -227,14 +210,14 @@ RSpec.describe Bundler do describe "#mkdir_p" do it "creates a folder at the given path" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G allow(Bundler).to receive(:root).and_return(bundled_app) - Bundler.mkdir_p(bundled_app.join("foo", "bar")) - expect(bundled_app.join("foo", "bar")).to exist + Bundler.mkdir_p(bundled_app("foo", "bar")) + expect(bundled_app("foo", "bar")).to exist end end @@ -267,6 +250,7 @@ RSpec.describe Bundler do it "should issue a warning and return a temporary user home" do allow(Bundler.rubygems).to receive(:user_home).and_return(path) allow(File).to receive(:directory?).with(path).and_return true + allow(File).to receive(:writable?).and_call_original allow(File).to receive(:writable?).with(path).and_return false allow(File).to receive(:directory?).with(dotbundle).and_return false allow(Bundler).to receive(:tmp).and_return(Pathname.new("/tmp/trulyrandom")) 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 c71fc8e9e7..56caf9937e 100644 --- a/spec/bundler/bundler/cli_spec.rb +++ b/spec/bundler/bundler/cli_spec.rb @@ -87,45 +87,69 @@ 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 it "ignores it" do gemfile bundled_app_gemfile, <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' G bundle :install, env: { "BUNDLE_GEMFILE" => "" } - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end end context "with --verbose" do + before do + gemfile "source 'https://gem.repo1'" + end + it "prints the running command" do - gemfile "source \"#{file_uri_for(gem_repo1)}\"" bundle "info bundler", verbose: true expect(out).to start_with("Running `bundle info bundler --verbose` with bundler #{Bundler::VERSION}") - end - it "doesn't print defaults" do - install_gemfile "source \"#{file_uri_for(gem_repo1)}\"", verbose: true + 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 \"#{file_uri_for(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 @@ -138,8 +162,8 @@ RSpec.describe "bundle executable" do before do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", '0.9.1' + source "https://gem.repo1" + gem "myrack", '0.9.1' G end @@ -149,8 +173,8 @@ RSpec.describe "bundle executable" do it "prints a message when there are outdated gems" do run_command - expect(out).to include("Gem Current Latest Requested Groups") - expect(out).to include("rack 0.9.1 1.0.0 = 0.9.1 default") + expect(out).to include("Gem Current Latest Requested Groups") + expect(out).to include("myrack 0.9.1 1.0.0 = 0.9.1 default") end end @@ -160,7 +184,7 @@ RSpec.describe "bundle executable" do it "prints a message when there are outdated gems" do run_command - expect(out).to include("rack (newest 1.0.0, installed 0.9.1, requested = 0.9.1)") + expect(out).to include("myrack (newest 1.0.0, installed 0.9.1, requested = 0.9.1)") end end @@ -170,7 +194,7 @@ RSpec.describe "bundle executable" do it "prints a simplified message when there are outdated gems" do run_command - expect(out).to include("rack (newest 1.0.0, installed 0.9.1, requested = 0.9.1)") + expect(out).to include("myrack (newest 1.0.0, installed 0.9.1, requested = 0.9.1)") end 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 new file mode 100644 index 0000000000..6aa867f058 --- /dev/null +++ b/spec/bundler/bundler/compact_index_client/parser_spec.rb @@ -0,0 +1,249 @@ +# frozen_string_literal: true + +require "bundler/compact_index_client" +require "bundler/compact_index_client/parser" + +TestCompactIndexClient = Struct.new(:names, :versions, :info_data) do + # Requiring the checksum to match the input data helps ensure + # that we are parsing the correct checksum from the versions file + def info(name, checksum) + info_data.dig(name, checksum) + end + + def set_info_data(name, value) + info_data[name] = value + end +end + +RSpec.describe Bundler::CompactIndexClient::Parser do + subject(:parser) { described_class.new(compact_index) } + + let(:compact_index) { TestCompactIndexClient.new(names, versions, info_data) } + let(:names) { "a\nb\nc\n" } + let(:versions) { <<~VERSIONS.dup } + 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 bbb222 + c 3.0.0,3.0.3,3.3.3 ccc333 + c -3.0.3 ccc333yanked + VERSIONS + let(:info_data) do + { + "a" => { "aaa111" => a_info }, + "b" => { "bbb222" => b_info }, + "c" => { "ccc333yanked" => c_info }, + } + end + let(:a_info) { <<~INFO.dup } + --- + 1.0.0 |checksum:aaa1,ruby:>= 3.0.0,rubygems:>= 3.2.3 + 1.0.1 |checksum:aaa2,ruby:>= 3.0.0,rubygems:>= 3.2.3 + 1.1.0 |checksum:aaa3,ruby:>= 3.0.0,rubygems:>= 3.2.3 + INFO + let(:b_info) { <<~INFO } + 2.0.0 a:~> 1.0&<= 3.0|checksum:bbb1 + 2.0.0-java a:~> 1.0&<= 3.0|checksum:bbb2 + 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,created_at:2026-05-12T10:00:00Z + INFO + + describe "#available?" do + it "returns true versions are available" do + expect(parser).to be_available + end + + it "returns true when versions has only one gem" do + compact_index.versions = +"a 1.0.0 aaa1\n" + expect(parser).to be_available + end + + it "returns true when versions has a gem and a header" do + compact_index.versions = +"---\na 1.0.0 aaa1\n" + expect(parser).to be_available + end + + it "returns true when versions has a gem and a header with header data" do + compact_index.versions = +"created_at: 2024-05-01T00:00:04Z\n---\na 1.0.0 aaa1\n" + expect(parser).to be_available + end + + it "returns false when versions has only the header" do + compact_index.versions = +"---\n" + expect(parser).not_to be_available + end + + it "returns false when versions has only the header with header data" do + compact_index.versions = +"created_at: 2024-05-01T00:00:04Z\n---\n" + expect(parser).not_to be_available + end + + it "returns false when versions index is not available" do + compact_index.versions = nil + expect(parser).not_to be_available + end + + it "returns false when versions is empty" do + compact_index.versions = +"" + expect(parser).not_to be_available + end + end + + describe "#names" do + it "returns the names" do + expect(parser.names).to eq(%w[a b c]) + end + + it "returns an empty array when names is empty" do + compact_index.names = "" + expect(parser.names).to eq([]) + end + + it "returns an empty array when names is not readable" do + compact_index.names = nil + expect(parser.names).to eq([]) + end + end + + describe "#versions" do + it "returns the versions" do + expect(parser.versions).to eq( + "a" => [ + ["a", "1.0.0"], + ["a", "1.0.1"], + ["a", "1.1.0"], + ], + "b" => [ + ["b", "2.0.0"], + ["b", "2.0.0", "java"], + ], + "c" => [ + ["c", "3.0.0"], + ["c", "3.3.3"], + ], + ) + end + + it "returns an empty hash when versions is empty" do + compact_index.versions = "" + expect(parser.versions).to eq({}) + end + + it "returns an empty hash when versions is not readable" do + compact_index.versions = nil + expect(parser.versions).to eq({}) + end + end + + describe "#info" do + let(:a_result) do + [ + [ + "a", + "1.0.0", + nil, + [], + [["checksum", ["aaa1"]], ["ruby", [">= 3.0.0"]], ["rubygems", [">= 3.2.3"]]], + ], + [ + "a", + "1.0.1", + nil, + [], + [["checksum", ["aaa2"]], ["ruby", [">= 3.0.0"]], ["rubygems", [">= 3.2.3"]]], + ], + [ + "a", + "1.1.0", + nil, + [], + [["checksum", ["aaa3"]], ["ruby", [">= 3.0.0"]], ["rubygems", [">= 3.2.3"]]], + ], + ] + end + let(:b_result) do + [ + [ + "b", + "2.0.0", + nil, + [["a", ["~> 1.0", "<= 3.0"]]], + [["checksum", ["bbb1"]]], + ], + [ + "b", + "2.0.0", + "java", + [["a", ["~> 1.0", "<= 3.0"]]], + [["checksum", ["bbb2"]]], + ], + ] + end + let(:c_result) do + [ + [ + "c", + "3.0.0", + nil, + [["a", ["= 1.0.0"]], ["b", ["~> 2.0"]]], + [["checksum", ["ccc1"]], ["ruby", [">= 2.7.0"]], ["rubygems", [">= 3.0.0"]]], + ], + [ + "c", + "3.3.3", + nil, + [["a", [">= 1.1.0"]], ["b", ["~> 2.0"]]], + [["checksum", ["ccc3"]], ["ruby", [">= 3.0.0"]], ["rubygems", [">= 3.2.3"]], ["created_at", ["2026-05-12T10:00:00Z"]]], + ], + ] + end + + it "returns the info for example gem 'a' which has no deps" do + expect(parser.info("a")).to eq(a_result) + end + + it "returns the info for example gem 'b' which has platform and compound deps" do + expect(parser.info("b")).to eq(b_result) + end + + it "returns the info for example gem 'c' which has deps and yanked version (requires use of correct info checksum)" do + expect(parser.info("c")).to eq(c_result) + end + + it "returns an empty array when the info is empty" do + compact_index.set_info_data("a", {}) + expect(parser.info("a")).to eq([]) + end + + it "returns an empty array when the info is not readable" do + expect(parser.info("d")).to eq([]) + end + + it "handles empty lines in the versions file (Artifactory bug that they have yet to fix)" 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 bbb222 + + c 3.0.0,3.0.3,3.3.3 ccc333 + c -3.0.3 ccc333yanked + 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 6eed88ca9e..fd63a652a4 100644 --- a/spec/bundler/bundler/compact_index_client/updater_spec.rb +++ b/spec/bundler/bundler/compact_index_client/updater_spec.rb @@ -115,27 +115,28 @@ RSpec.describe Bundler::CompactIndexClient::Updater do expect(local_path.read).to eq(full_body) expect(etag_path.read).to eq("NewEtag") end - end - context "without an etag file" do - let(:headers) do - { - "Range" => "bytes=2-", - # This MD5 feature should be deleted after sufficient time has passed since release. - # From then on, requests that still don't have a saved etag will be made without this header. - "If-None-Match" => %("#{Digest::MD5.hexdigest(local_body)}"), - } - end - - it "saves only the etag_path if generated etag matches" do + 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) - allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { false } - allow(response).to receive(:is_a?).with(Gem::Net::HTTPNotModified) { true } + + 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("abc") - expect(%("#{etag_path.read}")).to eq(headers["If-None-Match"]) + expect(local_path.read).to eq(full_body) + expect(etag_path.read).to eq("NewEtag") + end + end + + context "without an etag file" do + let(:headers) do + { "Range" => "bytes=2-" } end it "appends the 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 28c04e0860..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 } @@ -15,7 +33,7 @@ RSpec.describe Bundler::Definition do it "raises an PermissionError with explanation" do allow(File).to receive(:open).and_call_original expect(File).to receive(:open).with(bundled_app_lock, "wb"). - and_raise(Errno::EACCES) + and_raise(Errno::EACCES.new(bundled_app_lock.to_s)) expect { subject.lock }. to raise_error(Bundler::PermissionError, /Gemfile\.lock/) end @@ -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 @@ -45,17 +63,17 @@ RSpec.describe Bundler::Definition do build_lib "foo", "1.0", path: lib_path("foo") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :path => "#{lib_path("foo")}" G build_lib "foo", "1.0", path: lib_path("foo") do |s| - s.add_dependency "rack", "1.0" + s.add_dependency "myrack", "1.0" end - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.no_checksum "foo", "1.0" - c.checksum gem_repo1, "rack", "1.0.0" + c.checksum gem_repo1, "myrack", "1.0.0" end bundle :install, env: { "DEBUG" => "1" } @@ -66,12 +84,12 @@ RSpec.describe Bundler::Definition do remote: #{lib_path("foo")} specs: foo (1.0) - rack (= 1.0) + myrack (= 1.0) GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{lockfile_platforms} @@ -80,7 +98,7 @@ RSpec.describe Bundler::Definition do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -91,7 +109,7 @@ RSpec.describe Bundler::Definition do end gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "ffi" G @@ -104,17 +122,17 @@ RSpec.describe Bundler::Definition do it "for a path gem with deps and no changes" do build_lib "foo", "1.0", path: lib_path("foo") do |s| - s.add_dependency "rack", "1.0" + s.add_dependency "myrack", "1.0" s.add_development_dependency "net-ssh", "1.0" end - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.no_checksum "foo", "1.0" - c.checksum gem_repo1, "rack", "1.0.0" + c.checksum gem_repo1, "myrack", "1.0.0" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :path => "#{lib_path("foo")}" G @@ -123,12 +141,12 @@ RSpec.describe Bundler::Definition do remote: #{lib_path("foo")} specs: foo (1.0) - rack (= 1.0) + myrack (= 1.0) GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{lockfile_platforms} @@ -137,7 +155,7 @@ RSpec.describe Bundler::Definition do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G expect(lockfile).to eq(expected_lockfile) @@ -149,22 +167,22 @@ RSpec.describe Bundler::Definition do end it "for a locked gem for another platform" do - checksums = checksums_section_when_existing do |c| - c.no_checksum "only_java", "1.1", "java" - end - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "only_java", platform: :jruby G + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo1, "only_java", "1.1", "java" + end + bundle "lock --add-platform java" bundle :check, env: { "DEBUG" => "1" } expect(out).to match(/using resolution from the lockfile/) expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: only_java (1.1-java) @@ -175,17 +193,17 @@ RSpec.describe Bundler::Definition do only_java #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end it "for a rubygems gem" do - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum gem_repo1, "foo", "1.0" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo" G @@ -194,7 +212,7 @@ RSpec.describe Bundler::Definition do expect(out).to match(/using resolution from the lockfile/) expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: foo (1.0) @@ -205,7 +223,7 @@ RSpec.describe Bundler::Definition do foo #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end end @@ -215,13 +233,13 @@ RSpec.describe Bundler::Definition do context "eager unlock" do let(:source_list) do Bundler::SourceList.new.tap do |source_list| - source_list.add_global_rubygems_remote(file_uri_for(gem_repo4)) + source_list.add_global_rubygems_remote("https://gem.repo4") end end before do gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem 'isolated_owner' gem 'shared_owner_a' @@ -230,7 +248,7 @@ RSpec.describe Bundler::Definition do lockfile <<-L GEM - remote: #{file_uri_for(gem_repo4)} + remote: https://gem.repo4 specs: isolated_dep (2.0.1) isolated_owner (1.0.1) @@ -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 a953372742..f930459571 100644 --- a/spec/bundler/bundler/dependency_spec.rb +++ b/spec/bundler/bundler/dependency_spec.rb @@ -35,133 +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, - mri: Gem::Platform::RUBY, - mri_18: Gem::Platform::RUBY, - mri_19: Gem::Platform::RUBY, - mri_20: Gem::Platform::RUBY, - mri_21: Gem::Platform::RUBY, - mri_22: Gem::Platform::RUBY, - mri_23: Gem::Platform::RUBY, - mri_24: Gem::Platform::RUBY, - mri_25: Gem::Platform::RUBY, - mri_26: Gem::Platform::RUBY, - mri_27: Gem::Platform::RUBY, - mri_30: Gem::Platform::RUBY, - mri_31: Gem::Platform::RUBY, - mri_32: Gem::Platform::RUBY, - mri_33: Gem::Platform::RUBY, - mri_34: Gem::Platform::RUBY, - rbx: Gem::Platform::RUBY, - truffleruby: Gem::Platform::RUBY, - jruby: Gem::Platform::JAVA, - jruby_18: Gem::Platform::JAVA, - jruby_19: Gem::Platform::JAVA, - windows: Gem::Platform::WINDOWS, - windows_18: Gem::Platform::WINDOWS, - windows_19: Gem::Platform::WINDOWS, - windows_20: Gem::Platform::WINDOWS, - windows_21: Gem::Platform::WINDOWS, - windows_22: Gem::Platform::WINDOWS, - windows_23: Gem::Platform::WINDOWS, - windows_24: Gem::Platform::WINDOWS, - windows_25: Gem::Platform::WINDOWS, - windows_26: Gem::Platform::WINDOWS, - windows_27: Gem::Platform::WINDOWS, - windows_30: Gem::Platform::WINDOWS, - windows_31: Gem::Platform::WINDOWS, - windows_32: Gem::Platform::WINDOWS, - windows_33: Gem::Platform::WINDOWS, - windows_34: Gem::Platform::WINDOWS } - end - - let(:deprecated) do - { mswin: Gem::Platform::MSWIN, - mswin_18: Gem::Platform::MSWIN, - mswin_19: Gem::Platform::MSWIN, - mswin_20: Gem::Platform::MSWIN, - mswin_21: Gem::Platform::MSWIN, - mswin_22: Gem::Platform::MSWIN, - mswin_23: Gem::Platform::MSWIN, - mswin_24: Gem::Platform::MSWIN, - mswin_25: Gem::Platform::MSWIN, - mswin_26: Gem::Platform::MSWIN, - mswin_27: Gem::Platform::MSWIN, - mswin_30: Gem::Platform::MSWIN, - mswin_31: Gem::Platform::MSWIN, - mswin_32: Gem::Platform::MSWIN, - mswin_33: Gem::Platform::MSWIN, - mswin_34: Gem::Platform::MSWIN, - mswin64: Gem::Platform::MSWIN64, - mswin64_19: Gem::Platform::MSWIN64, - mswin64_20: Gem::Platform::MSWIN64, - mswin64_21: Gem::Platform::MSWIN64, - mswin64_22: Gem::Platform::MSWIN64, - mswin64_23: Gem::Platform::MSWIN64, - mswin64_24: Gem::Platform::MSWIN64, - mswin64_25: Gem::Platform::MSWIN64, - mswin64_26: Gem::Platform::MSWIN64, - mswin64_27: Gem::Platform::MSWIN64, - mswin64_30: Gem::Platform::MSWIN64, - mswin64_31: Gem::Platform::MSWIN64, - mswin64_32: Gem::Platform::MSWIN64, - mswin64_33: Gem::Platform::MSWIN64, - mswin64_34: Gem::Platform::MSWIN64, - mingw: Gem::Platform::MINGW, - mingw_18: Gem::Platform::MINGW, - mingw_19: Gem::Platform::MINGW, - mingw_20: Gem::Platform::MINGW, - mingw_21: Gem::Platform::MINGW, - mingw_22: Gem::Platform::MINGW, - mingw_23: Gem::Platform::MINGW, - mingw_24: Gem::Platform::MINGW, - mingw_25: Gem::Platform::MINGW, - mingw_26: Gem::Platform::MINGW, - mingw_27: Gem::Platform::MINGW, - mingw_30: Gem::Platform::MINGW, - mingw_31: Gem::Platform::MINGW, - mingw_32: Gem::Platform::MINGW, - mingw_33: Gem::Platform::MINGW, - mingw_34: Gem::Platform::MINGW, - x64_mingw: Gem::Platform::X64_MINGW, - x64_mingw_20: Gem::Platform::X64_MINGW, - x64_mingw_21: Gem::Platform::X64_MINGW, - x64_mingw_22: Gem::Platform::X64_MINGW, - x64_mingw_23: Gem::Platform::X64_MINGW, - x64_mingw_24: Gem::Platform::X64_MINGW, - x64_mingw_25: Gem::Platform::X64_MINGW, - x64_mingw_26: Gem::Platform::X64_MINGW, - x64_mingw_27: Gem::Platform::X64_MINGW, - x64_mingw_30: Gem::Platform::X64_MINGW, - x64_mingw_31: Gem::Platform::X64_MINGW, - x64_mingw_32: Gem::Platform::X64_MINGW, - x64_mingw_33: Gem::Platform::X64_MINGW, - x64_mingw_34: Gem::Platform::X64_MINGW } - end - # rubocop:enable Naming/VariableNumber + 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/digest_spec.rb b/spec/bundler/bundler/digest_spec.rb index fd7b0c968e..f876827964 100644 --- a/spec/bundler/bundler/digest_spec.rb +++ b/spec/bundler/bundler/digest_spec.rb @@ -11,7 +11,7 @@ RSpec.describe Bundler::Digest do it "is compatible with stdlib" do random_strings = ["foo", "skfjsdlkfjsdf", "3924m", "ldskfj"] - # https://datatracker.ietf.org/doc/html/rfc3174#section-7.3 + # https://www.rfc-editor.org/rfc/rfc3174#section-7.3 rfc3174_test_cases = ["abc", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "a", "01234567" * 8] (random_strings + rfc3174_test_cases).each do |payload| diff --git a/spec/bundler/bundler/dsl_spec.rb b/spec/bundler/bundler/dsl_spec.rb index 3c3b6c26c3..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 '\}'). Bundler cannot continue./) + 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" @@ -322,7 +338,7 @@ RSpec.describe Bundler::Dsl do it "will raise a Bundler::GemfileError" do gemfile "gem 'foo', :path => /unquoted/string/syntax/error" expect { Bundler::Dsl.evaluate(bundled_app_gemfile, nil, true) }. - to raise_error(Bundler::GemfileError, /There was an error parsing `Gemfile`:( compile error -)? unknown regexp options - trg.+ Bundler cannot continue./) + to raise_error(Bundler::GemfileError, /There was an error parsing `Gemfile`:( compile error -)?.+?unknown regexp options - trg.+ Bundler cannot continue./m) end end @@ -351,20 +367,192 @@ RSpec.describe Bundler::Dsl do end end - describe "#check_primary_source_safety" do - context "when a global source is not defined implicitly" do - it "will raise a major deprecation warning" do - not_a_global_source = double("not-a-global-source", no_remotes?: true) - allow(Bundler::Source::Rubygems).to receive(:new).and_return(not_a_global_source) + describe "#source with cooldown" do + before do + allow(@rubygems).to receive(:add_remote) + end - warning = "This Gemfile does not include an explicit global source. " \ - "Not using an explicit global source may result in a different lockfile being generated depending on " \ - "the gems you have installed locally before bundler is run. " \ - "Instead, define a global source in your Gemfile like this: source \"https://rubygems.org\"." - expect(Bundler::SharedHelpers).to receive(:major_deprecation).with(2, warning) + it "accepts a non-negative integer" do + expect do + subject.source("https://rubygems.org", cooldown: 7) + end.not_to raise_error + end - subject.check_primary_source_safety - 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 e7e10730cf..4fbd59d48f 100644 --- a/spec/bundler/bundler/endpoint_specification_spec.rb +++ b/spec/bundler/bundler/endpoint_specification_spec.rb @@ -42,10 +42,50 @@ RSpec.describe Bundler::EndpointSpecification do expect { subject }.to raise_error( Bundler::GemspecError, a_string_including("There was an error parsing the metadata for the gem foo (1.0.0)"). - and(a_string_including('The metadata was {"rubygems"=>">\n"}')) + and(a_string_including("The metadata was #{{ "rubygems" => ">\n" }.inspect}")) ) 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 7997cb0c40..2b7dbde217 100644 --- a/spec/bundler/bundler/env_spec.rb +++ b/spec/bundler/bundler/env_spec.rb @@ -70,16 +70,16 @@ RSpec.describe Bundler::Env do context "when there is a Gemfile and a lockfile and print_gemfile is true" do before do - gemfile "source \"#{file_uri_for(gem_repo1)}\"; gem 'rack', '1.0.0'" + gemfile "source 'https://gem.repo1'; gem 'myrack', '1.0.0'" lockfile <<-L GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: - rack (1.0.0) + myrack (1.0.0) DEPENDENCIES - rack + myrack BUNDLED WITH 1.10.0 @@ -92,12 +92,12 @@ RSpec.describe Bundler::Env do it "prints the Gemfile" do expect(output).to include("Gemfile") - expect(output).to include("'rack', '1.0.0'") + expect(output).to include("'myrack', '1.0.0'") end it "prints the lockfile" do expect(output).to include("Gemfile.lock") - expect(output).to include("rack (1.0.0)") + expect(output).to include("myrack (1.0.0)") end end @@ -148,9 +148,9 @@ RSpec.describe Bundler::Env do end before do - gemfile("source \"#{file_uri_for(gem_repo1)}\"; gemspec") + gemfile("source 'https://gem.repo1'; gemspec") - File.open(bundled_app.join("foo.gemspec"), "wb") do |f| + File.open(bundled_app("foo.gemspec"), "wb") do |f| f.write(gemspec) end @@ -167,10 +167,10 @@ RSpec.describe Bundler::Env do context "when eval_gemfile is used" do it "prints all gemfiles" do - create_file bundled_app("other/Gemfile-other"), "gem 'rack'" - create_file bundled_app("other/Gemfile"), "eval_gemfile 'Gemfile-other'" - create_file bundled_app("Gemfile-alt"), <<-G - source "#{file_uri_for(gem_repo1)}" + gemfile bundled_app("other/Gemfile-other"), "gem 'myrack'" + gemfile bundled_app("other/Gemfile"), "eval_gemfile 'Gemfile-other'" + gemfile bundled_app("Gemfile-alt"), <<-G + source "https://gem.repo1" eval_gemfile "other/Gemfile" G gemfile "eval_gemfile #{bundled_app("Gemfile-alt").to_s.dump}" @@ -190,7 +190,7 @@ RSpec.describe Bundler::Env do ### Gemfile-alt ```ruby - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" eval_gemfile "other/Gemfile" ``` @@ -203,7 +203,7 @@ RSpec.describe Bundler::Env do ### other/Gemfile-other ```ruby - gem 'rack' + gem 'myrack' ``` ### Gemfile.lock @@ -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/compact_index_spec.rb b/spec/bundler/bundler/fetcher/compact_index_spec.rb index a988171f34..aa536673d9 100644 --- a/spec/bundler/bundler/fetcher/compact_index_spec.rb +++ b/spec/bundler/bundler/fetcher/compact_index_spec.rb @@ -4,14 +4,18 @@ require "bundler/compact_index_client" RSpec.describe Bundler::Fetcher::CompactIndex do - let(:downloader) { double(:downloader) } + let(:response) { double(:response) } + let(:downloader) { double(:downloader, fetch: response) } let(:display_uri) { Gem::URI("http://sampleuri.com") } let(:remote) { double(:remote, cache_slug: "lsjdf", uri: display_uri) } let(:gem_remote_fetcher) { nil } let(:compact_index) { described_class.new(downloader, remote, display_uri, gem_remote_fetcher) } + let(:compact_index_client) { double(:compact_index_client, available?: true, info: [["lskdjf", "1", nil, [], []]]) } before do + allow(response).to receive(:is_a?).with(Gem::Net::HTTPNotModified).and_return(true) allow(compact_index).to receive(:log_specs) {} + allow(compact_index).to receive(:compact_index_client).and_return(compact_index_client) end describe "#specs_for_names" do @@ -32,11 +36,6 @@ RSpec.describe Bundler::Fetcher::CompactIndex do end describe "#available?" do - before do - allow(compact_index).to receive(:compact_index_client). - and_return(double(:compact_index_client, update_and_parse_checksums!: true)) - end - it "returns true" do expect(compact_index).to be_available 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 d5c32f4730..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") } @@ -154,35 +186,39 @@ RSpec.describe Bundler::Fetcher::Downloader do context "that contains cgi escaped characters" do let(:uri) { Gem::URI("http://username:password%24@www.uri-to-fetch.com/api/v2/endpoint") } - it "should request basic authentication with the username and password" do + it "should request basic authentication with the username and password, and log the HTTP GET request to debug, without the password" do expect(net_http_get).to receive(:basic_auth).with("username", "password$") + expect(Bundler).to receive_message_chain(:ui, :debug).with("HTTP GET http://username@www.uri-to-fetch.com/api/v2/endpoint") subject.request(uri, options) end end context "that is all unescaped characters" do let(:uri) { Gem::URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint") } - it "should request basic authentication with the username and proper cgi compliant password" do + it "should request basic authentication with the username and proper cgi compliant password, and log the HTTP GET request to debug, without the password" do expect(net_http_get).to receive(:basic_auth).with("username", "password") + expect(Bundler).to receive_message_chain(:ui, :debug).with("HTTP GET http://username@www.uri-to-fetch.com/api/v2/endpoint") subject.request(uri, options) end end end - context "and there is no password provided" do + context "and it's used as the authentication token" do let(:uri) { Gem::URI("http://username@www.uri-to-fetch.com/api/v2/endpoint") } - it "should request basic authentication with just the user" do + it "should request basic authentication with just the user, and log the HTTP GET request to debug, without the token" do expect(net_http_get).to receive(:basic_auth).with("username", nil) + expect(Bundler).to receive_message_chain(:ui, :debug).with("HTTP GET http://www.uri-to-fetch.com/api/v2/endpoint") subject.request(uri, options) end end - context "that contains cgi escaped characters" do + context "and it's used as the authentication token, and contains cgi escaped characters" do let(:uri) { Gem::URI("http://username%24@www.uri-to-fetch.com/api/v2/endpoint") } - it "should request basic authentication with the proper cgi compliant password user" do + it "should request basic authentication with the proper cgi compliant password user, and log the HTTP GET request to debug, without the token" do expect(net_http_get).to receive(:basic_auth).with("username$", nil) + expect(Bundler).to receive_message_chain(:ui, :debug).with("HTTP GET http://www.uri-to-fetch.com/api/v2/endpoint") subject.request(uri, options) end end @@ -197,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") @@ -237,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/fetcher/index_spec.rb b/spec/bundler/bundler/fetcher/index_spec.rb index dff9ccc3cc..a6a18efd98 100644 --- a/spec/bundler/bundler/fetcher/index_spec.rb +++ b/spec/bundler/bundler/fetcher/index_spec.rb @@ -4,7 +4,9 @@ require "rubygems/remote_fetcher" RSpec.describe Bundler::Fetcher::Index do let(:downloader) { nil } - let(:remote) { nil } + let(:remote) { double(:remote, uri: remote_uri) } + let(:remote_uri) { Gem::URI("http://#{userinfo}remote-uri.org") } + let(:userinfo) { "" } let(:display_uri) { "http://sample_uri.com" } let(:rubygems) { double(:rubygems) } let(:gem_names) { %w[foo bar] } @@ -20,10 +22,8 @@ RSpec.describe Bundler::Fetcher::Index do end context "error handling" do - let(:remote_uri) { Gem::URI("http://remote-uri.org") } before do allow(rubygems).to receive(:fetch_all_remote_specs) { raise Gem::RemoteFetcher::FetchError.new(error_message, display_uri) } - allow(subject).to receive(:remote_uri).and_return(remote_uri) end context "when certificate verify failed" do @@ -38,25 +38,17 @@ RSpec.describe Bundler::Fetcher::Index do context "when a 401 response occurs" do let(:error_message) { "401" } - before do - allow(remote_uri).to receive(:userinfo).and_return(userinfo) + it "should raise a Bundler::Fetcher::AuthenticationRequiredError" do + expect { subject.specs(gem_names) }.to raise_error(Bundler::Fetcher::AuthenticationRequiredError, + %r{Authentication is required for http://remote-uri.org}) end context "and there was userinfo" do - let(:userinfo) { double(:userinfo) } + let(:userinfo) { "user:pass@" } it "should raise a Bundler::Fetcher::BadAuthenticationError" do expect { subject.specs(gem_names) }.to raise_error(Bundler::Fetcher::BadAuthenticationError, - %r{Bad username or password for http://remote-uri.org}) - end - end - - context "and there was no userinfo" do - let(:userinfo) { nil } - - it "should raise a Bundler::Fetcher::AuthenticationRequiredError" do - expect { subject.specs(gem_names) }.to raise_error(Bundler::Fetcher::AuthenticationRequiredError, - %r{Authentication is required for http://remote-uri.org}) + %r{Bad username or password for http://user@remote-uri.org}) end end end diff --git a/spec/bundler/bundler/friendly_errors_spec.rb b/spec/bundler/bundler/friendly_errors_spec.rb index cda2ef31de..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 @@ -18,8 +19,8 @@ RSpec.describe Bundler, "friendly errors" do it "reports a relevant friendly error message" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G bundle :install, env: { "DEBUG" => "true" } @@ -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 940e5df9de..b4ae2abdc5 100644 --- a/spec/bundler/bundler/gem_helper_spec.rb +++ b/spec/bundler/bundler/gem_helper_spec.rb @@ -9,9 +9,13 @@ 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" - sys_exec("git config --global init.defaultBranch main") + 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) end @@ -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 @@ -253,11 +257,11 @@ RSpec.describe Bundler::GemHelper do end before do - sys_exec("git init", dir: app_path) - sys_exec("git config user.email \"you@example.com\"", dir: app_path) - sys_exec("git config user.name \"name\"", dir: app_path) - sys_exec("git config commit.gpgsign false", dir: app_path) - sys_exec("git config push.default simple", dir: app_path) + git("init", app_path) + git("config user.email \"you@example.com\"", app_path) + git("config user.name \"name\"", app_path) + git("config commit.gpgsign false", app_path) + git("config push.default simple", app_path) # silence messages allow(Bundler.ui).to receive(:confirm) @@ -271,13 +275,13 @@ RSpec.describe Bundler::GemHelper do end it "when there are uncommitted files" do - sys_exec("git add .", dir: app_path) + git("add .", app_path) expect { Rake.application["release"].invoke }. to raise_error("There are files that need to be committed first.") end it "when there is no git remote" do - sys_exec("git commit -a -m \"initial commit\"", dir: app_path) + git("commit -a -m \"initial commit\"", app_path) expect { Rake.application["release"].invoke }.to raise_error(RuntimeError) end end @@ -286,8 +290,8 @@ RSpec.describe Bundler::GemHelper do let(:repo) { build_git("foo", bare: true) } before do - sys_exec("git remote add origin #{file_uri_for(repo.path)}", dir: app_path) - sys_exec('git commit -a -m "initial commit"', dir: app_path) + git("remote add origin #{repo.path}", app_path) + git('commit -a -m "initial commit"', app_path) end context "on releasing" do @@ -296,7 +300,7 @@ RSpec.describe Bundler::GemHelper do mock_confirm_message "Tagged v#{app_version}." mock_confirm_message "Pushed git commits and release tag." - sys_exec("git push -u origin main", dir: app_path) + git("push -u origin main", app_path) end it "calls rubygem_push with proper arguments" do @@ -314,8 +318,8 @@ RSpec.describe Bundler::GemHelper do it "also works when releasing from an ambiguous reference" do # Create a branch with the same name as the tag - sys_exec("git checkout -b v#{app_version}", dir: app_path) - sys_exec("git push -u origin v#{app_version}", dir: app_path) + git("checkout -b v#{app_version}", app_path) + git("push -u origin v#{app_version}", app_path) expect(subject).to receive(:rubygem_push).with(app_gem_path.to_s) @@ -323,7 +327,7 @@ RSpec.describe Bundler::GemHelper do end it "also works with releasing from a branch not yet pushed" do - sys_exec("git checkout -b module_function", dir: app_path) + git("checkout -b module_function", app_path) expect(subject).to receive(:rubygem_push).with(app_gem_path.to_s) @@ -337,7 +341,7 @@ RSpec.describe Bundler::GemHelper do mock_build_message app_name, app_version mock_confirm_message "Pushed git commits and release tag." - sys_exec("git push -u origin main", dir: app_path) + git("push -u origin main", app_path) expect(subject).to receive(:rubygem_push).with(app_gem_path.to_s) end @@ -353,7 +357,7 @@ RSpec.describe Bundler::GemHelper do mock_confirm_message "Tag v#{app_version} has already been created." expect(subject).to receive(:rubygem_push).with(app_gem_path.to_s) - sys_exec("git tag -a -m \"Version #{app_version}\" v#{app_version}", dir: app_path) + git("tag -a -m \"Version #{app_version}\" v#{app_version}", app_path) Rake.application["release"].invoke end @@ -374,10 +378,10 @@ RSpec.describe Bundler::GemHelper do end before do - sys_exec("git init", dir: app_path) - sys_exec("git config user.email \"you@example.com\"", dir: app_path) - sys_exec("git config user.name \"name\"", dir: app_path) - sys_exec("git config push.gpgsign simple", dir: app_path) + git("init", app_path) + git("config user.email \"you@example.com\"", app_path) + git("config user.name \"name\"", app_path) + git("config push.gpgsign simple", app_path) # silence messages allow(Bundler.ui).to receive(:confirm) @@ -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 fccbb58fea..0e1b7c9cc8 100644 --- a/spec/bundler/bundler/gem_version_promoter_spec.rb +++ b/spec/bundler/bundler/gem_version_promoter_spec.rb @@ -20,26 +20,26 @@ 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 it "numerically sorts versions" do versions = sorted_versions(candidates: %w[1.7.7 1.7.8 1.7.9 1.7.15 1.8.0], current: "1.7.8") - expect(versions).to eq %w[1.7.7 1.7.8 1.7.9 1.7.15 1.8.0] + expect(versions).to eq %w[1.8.0 1.7.15 1.7.9 1.7.8 1.7.7] end context "with no options" do it "defaults to level=:major, strict=false, pre=false" do versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") - expect(versions).to eq %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0] + expect(versions).to eq %w[2.1.0 2.0.1 1.0.0 0.9.0 0.3.1 0.3.0 0.2.0] end end @@ -51,25 +51,25 @@ RSpec.describe Bundler::GemVersionPromoter do it "keeps downgrades" do versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") - expect(versions).to eq %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0] + expect(versions).to eq %w[2.1.0 2.0.1 1.0.0 0.9.0 0.3.1 0.3.0 0.2.0] end end context "when level is minor" do before { gvp.level = :minor } - it "removes downgrades and major upgrades" do + it "sorts highest minor within same major in first position" do versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") - expect(versions).to eq %w[0.3.0 0.3.1 0.9.0] + expect(versions).to eq %w[0.9.0 0.3.1 0.3.0 1.0.0 2.1.0 2.0.1 0.2.0] end end context "when level is patch" do before { gvp.level = :patch } - it "removes downgrades and major and minor upgrades" do + it "sorts highest patch within same minor in first position" do versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") - expect(versions).to eq %w[0.3.0 0.3.1] + expect(versions).to eq %w[0.3.1 0.3.0 0.9.0 1.0.0 2.0.1 2.1.0 0.2.0] end end end @@ -82,25 +82,25 @@ RSpec.describe Bundler::GemVersionPromoter do it "orders by version" do versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") - expect(versions).to eq %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0] + expect(versions).to eq %w[2.1.0 2.0.1 1.0.0 0.9.0 0.3.1 0.3.0 0.2.0] end end context "when level is minor" do before { gvp.level = :minor } - it "favors downgrades, then upgrades by major descending, minor ascending, patch ascending" do + it "favors minor upgrades, then patch upgrades, then major upgrades, then downgrades" do versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") - expect(versions).to eq %w[0.2.0 2.0.1 2.1.0 1.0.0 0.3.0 0.3.1 0.9.0] + expect(versions).to eq %w[0.9.0 0.3.1 0.3.0 1.0.0 2.1.0 2.0.1 0.2.0] end end context "when level is patch" do before { gvp.level = :patch } - it "favors downgrades, then upgrades by major descending, minor descending, patch ascending" do + it "favors patch upgrades, then minor upgrades, then major upgrades, then downgrades" do versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") - expect(versions).to eq %w[0.2.0 2.1.0 2.0.1 1.0.0 0.9.0 0.3.0 0.3.1] + expect(versions).to eq %w[0.3.1 0.3.0 0.9.0 1.0.0 2.0.1 2.1.0 0.2.0] end end end @@ -110,7 +110,7 @@ RSpec.describe Bundler::GemVersionPromoter do it "sorts regardless of prerelease status" do versions = sorted_versions(candidates: %w[1.7.7.pre 1.8.0 1.8.1.pre 1.8.1 2.0.0.pre 2.0.0], current: "1.8.0") - expect(versions).to eq %w[1.7.7.pre 1.8.0 1.8.1.pre 1.8.1 2.0.0.pre 2.0.0] + expect(versions).to eq %w[2.0.0 2.0.0.pre 1.8.1 1.8.1.pre 1.8.0 1.7.7.pre] end end @@ -119,16 +119,16 @@ RSpec.describe Bundler::GemVersionPromoter do it "deprioritizes prerelease gems" do versions = sorted_versions(candidates: %w[1.7.7.pre 1.8.0 1.8.1.pre 1.8.1 2.0.0.pre 2.0.0], current: "1.8.0") - expect(versions).to eq %w[1.7.7.pre 1.8.1.pre 2.0.0.pre 1.8.0 1.8.1 2.0.0] + expect(versions).to eq %w[2.0.0 1.8.1 1.8.0 2.0.0.pre 1.8.1.pre 1.7.7.pre] end end context "when locking and not major" do before { gvp.level = :minor } - it "keeps the current version last" do - versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.1.0 2.0.1], current: "0.3.0", locked: ["bar"]) - expect(versions.last).to eq("0.3.0") + 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", unlock: []) + expect(versions.first).to eq("0.3.0") end end end diff --git a/spec/bundler/bundler/installer/gem_installer_spec.rb b/spec/bundler/bundler/installer/gem_installer_spec.rb index 4b6a07f344..dbd4e1d2c8 100644 --- a/spec/bundler/bundler/installer/gem_installer_spec.rb +++ b/spec/bundler/bundler/installer/gem_installer_spec.rb @@ -7,6 +7,7 @@ RSpec.describe Bundler::GemInstaller do let(:installer) { instance_double("Installer", definition: definition) } let(:spec_source) { instance_double("SpecSource") } let(:spec) { instance_double("Specification", name: "dummy", version: "0.0.1", loaded_from: "dummy", source: spec_source) } + let(:base_options) { { force: false, local: false, previous_spec: nil } } subject { described_class.new(spec, installer) } @@ -14,7 +15,7 @@ RSpec.describe Bundler::GemInstaller do it "invokes install method with empty build_args" do allow(spec_source).to receive(:install).with( spec, - { force: false, ensure_builtin_gems_cached: false, build_args: [], previous_spec: nil } + base_options.merge(build_args: []) ) subject.install_from_spec end @@ -22,13 +23,11 @@ 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, - { force: false, ensure_builtin_gems_cached: false, build_args: ["--with-dummy-config=dummy"], previous_spec: nil } + base_options.merge(build_args: ["--with-dummy-config=dummy"]) ) subject.install_from_spec end @@ -36,13 +35,11 @@ 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, - { force: false, ensure_builtin_gems_cached: false, build_args: ["--with-dummy-config=dummy", "--with-another-dummy-config"], previous_spec: nil } + base_options.merge(build_args: ["--with-dummy-config=dummy", "--with-another-dummy-config"]) ) subject.install_from_spec end 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 5a7047459f..a28934269b 100644 --- a/spec/bundler/bundler/plugin/index_spec.rb +++ b/spec/bundler/bundler/plugin/index_spec.rb @@ -5,7 +5,7 @@ RSpec.describe Bundler::Plugin::Index do before do allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) - gemfile "source \"#{file_uri_for(gem_repo1)}\"" + gemfile "source 'https://gem.repo1'" path = lib_path(plugin_name) index.register_plugin("new-plugin", path.to_s, [path.join("lib").to_s], commands, sources, hooks) end @@ -194,11 +194,82 @@ RSpec.describe Bundler::Plugin::Index do end end - describe "readonly disk without home" do - it "ignores being unable to create temp home dir" do - expect_any_instance_of(Bundler::Plugin::Index).to receive(:global_index_file). - and_raise(Bundler::GenericSystemCallError.new("foo", "bar")) - Bundler::Plugin::Index.new + 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 ed40029f5a..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 @@ -57,7 +64,7 @@ RSpec.describe Bundler::Plugin::Installer do end let(:result) do - installer.install(["ga-plugin"], git: file_uri_for(lib_path("ga-plugin"))) + installer.install(["ga-plugin"], git: lib_path("ga-plugin").to_s) end it "returns the installed spec after installing" do diff --git a/spec/bundler/bundler/plugin_spec.rb b/spec/bundler/bundler/plugin_spec.rb index f41b4eff3a..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 @@ -247,7 +247,7 @@ RSpec.describe Bundler::Plugin do end it "returns plugin dir in app .bundle path" do - expect(subject.root).to eq(bundled_app.join(".bundle/plugin")) + expect(subject.root).to eq(bundled_app(".bundle/plugin")) end end @@ -257,7 +257,7 @@ RSpec.describe Bundler::Plugin do end it "returns plugin dir in global bundle path" do - expect(subject.root).to eq(home.join(".bundle/plugin")) + expect(subject.root).to eq(home(".bundle/plugin")) end end 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 @@ -333,5 +341,28 @@ RSpec.describe Bundler::Plugin do end.to output("win\n").to_stdout end end + + context "the plugin load_path is invalid" do + before do + allow(index).to receive(:load_paths).with("foo-plugin"). + and_return(["invalid-file-name1", "invalid-file-name2"]) + end + + it "outputs a useful warning" do + msg = + "The following plugin paths don't exist: invalid-file-name1, invalid-file-name2.\n\n" \ + "This can happen if the plugin was " \ + "installed with a different version of Ruby that has since been uninstalled.\n\n" \ + "If you would like to reinstall the plugin, run:\n\n" \ + "bundler plugin uninstall foo-plugin && bundler plugin install foo-plugin\n\n" \ + "Continuing without installing plugin foo-plugin.\n" + + expect(Bundler.ui).to receive(:warn).with(msg) + + Plugin.hook(Bundler::Plugin::Events::EVENT1) + + expect(subject.loaded?("foo-plugin")).to be_falsey + end + end end end diff --git a/spec/bundler/bundler/resolver/candidate_spec.rb b/spec/bundler/bundler/resolver/candidate_spec.rb index f7b378d32b..aefad3316e 100644 --- a/spec/bundler/bundler/resolver/candidate_spec.rb +++ b/spec/bundler/bundler/resolver/candidate_spec.rb @@ -2,20 +2,19 @@ RSpec.describe Bundler::Resolver::Candidate do it "compares fine" do - version1 = described_class.new("1.12.5", specs: [Gem::Specification.new("foo", "1.12.5") {|s| s.platform = Gem::Platform::RUBY }]) - version2 = described_class.new("1.12.5") # passing no specs creates a platform specific candidate, so sorts higher + version1 = described_class.new("1.12.5", priority: -1) + version2 = described_class.new("1.12.5", priority: 1) - expect(version2 >= version1).to be true + expect(version2 > version1).to be true - expect(version1.generic! == version2.generic!).to be true - expect(version1.platform_specific! == version2.platform_specific!).to be true + version1 = described_class.new("1.12.5") + version2 = described_class.new("1.12.5") - expect(version1.platform_specific! >= version2.generic!).to be true - expect(version2.platform_specific! >= version1.generic!).to be true + expect(version2 == version1).to be true - version1 = described_class.new("1.12.5", specs: [Gem::Specification.new("foo", "1.12.5") {|s| s.platform = Gem::Platform::RUBY }]) - version2 = described_class.new("1.12.5", specs: [Gem::Specification.new("foo", "1.12.5") {|s| s.platform = Gem::Platform::X64_LINUX }]) + version1 = described_class.new("1.12.5", priority: 1) + version2 = described_class.new("1.12.5", priority: -1) - expect(version2 >= version1).to be true + expect(version2 < version1).to be true end end 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 b893580d72..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 @@ -68,7 +68,7 @@ RSpec.describe Bundler::Retry do it "print error message with newlines" do allow(Bundler.ui).to receive(:debug?).and_return(false) expect(Bundler.ui).to receive(:info).with("").twice - expect(Bundler.ui).to receive(:warn).with(failure_message, false) + expect(Bundler.ui).to receive(:warn).with(failure_message, true) expect do Bundler::Retry.new("test", [], 1).attempt do @@ -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 384ac4b8b2..45a37c5795 100644 --- a/spec/bundler/bundler/ruby_dsl_spec.rb +++ b/spec/bundler/bundler/ruby_dsl_spec.rb @@ -80,7 +80,7 @@ RSpec.describe Bundler::RubyDsl do context "with two requirements in the same string" do let(:ruby_version) { ">= 2.0.0, < 3.0" } it "raises an error" do - expect { subject }.to raise_error(ArgumentError) + expect { subject }.to raise_error(Bundler::InvalidArgumentError) end end @@ -168,7 +168,43 @@ RSpec.describe Bundler::RubyDsl do let(:file_content) { "ruby-#{version}@gemset\n" } it "raises an error" do - expect { subject }.to raise_error(Gem::Requirement::BadRequirementError, "Illformed requirement [\"#{version}@gemset\"]") + expect { subject }.to raise_error(Bundler::InvalidArgumentError, "2.0.0@gemset is not a valid requirement on the Ruby version") + end + end + + context "with a mise.toml file format" do + let(:file) { "mise.toml" } + let(:ruby_version_arg) { nil } + let(:file_content) do + <<~TOML + [tools] + ruby = #{quote}#{version}#{quote} + TOML + end + + 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 @@ -197,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/rubygems_integration_spec.rb b/spec/bundler/bundler/rubygems_integration_spec.rb index b6bda9f43e..a2c63a7ca0 100644 --- a/spec/bundler/bundler/rubygems_integration_spec.rb +++ b/spec/bundler/bundler/rubygems_integration_spec.rb @@ -11,14 +11,14 @@ RSpec.describe Bundler::RubygemsIntegration do end subject { Bundler.rubygems.validate(spec) } - it "validates with packaging mode disabled" do - expect(spec).to receive(:validate).with(false) + it "validates for resolution" do + expect(spec).to receive(:validate_for_resolution) subject end context "with an invalid spec" do before do - expect(spec).to receive(:validate).with(false). + expect(spec).to receive(:validate_for_resolution). and_raise(Gem::InvalidSpecificationException.new("TODO is not an author")) end @@ -32,7 +32,6 @@ RSpec.describe Bundler::RubygemsIntegration do describe "#download_gem" do let(:bundler_retry) { double(Bundler::Retry) } - let(:uri) { Gem::URI.parse("https://foo.bar") } let(:cache_dir) { "#{Gem.path.first}/cache" } let(:spec) do spec = Gem::Specification.new("Foo", Gem::Version.new("2.5.2")) @@ -41,13 +40,47 @@ RSpec.describe Bundler::RubygemsIntegration do end let(:fetcher) { double("gem_remote_fetcher") } - it "successfully downloads gem with retries" do - expect(Bundler::Retry).to receive(:new).with("download gem from #{uri}/"). - and_return(bundler_retry) - expect(bundler_retry).to receive(:attempts).and_yield - expect(fetcher).to receive(:cache_update_path) + context "when uri is public" do + let(:uri) { Gem::URI.parse("https://foo.bar") } - Bundler.rubygems.download_gem(spec, uri, cache_dir, fetcher) + it "successfully downloads gem with retries" do + expect(Bundler::Retry).to receive(:new).with("download gem from #{uri}/"). + and_return(bundler_retry) + expect(bundler_retry).to receive(:attempts).and_yield + expect(fetcher).to receive(:cache_update_path) + + Bundler.rubygems.download_gem(spec, uri, cache_dir, fetcher) + end + end + + context "when uri contains userinfo part" do + let(:uri) { Gem::URI.parse("https://#{userinfo}@foo.bar") } + + context "with user and password" do + let(:userinfo) { "user:password" } + + it "successfully downloads gem with retries with filtered log" do + expect(Bundler::Retry).to receive(:new).with("download gem from https://user:REDACTED@foo.bar/"). + and_return(bundler_retry) + expect(bundler_retry).to receive(:attempts).and_yield + expect(fetcher).to receive(:cache_update_path) + + Bundler.rubygems.download_gem(spec, uri, cache_dir, fetcher) + end + end + + context "with token [as user]" do + let(:userinfo) { "token" } + + it "successfully downloads gem with retries with filtered log" do + expect(Bundler::Retry).to receive(:new).with("download gem from https://REDACTED@foo.bar/"). + and_return(bundler_retry) + expect(bundler_retry).to receive(:attempts).and_yield + expect(fetcher).to receive(:cache_update_path) + + Bundler.rubygems.download_gem(spec, uri, cache_dir, fetcher) + end + end end end diff --git a/spec/bundler/bundler/settings_spec.rb b/spec/bundler/bundler/settings_spec.rb index 634e0faf91..5e1aaaa555 100644 --- a/spec/bundler/bundler/settings_spec.rb +++ b/spec/bundler/bundler/settings_spec.rb @@ -6,12 +6,18 @@ RSpec.describe Bundler::Settings do subject(:settings) { described_class.new(bundled_app) } describe "#set_local" do - context "when the local config file is not found" do + context "root is nil" do subject(:settings) { described_class.new(nil) } - it "raises a GemfileNotFound error with explanation" do - expect { subject.set_local("foo", "bar") }. - to raise_error(Bundler::GemfileNotFound, "Could not locate Gemfile") + before do + allow(Pathname).to receive(:new).and_call_original + allow(Pathname).to receive(:new).with(".bundle").and_return home(".bundle") + end + + it "works" do + subject.set_local("foo", "bar") + + expect(subject["foo"]).to eq("bar") end end end @@ -113,14 +119,20 @@ 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 write to the file" do + context "when it's not possible to create the settings directory" do it "raises an PermissionError with explanation" do - expect(::Bundler::FileUtils).to receive(:mkdir_p).with(settings.send(:local_config_file).dirname). - and_raise(Errno::EACCES) + settings_dir = settings.send(:local_config_file).dirname + expect(::Bundler::FileUtils).to receive(:mkdir_p).with(settings_dir). + and_raise(Errno::EACCES.new(settings_dir.to_s)) expect { settings.set_local :frozen, "1" }. - to raise_error(Bundler::PermissionError, /config/) + to raise_error(Bundler::PermissionError, /#{settings_dir}/) end end end @@ -158,12 +170,13 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow end describe "#set_global" do - context "when it's not possible to write to the file" do + context "when it's not possible to write to create the settings directory" do it "raises an PermissionError with explanation" do - expect(::Bundler::FileUtils).to receive(:mkdir_p).with(settings.send(:global_config_file).dirname). - and_raise(Errno::EACCES) + settings_dir = settings.send(:global_config_file).dirname + expect(::Bundler::FileUtils).to receive(:mkdir_p).with(settings_dir). + and_raise(Errno::EACCES.new(settings_dir.to_s)) expect { settings.set_global(:frozen, "1") }. - to raise_error(Bundler::PermissionError, %r{\.bundle/config}) + to raise_error(Bundler::PermissionError, /#{settings_dir}/) end end end @@ -192,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 } @@ -269,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 @@ -289,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 @@ -310,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.RACK" => "~/Work/git/rack") - expect(settings["my.personal.rack"]).to eq("~/Work/git/rack") + 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 @@ -329,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" \ @@ -339,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 918f73b337..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 @@ -258,8 +258,7 @@ RSpec.describe Bundler::SharedHelpers do it "ensures bundler's ruby version lib path is in ENV['RUBYLIB']" do subject.set_bundle_environment - paths = (ENV["RUBYLIB"]).split(File::PATH_SEPARATOR) - expect(paths).to include(ruby_lib_path) + expect(rubylib).to include(ruby_lib_path) end end @@ -276,8 +275,7 @@ RSpec.describe Bundler::SharedHelpers do subject.set_bundle_environment - paths = (ENV["RUBYLIB"]).split(File::PATH_SEPARATOR) - expect(paths.count(RbConfig::CONFIG["rubylibdir"])).to eq(0) + expect(rubylib.count(RbConfig::CONFIG["rubylibdir"])).to eq(0) end it "exits if bundle path contains the unix-like path separator" do @@ -356,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 @@ -425,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 @@ -441,8 +439,7 @@ RSpec.describe Bundler::SharedHelpers do it "ENV['RUBYLIB'] should only contain one instance of bundler's ruby version lib path" do subject.set_bundle_environment - paths = (ENV["RUBYLIB"]).split(File::PATH_SEPARATOR) - expect(paths.count(ruby_lib_path)).to eq(1) + expect(rubylib.count(ruby_lib_path)).to eq(1) end end end @@ -458,7 +455,7 @@ RSpec.describe Bundler::SharedHelpers do end context "system throws Errno::EACESS" do - let(:file_op_block) { proc {|_path| raise Errno::EACCES } } + let(:file_op_block) { proc {|_path| raise Errno::EACCES.new("/path") } } it "raises a PermissionError" do expect { subject.filesystem_access("/path", &file_op_block) }.to raise_error( @@ -513,39 +510,9 @@ RSpec.describe Bundler::SharedHelpers do it "raises a GenericSystemCallError" do expect { subject.filesystem_access("/path", &file_op_block) }.to raise_error( - Bundler::GenericSystemCallError, /error accessing.+underlying.+Shields down/m + Bundler::GenericSystemCallError, /error creating.+underlying.+Shields down/m ) 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 1450316d59..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 @@ -197,4 +202,153 @@ RSpec.describe Bundler::Source::Git::GitProxy do expect(Pathname.new(bundled_app("canary"))).not_to exist end + + context "URI is HTTP" do + let(:uri) { "http://github.com/ruby/rubygems.git" } + let(:clone_args_without_depth) { ["clone", "--bare", "--no-hardlinks", "--quiet", "--no-tags", "--single-branch"] } + + 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") + 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 46eb068cd1..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.0.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 dae9f3cfba..f2faa2ea64 100644 --- a/spec/bundler/bundler/stub_specification_spec.rb +++ b/spec/bundler/bundler/stub_specification_spec.rb @@ -49,10 +49,34 @@ 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 + + describe "#activated?" do + it "returns true after activation" do + stub = described_class.from_stub(with_bundler_stub_spec) + + expect(stub.activated?).to be_falsey + stub.activated = true + expect(stub.activated?).to be true + end + + it "returns true after activation if the underlying stub is a `Gem::StubSpecification`" do + spec_path = File.join(File.dirname(__FILE__), "specifications", "foo.gemspec") + gem_stub = Gem::StubSpecification.new(spec_path, File.dirname(__FILE__),"","") + stub = described_class.from_stub(gem_stub) + + expect(stub.activated?).to be_falsey + stub.activated = true + expect(stub.activated?).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 15120a8a41..83f147191e 100644 --- a/spec/bundler/bundler/ui/shell_spec.rb +++ b/spec/bundler/bundler/ui/shell_spec.rb @@ -10,6 +10,13 @@ RSpec.describe Bundler::UI::Shell do it "prints to stdout" do expect { subject.info("info") }.to output("info\n").to_stdout end + + context "when output_stream is :stderr" do + before { subject.output_stream = :stderr } + it "prints to stderr" do + expect { subject.info("info") }.to output("info\n").to_stderr + end + end end describe "#confirm" do @@ -17,19 +24,36 @@ RSpec.describe Bundler::UI::Shell do it "prints to stdout" do expect { subject.confirm("confirm") }.to output("confirm\n").to_stdout end + + context "when output_stream is :stderr" do + before { subject.output_stream = :stderr } + it "prints to stderr" do + expect { subject.confirm("confirm") }.to output("confirm\n").to_stderr + end + end end describe "#warn" do before { subject.level = "warn" } - it "prints to stderr" do + it "prints to stderr, implicitly adding a newline" do expect { subject.warn("warning") }.to output("warning\n").to_stderr end + it "can be told not to emit a newline" do + expect { subject.warn("warning", false) }.to output("warning").to_stderr + end end describe "#debug" do it "prints to stdout" do expect { subject.debug("debug") }.to output("debug\n").to_stdout end + + context "when output_stream is :stderr" do + before { subject.output_stream = :stderr } + it "prints to stderr" do + expect { subject.debug("debug") }.to output("debug\n").to_stderr + end + end end describe "#error" do @@ -57,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_credentials_filter_spec.rb b/spec/bundler/bundler/uri_credentials_filter_spec.rb index ed24744a1c..641f0addb4 100644 --- a/spec/bundler/bundler/uri_credentials_filter_spec.rb +++ b/spec/bundler/bundler/uri_credentials_filter_spec.rb @@ -31,6 +31,16 @@ RSpec.describe Bundler::URICredentialsFilter do it_behaves_like "original type of uri is maintained" end + + context "specified without empty username" do + let(:credentials) { "oauth_token@" } + + it "returns the uri without the oauth token" do + expect(subject.credential_filtered_uri(uri).to_s).to eq(Gem::URI("https://github.com/company/private-repo").to_s) + end + + it_behaves_like "original type of uri is maintained" + end end context "authentication using login credentials" do 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 12385427b1..2a280ea858 100644 --- a/spec/bundler/cache/cache_path_spec.rb +++ b/spec/bundler/cache/cache_path_spec.rb @@ -3,30 +3,30 @@ RSpec.describe "bundle package" do before do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G end context "with --cache-path" do it "caches gems at given path" do bundle :cache, "cache-path" => "vendor/cache-foo" - expect(bundled_app("vendor/cache-foo/rack-1.0.0.gem")).to exist + expect(bundled_app("vendor/cache-foo/myrack-1.0.0.gem")).to exist end end 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/rack-1.0.0.gem")).to exist + expect(bundled_app("vendor/cache-foo/myrack-1.0.0.gem")).to exist end end context "with absolute --cache-path" do it "caches gems at given path" do - bundle :cache, "cache-path" => "/tmp/cache-foo" - expect(bundled_app("/tmp/cache-foo/rack-1.0.0.gem")).to exist + bundle :cache, "cache-path" => bundled_app("vendor/cache-foo") + expect(bundled_app("vendor/cache-foo/myrack-1.0.0.gem")).to exist end end end diff --git a/spec/bundler/cache/gems_spec.rb b/spec/bundler/cache/gems_spec.rb index abbc2c3cf2..198279d84c 100644 --- a/spec/bundler/cache/gems_spec.rb +++ b/spec/bundler/cache/gems_spec.rb @@ -4,23 +4,23 @@ RSpec.describe "bundle cache" do shared_examples_for "when there are only gemsources" do before :each do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' G - system_gems "rack-1.0.0", path: path + system_gems "myrack-1.0.0", path: path bundle :cache end it "copies the .gem file to vendor/cache" do - expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist end it "uses the cache as a source when installing gems" do build_gem "omg", path: bundled_app("vendor/cache") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "omg" G @@ -31,44 +31,44 @@ RSpec.describe "bundle cache" do system_gems [], path: default_bundle_path bundle "install --local" - expect(the_bundle).to include_gems("rack 1.0.0") + expect(the_bundle).to include_gems("myrack 1.0.0") end it "does not reinstall gems from the cache if they exist on the system" do - build_gem "rack", "1.0.0", path: bundled_app("vendor/cache") do |s| - s.write "lib/rack.rb", "RACK = 'FAIL'" + build_gem "myrack", "1.0.0", path: bundled_app("vendor/cache") do |s| + s.write "lib/myrack.rb", "MYRACK = 'FAIL'" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G - expect(the_bundle).to include_gems("rack 1.0.0") + expect(the_bundle).to include_gems("myrack 1.0.0") end it "does not reinstall gems from the cache if they exist in the bundle" do - system_gems "rack-1.0.0", path: default_bundle_path + system_gems "myrack-1.0.0", path: default_bundle_path gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G - build_gem "rack", "1.0.0", path: bundled_app("vendor/cache") do |s| - s.write "lib/rack.rb", "RACK = 'FAIL'" + build_gem "myrack", "1.0.0", path: bundled_app("vendor/cache") do |s| + s.write "lib/myrack.rb", "MYRACK = 'FAIL'" end bundle :install, local: true - expect(the_bundle).to include_gems("rack 1.0.0") + expect(the_bundle).to include_gems("myrack 1.0.0") end it "creates a lockfile" do - cache_gems "rack-1.0.0" + cache_gems "myrack-1.0.0" gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G bundle "cache" @@ -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 @@ -93,82 +93,102 @@ RSpec.describe "bundle cache" do let(:default_json_version) { ruby "gem 'json'; require 'json'; puts JSON::VERSION" } before :each do - build_repo2 do - build_gem "json", default_json_version - end - build_gem "json", default_json_version, to_system: true, default: true end - it "uses builtin gems when installing to system gems" do - bundle "config set path.system true" - install_gemfile %(source "#{file_uri_for(gem_repo1)}"; gem 'json', '#{default_json_version}'), verbose: true - expect(out).to include("Using json #{default_json_version}") - end + context "when a remote gem is available for caching" do + before do + build_repo2 do + build_gem "json", default_json_version + end + end - it "caches remote and builtin gems" do - install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem 'json', '#{default_json_version}' - gem 'rack', '1.0.0' - G + it "uses remote gems when installing" do + install_gemfile %(source "https://gem.repo2"; gem 'json', '#{default_json_version}'), verbose: true + expect(out).to include("Installing json #{default_json_version}") + end - bundle :cache - expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist - expect(bundled_app("vendor/cache/json-#{default_json_version}.gem")).to exist - end + it "does not use remote gems when installing with --local flag" do + install_gemfile %(source "https://gem.repo2"; gem 'json', '#{default_json_version}'), verbose: true, local: true + expect(out).to include("Using json #{default_json_version}") + end - it "caches builtin gems when cache_all_platforms is set" do - gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "json" - G + it "does not use remote gems when installing with --prefer-local flag" do + install_gemfile %(source "https://gem.repo2"; gem 'json', '#{default_json_version}'), verbose: true, "prefer-local": true + expect(out).to include("Using json #{default_json_version}") + end - bundle "config set cache_all_platforms true" + it "caches remote and builtin gems" do + install_gemfile <<-G + source "https://gem.repo2" + gem 'json', '#{default_json_version}' + gem 'myrack', '1.0.0' + G - bundle :cache - expect(bundled_app("vendor/cache/json-#{default_json_version}.gem")).to exist - end + bundle :cache + expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist + expect(bundled_app("vendor/cache/json-#{default_json_version}.gem")).to exist + end + + it "caches builtin gems when cache_all_platforms is set" do + gemfile <<-G + source "https://gem.repo2" + gem "json" + G + + bundle_config "cache_all_platforms true" - it "doesn't make remote request after caching the gem" do - build_gem "builtin_gem_2", "1.0.2", path: bundled_app("vendor/cache") do |s| - s.summary = "This builtin_gem is bundled with Ruby" + bundle :cache + expect(bundled_app("vendor/cache/json-#{default_json_version}.gem")).to exist end - install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem 'builtin_gem_2', '1.0.2' - G + it "doesn't make remote request after caching the gem" do + build_gem "builtin_gem_2", "1.0.2", path: bundled_app("vendor/cache"), default: true - bundle "install --local" - expect(the_bundle).to include_gems("builtin_gem_2 1.0.2") + install_gemfile <<-G + source "https://gem.repo2" + gem 'builtin_gem_2', '1.0.2' + G + + bundle "install --local" + expect(the_bundle).to include_gems("builtin_gem_2 1.0.2") + end end - it "errors if the builtin gem isn't available to cache" do - bundle "config set path.system true" + 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 "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 - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'json', '#{default_json_version}' - G + it "errors when explicitly caching" do + bundle_config "path.system true" + + install_gemfile <<-G + source "https://gem.repo1" + gem 'json', '#{default_json_version}' + G - bundle :cache, raise_on_error: false - expect(exitstatus).to_not eq(0) - expect(err).to include("json-#{default_json_version} is built in to Ruby, and can't be cached") + bundle :cache, raise_on_error: false + expect(last_command).to be_failure + expect(err).to include("json-#{default_json_version} is built in to Ruby, and can't be cached") + end end end describe "when there are also git sources" do before do build_git "foo" - system_gems "rack-1.0.0" + system_gems "myrack-1.0.0" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("foo-1.0")}" do gem 'foo' end - gem 'rack' + gem 'myrack' G end @@ -178,7 +198,7 @@ RSpec.describe "bundle cache" do system_gems [] bundle "install --local" - expect(the_bundle).to include_gems("rack 1.0.0", "foo 1.0") + expect(the_bundle).to include_gems("myrack 1.0.0", "foo 1.0") end it "should not explode if the lockfile is not present" do @@ -191,41 +211,44 @@ RSpec.describe "bundle cache" do end describe "when previously cached" do - before :each do + let :setup_main_repo do build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack" + source "https://gem.repo2" + gem "myrack" gem "actionpack" G bundle :cache - expect(cached_gem("rack-1.0.0")).to exist + expect(cached_gem("myrack-1.0.0")).to exist expect(cached_gem("actionpack-2.3.2")).to exist expect(cached_gem("activesupport-2.3.2")).to exist end it "re-caches during install" do - cached_gem("rack-1.0.0").rmtree + setup_main_repo + FileUtils.rm_rf cached_gem("myrack-1.0.0") bundle :install expect(out).to include("Updating files in vendor/cache") - expect(cached_gem("rack-1.0.0")).to exist + expect(cached_gem("myrack-1.0.0")).to exist end it "adds and removes when gems are updated" do + setup_main_repo update_repo2 do - build_gem "rack", "1.2" do |s| - s.executables = "rackup" + build_gem "myrack", "1.2" do |s| + s.executables = "myrackup" end end bundle "update", all: true - expect(cached_gem("rack-1.2")).to exist - expect(cached_gem("rack-1.0.0")).not_to exist + expect(cached_gem("myrack-1.2")).to exist + expect(cached_gem("myrack-1.0.0")).not_to exist end it "adds new gems and dependencies" do + setup_main_repo install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "rails" G expect(cached_gem("rails-2.3.2")).to exist @@ -233,24 +256,26 @@ RSpec.describe "bundle cache" do end it "removes .gems for removed gems and dependencies" do + setup_main_repo install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack" + source "https://gem.repo2" + gem "myrack" G - expect(cached_gem("rack-1.0.0")).to exist + expect(cached_gem("myrack-1.0.0")).to exist expect(cached_gem("actionpack-2.3.2")).not_to exist expect(cached_gem("activesupport-2.3.2")).not_to exist end it "removes .gems when gem changes to git source" do - build_git "rack" + setup_main_repo + build_git "myrack" install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack", :git => "#{lib_path("rack-1.0")}" + source "https://gem.repo2" + gem "myrack", :git => "#{lib_path("myrack-1.0")}" gem "actionpack" G - expect(cached_gem("rack-1.0.0")).not_to exist + expect(cached_gem("myrack-1.0.0")).not_to exist expect(cached_gem("actionpack-2.3.2")).to exist expect(cached_gem("activesupport-2.3.2")).to exist end @@ -258,7 +283,7 @@ RSpec.describe "bundle cache" do it "doesn't remove gems that are for another platform" do simulate_platform "java" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "platform_specific" G @@ -266,84 +291,111 @@ RSpec.describe "bundle cache" do expect(cached_gem("platform_specific-1.0-java")).to exist end - simulate_new_machine - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "platform_specific" - G + pristine_system_gems - expect(cached_gem("platform_specific-1.0-#{Bundler.local_platform}")).to exist - expect(cached_gem("platform_specific-1.0-java")).to exist + simulate_platform "x86-darwin-100" do + install_gemfile <<-G + source "https://gem.repo1" + gem "platform_specific" + G + + expect(cached_gem("platform_specific-1.0-x86-darwin-100")).to exist + expect(cached_gem("platform_specific-1.0-java")).to exist + end end - it "doesn't remove gems with mismatched :rubygems_version or :date" do - cached_gem("rack-1.0.0").rmtree - build_gem "rack", "1.0.0", - path: bundled_app("vendor/cache"), + 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") + FileUtils.rm_rf cached_myrack + build_gem "myrack", "1.0.0", + path: cached_myrack.parent, rubygems_version: "1.3.2" - # This test is only really valid if the checksum isn't saved. It otherwise can't be the same gem. Tested below. - bundled_app_lock.write remove_checksums_from_lockfile(bundled_app_lock.read, "rack (1.0.0)") - simulate_new_machine - bundle :install - expect(cached_gem("rack-1.0.0")).to exist + FileUtils.rm_r default_bundle_path + default_system_gems + + FileUtils.rm bundled_app_lock + bundle :install, raise_on_error: false + + expect(err).to eq <<~E.strip + 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_from_package(cached_myrack, "myrack", "1.0.0")} + from the gem at #{cached_myrack} + + If you trust the API at https://gem.repo2/, to resolve this issue you can: + 1. remove the gem at #{cached_myrack} + 2. run `bundle install` + + To ignore checksum security warnings, disable checksum validation with + `bundle config set --local disable_checksum_validation true` + E + + expect(cached_gem("myrack-1.0.0")).to exist end - it "raises an error when the gem is altered and produces a different checksum" do - cached_gem("rack-1.0.0").rmtree - build_gem "rack", "1.0.0", path: bundled_app("vendor/cache") + it "raises an error when a cached gem is altered and produces a different checksum than the remote gem" do + setup_main_repo + 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, "rack", "1.0.0" + c.checksum gem_repo1, "myrack", "1.0.0" end - simulate_new_machine + FileUtils.rm_r default_bundle_path + default_system_gems lockfile <<-L GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: - rack (1.0.0) + myrack (1.0.0) #{checksums} L bundle :install, raise_on_error: false expect(exitstatus).to eq(37) expect(err).to include("Bundler found mismatched checksums.") - expect(err).to include("1. remove the gem at #{cached_gem("rack-1.0.0")}") + expect(err).to include("1. remove the gem at #{cached_gem("myrack-1.0.0")}") - expect(cached_gem("rack-1.0.0")).to exist - cached_gem("rack-1.0.0").rmtree + expect(cached_gem("myrack-1.0.0")).to exist + FileUtils.rm_rf cached_gem("myrack-1.0.0") bundle :install - expect(cached_gem("rack-1.0.0")).to exist + expect(cached_gem("myrack-1.0.0")).to exist end - it "installs a modified gem with a non-matching checksum when checksums is not opted in" do - cached_gem("rack-1.0.0").rmtree - build_gem "rack", "1.0.0", path: bundled_app("vendor/cache") - simulate_new_machine + it "installs a modified gem with a non-matching checksum when the API implementation does not provide checksums" do + setup_main_repo + FileUtils.rm_rf cached_gem("myrack-1.0.0") + build_gem "myrack", "1.0.0", path: bundled_app("vendor/cache") + pristine_system_gems lockfile <<-L GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: - rack (1.0.0) + myrack (1.0.0) L - bundle :install - expect(cached_gem("rack-1.0.0")).to exist + bundle :install, artifice: "endpoint", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + expect(cached_gem("myrack-1.0.0")).to exist end it "handles directories and non .gem files in the cache" do + setup_main_repo bundled_app("vendor/cache/foo").mkdir File.open(bundled_app("vendor/cache/bar"), "w") {|f| f.write("not a gem") } bundle :cache end it "does not say that it is removing gems when it isn't actually doing so" do + setup_main_repo install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G bundle "cache" bundle "install" @@ -351,9 +403,10 @@ RSpec.describe "bundle cache" do end it "does not warn about all if it doesn't have any git/path dependency" do + setup_main_repo install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G bundle "cache" expect(out).not_to match(/\-\-all/) @@ -364,7 +417,7 @@ RSpec.describe "bundle cache" do path: bundled_app("vendor/cache") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo-bundler" G diff --git a/spec/bundler/cache/git_spec.rb b/spec/bundler/cache/git_spec.rb index 4b3cd4d2eb..f0976ecac7 100644 --- a/spec/bundler/cache/git_spec.rb +++ b/spec/bundler/cache/git_spec.rb @@ -13,22 +13,37 @@ 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) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" 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 @@ -37,19 +52,18 @@ RSpec.describe "bundle cache with git" do ref = git.ref_for("main", 11) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" 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 @@ -57,16 +71,15 @@ RSpec.describe "bundle cache with git" do build_git "foo" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" 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 @@ -75,11 +88,10 @@ RSpec.describe "bundle cache with git" do old_ref = git.ref_for("main", 11) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" 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 @@ -105,11 +117,10 @@ RSpec.describe "bundle cache with git" do old_ref = git.ref_for("main", 11) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" 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 @@ -134,13 +145,12 @@ RSpec.describe "bundle cache with git" do ref = git.ref_for("main", 11) gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => '#{lib_path("foo-invalid")}', :branch => :main G 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 @@ -154,6 +164,167 @@ RSpec.describe "bundle cache with git" do expect(out).to eq("LOCAL") end + it "can use gems after copying install folder to a different machine with git not installed" do + build_git "foo" + + gemfile <<-G + source "https://gem.repo1" + gem "foo", :git => '#{lib_path("foo-1.0")}' + G + bundle_config "path vendor/bundle" + bundle :install + + pristine_system_gems + with_path_as "" do + bundle_config "deployment true" + bundle "install --local" + expect(the_bundle).to include_gem "foo 1.0" + end + end + + it "can install after bundle cache without cloning remote repositories" do + build_git "foo" + + gemfile <<-G + source "https://gem.repo1" + gem "foo", :git => '#{lib_path("foo-1.0")}' + G + bundle :cache, "all-platforms" => 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" + end + + it "can install after bundle cache without cloning remote repositories even without the original cache" do + build_git "foo" + + gemfile <<-G + source "https://gem.repo1" + gem "foo", :git => '#{lib_path("foo-1.0")}' + G + bundle :cache, "all-platforms" => 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" + end + + it "can install after bundle cache without cloning remote repositories with only git tracked files" do + build_git "foo" + + gemfile <<-G + source "https://gem.repo1" + gem "foo", :git => '#{lib_path("foo-1.0")}' + G + bundle :cache, "all-platforms" => true + + pristine_system_gems + bundle_config "frozen true" + + # Remove untracked files (including the empty refs dir in the cache) + Dir.chdir(bundled_app) do + system(*%W[git init --quiet]) + system(*%W[git add --all]) + system(*%W[git clean -d --force --quiet]) + end + + bundle "install --local --verbose" + expect(out).to_not include("Fetching") + expect(the_bundle).to include_gem "foo 1.0" + end + + it "installs properly a bundler 2.5.17-2.5.23 cache as a bare repository without cloning remote repositories" do + git = build_git "foo" + + short_ref = git.ref_for("main", 11) + cache_dir = bundled_app("vendor/cache/foo-1.0-#{short_ref}") + + gemfile <<-G + source "https://gem.repo1" + gem "foo", :git => '#{lib_path("foo-1.0")}' + G + 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_r bundled_app("vendor/bundle") + + bundle "install --local --verbose" + expect(err).to include("Installing from cache in old \"bare repository\" format for compatibility") + + expect(out).to_not include("Fetching") + + # leaves old cache alone + expect(cache_dir.join("lib/foo.rb")).not_to exist + expect(cache_dir.join("HEAD")).to exist + + expect(the_bundle).to include_gem "foo 1.0" + end + + it "migrates a bundler 2.5.17-2.5.23 cache as a bare repository when not running with --local" do + git = build_git "foo" + + short_ref = git.ref_for("main", 11) + cache_dir = bundled_app("vendor/cache/foo-1.0-#{short_ref}") + + gemfile <<-G + source "https://gem.repo1" + gem "foo", :git => '#{lib_path("foo-1.0")}' + G + 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_r bundled_app("vendor/bundle") + + bundle "install --verbose" + expect(out).to include("Fetching") + + # migrates old cache alone + expect(cache_dir.join("lib/foo.rb")).to exist + expect(cache_dir.join("HEAD")).not_to exist + + expect(the_bundle).to include_gem "foo 1.0" + end + + it "migrates a bundler 2.5.17-2.5.23 cache as a bare repository when running `bundle cache`, even if gems already installed" do + git = build_git "foo" + + short_ref = git.ref_for("main", 11) + cache_dir = bundled_app("vendor/cache/foo-1.0-#{short_ref}") + + gemfile <<-G + source "https://gem.repo1" + gem "foo", :git => '#{lib_path("foo-1.0")}' + G + 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 + + bundle "cache" + + # migrates old cache alone + expect(cache_dir.join("lib/foo.rb")).to exist + expect(cache_dir.join("HEAD")).not_to exist + + expect(the_bundle).to include_gem "foo 1.0" + end + it "copies repository to vendor cache, including submodules" do # CVE-2022-39253: https://lore.kernel.org/lkml/xmqq4jw1uku5.fsf@gitster.g/ system(*%W[git config --global protocol.file.allow always]) @@ -164,18 +335,17 @@ RSpec.describe "bundle cache with git" do s.add_dependency "submodule" end - sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0", dir: lib_path("has_submodule-1.0") - sys_exec "git commit -m \"submodulator\"", dir: lib_path("has_submodule-1.0") + git "submodule add #{lib_path("submodule-1.0")} submodule-1.0", lib_path("has_submodule-1.0") + git "commit -m \"submodulator\"", lib_path("has_submodule-1.0") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("has_submodule-1.0")}", :submodules => true do gem "has_submodule" end 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 @@ -192,10 +362,9 @@ RSpec.describe "bundle cache with git" do update_git("foo") {|s| s.write "foo.gemspec", spec_lines.join("\n") } install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + 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) @@ -207,29 +376,67 @@ RSpec.describe "bundle cache with git" do build_git "foo" gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + 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 end + it "can install after bundle cache generated with an older Bundler that kept checkouts in the cache" do + git = build_git("foo") + locked_revision = git.ref_for("main") + path_revision = git.ref_for("main", 11) + + git_path = lib_path("foo-1.0") + + gemfile <<-G + source "https://gem.repo1" + gem "foo", :git => '#{git_path}' + G + lockfile <<~L + GIT + remote: #{git_path}/ + revision: #{locked_revision} + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + + BUNDLED WITH + #{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_r bundled_app("vendor/cache/foo-1.0-#{path_revision}/.git") + + bundle :install, env: { "BUNDLE_DEPLOYMENT" => "true", "BUNDLE_CACHE_ALL" => "true" } + end + it "respects the --no-install flag" do git = build_git "foo", &:add_c_extension ref = git.ref_for("main", 11) gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + 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 @@ -245,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 @@ -273,4 +480,32 @@ RSpec.describe "bundle cache with git" do R expect(last_command).to_not be_failure end + + it "doesn't fail when git gem has extensions and an empty cache folder is present before bundle install" do + build_git "puma" do |s| + s.add_dependency "rake" + s.extensions << "Rakefile" + s.executables = "puma" + s.write "Rakefile", <<-RUBY + task :default do + path = File.expand_path("../lib", __FILE__) + FileUtils.mkdir_p(path) + File.open("\#{path}/puma.rb", "w") do |f| + f.puts "PUMA = 'YES'" + end + end + RUBY + end + + FileUtils.mkdir_p(bundled_app("vendor/cache")) + + install_gemfile <<-G + source "https://gem.repo1" + gem "puma", :git => "#{lib_path("puma-1.0")}" + G + + bundle "exec puma" + + expect(out).to eq("YES") + end end diff --git a/spec/bundler/cache/path_spec.rb b/spec/bundler/cache/path_spec.rb index 2c8a52617a..42648aea1f 100644 --- a/spec/bundler/cache/path_spec.rb +++ b/spec/bundler/cache/path_spec.rb @@ -5,11 +5,10 @@ RSpec.describe "bundle cache with path" do build_lib "foo", path: bundled_app("lib/foo") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" 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" @@ -19,11 +18,10 @@ RSpec.describe "bundle cache with path" do build_lib "foo" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" 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 @@ -38,11 +36,10 @@ RSpec.describe "bundle cache with path" do build_lib libname, path: libpath install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" 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 @@ -54,11 +51,10 @@ RSpec.describe "bundle cache with path" do build_lib "foo" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :path => '#{lib_path("foo-1.0")}' G - bundle "config set cache_all true" bundle :cache build_lib "foo" do |s| @@ -77,11 +73,10 @@ RSpec.describe "bundle cache with path" do build_lib "foo" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" 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 @@ -89,7 +84,7 @@ RSpec.describe "bundle cache with path" do build_lib "bar" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "bar", :path => '#{lib_path("bar-1.0")}' G @@ -97,24 +92,25 @@ 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 "#{file_uri_for(gem_repo1)}" + 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 - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :path => '#{lib_path("foo-1.0")}' 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 "#{file_uri_for(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 "#{file_uri_for(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 "#{file_uri_for(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 "#{file_uri_for(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/cache/platform_spec.rb b/spec/bundler/cache/platform_spec.rb index 36db954c79..71c0eaee8e 100644 --- a/spec/bundler/cache/platform_spec.rb +++ b/spec/bundler/cache/platform_spec.rb @@ -3,10 +3,10 @@ RSpec.describe "bundle cache with multiple platforms" do before :each do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" platforms :mri, :rbx do - gem "rack", "1.0.0" + gem "myrack", "1.0.0" end platforms :jruby do @@ -16,9 +16,9 @@ RSpec.describe "bundle cache with multiple platforms" do lockfile <<-G GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: - rack (1.0.0) + myrack (1.0.0) activesupport (2.3.5) PLATFORMS @@ -26,24 +26,24 @@ RSpec.describe "bundle cache with multiple platforms" do java DEPENDENCIES - rack (1.0.0) + myrack (1.0.0) activesupport (2.3.5) G - cache_gems "rack-1.0.0", "activesupport-2.3.5" + cache_gems "myrack-1.0.0", "activesupport-2.3.5" end it "ensures that a successful bundle install does not delete gems for other platforms" do bundle "install" - expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist expect(bundled_app("vendor/cache/activesupport-2.3.5.gem")).to exist end it "ensures that a successful bundle update does not delete gems for other platforms" do bundle "update", all: true - expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist expect(bundled_app("vendor/cache/activesupport-2.3.5.gem")).to exist end end diff --git a/spec/bundler/commands/add_spec.rb b/spec/bundler/commands/add_spec.rb index e2f5bbf42f..162650f2e5 100644 --- a/spec/bundler/commands/add_spec.rb +++ b/spec/bundler/commands/add_spec.rb @@ -15,7 +15,7 @@ RSpec.describe "bundle add" do build_git "foo", "2.0" gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "weakling", "~> 0.0.1" G end @@ -28,35 +28,44 @@ RSpec.describe "bundle add" do end end + context "when Gemfile is empty, and frozen mode is set" do + it "shows error" do + gemfile 'source "https://gem.repo2"' + bundle "add bar", raise_on_error: false, env: { "BUNDLE_FROZEN" => "true" } + + expect(err).to include("Frozen mode is set, but there's no lockfile") + end + 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 @@ -79,34 +88,34 @@ 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 describe "with --source" do it "adds dependency with specified source" do - bundle "add 'foo' --source='#{file_uri_for(gem_repo2)}'" + bundle "add 'foo' --source='https://gem.repo2'" - expect(bundled_app_gemfile.read).to match(/gem "foo", "~> 2.0", :source => "#{file_uri_for(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 @@ -115,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 @@ -124,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 @@ -137,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 @@ -146,32 +155,118 @@ 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 - it "adds dependency with specified github source", :realworld 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 + + it "adds dependency with specified github source and branch" do + bundle "add rake --github=ruby/rake --branch=main" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", ">= 13\.\d+", github: "ruby\/rake", branch: "main"}) + end + + it "adds dependency with specified github source and ref" do + 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", 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 - describe "with --github and --branch" do - it "adds dependency with specified github source and branch", :realworld do - bundle "add rake --github=ruby/rake --branch=master" + describe "with --git and --glob" do + it "adds dependency with specified git source" do + bundle "add foo --git=#{lib_path("foo-2.0")} --glob='./*.gemspec'" - expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", :github => "ruby\/rake", :branch => "master"}) + expect(bundled_app_gemfile.read).to match(%r{gem "foo", ">= 2.0", git: "#{lib_path("foo-2.0")}", glob: "\./\*\.gemspec"}) + expect(the_bundle).to include_gems "foo 2.0" end end - describe "with --github and --ref" do - it "adds dependency with specified github source and ref", :realworld do - bundle "add rake --github=ruby/rake --ref=5c60da8" + describe "with --git and --branch and --glob" do + before do + update_git "foo", "2.0", branch: "test" + end + + it "adds dependency with specified git source and branch" do + bundle "add foo --git=#{lib_path("foo-2.0")} --branch=test --glob='./*.gemspec'" - expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", :github => "ruby\/rake", :ref => "5c60da8"}) + expect(bundled_app_gemfile.read).to match(%r{gem "foo", ">= 2.0", git: "#{lib_path("foo-2.0")}", branch: "test", glob: "\./\*\.gemspec"}) + expect(the_bundle).to include_gems "foo 2.0" + end + end + + describe "with --git and --ref and --glob" do + it "adds dependency with specified git source and branch" do + bundle "add foo --git=#{lib_path("foo-2.0")} --ref=#{revision_for(lib_path("foo-2.0"))} --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "foo", ">= 2\.0", git: "#{lib_path("foo-2.0")}", ref: "#{revision_for(lib_path("foo-2.0"))}", glob: "\./\*\.gemspec"}) + expect(the_bundle).to include_gems "foo 2.0" + end + end + + describe "with 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(err).to include("You cannot specify `--git` and `--github` at the same time.") + end + end + + 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(err).to include("You cannot specify `--branch` and `--ref` at the same time.") + end + end + + describe "with --branch but without --git or --github" do + it "throws error" do + bundle "add 'foo' --branch x", raise_on_error: false + + 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 @@ -185,8 +280,8 @@ RSpec.describe "bundle add" do end it "using combination of short form options works like long form" do - bundle "add 'foo' -s='#{file_uri_for(gem_repo2)}' -g='development' -v='~>1.0'" - expect(bundled_app_gemfile.read).to include %(gem "foo", "~> 1.0", :group => :development, :source => "#{file_uri_for(gem_repo2)}") + 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(the_bundle).to include_gems "foo 1.1" end @@ -196,16 +291,16 @@ 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") - bundle "add 'werk_it' -s='#{file_uri_for(gem_repo2)}'", raise_on_error: false + bundle "add 'werk_it' -s='https://gem.repo2'", raise_on_error: false expect(err).to match("Could not find gem 'werk_it' in rubygems repository") end it "shows error message when source cannot be reached" do - bundle "add 'baz' --source='http://badhostasdf'", raise_on_error: false + bundle "add 'baz' --source='http://badhostasdf'", raise_on_error: false, artifice: "fail" expect(err).to include("Could not reach host badhostasdf. Check your network connection and try again.") bundle "add 'baz' --source='file://does/not/exist'", raise_on_error: false @@ -213,13 +308,53 @@ 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" + expect(out).to be_empty + expect(err).to be_empty + end + + it "still displays warning and errors" do + create_file("add_with_warning.rb", <<~RUBY) + require "#{lib_dir}/bundler" + require "#{lib_dir}/bundler/cli" + require "#{lib_dir}/bundler/cli/add" + + module RunWithWarning + def run + super + rescue + Bundler.ui.warn "This is a warning" + raise + end + end + + Bundler::CLI::Add.prepend(RunWithWarning) + RUBY + + bundle "add 'non-existing-gem' --quiet", raise_on_error: false, env: { "RUBYOPT" => "-r#{bundled_app("add_with_warning.rb")}" } + expect(out).to be_empty + expect(err).to include("Could not find gem 'non-existing-gem'") + expect(err).to include("This is a warning") + end + end + describe "with --strict option" do it "adds strict version" do bundle "add 'foo' --strict" @@ -229,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 can not specify `--strict` and `--optimistic` at the same time") + expect(err).to include("You cannot specify `--strict` and `--pessimistic` at the same time") end end @@ -248,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 @@ -263,41 +398,41 @@ RSpec.describe "bundle add" do describe "when a gem is added which is already specified in Gemfile with version" do it "shows an error when added with different version requirement" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack", "1.0" + source "https://gem.repo2" + gem "myrack", "1.0" G - bundle "add 'rack' --version=1.1", raise_on_error: false + bundle "add 'myrack' --version=1.1", raise_on_error: false expect(err).to include("You cannot specify the same gem twice with different version requirements") - expect(err).to include("If you want to update the gem version, run `bundle update rack`. You may also need to change the version requirement specified in the Gemfile if it's too restrictive") + expect(err).to include("If you want to update the gem version, run `bundle update myrack`. You may also need to change the version requirement specified in the Gemfile if it's too restrictive") end it "shows error when added without version requirements" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack", "1.0" + source "https://gem.repo2" + gem "myrack", "1.0" G - bundle "add 'rack'", raise_on_error: false + bundle "add 'myrack'", raise_on_error: false expect(err).to include("Gem already added.") expect(err).to include("You cannot specify the same gem twice with different version requirements") - expect(err).not_to include("If you want to update the gem version, run `bundle update rack`. You may also need to change the version requirement specified in the Gemfile if it's too restrictive") + expect(err).not_to include("If you want to update the gem version, run `bundle update myrack`. You may also need to change the version requirement specified in the Gemfile if it's too restrictive") end end describe "when a gem is added which is already specified in Gemfile without version" do it "shows an error when added with different version requirement" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack" + source "https://gem.repo2" + gem "myrack" G - bundle "add 'rack' --version=1.1", raise_on_error: false + bundle "add 'myrack' --version=1.1", raise_on_error: false expect(err).to include("You cannot specify the same gem twice with different version requirements") - expect(err).to include("If you want to update the gem version, run `bundle update rack`.") + expect(err).to include("If you want to update the gem version, run `bundle update myrack`.") expect(err).not_to include("You may also need to change the version requirement specified in the Gemfile if it's too restrictive") end end @@ -306,8 +441,8 @@ RSpec.describe "bundle add" do it "caches all new dependencies added for the specified gem" do bundle :cache - bundle "add 'rack' --version=1.0.0" - expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + bundle "add 'myrack' --version=1.0.0" + expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist end end end diff --git a/spec/bundler/commands/binstubs_spec.rb b/spec/bundler/commands/binstubs_spec.rb index 6c3dc7bb2d..af4d24a9e8 100644 --- a/spec/bundler/commands/binstubs_spec.rb +++ b/spec/bundler/commands/binstubs_spec.rb @@ -4,44 +4,44 @@ RSpec.describe "bundle binstubs <gem>" do context "when the gem exists in the lockfile" do it "sets up the binstub" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G - bundle "binstubs rack" + bundle "binstubs myrack" - expect(bundled_app("bin/rackup")).to exist + expect(bundled_app("bin/myrackup")).to exist end it "does not install other binstubs" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" gem "rails" G bundle "binstubs rails" - expect(bundled_app("bin/rackup")).not_to exist + expect(bundled_app("bin/myrackup")).not_to exist expect(bundled_app("bin/rails")).to exist end it "does install multiple binstubs" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" gem "rails" G - bundle "binstubs rails rack" + bundle "binstubs rails myrack" - expect(bundled_app("bin/rackup")).to exist + expect(bundled_app("bin/myrackup")).to exist expect(bundled_app("bin/rails")).to exist end it "allows installing all binstubs" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rails" G @@ -53,20 +53,20 @@ RSpec.describe "bundle binstubs <gem>" do it "allows installing binstubs for all platforms" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G - bundle "binstubs rack --all-platforms" + bundle "binstubs myrack --all-platforms" - expect(bundled_app("bin/rackup")).to exist - expect(bundled_app("bin/rackup.cmd")).to exist + expect(bundled_app("bin/myrackup")).to exist + expect(bundled_app("bin/myrackup.cmd")).to exist end it "displays an error when used without any gem" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G bundle "binstubs", raise_on_error: false @@ -76,228 +76,15 @@ RSpec.describe "bundle binstubs <gem>" do it "displays an error when used with --all and gems" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G - bundle "binstubs rack", all: true, raise_on_error: false + bundle "binstubs myrack", all: true, raise_on_error: false expect(last_command).to be_failure 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 "#{file_uri_for(gem_repo1)}" - gem "rack" - G - - bundle "binstubs rack" - - File.open(bundled_app("bin/bundle"), "wb") do |file| - file.print "OMG" - end - - sys_exec "bin/rackup", 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 "rack", "1.2" do |s| - s.executables = "rackup" - 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 - specs = Gem.loaded_specs.values.reject {|s| s.default_gem? } - puts specs.map(&:full_name).sort.inspect - R - end - end - install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack" - gem "prints_loaded_gems" - G - bundle "binstubs bundler rack prints_loaded_gems" - end - - let(:system_bundler_version) { Bundler::VERSION } - - it "runs bundler" do - sys_exec "bin/bundle install", env: { "DEBUG" => "1" } - expect(out).to include %(Using bundler #{system_bundler_version}\n) - end - - context "when BUNDLER_VERSION is set" do - it "runs the correct version of bundler" do - sys_exec "bin/bundle install", env: { "BUNDLER_VERSION" => "999.999.999" }, raise_on_error: false - 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" - - sys_exec "bin/bundle install", env: { "BUNDLER_VERSION" => "999.999.998", "DEBUG" => "1" }, raise_on_error: false - expect(out).to include %(Using bundler 999.999.998\n) - end - end - - context "when a lockfile exists with a locked bundler version" do - context "and the version is newer" do - before do - lockfile lockfile.gsub(system_bundler_version, "999.999") - end - - it "runs the correct version of bundler" do - sys_exec "bin/bundle install", raise_on_error: false - expect(exitstatus).to eq(42) - expect(err).to include("Activating bundler (~> 999.999) failed:"). - and include("To install the version of bundler this project requires, run `gem install bundler -v '~> 999.999'`") - end - end - - context "and the version is newer when given `gems.rb` and `gems.locked`" do - before do - gemfile bundled_app("gems.rb"), gemfile - lockfile bundled_app("gems.locked"), lockfile.gsub(system_bundler_version, "999.999") - end - - it "runs the correct version of bundler" do - sys_exec "bin/bundle install", env: { "BUNDLE_GEMFILE" => "gems.rb" }, raise_on_error: false - - expect(exitstatus).to eq(42) - expect(err).to include("Activating bundler (~> 999.999) failed:"). - and include("To install the version of bundler this project requires, run `gem install bundler -v '~> 999.999'`") - end - end - - context "and the version is older and a different major" do - let(:system_bundler_version) { "55" } - - before do - lockfile lockfile.gsub(/BUNDLED WITH\n .*$/m, "BUNDLED WITH\n 44.0") - end - - it "runs the correct version of bundler" do - sys_exec "bin/bundle install", raise_on_error: false - expect(exitstatus).to eq(42) - expect(err).to include("Activating bundler (~> 44.0) failed:"). - and include("To install the version of bundler this project requires, run `gem install bundler -v '~> 44.0'`") - end - end - - context "and the version is older and a different major when given `gems.rb` and `gems.locked`" do - let(:system_bundler_version) { "55" } - - before do - gemfile bundled_app("gems.rb"), gemfile - lockfile bundled_app("gems.locked"), lockfile.gsub(/BUNDLED WITH\n .*$/m, "BUNDLED WITH\n 44.0") - end - - it "runs the correct version of bundler" do - sys_exec "bin/bundle install", env: { "BUNDLE_GEMFILE" => "gems.rb" }, raise_on_error: false - expect(exitstatus).to eq(42) - expect(err).to include("Activating bundler (~> 44.0) failed:"). - and include("To install the version of bundler this project requires, run `gem install bundler -v '~> 44.0'`") - end - end - - context "and the version is older and the same major" do - let(:system_bundler_version) { "2.999.999" } - - before do - lockfile lockfile.gsub(/BUNDLED WITH\n .*$/m, "BUNDLED WITH\n 2.3.0") - end - - it "installs and runs the exact version of bundler", rubygems: ">= 3.3.0.dev", realworld: true do - sys_exec "bin/bundle install --verbose", artifice: "vcr" - expect(exitstatus).not_to eq(42) - expect(out).to include("Bundler 2.999.999 is running, but your lockfile was generated with 2.3.0. Installing Bundler 2.3.0 and restarting using that version.") - expect(out).to include("Using bundler 2.3.0") - expect(err).not_to include("Activating bundler (~> 2.3.0) failed:") - end - - it "runs the available version of bundler", rubygems: "< 3.3.0.dev" do - sys_exec "bin/bundle install --verbose" - expect(exitstatus).not_to eq(42) - expect(out).not_to include("Bundler 2.999.999 is running, but your lockfile was generated with 2.3.0. Installing Bundler 2.3.0 and restarting using that version.") - expect(out).to include("Using bundler 2.999.999") - expect(err).not_to include("Activating bundler (~> 2.3.0) failed:") - end - end - - context "and the version is a pre-releaser" do - let(:system_bundler_version) { "55" } - - before do - lockfile lockfile.gsub(/BUNDLED WITH\n .*$/m, "BUNDLED WITH\n 2.12.0.a") - end - - it "runs the correct version of bundler when the version is a pre-release" do - sys_exec "bin/bundle install", raise_on_error: false - 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 - before { lockfile.gsub(system_bundler_version, "1.1.1") } - - it "calls through to the latest bundler version", :realworld do - sys_exec "bin/bundle update --bundler", env: { "DEBUG" => "1" } - using_bundler_line = /Using bundler ([\w\.]+)\n/.match(out) - expect(using_bundler_line).to_not be_nil - latest_version = using_bundler_line[1] - expect(Gem::Version.new(latest_version)).to be >= Gem::Version.new(system_bundler_version) - end - - it "calls through to the explicit bundler version" do - sys_exec "bin/bundle update --bundler=999.999.999", raise_on_error: false - 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 - sys_exec "bin/bundle install", env: { "DEBUG" => "1" } - 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}", "prints_loaded_gems-1.0", "rack-1.2"]) - 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/rackup").to_s, raise_on_error: false - expect(exitstatus).to eq(42) - expect(err).to include("Activating bundler (~> 999.999) failed:"). - and include("To install the version of bundler this project requires, run `gem install bundler -v '~> 999.999'`") - 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")) @@ -305,7 +92,7 @@ RSpec.describe "bundle binstubs <gem>" do s.executables = %w[foo] end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo")}" G @@ -321,7 +108,7 @@ RSpec.describe "bundle binstubs <gem>" do s.executables = %w[foo] end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :path => "#{lib_path("foo")}" G @@ -333,12 +120,12 @@ RSpec.describe "bundle binstubs <gem>" do it "sets correct permissions for binstubs" do with_umask(0o002) do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G - bundle "binstubs rack" - binary = bundled_app("bin/rackup") + bundle "binstubs myrack" + binary = bundled_app("bin/myrackup") expect(File.stat(binary).mode.to_s(8)).to eq(Gem.win_platform? ? "100644" : "100775") end end @@ -346,12 +133,12 @@ RSpec.describe "bundle binstubs <gem>" do context "when using --shebang" do it "sets the specified shebang for the binstub" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G - bundle "binstubs rack --shebang jruby" - expect(File.readlines(bundled_app("bin/rackup")).first).to eq("#!/usr/bin/env jruby\n") + bundle "binstubs myrack --shebang jruby" + expect(File.readlines(bundled_app("bin/myrackup")).first).to eq("#!/usr/bin/env jruby\n") end end end @@ -359,7 +146,7 @@ RSpec.describe "bundle binstubs <gem>" do context "when the gem doesn't exist" do it "displays an error with correct status" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G bundle "binstubs doesnt_exist", raise_on_error: false @@ -369,63 +156,53 @@ RSpec.describe "bundle binstubs <gem>" do end end - context "--path" do - it "sets the binstubs dir" do - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" - G - - bundle "binstubs rack --path exec" - - expect(bundled_app("exec/rackup")).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 "#{file_uri_for(gem_repo1)}" - gem "rack" - gem "rails" + source "https://gem.repo1" + gem "myrack" G - bundle "binstubs rack", path: "exec" - bundle :install + bundle "binstubs myrack" - expect(bundled_app("exec/rails")).to exist + expect(bundled_app("exec/myrackup")).to exist end end context "with --standalone option" do before do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" gem "rails" G end it "generates a standalone binstub" do - bundle "binstubs rack --standalone" - expect(bundled_app("bin/rackup")).to exist + bundle "binstubs myrack --standalone" + expect(bundled_app("bin/myrackup")).to exist end it "generates a binstub that does not depend on rubygems or bundler" do - bundle "binstubs rack --standalone" - expect(File.read(bundled_app("bin/rackup"))).to_not include("Gem.bin_path") + bundle "binstubs myrack --standalone" + 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 rack --standalone --path foo" - expect(bundled_app("foo/rackup")).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 it "generates standalone binstubs for all platforms" do - bundle "binstubs rack --standalone --all-platforms" - expect(bundled_app("bin/rackup")).to exist - expect(bundled_app("bin/rackup.cmd")).to exist + bundle "binstubs myrack --standalone --all-platforms" + expect(bundled_app("bin/myrackup")).to exist + expect(bundled_app("bin/myrackup.cmd")).to exist end end @@ -441,7 +218,7 @@ RSpec.describe "bundle binstubs <gem>" do context "when specified --all option" do it "generates standalone binstubs for all gems except bundler" do bundle "binstubs --standalone --all" - expect(bundled_app("bin/rackup")).to exist + expect(bundled_app("bin/myrackup")).to exist expect(bundled_app("bin/rails")).to exist expect(bundled_app("bin/bundle")).not_to exist expect(bundled_app("bin/bundler")).not_to exist @@ -453,39 +230,39 @@ RSpec.describe "bundle binstubs <gem>" do context "when the bin already exists" do it "doesn't overwrite and warns" do FileUtils.mkdir_p(bundled_app("bin")) - File.open(bundled_app("bin/rackup"), "wb") do |file| + File.open(bundled_app("bin/myrackup"), "wb") do |file| file.print "OMG" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G - bundle "binstubs rack" + bundle "binstubs myrack" - expect(bundled_app("bin/rackup")).to exist - expect(File.read(bundled_app("bin/rackup"))).to eq("OMG") - expect(err).to include("Skipped rackup") + expect(bundled_app("bin/myrackup")).to exist + expect(File.read(bundled_app("bin/myrackup"))).to eq("OMG") + expect(err).to include("Skipped myrackup") expect(err).to include("overwrite skipped stubs, use --force") end context "when using --force" do it "overwrites the binstub" do FileUtils.mkdir_p(bundled_app("bin")) - File.open(bundled_app("bin/rackup"), "wb") do |file| + File.open(bundled_app("bin/myrackup"), "wb") do |file| file.print "OMG" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G - bundle "binstubs rack --force" + bundle "binstubs myrack --force" - expect(bundled_app("bin/rackup")).to exist - expect(File.read(bundled_app("bin/rackup"))).not_to eq("OMG") + expect(bundled_app("bin/myrackup")).to exist + expect(File.read(bundled_app("bin/myrackup"))).not_to eq("OMG") end end end @@ -493,18 +270,18 @@ RSpec.describe "bundle binstubs <gem>" do context "when the gem has no bins" do it "suggests child gems if they have bins" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack-obama" + source "https://gem.repo1" + gem "myrack-obama" G - bundle "binstubs rack-obama" - expect(err).to include("rack-obama has no executables") - expect(err).to include("rack has: rackup") + bundle "binstubs myrack-obama" + expect(err).to include("myrack-obama has no executables") + expect(err).to include("myrack has: myrackup") end it "works if child gems don't have bins" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "actionpack" G @@ -520,7 +297,7 @@ RSpec.describe "bundle binstubs <gem>" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "with_development_dependency" G @@ -532,25 +309,25 @@ RSpec.describe "bundle binstubs <gem>" do context "when BUNDLE_INSTALL is specified" do it "performs an automatic bundle install" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G - bundle "config set auto_install 1" - bundle "binstubs rack" - expect(out).to include("Installing rack 1.0.0") - expect(the_bundle).to include_gems "rack 1.0.0" + 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" end it "does nothing when already up to date" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G - bundle "config set auto_install 1" - bundle "binstubs rack", env: { "BUNDLE_INSTALL" => "1" } - expect(out).not_to include("Installing rack 1.0.0") + bundle_config "auto_install 1" + bundle "binstubs myrack", env: { "BUNDLE_INSTALL" => "1" } + expect(out).not_to include("Installing myrack 1.0.0") end end end diff --git a/spec/bundler/commands/cache_spec.rb b/spec/bundler/commands/cache_spec.rb index 70e2c84961..e223d07f7f 100644 --- a/spec/bundler/commands/cache_spec.rb +++ b/spec/bundler/commands/cache_spec.rb @@ -3,8 +3,8 @@ RSpec.describe "bundle cache" do it "doesn't update the cache multiple times, even if it already exists" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G bundle :cache @@ -17,30 +17,30 @@ RSpec.describe "bundle cache" do context "with --gemfile" do it "finds the gemfile" do gemfile bundled_app("NotGemfile"), <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' G bundle "cache --gemfile=NotGemfile" ENV["BUNDLE_GEMFILE"] = "NotGemfile" - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" 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 - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' gem 'bundler' D - bundle "config set cache_all true" + bundle_config "cache_all true" bundle :cache - expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist expect(bundled_app("vendor/cache/bundler-0.9.gem")).to_not exist end end @@ -63,15 +63,15 @@ RSpec.describe "bundle cache" do it "caches all dependencies except bundler and the gemspec specified gem" do gemfile <<-D - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' gemspec D - bundle "config set cache_all true" + bundle_config "cache_all true" bundle :cache - expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist expect(bundled_app("vendor/cache/nokogiri-1.4.2.gem")).to exist expect(bundled_app("vendor/cache/mygem-0.1.1.gem")).to_not exist expect(bundled_app("vendor/cache/bundler-0.9.gem")).to_not exist @@ -95,15 +95,15 @@ RSpec.describe "bundle cache" do it "caches all dependencies except bundler and the gemspec specified gem" do gemfile <<-D - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' gemspec D - bundle "config set cache_all true" + bundle_config "cache_all true" bundle :cache - expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist expect(bundled_app("vendor/cache/nokogiri-1.4.2.gem")).to exist expect(bundled_app("vendor/cache/mygem-0.1.1.gem")).to_not exist expect(bundled_app("vendor/cache/bundler-0.9.gem")).to_not exist @@ -139,16 +139,16 @@ RSpec.describe "bundle cache" do it "caches all dependencies except bundler and the gemspec specified gems" do gemfile <<-D - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' gemspec :name => 'mygem' gemspec :name => 'mygem_test' D - bundle "config set cache_all true" + bundle_config "cache_all true" bundle :cache - expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist expect(bundled_app("vendor/cache/nokogiri-1.4.2.gem")).to exist expect(bundled_app("vendor/cache/weakling-0.0.3.gem")).to exist expect(bundled_app("vendor/cache/mygem-0.1.1.gem")).to_not exist @@ -158,77 +158,64 @@ RSpec.describe "bundle cache" do end end - context "with --path", bundler: "< 3" do - it "sets root directory for gems" do - gemfile <<-D - source "#{file_uri_for(gem_repo1)}" - gem 'rack' - D - - bundle "cache --path #{bundled_app("test")}" - - expect(the_bundle).to include_gems "rack 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 - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' D bundle "cache --no-install" - expect(the_bundle).not_to include_gems "rack 1.0.0" - expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + expect(the_bundle).not_to include_gems "myrack 1.0.0" + expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist end it "does not prevent installing gems with bundle install" do gemfile <<-D - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' D bundle "cache --no-install" bundle "install" - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "does not prevent installing gems with bundle update" do gemfile <<-D - source "#{file_uri_for(gem_repo1)}" - gem "rack", "1.0.0" + source "https://gem.repo1" + gem "myrack", "1.0.0" D bundle "cache --no-install" bundle "update --all" - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end 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 "#{file_uri_for(gem_repo1)}" - gem 'rack', :platforms => [:ruby_20, :windows_20] + source "https://gem.repo1" + gem 'myrack', :platforms => [:ruby_20, :windows_20] D bundle "cache --all-platforms" - expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + 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 "#{file_uri_for(gem_repo1)}" - gem 'rack', :platforms => [:ruby_20, :x64_mingw_20] + source "https://gem.repo1" + gem 'myrack', :platforms => [:ruby_20, :x64_mingw_20] D - bundle "cache --all-platforms" - expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + 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 it "does not attempt to install gems in without groups" do @@ -240,72 +227,146 @@ RSpec.describe "bundle cache" do end end - bundle "config set --local without wo" - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + bundle_config "without wo" + install_gemfile <<-G, artifice: "compact_index_extra_api" + source "https://main.repo" + gem "myrack" group :wo do gem "weakling" - gem "uninstallable", :source => "#{file_uri_for(gem_repo4)}" + gem "uninstallable", :source => "https://main.repo/extra" end G - bundle :cache, "all-platforms" => true + bundle :cache, "all-platforms" => true, artifice: "compact_index_extra_api" expect(bundled_app("vendor/cache/weakling-0.0.3.gem")).to exist expect(bundled_app("vendor/cache/uninstallable-2.0.gem")).to exist - expect(the_bundle).to include_gem "rack 1.0" + 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 :install - expect(the_bundle).to include_gem "rack 1.0" - expect(the_bundle).not_to include_gems "weakling", "uninstallable" + 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://my.gem.repo.1" - gem "rack" + source "https://gem.repo1" + gem "myrack" group :wo do gem "weakling" end G - bundle :lock, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s } - bundle :cache, "all-platforms" => true, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s } + bundle :lock + bundle :cache, "all-platforms" => true expect(bundled_app("vendor/cache/weakling-0.0.3.gem")).to exist end 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 "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G - bundle "install" + lockfile <<-L + GEM + remote: https://gem.repo1/ + specs: + myrack (1.0.0) + myrack-obama (1.0) + myrack + + PLATFORMS + #{lockfile_platforms} + + 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 - subject do - bundle "config set --local frozen true" - bundle :cache, raise_on_error: false + 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" + G + 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 "tries to install with frozen" do - bundle "config set deployment true" + 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 "#{file_uri_for(gem_repo1)}" - gem "rack" - gem "rack-obama" + source "https://gem.repo4" + gem "foo" + gem "bar" 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("* rack-obama") - bundle "env" - expect(out).to include("frozen").or include("deployment") + 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 @@ -315,12 +376,12 @@ RSpec.describe "bundle cache" do build_gem "racc", "2.0" do |s| s.add_dependency "rake" s.extensions << "Rakefile" - s.write "Rakefile", "task(:default) { puts 'INSTALLING rack' }" + s.write "Rakefile", "task(:default) { puts 'INSTALLING myrack' }" end end gemfile <<~G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "racc" G @@ -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 @@ -339,94 +400,215 @@ RSpec.describe "bundle install with gem sources" do it "does not hit the remote at all" do build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack" + source "https://gem.repo2" + gem "myrack" 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 "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "does not hit the remote at all in frozen mode" do build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack" + source "https://gem.repo2" + gem "myrack" + G + + bundle :cache + pristine_system_gems + FileUtils.rm_r gem_repo2 + + 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 - 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 "path vendor/bundle" bundle :install - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "does not hit the remote at all when cache_all_platforms configured" do build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack" + source "https://gem.repo2" + gem "myrack" 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 "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" + end + + it "uses cached gems for secondary sources when cache_all_platforms configured" do + build_repo4 do + build_gem "foo", "1.0.0" do |s| + s.platform = "x86_64-linux" + end + + build_gem "foo", "1.0.0" do |s| + s.platform = "arm64-darwin" + end + end + + gemfile <<~G + source "https://gem.repo2" + + source "https://gem.repo4" do + gem "foo" + end + G + + lockfile <<~L + GEM + remote: https://gem.repo2/ + specs: + + GEM + remote: https://gem.repo4/ + specs: + foo (1.0.0-x86_64-linux) + foo (1.0.0-arm64-darwin) + + PLATFORMS + arm64-darwin + ruby + x86_64-linux + + DEPENDENCIES + foo + + BUNDLED WITH + #{Bundler::VERSION} + L + + simulate_platform "x86_64-linux" do + 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 } + + # simulate removal of all remote gems + empty_repo4 + + # delete compact index cache + FileUtils.rm_r home(".bundle/cache/compact_index") + + bundle "install", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + + expect(the_bundle).to include_gems "foo 1.0.0 x86_64-linux" + end end it "does not reinstall already-installed gems" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G bundle :cache - build_gem "rack", "1.0.0", path: bundled_app("vendor/cache") do |s| - s.write "lib/rack.rb", "raise 'omg'" + build_gem "myrack", "1.0.0", path: bundled_app("vendor/cache") do |s| + s.write "lib/myrack.rb", "raise 'omg'" end bundle :install expect(err).to be_empty - expect(the_bundle).to include_gems "rack 1.0" + expect(the_bundle).to include_gems "myrack 1.0" end it "ignores cached gems for the wrong platform" do simulate_platform "java" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "platform_specific" G 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "platform_specific" G - run "require 'platform_specific' ; puts PLATFORM_SPECIFIC" - expect(out).to eq("1.0.0 RUBY") + 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 "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G bundled_app("vendor/cache").mkpath expect(bundled_app("vendor/cache").children).to be_empty diff --git a/spec/bundler/commands/check_spec.rb b/spec/bundler/commands/check_spec.rb index 02f9bb5b7a..7fe6897ae3 100644 --- a/spec/bundler/commands/check_spec.rb +++ b/spec/bundler/commands/check_spec.rb @@ -3,7 +3,7 @@ RSpec.describe "bundle check" do it "returns success when the Gemfile is satisfied" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rails" G @@ -13,7 +13,7 @@ RSpec.describe "bundle check" do it "works with the --gemfile flag when not in the directory" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rails" G @@ -23,7 +23,7 @@ RSpec.describe "bundle check" do it "creates a Gemfile.lock by default if one does not exist" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rails" G @@ -36,7 +36,7 @@ RSpec.describe "bundle check" do it "does not create a Gemfile.lock if --dry-run was passed" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rails" G @@ -47,26 +47,57 @@ RSpec.describe "bundle check" do expect(bundled_app_lock).not_to exist end - it "prints a generic error if the missing gems are unresolvable" do - system_gems ["rails-2.3.2"] + it "prints an error that shows missing gems" do + system_gems ["rails-2.3.2"], path: default_bundle_path gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rails" G bundle :check, raise_on_error: false - expect(err).to include("Bundler can't satisfy your Gemfile's dependencies.") + expect(err).to include("The following gems are missing") + 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)") + expect(err).to include(" * activeresource (2.3.2)") + expect(err).to include(" * activesupport (2.3.2)") + expect(err).to include("Install missing gems with `bundle install`") end - it "prints a generic error if a Gemfile.lock does not exist and a toplevel dependency does not exist" do + it "prints an error that shows missing gems if a Gemfile.lock does not exist and a toplevel dependency is missing" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rails" G bundle :check, raise_on_error: false 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 (#{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)") + expect(err).to include(" * activeresource (2.3.2)") + expect(err).to include(" * activesupport (2.3.2)") + expect(err).to include("Install missing gems with `bundle install`") + end + + 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" + + install_gemfile <<-G + source "https://gem.repo1" + gem "foo", git: "#{lib_path("foo")}" + G + + 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.") end @@ -78,12 +109,12 @@ RSpec.describe "bundle check" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem 'rails' G gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "rails" gem "rails_pinned_to_old_activesupport" G @@ -92,25 +123,12 @@ 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 "#{file_uri_for(gem_repo1)}" - group :foo do - gem "rack" - 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" group :foo do - gem "rack" + gem "myrack" end G @@ -120,63 +138,63 @@ RSpec.describe "bundle check" do it "ensures that gems are actually installed and not just cached" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :group => :foo + source "https://gem.repo1" + gem "myrack", :group => :foo G - bundle "config set --local without foo" + bundle_config "without foo" bundle :install gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G bundle "check", raise_on_error: false - expect(err).to include("* rack (1.0.0)") + expect(err).to include("* myrack (1.0.0)") expect(exitstatus).to eq(1) end it "ensures that gems are actually installed and not just cached in applications' cache" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G - bundle "config set --local path vendor/bundle" + bundle_config "path vendor/bundle" bundle :cache - gem_command "uninstall rack", 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("* rack (1.0.0)") + expect(err).to include("* myrack (1.0.0)") expect(exitstatus).to eq(1) end it "ignores missing gems restricted to other platforms" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" platforms :#{not_local_tag} do gem "activesupport" end G - system_gems "rack-1.0.0", path: default_bundle_path + system_gems "myrack-1.0.0", path: default_bundle_path lockfile <<-G GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: activesupport (2.3.5) - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{generic_local_platform} #{not_local} DEPENDENCIES - rack + myrack activesupport G @@ -186,28 +204,28 @@ RSpec.describe "bundle check" do it "works with env conditionals" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" env :NOT_GOING_TO_BE_SET do gem "activesupport" end G - system_gems "rack-1.0.0", path: default_bundle_path + system_gems "myrack-1.0.0", path: default_bundle_path lockfile <<-G GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: activesupport (2.3.5) - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{generic_local_platform} #{not_local} DEPENDENCIES - rack + myrack activesupport G @@ -227,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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo" G - bundle "config set --local deployment true" + bundle_config "deployment true" bundle "install" FileUtils.rm(bundled_app_lock) @@ -241,52 +259,12 @@ 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 "#{file_uri_for(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 "#{file_uri_for(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 "rack-1.0.0" + system_gems "myrack-1.0.0" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "1.0" + source "https://gem.repo1" + gem "myrack", "1.0" G end @@ -297,29 +275,30 @@ 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("* rack (1.0") + expect(err).to include("* myrack (1.0") end end describe "when locked with multiple dependents with different requirements" do before :each do build_repo4 do - build_gem "depends_on_rack" do |s| - s.add_dependency "rack", ">= 1.0" + build_gem "depends_on_myrack" do |s| + s.add_dependency "myrack", ">= 1.0" end - build_gem "also_depends_on_rack" do |s| - s.add_dependency "rack", "~> 1.0" + build_gem "also_depends_on_myrack" do |s| + s.add_dependency "myrack", "~> 1.0" end - build_gem "rack" + build_gem "myrack" end gemfile <<-G - source "#{file_uri_for(gem_repo4)}" - gem "depends_on_rack" - gem "also_depends_on_rack" + source "https://gem.repo4" + gem "depends_on_myrack" + gem "also_depends_on_myrack" G bundle "lock" @@ -328,59 +307,59 @@ RSpec.describe "bundle check" do it "shows what is missing with the current Gemfile without duplications" do bundle :check, raise_on_error: false expect(err).to match(/The following gems are missing/) - expect(err).to include("* rack (1.0").once + expect(err).to include("* myrack (1.0").once end end describe "when locked under multiple platforms" do before :each do build_repo4 do - build_gem "rack" + build_gem "myrack" end gemfile <<-G - source "#{file_uri_for(gem_repo4)}" - gem "rack" + source "https://gem.repo4" + gem "myrack" G lockfile <<-L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: - rack (1.0) + myrack (1.0) PLATFORMS ruby #{local_platform} DEPENDENCIES - rack + myrack BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end it "shows what is missing with the current Gemfile without duplications" do bundle :check, raise_on_error: false expect(err).to match(/The following gems are missing/) - expect(err).to include("* rack (1.0").once + expect(err).to include("* myrack (1.0").once end end describe "when using only scoped rubygems sources" do before do gemfile <<~G - source "#{file_uri_for(gem_repo2)}" - source "#{file_uri_for(gem_repo1)}" do - gem "rack" + source "https://gem.repo2" + source "https://gem.repo1" do + gem "myrack" end G end it "returns success when the Gemfile is satisfied" do - system_gems "rack-1.0.0", path: default_bundle_path - bundle :check + system_gems "myrack-1.0.0", path: default_bundle_path + bundle :check, artifice: "compact_index" expect(out).to include("The Gemfile's dependencies are satisfied") end end @@ -388,51 +367,51 @@ RSpec.describe "bundle check" do describe "when using only scoped rubygems sources with indirect dependencies" do before do build_repo4 do - build_gem "depends_on_rack" do |s| - s.add_dependency "rack" + build_gem "depends_on_myrack" do |s| + s.add_dependency "myrack" end - build_gem "rack" + build_gem "myrack" end gemfile <<~G - source "#{file_uri_for(gem_repo1)}" - source "#{file_uri_for(gem_repo4)}" do - gem "depends_on_rack" + source "https://gem.repo1" + source "https://gem.repo4" do + gem "depends_on_myrack" end G end it "returns success when the Gemfile is satisfied and generates a correct lockfile" do - system_gems "depends_on_rack-1.0", "rack-1.0", gem_repo: gem_repo4, path: default_bundle_path - bundle :check + system_gems "depends_on_myrack-1.0", "myrack-1.0", gem_repo: gem_repo4, path: default_bundle_path + bundle :check, artifice: "compact_index" - checksums = checksums_section_when_existing do |c| - c.no_checksum "depends_on_rack", "1.0" - c.no_checksum "rack", "1.0" + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "depends_on_myrack", "1.0" + c.checksum gem_repo4, "myrack", "1.0" end expect(out).to include("The Gemfile's dependencies are satisfied") expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: - depends_on_rack (1.0) - rack - rack (1.0) + depends_on_myrack (1.0) + myrack + myrack (1.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - depends_on_rack! + depends_on_myrack! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -447,7 +426,7 @@ RSpec.describe "bundle check" do build_gem "dex-dispatch-engine" end - build_lib("bundle-check-issue", path: tmp.join("bundle-check-issue")) do |s| + build_lib("bundle-check-issue", path: tmp("bundle-check-issue")) do |s| s.write "Gemfile", <<-G source "https://localgemserver.test" @@ -461,22 +440,24 @@ RSpec.describe "bundle check" do s.add_dependency "awesome_print" end - bundle "install", artifice: "compact_index_extra", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }, dir: tmp.join("bundle-check-issue") + bundle "install", artifice: "compact_index_extra", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }, dir: tmp("bundle-check-issue") end it "does not corrupt lockfile when changing version" do - version_file = tmp.join("bundle-check-issue/bundle-check-issue.gemspec") + version_file = tmp("bundle-check-issue/bundle-check-issue.gemspec") File.write(version_file, File.read(version_file).gsub(/s\.version = .+/, "s.version = '9999'")) - bundle "check --verbose", dir: tmp.join("bundle-check-issue") + bundle "check --verbose", dir: tmp("bundle-check-issue") - checksums = checksums_section_when_existing do |c| + lockfile = File.read(tmp("bundle-check-issue/Gemfile.lock")) + + checksums = checksums_section_when_enabled(lockfile) do |c| c.checksum gem_repo4, "awesome_print", "1.0" c.no_checksum "bundle-check-issue", "9999" c.checksum gem_repo2, "dex-dispatch-engine", "1.0" end - expect(File.read(tmp.join("bundle-check-issue/Gemfile.lock"))).to eq <<~L + expect(lockfile).to eq <<~L PATH remote: . specs: @@ -501,24 +482,78 @@ RSpec.describe "bundle check" do dex-dispatch-engine! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end + context "with scoped and unscoped sources" do + it "does not corrupt lockfile" do + build_repo2 do + build_gem "foo" + build_gem "wadus" + build_gem("baz") {|s| s.add_dependency "wadus" } + end + + build_repo4 do + build_gem "bar" + end + + bundle_config "path.system true" + + # Add all gems to ensure all gems are installed so that a bundle check + # would be successful + install_gemfile(<<-G, artifice: "compact_index_extra") + source "https://gem.repo2" + + source "https://gem.repo4" do + gem "bar" + end + + gem "foo" + gem "baz" + G + + original_lockfile = lockfile + + # Remove "baz" gem from the Gemfile, and bundle install again to generate + # a functional lockfile with no "baz" dependency or "wadus" transitive + # dependency + install_gemfile(<<-G, artifice: "compact_index_extra") + source "https://gem.repo2" + + source "https://gem.repo4" do + gem "bar" + end + + gem "foo" + G + + # Add back "baz" gem back to the Gemfile, but _crucially_ we do not perform a + # bundle install + gemfile(gemfile + 'gem "baz"') + + bundle :check, verbose: true + + # Bundle check should succeed and restore the lockfile to its original + # state + expect(lockfile).to eq(original_lockfile) + end + end + describe "BUNDLED WITH" do def lock_with(bundler_version = nil) lock = <<~L GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack + myrack L if bundler_version @@ -529,11 +564,11 @@ RSpec.describe "bundle check" do end before do - bundle "config set --local path vendor/bundle" + bundle_config "path vendor/bundle" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G end diff --git a/spec/bundler/commands/clean_spec.rb b/spec/bundler/commands/clean_spec.rb index 0b559a87c8..c77859d378 100644 --- a/spec/bundler/commands/clean_spec.rb +++ b/spec/bundler/commands/clean_spec.rb @@ -19,18 +19,18 @@ RSpec.describe "bundle clean" do it "removes unused gems that are different" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "thin" 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 - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "thin" G @@ -40,95 +40,95 @@ RSpec.describe "bundle clean" do expect(out).to include("Removing foo (1.0)") - should_have_gems "thin-1.0", "rack-1.0.0" + should_have_gems "thin-1.0", "myrack-1.0.0" should_not_have_gems "foo-1.0" - expect(vendored_gems("bin/rackup")).to exist + expect(vendored_gems("bin/myrackup")).to exist end it "removes old version of gem if unused" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack", "0.9.1" + gem "myrack", "0.9.1" 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 - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack", "1.0.0" + gem "myrack", "1.0.0" gem "foo" G bundle "install" bundle :clean - expect(out).to include("Removing rack (0.9.1)") + expect(out).to include("Removing myrack (0.9.1)") - should_have_gems "foo-1.0", "rack-1.0.0" - should_not_have_gems "rack-0.9.1" + should_have_gems "foo-1.0", "myrack-1.0.0" + should_not_have_gems "myrack-0.9.1" - expect(vendored_gems("bin/rackup")).to exist + expect(vendored_gems("bin/myrackup")).to exist end it "removes new version of gem if unused" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack", "1.0.0" + gem "myrack", "1.0.0" 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 - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack", "0.9.1" + gem "myrack", "0.9.1" gem "foo" G - bundle "update rack" + bundle "update myrack" bundle :clean - expect(out).to include("Removing rack (1.0.0)") + expect(out).to include("Removing myrack (1.0.0)") - should_have_gems "foo-1.0", "rack-0.9.1" - should_not_have_gems "rack-1.0.0" + should_have_gems "foo-1.0", "myrack-0.9.1" + should_not_have_gems "myrack-1.0.0" - expect(vendored_gems("bin/rackup")).to exist + expect(vendored_gems("bin/myrackup")).to exist end it "removes gems in bundle without groups" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo" group :test_group do - gem "rack", "1.0.0" + gem "myrack", "1.0.0" 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 - expect(out).to include("Removing rack (1.0.0)") + expect(out).to include("Removing myrack (1.0.0)") should_have_gems "foo-1.0" - should_not_have_gems "rack-1.0.0" + should_not_have_gems "myrack-1.0.0" - expect(vendored_gems("bin/rackup")).to_not exist + expect(vendored_gems("bin/myrackup")).to_not exist end it "does not remove cached git dir if it's being used" do @@ -137,21 +137,21 @@ RSpec.describe "bundle clean" do git_path = lib_path("foo-1.0") gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack", "1.0.0" + gem "myrack", "1.0.0" git "#{git_path}", :ref => "#{revision}" do gem "foo" 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 @@ -161,21 +161,21 @@ RSpec.describe "bundle clean" do revision = revision_for(git_path) gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack", "1.0.0" + gem "myrack", "1.0.0" git "#{git_path}", :ref => "#{revision}" do gem "foo" end G - bundle "config set path vendor/bundle" + bundle_config "path vendor/bundle" bundle "install" gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack", "1.0.0" + gem "myrack", "1.0.0" G bundle "install" @@ -183,14 +183,14 @@ RSpec.describe "bundle clean" do expect(out).to include("Removing foo (#{revision[0..11]})") - expect(vendored_gems("gems/rack-1.0.0")).to exist + expect(vendored_gems("gems/myrack-1.0.0")).to exist expect(vendored_gems("bundler/gems/foo-#{revision[0..11]}")).not_to exist digest = Digest(:SHA1).hexdigest(git_path.to_s) expect(vendored_gems("cache/bundler/git/foo-#{digest}")).not_to exist - expect(vendored_gems("specifications/rack-1.0.0.gemspec")).to exist + expect(vendored_gems("specifications/myrack-1.0.0.gemspec")).to exist - expect(vendored_gems("bin/rackup")).to exist + expect(vendored_gems("bin/myrackup")).to exist end it "keeps used git gems even if installed to a symlinked location" do @@ -199,9 +199,9 @@ RSpec.describe "bundle clean" do revision = revision_for(git_path) gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack", "1.0.0" + gem "myrack", "1.0.0" git "#{git_path}", :ref => "#{revision}" do gem "foo" end @@ -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,20 +220,20 @@ 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")) gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack", "1.0.0" + gem "myrack", "1.0.0" git "#{lib_path("foo-bar")}" do gem "foo-bar" end G - bundle "config set path vendor/bundle" + bundle_config "path vendor/bundle" bundle "install" update_git "foo-bar", path: lib_path("foo-bar") @@ -244,13 +244,13 @@ RSpec.describe "bundle clean" do expect(out).to include("Removing foo-bar (#{revision[0..11]})") - expect(vendored_gems("gems/rack-1.0.0")).to exist + expect(vendored_gems("gems/myrack-1.0.0")).to exist expect(vendored_gems("bundler/gems/foo-bar-#{revision[0..11]}")).not_to exist expect(vendored_gems("bundler/gems/foo-bar-#{revision2[0..11]}")).to exist - expect(vendored_gems("specifications/rack-1.0.0.gemspec")).to exist + expect(vendored_gems("specifications/myrack-1.0.0.gemspec")).to exist - expect(vendored_gems("bin/rackup")).to exist + expect(vendored_gems("bin/myrackup")).to exist end it "does not remove nested gems in a git repo" do @@ -261,11 +261,11 @@ RSpec.describe "bundle clean" do revision = revision_for(lib_path("rails")) gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" 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("") @@ -279,17 +279,17 @@ RSpec.describe "bundle clean" do revision = revision_for(git_path) gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack", "1.0.0" + gem "myrack", "1.0.0" group :test do git "#{git_path}", :ref => "#{revision}" do gem "foo" 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 @@ -302,28 +302,28 @@ RSpec.describe "bundle clean" do it "does not blow up when using without groups" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack" + gem "myrack" group :development do gem "foo" 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack", "1.0.0" + gem "myrack", "1.0.0" G bundle :clean, raise_on_error: false @@ -335,87 +335,62 @@ RSpec.describe "bundle clean" do # handling bundle clean upgrade path from the pre's it "removes .gem/.gemspec file even if there's no corresponding gem dir" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "thin" gem "foo" G - bundle "config set path vendor/bundle" + bundle_config "path vendor/bundle" bundle "install" gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo" G bundle "install" - FileUtils.rm(vendored_gems("bin/rackup")) - FileUtils.rm_rf(vendored_gems("gems/thin-1.0")) - FileUtils.rm_rf(vendored_gems("gems/rack-1.0.0")) + FileUtils.rm(vendored_gems("bin/myrackup")) + FileUtils.rm_r(vendored_gems("gems/thin-1.0")) + FileUtils.rm_r(vendored_gems("gems/myrack-1.0.0")) bundle :clean - should_not_have_gems "thin-1.0", "rack-1.0" + should_not_have_gems "thin-1.0", "myrack-1.0" should_have_gems "foo-1.0" - expect(vendored_gems("bin/rackup")).not_to exist + expect(vendored_gems("bin/myrackup")).not_to exist 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "thin" - gem "rack" + gem "myrack" G install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack" + gem "myrack" G - gem_command :list - expect(out).to include("rack (1.0.0)").and include("thin (1.0)") + 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 "#{file_uri_for(gem_repo1)}" - - gem "thin" - gem "rack" - G - bundle "config set path vendor/bundle" - bundle "config set clean false" - bundle "install --clean true" - - gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - - gem "rack" - G - bundle "install" - - should_have_gems "rack-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 - source "#{file_uri_for(gem_repo2)}" + 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,15 +398,31 @@ 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 - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "foo" 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,35 +442,35 @@ 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "thin" - gem "rack" + gem "myrack" G - bundle "config set path vendor/bundle" + bundle_config "path vendor/bundle" bundle "install" gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack" + gem "myrack" G bundle "install" - should_have_gems "rack-1.0.0", "thin-1.0" + 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 - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "foo" G - bundle "config set path vendor/bundle" + bundle_config "path vendor/bundle" bundle "install" update_repo2 do @@ -490,13 +481,13 @@ 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 gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "foo" G @@ -507,33 +498,33 @@ 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo" - gem "rack" + gem "myrack" G bundle :install gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack" + gem "myrack" G bundle :install 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("rack (1.0.0)") + expect(out).to include("myrack (1.0.0)") end describe "when missing permissions", :permissions do @@ -544,17 +535,17 @@ RSpec.describe "bundle clean" do end it "returns a helpful error message" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo" - gem "rack" + gem "myrack" G bundle :install gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack" + gem "myrack" G bundle :install @@ -565,9 +556,9 @@ 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("rack (1.0.0)") + expect(out).to include("myrack (1.0.0)") end end @@ -576,12 +567,12 @@ RSpec.describe "bundle clean" do revision = revision_for(lib_path("foo-1.0")) gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + 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" # 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| @@ -612,7 +603,7 @@ RSpec.describe "bundle clean" do end gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "bindir" G @@ -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,10 +625,8 @@ 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 "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" G bundle "clean --force", env: { "BUNDLER_GEM_DEFAULT_DIR" => system_gem_path.to_s } @@ -654,31 +643,31 @@ RSpec.describe "bundle clean" do end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo" gem "bar", "1.0", :path => "#{relative_path}" G - bundle "config set path vendor/bundle" + bundle_config "path vendor/bundle" bundle "install" bundle :clean end it "doesn't remove gems in dry-run mode with path set" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "thin" 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 - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "thin" G @@ -690,25 +679,25 @@ RSpec.describe "bundle clean" do expect(out).not_to include("Removing foo (1.0)") expect(out).to include("Would have removed foo (1.0)") - should_have_gems "thin-1.0", "rack-1.0.0", "foo-1.0" + should_have_gems "thin-1.0", "myrack-1.0.0", "foo-1.0" - expect(vendored_gems("bin/rackup")).to exist + expect(vendored_gems("bin/myrackup")).to exist end it "doesn't remove gems in dry-run mode with no path set" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "thin" 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 - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "thin" G @@ -720,26 +709,26 @@ RSpec.describe "bundle clean" do expect(out).not_to include("Removing foo (1.0)") expect(out).to include("Would have removed foo (1.0)") - should_have_gems "thin-1.0", "rack-1.0.0", "foo-1.0" + should_have_gems "thin-1.0", "myrack-1.0.0", "foo-1.0" - expect(vendored_gems("bin/rackup")).to exist + expect(vendored_gems("bin/myrackup")).to exist end it "doesn't store dry run as a config setting" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "thin" 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "thin" G @@ -751,35 +740,35 @@ RSpec.describe "bundle clean" do expect(out).to include("Removing foo (1.0)") expect(out).not_to include("Would have removed foo (1.0)") - should_have_gems "thin-1.0", "rack-1.0.0" + should_have_gems "thin-1.0", "myrack-1.0.0" should_not_have_gems "foo-1.0" - expect(vendored_gems("bin/rackup")).to exist + expect(vendored_gems("bin/myrackup")).to exist end it "performs an automatic bundle install" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "thin" 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 - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "thin" 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", "rack-1.0.0", "weakling-0.0.3" + should_have_gems "thin-1.0", "myrack-1.0.0", "weakling-0.0.3" should_not_have_gems "foo-1.0" end @@ -789,12 +778,12 @@ RSpec.describe "bundle clean" do revision = revision_for(lib_path("very_simple_git_binary-1.0")) gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" 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 @@ -808,14 +797,14 @@ RSpec.describe "bundle clean" do it "removes extension directories" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "thin" gem "very_simple_binary" gem "simple_binary" G - bundle "config set path vendor/bundle" + bundle_config "path vendor/bundle" bundle "install" very_simple_binary_extensions_dir = @@ -828,7 +817,7 @@ RSpec.describe "bundle clean" do expect(simple_binary_extensions_dir).to exist gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "thin" gem "simple_binary" @@ -849,13 +838,13 @@ RSpec.describe "bundle clean" do short_revision = revision[0..11] gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "thin" 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 = @@ -864,7 +853,7 @@ RSpec.describe "bundle clean" do expect(very_simple_binary_extensions_dir).to exist gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "very_simple_git_binary", :git => "#{lib_path("very_simple_git_binary-1.0")}", :ref => "#{revision}" G @@ -874,7 +863,7 @@ RSpec.describe "bundle clean" do expect(very_simple_binary_extensions_dir).to exist gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G bundle "install" @@ -891,7 +880,7 @@ RSpec.describe "bundle clean" do short_revision = revision[0..11] gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" group :development do gem "very_simple_git_binary", :git => "#{lib_path("very_simple_git_binary-1.0")}", :ref => "#{revision}" @@ -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 547fd2d869..e8ab32ca5d 100644 --- a/spec/bundler/commands/config_spec.rb +++ b/spec/bundler/commands/config_spec.rb @@ -38,8 +38,8 @@ RSpec.describe ".bundle/config" do describe "location with a gemfile" do before :each do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "1.0.0" + source "https://gem.repo1" + gem "myrack", "1.0.0" G end @@ -56,7 +56,7 @@ RSpec.describe ".bundle/config" do expect(bundled_app(".bundle")).not_to exist expect(tmp("foo/bar/config")).to exist - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "can provide a relative path with the environment variable" do @@ -68,7 +68,7 @@ RSpec.describe ".bundle/config" do expect(bundled_app(".bundle")).not_to exist expect(bundled_app("../foo/config")).to exist - expect(the_bundle).to include_gems "rack 1.0.0", dir: bundled_app("omg") + expect(the_bundle).to include_gems "myrack 1.0.0", dir: bundled_app("omg") end end @@ -79,6 +79,14 @@ RSpec.describe ".bundle/config" do expect(home(".bundle/config")).to exist end + it "does not list global settings as local" do + bundle "config set --global foo bar" + bundle "config list", dir: home + + expect(out).to include("for the current user") + expect(out).not_to include("for your local app") + end + it "works with an absolute path" do ENV["BUNDLE_APP_CONFIG"] = tmp("foo/bar").to_s bundle "config set --local path vendor/bundle" @@ -115,8 +123,8 @@ RSpec.describe ".bundle/config" do describe "global" do before(:each) do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "1.0.0" + source "https://gem.repo1" + gem "myrack", "1.0.0" G end @@ -207,8 +215,8 @@ RSpec.describe ".bundle/config" do describe "local" do before(:each) do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "1.0.0" + source "https://gem.repo1" + gem "myrack", "1.0.0" G end @@ -263,8 +271,8 @@ RSpec.describe ".bundle/config" do describe "env" do before(:each) do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "1.0.0" + source "https://gem.repo1" + gem "myrack", "1.0.0" G end @@ -305,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 @@ -336,8 +345,8 @@ RSpec.describe ".bundle/config" do describe "gem mirrors" do before(:each) do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "1.0.0" + source "https://gem.repo1" + gem "myrack", "1.0.0" G end @@ -350,10 +359,16 @@ end E expect(out).to eq("http://gems.example.org/ => http://gem-mirror.example.org/") end + + it "allows configuring fallback timeout for each mirror, and does not duplicate configs", rubygems: ">= 3.5.12" do + bundle "config set --global mirror.https://rubygems.org.fallback_timeout 1" + bundle "config set --global mirror.https://rubygems.org.fallback_timeout 2" + expect(File.read(home(".bundle/config"))).to include("BUNDLE_MIRROR").once + end end describe "quoting" do - before(:each) { gemfile "source \"#{file_uri_for(gem_repo1)}\"" } + before(:each) { gemfile "source 'https://gem.repo1'" } let(:long_string) do "--with-xml2-include=/usr/pkg/include/libxml2 --with-xml2-lib=/usr/pkg/lib " \ "--with-xslt-dir=/usr/pkg" @@ -403,8 +418,8 @@ E describe "very long lines" do before(:each) do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "1.0.0" + source "https://gem.repo1" + gem "myrack", "1.0.0" G end @@ -439,7 +454,7 @@ E it "does not make bundler crash and ignores the configuration" do bundle "config list --parseable" - expect(out).to eq("#mirror.https://rails-assets.org/=http://localhost:9292") + expect(out).to be_empty expect(err).to be_empty ruby(<<~RUBY) @@ -487,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" @@ -533,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" @@ -543,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" @@ -553,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 @@ -563,11 +582,41 @@ 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 - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' G bundle "config set --local gemfile #{bundled_app("NotGemfile")}" @@ -578,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 a41432b88a..a44f607546 100644 --- a/spec/bundler/commands/console_spec.rb +++ b/spec/bundler/commands/console_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe "bundle console", bundler: "< 3", readline: true do +RSpec.describe "bundle console", readline: true do before :each do build_repo2 do # A minimal fake pry console @@ -35,107 +35,180 @@ RSpec.describe "bundle console", bundler: "< 3", readline: true do end RUBY end - end - install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack" - gem "activesupport", :group => :test - gem "rack_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 RACK") - 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 "#{file_uri_for(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 RACK") + 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 "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 other groups" do - bundle "console test" do |input, _, _| - input.puts("puts RACK_MIDDLEWARE") + 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 "#{file_uri_for(gem_repo2)}" - gem "rack" - gem "activesupport", :group => :test - gem "rack_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 666b23a141..d350b4b3d1 100644 --- a/spec/bundler/commands/doctor_spec.rb +++ b/spec/bundler/commands/doctor_spec.rb @@ -4,17 +4,18 @@ require "find" require "stringio" require "bundler/cli" require "bundler/cli/doctor" +require "bundler/cli/doctor/diagnose" RSpec.describe "bundle doctor" do before(:each) do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G @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,29 +48,37 @@ 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({}) - expect(doctor).to receive(:bundles_for_gem).exactly(2).times.and_return ["/path/to/rack/rack.bundle"] + 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({}) - expect(doctor).to receive(:bundles_for_gem).exactly(2).times.and_return ["/path/to/rack/rack.bundle"] + 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 - * rack: /usr/local/opt/icu4c/lib/libicui18n.57.1.dylib + * myrack: /usr/local/opt/icu4c/lib/libicui18n.57.1.dylib E end end @@ -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 d59b690d2f..aa35685be8 100644 --- a/spec/bundler/commands/exec_spec.rb +++ b/spec/bundler/commands/exec_spec.rb @@ -1,145 +1,126 @@ # frozen_string_literal: true RSpec.describe "bundle exec" do - let(:system_gems_to_install) { %w[rack-1.0.0 rack-0.9.1] } - it "works with --gemfile flag" do - system_gems(system_gems_to_install, path: default_bundle_path) + system_gems(%w[myrack-1.0.0 myrack-0.9.1], path: default_bundle_path) - create_file "CustomGemfile", <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "1.0.0" + gemfile "CustomGemfile", <<-G + source "https://gem.repo1" + gem "myrack", "1.0.0" G - bundle "exec --gemfile CustomGemfile rackup" + bundle "exec --gemfile CustomGemfile myrackup" expect(out).to eq("1.0.0") end it "activates the correct gem" do - system_gems(system_gems_to_install, path: default_bundle_path) + system_gems(%w[myrack-1.0.0 myrack-0.9.1], path: default_bundle_path) gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "0.9.1" + source "https://gem.repo1" + gem "myrack", "0.9.1" G - bundle "exec rackup" + bundle "exec myrackup" expect(out).to eq("0.9.1") end it "works and prints no warnings when HOME is not writable" do - system_gems(system_gems_to_install, path: default_bundle_path) + system_gems(%w[myrack-1.0.0 myrack-0.9.1], path: default_bundle_path) gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "0.9.1" + source "https://gem.repo1" + gem "myrack", "0.9.1" G - bundle "exec rackup", env: { "HOME" => "/" } + bundle "exec myrackup", env: { "HOME" => "/" } expect(out).to eq("0.9.1") expect(err).to be_empty end it "works when the bins are in ~/.bundle" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G - bundle "exec rackup" + bundle "exec myrackup" expect(out).to eq("1.0.0") end it "works when running from a random directory" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G - bundle "exec 'cd #{tmp("gems")} && rackup'" + bundle "exec 'cd #{tmp("gems")} && myrackup'" expect(out).to eq("1.0.0") end it "works when exec'ing something else" do - install_gemfile "source \"#{file_uri_for(gem_repo1)}\"; gem \"rack\"" + install_gemfile "source \"https://gem.repo1\"; gem \"myrack\"" bundle "exec echo exec" expect(out).to eq("exec") end it "works when exec'ing to ruby" do - install_gemfile "source \"#{file_uri_for(gem_repo1)}\"; gem \"rack\"" + install_gemfile "source \"https://gem.repo1\"; gem \"myrack\"" bundle "exec ruby -e 'puts %{hi}'" expect(out).to eq("hi") end it "works when exec'ing to rubygems" do - install_gemfile "source \"#{file_uri_for(gem_repo1)}\"; gem \"rack\"" + install_gemfile "source \"https://gem.repo1\"; gem \"myrack\"" bundle "exec #{gem_cmd} --version" expect(out).to eq(Gem::VERSION) end it "works when exec'ing to rubygems through sh -c" do - install_gemfile "source \"#{file_uri_for(gem_repo1)}\"; gem \"rack\"" + install_gemfile "source \"https://gem.repo1\"; gem \"myrack\"" bundle "exec sh -c '#{gem_cmd} --version'" expect(out).to eq(Gem::VERSION) end - it "works when exec'ing back to bundler with a lockfile that doesn't include the current platform" do + it "works when exec'ing back to bundler to run a remote resolve" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "0.9.1" + source "https://gem.repo1" + gem "myrack", "0.9.1" G - # simulate lockfile generated with old version not including specific platform - lockfile <<-L - GEM - remote: #{file_uri_for(gem_repo1)}/ - specs: - rack (0.9.1) - - PLATFORMS - RUBY - - DEPENDENCIES - rack (= 0.9.1) + bundle "exec bundle lock", env: { "BUNDLER_VERSION" => Bundler::VERSION } - BUNDLED WITH - 2.1.4 - L - - bundle "exec bundle cache", env: { "BUNDLER_VERSION" => Bundler::VERSION } - - expect(out).to include("Updating files in vendor/cache") + expect(out).to include("Writing lockfile") 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") puts `ps -ocommand= -p#{$$}` RUBY - create_file "Gemfile", "source \"#{file_uri_for(gem_repo1)}\"" + gemfile "Gemfile", "source \"https://gem.repo1\"" create_file "a.rb", script_that_changes_its_own_title_and_checks_if_picked_up_by_ps_unix_utility bundle "exec ruby a.rb" expect(out).to eq("1-2-3-4-5-6-7") end it "accepts --verbose" do - install_gemfile "source \"#{file_uri_for(gem_repo1)}\"; gem \"rack\"" + install_gemfile "source \"https://gem.repo1\"; gem \"myrack\"" bundle "exec --verbose echo foobar" expect(out).to eq("foobar") end it "passes --verbose to command if it is given after the command" do - install_gemfile "source \"#{file_uri_for(gem_repo1)}\"; gem \"rack\"" + install_gemfile "source \"https://gem.repo1\"; gem \"myrack\"" bundle "exec echo --verbose" expect(out).to eq("--verbose") 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" @@ -157,24 +138,24 @@ RSpec.describe "bundle exec" do end G - install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" - sys_exec "#{Gem.ruby} #{command.path}" + install_gemfile "source \"https://gem.repo1\"" + in_bundled_app "#{Gem.ruby} #{command.path}" expect(out).to be_empty expect(err).to be_empty end it "accepts --keep-file-descriptors" do - install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" + install_gemfile "source \"https://gem.repo1\"" bundle "exec --keep-file-descriptors echo foobar" expect(err).to be_empty 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 \"#{file_uri_for(gem_repo1)}\"; gem \"rack\"" + install_gemfile "source \"https://gem.repo1\"; gem \"myrack\"" File.open(bundled_app("--verbose"), "w") do |f| f.puts "#!/bin/sh" f.puts "echo foobar" @@ -188,189 +169,182 @@ RSpec.describe "bundle exec" do it "handles different versions in different bundles" do build_repo2 do - build_gem "rack_two", "1.0.0" do |s| - s.executables = "rackup" + build_gem "myrack_two", "1.0.0" do |s| + s.executables = "myrackup" end end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "0.9.1" + source "https://gem.repo1" + gem "myrack", "0.9.1" G install_gemfile bundled_app2("Gemfile"), <<-G, dir: bundled_app2 - source "#{file_uri_for(gem_repo2)}" - gem "rack_two", "1.0.0" + source "https://gem.repo2" + gem "myrack_two", "1.0.0" G - bundle "exec rackup" + bundle "exec myrackup" expect(out).to eq("0.9.1") - bundle "exec rackup", dir: bundled_app2 + bundle "exec myrackup", dir: bundled_app2 expect(out).to eq("1.0.0") 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 \"#{file_uri_for(gem_repo1)}\"" + 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 "#{file_uri_for(gem_repo2)}" - gem "irb", "#{specified_irb_version}" + source "https://gem.repo2" + 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 "#{file_uri_for(gem_repo2)}" - gem "gem_depending_on_old_irb" + source "https://gem.repo2" + 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 it "warns about executable conflicts" do build_repo2 do - build_gem "rack_two", "1.0.0" do |s| - s.executables = "rackup" + build_gem "myrack_two", "1.0.0" do |s| + s.executables = "myrackup" end end - bundle "config set --global path.system true" + bundle_config_global "path.system true" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "0.9.1" + source "https://gem.repo1" + gem "myrack", "0.9.1" G install_gemfile bundled_app2("Gemfile"), <<-G, dir: bundled_app2 - source "#{file_uri_for(gem_repo2)}" - gem "rack_two", "1.0.0" + source "https://gem.repo2" + gem "myrack_two", "1.0.0" G - bundle "exec rackup" + bundle "exec myrackup" expect(last_command.stderr).to eq( - "Bundler is using a binstub that was created for a different gem (rack).\n" \ - "You should run `bundle binstub rack_two` to work around a system/bundle conflict." + "Bundler is using a binstub that was created for a different gem (myrack).\n" \ + "You should run `bundle binstub myrack_two` to work around a system/bundle conflict." ) end it "handles gems installed with --without" do - bundle "config set --local without middleware" + bundle_config "without middleware" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" # rack 0.9.1 and 1.0 exist + source "https://gem.repo1" + gem "myrack" # myrack 0.9.1 and 1.0 exist group :middleware do - gem "rack_middleware" # rack_middleware depends on rack 0.9.1 + gem "myrack_middleware" # myrack_middleware depends on myrack 0.9.1 end G - bundle "exec rackup" + bundle "exec myrackup" expect(out).to eq("0.9.1") - expect(the_bundle).not_to include_gems "rack_middleware 1.0" + expect(the_bundle).not_to include_gems "myrack_middleware 1.0" end it "does not duplicate already exec'ed RUBYOPT" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? - + create_file("echoopt", "#!/usr/bin/env ruby\nprint ENV['RUBYOPT']") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G bundler_setup_opt = "-r#{lib_dir}/bundler/setup" rubyopt = opt_add(bundler_setup_opt, ENV["RUBYOPT"]) - bundle "exec 'echo $RUBYOPT'" + bundle "exec echoopt" expect(out.split(" ").count(bundler_setup_opt)).to eq(1) - bundle "exec 'echo $RUBYOPT'", env: { "RUBYOPT" => rubyopt } + bundle "exec echoopt", env: { "RUBYOPT" => rubyopt } expect(out.split(" ").count(bundler_setup_opt)).to eq(1) end it "does not duplicate already exec'ed RUBYLIB" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? - + create_file("echolib", "#!/usr/bin/env ruby\nprint ENV['RUBYLIB']") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G rubylib = ENV["RUBYLIB"] rubylib = rubylib.to_s.split(File::PATH_SEPARATOR).unshift lib_dir.to_s rubylib = rubylib.uniq.join(File::PATH_SEPARATOR) - bundle "exec 'echo $RUBYLIB'" + bundle "exec echolib" expect(out).to include(rubylib) - bundle "exec 'echo $RUBYLIB'", env: { "RUBYLIB" => rubylib } + bundle "exec echolib", env: { "RUBYLIB" => rubylib } expect(out).to include(rubylib) end it "errors nicely when the argument doesn't exist" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G bundle "exec foobarbaz", raise_on_error: false @@ -381,11 +355,11 @@ RSpec.describe "bundle exec" do it "errors nicely when the argument is not executable" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G - bundle "exec touch foo" + bundled_app("foo").write("") bundle "exec ./foo", raise_on_error: false expect(exitstatus).to eq(126) expect(err).to include("bundler: not executable: ./foo") @@ -393,8 +367,8 @@ RSpec.describe "bundle exec" do it "errors nicely when no arguments are passed" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G bundle "exec", raise_on_error: false @@ -403,17 +377,17 @@ RSpec.describe "bundle exec" do end it "raises a helpful error when exec'ing to something outside of the bundle" do - system_gems(system_gems_to_install, path: default_bundle_path) + system_gems(%w[myrack-1.0.0 myrack-0.9.1], path: default_bundle_path) - bundle "config set clean false" # want to keep the rackup binstub + bundle_config "clean false" # want to keep the myrackup binstub install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo" G [true, false].each do |l| - bundle "config set disable_exec_load #{l}" - bundle "exec rackup", raise_on_error: false - expect(err).to include "can't find executable rackup for gem rack. rack is not currently included in the bundle, perhaps you meant to add it to your Gemfile?" + 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 end @@ -424,18 +398,15 @@ RSpec.describe "bundle exec" do each_prefix.call("exec") do |exec| describe "when #{exec} is used" do before(:each) do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G create_file("print_args", <<-'RUBY') #!/usr/bin/env ruby puts "args: #{ARGV.inspect}" RUBY - bundled_app("print_args").chmod(0o755) end it "shows executable's man page when --help is after the executable" do @@ -456,6 +427,7 @@ RSpec.describe "bundle exec" do it "shows executable's man page when the executable has a -" do FileUtils.mv(bundled_app("print_args"), bundled_app("docker-template")) + FileUtils.mv(bundled_app("print_args.bat"), bundled_app("docker-template.bat")) if Gem.win_platform? bundle "#{exec} docker-template build discourse --help" expect(out).to eq('args: ["build", "discourse", "--help"]') end @@ -472,7 +444,7 @@ RSpec.describe "bundle exec" do it "shows bundle-exec's man page when --help is between exec and the executable" do with_fake_man do - bundle "#{exec} --help cat" + bundle "#{exec} --help echo" end expect(out).to include(%(["#{man_dir}/bundle-exec.1"])) end @@ -512,19 +484,19 @@ RSpec.describe "bundle exec" do describe "run from a random directory" do before(:each) do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G end it "works when unlocked" do - bundle "exec 'cd #{tmp("gems")} && rackup'" + bundle "exec 'cd #{tmp("gems")} && myrackup'" expect(out).to eq("1.0.0") end it "works when locked" do expect(the_bundle).to be_locked - bundle "exec 'cd #{tmp("gems")} && rackup'" + bundle "exec 'cd #{tmp("gems")} && myrackup'" expect(out).to eq("1.0.0") end end @@ -536,7 +508,7 @@ RSpec.describe "bundle exec" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "fizz", :path => "#{File.expand_path(home("fizz"))}" G end @@ -561,7 +533,7 @@ RSpec.describe "bundle exec" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "fizz_git", :git => "#{lib_path("fizz_git-1.0")}" G end @@ -585,7 +557,7 @@ RSpec.describe "bundle exec" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "fizz_no_gemspec", "1.0", :git => "#{lib_path("fizz_no_gemspec-1.0")}" G end @@ -605,13 +577,13 @@ RSpec.describe "bundle exec" do it "performs an automatic bundle install" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "0.9.1" + source "https://gem.repo1" + gem "myrack", "0.9.1" gem "foo" G - bundle "config set auto_install 1" - bundle "exec rackup" + bundle_config "auto_install 1" + bundle "exec myrackup", artifice: "compact_index" expect(out).to include("Installing foo 1.0") end @@ -620,23 +592,19 @@ RSpec.describe "bundle exec" do s.executables = "foo" end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "0.9.1" + source "https://gem.repo1" + gem "myrack", "0.9.1" gem "foo", :git => "#{lib_path("foo-1.0")}" G - bundle "config set auto_install 1" - bundle "exec foo" - expect(out).to include("Fetching rack 0.9.1") + 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") end it "loads the correct optparse when `auto_install` is set, and optparse is a dependency" do - if Gem.rubygems_version < Gem::Version.new("3.3.0.a") - skip "optparse is a default gem, and rubygems loads it during install" - end - build_repo4 do build_gem "fastlane", "2.192.0" do |s| s.executables = "fastlane" @@ -649,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 "#{file_uri_for(gem_repo4)}" + 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 @@ -674,28 +642,27 @@ RSpec.describe "bundle exec" do s.version = '1.0' s.summary = 'TODO: Add summary' s.authors = 'Me' + s.rubygems_version = nil end G end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" 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('"TODO" is not a summary') + expect(err).to match(/missing value for attribute rubygems_version|rubygems_version must not be nil/) end end 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" module Monkey def bin_path(a,b,c) @@ -704,56 +671,97 @@ 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") end end + describe "bundle exec gem uninstall" do + before do + build_repo4 do + build_gem "foo" + end + + install_gemfile <<-G + source "https://gem.repo4" + + gem "foo" + G + end + + it "works" do + bundle "exec #{gem_cmd} uninstall foo" + expect(out).to eq("Successfully uninstalled foo-1.0") + 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" } let(:executable) { <<~RUBY.strip } #{shebang} - require "rack" + require "myrack" puts "EXEC: \#{caller.grep(/load/).empty? ? 'exec' : 'load'}" puts "ARGS: \#{$0} \#{ARGV.join(' ')}" - puts "RACK: \#{RACK}" - process_title = `ps -o args -p \#{Process.pid}`.split("\n", 2).last.strip + puts "MYRACK: \#{MYRACK}" + 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 - system_gems(system_gems_to_install, path: default_bundle_path) - - bundled_app(path).open("w") {|f| f << executable } - bundled_app(path).chmod(0o755) + create_file(bundled_app(path), executable) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G end let(:exec) { "EXEC: load" } let(:args) { "ARGS: #{path} arg1 arg2" } - let(:rack) { "RACK: 1.0.0" } + 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, rack, process].join("\n") } + let(:expected) { [exec, args, myrack, process].join("\n") } let(:expected_err) { "" } 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) @@ -765,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) @@ -778,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) @@ -801,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) @@ -818,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) @@ -831,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) @@ -851,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) @@ -864,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) @@ -873,58 +876,29 @@ RSpec.describe "bundle exec" do end end - context "when Bundler.setup fails", bundler: "< 3" do + context "when Bundler.setup fails" do before do - gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack', '2' - G - ENV["BUNDLER_FORCE_TTY"] = "true" - end + system_gems(%w[myrack-1.0.0 myrack-0.9.1], path: default_bundle_path) - let(:exit_code) { Bundler::GemNotFound.new.status_code } - let(:expected) { "" } - let(:expected_err) { <<-EOS.strip } -Could not find gem 'rack (= 2)' in cached gems or installed locally. - -The source contains the following gems matching 'rack': - * rack-0.9.1 - * rack-1.0.0 -Run `bundle install` to install missing gems. - EOS - - it "runs" do - 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) - expect(out).to eq(expected) - end - end - - context "when Bundler.setup fails", bundler: "3" do - before do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack', '2' + source "https://gem.repo1" + gem 'myrack', '2' G ENV["BUNDLER_FORCE_TTY"] = "true" end let(:exit_code) { Bundler::GemNotFound.new.status_code } let(:expected) { "" } - let(:expected_err) { <<-EOS.strip } -Could not find gem 'rack (= 2)' in cached gems or installed locally. + let(:expected_err) { <<~EOS.strip } + Could not find gem 'myrack (= 2)' in locally installed gems. -The source contains the following gems matching 'rack': - * rack-1.0.0 -Run `bundle install` to install missing gems. + The source contains the following gems matching 'myrack': + * myrack-0.9.1 + * myrack-1.0.0 + Run `bundle install` to install missing gems. 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) @@ -934,9 +908,9 @@ Run `bundle install` to install missing gems. context "when Bundler.setup fails and Gemfile is not the default" do before do - create_file "CustomGemfile", <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack', '2' + gemfile "CustomGemfile", <<-G + source "https://gem.repo1" + gem 'myrack', '2' G ENV["BUNDLER_FORCE_TTY"] = "true" ENV["BUNDLE_GEMFILE"] = "CustomGemfile" @@ -947,8 +921,6 @@ Run `bundle install` to install missing gems. 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.") @@ -961,8 +933,6 @@ Run `bundle install` to install missing gems. 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) @@ -972,15 +942,19 @@ Run `bundle install` to install missing gems. 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) @@ -995,27 +969,30 @@ Run `bundle install` to install missing gems. puts "__FILE__: #{__FILE__.inspect}" RUBY - let(:expected) { super() + <<-EOS.chomp } + context "when the path is absolute" do + let(:expected) { super() + <<~EOS.chomp } -$0: #{path.to_s.inspect} -__FILE__: #{path.to_s.inspect} - EOS - - it "runs" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + $0: #{path.to_s.inspect} + __FILE__: #{path.to_s.inspect} + EOS - subject - expect(exitstatus).to eq(exit_code) - expect(err).to eq(expected_err) - expect(out).to eq(expected) + it "runs" do + subject + expect(exitstatus).to eq(exit_code) + expect(err).to eq(expected_err) + expect(out).to eq(expected) + end end context "when the path is relative" do let(:path) { super().relative_path_from(bundled_app) } + let(:expected) { super() + <<~EOS.chomp } - it "runs" do - skip "https://github.com/rubygems/rubygems/issues/3351" if Gem.win_platform? + $0: #{path.to_s.inspect} + __FILE__: #{path.to_s.inspect} + EOS + it "runs" do subject expect(exitstatus).to eq(exit_code) expect(err).to eq(expected_err) @@ -1025,15 +1002,25 @@ __FILE__: #{path.to_s.inspect} context "when the path is relative with a leading ./" do let(:path) { Pathname.new("./#{super().relative_path_from(bundled_app)}") } + let(:expected) { super() + <<~EOS.chomp } + + $0: #{path.to_s.inspect} + __FILE__: #{File.expand_path(path, bundled_app).inspect} + EOS - pending "relative paths with ./ have absolute __FILE__" + it "runs" do + subject + expect(exitstatus).to eq(exit_code) + expect(err).to eq(expected_err) + expect(out).to eq(expected) + end end end 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) @@ -1047,7 +1034,7 @@ __FILE__: #{path.to_s.inspect} 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" @@ -1055,7 +1042,7 @@ __FILE__: #{path.to_s.inspect} 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 @@ -1078,7 +1065,7 @@ __FILE__: #{path.to_s.inspect} 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") @@ -1095,13 +1082,13 @@ __FILE__: #{path.to_s.inspect} 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 "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G - bundle "config set path vendor/bundler" + bundle_config "path vendor/bundler" bundle :install end @@ -1119,9 +1106,9 @@ __FILE__: #{path.to_s.inspect} 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 \"#{file_uri_for(gem_repo1)}\"" + install_gemfile "source \"https://gem.repo1\"" end it "does not undo the monkeypatches" do @@ -1162,27 +1149,25 @@ __FILE__: #{path.to_s.inspect} 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/rack_6_1.gemfile" + bundle_config "path vendor/bundle" + bundle_config "gemfile gemfiles/myrack_6_1.gemfile" - create_file(bundled_app("gemfiles/rack_6_1.gemfile"), <<~RUBY) - source "#{file_uri_for(gem_repo2)}" + gemfile(bundled_app("gemfiles/myrack_6_1.gemfile"), <<~RUBY) + source "https://gem.repo2" gem "rails", "6.1.0" RUBY # A Gemfile needs to be in the root to trick bundler's root resolution - create_file(bundled_app("Gemfile"), "source \"#{file_uri_for(gem_repo1)}\"") + gemfile "source 'https://gem.repo1'" - bundle "install" + bundle "install", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } end it "can still find gems after a nested subprocess" do @@ -1201,22 +1186,39 @@ __FILE__: #{path.to_s.inspect} expect(err).to be_empty expect(out).to eq("6.1.0") end + + it "can still find gems after a nested subprocess when using bundler (with a final r) executable" do + script = bundled_app("bin/myscript") + + create_file(script, <<~RUBY) + #!#{Gem.ruby} + + puts `bundler exec rails` + RUBY + + script.chmod(0o777) + + bundle "exec #{script}" + + expect(err).to be_empty + expect(out).to eq("6.1.0") + end end 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 \"#{file_uri_for(gem_repo1)}\"" # must happen before installing the broken system gem + 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 @@ __FILE__: #{path.to_s.inspect} env = { "PATH" => path } aggregate_failures do - expect(bundle("exec #{file}", artifice: nil, env: env)).to eq(expected) - expect(bundle("exec bundle exec #{file}", artifice: nil, env: env)).to eq(expected) - expect(bundle("exec ruby #{file}", artifice: nil, env: env)).to eq(expected) - expect(run(file.read, artifice: nil, env: env)).to eq(expected) + expect(bundle("exec #{file}", 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,9 +1252,9 @@ __FILE__: #{path.to_s.inspect} 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "simple_git_binary", :git => '#{lib_path("simple_git_binary-1.0")}' G end @@ -1262,7 +1264,7 @@ __FILE__: #{path.to_s.inspect} 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 5415b88eeb..5883b8a63a 100644 --- a/spec/bundler/commands/fund_spec.rb +++ b/spec/bundler/commands/fund_spec.rb @@ -30,22 +30,22 @@ RSpec.describe "bundle fund" do it "prints fund information for all gems in the bundle" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem 'has_funding_and_other_metadata' gem 'has_funding' - gem 'rack-obama' + gem 'myrack-obama' G bundle "fund" expect(out).to include("* has_funding_and_other_metadata (1.0)\n Funding: https://example.com/has_funding_and_other_metadata/funding") expect(out).to include("* has_funding (1.2.3)\n Funding: https://example.com/has_funding/funding") - expect(out).to_not include("rack-obama") + expect(out).to_not include("myrack-obama") end it "does not consider fund information for gem dependencies" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem 'gem_with_dependent_funding' G @@ -55,10 +55,46 @@ RSpec.describe "bundle fund" do expect(out).to_not include("gem_with_dependent_funding") end + it "does not consider fund information for uninstalled optional dependencies" do + install_gemfile <<-G + source "https://gem.repo2" + group :whatever, optional: true do + gem 'has_funding_and_other_metadata' + end + gem 'has_funding' + gem 'myrack-obama' + G + + bundle "fund" + + expect(out).to include("* has_funding (1.2.3)\n Funding: https://example.com/has_funding/funding") + expect(out).to_not include("has_funding_and_other_metadata") + expect(out).to_not include("myrack-obama") + end + + it "considers fund information for installed optional dependencies" do + bundle_config "with whatever" + + install_gemfile <<-G + source "https://gem.repo2" + group :whatever, optional: true do + gem 'has_funding_and_other_metadata' + end + gem 'has_funding' + gem 'myrack-obama' + G + + bundle "fund" + + expect(out).to include("* has_funding_and_other_metadata (1.0)\n Funding: https://example.com/has_funding_and_other_metadata/funding") + expect(out).to include("* has_funding (1.2.3)\n Funding: https://example.com/has_funding/funding") + expect(out).to_not include("myrack-obama") + end + it "prints message if none of the gems have fund information" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem 'rack-obama' + source "https://gem.repo2" + gem 'myrack-obama' G bundle "fund" @@ -69,7 +105,7 @@ RSpec.describe "bundle fund" do describe "with --group option" do it "prints fund message for only specified group gems" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem 'has_funding_and_other_metadata', :group => :development gem 'has_funding' G diff --git a/spec/bundler/commands/help_spec.rb b/spec/bundler/commands/help_spec.rb index 535df8e35a..f9ad9fff14 100644 --- a/spec/bundler/commands/help_spec.rb +++ b/spec/bundler/commands/help_spec.rb @@ -15,6 +15,13 @@ RSpec.describe "bundle help" do expect(out).to eq(%(["#{man_dir}/bundle-install.1"])) end + it "prexifes bundle commands with bundle- and resolves aliases when finding the man files" do + with_fake_man do + bundle "help package" + end + expect(out).to eq(%(["#{man_dir}/bundle-cache.1"])) + end + it "simply outputs the human readable file when there is no man on the path" do with_path_as("") do bundle "help install" @@ -22,11 +29,6 @@ RSpec.describe "bundle help" do expect(out).to match(/bundle-install/) end - it "still outputs the old help for commands that do not have man pages yet" do - bundle "help fund" - expect(out).to include("Lists information about gems seeking funding assistance") - end - it "looks for a binary and executes it with --help option if it's named bundler-<task>" do skip "Could not find command testtasks, probably because not a windows friendly executable" if Gem.win_platform? diff --git a/spec/bundler/commands/info_spec.rb b/spec/bundler/commands/info_spec.rb index a5a09bc147..a26b1696fb 100644 --- a/spec/bundler/commands/info_spec.rb +++ b/spec/bundler/commands/info_spec.rb @@ -18,7 +18,7 @@ RSpec.describe "bundle info" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "rails" gem "has_metadata" gem "thin" @@ -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 @@ -127,9 +127,9 @@ RSpec.describe "bundle info" do context "when gem has a reverse dependency on any version" do it "prints the details" do - bundle "info rack" + bundle "info myrack" - expect(out).to include("Reverse Dependencies: \n\t\tthin (1.0) depends on rack (>= 0)") + expect(out).to include("Reverse Dependencies: \n\t\tthin (1.0) depends on myrack (>= 0)") end end @@ -157,7 +157,7 @@ RSpec.describe "bundle info" do it "prints out git info" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}" G expect(the_bundle).to include_gems "foo 1.0" @@ -173,7 +173,7 @@ RSpec.describe "bundle info" do @revision = revision_for(lib_path("foo-1.0"))[0...6] install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}", :branch => "omg" G expect(the_bundle).to include_gems "foo 1.0.omg" @@ -185,7 +185,7 @@ RSpec.describe "bundle info" do it "doesn't print the branch when tied to a ref" do sha = revision_for(lib_path("foo-1.0")) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "#{sha}" G @@ -196,7 +196,7 @@ RSpec.describe "bundle info" do it "handles when a version is a '-' prerelease" do @git = build_git("foo", "1.0.0-beta.1", path: lib_path("foo")) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", "1.0.0-beta.1", :git => "#{lib_path("foo")}" G expect(the_bundle).to include_gems "foo 1.0.0.pre.beta.1" @@ -209,20 +209,20 @@ RSpec.describe "bundle info" do context "with a valid regexp for gem name" do it "presents alternatives", :readline do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" - gem "rack-obama" + source "https://gem.repo1" + gem "myrack" + gem "myrack-obama" G bundle "info rac" - expect(out).to match(/\A1 : rack\n2 : rack-obama\n0 : - exit -(\n>.*)?\z/) + expect(out).to match(/\A1 : myrack\n2 : myrack-obama\n0 : - exit -(\n>|\z)/) end end context "with an invalid regexp for gem name" do it "does not find the gem" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rails" G @@ -235,10 +235,10 @@ 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rails", group: :test G diff --git a/spec/bundler/commands/init_spec.rb b/spec/bundler/commands/init_spec.rb index 0a1336572a..989d6fa812 100644 --- a/spec/bundler/commands/init_spec.rb +++ b/spec/bundler/commands/init_spec.rb @@ -32,7 +32,7 @@ RSpec.describe "bundle init" do context "when a Gemfile already exists" do before do - create_file "Gemfile", <<-G + gemfile <<-G gem "rails" G end @@ -79,14 +79,14 @@ RSpec.describe "bundle init" do end context "given --gemspec option" do - let(:spec_file) { tmp.join("test.gemspec") } + let(:spec_file) { tmp("test.gemspec") } it "should generate from an existing gemspec" do File.open(spec_file, "w") do |file| file << <<-S Gem::Specification.new do |s| s.name = 'test' - s.add_dependency 'rack', '= 1.0.1' + s.add_dependency 'myrack', '= 1.0.1' s.add_development_dependency 'rspec', '1.2' end S @@ -96,7 +96,7 @@ RSpec.describe "bundle init" do gemfile = bundled_app_gemfile.read expect(gemfile).to match(%r{source 'https://rubygems.org'}) - expect(gemfile.scan(/gem "rack", "= 1.0.1"/).size).to eq(1) + expect(gemfile.scan(/gem "myrack", "= 1.0.1"/).size).to eq(1) expect(gemfile.scan(/gem "rspec", "= 1.2"/).size).to eq(1) expect(gemfile.scan(/group :development/).size).to eq(1) end @@ -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 @@ -129,7 +129,7 @@ RSpec.describe "bundle init" do context "when gems.rb already exists" do before do - create_file("gems.rb", <<-G) + gemfile("gems.rb", <<-G) gem "rails" G end @@ -160,14 +160,14 @@ RSpec.describe "bundle init" do end context "given --gemspec option" do - let(:spec_file) { tmp.join("test.gemspec") } + let(:spec_file) { tmp("test.gemspec") } before do File.open(spec_file, "w") do |file| file << <<-S Gem::Specification.new do |s| s.name = 'test' - s.add_dependency 'rack', '= 1.0.1' + s.add_dependency 'myrack', '= 1.0.1' s.add_development_dependency 'rspec', '1.2' end S @@ -179,7 +179,7 @@ RSpec.describe "bundle init" do gemfile = bundled_app("gems.rb").read expect(gemfile).to match(%r{source 'https://rubygems.org'}) - expect(gemfile.scan(/gem "rack", "= 1.0.1"/).size).to eq(1) + expect(gemfile.scan(/gem "myrack", "= 1.0.1"/).size).to eq(1) expect(gemfile.scan(/gem "rspec", "= 1.2"/).size).to eq(1) expect(gemfile.scan(/group :development/).size).to eq(1) end diff --git a/spec/bundler/commands/inject_spec.rb b/spec/bundler/commands/inject_spec.rb deleted file mode 100644 index 255a03c135..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 "#{file_uri_for(gem_repo1)}" - gem "rack" - G - end - - context "without a lockfile" do - it "locks with the injected gems" do - expect(bundled_app_lock).not_to exist - bundle "inject 'rack-obama' '> 0'" - expect(bundled_app_lock.read).to match(/rack-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(/rack-obama/) - bundle "inject 'rack-obama' '> 0'" - expect(bundled_app_gemfile.read).to match(/rack-obama/) - end - - it "locks with the injected gems" do - expect(bundled_app_lock.read).not_to match(/rack-obama/) - bundle "inject 'rack-obama' '> 0'" - expect(bundled_app_lock.read).to match(/rack-obama/) - end - end - - context "with injected gems already in the Gemfile" do - it "doesn't add existing gems" do - bundle "inject 'rack' '> 0'", raise_on_error: false - expect(err).to match(/cannot specify the same gem twice/i) - end - end - - context "incorrect arguments" do - it "fails when more than 2 arguments are passed" do - bundle "inject gem_name 1 v", raise_on_error: false - 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 #{file_uri_for(gem_repo1)}" - gemfile = bundled_app_gemfile.read - str = "gem \"foo\", \"> 0\", :source => \"#{file_uri_for(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 'rack-obama' '>0' --group=development" - gemfile = bundled_app_gemfile.read - str = "gem \"rack-obama\", \"> 0\", :group => :development" - expect(gemfile).to include str - end - - it "add gem with multiple groups in gemfile" do - bundle "inject 'rack-obama' '>0' --group=development,test" - gemfile = bundled_app_gemfile.read - str = "gem \"rack-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 'rack-obama' '> 0'" - expect(bundled_app_gemfile.read).to match(/rack-obama/) - end - - it "locks with the injected gems" do - expect(bundled_app_lock.read).not_to match(/rack-obama/) - bundle "inject 'rack-obama' '> 0'" - expect(bundled_app_lock.read).to match(/rack-obama/) - end - - it "restores frozen afterwards" do - bundle "inject 'rack-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 "#{file_uri_for(gem_repo1)}" - gem "rack-obama" - G - bundle "inject 'rack' '> 0'", raise_on_error: false - expect(err).to match(/the lockfile can't be updated because frozen mode is set/) - - expect(bundled_app_lock.read).not_to match(/rack-obama/) - end - end -end diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb index f0c9aaea8e..3b24434dc7 100644 --- a/spec/bundler/commands/install_spec.rb +++ b/spec/bundler/commands/install_spec.rb @@ -4,7 +4,7 @@ RSpec.describe "bundle install with gem sources" do describe "the simple case" do it "prints output and returns if no dependencies are specified" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G bundle :install @@ -22,37 +22,91 @@ RSpec.describe "bundle install with gem sources" do it "creates a Gemfile.lock" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G expect(bundled_app_lock).to exist end - it "does not create ./.bundle by default", bundler: "< 3" do - gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + 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 - source "#{file_uri_for(gem_repo1)}" - gem "rack" + 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 "#{file_uri_for(gem_repo1)}" - gem "rack", "1.0" + source "https://gem.repo1" + gem "myrack", "1.0" G bundle "install --gemfile OmgFile" @@ -60,10 +114,33 @@ 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 "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' G lockfile = File.read(bundled_app_lock) @@ -77,8 +154,8 @@ RSpec.describe "bundle install with gem sources" do it "does not touch the lockfile if nothing changed" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G expect { run "1" }.not_to change { File.mtime(bundled_app_lock) } @@ -86,60 +163,77 @@ RSpec.describe "bundle install with gem sources" do it "fetches gems" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' G - expect(default_bundle_path("gems/rack-1.0.0")).to exist - expect(the_bundle).to include_gems("rack 1.0.0") + expect(default_bundle_path("gems/myrack-1.0.0")).to exist + expect(the_bundle).to include_gems("myrack 1.0.0") end it "auto-heals missing gems" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' G - FileUtils.rm_rf(default_bundle_path("gems/rack-1.0.0")) + FileUtils.rm_r(default_bundle_path("gems/myrack-1.0.0")) bundle "install --verbose" - expect(out).to include("Installing rack 1.0.0") - expect(default_bundle_path("gems/rack-1.0.0")).to exist - expect(the_bundle).to include_gems("rack 1.0.0") + expect(out).to include("Installing myrack 1.0.0") + expect(default_bundle_path("gems/myrack-1.0.0")).to exist + 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 "#{file_uri_for(gem_repo1)}" - gem 'rack', "> 0.9", "< 1.0" + source "https://gem.repo1" + gem 'myrack', "> 0.9", "< 1.0" G - expect(default_bundle_path("gems/rack-0.9.1")).to exist - expect(the_bundle).to include_gems("rack 0.9.1") + expect(default_bundle_path("gems/myrack-0.9.1")).to exist + expect(the_bundle).to include_gems("myrack 0.9.1") end it "fetches gems when multiple versions are specified take 2" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack', "< 1.0", "> 0.9" + source "https://gem.repo1" + gem 'myrack', "< 1.0", "> 0.9" G - expect(default_bundle_path("gems/rack-0.9.1")).to exist - expect(the_bundle).to include_gems("rack 0.9.1") + expect(default_bundle_path("gems/myrack-0.9.1")).to exist + expect(the_bundle).to include_gems("myrack 0.9.1") end it "raises an appropriate error when gems are specified using symbols" do install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo1)}" - gem :rack + source "https://gem.repo1" + gem :myrack G expect(exitstatus).to eq(4) end it "pulls in dependencies" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rails" G @@ -148,11 +242,11 @@ RSpec.describe "bundle install with gem sources" do it "does the right version" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "0.9.1" + source "https://gem.repo1" + gem "myrack", "0.9.1" G - expect(the_bundle).to include_gems "rack 0.9.1" + expect(the_bundle).to include_gems "myrack 0.9.1" end it "does not install the development dependency" do @@ -163,7 +257,7 @@ RSpec.describe "bundle install with gem sources" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "with_development_dependency" G @@ -173,7 +267,7 @@ RSpec.describe "bundle install with gem sources" do it "resolves correctly" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "activemerchant" gem "rails" G @@ -183,12 +277,12 @@ RSpec.describe "bundle install with gem sources" do it "activates gem correctly according to the resolved gems" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "activesupport", "2.3.5" G install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "activemerchant" gem "rails" G @@ -206,7 +300,7 @@ RSpec.describe "bundle install with gem sources" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activerecord", "2.3.2" G @@ -216,148 +310,128 @@ RSpec.describe "bundle install with gem sources" do it "works when the gemfile specifies gems that only exist in the system" do build_gem "foo", to_bundle: true install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" gem "foo" G - expect(the_bundle).to include_gems "rack 1.0.0", "foo 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0", "foo 1.0.0" end it "prioritizes local gems over remote gems" do - build_gem "rack", "1.0.0", to_bundle: true do |s| - s.add_dependency "activesupport", "2.3.5" - end + build_gem "myrack", "9.0.0", to_bundle: true install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G - expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.5" + expect(the_bundle).to include_gems "myrack 9.0.0" end it "loads env plugins" do plugin_msg = "hello from an env plugin!" create_file "plugins/rubygems_plugin.rb", "puts '#{plugin_msg}'" - rubylib = ENV["RUBYLIB"].to_s.split(File::PATH_SEPARATOR).unshift(bundled_app("plugins").to_s).join(File::PATH_SEPARATOR) - install_gemfile <<-G, env: { "RUBYLIB" => rubylib } - source "#{file_uri_for(gem_repo1)}" - gem "rack" + install_gemfile <<-G, env: { "RUBYLIB" => rubylib.unshift(bundled_app("plugins").to_s).join(File::PATH_SEPARATOR) } + source "https://gem.repo1" + 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 it "installs gems for the local platform as first choice" do - skip "version is 1.0, not 1.0.0" if Gem.win_platform? - - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "platform_specific" - G + simulate_platform "x86-darwin-100" do + install_gemfile <<-G + source "https://gem.repo1" + gem "platform_specific" + G - run "require 'platform_specific' ; puts PLATFORM_SPECIFIC" - expect(out).to eq("1.0.0 #{Bundler.local_platform}") + expect(the_bundle).to include_gems("platform_specific 1.0 x86-darwin-100") + end end it "falls back on plain ruby" do - simulate_platform "foo-bar-baz" - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "platform_specific" - G + simulate_platform "foo-bar-baz" do + install_gemfile <<-G + source "https://gem.repo1" + gem "platform_specific" + G - run "require 'platform_specific' ; puts PLATFORM_SPECIFIC" - expect(out).to eq("1.0.0 RUBY") + expect(the_bundle).to include_gems("platform_specific 1.0 ruby") + end end it "installs gems for java" do - simulate_platform "java" - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "platform_specific" - G + simulate_platform "java" do + install_gemfile <<-G + source "https://gem.repo1" + gem "platform_specific" + G - run "require 'platform_specific' ; puts PLATFORM_SPECIFIC" - expect(out).to eq("1.0.0 JAVA") + expect(the_bundle).to include_gems("platform_specific 1.0 java") + end end it "installs gems for windows" do - simulate_platform x86_mswin32 + simulate_platform "x86-mswin32" do + install_gemfile <<-G + source "https://gem.repo1" + gem "platform_specific" + G - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "platform_specific" - G - - run "require 'platform_specific' ; puts PLATFORM_SPECIFIC" - expect(out).to eq("1.0 x86-mswin32") + 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 "#{file_uri_for(gem_repo1)}" - gem "rack" - G - end + 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 - it "works" do - bundle "config set --local path vendor" - bundle "install" - expect(the_bundle).to include_gems "rack 1.0" + expect(out).to include("Installing platform_specific 1.0 (aarch64-mingw-ucrt)") end + 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 "rack 1.0" - end + it "gives useful errors if no global sources are set, and gems not installed locally, with and without a lockfile" do + install_gemfile <<-G, raise_on_error: false + gem "myrack" + G - 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 "rack 1.0" - end - end + expect(err).to eq("Could not find gem 'myrack' in locally installed gems.") - it "finds gems in multiple sources", bundler: "< 3" do - build_repo2 do - build_gem "rack", "1.2" do |s| - s.executables = "rackup" - end - end + lockfile <<~L + GEM + specs: + myrack (1.0.0) - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - source "#{file_uri_for(gem_repo2)}" + PLATFORMS + #{lockfile_platforms} - gem "activesupport", "1.2.3" - gem "rack", "1.2" - G + DEPENDENCIES + myrack - expect(the_bundle).to include_gems "rack 1.2", "activesupport 1.2.3" - end + BUNDLED WITH + #{Bundler::VERSION} + L - it "gives a useful error if no sources are set" do - install_gemfile <<-G, raise_on_error: false - gem "rack" - G + bundle "install", raise_on_error: false - expect(err).to include("This Gemfile does not include an explicit global source. " \ - "Not using an explicit global source may result in a different lockfile being generated depending on " \ - "the gems you have installed locally before bundler is run. " \ - "Instead, define a global source in your Gemfile like this: source \"https://rubygems.org\".") + expect(err).to include( + "Because your Gemfile specifies no global remote source, your bundle is locked to " \ + "myrack (1.0.0) from locally installed gems. However, myrack (1.0.0) is not installed. " \ + "You'll need to either add a global remote source to your Gemfile or make sure myrack (1.0.0) " \ + "is available locally before rerunning Bundler." + ) end it "creates a Gemfile.lock on a blank Gemfile" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G expect(File.exist?(bundled_app_lock)).to eq(true) @@ -367,12 +441,12 @@ RSpec.describe "bundle install with gem sources" do build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack" - gem "rack" + source "https://gem.repo2" + gem "myrack" + gem "myrack" G - expect(err).to include("Your Gemfile lists the gem rack (>= 0) more than once.") + expect(err).to include("Your Gemfile lists the gem myrack (>= 0) more than once.") expect(err).to include("Remove any duplicate entries and specify the gem only once.") expect(err).to include("While it's not a problem now, it could cause errors if you change the version of one of them later.") end @@ -381,12 +455,12 @@ RSpec.describe "bundle install with gem sources" do build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack", "1.0" - gem "rack", "1.0" + source "https://gem.repo2" + gem "myrack", "1.0" + gem "myrack", "1.0" G - expect(err).to include("Your Gemfile lists the gem rack (= 1.0) more than once.") + expect(err).to include("Your Gemfile lists the gem myrack (= 1.0) more than once.") expect(err).to include("Remove any duplicate entries and specify the gem only once.") expect(err).to include("While it's not a problem now, it could cause errors if you change the version of one of them later.") end @@ -395,14 +469,14 @@ RSpec.describe "bundle install with gem sources" do build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack", :platform => :jruby - gem "rack" + source "https://gem.repo2" + gem "myrack", :platform => :jruby + gem "myrack" G bundle "install" - expect(err).to include("Your Gemfile lists the gem rack (>= 0) more than once.") + expect(err).to include("Your Gemfile lists the gem myrack (>= 0) more than once.") expect(err).to include("Remove any duplicate entries and specify the gem only once.") expect(err).to include("While it's not a problem now, it could cause errors if you change the version of one of them later.") end @@ -417,7 +491,7 @@ RSpec.describe "bundle install with gem sources" do end gemfile <<~G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gemspec @@ -430,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 @@ -441,7 +515,7 @@ RSpec.describe "bundle install with gem sources" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gemspec @@ -450,19 +524,37 @@ 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 - # 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(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 + + 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 - gem1 = tmp.join("my-gem-1") - gem2 = tmp.join("my-gem-2") + gem1 = tmp("my-gem-1") + gem2 = tmp("my-gem-2") build_lib "my-gem", path: gem1 do |s| s.add_development_dependency "rubocop", "~> 1.36.0" @@ -477,7 +569,7 @@ RSpec.describe "bundle install with gem sources" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gemspec path: "#{gem1}" gemspec path: "#{gem2}" @@ -489,33 +581,85 @@ RSpec.describe "bundle install with gem sources" do expect(the_bundle).to include_gems("rubocop 1.36.0") end - it "warns when a Gemfile dependency is overriding a gemspec development dependency, with different requirements" do - build_lib "my-gem", path: bundled_app do |s| - s.add_development_dependency "rails", ">= 5" + it "includes the gem without warning if two gemspecs add it with compatible requirements" do + gem1 = tmp("my-gem-1") + gem2 = tmp("my-gem-2") + + build_lib "my-gem", path: gem1 do |s| + s.add_development_dependency "rubocop", "~> 1.0" + end + + build_lib "my-gem-2", path: gem2 do |s| + s.add_development_dependency "rubocop", "~> 1.36.0" end build_repo4 do - build_gem "rails", "7.0.8" + build_gem "rubocop", "1.36.0" end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" - - gem "rails", "~> 7.0.8" + source "https://gem.repo4" - gemspec + gemspec path: "#{gem1}" + gemspec path: "#{gem2}" 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") + expect(err).to be_empty + expect(the_bundle).to include_gems("rubocop 1.36.0") + end + + it "errors out if two gemspecs add it with incompatible requirements" do + gem1 = tmp("my-gem-1") + gem2 = tmp("my-gem-2") + + build_lib "my-gem", path: gem1 do |s| + s.add_development_dependency "rubocop", "~> 2.0" + end + + build_lib "my-gem-2", path: gem2 do |s| + s.add_development_dependency "rubocop", "~> 1.36.0" + end + + build_repo4 do + build_gem "rubocop", "1.36.0" + end - # 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") + gemfile <<~G + source "https://gem.repo4" + + gemspec path: "#{gem1}" + gemspec path: "#{gem2}" + G + + bundle :install, raise_on_error: false + + 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 "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 "rubocop" + end + + gemfile <<~G + source "https://gem.repo4" + + gem "rubocop", :path => "#{gem}" + gemspec path: "#{gem}" + G + + bundle :install, raise_on_error: false + + 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 @@ -530,11 +674,11 @@ RSpec.describe "bundle install with gem sources" do build_git "activesupport", "1.0", path: lib_path("activesupport") install_gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gemspec - gem "activesupport", :git => "#{file_uri_for(lib_path("activesupport"))}" + gem "activesupport", :git => "#{lib_path("activesupport")}" G expect(err).to be_empty @@ -542,9 +686,9 @@ RSpec.describe "bundle install with gem sources" do # if the Gemfile dependency is specified first install_gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" - gem "activesupport", :git => "#{file_uri_for(lib_path("activesupport"))}" + gem "activesupport", :git => "#{lib_path("activesupport")}" gemspec G @@ -564,7 +708,7 @@ RSpec.describe "bundle install with gem sources" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gemspec @@ -579,60 +723,57 @@ RSpec.describe "bundle install with gem sources" do it "throws an error if a gem is added twice in Gemfile when version of one dependency is not specified" do install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo2)}" - gem "rack" - gem "rack", "1.0" + source "https://gem.repo2" + gem "myrack" + gem "myrack", "1.0" G expect(err).to include("You cannot specify the same gem twice with different version requirements") - expect(err).to include("You specified: rack (>= 0) and rack (= 1.0).") + expect(err).to include("You specified: myrack (>= 0) and myrack (= 1.0).") end it "throws an error if a gem is added twice in Gemfile when different versions of both dependencies are specified" do install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo2)}" - gem "rack", "1.0" - gem "rack", "1.1" + source "https://gem.repo2" + gem "myrack", "1.0" + gem "myrack", "1.1" G expect(err).to include("You cannot specify the same gem twice with different version requirements") - expect(err).to include("You specified: rack (= 1.0) and rack (= 1.1).") + expect(err).to include("You specified: myrack (= 1.0) and myrack (= 1.1).") 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" source "http://0.0.0.0:9384" do gem 'foo' 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 install_gemfile <<-G, full_index: true, raise_on_error: false - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" 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 @@ -642,7 +783,7 @@ RSpec.describe "bundle install with gem sources" do FileUtils.touch(bundled_app(".bundle/config")) install_gemfile(<<-G) - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'foo' G @@ -653,7 +794,7 @@ RSpec.describe "bundle install with gem sources" do FileUtils.touch("#{Bundler.rubygems.user_home}/.bundle/config") install_gemfile(<<-G) - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'foo' G @@ -665,7 +806,7 @@ RSpec.describe "bundle install with gem sources" do it "prints an error" do install_gemfile <<-G, raise_on_error: false ruby '~> 1.2' - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G expect(err).to include("Your Ruby version is #{Gem.ruby_version}, but your Gemfile specified ~> 1.2") end @@ -675,15 +816,15 @@ RSpec.describe "bundle install with gem sources" do before do install_gemfile <<-G ruby '~> #{Gem.ruby_version}' - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G end it "writes current Ruby version to Gemfile.lock" do - checksums = checksums_section_when_existing + checksums = checksums_section_when_enabled expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS @@ -692,24 +833,24 @@ 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 it "updates Gemfile.lock with updated yet still compatible ruby version" do install_gemfile <<-G ruby '~> #{current_ruby_minor}' - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G - checksums = checksums_section_when_existing + checksums = checksums_section_when_enabled expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS @@ -718,16 +859,16 @@ 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 it "does not crash when unlocking" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" ruby '>= 2.1.0' G @@ -746,7 +887,7 @@ RSpec.describe "bundle install with gem sources" do build_lib "foo" gemfile = <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'foo', :path => "#{lib_path("foo-1.0")}" G File.open("#{root_dir}/Gemfile", "w") do |file| @@ -763,7 +904,7 @@ RSpec.describe "bundle install with gem sources" do build_lib "foo", path: root_dir gemfile = <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gemspec G File.open("#{root_dir}/Gemfile", "w") do |file| @@ -776,11 +917,11 @@ 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 "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' G bundle :install, quiet: true @@ -789,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" @@ -809,7 +950,7 @@ RSpec.describe "bundle install with gem sources" do RUBY gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'non-existing-gem' G @@ -820,41 +961,41 @@ RSpec.describe "bundle install with gem sources" do end end - describe "when bundle path does not have write access", :permissions do + describe "when bundle path does not have cd permission", :permissions do let(:bundle_path) { bundled_app("vendor") } before do FileUtils.mkdir_p(bundle_path) gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' G end 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 write permissions") + expect(err).to include("grant executable permissions") end end - describe "when bundle gems path does not have write access", :permissions do + describe "when bundle gems path does not have cd permission", :permissions do let(:gems_path) { bundled_app("vendor/#{Bundler.ruby_scope}/gems") } before do FileUtils.mkdir_p(gems_path) gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' G end 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 @@ -865,26 +1006,116 @@ RSpec.describe "bundle install with gem sources" do expect(err).not_to include("ERROR REPORT TEMPLATE") expect(err).to include( - "There was an error while trying to create `#{gems_path.join("rack-1.0.0")}`. " \ + "There was an error while trying to create `#{gems_path.join("myrack-1.0.0")}`. " \ "It is likely that you need to grant executable permissions for all parent directories and write permissions for `#{gems_path}`." ) end end + describe "when there's an empty install folder (like with default gems) without cd permissions", :permissions do + let(:full_gem_path) { bundled_app("vendor/#{Bundler.ruby_scope}/gems/myrack-1.0.0") } + + before do + FileUtils.mkdir_p(full_gem_path) + gemfile <<-G + source "https://gem.repo1" + gem 'myrack' + G + end + + it "should display a proper message to explain the problem" do + FileUtils.chmod("-x", full_gem_path) + bundle_config "path vendor" + + begin + bundle :install, raise_on_error: false + ensure + FileUtils.chmod("+x", full_gem_path) + end + + expect(err).not_to include("ERROR REPORT TEMPLATE") + + expect(err).to include( + "There was an error while trying to write to `#{full_gem_path}`. " \ + "It is likely that you need to grant write permissions for that path." + ) + end + end + + describe "when bundle bin dir does not have cd permission", :permissions do + let(:bin_dir) { bundled_app("vendor/#{Bundler.ruby_scope}/bin") } + + before do + FileUtils.mkdir_p(bin_dir) + gemfile <<-G + source "https://gem.repo1" + gem 'myrack' + G + end + + it "should display a proper message to explain the problem" do + FileUtils.chmod("-x", bin_dir) + bundle_config "path vendor" + + begin + bundle :install, raise_on_error: false + ensure + FileUtils.chmod("+x", bin_dir) + end + + expect(err).not_to include("ERROR REPORT TEMPLATE") + + expect(err).to include( + "There was an error while trying to write to `#{bin_dir}`. " \ + "It is likely that you need to grant write permissions for that path." + ) + end + end + + describe "when bundle bin dir does not have write access", :permissions do + let(:bin_dir) { bundled_app("vendor/#{Bundler.ruby_scope}/bin") } + + before do + FileUtils.mkdir_p(bin_dir) + gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + end + + it "should display a proper message to explain the problem" do + FileUtils.chmod("-w", bin_dir) + bundle_config "path vendor" + + begin + bundle :install, raise_on_error: false + ensure + FileUtils.chmod("+w", bin_dir) + end + + expect(err).not_to include("ERROR REPORT TEMPLATE") + + expect(err).to include( + "There was an error while trying to write to `#{bin_dir}`. " \ + "It is likely that you need to grant write permissions for that path." + ) + end + end + describe "when bundle extensions path does not have write access", :permissions do let(:extensions_path) { bundled_app("vendor/#{Bundler.ruby_scope}/extensions/#{Gem::Platform.local}/#{Gem.extension_api_version}") } before do FileUtils.mkdir_p(extensions_path) gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'simple_binary' G end it "should display a proper message to explain the problem" do FileUtils.chmod("-x", extensions_path) - bundle "config set --local path vendor" + bundle_config "path vendor" begin bundle :install, raise_on_error: false @@ -901,7 +1132,7 @@ RSpec.describe "bundle install with gem sources" do end end - describe "when the path of a specific gem is not writable", :permissions do + describe "when the path of a specific gem does not have cd permission", :permissions do let(:gems_path) { bundled_app("vendor/#{Bundler.ruby_scope}/gems") } let(:foo_path) { gems_path.join("foo-1.0.0") } @@ -913,13 +1144,13 @@ RSpec.describe "bundle install with gem sources" do end gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem 'foo' G 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 @@ -927,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 @@ -949,13 +1180,13 @@ RSpec.describe "bundle install with gem sources" do end gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem 'foo' G end - it "should display a proper message to explain the problem" do - bundle "config set --local path vendor" + it "should still work" do + bundle_config "path vendor" bundle :install expect(out).to include("Bundle complete!") expect(err).to be_empty @@ -963,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 @@ -984,22 +1215,50 @@ RSpec.describe "bundle install with gem sources" do end gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem 'foo' G 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("The installation path is insecure. Bundler cannot continue.") + 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 + end + + describe "when gems path is world writable (no sticky bit set), but previous install is just an empty dir (like it happens with default gems)", :permissions do + let(:gems_path) { bundled_app("vendor/#{Bundler.ruby_scope}/gems") } + let(:full_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 + end + + it "does not try to remove the directory and thus don't abort with an error about unsafe directory removal" do + bundle_config "path vendor" + + FileUtils.mkdir_p(gems_path) + FileUtils.chmod(0o777, gems_path) + Dir.mkdir(full_path) + + bundle "install" end end @@ -1009,34 +1268,238 @@ RSpec.describe "bundle install with gem sources" do before do FileUtils.mkdir_p(cache_path) gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' G end 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") end end + describe "when gemspecs are unreadable", :permissions do + let(:gemspec_path) { vendored_gems("specifications/myrack-1.0.0.gemspec") } + + before do + gemfile <<~G + source "https://gem.repo1" + gem 'myrack' + G + bundle_config "path vendor/bundle" + bundle :install + expect(out).to include("Bundle complete!") + expect(err).to be_empty + + FileUtils.chmod("-r", gemspec_path) + end + + it "shows a good error" do + bundle :install, raise_on_error: false + expect(err).to include(gemspec_path.to_s) + expect(err).to include("grant read permissions") + 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("♥") + FileUtils.mkdir_p(path) + path + end + + let(:path) do + root.join("vendor/bundle") + end + + before do + build_repo4 do + build_gem "mygem" do |s| + s.write "spec/fixtures/_posts/2016-04-01-错误.html" + end + end + end + + it "works" do + 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" + gem "mygem", "1.0" + G + end + end + context "after installing with --standalone" do before do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G - bundle "config set --local path bundle" + bundle_config "path bundle" bundle "install", standalone: true end it "includes the standalone path" do - bundle "binstubs rack", standalone: true - standalone_line = File.read(bundled_app("bin/rackup")).each_line.find {|line| line.include? "$:.unshift" }.strip + bundle "binstubs myrack", standalone: true + standalone_line = File.read(bundled_app("bin/myrackup")).each_line.find {|line| line.include? "$:.unshift" }.strip expect(standalone_line).to eq %($:.unshift File.expand_path "../bundle", __dir__) end end @@ -1057,23 +1520,27 @@ RSpec.describe "bundle install with gem sources" do end end - context "in a frozen bundle" do - before do + context "when current platform not included in the lockfile" do + around do |example| build_repo4 do build_gem "libv8", "8.4.255.0" do |s| s.platform = "x86_64-darwin-19" end + + build_gem "libv8", "8.4.255.0" do |s| + s.platform = "x86_64-linux" + end end gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "libv8" G lockfile <<-L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: libv8 (8.4.255.0-x86_64-darwin-19) @@ -1084,14 +1551,40 @@ RSpec.describe "bundle install with gem sources" do libv8 BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L - bundle "config set --local deployment true" + simulate_platform("x86_64-linux", &example) + end + + it "adds the current platform to the lockfile" do + bundle "install --verbose" + + 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 + remote: https://gem.repo4/ + specs: + libv8 (8.4.255.0-x86_64-darwin-19) + libv8 (8.4.255.0-x86_64-linux) + + PLATFORMS + x86_64-darwin-19 + x86_64-linux + + DEPENDENCIES + libv8 + + BUNDLED WITH + #{Bundler::VERSION} + L end - it "should fail loudly if the lockfile platforms don't include the current platform" do - simulate_platform(Gem::Platform.new("x86_64-linux")) { bundle "install", raise_on_error: false } + it "fails loudly if frozen mode set" do + bundle_config "deployment true" + bundle "install", raise_on_error: false expect(err).to eq( "Your bundle only supports platforms [\"x86_64-darwin-19\"] but your local platform is x86_64-linux. " \ @@ -1107,19 +1600,19 @@ RSpec.describe "bundle install with gem sources" do build_gem "nokogiri", "1.12.4" do |s| s.platform = "x86_64-darwin" - s.add_runtime_dependency "racca", "~> 1.4" + s.add_dependency "racca", "~> 1.4" end build_gem "nokogiri", "1.12.4" do |s| s.platform = "x86_64-linux" - s.add_runtime_dependency "racca", "~> 1.4" + s.add_dependency "racca", "~> 1.4" end build_gem "crass", "1.0.6" build_gem "loofah", "2.12.0" do |s| - s.add_runtime_dependency "crass", "~> 1.0.2" - s.add_runtime_dependency "nokogiri", ">= 1.5.9" + s.add_dependency "crass", "~> 1.0.2" + s.add_dependency "nokogiri", ">= 1.5.9" end end @@ -1161,18 +1654,18 @@ 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" end - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum gem_repo4, "crass", "1.0.6" c.checksum gem_repo4, "loofah", "2.12.0" c.checksum gem_repo4, "nokogiri", "1.12.4", "x86_64-darwin" @@ -1202,25 +1695,75 @@ 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 "rack-1.0.0", path: default_bundle_path + system_gems "myrack-1.0.0", path: default_bundle_path end it "respects installed gems without fetching any remote sources" do install_gemfile <<-G, local: true - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" source "https://not-existing-source" do - gem "rack" + gem "myrack" end G @@ -1230,45 +1773,113 @@ 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 install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rails" - gem "rack", group: :a + gem "myrack", group: :a gem "rake", group: :b gem "yard", group: :c G - expect(out).to include("Installing rack") + expect(out).to include("Installing myrack") expect(out).to include("Installing rake") expect(out).not_to include("Installing yard") end end context "with --prefer-local flag" do - before do - build_repo4 do - build_gem "foo", "1.0.1" - build_gem "foo", "1.0.0" - build_gem "bar", "1.0.0" + 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 "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 + 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 + + 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" + + 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 + + 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 + + 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 + + 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 only when not available locally" do - install_gemfile <<-G, "prefer-local": true, verbose: true - source "#{file_uri_for(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 "foo" - gem "bar" - G + build_gem "debug", "1.10.0" do |s| + s.add_dependency "myreline" + end + end + 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 + 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 @@ -1276,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 @@ -1298,7 +1909,7 @@ RSpec.describe "bundle install with gem sources" do it "installs fine" do install_gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "binman" G @@ -1320,7 +1931,7 @@ RSpec.describe "bundle install with gem sources" do it "does not crash unexpectedly" do gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "autobuild", "1.10.rc2" G @@ -1343,7 +1954,7 @@ RSpec.describe "bundle install with gem sources" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "aaa" gem "zzz" @@ -1351,7 +1962,7 @@ RSpec.describe "bundle install with gem sources" do lockfile <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: aaa (0.2.0) zzz (< 0.2.0) @@ -1365,7 +1976,7 @@ RSpec.describe "bundle install with gem sources" do zzz! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -1377,11 +1988,128 @@ RSpec.describe "bundle install with gem sources" do context "when --jobs option given" do before do - install_gemfile "source \"#{file_uri_for(gem_repo1)}\"", jobs: 1 + install_gemfile "source 'https://gem.repo1'", jobs: 1 end it "does not save the flag to config" do expect(bundled_app(".bundle/config")).not_to exist end end + + context "when bundler installation is corrupt" do + before do + system_gems "bundler-9.99.8" + + replace_version_file("9.99.9", dir: system_gem_path("gems/bundler-9.99.8")) + end + + it "shows a proper error" do + lockfile <<~L + GEM + remote: https://gem.repo1/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + + BUNDLED WITH + 9.99.8 + L + + install_gemfile "source \"https://gem.repo1\"", env: { "BUNDLER_VERSION" => "9.99.8" }, raise_on_error: false + + expect(err).not_to include("ERROR REPORT TEMPLATE") + expect(err).to include("The running version of Bundler (9.99.9) does not match the version of the specification installed for it (9.99.8)") + end + end + + it "only installs executable files in bin" do + bundle_config "path vendor/bundle" + + install_gemfile <<~G + source "https://gem.repo1" + gem "myrack" + G + + expected_executables = [vendored_gems("bin/myrackup").to_s] + expected_executables << vendored_gems("bin/myrackup.bat").to_s if Gem.win_platform? + 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| + s.add_dependency "mystringio" + end + + build_gem "mypsych", "5.1.2" do |s| + s.add_dependency "mystringio" + end + + build_gem "mystringio", "3.1.0" + build_gem "mystringio", "3.1.1" + end + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + mypsych (4.0.6) + mystringio + mystringio (3.1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + mypsych (~> 4.0) + + BUNDLED WITH + #{Bundler::VERSION} + L + + install_gemfile <<~G + source "https://gem.repo4" + gem "mypsych", "~> 5.0" + G + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + mypsych (5.1.2) + mystringio + mystringio (3.1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + mypsych (~> 5.0) + + BUNDLED WITH + #{Bundler::VERSION} + L + end end diff --git a/spec/bundler/commands/issue_spec.rb b/spec/bundler/commands/issue_spec.rb index 143f6333ce..346cdedc42 100644 --- a/spec/bundler/commands/issue_spec.rb +++ b/spec/bundler/commands/issue_spec.rb @@ -3,7 +3,7 @@ RSpec.describe "bundle issue" do it "exits with a message" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rails" G diff --git a/spec/bundler/commands/licenses_spec.rb b/spec/bundler/commands/licenses_spec.rb index a203984890..ebfad5ed4a 100644 --- a/spec/bundler/commands/licenses_spec.rb +++ b/spec/bundler/commands/licenses_spec.rb @@ -9,7 +9,7 @@ RSpec.describe "bundle licenses" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "rails" gem "with_license" G @@ -24,13 +24,13 @@ RSpec.describe "bundle licenses" do it "performs an automatic bundle install" do gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "rails" gem "with_license" 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 5ac2077d81..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,12 +39,26 @@ 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 - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack" + gem "myrack" gem "rspec", :group => [:test] gem "rails", :group => [:production] G @@ -32,10 +68,21 @@ RSpec.describe "bundle list" do it "prints the gems not in the specified group" do bundle "list --without-group test" - expect(out).to include(" * rack (1.0.0)") + expect(out).to include(" * myrack (1.0.0)") 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 @@ -50,19 +97,30 @@ RSpec.describe "bundle list" do it "prints the gems not in the specified groups" do bundle "list --without-group test production" - expect(out).to include(" * rack (1.0.0)") + expect(out).to include(" * myrack (1.0.0)") 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 describe "with only-group option" do before do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack" + gem "myrack" gem "rspec", :group => [:test] gem "rails", :group => [:production] G @@ -72,9 +130,18 @@ RSpec.describe "bundle list" do it "prints the gems in the specified group" do bundle "list --only-group default" - expect(out).to include(" * rack (1.0.0)") + 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 @@ -89,19 +156,30 @@ RSpec.describe "bundle list" do it "prints the gems in the specified groups" do bundle "list --only-group default production" - expect(out).to include(" * rack (1.0.0)") + expect(out).to include(" * myrack (1.0.0)") 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 context "with name-only option" do before do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack" + gem "myrack" gem "rspec", :group => [:test] G end @@ -109,16 +187,25 @@ RSpec.describe "bundle list" do it "prints only the name of the gems in the bundle" do bundle "list --name-only" - expect(out).to include("rack") + 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 before do build_repo2 do - build_gem "rack", "1.2" do |s| - s.executables = "rackup" + build_gem "myrack", "1.2" do |s| + s.executables = "myrackup" end build_gem "bar" @@ -126,32 +213,53 @@ RSpec.describe "bundle list" do build_git "git_test", "1.0.0", path: lib_path("git_test") - build_lib("gemspec_test", path: tmp.join("gemspec_test")) do |s| + build_lib("gemspec_test", path: tmp("gemspec_test")) do |s| s.add_dependency "bar", "=1.0.0" end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack" + source "https://gem.repo2" + gem "myrack" gem "rails" gem "git_test", :git => "#{lib_path("git_test")}" - gemspec :path => "#{tmp.join("gemspec_test")}" + gemspec :path => "#{tmp("gemspec_test")}" G end it "prints the path of each gem in the bundle" do bundle "list --paths" expect(out).to match(%r{.*\/rails\-2\.3\.2}) - expect(out).to match(%r{.*\/rack\-1\.2}) + expect(out).to match(%r{.*\/myrack\-1\.2}) 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 before do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G end @@ -159,30 +267,42 @@ 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 before do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack" + gem "myrack" gem "rspec", :group => [:test] G end it "lists gems installed in the bundle" do bundle "list" - expect(out).to include(" * rack (1.0.0)") + 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 before do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack" + gem "myrack" gem "rspec", :group => [:test] G end diff --git a/spec/bundler/commands/lock_spec.rb b/spec/bundler/commands/lock_spec.rb index 0f1aeef910..8ab3cc7e8d 100644 --- a/spec/bundler/commands/lock_spec.rb +++ b/spec/bundler/commands/lock_spec.rb @@ -1,31 +1,22 @@ # frozen_string_literal: true RSpec.describe "bundle lock" do - let(:repo) { gem_repo1 } - - before :each do - gemfile <<-G - source "#{file_uri_for(repo)}" - gem "rails" - gem "weakling" - gem "foo" - G - - checksums = checksums_section_when_existing do |c| - c.checksum repo, "actionmailer", "2.3.2" - c.checksum repo, "actionpack", "2.3.2" - c.checksum repo, "activerecord", "2.3.2" - c.checksum repo, "activeresource", "2.3.2" - c.checksum repo, "activesupport", "2.3.2" - c.checksum repo, "foo", "1.0" - c.checksum repo, "rails", "2.3.2" - c.checksum repo, "rake", rake_version - c.checksum repo, "weakling", "0.0.3" + let(:expected_lockfile) do + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "actionmailer", "2.3.2" + c.checksum gem_repo4, "actionpack", "2.3.2" + c.checksum gem_repo4, "activerecord", "2.3.2" + c.checksum gem_repo4, "activeresource", "2.3.2" + c.checksum gem_repo4, "activesupport", "2.3.2" + c.checksum gem_repo4, "foo", "1.0" + c.checksum gem_repo4, "rails", "2.3.2" + c.checksum gem_repo4, "rake", rake_version + c.checksum gem_repo4, "weakling", "0.0.3" end - @lockfile = <<~L + <<~L GEM - remote: #{file_uri_for(repo)}/ + remote: https://gem.repo4/ specs: actionmailer (2.3.2) activesupport (= 2.3.2) @@ -55,48 +46,151 @@ RSpec.describe "bundle lock" do weakling #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end + let(:outdated_lockfile) do + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "actionmailer", "2.3.1" + c.checksum gem_repo4, "actionpack", "2.3.1" + c.checksum gem_repo4, "activerecord", "2.3.1" + c.checksum gem_repo4, "activeresource", "2.3.1" + c.checksum gem_repo4, "activesupport", "2.3.1" + c.checksum gem_repo4, "foo", "1.0" + c.checksum gem_repo4, "rails", "2.3.1" + c.checksum gem_repo4, "rake", rake_version + c.checksum gem_repo4, "weakling", "0.0.3" + end + + <<~L + GEM + remote: https://gem.repo4/ + specs: + actionmailer (2.3.1) + activesupport (= 2.3.1) + actionpack (2.3.1) + activesupport (= 2.3.1) + activerecord (2.3.1) + activesupport (= 2.3.1) + activeresource (2.3.1) + activesupport (= 2.3.1) + activesupport (2.3.1) + foo (1.0) + rails (2.3.1) + actionmailer (= 2.3.1) + actionpack (= 2.3.1) + activerecord (= 2.3.1) + activeresource (= 2.3.1) + rake (= #{rake_version}) + rake (#{rake_version}) + weakling (0.0.3) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo + rails + weakling + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + + let(:gemfile_with_rails_weakling_and_foo_from_repo4) do + build_repo4 do + 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| + s.executables = "rails" + s.add_dependency "rake", version == "2.3.1" ? "10.0.1" : rake_version + s.add_dependency "actionpack", version + s.add_dependency "activerecord", version + s.add_dependency "actionmailer", version + s.add_dependency "activeresource", version + end + build_gem "actionpack", version do |s| + s.add_dependency "activesupport", version + end + build_gem "activerecord", version do |s| + s.add_dependency "activesupport", version + end + build_gem "actionmailer", version do |s| + s.add_dependency "activesupport", version + end + build_gem "activeresource", version do |s| + s.add_dependency "activesupport", version + end + build_gem "activesupport", version + end + + build_gem "weakling", "0.0.3" + + build_gem "foo" + end + + gemfile <<-G + source "https://gem.repo4" + gem "rails" + gem "weakling" + gem "foo" + G + 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(@lockfile.chomp) + expect(out).to eq(expected_lockfile.chomp) end it "prints a lockfile when there is an existing lockfile with --print" do - lockfile remove_checksums_section_from_lockfile(@lockfile) + gemfile_with_rails_weakling_and_foo_from_repo4 + + lockfile expected_lockfile bundle "lock --print" - expect(out).to eq(remove_checksums_section_from_lockfile(@lockfile).chomp) + expect(out).to eq(expected_lockfile.chomp) end it "prints a lockfile when there is an existing checksums lockfile with --print" do - lockfile @lockfile + gemfile_with_rails_weakling_and_foo_from_repo4 + + lockfile expected_lockfile bundle "lock --print" - expect(out).to eq(@lockfile.chomp) + expect(out).to eq(expected_lockfile.chomp) 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(@lockfile) + expect(read_lockfile).to eq(expected_lockfile) end it "prints a lockfile without fetching new checksums if the existing lockfile had no checksums" do - lockfile remove_checksums_from_lockfile(@lockfile) + gemfile_with_rails_weakling_and_foo_from_repo4 + + lockfile expected_lockfile bundle "lock --print" - expect(out).to eq(remove_checksums_from_lockfile(@lockfile).chomp) + expect(out).to eq(expected_lockfile.chomp) end it "touches the lockfile when there is an existing lockfile that does not need changes" do - lockfile @lockfile + gemfile_with_rails_weakling_and_foo_from_repo4 + + lockfile expected_lockfile expect do bundle "lock" @@ -104,7 +198,9 @@ RSpec.describe "bundle lock" do end it "does not touch lockfile with --print" do - lockfile @lockfile + gemfile_with_rails_weakling_and_foo_from_repo4 + + lockfile expected_lockfile expect do bundle "lock --print" @@ -112,56 +208,82 @@ RSpec.describe "bundle lock" do end it "writes a lockfile when there is an outdated lockfile using --update" do - lockfile remove_checksums_from_lockfile(@lockfile.gsub("2.3.2", "2.3.1"), " (2.3.1)") + gemfile_with_rails_weakling_and_foo_from_repo4 + + lockfile outdated_lockfile bundle "lock --update" - expect(read_lockfile).to eq(remove_checksums_from_lockfile(@lockfile)) + expect(read_lockfile).to eq(expected_lockfile) end - it "writes a lockfile with checksums on --update when checksums exist" do - lockfile @lockfile.gsub("2.3.2", "2.3.1") + it "prints an updated lockfile when there is an outdated lockfile using --print --update" do + gemfile_with_rails_weakling_and_foo_from_repo4 - bundle "lock --update" + lockfile outdated_lockfile + + bundle "lock --print --update" - expect(read_lockfile).to eq(@lockfile) + expect(out).to eq(expected_lockfile.rstrip) + 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" + + expect(err).to eq(<<~STDERR.rstrip) + Fetching gem metadata from https://gem.repo4/... + Resolving dependencies... + STDERR end it "writes a lockfile when there is an outdated lockfile and bundle is frozen" do - lockfile @lockfile.gsub("2.3.2", "2.3.1") + gemfile_with_rails_weakling_and_foo_from_repo4 + + lockfile outdated_lockfile bundle "lock --update", env: { "BUNDLE_FROZEN" => "true" } - expect(read_lockfile).to eq(@lockfile) + expect(read_lockfile).to eq(expected_lockfile) 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(/cached gems or installed locally/) + expect(err).to match(/locally installed gems/) end it "does not fetch remote checksums with --local" do - lockfile remove_checksums_from_lockfile(@lockfile) + gemfile_with_rails_weakling_and_foo_from_repo4 + + lockfile expected_lockfile bundle "lock --print --local" - # No checksums because --local prevents fetching them - expect(out).to eq(remove_checksums_from_lockfile(@lockfile).chomp) + expect(out).to eq(expected_lockfile.chomp) end it "works with --gemfile flag" do - create_file "CustomGemfile", <<-G - source "#{file_uri_for(repo)}" + gemfile_with_rails_weakling_and_foo_from_repo4 + + gemfile "CustomGemfile", <<-G + source "https://gem.repo4" gem "foo" G - checksums = checksums_section_when_existing do |c| - c.no_checksum "foo", "1.0" + bundle "lock --gemfile CustomGemfile" + + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "foo", "1.0" end lockfile = <<~L GEM - remote: #{file_uri_for(repo)}/ + remote: https://gem.repo4/ specs: foo (1.0) @@ -172,42 +294,82 @@ RSpec.describe "bundle lock" do foo #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L - bundle "lock --gemfile CustomGemfile" - expect(out).to match(/Writing lockfile to.+CustomGemfile\.lock/) expect(read_lockfile("CustomGemfile.lock")).to eq(lockfile) expect { read_lockfile }.to raise_error(Errno::ENOENT) 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/) - expect(read_lockfile("lock")).to eq(remove_checksums_from_lockfile(@lockfile)) + expect(read_lockfile("lock")).to eq(expected_lockfile) 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" - checksums = checksums_section_when_existing do |c| - c.checksum repo, "actionmailer", "2.3.2" - c.checksum repo, "actionpack", "2.3.2" - c.checksum repo, "activerecord", "2.3.2" - c.checksum repo, "activeresource", "2.3.2" - c.checksum repo, "activesupport", "2.3.2" - c.checksum repo, "foo", "1.0" - c.checksum repo, "rails", "2.3.2" - c.checksum repo, "rake", rake_version - c.checksum repo, "weakling", "0.0.3" + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "actionmailer", "2.3.2" + c.checksum gem_repo4, "actionpack", "2.3.2" + c.checksum gem_repo4, "activerecord", "2.3.2" + c.checksum gem_repo4, "activeresource", "2.3.2" + c.checksum gem_repo4, "activesupport", "2.3.2" + c.checksum gem_repo4, "foo", "1.0" + c.checksum gem_repo4, "rails", "2.3.2" + c.checksum gem_repo4, "rake", rake_version + c.checksum gem_repo4, "weakling", "0.0.3" end lockfile = <<~L GEM - remote: #{file_uri_for(repo)}/ + remote: https://gem.repo4/ specs: actionmailer (2.3.2) activesupport (= 2.3.2) @@ -237,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/) @@ -245,15 +407,188 @@ RSpec.describe "bundle lock" do end it "update specific gems using --update" do - lockfile @lockfile.gsub("2.3.2", "2.3.1").gsub(rake_version, "10.0.1") + 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" + c.checksum gem_repo4, "activerecord", "2.3.1" + c.checksum gem_repo4, "activeresource", "2.3.1" + c.checksum gem_repo4, "activesupport", "2.3.1" + c.checksum gem_repo4, "foo", "1.0" + c.checksum gem_repo4, "rails", "2.3.1" + c.checksum gem_repo4, "rake", "10.0.1" + c.checksum gem_repo4, "weakling", "0.0.3" + end + + lockfile_with_outdated_rails_and_rake = <<~L + GEM + remote: https://gem.repo4/ + specs: + actionmailer (2.3.1) + activesupport (= 2.3.1) + actionpack (2.3.1) + activesupport (= 2.3.1) + activerecord (2.3.1) + activesupport (= 2.3.1) + activeresource (2.3.1) + activesupport (= 2.3.1) + activesupport (2.3.1) + foo (1.0) + rails (2.3.1) + actionmailer (= 2.3.1) + actionpack (= 2.3.1) + activerecord (= 2.3.1) + activeresource (= 2.3.1) + rake (= 10.0.1) + rake (10.0.1) + weakling (0.0.3) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo + rails + weakling + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + lockfile lockfile_with_outdated_rails_and_rake bundle "lock --update rails rake" - expect(read_lockfile).to eq(remove_checksums_from_lockfile(@lockfile, "(2.3.2)", "(#{rake_version})")) + expect(read_lockfile).to eq(expected_lockfile) + end + + it "updates specific gems using --update, even if that requires unlocking other top level gems" do + build_repo4 do + build_gem "prism", "0.15.1" + build_gem "prism", "0.24.0" + + build_gem "ruby-lsp", "0.12.0" do |s| + s.add_dependency "prism", "< 0.24.0" + end + + build_gem "ruby-lsp", "0.16.1" do |s| + s.add_dependency "prism", ">= 0.24.0" + end + + build_gem "tapioca", "0.11.10" do |s| + s.add_dependency "prism", "< 0.24.0" + end + + build_gem "tapioca", "0.13.1" do |s| + s.add_dependency "prism", ">= 0.24.0" + end + end + + gemfile <<~G + source "https://gem.repo4" + + gem "tapioca" + gem "ruby-lsp" + G + + lockfile <<~L + GEM + remote: https://gem.repo4 + specs: + prism (0.15.1) + ruby-lsp (0.12.0) + prism (< 0.24.0) + tapioca (0.11.10) + prism (< 0.24.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ruby-lsp + tapioca + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "lock --update tapioca --verbose" + + expect(lockfile).to include("tapioca (0.13.1)") + end + + it "updates specific gems using --update, even if that requires unlocking other top level gems, but only as few as possible" do + build_repo4 do + build_gem "prism", "0.15.1" + build_gem "prism", "0.24.0" + + build_gem "ruby-lsp", "0.12.0" do |s| + s.add_dependency "prism", "< 0.24.0" + end + + build_gem "ruby-lsp", "0.16.1" do |s| + s.add_dependency "prism", ">= 0.24.0" + end + + build_gem "tapioca", "0.11.10" do |s| + s.add_dependency "prism", "< 0.24.0" + end + + build_gem "tapioca", "0.13.1" do |s| + s.add_dependency "prism", ">= 0.24.0" + end + + build_gem "other-prism-dependent", "1.0.0" do |s| + s.add_dependency "prism", ">= 0.15.1" + end + + build_gem "other-prism-dependent", "1.1.0" do |s| + s.add_dependency "prism", ">= 0.15.1" + end + end + + gemfile <<~G + source "https://gem.repo4" + + gem "tapioca" + gem "ruby-lsp" + gem "other-prism-dependent" + G + + lockfile <<~L + GEM + remote: https://gem.repo4 + specs: + other-prism-dependent (1.0.0) + prism (>= 0.15.1) + prism (0.15.1) + ruby-lsp (0.12.0) + prism (< 0.24.0) + tapioca (0.11.10) + prism (< 0.24.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ruby-lsp + tapioca + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "lock --update tapioca" + + expect(lockfile).to include("tapioca (0.13.1)") + expect(lockfile).to include("other-prism-dependent (1.0.0)") end it "preserves unknown checksum algorithms" do - lockfile @lockfile.gsub(/(sha256=[a-f0-9]+)$/, "constant=true,\\1,xyz=123") + 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 @@ -263,17 +598,19 @@ 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 - source "#{file_uri_for(gem_repo1)}" - gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}" + source "https://gem.repo1" + gem "foo", :git => "#{lib_path("foo-1.0")}" G # Change uri format to end with "/" and reinstall install_gemfile <<-G, verbose: true - source "#{file_uri_for(gem_repo1)}" - gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}/" + source "https://gem.repo1" + gem "foo", :git => "#{lib_path("foo-1.0")}/" G expect(out).to include("using resolution from the lockfile") @@ -281,24 +618,26 @@ 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 - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rake" - gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}", :branch => "deadbeef" + gem "foo", :git => "#{lib_path("foo-1.0")}", :branch => "deadbeef" G lockfile <<~L GIT - remote: #{file_uri_for(lib_path("foo-1.0"))} + remote: #{lib_path("foo-1.0")} revision: #{ref} branch: deadbeef specs: foo (1.0) GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: rake (10.0.1) @@ -310,7 +649,7 @@ RSpec.describe "bundle lock" do rake BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "lock --update rake --verbose" @@ -319,23 +658,27 @@ RSpec.describe "bundle lock" do end it "errors when updating a missing specific gems using --update" do - lockfile @lockfile + gemfile_with_rails_weakling_and_foo_from_repo4 + + lockfile expected_lockfile bundle "lock --update blahblah", raise_on_error: false expect(err).to eq("Could not find gem 'blahblah'.") - expect(read_lockfile).to eq(@lockfile) + expect(read_lockfile).to eq(expected_lockfile) end it "can lock without downloading gems" do + gemfile_with_rails_weakling_and_foo_from_repo4 + gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "thin" - gem "rack_middleware", :group => "test" + 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 @@ -363,7 +706,7 @@ RSpec.describe "bundle lock" do # establish a lockfile set to 1.4.3 install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem 'foo', '1.4.3' gem 'bar', '2.0.3' gem 'qux', '1.0.0' @@ -372,7 +715,7 @@ RSpec.describe "bundle lock" do # remove 1.4.3 requirement and bar altogether # to setup update specs below gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem 'foo' gem 'qux' G @@ -383,38 +726,54 @@ 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 + # force next minor via Gemfile + gemfile <<-G + source "https://gem.repo4" + gem 'foo', '1.5.0' + gem 'qux' + G + + bundle "lock --update foo --patch --strict", raise_on_error: false + + expect(err).to include( + "foo is locked to 1.4.3, while Gemfile is requesting foo (= 1.5.0). " \ + "--strict --patch was specified, but there are no patch level upgrades from 1.4.3 satisfying foo (= 1.5.0), so version solving has failed" + ) end context "pre" do it "defaults to major" do bundle "lock --update --pre" - expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[foo-2.0.0.pre bar-4.0.0.pre qux-2.0.0].sort) + 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 @@ -430,13 +789,13 @@ RSpec.describe "bundle lock" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem 'sequel' G lockfile <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: sequel (5.71.0) @@ -447,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) @@ -456,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 @@ -468,33 +827,104 @@ RSpec.describe "bundle lock" do system_gems "bundler-55", gem_repo: gem_repo4 install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } - source "https://gems.repo4" + source "https://gem.repo4" G lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, '\11.0.0\2') bundle "lock --update --bundler --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } - expect(lockfile).to end_with("BUNDLED WITH\n 55\n") + 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) - 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 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) + 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 + simulate_platform "arm64-darwin-23" do + build_repo4 do + build_gem "foo" do |s| + s.platform = "arm64-darwin" + end + + build_gem "foo" do |s| + s.platform = "java" + end + end + + gemfile <<-G + source "https://gem.repo4" + + gem "foo" + G + + lockfile <<-L + GEM + remote: https://gem.repo4/ + specs: + foo (1.0-arm64-darwin) + + PLATFORMS + arm64-darwin + + DEPENDENCIES + foo + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "lock --add-platform java" + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + foo (1.0-arm64-darwin) + foo (1.0-java) + + PLATFORMS + arm64-darwin + java + + DEPENDENCIES + foo + + BUNDLED WITH + #{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: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: platform_specific (1.0) platform_specific (1.0-x86-64_linux) @@ -507,38 +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 "warns when adding an unknown platform" do - bundle "lock --add-platform foobarbaz" - expect(err).to include("The platform `foobarbaz` is unknown to RubyGems and adding it will likely lead to resolution errors") + 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 @@ -549,14 +982,14 @@ RSpec.describe "bundle lock" do end end - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum gem_repo4, "nokogiri", "1.12.0" c.checksum gem_repo4, "nokogiri", "1.12.0", "x86_64-darwin" end simulate_platform "x86_64-darwin-22" do install_gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "nokogiri" G @@ -564,7 +997,7 @@ RSpec.describe "bundle lock" do lockfile <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: nokogiri (1.12.0) nokogiri (1.12.0-x86_64-darwin) @@ -577,7 +1010,7 @@ RSpec.describe "bundle lock" do nokogiri #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L checksums.delete("nokogiri", Gem::Platform::RUBY) @@ -588,7 +1021,7 @@ RSpec.describe "bundle lock" do expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: nokogiri (1.12.0-x86_64-darwin) @@ -599,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 @@ -613,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" @@ -639,24 +1074,24 @@ RSpec.describe "bundle lock" do end gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "mixlib-shellout" gem "gssapi" G - checksums = checksums_section_when_existing do |c| - c.no_checksum "ffi", "1.9.14", "x86-mingw32" - c.no_checksum "gssapi", "1.2.0" - c.no_checksum "mixlib-shellout", "2.2.6", "universal-mingw32" - c.no_checksum "win32-process", "0.8.3" - end + simulate_platform("x86-mingw32") { bundle :lock } - simulate_platform(x86_mingw32) { bundle :lock } + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "ffi", "1.9.14", "x86-mingw32" + c.checksum gem_repo4, "gssapi", "1.2.0" + c.checksum gem_repo4, "mixlib-shellout", "2.2.6", "universal-mingw32" + c.checksum gem_repo4, "win32-process", "0.8.3" + end expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: ffi (1.9.14-x86-mingw32) gssapi (1.2.0) @@ -674,18 +1109,18 @@ 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.no_checksum "ffi", "1.9.14" - checksums.no_checksum "mixlib-shellout", "2.2.6" + checksums.checksum gem_repo4, "ffi", "1.9.14" + checksums.checksum gem_repo4, "mixlib-shellout", "2.2.6" expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: ffi (1.9.14) ffi (1.9.14-x86-mingw32) @@ -706,7 +1141,7 @@ RSpec.describe "bundle lock" do mixlib-shellout #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -723,14 +1158,14 @@ RSpec.describe "bundle lock" do end gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "libv8" G lockfile <<-G GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: libv8 (8.4.255.0) libv8 (8.4.255.0-x86_64-darwin-19) @@ -743,10 +1178,10 @@ RSpec.describe "bundle lock" do libv8 BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G - simulate_platform(Gem::Platform.new("x86_64-darwin-19")) { bundle "lock --update" } + simulate_platform("x86_64-darwin-19") { bundle "lock --update" } expect(out).to match(/Writing lockfile to.+Gemfile\.lock/) end @@ -763,21 +1198,21 @@ RSpec.describe "bundle lock" do end gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "libv8" G - simulate_platform(Gem::Platform.new("x86_64-darwin-19")) { bundle "lock" } + simulate_platform("x86_64-darwin-19") { bundle "lock" } - checksums = checksums_section_when_existing do |c| - c.no_checksum "libv8", "8.4.255.0", "x86_64-darwin-19" - c.no_checksum "libv8", "8.4.255.0", "x86_64-darwin-20" + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "libv8", "8.4.255.0", "x86_64-darwin-19" + c.checksum gem_repo4, "libv8", "8.4.255.0", "x86_64-darwin-20" end expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: libv8 (8.4.255.0-x86_64-darwin-19) libv8 (8.4.255.0-x86_64-darwin-20) @@ -790,7 +1225,7 @@ RSpec.describe "bundle lock" do libv8 #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -805,20 +1240,20 @@ RSpec.describe "bundle lock" do end end - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum gem_repo4, "libv8", "8.4.255.0", "x86_64-darwin-19" c.checksum gem_repo4, "libv8", "8.4.255.0", "x86_64-darwin-20" end gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "libv8" G lockfile <<-G GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: libv8 (8.4.255.0-x86_64-darwin-19) libv8 (8.4.255.0-x86_64-darwin-20) @@ -830,13 +1265,13 @@ RSpec.describe "bundle lock" do libv8 #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G previous_lockfile = lockfile %w[x86_64-darwin-19 x86_64-darwin-20].each do |platform| - simulate_platform(Gem::Platform.new(platform)) do + simulate_platform(platform) do bundle "lock" expect(lockfile).to eq(previous_lockfile) @@ -859,25 +1294,20 @@ 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 end gemfile <<-G - source "https://localgemserver.test" + source "https://gem.repo4" gem "raygun-apm" G lockfile <<-L GEM - remote: https://localgemserver.test/ + remote: https://gem.repo4/ specs: raygun-apm (1.0.78-universal-darwin) @@ -888,66 +1318,106 @@ RSpec.describe "bundle lock" do raygun-apm BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L - bundle "lock --add-platform x86_64-linux", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + bundle "lock --add-platform x86_64-linux" end - it "does not crash on conflicting ruby requirements between platform versions in two different gems" do + it "adds platform specific gems as necessary, even when adding the current platform" do build_repo4 do - build_gem "unf_ext", "0.0.8.2" + build_gem "nokogiri", "1.16.0" - build_gem "unf_ext", "0.0.8.2" do |s| - s.required_ruby_version = [">= 2.4", "< #{previous_ruby_minor}"] - s.platform = "x64-mingw32" + build_gem "nokogiri", "1.16.0" do |s| + s.platform = "x86_64-linux" end + 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 + gemfile <<-G + source "https://gem.repo4" - build_gem "google-protobuf", "3.21.12" + gem "nokogiri" + G - build_gem "google-protobuf", "3.21.12" do |s| - s.required_ruby_version = [">= 2.5", "< #{previous_ruby_minor}"] - s.platform = "x64-mingw32" - end + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + nokogiri (1.16.0) - build_gem "google-protobuf", "3.21.12" do |s| - s.required_ruby_version = [">= #{previous_ruby_minor}", "< #{current_ruby_minor}"] - s.platform = "x64-mingw-ucrt" + PLATFORMS + ruby + + DEPENDENCIES + nokogiri + + BUNDLED WITH + #{Bundler::VERSION} + L + + simulate_platform "x86_64-linux" do + bundle "lock --add-platform x86_64-linux" + end + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + nokogiri (1.16.0) + nokogiri (1.16.0-x86_64-linux) + + PLATFORMS + ruby + x86_64-linux + + DEPENDENCIES + nokogiri + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "refuses to add platforms incompatible with the lockfile" do + build_repo4 do + 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 @@ -976,42 +1446,41 @@ 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 } end context "when an update is available" do - let(:repo) do - build_repo2 do + before do + gemfile_with_rails_weakling_and_foo_from_repo4 + + build_repo4 do build_gem "foo", "2.0" end - gem_repo2 - end - before do - lockfile(@lockfile) + lockfile(expected_lockfile) end it "does not implicitly update" do bundle "lock" - checksums = checksums_section_when_existing do |c| - c.checksum repo, "actionmailer", "2.3.2" - c.checksum repo, "actionpack", "2.3.2" - c.checksum repo, "activerecord", "2.3.2" - c.checksum repo, "activeresource", "2.3.2" - c.checksum repo, "activesupport", "2.3.2" - c.checksum repo, "foo", "1.0" - c.checksum repo, "rails", "2.3.2" - c.checksum repo, "rake", rake_version - c.checksum repo, "weakling", "0.0.3" + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "actionmailer", "2.3.2" + c.checksum gem_repo4, "actionpack", "2.3.2" + c.checksum gem_repo4, "activerecord", "2.3.2" + c.checksum gem_repo4, "activeresource", "2.3.2" + c.checksum gem_repo4, "activesupport", "2.3.2" + c.checksum gem_repo4, "foo", "1.0" + c.checksum gem_repo4, "rails", "2.3.2" + c.checksum gem_repo4, "rake", rake_version + c.checksum gem_repo4, "weakling", "0.0.3" end expected_lockfile = <<~L GEM - remote: #{file_uri_for(repo)}/ + remote: https://gem.repo4/ specs: actionmailer (2.3.2) activesupport (= 2.3.2) @@ -1041,7 +1510,7 @@ RSpec.describe "bundle lock" do weakling #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L expect(read_lockfile).to eq(expected_lockfile) @@ -1051,21 +1520,21 @@ RSpec.describe "bundle lock" do gemfile gemfile.gsub('"foo"', '"foo", "2.0"') bundle "lock" - checksums = checksums_section_when_existing do |c| - c.checksum repo, "actionmailer", "2.3.2" - c.checksum repo, "actionpack", "2.3.2" - c.checksum repo, "activerecord", "2.3.2" - c.checksum repo, "activeresource", "2.3.2" - c.checksum repo, "activesupport", "2.3.2" - c.no_checksum "foo", "2.0" - c.checksum repo, "rails", "2.3.2" - c.checksum repo, "rake", rake_version - c.checksum repo, "weakling", "0.0.3" + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "actionmailer", "2.3.2" + c.checksum gem_repo4, "actionpack", "2.3.2" + c.checksum gem_repo4, "activerecord", "2.3.2" + c.checksum gem_repo4, "activeresource", "2.3.2" + c.checksum gem_repo4, "activesupport", "2.3.2" + c.checksum gem_repo4, "foo", "2.0" + c.checksum gem_repo4, "rails", "2.3.2" + c.checksum gem_repo4, "rake", rake_version + c.checksum gem_repo4, "weakling", "0.0.3" end expected_lockfile = <<~L GEM - remote: #{file_uri_for(repo)}/ + remote: https://gem.repo4/ specs: actionmailer (2.3.2) activesupport (= 2.3.2) @@ -1095,7 +1564,7 @@ RSpec.describe "bundle lock" do weakling #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L expect(read_lockfile).to eq(expected_lockfile) @@ -1124,14 +1593,19 @@ RSpec.describe "bundle lock" do it "respects the existing lockfile, even when reresolving" do gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "debug" G + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "debug", "1.6.3" + c.checksum gem_repo4, "irb", "1.5.0" + end + lockfile <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: debug (1.6.3) irb (>= 1.3.6) @@ -1142,23 +1616,18 @@ RSpec.describe "bundle lock" do DEPENDENCIES debug - #{checksums_section} + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L simulate_platform "arm64-darwin-22" do bundle "lock" end - checksums = checksums_section do |c| - c.no_checksum "debug", "1.6.3" - c.no_checksum "irb", "1.5.0" - end - expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: debug (1.6.3) irb (>= 1.3.6) @@ -1172,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 @@ -1197,7 +1724,7 @@ RSpec.describe "bundle lock" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "rails", ">= 7.0.3.1" gem "activeadmin", "2.13.1" @@ -1211,7 +1738,7 @@ RSpec.describe "bundle lock" do Because rails >= 7.0.4 depends on railties = 7.0.4 and rails < 7.0.4 depends on railties = 7.0.3.1, railties = 7.0.3.1 OR = 7.0.4 is required. - So, because railties = 7.0.3.1 OR = 7.0.4 could not be found in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally, + So, because railties = 7.0.3.1 OR = 7.0.4 could not be found in rubygems repository https://gem.repo4/ or installed locally, version solving has failed. ERR end @@ -1252,7 +1779,7 @@ RSpec.describe "bundle lock" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "rails", ">= 7.0.2.3" gem "activeadmin", "= 2.13.1" @@ -1260,7 +1787,7 @@ RSpec.describe "bundle lock" do lockfile <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: activeadmin (2.13.1) ransack (= 3.1.0) @@ -1275,57 +1802,37 @@ RSpec.describe "bundle lock" do ransack (= 3.1.0) BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L - bundle "lock", raise_on_error: false - - expect(err).to eq <<~ERR.strip + expected_error = <<~ERR.strip Could not find compatible versions - Because every version of activemodel depends on activesupport = 6.0.4 - and rails >= 7.0.2.3, < 7.0.3.1 depends on activesupport = 7.0.2.3, - every version of activemodel is incompatible with rails >= 7.0.2.3, < 7.0.3.1. - And because rails >= 7.0.2.3, < 7.0.3.1 depends on activemodel = 7.0.2.3, - rails >= 7.0.2.3, < 7.0.3.1 cannot be used. - (1) So, because rails >= 7.0.3.1, < 7.0.4 depends on activemodel = 7.0.3.1 - and rails >= 7.0.4 depends on activemodel = 7.0.4, - rails >= 7.0.2.3 requires activemodel = 7.0.3.1 OR = 7.0.4. + Because rails >= 7.0.4 depends on activemodel = 7.0.4 + and rails >= 7.0.3.1, < 7.0.4 depends on activemodel = 7.0.3.1, + rails >= 7.0.3.1 requires activemodel = 7.0.3.1 OR = 7.0.4. + (1) So, because rails >= 7.0.2.3, < 7.0.3.1 depends on activemodel = 7.0.2.3 + and every version of activemodel depends on activesupport = 6.0.4, + rails >= 7.0.2.3 requires activesupport = 6.0.4. - Because rails >= 7.0.2.3, < 7.0.3.1 depends on activemodel = 7.0.2.3 + Because rails >= 7.0.2.3, < 7.0.3.1 depends on activesupport = 7.0.2.3 and rails >= 7.0.3.1, < 7.0.4 depends on activesupport = 7.0.3.1, - rails >= 7.0.2.3, < 7.0.4 requires activemodel = 7.0.2.3 or activesupport = 7.0.3.1. - And because rails >= 7.0.4 depends on activesupport = 7.0.4 - and every version of activemodel depends on activesupport = 6.0.4, - activemodel != 7.0.2.3 is incompatible with rails >= 7.0.2.3. - And because rails >= 7.0.2.3 requires activemodel = 7.0.3.1 OR = 7.0.4 (1), + rails >= 7.0.2.3, < 7.0.4 requires activesupport = 7.0.2.3 OR = 7.0.3.1. + And because rails >= 7.0.4 depends on activesupport = 7.0.4, + rails >= 7.0.2.3 requires activesupport = 7.0.2.3 OR = 7.0.3.1 OR = 7.0.4. + And because rails >= 7.0.2.3 requires activesupport = 6.0.4 (1), rails >= 7.0.2.3 cannot be used. So, because Gemfile depends on rails >= 7.0.2.3, version solving has failed. ERR - lockfile lockfile.gsub(/PLATFORMS\n #{local_platform}/m, "PLATFORMS\n #{lockfile_platforms("ruby")}") - bundle "lock", raise_on_error: false + expect(err).to eq(expected_error) - expect(err).to eq <<~ERR.strip - Could not find compatible versions + lockfile lockfile.gsub(/PLATFORMS\n #{local_platform}/m, "PLATFORMS\n #{lockfile_platforms("ruby")}") - Because rails >= 7.0.3.1, < 7.0.4 depends on activemodel = 7.0.3.1 - and rails >= 7.0.2.3, < 7.0.3.1 depends on activemodel = 7.0.2.3, - rails >= 7.0.2.3, < 7.0.4 requires activemodel = 7.0.2.3 OR = 7.0.3.1. - And because every version of activemodel depends on activesupport = 6.0.4, - rails >= 7.0.2.3, < 7.0.4 requires activesupport = 6.0.4. - Because rails >= 7.0.3.1, < 7.0.4 depends on activesupport = 7.0.3.1 - and rails >= 7.0.2.3, < 7.0.3.1 depends on activesupport = 7.0.2.3, - rails >= 7.0.2.3, < 7.0.4 requires activesupport = 7.0.2.3 OR = 7.0.3.1. - Thus, rails >= 7.0.2.3, < 7.0.4 cannot be used. - And because rails >= 7.0.4 depends on activemodel = 7.0.4, - rails >= 7.0.2.3 requires activemodel = 7.0.4. - So, because activemodel = 7.0.4 could not be found in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally - and Gemfile depends on rails >= 7.0.2.3, - version solving has failed. - ERR + bundle "lock", raise_on_error: false + expect(err).to eq(expected_error) end it "does not accidentally resolves to prereleases" do @@ -1345,7 +1852,7 @@ RSpec.describe "bundle lock" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "autoproj", ">= 2.0.0" G @@ -1370,7 +1877,7 @@ RSpec.describe "bundle lock" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "rails" G @@ -1393,7 +1900,7 @@ RSpec.describe "bundle lock" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "activerecord", "6.0.6" gem "activerecord-jdbc-adapter", "61.0" G @@ -1406,6 +1913,409 @@ RSpec.describe "bundle lock" do expect(err).not_to include("ERROR REPORT TEMPLATE") end + it "adds checksums to an existing lockfile, when re-resolving is necessary" 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 + + gemfile <<-G + source "https://gem.repo4" + + gem "nokogiri" + G + + # lockfile has a typo (nogokiri) in the dependencies section, so Bundler + # sees dependencies have changed, and re-resolves + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + nokogiri (1.14.2) + nokogiri (1.14.2-x86_64-linux) + + PLATFORMS + ruby + x86_64-linux + + DEPENDENCIES + nogokiri + + BUNDLED WITH + #{Bundler::VERSION} + L + + simulate_platform "x86_64-linux" do + bundle "lock --add-checksums" + end + + checksums = checksums_section do |c| + c.checksum gem_repo4, "nokogiri", "1.14.2" + c.checksum gem_repo4, "nokogiri", "1.14.2", "x86_64-linux" + 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 + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "adds checksums to an existing lockfile, when no re-resolve is necessary" 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 + + gemfile <<-G + source "https://gem.repo4" + + gem "nokogiri" + G + + lockfile <<~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 + + simulate_platform "x86_64-linux" do + bundle "lock --add-checksums" + end + + checksums = checksums_section do |c| + c.checksum gem_repo4, "nokogiri", "1.14.2" + c.checksum gem_repo4, "nokogiri", "1.14.2", "x86_64-linux" + 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 + #{checksums} + BUNDLED WITH + #{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 + + it "adds checksums to an existing lockfile, when gems are already installed" 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 + + gemfile <<-G + source "https://gem.repo4" + + gem "nokogiri" + G + + lockfile <<~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 + + simulate_platform "x86_64-linux" do + bundle "install" + + bundle "lock --add-checksums" + end + + checksums = checksums_section do |c| + c.checksum gem_repo4, "nokogiri", "1.14.2" + c.checksum gem_repo4, "nokogiri", "1.14.2", "x86_64-linux" + 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 + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "generates checksums by default" 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 + + simulate_platform "x86_64-linux" do + install_gemfile <<-G + source "https://gem.repo4" + + gem "nokogiri" + G + end + + checksums = checksums_section do |c| + c.checksum gem_repo4, "nokogiri", "1.14.2" + c.checksum gem_repo4, "nokogiri", "1.14.2", "x86_64-linux" + 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 + #{checksums} + BUNDLED WITH + #{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 + context "when re-resolving to include prereleases" do before do build_repo4 do @@ -1419,7 +2329,7 @@ RSpec.describe "bundle lock" do it "does not end up including gems scoped to other platforms in the lockfile" do gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "rails" gem "tzinfo-data", platform: :windows G @@ -1446,15 +2356,16 @@ RSpec.describe "bundle lock" do end gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gemspec G end - it "locks ruby specs" do - checksums = checksums_section_when_existing do |c| + it "locks both ruby and platform specific specs" do + checksums = checksums_section_when_enabled do |c| c.no_checksum "foo", "1.0" - c.no_checksum "nokogiri", "1.14.2" + c.checksum gem_repo4, "nokogiri", "1.14.2" + c.checksum gem_repo4, "nokogiri", "1.14.2", "x86_64-linux" end simulate_platform "x86_64-linux" do @@ -1469,20 +2380,88 @@ RSpec.describe "bundle lock" do nokogiri GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: nokogiri (1.14.2) + nokogiri (1.14.2-x86_64-linux) PLATFORMS - #{lockfile_platforms} + ruby + x86_64-linux DEPENDENCIES foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end + + context "and a lockfile with platform specific gems only already exists" do + before do + checksums = checksums_section_when_enabled do |c| + c.no_checksum "foo", "1.0" + c.checksum gem_repo4, "nokogiri", "1.14.2", "x86_64-linux" + end + + lockfile <<~L + PATH + remote: . + specs: + foo (1.0) + nokogiri + + GEM + remote: https://gem.repo4/ + specs: + nokogiri (1.14.2-x86_64-linux) + + PLATFORMS + x86_64-linux + + DEPENDENCIES + foo! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "keeps platform specific gems" do + checksums = checksums_section_when_enabled do |c| + c.no_checksum "foo", "1.0" + c.checksum gem_repo4, "nokogiri", "1.14.2" + c.checksum gem_repo4, "nokogiri", "1.14.2", "x86_64-linux" + end + + simulate_platform "x86_64-linux" do + bundle "install" + end + + expect(lockfile).to eq <<~L + PATH + remote: . + specs: + foo (1.0) + nokogiri + + GEM + remote: https://gem.repo4/ + specs: + nokogiri (1.14.2) + nokogiri (1.14.2-x86_64-linux) + + PLATFORMS + x86_64-linux + + DEPENDENCIES + foo! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end end context "when adding a new gem that requires unlocking other transitive deps" do @@ -1506,7 +2485,7 @@ RSpec.describe "bundle lock" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "govuk_app_config" gem "activesupport", "7.0.4.3" @@ -1517,7 +2496,7 @@ RSpec.describe "bundle lock" do # version lockfile <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: actionpack (7.0.4.1) activesupport (7.0.4.1) @@ -1534,12 +2513,12 @@ RSpec.describe "bundle lock" do govuk_app_config BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end it "does not downgrade top level dependencies" do - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.no_checksum "actionpack", "7.0.4.3" c.no_checksum "activesupport", "7.0.4.3" c.no_checksum "govuk_app_config", "4.13.0" @@ -1552,7 +2531,7 @@ RSpec.describe "bundle lock" do expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: actionpack (7.0.4.3) activesupport (7.0.4.3) @@ -1570,8 +2549,329 @@ RSpec.describe "bundle lock" do govuk_app_config #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end + + context "when lockfile has incorrectly indented platforms" do + before do + build_repo4 do + build_gem "ffi", "1.1.0" do |s| + s.platform = "x86_64-linux" + end + + build_gem "ffi", "1.1.0" do |s| + s.platform = "arm64-darwin" + end + end + + gemfile <<~G + source "https://gem.repo4" + + gem "ffi" + G + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + ffi (1.1.0-arm64-darwin) + + PLATFORMS + arm64-darwin + + DEPENDENCIES + ffi + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "does not remove any gems" do + simulate_platform "x86_64-linux" do + bundle "lock --update" + end + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + ffi (1.1.0-arm64-darwin) + ffi (1.1.0-x86_64-linux) + + PLATFORMS + arm64-darwin + x86_64-linux + + DEPENDENCIES + ffi + + BUNDLED WITH + #{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 199340b131..65fbad05aa 100644 --- a/spec/bundler/commands/newgem_spec.rb +++ b/spec/bundler/commands/newgem_spec.rb @@ -6,52 +6,70 @@ 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 - sys_exec("git config --global user.name 'Bundler User'") - sys_exec("git config --global user.email user@example.com") - sys_exec("git config --global github.user bundleuser") + git("config --global user.name 'Bundler User'") + git("config --global user.email user@example.com") + git("config --global github.user bundleuser") + + 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 - it "generates a gem skeleton with a .git folder", :readline do + it "generates a gem skeleton with a .git folder" do bundle "gem #{gem_name}" gem_skeleton_assertions expect(bundled_app("#{gem_name}/.git")).to exist end - it "generates a gem skeleton with a .git folder when passing --git", :readline do + it "generates a gem skeleton with a .git folder when passing --git" do bundle "gem #{gem_name} --git" gem_skeleton_assertions expect(bundled_app("#{gem_name}/.git")).to exist end - it "generates a gem skeleton without a .git folder when passing --no-git", :readline do + it "generates a gem skeleton without a .git folder when passing --no-git" do bundle "gem #{gem_name} --no-git" gem_skeleton_assertions expect(bundled_app("#{gem_name}/.git")).not_to exist @@ -62,7 +80,9 @@ RSpec.describe "bundle gem" do Dir.mkdir(bundled_app("path with spaces")) end - it "properly initializes git repo", :readline do + it "properly initializes git repo" do + skip "path with spaces needs special handling on Windows" if Gem.win_platform? + bundle "gem #{gem_name}", dir: bundled_app("path with spaces") expect(bundled_app("path with spaces/#{gem_name}/.git")).to exist end @@ -104,7 +124,7 @@ RSpec.describe "bundle gem" do end it "generates the README with a section for the Code of Conduct, respecting the configured git default branch", git: ">= 2.28.0" do - sys_exec("git config --global init.defaultBranch main") + git("config --global init.defaultBranch main") bundle "gem #{gem_name} --coc" expect(bundled_app("#{gem_name}/README.md").read).to include("## Code of Conduct") @@ -131,7 +151,7 @@ RSpec.describe "bundle gem" do before do bundle "gem #{gem_name} --changelog" end - it "generates a gem skeleton with a CHANGELOG", :readline do + it "generates a gem skeleton with a CHANGELOG" do gem_skeleton_assertions expect(bundled_app("#{gem_name}/CHANGELOG.md")).to exist end @@ -141,69 +161,29 @@ RSpec.describe "bundle gem" do before do bundle "gem #{gem_name} --no-changelog" end - it "generates a gem skeleton without a CHANGELOG", :readline do + it "generates a gem skeleton without a CHANGELOG" do gem_skeleton_assertions expect(bundled_app("#{gem_name}/CHANGELOG.md")).to_not exist end end - shared_examples_for "--rubocop flag" do - context "is deprecated", bundler: "< 3" do - before do - 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 @@ -214,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"). @@ -228,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 @@ -243,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/)) ) @@ -255,25 +240,30 @@ 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 "--linter=none flag" do + shared_examples_for "--no-linter flag" do define_negated_matcher :exclude, :include before do - bundle "gem #{gem_name} --linter=none" + bundle "gem #{gem_name} --no-linter" 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")) + 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 @@ -298,83 +288,87 @@ 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", :readline do + it "has no rubocop offenses when using --linter=rubocop flag" do skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? bundle "gem #{gem_name} --linter=rubocop" bundle_exec_rubocop expect(last_command).to be_success end - it "has no rubocop offenses when using --ext=c and --linter=rubocop flag", :readline do + it "has no rubocop offenses when using --ext=c 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? bundle "gem #{gem_name} --ext=c --linter=rubocop" bundle_exec_rubocop expect(last_command).to be_success end - it "has no rubocop offenses when using --ext=c, --test=minitest, and --linter=rubocop flag", :readline do + it "has no rubocop offenses when using --ext=c, --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? bundle "gem #{gem_name} --ext=c --test=minitest --linter=rubocop" bundle_exec_rubocop expect(last_command).to be_success end - it "has no rubocop offenses when using --ext=c, --test=rspec, and --linter=rubocop flag", :readline do + it "has no rubocop offenses when using --ext=c, --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? bundle "gem #{gem_name} --ext=c --test=rspec --linter=rubocop" bundle_exec_rubocop expect(last_command).to be_success end - it "has no rubocop offenses when using --ext=c, --test=test-unit, and --linter=rubocop flag", :readline do + it "has no rubocop offenses when using --ext=c, --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? bundle "gem #{gem_name} --ext=c --test=test-unit --linter=rubocop" bundle_exec_rubocop expect(last_command).to be_success end - it "has no standard offenses when using --linter=standard flag", :readline do + it "has no standard offenses when using --linter=standard flag" do skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? bundle "gem #{gem_name} --linter=standard" bundle_exec_standardrb expect(last_command).to be_success end - it "has no rubocop offenses when using --ext=rust and --linter=rubocop flag", :readline 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 expect(last_command).to be_success end - it "has no rubocop offenses when using --ext=rust, --test=minitest, and --linter=rubocop flag", :readline 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 expect(last_command).to be_success end - it "has no rubocop offenses when using --ext=rust, --test=rspec, and --linter=rubocop flag", :readline 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 expect(last_command).to be_success end - it "has no rubocop offenses when using --ext=rust, --test=test-unit, and --linter=rubocop flag", :readline do + 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 @@ -392,14 +386,20 @@ 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", :readline do + context "README.md" do context "git config github.user present" do before do bundle "gem #{gem_name}" @@ -413,7 +413,7 @@ RSpec.describe "bundle gem" do context "git config github.user is absent" do before do - sys_exec("git config --global --unset github.user") + git("config --global --unset github.user") bundle "gem #{gem_name}" end @@ -422,20 +422,33 @@ RSpec.describe "bundle gem" do expect(bundled_app("#{gem_name}/README.md").read).not_to include("github.com/bundleuser") end end + + describe "test task name on readme" do + shared_examples_for "test task name on readme" do |framework, task_name| + before do + bundle "gem #{gem_name} --test=#{framework}" + end + + it "renders with correct name" do + expect(bundled_app("#{gem_name}/README.md").read).to include("Then, run `rake #{task_name}` to run the tests.") + end + end + + it_behaves_like "test task name on readme", "test-unit", "test" + it_behaves_like "test task name on readme", "minitest", "test" + it_behaves_like "test task name on readme", "rspec", "spec" + end end - it "creates a new git repository", :readline do + it "creates a new git repository" do bundle "gem #{gem_name}" expect(bundled_app("#{gem_name}/.git")).to exist end - context "when git is not available", :readline 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 @@ -449,22 +462,28 @@ 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", :readline, :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", :readline do + context "gem naming with relative paths" do it "resolves ." do create_temporary_dir("tmp") @@ -512,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 @@ -528,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 @@ -543,803 +562,1111 @@ 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 - expect(bundled_app("#{gem_name}/bin/setup").read).to start_with("#!") - expect(bundled_app("#{gem_name}/bin/console").read).to start_with("#!") 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 - expect(bundled_app("#{gem_name}/lib/#{require_path}/version.rb").read).to match(/VERSION = "0.1.0"/) + it "sets gemspec author to git user.name if available" do + expect(generated_gemspec.authors.first).to eq("Bundler User") end - it "declare String type for VERSION constant" do + it "sets gemspec email to git user.email if available" do + expect(generated_gemspec.email.first).to eq("user@example.com") + end + 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 - 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 "sets gemspec email to git user.email if available" do - expect(generated_gemspec.email.first).to eq("user@example.com") + it "does not include the gemspec file in files" do + bundle "gem #{gem_name}" + + bundler_gemspec = Bundler::GemHelper.new(bundled_app(gem_name), gem_name).gemspec + + expect(bundler_gemspec.files).not_to include("#{gem_name}.gemspec") + end + + it "does not include the Gemfile file in files" do + bundle "gem #{gem_name}" + + bundler_gemspec = Bundler::GemHelper.new(bundled_app(gem_name), gem_name).gemspec + + expect(bundler_gemspec.files).not_to include("Gemfile") + end + + it "runs rake without problems" do + bundle "gem #{gem_name}" + + system_gems ["rake-#{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 - sys_exec("git config --global --unset user.name") - sys_exec("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}" + end + + it_behaves_like "test framework is absent" + end - expect(generated_gemspec.required_ruby_version.to_s).to start_with(">=") + context "--test parameter set to rspec" do + before do + bundle "gem #{gem_name} --test=rspec" end - it "requires the version file" 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(%r{require_relative "#{require_relative_path}/version"}) + it "includes .rspec and spec/ into ignore list" do + expect(ignore_paths).to include(".rspec") + expect(ignore_paths).to include("spec/") end - it "creates a base error class" do + 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 + + context "init_gems_rb setting to true" do + before do + bundle_config "init_gems_rb true" bundle "gem #{gem_name}" + end - expect(bundled_app("#{gem_name}/lib/#{require_path}.rb").read).to match(/class Error < StandardError; 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 - it "does not include the gemspec file in files" do + 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 + + 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("#{gem_name}.gemspec") + 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 "does not include the Gemfile file in files" do + context "gem.test setting set to rspec" do + before do + bundle_config "gem.test rspec" bundle "gem #{gem_name}" + end - bundler_gemspec = Bundler::GemHelper.new(bundled_app(gem_name), gem_name).gemspec + 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(bundler_gemspec.files).not_to include("Gemfile") + it "includes .rspec and spec/ into ignore list" do + expect(ignore_paths).to include(".rspec") + expect(ignore_paths).to include("spec/") end + end - it "runs rake without problems" do - bundle "gem #{gem_name}" + 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 "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 - system_gems ["rake-#{rake_version}"] + it "includes test/ into ignore list" do + expect(ignore_paths).to include("test/") + end + end + context "--test parameter set to minitest" do + before do + bundle "gem #{gem_name} --test=minitest" + 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 + + 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 "includes test/ into ignore list" do + expect(ignore_paths).to include("test/") + end + + it "creates a default rake task to run the test suite" do rakefile = <<~RAKEFILE - task :default do - puts 'SUCCESS' - end + # frozen_string_literal: true + + require "bundler/gem_tasks" + require "minitest/test_task" + + Minitest::TestTask.create + + task default: :test 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") + expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) end + end - context "--exe parameter set" do - before do - bundle "gem #{gem_name} --exe" - end + context "gem.test setting set to minitest" do + before do + bundle_config "gem.test minitest" + bundle "gem #{gem_name}" + end - it "builds exe skeleton" do - expect(bundled_app("#{gem_name}/exe/#{gem_name}")).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}/exe/#{gem_name}").read).to match(/require "#{require_path}"/) - end - end + require "bundler/gem_tasks" + require "minitest/test_task" - context "--bin parameter set" do - before do - bundle "gem #{gem_name} --bin" - end + Minitest::TestTask.create - it "builds exe skeleton" do - expect(bundled_app("#{gem_name}/exe/#{gem_name}")).to exist - end + task default: :test + RAKEFILE - it "requires the main file" do - expect(bundled_app("#{gem_name}/exe/#{gem_name}").read).to match(/require "#{require_path}"/) - end + expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) end + end - context "no --test parameter" do - before do - bundle "gem #{gem_name}" - end + context "--test parameter set to test-unit" do + before do + bundle "gem #{gem_name} --test=test-unit" + end - it_behaves_like "test framework is absent" + 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 rspec" do - before do - bundle "gem #{gem_name} --test=rspec" - 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 "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 "includes test/ into ignore list" do + expect(ignore_paths).to include("test/") + end - 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 + 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}/spec/spec_helper.rb").read).to include(%(require "#{require_path}")) - end + require "bundler/gem_tasks" + require "rake/testtask" - 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 + Rake::TestTask.new(:test) do |t| + t.libs << "test" + t.libs << "lib" + t.test_files = FileList["test/**/*_test.rb"] + end + + task default: :test + RAKEFILE + + expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) end + end - context "gem.test setting set to rspec" do - before do - bundle "config set gem.test rspec" - bundle "gem #{gem_name}" - end + context "--test parameter set to an invalid value" do + before do + bundle "gem #{gem_name} --test=foo", raise_on_error: false + 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 "fails loudly" do + expect(last_command).to be_failure + expect(err).to match(/Expected '--test' to be one of .*; got foo/) 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 "gem.test set to rspec and --test with no arguments" do + before do + bundle_config "gem.test rspec" + bundle "gem #{gem_name} --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 "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 "--test parameter set to minitest" do - before do - bundle "gem #{gem_name} --test=minitest" - 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 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 "hints that --test is already configured" do + expect(out).to match("rspec is already configured, ignoring --test flag.") + end + 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 + 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 "requires the main file" do - expect(bundled_app("#{gem_name}/test/test_helper.rb").read).to include(%(require "#{require_path}")) - end + it "asks to generate test files" do + expect(out).to match("Do you want to generate tests with your gem?") + end - it "requires 'test_helper'" do - expect(bundled_app("#{gem_name}/#{minitest_test_file_path}").read).to include(%(require "test_helper")) - 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 "defines valid test class name" do - expect(bundled_app("#{gem_name}/#{minitest_test_file_path}").read).to include(minitest_test_class_name) - end + it_behaves_like "test framework is absent" + end - it "creates a default test which fails" do - expect(bundled_app("#{gem_name}/#{minitest_test_file_path}").read).to include("assert false") + 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 "gem.test setting set to minitest" do - before do - bundle "config set gem.test minitest" - bundle "gem #{gem_name}" - end + it "asks to generate test files" do + expect(out).to match("Do you want to generate tests with your gem?") + end - it "creates a default rake task to run the test suite" do - rakefile = <<~RAKEFILE - # frozen_string_literal: true + 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 - require "bundler/gem_tasks" - require "minitest/test_task" + it_behaves_like "test framework is absent" + end - Minitest::TestTask.create + 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 - task default: :test - RAKEFILE + it_behaves_like "test framework is absent" + end - expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) - end + context "--ci with no argument" do + before do + bundle "gem #{gem_name}" end - context "--test parameter set to test-unit" do - before do - bundle "gem #{gem_name} --test=test-unit" - 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 - 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 "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 "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 - it "requires the main file" do - expect(bundled_app("#{gem_name}/test/test_helper.rb").read).to include(%(require "#{require_path}")) - end + context "--ci set to github" do + before do + bundle "gem #{gem_name} --ci=github" + end - it "requires 'test_helper'" do - expect(bundled_app("#{gem_name}/test/#{require_path}_test.rb").read).to include(%(require "test_helper")) - end + it "generates a GitHub Actions config file" do + expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist + 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\")") - end + it "includes .github/ into ignore list" do + expect(ignore_paths).to include(".github/") end + end - context "gem.test setting set to test-unit" do - before do - bundle "config set gem.test test-unit" - bundle "gem #{gem_name}" - end + context "--ci set to gitlab" do + before do + bundle "gem #{gem_name} --ci=gitlab" + end - it "creates a default rake task to run the test suite" do - rakefile = <<~RAKEFILE - # frozen_string_literal: true + it "generates a GitLab CI config file" do + expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to exist + end - require "bundler/gem_tasks" - require "rake/testtask" + it "includes .gitlab-ci.yml into ignore list" do + expect(ignore_paths).to include(".gitlab-ci.yml") + end + end - Rake::TestTask.new(:test) do |t| - t.libs << "test" - t.libs << "lib" - t.test_files = FileList["test/**/*_test.rb"] - end + context "--ci set to circle" do + before do + bundle "gem #{gem_name} --ci=circle" + end - task default: :test - RAKEFILE + it "generates a CircleCI config file" do + expect(bundled_app("#{gem_name}/.circleci/config.yml")).to exist + end - expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) - end + it "includes .circleci/ into ignore list" do + expect(ignore_paths).to include(".circleci/") end + end - context "gem.test set to rspec and --test with no arguments", :hint_text do - before do - bundle "config set gem.test rspec" - bundle "gem #{gem_name} --test" - end + context "--ci set to an invalid value" do + before do + bundle "gem #{gem_name} --ci=foo", raise_on_error: false + 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 "fails loudly" do + expect(last_command).to be_failure + expect(err).to match(/Expected '--ci' to be one of .*; got foo/) + end + end - it "hints that --test is already configured" do - expect(out).to match("rspec is already configured, ignoring --test flag.") - 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 - context "gem.test setting set to false and --test with no arguments", :hint_text do - before do - bundle "config set gem.test false" - bundle "gem #{gem_name} --test" - end + 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}" - it "asks to generate test files" do - expect(out).to match("Do you want to generate tests with your gem?") - end + expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist + 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.ci setting set to gitlab" do + it "generates a GitLab CI config file" do + bundle_config "gem.ci gitlab" + bundle "gem #{gem_name}" - it_behaves_like "test framework is absent" + expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to exist end + end - context "gem.test setting not set and --test with no arguments", :hint_text do - before do - bundle "gem #{gem_name} --test" - end + context "gem.ci setting set to circle" do + it "generates a CircleCI config file" do + bundle_config "gem.ci circle" + bundle "gem #{gem_name}" - it "asks to generate test files" do - expect(out).to match("Do you want to generate tests with your gem?") - end + expect(bundled_app("#{gem_name}/.circleci/config.yml")).to exist + 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 "gem.ci set to github and --ci with no arguments" do + before do + bundle_config "gem.ci github" + bundle "gem #{gem_name} --ci" + end - it_behaves_like "test framework is absent" + it "generates a GitHub Actions config file" do + expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist end - context "--ci with no argument" do - it "does not generate any CI config" do - bundle "gem #{gem_name}" + 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}/.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 + 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 - context "--ci set to github" do - it "generates a GitHub Actions config file" do - bundle "gem #{gem_name} --ci=github" + it "asks to setup CI" do + expect(out).to match("Do you want to set up continuous integration for your gem?") + 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 - expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist + 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 - it "contained .gitlab-ci.yml into ignore list" do - bundle "gem #{gem_name} --ci=github" + 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}/#{gem_name}.gemspec").read).to include(".git .github appveyor") - 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 - context "--ci set to gitlab" do - it "generates a GitLab CI config file" do - bundle "gem #{gem_name} --ci=gitlab" + 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}/.gitlab-ci.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 + end - it "contained .gitlab-ci.yml into ignore list" do - bundle "gem #{gem_name} --ci=gitlab" + context "--linter with no argument" do + before do + bundle "gem #{gem_name}" + end - expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include(".git .gitlab-ci.yml appveyor") - 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 "--ci set to circle" do - it "generates a CircleCI config file" do - bundle "gem #{gem_name} --ci=circle" + 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 - expect(bundled_app("#{gem_name}/.circleci/config.yml")).to exist - end + context "--linter set to rubocop" do + before do + bundle "gem #{gem_name} --linter=rubocop" + end - it "contained .circleci into ignore list" do - bundle "gem #{gem_name} --ci=circle" + 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 - expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include(".git .circleci appveyor") - end + 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 - 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 + context "--linter set to standard" do + before do + bundle "gem #{gem_name} --linter=standard" 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}" + 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}/.github/workflows/main.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 setting set to gitlab" do - it "generates a GitLab CI config file" do - bundle "config set gem.ci gitlab" - bundle "gem #{gem_name}" + context "--linter set to an invalid value" do + before do + bundle "gem #{gem_name} --linter=foo", raise_on_error: false + end - expect(bundled_app("#{gem_name}/.gitlab-ci.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 - 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}" + context "gem.linter setting set to none" do + before do + bundle "gem #{gem_name}" + end - expect(bundled_app("#{gem_name}/.circleci/config.yml")).to exist - 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 - context "gem.ci set to github and --ci with no arguments", :hint_text do - before do - bundle "config set gem.ci github" - bundle "gem #{gem_name} --ci" - 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 "generates a GitHub Actions config file" do - expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist - end + context "gem.linter setting set to rubocop" do + before do + bundle_config "gem.linter rubocop" + bundle "gem #{gem_name}" + end - it "hints that --ci is already configured" do - expect(out).to match("github is already configured, ignoring --ci flag.") - end + it "generates a RuboCop config file" do + expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist end - context "gem.ci setting set to false and --ci with no arguments", :hint_text do - before do - bundle "config set gem.ci false" - bundle "gem #{gem_name} --ci" - end + it "includes .rubocop.yml into ignore list" do + expect(ignore_paths).to include(".rubocop.yml") + end + end - it "asks to setup CI" do - expect(out).to match("Do you want to set up continuous integration for your gem?") - end + context "gem.linter setting set to standard" do + before do + bundle_config "gem.linter standard" + bundle "gem #{gem_name}" + 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 "generates a Standard config file" do + expect(bundled_app("#{gem_name}/.standard.yml")).to exist end - context "gem.ci setting not set and --ci with no arguments", :hint_text do - before do - bundle "gem #{gem_name} --ci" - end + it "includes .standard.yml into ignore list" do + expect(ignore_paths).to include(".standard.yml") + end + end - it "asks to setup CI" do - expect(out).to match("Do you want to set up continuous integration for your gem?") - end + 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 - 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 + it "generates a RuboCop config file" do + expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist end - context "--linter with no argument" do - it "does not generate any linter config" do - bundle "gem #{gem_name}" + it "includes .rubocop.yml into ignore list" do + expect(ignore_paths).to include(".rubocop.yml") + end + + it "hints that --linter is already configured" do + expect(out).to match("rubocop is already configured, ignoring --linter flag.") + end + end - expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist - expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist + 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 rubocop" do - it "generates a RuboCop config" do - bundle "gem #{gem_name} --linter=rubocop" + 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 "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}/.rubocop.yml")).to exist - expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist + 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 - 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 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 - context "gem.linter setting set to none" do - it "doesn't generate any linter config" do - bundle "gem #{gem_name}" + 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 - expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist - expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist - 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.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 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 - expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist - 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 + 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}" + 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}/.standard.yml")).to exist - end + 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 - 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", :hint_text 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", :hint_text do + context "--test parameter set to minitest" do before do - bundle "config set gem.linter false" - bundle "gem #{gem_name} --linter" + 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", :hint_text do + context "--test parameter set to test-unit" do before do - bundle "gem #{gem_name} --linter" + 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 "--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 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 - context "testing --mit and --coc options against bundle config settings", :readline do - let(:gem_name) { "test-gem" } + 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 - let(:require_path) { "test/gem" } + 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 "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 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 - 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 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 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 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 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 false" do - before do - global_config "BUNDLE_GEM__COC" => "false" - 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 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 "--linter=none flag" - it_behaves_like "--rubocop flag" - it_behaves_like "--no-rubocop 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 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 "--linter=none 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 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 "--linter=none 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 standard" do + 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 "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 "--linter=none 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 "--linter=none 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", :readline 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 - sys_exec("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", :readline do - context "without git config set" do - before do - sys_exec("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", :readline do + context "gem naming with underscore" do let(:gem_name) { "test_gem" } let(:require_path) { "test_gem" } @@ -1358,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" } @@ -1385,20 +1694,23 @@ 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"') - expect(bundled_app("#{gem_name}/Gemfile").read).to_not include('gem "rb_sys"') + expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to_not include('spec.add_dependency "rb_sys"') expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to_not include('spec.required_rubygems_version = ">= ') end @@ -1424,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 @@ -1454,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}/Gemfile").read).to include('gem "rb_sys"') - expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include('spec.required_rubygems_version = ">= ') + expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include('spec.add_dependency "rb_sys"') end it "depends on compile task for build" do @@ -1482,10 +1780,186 @@ 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 - context "gem naming with dashed", :readline do + context "gem naming with dashed" do let(:gem_name) { "test-gem" } let(:require_path) { "test/gem" } @@ -1502,11 +1976,11 @@ 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 - it "can deal with two dashes", :readline do + it "can deal with two dashes" do bundle "gem a--a" expect(bundled_app("a--a/a--a.gemspec")).to exist @@ -1536,7 +2010,7 @@ Usage: "bundle gem NAME [OPTIONS]" end end - describe "#ensure_safe_gem_name", :readline do + describe "#ensure_safe_gem_name" do before do bundle "gem #{subject}", raise_on_error: false end @@ -1551,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}") } @@ -1564,7 +2051,7 @@ Usage: "bundle gem NAME [OPTIONS]" context "on first run", :readline do it "asks about test framework" do - global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__COC" => "false" + bundle_config_global "BUNDLE_GEM__TEST" => nil bundle "gem foobar" do |input, _, _| input.puts "rspec" @@ -1587,7 +2074,7 @@ Usage: "bundle gem NAME [OPTIONS]" end it "asks about CI service" do - global_config "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__COC" => "false", "BUNDLE_GEM__LINTER" => "false" + bundle_config_global "BUNDLE_GEM__CI" => nil bundle "gem foobar" do |input, _, _| input.puts "github" @@ -1596,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__TEST" => "false", "BUNDLE_GEM__COC" => "false", "BUNDLE_GEM__CI" => "false", "BUNDLE_GEM__LINTER" => "false" + it "asks about MIT license just once" do + bundle_config_global "BUNDLE_GEM__MIT" => nil bundle "config list" @@ -1606,31 +2093,33 @@ 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__MIT" => "false", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__CI" => "false", "BUNDLE_GEM__LINTER" => "false" + 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__MIT" => "false", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__CI" => "false", "BUNDLE_GEM__LINTER" => "false", - "BUNDLE_GEM__COC" => "false" + 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 - context "on conflicts with a previously created file", :readline do + context "on conflicts with a previously created file" do it "should fail gracefully" do FileUtils.touch(bundled_app("conflict-foobar")) bundle "gem conflict-foobar", raise_on_error: false @@ -1639,7 +2128,7 @@ Usage: "bundle gem NAME [OPTIONS]" end end - context "on conflicts with a previously created directory", :readline do + context "on conflicts with a previously created directory" do it "should succeed" do FileUtils.mkdir_p(bundled_app("conflict-foobar/Gemfile")) bundle "gem conflict-foobar" diff --git a/spec/bundler/commands/open_spec.rb b/spec/bundler/commands/open_spec.rb index 97374f30c3..664dc58919 100644 --- a/spec/bundler/commands/open_spec.rb +++ b/spec/bundler/commands/open_spec.rb @@ -4,7 +4,7 @@ RSpec.describe "bundle open" do context "when opening a regular gem" do before do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rails" G end @@ -39,17 +39,17 @@ RSpec.describe "bundle open" do ref = git.ref_for("main", 11) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'foo', :git => "#{lib_path("foo-1.0")}" G bundle "open foo", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } - expect(out).to include("editor #{default_bundle_path.join("bundler", "gems", "foo-1.0-#{ref}")}") + expect(out).to include("editor #{default_bundle_path("bundler", "gems", "foo-1.0-#{ref}")}") end it "suggests alternatives for similar-sounding gems" do bundle "open Rails", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, raise_on_error: false - 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 @@ -134,12 +134,12 @@ RSpec.describe "bundle open" do it "performs an automatic bundle install" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rails" 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 @@ -163,8 +163,7 @@ RSpec.describe "bundle open" do skip "No default gems available on this test run" if default_gems.empty? install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "json" + source "https://gem.repo1" G end diff --git a/spec/bundler/commands/outdated_spec.rb b/spec/bundler/commands/outdated_spec.rb index e7edc67e57..28ed51d61e 100644 --- a/spec/bundler/commands/outdated_spec.rb +++ b/spec/bundler/commands/outdated_spec.rb @@ -9,7 +9,7 @@ RSpec.describe "bundle outdated" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "zebra", :git => "#{lib_path("zebra")}" gem "foo", :git => "#{lib_path("foo")}" gem "activesupport", "2.3.5" @@ -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 @@ -46,14 +46,14 @@ RSpec.describe "bundle outdated" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "AAA", "1.0.0" G 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 @@ -77,7 +77,7 @@ RSpec.describe "bundle outdated" do it "adds gem group to dependency output when repo is updated" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "terranova", '8' @@ -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 @@ -109,7 +109,7 @@ RSpec.describe "bundle outdated" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "zebra", :git => "#{lib_path("zebra")}" gem "foo", :git => "#{lib_path("foo")}" gem "activesupport", "2.3.5" @@ -120,20 +120,20 @@ 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" } install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "terranova", '9' gem 'activesupport', '2.3.5' G gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "terranova", '8' gem 'activesupport', '2.3.5' @@ -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_repo gem_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 @@ -225,7 +159,7 @@ RSpec.describe "bundle outdated" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "weakling", "~> 0.0.1" gem "terranova", '8' @@ -251,11 +185,19 @@ RSpec.describe "bundle outdated" do expect(out).to end_with("Bundle up to date!") end + it "works when only out of date gems are not in given group" do + update_repo2 do + build_gem "terranova", "9" + end + bundle "outdated --group development" + expect(out).to end_with("Bundle up to date!") + end + it "returns a sorted list of outdated gems from one group => 'default'" 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 @@ -266,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 @@ -278,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 @@ -301,7 +243,7 @@ RSpec.describe "bundle outdated" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "bar_dependant", '7.0' G @@ -315,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 @@ -331,7 +273,7 @@ RSpec.describe "bundle outdated" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "weakling", "~> 0.0.1" gem "terranova", '8' @@ -357,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 @@ -375,7 +317,7 @@ RSpec.describe "bundle outdated" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "weakling", "~> 0.0.1" gem "terranova", '8' @@ -391,17 +333,17 @@ 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 "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport", "2.3.4" G 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 @@ -409,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/) @@ -428,7 +370,7 @@ RSpec.describe "bundle outdated" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "zebra", :git => "#{lib_path("zebra")}" gem "foo", :git => "#{lib_path("foo")}" gem "activesupport", "2.3.5" @@ -438,19 +380,40 @@ RSpec.describe "bundle outdated" do G end - it "outputs a sorted list of outdated gems with a more minimal format" do + it "outputs a sorted list of outdated gems with a more minimal format to stdout" do minimal_output = "activesupport (newest 3.0, installed 2.3.5, requested = 2.3.5)\n" \ "weakling (newest 0.2, installed 0.0.3, requested ~> 0.0.1)" subject expect(out).to eq(minimal_output) end + + it "outputs progress to stderr" do + subject + expect(err).to include("Fetching gem metadata") + end end context "and no gems are outdated" do - it "has empty output" do + before do + build_repo2 do + build_gem "activesupport", "3.0" + end + + install_gemfile <<-G + source "https://gem.repo2" + gem "activesupport", "3.0" + G + end + + it "does not output to stdout" do subject expect(out).to be_empty end + + it "outputs progress to stderr" do + subject + expect(err).to include("Fetching gem metadata") + end end end @@ -474,7 +437,7 @@ RSpec.describe "bundle outdated" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "zebra", :git => "#{lib_path("zebra")}" gem "foo", :git => "#{lib_path("foo")}" gem "activesupport", "2.3.5" @@ -491,12 +454,50 @@ 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 expect(out).to match(Regexp.new(expected_output)) end + + it "does not require gems to be installed" do + build_repo4 do + build_gem "zeitwerk", "1.0.0" + build_gem "zeitwerk", "2.0.0" + end + + gemfile <<-G + source "https://gem.repo4" + gem "zeitwerk" + G + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + zeitwerk (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + zeitwerk + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "outdated zeitwerk", raise_on_error: false + + expected_output = <<~TABLE.tr(".", "\.").strip + Gem Current Latest Requested Groups Release Date + zeitwerk 1.0.0 2.0.0 >= 0 default + TABLE + + expect(out).to match(Regexp.new(expected_output)) + expect(err).to be_empty + end end describe "pre-release gems" do @@ -507,7 +508,7 @@ RSpec.describe "bundle outdated" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "zebra", :git => "#{lib_path("zebra")}" gem "foo", :git => "#{lib_path("foo")}" gem "activesupport", "2.3.5" @@ -538,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 @@ -554,14 +555,14 @@ RSpec.describe "bundle outdated" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport", "3.0.0.beta.1" G 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 @@ -578,7 +579,7 @@ RSpec.describe "bundle outdated" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "zebra", :git => "#{lib_path("zebra")}" gem "foo", :git => "#{lib_path("foo")}" gem "activesupport", "2.3.5" @@ -597,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 @@ -613,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 @@ -622,7 +623,7 @@ RSpec.describe "bundle outdated" do it "doesn't crash when some deps unused on the current platform" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport", platforms: [:ruby_22] G @@ -633,8 +634,8 @@ RSpec.describe "bundle outdated" do it "only reports gem dependencies when they can actually be updated" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack_middleware", "1.0" + source "https://gem.repo2" + gem "myrack_middleware", "1.0" G bundle :outdated, "filter-strict": true @@ -645,7 +646,7 @@ RSpec.describe "bundle outdated" do describe "and filter options" do it "only reports gems that match requirement and patch filter level" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport", "~> 2.3" gem "weakling", ">= 0.0.1" G @@ -658,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 @@ -667,7 +668,7 @@ RSpec.describe "bundle outdated" do it "only reports gems that match requirement and minor filter level" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport", "~> 2.3" gem "weakling", ">= 0.0.1" G @@ -680,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 @@ -689,7 +690,7 @@ RSpec.describe "bundle outdated" do it "only reports gems that match requirement and major filter level" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport", "~> 2.3" gem "weakling", ">= 0.0.1" G @@ -702,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 @@ -719,7 +720,7 @@ RSpec.describe "bundle outdated" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "zebra", :git => "#{lib_path("zebra")}" gem "foo", :git => "#{lib_path("foo")}" gem "activesupport", "2.3.5" @@ -742,28 +743,28 @@ RSpec.describe "bundle outdated" do it "performs an automatic bundle install" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "0.9.1" + source "https://gem.repo1" + gem "myrack", "0.9.1" 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 gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" - gem "rack" + gem "myrack" 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 @@ -786,12 +787,12 @@ RSpec.describe "bundle outdated" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" - gem "rack" + 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 @@ -811,7 +812,7 @@ RSpec.describe "bundle outdated" do build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "laduradura", '= 5.15.2' G end @@ -829,7 +830,7 @@ RSpec.describe "bundle outdated" do it "reports that updates are available if the Ruby platform is used" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "laduradura", '= 5.15.2', :platforms => [:ruby, :jruby] G @@ -839,14 +840,14 @@ RSpec.describe "bundle outdated" do it "reports that updates are available if the JRuby platform is used", :jruby_only do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "laduradura", '= 5.15.2', :platforms => [:ruby, :jruby] G 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 @@ -872,7 +873,7 @@ RSpec.describe "bundle outdated" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "zebra", :git => "#{lib_path("zebra")}" gem "foo", :git => "#{lib_path("foo")}" gem "activesupport", "2.3.5" @@ -898,7 +899,7 @@ RSpec.describe "bundle outdated" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "zebra", :git => "#{lib_path("zebra")}" gem "foo", :git => "#{lib_path("foo")}" gem "activesupport", "2.3.5" @@ -907,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 @@ -928,7 +929,7 @@ RSpec.describe "bundle outdated" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "zebra", :git => "#{lib_path("zebra")}" gem "foo", :git => "#{lib_path("foo")}" gem "activesupport", "2.3.5" @@ -954,7 +955,7 @@ RSpec.describe "bundle outdated" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "zebra", :git => "#{lib_path("zebra")}" gem "foo", :git => "#{lib_path("foo")}" gem "activesupport", "2.3.5" @@ -987,7 +988,7 @@ RSpec.describe "bundle outdated" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "zebra", :git => "#{lib_path("zebra")}" gem "foo", :git => "#{lib_path("foo")}" gem "activesupport", "2.3.5" @@ -1013,7 +1014,7 @@ RSpec.describe "bundle outdated" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "zebra", :git => "#{lib_path("zebra")}" gem "foo", :git => "#{lib_path("foo")}" gem "activesupport", "2.3.5" @@ -1039,7 +1040,7 @@ RSpec.describe "bundle outdated" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "zebra", :git => "#{lib_path("zebra")}" gem "foo", :git => "#{lib_path("foo")}" gem "activesupport", "2.3.5" @@ -1123,7 +1124,7 @@ RSpec.describe "bundle outdated" do # establish a lockfile set to 1.0.0 install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem 'patch', '1.0.0' gem 'minor', '1.0.0' gem 'major', '1.0.0' @@ -1131,7 +1132,7 @@ RSpec.describe "bundle outdated" do # remove all version requirements gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem 'patch' gem 'minor' gem 'major' @@ -1148,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 @@ -1161,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 @@ -1196,7 +1197,7 @@ RSpec.describe "bundle outdated" do # establish a lockfile set to 1.4.3 install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem 'foo', '1.4.3' gem 'bar', '2.0.3' gem 'qux', '1.0.0' @@ -1205,7 +1206,7 @@ RSpec.describe "bundle outdated" do # remove 1.4.3 requirement and bar altogether # to setup update specs below gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem 'foo' gem 'qux' G @@ -1215,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 @@ -1227,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 @@ -1246,20 +1247,20 @@ RSpec.describe "bundle outdated" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem 'weakling', '0.2' gem 'bar', '2.1' G gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem 'weakling' G 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 @@ -1283,7 +1284,7 @@ RSpec.describe "bundle outdated" do lockfile <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: nokogiri (1.11.1) nokogiri (1.11.1-#{Bundler.local_platform}) @@ -1296,11 +1297,11 @@ RSpec.describe "bundle outdated" do nokogiri BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "nokogiri" G end @@ -1309,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 @@ -1330,14 +1331,14 @@ RSpec.describe "bundle outdated" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "mini_portile2" G lockfile <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: mini_portile2 (2.5.2) net-ftp (~> 0.1) @@ -1350,7 +1351,7 @@ RSpec.describe "bundle outdated" do mini_portile2 BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -1358,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 61e615acae..9d7354c54f 100644 --- a/spec/bundler/commands/platform_spec.rb +++ b/spec/bundler/commands/platform_spec.rb @@ -4,7 +4,7 @@ RSpec.describe "bundle platform" do context "without flags" do it "returns all the output" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" #{ruby_version_correct} @@ -27,7 +27,7 @@ G it "returns all the output including the patchlevel" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" #{ruby_version_correct_patchlevel} @@ -50,7 +50,7 @@ G it "doesn't print ruby version requirement if it isn't specified" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo" G @@ -68,7 +68,7 @@ G it "doesn't match the ruby version requirement" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" #{ruby_version_incorrect} @@ -93,7 +93,7 @@ G context "--ruby" do it "returns ruby version when explicit" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" ruby "1.9.3", :engine => 'ruby', :engine_version => '1.9.3' gem "foo" @@ -106,7 +106,7 @@ G it "defaults to MRI" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" ruby "1.9.3" gem "foo" @@ -119,7 +119,7 @@ G it "handles jruby" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" ruby "1.8.7", :engine => 'jruby', :engine_version => '1.6.5' gem "foo" @@ -132,7 +132,7 @@ G it "handles rbx" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" ruby "1.8.7", :engine => 'rbx', :engine_version => '1.2.4' gem "foo" @@ -145,7 +145,7 @@ G it "handles truffleruby" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" ruby "2.5.1", :engine => 'truffleruby', :engine_version => '1.0.0-rc6' gem "foo" @@ -158,7 +158,7 @@ G it "raises an error if engine is used but engine version is not" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" ruby "1.8.7", :engine => 'rbx' gem "foo" @@ -171,7 +171,7 @@ G it "raises an error if engine_version is used but engine is not" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" ruby "1.8.7", :engine_version => '1.2.4' gem "foo" @@ -184,7 +184,7 @@ G it "raises an error if engine version doesn't match ruby version for MRI" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" ruby "1.8.7", :engine => 'ruby', :engine_version => '1.2.4' gem "foo" @@ -197,7 +197,7 @@ G it "should print if no ruby version is specified" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo" G @@ -209,13 +209,13 @@ G it "handles when there is a locked requirement" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" ruby "< 1.8.7" G lockfile <<-L GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS @@ -227,7 +227,7 @@ G ruby 1.0.0p127 BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "platform --ruby" @@ -236,12 +236,12 @@ G it "handles when there is a lockfile with no requirement" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G lockfile <<-L GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS @@ -250,7 +250,7 @@ G DEPENDENCIES BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "platform --ruby" @@ -259,7 +259,7 @@ G it "handles when there is a requirement in the gemfile" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" ruby ">= 1.8.7" G @@ -269,7 +269,7 @@ G it "handles when there are multiple requirements in the gemfile" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" ruby ">= 1.8.7", "< 2.0.0" G @@ -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 @@ -315,8 +315,8 @@ G context "bundle install" do it "installs fine when the ruby version matches" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" #{ruby_version_correct} G @@ -326,8 +326,8 @@ G it "installs fine with any engine", :jruby_only do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" #{ruby_version_correct_engineless} G @@ -337,8 +337,8 @@ G it "installs fine when the patchlevel matches" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" #{ruby_version_correct_patchlevel} G @@ -348,8 +348,8 @@ G it "doesn't install when the ruby version doesn't match" do install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" #{ruby_version_incorrect} G @@ -360,8 +360,8 @@ G it "doesn't install when engine doesn't match" do install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" #{engine_incorrect} G @@ -372,8 +372,8 @@ G it "doesn't install when engine version doesn't match", :jruby_only do install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" #{engine_version_incorrect} G @@ -382,29 +382,29 @@ 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 "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" #{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 context "bundle check" do it "checks fine when the ruby version matches" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" #{ruby_version_correct} G @@ -415,13 +415,13 @@ G it "checks fine with any engine", :jruby_only do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" #{ruby_version_correct_engineless} G @@ -432,13 +432,13 @@ G it "fails when ruby version doesn't match" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" #{ruby_version_incorrect} G @@ -449,13 +449,13 @@ G it "fails when engine doesn't match" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" #{engine_incorrect} G @@ -466,13 +466,13 @@ G it "fails when engine version doesn't match", :jruby_only do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" #{engine_version_incorrect} G @@ -481,21 +481,21 @@ 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 "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" #{patchlevel_incorrect} G - bundle :check, raise_on_error: false - should_be_patchlevel_incorrect + bundle :check + should_ignore_patchlevel end end @@ -504,57 +504,57 @@ G build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport" - gem "rack-obama" + gem "myrack-obama" G end it "updates successfully when the ruby version matches" do gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport" - gem "rack-obama" + gem "myrack-obama" #{ruby_version_correct} G update_repo2 do - build_gem "rack", "1.2" do |s| - s.executables = "rackup" + build_gem "myrack", "1.2" do |s| + s.executables = "myrackup" end build_gem "activesupport", "3.0" end bundle "update", all: true - expect(the_bundle).to include_gems "rack 1.2", "rack-obama 1.0", "activesupport 3.0" + expect(the_bundle).to include_gems "myrack 1.2", "myrack-obama 1.0", "activesupport 3.0" end it "updates fine with any engine", :jruby_only do gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport" - gem "rack-obama" + gem "myrack-obama" #{ruby_version_correct_engineless} G update_repo2 do - build_gem "rack", "1.2" do |s| - s.executables = "rackup" + build_gem "myrack", "1.2" do |s| + s.executables = "myrackup" end build_gem "activesupport", "3.0" end bundle "update", all: true - expect(the_bundle).to include_gems "rack 1.2", "rack-obama 1.0", "activesupport 3.0" + expect(the_bundle).to include_gems "myrack 1.2", "myrack-obama 1.0", "activesupport 3.0" end it "fails when ruby version doesn't match" do gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport" - gem "rack-obama" + gem "myrack-obama" #{ruby_version_incorrect} G @@ -568,9 +568,9 @@ G it "fails when ruby engine doesn't match", :jruby_only do gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport" - gem "rack-obama" + gem "myrack-obama" #{engine_incorrect} G @@ -584,9 +584,9 @@ G it "fails when ruby engine version doesn't match", :jruby_only do gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport" - gem "rack-obama" + gem "myrack-obama" #{engine_version_incorrect} G @@ -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 "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo2" + gem "activesupport" #{patchlevel_incorrect} G @@ -609,22 +609,23 @@ 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 context "bundle info" do before do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rails" G end it "prints path if ruby version is correct" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rails" #{ruby_version_correct} @@ -636,7 +637,7 @@ G it "prints path if ruby version is correct for any engine", :jruby_only do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rails" #{ruby_version_correct_engineless} @@ -646,9 +647,9 @@ 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rails" #{ruby_version_incorrect} @@ -658,9 +659,9 @@ 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rails" #{engine_incorrect} @@ -670,9 +671,9 @@ 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rails" #{engine_version_incorrect} @@ -682,58 +683,56 @@ 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 "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + 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 context "bundle cache" do before do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' G end it "copies the .gem file to vendor/cache when ruby version matches" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' #{ruby_version_correct} G bundle :cache - expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist end it "copies the .gem file to vendor/cache when ruby version matches for any engine", :jruby_only do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' #{ruby_version_correct_engineless} G bundle :cache - expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist end it "fails if the ruby version doesn't match" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' #{ruby_version_incorrect} G @@ -744,8 +743,8 @@ G it "fails if the engine doesn't match" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' #{engine_incorrect} G @@ -756,8 +755,8 @@ G it "fails if the engine version doesn't match", :jruby_only do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' #{engine_version_incorrect} G @@ -766,55 +765,56 @@ 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 "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" #{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 context "bundle pack" do before do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' G end it "copies the .gem file to vendor/cache when ruby version matches" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' #{ruby_version_correct} G bundle :cache - expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist end it "copies the .gem file to vendor/cache when ruby version matches any engine", :jruby_only do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' #{ruby_version_correct_engineless} G bundle :cache - expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist end it "fails if the ruby version doesn't match" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' #{ruby_version_incorrect} G @@ -825,8 +825,8 @@ G it "fails if the engine doesn't match" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' #{engine_incorrect} G @@ -837,8 +837,8 @@ G it "fails if the engine version doesn't match", :jruby_only do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' #{engine_version_incorrect} G @@ -847,205 +847,174 @@ 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 "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" #{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 context "bundle exec" do before do ENV["BUNDLER_FORCE_TTY"] = "true" - system_gems "rack-1.0.0", "rack-0.9.1", path: default_bundle_path + system_gems "myrack-1.0.0", "myrack-0.9.1", path: default_bundle_path end it "activates the correct gem when ruby version matches" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "0.9.1" + source "https://gem.repo1" + gem "myrack", "0.9.1" #{ruby_version_correct} G - bundle "exec rackup" + bundle "exec myrackup" expect(out).to include("0.9.1") end it "activates the correct gem when ruby version matches any engine", :jruby_only do - system_gems "rack-1.0.0", "rack-0.9.1", path: default_bundle_path + system_gems "myrack-1.0.0", "myrack-0.9.1", path: default_bundle_path gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "0.9.1" + source "https://gem.repo1" + gem "myrack", "0.9.1" #{ruby_version_correct_engineless} G - bundle "exec rackup" + bundle "exec myrackup" expect(out).to include("0.9.1") end it "fails when the ruby version doesn't match" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "0.9.1" + source "https://gem.repo1" + gem "myrack", "0.9.1" #{ruby_version_incorrect} G - bundle "exec rackup", raise_on_error: false + bundle "exec myrackup", raise_on_error: false should_be_ruby_version_incorrect end it "fails when the engine doesn't match" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "0.9.1" + source "https://gem.repo1" + gem "myrack", "0.9.1" #{engine_incorrect} G - bundle "exec rackup", raise_on_error: false + bundle "exec myrackup", raise_on_error: false should_be_engine_incorrect end - # it "fails when the engine version doesn't match", :jruby_only do - # gemfile <<-G - # gem "rack", "0.9.1" - # - # #{engine_version_incorrect} - # G - # - # bundle "exec rackup" - # should_be_engine_version_incorrect - # end + it "fails when the engine version doesn't match", :jruby_only do + gemfile <<-G + gem "myrack", "0.9.1" + + #{engine_version_incorrect} + G + + bundle "exec myrackup", raise_on_error: false + 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 "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" #{patchlevel_incorrect} G - bundle "exec rackup", 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 "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo2" + gem "irb" + gem "myrack" gem "activesupport", :group => :test - gem "rack_middleware", :group => :development + gem "myrack_middleware", :group => :development G end it "starts IRB with the default group loaded when ruby version matches", :readline do - gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" - gem "activesupport", :group => :test - gem "rack_middleware", :group => :development - - #{ruby_version_correct} - G + gemfile gemfile + "\n\n#{ruby_version_correct}\n" bundle "console" do |input, _, _| - input.puts("puts RACK") + input.puts("puts MYRACK") input.puts("exit") end expect(out).to include("0.9.1") end it "starts IRB with the default group loaded when ruby version matches", :readline, :jruby_only do - gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" - gem "activesupport", :group => :test - gem "rack_middleware", :group => :development - - #{ruby_version_correct_engineless} - G + gemfile gemfile + "\n\n#{ruby_version_correct_engineless}\n" bundle "console" do |input, _, _| - input.puts("puts RACK") + input.puts("puts MYRACK") input.puts("exit") end expect(out).to include("0.9.1") end it "fails when ruby version doesn't match" do - gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" - gem "activesupport", :group => :test - gem "rack_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 "#{file_uri_for(gem_repo1)}" - gem "rack" - gem "activesupport", :group => :test - gem "rack_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 "#{file_uri_for(gem_repo1)}" - gem "rack" - gem "activesupport", :group => :test - gem "rack_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 "#{file_uri_for(gem_repo1)}" - gem "rack" - gem "activesupport", :group => :test - gem "rack_middleware", :group => :development - - #{patchlevel_incorrect} - G + it "starts IRB with the default group loaded even when patchlevel doesn't match", :readline do + gemfile gemfile + "\n\n#{patchlevel_incorrect}\n" - 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 context "Bundler.setup" do before do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "yard" - gem "rack", :group => :test + gem "myrack", :group => :test G ENV["BUNDLER_FORCE_TTY"] = "true" @@ -1053,9 +1022,9 @@ G it "makes a Gemfile.lock if setup succeeds" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "yard" - gem "rack" + gem "myrack" #{ruby_version_correct} G @@ -1068,9 +1037,9 @@ G it "makes a Gemfile.lock if setup succeeds for any engine", :jruby_only do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "yard" - gem "rack" + gem "myrack" #{ruby_version_correct_engineless} G @@ -1083,9 +1052,9 @@ G it "fails when ruby version doesn't match" do install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "yard" - gem "rack" + gem "myrack" #{ruby_version_incorrect} G @@ -1100,9 +1069,9 @@ G it "fails when engine doesn't match" do install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "yard" - gem "rack" + gem "myrack" #{engine_incorrect} G @@ -1117,9 +1086,9 @@ G it "fails when engine version doesn't match", :jruby_only do install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "yard" - gem "rack" + gem "myrack" #{engine_version_incorrect} G @@ -1132,21 +1101,21 @@ 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "yard" - gem "rack" + gem "myrack" #{patchlevel_incorrect} 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 @@ -1157,7 +1126,7 @@ G end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport", "2.3.5" gem "foo", :git => "#{lib_path("foo")}" G @@ -1170,7 +1139,7 @@ G end gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport", "2.3.5" gem "foo", :git => "#{lib_path("foo")}" @@ -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 @@ -1196,7 +1165,7 @@ G end gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport", "2.3.5" gem "foo", :git => "#{lib_path("foo")}" @@ -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 @@ -1221,7 +1190,7 @@ G end gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport", "2.3.5" gem "foo", :git => "#{lib_path("foo")}" @@ -1239,7 +1208,7 @@ G end gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport", "2.3.5" gem "foo", :git => "#{lib_path("foo")}" @@ -1257,7 +1226,7 @@ G end gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport", "2.3.5" gem "foo", :git => "#{lib_path("foo")}" @@ -1268,14 +1237,14 @@ 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") end gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport", "2.3.5" gem "foo", :git => "#{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 "#{file_uri_for(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 07fd5a79e9..088fc29fe1 100644 --- a/spec/bundler/commands/post_bundle_message_spec.rb +++ b/spec/bundler/commands/post_bundle_message_spec.rb @@ -3,13 +3,13 @@ RSpec.describe "post bundle message" do before :each do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" gem "activesupport", "2.3.5", :group => [:emo, :test] group :test do gem "rspec" end - gem "rack-obama", :group => :obama + gem "myrack-obama", :group => :obama G end @@ -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 "#{file_uri_for(gem_repo1)}" - gem "rack" - gem "not-a-gem", :group => :development - G - expect(err).to include <<-EOS.strip -Could not find gem 'not-a-gem' in rubygems repository #{file_uri_for(gem_repo1)}/, cached gems 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 "#{file_uri_for(gem_repo1)}" - gem "rack" - G - bundle :cache - expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist - install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo1)}" - gem "rack" - gem "not-a-gem", :group => :development - G - expect(err).to include("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 #{file_uri_for(gem_repo1)} 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 1aec37f850..5f80b9e534 100644 --- a/spec/bundler/commands/pristine_spec.rb +++ b/spec/bundler/commands/pristine_spec.rb @@ -19,7 +19,7 @@ RSpec.describe "bundle pristine" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "weakling" gem "very_simple_binary" gem "foo", :git => "#{lib_path("foo")}", :branch => "main" @@ -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 197fcde091..8a2e6778ea 100644 --- a/spec/bundler/commands/remove_spec.rb +++ b/spec/bundler/commands/remove_spec.rb @@ -4,7 +4,7 @@ RSpec.describe "bundle remove" do context "when no gems are specified" do it "throws error" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G bundle "remove", raise_on_error: false @@ -16,85 +16,70 @@ RSpec.describe "bundle remove" do context "after 'bundle install' is run" do describe "running 'bundle remove GEM_NAME'" do it "removes it from the lockfile" do - rack_dep = <<~L + myrack_dep = <<~L DEPENDENCIES - rack + myrack L gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack" + gem "myrack" G bundle "install" - expect(lockfile).to include(rack_dep) + expect(lockfile).to include(myrack_dep) - bundle "remove rack" + bundle "remove myrack" expect(gemfile).to eq <<~G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G - expect(lockfile).to_not include(rack_dep) + expect(lockfile).to_not include(myrack_dep) end end end - context "when --install flag is specified", bundler: "< 3" do - it "removes gems from .bundle" do - gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - - gem "rack" - G - - bundle "remove rack --install" - - expect(out).to include("rack was removed.") - expect(the_bundle).to_not include_gems "rack" - end - end - describe "remove single gem from gemfile" do context "when gem is present in gemfile" do it "shows success for removed gem" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack" + gem "myrack" G - bundle "remove rack" + bundle "remove myrack" - expect(out).to include("rack was removed.") - expect(the_bundle).to_not include_gems "rack" + expect(out).to include("myrack was removed.") + expect(the_bundle).to_not include_gems "myrack" expect(gemfile).to eq <<~G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G end context "when gem is specified in multiple lines" do it "shows success for removed gem" do - build_git "rack" + build_git "myrack" gemfile <<-G - source '#{file_uri_for(gem_repo1)}' + source 'https://gem.repo1' gem 'git' - gem 'rack', - git: "#{lib_path("rack-1.0")}", + gem 'myrack', + git: "#{lib_path("myrack-1.0")}", branch: 'main' gem 'nokogiri' G - bundle "remove rack" + bundle "remove myrack" - expect(out).to include("rack was removed.") + expect(out).to include("myrack was removed.") expect(gemfile).to eq <<~G - source '#{file_uri_for(gem_repo1)}' + source 'https://gem.repo1' gem 'git' gem 'nokogiri' @@ -106,12 +91,12 @@ RSpec.describe "bundle remove" do context "when gem is not present in gemfile" do it "shows warning for gem that could not be removed" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G - bundle "remove rack", raise_on_error: false + bundle "remove myrack", raise_on_error: false - expect(err).to include("`rack` is not specified in #{bundled_app_gemfile} so it could not be removed.") + expect(err).to include("`myrack` is not specified in #{bundled_app_gemfile} so it could not be removed.") end end end @@ -120,18 +105,18 @@ RSpec.describe "bundle remove" do context "when all gems are present in gemfile" do it "shows success fir all removed gems" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack" + gem "myrack" gem "rails" G - bundle "remove rack rails" + bundle "remove myrack rails" - expect(out).to include("rack was removed.") + expect(out).to include("myrack was removed.") expect(out).to include("rails was removed.") expect(gemfile).to eq <<~G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G end end @@ -139,18 +124,18 @@ RSpec.describe "bundle remove" do context "when some gems are not present in the gemfile" do it "shows warning for those not present and success for those that can be removed" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rails" gem "minitest" gem "rspec" G - bundle "remove rails rack minitest", raise_on_error: false + bundle "remove rails myrack minitest", raise_on_error: false - expect(err).to include("`rack` is not specified in #{bundled_app_gemfile} so it could not be removed.") + expect(err).to include("`myrack` is not specified in #{bundled_app_gemfile} so it could not be removed.") expect(gemfile).to eq <<~G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rails" gem "minitest" @@ -163,16 +148,16 @@ RSpec.describe "bundle remove" do context "with inline groups" do it "removes the specified gem" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack", :group => [:dev] + gem "myrack", :group => [:dev] G - bundle "remove rack" + bundle "remove myrack" - expect(out).to include("rack was removed.") + expect(out).to include("myrack was removed.") expect(gemfile).to eq <<~G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G end end @@ -181,7 +166,7 @@ RSpec.describe "bundle remove" do context "when single group block with gem to be removed is present" do it "removes the group block" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" group :test do gem "rspec" @@ -192,7 +177,7 @@ RSpec.describe "bundle remove" do expect(out).to include("rspec was removed.") expect(gemfile).to eq <<~G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G end end @@ -200,19 +185,19 @@ RSpec.describe "bundle remove" do context "when gem to be removed is outside block" do it "does not modify group" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack" + gem "myrack" group :test do gem "coffee-script-source" end G - bundle "remove rack" + bundle "remove myrack" - expect(out).to include("rack was removed.") + expect(out).to include("myrack was removed.") expect(gemfile).to eq <<~G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" group :test do gem "coffee-script-source" @@ -224,7 +209,7 @@ RSpec.describe "bundle remove" do context "when an empty block is also present" do it "removes all empty blocks" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" group :test do gem "rspec" @@ -238,7 +223,7 @@ RSpec.describe "bundle remove" do expect(out).to include("rspec was removed.") expect(gemfile).to eq <<~G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G end end @@ -246,7 +231,7 @@ RSpec.describe "bundle remove" do context "when the gem belongs to multiple groups" do it "removes the groups" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" group :test, :serioustest do gem "rspec" @@ -257,7 +242,7 @@ RSpec.describe "bundle remove" do expect(out).to include("rspec was removed.") expect(gemfile).to eq <<~G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G end end @@ -265,7 +250,7 @@ RSpec.describe "bundle remove" do context "when the gem is present in multiple groups" do it "removes all empty blocks" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" group :one do gem "rspec" @@ -280,7 +265,7 @@ RSpec.describe "bundle remove" do expect(out).to include("rspec was removed.") expect(gemfile).to eq <<~G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G end end @@ -290,7 +275,7 @@ RSpec.describe "bundle remove" do context "when all the groups will be empty after removal" do it "removes the empty nested blocks" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" group :test do group :serioustest do @@ -303,7 +288,7 @@ RSpec.describe "bundle remove" do expect(out).to include("rspec was removed.") expect(gemfile).to eq <<~G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G end end @@ -311,10 +296,10 @@ RSpec.describe "bundle remove" do context "when outer group will not be empty after removal" do it "removes only empty blocks" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" group :test do - gem "rack-test" + gem "myrack-test" group :serioustest do gem "rspec" @@ -326,10 +311,10 @@ RSpec.describe "bundle remove" do expect(out).to include("rspec was removed.") expect(gemfile).to eq <<~G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" group :test do - gem "rack-test" + gem "myrack-test" end G @@ -339,12 +324,12 @@ RSpec.describe "bundle remove" do context "when inner group will not be empty after removal" do it "removes only empty blocks" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" group :test do group :serioustest do gem "rspec" - gem "rack-test" + gem "myrack-test" end end G @@ -353,11 +338,11 @@ RSpec.describe "bundle remove" do expect(out).to include("rspec was removed.") expect(gemfile).to eq <<~G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" group :test do group :serioustest do - gem "rack-test" + gem "myrack-test" end end G @@ -369,16 +354,16 @@ RSpec.describe "bundle remove" do context "when multiple gems are present in same line" do it "shows warning for gems not removed" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack"; gem "rails" + source "https://gem.repo1" + gem "myrack"; gem "rails" G bundle "remove rails", raise_on_error: false - expect(err).to include("Gems could not be removed. rack (>= 0) would also have been removed.") + expect(err).to include("Gems could not be removed. myrack (>= 0) would also have been removed.") expect(gemfile).to eq <<~G - source "#{file_uri_for(gem_repo1)}" - gem "rack"; gem "rails" + source "https://gem.repo1" + gem "myrack"; gem "rails" G end end @@ -386,21 +371,21 @@ RSpec.describe "bundle remove" do context "when some gems could not be removed" do it "shows warning for gems not removed and success for those removed" do install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo1)}" - gem"rack" + source "https://gem.repo1" + gem"myrack" gem"rspec" gem "rails" gem "minitest" G - bundle "remove rails rack rspec minitest" + bundle "remove rails myrack rspec minitest" expect(out).to include("rails was removed.") expect(out).to include("minitest was removed.") - expect(out).to include("rack, rspec could not be removed.") + expect(out).to include("myrack, rspec could not be removed.") expect(gemfile).to eq <<~G - source "#{file_uri_for(gem_repo1)}" - gem"rack" + source "https://gem.repo1" + gem"myrack" gem"rspec" G end @@ -409,18 +394,18 @@ RSpec.describe "bundle remove" do context "with sources" do before do - build_repo gem_repo3 do + build_repo3 do build_gem "rspec" end end it "removes gems and empty source blocks" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack" + gem "myrack" - source "#{file_uri_for(gem_repo3)}" do + source "https://gem.repo3" do gem "rspec" end G @@ -431,9 +416,9 @@ RSpec.describe "bundle remove" do expect(out).to include("rspec was removed.") expect(gemfile).to eq <<~G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack" + gem "myrack" G end end @@ -441,40 +426,40 @@ RSpec.describe "bundle remove" do describe "with eval_gemfile" do context "when gems are present in both gemfiles" do it "removes the gems" do - create_file "Gemfile-other", <<-G - gem "rack" + gemfile "Gemfile-other", <<-G + gem "myrack" G install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" eval_gemfile "Gemfile-other" - gem "rack" + gem "myrack" G - bundle "remove rack" + bundle "remove myrack" - expect(out).to include("rack was removed.") + expect(out).to include("myrack was removed.") end end context "when gems are present in other gemfile" do it "removes the gems" do - create_file "Gemfile-other", <<-G - gem "rack" + gemfile "Gemfile-other", <<-G + gem "myrack" G install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" eval_gemfile "Gemfile-other" G - bundle "remove rack" + bundle "remove myrack" - expect(bundled_app("Gemfile-other").read).to_not include("gem \"rack\"") - expect(out).to include("rack was removed.") + expect(bundled_app("Gemfile-other").read).to_not include("gem \"myrack\"") + expect(out).to include("myrack was removed.") end end @@ -486,36 +471,36 @@ RSpec.describe "bundle remove" do G install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" eval_gemfile "Gemfile-other" G - bundle "remove rack", raise_on_error: false + bundle "remove myrack", raise_on_error: false - expect(err).to include("`rack` is not specified in #{bundled_app_gemfile} so it could not be removed.") + expect(err).to include("`myrack` is not specified in #{bundled_app_gemfile} so it could not be removed.") end end context "when the gem is present in parent file but not in gemfile specified by eval_gemfile" do it "removes the gem" do - create_file "Gemfile-other", <<-G + gemfile "Gemfile-other", <<-G gem "rails" G install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" eval_gemfile "Gemfile-other" - gem "rack" + gem "myrack" G - bundle "remove rack", raise_on_error: false + bundle "remove myrack", raise_on_error: false - expect(out).to include("rack was removed.") - expect(err).to include("`rack` is not specified in #{bundled_app("Gemfile-other")} so it could not be removed.") + expect(out).to include("myrack was removed.") + expect(err).to include("`myrack` is not specified in #{bundled_app("Gemfile-other")} so it could not be removed.") expect(gemfile).to eq <<~G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" eval_gemfile "Gemfile-other" G @@ -524,23 +509,23 @@ RSpec.describe "bundle remove" do context "when gems cannot be removed from other gemfile" do it "shows error" do - create_file "Gemfile-other", <<-G - gem "rails"; gem "rack" + gemfile "Gemfile-other", <<-G + gem "rails"; gem "myrack" G install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" eval_gemfile "Gemfile-other" - gem "rack" + gem "myrack" G - bundle "remove rack", raise_on_error: false + bundle "remove myrack", raise_on_error: false - expect(out).to include("rack was removed.") + expect(out).to include("myrack was removed.") expect(err).to include("Gems could not be removed. rails (>= 0) would also have been removed.") expect(gemfile).to eq <<~G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" eval_gemfile "Gemfile-other" G @@ -549,47 +534,47 @@ RSpec.describe "bundle remove" do context "when gems could not be removed from parent gemfile" do it "shows error" do - create_file "Gemfile-other", <<-G - gem "rack" + gemfile "Gemfile-other", <<-G + gem "myrack" G install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" eval_gemfile "Gemfile-other" - gem "rails"; gem "rack" + gem "rails"; gem "myrack" G - bundle "remove rack", raise_on_error: false + bundle "remove myrack", raise_on_error: false expect(err).to include("Gems could not be removed. rails (>= 0) would also have been removed.") - expect(bundled_app("Gemfile-other").read).to include("gem \"rack\"") + expect(bundled_app("Gemfile-other").read).to include("gem \"myrack\"") expect(gemfile).to eq <<~G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" eval_gemfile "Gemfile-other" - gem "rails"; gem "rack" + gem "rails"; gem "myrack" G end end context "when gem present in gemfiles but could not be removed from one from one of them" do it "removes gem which can be removed and shows warning for file from which it cannot be removed" do - create_file "Gemfile-other", <<-G - gem "rack" + gemfile "Gemfile-other", <<-G + gem "myrack" G install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" eval_gemfile "Gemfile-other" - gem"rack" + gem"myrack" G - bundle "remove rack" + bundle "remove myrack" - expect(out).to include("rack was removed.") - expect(bundled_app("Gemfile-other").read).to_not include("gem \"rack\"") + expect(out).to include("myrack was removed.") + expect(bundled_app("Gemfile-other").read).to_not include("gem \"myrack\"") end end end @@ -597,18 +582,18 @@ RSpec.describe "bundle remove" do context "with install_if" do it "removes gems inside blocks and empty blocks" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" install_if(lambda { false }) do - gem "rack" + gem "myrack" end G - bundle "remove rack" + bundle "remove myrack" - expect(out).to include("rack was removed.") + expect(out).to include("myrack was removed.") expect(gemfile).to eq <<~G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G end end @@ -616,32 +601,32 @@ RSpec.describe "bundle remove" do context "with env" do it "removes gems inside blocks and empty blocks" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" env "BUNDLER_TEST" do - gem "rack" + gem "myrack" end G - bundle "remove rack" + bundle "remove myrack" - expect(out).to include("rack was removed.") + expect(out).to include("myrack was removed.") expect(gemfile).to eq <<~G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G end end context "with gemspec" do it "should not remove the gem" do - build_lib("foo", path: tmp.join("foo")) do |s| + build_lib("foo", path: tmp("foo")) do |s| s.write("foo.gemspec", "") - s.add_dependency "rack" + s.add_dependency "myrack" end install_gemfile(<<-G) - source "#{file_uri_for(gem_repo1)}" - gemspec :path => '#{tmp.join("foo")}', :name => 'foo' + source "https://gem.repo1" + gemspec :path => '#{tmp("foo")}', :name => 'foo' G bundle "remove foo" @@ -654,19 +639,19 @@ RSpec.describe "bundle remove" do context "when comment is a separate line comment" do it "does not remove the line comment" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - # gem "rack" might be used in the future - gem "rack" + # gem "myrack" might be used in the future + gem "myrack" G - bundle "remove rack" + bundle "remove myrack" - expect(out).to include("rack was removed.") + expect(out).to include("myrack was removed.") expect(gemfile).to eq <<~G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - # gem "rack" might be used in the future + # gem "myrack" might be used in the future G end end @@ -674,16 +659,16 @@ RSpec.describe "bundle remove" do context "when gem specified for removal has an inline comment" do it "removes the inline comment" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack" # this can be removed + gem "myrack" # this can be removed G - bundle "remove rack" + bundle "remove myrack" - expect(out).to include("rack was removed.") + expect(out).to include("myrack was removed.") expect(gemfile).to eq <<~G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G end end @@ -691,19 +676,19 @@ RSpec.describe "bundle remove" do context "when gem specified for removal is mentioned in other gem's comment" do it "does not remove other gem" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "puma" # implements interface provided by gem "rack" + source "https://gem.repo1" + gem "puma" # implements interface provided by gem "myrack" - gem "rack" + gem "myrack" G - bundle "remove rack" + bundle "remove myrack" expect(out).to_not include("puma was removed.") - expect(out).to include("rack was removed.") + expect(out).to include("myrack was removed.") expect(gemfile).to eq <<~G - source "#{file_uri_for(gem_repo1)}" - gem "puma" # implements interface provided by gem "rack" + source "https://gem.repo1" + gem "puma" # implements interface provided by gem "myrack" G end end @@ -711,22 +696,41 @@ RSpec.describe "bundle remove" do context "when gem specified for removal has a comment that mentions other gem" do it "does not remove other gem" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "puma" # implements interface provided by gem "rack" + source "https://gem.repo1" + gem "puma" # implements interface provided by gem "myrack" - gem "rack" + gem "myrack" G bundle "remove puma" expect(out).to include("puma was removed.") - expect(out).to_not include("rack was removed.") + expect(out).to_not include("myrack was removed.") expect(gemfile).to eq <<~G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack" + gem "myrack" G end end end + + context "when gem definition has parentheses" do + it "removes the gem" do + gemfile <<-G + source "https://gem.repo1" + + gem("myrack") + gem("myrack", ">= 0") + gem("myrack", require: false) + G + + bundle "remove myrack" + + expect(out).to include("myrack was removed.") + expect(gemfile).to eq <<~G + source "https://gem.repo1" + G + end + end end diff --git a/spec/bundler/commands/show_spec.rb b/spec/bundler/commands/show_spec.rb index 2b6d4d2d00..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 "#{file_uri_for(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 @@ -155,11 +175,11 @@ RSpec.describe "bundle show", bundler: "< 3" do it "performs an automatic bundle install" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" 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 @@ -167,20 +187,20 @@ RSpec.describe "bundle show", bundler: "< 3" do context "with a valid regexp for gem name" do it "presents alternatives", :readline do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" - gem "rack-obama" + source "https://gem.repo1" + gem "myrack" + gem "myrack-obama" G bundle "show rac" - expect(out).to match(/\A1 : rack\n2 : rack-obama\n0 : - exit -(\n>.*)?\z/) + expect(out).to match(/\A1 : myrack\n2 : myrack-obama\n0 : - exit -(\n>|\z)/) end end context "with an invalid regexp for gem name" do it "does not find the gem" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rails" G @@ -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 "#{file_uri_for(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 2a09e0531b..03a3786d80 100644 --- a/spec/bundler/commands/update_spec.rb +++ b/spec/bundler/commands/update_spec.rb @@ -6,17 +6,17 @@ RSpec.describe "bundle update" do build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport" - gem "rack-obama" + gem "myrack-obama" gem "platform_specific" G end it "updates the entire bundle" do update_repo2 do - build_gem "rack", "1.2" do |s| - s.executables = "rackup" + build_gem "myrack", "1.2" do |s| + s.executables = "myrackup" end build_gem "activesupport", "3.0" @@ -24,14 +24,14 @@ RSpec.describe "bundle update" do bundle "update" expect(out).to include("Bundle updated!") - expect(the_bundle).to include_gems "rack 1.2", "rack-obama 1.0", "activesupport 3.0" + expect(the_bundle).to include_gems "myrack 1.2", "myrack-obama 1.0", "activesupport 3.0" end it "doesn't delete the Gemfile.lock file if something goes wrong" do gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport" - gem "rack-obama" + gem "myrack-obama" exit! G bundle "update", raise_on_error: false @@ -39,22 +39,39 @@ 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 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport" - gem "rack-obama" + gem "myrack-obama" gem "platform_specific" G end it "updates the entire bundle" do update_repo2 do - build_gem "rack", "1.2" do |s| - s.executables = "rackup" + build_gem "myrack", "1.2" do |s| + s.executables = "myrackup" end build_gem "activesupport", "3.0" @@ -62,16 +79,16 @@ RSpec.describe "bundle update" do bundle "update", all: true expect(out).to include("Bundle updated!") - expect(the_bundle).to include_gems "rack 1.2", "rack-obama 1.0", "activesupport 3.0" + expect(the_bundle).to include_gems "myrack 1.2", "myrack-obama 1.0", "activesupport 3.0" end it "doesn't delete the Gemfile.lock file if something goes wrong" do - install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" + install_gemfile "source 'https://gem.repo1'" gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport" - gem "rack-obama" + gem "myrack-obama" exit! G bundle "update", all: true, raise_on_error: false @@ -80,10 +97,10 @@ 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 "#{file_uri_for(gem_repo1)}" - gem "rack", "1.0" + source "https://gem.repo1" + gem "myrack", "1.0" G bundle "update --gemfile OmgFile", all: true @@ -93,22 +110,22 @@ 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 \"#{file_uri_for(gem_repo1)}\"" + install_gemfile "source 'https://gem.repo1'" bundle :update, raise_on_error: false expect(err).to eq("To update everything, pass the `--all` flag.") end it "errors when passed --all and another option" do - install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" + install_gemfile "source 'https://gem.repo1'" bundle "update --all foo", raise_on_error: false expect(err).to eq("Cannot specify --all along with specific options.") end it "updates everything when passed --all" do - install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" + install_gemfile "source 'https://gem.repo1'" bundle "update --all" expect(out).to include("Bundle updated!") end @@ -119,9 +136,9 @@ RSpec.describe "bundle update" do build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport" - gem "rack-obama" + gem "myrack-obama" gem "platform_specific" G end @@ -137,24 +154,24 @@ RSpec.describe "bundle update" do build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport" - gem "rack-obama" + gem "myrack-obama" gem "platform_specific" G end it "unlocks all child dependencies that are unrelated to other locked dependencies" do update_repo2 do - build_gem "rack", "1.2" do |s| - s.executables = "rackup" + build_gem "myrack", "1.2" do |s| + s.executables = "myrackup" end build_gem "activesupport", "3.0" end - bundle "update rack-obama" - expect(the_bundle).to include_gems "rack 1.2", "rack-obama 1.0", "activesupport 2.3.5" + bundle "update myrack-obama" + expect(the_bundle).to include_gems "myrack 1.2", "myrack-obama 1.0", "activesupport 2.3.5" end end @@ -163,9 +180,9 @@ RSpec.describe "bundle update" do build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport" - gem "rack-obama" + gem "myrack-obama" gem "platform_specific" G end @@ -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 @@ -185,22 +202,22 @@ RSpec.describe "bundle update" do build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport" - gem "rack-obama" + gem "myrack-obama" gem "platform_specific" G end it "should update the child dependency" do update_repo2 do - build_gem "rack", "1.2" do |s| - s.executables = "rackup" + build_gem "myrack", "1.2" do |s| + s.executables = "myrackup" end end - bundle "update rack" - expect(the_bundle).to include_gems "rack 1.2" + bundle "update myrack" + expect(the_bundle).to include_gems "myrack 1.2" end end @@ -223,14 +240,14 @@ RSpec.describe "bundle update" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "slim-rails" gem "slim_lint" G 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 @@ -269,20 +286,20 @@ RSpec.describe "bundle update" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "country_select" gem "countries" G - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum(gem_repo4, "countries", "3.1.0") c.checksum(gem_repo4, "country_select", "5.1.0") end lockfile <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: countries (3.1.0) country_select (5.1.0) @@ -296,7 +313,7 @@ RSpec.describe "bundle update" do country_select #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L previous_lockfile = lockfile @@ -331,7 +348,7 @@ RSpec.describe "bundle update" do end gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "oauth2" gem "quickbooks-ruby" @@ -339,7 +356,7 @@ RSpec.describe "bundle update" do lockfile <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: faraday (2.5.2) multi_json (1.15.0) @@ -357,7 +374,7 @@ RSpec.describe "bundle update" do quickbooks-ruby BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "update --conservative --verbose" @@ -390,7 +407,7 @@ RSpec.describe "bundle update" do end gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gemspec @@ -404,7 +421,7 @@ RSpec.describe "bundle update" do specs: GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: rake (13.0.6) sneakers (2.11.0) @@ -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| @@ -440,13 +566,13 @@ RSpec.describe "bundle update" do end install_gemfile <<-G, verbose: true - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "a" G 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 @@ -464,7 +590,7 @@ RSpec.describe "bundle update" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "a" gem "b" G @@ -472,7 +598,7 @@ RSpec.describe "bundle update" do expect(the_bundle).to include_gems("a 1.0", "b 2.0") gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "a" gem "b", "1.0" G @@ -497,27 +623,27 @@ RSpec.describe "bundle update" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "activesupport", "~> 6.1.0" G expect(the_bundle).to include_gems("activesupport 6.1.4.1", "tzinfo 2.0.4") gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "activesupport", "~> 6.0.0" G original_lockfile = lockfile - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum gem_repo4, "activesupport", "6.0.4.1" c.checksum gem_repo4, "tzinfo", "1.2.9" end expected_lockfile = <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: activesupport (6.0.4.1) tzinfo (~> 1.1) @@ -530,17 +656,13 @@ RSpec.describe "bundle update" do activesupport (~> 6.0.0) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "update activesupport" expect(the_bundle).to include_gems("activesupport 6.0.4.1", "tzinfo 1.2.9") expect(lockfile).to eq(expected_lockfile) - # needed because regressing to versions already present on the system - # won't add a checksum - expected_lockfile = remove_checksums_from_lockfile(expected_lockfile) - lockfile original_lockfile bundle "update" expect(the_bundle).to include_gems("activesupport 6.0.4.1", "tzinfo 1.2.9") @@ -558,42 +680,44 @@ RSpec.describe "bundle update" do build_repo2 gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport" - gem "rack-obama" + gem "myrack-obama" gem "platform_specific" G - - lockfile <<~L - GEM - remote: #{file_uri_for(gem_repo2)}/ - specs: - activesupport (2.3.5) - platform_specific (1.0-#{local_platform}) - rack (1.0.0) - rack-obama (1.0) - rack - - PLATFORMS - #{local_platform} - - DEPENDENCIES - activesupport - platform_specific - rack-obama - - BUNDLED WITH - #{Bundler::VERSION} - L - - bundle "install" end it "doesn't hit repo2" do - FileUtils.rm_rf(gem_repo2) - - bundle "update --local --all" - expect(out).not_to include("Fetching source index") + simulate_platform "x86-darwin-100" do + lockfile <<~L + GEM + remote: https://gem.repo2/ + specs: + activesupport (2.3.5) + platform_specific (1.0-x86-darwin-100) + myrack (1.0.0) + myrack-obama (1.0) + myrack + + PLATFORMS + x86-darwin-100 + + DEPENDENCIES + activesupport + platform_specific + myrack-obama + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" + + FileUtils.rm_r(gem_repo2) + + bundle "update --local --all" + expect(out).not_to include("Fetching source index") + end end end @@ -604,26 +728,26 @@ RSpec.describe "bundle update" do it "should update only specified group gems" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport", :group => :development - gem "rack" + gem "myrack" G update_repo2 do - build_gem "rack", "1.2" do |s| - s.executables = "rackup" + build_gem "myrack", "1.2" do |s| + s.executables = "myrackup" end build_gem "activesupport", "3.0" end bundle "update --group development" expect(the_bundle).to include_gems "activesupport 3.0" - expect(the_bundle).not_to include_gems "rack 1.2" + expect(the_bundle).not_to include_gems "myrack 1.2" end context "when conservatively updating a group with non-group sub-deps" do it "should update only specified group gems" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activemerchant", :group => :development gem "activesupport" G @@ -641,7 +765,7 @@ RSpec.describe "bundle update" do before do build_git "foo", path: lib_path("activesupport") install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport", :group => :development gem "foo", :git => "#{lib_path("activesupport")}" G @@ -660,13 +784,13 @@ RSpec.describe "bundle update" do context "when bundler itself is a transitive dependency" do it "executes without error" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "activesupport", :group => :development - gem "rack" + gem "myrack" G update_repo2 do - build_gem "rack", "1.2" do |s| - s.executables = "rackup" + build_gem "myrack", "1.2" do |s| + s.executables = "myrackup" end build_gem "activesupport", "3.0" @@ -674,7 +798,7 @@ RSpec.describe "bundle update" do bundle "update --group development" expect(the_bundle).to include_gems "activesupport 2.3.5" expect(the_bundle).to include_gems "bundler #{Bundler::VERSION}" - expect(the_bundle).not_to include_gems "rack 1.2" + expect(the_bundle).not_to include_gems "myrack 1.2" end end end @@ -684,40 +808,48 @@ RSpec.describe "bundle update" do build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport" - gem "rack-obama" + gem "myrack-obama" gem "platform_specific" 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 @@ -728,7 +860,7 @@ RSpec.describe "bundle update" do it "should not update gems not included in the source that happen to have the same name" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport" G update_repo2 { build_gem "activesupport", "3.0" } @@ -739,7 +871,7 @@ RSpec.describe "bundle update" do it "should not update gems not included in the source that happen to have the same name" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport" G update_repo2 { build_gem "activesupport", "3.0" } @@ -759,7 +891,7 @@ RSpec.describe "bundle update" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "harry" gem "fred" G @@ -791,7 +923,7 @@ RSpec.describe "bundle update" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "harry" gem "fred" G @@ -814,7 +946,7 @@ RSpec.describe "bundle update" do build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport" G @@ -836,7 +968,7 @@ RSpec.describe "bundle update" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "bar" gem "foo" G @@ -844,18 +976,17 @@ 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 bundle "update", all: true - out.sub!("Removing foo (1.0)\n", "") - expect(out).to match(/Resolving dependencies\.\.\.\.*\nFetching foo 2\.0 \(was 1\.0\)\nInstalling foo 2\.0 \(was 1\.0\)\nBundle updated/) + expect(out.sub("Removing foo (1.0)\n", "")).to match(/Resolving dependencies\.\.\.\.*\nFetching foo 2\.0 \(was 1\.0\)\nInstalling foo 2\.0 \(was 1\.0\)\nBundle updated/) end it "shows error message when Gemfile.lock is not preset and gem is specified" do gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport" G @@ -864,65 +995,87 @@ RSpec.describe "bundle update" do expect(exitstatus).to eq(22) end - context "with multiple, duplicated sources, with lockfile in old format", bundler: "< 3" do + context "with multiple sources and caching enabled" do before do build_repo2 do - build_gem "dotenv", "2.7.6" - - build_gem "oj", "3.11.3" - build_gem "oj", "3.11.5" + build_gem "myrack", "1.0.0" - build_gem "vcr", "6.0.0" + build_gem "request_store", "1.0.0" do |s| + s.add_dependency "myrack", "1.0.0" + end end - build_repo gem_repo3 do - build_gem "pkg-gem-flowbyte-with-dep", "1.0.0" do |s| - s.add_dependency "oj" - end + build_repo4 do + # set up repo with no gems end gemfile <<~G source "https://gem.repo2" - gem "dotenv" + gem "request_store" - source "https://gem.repo3" do - gem 'pkg-gem-flowbyte-with-dep' + source "https://gem.repo4" do 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) + myrack (1.0.0) + request_store (1.0.0) + myrack (= 1.0.0) + + GEM + remote: https://gem.repo4/ + specs: PLATFORMS #{local_platform} DEPENDENCIES - dotenv - pkg-gem-flowbyte-with-dep! - vcr! + request_store BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end it "works" do - bundle :install, artifice: "compact_index" - bundle "update oj", artifice: "compact_index" + bundle :install + bundle :cache + + update_repo2 do + build_gem "request_store", "1.1.0" do |s| + s.add_dependency "myrack", "1.0.0" + end + end + + bundle "update request_store" expect(out).to include("Bundle updated!") - expect(the_bundle).to include_gems "oj 3.11.5" + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo2/ + specs: + myrack (1.0.0) + request_store (1.1.0) + myrack (= 1.0.0) + + GEM + remote: https://gem.repo4/ + specs: + + PLATFORMS + #{local_platform} + + DEPENDENCIES + request_store + + BUNDLED WITH + #{Bundler::VERSION} + L end end end @@ -934,49 +1087,49 @@ RSpec.describe "bundle update in more complicated situations" do it "will eagerly unlock dependencies of a specified gem" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "thin" - gem "rack-obama" + gem "myrack-obama" G update_repo2 do - build_gem "rack", "1.2" do |s| - s.executables = "rackup" + build_gem "myrack", "1.2" do |s| + s.executables = "myrackup" end build_gem "thin", "2.0" do |s| - s.add_dependency "rack" + s.add_dependency "myrack" end end bundle "update thin" - expect(the_bundle).to include_gems "thin 2.0", "rack 1.2", "rack-obama 1.0" + expect(the_bundle).to include_gems "thin 2.0", "myrack 1.2", "myrack-obama 1.0" end it "will warn when some explicitly updated gems are not updated" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "thin" - gem "rack-obama" + gem "myrack-obama" G update_repo2 do - build_gem("thin", "2.0") {|s| s.add_dependency "rack" } - build_gem "rack", "10.0" + build_gem("thin", "2.0") {|s| s.add_dependency "myrack" } + build_gem "myrack", "10.0" end - bundle "update thin rack-obama" - expect(last_command.stdboth).to include "Bundler attempted to update rack-obama but its version stayed the same" - expect(the_bundle).to include_gems "thin 2.0", "rack 10.0", "rack-obama 1.0" + bundle "update thin myrack-obama" + 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 it "will not warn when an explicitly updated git gem changes sha but not version" do build_git "foo" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => '#{lib_path("foo-1.0")}' G @@ -986,32 +1139,32 @@ 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 - build_git "rack" + build_git "myrack" install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack", :git => '#{lib_path("rack-1.0")}' + source "https://gem.repo2" + gem "myrack", :git => '#{lib_path("myrack-1.0")}' G gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G - bundle "update rack" + 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 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" - source "#{file_uri_for(gem_repo1)}" do + source "https://gem.repo1" do gem "thin" end G @@ -1020,12 +1173,12 @@ RSpec.describe "bundle update in more complicated situations" do build_gem "thin", "2.0" end - bundle "update" + bundle "update", artifice: "compact_index" expect(the_bundle).to include_gems "thin 1.0" end context "when the lockfile is for a different platform" do - before do + around do |example| build_repo4 do build_gem("a", "0.9") build_gem("a", "0.9") {|s| s.platform = "java" } @@ -1034,13 +1187,13 @@ RSpec.describe "bundle update in more complicated situations" do end gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "a" G lockfile <<-L GEM - remote: #{file_uri_for(gem_repo4)} + remote: https://gem.repo4 specs: a (0.9-java) @@ -1051,7 +1204,7 @@ RSpec.describe "bundle update in more complicated situations" do a L - simulate_platform linux + simulate_platform "x86_64-linux", &example end it "allows updating" do @@ -1073,13 +1226,13 @@ RSpec.describe "bundle update in more complicated situations" do end gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "a", platform: :jruby G lockfile <<-L GEM - remote: #{file_uri_for(gem_repo4)} + remote: https://gem.repo4 specs: a (0.9-java) @@ -1089,14 +1242,14 @@ RSpec.describe "bundle update in more complicated situations" do DEPENDENCIES a L - - simulate_platform linux end it "is not updated because it is not actually included in the bundle" 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(the_bundle).to_not include_gem "a" + simulate_platform "x86_64-linux" do + bundle "update a" + 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 end end @@ -1106,14 +1259,14 @@ RSpec.describe "bundle update without a Gemfile.lock" do build_repo2 gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" - gem "rack", "1.0" + gem "myrack", "1.0" G bundle "update", all: true - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end end @@ -1121,23 +1274,23 @@ RSpec.describe "bundle update when a gem depends on a newer version of bundler" before do build_repo2 do build_gem "rails", "3.0.1" do |s| - s.add_dependency "bundler", Bundler::VERSION.succ + s.add_dependency "bundler", "9.9.9" end - build_gem "bundler", Bundler::VERSION.succ + build_gem "bundler", "9.9.9" end gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "rails", "3.0.1" G end 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:#{Bundler::VERSION.succ}`/i) + and match(/Install the necessary version with `gem install bundler:9\.9\.9`/i) end end @@ -1146,11 +1299,11 @@ RSpec.describe "bundle update --ruby" do before do install_gemfile <<-G ruby '~> #{Gem.ruby_version}' - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G end @@ -1159,16 +1312,16 @@ RSpec.describe "bundle update --ruby" do expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS #{lockfile_platforms} DEPENDENCIES - + #{checksums_section_when_enabled} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -1177,12 +1330,12 @@ RSpec.describe "bundle update --ruby" do before do install_gemfile <<-G ruby '~> #{Gem.ruby_version}' - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G gemfile <<-G ruby '~> #{current_ruby_minor}' - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G end @@ -1191,19 +1344,19 @@ RSpec.describe "bundle update --ruby" do expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS #{lockfile_platforms} DEPENDENCIES - + #{checksums_section_when_enabled} RUBY VERSION - #{Bundler::RubyVersion.system} + #{Bundler::RubyVersion.system} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -1212,12 +1365,12 @@ RSpec.describe "bundle update --ruby" do before do install_gemfile <<-G ruby '~> #{Gem.ruby_version}' - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G gemfile <<-G ruby '~> 2.1.0' - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G end it "shows a helpful error message" do @@ -1231,7 +1384,7 @@ RSpec.describe "bundle update --ruby" do before do lockfile <<~L GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS @@ -1245,12 +1398,12 @@ RSpec.describe "bundle update --ruby" do ruby 2.1.4p222 BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L gemfile <<-G ruby '~> #{Gem.ruby_version}' - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G end @@ -1259,21 +1412,19 @@ RSpec.describe "bundle update --ruby" do expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS #{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 @@ -1282,171 +1433,167 @@ end RSpec.describe "bundle update --bundler" do it "updates the bundler version in the lockfile" do build_repo4 do - build_gem "rack", "1.0" + build_gem "bundler", "2.5.9" + build_gem "myrack", "1.0" end - checksums = checksums_section_when_existing do |c| - c.checksum(gem_repo4, "rack", "1.0") + checksums = checksums_section_when_enabled do |c| + c.checksum(gem_repo4, "myrack", "1.0") end install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" - gem "rack" + source "https://gem.repo4" + gem "myrack" G expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: - rack (1.0) + myrack (1.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack + myrack #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, '\11.0.0\2') - bundle :update, bundler: true, artifice: "compact_index", verbose: true + bundle :update, bundler: true, verbose: true expect(out).to include("Using bundler #{Bundler::VERSION}") expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: - rack (1.0) + myrack (1.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack + myrack #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L - expect(the_bundle).to include_gem "rack 1.0" + expect(the_bundle).to include_gem "myrack 1.0" end it "updates the bundler version in the lockfile without re-resolving if the highest version is already installed" do - system_gems "bundler-2.3.9" - build_repo4 do - build_gem "rack", "1.0" + build_gem "bundler", "2.3.9" + build_gem "myrack", "1.0" end install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" - gem "rack" + source "https://gem.repo4" + gem "myrack" G lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, "2.3.9") - checksums = checksums_section_when_existing do |c| - c.checksum(gem_repo4, "rack", "1.0") + checksums = checksums_section_when_enabled do |c| + c.checksum(gem_repo4, "myrack", "1.0") end - bundle :update, bundler: true, artifice: "compact_index", verbose: true + bundle :update, bundler: true, verbose: true expect(out).to include("Using bundler #{Bundler::VERSION}") expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: - rack (1.0) + myrack (1.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack + myrack #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L - expect(the_bundle).to include_gem "rack 1.0" + 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.3.9" + bundle_config "path.system true" + + pristine_system_gems "bundler-9.0.0" build_repo4 do - build_gem "rack", "1.0" + build_gem "myrack", "1.0" build_bundler "999.0.0" end - install_gemfile <<-G, artifice: nil, env: { "BUNDLER_IGNORE_DEFAULT_GEM" => "true" } - source "#{file_uri_for(gem_repo4)}" - gem "rack" - G - lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, "2.3.9") - - bundle :update, bundler: true, artifice: "compact_index", verbose: true, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + checksums = checksums_section do |c| + c.checksum(gem_repo4, "myrack", "1.0") + c.checksum(gem_repo4, "bundler", "999.0.0") + end - # Only updates properly on modern RubyGems. + install_gemfile <<-G + source "https://gem.repo4" + gem "myrack" + G - if Gem.rubygems_version >= Gem::Version.new("3.3.0.dev") - expect(out).to include("Updating bundler to 999.0.0") - expect(out).to include("Using bundler 999.0.0") - expect(out).not_to include("Installing Bundler 2.3.9 and restarting using that version.") + bundle :update, bundler: true, verbose: true - expect(lockfile).to eq <<~L - GEM - remote: #{file_uri_for(gem_repo4)}/ - specs: - rack (1.0) + 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") + expect(out).not_to include("Installing Bundler 2.99.9 and restarting using that version.") - PLATFORMS - #{lockfile_platforms} + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + myrack (1.0) - DEPENDENCIES - rack + PLATFORMS + #{lockfile_platforms} - BUNDLED WITH - 999.0.0 - L + DEPENDENCIES + myrack + #{checksums} + BUNDLED WITH + 999.0.0 + L - expect(the_bundle).to include_gems "bundler 999.0.0" - expect(the_bundle).to include_gems "rack 1.0" - else - # Old RubyGems versions do not trampoline but they still change BUNDLED - # WITH to the latest bundler version. This means the below check fails - # because it tries to use bundler 999.0.0 which did not get installed. - # Workaround the bug by forcing the version we know is installed. - expect(the_bundle).to include_gems "rack 1.0", env: { "BUNDLER_VERSION" => "2.3.9" } - end + expect(the_bundle).to include_gems "bundler 999.0.0" + expect(the_bundle).to include_gems "myrack 1.0" 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 "rack", "3.0.9.1" + build_gem "myrack", "3.0.9.1" - build_bundler "2.99.0" + build_bundler "4.99.0" end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" - gem "rack" + source "https://gem.repo4" + gem "myrack" G lockfile <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: - rack (3.0.9.1) + myrack (3.0.9.1) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack + myrack BUNDLED WITH 2.99.0 @@ -1454,168 +1601,183 @@ RSpec.describe "bundle update --bundler" do bundle :cache, verbose: true - bundle :update, bundler: true, artifice: "compact_index", verbose: true, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + bundle :update, bundler: true, verbose: true expect(out).not_to include("Updating bundler to") end it "does not update the bundler version in the lockfile if the latest version is not compatible with current ruby", :ruby_repo do - pristine_system_gems "bundler-2.3.9" + pristine_system_gems "bundler-9.9.9" build_repo4 do - build_gem "rack", "1.0" + build_gem "myrack", "1.0" - build_bundler "2.3.9" + build_bundler "9.9.9" build_bundler "999.0.0" do |s| s.required_ruby_version = "> #{Gem.ruby_version}" end end - install_gemfile <<-G, env: { "BUNDLER_IGNORE_DEFAULT_GEM" => "true" } - source "#{file_uri_for(gem_repo4)}" - gem "rack" + checksums = checksums_section do |c| + c.checksum(gem_repo4, "myrack", "1.0") + c.checksum(gem_repo4, "bundler", "9.9.9") + end + + install_gemfile <<-G + source "https://gem.repo4" + gem "myrack" G - lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, "2.3.9") - bundle :update, bundler: true, artifice: "compact_index", verbose: true, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s, "BUNDLER_IGNORE_DEFAULT_GEM" => "true" } + bundle :update, bundler: true, verbose: true - expect(out).to include("Using bundler 2.3.9") + expect(out).to include("Using bundler 9.9.9") expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: - rack (1.0) + myrack (1.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack - + myrack + #{checksums} BUNDLED WITH - 2.3.9 + 9.9.9 L - expect(the_bundle).to include_gems "bundler 2.3.9" - expect(the_bundle).to include_gems "rack 1.0" + expect(the_bundle).to include_gems "bundler 9.9.9" + expect(the_bundle).to include_gems "myrack 1.0" end it "errors if the explicit target version does not exist" do - pristine_system_gems "bundler-2.3.9" + pristine_system_gems "bundler-9.9.9" build_repo4 do - build_gem "rack", "1.0" + build_gem "myrack", "1.0" end - install_gemfile <<-G, env: { "BUNDLER_IGNORE_DEFAULT_GEM" => "true" } - source "#{file_uri_for(gem_repo4)}" - gem "rack" + install_gemfile <<-G + source "https://gem.repo4" + gem "myrack" G - lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, "2.3.9") - bundle :update, bundler: "999.999.999", artifice: "compact_index", raise_on_error: false + bundle :update, bundler: "999.999.999", raise_on_error: false + + expect(last_command).to be_failure + expect(err).to eq("The `bundle update --bundler` target version (999.999.999) does not exist") + end - # Only gives a meaningful error message on modern RubyGems. + it "errors if the explicit target version does not exist, even if auto switching is disabled" do + pristine_system_gems "bundler-9.9.9" - if Gem.rubygems_version >= Gem::Version.new("3.3.0.dev") - expect(last_command).to be_failure - expect(err).to include("The `bundle update --bundler` target version (999.999.999) does not exist") + 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 "rack", "1.0" + build_gem "myrack", "1.0" end install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" - gem "rack" + source "https://gem.repo4" + 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" - # Only updates properly on modern RubyGems. - if Gem.rubygems_version >= Gem::Version.new("3.3.0.dev") - checksums = checksums_section_when_existing do |c| - c.checksum(gem_repo4, "rack", "1.0") - end + checksums = checksums_section_when_enabled do |c| + c.checksum(gem_repo4, "myrack", "1.0") + end + checksums.delete("bundler") - expect(lockfile).to eq <<~L + expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: - rack (1.0) + myrack (1.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack + myrack #{checksums} BUNDLED WITH - 2.3.0.dev + 9.0.0.dev L - expect(out).to include("Using bundler 2.3.0.dev") - end + 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 "rack", "1.0" + build_gem "myrack", "1.0" end install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" - gem "rack" + 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_existing do |c| - c.checksum(gem_repo4, "rack", "1.0") + 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 - if Gem.rubygems_version >= Gem::Version.new("3.3.0.dev") - expect(lockfile).to eq <<~L + expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: - rack (1.0) + myrack (1.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack + myrack #{checksums} BUNDLED WITH - 2.3.9 + 9.0.0 L - expect(out).to include("Using bundler 2.3.9") - end + 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 "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" G lockfile <<-L GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: PLATFORMS @@ -1624,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 @@ -1655,7 +1819,7 @@ RSpec.describe "bundle update conservative" do # establish a lockfile set to 1.4.3 install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem 'foo', '1.4.3' gem 'bar', '2.0.3' gem 'qux', '1.0.0' @@ -1664,7 +1828,7 @@ RSpec.describe "bundle update conservative" do # remove 1.4.3 requirement and bar altogether # to setup update specs below gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem 'foo' gem 'qux' G @@ -1672,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" @@ -1760,7 +1924,7 @@ RSpec.describe "bundle update conservative" do end gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem 'isolated_owner' gem 'shared_owner_a' @@ -1769,7 +1933,7 @@ RSpec.describe "bundle update conservative" do lockfile <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: isolated_dep (2.0.1) isolated_owner (1.0.1) @@ -1791,7 +1955,7 @@ RSpec.describe "bundle update conservative" do CHECKSUMS BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -1822,9 +1986,17 @@ RSpec.describe "bundle update conservative" do it "should only change direct dependencies when updating the lockfile with --conservative" do bundle "lock --update --conservative" + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "isolated_dep", "2.0.1" + c.checksum gem_repo4, "isolated_owner", "1.0.2" + c.checksum gem_repo4, "shared_dep", "5.0.1" + c.checksum gem_repo4, "shared_owner_a", "3.0.2" + c.checksum gem_repo4, "shared_owner_b", "4.0.2" + end + expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: isolated_dep (2.0.1) isolated_owner (1.0.2) @@ -1842,22 +2014,15 @@ RSpec.describe "bundle update conservative" do isolated_owner shared_owner_a shared_owner_b - - CHECKSUMS - isolated_dep (2.0.1) - isolated_owner (1.0.2) - shared_dep (5.0.1) - shared_owner_a (3.0.2) - shared_owner_b (4.0.2) - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end it "should match bundle install conservative update behavior when not eagerly unlocking" do gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem 'isolated_owner', '1.0.2' gem 'shared_owner_a', '3.0.2' @@ -1866,13 +2031,59 @@ RSpec.describe "bundle update conservative" do bundle "install" - expect(the_bundle).to include_gems "isolated_owner 1.0.2", "isolated_dep 2.0.2", "shared_dep 5.0.1", "shared_owner_a 3.0.2", "shared_owner_b 4.0.1" + expect(the_bundle).to include_gems "isolated_owner 1.0.2", "isolated_dep 2.0.1", "shared_dep 5.0.1", "shared_owner_a 3.0.2", "shared_owner_b 4.0.1" + end + end + + context "when Gemfile dependencies have changed" do + before do + build_repo4 do + build_gem "nokogiri", "1.16.4" do |s| + s.platform = "arm64-darwin" + end + + build_gem "nokogiri", "1.16.4" do |s| + s.platform = "x86_64-linux" + end + + build_gem "prism", "0.25.0" + end + + gemfile <<~G + source "https://gem.repo4" + gem "nokogiri", ">=1.16.4" + gem "prism", ">=0.25.0" + G + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + nokogiri (1.16.4-arm64-darwin) + nokogiri (1.16.4-x86_64-linux) + + PLATFORMS + arm64-darwin + x86_64-linux + + DEPENDENCIES + nokogiri (>= 1.16.4) + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "still works" do + simulate_platform "arm64-darwin-23" do + bundle "update" + end end end context "error handling" do before do - gemfile "source \"#{file_uri_for(gem_repo1)}\"" + gemfile "source 'https://gem.repo1'" end it "raises if too many flags are provided" do 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 f8b5f7836e..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"), realworld: true do - before do - realworld_system_gems "ruby-graphviz --version 1.2.5" - end - - it "graphs gems from the Gemfile" do - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" - gem "rack-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"]; - rack [style = "filled", fillcolor = "#B9B9D5", label = "rack"]; - default -> rack [constraint = "false"]; - "rack-obama" [style = "filled", fillcolor = "#B9B9D5", label = "rack-obama"]; - default -> "rack-obama" [constraint = "false"]; - "rack-obama" -> rack; - } - debugging bundle viz... - DOT - end - - it "graphs gems that are prereleases" do - build_repo2 do - build_gem "rack", "1.3.pre" - end - - install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack", "= 1.3.pre" - gem "rack-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"]; - rack [style = "filled", fillcolor = "#B9B9D5", label = "rack\\n1.3.pre"]; - default -> rack [constraint = "false"]; - "rack-obama" [style = "filled", fillcolor = "#B9B9D5", label = "rack-obama\\n1.0"]; - default -> "rack-obama" [constraint = "false"]; - "rack-obama" -> rack; - } - 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 "#{file_uri_for(gem_repo1)}" - gem "rack" - gem "rack-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"]; - rack [style = "filled", fillcolor = "#B9B9D5", label = "rack"]; - default -> rack [constraint = "false"]; - "rack-obama" [style = "filled", fillcolor = "#B9B9D5", label = "rack-obama"]; - default -> "rack-obama" [constraint = "false"]; - "rack-obama" -> rack; - } - debugging bundle viz... - DOT - end - end - - context "--without option" do - it "one group" do - install_gemfile <<-G - source "#{file_uri_for(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 "#{file_uri_for(gem_repo1)}" - gem "activesupport" - - group :rack do - gem "rack" - end - - group :rails do - gem "rails" - end - G - - bundle "viz --without=rails:rack" - 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 8da94718e0..c7ab7c3d7e 100644 --- a/spec/bundler/install/allow_offline_install_spec.rb +++ b/spec/bundler/install/allow_offline_install_spec.rb @@ -1,23 +1,19 @@ # 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" source "http://testgemserver.local" - gem "rack-obama" + gem "myrack-obama" G - expect(the_bundle).to include_gem("rack 1.0") + expect(the_bundle).to include_gem("myrack 1.0") end it "still fails when the network is down" do install_gemfile <<-G, artifice: "fail", raise_on_error: false source "http://testgemserver.local" - gem "rack-obama" + gem "myrack-obama" G expect(err).to include("Could not reach host testgemserver.local.") expect(the_bundle).to_not be_locked @@ -26,26 +22,26 @@ RSpec.describe "bundle install with :allow_offline_install" do context "with cached data locally" do it "will install from the compact index" do - system_gems ["rack-1.0.0"], path: default_bundle_path + system_gems ["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 "rack-obama" - gem "rack", "< 1.0" + gem "myrack-obama" + gem "myrack", "< 1.0" G - expect(the_bundle).to include_gems("rack-obama 1.0", "rack 0.9.1") + expect(the_bundle).to include_gems("myrack-obama 1.0", "myrack 0.9.1") gemfile <<-G source "http://testgemserver.local" - gem "rack-obama" + gem "myrack-obama" 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("rack-obama 1.0", "rack 1.0.0") + expect(the_bundle).to include_gems("myrack-obama 1.0", "myrack 1.0.0") end def break_git_remote_ops! @@ -53,10 +49,10 @@ RSpec.describe "bundle install with :allow_offline_install" do File.open(tmp("broken_path/git"), "w", 0o755) do |f| f.puts <<~RUBY #!/usr/bin/env ruby - fetch_args = %w(fetch --force --quiet) + fetch_args = %w(fetch --force --quiet --no-tags) clone_args = %w(clone --bare --no-hardlinks --quiet) - if (fetch_args.-(ARGV).empty? || clone_args.-(ARGV).empty?) && ARGV.any? {|arg| arg.start_with?("file://") } + if (fetch_args.-(ARGV).empty? || clone_args.-(ARGV).empty?) && File.exist?(ARGV[ARGV.index("--") + 1]) warn "git remote ops have been disabled" exit 1 end @@ -78,7 +74,7 @@ RSpec.describe "bundle install with :allow_offline_install" do git = build_git "a", "1.0.0", path: lib_path("a") update_git("a", path: git.path, branch: "new_branch") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "a", :git => #{git.path.to_s.dump} G @@ -88,7 +84,7 @@ RSpec.describe "bundle install with :allow_offline_install" do break_git_remote_ops! do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "a", :git => #{git.path.to_s.dump}, :branch => "new_branch" G end diff --git a/spec/bundler/install/binstubs_spec.rb b/spec/bundler/install/binstubs_spec.rb index 928ba80b15..c2eccb3ef2 100644 --- a/spec/bundler/install/binstubs_spec.rb +++ b/spec/bundler/install/binstubs_spec.rb @@ -6,14 +6,14 @@ RSpec.describe "bundle install" do expect(Pathname.new("/usr/bin")).not_to be_writable gemfile <<-G def Gem.bindir; "/usr/bin"; end - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + 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 "rack 1.0.0" - expect(system_gem_path("altbin/rackup")).to exist + expect(the_bundle).to include_gems "myrack 1.0.0" + expect(system_gem_path("altbin/myrackup")).to exist end end @@ -21,26 +21,26 @@ RSpec.describe "bundle install" do before do build_repo2 do build_gem "fake", "14" do |s| - s.executables = "rackup" + s.executables = "myrackup" end end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "fake" - gem "rack" + gem "myrack" G end it "warns about the situation" do - bundle "exec rackup" + bundle "exec myrackup" expect(last_command.stderr).to include( - "The `rackup` executable in the `fake` gem is being loaded, but it's also present in other gems (rack).\n" \ + "The `myrackup` executable in the `fake` gem is being loaded, but it's also present in other gems (myrack).\n" \ "If you meant to run the executable for another gem, make sure you use a project specific binstub (`bundle binstub <gem_name>`).\n" \ "If you plan to use multiple conflicting executables, generate binstubs for them and disambiguate their names." ).or include( - "The `rackup` executable in the `rack` gem is being loaded, but it's also present in other gems (fake).\n" \ + "The `myrackup` executable in the `myrack` gem is being loaded, but it's also present in other gems (fake).\n" \ "If you meant to run the executable for another gem, make sure you use a project specific binstub (`bundle binstub <gem_name>`).\n" \ "If you plan to use multiple conflicting executables, generate binstubs for them and disambiguate their names." ) diff --git a/spec/bundler/install/bundler_spec.rb b/spec/bundler/install/bundler_spec.rb index 19911f1154..86c22dad55 100644 --- a/spec/bundler/install/bundler_spec.rb +++ b/spec/bundler/install/bundler_spec.rb @@ -5,7 +5,7 @@ RSpec.describe "bundle install" do before(:each) do build_repo2 do build_gem "rails", "3.0" do |s| - s.add_dependency "bundler", ">= 0.9.0.pre" + s.add_dependency "bundler", ">= 0.9.0" end build_gem "bundler", "0.9.1" build_gem "bundler", Bundler::VERSION @@ -14,7 +14,7 @@ RSpec.describe "bundle install" do it "are forced to the current bundler version" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "rails", "3.0" G @@ -23,15 +23,15 @@ RSpec.describe "bundle install" do it "are forced to the current bundler version even if not already present" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G expect(the_bundle).to include_gems "bundler #{Bundler::VERSION}" end it "causes a conflict if explicitly requesting a different version of bundler" do install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "rails", "3.0" gem "bundler", "0.9.1" G @@ -51,7 +51,7 @@ RSpec.describe "bundle install" do it "causes a conflict if explicitly requesting a non matching requirement on bundler" do install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "rails", "3.0" gem "bundler", "~> 0.8" G @@ -59,8 +59,8 @@ RSpec.describe "bundle install" do nice_error = <<~E.strip Could not find compatible versions - Because rails >= 3.0 depends on bundler >= 0.9.0.pre - and the current Bundler version (#{Bundler::VERSION}) does not satisfy bundler >= 0.9.0.pre, < 1.A, + Because rails >= 3.0 depends on bundler >= 0.9.0 + and the current Bundler version (#{Bundler::VERSION}) does not satisfy bundler >= 0.9.0, < 1.A, rails >= 3.0 requires bundler >= 1.A. So, because Gemfile depends on rails = 3.0 and Gemfile depends on bundler ~> 0.8, @@ -74,7 +74,7 @@ RSpec.describe "bundle install" do it "causes a conflict if explicitly requesting a version of bundler that doesn't exist" do install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "rails", "3.0" gem "bundler", "0.9.2" G @@ -99,16 +99,16 @@ RSpec.describe "bundle install" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "multiple_versioned_deps" G install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "multiple_versioned_deps" - gem "rack" + gem "myrack" G expect(the_bundle).to include_gems "multiple_versioned_deps 1.0.0" @@ -116,7 +116,7 @@ RSpec.describe "bundle install" do it "includes bundler in the bundle when it's a child dependency" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "rails", "3.0" G @@ -126,8 +126,8 @@ RSpec.describe "bundle install" do it "allows gem 'bundler' when Bundler is not in the Gemfile or its dependencies" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack" + source "https://gem.repo2" + gem "myrack" G run "begin; gem 'bundler'; puts 'WIN'; rescue Gem::LoadError => e; puts e.backtrace; end" @@ -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| @@ -144,7 +144,7 @@ RSpec.describe "bundle install" do end install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activemerchant" gem "rails_pinned_to_old_activesupport" G @@ -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| @@ -172,7 +172,7 @@ RSpec.describe "bundle install" do end install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "rails_pinned_to_old_activesupport" gem "activesupport", "2.3.5" G @@ -197,12 +197,12 @@ RSpec.describe "bundle install" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem 'rails', "2.3.2" G install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "rails_pinned_to_old_activesupport" G @@ -216,20 +216,20 @@ 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 "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem 'rails', "7.0.4" G install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem 'rails', "7.0.3" G install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem 'rails', "7.0.4" G @@ -238,12 +238,12 @@ 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" install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "rails", "3.0" G @@ -252,12 +252,12 @@ 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" install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "rails", "3.0" G 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 8002978368..a3b4a87ecf 100644 --- a/spec/bundler/install/deploy_spec.rb +++ b/spec/bundler/install/deploy_spec.rb @@ -3,97 +3,43 @@ RSpec.describe "install in deployment or frozen mode" do before do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" 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 "rack 1.0" - end - - it "installs gems by default to vendor/bundle" do - bundle :lock - bundle "install --deployment" - expect(out).to include("vendor/bundle") - end - - it "installs gems to custom path if specified" do - bundle :lock - bundle "install --path vendor/bundle2 --deployment" - expect(out).to include("vendor/bundle2") - end - - it "works with the --frozen flag" do - bundle :lock - bundle "install --frozen" - end - - it "explodes with the --deployment flag if you make a change and don't check in the lockfile" do - bundle :lock - gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" - gem "rack-obama" - G + it "fails without a lockfile and says that deployment requires a lock" do + bundle_config "deployment true" + bundle "install", raise_on_error: false + expect(err).to include("The deployment setting requires a lockfile") + end - bundle "install --deployment", raise_on_error: false - expect(err).to include("frozen mode") - expect(err).to include("You have added to the Gemfile") - expect(err).to include("* rack-obama") - expect(err).not_to include("You have deleted from the Gemfile") - expect(err).not_to include("You have changed in the Gemfile") - end + it "fails without a lockfile and says that frozen requires a lock" do + 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 "rack 1.0" + expect(the_bundle).to include_gems "myrack 1.0" end it "works if you exclude a group with a git gem" do build_git "foo" gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" group :test do gem "foo", :git => "#{lib_path("foo-1.0")}" 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 @@ -101,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 @@ -110,26 +56,26 @@ RSpec.describe "install in deployment or frozen mode" do build_lib "foo", path: lib_path("nested/foo") build_lib "bar", path: lib_path("nested/bar") gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", "1.0", :path => "#{lib_path("nested")}" gem "bar", :path => "#{lib_path("nested")}" G bundle :install - bundle "config set --local deployment true" + bundle_config "deployment true" bundle :install end it "works when path gems are specified twice" do build_lib "foo", path: lib_path("nested/foo") gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :path => "#{lib_path("nested/foo")}" gem "foo", :path => "#{lib_path("nested/foo")}" G bundle :install - bundle "config set --local deployment true" + bundle_config "deployment true" bundle :install end @@ -137,96 +83,56 @@ RSpec.describe "install in deployment or frozen mode" do install_gemfile(<<-G, artifice: "endpoint_strict_basic_authentication", quiet: true) source "http://user:pass@localgemserver.test/" - gem "rack-obama", ">= 1.0" + gem "myrack-obama", ">= 1.0" G - bundle "config set --local deployment true" + bundle_config "deployment true" bundle :install, artifice: "endpoint_strict_basic_authentication" end it "works with sources given by a block" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - source "#{file_uri_for(gem_repo1)}" do - gem "rack" + source "https://gem.repo1" + source "https://gem.repo1" do + gem "myrack" end G - bundle "config set --local deployment true" + bundle_config "deployment true" bundle :install - expect(the_bundle).to include_gems "rack 1.0" + expect(the_bundle).to include_gems "myrack 1.0" end 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/" - gem "rack" + gem "myrack" G lockfile <<-G GEM remote: http://localgemserver.test/ specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{generic_local_platform} DEPENDENCIES - rack + myrack G - bundle "config set --local deployment true" - end - - it "prevents the replace by default" do - bundle :install, raise_on_error: false - - expect(err).to match(/The list of sources changed/) - end - - context "when allow_deployment_source_credential_changes is true" do - before { bundle "config set allow_deployment_source_credential_changes true" } - - it "allows the replace" do - bundle :install - - expect(out).to match(/Bundle complete!/) - end - end - - context "when allow_deployment_source_credential_changes is false" do - before { bundle "config set allow_deployment_source_credential_changes false" } - - it "prevents the replace" do - bundle :install, raise_on_error: false - - expect(err).to match(/The list of sources changed/) - end + bundle_config "deployment true" end - context "when BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES env var is true" do - before { ENV["BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES"] = "true" } - - it "allows the replace" do - bundle :install - - expect(out).to match(/Bundle complete!/) - end - end - - context "when BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES env var is false" do - before { ENV["BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES"] = "false" } - - it "prevents the replace" do - bundle :install, raise_on_error: false + it "allows the replace" do + bundle :install - expect(err).to match(/The list of sources changed/) - end + expect(out).to match(/Bundle complete!/) end end @@ -236,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 } @@ -244,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 } @@ -271,16 +177,16 @@ RSpec.describe "install in deployment or frozen mode" do it "explodes with the `deployment` setting if you make a change and don't check in the lockfile" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" - gem "rack-obama" + source "https://gem.repo1" + gem "myrack" + 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") - expect(err).to include("* rack-obama") + 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 @@ -288,16 +194,16 @@ RSpec.describe "install in deployment or frozen mode" do it "works if a path gem is missing but is in a without group" do build_lib "path_gem" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rake" gem "path_gem", :path => "#{lib_path("path_gem-1.0")}", :group => :development G 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") @@ -307,9 +213,9 @@ RSpec.describe "install in deployment or frozen mode" do build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" - source "#{file_uri_for(gem_repo1)}" do + source "https://gem.repo1" do gem "rake", platform: :#{not_local_tag} end G @@ -345,57 +251,57 @@ 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 build_lib "path_gem" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rake" gem "path_gem", :path => "#{lib_path("path_gem-1.0")}", :group => :development G 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 it "can have --frozen set via an environment variable" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" - gem "rack-obama" + source "https://gem.repo1" + gem "myrack" + gem "myrack-obama" G ENV["BUNDLE_FROZEN"] = "1" bundle "install", raise_on_error: false expect(err).to include("frozen mode") expect(err).to include("You have added to the Gemfile") - expect(err).to include("* rack-obama") + expect(err).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 it "can have --deployment set via an environment variable" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" - gem "rack-obama" + source "https://gem.repo1" + gem "myrack" + gem "myrack-obama" G ENV["BUNDLE_DEPLOYMENT"] = "true" bundle "install", raise_on_error: false expect(err).to include("frozen mode") expect(err).to include("You have added to the Gemfile") - expect(err).to include("* rack-obama") + expect(err).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 @@ -415,9 +321,9 @@ RSpec.describe "install in deployment or frozen mode" do it "can have --frozen set to false via an environment variable" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" - gem "rack-obama" + source "https://gem.repo1" + gem "myrack" + gem "myrack-obama" G ENV["BUNDLE_FROZEN"] = "false" @@ -425,77 +331,109 @@ RSpec.describe "install in deployment or frozen mode" do bundle "install" expect(out).not_to include("frozen mode") expect(out).not_to include("You have added to the Gemfile") - expect(out).not_to include("* rack-obama") + expect(out).not_to include("* myrack-obama") end - it "explodes if you remove a gem and don't check in the lockfile" do + it "explodes if you replace a gem and don't check in the lockfile" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" 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") - expect(err).to include("You have deleted from the Gemfile:\n* rack") + expect(err).to include("You have deleted from the Gemfile:\n* myrack") + expect(err).not_to include("You have changed in the Gemfile") + end + + it "explodes if you remove a gem and don't check in the lockfile" do + gemfile 'source "https://gem.repo1"' + + bundle_config "deployment true" + bundle :install, raise_on_error: false + expect(err).to include("Some dependencies were deleted") + expect(err).to include("frozen mode") + expect(err).to include("You have deleted from the Gemfile:\n* myrack") expect(err).not_to include("You have changed in the Gemfile") end it "explodes if you add a source" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "git://hubz.com" + source "https://gem.repo1" + 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") - expect(err).to include("You have changed in the Gemfile:\n* rack from `no specified source` to `git://hubz.com`") + expect(err).to include("You have changed in the Gemfile:\n* myrack from `no specified source` to `git://hubz.com`") end - it "explodes if you change a source" do - build_git "rack" + it "explodes if you change a source from git to the default" do + build_git "myrack" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-1.0")}" + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-1.0")}" G gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + 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") expect(err).not_to include("You have added to the Gemfile") - expect(err).to include("You have changed in the Gemfile:\n* rack from `#{lib_path("rack-1.0")}` to `no specified source`") + expect(err).to include("You have changed in the Gemfile:\n* myrack from `#{lib_path("myrack-1.0")}` to `no specified source`") + end + + it "explodes if you change a source from git to the default, in presence of other git sources" do + build_lib "foo", path: lib_path("myrack/foo") + build_git "myrack", path: lib_path("myrack") + + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack")}" + gem "foo", :git => "#{lib_path("myrack")}" + G + + gemfile <<-G + source "https://gem.repo1" + gem "myrack" + gem "foo", :git => "#{lib_path("myrack")}" + G + + 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`") + expect(err).not_to include("You have added to the Gemfile") + expect(err).not_to include("You have deleted from the Gemfile") end - it "explodes if you change a source" do - build_lib "foo", path: lib_path("rack/foo") - build_git "rack", path: lib_path("rack") + it "explodes if you change a source from path to git" do + build_git "myrack", path: lib_path("myrack") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack")}" - gem "foo", :git => "#{lib_path("rack")}" + source "https://gem.repo1" + gem "myrack", :path => "#{lib_path("myrack")}" G gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" - gem "foo", :git => "#{lib_path("rack")}" + source "https://gem.repo1" + gem "myrack", :git => "https:/my-git-repo-for-myrack" G - bundle "config set --local deployment 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* rack from `#{lib_path("rack")}` to `no specified source`") + expect(err).to include("You have changed in the Gemfile:\n* myrack from `#{lib_path("myrack")}` to `https:/my-git-repo-for-myrack`") expect(err).not_to include("You have added to the Gemfile") expect(err).not_to include("You have deleted from the Gemfile") end @@ -503,24 +441,24 @@ 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 "#{file_uri_for(gem_repo1)}" - gem "rack", "1.0.0" - gem "rack-obama" + source "https://gem.repo1" + gem "myrack", "1.0.0" + gem "myrack-obama" G - run "require 'rack'", raise_on_error: false + run "require 'myrack'", raise_on_error: false expect(err).to include <<~E.strip The dependencies in your gemfile changed, but the lockfile can't be updated because frozen mode is set (Bundler::ProductionError) You have added to the Gemfile: - * rack (= 1.0.0) - * rack-obama + * myrack (= 1.0.0) + * myrack-obama You have deleted from the Gemfile: - * rack + * myrack E end end @@ -529,24 +467,23 @@ RSpec.describe "install in deployment or frozen mode" do it "works fine after bundle package and bundle install --local" do build_lib "foo", path: lib_path("foo") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :path => "#{lib_path("foo")}" G 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 f972a37bf6..32ca455439 100644 --- a/spec/bundler/install/failure_spec.rb +++ b/spec/bundler/install/failure_spec.rb @@ -15,7 +15,7 @@ RSpec.describe "bundle install" do end install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "rails" G expect(err).to start_with("Gem::Ext::BuildError: ERROR: Failed to build gem native extension.") @@ -40,7 +40,7 @@ In Gemfile: it "removes the downloaded .gem" do install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "a" G @@ -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/force_spec.rb b/spec/bundler/install/force_spec.rb new file mode 100644 index 0000000000..e0f6fb6364 --- /dev/null +++ b/spec/bundler/install/force_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install" do + before :each do + gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + end + + 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") + + bundle :install + myrack_lib.open("w") {|f| f.write("blah blah blah") } + bundle :install, flag => 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 "works on first bundle install" do + bundle :install, flag => true + + expect(out).to include "Installing myrack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" + end + + context "with a git gem" do + let!(:ref) { build_git("foo", "1.0").ref_for("HEAD", 11) } + + before do + gemfile <<-G + source "https://gem.repo1" + gem "foo", :git => "#{lib_path("foo-1.0")}" + G + end + + it "re-installs installed gems" do + foo_lib = default_bundle_path("bundler/gems/foo-1.0-#{ref}/lib/foo.rb") + + bundle :install + foo_lib.open("w") {|f| f.write("blah blah blah") } + bundle :install, flag => true + + expect(foo_lib.open(&:read)).to eq("FOO = '1.0'\n") + expect(the_bundle).to include_gems "foo 1.0" + end + + it "works on first bundle install" do + bundle :install, flag => true + + expect(the_bundle).to include_gems "foo 1.0" + end + end + end + + describe "with --force" do + it_behaves_like "an option to force reinstalling gems" do + let(:flag) { "force" } + end + end + + describe "with --redownload" do + it_behaves_like "an option to force reinstalling gems" do + let(:flag) { "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 cfa66e5986..3afa4f5daa 100644 --- a/spec/bundler/install/gemfile/eval_gemfile_spec.rb +++ b/spec/bundler/install/gemfile/eval_gemfile_spec.rb @@ -2,7 +2,7 @@ RSpec.describe "bundle install with gemfile that uses eval_gemfile" do before do - build_lib("gunks", path: bundled_app.join("gems/gunks")) do |s| + build_lib("gunks", path: bundled_app("gems/gunks")) do |s| s.name = "gunks" s.version = "0.0.1" end @@ -10,15 +10,15 @@ RSpec.describe "bundle install with gemfile that uses eval_gemfile" do context "eval-ed Gemfile points to an internal gemspec" do before do - create_file "Gemfile-other", <<-G - source "#{file_uri_for(gem_repo1)}" + gemfile "Gemfile-other", <<-G + source "https://gem.repo1" gemspec :path => 'gems/gunks' G end it "installs the gemspec specified gem" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" eval_gemfile 'Gemfile-other' G expect(out).to include("Resolving dependencies") @@ -36,12 +36,12 @@ RSpec.describe "bundle install with gemfile that uses eval_gemfile" do build_gem "zip-zip", "0.3" end - create_file bundled_app("gems/Gemfile"), <<-G - source "#{file_uri_for(gem_repo2)}" + gemfile bundled_app("gems/Gemfile"), <<-G + source "https://gem.repo2" gemspec :path => "\#{__dir__}/gunks" - source "#{file_uri_for(gem_repo2)}" do + source "https://gem.repo2" do gem "zip-zip" end G @@ -49,7 +49,7 @@ RSpec.describe "bundle install with gemfile that uses eval_gemfile" do it "installs and finds gems correctly" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "rails" @@ -65,13 +65,13 @@ RSpec.describe "bundle install with gemfile that uses eval_gemfile" do context "eval-ed Gemfile has relative-path gems" do before do build_lib("a", path: bundled_app("gems/a")) - create_file bundled_app("nested/Gemfile-nested"), <<-G - source "#{file_uri_for(gem_repo1)}" + gemfile bundled_app("nested/Gemfile-nested"), <<-G + source "https://gem.repo1" gem "a", :path => "../gems/a" G gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" eval_gemfile "nested/Gemfile-nested" G end @@ -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 @@ -95,7 +95,7 @@ RSpec.describe "bundle install with gemfile that uses eval_gemfile" do it "installs the gemspec specified gem" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" eval_gemfile 'other/Gemfile-other' gemspec :path => 'gems/gunks' G @@ -108,15 +108,15 @@ RSpec.describe "bundle install with gemfile that uses eval_gemfile" do context "eval-ed Gemfile references other gemfiles" do it "works with relative paths" do - create_file "other/Gemfile-other", "gem 'rack'" - create_file "other/Gemfile", "eval_gemfile 'Gemfile-other'" - create_file "Gemfile-alt", <<-G - source "#{file_uri_for(gem_repo1)}" + gemfile "other/Gemfile-other", "gem 'myrack'" + gemfile "other/Gemfile", "eval_gemfile 'Gemfile-other'" + gemfile "Gemfile-alt", <<-G + source "https://gem.repo1" eval_gemfile "other/Gemfile" G install_gemfile "eval_gemfile File.expand_path('Gemfile-alt')" - expect(the_bundle).to include_gem "rack 1.0.0" + expect(the_bundle).to include_gem "myrack 1.0.0" end 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 a29b79ad62..bcc1f36823 100644 --- a/spec/bundler/install/gemfile/force_ruby_platform_spec.rb +++ b/spec/bundler/install/gemfile/force_ruby_platform_spec.rb @@ -5,77 +5,68 @@ RSpec.describe "bundle install with force_ruby_platform DSL option", :jruby do before do build_repo4 do # Build a gem with platform specific versions - build_gem("platform_specific") do |s| - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 RUBY'" - end + build_gem("platform_specific") build_gem("platform_specific") do |s| s.platform = Bundler.local_platform - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 #{Bundler.local_platform}'" end # Build the exact same gem with a different name to compare using vs not using the option - build_gem("platform_specific_forced") do |s| - s.write "lib/platform_specific_forced.rb", "PLATFORM_SPECIFIC_FORCED = '1.0.0 RUBY'" - end + build_gem("platform_specific_forced") build_gem("platform_specific_forced") do |s| s.platform = Bundler.local_platform - s.write "lib/platform_specific_forced.rb", "PLATFORM_SPECIFIC_FORCED = '1.0.0 #{Bundler.local_platform}'" end end end it "pulls the pure ruby variant of the given gem" do install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "platform_specific_forced", :force_ruby_platform => true gem "platform_specific" G - expect(the_bundle).to include_gems "platform_specific_forced 1.0.0 RUBY" - expect(the_bundle).to include_gems "platform_specific 1.0.0 #{Bundler.local_platform}" + expect(the_bundle).to include_gems "platform_specific_forced 1.0 ruby" + expect(the_bundle).to include_gems "platform_specific 1.0 #{Bundler.local_platform}" end it "still respects a global `force_ruby_platform` config" do install_gemfile <<-G, env: { "BUNDLE_FORCE_RUBY_PLATFORM" => "true" } - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "platform_specific_forced", :force_ruby_platform => true gem "platform_specific" G - expect(the_bundle).to include_gems "platform_specific_forced 1.0.0 RUBY" - expect(the_bundle).to include_gems "platform_specific 1.0.0 RUBY" + expect(the_bundle).to include_gems "platform_specific_forced 1.0 ruby" + expect(the_bundle).to include_gems "platform_specific 1.0 ruby" end end context "when also a transitive dependency" do before do build_repo4 do - build_gem("depends_on_platform_specific") {|s| s.add_runtime_dependency "platform_specific" } + build_gem("depends_on_platform_specific") {|s| s.add_dependency "platform_specific" } - build_gem("platform_specific") do |s| - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 RUBY'" - end + build_gem("platform_specific") build_gem("platform_specific") do |s| s.platform = Bundler.local_platform - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 #{Bundler.local_platform}'" end end end it "still pulls the ruby variant" do install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "depends_on_platform_specific" gem "platform_specific", :force_ruby_platform => true G - expect(the_bundle).to include_gems "platform_specific 1.0.0 RUBY" + expect(the_bundle).to include_gems "platform_specific 1.0 ruby" end end @@ -83,42 +74,39 @@ RSpec.describe "bundle install with force_ruby_platform DSL option", :jruby do before do build_repo4 do build_gem("depends_on_platform_specific") do |s| - s.add_runtime_dependency "platform_specific" - s.write "lib/depends_on_platform_specific.rb", "DEPENDS_ON_PLATFORM_SPECIFIC = '1.0.0 RUBY'" + s.add_dependency "platform_specific" end build_gem("depends_on_platform_specific") do |s| - s.add_runtime_dependency "platform_specific" + s.add_dependency "platform_specific" s.platform = Bundler.local_platform - s.write "lib/depends_on_platform_specific.rb", "DEPENDS_ON_PLATFORM_SPECIFIC = '1.0.0 #{Bundler.local_platform}'" end - build_gem("platform_specific") do |s| - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 RUBY'" - end + build_gem("platform_specific") build_gem("platform_specific") do |s| s.platform = Bundler.local_platform - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 #{Bundler.local_platform}'" end end end it "ignores ruby variants for the transitive dependencies" do install_gemfile <<-G, env: { "DEBUG_RESOLVER" => "true" } - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "depends_on_platform_specific", :force_ruby_platform => true G - expect(the_bundle).to include_gems "depends_on_platform_specific 1.0.0 RUBY" - expect(the_bundle).to include_gems "platform_specific 1.0.0 #{Bundler.local_platform}" + expect(the_bundle).to include_gems "depends_on_platform_specific 1.0 ruby" + expect(the_bundle).to include_gems "platform_specific 1.0 #{Bundler.local_platform}" end - it "reinstalls the ruby variant when a platform specific variant is already installed, the lockile has only RUBY platform, and :force_ruby_platform is used in the Gemfile" do + it "reinstalls the ruby variant when a platform specific variant is already installed, the lockile has only ruby platform, and :force_ruby_platform is used in the Gemfile" do + skip "Can't simulate platform reliably on JRuby, installing a platform specific gem fails to activate io-wait because only the -java version is present, and we're simulating a different platform" if RUBY_ENGINE == "jruby" + lockfile <<-L GEM - remote: #{file_uri_for(gem_repo4)} + remote: https://gem.repo4 specs: platform_specific (1.0) @@ -129,18 +117,20 @@ RSpec.describe "bundle install with force_ruby_platform DSL option", :jruby do platform_specific BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L - system_gems "platform_specific-1.0-#{Gem::Platform.local}", path: default_bundle_path + simulate_platform "x86-darwin-100" do + system_gems "platform_specific-1.0-x86-darwin-100", path: default_bundle_path - install_gemfile <<-G, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }, artifice: "compact_index" - source "#{file_uri_for(gem_repo4)}" + install_gemfile <<-G, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }, artifice: "compact_index" + source "https://gem.repo4" - gem "platform_specific", :force_ruby_platform => true - G + gem "platform_specific", :force_ruby_platform => true + G - expect(the_bundle).to include_gems "platform_specific 1.0.0 RUBY" + expect(the_bundle).to include_gems "platform_specific 1.0 ruby" + end end end end diff --git a/spec/bundler/install/gemfile/gemspec_spec.rb b/spec/bundler/install/gemfile/gemspec_spec.rb index 63778567cf..e51fc9247d 100644 --- a/spec/bundler/install/gemfile/gemspec_spec.rb +++ b/spec/bundler/install/gemfile/gemspec_spec.rb @@ -8,45 +8,15 @@ RSpec.describe "bundle install from an existing gemspec" do end end - let(:x64_mingw_archs) do - if RUBY_PLATFORM == "x64-mingw-ucrt" - if Gem.rubygems_version >= Gem::Version.new("3.2.28") - ["x64-mingw-ucrt", "x64-mingw32"] - else - ["x64-mingw32", "x64-unknown"] - end - else - ["x64-mingw32"] - end - end - - let(:x64_mingw_gems) do - x64_mingw_archs.map {|p| "platform_specific (1.0-#{p})" }.join("\n ") - end - - let(:x64_mingw_platforms) do - x64_mingw_archs.join("\n ") - end - - def x64_mingw_checksums(checksums) - x64_mingw_archs.each do |arch| - if arch == "x64-mingw-ucrt" - checksums.no_checksum "platform_specific", "1.0", arch - else - checksums.checksum gem_repo2, "platform_specific", "1.0", arch - end - end - end - it "should install runtime and development dependencies" do - build_lib("foo", path: tmp.join("foo")) do |s| + build_lib("foo", path: tmp("foo")) do |s| s.write("Gemfile", "source :rubygems\ngemspec") s.add_dependency "bar", "=1.0.0" s.add_development_dependency "bar-dev", "=1.0.0" end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gemspec :path => '#{tmp.join("foo")}' + source "https://gem.repo2" + gemspec :path => '#{tmp("foo")}' G expect(the_bundle).to include_gems "bar 1.0.0" @@ -54,16 +24,16 @@ RSpec.describe "bundle install from an existing gemspec" do end it "that is hidden should install runtime and development dependencies" do - build_lib("foo", path: tmp.join("foo")) do |s| + build_lib("foo", path: tmp("foo")) do |s| s.write("Gemfile", "source :rubygems\ngemspec") s.add_dependency "bar", "=1.0.0" s.add_development_dependency "bar-dev", "=1.0.0" end - FileUtils.mv tmp.join("foo", "foo.gemspec"), tmp.join("foo", ".gemspec") + FileUtils.mv tmp("foo", "foo.gemspec"), tmp("foo", ".gemspec") install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gemspec :path => '#{tmp.join("foo")}' + source "https://gem.repo2" + gemspec :path => '#{tmp("foo")}' G expect(the_bundle).to include_gems "bar 1.0.0" @@ -76,50 +46,50 @@ RSpec.describe "bundle install from an existing gemspec" do build_gem "baz", "1.1" end - build_lib("foo", path: tmp.join("foo")) do |s| + build_lib("foo", path: tmp("foo")) do |s| s.write("Gemfile", "source :rubygems\ngemspec") s.add_dependency "baz", ">= 1.0", "< 1.1" end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gemspec :path => '#{tmp.join("foo")}' + source "https://gem.repo2" + gemspec :path => '#{tmp("foo")}' G expect(the_bundle).to include_gems "baz 1.0" end it "should raise if there are no gemspecs available" do - build_lib("foo", path: tmp.join("foo"), gemspec: false) + build_lib("foo", path: tmp("foo"), gemspec: false) install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo2)}" - gemspec :path => '#{tmp.join("foo")}' + source "https://gem.repo2" + gemspec :path => '#{tmp("foo")}' G - expect(err).to match(/There are no gemspecs at #{tmp.join("foo")}/) + expect(err).to match(/There are no gemspecs at #{tmp("foo")}/) end it "should raise if there are too many gemspecs available" do - build_lib("foo", path: tmp.join("foo")) do |s| + build_lib("foo", path: tmp("foo")) do |s| s.write("foo2.gemspec", build_spec("foo", "4.0").first.to_ruby) end install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo2)}" - gemspec :path => '#{tmp.join("foo")}' + source "https://gem.repo2" + gemspec :path => '#{tmp("foo")}' G - expect(err).to match(/There are multiple gemspecs at #{tmp.join("foo")}/) + expect(err).to match(/There are multiple gemspecs at #{tmp("foo")}/) end it "should pick a specific gemspec" do - build_lib("foo", path: tmp.join("foo")) do |s| + build_lib("foo", path: tmp("foo")) do |s| s.write("foo2.gemspec", "") s.add_dependency "bar", "=1.0.0" s.add_development_dependency "bar-dev", "=1.0.0" end install_gemfile(<<-G) - source "#{file_uri_for(gem_repo2)}" - gemspec :path => '#{tmp.join("foo")}', :name => 'foo' + source "https://gem.repo2" + gemspec :path => '#{tmp("foo")}', :name => 'foo' G expect(the_bundle).to include_gems "bar 1.0.0" @@ -127,15 +97,15 @@ RSpec.describe "bundle install from an existing gemspec" do end it "should use a specific group for development dependencies" do - build_lib("foo", path: tmp.join("foo")) do |s| + build_lib("foo", path: tmp("foo")) do |s| s.write("foo2.gemspec", "") s.add_dependency "bar", "=1.0.0" s.add_development_dependency "bar-dev", "=1.0.0" end install_gemfile(<<-G) - source "#{file_uri_for(gem_repo2)}" - gemspec :path => '#{tmp.join("foo")}', :name => 'foo', :development_group => :dev + source "https://gem.repo2" + gemspec :path => '#{tmp("foo")}', :name => 'foo', :development_group => :dev G expect(the_bundle).to include_gems "bar 1.0.0" @@ -144,33 +114,33 @@ RSpec.describe "bundle install from an existing gemspec" do end it "should match a lockfile even if the gemspec defines development dependencies" do - build_lib("foo", path: tmp.join("foo")) do |s| - s.write("Gemfile", "source '#{file_uri_for(gem_repo1)}'\ngemspec") + build_lib("foo", path: tmp("foo")) do |s| + s.write("Gemfile", "source 'https://gem.repo1'\ngemspec") s.add_dependency "actionpack", "=2.3.2" s.add_development_dependency "rake", rake_version end - bundle "install", dir: tmp.join("foo") + bundle "install", dir: tmp("foo"), artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s } # This should really be able to rely on $stderr, but, it's not written # right, so we can't. In fact, this is a bug negation test, and so it'll # ghost pass in future, and will only catch a regression if the message # doesn't change. Exit codes should be used correctly (they can be more # than just 0 and 1). - bundle "config set --local deployment true" - output = bundle("install", dir: tmp.join("foo")) + 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/) expect(output).not_to match(/the lockfile can't be updated because frozen mode is set/) end it "should match a lockfile without needing to re-resolve" do - build_lib("foo", path: tmp.join("foo")) do |s| - s.add_dependency "rack" + build_lib("foo", path: tmp("foo")) do |s| + s.add_dependency "myrack" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gemspec :path => '#{tmp.join("foo")}' + source "https://gem.repo1" + gemspec :path => '#{tmp("foo")}' G bundle "install", verbose: true @@ -180,49 +150,49 @@ 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 - - build_lib("foo", path: tmp.join("foo")) do |s| - s.add_dependency "rack" - s.add_development_dependency "thin" - end + simulate_platform "java" do + build_lib("foo", path: tmp("foo")) do |s| + s.add_dependency "myrack" + s.add_development_dependency "thin" + end - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gemspec :path => '#{tmp.join("foo")}' - G + install_gemfile <<-G + source "https://gem.repo1" + gemspec :path => '#{tmp("foo")}' + G - bundle "install", verbose: true + bundle "install", verbose: true - message = "Found no changes, using resolution from the lockfile" - expect(out.scan(message).size).to eq(1) + message = "Found no changes, using resolution from the lockfile" + expect(out.scan(message).size).to eq(1) + end end it "should match a lockfile on non-ruby platforms with a transitive platform dependency", :jruby_only do - build_lib("foo", path: tmp.join("foo")) do |s| + build_lib("foo", path: tmp("foo")) do |s| s.add_dependency "platform_specific" end system_gems "platform_specific-1.0-java", path: default_bundle_path install_gemfile <<-G - gemspec :path => '#{tmp.join("foo")}' + gemspec :path => '#{tmp("foo")}' G bundle "update --bundler", artifice: "compact_index", verbose: true - expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 JAVA" + expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 java" end it "should evaluate the gemspec in its directory" do - build_lib("foo", path: tmp.join("foo")) - File.open(tmp.join("foo/foo.gemspec"), "w") do |s| - s.write "raise 'ahh' unless Dir.pwd == '#{tmp.join("foo")}'" + build_lib("foo", path: tmp("foo")) + File.open(tmp("foo/foo.gemspec"), "w") do |s| + s.write "raise 'ahh' unless Dir.pwd == '#{tmp("foo")}'" end install_gemfile <<-G, raise_on_error: false - gemspec :path => '#{tmp.join("foo")}' + 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 @@ -231,16 +201,16 @@ RSpec.describe "bundle install from an existing gemspec" do # # issue was caused by rubygems having an unresolved gem during a require, # so emulate that - system_gems %w[rack-1.0.0 rack-0.9.1 rack-obama-1.0] + system_gems %w[myrack-1.0.0 myrack-0.9.1 myrack-obama-1.0] build_lib("foo", path: bundled_app) gemspec = bundled_app("foo.gemspec").read bundled_app("foo.gemspec").open("w") do |f| - f.write "#{gemspec.strip}.tap { gem 'rack-obama'; require 'rack/obama' }" + f.write "#{gemspec.strip}.tap { gem 'myrack-obama'; require 'myrack/obama' }" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gemspec G @@ -248,7 +218,7 @@ RSpec.describe "bundle install from an existing gemspec" do end it "allows conflicts" do - build_lib("foo", path: tmp.join("foo")) do |s| + build_lib("foo", path: tmp("foo")) do |s| s.version = "1.0.0" s.add_dependency "bar", "= 1.0.0" end @@ -258,16 +228,16 @@ RSpec.describe "bundle install from an existing gemspec" do build_gem "foo", "0.0.1", to_bundle: true install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "deps" - gemspec :path => '#{tmp.join("foo")}', :name => 'foo' + gemspec :path => '#{tmp("foo")}', :name => 'foo' G expect(the_bundle).to include_gems "foo 1.0.0" end it "does not break Gem.finish_resolve with conflicts" do - build_lib("foo", path: tmp.join("foo")) do |s| + build_lib("foo", path: tmp("foo")) do |s| s.version = "1.0.0" s.add_dependency "bar", "= 1.0.0" end @@ -279,9 +249,9 @@ RSpec.describe "bundle install from an existing gemspec" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "deps" - gemspec :path => '#{tmp.join("foo")}', :name => 'foo' + gemspec :path => '#{tmp("foo")}', :name => 'foo' G expect(the_bundle).to include_gems "foo 1.0.0" @@ -290,11 +260,30 @@ 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") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gemspec :path => "#{lib_path("omg")}" G @@ -313,7 +302,7 @@ RSpec.describe "bundle install from an existing gemspec" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gemspec G @@ -323,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") @@ -335,23 +324,23 @@ RSpec.describe "bundle install from an existing gemspec" do before do # build the "parent" gem that depends on another gem in the same repo build_lib "source_conflict", path: bundled_app do |s| - s.add_dependency "rack_middleware" + s.add_dependency "myrack_middleware" end # build the "child" gem that is the same version as a released gem, but # has completely different and conflicting dependency requirements - build_lib "rack_middleware", "1.0", path: bundled_app("rack_middleware") do |s| - s.add_dependency "rack", "1.0" # anything other than 0.9.1 + build_lib "myrack_middleware", "1.0", path: bundled_app("myrack_middleware") do |s| + s.add_dependency "myrack", "1.0" # anything other than 0.9.1 end end it "should install the child gemspec's deps" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gemspec G - expect(the_bundle).to include_gems "rack 1.0" + expect(the_bundle).to include_gems "myrack 1.0" end end @@ -359,8 +348,8 @@ RSpec.describe "bundle install from an existing gemspec" do let(:source_uri) { "http://localgemserver.test" } before do - build_lib("foo", path: tmp.join("foo")) do |s| - s.add_dependency "rack", "=1.0.0" + build_lib("foo", path: tmp("foo")) do |s| + s.add_dependency "myrack", "=1.0.0" end gemfile <<-G @@ -368,7 +357,7 @@ RSpec.describe "bundle install from an existing gemspec" do gemspec :path => "../foo" G - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.no_checksum "foo", "1.0" end @@ -377,30 +366,30 @@ RSpec.describe "bundle install from an existing gemspec" do remote: ../foo specs: foo (1.0) - rack (= 1.0.0) + myrack (= 1.0.0) GEM remote: #{source_uri} specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS - #{generic_local_platform} + ruby DEPENDENCIES foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end context "using JRuby with explicit platform", :jruby_only do before do create_file( - tmp.join("foo", "foo-java.gemspec"), + tmp("foo", "foo-java.gemspec"), build_spec("foo", "1.0", "java") do - dep "rack", "=1.0.0" + dep "myrack", "=1.0.0" @spec.authors = "authors" @spec.summary = "summary" end.first.to_ruby @@ -409,15 +398,15 @@ RSpec.describe "bundle install from an existing gemspec" do it "should install" do results = bundle "install", artifice: "endpoint" - expect(results).to include("Installing rack 1.0.0") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(results).to include("Installing myrack 1.0.0") + expect(the_bundle).to include_gems "myrack 1.0.0" end end it "should install", :jruby do results = bundle "install", artifice: "endpoint" - expect(results).to include("Installing rack 1.0.0") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(results).to include("Installing myrack 1.0.0") + expect(the_bundle).to include_gems "myrack 1.0.0" end context "bundled for multiple platforms" do @@ -431,43 +420,45 @@ 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 gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" 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 context "as a runtime dependency" do it "keeps all platform dependencies in the lockfile" do - expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 RUBY" + expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 ruby" - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.no_checksum "foo", "1.0" c.checksum gem_repo2, "platform_specific", "1.0" c.checksum gem_repo2, "platform_specific", "1.0", "java" - x64_mingw_checksums(c) + c.checksum gem_repo2, "platform_specific", "1.0", "x64-mingw-ucrt" end expect(lockfile).to eq <<~L @@ -478,22 +469,22 @@ RSpec.describe "bundle install from an existing gemspec" do platform_specific GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ 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 @@ -502,13 +493,13 @@ RSpec.describe "bundle install from an existing gemspec" do let(:platform_specific_type) { :development } it "keeps all platform dependencies in the lockfile" do - expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 RUBY" + expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 ruby" - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.no_checksum "foo", "1.0" c.checksum gem_repo2, "platform_specific", "1.0" c.checksum gem_repo2, "platform_specific", "1.0", "java" - x64_mingw_checksums(c) + c.checksum gem_repo2, "platform_specific", "1.0", "x64-mingw-ucrt" end expect(lockfile).to eq <<~L @@ -518,23 +509,23 @@ RSpec.describe "bundle install from an existing gemspec" do foo (1.0) GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ 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 @@ -544,14 +535,14 @@ RSpec.describe "bundle install from an existing gemspec" do let(:dependency) { "indirect_platform_specific" } it "keeps all platform dependencies in the lockfile" do - expect(the_bundle).to include_gems "foo 1.0", "indirect_platform_specific 1.0", "platform_specific 1.0 RUBY" + expect(the_bundle).to include_gems "foo 1.0", "indirect_platform_specific 1.0", "platform_specific 1.0 ruby" - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.no_checksum "foo", "1.0" c.checksum gem_repo2, "indirect_platform_specific", "1.0" c.checksum gem_repo2, "platform_specific", "1.0" c.checksum gem_repo2, "platform_specific", "1.0", "java" - x64_mingw_checksums(c) + c.checksum gem_repo2, "platform_specific", "1.0", "x64-mingw-ucrt" end expect(lockfile).to eq <<~L @@ -561,25 +552,25 @@ RSpec.describe "bundle install from an existing gemspec" do foo (1.0) GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: indirect_platform_specific (1.0) 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 @@ -589,62 +580,62 @@ RSpec.describe "bundle install from an existing gemspec" do context "with multiple platforms" do before do - build_lib("foo", path: tmp.join("foo")) do |s| + build_lib("foo", path: tmp("foo")) do |s| s.version = "1.0.0" - s.add_development_dependency "rack" - s.write "foo-universal-java.gemspec", build_spec("foo", "1.0.0", "universal-java") {|sj| sj.runtime "rack", "1.0.0" }.first.to_ruby + s.add_development_dependency "myrack" + s.write "foo-universal-java.gemspec", build_spec("foo", "1.0.0", "universal-java") {|sj| sj.runtime "myrack", "1.0.0" }.first.to_ruby end 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 "#{file_uri_for(gem_repo1)}" - gemspec :path => '#{tmp.join("foo")}', :name => 'foo' + source "https://gem.repo1" + gemspec :path => '#{tmp("foo")}', :name => 'foo' G - expect(the_bundle).to include_gems "foo 1.0.0", "rack 1.0.0" + expect(the_bundle).to include_gems "foo 1.0.0", "myrack 1.0.0" 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 "#{file_uri_for(gem_repo1)}" - gemspec :path => '#{tmp.join("foo")}', :name => 'foo' + source "https://gem.repo1" + gemspec :path => '#{tmp("foo")}', :name => 'foo' G expect(the_bundle).to include_gem "foo 1.0.0" - expect(the_bundle).not_to include_gem "rack" + expect(the_bundle).not_to include_gem "myrack" end end context "with multiple platforms and resolving for more specific platforms" do before do - build_lib("chef", path: tmp.join("chef")) do |s| + build_lib("chef", path: tmp("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 gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gemspec :path => "../chef" G - checksums = checksums_section_when_existing do |c| + 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 @@ -652,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: #{file_uri_for(gem_repo4)}/ + 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 @@ -682,7 +671,7 @@ RSpec.describe "bundle install from an existing gemspec" do context "with multiple locked platforms" do before do - build_lib("activeadmin", path: tmp.join("activeadmin")) do |s| + build_lib("activeadmin", path: tmp("activeadmin")) do |s| s.version = "2.9.0" s.add_dependency "railties", ">= 5.2", "< 6.2" end @@ -696,7 +685,7 @@ RSpec.describe "bundle install from an existing gemspec" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gemspec :path => "../activeadmin" gem "jruby-openssl", :platform => :jruby G @@ -705,9 +694,9 @@ RSpec.describe "bundle install from an existing gemspec" do end it "does not remove the platform specific specs from the lockfile when re-resolving due to gemspec changes" do - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.no_checksum "activeadmin", "2.9.0" - c.no_checksum "jruby-openssl", "0.10.7", "java" + c.checksum gem_repo4, "jruby-openssl", "0.10.7", "java" c.checksum gem_repo4, "railties", "6.1.4" end @@ -719,7 +708,7 @@ RSpec.describe "bundle install from an existing gemspec" do railties (>= 5.2, < 6.2) GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: jruby-openssl (0.10.7-java) railties (6.1.4) @@ -732,10 +721,10 @@ RSpec.describe "bundle install from an existing gemspec" do jruby-openssl #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L - gemspec = tmp.join("activeadmin/activeadmin.gemspec") + gemspec = tmp("activeadmin/activeadmin.gemspec") File.write(gemspec, File.read(gemspec).sub(">= 5.2", ">= 6.0")) previous_lockfile = lockfile diff --git a/spec/bundler/install/gemfile/git_spec.rb b/spec/bundler/install/gemfile/git_spec.rb index 45ee7b44d1..b2a82caf01 100644 --- a/spec/bundler/install/gemfile/git_spec.rb +++ b/spec/bundler/install/gemfile/git_spec.rb @@ -2,20 +2,25 @@ 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 - source "#{file_uri_for(gem_repo1)}" + let(:base_gemfile) do + <<-G + source "https://gem.repo1" git "#{lib_path("foo-1.0")}" do gem 'foo' end 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.join("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 @@ -59,7 +104,7 @@ RSpec.describe "bundle install with git sources" do bundle "update foo" sha = git.ref_for("main", 11) - spec_file = default_bundle_path.join("bundler/gems/foo-1.0-#{sha}/foo.gemspec") + spec_file = default_bundle_path("bundler/gems/foo-1.0-#{sha}/foo.gemspec") expect(spec_file).to exist ruby_code = Gem::Specification.load(spec_file.to_s).to_ruby file_code = File.read(spec_file) @@ -67,10 +112,11 @@ 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 - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("foo-1.0")}" do gem 'foo' end @@ -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 @@ -93,7 +140,7 @@ RSpec.describe "bundle install with git sources" do build_git "foo" install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", "1.1", :git => "#{lib_path("foo-1.0")}" G @@ -106,7 +153,7 @@ RSpec.describe "bundle install with git sources" do end install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" platforms :jruby do gem "only_java", "1.2", :git => "#{lib_path("only_java-1.0-java")}" end @@ -126,7 +173,7 @@ RSpec.describe "bundle install with git sources" do end install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" platforms :jruby do gem "only_java", "1.2", :git => "#{lib_path("only_java-1.1-java")}" 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,25 +192,25 @@ 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") update_git "foo", "1.1", path: lib_path("foo-1.0") gemfile tmp("bundled_app.bck/Gemfile"), <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("foo-1.0")}" do gem 'foo' end - gem "rack", "1.0" + gem "myrack", "1.0" G bundle "update foo", dir: tmp("bundled_app.bck") - expect(the_bundle).to include_gems "foo 1.1", "rack 1.0", dir: tmp("bundled_app.bck") + expect(the_bundle).to include_gems "foo 1.1", "myrack 1.0", dir: tmp("bundled_app.bck") end end @@ -171,8 +218,8 @@ RSpec.describe "bundle install with git sources" do before do build_git "foo" gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" git "#{lib_path("foo-1.0")}" do # this page left intentionally blank @@ -182,7 +229,7 @@ RSpec.describe "bundle install with git sources" do it "does not explode" do bundle "install" - expect(the_bundle).to include_gems "rack 1.0" + expect(the_bundle).to include_gems "myrack 1.0" end end @@ -195,7 +242,7 @@ RSpec.describe "bundle install with git sources" do it "works" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("foo-1.0")}", :ref => "#{@revision}" do gem "foo" end @@ -212,7 +259,7 @@ RSpec.describe "bundle install with git sources" do it "works when the revision is a symbol" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("foo-1.0")}", :ref => #{@revision.to_sym.inspect} do gem "foo" end @@ -229,14 +276,14 @@ RSpec.describe "bundle install with git sources" do it "works when an abbreviated revision is added after an initial, potentially shallow clone" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("foo-1.0")}" do gem "foo" end G install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("foo-1.0")}", :ref => #{@revision[0..7].inspect} do gem "foo" end @@ -246,11 +293,11 @@ RSpec.describe "bundle install with git sources" do it "works when a tag that does not look like a commit hash is used as the value of :ref" do build_git "foo" @remote = build_git("bar", bare: true) - update_git "foo", remote: file_uri_for(@remote.path) + update_git "foo", remote: @remote.path update_git "foo", push: "main" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'foo', :git => "#{@remote.path}" G @@ -259,7 +306,7 @@ RSpec.describe "bundle install with git sources" do update_git "foo", push: "v1.0.0" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'foo', :git => "#{@remote.path}", :ref => "v1.0.0" G @@ -272,7 +319,7 @@ RSpec.describe "bundle install with git sources" do s.write("lib/foo.rb", "raise 'FAIL'") end - sys_exec("git update-ref -m \"Bundler Spec!\" refs/bundler/1 main~1", dir: lib_path("foo-1.0")) + git("update-ref -m \"Bundler Spec!\" refs/bundler/1 main~1", lib_path("foo-1.0")) # want to ensure we don't fallback to HEAD update_git "foo", path: lib_path("foo-1.0"), branch: "rando" do |s| @@ -280,7 +327,7 @@ RSpec.describe "bundle install with git sources" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("foo-1.0")}", :ref => "refs/bundler/1" do gem "foo" end @@ -297,7 +344,7 @@ RSpec.describe "bundle install with git sources" do it "works when the revision is a non-head ref and it was previously downloaded" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("foo-1.0")}" do gem "foo" end @@ -308,7 +355,7 @@ RSpec.describe "bundle install with git sources" do s.write("lib/foo.rb", "raise 'FAIL'") end - sys_exec("git update-ref -m \"Bundler Spec!\" refs/bundler/1 main~1", dir: lib_path("foo-1.0")) + git("update-ref -m \"Bundler Spec!\" refs/bundler/1 main~1", lib_path("foo-1.0")) # want to ensure we don't fallback to HEAD update_git "foo", path: lib_path("foo-1.0"), branch: "rando" do |s| @@ -316,7 +363,7 @@ RSpec.describe "bundle install with git sources" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("foo-1.0")}", :ref => "refs/bundler/1" do gem "foo" end @@ -332,12 +379,12 @@ RSpec.describe "bundle install with git sources" do end it "does not download random non-head refs" do - sys_exec("git update-ref -m \"Bundler Spec!\" refs/bundler/1 main~1", dir: lib_path("foo-1.0")) + 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("foo-1.0")}" do gem "foo" end @@ -346,7 +393,7 @@ RSpec.describe "bundle install with git sources" do # ensure we also git fetch after cloning bundle :update, all: true - sys_exec("git ls-remote .", dir: Dir[home(".bundle/cache/git/foo-*")].first) + git("ls-remote .", Dir[home(".bundle/cache/git/foo-*")].first) expect(out).not_to include("refs/bundler/1") end @@ -360,7 +407,7 @@ RSpec.describe "bundle install with git sources" do update_git("foo", path: repo, branch: branch) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{repo}", :branch => #{branch.dump} do gem "foo" end @@ -377,7 +424,7 @@ RSpec.describe "bundle install with git sources" do update_git("foo", path: repo, branch: branch) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{repo}", :branch => #{branch.dump} do gem "foo" end @@ -395,7 +442,7 @@ RSpec.describe "bundle install with git sources" do update_git("foo", path: repo, branch: branch) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{repo}", :branch => #{branch.dump} do gem "foo" end @@ -414,7 +461,7 @@ RSpec.describe "bundle install with git sources" do update_git("foo", path: repo, tag: tag) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{repo}", :tag => #{tag.dump} do gem "foo" end @@ -431,7 +478,7 @@ RSpec.describe "bundle install with git sources" do update_git("foo", path: repo, tag: tag) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{repo}", :tag => #{tag.dump} do gem "foo" end @@ -449,7 +496,7 @@ RSpec.describe "bundle install with git sources" do update_git("foo", path: repo, tag: tag) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{repo}", :tag => #{tag.dump} do gem "foo" end @@ -462,100 +509,100 @@ RSpec.describe "bundle install with git sources" do describe "when specifying local override" do it "uses the local repository instead of checking a new one out" do - build_git "rack", "0.8", path: lib_path("local-rack") do |s| - s.write "lib/rack.rb", "puts :LOCAL" + build_git "myrack", "0.8", path: lib_path("local-myrack") do |s| + s.write "lib/myrack.rb", "puts :LOCAL" end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main" G - bundle %(config set local.rack #{lib_path("local-rack")}) + bundle %(config set local.myrack #{lib_path("local-myrack")}) bundle :install - run "require 'rack'" + run "require 'myrack'" expect(out).to eq("LOCAL") end it "chooses the local repository on runtime" do - build_git "rack", "0.8" + build_git "myrack", "0.8" - FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) + FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack")) - update_git "rack", "0.8", path: lib_path("local-rack") do |s| - s.write "lib/rack.rb", "puts :LOCAL" + update_git "myrack", "0.8", path: lib_path("local-myrack") do |s| + s.write "lib/myrack.rb", "puts :LOCAL" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main" G - bundle %(config set local.rack #{lib_path("local-rack")}) - run "require 'rack'" + bundle %(config set local.myrack #{lib_path("local-myrack")}) + run "require 'myrack'" expect(out).to eq("LOCAL") end it "unlocks the source when the dependencies have changed while switching to the local" do - build_git "rack", "0.8" + build_git "myrack", "0.8" - FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) + FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack")) - update_git "rack", "0.8", path: lib_path("local-rack") do |s| - s.write "rack.gemspec", build_spec("rack", "0.8") { runtime "rspec", "> 0" }.first.to_ruby - s.write "lib/rack.rb", "puts :LOCAL" + update_git "myrack", "0.8", path: lib_path("local-myrack") do |s| + s.write "myrack.gemspec", build_spec("myrack", "0.8") { runtime "rspec", "> 0" }.first.to_ruby + s.write "lib/myrack.rb", "puts :LOCAL" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main" G - bundle %(config set local.rack #{lib_path("local-rack")}) + bundle %(config set local.myrack #{lib_path("local-myrack")}) bundle :install - run "require 'rack'" + run "require 'myrack'" expect(out).to eq("LOCAL") end it "updates specs on runtime" do system_gems "nokogiri-1.4.2" - build_git "rack", "0.8" + build_git "myrack", "0.8" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main" G lockfile0 = File.read(bundled_app_lock) - FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) - update_git "rack", "0.8", path: lib_path("local-rack") do |s| + FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack")) + update_git "myrack", "0.8", path: lib_path("local-myrack") do |s| s.add_dependency "nokogiri", "1.4.2" end - bundle %(config set local.rack #{lib_path("local-rack")}) - run "require 'rack'" + bundle %(config set local.myrack #{lib_path("local-myrack")}) + run "require 'myrack'" lockfile1 = File.read(bundled_app_lock) expect(lockfile1).not_to eq(lockfile0) end it "updates ref on install" do - build_git "rack", "0.8" + build_git "myrack", "0.8" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main" G lockfile0 = File.read(bundled_app_lock) - FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) - update_git "rack", "0.8", path: lib_path("local-rack") + FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack")) + update_git "myrack", "0.8", path: lib_path("local-myrack") - bundle %(config set local.rack #{lib_path("local-rack")}) + bundle %(config set local.myrack #{lib_path("local-myrack")}) bundle :install lockfile1 = File.read(bundled_app_lock) @@ -563,18 +610,18 @@ RSpec.describe "bundle install with git sources" do end it "explodes and gives correct solution if given path does not exist on install" do - build_git "rack", "0.8" + build_git "myrack", "0.8" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main" G - bundle %(config set local.rack #{lib_path("local-rack")}) + bundle %(config set local.myrack #{lib_path("local-myrack")}) bundle :install, raise_on_error: false - expect(err).to match(/Cannot use local override for rack-0.8 because #{Regexp.escape(lib_path("local-rack").to_s)} does not exist/) + expect(err).to match(/Cannot use local override for myrack-0.8 because #{Regexp.escape(lib_path("local-myrack").to_s)} does not exist/) - solution = "config unset local.rack" + solution = "config unset local.myrack" expect(err).to match(/Run `bundle #{solution}` to remove the local override/) bundle solution @@ -584,19 +631,19 @@ RSpec.describe "bundle install with git sources" do end it "explodes and gives correct solution if branch is not given on install" do - build_git "rack", "0.8" - FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) + build_git "myrack", "0.8" + FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack")) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}" + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}" G - bundle %(config set local.rack #{lib_path("local-rack")}) + bundle %(config set local.myrack #{lib_path("local-myrack")}) bundle :install, raise_on_error: false - expect(err).to match(/Cannot use local override for rack-0.8 at #{Regexp.escape(lib_path("local-rack").to_s)} because :branch is not specified in Gemfile/) + expect(err).to match(/Cannot use local override for myrack-0.8 at #{Regexp.escape(lib_path("local-myrack").to_s)} because :branch is not specified in Gemfile/) - solution = "config unset local.rack" + solution = "config unset local.myrack" expect(err).to match(/Specify a branch or run `bundle #{solution}` to remove the local override/) bundle solution @@ -606,69 +653,69 @@ RSpec.describe "bundle install with git sources" do end it "does not explode if disable_local_branch_check is given" do - build_git "rack", "0.8" - FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) + build_git "myrack", "0.8" + FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack")) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}" + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}" G - bundle %(config set local.rack #{lib_path("local-rack")}) + bundle %(config set local.myrack #{lib_path("local-myrack")}) bundle %(config set disable_local_branch_check true) bundle :install expect(out).to match(/Bundle complete!/) end it "explodes on different branches on install" do - build_git "rack", "0.8" + build_git "myrack", "0.8" - FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) + FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack")) - update_git "rack", "0.8", path: lib_path("local-rack"), branch: "another" do |s| - s.write "lib/rack.rb", "puts :LOCAL" + update_git "myrack", "0.8", path: lib_path("local-myrack"), branch: "another" do |s| + s.write "lib/myrack.rb", "puts :LOCAL" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main" G - bundle %(config set local.rack #{lib_path("local-rack")}) + bundle %(config set local.myrack #{lib_path("local-myrack")}) bundle :install, raise_on_error: false expect(err).to match(/is using branch another but Gemfile specifies main/) end it "explodes on invalid revision on install" do - build_git "rack", "0.8" + build_git "myrack", "0.8" - build_git "rack", "0.8", path: lib_path("local-rack") do |s| - s.write "lib/rack.rb", "puts :LOCAL" + build_git "myrack", "0.8", path: lib_path("local-myrack") do |s| + s.write "lib/myrack.rb", "puts :LOCAL" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main" G - bundle %(config set local.rack #{lib_path("local-rack")}) + bundle %(config set local.myrack #{lib_path("local-myrack")}) bundle :install, raise_on_error: false expect(err).to match(/The Gemfile lock is pointing to revision \w+/) end it "does not explode on invalid revision on install" do - build_git "rack", "0.8" + build_git "myrack", "0.8" - build_git "rack", "0.8", path: lib_path("local-rack") do |s| - s.write "lib/rack.rb", "puts :LOCAL" + build_git "myrack", "0.8", path: lib_path("local-myrack") do |s| + s.write "lib/myrack.rb", "puts :LOCAL" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main" G - bundle %(config set local.rack #{lib_path("local-rack")}) + bundle %(config set local.myrack #{lib_path("local-myrack")}) bundle %(config set disable_local_revision_check true) bundle :install expect(out).to match(/Bundle complete!/) @@ -693,66 +740,66 @@ RSpec.describe "bundle install with git sources" do # end it "installs from git even if a newer gem is available elsewhere" do - build_git "rack", "0.8" + build_git "myrack", "0.8" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}" + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}" G - expect(the_bundle).to include_gems "rack 0.8" + expect(the_bundle).to include_gems "myrack 0.8" end it "installs dependencies from git even if a newer gem is available elsewhere" do - system_gems "rack-1.0.0" + system_gems "myrack-1.0.0" - build_lib "rack", "1.0", path: lib_path("nested/bar") do |s| - s.write "lib/rack.rb", "puts 'WIN OVERRIDE'" + build_lib "myrack", "1.0", path: lib_path("nested/bar") do |s| + s.write "lib/myrack.rb", "puts 'WIN OVERRIDE'" end build_git "foo", path: lib_path("nested") do |s| - s.add_dependency "rack", "= 1.0" + s.add_dependency "myrack", "= 1.0" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("nested")}" G - run "require 'rack'" + run "require 'myrack'" expect(out).to eq("WIN OVERRIDE") end it "correctly unlocks when changing to a git source" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "0.9.1" + source "https://gem.repo1" + gem "myrack", "0.9.1" G - build_git "rack", path: lib_path("rack") + build_git "myrack", path: lib_path("myrack") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "1.0.0", :git => "#{lib_path("rack")}" + source "https://gem.repo1" + gem "myrack", "1.0.0", :git => "#{lib_path("myrack")}" G - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "correctly unlocks when changing to a git source without versions" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G - build_git "rack", "1.2", path: lib_path("rack") + build_git "myrack", "1.2", path: lib_path("myrack") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack")}" + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack")}" G - expect(the_bundle).to include_gems "rack 1.2" + expect(the_bundle).to include_gems "myrack 1.2" end end @@ -762,7 +809,7 @@ RSpec.describe "bundle install with git sources" do build_lib "hi2u", path: lib_path("hi2u") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" path "#{lib_path("hi2u")}" do gem "omg" gem "hi2u" @@ -779,7 +826,7 @@ RSpec.describe "bundle install with git sources" do update_git "foo" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "#{@revision}" G @@ -797,7 +844,7 @@ RSpec.describe "bundle install with git sources" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}" gem "rails", "2.3.2" G @@ -827,7 +874,7 @@ RSpec.describe "bundle install with git sources" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "bar", :git => "#{lib_path("foo")}" gem "rails", "2.3.2" G @@ -836,6 +883,32 @@ RSpec.describe "bundle install with git sources" do expect(the_bundle).to include_gems "rails 2.3.2" end + it "runs the gemspec in the context of its parent directory, when using local overrides" do + build_git "foo", path: lib_path("foo"), gemspec: false do |s| + s.write lib_path("foo/lib/foo/version.rb"), %(FOO_VERSION = '1.0') + s.write "foo.gemspec", <<-G + $:.unshift Dir.pwd + require 'lib/foo/version' + Gem::Specification.new do |s| + s.name = 'foo' + s.author = 'no one' + s.version = FOO_VERSION + s.summary = 'Foo' + s.files = Dir["lib/**/*.rb"] + end + G + end + + gemfile <<-G + source "https://gem.repo1" + gem "foo", :git => "https://github.com/gems/foo", branch: "main" + G + + bundle %(config set local.foo #{lib_path("foo")}) + + expect(the_bundle).to include_gems "foo 1.0" + end + it "installs from git even if a rubygems gem is present" do build_gem "foo", "1.0", path: lib_path("fake_foo"), to_system: true do |s| s.write "lib/foo.rb", "raise 'FAIL'" @@ -844,7 +917,7 @@ RSpec.describe "bundle install with git sources" do build_git "foo", "1.0" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", "1.0", :git => "#{lib_path("foo-1.0")}" G @@ -855,7 +928,7 @@ RSpec.describe "bundle install with git sources" do build_git "foo", gemspec: false install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", "1.0", :git => "#{lib_path("foo-1.0")}" gem "rails", "2.3.2" G @@ -866,7 +939,7 @@ RSpec.describe "bundle install with git sources" do it "catches git errors and spits out useful output" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", "1.0", :git => "omgomg" G @@ -881,7 +954,7 @@ RSpec.describe "bundle install with git sources" do build_git "foo", path: lib_path("foo space-1.0") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo space-1.0")}" G @@ -892,7 +965,7 @@ RSpec.describe "bundle install with git sources" do build_git "forced", "1.0" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("forced-1.0")}" do gem 'forced' end @@ -906,7 +979,7 @@ RSpec.describe "bundle install with git sources" do bundle "update", all: true expect(the_bundle).to include_gems "forced 1.1" - sys_exec("git reset --hard HEAD^", dir: lib_path("forced-1.0")) + git("reset --hard HEAD^", lib_path("forced-1.0")) bundle "update", all: true expect(the_bundle).to include_gems "forced 1.0" @@ -920,16 +993,16 @@ RSpec.describe "bundle install with git sources" do build_git "has_submodule", "1.0" do |s| s.add_dependency "submodule" end - sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0", dir: lib_path("has_submodule-1.0") - sys_exec "git commit -m \"submodulator\"", dir: lib_path("has_submodule-1.0") + git "submodule add #{lib_path("submodule-1.0")} submodule-1.0", lib_path("has_submodule-1.0") + git "commit -m \"submodulator\"", lib_path("has_submodule-1.0") install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("has_submodule-1.0")}" do gem "has_submodule" end G - expect(err).to match(%r{submodule >= 0 could not be found in rubygems repository #{file_uri_for(gem_repo1)}/, cached gems or installed locally}) + expect(err).to match(%r{submodule >= 0 could not be found in rubygems repository https://gem.repo1/ or installed locally}) expect(the_bundle).not_to include_gems "has_submodule 1.0" end @@ -942,11 +1015,11 @@ RSpec.describe "bundle install with git sources" do build_git "has_submodule", "1.0" do |s| s.add_dependency "submodule" end - sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0", dir: lib_path("has_submodule-1.0") - sys_exec "git commit -m \"submodulator\"", dir: lib_path("has_submodule-1.0") + git "submodule add #{lib_path("submodule-1.0")} submodule-1.0", lib_path("has_submodule-1.0") + git "commit -m \"submodulator\"", lib_path("has_submodule-1.0") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("has_submodule-1.0")}", :submodules => true do gem "has_submodule" end @@ -962,11 +1035,11 @@ RSpec.describe "bundle install with git sources" do build_git "submodule", "1.0" build_git "has_submodule", "1.0" - sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0", dir: lib_path("has_submodule-1.0") - sys_exec "git commit -m \"submodulator\"", dir: lib_path("has_submodule-1.0") + git "submodule add #{lib_path("submodule-1.0")} submodule-1.0", lib_path("has_submodule-1.0") + git "commit -m \"submodulator\"", lib_path("has_submodule-1.0") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("has_submodule-1.0")}" do gem "has_submodule" end @@ -981,7 +1054,7 @@ RSpec.describe "bundle install with git sources" do git = build_git "foo" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("foo-1.0")}" do gem "foo" end @@ -991,7 +1064,7 @@ RSpec.describe "bundle install with git sources" do update_git "foo" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("foo-1.0")}", :ref => "#{git.ref_for("HEAD^")}" do gem "foo" end @@ -1005,15 +1078,15 @@ 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 - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" 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) @@ -1023,7 +1096,7 @@ RSpec.describe "bundle install with git sources" do build_git "foo" gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -1038,11 +1111,11 @@ RSpec.describe "bundle install with git sources" do FileUtils.touch(default_bundle_path("bundler")) install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}" G - expect(exitstatus).to_not eq(0) + expect(last_command).to be_failure expect(err).to include("Bundler could not install a gem because it " \ "needs to create a directory, but a file exists " \ "- #{default_bundle_path("bundler")}") @@ -1056,7 +1129,7 @@ RSpec.describe "bundle install with git sources" do build_git "bar", path: lib_path("nested") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("nested")}" gem "bar", :git => "#{lib_path("nested")}" G @@ -1075,12 +1148,12 @@ RSpec.describe "bundle install with git sources" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "bar", :path => "#{lib_path("bar")}" G install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "bar", :git => "#{lib_path("bar")}" G @@ -1089,24 +1162,67 @@ RSpec.describe "bundle install with git sources" do it "doesn't explode when switching Gem to Git source" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack-obama" - gem "rack", "1.0.0" + source "https://gem.repo1" + gem "myrack-obama" + gem "myrack", "1.0.0" G - build_git "rack", "1.0" do |s| + build_git "myrack", "1.0" do |s| s.write "lib/new_file.rb", "puts 'USING GIT'" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack-obama" - gem "rack", "1.0.0", :git => "#{lib_path("rack-1.0")}" + source "https://gem.repo1" + gem "myrack-obama" + gem "myrack", "1.0.0", :git => "#{lib_path("myrack-1.0")}" G run "require 'new_file'" expect(out).to eq("USING GIT") end + + it "doesn't explode when removing an explicit exact version from a git gem with dependencies" do + build_lib "activesupport", "7.1.4", path: lib_path("rails/activesupport") + build_git "rails", "7.1.4", path: lib_path("rails") do |s| + s.add_dependency "activesupport", "= 7.1.4" + end + + install_gemfile <<-G + source "https://gem.repo1" + gem "rails", "7.1.4", :git => "#{lib_path("rails")}" + G + + install_gemfile <<-G + source "https://gem.repo1" + gem "rails", :git => "#{lib_path("rails")}" + G + + 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 @@ -1114,8 +1230,8 @@ RSpec.describe "bundle install with git sources" do build_git "valim" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "valim", :git => "#{file_uri_for(lib_path("valim-1.0"))}" + source "https://gem.repo1" + gem "valim", :git => "#{lib_path("valim-1.0")}" G old_revision = revision_for(lib_path("valim-1.0")) @@ -1140,14 +1256,14 @@ RSpec.describe "bundle install with git sources" do revision = revision_for(lib_path("foo-1.0")) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}", :ref => "#{revision}" + source "https://gem.repo1" + gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "#{revision}" G expect(out).to_not match(/Revision.*does not exist/) install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo1)}" - gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}", :ref => "deadbeef" + source "https://gem.repo1" + gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "deadbeef" G expect(err).to include("Revision deadbeef does not exist in the repository") end @@ -1156,8 +1272,8 @@ RSpec.describe "bundle install with git sources" do build_git "foo" install_gemfile <<-G, env: { "LANG" => "en" }, raise_on_error: false - source "#{file_uri_for(gem_repo1)}" - gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}", :branch => "deadbeef" + source "https://gem.repo1" + gem "foo", :git => "#{lib_path("foo-1.0")}", :branch => "deadbeef" G expect(err).to include("Revision deadbeef does not exist in the repository") @@ -1169,13 +1285,13 @@ RSpec.describe "bundle install with git sources" do build_git "valim", path: lib_path("valim") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" 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 @@ -1184,7 +1300,7 @@ RSpec.describe "bundle install with git sources" do it "runs pre-install hooks" do build_git "foo" gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -1204,7 +1320,7 @@ RSpec.describe "bundle install with git sources" do it "runs post-install hooks" do build_git "foo" gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -1224,7 +1340,7 @@ RSpec.describe "bundle install with git sources" do it "complains if the install hook fails" do build_git "foo" gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -1258,7 +1374,7 @@ RSpec.describe "bundle install with git sources" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -1288,16 +1404,16 @@ 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 - sys_exec("git commit -m \"commit for iteration #{i}\" ext/foo.c", dir: git_reader.path) + git("commit -m \"commit for iteration #{i}\" ext/foo.c", git_reader.path) git_commit_sha = git_reader.ref_for("HEAD") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "#{git_commit_sha}" G @@ -1322,7 +1438,7 @@ RSpec.describe "bundle install with git sources" do end install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -1352,7 +1468,7 @@ In Gemfile: end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -1365,7 +1481,7 @@ In Gemfile: expect(installed_time).to match(/\A\d+\.\d+\z/) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -1393,8 +1509,8 @@ In Gemfile: end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "0.9.1" + source "https://gem.repo1" + gem "myrack", "0.9.1" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -1407,8 +1523,8 @@ In Gemfile: expect(installed_time).to match(/\A\d+\.\d+\z/) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "1.0.0" + source "https://gem.repo1" + gem "myrack", "1.0.0" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -1436,7 +1552,7 @@ In Gemfile: end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -1452,7 +1568,7 @@ In Gemfile: expect(installed_time).to match(/\A\d+\.\d+\z/) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}", :branch => "branch2" G @@ -1485,7 +1601,7 @@ In Gemfile: ENV["GIT_WORK_TREE"] = "bar" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("xxxxxx-1.0")}" do gem 'xxxxxx' end @@ -1499,7 +1615,7 @@ In Gemfile: describe "without git installed" do it "prints a better error message when installing" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rake", git: "https://github.com/ruby/rake" G @@ -1522,7 +1638,7 @@ In Gemfile: rake! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L with_path_as("") do @@ -1536,7 +1652,7 @@ In Gemfile: build_git "foo" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("foo-1.0")}" do gem 'foo' end @@ -1549,18 +1665,18 @@ In Gemfile: to include("You need to install git to be able to use gems from git repositories. For help installing git, please refer to GitHub's tutorial at https://help.github.com/articles/set-up-git") end - it "installs a packaged git gem successfully" do + it "doesn't need git in the new machine if an installed git gem is copied to another machine" do build_git "foo" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("foo-1.0")}" do gem 'foo' end G - bundle "config set cache_all true" - bundle :cache - simulate_new_machine + bundle_config_global "path vendor/bundle" + bundle :install + 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.") @@ -1569,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 @@ -1583,7 +1699,7 @@ In Gemfile: build_git "foo", "1.0", path: lib_path("foo") gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo")}", :branch => "main" G @@ -1599,13 +1715,13 @@ In Gemfile: it "does not display the password" do install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "https://#{credentials}@github.com/company/private-repo" do gem "foo" 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 @@ -1615,13 +1731,13 @@ In Gemfile: it "displays the oauth scheme but not the oauth token" do install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "https://#{credentials}:x-oauth-basic@github.com/company/private-repo" do gem "foo" 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 f7907a9cad..4013b112ec 100644 --- a/spec/bundler/install/gemfile/groups_spec.rb +++ b/spec/bundler/install/gemfile/groups_spec.rb @@ -4,8 +4,8 @@ RSpec.describe "bundle install with groups" do describe "installing with no options" do before :each do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" group :emo do gem "activesupport", "2.3.5" end @@ -14,7 +14,7 @@ RSpec.describe "bundle install with groups" do end it "installs gems in the default group" do - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "installs gems in a group block into that group" do @@ -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,11 +36,11 @@ 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 - output = run("require 'rack'; puts RACK") + output = run("require 'myrack'; puts MYRACK") expect(output).to eq("1.0.0") output = run("require 'activesupport'; puts ACTIVESUPPORT") @@ -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 @@ -74,8 +74,8 @@ RSpec.describe "bundle install with groups" do describe "with gems assigned to a single group" do before :each do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" group :emo do gem "activesupport", "2.3.5" end @@ -86,63 +86,56 @@ 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 "rack 1.0.0", groups: [:default] + 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 "rack 1.0.0", groups: [:default] + expect(the_bundle).to include_gems "myrack 1.0.0", groups: [:default] bundle "config list" expect(out).not_to include("Set for your local app (#{bundled_app(".bundle/config")}): [:emo]") expect(out).to include("Set for the current user (#{home(".bundle/config")}): [:emo]") end - it "allows running application where groups where configured by a different user", bundler: "< 3" do - 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 'rack'; puts RACK", :default) + run("require 'myrack'; puts MYRACK", :default) expect(out).to eq("1.0.0") end it "does not effect the resolve" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "activesupport" group :emo do gem "rails", "2.3.2" 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 @@ -153,40 +146,19 @@ RSpec.describe "bundle install with groups" do bundle :install expect(out).not_to include("activesupport") - expect(the_bundle).to include_gems "rack 1.0.0", groups: [:default] + expect(the_bundle).to include_gems "myrack 1.0.0", groups: [:default] expect(the_bundle).not_to include_gems "activesupport 2.3.5", groups: [:default] ENV["BUNDLE_WITHOUT"] = nil end - it "clears --without when passed an empty list", bundler: "< 3" do - 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 @@ -257,8 +199,8 @@ RSpec.describe "bundle install with groups" do describe "with gems assigned to multiple groups" do before :each do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" group :emo, :lolercoaster do gem "activesupport", "2.3.5" end @@ -266,22 +208,22 @@ 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 "rack 1.0.0" + 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 "rack 1.0.0", "activesupport 2.3.5" + expect(the_bundle).to include_gems "myrack 1.0.0", "activesupport 2.3.5" end describe "with a gem defined multiple times in different groups" do before :each do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" group :emo do gem "activesupport", "2.3.5" @@ -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" @@ -316,8 +258,8 @@ RSpec.describe "bundle install with groups" do describe "nesting groups" do before :each do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" group :emo do group :lolercoaster do gem "activesupport", "2.3.5" @@ -327,15 +269,15 @@ 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 "rack 1.0.0" + 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 "rack 1.0.0", "activesupport 2.3.5" + expect(the_bundle).to include_gems "myrack 1.0.0", "activesupport 2.3.5" end end end @@ -343,8 +285,8 @@ RSpec.describe "bundle install with groups" do describe "when loading only the default group" do it "should not load all groups" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" gem "activesupport", :groups => :development G @@ -352,7 +294,7 @@ RSpec.describe "bundle install with groups" do require "bundler" Bundler.setup :default Bundler.require :default - puts RACK + puts MYRACK begin require "activesupport" rescue LoadError @@ -365,39 +307,39 @@ 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 "rack-0.9.1" + system_gems "myrack-0.9.1" - bundle "config set --local without rack" + bundle_config "without myrack" install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack" + source "https://gem.repo2" + gem "myrack" - group :rack do - gem "rack_middleware" + group :myrack do + gem "myrack_middleware" end G end - it "uses the correct versions even if --without was used on the original" do - expect(the_bundle).to include_gems "rack 0.9.1" - expect(the_bundle).not_to include_gems "rack_middleware 1.0" + 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 bundle :install - expect(the_bundle).to include_gems "rack 0.9.1" - expect(the_bundle).to include_gems "rack_middleware 1.0" + expect(the_bundle).to include_gems "myrack 0.9.1" + expect(the_bundle).to include_gems "myrack_middleware 1.0" end it "does not hit the remote a second time" do - FileUtils.rm_rf gem_repo2 - bundle "config set --local without rack" + 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 c7640d07e1..05a6d15129 100644 --- a/spec/bundler/install/gemfile/install_if_spec.rb +++ b/spec/bundler/install/gemfile/install_if_spec.rb @@ -3,7 +3,7 @@ RSpec.describe "bundle install with install_if conditionals" do it "follows the install_if DSL" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" install_if(lambda { true }) do gem "activesupport", "2.3.5" end @@ -11,29 +11,29 @@ RSpec.describe "bundle install with install_if conditionals" do install_if(lambda { false }) do gem "foo" end - gem "rack" + gem "myrack" G - expect(the_bundle).to include_gems("rack 1.0", "activesupport 2.3.5") + expect(the_bundle).to include_gems("myrack 1.0", "activesupport 2.3.5") expect(the_bundle).not_to include_gems("thin") expect(the_bundle).not_to include_gems("foo") - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum gem_repo1, "activesupport", "2.3.5" - c.no_checksum "foo", "1.0" - c.checksum gem_repo1, "rack", "1.0.0" - c.no_checksum "thin", "1.0" + c.checksum gem_repo1, "foo", "1.0" + c.checksum gem_repo1, "myrack", "1.0.0" + c.checksum gem_repo1, "thin", "1.0" end expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: activesupport (2.3.5) foo (1.0) - rack (1.0.0) + myrack (1.0.0) thin (1.0) - rack + myrack PLATFORMS #{lockfile_platforms} @@ -41,11 +41,11 @@ RSpec.describe "bundle install with install_if conditionals" do DEPENDENCIES activesupport (= 2.3.5) foo - rack + myrack 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 4601d3e2a8..19bd7074b2 100644 --- a/spec/bundler/install/gemfile/lockfile_spec.rb +++ b/spec/bundler/install/gemfile/lockfile_spec.rb @@ -2,17 +2,13 @@ RSpec.describe "bundle install with a lockfile present" do let(:gf) { <<-G } - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack", "1.0.0" + 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 "rack 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 - 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 "rack 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 a57b7ee560..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 "#{file_uri_for(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 "#{file_uri_for(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 "#{file_uri_for(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 "#{file_uri_for(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 "#{file_uri_for(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 "#{file_uri_for(gem_repo1)}" gem 'foo', :path => "./foo-1.0" G @@ -93,12 +76,12 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "aaa", path: lib_path("demo/aaa") gemfile lib_path("demo/Gemfile"), <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gemspec gem "aaa", :path => "./aaa" G - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.no_checksum "aaa", "1.0" c.no_checksum "demo", "1.0" end @@ -115,7 +98,7 @@ RSpec.describe "bundle install with explicit source paths" do aaa (1.0) GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS @@ -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,31 +122,29 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "foo", path: bundled_app("foo-1.0") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" gem 'foo', :path => File.expand_path("foo-1.0", __dir__) G - bundle "config set --local frozen true" + bundle_config "frozen true" bundle :install end it "installs dependencies from the path even if a newer gem is available elsewhere" do - system_gems "rack-1.0.0" + system_gems "myrack-1.0.0" - build_lib "rack", "1.0", path: lib_path("nested/bar") do |s| - s.write "lib/rack.rb", "puts 'WIN OVERRIDE'" + build_lib "myrack", "1.0", path: lib_path("nested/bar") do |s| + s.write "lib/myrack.rb", "puts 'WIN OVERRIDE'" end build_lib "foo", path: lib_path("nested") do |s| - s.add_dependency "rack", "= 1.0" + s.add_dependency "myrack", "= 1.0" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("nested")}" G - run "require 'rack'" + run "require 'myrack'" expect(out).to eq("WIN OVERRIDE") end @@ -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 "#{file_uri_for(gem_repo1)}" gem "omg", :path => "#{lib_path("omg")}" G @@ -190,7 +170,7 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "foo", "0.0.0.dev", path: lib_path("foo") gemfile <<~G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :path => "#{lib_path("foo")}" G @@ -201,7 +181,7 @@ RSpec.describe "bundle install with explicit source paths" do foo (0.0.0.dev) GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS @@ -223,7 +203,7 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "foo", "0.0.0.SNAPSHOT", path: lib_path("foo") gemfile <<~G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :path => "#{lib_path("foo")}" G @@ -234,7 +214,7 @@ RSpec.describe "bundle install with explicit source paths" do foo (0.0.0.SNAPSHOT) GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS @@ -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 "#{file_uri_for(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 "#{file_uri_for(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 "#{file_uri_for(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/) @@ -314,17 +290,17 @@ RSpec.describe "bundle install with explicit source paths" do it "supports gemspec syntax" do build_lib "foo", "1.0", path: lib_path("foo") do |s| - s.add_dependency "rack", "1.0" + s.add_dependency "myrack", "1.0" end gemfile lib_path("foo/Gemfile"), <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gemspec G bundle "install", dir: lib_path("foo") expect(the_bundle).to include_gems "foo 1.0", dir: lib_path("foo") - expect(the_bundle).to include_gems "rack 1.0", dir: lib_path("foo") + expect(the_bundle).to include_gems "myrack 1.0", dir: lib_path("foo") end it "does not unlock dependencies of path sources" do @@ -340,13 +316,13 @@ RSpec.describe "bundle install with explicit source paths" do gemfile_path = lib_path("foo/Gemfile") gemfile gemfile_path, <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gemspec G lockfile_path = lib_path("foo/Gemfile.lock") - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.no_checksum "foo", "0.1.0" c.checksum gem_repo4, "graphql", "2.0.15" end @@ -359,7 +335,7 @@ RSpec.describe "bundle install with explicit source paths" do graphql (~> 2.0) GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: graphql (2.0.15) @@ -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 @@ -385,53 +361,53 @@ RSpec.describe "bundle install with explicit source paths" do it "supports gemspec syntax with an alternative path" do build_lib "foo", "1.0", path: lib_path("foo") do |s| - s.add_dependency "rack", "1.0" + s.add_dependency "myrack", "1.0" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gemspec :path => "#{lib_path("foo")}" G expect(the_bundle).to include_gems "foo 1.0" - expect(the_bundle).to include_gems "rack 1.0" + expect(the_bundle).to include_gems "myrack 1.0" end it "doesn't automatically unlock dependencies when using the gemspec syntax" do build_lib "foo", "1.0", path: lib_path("foo") do |s| - s.add_dependency "rack", ">= 1.0" + s.add_dependency "myrack", ">= 1.0" end install_gemfile lib_path("foo/Gemfile"), <<-G, dir: lib_path("foo") - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gemspec G - build_gem "rack", "1.0.1", to_system: true + build_gem "myrack", "1.0.1", to_system: true bundle "install", dir: lib_path("foo") expect(the_bundle).to include_gems "foo 1.0", dir: lib_path("foo") - expect(the_bundle).to include_gems "rack 1.0", dir: lib_path("foo") + expect(the_bundle).to include_gems "myrack 1.0", dir: lib_path("foo") end it "doesn't automatically unlock dependencies when using the gemspec syntax and the gem has development dependencies" do build_lib "foo", "1.0", path: lib_path("foo") do |s| - s.add_dependency "rack", ">= 1.0" + s.add_dependency "myrack", ">= 1.0" s.add_development_dependency "activesupport" end install_gemfile lib_path("foo/Gemfile"), <<-G, dir: lib_path("foo") - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gemspec G - build_gem "rack", "1.0.1", to_system: true + build_gem "myrack", "1.0.1", to_system: true bundle "install", dir: lib_path("foo") expect(the_bundle).to include_gems "foo 1.0", dir: lib_path("foo") - expect(the_bundle).to include_gems "rack 1.0", dir: lib_path("foo") + expect(the_bundle).to include_gems "myrack 1.0", dir: lib_path("foo") end it "raises if there are multiple gemspecs" do @@ -440,7 +416,6 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(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 "#{file_uri_for(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 "#{file_uri_for(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 "#{file_uri_for(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 "#{file_uri_for(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 "#{file_uri_for(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 "#{file_uri_for(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 "#{file_uri_for(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 @@ -569,18 +533,18 @@ RSpec.describe "bundle install with explicit source paths" do context "existing lockfile" do it "rubygems gems don't re-resolve without changes" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack-obama', '1.0' + source "https://gem.repo1" + gem 'myrack-obama', '1.0' gem 'net-ssh', '1.0' G bundle :check, env: { "DEBUG" => "1" } expect(out).to match(/using resolution from the lockfile/) - expect(the_bundle).to include_gems "rack-obama 1.0", "net-ssh 1.0" + expect(the_bundle).to include_gems "myrack-obama 1.0", "net-ssh 1.0" end it "source path gems w/deps don't re-resolve without changes" do - build_lib "rack-obama", "1.0", path: lib_path("omg") do |s| + build_lib "myrack-obama", "1.0", path: lib_path("omg") do |s| s.add_dependency "yard" end @@ -589,14 +553,14 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack-obama', :path => "#{lib_path("omg")}" + source "https://gem.repo1" + gem 'myrack-obama', :path => "#{lib_path("omg")}" gem 'net-ssh', :path => "#{lib_path("omg")}" G bundle :check, env: { "DEBUG" => "1" } expect(out).to match(/using resolution from the lockfile/) - expect(the_bundle).to include_gems "rack-obama 1.0", "net-ssh 1.0" + expect(the_bundle).to include_gems "myrack-obama 1.0", "net-ssh 1.0" end end @@ -606,7 +570,6 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G - source "#{file_uri_for(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 "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("foo")}" G end @@ -651,33 +613,33 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "foo", "1.0", path: lib_path("foo") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :path => "#{lib_path("foo")}" G end it "gets dependencies that are updated in the path" do build_lib "foo", "1.0", path: lib_path("foo") do |s| - s.add_dependency "rack" + s.add_dependency "myrack" end bundle "install" - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "keeps using the same version if it's compatible" do build_lib "foo", "1.0", path: lib_path("foo") do |s| - s.add_dependency "rack", "0.9.1" + s.add_dependency "myrack", "0.9.1" end bundle "install" - expect(the_bundle).to include_gems "rack 0.9.1" + expect(the_bundle).to include_gems "myrack 0.9.1" - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.no_checksum "foo", "1.0" - c.checksum gem_repo1, "rack", "0.9.1" + c.checksum gem_repo1, "myrack", "0.9.1" end expect(lockfile).to eq <<~G @@ -685,12 +647,12 @@ RSpec.describe "bundle install with explicit source paths" do remote: #{lib_path("foo")} specs: foo (1.0) - rack (= 0.9.1) + myrack (= 0.9.1) GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: - rack (0.9.1) + myrack (0.9.1) PLATFORMS #{lockfile_platforms} @@ -699,11 +661,11 @@ 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| - s.add_dependency "rack" + s.add_dependency "myrack" end bundle "install" @@ -713,12 +675,12 @@ RSpec.describe "bundle install with explicit source paths" do remote: #{lib_path("foo")} specs: foo (1.0) - rack + myrack GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: - rack (0.9.1) + myrack (0.9.1) PLATFORMS #{lockfile_platforms} @@ -727,24 +689,24 @@ RSpec.describe "bundle install with explicit source paths" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G - expect(the_bundle).to include_gems "rack 0.9.1" + expect(the_bundle).to include_gems "myrack 0.9.1" end it "keeps using the same version even when another dependency is added" do build_lib "foo", "1.0", path: lib_path("foo") do |s| - s.add_dependency "rack", "0.9.1" + s.add_dependency "myrack", "0.9.1" end bundle "install" - expect(the_bundle).to include_gems "rack 0.9.1" + expect(the_bundle).to include_gems "myrack 0.9.1" - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.no_checksum "foo", "1.0" - c.checksum gem_repo1, "rack", "0.9.1" + c.checksum gem_repo1, "myrack", "0.9.1" end expect(lockfile).to eq <<~G @@ -752,12 +714,12 @@ RSpec.describe "bundle install with explicit source paths" do remote: #{lib_path("foo")} specs: foo (1.0) - rack (= 0.9.1) + myrack (= 0.9.1) GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: - rack (0.9.1) + myrack (0.9.1) PLATFORMS #{lockfile_platforms} @@ -766,11 +728,11 @@ 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| - s.add_dependency "rack" + s.add_dependency "myrack" s.add_dependency "rake", rake_version end @@ -783,13 +745,13 @@ RSpec.describe "bundle install with explicit source paths" do remote: #{lib_path("foo")} specs: foo (1.0) - rack + myrack rake (= #{rake_version}) GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: - rack (0.9.1) + myrack (0.9.1) rake (#{rake_version}) PLATFORMS @@ -799,18 +761,18 @@ RSpec.describe "bundle install with explicit source paths" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G - expect(the_bundle).to include_gems "rack 0.9.1" + expect(the_bundle).to include_gems "myrack 0.9.1" end it "does not remove existing ruby platform" do build_lib "foo", "1.0", path: lib_path("foo") do |s| - s.add_dependency "rack", "0.9.1" + s.add_dependency "myrack", "0.9.1" end - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.no_checksum "foo", "1.0" end @@ -827,24 +789,24 @@ RSpec.describe "bundle install with explicit source paths" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "lock" - checksums.no_checksum "rack", "0.9.1" + checksums.checksum gem_repo1, "myrack", "0.9.1" expect(lockfile).to eq <<~G PATH remote: #{lib_path("foo")} specs: foo (1.0) - rack (= 0.9.1) + myrack (= 0.9.1) GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: - rack (0.9.1) + myrack (0.9.1) PLATFORMS #{lockfile_platforms("ruby")} @@ -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 "#{file_uri_for(gem_repo1)}" gem "bar", :git => "#{lib_path("bar")}" G install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" gem "bar", :path => "#{lib_path("bar")}" G @@ -888,7 +897,7 @@ RSpec.describe "bundle install with explicit source paths" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "bar" path "#{lib_path("foo")}" do gem "foo" @@ -898,7 +907,7 @@ RSpec.describe "bundle install with explicit source paths" do build_lib "bar", "1.0", path: lib_path("foo/bar") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" path "#{lib_path("foo")}" do gem "foo" gem "bar" @@ -915,13 +924,13 @@ RSpec.describe "bundle install with explicit source paths" do gemfile lib_path("private_lib/Gemfile"), <<-G source "http://localgemserver.test" gemspec - gem 'rack' + gem 'myrack' G bundle :install, env: { "DEBUG" => "1" }, artifice: "endpoint", dir: lib_path("private_lib") - expect(out).to match(%r{^HTTP GET http://localgemserver\.test/api/v1/dependencies\?gems=rack$}) + expect(out).to match(%r{^HTTP GET http://localgemserver\.test/api/v1/dependencies\?gems=myrack$}) expect(out).not_to match(/^HTTP GET.*private_lib/) expect(the_bundle).to include_gems "private_lib 2.2", dir: lib_path("private_lib") - expect(the_bundle).to include_gems "rack 1.0", dir: lib_path("private_lib") + expect(the_bundle).to include_gems "myrack 1.0", dir: lib_path("private_lib") end end @@ -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 "#{file_uri_for(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 "#{file_uri_for(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 "#{file_uri_for(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 "#{file_uri_for(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 d90dacdc02..e12933ebcf 100644 --- a/spec/bundler/install/gemfile/platform_spec.rb +++ b/spec/bundler/install/gemfile/platform_spec.rb @@ -4,30 +4,30 @@ RSpec.describe "bundle install across platforms" do it "maintains the same lockfile if all gems are compatible across platforms" do lockfile <<-G GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: - rack (0.9.1) + myrack (0.9.1) PLATFORMS #{not_local} DEPENDENCIES - rack + myrack G install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack" + gem "myrack" G - expect(the_bundle).to include_gems "rack 0.9.1" + expect(the_bundle).to include_gems "myrack 0.9.1" end it "pulls in the correct platform specific gem" do lockfile <<-G GEM - remote: #{file_uri_for(gem_repo1)} + remote: https://gem.repo1 specs: platform_specific (1.0) platform_specific (1.0-java) @@ -40,20 +40,21 @@ RSpec.describe "bundle install across platforms" do platform_specific G - simulate_platform "java" - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + simulate_platform "java" do + install_gemfile <<-G + source "https://gem.repo1" - gem "platform_specific" - G + gem "platform_specific" + G - expect(the_bundle).to include_gems "platform_specific 1.0 JAVA" + expect(the_bundle).to include_gems "platform_specific 1.0 java" + end end it "pulls the pure ruby version on jruby if the java platform is not present in the lockfile and bundler is run in frozen mode", :jruby_only do lockfile <<-G GEM - remote: #{file_uri_for(gem_repo1)} + remote: https://gem.repo1 specs: platform_specific (1.0) @@ -64,15 +65,16 @@ RSpec.describe "bundle install across platforms" do platform_specific G - bundle "config set --local frozen true" + bundle_config "frozen true" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "platform_specific" G - expect(the_bundle).to include_gems "platform_specific 1.0 RUBY" + expect(the_bundle).to include_gems "platform_specific 1.0 ruby" + expect(err).to be_empty end context "on universal Rubies" do @@ -80,15 +82,12 @@ RSpec.describe "bundle install across platforms" do build_repo4 do build_gem "darwin_single_arch" do |s| s.platform = "ruby" - s.write "lib/darwin_single_arch.rb", "DARWIN_SINGLE_ARCH = '1.0 RUBY'" end build_gem "darwin_single_arch" do |s| s.platform = "arm64-darwin" - s.write "lib/darwin_single_arch.rb", "DARWIN_SINGLE_ARCH = '1.0 arm64-darwin'" end build_gem "darwin_single_arch" do |s| s.platform = "x86_64-darwin" - s.write "lib/darwin_single_arch.rb", "DARWIN_SINGLE_ARCH = '1.0 x86_64-darwin'" end end end @@ -96,7 +95,7 @@ RSpec.describe "bundle install across platforms" do it "pulls in the correct architecture gem" do lockfile <<-G GEM - remote: #{file_uri_for(gem_repo4)} + remote: https://gem.repo4 specs: darwin_single_arch (1.0) darwin_single_arch (1.0-arm64-darwin) @@ -109,22 +108,23 @@ RSpec.describe "bundle install across platforms" do darwin_single_arch G - simulate_platform "universal-darwin-21" - simulate_ruby_platform "universal.x86_64-darwin21" do - install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + simulate_platform "universal-darwin-21" do + simulate_ruby_platform "universal.x86_64-darwin21" do + install_gemfile <<-G + source "https://gem.repo4" - gem "darwin_single_arch" - G + gem "darwin_single_arch" + G - expect(the_bundle).to include_gems "darwin_single_arch 1.0 x86_64-darwin" + expect(the_bundle).to include_gems "darwin_single_arch 1.0 x86_64-darwin" + end end end it "pulls in the correct architecture gem on arm64e macOS Ruby" do lockfile <<-G GEM - remote: #{file_uri_for(gem_repo4)} + remote: https://gem.repo4 specs: darwin_single_arch (1.0) darwin_single_arch (1.0-arm64-darwin) @@ -137,41 +137,42 @@ RSpec.describe "bundle install across platforms" do darwin_single_arch G - simulate_platform "universal-darwin-21" - simulate_ruby_platform "universal.arm64e-darwin21" do - install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + simulate_platform "universal-darwin-21" do + simulate_ruby_platform "universal.arm64e-darwin21" do + install_gemfile <<-G + source "https://gem.repo4" - gem "darwin_single_arch" - G + gem "darwin_single_arch" + G - expect(the_bundle).to include_gems "darwin_single_arch 1.0 arm64-darwin" + expect(the_bundle).to include_gems "darwin_single_arch 1.0 arm64-darwin" + end end end end it "works with gems that have different dependencies" do - simulate_platform "java" - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + simulate_platform "java" do + install_gemfile <<-G + source "https://gem.repo1" - gem "nokogiri" - G + gem "nokogiri" + G - expect(the_bundle).to include_gems "nokogiri 1.4.2 JAVA", "weakling 0.0.3" + 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" - bundle "install" + pristine_system_gems + bundle_config "force_ruby_platform true" + bundle "install" - expect(the_bundle).to include_gems "nokogiri 1.4.2" - expect(the_bundle).not_to include_gems "weakling" + expect(the_bundle).to include_gems "nokogiri 1.4.2" + expect(the_bundle).not_to include_gems "weakling" - simulate_new_machine - simulate_platform "java" - bundle "install" + simulate_new_machine + bundle "install" - expect(the_bundle).to include_gems "nokogiri 1.4.2 JAVA", "weakling 0.0.3" + expect(the_bundle).to include_gems "nokogiri 1.4.2 java", "weakling 0.0.3" + end end it "does not keep unneeded platforms for gems that are used" do @@ -179,207 +180,209 @@ RSpec.describe "bundle install across platforms" do build_gem "empyrean", "0.1.0" build_gem "coderay", "1.1.2" build_gem "method_source", "0.9.0" - build_gem("spoon", "0.0.6") {|s| s.add_runtime_dependency "ffi" } + build_gem("spoon", "0.0.6") {|s| s.add_dependency "ffi" } build_gem "pry", "0.11.3" do |s| s.platform = "java" - s.add_runtime_dependency "coderay", "~> 1.1.0" - s.add_runtime_dependency "method_source", "~> 0.9.0" - s.add_runtime_dependency "spoon", "~> 0.0" + s.add_dependency "coderay", "~> 1.1.0" + s.add_dependency "method_source", "~> 0.9.0" + s.add_dependency "spoon", "~> 0.0" end build_gem "pry", "0.11.3" do |s| - s.add_runtime_dependency "coderay", "~> 1.1.0" - s.add_runtime_dependency "method_source", "~> 0.9.0" + s.add_dependency "coderay", "~> 1.1.0" + s.add_dependency "method_source", "~> 0.9.0" end build_gem("ffi", "1.9.23") {|s| s.platform = "java" } build_gem("ffi", "1.9.23") end - simulate_platform java - - install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" - - gem "empyrean", "0.1.0" - gem "pry" - G - - checksums = checksums_section_when_existing do |c| - c.checksum gem_repo4, "coderay", "1.1.2" - c.checksum gem_repo4, "empyrean", "0.1.0" - c.checksum gem_repo4, "ffi", "1.9.23", "java" - c.checksum gem_repo4, "method_source", "0.9.0" - c.checksum gem_repo4, "pry", "0.11.3", "java" - c.checksum gem_repo4, "spoon", "0.0.6" - end - - expect(lockfile).to eq <<~L - GEM - remote: #{file_uri_for(gem_repo4)}/ - specs: - coderay (1.1.2) - empyrean (0.1.0) - ffi (1.9.23-java) - method_source (0.9.0) - pry (0.11.3-java) - coderay (~> 1.1.0) - method_source (~> 0.9.0) - spoon (~> 0.0) - spoon (0.0.6) - ffi - - PLATFORMS - java + simulate_platform "java" do + install_gemfile <<-G + source "https://gem.repo4" - DEPENDENCIES - empyrean (= 0.1.0) - pry - #{checksums} - BUNDLED WITH - #{Bundler::VERSION} - L + gem "empyrean", "0.1.0" + gem "pry" + G - bundle "lock --add-platform ruby" + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "coderay", "1.1.2" + c.checksum gem_repo4, "empyrean", "0.1.0" + c.checksum gem_repo4, "ffi", "1.9.23", "java" + c.checksum gem_repo4, "method_source", "0.9.0" + c.checksum gem_repo4, "pry", "0.11.3", "java" + c.checksum gem_repo4, "spoon", "0.0.6" + end - good_lockfile = <<~L - GEM - remote: #{file_uri_for(gem_repo4)}/ - specs: - coderay (1.1.2) - empyrean (0.1.0) - ffi (1.9.23-java) - method_source (0.9.0) - pry (0.11.3) - coderay (~> 1.1.0) - method_source (~> 0.9.0) - pry (0.11.3-java) - coderay (~> 1.1.0) - method_source (~> 0.9.0) - spoon (~> 0.0) - spoon (0.0.6) - ffi + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + coderay (1.1.2) + empyrean (0.1.0) + ffi (1.9.23-java) + method_source (0.9.0) + pry (0.11.3-java) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + spoon (~> 0.0) + spoon (0.0.6) + ffi - PLATFORMS - java - ruby + PLATFORMS + java - DEPENDENCIES - empyrean (= 0.1.0) - pry - #{checksums} - BUNDLED WITH - #{Bundler::VERSION} - L + DEPENDENCIES + empyrean (= 0.1.0) + pry + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L - expect(lockfile).to eq good_lockfile + bundle "lock --add-platform ruby" - bad_lockfile = <<~L - GEM - remote: #{file_uri_for(gem_repo4)}/ - specs: - coderay (1.1.2) - empyrean (0.1.0) - ffi (1.9.23) - ffi (1.9.23-java) - method_source (0.9.0) - pry (0.11.3) - coderay (~> 1.1.0) - method_source (~> 0.9.0) - pry (0.11.3-java) - coderay (~> 1.1.0) - method_source (~> 0.9.0) - spoon (~> 0.0) - spoon (0.0.6) - ffi + checksums.checksum gem_repo4, "pry", "0.11.3" - PLATFORMS - java - ruby + good_lockfile = <<~L + GEM + remote: https://gem.repo4/ + specs: + coderay (1.1.2) + empyrean (0.1.0) + ffi (1.9.23-java) + method_source (0.9.0) + pry (0.11.3) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + pry (0.11.3-java) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + spoon (~> 0.0) + spoon (0.0.6) + ffi - DEPENDENCIES - empyrean (= 0.1.0) - pry - #{checksums} - BUNDLED WITH - 1.16.1 - L + PLATFORMS + java + ruby - aggregate_failures do - lockfile bad_lockfile - bundle :install, env: { "BUNDLER_VERSION" => Bundler::VERSION } - expect(lockfile).to eq good_lockfile + DEPENDENCIES + empyrean (= 0.1.0) + pry + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L - lockfile bad_lockfile - bundle :update, all: true, env: { "BUNDLER_VERSION" => Bundler::VERSION } expect(lockfile).to eq good_lockfile - lockfile bad_lockfile - bundle "update ffi", env: { "BUNDLER_VERSION" => Bundler::VERSION } - expect(lockfile).to eq good_lockfile + bad_lockfile = <<~L + GEM + remote: https://gem.repo4/ + specs: + coderay (1.1.2) + empyrean (0.1.0) + ffi (1.9.23) + ffi (1.9.23-java) + method_source (0.9.0) + pry (0.11.3) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + pry (0.11.3-java) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + spoon (~> 0.0) + spoon (0.0.6) + ffi - lockfile bad_lockfile - bundle "update empyrean", env: { "BUNDLER_VERSION" => Bundler::VERSION } - expect(lockfile).to eq good_lockfile + PLATFORMS + java + ruby - lockfile bad_lockfile - bundle :lock, env: { "BUNDLER_VERSION" => Bundler::VERSION } - expect(lockfile).to eq good_lockfile + DEPENDENCIES + empyrean (= 0.1.0) + pry + #{checksums} + BUNDLED WITH + 1.16.1 + L + + aggregate_failures do + lockfile bad_lockfile + bundle :install, env: { "BUNDLER_VERSION" => Bundler::VERSION } + expect(lockfile).to eq good_lockfile + + lockfile bad_lockfile + bundle :update, all: true, env: { "BUNDLER_VERSION" => Bundler::VERSION } + expect(lockfile).to eq good_lockfile + + lockfile bad_lockfile + bundle "update ffi", env: { "BUNDLER_VERSION" => Bundler::VERSION } + expect(lockfile).to eq good_lockfile + + lockfile bad_lockfile + bundle "update empyrean", env: { "BUNDLER_VERSION" => Bundler::VERSION } + expect(lockfile).to eq good_lockfile + + lockfile bad_lockfile + bundle :lock, env: { "BUNDLER_VERSION" => Bundler::VERSION } + expect(lockfile).to eq good_lockfile + end end end it "works with gems with platform-specific dependency having different requirements order" do - simulate_platform x64_mac - - update_repo2 do - build_gem "fspath", "3" - build_gem "image_optim_pack", "1.2.3" do |s| - s.add_runtime_dependency "fspath", ">= 2.1", "< 4" - end - build_gem "image_optim_pack", "1.2.3" do |s| - s.platform = "universal-darwin" - s.add_runtime_dependency "fspath", "< 4", ">= 2.1" + simulate_platform "x86_64-darwin-15" do + update_repo2 do + build_gem "fspath", "3" + build_gem "image_optim_pack", "1.2.3" do |s| + s.add_dependency "fspath", ">= 2.1", "< 4" + end + build_gem "image_optim_pack", "1.2.3" do |s| + s.platform = "universal-darwin" + s.add_dependency "fspath", "< 4", ">= 2.1" + end end - end - install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - G + install_gemfile <<-G + source "https://gem.repo2" + G - install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + install_gemfile <<-G + source "https://gem.repo2" - gem "image_optim_pack" - G + gem "image_optim_pack" + G - expect(err).not_to include "Unable to use the platform-specific" + expect(err).not_to include "Unable to use the platform-specific" - expect(the_bundle).to include_gem "image_optim_pack 1.2.3 universal-darwin" + expect(the_bundle).to include_gem "image_optim_pack 1.2.3 universal-darwin" + end end it "fetches gems again after changing the version of Ruby" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack", "1.0.0" + 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")) bundle :install - expect(vendored_gems("gems/rack-1.0.0")).to exist + expect(vendored_gems("gems/myrack-1.0.0")).to exist end it "keeps existing platforms when installing with force_ruby_platform" do - checksums = checksums_section do |c| - c.no_checksum "platform_specific", "1.0" - c.no_checksum "platform_specific", "1.0", "java" + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo1, "platform_specific", "1.0" + c.checksum gem_repo1, "platform_specific", "1.0", "java" end lockfile <<-G GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: platform_specific (1.0-java) @@ -391,20 +394,20 @@ 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "platform_specific" G checksums.checksum gem_repo1, "platform_specific", "1.0" - expect(the_bundle).to include_gem "platform_specific 1.0 RUBY" + expect(the_bundle).to include_gem "platform_specific 1.0 ruby" expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: platform_specific (1.0) platform_specific (1.0-java) @@ -417,7 +420,7 @@ RSpec.describe "bundle install across platforms" do platform_specific #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end end @@ -425,7 +428,7 @@ end RSpec.describe "bundle install with platform conditionals" do it "installs gems tagged w/ the current platforms" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" platforms :#{local_tag} do gem "nokogiri" @@ -437,14 +440,14 @@ RSpec.describe "bundle install with platform conditionals" do it "does not install gems tagged w/ another platforms" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" platforms :#{not_local_tag} do gem "nokogiri" end G - expect(the_bundle).to include_gems "rack 1.0" + expect(the_bundle).to include_gems "myrack 1.0" expect(the_bundle).not_to include_gems "nokogiri 1.4.2" end @@ -458,7 +461,7 @@ RSpec.describe "bundle install with platform conditionals" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "activesupport" @@ -469,7 +472,7 @@ RSpec.describe "bundle install with platform conditionals" do lockfile <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: activesupport (6.1.4.1) tzinfo (~> 2.0) @@ -483,7 +486,7 @@ RSpec.describe "bundle install with platform conditionals" do tzinfo (~> 1.2) BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install --verbose" @@ -493,7 +496,7 @@ RSpec.describe "bundle install with platform conditionals" do it "installs gems tagged w/ the current platforms inline" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "nokogiri", :platforms => :#{local_tag} G expect(the_bundle).to include_gems "nokogiri 1.4.2" @@ -501,17 +504,17 @@ RSpec.describe "bundle install with platform conditionals" do it "does not install gems tagged w/ another platforms inline" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" gem "nokogiri", :platforms => :#{not_local_tag} G - expect(the_bundle).to include_gems "rack 1.0" + expect(the_bundle).to include_gems "myrack 1.0" expect(the_bundle).not_to include_gems "nokogiri 1.4.2" end it "installs gems tagged w/ the current platform inline" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "nokogiri", :platform => :#{local_tag} G expect(the_bundle).to include_gems "nokogiri 1.4.2" @@ -519,7 +522,7 @@ RSpec.describe "bundle install with platform conditionals" do it "doesn't install gems tagged w/ another platform inline" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "nokogiri", :platform => :#{not_local_tag} G expect(the_bundle).not_to include_gems "nokogiri 1.4.2" @@ -529,7 +532,7 @@ RSpec.describe "bundle install with platform conditionals" do build_git "foo" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" platform :#{not_local_tag} do gem "foo", :git => "#{lib_path("foo-1.0")}" end @@ -539,10 +542,10 @@ 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "some_gem", :platform => :rbx G @@ -551,9 +554,9 @@ 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "some_gem", platform: :ruby_22 G @@ -562,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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack", :platform => [:windows, :mswin, :mswin64, :mingw, :x64_mingw, :jruby] + gem "myrack", :platform => [:windows, :jruby] G bundle "install" @@ -576,22 +579,22 @@ RSpec.describe "bundle install with platform conditionals" do expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS ruby DEPENDENCIES - rack - #{checksums_section_when_existing} + 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| @@ -602,7 +605,7 @@ RSpec.describe "bundle install with platform conditionals" do end install_gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "listen" gem "ffi", :platform => :windows @@ -613,23 +616,23 @@ end RSpec.describe "when a gem has no architecture" do it "still installs correctly" do - simulate_platform x86_mswin32 - - build_repo2 do - # The rcov gem is platform mswin32, but has no arch - build_gem "rcov" do |s| - s.platform = Gem::Platform.new([nil, "mswin32", nil]) - s.write "lib/rcov.rb", "RCOV = '1.0.0'" + simulate_platform "x86-mswin32" do + build_repo2 do + # The rcov gem is platform mswin32, but has no arch + build_gem "rcov" do |s| + s.platform = Gem::Platform.new([nil, "mswin32", nil]) + s.write "lib/rcov.rb", "RCOV = '1.0.0'" + end end - end - gemfile <<-G - # Try to install gem with nil arch - source "http://localgemserver.test/" - gem "rcov" - G + gemfile <<-G + # Try to install gem with nil arch + source "http://localgemserver.test/" + gem "rcov" + G - bundle :install, artifice: "windows", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } - expect(the_bundle).to include_gems "rcov 1.0.0" + bundle :install, artifice: "windows", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + expect(the_bundle).to include_gems "rcov 1.0.0" + end end end diff --git a/spec/bundler/install/gemfile/ruby_spec.rb b/spec/bundler/install/gemfile/ruby_spec.rb index b64d633fd3..d937abd714 100644 --- a/spec/bundler/install/gemfile/ruby_spec.rb +++ b/spec/bundler/install/gemfile/ruby_spec.rb @@ -10,112 +10,112 @@ RSpec.describe "ruby requirement" do # requirement. This test verifies the fix, committed in bfbad5c5. it "allows adding gems" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" ruby "#{Gem.ruby_version}" - gem "rack" + gem "myrack" G install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" ruby "#{Gem.ruby_version}" - gem "rack" - gem "rack-obama" + gem "myrack" + gem "myrack-obama" G - expect(the_bundle).to include_gems "rack-obama 1.0" + expect(the_bundle).to include_gems "myrack-obama 1.0" end it "allows removing the ruby version requirement" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" ruby "~> #{Gem.ruby_version}" - gem "rack" + gem "myrack" G expect(lockfile).to include("RUBY VERSION") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" expect(lockfile).not_to include("RUBY VERSION") end it "allows changing the ruby version requirement to something compatible" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" ruby ">= #{current_ruby_minor}" - gem "rack" + gem "myrack" G allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) expect(locked_ruby_version).to eq(Bundler::RubyVersion.system) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" ruby ">= #{Gem.ruby_version}" - gem "rack" + gem "myrack" G - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" expect(locked_ruby_version).to eq(Bundler::RubyVersion.system) end it "allows changing the ruby version requirement to something incompatible" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" ruby ">= 1.0.0" - gem "rack" + gem "myrack" G lockfile <<~L GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack + 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) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" ruby ">= #{current_ruby_minor}" - gem "rack" + gem "myrack" G - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" expect(locked_ruby_version).to eq(Bundler::RubyVersion.system) end it "allows requirements with trailing whitespace" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" ruby "#{Gem.ruby_version}\\n \t\\n" - gem "rack" + gem "myrack" G - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "fails gracefully with malformed requirements" do install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" ruby ">= 0", "-.\\0" - gem "rack" + gem "myrack" G expect(err).to include("There was an error parsing") # i.e. DSL error, not error template @@ -125,9 +125,9 @@ RSpec.describe "ruby requirement" do create_file ".ruby-version", Gem.ruby_version.to_s install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" ruby file: ".ruby-version" - gem "rack" + gem "myrack" G expect(lockfile).to include("RUBY VERSION") @@ -137,16 +137,16 @@ RSpec.describe "ruby requirement" do create_file ".ruby-version", Gem.ruby_version.to_s gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" ruby file: ".ruby-version" - gem "rack" + gem "myrack" G nested_dir = bundled_app(".ruby-lsp") FileUtils.mkdir nested_dir - create_file ".ruby-lsp/Gemfile", <<-G + gemfile ".ruby-lsp/Gemfile", <<-G eval_gemfile(File.expand_path("../Gemfile", __dir__)) G diff --git a/spec/bundler/install/gemfile/sources_spec.rb b/spec/bundler/install/gemfile/sources_spec.rb index a5ba76f4d9..654d638e1f 100644 --- a/spec/bundler/install/gemfile/sources_spec.rb +++ b/spec/bundler/install/gemfile/sources_spec.rb @@ -2,167 +2,20 @@ RSpec.describe "bundle install with gems on multiple sources" do # repo1 is built automatically before all of the specs run - # it contains rack-obama 1.0.0 and rack 0.9.1 & 1.0.0 amongst other gems - - context "without source affinity" do - before do - # Oh no! Someone evil is trying to hijack rack :( - # need this to be broken to check for correct source ordering - build_repo gem_repo3 do - build_gem "rack", repo3_rack_version do |s| - s.write "lib/rack.rb", "RACK = 'FAIL'" - end - end - end - - context "with multiple toplevel sources" do - let(:repo3_rack_version) { "1.0.0" } - - before do - gemfile <<-G - source "https://gem.repo3" - source "https://gem.repo1" - gem "rack-obama" - gem "rack" - G - end - - it "refuses to install mismatched checksum because one gem has been tampered with", bundler: "< 3" do - lockfile <<~L - GEM - remote: https://gem.repo3/ - remote: https://gem.repo1/ - specs: - rack (1.0.0) - - PLATFORMS - #{local_platform} - - DEPENDENCIES - depends_on_rack! - - BUNDLED WITH - #{Bundler::VERSION} - L - - bundle :install, artifice: "compact_index", raise_on_error: false - - expect(exitstatus).to eq(37) - expect(err).to eq <<~E.strip - [DEPRECATED] Your Gemfile contains multiple global sources. Using `source` more than once without a block is a security risk, and may result in installing unexpected gems. To resolve this warning, use a block to indicate which gems should come from the secondary source. - Bundler found mismatched checksums. This is a potential security risk. - #{checksum_to_lock(gem_repo1, "rack", "1.0.0")} - from the API at https://gem.repo1/ - #{checksum_to_lock(gem_repo3, "rack", "1.0.0")} - from the API at https://gem.repo3/ - - Mismatched checksums each have an authoritative source: - 1. the API at https://gem.repo1/ - 2. the API at https://gem.repo3/ - You may need to alter your Gemfile sources to resolve this issue. - - To ignore checksum security warnings, disable checksum validation with - `bundle config set --local disable_checksum_validation true` - E - end - - context "when checksum validation is disabled" do - before do - bundle "config set --local disable_checksum_validation true" - end - - it "warns about ambiguous gems, but installs anyway, prioritizing sources last to first", bundler: "< 3" do - bundle :install, artifice: "compact_index" - - expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") - expect(err).to include("Installed from: https://gem.repo1") - expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", source: "remote1") - end - - it "does not use the full index unnecessarily", bundler: "< 3" do - bundle :install, artifice: "compact_index", verbose: true - - expect(out).to include("https://gem.repo1/versions") - expect(out).to include("https://gem.repo3/versions") - expect(out).not_to include("https://gem.repo1/quick/Marshal.4.8/") - expect(out).not_to include("https://gem.repo3/quick/Marshal.4.8/") - end - - it "fails", bundler: "3" do - bundle :install, artifice: "compact_index", raise_on_error: false - expect(err).to include("Each source after the first must include a block") - expect(exitstatus).to eq(4) - end - end - end - - context "when different versions of the same gem are in multiple sources" do - let(:repo3_rack_version) { "1.2" } - - before do - gemfile <<-G - source "https://gem.repo3" - source "https://gem.repo1" - gem "rack-obama" - gem "rack", "1.0.0" # force it to install the working version in repo1 - G - end - - it "warns about ambiguous gems, but installs anyway", bundler: "< 3" do - bundle :install, artifice: "compact_index" - expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") - expect(err).to include("Installed from: https://gem.repo1") - expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", source: "remote1") - end - - it "fails", bundler: "3" do - bundle :install, artifice: "compact_index", raise_on_error: false - expect(err).to include("Each source after the first must include a block") - expect(exitstatus).to eq(4) - end - end - end - - context "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 + # it contains myrack-obama 1.0.0 and myrack 0.9.1 & 1.0.0 amongst other gems context "with source affinity" do context "with sources given by a block" do before do - # Oh no! Someone evil is trying to hijack rack :( + # Oh no! Someone evil is trying to hijack myrack :( # need this to be broken to check for correct source ordering - build_repo gem_repo3 do - build_gem "rack", "1.0.0" do |s| - s.write "lib/rack.rb", "RACK = 'FAIL'" + build_repo3 do + build_gem "myrack", "1.0.0" do |s| + s.write "lib/myrack.rb", "MYRACK = 'FAIL'" end - build_gem "rack-obama" do |s| - s.add_dependency "rack" + build_gem "myrack-obama" do |s| + s.add_dependency "myrack" end end @@ -170,76 +23,76 @@ RSpec.describe "bundle install with gems on multiple sources" do source "https://gem.repo3" source "https://gem.repo1" do gem "thin" # comes first to test name sorting - gem "rack" + gem "myrack" end - gem "rack-obama" # should come from repo3! + gem "myrack-obama" # should come from repo3! G end it "installs the gems without any warning" do bundle :install, artifice: "compact_index" expect(err).not_to include("Warning") - expect(the_bundle).to include_gems("rack-obama 1.0.0") - expect(the_bundle).to include_gems("rack 1.0.0", source: "remote1") + expect(the_bundle).to include_gems("myrack-obama 1.0.0") + expect(the_bundle).to include_gems("myrack 1.0.0", source: "remote1") end it "can cache and deploy" do bundle :cache, artifice: "compact_index" - expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist - expect(bundled_app("vendor/cache/rack-obama-1.0.gem")).to exist + 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("rack-obama 1.0.0", "rack 1.0.0") + expect(the_bundle).to include_gems("myrack-obama 1.0.0", "myrack 1.0.0") end end context "with sources set by an option" do before do - # Oh no! Someone evil is trying to hijack rack :( + # Oh no! Someone evil is trying to hijack myrack :( # need this to be broken to check for correct source ordering - build_repo gem_repo3 do - build_gem "rack", "1.0.0" do |s| - s.write "lib/rack.rb", "RACK = 'FAIL'" + build_repo3 do + build_gem "myrack", "1.0.0" do |s| + s.write "lib/myrack.rb", "MYRACK = 'FAIL'" end - build_gem "rack-obama" do |s| - s.add_dependency "rack" + build_gem "myrack-obama" do |s| + s.add_dependency "myrack" end end install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo3" - gem "rack-obama" # should come from repo3! - gem "rack", :source => "https://gem.repo1" + gem "myrack-obama" # should come from repo3! + gem "myrack", :source => "https://gem.repo1" G end it "installs the gems without any warning" do expect(err).not_to include("Warning") - expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0") + expect(the_bundle).to include_gems("myrack-obama 1.0.0", "myrack 1.0.0") end end context "when a pinned gem has an indirect dependency in the pinned source" do before do - build_repo gem_repo3 do - build_gem "depends_on_rack", "1.0.1" do |s| - s.add_dependency "rack" + build_repo3 do + build_gem "depends_on_myrack", "1.0.1" do |s| + s.add_dependency "myrack" end end - # we need a working rack gem in repo3 + # we need a working myrack gem in repo3 update_repo gem_repo3 do - build_gem "rack", "1.0.0" + build_gem "myrack", "1.0.0" end gemfile <<-G source "https://gem.repo2" source "https://gem.repo3" do - gem "depends_on_rack" + gem "depends_on_myrack" end G end @@ -252,7 +105,7 @@ RSpec.describe "bundle install with gems on multiple sources" do it "installs from the same source without any warning" do bundle :install, artifice: "compact_index" expect(err).not_to include("Warning") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", source: "remote3") + expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 1.0.0", source: "remote3") end end @@ -260,8 +113,8 @@ RSpec.describe "bundle install with gems on multiple sources" do before do # need this to be broken to check for correct source ordering build_repo gem_repo2 do - build_gem "rack", "1.0.0" do |s| - s.write "lib/rack.rb", "RACK = 'FAIL'" + build_gem "myrack", "1.0.0" do |s| + s.write "lib/myrack.rb", "MYRACK = 'FAIL'" end end end @@ -269,32 +122,32 @@ RSpec.describe "bundle install with gems on multiple sources" do it "installs from the same source without any warning" do bundle :install, artifice: "compact_index" - expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", source: "remote3") + 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", 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" - expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", source: "remote3") + expect(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", source: "remote3") end end end context "when a pinned gem has an indirect dependency in a different source" do before do - # In these tests, we need a working rack gem in repo2 and not repo3 + # In these tests, we need a working myrack gem in repo2 and not repo3 - build_repo gem_repo3 do - build_gem "depends_on_rack", "1.0.1" do |s| - s.add_dependency "rack" + build_repo3 do + build_gem "depends_on_myrack", "1.0.1" do |s| + s.add_dependency "myrack" end end build_repo gem_repo2 do - build_gem "rack", "1.0.0" + build_gem "myrack", "1.0.0" end end @@ -303,197 +156,14 @@ RSpec.describe "bundle install with gems on multiple sources" do install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo2" source "https://gem.repo3" do - gem "depends_on_rack" + gem "depends_on_myrack" end G end it "installs from the other source without any warning" do expect(err).not_to include("Warning") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") - end - end - - context "and in yet another source" do - before do - gemfile <<-G - source "https://gem.repo1" - source "https://gem.repo2" - source "https://gem.repo3" do - gem "depends_on_rack" - end - G - end - - it "fails when the two sources don't have the same checksum", bundler: "< 3" do - bundle :install, artifice: "compact_index", raise_on_error: false - - expect(err).to eq(<<~E.strip) - [DEPRECATED] Your Gemfile contains multiple global sources. Using `source` more than once without a block is a security risk, and may result in installing unexpected gems. To resolve this warning, use a block to indicate which gems should come from the secondary source. - Bundler found mismatched checksums. This is a potential security risk. - #{checksum_to_lock(gem_repo2, "rack", "1.0.0")} - from the API at https://gem.repo2/ - #{checksum_to_lock(gem_repo1, "rack", "1.0.0")} - from the API at https://gem.repo1/ - - Mismatched checksums each have an authoritative source: - 1. the API at https://gem.repo2/ - 2. the API at https://gem.repo1/ - You may need to alter your Gemfile sources to resolve this issue. - - To ignore checksum security warnings, disable checksum validation with - `bundle config set --local disable_checksum_validation true` - E - expect(exitstatus).to eq(37) - end - - it "fails when the two sources agree, but the local gem calculates a different checksum", bundler: "< 3" do - rack_checksum = "c0ffee11" * 8 - bundle :install, artifice: "compact_index", env: { "BUNDLER_SPEC_RACK_CHECKSUM" => rack_checksum }, raise_on_error: false - - expect(err).to eq(<<~E.strip) - [DEPRECATED] Your Gemfile contains multiple global sources. Using `source` more than once without a block is a security risk, and may result in installing unexpected gems. To resolve this warning, use a block to indicate which gems should come from the secondary source. - Bundler found mismatched checksums. This is a potential security risk. - rack (1.0.0) sha256=#{rack_checksum} - from the API at https://gem.repo2/ - and the API at https://gem.repo1/ - #{checksum_to_lock(gem_repo2, "rack", "1.0.0")} - from the gem at #{default_bundle_path("cache", "rack-1.0.0.gem")} - - If you trust the API at https://gem.repo2/, to resolve this issue you can: - 1. remove the gem at #{default_bundle_path("cache", "rack-1.0.0.gem")} - 2. run `bundle install` - - To ignore checksum security warnings, disable checksum validation with - `bundle config set --local disable_checksum_validation true` - E - expect(exitstatus).to eq(37) - end - - it "installs from the other source and warns about ambiguous gems when the sources have the same checksum", bundler: "< 3" do - gem_checksum = checksum_digest(gem_repo2, "rack", "1.0.0") - bundle :install, artifice: "compact_index", env: { "BUNDLER_SPEC_RACK_CHECKSUM" => gem_checksum, "DEBUG" => "1" } - - expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") - expect(err).to include("Installed from: https://gem.repo2") - - checksums = checksums_section_when_existing do |c| - c.checksum gem_repo3, "depends_on_rack", "1.0.1" - c.checksum gem_repo2, "rack", "1.0.0" - end - - expect(lockfile).to eq <<~L - GEM - remote: https://gem.repo1/ - remote: https://gem.repo2/ - specs: - rack (1.0.0) - - GEM - remote: https://gem.repo3/ - specs: - depends_on_rack (1.0.1) - rack - - PLATFORMS - #{lockfile_platforms} - - DEPENDENCIES - depends_on_rack! - #{checksums} - BUNDLED WITH - #{Bundler::VERSION} - L - - previous_lockfile = lockfile - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") - expect(lockfile).to eq(previous_lockfile) - end - - it "installs from the other source and warns about ambiguous gems when checksum validation is disabled", bundler: "< 3" do - bundle "config set --local disable_checksum_validation true" - bundle :install, artifice: "compact_index" - - expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") - expect(err).to include("Installed from: https://gem.repo2") - - checksums = checksums_section_when_existing do |c| - c.no_checksum "depends_on_rack", "1.0.1" - c.no_checksum "rack", "1.0.0" - end - - expect(lockfile).to eq <<~L - GEM - remote: https://gem.repo1/ - remote: https://gem.repo2/ - specs: - rack (1.0.0) - - GEM - remote: https://gem.repo3/ - specs: - depends_on_rack (1.0.1) - rack - - PLATFORMS - #{lockfile_platforms} - - DEPENDENCIES - depends_on_rack! - #{checksums} - BUNDLED WITH - #{Bundler::VERSION} - L - - previous_lockfile = lockfile - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") - expect(lockfile).to eq(previous_lockfile) - end - - it "fails", bundler: "3" do - bundle :install, artifice: "compact_index", raise_on_error: false - expect(err).to include("Each source after the first must include a block") - expect(exitstatus).to eq(4) - end - end - - context "and only the dependency is pinned" do - before do - # need this to be broken to check for correct source ordering - build_repo gem_repo2 do - build_gem "rack", "1.0.0" do |s| - s.write "lib/rack.rb", "RACK = 'FAIL'" - end - end - - gemfile <<-G - source "https://gem.repo3" # contains depends_on_rack - source "https://gem.repo2" # contains broken rack - - gem "depends_on_rack" # installed from gem_repo3 - gem "rack", :source => "https://gem.repo1" - G - end - - it "installs the dependency from the pinned source without warning", bundler: "< 3" do - bundle :install, artifice: "compact_index" - - expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") - - # In https://github.com/rubygems/bundler/issues/3585 this failed - # when there is already a lock file, and the gems are missing, so try again - system_gems [] - bundle :install, artifice: "compact_index" - - expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0") - end - - it "fails", bundler: "3" do - bundle :install, artifice: "compact_index", raise_on_error: false - expect(err).to include("Each source after the first must include a block") - expect(exitstatus).to eq(4) + expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 1.0.0") end end end @@ -502,7 +172,7 @@ RSpec.describe "bundle install with gems on multiple sources" do before do build_repo2 - build_repo gem_repo3 do + build_repo3 do build_gem "private_gem_1", "1.0.0" build_gem "private_gem_2", "1.0.0" end @@ -520,59 +190,26 @@ RSpec.describe "bundle install with gems on multiple sources" do it "fails" do bundle :install, artifice: "compact_index", raise_on_error: false - expect(err).to include("Could not find gem 'private_gem_1' in rubygems repository https://gem.repo2/, cached gems or installed locally.") - end - end - - context "when an indirect dependency can't be found in the aggregate rubygems source", bundler: "< 3" do - before do - build_repo2 - - build_repo gem_repo3 do - build_gem "depends_on_missing", "1.0.1" do |s| - s.add_dependency "missing" - end - end - - gemfile <<-G - source "https://gem.repo2" - - source "https://gem.repo3" - - gem "depends_on_missing" - G - end - - it "fails" do - bundle :install, artifice: "compact_index", raise_on_error: false - expect(err).to 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 + expect(err).to include("Could not find gem 'private_gem_1' in rubygems repository https://gem.repo2/ or installed locally.") end end context "when a top-level gem has an indirect dependency" do before do build_repo gem_repo2 do - build_gem "depends_on_rack", "1.0.1" do |s| - s.add_dependency "rack" + build_gem "depends_on_myrack", "1.0.1" do |s| + s.add_dependency "myrack" end end - build_repo gem_repo3 do + build_repo3 do build_gem "unrelated_gem", "1.0.0" end gemfile <<-G source "https://gem.repo2" - gem "depends_on_rack" + gem "depends_on_myrack" source "https://gem.repo3" do gem "unrelated_gem" @@ -583,15 +220,15 @@ RSpec.describe "bundle install with gems on multiple sources" do context "and the dependency is only in the top-level source" do before do update_repo gem_repo2 do - build_gem "rack", "1.0.0" + build_gem "myrack", "1.0.0" end end it "installs the dependency from the top-level source without warning" do bundle :install, artifice: "compact_index" expect(err).not_to include("Warning") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", source: "remote2") + expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 1.0.0", "unrelated_gem 1.0.0") + expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 1.0.0", source: "remote2") expect(the_bundle).to include_gems("unrelated_gem 1.0.0", source: "remote3") end end @@ -599,8 +236,8 @@ RSpec.describe "bundle install with gems on multiple sources" do context "and the dependency is only in a pinned source" do before do update_repo gem_repo3 do - build_gem "rack", "1.0.0" do |s| - s.write "lib/rack.rb", "RACK = 'FAIL'" + build_gem "myrack", "1.0.0" do |s| + s.write "lib/myrack.rb", "MYRACK = 'FAIL'" end end end @@ -610,10 +247,10 @@ RSpec.describe "bundle install with gems on multiple sources" do expect(err).to end_with <<~E.strip Could not find compatible versions - Because every version of depends_on_rack depends on rack >= 0 - and rack >= 0 could not be found in rubygems repository https://gem.repo2/, cached gems or installed locally, - depends_on_rack cannot be used. - So, because Gemfile depends on depends_on_rack >= 0, + Because every version of depends_on_myrack depends on myrack >= 0 + and myrack >= 0 could not be found in rubygems repository https://gem.repo2/ or installed locally, + depends_on_myrack cannot be used. + So, because Gemfile depends on depends_on_myrack >= 0, version solving has failed. E end @@ -622,12 +259,12 @@ RSpec.describe "bundle install with gems on multiple sources" do context "and the dependency is in both the top-level and a pinned source" do before do update_repo gem_repo2 do - build_gem "rack", "1.0.0" + build_gem "myrack", "1.0.0" end update_repo gem_repo3 do - build_gem "rack", "1.0.0" do |s| - s.write "lib/rack.rb", "RACK = 'FAIL'" + build_gem "myrack", "1.0.0" do |s| + s.write "lib/myrack.rb", "MYRACK = 'FAIL'" end end end @@ -635,9 +272,9 @@ RSpec.describe "bundle install with gems on multiple sources" do it "installs the dependency from the top-level source without warning" do bundle :install, artifice: "compact_index" expect(err).not_to include("Warning") - expect(run("require 'rack'; puts RACK")).to eq("1.0.0") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", source: "remote2") + expect(run("require 'myrack'; puts MYRACK")).to eq("1.0.0") + expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 1.0.0", "unrelated_gem 1.0.0") + expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 1.0.0", source: "remote2") expect(the_bundle).to include_gems("unrelated_gem 1.0.0", source: "remote3") end end @@ -645,13 +282,13 @@ RSpec.describe "bundle install with gems on multiple sources" do context "when a scoped gem has a deeply nested indirect dependency" do before do - build_repo gem_repo3 do - build_gem "depends_on_depends_on_rack", "1.0.1" do |s| - s.add_dependency "depends_on_rack" + build_repo3 do + build_gem "depends_on_depends_on_myrack", "1.0.1" do |s| + s.add_dependency "depends_on_myrack" end - build_gem "depends_on_rack", "1.0.1" do |s| - s.add_dependency "rack" + build_gem "depends_on_myrack", "1.0.1" do |s| + s.add_dependency "myrack" end end @@ -659,7 +296,7 @@ RSpec.describe "bundle install with gems on multiple sources" do source "https://gem.repo2" source "https://gem.repo3" do - gem "depends_on_depends_on_rack" + gem "depends_on_depends_on_myrack" end G end @@ -667,15 +304,15 @@ RSpec.describe "bundle install with gems on multiple sources" do context "and the dependency is only in the top-level source" do before do update_repo gem_repo2 do - build_gem "rack", "1.0.0" + build_gem "myrack", "1.0.0" end end it "installs the dependency from the top-level source" do bundle :install, artifice: "compact_index" - expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0") - expect(the_bundle).to include_gems("rack 1.0.0", source: "remote2") - expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", source: "remote3") + expect(the_bundle).to include_gems("depends_on_depends_on_myrack 1.0.1", "depends_on_myrack 1.0.1", "myrack 1.0.0") + expect(the_bundle).to include_gems("myrack 1.0.0", source: "remote2") + expect(the_bundle).to include_gems("depends_on_depends_on_myrack 1.0.1", "depends_on_myrack 1.0.1", source: "remote3") end end @@ -684,367 +321,36 @@ RSpec.describe "bundle install with gems on multiple sources" do build_repo2 update_repo gem_repo3 do - build_gem "rack", "1.0.0" + build_gem "myrack", "1.0.0" end end it "installs the dependency from the pinned source" do bundle :install, artifice: "compact_index" - expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0", source: "remote3") + expect(the_bundle).to include_gems("depends_on_depends_on_myrack 1.0.1", "depends_on_myrack 1.0.1", "myrack 1.0.0", source: "remote3") end end context "and the dependency is in both the top-level and a pinned source" do before do update_repo gem_repo2 do - build_gem "rack", "1.0.0" do |s| - s.write "lib/rack.rb", "RACK = 'FAIL'" + build_gem "myrack", "1.0.0" do |s| + s.write "lib/myrack.rb", "MYRACK = 'FAIL'" end end update_repo gem_repo3 do - build_gem "rack", "1.0.0" + build_gem "myrack", "1.0.0" end end it "installs the dependency from the pinned source without warning" do bundle :install, artifice: "compact_index" - expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0", source: "remote3") + expect(the_bundle).to include_gems("depends_on_depends_on_myrack 1.0.1", "depends_on_myrack 1.0.1", "myrack 1.0.0", source: "remote3") end end end - context "when the lockfile has aggregated rubygems sources and newer versions of dependencies are available" do - before do - build_repo gem_repo2 do - build_gem "activesupport", "6.0.3.4" do |s| - s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2" - s.add_dependency "i18n", ">= 0.7", "< 2" - s.add_dependency "minitest", "~> 5.1" - s.add_dependency "tzinfo", "~> 1.1" - s.add_dependency "zeitwerk", "~> 2.2", ">= 2.2.2" - end - - build_gem "activesupport", "6.1.2.1" do |s| - s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2" - s.add_dependency "i18n", ">= 1.6", "< 2" - s.add_dependency "minitest", ">= 5.1" - s.add_dependency "tzinfo", "~> 2.0" - s.add_dependency "zeitwerk", "~> 2.3" - end - - build_gem "concurrent-ruby", "1.1.8" - build_gem "concurrent-ruby", "1.1.9" - build_gem "connection_pool", "2.2.3" - - build_gem "i18n", "1.8.9" do |s| - s.add_dependency "concurrent-ruby", "~> 1.0" - end - - build_gem "minitest", "5.14.3" - build_gem "rack", "2.2.3" - build_gem "redis", "4.2.5" - - build_gem "sidekiq", "6.1.3" do |s| - s.add_dependency "connection_pool", ">= 2.2.2" - s.add_dependency "rack", "~> 2.0" - s.add_dependency "redis", ">= 4.2.0" - end - - build_gem "thread_safe", "0.3.6" - - build_gem "tzinfo", "1.2.9" do |s| - s.add_dependency "thread_safe", "~> 0.1" - end - - build_gem "tzinfo", "2.0.4" do |s| - s.add_dependency "concurrent-ruby", "~> 1.0" - end - - build_gem "zeitwerk", "2.4.2" - end - - build_repo gem_repo3 do - build_gem "sidekiq-pro", "5.2.1" do |s| - s.add_dependency "connection_pool", ">= 2.2.3" - s.add_dependency "sidekiq", ">= 6.1.0" - end - end - - gemfile <<-G - # frozen_string_literal: true - - source "https://gem.repo2" - - gem "activesupport" - - source "https://gem.repo3" do - gem "sidekiq-pro" - end - G - - @locked_checksums = checksums_section_when_existing do |c| - c.checksum gem_repo2, "activesupport", "6.0.3.4" - c.checksum gem_repo2, "concurrent-ruby", "1.1.8" - c.checksum gem_repo2, "connection_pool", "2.2.3" - c.checksum gem_repo2, "i18n", "1.8.9" - c.checksum gem_repo2, "minitest", "5.14.3" - c.checksum gem_repo2, "rack", "2.2.3" - c.checksum gem_repo2, "redis", "4.2.5" - c.checksum gem_repo2, "sidekiq", "6.1.3" - c.checksum gem_repo3, "sidekiq-pro", "5.2.1" - c.checksum gem_repo2, "thread_safe", "0.3.6" - c.checksum gem_repo2, "tzinfo", "1.2.9" - c.checksum gem_repo2, "zeitwerk", "2.4.2" - end - - lockfile <<~L - GEM - remote: https://gem.repo2/ - remote: https://gem.repo3/ - specs: - activesupport (6.0.3.4) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) - concurrent-ruby (1.1.8) - connection_pool (2.2.3) - i18n (1.8.9) - concurrent-ruby (~> 1.0) - minitest (5.14.3) - rack (2.2.3) - redis (4.2.5) - sidekiq (6.1.3) - connection_pool (>= 2.2.2) - rack (~> 2.0) - redis (>= 4.2.0) - sidekiq-pro (5.2.1) - connection_pool (>= 2.2.3) - sidekiq (>= 6.1.0) - thread_safe (0.3.6) - tzinfo (1.2.9) - thread_safe (~> 0.1) - zeitwerk (2.4.2) - - PLATFORMS - #{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) - rack (2.2.3) - redis (4.2.5) - sidekiq (6.1.3) - connection_pool (>= 2.2.2) - rack (~> 2.0) - redis (>= 4.2.0) - thread_safe (0.3.6) - tzinfo (1.2.9) - thread_safe (~> 0.1) - zeitwerk (2.4.2) - - GEM - remote: https://gem.repo3/ - specs: - sidekiq-pro (5.2.1) - connection_pool (>= 2.2.3) - sidekiq (>= 6.1.0) - - PLATFORMS - #{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) - rack (2.2.3) - redis (4.2.5) - sidekiq (6.1.3) - connection_pool (>= 2.2.2) - rack (~> 2.0) - redis (>= 4.2.0) - tzinfo (2.0.4) - concurrent-ruby (~> 1.0) - zeitwerk (2.4.2) - - GEM - remote: https://gem.repo3/ - specs: - sidekiq-pro (5.2.1) - connection_pool (>= 2.2.3) - sidekiq (>= 6.1.0) - - PLATFORMS - #{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) - rack (2.2.3) - redis (4.2.5) - sidekiq (6.1.3) - connection_pool (>= 2.2.2) - rack (~> 2.0) - redis (>= 4.2.0) - thread_safe (0.3.6) - tzinfo (1.2.9) - thread_safe (~> 0.1) - zeitwerk (2.4.2) - - GEM - remote: https://gem.repo3/ - specs: - sidekiq-pro (5.2.1) - connection_pool (>= 2.2.3) - sidekiq (>= 6.1.0) - - PLATFORMS - #{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") @@ -1080,7 +386,7 @@ RSpec.describe "bundle install with gems on multiple sources" do context "when a pinned gem has an indirect dependency with more than one level of indirection in the default source " do before do - build_repo gem_repo3 do + build_repo3 do build_gem "handsoap", "0.2.5.5" do |s| s.add_dependency "nokogiri", ">= 1.2.3" end @@ -1106,7 +412,7 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs from the default source without any warnings or errors and generates a proper lockfile" do - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum gem_repo3, "handsoap", "0.2.5.5" c.checksum gem_repo2, "nokogiri", "1.11.1" c.checksum gem_repo2, "racca", "1.5.2" @@ -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" @@ -1157,7 +463,7 @@ RSpec.describe "bundle install with gems on multiple sources" do context "with a gem that is only found in the wrong source" do before do - build_repo gem_repo3 do + build_repo3 do build_gem "not_in_repo1", "1.0.0" end @@ -1174,7 +480,7 @@ RSpec.describe "bundle install with gems on multiple sources" do context "with an existing lockfile" do before do - system_gems "rack-0.9.1", "rack-1.0.0", path: default_bundle_path + system_gems "myrack-0.9.1", "myrack-1.0.0", path: default_bundle_path lockfile <<-L GEM @@ -1184,132 +490,26 @@ RSpec.describe "bundle install with gems on multiple sources" do GEM remote: https://gem.repo3 specs: - rack (0.9.1) + myrack (0.9.1) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack! + myrack! L gemfile <<-G source "https://gem.repo1" source "https://gem.repo3" do - gem 'rack' + gem 'myrack' end G end # Reproduction of https://github.com/rubygems/bundler/issues/3298 it "does not unlock the installed gem on exec" do - expect(the_bundle).to include_gems("rack 0.9.1") - end - end - - context "with a lockfile with aggregated rubygems sources" do - let(:aggregate_gem_section_lockfile) do - <<~L - GEM - remote: https://gem.repo1/ - remote: https://gem.repo3/ - specs: - rack (0.9.1) - - PLATFORMS - #{lockfile_platforms} - - DEPENDENCIES - rack! - #{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: - rack (0.9.1) - - PLATFORMS - #{lockfile_platforms} - - DEPENDENCIES - rack! - - BUNDLED WITH - #{Bundler::VERSION} - L - end - - before do - build_repo gem_repo3 do - build_gem "rack", "0.9.1" - end - - gemfile <<-G - source "https://gem.repo1" - source "https://gem.repo3" do - gem 'rack' - end - G - - lockfile aggregate_gem_section_lockfile - end - - it "installs the existing lockfile but prints a warning 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("rack 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, "rack", "0.9.1") - api_checksum3 = checksum_digest(gem_repo3, "rack", "0.9.1") - - expect(exitstatus).to eq(37) - expect(err).to eq(<<~E.strip) - [DEPRECATED] Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure. - Bundler found mismatched checksums. This is a potential security risk. - rack (0.9.1) sha256=#{api_checksum3} - from the API at https://gem.repo3/ - rack (0.9.1) sha256=#{api_checksum1} - from the API at https://gem.repo1/ - - Mismatched checksums each have an authoritative source: - 1. the API at https://gem.repo3/ - 2. the API at https://gem.repo1/ - You may need to alter your Gemfile sources to resolve this issue. - - To ignore checksum security warnings, disable checksum validation with - `bundle config set --local disable_checksum_validation true` - E - end - - it "refuses to install the existing lockfile and prints an error", bundler: "3" do - bundle "config set --local deployment true" - - bundle "install", artifice: "compact_index", raise_on_error: false - - expect(lockfile).to eq(aggregate_gem_section_lockfile) - expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.") - expect(out).to be_empty + expect(the_bundle).to include_gems("myrack 0.9.1") end end @@ -1318,8 +518,8 @@ RSpec.describe "bundle install with gems on multiple sources" do build_lib "foo" gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :source => "https://gem.repo1" + source "https://gem.repo1" + gem "myrack", :source => "https://gem.repo1" gem "foo", :path => "#{lib_path("foo-1.0")}" G end @@ -1336,17 +536,17 @@ RSpec.describe "bundle install with gems on multiple sources" do context "when an older version of the same gem also ships with Ruby" do before do - system_gems "rack-0.9.1" + system_gems "myrack-0.9.1" install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo1" - gem "rack" # should come from repo1! + gem "myrack" # should come from repo1! G end it "installs the gems without any warning" do expect(err).not_to include("Warning") - expect(the_bundle).to include_gems("rack 1.0.0") + expect(the_bundle).to include_gems("myrack 1.0.0") end end @@ -1361,16 +561,16 @@ RSpec.describe "bundle install with gems on multiple sources" do # Installing this gemfile... gemfile <<-G source 'https://gem.repo1' - gem 'rack' + gem 'myrack' gem 'foo', '~> 0.1', :source => 'https://gem.repo4' gem 'bar', '~> 0.1', :source => 'https://gem.repo4' G - bundle "config set --local path ../gems/system" + bundle_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 @@ -1380,7 +580,7 @@ RSpec.describe "bundle install with gems on multiple sources" do # And install this gemfile, updating only foo. install_gemfile <<-G, artifice: "compact_index" source 'https://gem.repo1' - gem 'rack' + gem 'myrack' gem 'foo', '~> 0.2', :source => 'https://gem.repo4' gem 'bar', '~> 0.1', :source => 'https://gem.repo4' G @@ -1393,8 +593,8 @@ RSpec.describe "bundle install with gems on multiple sources" do context "re-resolving" do context "when there is a mix of sources in the gemfile" do before do - build_repo gem_repo3 do - build_gem "rack" + build_repo3 do + build_gem "myrack" end build_lib "path1" @@ -1407,7 +607,7 @@ RSpec.describe "bundle install with gems on multiple sources" do gem "rails" source "https://gem.repo3" do - gem "rack" + gem "myrack" end gem "path1", :path => "#{lib_path("path1-1.0")}" @@ -1429,14 +629,14 @@ RSpec.describe "bundle install with gems on multiple sources" do before do install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo1" - gem "rack" + gem "myrack" G end context "and the gemfile changes" do it "is still able to find that gem from remote sources" do build_repo4 do - build_gem "rack", "2.0.1.1.forked" + build_gem "myrack", "2.0.1.1.forked" build_gem "thor", "0.19.1.1.forked" end @@ -1445,10 +645,10 @@ RSpec.describe "bundle install with gems on multiple sources" do source "https://gem.repo1" source "https://gem.repo4" do - gem "rack", "2.0.1.1.forked" + gem "myrack", "2.0.1.1.forked" gem "thor" end - gem "rack-obama" + gem "myrack-obama" G # Then we change the Gemfile by adding a version to thor @@ -1456,13 +656,13 @@ RSpec.describe "bundle install with gems on multiple sources" do source "https://gem.repo1" source "https://gem.repo4" do - gem "rack", "2.0.1.1.forked" + gem "myrack", "2.0.1.1.forked" gem "thor", "0.19.1.1.forked" end - gem "rack-obama" + gem "myrack-obama" G - # But we should still be able to find rack 2.0.1.1.forked and install it + # But we should still be able to find myrack 2.0.1.1.forked and install it bundle :install, artifice: "compact_index" end end @@ -1473,30 +673,30 @@ RSpec.describe "bundle install with gems on multiple sources" do install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo1" - gem "rack" + gem "myrack" G build_repo2 do - build_gem "rack", "1.2" do |s| - s.executables = "rackup" + build_gem "myrack", "1.2" do |s| + s.executables = "myrackup" end build_gem "bar" end - build_lib("gemspec_test", path: tmp.join("gemspec_test")) do |s| + build_lib("gemspec_test", path: tmp("gemspec_test")) do |s| s.add_dependency "bar", "=1.0.0" end install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo2" - gem "rack" - gemspec :path => "#{tmp.join("gemspec_test")}" + gem "myrack" + gemspec :path => "#{tmp("gemspec_test")}" G end it "conservatively installs the existing locked version" do - expect(the_bundle).to include_gems("rack 1.0.0") + expect(the_bundle).to include_gems("myrack 1.0.0") end end @@ -1506,7 +706,7 @@ RSpec.describe "bundle install with gems on multiple sources" do build_gem "bar" end - build_lib("gemspec_test", path: tmp.join("gemspec_test")) do |s| + build_lib("gemspec_test", path: tmp("gemspec_test")) do |s| s.add_development_dependency "bar" end @@ -1517,7 +717,7 @@ RSpec.describe "bundle install with gems on multiple sources" do gem "bar" end - gemspec :path => "#{tmp.join("gemspec_test")}" + gemspec :path => "#{tmp("gemspec_test")}" G end @@ -1583,143 +783,37 @@ 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 + 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_rack" do |s| - s.add_dependency "rack" + build_gem "depends_on_myrack" do |s| + s.add_dependency "myrack" end - build_gem "rack" + build_gem "myrack" end - install_gemfile <<-G, artifice: "compact_index", raise_on_error: false - source "#{file_uri_for(gem_repo1)}" + install_gemfile <<-G, artifice: "compact_index_extra_api", raise_on_error: false + source "https://global.source" - source "https://gem.repo4" do - gem "depends_on_rack" + source "https://scoped.source/extra" do + gem "depends_on_myrack" end - source "https://gem.repo1" do - gem "thin" - end - G - expect(err).to eq <<~EOS.strip - Warning: The gem 'rack' was found in multiple relevant sources. - * rubygems repository https://gem.repo1/ - * rubygems repository https://gem.repo4/ - You should add this gem to the source block for the source you wish it to be installed from. - EOS - expect(last_command).to be_success - expect(the_bundle).to be_locked - end - end - - context "when an indirect dependency is available from multiple ambiguous sources", bundler: "3" do - it "raises, suggesting a source block" do - build_repo4 do - build_gem "depends_on_rack" do |s| - s.add_dependency "rack" - end - build_gem "rack" - end - - install_gemfile <<-G, artifice: "compact_index", raise_on_error: false - source "#{file_uri_for(gem_repo1)}" - source "https://gem.repo4" do - gem "depends_on_rack" - end - source "https://gem.repo1" do + source "https://scoped.source" do gem "thin" end G expect(last_command).to be_failure expect(err).to eq <<~EOS.strip - The gem 'rack' was found in multiple relevant sources. - * rubygems repository https://gem.repo1/ - * rubygems repository https://gem.repo4/ + The gem 'myrack' was found in multiple relevant sources. + * rubygems repository https://scoped.source/ + * rubygems repository https://scoped.source/extra/ You must add this gem to the source block for the source you wish it to be installed from. EOS expect(the_bundle).not_to be_locked end end - context "when upgrading a lockfile suffering from dependency confusion" do - before do - build_repo4 do - build_gem "mime-types", "3.0.0" - end - - build_repo2 do - build_gem "capybara", "2.5.0" do |s| - s.add_dependency "mime-types", ">= 1.16" - end - - build_gem "mime-types", "3.3.1" - end - - gemfile <<~G - source "https://gem.repo2" - - gem "capybara", "~> 2.5.0" - - source "https://gem.repo4" do - gem "mime-types", "~> 3.0" - end - G - - lockfile <<-L - GEM - remote: https://gem.repo2/ - remote: https://gem.repo4/ - specs: - capybara (2.5.0) - mime-types (>= 1.16) - mime-types (3.3.1) - - PLATFORMS - #{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_existing do |c| - c.checksum gem_repo2, "capybara", "2.5.0" - c.checksum gem_repo4, "mime-types", "3.0.0" - end - - expect(lockfile).to eq <<~L - GEM - 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 @@ -1740,28 +834,28 @@ RSpec.describe "bundle install with gems on multiple sources" do end gemfile <<~G - source "https://localgemserver.test" + source "https://gem.repo4" - gem "ruport", "= 1.7.0.3", :source => "https://localgemserver.test/extra" + gem "ruport", "= 1.7.0.3", :source => "https://gem.repo4/extra" G end it "handles that fine" do - bundle "install", artifice: "compact_index_extra", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + bundle "install", artifice: "compact_index_extra" - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum gem_repo4, "pdf-writer", "1.1.8" c.checksum gem_repo2, "ruport", "1.7.0.3" end expect(lockfile).to eq <<~L GEM - remote: https://localgemserver.test/ + remote: https://gem.repo4/ specs: pdf-writer (1.1.8) GEM - remote: https://localgemserver.test/extra/ + remote: https://gem.repo4/extra/ specs: ruport (1.7.0.3) pdf-writer (= 1.1.8) @@ -1773,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 @@ -1798,28 +892,28 @@ RSpec.describe "bundle install with gems on multiple sources" do end gemfile <<~G - source "https://localgemserver.test" + source "https://gem.repo4" - gem "ruport", "= 1.7.0.3", :source => "https://localgemserver.test/extra" + gem "ruport", "= 1.7.0.3", :source => "https://gem.repo4/extra" G end it "handles that fine" do - bundle "install", artifice: "compact_index_extra", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + bundle "install", artifice: "compact_index_extra" - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum gem_repo4, "pdf-writer", "1.1.8" c.checksum gem_repo2, "ruport", "1.7.0.3" end expect(lockfile).to eq <<~L GEM - remote: https://localgemserver.test/ + remote: https://gem.repo4/ specs: pdf-writer (1.1.8) GEM - remote: https://localgemserver.test/extra/ + remote: https://gem.repo4/extra/ specs: ruport (1.7.0.3) pdf-writer (= 1.1.8) @@ -1831,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 @@ -1850,22 +944,22 @@ RSpec.describe "bundle install with gems on multiple sources" do end gemfile <<~G - source "https://localgemserver.test" + source "https://gem.repo4" gem "pdf-writer", "= 1.1.8" G end it "handles that fine" do - bundle "install --verbose", artifice: "endpoint", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + bundle "install --verbose", artifice: "endpoint" - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum gem_repo4, "pdf-writer", "1.1.8" end expect(lockfile).to eq <<~L GEM - remote: https://localgemserver.test/ + remote: https://gem.repo4/ specs: pdf-writer (1.1.8) @@ -1876,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 @@ -1919,4 +1013,301 @@ RSpec.describe "bundle install with gems on multiple sources" do expect(err).to include("Could not find gem 'example' in rubygems repository https://gem.repo4/") end end + + context "when a gem has versions in two sources, but only the locked one has updates" do + let(:original_lockfile) do + <<~L + GEM + remote: https://main.source/ + specs: + activesupport (1.0) + bigdecimal + bigdecimal (1.0.0) + + GEM + remote: https://main.source/extra/ + specs: + foo (1.0) + bigdecimal + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + activesupport + foo! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + before do + build_repo3 do + build_gem "activesupport" do |s| + s.add_dependency "bigdecimal" + end + + build_gem "bigdecimal", "1.0.0" + build_gem "bigdecimal", "3.3.1" + end + + build_repo4 do + build_gem "foo" do |s| + s.add_dependency "bigdecimal" + end + + build_gem "bigdecimal", "1.0.0" + end + + gemfile <<~G + source "https://main.source" + + gem "activesupport" + + source "https://main.source/extra" do + gem "foo" + end + G + + lockfile original_lockfile + end + + it "properly upgrades the lockfile when updating that specific gem" do + bundle "update bigdecimal --conservative", artifice: "compact_index_extra_api", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo3.to_s } + + 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 c81c7095b0..97b1d233bf 100644 --- a/spec/bundler/install/gemfile/specific_platform_spec.rb +++ b/spec/bundler/install/gemfile/specific_platform_spec.rb @@ -2,7 +2,7 @@ RSpec.describe "bundle install with specific platforms" do let(:google_protobuf) { <<-G } - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "google-protobuf" G @@ -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" @@ -19,21 +19,65 @@ RSpec.describe "bundle install with specific platforms" do end end + it "still installs the platform specific variant when locked only to ruby, and the platform specific variant has different dependencies" do + simulate_platform "x86_64-darwin-15" do + build_repo4 do + build_gem("sass-embedded", "1.72.0") do |s| + s.add_dependency "rake" + end + + build_gem("sass-embedded", "1.72.0") do |s| + s.platform = "x86_64-darwin-15" + end + + build_gem "rake" + end + + gemfile <<~G + source "https://gem.repo4" + + gem "sass-embedded" + G + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + rake (1.0) + sass-embedded (1.72.0) + rake + + PLATFORMS + ruby + + DEPENDENCIES + sass-embedded + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install --verbose" + expect(err).to include("The following platform specific gems are getting installed, yet the lockfile includes only their generic ruby version") + expect(out).to include("Installing sass-embedded 1.72.0 (x86_64-darwin-15)") + + expect(the_bundle).to include_gem("sass-embedded 1.72.0 x86_64-darwin-15") + end + end + it "understands that a non-platform specific gem in a old lockfile doesn't necessarily mean installing the non-specific variant" do simulate_platform "x86_64-darwin-15" do setup_multiplatform_gem - system_gems "bundler-2.1.4" - # Consistent location to install and look for gems - bundle "config set --local path vendor/bundle", env: { "BUNDLER_VERSION" => "2.1.4" } + bundle_config "path vendor/bundle" - install_gemfile(google_protobuf, env: { "BUNDLER_VERSION" => "2.1.4" }) + install_gemfile(google_protobuf) # simulate lockfile created with old bundler, which only locks for ruby platform lockfile <<-L GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: google-protobuf (3.0.0.alpha.5.0.5.1) @@ -44,36 +88,34 @@ RSpec.describe "bundle install with specific platforms" do google-protobuf BUNDLED WITH - 2.1.4 + #{Bundler::VERSION} L - # force strict usage of the lock file by setting frozen mode - bundle "config set --local frozen true", env: { "BUNDLER_VERSION" => "2.1.4" } + # 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") end end - it "understands that a non-platform specific gem in a new lockfile locked only to RUBY doesn't necessarily mean installing the non-specific variant" do + it "understands that a non-platform specific gem in a new lockfile locked only to ruby doesn't necessarily mean installing the non-specific variant" do simulate_platform "x86_64-darwin-15" do setup_multiplatform_gem - system_gems "bundler-2.1.4" - # Consistent location to install and look for gems - bundle "config set --local path vendor/bundle", env: { "BUNDLER_VERSION" => "2.1.4" } + bundle_config "path vendor/bundle" gemfile google_protobuf - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum gem_repo2, "google-protobuf", "3.0.0.alpha.4.0" end # simulate lockfile created with old bundler, which only locks for ruby platform lockfile <<-L GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: google-protobuf (3.0.0.alpha.4.0) @@ -84,10 +126,11 @@ RSpec.describe "bundle install with specific platforms" do google-protobuf #{checksums} BUNDLED WITH - 2.1.4 + #{Bundler::VERSION} L - bundle "update", env: { "BUNDLER_VERSION" => Bundler::VERSION } + bundle "update" + expect(err).to include("The following platform specific gems are getting installed, yet the lockfile includes only their generic ruby version") checksums.checksum gem_repo2, "google-protobuf", "3.0.0.alpha.5.0.5.1" @@ -97,7 +140,7 @@ RSpec.describe "bundle install with specific platforms" do # make sure we're still only locked to ruby expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: google-protobuf (3.0.0.alpha.5.0.5.1) @@ -108,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 + 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" @@ -124,14 +172,14 @@ RSpec.describe "bundle install with specific platforms" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "nokogiri" G lockfile <<-L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: nokogiri (1.3.10) @@ -142,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", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } - expect(out).to include("Installing nokogiri 1.3.10") + it "still installs the generic ruby variant if necessary" do + 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", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s, "BUNDLE_FROZEN" => "true" } - expect(out).to include("Installing nokogiri 1.3.10") + it "still installs the generic ruby variant if necessary, even in frozen mode" do + 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 @@ -166,24 +270,22 @@ RSpec.describe "bundle install with specific platforms" do build_gem("libv8", "8.4.255.0") {|s| s.platform = "universal-darwin" } build_gem("mini_racer", "1.0.0") do |s| - s.add_runtime_dependency "libv8" + s.add_dependency "libv8" end end - system_gems "bundler-2.1.4" - # Consistent location to install and look for gems - bundle "config set --local path vendor/bundle", env: { "BUNDLER_VERSION" => "2.1.4" } + bundle_config "path vendor/bundle" gemfile <<-G - source "https://localgemserver.test" + source "https://gem.repo2" gem "libv8" G # simulate lockfile created with old bundler, which only locks for ruby platform lockfile <<-L GEM - remote: https://localgemserver.test/ + remote: https://gem.repo2/ specs: libv8 (8.4.255.0) @@ -194,13 +296,14 @@ RSpec.describe "bundle install with specific platforms" do libv8 BUNDLED WITH - 2.1.4 + #{Bundler::VERSION} L - bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_VERSION" => "2.1.4", "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + bundle "install --verbose" + expect(err).to include("The following platform specific gems are getting installed, yet the lockfile includes only their generic ruby version") expect(out).to include("Installing libv8 8.4.255.0 (universal-darwin)") - bundle "add mini_racer --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + bundle "add mini_racer --verbose" expect(out).to include("Using libv8 8.4.255.0 (universal-darwin)") end end @@ -213,14 +316,14 @@ RSpec.describe "bundle install with specific platforms" do end gemfile <<-G - source "https://localgemserver.test" + source "https://gem.repo4" gem "grpc" G # simulate lockfile created with old bundler, which only locks for ruby platform lockfile <<-L GEM - remote: https://localgemserver.test/ + remote: https://gem.repo4/ specs: grpc (1.50.0) @@ -231,10 +334,11 @@ RSpec.describe "bundle install with specific platforms" do grpc BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L - bundle "install --verbose", artifice: "compact_index_precompiled_before", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + bundle "install --verbose", artifice: "compact_index_precompiled_before" + expect(err).to include("The following platform specific gems are getting installed, yet the lockfile includes only their generic ruby version") expect(out).to include("Installing grpc 1.50.0 (universal-darwin)") end end @@ -255,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 @@ -268,7 +372,7 @@ RSpec.describe "bundle install with specific platforms" do git = build_git "pg_array_parser", "1.0" gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "pg_array_parser", :git => "#{lib_path("pg_array_parser-1.0")}" G @@ -284,17 +388,15 @@ RSpec.describe "bundle install with specific platforms" do specs: PLATFORMS - java - #{lockfile_platforms} + #{lockfile_platforms("java")} DEPENDENCIES 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 @@ -304,12 +406,12 @@ RSpec.describe "bundle install with specific platforms" do simulate_platform "x86_64-darwin-15" do setup_multiplatform_gem_with_different_dependencies_per_platform install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "facter" 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") @@ -325,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 @@ -339,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" @@ -350,7 +452,7 @@ 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 @@ -358,14 +460,30 @@ RSpec.describe "bundle install with specific platforms" do end gemfile <<~G - source "#{file_uri_for(gem_repo2)}" + 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 + 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 lockfile <<~L GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: sorbet-static (0.5.6403-#{Bundler.local_platform}) @@ -376,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" @@ -389,13 +507,13 @@ RSpec.describe "bundle install with specific platforms" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "sorbet-static", "0.5.6433" G error_message = <<~ERROR.strip - Could not find gem 'sorbet-static (= 0.5.6433)' with platform 'arm64-darwin-21' in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally. + Could not find gem 'sorbet-static (= 0.5.6433)' with platform 'arm64-darwin-21' in rubygems repository https://gem.repo4/ or installed locally. The source contains the following gems matching 'sorbet-static (= 0.5.6433)': * sorbet-static-0.5.6433-universal-darwin-20 @@ -417,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" } @@ -425,7 +578,7 @@ RSpec.describe "bundle install with specific platforms" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "sorbet", "0.5.6433" G @@ -434,7 +587,7 @@ RSpec.describe "bundle install with specific platforms" do Could not find compatible versions Because every version of sorbet depends on sorbet-static = 0.5.6433 - and sorbet-static = 0.5.6433 could not be found in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally for any resolution platforms (arm64-darwin-21), + and sorbet-static = 0.5.6433 could not be found in rubygems repository https://gem.repo4/ or installed locally for any resolution platforms (arm64-darwin-21), sorbet cannot be used. So, because Gemfile depends on sorbet = 0.5.6433, version solving has failed. @@ -459,13 +612,13 @@ RSpec.describe "bundle install with specific platforms" do expect(err).to include(error_message).once end - it "does not generate a lockfile if RUBY platform is forced and some gem has no RUBY variant available" do + it "does not generate a lockfile if ruby platform is forced and some gem has no ruby variant available" do build_repo4 do build_gem("sorbet-static", "0.5.9889") {|s| s.platform = Gem::Platform.local } end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "sorbet-static", "0.5.9889" G @@ -473,22 +626,22 @@ RSpec.describe "bundle install with specific platforms" do bundle "lock", raise_on_error: false, env: { "BUNDLE_FORCE_RUBY_PLATFORM" => "true" } expect(err).to include <<~ERROR.rstrip - Could not find gem 'sorbet-static (= 0.5.9889)' with platform 'ruby' in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally. + Could not find gem 'sorbet-static (= 0.5.9889)' with platform 'ruby' in rubygems repository https://gem.repo4/ or installed locally. The source contains the following gems matching 'sorbet-static (= 0.5.9889)': * sorbet-static-0.5.9889-#{Gem::Platform.local} ERROR end - it "automatically fixes the lockfile if RUBY platform is locked and some gem has no RUBY variant available" do + it "automatically fixes the lockfile if ruby platform is locked and some gem has no ruby variant available" do build_repo4 do build_gem("sorbet-static-and-runtime", "0.5.10160") do |s| - s.add_runtime_dependency "sorbet", "= 0.5.10160" - s.add_runtime_dependency "sorbet-runtime", "= 0.5.10160" + s.add_dependency "sorbet", "= 0.5.10160" + s.add_dependency "sorbet-runtime", "= 0.5.10160" end build_gem("sorbet", "0.5.10160") do |s| - s.add_runtime_dependency "sorbet-static", "= 0.5.10160" + s.add_dependency "sorbet-static", "= 0.5.10160" end build_gem("sorbet-runtime", "0.5.10160") @@ -499,14 +652,14 @@ RSpec.describe "bundle install with specific platforms" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "sorbet-static-and-runtime" G lockfile <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: sorbet (0.5.10160) sorbet-static (= 0.5.10160) @@ -523,12 +676,12 @@ RSpec.describe "bundle install with specific platforms" do sorbet-static-and-runtime BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "update" - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum gem_repo4, "sorbet", "0.5.10160" c.checksum gem_repo4, "sorbet-runtime", "0.5.10160" c.checksum gem_repo4, "sorbet-static", "0.5.10160", Gem::Platform.local @@ -537,7 +690,7 @@ RSpec.describe "bundle install with specific platforms" do expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: sorbet (0.5.10160) sorbet-static (= 0.5.10160) @@ -554,11 +707,11 @@ 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 if both RUBY platform and a more specific platform are locked, and some gem has no RUBY variant available" do + it "automatically fixes the lockfile if both ruby platform and a more specific platform are locked, and some gem has no ruby variant available" do build_repo4 do build_gem "nokogiri", "1.12.0" build_gem "nokogiri", "1.12.0" do |s| @@ -577,21 +730,21 @@ RSpec.describe "bundle install with specific platforms" do simulate_platform "x86_64-darwin-22" do install_gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "nokogiri" gem "sorbet-static" G end - checksums = checksums_section_when_existing do |c| - c.no_checksum "nokogiri", "1.13.0", "x86_64-darwin" - c.no_checksum "sorbet-static", "0.5.10601", "x86_64-darwin" + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "nokogiri", "1.13.0", "x86_64-darwin" + c.checksum gem_repo4, "sorbet-static", "0.5.10601", "x86_64-darwin" end lockfile <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: nokogiri (1.12.0) nokogiri (1.12.0-x86_64-darwin) @@ -606,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 @@ -615,7 +768,7 @@ RSpec.describe "bundle install with specific platforms" do expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: nokogiri (1.13.0-x86_64-darwin) sorbet-static (0.5.10601-x86_64-darwin) @@ -628,19 +781,19 @@ RSpec.describe "bundle install with specific platforms" do sorbet-static #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end - it "automatically fixes the lockfile if only RUBY platform is locked and some gem has no RUBY variant available" do + it "automatically fixes the lockfile if only ruby platform is locked and some gem has no ruby variant available" do build_repo4 do build_gem("sorbet-static-and-runtime", "0.5.10160") do |s| - s.add_runtime_dependency "sorbet", "= 0.5.10160" - s.add_runtime_dependency "sorbet-runtime", "= 0.5.10160" + s.add_dependency "sorbet", "= 0.5.10160" + s.add_dependency "sorbet-runtime", "= 0.5.10160" end build_gem("sorbet", "0.5.10160") do |s| - s.add_runtime_dependency "sorbet-static", "= 0.5.10160" + s.add_dependency "sorbet-static", "= 0.5.10160" end build_gem("sorbet-runtime", "0.5.10160") @@ -651,14 +804,14 @@ RSpec.describe "bundle install with specific platforms" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "sorbet-static-and-runtime" G lockfile <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: sorbet (0.5.10160) sorbet-static (= 0.5.10160) @@ -675,12 +828,12 @@ RSpec.describe "bundle install with specific platforms" do sorbet-static-and-runtime BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "update" - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum gem_repo4, "sorbet", "0.5.10160" c.checksum gem_repo4, "sorbet-runtime", "0.5.10160" c.checksum gem_repo4, "sorbet-static", "0.5.10160", Gem::Platform.local @@ -689,7 +842,7 @@ RSpec.describe "bundle install with specific platforms" do expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: sorbet (0.5.10160) sorbet-static (= 0.5.10160) @@ -706,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 @@ -726,7 +953,7 @@ RSpec.describe "bundle install with specific platforms" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "nokogiri" gem "sorbet-static" @@ -734,7 +961,7 @@ RSpec.describe "bundle install with specific platforms" do lockfile <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: nokogiri (1.14.0-arm-linux) nokogiri (1.14.0-x86_64-linux) @@ -750,19 +977,19 @@ RSpec.describe "bundle install with specific platforms" do sorbet-static BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "update" - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum gem_repo4, "nokogiri", "1.14.0", "x86_64-linux" c.checksum gem_repo4, "sorbet-static", "0.5.10696", "x86_64-linux" end expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: nokogiri (1.14.0-x86_64-linux) sorbet-static (0.5.10696-x86_64-linux) @@ -775,7 +1002,7 @@ RSpec.describe "bundle install with specific platforms" do sorbet-static #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -794,12 +1021,12 @@ RSpec.describe "bundle install with specific platforms" do # Make sure sorbet-static-0.5.10549-universal-darwin-21 is installed install_gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "sorbet-static", "= 0.5.10549" G - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum gem_repo4, "sorbet-static", "0.5.10549", "universal-darwin-20" c.checksum gem_repo4, "sorbet-static", "0.5.10549", "universal-darwin-21" end @@ -807,7 +1034,7 @@ RSpec.describe "bundle install with specific platforms" do # Make sure the lockfile is missing sorbet-static-0.5.10549-universal-darwin-21 lockfile <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: sorbet-static (0.5.10549-universal-darwin-20) @@ -818,16 +1045,14 @@ RSpec.describe "bundle install with specific platforms" do sorbet-static (= 0.5.10549) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install" - checksums.no_checksum "sorbet-static", "0.5.10549", "universal-darwin-21" - expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: sorbet-static (0.5.10549-universal-darwin-20) sorbet-static (0.5.10549-universal-darwin-21) @@ -839,7 +1064,64 @@ RSpec.describe "bundle install with specific platforms" do sorbet-static (= 0.5.10549) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} + L + end + end + + it "automatically fixes the lockfile if locked only to ruby, and some locked specs don't meet locked dependencies" do + simulate_platform "x86_64-linux" do + build_repo4 do + build_gem("ibandit", "0.7.0") do |s| + s.add_dependency "i18n", "~> 0.7.0" + end + + build_gem("i18n", "0.7.0.beta1") + build_gem("i18n", "0.7.0") + end + + gemfile <<~G + source "https://gem.repo4" + + gem "ibandit", "~> 0.7.0" + G + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + i18n (0.7.0.beta1) + ibandit (0.7.0) + i18n (~> 0.7.0) + + PLATFORMS + ruby + + DEPENDENCIES + ibandit (~> 0.7.0) + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "lock --update i18n" + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + i18n (0.7.0) + ibandit (0.7.0) + i18n (~> 0.7.0) + + PLATFORMS + ruby + + DEPENDENCIES + ibandit (~> 0.7.0) + + BUNDLED WITH + #{Bundler::VERSION} L end end @@ -853,16 +1135,21 @@ RSpec.describe "bundle install with specific platforms" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "nokogiri" gem "tzinfo", "~> 1.2", platform: :#{not_local_tag} G + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "nokogiri", "1.13.8" + c.checksum gem_repo4, "nokogiri", "1.13.8", Gem::Platform.local + end + original_lockfile = <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: nokogiri (1.13.8) nokogiri (1.13.8-#{Gem::Platform.local}) @@ -873,64 +1160,85 @@ RSpec.describe "bundle install with specific platforms" do DEPENDENCIES nokogiri tzinfo (~> 1.2) - - CHECKSUMS - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L lockfile original_lockfile bundle "lock --update" - checksums = checksums_section_when_existing do |c| - c.no_checksum "nokogiri", "1.13.8" - c.no_checksum "nokogiri", "1.13.8", Gem::Platform.local + expect(lockfile).to eq(original_lockfile) + end + + it "does not remove ruby if gems for other platforms, and not present in the lockfile, exist in the Gemfile, and the lockfile only has ruby" do + build_repo4 do + build_gem "nokogiri", "1.13.8" + build_gem "nokogiri", "1.13.8" do |s| + s.platform = "arm64-darwin" + end end - updated_lockfile = <<~L + gemfile <<~G + source "https://gem.repo4" + + gem "nokogiri" + + gem "tzinfo", "~> 1.2", platforms: %i[windows jruby] + G + + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "nokogiri", "1.13.8" + end + + original_lockfile = <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: nokogiri (1.13.8) - nokogiri (1.13.8-#{Gem::Platform.local}) PLATFORMS - #{lockfile_platforms("ruby")} + ruby DEPENDENCIES nokogiri tzinfo (~> 1.2) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L - expect(lockfile).to eq(updated_lockfile) + lockfile original_lockfile + + simulate_platform "arm64-darwin-23" do + bundle "lock --update" + end + + expect(lockfile).to eq(original_lockfile) end it "does not remove ruby when adding a new gem to the Gemfile" do build_repo4 do build_gem "concurrent-ruby", "1.2.2" - build_gem "rack", "3.0.7" + build_gem "myrack", "3.0.7" end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "concurrent-ruby" - gem "rack" + gem "myrack" G - checksums = checksums_section_when_existing do |c| - c.no_checksum "concurrent-ruby", "1.2.2" - c.no_checksum "rack", "3.0.7" + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "concurrent-ruby", "1.2.2" + c.checksum gem_repo4, "myrack", "3.0.7" end lockfile <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: concurrent-ruby (1.2.2) @@ -941,37 +1249,35 @@ RSpec.describe "bundle install with specific platforms" do concurrent-ruby #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "lock" expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: concurrent-ruby (1.2.2) - rack (3.0.7) + 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 - rack + myrack #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end it "can fallback to a source gem when platform gems are incompatible with current ruby version" do setup_multiplatform_gem_with_source_gem - source = file_uri_for(gem_repo2) - gemfile <<~G - source "#{source}" + source "https://gem.repo2" gem "my-precompiled-gem" G @@ -981,7 +1287,7 @@ RSpec.describe "bundle install with specific platforms" do # - A source gem with compatible ruby version lockfile <<-L GEM - remote: #{source}/ + remote: https://gem.repo2/ specs: my-precompiled-gem (3.0.0) my-precompiled-gem (3.0.0-#{Bundler.local_platform}) @@ -994,13 +1300,13 @@ RSpec.describe "bundle install with specific platforms" do my-precompiled-gem BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle :install end - it "automatically fixes the lockfile if the specific platform is locked and we move to a newer ruby version for which a native package is not available" do + it "automatically adds the ruby variant to the lockfile if the specific platform is locked and we move to a newer ruby version for which a native package is not available" do # # Given an existing application using native gems (e.g., nokogiri) # And a lockfile generated with a stable ruby version @@ -1017,18 +1323,18 @@ RSpec.describe "bundle install with specific platforms" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "nokogiri", "1.14.0" G - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum gem_repo4, "nokogiri", "1.14.0", "x86_64-linux" end lockfile <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: nokogiri (1.14.0-x86_64-linux) @@ -1039,20 +1345,22 @@ RSpec.describe "bundle install with specific platforms" do nokogiri (= 1.14.0) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle :install - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum gem_repo4, "nokogiri", "1.14.0" + c.checksum gem_repo4, "nokogiri", "1.14.0", "x86_64-linux" end expect(lockfile).to eq(<<~L) GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: nokogiri (1.14.0) + nokogiri (1.14.0-x86_64-linux) PLATFORMS x86_64-linux @@ -1061,7 +1369,61 @@ RSpec.describe "bundle install with specific platforms" do nokogiri (= 1.14.0) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} + L + end + end + + it "automatically fixes the lockfile when only ruby platform locked, and adding a dependency with subdependencies not valid for ruby" do + simulate_platform "x86_64-linux" do + build_repo4 do + build_gem("sorbet", "0.5.10160") do |s| + s.add_dependency "sorbet-static", "= 0.5.10160" + end + + build_gem("sorbet-static", "0.5.10160") do |s| + s.platform = "x86_64-linux" + end + end + + gemfile <<~G + source "https://gem.repo4" + + gem "sorbet" + G + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + + PLATFORMS + ruby + + DEPENDENCIES + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "lock" + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + sorbet (0.5.10160) + sorbet-static (= 0.5.10160) + sorbet-static (0.5.10160-x86_64-linux) + + PLATFORMS + x86_64-linux + + DEPENDENCIES + sorbet + + BUNDLED WITH + #{Bundler::VERSION} L end end @@ -1077,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" @@ -1092,23 +1454,23 @@ RSpec.describe "bundle install with specific platforms" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "nokogiri" G bundle "lock" - checksums = checksums_section_when_existing do |c| - c.no_checksum "nokogiri", "1.14.0" - c.no_checksum "nokogiri", "1.14.0", "arm-linux" - c.no_checksum "nokogiri", "1.14.0", "x86_64-linux" + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "nokogiri", "1.14.0" + c.checksum gem_repo4, "nokogiri", "1.14.0", "arm-linux" + c.checksum gem_repo4, "nokogiri", "1.14.0", "x86_64-linux" end # locks all compatible platforms, excluding Java and Windows expect(lockfile).to eq(<<~L) GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: nokogiri (1.14.0) nokogiri (1.14.0-arm-linux) @@ -1123,11 +1485,11 @@ RSpec.describe "bundle install with specific platforms" do nokogiri #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "nokogiri" gem "sorbet-static" @@ -1138,13 +1500,13 @@ RSpec.describe "bundle install with specific platforms" do bundle "lock" checksums.delete "nokogiri", "arm-linux" - checksums.no_checksum "sorbet-static", "0.5.10696", "universal-darwin-22" - checksums.no_checksum "sorbet-static", "0.5.10696", "x86_64-linux" + checksums.checksum gem_repo4, "sorbet-static", "0.5.10696", "universal-darwin-22" + checksums.checksum gem_repo4, "sorbet-static", "0.5.10696", "x86_64-linux" # locks only platforms compatible with all gems in the bundle expect(lockfile).to eq(<<~L) GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: nokogiri (1.14.0) nokogiri (1.14.0-x86_64-linux) @@ -1160,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" @@ -1182,25 +1544,25 @@ RSpec.describe "bundle install with specific platforms" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "nokogiri" gem "sass-embedded" G - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum gem_repo4, "nokogiri", "1.15.5" - c.no_checksum "sass-embedded", "1.69.5" + c.checksum gem_repo4, "sass-embedded", "1.69.5" c.checksum gem_repo4, "sass-embedded", "1.69.5", "x86_64-linux-gnu" end simulate_platform "x86_64-linux" do - bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + bundle "install --verbose" # locks all compatible platforms, excluding Java and Windows expect(lockfile).to eq(<<~L) GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: nokogiri (1.15.5) sass-embedded (1.69.5) @@ -1215,7 +1577,7 @@ RSpec.describe "bundle install with specific platforms" do sass-embedded #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -1232,21 +1594,21 @@ RSpec.describe "bundle install with specific platforms" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "nokogiri" G - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum gem_repo4, "nokogiri", "1.15.5", "x86_64-linux" end simulate_platform "x86_64-linux" do - bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + bundle "install --verbose" expect(lockfile).to eq(<<~L) GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: nokogiri (1.15.5-x86_64-linux) @@ -1257,15 +1619,64 @@ RSpec.describe "bundle install with specific platforms" do nokogiri #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end - it "adds current musl platform" do + ["x86_64-linux", "x86_64-linux-musl"].each do |host_platform| + describe "on host platform #{host_platform}" do + it "adds current musl platform" do + build_repo4 do + build_gem "rcee_precompiled", "0.5.0" do |s| + s.platform = "x86_64-linux" + end + + build_gem "rcee_precompiled", "0.5.0" do |s| + s.platform = "x86_64-linux-musl" + end + end + + gemfile <<~G + source "https://gem.repo4" + + gem "rcee_precompiled", "0.5.0" + G + + simulate_platform host_platform do + bundle "lock" + + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "rcee_precompiled", "0.5.0", "x86_64-linux" + c.checksum gem_repo4, "rcee_precompiled", "0.5.0", "x86_64-linux-musl" + end + + expect(lockfile).to eq(<<~L) + GEM + remote: https://gem.repo4/ + specs: + rcee_precompiled (0.5.0-x86_64-linux) + rcee_precompiled (0.5.0-x86_64-linux-musl) + + PLATFORMS + x86_64-linux + x86_64-linux-musl + + DEPENDENCIES + rcee_precompiled (= 0.5.0) + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + end + end + + 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" + s.platform = "x86_64-linux-gnu" end build_gem "rcee_precompiled", "0.5.0" do |s| @@ -1274,106 +1685,247 @@ RSpec.describe "bundle install with specific platforms" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "rcee_precompiled", "0.5.0" G simulate_platform "x86_64-linux-musl" do - bundle "lock", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + bundle "lock" + + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "rcee_precompiled", "0.5.0", "x86_64-linux-gnu" + c.checksum gem_repo4, "rcee_precompiled", "0.5.0", "x86_64-linux-musl" + end expect(lockfile).to eq(<<~L) GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: - rcee_precompiled (0.5.0-x86_64-linux) + rcee_precompiled (0.5.0-x86_64-linux-gnu) rcee_precompiled (0.5.0-x86_64-linux-musl) PLATFORMS - x86_64-linux + x86_64-linux-gnu x86_64-linux-musl DEPENDENCIES rcee_precompiled (= 0.5.0) - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end - it "adds current musl platform, when there are also gnu variants", rubygems: ">= 3.3.21" do + it "does not add current platform if there's an equivalent less specific platform among the ones resolved" do build_repo4 do build_gem "rcee_precompiled", "0.5.0" do |s| - s.platform = "x86_64-linux-gnu" - end - - build_gem "rcee_precompiled", "0.5.0" do |s| - s.platform = "x86_64-linux-musl" + s.platform = "universal-darwin" end end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "rcee_precompiled", "0.5.0" G - simulate_platform "x86_64-linux-musl" do - bundle "lock", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + simulate_platform "x86_64-darwin-15" do + bundle "lock" + + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "rcee_precompiled", "0.5.0", "universal-darwin" + end expect(lockfile).to eq(<<~L) GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: - rcee_precompiled (0.5.0-x86_64-linux-gnu) - rcee_precompiled (0.5.0-x86_64-linux-musl) + rcee_precompiled (0.5.0-universal-darwin) PLATFORMS - x86_64-linux-gnu - x86_64-linux-musl + universal-darwin DEPENDENCIES rcee_precompiled (= 0.5.0) - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end - it "does not add current platform if there's an equivalent less specific platform among the ones resolved" do + it "does not re-resolve when a specific platform, but less specific than the current platform, is locked" do build_repo4 do - build_gem "rcee_precompiled", "0.5.0" do |s| - s.platform = "universal-darwin" + build_gem "nokogiri" + end + + gemfile <<~G + source "https://gem.repo4" + + gem "nokogiri" + G + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + nokogiri (1.0) + + PLATFORMS + arm64-darwin + + DEPENDENCIES + nokogiri! + + BUNDLED WITH + #{Bundler::VERSION} + L + + simulate_platform "arm64-darwin-23" do + bundle "install --verbose" + + expect(out).to include("Found no changes, using resolution from the lockfile") + end + end + + it "does not remove generic platform gems locked for a specific platform from lockfile when unlocking an unrelated gem" do + build_repo4 do + build_gem "ffi" + + build_gem "ffi" do |s| + s.platform = "x86_64-linux" end + + build_gem "nokogiri" end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" - gem "rcee_precompiled", "0.5.0" + gem "ffi" + gem "nokogiri" G - simulate_platform "x86_64-darwin-15" do - bundle "lock", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + original_lockfile = <<~L + GEM + remote: https://gem.repo4/ + specs: + ffi (1.0) + nokogiri (1.0) - expect(lockfile).to eq(<<~L) - GEM - remote: #{file_uri_for(gem_repo4)}/ - specs: - rcee_precompiled (0.5.0-universal-darwin) + PLATFORMS + x86_64-linux - PLATFORMS - universal-darwin + DEPENDENCIES + ffi + nokogiri - DEPENDENCIES - rcee_precompiled (= 0.5.0) + BUNDLED WITH + #{Bundler::VERSION} + L - BUNDLED WITH - #{Bundler::VERSION} - L + lockfile original_lockfile + + simulate_platform "x86_64-linux" do + bundle "lock --update nokogiri" + + expect(lockfile).to eq(original_lockfile) + end + end + + it "does not remove generic platform gems locked for a specific platform from lockfile when unlocking an unrelated gem, and variants for other platform also locked" do + build_repo4 do + build_gem "ffi" + + build_gem "ffi" do |s| + s.platform = "x86_64-linux" + end + + build_gem "ffi" do |s| + s.platform = "java" + end + + build_gem "nokogiri" end + + gemfile <<~G + source "https://gem.repo4" + + gem "ffi" + gem "nokogiri" + G + + original_lockfile = <<~L + GEM + remote: https://gem.repo4/ + specs: + ffi (1.0) + ffi (1.0-java) + nokogiri (1.0) + + PLATFORMS + java + x86_64-linux + + DEPENDENCIES + ffi + nokogiri + + BUNDLED WITH + #{Bundler::VERSION} + L + + lockfile original_lockfile + + simulate_platform "x86_64-linux" do + bundle "lock --update nokogiri" + + expect(lockfile).to eq(original_lockfile) + end + end + + it "does not remove platform specific gems from lockfile when using a ruby version that does not match their ruby requirements, since they may be useful in other rubies" do + build_repo4 do + build_gem("google-protobuf", "3.25.5") + build_gem("google-protobuf", "3.25.5") do |s| + s.required_ruby_version = "< #{current_ruby_minor}.dev" + s.platform = "x86_64-linux" + end + end + + gemfile <<~G + source "https://gem.repo4" + + gem "google-protobuf", "~> 3.0" + G + + original_lockfile = <<~L + GEM + remote: https://gem.repo4/ + specs: + google-protobuf (3.25.5) + google-protobuf (3.25.5-x86_64-linux) + + PLATFORMS + ruby + x86_64-linux + + DEPENDENCIES + google-protobuf (~> 3.0) + + BUNDLED WITH + #{Bundler::VERSION} + L + + lockfile original_lockfile + + simulate_platform "x86_64-linux" do + bundle "lock --update" + end + + expect(lockfile).to eq(original_lockfile) end private @@ -1382,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" } @@ -1401,7 +1953,7 @@ RSpec.describe "bundle install with specific platforms" do build_gem("facter", "2.4.6") build_gem("facter", "2.4.6") do |s| s.platform = "universal-darwin" - s.add_runtime_dependency "CFPropertyList" + s.add_dependency "CFPropertyList" end build_gem("CFPropertyList") end diff --git a/spec/bundler/install/gemfile_spec.rb b/spec/bundler/install/gemfile_spec.rb index 158645d3eb..83875a3d0e 100644 --- a/spec/bundler/install/gemfile_spec.rb +++ b/spec/bundler/install/gemfile_spec.rb @@ -4,7 +4,7 @@ RSpec.describe "bundle install" do context "with duplicated gems" do it "will display a warning" do install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'rails', '~> 4.0.0' gem 'rails', '~> 4.0.0' @@ -16,8 +16,8 @@ RSpec.describe "bundle install" do context "with --gemfile" do it "finds the gemfile" do gemfile bundled_app("NotGemfile"), <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' G bundle :install, gemfile: bundled_app("NotGemfile") @@ -25,51 +25,125 @@ RSpec.describe "bundle install" do # Specify BUNDLE_GEMFILE for `the_bundle` # to retrieve the proper Gemfile ENV["BUNDLE_GEMFILE"] = "NotGemfile" - expect(the_bundle).to include_gems "rack 1.0.0" + 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 before do gemfile bundled_app("NotGemfile"), <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + 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" bundle "list" - expect(out).to include("rack (1.0.0)") + expect(out).to include("myrack (1.0.0)") end it "uses the gemfile while in a subdirectory" do bundled_app("subdir").mkpath bundle "install", dir: bundled_app("subdir") bundle "list", dir: bundled_app("subdir") - expect(out).to include("rack (1.0.0)") + expect(out).to include("myrack (1.0.0)") end end - context "with deprecated features" do - it "reports that lib is an invalid option" do - gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + it "reports that lib is an invalid option" do + gemfile <<-G + source "https://gem.repo1" - gem "rack", :lib => "rack" - 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 + + 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 + let(:bundler_bug) do + create_file("bundler_bug.rb", <<~RUBY) + require "bundler" + + module Bundler + class Dsl + def source(source, *args, &blk) + nil.name + end + end + end + RUBY + + bundled_app("bundler_bug.rb").to_s + end + + it "shows culprit file and line" do + skip "ruby-core test setup has always \"lib\" in $LOAD_PATH so `require \"bundler\"` always activates the local version rather than using RubyGems gem activation stuff, causing conflicts" if ruby_core? - bundle :install, raise_on_error: false - expect(err).to match(/You passed :lib as an option for gem 'rack', but it is invalid/) + install_gemfile "source 'https://gem.repo1'", requires: [bundler_bug], artifice: nil, raise_on_error: false + expect(err).to include("bundler_bug.rb:6") end end context "with engine specified in symbol", :jruby_only do it "does not raise any error parsing Gemfile" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" ruby "#{RUBY_VERSION}", :engine => :jruby, :engine_version => "#{RUBY_ENGINE_VERSION}" G @@ -78,19 +152,19 @@ RSpec.describe "bundle install" do it "installation succeeds" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" ruby "#{RUBY_VERSION}", :engine => :jruby, :engine_version => "#{RUBY_ENGINE_VERSION}" - gem "rack" + gem "myrack" G - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end end context "with a Gemfile containing non-US-ASCII characters" do it "reads the Gemfile with the UTF-8 encoding by default" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" str = "Il était une fois ..." puts "The source encoding is: " + str.encoding.name @@ -105,7 +179,7 @@ RSpec.describe "bundle install" do # NOTE: This works thanks to #eval interpreting the magic encoding comment install_gemfile <<-G # encoding: iso-8859-1 - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" str = "Il #{"\xE9".dup.force_encoding("binary")}tait une fois ..." puts "The source encoding is: " + str.encoding.name diff --git a/spec/bundler/install/gems/compact_index_spec.rb b/spec/bundler/install/gems/compact_index_spec.rb index 50add8743b..9db73b84b5 100644 --- a/spec/bundler/install/gems/compact_index_spec.rb +++ b/spec/bundler/install/gems/compact_index_spec.rb @@ -7,12 +7,27 @@ RSpec.describe "compact index api" do it "should use the API" do gemfile <<-G source "#{source_uri}" - gem "rack" + gem "myrack" G bundle :install, artifice: "compact_index" expect(out).to include("Fetching gem metadata from #{source_uri}") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" + end + + it "has a debug mode" do + gemfile <<-G + source "#{source_uri}" + gem "myrack" + G + + bundle :install, artifice: "compact_index", env: { "DEBUG_COMPACT_INDEX" => "true" } + expect(out).to include("Fetching gem metadata from #{source_uri}") + expect(err).to include("[Bundler::CompactIndexClient] available?") + expect(err).to include("[Bundler::CompactIndexClient] fetching versions") + expect(err).to include("[Bundler::CompactIndexClient] info(myrack)") + expect(err).to include("[Bundler::CompactIndexClient] fetching info/myrack") + expect(the_bundle).to include_gems "myrack 1.0.0" end it "should URI encode gem names" do @@ -45,22 +60,22 @@ RSpec.describe "compact index api" do it "should handle case sensitivity conflicts" do build_repo4(build_compact_index: false) do - build_gem "rack", "1.0" do |s| - s.add_runtime_dependency("Rack", "0.1") + build_gem "myrack", "1.0" do |s| + s.add_dependency("Myrack", "0.1") end - build_gem "Rack", "0.1" + build_gem "Myrack", "0.1" end install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } source "#{source_uri}" - gem "rack", "1.0" - gem "Rack", "0.1" + gem "myrack", "1.0" + gem "Myrack", "0.1" G # can't use `include_gems` here since the `require` will conflict on a # case-insensitive FS - run "Bundler.require; puts Gem.loaded_specs.values_at('rack', 'Rack').map(&:full_name)" - expect(out).to eq("rack-1.0\nRack-0.1") + run "Bundler.require; puts Gem.loaded_specs.values_at('myrack', 'Myrack').map(&:full_name)" + expect(out).to eq("myrack-1.0\nMyrack-0.1") end it "should handle multiple gem dependencies on the same gem" do @@ -76,15 +91,14 @@ RSpec.describe "compact index api" do it "should use the endpoint when using deployment mode" do gemfile <<-G source "#{source_uri}" - gem "rack" + gem "myrack" 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 "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "handles git dependencies that are in rubygems" do @@ -95,7 +109,7 @@ RSpec.describe "compact index api" do gemfile <<-G source "#{source_uri}" - git "#{file_uri_for(lib_path("foo-1.0"))}" do + git "#{lib_path("foo-1.0")}" do gem 'foo' end G @@ -113,12 +127,12 @@ RSpec.describe "compact index api" do gemfile <<-G source "#{source_uri}" - gem 'foo', :git => "#{file_uri_for(lib_path("foo-1.0"))}" + gem 'foo', :git => "#{lib_path("foo-1.0")}" 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("rails 2.3.2") @@ -128,11 +142,11 @@ RSpec.describe "compact index api" do build_git "foo" gemfile <<-G source "#{source_uri}" - gem 'foo', :git => "#{file_uri_for(lib_path("foo-1.0"))}" + gem 'foo', :git => "#{lib_path("foo-1.0")}" 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") @@ -141,35 +155,34 @@ RSpec.describe "compact index api" do it "falls back when the API URL returns 403 Forbidden" do gemfile <<-G source "#{source_uri}" - gem "rack" + gem "myrack" G bundle :install, verbose: true, artifice: "compact_index_forbidden" expect(out).to include("Fetching gem metadata from #{source_uri}") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "falls back when the versions endpoint has a checksum mismatch" do gemfile <<-G source "#{source_uri}" - gem "rack" + gem "myrack" G bundle :install, verbose: true, artifice: "compact_index_checksum_mismatch" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(out).to include("The checksum of /versions does not match the checksum provided by the server!") - expect(out).to include('Calculated checksums {"sha-256"=>"8KfZiM/fszVkqhP/m5s9lvE6M9xKu4I1bU4Izddp5Ms="} did not match expected {"sha-256"=>"ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0="}') - expect(the_bundle).to include_gems "rack 1.0.0" + expect(out).to include("Calculated checksums #{{ "sha-256" => "8KfZiM/fszVkqhP/m5s9lvE6M9xKu4I1bU4Izddp5Ms=" }.inspect} did not match expected #{{ "sha-256" => "ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0=" }.inspect}") + expect(the_bundle).to include_gems "myrack 1.0.0" end it "shows proper path when permission errors happen", :permissions do gemfile <<-G source "#{source_uri}" - gem "rack" + 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 @@ -188,28 +201,28 @@ RSpec.describe "compact index api" do gemfile <<-G source "#{source_uri}" - gem "rack" + gem "myrack" G bundle :install, artifice: "compact_index" expect(out).to include("Fetching gem metadata from #{source_uri}") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "handles host redirects" do gemfile <<-G source "#{source_uri}" - gem "rack" + gem "myrack" G bundle :install, artifice: "compact_index_host_redirect" - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "handles host redirects without Gem::Net::HTTP::Persistent" do gemfile <<-G source "#{source_uri}" - gem "rack" + gem "myrack" G FileUtils.mkdir_p lib_path @@ -227,13 +240,13 @@ RSpec.describe "compact index api" do bundle :install, artifice: "compact_index_host_redirect", requires: [lib_path("disable_net_http_persistent.rb")] expect(out).to_not match(/Too many redirects/) - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "times out when Bundler::Fetcher redirects too much" do gemfile <<-G source "#{source_uri}" - gem "rack" + gem "myrack" G bundle :install, artifice: "compact_index_redirects", raise_on_error: false @@ -244,23 +257,23 @@ RSpec.describe "compact index api" do it "should use the modern index for install" do gemfile <<-G source "#{source_uri}" - gem "rack" + gem "myrack" G bundle "install --full-index", artifice: "compact_index" expect(out).to include("Fetching source index from #{source_uri}") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "should use the modern index for update" do gemfile <<-G source "#{source_uri}" - gem "rack" + gem "myrack" G bundle "update --full-index", artifice: "compact_index", all: true expect(out).to include("Fetching source index from #{source_uri}") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end end @@ -288,45 +301,27 @@ RSpec.describe "compact index api" do end end - system_gems %w[rack-1.0.0 thin-1.0 net_a-1.0], gem_repo: gem_repo2 - bundle "config set --local path.system true" + system_gems %w[myrack-1.0.0 thin-1.0 net_a-1.0], gem_repo: gem_repo2 + bundle_config "path.system true" ENV["BUNDLER_SPEC_ALL_REQUESTS"] = <<~EOS.strip #{source_uri}/versions - #{source_uri}/info/rack + #{source_uri}/info/myrack EOS install_gemfile <<-G, artifice: "compact_index", verbose: true, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } source "#{source_uri}" - gem "rack" + 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 @@ -342,31 +337,33 @@ RSpec.describe "compact index api" do it "fetches gem versions even when those gems are already installed" do gemfile <<-G source "#{source_uri}" - gem "rack", "1.0.0" + gem "myrack", "1.0.0" G bundle :install, artifice: "compact_index_extra_api" - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" build_repo4 do - build_gem "rack", "1.2" do |s| - s.executables = "rackup" + build_gem "myrack", "1.2" do |s| + s.executables = "myrackup" end end gemfile <<-G source "#{source_uri}" do; end source "#{source_uri}/extra" - gem "rack", "1.2" + gem "myrack", "1.2" G bundle :install, artifice: "compact_index_extra_api" - expect(the_bundle).to include_gems "rack 1.2" + 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| @@ -376,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 @@ -391,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 @@ -414,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" @@ -434,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" @@ -458,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 - 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 + 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 @@ -494,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 @@ -516,59 +493,25 @@ 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 "rack" - G - - bundle "install --binstubs", artifice: "compact_index" - - gembin "rackup" - expect(out).to eq("1.0.0") - end - - it "installs the bins when using --path and uses autoclean", bundler: "< 3" do - gemfile <<-G - source "#{source_uri}" - gem "rack" - G - - bundle "install --path vendor/bundle", artifice: "compact_index" - - expect(vendored_gems("bin/rackup")).to exist - end - - it "installs the bins when using --path and uses bundle clean", bundler: "< 3" do - gemfile <<-G - source "#{source_uri}" - gem "rack" - G - - bundle "install --path vendor/bundle --no-clean", artifice: "compact_index" - - expect(vendored_gems("bin/rackup")).to exist - end - it "prints post_install_messages" do gemfile <<-G source "#{source_uri}" - gem 'rack-obama' + gem 'myrack-obama' G bundle :install, artifice: "compact_index" - expect(out).to include("Post-install message from rack:") + expect(out).to include("Post-install message from myrack:") end it "should display the post install message for a dependency" do gemfile <<-G source "#{source_uri}" - gem 'rack_middleware' + gem 'myrack_middleware' G bundle :install, artifice: "compact_index" - expect(out).to include("Post-install message from rack:") - expect(out).to include("Rack's post install message") + expect(out).to include("Post-install message from myrack:") + expect(out).to include("Myrack's post install message") end context "when using basic authentication" do @@ -585,53 +528,40 @@ RSpec.describe "compact index api" do it "passes basic authentication details and strips out creds" do gemfile <<-G source "#{basic_auth_source_uri}" - gem "rack" + gem "myrack" G bundle :install, artifice: "compact_index_basic_authentication" expect(out).not_to include("#{user}:#{password}") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "passes basic authentication details and strips out creds also in verbose mode" do gemfile <<-G source "#{basic_auth_source_uri}" - gem "rack" + gem "myrack" G bundle :install, verbose: true, artifice: "compact_index_basic_authentication" expect(out).not_to include("#{user}:#{password}") - expect(the_bundle).to include_gems "rack 1.0.0" - end - - it "strips http basic auth creds when warning about ambiguous sources", bundler: "< 3" do - gemfile <<-G - source "#{basic_auth_source_uri}" - source "#{file_uri_for(gem_repo1)}" - gem "rack" - G - - bundle :install, artifice: "compact_index_basic_authentication" - expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") - expect(err).not_to include("#{user}:#{password}") - expect(the_bundle).to include_gems "rack 1.0.0" + 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}" - gem "rack" + gem "myrack" G bundle :install, artifice: "compact_index_creds_diff_host" - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end describe "with authentication details in bundle config" do before do gemfile <<-G source "#{source_uri}" - gem "rack" + gem "myrack" G end @@ -641,7 +571,7 @@ RSpec.describe "compact index api" do bundle :install, artifice: "compact_index_strict_basic_authentication" expect(out).to include("Fetching gem metadata from #{source_uri}") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "reads authentication details by full url from bundle config" do @@ -651,26 +581,26 @@ RSpec.describe "compact index api" do bundle :install, artifice: "compact_index_strict_basic_authentication" expect(out).to include("Fetching gem metadata from #{source_uri}") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "should use the API" do bundle "config set #{source_hostname} #{user}:#{password}" bundle :install, artifice: "compact_index_strict_basic_authentication" expect(out).to include("Fetching gem metadata from #{source_uri}") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "prefers auth supplied in the source uri" do gemfile <<-G source "#{basic_auth_source_uri}" - gem "rack" + gem "myrack" G bundle "config set #{source_hostname} otheruser:wrong" bundle :install, artifice: "compact_index_strict_basic_authentication" - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "shows instructions if auth is not provided for the source" do @@ -701,11 +631,11 @@ RSpec.describe "compact index api" do it "passes basic authentication details" do gemfile <<-G source "#{basic_auth_source_uri}" - gem "rack" + gem "myrack" G bundle :install, artifice: "compact_index_basic_authentication" - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end end end @@ -723,14 +653,14 @@ RSpec.describe "compact index api" do end end - it "explains what to do to get it" do + it "explains what to do to get it, and includes original error" do gemfile <<-G source "#{source_uri.gsub(/http/, "https")}" - gem "rack" + gem "myrack" G - bundle :install, env: { "RUBYOPT" => opt_add("-I#{bundled_app("broken_ssl")}", ENV["RUBYOPT"]) }, raise_on_error: false - expect(err).to include("OpenSSL") + 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 @@ -746,7 +676,7 @@ RSpec.describe "compact index api" do end source "#{source_uri.gsub(/http/, "https")}" - gem "rack" + gem "myrack" G bundle :install, raise_on_error: false @@ -763,26 +693,25 @@ RSpec.describe "compact index api" do begin gemfile <<-G source "#{source_uri}" - gem 'rack' + gem 'myrack' G 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) gemfile <<-G source "#{source_uri}" - gem 'rack', '0.9.1' + gem 'myrack', '0.9.1' G # Initial install creates the cached versions file and etag file @@ -794,20 +723,20 @@ RSpec.describe "compact index api" do # Update the Gemfile so we can check subsequent install was successful gemfile <<-G source "#{source_uri}" - gem 'rack', '1.0.0' + gem 'myrack', '1.0.0' G # Second install should match etag bundle :install, artifice: "compact_index_etag_match" expect(versions_etag.binread).to eq(previous_content) - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "performs full update when range is ignored" do gemfile <<-G source "#{source_uri}" - gem 'rack', '0.9.1' + gem 'myrack', '0.9.1' G # Initial install creates the cached versions file and etag file @@ -815,11 +744,10 @@ RSpec.describe "compact index api" do gemfile <<-G source "#{source_uri}" - gem 'rack', '1.0.0' + 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, @@ -828,41 +756,40 @@ RSpec.describe "compact index api" do bundle :install, artifice: "compact_index_range_ignored" - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "performs partial update with a non-empty range" do build_repo4 do - build_gem "rack", "0.9.1" + build_gem "myrack", "0.9.1" end # Initial install creates the cached versions file install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } source "#{source_uri}" - gem 'rack', '0.9.1' + gem 'myrack', '0.9.1' G - update_repo4 do - build_gem "rack", "1.0.0" + build_repo4 do + build_gem "myrack", "1.0.0" end install_gemfile <<-G, artifice: "compact_index_partial_update", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } source "#{source_uri}" - gem 'rack', '1.0.0' + gem 'myrack', '1.0.0' G - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "performs partial update while local cache is updated by another process" do gemfile <<-G source "#{source_uri}" - gem 'rack' + gem 'myrack' 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 @@ -871,92 +798,92 @@ RSpec.describe "compact index api" do bundle :install, artifice: "compact_index_concurrent_download" expect(versions.read).to start_with("created_at") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "performs a partial update that fails digest check, then a full update" do build_repo4 do - build_gem "rack", "0.9.1" + build_gem "myrack", "0.9.1" end install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } source "#{source_uri}" - gem 'rack', '0.9.1' + gem 'myrack', '0.9.1' G - update_repo4 do - build_gem "rack", "1.0.0" + build_repo4 do + build_gem "myrack", "1.0.0" end install_gemfile <<-G, artifice: "compact_index_partial_update_bad_digest", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } source "#{source_uri}" - gem 'rack', '1.0.0' + gem 'myrack', '1.0.0' G - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "performs full update if server endpoints serve partial content responses but don't have incremental content and provide no digest" do build_repo4 do - build_gem "rack", "0.9.1" + build_gem "myrack", "0.9.1" end install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } source "#{source_uri}" - gem 'rack', '0.9.1' + gem 'myrack', '0.9.1' G - update_repo4 do - build_gem "rack", "1.0.0" + build_repo4 do + build_gem "myrack", "1.0.0" end install_gemfile <<-G, artifice: "compact_index_partial_update_no_digest_not_incremental", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } source "#{source_uri}" - gem 'rack', '1.0.0' + gem 'myrack', '1.0.0' G - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "performs full update of compact index info cache if range is not satisfiable" do gemfile <<-G source "#{source_uri}" - gem 'rack', '0.9.1' + gem 'myrack', '0.9.1' G bundle :install, artifice: "compact_index" + 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. - rake_info_etag_path = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index", - "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "info-etags", "rack-11690b09f16021ff06a6857d784a1870") - File.unlink(rake_info_etag_path) if File.exist?(rake_info_etag_path) + myrack_info_etag_path = File.join(cache_path, "info-etags", "myrack-92f3313ce5721296f14445c3a6b9c073") + File.unlink(myrack_info_etag_path) if File.exist?(myrack_info_etag_path) - rake_info_path = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index", - "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "info", "rack") - expected_rack_info_content = File.read(rake_info_path) + myrack_info_path = File.join(cache_path, "info", "myrack") + expected_myrack_info_content = File.read(myrack_info_path) # Modify the cache files to make the range not satisfiable - File.open(rake_info_path, "a") {|f| f << "0.9.2 |checksum:c55b525b421fd833a93171ad3d7f04528ca8e87d99ac273f8933038942a5888c" } + File.open(myrack_info_path, "a") {|f| f << "0.9.2 |checksum:c55b525b421fd833a93171ad3d7f04528ca8e87d99ac273f8933038942a5888c" } # Update the Gemfile so the next install does its normal things gemfile <<-G source "#{source_uri}" - gem 'rack', '1.0.0' + gem 'myrack', '1.0.0' G # The cache files now being longer means the requested range is going to be not satisfiable # Bundler must end up requesting the whole file to fix things up. bundle :install, artifice: "compact_index_range_not_satisfiable" - resulting_rack_info_content = File.read(rake_info_path) + resulting_myrack_info_content = File.read(myrack_info_path) - expect(resulting_rack_info_content).to eq(expected_rack_info_content) + expect(resulting_myrack_info_content).to eq(expected_myrack_info_content) end it "fails gracefully when the source URI has an invalid scheme" do install_gemfile <<-G, raise_on_error: false source "htps://rubygems.org" - gem "rack" + gem "myrack" G expect(exitstatus).to eq(15) expect(err).to end_with(<<-E.strip) @@ -970,7 +897,7 @@ RSpec.describe "compact index api" do GEM remote: #{source_uri} specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS ruby @@ -978,40 +905,36 @@ RSpec.describe "compact index api" do DEPENDENCIES #{checksums_section} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end it "handles checksums from the server in base64" do - api_checksum = checksum_digest(gem_repo1, "rack", "1.0.0") - rack_checksum = [[api_checksum].pack("H*")].pack("m0") - install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_RACK_CHECKSUM" => rack_checksum } + api_checksum = checksum_digest(gem_repo1, "myrack", "1.0.0") + myrack_checksum = [[api_checksum].pack("H*")].pack("m0") + install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_MYRACK_CHECKSUM" => myrack_checksum } source "#{source_uri}" - gem "rack" + gem "myrack" G expect(out).to include("Fetching gem metadata from #{source_uri}") - expect(the_bundle).to include_gems("rack 1.0.0") + expect(the_bundle).to include_gems("myrack 1.0.0") end it "raises when the checksum does not match" do install_gemfile <<-G, artifice: "compact_index_wrong_gem_checksum", raise_on_error: false source "#{source_uri}" - gem "rack" + gem "myrack" G - gem_path = if Bundler.feature_flag.global_gem_cache? - default_cache_path.dirname.join("cache", "gems", "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "rack-1.0.0.gem") - else - default_cache_path.dirname.join("rack-1.0.0.gem") - end + gem_path = default_cache_path.dirname.join("myrack-1.0.0.gem") expect(exitstatus).to eq(37) expect(err).to eq <<~E.strip Bundler found mismatched checksums. This is a potential security risk. - rack (1.0.0) sha256=2222222222222222222222222222222222222222222222222222222222222222 + myrack (1.0.0) sha256=2222222222222222222222222222222222222222222222222222222222222222 from the API at http://localgemserver.test/ - #{checksum_to_lock(gem_repo1, "rack", "1.0.0")} + #{checksum_to_lock(gem_repo1, "myrack", "1.0.0")} from the gem at #{gem_path} If you trust the API at http://localgemserver.test/, to resolve this issue you can: @@ -1024,19 +947,19 @@ RSpec.describe "compact index api" do end it "raises when the checksum is the wrong length" do - install_gemfile <<-G, artifice: "compact_index_wrong_gem_checksum", env: { "BUNDLER_SPEC_RACK_CHECKSUM" => "checksum!", "DEBUG" => "1" }, verbose: true, raise_on_error: false + install_gemfile <<-G, artifice: "compact_index_wrong_gem_checksum", env: { "BUNDLER_SPEC_MYRACK_CHECKSUM" => "checksum!", "DEBUG" => "1" }, verbose: true, raise_on_error: false source "#{source_uri}" - gem "rack" + gem "myrack" G expect(exitstatus).to eq(14) - expect(err).to include('Invalid checksum for rack-0.9.1: "checksum!" is not a valid SHA256 hex or base64 digest') + expect(err).to include('Invalid checksum for myrack-0.9.1: "checksum!" is not a valid SHA256 hex or base64 digest') end it "does not raise when disable_checksum_validation is set" do - bundle "config set disable_checksum_validation true" + bundle_config "disable_checksum_validation true" install_gemfile <<-G, artifice: "compact_index_wrong_gem_checksum" source "#{source_uri}" - gem "rack" + gem "myrack" G end end @@ -1045,7 +968,7 @@ RSpec.describe "compact index api" do install_gemfile <<-G, artifice: "compact_index" File.umask(0000) source "#{source_uri}" - gem "rack" + gem "myrack" G end @@ -1059,24 +982,31 @@ RSpec.describe "compact index api" do Gem::Dependency.new("activerecord", "= 2.3.2"), Gem::Dependency.new("actionmailer", "= 2.3.2"), Gem::Dependency.new("activeresource", "= 2.3.2")] - expect(out).to include("rails-2.3.2 from rubygems remote at #{source_uri}/ has either corrupted API or lockfile dependencies") + expect(out).to include("rails-2.3.2 from rubygems remote at #{source_uri}/ has corrupted API dependencies") expect(err).to include(<<-E.strip) -Bundler::APIResponseMismatchError: Downloading rails-2.3.2 revealed dependencies not in the API or the lockfile (#{deps.map(&:to_s).join(", ")}). +Bundler::APIResponseMismatchError: Downloading rails-2.3.2 revealed dependencies not in the API (#{deps.map(&:to_s).join(", ")}). Running `bundle update rails` should fix the problem. E end it "does not duplicate specs in the lockfile when updating and a dependency is not installed" do install_gemfile <<-G, artifice: "compact_index" - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" source "#{source_uri}" do gem "rails" 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 new file mode 100644 index 0000000000..c7b0c537e4 --- /dev/null +++ b/spec/bundler/install/gems/dependency_api_fallback_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +RSpec.describe "gemcutter's dependency API" do + context "when Gemcutter API takes too long to respond" do + before do + bundle_config "timeout 1" + end + + it "times out and falls back on the modern index" do + install_gemfile <<-G, artifice: "endpoint_timeout" + source "https://gem.repo1" + gem "myrack" + G + + expect(out).to include("Fetching source index from https://gem.repo1/") + expect(the_bundle).to include_gems "myrack 1.0.0" + end + end +end diff --git a/spec/bundler/install/gems/dependency_api_spec.rb b/spec/bundler/install/gems/dependency_api_spec.rb index 35468b3a1c..32a1b98b6d 100644 --- a/spec/bundler/install/gems/dependency_api_spec.rb +++ b/spec/bundler/install/gems/dependency_api_spec.rb @@ -7,12 +7,12 @@ RSpec.describe "gemcutter's dependency API" do it "should use the API" do gemfile <<-G source "#{source_uri}" - gem "rack" + gem "myrack" G bundle :install, artifice: "endpoint" expect(out).to include("Fetching gem metadata from #{source_uri}") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "should URI encode gem names" do @@ -56,15 +56,14 @@ RSpec.describe "gemcutter's dependency API" do it "should use the endpoint when using deployment mode" do gemfile <<-G source "#{source_uri}" - gem "rack" + gem "myrack" 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 "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "handles git dependencies that are in rubygems" do @@ -75,7 +74,7 @@ RSpec.describe "gemcutter's dependency API" do gemfile <<-G source "#{source_uri}" - git "#{file_uri_for(lib_path("foo-1.0"))}" do + git "#{lib_path("foo-1.0")}" do gem 'foo' end G @@ -93,12 +92,12 @@ RSpec.describe "gemcutter's dependency API" do gemfile <<-G source "#{source_uri}" - gem 'foo', :git => "#{file_uri_for(lib_path("foo-1.0"))}" + gem 'foo', :git => "#{lib_path("foo-1.0")}" 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("rails 2.3.2") @@ -108,35 +107,35 @@ RSpec.describe "gemcutter's dependency API" do build_git "foo" gemfile <<-G source "#{source_uri}" - gem 'foo', :git => "#{file_uri_for(lib_path("foo-1.0"))}" + gem 'foo', :git => "#{lib_path("foo-1.0")}" 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 - - build_repo2 do - # The rcov gem is platform mswin32, but has no arch - build_gem "rcov" do |s| - s.platform = Gem::Platform.new([nil, "mswin32", nil]) - s.write "lib/rcov.rb", "RCOV = '1.0.0'" + simulate_platform "x86-mswin32" do + build_repo2 do + # The rcov gem is platform mswin32, but has no arch + build_gem "rcov" do |s| + s.platform = Gem::Platform.new([nil, "mswin32", nil]) + s.write "lib/rcov.rb", "RCOV = '1.0.0'" + end end - end - gemfile <<-G - source "#{source_uri}" - gem "rcov" - G + gemfile <<-G + source "#{source_uri}" + gem "rcov" + G - bundle :install, artifice: "windows", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } - expect(out).to include("Fetching source index from #{source_uri}") - expect(the_bundle).to include_gems "rcov 1.0.0" + bundle :install, artifice: "windows", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + expect(out).to include("Fetching source index from #{source_uri}") + expect(the_bundle).to include_gems "rcov 1.0.0" + end end it "falls back when hitting the Gemcutter Dependency Limit" do @@ -147,7 +146,7 @@ RSpec.describe "gemcutter's dependency API" do gem "actionmailer" gem "activeresource" gem "thin" - gem "rack" + gem "myrack" gem "rails" G bundle :install, artifice: "endpoint_fallback" @@ -160,7 +159,7 @@ RSpec.describe "gemcutter's dependency API" do "activeresource 2.3.2", "activesupport 2.3.2", "thin 1.0.0", - "rack 1.0.0", + "myrack 1.0.0", "rails 2.3.2" ) end @@ -168,39 +167,39 @@ RSpec.describe "gemcutter's dependency API" do it "falls back when Gemcutter API doesn't return proper Marshal format" do gemfile <<-G source "#{source_uri}" - gem "rack" + gem "myrack" G bundle :install, verbose: true, artifice: "endpoint_marshal_fail" expect(out).to include("could not fetch from the dependency API, trying the full index") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "falls back when the API URL returns 403 Forbidden" do gemfile <<-G source "#{source_uri}" - gem "rack" + gem "myrack" G bundle :install, verbose: true, artifice: "endpoint_api_forbidden" expect(out).to include("Fetching source index from #{source_uri}") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "handles host redirects" do gemfile <<-G source "#{source_uri}" - gem "rack" + gem "myrack" G bundle :install, artifice: "endpoint_host_redirect" - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "handles host redirects without Gem::Net::HTTP::Persistent" do gemfile <<-G source "#{source_uri}" - gem "rack" + gem "myrack" G FileUtils.mkdir_p lib_path @@ -218,13 +217,13 @@ RSpec.describe "gemcutter's dependency API" do bundle :install, artifice: "endpoint_host_redirect", requires: [lib_path("disable_net_http_persistent.rb")] expect(out).to_not match(/Too many redirects/) - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "timeouts when Bundler::Fetcher redirects too much" do gemfile <<-G source "#{source_uri}" - gem "rack" + gem "myrack" G bundle :install, artifice: "endpoint_redirect", raise_on_error: false @@ -235,50 +234,32 @@ RSpec.describe "gemcutter's dependency API" do it "should use the modern index for install" do gemfile <<-G source "#{source_uri}" - gem "rack" + gem "myrack" G bundle "install --full-index", artifice: "endpoint" expect(out).to include("Fetching source index from #{source_uri}") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "should use the modern index for update" do gemfile <<-G source "#{source_uri}" - gem "rack" + gem "myrack" G bundle "update --full-index", artifice: "endpoint", all: true expect(out).to include("Fetching source index from #{source_uri}") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end end - it "fetches again when more dependencies are found in subsequent sources", bundler: "< 3" do - build_repo2 do - build_gem "back_deps" do |s| - s.add_dependency "foo" - 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 + 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")] + FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")] end gemfile <<-G @@ -295,30 +276,32 @@ RSpec.describe "gemcutter's dependency API" do it "fetches gem versions even when those gems are already installed" do gemfile <<-G source "#{source_uri}" - gem "rack", "1.0.0" + gem "myrack", "1.0.0" G bundle :install, artifice: "endpoint_extra_api" build_repo4 do - build_gem "rack", "1.2" do |s| - s.executables = "rackup" + build_gem "myrack", "1.2" do |s| + s.executables = "myrackup" end end gemfile <<-G source "#{source_uri}" do; end source "#{source_uri}/extra" - gem "rack", "1.2" + gem "myrack", "1.2" G bundle :install, artifice: "endpoint_extra_api" - expect(the_bundle).to include_gems "rack 1.2" + 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 + 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: "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 - 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,59 +416,25 @@ 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 "rack" - G - - bundle "install --binstubs", artifice: "endpoint" - - gembin "rackup" - expect(out).to eq("1.0.0") - end - - it "installs the bins when using --path and uses autoclean", bundler: "< 3" do - gemfile <<-G - source "#{source_uri}" - gem "rack" - G - - bundle "install --path vendor/bundle", artifice: "endpoint" - - expect(vendored_gems("bin/rackup")).to exist - end - - it "installs the bins when using --path and uses bundle clean", bundler: "< 3" do - gemfile <<-G - source "#{source_uri}" - gem "rack" - G - - bundle "install --path vendor/bundle --no-clean", artifice: "endpoint" - - expect(vendored_gems("bin/rackup")).to exist - end - it "prints post_install_messages" do gemfile <<-G source "#{source_uri}" - gem 'rack-obama' + gem 'myrack-obama' G bundle :install, artifice: "endpoint" - expect(out).to include("Post-install message from rack:") + expect(out).to include("Post-install message from myrack:") end it "should display the post install message for a dependency" do gemfile <<-G source "#{source_uri}" - gem 'rack_middleware' + gem 'myrack_middleware' G bundle :install, artifice: "endpoint" - expect(out).to include("Post-install message from rack:") - expect(out).to include("Rack's post install message") + expect(out).to include("Post-install message from myrack:") + expect(out).to include("Myrack's post install message") end context "when using basic authentication" do @@ -541,74 +451,61 @@ RSpec.describe "gemcutter's dependency API" do it "passes basic authentication details and strips out creds" do gemfile <<-G source "#{basic_auth_source_uri}" - gem "rack" + gem "myrack" G bundle :install, artifice: "endpoint_basic_authentication" expect(out).not_to include("#{user}:#{password}") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "passes basic authentication details and strips out creds also in verbose mode" do gemfile <<-G source "#{basic_auth_source_uri}" - gem "rack" + gem "myrack" G bundle :install, verbose: true, artifice: "endpoint_basic_authentication" expect(out).not_to include("#{user}:#{password}") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "strips http basic authentication creds for modern index" do gemfile <<-G source "#{basic_auth_source_uri}" - gem "rack" + gem "myrack" G bundle :install, artifice: "endpoint_marshal_fail_basic_authentication" expect(out).not_to include("#{user}:#{password}") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "strips http basic auth creds when it can't reach the server" do gemfile <<-G source "#{basic_auth_source_uri}" - gem "rack" + gem "myrack" G bundle :install, artifice: "endpoint_500", raise_on_error: false expect(out).not_to include("#{user}:#{password}") end - it "strips http basic auth creds when warning about ambiguous sources", bundler: "< 3" do - gemfile <<-G - source "#{basic_auth_source_uri}" - source "#{file_uri_for(gem_repo1)}" - gem "rack" - G - - bundle :install, artifice: "endpoint_basic_authentication" - expect(err).to include("Warning: the gem 'rack' was found in multiple sources.") - expect(err).not_to include("#{user}:#{password}") - expect(the_bundle).to include_gems "rack 1.0.0" - end - it "does not pass the user / password to different hosts on redirect" do gemfile <<-G source "#{basic_auth_source_uri}" - gem "rack" + gem "myrack" G bundle :install, artifice: "endpoint_creds_diff_host" - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end describe "with host including dashes" do before do gemfile <<-G source "http://local-gemserver.test" - gem "rack" + gem "myrack" G end @@ -616,7 +513,7 @@ RSpec.describe "gemcutter's dependency API" do bundle :install, artifice: "endpoint_strict_basic_authentication", env: { "BUNDLE_LOCAL___GEMSERVER__TEST" => "#{user}:#{password}" } expect(out).to include("Fetching gem metadata from http://local-gemserver.test") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end end @@ -624,7 +521,7 @@ RSpec.describe "gemcutter's dependency API" do before do gemfile <<-G source "#{source_uri}" - gem "rack" + gem "myrack" G end @@ -634,7 +531,7 @@ RSpec.describe "gemcutter's dependency API" do bundle :install, artifice: "endpoint_strict_basic_authentication" expect(out).to include("Fetching gem metadata from #{source_uri}") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "reads authentication details by full url from bundle config" do @@ -644,26 +541,26 @@ RSpec.describe "gemcutter's dependency API" do bundle :install, artifice: "endpoint_strict_basic_authentication" expect(out).to include("Fetching gem metadata from #{source_uri}") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "should use the API" do bundle "config set #{source_hostname} #{user}:#{password}" bundle :install, artifice: "endpoint_strict_basic_authentication" expect(out).to include("Fetching gem metadata from #{source_uri}") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "prefers auth supplied in the source uri" do gemfile <<-G source "#{basic_auth_source_uri}" - gem "rack" + gem "myrack" G bundle "config set #{source_hostname} otheruser:wrong" bundle :install, artifice: "endpoint_strict_basic_authentication" - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "shows instructions if auth is not provided for the source" do @@ -685,11 +582,11 @@ RSpec.describe "gemcutter's dependency API" do it "passes basic authentication details" do gemfile <<-G source "#{basic_auth_source_uri}" - gem "rack" + gem "myrack" G bundle :install, artifice: "endpoint_basic_authentication" - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end end end @@ -707,14 +604,14 @@ RSpec.describe "gemcutter's dependency API" do end end - it "explains what to do to get it" do + it "explains what to do to get it, and includes original error" do gemfile <<-G source "#{source_uri.gsub(/http/, "https")}" - gem "rack" + gem "myrack" G - bundle :install, env: { "RUBYOPT" => opt_add("-I#{bundled_app("broken_ssl")}", ENV["RUBYOPT"]) }, raise_on_error: false - expect(err).to include("OpenSSL") + 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 @@ -730,7 +627,7 @@ RSpec.describe "gemcutter's dependency API" do end source "#{source_uri.gsub(/http/, "https")}" - gem "rack" + gem "myrack" G bundle :install, raise_on_error: false @@ -747,12 +644,12 @@ RSpec.describe "gemcutter's dependency API" do begin gemfile <<-G source "#{source_uri}" - gem 'rack' + gem 'myrack' G 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/env_spec.rb b/spec/bundler/install/gems/env_spec.rb index a6dfadcfc8..6d5aa456fe 100644 --- a/spec/bundler/install/gems/env_spec.rb +++ b/spec/bundler/install/gems/env_spec.rb @@ -4,104 +4,104 @@ RSpec.describe "bundle install with ENV conditionals" do describe "when just setting an ENV key as a string" do before :each do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" env "BUNDLER_TEST" do - gem "rack" + gem "myrack" end G end it "excludes the gems when the ENV variable is not set" do bundle :install - expect(the_bundle).not_to include_gems "rack" + expect(the_bundle).not_to include_gems "myrack" end it "includes the gems when the ENV variable is set" do ENV["BUNDLER_TEST"] = "1" bundle :install - expect(the_bundle).to include_gems "rack 1.0" + expect(the_bundle).to include_gems "myrack 1.0" end end describe "when just setting an ENV key as a symbol" do before :each do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" env :BUNDLER_TEST do - gem "rack" + gem "myrack" end G end it "excludes the gems when the ENV variable is not set" do bundle :install - expect(the_bundle).not_to include_gems "rack" + expect(the_bundle).not_to include_gems "myrack" end it "includes the gems when the ENV variable is set" do ENV["BUNDLER_TEST"] = "1" bundle :install - expect(the_bundle).to include_gems "rack 1.0" + expect(the_bundle).to include_gems "myrack 1.0" end end describe "when setting a string to match the env" do before :each do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" env "BUNDLER_TEST" => "foo" do - gem "rack" + gem "myrack" end G end it "excludes the gems when the ENV variable is not set" do bundle :install - expect(the_bundle).not_to include_gems "rack" + expect(the_bundle).not_to include_gems "myrack" end it "excludes the gems when the ENV variable is set but does not match the condition" do ENV["BUNDLER_TEST"] = "1" bundle :install - expect(the_bundle).not_to include_gems "rack" + expect(the_bundle).not_to include_gems "myrack" end it "includes the gems when the ENV variable is set and matches the condition" do ENV["BUNDLER_TEST"] = "foo" bundle :install - expect(the_bundle).to include_gems "rack 1.0" + expect(the_bundle).to include_gems "myrack 1.0" end end describe "when setting a regex to match the env" do before :each do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" env "BUNDLER_TEST" => /foo/ do - gem "rack" + gem "myrack" end G end it "excludes the gems when the ENV variable is not set" do bundle :install - expect(the_bundle).not_to include_gems "rack" + expect(the_bundle).not_to include_gems "myrack" end it "excludes the gems when the ENV variable is set but does not match the condition" do ENV["BUNDLER_TEST"] = "fo" bundle :install - expect(the_bundle).not_to include_gems "rack" + expect(the_bundle).not_to include_gems "myrack" end it "includes the gems when the ENV variable is set and matches the condition" do ENV["BUNDLER_TEST"] = "foobar" bundle :install - expect(the_bundle).to include_gems "rack 1.0" + expect(the_bundle).to include_gems "myrack 1.0" end end end diff --git a/spec/bundler/install/gems/flex_spec.rb b/spec/bundler/install/gems/flex_spec.rb index 8ef3984975..a30b53d6ad 100644 --- a/spec/bundler/install/gems/flex_spec.rb +++ b/spec/bundler/install/gems/flex_spec.rb @@ -3,30 +3,30 @@ RSpec.describe "bundle flex_install" do it "installs the gems as expected" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' G - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" expect(the_bundle).to be_locked end it "installs even when the lockfile is invalid" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' G - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" expect(the_bundle).to be_locked gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack', '1.0' + source "https://gem.repo1" + gem 'myrack', '1.0' G bundle :install - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" expect(the_bundle).to be_locked end @@ -34,19 +34,19 @@ RSpec.describe "bundle flex_install" do build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack-obama" + source "https://gem.repo2" + gem "myrack-obama" G - expect(the_bundle).to include_gems "rack 1.0.0", "rack-obama 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0", "myrack-obama 1.0.0" update_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack-obama", "1.0" + source "https://gem.repo2" + gem "myrack-obama", "1.0" G - expect(the_bundle).to include_gems "rack 1.0.0", "rack-obama 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0", "myrack-obama 1.0.0" end describe "adding new gems" do @@ -54,38 +54,38 @@ RSpec.describe "bundle flex_install" do build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem 'rack' + source "https://gem.repo2" + gem 'myrack' G update_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem 'rack' + source "https://gem.repo2" + gem 'myrack' gem 'activesupport', '2.3.5' G - expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.5" + expect(the_bundle).to include_gems "myrack 1.0.0", "activesupport 2.3.5" end it "keeps child dependencies pinned" do build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack-obama" + source "https://gem.repo2" + gem "myrack-obama" G update_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack-obama" + source "https://gem.repo2" + gem "myrack-obama" gem "thin" G - expect(the_bundle).to include_gems "rack 1.0.0", "rack-obama 1.0", "thin 1.0" + expect(the_bundle).to include_gems "myrack 1.0.0", "myrack-obama 1.0", "thin 1.0" end end @@ -93,43 +93,43 @@ RSpec.describe "bundle flex_install" do it "removes gems without changing the versions of remaining gems" do build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem 'rack' + source "https://gem.repo2" + gem 'myrack' gem 'activesupport', '2.3.5' G update_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem 'rack' + source "https://gem.repo2" + gem 'myrack' G - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" expect(the_bundle).not_to include_gems "activesupport 2.3.5" install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem 'rack' + source "https://gem.repo2" + gem 'myrack' gem 'activesupport', '2.3.2' G - expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.2" + expect(the_bundle).to include_gems "myrack 1.0.0", "activesupport 2.3.2" end it "removes top level dependencies when removed from the Gemfile while leaving other dependencies intact" do build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem 'rack' + source "https://gem.repo2" + gem 'myrack' gem 'activesupport', '2.3.5' G update_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem 'rack' + source "https://gem.repo2" + gem 'myrack' G expect(the_bundle).not_to include_gems "activesupport 2.3.5" @@ -138,21 +138,21 @@ RSpec.describe "bundle flex_install" do it "removes child dependencies" do build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem 'rack-obama' + source "https://gem.repo2" + gem 'myrack-obama' gem 'activesupport' G - expect(the_bundle).to include_gems "rack 1.0.0", "rack-obama 1.0.0", "activesupport 2.3.5" + expect(the_bundle).to include_gems "myrack 1.0.0", "myrack-obama 1.0.0", "activesupport 2.3.5" update_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem 'activesupport' G expect(the_bundle).to include_gems "activesupport 2.3.5" - expect(the_bundle).not_to include_gems "rack-obama", "rack" + expect(the_bundle).not_to include_gems "myrack-obama", "myrack" end end @@ -160,25 +160,25 @@ RSpec.describe "bundle flex_install" do before(:each) do build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack_middleware" + source "https://gem.repo2" + gem "myrack_middleware" G - expect(the_bundle).to include_gems "rack_middleware 1.0", "rack 0.9.1" + expect(the_bundle).to include_gems "myrack_middleware 1.0", "myrack 0.9.1" build_repo2 do - build_gem "rack-obama", "2.0" do |s| - s.add_dependency "rack", "=1.2" + build_gem "myrack-obama", "2.0" do |s| + s.add_dependency "myrack", "=1.2" end - build_gem "rack_middleware", "2.0" do |s| - s.add_dependency "rack", ">=1.0" + build_gem "myrack_middleware", "2.0" do |s| + s.add_dependency "myrack", ">=1.0" end end gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack-obama", "2.0" - gem "rack_middleware" + source "https://gem.repo2" + gem "myrack-obama", "2.0" + gem "myrack_middleware" G end @@ -187,19 +187,19 @@ RSpec.describe "bundle flex_install" do ruby <<-RUBY, raise_on_error: false require 'bundler/setup' RUBY - expect(err).to match(/could not find gem 'rack-obama/i) + expect(err).to match(/could not find gem 'myrack-obama/i) 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 - Because rack-obama >= 2.0 depends on rack = 1.2 - and rack = 1.2 could not be found in rubygems repository #{file_uri_for(gem_repo2)}/, cached gems or installed locally, - rack-obama >= 2.0 cannot be used. - So, because Gemfile depends on rack-obama = 2.0, + Because myrack-obama >= 2.0 depends on myrack = 1.2 + and myrack = 1.2 could not be found in rubygems repository https://gem.repo2/ or installed locally, + myrack-obama >= 2.0 cannot be used. + So, because Gemfile depends on myrack-obama = 2.0, version solving has failed. E @@ -208,15 +208,15 @@ 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 "rack-obama": + Bundler could not find compatible versions for gem "myrack-obama": In Gemfile: - rack-obama (= 2.0) + myrack-obama (= 2.0) E - bundle "update rack_middleware", retry: 0, raise_on_error: false + bundle "update myrack_middleware", retry: 0, raise_on_error: false expect(err).not_to end_with(bad_error) end end @@ -233,12 +233,12 @@ RSpec.describe "bundle flex_install" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "jekyll-feed", "~> 0.12" G gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "github-pages", "~> 226" gem "jekyll-feed", "~> 0.12" G @@ -253,48 +253,72 @@ RSpec.describe "bundle flex_install" do describe "subtler cases" do before :each do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" - gem "rack-obama" + source "https://gem.repo1" + gem "myrack" + gem "myrack-obama" G gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "0.9.1" - gem "rack-obama" + source "https://gem.repo1" + gem "myrack", "0.9.1" + gem "myrack-obama" G end it "should work when you install" do bundle "install" - checksums = checksums_section_when_existing do |c| - c.checksum gem_repo1, "rack", "0.9.1" - c.checksum gem_repo1, "rack-obama", "1.0" + 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: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: - rack (0.9.1) - rack-obama (1.0) - rack + myrack (0.9.1) + myrack-obama (1.0) + myrack PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack (= 0.9.1) - rack-obama + myrack (= 0.9.1) + myrack-obama #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end it "should work when you update" do - bundle "update rack" + 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 @@ -302,39 +326,39 @@ RSpec.describe "bundle flex_install" do it "updates the lockfile" do build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - source "#{file_uri_for(gem_repo2)}" do + source "https://gem.repo1" + source "https://gem.repo2" do end - gem "rack" + gem "myrack" G - checksums = checksums_section_when_existing do |c| - c.checksum gem_repo1, "rack", "1.0.0" + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo1, "myrack", "1.0.0" end expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: - rack (1.0.0) + myrack (1.0.0) GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack + myrack #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -344,17 +368,17 @@ RSpec.describe "bundle flex_install" do before(:each) do build_repo2 do build_gem "capybara", "0.3.9" do |s| - s.add_dependency "rack", ">= 1.0.0" + s.add_dependency "myrack", ">= 1.0.0" end - build_gem "rack", "1.1.0" + build_gem "myrack", "1.1.0" build_gem "rails", "3.0.0.rc4" do |s| - s.add_dependency "rack", "~> 1.1.0" + s.add_dependency "myrack", "~> 1.1.0" end - build_gem "rack", "1.2.1" + build_gem "myrack", "1.2.1" build_gem "rails", "3.0.0" do |s| - s.add_dependency "rack", "~> 1.2.1" + s.add_dependency "myrack", "~> 1.2.1" end end end @@ -362,14 +386,14 @@ RSpec.describe "bundle flex_install" do it "resolves them" do # install Rails 3.0.0.rc install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "rails", "3.0.0.rc4" gem "capybara", "0.3.9" G # upgrade Rails to 3.0.0 and then install again install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "rails", "3.0.0" gem "capybara", "0.3.9" G diff --git a/spec/bundler/install/gems/fund_spec.rb b/spec/bundler/install/gems/fund_spec.rb index 9aadc9ed25..8a3a51270a 100644 --- a/spec/bundler/install/gems/fund_spec.rb +++ b/spec/bundler/install/gems/fund_spec.rb @@ -32,10 +32,10 @@ RSpec.describe "bundle install" do context "when gems include a fund URI" do it "displays the plural fund message after installing" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem 'has_funding_and_other_metadata' gem 'has_funding' - gem 'rack-obama' + gem 'myrack-obama' G expect(out).to include("2 installed gems you directly depend on are looking for funding.") @@ -43,9 +43,9 @@ RSpec.describe "bundle install" do it "displays the singular fund message after installing" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem 'has_funding' - gem 'rack-obama' + gem 'myrack-obama' G expect(out).to include("1 installed gem you directly depend on is looking for funding.") @@ -54,15 +54,15 @@ 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 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem 'has_funding_and_other_metadata' gem 'has_funding' - gem 'rack-obama' + gem 'myrack-obama' G expect(out).not_to include("2 installed gems you directly depend on are looking for funding.") @@ -70,9 +70,9 @@ RSpec.describe "bundle install" do it "does not display the singular fund message after installing" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem 'has_funding' - gem 'rack-obama' + gem 'myrack-obama' G expect(out).not_to include("1 installed gem you directly depend on is looking for funding.") @@ -82,7 +82,7 @@ RSpec.describe "bundle install" do context "when gems do not include fund messages" do it "does not display any fund messages" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport" G @@ -93,7 +93,7 @@ RSpec.describe "bundle install" do context "when a dependency includes a fund message" do it "does not display the fund message" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem 'gem_with_dependent_funding' G @@ -111,7 +111,7 @@ RSpec.describe "bundle install" do } end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'also_has_funding', :git => '#{lib_path("also_has_funding-1.0")}' G @@ -125,7 +125,7 @@ RSpec.describe "bundle install" do } end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'also_has_funding', :git => '#{lib_path("also_has_funding-1.0")}' G @@ -135,7 +135,7 @@ RSpec.describe "bundle install" do } end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'also_has_funding', :git => '#{lib_path("also_has_funding-1.1")}' G @@ -149,7 +149,7 @@ RSpec.describe "bundle install" do } end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'also_has_funding', :git => '#{lib_path("also_has_funding-1.0")}' G 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 9611973701..e1fbeac454 100644 --- a/spec/bundler/install/gems/mirror_spec.rb +++ b/spec/bundler/install/gems/mirror_spec.rb @@ -4,17 +4,17 @@ RSpec.describe "bundle install with a mirror configured" do describe "when the mirror does not match the gem source" do before :each do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack" + 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 bundle :install - expect(out).to include("Fetching source index from #{file_uri_for(gem_repo1)}") - expect(the_bundle).to include_gems "rack 1.0" + expect(out).to include("Fetching gem metadata from https://gem.repo1") + expect(the_bundle).to include_gems "myrack 1.0" end end @@ -22,18 +22,18 @@ RSpec.describe "bundle install with a mirror configured" do before :each do gemfile <<-G # This source is bogus and doesn't have the gem we're looking for - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" - gem "rack" + gem "myrack" G - bundle "config set --local mirror.#{file_uri_for(gem_repo2)} #{file_uri_for(gem_repo1)}" + bundle_config "mirror.https://gem.repo2 https://gem.repo1" end it "installs the gem from the mirror" do - bundle :install - expect(out).to include("Fetching source index from #{file_uri_for(gem_repo1)}") - expect(out).not_to include("Fetching source index from #{file_uri_for(gem_repo2)}") - expect(the_bundle).to include_gems "rack 1.0" + bundle :install, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s } + expect(out).to include("Fetching gem metadata from https://gem.repo1") + expect(out).not_to include("Fetching gem metadata from https://gem.repo2") + expect(the_bundle).to include_gems "myrack 1.0" end end end diff --git a/spec/bundler/install/gems/native_extensions_spec.rb b/spec/bundler/install/gems/native_extensions_spec.rb index 907778a384..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 @@ -33,11 +33,11 @@ RSpec.describe "installing a gem with native extensions" do end gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" 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,10 +75,10 @@ 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "c_extension", :git => #{lib_path("c_extension-1.0").to_s.dump} G @@ -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,18 +122,18 @@ 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 - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "c_extension_one", :git => #{lib_path("gems").to_s.dump} G # 2nd time, require both gems -- we need both extensions to be built now. install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "c_extension_one", :git => #{lib_path("gems").to_s.dump} gem "c_extension_two", :git => #{lib_path("gems").to_s.dump} 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,10 +171,10 @@ 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "c_extension", :git => #{lib_path("c_extension-1.0").to_s.dump} G 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 7426f54877..e49fd2a9a3 100644 --- a/spec/bundler/install/gems/post_install_spec.rb +++ b/spec/bundler/install/gems/post_install_spec.rb @@ -5,26 +5,26 @@ RSpec.describe "bundle install" do context "when gems include post install messages" do it "should display the post-install messages after installing" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' gem 'thin' - gem 'rack-obama' + gem 'myrack-obama' G bundle :install - expect(out).to include("Post-install message from rack:") - expect(out).to include("Rack's post install message") + expect(out).to include("Post-install message from myrack:") + expect(out).to include("Myrack's post install message") expect(out).to include("Post-install message from thin:") expect(out).to include("Thin's post install message") - expect(out).to include("Post-install message from rack-obama:") - expect(out).to include("Rack-obama's post install message") + expect(out).to include("Post-install message from myrack-obama:") + expect(out).to include("Myrack-obama's post install message") end end context "when gems do not include post install messages" do it "should not display any post-install messages" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "activesupport" G @@ -36,13 +36,13 @@ RSpec.describe "bundle install" do context "when a dependency includes a post install message" do it "should display the post install message" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack_middleware' + source "https://gem.repo1" + gem 'myrack_middleware' G bundle :install - expect(out).to include("Post-install message from rack:") - expect(out).to include("Rack's post install message") + expect(out).to include("Post-install message from myrack:") + expect(out).to include("Myrack's post install message") end end end @@ -54,7 +54,7 @@ RSpec.describe "bundle install" do s.post_install_message = "Foo's post install message" end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'foo', :git => '#{lib_path("foo-1.0")}' G @@ -68,7 +68,7 @@ RSpec.describe "bundle install" do s.post_install_message = "Foo's post install message" end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'foo', :git => '#{lib_path("foo-1.0")}' G bundle :install @@ -77,7 +77,7 @@ RSpec.describe "bundle install" do s.post_install_message = "Foo's 1.1 post install message" end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'foo', :git => '#{lib_path("foo-1.1")}' G bundle :install @@ -91,7 +91,7 @@ RSpec.describe "bundle install" do s.post_install_message = "Foo's post install message" end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'foo', :git => '#{lib_path("foo-1.0")}' G @@ -110,7 +110,7 @@ RSpec.describe "bundle install" do s.post_install_message = nil end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'foo', :git => '#{lib_path("foo-1.0")}' G @@ -123,11 +123,11 @@ RSpec.describe "bundle install" do context "when ignore post-install messages for gem is set" do it "doesn't display any post-install messages" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G - bundle "config set ignore_messages.rack true" + bundle_config "ignore_messages.myrack true" bundle :install expect(out).not_to include("Post-install message") @@ -137,11 +137,11 @@ RSpec.describe "bundle install" do context "when ignore post-install messages for all gems" do it "doesn't display any post-install messages" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + 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 c5f9c4a3d3..111d361aab 100644 --- a/spec/bundler/install/gems/resolving_spec.rb +++ b/spec/bundler/install/gems/resolving_spec.rb @@ -66,7 +66,7 @@ RSpec.describe "bundle install with install-time dependencies" do it "installs gems with implicit rake dependencies" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "with_implicit_rake_dep" gem "another_implicit_rake_dep" gem "rake" @@ -84,7 +84,7 @@ RSpec.describe "bundle install with install-time dependencies" do it "installs gems with implicit rake dependencies without rake previously installed" do with_path_as("") do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "with_implicit_rake_dep" gem "another_implicit_rake_dep" gem "rake" @@ -100,7 +100,7 @@ RSpec.describe "bundle install with install-time dependencies" do expect(out).to eq("YES\nYES") end - it "installs gems with a dependency with no type" do + it "does not install gems with a dependency with no type" do build_repo2 path = "#{gem_repo2}/#{Gem::MARSHAL_SPEC_DIR}/actionpack-2.3.2.gemspec.rz" @@ -112,18 +112,20 @@ RSpec.describe "bundle install with install-time dependencies" do f.write Gem.deflate(Marshal.dump(spec)) end - install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo2" gem "actionpack", "2.3.2" G - expect(the_bundle).to include_gems "actionpack 2.3.2", "activesupport 2.3.2" + expect(err).to include("Downloading actionpack-2.3.2 revealed dependencies not in the API (activesupport (= 2.3.2)).") + + expect(the_bundle).not_to include_gems "actionpack 2.3.2", "activesupport 2.3.2" end describe "with crazy rubygem plugin stuff" do it "installs plugins" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "net_b" G @@ -132,7 +134,7 @@ RSpec.describe "bundle install with install-time dependencies" do it "installs plugins depended on by other plugins" do install_gemfile <<-G, env: { "DEBUG" => "1" } - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "net_a" G @@ -141,7 +143,7 @@ RSpec.describe "bundle install with install-time dependencies" do it "installs multiple levels of dependencies" do install_gemfile <<-G, env: { "DEBUG" => "1" } - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "net_c" gem "net_e" G @@ -152,7 +154,7 @@ RSpec.describe "bundle install with install-time dependencies" do context "with ENV['BUNDLER_DEBUG_RESOLVER'] set" do it "produces debug output" do gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "net_c" gem "net_e" G @@ -166,7 +168,7 @@ RSpec.describe "bundle install with install-time dependencies" do context "with ENV['DEBUG_RESOLVER'] set" do it "produces debug output" do gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "net_c" gem "net_e" G @@ -180,7 +182,7 @@ RSpec.describe "bundle install with install-time dependencies" do context "with ENV['DEBUG_RESOLVER_TREE'] set" do it "produces debug output" do gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "net_c" gem "net_e" G @@ -199,44 +201,44 @@ RSpec.describe "bundle install with install-time dependencies" do context "allows only an older version" do it "installs the older version" do build_repo2 do - build_gem "rack", "1.2" do |s| - s.executables = "rackup" + build_gem "myrack", "1.2" do |s| + s.executables = "myrackup" end - build_gem "rack", "9001.0.0" do |s| + build_gem "myrack", "9001.0.0" do |s| s.required_ruby_version = "> 9000" end end - install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + install_gemfile <<-G ruby "#{Gem.ruby_version}" - source "http://localgemserver.test/" - gem 'rack' + source "https://gem.repo2" + gem 'myrack' G - expect(err).to_not include("rack-9001.0.0 requires ruby version > 9000") - expect(the_bundle).to include_gems("rack 1.2") + expect(err).to_not include("myrack-9001.0.0 requires ruby version > 9000") + expect(the_bundle).to include_gems("myrack 1.2") end it "installs the older version when using servers not implementing the compact index API" do build_repo2 do - build_gem "rack", "1.2" do |s| - s.executables = "rackup" + build_gem "myrack", "1.2" do |s| + s.executables = "myrackup" end - build_gem "rack", "9001.0.0" do |s| + build_gem "myrack", "9001.0.0" do |s| s.required_ruby_version = "> 9000" end end - install_gemfile <<-G, artifice: "endpoint", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + install_gemfile <<-G, artifice: "endpoint" ruby "#{Gem.ruby_version}" - source "http://localgemserver.test/" - gem 'rack' + source "https://gem.repo2" + gem 'myrack' G - expect(err).to_not include("rack-9001.0.0 requires ruby version > 9000") - expect(the_bundle).to include_gems("rack 1.2") + expect(err).to_not include("myrack-9001.0.0 requires ruby version > 9000") + expect(the_bundle).to include_gems("myrack 1.2") end context "when there is a lockfile using the newer incompatible version" do @@ -252,7 +254,7 @@ RSpec.describe "bundle install with install-time dependencies" do end gemfile <<-G - source "http://localgemserver.test/" + source "https://gem.repo2" gem 'parallel_tests' G @@ -262,7 +264,7 @@ RSpec.describe "bundle install with install-time dependencies" do lockfile <<~L GEM - remote: http://localgemserver.test/ + remote: https://gem.repo2/ specs: parallel_tests (3.8.0) @@ -273,20 +275,20 @@ RSpec.describe "bundle install with install-time dependencies" do parallel_tests #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end it "automatically updates lockfile to use the older version" do - bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + bundle "install --verbose" - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum gem_repo2, "parallel_tests", "3.7.0" end expect(lockfile).to eq <<~L GEM - remote: http://localgemserver.test/ + remote: https://gem.repo2/ specs: parallel_tests (3.7.0) @@ -297,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", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s, "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 @@ -336,7 +337,7 @@ RSpec.describe "bundle install with install-time dependencies" do end gemfile <<-G - source "http://localgemserver.test/" + source "https://gem.repo2" gem 'rubocop' G @@ -347,7 +348,7 @@ RSpec.describe "bundle install with install-time dependencies" do lockfile <<~L GEM - remote: http://localgemserver.test/ + remote: https://gem.repo2/ specs: rubocop (1.35.0) rubocop-ast (>= 1.20.1, < 2.0) @@ -357,24 +358,24 @@ RSpec.describe "bundle install with install-time dependencies" do #{lockfile_platforms} DEPENDENCIES - parallel_tests + rubocop #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end it "automatically updates lockfile to use the older compatible versions" do - bundle "install --verbose", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + bundle "install --verbose" - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum gem_repo2, "rubocop", "1.28.2" c.checksum gem_repo2, "rubocop-ast", "1.17.0" end expect(lockfile).to eq <<~L GEM - remote: http://localgemserver.test/ + remote: https://gem.repo2/ specs: rubocop (1.28.2) rubocop-ast (>= 1.17.0, < 2.0) @@ -387,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| @@ -405,13 +406,13 @@ RSpec.describe "bundle install with install-time dependencies" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem 'sorbet', '= 0.5.10554' G lockfile <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: sorbet (0.5.10554) sorbet-static (= 0.5.10554) @@ -424,7 +425,7 @@ RSpec.describe "bundle install with install-time dependencies" do sorbet (= 0.5.10554) BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -434,12 +435,14 @@ RSpec.describe "bundle install with install-time dependencies" do end nice_error = <<~E.strip - Could not find gems matching 'sorbet-static (= 0.5.10554)' valid for all resolution platforms (arm64-darwin-21, aarch64-linux) in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally. + Could not find gems matching 'sorbet-static (= 0.5.10554)' valid for all resolution platforms (arm64-darwin-21, aarch64-linux) in rubygems repository https://gem.repo4/ or installed locally. The source contains the following gems matching 'sorbet-static (= 0.5.10554)': * sorbet-static-0.5.10554-universal-darwin-21 E - expect(err).to end_with(nice_error) + 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 @@ -461,7 +464,7 @@ RSpec.describe "bundle install with install-time dependencies" do lockfile <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: nokogiri (1.14.0-arm-linux) nokogiri (1.14.0-x86_64-linux) @@ -474,11 +477,11 @@ RSpec.describe "bundle install with install-time dependencies" do nokogiri BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "nokogiri" gem "sorbet-static" @@ -490,7 +493,7 @@ RSpec.describe "bundle install with install-time dependencies" do it "raises a proper error" do nice_error = <<~E.strip - Could not find gems matching 'sorbet-static' valid for all resolution platforms (arm-linux, x86_64-linux) in rubygems repository #{file_uri_for(gem_repo4)}/, cached gems or installed locally. + Could not find gems matching 'sorbet-static' valid for all resolution platforms (arm-linux, x86_64-linux) in rubygems repository https://gem.repo4/ or installed locally. The source contains the following gems matching 'sorbet-static': * sorbet-static-0.5.10696-x86_64-linux @@ -499,6 +502,64 @@ RSpec.describe "bundle install with install-time dependencies" do end end + context "when locked generic variant supports current Ruby, but locked specific variant does not" do + let(:original_lockfile) do + <<~L + GEM + remote: https://gem.repo4/ + specs: + nokogiri (1.16.3) + nokogiri (1.16.3-x86_64-linux) + + PLATFORMS + ruby + x86_64-linux + + DEPENDENCIES + nokogiri + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + before do + build_repo4 do + build_gem "nokogiri", "1.16.3" + build_gem "nokogiri", "1.16.3" do |s| + s.required_ruby_version = "< #{Gem.ruby_version}" + s.platform = "x86_64-linux" + end + end + + gemfile <<~G + source "https://gem.repo4" + + gem "nokogiri" + G + + lockfile original_lockfile + end + + 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" + + expect(lockfile).to eq(original_lockfile) + 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 build_repo4 do build_gem "requires-old-ruby" do |s| @@ -513,7 +574,7 @@ RSpec.describe "bundle install with install-time dependencies" do end install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gemspec G @@ -531,47 +592,47 @@ RSpec.describe "bundle install with install-time dependencies" do it "installs the older version under rate limiting conditions" do build_repo4 do - build_gem "rack", "9001.0.0" do |s| + build_gem "myrack", "9001.0.0" do |s| s.required_ruby_version = "> 9000" end - build_gem "rack", "1.2" + build_gem "myrack", "1.2" build_gem "foo1", "1.0" end - install_gemfile <<-G, artifice: "compact_index_rate_limited", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + install_gemfile <<-G, artifice: "compact_index_rate_limited" ruby "#{Gem.ruby_version}" - source "http://localgemserver.test/" - gem 'rack' + source "https://gem.repo4" + gem 'myrack' gem 'foo1' G - expect(err).to_not include("rack-9001.0.0 requires ruby version > 9000") - expect(the_bundle).to include_gems("rack 1.2") + expect(err).to_not include("myrack-9001.0.0 requires ruby version > 9000") + expect(the_bundle).to include_gems("myrack 1.2") end it "installs the older not platform specific version" do build_repo4 do - build_gem "rack", "9001.0.0" do |s| + build_gem "myrack", "9001.0.0" do |s| s.required_ruby_version = "> 9000" end - build_gem "rack", "1.2" do |s| - s.platform = x86_mingw32 + build_gem "myrack", "1.2" do |s| + s.platform = "x86-mingw32" s.required_ruby_version = "> 9000" end - build_gem "rack", "1.2" + build_gem "myrack", "1.2" end - simulate_platform x86_mingw32 do - install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + simulate_platform "x86-mingw32" do + install_gemfile <<-G, artifice: "compact_index" ruby "#{Gem.ruby_version}" - source "http://localgemserver.test/" - gem 'rack' + source "https://gem.repo4" + gem 'myrack' G end - expect(err).to_not include("rack-9001.0.0 requires ruby version > 9000") - expect(err).to_not include("rack-1.2-#{Bundler.local_platform} requires ruby version > 9000") - expect(the_bundle).to include_gems("rack 1.2") + expect(err).to_not include("myrack-9001.0.0 requires ruby version > 9000") + expect(err).to_not include("myrack-1.2-#{Bundler.local_platform} requires ruby version > 9000") + expect(the_bundle).to include_gems("myrack 1.2") end end @@ -588,8 +649,8 @@ RSpec.describe "bundle install with install-time dependencies" do let(:error_message_requirement) { "= #{Gem.ruby_version}" } it "raises a proper error that mentions the current Ruby version during resolution" do - install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }, raise_on_error: false - source "http://localgemserver.test/" + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo2" gem 'require_ruby' G @@ -609,8 +670,8 @@ RSpec.describe "bundle install with install-time dependencies" do shared_examples_for "ruby version conflicts" do it "raises an error during resolution" do - install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }, raise_on_error: false - source "http://localgemserver.test/" + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo2" ruby #{ruby_requirement} gem 'require_ruby' G @@ -656,7 +717,7 @@ RSpec.describe "bundle install with install-time dependencies" do end install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem 'require_rubygems' G @@ -671,4 +732,55 @@ RSpec.describe "bundle install with install-time dependencies" do expect(err).to end_with(nice_error) end end + + context "when non platform specific gems bring more dependencies", :truffleruby_only do + before do + build_repo4 do + build_gem "foo", "1.0" do |s| + s.add_dependency "bar" + end + + build_gem "foo", "2.0" do |s| + s.platform = "x86_64-linux" + end + + build_gem "bar" + end + + gemfile <<-G + source "https://gem.repo4" + gem "foo" + G + end + + it "locks both ruby and current platform, and resolve to ruby variants that install on truffleruby" do + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "foo", "1.0" + c.checksum gem_repo4, "bar", "1.0" + end + + simulate_platform "x86_64-linux" do + bundle "install" + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + bar (1.0) + foo (1.0) + bar + + PLATFORMS + ruby + x86_64-linux + + DEPENDENCIES + foo + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + end end diff --git a/spec/bundler/install/gems/standalone_spec.rb b/spec/bundler/install/gems/standalone_spec.rb index 46cab2dfeb..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}" } @@ -8,9 +8,9 @@ RSpec.shared_examples "bundle install --standalone" do end it "still makes system gems unavailable to normal bundler" do - system_gems "rack-1.0.0" + system_gems "myrack-1.0.0" - expect(the_bundle).to_not include_gems("rack") + expect(the_bundle).to_not include_gems("myrack") end it "generates a bundle/bundler/setup.rb" do @@ -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 @@ -63,14 +63,14 @@ RSpec.shared_examples "bundle install --standalone" do end it "makes system gems unavailable without bundler" do - system_gems "rack-1.0.0" + system_gems "myrack-1.0.0" testrb = String.new <<-RUBY $:.unshift File.expand_path("bundle") require "bundler/setup" begin - require "rack" + require "myrack" rescue LoadError puts "LoadError" 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 @@ -122,10 +122,10 @@ RSpec.shared_examples "bundle install --standalone" do describe "with simple gems" do before do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + 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,13 +140,8 @@ RSpec.shared_examples "bundle install --standalone" do end describe "with default gems and a lockfile", :ruby_repo do - before do - realworld_system_gems "tsort --version 0.1.0" - - necessary_system_gems = ["optparse --version 0.1.1", "psych --version 3.3.2", "logger --version 1.4.3", "etc --version 1.2.0", "stringio --version 3.1.0"] - necessary_system_gems += ["shellwords --version 0.1.0", "base64 --version 0.1.0", "resolv --version 0.2.1"] if Gem.rubygems_version < Gem::Version.new("3.3.a") - necessary_system_gems += ["yaml --version 0.1.1"] if Gem.rubygems_version < Gem::Version.new("3.4.a") - realworld_system_gems(*necessary_system_gems, path: scoped_gem_path(bundled_app("bundle"))) + it "works and points to the vendored copies, not to the default copies" do + 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" @@ -167,12 +162,11 @@ RSpec.shared_examples "bundle install --standalone" do gem "foo" G - bundle "lock", dir: cwd, artifice: "compact_index" - end + bundle "lock", dir: cwd - it "works and points to the vendored copies, not to the default copies", :realworld do - bundle "config set --local path #{bundled_app("bundle")}" - bundle :install, standalone: true, dir: cwd, artifice: "compact_index", env: { "BUNDLER_GEM_DEFAULT_DIR" => system_gem_path.to_s } + bundle_config "path #{bundled_app("bundle")}" + + 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") } @@ -181,6 +175,36 @@ RSpec.shared_examples "bundle install --standalone" do '$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/foo-1.0.0/lib")', ] end + + it "works for gems with extensions and points to the vendored copies, not to the default copies" do + simulate_platform "arm64-darwin-23" do + 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 + + build_repo4 do + build_gem "baz", "1.0.0", &:add_c_extension + end + + gemfile <<-G + source "https://gem.repo4" + gem "baz" + G + + bundle_config "path #{bundled_app("bundle")}" + + bundle "lock", dir: cwd + + 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") } + + expect(load_path_lines).to eq [ + '$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/extensions/arm64-darwin-23/#{Gem.extension_api_version}/baz-1.0.0")', + '$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/baz-1.0.0/lib")', + ] + end end describe "with Gemfiles using absolute path sources and resulting bundle moved to a folder hierarchy with different nesting" do @@ -190,7 +214,7 @@ RSpec.shared_examples "bundle install --standalone" do Dir.mkdir bundled_app("app") gemfile bundled_app("app/Gemfile"), <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "minitest", :path => "#{lib_path("minitest")}" G @@ -213,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") @@ -220,7 +246,7 @@ RSpec.shared_examples "bundle install --standalone" do build_lib "minitest", "1.0.0", path: bundled_app("app/vendor/minitest") gemfile bundled_app("app/Gemfile"), <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "minitest", :path => "vendor/minitest" G @@ -244,9 +270,9 @@ 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "very_simple_binary" G end @@ -282,16 +308,16 @@ 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "bar", :git => "#{lib_path("bar-1.0")}" G end it "outputs a helpful error message" do expect(err).to include("You have one or more invalid gemspecs that need to be fixed.") - expect(err).to include("bar 1.0 has an invalid gemspec") + expect(err).to include("bar.gemspec is not valid") end end @@ -300,11 +326,11 @@ RSpec.shared_examples "bundle install --standalone" do build_git "devise", "1.0" gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" 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 @@ -324,15 +350,15 @@ RSpec.shared_examples "bundle install --standalone" do build_git "devise", "1.0" gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rails" group :test do gem "rspec" - gem "rack-test" + 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 @@ -346,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" @@ -359,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" @@ -377,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 @@ -396,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" @@ -411,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 @@ -424,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 @@ -438,91 +464,61 @@ 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 "#{file_uri_for(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 - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G - system_gems "rack-1.0.0", path: default_bundle_path + system_gems "myrack-1.0.0", path: default_bundle_path end it "generates script pointing to system gems" do bundle "install --standalone --local --verbose" - expect(out).to include("Using rack 1.0.0") + expect(out).to include("Using myrack 1.0.0") load_error_ruby <<-RUBY, "spec" require "./bundler/setup" - require "rack" - puts RACK + require "myrack" + puts MYRACK require "spec" 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/gems/win32_spec.rb b/spec/bundler/install/gems/win32_spec.rb index 419b14ff0f..be37673aa1 100644 --- a/spec/bundler/install/gems/win32_spec.rb +++ b/spec/bundler/install/gems/win32_spec.rb @@ -4,22 +4,22 @@ RSpec.describe "bundle install with win32-generated lockfile" do it "should read lockfile" do File.open(bundled_app_lock, "wb") do |f| f << "GEM\r\n" - f << " remote: #{file_uri_for(gem_repo1)}/\r\n" + f << " remote: https://gem.repo1/\r\n" f << " specs:\r\n" f << "\r\n" - f << " rack (1.0.0)\r\n" + f << " myrack (1.0.0)\r\n" f << "\r\n" f << "PLATFORMS\r\n" f << " ruby\r\n" f << "\r\n" f << "DEPENDENCIES\r\n" - f << " rack\r\n" + f << " myrack\r\n" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" - gem "rack" + gem "myrack" G end end diff --git a/spec/bundler/install/gemspecs_spec.rb b/spec/bundler/install/gemspecs_spec.rb index 51aa0ed14f..fb2271c830 100644 --- a/spec/bundler/install/gemspecs_spec.rb +++ b/spec/bundler/install/gemspecs_spec.rb @@ -10,7 +10,7 @@ RSpec.describe "bundle install" do it "still installs correctly" do gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "yaml_spec" G bundle :install @@ -21,7 +21,7 @@ RSpec.describe "bundle install" do build_lib "yaml_spec", gemspec: :yaml install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'yaml_spec', :path => "#{lib_path("yaml_spec-1.0")}" G expect(err).to be_empty @@ -30,23 +30,23 @@ RSpec.describe "bundle install" do it "should use gemspecs in the system cache when available" do gemfile <<-G - source "http://localtestserver.gem" - gem 'rack' + source "http://localgemserver.test" + gem 'myrack' G - system_gems "rack-1.0.0", path: default_bundle_path + system_gems "myrack-1.0.0", path: default_bundle_path FileUtils.mkdir_p "#{default_bundle_path}/specifications" - File.open("#{default_bundle_path}/specifications/rack-1.0.0.gemspec", "w+") do |f| + File.open("#{default_bundle_path}/specifications/myrack-1.0.0.gemspec", "w+") do |f| spec = Gem::Specification.new do |s| - s.name = "rack" + s.name = "myrack" s.version = "1.0.0" - s.add_runtime_dependency "activesupport", "2.3.2" + s.add_dependency "activesupport", "2.3.2" end f.write spec.to_ruby end bundle :install, artifice: "endpoint_marshal_fail" # force gemspec load - expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.2" + expect(the_bundle).to include_gems "myrack 1.0.0", "activesupport 2.3.2" end it "does not hang when gemspec has incompatible encoding" do @@ -60,7 +60,7 @@ RSpec.describe "bundle install" do G install_gemfile <<-G, env: { "LANG" => "C" } - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gemspec G @@ -86,7 +86,7 @@ RSpec.describe "bundle install" do G install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gemspec G @@ -102,7 +102,7 @@ RSpec.describe "bundle install" do install_gemfile <<-G ruby '#{RUBY_VERSION}', :engine_version => '#{RUBY_VERSION}', :engine => 'ruby' - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gemspec G expect(the_bundle).to include_gems "foo 1.0" @@ -116,13 +116,13 @@ RSpec.describe "bundle install" do install_gemfile <<-G, raise_on_error: false ruby '#{RUBY_VERSION}', :engine_version => '#{RUBY_VERSION}', :engine => 'ruby', :patchlevel => '#{RUBY_PATCHLEVEL}' - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gemspec G 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| @@ -131,13 +131,11 @@ RSpec.describe "bundle install" do install_gemfile <<-G, raise_on_error: false ruby '#{RUBY_VERSION}', :engine_version => '#{RUBY_VERSION}', :engine => 'ruby', :patchlevel => '#{patchlevel}' - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" 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 @@ -149,7 +147,7 @@ RSpec.describe "bundle install" do install_gemfile <<-G, raise_on_error: false ruby '#{version}', :engine_version => '#{version}', :engine => 'ruby' - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gemspec G @@ -157,5 +155,25 @@ RSpec.describe "bundle install" do expect(err).to include("but your Gemfile specified") expect(exitstatus).to eq(18) end + + it "validates gemspecs just once when everything installed and lockfile up to date" do + build_lib "foo" + + install_gemfile <<-G + source "https://gem.repo1" + gemspec path: "#{lib_path("foo-1.0")}" + + module Monkey + def validate(spec) + puts "Validate called on \#{spec.full_name}" + end + end + Bundler.rubygems.extend(Monkey) + G + + bundle "install" + + expect(out).to include("Validate called on foo-1.0").once + end end end diff --git a/spec/bundler/install/git_spec.rb b/spec/bundler/install/git_spec.rb index c8d574baf3..1172d661ae 100644 --- a/spec/bundler/install/git_spec.rb +++ b/spec/bundler/install/git_spec.rb @@ -6,23 +6,36 @@ RSpec.describe "bundle install" do build_git "foo", "1.0", path: lib_path("foo") install_gemfile <<-G, verbose: true - source "#{file_uri_for(gem_repo1)}" - gem "foo", :git => "#{file_uri_for(lib_path("foo"))}" + source "https://gem.repo1" + gem "foo", :git => "#{lib_path("foo")}" G - expect(out).to include("Using foo 1.0 from #{file_uri_for(lib_path("foo"))} (at main@#{revision_for(lib_path("foo"))[0..6]})") + expect(out).to include("Using foo 1.0 from #{lib_path("foo")} (at main@#{revision_for(lib_path("foo"))[0..6]})") + expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}" + end + + it "displays the revision hash of the gem repository when passed a relative local path" 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("Using foo 1.0 from #{relative_path} (at main@#{revision_for(lib_path("foo"))[0..6]})") expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}" end it "displays the correct default branch", git: ">= 2.28.0" do - build_git "foo", "1.0", path: lib_path("foo"), default_branch: "main" + build_git "foo", "1.0", path: lib_path("foo"), default_branch: "non-standard" install_gemfile <<-G, verbose: true - source "#{file_uri_for(gem_repo1)}" - gem "foo", :git => "#{file_uri_for(lib_path("foo"))}" + source "https://gem.repo1" + gem "foo", :git => "#{lib_path("foo")}" G - expect(out).to include("Using foo 1.0 from #{file_uri_for(lib_path("foo"))} (at main@#{revision_for(lib_path("foo"))[0..6]})") + expect(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 @@ -36,31 +49,31 @@ RSpec.describe "bundle install" do update_git "foo", "3.0", path: lib_path("foo"), gemspec: true install_gemfile <<-G, verbose: true - source "#{file_uri_for(gem_repo1)}" - gem "foo", :git => "#{file_uri_for(lib_path("foo"))}", :ref => "main~2" + source "https://gem.repo1" + gem "foo", :git => "#{lib_path("foo")}", :ref => "main~2" G - expect(out).to include("Using foo 1.0 from #{file_uri_for(lib_path("foo"))} (at main~2@#{rev})") + expect(out).to include("Using foo 1.0 from #{lib_path("foo")} (at main~2@#{rev})") expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}" update_git "foo", "4.0", path: lib_path("foo"), gemspec: true bundle :update, all: true, verbose: true - expect(out).to include("Using foo 2.0 (was 1.0) from #{file_uri_for(lib_path("foo"))} (at main~2@#{rev2})") + expect(out).to include("Using foo 2.0 (was 1.0) from #{lib_path("foo")} (at main~2@#{rev2})") expect(the_bundle).to include_gems "foo 2.0", source: "git@#{lib_path("foo")}" end - it "should allows git repos that are missing but not being installed" do + it "allows git repos that are missing but not being installed" do revision = build_git("foo").ref_for("HEAD") gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}", :group => :development + source "https://gem.repo1" + gem "foo", :git => "#{lib_path("foo-1.0")}", :group => :development G lockfile <<-L GIT - remote: #{file_uri_for(lib_path("foo-1.0"))} + remote: #{lib_path("foo-1.0")} revision: #{revision} specs: foo (1.0) @@ -72,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!") @@ -87,9 +100,9 @@ RSpec.describe "bundle install" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "foo", :git => "#{file_uri_for(lib_path("gems"))}", :glob => "foo/*.gemspec" - gem "zebra", :git => "#{file_uri_for(lib_path("gems"))}", :glob => "zebra/*.gemspec" + source "https://gem.repo2" + gem "foo", :git => "#{lib_path("gems")}", :glob => "foo/*.gemspec" + gem "zebra", :git => "#{lib_path("gems")}", :glob => "zebra/*.gemspec" G bundle "info foo" @@ -112,7 +125,7 @@ RSpec.describe "bundle install" do other_ref = other.ref_for("HEAD") gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "test", git: #{test.path.to_s.inspect} gem "other", ref: #{other_ref.inspect}, git: #{other.path.to_s.inspect} @@ -133,7 +146,7 @@ RSpec.describe "bundle install" do other (1.0.0) GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS @@ -144,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 @@ -175,14 +188,14 @@ 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 "#{file_uri_for(gem_repo1)}" - gem "foo", :git => "#{file_uri_for(lib_path("foo"))}" + source "https://gem.repo1" + gem "foo", :git => "#{lib_path("foo")}" G - expect(out).to include("Using foo 1.0 from #{file_uri_for(lib_path("foo"))} (at main@#{rev[0..6]})") + expect(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")}" old_lockfile = lockfile @@ -191,15 +204,166 @@ RSpec.describe "bundle install" do rev2 = revision_for(lib_path("foo")) bundle :update, all: true, verbose: true - expect(out).to include("Using foo 2.0 (was 1.0) from #{file_uri_for(lib_path("foo"))} (at main@#{rev2[0..6]})") + expect(out).to include("Using foo 2.0 (was 1.0) from #{lib_path("foo")} (at main@#{rev2[0..6]})") expect(out).to include("Removing foo (#{rev[0..11]})") expect(the_bundle).to include_gems "foo 2.0", source: "git@#{lib_path("foo")}" lockfile(old_lockfile) bundle :install, verbose: true - expect(out).to include("Using foo 1.0 from #{file_uri_for(lib_path("foo"))} (at main@#{rev[0..6]})") + expect(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 0da4de05b2..4cffa65b2a 100644 --- a/spec/bundler/install/global_cache_spec.rb +++ b/spec/bundler/install/global_cache_spec.rb @@ -1,180 +1,232 @@ # 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 install_gemfile <<-G, artifice: "compact_index" source "#{source}" - gem "rack" + gem "myrack" G - expect(the_bundle).to include_gems "rack 1.0.0" - expect(source_global_cache("rack-1.0.0.gem")).to exist + expect(the_bundle).to include_gems "myrack 1.0.0" + expect(source_global_cache("myrack-1.0.0.gem")).to exist end it "uses globally cached gems if they exist" do source_global_cache.mkpath - FileUtils.cp(gem_repo1("gems/rack-1.0.0.gem"), source_global_cache("rack-1.0.0.gem")) + FileUtils.cp(gem_repo1("gems/myrack-1.0.0.gem"), source_global_cache("myrack-1.0.0.gem")) install_gemfile <<-G, artifice: "compact_index_no_gem" source "#{source}" - gem "rack" + gem "myrack" G - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" 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("rack-1.0.0.gem")) + FileUtils.touch(source_global_cache("myrack-1.0.0.gem")) install_gemfile <<-G, artifice: "compact_index_no_gem", raise_on_error: false source "#{source}" - gem "rack" + gem "myrack" G - expect(err).to include("Gem::Package::FormatError: package metadata is missing in #{source_global_cache("rack-1.0.0.gem")}") + 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 "rack" + gem "myrack" G - simulate_new_machine - expect(the_bundle).not_to include_gems "rack 1.0.0" - expect(source_global_cache("rack-1.0.0.gem")).to exist - # rack 1.0.0 is not installed and it is in the global cache + 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 install_gemfile <<-G, artifice: "compact_index" source "#{source2}" - gem "rack", "0.9.1" + gem "myrack", "0.9.1" G - simulate_new_machine - expect(the_bundle).not_to include_gems "rack 0.9.1" - expect(source2_global_cache("rack-0.9.1.gem")).to exist - # rack 0.9.1 is not installed and it is in the global cache + 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 gemfile <<-G source "#{source}" - gem "rack", "1.0.0" + gem "myrack", "1.0.0" G bundle :install, artifice: "compact_index_no_gem" - # rack 1.0.0 is installed and rack 0.9.1 is not - expect(the_bundle).to include_gems "rack 1.0.0" - expect(the_bundle).not_to include_gems "rack 0.9.1" - simulate_new_machine + # 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" + pristine_system_gems gemfile <<-G source "#{source2}" - gem "rack", "0.9.1" + gem "myrack", "0.9.1" G bundle :install, artifice: "compact_index_no_gem" - # rack 0.9.1 is installed and rack 1.0.0 is not - expect(the_bundle).to include_gems "rack 0.9.1" - expect(the_bundle).not_to include_gems "rack 1.0.0" + # myrack 0.9.1 is installed and myrack 1.0.0 is not + expect(the_bundle).to include_gems "myrack 0.9.1" + expect(the_bundle).not_to include_gems "myrack 1.0.0" end it "should not install if the wrong source is provided" do + bundle_config "path.system true" + gemfile <<-G source "#{source}" - gem "rack" + gem "myrack" G bundle :install, artifice: "compact_index" - simulate_new_machine - expect(the_bundle).not_to include_gems "rack 1.0.0" - expect(source_global_cache("rack-1.0.0.gem")).to exist - # rack 1.0.0 is not installed and it is in the global cache + 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 gemfile <<-G source "#{source2}" - gem "rack", "0.9.1" + gem "myrack", "0.9.1" G bundle :install, artifice: "compact_index" - simulate_new_machine - expect(the_bundle).not_to include_gems "rack 0.9.1" - expect(source2_global_cache("rack-0.9.1.gem")).to exist - # rack 0.9.1 is not installed and it is in the global cache + 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 gemfile <<-G source "#{source2}" - gem "rack", "1.0.0" + gem "myrack", "1.0.0" G - expect(source_global_cache("rack-1.0.0.gem")).to exist - expect(source2_global_cache("rack-0.9.1.gem")).to exist + expect(source_global_cache("myrack-1.0.0.gem")).to exist + expect(source2_global_cache("myrack-0.9.1.gem")).to exist bundle :install, artifice: "compact_index_no_gem", raise_on_error: false expect(err).to include("Internal Server Error 500") expect(err).not_to include("ERROR REPORT TEMPLATE") - # rack 1.0.0 is not installed and rack 0.9.1 is not - expect(the_bundle).not_to include_gems "rack 1.0.0" - expect(the_bundle).not_to include_gems "rack 0.9.1" + # myrack 1.0.0 is not installed and myrack 0.9.1 is not + expect(the_bundle).not_to include_gems "myrack 1.0.0" + expect(the_bundle).not_to include_gems "myrack 0.9.1" gemfile <<-G source "#{source}" - gem "rack", "0.9.1" + gem "myrack", "0.9.1" G - expect(source_global_cache("rack-1.0.0.gem")).to exist - expect(source2_global_cache("rack-0.9.1.gem")).to exist + expect(source_global_cache("myrack-1.0.0.gem")).to exist + expect(source2_global_cache("myrack-0.9.1.gem")).to exist bundle :install, artifice: "compact_index_no_gem", raise_on_error: false expect(err).to include("Internal Server Error 500") expect(err).not_to include("ERROR REPORT TEMPLATE") - # rack 0.9.1 is not installed and rack 1.0.0 is not - expect(the_bundle).not_to include_gems "rack 0.9.1" - expect(the_bundle).not_to include_gems "rack 1.0.0" + # myrack 0.9.1 is not installed and myrack 1.0.0 is not + expect(the_bundle).not_to include_gems "myrack 0.9.1" + expect(the_bundle).not_to include_gems "myrack 1.0.0" end end 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 "rack" + gem "myrack" gem "activesupport" G # Both gems are installed and in the global cache - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" expect(the_bundle).to include_gems "activesupport 2.3.5" - expect(source_global_cache("rack-1.0.0.gem")).to exist + 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 "rack 1.0.0" + expect(the_bundle).not_to include_gems "myrack 1.0.0" expect(the_bundle).not_to include_gems "activesupport 2.3.5" install_gemfile <<-G, artifice: "compact_index_no_gem" source "#{source}" - gem "rack" + gem "myrack" G - # rack is installed and both are in the global cache - expect(the_bundle).to include_gems "rack 1.0.0" + # myrack is installed and both are in the global cache + expect(the_bundle).to include_gems "myrack 1.0.0" expect(the_bundle).not_to include_gems "activesupport 2.3.5" - expect(source_global_cache("rack-1.0.0.gem")).to exist + expect(source_global_cache("myrack-1.0.0.gem")).to exist expect(source_global_cache("activesupport-2.3.5.gem")).to exist create_file bundled_app2("gems.rb"), <<-G @@ -183,9 +235,9 @@ RSpec.describe "global gem caching" do G # Neither gem is installed and both are in the global cache - expect(the_bundle).not_to include_gems "rack 1.0.0", dir: bundled_app2 + expect(the_bundle).not_to include_gems "myrack 1.0.0", dir: bundled_app2 expect(the_bundle).not_to include_gems "activesupport 2.3.5", dir: bundled_app2 - expect(source_global_cache("rack-1.0.0.gem")).to exist + expect(source_global_cache("myrack-1.0.0.gem")).to exist expect(source_global_cache("activesupport-2.3.5.gem")).to exist # Install using the global cache instead of by downloading the .gem @@ -193,10 +245,10 @@ RSpec.describe "global gem caching" do bundle :install, artifice: "compact_index_no_gem", dir: bundled_app2 # activesupport is installed and both are in the global cache - expect(the_bundle).not_to include_gems "rack 1.0.0", dir: bundled_app2 + expect(the_bundle).not_to include_gems "myrack 1.0.0", dir: bundled_app2 expect(the_bundle).to include_gems "activesupport 2.3.5", dir: bundled_app2 - expect(source_global_cache("rack-1.0.0.gem")).to exist + expect(source_global_cache("myrack-1.0.0.gem")).to exist expect(source_global_cache("activesupport-2.3.5.gem")).to exist end end @@ -205,13 +257,14 @@ 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 revision = revision_for(lib_path("very_simple_git_binary-1.0"))[0, 12] install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "very_simple_binary" gem "very_simple_git_binary", :git => "#{lib_path("very_simple_git_binary-1.0")}" @@ -219,7 +272,7 @@ RSpec.describe "global gem caching" do G gem_binary_cache = home(".bundle", "cache", "extensions", local_platform.to_s, Bundler.ruby_scope, - Digest(:MD5).hexdigest("#{gem_repo1}/"), "very_simple_binary-1.0") + "gem.repo1.443.#{Digest(:MD5).hexdigest("gem.repo1.443./")}", "very_simple_binary-1.0") git_binary_cache = home(".bundle", "cache", "extensions", local_platform.to_s, Bundler.ruby_scope, "very_simple_git_binary-1.0-#{revision}", "very_simple_git_binary-1.0") @@ -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 0a30e402b7..49360e511e 100644 --- a/spec/bundler/install/path_spec.rb +++ b/spec/bundler/install/path_spec.rb @@ -3,27 +3,27 @@ RSpec.describe "bundle install" do describe "with path configured" do before :each do - build_gem "rack", "1.0.0", to_system: true do |s| - s.write "lib/rack.rb", "puts 'FAIL'" + build_gem "myrack", "1.0.0", to_system: true do |s| + s.write "lib/myrack.rb", "puts 'FAIL'" end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G 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 "rack 1.0.0" + 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 'rack'", raise_on_error: false + run "require 'myrack'", raise_on_error: false expect(out).to include("FAIL") end @@ -31,69 +31,57 @@ 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/rack-1.0.0")).to be_directory - expect(the_bundle).to include_gems "rack 1.0.0" + 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 "rack 1.0.0" - end - - it "installs the standalone bundle relative to the cwd" do - bundle :install, gemfile: bundled_app_gemfile, standalone: true, dir: bundled_app.parent - 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" + 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 :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 describe "when BUNDLE_PATH or the global path config is set" do before :each do - build_lib "rack", "1.0.0", to_system: true do |s| - s.write "lib/rack.rb", "raise 'FAIL'" + build_lib "myrack", "1.0.0", to_system: true do |s| + s.write "lib/myrack.rb", "raise 'FAIL'" end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G end @@ -109,23 +97,23 @@ 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/rack-1.0.0")).to be_directory + expect(vendored_gems("gems/myrack-1.0.0")).to be_directory expect(bundled_app("vendor2")).not_to be_directory - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end 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 - paths_to_exist = %w[cache/rack-1.0.0.gem gems/rack-1.0.0 specifications/rack-1.0.0.gemspec].map {|path| bundled_app(Bundler.ruby_scope, path) } + paths_to_exist = %w[cache/myrack-1.0.0.gem gems/myrack-1.0.0 specifications/myrack-1.0.0.gemspec].map {|path| bundled_app(Bundler.ruby_scope, path) } expect(paths_to_exist).to all exist - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "installs gems to the path" do @@ -133,8 +121,8 @@ RSpec.describe "bundle install" do bundle :install - expect(bundled_app("vendor", Bundler.ruby_scope, "gems/rack-1.0.0")).to be_directory - expect(the_bundle).to include_gems "rack 1.0.0" + expect(bundled_app("vendor", Bundler.ruby_scope, "gems/myrack-1.0.0")).to be_directory + expect(the_bundle).to include_gems "myrack 1.0.0" end it "installs gems to the path relative to root when relative" do @@ -143,37 +131,37 @@ RSpec.describe "bundle install" do FileUtils.mkdir_p bundled_app("lol") bundle :install, dir: bundled_app("lol") - expect(bundled_app("vendor", Bundler.ruby_scope, "gems/rack-1.0.0")).to be_directory - expect(the_bundle).to include_gems "rack 1.0.0" + expect(bundled_app("vendor", Bundler.ruby_scope, "gems/myrack-1.0.0")).to be_directory + expect(the_bundle).to include_gems "myrack 1.0.0" end end 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 - expect(vendored_gems("gems/rack-1.0.0")).to be_directory - expect(the_bundle).to include_gems "rack 1.0.0" + expect(vendored_gems("gems/myrack-1.0.0")).to be_directory + expect(the_bundle).to include_gems "myrack 1.0.0" 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/rack-1.0.0")).to be_directory - expect(the_bundle).to include_gems "rack 1.0.0" + expect(vendored_gems("gems/myrack-1.0.0")).to be_directory + expect(the_bundle).to include_gems "myrack 1.0.0" end it "disables system gems when passing a path to install" do # This is so that vendored gems can be distributed to others - build_gem "rack", "1.1.0", to_system: true - bundle "config set --local path ./vendor/bundle" + build_gem "myrack", "1.1.0", to_system: true + bundle_config "path ./vendor/bundle" bundle :install - expect(vendored_gems("gems/rack-1.0.0")).to be_directory - expect(the_bundle).to include_gems "rack 1.0.0" + expect(vendored_gems("gems/myrack-1.0.0")).to be_directory + expect(the_bundle).to include_gems "myrack 1.0.0" end it "re-installs gems whose extensions have been deleted" do @@ -182,23 +170,23 @@ RSpec.describe "bundle install" do end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" 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 @@ -214,11 +202,11 @@ RSpec.describe "bundle install" do it "reports the file exists" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + 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 629eb89dac..9f764d127c 100644 --- a/spec/bundler/install/prereleases_spec.rb +++ b/spec/bundler/install/prereleases_spec.rb @@ -13,7 +13,7 @@ RSpec.describe "bundle install" do describe "when prerelease gems are available" do it "finds prereleases" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "not_released" G expect(the_bundle).to include_gems "not_released 1.0.pre" @@ -21,7 +21,7 @@ RSpec.describe "bundle install" do it "uses regular releases if available" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "has_prerelease" G expect(the_bundle).to include_gems "has_prerelease 1.0" @@ -29,7 +29,7 @@ RSpec.describe "bundle install" do it "uses prereleases if requested" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "has_prerelease", "1.1.pre" G expect(the_bundle).to include_gems "has_prerelease 1.1.pre" @@ -38,17 +38,17 @@ RSpec.describe "bundle install" do describe "when prerelease gems are not available" do it "still works" do - build_repo gem_repo3 do - build_gem "rack" + 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 "#{file_uri_for(gem_repo3)}" - gem "rack" + source "https://gem.repo3" + gem "myrack" G - expect(the_bundle).to include_gems "rack 1.0" + expect(the_bundle).to include_gems "myrack 1.0" end end end diff --git a/spec/bundler/install/process_lock_spec.rb b/spec/bundler/install/process_lock_spec.rb index 1f8c62f26e..b096291d1a 100644 --- a/spec/bundler/install/process_lock_spec.rb +++ b/spec/bundler/install/process_lock_spec.rb @@ -8,23 +8,23 @@ RSpec.describe "process lock spec" do thread = Thread.new do Bundler::ProcessLock.lock(default_bundle_path) do sleep 1 # ignore quality_spec - expect(the_bundle).not_to include_gems "rack 1.0" + expect(the_bundle).not_to include_gems "myrack 1.0" end end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G thread.join - expect(the_bundle).to include_gems "rack 1.0" + 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 lock file and yields" do + it "skips creating the lockfile and yields" do processed = false Bundler::ProcessLock.lock(default_bundle_path) { processed = true } @@ -35,7 +35,7 @@ RSpec.describe "process lock spec" do context "when creating a lock raises Errno::EPERM" do before { allow(File).to receive(:open).and_raise(Errno::EPERM) } - it "skips creating the lock file and yields" do + it "skips creating the lockfile and yields" do processed = false Bundler::ProcessLock.lock(default_bundle_path) { processed = true } @@ -46,12 +46,68 @@ RSpec.describe "process lock spec" do context "when creating a lock raises Errno::EROFS" do before { allow(File).to receive(:open).and_raise(Errno::EROFS) } - it "skips creating the lock file and yields" do + 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/redownload_spec.rb b/spec/bundler/install/redownload_spec.rb deleted file mode 100644 index 3a72c356d9..0000000000 --- a/spec/bundler/install/redownload_spec.rb +++ /dev/null @@ -1,91 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe "bundle install" do - before :each do - gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" - G - end - - shared_examples_for "an option to force redownloading gems" do - it "re-installs installed gems" do - rack_lib = default_bundle_path("gems/rack-1.0.0/lib/rack.rb") - - bundle :install - rack_lib.open("w") {|f| f.write("blah blah blah") } - bundle :install, flag => true - - expect(out).to include "Installing rack 1.0.0" - expect(rack_lib.open(&:read)).to eq("RACK = '1.0.0'\n") - expect(the_bundle).to include_gems "rack 1.0.0" - end - - it "works on first bundle install" do - bundle :install, flag => true - - expect(out).to include "Installing rack 1.0.0" - expect(the_bundle).to include_gems "rack 1.0.0" - end - - context "with a git gem" do - let!(:ref) { build_git("foo", "1.0").ref_for("HEAD", 11) } - - before do - gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "foo", :git => "#{lib_path("foo-1.0")}" - G - end - - it "re-installs installed gems" do - foo_lib = default_bundle_path("bundler/gems/foo-1.0-#{ref}/lib/foo.rb") - - bundle :install - foo_lib.open("w") {|f| f.write("blah blah blah") } - bundle :install, flag => true - - expect(foo_lib.open(&:read)).to eq("FOO = '1.0'\n") - expect(the_bundle).to include_gems "foo 1.0" - end - - it "works on first bundle install" do - bundle :install, flag => true - - expect(the_bundle).to include_gems "foo 1.0" - end - end - end - - describe "with --force", bundler: 2 do - it_behaves_like "an option to force redownloading 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 - 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/security_policy_spec.rb b/spec/bundler/install/security_policy_spec.rb index befeb81da5..e7f64dc227 100644 --- a/spec/bundler/install/security_policy_spec.rb +++ b/spec/bundler/install/security_policy_spec.rb @@ -9,8 +9,8 @@ RSpec.describe "policies with unsigned gems" do before do build_security_repo gemfile <<-G - source "#{file_uri_for(security_repo)}" - gem "rack" + source "https://gems.security" + gem "myrack" gem "signed_gem" G end @@ -18,7 +18,7 @@ RSpec.describe "policies with unsigned gems" do it "will work after you try to deploy without a lock" do bundle "install --deployment", raise_on_error: false bundle :install - expect(the_bundle).to include_gems "rack 1.0", "signed_gem 1.0" + expect(the_bundle).to include_gems "myrack 1.0", "signed_gem 1.0" end it "will fail when given invalid security policy" do @@ -45,7 +45,7 @@ RSpec.describe "policies with signed gems and no CA" do before do build_security_repo gemfile <<-G - source "#{file_uri_for(security_repo)}" + source "https://gems.security" gem "signed_gem" G end diff --git a/spec/bundler/install/yanked_spec.rb b/spec/bundler/install/yanked_spec.rb index 7408c24327..c92af7bfb0 100644 --- a/spec/bundler/install/yanked_spec.rb +++ b/spec/bundler/install/yanked_spec.rb @@ -1,16 +1,14 @@ # 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: #{file_uri_for(gem_repo4)} + remote: https://gem.repo4 specs: foo (10.0.0) @@ -23,19 +21,42 @@ RSpec.context "when installing a bundle that includes yanked gems" do L install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "foo", "10.0.0" G expect(err).to include("Your bundle is locked to foo (10.0.0)") end - context "when a re-resolve is necessary, and a yanked version is considered by the resolver" do + context "when a platform specific yanked version is included in the lockfile, and a generic variant is available remotely" do + let(:original_lockfile) do + <<~L + GEM + remote: https://gem.repo4/ + specs: + actiontext (6.1.6) + nokogiri (>= 1.8) + foo (1.0.0) + nokogiri (1.13.8-#{Bundler.local_platform}) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + actiontext (= 6.1.6) + foo (= 1.0.0) + + BUNDLED WITH + #{Bundler::VERSION} + L + end + before do skip "Materialization on Windows is not yet strict, so the example does not detect the gem has been yanked" if Gem.win_platform? build_repo4 do - build_gem "foo", "1.0.0", "1.0.1" + build_gem "foo", "1.0.0" + build_gem "foo", "1.0.1" build_gem "actiontext", "6.1.7" do |s| s.add_dependency "nokogiri", ">= 1.8" end @@ -49,58 +70,58 @@ RSpec.context "when installing a bundle that includes yanked gems" do end gemfile <<~G - source "#{source_uri}" - gem "foo", "1.0.1" + source "https://gem.repo4" + gem "foo", "1.0.0" gem "actiontext", "6.1.6" G - lockfile <<~L - GEM - remote: #{source_uri}/ - specs: - actiontext (6.1.6) - nokogiri (>= 1.8) - foo (1.0.0) - nokogiri (1.13.8-#{Bundler.local_platform}) + lockfile original_lockfile + end - PLATFORMS - #{lockfile_platforms} + context "and a re-resolve is necessary" do + before do + gemfile gemfile.sub('"foo", "1.0.0"', '"foo", "1.0.1"') + end - DEPENDENCIES - actiontext (= 6.1.6) - foo (= 1.0.0) + it "reresolves, and replaces the yanked gem with the generic version, printing a warning, when the old index is used" do + bundle "install", artifice: "endpoint", verbose: true - BUNDLED WITH - #{Bundler::VERSION} - L - end - - context "and the old index is used" do - let(:source_uri) { file_uri_for(gem_repo4) } + expect(out).to include("Installing nokogiri 1.13.8").and include("Installing foo 1.0.1") + expect(lockfile).to eq(original_lockfile.sub("nokogiri (1.13.8-#{Bundler.local_platform})", "nokogiri (1.13.8)").gsub("1.0.0", "1.0.1")) + expect(err).to include("Some locked specs have possibly been yanked (nokogiri-1.13.8-#{Bundler.local_platform}). Ignoring them...") + end - it "reports the yanked gem properly" do - bundle "install", raise_on_error: false + it "reresolves, and replaces the yanked gem with the generic version, printing a warning, when the compact index API is used" do + bundle "install", artifice: "compact_index", verbose: true - expect(err).to include("Your bundle is locked to nokogiri (1.13.8-#{Bundler.local_platform})") + expect(out).to include("Installing nokogiri 1.13.8").and include("Installing foo 1.0.1") + expect(lockfile).to eq(original_lockfile.sub("nokogiri (1.13.8-#{Bundler.local_platform})", "nokogiri (1.13.8)").gsub("1.0.0", "1.0.1")) + expect(err).to include("Some locked specs have possibly been yanked (nokogiri-1.13.8-#{Bundler.local_platform}). Ignoring them...") end end - context "and the compact index API is used" do - let(:source_uri) { "https://gem.repo4" } + it "reports the yanked gem properly when the old index is used" do + bundle "install", artifice: "endpoint", raise_on_error: false - it "reports the yanked gem properly" do - bundle "install", artifice: "compact_index", raise_on_error: false + expect(err).to include("Your bundle is locked to nokogiri (1.13.8-#{Bundler.local_platform})") + end - expect(err).to include("Your bundle is locked to nokogiri (1.13.8-#{Bundler.local_platform})") - end + it "reports the yanked gem properly when the compact index API is used" do + bundle "install", artifice: "compact_index", raise_on_error: false + + expect(err).to include("Your bundle is locked to nokogiri (1.13.8-#{Bundler.local_platform})") end end 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 "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "foo", "10.0.0" G @@ -120,7 +141,7 @@ RSpec.context "when resolving a bundle that includes yanked gems, but unlocking lockfile <<-L GEM - remote: #{file_uri_for(gem_repo4)} + remote: https://gem.repo4 specs: foo (9.0.0) bar (1.0.0) @@ -133,11 +154,11 @@ RSpec.context "when resolving a bundle that includes yanked gems, but unlocking bar BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "foo" gem "bar" G @@ -148,7 +169,7 @@ RSpec.context "when resolving a bundle that includes yanked gems, but unlocking expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: bar (2.0.0) foo (9.0.0) @@ -161,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 @@ -169,65 +190,65 @@ end RSpec.context "when using gem before installing" do it "does not suggest the author has yanked the gem" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "0.9.1" + source "https://gem.repo1" + gem "myrack", "0.9.1" G lockfile <<-L GEM - remote: #{file_uri_for(gem_repo1)} + remote: https://gem.repo1 specs: - rack (0.9.1) + myrack (0.9.1) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack (= 0.9.1) + myrack (= 0.9.1) L bundle :list, raise_on_error: false - expect(err).to include("Could not find rack-0.9.1 in cached gems or installed locally") - expect(err).to_not include("Your bundle is locked to rack (0.9.1) from") - expect(err).to_not include("If you haven't changed sources, that means the author of rack (0.9.1) has removed it.") - expect(err).to_not include("You'll need to update your bundle to a different version of rack (0.9.1) that hasn't been removed in order to install.") + expect(err).to include("Could not find myrack-0.9.1 in locally installed gems") + expect(err).to_not include("Your bundle is locked to myrack (0.9.1) from") + expect(err).to_not include("If you haven't changed sources, that means the author of myrack (0.9.1) has removed it.") + expect(err).to_not include("You'll need to update your bundle to a different version of myrack (0.9.1) that hasn't been removed in order to install.") # Check error message is still correct when multiple platforms are locked lockfile lockfile.gsub(/PLATFORMS\n #{lockfile_platforms}/m, "PLATFORMS\n #{lockfile_platforms("ruby")}") bundle :list, raise_on_error: false - expect(err).to include("Could not find rack-0.9.1 in cached gems or installed locally") + expect(err).to include("Could not find myrack-0.9.1 in locally installed gems") end it "does not suggest the author has yanked the gem when using more than one gem, but shows all gems that couldn't be found in the source" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "0.9.1" - gem "rack_middleware", "1.0" + source "https://gem.repo1" + gem "myrack", "0.9.1" + gem "myrack_middleware", "1.0" G lockfile <<-L GEM - remote: #{file_uri_for(gem_repo1)} + remote: https://gem.repo1 specs: - rack (0.9.1) - rack_middleware (1.0) + myrack (0.9.1) + myrack_middleware (1.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack (= 0.9.1) - rack_middleware (1.0) + myrack (= 0.9.1) + myrack_middleware (1.0) L bundle :list, raise_on_error: false - expect(err).to include("Could not find rack-0.9.1, rack_middleware-1.0 in cached gems or installed locally") + expect(err).to include("Could not find myrack-0.9.1, myrack_middleware-1.0 in locally installed gems") expect(err).to include("Install missing gems with `bundle install`.") - expect(err).to_not include("Your bundle is locked to rack (0.9.1) from") - expect(err).to_not include("If you haven't changed sources, that means the author of rack (0.9.1) has removed it.") - expect(err).to_not include("You'll need to update your bundle to a different version of rack (0.9.1) that hasn't been removed in order to install.") + expect(err).to_not include("Your bundle is locked to myrack (0.9.1) from") + expect(err).to_not include("If you haven't changed sources, that means the author of myrack (0.9.1) has removed it.") + expect(err).to_not include("You'll need to update your bundle to a different version of myrack (0.9.1) that hasn't been removed in order to install.") end end diff --git a/spec/bundler/lock/git_spec.rb b/spec/bundler/lock/git_spec.rb index ad13e8ffc6..c9f76115dc 100644 --- a/spec/bundler/lock/git_spec.rb +++ b/spec/bundler/lock/git_spec.rb @@ -1,21 +1,25 @@ # frozen_string_literal: true RSpec.describe "bundle lock with git gems" do - before :each do + let(:install_gemfile_with_foo_as_a_git_dependency) do build_git "foo" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'foo', :git => "#{lib_path("foo-1.0")}" G end it "doesn't break right after running lock" do + install_gemfile_with_foo_as_a_git_dependency + expect(the_bundle).to include_gems "foo 1.0.0" end it "doesn't print errors even if running lock after removing the cache" do - FileUtils.rm_rf(Dir[default_cache_path("git/foo-1.0-*")].first) + install_gemfile_with_foo_as_a_git_dependency + + FileUtils.rm_r(Dir[default_cache_path("git/foo-1.0-*")].first) bundle "lock --verbose" @@ -23,8 +27,10 @@ RSpec.describe "bundle lock with git gems" do end it "prints a proper error when changing a locked Gemfile to point to a bad branch" do + install_gemfile_with_foo_as_a_git_dependency + gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'foo', :git => "#{lib_path("foo-1.0")}", :branch => "bad" G @@ -34,6 +40,8 @@ RSpec.describe "bundle lock with git gems" do end it "prints a proper error when installing a Gemfile with a locked ref that does not exist" do + install_gemfile_with_foo_as_a_git_dependency + lockfile <<~L GIT remote: #{lib_path("foo-1.0")} @@ -42,7 +50,7 @@ RSpec.describe "bundle lock with git gems" do foo (1.0) GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS @@ -52,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 @@ -61,6 +69,8 @@ RSpec.describe "bundle lock with git gems" do end it "locks a git source to the current ref" do + install_gemfile_with_foo_as_a_git_dependency + update_git "foo" bundle :install @@ -73,6 +83,8 @@ RSpec.describe "bundle lock with git gems" do end it "properly clones a git source locked to an out of date ref" do + install_gemfile_with_foo_as_a_git_dependency + update_git "foo" bundle :install, env: { "BUNDLE_PATH" => "foo" } @@ -80,6 +92,8 @@ RSpec.describe "bundle lock with git gems" do end it "properly fetches a git source locked to an unreachable ref" do + install_gemfile_with_foo_as_a_git_dependency + # Create a commit and make it unreachable git "checkout -b foo ", lib_path("foo-1.0") unreachable_sha = update_git("foo").ref_for("HEAD") @@ -87,7 +101,7 @@ RSpec.describe "bundle lock with git gems" do git "branch -D foo ", lib_path("foo-1.0") gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'foo', :git => "#{lib_path("foo-1.0")}" G @@ -99,7 +113,7 @@ RSpec.describe "bundle lock with git gems" do foo (1.0) GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS @@ -109,7 +123,7 @@ RSpec.describe "bundle lock with git gems" do foo! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install" @@ -118,12 +132,14 @@ RSpec.describe "bundle lock with git gems" do end it "properly fetches a git source locked to an annotated tag" do + install_gemfile_with_foo_as_a_git_dependency + # Create an annotated tag git("tag -a v1.0 -m 'Annotated v1.0'", lib_path("foo-1.0")) annotated_tag = git("rev-parse v1.0", lib_path("foo-1.0")) gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'foo', :git => "#{lib_path("foo-1.0")}" G @@ -135,7 +151,7 @@ RSpec.describe "bundle lock with git gems" do foo (1.0) GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS @@ -145,7 +161,7 @@ RSpec.describe "bundle lock with git gems" do foo! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install" @@ -154,9 +170,89 @@ RSpec.describe "bundle lock with git gems" do end it "provides correct #full_gem_path" do + install_gemfile_with_foo_as_a_git_dependency + run <<-RUBY puts Bundler.rubygems.find_name('foo').first.full_gem_path RUBY expect(out).to eq(bundle("info foo --path")) end + + it "does not lock versions that don't exist in the repository when changing a GEM transitive dep to a GIT direct dep" do + build_repo4 do + build_gem "activesupport", "8.0.0" do |s| + s.add_dependency "securerandom" + end + + build_gem "securerandom", "0.3.1" + end + + path = lib_path("securerandom") + + build_git "securerandom", "0.3.2", path: path + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + activesupport (8.0.0) + securerandom + securerandom (0.3.1) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + activesupport + + BUNDLED WITH + #{Bundler::VERSION} + L + + gemfile <<~G + source "https://gem.repo4" + + gem "activesupport" + gem "securerandom", git: "#{path}" + G + + bundle "lock" + + expect(lockfile).to include("securerandom (0.3.2)") + end + + it "does not lock versions that don't exist in the repository when changing a GIT direct dep to a GEM direct dep" do + build_repo4 do + build_gem "ruby-lsp", "0.16.1" + end + + path = lib_path("ruby-lsp") + revision = build_git("ruby-lsp", "0.16.2", path: path).ref_for("HEAD") + + lockfile <<~L + GIT + remote: #{path} + revision: #{revision} + specs: + ruby-lsp (0.16.2) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ruby-lsp! + + BUNDLED WITH + #{Bundler::VERSION} + L + + gemfile <<~G + source "https://gem.repo4" + gem "ruby-lsp" + G + + bundle "lock" + + expect(lockfile).to include("ruby-lsp (0.16.1)") + end end diff --git a/spec/bundler/lock/lockfile_spec.rb b/spec/bundler/lock/lockfile_spec.rb index 7f664abc4d..654ac02aa7 100644 --- a/spec/bundler/lock/lockfile_spec.rb +++ b/spec/bundler/lock/lockfile_spec.rb @@ -6,30 +6,30 @@ RSpec.describe "the lockfile format" do end it "generates a simple lockfile for a single source, gem" do - checksums = checksums_section_when_existing do |c| - c.checksum(gem_repo2, "rack", "1.0.0") + checksums = checksums_section_when_enabled do |c| + c.checksum(gem_repo2, "myrack", "1.0.0") end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" - gem "rack" + gem "myrack" G expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack + myrack #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -43,25 +43,25 @@ RSpec.describe "the lockfile format" do specs: GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES omg! - rack + myrack BUNDLED WITH 1.8.2 L install_gemfile <<-G, verbose: true, env: { "BUNDLER_VERSION" => Bundler::VERSION } - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" - gem "rack" + gem "myrack" G expect(out).not_to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with 1.8.2.") @@ -69,44 +69,50 @@ RSpec.describe "the lockfile format" do expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack + myrack BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end - it "does not update the lockfile's bundler version if nothing changed during bundle install, but uses the locked version", rubygems: ">= 3.3.0.a", realworld: true do + it "does not update the lockfile's bundler version if nothing changed during bundle install, but uses the locked version" do version = "2.3.0" + build_repo4 do + build_gem "myrack", "1.0.0" + + build_bundler version + end + lockfile <<-L GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo4/ specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack + myrack BUNDLED WITH #{version} L - install_gemfile <<-G, verbose: true, artifice: "vcr" - source "#{file_uri_for(gem_repo2)}" + install_gemfile <<-G, verbose: true + source "https://gem.repo4" - gem "rack" + gem "myrack" G expect(out).to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{version}.") @@ -114,277 +120,342 @@ RSpec.describe "the lockfile format" do expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo4/ specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack + myrack BUNDLED WITH #{version} G end - it "does not update the lockfile's bundler version if nothing changed during bundle install, and uses the latest version", rubygems: "< 3.3.0.a" do - version = "#{Bundler::VERSION.split(".").first}.0.0.a" - - checksums = checksums_section do |c| - c.checksum(gem_repo2, "rack", "1.0.0") - end - + it "adds the BUNDLED WITH section if not present" do lockfile <<-L GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack - #{checksums} - BUNDLED WITH - #{version} + myrack L - install_gemfile <<-G, verbose: true - source "#{file_uri_for(gem_repo2)}" + install_gemfile <<-G + source "https://gem.repo2" - gem "rack" + gem "myrack", "> 0" G - expect(out).not_to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{version}.") - expect(out).to include("Using bundler #{Bundler::VERSION}") - expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack - #{checksums} + myrack (> 0) + BUNDLED WITH - #{version} + #{Bundler::VERSION} G end - it "adds the BUNDLED WITH section if not present" do + it "update the bundler major version just fine" do + current_version = Bundler::VERSION + older_major = previous_major(current_version) + + system_gems "bundler-#{older_major}" + lockfile <<-L GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack + myrack + + BUNDLED WITH + #{older_major} L - install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + install_gemfile <<-G, env: { "BUNDLER_VERSION" => Bundler::VERSION } + source "https://gem.repo2/" - gem "rack", "> 0" + gem "myrack" G + expect(err).to be_empty + expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack (> 0) + myrack BUNDLED WITH - #{Bundler::VERSION} + #{current_version} G end - it "update the bundler major version just fine" do - current_version = Bundler::VERSION - older_major = previous_major(current_version) + it "generates a simple lockfile for a single source, gem with dependencies" do + install_gemfile <<-G + source "https://gem.repo2/" - system_gems "bundler-#{older_major}" + gem "myrack-obama" + G - lockfile <<-L + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "myrack", "1.0.0" + c.checksum gem_repo2, "myrack-obama", "1.0" + end + + expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: - rack (1.0.0) + myrack (1.0.0) + myrack-obama (1.0) + myrack PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack - + myrack-obama + #{checksums} BUNDLED WITH - #{older_major} - L + #{Bundler::VERSION} + G + end - install_gemfile <<-G, env: { "BUNDLER_VERSION" => Bundler::VERSION } - source "#{file_uri_for(gem_repo2)}/" + it "generates a simple lockfile for a single source, gem with a version requirement" do + install_gemfile <<-G + source "https://gem.repo2/" - gem "rack" + gem "myrack-obama", ">= 1.0" G - expect(err).to be_empty + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "myrack", "1.0.0" + c.checksum gem_repo2, "myrack-obama", "1.0" + end expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: - rack (1.0.0) + myrack (1.0.0) + myrack-obama (1.0) + myrack PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack - + myrack-obama (>= 1.0) + #{checksums} BUNDLED WITH - #{current_version} + #{Bundler::VERSION} G end - it "generates a simple lockfile for a single source, gem with dependencies" do - install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}/" + it "generates a lockfile without credentials" do + bundle "config set https://localgemserver.test/ user:pass" + + install_gemfile(<<-G, artifice: "endpoint_strict_basic_authentication", quiet: true) + source "https://gem.repo1" - gem "rack-obama" + source "https://localgemserver.test/" do + + end + + source "https://user:pass@othergemserver.test/" do + gem "myrack-obama", ">= 1.0" + end G - checksums = checksums_section_when_existing do |c| - c.checksum gem_repo2, "rack", "1.0.0" - c.checksum gem_repo2, "rack-obama", "1.0" + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "myrack", "1.0.0" + c.checksum gem_repo2, "myrack-obama", "1.0" end expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo1/ specs: - rack (1.0.0) - rack-obama (1.0) - rack + + GEM + remote: https://localgemserver.test/ + specs: + + GEM + remote: https://othergemserver.test/ + specs: + myrack (1.0.0) + myrack-obama (1.0) + myrack PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack-obama + myrack-obama (>= 1.0)! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end - it "generates a simple lockfile for a single source, gem with a version requirement" do - install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}/" + it "does not add credentials to lockfile when it does not have them already" do + bundle "config set http://localgemserver.test/ user:pass" + + gemfile <<~G + source "https://gem.repo1" + + source "http://localgemserver.test/" do - gem "rack-obama", ">= 1.0" + end + + source "http://user:pass@othergemserver.test/" do + gem "myrack-obama", ">= 1.0" + end G - checksums = checksums_section_when_existing do |c| - c.checksum gem_repo2, "rack", "1.0.0" - c.checksum gem_repo2, "rack-obama", "1.0" + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "myrack", "1.0.0" + c.checksum gem_repo2, "myrack-obama", "1.0" end - expect(lockfile).to eq <<~G + lockfile_without_credentials = <<~L GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: http://localgemserver.test/ + specs: + + GEM + remote: http://othergemserver.test/ + specs: + myrack (1.0.0) + myrack-obama (1.0) + myrack + + GEM + remote: https://gem.repo1/ specs: - rack (1.0.0) - rack-obama (1.0) - rack PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack-obama (>= 1.0) + myrack-obama (>= 1.0)! #{checksums} BUNDLED WITH - #{Bundler::VERSION} - G + #{Bundler::VERSION} + L + + lockfile lockfile_without_credentials + + # when not re-resolving + bundle "install", artifice: "endpoint_strict_basic_authentication", quiet: true + expect(lockfile).to eq lockfile_without_credentials + + # when re-resolving with full unlock + bundle "update", artifice: "endpoint_strict_basic_authentication" + expect(lockfile).to eq lockfile_without_credentials + + # when re-resolving without ful unlocking + bundle "update myrack-obama", artifice: "endpoint_strict_basic_authentication" + expect(lockfile).to eq lockfile_without_credentials end - it "generates a lockfile without credentials for a configured source" do + it "keeps credentials in lockfile if already there" do bundle "config set http://localgemserver.test/ user:pass" - install_gemfile(<<-G, artifice: "endpoint_strict_basic_authentication", quiet: true) - source "#{file_uri_for(gem_repo1)}" + gemfile <<~G + source "https://gem.repo1" source "http://localgemserver.test/" do end source "http://user:pass@othergemserver.test/" do - gem "rack-obama", ">= 1.0" + gem "myrack-obama", ">= 1.0" end G - checksums = checksums_section_when_existing do |c| - c.checksum gem_repo2, "rack", "1.0.0" - c.checksum gem_repo2, "rack-obama", "1.0" + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "myrack", "1.0.0" + c.checksum gem_repo2, "myrack-obama", "1.0" end - expect(lockfile).to eq <<~G + lockfile_with_credentials = <<~L GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: http://localgemserver.test/ specs: GEM - remote: http://localgemserver.test/ + remote: http://user:pass@othergemserver.test/ specs: + myrack (1.0.0) + myrack-obama (1.0) + myrack GEM - remote: http://user:pass@othergemserver.test/ + remote: https://gem.repo1/ specs: - rack (1.0.0) - rack-obama (1.0) - rack PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack-obama (>= 1.0)! + myrack-obama (>= 1.0)! #{checksums} BUNDLED WITH - #{Bundler::VERSION} - G + #{Bundler::VERSION} + L + + lockfile lockfile_with_credentials + + bundle "install", artifice: "endpoint_strict_basic_authentication", quiet: true + + expect(lockfile).to eq lockfile_with_credentials end it "generates lockfiles with multiple requirements" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}/" + source "https://gem.repo2/" gem "net-sftp" G - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum gem_repo2, "net-sftp", "1.1.1" c.checksum gem_repo2, "net-ssh", "1.0" end expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: net-sftp (1.1.1) net-ssh (>= 1.0.0, < 1.99.0) @@ -397,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" @@ -407,11 +478,11 @@ RSpec.describe "the lockfile format" do git = build_git "foo" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}" G - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.no_checksum "foo", "1.0" end @@ -423,7 +494,7 @@ RSpec.describe "the lockfile format" do foo (1.0) GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS @@ -433,7 +504,7 @@ RSpec.describe "the lockfile format" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -441,13 +512,13 @@ RSpec.describe "the lockfile format" do build_lib "omg", path: lib_path("omg") gemfile <<-G - source "#{file_uri_for(gem_repo2)}/" + source "https://gem.repo2/" platforms :#{not_local_tag} do gem "omg", :path => "#{lib_path("omg")}" end - gem "rack" + gem "myrack" G lockfile <<-L @@ -457,34 +528,34 @@ RSpec.describe "the lockfile format" do specs: GEM - remote: #{file_uri_for(gem_repo2)}// + remote: https://gem.repo2// specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{not_local} DEPENDENCIES omg! - rack + myrack BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install" - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "serializes global git sources" do git = build_git "foo" - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.no_checksum "foo", "1.0" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("foo-1.0")}" do gem "foo" end @@ -498,7 +569,7 @@ RSpec.describe "the lockfile format" do foo (1.0) GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS @@ -508,7 +579,7 @@ RSpec.describe "the lockfile format" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -516,12 +587,12 @@ RSpec.describe "the lockfile format" do git = build_git "foo" update_git "foo", branch: "omg" - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.no_checksum "foo", "1.0" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}", :branch => "omg" G @@ -534,7 +605,7 @@ RSpec.describe "the lockfile format" do foo (1.0) GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS @@ -544,7 +615,7 @@ RSpec.describe "the lockfile format" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -552,12 +623,12 @@ RSpec.describe "the lockfile format" do git = build_git "foo" update_git "foo", tag: "omg" - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.no_checksum "foo", "1.0" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}", :tag => "omg" G @@ -570,7 +641,7 @@ RSpec.describe "the lockfile format" do foo (1.0) GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS @@ -580,7 +651,7 @@ RSpec.describe "the lockfile format" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -600,7 +671,7 @@ RSpec.describe "the lockfile format" do s.add_dependency "orm_adapter" end - update_git "ckeditor", path: lib_path("ckeditor"), remote: file_uri_for(@remote.path) + update_git "ckeditor", path: lib_path("ckeditor"), remote: @remote.path update_git "ckeditor", path: lib_path("ckeditor"), tag: "v4.0.7" old_git = update_git "ckeditor", path: lib_path("ckeditor"), push: "v4.0.7" @@ -614,7 +685,7 @@ RSpec.describe "the lockfile format" do new_git = update_git "ckeditor", path: lib_path("ckeditor"), push: "v4.0.8" gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "ckeditor", :git => "#{@remote.path}", :tag => "v4.0.8" G @@ -628,7 +699,7 @@ RSpec.describe "the lockfile format" do orm_adapter GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: orm_adapter (0.4.1) @@ -639,7 +710,7 @@ RSpec.describe "the lockfile format" do ckeditor! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "lock" @@ -655,7 +726,7 @@ RSpec.describe "the lockfile format" do orm_adapter GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: orm_adapter (0.4.1) @@ -666,19 +737,19 @@ RSpec.describe "the lockfile format" do ckeditor! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end it "serializes pinned path sources to the lockfile" do build_lib "foo" - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.no_checksum "foo", "1.0" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :path => "#{lib_path("foo-1.0")}" G @@ -689,7 +760,7 @@ RSpec.describe "the lockfile format" do foo (1.0) GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS @@ -699,23 +770,22 @@ RSpec.describe "the lockfile format" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end it "serializes pinned path sources to the lockfile even when packaging" do build_lib "foo" - checksums = checksums_section_when_existing do |c| - c.no_checksum "foo", "1.0" - end - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :path => "#{lib_path("foo-1.0")}" G - bundle "config set cache_all true" + checksums = checksums_section_when_enabled do |c| + c.no_checksum "foo", "1.0" + end + bundle :cache bundle :install, local: true @@ -726,7 +796,7 @@ RSpec.describe "the lockfile format" do foo (1.0) GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS @@ -736,7 +806,7 @@ RSpec.describe "the lockfile format" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -744,16 +814,16 @@ RSpec.describe "the lockfile format" do build_lib "foo" bar = build_git "bar" - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.no_checksum "foo", "1.0" c.no_checksum "bar", "1.0" - c.checksum gem_repo2, "rack", "1.0.0" + c.checksum gem_repo2, "myrack", "1.0.0" end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}/" + source "https://gem.repo2/" - gem "rack" + gem "myrack" gem "foo", :path => "#{lib_path("foo-1.0")}" gem "bar", :git => "#{lib_path("bar-1.0")}" G @@ -771,9 +841,9 @@ RSpec.describe "the lockfile format" do foo (1.0) GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{lockfile_platforms} @@ -781,92 +851,92 @@ RSpec.describe "the lockfile format" do DEPENDENCIES bar! foo! - rack + myrack #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end it "removes redundant sources" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}/" + source "https://gem.repo2/" - gem "rack", :source => "#{file_uri_for(gem_repo2)}/" + gem "myrack", :source => "https://gem.repo2/" G - checksums = checksums_section_when_existing do |c| - c.checksum gem_repo2, "rack", "1.0.0" + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "myrack", "1.0.0" end expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack! + myrack! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end it "lists gems alphabetically" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}/" + source "https://gem.repo2/" gem "thin" gem "actionpack" - gem "rack-obama" + gem "myrack-obama" G - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum gem_repo2, "actionpack", "2.3.2" c.checksum gem_repo2, "activesupport", "2.3.2" - c.checksum gem_repo2, "rack", "1.0.0" - c.checksum gem_repo2, "rack-obama", "1.0" + c.checksum gem_repo2, "myrack", "1.0.0" + c.checksum gem_repo2, "myrack-obama", "1.0" c.checksum gem_repo2, "thin", "1.0" end expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: actionpack (2.3.2) activesupport (= 2.3.2) activesupport (2.3.2) - rack (1.0.0) - rack-obama (1.0) - rack + myrack (1.0.0) + myrack-obama (1.0) + myrack thin (1.0) - rack + myrack PLATFORMS #{lockfile_platforms} DEPENDENCIES actionpack - rack-obama + myrack-obama thin #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end it "orders dependencies' dependencies in alphabetical order" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}/" + source "https://gem.repo2/" gem "rails" G - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum gem_repo2, "actionmailer", "2.3.2" c.checksum gem_repo2, "actionpack", "2.3.2" c.checksum gem_repo2, "activerecord", "2.3.2" @@ -878,7 +948,7 @@ RSpec.describe "the lockfile format" do expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: actionmailer (2.3.2) activesupport (= 2.3.2) @@ -904,7 +974,7 @@ RSpec.describe "the lockfile format" do rails #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -912,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" @@ -920,18 +990,18 @@ RSpec.describe "the lockfile format" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}/" + source "https://gem.repo2" gem 'double_deps' G - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum gem_repo2, "double_deps", "1.0" c.checksum gem_repo2, "net-ssh", "1.0" end expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: double_deps (1.0) net-ssh @@ -945,81 +1015,81 @@ RSpec.describe "the lockfile format" do double_deps #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end it "does not add the :require option to the lockfile" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}/" + source "https://gem.repo2/" - gem "rack-obama", ">= 1.0", :require => "rack/obama" + gem "myrack-obama", ">= 1.0", :require => "myrack/obama" G - checksums = checksums_section_when_existing do |c| - c.checksum gem_repo2, "rack", "1.0.0" - c.checksum gem_repo2, "rack-obama", "1.0" + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "myrack", "1.0.0" + c.checksum gem_repo2, "myrack-obama", "1.0" end expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: - rack (1.0.0) - rack-obama (1.0) - rack + myrack (1.0.0) + myrack-obama (1.0) + myrack PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack-obama (>= 1.0) + myrack-obama (>= 1.0) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end it "does not add the :group option to the lockfile" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}/" + source "https://gem.repo2/" - gem "rack-obama", ">= 1.0", :group => :test + gem "myrack-obama", ">= 1.0", :group => :test G - checksums = checksums_section_when_existing do |c| - c.checksum gem_repo2, "rack", "1.0.0" - c.checksum gem_repo2, "rack-obama", "1.0" + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "myrack", "1.0.0" + c.checksum gem_repo2, "myrack-obama", "1.0" end expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: - rack (1.0.0) - rack-obama (1.0) - rack + myrack (1.0.0) + myrack-obama (1.0) + myrack PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack-obama (>= 1.0) + myrack-obama (>= 1.0) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end it "stores relative paths when the path is provided in a relative fashion and in Gemfile dir" do build_lib "foo", path: bundled_app("foo") - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.no_checksum "foo", "1.0" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" path "foo" do gem "foo" end @@ -1032,7 +1102,7 @@ RSpec.describe "the lockfile format" do foo (1.0) GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS @@ -1042,19 +1112,19 @@ RSpec.describe "the lockfile format" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end it "stores relative paths when the path is provided in a relative fashion and is above Gemfile dir" do build_lib "foo", path: bundled_app(File.join("..", "foo")) - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.no_checksum "foo", "1.0" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" path "../foo" do gem "foo" end @@ -1067,7 +1137,7 @@ RSpec.describe "the lockfile format" do foo (1.0) GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS @@ -1077,24 +1147,24 @@ RSpec.describe "the lockfile format" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end it "stores relative paths when the path is provided in an absolute fashion but is relative" do build_lib "foo", path: bundled_app("foo") - checksums = checksums_section_when_existing do |c| - c.no_checksum "foo", "1.0" - end - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" path File.expand_path("foo", __dir__) do gem "foo" end G + checksums = checksums_section_when_enabled do |c| + c.no_checksum "foo", "1.0" + end + expect(lockfile).to eq <<~G PATH remote: foo @@ -1102,7 +1172,7 @@ RSpec.describe "the lockfile format" do foo (1.0) GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS @@ -1112,19 +1182,19 @@ RSpec.describe "the lockfile format" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end it "stores relative paths when the path is provided for gemspec" do - build_lib("foo", path: tmp.join("foo")) + build_lib("foo", path: tmp("foo")) - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.no_checksum "foo", "1.0" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gemspec :path => "../foo" G @@ -1135,7 +1205,7 @@ RSpec.describe "the lockfile format" do foo (1.0) GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS @@ -1145,57 +1215,57 @@ RSpec.describe "the lockfile format" do foo! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end it "keeps existing platforms in the lockfile" do - checksums = checksums_section_when_existing do |c| - c.no_checksum "rack", "1.0.0" + checksums = checksums_section_when_enabled do |c| + c.no_checksum "myrack", "1.0.0" end lockfile <<-G GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS java DEPENDENCIES - rack + myrack #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}/" + source "https://gem.repo2/" - gem "rack" + gem "myrack" G - checksums.checksum(gem_repo2, "rack", "1.0.0") + checksums.checksum(gem_repo2, "myrack", "1.0.0") expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{lockfile_platforms("java", local_platform, defaults: [])} DEPENDENCIES - rack + myrack #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end - it "adds compatible platform specific variants to the lockfile, even if resolution fallback to RUBY due to some other incompatible platform specific variant" do + it "adds compatible platform specific variants to the lockfile, even if resolution fallback to ruby due to some other incompatible platform specific variant" do simulate_platform "arm64-darwin-23" do build_repo4 do build_gem "google-protobuf", "3.25.1" @@ -1209,14 +1279,19 @@ RSpec.describe "the lockfile format" do end gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "google-protobuf" G bundle "lock --add-platform x64-mingw-ucrt" + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "google-protobuf", "3.25.1" + c.checksum gem_repo4, "google-protobuf", "3.25.1", "arm64-darwin-23" + end + expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: google-protobuf (3.25.1) google-protobuf (3.25.1-arm64-darwin-23) @@ -1228,9 +1303,9 @@ RSpec.describe "the lockfile format" do DEPENDENCIES google-protobuf - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -1242,255 +1317,537 @@ RSpec.describe "the lockfile format" do end end - simulate_platform "universal-java-16" - - install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "platform_specific" - G + simulate_platform "universal-java-16" do + install_gemfile <<-G + source "https://gem.repo2" + gem "platform_specific" + G - checksums = checksums_section_when_existing do |c| - c.checksum gem_repo2, "platform_specific", "1.0", "universal-java-16" - end + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "platform_specific", "1.0", "universal-java-16" + end - expect(lockfile).to eq <<~G - GEM - remote: #{file_uri_for(gem_repo2)}/ - specs: - platform_specific (1.0-universal-java-16) + expect(lockfile).to eq <<~G + GEM + remote: https://gem.repo2/ + specs: + platform_specific (1.0-universal-java-16) - PLATFORMS - universal-java-16 + PLATFORMS + universal-java-16 - DEPENDENCIES - platform_specific - #{checksums} - BUNDLED WITH - #{Bundler::VERSION} - G + DEPENDENCIES + platform_specific + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end end it "does not add duplicate gems" do - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.checksum(gem_repo2, "activesupport", "2.3.5") - c.checksum(gem_repo2, "rack", "1.0.0") + c.checksum(gem_repo2, "myrack", "1.0.0") end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}/" - gem "rack" + source "https://gem.repo2/" + gem "myrack" G install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}/" - gem "rack" + source "https://gem.repo2/" + gem "myrack" gem "activesupport" G expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: activesupport (2.3.5) - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES activesupport - rack + myrack #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end it "does not add duplicate dependencies" do - checksums = checksums_section_when_existing do |c| - c.checksum(gem_repo2, "rack", "1.0.0") + checksums = checksums_section_when_enabled do |c| + c.checksum(gem_repo2, "myrack", "1.0.0") end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}/" - gem "rack" - gem "rack" + source "https://gem.repo2/" + gem "myrack" + gem "myrack" G expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack + myrack #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end it "does not add duplicate dependencies with versions" do - checksums = checksums_section_when_existing do |c| - c.checksum(gem_repo2, "rack", "1.0.0") + checksums = checksums_section_when_enabled do |c| + c.checksum(gem_repo2, "myrack", "1.0.0") end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}/" - gem "rack", "1.0" - gem "rack", "1.0" + source "https://gem.repo2/" + gem "myrack", "1.0" + gem "myrack", "1.0" G expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack (= 1.0) + myrack (= 1.0) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end it "does not add duplicate dependencies in different groups" do - checksums = checksums_section_when_existing do |c| - c.checksum(gem_repo2, "rack", "1.0.0") + checksums = checksums_section_when_enabled do |c| + c.checksum(gem_repo2, "myrack", "1.0.0") end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}/" - gem "rack", "1.0", :group => :one - gem "rack", "1.0", :group => :two + source "https://gem.repo2/" + gem "myrack", "1.0", :group => :one + gem "myrack", "1.0", :group => :two G expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack (= 1.0) + myrack (= 1.0) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end it "raises if two different versions are used" do install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo2)}/" - gem "rack", "1.0" - gem "rack", "1.1" + source "https://gem.repo2/" + gem "myrack", "1.0" + gem "myrack", "1.1" G expect(bundled_app_lock).not_to exist - expect(err).to include "rack (= 1.0) and rack (= 1.1)" + expect(err).to include "myrack (= 1.0) and myrack (= 1.1)" end it "raises if two different sources are used" do install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo2)}/" - gem "rack" - gem "rack", :git => "git://hubz.com" + source "https://gem.repo2/" + gem "myrack" + gem "myrack", :git => "git://hubz.com" G expect(bundled_app_lock).not_to exist - expect(err).to include "rack (>= 0) should come from an unspecified source and git://hubz.com" + expect(err).to include "myrack (>= 0) should come from an unspecified source and git://hubz.com" end it "works correctly with multiple version dependencies" do - checksums = checksums_section_when_existing do |c| - c.checksum(gem_repo2, "rack", "0.9.1") + checksums = checksums_section_when_enabled do |c| + c.checksum(gem_repo2, "myrack", "0.9.1") end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}/" - gem "rack", "> 0.9", "< 1.0" + source "https://gem.repo2/" + gem "myrack", "> 0.9", "< 1.0" G expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: - rack (0.9.1) + myrack (0.9.1) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack (> 0.9, < 1.0) + myrack (> 0.9, < 1.0) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end it "captures the Ruby version in the lockfile" do - checksums = checksums_section_when_existing do |c| - c.checksum(gem_repo2, "rack", "0.9.1") + checksums = checksums_section_when_enabled do |c| + c.checksum(gem_repo2, "myrack", "0.9.1") end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}/" + source "https://gem.repo2/" ruby '#{Gem.ruby_version}' - gem "rack", "> 0.9", "< 1.0" + gem "myrack", "> 0.9", "< 1.0" G expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: - rack (0.9.1) + myrack (0.9.1) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack (> 0.9, < 1.0) + myrack (> 0.9, < 1.0) #{checksums} RUBY VERSION - #{Bundler::RubyVersion.system} + #{Bundler::RubyVersion.system} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end - it "raises a helpful error message when the lockfile is missing deps" do + it "automatically fixes the lockfile when it's missing deps" do lockfile <<-L GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ + specs: + myrack_middleware (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack_middleware + + BUNDLED WITH + #{Bundler::VERSION} + L + + install_gemfile <<-G + source "https://gem.repo2" + gem "myrack_middleware" + G + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo2/ + specs: + myrack (0.9.1) + myrack_middleware (1.0) + myrack (= 0.9.1) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack_middleware + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "raises a clear error when frozen mode is set and lockfile is missing deps, and does not install any gems" do + lockfile <<-L + GEM + remote: https://gem.repo2/ + specs: + myrack_middleware (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack_middleware + + BUNDLED WITH + #{Bundler::VERSION} + L + + install_gemfile <<-G, env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false + source "https://gem.repo2" + gem "myrack_middleware" + G + + expect(err).to include("Bundler found incorrect dependencies in the lockfile for myrack_middleware-1.0") + expect(err).to include("myrack: gemspec specifies = 0.9.1, not in lockfile") + expect(the_bundle).not_to include_gems "myrack_middleware 1.0" + end + + it "raises a clear error when frozen mode is set and lockfile is missing entries in CHECKSUMS section, and does not install any gems" do + lockfile <<-L + GEM + remote: https://gem.repo2/ + specs: + myrack_middleware (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack_middleware + + CHECKSUMS + + BUNDLED WITH + #{Bundler::VERSION} + L + + install_gemfile <<-G, env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false + source "https://gem.repo2" + gem "myrack_middleware" + G + + expect(err).to eq <<~L.strip + Your lockfile is missing a CHECKSUMS entry for \"myrack_middleware\", but can't be updated because frozen mode is set + + Run `bundle install` elsewhere and add the updated Gemfile.lock to version control. + L + + expect(the_bundle).not_to include_gems "myrack_middleware 1.0" + end + + it "raises a clear error when frozen mode is set and lockfile has empty checksums in CHECKSUMS section, and does not install any gems" do + lockfile <<-L + GEM + remote: https://gem.repo2/ + specs: + myrack (0.9.1) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack + + CHECKSUMS + myrack (0.9.1) + + BUNDLED WITH + #{Bundler::VERSION} + L + + install_gemfile <<-G, env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false + source "https://gem.repo2" + gem "myrack" + G + + expect(err).to eq <<~L.strip + Your lockfile has an empty CHECKSUMS entry for \"myrack\", but can't be updated because frozen mode is set + + Run `bundle install` elsewhere and add the updated Gemfile.lock to version control. + L + + expect(the_bundle).not_to include_gems "myrack 0.9.1" + end + + it "automatically fixes the lockfile when it's missing deps, they conflict with other locked deps, but conflicts are fixable" do + build_repo4 do + build_gem "other_dep", "0.9" + build_gem "other_dep", "1.0" + + build_gem "myrack", "0.9.1" + + build_gem "myrack_middleware", "1.0" do |s| + s.add_dependency "myrack", "= 0.9.1" + s.add_dependency "other_dep", "= 0.9" + end + end + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + myrack_middleware (1.0) + other_dep (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack_middleware + other_dep + + BUNDLED WITH + #{Bundler::VERSION} + L + + install_gemfile <<-G + source "https://gem.repo4" + gem "myrack_middleware" + gem "other_dep" + G + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + myrack (0.9.1) + myrack_middleware (1.0) + myrack (= 0.9.1) + other_dep (= 0.9) + other_dep (0.9) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack_middleware + other_dep + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "automatically fixes the lockfile when it's missing multiple deps, they conflict with other locked deps, but conflicts are fixable" do + build_repo4 do + build_gem "other_dep", "0.9" + build_gem "other_dep", "1.0" + + build_gem "myrack", "0.9.1" + + build_gem "myrack_middleware", "1.0" do |s| + s.add_dependency "myrack", "= 0.9.1" + end + + build_gem "another_dep_middleware", "1.0" do |s| + s.add_dependency "other_dep", "= 0.9" + end + end + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + myrack_middleware (1.0) + another_dep_middleware (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack_middleware + another_dep_middleware + + BUNDLED WITH + #{Bundler::VERSION} + L + + install_gemfile <<-G + source "https://gem.repo4" + gem "myrack_middleware" + gem "another_dep_middleware" + G + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + another_dep_middleware (1.0) + other_dep (= 0.9) + myrack (0.9.1) + myrack_middleware (1.0) + myrack (= 0.9.1) + other_dep (0.9) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + another_dep_middleware + myrack_middleware + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "raises a resolution error when lockfile is missing deps, they conflict with other locked deps, and conflicts are not fixable" do + build_repo4 do + build_gem "other_dep", "0.9" + build_gem "other_dep", "1.0" + + build_gem "myrack", "0.9.1" + + build_gem "myrack_middleware", "1.0" do |s| + s.add_dependency "myrack", "= 0.9.1" + s.add_dependency "other_dep", "= 0.9" + end + end + + lockfile <<~L + GEM + remote: https://gem.repo4/ specs: - rack_middleware (1.0) + myrack_middleware (1.0) + other_dep (1.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack_middleware + myrack_middleware + other_dep + + BUNDLED WITH + #{Bundler::VERSION} L install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo2)}" - gem "rack_middleware" + source "https://gem.repo4" + gem "myrack_middleware" + gem "other_dep", "1.0" G - expect(err).to include("Downloading rack_middleware-1.0 revealed dependencies not in the API or the lockfile (#{Gem::Dependency.new("rack", "= 0.9.1")})."). - and include("Running `bundle update rack_middleware` should fix the problem.") + expect(err).to eq <<~ERROR.strip + Could not find compatible versions + + Because every version of myrack_middleware depends on other_dep = 0.9 + and Gemfile depends on myrack_middleware >= 0, + other_dep = 0.9 is required. + So, because Gemfile depends on other_dep = 1.0, + version solving has failed. + ERROR end it "regenerates a lockfile with no specs" do @@ -1506,7 +1863,7 @@ RSpec.describe "the lockfile format" do lockfile <<-G GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: PLATFORMS @@ -1516,34 +1873,313 @@ RSpec.describe "the lockfile format" do direct_dependency BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "direct_dependency" G expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: direct_dependency (4.5.6) indirect_dependency 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 + it "automatically fixes the lockfile when it's missing deps and the full index is in use" do + lockfile <<-L + GEM + remote: https://gem.repo2/ + specs: + myrack_middleware (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack_middleware + + BUNDLED WITH + #{Bundler::VERSION} + L + + install_gemfile <<-G + source "#{file_uri_for(gem_repo2)}" + gem "myrack_middleware" + G + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo2)}/ + specs: + myrack (0.9.1) + myrack_middleware (1.0) + myrack (= 0.9.1) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack_middleware + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "automatically fixes the lockfile when it includes a gem under the correct GIT section, but also under an incorrect GEM section, with a higher version, and with no explicit Gemfile requirement" do + git = build_git "foo" + + gemfile <<~G + source "https://gem.repo1/" + gem "foo", git: "#{lib_path("foo-1.0")}" + G + + # If the lockfile erroneously lists platform versions of the gem + # that don't match the locked version of the git repo we should remove them. + + lockfile <<~L + GIT + remote: #{lib_path("foo-1.0")} + revision: #{git.ref_for("main")} + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + foo (1.1-x86_64-linux-gnu) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" + + expect(lockfile).to eq <<~L + GIT + remote: #{lib_path("foo-1.0")} + revision: #{git.ref_for("main")} + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "automatically fixes the lockfile when it includes a gem under the correct GIT section, but also under an incorrect GEM section, with a higher version" do + git = build_git "foo" + + gemfile <<~G + source "https://gem.repo1/" + gem "foo", "= 1.0", git: "#{lib_path("foo-1.0")}" + G + + # If the lockfile erroneously lists platform versions of the gem + # that don't match the locked version of the git repo we should remove them. + + lockfile <<~L + GIT + remote: #{lib_path("foo-1.0")} + revision: #{git.ref_for("main")} + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + foo (1.1-x86_64-linux-gnu) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo (= 1.0)! + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" + + expect(lockfile).to eq <<~L + GIT + remote: #{lib_path("foo-1.0")} + revision: #{git.ref_for("main")} + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo (= 1.0)! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "automatically fixes the lockfile when it has incorrect deps, keeping the locked version" do + build_repo4 do + build_gem "net-smtp", "0.5.0" do |s| + s.add_dependency "net-protocol" + end + + build_gem "net-smtp", "0.5.1" do |s| + s.add_dependency "net-protocol" + end + + build_gem "net-protocol", "0.2.2" + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + gem "net-smtp" + G + + lockfile <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + net-protocol (0.2.2) + net-smtp (0.5.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + net-smtp + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" + + expect(lockfile).to eq <<~L + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + net-protocol (0.2.2) + net-smtp (0.5.0) + net-protocol + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + net-smtp + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "successfully updates the lockfile when a new gem is added in the Gemfile includes a gem that shouldn't be included" do + build_repo4 do + build_gem "logger", "1.7.0" + build_gem "rack", "3.2.0" + build_gem "net-smtp", "0.5.0" + end + + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" + gem "logger" + gem "net-smtp" + + install_if -> { false } do + gem 'rack', github: 'rack/rack' + end + G + + lockfile <<~L + GIT + remote: https://github.com/rack/rack.git + revision: 2fface9ac09fc582a81386becd939c987ad33f99 + specs: + rack (3.2.0) + + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + logger (1.7.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + logger + rack! + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "install" + + expect(lockfile).to eq <<~L + GIT + remote: https://github.com/rack/rack.git + revision: 2fface9ac09fc582a81386becd939c987ad33f99 + specs: + rack (3.2.0) + + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + logger (1.7.0) + net-smtp (0.5.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + logger + net-smtp + rack! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + shared_examples_for "a lockfile missing dependent specs" do it "auto-heals" do build_repo4 do @@ -1555,14 +2191,14 @@ RSpec.describe "the lockfile format" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "minitest-bisect" G # Corrupt lockfile (completely missing path_expander) lockfile <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: minitest-bisect (1.6.0) @@ -1573,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 @@ -1581,7 +2217,7 @@ RSpec.describe "the lockfile format" do expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: minitest-bisect (1.6.0) path_expander (~> 1.1) @@ -1594,7 +2230,7 @@ RSpec.describe "the lockfile format" do minitest-bisect BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -1621,13 +2257,13 @@ RSpec.describe "the lockfile format" do end gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "minitest-bisect" G lockfile <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: minitest-bisect (1.6.0) path_expander (~> 1.1) @@ -1639,15 +2275,15 @@ 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 - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: minitest-bisect (1.6.0) path_expander (~> 1.1) @@ -1660,7 +2296,7 @@ RSpec.describe "the lockfile format" do minitest-bisect BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -1673,36 +2309,36 @@ RSpec.describe "the lockfile format" do build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "rack" + source "https://gem.repo2" + gem "myrack" G set_lockfile_mtime_to_known_value end it "generates Gemfile.lock with \\n line endings" do expect(File.read(bundled_app_lock)).not_to match("\r\n") - expect(the_bundle).to include_gems "rack 1.0" + expect(the_bundle).to include_gems "myrack 1.0" end context "during updates" do it "preserves Gemfile.lock \\n line endings" do update_repo2 do - build_gem "rack", "1.2" do |s| - s.executables = "rackup" + build_gem "myrack", "1.2" do |s| + s.executables = "myrackup" end end expect { bundle "update", all: true }.to change { File.mtime(bundled_app_lock) } expect(File.read(bundled_app_lock)).not_to match("\r\n") - expect(the_bundle).to include_gems "rack 1.2" + expect(the_bundle).to include_gems "myrack 1.2" end it "preserves Gemfile.lock \\n\\r line endings" do skip "needs to be adapted" if Gem.win_platform? update_repo2 do - build_gem "rack", "1.2" do |s| - s.executables = "rackup" + build_gem "myrack", "1.2" do |s| + s.executables = "myrackup" end end @@ -1713,7 +2349,7 @@ RSpec.describe "the lockfile format" do expect { bundle "update", all: true }.to change { File.mtime(bundled_app_lock) } expect(File.read(bundled_app_lock)).to match("\r\n") - expect(the_bundle).to include_gems "rack 1.2" + expect(the_bundle).to include_gems "myrack 1.2" end end @@ -1745,27 +2381,27 @@ RSpec.describe "the lockfile format" do it "refuses to install if Gemfile.lock contains conflict markers" do lockfile <<-L GEM - remote: #{file_uri_for(gem_repo2)}// + remote: https://gem.repo2// specs: <<<<<<< - rack (1.0.0) + myrack (1.0.0) ======= - rack (1.0.1) + myrack (1.0.1) >>>>>>> PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack + myrack BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L install_gemfile <<-G, raise_on_error: false - source "#{file_uri_for(gem_repo2)}/" - gem "rack" + source "https://gem.repo2/" + gem "myrack" G expect(err).to match(/your Gemfile.lock contains merge conflicts/i) @@ -1774,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 new file mode 100644 index 0000000000..4e8f155309 --- /dev/null +++ b/spec/bundler/other/cli_man_pages_spec.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +RSpec.describe "bundle commands" do + it "expects all commands to have all options and subcommands documented" do + check_commands!(Bundler::CLI) + + Bundler::CLI.subcommand_classes.each_value do |klass| + check_commands!(klass) + end + end + + private + + 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 + + check_options!(command, man_page) + else + man_page = man_page(command.ancestor_name) + expect(man_page).to exist + + check_options!(command, man_page) + check_subcommand!(command_name, man_page) + end + end + end + + 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 + + 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 + + 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 + + def check_subcommand!(name, man_page) + expect(man_page.read).to match(name) + end + + def append_aliases(text, aliases) + return text if aliases.empty? + + "#{text}, #{aliases}" + end + + def prepend_aliases(text, aliases) + return text if aliases.empty? + + "#{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 4d954b474f..a883eefe06 100644 --- a/spec/bundler/other/ext_spec.rb +++ b/spec/bundler/other/ext_spec.rb @@ -1,60 +1,25 @@ # 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 - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G end diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb index 939b68a0bb..ab7589d698 100644 --- a/spec/bundler/other/major_deprecation_spec.rb +++ b/spec/bundler/other/major_deprecation_spec.rb @@ -6,89 +6,75 @@ RSpec.describe "major deprecations" do describe "Bundler" do before do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G end 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 @@ -114,89 +98,181 @@ RSpec.describe "major deprecations" do context "bundle check --path" do before do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G 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 before do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G 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 + + context "bundle binstubs --path=" do + before do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G - pending "fails with a helpful error", bundler: "3" + 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 before do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + 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 + + bundle "cache --no-all", raise_on_error: false + end - pending "fails with a helpful error", bundler: "3" + 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 before do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G 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 - pending "fails with a helpful error", bundler: "3" + 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 + + 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,28 +365,28 @@ 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 describe "bundle update" do before do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" 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,24 +396,22 @@ RSpec.describe "major deprecations" do describe "bundle install --binstubs" do before do - install_gemfile <<-G, binstubs: true - source "#{file_uri_for(gem_repo1)}" - gem "rack" + 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 it "should not warn about gems.rb" do - create_file "gems.rb", <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + gemfile "gems.rb", <<-G + source "https://gem.repo1" + gem "myrack" G bundle :install @@ -345,27 +419,27 @@ RSpec.describe "major deprecations" do end it "should print a proper warning, and use gems.rb" do - create_file "gems.rb", "source \"#{file_uri_for(gem_repo1)}\"" + gemfile "gems.rb", "source 'https://gem.repo1'" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G expect(warnings).to include( "Multiple gemfiles (gems.rb and Gemfile) detected. Make sure you remove Gemfile and Gemfile.lock since bundler is ignoring them in favor of gems.rb and gems.locked." ) - expect(the_bundle).not_to include_gem "rack 1.0" + expect(the_bundle).not_to include_gem "myrack 1.0" end end context "bundle install with flags" do before do - bundle "config set --local path vendor/bundle" + bundle_config "path vendor/bundle" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G end @@ -383,123 +457,175 @@ 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 - source "#{file_uri_for(gem_repo3)}" - source "#{file_uri_for(gem_repo1)}" + 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} + + DEPENDENCIES + + BUNDLED WITH + #{Bundler::VERSION} + L + bundle "install", raise_on_error: false - 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." + 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_repo gem_repo3 do - build_gem "rack", "0.9.1" + build_repo3 do + build_gem "myrack", "0.9.1" end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - source "#{file_uri_for(gem_repo3)}" do - gem 'rack' + source "https://gem.repo1" + source "https://gem.repo3" do + gem 'myrack' end G lockfile <<~L GEM - remote: #{file_uri_for(gem_repo1)}/ - remote: #{file_uri_for(gem_repo3)}/ + remote: https://gem.repo1/ + remote: https://gem.repo3/ + specs: + myrack (0.9.1) + + PLATFORMS + ruby + + DEPENDENCIES + myrack! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + 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 + + 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: - rack (0.9.1) + rake (10.3.2) PLATFORMS ruby + x64-mingw32 DEPENDENCIES - rack! + rake BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L + end - bundle "config set --local frozen true" + 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 - it "shows a deprecation", bundler: "< 3" do - bundle "install" + context "with a global path source" do + before do + build_lib "foo" - 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.") + install_gemfile <<-G, raise_on_error: false + path "#{lib_path("foo-1.0")}" + gem 'foo' + G end - pending "fails with a helpful error", bundler: "3" + 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 before do - create_file "gems.rb", "source \"#{file_uri_for(gem_repo1)}\"" + create_file "gems.rb", "source 'https://gem.repo1'" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :group => :test + source "https://gem.repo1" + gem "myrack", :group => :test G ruby <<-RUBY @@ -519,86 +645,97 @@ 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 + + context "when `bundler/capistrano` is required in a ruby script" do + before do + ruby <<-RUBY, raise_on_error: false + require 'bundler/capistrano' + RUBY + end - pending "fails with a helpful error", bundler: "3" + 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 before do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G end 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 context "bundle remove" do before do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G end context "with --install" do - it "shows a deprecation warning", bundler: "< 3" do - bundle "remove rack --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 console" do + context "bundle viz" do before do - bundle "console", raise_on_error: false + bundle "viz", raise_on_error: false end - it "prints a deprecation warning", bundler: "< 3" do - expect(deprecations).to include \ - "bundle console will be replaced by `bin/console` generated by `bundle gem <name>`" + 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 - - pending "fails with a helpful message", bundler: "3" end - context "bundle viz", :realworld do + context "bundle inject" do before do - realworld_system_gems "ruby-graphviz --version 1.2.5" - create_file "gems.rb", "source \"#{file_uri_for(gem_repo1)}\"" - bundle "viz" + bundle "inject", 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 `inject` command has been replaced by the `add` command" end - - pending "fails with a helpful message", bundler: "3" end context "bundle plugin install --local_git" do @@ -608,25 +745,30 @@ 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", :readline do + describe "removing rubocop" do + before do + 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 before 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 @@ -635,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 af132d6550..05d535a70c 100644 --- a/spec/bundler/plugins/command_spec.rb +++ b/spec/bundler/plugins/command_spec.rb @@ -18,7 +18,7 @@ RSpec.describe "command plugins" do end end - bundle "plugin install command-mah --source #{file_uri_for(gem_repo2)}" + bundle "plugin install command-mah --source https://gem.repo2" end it "executes without arguments" do @@ -29,7 +29,7 @@ RSpec.describe "command plugins" do end it "accepts the arguments" do - build_repo2 do + update_repo2 do build_plugin "the-echoer" do |s| s.write "plugins.rb", <<-RUBY module Resonance @@ -46,15 +46,49 @@ RSpec.describe "command plugins" do end end - bundle "plugin install the-echoer --source #{file_uri_for(gem_repo2)}" + bundle "plugin install the-echoer --source https://gem.repo2" expect(out).to include("Installed plugin the-echoer") bundle "echo tacos tofu lasange" 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 - build_repo2 do + update_repo2 do build_plugin "copycat" do |s| s.write "plugins.rb", <<-RUBY module CopyCat @@ -69,7 +103,7 @@ RSpec.describe "command plugins" do end end - bundle "plugin install copycat --source #{file_uri_for(gem_repo2)}", raise_on_error: false + bundle "plugin install copycat --source https://gem.repo2", raise_on_error: false expect(out).not_to include("Installed plugin copycat") diff --git a/spec/bundler/plugins/hook_spec.rb b/spec/bundler/plugins/hook_spec.rb index 72feb14d84..ad8a4daeff 100644 --- a/spec/bundler/plugins/hook_spec.rb +++ b/spec/bundler/plugins/hook_spec.rb @@ -13,17 +13,17 @@ RSpec.describe "hook plugins" do end end - bundle "plugin install before-install-all-plugin --source #{file_uri_for(gem_repo2)}" + bundle "plugin install before-install-all-plugin --source https://gem.repo2" end it "runs before all rubygems are installed" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rake" - gem "rack" + gem "myrack" G - expect(out).to include "gems to be installed rake, rack" + expect(out).to include "gems to be installed rake, myrack" end end @@ -39,18 +39,18 @@ RSpec.describe "hook plugins" do end end - bundle "plugin install before-install-plugin --source #{file_uri_for(gem_repo2)}" + bundle "plugin install before-install-plugin --source https://gem.repo2" end it "runs before each rubygem is installed" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rake" - gem "rack" + gem "myrack" G expect(out).to include "installing gem rake" - expect(out).to include "installing gem rack" + expect(out).to include "installing gem myrack" end end @@ -66,17 +66,17 @@ RSpec.describe "hook plugins" do end end - bundle "plugin install after-install-all-plugin --source #{file_uri_for(gem_repo2)}" + bundle "plugin install after-install-all-plugin --source https://gem.repo2" end - it "runs after each rubygem is installed" do + it "runs after each all rubygems are installed" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rake" - gem "rack" + gem "myrack" G - expect(out).to include "installed gems rake, rack" + expect(out).to include "installed gems rake, myrack" end end @@ -92,18 +92,240 @@ RSpec.describe "hook plugins" do end end - bundle "plugin install after-install-plugin --source #{file_uri_for(gem_repo2)}" + bundle "plugin install after-install-plugin --source https://gem.repo2" end it "runs after each rubygem is installed" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rake" - gem "rack" + gem "myrack" G expect(out).to include "installed gem rake : installed" - expect(out).to include "installed gem rack : installed" + expect(out).to include "installed gem myrack : installed" + end + end + + context "before-require-all hook" do + before do + build_repo2 do + build_plugin "before-require-all-plugin" do |s| + s.write "plugins.rb", <<-RUBY + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_BEFORE_REQUIRE_ALL do |deps| + puts "gems to be required \#{deps.map(&:name).join(", ")}" + end + RUBY + end + end + + bundle "plugin install before-require-all-plugin --source https://gem.repo2" + end + + it "runs before all rubygems are required" do + install_gemfile_and_bundler_require + expect(out).to include "gems to be required rake, myrack" + end + end + + context "before-require hook" do + before do + build_repo2 do + build_plugin "before-require-plugin" do |s| + s.write "plugins.rb", <<-RUBY + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_BEFORE_REQUIRE do |dep| + puts "requiring gem \#{dep.name}" + end + RUBY + end + end + + bundle "plugin install before-require-plugin --source https://gem.repo2" + end + + it "runs before each rubygem is required" do + install_gemfile_and_bundler_require + expect(out).to include "requiring gem rake" + expect(out).to include "requiring gem myrack" end end + + context "after-require-all hook" do + before do + build_repo2 do + build_plugin "after-require-all-plugin" do |s| + s.write "plugins.rb", <<-RUBY + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_AFTER_REQUIRE_ALL do |deps| + puts "required gems \#{deps.map(&:name).join(", ")}" + end + RUBY + end + end + + bundle "plugin install after-require-all-plugin --source https://gem.repo2" + end + + it "runs after all rubygems are required" do + install_gemfile_and_bundler_require + expect(out).to include "required gems rake, myrack" + end + end + + context "after-require hook" do + before do + build_repo2 do + build_plugin "after-require-plugin" do |s| + s.write "plugins.rb", <<-RUBY + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_AFTER_REQUIRE do |dep| + puts "required gem \#{dep.name}" + end + RUBY + end + end + + bundle "plugin install after-require-plugin --source https://gem.repo2" + end + + it "runs after each rubygem is required" do + install_gemfile_and_bundler_require + expect(out).to include "required gem rake" + expect(out).to include "required gem myrack" + 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" + gem "rake" + gem "myrack" + G + + ruby <<-RUBY + require "bundler" + Bundler.require + RUBY + end end diff --git a/spec/bundler/plugins/install_spec.rb b/spec/bundler/plugins/install_spec.rb index 86eb4e584c..dcacf764be 100644 --- a/spec/bundler/plugins/install_spec.rb +++ b/spec/bundler/plugins/install_spec.rb @@ -9,28 +9,28 @@ RSpec.describe "bundler plugin install" do end it "shows proper message when gem in not found in the source" do - bundle "plugin install no-foo --source #{file_uri_for(gem_repo1)}", raise_on_error: false + bundle "plugin install no-foo --source https://gem.repo1", raise_on_error: false expect(err).to include("Could not find") plugin_should_not_be_installed("no-foo") end it "installs from rubygems source" do - bundle "plugin install foo --source #{file_uri_for(gem_repo2)}" + bundle "plugin install foo --source https://gem.repo2" expect(out).to include("Installed plugin foo") plugin_should_be_installed("foo") end it "installs from rubygems source in frozen mode" do - bundle "plugin install foo --source #{file_uri_for(gem_repo2)}", env: { "BUNDLE_DEPLOYMENT" => "true" } + bundle "plugin install foo --source https://gem.repo2", env: { "BUNDLE_DEPLOYMENT" => "true" } expect(out).to include("Installed plugin foo") plugin_should_be_installed("foo") end it "installs from sources configured as Gem.sources without any flags" do - bundle "plugin install foo", env: { "BUNDLER_SPEC_GEM_SOURCES" => file_uri_for(gem_repo2).to_s } + bundle "plugin install foo", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_SOURCES" => "https://gem.repo2" } expect(out).to include("Installed plugin foo") plugin_should_be_installed("foo") @@ -45,18 +45,18 @@ RSpec.describe "bundler plugin install" do context "plugin is already installed" do before do - bundle "plugin install foo --source #{file_uri_for(gem_repo2)}" + bundle "plugin install foo --source https://gem.repo2" end it "doesn't install plugin again" do - bundle "plugin install foo --source #{file_uri_for(gem_repo2)}" + bundle "plugin install foo --source https://gem.repo2" expect(out).not_to include("Installing plugin foo") expect(out).not_to include("Installed plugin foo") end end it "installs multiple plugins" do - bundle "plugin install foo kung-foo --source #{file_uri_for(gem_repo2)}" + bundle "plugin install foo kung-foo --source https://gem.repo2" expect(out).to include("Installed plugin foo") expect(out).to include("Installed plugin kung-foo") @@ -70,7 +70,7 @@ RSpec.describe "bundler plugin install" do build_plugin "kung-foo", "1.1" end - bundle "plugin install foo kung-foo --version '1.0' --source #{file_uri_for(gem_repo2)}" + bundle "plugin install foo kung-foo --version '1.0' --source https://gem.repo2" expect(out).to include("Installing foo 1.0") expect(out).to include("Installing kung-foo 1.0") @@ -82,18 +82,18 @@ RSpec.describe "bundler plugin install" do build_plugin "foo", "1.1" end - bundle "plugin install foo --version 1.0 --source #{file_uri_for(gem_repo2)} --verbose" + bundle "plugin install foo --version 1.0 --source https://gem.repo2 --verbose" expect(out).to include("Installing foo 1.0") - bundle "plugin install foo --source #{file_uri_for(gem_repo2)} --verbose" + bundle "plugin install foo --source https://gem.repo2 --verbose" expect(out).to include("Installing foo 1.1") - bundle "plugin install foo --source #{file_uri_for(gem_repo2)} --verbose" + bundle "plugin install foo --source https://gem.repo2 --verbose" expect(out).to include("Using foo 1.1") end it "raises an error when when --branch specified" do - bundle "plugin install foo --branch main --source #{file_uri_for(gem_repo2)}", raise_on_error: false + bundle "plugin install foo --branch main --source https://gem.repo2", raise_on_error: false expect(out).not_to include("Installed plugin foo") @@ -101,13 +101,13 @@ RSpec.describe "bundler plugin install" do end it "raises an error when --ref specified" do - bundle "plugin install foo --ref v1.2.3 --source #{file_uri_for(gem_repo2)}", raise_on_error: false + bundle "plugin install foo --ref v1.2.3 --source https://gem.repo2", raise_on_error: false expect(err).to include("--ref can only be used with git sources") end it "raises error when both --branch and --ref options are specified" do - bundle "plugin install foo --source #{file_uri_for(gem_repo2)} --branch main --ref v1.2.3", raise_on_error: false + bundle "plugin install foo --source https://gem.repo2 --branch main --ref v1.2.3", raise_on_error: false expect(out).not_to include("Installed plugin foo") @@ -131,7 +131,7 @@ RSpec.describe "bundler plugin install" do s.write("src/fubar.rb") end end - bundle "plugin install testing --source #{file_uri_for(gem_repo2)}" + bundle "plugin install testing --source https://gem.repo2" bundle "check2", "no-color" => false expect(out).to eq("mate") @@ -144,17 +144,17 @@ RSpec.describe "bundler plugin install" do build_plugin "kung-foo", "1.1" end - bundle "plugin install foo kung-foo --version '1.0' --source #{file_uri_for(gem_repo2)}" + bundle "plugin install foo kung-foo --version '1.0' --source https://gem.repo2" expect(out).to include("Installing foo 1.0") expect(out).to include("Installing kung-foo 1.0") plugin_should_be_installed("foo", "kung-foo") - build_repo2 do + update_repo2 do build_gem "charlie" end - bundle "plugin install charlie --source #{file_uri_for(gem_repo2)}", raise_on_error: false + bundle "plugin install charlie --source https://gem.repo2", raise_on_error: false expect(err).to include("Failed to install plugin `charlie`, due to Bundler::Plugin::MalformattedPlugin (plugins.rb was not found in the plugin.)") @@ -168,12 +168,12 @@ 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 - bundle "plugin install chaplin --source #{file_uri_for(gem_repo2)}", raise_on_error: false + bundle "plugin install chaplin --source https://gem.repo2", raise_on_error: false expect(global_plugin_gem("chaplin-1.0")).not_to be_directory @@ -187,7 +187,7 @@ RSpec.describe "bundler plugin install" do s.write "plugins.rb" end - bundle "plugin install foo --git #{file_uri_for(lib_path("foo-1.0"))}" + bundle "plugin install foo --git #{lib_path("foo-1.0")}" expect(out).to include("Installed plugin foo") plugin_should_be_installed("foo") @@ -203,12 +203,43 @@ RSpec.describe "bundler plugin install" do expect(out).to include("Installed plugin foo") plugin_should_be_installed("foo") end + end + + context "path plugins" do + it "installs from a path source" do + build_lib "path_plugin" do |s| + s.write "plugins.rb" + end + bundle "plugin install path_plugin --path #{lib_path("path_plugin-1.0")}" + + expect(out).to include("Installed plugin path_plugin") + plugin_should_be_installed("path_plugin") + end + + it "installs from a relative path source" do + build_lib "path_plugin" do |s| + s.write "plugins.rb" + end + path = lib_path("path_plugin-1.0").relative_path_from(bundled_app) + bundle "plugin install path_plugin --path #{path}" + + expect(out).to include("Installed plugin path_plugin") + plugin_should_be_installed("path_plugin") + end - it "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 + it "installs from a relative path source when inside an app" do + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + gemfile "" + + build_lib "ga-plugin" do |s| + s.write "plugins.rb" + end + + path = lib_path("ga-plugin-1.0").relative_path_from(bundled_app) + bundle "plugin install ga-plugin --path #{path}" - expect(exitstatus).not_to eq(0) - expect(err).to eq("Remote and local plugin git sources can't be both specified") + plugin_should_be_installed("ga-plugin") + expect(local_plugin_gem("foo-1.0")).not_to be_directory end end @@ -219,9 +250,9 @@ RSpec.describe "bundler plugin install" do it "installs plugins listed in gemfile" do gemfile <<-G - source '#{file_uri_for(gem_repo2)}' + source 'https://gem.repo2' plugin 'foo' - gem 'rack', "1.0.0" + gem 'myrack', "1.0.0" G bundle "install" @@ -230,17 +261,54 @@ RSpec.describe "bundler plugin install" do expect(out).to include("Bundle complete!") - expect(the_bundle).to include_gems("rack 1.0.0") + expect(the_bundle).to include_gems("myrack 1.0.0") 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" end gemfile <<-G - source '#{file_uri_for(gem_repo2)}' + source 'https://gem.repo2' plugin 'foo', "1.0" G @@ -259,7 +327,7 @@ RSpec.describe "bundler plugin install" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" plugin 'ga-plugin', :git => "#{lib_path("ga-plugin-1.0")}" G @@ -273,7 +341,7 @@ RSpec.describe "bundler plugin install" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" plugin 'ga-plugin', :path => "#{lib_path("ga-plugin-1.0")}" G @@ -281,25 +349,40 @@ RSpec.describe "bundler plugin install" do plugin_should_be_installed("ga-plugin") end + it "accepts relative path sources" do + build_lib "ga-plugin" do |s| + s.write "plugins.rb" + end + + path = lib_path("ga-plugin-1.0").relative_path_from(bundled_app) + install_gemfile <<-G + source "https://gem.repo1" + plugin 'ga-plugin', :path => "#{path}" + G + + expect(out).to include("Installed plugin ga-plugin") + plugin_should_be_installed("ga-plugin") + end + context "in deployment mode" do it "installs plugins" do install_gemfile <<-G - source '#{file_uri_for(gem_repo2)}' - gem 'rack', "1.0.0" + source 'https://gem.repo2' + gem 'myrack', "1.0.0" G - bundle "config set --local deployment true" + bundle_config "deployment true" install_gemfile <<-G - source '#{file_uri_for(gem_repo2)}' + source 'https://gem.repo2' plugin 'foo' - gem 'rack', "1.0.0" + gem 'myrack', "1.0.0" G expect(out).to include("Installed plugin foo") expect(out).to include("Bundle complete!") - expect(the_bundle).to include_gems("rack 1.0.0") + expect(the_bundle).to include_gems("myrack 1.0.0") plugin_should_be_installed("foo") end end @@ -311,12 +394,12 @@ RSpec.describe "bundler plugin install" do require "bundler/inline" gemfile do - source '#{file_uri_for(gem_repo2)}' + source 'https://gem.repo2' plugin 'foo' end RUBY - ruby code, env: { "BUNDLER_VERSION" => Bundler::VERSION } + ruby code, artifice: "compact_index", env: { "BUNDLER_VERSION" => Bundler::VERSION } expect(local_plugin_gem("foo-1.0", "plugins.rb")).to exist end end @@ -325,7 +408,7 @@ RSpec.describe "bundler plugin install" do it "is installed when inside an app" do allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) gemfile "" - bundle "plugin install foo --source #{file_uri_for(gem_repo2)}" + bundle "plugin install foo --source https://gem.repo2" plugin_should_be_installed("foo") expect(local_plugin_gem("foo-1.0")).to be_directory @@ -348,7 +431,7 @@ RSpec.describe "bundler plugin install" do end # inside the app - gemfile "source '#{file_uri_for(gem_repo2)}'\nplugin 'fubar'" + gemfile "source 'https://gem.repo2'\nplugin 'fubar'" bundle "install" update_repo2 do @@ -366,7 +449,7 @@ RSpec.describe "bundler plugin install" do end # outside the app - bundle "plugin install fubar --source #{file_uri_for(gem_repo2)}", dir: tmp + bundle "plugin install fubar --source https://gem.repo2", dir: tmp end it "inside the app takes precedence over global plugin" do diff --git a/spec/bundler/plugins/list_spec.rb b/spec/bundler/plugins/list_spec.rb index 4a686415ad..30e3f82467 100644 --- a/spec/bundler/plugins/list_spec.rb +++ b/spec/bundler/plugins/list_spec.rb @@ -38,7 +38,7 @@ RSpec.describe "bundler plugin list" do context "single plugin installed" do it "shows plugin name with commands list" do - bundle "plugin install foo --source #{file_uri_for(gem_repo2)}" + bundle "plugin install foo --source https://gem.repo2" plugin_should_be_installed("foo") bundle "plugin list" @@ -49,7 +49,7 @@ RSpec.describe "bundler plugin list" do context "multiple plugins installed" do it "shows plugin names with commands list" do - bundle "plugin install foo bar --source #{file_uri_for(gem_repo2)}" + bundle "plugin install foo bar --source https://gem.repo2" plugin_should_be_installed("foo", "bar") bundle "plugin list" diff --git a/spec/bundler/plugins/source/example_spec.rb b/spec/bundler/plugins/source/example_spec.rb index e569f3e415..4cd4a1a931 100644 --- a/spec/bundler/plugins/source/example_spec.rb +++ b/spec/bundler/plugins/source/example_spec.rb @@ -52,7 +52,7 @@ RSpec.describe "real source plugins" do build_lib "a-path-gem" gemfile <<-G - source "#{file_uri_for(gem_repo2)}" # plugin source + source "https://gem.repo2" # plugin source source "#{lib_path("a-path-gem-1.0")}", :type => :mpath do gem "a-path-gem" end @@ -67,10 +67,10 @@ 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_existing do |c| + checksums = checksums_section_when_enabled do |c| c.no_checksum "a-path-gem", "1.0" end @@ -82,7 +82,7 @@ RSpec.describe "real source plugins" do a-path-gem (1.0) GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: PLATFORMS @@ -92,7 +92,7 @@ RSpec.describe "real source plugins" do a-path-gem! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -110,7 +110,7 @@ RSpec.describe "real source plugins" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" # plugin source + source "https://gem.repo2" # plugin source source "#{lib_path("gem_with_bin-1.0")}", :type => :mpath do gem "gem_with_bin" 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 @@ -170,7 +167,7 @@ RSpec.describe "real source plugins" do a-path-gem (1.0) GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: PLATFORMS @@ -180,7 +177,7 @@ RSpec.describe "real source plugins" do a-path-gem! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -323,8 +320,8 @@ RSpec.describe "real source plugins" do build_git "ma-gitp-gem" gemfile <<-G - source "#{file_uri_for(gem_repo2)}" # plugin source - source "#{file_uri_for(lib_path("ma-gitp-gem-1.0"))}", :type => :gitp do + source "https://gem.repo2" # plugin source + source "#{lib_path("ma-gitp-gem-1.0")}", :type => :gitp do gem "ma-gitp-gem" end G @@ -336,24 +333,24 @@ 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" - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.no_checksum "ma-gitp-gem", "1.0" end expect(lockfile).to eq <<~G PLUGIN SOURCE - remote: #{file_uri_for(lib_path("ma-gitp-gem-1.0"))} + remote: #{lib_path("ma-gitp-gem-1.0")} type: gitp revision: #{revision} specs: ma-gitp-gem (1.0) GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: PLATFORMS @@ -363,7 +360,7 @@ RSpec.describe "real source plugins" do ma-gitp-gem! #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -372,14 +369,14 @@ RSpec.describe "real source plugins" do revision = revision_for(lib_path("ma-gitp-gem-1.0")) lockfile <<-G PLUGIN SOURCE - remote: #{file_uri_for(lib_path("ma-gitp-gem-1.0"))} + remote: #{lib_path("ma-gitp-gem-1.0")} type: gitp revision: #{revision} specs: ma-gitp-gem (1.0) GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: PLATFORMS @@ -389,7 +386,7 @@ RSpec.describe "real source plugins" do ma-gitp-gem! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -423,8 +420,8 @@ RSpec.describe "real source plugins" do it "updates the deps on change in gemfile" do update_git "ma-gitp-gem", "1.1", path: lib_path("ma-gitp-gem-1.0"), gemspec: true gemfile <<-G - source "#{file_uri_for(gem_repo2)}" # plugin source - source "#{file_uri_for(lib_path("ma-gitp-gem-1.0"))}", :type => :gitp do + source "https://gem.repo2" # plugin source + source "#{lib_path("ma-gitp-gem-1.0")}", :type => :gitp do gem "ma-gitp-gem", "1.1" end G @@ -440,19 +437,18 @@ RSpec.describe "real source plugins" do ref = git.ref_for("main", 11) install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" # plugin source + source "https://gem.repo2" # plugin source source '#{lib_path("foo-1.0")}', :type => :gitp do gem "foo" 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/plugins/source_spec.rb b/spec/bundler/plugins/source_spec.rb index 14643e5c81..995e50e653 100644 --- a/spec/bundler/plugins/source_spec.rb +++ b/spec/bundler/plugins/source_spec.rb @@ -16,8 +16,8 @@ RSpec.describe "bundler source plugin" do it "installs bundler-source-* gem when no handler for source is present" do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - source "#{file_uri_for(lib_path("gitp"))}", :type => :psource do + source "https://gem.repo2" + source "#{lib_path("gitp")}", :type => :psource do end G @@ -38,8 +38,8 @@ RSpec.describe "bundler source plugin" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - source "#{file_uri_for(lib_path("gitp"))}", :type => :psource do + source "https://gem.repo2" + source "#{lib_path("gitp")}", :type => :psource do end G @@ -62,11 +62,11 @@ RSpec.describe "bundler source plugin" do context "explicit presence in gemfile" do before do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" plugin "another-psource" - source "#{file_uri_for(lib_path("gitp"))}", :type => :psource do + source "#{lib_path("gitp")}", :type => :psource do end G end @@ -88,11 +88,11 @@ RSpec.describe "bundler source plugin" do context "explicit default source" do before do install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" plugin "bundler-source-psource" - source "#{file_uri_for(lib_path("gitp"))}", :type => :psource do + source "#{lib_path("gitp")}", :type => :psource do end G end diff --git a/spec/bundler/plugins/uninstall_spec.rb b/spec/bundler/plugins/uninstall_spec.rb index 555c6a7002..dedcc9f37c 100644 --- a/spec/bundler/plugins/uninstall_spec.rb +++ b/spec/bundler/plugins/uninstall_spec.rb @@ -14,7 +14,7 @@ RSpec.describe "bundler plugin uninstall" do end it "uninstalls specified plugins" do - bundle "plugin install foo kung-foo --source #{file_uri_for(gem_repo2)}" + bundle "plugin install foo kung-foo --source https://gem.repo2" plugin_should_be_installed("foo") plugin_should_be_installed("kung-foo") @@ -40,9 +40,9 @@ RSpec.describe "bundler plugin uninstall" do allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) install_gemfile <<-G - source '#{file_uri_for(gem_repo2)}' + source 'https://gem.repo2' plugin 'path_plugin', :path => "#{path}" - gem 'rack', '1.0.0' + gem 'myrack', '1.0.0' G plugin_should_be_installed("path_plugin") @@ -57,7 +57,7 @@ RSpec.describe "bundler plugin uninstall" do describe "with --all" do it "uninstalls all installed plugins" do - bundle "plugin install foo kung-foo --source #{file_uri_for(gem_repo2)}" + bundle "plugin install foo kung-foo --source https://gem.repo2" plugin_should_be_installed("foo") plugin_should_be_installed("kung-foo") 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 7cdb993017..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#=~} } @@ -236,9 +238,24 @@ RSpec.describe "The library itself" do expect(all_bad_requires).to be_empty, "#{all_bad_requires.size} internal requires that should use `require_relative`: #{all_bad_requires}" end + # We don't want our artifice code to activate bundler, but it needs to use the + # namespaced implementation of `Net::HTTP`. So we duplicate the file in + # bundler that loads that. + it "keeps vendored_net_http spec code in sync with the lib implementation" do + lib_implementation_path = File.join(source_lib_dir, "bundler", "vendored_net_http.rb") + expect(File.exist?(lib_implementation_path)).to be_truthy + lib_code = File.read(lib_implementation_path) + + spec_implementation_path = File.join(spec_dir, "support", "vendored_net_http.rb") + expect(File.exist?(spec_implementation_path)).to be_truthy + spec_code = File.read(spec_implementation_path) + + expect(lib_code).to eq(spec_code) + end + 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/dependency_api_spec.rb b/spec/bundler/realworld/dependency_api_spec.rb deleted file mode 100644 index ee5c0e3d0a..0000000000 --- a/spec/bundler/realworld/dependency_api_spec.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require_relative "../support/silent_logger" - -RSpec.describe "gemcutter's dependency API", realworld: true 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" - - @t = Thread.new do - server = Rack::Server.start(app: EndpointTimeout, - Host: "0.0.0.0", - Port: port, - server: "webrick", - AccessLog: [], - Logger: Spec::SilentLogger.new) - server.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 - end - - it "times out and falls back on the modern index" do - install_gemfile <<-G, artifice: nil - source "#{@server_uri}" - gem "rack" - G - - expect(out).to include("Fetching source index from #{@server_uri}/") - expect(the_bundle).to include_gems "rack 1.0.0" - end - end -end diff --git a/spec/bundler/realworld/edgecases_spec.rb b/spec/bundler/realworld/edgecases_spec.rb index 94ca3554b1..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,153 +63,7 @@ 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 error message when gems have invalid gemspecs", rubygems: "< 3.3.16" do - install_gemfile <<-G, standalone: true, raise_on_error: false, env: { "BUNDLE_FORCE_RUBY_PLATFORM" => "1" } - source 'https://rubygems.org' - gem "resque-scheduler", "2.2.0" - gem "redis-namespace", "1.6.0" # for a consistent resolution including ruby 2.3.0 - gem "ruby2_keywords", "0.0.5" - G - expect(err).to include("You have one or more invalid gemspecs that need to be fixed.") - expect(err).to include("resque-scheduler 2.2.0 has an invalid gemspec") - end - - it "outputs a helpful warning when gems have a gemspec with invalid `require_paths`", rubygems: ">= 3.3.16" do + 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' gem "resque-scheduler", "2.2.0" @@ -218,166 +72,4 @@ RSpec.describe "real world edgecases", realworld: true do G expect(err).to include("resque-scheduler 2.2.0 includes a gemspec with `require_paths` set to an array of arrays. Newer versions of this gem might've already fixed this").once end - - it "doesn't hang on nix gemfile" do - skip "Only for ruby 3.0" unless RUBY_VERSION.start_with?("3.0") - - gemfile <<~G - source "https://rubygems.org" - - gem "addressable" - gem "atk" - gem "awesome_print" - gem "bacon" - gem "byebug" - gem "cairo" - gem "cairo-gobject" - gem "camping" - gem "charlock_holmes" - gem "cld3" - gem "cocoapods" - gem "cocoapods-acknowledgements" - gem "cocoapods-art" - gem "cocoapods-bin" - gem "cocoapods-browser" - gem "cocoapods-bugsnag" - gem "cocoapods-check" - gem "cocoapods-clean" - gem "cocoapods-clean_build_phases_scripts" - gem "cocoapods-core" - gem "cocoapods-coverage" - gem "cocoapods-deintegrate" - gem "cocoapods-dependencies" - gem "cocoapods-deploy" - gem "cocoapods-downloader" - gem "cocoapods-expert-difficulty" - gem "cocoapods-fix-react-native" - gem "cocoapods-generate" - gem "cocoapods-git_url_rewriter" - gem "cocoapods-keys" - gem "cocoapods-no-dev-schemes" - gem "cocoapods-open" - gem "cocoapods-packager" - gem "cocoapods-playgrounds" - gem "cocoapods-plugins" - gem "cocoapods-prune-localizations" - gem "cocoapods-rome" - gem "cocoapods-search" - gem "cocoapods-sorted-search" - gem "cocoapods-static-swift-framework" - gem "cocoapods-stats" - gem "cocoapods-tdfire-binary" - gem "cocoapods-testing" - gem "cocoapods-trunk" - gem "cocoapods-try" - gem "cocoapods-try-release-fix" - gem "cocoapods-update-if-you-dare" - gem "cocoapods-whitelist" - gem "cocoapods-wholemodule" - gem "coderay" - gem "concurrent-ruby" - gem "curb" - gem "curses" - gem "daemons" - gem "dep-selector-libgecode" - gem "digest-sha3" - gem "domain_name" - gem "do_sqlite3" - gem "ethon" - gem "eventmachine" - gem "excon" - gem "faraday" - gem "ffi" - gem "ffi-rzmq-core" - gem "fog-dnsimple" - gem "gdk_pixbuf2" - gem "gio2" - gem "gitlab-markup" - gem "glib2" - gem "gpgme" - gem "gtk2" - gem "hashie" - gem "highline" - gem "hike" - gem "hitimes" - gem "hpricot" - gem "httpclient" - gem "http-cookie" - gem "iconv" - gem "idn-ruby" - gem "jbuilder" - gem "jekyll" - gem "jmespath" - gem "jwt" - gem "libv8" - gem "libxml-ruby" - gem "magic" - gem "markaby" - gem "method_source" - gem "mini_magick" - gem "msgpack" - gem "mysql2" - gem "ncursesw" - gem "netrc" - gem "net-scp" - gem "net-ssh" - gem "nokogiri" - gem "opus-ruby" - gem "ovirt-engine-sdk" - gem "pango" - gem "patron" - gem "pcaprub" - gem "pg" - gem "pry" - gem "pry-byebug" - gem "pry-doc" - gem "public_suffix" - gem "puma" - gem "rails" - gem "rainbow" - gem "rbnacl" - gem "rb-readline" - gem "re2" - gem "redis" - gem "redis-rack" - gem "rest-client" - gem "rmagick" - gem "rpam2" - gem "rspec" - gem "rubocop" - gem "rubocop-performance" - gem "ruby-libvirt" - gem "ruby-lxc" - gem "ruby-progressbar" - gem "ruby-terminfo" - gem "ruby-vips" - gem "rubyzip" - gem "rugged" - gem "sassc" - gem "scrypt" - gem "semian" - gem "sequel" - gem "sequel_pg" - gem "simplecov" - gem "sinatra" - gem "slop" - gem "snappy" - gem "sqlite3" - gem "taglib-ruby" - gem "thrift" - gem "tilt" - gem "tiny_tds" - gem "treetop" - gem "typhoeus" - gem "tzinfo" - gem "unf_ext" - gem "uuid4r" - gem "whois" - gem "zookeeper" - G - - bundle :lock, env: { "DEBUG_RESOLVER" => "1" } - - expect(out).to include("Solution found after 4 attempts") - end end diff --git a/spec/bundler/realworld/ffi_spec.rb b/spec/bundler/realworld/ffi_spec.rb 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 b/spec/bundler/realworld/fixtures/tapioca/Gemfile new file mode 100644 index 0000000000..447d715706 --- /dev/null +++ b/spec/bundler/realworld/fixtures/tapioca/Gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem "tapioca" diff --git a/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock b/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock new file mode 100644 index 0000000000..c2df2f9229 --- /dev/null +++ b/spec/bundler/realworld/fixtures/tapioca/Gemfile.lock @@ -0,0 +1,49 @@ +GEM + remote: https://rubygems.org/ + specs: + erubi (1.13.1) + netrc (0.11.0) + parallel (1.26.3) + prism (1.3.0) + rbi (0.2.2) + prism (~> 1.0) + sorbet-runtime (>= 0.5.9204) + sorbet (0.5.11725) + sorbet-static (= 0.5.11725) + sorbet-runtime (0.5.11725) + sorbet-static (0.5.11725-aarch64-linux) + sorbet-static (0.5.11725-universal-darwin) + sorbet-static (0.5.11725-x86_64-linux) + sorbet-static-and-runtime (0.5.11725) + sorbet (= 0.5.11725) + sorbet-runtime (= 0.5.11725) + spoom (1.5.0) + erubi (>= 1.10.0) + prism (>= 0.28.0) + sorbet-static-and-runtime (>= 0.5.10187) + thor (>= 0.19.2) + tapioca (0.16.6) + bundler (>= 2.2.25) + netrc (>= 0.11.0) + parallel (>= 1.21.0) + rbi (~> 0.2) + sorbet-static-and-runtime (>= 0.5.11087) + spoom (>= 1.2.0) + thor (>= 1.2.0) + yard-sorbet + thor (1.4.0) + yard (0.9.42) + yard-sorbet (0.9.0) + sorbet-runtime + yard + +PLATFORMS + aarch64-linux + universal-darwin + x86_64-linux + +DEPENDENCIES + tapioca + +BUNDLED WITH + 4.1.0.dev diff --git a/spec/bundler/realworld/fixtures/warbler/Gemfile b/spec/bundler/realworld/fixtures/warbler/Gemfile index a8dbb4911c..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.2" -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 5b476f8df2..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.2.16.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.2) - warbler (~> 2.0) + jruby-jars (~> 10.0) + warbler (~> 2.1) BUNDLED WITH - 2.5.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 45f5d0fd22..0000000000 --- a/spec/bundler/realworld/gemfile_source_header_spec.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -require_relative "../support/silent_logger" - -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" - - @t = Thread.new do - Rack::Server.start(app: EndpointMirrorSource, - Host: "0.0.0.0", - Port: @port, - server: "webrick", - AccessLog: [], - Logger: Spec::SilentLogger.new) - end.run - - wait_for_server("127.0.0.1", @port) - 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 fc97f92375..0000000000 --- a/spec/bundler/realworld/mirror_probe_spec.rb +++ /dev/null @@ -1,131 +0,0 @@ -# frozen_string_literal: true - -require_relative "../support/silent_logger" - -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" - - @server_thread = Thread.new do - Rack::Server.start(app: Endpoint, - Host: host, - Port: @server_port, - server: "webrick", - AccessLog: [], - Logger: Spec::SilentLogger.new) - end.run - - wait_for_server(host, @server_port) - 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 4a0dd37bf9..185df1b1c7 100644 --- a/spec/bundler/resolver/basic_spec.rb +++ b/spec/bundler/resolver/basic_spec.rb @@ -6,15 +6,15 @@ RSpec.describe "Resolving" do end it "resolves a single gem" do - dep "rack" + dep "myrack" - should_resolve_as %w[rack-1.1] + should_resolve_as %w[myrack-1.1] end it "resolves a gem with dependencies" do dep "actionpack" - should_resolve_as %w[actionpack-2.3.5 activesupport-2.3.5 rack-1.0] + should_resolve_as %w[actionpack-2.3.5 activesupport-2.3.5 myrack-1.0] end it "resolves a conflicting index" do @@ -84,7 +84,7 @@ RSpec.describe "Resolving" do dep "activesupport", "= 3.0.0.beta" dep "actionpack" - should_resolve_as %w[activesupport-3.0.0.beta actionpack-3.0.0.beta rack-1.1 rack-mount-0.6] + should_resolve_as %w[activesupport-3.0.0.beta actionpack-3.0.0.beta myrack-1.1 myrack-mount-0.6] end it "prefers non-pre-releases when doing conservative updates" do @@ -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 << build_spec("bar", "2.0.3").first + @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,31 +292,31 @@ 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 it "handles versions that redundantly depend on themselves" do @index = build_index do - gem "rack", "3.0.0" + gem "myrack", "3.0.0" gem "standalone_migrations", "7.1.0" do - dep "rack", "~> 2.0" + dep "myrack", "~> 2.0" end gem "standalone_migrations", "2.0.4" do @@ -324,22 +324,22 @@ RSpec.describe "Resolving" do end gem "standalone_migrations", "1.0.13" do - dep "rack", ">= 0" + dep "myrack", ">= 0" end end - dep "rack", "~> 3.0" + dep "myrack", "~> 3.0" dep "standalone_migrations" - should_resolve_as %w[rack-3.0.0 standalone_migrations-2.0.4] + should_resolve_as %w[myrack-3.0.0 standalone_migrations-2.0.4] end it "ignores versions that incorrectly depend on themselves" do @index = build_index do - gem "rack", "3.0.0" + gem "myrack", "3.0.0" gem "standalone_migrations", "7.1.0" do - dep "rack", "~> 2.0" + dep "myrack", "~> 2.0" end gem "standalone_migrations", "2.0.4" do @@ -347,22 +347,22 @@ RSpec.describe "Resolving" do end gem "standalone_migrations", "1.0.13" do - dep "rack", ">= 0" + dep "myrack", ">= 0" end end - dep "rack", "~> 3.0" + dep "myrack", "~> 3.0" dep "standalone_migrations" - should_resolve_as %w[rack-3.0.0 standalone_migrations-1.0.13] + should_resolve_as %w[myrack-3.0.0 standalone_migrations-1.0.13] end it "does not ignore versions that incorrectly depend on themselves when dependency_api is not available" do @index = build_index do - gem "rack", "3.0.0" + gem "myrack", "3.0.0" gem "standalone_migrations", "7.1.0" do - dep "rack", "~> 2.0" + dep "myrack", "~> 2.0" end gem "standalone_migrations", "2.0.4" do @@ -370,13 +370,53 @@ RSpec.describe "Resolving" do end gem "standalone_migrations", "1.0.13" do - dep "rack", ">= 0" + dep "myrack", ">= 0" end end - dep "rack", "~> 3.0" + dep "myrack", "~> 3.0" dep "standalone_migrations" - should_resolve_without_dependency_api %w[rack-3.0.0 standalone_migrations-2.0.4] + 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 3e959aeb89..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,25 +376,21 @@ 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 - if Gem.rubygems_version >= Gem::Version.new("3.2.28") - it "finds x64-mingw-ucrt gems" do - platforms "x64-mingw-ucrt" - dep "thin" - should_resolve_as %w[thin-1.2.7-x64-mingw-ucrt] - end + it "finds x64-mingw-ucrt gems" do + platforms "x64-mingw-ucrt" + dep "thin" + should_resolve_as %w[thin-1.2.7-x64-mingw-ucrt] end - if Gem.rubygems_version >= Gem::Version.new("3.3.18") - it "finds universal-mingw gems on x64-mingw-ucrt" do - platform "x64-mingw-ucrt" - dep "win32-api" - should_resolve_as %w[win32-api-1.5.1-universal-mingw32] - end + it "finds universal-mingw gems on x64-mingw-ucrt" do + platform "x64-mingw-ucrt" + dep "win32-api" + should_resolve_as %w[win32-api-1.5.1-universal-mingw32] end end diff --git a/spec/bundler/runtime/with_unbundled_env_spec.rb b/spec/bundler/runtime/env_helpers_spec.rb index 84b198cfb6..c4ebdd1fd2 100644 --- a/spec/bundler/runtime/with_unbundled_env_spec.rb +++ b/spec/bundler/runtime/env_helpers_spec.rb @@ -1,14 +1,14 @@ # frozen_string_literal: true -RSpec.describe "Bundler.with_env helpers" do +RSpec.describe "env helpers" do def bundle_exec_ruby(args, options = {}) - build_bundler_context options + build_bundler_context options.dup bundle "exec '#{Gem.ruby}' #{args}", options end def build_bundler_context(options = {}) - bundle "config set path vendor/bundle" - gemfile "source \"#{file_uri_for(gem_repo1)}\"" + bundle "config set path vendor/bundle", options.dup + gemfile "source 'https://gem.repo1'" bundle "install", options end @@ -24,7 +24,7 @@ RSpec.describe "Bundler.with_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 "Bundler.with_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,84 +62,78 @@ RSpec.describe "Bundler.with_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")') + 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") RUBY - bundle_exec_ruby bundled_app("source.rb") + bundle_exec_ruby bundled_app("source.rb"), artifice: "fail" expect(out).to eq original 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 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(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 - actual = Bundler.with_original_env { Bundler::EnvironmentPreserver.env_to_hash(ENV) } + actual = Bundler.with_original_env { ENV.to_hash } expect(actual).to eq(expected) end @@ -152,30 +146,10 @@ RSpec.describe "Bundler.with_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 { Bundler::EnvironmentPreserver.env_to_hash(ENV) } - 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 - actual = Bundler.with_unbundled_env { Bundler::EnvironmentPreserver.env_to_hash(ENV) } + actual = Bundler.with_unbundled_env { ENV.to_hash } expect(actual).to eq(expected) end @@ -203,21 +177,6 @@ RSpec.describe "Bundler.with_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') @@ -254,27 +213,6 @@ RSpec.describe "Bundler.with_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') @@ -288,7 +226,7 @@ RSpec.describe "Bundler.with_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 36ce6dcf67..89cee21b00 100644 --- a/spec/bundler/runtime/executable_spec.rb +++ b/spec/bundler/runtime/executable_spec.rb @@ -3,140 +3,112 @@ RSpec.describe "Running bin/* commands" do before :each do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G end it "runs the bundled command when in the bundle" do - bundle "binstubs rack" + bundle "binstubs myrack" - build_gem "rack", "2.0", to_system: true do |s| - s.executables = "rackup" + build_gem "myrack", "2.0", to_system: true do |s| + s.executables = "myrackup" end - gembin "rackup" + gembin "myrackup" expect(out).to eq("1.0.0") end - it "allows the location of the gem stubs to be specified" do - bundle "binstubs rack", 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/rackup")).to exist + expect(bundled_app("gbin/myrackup")).to exist - gembin bundled_app("gbin/rackup") + gembin bundled_app("gbin/myrackup") expect(out).to eq("1.0.0") end it "allows absolute paths as a specification of where to install bin stubs" do - bundle "binstubs rack", path: tmp("bin") + bundle_config "bin #{tmp("bin")}" + bundle "binstubs myrack" - gembin tmp("bin/rackup") + gembin tmp("bin/myrackup") expect(out).to eq("1.0.0") end it "uses the default ruby install name when shebang is not specified" do - bundle "binstubs rack" - expect(File.readlines(bundled_app("bin/rackup")).first).to eq("#!/usr/bin/env #{RbConfig::CONFIG["ruby_install_name"]}\n") + bundle "binstubs myrack" + expect(File.readlines(bundled_app("bin/myrackup")).first).to eq("#!/usr/bin/env #{RbConfig::CONFIG["ruby_install_name"]}\n") end it "allows the name of the shebang executable to be specified" do - bundle "binstubs rack", shebang: "ruby-foo" - expect(File.readlines(bundled_app("bin/rackup")).first).to eq("#!/usr/bin/env ruby-foo\n") + bundle "binstubs myrack", shebang: "ruby-foo" + expect(File.readlines(bundled_app("bin/myrackup")).first).to eq("#!/usr/bin/env ruby-foo\n") end it "runs the bundled command when out of the bundle" do - bundle "binstubs rack" + bundle "binstubs myrack" - build_gem "rack", "2.0", to_system: true do |s| - s.executables = "rackup" + build_gem "myrack", "2.0", to_system: true do |s| + s.executables = "myrackup" end - gembin "rackup", dir: tmp + gembin "myrackup", dir: tmp expect(out).to eq("1.0.0") end it "works with gems in path" do - build_lib "rack", path: lib_path("rack") do |s| - s.executables = "rackup" + build_lib "myrack", path: lib_path("myrack") do |s| + s.executables = "myrackup" end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :path => "#{lib_path("rack")}" + source "https://gem.repo1" + gem "myrack", :path => "#{lib_path("myrack")}" G - bundle "binstubs rack" + bundle "binstubs myrack" - build_gem "rack", "2.0", to_system: true do |s| - s.executables = "rackup" + build_gem "myrack", "2.0", to_system: true do |s| + s.executables = "myrackup" end - gembin "rackup" + gembin "myrackup" expect(out).to eq("1.0") end - it "creates a bundle binstub" do + it "does not create a bundle binstub" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "bundler" G bundle "binstubs bundler" - expect(bundled_app("bin/bundle")).to exist - end - - it "does not generate bin stubs if the option was not specified" do - bundle "install" - - expect(bundled_app("bin/rackup")).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/rackup").rmtree - bundle "install --binstubs \"\"" - - expect(bundled_app("bin/rackup")).not_to exist + expect(bundled_app("bin/bundle")).not_to exist - bundle "config bin" - expect(out).to include("You have not configured a value for `bin`") + expect(err).to include("Bundler itself does not use binstubs because its version is selected by RubyGems") end - it "remembers that the option was specified", bundler: "< 3" do - gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "activesupport" - G - - bundle :install, binstubs: "bin" - - gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "activesupport" - gem "rack" - G - + it "does not generate bin stubs if the option was not specified" do bundle "install" - expect(bundled_app("bin/rackup")).to exist + expect(bundled_app("bin/myrackup")).not_to exist end - it "rewrites bins on binstubs (to maintain backwards compatibility)" do + it "rewrites bins on binstubs with --force option" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G - create_file("bin/rackup", "OMG") + create_file("bin/myrackup", "OMG") - bundle "binstubs rack" + bundle "binstubs myrack", { force: true } - expect(bundled_app("bin/rackup").read).to_not eq("OMG") + expect(bundled_app("bin/myrackup").read.strip).to_not eq("OMG") end it "use BUNDLE_GEMFILE gemfile for binstub" do @@ -148,8 +120,8 @@ RSpec.describe "Running bin/* commands" do build_gem("bindir") {|s| s.executables = "foo" } end - create_file("OtherGemfile", <<-G) - source "#{file_uri_for(gem_repo2)}" + gemfile("OtherGemfile", <<-G) + source "https://gem.repo2" gem 'bindir' G diff --git a/spec/bundler/runtime/gem_tasks_spec.rb b/spec/bundler/runtime/gem_tasks_spec.rb index f7afc0eb92..b855142e60 100644 --- a/spec/bundler/runtime/gem_tasks_spec.rb +++ b/spec/bundler/runtime/gem_tasks_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.describe "require 'bundler/gem_tasks'" do - before :each do + let(:define_local_gem_using_gem_tasks) do bundled_app("foo.gemspec").open("w") do |f| f.write <<-GEMSPEC Gem::Specification.new do |s| @@ -20,17 +20,54 @@ RSpec.describe "require 'bundler/gem_tasks'" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rake" G end - it "includes the relevant tasks" do - with_gem_path_as(base_system_gem_path.to_s) do - sys_exec "#{rake} -T", env: { "GEM_HOME" => system_gem_path.to_s } + let(:define_local_gem_with_extensions_using_gem_tasks_and_gemspec_dsl) do + bundled_app("foo.gemspec").open("w") do |f| + f.write <<-GEMSPEC + Gem::Specification.new do |s| + s.name = "foo" + s.version = "1.0" + s.summary = "dummy" + s.author = "Perry Mason" + s.extensions = "ext/extconf.rb" + end + GEMSPEC + end + + bundled_app("Rakefile").open("w") do |f| + f.write <<-RAKEFILE + require "bundler/gem_tasks" + RAKEFILE end + Dir.mkdir bundled_app("ext") + + bundled_app("ext/extconf.rb").open("w") do |f| + f.write <<-EXTCONF + require "mkmf" + File.write("Makefile", dummy_makefile($srcdir).join) + EXTCONF + end + + install_gemfile <<-G + source "https://gem.repo1" + + gemspec + + gem "rake" + G + end + + it "includes the relevant tasks" do + define_local_gem_using_gem_tasks + + in_bundled_app "rake -T" + expect(err).to be_empty expected_tasks = [ "rake build", @@ -44,9 +81,9 @@ RSpec.describe "require 'bundler/gem_tasks'" do end it "defines a working `rake install` task", :ruby_repo do - with_gem_path_as(base_system_gem_path.to_s) do - sys_exec "#{rake} install", env: { "GEM_HOME" => system_gem_path.to_s } - end + define_local_gem_using_gem_tasks + + in_bundled_app "rake install" expect(err).to be_empty @@ -55,9 +92,19 @@ RSpec.describe "require 'bundler/gem_tasks'" do expect(err).to be_empty end + it "defines a working `rake install` task for local gems with extensions", :ruby_repo do + define_local_gem_with_extensions_using_gem_tasks_and_gemspec_dsl + + bundle "exec rake install" + + expect(err).to be_empty + end + context "rake build when path has spaces", :ruby_repo do before do - spaced_bundled_app = tmp.join("bundled app") + define_local_gem_using_gem_tasks + + spaced_bundled_app = tmp("bundled app") FileUtils.cp_r bundled_app, spaced_bundled_app bundle "exec rake build", dir: spaced_bundled_app end @@ -69,7 +116,9 @@ RSpec.describe "require 'bundler/gem_tasks'" do context "rake build when path has brackets", :ruby_repo do before do - bracketed_bundled_app = tmp.join("bundled[app") + define_local_gem_using_gem_tasks + + bracketed_bundled_app = tmp("bundled[app") FileUtils.cp_r bundled_app, bracketed_bundled_app bundle "exec rake build", dir: bracketed_bundled_app end @@ -81,12 +130,14 @@ RSpec.describe "require 'bundler/gem_tasks'" do context "bundle path configured locally" do before do - bundle "config set path vendor/bundle" + define_local_gem_using_gem_tasks + + bundle_config "path vendor/bundle" end it "works", :ruby_repo do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rake" G @@ -98,9 +149,10 @@ RSpec.describe "require 'bundler/gem_tasks'" do end it "adds 'pkg' to rake/clean's CLOBBER" do - 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 + define_local_gem_using_gem_tasks + + 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 ffac30d6d8..c6f9bbdbd7 100644 --- a/spec/bundler/runtime/inline_spec.rb +++ b/spec/bundler/runtime/inline_spec.rb @@ -2,10 +2,9 @@ RSpec.describe "bundler/inline#gemfile" do def script(code, options = {}) - requires = ["bundler/inline"] - requires.unshift "#{spec_dir}/support/artifice/" + options.delete(:artifice) if options.key?(:artifice) - requires = requires.map {|r| "require '#{r}'" }.join("\n") - ruby("#{requires}\n\n" + code, options) + options[:artifice] ||= "compact_index" + options[:env] ||= { "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s } + ruby("require 'bundler/inline'\n\n" + code, options) end before :each do @@ -48,7 +47,7 @@ RSpec.describe "bundler/inline#gemfile" do it "requires the gems" do script <<-RUBY gemfile do - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" path "#{lib_path}" do gem "two" end @@ -59,7 +58,7 @@ RSpec.describe "bundler/inline#gemfile" do script <<-RUBY, raise_on_error: false gemfile do - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" path "#{lib_path}" do gem "eleven" end @@ -73,16 +72,16 @@ RSpec.describe "bundler/inline#gemfile" do script <<-RUBY gemfile(true) do - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" end RUBY - expect(out).to include("Rack's post install message") + expect(out).to include("Myrack's post install message") script <<-RUBY, artifice: "endpoint" gemfile(true) do - source "https://notaserver.com" + source "https://notaserver.test" gem "activesupport", :require => true end RUBY @@ -104,7 +103,7 @@ RSpec.describe "bundler/inline#gemfile" do my_ui = MyBundlerUI.new my_ui.level = "confirm" gemfile(true, :ui => my_ui) do - source "https://notaserver.com" + source "https://notaserver.test" gem "activesupport", :require => true end RUBY @@ -117,7 +116,7 @@ RSpec.describe "bundler/inline#gemfile" do require 'bundler/inline' gemfile(true, :quiet => true) do - source "https://notaserver.com" + source "https://notaserver.test" gem "activesupport", :require => true end RUBY @@ -143,7 +142,7 @@ RSpec.describe "bundler/inline#gemfile" do require 'bundler' options = { :ui => Bundler::UI::Shell.new } gemfile(false, options) do - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" path "#{lib_path}" do gem "two" end @@ -157,11 +156,11 @@ RSpec.describe "bundler/inline#gemfile" do it "installs quietly if necessary when the install option is not set" do script <<-RUBY gemfile do - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" end - puts RACK + puts MYRACK RUBY expect(out).to eq("1.0.0") @@ -170,21 +169,21 @@ RSpec.describe "bundler/inline#gemfile" do it "installs subdependencies quietly if necessary when the install option is not set" do build_repo4 do - build_gem "rack" do |s| - s.add_dependency "rackdep" + build_gem "myrack" do |s| + s.add_dependency "myrackdep" end - build_gem "rackdep", "1.0.0" + build_gem "myrackdep", "1.0.0" end - script <<-RUBY + script <<-RUBY, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } gemfile do - source "#{file_uri_for(gem_repo4)}" - gem "rack" + source "https://gem.repo4" + gem "myrack" end - require "rackdep" - puts RACKDEP + require "myrackdep" + puts MYRACKDEP RUBY expect(out).to eq("1.0.0") @@ -193,23 +192,23 @@ RSpec.describe "bundler/inline#gemfile" do it "installs subdependencies quietly if necessary when the install option is not set, and multiple sources used" do build_repo4 do - build_gem "rack" do |s| - s.add_dependency "rackdep" + build_gem "myrack" do |s| + s.add_dependency "myrackdep" end - build_gem "rackdep", "1.0.0" + build_gem "myrackdep", "1.0.0" end - script <<-RUBY + script <<-RUBY, artifice: "compact_index_extra_api" gemfile do - source "#{file_uri_for(gem_repo1)}" - source "#{file_uri_for(gem_repo4)}" do - gem "rack" + source "https://test.repo" + source "https://test.repo/extra" do + gem "myrack" end end - require "rackdep" - puts RACKDEP + require "myrackdep" + puts MYRACKDEP RUBY expect(out).to eq("1.0.0") @@ -221,7 +220,7 @@ RSpec.describe "bundler/inline#gemfile" do baz_ref = build_git("baz", "2.0.0").ref_for("HEAD") script <<-RUBY gemfile do - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => #{lib_path("foo-1.0.0").to_s.dump} gem "baz", :git => #{lib_path("baz-2.0.0").to_s.dump}, :ref => #{baz_ref.dump} end @@ -238,14 +237,14 @@ RSpec.describe "bundler/inline#gemfile" do script <<-RUBY gemfile do path "#{lib_path}" do - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "two" end end gemfile do path "#{lib_path}" do - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "four" end end @@ -256,7 +255,7 @@ RSpec.describe "bundler/inline#gemfile" do end it "doesn't reinstall already installed gems" do - system_gems "rack-1.0.0" + system_gems "myrack-1.0.0" script <<-RUBY require 'bundler' @@ -264,65 +263,65 @@ RSpec.describe "bundler/inline#gemfile" do ui.level = "confirm" gemfile(true, ui: ui) do - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "activesupport" - gem "rack" + gem "myrack" end RUBY expect(out).to include("Installing activesupport") - expect(out).not_to include("Installing rack") + expect(out).not_to include("Installing myrack") expect(err).to be_empty end it "installs gems in later gemfile calls" do - system_gems "rack-1.0.0" + system_gems "myrack-1.0.0" script <<-RUBY require 'bundler' ui = Bundler::UI::Shell.new ui.level = "confirm" gemfile(true, ui: ui) do - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" end gemfile(true, ui: ui) do - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "activesupport" end RUBY expect(out).to include("Installing activesupport") - expect(out).not_to include("Installing rack") + expect(out).not_to include("Installing myrack") expect(err).to be_empty end it "doesn't reinstall already installed gems in later gemfile calls" do - system_gems "rack-1.0.0" + system_gems "myrack-1.0.0" script <<-RUBY require 'bundler' ui = Bundler::UI::Shell.new ui.level = "confirm" gemfile(true, ui: ui) do - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "activesupport" end gemfile(true, ui: ui) do - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" end RUBY expect(out).to include("Installing activesupport") - expect(out).not_to include("Installing rack") + expect(out).not_to include("Installing myrack") expect(err).to be_empty end it "installs gems with native extensions in later gemfile calls" do - system_gems "rack-1.0.0" + system_gems "myrack-1.0.0" build_git "foo" do |s| s.add_dependency "rake" @@ -343,12 +342,12 @@ RSpec.describe "bundler/inline#gemfile" do ui = Bundler::UI::Shell.new ui.level = "confirm" gemfile(true, ui: ui) do - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" end gemfile(true, ui: ui) do - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}" end @@ -364,7 +363,7 @@ RSpec.describe "bundler/inline#gemfile" do it "installs inline gems when a Gemfile.lock is present" do gemfile <<-G - source "https://notaserver.com" + source "https://notaserver.test" gem "rake" G @@ -381,16 +380,16 @@ RSpec.describe "bundler/inline#gemfile" do rake BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G script <<-RUBY gemfile do - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" end - puts RACK + puts MYRACK RUBY expect(err).to be_empty @@ -398,7 +397,7 @@ RSpec.describe "bundler/inline#gemfile" do it "does not leak Gemfile.lock versions to the installation output" do gemfile <<-G - source "https://notaserver.com" + source "https://notaserver.test" gem "rake" G @@ -415,12 +414,12 @@ RSpec.describe "bundler/inline#gemfile" do rake BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G script <<-RUBY gemfile(true) do - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rake", "#{rake_version}" end RUBY @@ -431,26 +430,26 @@ RSpec.describe "bundler/inline#gemfile" do end it "installs inline gems when frozen is set" do - script <<-RUBY, env: { "BUNDLE_FROZEN" => "true" } + script <<-RUBY, env: { "BUNDLE_FROZEN" => "true", "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s } gemfile do - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" end - puts RACK + puts MYRACK RUBY expect(last_command.stderr).to be_empty end it "installs inline gems when deployment is set" do - script <<-RUBY, env: { "BUNDLE_DEPLOYMENT" => "true" } + script <<-RUBY, env: { "BUNDLE_DEPLOYMENT" => "true", "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s } gemfile do - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" end - puts RACK + puts MYRACK RUBY expect(last_command.stderr).to be_empty @@ -461,11 +460,11 @@ RSpec.describe "bundler/inline#gemfile" do script <<-RUBY gemfile do - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" end - puts RACK + puts MYRACK RUBY expect(err).to be_empty @@ -476,11 +475,11 @@ RSpec.describe "bundler/inline#gemfile" do script <<-RUBY gemfile do - source "#{file_uri_for(gem_repo1)}" - gem "rack" # has the rackup executable + source "https://gem.repo1" + gem "myrack" # has the myrackup executable end - puts RACK + puts MYRACK RUBY expect(last_command).to be_success expect(out).to eq "1.0.0" @@ -488,24 +487,24 @@ RSpec.describe "bundler/inline#gemfile" do context "when BUNDLE_PATH is set" do it "installs inline gems to the system path regardless" do - script <<-RUBY, env: { "BUNDLE_PATH" => "./vendor/inline" } + script <<-RUBY, env: { "BUNDLE_PATH" => "./vendor/inline", "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s } gemfile(true) do - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" end RUBY expect(last_command).to be_success - expect(system_gem_path("gems/rack-1.0.0")).to exist + expect(system_gem_path("gems/myrack-1.0.0")).to exist end 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 - source "#{file_uri_for(gem_repo1)}" - gem "rack", platform: :jruby + source "https://gem.repo1" + gem "myrack", platform: :jruby end RUBY @@ -513,25 +512,25 @@ 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 - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" end RUBY expect(last_command).to be_success - expect(system_gem_path("gems/rack-1.0.0")).to exist + expect(system_gem_path("gems/myrack-1.0.0")).to exist end it "preserves previous BUNDLE_GEMFILE value" do ENV["BUNDLE_GEMFILE"] = "" script <<-RUBY gemfile do - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" end puts "BUNDLE_GEMFILE is empty" if ENV["BUNDLE_GEMFILE"].empty? @@ -547,8 +546,8 @@ RSpec.describe "bundler/inline#gemfile" do ENV["BUNDLE_GEMFILE"] = nil script <<-RUBY gemfile do - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" end puts "BUNDLE_GEMFILE is empty" if ENV["BUNDLE_GEMFILE"].empty? @@ -576,9 +575,9 @@ RSpec.describe "bundler/inline#gemfile" do s.write "lib/foo.rb", foo_code end - script <<-RUBY, dir: tmp("path_without_gemfile") + script <<-RUBY, dir: tmp("path_without_gemfile"), env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } gemfile do - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" path "#{lib_path}" do gem "foo", require: false end @@ -591,51 +590,162 @@ RSpec.describe "bundler/inline#gemfile" do expect(err).to be_empty end - it "when requiring fileutils after does not show redefinition warnings", :realworld do - Dir.mkdir tmp("path_without_gemfile") + 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? + + build_repo4 do + build_gem "timeout", "999" + end + + script <<-RUBY, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + require "bundler/inline" + + gemfile(true) do + source "https://gem.repo4" + + gem "timeout" + end + RUBY + + expect(out).to include("Installing timeout 999") + end + + it "does not upcase ENV" do + script <<-RUBY + require 'bundler/inline' + + ENV['Test_Variable'] = 'value string' + puts("before: \#{ENV.each_key.select { |key| key.match?(/test_variable/i) }}") + + gemfile do + source "https://gem.repo1" + end + + puts("after: \#{ENV.each_key.select { |key| key.match?(/test_variable/i) }}") + RUBY + + expect(out).to include("before: [\"Test_Variable\"]") + expect(out).to include("after: [\"Test_Variable\"]") + end + + it "does not create a lockfile" do + script <<-RUBY + require 'bundler/inline' + + gemfile do + source "https://gem.repo1" + end + + puts Dir.glob("Gemfile.lock") + RUBY + + expect(out).to be_empty + end + + it "does not reset ENV" do + script <<-RUBY + require 'bundler/inline' - 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? + gemfile do + source "https://gem.repo1" - realworld_system_gems "fileutils --version 1.4.1" + ENV['FOO'] = 'bar' + end - realworld_system_gems "pathname --version 0.2.0" + puts ENV['FOO'] + RUBY - script <<-RUBY, dir: tmp("path_without_gemfile"), env: { "BUNDLER_GEM_DEFAULT_DIR" => system_gem_path.to_s } + expect(out).to eq("bar") + end + + it "does not load specified version of psych and stringio", :ruby_repo do + build_repo4 do + build_gem "psych", "999" + build_gem "stringio", "999" + end + + script <<-RUBY, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } require "bundler/inline" gemfile(true) do - source "#{file_uri_for(gem_repo2)}" - end + source "https://gem.repo4" - require "fileutils" + gem "psych" + gem "stringio" + end RUBY - expect(err).to eq("The Gemfile specifies no dependencies") + expect(out).to include("Installing psych 999") + expect(out).to include("Installing stringio 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 "does not load default timeout" do - default_timeout_version = ruby "gem 'timeout', '< 999999'; require 'timeout'; puts Timeout::VERSION", raise_on_error: false - skip "timeout isn't a default gem" if default_timeout_version.empty? + 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 - # 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? + 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 "timeout", "999" + build_gem "securerandom", "999" end - script <<-RUBY + 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 + install_gemfile <<~G + source "https://gem.repo1" + gem "foo" + G + + original_lockfile = lockfile + + script <<-RUBY, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo1.to_s } require "bundler/inline" gemfile(true) do - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo1" - gem "timeout" + gem "myrack" end RUBY - expect(out).to include("Installing timeout 999") + expect(lockfile).to eq(original_lockfile) end end diff --git a/spec/bundler/runtime/load_spec.rb b/spec/bundler/runtime/load_spec.rb index f28ffd9460..472cde87c5 100644 --- a/spec/bundler/runtime/load_spec.rb +++ b/spec/bundler/runtime/load_spec.rb @@ -4,18 +4,18 @@ RSpec.describe "Bundler.load" do describe "with a gemfile" do before(:each) do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G allow(Bundler::SharedHelpers).to receive(:pwd).and_return(bundled_app) end it "provides a list of the env dependencies" do - expect(Bundler.load.dependencies).to have_dep("rack", ">= 0") + expect(Bundler.load.dependencies).to have_dep("myrack", ">= 0") end it "provides a list of the resolved gems" do - expect(Bundler.load.gems).to have_gem("rack-1.0.0", "bundler-#{Bundler::VERSION}") + expect(Bundler.load.gems).to have_gem("myrack-1.0.0", "bundler-#{Bundler::VERSION}") end it "ignores blank BUNDLE_GEMFILEs" do @@ -28,20 +28,20 @@ RSpec.describe "Bundler.load" do describe "with a gems.rb file" do before(:each) do - create_file "gems.rb", <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + gemfile "gems.rb", <<-G + source "https://gem.repo1" + gem "myrack" G bundle :install allow(Bundler::SharedHelpers).to receive(:pwd).and_return(bundled_app) end it "provides a list of the env dependencies" do - expect(Bundler.load.dependencies).to have_dep("rack", ">= 0") + expect(Bundler.load.dependencies).to have_dep("myrack", ">= 0") end it "provides a list of the resolved gems" do - expect(Bundler.load.gems).to have_gem("rack-1.0.0", "bundler-#{Bundler::VERSION}") + expect(Bundler.load.gems).to have_gem("myrack-1.0.0", "bundler-#{Bundler::VERSION}") end end @@ -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 @@ -76,8 +76,8 @@ RSpec.describe "Bundler.load" do describe "when called twice" do it "doesn't try to load the runtime twice" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" gem "activesupport", :group => :test G @@ -85,7 +85,7 @@ RSpec.describe "Bundler.load" do require "bundler" Bundler.setup :default Bundler.require :default - puts RACK + puts MYRACK begin require "activesupport" rescue LoadError @@ -100,7 +100,7 @@ RSpec.describe "Bundler.load" do describe "not hurting brittle rubygems" do it "does not inject #source into the generated YAML of the gem specs" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "activerecord" G allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) diff --git a/spec/bundler/runtime/platform_spec.rb b/spec/bundler/runtime/platform_spec.rb index 1925e9bf2e..6d96758956 100644 --- a/spec/bundler/runtime/platform_spec.rb +++ b/spec/bundler/runtime/platform_spec.rb @@ -3,21 +3,21 @@ RSpec.describe "Bundler.setup with multi platform stuff" do it "raises a friendly error when gems are missing locally" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G lockfile <<-G GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: - rack (1.0) + myrack (1.0) PLATFORMS #{local_tag} DEPENDENCIES - rack + myrack G ruby <<-R @@ -35,7 +35,7 @@ RSpec.describe "Bundler.setup with multi platform stuff" do it "will resolve correctly on the current platform when the lockfile was targeted for a different one" do lockfile <<-G GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: nokogiri (1.4.2-java) weakling (= 0.0.3) @@ -48,13 +48,14 @@ RSpec.describe "Bundler.setup with multi platform stuff" do nokogiri G - simulate_platform "x86-darwin-10" - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "nokogiri" - G + simulate_platform "x86-darwin-10" do + install_gemfile <<-G + source "https://gem.repo1" + gem "nokogiri" + G - expect(the_bundle).to include_gems "nokogiri 1.4.2" + expect(the_bundle).to include_gems "nokogiri 1.4.2" + end end it "will keep both platforms when both ruby and a specific ruby platform are locked and the bundle is unlocked" do @@ -82,7 +83,7 @@ RSpec.describe "Bundler.setup with multi platform stuff" do good_lockfile = <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: mini_portile2 (2.5.0) nokogiri (1.11.1) @@ -99,11 +100,11 @@ RSpec.describe "Bundler.setup with multi platform stuff" do nokogiri (~> 1.11) #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "nokogiri", "~> 1.11" G @@ -125,13 +126,13 @@ RSpec.describe "Bundler.setup with multi platform stuff" do end gemfile <<-G - source "https://gems.repo4" + source "https://gem.repo4" gem "nokogiri" G lockfile <<~L GEM - remote: https://gems.repo4/ + remote: https://gem.repo4/ specs: nokogiri (1.11.1) @@ -142,10 +143,10 @@ RSpec.describe "Bundler.setup with multi platform stuff" do nokogiri BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L - bundle "install", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + bundle "install" expect(out).to include("Fetching nokogiri 1.11.1") expect(the_bundle).to include_gems "nokogiri 1.11.1" @@ -154,13 +155,13 @@ RSpec.describe "Bundler.setup with multi platform stuff" do it "will use the java platform if both generic java and generic ruby platforms are locked", :jruby_only do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "nokogiri" G lockfile <<-G GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: nokogiri (1.4.2) nokogiri (1.4.2-java) @@ -181,13 +182,13 @@ RSpec.describe "Bundler.setup with multi platform stuff" do bundle "install" expect(out).to include("Fetching nokogiri 1.4.2 (java)") - expect(the_bundle).to include_gems "nokogiri 1.4.2 JAVA" + expect(the_bundle).to include_gems "nokogiri 1.4.2 java" end it "will add the resolve for the current platform" do lockfile <<-G GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: nokogiri (1.4.2-java) weakling (= 0.0.3) @@ -200,77 +201,77 @@ RSpec.describe "Bundler.setup with multi platform stuff" do nokogiri G - simulate_platform "x86-darwin-100" - - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "nokogiri" - gem "platform_specific" - G + simulate_platform "x86-darwin-100" do + install_gemfile <<-G + source "https://gem.repo1" + gem "nokogiri" + gem "platform_specific" + G - expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 x86-darwin-100" + expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 x86-darwin-100" + end end it "allows specifying only-ruby-platform on jruby", :jruby_only do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "nokogiri" gem "platform_specific" G - bundle "config set force_ruby_platform true" + bundle_config "force_ruby_platform true" bundle "install" - expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 RUBY" + expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 ruby" end it "allows specifying only-ruby-platform" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "nokogiri" gem "platform_specific" G - bundle "config set force_ruby_platform true" + bundle_config "force_ruby_platform true" bundle "install" - expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 RUBY" + expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 ruby" end it "allows specifying only-ruby-platform even if the lockfile is locked to a specific compatible platform" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "nokogiri" gem "platform_specific" G - bundle "config set force_ruby_platform true" + bundle_config "force_ruby_platform true" bundle "install" - expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 RUBY" + expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 ruby" end it "doesn't pull platform specific gems on truffleruby", :truffleruby_only do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "platform_specific" G - expect(the_bundle).to include_gems "platform_specific 1.0 RUBY" + expect(the_bundle).to include_gems "platform_specific 1.0 ruby" end - it "doesn't pull platform specific gems on truffleruby (except when whitelisted) even if lockfile was generated with an older version that declared RUBY as platform", :truffleruby_only do + it "doesn't pull platform specific gems on truffleruby (except when whitelisted) even if lockfile was generated with an older version that declared ruby as platform", :truffleruby_only do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "platform_specific" G lockfile <<-L GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: platform_specific (1.0) @@ -281,12 +282,12 @@ RSpec.describe "Bundler.setup with multi platform stuff" do platform_specific BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install" - expect(the_bundle).to include_gems "platform_specific 1.0 RUBY" + expect(the_bundle).to include_gems "platform_specific 1.0 ruby" simulate_platform "x86_64-linux" do build_repo4 do @@ -298,13 +299,13 @@ RSpec.describe "Bundler.setup with multi platform stuff" do end gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "libv8" G lockfile <<-L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: libv8 (1.0) @@ -315,7 +316,7 @@ RSpec.describe "Bundler.setup with multi platform stuff" do libv8 BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install" @@ -326,13 +327,13 @@ RSpec.describe "Bundler.setup with multi platform stuff" do it "doesn't pull platform specific gems on truffleruby, even if lockfile only includes those", :truffleruby_only do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "platform_specific" G lockfile <<-L GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: platform_specific (1.0-x86-darwin-100) @@ -343,12 +344,12 @@ RSpec.describe "Bundler.setup with multi platform stuff" do platform_specific BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install" - expect(the_bundle).to include_gems "platform_specific 1.0 RUBY" + expect(the_bundle).to include_gems "platform_specific 1.0 ruby" end it "pulls platform specific gems correctly on musl" do @@ -359,8 +360,8 @@ RSpec.describe "Bundler.setup with multi platform stuff" do end simulate_platform "aarch64-linux-musl" do - install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }, verbose: true - source "https://gems.repo4" + install_gemfile <<-G, verbose: true + source "https://gem.repo4" gem "nokogiri" G end @@ -369,38 +370,38 @@ 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 "#{file_uri_for(gem_repo1)}" - gem "nokogiri", :platforms => [:windows, :mswin, :mswin64, :mingw, :x64_mingw, :jruby] + source "https://gem.repo1" + gem "nokogiri", :platforms => [:windows, :jruby] gem "platform_specific" G - bundle "config set force_ruby_platform true" + bundle_config "force_ruby_platform true" bundle "install" - expect(the_bundle).to include_gems "platform_specific 1.0 RUBY" + expect(the_bundle).to include_gems "platform_specific 1.0 ruby" expect(the_bundle).to not_include_gems "nokogiri" end end it "allows specifying only-ruby-platform on windows with gemspec dependency" do build_lib("foo", "1.0", path: bundled_app) do |s| - s.add_dependency "rack" + s.add_dependency "myrack" end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gemspec 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 "rack 1.0" + expect(the_bundle).to include_gems "myrack 1.0" end end @@ -410,17 +411,17 @@ 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: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: platform_specific (1.0-x86-mingw32) requires_platform_specific (1.0) platform_specific PLATFORMS - x64-mingw32 + x64-mingw-ucrt x86-mingw32 DEPENDENCIES @@ -428,23 +429,21 @@ RSpec.describe "Bundler.setup with multi platform stuff" do L install_gemfile <<-G, verbose: true - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "requires_platform_specific" G expect(out).to include("lockfile does not have all gems needed for the current platform") - expect(the_bundle).to include_gem "platform_specific 1.0 x64-mingw32" + 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: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: platform_specific (1.0-#{platform}) requires_platform_specific (1.0) @@ -458,12 +457,10 @@ RSpec.describe "Bundler.setup with multi platform stuff" do L install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" 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 76271a5593..46613286d2 100644 --- a/spec/bundler/runtime/require_spec.rb +++ b/spec/bundler/runtime/require_spec.rb @@ -46,7 +46,7 @@ RSpec.describe "Bundler.require" do end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" path "#{lib_path}" do gem "one", :group => :bar, :require => %w[baz qux] gem "two" @@ -113,17 +113,15 @@ RSpec.describe "Bundler.require" do it "raises an exception if a require is specified but the file does not exist" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" path "#{lib_path}" do gem "two", :require => 'fail' 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 @@ -132,7 +130,7 @@ RSpec.describe "Bundler.require" do end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" path "#{lib_path}" do gem "faulty" end @@ -149,22 +147,15 @@ RSpec.describe "Bundler.require" do end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" path "#{lib_path}" do gem "loadfuuu" 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 @@ -176,7 +167,7 @@ RSpec.describe "Bundler.require" do it "requires gem names that are namespaced" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" path '#{lib_path}' do gem 'jquery-rails' end @@ -191,7 +182,7 @@ RSpec.describe "Bundler.require" do s.write "lib/brcrypt.rb", "BCrypt = '1.0.0'" end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" path "#{lib_path}" do gem "bcrypt-ruby" @@ -209,16 +200,15 @@ RSpec.describe "Bundler.require" do it "does not mangle explicitly given requires" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" path "#{lib_path}" do gem 'jquery-rails', :require => 'jquery-rails' 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 @@ -227,22 +217,15 @@ RSpec.describe "Bundler.require" do end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" path "#{lib_path}" do gem "load-fuuu" 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 @@ -251,22 +234,15 @@ RSpec.describe "Bundler.require" do end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" path "#{lib_path}" do gem "load-fuuu" 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 @@ -310,7 +286,7 @@ RSpec.describe "Bundler.require" do it "works when the gems are in the Gemfile in the correct order" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" path "#{lib_path}" do gem "two" gem "one" @@ -329,7 +305,7 @@ RSpec.describe "Bundler.require" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "multi_gem", :require => "one", :group => :one gem "multi_gem", :require => "two", :group => :two G @@ -353,7 +329,7 @@ RSpec.describe "Bundler.require" do it "fails when the gems are in the Gemfile in the wrong order" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" path "#{lib_path}" do gem "one" gem "two" @@ -371,26 +347,25 @@ RSpec.describe "Bundler.require" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" 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 it "does not load rubygems gemspecs that are used" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G run <<-R - path = File.join(Gem.dir, "specifications", "rack-1.0.0.gemspec") + path = File.join(Gem.dir, "specifications", "myrack-1.0.0.gemspec") contents = File.read(path) contents = contents.lines.to_a.insert(-2, "\n raise 'broken gemspec'\n").join File.open(path, "w") do |f| @@ -410,7 +385,7 @@ RSpec.describe "Bundler.require" do build_git "foo" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -430,18 +405,58 @@ RSpec.describe "Bundler.require" do expect(out).to eq("WIN") end + + it "does not load plugins" do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + create_file "plugins/rubygems_plugin.rb", "puts 'FAIL'" + + run <<~R, env: { "RUBYLIB" => rubylib.unshift(bundled_app("plugins").to_s).join(File::PATH_SEPARATOR) } + Bundler.require + puts "WIN" + R + + expect(out).to eq("WIN") + end + + it "does not extract gemspecs from application cache packages" do + gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + bundle :cache + + path = cached_gem("myrack-1.0.0") + + run <<-R + File.open("#{path}", "w") do |f| + f.write "broken package" + end + R + + run <<-R + Bundler.require + puts "WIN" + R + + expect(out).to eq("WIN") + end end RSpec.describe "Bundler.require with platform specific dependencies" do it "does not require the gems that are pinned to other platforms" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" platforms :#{not_local_tag} do gem "platform_specific", :require => "omgomg" end - gem "rack", "1.0.0" + gem "myrack", "1.0.0" G run "Bundler.require" @@ -450,14 +465,14 @@ RSpec.describe "Bundler.require with platform specific dependencies" do it "requires gems pinned to multiple platforms, including the current one" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" platforms :#{not_local_tag}, :#{local_tag} do - gem "rack", :require => "rack" + gem "myrack", :require => "myrack" end G - run "Bundler.require; puts RACK" + run "Bundler.require; puts MYRACK" expect(out).to eq("1.0.0") expect(err).to be_empty 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 d15ca3189e..176c2a3121 100644 --- a/spec/bundler/runtime/self_management_spec.rb +++ b/spec/bundler/runtime/self_management_spec.rb @@ -1,52 +1,75 @@ # frozen_string_literal: true -RSpec.describe "Self management", rubygems: ">= 3.3.0.dev", realworld: true do +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 - build_repo2 + build_repo4 do + build_bundler previous_minor + + build_bundler current_version + + build_gem "myrack", "1.0.0" + end gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo4" - gem "rack" + 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", artifice: "vcr" - expect(out).to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") + 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? + # App now uses locked version, even when not using the CLI directly + file = bundled_app("bin/bundle_version.rb") + create_file file, <<-RUBY + #!#{Gem.ruby} + require 'bundler/setup' + puts '#{previous_minor}' + RUBY + file.chmod(0o777) + cmd = Gem.win_platform? ? "#{Gem.ruby} bin/bundle_version.rb" : "bin/bundle_version.rb" + 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", artifice: "vcr" - expect(out).to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") + 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 @@ -55,20 +78,39 @@ RSpec.describe "Self management", rubygems: ">= 3.3.0.dev", realworld: true 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? + # App now uses locked version, even when not using the CLI directly + file = bundled_app("bin/bundle_version.rb") + create_file file, <<-RUBY + #!#{Gem.ruby} + require 'bundler/setup' + puts '#{previous_minor}' + RUBY + file.chmod(0o777) + cmd = Gem.win_platform? ? "#{Gem.ruby} bin/bundle_version.rb" : "bin/bundle_version.rb" + 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", artifice: "vcr" - expect(out).to include("Bundler #{Bundler::VERSION} is running, but your lockfile was generated with #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") + 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 @@ -77,12 +119,12 @@ RSpec.describe "Self management", rubygems: ">= 3.3.0.dev", realworld: true 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 @@ -92,48 +134,126 @@ RSpec.describe "Self management", rubygems: ">= 3.3.0.dev", realworld: true 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: local_gem_path + + bundle "install --local" + expect(out).not_to match(/Installing Bundler/) + + bundle "-v" + 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", artifice: "vcr" - expect(err).to eq("Your lockfile is locked to a version of bundler (#{missing_minor}) that doesn't exist at https://rubygems.org/. Going on using #{Bundler::VERSION}") + bundle "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 #{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", artifice: "vcr" - expect(out).to include("Bundler #{Bundler::VERSION} is running, but your configuration was #{previous_minor}. Installing Bundler #{previous_minor} and restarting using that version.") + bundle_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 "install", artifice: "vcr" + bundle_config "version system" + bundle "install" + expect(out).not_to match(/restarting using that version/) + + bundle "-v" + 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 "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 + system_gems "bundler-9.9.9", path: local_gem_path + + test = bundled_app("test.rb") + + create_file test, <<~RUBY + $0 = "this is the program name" + require "bundler/setup" + RUBY + + lockfile_bundled_with("9.9.9") + + 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 + system_gems "bundler-9.9.9", path: local_gem_path + + runner = bundled_app("runner.rb") + + create_file runner, <<~RUBY + $0 = ARGV.shift + load $0 + RUBY + + script = bundled_app("script.rb") + create_file script, <<~RUBY + require "bundler/setup" + RUBY + + lockfile_bundled_with("9.9.9") + + in_bundled_app "#{Gem.ruby} #{runner} #{script}", raise_on_error: false + expect(err).to include("Could not find myrack-1.0.0") end private @@ -141,15 +261,15 @@ RSpec.describe "Self management", rubygems: ">= 3.3.0.dev", realworld: true do def lockfile_bundled_with(version) lockfile <<~L GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo4/ specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack + myrack BUNDLED WITH #{version} diff --git a/spec/bundler/runtime/setup_spec.rb b/spec/bundler/runtime/setup_spec.rb index ccfe5d55b6..ceb6fcf66a 100644 --- a/spec/bundler/runtime/setup_spec.rb +++ b/spec/bundler/runtime/setup_spec.rb @@ -6,16 +6,16 @@ RSpec.describe "Bundler.setup" do describe "with no arguments" do it "makes all groups available" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :group => :test + source "https://gem.repo1" + gem "myrack", :group => :test G ruby <<-RUBY require 'bundler' Bundler.setup - require 'rack' - puts RACK + require 'myrack' + puts MYRACK RUBY expect(err).to be_empty expect(out).to eq("1.0.0") @@ -25,9 +25,9 @@ RSpec.describe "Bundler.setup" do describe "when called with groups" do before(:each) do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "yard" - gem "rack", :group => :test + gem "myrack", :group => :test G end @@ -37,7 +37,7 @@ RSpec.describe "Bundler.setup" do Bundler.setup(:default) begin - require 'rack' + require 'myrack' rescue LoadError puts "WIN" end @@ -51,8 +51,8 @@ RSpec.describe "Bundler.setup" do require 'bundler' Bundler.setup(:default, 'test') - require 'rack' - puts RACK + require 'myrack' + puts MYRACK RUBY expect(err).to be_empty expect(out).to eq("1.0.0") @@ -64,8 +64,8 @@ RSpec.describe "Bundler.setup" do Bundler.setup Bundler.setup(:default) - require 'rack' - puts RACK + require 'myrack' + puts MYRACK RUBY expect(err).to be_empty expect(out).to eq("1.0.0") @@ -93,12 +93,12 @@ RSpec.describe "Bundler.setup" do require 'bundler' Bundler.setup(:default, :test) Bundler.setup(:default) - require 'rack' + require 'myrack' puts "FAIL" RUBY - expect(err).to match("rack") + expect(err).to match("myrack") expect(err).to match("LoadError") expect(out).not_to match("FAIL") end @@ -113,8 +113,8 @@ RSpec.describe "Bundler.setup" do it "puts loaded gems after -I and RUBYLIB", :ruby_repo do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G ENV["RUBYOPT"] = "#{ENV["RUBYOPT"]} -Idash_i_dir" @@ -127,18 +127,18 @@ RSpec.describe "Bundler.setup" do RUBY load_path = out.split("\n") - rack_load_order = load_path.index {|path| path.include?("rack") } + myrack_load_order = load_path.index {|path| path.include?("myrack") } expect(err).to be_empty expect(load_path).to include(a_string_ending_with("dash_i_dir"), "rubylib_dir") - expect(rack_load_order).to be > 0 + expect(myrack_load_order).to be > 0 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rails" G @@ -163,10 +163,10 @@ 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "weakling" gem "duradura" gem "terranova" @@ -189,8 +189,8 @@ RSpec.describe "Bundler.setup" do it "raises if the Gemfile was not yet installed" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G ruby <<-R @@ -209,8 +209,8 @@ RSpec.describe "Bundler.setup" do it "doesn't create a Gemfile.lock if the setup fails" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G ruby <<-R, raise_on_error: false @@ -224,15 +224,15 @@ RSpec.describe "Bundler.setup" do it "doesn't change the Gemfile.lock if the setup fails" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G lockfile = File.read(bundled_app_lock) gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" gem "nosuchgem", "10.0" G @@ -247,8 +247,8 @@ RSpec.describe "Bundler.setup" do it "makes a Gemfile.lock if setup succeeds" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G File.read(bundled_app_lock) @@ -263,12 +263,12 @@ RSpec.describe "Bundler.setup" do context "user provides an absolute path" do it "uses BUNDLE_GEMFILE to locate the gemfile if present" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G gemfile bundled_app("4realz"), <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "activesupport", "2.3.5" G @@ -282,11 +282,11 @@ RSpec.describe "Bundler.setup" do context "an absolute path is not provided" do it "uses BUNDLE_GEMFILE to locate the gemfile if present and doesn't fail in deployment mode" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G bundle "install" - bundle "config set --local deployment true" + bundle_config "deployment true" ENV["BUNDLE_GEMFILE"] = "Gemfile" ruby <<-R @@ -303,28 +303,54 @@ 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 ENV["BUNDLE_PATH"] = bundled_app(".bundle").to_s install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "1.0.0" + source "https://gem.repo1" + gem "myrack", "1.0.0" G - build_gem "rack", "1.0", to_system: true do |s| - s.write "lib/rack.rb", "RACK = 'FAIL'" + build_gem "myrack", "1.0", to_system: true do |s| + s.write "lib/myrack.rb", "MYRACK = 'FAIL'" end - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end describe "integrate with rubygems" do describe "by replacing #gem" do before :each do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "0.9.1" + source "https://gem.repo1" + gem "myrack", "0.9.1" G end @@ -344,7 +370,7 @@ RSpec.describe "Bundler.setup" do it "replaces #gem but raises when the version is wrong" do run <<-R begin - gem "rack", "1.0.0" + gem "myrack", "1.0.0" puts "FAIL" rescue LoadError puts "WIN" @@ -359,14 +385,15 @@ RSpec.describe "Bundler.setup" do before :each do system_gems "activesupport-2.3.5" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "yard" G end it "removes system gems from Gem.source_index" do run "require 'yard'" - expect(out).to eq("bundler-#{Bundler::VERSION}\nyard-1.0") + expect(out).to include("bundler-#{Bundler::VERSION}").and include("yard-1.0") + expect(out).not_to include("activesupport-2.3.5") end context "when the ruby stdlib is a substring of Gem.path" do @@ -381,37 +408,37 @@ RSpec.describe "Bundler.setup" do describe "with paths" do it "activates the gems in the path source" do - system_gems "rack-1.0.0" + system_gems "myrack-1.0.0" - build_lib "rack", "1.0.0" do |s| - s.write "lib/rack.rb", "puts 'WIN'" + build_lib "myrack", "1.0.0" do |s| + s.write "lib/myrack.rb", "puts 'WIN'" end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - path "#{lib_path("rack-1.0.0")}" do - gem "rack" + source "https://gem.repo1" + path "#{lib_path("myrack-1.0.0")}" do + gem "myrack" end G - run "require 'rack'" + run "require 'myrack'" expect(out).to eq("WIN") end end describe "with git" do before do - build_git "rack", "1.0.0" + build_git "myrack", "1.0.0" gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-1.0.0")}" + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-1.0.0")}" G end it "provides a useful exception when the git repo is not checked out yet" do run "1", raise_on_error: false - expect(err).to match(/the git source #{lib_path("rack-1.0.0")} is not yet checked out. Please run `bundle install`/i) + expect(err).to match(/the git source #{lib_path("myrack-1.0.0")} is not yet checked out. Please run `bundle install`/i) end it "does not hit the git binary if the lockfile is available and up to date" do @@ -457,120 +484,119 @@ 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") - expect(the_bundle).to include_gems "rack 1.0.0" + 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 - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end 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 - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end end end describe "when specifying local override" do it "explodes if given path does not exist on runtime" do - build_git "rack", "0.8" + build_git "myrack", "0.8" - FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) + FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack")) gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main" G - bundle %(config set local.rack #{lib_path("local-rack")}) + bundle %(config set local.myrack #{lib_path("local-myrack")}) bundle :install - FileUtils.rm_rf(lib_path("local-rack")) - run "require 'rack'", raise_on_error: false - expect(err).to match(/Cannot use local override for rack-0.8 because #{Regexp.escape(lib_path("local-rack").to_s)} does not exist/) + 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 it "explodes if branch is not given on runtime" do - build_git "rack", "0.8" + build_git "myrack", "0.8" - FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) + FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack")) gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main" G - bundle %(config set local.rack #{lib_path("local-rack")}) + bundle %(config set local.myrack #{lib_path("local-myrack")}) bundle :install gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}" + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}" G - run "require 'rack'", raise_on_error: false + run "require 'myrack'", raise_on_error: false expect(err).to match(/because :branch is not specified in Gemfile/) end it "explodes on different branches on runtime" do - build_git "rack", "0.8" + build_git "myrack", "0.8" - FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) + FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack")) gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "main" + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main" G - bundle %(config set local.rack #{lib_path("local-rack")}) + bundle %(config set local.myrack #{lib_path("local-myrack")}) bundle :install gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :branch => "changed" + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "changed" G - run "require 'rack'", raise_on_error: false + run "require 'myrack'", raise_on_error: false expect(err).to match(/is using branch main but Gemfile specifies changed/) end it "explodes on refs with different branches on runtime" do - build_git "rack", "0.8" + build_git "myrack", "0.8" - FileUtils.cp_r("#{lib_path("rack-0.8")}/.", lib_path("local-rack")) + FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack")) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :ref => "main", :branch => "main" + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}", :ref => "main", :branch => "main" G gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{lib_path("rack-0.8")}", :ref => "main", :branch => "nonexistent" + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}", :ref => "main", :branch => "nonexistent" G - bundle %(config set local.rack #{lib_path("local-rack")}) - run "require 'rack'", raise_on_error: false + bundle %(config set local.myrack #{lib_path("local-myrack")}) + run "require 'myrack'", raise_on_error: false expect(err).to match(/is using branch main but Gemfile specifies nonexistent/) end end 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "activesupport" group :rails do @@ -584,9 +610,9 @@ 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "activesupport" group :rails do @@ -600,18 +626,18 @@ 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 install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "activesupport", "2.3.2" 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") @@ -629,7 +655,7 @@ RSpec.describe "Bundler.setup" do end install_gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "depends_on_bundler" G @@ -641,7 +667,7 @@ RSpec.describe "Bundler.setup" do it "doesn't fail in frozen mode when bundler is a Gemfile dependency" do install_gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "bundler" G @@ -651,14 +677,14 @@ RSpec.describe "Bundler.setup" do it "doesn't re-resolve when deleting dependencies" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" gem "actionpack" G install_gemfile <<-G, verbose: true - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G expect(out).to include("Some dependencies were deleted, using a subset of the resolution from the lockfile") @@ -666,13 +692,13 @@ 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 "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "activesupport" - group :rack do - gem "rack" + group :myrack do + gem "myrack" end group :rails do @@ -680,8 +706,8 @@ RSpec.describe "Bundler.setup" do end G - expect(the_bundle).not_to include_gems "activesupport 2.3.2", groups: :rack - expect(the_bundle).to include_gems "rack 1.0.0", groups: :rack + expect(the_bundle).not_to include_gems "activesupport 2.3.2", groups: :myrack + expect(the_bundle).to include_gems "myrack 1.0.0", groups: :myrack end end @@ -691,8 +717,8 @@ RSpec.describe "Bundler.setup" do build_git "no-gemspec", gemspec: false install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" gem "foo", :git => "#{lib_path("foo-1.0")}" gem "no-gemspec", "1.0", :git => "#{lib_path("no-gemspec-1.0")}" G @@ -706,59 +732,80 @@ RSpec.describe "Bundler.setup" do expect(out).to be_empty end + it "has gem_dir pointing to local repo" do + build_lib "foo", "1.0", path: bundled_app + + install_gemfile <<-G + source "https://gem.repo1" + gemspec + G + + run <<-R + puts Gem.loaded_specs['foo'].gem_dir + R + + expect(out).to eq(bundled_app.to_s) + end + it "does not load all gemspecs" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" 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 install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G ENV["GEM_HOME"] = "" @@ -767,6 +814,18 @@ end expect(err).to be_empty end + it "can require rubygems without warnings, when using a local cache", :truffleruby do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + bundle "package" + bundle %(exec ruby -w -e "require 'rubygems'") + + expect(err).to be_empty + end + context "when the user has `MANPATH` set", :man do before { ENV["MANPATH"] = "/foo#{File::PATH_SEPARATOR}" } @@ -778,7 +837,7 @@ end end install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "with_man" G @@ -802,7 +861,7 @@ end end install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "with_man" G @@ -815,7 +874,7 @@ end expect(out).to eq("#{default_bundle_path("gems/with_man-1.0/man")}#{File::PATH_SEPARATOR}\ntrue") install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "with_man_overriding_system_man" G @@ -842,7 +901,7 @@ end end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "requirepaths", :require => nil G @@ -853,21 +912,14 @@ end it "should clean $LOAD_PATH properly" do gem_name = "very_simple_binary" full_gem_name = gem_name + "-1.0" - ext_dir = File.join(tmp("extensions", full_gem_name)) system_gems full_gem_name install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" G ruby <<-R - s = Gem::Specification.find_by_name '#{gem_name}' - s.extension_dir = '#{ext_dir}' - - # Don't build extensions. - s.class.send(:define_method, :build_extensions) { nil } - require 'bundler' gem '#{gem_name}' @@ -905,7 +957,7 @@ end end it "should not remove itself from the LOAD_PATH and require a different copy of 'bundler/setup'" do - install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" + install_gemfile "source 'https://gem.repo1'" ruby <<-R, env: { "GEM_PATH" => symlinked_gem_home } TracePoint.trace(:class) do |tp| @@ -922,17 +974,17 @@ end end it "does not reveal system gems even when Gem.refresh is called" do - system_gems "rack-1.0.0" + system_gems "myrack-1.0.0" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "activesupport" G run <<-R - puts Bundler.rubygems.all_specs.map(&:name) + puts Bundler.rubygems.installed_specs.map(&:name) Gem.refresh - puts Bundler.rubygems.all_specs.map(&:name) + puts Bundler.rubygems.installed_specs.map(&:name) R expect(out).to eq("activesupport\nbundler\nactivesupport\nbundler") @@ -954,7 +1006,7 @@ end FileUtils.rm(File.join(path, "foo.gemspec")) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'foo', '1.2.3', :path => 'vendor/foo' G @@ -975,7 +1027,7 @@ end FileUtils.rm(File.join(absolute_path, "foo.gemspec")) gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'foo', '1.2.3', :path => '#{relative_path}' G @@ -994,7 +1046,7 @@ end build_git "no_gemspec", gemspec: false install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "no_gemspec", "1.0", :git => "#{lib_path("no_gemspec-1.0")}" G end @@ -1011,10 +1063,10 @@ end describe "with bundled and system gems" do before :each do - system_gems "rack-1.0.0" + system_gems "myrack-1.0.0" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "activesupport", "2.3.5" G @@ -1023,7 +1075,7 @@ end it "does not pull in system gems" do run <<-R begin; - require 'rack' + require 'myrack' rescue LoadError puts 'WIN' end @@ -1045,13 +1097,13 @@ end it "raises an exception if gem is used to invoke a system gem not in the bundle" do run <<-R begin - gem 'rack' + gem 'myrack' rescue LoadError => e puts e.message end R - expect(out).to eq("rack is not part of the bundle. Add it to your Gemfile.") + expect(out).to eq("myrack is not part of the bundle. Add it to your Gemfile.") end it "sets GEM_HOME appropriately" do @@ -1062,12 +1114,12 @@ end describe "with system gems in the bundle" do before :each do - bundle "config set path.system true" - system_gems "rack-1.0.0" + bundle_config "path.system true" + system_gems "myrack-1.0.0" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", "1.0.0" + source "https://gem.repo1" + gem "myrack", "1.0.0" gem "activesupport", "2.3.5" G end @@ -1097,7 +1149,7 @@ end end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "bar", :git => "#{lib_path("bar-1.0")}" G end @@ -1131,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 @@ -1144,7 +1196,7 @@ end describe "when Bundler is bundled" do it "doesn't blow up" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "bundler", :path => "#{root}" G @@ -1157,15 +1209,15 @@ end def lock_with(bundler_version = nil) lock = <<~L GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack + myrack L if bundler_version @@ -1176,11 +1228,11 @@ end end before do - bundle "config set --local path.system true" + bundle_config "path.system true" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G end @@ -1217,20 +1269,20 @@ end def lock_with(ruby_version = nil) checksums = checksums_section do |c| - c.checksum gem_repo1, "rack", "1.0.0" + c.checksum gem_repo1, "myrack", "1.0.0" end lock = <<~L GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack + myrack #{checksums} L @@ -1241,7 +1293,7 @@ end lock += <<~L BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L lock @@ -1250,14 +1302,19 @@ end before do install_gemfile <<-G ruby ">= 0" - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G lockfile lock_with(ruby_version) 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 @@ -1293,19 +1350,19 @@ end s.files = Dir["lib/**/*.rb"] s.author = 'no one' - s.add_runtime_dependency 'digest' + s.add_dependency 'digest' end G end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "bar", :git => "#{lib_path("bar-1.0")}" G - bundle :install + bundle :install, env: { "BUNDLE_LOCKFILE_CHECKSUMS" => "false" } - ruby <<-RUBY + ruby <<-RUBY, artifice: nil require 'bundler/setup' puts defined?(::Digest) ? "Digest defined" : "Digest undefined" require 'digest' @@ -1314,7 +1371,7 @@ end end it "does not load Psych" do - gemfile "source \"#{file_uri_for(gem_repo1)}\"" + gemfile "source 'https://gem.repo1'" ruby <<-RUBY require 'bundler/setup' puts defined?(Psych::VERSION) ? Psych::VERSION : "undefined" @@ -1327,8 +1384,8 @@ end end it "does not load openssl" do - install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" - ruby <<-RUBY + install_gemfile "source 'https://gem.repo1'" + ruby <<-RUBY, artifice: nil require "bundler/setup" puts defined?(OpenSSL) || "undefined" require "openssl" @@ -1351,11 +1408,11 @@ end G install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "test", path: "#{bundled_app("test")}" G - ruby <<-RUBY + ruby <<-RUBY, artifice: nil require "bundler/setup" puts defined?(URI) || "undefined" require "uri" @@ -1364,12 +1421,28 @@ end expect(out).to eq("undefined\nconstant") end + it "activates default gems when they are part of the bundle, but not installed explicitly", :ruby_repo do + default_delegate_version = ruby "gem 'delegate'; require 'delegate'; puts Delegator::VERSION" + + build_repo2 do + build_gem "delegate", default_delegate_version + end + + gemfile "source \"https://gem.repo2\"; gem 'delegate'" + + ruby <<-RUBY + require "bundler/setup" + require "delegate" + puts defined?(::Delegator) ? "Delegator defined" : "Delegator undefined" + RUBY + + expect(out).to eq("Delegator defined") + expect(err).to be_empty + 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.win_platform? - exempts << "set" unless Gem.rubygems_version >= Gem::Version.new("3.2.6") - exempts << "tsort" unless Gem.rubygems_version >= Gem::Version.new("3.2.31") exempts << "error_highlight" # added in Ruby 3.1 as a default gem exempts << "ruby2_keywords" # added in Ruby 3.1 as a default gem exempts << "syntax_suggest" # added in Ruby 3.2 as a default gem @@ -1410,13 +1483,13 @@ end RUBY it "activates no gems with -rbundler/setup" do - install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" - ruby code, env: { "RUBYOPT" => activation_warning_hack_rubyopt + " -rbundler/setup" } + install_gemfile "source 'https://gem.repo1'" + ruby code, env: { "RUBYOPT" => activation_warning_hack_rubyopt + " -rbundler/setup" }, artifice: nil expect(out).to eq("{}") end it "activates no gems with bundle exec" do - install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" + install_gemfile "source 'https://gem.repo1'" create_file("script.rb", code) bundle "exec ruby ./script.rb", env: { "RUBYOPT" => activation_warning_hack_rubyopt } expect(out).to eq("{}") @@ -1425,10 +1498,10 @@ end it "activates no gems with bundle exec that is loaded" do skip "not executable" if Gem.win_platform? - install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" + 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 @@ -1440,11 +1513,11 @@ end system_gems "net-http-pipeline-1.0.1", gem_repo: gem_repo4 gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "net-http-pipeline", "1.0.1" G - bundle "config set --local path vendor/bundle" + bundle_config "path vendor/bundle" bundle :install @@ -1462,11 +1535,11 @@ end end install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "#{g}", "999999" G - expect(the_bundle).to include_gem("#{g} 999999", env: { "RUBYOPT" => activation_warning_hack_rubyopt }) + expect(the_bundle).to include_gem("#{g} 999999", env: { "RUBYOPT" => activation_warning_hack_rubyopt }, artifice: nil) end it "activates older versions of #{g}", :ruby_repo do @@ -1477,69 +1550,54 @@ end end install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "#{g}", "0.0.0.a" G - expect(the_bundle).to include_gem("#{g} 0.0.0.a", env: { "RUBYOPT" => activation_warning_hack_rubyopt }) + expect(the_bundle).to include_gem("#{g} 0.0.0.a", env: { "RUBYOPT" => activation_warning_hack_rubyopt }, artifice: nil) end end end end describe "after setup" do - it "allows calling #gem on random objects", bundler: "< 3" do + it "keeps Kernel#gem private" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" - G - - ruby <<-RUBY - require "bundler/setup" - Object.new.gem "rack" - puts Gem.loaded_specs["rack"].full_name - RUBY - - expect(out).to eq("rack-1.0.0") - end - - it "keeps Kernel#gem private", bundler: "3" do - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G ruby <<-RUBY, raise_on_error: false require "bundler/setup" - Object.new.gem "rack" + Object.new.gem "myrack" puts "FAIL" RUBY - expect(last_command.stdboth).not_to include "FAIL" - expect(err).to include "private method `gem'" + expect(stdboth).not_to include "FAIL" + expect(err).to match(/private method [`']gem'/) end it "keeps Kernel#require private" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" G ruby <<-RUBY, raise_on_error: false require "bundler/setup" - Object.new.require "rack" + Object.new.require "myrack" 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 it "memoizes initial set of specs when requiring bundler/setup, so that even if further code mutates dependencies, Bundler.definition.specs is not affected" do install_gemfile <<~G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "yard" - gem "rack", :group => :test + gem "myrack", :group => :test G ruby <<-RUBY, raise_on_error: false @@ -1548,7 +1606,7 @@ end puts Bundler.definition.specs.map(&:name).join(", ") RUBY - expect(out).to include("rack, yard") + expect(out).to include("myrack, yard") end it "does not cause double loads when higher versions of default gems are activated before bundler" do @@ -1564,7 +1622,7 @@ end system_gems "json-999.999.999", gem_repo: gem_repo2 - install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" + install_gemfile "source 'https://gem.repo1'" ruby <<-RUBY require "json" require "bundler/setup" @@ -1576,7 +1634,7 @@ end end it "does not undo the Kernel.require decorations", rubygems: ">= 3.4.6" do - install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" + install_gemfile "source 'https://gem.repo1'" script = bundled_app("bin/script") create_file(script, <<~RUBY) module Kernel @@ -1599,4 +1657,54 @@ end sys_exec "#{Gem.ruby} #{script}", raise_on_error: false expect(out).to include("requiring foo used the monkeypatch") end + + it "performs an automatic bundle install" do + build_repo4 do + build_gem "myrack", "1.0.0" + end + + gemfile <<-G + source "https://gem.repo1" + gem "myrack", :group => :test + G + + bundle_config "auto_install 1" + + ruby <<-RUBY, artifice: "compact_index" + require 'bundler/setup' + RUBY + 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 66bdcfa028..27ddc6a771 100644 --- a/spec/bundler/spec_helper.rb +++ b/spec/bundler/spec_helper.rb @@ -9,14 +9,28 @@ 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 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/build_metadata" require_relative "support/checksums" require_relative "support/filters" require_relative "support/helpers" @@ -24,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 @@ -42,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" @@ -76,31 +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::Helpers) - system_gems :bundler, path: pristine_system_gem_path - end - - config.before :all do - check_test_gems! + extend(Spec::Builders) 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 } @@ -117,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/activate.rb b/spec/bundler/support/activate.rb index e19a6d0ed1..143b77833d 100644 --- a/spec/bundler/support/activate.rb +++ b/spec/bundler/support/activate.rb @@ -5,5 +5,5 @@ Gem.instance_variable_set(:@ruby, ENV["RUBY"]) if ENV["RUBY"] require_relative "path" bundler_gemspec = Spec::Path.loaded_gemspec -bundler_gemspec.instance_variable_set(:@full_gem_path, Spec::Path.source_root) +bundler_gemspec.instance_variable_set(:@full_gem_path, Spec::Path.source_root.to_s) bundler_gemspec.activate if bundler_gemspec.respond_to?(:activate) diff --git a/spec/bundler/support/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_creds_diff_host.rb b/spec/bundler/support/artifice/compact_index_creds_diff_host.rb index 401e8a98d8..282e9c8961 100644 --- a/spec/bundler/support/artifice/compact_index_creds_diff_host.rb +++ b/spec/bundler/support/artifice/compact_index_creds_diff_host.rb @@ -24,7 +24,7 @@ class CompactIndexCredsDiffHost < CompactIndexAPI end get "/gems/:id" do - redirect "http://diffhost.com/no/creds/#{params[:id]}" + redirect "http://diffhost.test/no/creds/#{params[:id]}" end get "/no/creds/:id" do 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/endpoint_500.rb b/spec/bundler/support/artifice/endpoint_500.rb index b1ed1964c8..9dd373bbf6 100644 --- a/spec/bundler/support/artifice/endpoint_500.rb +++ b/spec/bundler/support/artifice/endpoint_500.rb @@ -2,7 +2,7 @@ require_relative "../path" -$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gem_path.join("gems/{mustermann,rack,tilt,sinatra,ruby2_keywords,base64}-*/lib")].map(&:to_s)) +$LOAD_PATH.unshift(*Spec::Path.sinatra_dependency_paths) require "sinatra/base" diff --git a/spec/bundler/support/artifice/endpoint_creds_diff_host.rb b/spec/bundler/support/artifice/endpoint_creds_diff_host.rb index ce30de0a68..9cbb4de61a 100644 --- a/spec/bundler/support/artifice/endpoint_creds_diff_host.rb +++ b/spec/bundler/support/artifice/endpoint_creds_diff_host.rb @@ -24,7 +24,7 @@ class EndpointCredsDiffHost < Endpoint end get "/gems/:id" do - redirect "http://diffhost.com/no/creds/#{params[:id]}" + redirect "http://diffhost.test/no/creds/#{params[:id]}" end get "/no/creds/:id" do diff --git a/spec/bundler/support/artifice/fail.rb b/spec/bundler/support/artifice/fail.rb index 8822e5b8e2..5ddbc4e590 100644 --- a/spec/bundler/support/artifice/fail.rb +++ b/spec/bundler/support/artifice/fail.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "bundler/vendored_net_http" +require_relative "../vendored_net_http" class Fail < Gem::Net::HTTP # Gem::Net::HTTP uses a @newimpl instance variable to decide whether diff --git a/spec/bundler/support/artifice/helpers/compact_index.rb b/spec/bundler/support/artifice/helpers/compact_index.rb index a803a2d30a..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" @@ -40,7 +40,7 @@ class CompactIndexAPI < Endpoint end def requested_range_for(response_body) - ranges = Rack::Utils.byte_ranges(env, response_body.bytesize) + ranges = Rack::Utils.get_byte_ranges(env["HTTP_RANGE"], response_body.bytesize) if ranges status 206 @@ -67,11 +67,14 @@ class CompactIndexAPI < Endpoint @gems ||= {} @gems[gem_repo] ||= begin specs = Bundler::Deprecate.skip_during do - %w[specs.4.8 prerelease_specs.4.8].map do |filename| - Marshal.load(File.open(gem_repo.join(filename)).read).map do |name, version, platform| + %w[specs.4.8 prerelease_specs.4.8].flat_map do |filename| + spec_index = gem_repo.join(filename) + next [] unless File.exist?(spec_index) + + Marshal.load(File.binread(spec_index)).map do |name, version, platform| load_spec(name, version, platform, gem_repo) end - end.flatten + end end specs.group_by(&:name).map do |name, versions| @@ -87,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 83ba1be0fc..9590611dfe 100644 --- a/spec/bundler/support/artifice/helpers/endpoint.rb +++ b/spec/bundler/support/artifice/helpers/endpoint.rb @@ -2,7 +2,7 @@ require_relative "../../path" -$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gem_path.join("gems/{mustermann,rack,tilt,sinatra,ruby2_keywords,base64}-*/lib")].map(&:to_s)) +$LOAD_PATH.unshift(*Spec::Path.sinatra_dependency_paths) require "sinatra/base" @@ -27,6 +27,7 @@ class Endpoint < Sinatra::Base set :raise_errors, true set :show_exceptions, false + 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 @@ -62,10 +63,10 @@ class Endpoint < Sinatra::Base return [] if gem_names.nil? || gem_names.empty? all_specs = %w[specs.4.8 prerelease_specs.4.8].map do |filename| - Marshal.load(File.open(gem_repo.join(filename)).read) + Marshal.load(File.binread(gem_repo.join(filename))) end.inject(:+) - all_specs.map do |name, version, platform| + all_specs.filter_map do |name, version, platform| spec = load_spec(name, version, platform, gem_repo) next unless gem_names.include?(spec.name) { @@ -76,7 +77,7 @@ class Endpoint < Sinatra::Base [dep.name, dep.requirement.requirements.map {|a| a.join(" ") }.join(", ")] end, } - end.compact + end end def load_spec(name, version, platform, gem_repo) diff --git a/spec/bundler/support/artifice/helpers/rack_request.rb b/spec/bundler/support/artifice/helpers/rack_request.rb index f419bacb8c..05ff034463 100644 --- a/spec/bundler/support/artifice/helpers/rack_request.rb +++ b/spec/bundler/support/artifice/helpers/rack_request.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "rack/test" -require "bundler/vendored_net_http" +require_relative "../../vendored_net_http" module Artifice module Net diff --git a/spec/bundler/support/artifice/vcr.rb b/spec/bundler/support/artifice/vcr.rb index 7b9a8bdeaf..0bf5ade8f6 100644 --- a/spec/bundler/support/artifice/vcr.rb +++ b/spec/bundler/support/artifice/vcr.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "bundler/vendored_net_http" +require_relative "../vendored_net_http" require_relative "../path" CASSETTE_PATH = "#{Spec::Path.spec_dir}/support/artifice/vcr_cassettes".freeze @@ -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/artifice/windows.rb b/spec/bundler/support/artifice/windows.rb index fea991c071..3056540beb 100644 --- a/spec/bundler/support/artifice/windows.rb +++ b/spec/bundler/support/artifice/windows.rb @@ -2,7 +2,7 @@ require_relative "../path" -$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gem_path.join("gems/{mustermann,rack,tilt,sinatra,ruby2_keywords,base64}-*/lib")].map(&:to_s)) +$LOAD_PATH.unshift(*Spec::Path.sinatra_dependency_paths) require "sinatra/base" diff --git a/spec/bundler/support/build_metadata.rb b/spec/bundler/support/build_metadata.rb index 5898e7f3bd..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) @@ -41,7 +40,12 @@ module Spec end def git_commit_sha - ruby_core_tarball? ? "unknown" : sys_exec("git rev-parse --short HEAD", dir: source_root).strip + ruby_core_tarball? ? "unknown" : 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 diff --git a/spec/bundler/support/builders.rb b/spec/bundler/support/builders.rb index 46931e9ca5..43ab7e053d 100644 --- a/spec/bundler/support/builders.rb +++ b/spec/bundler/support/builders.rb @@ -2,9 +2,18 @@ require "bundler/shared_helpers" require "shellwords" +require "fileutils" +require "rubygems/package" + +require_relative "build_metadata" module Spec module Builders + def self.extended(mod) + mod.extend Path + mod.extend Helpers + end + def self.constantize(name) name.delete("-").upcase end @@ -17,13 +26,7 @@ module Spec Gem::Platform.new(platform) end - def rake_version - "13.1.0" - end - def build_repo1 - rake_path = Dir["#{Path.base_system_gems}/**/rake*.gem"].first - build_repo gem_repo1 do FileUtils.cp rake_path, "#{gem_repo1}/gems/" @@ -32,23 +35,23 @@ module Spec build_gem "puma" build_gem "minitest" - build_gem "rack", %w[0.9.1 1.0.0] do |s| - s.executables = "rackup" - s.post_install_message = "Rack's post install message" + build_gem "myrack", %w[0.9.1 1.0.0] do |s| + s.executables = "myrackup" + s.post_install_message = "Myrack's post install message" end build_gem "thin" do |s| - s.add_dependency "rack" + s.add_dependency "myrack" s.post_install_message = "Thin's post install message" end - build_gem "rack-obama" do |s| - s.add_dependency "rack" - s.post_install_message = "Rack-obama's post install message" + build_gem "myrack-obama" do |s| + s.add_dependency "myrack" + s.post_install_message = "Myrack-obama's post install message" end - build_gem "rack_middleware", "1.0" do |s| - s.add_dependency "rack", "0.9.1" + build_gem "myrack_middleware", "1.0" do |s| + s.add_dependency "myrack", "0.9.1" end build_gem "rails", "2.3.2" do |s| @@ -81,80 +84,62 @@ module Spec s.write "lib/spec.rb", "SPEC = '1.2.7'" end - build_gem "rack-test", no_default: true do |s| - s.write "lib/rack/test.rb", "RACK_TEST = '1.0'" - end - - build_gem "platform_specific" do |s| - s.platform = Gem::Platform.local - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 #{Gem::Platform.local}'" + build_gem "myrack-test", no_default: true do |s| + s.write "lib/myrack/test.rb", "MYRACK_TEST = '1.0'" end build_gem "platform_specific" do |s| s.platform = "java" - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 JAVA'" end build_gem "platform_specific" do |s| s.platform = "ruby" - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 RUBY'" end build_gem "platform_specific" do |s| s.platform = "x86-mswin32" - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0 x86-mswin32'" end build_gem "platform_specific" do |s| s.platform = "x64-mswin64" - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0 x64-mswin64'" end build_gem "platform_specific" do |s| s.platform = "x86-mingw32" - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0 x86-mingw32'" end build_gem "platform_specific" do |s| - s.platform = "x64-mingw32" - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0 x64-mingw32'" + s.platform = "x64-mingw-ucrt" end build_gem "platform_specific" do |s| - s.platform = "x64-mingw-ucrt" - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0 x64-mingw-ucrt'" + s.platform = "aarch64-mingw-ucrt" end build_gem "platform_specific" do |s| s.platform = "x86-darwin-100" - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 x86-darwin-100'" end build_gem "only_java", "1.0" do |s| s.platform = "java" - s.write "lib/only_java.rb", "ONLY_JAVA = '1.0.0 JAVA'" end build_gem "only_java", "1.1" do |s| s.platform = "java" - s.write "lib/only_java.rb", "ONLY_JAVA = '1.1.0 JAVA'" end build_gem "nokogiri", "1.4.2" build_gem "nokogiri", "1.4.2" do |s| s.platform = "java" - s.write "lib/nokogiri.rb", "NOKOGIRI = '1.4.2 JAVA'" s.add_dependency "weakling", ">= 0.0.3" end build_gem "laduradura", "5.15.2" build_gem "laduradura", "5.15.2" do |s| s.platform = "java" - s.write "lib/laduradura.rb", "LADURADURA = '5.15.2 JAVA'" end build_gem "laduradura", "5.15.3" do |s| s.platform = "java" - s.write "lib/laduradura.rb", "LADURADURA = '5.15.2 JAVA'" end build_gem "weakling", "0.0.3" @@ -168,7 +153,7 @@ module Spec build_gem "bundler", "0.9" do |s| s.executables = "bundle" - s.write "bin/bundle", "puts 'FAIL'" + s.write "bin/bundle", "#!/usr/bin/env ruby\nputs 'FAIL'" end # The bundler 0.8 gem has a rubygems plugin that always loads :( @@ -196,29 +181,44 @@ 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.) - def build_repo4(**kwargs, &blk) - FileUtils.rm_rf gem_repo4 - build_repo(gem_repo4, **kwargs, &blk) + # + # If the repo already exists, `#update_repo` will be called. + def build_repo3(**kwargs, &blk) + if File.exist?(gem_repo3) + update_repo(gem_repo3, &blk) + else + build_repo gem_repo3, **kwargs, &blk + end end - def update_repo4(&blk) - update_repo(gem_repo4, &blk) + # Like build_repo3, this is a repo that has no pre-installed gems included. + # + # If the repo already exists, `#udpate_repo` will be called + def build_repo4(**kwargs, &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 "rack" + build_gem "myrack" build_gem "signed_gem" do |s| cert = "signing-cert.pem" @@ -231,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) @@ -239,36 +274,17 @@ module Spec update_repo(path,**kwargs, &blk) end - def check_test_gems! - rake_path = Dir["#{Path.base_system_gems}/**/rake*.gem"].first - - if rake_path.nil? - FileUtils.rm_rf(Path.base_system_gems) - Spec::Rubygems.install_test_deps - rake_path = Dir["#{Path.base_system_gems}/**/rake*.gem"].first - end - - if rake_path.nil? - abort "Your test gems are missing! Run `rm -rf #{tmp}` and try again." - end - end - def update_repo(path, build_compact_index: true) - if path == gem_repo1 && caller.first.split(" ").last == "`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 return unless block_given? @_build_path = "#{path}/gems" @_build_repo = File.basename(path) yield - with_gem_path_as Path.base_system_gem_path do - Dir[Spec::Path.base_system_gem_path.join("gems/rubygems-generate_index*/lib")].first || - raise("Could not find rubygems-generate_index lib directory in #{Spec::Path.base_system_gem_path}") - - command = "generate_index" - command += " --no-compact" if !build_compact_index && gem_command(command + " --help").include?("--[no-]compact") - gem_command command, dir: path - end + options = { build_compact: build_compact_index } + Gem::Indexer.new(path, options).generate_index ensure @_build_path = nil @_build_repo = nil @@ -407,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 @@ -429,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) @@ -445,7 +470,7 @@ module Spec FileUtils.mv bundler_path, options[:path] end ensure - build_path.rmtree + FileUtils.rm_rf build_path end end @@ -462,6 +487,7 @@ module Spec s.email = "foo@bar.baz" s.homepage = "http://example.com" s.license = "MIT" + s.required_ruby_version = ">= 3.0" end @files = {} end @@ -478,18 +504,13 @@ module Spec @spec.executables = Array(val) @spec.executables.each do |file| executable = "#{@spec.bindir}/#{file}" - shebang = if Bundler.current_ruby.jruby? - "#!/usr/bin/env jruby\n" - else - "#!/usr/bin/env ruby\n" - end + shebang = "#!/usr/bin/env ruby\n" @spec.files << executable write executable, "#{shebang}require_relative '../lib/#{@name}' ; puts #{Builders.constantize(@name)}" end end def add_c_extension - require_paths << "ext" extensions << "ext/extconf.rb" write "ext/extconf.rb", <<-RUBY require "mkmf" @@ -507,7 +528,7 @@ module Spec write "ext/#{name}.c", <<-C #include "ruby.h" - void Init_#{name}_c() { + void Init_#{name}_c(void) { rb_define_module("#{Builders.constantize(name)}_IN_C"); } C @@ -518,7 +539,6 @@ module Spec if options[:rubygems_version] @spec.rubygems_version = options[:rubygems_version] - def @spec.mark_version; end def @spec.validate(*); end end @@ -537,18 +557,16 @@ 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 @files.each do |file, source| - file = Pathname.new(path).join(file) - FileUtils.mkdir_p(file.dirname) - File.open(file, "w") {|f| f.puts source } - File.chmod("+x", file) if @spec.executables.map {|exe| "#{@spec.bindir}/#{exe}" }.include?(file) + full_path = Pathname.new(path).join(file) + FileUtils.mkdir_p(full_path.dirname) + File.open(full_path, "w") {|f| f.puts source } + FileUtils.chmod("+x", full_path) if @spec.executables.map {|exe| "#{@spec.bindir}/#{exe}" }.include?(file) end path end @@ -641,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) @@ -677,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 f758559b3b..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_existing(&block) + def checksums_section_when_enabled(target_lockfile = nil, &block) begin - enabled = lockfile.match?(/^CHECKSUMS$/) + enabled = (target_lockfile || lockfile).match?(/^CHECKSUMS$/) rescue Errno::ENOENT - enabled = false + 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 @@ -110,5 +119,17 @@ module Spec _checksums, tail = remaining.split("\n\n", 2) head.concat(tail) end + + def checksum_from_package(gem_file, name, version) + name_tuple = Gem::NameTuple.new(name, version) + + checksum = nil + + File.open(gem_file, "rb") do |f| + checksum = Bundler::Checksum.from_gem(f, gemfile) + end + + "#{name_tuple.lock_name} #{checksum.to_lock}" + end end end diff --git a/spec/bundler/support/command_execution.rb b/spec/bundler/support/command_execution.rb index 5639fda3b6..e2915b996d 100644 --- a/spec/bundler/support/command_execution.rb +++ b/spec/bundler/support/command_execution.rb @@ -1,7 +1,36 @@ # frozen_string_literal: true module Spec - CommandExecution = Struct.new(:command, :working_directory, :exitstatus, :original_stdout, :original_stderr) do + class CommandExecution + def initialize(command, timeout:) + @command = command + @timeout = timeout + @original_stdout = String.new + @original_stderr = String.new + end + + attr_accessor :exitstatus, :command, :original_stdout, :original_stderr + attr_reader :timeout + attr_writer :failure_reason + + def raise_error! + return unless failure? + + error_header = if failure_reason == :timeout + "Invoking `#{command}` was aborted after #{timeout} seconds with output:" + else + "Invoking `#{command}` failed with output:" + end + + raise <<~ERROR + #{error_header} + + ---------------------------------------------------------------------- + #{stdboth} + ---------------------------------------------------------------------- + ERROR + end + def to_s "$ #{command}" end @@ -12,16 +41,11 @@ module Spec end def stdout - original_stdout + normalize(original_stdout) end - # Can be removed once/if https://github.com/oneclick/rubyinstaller2/pull/369 is resolved def stderr - return original_stderr unless Gem.win_platform? - - original_stderr.split("\n").reject do |l| - l.include?("operating_system_defaults") - end.join("\n") + normalize(original_stderr) end def to_s_verbose @@ -42,5 +66,13 @@ module Spec return true unless exitstatus exitstatus > 0 end + + private + + attr_reader :failure_reason + + def normalize(string) + string.dup.force_encoding(Encoding::UTF_8).scrub.strip.gsub("\r\n", "\n") + end end end diff --git a/spec/bundler/support/env.rb b/spec/bundler/support/env.rb new file mode 100644 index 0000000000..0899bd82a3 --- /dev/null +++ b/spec/bundler/support/env.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Spec + module Env + def ruby_core? + File.exist?(File.expand_path("../../../lib/bundler/bundler.gemspec", __dir__)) + end + + def rubylib + ENV["RUBYLIB"].to_s.split(File::PATH_SEPARATOR) + end + end +end diff --git a/spec/bundler/support/filters.rb b/spec/bundler/support/filters.rb index 8e164af756..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 @@ -14,18 +14,17 @@ class RequirementChecker < Proc attr_accessor :provided def inspect - "\"!= #{provided}\"" + "\"#{provided}\"" end end +git_version = Gem::Version.new(`git --version`[/(\d+\.\d+\.\d+)/, 1]) + RSpec.configure do |config| config.filter_run_excluding realworld: true - git_version = Bundler::Source::Git::GitProxy.new(nil, nil).version - + config.filter_run_excluding rubygems: RequirementChecker.against(Gem.rubygems_version) config.filter_run_excluding git: RequirementChecker.against(git_version) - config.filter_run_excluding bundler: RequirementChecker.against(Bundler::VERSION.split(".")[0]) - config.filter_run_excluding rubygems: RequirementChecker.against(Gem::VERSION) config.filter_run_excluding ruby_repo: !ENV["GEM_COMMAND"].nil? config.filter_run_excluding no_color_tty: Gem.win_platform? || !ENV["GITHUB_ACTION"].nil? config.filter_run_excluding permissions: Gem.win_platform? @@ -33,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 c7fe3637cc..46718f5fa4 100644 --- a/spec/bundler/support/hax.rb +++ b/spec/bundler/support/hax.rb @@ -19,35 +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_IGNORE_DEFAULT_GEM"] - module RemoveDefaultBundlerStub - def default_stubs(pattern = "*") - super.delete_if {|stub| stub.name == "bundler" } + 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 - class Specification - class << self - prepend RemoveDefaultBundlerStub + 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 1ad9cc78ca..b0d4b5008b 100644 --- a/spec/bundler/support/helpers.rb +++ b/spec/bundler/support/helpers.rb @@ -1,46 +1,36 @@ # frozen_string_literal: true -require_relative "command_execution" require_relative "the_bundle" require_relative "path" +require_relative "options" +require_relative "subprocess" module Spec module Helpers include Spec::Path + include Spec::Options + include Spec::Subprocess + + def self.extended(mod) + mod.extend Spec::Path + mod.extend Spec::Options + mod.extend Spec::Subprocess + end 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) - end - - def command_executions - @command_executions ||= [] - end - - def last_command - command_executions.last || raise("There is no last command") - end - - def out - last_command.stdout - end - - def err - last_command.stderr + def the_bundle + TheBundle.new end MAJOR_DEPRECATION = /^\[DEPRECATED\]\s*/ @@ -50,11 +40,7 @@ module Spec end def deprecations - err.split("\n").select {|l| l =~ MAJOR_DEPRECATION }.join("\n").split(MAJOR_DEPRECATION) - end - - def exitstatus - last_command.exitstatus + err.split("\n").filter_map {|l| l.sub(MAJOR_DEPRECATION, "") if l.match?(MAJOR_DEPRECATION) } end def run(cmd, *args) @@ -69,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 : {} @@ -77,6 +63,10 @@ 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") @@ -84,29 +74,24 @@ module Spec env = options.delete(:env) || {} requires = options.delete(:requires) || [] - realworld = RSpec.current_example.metadata[:realworld] - artifice = options.delete(:artifice) do - if realworld - "vcr" - else - "fail" - end - end - if artifice - requires << "#{Path.spec_dir}/support/artifice/#{artifice}.rb" - end + dir = options.delete(:dir) || bundled_app + custom_load_path = options.delete(:load_path) load_path = [] - load_path << spec_dir + load_path << custom_load_path if custom_load_path + + 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 = build_env(build_env_options) - dir = options.delete(:dir) || bundled_app raise_on_error = options.delete(:raise_on_error) args = options.map do |k, v| case v - when nil - next when true " --#{k}" when false @@ -116,20 +101,31 @@ module Spec end end.join - ruby_cmd = build_ruby_cmd({ load_path: load_path, requires: requires, env: env }) - cmd = "#{ruby_cmd} #{bundle_bin} #{cmd}#{args}" + cmd = "#{Gem.ruby} #{bundle_bin} #{cmd}#{args}" sys_exec(cmd, { env: env, dir: dir, raise_on_error: raise_on_error }, &block) end + def main_source(dir) + gemfile = File.expand_path("Gemfile", dir) + return unless File.exist?(gemfile) + + match = File.readlines(gemfile).first.match(/source ["'](?<source>[^"']+)["']/) + return unless match + + match[:source] + end + def bundler(cmd, options = {}) - options[:bundle_bin] = system_gem_path.join("bin/bundler") + options[:bundle_bin] = system_gem_path("bin/bundler") bundle(cmd, options) end def ruby(ruby, options = {}) - ruby_cmd = build_ruby_cmd + env = build_env({ artifice: nil }.merge(options)) escaped_ruby = ruby.shellescape - sys_exec(%(#{ruby_cmd} -w -e #{escaped_ruby}), options) + options[:env] = env if env + options[:dir] ||= bundled_app + sys_exec(%(#{Gem.ruby} -w -e #{escaped_ruby}), options) end def load_error_ruby(ruby, name, opts = {}) @@ -137,26 +133,49 @@ 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 - hax_path = "#{Path.spec_dir}/support/hax.rb" + artifice = options.delete(:artifice) do + if current_example && current_example.metadata[:realworld] + "vcr" + elsif compact_index_main_source + env["BUNDLER_SPEC_GEM_REPO"] ||= + case main_source + when "https://gem.repo1" then gem_repo1.to_s + when "https://gem.repo2" then gem_repo2.to_s + when "https://gem.repo3" then gem_repo3.to_s + when "https://gem.repo4" then gem_repo4.to_s + when "https://gems.security" then security_repo.to_s + end + + "compact_index" + else + "fail" + end + end + if artifice + requires << "#{Path.spec_dir}/support/artifice/#{artifice}.rb" + end - # For specs that need to ignore the default Bundler gem, load hax before - # anything else since other stuff may actually load bundler and not skip - # the default version - options[:env]&.include?("BUNDLER_IGNORE_DEFAULT_GEM") ? requires.prepend(hax_path) : requires.append(hax_path) - require_option = requires.map {|r| "-r#{r}" } + requires.each {|r| env["RUBYOPT"] = opt_add("-r#{r}", env["RUBYOPT"]) } - [Gem.ruby, *lib_option, *require_option].compact.join(" ") + env end def gembin(cmd, options = {}) @@ -164,85 +183,55 @@ module Spec sys_exec(cmd.to_s, options) end - def gem_command(command, options = {}) + def sys_exec(cmd, options = {}, &block) env = options[:env] || {} - env["RUBYOPT"] = opt_add(opt_add("-r#{spec_dir}/support/hax.rb", env["RUBYOPT"]), ENV["RUBYOPT"]) + env["RUBYOPT"] = opt_add(opt_add("-r#{spec_dir}/support/switch_rubygems.rb", env["RUBYOPT"]), ENV["RUBYOPT"]) options[:env] = env - sys_exec("#{Path.gem_bin} #{command}", options) - end - def rake - "#{Gem.ruby} -S #{ENV["GEM_PATH"]}/bin/rake" + sh(cmd, options, &block) end - def git(cmd, path, options = {}) - sys_exec("git #{cmd}", options.merge(dir: path)) - end - - def sys_exec(cmd, options = {}) - env = options[:env] || {} - env["RUBYOPT"] = opt_add(opt_add("-r#{spec_dir}/support/switch_rubygems.rb", env["RUBYOPT"]), ENV["RUBYOPT"]) - dir = options[:dir] || bundled_app - command_execution = CommandExecution.new(cmd.to_s, dir) - - require "open3" - require "shellwords" - Open3.popen3(env, *cmd.shellsplit, chdir: dir) do |stdin, stdout, stderr, wait_thr| - yield stdin, stdout, wait_thr if block_given? - stdin.close - - stdout_read_thread = Thread.new { stdout.read } - stderr_read_thread = Thread.new { stderr.read } - command_execution.original_stdout = stdout_read_thread.value.strip - command_execution.original_stderr = stderr_read_thread.value.strip - - status = wait_thr.value - command_execution.exitstatus = if status.exited? - status.exitstatus - elsif status.signaled? - exit_status_for_signal(status.termsig) - end + 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 - unless options[:raise_on_error] == false || command_execution.success? - raise <<~ERROR + current = File.exist?(path) ? Psych.load_file(path) : {} + return current unless config - Invoking `#{cmd}` failed with output: - ---------------------------------------------------------------------- - #{command_execution.stdboth} - ---------------------------------------------------------------------- - ERROR - end - - command_executions << command_execution - - command_execution.stdout - end + current = {} if current.empty? - def all_commands_output - return "" if command_executions.empty? + FileUtils.mkdir_p(File.dirname(path)) - "\n\nCommands:\n#{command_executions.map(&:to_s_verbose).join("\n\n")}" - end + new_config = current.merge(config).compact - def config(config = nil, path = bundled_app(".bundle/config")) - return Psych.load_file(path) unless config - FileUtils.mkdir_p(File.dirname(path)) - File.open(path, "w") do |f| - f.puts config.to_yaml + File.open(path, "w+") do |f| + f.puts new_config.to_yaml end - config + + 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 = "") + contents = strip_whitespace(contents) path = Pathname.new(path).expand_path(bundled_app) unless path.is_a?(Pathname) path.dirname.mkpath - File.open(path.to_s, "w") do |f| - f.puts strip_whitespace(contents) + path.write(contents) + + # if the file is a script, create respective bat file on Windows + if contents.start_with?("#!") + path.chmod(0o755) + if Gem.win_platform? + path.sub_ext(".bat").write <<~SCRIPT + @ECHO OFF + @"ruby.exe" "%~dpn0" %* + SCRIPT + end end end @@ -252,6 +241,7 @@ module Spec if contents.nil? read_gemfile else + match_source(contents) create_file(args.pop || "Gemfile", contents) end end @@ -296,38 +286,123 @@ 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 : {} install_dir = options.fetch(:path, system_gem_path) default = options.fetch(:default, false) - with_gem_path_as(install_dir) do - gem_repo = options.fetch(:gem_repo, gem_repo1) - gems.each do |g| - gem_name = g.to_s - if gem_name.start_with?("bundler") - version = gem_name.match(/\Abundler-(?<version>.*)\z/)[:version] if gem_name != "bundler" - with_built_bundler(version) {|gem_path| install_gem(gem_path, install_dir, default) } - elsif %r{\A(?:[a-zA-Z]:)?/.*\.gem\z}.match?(gem_name) - install_gem(gem_name, install_dir, default) - else - install_gem("#{gem_repo}/gems/#{gem_name}.gem", install_dir, default) - end + gems.each do |g| + gem_name = g.to_s + 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 + gem_repo = options.fetch(:gem_repo, gem_repo1) + install_gem("#{gem_repo}/gems/#{gem_name}.gem", install_dir, default) end end end + def self.install_dev_bundler + extend self + + 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] - args = "--no-document --ignore-dependencies --verbose --local --install-dir #{install_dir}" - args += " --default" if default + # 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") - gem_command "install #{args} '#{path}'" + # Revert Gem::Installer#write_cache_file + File.delete File.join(install_dir, "cache", gem + ".gem") + end end - def with_built_bundler(version = nil, &block) - Builders::BundlerBuilder.new(self, "bundler", version)._build(&block) + def uninstall_gem(name, options = {}) + require "rubygems/uninstaller" + + 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 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(opts, &block) end def with_gem_path_as(path) @@ -355,20 +430,40 @@ module Spec ENV.replace(backup) end - def with_path_added(path) - with_path_as([path.to_s, ENV["PATH"]].join(File::PATH_SEPARATOR)) do - yield + # 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 - end - def opt_add(option, options) - [option.strip, options].compact.reject(&:empty?).join(" ") - end + old_arch = RbConfig::CONFIG["arch"] + old_host_os = RbConfig::CONFIG["host_os"] - def opt_remove(option, options) - return unless options + 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, []) - options.split(" ").reject {|opt| opt.strip == option.strip }.join(" ") + 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 + end end def break_git! @@ -381,49 +476,43 @@ module Spec end def with_fake_man - skip "fake_man is not a Windows friendly binstub" if Gem.win_platform? - FileUtils.mkdir_p(tmp("fake_man")) - File.open(tmp("fake_man/man"), "w", 0o755) do |f| - f.puts "#!/usr/bin/env ruby\nputs ARGV.inspect\n" - end + create_file(tmp("fake_man/man"), <<~SCRIPT) + #!/usr/bin/env ruby + puts ARGV.inspect + SCRIPT with_path_added(tmp("fake_man")) { yield } end def pristine_system_gems(*gems) - FileUtils.rm_rf(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) + FileUtils.rm_r(system_gem_path) - with_gem_path_as(path) do - gems.each do |gem| - gem_command "install --no-document #{gem}" - end + 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) @@ -437,21 +526,11 @@ module Spec def simulate_platform(platform) old = ENV["BUNDLER_SPEC_PLATFORM"] ENV["BUNDLER_SPEC_PLATFORM"] = platform.to_s - yield if block_given? + yield ensure 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,18 +539,12 @@ 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 def revision_for(path) - sys_exec("git rev-parse HEAD", dir: path).strip + git("rev-parse HEAD", path).strip end def with_read_only(pattern) @@ -523,41 +596,34 @@ 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 + def empty_repo4 + FileUtils.rm_r gem_repo4 + + build_repo4 {} + end + private + def match_source(contents) + match = /source ["']?(?<source>http[^"']+)["']?/.match(contents) + return unless match + + @gemfile_source = match[:source] + end + def git_root_dir? root.to_s == `git rev-parse --show-toplevel`.chomp end diff --git a/spec/bundler/support/indexes.rb b/spec/bundler/support/indexes.rb index 086a311551..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 @@ -77,8 +76,8 @@ module Spec def an_awesome_index build_index do - gem "rack", %w[0.8 0.9 0.9.1 0.9.2 1.0 1.1] - gem "rack-mount", %w[0.4 0.5 0.5.1 0.5.2 0.6] + gem "myrack", %w[0.8 0.9 0.9.1 0.9.2 1.0 1.1] + gem "myrack-mount", %w[0.4 0.5 0.5.1 0.5.2 0.6] # --- Pre-release support gem "RubyGems\0", ["1.3.2"] @@ -89,10 +88,10 @@ module Spec gem "actionpack", version do dep "activesupport", version if version >= v("3.0.0.beta") - dep "rack", "~> 1.1" - dep "rack-mount", ">= 0.5" - elsif version > v("2.3") then dep "rack", "~> 1.0.0" - elsif version > v("2.0.0") then dep "rack", "~> 0.9.0" + dep "myrack", "~> 1.1" + dep "myrack-mount", ">= 0.5" + elsif version > v("2.3") then dep "myrack", "~> 1.0.0" + elsif version > v("2.0.0") then dep "myrack", "~> 0.9.0" end end gem "activerecord", version do @@ -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 @@ -367,7 +366,7 @@ module Spec def a_circular_index build_index do - gem "rack", "1.0.1" + gem "myrack", "1.0.1" gem("foo", "0.2.6") do dep "bar", ">= 0" end diff --git a/spec/bundler/support/matchers.rb b/spec/bundler/support/matchers.rb index 0f027dcf04..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), @@ -116,8 +116,9 @@ module Spec source = opts.delete(:source) groups = Array(opts.delete(:groups)).map(&:inspect).join(", ") opts[:raise_on_error] = false - @errors = names.map do |full_name| + @errors = names.filter_map do |full_name| name, version, platform = full_name.split(/\s+/) + platform ||= "ruby" require_path = name.tr("-", "/") version_const = name == "bundler" ? "Bundler::VERSION" : Spec::Builders.constantize(name) source_const = "#{Spec::Builders.constantize(name)}_SOURCE" @@ -127,6 +128,7 @@ module Spec require '#{require_path}' actual_version, actual_platform = #{version_const}.split(/\s+/, 2) + actual_platform ||= "ruby" unless Gem::Version.new(actual_version) == Gem::Version.new('#{version}') puts actual_version exit 64 @@ -150,14 +152,14 @@ module Spec end if exitstatus == 65 actual_platform = out.split("\n").last - next "#{name} was expected to be of platform #{platform || "ruby"} but was #{actual_platform || "ruby"}" + next "#{name} was expected to be of platform #{platform} but was #{actual_platform}" end if exitstatus == 66 actual_source = out.split("\n").last next "Expected #{name} (#{version}) to be installed from `#{source}`, was actually from `#{actual_source}`" end next "Command to check for inclusion of gem #{full_name} failed" - end.compact + end @errors.empty? end @@ -166,7 +168,7 @@ module Spec opts = names.last.is_a?(Hash) ? names.pop : {} groups = Array(opts.delete(:groups)).map(&:inspect).join(", ") opts[:raise_on_error] = false - @errors = names.map do |name| + @errors = names.filter_map do |name| name, version = name.split(/\s+/, 2) ruby <<-R, opts begin @@ -192,7 +194,7 @@ module Spec next "command to check version of #{name} installed failed" unless exitstatus == 64 next "expected #{name} to not be installed, but it was" if version.nil? next "expected #{name} (#{version}) not to be installed, but it was" - end.compact + end @errors.empty? end diff --git a/spec/bundler/support/options.rb b/spec/bundler/support/options.rb new file mode 100644 index 0000000000..551fa1acd8 --- /dev/null +++ b/spec/bundler/support/options.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Spec + module Options + def opt_add(option, options) + [option.strip, options].compact.reject(&:empty?).join(" ") + end + + def opt_remove(option, options) + return unless options + + options.split(" ").reject {|opt| opt.strip == option.strip }.join(" ") + end + end +end diff --git a/spec/bundler/support/path.rb b/spec/bundler/support/path.rb index 7352d5a353..2e6486412f 100644 --- a/spec/bundler/support/path.rb +++ b/spec/bundler/support/path.rb @@ -1,12 +1,16 @@ # frozen_string_literal: true -require "pathname" +require "pathname" unless defined?(Pathname) require "rbconfig" +require_relative "env" + module Spec module Path + 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 @@ -21,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 @@ -45,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 @@ -58,23 +66,27 @@ module Spec end def gem_bin - @gem_bin ||= ruby_core? ? ENV["GEM_COMMAND"] : "gem" + @gem_bin ||= ENV["GEM_COMMAND"] || "gem" end 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 @@ -98,7 +110,28 @@ module Spec end def tmp(*path) - source_root.join("tmp", scope, *path) + 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 + 2 end def scope @@ -109,33 +142,29 @@ module Spec end def home(*path) - tmp.join("home", *path) + tmp("home", *path) 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) - root = tmp.join("bundled_app") + root = tmp("bundled_app") FileUtils.mkdir_p(root) root.join(*path) end def bundled_app2(*path) - root = tmp.join("bundled_app2") + root = tmp("bundled_app2") FileUtils.mkdir_p(root) root.join(*path) end @@ -156,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.join("gems/base") + def base_system_gem_path + tmp_root.join("gems/base") end - def rubocop_gems - tmp.join("gems/rubocop") + def rubocop_gem_path + tmp_root.join("gems/rubocop") end - def standard_gems - tmp.join("gems/standard") + def standard_gem_path + tmp_root.join("gems/standard") end def file_uri_for(path) @@ -180,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) @@ -219,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 @@ -246,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 @@ -257,31 +290,62 @@ module Spec File.open(gemspec_file, "w") {|f| f << contents } end - def ruby_core? - # avoid to warnings - @ruby_core ||= nil - - if @ruby_core.nil? - @ruby_core = true & ENV["GEM_COMMAND"] - else - @ruby_core - 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 + 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 + base64 + logger + compact_index + ] + 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? - sys_exec("git ls-files -z -- #{glob}", dir: source_root).split("\x0") + git("ls-files -z -- #{glob}", source_root).split("\x0") 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 @@ -289,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 889ebc90c3..812dc4deaa 100644 --- a/spec/bundler/support/rubygems_ext.rb +++ b/spec/bundler/support/rubygems_ext.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +abort "RubyGems only supports Ruby 3.2 or higher" if RUBY_VERSION < "3.2.0" + require_relative "path" $LOAD_PATH.unshift(Spec::Path.source_lib_dir.to_s) @@ -8,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" @@ -30,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" @@ -42,42 +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.source_root.join("tmp", "1") - destination = Path.source_root.join("tmp", 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.join("bin"), ENV["PATH"]].join(File::PATH_SEPARATOR) - ENV["PATH"] = [Path.bindir, ENV["PATH"]].join(File::PATH_SEPARATOR) if Path.ruby_core? + ENV["PATH"] = [Path.system_gem_path("bin"), ENV["PATH"]].join(File::PATH_SEPARATOR) + ENV["PATH"] = [Path.exedir, ENV["PATH"]].join(File::PATH_SEPARATOR) if Path.ruby_core? end def install_test_deps - 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) + 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) + + require_relative "helpers" + Helpers.install_dev_bundler + + 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:) @@ -100,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) @@ -128,33 +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 - - puts `#{Gem.ruby} #{File.expand_path("support/bundle.rb", Path.spec_dir)} install --verbose` - raise unless $?.success? - ensure - if path - ENV["BUNDLE_PATH"] = old_path - 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/rubygems_version_manager.rb b/spec/bundler/support/rubygems_version_manager.rb index 88da14b67e..c174c461f0 100644 --- a/spec/bundler/support/rubygems_version_manager.rb +++ b/spec/bundler/support/rubygems_version_manager.rb @@ -1,12 +1,13 @@ # frozen_string_literal: true -require "pathname" -require_relative "helpers" -require_relative "path" +require_relative "options" +require_relative "env" +require_relative "subprocess" class RubygemsVersionManager - include Spec::Helpers - include Spec::Path + include Spec::Options + include Spec::Env + include Spec::Subprocess def initialize(source) @source = source @@ -57,7 +58,7 @@ class RubygemsVersionManager cmd = [RbConfig.ruby, $0, *ARGV].compact - ENV["RUBYOPT"] = opt_add("-I#{local_copy_path.join("lib")}", opt_remove("--disable-gems", ENV["RUBYOPT"])) + ENV["RUBYOPT"] = opt_add("-I#{File.join(local_copy_path, "lib")}", opt_remove("--disable-gems", ENV["RUBYOPT"])) exec(ENV, *cmd) end @@ -65,14 +66,14 @@ class RubygemsVersionManager def switch_local_copy_if_needed return unless local_copy_switch_needed? - sys_exec("git checkout #{target_tag}", dir: local_copy_path) + git("checkout #{target_tag}", local_copy_path) - ENV["RGV"] = local_copy_path.to_s + ENV["RGV"] = local_copy_path end def rubygems_unrequire_needed? require "rubygems" - !$LOADED_FEATURES.include?(local_copy_path.join("lib/rubygems.rb").to_s) + !$LOADED_FEATURES.include?(File.join(local_copy_path, "lib/rubygems.rb")) end def local_copy_switch_needed? @@ -84,7 +85,7 @@ class RubygemsVersionManager end def local_copy_tag - sys_exec("git rev-parse --abbrev-ref HEAD", dir: local_copy_path) + git("rev-parse --abbrev-ref HEAD", local_copy_path) end def local_copy_path @@ -94,21 +95,25 @@ class RubygemsVersionManager def resolve_local_copy_path return expanded_source if source_is_path? - rubygems_path = source_root.join("tmp/rubygems") + rubygems_path = File.join(source_root, "tmp/rubygems") - unless rubygems_path.directory? - sys_exec("git clone .. #{rubygems_path}", dir: source_root) + unless File.directory?(rubygems_path) + git("clone .. #{rubygems_path}", source_root) end rubygems_path end def source_is_path? - expanded_source.directory? + File.directory?(expanded_source) end def expanded_source - @expanded_source ||= Pathname.new(@source).expand_path(source_root) + @expanded_source ||= File.expand_path(@source, source_root) + end + + def source_root + @source_root ||= File.expand_path(ruby_core? ? "../../.." : "../..", __dir__) end def resolve_target_tag 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 8665beb2c9..0000000000 --- a/spec/bundler/support/silent_logger.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -require "logger" -module Spec - class SilentLogger - (::Logger.instance_methods - Object.instance_methods).each do |logger_instance_method| - define_method(logger_instance_method) {|*args, &blk| } - end - end -end diff --git a/spec/bundler/support/subprocess.rb b/spec/bundler/support/subprocess.rb new file mode 100644 index 0000000000..91db80da48 --- /dev/null +++ b/spec/bundler/support/subprocess.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +require_relative "command_execution" + +module Spec + module Subprocess + class TimeoutExceeded < StandardError; end + + def command_executions + @command_executions ||= [] + end + + def last_command + command_executions.last || raise("There is no last command") + end + + def out + last_command.stdout + end + + def err + last_command.stderr + end + + def stdboth + last_command.stdboth + end + + def exitstatus + last_command.exitstatus + end + + def git(cmd, path = Dir.pwd, options = {}) + sh("git #{cmd}", options.merge(dir: path)) + end + + def sh(cmd, options = {}) + dir = options[:dir] + env = options[:env] || {} + + 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, **open3_opts) do |stdin, stdout, stderr, wait_thr| + yield stdin, stdout, wait_thr if block_given? + stdin.close + + stdout_handler = ->(data) { command_execution.original_stdout << data } + stderr_handler = ->(data) { command_execution.original_stderr << data } + + stdout_thread = read_stream(stdout, stdout_handler, timeout: command_execution.timeout) + stderr_thread = read_stream(stderr, stderr_handler, timeout: command_execution.timeout) + + stdout_thread.join + stderr_thread.join + + status = wait_thr.value + command_execution.exitstatus = if status.exited? + status.exitstatus + elsif status.signaled? + exit_status_for_signal(status.termsig) + end + rescue TimeoutExceeded + command_execution.failure_reason = :timeout + command_execution.exitstatus = exit_status_for_signal(Signal.list["INT"]) + end + + unless options[:raise_on_error] == false || command_execution.success? + command_execution.raise_error! + end + + command_executions << command_execution + + command_execution.stdout + end + + # Mostly copied from https://github.com/piotrmurach/tty-command/blob/49c37a895ccea107e8b78d20e4cb29de6a1a53c8/lib/tty/command/process_runner.rb#L165-L193 + def read_stream(stream, handler, timeout:) + Thread.new do + Thread.current.report_on_exception = false + cmd_start = Time.now + readers = [stream] + + while readers.any? + ready = IO.select(readers, nil, readers, timeout) + raise TimeoutExceeded if ready.nil? + + ready[0].each do |reader| + chunk = reader.readpartial(16 * 1024) + handler.call(chunk) + + # control total time spent reading + runtime = Time.now - cmd_start + time_left = timeout - runtime + raise TimeoutExceeded if time_left < 0.0 + rescue Errno::EAGAIN, Errno::EINTR + rescue EOFError, Errno::EPIPE, Errno::EIO + readers.delete(reader) + reader.close + end + end + end + end + + def all_commands_output + return "" if command_executions.empty? + + "\n\nCommands:\n#{command_executions.map(&:to_s_verbose).join("\n\n")}" + end + end +end 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/support/vendored_net_http.rb b/spec/bundler/support/vendored_net_http.rb new file mode 100644 index 0000000000..8ff2ccd1fe --- /dev/null +++ b/spec/bundler/support/vendored_net_http.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# This defined? guard can be removed once RubyGems 3.4 support is dropped. +# +# Bundler specs load this code from `spec/support/vendored_net_http.rb` to avoid +# activating the Bundler gem too early. Without this guard, we get redefinition +# warnings once Bundler is actually activated and +# `lib/bundler/vendored_net_http.rb` is required. This is not an issue in +# RubyGems versions including `rubygems/vendored_net_http` since `require` takes +# care of avoiding the double load. +# +unless defined?(Gem::Net) + begin + require "rubygems/vendored_net_http" + rescue LoadError + begin + require "rubygems/net/http" + rescue LoadError + require "net/http" + Gem::Net = Net + 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 d32a7945b0..f8849640b6 100644 --- a/spec/bundler/update/gemfile_spec.rb +++ b/spec/bundler/update/gemfile_spec.rb @@ -4,8 +4,8 @@ RSpec.describe "bundle update" do context "with --gemfile" do it "finds the gemfile" do gemfile bundled_app("NotGemfile"), <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' G bundle :install, gemfile: bundled_app("NotGemfile") @@ -14,18 +14,18 @@ RSpec.describe "bundle update" do # Specify BUNDLE_GEMFILE for `the_bundle` # to retrieve the proper Gemfile ENV["BUNDLE_GEMFILE"] = "NotGemfile" - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end end context "with gemfile set via config" do before do gemfile bundled_app("NotGemfile"), <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' G - bundle "config set --local gemfile #{bundled_app("NotGemfile")}" + bundle_config "gemfile #{bundled_app("NotGemfile")}" bundle :install end @@ -33,7 +33,7 @@ RSpec.describe "bundle update" do bundle "update", all: true bundle "list" - expect(out).to include("rack (1.0.0)") + expect(out).to include("myrack (1.0.0)") end it "uses the gemfile while in a subdirectory" do @@ -41,7 +41,7 @@ RSpec.describe "bundle update" do bundle "update", all: true, dir: bundled_app("subdir") bundle "list", dir: bundled_app("subdir") - expect(out).to include("rack (1.0.0)") + expect(out).to include("myrack (1.0.0)") end end end diff --git a/spec/bundler/update/gems/fund_spec.rb b/spec/bundler/update/gems/fund_spec.rb index 4a87c16bf7..a5624d3e0a 100644 --- a/spec/bundler/update/gems/fund_spec.rb +++ b/spec/bundler/update/gems/fund_spec.rb @@ -24,7 +24,7 @@ RSpec.describe "bundle update" do end gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem 'has_funding_and_other_metadata' gem 'has_funding', '< 2.0' G @@ -35,7 +35,7 @@ RSpec.describe "bundle update" do context "when listed gems are updated" do before do gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem 'has_funding_and_other_metadata' gem 'has_funding' G diff --git a/spec/bundler/update/gems/post_install_spec.rb b/spec/bundler/update/gems/post_install_spec.rb index e3593387d4..9c71f6e0e3 100644 --- a/spec/bundler/update/gems/post_install_spec.rb +++ b/spec/bundler/update/gems/post_install_spec.rb @@ -5,8 +5,8 @@ RSpec.describe "bundle update" do before do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack', "< 1.0" + source "https://gem.repo1" + gem 'myrack', "< 1.0" gem 'thin' G @@ -17,10 +17,10 @@ RSpec.describe "bundle update" do shared_examples "a config observer" do context "when ignore post-install messages for gem is set" do - let(:config) { "ignore_messages.rack true" } + let(:config) { "ignore_messages.myrack true" } it "doesn't display gem's post-install message" do - expect(out).not_to include("Rack's post install message") + expect(out).not_to include("Myrack's post install message") end end @@ -35,8 +35,8 @@ RSpec.describe "bundle update" do shared_examples "a post-install message outputter" do it "should display post-install messages for updated gems" do - expect(out).to include("Post-install message from rack:") - expect(out).to include("Rack's post install message") + expect(out).to include("Post-install message from myrack:") + expect(out).to include("Myrack's post install message") end it "should not display the post-install message for non-updated gems" do @@ -47,8 +47,8 @@ RSpec.describe "bundle update" do context "when listed gem is updated" do before do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' gem 'thin' G @@ -62,8 +62,8 @@ RSpec.describe "bundle update" do context "when dependency triggers update" do before do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack-obama' + source "https://gem.repo1" + gem 'myrack-obama' gem 'thin' G diff --git a/spec/bundler/update/git_spec.rb b/spec/bundler/update/git_spec.rb index 3b7bbfd979..526e988ab7 100644 --- a/spec/bundler/update/git_spec.rb +++ b/spec/bundler/update/git_spec.rb @@ -7,7 +7,7 @@ RSpec.describe "bundle update" do update_git "foo", branch: "omg" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("foo-1.0")}", :branch => "omg" do gem 'foo' end @@ -29,8 +29,8 @@ RSpec.describe "bundle update" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rails", :git => "#{file_uri_for(lib_path("rails"))}" + source "https://gem.repo1" + gem "rails", :git => "#{lib_path("rails")}" G bundle "update rails" @@ -42,7 +42,7 @@ RSpec.describe "bundle update" do update_git "foo", branch: "omg", path: lib_path("foo") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("foo")}", :branch => "omg" do gem 'foo' end @@ -64,8 +64,8 @@ RSpec.describe "bundle update" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "foo", :git => "#{file_uri_for(lib_path("foo"))}" + source "https://gem.repo1" + gem "foo", :git => "#{lib_path("foo")}" gem "bar" G @@ -83,30 +83,30 @@ RSpec.describe "bundle update" do build_git "foo", path: lib_path("foo_two") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "foo", "1.0", :git => "#{file_uri_for(lib_path("foo_one"))}" + source "https://gem.repo1" + 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 "#{file_uri_for(gem_repo1)}" - gem "foo", "1.0", :git => "#{file_uri_for(lib_path("foo_two"))}" + source "https://gem.repo1" + gem "foo", "1.0", :git => "#{lib_path("foo_two")}" G expect(err).to be_empty - expect(out).to include("Fetching #{file_uri_for(lib_path)}/foo_two") + expect(out).to include("Fetching #{lib_path}/foo_two") expect(out).to include("Bundle complete!") end it "fetches tags from the remote" do build_git "foo" @remote = build_git("bar", bare: true) - update_git "foo", remote: file_uri_for(@remote.path) + update_git "foo", remote: @remote.path update_git "foo", push: "main" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'foo', :git => "#{@remote.path}" G @@ -115,7 +115,7 @@ RSpec.describe "bundle update" do update_git "foo", push: "fubar" gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'foo', :git => "#{@remote.path}", :tag => "fubar" G @@ -142,13 +142,13 @@ RSpec.describe "bundle update" do s.add_dependency "submodule" end - sys_exec "git submodule add #{lib_path("submodule-1.0")} submodule-1.0", dir: lib_path("has_submodule-1.0") - sys_exec "git commit -m \"submodulator\"", dir: lib_path("has_submodule-1.0") + git "submodule add #{lib_path("submodule-1.0")} submodule-1.0", lib_path("has_submodule-1.0") + git "commit -m \"submodulator\"", lib_path("has_submodule-1.0") end it "it unlocks the source when submodules are added to a git source" do install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" git "#{lib_path("has_submodule-1.0")}" do gem "has_submodule" end @@ -158,7 +158,7 @@ RSpec.describe "bundle update" do expect(out).to eq("GEM") install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" git "#{lib_path("has_submodule-1.0")}", :submodules => true do gem "has_submodule" end @@ -170,7 +170,7 @@ RSpec.describe "bundle update" do it "unlocks the source when submodules are removed from git source", git: ">= 2.9.0" do install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" git "#{lib_path("has_submodule-1.0")}", :submodules => true do gem "has_submodule" end @@ -180,7 +180,7 @@ RSpec.describe "bundle update" do expect(out).to eq("GIT") install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" git "#{lib_path("has_submodule-1.0")}" do gem "has_submodule" end @@ -195,11 +195,11 @@ RSpec.describe "bundle update" do build_git "foo", "1.0" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}" + source "https://gem.repo1" + 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). @@ -207,19 +207,19 @@ RSpec.describe "bundle update" do end it "should not explode on invalid revision on update of gem by name" do - build_git "rack", "0.8" + build_git "myrack", "0.8" - build_git "rack", "0.8", path: lib_path("local-rack") do |s| - s.write "lib/rack.rb", "puts :LOCAL" + build_git "myrack", "0.8", path: lib_path("local-myrack") do |s| + s.write "lib/myrack.rb", "puts :LOCAL" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack", :git => "#{file_uri_for(lib_path("rack-0.8"))}", :branch => "main" + source "https://gem.repo1" + gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main" G - bundle %(config set local.rack #{lib_path("local-rack")}) - bundle "update rack" + bundle %(config set local.myrack #{lib_path("local-myrack")}) + bundle "update myrack" expect(out).to include("Bundle updated!") end @@ -227,14 +227,14 @@ RSpec.describe "bundle update" do build_git "rails", "2.3.2", path: lib_path("rails") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rails", :git => "#{file_uri_for(lib_path("rails"))}" + source "https://gem.repo1" + gem "rails", :git => "#{lib_path("rails")}" G update_git "rails", "3.0", path: lib_path("rails"), gemspec: true bundle "update", all: true - expect(out).to include("Using rails 3.0 (was 2.3.2) from #{file_uri_for(lib_path("rails"))} (at main@#{revision_for(lib_path("rails"))[0..6]})") + expect(out).to include("Using rails 3.0 (was 2.3.2) from #{lib_path("rails")} (at main@#{revision_for(lib_path("rails"))[0..6]})") end end @@ -246,11 +246,11 @@ RSpec.describe "bundle update" do end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" git "#{lib_path("foo")}" do gem 'foo' end - gem 'rack' + gem 'myrack' G end @@ -279,7 +279,7 @@ RSpec.describe "bundle update" do update_git "foo", path: @git.path bundle "update --source foo" - expect(the_bundle).to include_gems "rack 1.0" + expect(the_bundle).to include_gems "myrack 1.0" end end @@ -289,11 +289,11 @@ RSpec.describe "bundle update" do @git = build_git "foo", path: lib_path("bar") install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" git "#{lib_path("bar")}" do gem 'foo' end - gem 'rack' + gem 'myrack' G end @@ -309,9 +309,9 @@ RSpec.describe "bundle update" do bundle "update --source bar" - checksums = checksums_section_when_existing do |c| + checksums = checksums_section_when_enabled do |c| c.no_checksum "foo", "2.0" - c.checksum gem_repo2, "rack", "1.0.0" + c.checksum gem_repo2, "myrack", "1.0.0" end expect(lockfile).to eq <<~G @@ -322,19 +322,19 @@ RSpec.describe "bundle update" do foo (2.0) GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: - rack (1.0.0) + myrack (1.0.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES foo! - rack + myrack #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end end diff --git a/spec/bundler/update/path_spec.rb b/spec/bundler/update/path_spec.rb index 1f8992b33f..8c76c94e1a 100644 --- a/spec/bundler/update/path_spec.rb +++ b/spec/bundler/update/path_spec.rb @@ -6,7 +6,7 @@ RSpec.describe "path sources" do build_lib "activesupport", "2.3.5", path: lib_path("rails/activesupport") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "activesupport", :path => "#{lib_path("rails/activesupport")}" G diff --git a/spec/bundler/update/redownload_spec.rb b/spec/bundler/update/redownload_spec.rb deleted file mode 100644 index 4a8c711bfa..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 "#{file_uri_for(gem_repo1)}" - gem "rack" - G - end - - describe "with --force" do - it "shows a deprecation when single flag passed", bundler: 2 do - bundle "update rack --force" - expect(err).to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" - end - - it "shows a deprecation when multiple flags passed", bundler: 2 do - bundle "update rack --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 rack --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 rack --no-color --redownload" - expect(err).not_to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" - end - - it "re-installs installed gems" do - rack_lib = default_bundle_path("gems/rack-1.0.0/lib/rack.rb") - rack_lib.open("w") {|f| f.write("blah blah blah") } - bundle :update, redownload: true - - expect(out).to include "Installing rack 1.0.0" - expect(rack_lib.open(&:read)).to eq("RACK = '1.0.0'\n") - expect(the_bundle).to include_gems "rack 1.0.0" - end - end -end |
