diff options
Diffstat (limited to 'spec/bundler')
246 files changed, 25472 insertions, 12757 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 862a2c3e07..bddcbdaef3 100644 --- a/spec/bundler/bundler/bundler_spec.rb +++ b/spec/bundler/bundler/bundler_spec.rb @@ -7,7 +7,7 @@ RSpec.describe Bundler do describe "#load_marshal" do it "is a private method and raises an error" do data = Marshal.dump(Bundler) - expect { Bundler.load_marshal(data) }.to raise_error(NoMethodError, /private method `load_marshal' called/) + expect { Bundler.load_marshal(data) }.to raise_error(NoMethodError, /private method [`']load_marshal' called/) end it "loads any data" do @@ -23,19 +23,47 @@ RSpec.describe Bundler do end it "loads simple structure" do - simple_structure = { "name" => [:abc] } + simple_structure = { "name" => [:development] } data = Marshal.dump(simple_structure) expect(Bundler.safe_load_marshal(data)).to eq(simple_structure) end - it "loads Gem::Version" do - gem_version = Gem::Version.new("3.7.2") - data = Marshal.dump(gem_version) - expect(Bundler.safe_load_marshal(data)).to eq(gem_version) - end - it "loads Gem::Specification" do - gem_spec = Gem::Specification.new("name", "3.7.2") + gem_spec = Gem::Specification.new do |s| + s.name = "bundler" + s.version = Gem::Version.new("2.4.7") + s.installed_by_version = Gem::Version.new("0") + s.authors = ["André Arko", + "Samuel Giddins", + "Colby Swandale", + "Hiroshi Shibata", + "David Rodríguez", + "Grey Baker", + "Stephanie Morillo", + "Chris Morris", + "James Wen", + "Tim Moore", + "André Medeiros", + "Jessica Lynn Suttles", + "Terence Lee", + "Carl Lerche", + "Yehuda Katz"] + s.date = Time.utc(2023, 2, 15) + s.description = "Bundler manages an application's dependencies through its entire life, across many machines, systematically and repeatably" + s.email = ["team@bundler.io"] + s.homepage = "https://bundler.io" + s.metadata = { "bug_tracker_uri" => "https://github.com/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/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"]) + s.rubygems_version = "3.4.7" + s.specification_version = 4 + s.summary = "The best way to manage your application's dependencies" + s.license = false + end data = Marshal.dump(gem_spec) expect(Bundler.safe_load_marshal(data)).to eq(gem_spec) end @@ -48,7 +76,7 @@ RSpec.describe Bundler do context "with incorrect YAML file" do before do File.open(app_gemspec_path, "wb") do |f| - f.write strip_whitespace(<<-GEMSPEC) + f.write <<~GEMSPEC --- {:!00 ao=gu\g1= 7~f GEMSPEC @@ -60,7 +88,7 @@ RSpec.describe Bundler do end end - context "with correct YAML file", :if => defined?(Encoding) do + context "with correct YAML file", if: defined?(Encoding) do it "can load a gemspec with unicode characters with default ruby encoding" do # spec_helper forces the external encoding to UTF-8 but that's not the # default until Ruby 2.0 @@ -71,7 +99,7 @@ RSpec.describe Bundler do $VERBOSE = verbose File.open(app_gemspec_path, "wb") do |file| - file.puts <<-GEMSPEC.gsub(/^\s+/, "") + file.puts <<~GEMSPEC # -*- encoding: utf-8 -*- Gem::Specification.new do |gem| gem.author = "André the Giant" @@ -111,7 +139,7 @@ RSpec.describe Bundler do end GEMSPEC end - expect(Bundler.rubygems).to receive(:validate).with have_attributes(:name => "validated") + expect(Bundler.rubygems).to receive(:validate).with have_attributes(name: "validated") subject end end @@ -119,7 +147,7 @@ RSpec.describe Bundler do context "with gemspec containing local variables" do before do File.open(app_gemspec_path, "wb") do |f| - f.write strip_whitespace(<<-GEMSPEC) + f.write <<~GEMSPEC must_not_leak = true Gem::Specification.new do |gem| gem.name = "leak check" @@ -136,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 + expect(result).to eq(nil) - let(:expected) { "executable" } - - before do - ENV["PATH"] = path.join(File::PATH_SEPARATOR) - - 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 @@ -196,35 +207,17 @@ RSpec.describe Bundler do end end - describe "#rm_rf" do - context "the directory is world writable" do - let(:bundler_ui) { Bundler.ui } - it "should raise a friendly error" do - allow(File).to receive(:exist?).and_return(true) - allow(::Bundler::FileUtils).to receive(:remove_entry_secure).and_raise(ArgumentError) - allow(File).to receive(:world_writable?).and_return(true) - message = <<EOF -It is a security vulnerability to allow your home directory to be world-writable, and bundler cannot continue. -You should probably consider fixing this issue by running `chmod o-w ~` on *nix. -Please refer to https://ruby-doc.org/stdlib-3.1.2/libdoc/fileutils/rdoc/FileUtils.html#method-c-remove_entry_secure for details. -EOF - expect(bundler_ui).to receive(:warn).with(message) - expect { Bundler.send(:rm_rf, bundled_app) }.to raise_error(Bundler::PathError) - end - end - end - 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 @@ -257,6 +250,7 @@ EOF 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/ci_detector_spec.rb b/spec/bundler/bundler/ci_detector_spec.rb new file mode 100644 index 0000000000..299d8005e8 --- /dev/null +++ b/spec/bundler/bundler/ci_detector_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +RSpec.describe Bundler::CIDetector do + # This is properly tested in rubygems, under the name Gem::CIDetector + # But the test that confirms that our version _stays in sync_ with that version + # will live here. + + it "stays in sync with the rubygems implementation" do + rubygems_implementation_path = File.join(git_root, "lib", "rubygems", "ci_detector.rb") + expect(File.exist?(rubygems_implementation_path)).to be_truthy + rubygems_code = File.read(rubygems_implementation_path) + denamespaced_rubygems_code = rubygems_code.sub("Gem", "NAMESPACE") + + bundler_implementation_path = File.join(source_lib_dir, "bundler", "ci_detector.rb") + expect(File.exist?(bundler_implementation_path)).to be_truthy + bundler_code = File.read(bundler_implementation_path) + denamespaced_bundler_code = bundler_code.sub("Bundler", "NAMESPACE") + + expect(denamespaced_bundler_code).to eq(denamespaced_rubygems_code) + end +end diff --git a/spec/bundler/bundler/cli_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 b752cd7e70..56caf9937e 100644 --- a/spec/bundler/bundler/cli_spec.rb +++ b/spec/bundler/bundler/cli_spec.rb @@ -4,12 +4,12 @@ require "bundler/cli" RSpec.describe "bundle executable" do it "returns non-zero exit status when passed unrecognized options" do - bundle "--invalid_argument", :raise_on_error => false + bundle "--invalid_argument", raise_on_error: false expect(exitstatus).to_not be_zero end it "returns non-zero exit status when passed unrecognized task" do - bundle "unrecognized-task", :raise_on_error => false + bundle "unrecognized-task", raise_on_error: false expect(exitstatus).to_not be_zero end @@ -87,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" => "" } + 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 + 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 @@ -133,13 +157,13 @@ RSpec.describe "bundle executable" do let(:run_command) do bundle "install" - bundle "outdated #{flags}", :raise_on_error => false + bundle "outdated #{flags}", raise_on_error: false end 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 @@ -178,15 +202,15 @@ RSpec.describe "bundle executable" do describe "printing the outdated warning" do shared_examples_for "no warning" do it "prints no warning" do - bundle "fail", :env => { "BUNDLER_VERSION" => bundler_version }, :raise_on_error => false - expect(last_command.stdboth).to eq("Could not find command \"fail\".") + bundle "fail", env: { "BUNDLER_VERSION" => bundler_version }, raise_on_error: false + 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 @@ -213,7 +237,7 @@ RSpec.describe "bundle executable" do context "when the latest version is greater than the current version" do let(:latest_version) { "222.0" } it "prints the version warning" do - bundle "fail", :env => { "BUNDLER_VERSION" => bundler_version }, :raise_on_error => false + bundle "fail", env: { "BUNDLER_VERSION" => bundler_version }, raise_on_error: false expect(err).to start_with(<<-EOS.strip) The latest bundler is #{latest_version}, but you are currently running #{bundler_version}. To update to the most recent version, run `bundle update --bundler` @@ -221,24 +245,26 @@ To update to the most recent version, run `bundle update --bundler` end context "and disable_version_check is set" do - before { bundle "config set disable_version_check true", :env => { "BUNDLER_VERSION" => bundler_version } } + before { bundle "config set disable_version_check true", env: { "BUNDLER_VERSION" => bundler_version } } include_examples "no warning" end context "running a parseable command" do it "prints no warning" do - bundle "config get --parseable foo", :env => { "BUNDLER_VERSION" => bundler_version } - expect(last_command.stdboth).to eq "" + bundle "config set foo value", env: { "BUNDLER_VERSION" => bundler_version } + bundle "config get --parseable foo", env: { "BUNDLER_VERSION" => bundler_version } + 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" + bundle "platform --ruby", env: { "BUNDLER_VERSION" => bundler_version }, raise_on_error: false + expect(stdboth).to eq "Could not locate Gemfile" end end context "and is a pre-release" do let(:latest_version) { "222.0.0.pre.4" } it "prints the version warning" do - bundle "fail", :env => { "BUNDLER_VERSION" => bundler_version }, :raise_on_error => false + bundle "fail", env: { "BUNDLER_VERSION" => bundler_version }, raise_on_error: false expect(err).to start_with(<<-EOS.strip) The latest bundler is #{latest_version}, but you are currently running #{bundler_version}. To update to the most recent version, run `bundle update --bundler` @@ -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 fe417e3920..fd63a652a4 100644 --- a/spec/bundler/bundler/compact_index_client/updater_spec.rb +++ b/spec/bundler/bundler/compact_index_client/updater_spec.rb @@ -1,43 +1,225 @@ # frozen_string_literal: true -require "net/http" +require "bundler/vendored_net_http" require "bundler/compact_index_client" require "bundler/compact_index_client/updater" require "tmpdir" RSpec.describe Bundler::CompactIndexClient::Updater do + subject(:updater) { described_class.new(fetcher) } + let(:fetcher) { double(:fetcher) } - let(:local_path) { Pathname.new Dir.mktmpdir("localpath") } + let(:local_path) { Pathname.new(Dir.mktmpdir("localpath")).join("versions") } + let(:etag_path) { Pathname.new(Dir.mktmpdir("localpath-etags")).join("versions.etag") } let(:remote_path) { double(:remote_path) } - let!(:updater) { described_class.new(fetcher) } + let(:full_body) { "abc123" } + let(:response) { double(:response, body: full_body, is_a?: false) } + let(:digest) { Digest::SHA256.base64digest(full_body) } + + context "when the local path does not exist" do + before do + allow(response).to receive(:[]).with("Repr-Digest") { nil } + allow(response).to receive(:[]).with("Digest") { nil } + allow(response).to receive(:[]).with("ETag") { '"thisisanetag"' } + end + + it "downloads the file without attempting append" do + expect(fetcher).to receive(:call).once.with(remote_path, {}) { response } + + updater.update(remote_path, local_path, etag_path) + + expect(local_path.read).to eq(full_body) + expect(etag_path.read).to eq("thisisanetag") + end + + it "fails immediately on bad checksum" do + expect(fetcher).to receive(:call).once.with(remote_path, {}) { response } + allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:baddigest:" } + + expect do + updater.update(remote_path, local_path, etag_path) + end.to raise_error(Bundler::CompactIndexClient::Updater::MismatchedChecksumError) + end + end + + context "when the local path exists" do + let(:local_body) { "abc" } + + before do + local_path.open("w") {|f| f.write(local_body) } + end + + context "with an etag" do + before do + etag_path.open("w") {|f| f.write("LocalEtag") } + end + + let(:headers) do + { + "If-None-Match" => '"LocalEtag"', + "Range" => "bytes=2-", + } + end + + it "does nothing if etags match" do + expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response) + allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { false } + allow(response).to receive(:is_a?).with(Gem::Net::HTTPNotModified) { true } + + updater.update(remote_path, local_path, etag_path) + + expect(local_path.read).to eq("abc") + expect(etag_path.read).to eq("LocalEtag") + end + + it "appends the file if etags do not match" do + expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response) + allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" } + allow(response).to receive(:[]).with("ETag") { '"NewEtag"' } + allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { true } + allow(response).to receive(:is_a?).with(Gem::Net::HTTPNotModified) { false } + allow(response).to receive(:body) { "c123" } + + updater.update(remote_path, local_path, etag_path) + + expect(local_path.read).to eq(full_body) + expect(etag_path.read).to eq("NewEtag") + end + + it "replaces the file if response ignores range" do + expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response) + allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" } + allow(response).to receive(:[]).with("ETag") { '"NewEtag"' } + allow(response).to receive(:body) { full_body } + + updater.update(remote_path, local_path, etag_path) + + expect(local_path.read).to eq(full_body) + expect(etag_path.read).to eq("NewEtag") + end + + it "tries the request again if the partial response fails digest check" do + allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:baddigest:" } + allow(response).to receive(:body) { "the beginning of the file changed" } + allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { true } + expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response) + + full_response = double(:full_response, body: full_body, is_a?: false) + allow(full_response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" } + allow(full_response).to receive(:[]).with("ETag") { '"NewEtag"' } + expect(fetcher).to receive(:call).once.with(remote_path, { "If-None-Match" => '"LocalEtag"' }).and_return(full_response) + + updater.update(remote_path, local_path, etag_path) + + expect(local_path.read).to eq(full_body) + expect(etag_path.read).to eq("NewEtag") + end + + it "tries the request again if the partial response is blank" do + allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:baddigest:" } + allow(response).to receive(:body) { "" } + allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { true } + expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response) + + full_response = double(:full_response, body: full_body, is_a?: false) + allow(full_response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" } + allow(full_response).to receive(:[]).with("ETag") { '"NewEtag"' } + expect(fetcher).to receive(:call).once.with(remote_path, { "If-None-Match" => '"LocalEtag"' }).and_return(full_response) + + updater.update(remote_path, local_path, etag_path) + + expect(local_path.read).to eq(full_body) + expect(etag_path.read).to eq("NewEtag") + end + end + + context "without an etag file" do + let(:headers) do + { "Range" => "bytes=2-" } + end + + it "appends the file" do + expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response) + allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" } + allow(response).to receive(:[]).with("ETag") { '"OpaqueEtag"' } + allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { true } + allow(response).to receive(:is_a?).with(Gem::Net::HTTPNotModified) { false } + allow(response).to receive(:body) { "c123" } + + updater.update(remote_path, local_path, etag_path) + + expect(local_path.read).to eq(full_body) + expect(etag_path.read).to eq("OpaqueEtag") + end + + it "replaces the file on full file response that ignores range request" do + expect(fetcher).to receive(:call).once.with(remote_path, headers).and_return(response) + allow(response).to receive(:[]).with("Repr-Digest") { nil } + allow(response).to receive(:[]).with("Digest") { nil } + allow(response).to receive(:[]).with("ETag") { '"OpaqueEtag"' } + allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { false } + allow(response).to receive(:is_a?).with(Gem::Net::HTTPNotModified) { false } + allow(response).to receive(:body) { full_body } + + updater.update(remote_path, local_path, etag_path) + + expect(local_path.read).to eq(full_body) + expect(etag_path.read).to eq("OpaqueEtag") + end + + it "tries the request again if the partial response fails digest check" do + allow(response).to receive(:[]).with("Repr-Digest") { "sha-256=:baddigest:" } + allow(response).to receive(:body) { "the beginning of the file changed" } + allow(response).to receive(:is_a?).with(Gem::Net::HTTPPartialContent) { true } + expect(fetcher).to receive(:call).once.with(remote_path, headers) do + # During the failed first request, we simulate another process writing the etag. + # This ensures the second request doesn't generate the md5 etag again but just uses whatever is written. + etag_path.open("w") {|f| f.write("LocalEtag") } + response + end + + full_response = double(:full_response, body: full_body, is_a?: false) + allow(full_response).to receive(:[]).with("Repr-Digest") { "sha-256=:#{digest}:" } + allow(full_response).to receive(:[]).with("ETag") { '"NewEtag"' } + expect(fetcher).to receive(:call).once.with(remote_path, { "If-None-Match" => '"LocalEtag"' }).and_return(full_response) + + updater.update(remote_path, local_path, etag_path) + + expect(local_path.read).to eq(full_body) + expect(etag_path.read).to eq("NewEtag") + end + end + end context "when the ETag header is missing" do # Regression test for https://github.com/rubygems/bundler/issues/5463 - let(:response) { double(:response, :body => "abc123") } + let(:response) { double(:response, body: full_body) } it "treats the response as an update" do - expect(response).to receive(:[]).with("ETag") { nil } + allow(response).to receive(:[]).with("Repr-Digest") { nil } + allow(response).to receive(:[]).with("Digest") { nil } + allow(response).to receive(:[]).with("ETag") { nil } expect(fetcher).to receive(:call) { response } - updater.update(local_path, remote_path) + updater.update(remote_path, local_path, etag_path) end end context "when the download is corrupt" do - let(:response) { double(:response, :body => "") } + let(:response) { double(:response, body: "") } it "raises HTTPError" do expect(fetcher).to receive(:call).and_raise(Zlib::GzipFile::Error) expect do - updater.update(local_path, remote_path) + updater.update(remote_path, local_path, etag_path) end.to raise_error(Bundler::HTTPError) end end context "when receiving non UTF-8 data and default internal encoding set to ASCII" do - let(:response) { double(:response, :body => "\x8B".b) } + let(:response) { double(:response, body: "\x8B".b) } it "works just fine" do old_verbose = $VERBOSE @@ -46,10 +228,12 @@ RSpec.describe Bundler::CompactIndexClient::Updater do begin $VERBOSE = false Encoding.default_internal = "ASCII" - expect(response).to receive(:[]).with("ETag") { nil } + allow(response).to receive(:[]).with("Repr-Digest") { nil } + allow(response).to receive(:[]).with("Digest") { nil } + allow(response).to receive(:[]).with("ETag") { nil } expect(fetcher).to receive(:call) { response } - updater.update(local_path, remote_path) + updater.update(remote_path, local_path, etag_path) ensure Encoding.default_internal = previous_internal_encoding $VERBOSE = old_verbose diff --git a/spec/bundler/bundler/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 59b958ae42..8c4a5a0331 100644 --- a/spec/bundler/bundler/definition_spec.rb +++ b/spec/bundler/bundler/definition_spec.rb @@ -3,60 +3,80 @@ 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).to receive(:settings) { Bundler::Settings.new(".") } - allow(Bundler::SharedHelpers).to receive(:find_gemfile) { Pathname.new("Gemfile") } - allow(Bundler).to receive(:ui) { double("UI", :info => "", :debug => "") } + allow(Bundler::SharedHelpers).to receive(:find_gemfile) { bundled_app_gemfile } + allow(Bundler).to receive(:ui) { double("UI", info: "", debug: "") } end - context "when it's not possible to write to the file" do - subject { Bundler::Definition.new(nil, [], Bundler::SourceList.new, []) } + subject { Bundler::Definition.new(bundled_app_lock, [], Bundler::SourceList.new, {}) } + + context "when it's not possible to write to the file" do it "raises an PermissionError with explanation" do allow(File).to receive(:open).and_call_original - expect(File).to receive(:open).with("Gemfile.lock", "wb"). - and_raise(Errno::EACCES) - expect { subject.lock("Gemfile.lock") }. + expect(File).to receive(:open).with(bundled_app_lock, "wb"). + and_raise(Errno::EACCES.new(bundled_app_lock.to_s)) + expect { subject.lock }. to raise_error(Bundler::PermissionError, /Gemfile\.lock/) end end context "when a temporary resource access issue occurs" do - subject { Bundler::Definition.new(nil, [], Bundler::SourceList.new, []) } - it "raises a TemporaryResourceError with explanation" do allow(File).to receive(:open).and_call_original - expect(File).to receive(:open).with("Gemfile.lock", "wb"). + expect(File).to receive(:open).with(bundled_app_lock, "wb"). and_raise(Errno::EAGAIN) - expect { subject.lock("Gemfile.lock") }. + expect { subject.lock }. to raise_error(Bundler::TemporaryResourceError, /temporarily unavailable/) end end context "when Bundler::Definition.no_lock is set to true" do - subject { Bundler::Definition.new(nil, [], Bundler::SourceList.new, []) } before { Bundler::Definition.no_lock = true } after { Bundler::Definition.no_lock = false } - it "does not create a lock file" do - subject.lock("Gemfile.lock") - expect(File.file?("Gemfile.lock")).to eq false + it "does not create a lockfile" do + subject.lock + expect(bundled_app_lock).not_to be_file end end end describe "detects changes" do it "for a path gem with changes" do - build_lib "foo", "1.0", :path => lib_path("foo") + build_lib "foo", "1.0", path: lib_path("foo") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + 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" + build_lib "foo", "1.0", path: lib_path("foo") do |s| + s.add_dependency "myrack", "1.0" + end + + checksums = checksums_section_when_enabled do |c| + c.no_checksum "foo", "1.0" + c.checksum gem_repo1, "myrack", "1.0.0" end - bundle :install, :env => { "DEBUG" => "1" } + bundle :install, env: { "DEBUG" => "1" } expect(out).to match(/re-resolving dependencies/) expect(lockfile).to eq <<~G @@ -64,21 +84,21 @@ 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} DEPENDENCIES foo! - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -89,67 +109,80 @@ RSpec.describe Bundler::Definition do end gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "ffi" G bundle "lock --add-platform java" - bundle "update ffi", :env => { "DEBUG" => "1" } + bundle "update ffi", env: { "DEBUG" => "1" } expect(out).to match(/because bundler is unlocking gems: \(ffi\)/) end it "for a path gem with deps and no changes" do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| - s.add_dependency "rack", "1.0" + build_lib "foo", "1.0", path: lib_path("foo") do |s| + s.add_dependency "myrack", "1.0" s.add_development_dependency "net-ssh", "1.0" end + checksums = checksums_section_when_enabled do |c| + c.no_checksum "foo", "1.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 - bundle :check, :env => { "DEBUG" => "1" } - - expect(out).to match(/using resolution from the lockfile/) - expect(lockfile).to eq <<~G + expected_lockfile = <<~G PATH 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} DEPENDENCIES foo! - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G + + expect(lockfile).to eq(expected_lockfile) + + bundle :check, env: { "DEBUG" => "1" } + + expect(out).to match(/using resolution from the lockfile/) + expect(lockfile).to eq(expected_lockfile) end it "for a locked gem for another platform" do 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" } + 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) @@ -158,24 +191,28 @@ RSpec.describe Bundler::Definition do DEPENDENCIES only_java - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end it "for a rubygems gem" do + 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 - bundle :check, :env => { "DEBUG" => "1" } + 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: foo (1.0) @@ -184,9 +221,9 @@ RSpec.describe Bundler::Definition do DEPENDENCIES foo - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end end @@ -196,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' @@ -211,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) @@ -260,7 +297,7 @@ RSpec.describe Bundler::Definition do bundled_app_lock, updated_deps_in_gemfile, source_list, - :gems => ["shared_owner_a"], :conservative => true + gems: ["shared_owner_a"], conservative: true ) locked = definition.send(:converge_locked_specs).map(&:name) expect(locked).to eq %w[isolated_dep isolated_owner shared_dep shared_owner_b] @@ -270,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 @@ -280,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 1ef511f1aa..f930459571 100644 --- a/spec/bundler/bundler/dependency_spec.rb +++ b/spec/bundler/bundler/dependency_spec.rb @@ -35,126 +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, - :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, - :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 } - 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, - :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, - :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, - :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 } - 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 8b5bf930f2..b6e67a312c 100644 --- a/spec/bundler/bundler/dsl_spec.rb +++ b/spec/bundler/bundler/dsl_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Bundler::Dsl do it "registers custom hosts" do subject.git_source(:example) {|repo_name| "git@git.example.com:#{repo_name}.git" } subject.git_source(:foobar) {|repo_name| "git@foobar.com:#{repo_name}.git" } - subject.gem("dobry-pies", :example => "strzalek/dobry-pies") + subject.gem("dobry-pies", example: "strzalek/dobry-pies") example_uri = "git@git.example.com:strzalek/dobry-pies.git" expect(subject.dependencies.first.source.uri).to eq(example_uri) end @@ -26,92 +26,143 @@ RSpec.describe Bundler::Dsl do end it "converts :github PR to URI using https" do - subject.gem("sparks", :github => "https://github.com/indirect/sparks/pull/5") + subject.gem("sparks", github: "https://github.com/indirect/sparks/pull/5") github_uri = "https://github.com/indirect/sparks.git" expect(subject.dependencies.first.source.uri).to eq(github_uri) expect(subject.dependencies.first.source.ref).to eq("refs/pull/5/head") end + it "converts :gitlab PR to URI using https" do + subject.gem("sparks", gitlab: "https://gitlab.com/indirect/sparks/-/merge_requests/5") + gitlab_uri = "https://gitlab.com/indirect/sparks.git" + expect(subject.dependencies.first.source.uri).to eq(gitlab_uri) + expect(subject.dependencies.first.source.ref).to eq("refs/merge-requests/5/head") + end + it "rejects :github PR URI with a branch, ref or tag" do expect do - subject.gem("sparks", :github => "https://github.com/indirect/sparks/pull/5", :branch => "foo") + subject.gem("sparks", github: "https://github.com/indirect/sparks/pull/5", branch: "foo") end.to raise_error( Bundler::GemfileError, %(The :branch option can't be used with `github: "https://github.com/indirect/sparks/pull/5"`), ) expect do - subject.gem("sparks", :github => "https://github.com/indirect/sparks/pull/5", :ref => "foo") + subject.gem("sparks", github: "https://github.com/indirect/sparks/pull/5", ref: "foo") end.to raise_error( Bundler::GemfileError, %(The :ref option can't be used with `github: "https://github.com/indirect/sparks/pull/5"`), ) expect do - subject.gem("sparks", :github => "https://github.com/indirect/sparks/pull/5", :tag => "foo") + subject.gem("sparks", github: "https://github.com/indirect/sparks/pull/5", tag: "foo") end.to raise_error( Bundler::GemfileError, %(The :tag option can't be used with `github: "https://github.com/indirect/sparks/pull/5"`), ) end + it "rejects :gitlab PR URI with a branch, ref or tag" do + expect do + subject.gem("sparks", gitlab: "https://gitlab.com/indirect/sparks/-/merge_requests/5", branch: "foo") + end.to raise_error( + Bundler::GemfileError, + %(The :branch option can't be used with `gitlab: "https://gitlab.com/indirect/sparks/-/merge_requests/5"`), + ) + + expect do + subject.gem("sparks", gitlab: "https://gitlab.com/indirect/sparks/-/merge_requests/5", ref: "foo") + end.to raise_error( + Bundler::GemfileError, + %(The :ref option can't be used with `gitlab: "https://gitlab.com/indirect/sparks/-/merge_requests/5"`), + ) + + expect do + subject.gem("sparks", gitlab: "https://gitlab.com/indirect/sparks/-/merge_requests/5", tag: "foo") + end.to raise_error( + Bundler::GemfileError, + %(The :tag option can't be used with `gitlab: "https://gitlab.com/indirect/sparks/-/merge_requests/5"`), + ) + end + it "rejects :github with :git" do expect do - subject.gem("sparks", :github => "indirect/sparks", :git => "https://github.com/indirect/sparks.git") + subject.gem("sparks", github: "indirect/sparks", git: "https://github.com/indirect/sparks.git") end.to raise_error( Bundler::GemfileError, %(The :git option can't be used with `github: "indirect/sparks"`), ) end - context "default hosts", :bundler => "< 3" do + it "rejects :gitlab with :git" do + expect do + subject.gem("sparks", gitlab: "indirect/sparks", git: "https://gitlab.com/indirect/sparks.git") + end.to raise_error( + Bundler::GemfileError, + %(The :git option can't be used with `gitlab: "indirect/sparks"`), + ) + end + + context "default hosts" do it "converts :github to URI using https" do - subject.gem("sparks", :github => "indirect/sparks") + subject.gem("sparks", github: "indirect/sparks") github_uri = "https://github.com/indirect/sparks.git" expect(subject.dependencies.first.source.uri).to eq(github_uri) end it "converts :github shortcut to URI using https" do - subject.gem("sparks", :github => "rails") + subject.gem("sparks", github: "rails") github_uri = "https://github.com/rails/rails.git" expect(subject.dependencies.first.source.uri).to eq(github_uri) end + it "converts :gitlab to URI using https" do + subject.gem("sparks", gitlab: "indirect/sparks") + gitlab_uri = "https://gitlab.com/indirect/sparks.git" + expect(subject.dependencies.first.source.uri).to eq(gitlab_uri) + end + + it "converts :gitlab shortcut to URI using https" do + subject.gem("sparks", gitlab: "rails") + gitlab_uri = "https://gitlab.com/rails/rails.git" + expect(subject.dependencies.first.source.uri).to eq(gitlab_uri) + end + it "converts numeric :gist to :git" do - subject.gem("not-really-a-gem", :gist => 2_859_988) + subject.gem("not-really-a-gem", gist: 2_859_988) github_uri = "https://gist.github.com/2859988.git" expect(subject.dependencies.first.source.uri).to eq(github_uri) end it "converts :gist to :git" do - subject.gem("not-really-a-gem", :gist => "2859988") + subject.gem("not-really-a-gem", gist: "2859988") github_uri = "https://gist.github.com/2859988.git" expect(subject.dependencies.first.source.uri).to eq(github_uri) end it "converts :bitbucket to :git" do - subject.gem("not-really-a-gem", :bitbucket => "mcorp/flatlab-rails") + subject.gem("not-really-a-gem", bitbucket: "mcorp/flatlab-rails") bitbucket_uri = "https://mcorp@bitbucket.org/mcorp/flatlab-rails.git" expect(subject.dependencies.first.source.uri).to eq(bitbucket_uri) end it "converts 'mcorp' to 'mcorp/mcorp'" do - subject.gem("not-really-a-gem", :bitbucket => "mcorp") + subject.gem("not-really-a-gem", bitbucket: "mcorp") bitbucket_uri = "https://mcorp@bitbucket.org/mcorp/mcorp.git" expect(subject.dependencies.first.source.uri).to eq(bitbucket_uri) end end context "default git sources" do - it "has bitbucket, gist, and github" do - expect(subject.instance_variable_get(:@git_sources).keys.sort).to eq(%w[bitbucket gist github]) + it "has bitbucket, gist, github, and gitlab" do + expect(subject.instance_variable_get(:@git_sources).keys.sort).to eq(%w[bitbucket gist github gitlab]) end end end 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." @@ -122,41 +173,59 @@ 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") }. to raise_error(Bundler::GemfileError, /There was an error evaluating `Gemfile`: ruby_version must match the :engine_version for MRI/) end + + it "populates __dir__ and __FILE__ correctly" do + abs_path = 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(git_root.dirname.to_s) + expect(subject.instance_variable_get(:@fragment_file)).to eq(abs_path) + end end 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) + subject.gem("foo", platform: platform) end end # rubocop:enable Naming/VariableNumber it "allows platforms matching the running Ruby version" do platform = "ruby_#{RbConfig::CONFIG["MAJOR"]}#{RbConfig::CONFIG["MINOR"]}" - subject.gem("foo", :platform => platform) + + 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 - expect { subject.gem("foo", :platform => :bogus) }. + expect { subject.gem("foo", platform: :bogus) }. 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/) @@ -203,24 +272,33 @@ RSpec.describe Bundler::Dsl do end it "rejects branch option on non-git gems" do - expect { subject.gem("foo", :branch => "test") }. + expect { subject.gem("foo", branch: "test") }. to raise_error(Bundler::GemfileError, /The `branch` option for `gem 'foo'` is not allowed. Only gems with a git source can specify a branch/) end it "allows specifying a branch on git gems" do - subject.gem("foo", :branch => "test", :git => "http://mytestrepo") + subject.gem("foo", branch: "test", git: "http://mytestrepo") dep = subject.dependencies.last expect(dep.name).to eq "foo" end it "allows specifying a branch on git gems with a git_source" do subject.git_source(:test_source) {|n| "https://github.com/#{n}" } - subject.gem("foo", :branch => "test", :test_source => "bundler/bundler") + subject.gem("foo", branch: "test", test_source: "bundler/bundler") dep = subject.dependencies.last expect(dep.name).to eq "foo" end 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" @@ -260,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 @@ -280,7 +358,7 @@ RSpec.describe Bundler::Dsl do allow(Bundler).to receive(:default_gemfile).and_return(Pathname.new("./Gemfile")) subject.source("https://other-source.org") do - subject.gem("dobry-pies", :path => "foo") + subject.gem("dobry-pies", path: "foo") subject.gem("foo") end @@ -289,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 7dd6925007..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 @@ -60,21 +100,21 @@ RSpec.describe Bundler::EndpointSpecification do it "should return the remote spec value when not set on endpoint specification and remote spec has one" do remote_value = "remote_value" - remote_spec = double(:remote_spec, :required_ruby_version => remote_value, :required_rubygems_version => nil) + remote_spec = double(:remote_spec, required_ruby_version: remote_value, required_rubygems_version: nil) allow(spec_fetcher).to receive(:fetch_spec).and_return(remote_spec) expect(spec.required_ruby_version). eql?(remote_value) end it "should use the default Gem Requirement value when not set on endpoint specification and not set on remote spec" do - remote_spec = double(:remote_spec, :required_ruby_version => nil, :required_rubygems_version => nil) + remote_spec = double(:remote_spec, required_ruby_version: nil, required_rubygems_version: nil) allow(spec_fetcher).to receive(:fetch_spec).and_return(remote_spec) expect(spec.required_ruby_version). eql?(Gem::Requirement.default) end end it "supports equality comparison" do - remote_spec = double(:remote_spec, :required_ruby_version => nil, :required_rubygems_version => nil) + remote_spec = double(:remote_spec, required_ruby_version: nil, required_rubygems_version: nil) allow(spec_fetcher).to receive(:fetch_spec).and_return(remote_spec) other_spec = described_class.new("bar", version, platform, spec_fetcher, dependencies, metadata) expect(spec).to eql(spec) diff --git a/spec/bundler/bundler/env_spec.rb b/spec/bundler/bundler/env_spec.rb index 186b207b9f..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 @@ -88,21 +88,21 @@ RSpec.describe Bundler::Env do allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) end - let(:output) { described_class.report(:print_gemfile => true) } + let(:output) { described_class.report(print_gemfile: true) } it "prints the Gemfile" do expect(output).to include("Gemfile") - 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 context "when there no Gemfile and print_gemfile is true" do - let(:output) { described_class.report(:print_gemfile => true) } + let(:output) { described_class.report(print_gemfile: true) } it "prints the environment" do expect(output).to start_with("## Environment") @@ -114,7 +114,7 @@ RSpec.describe Bundler::Env do bundle "config set https://localgemserver.test/ user:pass" end - let(:output) { described_class.report(:print_gemfile => true) } + let(:output) { described_class.report(print_gemfile: true) } it "prints the config with redacted values" do expect(output).to include("https://localgemserver.test") @@ -128,7 +128,7 @@ RSpec.describe Bundler::Env do bundle "config set https://localgemserver.test/ api_token:x-oauth-basic" end - let(:output) { described_class.report(:print_gemfile => true) } + let(:output) { described_class.report(print_gemfile: true) } it "prints the config with redacted values" do expect(output).to include("https://localgemserver.test") @@ -139,7 +139,7 @@ RSpec.describe Bundler::Env do context "when Gemfile contains a gemspec and print_gemspecs is true" do let(:gemspec) do - strip_whitespace(<<-GEMSPEC) + <<~GEMSPEC Gem::Specification.new do |gem| gem.name = "foo" gem.author = "Fumofu" @@ -148,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 @@ -158,7 +158,7 @@ RSpec.describe Bundler::Env do end it "prints the gemspec" do - output = described_class.report(:print_gemspecs => true) + output = described_class.report(print_gemspecs: true) expect(output).to include("foo.gemspec") expect(output).to include(gemspec) @@ -167,18 +167,18 @@ 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}" allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) allow(Bundler::SharedHelpers).to receive(:pwd).and_return(bundled_app) - output = described_class.report(:print_gemspecs => true) - expect(output).to include(strip_whitespace(<<-ENV)) + output = described_class.report(print_gemspecs: true) + expect(output).to include(<<~ENV) ## Gemfile ### Gemfile @@ -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).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/environment_preserver_spec.rb b/spec/bundler/bundler/environment_preserver_spec.rb index 530ca6f835..6c7066d0c6 100644 --- a/spec/bundler/bundler/environment_preserver_spec.rb +++ b/spec/bundler/bundler/environment_preserver_spec.rb @@ -27,8 +27,12 @@ RSpec.describe Bundler::EnvironmentPreserver do context "when a key is empty" do let(:env) { { "foo" => "" } } - it "should not create backup entries" do - expect(subject).not_to have_key "BUNDLER_ORIG_foo" + it "should keep the original entry" do + expect(subject["foo"]).to be_empty + end + + it "should still create backup entries" do + expect(subject).to have_key "BUNDLER_ORIG_foo" end end @@ -71,8 +75,12 @@ RSpec.describe Bundler::EnvironmentPreserver do context "when the original key is empty" do let(:env) { { "foo" => "my-foo", "BUNDLER_ORIG_foo" => "" } } - it "should keep the current value" do - expect(subject["foo"]).to eq("my-foo") + it "should restore the original value" do + expect(subject["foo"]).to be_empty + end + + it "should delete the backup value" do + expect(subject.key?("BUNDLER_ORIG_foo")).to eq(false) end end end diff --git a/spec/bundler/bundler/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/base_spec.rb b/spec/bundler/bundler/fetcher/base_spec.rb index 02506591f3..b8c6b57b10 100644 --- a/spec/bundler/bundler/fetcher/base_spec.rb +++ b/spec/bundler/bundler/fetcher/base_spec.rb @@ -4,15 +4,16 @@ RSpec.describe Bundler::Fetcher::Base do let(:downloader) { double(:downloader) } let(:remote) { double(:remote) } let(:display_uri) { "http://sample_uri.com" } + let(:gem_remote_fetcher) { nil } class TestClass < described_class; end - subject { TestClass.new(downloader, remote, display_uri) } + subject { TestClass.new(downloader, remote, display_uri, gem_remote_fetcher) } describe "#initialize" do context "with the abstract Base class" do it "should raise an error" do - expect { described_class.new(downloader, remote, display_uri) }.to raise_error(RuntimeError, "Abstract class") + expect { described_class.new(downloader, remote, display_uri, gem_remote_fetcher) }.to raise_error(RuntimeError, "Abstract class") end end @@ -36,7 +37,7 @@ RSpec.describe Bundler::Fetcher::Base do end describe "#fetch_uri" do - let(:remote_uri_obj) { Bundler::URI("http://rubygems.org") } + let(:remote_uri_obj) { Gem::URI("http://rubygems.org") } before { allow(subject).to receive(:remote_uri).and_return(remote_uri_obj) } @@ -49,10 +50,10 @@ RSpec.describe Bundler::Fetcher::Base do end context "when the remote uri's host is not rubygems.org" do - let(:remote_uri_obj) { Bundler::URI("http://otherhost.org") } + let(:remote_uri_obj) { Gem::URI("http://otherhost.org") } it "should return the remote uri" do - expect(subject.fetch_uri).to eq(Bundler::URI("http://otherhost.org")) + expect(subject.fetch_uri).to eq(Gem::URI("http://otherhost.org")) end end diff --git a/spec/bundler/bundler/fetcher/compact_index_spec.rb b/spec/bundler/bundler/fetcher/compact_index_spec.rb index 00eb27edea..aa536673d9 100644 --- a/spec/bundler/bundler/fetcher/compact_index_spec.rb +++ b/spec/bundler/bundler/fetcher/compact_index_spec.rb @@ -4,13 +4,18 @@ require "bundler/compact_index_client" RSpec.describe Bundler::Fetcher::CompactIndex do - let(:downloader) { double(:downloader) } - let(:display_uri) { Bundler::URI("http://sampleuri.com") } - let(:remote) { double(:remote, :cache_slug => "lsjdf", :uri => display_uri) } - let(:compact_index) { described_class.new(downloader, remote, display_uri) } + let(: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 @@ -31,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 7cfc86ef76..501bc269a5 100644 --- a/spec/bundler/bundler/fetcher/dependency_spec.rb +++ b/spec/bundler/bundler/fetcher/dependency_spec.rb @@ -2,10 +2,11 @@ RSpec.describe Bundler::Fetcher::Dependency do let(:downloader) { double(:downloader) } - let(:remote) { double(:remote, :uri => Bundler::URI("http://localhost:5000")) } + let(:remote) { double(:remote, uri: Gem::URI("http://localhost:5000")) } let(:display_uri) { "http://sample_uri.com" } + let(:gem_remote_fetcher) { nil } - subject { described_class.new(downloader, remote, display_uri) } + subject { described_class.new(downloader, remote, display_uri, gem_remote_fetcher) } describe "#available?" do let(:dependency_api_uri) { double(:dependency_api_uri) } @@ -155,9 +156,9 @@ RSpec.describe Bundler::Fetcher::Dependency do end end - shared_examples_for "the error suggests retrying with the full index" do - it "should log the inability to fetch from API at debug level" do - expect(Bundler).to receive_message_chain(:ui, :debug).with("could not fetch from the dependency API\nit's suggested to retry using the full index via `bundle install --full-index`") + shared_examples_for "the error is logged" do + it "should log the inability to fetch from API at debug level, and mention retrying" do + expect(Bundler).to receive_message_chain(:ui, :debug).with("could not fetch from the dependency API, trying the full index") subject.specs(gem_names, full_dependency_list, last_spec_list) end end @@ -166,25 +167,21 @@ RSpec.describe Bundler::Fetcher::Dependency do before { allow(subject).to receive(:dependency_specs) { raise Bundler::HTTPError.new } } it_behaves_like "the error is properly handled" - it_behaves_like "the error suggests retrying with the full index" + it_behaves_like "the error is logged" end context "when a GemspecError occurs" do before { allow(subject).to receive(:dependency_specs) { raise Bundler::GemspecError.new } } it_behaves_like "the error is properly handled" - it_behaves_like "the error suggests retrying with the full index" + it_behaves_like "the error is logged" end context "when a MarshalError occurs" do before { allow(subject).to receive(:dependency_specs) { raise Bundler::MarshalError.new } } it_behaves_like "the error is properly handled" - - it "should log the inability to fetch from API and mention retrying" do - expect(Bundler).to receive_message_chain(:ui, :debug).with("could not fetch from the dependency API, trying the full index") - subject.specs(gem_names, full_dependency_list, last_spec_list) - end + it_behaves_like "the error is logged" end end @@ -214,8 +211,8 @@ RSpec.describe Bundler::Fetcher::Dependency do let(:gem_names) { [%w[foo bar], %w[bundler rubocop]] } let(:dep_api_uri) { double(:dep_api_uri) } let(:unmarshalled_gems) { double(:unmarshalled_gems) } - let(:fetch_response) { double(:fetch_response, :body => double(:body)) } - let(:rubygems_limit) { 50 } + let(:fetch_response) { double(:fetch_response, body: double(:body)) } + let(:rubygems_limit) { 100 } before { allow(subject).to receive(:dependency_api_uri).with(gem_names).and_return(dep_api_uri) } @@ -225,26 +222,38 @@ 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 let(:gem_list) do [ { - :dependencies => { + dependencies: { "resque" => "req3,req4", }, - :name => "typhoeus", - :number => "1.0.1", - :platform => "ruby", + name: "typhoeus", + number: "1.0.1", + platform: "ruby", }, { - :dependencies => { + dependencies: { "faraday" => "req1,req2", }, - :name => "grape", - :number => "2.0.2", - :platform => "jruby", + name: "grape", + number: "2.0.2", + platform: "jruby", }, ] end @@ -258,7 +267,7 @@ RSpec.describe Bundler::Fetcher::Dependency do end describe "#dependency_api_uri" do - let(:uri) { Bundler::URI("http://gem-api.com") } + let(:uri) { Gem::URI("http://gem-api.com") } context "with gem names" do let(:gem_names) { %w[foo bar bundler rubocop] } diff --git a/spec/bundler/bundler/fetcher/downloader_spec.rb b/spec/bundler/bundler/fetcher/downloader_spec.rb index 0412ddb83a..edf426328a 100644 --- a/spec/bundler/bundler/fetcher/downloader_spec.rb +++ b/spec/bundler/bundler/fetcher/downloader_spec.rb @@ -3,7 +3,7 @@ RSpec.describe Bundler::Fetcher::Downloader do let(:connection) { double(:connection) } let(:redirect_limit) { 5 } - let(:uri) { Bundler::URI("http://www.uri-to-fetch.com/api/v2/endpoint") } + let(:uri) { Gem::URI("http://www.uri-to-fetch.com/api/v2/endpoint") } let(:options) { double(:options) } subject { described_class.new(connection, redirect_limit) } @@ -27,7 +27,7 @@ RSpec.describe Bundler::Fetcher::Downloader do end context "logging" do - let(:http_response) { Net::HTTPSuccess.new("1.1", 200, "Success") } + let(:http_response) { Gem::Net::HTTPSuccess.new("1.1", 200, "Success") } it "should log the HTTP response code and message to debug" do expect(Bundler).to receive_message_chain(:ui, :debug).with("HTTP 200 Success #{uri}") @@ -35,61 +35,61 @@ RSpec.describe Bundler::Fetcher::Downloader do end end - context "when the request response is a Net::HTTPRedirection" do - let(:http_response) { Net::HTTPRedirection.new(httpv, 308, "Moved") } + context "when the request response is a Gem::Net::HTTPRedirection" do + let(:http_response) { Gem::Net::HTTPRedirection.new(httpv, 308, "Moved") } before { http_response["location"] = "http://www.redirect-uri.com/api/v2/endpoint" } it "should try to fetch the redirect uri and iterate the # requests counter" do - expect(subject).to receive(:fetch).with(Bundler::URI("http://www.uri-to-fetch.com/api/v2/endpoint"), options, 0).and_call_original - expect(subject).to receive(:fetch).with(Bundler::URI("http://www.redirect-uri.com/api/v2/endpoint"), options, 1) + expect(subject).to receive(:fetch).with(Gem::URI("http://www.uri-to-fetch.com/api/v2/endpoint"), options, 0).and_call_original + expect(subject).to receive(:fetch).with(Gem::URI("http://www.redirect-uri.com/api/v2/endpoint"), options, 1) subject.fetch(uri, options, counter) end context "when the redirect uri and original uri are the same" do - let(:uri) { Bundler::URI("ssh://username:password@www.uri-to-fetch.com/api/v2/endpoint") } + let(:uri) { Gem::URI("ssh://username:password@www.uri-to-fetch.com/api/v2/endpoint") } before { http_response["location"] = "ssh://www.uri-to-fetch.com/api/v1/endpoint" } it "should set the same user and password for the redirect uri" do - expect(subject).to receive(:fetch).with(Bundler::URI("ssh://username:password@www.uri-to-fetch.com/api/v2/endpoint"), options, 0).and_call_original - expect(subject).to receive(:fetch).with(Bundler::URI("ssh://username:password@www.uri-to-fetch.com/api/v1/endpoint"), options, 1) + expect(subject).to receive(:fetch).with(Gem::URI("ssh://username:password@www.uri-to-fetch.com/api/v2/endpoint"), options, 0).and_call_original + expect(subject).to receive(:fetch).with(Gem::URI("ssh://username:password@www.uri-to-fetch.com/api/v1/endpoint"), options, 1) subject.fetch(uri, options, counter) end end end - context "when the request response is a Net::HTTPSuccess" do - let(:http_response) { Net::HTTPSuccess.new("1.1", 200, "Success") } + context "when the request response is a Gem::Net::HTTPSuccess" do + let(:http_response) { Gem::Net::HTTPSuccess.new("1.1", 200, "Success") } it "should return the response body" do expect(subject.fetch(uri, options, counter)).to eq(http_response) end end - context "when the request response is a Net::HTTPRequestEntityTooLarge" do - let(:http_response) { Net::HTTPRequestEntityTooLarge.new("1.1", 413, "Too Big") } + context "when the request response is a Gem::Net::HTTPRequestEntityTooLarge" do + let(:http_response) { Gem::Net::HTTPRequestEntityTooLarge.new("1.1", 413, "Too Big") } it "should raise a Bundler::Fetcher::FallbackError with the response body" do expect { subject.fetch(uri, options, counter) }.to raise_error(Bundler::Fetcher::FallbackError, "Body with info") end end - context "when the request response is a Net::HTTPUnauthorized" do - let(:http_response) { Net::HTTPUnauthorized.new("1.1", 401, "Unauthorized") } + context "when the request response is a Gem::Net::HTTPUnauthorized" do + let(:http_response) { Gem::Net::HTTPUnauthorized.new("1.1", 401, "Unauthorized") } it "should raise a Bundler::Fetcher::AuthenticationRequiredError with the uri host" do expect { subject.fetch(uri, options, counter) }.to raise_error(Bundler::Fetcher::AuthenticationRequiredError, /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 - let(:uri) { Bundler::URI("http://user:password@www.uri-to-fetch.com") } + 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 expect { subject.fetch(uri, options, counter) }. @@ -98,29 +98,71 @@ RSpec.describe Bundler::Fetcher::Downloader do end end - context "when the request response is a Net::HTTPNotFound" do - let(:http_response) { Net::HTTPNotFound.new("1.1", 404, "Not Found") } + context "when the request response is a Gem::Net::HTTPForbidden" do + let(:http_response) { Gem::Net::HTTPForbidden.new("1.1", 403, "Forbidden") } + let(:uri) { Gem::URI("http://user:password@www.uri-to-fetch.com") } - it "should raise a Bundler::Fetcher::FallbackError with Net::HTTPNotFound" do + it "should raise a Bundler::Fetcher::AuthenticationForbiddenError with the uri host" do + expect { subject.fetch(uri, options, counter) }.to raise_error(Bundler::Fetcher::AuthenticationForbiddenError, + /Access token could not be authenticated for www.uri-to-fetch.com/) + end + end + + context "when the request response is a Gem::Net::HTTPNotFound" do + let(:http_response) { Gem::Net::HTTPNotFound.new("1.1", 404, "Not Found") } + + it "should raise a Bundler::Fetcher::FallbackError with Gem::Net::HTTPNotFound" do expect { subject.fetch(uri, options, counter) }. - to raise_error(Bundler::Fetcher::FallbackError, "Net::HTTPNotFound: http://www.uri-to-fetch.com/api/v2/endpoint") + to raise_error(Bundler::Fetcher::FallbackError, "Gem::Net::HTTPNotFound: http://www.uri-to-fetch.com/api/v2/endpoint") end - context "when the there are credentials provided in the request" do - let(:uri) { Bundler::URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint") } + 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 expect { subject.fetch(uri, options, counter) }. - to raise_error(Bundler::Fetcher::FallbackError, "Net::HTTPNotFound: http://username@www.uri-to-fetch.com/api/v2/endpoint") + to raise_error(Bundler::Fetcher::FallbackError, "Gem::Net::HTTPNotFound: http://username@www.uri-to-fetch.com/api/v2/endpoint") end end end + context "when the request response is 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) { Net::HTTPBadGateway.new("1.1", 500, "Fatal Error") } + let(:http_response) { Gem::Net::HTTPBadGateway.new("1.1", 500, "Fatal Error") } it "should raise a Bundler::HTTPError with the response class and body" do - expect { subject.fetch(uri, options, counter) }.to raise_error(Bundler::HTTPError, "Net::HTTPBadGateway: Body with info") + expect { subject.fetch(uri, options, counter) }.to raise_error(Bundler::HTTPError, "Gem::Net::HTTPBadGateway: Body with info") end end end @@ -130,7 +172,7 @@ RSpec.describe Bundler::Fetcher::Downloader do let(:response) { double(:response) } before do - allow(Net::HTTP::Get).to receive(:new).with("/api/v2/endpoint", options).and_return(net_http_get) + allow(Gem::Net::HTTP::Get).to receive(:new).with("/api/v2/endpoint", options).and_return(net_http_get) allow(connection).to receive(:request).with(uri, net_http_get).and_return(response) end @@ -142,37 +184,41 @@ RSpec.describe Bundler::Fetcher::Downloader do context "when there is a user provided in the request" do context "and there is also a password provided" do context "that contains cgi escaped characters" do - let(:uri) { Bundler::URI("http://username:password%24@www.uri-to-fetch.com/api/v2/endpoint") } + let(:uri) { Gem::URI("http://username:password%24@www.uri-to-fetch.com/api/v2/endpoint") } - it "should request basic authentication with the username and password" do + 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) { Bundler::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 + 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, 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 - let(:uri) { Bundler::URI("http://username@www.uri-to-fetch.com/api/v2/endpoint") } + 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 - let(:uri) { Bundler::URI("http://username%24@www.uri-to-fetch.com/api/v2/endpoint") } + 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 @@ -187,57 +233,68 @@ 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 - let(:uri) { Bundler::URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint") } + 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") end 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 f0db07583c..a6a18efd98 100644 --- a/spec/bundler/bundler/fetcher/index_spec.rb +++ b/spec/bundler/bundler/fetcher/index_spec.rb @@ -4,12 +4,15 @@ 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] } + let(:gem_remote_fetcher) { nil } - subject { described_class.new(downloader, remote, display_uri) } + subject { described_class.new(downloader, remote, display_uri, gem_remote_fetcher) } before { allow(Bundler).to receive(:rubygems).and_return(rubygems) } @@ -19,10 +22,8 @@ RSpec.describe Bundler::Fetcher::Index do end context "error handling" do - let(:remote_uri) { Bundler::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 @@ -37,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 @@ -63,33 +56,16 @@ RSpec.describe Bundler::Fetcher::Index do context "when a 403 response occurs" do let(:error_message) { "403" } - before do - allow(remote_uri).to receive(:userinfo).and_return(userinfo) - end - - context "and there was userinfo" do - let(:userinfo) { double(:userinfo) } - - it "should raise a Bundler::Fetcher::BadAuthenticationError" do - expect { subject.specs(gem_names) }.to raise_error(Bundler::Fetcher::BadAuthenticationError, - %r{Bad username or password for http://remote-uri.org}) - end - end - - context "and there was no userinfo" do - let(:userinfo) { nil } - - it "should raise a Bundler::Fetcher::AuthenticationRequiredError" do - expect { subject.specs(gem_names) }.to raise_error(Bundler::Fetcher::AuthenticationRequiredError, - %r{Authentication is required for http://remote-uri.org}) - end + it "should raise a Bundler::Fetcher::AuthenticationForbiddenError" do + expect { subject.specs(gem_names) }.to raise_error(Bundler::Fetcher::AuthenticationForbiddenError, + %r{Access token could not be authenticated for http://remote-uri.org}) end end context "any other message is returned" do let(:error_message) { "You get an error, you get an error!" } - before { allow(Bundler).to receive(:ui).and_return(double(:trace => nil)) } + before { allow(Bundler).to receive(:ui).and_return(double(trace: nil)) } it "should raise a Bundler::HTTPError" do expect { subject.specs(gem_names) }.to raise_error(Bundler::HTTPError, "Could not fetch specs from http://sample_uri.com due to underlying error <You get an error, you get an error! (http://sample_uri.com)>") diff --git a/spec/bundler/bundler/fetcher_spec.rb b/spec/bundler/bundler/fetcher_spec.rb index 27a63c476d..e20f7e7c48 100644 --- a/spec/bundler/bundler/fetcher_spec.rb +++ b/spec/bundler/bundler/fetcher_spec.rb @@ -3,8 +3,8 @@ require "bundler/fetcher" RSpec.describe Bundler::Fetcher do - let(:uri) { Bundler::URI("https://example.com") } - let(:remote) { double("remote", :uri => uri, :original_uri => nil) } + let(:uri) { Gem::URI("https://example.com") } + let(:remote) { double("remote", uri: uri, original_uri: nil) } subject(:fetcher) { Bundler::Fetcher.new(remote) } @@ -45,9 +45,9 @@ RSpec.describe Bundler::Fetcher do end context "when a rubygems source mirror is set" do - let(:orig_uri) { Bundler::URI("http://zombo.com") } + let(:orig_uri) { Gem::URI("http://zombo.com") } let(:remote_with_mirror) do - double("remote", :uri => uri, :original_uri => orig_uri, :anonymized_uri => uri) + double("remote", uri: uri, original_uri: orig_uri, anonymized_uri: uri) end let(:fetcher) { Bundler::Fetcher.new(remote_with_mirror) } @@ -61,7 +61,7 @@ RSpec.describe Bundler::Fetcher do context "when there is no rubygems source mirror set" do let(:remote_no_mirror) do - double("remote", :uri => uri, :original_uri => nil, :anonymized_uri => uri) + double("remote", uri: uri, original_uri: nil, anonymized_uri: uri) end let(:fetcher) { Bundler::Fetcher.new(remote_no_mirror) } @@ -114,9 +114,9 @@ RSpec.describe Bundler::Fetcher do context "when gem ssl configuration is set" do before do allow(Gem.configuration).to receive_messages( - :http_proxy => nil, - :ssl_client_cert => "cert", - :ssl_ca_cert => "ca" + http_proxy: nil, + ssl_client_cert: "cert", + ssl_ca_cert: "ca" ) expect(File).to receive(:read).and_return("") expect(OpenSSL::X509::Certificate).to receive(:new).and_return("cert") @@ -143,18 +143,20 @@ RSpec.describe Bundler::Fetcher do describe "include CI information" do it "from one CI" do - with_env_vars("JENKINS_URL" => "foo") do + with_env_vars("CI" => nil, "JENKINS_URL" => "foo") do ci_part = fetcher.user_agent.split(" ").find {|x| x.start_with?("ci/") } - expect(ci_part).to match("jenkins") + cis = ci_part.split("/").last.split(",") + expect(cis).to include("jenkins") + expect(cis).not_to include("ci") end end it "from many CI" do - with_env_vars("TRAVIS" => "foo", "GITLAB_CI" => "gitlab", "CI_NAME" => "my_ci") do + with_env_vars("CI" => "true", "SEMAPHORE" => nil, "TRAVIS" => "foo", "GITLAB_CI" => "gitlab", "CI_NAME" => "MY_ci") do ci_part = fetcher.user_agent.split(" ").find {|x| x.start_with?("ci/") } - expect(ci_part).to match("travis") - expect(ci_part).to match("gitlab") - expect(ci_part).to match("my_ci") + cis = ci_part.split("/").last.split(",") + expect(cis).to include("ci", "gitlab", "my_ci", "travis") + expect(cis).not_to include("semaphore") end end end @@ -165,7 +167,7 @@ RSpec.describe Bundler::Fetcher do let(:version) { "1.3.17" } let(:platform) { "platform" } let(:downloader) { double("downloader") } - let(:body) { double(Net::HTTP::Get, :body => downloaded_data) } + let(:body) { double(Gem::Net::HTTP::Get, body: downloaded_data) } context "when attempting to load a Gem::Specification" do let(:spec) { Gem::Specification.new(name, version) } @@ -189,4 +191,70 @@ RSpec.describe Bundler::Fetcher do end end end + + describe "#specs_with_retry" do + let(:downloader) { double(:downloader) } + let(:remote) { double(:remote, cache_slug: "slug", uri: uri, original_uri: nil, anonymized_uri: uri) } + let(:compact_index) { double(Bundler::Fetcher::CompactIndex, available?: true, api_fetcher?: true) } + let(:dependency) { double(Bundler::Fetcher::Dependency, available?: true, api_fetcher?: true) } + let(:index) { double(Bundler::Fetcher::Index, available?: true, api_fetcher?: false) } + + before do + allow(Bundler::Fetcher::CompactIndex).to receive(:new).and_return(compact_index) + allow(Bundler::Fetcher::Dependency).to receive(:new).and_return(dependency) + allow(Bundler::Fetcher::Index).to receive(:new).and_return(index) + end + + it "picks the first fetcher that works" do + expect(compact_index).to receive(:specs).with("name").and_return([["name", "1.2.3", "ruby"]]) + expect(dependency).not_to receive(:specs) + expect(index).not_to receive(:specs) + fetcher.specs_with_retry("name", double(Bundler::Source::Rubygems)) + end + + context "when APIs are not available" do + before do + allow(compact_index).to receive(:available?).and_return(false) + allow(dependency).to receive(:available?).and_return(false) + end + + it "uses the index" do + expect(compact_index).not_to receive(:specs) + expect(dependency).not_to receive(:specs) + expect(index).to receive(:specs).with("name").and_return([["name", "1.2.3", "ruby"]]) + + fetcher.specs_with_retry("name", double(Bundler::Source::Rubygems)) + end + end + end + + describe "#api_fetcher?" do + let(:downloader) { double(:downloader) } + let(:remote) { double(:remote, cache_slug: "slug", uri: uri, original_uri: nil, anonymized_uri: uri) } + let(:compact_index) { double(Bundler::Fetcher::CompactIndex, available?: false, api_fetcher?: true) } + let(:dependency) { double(Bundler::Fetcher::Dependency, available?: false, api_fetcher?: true) } + let(:index) { double(Bundler::Fetcher::Index, available?: true, api_fetcher?: false) } + + before do + allow(Bundler::Fetcher::CompactIndex).to receive(:new).and_return(compact_index) + allow(Bundler::Fetcher::Dependency).to receive(:new).and_return(dependency) + allow(Bundler::Fetcher::Index).to receive(:new).and_return(index) + end + + context "when an api fetcher is available" do + before do + allow(compact_index).to receive(:available?).and_return(true) + end + + it "is truthy" do + expect(fetcher).to be_api_fetcher + end + end + + context "when only the index fetcher is available" do + it "is falsey" do + expect(fetcher).not_to be_api_fetcher + end + end + end end diff --git a/spec/bundler/bundler/friendly_errors_spec.rb b/spec/bundler/bundler/friendly_errors_spec.rb index 37afe488f3..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,11 +19,11 @@ 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" } + bundle :install, env: { "DEBUG" => "true" } expect(err).to include("Failed to load #{home(".gemrc")}") end @@ -101,7 +102,7 @@ RSpec.describe Bundler, "friendly errors" do context "BundlerError" do it "Bundler.ui receive error" do error = Bundler::BundlerError.new - expect(Bundler.ui).to receive(:error).with(error.message, :wrap => true) + expect(Bundler.ui).to receive(:error).with(error.message, wrap: true) Bundler::FriendlyErrors.log_error(error) end end @@ -121,7 +122,7 @@ RSpec.describe Bundler, "friendly errors" do context "Gem::InvalidSpecificationException" do it "Bundler.ui receive error" do error = Gem::InvalidSpecificationException.new - expect(Bundler.ui).to receive(:error).with(error.message, :wrap => true) + expect(Bundler.ui).to receive(:error).with(error.message, wrap: true) Bundler::FriendlyErrors.log_error(error) end end @@ -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 7d955007ab..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,23 +275,23 @@ 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 context "succeeds" do - let(:repo) { build_git("foo", :bare => true) } + let(:repo) { build_git("foo", bare: true) } before do - sys_exec("git remote add origin #{file_uri_for(repo.path)}", :dir => app_path) - sys_exec('git commit -a -m "initial commit"', :dir => app_path) + 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 b5c0f69795..0e1b7c9cc8 100644 --- a/spec/bundler/bundler/gem_version_promoter_spec.rb +++ b/spec/bundler/bundler/gem_version_promoter_spec.rb @@ -1,175 +1,162 @@ # frozen_string_literal: true RSpec.describe Bundler::GemVersionPromoter do - context "conservative resolver" do - def versions(result) - result.flatten.map(&:version).map(&:to_s) - end - + let(:gvp) { described_class.new } + + # Rightmost (highest array index) in result is most preferred. + # Leftmost (lowest array index) in result is least preferred. + # `build_candidates` has all versions of gem in index. + # `build_spec` is the version currently in the .lock file. + # + # In default (not strict) mode, all versions in the index will + # be returned, allowing Bundler the best chance to resolve all + # dependencies, but sometimes resulting in upgrades that some + # would not consider conservative. + + describe "#sort_versions" do def build_candidates(versions) versions.map do |v| Bundler::Resolver::Candidate.new(v) end end - def build_spec_set(name, v) - Bundler::SpecSet.new(build_spec(name, v)) + def build_package(name, version, unlock) + Bundler::Resolver::Package.new(name, [], locked_specs: Bundler::SpecSet.new(build_spec(name, version)), unlock: unlock) end - def build_package(name, platforms, locked_specs, unlock) - Bundler::Resolver::Package.new(name, platforms, :locked_specs => locked_specs, :unlock => unlock) + def sorted_versions(candidates:, current:, unlock: true) + gvp.sort_versions( + build_package("foo", current, unlock), + build_candidates(candidates) + ).flatten.map(&:version).map(&:to_s) end - # Rightmost (highest array index) in result is most preferred. - # Leftmost (lowest array index) in result is least preferred. - # `build_candidates` has all versions of gem in index. - # `build_spec` is the version currently in the .lock file. - # - # In default (not strict) mode, all versions in the index will - # be returned, allowing Bundler the best chance to resolve all - # dependencies, but sometimes resulting in upgrades that some - # would not consider conservative. - context "filter specs (strict) level patch" do - let(:gvp) do - Bundler::GemVersionPromoter.new.tap do |gvp| - gvp.level = :patch - gvp.strict = true - end - end + it "numerically sorts versions" do + versions = sorted_versions(candidates: %w[1.7.7 1.7.8 1.7.9 1.7.15 1.8.0], current: "1.7.8") + expect(versions).to eq %w[1.8.0 1.7.15 1.7.9 1.7.8 1.7.7] + end - it "when keeping build_spec, keep current, next release" do - res = gvp.sort_versions( - build_package("foo", [], build_spec_set("foo", "1.7.8"), []), - build_candidates(%w[1.7.8 1.7.9 1.8.0]) - ) - expect(versions(res)).to eq %w[1.7.8 1.7.9] + context "with no options" do + it "defaults to level=:major, strict=false, pre=false" do + versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") + expect(versions).to eq %w[2.1.0 2.0.1 1.0.0 0.9.0 0.3.1 0.3.0 0.2.0] end + end - it "when unlocking prefer next release first" do - res = gvp.sort_versions( - build_package("foo", [], build_spec_set("foo", "1.7.8"), []), - build_candidates(%w[1.7.8 1.7.9 1.8.0]) - ) - expect(versions(res)).to eq %w[1.7.8 1.7.9] - end + context "when strict" do + before { gvp.strict = true } - it "when unlocking keep current when already at latest release" do - res = gvp.sort_versions( - build_package("foo", [], build_spec_set("foo", "1.7.9"), []), - build_candidates(%w[1.7.9 1.8.0 2.0.0]) - ) - expect(versions(res)).to eq %w[1.7.9] - end - end + context "when level is major" do + before { gvp.level = :major } - context "filter specs (strict) level minor" do - let(:gvp) do - Bundler::GemVersionPromoter.new.tap do |gvp| - gvp.level = :minor - gvp.strict = true + it "keeps downgrades" do + versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") + expect(versions).to eq %w[2.1.0 2.0.1 1.0.0 0.9.0 0.3.1 0.3.0 0.2.0] end end - it "when unlocking favor next releases, remove minor and major increases" do - res = gvp.sort_versions( - build_package("foo", [], build_spec_set("foo", "0.2.0"), []), - build_candidates(%w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.0 2.0.1]) - ) - expect(versions(res)).to eq %w[0.2.0 0.3.0 0.3.1 0.9.0] + context "when level is minor" do + before { gvp.level = :minor } + + it "sorts highest minor within same major in first position" do + versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") + expect(versions).to eq %w[0.9.0 0.3.1 0.3.0 1.0.0 2.1.0 2.0.1 0.2.0] + end end - it "when keep locked, keep current, then favor next release, remove minor and major increases" do - res = gvp.sort_versions( - build_package("foo", [], build_spec_set("foo", "0.2.0"), ["bar"]), - build_candidates(%w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.0 2.0.1]) - ) - expect(versions(res)).to eq %w[0.3.0 0.3.1 0.9.0 0.2.0] + context "when level is patch" do + before { gvp.level = :patch } + + it "sorts highest patch within same minor in first position" do + versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") + expect(versions).to eq %w[0.3.1 0.3.0 0.9.0 1.0.0 2.0.1 2.1.0 0.2.0] + end end end - context "sort specs (not strict) level patch" do - let(:gvp) do - Bundler::GemVersionPromoter.new.tap do |gvp| - gvp.level = :patch - gvp.strict = false + context "when not strict" do + before { gvp.strict = false } + + context "when level is major" do + before { gvp.level = :major } + + it "orders by version" do + versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") + expect(versions).to eq %w[2.1.0 2.0.1 1.0.0 0.9.0 0.3.1 0.3.0 0.2.0] end end - it "when not unlocking, same order but make sure build_spec version is most preferred to stay put" do - res = gvp.sort_versions( - build_package("foo", [], build_spec_set("foo", "1.7.7"), ["bar"]), - build_candidates(%w[1.5.4 1.6.5 1.7.6 1.7.7 1.7.8 1.7.9 1.8.0 1.8.1 2.0.0 2.0.1]) - ) - expect(versions(res)).to eq %w[1.5.4 1.6.5 1.7.6 2.0.0 2.0.1 1.8.0 1.8.1 1.7.8 1.7.9 1.7.7] - end + context "when level is minor" do + before { gvp.level = :minor } - it "when unlocking favor next release, then current over minor increase" do - res = gvp.sort_versions( - build_package("foo", [], build_spec_set("foo", "1.7.8"), []), - build_candidates(%w[1.7.7 1.7.8 1.7.9 1.8.0]) - ) - expect(versions(res)).to eq %w[1.7.7 1.8.0 1.7.8 1.7.9] + it "favors minor upgrades, then patch upgrades, then major upgrades, then downgrades" do + versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") + expect(versions).to eq %w[0.9.0 0.3.1 0.3.0 1.0.0 2.1.0 2.0.1 0.2.0] + end end - it "when unlocking do proper integer comparison, not string" do - res = gvp.sort_versions( - build_package("foo", [], build_spec_set("foo", "1.7.8"), []), - build_candidates(%w[1.7.7 1.7.8 1.7.9 1.7.15 1.8.0]) - ) - expect(versions(res)).to eq %w[1.7.7 1.8.0 1.7.8 1.7.9 1.7.15] - end + context "when level is patch" do + before { gvp.level = :patch } - it "leave current when unlocking but already at latest release" do - res = gvp.sort_versions( - build_package("foo", [], build_spec_set("foo", "1.7.9"), []), - build_candidates(%w[1.7.9 1.8.0 2.0.0]) - ) - expect(versions(res)).to eq %w[2.0.0 1.8.0 1.7.9] + it "favors patch upgrades, then minor upgrades, then major upgrades, then downgrades" do + versions = sorted_versions(candidates: %w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.1 2.1.0], current: "0.3.0") + expect(versions).to eq %w[0.3.1 0.3.0 0.9.0 1.0.0 2.0.1 2.1.0 0.2.0] + end end end - context "sort specs (not strict) level minor" do - let(:gvp) do - Bundler::GemVersionPromoter.new.tap do |gvp| - gvp.level = :minor - gvp.strict = false - end + context "when pre" do + before { gvp.pre = true } + + it "sorts regardless of prerelease status" do + versions = sorted_versions(candidates: %w[1.7.7.pre 1.8.0 1.8.1.pre 1.8.1 2.0.0.pre 2.0.0], current: "1.8.0") + expect(versions).to eq %w[2.0.0 2.0.0.pre 1.8.1 1.8.1.pre 1.8.0 1.7.7.pre] end + end + + context "when not pre" do + before { gvp.pre = false } - it "when unlocking favor next release, then minor increase over current" do - res = gvp.sort_versions( - build_package("foo", [], build_spec_set("foo", "0.2.0"), []), - build_candidates(%w[0.2.0 0.3.0 0.3.1 0.9.0 1.0.0 2.0.0 2.0.1]) - ) - expect(versions(res)).to eq %w[2.0.0 2.0.1 1.0.0 0.2.0 0.3.0 0.3.1 0.9.0] + it "deprioritizes prerelease gems" do + versions = sorted_versions(candidates: %w[1.7.7.pre 1.8.0 1.8.1.pre 1.8.1 2.0.0.pre 2.0.0], current: "1.8.0") + expect(versions).to eq %w[2.0.0 1.8.1 1.8.0 2.0.0.pre 1.8.1.pre 1.7.7.pre] end end - context "level error handling" do - subject { Bundler::GemVersionPromoter.new } + context "when locking and not major" do + before { gvp.level = :minor } - it "should raise if not major, minor or patch is passed" do - expect { subject.level = :minjor }.to raise_error ArgumentError + 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 - it "should raise if invalid classes passed" do - [123, nil].each do |value| - expect { subject.level = value }.to raise_error ArgumentError - end + describe "#level=" do + subject { described_class.new } + + it "should raise if not major, minor or patch is passed" do + expect { subject.level = :minjor }.to raise_error ArgumentError + end + + it "should raise if invalid classes passed" do + [123, nil].each do |value| + expect { subject.level = value }.to raise_error ArgumentError end + end - it "should accept major, minor patch symbols" do - [:major, :minor, :patch].each do |value| - subject.level = value - expect(subject.level).to eq value - end + it "should accept major, minor patch symbols" do + [:major, :minor, :patch].each do |value| + subject.level = value + expect(subject.level).to eq value end + end - it "should accept major, minor patch strings" do - %w[major minor patch].each do |value| - subject.level = value - expect(subject.level).to eq value.to_sym - end + it "should accept major, minor patch strings" do + %w[major minor patch].each do |value| + subject.level = value + expect(subject.level).to eq value.to_sym end end end diff --git a/spec/bundler/bundler/installer/gem_installer_spec.rb b/spec/bundler/bundler/installer/gem_installer_spec.rb index e86bdf009a..dbd4e1d2c8 100644 --- a/spec/bundler/bundler/installer/gem_installer_spec.rb +++ b/spec/bundler/bundler/installer/gem_installer_spec.rb @@ -3,10 +3,11 @@ require "bundler/installer/gem_installer" RSpec.describe Bundler::GemInstaller do - let(:definition) { instance_double("Definition", :locked_gems => nil) } - let(:installer) { instance_double("Installer", :definition => definition) } + let(:definition) { instance_double("Definition", locked_gems: nil) } + let(:installer) { instance_double("Installer", definition: definition) } let(:spec_source) { instance_double("SpecSource") } - let(:spec) { instance_double("Specification", :name => "dummy", :version => "0.0.1", :loaded_from => "dummy", :source => spec_source) } + let(:spec) { instance_double("Specification", name: "dummy", version: "0.0.1", loaded_from: "dummy", source: spec_source) } + 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 index c8403a2e38..49bcb5310b 100644 --- a/spec/bundler/bundler/installer/parallel_installer_spec.rb +++ b/spec/bundler/bundler/installer/parallel_installer_spec.rb @@ -1,46 +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 - let(:installer) { instance_double("Installer") } - let(:all_specs) { [] } - let(:size) { 1 } - let(:standalone) { false } - let(:force) { false } - - subject { described_class.new(installer, all_specs, size, standalone, force) } - - context "when the spec set is not a valid resolution" do - let(:all_specs) do - [ - build_spec("cucumber", "4.1.0") {|s| s.runtime "diff-lcs", "< 1.4" }, - build_spec("diff-lcs", "1.4.4"), - ].flatten + 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 - it "prints a warning" do - expect(Bundler.ui).to receive(:warn).with(<<-W.strip) -Your lockfile doesn't include a valid resolution. -You can fix this by regenerating your lockfile or manually editing the bad locked gems to a version that satisfies all dependencies. -The unmet dependencies are: -* diff-lcs (< 1.4), dependency of cucumber-4.1.0, unsatisfied by diff-lcs-1.4.4 - W - subject.check_for_unmet_dependencies + after do + Bundler.ui = @old_ui + Gem::Request::ConnectionPools.client = @previous_client + Artifice.deactivate end - end - context "when the spec set is a valid resolution" do - let(:all_specs) do - [ - build_spec("cucumber", "4.1.0") {|s| s.runtime "diff-lcs", "< 1.4" }, - build_spec("diff-lcs", "1.3"), - ].flatten + 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 - it "doesn't print a warning" do - expect(Bundler.ui).not_to receive(:warn) - subject.check_for_unmet_dependencies + 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 e63ef26cb3..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,27 +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) - expect(spec.dependencies_installed?(all_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) - expect(spec.dependencies_installed?(all_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 3a6d61336f..7364ab98e5 100644 --- a/spec/bundler/bundler/lockfile_parser_spec.rb +++ b/spec/bundler/bundler/lockfile_parser_spec.rb @@ -3,7 +3,7 @@ require "bundler/lockfile_parser" RSpec.describe Bundler::LockfileParser do - let(:lockfile_contents) { strip_whitespace(<<-L) } + let(:lockfile_contents) { <<~L } GIT remote: https://github.com/alloy/peiji-san.git revision: eca485d8dc95f12aaec1a434b49d295c7e91844b @@ -22,6 +22,9 @@ RSpec.describe Bundler::LockfileParser do peiji-san! rake + CHECKSUMS + rake (10.3.2) sha256=814828c34f1315d7e7b7e8295184577cc4e969bad6156ac069d02d63f58d82e8 + RUBY VERSION ruby 2.1.3p242 @@ -33,13 +36,13 @@ RSpec.describe Bundler::LockfileParser do it "returns the attributes" do attributes = described_class.sections_in_lockfile(lockfile_contents) expect(attributes).to contain_exactly( - "BUNDLED WITH", "DEPENDENCIES", "GEM", "GIT", "PLATFORMS", "RUBY VERSION" + "BUNDLED WITH", "CHECKSUMS", "DEPENDENCIES", "GEM", "GIT", "PLATFORMS", "RUBY VERSION" ) end end describe ".unknown_sections_in_lockfile" do - let(:lockfile_contents) { strip_whitespace(<<-L) } + let(:lockfile_contents) { <<~L } UNKNOWN ATTR UNKNOWN ATTR 2 @@ -60,7 +63,7 @@ RSpec.describe Bundler::LockfileParser do it "returns the same as > 1.0" do expect(subject).to contain_exactly( - described_class::BUNDLED, described_class::RUBY, described_class::PLUGIN + described_class::BUNDLED, described_class::CHECKSUMS, described_class::RUBY, described_class::PLUGIN ) end end @@ -70,7 +73,7 @@ RSpec.describe Bundler::LockfileParser do it "returns the same as for the release version" do expect(subject).to contain_exactly( - described_class::RUBY, described_class::PLUGIN + described_class::CHECKSUMS, described_class::RUBY, described_class::PLUGIN ) end end @@ -108,16 +111,25 @@ 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) } + let(:rake_sha256_checksum) do + Bundler::Checksum.from_lock( + "sha256=814828c34f1315d7e7b7e8295184577cc4e969bad6156ac069d02d63f58d82e8", + "#{lockfile_path}:20:17" + ) + end + let(:rake_checksums) { [rake_sha256_checksum] } shared_examples_for "parsing" do it "parses correctly" do + expect(subject.valid?).to be(true) expect(subject.sources).to eq sources expect(subject.dependencies).to eq dependencies expect(subject.specs).to eq specs @@ -125,6 +137,9 @@ RSpec.describe Bundler::LockfileParser do expect(subject.platforms).to eq platforms expect(subject.bundler_version).to eq bundler_version expect(subject.ruby_version).to eq ruby_version + rake_spec = specs.last + checksums = subject.sources.last.checksum_store.to_lock(specs.last) + expect(checksums).to eq("#{rake_spec.lock_name} #{rake_checksums.map(&:to_lock).sort.join(",")}") end end @@ -149,5 +164,140 @@ RSpec.describe Bundler::LockfileParser do let(:lockfile_contents) { super().sub("peiji-san!", "peiji-san!\n foo: bar") } include_examples "parsing" end + + context "when the checksum is urlsafe base64 encoded" do + let(:lockfile_contents) do + super().sub( + "sha256=814828c34f1315d7e7b7e8295184577cc4e969bad6156ac069d02d63f58d82e8", + "sha256=gUgow08TFdfnt-gpUYRXfMTpabrWFWrAadAtY_WNgug=" + ) + end + include_examples "parsing" + end + + context "when the checksum is of an unknown algorithm" do + let(:rake_sha512_checksum) do + Bundler::Checksum.from_lock( + "sha512=pVDn9GLmcFkz8vj1ueiVxj5uGKkAyaqYjEX8zG6L5O4BeVg3wANaKbQdpj/B82Nd/MHVszy6polHcyotUdwilQ==", + "#{lockfile_path}:20:17" + ) + end + let(:lockfile_contents) do + super().sub( + "sha256=", + "sha512=pVDn9GLmcFkz8vj1ueiVxj5uGKkAyaqYjEX8zG6L5O4BeVg3wANaKbQdpj/B82Nd/MHVszy6polHcyotUdwilQ==,sha256=" + ) + end + let(:rake_checksums) { [rake_sha256_checksum, rake_sha512_checksum] } + include_examples "parsing" + end + + context "when 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 } + + it "raises a security error" do + expect { subject }.to raise_error(Bundler::SecurityError) do |e| + expect(e.message).to match <<~MESSAGE + Bundler found mismatched checksums. This is a potential security risk. + rake (10.3.2) #{bad_checksum} + from the lockfile CHECKSUMS at #{lockfile_path}:20:17 + rake (10.3.2) #{rake_sha256_checksum.to_lock} + from the lockfile CHECKSUMS at #{lockfile_path}:21:17 + + To resolve this issue you can either: + 1. remove the matching checksum in #{lockfile_path}:21:17 + 2. run `bundle install` + or if you are sure that the new checksum from the lockfile CHECKSUMS at #{lockfile_path}:21:17 is correct: + 1. remove the matching checksum in #{lockfile_path}:20:17 + 2. run `bundle install` + + To ignore checksum security warnings, disable checksum validation with + `bundle config set --local disable_checksum_validation true` + MESSAGE + end + end + end end end diff --git a/spec/bundler/bundler/mirror_spec.rb b/spec/bundler/bundler/mirror_spec.rb index 1eaf1e9a8e..ba1c6ed413 100644 --- a/spec/bundler/bundler/mirror_spec.rb +++ b/spec/bundler/bundler/mirror_spec.rb @@ -36,12 +36,12 @@ RSpec.describe Bundler::Settings::Mirror do it "takes a string for the uri but returns an uri object" do mirror.uri = "http://localhost:9292" - expect(mirror.uri).to eq(Bundler::URI("http://localhost:9292")) + expect(mirror.uri).to eq(Gem::URI("http://localhost:9292")) end it "takes an uri object for the uri" do - mirror.uri = Bundler::URI("http://localhost:9293") - expect(mirror.uri).to eq(Bundler::URI("http://localhost:9293")) + mirror.uri = Gem::URI("http://localhost:9293") + expect(mirror.uri).to eq(Gem::URI("http://localhost:9293")) end context "without a uri" do @@ -145,7 +145,7 @@ RSpec.describe Bundler::Settings::Mirror do end RSpec.describe Bundler::Settings::Mirrors do - let(:localhost_uri) { Bundler::URI("http://localhost:9292") } + let(:localhost_uri) { Gem::URI("http://localhost:9292") } context "with a just created mirror" do let(:mirrors) do @@ -260,7 +260,7 @@ RSpec.describe Bundler::Settings::Mirrors do before { mirrors.parse("mirror.all.fallback_timeout", "true") } it "returns the source uri, not localhost" do - expect(mirrors.for("http://whatever.com").uri).to eq(Bundler::URI("http://whatever.com/")) + expect(mirrors.for("http://whatever.com").uri).to eq(Gem::URI("http://whatever.com/")) end end end @@ -270,7 +270,7 @@ RSpec.describe Bundler::Settings::Mirrors do context "without a fallback timeout" do it "returns the uri that is not mirrored" do - expect(mirrors.for("http://whatever.com").uri).to eq(Bundler::URI("http://whatever.com/")) + expect(mirrors.for("http://whatever.com").uri).to eq(Gem::URI("http://whatever.com/")) end it "returns localhost for rubygems.org" do @@ -282,11 +282,11 @@ RSpec.describe Bundler::Settings::Mirrors do before { mirrors.parse("mirror.http://rubygems.org/.fallback_timeout", "true") } it "returns the uri that is not mirrored" do - expect(mirrors.for("http://whatever.com").uri).to eq(Bundler::URI("http://whatever.com/")) + expect(mirrors.for("http://whatever.com").uri).to eq(Gem::URI("http://whatever.com/")) end it "returns rubygems.org for rubygems.org" do - expect(mirrors.for("http://rubygems.org/").uri).to eq(Bundler::URI("http://rubygems.org/")) + expect(mirrors.for("http://rubygems.org/").uri).to eq(Gem::URI("http://rubygems.org/")) end end end diff --git a/spec/bundler/bundler/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/api/source_spec.rb b/spec/bundler/bundler/plugin/api/source_spec.rb index 428ceb220a..ae02e08bea 100644 --- a/spec/bundler/bundler/plugin/api/source_spec.rb +++ b/spec/bundler/bundler/plugin/api/source_spec.rb @@ -51,7 +51,7 @@ RSpec.describe Bundler::Plugin::API::Source do context "to_lock" do it "returns the string with remote and type" do - expected = strip_whitespace <<-L + expected = <<~L PLUGIN SOURCE remote: #{uri} type: #{type} @@ -67,7 +67,7 @@ RSpec.describe Bundler::Plugin::API::Source do end it "includes them" do - expected = strip_whitespace <<-L + expected = <<~L PLUGIN SOURCE remote: #{uri} type: #{type} diff --git a/spec/bundler/bundler/plugin/dsl_spec.rb b/spec/bundler/bundler/plugin/dsl_spec.rb index 00e39dca69..235a549735 100644 --- a/spec/bundler/bundler/plugin/dsl_spec.rb +++ b/spec/bundler/bundler/plugin/dsl_spec.rb @@ -23,7 +23,7 @@ RSpec.describe Bundler::Plugin::DSL do it "adds #source with :type to list and also inferred_plugins list" do expect(dsl).to receive(:plugin).with("bundler-source-news").once - dsl.source("some_random_url", :type => "news") {} + dsl.source("some_random_url", type: "news") {} expect(dsl.inferred_plugins).to eq(["bundler-source-news"]) end @@ -31,8 +31,8 @@ RSpec.describe Bundler::Plugin::DSL do it "registers a source type plugin only once for multiple declarations" do expect(dsl).to receive(:plugin).with("bundler-source-news").and_call_original.once - dsl.source("some_random_url", :type => "news") {} - dsl.source("another_random_url", :type => "news") {} + dsl.source("some_random_url", type: "news") {} + dsl.source("another_random_url", type: "news") {} end end end diff --git a/spec/bundler/bundler/plugin/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 2c50ee5afc..c200a98afa 100644 --- a/spec/bundler/bundler/plugin/installer_spec.rb +++ b/spec/bundler/bundler/plugin/installer_spec.rb @@ -6,7 +6,6 @@ RSpec.describe Bundler::Plugin::Installer do describe "cli install" do it "uses Gem.sources when non of the source is provided" do sources = double(:sources) - Bundler.settings # initialize it before we have to touch rubygems.ext_lock allow(Gem).to receive(:sources) { sources } allow(installer).to receive(:install_rubygems). @@ -21,15 +20,15 @@ RSpec.describe Bundler::Plugin::Installer do allow(installer).to receive(:install_git). and_return("new-plugin" => spec) - expect(installer.install(["new-plugin"], :git => "https://some.ran/dom")). + expect(installer.install(["new-plugin"], git: "https://some.ran/dom")). to eq("new-plugin" => spec) end it "returns the installed spec after installing local git plugins" do - allow(installer).to receive(:install_local_git). + allow(installer).to receive(:install_git). and_return("new-plugin" => spec) - expect(installer.install(["new-plugin"], :local_git => "/phony/path/repo")). + expect(installer.install(["new-plugin"], git: "/phony/path/repo")). to eq("new-plugin" => spec) end @@ -37,7 +36,7 @@ RSpec.describe Bundler::Plugin::Installer do allow(installer).to receive(:install_rubygems). and_return("new-plugin" => spec) - expect(installer.install(["new-plugin"], :source => "https://some.ran/dom")). + expect(installer.install(["new-plugin"], source: "https://some.ran/dom")). to eq("new-plugin" => spec) end end @@ -48,17 +47,24 @@ 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 before do - build_git "ga-plugin", :path => lib_path("ga-plugin") do |s| + build_git "ga-plugin", path: lib_path("ga-plugin") do |s| s.write "plugins.rb" end end let(:result) do - installer.install(["ga-plugin"], :git => file_uri_for(lib_path("ga-plugin"))) + installer.install(["ga-plugin"], git: lib_path("ga-plugin").to_s) end it "returns the installed spec after installing" do @@ -75,13 +81,13 @@ RSpec.describe Bundler::Plugin::Installer do context "local git plugins" do before do - build_git "ga-plugin", :path => lib_path("ga-plugin") do |s| + build_git "ga-plugin", path: lib_path("ga-plugin") do |s| s.write "plugins.rb" end end let(:result) do - installer.install(["ga-plugin"], :local_git => lib_path("ga-plugin").to_s) + installer.install(["ga-plugin"], git: lib_path("ga-plugin").to_s) end it "returns the installed spec after installing" do @@ -98,7 +104,7 @@ RSpec.describe Bundler::Plugin::Installer do context "rubygems plugins" do let(:result) do - installer.install(["re-plugin"], :source => file_uri_for(gem_repo2)) + installer.install(["re-plugin"], source: file_uri_for(gem_repo2)) end it "returns the installed spec after installing " do @@ -113,7 +119,7 @@ RSpec.describe Bundler::Plugin::Installer do context "multiple plugins" do let(:result) do - installer.install(["re-plugin", "ma-plugin"], :source => file_uri_for(gem_repo2)) + installer.install(["re-plugin", "ma-plugin"], source: file_uri_for(gem_repo2)) end it "returns the installed spec after installing " do diff --git a/spec/bundler/bundler/plugin_spec.rb b/spec/bundler/bundler/plugin_spec.rb index d28479cf31..b379594c6f 100644 --- a/spec/bundler/bundler/plugin_spec.rb +++ b/spec/bundler/bundler/plugin_spec.rb @@ -9,11 +9,11 @@ RSpec.describe Bundler::Plugin do let(:spec2) { double(:spec2) } before do - build_lib "new-plugin", :path => lib_path("new-plugin") do |s| + build_lib "new-plugin", path: lib_path("new-plugin") do |s| s.write "plugins.rb" end - build_lib "another-plugin", :path => lib_path("another-plugin") do |s| + build_lib "another-plugin", path: lib_path("another-plugin") do |s| s.write "plugins.rb" end @@ -65,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 @@ -225,7 +225,7 @@ RSpec.describe Bundler::Plugin do end end - describe "#source_from_lock" do + describe "#from_lock" do it "returns instance of registered class initialized with locked opts" do opts = { "type" => "l_source", "remote" => "xyz", "other" => "random" } allow(index).to receive(:source_plugin).with("l_source") { "plugin_name" } @@ -236,7 +236,7 @@ RSpec.describe Bundler::Plugin do expect(SClass).to receive(:new). with(hash_including("type" => "l_source", "uri" => "xyz", "other" => "random")) { s_instance } - expect(subject.source_from_lock(opts)).to be(s_instance) + expect(subject.from_lock(opts)).to be(s_instance) end end @@ -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 @@ -275,10 +275,11 @@ RSpec.describe Bundler::Plugin do describe "#hook" do before do path = lib_path("foo-plugin") - build_lib "foo-plugin", :path => path do |s| + build_lib "foo-plugin", path: path do |s| s.write "plugins.rb", code end + @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/remote_specification_spec.rb b/spec/bundler/bundler/remote_specification_spec.rb index 921a47a2d3..f35b231d58 100644 --- a/spec/bundler/bundler/remote_specification_spec.rb +++ b/spec/bundler/bundler/remote_specification_spec.rb @@ -17,7 +17,7 @@ RSpec.describe Bundler::RemoteSpecification do end describe "#fetch_platform" do - let(:remote_spec) { double(:remote_spec, :platform => "jruby") } + let(:remote_spec) { double(:remote_spec, platform: "jruby") } before { allow(spec_fetcher).to receive(:fetch_spec).and_return(remote_spec) } @@ -113,7 +113,7 @@ RSpec.describe Bundler::RemoteSpecification do context "comparing a non sortable object" do let(:other) { Object.new } - let(:remote_spec) { double(:remote_spec, :platform => "jruby") } + let(:remote_spec) { double(:remote_spec, platform: "jruby") } before do allow(spec_fetcher).to receive(:fetch_spec).and_return(remote_spec) @@ -127,8 +127,8 @@ RSpec.describe Bundler::RemoteSpecification do end describe "#__swap__" do - let(:spec) { double(:spec, :dependencies => []) } - let(:new_spec) { double(:new_spec, :dependencies => [], :runtime_dependencies => []) } + let(:spec) { double(:spec, dependencies: []) } + let(:new_spec) { double(:new_spec, dependencies: [], runtime_dependencies: []) } before { subject.instance_variable_set(:@_remote_specification, spec) } @@ -157,7 +157,7 @@ RSpec.describe Bundler::RemoteSpecification do describe "method missing" do context "and is present in Gem::Specification" do - let(:remote_spec) { double(:remote_spec, :authors => "abcd") } + let(:remote_spec) { double(:remote_spec, authors: "abcd") } before do allow(subject).to receive(:_remote_specification).and_return(remote_spec) @@ -172,7 +172,7 @@ RSpec.describe Bundler::RemoteSpecification do describe "respond to missing?" do context "and is present in Gem::Specification" do - let(:remote_spec) { double(:remote_spec, :authors => "abcd") } + let(:remote_spec) { double(:remote_spec, authors: "abcd") } before do allow(subject).to receive(:_remote_specification).and_return(remote_spec) diff --git a/spec/bundler/bundler/resolver/candidate_spec.rb b/spec/bundler/bundler/resolver/candidate_spec.rb index cd52c867c4..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 bc1ca98457..45a37c5795 100644 --- a/spec/bundler/bundler/ruby_dsl_spec.rb +++ b/spec/bundler/bundler/ruby_dsl_spec.rb @@ -7,28 +7,37 @@ RSpec.describe Bundler::RubyDsl do include Bundler::RubyDsl attr_reader :ruby_version + attr_accessor :gemfile end let(:dsl) { MockDSL.new } let(:ruby_version) { "2.0.0" } + let(:ruby_version_arg) { ruby_version } let(:version) { "2.0.0" } let(:engine) { "jruby" } let(:engine_version) { "9000" } let(:patchlevel) { "100" } let(:options) do - { :patchlevel => patchlevel, - :engine => engine, - :engine_version => engine_version } + { patchlevel: patchlevel, + engine: engine, + engine_version: engine_version } end + let(:project_root) { Pathname.new("/path/to/project") } + let(:gemfile) { project_root.join("Gemfile") } + before { allow(Bundler).to receive(:root).and_return(project_root) } let(:invoke) do proc do - args = Array(ruby_version) + [options] + args = [] + args << ruby_version_arg if ruby_version_arg + args << options + dsl.ruby(*args) end end subject do + dsl.gemfile = gemfile invoke.call dsl.ruby_version end @@ -59,10 +68,19 @@ RSpec.describe Bundler::RubyDsl do it_behaves_like "it stores the ruby version" end + context "with a preview version" do + let(:ruby_version) { "3.3.0-preview2" } + + it "stores the version" do + expect(subject.versions).to eq(Array("3.3.0.preview2")) + expect(subject.gem_version.version).to eq("3.3.0.preview2") + end + end + context "with two requirements in the same string" do let(:ruby_version) { ">= 2.0.0, < 3.0" } it "raises an error" do - expect { subject }.to raise_error(ArgumentError) + expect { subject }.to raise_error(Bundler::InvalidArgumentError) end end @@ -91,5 +109,140 @@ RSpec.describe Bundler::RubyDsl do it_behaves_like "it stores the ruby version" end end + + context "with a file option" do + let(:file) { ".ruby-version" } + let(:ruby_version_file_path) { gemfile.dirname.join(file) } + let(:options) do + { file: file, + patchlevel: patchlevel, + engine: engine, + engine_version: engine_version } + end + let(:ruby_version_arg) { nil } + let(:file_content) { "#{version}\n" } + + before do + allow(Bundler).to receive(:read_file) do |path| + raise Errno::ENOENT, <<~ERROR unless path == ruby_version_file_path + #{file} not found in specs: + expected: #{ruby_version_file_path} + received: #{path} + ERROR + file_content + end + end + + it_behaves_like "it stores the ruby version" + + context "with the Gemfile ruby file: path is relative to the Gemfile in a subdir" do + let(:gemfile) { project_root.join("subdir", "Gemfile") } + let(:file) { "../.ruby-version" } + let(:ruby_version_file_path) { gemfile.dirname.join(file) } + + it_behaves_like "it stores the ruby version" + end + + context "with bundler root in a subdir of the project" do + let(:project_root) { Pathname.new("/path/to/project/subdir") } + let(:gemfile) { project_root.parent.join("Gemfile") } + + it_behaves_like "it stores the ruby version" + end + + context "with the ruby- prefix in the file" do + let(:file_content) { "ruby-#{version}\n" } + + it_behaves_like "it stores the ruby version" + end + + context "and a version" do + let(:ruby_version_arg) { version } + + it "raises an error" do + expect { subject }.to raise_error(Bundler::GemfileError, "Do not pass version argument when using :file option") + end + end + + context "with a @gemset" do + let(:file_content) { "ruby-#{version}@gemset\n" } + + it "raises an error" do + expect { subject }.to raise_error(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 + + context "with a .tool-versions file format" do + let(:file) { ".tool-versions" } + let(:ruby_version_arg) { nil } + let(:file_content) do + <<~TOOLS + nodejs 18.16.0 + ruby #{version} # This is a comment + pnpm 8.6.12 + TOOLS + end + + it_behaves_like "it stores the ruby version" + + context "with extra spaces and a very cozy comment" do + let(:file_content) do + <<~TOOLS + nodejs 18.16.0 + ruby #{version}# This is a cozy comment + pnpm 8.6.12 + TOOLS + end + + it_behaves_like "it stores the ruby version" + end + end + + 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 f1df12294d..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 @@ -400,19 +400,19 @@ RSpec.describe "Bundler::RubyVersion and its subclasses" do let(:bundler_system_ruby_version) { subject } around do |example| - if Bundler::RubyVersion.instance_variable_defined?("@ruby_version") + if Bundler::RubyVersion.instance_variable_defined?("@system") begin - old_ruby_version = Bundler::RubyVersion.instance_variable_get("@ruby_version") - Bundler::RubyVersion.remove_instance_variable("@ruby_version") + old_ruby_version = Bundler::RubyVersion.instance_variable_get("@system") + Bundler::RubyVersion.remove_instance_variable("@system") example.run ensure - Bundler::RubyVersion.instance_variable_set("@ruby_version", old_ruby_version) + Bundler::RubyVersion.instance_variable_set("@system", old_ruby_version) end else begin example.run ensure - Bundler::RubyVersion.remove_instance_variable("@ruby_version") + Bundler::RubyVersion.remove_instance_variable("@system") end end end 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 182aa3646a..a2c63a7ca0 100644 --- a/spec/bundler/bundler/rubygems_integration_spec.rb +++ b/spec/bundler/bundler/rubygems_integration_spec.rb @@ -1,10 +1,6 @@ # frozen_string_literal: true RSpec.describe Bundler::RubygemsIntegration do - it "uses the same chdir lock as rubygems" do - expect(Bundler.rubygems.ext_lock).to eq(Gem::Ext::Builder::CHDIR_MONITOR) - end - context "#validate" do let(:spec) do Gem::Specification.new do |s| @@ -15,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 @@ -36,7 +32,6 @@ RSpec.describe Bundler::RubygemsIntegration do describe "#download_gem" do let(:bundler_retry) { double(Bundler::Retry) } - let(:uri) { Bundler::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")) @@ -45,15 +40,47 @@ RSpec.describe Bundler::RubygemsIntegration do end let(:fetcher) { double("gem_remote_fetcher") } - it "successfully downloads gem with retries" do - expect(Bundler.rubygems).to receive(:gem_remote_fetcher).and_return(fetcher) - expect(fetcher).to receive(:headers=).with({ "X-Gemfile-Source" => "https://foo.bar" }) - expect(Bundler::Retry).to receive(:new).with("download gem from #{uri}/"). - and_return(bundler_retry) - expect(bundler_retry).to receive(:attempts).and_yield - expect(fetcher).to receive(:cache_update_path) + context "when uri is public" do + let(:uri) { Gem::URI.parse("https://foo.bar") } + + 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") } - Bundler.rubygems.download_gem(spec, uri, cache_dir) + 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 @@ -64,40 +91,35 @@ RSpec.describe Bundler::RubygemsIntegration do let(:prerelease_specs_response) { Marshal.dump(["prerelease_specs"]) } context "when a rubygems source mirror is set" do - let(:orig_uri) { Bundler::URI("http://zombo.com") } - let(:remote_with_mirror) { double("remote", :uri => uri, :original_uri => orig_uri) } + let(:orig_uri) { Gem::URI("http://zombo.com") } + let(:remote_with_mirror) { double("remote", uri: uri, original_uri: orig_uri) } it "sets the 'X-Gemfile-Source' header containing the original source" do - expect(Bundler.rubygems).to receive(:gem_remote_fetcher).twice.and_return(fetcher) - expect(fetcher).to receive(:headers=).with({ "X-Gemfile-Source" => "http://zombo.com" }).twice expect(fetcher).to receive(:fetch_path).with(uri + "specs.4.8.gz").and_return(specs_response) expect(fetcher).to receive(:fetch_path).with(uri + "prerelease_specs.4.8.gz").and_return(prerelease_specs_response) - result = Bundler.rubygems.fetch_all_remote_specs(remote_with_mirror) + result = Bundler.rubygems.fetch_all_remote_specs(remote_with_mirror, fetcher) expect(result).to eq(%w[specs prerelease_specs]) end end context "when there is no rubygems source mirror set" do - let(:remote_no_mirror) { double("remote", :uri => uri, :original_uri => nil) } + let(:remote_no_mirror) { double("remote", uri: uri, original_uri: nil) } it "does not set the 'X-Gemfile-Source' header" do - expect(Bundler.rubygems).to receive(:gem_remote_fetcher).twice.and_return(fetcher) - expect(fetcher).to_not receive(:headers=) expect(fetcher).to receive(:fetch_path).with(uri + "specs.4.8.gz").and_return(specs_response) expect(fetcher).to receive(:fetch_path).with(uri + "prerelease_specs.4.8.gz").and_return(prerelease_specs_response) - result = Bundler.rubygems.fetch_all_remote_specs(remote_no_mirror) + result = Bundler.rubygems.fetch_all_remote_specs(remote_no_mirror, fetcher) expect(result).to eq(%w[specs prerelease_specs]) end end context "when loading an unexpected class" do - let(:remote_no_mirror) { double("remote", :uri => uri, :original_uri => nil) } + let(:remote_no_mirror) { double("remote", uri: uri, original_uri: nil) } let(:unexpected_specs_response) { Marshal.dump(3) } it "raises a MarshalError error" do - expect(Bundler.rubygems).to receive(:gem_remote_fetcher).once.and_return(fetcher) expect(fetcher).to receive(:fetch_path).with(uri + "specs.4.8.gz").and_return(unexpected_specs_response) - expect { Bundler.rubygems.fetch_all_remote_specs(remote_no_mirror) }.to raise_error(Bundler::MarshalError, /unexpected class/i) + expect { Bundler.rubygems.fetch_all_remote_specs(remote_no_mirror, fetcher) }.to raise_error(Bundler::MarshalError, /unexpected class/i) end end end diff --git a/spec/bundler/bundler/settings/validator_spec.rb b/spec/bundler/bundler/settings/validator_spec.rb index e4ffd89435..b252ba59a0 100644 --- a/spec/bundler/bundler/settings/validator_spec.rb +++ b/spec/bundler/bundler/settings/validator_spec.rb @@ -44,14 +44,14 @@ RSpec.describe Bundler::Settings::Validator do validate!("without", "b:c", "BUNDLE_WITH" => "a") end.not_to raise_error - expect { validate!("with", "b:c", "BUNDLE_WITHOUT" => "c:d") }.to raise_error Bundler::InvalidOption, strip_whitespace(<<-EOS).strip + expect { validate!("with", "b:c", "BUNDLE_WITHOUT" => "c:d") }.to raise_error Bundler::InvalidOption, <<~EOS.strip Setting `with` to "b:c" failed: - a group cannot be in both `with` & `without` simultaneously - `without` is current set to [:c, :d] - the `c` groups conflict EOS - expect { validate!("without", "b:c", "BUNDLE_WITH" => "c:d") }.to raise_error Bundler::InvalidOption, strip_whitespace(<<-EOS).strip + expect { validate!("without", "b:c", "BUNDLE_WITH" => "c:d") }.to raise_error Bundler::InvalidOption, <<~EOS.strip Setting `without` to "b:c" failed: - a group cannot be in both `with` & `without` simultaneously - `with` is current set to [:c, :d] @@ -74,7 +74,7 @@ RSpec.describe Bundler::Settings::Validator do describe "#fail!" do it "raises with a helpful message" do - expect { subject.fail!("key", "value", "reason1", "reason2") }.to raise_error Bundler::InvalidOption, strip_whitespace(<<-EOS).strip + expect { subject.fail!("key", "value", "reason1", "reason2") }.to raise_error Bundler::InvalidOption, <<~EOS.strip Setting `key` to "value" failed: - rule description - reason1 diff --git a/spec/bundler/bundler/settings_spec.rb b/spec/bundler/bundler/settings_spec.rb index 4636993d9f..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 @@ -131,7 +143,7 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow Bundler.settings.set_command_option :no_install, true - Bundler.settings.temporary(:no_install => false) do + Bundler.settings.temporary(no_install: false) do expect(Bundler.settings[:no_install]).to eq false end @@ -147,23 +159,24 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow context "when called without a block" do it "leaves the setting changed" do - Bundler.settings.temporary(:foo => :random) + Bundler.settings.temporary(foo: :random) expect(Bundler.settings[:foo]).to eq "random" end it "returns nil" do - expect(Bundler.settings.temporary(:foo => :bar)).to be_nil + expect(Bundler.settings.temporary(foo: :bar)).to be_nil end end end 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 @@ -179,7 +192,7 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow end describe "#mirror_for" do - let(:uri) { Bundler::URI("https://rubygems.org/") } + let(:uri) { Gem::URI("https://rubygems.org/") } context "with no configured mirror" do it "returns the original URI" do @@ -192,7 +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) { Bundler::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 } @@ -213,7 +226,7 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow end context "with a file URI" do - let(:mirror_uri) { Bundler::URI("file:/foo/BAR/baz/qUx/") } + let(:mirror_uri) { Gem::URI("file:/foo/BAR/baz/qUx/") } it "returns the mirror URI" do expect(settings.mirror_for(uri)).to eq(mirror_uri) @@ -231,7 +244,7 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow end describe "#credentials_for" do - let(:uri) { Bundler::URI("https://gemserver.example.org/") } + let(:uri) { Gem::URI("https://gemserver.example.org/") } let(:credentials) { "username:password" } context "with no configured credentials" do @@ -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( - Bundler::URI("http://rubygems-mirror.org/") + Gem::URI("http://example-mirror.rubygems.org/") ) end @@ -310,17 +323,26 @@ 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 + create_file bundled_app(".bundle/config"), <<~C + # BUNDLE_MY-PERSONAL-SERVER__ORG: my-personal-server.org + C + + expect(Bundler.ui).not_to receive(:warn) + expect(settings.all).to be_empty end it "converts older keys with dashes" do - config("BUNDLE_MY-PERSONAL-SERVER__ORG" => "my-personal-server.org") + 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" \ @@ -330,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 3c6536c4eb..41115aa667 100644 --- a/spec/bundler/bundler/shared_helpers_spec.rb +++ b/spec/bundler/bundler/shared_helpers_spec.rb @@ -1,12 +1,8 @@ # frozen_string_literal: true RSpec.describe Bundler::SharedHelpers do - let(:ext_lock_double) { double(:ext_lock) } - before do pwd_stub - allow(Bundler.rubygems).to receive(:ext_lock).and_return(ext_lock_double) - allow(ext_lock_double).to receive(:synchronize) {|&block| block.call } end let(:pwd_stub) { allow(subject).to receive(:pwd).and_return(bundled_app) } @@ -63,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 @@ -163,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 @@ -242,14 +238,12 @@ RSpec.describe Bundler::SharedHelpers do shared_examples_for "ENV['RUBYOPT'] gets set correctly" do it "ensures -rbundler/setup is at the beginning of ENV['RUBYOPT']" do subject.set_bundle_environment - expect(ENV["RUBYOPT"].split(" ")).to start_with("-r#{source_lib_dir}/bundler/setup") + expect(ENV["RUBYOPT"].split(" ")).to start_with("-r#{install_path}/bundler/setup") end end shared_examples_for "ENV['BUNDLER_SETUP'] gets set correctly" do it "ensures bundler/setup is set in ENV['BUNDLER_SETUP']" do - skip "Does not play well with DidYouMean being a bundled gem instead of a default gem in Ruby 2.6" if RUBY_VERSION < "2.7" - subject.set_bundle_environment expect(ENV["BUNDLER_SETUP"]).to eq("#{source_lib_dir}/bundler/setup") end @@ -264,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 @@ -282,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 @@ -362,25 +354,46 @@ 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 - context "ENV['RUBYOPT'] does not exist" do - before { ENV.delete("RUBYOPT") } + context "when bundler install path is standard" do + let(:install_path) { source_lib_dir } - it_behaves_like "ENV['RUBYOPT'] gets set correctly" - end + context "ENV['RUBYOPT'] does not exist" do + before { ENV.delete("RUBYOPT") } - context "ENV['RUBYOPT'] exists without -rbundler/setup" do - before { ENV["RUBYOPT"] = "-I/some_app_path/lib" } + it_behaves_like "ENV['RUBYOPT'] gets set correctly" + end - it_behaves_like "ENV['RUBYOPT'] gets set correctly" + context "ENV['RUBYOPT'] exists without -rbundler/setup" do + before { ENV["RUBYOPT"] = "-I/some_app_path/lib" } + + it_behaves_like "ENV['RUBYOPT'] gets set correctly" + end + + context "ENV['RUBYOPT'] exists and contains -rbundler/setup" do + before { ENV["RUBYOPT"] = "-rbundler/setup" } + + it_behaves_like "ENV['RUBYOPT'] gets set correctly" + end end - context "ENV['RUBYOPT'] exists and contains -rbundler/setup" do - before { ENV["RUBYOPT"] = "-rbundler/setup" } + context "when bundler install path contains special characters" do + let(:install_path) { "/opt/ruby3.3.0-preview2/lib/ruby/3.3.0+0" } + + before do + ENV["RUBYOPT"] = "-r#{install_path}/bundler/setup" + allow(File).to receive(:expand_path).and_return("#{install_path}/bundler/setup") + allow(Gem).to receive(:bin_path).and_return("#{install_path}/bundler/setup") + end + + it "ensures -rbundler/setup is not duplicated" do + subject.set_bundle_environment + expect(ENV["RUBYOPT"].split(" ").grep(%r{-r.*/bundler/setup}).length).to eq(1) + end it_behaves_like "ENV['RUBYOPT'] gets set correctly" end @@ -410,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 @@ -426,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 @@ -443,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( @@ -498,7 +510,7 @@ 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 diff --git a/spec/bundler/bundler/source/git/git_proxy_spec.rb b/spec/bundler/bundler/source/git/git_proxy_spec.rb index 98d54015e7..1f10ca4b07 100644 --- a/spec/bundler/bundler/source/git/git_proxy_spec.rb +++ b/spec/bundler/bundler/source/git/git_proxy_spec.rb @@ -2,46 +2,98 @@ RSpec.describe Bundler::Source::Git::GitProxy do let(:path) { Pathname("path") } - let(:uri) { "https://github.com/rubygems/rubygems.git" } - let(:ref) { "HEAD" } + let(:uri) { "https://github.com/ruby/rubygems.git" } + let(:ref) { nil } + let(:branch) { nil } + let(:tag) { nil } + let(:options) { { "ref" => ref, "branch" => branch, "tag" => tag }.compact } let(:revision) { nil } let(:git_source) { nil } - let(:clone_result) { double(Process::Status, :success? => true) } + 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"] } - subject { described_class.new(path, uri, ref, revision, git_source) } + 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 + context "with branch only" do + let(:branch) { "main" } + it "sets explicit ref to branch" do + expect(git_proxy.explicit_ref).to eq(branch) + end + end + + context "with ref only" do + let(:ref) { "HEAD" } + it "sets explicit ref to ref" do + expect(git_proxy.explicit_ref).to eq(ref) + end + end + + context "with tag only" do + let(:tag) { "v1.0" } + it "sets explicit ref to ref" do + expect(git_proxy.explicit_ref).to eq(tag) + end + end + + context "with tag and branch" do + let(:tag) { "v1.0" } + let(:branch) { "main" } + it "raises error" do + expect { git_proxy }.to raise_error(Bundler::Source::Git::AmbiguousGitReference) + end + end + + context "with tag and ref" do + let(:tag) { "v1.0" } + let(:ref) { "HEAD" } + it "raises error" do + expect { git_proxy }.to raise_error(Bundler::Source::Git::AmbiguousGitReference) + end + end + + context "with branch and ref" do + let(:branch) { "main" } + let(:ref) { "HEAD" } + it "honors ref over branch" do + expect(git_proxy.explicit_ref).to eq(ref) + end + end + end context "with configured credentials" do it "adds username and password to URI" do Bundler.settings.temporary(uri => "u:p") do - allow(subject).to receive(:git).with("--version").and_return("git version 2.14.0") - expect(subject).to receive(:capture).with([*base_clone_args, "--", "https://u:p@github.com/rubygems/rubygems.git", path.to_s], nil).and_return(["", "", clone_result]) + 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/ruby/rubygems.git", path.to_s], nil).and_return(["", "", clone_result]) subject.checkout end end it "adds username and password to URI for host" do Bundler.settings.temporary("github.com" => "u:p") do - allow(subject).to receive(:git).with("--version").and_return("git version 2.14.0") - expect(subject).to receive(:capture).with([*base_clone_args, "--", "https://u:p@github.com/rubygems/rubygems.git", path.to_s], nil).and_return(["", "", clone_result]) + 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/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 - allow(subject).to receive(:git).with("--version").and_return("git version 2.14.0") - expect(subject).to receive(:capture).with([*base_clone_args, "--", uri, path.to_s], nil).and_return(["", "", clone_result]) + 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 end end it "keeps original userinfo" do Bundler.settings.temporary("github.com" => "u:p") do - original = "https://orig:info@github.com/rubygems/rubygems.git" - subject = described_class.new(Pathname("path"), original, "HEAD") - allow(subject).to receive(:git).with("--version").and_return("git version 2.14.0") - expect(subject).to receive(:capture).with([*base_clone_args, "--", original, path.to_s], nil).and_return(["", "", clone_result]) - subject.checkout + 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]) + git_proxy.checkout end end end @@ -49,46 +101,46 @@ RSpec.describe Bundler::Source::Git::GitProxy do describe "#version" do context "with a normal version number" do before do - expect(subject).to receive(:git).with("--version"). + expect(described_class).to receive(:full_version). and_return("git version 1.2.3") end it "returns the git version number" do - expect(subject.version).to eq("1.2.3") + expect(git_proxy.version).to eq("1.2.3") end it "does not raise an error when passed into Gem::Version.create" do - expect { Gem::Version.create subject.version }.not_to raise_error + expect { Gem::Version.create git_proxy.version }.not_to raise_error end end context "with a OSX version number" do before do - expect(subject).to receive(:git).with("--version"). + expect(described_class).to receive(:full_version). and_return("git version 1.2.3 (Apple Git-BS)") end it "strips out OSX specific additions in the version string" do - expect(subject.version).to eq("1.2.3") + expect(git_proxy.version).to eq("1.2.3") end it "does not raise an error when passed into Gem::Version.create" do - expect { Gem::Version.create subject.version }.not_to raise_error + expect { Gem::Version.create git_proxy.version }.not_to raise_error end end context "with a msysgit version number" do before do - expect(subject).to receive(:git).with("--version"). + expect(described_class).to receive(:full_version). and_return("git version 1.2.3.msysgit.0") end it "strips out msysgit specific additions in the version string" do - expect(subject.version).to eq("1.2.3") + expect(git_proxy.version).to eq("1.2.3") end it "does not raise an error when passed into Gem::Version.create" do - expect { Gem::Version.create subject.version }.not_to raise_error + expect { Gem::Version.create git_proxy.version }.not_to raise_error end end end @@ -96,34 +148,37 @@ RSpec.describe Bundler::Source::Git::GitProxy do describe "#full_version" do context "with a normal version number" do before do - expect(subject).to receive(:git).with("--version"). - 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 - expect(subject.full_version).to eq("1.2.3") + expect(git_proxy.full_version).to eq("1.2.3") end end context "with a OSX version number" do before do - expect(subject).to receive(:git).with("--version"). - 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 - expect(subject.full_version).to eq("1.2.3 (Apple Git-BS)") + expect(git_proxy.full_version).to eq("1.2.3 (Apple Git-BS)") end end context "with a msysgit version number" do before do - expect(subject).to receive(:git).with("--version"). - 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 - expect(subject.full_version).to eq("1.2.3.msysgit.0") + expect(git_proxy.full_version).to eq("1.2.3.msysgit.0") end end end @@ -143,8 +198,157 @@ RSpec.describe Bundler::Source::Git::GitProxy do FileUtils.chmod("+x", file) - bundle :lock, :raise_on_error => false + bundle :lock, raise_on_error: false 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 ed6dc3cd29..14e91c6bdc 100644 --- a/spec/bundler/bundler/source/git_spec.rb +++ b/spec/bundler/bundler/source/git_spec.rb @@ -38,7 +38,7 @@ RSpec.describe Bundler::Source::Git do context "when the source has a reference" do let(:git_proxy_stub) do - instance_double(Bundler::Source::Git::GitProxy, :revision => "123abc", :branch => "v1.0.0") + instance_double(Bundler::Source::Git::GitProxy, revision: "123abc", branch: "v1.0.0") end let(:options) do { "uri" => uri, "ref" => "v1.0.0" } @@ -55,7 +55,7 @@ RSpec.describe Bundler::Source::Git do context "when the source has both reference and glob specifiers" do let(:git_proxy_stub) do - instance_double(Bundler::Source::Git::GitProxy, :revision => "123abc", :branch => "v1.0.0") + instance_double(Bundler::Source::Git::GitProxy, revision: "123abc", branch: "v1.0.0") end let(:options) do { "uri" => uri, "ref" => "v1.0.0", "glob" => "gems/foo/*.gemspec" } @@ -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 07ce4f968e..27430d4a3b 100644 --- a/spec/bundler/bundler/source/rubygems/remote_spec.rb +++ b/spec/bundler/bundler/source/rubygems/remote_spec.rb @@ -11,8 +11,8 @@ RSpec.describe Bundler::Source::Rubygems::Remote do allow(Digest(:MD5)).to receive(:hexdigest).with(duck_type(:to_s)) {|string| "MD5HEX(#{string})" } end - let(:uri_no_auth) { Bundler::URI("https://gems.example.com") } - let(:uri_with_auth) { Bundler::URI("https://#{credentials}@gems.example.com") } + let(:uri_no_auth) { Gem::URI("https://gems.example.com") } + let(:uri_with_auth) { Gem::URI("https://#{credentials}@gems.example.com") } let(:credentials) { "username:password" } context "when the original URI has no credentials" do @@ -89,11 +89,11 @@ RSpec.describe Bundler::Source::Rubygems::Remote do end context "when the original URI has only a username" do - let(:uri) { Bundler::URI("https://SeCrEt-ToKeN@gem.fury.io/me/") } + let(:uri) { Gem::URI("https://SeCrEt-ToKeN@gem.fury.io/me/") } describe "#anonymized_uri" do it "returns the URI without username and password" do - expect(remote(uri).anonymized_uri).to eq(Bundler::URI("https://gem.fury.io/me/")) + expect(remote(uri).anonymized_uri).to eq(Gem::URI("https://gem.fury.io/me/")) end end @@ -105,9 +105,9 @@ RSpec.describe Bundler::Source::Rubygems::Remote do end context "when a mirror with inline credentials is configured for the URI" do - let(:uri) { Bundler::URI("https://rubygems.org/") } - let(:mirror_uri_with_auth) { Bundler::URI("https://username:password@rubygems-mirror.org/") } - let(:mirror_uri_no_auth) { Bundler::URI("https://rubygems-mirror.org/") } + let(:uri) { Gem::URI("https://rubygems.org/") } + let(:mirror_uri_with_auth) { Gem::URI("https://username:password@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) } @@ -131,9 +131,9 @@ RSpec.describe Bundler::Source::Rubygems::Remote do end context "when a mirror with configured credentials is configured for the URI" do - let(:uri) { Bundler::URI("https://rubygems.org/") } - let(:mirror_uri_with_auth) { Bundler::URI("https://#{credentials}@rubygems-mirror.org/") } - let(:mirror_uri_no_auth) { Bundler::URI("https://rubygems-mirror.org/") } + let(:uri) { Gem::URI("https://rubygems.org/") } + let(:mirror_uri_with_auth) { Gem::URI("https://#{credentials}@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 f860e9ff58..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 @@ -85,7 +85,7 @@ RSpec.describe Bundler::SourceList do end it "ignores git protocols on request" do - Bundler.settings.temporary(:"git.allow_insecure" => true) + Bundler.settings.temporary("git.allow_insecure": true) expect(Bundler.ui).to_not receive(:warn).with(msg) source_list.add_git_source("uri" => "git://existing-git.org/path.git") end @@ -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 [ - Bundler::URI("https://othersource.org/"), - Bundler::URI("https://rubygems.org/"), + 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 ceb369ecdb..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 @@ -32,19 +32,19 @@ RSpec.describe Bundler::Source do context "when there are locked gems" do context "that contain the relevant gem spec" do context "without a version" do - let(:locked_gem) { double(:locked_gem, :name => "nokogiri", :version => nil) } + let(:locked_gem) { double(:locked_gem, name: "nokogiri", version: nil) } it_behaves_like "the lockfile specs are not relevant" end context "with the same version" do - let(:locked_gem) { double(:locked_gem, :name => "nokogiri", :version => ">= 1.6") } + let(:locked_gem) { double(:locked_gem, name: "nokogiri", version: ">= 1.6") } it_behaves_like "the lockfile specs are not relevant" end context "with a different version" do - let(:locked_gem) { double(:locked_gem, :name => "nokogiri", :version => "< 1.5") } + let(:locked_gem) { double(:locked_gem, name: "nokogiri", version: "< 1.5") } context "with color", :no_color_tty do before do @@ -70,8 +70,8 @@ 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(:locked_gem) { double(:locked_gem, :name => "nokogiri", :version => "1.7.0") } + 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 before do @@ -97,8 +97,8 @@ 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(:locked_gem) { double(:locked_gem, :name => "nokogiri", :version => "1.7.0") } + 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 before do @@ -128,7 +128,7 @@ RSpec.describe Bundler::Source do describe "#can_lock?" do context "when the passed spec's source is equivalent" do - let(:spec) { double(:spec, :source => subject) } + let(:spec) { double(:spec, source: subject) } it "should return true" do expect(subject.can_lock?(spec)).to be_truthy @@ -136,7 +136,7 @@ RSpec.describe Bundler::Source do end context "when the passed spec's source is not equivalent" do - let(:spec) { double(:spec, :source => double(:other_source)) } + let(:spec) { double(:spec, source: double(:other_source)) } it "should return false" do expect(subject.can_lock?(spec)).to be_falsey diff --git a/spec/bundler/bundler/spec_set_spec.rb b/spec/bundler/bundler/spec_set_spec.rb index 6fedd38b50..1e1ceadf26 100644 --- a/spec/bundler/bundler/spec_set_spec.rb +++ b/spec/bundler/bundler/spec_set_spec.rb @@ -43,23 +43,29 @@ RSpec.describe Bundler::SpecSet do spec = described_class.new(specs).find_by_name_and_platform("b", platform) expect(spec).to eq platform_spec end - end - describe "#merge" do - let(:other_specs) do - [ - build_spec("f", "1.0"), - build_spec("g", "2.0"), - ].flatten + 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 - let(:other_spec_set) { described_class.new(other_specs) } + 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 - it "merges the items in each gemspec" do - new_spec_set = subject.merge(other_spec_set) - specs = new_spec_set.to_a.map(&:full_name) - expect(specs).to include("a-1.0") - expect(specs).to include("f-1.0") + 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 @@ -73,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 new file mode 100644 index 0000000000..19b7724e81 --- /dev/null +++ b/spec/bundler/bundler/specifications/foo.gemspec @@ -0,0 +1,13 @@ +# rubocop:disable Style/FrozenStringLiteralComment +# stub: foo 1.0.0 ruby lib + +# The first line would be '# -*- encoding: utf-8 -*-' in a real stub gemspec + +Gem::Specification.new do |s| + s.name = "foo" + s.version = "1.0.0" + s.loaded_from = __FILE__ + s.extensions = "ext/foo" + s.required_ruby_version = ">= 3.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 fb612813c2..f2faa2ea64 100644 --- a/spec/bundler/bundler/stub_specification_spec.rb +++ b/spec/bundler/bundler/stub_specification_spec.rb @@ -19,6 +19,17 @@ RSpec.describe Bundler::StubSpecification do end end + describe "#gem_build_complete_path" do + it "StubSpecification should have equal gem_build_complete_path as Specification" do + spec_path = File.join(File.dirname(__FILE__), "specifications", "foo.gemspec") + spec = Gem::Specification.load(spec_path) + gem_stub = Gem::StubSpecification.new(spec_path, File.dirname(__FILE__),"","") + + stub = described_class.from_stub(gem_stub) + expect(stub.gem_build_complete_path).to eq spec.gem_build_complete_path + end + end + describe "#manually_installed?" do it "returns true if installed_by_version is nil or 0" do stub = described_class.from_stub(with_bundler_stub_spec) @@ -38,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 466c1b8594..641f0addb4 100644 --- a/spec/bundler/bundler/uri_credentials_filter_spec.rb +++ b/spec/bundler/bundler/uri_credentials_filter_spec.rb @@ -16,7 +16,7 @@ RSpec.describe Bundler::URICredentialsFilter do let(:credentials) { "oauth_token:x-oauth-basic@" } it "returns the uri without the oauth token" do - expect(subject.credential_filtered_uri(uri).to_s).to eq(Bundler::URI("https://x-oauth-basic@github.com/company/private-repo").to_s) + expect(subject.credential_filtered_uri(uri).to_s).to eq(Gem::URI("https://x-oauth-basic@github.com/company/private-repo").to_s) end it_behaves_like "original type of uri is maintained" @@ -26,7 +26,17 @@ RSpec.describe Bundler::URICredentialsFilter do let(:credentials) { "oauth_token:x@" } it "returns the uri without the oauth token" do - expect(subject.credential_filtered_uri(uri).to_s).to eq(Bundler::URI("https://x@github.com/company/private-repo").to_s) + expect(subject.credential_filtered_uri(uri).to_s).to eq(Gem::URI("https://x@github.com/company/private-repo").to_s) + end + + it_behaves_like "original type of uri is maintained" + 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" @@ -37,7 +47,7 @@ RSpec.describe Bundler::URICredentialsFilter do let(:credentials) { "username1:hunter3@" } it "returns the uri without the password" do - expect(subject.credential_filtered_uri(uri).to_s).to eq(Bundler::URI("https://username1@github.com/company/private-repo").to_s) + expect(subject.credential_filtered_uri(uri).to_s).to eq(Gem::URI("https://username1@github.com/company/private-repo").to_s) end it_behaves_like "original type of uri is maintained" @@ -55,7 +65,7 @@ RSpec.describe Bundler::URICredentialsFilter do end context "uri is a uri object" do - let(:uri) { Bundler::URI("https://#{credentials}github.com/company/private-repo") } + let(:uri) { Gem::URI("https://#{credentials}github.com/company/private-repo") } it_behaves_like "sensitive credentials in uri are filtered out" end @@ -90,7 +100,7 @@ RSpec.describe Bundler::URICredentialsFilter do describe "#credential_filtered_string" do let(:str_to_filter) { "This is a git message containing a uri #{uri}!" } let(:credentials) { "" } - let(:uri) { Bundler::URI("https://#{credentials}github.com/company/private-repo") } + let(:uri) { Gem::URI("https://#{credentials}github.com/company/private-repo") } context "with a uri that contains credentials" do let(:credentials) { "oauth_token:x-oauth-basic@" } diff --git a/spec/bundler/bundler/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 1241c74bbf..9ff1579b76 100644 --- a/spec/bundler/bundler/yaml_serializer_spec.rb +++ b/spec/bundler/bundler/yaml_serializer_spec.rb @@ -9,7 +9,7 @@ RSpec.describe Bundler::YAMLSerializer do it "works for simple hash" do hash = { "Q" => "Where does Thursday come before Wednesday? In the dictionary. :P" } - expected = strip_whitespace <<-YAML + expected = <<~YAML --- Q: "Where does Thursday come before Wednesday? In the dictionary. :P" YAML @@ -24,7 +24,7 @@ RSpec.describe Bundler::YAMLSerializer do }, } - expected = strip_whitespace <<-YAML + expected = <<~YAML --- nice-one: read_ahead: "All generalizations are false, including this one" @@ -45,7 +45,7 @@ RSpec.describe Bundler::YAMLSerializer do }, } - expected = strip_whitespace <<-YAML + expected = <<~YAML --- nested_hash: contains_array: @@ -57,11 +57,24 @@ RSpec.describe Bundler::YAMLSerializer do expect(serializer.dump(hash)).to eq(expected) end + + it "handles empty array" do + hash = { + "empty_array" => [], + } + + expected = <<~YAML + --- + empty_array: [] + YAML + + expect(serializer.dump(hash)).to eq(expected) + end end describe "#load" do it "works for simple hash" do - yaml = strip_whitespace <<-YAML + yaml = <<~YAML --- Jon: "Air is free dude!" Jack: "Yes.. until you buy a bag of chips!" @@ -76,7 +89,7 @@ RSpec.describe Bundler::YAMLSerializer do end it "works for nested hash" do - yaml = strip_whitespace <<-YAML + yaml = <<~YAML --- baa: baa: "black sheep" @@ -98,15 +111,15 @@ RSpec.describe Bundler::YAMLSerializer do end it "handles colon in key/value" do - yaml = strip_whitespace <<-YAML - BUNDLE_MIRROR__HTTPS://RUBYGEMS__ORG/: http://rubygems-mirror.org + yaml = <<~YAML + 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 - yaml = strip_whitespace <<-YAML + yaml = <<~YAML --- nested_hash: contains_array: @@ -127,7 +140,7 @@ RSpec.describe Bundler::YAMLSerializer do end it "handles windows-style CRLF line endings" do - yaml = strip_whitespace(<<-YAML).gsub("\n", "\r\n") + yaml = <<~YAML.gsub("\n", "\r\n") --- nested_hash: contains_array: @@ -148,6 +161,34 @@ RSpec.describe Bundler::YAMLSerializer do expect(serializer.load(yaml)).to eq(hash) end + + it "handles empty array" do + yaml = <<~YAML + --- + empty_array: [] + YAML + + hash = { + "empty_array" => [], + } + + expect(serializer.load(yaml)).to eq(hash) + end + + it "skip commented out words" do + yaml = <<~YAML + --- + foo: bar + buzz: foo # bar + YAML + + hash = { + "foo" => "bar", + "buzz" => "foo", + } + + expect(serializer.load(yaml)).to eq(hash) + end end describe "against yaml lib" do diff --git a/spec/bundler/cache/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 63c00eba01..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") + build_gem "omg", path: bundled_app("vendor/cache") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "omg" G @@ -28,47 +28,47 @@ RSpec.describe "bundle cache" do end it "uses the cache as a source when installing gems with --local" do - system_gems [], :path => default_bundle_path + system_gems [], path: default_bundle_path bundle "install --local" - expect(the_bundle).to include_gems("rack 1.0.0") + 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") + bundle :install, local: true + 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 + build_gem "json", default_json_version, to_system: true, default: true + end + + context "when a remote gem is available for caching" do + before do + build_repo2 do + build_gem "json", default_json_version + end end - build_gem "json", default_json_version, :to_system => true, :default => true - end + 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 - 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 + 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 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 "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 :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 "caches remote and builtin gems" do + install_gemfile <<-G + source "https://gem.repo2" + gem 'json', '#{default_json_version}' + gem 'myrack', '1.0.0' + G - it "caches builtin gems when cache_all_platforms is set" do - gemfile <<-G - source "#{file_uri_for(gem_repo2)}" - gem "json" - G + 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 - bundle "config set cache_all_platforms true" + it "caches builtin gems when cache_all_platforms is set" do + gemfile <<-G + source "https://gem.repo2" + gem "json" + G - bundle :cache - expect(bundled_app("vendor/cache/json-#{default_json_version}.gem")).to exist - end + 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 + + it "errors when explicitly caching" do + bundle_config "path.system true" - install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'json', '#{default_json_version}' - G + 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 + bundle "update", all: true + 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,37 +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 + + 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 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" + + 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("platform_specific-1.0-#{Bundler.local_platform}")).to exist - expect(cached_gem("platform_specific-1.0-java")).to exist + expect(cached_gem("myrack-1.0.0")).to exist 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"), - :rubygems_version => "1.3.2" - simulate_new_machine + 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, "myrack", "1.0.0" + end + + FileUtils.rm_r default_bundle_path + default_system_gems + + lockfile <<-L + GEM + remote: https://gem.repo2/ + specs: + 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("myrack-1.0.0")}") + + 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 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: https://gem.repo2/ + specs: + myrack (1.0.0) + L + + 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" @@ -304,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/) @@ -314,10 +414,10 @@ RSpec.describe "bundle cache" do it "should install gems with the name bundler in them (that aren't bundler)" do build_gem "foo-bundler", "1.0", - :path => bundled_app("vendor/cache") + path: bundled_app("vendor/cache") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + 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 890ba88605..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| @@ -89,13 +101,13 @@ RSpec.describe "bundle cache with git" do ref = git.ref_for("main", 11) expect(ref).not_to eq(old_ref) - bundle "update", :all => true + bundle "update", all: true bundle :cache 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 :install, :local => 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 @@ -266,11 +473,39 @@ RSpec.describe "bundle cache with git" do # Verify that the compilation worked and the result is in $LOAD_PATH by simply attempting # to require it; that should make sure this spec does not break if the load path behaviour # is changed. - bundle :install, :local => true - ruby <<~R, :raise_on_error => false + bundle :install, local: true + ruby <<~R, raise_on_error: false require 'bundler/setup' require 'foo_c' R expect(last_command).to_not be_failure end + + 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 2ad136a008..42648aea1f 100644 --- a/spec/bundler/cache/path_spec.rb +++ b/spec/bundler/cache/path_spec.rb @@ -2,14 +2,13 @@ RSpec.describe "bundle cache with path" do it "is no-op when the path is within the bundle" do - build_lib "foo", :path => bundled_app("lib/foo") + build_lib "foo", path: bundled_app("lib/foo") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + 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 @@ -35,14 +33,13 @@ RSpec.describe "bundle cache with path" do libname = File.basename(bundled_app) + "_gem" libpath = File.join(File.dirname(bundled_app), libname) - build_lib libname, :path => libpath + build_lib libname, path: libpath install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + 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 128278956c..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 + 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 5a5b534e8d..162650f2e5 100644 --- a/spec/bundler/commands/add_spec.rb +++ b/spec/bundler/commands/add_spec.rb @@ -9,48 +9,65 @@ RSpec.describe "bundle add" do build_gem "bar", "0.12.3" build_gem "cat", "0.12.3.pre" build_gem "dog", "1.1.3.pre" + build_gem "lemur", "3.1.1.pre.2023.1.1" end build_git "foo", "2.0" gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "weakling", "~> 0.0.1" G end context "when no gems are specified" do it "shows error" do - bundle "add", :raise_on_error => false + bundle "add", raise_on_error: false expect(err).to include("Please specify gems to add") end 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 + bundle "add 'lemur'" + # the trailing pre purposely matches the release version to ensure that subbing the release doesn't change the pre.version" + expect(bundled_app_gemfile.read).to match(/gem "lemur", ">= 3.1.pre.2023.1.1"/) + expect(the_bundle).to include_gems "lemur 3.1.1.pre.2023.1.1" + end end describe "with --version" do @@ -63,7 +80,7 @@ RSpec.describe "bundle add" do it "adds multiple version constraints when specified" do requirements = ["< 3.0", "> 1.0"] bundle "add 'foo' --version='#{requirements.join(", ")}'" - expect(bundled_app_gemfile.read).to match(/gem "foo", #{Gem::Requirement.new(requirements).as_list.map(&:dump).join(', ')}/) + expect(bundled_app_gemfile.read).to match(/gem "foo", #{Gem::Requirement.new(requirements).as_list.map(&:dump).join(", ")}/) expect(the_bundle).to include_gems "foo 2.0" end end @@ -71,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 @@ -107,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 @@ -116,20 +133,20 @@ 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 describe "with --git and --branch" do before do - update_git "foo", "2.0", :branch => "test" + update_git "foo", "2.0", branch: "test" end it "adds dependency with specified 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 @@ -138,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\.0", :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 --git and --glob" do + it "adds dependency with specified git source" do + bundle "add foo --git=#{lib_path("foo-2.0")} --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "foo", ">= 2.0", git: "#{lib_path("foo-2.0")}", glob: "\./\*\.gemspec"}) + expect(the_bundle).to include_gems "foo 2.0" + end + end + + describe "with --git and --branch and --glob" do + before do + update_git "foo", "2.0", branch: "test" + end + + it "adds dependency with specified git source and branch" do + bundle "add foo --git=#{lib_path("foo-2.0")} --branch=test --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "foo", ">= 2.0", git: "#{lib_path("foo-2.0")}", branch: "test", glob: "\./\*\.gemspec"}) + expect(the_bundle).to include_gems "foo 2.0" end end - describe "with --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 --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 "rake", "~> 13\.0", :github => "ruby\/rake", :branch => "master"}) + expect(bundled_app_gemfile.read).to match(%r{gem "foo", ">= 2\.0", git: "#{lib_path("foo-2.0")}", ref: "#{revision_for(lib_path("foo-2.0"))}", glob: "\./\*\.gemspec"}) + expect(the_bundle).to include_gems "foo 2.0" end end - describe "with --github and --ref" do - it "adds dependency with specified github source and ref", :realworld do - bundle "add rake --github=ruby/rake --ref=5c60da8" + 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(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.0", :github => "ruby\/rake", :ref => "5c60da8"}) + 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 @@ -177,41 +280,81 @@ 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 it "shows error message when version is not formatted correctly" do - bundle "add 'foo' -v='~>1 . 0'", :raise_on_error => false + bundle "add 'foo' -v='~>1 . 0'", raise_on_error: false expect(err).to match("Invalid gem requirement pattern '~>1 . 0'") end it "shows error message when gem cannot be found" do - bundle "config set force_ruby_platform true" - bundle "add 'werk_it'", :raise_on_error => false + bundle_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 + bundle "add 'baz' --source='file://does/not/exist'", raise_on_error: false expect(err).to include("Could not fetch specs from file://does/not/exist/") end 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" @@ -221,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 @@ -240,12 +383,12 @@ 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 - bundle "add weakling bar", :raise_on_error => false + bundle "add weakling bar", raise_on_error: false expect(err).to include("You cannot specify the same gem twice with different version requirements") expect(err).to include("You specified: weakling (~> 0.0.1) and weakling (>= 0).") @@ -255,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 @@ -298,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 bfbef58181..af4d24a9e8 100644 --- a/spec/bundler/commands/binstubs_spec.rb +++ b/spec/bundler/commands/binstubs_spec.rb @@ -4,48 +4,48 @@ 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 - bundle :binstubs, :all => true + bundle :binstubs, all: true expect(bundled_app("bin/rails")).to exist expect(bundled_app("bin/rake")).to exist @@ -53,259 +53,46 @@ 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 + bundle "binstubs", raise_on_error: false expect(exitstatus).to eq(1) expect(err).to include("`bundle binstubs` needs at least one gem to run.") end 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")) - build_git "foo", "1.0", :path => lib_path("foo") do |s| + build_git "foo", "1.0", path: lib_path("foo") do |s| s.executables = %w[foo] end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo")}" G @@ -317,11 +104,11 @@ RSpec.describe "bundle binstubs <gem>" do it "installs binstubs from path gems" do FileUtils.mkdir_p(lib_path("foo/bin")) FileUtils.touch(lib_path("foo/bin/foo")) - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.executables = %w[foo] end install_gemfile <<-G - 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,73 +146,63 @@ 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 + bundle "binstubs doesnt_exist", raise_on_error: false expect(exitstatus).to eq(7) expect(err).to include("Could not find gem 'doesnt_exist'.") 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 790373be1f..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("deployment 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 99a858e9e9..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,17 +13,17 @@ 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 - bundle "check --gemfile bundled_app/Gemfile", :dir => tmp + bundle "check --gemfile bundled_app/Gemfile", dir: tmp expect(out).to include("The Gemfile's dependencies are satisfied") end 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.") + bundle :check, raise_on_error: false + 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 + 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,39 +109,26 @@ 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 - bundle :check, :raise_on_error => false + bundle :check, raise_on_error: false expect(err).to include("Bundler can't satisfy your Gemfile's dependencies.") end - it "remembers --without option from install", :bundler => "< 3" do - 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)") + bundle "check", raise_on_error: false + 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)") + bundle "check", raise_on_error: false + 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 @@ -216,77 +234,37 @@ RSpec.describe "bundle check" do end it "outputs an error when the default Gemfile is not found" do - bundle :check, :raise_on_error => false + bundle :check, raise_on_error: false expect(exitstatus).to eq(10) expect(err).to include("Could not locate Gemfile") end it "does not output fatal error message" do - bundle :check, :raise_on_error => false + bundle :check, raise_on_error: false expect(exitstatus).to eq(10) expect(err).not_to include("Unfortunately, a fatal error has occurred. ") end - 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) - bundle :check, :raise_on_error => false + bundle :check, raise_on_error: false expect(last_command).to be_failure end - context "--path", :bundler => "< 3" do - context "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,90 +275,91 @@ RSpec.describe "bundle check" do end it "shows what is missing with the current Gemfile if it is not satisfied" do - simulate_new_machine - bundle :check, :raise_on_error => false + 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" end it "shows what is missing with the current Gemfile without duplications" do - bundle :check, :raise_on_error => false + bundle :check, raise_on_error: false expect(err).to match(/The following gems are missing/) - expect(err).to include("* rack (1.0").once + 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 + 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,45 +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_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 @@ -441,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" @@ -455,16 +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") + + 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: @@ -487,26 +480,80 @@ RSpec.describe "bundle check" do DEPENDENCIES bundle-check-issue! 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 @@ -517,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 471cd6c354..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,45 +137,45 @@ 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 it "removes unused git gems" do - build_git "foo", :path => lib_path("foo") + build_git "foo", path: lib_path("foo") git_path = lib_path("foo") revision = revision_for(git_path) 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,25 +183,25 @@ 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 - build_git "foo", :path => lib_path("foo") + build_git "foo", path: lib_path("foo") git_path = lib_path("foo") 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,52 +220,52 @@ 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 - build_git "foo-bar", :path => lib_path("foo-bar") + 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") + update_git "foo-bar", path: lib_path("foo-bar") revision2 = revision_for(lib_path("foo-bar")) - bundle "update", :all => true + bundle "update", all: true bundle :clean expect(out).to include("Removing foo-bar (#{revision[0..11]})") - 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 - build_lib "activesupport", "3.0", :path => lib_path("rails/activesupport") - build_git "rails", "3.0", :path => lib_path("rails") do |s| + build_lib "activesupport", "3.0", path: lib_path("rails/activesupport") + build_git "rails", "3.0", path: lib_path("rails") do |s| s.add_dependency "activesupport", "= 3.0" end revision = revision_for(lib_path("rails")) 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("") @@ -274,22 +274,22 @@ RSpec.describe "bundle clean" do end it "does not remove git sources that are in without groups" do - build_git "foo", :path => lib_path("foo") + build_git "foo", path: lib_path("foo") git_path = lib_path("foo") revision = revision_for(git_path) 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,31 +302,31 @@ 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 + bundle :clean, raise_on_error: false expect(exitstatus).to eq(15) expect(err).to include("--force") @@ -335,103 +335,94 @@ 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" end - bundle "update", :all => true + 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 @@ -440,10 +431,10 @@ RSpec.describe "bundle clean" do build_gem "foo", "1.0.1" end - bundle "update", :all => true + bundle "update", all: true - files = Pathname.glob(bundled_app(".bundle", Bundler.ruby_scope, "*", "*")) - files.map! {|f| f.to_s.sub(bundled_app(".bundle", Bundler.ruby_scope).to_s, "") } + 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,52 +442,52 @@ 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 build_gem "foo", "1.0.1" end - bundle :update, :all => true + bundle :update, all: true should_have_gems "foo-1.0", "foo-1.0.1" end - 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 @@ -505,35 +496,35 @@ RSpec.describe "bundle clean" do update_repo2 do build_gem "foo", "1.0.1" end - bundle :update, :all => true + bundle :update, all: true - gem_command :list + 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,30 +535,30 @@ 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 FileUtils.chmod(0o500, system_cache_path) - bundle :clean, :force => true, :raise_on_error => false + bundle :clean, force: true, raise_on_error: false expect(err).to include(system_gem_path.to_s) expect(err).to include("grant write permissions") - 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,26 +616,20 @@ 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 - skip "does not work on old rubies because the realworld gems that need to be installed don't support them" if RUBY_VERSION < "2.7.0" - - skip "does not work on rubygems versions where `--install_dir` doesn't respect --default" unless Gem::Installer.for_spec(loaded_gemspec, :install_dir => "/foo").default_spec_file == "/foo/specifications/default/bundler-#{Bundler::VERSION}.gemspec" # Since rubygems 3.2.0.rc.2 - - default_irb_version = ruby "gem 'irb', '< 999999'; require 'irb'; puts IRB::VERSION", :raise_on_error => false + it "when using --force, it doesn't remove default gem binaries" 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? # simulate executable for default gem - build_gem "irb", default_irb_version, :to_system => true, :default => true do |s| + build_gem "irb", default_irb_version, to_system: true, default: true do |s| 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 } + bundle "clean --force", env: { "BUNDLER_GEM_DEFAULT_DIR" => system_gem_path.to_s } expect(out).not_to include("Removing irb") end @@ -658,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 @@ -694,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 @@ -724,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 @@ -755,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 @@ -793,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 @@ -812,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 = @@ -832,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" @@ -853,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 = @@ -868,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 @@ -878,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" @@ -895,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}" @@ -903,9 +888,9 @@ RSpec.describe "bundle clean" do G bundle :lock - bundle "config set without development" - bundle "config set path vendor/bundle" - bundle "install" + bundle_config "without development" + bundle_config "path vendor/bundle" + bundle "install", verbose: true bundle :clean very_simple_binary_extensions_dir = @@ -913,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 ede93f99eb..e8ab32ca5d 100644 --- a/spec/bundler/commands/config_spec.rb +++ b/spec/bundler/commands/config_spec.rb @@ -28,7 +28,7 @@ RSpec.describe ".bundle/config" do context "with env overwrite" do it "prints config with env" do - bundle "config list --parseable", :env => { "BUNDLE_FOO" => "bar3" } + bundle "config list --parseable", env: { "BUNDLE_FOO" => "bar3" } expect(out).to include("foo=bar3") end end @@ -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 @@ -64,11 +64,11 @@ RSpec.describe ".bundle/config" do ENV["BUNDLE_APP_CONFIG"] = "../foo" bundle "config set --local path vendor/bundle" - bundle "install", :dir => bundled_app("omg") + bundle "install", dir: bundled_app("omg") expect(bundled_app(".bundle")).not_to exist expect(bundled_app("../foo/config")).to exist - expect(the_bundle).to include_gems "rack 1.0.0", :dir => bundled_app("omg") + expect(the_bundle).to include_gems "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" @@ -96,8 +104,8 @@ RSpec.describe ".bundle/config" do end it "can be configured through BUNDLE_USER_CONFIG" do - bundle "config set path vendor", :env => { "BUNDLE_USER_CONFIG" => bundle_user_config } - bundle "config get path", :env => { "BUNDLE_USER_CONFIG" => bundle_user_config } + bundle "config set path vendor", env: { "BUNDLE_USER_CONFIG" => bundle_user_config } + bundle "config get path", env: { "BUNDLE_USER_CONFIG" => bundle_user_config } expect(out).to include("Set for the current user (#{bundle_user_config}): \"vendor\"") end @@ -105,8 +113,8 @@ RSpec.describe ".bundle/config" do let(:bundle_user_home) { bundled_app(".bundle").to_s } it "uses the right location" do - bundle "config set path vendor", :env => { "BUNDLE_USER_HOME" => bundle_user_home } - bundle "config get path", :env => { "BUNDLE_USER_HOME" => bundle_user_home } + bundle "config set path vendor", env: { "BUNDLE_USER_HOME" => bundle_user_home } + bundle "config get path", env: { "BUNDLE_USER_HOME" => bundle_user_home } expect(out).to include("Set for the current user (#{bundle_user_home}/config): \"vendor\"") end end @@ -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,11 +454,11 @@ 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) - require "#{entrypoint}" + require "bundler" print Bundler.settings.mirror_for("https://rails-assets.org") RUBY expect(out).to eq("https://rails-assets.org/") @@ -451,7 +466,7 @@ E bundle "config set mirror.all http://localhost:9293" ruby(<<~RUBY) - require "#{entrypoint}" + require "bundler" print Bundler.settings.mirror_for("https://rails-assets.org") RUBY expect(out).to eq("http://localhost:9293/") @@ -461,34 +476,35 @@ E describe "subcommands" do it "list" do - bundle "config list", :env => { "BUNDLE_FOO" => "bar" } + bundle "config list", env: { "BUNDLE_FOO" => "bar" } expect(out).to eq "Settings are listed in order of priority. The top value will be used.\nfoo\nSet via BUNDLE_FOO: \"bar\"" - bundle "config list", :env => { "BUNDLE_FOO" => "bar" }, :parseable => true + bundle "config list", env: { "BUNDLE_FOO" => "bar" }, parseable: true expect(out).to eq "foo=bar" end it "list with credentials" do - bundle "config list", :env => { "BUNDLE_GEMS__MYSERVER__COM" => "user:password" } + bundle "config list", env: { "BUNDLE_GEMS__MYSERVER__COM" => "user:password" } expect(out).to eq "Settings are listed in order of priority. The top value will be used.\ngems.myserver.com\nSet via BUNDLE_GEMS__MYSERVER__COM: \"user:[REDACTED]\"" - bundle "config list", :parseable => true, :env => { "BUNDLE_GEMS__MYSERVER__COM" => "user:password" } + bundle "config list", parseable: true, env: { "BUNDLE_GEMS__MYSERVER__COM" => "user:password" } expect(out).to eq "gems.myserver.com=user:password" end it "list with API token credentials" do - bundle "config list", :env => { "BUNDLE_GEMS__MYSERVER__COM" => "api_token:x-oauth-basic" } + bundle "config list", env: { "BUNDLE_GEMS__MYSERVER__COM" => "api_token:x-oauth-basic" } expect(out).to eq "Settings are listed in order of priority. The top value will be used.\ngems.myserver.com\nSet via BUNDLE_GEMS__MYSERVER__COM: \"[REDACTED]:x-oauth-basic\"" - bundle "config list", :parseable => true, :env => { "BUNDLE_GEMS__MYSERVER__COM" => "api_token:x-oauth-basic" } + bundle "config list", parseable: true, env: { "BUNDLE_GEMS__MYSERVER__COM" => "api_token:x-oauth-basic" } expect(out).to eq "gems.myserver.com=api_token:x-oauth-basic" end it "get" do 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" @@ -515,7 +531,7 @@ E bundle "config set --local foo 4.1" expect(out).to eq "You are replacing the current local value of foo, which is currently \"4\"" - bundle "config set --global --local foo 5", :raise_on_error => false + bundle "config set --global --local foo 5", raise_on_error: false expect(last_command).to be_failure expect(err).to eq "The options global and local were specified. Please only use one of the switches at a time." end @@ -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,9 +571,10 @@ 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 + bundle "config unset foo --local --global", raise_on_error: false expect(last_command).to be_failure expect(err).to eq "The options global and local were specified. Please only use one of the switches at a time." end @@ -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 aa76096e3d..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 1afac00923..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,36 +34,51 @@ 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 } allow(File).to receive(:writable?).with(unwritable_file) { true } allow(File).to receive(:readable?).with(unwritable_file) { true } + + # The following lines are for `Gem::PathSupport#initialize`. + allow(File).to receive(:exist?).with(Gem.default_dir) + allow(File).to receive(:writable?).with(Gem.default_dir) + allow(File).to receive(:writable?).with(File.expand_path("..", Gem.default_dir)) end - it "exits with no message if the installed gem has no C extensions" do - 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) - expect { doctor.run }.to raise_error(Bundler::ProductionError, strip_whitespace(<<-E).strip), @stdout.string + 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 @@ -77,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}" ) @@ -92,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 @@ -136,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 099cdb39fa..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,192 +169,185 @@ 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" + install_gemfile bundled_app2("Gemfile"), <<-G, dir: bundled_app2 + 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" + install_gemfile bundled_app2("Gemfile"), <<-G, dir: bundled_app2 + 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 + bundle "exec foobarbaz", raise_on_error: false expect(exitstatus).to eq(127) expect(err).to include("bundler: command not found: foobarbaz") expect(err).to include("Install missing gem executables with `bundle install`") @@ -381,39 +355,39 @@ 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" - bundle "exec ./foo", :raise_on_error => false + bundled_app("foo").write("") + bundle "exec ./foo", raise_on_error: false expect(exitstatus).to eq(126) expect(err).to include("bundler: not executable: ./foo") end 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 + bundle "exec", raise_on_error: false expect(exitstatus).to eq(128) expect(err).to include("bundler: exec needs a command to run") end it "raises a helpful error when exec'ing to something outside of the bundle" do - system_gems(system_gems_to_install, :path => default_bundle_path) + 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,31 +484,31 @@ 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 describe "from gems bundled via :path" do before(:each) do - build_lib "fizz", :path => home("fizz") do |s| + build_lib "fizz", path: home("fizz") do |s| s.executables = "fizz" end 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 @@ -580,12 +552,12 @@ RSpec.describe "bundle exec" do describe "from gems bundled via :git with no gemspec" do before(:each) do - build_git "fizz_no_gemspec", :gemspec => false do |s| + build_git "fizz_no_gemspec", gemspec: false do |s| s.executables = "fizz_no_gemspec" end 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,21 +577,34 @@ 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 - it "loads the correct optparse when `auto_install` is set, and optparse is a dependency" do - if Gem.ruby_version >= Gem::Version.new("3.0.0") && Gem.rubygems_version < Gem::Version.new("3.3.0.a") - skip "optparse is a default gem, and rubygems loads it during install" + it "performs an automatic bundle install with git gems" do + build_git "foo" do |s| + s.executables = "foo" end + gemfile <<-G + source "https://gem.repo1" + gem "myrack", "0.9.1" + gem "foo", :git => "#{lib_path("foo-1.0")}" + G + + 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 build_repo4 do build_gem "fastlane", "2.192.0" do |s| s.executables = "fastlane" @@ -630,17 +615,17 @@ RSpec.describe "bundle exec" do build_gem "optparse", "999.999.999" end - system_gems "optparse-999.999.998", :gem_repo => gem_repo4 + 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 @@ -657,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) @@ -687,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 } + 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.gsub(/^ */, "").strip } + 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 } + 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) @@ -748,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) @@ -761,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) @@ -784,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) @@ -801,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) @@ -814,16 +840,13 @@ RSpec.describe "bundle exec" do let(:executable) { super() << "\nraise 'ERROR'" } let(:exit_code) { 1 } let(:expected_err) do - "bundler: failed to load command: #{path} (#{path})" \ - "\n#{path}:10:in `<top (required)>': ERROR (RuntimeError)" + /\Abundler: failed to load command: #{Regexp.quote(path.to_s)} \(#{Regexp.quote(path.to_s)}\)\n#{Regexp.quote(path.to_s)}:[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 start_with(expected_err) + expect(err).to match(expected_err) expect(out).to eq(expected) end end @@ -835,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) @@ -848,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) @@ -857,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 locally installed gems. - -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 locally installed gems. + 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) @@ -918,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" @@ -931,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.") @@ -945,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) @@ -956,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) @@ -979,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 + $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? - - 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) @@ -1009,29 +1002,39 @@ __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 } - pending "relative paths with ./ have absolute __FILE__" + $0: #{path.to_s.inspect} + __FILE__: #{File.expand_path(path, bundled_app).inspect} + EOS + + 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) end context "signals being trapped by bundler" do - let(:executable) { strip_whitespace <<-RUBY } + let(:executable) { <<~RUBY } #{shebang} begin Thread.new do puts 'Started' # For process sync STDOUT.flush sleep 1 # ignore quality_spec - raise "Didn't receive INT at all" + raise RuntimeError, "Didn't receive expected INT" end.join rescue Interrupt puts "foo" @@ -1039,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 @@ -1051,7 +1054,7 @@ __FILE__: #{path.to_s.inspect} end context "signals not being trapped by bunder" do - let(:executable) { strip_whitespace <<-RUBY } + let(:executable) { <<~RUBY } #{shebang} signals = #{test_signals.inspect} @@ -1062,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") @@ -1079,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 @@ -1096,16 +1099,16 @@ __FILE__: #{path.to_s.inspect} puts `bundle exec echo foo` RUBY file.chmod(0o777) - bundle "exec #{file}", :env => { "PATH" => path } + bundle "exec #{file}", env: { "PATH" => path } expect(out).to eq("foo") end end 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 @@ -1146,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 @@ -1185,27 +1186,44 @@ __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 - system_gems("openssl-#{openssl_version}", :gem_repo => gem_repo4) + system_gems("openssl-#{openssl_version}", gem_repo: gem_repo4) file = bundled_app("require_openssl.rb") create_file(file, <<-RUBY) @@ -1218,15 +1236,15 @@ __FILE__: #{path.to_s.inspect} env = { "PATH" => path } aggregate_failures do - expect(bundle("exec #{file}", :artifice => nil, :env => env)).to eq(expected) - expect(bundle("exec bundle exec #{file}", :artifice => nil, :env => env)).to eq(expected) - expect(bundle("exec ruby #{file}", :artifice => nil, :env => env)).to eq(expected) - expect(run(file.read, :artifice => nil, :env => env)).to eq(expected) + expect(bundle("exec #{file}", 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? # sanity check that we get the newer, custom version without bundler - sys_exec "#{Gem.ruby} #{file}", :env => env, :raise_on_error => false + sys_exec "#{Gem.ruby} #{file}", env: env, raise_on_error: false expect(err).to include("custom openssl should not be loaded") end end @@ -1234,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 @@ -1246,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 409c49f9e1..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? @@ -71,7 +73,7 @@ RSpec.describe "bundle help" do it "has helpful output when using --help flag for a non-existent command" do with_fake_man do - bundle "instill -h", :raise_on_error => false + bundle "instill -h", raise_on_error: false end expect(err).to include('Could not find command "instill".') end diff --git a/spec/bundler/commands/info_spec.rb b/spec/bundler/commands/info_spec.rb index 2e17ee6dd8..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,40 +56,40 @@ 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 - bundle "info missing", :raise_on_error => false + bundle "info missing", raise_on_error: false 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 match(/The gem rails has been deleted/i) - expect(err).to match(default_bundle_path("gems", "rails-2.3.2").to_s) + expect(err).to include("The gem rails is missing.") + expect(err).to include(default_bundle_path("gems", "rails-2.3.2").to_s) bundle "info rail --path" - expect(err).to match(/The gem rails has been deleted/i) - expect(err).to match(default_bundle_path("gems", "rails-2.3.2").to_s) + expect(err).to include("The gem rails is missing.") + expect(err).to include(default_bundle_path("gems", "rails-2.3.2").to_s) bundle "info rails" - expect(err).to match(/The gem rails has been deleted/i) - expect(err).to match(default_bundle_path("gems", "rails-2.3.2").to_s) + expect(err).to include("The gem rails 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" @@ -167,13 +167,13 @@ RSpec.describe "bundle info" do end it "prints out branch names other than main" do - update_git "foo", :branch => "omg" do |s| + update_git "foo", branch: "omg" do |s| s.write "lib/foo.rb", "FOO = '1.0.omg'" end @revision = revision_for(lib_path("foo-1.0"))[0...6] 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 @@ -194,9 +194,9 @@ RSpec.describe "bundle info" do end it "handles when a version is a '-' prerelease" do - @git = build_git("foo", "1.0.0-beta.1", :path => lib_path("foo")) + @git = build_git("foo", "1.0.0-beta.1", path: lib_path("foo")) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + 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,40 +209,40 @@ 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 invalid_regexp = "[]" - bundle "info #{invalid_regexp}", :raise_on_error => false + bundle "info #{invalid_regexp}", raise_on_error: false expect(err).to include("Could not find gem '#{invalid_regexp}'.") end end 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 - bundle "info rails", :raise_on_error => false + bundle "info rails", raise_on_error: false expect(err).to include("Could not find gem 'rails', because it's in the group 'test', configured to be ignored.") end end diff --git a/spec/bundler/commands/init_spec.rb b/spec/bundler/commands/init_spec.rb index 6aa3e9edd1..989d6fa812 100644 --- a/spec/bundler/commands/init_spec.rb +++ b/spec/bundler/commands/init_spec.rb @@ -24,7 +24,7 @@ RSpec.describe "bundle init" do it "honours the current process umask when generating from a template" do FileUtils.mkdir(target_dir) - bundle :init, :dir => target_dir + bundle :init, dir: target_dir generated_mode = File.stat(File.join(target_dir, "Gemfile")).mode & 0o111 expect(generated_mode).to be_zero end @@ -32,17 +32,17 @@ RSpec.describe "bundle init" do context "when a Gemfile already exists" do before do - create_file "Gemfile", <<-G + gemfile <<-G gem "rails" G end it "does not change existing Gemfiles" do - expect { bundle :init, :raise_on_error => false }.not_to change { File.read(bundled_app_gemfile) } + expect { bundle :init, raise_on_error: false }.not_to change { File.read(bundled_app_gemfile) } end it "notifies the user that an existing Gemfile already exists" do - bundle :init, :raise_on_error => false + bundle :init, raise_on_error: false expect(err).to include("Gemfile already exists") end end @@ -55,7 +55,7 @@ RSpec.describe "bundle init" do FileUtils.mkdir bundled_app(subdir) - bundle :init, :dir => bundled_app(subdir) + bundle :init, dir: bundled_app(subdir) expect(out).to include("Writing new Gemfile") expect(bundled_app("#{subdir}/Gemfile")).to be_file @@ -71,7 +71,7 @@ RSpec.describe "bundle init" do mode = File.stat(bundled_app(subdir)).mode ^ 0o222 FileUtils.chmod mode, bundled_app(subdir) - bundle :init, :dir => bundled_app(subdir), :raise_on_error => false + bundle :init, dir: bundled_app(subdir), raise_on_error: false expect(err).to include("directory is not writable") expect(Dir[bundled_app("#{subdir}/*")]).to be_empty @@ -79,24 +79,24 @@ 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 end - bundle :init, :gemspec => spec_file + bundle :init, gemspec: spec_file gemfile = bundled_app_gemfile.read expect(gemfile).to match(%r{source 'https://rubygems.org'}) - 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 @@ -112,14 +112,14 @@ RSpec.describe "bundle init" do S end - bundle :init, :gemspec => spec_file, :raise_on_error => false + bundle :init, gemspec: spec_file, raise_on_error: false expect(err).to include("There was an error while loading `test.gemspec`") end end 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,17 +129,17 @@ 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 it "does not change existing Gemfiles" do - expect { bundle :init, :raise_on_error => false }.not_to change { File.read(bundled_app("gems.rb")) } + expect { bundle :init, raise_on_error: false }.not_to change { File.read(bundled_app("gems.rb")) } end it "notifies the user that an existing gems.rb already exists" do - bundle :init, :raise_on_error => false + bundle :init, raise_on_error: false expect(err).to include("gems.rb already exists") end end @@ -152,7 +152,7 @@ RSpec.describe "bundle init" do FileUtils.mkdir bundled_app(subdir) - bundle :init, :dir => bundled_app(subdir) + bundle :init, dir: bundled_app(subdir) expect(out).to include("Writing new gems.rb") expect(bundled_app("#{subdir}/gems.rb")).to be_file @@ -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 @@ -175,17 +175,17 @@ RSpec.describe "bundle init" do end it "should generate from an existing gemspec" do - bundle :init, :gemspec => spec_file + bundle :init, gemspec: spec_file gemfile = bundled_app("gems.rb").read expect(gemfile).to match(%r{source 'https://rubygems.org'}) - 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 it "prints message to user" do - bundle :init, :gemspec => spec_file + bundle :init, gemspec: spec_file expect(out).to include("Writing new gems.rb") end @@ -196,7 +196,7 @@ RSpec.describe "bundle init" do it "should use the --gemfile value to name the gemfile" do custom_gemfile_name = "NiceGemfileName" - bundle :init, :gemfile => custom_gemfile_name + bundle :init, gemfile: custom_gemfile_name expect(out).to include("Writing new #{custom_gemfile_name}") used_template = File.read("#{source_root}/lib/bundler/templates/Gemfile") diff --git a/spec/bundler/commands/inject_spec.rb b/spec/bundler/commands/inject_spec.rb deleted file mode 100644 index 92e86bd6cc..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(/trying to install in deployment mode after changing/) - - 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 f572bdf176..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 @@ -12,7 +12,7 @@ RSpec.describe "bundle install with gem sources" do end it "does not make a lockfile if the install fails" do - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false raise StandardError, "FAIL" G @@ -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,15 +114,38 @@ 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) - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false raise StandardError, "FAIL" G @@ -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 + install_gemfile <<-G, raise_on_error: false + 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 @@ -197,7 +291,7 @@ RSpec.describe "bundle install with gem sources" do end it "does not reinstall any gem that is already available locally" do - system_gems "activesupport-2.3.2", :path => default_bundle_path + system_gems "activesupport-2.3.2", path: default_bundle_path build_repo2 do build_gem "activesupport", "2.3.2" do |s| @@ -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 @@ -214,150 +308,130 @@ RSpec.describe "bundle install with gem sources" do end it "works when the gemfile specifies gems that only exist in the system" do - build_gem "foo", :to_bundle => true + build_gem "foo", to_bundle: true install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + 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,20 +469,20 @@ 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 it "does not throw a warning if a gem is added once in Gemfile and also inside a gemspec as a development dependency" do - build_lib "my-gem", :path => bundled_app do |s| + build_lib "my-gem", path: bundled_app do |s| s.add_development_dependency "my-private-gem" end @@ -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,62 +504,276 @@ RSpec.describe "bundle install with gem sources" do expect(the_bundle).to include_gems("my-private-gem 1.0") end + 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 + + 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", group: :development + G + + bundle :install + + expect(err).to be_empty + + expect(the_bundle).to include_gems("rubocop 1.36.0") + end + + it "raises an error if a gem is added once in Gemfile and also inside a gemspec as a development dependency, with incompatible requirements" do + build_lib "my-gem", path: bundled_app do |s| + s.add_development_dependency "rubocop", "~> 1.36.0" + end + + build_repo4 do + build_gem "rubocop", "1.36.0" + build_gem "rubocop", "1.37.1" + end + + gemfile <<~G + source "https://gem.repo4" + + gemspec + + gem "rubocop", "~> 1.37.0", group: :development + G + + bundle :install, raise_on_error: false + + 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("my-gem-1") + gem2 = tmp("my-gem-2") + + build_lib "my-gem", path: gem1 do |s| + s.add_development_dependency "rubocop", "~> 1.36.0" + end + + build_lib "my-gem-2", path: gem2 do |s| + s.add_development_dependency "rubocop", "~> 1.36.0" + end + + build_repo4 do + build_gem "rubocop", "1.36.0" + end + + gemfile <<~G + source "https://gem.repo4" + + gemspec path: "#{gem1}" + gemspec path: "#{gem2}" + G + + bundle :install + + expect(err).to be_empty + expect(the_bundle).to include_gems("rubocop 1.36.0") + end + + it "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 "rubocop", "1.36.0" + end + + gemfile <<~G + source "https://gem.repo4" + + gemspec path: "#{gem1}" + gemspec path: "#{gem2}" + G + + bundle :install + + expect(err).to be_empty + expect(the_bundle).to include_gems("rubocop 1.36.0") + end + + it "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 + + 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 + build_lib "my-gem", path: bundled_app do |s| + s.add_development_dependency "activesupport" + end + + build_repo4 do + build_gem "activesupport" + end + + build_git "activesupport", "1.0", path: lib_path("activesupport") + + install_gemfile <<~G + source "https://gem.repo4" + + gemspec + + gem "activesupport", :git => "#{lib_path("activesupport")}" + G + + expect(err).to be_empty + expect(the_bundle).to include_gems "activesupport 1.0", source: "git@#{lib_path("activesupport")}" + + # if the Gemfile dependency is specified first + install_gemfile <<~G + source "https://gem.repo4" + + gem "activesupport", :git => "#{lib_path("activesupport")}" + + gemspec + G + + expect(err).to be_empty + expect(the_bundle).to include_gems "activesupport 1.0", source: "git@#{lib_path("activesupport")}" + end + + it "considers both dependencies for resolution if a gem is added once in Gemfile and also inside a local gemspec as a runtime dependency, with different requirements" do + build_lib "my-gem", path: bundled_app do |s| + s.add_dependency "rubocop", "~> 1.36.0" + end + + build_repo4 do + build_gem "rubocop", "1.36.0" + build_gem "rubocop", "1.37.1" + end + + gemfile <<~G + source "https://gem.repo4" + + gemspec + + gem "rubocop" + G + + bundle :install + + expect(err).to be_empty + expect(the_bundle).to include_gems("rubocop 1.36.0") + end + it "throws an error if a gem is added twice in Gemfile when version of one dependency is not specified" do - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo2)}" - gem "rack" - gem "rack", "1.0" + install_gemfile <<-G, raise_on_error: false + 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" + install_gemfile <<-G, raise_on_error: false + 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)}" + install_gemfile <<-G, artifice: nil, raise_on_error: false + 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 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"]] + build_repo2(build_compact_index: false) do + build_gem "ajp-rails", "0.0.0", gemspec: false, skip_validation: true do |s| + 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)}" + install_gemfile <<-G, full_index: true, raise_on_error: false + 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 @@ -495,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 @@ -506,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 @@ -514,13 +802,11 @@ RSpec.describe "bundle install with gem sources" do end describe "Ruby version in Gemfile.lock" do - include Bundler::GemHelpers - context "and using an unsupported Ruby version" do it "prints an error" do - install_gemfile <<-G, :raise_on_error => false + 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 @@ -530,56 +816,59 @@ 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_enabled expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS #{lockfile_platforms} 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_enabled + expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: PLATFORMS #{lockfile_platforms} 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 @@ -598,14 +887,14 @@ 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| file.puts gemfile end - bundle :install, :dir => root_dir + bundle :install, dir: root_dir end it "doesn't blow up when using the `gemspec` DSL" do @@ -613,35 +902,35 @@ RSpec.describe "bundle install with gem sources" do FileUtils.mkdir_p(root_dir) - build_lib "foo", :path => root_dir + build_lib "foo", path: root_dir gemfile = <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gemspec G File.open("#{root_dir}/Gemfile", "w") do |file| file.puts gemfile end - bundle :install, :dir => root_dir + bundle :install, dir: root_dir end end 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 + bundle :install, quiet: true expect(out).to be_empty expect(err).to be_empty 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" @@ -661,55 +950,55 @@ 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 - bundle :install, :quiet => true, :raise_on_error => false, :env => { "RUBYOPT" => "-r#{bundled_app("install_with_warning.rb")}" } + bundle :install, quiet: true, raise_on_error: false, env: { "RUBYOPT" => "-r#{bundled_app("install_with_warning.rb")}" } expect(out).to be_empty expect(err).to include("Could not find gem 'non-existing-gem'") expect(err).to include("BOOOOO") 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 :install, :raise_on_error => false + 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 + bundle :install, raise_on_error: false ensure FileUtils.chmod("+x", gems_path) end @@ -717,29 +1006,119 @@ 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 + bundle :install, raise_on_error: false ensure FileUtils.chmod("+x", extensions_path) end @@ -753,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") } @@ -765,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 @@ -779,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 @@ -790,40 +1169,337 @@ RSpec.describe "bundle install with gem sources" do end end + describe "when gem home does not have the writable bit set, yet it's still writable", :permissions do + let(:gem_home) { bundled_app("vendor/#{Bundler.ruby_scope}") } + + before do + build_repo4 do + build_gem "foo", "1.0.0" do |s| + s.write "CHANGELOG.md", "foo" + end + end + + gemfile <<-G + source "https://gem.repo4" + gem 'foo' + G + end + + it "should still work" do + bundle_config "path vendor" + bundle :install + expect(out).to include("Bundle complete!") + expect(err).to be_empty + + FileUtils.chmod("-w", gem_home) + + begin + bundle "install --force" + ensure + FileUtils.chmod("+w", gem_home) + end + + expect(out).to include("Bundle complete!") + expect(err).to be_empty + end + end + + describe "when gems path is world writable (no sticky bit set)", :permissions do + let(:gems_path) { bundled_app("vendor/#{Bundler.ruby_scope}/gems") } + + before do + build_repo4 do + build_gem "foo", "1.0.0" do |s| + s.write "CHANGELOG.md", "foo" + end + end + + gemfile <<-G + source "https://gem.repo4" + gem 'foo' + G + end + + it "should display a proper message to explain the problem" do + bundle_config "path vendor" + bundle :install + expect(out).to include("Bundle complete!") + expect(err).to be_empty + + FileUtils.chmod(0o777, gems_path) + + bundle "install --force", raise_on_error: false + + expect(err).to include("Bundler cannot reinstall foo-1.0.0 because there's a previous installation of it at #{gems_path}/foo-1.0.0 that is unsafe to remove") + end + 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 + describe "when bundle cache path does not have write access", :permissions do let(:cache_path) { bundled_app("vendor/#{Bundler.ruby_scope}/cache") } 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 :install, :raise_on_error => false + 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 "install", :standalone => true + 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 @@ -837,30 +1513,34 @@ RSpec.describe "bundle install with gem sources" do end it "should display a helpful message explaining how to fix it" do - bundle :install, :env => { "BUNDLE_RUBYGEMS__ORG" => "user:pass{word" }, :raise_on_error => false + bundle :install, env: { "BUNDLE_RUBYGEMS__ORG" => "user:pass{word" }, raise_on_error: false expect(exitstatus).to eq(17) expect(err).to eq("Please CGI escape your usernames and passwords before " \ "setting them for authentication.") 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) @@ -871,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 "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 "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 "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. " \ @@ -890,23 +1596,23 @@ RSpec.describe "bundle install with gem sources" do context "with missing platform specific gems in lockfile" do before do build_repo4 do - build_gem "racc", "1.5.2" + build_gem "racca", "1.5.2" build_gem "nokogiri", "1.12.4" do |s| s.platform = "x86_64-darwin" - s.add_runtime_dependency "racc", "~> 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 "racc", "~> 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 @@ -918,6 +1624,13 @@ RSpec.describe "bundle install with gem sources" do gem "loofah", "~> 2.12.0" G + checksums = checksums_section do |c| + c.checksum gem_repo4, "crass", "1.0.6" + c.checksum gem_repo4, "loofah", "2.12.0" + c.checksum gem_repo4, "nokogiri", "1.12.4", "x86_64-darwin" + c.checksum gem_repo4, "racca", "1.5.2" + end + lockfile <<-L GEM remote: https://gem.repo4/ @@ -927,8 +1640,8 @@ RSpec.describe "bundle install with gem sources" do crass (~> 1.0.2) nokogiri (>= 1.5.9) nokogiri (1.12.4-x86_64-darwin) - racc (~> 1.4) - racc (1.5.2) + racca (~> 1.4) + racca (1.5.2) PLATFORMS x86_64-darwin-20 @@ -936,20 +1649,28 @@ RSpec.describe "bundle install with gem sources" do DEPENDENCIES loofah (~> 2.12.0) - + #{checksums} RUBY VERSION #{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" + bundle "install", artifice: "compact_index" + end + + 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" + c.checksum gem_repo4, "racca", "1.5.2" + c.checksum gem_repo4, "nokogiri", "1.12.4", "x86_64-linux" end expect(lockfile).to eq <<~L @@ -961,10 +1682,10 @@ RSpec.describe "bundle install with gem sources" do crass (~> 1.0.2) nokogiri (>= 1.5.9) nokogiri (1.12.4-x86_64-darwin) - racc (~> 1.4) + racca (~> 1.4) nokogiri (1.12.4-x86_64-linux) - racc (~> 1.4) - racc (1.5.2) + racca (~> 1.4) + racca (1.5.2) PLATFORMS x86_64-darwin-20 @@ -972,27 +1693,77 @@ RSpec.describe "bundle install with gem sources" do DEPENDENCIES 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)}" + install_gemfile <<-G, local: true + source "https://gem.repo1" source "https://not-existing-source" do - gem "rack" + gem "myrack" end G @@ -1002,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 + + 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 - system_gems "foo-1.0.0", :path => default_bundle_path, :gem_repo => gem_repo4 + it "uses the locally available version for sub-dependencies when possible" do + install_gemfile <<-G, "prefer-local": true, verbose: true + source "https://gem.repo4" + + gem "a" + G + + 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 @@ -1048,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 @@ -1062,7 +1901,7 @@ RSpec.describe "bundle install with gem sources" do File.symlink("../../README.markdown", File.join(man_path, "README.markdown")) build_repo4 do - build_gem "binman", :path => gem_repo4("gems"), :lib_path => binman_path, :no_default => true do |s| + build_gem "binman", path: gem_repo4("gems"), lib_path: binman_path, no_default: true do |s| s.files = ["README.markdown", "man/man0/README.markdown"] end end @@ -1070,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 @@ -1092,15 +1931,185 @@ 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 - bundle "install --jobs 1", :raise_on_error => false + bundle "install --jobs 1", raise_on_error: false expect(err).not_to include("ERROR REPORT TEMPLATE") expect(err).to include("Could not find compatible versions") end end + + context "when a lockfile has unmet dependencies, and the Gemfile has no resolution" do + before do + build_repo4 do + build_gem "aaa", "0.2.0" do |s| + s.add_dependency "zzz", "< 0.2.0" + end + + build_gem "zzz", "0.2.0" + end + + gemfile <<~G + source "https://gem.repo4" + + gem "aaa" + gem "zzz" + G + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + aaa (0.2.0) + zzz (< 0.2.0) + zzz (0.2.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + aaa! + zzz! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "does not install, but raises a resolution error" do + bundle "install", raise_on_error: false + expect(err).to include("Could not find compatible versions") + end + end + + context "when --jobs option given" do + before do + install_gemfile "source '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 66930ded75..c890646a81 100644 --- a/spec/bundler/commands/list_spec.rb +++ b/spec/bundler/commands/list_spec.rb @@ -1,9 +1,31 @@ # 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 + bundle "list --name-only --paths", raise_on_error: false expect(err).to eq "The `--name-only` and `--paths` options cannot be used together" end @@ -11,18 +33,32 @@ RSpec.describe "bundle list" do context "with without-group and only-group option" do it "raises an error" do - bundle "list --without-group dev --only-group test", :raise_on_error => false + bundle "list --without-group dev --only-group test", raise_on_error: false expect(err).to eq "The `--only-group` and `--without-group` options cannot be used together" end 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,15 +68,26 @@ 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 it "raises an error" do - bundle "list --without-group random", :raise_on_error => false + bundle "list --without-group random", raise_on_error: false expect(err).to eq "`random` group could not be found." end @@ -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,14 +130,23 @@ 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 it "raises an error" do - bundle "list --only-group random", :raise_on_error => false + bundle "list --only-group random", raise_on_error: false expect(err).to eq "`random` group could not be found." end @@ -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,49 +187,79 @@ 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" end - build_git "git_test", "1.0.0", :path => lib_path("git_test") + build_git "git_test", "1.0.0", path: lib_path("git_test") - build_lib("gemspec_test", :path => tmp.join("gemspec_test")) do |s| + build_lib("gemspec_test", path: tmp("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 8663cda643..8ab3cc7e8d 100644 --- a/spec/bundler/commands/lock_spec.rb +++ b/spec/bundler/commands/lock_spec.rb @@ -1,27 +1,22 @@ # frozen_string_literal: true RSpec.describe "bundle lock" do - def strip_lockfile(lockfile) - strip_whitespace(lockfile).sub(/\n\Z/, "") - end - - def read_lockfile(file = "Gemfile.lock") - strip_lockfile bundled_app(file).read - end - - let(:repo) { gem_repo1 } - - before :each do - gemfile <<-G - source "#{file_uri_for(repo)}" - gem "rails" - gem "weakling" - gem "foo" - G + 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 = strip_lockfile(<<-L) + <<~L GEM - remote: #{file_uri_for(repo)}/ + remote: https://gem.repo4/ specs: actionmailer (2.3.2) activesupport (= 2.3.2) @@ -38,8 +33,8 @@ RSpec.describe "bundle lock" do actionpack (= 2.3.2) activerecord (= 2.3.2) activeresource (= 2.3.2) - rake (= 13.0.1) - rake (13.0.1) + rake (= #{rake_version}) + rake (#{rake_version}) weakling (0.0.3) PLATFORMS @@ -49,54 +44,246 @@ RSpec.describe "bundle lock" do foo rails weakling + #{checksums} + BUNDLED WITH + #{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} + #{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) + expect(out).to eq(expected_lockfile.chomp) end it "prints a lockfile when there is an existing lockfile with --print" do - lockfile @lockfile + gemfile_with_rails_weakling_and_foo_from_repo4 + + lockfile expected_lockfile + + bundle "lock --print" + + expect(out).to eq(expected_lockfile.chomp) + end + + it "prints a lockfile when there is an existing checksums lockfile with --print" do + gemfile_with_rails_weakling_and_foo_from_repo4 + + lockfile expected_lockfile bundle "lock --print" - expect(out).to eq(@lockfile) + 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 + gemfile_with_rails_weakling_and_foo_from_repo4 + + lockfile expected_lockfile + + bundle "lock --print" + + expect(out).to eq(expected_lockfile.chomp) + end + + it "touches the lockfile when there is an existing lockfile that does not need changes" do + gemfile_with_rails_weakling_and_foo_from_repo4 + + lockfile expected_lockfile + + expect do + bundle "lock" + end.to change { bundled_app_lock.mtime } + end + + it "does not touch lockfile with --print" do + gemfile_with_rails_weakling_and_foo_from_repo4 + + lockfile expected_lockfile + + expect do + bundle "lock --print" + end.not_to change { bundled_app_lock.mtime } end it "writes a lockfile when there is an outdated lockfile using --update" do - lockfile @lockfile.gsub("2.3.2", "2.3.1") + gemfile_with_rails_weakling_and_foo_from_repo4 + + lockfile outdated_lockfile bundle "lock --update" - expect(read_lockfile).to eq(@lockfile) + expect(read_lockfile).to eq(expected_lockfile) + end + + it "prints an updated lockfile when there is an outdated lockfile using --print --update" do + gemfile_with_rails_weakling_and_foo_from_repo4 + + lockfile outdated_lockfile + + bundle "lock --print --update" + + 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 + gemfile_with_rails_weakling_and_foo_from_repo4 + + lockfile outdated_lockfile + + bundle "lock --update", env: { "BUNDLE_FROZEN" => "true" } + + expect(read_lockfile).to eq(expected_lockfile) end it "does not fetch remote specs when using the --local option" do - bundle "lock --update --local", :raise_on_error => false + gemfile_with_rails_weakling_and_foo_from_repo4 + + bundle "lock --update --local", raise_on_error: false expect(err).to match(/locally installed gems/) end + it "does not fetch remote checksums with --local" do + gemfile_with_rails_weakling_and_foo_from_repo4 + + lockfile expected_lockfile + + bundle "lock --print --local" + + 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 - lockfile = strip_lockfile(<<-L) + 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) @@ -105,60 +292,394 @@ RSpec.describe "bundle lock" do DEPENDENCIES 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(@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_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: https://gem.repo4/ + specs: + actionmailer (2.3.2) + activesupport (= 2.3.2) + actionpack (2.3.2) + activesupport (= 2.3.2) + activerecord (2.3.2) + activesupport (= 2.3.2) + activeresource (2.3.2) + activesupport (= 2.3.2) + activesupport (2.3.2) + foo (1.0) + rails (2.3.2) + actionmailer (= 2.3.2) + actionpack (= 2.3.2) + activerecord (= 2.3.2) + activeresource (= 2.3.2) + rake (= #{rake_version}) + rake (#{rake_version}) + weakling (0.0.3) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo + rails + weakling + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + expect(out).to match(/Writing lockfile to.+lock/) - expect(read_lockfile("lock")).to eq(@lockfile) + expect(read_lockfile("lock")).to eq(lockfile) end it "update specific gems using --update" do - lockfile @lockfile.gsub("2.3.2", "2.3.1").gsub("13.0.1", "10.0.1") + 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(@lockfile) + 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 + 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 + + bundle "lock" + + expect(read_lockfile).to eq(previous_lockfile) + 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 "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 "https://gem.repo1" + gem "foo", :git => "#{lib_path("foo-1.0")}/" + G + + expect(out).to include("using resolution from the lockfile") + expect(out).not_to include("re-resolving dependencies because the list of sources changed") + end + + it "updates specific gems using --update using the locked revision of unrelated git gems for resolving" do + gemfile_with_rails_weakling_and_foo_from_repo4 + + ref = build_git("foo").ref_for("HEAD") + + gemfile <<-G + source "https://gem.repo1" + gem "rake" + gem "foo", :git => "#{lib_path("foo-1.0")}", :branch => "deadbeef" + G + + lockfile <<~L + GIT + remote: #{lib_path("foo-1.0")} + revision: #{ref} + branch: deadbeef + specs: + foo (1.0) + + GEM + remote: https://gem.repo1/ + specs: + rake (10.0.1) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo! + rake + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "lock --update rake --verbose" + expect(out).to match(/Writing lockfile to.+lock/) + expect(lockfile).to include("rake (#{rake_version})") end it "errors when updating a missing specific gems using --update" do - lockfile @lockfile + gemfile_with_rails_weakling_and_foo_from_repo4 + + lockfile expected_lockfile - bundle "lock --update blahblah", :raise_on_error => false + bundle "lock --update blahblah", raise_on_error: false expect(err).to eq("Could not find gem 'blahblah'.") - expect(read_lockfile).to eq(@lockfile) + 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 "lock" + bundle_config "without test" + bundle_config "path vendor/bundle" + bundle "lock", verbose: true expect(bundled_app("vendor/bundle")).not_to exist end @@ -185,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' @@ -194,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 @@ -205,74 +726,205 @@ 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 + + context "conservative updates when minor update adds a new dependency" do + before do + build_repo4 do + build_gem "sequel", "5.71.0" + build_gem "sequel", "5.72.0" do |s| + s.add_dependency "bigdecimal", ">= 0" + end + build_gem "bigdecimal", %w[1.4.4 99.1.4] end + + gemfile <<~G + source "https://gem.repo4" + gem 'sequel' + G + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + sequel (5.71.0) + + PLATFORMS + ruby + + DEPENDENCIES + sequel + + BUNDLED WITH + #{Bundler::VERSION} + L + + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + end + + it "adds the latest version of the new dependency" do + bundle "lock --minor --update sequel" + + expect(the_bundle.locked_specs).to eq(%w[sequel-5.72.0 bigdecimal-99.1.4].sort) end end - it "updates the bundler version in the lockfile without re-resolving", :rubygems => ">= 3.3.0.dev" do + it "updates the bundler version in the lockfile to the latest bundler version" do build_repo4 do - build_gem "rack", "1.0" + build_gem "bundler", "55" end - install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" - gem "rack" + 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://gem.repo4" G lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, '\11.0.0\2') - FileUtils.rm_r gem_repo4 + 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") - bundle "lock --update --bundler" - expect(the_bundle).to include_gem "rack 1.0" + 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") + end + + it "supports adding new platforms when there's no previous lockfile" do + gemfile_with_rails_weakling_and_foo_from_repo4 + + bundle "lock --add-platform java x86-mingw32 --verbose" + expect(out).to include("Resolving dependencies because there's no lockfile") allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) - expect(the_bundle.locked_gems.bundler_version).to eq v(Bundler::VERSION) + expect(the_bundle.locked_platforms).to match_array(default_platform_list("java", "x86-mingw32")) end - it "supports adding new platforms" do - bundle "lock --add-platform java x86-mingw32" + it "supports adding new platforms when a previous lockfile exists" do + gemfile_with_rails_weakling_and_foo_from_repo4 + + bundle "lock" + bundle "lock --add-platform java x86-mingw32 --verbose" + expect(out).to include("Found changes from the lockfile, re-resolving dependencies because you are adding a new platform to your lockfile") allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) - lockfile = Bundler::LockfileParser.new(read_lockfile) - expect(lockfile.platforms).to match_array([java, x86_mingw32, local_platform].uniq) + 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) @@ -285,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(["ruby", local_platform].uniq) + 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([java, x86_mingw32, local_platform].uniq) + 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([x86_mingw32, local_platform].uniq) + 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 @@ -327,9 +982,14 @@ RSpec.describe "bundle lock" do end end + 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 @@ -337,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) @@ -348,18 +1008,20 @@ RSpec.describe "bundle lock" do DEPENDENCIES nokogiri - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L + checksums.delete("nokogiri", Gem::Platform::RUBY) + simulate_platform "x86_64-darwin-22" do bundle "lock --remove-platform ruby" end expect(lockfile).to eq <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: nokogiri (1.12.0-x86_64-darwin) @@ -368,14 +1030,16 @@ RSpec.describe "bundle lock" do DEPENDENCIES nokogiri - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end it "errors when removing all platforms" do - bundle "lock --remove-platform #{local_platform}", :raise_on_error => false + 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 @@ -384,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" @@ -410,17 +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 - 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) @@ -436,17 +1107,20 @@ RSpec.describe "bundle lock" do DEPENDENCIES gssapi mixlib-shellout - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G - bundle "config set --local force_ruby_platform true" + bundle_config "force_ruby_platform true" bundle :lock + checksums.checksum gem_repo4, "ffi", "1.9.14" + 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) @@ -465,9 +1139,9 @@ RSpec.describe "bundle lock" do DEPENDENCIES gssapi mixlib-shellout - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -484,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) @@ -504,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 @@ -524,28 +1198,34 @@ 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")) { bundle "lock" } + simulate_platform("x86_64-darwin-19") { bundle "lock" } + + 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) PLATFORMS - x86_64-darwin + x86_64-darwin-19 + x86_64-darwin-20 DEPENDENCIES libv8 - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -560,15 +1240,20 @@ RSpec.describe "bundle lock" do end end + 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) @@ -578,15 +1263,15 @@ RSpec.describe "bundle lock" do DEPENDENCIES 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) @@ -609,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) @@ -638,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 @@ -726,33 +1446,128 @@ 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 } + bundle "install", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } end context "when an update is available" do - let(:repo) { gem_repo2 } - before do - lockfile(@lockfile) - build_repo2 do + gemfile_with_rails_weakling_and_foo_from_repo4 + + build_repo4 do build_gem "foo", "2.0" end + + lockfile(expected_lockfile) end it "does not implicitly update" do bundle "lock" - expect(read_lockfile).to eq(@lockfile) + 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: https://gem.repo4/ + specs: + actionmailer (2.3.2) + activesupport (= 2.3.2) + actionpack (2.3.2) + activesupport (= 2.3.2) + activerecord (2.3.2) + activesupport (= 2.3.2) + activeresource (2.3.2) + activesupport (= 2.3.2) + activesupport (2.3.2) + foo (1.0) + rails (2.3.2) + actionmailer (= 2.3.2) + actionpack (= 2.3.2) + activerecord (= 2.3.2) + activeresource (= 2.3.2) + rake (= #{rake_version}) + rake (#{rake_version}) + weakling (0.0.3) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo + rails + weakling + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + expect(read_lockfile).to eq(expected_lockfile) end it "accounts for changes in the gemfile" do gemfile gemfile.gsub('"foo"', '"foo", "2.0"') bundle "lock" - expect(read_lockfile).to eq(@lockfile.sub("foo (1.0)", "foo (2.0)").sub(/foo$/, "foo (= 2.0)")) + checksums = checksums_section_when_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: https://gem.repo4/ + specs: + actionmailer (2.3.2) + activesupport (= 2.3.2) + actionpack (2.3.2) + activesupport (= 2.3.2) + activerecord (2.3.2) + activesupport (= 2.3.2) + activeresource (2.3.2) + activesupport (= 2.3.2) + activesupport (2.3.2) + foo (2.0) + rails (2.3.2) + actionmailer (= 2.3.2) + actionpack (= 2.3.2) + activerecord (= 2.3.2) + activeresource (= 2.3.2) + rake (= #{rake_version}) + rake (#{rake_version}) + weakling (0.0.3) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo (= 2.0) + rails + weakling + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + expect(read_lockfile).to eq(expected_lockfile) end end @@ -766,8 +1581,8 @@ RSpec.describe "bundle lock" do build_gem "irb", "1.5.0" end - system_gems "irb-1.5.0", :gem_repo => gem_repo4 - system_gems "debug-1.6.3", :gem_repo => gem_repo4 + system_gems "irb-1.5.0", gem_repo: gem_repo4 + system_gems "debug-1.6.3", gem_repo: gem_repo4 # simulate gemspec with wrong empty dependencies debug_gemspec_path = system_gem_path("specifications/debug-1.6.3.gemspec") @@ -778,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) @@ -796,9 +1616,9 @@ RSpec.describe "bundle lock" do DEPENDENCIES debug - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L simulate_platform "arm64-darwin-22" do @@ -807,7 +1627,7 @@ RSpec.describe "bundle lock" do 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) @@ -819,9 +1639,67 @@ RSpec.describe "bundle lock" do DEPENDENCIES debug + #{checksums} + BUNDLED WITH + #{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} + #{Bundler::VERSION} L end end @@ -846,13 +1724,13 @@ 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" G - bundle "lock", :raise_on_error => false + bundle "lock", raise_on_error: false expect(err).to eq <<~ERR.strip Could not find compatible versions @@ -860,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)}/ 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 @@ -901,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" @@ -909,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) @@ -917,41 +1795,44 @@ RSpec.describe "bundle lock" do activemodel (>= 6.0.4) PLATFORMS - #{lockfile_platforms} + #{local_platform} DEPENDENCIES activeadmin (= 2.13.1) 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 + + bundle "lock", raise_on_error: false + expect(err).to eq(expected_error) + + 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) end it "does not accidentally resolves to prereleases" do @@ -971,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 @@ -980,6 +1861,31 @@ RSpec.describe "bundle lock" do expect(lockfile).to include("autobuild (1.10.1)") end + # Newer rails depends on Bundler, while ancient Rails does not. Bundler tries + # a first resolution pass that does not consider pre-releases. However, when + # using a pre-release Bundler (like the .dev version), that results in that + # pre-release being ignored and resolving to a version that does not depend on + # Bundler at all. We should avoid that and still consider .dev Bundler. + # + it "does not ignore prereleases with there's only one candidate" do + build_repo4 do + build_gem "rails", "7.4.0.2" do |s| + s.add_dependency "bundler", ">= 1.15.0" + end + + build_gem "rails", "2.3.18" + end + + gemfile <<~G + source "https://gem.repo4" + gem "rails" + G + + bundle "lock" + expect(lockfile).to_not include("rails (2.3.18)") + expect(lockfile).to include("rails (7.4.0.2)") + end + it "deals with platform specific incompatibilities" do build_repo4 do build_gem "activerecord", "6.0.6" @@ -994,19 +1900,422 @@ 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 simulate_platform "universal-java-19" do - bundle "lock", :raise_on_error => false + bundle "lock", raise_on_error: false end expect(err).to include("Could not find compatible versions") expect(err).not_to include("ERROR REPORT TEMPLATE") end + 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 @@ -1020,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 @@ -1032,4 +2341,537 @@ RSpec.describe "bundle lock" do expect(lockfile).not_to include("tzinfo-data (1.2022.7)") end end + + context "when resolving platform specific gems as indirect dependencies on truffleruby", :truffleruby_only do + before do + build_lib "foo", path: bundled_app do |s| + s.add_dependency "nokogiri" + end + + build_repo4 do + build_gem "nokogiri", "1.14.2" + build_gem "nokogiri", "1.14.2" do |s| + s.platform = "x86_64-linux" + end + end + + gemfile <<-G + source "https://gem.repo4" + gemspec + G + end + + it "locks both ruby and platform specific specs" 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 "lock" + 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 + ruby + x86_64-linux + + DEPENDENCIES + foo! + #{checksums} + BUNDLED WITH + #{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 + before do + build_repo4 do + build_gem "govuk_app_config", "0.1.0" + + build_gem "govuk_app_config", "4.13.0" do |s| + s.add_dependency "railties", ">= 5.0" + end + + %w[7.0.4.1 7.0.4.3].each do |v| + build_gem "railties", v do |s| + s.add_dependency "actionpack", v + s.add_dependency "activesupport", v + end + + build_gem "activesupport", v + build_gem "actionpack", v + end + end + + gemfile <<~G + source "https://gem.repo4" + + gem "govuk_app_config" + gem "activesupport", "7.0.4.3" + G + + # Simulate out of sync lockfile because top level dependency on + # activesuport has just been added to the Gemfile, and locked to a higher + # version + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + actionpack (7.0.4.1) + activesupport (7.0.4.1) + govuk_app_config (4.13.0) + railties (>= 5.0) + railties (7.0.4.1) + actionpack (= 7.0.4.1) + activesupport (= 7.0.4.1) + + PLATFORMS + arm64-darwin-22 + + DEPENDENCIES + govuk_app_config + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "does not downgrade top level dependencies" do + checksums = checksums_section_when_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" + c.no_checksum "railties", "7.0.4.3" + end + + simulate_platform "arm64-darwin-22" do + bundle "lock" + end + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + actionpack (7.0.4.3) + activesupport (7.0.4.3) + govuk_app_config (4.13.0) + railties (>= 5.0) + railties (7.0.4.3) + actionpack (= 7.0.4.3) + activesupport (= 7.0.4.3) + + PLATFORMS + arm64-darwin-22 + + DEPENDENCIES + activesupport (= 7.0.4.3) + govuk_app_config + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + 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 970e51b8ef..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 "exec rubocop --debug --config .rubocop.yml", :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 "exec standardrb --debug", :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,8 +80,10 @@ RSpec.describe "bundle gem" do Dir.mkdir(bundled_app("path with spaces")) end - it "properly initializes git repo", :readline do - bundle "gem #{gem_name}", :dir => bundled_app("path with spaces") + 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 end @@ -103,8 +123,8 @@ RSpec.describe "bundle gem" do expect(bundled_app("#{gem_name}/README.md").read).to match(%r{https://github\.com/bundleuser/#{gem_name}/blob/.*/CODE_OF_CONDUCT.md}) end - it "generates the README with a section for the Code of Conduct, respecting the configured git default branch", :git => ">= 2.28.0" do - sys_exec("git config --global init.defaultBranch main") + it "generates the README with a section for the Code of Conduct, respecting the configured git default branch", git: ">= 2.28.0" do + 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,26 +462,32 @@ 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-13.0.1"] - path = Bundler.feature_flag.default_install_uses_path? ? local_gem_path(:base => bundled_app("newgem")) : system_gem_path - system_gems gems, :path => path - bundle "exec rake build", :dir => bundled_app("newgem") + 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") - bundle "gem .", :dir => bundled_app("tmp") + bundle "gem .", dir: bundled_app("tmp") expect(bundled_app("tmp/lib/tmp.rb")).to exist end @@ -476,7 +495,7 @@ RSpec.describe "bundle gem" do it "resolves .." do create_temporary_dir("temp/empty_dir") - bundle "gem ..", :dir => bundled_app("temp/empty_dir") + bundle "gem ..", dir: bundled_app("temp/empty_dir") expect(bundled_app("temp/lib/temp.rb")).to exist end @@ -484,7 +503,7 @@ RSpec.describe "bundle gem" do it "resolves relative directory" do create_temporary_dir("tmp/empty/tmp") - bundle "gem ../../empty", :dir => bundled_app("tmp/empty/tmp") + bundle "gem ../../empty", dir: bundled_app("tmp/empty/tmp") expect(bundled_app("tmp/empty/lib/empty.rb")).to exist end @@ -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,811 +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 "runs rake without problems" do + context "gem.test setting set to rspec" do + before do + bundle_config "gem.test rspec" bundle "gem #{gem_name}" + end - system_gems ["rake-13.0.1"] + 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 - rakefile = strip_whitespace <<-RAKEFILE - task :default do - puts 'SUCCESS' - end - RAKEFILE - File.open(bundled_app("#{gem_name}/Rakefile"), "w") do |file| - file.puts rakefile - end + it "includes .rspec and spec/ into ignore list" do + expect(ignore_paths).to include(".rspec") + expect(ignore_paths).to include("spec/") + end + end - sys_exec(rake, :dir => bundled_app(gem_name)) - expect(out).to include("SUCCESS") + 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 - context "--exe parameter set" do - before do - bundle "gem #{gem_name} --exe" - 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 "builds exe skeleton" do - expect(bundled_app("#{gem_name}/exe/#{gem_name}")).to exist - end + it "includes test/ into ignore list" do + expect(ignore_paths).to include("test/") + end + end - it "requires the main file" do - expect(bundled_app("#{gem_name}/exe/#{gem_name}").read).to match(/require "#{require_path}"/) - end + context "--test parameter set to minitest" do + before do + bundle "gem #{gem_name} --test=minitest" end - context "--bin parameter set" do - before do - bundle "gem #{gem_name} --bin" - 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 exe skeleton" do - expect(bundled_app("#{gem_name}/exe/#{gem_name}")).to exist - 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 "requires the main file" do - expect(bundled_app("#{gem_name}/exe/#{gem_name}").read).to match(/require "#{require_path}"/) - end + it "includes test/ into ignore list" do + expect(ignore_paths).to include("test/") end - context "no --test parameter" do - before do - bundle "gem #{gem_name}" - end + it "creates a default rake task to run the test suite" do + rakefile = <<~RAKEFILE + # frozen_string_literal: true - it_behaves_like "test framework is absent" + require "bundler/gem_tasks" + require "minitest/test_task" + + Minitest::TestTask.create + + task default: :test + RAKEFILE + + expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) end + end - context "--test parameter set to rspec" do - before do - bundle "gem #{gem_name} --test=rspec" - end + context "gem.test setting set to minitest" do + before do + bundle_config "gem.test minitest" + bundle "gem #{gem_name}" + 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 "creates a default rake task to run the test suite" do + rakefile = <<~RAKEFILE + # frozen_string_literal: true - 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 + require "bundler/gem_tasks" + require "minitest/test_task" - it "requires the main file" do - expect(bundled_app("#{gem_name}/spec/spec_helper.rb").read).to include(%(require "#{require_path}")) - end + Minitest::TestTask.create - 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 - end + task default: :test + RAKEFILE - context "gem.test setting set to rspec" do - before do - bundle "config set gem.test rspec" - bundle "gem #{gem_name}" - end + expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) + end + 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 + context "--test parameter set to test-unit" do + before do + bundle "gem #{gem_name} --test=test-unit" 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 + 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 - 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}/test/#{gem_name}_test.rb")).to exist + expect(bundled_app("#{gem_name}/test/test_helper.rb")).to exist end - context "--test parameter set to minitest" do - before do - bundle "gem #{gem_name} --test=minitest" - end + it "includes test/ into ignore list" do + expect(ignore_paths).to include("test/") + 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 "creates a default rake task to run the test suite" do + rakefile = <<~RAKEFILE + # frozen_string_literal: true - it "builds spec skeleton" do - expect(bundled_app("#{gem_name}/#{minitest_test_file_path}")).to exist - expect(bundled_app("#{gem_name}/test/test_helper.rb")).to exist - end + require "bundler/gem_tasks" + require "rake/testtask" - it "requires the main file" do - expect(bundled_app("#{gem_name}/test/test_helper.rb").read).to include(%(require "#{require_path}")) - end + Rake::TestTask.new(:test) do |t| + t.libs << "test" + t.libs << "lib" + t.test_files = FileList["test/**/*_test.rb"] + end - it "requires 'test_helper'" do - expect(bundled_app("#{gem_name}/#{minitest_test_file_path}").read).to include(%(require "test_helper")) - end + task default: :test + RAKEFILE - it "defines valid test class name" do - expect(bundled_app("#{gem_name}/#{minitest_test_file_path}").read).to include(minitest_test_class_name) - end + expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) + end + end - it "creates a default test which fails" do - expect(bundled_app("#{gem_name}/#{minitest_test_file_path}").read).to include("assert false") - end + context "--test parameter set to an invalid value" do + before do + bundle "gem #{gem_name} --test=foo", raise_on_error: false end - context "gem.test setting set to minitest" do - before do - bundle "config set gem.test minitest" - bundle "gem #{gem_name}" - 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 - it "creates a default rake task to run the test suite" do - rakefile = strip_whitespace <<-RAKEFILE - # frozen_string_literal: true + 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 - require "bundler/gem_tasks" - require "rake/testtask" + 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 - Rake::TestTask.new(:test) do |t| - t.libs << "test" - t.libs << "lib" - t.test_files = FileList["test/**/test_*.rb"] - end + it "includes .rspec and spec/ into ignore list" do + expect(ignore_paths).to include(".rspec") + expect(ignore_paths).to include("spec/") + end - task default: :test - RAKEFILE + it "hints that --test is already configured" do + expect(out).to match("rspec is already configured, ignoring --test flag.") + end + end - expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) + 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 - context "--test parameter set to test-unit" do - before do - bundle "gem #{gem_name} --test=test-unit" - end + it "asks to generate test files" do + expect(out).to match("Do you want to generate tests with your gem?") + 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 "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 "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_behaves_like "test framework is absent" + end - it "requires the main file" do - expect(bundled_app("#{gem_name}/test/test_helper.rb").read).to include(%(require "#{require_path}")) + 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 - it "requires 'test_helper'" do - expect(bundled_app("#{gem_name}/test/#{require_path}_test.rb").read).to include(%(require "test_helper")) - 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 test which fails" do - expect(bundled_app("#{gem_name}/test/#{require_path}_test.rb").read).to include("assert_equal(\"expected\", \"actual\")") - end + it "hints that the choice will be applied to future bundle gem calls" do + hint = "Future `bundle gem` calls will use your choice. " \ + "This setting can be changed anytime with `bundle config gem.test`." + expect(out).to match(hint) end - context "gem.test setting set to test-unit" do - before do - bundle "config set gem.test test-unit" - bundle "gem #{gem_name}" - end + it_behaves_like "test framework is absent" + end - it "creates a default rake task to run the test suite" do - rakefile = strip_whitespace <<-RAKEFILE - # frozen_string_literal: true + context "gem.test setting set to a test framework and --no-test" do + before do + bundle_config "gem.test rspec" + bundle "gem #{gem_name} --no-test" + end - require "bundler/gem_tasks" - require "rake/testtask" + it_behaves_like "test framework is absent" + end - Rake::TestTask.new(:test) do |t| - t.libs << "test" - t.libs << "lib" - t.test_files = FileList["test/**/*_test.rb"] - end + context "--ci with no argument" do + before do + bundle "gem #{gem_name}" + end - task default: :test - RAKEFILE + it "does not generate any CI config" do + expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to_not exist + expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to_not exist + expect(bundled_app("#{gem_name}/.circleci/config.yml")).to_not exist + end - expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) - end + it "does not add any CI config files into ignore list" do + expect(ignore_paths).not_to include(".github/") + expect(ignore_paths).not_to include(".gitlab-ci.yml") + expect(ignore_paths).not_to include(".circleci/") end + end - context "gem.test set to rspec and --test with no arguments", :hint_text do - before do - bundle "config set gem.test rspec" - bundle "gem #{gem_name} --test" - end + context "--ci set to github" do + before do + bundle "gem #{gem_name} --ci=github" + end - it "builds spec skeleton" do - expect(bundled_app("#{gem_name}/.rspec")).to exist - expect(bundled_app("#{gem_name}/spec/#{require_path}_spec.rb")).to exist - expect(bundled_app("#{gem_name}/spec/spec_helper.rb")).to exist - end + it "generates a GitHub Actions config file" do + expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist + end - it "hints that --test is already configured" do - expect(out).to match("rspec is already configured, ignoring --test flag.") - end + it "includes .github/ into ignore list" do + expect(ignore_paths).to include(".github/") end + end - context "gem.test setting set to false and --test with no arguments", :hint_text do - before do - bundle "config set gem.test false" - bundle "gem #{gem_name} --test" - end + context "--ci set to gitlab" do + before do + bundle "gem #{gem_name} --ci=gitlab" + end - it "asks to generate test files" do - expect(out).to match("Do you want to generate tests with your gem?") - end + it "generates a GitLab CI config file" do + expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to exist + end - it "hints that the choice will only be applied to the current gem" do - expect(out).to match("Your choice will only be applied to this gem.") - end + it "includes .gitlab-ci.yml into ignore list" do + expect(ignore_paths).to include(".gitlab-ci.yml") + end + end - it_behaves_like "test framework is absent" + context "--ci set to circle" do + before do + bundle "gem #{gem_name} --ci=circle" end - context "gem.test setting not set and --test with no arguments", :hint_text do - before do - bundle "gem #{gem_name} --test" - end + it "generates a CircleCI config file" do + expect(bundled_app("#{gem_name}/.circleci/config.yml")).to exist + end - it "asks to generate test files" do - expect(out).to match("Do you want to generate tests with your gem?") - end + it "includes .circleci/ into ignore list" do + expect(ignore_paths).to include(".circleci/") + end + end - it "hints that the choice will be applied to future bundle gem calls" do - hint = "Future `bundle gem` calls will use your choice. " \ - "This setting can be changed anytime with `bundle config gem.test`." - expect(out).to match(hint) - end + context "--ci set to an invalid value" do + before do + bundle "gem #{gem_name} --ci=foo", raise_on_error: false + end - it_behaves_like "test framework is absent" + it "fails loudly" do + expect(last_command).to be_failure + expect(err).to match(/Expected '--ci' to be one of .*; got foo/) end + end - context "--ci with no argument" do - it "does not generate any CI config" do - bundle "gem #{gem_name}" + 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 - 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 "gem.ci setting set to github" do + it "generates a GitHub Actions config file" do + bundle_config "gem.ci github" + bundle "gem #{gem_name}" + + expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist end + end - context "--ci set to travis" do - it "generates a GitHub Actions config file" do - bundle "gem #{gem_name} --ci=travis", :raise_on_error => false - expect(err).to include("Support for Travis CI was removed from gem skeleton generator.") + context "gem.ci setting set to gitlab" do + it "generates a GitLab CI config file" do + bundle_config "gem.ci gitlab" + bundle "gem #{gem_name}" - expect(bundled_app("#{gem_name}/.travis.yml")).to_not exist - end + expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to exist end + end - context "--ci set to travis" do - it "generates a GitHub Actions config file" do - bundle "gem #{gem_name} --ci=travis", :raise_on_error => false - expect(err).to include("Support for Travis CI was removed from gem skeleton generator.") + context "gem.ci setting set to circle" do + it "generates a CircleCI config file" do + bundle_config "gem.ci circle" + bundle "gem #{gem_name}" - expect(bundled_app("#{gem_name}/.travis.yml")).to_not exist - end + expect(bundled_app("#{gem_name}/.circleci/config.yml")).to exist end + end - context "--ci set to github" do - it "generates a GitHub Actions config file" do - bundle "gem #{gem_name} --ci=github" + context "gem.ci set to github and --ci with no arguments" do + before do + bundle_config "gem.ci github" + bundle "gem #{gem_name} --ci" + end - expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist - end + it "generates a GitHub Actions config file" do + expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist end - context "--ci set to gitlab" do - it "generates a GitLab CI config file" do - bundle "gem #{gem_name} --ci=gitlab" + it "hints that --ci is already configured" do + expect(out).to match("github is already configured, ignoring --ci flag.") + end + end - expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to exist + context "gem.ci setting set to false and --ci with no arguments", :readline do + before do + bundle_config "gem.ci false" + bundle "gem #{gem_name} --ci" do |input, _, _| + input.puts "github" end end - context "--ci set to circle" do - it "generates a CircleCI config file" do - bundle "gem #{gem_name} --ci=circle" + it "asks to setup CI" do + expect(out).to match("Do you want to set up continuous integration for your gem?") + end - expect(bundled_app("#{gem_name}/.circleci/config.yml")).to exist - end + it "hints that the choice will only be applied to the current gem" do + expect(out).to match("Your choice will only be applied to this gem.") 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 + context "gem.ci setting not set and --ci with no arguments", :readline do + before do + bundle_config_global "BUNDLE_GEM__CI" => nil + bundle "gem #{gem_name} --ci" do |input, _, _| + input.puts "github" end end - context "gem.ci setting set to travis" do - it "errors with friendly message" do - bundle "config set gem.ci travis" - bundle "gem #{gem_name}", :raise_on_error => false + it "asks to setup CI" do + expect(out).to match("Do you want to set up continuous integration for your gem?") + end - expect(err).to include("Support for Travis CI was removed from gem skeleton generator,") - expect(err).to include("bundle config unset gem.ci") + 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 - expect(bundled_app("#{gem_name}/.travis.yml")).to_not exist - end + 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 - 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 "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 - expect(bundled_app("#{gem_name}/.github/workflows/main.yml")).to exist - end + context "--linter with no argument" do + before do + bundle "gem #{gem_name}" end - context "gem.ci setting set to gitlab" do - it "generates a GitLab CI config file" do - bundle "config set gem.ci gitlab" - bundle "gem #{gem_name}" + it "does not generate any linter config" do + expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist + expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist + end - expect(bundled_app("#{gem_name}/.gitlab-ci.yml")).to exist - end + it "does not add any linter config files into ignore list" do + expect(ignore_paths).not_to include(".rubocop.yml") + expect(ignore_paths).not_to include(".standard.yml") end + end - context "gem.ci setting set to circle" do - it "generates a CircleCI config file" do - bundle "config set gem.ci circle" - bundle "gem #{gem_name}" + context "--linter set to rubocop" do + before do + bundle "gem #{gem_name} --linter=rubocop" + end - expect(bundled_app("#{gem_name}/.circleci/config.yml")).to exist - end + it "generates a RuboCop config" do + expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist + expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist end - context "gem.ci 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 "includes .rubocop.yml into ignore list" do + expect(ignore_paths).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 "--linter set to standard" do + before do + bundle "gem #{gem_name} --linter=standard" + end - it "hints that --ci is already configured" do - expect(out).to match("github is already configured, ignoring --ci flag.") - end + 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 - 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 .standard.yml into ignore list" do + expect(ignore_paths).to include(".standard.yml") + expect(ignore_paths).not_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 "--linter set to an invalid value" do + before do + bundle "gem #{gem_name} --linter=foo", raise_on_error: false + 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 "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 not set and --ci with no arguments", :hint_text do - before do - bundle "gem #{gem_name} --ci" - end + context "gem.linter setting set to none" do + before do + bundle "gem #{gem_name}" + end - it "asks to setup CI" do - expect(out).to match("Do you want to set up continuous integration for your gem?") - end + it "doesn't generate any linter config" do + expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist + expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist + end - it "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 "does not add any linter config files into ignore list" do + expect(ignore_paths).not_to include(".rubocop.yml") + expect(ignore_paths).not_to include(".standard.yml") end + end - context "--linter with no argument" do - it "does not generate any linter config" do - bundle "gem #{gem_name}" + context "gem.linter setting set to rubocop" do + before do + bundle_config "gem.linter rubocop" + bundle "gem #{gem_name}" + end - expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist - expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist - end + it "generates a RuboCop config file" do + expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist end - context "--linter set to rubocop" do - it "generates a RuboCop config" do - bundle "gem #{gem_name} --linter=rubocop" + it "includes .rubocop.yml into ignore list" do + expect(ignore_paths).to include(".rubocop.yml") + end + end - expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist - expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist - end + context "gem.linter setting set to standard" do + before do + bundle_config "gem.linter standard" + bundle "gem #{gem_name}" end - context "--linter set to standard" do - it "generates a Standard config" do - bundle "gem #{gem_name} --linter=standard" + it "generates a Standard config file" do + expect(bundled_app("#{gem_name}/.standard.yml")).to exist + end - expect(bundled_app("#{gem_name}/.standard.yml")).to exist - expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist - end + it "includes .standard.yml into ignore list" do + expect(ignore_paths).to include(".standard.yml") 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 set to rubocop and --linter with no arguments" do + before do + bundle_config "gem.linter rubocop" + bundle "gem #{gem_name} --linter" + end - expect(bundled_app("#{gem_name}/.rubocop.yml")).to_not exist - expect(bundled_app("#{gem_name}/.standard.yml")).to_not exist - end + it "generates a RuboCop config file" do + expect(bundled_app("#{gem_name}/.rubocop.yml")).to exist end - context "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 "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 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 "gem.linter setting set to standard" do - it "generates a Standard config file" do - bundle "config set gem.linter standard" - bundle "gem #{gem_name}" + 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 + it "hints that the choice will only be applied to the current gem" do + expect(out).to match("Your choice will only be applied to this gem.") + end + end + + context "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 "gem.rubocop setting set to true", :bundler => "< 3" do + 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 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 a linter and --no-linter" do + before do + bundle_config "gem.linter rubocop" + bundle "gem #{gem_name} --no-linter" + 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 + + it "does not add any linter config files into ignore list" do + expect(ignore_paths).not_to include(".rubocop.yml") + expect(ignore_paths).not_to include(".standard.yml") + end + end + + context "--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 + + shared_examples_for "paths that depend on gem name" do + it "generates entrypoint, version file and signatures file at the proper path, with the proper content" do + bundle "gem #{gem_name}" + + expect(bundled_app("#{gem_name}/lib/#{require_path}.rb")).to exist + expect(bundled_app("#{gem_name}/lib/#{require_path}.rb").read).to match(%r{require_relative "#{require_relative_path}/version"}) + expect(bundled_app("#{gem_name}/lib/#{require_path}.rb").read).to match(/class Error < StandardError; end$/) + + expect(bundled_app("#{gem_name}/lib/#{require_path}/version.rb")).to exist + expect(bundled_app("#{gem_name}/sig/#{require_path}.rbs")).to exist + end + + context "--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 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 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 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 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 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 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 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 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 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 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 standard" do + 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" + + 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" } @@ -1366,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" } @@ -1393,25 +1694,28 @@ 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 it "depends on compile task for build" do - rakefile = strip_whitespace <<-RAKEFILE + rakefile = <<~RAKEFILE # frozen_string_literal: true require "bundler/gem_tasks" @@ -1419,7 +1723,9 @@ RSpec.describe "bundle gem" do task build: :compile - Rake::ExtensionTask.new("#{gem_name}") do |ext| + GEMSPEC = Gem::Specification.load("#{gem_name}.gemspec") + + Rake::ExtensionTask.new("#{gem_name}", GEMSPEC) do |ext| ext.lib_dir = "lib/#{gem_name}" end @@ -1430,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 @@ -1460,16 +1752,16 @@ 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 - rakefile = strip_whitespace <<-RAKEFILE + rakefile = <<~RAKEFILE # frozen_string_literal: true require "bundler/gem_tasks" @@ -1477,7 +1769,9 @@ RSpec.describe "bundle gem" do task build: :compile - RbSys::ExtensionTask.new("#{gem_name}") do |ext| + GEMSPEC = Gem::Specification.load("#{gem_name}.gemspec") + + RbSys::ExtensionTask.new("#{gem_name}", GEMSPEC) do |ext| ext.lib_dir = "lib/#{gem_name}" end @@ -1486,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" } @@ -1506,33 +1976,33 @@ 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 end it "fails gracefully with a ." do - bundle "gem foo.gemspec", :raise_on_error => false + bundle "gem foo.gemspec", raise_on_error: false expect(err).to end_with("Invalid gem name foo.gemspec -- `Foo.gemspec` is an invalid constant name") end it "fails gracefully with a ^" do - bundle "gem ^", :raise_on_error => false + bundle "gem ^", raise_on_error: false expect(err).to end_with("Invalid gem name ^ -- `^` is an invalid constant name") end it "fails gracefully with a space" do - bundle "gem 'foo bar'", :raise_on_error => false + bundle "gem 'foo bar'", raise_on_error: false expect(err).to end_with("Invalid gem name foo bar -- `Foo bar` is an invalid constant name") end it "fails gracefully when multiple names are passed" do - bundle "gem foo bar baz", :raise_on_error => false + bundle "gem foo bar baz", raise_on_error: false expect(err).to eq(<<-E.strip) ERROR: "bundle gem" was called with arguments ["foo", "bar", "baz"] Usage: "bundle gem NAME [OPTIONS]" @@ -1540,9 +2010,9 @@ 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 + bundle "gem #{subject}", raise_on_error: false end context "with an existing const name" do @@ -1555,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}") } @@ -1568,14 +2051,14 @@ 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" end expect(bundled_app("foobar/spec/spec_helper.rb")).to exist - rakefile = strip_whitespace <<-RAKEFILE + rakefile = <<~RAKEFILE # frozen_string_literal: true require "bundler/gem_tasks" @@ -1591,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" @@ -1600,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" @@ -1610,40 +2093,42 @@ 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 + bundle "gem conflict-foobar", raise_on_error: false expect(err).to eq("Couldn't create a new gem named `conflict-foobar` because there's an existing file named `conflict-foobar`.") expect(exitstatus).to eql(32) end 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 e30ebfea5b..664dc58919 100644 --- a/spec/bundler/commands/open_spec.rb +++ b/spec/bundler/commands/open_spec.rb @@ -4,33 +4,33 @@ 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 it "opens the gem with BUNDLER_EDITOR as highest priority" do - bundle "open rails", :env => { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" } + bundle "open rails", env: { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" } expect(out).to include("bundler_editor #{default_bundle_path("gems", "rails-2.3.2")}") end it "opens the gem with VISUAL as 2nd highest priority" do - bundle "open rails", :env => { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "" } + bundle "open rails", env: { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "" } expect(out).to include("visual #{default_bundle_path("gems", "rails-2.3.2")}") end it "opens the gem with EDITOR as 3rd highest priority" do - bundle "open rails", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } + bundle "open rails", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } expect(out).to include("editor #{default_bundle_path("gems", "rails-2.3.2")}") end it "complains if no EDITOR is set" do - bundle "open rails", :env => { "EDITOR" => "", "VISUAL" => "", "BUNDLER_EDITOR" => "" } + bundle "open rails", env: { "EDITOR" => "", "VISUAL" => "", "BUNDLER_EDITOR" => "" } expect(out).to eq("To open a bundled gem, set $EDITOR or $BUNDLER_EDITOR") end it "complains if gem not in bundle" do - bundle "open missing", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, :raise_on_error => false + bundle "open missing", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, raise_on_error: false expect(err).to match(/could not find gem 'missing'/i) end @@ -39,113 +39,113 @@ 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 match("editor #{default_bundle_path.join("bundler/gems/foo-1.0-#{ref}")}") + bundle "open foo", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } + expect(out).to include("editor #{default_bundle_path("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) + bundle "open Rails", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, raise_on_error: false + expect(err).to match(/did you mean 'rails'\?/i) end it "opens the gem with short words" do - bundle "open rec", :env => { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" } + bundle "open rec", env: { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" } expect(out).to include("bundler_editor #{default_bundle_path("gems", "activerecord-2.3.2")}") end it "opens subpath of the gem" do - bundle "open activerecord --path lib/activerecord", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } + bundle "open activerecord --path lib/activerecord", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } expect(out).to include("editor #{default_bundle_path("gems", "activerecord-2.3.2")}/lib/activerecord") end it "opens subpath file of the gem" do - bundle "open activerecord --path lib/version.rb", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } + bundle "open activerecord --path lib/version.rb", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } expect(out).to include("editor #{default_bundle_path("gems", "activerecord-2.3.2")}/lib/version.rb") end it "opens deep subpath of the gem" do - bundle "open activerecord --path lib/active_record", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } + bundle "open activerecord --path lib/active_record", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } expect(out).to include("editor #{default_bundle_path("gems", "activerecord-2.3.2")}/lib/active_record") end it "requires value for --path arg" do - bundle "open activerecord --path", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, :raise_on_error => false + bundle "open activerecord --path", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, raise_on_error: false expect(err).to eq "Cannot specify `--path` option without a value" end it "suggests alternatives for similar-sounding gems when using subpath" do - bundle "open Rails --path README.md", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, :raise_on_error => false - expect(err).to match(/did you mean rails\?/i) + bundle "open Rails --path README.md", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, raise_on_error: false + expect(err).to match(/did you mean 'rails'\?/i) end it "suggests alternatives for similar-sounding gems when using deep subpath" do - bundle "open Rails --path some/path/here", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, :raise_on_error => false - expect(err).to match(/did you mean rails\?/i) + bundle "open Rails --path some/path/here", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, raise_on_error: false + expect(err).to match(/did you mean 'rails'\?/i) end it "opens subpath of the short worded gem" do - bundle "open rec --path CHANGELOG.md", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } + bundle "open rec --path CHANGELOG.md", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } expect(out).to include("editor #{default_bundle_path("gems", "activerecord-2.3.2")}/CHANGELOG.md") end it "opens deep subpath of the short worded gem" do - bundle "open rec --path lib/activerecord", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } + bundle "open rec --path lib/activerecord", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } expect(out).to include("editor #{default_bundle_path("gems", "activerecord-2.3.2")}/lib/activerecord") end it "opens subpath of the selected matching gem", :readline do env = { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" } - bundle "open active --path CHANGELOG.md", :env => env do |input, _, _| + bundle "open active --path CHANGELOG.md", env: env do |input, _, _| input.puts "2" end - expect(out).to match(%r{bundler_editor #{default_bundle_path('gems', 'activerecord-2.3.2')}/CHANGELOG\.md\z}) + expect(out).to include("bundler_editor #{default_bundle_path("gems", "activerecord-2.3.2").join("CHANGELOG.md")}") end it "opens deep subpath of the selected matching gem", :readline do env = { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" } - bundle "open active --path lib/activerecord/version.rb", :env => env do |input, _, _| + bundle "open active --path lib/activerecord/version.rb", env: env do |input, _, _| input.puts "2" end - expect(out).to match(%r{bundler_editor #{default_bundle_path('gems', 'activerecord-2.3.2')}/lib/activerecord/version\.rb\z}) + expect(out).to include("bundler_editor #{default_bundle_path("gems", "activerecord-2.3.2").join("lib", "activerecord", "version.rb")}") end it "select the gem from many match gems", :readline do env = { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" } - bundle "open active", :env => env do |input, _, _| + bundle "open active", env: env do |input, _, _| input.puts "2" end - expect(out).to match(/bundler_editor #{default_bundle_path('gems', 'activerecord-2.3.2')}\z/) + expect(out).to include("bundler_editor #{default_bundle_path("gems", "activerecord-2.3.2")}") end it "allows selecting exit from many match gems", :readline do env = { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" } - bundle "open active", :env => env do |input, _, _| + bundle "open active", env: env do |input, _, _| input.puts "0" end end 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 "open rails", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } + bundle_config "auto_install 1" + bundle "open rails", env: { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } expect(out).to include("Installing foo 1.0") end it "opens the editor with a clean env" do - bundle "open", :env => { "EDITOR" => "sh -c 'env'", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, :raise_on_error => false + bundle "open", env: { "EDITOR" => "sh -c 'env'", "VISUAL" => "", "BUNDLER_EDITOR" => "" }, raise_on_error: false expect(out).not_to include("BUNDLE_GEMFILE=") end end @@ -163,13 +163,12 @@ 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 it "throws proper error when trying to open default gem" do - bundle "open json", :env => { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" } + bundle "open json", env: { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" } expect(out).to include("Unable to open json because it's a default gem, so the directory it would normally be installed to does not exist.") end end diff --git a/spec/bundler/commands/outdated_spec.rb b/spec/bundler/commands/outdated_spec.rb index bbc3cb54ae..28ed51d61e 100644 --- a/spec/bundler/commands/outdated_spec.rb +++ b/spec/bundler/commands/outdated_spec.rb @@ -4,12 +4,12 @@ RSpec.describe "bundle outdated" do describe "with no arguments" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G - 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" @@ -23,14 +23,14 @@ RSpec.describe "bundle outdated" do update_repo2 do build_gem "activesupport", "3.0" build_gem "weakling", "0.2" - update_git "foo", :path => lib_path("foo") - update_git "zebra", :path => lib_path("zebra") + update_git "foo", path: lib_path("foo") + update_git "zebra", path: lib_path("zebra") end - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false expected_output = <<~TABLE.gsub("x", "\\\h").tr(".", "\.").strip - Gem Current Latest Requested Groups + 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 + 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 @@ -63,10 +63,10 @@ RSpec.describe "bundle outdated" do it "returns non zero exit status if outdated gems present" do update_repo2 do build_gem "activesupport", "3.0" - update_git "foo", :path => lib_path("foo") + update_git "foo", path: lib_path("foo") end - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false expect(exitstatus).to_not be_zero end @@ -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' @@ -89,10 +89,10 @@ RSpec.describe "bundle outdated" do update_repo2 { build_gem "activesupport", "3.0" } update_repo2 { build_gem "terranova", "9" } - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date activesupport 2.3.5 3.0 = 2.3.5 development, test terranova 8 9 = 8 default TABLE @@ -104,12 +104,12 @@ RSpec.describe "bundle outdated" do describe "with --verbose option" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G - 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,112 +120,46 @@ 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' G - bundle "outdated --verbose", :raise_on_error => false + bundle "outdated --verbose", raise_on_error: false expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups Path + 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 - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "weakling", "~> 0.0.1" gem "terranova", '8' @@ -243,7 +177,7 @@ RSpec.describe "bundle outdated" do build_gem "duradura", "8.0" end - bundle "outdated --group #{group}", :raise_on_error => false + bundle "outdated --group #{group}", raise_on_error: false end it "works when the bundle is up to date" do @@ -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 @@ -290,8 +232,8 @@ RSpec.describe "bundle outdated" do describe "with --groups option and outdated transitive dependencies" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") build_gem "bar", %w[2.0.0] @@ -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 @@ -312,10 +254,10 @@ RSpec.describe "bundle outdated" do end it "returns a sorted list of outdated gems" do - bundle "outdated --groups", :raise_on_error => false + bundle "outdated --groups", raise_on_error: false expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + Gem Current Latest Requested Groups Release Date bar 2.0.0 3.0.0 TABLE @@ -326,12 +268,12 @@ RSpec.describe "bundle outdated" do describe "with --groups option" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "weakling", "~> 0.0.1" gem "terranova", '8' @@ -354,10 +296,10 @@ RSpec.describe "bundle outdated" do build_gem "duradura", "8.0" end - bundle "outdated --groups", :raise_on_error => false + bundle "outdated --groups", raise_on_error: false expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + 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 @@ -370,12 +312,12 @@ RSpec.describe "bundle outdated" do describe "with --local option" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G - 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 + 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/) @@ -420,15 +362,15 @@ RSpec.describe "bundle outdated" do context "and gems are outdated" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") build_gem "activesupport", "3.0" build_gem "weakling", "0.2" 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,30 +380,51 @@ 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 describe "with --parseable option" do - subject { bundle "outdated --parseable", :raise_on_error => false } + subject { bundle "outdated --parseable", raise_on_error: false } it_behaves_like "a minimal output is desired" end describe "with aliased --porcelain option" do - subject { bundle "outdated --porcelain", :raise_on_error => false } + subject { bundle "outdated --porcelain", raise_on_error: false } it_behaves_like "a minimal output is desired" end @@ -469,12 +432,12 @@ RSpec.describe "bundle outdated" do describe "with specified gems" do it "returns list of outdated gems" do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G - 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" @@ -485,29 +448,67 @@ RSpec.describe "bundle outdated" do update_repo2 do build_gem "activesupport", "3.0" - update_git "foo", :path => lib_path("foo") + update_git "foo", path: lib_path("foo") end - bundle "outdated foo", :raise_on_error => false + bundle "outdated foo", raise_on_error: false expected_output = <<~TABLE.gsub("x", "\\\h").tr(".", "\.").strip - Gem Current Latest Requested Groups + 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 before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G - 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" @@ -535,10 +536,10 @@ RSpec.describe "bundle outdated" do build_gem "activesupport", "3.0.0.beta" end - bundle "outdated --pre", :raise_on_error => false + bundle "outdated --pre", raise_on_error: false expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + 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 + 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 @@ -573,12 +574,12 @@ RSpec.describe "bundle outdated" do describe "with --filter-strict option" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G - 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" @@ -594,10 +595,10 @@ RSpec.describe "bundle outdated" do build_gem "weakling", "0.0.5" end - bundle :outdated, :"filter-strict" => true, :raise_on_error => false + 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 @@ -610,10 +611,10 @@ RSpec.describe "bundle outdated" do build_gem "weakling", "0.0.5" end - bundle :outdated, :strict => true, :raise_on_error => false + 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,22 +623,22 @@ 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 - bundle :outdated, :"filter-strict" => true + bundle :outdated, "filter-strict": true expect(out).to end_with("Bundle up to date!") end 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 + bundle :outdated, "filter-strict": true expect(out).to end_with("Bundle up to date!") end @@ -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 @@ -714,12 +715,12 @@ RSpec.describe "bundle outdated" do describe "with invalid gem name" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G - 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" @@ -730,46 +731,46 @@ RSpec.describe "bundle outdated" do end it "returns could not find gem name" do - bundle "outdated invalid_gem_name", :raise_on_error => false + bundle "outdated invalid_gem_name", raise_on_error: false expect(err).to include("Could not find gem 'invalid_gem_name'.") end it "returns non-zero exit code" do - bundle "outdated invalid_gem_name", :raise_on_error => false + bundle "outdated invalid_gem_name", raise_on_error: false expect(exitstatus).to_not be_zero end end 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 :outdated, :raise_on_error => false + 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 update_repo2 { build_gem "activesupport", "3.0" } - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false expect(last_command).to be_failure expect(err).to include("You are trying to check outdated gems in deployment mode.") expect(err).to include("Run `bundle outdated` elsewhere.") @@ -781,23 +782,23 @@ RSpec.describe "bundle outdated" do context "after bundle config set --local deployment true" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G - 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 update_repo2 { build_gem "activesupport", "3.0" } - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false expect(last_command).to be_failure expect(err).to include("You are trying to check outdated gems in deployment mode.") expect(err).to include("Run `bundle outdated` elsewhere.") @@ -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 + 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 @@ -867,12 +868,12 @@ RSpec.describe "bundle outdated" do shared_examples_for "major version updates are detected" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G - 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" @@ -893,12 +894,12 @@ RSpec.describe "bundle outdated" do context "when on a new machine" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G - 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,28 +908,28 @@ RSpec.describe "bundle outdated" do gem "terranova", '8' G - simulate_new_machine + pristine_system_gems - update_git "foo", :path => lib_path("foo") + update_git "foo", path: lib_path("foo") update_repo2 do build_gem "activesupport", "3.3.5" build_gem "weakling", "0.8.0" end end - subject { bundle "outdated", :raise_on_error => false } + subject { bundle "outdated", raise_on_error: false } it_behaves_like "version update is detected" end shared_examples_for "minor version updates are detected" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G - 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" @@ -949,12 +950,12 @@ RSpec.describe "bundle outdated" do shared_examples_for "patch version updates are detected" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G - 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" @@ -982,12 +983,12 @@ RSpec.describe "bundle outdated" do shared_examples_for "major version is ignored" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G - 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" @@ -1008,12 +1009,12 @@ RSpec.describe "bundle outdated" do shared_examples_for "minor version is ignored" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G - 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" @@ -1034,12 +1035,12 @@ RSpec.describe "bundle outdated" do shared_examples_for "patch version is ignored" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") - build_git "zebra", :path => lib_path("zebra") + build_git "foo", path: lib_path("foo") + build_git "zebra", path: lib_path("zebra") end install_gemfile <<-G - 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" @@ -1058,7 +1059,7 @@ RSpec.describe "bundle outdated" do end describe "with --filter-major option" do - subject { bundle "outdated --filter-major", :raise_on_error => false } + subject { bundle "outdated --filter-major", raise_on_error: false } it_behaves_like "major version updates are detected" it_behaves_like "minor version is ignored" @@ -1066,7 +1067,7 @@ RSpec.describe "bundle outdated" do end describe "with --filter-minor option" do - subject { bundle "outdated --filter-minor", :raise_on_error => false } + subject { bundle "outdated --filter-minor", raise_on_error: false } it_behaves_like "minor version updates are detected" it_behaves_like "major version is ignored" @@ -1074,7 +1075,7 @@ RSpec.describe "bundle outdated" do end describe "with --filter-patch option" do - subject { bundle "outdated --filter-patch", :raise_on_error => false } + subject { bundle "outdated --filter-patch", raise_on_error: false } it_behaves_like "patch version updates are detected" it_behaves_like "major version is ignored" @@ -1082,7 +1083,7 @@ RSpec.describe "bundle outdated" do end describe "with --filter-minor --filter-patch options" do - subject { bundle "outdated --filter-minor --filter-patch", :raise_on_error => false } + subject { bundle "outdated --filter-minor --filter-patch", raise_on_error: false } it_behaves_like "minor version updates are detected" it_behaves_like "patch version updates are detected" @@ -1090,7 +1091,7 @@ RSpec.describe "bundle outdated" do end describe "with --filter-major --filter-minor options" do - subject { bundle "outdated --filter-major --filter-minor", :raise_on_error => false } + subject { bundle "outdated --filter-major --filter-minor", raise_on_error: false } it_behaves_like "major version updates are detected" it_behaves_like "minor version updates are detected" @@ -1098,7 +1099,7 @@ RSpec.describe "bundle outdated" do end describe "with --filter-major --filter-patch options" do - subject { bundle "outdated --filter-major --filter-patch", :raise_on_error => false } + subject { bundle "outdated --filter-major --filter-patch", raise_on_error: false } it_behaves_like "major version updates are detected" it_behaves_like "patch version updates are detected" @@ -1106,7 +1107,7 @@ RSpec.describe "bundle outdated" do end describe "with --filter-major --filter-minor --filter-patch options" do - subject { bundle "outdated --filter-major --filter-minor --filter-patch", :raise_on_error => false } + subject { bundle "outdated --filter-major --filter-minor --filter-patch", raise_on_error: false } it_behaves_like "major version updates are detected" it_behaves_like "minor version updates are detected" @@ -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' @@ -1145,10 +1146,10 @@ RSpec.describe "bundle outdated" do end it "shows all gems when patching and filtering to patch" do - bundle "outdated --patch --filter-patch", :raise_on_error => false + 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 @@ -1158,10 +1159,10 @@ RSpec.describe "bundle outdated" do end it "shows minor and major when updating to minor and filtering to patch and minor" do - bundle "outdated --minor --filter-minor", :raise_on_error => false + 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 @@ -1170,7 +1171,7 @@ RSpec.describe "bundle outdated" do end it "shows minor when updating to major and filtering to minor with parseable" do - bundle "outdated --major --filter-minor --parseable", :raise_on_error => false + bundle "outdated --major --filter-minor --parseable", raise_on_error: false expect(out).not_to include("patch (newest") expect(out).to include("minor (newest") @@ -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,17 +1206,17 @@ 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 end it "shows gems updating to patch and filtering to patch" do - bundle "outdated --patch --filter-patch", :raise_on_error => false, :env => { "DEBUG_RESOLVER" => "1" } + 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 @@ -1224,10 +1225,10 @@ RSpec.describe "bundle outdated" do end it "shows gems updating to patch and filtering to patch, in debug mode" do - bundle "outdated --patch --filter-patch", :raise_on_error => false, :env => { "DEBUG" => "1" } + 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 + 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,20 +1297,20 @@ 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 it "reports a single entry per gem" do - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + 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,15 +1351,15 @@ RSpec.describe "bundle outdated" do mini_portile2 BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end it "works" do - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false expected_output = <<~TABLE.strip - Gem Current Latest Requested Groups + 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 688bf7b6df..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,46 +158,46 @@ 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" G - bundle "platform", :raise_on_error => false + bundle "platform", raise_on_error: false expect(exitstatus).not_to eq(0) end 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" G - bundle "platform", :raise_on_error => false + bundle "platform", raise_on_error: false expect(exitstatus).not_to eq(0) end 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" G - bundle "platform", :raise_on_error => false + bundle "platform", raise_on_error: false expect(exitstatus).not_to eq(0) end 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 @@ -347,9 +347,9 @@ G end 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" + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo1" + gem "myrack" #{ruby_version_incorrect} G @@ -359,9 +359,9 @@ G end 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" + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo1" + gem "myrack" #{engine_incorrect} G @@ -371,9 +371,9 @@ G end 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" + install_gemfile <<-G, raise_on_error: false + 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 - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo1)}" - gem "rack" + it "does install even when patchlevel doesn't match" do + install_gemfile <<-G, raise_on_error: false + 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,70 +432,70 @@ 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 - bundle :check, :raise_on_error => false + bundle :check, raise_on_error: false should_be_ruby_version_incorrect end 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 - bundle :check, :raise_on_error => false + bundle :check, raise_on_error: false should_be_engine_incorrect end it "fails when engine version doesn't match", :jruby_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 - bundle :check, :raise_on_error => false + bundle :check, raise_on_error: false 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" + bundle "update", all: true + 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" + bundle "update", all: true + 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 @@ -562,15 +562,15 @@ G build_gem "activesupport", "3.0" end - bundle :update, :all => true, :raise_on_error => false + bundle :update, all: true, raise_on_error: false should_be_ruby_version_incorrect end it "fails when ruby engine doesn't match", :jruby_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 @@ -578,15 +578,15 @@ G build_gem "activesupport", "3.0" end - bundle :update, :all => true, :raise_on_error => false + bundle :update, all: true, raise_on_error: false should_be_engine_incorrect end it "fails when ruby engine version doesn't match", :jruby_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 @@ -594,14 +594,14 @@ G build_gem "activesupport", "3.0" end - bundle :update, :all => true, :raise_on_error => false + bundle :update, all: true, raise_on_error: false should_be_engine_version_incorrect end - 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,406 +647,374 @@ 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} G - bundle "show rails", :raise_on_error => false + bundle "show rails", raise_on_error: false should_be_ruby_version_incorrect end - it "fails if engine doesn't match", :bundler => "< 3" do + it "fails if engine doesn't match" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "rails" #{engine_incorrect} G - bundle "show rails", :raise_on_error => false + bundle "show rails", raise_on_error: false should_be_engine_incorrect end - it "fails if engine version doesn't match", :bundler => "< 3", :jruby_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} G - bundle "show rails", :raise_on_error => false + bundle "show rails", raise_on_error: false should_be_engine_version_incorrect end - it "fails when patchlevel doesn't match", :bundler => "< 3" do + it "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 - bundle :cache, :raise_on_error => false + bundle :cache, raise_on_error: false should_be_ruby_version_incorrect end 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 - bundle :cache, :raise_on_error => false + bundle :cache, raise_on_error: false should_be_engine_incorrect end it "fails if the engine version doesn't match", :jruby_only do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' #{engine_version_incorrect} G - bundle :cache, :raise_on_error => false + bundle :cache, raise_on_error: false 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 - bundle :cache, :raise_on_error => false + bundle :cache, raise_on_error: false should_be_ruby_version_incorrect end 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 - bundle :cache, :raise_on_error => false + bundle :cache, raise_on_error: false should_be_engine_incorrect end it "fails if the engine version doesn't match", :jruby_only do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem 'rack' + source "https://gem.repo1" + gem 'myrack' #{engine_version_incorrect} G - bundle :cache, :raise_on_error => false + bundle :cache, raise_on_error: false 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 + gemfile gemfile + "\n\n#{ruby_version_incorrect}\n" - #{ruby_version_incorrect} - G - - bundle "console", :raise_on_error => false + 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 + gemfile gemfile + "\n\n#{engine_incorrect}\n" - #{engine_incorrect} - G - - bundle "console", :raise_on_error => false + bundle "console", raise_on_error: false should_be_engine_incorrect end it "fails when engine version doesn't match", :jruby_only do - gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" - gem "activesupport", :group => :test - gem "rack_middleware", :group => :development + gemfile gemfile + "\n\n#{engine_version_incorrect}\n" - #{engine_version_incorrect} - G - - bundle "console", :raise_on_error => false + 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 + it "starts IRB with the default group loaded even when patchlevel doesn't match", :readline do + gemfile gemfile + "\n\n#{patchlevel_incorrect}\n" - #{patchlevel_incorrect} - G - - bundle "console", :raise_on_error => false - should_be_patchlevel_incorrect + bundle "console" do |input, _, _| + input.puts("puts MYRACK") + input.puts("exit") + end + should_ignore_patchlevel + expect(out).to include("0.9.1") end end 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 @@ -1082,82 +1051,82 @@ G end it "fails when ruby version doesn't match" do - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo1)}" + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo1" gem "yard" - gem "rack" + gem "myrack" #{ruby_version_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 }, raise_on_error: false expect(bundled_app_lock).not_to exist should_be_ruby_version_incorrect end it "fails when engine doesn't match" do - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo1)}" + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo1" gem "yard" - gem "rack" + gem "myrack" #{engine_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 }, raise_on_error: false expect(bundled_app_lock).not_to exist should_be_engine_incorrect end it "fails when engine version doesn't match", :jruby_only do - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo1)}" + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo1" gem "yard" - gem "rack" + gem "myrack" #{engine_version_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 }, raise_on_error: false expect(bundled_app_lock).not_to exist should_be_engine_version_incorrect end - it "fails when patchlevel doesn't match" do - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo1)}" + it "makes a Gemfile.lock even when patchlevel doesn't match" do + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo1" gem "yard" - 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 context "bundle outdated" do before do build_repo2 do - build_git "foo", :path => lib_path("foo") + build_git "foo", path: lib_path("foo") end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport", "2.3.5" gem "foo", :git => "#{lib_path("foo")}" G @@ -1166,21 +1135,21 @@ G it "returns list of outdated gems when the ruby version matches" do update_repo2 do build_gem "activesupport", "3.0" - update_git "foo", :path => lib_path("foo") + update_git "foo", path: lib_path("foo") end gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport", "2.3.5" gem "foo", :git => "#{lib_path("foo")}" #{ruby_version_correct} G - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false expected_output = <<~TABLE.gsub("x", "\\\h").tr(".", "\.").strip - Gem Current Latest Requested Groups + 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 @@ -1192,21 +1161,21 @@ G bundle :install update_repo2 do build_gem "activesupport", "3.0" - update_git "foo", :path => lib_path("foo") + update_git "foo", path: lib_path("foo") end gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport", "2.3.5" gem "foo", :git => "#{lib_path("foo")}" #{ruby_version_correct_engineless} G - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false expected_output = <<~TABLE.gsub("x", "\\\h").tr(".", "\.").strip - Gem Current Latest Requested Groups + 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 @@ -1217,91 +1186,75 @@ G it "fails when the ruby version doesn't match" do update_repo2 do build_gem "activesupport", "3.0" - update_git "foo", :path => lib_path("foo") + update_git "foo", path: lib_path("foo") end gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport", "2.3.5" gem "foo", :git => "#{lib_path("foo")}" #{ruby_version_incorrect} G - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false should_be_ruby_version_incorrect end it "fails when the engine doesn't match" do update_repo2 do build_gem "activesupport", "3.0" - update_git "foo", :path => lib_path("foo") + update_git "foo", path: lib_path("foo") end gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport", "2.3.5" gem "foo", :git => "#{lib_path("foo")}" #{engine_incorrect} G - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false should_be_engine_incorrect end it "fails when the engine version doesn't match", :jruby_only do update_repo2 do build_gem "activesupport", "3.0" - update_git "foo", :path => lib_path("foo") + update_git "foo", path: lib_path("foo") end gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport", "2.3.5" gem "foo", :git => "#{lib_path("foo")}" #{engine_version_incorrect} G - bundle "outdated", :raise_on_error => false + bundle "outdated", raise_on_error: false should_be_engine_version_incorrect end - it "fails when the patchlevel doesn't match", :jruby_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") + 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")}" #{patchlevel_incorrect} 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 + bundle "outdated", raise_on_error: false + 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 3050b87754..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,186 +18,164 @@ 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)}/ 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 describe "for bundle update" do it "shows proper messages according to the configured groups" do - bundle :update, :all => true + bundle :update, all: true expect(out).not_to include("Gems in the groups") expect(out).to include(bundle_updated_message) - bundle "config set --local without emo" + bundle_config "without emo" bundle :install - bundle :update, :all => true + bundle :update, all: true expect(out).to include("Gems in the group 'emo' were not updated") expect(out).to include(bundle_updated_message) - bundle "config set --local without emo test" + bundle_config "without emo test" bundle :install - bundle :update, :all => true + bundle :update, all: true expect(out).to include("Gems in the groups 'emo' and 'test' were not updated") expect(out).to include(bundle_updated_message) - bundle "config set --local without emo obama test" + bundle_config "without emo obama test" bundle :install - bundle :update, :all => true + bundle :update, all: true expect(out).to include("Gems in the groups 'emo', 'obama' and 'test' were not updated") expect(out).to include(bundle_updated_message) end diff --git a/spec/bundler/commands/pristine_spec.rb b/spec/bundler/commands/pristine_spec.rb index 9e496dc91a..5f80b9e534 100644 --- a/spec/bundler/commands/pristine_spec.rb +++ b/spec/bundler/commands/pristine_spec.rb @@ -4,7 +4,7 @@ require "bundler/vendored_fileutils" RSpec.describe "bundle pristine" do before :each do - build_lib "baz", :path => bundled_app do |s| + build_lib "baz", path: bundled_app do |s| s.version = "1.0.0" s.add_development_dependency "baz-dev", "=1.0.0" end @@ -13,13 +13,13 @@ RSpec.describe "bundle pristine" do build_gem "weakling" build_gem "baz-dev", "1.0.0" build_gem "very_simple_binary", &:add_c_extension - build_git "foo", :path => lib_path("foo") - build_git "git_with_ext", :path => lib_path("git_with_ext"), &:add_c_extension - build_lib "bar", :path => lib_path("bar") + build_git "foo", path: lib_path("foo") + build_git "git_with_ext", path: lib_path("git_with_ext"), &:add_c_extension + build_lib "bar", path: lib_path("bar") end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + 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 @@ -164,7 +218,7 @@ RSpec.describe "bundle pristine" do end it "raises when one of them is not in the lockfile" do - bundle "pristine abcabcabc", :raise_on_error => false + bundle "pristine abcabcabc", raise_on_error: false expect(err).to include("Could not find gem 'abcabcabc'.") end end @@ -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 @@ -181,8 +235,8 @@ RSpec.describe "bundle pristine" do bundle "pristine" makefile_contents = File.read(c_ext_dir.join("Makefile").to_s) - expect(makefile_contents).to match(/libpath =.*#{c_ext_dir}/) - expect(makefile_contents).to match(/LIBPATH =.*-L#{c_ext_dir}/) + expect(makefile_contents).to match(/libpath =.*#{Regexp.escape(c_ext_dir.to_s)}/) + expect(makefile_contents).to match(/LIBPATH =.*-L#{Regexp.escape(c_ext_dir.to_s)}/) end end @@ -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 @@ -198,14 +252,14 @@ RSpec.describe "bundle pristine" do bundle "pristine" makefile_contents = File.read(c_ext_dir.join("Makefile").to_s) - expect(makefile_contents).to match(/libpath =.*#{c_ext_dir}/) - expect(makefile_contents).to match(/LIBPATH =.*-L#{c_ext_dir}/) + expect(makefile_contents).to match(/libpath =.*#{Regexp.escape(c_ext_dir.to_s)}/) + expect(makefile_contents).to match(/LIBPATH =.*-L#{Regexp.escape(c_ext_dir.to_s)}/) end end context "when BUNDLE_GEMFILE doesn't exist" do before do - bundle "pristine", :env => { "BUNDLE_GEMFILE" => "does/not/exist" }, :raise_on_error => false + bundle "pristine", env: { "BUNDLE_GEMFILE" => "does/not/exist" }, raise_on_error: false end it "shows a meaningful error" do diff --git a/spec/bundler/commands/remove_spec.rb b/spec/bundler/commands/remove_spec.rb index d757e0be4b..8a2e6778ea 100644 --- a/spec/bundler/commands/remove_spec.rb +++ b/spec/bundler/commands/remove_spec.rb @@ -4,10 +4,10 @@ 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 + bundle "remove", raise_on_error: false expect(err).to include("Please specify gems to remove.") end @@ -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,38 +354,38 @@ 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 + 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 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" + install_gemfile <<-G, raise_on_error: false + 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 925e40b71b..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 @@ -50,14 +52,14 @@ RSpec.describe "bundle show", :bundler => "< 3" do end it "complains if gem not in bundle" do - bundle "show missing", :raise_on_error => false + bundle "show missing", raise_on_error: false expect(err).to match(/could not find gem 'missing'/i) end it "prints path of all gems in bundle sorted by name" do bundle "show --paths" - expect(out).to include(default_bundle_path("gems", "rake-13.0.1").to_s) + expect(out).to include(default_bundle_path("gems", "rake-#{rake_version}").to_s) expect(out).to include(default_bundle_path("gems", "rails-2.3.2").to_s) # Gem names are the last component of their path. @@ -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 @@ -104,7 +124,7 @@ RSpec.describe "bundle show", :bundler => "< 3" do end it "prints out branch names other than main" do - update_git "foo", :branch => "omg" do |s| + update_git "foo", branch: "omg" do |s| s.write "lib/foo.rb", "FOO = '1.0.omg'" end @revision = revision_for(lib_path("foo-1.0"))[0...6] @@ -129,7 +149,7 @@ RSpec.describe "bundle show", :bundler => "< 3" do end it "handles when a version is a '-' prerelease" do - @git = build_git("foo", "1.0.0-beta.1", :path => lib_path("foo")) + @git = build_git("foo", "1.0.0-beta.1", path: lib_path("foo")) install_gemfile <<-G gem "foo", "1.0.0-beta.1", :git => "#{lib_path("foo")}" G @@ -142,24 +162,24 @@ RSpec.describe "bundle show", :bundler => "< 3" do context "in a fresh gem in a blank git repo" do before :each do - build_git "foo", :path => lib_path("foo") + build_git "foo", path: lib_path("foo") File.open(lib_path("foo/Gemfile"), "w") {|f| f.puts "gemspec" } - sys_exec "rm -rf .git && git init", :dir => lib_path("foo") + sys_exec "rm -rf .git && git init", dir: lib_path("foo") end it "does not output git errors" do - bundle :show, :dir => lib_path("foo") + bundle :show, dir: lib_path("foo") expect(err_without_deprecations).to be_empty end end 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,58 +187,31 @@ 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 invalid_regexp = "[]" - bundle "show #{invalid_regexp}", :raise_on_error => false + bundle "show #{invalid_regexp}", raise_on_error: false expect(err).to include("Could not find gem '#{invalid_regexp}'.") end end - - 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 7016c3e19f..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,91 +24,108 @@ 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 + bundle "update", raise_on_error: false expect(bundled_app_lock).to exist 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" end - bundle "update", :all => true + bundle "update", all: true expect(out).to include("Bundle updated!") - expect(the_bundle).to include_gems "rack 1.2", "rack-obama 1.0", "activesupport 3.0" + 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 + bundle "update", all: true, raise_on_error: false expect(bundled_app_lock).to exist end 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 + bundle "update --gemfile OmgFile", all: true expect(bundled_app("OmgFile.lock")).to exist end 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)}\"" - bundle :update, :raise_on_error => false + 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)}\"" - bundle "update --all foo", :raise_on_error => false + 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,20 +180,20 @@ 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 inform the user" do - bundle "update halting-problem-solver", :raise_on_error => false + bundle "update halting-problem-solver", raise_on_error: false expect(err).to include "Could not find gem 'halting-problem-solver'" end it "should suggest alternatives" do - bundle "update platformspecific", :raise_on_error => false - expect(err).to include "Did you mean platform_specific?" + bundle "update platformspecific", raise_on_error: false + 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,20 +240,20 @@ 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 end - bundle "update", :all => true + bundle "update", all: true expect(the_bundle).to include_gems("slim 3.0.9", "slim-rails 3.1.3", "slim_lint 0.16.1") end @@ -269,15 +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_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) @@ -289,14 +311,14 @@ RSpec.describe "bundle update" do DEPENDENCIES countries country_select - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L previous_lockfile = lockfile - bundle "lock --update" + bundle "lock --update", env: { "DEBUG" => "1" }, verbose: true expect(lockfile).to eq(previous_lockfile) end @@ -326,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" @@ -334,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) @@ -352,7 +374,7 @@ RSpec.describe "bundle update" do quickbooks-ruby BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "update --conservative --verbose" @@ -385,7 +407,7 @@ RSpec.describe "bundle update" do end gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gemspec @@ -399,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) @@ -413,7 +435,7 @@ RSpec.describe "bundle update" do sneakers BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "update --verbose" @@ -423,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| @@ -434,20 +565,20 @@ RSpec.describe "bundle update" do build_gem "c", "2.0" end - install_gemfile <<-G, :verbose => true - source "#{file_uri_for(gem_repo4)}" + install_gemfile <<-G, verbose: true + 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 end - bundle "update", :all => true, :verbose => true + bundle "update", all: true, verbose: true expect(the_bundle).to include_gems("a 1.0", "b 1.0", "c 2.0") end @@ -459,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 @@ -467,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 @@ -492,22 +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_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) @@ -518,9 +654,9 @@ RSpec.describe "bundle update" do DEPENDENCIES activesupport (~> 6.0.0) - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "update activesupport" @@ -543,19 +679,45 @@ RSpec.describe "bundle update" do before do build_repo2 - install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + gemfile <<-G + source "https://gem.repo2" gem "activesupport" - gem "rack-obama" + gem "myrack-obama" gem "platform_specific" G 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 @@ -566,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 @@ -601,9 +763,9 @@ RSpec.describe "bundle update" do context "when there is a source with the same name as a gem in a group" do before do - build_git "foo", :path => lib_path("activesupport") + build_git "foo", path: lib_path("activesupport") install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport", :group => :development gem "foo", :git => "#{lib_path("activesupport")}" G @@ -611,7 +773,7 @@ RSpec.describe "bundle update" do it "should not update the gems from that source" do update_repo2 { build_gem "activesupport", "3.0" } - update_git "foo", "2.0", :path => lib_path("activesupport") + update_git "foo", "2.0", path: lib_path("activesupport") bundle "update --group development" expect(the_bundle).to include_gems "activesupport 3.0" @@ -622,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" @@ -636,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 @@ -646,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" - bundle "update", :all => true, :raise_on_error => false + 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(/You are trying to install in deployment mode after changing.your Gemfile/m) - expect(err).to match(/freeze \nby running `bundle config unset deployment`./m) + expect(err).to 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 suggest different command when frozen is set globally", :bundler => "< 3" do - bundle "config set --global frozen 1" - bundle "update", :all => true, :raise_on_error => false - expect(err).to match(/You are trying to install in deployment mode after changing.your Gemfile/m). - and match(/freeze \nby running `bundle config unset frozen`./m) + it "should fail loudly when frozen is set globally" do + bundle_config_global "frozen 1" + bundle "update", all: true, raise_on_error: 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 suggest different command when frozen is set globally", :bundler => "3" do - bundle "config set --global deployment true" - bundle "update", :all => true, :raise_on_error => false - expect(err).to match(/You are trying to install in deployment mode after changing.your Gemfile/m). - and match(/freeze \nby running `bundle config unset deployment`./m) + it "should fail loudly when deployment is set globally" do + bundle_config_global "deployment true" + bundle "update", all: true, raise_on_error: 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(/You are trying to install in deployment mode after changing.your Gemfile/m) - expect(err).not_to match(/by running/) + bundle "update", all: true, raise_on_error: false, env: { "BUNDLE_FROZEN" => "true" } + expect(err).to eq("Bundler is unlocking, but the lockfile can't be updated because frozen mode is set") end end @@ -690,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" } @@ -701,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" } @@ -721,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 @@ -753,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 @@ -772,123 +942,140 @@ RSpec.describe "bundle update" do end end - it "shows the previous version of the gem when updated from rubygems source", :bundler => "< 3" do + it "shows the previous version of the gem when updated from rubygems source" do build_repo2 install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "activesupport" G - bundle "update", :all => true + bundle "update", all: true, verbose: true expect(out).to include("Using activesupport 2.3.5") update_repo2 do build_gem "activesupport", "3.0" end - bundle "update", :all => true + bundle "update", all: true expect(out).to include("Installing activesupport 3.0 (was 2.3.5)") end - context "with suppress_install_using_messages set" do - before { bundle "config set suppress_install_using_messages true" } - - it "only prints `Using` for versions that have changed" do - build_repo4 do - build_gem "bar" - build_gem "foo" - end - - install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" - gem "bar" - gem "foo" - G + it "only prints `Using` for versions that have changed" do + build_repo4 do + build_gem "bar" + build_gem "foo" + end - bundle "update", :all => true - expect(out).to match(/Resolving dependencies\.\.\.\.*\nBundle updated!/) + install_gemfile <<-G + source "https://gem.repo4" + gem "bar" + gem "foo" + G - update_repo4 do - build_gem "foo", "2.0" - end + bundle "update", all: true + expect(out).to match(/Resolving dependencies\.\.\.\.*\nBundle updated!/) - bundle "update", :all => true - out.sub!("Removing foo (1.0)\n", "") - expect(out).to match(/Resolving dependencies\.\.\.\.*\nFetching foo 2\.0 \(was 1\.0\)\nInstalling foo 2\.0 \(was 1\.0\)\nBundle updated/) + build_repo4 do + build_gem "foo", "2.0" end + + bundle "update", all: true + 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 - bundle "update nonexisting", :raise_on_error => false + bundle "update nonexisting", raise_on_error: false expect(err).to include("This Bundle hasn't been installed yet. Run `bundle install` to update and install the bundled gems.") expect(exitstatus).to eq(22) end - context "with multiple, duplicated sources, with lockfile in old format", :bundler => "< 3" do + context "with multiple sources and caching enabled" do before do build_repo2 do - build_gem "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 @@ -900,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 @@ -952,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 @@ -986,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" } @@ -1000,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) @@ -1017,11 +1204,11 @@ 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 - bundle :update, :all => true + bundle :update, all: true expect(the_bundle).to include_gem "a 1.1" end @@ -1039,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) @@ -1055,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 @@ -1072,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 + 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 @@ -1087,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) + bundle "update", all: true, raise_on_error: false + 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 @@ -1112,28 +1299,29 @@ 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 + it "removes the Ruby from the Gemfile.lock" do bundle "update --ruby" 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 @@ -1142,31 +1330,32 @@ 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)}" + ruby '~> #{current_ruby_minor}' + source "https://gem.repo1" G end + it "updates the Gemfile.lock with the latest version" do bundle "update --ruby" expect(lockfile).to eq <<~L - GEM - remote: #{file_uri_for(gem_repo1)}/ - specs: - - PLATFORMS - #{lockfile_platforms} + GEM + remote: https://gem.repo1/ + specs: - DEPENDENCIES + PLATFORMS + #{lockfile_platforms} - RUBY VERSION + DEPENDENCIES + #{checksums_section_when_enabled} + RUBY VERSION #{Bundler::RubyVersion.system} - BUNDLED WITH + BUNDLED WITH #{Bundler::VERSION} L end @@ -1176,16 +1365,16 @@ 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 - bundle "update --ruby", :raise_on_error => false + bundle "update --ruby", raise_on_error: false expect(err).to include("Your Ruby version is #{Bundler::RubyVersion.system.gem_version}, but your Gemfile specified ~> 2.1.0") end @@ -1195,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 @@ -1203,253 +1392,407 @@ RSpec.describe "bundle update --ruby" do DEPENDENCIES + CHECKSUMS + RUBY VERSION 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 + it "updates the Gemfile.lock with the latest version" do bundle "update --ruby" 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 end RSpec.describe "bundle update --bundler" do - it "updates the bundler version in the lockfile without re-resolving" do + it "updates the bundler version in the lockfile" do build_repo4 do - build_gem "rack", "1.0" + build_gem "bundler", "2.5.9" + build_gem "myrack", "1.0" + end + + 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 - lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, '\11.0.0\2') + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + myrack (1.0) - FileUtils.rm_r gem_repo4 + PLATFORMS + #{lockfile_platforms} - bundle :update, :bundler => true, :artifice => "compact_index", :verbose => true + DEPENDENCIES + myrack + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, '\11.0.0\2') + + 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") - bundle :update, :bundler => true, :artifice => "compact_index", :verbose => true + checksums = checksums_section_when_enabled do |c| + c.checksum(gem_repo4, "myrack", "1.0") + end + + 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} + L + + expect(the_bundle).to include_gem "myrack 1.0" + end + it "updates the bundler version in the lockfile even if the latest version is not installed", :ruby_repo do + bundle_config "path.system true" + + pristine_system_gems "bundler-9.0.0" + + build_repo4 do + build_gem "myrack", "1.0" + + build_bundler "999.0.0" + end + + checksums = checksums_section do |c| + c.checksum(gem_repo4, "myrack", "1.0") + c.checksum(gem_repo4, "bundler", "999.0.0") + end + + install_gemfile <<-G + source "https://gem.repo4" + gem "myrack" + G + + bundle :update, bundler: true, verbose: true + + expect(out).to include("Updating bundler to 999.0.0") + expect(out).to include("Running `bundle update --bundler \"> 0.a\" --verbose` with bundler 999.0.0") + expect(out).not_to include("Installing Bundler 2.99.9 and restarting using that version.") + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + myrack (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + 999.0.0 L - expect(the_bundle).to include_gem "rack 1.0" + expect(the_bundle).to include_gems "bundler 999.0.0" + expect(the_bundle).to include_gems "myrack 1.0" end - it "updates the bundler version in the lockfile even if the latest version is not installed", :ruby_repo, :realworld do - pristine_system_gems "bundler-2.3.9" + it "does not claim to update to Bundler version to a wrong version when cached gems are present" do + pristine_system_gems "bundler-4.99.0" build_repo4 do - build_gem "rack", "1.0" + build_gem "myrack", "3.0.9.1" + + build_bundler "4.99.0" end - install_gemfile <<-G, :env => { "BUNDLER_IGNORE_DEFAULT_GEM" => "true" } - source "#{file_uri_for(gem_repo4)}" - gem "rack" + gemfile <<~G + source "https://gem.repo4" + gem "myrack" G - lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, "2.3.9") - bundle :update, :bundler => true, :artifice => "vcr", :verbose => true, :env => { "BUNDLER_IGNORE_DEFAULT_GEM" => "true" } + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + myrack (3.0.9.1) - # Only updates properly on modern RubyGems. + PLATFORMS + #{lockfile_platforms} - if Gem.rubygems_version >= Gem::Version.new("3.3.0.dev") - expect(out).to include("Updating bundler to 2.3.10") - expect(out).to include("Using bundler 2.3.10") - expect(out).not_to include("Installing Bundler 2.3.9 and restarting using that version.") + DEPENDENCIES + myrack - expect(lockfile).to eq <<~L - GEM - remote: #{file_uri_for(gem_repo4)}/ - specs: - rack (1.0) + BUNDLED WITH + 2.99.0 + L - PLATFORMS - #{lockfile_platforms} + bundle :cache, verbose: true - DEPENDENCIES - rack + bundle :update, bundler: true, verbose: true - BUNDLED WITH - 2.3.10 - L + 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-9.9.9" - expect(the_bundle).to include_gems "bundler 2.3.10" + build_repo4 do + build_gem "myrack", "1.0" + + build_bundler "9.9.9" + build_bundler "999.0.0" do |s| + s.required_ruby_version = "> #{Gem.ruby_version}" + end + end + + checksums = checksums_section do |c| + c.checksum(gem_repo4, "myrack", "1.0") + c.checksum(gem_repo4, "bundler", "9.9.9") end - expect(the_bundle).to include_gems "rack 1.0" + install_gemfile <<-G + source "https://gem.repo4" + gem "myrack" + G + + bundle :update, bundler: true, verbose: true + + expect(out).to include("Using bundler 9.9.9") + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + myrack (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack + #{checksums} + BUNDLED WITH + 9.9.9 + L + + 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", :realworld do - pristine_system_gems "bundler-2.3.9" + it "errors if the explicit target version does not exist" do + 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 => "vcr", :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" + 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. + checksums = checksums_section_when_enabled do |c| + c.checksum(gem_repo4, "myrack", "1.0") + end + checksums.delete("bundler") - 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.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", :raise_on_error => false + system_gems "bundler-9.0.0", path: local_gem_path + bundle :update, bundler: "9.0.0", verbose: true expect(out).not_to include("Fetching gem metadata from https://rubygems.org/") # Only updates properly on modern RubyGems. + checksums = checksums_section_when_enabled do |c| + c.checksum(gem_repo4, "myrack", "1.0") + c.checksum(local_gem_path, "bundler", "9.0.0", Gem::Platform::RUBY, "cache") + end - 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-9.0.0" + + gemfile <<~G + source "https://gem.repo2" + G + + lockfile <<-L + GEM + remote: https://gem.repo2/ + specs: + + PLATFORMS + ruby + + DEPENDENCIES + + BUNDLED WITH + 9.0.0 + L + + 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 @@ -1476,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' @@ -1485,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 @@ -1493,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" @@ -1508,7 +1851,7 @@ RSpec.describe "bundle update conservative" do end it "update all" do - bundle "update --patch", :all => true + bundle "update --patch", all: true expect(the_bundle).to include_gems "foo 1.4.5", "bar 2.1.1", "qux 1.0.1" end @@ -1530,7 +1873,7 @@ RSpec.describe "bundle update conservative" do end it "minor preferred" do - bundle "update --minor --strict", :all => true + bundle "update --minor --strict", all: true expect(the_bundle).to include_gems "foo 1.5.0", "bar 2.1.1", "qux 1.1.0" end @@ -1581,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' @@ -1590,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) @@ -1609,8 +1952,10 @@ RSpec.describe "bundle update conservative" do shared_owner_a shared_owner_b + CHECKSUMS + BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -1641,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) @@ -1661,15 +2014,15 @@ RSpec.describe "bundle update conservative" do isolated_owner shared_owner_a shared_owner_b - + #{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' @@ -1678,17 +2031,63 @@ 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 - bundle "update --patch --minor", :all => true, :raise_on_error => false + bundle "update --patch --minor", all: true, raise_on_error: false expect(err).to eq "Provide only one of the following options: minor, patch" end diff --git a/spec/bundler/commands/version_spec.rb b/spec/bundler/commands/version_spec.rb index 53d545f5fa..4320ad0611 100644 --- a/spec/bundler/commands/version_spec.rb +++ b/spec/bundler/commands/version_spec.rb @@ -4,44 +4,62 @@ require_relative "../support/path" RSpec.describe "bundle version" do if Spec::Path.ruby_core? - COMMIT_HASH = /unknown|[a-fA-F0-9]{7,}/.freeze + COMMIT_HASH = /unknown|[a-fA-F0-9]{7,}/ else - COMMIT_HASH = /[a-fA-F0-9]{7,}/.freeze + COMMIT_HASH = /[a-fA-F0-9]{7,}/ end context "with -v" do - it "outputs the version", :bundler => "< 3" do + it "outputs the version 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 cf612397ab..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(strip_whitespace(<<-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(strip_whitespace(<<-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(strip_whitespace(<<-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 4c6c77a61e..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" + 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 + 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,37 +22,37 @@ 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" - install_gemfile <<-G, :artifice => "compact_index" + 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" + bundle :update, artifice: "fail", all: true + 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! FileUtils.mkdir_p(tmp("broken_path")) File.open(tmp("broken_path/git"), "w", 0o755) do |f| - f.puts strip_whitespace(<<-RUBY) + f.puts <<~RUBY #!/usr/bin/env ruby - 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 @@ -75,20 +71,20 @@ RSpec.describe "bundle install with :allow_offline_install" do it "will install from a cached git repo" do skip "doesn't print errors" if Gem.win_platform? - git = build_git "a", "1.0.0", :path => lib_path("a") - update_git("a", :path => git.path, :branch => "new_branch") + git = build_git "a", "1.0.0", path: lib_path("a") + update_git("a", path: git.path, branch: "new_branch") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "a", :git => #{git.path.to_s.dump} G - break_git_remote_ops! { bundle :update, :all => true } + break_git_remote_ops! { bundle :update, all: true } expect(err).to include("Using cached git data because of network errors") expect(the_bundle).to be_locked 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 e7ec3bc7e7..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,20 +23,20 @@ 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)}" + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo2" gem "rails", "3.0" gem "bundler", "0.9.1" G - nice_error = <<-E.strip.gsub(/^ {8}/, "") + nice_error = <<~E.strip Could not find compatible versions Because the current Bundler version (#{Bundler::VERSION}) does not satisfy bundler = 0.9.1 @@ -50,17 +50,17 @@ RSpec.describe "bundle install" do end it "causes a conflict if explicitly requesting a non matching requirement on bundler" do - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo2)}" + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo2" gem "rails", "3.0" gem "bundler", "~> 0.8" G - nice_error = <<-E.strip.gsub(/^ {8}/, "") + 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, @@ -73,13 +73,13 @@ RSpec.describe "bundle install" do end it "causes a conflict if explicitly requesting a version of bundler that doesn't exist" do - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo2)}" + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo2" gem "rails", "3.0" gem "bundler", "0.9.2" G - nice_error = <<-E.strip.gsub(/^ {8}/, "") + nice_error = <<~E.strip Could not find compatible versions Because the current Bundler version (#{Bundler::VERSION}) does not satisfy bundler = 0.9.2 @@ -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| @@ -143,13 +143,13 @@ RSpec.describe "bundle install" do end end - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo2)}" + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo2" gem "activemerchant" gem "rails_pinned_to_old_activesupport" G - nice_error = <<-E.strip.gsub(/^ {8}/, "") + nice_error = <<~E.strip Could not find compatible versions Because every version of rails_pinned_to_old_activesupport depends on activesupport = 1.2.3 @@ -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| @@ -171,13 +171,13 @@ RSpec.describe "bundle install" do end end - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo2)}" + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo2" gem "rails_pinned_to_old_activesupport" gem "activesupport", "2.3.5" G - nice_error = <<-E.strip.gsub(/^ {8}/, "") + nice_error = <<~E.strip Could not find compatible versions Because every version of rails_pinned_to_old_activesupport depends on activesupport = 1.2.3 @@ -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 484664b433..a3b4a87ecf 100644 --- a/spec/bundler/install/deploy_spec.rb +++ b/spec/bundler/install/deploy_spec.rb @@ -3,64 +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 Gemfile.lock") - end - - it "fails without a lockfile and says that --frozen requires a lock" do - bundle "install --frozen", :raise_on_error => false - expect(err).to include("The --frozen flag requires a Gemfile.lock") - 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 "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 - 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 "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" - bundle "install --gemfile #{tmp}/bundled_app/Gemfile", :dir => tmp - expect(the_bundle).to include_gems "rack 1.0" + pristine_system_gems + bundle_config "deployment true" + bundle_config "path vendor/bundle" + bundle "install --gemfile #{tmp}/bundled_app/Gemfile", dir: tmp + expect(the_bundle).to include_gems "myrack 1.0" end 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 @@ -68,132 +47,92 @@ 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 } + bundle "exec bundle check", env: { "PATH" => path } end it "works when using path gems from the same path and the version is specified" do - build_lib "foo", :path => lib_path("nested/foo") - build_lib "bar", :path => lib_path("nested/bar") + build_lib "foo", path: lib_path("nested/foo") + build_lib "bar", path: lib_path("nested/bar") gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + 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") + 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 it "works when there are credentials in the source URL" do - install_gemfile(<<-G, :artifice => "endpoint_strict_basic_authentication", :quiet => true) + install_gemfile(<<-G, artifice: "endpoint_strict_basic_authentication", quiet: true) source "http://user:pass@localgemserver.test/" - gem "rack-obama", ">= 1.0" + gem "myrack-obama", ">= 1.0" G - bundle "config set --local deployment true" - bundle :install, :artifice => "endpoint_strict_basic_authentication" + 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 + bundle_config "deployment true" end - context "when allow_deployment_source_credential_changes is false" do - before { bundle "config set allow_deployment_source_credential_changes false" } - - it "prevents the replace" do - bundle :install, :raise_on_error => false - - expect(err).to match(/The list of sources changed/) - end - end - - context "when BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES env var is true" do - before { ENV["BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES"] = "true" } - - it "allows the replace" do - bundle :install - - 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 @@ -202,40 +141,52 @@ RSpec.describe "install in deployment or frozen mode" do bundle "install" end - it "installs gems by default to vendor/bundle", :bundler => "< 3" do - bundle "install --deployment" + it "installs gems by default to vendor/bundle" do + bundle_config "deployment true" + expect do + bundle "install" + end.not_to change { bundled_app_lock.mtime } expect(out).to include("vendor/bundle") end - it "installs gems to custom path if specified", :bundler => "< 3" do - bundle "install --path vendor/bundle2 --deployment" + it "installs gems to custom path if specified" do + bundle_config "path vendor/bundle2" + bundle_config "deployment true" + bundle "install" expect(out).to include("vendor/bundle2") end - it "works with the --deployment flag if you didn't change anything", :bundler => "< 3" do - bundle "install --deployment" + it "installs gems to custom path if specified, even when configured through ENV" do + bundle_config "deployment true" + bundle "install", env: { "BUNDLE_PATH" => "vendor/bundle2" } + expect(out).to include("vendor/bundle2") end - it "works with the --frozen flag if you didn't change anything", :bundler => "< 3" do - bundle "install --frozen" + it "works with the `frozen` setting" do + bundle_config "frozen true" + expect do + bundle "install" + end.not_to change { bundled_app_lock.mtime } end it "works with BUNDLE_FROZEN if you didn't change anything" do - bundle :install, :env => { "BUNDLE_FROZEN" => "true" } + expect do + bundle :install, env: { "BUNDLE_FROZEN" => "true" } + end.not_to change { bundled_app_lock.mtime } end - it "explodes with the --deployment flag if you make a change and don't check in the lockfile" do + it "explodes with the `deployment` setting if you make a change and don't check in the lockfile" do gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" - gem "rack-obama" + source "https://gem.repo1" + gem "myrack" + gem "myrack-obama" G - bundle "config set --local deployment true" - bundle :install, :raise_on_error => false - expect(err).to include("deployment mode") + 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 @@ -243,87 +194,125 @@ 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 :install, :env => { "DEBUG" => "1" } + 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") end - it "works if a gem is missing, but it's on a different platform, and the Gemfile has no global source", :bundler => "< 3" do + it "works if a gem is missing, but it's on a different platform" do + build_repo2 + install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" do + source "https://gem.repo2" + + source "https://gem.repo1" do gem "rake", platform: :#{not_local_tag} end G - bundle :install, :env => { "BUNDLE_FROZEN" => "true" } + bundle :install, env: { "BUNDLE_FROZEN" => "true" } expect(last_command).to be_success end + it "shows a good error if a gem is missing from the lockfile" do + build_repo4 do + build_gem "foo" + build_gem "bar" + end + + gemfile <<-G + source "https://gem.repo4" + + gem "foo" + gem "bar" + G + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + foo (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo + bar + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle :install, env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false, artifice: "compact_index" + expect(err).to include("Your 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 :install, :raise_on_error => false + 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", :bundler => "< 3" do + 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("deployment mode") + bundle "install", raise_on_error: false + expect(err).to include("frozen mode") expect(err).to include("You have added to the Gemfile") - expect(err).to include("* rack-obama") + expect(err).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("deployment mode") + bundle "install", raise_on_error: false + expect(err).to include("frozen mode") expect(err).to include("You have added to the Gemfile") - expect(err).to include("* rack-obama") + expect(err).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 "installs gems by default to vendor/bundle when deployment mode is set via an environment variable", :bundler => "< 3" do + it "installs gems by default to vendor/bundle when deployment mode is set via an environment variable" do ENV["BUNDLE_DEPLOYMENT"] = "true" bundle "install" expect(out).to include("vendor/bundle") end - it "installs gems to custom path when deployment mode is set via an environment variable ", :bundler => "< 3" do + it "installs gems to custom path when deployment mode is set via an environment variable " do ENV["BUNDLE_DEPLOYMENT"] = "true" ENV["BUNDLE_PATH"] = "vendor/bundle2" bundle "install" @@ -332,87 +321,119 @@ 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" ENV["BUNDLE_DEPLOYMENT"] = "false" bundle "install" - expect(out).not_to include("deployment mode") + expect(out).not_to include("frozen mode") expect(out).not_to include("You have added to the Gemfile") - expect(out).not_to include("* rack-obama") + 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 :install, :raise_on_error => false - expect(err).to include("deployment mode") + 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 :install, :raise_on_error => false - expect(err).to include("deployment mode") + 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 :install, :raise_on_error => false - expect(err).to include("deployment mode") + 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 :install, :raise_on_error => false - expect(err).to include("deployment mode") - expect(err).to include("You have changed in the Gemfile:\n* rack from `#{lib_path("rack")}` to `no specified source`") + bundle_config "frozen true" + bundle :install, raise_on_error: false + expect(err).to include("frozen mode") + expect(err).to include("You have changed in the Gemfile:\n* myrack from `#{lib_path("myrack")}` to `https:/my-git-repo-for-myrack`") expect(err).not_to include("You have added to the Gemfile") expect(err).not_to include("You have deleted from the Gemfile") end @@ -420,50 +441,49 @@ 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 - expect(err).to include strip_whitespace(<<-E).strip -The dependencies in your gemfile changed + 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 + You have added to the Gemfile: + * myrack (= 1.0.0) + * myrack-obama -You have deleted from the Gemfile: -* rack + You have deleted from the Gemfile: + * myrack E end end context "with path in Gemfile and packed" do it "works fine after bundle package and bundle install --local" do - build_lib "foo", :path => lib_path("foo") + build_lib "foo", path: lib_path("foo") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + 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("You are trying to install in deployment mode after changing your Gemfile") + 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 4a9c33754f..32ca455439 100644 --- a/spec/bundler/install/failure_spec.rb +++ b/spec/bundler/install/failure_spec.rb @@ -14,8 +14,8 @@ RSpec.describe "bundle install" do end end - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo2)}" + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo2" gem "rails" G expect(err).to start_with("Gem::Ext::BuildError: ERROR: Failed to build gem native extension.") @@ -39,8 +39,8 @@ In Gemfile: end it "removes the downloaded .gem" do - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo4)}" + install_gemfile <<-G, raise_on_error: false + 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 02283291b4..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,21 +10,21 @@ 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") expect(out).to include("Bundle complete") - expect(the_bundle).to include_gem "gunks 0.0.1", :source => "path@#{bundled_app("gems", "gunks")}" + expect(the_bundle).to include_gem "gunks 0.0.1", source: "path@#{bundled_app("gems", "gunks")}" end end @@ -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" @@ -64,14 +64,14 @@ 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)}" + build_lib("a", path: bundled_app("gems/a")) + 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,28 +95,28 @@ 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 expect(out).to include("Resolving dependencies") expect(out).to include("Bundle complete") - expect(the_bundle).to include_gem "gunks 0.0.1", :source => "path@#{bundled_app("gems", "gunks")}" + expect(the_bundle).to include_gem "gunks 0.0.1", source: "path@#{bundled_app("gems", "gunks")}" end end 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 0e9f1f0292..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)}" + install_gemfile <<-G, env: { "BUNDLE_FORCE_RUBY_PLATFORM" => "true" } + 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,36 +74,63 @@ 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)}" + install_gemfile <<-G, env: { "DEBUG_RESOLVER" => "true" } + 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 + 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: https://gem.repo4 + specs: + platform_specific (1.0) + + PLATFORMS + ruby + + DEPENDENCIES + platform_specific + + BUNDLED WITH + #{Bundler::VERSION} + L + + 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 "https://gem.repo4" + + gem "platform_specific", :force_ruby_platform => true + G + + 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 2aa4214818..e51fc9247d 100644 --- a/spec/bundler/install/gemfile/gemspec_spec.rb +++ b/spec/bundler/install/gemfile/gemspec_spec.rb @@ -8,56 +8,36 @@ 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 - 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" - expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :development + expect(the_bundle).to include_gems "bar-dev 1.0.0", groups: :development end it "that is hidden should install runtime and development dependencies" do - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp("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" - expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :development + expect(the_bundle).to include_gems "bar-dev 1.0.0", groups: :development end it "should handle a list of requirements" do @@ -66,153 +46,153 @@ 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")}' + install_gemfile <<-G, raise_on_error: false + 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")}' + install_gemfile <<-G, raise_on_error: false + 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" - expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :development + expect(the_bundle).to include_gems "bar-dev 1.0.0", groups: :development end it "should use a specific group for development dependencies" do - build_lib("foo", :path => tmp.join("foo")) do |s| + build_lib("foo", path: tmp("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" - expect(the_bundle).not_to include_gems "bar-dev 1.0.0", :groups => :development - expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :dev + expect(the_bundle).not_to include_gems "bar-dev 1.0.0", groups: :development + expect(the_bundle).to include_gems "bar-dev 1.0.0", groups: :dev end it "should match a lockfile even if the gemspec defines development dependencies" do - build_lib("foo", :path => tmp.join("foo")) do |s| - 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", "=13.0.1" + 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(/install in deployment mode after changing/) + expect(output).not_to match(/the lockfile can't be updated because frozen mode is set/) end it "should match a lockfile without needing to re-resolve" do - build_lib("foo", :path => tmp.join("foo")) do |s| - 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 + bundle "install", verbose: true message = "Found no changes, using resolution from the lockfile" expect(out.scan(message).size).to eq(1) end it "should match a lockfile 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 + 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" + bundle "update --bundler", artifice: "compact_index", verbose: true + expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 java" end it "should evaluate the gemspec in its directory" do - build_lib("foo", :path => tmp.join("foo")) - 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")}' + install_gemfile <<-G, raise_on_error: false + gemspec :path => '#{tmp("foo")}' G - expect(last_command.stdboth).not_to include("ahh") + expect(stdboth).not_to include("ahh") end it "allows the gemspec to activate other gems" do @@ -221,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) + 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 @@ -238,26 +218,26 @@ 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 - build_gem "deps", :to_bundle => true do |s| + build_gem "deps", to_bundle: true do |s| s.add_dependency "foo", "= 0.0.1" end - build_gem "foo", "0.0.1", :to_bundle => true + build_gem "foo", "0.0.1", to_bundle: true install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + 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 @@ -269,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" @@ -280,15 +260,34 @@ 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") + 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 - build_lib "omg", "1.0", :path => lib_path("omg") + build_lib "omg", "1.0", path: lib_path("omg") bundle :install @@ -298,23 +297,23 @@ RSpec.describe "bundle install from an existing gemspec" do context "in deployment mode" do context "when the lockfile was not updated after a change to the gemspec's dependencies" do it "reports that installation failed" do - build_lib "cocoapods", :path => bundled_app do |s| + build_lib "cocoapods", path: bundled_app do |s| s.add_dependency "activesupport", ">= 1" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gemspec G expect(the_bundle).to include_gems("cocoapods 1.0", "activesupport 2.3.5") - build_lib "cocoapods", :path => bundled_app do |s| + build_lib "cocoapods", path: bundled_app do |s| s.add_dependency "activesupport", ">= 1.0.1" end - bundle "config set --local deployment true" - bundle :install, :raise_on_error => false + bundle_config "deployment true" + bundle :install, raise_on_error: false expect(err).to include("changed") end @@ -324,24 +323,24 @@ RSpec.describe "bundle install from an existing gemspec" do context "when child gemspecs conflict with a released gemspec" do before do # build the "parent" gem that depends on another gem in the same repo - build_lib "source_conflict", :path => bundled_app do |s| - s.add_dependency "rack_middleware" + build_lib "source_conflict", path: bundled_app do |s| + 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 @@ -349,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 @@ -358,35 +357,39 @@ RSpec.describe "bundle install from an existing gemspec" do gemspec :path => "../foo" G + checksums = checksums_section_when_enabled do |c| + c.no_checksum "foo", "1.0" + end + lockfile <<-L PATH 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 @@ -394,16 +397,16 @@ RSpec.describe "bundle install from an existing gemspec" do end it "should install" do - results = bundle "install", :artifice => "endpoint" - expect(results).to include("Installing rack 1.0.0") - expect(the_bundle).to include_gems "rack 1.0.0" + results = bundle "install", artifice: "endpoint" + 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" + results = bundle "install", artifice: "endpoint" + 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 @@ -416,39 +419,49 @@ RSpec.describe "bundle install from an existing gemspec" do end end - build_lib "foo", :path => bundled_app do |s| - if platform_specific_type == :runtime + build_lib "foo", path: bundled_app do |s| + 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(lockfile).to eq strip_whitespace(<<-L) + expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 ruby" + + 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" + c.checksum gem_repo2, "platform_specific", "1.0", "x64-mingw-ucrt" + end + + expect(lockfile).to eq <<~L PATH remote: . specs: @@ -456,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 @@ -480,31 +493,39 @@ 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(lockfile).to eq strip_whitespace(<<-L) + expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 ruby" + + 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" + c.checksum gem_repo2, "platform_specific", "1.0", "x64-mingw-ucrt" + end + + expect(lockfile).to eq <<~L PATH remote: . specs: 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 @@ -514,33 +535,42 @@ 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(lockfile).to eq strip_whitespace(<<-L) + expect(the_bundle).to include_gems "foo 1.0", "indirect_platform_specific 1.0", "platform_specific 1.0 ruby" + + 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" + c.checksum gem_repo2, "platform_specific", "1.0", "x64-mingw-ucrt" + end + + expect(lockfile).to eq <<~L PATH remote: . specs: 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 @@ -550,81 +580,85 @@ 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_enabled do |c| + c.no_checksum "chef", "17.1.17" + 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 PATH 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 @@ -637,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 @@ -651,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 @@ -660,6 +694,12 @@ 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_enabled do |c| + c.no_checksum "activeadmin", "2.9.0" + c.checksum gem_repo4, "jruby-openssl", "0.10.7", "java" + c.checksum gem_repo4, "railties", "6.1.4" + end + expect(lockfile).to eq <<~L PATH remote: ../activeadmin @@ -668,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) @@ -679,12 +719,12 @@ RSpec.describe "bundle install from an existing gemspec" do DEPENDENCIES activeadmin! 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 e3be680d89..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,15 +31,61 @@ 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 + install_base_gemfile + FileUtils.rm_r(default_cache_path) + ruby "require 'bundler/setup'" + 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 + expect(Dir["#{home}/.bundle/cache/git/foo-1.0-*"]).to have_attributes size: 1 bundle "install --verbose" expect(err).to be_empty @@ -42,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 @@ -52,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) @@ -60,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)}" + install_gemfile bundled_app2("Gemfile"), <<-G, dir: bundled_app2 + source "https://gem.repo1" git "#{lib_path("foo-1.0")}" do gem 'foo' end @@ -78,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 @@ -85,8 +139,8 @@ RSpec.describe "bundle install with git sources" do it "complains if pinned specs don't exist in the git repo" do build_git "foo" - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo1)}" + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo1" gem "foo", "1.1", :git => "#{lib_path("foo-1.0")}" G @@ -98,8 +152,8 @@ RSpec.describe "bundle install with git sources" do s.platform = "java" end - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo1)}" + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo1" platforms :jruby do gem "only_java", "1.2", :git => "#{lib_path("only_java-1.0-java")}" end @@ -118,8 +172,8 @@ RSpec.describe "bundle install with git sources" do s.write "only_java1-0.gemspec", File.read("#{lib_path("only_java-1.0-java")}/only_java.gemspec") end - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo1)}" + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo1" platforms :jruby do gem "only_java", "1.2", :git => "#{lib_path("only_java-1.1-java")}" end @@ -129,34 +183,34 @@ 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") - expect(the_bundle).to include_gems "foo 1.0", :dir => tmp("bundled_app.bck") + expect(the_bundle).to include_gems "foo 1.0", dir: tmp("bundled_app.bck") end it "can still install after moving the application directory" do - 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") + 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") + 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 @@ -164,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 @@ -175,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 @@ -188,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 @@ -205,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 @@ -222,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 @@ -238,21 +292,21 @@ 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", :push => "main" + @remote = build_git("bar", bare: true) + 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 # Create a new tag on the remote that needs fetching - update_git "foo", :tag => "v1.0.0" - update_git "foo", :push => "v1.0.0" + update_git "foo", tag: "v1.0.0" + 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 @@ -261,19 +315,19 @@ RSpec.describe "bundle install with git sources" do it "works when the revision is a non-head ref" do # want to ensure we don't fallback to main - update_git "foo", :path => lib_path("foo-1.0") do |s| + update_git "foo", path: lib_path("foo-1.0") do |s| s.write("lib/foo.rb", "raise 'FAIL'") end - sys_exec("git update-ref -m \"Bundler Spec!\" refs/bundler/1 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| + update_git "foo", path: lib_path("foo-1.0"), branch: "rando" do |s| s.write("lib/foo.rb", "raise 'FAIL_FROM_RANDO'") end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("foo-1.0")}", :ref => "refs/bundler/1" do gem "foo" end @@ -290,26 +344,26 @@ 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 G # want to ensure we don't fallback to main - update_git "foo", :path => lib_path("foo-1.0") do |s| + update_git "foo", path: lib_path("foo-1.0") do |s| s.write("lib/foo.rb", "raise 'FAIL'") end - sys_exec("git update-ref -m \"Bundler Spec!\" refs/bundler/1 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| + update_git "foo", path: lib_path("foo-1.0"), branch: "rando" do |s| s.write("lib/foo.rb", "raise 'FAIL_FROM_RANDO'") end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("foo-1.0")}", :ref => "refs/bundler/1" do gem "foo" end @@ -325,21 +379,21 @@ 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 G # ensure we also git fetch after cloning - bundle :update, :all => true + bundle :update, all: true - sys_exec("git ls-remote .", :dir => Dir[home(".bundle/cache/git/foo-*")].first) + git("ls-remote .", Dir[home(".bundle/cache/git/foo-*")].first) expect(out).not_to include("refs/bundler/1") end @@ -350,10 +404,10 @@ RSpec.describe "bundle install with git sources" do let(:repo) { build_git("foo").path } it "works" do - update_git("foo", :path => repo, :branch => branch) + update_git("foo", path: repo, branch: branch) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{repo}", :branch => #{branch.dump} do gem "foo" end @@ -367,10 +421,10 @@ RSpec.describe "bundle install with git sources" do it "works" do skip "git does not accept this" if Gem.win_platform? - update_git("foo", :path => repo, :branch => branch) + update_git("foo", path: repo, branch: branch) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{repo}", :branch => #{branch.dump} do gem "foo" end @@ -385,10 +439,10 @@ RSpec.describe "bundle install with git sources" do it "works" do skip "git does not accept this" if Gem.win_platform? - update_git("foo", :path => repo, :branch => branch) + update_git("foo", path: repo, branch: branch) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{repo}", :branch => #{branch.dump} do gem "foo" end @@ -404,10 +458,10 @@ RSpec.describe "bundle install with git sources" do let(:repo) { build_git("foo").path } it "works" do - update_git("foo", :path => repo, :tag => tag) + update_git("foo", path: repo, tag: tag) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{repo}", :tag => #{tag.dump} do gem "foo" end @@ -421,10 +475,10 @@ RSpec.describe "bundle install with git sources" do it "works" do skip "git does not accept this" if Gem.win_platform? - update_git("foo", :path => repo, :tag => tag) + update_git("foo", path: repo, tag: tag) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{repo}", :tag => #{tag.dump} do gem "foo" end @@ -439,10 +493,10 @@ RSpec.describe "bundle install with git sources" do it "works" do skip "git does not accept this" if Gem.win_platform? - update_git("foo", :path => repo, :tag => tag) + update_git("foo", path: repo, tag: tag) install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{repo}", :tag => #{tag.dump} do gem "foo" end @@ -455,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) @@ -556,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 :install, :raise_on_error => false - expect(err).to match(/Cannot use local override for rack-0.8 because #{Regexp.escape(lib_path('local-rack').to_s)} does not exist/) + bundle %(config set local.myrack #{lib_path("local-myrack")}) + bundle :install, 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/) - solution = "config unset local.rack" + solution = "config unset local.myrack" expect(err).to match(/Run `bundle #{solution}` to remove the local override/) bundle solution @@ -577,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 :install, :raise_on_error => false - expect(err).to match(/Cannot use local override for rack-0.8 at #{Regexp.escape(lib_path('local-rack').to_s)} because :branch is not specified in Gemfile/) + bundle %(config set local.myrack #{lib_path("local-myrack")}) + bundle :install, raise_on_error: false + 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 @@ -599,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 :install, :raise_on_error => false + 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 :install, :raise_on_error => false + 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!/) @@ -686,76 +740,76 @@ 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" + build_git "foo", path: lib_path("nested") do |s| + 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 describe "block syntax" do it "pulls all gems from a git block" do - build_lib "omg", :path => lib_path("hi2u/omg") - build_lib "hi2u", :path => lib_path("hi2u") + build_lib "omg", path: lib_path("hi2u/omg") + build_lib "hi2u", path: lib_path("hi2u") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" path "#{lib_path("hi2u")}" do gem "omg" gem "hi2u" @@ -772,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 @@ -790,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 @@ -800,7 +854,7 @@ RSpec.describe "bundle install with git sources" do end it "runs the gemspec in the context of its parent directory" do - build_lib "bar", :path => lib_path("foo/bar"), :gemspec => false do |s| + build_lib "bar", path: lib_path("foo/bar"), gemspec: false do |s| s.write lib_path("foo/bar/lib/version.rb"), %(BAR_VERSION = '1.0') s.write "bar.gemspec", <<-G $:.unshift Dir.pwd @@ -815,12 +869,12 @@ RSpec.describe "bundle install with git sources" do G end - build_git "foo", :path => lib_path("foo") do |s| + build_git "foo", path: lib_path("foo") do |s| s.write "bin/foo", "" end 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 @@ -829,15 +883,41 @@ 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| + build_gem "foo", "1.0", path: lib_path("fake_foo"), to_system: true do |s| s.write "lib/foo.rb", "raise 'FAIL'" end 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 @@ -845,10 +925,10 @@ RSpec.describe "bundle install with git sources" do end it "fakes the gem out if there is no gemspec" do - build_git "foo", :gemspec => false + build_git "foo", gemspec: false install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", "1.0", :git => "#{lib_path("foo-1.0")}" gem "rails", "2.3.2" G @@ -859,11 +939,11 @@ 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 - bundle :install, :raise_on_error => false + bundle :install, raise_on_error: false expect(err).to include("Git error:") expect(err).to include("fatal") @@ -871,10 +951,10 @@ RSpec.describe "bundle install with git sources" do end it "works when the gem path has spaces in it" do - build_git "foo", :path => lib_path("foo space-1.0") + build_git "foo", path: lib_path("foo space-1.0") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo space-1.0")}" G @@ -885,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 @@ -896,12 +976,12 @@ RSpec.describe "bundle install with git sources" do s.write "lib/forced.rb", "FORCED = '1.1'" end - bundle "update", :all => true + bundle "update", all: true expect(the_bundle).to include_gems "forced 1.1" - sys_exec("git reset --hard HEAD^", :dir => lib_path("forced-1.0")) + git("reset --hard HEAD^", lib_path("forced-1.0")) - bundle "update", :all => true + bundle "update", all: true expect(the_bundle).to include_gems "forced 1.0" end @@ -913,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)}" + install_gemfile <<-G, raise_on_error: false + 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)}/ 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 @@ -935,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 @@ -955,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 @@ -974,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 @@ -984,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 @@ -998,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) @@ -1016,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 @@ -1030,26 +1110,26 @@ RSpec.describe "bundle install with git sources" do FileUtils.mkdir_p(default_bundle_path) FileUtils.touch(default_bundle_path("bundler")) - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo1)}" + install_gemfile <<-G, raise_on_error: false + 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")}") end it "does not duplicate git gem sources" do - build_lib "foo", :path => lib_path("nested/foo") - build_lib "bar", :path => lib_path("nested/bar") + build_lib "foo", path: lib_path("nested/foo") + build_lib "bar", path: lib_path("nested/bar") - build_git "foo", :path => lib_path("nested") - build_git "bar", :path => lib_path("nested") + build_git "foo", path: lib_path("nested") + build_git "bar", path: lib_path("nested") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("nested")}" gem "bar", :git => "#{lib_path("nested")}" G @@ -1059,21 +1139,21 @@ RSpec.describe "bundle install with git sources" do describe "switching sources" do it "doesn't explode when switching Path to Git sources" do - build_gem "foo", "1.0", :to_system => true do |s| + build_gem "foo", "1.0", to_system: true do |s| s.write "lib/foo.rb", "raise 'fail'" end - build_lib "foo", "1.0", :path => lib_path("bar/foo") - build_git "bar", "1.0", :path => lib_path("bar") do |s| + build_lib "foo", "1.0", path: lib_path("bar/foo") + build_git "bar", "1.0", path: lib_path("bar") do |s| s.add_dependency "foo" end 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 @@ -1082,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 @@ -1107,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")) @@ -1133,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" + install_gemfile <<-G, raise_on_error: false + 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 @@ -1148,9 +1271,9 @@ RSpec.describe "bundle install with git sources" do it "gives a helpful error message when the remote branch no longer exists" do build_git "foo" - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo1)}" - gem "foo", :git => "#{file_uri_for(lib_path("foo-1.0"))}", :branch => "deadbeef" + install_gemfile <<-G, env: { "LANG" => "en" }, raise_on_error: false + 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") @@ -1159,16 +1282,16 @@ RSpec.describe "bundle install with git sources" do describe "bundle install with deployment mode configured and git sources" do it "works" do - build_git "valim", :path => lib_path("valim") + build_git "valim", path: lib_path("valim") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + 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 @@ -1177,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 @@ -1190,14 +1313,14 @@ RSpec.describe "bundle install with git sources" do end bundle :install, - :requires => [lib_path("install_hooks.rb")] + requires: [lib_path("install_hooks.rb")] expect(err_without_deprecations).to eq("Ran pre-install hook: foo-1.0") end 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 @@ -1210,14 +1333,14 @@ RSpec.describe "bundle install with git sources" do end bundle :install, - :requires => [lib_path("install_hooks.rb")] + requires: [lib_path("install_hooks.rb")] expect(err_without_deprecations).to eq("Ran post-install hook: foo-1.0") end 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 @@ -1229,7 +1352,7 @@ RSpec.describe "bundle install with git sources" do H end - bundle :install, :requires => [lib_path("install_hooks.rb")], :raise_on_error => false + bundle :install, requires: [lib_path("install_hooks.rb")], raise_on_error: false expect(err).to include("failed for foo-1.0") end end @@ -1251,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 @@ -1268,11 +1391,10 @@ RSpec.describe "bundle install with git sources" do end it "does not use old extension after ref changes" do - git_reader = build_git "foo", :no_default => true do |s| + git_reader = build_git "foo", no_default: true do |s| s.extensions = ["ext/extconf.rb"] s.write "ext/extconf.rb", <<-RUBY require "mkmf" - $extout = "$(topdir)/" + RbConfig::CONFIG["EXTOUT"] create_makefile("foo") RUBY s.write "ext/foo.c", "void Init_foo() {}" @@ -1282,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 @@ -1315,8 +1437,8 @@ RSpec.describe "bundle install with git sources" do RUBY end - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo1)}" + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo-1.0")}" G @@ -1346,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 @@ -1359,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 @@ -1387,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 @@ -1401,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 @@ -1430,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 @@ -1441,12 +1563,12 @@ In Gemfile: installed_time = out - update_git("foo", :branch => "branch2") + update_git("foo", branch: "branch2") 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 @@ -1479,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 @@ -1493,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 @@ -1516,11 +1638,11 @@ In Gemfile: rake! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L with_path_as("") do - bundle "install", :raise_on_error => false + bundle "install", raise_on_error: false end expect(err). to include("You need to install git to be able to use gems from git repositories. For help installing git, please refer to GitHub's tutorial at https://help.github.com/articles/set-up-git") @@ -1530,40 +1652,40 @@ 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 G with_path_as("") do - bundle "update", :all => true, :raise_on_error => false + bundle "update", all: true, raise_on_error: false end expect(err). to include("You need to install git to be able to use gems from git repositories. For help installing git, please refer to GitHub's tutorial at https://help.github.com/articles/set-up-git") 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" => "" } + bundle "install", env: { "PATH" => "" } expect(out).to_not include("You need to install git to be able to use gems from git repositories.") end end 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 @@ -1574,10 +1696,10 @@ In Gemfile: end it "installs successfully" do - build_git "foo", "1.0", :path => lib_path("foo") + build_git "foo", "1.0", path: lib_path("foo") gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo")}", :branch => "main" G @@ -1592,14 +1714,14 @@ In Gemfile: let(:credentials) { "user1:password1" } it "does not display the password" do - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo1)}" + install_gemfile <<-G, raise_on_error: false + 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 @@ -1608,14 +1730,14 @@ In Gemfile: let(:credentials) { "oauth_token" } it "displays the oauth scheme but not the oauth token" do - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo1)}" + install_gemfile <<-G, raise_on_error: false + 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 734e012e84..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,65 +86,58 @@ 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 } + 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" + expect(the_bundle).not_to include_gems "activesupport 2.3.5", groups: [:default] 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] + expect(the_bundle).to include_gems "activesupport 2.3.2", groups: [:default] end it "still works when BUNDLE_WITHOUT is set" do @@ -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).not_to include_gems "activesupport 2.3.5", :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,16 +285,16 @@ 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 ruby <<-R - require "#{entrypoint}" + 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" - bundle :install, :verbose => true - expect(last_command.stdboth).not_to match(/fetching/i) + FileUtils.rm_r gem_repo2 + bundle_config "without myrack" + bundle :install, verbose: true + 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 3d2d15a698..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,22 +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_enabled do |c| + c.checksum gem_repo1, "activesupport", "2.3.5" + 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} @@ -34,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 313e99d0b8..19bd7074b2 100644 --- a/spec/bundler/install/gemfile/lockfile_spec.rb +++ b/spec/bundler/install/gemfile/lockfile_spec.rb @@ -2,13 +2,14 @@ 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 + it "touches the lockfile on install even when nothing has changed" do install_gemfile(gf) + expect { bundle :install }.to change { bundled_app_lock.mtime } end context "gemfile evaluation" do @@ -16,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 9ef1c879f8..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 @@ -69,8 +54,7 @@ RSpec.describe "bundle install with explicit source paths" do username = "some_unexisting_user" relative_path = lib_path("foo-1.0").relative_path_from(Pathname.new("/home/#{username}").expand_path) - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo1)}" + install_gemfile <<-G, raise_on_error: false gem 'foo', :path => "~#{username}/#{relative_path}" G expect(err).to match("There was an error while trying to use the path `~#{username}/#{relative_path}`.") @@ -78,26 +62,30 @@ RSpec.describe "bundle install with explicit source paths" do end it "expands paths relative to Bundler.root" do - build_lib "foo", :path => bundled_app("foo-1.0") + build_lib "foo", path: bundled_app("foo-1.0") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" gem 'foo', :path => "./foo-1.0" G - expect(the_bundle).to include_gems("foo 1.0", :dir => bundled_app("subdir").mkpath) + expect(the_bundle).to include_gems("foo 1.0", dir: bundled_app("subdir").mkpath) end it "sorts paths consistently on install and update when they start with ./" do - build_lib "demo", :path => lib_path("demo") - build_lib "aaa", :path => lib_path("demo/aaa") + build_lib "demo", path: lib_path("demo") + build_lib "aaa", path: lib_path("demo/aaa") gemfile 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_enabled do |c| + c.no_checksum "aaa", "1.0" + c.no_checksum "demo", "1.0" + end + lockfile = <<~L PATH remote: . @@ -110,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 @@ -119,62 +107,59 @@ RSpec.describe "bundle install with explicit source paths" do DEPENDENCIES aaa! demo! - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L - bundle :install, :dir => lib_path("demo") + bundle :install, dir: lib_path("demo") expect(lib_path("demo/Gemfile.lock")).to read_as(lockfile) - bundle :update, :all => true, :dir => lib_path("demo") + bundle :update, all: true, dir: lib_path("demo") expect(lib_path("demo/Gemfile.lock")).to read_as(lockfile) end it "expands paths when comparing locked paths to Gemfile paths" do - build_lib "foo", :path => bundled_app("foo-1.0") + build_lib "foo", path: bundled_app("foo-1.0") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" gem 'foo', :path => File.expand_path("foo-1.0", __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" + build_lib "foo", path: lib_path("nested") do |s| + 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 it "works" do - build_gem "foo", "1.0.0", :to_system => true do |s| + build_gem "foo", "1.0.0", to_system: true do |s| s.write "lib/foo.rb", "puts 'FAIL'" end - build_lib "omg", "1.0", :path => lib_path("omg") do |s| + build_lib "omg", "1.0", path: lib_path("omg") do |s| s.add_dependency "foo" end - build_lib "foo", "1.0.0", :path => lib_path("omg/foo") + build_lib "foo", "1.0.0", path: lib_path("omg/foo") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" gem "omg", :path => "#{lib_path("omg")}" G @@ -182,10 +167,10 @@ RSpec.describe "bundle install with explicit source paths" do end it "works when using prereleases of 0.0.0" do - build_lib "foo", "0.0.0.dev", :path => lib_path("foo") + 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 @@ -196,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 @@ -215,10 +200,10 @@ RSpec.describe "bundle install with explicit source paths" do end it "works when using uppercase prereleases of 0.0.0" do - build_lib "foo", "0.0.0.SNAPSHOT", :path => lib_path("foo") + 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 @@ -229,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 @@ -248,14 +233,13 @@ RSpec.describe "bundle install with explicit source paths" do end it "handles downgrades" do - build_lib "omg", "2.0", :path => lib_path("omg") + build_lib "omg", "2.0", path: lib_path("omg") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" gem "omg", :path => "#{lib_path("omg")}" G - build_lib "omg", "1.0", :path => lib_path("omg") + build_lib "omg", "1.0", path: lib_path("omg") bundle :install @@ -263,7 +247,7 @@ RSpec.describe "bundle install with explicit source paths" do end it "prefers gemspecs closer to the path root" do - build_lib "premailer", "1.0.0", :path => lib_path("premailer") do |s| + build_lib "premailer", "1.0.0", path: lib_path("premailer") do |s| s.write "gemfiles/ruby187.gemspec", <<-G Gem::Specification.new do |s| s.name = 'premailer' @@ -275,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 @@ -296,30 +279,28 @@ RSpec.describe "bundle install with explicit source paths" do G end - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo1)}" + install_gemfile <<-G, raise_on_error: false 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/) end it "supports gemspec syntax" do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| - s.add_dependency "rack", "1.0" + build_lib "foo", "1.0", path: lib_path("foo") do |s| + 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") + 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 "myrack 1.0", dir: lib_path("foo") end it "does not unlock dependencies of path sources" do @@ -328,19 +309,24 @@ RSpec.describe "bundle install with explicit source paths" do build_gem "graphql", "2.0.16" end - build_lib "foo", "0.1.0", :path => lib_path("foo") do |s| + build_lib "foo", "0.1.0", path: lib_path("foo") do |s| s.add_dependency "graphql", "~> 2.0" end gemfile_path = lib_path("foo/Gemfile") gemfile gemfile_path, <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gemspec G lockfile_path = lib_path("foo/Gemfile.lock") + checksums = checksums_section_when_enabled do |c| + c.no_checksum "foo", "0.1.0" + c.checksum gem_repo4, "graphql", "2.0.15" + end + original_lockfile = <<~L PATH remote: . @@ -349,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) @@ -358,79 +344,78 @@ RSpec.describe "bundle install with explicit source paths" do DEPENDENCIES foo! - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L lockfile lockfile_path, original_lockfile - build_lib "foo", "0.1.1", :path => lib_path("foo") do |s| + build_lib "foo", "0.1.1", path: lib_path("foo") do |s| s.add_dependency "graphql", "~> 2.0" end - bundle "install", :dir => lib_path("foo") + bundle "install", dir: lib_path("foo") expect(lockfile_path).to read_as(original_lockfile.gsub("foo (0.1.0)", "foo (0.1.1)")) end it "supports gemspec syntax with an alternative path" do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| - s.add_dependency "rack", "1.0" + build_lib "foo", "1.0", path: lib_path("foo") do |s| + 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" + build_lib "foo", "1.0", path: lib_path("foo") do |s| + s.add_dependency "myrack", ">= 1.0" end - install_gemfile lib_path("foo/Gemfile"), <<-G, :dir => lib_path("foo") - source "#{file_uri_for(gem_repo1)}" + install_gemfile lib_path("foo/Gemfile"), <<-G, dir: lib_path("foo") + 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") + bundle "install", dir: lib_path("foo") - expect(the_bundle).to include_gems "foo 1.0", :dir => lib_path("foo") - expect(the_bundle).to include_gems "rack 1.0", :dir => lib_path("foo") + expect(the_bundle).to include_gems "foo 1.0", dir: lib_path("foo") + expect(the_bundle).to include_gems "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" + build_lib "foo", "1.0", path: lib_path("foo") do |s| + 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)}" + install_gemfile lib_path("foo/Gemfile"), <<-G, dir: lib_path("foo") + 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") + bundle "install", dir: lib_path("foo") - expect(the_bundle).to include_gems "foo 1.0", :dir => lib_path("foo") - expect(the_bundle).to include_gems "rack 1.0", :dir => lib_path("foo") + expect(the_bundle).to include_gems "foo 1.0", dir: lib_path("foo") + expect(the_bundle).to include_gems "myrack 1.0", dir: lib_path("foo") end it "raises if there are multiple gemspecs" do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.write "bar.gemspec", build_spec("bar", "1.0").first.to_ruby end - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo1)}" + install_gemfile <<-G, raise_on_error: false gemspec :path => "#{lib_path("foo")}" G @@ -439,12 +424,11 @@ RSpec.describe "bundle install with explicit source paths" do end it "allows :name to be specified to resolve ambiguity" do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.write "bar.gemspec" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" gemspec :path => "#{lib_path("foo")}", :name => "foo" G @@ -456,8 +440,7 @@ RSpec.describe "bundle install with explicit source paths" do s.executables = "foobar" end - install_gemfile <<-G, :verbose => true - source "#{file_uri_for(gem_repo1)}" + install_gemfile <<-G, verbose: true path "#{lib_path("foo-1.0")}" do gem 'foo' end @@ -471,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 @@ -485,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 @@ -498,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" @@ -510,14 +490,13 @@ RSpec.describe "bundle install with explicit source paths" do end it "keeps source pinning" do - build_lib "foo", "1.0", :path => lib_path("foo") - build_lib "omg", "1.0", :path => lib_path("omg") - build_lib "foo", "1.0", :path => lib_path("omg/foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") + build_lib "omg", "1.0", path: lib_path("omg") + build_lib "foo", "1.0", path: lib_path("omg/foo") do |s| s.write "lib/foo.rb", "puts 'FAIL'" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("foo")}" gem "omg", :path => "#{lib_path("omg")}" G @@ -526,10 +505,9 @@ RSpec.describe "bundle install with explicit source paths" do end it "works when the path does not have a gemspec" do - build_lib "foo", :gemspec => false + build_lib "foo", gemspec: false gemfile <<-G - source "#{file_uri_for(gem_repo1)}" gem "foo", "1.0", :path => "#{lib_path("foo-1.0")}" G @@ -543,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 @@ -559,34 +533,34 @@ 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" } + 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 - build_lib "net-ssh", "1.0", :path => lib_path("omg") do |s| + build_lib "net-ssh", "1.0", path: lib_path("omg") do |s| s.add_dependency "yard" end 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" } + 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 @@ -596,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 @@ -606,19 +579,18 @@ RSpec.describe "bundle install with explicit source paths" do describe "when the gem version in the path is updated" do before :each do - build_lib "foo", "1.0", :path => lib_path("foo") do |s| + build_lib "foo", "1.0", path: lib_path("foo") do |s| s.add_dependency "bar" end - build_lib "bar", "1.0", :path => lib_path("foo/bar") + build_lib "bar", "1.0", path: lib_path("foo/bar") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" gem "foo", :path => "#{lib_path("foo")}" G end it "unlocks all gems when the top level gem is updated" do - build_lib "foo", "2.0", :path => lib_path("foo") do |s| + build_lib "foo", "2.0", path: lib_path("foo") do |s| s.add_dependency "bar" end @@ -628,7 +600,7 @@ RSpec.describe "bundle install with explicit source paths" do end it "unlocks all gems when a child dependency gem is updated" do - build_lib "bar", "2.0", :path => lib_path("foo/bar") + build_lib "bar", "2.0", path: lib_path("foo/bar") bundle "install" @@ -638,57 +610,62 @@ RSpec.describe "bundle install with explicit source paths" do describe "when dependencies in the path are updated" do before :each do - build_lib "foo", "1.0", :path => lib_path("foo") + build_lib "foo", "1.0", path: lib_path("foo") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + 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" + build_lib "foo", "1.0", path: lib_path("foo") do |s| + 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" + build_lib "foo", "1.0", path: lib_path("foo") do |s| + 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_enabled do |c| + c.no_checksum "foo", "1.0" + c.checksum gem_repo1, "myrack", "0.9.1" + end 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} DEPENDENCIES foo! - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G - build_lib "foo", "1.0", :path => lib_path("foo") do |s| - s.add_dependency "rack" + build_lib "foo", "1.0", path: lib_path("foo") do |s| + s.add_dependency "myrack" end bundle "install" @@ -698,109 +675,215 @@ 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} DEPENDENCIES 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" + build_lib "foo", "1.0", path: lib_path("foo") do |s| + 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_enabled do |c| + c.no_checksum "foo", "1.0" + c.checksum gem_repo1, "myrack", "0.9.1" + end 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} DEPENDENCIES 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 "rake", "13.0.1" + build_lib "foo", "1.0", path: lib_path("foo") do |s| + s.add_dependency "myrack" + s.add_dependency "rake", rake_version end bundle "install" + checksums.checksum gem_repo1, "rake", rake_version + expect(lockfile).to eq <<~G PATH remote: #{lib_path("foo")} specs: foo (1.0) - rack - rake (= 13.0.1) + myrack + rake (= #{rake_version}) GEM - remote: #{file_uri_for(gem_repo1)}/ + remote: https://gem.repo1/ specs: - rack (0.9.1) - rake (13.0.1) + myrack (0.9.1) + rake (#{rake_version}) PLATFORMS #{lockfile_platforms} DEPENDENCIES foo! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + + 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 "myrack", "0.9.1" + end + + checksums = checksums_section_when_enabled do |c| + c.no_checksum "foo", "1.0" + end + + lockfile <<~L + PATH + remote: #{lib_path("foo")} + specs: + foo (1.0) + + PLATFORMS + #{lockfile_platforms("ruby")} + + DEPENDENCIES + foo! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "lock" + + checksums.checksum gem_repo1, "myrack", "0.9.1" + + expect(lockfile).to eq <<~G + PATH + remote: #{lib_path("foo")} + specs: + foo (1.0) + myrack (= 0.9.1) + GEM + remote: https://gem.repo1/ + specs: + myrack (0.9.1) + + PLATFORMS + #{lockfile_platforms("ruby")} + + DEPENDENCIES + 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 - expect(the_bundle).to include_gems "rack 0.9.1" + 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| + build_gem "foo", "1.0", to_system: true do |s| s.write "lib/foo.rb", "raise 'fail'" end - build_lib "foo", "1.0", :path => lib_path("bar/foo") - build_git "bar", "1.0", :path => lib_path("bar") do |s| + build_lib "foo", "1.0", path: lib_path("bar/foo") + build_git "bar", "1.0", path: lib_path("bar") do |s| s.add_dependency "foo" end 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 @@ -808,23 +891,23 @@ RSpec.describe "bundle install with explicit source paths" do end it "switches the source when the gem existed in rubygems and the path was already being used for another gem" do - build_lib "foo", "1.0", :path => lib_path("foo") - build_gem "bar", "1.0", :to_bundle => true do |s| + build_lib "foo", "1.0", path: lib_path("foo") + build_gem "bar", "1.0", to_bundle: true do |s| s.write "lib/bar.rb", "raise 'fail'" end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "bar" path "#{lib_path("foo")}" do gem "foo" end G - build_lib "bar", "1.0", :path => lib_path("foo/bar") + build_lib "bar", "1.0", path: lib_path("foo/bar") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" path "#{lib_path("foo")}" do gem "foo" gem "bar" @@ -837,17 +920,17 @@ RSpec.describe "bundle install with explicit source paths" do describe "when there are both a gemspec and remote gems" do it "doesn't query rubygems for local gemspec name" do - build_lib "private_lib", "2.2", :path => lib_path("private_lib") + build_lib "private_lib", "2.2", path: lib_path("private_lib") gemfile lib_path("private_lib/Gemfile"), <<-G source "http://localgemserver.test" gemspec - gem 'rack' + 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$}) + 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=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 "private_lib 2.2", dir: lib_path("private_lib") + expect(the_bundle).to include_gems "myrack 1.0", dir: lib_path("private_lib") end end @@ -855,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 @@ -868,14 +950,13 @@ RSpec.describe "bundle install with explicit source paths" do end bundle :install, - :requires => [lib_path("install_hooks.rb")] + requires: [lib_path("install_hooks.rb")] expect(err_without_deprecations).to eq("Ran pre-install hook: foo-1.0") end 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 @@ -888,14 +969,13 @@ RSpec.describe "bundle install with explicit source paths" do end bundle :install, - :requires => [lib_path("install_hooks.rb")] + requires: [lib_path("install_hooks.rb")] expect(err_without_deprecations).to eq("Ran post-install hook: foo-1.0") end 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 @@ -907,7 +987,7 @@ RSpec.describe "bundle install with explicit source paths" do H end - bundle :install, :requires => [lib_path("install_hooks.rb")], :raise_on_error => false + bundle :install, requires: [lib_path("install_hooks.rb")], raise_on_error: false expect(err).to include("failed for foo-1.0") end @@ -926,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 d28c7b781f..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,193 +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 - - 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 + simulate_platform "java" do + install_gemfile <<-G + source "https://gem.repo4" - PLATFORMS - java - - DEPENDENCIES - empyrean (= 0.1.0) - pry - - BUNDLED WITH - #{Bundler::VERSION} - L - - bundle "lock --add-platform ruby" - - 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 + gem "empyrean", "0.1.0" + gem "pry" + G - PLATFORMS - java - 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 - DEPENDENCIES - empyrean (= 0.1.0) - pry + 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 - BUNDLED WITH - #{Bundler::VERSION} - L + PLATFORMS + java - expect(lockfile).to eq good_lockfile + DEPENDENCIES + empyrean (= 0.1.0) + pry + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L - 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 + bundle "lock --add-platform ruby" - PLATFORMS - java - ruby + checksums.checksum gem_repo4, "pry", "0.11.3" - DEPENDENCIES - empyrean (= 0.1.0) - pry + 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 - 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_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) @@ -374,20 +391,23 @@ RSpec.describe "bundle install across platforms" do DEPENDENCIES platform_specific + #{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 - expect(the_bundle).to include_gem "platform_specific 1.0 RUBY" + checksums.checksum gem_repo1, "platform_specific", "1.0" + + 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) @@ -398,9 +418,9 @@ RSpec.describe "bundle install across platforms" do DEPENDENCIES platform_specific - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end end @@ -408,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" @@ -420,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 @@ -441,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" @@ -452,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) @@ -466,7 +486,7 @@ RSpec.describe "bundle install with platform conditionals" do tzinfo (~> 1.2) BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install --verbose" @@ -476,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" @@ -484,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" @@ -502,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" @@ -512,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 @@ -522,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 @@ -534,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 @@ -545,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" @@ -559,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 - + 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| @@ -585,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 @@ -596,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 39f09031b7..d937abd714 100644 --- a/spec/bundler/install/gemfile/ruby_spec.rb +++ b/spec/bundler/install/gemfile/ruby_spec.rb @@ -10,114 +10,148 @@ 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)}" + install_gemfile <<-G, raise_on_error: false + 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 end + + it "allows picking up ruby version from a file" do + create_file ".ruby-version", Gem.ruby_version.to_s + + install_gemfile <<-G + source "https://gem.repo1" + ruby file: ".ruby-version" + gem "myrack" + G + + expect(lockfile).to include("RUBY VERSION") + end + + it "reads the ruby version file from the right folder when nested Gemfiles are involved" do + create_file ".ruby-version", Gem.ruby_version.to_s + + gemfile <<-G + source "https://gem.repo1" + ruby file: ".ruby-version" + gem "myrack" + G + + nested_dir = bundled_app(".ruby-lsp") + + FileUtils.mkdir nested_dir + + gemfile ".ruby-lsp/Gemfile", <<-G + eval_gemfile(File.expand_path("../Gemfile", __dir__)) + G + + bundle "install", dir: nested_dir + + expect(bundled_app(".ruby-lsp/Gemfile.lock").read).to include("RUBY VERSION") + end end diff --git a/spec/bundler/install/gemfile/sources_spec.rb b/spec/bundler/install/gemfile/sources_spec.rb index 562fa6c5b4..654d638e1f 100644 --- a/spec/bundler/install/gemfile/sources_spec.rb +++ b/spec/bundler/install/gemfile/sources_spec.rb @@ -2,85 +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 "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 "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 "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 + # 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 @@ -88,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" + 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" + 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 :install, :artifice => "compact_index" + 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" + 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 @@ -168,9 +103,9 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs from the same source without any warning" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).not_to include("Warning") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3") + expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 1.0.0", source: "remote3") end end @@ -178,148 +113,57 @@ 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 it "installs from the same source without any warning" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" - expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3") + expect(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" + 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 context "and not in any other sources" do before do - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo2" source "https://gem.repo3" do - gem "depends_on_rack" + 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 "installs from the other source and warns about ambiguous gems", :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.repo2") - - 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 - #{local_platform} - - DEPENDENCIES - depends_on_rack! - - 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 @@ -328,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 @@ -345,60 +189,27 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "fails" do - bundle :install, :artifice => "compact_index", :raise_on_error => false + bundle :install, artifice: "compact_index", raise_on_error: false expect(err).to include("Could not find gem 'private_gem_1' in rubygems repository https://gem.repo2/ or installed locally.") 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 - 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" @@ -409,37 +220,37 @@ 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" + bundle :install, artifice: "compact_index" expect(err).not_to include("Warning") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote2") - expect(the_bundle).to include_gems("unrelated_gem 1.0.0", :source => "remote3") + expect(the_bundle).to include_gems("depends_on_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 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 it "does not find the dependency" do - bundle :install, :artifice => "compact_index", :raise_on_error => false + bundle :install, artifice: "compact_index", raise_on_error: false expect(err).to end_with <<~E.strip Could not find compatible versions - Because every version of depends_on_rack depends on rack >= 0 - and rack >= 0 could not be found in rubygems repository https://gem.repo2/ 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 @@ -448,36 +259,36 @@ 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 it "installs the dependency from the top-level source without warning" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).not_to include("Warning") - expect(run("require 'rack'; puts RACK")).to eq("1.0.0") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0") - expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote2") - expect(the_bundle).to include_gems("unrelated_gem 1.0.0", :source => "remote3") + expect(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 end 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 @@ -485,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 @@ -493,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") + bundle :install, artifice: "compact_index" + 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 @@ -510,348 +321,40 @@ 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") + bundle :install, artifice: "compact_index" + 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") + bundle :install, artifice: "compact_index" + 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 - - 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 - #{local_platform} - - DEPENDENCIES - activesupport - sidekiq-pro! - - 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 - #{local_platform} - - DEPENDENCIES - activesupport - sidekiq-pro! - - 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") - expect(the_bundle).not_to include_gems("tzinfo 1.2.9") - expect(the_bundle).to include_gems("tzinfo 2.0.4") - expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.8") - expect(the_bundle).to include_gems("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 - #{local_platform} - - DEPENDENCIES - activesupport - sidekiq-pro! - - 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") - - 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 - #{local_platform} - - DEPENDENCIES - activesupport - sidekiq-pro! - - 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") - build_lib "rails", "7.0.0.alpha", :path => lib_path("rails") do |s| + build_lib "activesupport", "7.0.0.alpha", path: lib_path("rails/activesupport") + build_lib "rails", "7.0.0.alpha", path: lib_path("rails") do |s| s.add_dependency "activesupport", "= 7.0.0.alpha" end @@ -873,17 +376,17 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs all gems without warning" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(err).not_to include("Warning") expect(the_bundle).to include_gems("activesupport 7.0.0.alpha", "rails 7.0.0.alpha") - expect(the_bundle).to include_gems("activesupport 7.0.0.alpha", :source => "path@#{lib_path("rails/activesupport")}") - expect(the_bundle).to include_gems("rails 7.0.0.alpha", :source => "path@#{lib_path("rails")}") + expect(the_bundle).to include_gems("activesupport 7.0.0.alpha", source: "path@#{lib_path("rails/activesupport")}") + expect(the_bundle).to include_gems("rails 7.0.0.alpha", source: "path@#{lib_path("rails")}") end end 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 @@ -909,6 +412,12 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "installs from the default source without any warnings or errors and generates a proper lockfile" do + checksums = checksums_section_when_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" + end + expected_lockfile = <<~L GEM remote: https://gem.repo2/ @@ -924,41 +433,41 @@ RSpec.describe "bundle install with gems on multiple sources" do nokogiri (>= 1.2.3) PLATFORMS - #{local_platform} + #{lockfile_platforms} DEPENDENCIES handsoap! nokogiri - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L - bundle "install --verbose", :artifice => "compact_index" + bundle "install --verbose", artifice: "compact_index" expect(err).not_to include("Warning") expect(the_bundle).to include_gems("handsoap 0.2.5.5", "nokogiri 1.11.1", "racca 1.5.2") - expect(the_bundle).to include_gems("handsoap 0.2.5.5", :source => "remote3") - expect(the_bundle).to include_gems("nokogiri 1.11.1", "racca 1.5.2", :source => "remote2") + expect(the_bundle).to include_gems("handsoap 0.2.5.5", source: "remote3") + expect(the_bundle).to include_gems("nokogiri 1.11.1", "racca 1.5.2", source: "remote2") expect(lockfile).to eq(expected_lockfile) # Even if the gems are already installed FileUtils.rm bundled_app_lock - bundle "install --verbose", :artifice => "compact_index" + bundle "install --verbose", artifice: "compact_index" expect(err).not_to include("Warning") expect(the_bundle).to include_gems("handsoap 0.2.5.5", "nokogiri 1.11.1", "racca 1.5.2") - expect(the_bundle).to include_gems("handsoap 0.2.5.5", :source => "remote3") - expect(the_bundle).to include_gems("nokogiri 1.11.1", "racca 1.5.2", :source => "remote2") + expect(the_bundle).to include_gems("handsoap 0.2.5.5", source: "remote3") + expect(the_bundle).to include_gems("nokogiri 1.11.1", "racca 1.5.2", source: "remote2") expect(lockfile).to eq(expected_lockfile) end end 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 - install_gemfile <<-G, :artifice => "compact_index", :raise_on_error => false + install_gemfile <<-G, artifice: "compact_index", raise_on_error: false source "https://gem.repo3" gem "not_in_repo1", :source => "https://gem.repo1" G @@ -971,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 @@ -981,104 +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 - #{local_platform} + #{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 - #{local_platform} - - DEPENDENCIES - rack! - - 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 - #{local_platform} - - 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", :bundler => "< 3" do - bundle "config set --local deployment 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 "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 @@ -1087,14 +518,14 @@ 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 it "does not unlock the non-path gem after install" do - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" bundle %(exec ruby -e 'puts "OK"') @@ -1105,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" + 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 @@ -1130,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 :install, :artifice => "compact_index" + 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 @@ -1147,9 +578,9 @@ RSpec.describe "bundle install with gems on multiple sources" do it "allows them to be unlocked separately" do # And install this gemfile, updating only foo. - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source 'https://gem.repo1' - gem 'rack' + gem 'myrack' gem 'foo', '~> 0.2', :source => 'https://gem.repo4' gem 'bar', '~> 0.1', :source => 'https://gem.repo4' G @@ -1162,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" @@ -1171,12 +602,12 @@ RSpec.describe "bundle install with gems on multiple sources" do build_git "git1" build_git "git2" - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo1" gem "rails" source "https://gem.repo3" do - gem "rack" + gem "myrack" end gem "path1", :path => "#{lib_path("path1-1.0")}" @@ -1187,7 +618,7 @@ RSpec.describe "bundle install with gems on multiple sources" do end it "does not re-resolve" do - bundle :install, :artifice => "compact_index", :verbose => true + bundle :install, artifice: "compact_index", verbose: true expect(out).to include("using resolution from the lockfile") expect(out).not_to include("re-resolving dependencies") end @@ -1196,28 +627,28 @@ RSpec.describe "bundle install with gems on multiple sources" do context "when a gem is installed to system gems" do before do - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo1" - gem "rack" + 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 # When this gemfile is installed... - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo1" source "https://gem.repo4" do - 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 @@ -1225,47 +656,47 @@ 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 - bundle :install, :artifice => "compact_index" + # But we should still be able to find myrack 2.0.1.1.forked and install it + bundle :install, artifice: "compact_index" end end end describe "source changed to one containing a higher version of a dependency" do before do - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo1" - gem "rack" + 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" + 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 @@ -1275,18 +706,18 @@ 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 - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo1" source "https://gem.repo4" do gem "bar" end - gemspec :path => "#{tmp.join("gemspec_test")}" + gemspec :path => "#{tmp("gemspec_test")}" G end @@ -1304,7 +735,7 @@ RSpec.describe "bundle install with gems on multiple sources" do build_gem "example", "1.0.2" end - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" source "https://gem.repo4" gem "example", :source => "https://gem.repo2" @@ -1313,9 +744,9 @@ RSpec.describe "bundle install with gems on multiple sources" do bundle "info example" expect(out).to include("example (0.1.0)") - system_gems "example-1.0.2", :path => default_bundle_path, :gem_repo => gem_repo4 + system_gems "example-1.0.2", path: default_bundle_path, gem_repo: gem_repo4 - bundle "update example --verbose", :artifice => "compact_index" + bundle "update example --verbose", artifice: "compact_index" expect(out).not_to include("Using example 1.0.2") expect(out).to include("Using example 0.1.0") end @@ -1329,11 +760,9 @@ RSpec.describe "bundle install with gems on multiple sources" do end G - simulate_bundler_version_when_missing_prerelease_default_gem_activation do - ruby <<~R, :raise_on_error => false - require 'bundler/setup' - R - end + ruby <<~R, raise_on_error: false + require 'bundler/setup' + R expect(last_command).to be_failure expect(err).to include("Could not find gem 'example' in locally installed gems.") @@ -1348,142 +777,43 @@ RSpec.describe "bundle install with gems on multiple sources" do end G - bundle "install", :artifice => nil, :raise_on_error => false + bundle "install", artifice: nil, raise_on_error: false expect(last_command).to be_failure expect(err).to include("Could not reach host gem.repo4. Check your network connection and try again.") end - context "when an indirect dependency is available from multiple ambiguous sources", :bundler => "< 3" do - 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)}" - - source "https://gem.repo4" do - gem "depends_on_rack" - end - - source "https://gem.repo1" do - gem "thin" - end - G - expect(err).to eq strip_whitespace(<<-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 + install_gemfile <<-G, artifice: "compact_index_extra_api", raise_on_error: false + source "https://global.source" - 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" + source "https://scoped.source/extra" do + gem "depends_on_myrack" 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 strip_whitespace(<<-EOS).strip - The gem 'rack' was found in multiple relevant sources. - * rubygems repository https://gem.repo1/ - * rubygems repository https://gem.repo4/ + expect(err).to eq <<~EOS.strip + 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 - #{local_platform} - - DEPENDENCIES - capybara (~> 2.5.0) - mime-types (~> 3.0)! - L - end - - it "upgrades the lockfile correctly" do - bundle "lock --update", :artifice => "compact_index" - - 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 - #{local_platform} - - DEPENDENCIES - capybara (~> 2.5.0) - mime-types (~> 3.0)! - - 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 @@ -1504,35 +834,40 @@ 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_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) PLATFORMS - #{local_platform} + #{lockfile_platforms} DEPENDENCIES ruport (= 1.7.0.3)! - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -1557,35 +892,40 @@ 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_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) PLATFORMS - #{local_platform} + #{lockfile_platforms} DEPENDENCIES ruport (= 1.7.0.3)! - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -1604,29 +944,33 @@ 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_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) PLATFORMS - #{local_platform} + #{lockfile_platforms} DEPENDENCIES pdf-writer (= 1.1.8) - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end end @@ -1643,7 +987,7 @@ RSpec.describe "bundle install with gems on multiple sources" do build_gem "example", "1.0.0" end - install_gemfile <<~G, :artifice => "compact_index" + install_gemfile <<~G, artifice: "compact_index" source "https://gem.repo2" source "https://gem.repo4" do @@ -1663,10 +1007,307 @@ RSpec.describe "bundle install with gems on multiple sources" do it "shows a proper error message and does not generate a corrupted lockfile" do expect do - bundle :install, :artifice => "compact_index", :raise_on_error => false, :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + bundle :install, artifice: "compact_index", raise_on_error: false, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } end.not_to change { lockfile } expect(err).to include("Could not find gem 'example' in rubygems repository https://gem.repo4/") end end + + 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 ce148af43d..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,11 +11,57 @@ 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 eq([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 eq(%w[ - google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin - ]) + expect(the_bundle.locked_gems.specs.map(&:full_name)).to include( + "google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin" + ) + end + end + + it "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 @@ -23,17 +69,15 @@ RSpec.describe "bundle install with specific platforms" 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,32 +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_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) @@ -78,12 +124,15 @@ RSpec.describe "bundle install with specific platforms" do DEPENDENCIES 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" # make sure the platform that the platform specific dependency is used, since we're only locked to ruby expect(the_bundle).to include_gem("google-protobuf 3.0.0.alpha.5.0.5.1 universal-darwin") @@ -91,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) @@ -100,14 +149,19 @@ RSpec.describe "bundle install with specific platforms" do DEPENDENCIES 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" @@ -115,19 +169,17 @@ RSpec.describe "bundle install with specific platforms" do s.platform = "arm64-darwin" s.required_ruby_version = "< #{Gem.ruby_version}" end - - build_gem "bundler", "2.1.4" 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) @@ -137,22 +189,77 @@ RSpec.describe "bundle install with specific platforms" do DEPENDENCIES nokogiri - RUBY VERSION - 2.5.3p105 - BUNDLED WITH - 2.1.4 + #{Bundler::VERSION} L simulate_platform "arm64-darwin-22", &example end - it "still installs the generic RUBY variant if necessary" do - bundle "update --bundler", :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + 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", 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) - it "still installs the generic RUBY variant if necessary, even in frozen mode" do - bundle "update --bundler", :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s, "BUNDLE_FROZEN" => "true" } + 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 @@ -163,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) @@ -191,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 @@ -210,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) @@ -228,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 @@ -252,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 @@ -265,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 @@ -281,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 @@ -301,15 +406,15 @@ 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 eq([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 eq(["CFPropertyList-1.0", - "facter-2.4.6-universal-darwin"]) + expect(the_bundle.locked_gems.specs.map(&:full_name)).to include("CFPropertyList-1.0", + "facter-2.4.6-universal-darwin") end end @@ -322,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 eq([x64_mingw32, pl("x86_64-darwin-15")]) - expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[ + 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 @@ -336,18 +441,18 @@ 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 eq([java, pl("x86_64-darwin-15")]) - expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[ - google-protobuf-3.0.0.alpha.5.0.5.1 - google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin - ]) + expect(the_bundle.locked_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" + ) end 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 @@ -355,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}) @@ -373,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" @@ -386,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)}/ 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 @@ -400,7 +521,7 @@ RSpec.describe "bundle install with specific platforms" do ERROR simulate_platform "arm64-darwin-21" do - bundle "lock", :raise_on_error => false + bundle "lock", raise_on_error: false end expect(err).to include(error_message).once @@ -408,12 +529,47 @@ RSpec.describe "bundle install with specific platforms" do # Make sure it doesn't print error twice in verbose mode simulate_platform "arm64-darwin-21" do - bundle "lock --verbose", :raise_on_error => false + bundle "lock --verbose", raise_on_error: false end 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" } @@ -422,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 @@ -431,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)}/ 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. @@ -442,7 +598,7 @@ RSpec.describe "bundle install with specific platforms" do ERROR simulate_platform "arm64-darwin-21" do - bundle "lock", :raise_on_error => false + bundle "lock", raise_on_error: false end expect(err).to include(error_message).once @@ -450,42 +606,42 @@ RSpec.describe "bundle install with specific platforms" do # Make sure it doesn't print error twice in verbose mode simulate_platform "arm64-darwin-21" do - bundle "lock --verbose", :raise_on_error => false + bundle "lock --verbose", raise_on_error: false end expect(err).to include(error_message).once end - it "does not generate a lockfile if RUBY platform is forced and some gem has no RUBY variant available" do + 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 - bundle "lock", :raise_on_error => false, :env => { "BUNDLE_FORCE_RUBY_PLATFORM" => "true" } + 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)}/ 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") @@ -496,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) @@ -514,20 +670,27 @@ RSpec.describe "bundle install with specific platforms" do sorbet-runtime (= 0.5.10160) PLATFORMS - #{lockfile_platforms("ruby")} + #{lockfile_platforms} DEPENDENCIES sorbet-static-and-runtime BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "update" + 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 + c.checksum gem_repo4, "sorbet-static-and-runtime", "0.5.10160" + end + 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) @@ -538,17 +701,17 @@ RSpec.describe "bundle install with specific platforms" do sorbet-runtime (= 0.5.10160) PLATFORMS - #{lockfile_platforms} + #{local_platform} DEPENDENCIES 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| @@ -567,16 +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_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) @@ -588,10 +756,10 @@ RSpec.describe "bundle install with specific platforms" do DEPENDENCIES nokogiri - sorbet - + sorbet-static + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L simulate_platform "x86_64-darwin-22" do @@ -600,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) @@ -611,21 +779,21 @@ RSpec.describe "bundle install with specific platforms" do DEPENDENCIES nokogiri 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") @@ -636,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) @@ -660,14 +828,21 @@ 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_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 + c.checksum gem_repo4, "sorbet-static-and-runtime", "0.5.10160" + end + 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) @@ -678,16 +853,160 @@ RSpec.describe "bundle install with specific platforms" do sorbet-runtime (= 0.5.10160) PLATFORMS - #{lockfile_platforms} + #{local_platform} DEPENDENCIES 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 + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "x86_64-linux" + end + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "arm-linux" + end + + build_gem "sorbet-static", "0.5.10696" do |s| + s.platform = "x86_64-linux" + end + end + + gemfile <<~G + source "https://gem.repo4" + + gem "nokogiri" + gem "sorbet-static" + G + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + nokogiri (1.14.0-arm-linux) + nokogiri (1.14.0-x86_64-linux) + sorbet-static (0.5.10696-x86_64-linux) + + PLATFORMS + aarch64-linux + arm-linux + x86_64-linux + + DEPENDENCIES + nokogiri + sorbet-static + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "update" + + checksums = checksums_section_when_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: https://gem.repo4/ + specs: + nokogiri (1.14.0-x86_64-linux) + sorbet-static (0.5.10696-x86_64-linux) + + PLATFORMS + x86_64-linux + + DEPENDENCIES + nokogiri + sorbet-static + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + it "automatically fixes the lockfile without removing other variants if it's missing platform gems, but they are installed locally" do simulate_platform "x86_64-darwin-21" do build_repo4 do @@ -702,15 +1021,20 @@ 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_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 + # 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) @@ -719,16 +1043,16 @@ RSpec.describe "bundle install with specific platforms" do DEPENDENCIES sorbet-static (= 0.5.10549) - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install" 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) @@ -738,9 +1062,66 @@ RSpec.describe "bundle install with specific platforms" do DEPENDENCIES sorbet-static (= 0.5.10549) + #{checksums} + BUNDLED WITH + #{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} + #{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 @@ -754,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}) @@ -774,9 +1160,9 @@ RSpec.describe "bundle install with specific platforms" do DEPENDENCIES nokogiri tzinfo (~> 1.2) - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L lockfile original_lockfile @@ -786,13 +1172,112 @@ RSpec.describe "bundle install with specific platforms" do 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 + + 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: https://gem.repo4/ + specs: + nokogiri (1.13.8) + + PLATFORMS + ruby + + DEPENDENCIES + nokogiri + tzinfo (~> 1.2) + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + 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 "myrack", "3.0.7" + end + + gemfile <<~G + source "https://gem.repo4" + + gem "concurrent-ruby" + gem "myrack" + G + + 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: https://gem.repo4/ + specs: + concurrent-ruby (1.2.2) + + PLATFORMS + ruby + + DEPENDENCIES + concurrent-ruby + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "lock" + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + concurrent-ruby (1.2.2) + myrack (3.0.7) + + PLATFORMS + #{lockfile_platforms(generic_default_locked_platform || local_platform, defaults: ["ruby"])} + + DEPENDENCIES + concurrent-ruby + myrack + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + it "can fallback to a source gem when platform gems are incompatible with current ruby version" do setup_multiplatform_gem_with_source_gem - source = file_uri_for(gem_repo2) - gemfile <<~G - source "#{source}" + source "https://gem.repo2" gem "my-precompiled-gem" G @@ -802,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}) @@ -815,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 @@ -838,14 +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_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) @@ -854,42 +1343,602 @@ RSpec.describe "bundle install with specific platforms" do DEPENDENCIES nokogiri (= 1.14.0) - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle :install + 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 DEPENDENCIES nokogiri (= 1.14.0) + #{checksums} + BUNDLED WITH + #{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} + #{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 + it "locks specific platforms automatically" do + simulate_platform "x86_64-linux" do + build_repo4 do + build_gem "nokogiri", "1.14.0" + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "x86_64-linux" + end + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "arm-linux" + end + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "x64-mingw-ucrt" + end + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "java" + end + + build_gem "sorbet-static", "0.5.10696" do |s| + s.platform = "x86_64-linux" + end + build_gem "sorbet-static", "0.5.10696" do |s| + s.platform = "universal-darwin-22" + end + end + + gemfile <<~G + source "https://gem.repo4" + + gem "nokogiri" + G + + bundle "lock" + + 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: https://gem.repo4/ + specs: + nokogiri (1.14.0) + nokogiri (1.14.0-arm-linux) + nokogiri (1.14.0-x86_64-linux) + + PLATFORMS + arm-linux + ruby + x86_64-linux + + DEPENDENCIES + nokogiri + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + + gemfile <<~G + source "https://gem.repo4" + + gem "nokogiri" + gem "sorbet-static" + G + + FileUtils.rm bundled_app_lock + + bundle "lock" + + checksums.delete "nokogiri", "arm-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: https://gem.repo4/ + specs: + nokogiri (1.14.0) + nokogiri (1.14.0-x86_64-linux) + sorbet-static (0.5.10696-universal-darwin-22) + sorbet-static (0.5.10696-x86_64-linux) + + PLATFORMS + universal-darwin-22 + x86_64-linux + + DEPENDENCIES + nokogiri + sorbet-static + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + it "does not fail when a platform variant is incompatible with the current ruby and another equivalent platform specific variant is part of the resolution" do + build_repo4 do + build_gem "nokogiri", "1.15.5" + + build_gem "nokogiri", "1.15.5" do |s| + s.platform = "x86_64-linux" + s.required_ruby_version = "< #{current_ruby_minor}.dev" + end + + build_gem "sass-embedded", "1.69.5" + + build_gem "sass-embedded", "1.69.5" do |s| + s.platform = "x86_64-linux-gnu" + end + end + + gemfile <<~G + source "https://gem.repo4" + + gem "nokogiri" + gem "sass-embedded" + G + + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "nokogiri", "1.15.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" + + # locks all compatible platforms, excluding Java and Windows + expect(lockfile).to eq(<<~L) + GEM + remote: https://gem.repo4/ + specs: + nokogiri (1.15.5) + sass-embedded (1.69.5) + sass-embedded (1.69.5-x86_64-linux-gnu) + + PLATFORMS + ruby + x86_64-linux + + DEPENDENCIES + nokogiri + sass-embedded + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + it "does not add ruby platform gem if it brings extra dependencies not resolved originally" do + build_repo4 do + build_gem "nokogiri", "1.15.5" do |s| + s.add_dependency "mini_portile2", "~> 2.8.2" + end + + build_gem "nokogiri", "1.15.5" do |s| + s.platform = "x86_64-linux" + end + end + + gemfile <<~G + source "https://gem.repo4" + + gem "nokogiri" + G + + 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" + + expect(lockfile).to eq(<<~L) + GEM + remote: https://gem.repo4/ + specs: + nokogiri (1.15.5-x86_64-linux) + + PLATFORMS + x86_64-linux + + DEPENDENCIES + nokogiri + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + ["x86_64-linux", "x86_64-linux-musl"].each do |host_platform| + describe "on host platform #{host_platform}" do + it "adds current musl platform" do + build_repo4 do + build_gem "rcee_precompiled", "0.5.0" do |s| + s.platform = "x86_64-linux" + end + + build_gem "rcee_precompiled", "0.5.0" do |s| + s.platform = "x86_64-linux-musl" + end + end + + gemfile <<~G + source "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-gnu" + 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 "x86_64-linux-musl" do + 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: https://gem.repo4/ + specs: + rcee_precompiled (0.5.0-x86_64-linux-gnu) + rcee_precompiled (0.5.0-x86_64-linux-musl) + + PLATFORMS + x86_64-linux-gnu + x86_64-linux-musl + + DEPENDENCIES + rcee_precompiled (= 0.5.0) + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + it "does not add current platform if there's an equivalent less specific platform among the ones resolved" do + build_repo4 do + build_gem "rcee_precompiled", "0.5.0" do |s| + s.platform = "universal-darwin" + end + end + + gemfile <<~G + source "https://gem.repo4" + + gem "rcee_precompiled", "0.5.0" + G + + 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: https://gem.repo4/ + specs: + rcee_precompiled (0.5.0-universal-darwin) + + PLATFORMS + universal-darwin + + DEPENDENCIES + rcee_precompiled (= 0.5.0) + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + it "does not re-resolve when a specific platform, but less specific than the current platform, is locked" do + build_repo4 do + 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 "https://gem.repo4" + + gem "ffi" + gem "nokogiri" + G + + original_lockfile = <<~L + GEM + remote: https://gem.repo4/ + specs: + ffi (1.0) + nokogiri (1.0) + + PLATFORMS + 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 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 def setup_multiplatform_gem 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" } @@ -904,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 e643573454..83875a3d0e 100644 --- a/spec/bundler/install/gemfile_spec.rb +++ b/spec/bundler/install/gemfile_spec.rb @@ -3,8 +3,8 @@ 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)}" + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo1" gem 'rails', '~> 4.0.0' gem 'rails', '~> 4.0.0' @@ -16,60 +16,134 @@ 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") + bundle :install, gemfile: bundled_app("NotGemfile") # 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") + 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 b6c361186a..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" + 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 @@ -21,7 +36,7 @@ RSpec.describe "compact index api" do gem " sinatra" G - bundle :install, :artifice => "compact_index", :raise_on_error => false + bundle :install, artifice: "compact_index", raise_on_error: false expect(err).to include("' sinatra' is not a valid gem name because it contains whitespace.") end @@ -31,7 +46,7 @@ RSpec.describe "compact index api" do gem "rails" G - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(out).to include("Fetching gem metadata from #{source_uri}") expect(the_bundle).to include_gems( "rails 2.3.2", @@ -44,23 +59,23 @@ RSpec.describe "compact index api" do end it "should handle case sensitivity conflicts" do - build_repo4 do - build_gem "rack", "1.0" do |s| - s.add_runtime_dependency("Rack", "0.1") + build_repo4(build_compact_index: false) do + 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 } + 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 @@ -69,22 +84,21 @@ RSpec.describe "compact index api" do gem "net-sftp" G - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(the_bundle).to include_gems "net-sftp 1.1.1" end 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 :install, artifice: "compact_index" - bundle "config set --local deployment true" - bundle "config set --local path vendor/bundle" - bundle :install, :artifice => "compact_index" + 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,12 +109,12 @@ 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 - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(the_bundle).to include_gems("rails 2.3.2") end @@ -113,13 +127,13 @@ 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 :install, artifice: "compact_index" - bundle "config set --local deployment true" - bundle :install, :artifice => "compact_index" + bundle_config "deployment true" + bundle :install, artifice: "compact_index" expect(the_bundle).to include_gems("rails 2.3.2") end @@ -128,12 +142,12 @@ 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 :install, :artifice => "compact_index" + bundle "install", artifice: "compact_index" + bundle_config "deployment true" + bundle :install, artifice: "compact_index" expect(the_bundle).to include_gems("foo 1.0") end @@ -141,41 +155,41 @@ 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" + 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" + bundle :install, verbose: true, artifice: "compact_index_checksum_mismatch" expect(out).to include("Fetching gem metadata from #{source_uri}") - expect(out).to include <<-'WARN' -The checksum of /versions does not match the checksum provided by the server! Something is wrong (local checksum is "\"d41d8cd98f00b204e9800998ecf8427e\"", was expecting "\"123\""). - WARN - expect(the_bundle).to include_gems "rack 1.0.0" + 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=" }.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 = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index", - "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions") - FileUtils.mkdir_p(File.dirname(versions)) - FileUtils.touch(versions) + versions = compact_index_cache_path.join( + "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions" + ) + versions.dirname.mkpath + versions.write("created_at") FileUtils.chmod("-r", versions) - bundle :install, :artifice => "compact_index", :raise_on_error => false + bundle :install, artifice: "compact_index", raise_on_error: false expect(err).to include( "There was an error while trying to read from `#{versions}`. It is likely that you need to grant read permissions for that path." @@ -187,28 +201,28 @@ The checksum of /versions does not match the checksum provided by the server! So gemfile <<-G source "#{source_uri}" - gem "rack" + gem "myrack" G - bundle :install, :artifice => "compact_index" + bundle :install, artifice: "compact_index" expect(out).to include("Fetching gem metadata from #{source_uri}") - expect(the_bundle).to include_gems "rack 1.0.0" + 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" + bundle :install, artifice: "compact_index_host_redirect" + expect(the_bundle).to include_gems "myrack 1.0.0" end - it "handles host redirects without Net::HTTP::Persistent" do + it "handles host redirects without Gem::Net::HTTP::Persistent" do gemfile <<-G source "#{source_uri}" - gem "rack" + gem "myrack" G FileUtils.mkdir_p lib_path @@ -224,18 +238,18 @@ The checksum of /versions does not match the checksum provided by the server! So H end - bundle :install, :artifice => "compact_index_host_redirect", :requires => [lib_path("disable_net_http_persistent.rb")] + bundle :install, artifice: "compact_index_host_redirect", requires: [lib_path("disable_net_http_persistent.rb")] expect(out).to_not match(/Too many redirects/) - expect(the_bundle).to include_gems "rack 1.0.0" + 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 + bundle :install, artifice: "compact_index_redirects", raise_on_error: false expect(err).to match(/Too many redirects/) end @@ -243,23 +257,23 @@ The checksum of /versions does not match the checksum provided by the server! So 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" + 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 + 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 @@ -287,48 +301,30 @@ The checksum of /versions does not match the checksum provided by the server! So end end - system_gems %w[rack-1.0.0 thin-1.0 net_a-1.0], :gem_repo => gem_repo2 - bundle "config set --local path.system true" - ENV["BUNDLER_SPEC_ALL_REQUESTS"] = strip_whitespace(<<-EOS).strip + 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 } + 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")] + FileUtils.rm_r 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")] - end - - install_gemfile <<-G, :artifice => "compact_index_extra", :verbose => true + install_gemfile <<-G, artifice: "compact_index_extra", verbose: true source "#{source_uri}" source "#{source_uri}/extra" do gem "back_deps" @@ -341,31 +337,33 @@ The checksum of /versions does not match the checksum provided by the server! So 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" + bundle :install, artifice: "compact_index_extra_api" + 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" + bundle :install, artifice: "compact_index_extra_api" + 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| @@ -375,14 +373,14 @@ The checksum of /versions does not match the checksum provided by the server! So 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 @@ -390,7 +388,7 @@ The checksum of /versions does not match the checksum provided by the server! So 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 @@ -400,25 +398,23 @@ The checksum of /versions does not match the checksum provided by the server! So end G - bundle :install, :artifice => "compact_index_extra" + bundle :install, artifice: "compact_index_extra" expect(out).to include("Fetching gem metadata from http://localgemserver.test/") expect(out).to include("Fetching source index from http://localgemserver.test/extra") end - it "does not fetch every spec if the index of gems is large when doing back deps" do + it "does not fetch every spec when doing back deps" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" end build_gem "missing" - FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] + FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")] end - api_request_limit = low_api_request_limit_for(gem_repo2) - - install_gemfile <<-G, :artifice => "compact_index_extra_missing", :requires => [api_request_limit_hack_file], :env => { "BUNDLER_SPEC_API_REQUEST_LIMIT" => api_request_limit.to_s }.merge(env_for_missing_prerelease_default_gem_activation) + install_gemfile <<-G, artifice: "compact_index_extra_missing" source "#{source_uri}" source "#{source_uri}/extra" do gem "back_deps" @@ -428,19 +424,17 @@ The checksum of /versions does not match the checksum provided by the server! So expect(the_bundle).to include_gems "back_deps 1.0" end - it "does not fetch every spec if the index of gems is large when doing back deps & everything is the compact index" do + it "does not fetch every spec when doing back deps & everything is the compact index" do build_repo4 do build_gem "back_deps" do |s| s.add_dependency "foo" end build_gem "missing" - FileUtils.rm_rf Dir[gem_repo4("gems/foo-*.gem")] + FileUtils.rm_r Dir[gem_repo4("gems/foo-*.gem")] end - api_request_limit = low_api_request_limit_for(gem_repo4) - - install_gemfile <<-G, :artifice => "compact_index_extra_api_missing", :requires => [api_request_limit_hack_file], :env => { "BUNDLER_SPEC_API_REQUEST_LIMIT" => api_request_limit.to_s }.merge(env_for_missing_prerelease_default_gem_activation) + install_gemfile <<-G, artifice: "compact_index_extra_api_missing" source "#{source_uri}" source "#{source_uri}/extra" do gem "back_deps" @@ -457,36 +451,16 @@ The checksum of /versions does not match the checksum provided by the server! So gem 'foo' G - bundle :install, :artifice => "compact_index_api_missing" + bundle :install, artifice: "compact_index_api_missing" expect(the_bundle).to include_gems "foo 1.0" end - it "fetches again when more dependencies are found in subsequent sources using deployment mode", :bundler => "< 3" do + it "fetches again when more dependencies are found in subsequent sources using deployment mode" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" end - FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] - end - - gemfile <<-G - source "#{source_uri}" - source "#{source_uri}/extra" - gem "back_deps" - G - - bundle :install, :artifice => "compact_index_extra" - bundle "config --set local deployment true" - bundle :install, :artifice => "compact_index_extra" - expect(the_bundle).to include_gems "back_deps 1.0" - end - - it "fetches again when more dependencies are found in subsequent sources using deployment mode with blocks" do - build_repo2 do - build_gem "back_deps" do |s| - s.add_dependency "foo" - end - FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] + FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")] end gemfile <<-G @@ -496,9 +470,9 @@ The checksum of /versions does not match the checksum provided by the server! So end G - bundle :install, :artifice => "compact_index_extra" - bundle "config set --local deployment true" - bundle :install, :artifice => "compact_index_extra" + bundle :install, artifice: "compact_index_extra" + bundle_config "deployment true" + bundle :install, artifice: "compact_index_extra" expect(the_bundle).to include_gems "back_deps 1.0" end @@ -515,70 +489,36 @@ The checksum of /versions does not match the checksum provided by the server! So gem "bundler_dep" G - bundle :install, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + bundle :install, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } expect(out).to include("Fetching gem metadata from #{source_uri}") end - it "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:") + bundle :install, artifice: "compact_index" + 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") + bundle :install, artifice: "compact_index" + 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 let(:user) { "user" } let(:password) { "pass" } let(:basic_auth_source_uri) do - uri = Bundler::URI.parse(source_uri) + uri = Gem::URI.parse(source_uri) uri.user = user uri.password = password @@ -588,105 +528,101 @@ The checksum of /versions does not match the checksum provided by the server! So 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" + 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" + 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" + bundle :install, artifice: "compact_index_creds_diff_host" + 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 it "reads authentication details by host name from bundle config" do bundle "config set #{source_hostname} #{user}:#{password}" - bundle :install, :artifice => "compact_index_strict_basic_authentication" + bundle :install, artifice: "compact_index_strict_basic_authentication" expect(out).to include("Fetching gem metadata from #{source_uri}") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "reads authentication details by full url from bundle config" do # The trailing slash is necessary here; Fetcher canonicalizes the URI. bundle "config set #{source_uri}/ #{user}:#{password}" - bundle :install, :artifice => "compact_index_strict_basic_authentication" + bundle :install, artifice: "compact_index_strict_basic_authentication" expect(out).to include("Fetching gem metadata from #{source_uri}") - expect(the_bundle).to include_gems "rack 1.0.0" + 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" + 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" + bundle :install, artifice: "compact_index_strict_basic_authentication" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "shows instructions if auth is not provided for the source" do - bundle :install, :artifice => "compact_index_strict_basic_authentication", :raise_on_error => false + bundle :install, artifice: "compact_index_strict_basic_authentication", raise_on_error: false expect(err).to include("bundle config set --global #{source_hostname} username:password") end it "fails if authentication has already been provided, but failed" do bundle "config set #{source_hostname} #{user}:wrong" - bundle :install, :artifice => "compact_index_strict_basic_authentication", :raise_on_error => false + bundle :install, artifice: "compact_index_strict_basic_authentication", raise_on_error: false expect(err).to include("Bad username or password") end + + it "does not fallback to old dependency API if bad authentication is provided" do + bundle "config set #{source_hostname} #{user}:wrong" + + bundle :install, artifice: "compact_index_strict_basic_authentication", raise_on_error: false, verbose: true + expect(err).to include("Bad username or password") + expect(out).to include("HTTP 401 Unauthorized http://user@localgemserver.test/versions") + expect(out).not_to include("HTTP 401 Unauthorized http://user@localgemserver.test/api/v1/dependencies") + end end describe "with no password" do @@ -695,11 +631,11 @@ The checksum of /versions does not match the checksum provided by the server! So 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" + bundle :install, artifice: "compact_index_basic_authentication" + expect(the_bundle).to include_gems "myrack 1.0.0" end end end @@ -717,14 +653,14 @@ The checksum of /versions does not match the checksum provided by the server! So 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 @@ -733,17 +669,17 @@ The checksum of /versions does not match the checksum provided by the server! So # Install a monkeypatch that reproduces the effects of openssl raising # a certificate validation error when RubyGems tries to connect. gemfile <<-G - class Net::HTTP + class Gem::Net::HTTP def start raise OpenSSL::SSL::SSLError, "certificate verify failed" end end source "#{source_uri.gsub(/http/, "https")}" - gem "rack" + gem "myrack" G - bundle :install, :raise_on_error => false + bundle :install, raise_on_error: false expect(err).to match(/could not verify the SSL certificate/i) end end @@ -751,118 +687,203 @@ The checksum of /versions does not match the checksum provided by the server! So context ".gemrc with sources is present" do it "uses other sources declared in the Gemfile" do File.open(home(".gemrc"), "w") do |file| - file.puts({ :sources => ["https://rubygems.org"] }.to_yaml) + file.puts({ sources: ["https://rubygems.org"] }.to_yaml) end begin gemfile <<-G source "#{source_uri}" - gem 'rack' + gem 'myrack' G - bundle :install, :artifice => "compact_index_forbidden" + bundle :install, artifice: "compact_index_forbidden" ensure - home(".gemrc").rmtree + FileUtils.rm_rf home(".gemrc") end end end - it "performs partial update with a non-empty range" do + it "performs update with etag not-modified" do + 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 - bundle :install, :artifice => "compact_index" + # Initial install creates the cached versions file and etag file + bundle :install, artifice: "compact_index" + + expect(versions_etag.file?).to eq(true) + previous_content = versions_etag.binread # Update the Gemfile so we can check subsequent install was successful gemfile <<-G source "#{source_uri}" - gem 'rack', '1.0.0' + gem 'myrack', '1.0.0' G - # Second install should make only a partial request to /versions - bundle :install, :artifice => "compact_index_partial_update" + # Second install should match etag + bundle :install, artifice: "compact_index_etag_match" - expect(the_bundle).to include_gems "rack 1.0.0" + expect(versions_etag.binread).to eq(previous_content) + 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 'myrack', '0.9.1' + G + + # Initial install creates the cached versions file and etag file + bundle :install, artifice: "compact_index" + + gemfile <<-G + source "#{source_uri}" + gem 'myrack', '1.0.0' + G + + versions = compact_index_cache_path.join( + "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions" + ) + # Modify the cached file. The ranged request will be based on this but, + # in this test, the range is ignored so this gets overwritten, allowing install. + versions.write "ruining this file" + + bundle :install, artifice: "compact_index_range_ignored" + + expect(the_bundle).to include_gems "myrack 1.0.0" + end + + it "performs partial update with a non-empty range" do + build_repo4 do + 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 'myrack', '0.9.1' + G + + 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 'myrack', '1.0.0' + G + + 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 an empty file to trigger a partial download - versions = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index", - "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions") - FileUtils.mkdir_p(File.dirname(versions)) - FileUtils.touch(versions) + # Create a partial cache versions file + versions = compact_index_cache_path.join( + "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions" + ) + versions.dirname.mkpath + versions.write("created_at") + + bundle :install, artifice: "compact_index_concurrent_download" + + expect(versions.read).to start_with("created_at") + 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 "myrack", "0.9.1" + end + + install_gemfile <<-G, artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + source "#{source_uri}" + gem 'myrack', '0.9.1' + G + + build_repo4 do + build_gem "myrack", "1.0.0" + end - bundle :install, :artifice => "compact_index_concurrent_download" + install_gemfile <<-G, artifice: "compact_index_partial_update_bad_digest", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + source "#{source_uri}" + gem 'myrack', '1.0.0' + G - expect(File.read(versions)).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 full update if server endpoints serve partial content responses but don't have incremental content and provide no Etag" do + 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 } + 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_etag_not_incremental", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + 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 - rake_info_path = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index", - "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "info", "rack") + bundle :install, artifice: "compact_index" + + cache_path = compact_index_cache_path.join("localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5") - bundle :install, :artifice => "compact_index" + # We must remove the etag so that we don't ignore the range and get a 304 Not Modified. + myrack_info_etag_path = File.join(cache_path, "info-etags", "myrack-92f3313ce5721296f14445c3a6b9c073") + File.unlink(myrack_info_etag_path) if File.exist?(myrack_info_etag_path) - 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. We expect them to be reset to the normal ones when we re-run :install - File.open(rake_info_path, "w") {|f| f << (expected_rack_info_content + "this is different") } + # Modify the cache files to make the range not satisfiable + File.open(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" + 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 + 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) @@ -871,79 +892,121 @@ The checksum of /versions does not match the checksum provided by the server! So end describe "checksum validation" do + before do + lockfile <<-L + GEM + remote: #{source_uri} + specs: + myrack (1.0.0) + + PLATFORMS + ruby + + DEPENDENCIES + #{checksums_section} + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "handles checksums from the server in base64" do + api_checksum = checksum_digest(gem_repo1, "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 "myrack" + G + + expect(out).to include("Fetching gem metadata from #{source_uri}") + 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 + install_gemfile <<-G, artifice: "compact_index_wrong_gem_checksum", raise_on_error: false source "#{source_uri}" - gem "rack" + gem "myrack" G - expect(exitstatus).to eq(19) - expect(err). - to include("Bundler cannot continue installing rack (1.0.0)."). - and include("The checksum for the downloaded `rack-1.0.0.gem` does not match the checksum given by the server."). - and include("This means the contents of the downloaded gem is different from what was uploaded to the server, and could be a potential security issue."). - and include("To resolve this issue:"). - and include("1. delete the downloaded gem located at: `#{default_bundle_path}/gems/rack-1.0.0/rack-1.0.0.gem`"). - and include("2. run `bundle install`"). - and include("If you wish to continue installing the downloaded gem, and are certain it does not pose a security issue despite the mismatching checksum, do the following:"). - and include("1. run `bundle config set --local disable_checksum_validation true` to turn off checksum verification"). - and include("2. run `bundle install`"). - and match(/\(More info: The expected SHA256 checksum was "#{"ab" * 22}", but the checksum for the downloaded gem was ".+?"\.\)/) + gem_path = 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. + myrack (1.0.0) sha256=2222222222222222222222222222222222222222222222222222222222222222 + from the API at http://localgemserver.test/ + #{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: + 1. remove the gem at #{gem_path} + 2. run `bundle install` + + To ignore checksum security warnings, disable checksum validation with + `bundle config set --local disable_checksum_validation true` + E end it "raises when the checksum is the wrong length" do - install_gemfile <<-G, :artifice => "compact_index_wrong_gem_checksum", :env => { "BUNDLER_SPEC_RACK_CHECKSUM" => "checksum!", "DEBUG" => "1" }, :verbose => true, :raise_on_error => false + install_gemfile <<-G, artifice: "compact_index_wrong_gem_checksum", env: { "BUNDLER_SPEC_MYRACK_CHECKSUM" => "checksum!", "DEBUG" => "1" }, verbose: true, raise_on_error: false source "#{source_uri}" - gem "rack" + gem "myrack" G - expect(exitstatus).to eq(5) - expect(err).to include("The given checksum for rack-1.0.0 (\"checksum!\") is not a valid SHA256 hexdigest nor base64digest") + expect(exitstatus).to eq(14) + expect(err).to include('Invalid checksum for 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" - install_gemfile <<-G, :artifice => "compact_index_wrong_gem_checksum" + 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 it "works when cache dir is world-writable" do - install_gemfile <<-G, :artifice => "compact_index" + install_gemfile <<-G, artifice: "compact_index" File.umask(0000) source "#{source_uri}" - gem "rack" + gem "myrack" G end it "doesn't explode when the API dependencies are wrong" do - install_gemfile <<-G, :artifice => "compact_index_wrong_dependencies", :env => { "DEBUG" => "true" }, :raise_on_error => false + install_gemfile <<-G, artifice: "compact_index_wrong_dependencies", env: { "DEBUG" => "true" }, raise_on_error: false source "#{source_uri}" gem "rails" G - deps = [Gem::Dependency.new("rake", "= 13.0.1"), + deps = [Gem::Dependency.new("rake", "= #{rake_version}"), Gem::Dependency.new("actionpack", "= 2.3.2"), Gem::Dependency.new("activerecord", "= 2.3.2"), Gem::Dependency.new("actionmailer", "= 2.3.2"), Gem::Dependency.new("activeresource", "= 2.3.2")] - expect(out).to include("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(", ")}). -Either installing with `--full-index` or running `bundle update rails` should fix the problem. +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)}" + install_gemfile <<-G, artifice: "compact_index" + source "https://gem.repo1" source "#{source_uri}" do gem "rails" gem "activemerchant" end G - gem_command "uninstall activemerchant" - bundle "update rails", :artifice => "compact_index" - expect(lockfile.scan(/activemerchant \(/).size).to eq(1) + 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 6cb3d9697d..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" + 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 @@ -21,7 +21,7 @@ RSpec.describe "gemcutter's dependency API" do gem " sinatra" G - bundle :install, :artifice => "endpoint", :raise_on_error => false + bundle :install, artifice: "endpoint", raise_on_error: false expect(err).to include("' sinatra' is not a valid gem name because it contains whitespace.") end @@ -31,7 +31,7 @@ RSpec.describe "gemcutter's dependency API" do gem "rails" G - bundle :install, :artifice => "endpoint" + bundle :install, artifice: "endpoint" expect(out).to include("Fetching gem metadata from #{source_uri}/...") expect(the_bundle).to include_gems( "rails 2.3.2", @@ -49,22 +49,21 @@ RSpec.describe "gemcutter's dependency API" do gem "net-sftp" G - bundle :install, :artifice => "endpoint" + bundle :install, artifice: "endpoint" expect(the_bundle).to include_gems "net-sftp 1.1.1" end 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 :install, artifice: "endpoint" - bundle "config set --local deployment true" - bundle "config set --local path vendor/bundle" - bundle :install, :artifice => "endpoint" + 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,12 +74,12 @@ 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 - bundle :install, :artifice => "endpoint" + bundle :install, artifice: "endpoint" expect(the_bundle).to include_gems("rails 2.3.2") end @@ -93,13 +92,13 @@ 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 :install, artifice: "endpoint" - bundle "config set --local deployment true" - bundle :install, :artifice => "endpoint" + bundle_config "deployment true" + bundle :install, artifice: "endpoint" expect(the_bundle).to include_gems("rails 2.3.2") end @@ -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 :install, :artifice => "endpoint" + bundle "install", artifice: "endpoint" + 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,10 +146,10 @@ 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" + bundle :install, artifice: "endpoint_fallback" expect(out).to include("Fetching source index from #{source_uri}") expect(the_bundle).to include_gems( @@ -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" + 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" + 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" + bundle :install, artifice: "endpoint_host_redirect" + expect(the_bundle).to include_gems "myrack 1.0.0" end - it "handles host redirects without Net::HTTP::Persistent" do + it "handles host redirects without Gem::Net::HTTP::Persistent" do gemfile <<-G source "#{source_uri}" - gem "rack" + gem "myrack" G FileUtils.mkdir_p lib_path @@ -216,18 +215,18 @@ RSpec.describe "gemcutter's dependency API" do H end - bundle :install, :artifice => "endpoint_host_redirect", :requires => [lib_path("disable_net_http_persistent.rb")] + bundle :install, artifice: "endpoint_host_redirect", requires: [lib_path("disable_net_http_persistent.rb")] expect(out).to_not match(/Too many redirects/) - expect(the_bundle).to include_gems "rack 1.0.0" + 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 + bundle :install, artifice: "endpoint_redirect", raise_on_error: false expect(err).to match(/Too many redirects/) end @@ -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" + 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 + 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 @@ -288,37 +269,39 @@ RSpec.describe "gemcutter's dependency API" do end G - bundle :install, :artifice => "endpoint_extra" + bundle :install, artifice: "endpoint_extra" expect(the_bundle).to include_gems "back_deps 1.0", "foo 1.0" end 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" + 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" + bundle :install, artifice: "endpoint_extra_api" + 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 @@ -353,46 +336,23 @@ RSpec.describe "gemcutter's dependency API" do end G - bundle :install, :artifice => "endpoint_extra" + bundle :install, artifice: "endpoint_extra" expect(out).to include("Fetching gem metadata from http://localgemserver.test/.") expect(out).to include("Fetching source index from http://localgemserver.test/extra") end - it "does not fetch every spec if the index of gems is large when doing back deps", :bundler => "< 3" do - 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 - - api_request_limit = low_api_request_limit_for(gem_repo2) - - install_gemfile <<-G, :artifice => "endpoint_extra_missing", :requires => [api_request_limit_hack_file], :env => { "BUNDLER_SPEC_API_REQUEST_LIMIT" => api_request_limit.to_s }.merge(env_for_missing_prerelease_default_gem_activation) - 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 if the index of gems is large 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 - api_request_limit = low_api_request_limit_for(gem_repo2) - - install_gemfile <<-G, :artifice => "endpoint_extra_missing", :requires => [api_request_limit_hack_file], :env => { "BUNDLER_SPEC_API_REQUEST_LIMIT" => api_request_limit.to_s }.merge(env_for_missing_prerelease_default_gem_activation) + install_gemfile <<-G, artifice: "endpoint_extra_missing" source "#{source_uri}" source "#{source_uri}/extra" do gem "back_deps" @@ -402,32 +362,12 @@ RSpec.describe "gemcutter's dependency API" do expect(the_bundle).to include_gems "back_deps 1.0" end - it "fetches again when more dependencies are found in subsequent sources using deployment mode", :bundler => "< 3" do - build_repo2 do - build_gem "back_deps" do |s| - s.add_dependency "foo" - end - FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] - end - - gemfile <<-G - source "#{source_uri}" - source "#{source_uri}/extra" - gem "back_deps" - G - - bundle :install, :artifice => "endpoint_extra" - bundle "config set --local deployment true" - bundle :install, :artifice => "endpoint_extra" - expect(the_bundle).to include_gems "back_deps 1.0" - end - - it "fetches again when more dependencies are found in subsequent sources using deployment mode with blocks" do + it "fetches again when more dependencies are found in subsequent sources using deployment mode" do build_repo2 do build_gem "back_deps" do |s| s.add_dependency "foo" end - FileUtils.rm_rf Dir[gem_repo2("gems/foo-*.gem")] + FileUtils.rm_r Dir[gem_repo2("gems/foo-*.gem")] end gemfile <<-G @@ -437,9 +377,9 @@ RSpec.describe "gemcutter's dependency API" do end G - bundle :install, :artifice => "endpoint_extra" - bundle "config set --local deployment true" - bundle "install", :artifice => "endpoint_extra" + bundle :install, artifice: "endpoint_extra" + bundle_config "deployment true" + bundle "install", artifice: "endpoint_extra" expect(the_bundle).to include_gems "back_deps 1.0" end @@ -449,7 +389,7 @@ RSpec.describe "gemcutter's dependency API" do build_gem "foo", "2.0" end - install_gemfile <<-G, :artifice => "endpoint", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }, :verbose => true + install_gemfile <<-G, artifice: "endpoint", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }, verbose: true source "#{source_uri}" gem "foo" @@ -472,70 +412,36 @@ RSpec.describe "gemcutter's dependency API" do gem "bundler_dep" G - bundle :install, :artifice => "endpoint", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } + bundle :install, artifice: "endpoint", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } expect(out).to include("Fetching gem metadata from #{source_uri}") end - it "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:") + bundle :install, artifice: "endpoint" + 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") + bundle :install, artifice: "endpoint" + 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 let(:user) { "user" } let(:password) { "pass" } let(:basic_auth_source_uri) do - uri = Bundler::URI.parse(source_uri) + uri = Gem::URI.parse(source_uri) uri.user = user uri.password = password @@ -545,82 +451,69 @@ 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" + 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" + 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" + 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 + 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" + bundle :install, artifice: "endpoint_creds_diff_host" + 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 it "reads authentication details from a valid ENV variable" do - bundle :install, :artifice => "endpoint_strict_basic_authentication", :env => { "BUNDLE_LOCAL___GEMSERVER__TEST" => "#{user}:#{password}" } + bundle :install, artifice: "endpoint_strict_basic_authentication", env: { "BUNDLE_LOCAL___GEMSERVER__TEST" => "#{user}:#{password}" } expect(out).to include("Fetching gem metadata from http://local-gemserver.test") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end end @@ -628,57 +521,57 @@ RSpec.describe "gemcutter's dependency API" do before do gemfile <<-G source "#{source_uri}" - gem "rack" + gem "myrack" G end it "reads authentication details by host name from bundle config" do bundle "config set #{source_hostname} #{user}:#{password}" - bundle :install, :artifice => "endpoint_strict_basic_authentication" + bundle :install, artifice: "endpoint_strict_basic_authentication" expect(out).to include("Fetching gem metadata from #{source_uri}") - expect(the_bundle).to include_gems "rack 1.0.0" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "reads authentication details by full url from bundle config" do # The trailing slash is necessary here; Fetcher canonicalizes the URI. bundle "config set #{source_uri}/ #{user}:#{password}" - bundle :install, :artifice => "endpoint_strict_basic_authentication" + bundle :install, artifice: "endpoint_strict_basic_authentication" expect(out).to include("Fetching gem metadata from #{source_uri}") - expect(the_bundle).to include_gems "rack 1.0.0" + 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" + 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" + bundle :install, artifice: "endpoint_strict_basic_authentication" + expect(the_bundle).to include_gems "myrack 1.0.0" end it "shows instructions if auth is not provided for the source" do - bundle :install, :artifice => "endpoint_strict_basic_authentication", :raise_on_error => false + bundle :install, artifice: "endpoint_strict_basic_authentication", raise_on_error: false expect(err).to include("bundle config set --global #{source_hostname} username:password") end it "fails if authentication has already been provided, but failed" do bundle "config set #{source_hostname} #{user}:wrong" - bundle :install, :artifice => "endpoint_strict_basic_authentication", :raise_on_error => false + bundle :install, artifice: "endpoint_strict_basic_authentication", raise_on_error: false expect(err).to include("Bad username or password") end end @@ -689,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" + bundle :install, artifice: "endpoint_basic_authentication" + expect(the_bundle).to include_gems "myrack 1.0.0" end end end @@ -711,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 @@ -727,17 +620,17 @@ RSpec.describe "gemcutter's dependency API" do # Install a monkeypatch that reproduces the effects of openssl raising # a certificate validation error when RubyGems tries to connect. gemfile <<-G - class Net::HTTP + class Gem::Net::HTTP def start raise OpenSSL::SSL::SSLError, "certificate verify failed" end end source "#{source_uri.gsub(/http/, "https")}" - gem "rack" + gem "myrack" G - bundle :install, :raise_on_error => false + bundle :install, raise_on_error: false expect(err).to match(/could not verify the SSL certificate/i) end end @@ -745,18 +638,18 @@ RSpec.describe "gemcutter's dependency API" do context ".gemrc with sources is present" do it "uses other sources declared in the Gemfile" do File.open(home(".gemrc"), "w") do |file| - file.puts({ :sources => ["https://rubygems.org"] }.to_yaml) + file.puts({ sources: ["https://rubygems.org"] }.to_yaml) end begin gemfile <<-G source "#{source_uri}" - gem 'rack' + gem 'myrack' G - bundle "install", :artifice => "endpoint_marshal_fail" + 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 d5fa55be48..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,63 +160,63 @@ 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 it "does not install gems whose dependencies are not met" do - bundle :install, :raise_on_error => false - ruby <<-RUBY, :raise_on_error => false + bundle :install, raise_on_error: false + ruby <<-RUBY, raise_on_error: false require 'bundler/setup' RUBY - expect(err).to match(/could not find gem 'rack-obama/i) + 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.gsub(/^ {8}/, "") + 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)}/ 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 - bundle :install, :retry => 0, :raise_on_error => false + bundle :install, retry: 0, raise_on_error: false expect(err).to end_with(nice_error) 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.gsub(/^ {8}/, "") - Bundler could not find compatible versions for gem "rack-obama": + bad_error = <<~E.strip + 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,19 +233,19 @@ 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 end it "discards the conflicting lockfile information and resolves properly" do - bundle :update, :raise_on_error => false, :all => true + bundle :update, raise_on_error: false, all: true expect(err).to be_empty end end @@ -253,43 +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_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 @@ -297,35 +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_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 @@ -335,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 @@ -353,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 5c18d2cc51..d5b10d2c8f 100644 --- a/spec/bundler/install/gems/native_extensions_spec.rb +++ b/spec/bundler/install/gems/native_extensions_spec.rb @@ -7,10 +7,9 @@ RSpec.describe "installing a gem with native extensions" do s.extensions = ["ext/extconf.rb"] s.write "ext/extconf.rb", <<-E require "mkmf" - $extout = "$(topdir)/" + RbConfig::CONFIG["EXTOUT"] 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 @@ -34,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") @@ -52,10 +51,9 @@ RSpec.describe "installing a gem with native extensions" do s.extensions = ["ext/extconf.rb"] s.write "ext/extconf.rb", <<-E require "mkmf" - $extout = "$(topdir)/" + RbConfig::CONFIG["EXTOUT"] 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 @@ -77,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 @@ -93,14 +91,13 @@ RSpec.describe "installing a gem with native extensions" do it "installs correctly from git when multiple gems with extensions share one repository" do build_repo2 do ["one", "two"].each do |n| - build_lib "c_extension_#{n}", "1.0", :path => lib_path("gems/c_extension_#{n}") do |s| + build_lib "c_extension_#{n}", "1.0", path: lib_path("gems/c_extension_#{n}") do |s| s.extensions = ["ext/extconf.rb"] s.write "ext/extconf.rb", <<-E require "mkmf" - $extout = "$(topdir)/" + RbConfig::CONFIG["EXTOUT"] 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,21 +119,21 @@ RSpec.describe "installing a gem with native extensions" do C end end - build_git "gems", :path => lib_path("gems"), :gemspec => false + build_git "gems", path: lib_path("gems"), gemspec: false end - bundle "config set build.c_extension_one --with-c_extension_one=one" - 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 @@ -150,10 +147,9 @@ RSpec.describe "installing a gem with native extensions" do s.extensions = ["ext/extconf.rb"] s.write "ext/extconf.rb", <<-E require "mkmf" - $extout = "$(topdir)/" + RbConfig::CONFIG["EXTOUT"] 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 @@ -175,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 fb6dda2f88..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,30 +100,32 @@ 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" spec = Marshal.load(Bundler.rubygems.inflate(File.binread(path))) spec.dependencies.each do |d| - d.instance_variable_set(:@type, :fail) + d.instance_variable_set(:@type, "fail") end File.open(path, "wb") do |f| 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 @@ -131,8 +133,8 @@ RSpec.describe "bundle install with install-time dependencies" do end it "installs plugins depended on by other plugins" do - install_gemfile <<-G, :env => { "DEBUG" => "1" } - source "#{file_uri_for(gem_repo2)}" + install_gemfile <<-G, env: { "DEBUG" => "1" } + source "https://gem.repo2" gem "net_a" G @@ -140,8 +142,8 @@ RSpec.describe "bundle install with install-time dependencies" do end it "installs multiple levels of dependencies" do - install_gemfile <<-G, :env => { "DEBUG" => "1" } - source "#{file_uri_for(gem_repo2)}" + install_gemfile <<-G, env: { "DEBUG" => "1" } + source "https://gem.repo2" gem "net_c" gem "net_e" G @@ -152,12 +154,12 @@ 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 - bundle :install, :env => { "BUNDLER_DEBUG_RESOLVER" => "1", "DEBUG" => "1" } + bundle :install, env: { "BUNDLER_DEBUG_RESOLVER" => "1", "DEBUG" => "1" } expect(out).to include("Resolving dependencies...") end @@ -166,12 +168,12 @@ 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 - bundle :install, :env => { "DEBUG_RESOLVER" => "1", "DEBUG" => "1" } + bundle :install, env: { "DEBUG_RESOLVER" => "1", "DEBUG" => "1" } expect(out).to include("Resolving dependencies...") end @@ -180,12 +182,12 @@ 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 - bundle :install, :env => { "DEBUG_RESOLVER_TREE" => "1", "DEBUG" => "1" } + bundle :install, env: { "DEBUG_RESOLVER_TREE" => "1", "DEBUG" => "1" } expect(out).to include(" net_b"). and include("Resolving dependencies..."). @@ -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,13 +254,17 @@ RSpec.describe "bundle install with install-time dependencies" do end gemfile <<-G - source "http://localgemserver.test/" + source "https://gem.repo2" gem 'parallel_tests' G + checksums = checksums_section do |c| + c.checksum gem_repo2, "parallel_tests", "3.8.0" + end + lockfile <<~L GEM - remote: http://localgemserver.test/ + remote: https://gem.repo2/ specs: parallel_tests (3.8.0) @@ -267,18 +273,22 @@ RSpec.describe "bundle install with install-time dependencies" do DEPENDENCIES 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_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) @@ -287,19 +297,18 @@ RSpec.describe "bundle install with install-time dependencies" do DEPENDENCIES 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 @@ -328,13 +337,18 @@ RSpec.describe "bundle install with install-time dependencies" do end gemfile <<-G - source "http://localgemserver.test/" + source "https://gem.repo2" gem 'rubocop' G + checksums = checksums_section do |c| + c.checksum gem_repo2, "rubocop", "1.35.0" + c.checksum gem_repo2, "rubocop-ast", "1.21.0" + end + lockfile <<~L GEM - remote: http://localgemserver.test/ + remote: https://gem.repo2/ specs: rubocop (1.35.0) rubocop-ast (>= 1.20.1, < 2.0) @@ -344,19 +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_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) @@ -367,14 +386,14 @@ RSpec.describe "bundle install with install-time dependencies" do DEPENDENCIES 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| @@ -387,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) @@ -406,25 +425,141 @@ RSpec.describe "bundle install with install-time dependencies" do sorbet (= 0.5.10554) BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end it "raises a proper error" do simulate_platform "aarch64-linux" do - bundle "install", :raise_on_error => false + bundle "install", raise_on_error: false end - nice_error = strip_whitespace(<<-E).strip - Could not find gem 'sorbet-static (= 0.5.10554)' with platforms 'arm64-darwin-21', 'aarch64-linux' in rubygems repository #{file_uri_for(gem_repo4)}/ or installed locally. + 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 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 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 + + context "when adding a new gem that does not resolve under all locked platforms" do + before do + simulate_platform "x86_64-linux" do + build_repo4 do + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "x86_64-linux" + end + build_gem "nokogiri", "1.14.0" do |s| + s.platform = "arm-linux" + end + + build_gem "sorbet-static", "0.5.10696" do |s| + s.platform = "x86_64-linux" + end + end + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + nokogiri (1.14.0-arm-linux) + nokogiri (1.14.0-x86_64-linux) + + PLATFORMS + arm-linux + x86_64-linux + + DEPENDENCIES + nokogiri + + BUNDLED WITH + #{Bundler::VERSION} + L + + gemfile <<~G + source "https://gem.repo4" + + gem "nokogiri" + gem "sorbet-static" + G + + bundle "lock", raise_on_error: false + end + end + + it "raises a proper error" do + nice_error = <<~E.strip + Could not find gems matching 'sorbet-static' valid for all resolution platforms (arm-linux, x86_64-linux) in rubygems repository https://gem.repo4/ or installed locally. + + The source contains the following gems matching 'sorbet-static': + * sorbet-static-0.5.10696-x86_64-linux + E expect(err).to end_with(nice_error) end end + 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| @@ -432,14 +567,14 @@ RSpec.describe "bundle install with install-time dependencies" do end end - build_lib("foo", :path => bundled_app) do |s| + build_lib("foo", path: bundled_app) do |s| s.required_ruby_version = ">= #{Gem.ruby_version}" s.add_dependency "requires-old-ruby" end - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo4)}" + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo4" gemspec G @@ -457,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 @@ -514,14 +649,14 @@ 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 expect(out).to_not include("Gem::InstallError: require_ruby requires Ruby version > 9000") - nice_error = strip_whitespace(<<-E).strip + nice_error = <<~E.strip Could not find compatible versions Because every version of require_ruby depends on Ruby > 9000 @@ -535,15 +670,15 @@ 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 expect(out).to_not include("Gem::InstallError: require_ruby requires Ruby version > 9000") - nice_error = strip_whitespace(<<-E).strip + nice_error = <<~E.strip Could not find compatible versions Because every version of require_ruby depends on Ruby > 9000 @@ -581,13 +716,13 @@ RSpec.describe "bundle install with install-time dependencies" do end end - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo2)}" + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo2" gem 'require_rubygems' G expect(err).to_not include("Gem::InstallError: require_rubygems requires RubyGems version > 9000") - nice_error = strip_whitespace(<<-E).strip + nice_error = <<~E.strip Because every version of require_rubygems depends on RubyGems > 9000 and Gemfile depends on require_rubygems >= 0, RubyGems > 9000 is required. @@ -597,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 4d08752256..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 @@ -97,7 +97,23 @@ RSpec.shared_examples "bundle install --standalone" do testrb << "\nrequire \"#{k}\"" testrb << "\nputs #{k.upcase}" end - ruby testrb, :dir => "#{bundled_app}2" + ruby testrb, dir: "#{bundled_app}2" + + expect(out).to eq(expected_gems.values.join("\n")) + end + + it "skips activating gems" do + testrb = String.new <<-RUBY + $:.unshift File.expand_path("bundle") + require "bundler/setup" + + gem "do_not_activate_me" + RUBY + expected_gems.each do |k, _| + testrb << "\nrequire \"#{k}\"" + testrb << "\nputs #{k.upcase}" + end + in_bundled_app %(#{Gem.ruby} -w -e #{testrb.shellescape}) expect(out).to eq(expected_gems.values.join("\n")) end @@ -106,11 +122,11 @@ 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 :install, :standalone => true, :dir => cwd + bundle_config "path #{bundled_app("bundle")}" + bundle :install, standalone: true, dir: cwd end let(:expected_gems) do @@ -124,22 +140,14 @@ RSpec.shared_examples "bundle install --standalone" do end describe "with default gems and a lockfile", :ruby_repo do - before do - skip "does not work on rubygems versions where `--install_dir` doesn't respect --default" unless Gem::Installer.for_spec(loaded_gemspec, :install_dir => "/foo").default_spec_file == "/foo/specifications/default/bundler-#{Bundler::VERSION}.gemspec" # Since rubygems 3.2.0.rc.2 - skip "does not work on old rubies because the realworld gems that need to be installed don't support them" if RUBY_VERSION < "2.7.0" + 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")) - 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.0.1"] - necessary_system_gems += ["shellwords --version 0.1.0", "base64 --version 0.1.0", "resolv --version 0.2.1"] if Gem.rubygems_version < Gem::Version.new("3.3.a") - necessary_system_gems += ["yaml --version 0.1.1"] if Gem.rubygems_version < Gem::Version.new("3.4.a") - realworld_system_gems(*necessary_system_gems, :path => scoped_gem_path(bundled_app("bundle"))) - - build_gem "foo", "1.0.0", :to_system => true, :default => true do |s| + build_gem "foo", "1.0.0", to_system: true, default: true do |s| s.add_dependency "bar" end - build_gem "bar", "1.0.0", :to_system => true, :default => true + build_gem "bar", "1.0.0", to_system: true, default: true build_repo4 do build_gem "foo", "1.0.0" do |s| @@ -154,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 + + bundle_config "path #{bundled_app("bundle")}" - 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 :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") } @@ -168,27 +175,57 @@ 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 before do - build_lib "minitest", "1.0.0", :path => lib_path("minitest") + build_lib "minitest", "1.0.0", path: lib_path("minitest") Dir.mkdir bundled_app("app") gemfile bundled_app("app/Gemfile"), <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "minitest", :path => "#{lib_path("minitest")}" G - bundle "install", :standalone => true, :dir => bundled_app("app") + bundle "install", standalone: true, dir: bundled_app("app") Dir.mkdir tmp("one_more_level") FileUtils.mv bundled_app, tmp("one_more_level") end it "also works" do - ruby <<-RUBY, :dir => tmp("one_more_level/bundled_app/app") + ruby <<-RUBY, dir: tmp("one_more_level/bundled_app/app") require "./bundle/bundler/setup" require "minitest" @@ -200,24 +237,26 @@ 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") - build_lib "minitest", "1.0.0", :path => bundled_app("app/vendor/minitest") + 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 - bundle "install", :standalone => true, :dir => bundled_app("app") + bundle "install", standalone: true, dir: bundled_app("app") FileUtils.mv(bundled_app("app"), bundled_app2("app")) end it "also works" do - ruby <<-RUBY, :dir => bundled_app2("app") + ruby <<-RUBY, dir: bundled_app2("app") require "./bundle/bundler/setup" require "minitest" @@ -231,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")}" - install_gemfile <<-G, :standalone => true, :dir => cwd - source "#{file_uri_for(gem_repo1)}" + bundle_config "path #{bundled_app("bundle")}" + install_gemfile <<-G, standalone: true, dir: cwd + source "https://gem.repo1" gem "very_simple_binary" G end @@ -252,7 +291,7 @@ RSpec.shared_examples "bundle install --standalone" do describe "with gem that has an invalid gemspec" do before do - build_git "bar", :gemspec => false do |s| + build_git "bar", gemspec: false do |s| s.write "lib/bar/version.rb", %(BAR_VERSION = '1.0') s.write "bar.gemspec", <<-G lib = File.expand_path('lib/', __dir__) @@ -269,16 +308,16 @@ RSpec.shared_examples "bundle install --standalone" do end G end - bundle "config set --local path #{bundled_app("bundle")}" - install_gemfile <<-G, :standalone => true, :dir => cwd, :raise_on_error => false - source "#{file_uri_for(gem_repo1)}" + bundle_config "path #{bundled_app("bundle")}" + install_gemfile <<-G, standalone: true, dir: cwd, raise_on_error: false + source "https://gem.repo1" gem "bar", :git => "#{lib_path("bar-1.0")}" 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 @@ -287,12 +326,12 @@ 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 :install, :standalone => true, :dir => cwd + bundle_config "path #{bundled_app("bundle")}" + bundle :install, standalone: true, dir: cwd end let(:expected_gems) do @@ -311,16 +350,16 @@ 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 :install, :standalone => true, :dir => cwd + bundle_config "path #{bundled_app("bundle")}" + bundle :install, standalone: true, dir: cwd end let(:expected_gems) do @@ -333,8 +372,8 @@ 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 :install, :standalone => "default", :dir => cwd + bundle_config "path #{bundled_app("bundle")}" + bundle :install, standalone: "default", dir: cwd load_error_ruby <<-RUBY, "spec" $:.unshift File.expand_path("bundle") @@ -346,13 +385,13 @@ 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 :install, :standalone => true, :dir => cwd + bundle_config "path #{bundled_app("bundle")}" + bundle_config "without test" + bundle :install, standalone: true, dir: cwd load_error_ruby <<-RUBY, "spec" $:.unshift File.expand_path("bundle") @@ -364,12 +403,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 `path` configuration to change the location of the standalone bundle" do - bundle "config set --local path path/to/bundle" - bundle "install", :standalone => true, :dir => cwd + bundle_config "path path/to/bundle" + bundle "install", standalone: true, dir: cwd ruby <<-RUBY $:.unshift File.expand_path("path/to/bundle") @@ -383,10 +422,10 @@ 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 :install, :dir => cwd - bundle "config set --local path #{bundled_app("bundle")}" - bundle :install, :standalone => true, :dir => cwd + bundle_config "without test" + bundle :install, dir: cwd + bundle_config "path #{bundled_app("bundle")}" + bundle :install, standalone: true, dir: cwd load_error_ruby <<-RUBY, "spec" $:.unshift File.expand_path("bundle") @@ -398,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 @@ -411,8 +450,8 @@ RSpec.shared_examples "bundle install --standalone" do source "#{source_uri}" gem "rails" G - bundle "config set --local path #{bundled_app("bundle")}" - bundle :install, :standalone => true, :artifice => "endpoint", :dir => cwd + bundle_config "path #{bundled_app("bundle")}" + bundle :install, standalone: true, artifice: "endpoint", dir: cwd end let(:expected_gems) do @@ -425,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") + it "generates the script in the proper place" do + bundle :install, standalone: true, dir: cwd - File.symlink(bundled_app("bin/rails"), symlink) - sys_exec("#{symlink} -v") - expect(out).to eq("2.3.2") - end - - 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 7b58ea9839..fb2271c830 100644 --- a/spec/bundler/install/gemspecs_spec.rb +++ b/spec/bundler/install/gemspecs_spec.rb @@ -4,13 +4,13 @@ RSpec.describe "bundle install" do describe "when a gem has a YAML gemspec" do before :each do build_repo2 do - build_gem "yaml_spec", :gemspec => :yaml + build_gem "yaml_spec", gemspec: :yaml end end it "still installs correctly" do gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "yaml_spec" G bundle :install @@ -18,10 +18,10 @@ RSpec.describe "bundle install" do end it "still installs correctly when using path" do - build_lib "yaml_spec", :gemspec => :yaml + build_lib "yaml_spec", gemspec: :yaml install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + 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" + bundle :install, artifice: "endpoint_marshal_fail" # force gemspec load + 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 @@ -59,8 +59,8 @@ RSpec.describe "bundle install" do end G - install_gemfile <<-G, :env => { "LANG" => "C" } - source "#{file_uri_for(gem_repo1)}" + install_gemfile <<-G, env: { "LANG" => "C" } + 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 @@ -95,61 +95,59 @@ RSpec.describe "bundle install" do context "when ruby version is specified in gemspec and gemfile" do it "installs when patch level is not specified and the version matches", - :if => RUBY_PATCHLEVEL >= 0 do - build_lib("foo", :path => bundled_app) do |s| + if: RUBY_PATCHLEVEL >= 0 do + build_lib("foo", path: bundled_app) do |s| s.required_ruby_version = "~> #{RUBY_VERSION}.0" end 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" end it "installs when patch level is specified and the version still matches the current version", - :if => RUBY_PATCHLEVEL >= 0 do - build_lib("foo", :path => bundled_app) do |s| + if: RUBY_PATCHLEVEL >= 0 do + build_lib("foo", path: bundled_app) do |s| s.required_ruby_version = "#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}" end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false ruby '#{RUBY_VERSION}', :engine_version => '#{RUBY_VERSION}', :engine => 'ruby', :patchlevel => '#{RUBY_PATCHLEVEL}' - source "#{file_uri_for(gem_repo1)}" + 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", - :if => RUBY_PATCHLEVEL >= 0 do + 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| + build_lib("foo", path: bundled_app) do |s| s.required_ruby_version = "#{RUBY_VERSION}.#{patchlevel}" end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false ruby '#{RUBY_VERSION}', :engine_version => '#{RUBY_VERSION}', :engine => 'ruby', :patchlevel => '#{patchlevel}' - source "#{file_uri_for(gem_repo1)}" + 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 version = Gem::Requirement.create(RUBY_VERSION).requirements.first.last.bump.version - build_lib("foo", :path => bundled_app) do |s| + build_lib("foo", path: bundled_app) do |s| s.required_ruby_version = version end - install_gemfile <<-G, :raise_on_error => false + install_gemfile <<-G, raise_on_error: false ruby '#{version}', :engine_version => '#{version}', :engine => 'ruby' - source "#{file_uri_for(gem_repo1)}" + 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 0fa7ed8531..1172d661ae 100644 --- a/spec/bundler/install/git_spec.rb +++ b/spec/bundler/install/git_spec.rb @@ -3,64 +3,77 @@ RSpec.describe "bundle install" do context "git sources" do it "displays the revision hash of the gem repository" do - build_git "foo", "1.0", :path => lib_path("foo") + build_git "foo", "1.0", path: lib_path("foo") - install_gemfile <<-G, :verbose => true - source "#{file_uri_for(gem_repo1)}" - gem "foo", :git => "#{file_uri_for(lib_path("foo"))}" + install_gemfile <<-G, verbose: true + source "https://gem.repo1" + gem "foo", :git => "#{lib_path("foo")}" G - expect(out).to include("Using foo 1.0 from #{file_uri_for(lib_path("foo"))} (at main@#{revision_for(lib_path("foo"))[0..6]})") - expect(the_bundle).to include_gems "foo 1.0", :source => "git@#{lib_path("foo")}" + expect(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 correct default branch", :git => ">= 2.28.0" do - build_git "foo", "1.0", :path => lib_path("foo"), :default_branch => "main" + 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") - install_gemfile <<-G, :verbose => true - source "#{file_uri_for(gem_repo1)}" - gem "foo", :git => "#{file_uri_for(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 #{file_uri_for(lib_path("foo"))} (at main@#{revision_for(lib_path("foo"))[0..6]})") - expect(the_bundle).to include_gems "foo 1.0", :source => "git@#{lib_path("foo")}" + expect(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: "non-standard" + + install_gemfile <<-G, verbose: true + source "https://gem.repo1" + gem "foo", :git => "#{lib_path("foo")}" + G + + expect(out).to include("Using foo 1.0 from #{lib_path("foo")} (at non-standard@#{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 ref of the gem repository when using branch~num as a ref" do skip "maybe branch~num notation doesn't work on Windows' git" if Gem.win_platform? - build_git "foo", "1.0", :path => lib_path("foo") + build_git "foo", "1.0", path: lib_path("foo") rev = revision_for(lib_path("foo"))[0..6] - update_git "foo", "2.0", :path => lib_path("foo"), :gemspec => true + update_git "foo", "2.0", path: lib_path("foo"), gemspec: true rev2 = revision_for(lib_path("foo"))[0..6] - update_git "foo", "3.0", :path => lib_path("foo"), :gemspec => true + update_git "foo", "3.0", path: lib_path("foo"), gemspec: true - install_gemfile <<-G, :verbose => true - source "#{file_uri_for(gem_repo1)}" - gem "foo", :git => "#{file_uri_for(lib_path("foo"))}", :ref => "main~2" + install_gemfile <<-G, verbose: true + 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(the_bundle).to include_gems "foo 1.0", :source => "git@#{lib_path("foo")}" + 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 + 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(the_bundle).to include_gems "foo 2.0", :source => "git@#{lib_path("foo")}" + bundle :update, all: true, verbose: true + expect(out).to include("Using foo 2.0 (was 1.0) from #{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!") @@ -81,15 +94,15 @@ RSpec.describe "bundle install" do it "allows multiple gems from the same git source" do build_repo2 do - build_lib "foo", "1.0", :path => lib_path("gems/foo") - build_lib "zebra", "2.0", :path => lib_path("gems/zebra") - build_git "gems", :path => lib_path("gems"), :gemspec => false + build_lib "foo", "1.0", path: lib_path("gems/foo") + build_lib "zebra", "2.0", path: lib_path("gems/zebra") + build_git "gems", path: lib_path("gems"), gemspec: false end install_gemfile <<-G - 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" @@ -98,5 +111,259 @@ RSpec.describe "bundle install" do bundle "info zebra" expect(out).to include("* zebra (2.0 #{revision_for(lib_path("gems"))[0..6]})") end + + it "should always sort dependencies in the same order" do + # This Gemfile + lockfile had a problem where the first + # `bundle install` would change the order, but the second would + # change it back. + + # NOTE: both gems MUST have the same path! It has to be two gems in one repo. + + test = build_git "test", "1.0.0", path: lib_path("test-and-other") + other = build_git "other", "1.0.0", path: lib_path("test-and-other") + test_ref = test.ref_for("HEAD") + other_ref = other.ref_for("HEAD") + + gemfile <<-G + source "https://gem.repo1" + + gem "test", git: #{test.path.to_s.inspect} + gem "other", ref: #{other_ref.inspect}, git: #{other.path.to_s.inspect} + G + + lockfile <<-L + GIT + remote: #{test.path} + revision: #{test_ref} + specs: + test (1.0.0) + + GIT + remote: #{other.path} + revision: #{other_ref} + ref: #{other_ref} + specs: + other (1.0.0) + + GEM + remote: https://gem.repo1/ + specs: + + PLATFORMS + ruby + + DEPENDENCIES + other! + test! + + BUNDLED WITH + #{Bundler::VERSION} + L + + # If GH#6743 is present, the first `bundle install` will change the + # lockfile, by flipping the order (`other` would be moved to the top). + # + # The second `bundle install` would then change the lockfile back + # to the original. + # + # The fix makes it so it may change it once, but it will not change + # it a second time. + # + # So, we run `bundle install` once, and store the value of the + # modified lockfile. + bundle :install + modified_lockfile = lockfile + + # If GH#6743 is present, the second `bundle install` would change the + # lockfile back to what it was originally. + # + # This `expect` makes sure it doesn't change a second time. + bundle :install + expect(lockfile).to eq(modified_lockfile) + + expect(out).to include("Bundle complete!") + end + + it "allows older revisions of git source when clean true" do + build_git "foo", "1.0", path: lib_path("foo") + rev = revision_for(lib_path("foo")) + + bundle_config "path vendor/bundle" + bundle_config "clean true" + install_gemfile <<-G, verbose: true + source "https://gem.repo1" + gem "foo", :git => "#{lib_path("foo")}" + G + + expect(out).to include("Using foo 1.0 from #{lib_path("foo")} (at main@#{rev[0..6]})") + expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}" + + old_lockfile = lockfile + + update_git "foo", "2.0", path: lib_path("foo"), gemspec: true + rev2 = revision_for(lib_path("foo")) + + bundle :update, all: true, verbose: true + expect(out).to include("Using foo 2.0 (was 1.0) from #{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 #{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 be34d8b5bc..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" + 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" + 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 + 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 - install_gemfile <<-G, :artifice => "compact_index" + 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" + 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 + bundle :install, artifice: "compact_index_no_gem" + # 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" + bundle :install, artifice: "compact_index_no_gem" + # 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 + bundle :install, artifice: "compact_index" + 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 + bundle :install, artifice: "compact_index" + 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 - bundle :install, :artifice => "compact_index_no_gem", :raise_on_error => false + 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 - bundle :install, :artifice => "compact_index_no_gem", :raise_on_error => false + 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 - install_gemfile <<-G, :artifice => "compact_index" + 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" + 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,22 +235,20 @@ RSpec.describe "global gem caching" do G # Neither gem is installed and both are in the global cache - expect(the_bundle).not_to include_gems "rack 1.0.0", :dir => bundled_app2 - expect(the_bundle).not_to include_gems "activesupport 2.3.5", :dir => bundled_app2 - expect(source_global_cache("rack-1.0.0.gem")).to exist + 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("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 # from the server - bundle :install, :artifice => "compact_index_no_gem", :dir => bundled_app2 + bundle :install, artifice: "compact_index_no_gem", dir: bundled_app2 # activesupport is installed and both are in the global cache - simulate_bundler_version_when_missing_prerelease_default_gem_activation do - expect(the_bundle).not_to include_gems "rack 1.0.0", :dir => bundled_app2 - expect(the_bundle).to include_gems "activesupport 2.3.5", :dir => bundled_app2 - end + expect(the_bundle).not_to include_gems "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 @@ -207,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")}" @@ -221,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") @@ -234,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 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 bd5385b265..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 :install, :dir => dir + 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,77 +121,77 @@ 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 set_bundle_path(type, "vendor") FileUtils.mkdir_p bundled_app("lol") - bundle :install, :dir => bundled_app("lol") + bundle :install, dir: bundled_app("lol") - expect(bundled_app("vendor", Bundler.ruby_scope, "gems/rack-1.0.0")).to be_directory - expect(the_bundle).to include_gems "rack 1.0.0" + 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 - build_lib "very_simple_binary", "1.0.0", :to_system => true do |s| + build_lib "very_simple_binary", "1.0.0", to_system: true do |s| s.write "lib/very_simple_binary.rb", "raise 'FAIL'" 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" + 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 + 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 expect(vendored_gems("extensions")).to be_directory - expect(the_bundle).to include_gems "very_simple_binary 1.0", :source => "remote1" + expect(the_bundle).to include_gems "very_simple_binary 1.0", source: "remote1" end end @@ -214,12 +202,12 @@ 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 :install, :raise_on_error => false + bundle_config "path bundle" + bundle :install, raise_on_error: false expect(err).to include("file already exists") end 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 a936b2b536..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 43c3069c4e..e7f64dc227 100644 --- a/spec/bundler/install/security_policy_spec.rb +++ b/spec/bundler/install/security_policy_spec.rb @@ -9,30 +9,30 @@ 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 it "will work after you try to deploy without a lock" do - bundle "install --deployment", :raise_on_error => false + bundle "install --deployment", raise_on_error: false bundle :install - expect(the_bundle).to include_gems "rack 1.0", "signed_gem 1.0" + expect(the_bundle).to include_gems "myrack 1.0", "signed_gem 1.0" end it "will fail when given invalid security policy" do - bundle "install --trust-policy=InvalidPolicyName", :raise_on_error => false + bundle "install --trust-policy=InvalidPolicyName", raise_on_error: false expect(err).to include("RubyGems doesn't know about trust policy") end it "will fail with High Security setting due to presence of unsigned gem" do - bundle "install --trust-policy=HighSecurity", :raise_on_error => false + bundle "install --trust-policy=HighSecurity", raise_on_error: false expect(err).to include("security policy didn't allow") end it "will fail with Medium Security setting due to presence of unsigned gem" do - bundle "install --trust-policy=MediumSecurity", :raise_on_error => false + bundle "install --trust-policy=MediumSecurity", raise_on_error: false expect(err).to include("security policy didn't allow") end @@ -45,18 +45,18 @@ 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 it "will fail with High Security setting, gem is self-signed" do - bundle "install --trust-policy=HighSecurity", :raise_on_error => false + bundle "install --trust-policy=HighSecurity", raise_on_error: false expect(err).to include("security policy didn't allow") end it "will fail with Medium Security setting, gem is self-signed" do - bundle "install --trust-policy=MediumSecurity", :raise_on_error => false + bundle "install --trust-policy=MediumSecurity", raise_on_error: false expect(err).to include("security policy didn't allow") end diff --git a/spec/bundler/install/yanked_spec.rb b/spec/bundler/install/yanked_spec.rb index dc054b50bb..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) @@ -22,20 +20,43 @@ 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)}" + install_gemfile <<-G, raise_on_error: false + 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)}" + install_gemfile <<-G, raise_on_error: false + 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 + bundle :list, raise_on_error: false - expect(err).to include("Could not find rack-0.9.1 in locally installed gems") - 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 locally installed gems") + bundle :list, raise_on_error: false + 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 + bundle :list, raise_on_error: false - expect(err).to include("Could not find rack-0.9.1, rack_middleware-1.0 in locally installed gems") + 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 1c1f6fa93d..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,17 +27,21 @@ 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 - bundle "lock --update foo", :raise_on_error => false + bundle "lock --update foo", env: { "LANG" => "en" }, raise_on_error: false expect(err).to include("Revision bad does not exist in the repository") end it "prints a proper error when installing a Gemfile with a locked ref that does not exist" do + install_gemfile_with_foo_as_a_git_dependency + lockfile <<~L GIT remote: #{lib_path("foo-1.0")} @@ -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,15 +60,17 @@ RSpec.describe "bundle lock with git gems" do foo! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L - bundle "install", :raise_on_error => false + bundle "install", raise_on_error: false expect(err).to include("Revision #{"a" * 40} does not exist in the repository") end it "locks a git source to the current ref" do + install_gemfile_with_foo_as_a_git_dependency + update_git "foo" bundle :install @@ -73,13 +83,17 @@ 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" } + bundle :install, env: { "BUNDLE_PATH" => "foo" } expect(err).to be_empty end it "properly fetches a git source locked to an unreachable ref" do + install_gemfile_with_foo_as_a_git_dependency + # Create a commit and make it unreachable git "checkout -b foo ", lib_path("foo-1.0") unreachable_sha = update_git("foo").ref_for("HEAD") @@ -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 dd656ae022..654ac02aa7 100644 --- a/spec/bundler/lock/lockfile_spec.rb +++ b/spec/bundler/lock/lockfile_spec.rb @@ -6,26 +6,30 @@ RSpec.describe "the lockfile format" do end it "generates a simple lockfile for a single source, gem" do + checksums = checksums_section_when_enabled do |c| + c.checksum(gem_repo2, "myrack", "1.0.0") + end + install_gemfile <<-G - source "#{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 @@ -39,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)}" + install_gemfile <<-G, verbose: true, env: { "BUNDLER_VERSION" => Bundler::VERSION } + 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.") @@ -65,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}.") @@ -110,222 +120,296 @@ 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" - + 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 - - 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 + 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" + + source "https://localgemserver.test/" do - gem "rack-obama" + end + + source "https://user:pass@othergemserver.test/" do + gem "myrack-obama", ">= 1.0" + end G + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "myrack", "1.0.0" + c.checksum gem_repo2, "myrack-obama", "1.0" + end + expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo1/ + specs: + + GEM + remote: https://localgemserver.test/ + specs: + + GEM + remote: https://othergemserver.test/ 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 - + 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" - gem "rack-obama", ">= 1.0" + source "http://localgemserver.test/" do + + end + + source "http://user:pass@othergemserver.test/" do + gem "myrack-obama", ">= 1.0" + end G - expect(lockfile).to eq <<~G + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "myrack", "1.0.0" + c.checksum gem_repo2, "myrack-obama", "1.0" + end + + lockfile_without_credentials = <<~L GEM - remote: #{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 - expect(lockfile).to eq <<~G - GEM - remote: #{file_uri_for(gem_repo1)}/ - specs: + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "myrack", "1.0.0" + c.checksum gem_repo2, "myrack-obama", "1.0" + end + lockfile_with_credentials = <<~L GEM remote: http://localgemserver.test/ specs: @@ -333,30 +417,45 @@ RSpec.describe "the lockfile format" do GEM remote: http://user:pass@othergemserver.test/ specs: - rack (1.0.0) - rack-obama (1.0) - rack + myrack (1.0.0) + myrack-obama (1.0) + myrack + + GEM + remote: https://gem.repo1/ + specs: 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_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) @@ -367,9 +466,9 @@ RSpec.describe "the lockfile format" do DEPENDENCIES 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" @@ -379,10 +478,14 @@ 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_enabled do |c| + c.no_checksum "foo", "1.0" + end + expect(lockfile).to eq <<~G GIT remote: #{lib_path("foo-1.0")} @@ -391,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 @@ -399,23 +502,23 @@ RSpec.describe "the lockfile format" do DEPENDENCIES foo! - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end it "does not asplode when a platform specific dependency is present and the Gemfile has not been resolved on that platform" do - build_lib "omg", :path => lib_path("omg") + build_lib "omg", path: lib_path("omg") gemfile <<-G - source "#{file_uri_for(gem_repo2)}/" + source "https://gem.repo2/" platforms :#{not_local_tag} do gem "omg", :path => "#{lib_path("omg")}" end - gem "rack" + gem "myrack" G lockfile <<-L @@ -425,30 +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_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 @@ -462,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 @@ -470,18 +577,22 @@ RSpec.describe "the lockfile format" do DEPENDENCIES foo! - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end it "generates a lockfile with a ref for a single pinned source, git gem with a branch requirement" do git = build_git "foo" - update_git "foo", :branch => "omg" + update_git "foo", branch: "omg" + + 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 @@ -494,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 @@ -502,18 +613,22 @@ RSpec.describe "the lockfile format" do DEPENDENCIES foo! - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end it "generates a lockfile with a ref for a single pinned source, git gem with a tag requirement" do git = build_git "foo" - update_git "foo", :tag => "omg" + update_git "foo", tag: "omg" + + 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 @@ -526,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 @@ -534,17 +649,107 @@ RSpec.describe "the lockfile format" do DEPENDENCIES foo! - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end + it "is conservative with dependencies of git gems" do + build_repo4 do + build_gem "orm_adapter", "0.4.1" + build_gem "orm_adapter", "0.5.0" + end + + FileUtils.mkdir_p lib_path("ckeditor/lib") + + @remote = build_git("ckeditor_remote", bare: true) + + build_git "ckeditor", path: lib_path("ckeditor") do |s| + s.write "lib/ckeditor.rb", "CKEDITOR = '4.0.7'" + s.version = "4.0.7" + s.add_dependency "orm_adapter" + end + + update_git "ckeditor", path: lib_path("ckeditor"), remote: @remote.path + update_git "ckeditor", path: lib_path("ckeditor"), tag: "v4.0.7" + old_git = update_git "ckeditor", path: lib_path("ckeditor"), push: "v4.0.7" + + update_git "ckeditor", path: lib_path("ckeditor"), gemspec: true do |s| + s.write "lib/ckeditor.rb", "CKEDITOR = '4.0.8'" + s.version = "4.0.8" + s.add_dependency "orm_adapter" + end + update_git "ckeditor", path: lib_path("ckeditor"), tag: "v4.0.8" + + new_git = update_git "ckeditor", path: lib_path("ckeditor"), push: "v4.0.8" + + gemfile <<-G + source "https://gem.repo4" + gem "ckeditor", :git => "#{@remote.path}", :tag => "v4.0.8" + G + + lockfile <<~L + GIT + remote: #{@remote.path} + revision: #{old_git.ref_for("v4.0.7")} + tag: v4.0.7 + specs: + ckeditor (4.0.7) + orm_adapter + + GEM + remote: https://gem.repo4/ + specs: + orm_adapter (0.4.1) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ckeditor! + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "lock" + + # Bumps the git gem, but keeps its dependency locked + expect(lockfile).to eq <<~L + GIT + remote: #{@remote.path} + revision: #{new_git.ref_for("v4.0.8")} + tag: v4.0.8 + specs: + ckeditor (4.0.8) + orm_adapter + + GEM + remote: https://gem.repo4/ + specs: + orm_adapter (0.4.1) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ckeditor! + + BUNDLED WITH + #{Bundler::VERSION} + L + end + it "serializes pinned path sources to the lockfile" do build_lib "foo" + checksums = checksums_section_when_enabled do |c| + c.no_checksum "foo", "1.0" + end + install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "foo", :path => "#{lib_path("foo-1.0")}" G @@ -555,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 @@ -563,9 +768,9 @@ RSpec.describe "the lockfile format" do DEPENDENCIES foo! - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -573,13 +778,16 @@ RSpec.describe "the lockfile format" 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" + checksums = checksums_section_when_enabled do |c| + c.no_checksum "foo", "1.0" + end + bundle :cache - bundle :install, :local => true + bundle :install, local: true expect(lockfile).to eq <<~G PATH @@ -588,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 @@ -596,9 +804,9 @@ RSpec.describe "the lockfile format" do DEPENDENCIES foo! - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -606,10 +814,16 @@ RSpec.describe "the lockfile format" do build_lib "foo" bar = build_git "bar" + checksums = checksums_section_when_enabled do |c| + c.no_checksum "foo", "1.0" + c.no_checksum "bar", "1.0" + c.checksum gem_repo2, "myrack", "1.0.0" + end + install_gemfile <<-G - source "#{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 @@ -627,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} @@ -637,82 +851,104 @@ 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_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_enabled do |c| + c.checksum gem_repo2, "actionpack", "2.3.2" + c.checksum gem_repo2, "activesupport", "2.3.2" + c.checksum gem_repo2, "myrack", "1.0.0" + c.checksum gem_repo2, "myrack-obama", "1.0" + c.checksum gem_repo2, "thin", "1.0" + end + expect(lockfile).to eq <<~G GEM - remote: #{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_enabled do |c| + c.checksum gem_repo2, "actionmailer", "2.3.2" + c.checksum gem_repo2, "actionpack", "2.3.2" + c.checksum gem_repo2, "activerecord", "2.3.2" + c.checksum gem_repo2, "activeresource", "2.3.2" + c.checksum gem_repo2, "activesupport", "2.3.2" + c.checksum gem_repo2, "rails", "2.3.2" + c.checksum gem_repo2, "rake", rake_version + end + expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: actionmailer (2.3.2) activesupport (= 2.3.2) @@ -728,17 +964,17 @@ RSpec.describe "the lockfile format" do actionpack (= 2.3.2) activerecord (= 2.3.2) activeresource (= 2.3.2) - rake (= 13.0.1) - rake (13.0.1) + rake (= #{rake_version}) + rake (#{rake_version}) PLATFORMS #{lockfile_platforms} DEPENDENCIES rails - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -746,21 +982,26 @@ 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 - build_gem "double_deps", "1.0", :skip_validation => true do |s| + # See https://github.com/ruby/rubygems/commit/03dbac93a3396a80db258d9bc63500333c25bd2f + build_gem "double_deps", "1.0", skip_validation: true do |s| s.add_dependency "net-ssh", ">= 1.0.0" s.add_dependency "net-ssh" end end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}/" + source "https://gem.repo2" gem 'double_deps' G + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "double_deps", "1.0" + c.checksum gem_repo2, "net-ssh", "1.0" + end + expect(lockfile).to eq <<~G GEM - remote: #{file_uri_for(gem_repo2)}/ + remote: https://gem.repo2/ specs: double_deps (1.0) net-ssh @@ -772,69 +1013,83 @@ RSpec.describe "the lockfile format" do DEPENDENCIES 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_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_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") + build_lib "foo", path: bundled_app("foo") + + checksums = checksums_section_when_enabled do |c| + c.no_checksum "foo", "1.0" + end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" path "foo" do gem "foo" end @@ -847,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 @@ -855,17 +1110,21 @@ RSpec.describe "the lockfile format" do DEPENDENCIES 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")) + build_lib "foo", path: bundled_app(File.join("..", "foo")) + + checksums = checksums_section_when_enabled do |c| + c.no_checksum "foo", "1.0" + end install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" path "../foo" do gem "foo" end @@ -878,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 @@ -886,22 +1145,26 @@ RSpec.describe "the lockfile format" do DEPENDENCIES 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") + build_lib "foo", path: bundled_app("foo") 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 @@ -909,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 @@ -917,17 +1180,21 @@ RSpec.describe "the lockfile format" do DEPENDENCIES 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_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 @@ -938,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 @@ -946,52 +1213,103 @@ RSpec.describe "the lockfile format" do DEPENDENCIES foo! - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end it "keeps existing platforms in the lockfile" do + checksums = checksums_section_when_enabled do |c| + c.no_checksum "myrack", "1.0.0" + end + lockfile <<-G GEM - remote: #{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, "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")} + #{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 + simulate_platform "arm64-darwin-23" do + build_repo4 do + build_gem "google-protobuf", "3.25.1" + build_gem "google-protobuf", "3.25.1" do |s| + s.platform = "arm64-darwin-23" + end + build_gem "google-protobuf", "3.25.1" do |s| + s.platform = "x64-mingw-ucrt" + s.required_ruby_version = "> #{Gem.ruby_version}" + end + end + + gemfile <<-G + source "https://gem.repo4" + gem "google-protobuf" + G + bundle "lock --add-platform x64-mingw-ucrt" + + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo4, "google-protobuf", "3.25.1" + c.checksum gem_repo4, "google-protobuf", "3.25.1", "arm64-darwin-23" + end + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + google-protobuf (3.25.1) + google-protobuf (3.25.1-arm64-darwin-23) + + PLATFORMS + arm64-darwin-23 + ruby + x64-mingw-ucrt + + DEPENDENCIES + google-protobuf + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + it "persists the spec's specific platform to the lockfile" do build_repo2 do build_gem "platform_specific", "1.0" do |s| @@ -999,226 +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 - expect(lockfile).to eq <<~G - GEM - remote: #{file_uri_for(gem_repo2)}/ - specs: - platform_specific (1.0-universal-java-16) + checksums = checksums_section_when_enabled do |c| + c.checksum gem_repo2, "platform_specific", "1.0", "universal-java-16" + end - PLATFORMS - universal-java-16 + expect(lockfile).to eq <<~G + GEM + remote: https://gem.repo2/ + specs: + platform_specific (1.0-universal-java-16) - DEPENDENCIES - platform_specific + PLATFORMS + universal-java-16 - 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_enabled do |c| + c.checksum(gem_repo2, "activesupport", "2.3.5") + 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_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_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_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" + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo2/" + gem "myrack", "1.0" + gem "myrack", "1.1" G expect(bundled_app_lock).not_to exist - expect(err).to include "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" + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo2/" + gem "myrack" + gem "myrack", :git => "git://hubz.com" G expect(bundled_app_lock).not_to exist - expect(err).to include "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_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_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: - rack_middleware (1.0) + myrack (0.9.1) + myrack_middleware (1.0) + myrack (= 0.9.1) PLATFORMS #{lockfile_platforms} DEPENDENCIES - rack_middleware + myrack_middleware + + BUNDLED WITH + #{Bundler::VERSION} L + end - install_gemfile <<-G, :raise_on_error => false - source "#{file_uri_for(gem_repo2)}" - gem "rack_middleware" + 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(err).to include("Downloading rack_middleware-1.0 revealed dependencies not in the API or the lockfile (#{Gem::Dependency.new("rack", "= 0.9.1")})."). - and include("Either installing with `--full-index` or running `bundle update rack_middleware` should fix the problem.") + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + another_dep_middleware (1.0) + other_dep (= 0.9) + myrack (0.9.1) + myrack_middleware (1.0) + myrack (= 0.9.1) + other_dep (0.9) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + another_dep_middleware + myrack_middleware + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "raises a resolution error when lockfile is missing deps, they conflict with other locked deps, and conflicts are not fixable" do + build_repo4 do + build_gem "other_dep", "0.9" + build_gem "other_dep", "1.0" + + build_gem "myrack", "0.9.1" + + build_gem "myrack_middleware", "1.0" do |s| + s.add_dependency "myrack", "= 0.9.1" + s.add_dependency "other_dep", "= 0.9" + end + end + + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + myrack_middleware (1.0) + other_dep (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + myrack_middleware + other_dep + + BUNDLED WITH + #{Bundler::VERSION} + L + + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo4" + gem "myrack_middleware" + gem "other_dep", "1.0" + G + + expect(err).to eq <<~ERROR.strip + Could not find compatible versions + + Because every version of myrack_middleware depends on other_dep = 0.9 + and Gemfile depends on myrack_middleware >= 0, + other_dep = 0.9 is required. + So, because Gemfile depends on other_dep = 1.0, + version solving has failed. + ERROR end it "regenerates a lockfile with no specs" do @@ -1234,7 +1863,7 @@ RSpec.describe "the lockfile format" do lockfile <<-G GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: PLATFORMS @@ -1244,87 +1873,380 @@ 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} + #{lockfile_platforms(generic_default_locked_platform || local_platform, defaults: ["ruby"])} DEPENDENCIES direct_dependency BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end - it "auto-heals when the lockfile is missing dependent specs" do + 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 "minitest-bisect", "1.6.0" do |s| - s.add_dependency "path_expander", "~> 1.1" + build_gem "net-smtp", "0.5.0" do |s| + s.add_dependency "net-protocol" end - build_gem "path_expander", "1.1.1" + 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 "minitest-bisect" + gem "net-smtp" G - # Corrupt lockfile (completely missing path_expander) lockfile <<~L GEM remote: #{file_uri_for(gem_repo4)}/ specs: - minitest-bisect (1.6.0) + net-protocol (0.2.2) + net-smtp (0.5.0) PLATFORMS #{lockfile_platforms} DEPENDENCIES - minitest-bisect + net-smtp BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L - cache_gems "minitest-bisect-1.6.0", "path_expander-1.1.1", :gem_repo => gem_repo4 - bundle :install + bundle "install" expect(lockfile).to eq <<~L GEM remote: #{file_uri_for(gem_repo4)}/ specs: - minitest-bisect (1.6.0) - path_expander (~> 1.1) - path_expander (1.1.1) + net-protocol (0.2.2) + net-smtp (0.5.0) + net-protocol PLATFORMS #{lockfile_platforms} DEPENDENCIES - minitest-bisect + 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} + #{Bundler::VERSION} L end + shared_examples_for "a lockfile missing dependent specs" do + it "auto-heals" do + build_repo4 do + build_gem "minitest-bisect", "1.6.0" do |s| + s.add_dependency "path_expander", "~> 1.1" + end + + build_gem "path_expander", "1.1.1" + end + + gemfile <<~G + source "https://gem.repo4" + gem "minitest-bisect" + G + + # Corrupt lockfile (completely missing path_expander) + lockfile <<~L + GEM + remote: https://gem.repo4/ + specs: + minitest-bisect (1.6.0) + + PLATFORMS + #{platforms} + + DEPENDENCIES + minitest-bisect + + BUNDLED WITH + #{Bundler::VERSION} + L + + cache_gems "minitest-bisect-1.6.0", "path_expander-1.1.1", gem_repo: gem_repo4 + bundle :install + + expect(lockfile).to eq <<~L + GEM + remote: https://gem.repo4/ + specs: + minitest-bisect (1.6.0) + path_expander (~> 1.1) + path_expander (1.1.1) + + PLATFORMS + #{platforms} + + DEPENDENCIES + minitest-bisect + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "with just specific platform" do + let(:platforms) { lockfile_platforms } + + it_behaves_like "a lockfile missing dependent specs" + end + + context "with both ruby and specific platform" do + let(:platforms) { lockfile_platforms("ruby") } + + it_behaves_like "a lockfile missing dependent specs" + end + it "auto-heals when the lockfile is missing specs" do build_repo4 do build_gem "minitest-bisect", "1.6.0" do |s| @@ -1335,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) @@ -1353,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 is missing some gems") + 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) @@ -1374,7 +2296,7 @@ RSpec.describe "the lockfile format" do minitest-bisect BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L end @@ -1387,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 { 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 @@ -1424,12 +2346,10 @@ RSpec.describe "the lockfile format" do File.open(bundled_app_lock, "wb") {|f| f.puts(win_lock) } set_lockfile_mtime_to_known_value - expect { bundle "update", :all => true }.to change { File.mtime(bundled_app_lock) } + expect { bundle "update", all: true }.to change { File.mtime(bundled_app_lock) } expect(File.read(bundled_app_lock)).to match("\r\n") - simulate_bundler_version_when_missing_prerelease_default_gem_activation do - expect(the_bundle).to include_gems "rack 1.2" - end + expect(the_bundle).to include_gems "myrack 1.2" end end @@ -1450,7 +2370,7 @@ RSpec.describe "the lockfile format" do expect do ruby <<-RUBY - require '#{entrypoint}' + require 'bundler' Bundler.setup RUBY end.not_to change { File.mtime(bundled_app_lock) } @@ -1461,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" + install_gemfile <<-G, raise_on_error: false + source "https://gem.repo2/" + gem "myrack" G expect(err).to match(/your Gemfile.lock contains merge conflicts/i) @@ -1490,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 2d6080296f..a2c745b070 100644 --- a/spec/bundler/other/cli_dispatch_spec.rb +++ b/spec/bundler/other/cli_dispatch_spec.rb @@ -2,19 +2,19 @@ RSpec.describe "bundle command names" do it "work when given fully" do - bundle "install", :raise_on_error => false + bundle "install", raise_on_error: false expect(err).to eq("Could not locate Gemfile") - expect(last_command.stdboth).not_to include("Ambiguous command") + expect(stdboth).not_to include("Ambiguous command") end it "work when not ambiguous" do - bundle "ins", :raise_on_error => false + bundle "ins", raise_on_error: false expect(err).to eq("Could not locate Gemfile") - expect(last_command.stdboth).not_to include("Ambiguous command") + 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]") + bundle "in", raise_on_error: false + 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 9b829a2b36..a883eefe06 100644 --- a/spec/bundler/other/ext_spec.rb +++ b/spec/bundler/other/ext_spec.rb @@ -1,65 +1,50 @@ # 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 it "does not explode when called" do - run "Gem.source_index.refresh!", :raise_on_error => false - run "Gem::SourceIndex.new([]).refresh!", :raise_on_error => false + run "Gem.source_index.refresh!", raise_on_error: false + run "Gem::SourceIndex.new([]).refresh!", raise_on_error: false + end +end + +RSpec.describe "Gem::NameTuple" do + describe "#initialize" do + it "creates a Gem::NameTuple with equality regardless of platform type" do + gem_platform = Gem::NameTuple.new "a", v("1"), pl("x86_64-linux") + str_platform = Gem::NameTuple.new "a", v("1"), "x86_64-linux" + expect(gem_platform).to eq(str_platform) + expect(gem_platform.hash).to eq(str_platform.hash) + expect(gem_platform.to_a).to eq(str_platform.to_a) + end + end + + describe "#lock_name" do + it "returns the lock name" do + expect(Gem::NameTuple.new("a", v("1.0.0"), pl("x86_64-linux")).lock_name).to eq("a (1.0.0-x86_64-linux)") + expect(Gem::NameTuple.new("a", v("1.0.0"), "ruby").lock_name).to eq("a (1.0.0)") + expect(Gem::NameTuple.new("a", v("1.0.0")).lock_name).to eq("a (1.0.0)") + end end end diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb index 7b7ceb4586..ab7589d698 100644 --- a/spec/bundler/other/major_deprecation_spec.rb +++ b/spec/bundler/other/major_deprecation_spec.rb @@ -6,107 +6,91 @@ 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 describe "bundle exec --no-keep-file-descriptors" do before do - bundle "exec --no-keep-file-descriptors -e 1", :raise_on_error => false + 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 it "does not print any deprecations" do - bundle :update, :quiet => true, :raise_on_error => false + bundle :update, quiet: true, raise_on_error: false expect(deprecations).to be_empty end end @@ -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 + 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 --local " \ - "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 + 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 --local " \ - "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 + 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 - pending "fails with a helpful error", :bundler => "3" + context "bundle cache --path=" do + before do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + bundle "cache --path=foo", raise_on_error: false + end + + it "should print a deprecation warning" do + expect(err).to include( + "The `--path` flag has been removed because its semantics were unclear. " \ + "Use `bundle config cache_path` to configure the path of your cache of gems, " \ + "and `bundle config path` to configure the path where your gems are installed, " \ + "and stop using this flag" + ) + end + end + + context "bundle cache --frozen" do + before do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + bundle "cache --frozen", raise_on_error: false + end + + it "fails with a helpful error" do + expect(err).to include( + "The `--frozen` flag has been removed because it relied on being " \ + "remembered across bundler invocations, which bundler no longer " \ + "does. Instead please use `bundle config set frozen true`, " \ + "and stop using this flag" + ) + end + end + + 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,165 +419,217 @@ 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 { - "clean" => ["clean", true], - "deployment" => ["deployment", true], - "frozen" => ["frozen", true], - "no-deployment" => ["deployment", false], - "no-prune" => ["no_prune", true], - "path" => ["path", "vendor/bundle"], - "shebang" => ["shebang", "ruby27"], - "system" => ["system", true], - "without" => ["without", "development"], - "with" => ["with", "development"], + "clean" => ["clean", "true"], + "deployment" => ["deployment", "true"], + "frozen" => ["frozen", "true"], + "no-deployment" => ["deployment", "false"], + "no-prune" => ["no_prune", "true"], + "path" => ["path", "'vendor/bundle'"], + "shebang" => ["shebang", "'ruby27'"], + "system" => ["path.system", "true"], + "without" => ["without", "'development'"], + "with" => ["with", "'development'"], }.each do |name, expectations| option_name, value = *expectations flag_name = "--#{name}" + 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 --local #{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: - 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." + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + + BUNDLED WITH + #{Bundler::VERSION} + L + bundle "install", raise_on_error: false + + expect(err).to include( + "This Gemfile contains multiple global sources. " \ + "Each source after the first must include a block to indicate which gems " \ + "should come from that source" ) - expect(deprecations).not_to include( + expect(err).not_to include( "Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. " \ "Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure." ) - bundle "config set --local frozen true" - bundle "install" - - expect(deprecations).to include( - "Your Gemfile contains multiple global sources. " \ - "Using `source` more than once without a block is a security risk, and " \ - "may result in installing unexpected gems. To resolve this warning, use " \ - "a block to indicate which gems should come from the secondary source." + bundle_config "frozen true" + bundle "install", raise_on_error: false + + expect(err).to include( + "This Gemfile contains multiple global sources. " \ + "Each source after the first must include a block to indicate which gems " \ + "should come from that source" ) - expect(deprecations).not_to include( + expect(err).not_to include( "Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. " \ "Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure." ) end - - pending "fails with a helpful error", :bundler => "3" end - context "bundle install in frozen mode with a lockfile with a single rubygems section with multiple remotes" do + context "bundle install with a lockfile with a single rubygems section with multiple remotes" do before do - build_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: - rack (0.9.1) + myrack (0.9.1) PLATFORMS ruby DEPENDENCIES - rack! + myrack! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L + end + + it "shows an error" do + bundle "install", raise_on_error: false - bundle "config set --local frozen true" + expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure.") end + end - it "shows a deprecation", :bundler => "< 3" do - bundle "install" + context "bundle install with a lockfile including X64_MINGW_LEGACY platform" do + before do + gemfile <<~G + source "https://gem.repo1" + gem "rake" + G + + lockfile <<~L + GEM + remote: https://rubygems.org/ + specs: + rake (10.3.2) - 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.") + PLATFORMS + ruby + x64-mingw32 + + DEPENDENCIES + rake + + BUNDLED WITH + #{Bundler::VERSION} + L end - pending "fails with a helpful error", :bundler => "3" + it "warns a helpful error" do + bundle "install", raise_on_error: false + + expect(err).to include("Found x64-mingw32 in lockfile, which is deprecated and will be removed in the future.") + end + end + + context "with a global path source" do + before do + build_lib "foo" + + install_gemfile <<-G, raise_on_error: false + path "#{lib_path("foo-1.0")}" + gem 'foo' + G + end + + it "shows an error" do + expect(err).to include("You can no longer specify a path source by itself") + end end context "when Bundler.setup is run in a ruby script" do 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 - require '#{entrypoint}' + require 'bundler' Bundler.setup Bundler.setup @@ -519,131 +645,154 @@ RSpec.describe "major deprecations" do context "when `bundler/deployment` is required in a ruby script" do before do - ruby(<<-RUBY, :env => env_for_missing_prerelease_default_gem_activation) + ruby <<-RUBY, 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 + + 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 - pending "fails with a helpful error", :bundler => "3" + 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 - describe "deprecating rubocop", :readline do - context "bundle gem --rubocop" do - before do - bundle "gem my_new_gem --rubocop", :raise_on_error => false + context "bundle plugin install --local_git" do + before do + build_git "foo" do |s| + s.write "plugins.rb" end + end - it "prints a deprecation warning", :bundler => "< 3" do - expect(deprecations).to include \ - "--rubocop is deprecated, use --linter=rubocop" - end + it "fails with a helpful message" do + bundle "plugin install foo --local_git #{lib_path("foo-1.0")}", raise_on_error: false + + expect(err).to include "--local_git has been removed, use --git" end + end - context "bundle gem --no-rubocop" 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 --no-rubocop", :raise_on_error => false + bundle "gem my_new_gem --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 \ + "--rubocop has been removed, use --linter=rubocop" end end - context "bundle gem with gem.rubocop set to true" do + context "bundle gem --no-rubocop" do before do - bundle "gem my_new_gem", :env => { "BUNDLE_GEM__RUBOCOP" => "true" }, :raise_on_error => false + bundle "gem my_new_gem --no-rubocop", 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" + 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 false" do - before do - bundle "gem my_new_gem", :env => { "BUNDLE_GEM__RUBOCOP" => "false" }, :raise_on_error => false - 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 - 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 3a7adf4b48..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 efee5fdd23..dcacf764be 100644 --- a/spec/bundler/plugins/install_spec.rb +++ b/spec/bundler/plugins/install_spec.rb @@ -9,21 +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 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") @@ -38,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") @@ -63,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") @@ -75,30 +82,32 @@ 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 "installs when --branch specified" do - bundle "plugin install foo --branch main --source #{file_uri_for(gem_repo2)}" + it "raises an error when when --branch specified" do + bundle "plugin install foo --branch main --source https://gem.repo2", raise_on_error: false - expect(out).to include("Installed plugin foo") + expect(out).not_to include("Installed plugin foo") + + expect(err).to include("--branch can only be used with git sources") end - it "installs when --ref specified" do - bundle "plugin install foo --ref v1.2.3 --source #{file_uri_for(gem_repo2)}" + it "raises an error when --ref specified" do + bundle "plugin install foo --ref v1.2.3 --source https://gem.repo2", raise_on_error: false - expect(out).to include("Installed plugin foo") + 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") @@ -122,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") @@ -135,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.)") @@ -159,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 @@ -178,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") @@ -189,17 +198,48 @@ RSpec.describe "bundler plugin install" do s.write "plugins.rb" end - bundle "plugin install foo --local_git #{lib_path("foo-1.0")}" + bundle "plugin install foo --git #{lib_path("foo-1.0")}" expect(out).to include("Installed plugin foo") plugin_should_be_installed("foo") end + end + + context "path plugins" do + it "installs from a path source" do + build_lib "path_plugin" do |s| + s.write "plugins.rb" + end + bundle "plugin install path_plugin --path #{lib_path("path_plugin-1.0")}" + + expect(out).to include("Installed plugin path_plugin") + plugin_should_be_installed("path_plugin") + end + + it "installs from a relative path source" do + build_lib "path_plugin" do |s| + s.write "plugins.rb" + end + path = lib_path("path_plugin-1.0").relative_path_from(bundled_app) + bundle "plugin install path_plugin --path #{path}" + + expect(out).to include("Installed plugin path_plugin") + plugin_should_be_installed("path_plugin") + end + + it "installs from a relative path source when inside an app" do + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + gemfile "" + + build_lib "ga-plugin" do |s| + s.write "plugins.rb" + end - it "raises an error when both git and local git sources are specified" do - bundle "plugin install foo --local_git /phony/path/project --git git@gitphony.com:/repo/project", :raise_on_error => false + 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 @@ -210,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" @@ -221,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 @@ -250,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 @@ -264,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 @@ -272,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 @@ -302,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 @@ -316,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 @@ -339,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 @@ -357,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 @@ -366,7 +458,7 @@ RSpec.describe "bundler plugin install" do end it "outside the app global plugin is used" do - bundle "shout", :dir => tmp + bundle "shout", dir: tmp expect(out).to eq("global_one") end end diff --git a/spec/bundler/plugins/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 9d153b6063..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,9 +67,13 @@ RSpec.describe "real source plugins" do expect(the_bundle).to include_gems("a-path-gem 1.0") end - it "writes to lock file" do + it "writes to lockfile" do bundle "install" + checksums = checksums_section_when_enabled do |c| + c.no_checksum "a-path-gem", "1.0" + end + expect(lockfile).to eq <<~G PLUGIN SOURCE remote: #{lib_path("a-path-gem-1.0")} @@ -78,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 @@ -86,9 +90,9 @@ RSpec.describe "real source plugins" do DEPENDENCIES a-path-gem! - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -106,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 @@ -120,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 @@ -166,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 @@ -176,7 +177,7 @@ RSpec.describe "real source plugins" do a-path-gem! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -319,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 @@ -332,20 +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_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 @@ -353,9 +358,9 @@ RSpec.describe "real source plugins" do DEPENDENCIES ma-gitp-gem! - + #{checksums} BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -364,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 @@ -381,7 +386,7 @@ RSpec.describe "real source plugins" do ma-gitp-gem! BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} G end @@ -413,10 +418,10 @@ RSpec.describe "real source plugins" do end it "updates the deps on change in gemfile" do - update_git "ma-gitp-gem", "1.1", :path => lib_path("ma-gitp-gem-1.0"), :gemspec => true + update_git "ma-gitp-gem", "1.1", path: lib_path("ma-gitp-gem-1.0"), gemspec: true gemfile <<-G - source "#{file_uri_for(gem_repo2)}" # plugin source - source "#{file_uri_for(lib_path("ma-gitp-gem-1.0"))}", :type => :gitp do + 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 @@ -432,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 8180241911..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") @@ -30,9 +30,34 @@ RSpec.describe "bundler plugin uninstall" do plugin_should_not_be_installed("foo") end + it "doesn't wipe out path plugins" do + build_lib "path_plugin" do |s| + s.write "plugins.rb" + end + path = lib_path("path_plugin-1.0") + expect(path).to be_a_directory + + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + + install_gemfile <<-G + source 'https://gem.repo2' + plugin 'path_plugin', :path => "#{path}" + gem 'myrack', '1.0.0' + G + + plugin_should_be_installed("path_plugin") + expect(Bundler::Plugin.index.plugin_path("path_plugin")).to eq path + + bundle "plugin uninstall path_plugin" + expect(out).to include("Uninstalled plugin path_plugin") + plugin_should_not_be_installed("path_plugin") + # the actual gem still exists though + expect(path).to be_a_directory + end + describe "with --all" do it "uninstalls all installed plugins" do - bundle "plugin install foo kung-foo --source #{file_uri_for(gem_repo2)}" + 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 a98815158e..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") @@ -40,16 +43,14 @@ RSpec.describe "The library itself" do "#{filename} has spaces on the EOL on lines #{failing_lines.join(", ")}" end - def check_for_straneous_quotes(filename) - return if File.expand_path(filename) == __FILE__ - + def check_for_extraneous_quotes(filename) failing_lines = [] each_line(filename) do |line, number| - failing_lines << number + 1 if /’/.match?(line) + failing_lines << number + 1 if /\u{2019}/.match?(line) end return if failing_lines.empty? - "#{filename} has an straneous quote on lines #{failing_lines.join(", ")}" + "#{filename} has an extraneous quote on lines #{failing_lines.join(", ")}" end def check_for_expendable_words(filename) @@ -96,19 +97,19 @@ RSpec.describe "The library itself" do expect(error_messages.compact).to be_well_formed end - it "has no estraneous quotes" do + it "has no extraneous quotes" do exempt = /vendor|vcr_cassettes|LICENSE|rbreadline\.diff/ error_messages = [] tracked_files.each do |filename| next if filename&.match?(exempt) - error_messages << check_for_straneous_quotes(filename) + error_messages << check_for_extraneous_quotes(filename) end expect(error_messages.compact).to be_well_formed end 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) @@ -117,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 @@ -140,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 @@ -167,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) @@ -187,15 +187,16 @@ 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 it "ships the correct set of files" do - git_list = git_ls_files(ruby_core? ? "lib/bundler lib/bundler.rb libexec/bundle*" : "lib exe CHANGELOG.md LICENSE.md README.md bundler.gemspec") + git_list = tracked_files.reject {|f| f.start_with?("spec/") } gem_list = loaded_gemspec.files + gem_list.map! {|f| f.sub(%r{\Aexe/}, "libexec/") } if ruby_core? expect(git_list).to match_array(gem_list) end @@ -217,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#=~} } @@ -237,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 14f99bd262..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/double_check_spec.rb b/spec/bundler/realworld/double_check_spec.rb index d7f28d10bb..0741560395 100644 --- a/spec/bundler/realworld/double_check_spec.rb +++ b/spec/bundler/realworld/double_check_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe "double checking sources", :realworld => true do +RSpec.describe "double checking sources", realworld: true do it "finds already-installed gems" do create_file("rails.gemspec", <<-RUBY) Gem::Specification.new do |s| @@ -25,9 +25,9 @@ RSpec.describe "double checking sources", :realworld => true do RUBY cmd = <<-RUBY - require "#{entrypoint}" + require "bundler" require "#{spec_dir}/support/artifice/vcr" - require "#{entrypoint}/inline" + require "bundler/inline" gemfile(true) do source "https://rubygems.org" gem "rails", path: "." diff --git a/spec/bundler/realworld/edgecases_spec.rb b/spec/bundler/realworld/edgecases_spec.rb index 2f465b7b25..391aa0cef6 100644 --- a/spec/bundler/realworld/edgecases_spec.rb +++ b/spec/bundler/realworld/edgecases_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe "real world edgecases", :realworld => true do +RSpec.describe "real world edgecases", realworld: true do def rubygems_version(name, requirement) ruby <<-RUBY require "#{spec_dir}/support/artifice/vcr" @@ -8,14 +8,15 @@ RSpec.describe "real world edgecases", :realworld => true do require "bundler/source/rubygems/remote" require "bundler/fetcher" rubygem = Bundler.ui.silence do - source = Bundler::Source::Rubygems::Remote.new(Bundler::URI("https://rubygems.org")) - fetcher = Bundler::Fetcher.new(source) - index = fetcher.specs([#{name.dump}], nil) + remote = Bundler::Source::Rubygems::Remote.new(Gem::URI("https://rubygems.org")) + source = Bundler::Source::Rubygems.new + fetcher = Bundler::Fetcher.new(remote) + index = fetcher.specs([#{name.dump}], source) requirement = Gem::Requirement.create(#{requirement.dump}) index.search(#{name.dump}).select {|spec| requirement.satisfied_by?(spec.version) }.last end if rubygem.nil? - raise "Could not find #{name} (#{requirement}) on rubygems.org!\n" \ + raise ArgumentError, "Could not find #{name} (#{requirement}) on rubygems.org!\n" \ "Found specs:\n\#{index.send(:specs).inspect}" end puts "#{name} (\#{rubygem.version})" @@ -62,154 +63,8 @@ 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 - install_gemfile <<-G, :standalone => true, :env => { "BUNDLE_FORCE_RUBY_PLATFORM" => "1" } + 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" gem "redis-namespace", "1.6.0" # for a consistent resolution including ruby 2.3.0 @@ -217,300 +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 big gemfile" do - skip "Only for ruby 2.7" unless RUBY_VERSION.start_with?("2.7") - - gemfile <<~G - # frozen_string_literal: true - - source "https://rubygems.org" - - ruby "~> 2.7.7" - - gem "rails" - gem "pg", ">= 0.18", "< 2.0" - gem "goldiloader" - gem "awesome_nested_set" - gem "circuitbox" - gem "passenger" - gem "globalid" - gem "rack-cors" - gem "rails-pg-extras" - gem "linear_regression_trend" - gem "rack-protection" - gem "pundit" - gem "remote_ip_proxy_scrubber" - gem "bcrypt" - gem "searchkick" - gem "excon" - gem "faraday_middleware-aws-sigv4" - gem "typhoeus" - gem "sidekiq" - gem "sidekiq-undertaker" - gem "sidekiq-cron" - gem "storext" - gem "appsignal" - gem "fcm" - gem "business_time" - gem "tzinfo" - gem "holidays" - gem "bigdecimal" - gem "progress_bar" - gem "redis" - gem "hiredis" - gem "state_machines" - gem "state_machines-audit_trail" - gem "state_machines-activerecord" - gem "interactor" - gem "ar_transaction_changes" - gem "redis-rails" - gem "seed_migration" - gem "lograge" - gem "graphiql-rails", group: :development - gem "graphql" - gem "pusher" - gem "rbnacl" - gem "jwt" - gem "json-schema" - gem "discard" - gem "money" - gem "strip_attributes" - gem "validates_email_format_of" - gem "audited" - gem "concurrent-ruby" - gem "with_advisory_lock" - - group :test do - gem "rspec-sidekiq" - gem "simplecov", require: false - end - - group :development, :test do - gem "byebug", platform: :mri - gem "guard" - gem "guard-bundler" - gem "guard-rspec" - gem "rb-fsevent" - gem "rspec_junit_formatter" - gem "rspec-collection_matchers" - gem "rspec-rails" - gem "rspec-retry" - gem "state_machines-rspec" - gem "dotenv-rails" - gem "database_cleaner-active_record" - gem "database_cleaner-redis" - gem "timecop" - end - - gem "factory_bot_rails" - gem "faker" - - group :development do - gem "listen" - gem "sql_queries_count" - gem "rubocop" - gem "rubocop-performance" - gem "rubocop-rspec" - gem "rubocop-rails" - gem "brakeman" - gem "bundler-audit" - gem "solargraph" - gem "annotate" - end - G - - if Bundler.feature_flag.bundler_3_mode? - # Conflicts on bundler version, so we count attempts differently - bundle :lock, :env => { "DEBUG_RESOLVER" => "1" }, :raise_on_error => false - expect(out.split("\n").grep(/backtracking to/).count).to eq(16) - else - bundle :lock, :env => { "DEBUG_RESOLVER" => "1" } - expect(out).to include("Solution found after 7 attempts") - end - end - - it "doesn't hang on tricky gemfile" do - skip "Only for ruby 2.7" unless RUBY_VERSION.start_with?("2.7") - - gemfile <<~G - source 'https://rubygems.org' - - group :development do - gem "puppet-module-posix-default-r2.7", '~> 0.3' - gem "puppet-module-posix-dev-r2.7", '~> 0.3' - gem "beaker-rspec" - gem "beaker-puppet" - gem "beaker-docker" - gem "beaker-puppet_install_helper" - gem "beaker-module_install_helper" - end - G - - bundle :lock, :env => { "DEBUG_RESOLVER" => "1" } - - expect(out).to include("Solution found after 6 attempts") - 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 fdefc14091..bede372b41 100644 --- a/spec/bundler/realworld/ffi_spec.rb +++ b/spec/bundler/realworld/ffi_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe "loading dynamically linked library on a bundle exec context", :realworld => true do +RSpec.describe "loading dynamically linked library on a bundle exec context", realworld: true do it "passes ENV right after argv in memory" do create_file "foo.rb", <<~RUBY require 'ffi' @@ -42,12 +42,12 @@ RSpec.describe "loading dynamically linked library on a bundle exec context", :r } 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 4fbf2d05a7..5687bbd975 100644 --- a/spec/bundler/realworld/fixtures/warbler/Gemfile +++ b/spec/bundler/realworld/fixtures/warbler/Gemfile @@ -2,6 +2,6 @@ source "https://rubygems.org" -gem "demo", :path => "./demo" -gem "jruby-jars", "~> 9.2" -gem "warbler", "~> 2.0" +gem "demo", path: "./demo" +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 60c0055a62..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' header 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/git_spec.rb b/spec/bundler/realworld/git_spec.rb index 3d352626ea..9eff74f1c9 100644 --- a/spec/bundler/realworld/git_spec.rb +++ b/spec/bundler/realworld/git_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe "github source", :realworld => true do +RSpec.describe "github source", realworld: true do it "properly fetches PRs" do install_gemfile <<-G source "https://rubygems.org" diff --git a/spec/bundler/realworld/mirror_probe_spec.rb b/spec/bundler/realworld/mirror_probe_spec.rb deleted file mode 100644 index f2ce477c10..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/parallel_spec.rb b/spec/bundler/realworld/parallel_spec.rb index a1e4f83909..b57fdfd0ee 100644 --- a/spec/bundler/realworld/parallel_spec.rb +++ b/spec/bundler/realworld/parallel_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe "parallel", :realworld => true do +RSpec.describe "parallel", realworld: true do it "installs" do gemfile <<-G source "https://rubygems.org" @@ -9,7 +9,7 @@ RSpec.describe "parallel", :realworld => true do gem 'i18n', '~> 0.6.0' # Because 0.7+ requires Ruby 1.9.3+ G - bundle :install, :jobs => 4, :env => { "DEBUG" => "1" } + bundle :install, jobs: 4, env: { "DEBUG" => "1" } expect(out).to match(/[1-3]: /) @@ -34,7 +34,7 @@ RSpec.describe "parallel", :realworld => true do gem 'i18n', '~> 0.6.0' # Because 0.7+ requires Ruby 1.9.3+ G - bundle :update, :jobs => 4, :env => { "DEBUG" => "1" }, :all => true + bundle :update, jobs: 4, env: { "DEBUG" => "1" }, all: true expect(out).to match(/[1-3]: /) @@ -51,7 +51,7 @@ RSpec.describe "parallel", :realworld => true do gem "diff-lcs" G - bundle :install, :standalone => true, :jobs => 4 + bundle :install, standalone: true, jobs: 4 ruby <<-RUBY $:.unshift File.expand_path("bundle") diff --git a/spec/bundler/realworld/slow_perf_spec.rb b/spec/bundler/realworld/slow_perf_spec.rb index aa8a48fcc7..5d36ba7455 100644 --- a/spec/bundler/realworld/slow_perf_spec.rb +++ b/spec/bundler/realworld/slow_perf_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -RSpec.describe "bundle install with complex dependencies", :realworld => true do +RSpec.describe "bundle install with complex dependencies", realworld: true do it "resolves quickly" do gemfile <<-G source 'https://rubygems.org' @@ -11,7 +11,8 @@ RSpec.describe "bundle install with complex dependencies", :realworld => true do gem "mongoid", ">= 0.10.2" G - expect { bundle "lock" }.to take_less_than(18) # seconds + bundle "lock", env: { "DEBUG_RESOLVER" => "1" } + expect(out).to include("Solution found after 1 attempts") end it "resolves quickly (case 2)" do @@ -28,6 +29,7 @@ RSpec.describe "bundle install with complex dependencies", :realworld => true do gem 'rspec-rails' G - expect { bundle "lock" }.to take_less_than(30) # seconds + bundle "lock", env: { "DEBUG_RESOLVER" => "1" } + 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 f739f8c02b..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 @@ -100,13 +100,22 @@ RSpec.describe "Resolving" do end it "raises an exception if a child dependency is not resolved" do - @index = a_unresovable_child_index + @index = a_unresolvable_child_index dep "chef_app_error" expect do resolve end.to raise_error(Bundler::SolveFailure) end + it "does not try to re-resolve including prereleases if gems involved don't have prereleases" do + @index = a_unresolvable_child_index + dep "chef_app_error" + expect(Bundler.ui).not_to receive(:debug).with("Retrying resolution...", any_args) + expect do + resolve + end.to raise_error(Bundler::SolveFailure) + end + it "raises an exception with the minimal set of conflicting dependencies" do @index = build_index do %w[0.9 1.0 2.0].each {|v| gem("a", v) } @@ -202,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 @@ -229,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 @@ -247,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 @@ -283,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 @@ -315,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 @@ -338,13 +347,76 @@ 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 "myrack", "3.0.0" + + gem "standalone_migrations", "7.1.0" do + dep "myrack", "~> 2.0" + end + + gem "standalone_migrations", "2.0.4" do + dep "standalone_migrations", ">= 2.0.5" + end + + gem "standalone_migrations", "1.0.13" do + dep "myrack", ">= 0" + end + end + + dep "myrack", "~> 3.0" + dep "standalone_migrations" + + 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 a710dfcb28..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,15 +74,15 @@ 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", :rubygems => ">= 3.1.0.pre.1" do + describe "on a linux platform" do # Ruby's platform is *-linux => platform's libc is glibc, so not musl # Ruby's platform is *-linux-musl => platform's libc is musl, so not glibc # Gem's platform is *-linux => gem is glibc + maybe musl compatible @@ -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 731a9921a2..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,88 +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"]}" - simulate_bundler_version_when_missing_prerelease_default_gem_activation do - bundle_exec_ruby bundled_app("source.rb") - end - expect(last_command.stdboth).not_to include(setup_require) + bundle_exec_ruby bundled_app("source.rb") + 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"]}" - simulate_bundler_version_when_missing_prerelease_default_gem_activation do - bundle_exec_ruby bundled_app("source.rb") - end - expect(last_command.stdboth).not_to include("-rbundler/setup") + bundle_exec_ruby bundled_app("source.rb") + 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 @@ -156,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 @@ -207,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') @@ -258,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') @@ -292,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 a11f547648..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 b89fdf2cb1..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,11 +92,21 @@ 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 + bundle "exec rake build", dir: spaced_bundled_app end it "still runs successfully" do @@ -69,9 +116,11 @@ 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 + bundle "exec rake build", dir: bracketed_bundled_app end it "still runs successfully" do @@ -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 9567d2a3c3..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 = ["#{entrypoint}/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 @@ -28,7 +27,7 @@ RSpec.describe "bundler/inline#gemfile" do s.write "lib/four.rb", "puts 'four'" end - build_lib "five", "1.0.0", :no_default => true do |s| + build_lib "five", "1.0.0", no_default: true do |s| s.write "lib/mofive.rb", "puts 'five'" end @@ -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 @@ -57,9 +56,9 @@ RSpec.describe "bundler/inline#gemfile" do expect(out).to eq("two") - script <<-RUBY, :raise_on_error => false + script <<-RUBY, raise_on_error: false gemfile do - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" path "#{lib_path}" do gem "eleven" end @@ -73,29 +72,29 @@ 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" + script <<-RUBY, artifice: "endpoint" gemfile(true) do - source "https://notaserver.com" + source "https://notaserver.test" gem "activesupport", :require => true end RUBY expect(out).to include("Installing activesupport") err_lines = err.split("\n") - err_lines.reject! {|line| line =~ /\.rb:\d+: warning: / } unless RUBY_VERSION < "2.7" + err_lines.reject! {|line| line =~ /\.rb:\d+: warning: / } expect(err_lines).to be_empty end it "lets me use my own ui object" do - script <<-RUBY, :artifice => "endpoint" - require '#{entrypoint}' + script <<-RUBY, artifice: "endpoint" + require 'bundler' class MyBundlerUI < Bundler::UI::Shell def confirm(msg, newline = nil) puts "CONFIRMED!" @@ -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 @@ -113,11 +112,11 @@ RSpec.describe "bundler/inline#gemfile" do end it "has an option for quiet installation" do - script <<-RUBY, :artifice => "endpoint" - require '#{entrypoint}/inline' + script <<-RUBY, artifice: "endpoint" + require 'bundler/inline' gemfile(true, :quiet => true) do - source "https://notaserver.com" + source "https://notaserver.test" gem "activesupport", :require => true end RUBY @@ -126,7 +125,7 @@ RSpec.describe "bundler/inline#gemfile" do end it "raises an exception if passed unknown arguments" do - script <<-RUBY, :raise_on_error => false + script <<-RUBY, raise_on_error: false gemfile(true, :arglebargle => true) do path "#{lib_path}" gem "two" @@ -140,10 +139,10 @@ RSpec.describe "bundler/inline#gemfile" do it "does not mutate the option argument" do script <<-RUBY - require '#{entrypoint}' + require 'bundler' options = { :ui => Bundler::UI::Shell.new } gemfile(false, options) do - source "#{file_uri_for(gem_repo1)}" + 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,46 @@ 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 "https://gem.repo4" + gem "myrack" + end + + require "myrackdep" + puts MYRACKDEP + RUBY + + expect(out).to eq("1.0.0") + expect(err).to be_empty + end + + it "installs subdependencies quietly if necessary when the install option is not set, and multiple sources used" do + build_repo4 do + build_gem "myrack" do |s| + s.add_dependency "myrackdep" + end + + build_gem "myrackdep", "1.0.0" + end + + script <<-RUBY, artifice: "compact_index_extra_api" gemfile do - source "#{file_uri_for(gem_repo4)}" - 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") @@ -196,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 @@ -213,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 @@ -231,73 +255,73 @@ 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 '#{entrypoint}' + 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" - 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 '#{entrypoint}' + 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 '#{entrypoint}' + 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" @@ -314,16 +338,16 @@ RSpec.describe "bundler/inline#gemfile" do end script <<-RUBY - require '#{entrypoint}' + 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 "foo", :git => "#{lib_path("foo-1.0")}" end @@ -339,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 @@ -356,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 @@ -373,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 @@ -390,42 +414,42 @@ 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)}" - gem "rake", "~> 13.0" + source "https://gem.repo1" + gem "rake", "#{rake_version}" end RUBY - expect(out).to include("Installing rake 13.0") + expect(out).to include("Installing rake #{rake_version}") expect(out).not_to include("was 11.3.0") expect(err).to be_empty end it "installs inline gems when frozen is set" do - script <<-RUBY, :env => { "BUNDLE_FROZEN" => "true" } + script <<-RUBY, env: { "BUNDLE_FROZEN" => "true", "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 @@ -436,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 @@ -451,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" @@ -463,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 @@ -488,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? @@ -522,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? @@ -551,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 @@ -566,36 +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 - dependency_installer_loads_fileutils = ruby "require 'rubygems/dependency_installer'; puts $LOADED_FEATURES.grep(/fileutils/)", :raise_on_error => false - skip "does not work if rubygems/dependency_installer loads fileutils, which happens until rubygems 3.2.0" unless dependency_installer_loads_fileutils.empty? + 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" - skip "pathname does not install cleanly on this ruby" if RUBY_VERSION < "2.7.0" + gemfile(true) do + source "https://gem.repo4" - Dir.mkdir tmp("path_without_gemfile") + 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 - 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? + it "does not reset ENV" do + script <<-RUBY + require 'bundler/inline' + + 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 - realworld_system_gems "timeout uri" # this spec uses net/http which requires these default gems + expect(out).to eq("bar") + end - # on prerelease rubies, a required_rubygems_version constraint is added by RubyGems to the resolution, causing Molinillo to load the `set` gem - realworld_system_gems "set --version 1.0.3" if Gem.ruby_version.prerelease? + 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, :dir => tmp("path_without_gemfile"), :env => { "BUNDLER_GEM_DEFAULT_DIR" => system_gem_path.to_s } + script <<-RUBY, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } require "bundler/inline" gemfile(true) do - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo4" + + gem "psych" + gem "stringio" + end + RUBY + + 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 "installs a conflicting default gem and non-default gems together" do + build_repo4 do + build_gem "securerandom", "999" + build_gem "myrack", "1.0.0" + end + + script <<-RUBY, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + gemfile(true) do + source "https://gem.repo4" + gem "securerandom" + gem "myrack" end - require "fileutils" + puts MYRACK + RUBY + + expect(out).to include("Installing securerandom 999") + expect(out).to include("Installing myrack 1.0.0") + expect(out).to include("1.0.0") + expect(err).to be_empty + end + + it "installs a conflicting default gem alongside git sources" do + build_repo4 do + build_gem "securerandom", "999" + end + + build_git "foo", "1.0.0" + + script <<-RUBY, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + gemfile(true) do + source "https://gem.repo4" + gem "securerandom" + gem "foo", :git => #{lib_path("foo-1.0.0").to_s.dump} + end + + puts FOO + RUBY + + expect(out).to include("Installing securerandom 999") + expect(out).to include("1.0.0") + expect(err).to be_empty + end + + it "leaves a lockfile in the same directory as the inline script alone" do + 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 "https://gem.repo1" + + gem "myrack" + end RUBY - expect(err).to eq("The Gemfile specifies no dependencies") + 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 96a22a46cc..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,16 +76,16 @@ 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 ruby <<-RUBY - require "#{entrypoint}" + 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 b31bc4abe8..6d96758956 100644 --- a/spec/bundler/runtime/platform_spec.rb +++ b/spec/bundler/runtime/platform_spec.rb @@ -3,26 +3,26 @@ 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 begin - require '#{entrypoint}' + require 'bundler' Bundler.ui.silence { Bundler.setup } rescue Bundler::GemNotFound => e puts "WIN" @@ -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,55 +48,63 @@ 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 build_repo4 do build_gem "nokogiri", "1.11.1" do |s| s.add_dependency "mini_portile2", "~> 2.5.0" - s.add_dependency "racc", "~> 1.5.2" + s.add_dependency "racca", "~> 1.5.2" end build_gem "nokogiri", "1.11.1" do |s| s.platform = Bundler.local_platform - s.add_dependency "racc", "~> 1.4" + s.add_dependency "racca", "~> 1.4" end build_gem "mini_portile2", "2.5.0" - build_gem "racc", "1.5.2" + build_gem "racca", "1.5.2" + end + + checksums = checksums_section do |c| + c.checksum gem_repo4, "mini_portile2", "2.5.0" + c.checksum gem_repo4, "nokogiri", "1.11.1" + c.checksum gem_repo4, "nokogiri", "1.11.1", Bundler.local_platform + c.checksum gem_repo4, "racca", "1.5.2" end good_lockfile = <<~L GEM - remote: #{file_uri_for(gem_repo4)}/ + remote: https://gem.repo4/ specs: mini_portile2 (2.5.0) nokogiri (1.11.1) mini_portile2 (~> 2.5.0) - racc (~> 1.5.2) + racca (~> 1.5.2) nokogiri (1.11.1-#{Bundler.local_platform}) - racc (~> 1.4) - racc (1.5.2) + racca (~> 1.4) + racca (1.5.2) PLATFORMS #{lockfile_platforms("ruby")} DEPENDENCIES 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 @@ -118,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) @@ -135,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" @@ -147,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) @@ -174,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) @@ -193,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) @@ -274,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 @@ -291,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) @@ -308,7 +316,7 @@ RSpec.describe "Bundler.setup with multi platform stuff" do libv8 BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L bundle "install" @@ -319,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) @@ -336,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 @@ -352,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 @@ -362,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" + build_lib("foo", "1.0", path: bundled_app) do |s| + 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 @@ -403,41 +411,39 @@ 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 requires_platform_specific L - install_gemfile <<-G, :verbose => true - source "#{file_uri_for(gem_repo2)}" + install_gemfile <<-G, verbose: true + 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) @@ -451,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 e59fa564f6..46613286d2 100644 --- a/spec/bundler/runtime/require_spec.rb +++ b/spec/bundler/runtime/require_spec.rb @@ -21,7 +21,7 @@ RSpec.describe "Bundler.require" do s.write "lib/four.rb", "puts 'four'" end - build_lib "five", "1.0.0", :no_default => true do |s| + build_lib "five", "1.0.0", no_default: true do |s| s.write "lib/mofive.rb", "puts 'five'" end @@ -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,13 +130,13 @@ 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 G - run "Bundler.require", :raise_on_error => false + run "Bundler.require", raise_on_error: false expect(err).to match("error while trying to load the gem 'faulty'") expect(err).to match("Gem Internal Error Message") end @@ -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 @@ -187,11 +178,11 @@ RSpec.describe "Bundler.require" do end it "silently passes if the require fails" do - build_lib "bcrypt-ruby", "1.0.0", :no_default => true do |s| + build_lib "bcrypt-ruby", "1.0.0", no_default: true do |s| s.write "lib/brcrypt.rb", "BCrypt = '1.0.0'" end gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" path "#{lib_path}" do gem "bcrypt-ruby" @@ -199,7 +190,7 @@ RSpec.describe "Bundler.require" do G cmd = <<-RUBY - require '#{entrypoint}' + require 'bundler' Bundler.require RUBY ruby(cmd) @@ -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" @@ -323,13 +299,13 @@ RSpec.describe "Bundler.require" do describe "a gem with different requires for different envs" do before(:each) do - build_gem "multi_gem", :to_bundle => true do |s| + build_gem "multi_gem", to_bundle: true do |s| s.write "lib/one.rb", "puts 'ONE'" s.write "lib/two.rb", "puts 'TWO'" end 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" @@ -366,31 +342,30 @@ RSpec.describe "Bundler.require" do describe "with busted gems" do it "should be busted" do - build_gem "busted_require", :to_bundle => true do |s| + build_gem "busted_require", to_bundle: true do |s| s.write "lib/busted_require.rb", "require 'no_such_file_omg'" end 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 new file mode 100644 index 0000000000..f0e0aeacaf --- /dev/null +++ b/spec/bundler/runtime/requiring_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +RSpec.describe "Requiring bundler" do + it "takes care of requiring rubygems when entrypoint is bundler/setup" do + sys_exec("#{Gem.ruby} -I#{lib_dir} -rbundler/setup -e'puts true'", env: { "RUBYOPT" => "--disable=gems" }) + + 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" => "--disable=gems" }) + + 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 700084babf..176c2a3121 100644 --- a/spec/bundler/runtime/self_management_spec.rb +++ b/spec/bundler/runtime/self_management_spec.rb @@ -1,48 +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 + "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" - expect(out).to eq("Removing bundler (#{Bundler::VERSION})") + bundle "clean --force", artifice: nil + expect(out).to eq("Removing bundler (#{current_version})") # App now uses locked version - bundle "-v" - expect(out).to end_with(previous_minor[0] == "2" ? "Bundler version #{previous_minor}" : previous_minor) + bundle "-v", artifice: nil + 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" + 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 @@ -51,20 +78,39 @@ RSpec.describe "Self management", :rubygems => ">= 3.3.0.dev", :realworld => tru # 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 @@ -73,12 +119,12 @@ RSpec.describe "Self management", :rubygems => ">= 3.3.0.dev", :realworld => tru # 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 @@ -88,19 +134,126 @@ RSpec.describe "Self management", :rubygems => ">= 3.3.0.dev", :realworld => tru 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(current_version) + end + + it "installs BUNDLE_VERSION version when using bundle config version x.y.z" do + lockfile_bundled_with(current_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) + 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 "version system" + bundle "install" + expect(out).not_to match(/restarting using that version/) bundle "-v" - expect(out).to eq(Bundler::VERSION[0] == "2" ? "Bundler version #{Bundler::VERSION}" : Bundler::VERSION) + expect(out).to eq(current_version) + end + + it "does not try to install when using bundle config version <dev-version>" do + lockfile_bundled_with(previous_minor) + + bundle_config "version #{previous_minor}.dev" + bundle "install" + expect(out).not_to match(/restarting using that version/) + + bundle "-v" + 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 #{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 @@ -108,15 +261,15 @@ RSpec.describe "Self management", :rubygems => ">= 3.3.0.dev", :realworld => tru 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 9bfcbdaed8..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") @@ -89,16 +89,16 @@ RSpec.describe "Bundler.setup" do end it "handles multiple non-additive invocations" do - ruby <<-RUBY, :raise_on_error => false + ruby <<-RUBY, raise_on_error: false require 'bundler' Bundler.setup(:default, :test) Bundler.setup(:default) - 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,23 +127,24 @@ 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 ruby <<-RUBY require 'bundler' + gem "bundler", "#{Bundler::VERSION}" if #{ruby_core?} Bundler.setup puts $LOAD_PATH RUBY @@ -157,15 +158,15 @@ RSpec.describe "Bundler.setup" do "/gems/actionpack-2.3.2/lib", "/gems/actionmailer-2.3.2/lib", "/gems/activesupport-2.3.2/lib", - "/gems/rake-13.0.1/lib" + "/gems/rake-#{rake_version}/lib" ) end 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" @@ -188,12 +189,12 @@ 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 - require '#{entrypoint}' + require 'bundler' begin Bundler.setup @@ -208,11 +209,11 @@ 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 + ruby <<-R, raise_on_error: false require 'bundler' Bundler.setup @@ -223,19 +224,19 @@ 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 - ruby <<-R, :raise_on_error => false + ruby <<-R, raise_on_error: false require 'bundler' Bundler.setup @@ -246,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) @@ -262,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 @@ -281,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 @@ -302,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 @@ -343,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" @@ -358,20 +385,21 @@ 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 it "does not reject the stdlib from $LOAD_PATH" do substring = "/" + $LOAD_PATH.find {|p| p.include?("vendor_ruby") }.split("/")[2] - run "puts 'worked!'", :env => { "GEM_PATH" => substring } + run "puts 'worked!'", env: { "GEM_PATH" => substring } expect(out).to eq("worked!") end end @@ -380,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) + run "1", raise_on_error: false + 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 @@ -440,7 +468,7 @@ RSpec.describe "Bundler.setup" do break_git! ruby <<-R - require "#{entrypoint}" + require "bundler" begin Bundler.setup @@ -450,126 +478,125 @@ RSpec.describe "Bundler.setup" do end R - run "puts 'FAIL'", :raise_on_error => false + run "puts 'FAIL'", raise_on_error: false expect(err).not_to include "This is not the git you are looking for" end 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 @@ -579,13 +606,13 @@ RSpec.describe "Bundler.setup" do system_gems "activesupport-2.3.5" - expect(the_bundle).to include_gems "activesupport 2.3.2", :groups => :default + expect(the_bundle).to include_gems "activesupport 2.3.2", groups: :default end it "remembers --without and does not bail on bare Bundler.setup" do - 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 @@ -599,20 +626,20 @@ 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 + 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" } + ruby "require 'bundler'; Bundler.setup", env: { "DEBUG" => "1" } expect(out).to include("Assuming that source at `vendor/foo` has not changed since fetching its specs errored") expect(out).to include("Found no changes, using resolution from the lockfile") expect(err).to be_empty @@ -628,26 +655,36 @@ RSpec.describe "Bundler.setup" do end install_gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "depends_on_bundler" G - ruby "require '#{system_gem_path("gems/bundler-9.99.9.beta1/lib/bundler.rb")}'; Bundler.setup", :env => { "DEBUG" => "1" } + ruby "require '#{system_gem_path("gems/bundler-9.99.9.beta1/lib/bundler.rb")}'; Bundler.setup", env: { "DEBUG" => "1" } expect(out).to include("Found no changes, using resolution from the lockfile") expect(out).not_to include("lockfile does not have all gems needed for the current platform") expect(err).to be_empty end + it "doesn't fail in frozen mode when bundler is a Gemfile dependency" do + install_gemfile <<~G + source "https://gem.repo4" + gem "bundler" + G + + bundle "install --verbose", env: { "BUNDLE_FROZEN" => "true" } + expect(err).to be_empty + end + it "doesn't re-resolve when deleting dependencies" do install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rack" + source "https://gem.repo1" + gem "myrack" gem "actionpack" G - install_gemfile <<-G, :verbose => true - source "#{file_uri_for(gem_repo1)}" - gem "rack" + install_gemfile <<-G, verbose: true + source "https://gem.repo1" + gem "myrack" G expect(out).to include("Some dependencies were deleted, using a subset of the resolution from the lockfile") @@ -655,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 @@ -669,19 +706,19 @@ 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 # RubyGems returns loaded_from as a string it "has loaded_from as a string on all specs" do build_git "foo" - build_git "no-gemspec", :gemspec => false + build_git "no-gemspec", gemspec: false install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - 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 @@ -695,38 +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 - puts "WIN" + File.open(File.join(Gem.dir, "specifications", "invalid-ext.gemspec"), "w") do |f| + f.write <<-RUBY +# -*- encoding: utf-8 -*- +# stub: invalid-ext 1.0.0 ruby lib +# stub: a.ext\\0b.ext + +Gem::Specification.new do |s| + s.name = "invalid-ext" + s.version = "1.0.0" + 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, "invalid-ext-1.0.0")) + File.open(File.join(extensions_dir, "invalid-ext-1.0.0", "gem.build_complete"), "w") {} R - expect(out).to eq("WIN") + run <<-R + puts "Success" + R + + 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"] = "" @@ -735,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}" } @@ -746,7 +837,7 @@ end end install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "with_man" G @@ -770,7 +861,7 @@ end end install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + source "https://gem.repo4" gem "with_man" G @@ -783,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 @@ -810,7 +901,7 @@ end end install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" gem "requirepaths", :require => nil G @@ -821,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}' @@ -873,9 +957,9 @@ 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 } + ruby <<-R, env: { "GEM_PATH" => symlinked_gem_home } TracePoint.trace(:class) do |tp| if tp.path.include?("bundler") && !tp.path.start_with?("#{source_root}") puts "OMG. Defining a class from another bundler at \#{tp.path}:\#{tp.lineno}" @@ -890,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") @@ -915,18 +999,18 @@ end it "should resolve paths relative to the Gemfile" do path = bundled_app(File.join("vendor", "foo")) - build_lib "foo", :path => path + build_lib "foo", path: path # If the .gemspec exists, then Bundler handles the path differently. # See Source::Path.load_spec_files for details. 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 - run <<-R, :env => { "BUNDLE_GEMFILE" => bundled_app_gemfile.to_s }, :dir => bundled_app.parent + run <<-R, env: { "BUNDLE_GEMFILE" => bundled_app_gemfile.to_s }, dir: bundled_app.parent require 'foo' R expect(err).to be_empty @@ -936,20 +1020,20 @@ end relative_path = File.join("vendor", Dir.pwd.gsub(/^#{filesystem_root}/, "")) absolute_path = bundled_app(relative_path) FileUtils.mkdir_p(absolute_path) - build_lib "foo", :path => absolute_path + build_lib "foo", path: absolute_path # If the .gemspec exists, then Bundler handles the path differently. # See Source::Path.load_spec_files for details. 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 bundle :install - run <<-R, :env => { "BUNDLE_GEMFILE" => bundled_app_gemfile.to_s }, :dir => bundled_app.parent + run <<-R, env: { "BUNDLE_GEMFILE" => bundled_app_gemfile.to_s }, dir: bundled_app.parent require 'foo' R @@ -959,10 +1043,10 @@ end describe "with git gems that don't have gemspecs" do before :each do - build_git "no_gemspec", :gemspec => false + build_git "no_gemspec", gemspec: false install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "no_gemspec", "1.0", :git => "#{lib_path("no_gemspec-1.0")}" G end @@ -979,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 @@ -991,7 +1075,7 @@ end it "does not pull in system gems" do run <<-R begin; - require 'rack' + require 'myrack' rescue LoadError puts 'WIN' end @@ -1013,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 @@ -1030,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 @@ -1049,7 +1133,7 @@ end describe "with a gemspec that requires other files" do before :each do - build_git "bar", :gemspec => false do |s| + build_git "bar", gemspec: false do |s| s.write "lib/bar/version.rb", %(BAR_VERSION = '1.0') s.write "bar.gemspec", <<-G require_relative 'lib/bar/version' @@ -1065,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 @@ -1079,10 +1163,10 @@ end it "error intelligently if the gemspec has a LoadError" do skip "whitespace issue?" if Gem.win_platform? - ref = update_git "bar", :gemspec => false do |s| + ref = update_git "bar", gemspec: false do |s| s.write "bar.gemspec", "require 'foobarbaz'" end.ref_for("HEAD") - bundle :install, :raise_on_error => false + bundle :install, raise_on_error: false expect(err.lines.map(&:chomp)).to include( a_string_starting_with("[!] There was an error while loading `bar.gemspec`:"), @@ -1099,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 @@ -1112,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 @@ -1125,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 @@ -1144,18 +1228,18 @@ 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 context "is not present" do it "does not change the lock" do lockfile lock_with(nil) - ruby "require '#{entrypoint}/setup'" + ruby "require 'bundler/setup'" expect(lockfile).to eq lock_with(nil) end end @@ -1174,7 +1258,7 @@ end it "does not change the lock" do system_gems "bundler-1.10.1" lockfile lock_with("1.10.1") - ruby "require '#{entrypoint}/setup'" + ruby "require 'bundler/setup'" expect(lockfile).to eq lock_with("1.10.1") end end @@ -1184,17 +1268,22 @@ end let(:ruby_version) { nil } def lock_with(ruby_version = nil) + checksums = checksums_section do |c| + 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 if ruby_version @@ -1204,7 +1293,7 @@ end lock += <<~L BUNDLED WITH - #{Bundler::VERSION} + #{Bundler::VERSION} L lock @@ -1213,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 @@ -1244,9 +1338,7 @@ end describe "with gemified standard libraries" do it "does not load Digest", :ruby_repo do - skip "Only for Ruby 3.0+" unless RUBY_VERSION >= "3.0" - - build_git "bar", :gemspec => false do |s| + build_git "bar", gemspec: false do |s| s.write "lib/bar/version.rb", %(BAR_VERSION = '1.0') s.write "bar.gemspec", <<-G require_relative 'lib/bar/version' @@ -1258,20 +1350,20 @@ 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 - require '#{entrypoint}/setup' + ruby <<-RUBY, artifice: nil + require 'bundler/setup' puts defined?(::Digest) ? "Digest defined" : "Digest undefined" require 'digest' RUBY @@ -1279,9 +1371,9 @@ end end it "does not load Psych" do - gemfile "source \"#{file_uri_for(gem_repo1)}\"" + gemfile "source 'https://gem.repo1'" ruby <<-RUBY - require '#{entrypoint}/setup' + require 'bundler/setup' puts defined?(Psych::VERSION) ? Psych::VERSION : "undefined" require 'psych' puts Psych::VERSION @@ -1292,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" @@ -1302,20 +1394,62 @@ end expect(out).to eq("undefined\nconstant") end + it "does not load uri while reading gemspecs", rubygems: ">= 3.6.0.dev" do + Dir.mkdir bundled_app("test") + + create_file(bundled_app("test/test.gemspec"), <<-G) + Gem::Specification.new do |s| + s.name = "test" + s.version = "1.0.0" + s.summary = "test" + s.authors = ['John Doe'] + s.homepage = 'https://example.com' + end + G + + install_gemfile <<-G + source "https://gem.repo1" + gem "test", path: "#{bundled_app("test")}" + G + + ruby <<-RUBY, artifice: nil + require "bundler/setup" + puts defined?(URI) || "undefined" + require "uri" + puts defined?(URI) || "undefined" + RUBY + 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] - exempts << "uri" if Gem.ruby_version >= Gem::Version.new("2.7") - exempts << "pathname" if Gem.ruby_version >= Gem::Version.new("3.0") - 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 = %w[did_you_mean bundler uri pathname] exempts << "error_highlight" # added in Ruby 3.1 as a default gem exempts << "ruby2_keywords" # added in Ruby 3.1 as a default gem exempts << "syntax_suggest" # added in Ruby 3.2 as a default gem exempts end - let(:activation_warning_hack) { strip_whitespace(<<-RUBY) } + let(:activation_warning_hack) { <<~RUBY } require #{spec_dir.join("support/hax").to_s.dump} Gem::Specification.send(:alias_method, :bundler_spec_activate, :activate) @@ -1335,7 +1469,7 @@ end "-r#{bundled_app("activation_warning_hack.rb")} #{ENV["RUBYOPT"]}" end - let(:code) { strip_whitespace(<<-RUBY) } + let(:code) { <<~RUBY } require "pp" loaded_specs = Gem.loaded_specs.dup #{exemptions.inspect}.each {|s| loaded_specs.delete(s) } @@ -1349,25 +1483,25 @@ 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 } + bundle "exec ruby ./script.rb", env: { "RUBYOPT" => activation_warning_hack_rubyopt } expect(out).to eq("{}") 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 @@ -1376,14 +1510,14 @@ end build_gem "net-http-pipeline", "1.0.1" end - system_gems "net-http-pipeline-1.0.1", :gem_repo => gem_repo4 + system_gems "net-http-pipeline-1.0.1", gem_repo: gem_repo4 gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + 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 @@ -1401,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 @@ -1416,84 +1550,63 @@ 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 - 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 + it "keeps Kernel#gem 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 + 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 + 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(err).to include "private method `require'" - end - - it "takes care of requiring rubygems" do - sys_exec("#{Gem.ruby} -I#{lib_dir} -rbundler/setup -e'puts true'", :env => { "RUBYOPT" => opt_add("--disable=gems", ENV["RUBYOPT"]) }) - - expect(last_command.stdboth).to eq("true") + 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 + ruby <<-RUBY, raise_on_error: false require "bundler/setup" Bundler.require(:test).select! {|d| (d.groups & [:test]).any? } puts Bundler.definition.specs.map(&:name).join(", ") RUBY - expect(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 @@ -1507,9 +1620,9 @@ end end end - system_gems "json-999.999.999", :gem_repo => gem_repo2 + 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" @@ -1520,8 +1633,8 @@ end end end - it "does not undo the Kernel.require decorations", :rubygems => ">= 3.4.6" do - install_gemfile "source \"#{file_uri_for(gem_repo1)}\"" + it "does not undo the Kernel.require decorations", rubygems: ">= 3.4.6" do + install_gemfile "source 'https://gem.repo1'" script = bundled_app("bin/script") create_file(script, <<~RUBY) module Kernel @@ -1541,7 +1654,57 @@ end require "foo" RUBY - sys_exec "#{Gem.ruby} #{script}", :raise_on_error => false + 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 6a7e2891a6..27ddc6a771 100644 --- a/spec/bundler/spec_helper.rb +++ b/spec/bundler/spec_helper.rb @@ -9,20 +9,67 @@ 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" 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 @@ -34,18 +81,23 @@ end RSpec.configure do |config| config.include Spec::Builders + config.include Spec::Checksums config.include Spec::Helpers config.include Spec::Indexes config.include Spec::Matchers config.include Spec::Path config.include Spec::Platforms config.include Spec::Permissions + config.include Spec::Shards # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = ".rspec_status" config.silence_filter_announcements = !ENV["TEST_ENV_NUMBER"].nil? + config.backtrace_exclusion_patterns << + %r{./spec/(spec_helper\.rb|support/.+)} + config.disable_monkey_patching! # Since failures cause us to keep a bunch of long strings in memory, stop @@ -71,32 +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["BUNDLER_NO_OLD_RUBYGEMS_WARNING"] = "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 } @@ -113,7 +175,18 @@ RSpec.configure do |config| reset! end - config.after :suite do - FileUtils.rm_r 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 new file mode 100644 index 0000000000..143b77833d --- /dev/null +++ b/spec/bundler/support/activate.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require "rubygems" +Gem.instance_variable_set(:@ruby, ENV["RUBY"]) if ENV["RUBY"] + +require_relative "path" +bundler_gemspec = Spec::Path.loaded_gemspec +bundler_gemspec.instance_variable_set(:@full_gem_path, Spec::Path.source_root.to_s) +bundler_gemspec.activate if bundler_gemspec.respond_to?(:activate) diff --git a/spec/bundler/support/api_request_limit_hax.rb b/spec/bundler/support/api_request_limit_hax.rb deleted file mode 100644 index 37ff0203b3..0000000000 --- a/spec/bundler/support/api_request_limit_hax.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -if ENV["BUNDLER_SPEC_API_REQUEST_LIMIT"] - require_relative "path" - require "bundler/source" - require "bundler/source/rubygems" - - module Bundler - class Source - class Rubygems < Source - remove_const :API_REQUEST_LIMIT - API_REQUEST_LIMIT = ENV["BUNDLER_SPEC_API_REQUEST_LIMIT"].to_i - end - end - end -end diff --git a/spec/bundler/support/artifice/compact_index_checksum_mismatch.rb b/spec/bundler/support/artifice/compact_index_checksum_mismatch.rb index a6545b9ee4..83b147d2ae 100644 --- a/spec/bundler/support/artifice/compact_index_checksum_mismatch.rb +++ b/spec/bundler/support/artifice/compact_index_checksum_mismatch.rb @@ -4,10 +4,10 @@ require_relative "helpers/compact_index" class CompactIndexChecksumMismatch < CompactIndexAPI get "/versions" do - headers "ETag" => quote("123") + headers "Repr-Digest" => "sha-256=:ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0=:" headers "Surrogate-Control" => "max-age=2592000, stale-while-revalidate=60" content_type "text/plain" - body "" + body "content does not match the checksum" end end diff --git a/spec/bundler/support/artifice/compact_index_concurrent_download.rb b/spec/bundler/support/artifice/compact_index_concurrent_download.rb index 35548f278c..5d55b8a72b 100644 --- a/spec/bundler/support/artifice/compact_index_concurrent_download.rb +++ b/spec/bundler/support/artifice/compact_index_concurrent_download.rb @@ -7,11 +7,12 @@ class CompactIndexConcurrentDownload < CompactIndexAPI versions = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index", "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions") - # Verify the original (empty) content hasn't been deleted, e.g. on a retry - File.binread(versions) == "" || raise("Original file should be present and empty") + # Verify the original content hasn't been deleted, e.g. on a retry + data = File.binread(versions) + data == "created_at" || raise("Original file should be present with expected content") # Verify this is only requested once for a partial download - env["HTTP_RANGE"] || raise("Missing Range header for expected partial download") + env["HTTP_RANGE"] == "bytes=#{data.bytesize - 1}-" || raise("Missing Range header for expected partial download") # Overwrite the file in parallel, which should be then overwritten # after a successful download to prevent corruption 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 new file mode 100644 index 0000000000..6c62166051 --- /dev/null +++ b/spec/bundler/support/artifice/compact_index_etag_match.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require_relative "helpers/compact_index" + +class CompactIndexEtagMatch < CompactIndexAPI + get "/versions" do + raise ArgumentError, "ETag header should be present" unless env["HTTP_IF_NONE_MATCH"] + headers "ETag" => env["HTTP_IF_NONE_MATCH"] + status 304 + body "" + end +end + +require_relative "helpers/artifice" + +Artifice.activate_with(CompactIndexEtagMatch) diff --git a/spec/bundler/support/artifice/compact_index_host_redirect.rb b/spec/bundler/support/artifice/compact_index_host_redirect.rb index 9a711186db..4f82bf3812 100644 --- a/spec/bundler/support/artifice/compact_index_host_redirect.rb +++ b/spec/bundler/support/artifice/compact_index_host_redirect.rb @@ -3,7 +3,7 @@ require_relative "helpers/compact_index" class CompactIndexHostRedirect < CompactIndexAPI - get "/fetch/actual/gem/:id", :host_name => "localgemserver.test" do + get "/fetch/actual/gem/:id", host_name: "localgemserver.test" do redirect "http://bundler.localgemserver.test#{request.path_info}" end 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/compact_index_partial_update.rb b/spec/bundler/support/artifice/compact_index_partial_update.rb index 8c73011346..f111d91ef9 100644 --- a/spec/bundler/support/artifice/compact_index_partial_update.rb +++ b/spec/bundler/support/artifice/compact_index_partial_update.rb @@ -23,7 +23,7 @@ class CompactIndexPartialUpdate < CompactIndexAPI # Verify that a partial request is made, starting from the index of the # final byte of the cached file. unless env["HTTP_RANGE"] == "bytes=#{File.binread(cached_versions_path).bytesize - 1}-" - raise("Range header should be present, and start from the index of the final byte of the cache.") + raise("Range header should be present, and start from the index of the final byte of the cache. #{env["HTTP_RANGE"].inspect}") end etag_response do diff --git a/spec/bundler/support/artifice/compact_index_partial_update_bad_digest.rb b/spec/bundler/support/artifice/compact_index_partial_update_bad_digest.rb new file mode 100644 index 0000000000..ac04336636 --- /dev/null +++ b/spec/bundler/support/artifice/compact_index_partial_update_bad_digest.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require_relative "helpers/compact_index" + +# The purpose of this Artifice is to test that an incremental response is invalidated +# and a second request is issued for the full content. +class CompactIndexPartialUpdateBadDigest < CompactIndexAPI + def partial_update_bad_digest + response_body = yield + if request.env["HTTP_RANGE"] + headers "Repr-Digest" => "sha-256=:#{Digest::SHA256.base64digest("wrong digest on ranged request")}:" + else + headers "Repr-Digest" => "sha-256=:#{Digest::SHA256.base64digest(response_body)}:" + end + headers "Surrogate-Control" => "max-age=2592000, stale-while-revalidate=60" + content_type "text/plain" + requested_range_for(response_body) + end + + get "/versions" do + partial_update_bad_digest do + file = tmp("versions.list") + FileUtils.rm_f(file) + file = CompactIndex::VersionsFile.new(file.to_s) + file.create(gems) + file.contents([], calculate_info_checksums: true) + end + end + + get "/info/:name" do + partial_update_bad_digest do + gem = gems.find {|g| g.name == params[:name] } + CompactIndex.info(gem ? gem.versions : []) + end + end +end + +require_relative "helpers/artifice" + +Artifice.activate_with(CompactIndexPartialUpdateBadDigest) diff --git a/spec/bundler/support/artifice/compact_index_partial_update_no_etag_not_incremental.rb b/spec/bundler/support/artifice/compact_index_partial_update_no_digest_not_incremental.rb index 20546ba4c3..99bae039f0 100644 --- a/spec/bundler/support/artifice/compact_index_partial_update_no_etag_not_incremental.rb +++ b/spec/bundler/support/artifice/compact_index_partial_update_no_digest_not_incremental.rb @@ -2,8 +2,10 @@ require_relative "helpers/compact_index" -class CompactIndexPartialUpdateNoEtagNotIncremental < CompactIndexAPI - def partial_update_no_etag +# The purpose of this Artifice is to test that an incremental response is ignored +# when the digest is not present to verify that the partial response is valid. +class CompactIndexPartialUpdateNoDigestNotIncremental < CompactIndexAPI + def partial_update_no_digest response_body = yield headers "Surrogate-Control" => "max-age=2592000, stale-while-revalidate=60" content_type "text/plain" @@ -11,12 +13,12 @@ class CompactIndexPartialUpdateNoEtagNotIncremental < CompactIndexAPI end get "/versions" do - partial_update_no_etag do + partial_update_no_digest do file = tmp("versions.list") FileUtils.rm_f(file) file = CompactIndex::VersionsFile.new(file.to_s) file.create(gems) - lines = file.contents([], :calculate_info_checksums => true).split("\n") + lines = file.contents([], calculate_info_checksums: true).split("\n") name, versions, checksum = lines.last.split(" ") # shuffle versions so new versions are not appended to the end @@ -25,7 +27,7 @@ class CompactIndexPartialUpdateNoEtagNotIncremental < CompactIndexAPI end get "/info/:name" do - partial_update_no_etag do + partial_update_no_digest do gem = gems.find {|g| g.name == params[:name] } lines = CompactIndex.info(gem ? gem.versions : []).split("\n") @@ -37,4 +39,4 @@ end require_relative "helpers/artifice" -Artifice.activate_with(CompactIndexPartialUpdateNoEtagNotIncremental) +Artifice.activate_with(CompactIndexPartialUpdateNoDigestNotIncremental) diff --git a/spec/bundler/support/artifice/compact_index_range_ignored.rb b/spec/bundler/support/artifice/compact_index_range_ignored.rb new file mode 100644 index 0000000000..2303682c1f --- /dev/null +++ b/spec/bundler/support/artifice/compact_index_range_ignored.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require_relative "helpers/compact_index" + +class CompactIndexRangeIgnored < CompactIndexAPI + # Stub the server to not return 304 so that we don't bypass all the logic + def not_modified?(_checksum) + false + end + + get "/versions" do + cached_versions_path = File.join( + Bundler.rubygems.user_home, ".bundle", "cache", "compact_index", + "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions" + ) + + # Verify a cached copy of the versions file exists + unless File.binread(cached_versions_path).size > 0 + raise("Cached versions file should be present and have content") + end + + # Verify that a partial request is made, starting from the index of the + # final byte of the cached file. + unless env.delete("HTTP_RANGE") + raise("Expected client to write the full response on the first try") + end + + etag_response do + file = tmp("versions.list") + FileUtils.rm_f(file) + file = CompactIndex::VersionsFile.new(file.to_s) + file.create(gems) + file.contents + end + end +end + +require_relative "helpers/artifice" + +Artifice.activate_with(CompactIndexRangeIgnored) diff --git a/spec/bundler/support/artifice/compact_index_strict_basic_authentication.rb b/spec/bundler/support/artifice/compact_index_strict_basic_authentication.rb index fa25c4eca1..96259385e7 100644 --- a/spec/bundler/support/artifice/compact_index_strict_basic_authentication.rb +++ b/spec/bundler/support/artifice/compact_index_strict_basic_authentication.rb @@ -10,7 +10,7 @@ class CompactIndexStrictBasicAuthentication < CompactIndexAPI # Only accepts password == "password" unless env["HTTP_AUTHORIZATION"] == "Basic dXNlcjpwYXNz" - halt 403, "Authentication failed" + halt 401, "Authentication failed" end end end diff --git a/spec/bundler/support/artifice/compact_index_wrong_gem_checksum.rb b/spec/bundler/support/artifice/compact_index_wrong_gem_checksum.rb index acc13a56ff..9bd2ca0a9d 100644 --- a/spec/bundler/support/artifice/compact_index_wrong_gem_checksum.rb +++ b/spec/bundler/support/artifice/compact_index_wrong_gem_checksum.rb @@ -7,7 +7,8 @@ class CompactIndexWrongGemChecksum < CompactIndexAPI etag_response do name = params[:name] gem = gems.find {|g| g.name == name } - checksum = ENV.fetch("BUNDLER_SPEC_#{name.upcase}_CHECKSUM") { "ab" * 22 } + # This generates the hexdigest "2222222222222222222222222222222222222222222222222222222222222222" + checksum = ENV.fetch("BUNDLER_SPEC_#{name.upcase}_CHECKSUM") { "IiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiI=" } versions = gem ? gem.versions : [] versions.each {|v| v.checksum = checksum } CompactIndex.info(versions) diff --git a/spec/bundler/support/artifice/endpoint_500.rb b/spec/bundler/support/artifice/endpoint_500.rb index d8ab6b65bc..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}-*/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/endpoint_host_redirect.rb b/spec/bundler/support/artifice/endpoint_host_redirect.rb index 0efb6cda02..6ce51bed93 100644 --- a/spec/bundler/support/artifice/endpoint_host_redirect.rb +++ b/spec/bundler/support/artifice/endpoint_host_redirect.rb @@ -3,7 +3,7 @@ require_relative "helpers/endpoint" class EndpointHostRedirect < Endpoint - get "/fetch/actual/gem/:id", :host_name => "localgemserver.test" do + get "/fetch/actual/gem/:id", host_name: "localgemserver.test" do redirect "http://bundler.localgemserver.test#{request.path_info}" end diff --git a/spec/bundler/support/artifice/endpoint_mirror_source.rb b/spec/bundler/support/artifice/endpoint_mirror_source.rb index 6ea1a77eca..fed7a746b9 100644 --- a/spec/bundler/support/artifice/endpoint_mirror_source.rb +++ b/spec/bundler/support/artifice/endpoint_mirror_source.rb @@ -4,7 +4,7 @@ require_relative "helpers/endpoint" class EndpointMirrorSource < Endpoint get "/gems/:id" do - if request.env["HTTP_X_GEMFILE_SOURCE"] == "https://server.example.org/" + if request.env["HTTP_X_GEMFILE_SOURCE"] == "https://server.example.org/" && request.env["HTTP_USER_AGENT"].start_with?("bundler") File.binread("#{gem_repo1}/gems/#{params[:id]}") else halt 500 diff --git a/spec/bundler/support/artifice/endpoint_strict_basic_authentication.rb b/spec/bundler/support/artifice/endpoint_strict_basic_authentication.rb index 8ce1bdd4ad..dff360c5c5 100644 --- a/spec/bundler/support/artifice/endpoint_strict_basic_authentication.rb +++ b/spec/bundler/support/artifice/endpoint_strict_basic_authentication.rb @@ -10,7 +10,7 @@ class EndpointStrictBasicAuthentication < Endpoint # Only accepts password == "password" unless env["HTTP_AUTHORIZATION"] == "Basic dXNlcjpwYXNz" - halt 403, "Authentication failed" + halt 401, "Authentication failed" end end end diff --git a/spec/bundler/support/artifice/fail.rb b/spec/bundler/support/artifice/fail.rb index 6286e43fbd..5ddbc4e590 100644 --- a/spec/bundler/support/artifice/fail.rb +++ b/spec/bundler/support/artifice/fail.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -require "net/http" +require_relative "../vendored_net_http" -class Fail < Net::HTTP - # Net::HTTP uses a @newimpl instance variable to decide whether +class Fail < Gem::Net::HTTP + # Gem::Net::HTTP uses a @newimpl instance variable to decide whether # to use a legacy implementation. Since we are subclassing - # Net::HTTP, we must set it + # Gem::Net::HTTP, we must set it @newimpl = true def request(req, body = nil, &block) @@ -17,13 +17,11 @@ class Fail < Net::HTTP end def exception(req) - name = ENV.fetch("BUNDLER_SPEC_EXCEPTION") { "Errno::ENETUNREACH" } - const = name.split("::").reduce(Object) {|mod, sym| mod.const_get(sym) } - const.new("host down: Bundler spec artifice fail! #{req["PATH_INFO"]}") + Errno::ENETUNREACH.new("host down: Bundler spec artifice fail! #{req["PATH_INFO"]}") end end require_relative "helpers/artifice" -# Replace Net::HTTP with our failing subclass +# Replace Gem::Net::HTTP with our failing subclass Artifice.replace_net_http(::Fail) diff --git a/spec/bundler/support/artifice/helpers/artifice.rb b/spec/bundler/support/artifice/helpers/artifice.rb index b8c78614fb..788268295c 100644 --- a/spec/bundler/support/artifice/helpers/artifice.rb +++ b/spec/bundler/support/artifice/helpers/artifice.rb @@ -4,7 +4,7 @@ module Artifice # Activate Artifice with a particular Rack endpoint. # - # Calling this method will replace the Net::HTTP system + # Calling this method will replace the Gem::Net::HTTP system # with a replacement that routes all requests to the # Rack endpoint. # @@ -18,11 +18,11 @@ module Artifice # Deactivate the Artifice replacement. def self.deactivate - replace_net_http(::Net::HTTP) + replace_net_http(::Gem::Net::HTTP) end def self.replace_net_http(value) - ::Net.class_eval do + ::Gem::Net.class_eval do remove_const(:HTTP) const_set(:HTTP, value) end diff --git a/spec/bundler/support/artifice/helpers/compact_index.rb b/spec/bundler/support/artifice/helpers/compact_index.rb index 4df47a9659..e684aa8628 100644 --- a/spec/bundler/support/artifice/helpers/compact_index.rb +++ b/spec/bundler/support/artifice/helpers/compact_index.rb @@ -2,8 +2,9 @@ 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" class CompactIndexAPI < Endpoint helpers do @@ -17,9 +18,10 @@ class CompactIndexAPI < Endpoint def etag_response response_body = yield - checksum = Digest(:MD5).hexdigest(response_body) - return if not_modified?(checksum) - headers "ETag" => quote(checksum) + etag = Digest::MD5.hexdigest(response_body) + headers "ETag" => quote(etag) + return if not_modified?(etag) + headers "Repr-Digest" => "sha-256=:#{Digest::SHA256.base64digest(response_body)}:" headers "Surrogate-Control" => "max-age=2592000, stale-while-revalidate=60" content_type "text/plain" requested_range_for(response_body) @@ -29,17 +31,16 @@ class CompactIndexAPI < Endpoint raise end - def not_modified?(checksum) + def not_modified?(etag) etags = parse_etags(request.env["HTTP_IF_NONE_MATCH"]) - return unless etags.include?(checksum) - headers "ETag" => quote(checksum) + return unless etags.include?(etag) status 304 body "" 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 @@ -66,31 +67,40 @@ 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| gem_versions = versions.map do |spec| - deps = spec.dependencies.select {|d| d.type == :runtime }.map do |d| + deps = spec.runtime_dependencies.map do |d| reqs = d.requirement.requirements.map {|r| r.join(" ") }.join(", ") CompactIndex::Dependency.new(d.name, reqs) end - checksum = begin - Digest(:SHA256).file("#{gem_repo}/gems/#{spec.original_name}.gem").base64digest - rescue StandardError - nil - end - CompactIndex::GemVersion.new(spec.version.version, spec.platform.to_s, checksum, nil, - deps, spec.required_ruby_version.to_s, spec.required_rubygems_version.to_s) + begin + checksum = ENV.fetch("BUNDLER_SPEC_#{name.upcase}_CHECKSUM") do + Digest(:SHA256).file("#{gem_repo}/gems/#{spec.original_name}.gem").hexdigest + end + rescue StandardError + checksum = nil + end + 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 fc0381dc38..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}-*/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,21 +63,21 @@ 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) { - :name => spec.name, - :number => spec.version.version, - :platform => spec.platform.to_s, - :dependencies => spec.dependencies.select {|dep| dep.type == :runtime }.map do |dep| + name: spec.name, + number: spec.version.version, + platform: spec.platform.to_s, + dependencies: spec.runtime_dependencies.map do |dep| [dep.name, dep.requirement.requirements.map {|a| a.join(" ") }.join(", ")] end, } - end.compact + end 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 c4a07812a6..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 "net/http" +require_relative "../../vendored_net_http" module Artifice module Net @@ -16,25 +16,25 @@ module Artifice end end - class HTTP < ::Net::HTTP + class HTTP < ::Gem::Net::HTTP class << self attr_accessor :endpoint end - # Net::HTTP uses a @newimpl instance variable to decide whether + # Gem::Net::HTTP uses a @newimpl instance variable to decide whether # to use a legacy implementation. Since we are subclassing - # Net::HTTP, we must set it + # Gem::Net::HTTP, we must set it @newimpl = true # We don't need to connect, so blank out this method def connect end - # Replace the Net::HTTP request method with a method + # Replace the Gem::Net::HTTP request method with a method # that converts the request into a Rack request and # dispatches it to the Rack endpoint. # - # @param [Net::HTTPRequest] req A Net::HTTPRequest + # @param [Net::HTTPRequest] req A Gem::Net::HTTPRequest # object, or one if its subclasses # @param [optional, String, #read] body This should # be sent as "rack.input". If it's a String, it will @@ -42,7 +42,7 @@ module Artifice # @return [Net::HTTPResponse] # # @yield [Net::HTTPResponse] If a block is provided, - # this method will yield the Net::HTTPResponse to + # this method will yield the Gem::Net::HTTPResponse to # it after the body is read. def request(req, body = nil, &block) rack_request = RackRequest.new(self.class.endpoint) @@ -56,17 +56,17 @@ module Artifice body_stream_contents = req.body_stream.read if req.body_stream response = rack_request.request("#{prefix}#{req.path}", - { :method => req.method, :input => body || req.body || body_stream_contents }) + { method: req.method, input: body || req.body || body_stream_contents }) make_net_http_response(response, &block) end private - # This method takes a Rack response and creates a Net::HTTPResponse + # This method takes a Rack response and creates a Gem::Net::HTTPResponse # Instead of trying to mock HTTPResponse directly, we just convert # the Rack response into a String that looks like a normal HTTP - # response and call Net::HTTPResponse.read_new + # response and call Gem::Net::HTTPResponse.read_new # # @param [Array(#to_i, Hash, #each)] response a Rack response # @return [Net::HTTPResponse] @@ -86,8 +86,8 @@ module Artifice response_string << "" << body - response_io = ::Net::BufferedIO.new(StringIO.new(response_string.join("\n"))) - res = ::Net::HTTPResponse.read_new(response_io) + response_io = ::Gem::Net::BufferedIO.new(StringIO.new(response_string.join("\n"))) + res = ::Gem::Net::HTTPResponse.read_new(response_io) res.reading_body(response_io, true) do yield res if block_given? diff --git a/spec/bundler/support/artifice/vcr.rb b/spec/bundler/support/artifice/vcr.rb index 6a346f1ff9..0bf5ade8f6 100644 --- a/spec/bundler/support/artifice/vcr.rb +++ b/spec/bundler/support/artifice/vcr.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -require "net/http" +require_relative "../vendored_net_http" require_relative "../path" -CASSETTE_PATH = "#{Spec::Path.spec_dir}/support/artifice/vcr_cassettes" -USED_CASSETTES_PATH = "#{Spec::Path.spec_dir}/support/artifice/used_cassettes.txt" +CASSETTE_PATH = "#{Spec::Path.spec_dir}/support/artifice/vcr_cassettes".freeze +USED_CASSETTES_PATH = "#{Spec::Path.spec_dir}/support/artifice/used_cassettes.txt".freeze CASSETTE_NAME = ENV.fetch("BUNDLER_SPEC_VCR_CASSETTE_NAME") { "realworld" } -class BundlerVCRHTTP < Net::HTTP +class BundlerVCRHTTP < Gem::Net::HTTP class RequestHandler attr_reader :http, :request, :body, :response_block def initialize(http, request, body = nil, &response_block) @@ -24,7 +24,7 @@ class BundlerVCRHTTP < 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? @@ -41,8 +41,8 @@ class BundlerVCRHTTP < Net::HTTP def recorded_response File.open(request_pair_paths.last, "rb:ASCII-8BIT") do |response_file| - response_io = ::Net::BufferedIO.new(response_file) - ::Net::HTTPResponse.read_new(response_io).tap do |response| + response_io = ::Gem::Net::BufferedIO.new(response_file) + ::Gem::Net::HTTPResponse.read_new(response_io).tap do |response| response.decode_content = request.decode_content if request.respond_to?(:decode_content) response.uri = request.uri @@ -148,5 +148,5 @@ end require_relative "helpers/artifice" -# Replace Net::HTTP with our VCR subclass +# Replace Gem::Net::HTTP with our VCR subclass Artifice.replace_net_http(BundlerVCRHTTP) diff --git a/spec/bundler/support/artifice/windows.rb b/spec/bundler/support/artifice/windows.rb index 4d90e0a426..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}-*/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 98d8ac23c8..2eade4137b 100644 --- a/spec/bundler/support/build_metadata.rb +++ b/spec/bundler/support/build_metadata.rb @@ -8,22 +8,21 @@ 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, + git_commit_sha: git_commit_sha, + built_at: release_date_for(version, dir: dir), } - replace_build_metadata(build_metadata, dir: dir) # rubocop:disable Style/HashSyntax + replace_build_metadata(build_metadata, dir: dir) end def reset_build_metadata(dir: source_root) build_metadata = { - :release => false, + built_at: nil, } - replace_build_metadata(build_metadata, dir: dir) # rubocop:disable Style/HashSyntax + replace_build_metadata(build_metadata, dir: dir) end private @@ -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 3aa5454b6a..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,21 +26,7 @@ module Spec Gem::Platform.new(platform) end - # Returns a number smaller than the size of the index. Useful for specs that - # need the API request limit to be reached for some reason. - def low_api_request_limit_for(gem_repo) - all_gems = Dir[gem_repo.join("gems/*.gem")] - - all_gem_names = all_gems.map do |file| - File.basename(file, ".gem").match(/\A(?<gem_name>[^-]+)-.*\z/)[:gem_name] - end.uniq - - (all_gem_names - ["bundler"]).size - 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/" @@ -40,28 +35,28 @@ 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| s.executables = "rails" - s.add_dependency "rake", "13.0.1" + s.add_dependency "rake", rake_version s.add_dependency "actionpack", "2.3.2" s.add_dependency "activerecord", "2.3.2" s.add_dependency "actionmailer", "2.3.2" @@ -85,84 +80,66 @@ module Spec s.add_dependency "activesupport", ">= 2.0.0" end - build_gem "rspec", "1.2.7", :no_default => true do |s| + build_gem "rspec", "1.2.7", no_default: true do |s| s.write "lib/spec.rb", "SPEC = '1.2.7'" end - build_gem "rack-test", :no_default => true do |s| - 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" @@ -176,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 :( @@ -203,32 +180,45 @@ module Spec end end - def build_repo2(&blk) - FileUtils.rm_rf gem_repo2 - FileUtils.cp_r gem_repo1, gem_repo2 - update_repo2(&blk) if block_given? + def build_repo2(**kwargs, &blk) + 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(&blk) - FileUtils.rm_rf gem_repo4 - build_repo(gem_repo4, &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 - update_repo gem_repo2 do - yield if block_given? - 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" @@ -241,39 +231,60 @@ module Spec end end - def build_repo(path, &blk) - return if File.directory?(path) - - FileUtils.mkdir_p("#{path}/gems") - - update_repo(path, &blk) + # 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 check_test_gems! - rake_path = Dir["#{Path.base_system_gems}/**/rake*.gem"].first + def build_repo(path, **kwargs, &blk) + return if File.directory?(path) - 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 + FileUtils.mkdir_p("#{path}/gems") - if rake_path.nil? - abort "Your test gems are missing! Run `rm -rf #{tmp}` and try again." - end + update_repo(path,**kwargs, &blk) end - def update_repo(path) - if path == gem_repo1 && caller.first.split(" ").last == "`build_repo`" + def update_repo(path, build_compact_index: true) + 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 - gem_command :generate_index, :dir => path - end + options = { build_compact: build_compact_index } + Gem::Indexer.new(path, options).generate_index ensure @_build_path = nil @_build_repo = nil @@ -302,6 +313,10 @@ module Spec build_with(LibBuilder, name, args, &blk) end + def build_bundler(*args, &blk) + build_with(BundlerBuilder, "bundler", args, &blk) + end + def build_gem(name, *args, &blk) build_with(GemBuilder, name, args, &blk) end @@ -407,6 +422,58 @@ module Spec alias_method :dep, :runtime end + class BundlerBuilder + def initialize(context, name, version) + @context = context + @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-#{@spec.version}" + build_path = (options[:build_path] || @context.tmp) + full_name + bundler_path = build_path + "#{full_name}.gem" + + FileUtils.mkdir_p build_path + + @context.shipped_files.each do |shipped_file| + target_shipped_file = shipped_file + target_shipped_file = shipped_file.sub(/\Alibexec/, "exe") if @context.ruby_core? + target_shipped_file = build_path + target_shipped_file + target_shipped_dir = File.dirname(target_shipped_file) + FileUtils.mkdir_p target_shipped_dir unless File.directory?(target_shipped_dir) + FileUtils.cp File.expand_path(shipped_file, @context.source_root), target_shipped_file, preserve: true + end + + @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, version: @spec.version.to_s) + + 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) + else + FileUtils.mv bundler_path, options[:path] + end + ensure + FileUtils.rm_rf build_path + end + end + class LibBuilder def initialize(context, name, version) @context = context @@ -420,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 @@ -436,24 +504,17 @@ 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" - $extout = "$(topdir)/" + RbConfig::CONFIG["EXTOUT"] - extension_name = "#{name}_c" if extra_lib_dir = with_config("ext-lib") # add extra libpath if --with-ext-lib is @@ -467,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 @@ -478,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 @@ -497,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 @@ -535,7 +593,7 @@ module Spec default_branch = options[:default_branch] || "main" path = options[:path] || _default_path source = options[:source] || "git@#{path}" - super(options.merge(:path => path, :source => source)) + super(options.merge(path: path, source: source)) @context.git("config --global init.defaultBranch #{default_branch}", path) @context.git("init", path) @context.git("add *", path) @@ -549,7 +607,7 @@ module Spec class GitBareBuilder < LibBuilder def _build(options) path = options[:path] || _default_path - super(options.merge(:path => path)) + super(options.merge(path: path)) @context.git("init --bare", path) end end @@ -574,7 +632,7 @@ module Spec _default_files.keys.each do |path| _default_files[path] += "\n#{Builders.constantize(name)}_PREV_REF = '#{current_ref}'" end - super(options.merge(:path => libpath, :gemspec => update_gemspec, :source => source)) + super(options.merge(path: libpath, gemspec: update_gemspec, source: source)) @context.git("commit -am BUMP", libpath) end end @@ -597,25 +655,25 @@ module Spec class GemBuilder < LibBuilder def _build(opts) lib_path = opts[:lib_path] || @context.tmp(".tmp/#{@spec.full_name}") - lib_path = super(opts.merge(:path => lib_path, :no_default => opts[:no_default])) + lib_path = super(opts.merge(path: lib_path, no_default: opts[:no_default])) destination = opts[:path] || _default_path FileUtils.mkdir_p(lib_path.join(destination)) - 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) if opts[:to_system] - @context.system_gems gem_path, :default => opts[:default] + @context.system_gems gem_path, default: opts[:default] elsif opts[:to_bundle] - @context.system_gems gem_path, :path => @context.default_bundle_path + @context.system_gems gem_path, path: @context.default_bundle_path else FileUtils.mv(gem_path, destination) end @@ -635,56 +693,56 @@ module Spec end end - TEST_CERT = <<-CERT.gsub(/^\s*/, "") + 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.gsub(/^\s*/, "") + 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 5f808531ff..aa7b121706 100644 --- a/spec/bundler/support/bundle.rb +++ b/spec/bundler/support/bundle.rb @@ -1,10 +1,7 @@ # frozen_string_literal: true -require "rubygems" -Gem.instance_variable_set(:@ruby, ENV["RUBY"]) if ENV["RUBY"] - require_relative "path" -bundler_gemspec = Spec::Path.loaded_gemspec -bundler_gemspec.instance_variable_set(:@full_gem_path, Spec::Path.source_root) -bundler_gemspec.activate if bundler_gemspec.respond_to?(:activate) -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 new file mode 100644 index 0000000000..7b69bba668 --- /dev/null +++ b/spec/bundler/support/checksums.rb @@ -0,0 +1,135 @@ +# frozen_string_literal: true + +module Spec + module Checksums + class ChecksumsBuilder + attr_reader :bundler_registered + + def initialize(enabled = true, &block) + @enabled = enabled + @checksums = {} + yield self if block_given? + end + + def initialize_copy(original) + super + @checksums = @checksums.dup + end + + def checksum(repo, name, version, platform = Gem::Platform::RUBY, folder = "gems") + @bundler_registered = true if name == "bundler" + + name_tuple = Gem::NameTuple.new(name, version, platform) + 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 + end + + def no_checksum(name, version, platform = Gem::Platform::RUBY) + name_tuple = Gem::NameTuple.new(name, version, platform) + register(name_tuple, nil) + end + + def delete(name, platform = nil) + @checksums.reject! {|k, _| k.name == name && (platform.nil? || k.platform == platform) } + end + + def to_s + return "" unless @enabled + + locked_checksums = @checksums.map do |name_tuple, checksum| + checksum &&= " #{checksum.to_lock}" + " #{name_tuple.lock_name}#{checksum}\n" + end + + "\nCHECKSUMS\n#{locked_checksums.sort.join}" + end + + private + + def register(name_tuple, checksum) + delete(name_tuple.name, name_tuple.platform) + @checksums[name_tuple] = checksum + end + end + + def checksums_section(enabled = true, bundler_checksum: true, &block) + ChecksumsBuilder.new(enabled, &block).tap do |builder| + next if builder.bundler_registered || !bundler_checksum + + next if Bundler::VERSION.to_s.end_with?(".dev") + builder.checksum(system_gem_path, "bundler", Bundler::VERSION, Gem::Platform::RUBY, "cache") + end + end + + def checksums_section_when_enabled(target_lockfile = nil, &block) + begin + enabled = (target_lockfile || lockfile).match?(/^CHECKSUMS$/) + rescue Errno::ENOENT + enabled = true + end + checksums_section(enabled, &block) + end + + def checksum_to_lock(*args) + checksums_section(true, bundler_checksum: false) do |c| + c.checksum(*args) + end.to_s.sub(/^CHECKSUMS\n/, "").strip + end + + def checksum_digest(*args) + checksum_to_lock(*args).split(Bundler::Checksum::ALGO_SEPARATOR, 2).last + end + + # if prefixes is given, removes all checksums where the line + # has any of the prefixes on the line before the checksum + # otherwise, removes all checksums from the lockfile + def remove_checksums_from_lockfile(lockfile, *prefixes) + head, remaining = lockfile.split(/^CHECKSUMS$/, 2) + return lockfile unless remaining + checksums, tail = remaining.split("\n\n", 2) + + prefixes = + if prefixes.empty? + nil + else + /(#{prefixes.map {|p| Regexp.escape(p) }.join("|")})/ + end + + checksums = checksums.each_line.map do |line| + if prefixes.nil? || line.match?(prefixes) + line.gsub(/ sha256=[a-f0-9]{64}/i, "") + else + line + end + end + + head.concat( + "CHECKSUMS", + checksums.join, + "\n\n", + tail + ) + end + + def remove_checksums_section_from_lockfile(lockfile) + head, remaining = lockfile.split(/^CHECKSUMS$/, 2) + return lockfile unless remaining + _checksums, tail = remaining.split("\n\n", 2) + head.concat(tail) + end + + 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 68e5c56c75..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, :stdout, :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 @@ -11,6 +40,14 @@ module Spec @stdboth ||= [stderr, stdout].join("\n").strip end + def stdout + normalize(original_stdout) + end + + def stderr + normalize(original_stderr) + end + def to_s_verbose [ to_s, @@ -29,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 78545d2e64..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,25 +14,29 @@ class RequirementChecker < Proc attr_accessor :provided def inspect - "\"!= #{provided}\"" + "\"#{provided}\"" end end -RSpec.configure do |config| - config.filter_run_excluding :realworld => true - - git_version = Bundler::Source::Git::GitProxy.new(nil, nil).version +git_version = Gem::Version.new(`git --version`[/(\d+\.\d+\.\d+)/, 1]) - config.filter_run_excluding :git => RequirementChecker.against(git_version) - config.filter_run_excluding :bundler => RequirementChecker.against(Bundler::VERSION.split(".")[0]) - config.filter_run_excluding :rubygems => RequirementChecker.against(Gem::VERSION) - config.filter_run_excluding :ruby_repo => !ENV["GEM_COMMAND"].nil? - config.filter_run_excluding :no_color_tty => Gem.win_platform? || !ENV["GITHUB_ACTION"].nil? - config.filter_run_excluding :permissions => Gem.win_platform? - config.filter_run_excluding :readline => Gem.win_platform? - config.filter_run_excluding :jruby_only => RUBY_ENGINE != "jruby" - config.filter_run_excluding :truffleruby_only => RUBY_ENGINE != "truffleruby" - config.filter_run_excluding :man => Gem.win_platform? +RSpec.configure do |config| + config.filter_run_excluding realworld: true + + config.filter_run_excluding rubygems: RequirementChecker.against(Gem.rubygems_version) + config.filter_run_excluding git: RequirementChecker.against(git_version) + config.filter_run_excluding ruby_repo: !ENV["GEM_COMMAND"].nil? + config.filter_run_excluding no_color_tty: Gem.win_platform? || !ENV["GITHUB_ACTION"].nil? + config.filter_run_excluding permissions: Gem.win_platform? + config.filter_run_excluding readline: Gem.win_platform? + config.filter_run_excluding jruby_only: RUBY_ENGINE != "jruby" + config.filter_run_excluding truffleruby_only: RUBY_ENGINE != "truffleruby" + config.filter_run_excluding man: Gem.win_platform? + config.filter_run_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 9bfa1458c6..b0d4b5008b 100644 --- a/spec/bundler/support/helpers.rb +++ b/spec/bundler/support/helpers.rb @@ -1,66 +1,52 @@ # 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 + def the_bundle + TheBundle.new end - def err - last_command.stderr - end - - MAJOR_DEPRECATION = /^\[DEPRECATED\]\s*/.freeze + MAJOR_DEPRECATION = /^\[DEPRECATED\]\s*/ def err_without_deprecations err.gsub(/#{MAJOR_DEPRECATION}.+[\n]?/, "") 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) opts = args.last.is_a?(Hash) ? args.pop : {} groups = args.map(&:inspect).join(", ") - setup = "require '#{entrypoint}' ; Bundler.ui.silence { Bundler.setup(#{groups}) }" + setup = "require 'bundler' ; Bundler.ui.silence { Bundler.setup(#{groups}) }" ruby([setup, cmd].join(" ; "), opts) end @@ -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 }) - cmd = "#{ruby_cmd} #{bundle_bin} #{cmd}#{args}" - sys_exec(cmd, { :env => env, :dir => dir, :raise_on_error => raise_on_error }, &block) + 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,20 +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 << "#{Path.spec_dir}/support/hax.rb" - require_option = requires.map {|r| "-r#{r}" } + requires << hax + + 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 - [Gem.ruby, *lib_option, *require_option].compact.join(" ") + requires.each {|r| env["RUBYOPT"] = opt_add("-r#{r}", env["RUBYOPT"]) } + + env end def gembin(cmd, options = {}) @@ -158,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" - end - def git(cmd, path, options = {}) - sys_exec("git #{cmd}", options.merge(:dir => path)) + sh(cmd, options, &block) 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.stdout = stdout_read_thread.value.strip - command_execution.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 + current = {} if current.empty? - command_executions << command_execution - - command_execution.stdout - end - - 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 @@ -244,8 +239,9 @@ module Spec contents = args.pop if contents.nil? - File.open(bundled_app_gemfile, "r", &:read) + read_gemfile else + match_source(contents) create_file(args.pop || "Gemfile", contents) end end @@ -254,12 +250,24 @@ module Spec contents = args.pop if contents.nil? - File.open(bundled_app_lock, "r", &:read) + read_lockfile else create_file(args.pop || "Gemfile.lock", contents) end end + def read_gemfile(file = "Gemfile") + read_bundled_app_file(file) + end + + def read_lockfile(file = "Gemfile.lock") + read_bundled_app_file(file) + end + + def read_bundled_app_file(file) + bundled_app(file).read + end + def strip_whitespace(str) # Trim the leading spaces spaces = str[/\A\s+/, 0] || "" @@ -278,62 +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 : {} - path = options.fetch(:path, system_gem_path) + install_dir = options.fetch(:path, system_gem_path) default = options.fetch(:default, false) - with_gem_path_as(path) do - gem_repo = options.fetch(:gem_repo, gem_repo1) - gems.each do |g| - gem_name = g.to_s - if gem_name.start_with?("bundler") - version = gem_name.match(/\Abundler-(?<version>.*)\z/)[:version] if gem_name != "bundler" - with_built_bundler(version) {|gem_path| install_gem(gem_path, default) } - elsif %r{\A(?:[a-zA-Z]:)?/.*\.gem\z}.match?(gem_name) - install_gem(gem_name, default) - else - install_gem("#{gem_repo}/gems/#{gem_name}.gem", 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 install_gem(path, default = false) - raise "OMG `#{path}` does not exist!" unless File.exist?(path) + 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 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" - args += " --default --install-dir #{system_gem_path}" 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) - version ||= Bundler::VERSION - full_name = "bundler-#{version}" - build_path = tmp + full_name - bundler_path = build_path + "#{full_name}.gem" + def uninstall_gem(name, options = {}) + require "rubygems/uninstaller" - Dir.mkdir build_path + gem_home = options.dig(:env, "GEM_HOME") || system_gem_path.to_s - begin - shipped_files.each do |shipped_file| - target_shipped_file = build_path + shipped_file - target_shipped_dir = File.dirname(target_shipped_file) - FileUtils.mkdir_p target_shipped_dir unless File.directory?(target_shipped_dir) - FileUtils.cp shipped_file, target_shipped_file, :preserve => true - end + with_env_vars("GEM_HOME" => gem_home) do + Gem.clear_paths - replace_version_file(version, dir: build_path) # rubocop:disable Style/HashSyntax + uninstaller = Gem::Uninstaller.new( + name, + ignore: true, + executables: true, + all: true + ) + uninstaller.uninstall + ensure + Gem.clear_paths + end + end - Spec::BuildMetadata.write_build_metadata(dir: build_path) # rubocop:disable Style/HashSyntax + def installed_gems_list(options = {}) + gem_home = options.dig(:env, "GEM_HOME") || system_gem_path.to_s - gem_command "build #{relative_gemspec}", :dir => build_path + # Temporarily set GEM_HOME for the command + old_gem_home = ENV["GEM_HOME"] + ENV["GEM_HOME"] = gem_home + Gem.clear_paths - yield(bundler_path) + 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 - build_path.rmtree + 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) @@ -361,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! @@ -387,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) @@ -443,39 +526,9 @@ module Spec def simulate_platform(platform) old = ENV["BUNDLER_SPEC_PLATFORM"] ENV["BUNDLER_SPEC_PLATFORM"] = platform.to_s - yield if block_given? - 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 - simulate_bundler_version_when_missing_prerelease_default_gem_activation do - yield - end - end - ensure - ENV["BUNDLER_SPEC_WINDOWS"] = old - end - - def simulate_bundler_version_when_missing_prerelease_default_gem_activation - return yield unless rubygems_version_failing_to_activate_bundler_prereleases - - old = ENV["BUNDLER_VERSION"] - ENV["BUNDLER_VERSION"] = Bundler::VERSION yield ensure - ENV["BUNDLER_VERSION"] = old - end - - def env_for_missing_prerelease_default_gem_activation - if rubygems_version_failing_to_activate_bundler_prereleases - { "BUNDLER_VERSION" => Bundler::VERSION } - else - {} - end + ENV["BUNDLER_SPEC_PLATFORM"] = old if block_given? end def current_ruby_minor @@ -486,24 +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 - # versions not including - # https://github.com/rubygems/rubygems/commit/929e92d752baad3a08f3ac92eaec162cb96aedd1 - def rubygems_version_failing_to_activate_bundler_prereleases - Gem.rubygems_version < Gem::Version.new("3.1.0.pre.1") - 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) @@ -555,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 78372302f1..1fbdd49abe 100644 --- a/spec/bundler/support/indexes.rb +++ b/spec/bundler/support/indexes.rb @@ -14,10 +14,10 @@ module Spec alias_method :platforms, :platform - def resolve(args = []) + def resolve(args = [], dependency_api_available: true) @platforms ||= ["ruby"] - default_source = instance_double("Bundler::Source::Rubygems", :specs => @index, :to_s => "locally install gems") - source_requirements = { :default => default_source } + default_source = instance_double("Bundler::Source::Rubygems", specs: @index, to_s: "locally install gems", dependency_api_available?: dependency_api_available) + source_requirements = { default: default_source } base = args[0] || Bundler::SpecSet.new([]) base.each {|ls| ls.source = default_source } gem_version_promoter = args[1] || Bundler::GemVersionPromoter.new @@ -27,7 +27,7 @@ module Spec name = d.name source_requirements[name] = d.source = default_source end - packages = Bundler::Resolver::Base.new(source_requirements, @deps, base, @platforms, :locked_specs => originally_locked, :unlock => unlock) + packages = Bundler::Resolver::Base.new(source_requirements, @deps, base, @platforms, locked_specs: originally_locked, unlock: unlock) Bundler::Resolver.new(packages, gem_version_promoter).start end @@ -41,6 +41,12 @@ module Spec expect(got).to eq(specs.sort) end + def should_resolve_without_dependency_api(specs) + got = resolve(dependency_api_available: false) + got = got.map(&:full_name).sort + expect(got).to eq(specs.sort) + end + def should_resolve_and_include(specs, args = []) got = resolve(args) got = got.map(&:full_name).sort @@ -60,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 @@ -71,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"] @@ -83,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 @@ -117,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 @@ -298,7 +303,7 @@ module Spec end end - def a_unresovable_child_index + def a_unresolvable_child_index build_index do gem "json", %w[1.8.0] @@ -361,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 ea7c784683..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), @@ -97,18 +97,6 @@ module Spec end end - RSpec::Matchers.define :take_less_than do |seconds| - match do |actual| - start_time = Time.now - - actual.call - - (Time.now - start_time).to_f < seconds - end - - supports_block_expectations - end - define_compound_matcher :read_as, [exist] do |file_contents| diffable @@ -128,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" @@ -139,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 @@ -162,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 @@ -178,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 @@ -204,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 de03b2746e..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,33 +66,39 @@ 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") - end - - def api_request_limit_hack_file - spec_dir.join("support/api_request_limit_hax.rb") + @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 def shipped_files - @shipped_files ||= loaded_gemspec.files + @shipped_files ||= if ruby_core_tarball? + loaded_gemspec.files.map {|f| f.gsub(%r{^exe/}, "libexec/") } + elsif ruby_core? + tracked_files + else + loaded_gemspec.files + end end def lib_tracked_files @@ -96,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 @@ -107,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 @@ -154,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) @@ -178,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) @@ -217,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 @@ -229,13 +264,6 @@ module Spec root.join("lib") end - # Sometimes rubygems version under test does not include - # https://github.com/rubygems/rubygems/pull/2728 and will not always end up - # activating the current bundler. In that case, require bundler absolutely. - def entrypoint - Gem.rubygems_version < Gem::Version.new("3.1.a") ? "#{lib_dir}/bundler" : "bundler" - end - def global_plugin_gem(*args) home ".bundle", "plugin", "gems", *args end @@ -251,35 +279,73 @@ 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 - def ruby_core? - # avoid to warnings - @ruby_core ||= nil + def replace_required_ruby_version(version, dir:) + gemspec_file = File.expand_path("bundler.gemspec", dir) + contents = File.read(gemspec_file) + contents.sub!(/(^\s+s\.required_ruby_version\s*=\s*)"[^"]+"/, %(\\1"#{version}")) + File.open(gemspec_file, "w") {|f| f << contents } + end - 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? ? "lib/bundler lib/bundler.rb spec/bundler man/bundle*" : "" + 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 @@ -287,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? @@ -295,15 +361,15 @@ module Spec end def rubocop_gemfile_basename - source_root.join("tool/bundler/rubocop_gems.rb") + tool_dir.join("rubocop_gems.rb") end def standard_gemfile_basename - source_root.join("tool/bundler/standard_gems.rb") + tool_dir.join("standard_gems.rb") end def tool_dir - source_root.join("tool/bundler") + ruby_core? ? source_root.join("tool/bundler") : source_root.join("../tool/bundler") end def templates_dir diff --git a/spec/bundler/support/platforms.rb b/spec/bundler/support/platforms.rb index 89a8d45ffd..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 @@ -95,8 +53,23 @@ module Spec 9999 end - def lockfile_platforms(*extra) - [local_platform, *extra].map(&:to_s).sort.join("\n ") + def default_platform_list(*extra, defaults: default_locked_platforms) + defaults.concat(extra).map(&:to_s).uniq + end + + def lockfile_platforms(*extra, defaults: default_locked_platforms) + platforms = default_platform_list(*extra, defaults: defaults) + platforms.sort.join("\n ") + end + + def default_locked_platforms + [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 4553c0606e..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" @@ -24,12 +22,15 @@ module Spec gem_load_activate_and_possibly_install(gem_name, bin_container) end - def gem_require(gem_name) + def gem_require(gem_name, entrypoint) gem_activate(gem_name) - require gem_name + require entrypoint 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" @@ -38,42 +39,82 @@ module Spec FileUtils.mkdir_p(Path.tmpdir) ENV["HOME"] = Path.home.to_s - ENV["TMPDIR"] = Path.tmpdir.to_s + # Remove "RUBY_CODESIGN", which is used by mkmf-generated Makefile to + # sign extension bundles on macOS, to avoid trying to find the specified key + # from the fake $HOME/Library/Keychains directory. + ENV.delete "RUBY_CODESIGN" + 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:) @@ -86,7 +127,7 @@ module Spec puts success_message puts else - system("git status --porcelain") + system("git diff") puts puts error_message @@ -96,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) @@ -118,38 +189,12 @@ module Spec end def gem_activate(gem_name) + require_relative "activate" require "bundler" gem_requirement = Bundler::LockfileParser.new(File.read(dev_lockfile)).specs.find {|spec| spec.name == gem_name }.version gem gem_name, gem_requirement end - def install_gems(gemfile, path = nil) - old_gemfile = ENV["BUNDLE_GEMFILE"] - old_orig_gemfile = ENV["BUNDLER_ORIG_BUNDLE_GEMFILE"] - ENV["BUNDLE_GEMFILE"] = gemfile.to_s - ENV["BUNDLER_ORIG_BUNDLE_GEMFILE"] = nil - - if path - old_path = ENV["BUNDLE_PATH"] - 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 5653601ae8..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 @@ -30,11 +31,10 @@ class RubygemsVersionManager rubygems_default_path = rubygems_path + "/defaults" bundler_path = rubylibdir + "/bundler" - bundler_exemptions = Gem.rubygems_version < Gem::Version.new("3.2.0") ? [bundler_path + "/errors.rb"] : [] bad_loaded_features = $LOADED_FEATURES.select do |loaded_feature| (loaded_feature.start_with?(rubygems_path) && !loaded_feature.start_with?(rubygems_default_path)) || - (loaded_feature.start_with?(bundler_path) && !bundler_exemptions.any? {|bundler_exemption| loaded_feature.start_with?(bundler_exemption) }) + loaded_feature.start_with?(bundler_path) end errors = if bad_loaded_features.any? @@ -58,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 @@ -66,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? @@ -85,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 @@ -95,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 1c5294101e..f8849640b6 100644 --- a/spec/bundler/update/gemfile_spec.rb +++ b/spec/bundler/update/gemfile_spec.rb @@ -4,44 +4,44 @@ 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") - bundle :update, :gemfile => bundled_app("NotGemfile"), :all => true + bundle :install, gemfile: bundled_app("NotGemfile") + bundle :update, gemfile: bundled_app("NotGemfile"), all: true # Specify BUNDLE_GEMFILE for `the_bundle` # to retrieve the proper Gemfile 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 it "uses the gemfile to update" do - bundle "update", :all => true + bundle "update", all: true bundle "list" - expect(out).to include("rack (1.0.0)") + expect(out).to include("myrack (1.0.0)") end it "uses the gemfile while in a subdirectory" do bundled_app("subdir").mkpath - bundle "update", :all => true, :dir => bundled_app("subdir") - bundle "list", :dir => bundled_app("subdir") + bundle "update", all: true, dir: bundled_app("subdir") + bundle "list", dir: bundled_app("subdir") - expect(out).to include("rack (1.0.0)") + 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 d80f4018f3..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,12 +35,12 @@ 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 - bundle :update, :all => true + bundle :update, all: true end it "displays fund message" do diff --git a/spec/bundler/update/gems/post_install_spec.rb b/spec/bundler/update/gems/post_install_spec.rb index 3aaa659d57..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,12 +47,12 @@ 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 - bundle :update, :all => true + bundle :update, all: true end it_behaves_like "a post-install message outputter" @@ -62,12 +62,12 @@ 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 - bundle :update, :all => true + bundle :update, all: true end it_behaves_like "a post-install message outputter" diff --git a/spec/bundler/update/git_spec.rb b/spec/bundler/update/git_spec.rb index 59e3d2f5fb..526e988ab7 100644 --- a/spec/bundler/update/git_spec.rb +++ b/spec/bundler/update/git_spec.rb @@ -4,10 +4,10 @@ RSpec.describe "bundle update" do describe "git sources" do it "floats on a branch when :branch is used" do build_git "foo", "1.0" - update_git "foo", :branch => "omg" + update_git "foo", branch: "omg" install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("foo-1.0")}", :branch => "omg" do gem 'foo' end @@ -17,20 +17,20 @@ RSpec.describe "bundle update" do s.write "lib/foo.rb", "FOO = '1.1'" end - bundle "update", :all => true + bundle "update", all: true expect(the_bundle).to include_gems "foo 1.1" end it "updates correctly when you have like craziness" do - build_lib "activesupport", "3.0", :path => lib_path("rails/activesupport") - build_git "rails", "3.0", :path => lib_path("rails") do |s| + build_lib "activesupport", "3.0", path: lib_path("rails/activesupport") + build_git "rails", "3.0", path: lib_path("rails") do |s| s.add_dependency "activesupport", "= 3.0" end 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" @@ -38,17 +38,17 @@ RSpec.describe "bundle update" do end it "floats on a branch when :branch is used and the source is specified in the update" do - build_git "foo", "1.0", :path => lib_path("foo") - update_git "foo", :branch => "omg", :path => lib_path("foo") + build_git "foo", "1.0", path: lib_path("foo") + update_git "foo", branch: "omg", path: lib_path("foo") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" git "#{lib_path("foo")}", :branch => "omg" do gem 'foo' end G - update_git "foo", :path => lib_path("foo") do |s| + update_git "foo", path: lib_path("foo") do |s| s.write "lib/foo.rb", "FOO = '1.1'" end @@ -58,18 +58,18 @@ RSpec.describe "bundle update" do end it "floats on main when updating all gems that are pinned to the source even if you have child dependencies" do - build_git "foo", :path => lib_path("foo") - build_gem "bar", :to_bundle => true do |s| + build_git "foo", path: lib_path("foo") + build_gem "bar", to_bundle: true do |s| s.add_dependency "foo" 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 - update_git "foo", :path => lib_path("foo") do |s| + update_git "foo", path: lib_path("foo") do |s| s.write "lib/foo.rb", "FOO = '1.1'" end @@ -79,47 +79,47 @@ RSpec.describe "bundle update" do end it "notices when you change the repo url in the Gemfile" do - build_git "foo", :path => lib_path("foo_one") - build_git "foo", :path => lib_path("foo_two") + build_git "foo", path: lib_path("foo_one") + build_git "foo", path: lib_path("foo_two") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - 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", :push => "main" + @remote = build_git("bar", bare: true) + 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 # Create a new tag on the remote that needs fetching - update_git "foo", :tag => "fubar" - update_git "foo", :push => "fubar" + update_git "foo", tag: "fubar" + update_git "foo", push: "fubar" gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem 'foo', :git => "#{@remote.path}", :tag => "fubar" G - bundle "update", :all => true + bundle "update", all: true expect(err).to be_empty end @@ -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 @@ -168,9 +168,9 @@ RSpec.describe "bundle update" do expect(out).to eq("GIT") end - it "unlocks the source when submodules are removed from git source", :git => ">= 2.9.0" do + it "unlocks the source when submodules are removed from git source", git: ">= 2.9.0" do install_gemfile <<-G - source "#{file_uri_for(gem_repo4)}" + 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,67 +195,67 @@ 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 + bundle :update, all: true, raise_on_error: false expect(err).to include(lib_path("foo-1.0").to_s). and match(/Git error: command `git fetch.+has failed/) end 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 it "shows the previous version of the gem" do - build_git "rails", "2.3.2", :path => lib_path("rails") + build_git "rails", "2.3.2", path: lib_path("rails") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" - gem "rails", :git => "#{file_uri_for(lib_path("rails"))}" + source "https://gem.repo1" + gem "rails", :git => "#{lib_path("rails")}" G - update_git "rails", "3.0", :path => lib_path("rails"), :gemspec => true + update_git "rails", "3.0", path: lib_path("rails"), gemspec: true - bundle "update", :all => true - expect(out).to include("Using rails 3.0 (was 2.3.2) from #{file_uri_for(lib_path("rails"))} (at main@#{revision_for(lib_path("rails"))[0..6]})") + bundle "update", all: true + 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 describe "with --source flag" do before :each do build_repo2 - @git = build_git "foo", :path => lib_path("foo") do |s| + @git = build_git "foo", path: lib_path("foo") do |s| s.executables = "foobar" end 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 it "updates the source" do - update_git "foo", :path => @git.path + update_git "foo", path: @git.path bundle "update --source foo" @@ -268,7 +268,7 @@ RSpec.describe "bundle update" do end it "unlocks gems that were originally pulled in by the source" do - update_git "foo", "2.0", :path => @git.path + update_git "foo", "2.0", path: @git.path bundle "update --source foo" expect(the_bundle).to include_gems "foo 2.0" @@ -276,24 +276,24 @@ RSpec.describe "bundle update" do it "leaves all other gems frozen" do update_repo2 - update_git "foo", :path => @git.path + update_git "foo", path: @git.path bundle "update --source foo" - expect(the_bundle).to include_gems "rack 1.0" + expect(the_bundle).to include_gems "myrack 1.0" end end context "when the gem and the repository have different names" do before :each do build_repo2 - @git = build_git "foo", :path => lib_path("bar") + @git = build_git "foo", path: lib_path("bar") install_gemfile <<-G - source "#{file_uri_for(gem_repo2)}" + source "https://gem.repo2" git "#{lib_path("bar")}" do gem 'foo' end - gem 'rack' + gem 'myrack' G end @@ -301,7 +301,7 @@ RSpec.describe "bundle update" do spec_lines = lib_path("bar/foo.gemspec").read.split("\n") spec_lines[5] = "s.version = '2.0'" - update_git "foo", "2.0", :path => @git.path do |s| + update_git "foo", "2.0", path: @git.path do |s| s.write "foo.gemspec", spec_lines.join("\n") end @@ -309,6 +309,11 @@ RSpec.describe "bundle update" do bundle "update --source bar" + checksums = checksums_section_when_enabled do |c| + c.no_checksum "foo", "2.0" + c.checksum gem_repo2, "myrack", "1.0.0" + end + expect(lockfile).to eq <<~G GIT remote: #{@git.path} @@ -317,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 756770313b..8c76c94e1a 100644 --- a/spec/bundler/update/path_spec.rb +++ b/spec/bundler/update/path_spec.rb @@ -3,14 +3,14 @@ RSpec.describe "path sources" do describe "bundle update --source" do it "shows the previous version of the gem when updated from path source" do - build_lib "activesupport", "2.3.5", :path => lib_path("rails/activesupport") + build_lib "activesupport", "2.3.5", path: lib_path("rails/activesupport") install_gemfile <<-G - source "#{file_uri_for(gem_repo1)}" + source "https://gem.repo1" gem "activesupport", :path => "#{lib_path("rails/activesupport")}" G - build_lib "activesupport", "3.0", :path => lib_path("rails/activesupport") + build_lib "activesupport", "3.0", path: lib_path("rails/activesupport") bundle "update --source activesupport" expect(out).to include("Using activesupport 3.0 (was 2.3.5) from source at `#{lib_path("rails/activesupport")}`") diff --git a/spec/bundler/update/redownload_spec.rb b/spec/bundler/update/redownload_spec.rb deleted file mode 100644 index 147be823f5..0000000000 --- a/spec/bundler/update/redownload_spec.rb +++ /dev/null @@ -1,34 +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 - end -end |
