diff options
Diffstat (limited to 'spec/bundler/support')
39 files changed, 1314 insertions, 630 deletions
diff --git a/spec/bundler/support/activate.rb b/spec/bundler/support/activate.rb index e19a6d0ed1..143b77833d 100644 --- a/spec/bundler/support/activate.rb +++ b/spec/bundler/support/activate.rb @@ -5,5 +5,5 @@ Gem.instance_variable_set(:@ruby, ENV["RUBY"]) if ENV["RUBY"] require_relative "path" bundler_gemspec = Spec::Path.loaded_gemspec -bundler_gemspec.instance_variable_set(:@full_gem_path, Spec::Path.source_root) +bundler_gemspec.instance_variable_set(:@full_gem_path, Spec::Path.source_root.to_s) bundler_gemspec.activate if bundler_gemspec.respond_to?(:activate) diff --git a/spec/bundler/support/artifice/compact_index_cooldown.rb b/spec/bundler/support/artifice/compact_index_cooldown.rb new file mode 100644 index 0000000000..85e3173c98 --- /dev/null +++ b/spec/bundler/support/artifice/compact_index_cooldown.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +require_relative "helpers/compact_index_cooldown" +require_relative "helpers/artifice" + +Artifice.activate_with(CompactIndexCooldownAPI) diff --git a/spec/bundler/support/artifice/compact_index_creds_diff_host.rb b/spec/bundler/support/artifice/compact_index_creds_diff_host.rb index 401e8a98d8..282e9c8961 100644 --- a/spec/bundler/support/artifice/compact_index_creds_diff_host.rb +++ b/spec/bundler/support/artifice/compact_index_creds_diff_host.rb @@ -24,7 +24,7 @@ class CompactIndexCredsDiffHost < CompactIndexAPI end get "/gems/:id" do - redirect "http://diffhost.com/no/creds/#{params[:id]}" + redirect "http://diffhost.test/no/creds/#{params[:id]}" end get "/no/creds/:id" do diff --git a/spec/bundler/support/artifice/compact_index_etag_match.rb b/spec/bundler/support/artifice/compact_index_etag_match.rb index 08d7b5ec53..6c62166051 100644 --- a/spec/bundler/support/artifice/compact_index_etag_match.rb +++ b/spec/bundler/support/artifice/compact_index_etag_match.rb @@ -4,7 +4,7 @@ require_relative "helpers/compact_index" class CompactIndexEtagMatch < CompactIndexAPI get "/versions" do - raise "ETag header should be present" unless env["HTTP_IF_NONE_MATCH"] + raise ArgumentError, "ETag header should be present" unless env["HTTP_IF_NONE_MATCH"] headers "ETag" => env["HTTP_IF_NONE_MATCH"] status 304 body "" diff --git a/spec/bundler/support/artifice/compact_index_mirror_down.rb b/spec/bundler/support/artifice/compact_index_mirror_down.rb new file mode 100644 index 0000000000..88983c715d --- /dev/null +++ b/spec/bundler/support/artifice/compact_index_mirror_down.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require_relative "helpers/compact_index" +require_relative "helpers/artifice" +require_relative "helpers/rack_request" + +module Artifice + module Net + class HTTPMirrorDown < HTTP + def connect + raise SocketError if address == "gem.mirror" + + super + end + end + + HTTP.endpoint = CompactIndexAPI + end + + replace_net_http(Net::HTTPMirrorDown) +end diff --git a/spec/bundler/support/artifice/compact_index_no_checksums.rb b/spec/bundler/support/artifice/compact_index_no_checksums.rb new file mode 100644 index 0000000000..ecb7fc7d7c --- /dev/null +++ b/spec/bundler/support/artifice/compact_index_no_checksums.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require_relative "helpers/compact_index" + +class CompactIndexNoChecksums < CompactIndexAPI + get "/info/:name" do + etag_response do + gem = gems.find {|g| g.name == params[:name] } + gem.versions.map(&:number).join("\n") + end + end +end + +require_relative "helpers/artifice" + +Artifice.activate_with(CompactIndexNoChecksums) diff --git a/spec/bundler/support/artifice/endpoint_500.rb b/spec/bundler/support/artifice/endpoint_500.rb index b1ed1964c8..9dd373bbf6 100644 --- a/spec/bundler/support/artifice/endpoint_500.rb +++ b/spec/bundler/support/artifice/endpoint_500.rb @@ -2,7 +2,7 @@ require_relative "../path" -$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gem_path.join("gems/{mustermann,rack,tilt,sinatra,ruby2_keywords,base64}-*/lib")].map(&:to_s)) +$LOAD_PATH.unshift(*Spec::Path.sinatra_dependency_paths) require "sinatra/base" diff --git a/spec/bundler/support/artifice/endpoint_creds_diff_host.rb b/spec/bundler/support/artifice/endpoint_creds_diff_host.rb index ce30de0a68..9cbb4de61a 100644 --- a/spec/bundler/support/artifice/endpoint_creds_diff_host.rb +++ b/spec/bundler/support/artifice/endpoint_creds_diff_host.rb @@ -24,7 +24,7 @@ class EndpointCredsDiffHost < Endpoint end get "/gems/:id" do - redirect "http://diffhost.com/no/creds/#{params[:id]}" + redirect "http://diffhost.test/no/creds/#{params[:id]}" end get "/no/creds/:id" do diff --git a/spec/bundler/support/artifice/fail.rb b/spec/bundler/support/artifice/fail.rb index 8822e5b8e2..5ddbc4e590 100644 --- a/spec/bundler/support/artifice/fail.rb +++ b/spec/bundler/support/artifice/fail.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "bundler/vendored_net_http" +require_relative "../vendored_net_http" class Fail < Gem::Net::HTTP # Gem::Net::HTTP uses a @newimpl instance variable to decide whether diff --git a/spec/bundler/support/artifice/helpers/compact_index.rb b/spec/bundler/support/artifice/helpers/compact_index.rb index a803a2d30a..e684aa8628 100644 --- a/spec/bundler/support/artifice/helpers/compact_index.rb +++ b/spec/bundler/support/artifice/helpers/compact_index.rb @@ -2,7 +2,7 @@ require_relative "endpoint" -$LOAD_PATH.unshift Dir[Spec::Path.base_system_gem_path.join("gems/compact_index*/lib")].first.to_s +$LOAD_PATH.unshift Spec::Path.tmp_root.join("compact_index/lib").to_s require "compact_index" require "digest" @@ -40,7 +40,7 @@ class CompactIndexAPI < Endpoint end def requested_range_for(response_body) - ranges = Rack::Utils.byte_ranges(env, response_body.bytesize) + ranges = Rack::Utils.get_byte_ranges(env["HTTP_RANGE"], response_body.bytesize) if ranges status 206 @@ -67,11 +67,14 @@ class CompactIndexAPI < Endpoint @gems ||= {} @gems[gem_repo] ||= begin specs = Bundler::Deprecate.skip_during do - %w[specs.4.8 prerelease_specs.4.8].map do |filename| - Marshal.load(File.open(gem_repo.join(filename)).read).map do |name, version, platform| + %w[specs.4.8 prerelease_specs.4.8].flat_map do |filename| + spec_index = gem_repo.join(filename) + next [] unless File.exist?(spec_index) + + Marshal.load(File.binread(spec_index)).map do |name, version, platform| load_spec(name, version, platform, gem_repo) end - end.flatten + end end specs.group_by(&:name).map do |name, versions| @@ -87,13 +90,17 @@ class CompactIndexAPI < Endpoint rescue StandardError checksum = nil end - CompactIndex::GemVersion.new(spec.version.version, spec.platform.to_s, checksum, nil, - deps, spec.required_ruby_version.to_s, spec.required_rubygems_version.to_s) + build_gem_version(spec, deps, checksum) end CompactIndex::Gem.new(name, gem_versions) end end end + + def build_gem_version(spec, deps, checksum) + CompactIndex::GemVersion.new(spec.version.version, spec.platform.to_s, checksum, nil, + deps, spec.required_ruby_version.to_s, spec.required_rubygems_version.to_s) + end end get "/names" do diff --git a/spec/bundler/support/artifice/helpers/compact_index_cooldown.rb b/spec/bundler/support/artifice/helpers/compact_index_cooldown.rb new file mode 100644 index 0000000000..9920fd2c95 --- /dev/null +++ b/spec/bundler/support/artifice/helpers/compact_index_cooldown.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require_relative "compact_index" + +class CompactIndexCooldownAPI < CompactIndexAPI + helpers do + def build_gem_version(spec, deps, checksum) + created_at = spec.date&.utc&.iso8601 + CompactIndex::GemVersionV2.new(spec.version.version, spec.platform.to_s, checksum, nil, + deps, spec.required_ruby_version.to_s, spec.required_rubygems_version.to_s, created_at) + end + end +end diff --git a/spec/bundler/support/artifice/helpers/endpoint.rb b/spec/bundler/support/artifice/helpers/endpoint.rb index 83ba1be0fc..9590611dfe 100644 --- a/spec/bundler/support/artifice/helpers/endpoint.rb +++ b/spec/bundler/support/artifice/helpers/endpoint.rb @@ -2,7 +2,7 @@ require_relative "../../path" -$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gem_path.join("gems/{mustermann,rack,tilt,sinatra,ruby2_keywords,base64}-*/lib")].map(&:to_s)) +$LOAD_PATH.unshift(*Spec::Path.sinatra_dependency_paths) require "sinatra/base" @@ -27,6 +27,7 @@ class Endpoint < Sinatra::Base set :raise_errors, true set :show_exceptions, false + set :host_authorization, permitted_hosts: [".example.org", ".local", ".mirror", ".repo", ".repo1", ".repo2", ".repo3", ".repo4", ".rubygems.org", ".security", ".source", ".test", "127.0.0.1"] def call!(*) super.tap do @@ -62,10 +63,10 @@ class Endpoint < Sinatra::Base return [] if gem_names.nil? || gem_names.empty? all_specs = %w[specs.4.8 prerelease_specs.4.8].map do |filename| - Marshal.load(File.open(gem_repo.join(filename)).read) + Marshal.load(File.binread(gem_repo.join(filename))) end.inject(:+) - all_specs.map do |name, version, platform| + all_specs.filter_map do |name, version, platform| spec = load_spec(name, version, platform, gem_repo) next unless gem_names.include?(spec.name) { @@ -76,7 +77,7 @@ class Endpoint < Sinatra::Base [dep.name, dep.requirement.requirements.map {|a| a.join(" ") }.join(", ")] end, } - end.compact + end end def load_spec(name, version, platform, gem_repo) diff --git a/spec/bundler/support/artifice/helpers/rack_request.rb b/spec/bundler/support/artifice/helpers/rack_request.rb index f419bacb8c..05ff034463 100644 --- a/spec/bundler/support/artifice/helpers/rack_request.rb +++ b/spec/bundler/support/artifice/helpers/rack_request.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "rack/test" -require "bundler/vendored_net_http" +require_relative "../../vendored_net_http" module Artifice module Net diff --git a/spec/bundler/support/artifice/vcr.rb b/spec/bundler/support/artifice/vcr.rb index 7b9a8bdeaf..0bf5ade8f6 100644 --- a/spec/bundler/support/artifice/vcr.rb +++ b/spec/bundler/support/artifice/vcr.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "bundler/vendored_net_http" +require_relative "../vendored_net_http" require_relative "../path" CASSETTE_PATH = "#{Spec::Path.spec_dir}/support/artifice/vcr_cassettes".freeze @@ -24,7 +24,7 @@ class BundlerVCRHTTP < Gem::Net::HTTP end File.open(USED_CASSETTES_PATH, "a+") do |f| - f.puts request_pair_paths.map {|path| Pathname.new(path).relative_path_from(Spec::Path.source_root).to_s }.join("\n") + f.puts request_pair_paths.map {|path| Pathname.new(path).relative_path_from(Spec::Path.git_root).to_s }.join("\n") end if recorded_response? diff --git a/spec/bundler/support/artifice/windows.rb b/spec/bundler/support/artifice/windows.rb index fea991c071..3056540beb 100644 --- a/spec/bundler/support/artifice/windows.rb +++ b/spec/bundler/support/artifice/windows.rb @@ -2,7 +2,7 @@ require_relative "../path" -$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gem_path.join("gems/{mustermann,rack,tilt,sinatra,ruby2_keywords,base64}-*/lib")].map(&:to_s)) +$LOAD_PATH.unshift(*Spec::Path.sinatra_dependency_paths) require "sinatra/base" diff --git a/spec/bundler/support/build_metadata.rb b/spec/bundler/support/build_metadata.rb index 5898e7f3bd..2eade4137b 100644 --- a/spec/bundler/support/build_metadata.rb +++ b/spec/bundler/support/build_metadata.rb @@ -8,11 +8,10 @@ module Spec include Spec::Path include Spec::Helpers - def write_build_metadata(dir: source_root) + def write_build_metadata(dir: source_root, version: Bundler::VERSION) build_metadata = { git_commit_sha: git_commit_sha, - built_at: loaded_gemspec.date.utc.strftime("%Y-%m-%d"), - release: true, + built_at: release_date_for(version, dir: dir), } replace_build_metadata(build_metadata, dir: dir) @@ -20,7 +19,7 @@ module Spec def reset_build_metadata(dir: source_root) build_metadata = { - release: false, + built_at: nil, } replace_build_metadata(build_metadata, dir: dir) @@ -41,7 +40,12 @@ module Spec end def git_commit_sha - ruby_core_tarball? ? "unknown" : sys_exec("git rev-parse --short HEAD", dir: source_root).strip + ruby_core_tarball? ? "unknown" : git("rev-parse --short HEAD", source_root).strip + end + + def release_date_for(version, dir:) + changelog = File.expand_path("CHANGELOG.md", dir) + File.readlines(changelog)[2].scan(/^## #{Regexp.escape(version)} \((.*)\)/).first&.first if File.exist?(changelog) end extend self diff --git a/spec/bundler/support/builders.rb b/spec/bundler/support/builders.rb index 46931e9ca5..43ab7e053d 100644 --- a/spec/bundler/support/builders.rb +++ b/spec/bundler/support/builders.rb @@ -2,9 +2,18 @@ require "bundler/shared_helpers" require "shellwords" +require "fileutils" +require "rubygems/package" + +require_relative "build_metadata" module Spec module Builders + def self.extended(mod) + mod.extend Path + mod.extend Helpers + end + def self.constantize(name) name.delete("-").upcase end @@ -17,13 +26,7 @@ module Spec Gem::Platform.new(platform) end - def rake_version - "13.1.0" - end - def build_repo1 - rake_path = Dir["#{Path.base_system_gems}/**/rake*.gem"].first - build_repo gem_repo1 do FileUtils.cp rake_path, "#{gem_repo1}/gems/" @@ -32,23 +35,23 @@ module Spec build_gem "puma" build_gem "minitest" - build_gem "rack", %w[0.9.1 1.0.0] do |s| - s.executables = "rackup" - s.post_install_message = "Rack's post install message" + build_gem "myrack", %w[0.9.1 1.0.0] do |s| + s.executables = "myrackup" + s.post_install_message = "Myrack's post install message" end build_gem "thin" do |s| - s.add_dependency "rack" + s.add_dependency "myrack" s.post_install_message = "Thin's post install message" end - build_gem "rack-obama" do |s| - s.add_dependency "rack" - s.post_install_message = "Rack-obama's post install message" + build_gem "myrack-obama" do |s| + s.add_dependency "myrack" + s.post_install_message = "Myrack-obama's post install message" end - build_gem "rack_middleware", "1.0" do |s| - s.add_dependency "rack", "0.9.1" + build_gem "myrack_middleware", "1.0" do |s| + s.add_dependency "myrack", "0.9.1" end build_gem "rails", "2.3.2" do |s| @@ -81,80 +84,62 @@ module Spec s.write "lib/spec.rb", "SPEC = '1.2.7'" end - build_gem "rack-test", no_default: true do |s| - s.write "lib/rack/test.rb", "RACK_TEST = '1.0'" - end - - build_gem "platform_specific" do |s| - s.platform = Gem::Platform.local - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 #{Gem::Platform.local}'" + build_gem "myrack-test", no_default: true do |s| + s.write "lib/myrack/test.rb", "MYRACK_TEST = '1.0'" end build_gem "platform_specific" do |s| s.platform = "java" - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 JAVA'" end build_gem "platform_specific" do |s| s.platform = "ruby" - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 RUBY'" end build_gem "platform_specific" do |s| s.platform = "x86-mswin32" - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0 x86-mswin32'" end build_gem "platform_specific" do |s| s.platform = "x64-mswin64" - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0 x64-mswin64'" end build_gem "platform_specific" do |s| s.platform = "x86-mingw32" - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0 x86-mingw32'" end build_gem "platform_specific" do |s| - s.platform = "x64-mingw32" - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0 x64-mingw32'" + s.platform = "x64-mingw-ucrt" end build_gem "platform_specific" do |s| - s.platform = "x64-mingw-ucrt" - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0 x64-mingw-ucrt'" + s.platform = "aarch64-mingw-ucrt" end build_gem "platform_specific" do |s| s.platform = "x86-darwin-100" - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 x86-darwin-100'" end build_gem "only_java", "1.0" do |s| s.platform = "java" - s.write "lib/only_java.rb", "ONLY_JAVA = '1.0.0 JAVA'" end build_gem "only_java", "1.1" do |s| s.platform = "java" - s.write "lib/only_java.rb", "ONLY_JAVA = '1.1.0 JAVA'" end build_gem "nokogiri", "1.4.2" build_gem "nokogiri", "1.4.2" do |s| s.platform = "java" - s.write "lib/nokogiri.rb", "NOKOGIRI = '1.4.2 JAVA'" s.add_dependency "weakling", ">= 0.0.3" end build_gem "laduradura", "5.15.2" build_gem "laduradura", "5.15.2" do |s| s.platform = "java" - s.write "lib/laduradura.rb", "LADURADURA = '5.15.2 JAVA'" end build_gem "laduradura", "5.15.3" do |s| s.platform = "java" - s.write "lib/laduradura.rb", "LADURADURA = '5.15.2 JAVA'" end build_gem "weakling", "0.0.3" @@ -168,7 +153,7 @@ module Spec build_gem "bundler", "0.9" do |s| s.executables = "bundle" - s.write "bin/bundle", "puts 'FAIL'" + s.write "bin/bundle", "#!/usr/bin/env ruby\nputs 'FAIL'" end # The bundler 0.8 gem has a rubygems plugin that always loads :( @@ -196,29 +181,44 @@ module Spec end def build_repo2(**kwargs, &blk) - FileUtils.rm_rf gem_repo2 - FileUtils.cp_r gem_repo1, gem_repo2 + FileUtils.cp_r gem_repo1, gem_repo2, remove_destination: true update_repo2(**kwargs, &blk) if block_given? end # A repo that has no pre-installed gems included. (The caller completely # determines the contents with the block.) - def build_repo4(**kwargs, &blk) - FileUtils.rm_rf gem_repo4 - build_repo(gem_repo4, **kwargs, &blk) + # + # If the repo already exists, `#update_repo` will be called. + def build_repo3(**kwargs, &blk) + if File.exist?(gem_repo3) + update_repo(gem_repo3, &blk) + else + build_repo gem_repo3, **kwargs, &blk + end end - def update_repo4(&blk) - update_repo(gem_repo4, &blk) + # Like build_repo3, this is a repo that has no pre-installed gems included. + # + # If the repo already exists, `#udpate_repo` will be called + def build_repo4(**kwargs, &blk) + if File.exist?(gem_repo4) + update_repo gem_repo4, &blk + else + build_repo gem_repo4, **kwargs, &blk + end end def update_repo2(**kwargs, &blk) update_repo(gem_repo2, **kwargs, &blk) end + def update_repo3(&blk) + update_repo(gem_repo3, &blk) + end + def build_security_repo build_repo security_repo do - build_gem "rack" + build_gem "myrack" build_gem "signed_gem" do |s| cert = "signing-cert.pem" @@ -231,6 +231,41 @@ module Spec end end + # A minimal fake irb console + def build_dummy_irb(version = "9.9.9") + build_gem "irb", version do |s| + s.write "lib/irb.rb", <<-RUBY + class IRB + class << self + def toplevel_binding + unless defined?(@toplevel_binding) && @toplevel_binding + TOPLEVEL_BINDING.eval %{ + def self.__irb__; binding; end + IRB.instance_variable_set(:@toplevel_binding, __irb__) + class << self; undef __irb__; end + } + end + @toplevel_binding.eval('private') + @toplevel_binding + end + + def __irb__ + while line = gets + begin + puts eval(line, toplevel_binding).inspect.sub(/^"(.*)"$/, '=> \\1') + rescue Exception => e + puts "\#{e.class}: \#{e.message}" + puts e.backtrace.first + end + end + end + alias start __irb__ + end + end + RUBY + end + end + def build_repo(path, **kwargs, &blk) return if File.directory?(path) @@ -239,36 +274,17 @@ module Spec update_repo(path,**kwargs, &blk) end - def check_test_gems! - rake_path = Dir["#{Path.base_system_gems}/**/rake*.gem"].first - - if rake_path.nil? - FileUtils.rm_rf(Path.base_system_gems) - Spec::Rubygems.install_test_deps - rake_path = Dir["#{Path.base_system_gems}/**/rake*.gem"].first - end - - if rake_path.nil? - abort "Your test gems are missing! Run `rm -rf #{tmp}` and try again." - end - end - def update_repo(path, build_compact_index: true) - if path == gem_repo1 && caller.first.split(" ").last == "`build_repo`" + exempted_caller = Gem.ruby_version >= Gem::Version.new("3.4.0.dev") && RUBY_ENGINE != "jruby" ? "#{Module.nesting.first}#build_repo" : "build_repo" + if path == gem_repo1 && caller_locations(1, 1).first.label != exempted_caller raise "Updating gem_repo1 is unsupported -- use gem_repo2 instead" end return unless block_given? @_build_path = "#{path}/gems" @_build_repo = File.basename(path) yield - with_gem_path_as Path.base_system_gem_path do - Dir[Spec::Path.base_system_gem_path.join("gems/rubygems-generate_index*/lib")].first || - raise("Could not find rubygems-generate_index lib directory in #{Spec::Path.base_system_gem_path}") - - command = "generate_index" - command += " --no-compact" if !build_compact_index && gem_command(command + " --help").include?("--[no-]compact") - gem_command command, dir: path - end + options = { build_compact: build_compact_index } + Gem::Indexer.new(path, options).generate_index ensure @_build_path = nil @_build_repo = nil @@ -407,18 +423,23 @@ module Spec end class BundlerBuilder - attr_writer :required_ruby_version - def initialize(context, name, version) - raise "can only build bundler" unless name == "bundler" - @context = context - @version = version || Bundler::VERSION + @spec = Spec::Path.loaded_gemspec.dup + @spec.version = version || Bundler::VERSION + end + + def required_ruby_version + @spec.required_ruby_version + end + + def required_ruby_version=(x) + @spec.required_ruby_version = x end def _build(options = {}) - full_name = "bundler-#{@version}" - build_path = @context.tmp + full_name + full_name = "bundler-#{@spec.version}" + build_path = (options[:build_path] || @context.tmp) + full_name bundler_path = build_path + "#{full_name}.gem" FileUtils.mkdir_p build_path @@ -429,15 +450,19 @@ module Spec target_shipped_file = build_path + target_shipped_file target_shipped_dir = File.dirname(target_shipped_file) FileUtils.mkdir_p target_shipped_dir unless File.directory?(target_shipped_dir) - FileUtils.cp shipped_file, target_shipped_file, preserve: true + FileUtils.cp File.expand_path(shipped_file, @context.source_root), target_shipped_file, preserve: true end - @context.replace_version_file(@version, dir: build_path) - @context.replace_required_ruby_version(@required_ruby_version, dir: build_path) if @required_ruby_version + @context.replace_version_file(@spec.version, dir: build_path) + @context.replace_changelog(@spec.version, dir: build_path) if options[:released] - Spec::BuildMetadata.write_build_metadata(dir: build_path) + Spec::BuildMetadata.write_build_metadata(dir: build_path, version: @spec.version.to_s) - @context.gem_command "build #{@context.relative_gemspec}", dir: build_path + Dir.chdir build_path do + Gem::DefaultUserInteraction.use_ui(Gem::SilentUI.new) do + Gem::Package.build(@spec) + end + end if block_given? yield(bundler_path) @@ -445,7 +470,7 @@ module Spec FileUtils.mv bundler_path, options[:path] end ensure - build_path.rmtree + FileUtils.rm_rf build_path end end @@ -462,6 +487,7 @@ module Spec s.email = "foo@bar.baz" s.homepage = "http://example.com" s.license = "MIT" + s.required_ruby_version = ">= 3.0" end @files = {} end @@ -478,18 +504,13 @@ module Spec @spec.executables = Array(val) @spec.executables.each do |file| executable = "#{@spec.bindir}/#{file}" - shebang = if Bundler.current_ruby.jruby? - "#!/usr/bin/env jruby\n" - else - "#!/usr/bin/env ruby\n" - end + shebang = "#!/usr/bin/env ruby\n" @spec.files << executable write executable, "#{shebang}require_relative '../lib/#{@name}' ; puts #{Builders.constantize(@name)}" end end def add_c_extension - require_paths << "ext" extensions << "ext/extconf.rb" write "ext/extconf.rb", <<-RUBY require "mkmf" @@ -507,7 +528,7 @@ module Spec write "ext/#{name}.c", <<-C #include "ruby.h" - void Init_#{name}_c() { + void Init_#{name}_c(void) { rb_define_module("#{Builders.constantize(name)}_IN_C"); } C @@ -518,7 +539,6 @@ module Spec if options[:rubygems_version] @spec.rubygems_version = options[:rubygems_version] - def @spec.mark_version; end def @spec.validate(*); end end @@ -537,18 +557,16 @@ module Spec when false # do nothing when :yaml - @spec.files << "#{name}.gemspec" @files["#{name}.gemspec"] = @spec.to_yaml else - @spec.files << "#{name}.gemspec" @files["#{name}.gemspec"] = @spec.to_ruby end @files.each do |file, source| - file = Pathname.new(path).join(file) - FileUtils.mkdir_p(file.dirname) - File.open(file, "w") {|f| f.puts source } - File.chmod("+x", file) if @spec.executables.map {|exe| "#{@spec.bindir}/#{exe}" }.include?(file) + full_path = Pathname.new(path).join(file) + FileUtils.mkdir_p(full_path.dirname) + File.open(full_path, "w") {|f| f.puts source } + FileUtils.chmod("+x", full_path) if @spec.executables.map {|exe| "#{@spec.bindir}/#{exe}" }.include?(file) end path end @@ -641,14 +659,14 @@ module Spec destination = opts[:path] || _default_path FileUtils.mkdir_p(lib_path.join(destination)) - if opts[:gemspec] == :yaml || opts[:gemspec] == false + if [:yaml, false].include?(opts[:gemspec]) Dir.chdir(lib_path) do Bundler.rubygems.build(@spec, opts[:skip_validation]) end elsif opts[:skip_validation] - @context.gem_command "build --force #{@spec.name}", dir: lib_path + Dir.chdir(lib_path) { Gem::Package.build(@spec, true) } else - @context.gem_command "build #{@spec.name}", dir: lib_path + Dir.chdir(lib_path) { Gem::Package.build(@spec) } end gem_path = File.expand_path("#{@spec.full_name}.gem", lib_path) @@ -677,54 +695,54 @@ module Spec TEST_CERT = <<~CERT -----BEGIN CERTIFICATE----- - MIIDMjCCAhqgAwIBAgIBATANBgkqhkiG9w0BAQUFADAnMQwwCgYDVQQDDAN5b3Ux + MIIDNTCCAh2gAwIBAgIBATANBgkqhkiG9w0BAQsFADAnMQwwCgYDVQQDDAN5b3Ux FzAVBgoJkiaJk/IsZAEZFgdleGFtcGxlMB4XDTE1MDIwODAwMTIyM1oXDTQyMDYy NTAwMTIyM1owJzEMMAoGA1UEAwwDeW91MRcwFQYKCZImiZPyLGQBGRYHZXhhbXBs - ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANlvFdpN43c4DMS9Jo06 - m0a7k3bQ3HWQ1yrYhZMi77F1F73NpBknYHIzDktQpGn6hs/4QFJT4m4zNEBF47UL - jHU5nTK5rjkS3niGYUjvh3ZEzVeo9zHUlD/UwflDo4ALl3TSo2KY/KdPS/UTdLXL - ajkQvaVJtEDgBPE3DPhlj5whp+Ik3mDHej7qpV6F502leAwYaFyOtlEG/ZGNG+nZ - L0clH0j77HpP42AylHDi+vakEM3xcjo9BeWQ6Vkboic93c9RTt6CWBWxMQP7Nol1 - MOebz9XOSQclxpxWteXNfPRtMdAhmRl76SMI8ywzThNPpa4EH/yz34ftebVOgKyM - nd0CAwEAAaNpMGcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0OBBYEFA7D - n9qo0np23qi3aOYuAAPn/5IdMBYGA1UdEQQPMA2BC3lvdUBleGFtcGxlMBYGA1Ud - EgQPMA2BC3lvdUBleGFtcGxlMA0GCSqGSIb3DQEBBQUAA4IBAQA7Gyk62sWOUX/N - vk4tJrgKESph6Ns8+E36A7n3jt8zCep8ldzMvwTWquf9iqhsC68FilEoaDnUlWw7 - d6oNuaFkv7zfrWGLlvqQJC+cu2X5EpcCksg5oRp8VNbwJysJ6JgwosxzROII8eXc - R+j1j6mDvQYqig2QOnzf480pjaqbP+tspfDFZbhKPrgM3Blrb3ZYuFpv4zkqI7aB - 6fuk2DUhNO1CuwrJA84TqC+jGo73bDKaT5hrIDiaJRrN5+zcWja2uEWrj5jSbep4 - oXdEdyH73hOHMBP40uds3PqnUsxEJhzjB2sCCe1geV24kw9J4m7EQXPVkUKDgKrt - LlpDmOoo + ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMkupYkg3Nd1oXM3fo0d + mVJBWNrni88lKDuIIQXwcKe6XCgiloZG708ecLTOws9+o9MkTl9Wtpf/WGXT98NK + EPUYakd2Fv1SuD1jWYlP7iDR6hB3RkWBm5ziujYftVJ4ZrPD42PLjDASvlh75Tvr + MeM7yq/qkcgNsd9dQyUvMNPks3tla9je7Dt7Auli2IN3CNXys7gIOfwJH0Bb/M6t + y7oUfpoUKAfLzwe61abztgDu1lSNgdFBM1kcxYflyh/FkX5TlAcWeAXzLrnxAXGR + UxXrxW4oPC+kZi/pDRBd7X4zQDx7bCmr1+FsS3M05i3w5E08Tt9iKRk4V8nCmE4i + k6UCAwEAAaNsMGowCQYDVR0TBAIwADAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYE + FOOOFw5TNAqt/TcRRZEU3Dg/58XuMBYGA1UdEQQPMA2BC3lvdUBleGFtcGxlMBYG + A1UdEgQPMA2BC3lvdUBleGFtcGxlMA0GCSqGSIb3DQEBCwUAA4IBAQAy3xnmobxU + 1SyhHvoIXTJmG0wt1DQ/Dqwjy362LpEf1UHt29wtg1Mph58eVtl93z5Vd2t4/O77 + E2BHpSu9ujc6/Br4+2uA/Qk/xRyLBtZAwty6J4uFvOOg985HonN+RCUZbKSUTmtA + TZvNtIDAZFQ8Tu75K4gIBxDcz7biGi4i1VJ3F3GNCNeossr9IQwKvb+UWFq14U5R + IzUnGgMIzcjUG2kKQvddRD1CjS+egtcLvShbOfm5bs4w4rfQ2FPF+Aaf9v7fxa/c + Jrf3K+cB19eAy7O4nlPG1xurvnZd0QpqRk++werrBuKe1Pgga7YBLePfJhzwqcZv + wVOSsB870yeO -----END CERTIFICATE----- CERT TEST_PKEY = <<~PKEY -----BEGIN RSA PRIVATE KEY----- - MIIEowIBAAKCAQEA2W8V2k3jdzgMxL0mjTqbRruTdtDcdZDXKtiFkyLvsXUXvc2k - GSdgcjMOS1CkafqGz/hAUlPibjM0QEXjtQuMdTmdMrmuORLeeIZhSO+HdkTNV6j3 - MdSUP9TB+UOjgAuXdNKjYpj8p09L9RN0tctqORC9pUm0QOAE8TcM+GWPnCGn4iTe - YMd6PuqlXoXnTaV4DBhoXI62UQb9kY0b6dkvRyUfSPvsek/jYDKUcOL69qQQzfFy - Oj0F5ZDpWRuiJz3dz1FO3oJYFbExA/s2iXUw55vP1c5JByXGnFa15c189G0x0CGZ - GXvpIwjzLDNOE0+lrgQf/LPfh+15tU6ArIyd3QIDAQABAoIBACbDqz20TS1gDMa2 - gj0DidNedbflHKjJHdNBru7Ad8NHgOgR1YO2hXdWquG6itVqGMbTF4SV9/R1pIcg - 7qvEV1I+50u31tvOBWOvcYCzU48+TO2n7gowQA3xPHPYHzog1uu48fAOHl0lwgD7 - av9OOK3b0jO5pC08wyTOD73pPWU0NrkTh2+N364leIi1pNuI1z4V+nEuIIm7XpVd - 5V4sXidMTiEMJwE6baEDfTjHKaoRndXrrPo3ryIXmcX7Ag1SwAQwF5fBCRToCgIx - dszEZB1bJD5gA6r+eGnJLB/F60nK607az5o3EdguoB2LKa6q6krpaRCmZU5svvoF - J7xgBPECgYEA8RIzHAQ3zbaibKdnllBLIgsqGdSzebTLKheFuigRotEV3Or/z5Lg - k/nVnThWVkTOSRqXTNpJAME6a4KTdcVSxYP+SdZVO1esazHrGb7xPVb7MWSE1cqp - WEk3Yy8OUOPoPQMc4dyGzd30Mi8IBB6gnFIYOTrpUo0XtkBv8rGGhfsCgYEA5uYn - 6QgL4NqNT84IXylmMb5ia3iBt6lhxI/A28CDtQvfScl4eYK0IjBwdfG6E1vJgyzg - nJzv3xEVo9bz+Kq7CcThWpK5JQaPnsV0Q74Wjk0ShHet15txOdJuKImnh5F6lylC - GTLR9gnptytfMH/uuw4ws0Q2kcg4l5NHKOWOnAcCgYEAvAwIVkhsB0n59Wu4gCZu - FUZENxYWUk/XUyQ6KnZrG2ih90xQ8+iMyqFOIm/52R2fFKNrdoWoALC6E3ct8+ZS - pMRLrelFXx8K3it4SwMJR2H8XBEfFW4bH0UtsW7Zafv+AunUs9LETP5gKG1LgXsq - qgXX43yy2LQ61O365YPZfdUCgYBVbTvA3MhARbvYldrFEnUL3GtfZbNgdxuD9Mee - xig0eJMBIrgfBLuOlqtVB70XYnM4xAbKCso4loKSHnofO1N99siFkRlM2JOUY2tz - kMWZmmxKdFjuF0WZ5f/5oYxI/QsFGC+rUQEbbWl56mMKd5qkvEhKWudxoklF0yiV - ufC8SwKBgDWb8iWqWN5a/kfvKoxFcDM74UHk/SeKMGAL+ujKLf58F+CbweM5pX9C - EUsxeoUEraVWTiyFVNqD81rCdceus9TdBj0ZIK1vUttaRZyrMAwF0uQSfjtxsOpd - l69BkyvzjgDPkmOHVGiSZDLi3YDvypbUpo6LOy4v5rVg5U2F/A0v + MIIEowIBAAKCAQEAyS6liSDc13Whczd+jR2ZUkFY2ueLzyUoO4ghBfBwp7pcKCKW + hkbvTx5wtM7Cz36j0yROX1a2l/9YZdP3w0oQ9RhqR3YW/VK4PWNZiU/uINHqEHdG + RYGbnOK6Nh+1Unhms8PjY8uMMBK+WHvlO+sx4zvKr+qRyA2x311DJS8w0+Sze2Vr + 2N7sO3sC6WLYg3cI1fKzuAg5/AkfQFv8zq3LuhR+mhQoB8vPB7rVpvO2AO7WVI2B + 0UEzWRzFh+XKH8WRflOUBxZ4BfMuufEBcZFTFevFbig8L6RmL+kNEF3tfjNAPHts + KavX4WxLczTmLfDkTTxO32IpGThXycKYTiKTpQIDAQABAoIBABpyrHEWRed5X7aN + kXCBzKSN/LLChT8VNnB6bppLnV501yVbmV2hDlg2EJZkfCMvwIptwnPcKs2uqZ4G + u2gMC6X9Bgkg/YK4u4nZJBiIzoMNYEUL48wYGYS1dcokaapO3nQ8M1+XjyAexrFL + 5btL1IIisScRTQWiGe6FtzcN43sSNkBISyDF5zG4Kodynqi0ekITmMl2q5XLWcsM + KBnmZcRFEmFae2YYczVy8SXNApkZEvN69znvAX1iDNnZ3sJFchXo1nRPt4stOOKw + mydgIYqaNQ22aF3OkblvoA4Y4m+X2Qt1sfkryKa5xTT7DSE81GmmazNI64EWqtES + 6Xde6P0CgYEA+V1vuSnE5fWX188abWMbVwNMC71WfHbntFmI+qwWYPEpickm+RGX + DDfXs5unlVX4KUmjfplgavO29op1GZTuD9TlRnUAV0+0aJnNq4DY6XsHfD84qsBr + gQGEHeJ1cMGNDnZR/EV3eudMalj9Qjpx9NoXNzMykb0/SUYZQemiqwcCgYEAzokC + s0GoHVJqan4dfU0h0G5QPncrajW9DGG1ySxK/A2eqbVB8W2ZQx39OS26/Gydb31p + cR7zm8PZpNbzLqlIMEbD4F6q22xxvYVtDx/HHPjxHMi87yxwQ9uLDUHoMa/LciTO + djv3D1xTDDGxbpjmsdmINetunAs3htxku7JY5PMCgYBs3/TVvXzwgmhHm28Ib4sS + VKgxP/uw4CGORsFd4SDsNp9SP3c6rAltFjyheMaUlzKApFwz/DdyuvIZdp5mCvZe + BzALsS3y8SPtv6lixiDu3/6GqvvM4bKOYuESQzvPfVJfDB4DrTjben2MuUnqTqZO + p6IXQc1EgIJPNcH1W1LgpQKBgAKZlPAevngIBpDqn4JpSyititMOevxuSr/yJvCu + Xw9HOJ0YTAk3APvoT7y9h6IP1/eEU6R56EUotP+vOQZ4WRFKgsK7TllOxyvElzfe + hYom1BoxqLc2Dv+7rsdu8fZWKTB5qCOy44xM9DquEXa79AN/IojTOuQ5++v1sErw + ls/jAoGBANneGe9ogN51mYkrLyg1fhU1i24gFRq+sPGEvsCUoE6Vjw/lawQQ80T8 + v45TFqvhoGpgznqy3qxDJyguquZg6HN2yW6HE2Dvk7uk3XogcjdXgNDmWqb2j0eE + z9pKzHCqfwNVPuYf44Znyo2YeyZ2kHn42MU73oXuFshUs3QHcH+P -----END RSA PRIVATE KEY----- PKEY end diff --git a/spec/bundler/support/bundle b/spec/bundler/support/bundle new file mode 100755 index 0000000000..8f8b535295 --- /dev/null +++ b/spec/bundler/support/bundle @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative "../bundler/support/activate" + +load File.expand_path("bundle", Spec::Path.exedir) diff --git a/spec/bundler/support/bundle.rb b/spec/bundler/support/bundle.rb index 5d6d658040..aa7b121706 100644 --- a/spec/bundler/support/bundle.rb +++ b/spec/bundler/support/bundle.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true -require_relative "activate" +require_relative "path" -load File.expand_path("bundle", Spec::Path.bindir) +warn "#{__FILE__} is deprecated. Please use #{Spec::Path.dev_binstub} instead" + +load Spec::Path.dev_binstub diff --git a/spec/bundler/support/checksums.rb b/spec/bundler/support/checksums.rb index f758559b3b..7b69bba668 100644 --- a/spec/bundler/support/checksums.rb +++ b/spec/bundler/support/checksums.rb @@ -3,6 +3,8 @@ module Spec module Checksums class ChecksumsBuilder + attr_reader :bundler_registered + def initialize(enabled = true, &block) @enabled = enabled @checksums = {} @@ -14,9 +16,11 @@ module Spec @checksums = @checksums.dup end - def checksum(repo, name, version, platform = Gem::Platform::RUBY) + def checksum(repo, name, version, platform = Gem::Platform::RUBY, folder = "gems") + @bundler_registered = true if name == "bundler" + name_tuple = Gem::NameTuple.new(name, version, platform) - gem_file = File.join(repo, "gems", "#{name_tuple.full_name}.gem") + gem_file = File.join(repo, folder, "#{name_tuple.full_name}.gem") File.open(gem_file, "rb") do |f| register(name_tuple, Bundler::Checksum.from_gem(f, "#{gem_file} (via ChecksumsBuilder#checksum)")) end @@ -50,21 +54,26 @@ module Spec end end - def checksums_section(enabled = true, &block) - ChecksumsBuilder.new(enabled, &block) + def checksums_section(enabled = true, bundler_checksum: true, &block) + ChecksumsBuilder.new(enabled, &block).tap do |builder| + next if builder.bundler_registered || !bundler_checksum + + next if Bundler::VERSION.to_s.end_with?(".dev") + builder.checksum(system_gem_path, "bundler", Bundler::VERSION, Gem::Platform::RUBY, "cache") + end end - def checksums_section_when_existing(&block) + def checksums_section_when_enabled(target_lockfile = nil, &block) begin - enabled = lockfile.match?(/^CHECKSUMS$/) + enabled = (target_lockfile || lockfile).match?(/^CHECKSUMS$/) rescue Errno::ENOENT - enabled = false + enabled = true end checksums_section(enabled, &block) end def checksum_to_lock(*args) - checksums_section do |c| + checksums_section(true, bundler_checksum: false) do |c| c.checksum(*args) end.to_s.sub(/^CHECKSUMS\n/, "").strip end @@ -110,5 +119,17 @@ module Spec _checksums, tail = remaining.split("\n\n", 2) head.concat(tail) end + + def checksum_from_package(gem_file, name, version) + name_tuple = Gem::NameTuple.new(name, version) + + checksum = nil + + File.open(gem_file, "rb") do |f| + checksum = Bundler::Checksum.from_gem(f, gemfile) + end + + "#{name_tuple.lock_name} #{checksum.to_lock}" + end end end diff --git a/spec/bundler/support/command_execution.rb b/spec/bundler/support/command_execution.rb index 5639fda3b6..e2915b996d 100644 --- a/spec/bundler/support/command_execution.rb +++ b/spec/bundler/support/command_execution.rb @@ -1,7 +1,36 @@ # frozen_string_literal: true module Spec - CommandExecution = Struct.new(:command, :working_directory, :exitstatus, :original_stdout, :original_stderr) do + class CommandExecution + def initialize(command, timeout:) + @command = command + @timeout = timeout + @original_stdout = String.new + @original_stderr = String.new + end + + attr_accessor :exitstatus, :command, :original_stdout, :original_stderr + attr_reader :timeout + attr_writer :failure_reason + + def raise_error! + return unless failure? + + error_header = if failure_reason == :timeout + "Invoking `#{command}` was aborted after #{timeout} seconds with output:" + else + "Invoking `#{command}` failed with output:" + end + + raise <<~ERROR + #{error_header} + + ---------------------------------------------------------------------- + #{stdboth} + ---------------------------------------------------------------------- + ERROR + end + def to_s "$ #{command}" end @@ -12,16 +41,11 @@ module Spec end def stdout - original_stdout + normalize(original_stdout) end - # Can be removed once/if https://github.com/oneclick/rubyinstaller2/pull/369 is resolved def stderr - return original_stderr unless Gem.win_platform? - - original_stderr.split("\n").reject do |l| - l.include?("operating_system_defaults") - end.join("\n") + normalize(original_stderr) end def to_s_verbose @@ -42,5 +66,13 @@ module Spec return true unless exitstatus exitstatus > 0 end + + private + + attr_reader :failure_reason + + def normalize(string) + string.dup.force_encoding(Encoding::UTF_8).scrub.strip.gsub("\r\n", "\n") + end end end diff --git a/spec/bundler/support/env.rb b/spec/bundler/support/env.rb new file mode 100644 index 0000000000..0899bd82a3 --- /dev/null +++ b/spec/bundler/support/env.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Spec + module Env + def ruby_core? + File.exist?(File.expand_path("../../../lib/bundler/bundler.gemspec", __dir__)) + end + + def rubylib + ENV["RUBYLIB"].to_s.split(File::PATH_SEPARATOR) + end + end +end diff --git a/spec/bundler/support/filters.rb b/spec/bundler/support/filters.rb index 8e164af756..2be25b4a78 100644 --- a/spec/bundler/support/filters.rb +++ b/spec/bundler/support/filters.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true class RequirementChecker < Proc - def self.against(present) - provided = Gem::Version.new(present) - + def self.against(provided) new do |required| - !Gem::Requirement.new(required).satisfied_by?(provided) + requirement = Gem::Requirement.new(required) + + !requirement.satisfied_by?(provided) end.tap do |checker| checker.provided = provided end @@ -14,18 +14,17 @@ class RequirementChecker < Proc attr_accessor :provided def inspect - "\"!= #{provided}\"" + "\"#{provided}\"" end end +git_version = Gem::Version.new(`git --version`[/(\d+\.\d+\.\d+)/, 1]) + RSpec.configure do |config| config.filter_run_excluding realworld: true - git_version = Bundler::Source::Git::GitProxy.new(nil, nil).version - + config.filter_run_excluding rubygems: RequirementChecker.against(Gem.rubygems_version) config.filter_run_excluding git: RequirementChecker.against(git_version) - config.filter_run_excluding bundler: RequirementChecker.against(Bundler::VERSION.split(".")[0]) - config.filter_run_excluding rubygems: RequirementChecker.against(Gem::VERSION) config.filter_run_excluding ruby_repo: !ENV["GEM_COMMAND"].nil? config.filter_run_excluding no_color_tty: Gem.win_platform? || !ENV["GITHUB_ACTION"].nil? config.filter_run_excluding permissions: Gem.win_platform? @@ -33,6 +32,11 @@ RSpec.configure do |config| config.filter_run_excluding jruby_only: RUBY_ENGINE != "jruby" config.filter_run_excluding truffleruby_only: RUBY_ENGINE != "truffleruby" config.filter_run_excluding man: Gem.win_platform? + config.filter_run_excluding mri_only: RUBY_ENGINE != "ruby" config.filter_run_when_matching :focus unless ENV["CI"] + + config.before(:each, :bundler) do |example| + bundle_config "simulate_version #{example.metadata[:bundler]}" + end end diff --git a/spec/bundler/support/hax.rb b/spec/bundler/support/hax.rb index c7fe3637cc..46718f5fa4 100644 --- a/spec/bundler/support/hax.rb +++ b/spec/bundler/support/hax.rb @@ -19,35 +19,56 @@ module Gem @default_specifications_dir = nil end - if ENV["BUNDLER_SPEC_WINDOWS"] - @@win_platform = true # rubocop:disable Style/ClassVars - end + spec_platform = ENV["BUNDLER_SPEC_PLATFORM"] + if spec_platform + if /mingw|mswin/.match?(spec_platform) + @@win_platform = nil # rubocop:disable Style/ClassVars + RbConfig::CONFIG["host_os"] = spec_platform.gsub(/^[^-]+-/, "").tr("-", "_") + end - if ENV["BUNDLER_SPEC_PLATFORM"] - previous_platforms = @platforms - previous_local = Platform.local + RbConfig::CONFIG["arch"] = spec_platform class Platform - @local = new(ENV["BUNDLER_SPEC_PLATFORM"]) + @local = nil end - @platforms = previous_platforms.map {|platform| platform == previous_local ? Platform.local : platform } + @platforms = [] end if ENV["BUNDLER_SPEC_GEM_SOURCES"] self.sources = [ENV["BUNDLER_SPEC_GEM_SOURCES"]] end - if ENV["BUNDLER_IGNORE_DEFAULT_GEM"] - module RemoveDefaultBundlerStub - def default_stubs(pattern = "*") - super.delete_if {|stub| stub.name == "bundler" } + if ENV["BUNDLER_SPEC_READ_ONLY"] + module ReadOnly + def open(file, mode) + if file != IO::NULL && mode == "wb" + raise Errno::EROFS + else + super + end end end - class Specification - class << self - prepend RemoveDefaultBundlerStub + File.singleton_class.prepend ReadOnly + end + + if ENV["BUNDLER_SPEC_FAKE_RESOLVE"] + module FakeResolv + def getaddrinfo(host, port) + if host == ENV["BUNDLER_SPEC_FAKE_RESOLVE"] + [["AF_INET", port, "127.0.0.1", "127.0.0.1", 2, 2, 17]] + else + super + end end end + + Socket.singleton_class.prepend FakeResolv end end + +# mise installed rubygems_plugin.rb to system wide `site_ruby` directory. +# This empty module avoid to call `mise` command. +module ReshimInstaller + def self.reshim; end +end diff --git a/spec/bundler/support/helpers.rb b/spec/bundler/support/helpers.rb index 1ad9cc78ca..b0d4b5008b 100644 --- a/spec/bundler/support/helpers.rb +++ b/spec/bundler/support/helpers.rb @@ -1,46 +1,36 @@ # frozen_string_literal: true -require_relative "command_execution" require_relative "the_bundle" require_relative "path" +require_relative "options" +require_relative "subprocess" module Spec module Helpers include Spec::Path + include Spec::Options + include Spec::Subprocess + + def self.extended(mod) + mod.extend Spec::Path + mod.extend Spec::Options + mod.extend Spec::Subprocess + end def reset! Dir.glob("#{tmp}/{gems/*,*}", File::FNM_DOTMATCH).each do |dir| next if %w[base base_system remote1 rubocop standard gems rubygems . ..].include?(File.basename(dir)) - FileUtils.rm_rf(dir) + FileUtils.rm_r(dir) end FileUtils.mkdir_p(home) FileUtils.mkdir_p(tmpdir) - reset_paths! - end - - def reset_paths! Bundler.reset! + Bundler::Source::Git::GitProxy.reset Gem.clear_paths end - def the_bundle(*args) - TheBundle.new(*args) - end - - def command_executions - @command_executions ||= [] - end - - def last_command - command_executions.last || raise("There is no last command") - end - - def out - last_command.stdout - end - - def err - last_command.stderr + def the_bundle + TheBundle.new end MAJOR_DEPRECATION = /^\[DEPRECATED\]\s*/ @@ -50,11 +40,7 @@ module Spec end def deprecations - err.split("\n").select {|l| l =~ MAJOR_DEPRECATION }.join("\n").split(MAJOR_DEPRECATION) - end - - def exitstatus - last_command.exitstatus + err.split("\n").filter_map {|l| l.sub(MAJOR_DEPRECATION, "") if l.match?(MAJOR_DEPRECATION) } end def run(cmd, *args) @@ -69,7 +55,7 @@ module Spec begin #{ruby} rescue LoadError => e - warn "ZOMG LOAD ERROR" if e.message.include?("-- #{name}") + warn e.message if e.message.include?("-- #{name}") end RUBY opts = args.last.is_a?(Hash) ? args.pop : {} @@ -77,6 +63,10 @@ module Spec run(cmd, *args) end + def in_bundled_app(cmd, options = {}) + sys_exec(cmd, dir: bundled_app, raise_on_error: options[:raise_on_error]) + end + def bundle(cmd, options = {}, &block) bundle_bin = options.delete(:bundle_bin) bundle_bin ||= installed_bindir.join("bundle") @@ -84,29 +74,24 @@ module Spec env = options.delete(:env) || {} requires = options.delete(:requires) || [] - realworld = RSpec.current_example.metadata[:realworld] - artifice = options.delete(:artifice) do - if realworld - "vcr" - else - "fail" - end - end - if artifice - requires << "#{Path.spec_dir}/support/artifice/#{artifice}.rb" - end + dir = options.delete(:dir) || bundled_app + custom_load_path = options.delete(:load_path) load_path = [] - load_path << spec_dir + load_path << custom_load_path if custom_load_path + + build_env_options = { load_path: load_path, requires: requires, env: env } + build_env_options.merge!(artifice: options.delete(:artifice)) if options.key?(:artifice) || cmd.start_with?("exec") + + match_source(cmd) + + env = build_env(build_env_options) - dir = options.delete(:dir) || bundled_app raise_on_error = options.delete(:raise_on_error) args = options.map do |k, v| case v - when nil - next when true " --#{k}" when false @@ -116,20 +101,31 @@ module Spec end end.join - ruby_cmd = build_ruby_cmd({ load_path: load_path, requires: requires, env: env }) - cmd = "#{ruby_cmd} #{bundle_bin} #{cmd}#{args}" + cmd = "#{Gem.ruby} #{bundle_bin} #{cmd}#{args}" sys_exec(cmd, { env: env, dir: dir, raise_on_error: raise_on_error }, &block) end + def main_source(dir) + gemfile = File.expand_path("Gemfile", dir) + return unless File.exist?(gemfile) + + match = File.readlines(gemfile).first.match(/source ["'](?<source>[^"']+)["']/) + return unless match + + match[:source] + end + def bundler(cmd, options = {}) - options[:bundle_bin] = system_gem_path.join("bin/bundler") + options[:bundle_bin] = system_gem_path("bin/bundler") bundle(cmd, options) end def ruby(ruby, options = {}) - ruby_cmd = build_ruby_cmd + env = build_env({ artifice: nil }.merge(options)) escaped_ruby = ruby.shellescape - sys_exec(%(#{ruby_cmd} -w -e #{escaped_ruby}), options) + options[:env] = env if env + options[:dir] ||= bundled_app + sys_exec(%(#{Gem.ruby} -w -e #{escaped_ruby}), options) end def load_error_ruby(ruby, name, opts = {}) @@ -137,26 +133,49 @@ module Spec begin #{ruby} rescue LoadError => e - warn "ZOMG LOAD ERROR" if e.message.include?("-- #{name}") + warn e.message if e.message.include?("-- #{name}") end R end - def build_ruby_cmd(options = {}) - libs = options.delete(:load_path) - lib_option = libs ? "-I#{libs.join(File::PATH_SEPARATOR)}" : [] + def build_env(options = {}) + env = options.delete(:env) || {} + libs = options.delete(:load_path) || [] + env["RUBYOPT"] = opt_add("-I#{libs.join(File::PATH_SEPARATOR)}", env["RUBYOPT"]) if libs.any? + + current_example = RSpec.current_example + + main_source = @gemfile_source if defined?(@gemfile_source) + compact_index_main_source = main_source&.start_with?("https://gem.repo", "https://gems.security") requires = options.delete(:requires) || [] + requires << hax - hax_path = "#{Path.spec_dir}/support/hax.rb" + artifice = options.delete(:artifice) do + if current_example && current_example.metadata[:realworld] + "vcr" + elsif compact_index_main_source + env["BUNDLER_SPEC_GEM_REPO"] ||= + case main_source + when "https://gem.repo1" then gem_repo1.to_s + when "https://gem.repo2" then gem_repo2.to_s + when "https://gem.repo3" then gem_repo3.to_s + when "https://gem.repo4" then gem_repo4.to_s + when "https://gems.security" then security_repo.to_s + end + + "compact_index" + else + "fail" + end + end + if artifice + requires << "#{Path.spec_dir}/support/artifice/#{artifice}.rb" + end - # For specs that need to ignore the default Bundler gem, load hax before - # anything else since other stuff may actually load bundler and not skip - # the default version - options[:env]&.include?("BUNDLER_IGNORE_DEFAULT_GEM") ? requires.prepend(hax_path) : requires.append(hax_path) - require_option = requires.map {|r| "-r#{r}" } + requires.each {|r| env["RUBYOPT"] = opt_add("-r#{r}", env["RUBYOPT"]) } - [Gem.ruby, *lib_option, *require_option].compact.join(" ") + env end def gembin(cmd, options = {}) @@ -164,85 +183,55 @@ module Spec sys_exec(cmd.to_s, options) end - def gem_command(command, options = {}) + def sys_exec(cmd, options = {}, &block) env = options[:env] || {} - env["RUBYOPT"] = opt_add(opt_add("-r#{spec_dir}/support/hax.rb", env["RUBYOPT"]), ENV["RUBYOPT"]) + env["RUBYOPT"] = opt_add(opt_add("-r#{spec_dir}/support/switch_rubygems.rb", env["RUBYOPT"]), ENV["RUBYOPT"]) options[:env] = env - sys_exec("#{Path.gem_bin} #{command}", options) - end - def rake - "#{Gem.ruby} -S #{ENV["GEM_PATH"]}/bin/rake" + sh(cmd, options, &block) end - def git(cmd, path, options = {}) - sys_exec("git #{cmd}", options.merge(dir: path)) - end - - def sys_exec(cmd, options = {}) - env = options[:env] || {} - env["RUBYOPT"] = opt_add(opt_add("-r#{spec_dir}/support/switch_rubygems.rb", env["RUBYOPT"]), ENV["RUBYOPT"]) - dir = options[:dir] || bundled_app - command_execution = CommandExecution.new(cmd.to_s, dir) - - require "open3" - require "shellwords" - Open3.popen3(env, *cmd.shellsplit, chdir: dir) do |stdin, stdout, stderr, wait_thr| - yield stdin, stdout, wait_thr if block_given? - stdin.close - - stdout_read_thread = Thread.new { stdout.read } - stderr_read_thread = Thread.new { stderr.read } - command_execution.original_stdout = stdout_read_thread.value.strip - command_execution.original_stderr = stderr_read_thread.value.strip - - status = wait_thr.value - command_execution.exitstatus = if status.exited? - status.exitstatus - elsif status.signaled? - exit_status_for_signal(status.termsig) - end + def bundle_config(config = nil, path = bundled_app(".bundle/config")) + if config.is_a?(String) + key, value = config.split(" ", 2) + config = { Bundler::Settings.key_for(key) => value } end - unless options[:raise_on_error] == false || command_execution.success? - raise <<~ERROR + current = File.exist?(path) ? Psych.load_file(path) : {} + return current unless config - Invoking `#{cmd}` failed with output: - ---------------------------------------------------------------------- - #{command_execution.stdboth} - ---------------------------------------------------------------------- - ERROR - end - - command_executions << command_execution - - command_execution.stdout - end + current = {} if current.empty? - def all_commands_output - return "" if command_executions.empty? + FileUtils.mkdir_p(File.dirname(path)) - "\n\nCommands:\n#{command_executions.map(&:to_s_verbose).join("\n\n")}" - end + new_config = current.merge(config).compact - def config(config = nil, path = bundled_app(".bundle/config")) - return Psych.load_file(path) unless config - FileUtils.mkdir_p(File.dirname(path)) - File.open(path, "w") do |f| - f.puts config.to_yaml + File.open(path, "w+") do |f| + f.puts new_config.to_yaml end - config + + new_config end - def global_config(config = nil) - config(config, home(".bundle/config")) + def bundle_config_global(config = nil) + bundle_config(config, home(".bundle/config")) end def create_file(path, contents = "") + contents = strip_whitespace(contents) path = Pathname.new(path).expand_path(bundled_app) unless path.is_a?(Pathname) path.dirname.mkpath - File.open(path.to_s, "w") do |f| - f.puts strip_whitespace(contents) + path.write(contents) + + # if the file is a script, create respective bat file on Windows + if contents.start_with?("#!") + path.chmod(0o755) + if Gem.win_platform? + path.sub_ext(".bat").write <<~SCRIPT + @ECHO OFF + @"ruby.exe" "%~dpn0" %* + SCRIPT + end end end @@ -252,6 +241,7 @@ module Spec if contents.nil? read_gemfile else + match_source(contents) create_file(args.pop || "Gemfile", contents) end end @@ -296,38 +286,123 @@ module Spec bundle :lock, opts end + def base_system_gems(*names, **options) + system_gems names.map {|name| find_base_path(name) }, **options + end + def system_gems(*gems) gems = gems.flatten options = gems.last.is_a?(Hash) ? gems.pop : {} install_dir = options.fetch(:path, system_gem_path) default = options.fetch(:default, false) - with_gem_path_as(install_dir) do - gem_repo = options.fetch(:gem_repo, gem_repo1) - gems.each do |g| - gem_name = g.to_s - if gem_name.start_with?("bundler") - version = gem_name.match(/\Abundler-(?<version>.*)\z/)[:version] if gem_name != "bundler" - with_built_bundler(version) {|gem_path| install_gem(gem_path, install_dir, default) } - elsif %r{\A(?:[a-zA-Z]:)?/.*\.gem\z}.match?(gem_name) - install_gem(gem_name, install_dir, default) - else - install_gem("#{gem_repo}/gems/#{gem_name}.gem", install_dir, default) - end + gems.each do |g| + gem_name = g.to_s + bundler = gem_name.match(/\Abundler-(?<version>.*)\z/) + + if bundler + with_built_bundler(bundler[:version], released: options.fetch(:released, false)) {|gem_path| install_gem(gem_path, install_dir, default) } + elsif %r{\A(?:[a-zA-Z]:)?/.*\.gem\z}.match?(gem_name) + install_gem(gem_name, install_dir, default) + else + gem_repo = options.fetch(:gem_repo, gem_repo1) + install_gem("#{gem_repo}/gems/#{gem_name}.gem", install_dir, default) end end end + def self.install_dev_bundler + extend self + + with_built_bundler(nil, build_path: tmp_root) {|gem_path| install_gem(gem_path, pristine_system_gem_path) } + end + def install_gem(path, install_dir, default = false) - raise "OMG `#{path}` does not exist!" unless File.exist?(path) + raise ArgumentError, "`#{path}` does not exist!" unless File.exist?(path) + + require "rubygems/installer" + + with_simulated_platform do + installer = Gem::Installer.at( + path.to_s, + install_dir: install_dir.to_s, + document: [], + ignore_dependencies: true, + wrappers: true, + env_shebang: true, + force: true + ) + installer.install + end + + if default + gem = Pathname.new(path).basename.to_s.match(/(.*)\.gem/)[1] - args = "--no-document --ignore-dependencies --verbose --local --install-dir #{install_dir}" - args += " --default" if default + # Revert Gem::Installer#write_spec and apply Gem::Installer#write_default_spec + FileUtils.mkdir_p File.join(install_dir, "specifications", "default") + File.rename File.join(install_dir, "specifications", gem + ".gemspec"), + File.join(install_dir, "specifications", "default", gem + ".gemspec") - gem_command "install #{args} '#{path}'" + # Revert Gem::Installer#write_cache_file + File.delete File.join(install_dir, "cache", gem + ".gem") + end end - def with_built_bundler(version = nil, &block) - Builders::BundlerBuilder.new(self, "bundler", version)._build(&block) + def uninstall_gem(name, options = {}) + require "rubygems/uninstaller" + + gem_home = options.dig(:env, "GEM_HOME") || system_gem_path.to_s + + with_env_vars("GEM_HOME" => gem_home) do + Gem.clear_paths + + uninstaller = Gem::Uninstaller.new( + name, + ignore: true, + executables: true, + all: true + ) + uninstaller.uninstall + ensure + Gem.clear_paths + end + end + + def installed_gems_list(options = {}) + gem_home = options.dig(:env, "GEM_HOME") || system_gem_path.to_s + + # Temporarily set GEM_HOME for the command + old_gem_home = ENV["GEM_HOME"] + ENV["GEM_HOME"] = gem_home + Gem.clear_paths + + begin + require "rubygems/commands/list_command" + + # Capture output from the list command + require "stringio" + output_io = StringIO.new + cmd = Gem::Commands::ListCommand.new + cmd.ui = Gem::StreamUI.new(StringIO.new, output_io, StringIO.new, false) + cmd.invoke + output = output_io.string.strip + ensure + ENV["GEM_HOME"] = old_gem_home + Gem.clear_paths + end + + # Create a fake command execution so `out` helper works + command_execution = Spec::CommandExecution.new("gem list", timeout: 60) + command_execution.original_stdout << output + command_execution.exitstatus = 0 + command_executions << command_execution + + output + end + + def with_built_bundler(version = nil, opts = {}, &block) + require_relative "builders" + + Builders::BundlerBuilder.new(self, "bundler", version)._build(opts, &block) end def with_gem_path_as(path) @@ -355,20 +430,40 @@ module Spec ENV.replace(backup) end - def with_path_added(path) - with_path_as([path.to_s, ENV["PATH"]].join(File::PATH_SEPARATOR)) do - yield + # Simulate the platform set by BUNDLER_SPEC_PLATFORM for in-process + # operations, mirroring what hax.rb does for subprocesses. + def with_simulated_platform + spec_platform = ENV["BUNDLER_SPEC_PLATFORM"] + unless spec_platform + return yield end - end - def opt_add(option, options) - [option.strip, options].compact.reject(&:empty?).join(" ") - end + old_arch = RbConfig::CONFIG["arch"] + old_host_os = RbConfig::CONFIG["host_os"] - def opt_remove(option, options) - return unless options + if /mingw|mswin/.match?(spec_platform) + Gem.class_variable_set(:@@win_platform, nil) # rubocop:disable Style/ClassVars + RbConfig::CONFIG["host_os"] = spec_platform.gsub(/^[^-]+-/, "").tr("-", "_") + end + + RbConfig::CONFIG["arch"] = spec_platform + Gem::Platform.instance_variable_set(:@local, nil) + Gem.instance_variable_set(:@platforms, []) - options.split(" ").reject {|opt| opt.strip == option.strip }.join(" ") + yield + ensure + if spec_platform + RbConfig::CONFIG["arch"] = old_arch + RbConfig::CONFIG["host_os"] = old_host_os + Gem::Platform.instance_variable_set(:@local, nil) + Gem.instance_variable_set(:@platforms, []) + end + end + + def with_path_added(path) + with_path_as([path.to_s, ENV["PATH"]].join(File::PATH_SEPARATOR)) do + yield + end end def break_git! @@ -381,49 +476,43 @@ module Spec end def with_fake_man - skip "fake_man is not a Windows friendly binstub" if Gem.win_platform? - FileUtils.mkdir_p(tmp("fake_man")) - File.open(tmp("fake_man/man"), "w", 0o755) do |f| - f.puts "#!/usr/bin/env ruby\nputs ARGV.inspect\n" - end + create_file(tmp("fake_man/man"), <<~SCRIPT) + #!/usr/bin/env ruby + puts ARGV.inspect + SCRIPT with_path_added(tmp("fake_man")) { yield } end def pristine_system_gems(*gems) - FileUtils.rm_rf(system_gem_path) - - system_gems(*gems) - end - - def realworld_system_gems(*gems) - gems = gems.flatten - opts = gems.last.is_a?(Hash) ? gems.pop : {} - path = opts.fetch(:path, system_gem_path) + FileUtils.rm_r(system_gem_path) - with_gem_path_as(path) do - gems.each do |gem| - gem_command "install --no-document #{gem}" - end + if gems.any? + system_gems(*gems) + else + default_system_gems end end def cache_gems(*gems, gem_repo: gem_repo1) gems = gems.flatten - FileUtils.rm_rf("#{bundled_app}/vendor/cache") FileUtils.mkdir_p("#{bundled_app}/vendor/cache") gems.each do |g| path = "#{gem_repo}/gems/#{g}.gem" - raise "OMG `#{path}` does not exist!" unless File.exist?(path) + raise ArgumentError, "`#{path}` does not exist!" unless File.exist?(path) FileUtils.cp(path, "#{bundled_app}/vendor/cache") end end def simulate_new_machine - FileUtils.rm_rf bundled_app(".bundle") - pristine_system_gems :bundler + FileUtils.rm_r bundled_app(".bundle") + pristine_system_gems + end + + def default_system_gems + FileUtils.cp_r pristine_system_gem_path, system_gem_path end def simulate_ruby_platform(ruby_platform) @@ -437,21 +526,11 @@ module Spec def simulate_platform(platform) old = ENV["BUNDLER_SPEC_PLATFORM"] ENV["BUNDLER_SPEC_PLATFORM"] = platform.to_s - yield if block_given? + yield ensure ENV["BUNDLER_SPEC_PLATFORM"] = old if block_given? end - def simulate_windows(platform = x86_mswin32) - old = ENV["BUNDLER_SPEC_WINDOWS"] - ENV["BUNDLER_SPEC_WINDOWS"] = "true" - simulate_platform platform do - yield - end - ensure - ENV["BUNDLER_SPEC_WINDOWS"] = old - end - def current_ruby_minor Gem.ruby_version.segments.tap {|s| s.delete_at(2) }.join(".") end @@ -460,18 +539,12 @@ module Spec ruby_major_minor.map.with_index {|s, i| i == 1 ? s + 1 : s }.join(".") end - def previous_ruby_minor - return "2.7" if ruby_major_minor == [3, 0] - - ruby_major_minor.map.with_index {|s, i| i == 1 ? s - 1 : s }.join(".") - end - def ruby_major_minor Gem.ruby_version.segments[0..1] end def revision_for(path) - sys_exec("git rev-parse HEAD", dir: path).strip + git("rev-parse HEAD", path).strip end def with_read_only(pattern) @@ -523,41 +596,34 @@ module Spec end end - def require_rack - # need to hack, so we can require rack + def require_rack_test + # need to hack, so we can require rack for testing old_gem_home = ENV["GEM_HOME"] - ENV["GEM_HOME"] = Spec::Path.base_system_gem_path.to_s - require "rack" + ENV["GEM_HOME"] = Spec::Path.scoped_base_system_gem_path.to_s + require "rack/test" ENV["GEM_HOME"] = old_gem_home end - def wait_for_server(host, port, seconds = 15) - tries = 0 - sleep 0.5 - TCPSocket.new(host, port) - rescue StandardError => e - raise(e) if tries > (seconds * 2) - tries += 1 - retry - end - - def find_unused_port - port = 21_453 - begin - port += 1 while TCPSocket.new("127.0.0.1", port) - rescue StandardError - false - end - port - end - def exit_status_for_signal(signal_number) # For details see: https://en.wikipedia.org/wiki/Exit_status#Shell_and_scripts 128 + signal_number end + def empty_repo4 + FileUtils.rm_r gem_repo4 + + build_repo4 {} + end + private + def match_source(contents) + match = /source ["']?(?<source>http[^"']+)["']?/.match(contents) + return unless match + + @gemfile_source = match[:source] + end + def git_root_dir? root.to_s == `git rev-parse --show-toplevel`.chomp end diff --git a/spec/bundler/support/indexes.rb b/spec/bundler/support/indexes.rb index 086a311551..1fbdd49abe 100644 --- a/spec/bundler/support/indexes.rb +++ b/spec/bundler/support/indexes.rb @@ -66,7 +66,6 @@ module Spec end def should_conservative_resolve_and_include(opts, unlock, specs) - # empty unlock means unlock all opts = Array(opts) search = Bundler::GemVersionPromoter.new.tap do |s| s.level = opts.first @@ -77,8 +76,8 @@ module Spec def an_awesome_index build_index do - gem "rack", %w[0.8 0.9 0.9.1 0.9.2 1.0 1.1] - gem "rack-mount", %w[0.4 0.5 0.5.1 0.5.2 0.6] + gem "myrack", %w[0.8 0.9 0.9.1 0.9.2 1.0 1.1] + gem "myrack-mount", %w[0.4 0.5 0.5.1 0.5.2 0.6] # --- Pre-release support gem "RubyGems\0", ["1.3.2"] @@ -89,10 +88,10 @@ module Spec gem "actionpack", version do dep "activesupport", version if version >= v("3.0.0.beta") - dep "rack", "~> 1.1" - dep "rack-mount", ">= 0.5" - elsif version > v("2.3") then dep "rack", "~> 1.0.0" - elsif version > v("2.0.0") then dep "rack", "~> 0.9.0" + dep "myrack", "~> 1.1" + dep "myrack-mount", ">= 0.5" + elsif version > v("2.3") then dep "myrack", "~> 1.0.0" + elsif version > v("2.0.0") then dep "myrack", "~> 0.9.0" end end gem "activerecord", version do @@ -123,7 +122,7 @@ module Spec end versions "1.0 1.2 1.2.1 1.2.2 1.3 1.3.0.1 1.3.5 1.4.0 1.4.2 1.4.2.1" do |version| - platforms "ruby java mswin32 mingw32 x64-mingw32" do |platform| + platforms "ruby java mswin32 mingw32 x64-mingw-ucrt" do |platform| next if version == v("1.4.2.1") && platform != pl("x86-mswin32") next if version == v("1.4.2") && platform == pl("x86-mswin32") gem "nokogiri", version, platform do @@ -367,7 +366,7 @@ module Spec def a_circular_index build_index do - gem "rack", "1.0.1" + gem "myrack", "1.0.1" gem("foo", "0.2.6") do dep "bar", ">= 0" end diff --git a/spec/bundler/support/matchers.rb b/spec/bundler/support/matchers.rb index 0f027dcf04..5a3c38a4db 100644 --- a/spec/bundler/support/matchers.rb +++ b/spec/bundler/support/matchers.rb @@ -52,7 +52,7 @@ module Spec end def self.define_compound_matcher(matcher, preconditions, &declarations) - raise "Must have preconditions to define a compound matcher" if preconditions.empty? + raise ArgumentError, "Must have preconditions to define a compound matcher" if preconditions.empty? define_method(matcher) do |*expected, &block_arg| Precondition.new( RSpec::Matchers::DSL::Matcher.new(matcher, declarations, self, *expected, &block_arg), @@ -116,8 +116,9 @@ module Spec source = opts.delete(:source) groups = Array(opts.delete(:groups)).map(&:inspect).join(", ") opts[:raise_on_error] = false - @errors = names.map do |full_name| + @errors = names.filter_map do |full_name| name, version, platform = full_name.split(/\s+/) + platform ||= "ruby" require_path = name.tr("-", "/") version_const = name == "bundler" ? "Bundler::VERSION" : Spec::Builders.constantize(name) source_const = "#{Spec::Builders.constantize(name)}_SOURCE" @@ -127,6 +128,7 @@ module Spec require '#{require_path}' actual_version, actual_platform = #{version_const}.split(/\s+/, 2) + actual_platform ||= "ruby" unless Gem::Version.new(actual_version) == Gem::Version.new('#{version}') puts actual_version exit 64 @@ -150,14 +152,14 @@ module Spec end if exitstatus == 65 actual_platform = out.split("\n").last - next "#{name} was expected to be of platform #{platform || "ruby"} but was #{actual_platform || "ruby"}" + next "#{name} was expected to be of platform #{platform} but was #{actual_platform}" end if exitstatus == 66 actual_source = out.split("\n").last next "Expected #{name} (#{version}) to be installed from `#{source}`, was actually from `#{actual_source}`" end next "Command to check for inclusion of gem #{full_name} failed" - end.compact + end @errors.empty? end @@ -166,7 +168,7 @@ module Spec opts = names.last.is_a?(Hash) ? names.pop : {} groups = Array(opts.delete(:groups)).map(&:inspect).join(", ") opts[:raise_on_error] = false - @errors = names.map do |name| + @errors = names.filter_map do |name| name, version = name.split(/\s+/, 2) ruby <<-R, opts begin @@ -192,7 +194,7 @@ module Spec next "command to check version of #{name} installed failed" unless exitstatus == 64 next "expected #{name} to not be installed, but it was" if version.nil? next "expected #{name} (#{version}) not to be installed, but it was" - end.compact + end @errors.empty? end diff --git a/spec/bundler/support/options.rb b/spec/bundler/support/options.rb new file mode 100644 index 0000000000..551fa1acd8 --- /dev/null +++ b/spec/bundler/support/options.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Spec + module Options + def opt_add(option, options) + [option.strip, options].compact.reject(&:empty?).join(" ") + end + + def opt_remove(option, options) + return unless options + + options.split(" ").reject {|opt| opt.strip == option.strip }.join(" ") + end + end +end diff --git a/spec/bundler/support/path.rb b/spec/bundler/support/path.rb index 7352d5a353..2e6486412f 100644 --- a/spec/bundler/support/path.rb +++ b/spec/bundler/support/path.rb @@ -1,12 +1,16 @@ # frozen_string_literal: true -require "pathname" +require "pathname" unless defined?(Pathname) require "rbconfig" +require_relative "env" + module Spec module Path + include Spec::Env + def source_root - @source_root ||= Pathname.new(ruby_core? ? "../../.." : "../..").expand_path(__dir__) + @source_root ||= Pathname.new(ruby_core? ? "../../.." : "../../bundler").expand_path(__dir__) end def root @@ -21,12 +25,8 @@ module Spec @relative_gemspec ||= ruby_core? ? "lib/bundler/bundler.gemspec" : "bundler.gemspec" end - def gemspec_dir - @gemspec_dir ||= gemspec.parent - end - def loaded_gemspec - @loaded_gemspec ||= Gem::Specification.load(gemspec.to_s) + @loaded_gemspec ||= Dir.chdir(source_root) { Gem::Specification.load(gemspec.to_s) } end def test_gemfile @@ -45,8 +45,16 @@ module Spec @dev_gemfile ||= tool_dir.join("dev_gems.rb") end + def dev_binstub + @dev_binstub ||= bindir.join("bundle") + end + def bindir - @bindir ||= source_root.join(ruby_core? ? "libexec" : "exe") + @bindir ||= source_root.join(ruby_core? ? "spec/bin" : "../bin") + end + + def exedir + @exedir ||= source_root.join(ruby_core? ? "libexec" : "exe") end def installed_bindir @@ -58,23 +66,27 @@ module Spec end def gem_bin - @gem_bin ||= ruby_core? ? ENV["GEM_COMMAND"] : "gem" + @gem_bin ||= ENV["GEM_COMMAND"] || "gem" end def path env_path = ENV["PATH"] - env_path = env_path.split(File::PATH_SEPARATOR).reject {|path| path == bindir.to_s }.join(File::PATH_SEPARATOR) if ruby_core? + env_path = env_path.split(File::PATH_SEPARATOR).reject {|path| path == exedir.to_s }.join(File::PATH_SEPARATOR) if ruby_core? env_path end def spec_dir - @spec_dir ||= source_root.join(ruby_core? ? "spec/bundler" : "spec") + @spec_dir ||= source_root.join(ruby_core? ? "spec/bundler" : "../spec") end def man_dir @man_dir ||= lib_dir.join("bundler/man") end + def hax + @hax ||= spec_dir.join("support/hax.rb") + end + def tracked_files @tracked_files ||= git_ls_files(tracked_files_glob) end @@ -98,7 +110,28 @@ module Spec end def tmp(*path) - source_root.join("tmp", scope, *path) + tmp_root.join("#{test_env_version}.#{scope}").join(*path) + end + + def tmp_root + if ruby_core? && (tmpdir = ENV["TMPDIR"]) + # Use realpath to resolve any symlinks in TMPDIR (e.g., on macOS /var -> /private/var) + real = begin + File.realpath(tmpdir) + rescue Errno::ENOENT, Errno::EACCES + tmpdir + end + Pathname(real) + else + (ruby_core? ? source_root : source_root.parent).join("tmp") + end + end + + # Bump this version whenever you make a breaking change to the spec setup + # that requires regenerating tmp/. + + def test_env_version + 2 end def scope @@ -109,33 +142,29 @@ module Spec end def home(*path) - tmp.join("home", *path) + tmp("home", *path) end def default_bundle_path(*path) - if Bundler.feature_flag.default_install_uses_path? - local_gem_path(*path) - else - system_gem_path(*path) - end + system_gem_path(*path) end def default_cache_path(*path) - if Bundler.feature_flag.global_gem_cache? - home(".bundle/cache", *path) - else - default_bundle_path("cache/bundler", *path) - end + default_bundle_path("cache/bundler", *path) + end + + def compact_index_cache_path + home(".bundle/cache/compact_index") end def bundled_app(*path) - root = tmp.join("bundled_app") + root = tmp("bundled_app") FileUtils.mkdir_p(root) root.join(*path) end def bundled_app2(*path) - root = tmp.join("bundled_app2") + root = tmp("bundled_app2") FileUtils.mkdir_p(root) root.join(*path) end @@ -156,20 +185,20 @@ module Spec bundled_app("Gemfile.lock") end - def base_system_gem_path - scoped_gem_path(base_system_gems) + def scoped_base_system_gem_path + scoped_gem_path(base_system_gem_path) end - def base_system_gems - tmp.join("gems/base") + def base_system_gem_path + tmp_root.join("gems/base") end - def rubocop_gems - tmp.join("gems/rubocop") + def rubocop_gem_path + tmp_root.join("gems/rubocop") end - def standard_gems - tmp.join("gems/standard") + def standard_gem_path + tmp_root.join("gems/standard") end def file_uri_for(path) @@ -180,35 +209,35 @@ module Spec end def gem_repo1(*args) - tmp("gems/remote1", *args) + gem_path("remote1", *args) end def gem_repo_missing(*args) - tmp("gems/missing", *args) + gem_path("missing", *args) end def gem_repo2(*args) - tmp("gems/remote2", *args) + gem_path("remote2", *args) end def gem_repo3(*args) - tmp("gems/remote3", *args) + gem_path("remote3", *args) end def gem_repo4(*args) - tmp("gems/remote4", *args) + gem_path("remote4", *args) end def security_repo(*args) - tmp("gems/security_repo", *args) + gem_path("security_repo", *args) end def system_gem_path(*path) - tmp("gems/system", *path) + gem_path("system", *path) end def pristine_system_gem_path - tmp("gems/base_system") + tmp_root.join("gems/pristine_system") end def local_gem_path(*path, base: bundled_app) @@ -219,6 +248,10 @@ module Spec base.join(Gem.ruby_engine, RbConfig::CONFIG["ruby_version"]) end + def gem_path(*args) + tmp("gems", *args) + end + def lib_path(*args) tmp("libs", *args) end @@ -246,7 +279,7 @@ module Spec def replace_version_file(version, dir: source_root) version_file = File.expand_path("lib/bundler/version.rb", dir) contents = File.read(version_file) - contents.sub!(/(^\s+VERSION\s*=\s*)"#{Gem::Version::VERSION_PATTERN}"/, %(\\1"#{version}")) + contents.sub!(/(^\s+VERSION\s*=\s*).*$/, %(\\1"#{version}")) File.open(version_file, "w") {|f| f << contents } end @@ -257,31 +290,62 @@ module Spec File.open(gemspec_file, "w") {|f| f << contents } end - def ruby_core? - # avoid to warnings - @ruby_core ||= nil - - if @ruby_core.nil? - @ruby_core = true & ENV["GEM_COMMAND"] - else - @ruby_core - end + def replace_changelog(version, dir:) + changelog = File.expand_path("CHANGELOG.md", dir) + contents = File.readlines(changelog) + contents = [contents[0], contents[1], "## #{version} (2100-01-01)\n", *contents[3..-1]].join + File.open(changelog, "w") {|f| f << contents } end def git_root ruby_core? ? source_root : source_root.parent end + def rake_path + find_base_path("rake") + end + + def rake_version + File.basename(rake_path).delete_prefix("rake-").delete_suffix(".gem") + end + + def sinatra_dependency_paths + deps = %w[ + mustermann + rack + rack-protection + rack-session + tilt + sinatra + base64 + logger + compact_index + ] + path = if deps.all? {|dep| !Dir[scoped_base_system_gem_path.join("gems/#{dep}-*")].empty? } + scoped_base_system_gem_path + elsif ruby_core? && deps.all? {|dep| !Dir[source_root.join(".bundle/gems/#{dep}-*")].empty? } + source_root.join(".bundle") + else + scoped_base_system_gem_path + end + + Dir[path.join("gems/{#{deps.join(",")}}-*/lib")].map(&:to_s) + end + private + def find_base_path(name) + Dir["#{scoped_base_system_gem_path}/**/#{name}-*.gem"].first + end + def git_ls_files(glob) skip "Not running on a git context, since running tests from a tarball" if ruby_core_tarball? - sys_exec("git ls-files -z -- #{glob}", dir: source_root).split("\x0") + git("ls-files -z -- #{glob}", source_root).split("\x0") end def tracked_files_glob - ruby_core? ? "libexec/bundle* lib/bundler lib/bundler.rb spec/bundler man/bundle*" : "lib exe spec CHANGELOG.md LICENSE.md README.md bundler.gemspec" + ruby_core? ? "libexec/bundle* lib/bundler lib/bundler.rb spec/bundler man/bundle*" : "lib exe CHANGELOG.md LICENSE.md README.md bundler.gemspec" end def lib_tracked_files_glob @@ -289,7 +353,7 @@ module Spec end def man_tracked_files_glob - ruby_core? ? "man/bundle* man/gemfile*" : "lib/bundler/man/bundle*.1 lib/bundler/man/gemfile*.5" + "lib/bundler/man/bundle*.1.ronn lib/bundler/man/gemfile*.5.ronn" end def ruby_core_tarball? diff --git a/spec/bundler/support/platforms.rb b/spec/bundler/support/platforms.rb index 526e1c09a9..56a0843005 100644 --- a/spec/bundler/support/platforms.rb +++ b/spec/bundler/support/platforms.rb @@ -2,64 +2,22 @@ module Spec module Platforms - include Bundler::GemHelpers - - def rb - Gem::Platform::RUBY - end - - def mac - Gem::Platform.new("x86-darwin-10") - end - - def x64_mac - Gem::Platform.new("x86_64-darwin-15") - end - - def java - Gem::Platform.new([nil, "java", nil]) - end - - def linux - Gem::Platform.new("x86_64-linux") - end - - def x86_mswin32 - Gem::Platform.new(["x86", "mswin32", nil]) - end - - def x64_mswin64 - Gem::Platform.new(["x64", "mswin64", nil]) - end - - def x86_mingw32 - Gem::Platform.new(["x86", "mingw32", nil]) - end - - def x64_mingw32 - Gem::Platform.new(["x64", "mingw32", nil]) - end - - def x64_mingw_ucrt - Gem::Platform.new(["x64", "mingw", "ucrt"]) - end - - def windows_platforms - [x86_mswin32, x64_mswin64, x86_mingw32, x64_mingw32, x64_mingw_ucrt] + def not_local + generic_local_platform == Gem::Platform::RUBY ? "java" : Gem::Platform::RUBY end - def all_platforms - [rb, java, linux, windows_platforms].flatten + def local_platform + Bundler.local_platform end - def not_local - all_platforms.find {|p| p != generic_local_platform } + def generic_local_platform + Gem::Platform.generic(local_platform) end def local_tag - if RUBY_PLATFORM == "java" + if Gem.java_platform? :jruby - elsif ["x64-mingw32", "x64-mingw-ucrt"].include?(RUBY_PLATFORM) + elsif Gem.win_platform? :windows else :ruby @@ -96,16 +54,22 @@ module Spec end def default_platform_list(*extra, defaults: default_locked_platforms) - defaults.concat(extra).uniq + defaults.concat(extra).map(&:to_s).uniq end def lockfile_platforms(*extra, defaults: default_locked_platforms) platforms = default_platform_list(*extra, defaults: defaults) - platforms.map(&:to_s).sort.join("\n ") + platforms.sort.join("\n ") end def default_locked_platforms - [local_platform, generic_local_platform] + [local_platform, generic_default_locked_platform].compact + end + + def generic_default_locked_platform + return unless Bundler::MatchPlatform.generic_local_platform_is_ruby? + + Gem::Platform::RUBY end end end diff --git a/spec/bundler/support/rubygems_ext.rb b/spec/bundler/support/rubygems_ext.rb index 889ebc90c3..812dc4deaa 100644 --- a/spec/bundler/support/rubygems_ext.rb +++ b/spec/bundler/support/rubygems_ext.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +abort "RubyGems only supports Ruby 3.2 or higher" if RUBY_VERSION < "3.2.0" + require_relative "path" $LOAD_PATH.unshift(Spec::Path.source_lib_dir.to_s) @@ -8,10 +10,6 @@ module Spec module Rubygems extend self - def dev_setup - install_gems(dev_gemfile) - end - def gem_load(gem_name, bin_container) require_relative "switch_rubygems" @@ -30,6 +28,9 @@ module Spec end def test_setup + # Install test dependencies unless parallel-rspec is being used, since in that case they should be setup already + install_test_deps unless ENV["RSPEC_FORMATTER_OUTPUT_ID"] + setup_test_paths require "fileutils" @@ -42,42 +43,78 @@ module Spec # sign extension bundles on macOS, to avoid trying to find the specified key # from the fake $HOME/Library/Keychains directory. ENV.delete "RUBY_CODESIGN" - ENV["TMPDIR"] = Path.tmpdir.to_s + if Path.ruby_core? + if (tmpdir = ENV["TMPDIR"]) + tmpdir_real = begin + File.realpath(tmpdir) + rescue Errno::ENOENT, Errno::EACCES + tmpdir + end + ENV["TMPDIR"] = tmpdir_real if tmpdir_real != tmpdir + end + else + ENV["TMPDIR"] = Path.tmpdir.to_s + end require "rubygems/user_interaction" Gem::DefaultUserInteraction.ui = Gem::SilentUI.new end - def install_parallel_test_deps - Gem.clear_paths - - require "parallel" - require "fileutils" - - install_test_deps - - (2..Parallel.processor_count).each do |n| - source = Path.source_root.join("tmp", "1") - destination = Path.source_root.join("tmp", n.to_s) - - FileUtils.rm_rf destination - FileUtils.cp_r source, destination - end - end - def setup_test_paths - Gem.clear_paths - ENV["BUNDLE_PATH"] = nil - ENV["GEM_HOME"] = ENV["GEM_PATH"] = Path.base_system_gem_path.to_s - ENV["PATH"] = [Path.system_gem_path.join("bin"), ENV["PATH"]].join(File::PATH_SEPARATOR) - ENV["PATH"] = [Path.bindir, ENV["PATH"]].join(File::PATH_SEPARATOR) if Path.ruby_core? + ENV["PATH"] = [Path.system_gem_path("bin"), ENV["PATH"]].join(File::PATH_SEPARATOR) + ENV["PATH"] = [Path.exedir, ENV["PATH"]].join(File::PATH_SEPARATOR) if Path.ruby_core? end def install_test_deps - install_gems(test_gemfile, Path.base_system_gems.to_s) - install_gems(rubocop_gemfile, Path.rubocop_gems.to_s) - install_gems(standard_gemfile, Path.standard_gems.to_s) + dev_bundle("install", gemfile: test_gemfile, path: Path.base_system_gem_path.to_s) + dev_bundle("install", gemfile: rubocop_gemfile, path: Path.rubocop_gem_path.to_s) + dev_bundle("install", gemfile: standard_gemfile, path: Path.standard_gem_path.to_s) + + require_relative "helpers" + Helpers.install_dev_bundler + + install_vendored_compact_index + end + + # Vendor `rubygems/rubygems.org#lib/compact_index/` under `tmp/compact_index/` + # so the artifice can serve compact-index responses without a runtime gem + # dependency. Pinned to a reviewed commit; override with COMPACT_INDEX_REF + # to refresh against another ref (the existing vendor copy is discarded). + def install_vendored_compact_index + target_root = Path.tmp_root.join("compact_index") + require "fileutils" + FileUtils.mkdir_p(Path.tmp_root) + + files = %w[ + lib/compact_index.rb + lib/compact_index/dependency.rb + lib/compact_index/gem.rb + lib/compact_index/gem_version.rb + lib/compact_index/versions_file.rb + ] + + # Serialize installs so parallel test setups don't race on the same + # vendor tree, and only skip the download when every file is present so + # an interrupted run can't leave a partial copy behind. + File.open(Path.tmp_root.join("compact_index.lock"), File::CREAT | File::RDWR) do |lock| + lock.flock(File::LOCK_EX) + + FileUtils.rm_rf(target_root) if ENV["COMPACT_INDEX_REF"] + + next if files.all? {|path| File.exist?(target_root.join(path)) } + + require "open-uri" + ref = ENV["COMPACT_INDEX_REF"] || "7c68a7b39761c61a66f9299f85b889ec39afc02c" + files.each do |path| + url = "https://raw.githubusercontent.com/rubygems/rubygems.org/#{ref}/#{path}" + target = target_root.join(path) + FileUtils.mkdir_p(File.dirname(target)) + tmp = "#{target}.tmp" + File.write(tmp, URI.parse(url).open(&:read)) + File.rename(tmp, target) + end + end end def check_source_control_changes(success_message:, error_message:) @@ -100,6 +137,36 @@ module Spec end end + def dev_bundle(*args, gemfile: dev_gemfile, path: nil) + old_gemfile = ENV["BUNDLE_GEMFILE"] + old_orig_gemfile = ENV["BUNDLER_ORIG_BUNDLE_GEMFILE"] + ENV["BUNDLE_GEMFILE"] = gemfile.to_s + ENV["BUNDLER_ORIG_BUNDLE_GEMFILE"] = nil + + if path + old_path = ENV["BUNDLE_PATH"] + ENV["BUNDLE_PATH"] = path + else + old_path__system = ENV["BUNDLE_PATH__SYSTEM"] + ENV["BUNDLE_PATH__SYSTEM"] = "true" + end + + require "shellwords" + # We don't use `Open3` here because it does not work on JRuby + Windows + output = `ruby #{Path.dev_binstub} #{args.shelljoin}` + raise output unless $?.success? + output + ensure + if path + ENV["BUNDLE_PATH"] = old_path + else + ENV["BUNDLE_PATH__SYSTEM"] = old_path__system + end + + ENV["BUNDLER_ORIG_BUNDLE_GEMFILE"] = old_orig_gemfile + ENV["BUNDLE_GEMFILE"] = old_gemfile + end + private def gem_load_and_activate(gem_name, bin_container) @@ -128,33 +195,6 @@ module Spec gem gem_name, gem_requirement end - def install_gems(gemfile, path = nil) - old_gemfile = ENV["BUNDLE_GEMFILE"] - old_orig_gemfile = ENV["BUNDLER_ORIG_BUNDLE_GEMFILE"] - ENV["BUNDLE_GEMFILE"] = gemfile.to_s - ENV["BUNDLER_ORIG_BUNDLE_GEMFILE"] = nil - - if path - old_path = ENV["BUNDLE_PATH"] - ENV["BUNDLE_PATH"] = path - else - old_path__system = ENV["BUNDLE_PATH__SYSTEM"] - ENV["BUNDLE_PATH__SYSTEM"] = "true" - end - - puts `#{Gem.ruby} #{File.expand_path("support/bundle.rb", Path.spec_dir)} install --verbose` - raise unless $?.success? - ensure - if path - ENV["BUNDLE_PATH"] = old_path - else - ENV["BUNDLE_PATH__SYSTEM"] = old_path__system - end - - ENV["BUNDLER_ORIG_BUNDLE_GEMFILE"] = old_orig_gemfile - ENV["BUNDLE_GEMFILE"] = old_gemfile - end - def test_gemfile Path.test_gemfile end diff --git a/spec/bundler/support/rubygems_version_manager.rb b/spec/bundler/support/rubygems_version_manager.rb index 88da14b67e..c174c461f0 100644 --- a/spec/bundler/support/rubygems_version_manager.rb +++ b/spec/bundler/support/rubygems_version_manager.rb @@ -1,12 +1,13 @@ # frozen_string_literal: true -require "pathname" -require_relative "helpers" -require_relative "path" +require_relative "options" +require_relative "env" +require_relative "subprocess" class RubygemsVersionManager - include Spec::Helpers - include Spec::Path + include Spec::Options + include Spec::Env + include Spec::Subprocess def initialize(source) @source = source @@ -57,7 +58,7 @@ class RubygemsVersionManager cmd = [RbConfig.ruby, $0, *ARGV].compact - ENV["RUBYOPT"] = opt_add("-I#{local_copy_path.join("lib")}", opt_remove("--disable-gems", ENV["RUBYOPT"])) + ENV["RUBYOPT"] = opt_add("-I#{File.join(local_copy_path, "lib")}", opt_remove("--disable-gems", ENV["RUBYOPT"])) exec(ENV, *cmd) end @@ -65,14 +66,14 @@ class RubygemsVersionManager def switch_local_copy_if_needed return unless local_copy_switch_needed? - sys_exec("git checkout #{target_tag}", dir: local_copy_path) + git("checkout #{target_tag}", local_copy_path) - ENV["RGV"] = local_copy_path.to_s + ENV["RGV"] = local_copy_path end def rubygems_unrequire_needed? require "rubygems" - !$LOADED_FEATURES.include?(local_copy_path.join("lib/rubygems.rb").to_s) + !$LOADED_FEATURES.include?(File.join(local_copy_path, "lib/rubygems.rb")) end def local_copy_switch_needed? @@ -84,7 +85,7 @@ class RubygemsVersionManager end def local_copy_tag - sys_exec("git rev-parse --abbrev-ref HEAD", dir: local_copy_path) + git("rev-parse --abbrev-ref HEAD", local_copy_path) end def local_copy_path @@ -94,21 +95,25 @@ class RubygemsVersionManager def resolve_local_copy_path return expanded_source if source_is_path? - rubygems_path = source_root.join("tmp/rubygems") + rubygems_path = File.join(source_root, "tmp/rubygems") - unless rubygems_path.directory? - sys_exec("git clone .. #{rubygems_path}", dir: source_root) + unless File.directory?(rubygems_path) + git("clone .. #{rubygems_path}", source_root) end rubygems_path end def source_is_path? - expanded_source.directory? + File.directory?(expanded_source) end def expanded_source - @expanded_source ||= Pathname.new(@source).expand_path(source_root) + @expanded_source ||= File.expand_path(@source, source_root) + end + + def source_root + @source_root ||= File.expand_path(ruby_core? ? "../../.." : "../..", __dir__) end def resolve_target_tag diff --git a/spec/bundler/support/setup.rb b/spec/bundler/support/setup.rb new file mode 100644 index 0000000000..4ac2e5b472 --- /dev/null +++ b/spec/bundler/support/setup.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require_relative "switch_rubygems" + +require_relative "rubygems_ext" +Spec::Rubygems.install_test_deps + +require_relative "path" +$LOAD_PATH.unshift(File.expand_path("../../lib", __dir__)) if Spec::Path.ruby_core? diff --git a/spec/bundler/support/shards.rb b/spec/bundler/support/shards.rb new file mode 100644 index 0000000000..ce33896539 --- /dev/null +++ b/spec/bundler/support/shards.rb @@ -0,0 +1,200 @@ +# frozen_string_literal: true + +# This classifies test files into 4 shards by running `bin/rspec --profile 10000` +# to ensure balanced execution times. When adding new test files, it is recommended to +# re-aggregate and adjust the shards to keep them balanced. +# For now, please add new files to shard 'shard_d'. + +module Spec + module Shards + EXAMPLE_MAPPINGS = { + shard_a: [ + "spec/runtime/setup_spec.rb", + "spec/commands/install_spec.rb", + "spec/commands/add_spec.rb", + "spec/install/gems/compact_index_spec.rb", + "spec/commands/config_spec.rb", + "spec/commands/pristine_spec.rb", + "spec/install/gemfile/path_spec.rb", + "spec/update/git_spec.rb", + "spec/commands/open_spec.rb", + "spec/commands/remove_spec.rb", + "spec/commands/show_spec.rb", + "spec/plugins/source/example_spec.rb", + "spec/commands/console_spec.rb", + "spec/runtime/require_spec.rb", + "spec/runtime/env_helpers_spec.rb", + "spec/runtime/gem_tasks_spec.rb", + "spec/install/gemfile_spec.rb", + "spec/commands/fund_spec.rb", + "spec/commands/init_spec.rb", + "spec/bundler/ruby_dsl_spec.rb", + "spec/bundler/mirror_spec.rb", + "spec/bundler/source/git/git_proxy_spec.rb", + "spec/bundler/source_list_spec.rb", + "spec/bundler/plugin/installer_spec.rb", + "spec/bundler/errors_spec.rb", + "spec/bundler/friendly_errors_spec.rb", + "spec/resolver/platform_spec.rb", + "spec/bundler/fetcher/downloader_spec.rb", + "spec/update/force_spec.rb", + "spec/bundler/env_spec.rb", + "spec/install/gems/mirror_spec.rb", + "spec/install/failure_spec.rb", + "spec/bundler/yaml_serializer_spec.rb", + "spec/bundler/environment_preserver_spec.rb", + "spec/install/gemfile/install_if_spec.rb", + "spec/install/gems/gemfile_source_header_spec.rb", + "spec/bundler/fetcher/base_spec.rb", + "spec/bundler/rubygems_integration_spec.rb", + "spec/bundler/worker_spec.rb", + "spec/bundler/dependency_spec.rb", + "spec/bundler/ui_spec.rb", + "spec/bundler/plugin/source_list_spec.rb", + "spec/bundler/source/path_spec.rb", + ], + shard_b: [ + "spec/install/gemfile/git_spec.rb", + "spec/install/gems/standalone_spec.rb", + "spec/commands/lock_spec.rb", + "spec/cache/gems_spec.rb", + "spec/other/major_deprecation_spec.rb", + "spec/install/gems/dependency_api_spec.rb", + "spec/install/gemfile/gemspec_spec.rb", + "spec/plugins/install_spec.rb", + "spec/commands/binstubs_spec.rb", + "spec/install/gems/flex_spec.rb", + "spec/runtime/inline_spec.rb", + "spec/commands/post_bundle_message_spec.rb", + "spec/runtime/executable_spec.rb", + "spec/lock/git_spec.rb", + "spec/plugins/hook_spec.rb", + "spec/install/allow_offline_install_spec.rb", + "spec/install/gems/post_install_spec.rb", + "spec/install/gemfile/ruby_spec.rb", + "spec/install/security_policy_spec.rb", + "spec/install/yanked_spec.rb", + "spec/update/gemfile_spec.rb", + "spec/runtime/load_spec.rb", + "spec/plugins/command_spec.rb", + "spec/commands/version_spec.rb", + "spec/install/prereleases_spec.rb", + "spec/bundler/uri_credentials_filter_spec.rb", + "spec/bundler/plugin_spec.rb", + "spec/install/gems/mirror_probe_spec.rb", + "spec/plugins/list_spec.rb", + "spec/bundler/compact_index_client/parser_spec.rb", + "spec/bundler/gem_version_promoter_spec.rb", + "spec/other/cli_dispatch_spec.rb", + "spec/bundler/source/rubygems_spec.rb", + "spec/cache/platform_spec.rb", + "spec/update/gems/fund_spec.rb", + "spec/bundler/stub_specification_spec.rb", + "spec/bundler/retry_spec.rb", + "spec/bundler/installer/spec_installation_spec.rb", + "spec/bundler/spec_set_spec.rb", + "spec/quality_es_spec.rb", + "spec/bundler/index_spec.rb", + "spec/other/cli_man_pages_spec.rb", + ], + shard_c: [ + "spec/commands/newgem_spec.rb", + "spec/commands/exec_spec.rb", + "spec/commands/clean_spec.rb", + "spec/commands/platform_spec.rb", + "spec/cache/git_spec.rb", + "spec/install/gemfile/groups_spec.rb", + "spec/commands/cache_spec.rb", + "spec/commands/check_spec.rb", + "spec/commands/list_spec.rb", + "spec/install/path_spec.rb", + "spec/bundler/cli_spec.rb", + "spec/install/bundler_spec.rb", + "spec/install/git_spec.rb", + "spec/commands/doctor_spec.rb", + "spec/bundler/dsl_spec.rb", + "spec/install/gems/fund_spec.rb", + "spec/install/gems/env_spec.rb", + "spec/bundler/ruby_version_spec.rb", + "spec/bundler/definition_spec.rb", + "spec/install/gemfile/eval_gemfile_spec.rb", + "spec/plugins/source_spec.rb", + "spec/install/gems/dependency_api_fallback_spec.rb", + "spec/plugins/uninstall_spec.rb", + "spec/bundler/plugin/index_spec.rb", + "spec/bundler/bundler_spec.rb", + "spec/bundler/fetcher_spec.rb", + "spec/bundler/source/rubygems/remote_spec.rb", + "spec/bundler/lockfile_parser_spec.rb", + "spec/cache/cache_path_spec.rb", + "spec/bundler/source/git_spec.rb", + "spec/bundler/source_spec.rb", + "spec/commands/ssl_spec.rb", + "spec/bundler/fetcher/compact_index_spec.rb", + "spec/bundler/plugin/api_spec.rb", + "spec/bundler/endpoint_specification_spec.rb", + "spec/bundler/fetcher/index_spec.rb", + "spec/bundler/settings/validator_spec.rb", + "spec/bundler/build_metadata_spec.rb", + "spec/bundler/current_ruby_spec.rb", + "spec/bundler/installer/gem_installer_spec.rb", + "spec/bundler/installer/parallel_installer_spec.rb", + "spec/bundler/cli_common_spec.rb", + "spec/bundler/ci_detector_spec.rb", + ], + shard_d: [ + "spec/bundler/rubygems_ext_spec.rb", + "spec/bundler/resolver/cooldown_spec.rb", + "spec/install/cooldown_spec.rb", + "spec/commands/outdated_spec.rb", + "spec/commands/update_spec.rb", + "spec/lock/lockfile_spec.rb", + "spec/install/deploy_spec.rb", + "spec/install/gemfile/sources_spec.rb", + "spec/runtime/self_management_spec.rb", + "spec/install/gemfile/specific_platform_spec.rb", + "spec/commands/info_spec.rb", + "spec/install/gems/resolving_spec.rb", + "spec/install/gemfile/platform_spec.rb", + "spec/bundler/gem_helper_spec.rb", + "spec/install/global_cache_spec.rb", + "spec/runtime/platform_spec.rb", + "spec/update/gems/post_install_spec.rb", + "spec/install/gems/native_extensions_spec.rb", + "spec/install/force_spec.rb", + "spec/cache/path_spec.rb", + "spec/install/gemspecs_spec.rb", + "spec/commands/help_spec.rb", + "spec/bundler/shared_helpers_spec.rb", + "spec/bundler/settings_spec.rb", + "spec/resolver/basic_spec.rb", + "spec/install/gemfile/force_ruby_platform_spec.rb", + "spec/commands/licenses_spec.rb", + "spec/install/gemfile/lockfile_spec.rb", + "spec/bundler/fetcher/dependency_spec.rb", + "spec/quality_spec.rb", + "spec/bundler/remote_specification_spec.rb", + "spec/install/process_lock_spec.rb", + "spec/install/binstubs_spec.rb", + "spec/bundler/compact_index_client/updater_spec.rb", + "spec/bundler/ui/shell_spec.rb", + "spec/other/ext_spec.rb", + "spec/commands/issue_spec.rb", + "spec/update/path_spec.rb", + "spec/bundler/plugin/api/source_spec.rb", + "spec/install/gems/win32_spec.rb", + "spec/bundler/plugin/dsl_spec.rb", + "spec/runtime/requiring_spec.rb", + "spec/bundler/plugin/events_spec.rb", + "spec/bundler/resolver/candidate_spec.rb", + "spec/bundler/digest_spec.rb", + "spec/bundler/fetcher/gem_remote_fetcher_spec.rb", + "spec/bundler/uri_normalizer_spec.rb", + "spec/install/gems/no_build_extension_spec.rb", + "spec/install/gems/no_install_plugin_spec.rb", + "spec/bundler/override_spec.rb", + "spec/install/gemfile/override_spec.rb", + ], + }.freeze + end +end diff --git a/spec/bundler/support/silent_logger.rb b/spec/bundler/support/silent_logger.rb deleted file mode 100644 index 8665beb2c9..0000000000 --- a/spec/bundler/support/silent_logger.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -require "logger" -module Spec - class SilentLogger - (::Logger.instance_methods - Object.instance_methods).each do |logger_instance_method| - define_method(logger_instance_method) {|*args, &blk| } - end - end -end diff --git a/spec/bundler/support/subprocess.rb b/spec/bundler/support/subprocess.rb new file mode 100644 index 0000000000..91db80da48 --- /dev/null +++ b/spec/bundler/support/subprocess.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +require_relative "command_execution" + +module Spec + module Subprocess + class TimeoutExceeded < StandardError; end + + def command_executions + @command_executions ||= [] + end + + def last_command + command_executions.last || raise("There is no last command") + end + + def out + last_command.stdout + end + + def err + last_command.stderr + end + + def stdboth + last_command.stdboth + end + + def exitstatus + last_command.exitstatus + end + + def git(cmd, path = Dir.pwd, options = {}) + sh("git #{cmd}", options.merge(dir: path)) + end + + def sh(cmd, options = {}) + dir = options[:dir] + env = options[:env] || {} + + command_execution = CommandExecution.new(cmd.to_s, timeout: options[:timeout] || 60) + + open3_opts = {} + open3_opts[:chdir] = dir if dir + + require "open3" + require "shellwords" + Open3.popen3(env, *cmd.shellsplit, **open3_opts) do |stdin, stdout, stderr, wait_thr| + yield stdin, stdout, wait_thr if block_given? + stdin.close + + stdout_handler = ->(data) { command_execution.original_stdout << data } + stderr_handler = ->(data) { command_execution.original_stderr << data } + + stdout_thread = read_stream(stdout, stdout_handler, timeout: command_execution.timeout) + stderr_thread = read_stream(stderr, stderr_handler, timeout: command_execution.timeout) + + stdout_thread.join + stderr_thread.join + + status = wait_thr.value + command_execution.exitstatus = if status.exited? + status.exitstatus + elsif status.signaled? + exit_status_for_signal(status.termsig) + end + rescue TimeoutExceeded + command_execution.failure_reason = :timeout + command_execution.exitstatus = exit_status_for_signal(Signal.list["INT"]) + end + + unless options[:raise_on_error] == false || command_execution.success? + command_execution.raise_error! + end + + command_executions << command_execution + + command_execution.stdout + end + + # Mostly copied from https://github.com/piotrmurach/tty-command/blob/49c37a895ccea107e8b78d20e4cb29de6a1a53c8/lib/tty/command/process_runner.rb#L165-L193 + def read_stream(stream, handler, timeout:) + Thread.new do + Thread.current.report_on_exception = false + cmd_start = Time.now + readers = [stream] + + while readers.any? + ready = IO.select(readers, nil, readers, timeout) + raise TimeoutExceeded if ready.nil? + + ready[0].each do |reader| + chunk = reader.readpartial(16 * 1024) + handler.call(chunk) + + # control total time spent reading + runtime = Time.now - cmd_start + time_left = timeout - runtime + raise TimeoutExceeded if time_left < 0.0 + rescue Errno::EAGAIN, Errno::EINTR + rescue EOFError, Errno::EPIPE, Errno::EIO + readers.delete(reader) + reader.close + end + end + end + end + + def all_commands_output + return "" if command_executions.empty? + + "\n\nCommands:\n#{command_executions.map(&:to_s_verbose).join("\n\n")}" + end + end +end diff --git a/spec/bundler/support/switch_rubygems.rb b/spec/bundler/support/switch_rubygems.rb index a138d22333..640b9f83b7 100644 --- a/spec/bundler/support/switch_rubygems.rb +++ b/spec/bundler/support/switch_rubygems.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true require_relative "rubygems_version_manager" +ENV["RGV"] ||= "." RubygemsVersionManager.new(ENV["RGV"]).switch diff --git a/spec/bundler/support/the_bundle.rb b/spec/bundler/support/the_bundle.rb index f252a4515b..452abd7d41 100644 --- a/spec/bundler/support/the_bundle.rb +++ b/spec/bundler/support/the_bundle.rb @@ -8,10 +8,8 @@ module Spec attr_accessor :bundle_dir - def initialize(opts = {}) - opts = opts.dup - @bundle_dir = Pathname.new(opts.delete(:bundle_dir) { bundled_app }) - raise "Too many options! #{opts}" unless opts.empty? + def initialize + @bundle_dir = Pathname.new(bundled_app) end def to_s @@ -28,8 +26,16 @@ module Spec end def locked_gems - raise "Cannot read lockfile if it doesn't exist" unless locked? + raise ArgumentError, "Cannot read lockfile if it doesn't exist" unless locked? Bundler::LockfileParser.new(lockfile.read) end + + def locked_specs + locked_gems.specs.map(&:full_name) + end + + def locked_platforms + locked_gems.platforms.map(&:to_s) + end end end diff --git a/spec/bundler/support/vendored_net_http.rb b/spec/bundler/support/vendored_net_http.rb new file mode 100644 index 0000000000..8ff2ccd1fe --- /dev/null +++ b/spec/bundler/support/vendored_net_http.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# This defined? guard can be removed once RubyGems 3.4 support is dropped. +# +# Bundler specs load this code from `spec/support/vendored_net_http.rb` to avoid +# activating the Bundler gem too early. Without this guard, we get redefinition +# warnings once Bundler is actually activated and +# `lib/bundler/vendored_net_http.rb` is required. This is not an issue in +# RubyGems versions including `rubygems/vendored_net_http` since `require` takes +# care of avoiding the double load. +# +unless defined?(Gem::Net) + begin + require "rubygems/vendored_net_http" + rescue LoadError + begin + require "rubygems/net/http" + rescue LoadError + require "net/http" + Gem::Net = Net + end + end +end |
