summaryrefslogtreecommitdiff
path: root/spec/bundler/support
diff options
context:
space:
mode:
Diffstat (limited to 'spec/bundler/support')
-rw-r--r--spec/bundler/support/activate.rb9
-rw-r--r--spec/bundler/support/api_request_limit_hax.rb16
-rw-r--r--spec/bundler/support/artifice/compact_index_checksum_mismatch.rb4
-rw-r--r--spec/bundler/support/artifice/compact_index_concurrent_download.rb7
-rw-r--r--spec/bundler/support/artifice/compact_index_cooldown.rb6
-rw-r--r--spec/bundler/support/artifice/compact_index_creds_diff_host.rb2
-rw-r--r--spec/bundler/support/artifice/compact_index_etag_match.rb16
-rw-r--r--spec/bundler/support/artifice/compact_index_host_redirect.rb2
-rw-r--r--spec/bundler/support/artifice/compact_index_mirror_down.rb21
-rw-r--r--spec/bundler/support/artifice/compact_index_no_checksums.rb16
-rw-r--r--spec/bundler/support/artifice/compact_index_partial_update.rb2
-rw-r--r--spec/bundler/support/artifice/compact_index_partial_update_bad_digest.rb40
-rw-r--r--spec/bundler/support/artifice/compact_index_partial_update_no_digest_not_incremental.rb (renamed from spec/bundler/support/artifice/compact_index_partial_update_no_etag_not_incremental.rb)14
-rw-r--r--spec/bundler/support/artifice/compact_index_range_ignored.rb40
-rw-r--r--spec/bundler/support/artifice/compact_index_strict_basic_authentication.rb2
-rw-r--r--spec/bundler/support/artifice/compact_index_wrong_gem_checksum.rb3
-rw-r--r--spec/bundler/support/artifice/endpoint_500.rb2
-rw-r--r--spec/bundler/support/artifice/endpoint_creds_diff_host.rb2
-rw-r--r--spec/bundler/support/artifice/endpoint_host_redirect.rb2
-rw-r--r--spec/bundler/support/artifice/endpoint_mirror_source.rb2
-rw-r--r--spec/bundler/support/artifice/endpoint_strict_basic_authentication.rb2
-rw-r--r--spec/bundler/support/artifice/fail.rb14
-rw-r--r--spec/bundler/support/artifice/helpers/artifice.rb6
-rw-r--r--spec/bundler/support/artifice/helpers/compact_index.rb48
-rw-r--r--spec/bundler/support/artifice/helpers/compact_index_cooldown.rb13
-rw-r--r--spec/bundler/support/artifice/helpers/endpoint.rb17
-rw-r--r--spec/bundler/support/artifice/helpers/rack_request.rb24
-rw-r--r--spec/bundler/support/artifice/vcr.rb16
-rw-r--r--spec/bundler/support/artifice/windows.rb2
-rw-r--r--spec/bundler/support/build_metadata.rb20
-rw-r--r--spec/bundler/support/builders.rb360
-rwxr-xr-xspec/bundler/support/bundle6
-rw-r--r--spec/bundler/support/bundle.rb11
-rw-r--r--spec/bundler/support/checksums.rb135
-rw-r--r--spec/bundler/support/command_execution.rb47
-rw-r--r--spec/bundler/support/env.rb13
-rw-r--r--spec/bundler/support/filters.rb42
-rw-r--r--spec/bundler/support/hax.rb51
-rw-r--r--spec/bundler/support/helpers.rb550
-rw-r--r--spec/bundler/support/indexes.rb52
-rw-r--r--spec/bundler/support/matchers.rb26
-rw-r--r--spec/bundler/support/options.rb15
-rw-r--r--spec/bundler/support/path.rb202
-rw-r--r--spec/bundler/support/platforms.rb83
-rw-r--r--spec/bundler/support/rubygems_ext.rb165
-rw-r--r--spec/bundler/support/rubygems_version_manager.rb38
-rw-r--r--spec/bundler/support/setup.rb9
-rw-r--r--spec/bundler/support/shards.rb200
-rw-r--r--spec/bundler/support/silent_logger.rb10
-rw-r--r--spec/bundler/support/subprocess.rb115
-rw-r--r--spec/bundler/support/switch_rubygems.rb1
-rw-r--r--spec/bundler/support/the_bundle.rb16
-rw-r--r--spec/bundler/support/vendored_net_http.rb23
53 files changed, 1725 insertions, 815 deletions
diff --git a/spec/bundler/support/activate.rb b/spec/bundler/support/activate.rb
new file mode 100644
index 0000000000..143b77833d
--- /dev/null
+++ b/spec/bundler/support/activate.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require "rubygems"
+Gem.instance_variable_set(:@ruby, ENV["RUBY"]) if ENV["RUBY"]
+
+require_relative "path"
+bundler_gemspec = Spec::Path.loaded_gemspec
+bundler_gemspec.instance_variable_set(:@full_gem_path, Spec::Path.source_root.to_s)
+bundler_gemspec.activate if bundler_gemspec.respond_to?(:activate)
diff --git a/spec/bundler/support/api_request_limit_hax.rb b/spec/bundler/support/api_request_limit_hax.rb
deleted file mode 100644
index 37ff0203b3..0000000000
--- a/spec/bundler/support/api_request_limit_hax.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-if ENV["BUNDLER_SPEC_API_REQUEST_LIMIT"]
- require_relative "path"
- require "bundler/source"
- require "bundler/source/rubygems"
-
- module Bundler
- class Source
- class Rubygems < Source
- remove_const :API_REQUEST_LIMIT
- API_REQUEST_LIMIT = ENV["BUNDLER_SPEC_API_REQUEST_LIMIT"].to_i
- end
- end
- end
-end
diff --git a/spec/bundler/support/artifice/compact_index_checksum_mismatch.rb b/spec/bundler/support/artifice/compact_index_checksum_mismatch.rb
index a6545b9ee4..83b147d2ae 100644
--- a/spec/bundler/support/artifice/compact_index_checksum_mismatch.rb
+++ b/spec/bundler/support/artifice/compact_index_checksum_mismatch.rb
@@ -4,10 +4,10 @@ require_relative "helpers/compact_index"
class CompactIndexChecksumMismatch < CompactIndexAPI
get "/versions" do
- headers "ETag" => quote("123")
+ headers "Repr-Digest" => "sha-256=:ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0=:"
headers "Surrogate-Control" => "max-age=2592000, stale-while-revalidate=60"
content_type "text/plain"
- body ""
+ body "content does not match the checksum"
end
end
diff --git a/spec/bundler/support/artifice/compact_index_concurrent_download.rb b/spec/bundler/support/artifice/compact_index_concurrent_download.rb
index 35548f278c..5d55b8a72b 100644
--- a/spec/bundler/support/artifice/compact_index_concurrent_download.rb
+++ b/spec/bundler/support/artifice/compact_index_concurrent_download.rb
@@ -7,11 +7,12 @@ class CompactIndexConcurrentDownload < CompactIndexAPI
versions = File.join(Bundler.rubygems.user_home, ".bundle", "cache", "compact_index",
"localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions")
- # Verify the original (empty) content hasn't been deleted, e.g. on a retry
- File.binread(versions) == "" || raise("Original file should be present and empty")
+ # Verify the original content hasn't been deleted, e.g. on a retry
+ data = File.binread(versions)
+ data == "created_at" || raise("Original file should be present with expected content")
# Verify this is only requested once for a partial download
- env["HTTP_RANGE"] || raise("Missing Range header for expected partial download")
+ env["HTTP_RANGE"] == "bytes=#{data.bytesize - 1}-" || raise("Missing Range header for expected partial download")
# Overwrite the file in parallel, which should be then overwritten
# after a successful download to prevent corruption
diff --git a/spec/bundler/support/artifice/compact_index_cooldown.rb b/spec/bundler/support/artifice/compact_index_cooldown.rb
new file mode 100644
index 0000000000..85e3173c98
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_cooldown.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index_cooldown"
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexCooldownAPI)
diff --git a/spec/bundler/support/artifice/compact_index_creds_diff_host.rb b/spec/bundler/support/artifice/compact_index_creds_diff_host.rb
index 401e8a98d8..282e9c8961 100644
--- a/spec/bundler/support/artifice/compact_index_creds_diff_host.rb
+++ b/spec/bundler/support/artifice/compact_index_creds_diff_host.rb
@@ -24,7 +24,7 @@ class CompactIndexCredsDiffHost < CompactIndexAPI
end
get "/gems/:id" do
- redirect "http://diffhost.com/no/creds/#{params[:id]}"
+ redirect "http://diffhost.test/no/creds/#{params[:id]}"
end
get "/no/creds/:id" do
diff --git a/spec/bundler/support/artifice/compact_index_etag_match.rb b/spec/bundler/support/artifice/compact_index_etag_match.rb
new file mode 100644
index 0000000000..6c62166051
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_etag_match.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+
+class CompactIndexEtagMatch < CompactIndexAPI
+ get "/versions" do
+ raise ArgumentError, "ETag header should be present" unless env["HTTP_IF_NONE_MATCH"]
+ headers "ETag" => env["HTTP_IF_NONE_MATCH"]
+ status 304
+ body ""
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexEtagMatch)
diff --git a/spec/bundler/support/artifice/compact_index_host_redirect.rb b/spec/bundler/support/artifice/compact_index_host_redirect.rb
index 9a711186db..4f82bf3812 100644
--- a/spec/bundler/support/artifice/compact_index_host_redirect.rb
+++ b/spec/bundler/support/artifice/compact_index_host_redirect.rb
@@ -3,7 +3,7 @@
require_relative "helpers/compact_index"
class CompactIndexHostRedirect < CompactIndexAPI
- get "/fetch/actual/gem/:id", :host_name => "localgemserver.test" do
+ get "/fetch/actual/gem/:id", host_name: "localgemserver.test" do
redirect "http://bundler.localgemserver.test#{request.path_info}"
end
diff --git a/spec/bundler/support/artifice/compact_index_mirror_down.rb b/spec/bundler/support/artifice/compact_index_mirror_down.rb
new file mode 100644
index 0000000000..88983c715d
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_mirror_down.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+require_relative "helpers/artifice"
+require_relative "helpers/rack_request"
+
+module Artifice
+ module Net
+ class HTTPMirrorDown < HTTP
+ def connect
+ raise SocketError if address == "gem.mirror"
+
+ super
+ end
+ end
+
+ HTTP.endpoint = CompactIndexAPI
+ end
+
+ replace_net_http(Net::HTTPMirrorDown)
+end
diff --git a/spec/bundler/support/artifice/compact_index_no_checksums.rb b/spec/bundler/support/artifice/compact_index_no_checksums.rb
new file mode 100644
index 0000000000..ecb7fc7d7c
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_no_checksums.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+
+class CompactIndexNoChecksums < CompactIndexAPI
+ get "/info/:name" do
+ etag_response do
+ gem = gems.find {|g| g.name == params[:name] }
+ gem.versions.map(&:number).join("\n")
+ end
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexNoChecksums)
diff --git a/spec/bundler/support/artifice/compact_index_partial_update.rb b/spec/bundler/support/artifice/compact_index_partial_update.rb
index 8c73011346..f111d91ef9 100644
--- a/spec/bundler/support/artifice/compact_index_partial_update.rb
+++ b/spec/bundler/support/artifice/compact_index_partial_update.rb
@@ -23,7 +23,7 @@ class CompactIndexPartialUpdate < CompactIndexAPI
# Verify that a partial request is made, starting from the index of the
# final byte of the cached file.
unless env["HTTP_RANGE"] == "bytes=#{File.binread(cached_versions_path).bytesize - 1}-"
- raise("Range header should be present, and start from the index of the final byte of the cache.")
+ raise("Range header should be present, and start from the index of the final byte of the cache. #{env["HTTP_RANGE"].inspect}")
end
etag_response do
diff --git a/spec/bundler/support/artifice/compact_index_partial_update_bad_digest.rb b/spec/bundler/support/artifice/compact_index_partial_update_bad_digest.rb
new file mode 100644
index 0000000000..ac04336636
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_partial_update_bad_digest.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+
+# The purpose of this Artifice is to test that an incremental response is invalidated
+# and a second request is issued for the full content.
+class CompactIndexPartialUpdateBadDigest < CompactIndexAPI
+ def partial_update_bad_digest
+ response_body = yield
+ if request.env["HTTP_RANGE"]
+ headers "Repr-Digest" => "sha-256=:#{Digest::SHA256.base64digest("wrong digest on ranged request")}:"
+ else
+ headers "Repr-Digest" => "sha-256=:#{Digest::SHA256.base64digest(response_body)}:"
+ end
+ headers "Surrogate-Control" => "max-age=2592000, stale-while-revalidate=60"
+ content_type "text/plain"
+ requested_range_for(response_body)
+ end
+
+ get "/versions" do
+ partial_update_bad_digest do
+ file = tmp("versions.list")
+ FileUtils.rm_f(file)
+ file = CompactIndex::VersionsFile.new(file.to_s)
+ file.create(gems)
+ file.contents([], calculate_info_checksums: true)
+ end
+ end
+
+ get "/info/:name" do
+ partial_update_bad_digest do
+ gem = gems.find {|g| g.name == params[:name] }
+ CompactIndex.info(gem ? gem.versions : [])
+ end
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexPartialUpdateBadDigest)
diff --git a/spec/bundler/support/artifice/compact_index_partial_update_no_etag_not_incremental.rb b/spec/bundler/support/artifice/compact_index_partial_update_no_digest_not_incremental.rb
index 20546ba4c3..99bae039f0 100644
--- a/spec/bundler/support/artifice/compact_index_partial_update_no_etag_not_incremental.rb
+++ b/spec/bundler/support/artifice/compact_index_partial_update_no_digest_not_incremental.rb
@@ -2,8 +2,10 @@
require_relative "helpers/compact_index"
-class CompactIndexPartialUpdateNoEtagNotIncremental < CompactIndexAPI
- def partial_update_no_etag
+# The purpose of this Artifice is to test that an incremental response is ignored
+# when the digest is not present to verify that the partial response is valid.
+class CompactIndexPartialUpdateNoDigestNotIncremental < CompactIndexAPI
+ def partial_update_no_digest
response_body = yield
headers "Surrogate-Control" => "max-age=2592000, stale-while-revalidate=60"
content_type "text/plain"
@@ -11,12 +13,12 @@ class CompactIndexPartialUpdateNoEtagNotIncremental < CompactIndexAPI
end
get "/versions" do
- partial_update_no_etag do
+ partial_update_no_digest do
file = tmp("versions.list")
FileUtils.rm_f(file)
file = CompactIndex::VersionsFile.new(file.to_s)
file.create(gems)
- lines = file.contents([], :calculate_info_checksums => true).split("\n")
+ lines = file.contents([], calculate_info_checksums: true).split("\n")
name, versions, checksum = lines.last.split(" ")
# shuffle versions so new versions are not appended to the end
@@ -25,7 +27,7 @@ class CompactIndexPartialUpdateNoEtagNotIncremental < CompactIndexAPI
end
get "/info/:name" do
- partial_update_no_etag do
+ partial_update_no_digest do
gem = gems.find {|g| g.name == params[:name] }
lines = CompactIndex.info(gem ? gem.versions : []).split("\n")
@@ -37,4 +39,4 @@ end
require_relative "helpers/artifice"
-Artifice.activate_with(CompactIndexPartialUpdateNoEtagNotIncremental)
+Artifice.activate_with(CompactIndexPartialUpdateNoDigestNotIncremental)
diff --git a/spec/bundler/support/artifice/compact_index_range_ignored.rb b/spec/bundler/support/artifice/compact_index_range_ignored.rb
new file mode 100644
index 0000000000..2303682c1f
--- /dev/null
+++ b/spec/bundler/support/artifice/compact_index_range_ignored.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require_relative "helpers/compact_index"
+
+class CompactIndexRangeIgnored < CompactIndexAPI
+ # Stub the server to not return 304 so that we don't bypass all the logic
+ def not_modified?(_checksum)
+ false
+ end
+
+ get "/versions" do
+ cached_versions_path = File.join(
+ Bundler.rubygems.user_home, ".bundle", "cache", "compact_index",
+ "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "versions"
+ )
+
+ # Verify a cached copy of the versions file exists
+ unless File.binread(cached_versions_path).size > 0
+ raise("Cached versions file should be present and have content")
+ end
+
+ # Verify that a partial request is made, starting from the index of the
+ # final byte of the cached file.
+ unless env.delete("HTTP_RANGE")
+ raise("Expected client to write the full response on the first try")
+ end
+
+ etag_response do
+ file = tmp("versions.list")
+ FileUtils.rm_f(file)
+ file = CompactIndex::VersionsFile.new(file.to_s)
+ file.create(gems)
+ file.contents
+ end
+ end
+end
+
+require_relative "helpers/artifice"
+
+Artifice.activate_with(CompactIndexRangeIgnored)
diff --git a/spec/bundler/support/artifice/compact_index_strict_basic_authentication.rb b/spec/bundler/support/artifice/compact_index_strict_basic_authentication.rb
index fa25c4eca1..96259385e7 100644
--- a/spec/bundler/support/artifice/compact_index_strict_basic_authentication.rb
+++ b/spec/bundler/support/artifice/compact_index_strict_basic_authentication.rb
@@ -10,7 +10,7 @@ class CompactIndexStrictBasicAuthentication < CompactIndexAPI
# Only accepts password == "password"
unless env["HTTP_AUTHORIZATION"] == "Basic dXNlcjpwYXNz"
- halt 403, "Authentication failed"
+ halt 401, "Authentication failed"
end
end
end
diff --git a/spec/bundler/support/artifice/compact_index_wrong_gem_checksum.rb b/spec/bundler/support/artifice/compact_index_wrong_gem_checksum.rb
index acc13a56ff..9bd2ca0a9d 100644
--- a/spec/bundler/support/artifice/compact_index_wrong_gem_checksum.rb
+++ b/spec/bundler/support/artifice/compact_index_wrong_gem_checksum.rb
@@ -7,7 +7,8 @@ class CompactIndexWrongGemChecksum < CompactIndexAPI
etag_response do
name = params[:name]
gem = gems.find {|g| g.name == name }
- checksum = ENV.fetch("BUNDLER_SPEC_#{name.upcase}_CHECKSUM") { "ab" * 22 }
+ # This generates the hexdigest "2222222222222222222222222222222222222222222222222222222222222222"
+ checksum = ENV.fetch("BUNDLER_SPEC_#{name.upcase}_CHECKSUM") { "IiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiI=" }
versions = gem ? gem.versions : []
versions.each {|v| v.checksum = checksum }
CompactIndex.info(versions)
diff --git a/spec/bundler/support/artifice/endpoint_500.rb b/spec/bundler/support/artifice/endpoint_500.rb
index d8ab6b65bc..9dd373bbf6 100644
--- a/spec/bundler/support/artifice/endpoint_500.rb
+++ b/spec/bundler/support/artifice/endpoint_500.rb
@@ -2,7 +2,7 @@
require_relative "../path"
-$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gem_path.join("gems/{mustermann,rack,tilt,sinatra,ruby2_keywords}-*/lib")].map(&:to_s))
+$LOAD_PATH.unshift(*Spec::Path.sinatra_dependency_paths)
require "sinatra/base"
diff --git a/spec/bundler/support/artifice/endpoint_creds_diff_host.rb b/spec/bundler/support/artifice/endpoint_creds_diff_host.rb
index ce30de0a68..9cbb4de61a 100644
--- a/spec/bundler/support/artifice/endpoint_creds_diff_host.rb
+++ b/spec/bundler/support/artifice/endpoint_creds_diff_host.rb
@@ -24,7 +24,7 @@ class EndpointCredsDiffHost < Endpoint
end
get "/gems/:id" do
- redirect "http://diffhost.com/no/creds/#{params[:id]}"
+ redirect "http://diffhost.test/no/creds/#{params[:id]}"
end
get "/no/creds/:id" do
diff --git a/spec/bundler/support/artifice/endpoint_host_redirect.rb b/spec/bundler/support/artifice/endpoint_host_redirect.rb
index 0efb6cda02..6ce51bed93 100644
--- a/spec/bundler/support/artifice/endpoint_host_redirect.rb
+++ b/spec/bundler/support/artifice/endpoint_host_redirect.rb
@@ -3,7 +3,7 @@
require_relative "helpers/endpoint"
class EndpointHostRedirect < Endpoint
- get "/fetch/actual/gem/:id", :host_name => "localgemserver.test" do
+ get "/fetch/actual/gem/:id", host_name: "localgemserver.test" do
redirect "http://bundler.localgemserver.test#{request.path_info}"
end
diff --git a/spec/bundler/support/artifice/endpoint_mirror_source.rb b/spec/bundler/support/artifice/endpoint_mirror_source.rb
index 6ea1a77eca..fed7a746b9 100644
--- a/spec/bundler/support/artifice/endpoint_mirror_source.rb
+++ b/spec/bundler/support/artifice/endpoint_mirror_source.rb
@@ -4,7 +4,7 @@ require_relative "helpers/endpoint"
class EndpointMirrorSource < Endpoint
get "/gems/:id" do
- if request.env["HTTP_X_GEMFILE_SOURCE"] == "https://server.example.org/"
+ if request.env["HTTP_X_GEMFILE_SOURCE"] == "https://server.example.org/" && request.env["HTTP_USER_AGENT"].start_with?("bundler")
File.binread("#{gem_repo1}/gems/#{params[:id]}")
else
halt 500
diff --git a/spec/bundler/support/artifice/endpoint_strict_basic_authentication.rb b/spec/bundler/support/artifice/endpoint_strict_basic_authentication.rb
index 8ce1bdd4ad..dff360c5c5 100644
--- a/spec/bundler/support/artifice/endpoint_strict_basic_authentication.rb
+++ b/spec/bundler/support/artifice/endpoint_strict_basic_authentication.rb
@@ -10,7 +10,7 @@ class EndpointStrictBasicAuthentication < Endpoint
# Only accepts password == "password"
unless env["HTTP_AUTHORIZATION"] == "Basic dXNlcjpwYXNz"
- halt 403, "Authentication failed"
+ halt 401, "Authentication failed"
end
end
end
diff --git a/spec/bundler/support/artifice/fail.rb b/spec/bundler/support/artifice/fail.rb
index 6286e43fbd..5ddbc4e590 100644
--- a/spec/bundler/support/artifice/fail.rb
+++ b/spec/bundler/support/artifice/fail.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require "net/http"
+require_relative "../vendored_net_http"
-class Fail < Net::HTTP
- # Net::HTTP uses a @newimpl instance variable to decide whether
+class Fail < Gem::Net::HTTP
+ # Gem::Net::HTTP uses a @newimpl instance variable to decide whether
# to use a legacy implementation. Since we are subclassing
- # Net::HTTP, we must set it
+ # Gem::Net::HTTP, we must set it
@newimpl = true
def request(req, body = nil, &block)
@@ -17,13 +17,11 @@ class Fail < Net::HTTP
end
def exception(req)
- name = ENV.fetch("BUNDLER_SPEC_EXCEPTION") { "Errno::ENETUNREACH" }
- const = name.split("::").reduce(Object) {|mod, sym| mod.const_get(sym) }
- const.new("host down: Bundler spec artifice fail! #{req["PATH_INFO"]}")
+ Errno::ENETUNREACH.new("host down: Bundler spec artifice fail! #{req["PATH_INFO"]}")
end
end
require_relative "helpers/artifice"
-# Replace Net::HTTP with our failing subclass
+# Replace Gem::Net::HTTP with our failing subclass
Artifice.replace_net_http(::Fail)
diff --git a/spec/bundler/support/artifice/helpers/artifice.rb b/spec/bundler/support/artifice/helpers/artifice.rb
index b8c78614fb..788268295c 100644
--- a/spec/bundler/support/artifice/helpers/artifice.rb
+++ b/spec/bundler/support/artifice/helpers/artifice.rb
@@ -4,7 +4,7 @@
module Artifice
# Activate Artifice with a particular Rack endpoint.
#
- # Calling this method will replace the Net::HTTP system
+ # Calling this method will replace the Gem::Net::HTTP system
# with a replacement that routes all requests to the
# Rack endpoint.
#
@@ -18,11 +18,11 @@ module Artifice
# Deactivate the Artifice replacement.
def self.deactivate
- replace_net_http(::Net::HTTP)
+ replace_net_http(::Gem::Net::HTTP)
end
def self.replace_net_http(value)
- ::Net.class_eval do
+ ::Gem::Net.class_eval do
remove_const(:HTTP)
const_set(:HTTP, value)
end
diff --git a/spec/bundler/support/artifice/helpers/compact_index.rb b/spec/bundler/support/artifice/helpers/compact_index.rb
index 4df47a9659..e684aa8628 100644
--- a/spec/bundler/support/artifice/helpers/compact_index.rb
+++ b/spec/bundler/support/artifice/helpers/compact_index.rb
@@ -2,8 +2,9 @@
require_relative "endpoint"
-$LOAD_PATH.unshift Dir[Spec::Path.base_system_gem_path.join("gems/compact_index*/lib")].first.to_s
+$LOAD_PATH.unshift Spec::Path.tmp_root.join("compact_index/lib").to_s
require "compact_index"
+require "digest"
class CompactIndexAPI < Endpoint
helpers do
@@ -17,9 +18,10 @@ class CompactIndexAPI < Endpoint
def etag_response
response_body = yield
- checksum = Digest(:MD5).hexdigest(response_body)
- return if not_modified?(checksum)
- headers "ETag" => quote(checksum)
+ etag = Digest::MD5.hexdigest(response_body)
+ headers "ETag" => quote(etag)
+ return if not_modified?(etag)
+ headers "Repr-Digest" => "sha-256=:#{Digest::SHA256.base64digest(response_body)}:"
headers "Surrogate-Control" => "max-age=2592000, stale-while-revalidate=60"
content_type "text/plain"
requested_range_for(response_body)
@@ -29,17 +31,16 @@ class CompactIndexAPI < Endpoint
raise
end
- def not_modified?(checksum)
+ def not_modified?(etag)
etags = parse_etags(request.env["HTTP_IF_NONE_MATCH"])
- return unless etags.include?(checksum)
- headers "ETag" => quote(checksum)
+ return unless etags.include?(etag)
status 304
body ""
end
def requested_range_for(response_body)
- ranges = Rack::Utils.byte_ranges(env, response_body.bytesize)
+ ranges = Rack::Utils.get_byte_ranges(env["HTTP_RANGE"], response_body.bytesize)
if ranges
status 206
@@ -66,31 +67,40 @@ class CompactIndexAPI < Endpoint
@gems ||= {}
@gems[gem_repo] ||= begin
specs = Bundler::Deprecate.skip_during do
- %w[specs.4.8 prerelease_specs.4.8].map do |filename|
- Marshal.load(File.open(gem_repo.join(filename)).read).map do |name, version, platform|
+ %w[specs.4.8 prerelease_specs.4.8].flat_map do |filename|
+ spec_index = gem_repo.join(filename)
+ next [] unless File.exist?(spec_index)
+
+ Marshal.load(File.binread(spec_index)).map do |name, version, platform|
load_spec(name, version, platform, gem_repo)
end
- end.flatten
+ end
end
specs.group_by(&:name).map do |name, versions|
gem_versions = versions.map do |spec|
- deps = spec.dependencies.select {|d| d.type == :runtime }.map do |d|
+ deps = spec.runtime_dependencies.map do |d|
reqs = d.requirement.requirements.map {|r| r.join(" ") }.join(", ")
CompactIndex::Dependency.new(d.name, reqs)
end
- checksum = begin
- Digest(:SHA256).file("#{gem_repo}/gems/#{spec.original_name}.gem").base64digest
- rescue StandardError
- nil
- end
- CompactIndex::GemVersion.new(spec.version.version, spec.platform.to_s, checksum, nil,
- deps, spec.required_ruby_version.to_s, spec.required_rubygems_version.to_s)
+ begin
+ checksum = ENV.fetch("BUNDLER_SPEC_#{name.upcase}_CHECKSUM") do
+ Digest(:SHA256).file("#{gem_repo}/gems/#{spec.original_name}.gem").hexdigest
+ end
+ rescue StandardError
+ checksum = nil
+ end
+ build_gem_version(spec, deps, checksum)
end
CompactIndex::Gem.new(name, gem_versions)
end
end
end
+
+ def build_gem_version(spec, deps, checksum)
+ CompactIndex::GemVersion.new(spec.version.version, spec.platform.to_s, checksum, nil,
+ deps, spec.required_ruby_version.to_s, spec.required_rubygems_version.to_s)
+ end
end
get "/names" do
diff --git a/spec/bundler/support/artifice/helpers/compact_index_cooldown.rb b/spec/bundler/support/artifice/helpers/compact_index_cooldown.rb
new file mode 100644
index 0000000000..9920fd2c95
--- /dev/null
+++ b/spec/bundler/support/artifice/helpers/compact_index_cooldown.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require_relative "compact_index"
+
+class CompactIndexCooldownAPI < CompactIndexAPI
+ helpers do
+ def build_gem_version(spec, deps, checksum)
+ created_at = spec.date&.utc&.iso8601
+ CompactIndex::GemVersionV2.new(spec.version.version, spec.platform.to_s, checksum, nil,
+ deps, spec.required_ruby_version.to_s, spec.required_rubygems_version.to_s, created_at)
+ end
+ end
+end
diff --git a/spec/bundler/support/artifice/helpers/endpoint.rb b/spec/bundler/support/artifice/helpers/endpoint.rb
index fc0381dc38..9590611dfe 100644
--- a/spec/bundler/support/artifice/helpers/endpoint.rb
+++ b/spec/bundler/support/artifice/helpers/endpoint.rb
@@ -2,7 +2,7 @@
require_relative "../../path"
-$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gem_path.join("gems/{mustermann,rack,tilt,sinatra,ruby2_keywords}-*/lib")].map(&:to_s))
+$LOAD_PATH.unshift(*Spec::Path.sinatra_dependency_paths)
require "sinatra/base"
@@ -27,6 +27,7 @@ class Endpoint < Sinatra::Base
set :raise_errors, true
set :show_exceptions, false
+ set :host_authorization, permitted_hosts: [".example.org", ".local", ".mirror", ".repo", ".repo1", ".repo2", ".repo3", ".repo4", ".rubygems.org", ".security", ".source", ".test", "127.0.0.1"]
def call!(*)
super.tap do
@@ -62,21 +63,21 @@ class Endpoint < Sinatra::Base
return [] if gem_names.nil? || gem_names.empty?
all_specs = %w[specs.4.8 prerelease_specs.4.8].map do |filename|
- Marshal.load(File.open(gem_repo.join(filename)).read)
+ Marshal.load(File.binread(gem_repo.join(filename)))
end.inject(:+)
- all_specs.map do |name, version, platform|
+ all_specs.filter_map do |name, version, platform|
spec = load_spec(name, version, platform, gem_repo)
next unless gem_names.include?(spec.name)
{
- :name => spec.name,
- :number => spec.version.version,
- :platform => spec.platform.to_s,
- :dependencies => spec.dependencies.select {|dep| dep.type == :runtime }.map do |dep|
+ name: spec.name,
+ number: spec.version.version,
+ platform: spec.platform.to_s,
+ dependencies: spec.runtime_dependencies.map do |dep|
[dep.name, dep.requirement.requirements.map {|a| a.join(" ") }.join(", ")]
end,
}
- end.compact
+ end
end
def load_spec(name, version, platform, gem_repo)
diff --git a/spec/bundler/support/artifice/helpers/rack_request.rb b/spec/bundler/support/artifice/helpers/rack_request.rb
index c4a07812a6..05ff034463 100644
--- a/spec/bundler/support/artifice/helpers/rack_request.rb
+++ b/spec/bundler/support/artifice/helpers/rack_request.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "rack/test"
-require "net/http"
+require_relative "../../vendored_net_http"
module Artifice
module Net
@@ -16,25 +16,25 @@ module Artifice
end
end
- class HTTP < ::Net::HTTP
+ class HTTP < ::Gem::Net::HTTP
class << self
attr_accessor :endpoint
end
- # Net::HTTP uses a @newimpl instance variable to decide whether
+ # Gem::Net::HTTP uses a @newimpl instance variable to decide whether
# to use a legacy implementation. Since we are subclassing
- # Net::HTTP, we must set it
+ # Gem::Net::HTTP, we must set it
@newimpl = true
# We don't need to connect, so blank out this method
def connect
end
- # Replace the Net::HTTP request method with a method
+ # Replace the Gem::Net::HTTP request method with a method
# that converts the request into a Rack request and
# dispatches it to the Rack endpoint.
#
- # @param [Net::HTTPRequest] req A Net::HTTPRequest
+ # @param [Net::HTTPRequest] req A Gem::Net::HTTPRequest
# object, or one if its subclasses
# @param [optional, String, #read] body This should
# be sent as "rack.input". If it's a String, it will
@@ -42,7 +42,7 @@ module Artifice
# @return [Net::HTTPResponse]
#
# @yield [Net::HTTPResponse] If a block is provided,
- # this method will yield the Net::HTTPResponse to
+ # this method will yield the Gem::Net::HTTPResponse to
# it after the body is read.
def request(req, body = nil, &block)
rack_request = RackRequest.new(self.class.endpoint)
@@ -56,17 +56,17 @@ module Artifice
body_stream_contents = req.body_stream.read if req.body_stream
response = rack_request.request("#{prefix}#{req.path}",
- { :method => req.method, :input => body || req.body || body_stream_contents })
+ { method: req.method, input: body || req.body || body_stream_contents })
make_net_http_response(response, &block)
end
private
- # This method takes a Rack response and creates a Net::HTTPResponse
+ # This method takes a Rack response and creates a Gem::Net::HTTPResponse
# Instead of trying to mock HTTPResponse directly, we just convert
# the Rack response into a String that looks like a normal HTTP
- # response and call Net::HTTPResponse.read_new
+ # response and call Gem::Net::HTTPResponse.read_new
#
# @param [Array(#to_i, Hash, #each)] response a Rack response
# @return [Net::HTTPResponse]
@@ -86,8 +86,8 @@ module Artifice
response_string << "" << body
- response_io = ::Net::BufferedIO.new(StringIO.new(response_string.join("\n")))
- res = ::Net::HTTPResponse.read_new(response_io)
+ response_io = ::Gem::Net::BufferedIO.new(StringIO.new(response_string.join("\n")))
+ res = ::Gem::Net::HTTPResponse.read_new(response_io)
res.reading_body(response_io, true) do
yield res if block_given?
diff --git a/spec/bundler/support/artifice/vcr.rb b/spec/bundler/support/artifice/vcr.rb
index 6a346f1ff9..0bf5ade8f6 100644
--- a/spec/bundler/support/artifice/vcr.rb
+++ b/spec/bundler/support/artifice/vcr.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require "net/http"
+require_relative "../vendored_net_http"
require_relative "../path"
-CASSETTE_PATH = "#{Spec::Path.spec_dir}/support/artifice/vcr_cassettes"
-USED_CASSETTES_PATH = "#{Spec::Path.spec_dir}/support/artifice/used_cassettes.txt"
+CASSETTE_PATH = "#{Spec::Path.spec_dir}/support/artifice/vcr_cassettes".freeze
+USED_CASSETTES_PATH = "#{Spec::Path.spec_dir}/support/artifice/used_cassettes.txt".freeze
CASSETTE_NAME = ENV.fetch("BUNDLER_SPEC_VCR_CASSETTE_NAME") { "realworld" }
-class BundlerVCRHTTP < Net::HTTP
+class BundlerVCRHTTP < Gem::Net::HTTP
class RequestHandler
attr_reader :http, :request, :body, :response_block
def initialize(http, request, body = nil, &response_block)
@@ -24,7 +24,7 @@ class BundlerVCRHTTP < Net::HTTP
end
File.open(USED_CASSETTES_PATH, "a+") do |f|
- f.puts request_pair_paths.map {|path| Pathname.new(path).relative_path_from(Spec::Path.source_root).to_s }.join("\n")
+ f.puts request_pair_paths.map {|path| Pathname.new(path).relative_path_from(Spec::Path.git_root).to_s }.join("\n")
end
if recorded_response?
@@ -41,8 +41,8 @@ class BundlerVCRHTTP < Net::HTTP
def recorded_response
File.open(request_pair_paths.last, "rb:ASCII-8BIT") do |response_file|
- response_io = ::Net::BufferedIO.new(response_file)
- ::Net::HTTPResponse.read_new(response_io).tap do |response|
+ response_io = ::Gem::Net::BufferedIO.new(response_file)
+ ::Gem::Net::HTTPResponse.read_new(response_io).tap do |response|
response.decode_content = request.decode_content if request.respond_to?(:decode_content)
response.uri = request.uri
@@ -148,5 +148,5 @@ end
require_relative "helpers/artifice"
-# Replace Net::HTTP with our VCR subclass
+# Replace Gem::Net::HTTP with our VCR subclass
Artifice.replace_net_http(BundlerVCRHTTP)
diff --git a/spec/bundler/support/artifice/windows.rb b/spec/bundler/support/artifice/windows.rb
index 4d90e0a426..3056540beb 100644
--- a/spec/bundler/support/artifice/windows.rb
+++ b/spec/bundler/support/artifice/windows.rb
@@ -2,7 +2,7 @@
require_relative "../path"
-$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gem_path.join("gems/{mustermann,rack,tilt,sinatra,ruby2_keywords}-*/lib")].map(&:to_s))
+$LOAD_PATH.unshift(*Spec::Path.sinatra_dependency_paths)
require "sinatra/base"
diff --git a/spec/bundler/support/build_metadata.rb b/spec/bundler/support/build_metadata.rb
index 98d8ac23c8..2eade4137b 100644
--- a/spec/bundler/support/build_metadata.rb
+++ b/spec/bundler/support/build_metadata.rb
@@ -8,22 +8,21 @@ module Spec
include Spec::Path
include Spec::Helpers
- def write_build_metadata(dir: source_root)
+ def write_build_metadata(dir: source_root, version: Bundler::VERSION)
build_metadata = {
- :git_commit_sha => git_commit_sha,
- :built_at => loaded_gemspec.date.utc.strftime("%Y-%m-%d"),
- :release => true,
+ git_commit_sha: git_commit_sha,
+ built_at: release_date_for(version, dir: dir),
}
- replace_build_metadata(build_metadata, dir: dir) # rubocop:disable Style/HashSyntax
+ replace_build_metadata(build_metadata, dir: dir)
end
def reset_build_metadata(dir: source_root)
build_metadata = {
- :release => false,
+ built_at: nil,
}
- replace_build_metadata(build_metadata, dir: dir) # rubocop:disable Style/HashSyntax
+ replace_build_metadata(build_metadata, dir: dir)
end
private
@@ -41,7 +40,12 @@ module Spec
end
def git_commit_sha
- ruby_core_tarball? ? "unknown" : sys_exec("git rev-parse --short HEAD", :dir => source_root).strip
+ ruby_core_tarball? ? "unknown" : git("rev-parse --short HEAD", source_root).strip
+ end
+
+ def release_date_for(version, dir:)
+ changelog = File.expand_path("CHANGELOG.md", dir)
+ File.readlines(changelog)[2].scan(/^## #{Regexp.escape(version)} \((.*)\)/).first&.first if File.exist?(changelog)
end
extend self
diff --git a/spec/bundler/support/builders.rb b/spec/bundler/support/builders.rb
index 3aa5454b6a..43ab7e053d 100644
--- a/spec/bundler/support/builders.rb
+++ b/spec/bundler/support/builders.rb
@@ -2,9 +2,18 @@
require "bundler/shared_helpers"
require "shellwords"
+require "fileutils"
+require "rubygems/package"
+
+require_relative "build_metadata"
module Spec
module Builders
+ def self.extended(mod)
+ mod.extend Path
+ mod.extend Helpers
+ end
+
def self.constantize(name)
name.delete("-").upcase
end
@@ -17,21 +26,7 @@ module Spec
Gem::Platform.new(platform)
end
- # Returns a number smaller than the size of the index. Useful for specs that
- # need the API request limit to be reached for some reason.
- def low_api_request_limit_for(gem_repo)
- all_gems = Dir[gem_repo.join("gems/*.gem")]
-
- all_gem_names = all_gems.map do |file|
- File.basename(file, ".gem").match(/\A(?<gem_name>[^-]+)-.*\z/)[:gem_name]
- end.uniq
-
- (all_gem_names - ["bundler"]).size
- end
-
def build_repo1
- rake_path = Dir["#{Path.base_system_gems}/**/rake*.gem"].first
-
build_repo gem_repo1 do
FileUtils.cp rake_path, "#{gem_repo1}/gems/"
@@ -40,28 +35,28 @@ module Spec
build_gem "puma"
build_gem "minitest"
- build_gem "rack", %w[0.9.1 1.0.0] do |s|
- s.executables = "rackup"
- s.post_install_message = "Rack's post install message"
+ build_gem "myrack", %w[0.9.1 1.0.0] do |s|
+ s.executables = "myrackup"
+ s.post_install_message = "Myrack's post install message"
end
build_gem "thin" do |s|
- s.add_dependency "rack"
+ s.add_dependency "myrack"
s.post_install_message = "Thin's post install message"
end
- build_gem "rack-obama" do |s|
- s.add_dependency "rack"
- s.post_install_message = "Rack-obama's post install message"
+ build_gem "myrack-obama" do |s|
+ s.add_dependency "myrack"
+ s.post_install_message = "Myrack-obama's post install message"
end
- build_gem "rack_middleware", "1.0" do |s|
- s.add_dependency "rack", "0.9.1"
+ build_gem "myrack_middleware", "1.0" do |s|
+ s.add_dependency "myrack", "0.9.1"
end
build_gem "rails", "2.3.2" do |s|
s.executables = "rails"
- s.add_dependency "rake", "13.0.1"
+ s.add_dependency "rake", rake_version
s.add_dependency "actionpack", "2.3.2"
s.add_dependency "activerecord", "2.3.2"
s.add_dependency "actionmailer", "2.3.2"
@@ -85,84 +80,66 @@ module Spec
s.add_dependency "activesupport", ">= 2.0.0"
end
- build_gem "rspec", "1.2.7", :no_default => true do |s|
+ build_gem "rspec", "1.2.7", no_default: true do |s|
s.write "lib/spec.rb", "SPEC = '1.2.7'"
end
- build_gem "rack-test", :no_default => true do |s|
- s.write "lib/rack/test.rb", "RACK_TEST = '1.0'"
- end
-
- build_gem "platform_specific" do |s|
- s.platform = Gem::Platform.local
- s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 #{Gem::Platform.local}'"
+ build_gem "myrack-test", no_default: true do |s|
+ s.write "lib/myrack/test.rb", "MYRACK_TEST = '1.0'"
end
build_gem "platform_specific" do |s|
s.platform = "java"
- s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 JAVA'"
end
build_gem "platform_specific" do |s|
s.platform = "ruby"
- s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 RUBY'"
end
build_gem "platform_specific" do |s|
s.platform = "x86-mswin32"
- s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0 x86-mswin32'"
end
build_gem "platform_specific" do |s|
s.platform = "x64-mswin64"
- s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0 x64-mswin64'"
end
build_gem "platform_specific" do |s|
s.platform = "x86-mingw32"
- s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0 x86-mingw32'"
end
build_gem "platform_specific" do |s|
- s.platform = "x64-mingw32"
- s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0 x64-mingw32'"
+ s.platform = "x64-mingw-ucrt"
end
build_gem "platform_specific" do |s|
- s.platform = "x64-mingw-ucrt"
- s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0 x64-mingw-ucrt'"
+ s.platform = "aarch64-mingw-ucrt"
end
build_gem "platform_specific" do |s|
s.platform = "x86-darwin-100"
- s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 x86-darwin-100'"
end
build_gem "only_java", "1.0" do |s|
s.platform = "java"
- s.write "lib/only_java.rb", "ONLY_JAVA = '1.0.0 JAVA'"
end
build_gem "only_java", "1.1" do |s|
s.platform = "java"
- s.write "lib/only_java.rb", "ONLY_JAVA = '1.1.0 JAVA'"
end
build_gem "nokogiri", "1.4.2"
build_gem "nokogiri", "1.4.2" do |s|
s.platform = "java"
- s.write "lib/nokogiri.rb", "NOKOGIRI = '1.4.2 JAVA'"
s.add_dependency "weakling", ">= 0.0.3"
end
build_gem "laduradura", "5.15.2"
build_gem "laduradura", "5.15.2" do |s|
s.platform = "java"
- s.write "lib/laduradura.rb", "LADURADURA = '5.15.2 JAVA'"
end
build_gem "laduradura", "5.15.3" do |s|
s.platform = "java"
- s.write "lib/laduradura.rb", "LADURADURA = '5.15.2 JAVA'"
end
build_gem "weakling", "0.0.3"
@@ -176,7 +153,7 @@ module Spec
build_gem "bundler", "0.9" do |s|
s.executables = "bundle"
- s.write "bin/bundle", "puts 'FAIL'"
+ s.write "bin/bundle", "#!/usr/bin/env ruby\nputs 'FAIL'"
end
# The bundler 0.8 gem has a rubygems plugin that always loads :(
@@ -203,32 +180,45 @@ module Spec
end
end
- def build_repo2(&blk)
- FileUtils.rm_rf gem_repo2
- FileUtils.cp_r gem_repo1, gem_repo2
- update_repo2(&blk) if block_given?
+ def build_repo2(**kwargs, &blk)
+ FileUtils.cp_r gem_repo1, gem_repo2, remove_destination: true
+ update_repo2(**kwargs, &blk) if block_given?
end
# A repo that has no pre-installed gems included. (The caller completely
# determines the contents with the block.)
- def build_repo4(&blk)
- FileUtils.rm_rf gem_repo4
- build_repo(gem_repo4, &blk)
+ #
+ # If the repo already exists, `#update_repo` will be called.
+ def build_repo3(**kwargs, &blk)
+ if File.exist?(gem_repo3)
+ update_repo(gem_repo3, &blk)
+ else
+ build_repo gem_repo3, **kwargs, &blk
+ end
end
- def update_repo4(&blk)
- update_repo(gem_repo4, &blk)
+ # Like build_repo3, this is a repo that has no pre-installed gems included.
+ #
+ # If the repo already exists, `#udpate_repo` will be called
+ def build_repo4(**kwargs, &blk)
+ if File.exist?(gem_repo4)
+ update_repo gem_repo4, &blk
+ else
+ build_repo gem_repo4, **kwargs, &blk
+ end
end
- def update_repo2
- update_repo gem_repo2 do
- yield if block_given?
- end
+ def update_repo2(**kwargs, &blk)
+ update_repo(gem_repo2, **kwargs, &blk)
+ end
+
+ def update_repo3(&blk)
+ update_repo(gem_repo3, &blk)
end
def build_security_repo
build_repo security_repo do
- build_gem "rack"
+ build_gem "myrack"
build_gem "signed_gem" do |s|
cert = "signing-cert.pem"
@@ -241,39 +231,60 @@ module Spec
end
end
- def build_repo(path, &blk)
- return if File.directory?(path)
-
- FileUtils.mkdir_p("#{path}/gems")
-
- update_repo(path, &blk)
+ # A minimal fake irb console
+ def build_dummy_irb(version = "9.9.9")
+ build_gem "irb", version do |s|
+ s.write "lib/irb.rb", <<-RUBY
+ class IRB
+ class << self
+ def toplevel_binding
+ unless defined?(@toplevel_binding) && @toplevel_binding
+ TOPLEVEL_BINDING.eval %{
+ def self.__irb__; binding; end
+ IRB.instance_variable_set(:@toplevel_binding, __irb__)
+ class << self; undef __irb__; end
+ }
+ end
+ @toplevel_binding.eval('private')
+ @toplevel_binding
+ end
+
+ def __irb__
+ while line = gets
+ begin
+ puts eval(line, toplevel_binding).inspect.sub(/^"(.*)"$/, '=> \\1')
+ rescue Exception => e
+ puts "\#{e.class}: \#{e.message}"
+ puts e.backtrace.first
+ end
+ end
+ end
+ alias start __irb__
+ end
+ end
+ RUBY
+ end
end
- def check_test_gems!
- rake_path = Dir["#{Path.base_system_gems}/**/rake*.gem"].first
+ def build_repo(path, **kwargs, &blk)
+ return if File.directory?(path)
- if rake_path.nil?
- FileUtils.rm_rf(Path.base_system_gems)
- Spec::Rubygems.install_test_deps
- rake_path = Dir["#{Path.base_system_gems}/**/rake*.gem"].first
- end
+ FileUtils.mkdir_p("#{path}/gems")
- if rake_path.nil?
- abort "Your test gems are missing! Run `rm -rf #{tmp}` and try again."
- end
+ update_repo(path,**kwargs, &blk)
end
- def update_repo(path)
- if path == gem_repo1 && caller.first.split(" ").last == "`build_repo`"
+ def update_repo(path, build_compact_index: true)
+ exempted_caller = Gem.ruby_version >= Gem::Version.new("3.4.0.dev") && RUBY_ENGINE != "jruby" ? "#{Module.nesting.first}#build_repo" : "build_repo"
+ if path == gem_repo1 && caller_locations(1, 1).first.label != exempted_caller
raise "Updating gem_repo1 is unsupported -- use gem_repo2 instead"
end
return unless block_given?
@_build_path = "#{path}/gems"
@_build_repo = File.basename(path)
yield
- with_gem_path_as Path.base_system_gem_path do
- gem_command :generate_index, :dir => path
- end
+ options = { build_compact: build_compact_index }
+ Gem::Indexer.new(path, options).generate_index
ensure
@_build_path = nil
@_build_repo = nil
@@ -302,6 +313,10 @@ module Spec
build_with(LibBuilder, name, args, &blk)
end
+ def build_bundler(*args, &blk)
+ build_with(BundlerBuilder, "bundler", args, &blk)
+ end
+
def build_gem(name, *args, &blk)
build_with(GemBuilder, name, args, &blk)
end
@@ -407,6 +422,58 @@ module Spec
alias_method :dep, :runtime
end
+ class BundlerBuilder
+ def initialize(context, name, version)
+ @context = context
+ @spec = Spec::Path.loaded_gemspec.dup
+ @spec.version = version || Bundler::VERSION
+ end
+
+ def required_ruby_version
+ @spec.required_ruby_version
+ end
+
+ def required_ruby_version=(x)
+ @spec.required_ruby_version = x
+ end
+
+ def _build(options = {})
+ full_name = "bundler-#{@spec.version}"
+ build_path = (options[:build_path] || @context.tmp) + full_name
+ bundler_path = build_path + "#{full_name}.gem"
+
+ FileUtils.mkdir_p build_path
+
+ @context.shipped_files.each do |shipped_file|
+ target_shipped_file = shipped_file
+ target_shipped_file = shipped_file.sub(/\Alibexec/, "exe") if @context.ruby_core?
+ target_shipped_file = build_path + target_shipped_file
+ target_shipped_dir = File.dirname(target_shipped_file)
+ FileUtils.mkdir_p target_shipped_dir unless File.directory?(target_shipped_dir)
+ FileUtils.cp File.expand_path(shipped_file, @context.source_root), target_shipped_file, preserve: true
+ end
+
+ @context.replace_version_file(@spec.version, dir: build_path)
+ @context.replace_changelog(@spec.version, dir: build_path) if options[:released]
+
+ Spec::BuildMetadata.write_build_metadata(dir: build_path, version: @spec.version.to_s)
+
+ Dir.chdir build_path do
+ Gem::DefaultUserInteraction.use_ui(Gem::SilentUI.new) do
+ Gem::Package.build(@spec)
+ end
+ end
+
+ if block_given?
+ yield(bundler_path)
+ else
+ FileUtils.mv bundler_path, options[:path]
+ end
+ ensure
+ FileUtils.rm_rf build_path
+ end
+ end
+
class LibBuilder
def initialize(context, name, version)
@context = context
@@ -420,6 +487,7 @@ module Spec
s.email = "foo@bar.baz"
s.homepage = "http://example.com"
s.license = "MIT"
+ s.required_ruby_version = ">= 3.0"
end
@files = {}
end
@@ -436,24 +504,17 @@ module Spec
@spec.executables = Array(val)
@spec.executables.each do |file|
executable = "#{@spec.bindir}/#{file}"
- shebang = if Bundler.current_ruby.jruby?
- "#!/usr/bin/env jruby\n"
- else
- "#!/usr/bin/env ruby\n"
- end
+ shebang = "#!/usr/bin/env ruby\n"
@spec.files << executable
write executable, "#{shebang}require_relative '../lib/#{@name}' ; puts #{Builders.constantize(@name)}"
end
end
def add_c_extension
- require_paths << "ext"
extensions << "ext/extconf.rb"
write "ext/extconf.rb", <<-RUBY
require "mkmf"
- $extout = "$(topdir)/" + RbConfig::CONFIG["EXTOUT"]
-
extension_name = "#{name}_c"
if extra_lib_dir = with_config("ext-lib")
# add extra libpath if --with-ext-lib is
@@ -467,7 +528,7 @@ module Spec
write "ext/#{name}.c", <<-C
#include "ruby.h"
- void Init_#{name}_c() {
+ void Init_#{name}_c(void) {
rb_define_module("#{Builders.constantize(name)}_IN_C");
}
C
@@ -478,7 +539,6 @@ module Spec
if options[:rubygems_version]
@spec.rubygems_version = options[:rubygems_version]
- def @spec.mark_version; end
def @spec.validate(*); end
end
@@ -497,18 +557,16 @@ module Spec
when false
# do nothing
when :yaml
- @spec.files << "#{name}.gemspec"
@files["#{name}.gemspec"] = @spec.to_yaml
else
- @spec.files << "#{name}.gemspec"
@files["#{name}.gemspec"] = @spec.to_ruby
end
@files.each do |file, source|
- file = Pathname.new(path).join(file)
- FileUtils.mkdir_p(file.dirname)
- File.open(file, "w") {|f| f.puts source }
- File.chmod("+x", file) if @spec.executables.map {|exe| "#{@spec.bindir}/#{exe}" }.include?(file)
+ full_path = Pathname.new(path).join(file)
+ FileUtils.mkdir_p(full_path.dirname)
+ File.open(full_path, "w") {|f| f.puts source }
+ FileUtils.chmod("+x", full_path) if @spec.executables.map {|exe| "#{@spec.bindir}/#{exe}" }.include?(file)
end
path
end
@@ -535,7 +593,7 @@ module Spec
default_branch = options[:default_branch] || "main"
path = options[:path] || _default_path
source = options[:source] || "git@#{path}"
- super(options.merge(:path => path, :source => source))
+ super(options.merge(path: path, source: source))
@context.git("config --global init.defaultBranch #{default_branch}", path)
@context.git("init", path)
@context.git("add *", path)
@@ -549,7 +607,7 @@ module Spec
class GitBareBuilder < LibBuilder
def _build(options)
path = options[:path] || _default_path
- super(options.merge(:path => path))
+ super(options.merge(path: path))
@context.git("init --bare", path)
end
end
@@ -574,7 +632,7 @@ module Spec
_default_files.keys.each do |path|
_default_files[path] += "\n#{Builders.constantize(name)}_PREV_REF = '#{current_ref}'"
end
- super(options.merge(:path => libpath, :gemspec => update_gemspec, :source => source))
+ super(options.merge(path: libpath, gemspec: update_gemspec, source: source))
@context.git("commit -am BUMP", libpath)
end
end
@@ -597,25 +655,25 @@ module Spec
class GemBuilder < LibBuilder
def _build(opts)
lib_path = opts[:lib_path] || @context.tmp(".tmp/#{@spec.full_name}")
- lib_path = super(opts.merge(:path => lib_path, :no_default => opts[:no_default]))
+ lib_path = super(opts.merge(path: lib_path, no_default: opts[:no_default]))
destination = opts[:path] || _default_path
FileUtils.mkdir_p(lib_path.join(destination))
- if opts[:gemspec] == :yaml || opts[:gemspec] == false
+ if [:yaml, false].include?(opts[:gemspec])
Dir.chdir(lib_path) do
Bundler.rubygems.build(@spec, opts[:skip_validation])
end
elsif opts[:skip_validation]
- @context.gem_command "build --force #{@spec.name}", :dir => lib_path
+ Dir.chdir(lib_path) { Gem::Package.build(@spec, true) }
else
- @context.gem_command "build #{@spec.name}", :dir => lib_path
+ Dir.chdir(lib_path) { Gem::Package.build(@spec) }
end
gem_path = File.expand_path("#{@spec.full_name}.gem", lib_path)
if opts[:to_system]
- @context.system_gems gem_path, :default => opts[:default]
+ @context.system_gems gem_path, default: opts[:default]
elsif opts[:to_bundle]
- @context.system_gems gem_path, :path => @context.default_bundle_path
+ @context.system_gems gem_path, path: @context.default_bundle_path
else
FileUtils.mv(gem_path, destination)
end
@@ -635,56 +693,56 @@ module Spec
end
end
- TEST_CERT = <<-CERT.gsub(/^\s*/, "")
+ TEST_CERT = <<~CERT
-----BEGIN CERTIFICATE-----
- MIIDMjCCAhqgAwIBAgIBATANBgkqhkiG9w0BAQUFADAnMQwwCgYDVQQDDAN5b3Ux
+ MIIDNTCCAh2gAwIBAgIBATANBgkqhkiG9w0BAQsFADAnMQwwCgYDVQQDDAN5b3Ux
FzAVBgoJkiaJk/IsZAEZFgdleGFtcGxlMB4XDTE1MDIwODAwMTIyM1oXDTQyMDYy
NTAwMTIyM1owJzEMMAoGA1UEAwwDeW91MRcwFQYKCZImiZPyLGQBGRYHZXhhbXBs
- ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANlvFdpN43c4DMS9Jo06
- m0a7k3bQ3HWQ1yrYhZMi77F1F73NpBknYHIzDktQpGn6hs/4QFJT4m4zNEBF47UL
- jHU5nTK5rjkS3niGYUjvh3ZEzVeo9zHUlD/UwflDo4ALl3TSo2KY/KdPS/UTdLXL
- ajkQvaVJtEDgBPE3DPhlj5whp+Ik3mDHej7qpV6F502leAwYaFyOtlEG/ZGNG+nZ
- L0clH0j77HpP42AylHDi+vakEM3xcjo9BeWQ6Vkboic93c9RTt6CWBWxMQP7Nol1
- MOebz9XOSQclxpxWteXNfPRtMdAhmRl76SMI8ywzThNPpa4EH/yz34ftebVOgKyM
- nd0CAwEAAaNpMGcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0OBBYEFA7D
- n9qo0np23qi3aOYuAAPn/5IdMBYGA1UdEQQPMA2BC3lvdUBleGFtcGxlMBYGA1Ud
- EgQPMA2BC3lvdUBleGFtcGxlMA0GCSqGSIb3DQEBBQUAA4IBAQA7Gyk62sWOUX/N
- vk4tJrgKESph6Ns8+E36A7n3jt8zCep8ldzMvwTWquf9iqhsC68FilEoaDnUlWw7
- d6oNuaFkv7zfrWGLlvqQJC+cu2X5EpcCksg5oRp8VNbwJysJ6JgwosxzROII8eXc
- R+j1j6mDvQYqig2QOnzf480pjaqbP+tspfDFZbhKPrgM3Blrb3ZYuFpv4zkqI7aB
- 6fuk2DUhNO1CuwrJA84TqC+jGo73bDKaT5hrIDiaJRrN5+zcWja2uEWrj5jSbep4
- oXdEdyH73hOHMBP40uds3PqnUsxEJhzjB2sCCe1geV24kw9J4m7EQXPVkUKDgKrt
- LlpDmOoo
+ ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMkupYkg3Nd1oXM3fo0d
+ mVJBWNrni88lKDuIIQXwcKe6XCgiloZG708ecLTOws9+o9MkTl9Wtpf/WGXT98NK
+ EPUYakd2Fv1SuD1jWYlP7iDR6hB3RkWBm5ziujYftVJ4ZrPD42PLjDASvlh75Tvr
+ MeM7yq/qkcgNsd9dQyUvMNPks3tla9je7Dt7Auli2IN3CNXys7gIOfwJH0Bb/M6t
+ y7oUfpoUKAfLzwe61abztgDu1lSNgdFBM1kcxYflyh/FkX5TlAcWeAXzLrnxAXGR
+ UxXrxW4oPC+kZi/pDRBd7X4zQDx7bCmr1+FsS3M05i3w5E08Tt9iKRk4V8nCmE4i
+ k6UCAwEAAaNsMGowCQYDVR0TBAIwADAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYE
+ FOOOFw5TNAqt/TcRRZEU3Dg/58XuMBYGA1UdEQQPMA2BC3lvdUBleGFtcGxlMBYG
+ A1UdEgQPMA2BC3lvdUBleGFtcGxlMA0GCSqGSIb3DQEBCwUAA4IBAQAy3xnmobxU
+ 1SyhHvoIXTJmG0wt1DQ/Dqwjy362LpEf1UHt29wtg1Mph58eVtl93z5Vd2t4/O77
+ E2BHpSu9ujc6/Br4+2uA/Qk/xRyLBtZAwty6J4uFvOOg985HonN+RCUZbKSUTmtA
+ TZvNtIDAZFQ8Tu75K4gIBxDcz7biGi4i1VJ3F3GNCNeossr9IQwKvb+UWFq14U5R
+ IzUnGgMIzcjUG2kKQvddRD1CjS+egtcLvShbOfm5bs4w4rfQ2FPF+Aaf9v7fxa/c
+ Jrf3K+cB19eAy7O4nlPG1xurvnZd0QpqRk++werrBuKe1Pgga7YBLePfJhzwqcZv
+ wVOSsB870yeO
-----END CERTIFICATE-----
CERT
- TEST_PKEY = <<-PKEY.gsub(/^\s*/, "")
+ TEST_PKEY = <<~PKEY
-----BEGIN RSA PRIVATE KEY-----
- MIIEowIBAAKCAQEA2W8V2k3jdzgMxL0mjTqbRruTdtDcdZDXKtiFkyLvsXUXvc2k
- GSdgcjMOS1CkafqGz/hAUlPibjM0QEXjtQuMdTmdMrmuORLeeIZhSO+HdkTNV6j3
- MdSUP9TB+UOjgAuXdNKjYpj8p09L9RN0tctqORC9pUm0QOAE8TcM+GWPnCGn4iTe
- YMd6PuqlXoXnTaV4DBhoXI62UQb9kY0b6dkvRyUfSPvsek/jYDKUcOL69qQQzfFy
- Oj0F5ZDpWRuiJz3dz1FO3oJYFbExA/s2iXUw55vP1c5JByXGnFa15c189G0x0CGZ
- GXvpIwjzLDNOE0+lrgQf/LPfh+15tU6ArIyd3QIDAQABAoIBACbDqz20TS1gDMa2
- gj0DidNedbflHKjJHdNBru7Ad8NHgOgR1YO2hXdWquG6itVqGMbTF4SV9/R1pIcg
- 7qvEV1I+50u31tvOBWOvcYCzU48+TO2n7gowQA3xPHPYHzog1uu48fAOHl0lwgD7
- av9OOK3b0jO5pC08wyTOD73pPWU0NrkTh2+N364leIi1pNuI1z4V+nEuIIm7XpVd
- 5V4sXidMTiEMJwE6baEDfTjHKaoRndXrrPo3ryIXmcX7Ag1SwAQwF5fBCRToCgIx
- dszEZB1bJD5gA6r+eGnJLB/F60nK607az5o3EdguoB2LKa6q6krpaRCmZU5svvoF
- J7xgBPECgYEA8RIzHAQ3zbaibKdnllBLIgsqGdSzebTLKheFuigRotEV3Or/z5Lg
- k/nVnThWVkTOSRqXTNpJAME6a4KTdcVSxYP+SdZVO1esazHrGb7xPVb7MWSE1cqp
- WEk3Yy8OUOPoPQMc4dyGzd30Mi8IBB6gnFIYOTrpUo0XtkBv8rGGhfsCgYEA5uYn
- 6QgL4NqNT84IXylmMb5ia3iBt6lhxI/A28CDtQvfScl4eYK0IjBwdfG6E1vJgyzg
- nJzv3xEVo9bz+Kq7CcThWpK5JQaPnsV0Q74Wjk0ShHet15txOdJuKImnh5F6lylC
- GTLR9gnptytfMH/uuw4ws0Q2kcg4l5NHKOWOnAcCgYEAvAwIVkhsB0n59Wu4gCZu
- FUZENxYWUk/XUyQ6KnZrG2ih90xQ8+iMyqFOIm/52R2fFKNrdoWoALC6E3ct8+ZS
- pMRLrelFXx8K3it4SwMJR2H8XBEfFW4bH0UtsW7Zafv+AunUs9LETP5gKG1LgXsq
- qgXX43yy2LQ61O365YPZfdUCgYBVbTvA3MhARbvYldrFEnUL3GtfZbNgdxuD9Mee
- xig0eJMBIrgfBLuOlqtVB70XYnM4xAbKCso4loKSHnofO1N99siFkRlM2JOUY2tz
- kMWZmmxKdFjuF0WZ5f/5oYxI/QsFGC+rUQEbbWl56mMKd5qkvEhKWudxoklF0yiV
- ufC8SwKBgDWb8iWqWN5a/kfvKoxFcDM74UHk/SeKMGAL+ujKLf58F+CbweM5pX9C
- EUsxeoUEraVWTiyFVNqD81rCdceus9TdBj0ZIK1vUttaRZyrMAwF0uQSfjtxsOpd
- l69BkyvzjgDPkmOHVGiSZDLi3YDvypbUpo6LOy4v5rVg5U2F/A0v
+ MIIEowIBAAKCAQEAyS6liSDc13Whczd+jR2ZUkFY2ueLzyUoO4ghBfBwp7pcKCKW
+ hkbvTx5wtM7Cz36j0yROX1a2l/9YZdP3w0oQ9RhqR3YW/VK4PWNZiU/uINHqEHdG
+ RYGbnOK6Nh+1Unhms8PjY8uMMBK+WHvlO+sx4zvKr+qRyA2x311DJS8w0+Sze2Vr
+ 2N7sO3sC6WLYg3cI1fKzuAg5/AkfQFv8zq3LuhR+mhQoB8vPB7rVpvO2AO7WVI2B
+ 0UEzWRzFh+XKH8WRflOUBxZ4BfMuufEBcZFTFevFbig8L6RmL+kNEF3tfjNAPHts
+ KavX4WxLczTmLfDkTTxO32IpGThXycKYTiKTpQIDAQABAoIBABpyrHEWRed5X7aN
+ kXCBzKSN/LLChT8VNnB6bppLnV501yVbmV2hDlg2EJZkfCMvwIptwnPcKs2uqZ4G
+ u2gMC6X9Bgkg/YK4u4nZJBiIzoMNYEUL48wYGYS1dcokaapO3nQ8M1+XjyAexrFL
+ 5btL1IIisScRTQWiGe6FtzcN43sSNkBISyDF5zG4Kodynqi0ekITmMl2q5XLWcsM
+ KBnmZcRFEmFae2YYczVy8SXNApkZEvN69znvAX1iDNnZ3sJFchXo1nRPt4stOOKw
+ mydgIYqaNQ22aF3OkblvoA4Y4m+X2Qt1sfkryKa5xTT7DSE81GmmazNI64EWqtES
+ 6Xde6P0CgYEA+V1vuSnE5fWX188abWMbVwNMC71WfHbntFmI+qwWYPEpickm+RGX
+ DDfXs5unlVX4KUmjfplgavO29op1GZTuD9TlRnUAV0+0aJnNq4DY6XsHfD84qsBr
+ gQGEHeJ1cMGNDnZR/EV3eudMalj9Qjpx9NoXNzMykb0/SUYZQemiqwcCgYEAzokC
+ s0GoHVJqan4dfU0h0G5QPncrajW9DGG1ySxK/A2eqbVB8W2ZQx39OS26/Gydb31p
+ cR7zm8PZpNbzLqlIMEbD4F6q22xxvYVtDx/HHPjxHMi87yxwQ9uLDUHoMa/LciTO
+ djv3D1xTDDGxbpjmsdmINetunAs3htxku7JY5PMCgYBs3/TVvXzwgmhHm28Ib4sS
+ VKgxP/uw4CGORsFd4SDsNp9SP3c6rAltFjyheMaUlzKApFwz/DdyuvIZdp5mCvZe
+ BzALsS3y8SPtv6lixiDu3/6GqvvM4bKOYuESQzvPfVJfDB4DrTjben2MuUnqTqZO
+ p6IXQc1EgIJPNcH1W1LgpQKBgAKZlPAevngIBpDqn4JpSyititMOevxuSr/yJvCu
+ Xw9HOJ0YTAk3APvoT7y9h6IP1/eEU6R56EUotP+vOQZ4WRFKgsK7TllOxyvElzfe
+ hYom1BoxqLc2Dv+7rsdu8fZWKTB5qCOy44xM9DquEXa79AN/IojTOuQ5++v1sErw
+ ls/jAoGBANneGe9ogN51mYkrLyg1fhU1i24gFRq+sPGEvsCUoE6Vjw/lawQQ80T8
+ v45TFqvhoGpgznqy3qxDJyguquZg6HN2yW6HE2Dvk7uk3XogcjdXgNDmWqb2j0eE
+ z9pKzHCqfwNVPuYf44Znyo2YeyZ2kHn42MU73oXuFshUs3QHcH+P
-----END RSA PRIVATE KEY-----
PKEY
end
diff --git a/spec/bundler/support/bundle b/spec/bundler/support/bundle
new file mode 100755
index 0000000000..8f8b535295
--- /dev/null
+++ b/spec/bundler/support/bundle
@@ -0,0 +1,6 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+require_relative "../bundler/support/activate"
+
+load File.expand_path("bundle", Spec::Path.exedir)
diff --git a/spec/bundler/support/bundle.rb b/spec/bundler/support/bundle.rb
index 5f808531ff..aa7b121706 100644
--- a/spec/bundler/support/bundle.rb
+++ b/spec/bundler/support/bundle.rb
@@ -1,10 +1,7 @@
# frozen_string_literal: true
-require "rubygems"
-Gem.instance_variable_set(:@ruby, ENV["RUBY"]) if ENV["RUBY"]
-
require_relative "path"
-bundler_gemspec = Spec::Path.loaded_gemspec
-bundler_gemspec.instance_variable_set(:@full_gem_path, Spec::Path.source_root)
-bundler_gemspec.activate if bundler_gemspec.respond_to?(:activate)
-load File.expand_path("bundle", Spec::Path.bindir)
+
+warn "#{__FILE__} is deprecated. Please use #{Spec::Path.dev_binstub} instead"
+
+load Spec::Path.dev_binstub
diff --git a/spec/bundler/support/checksums.rb b/spec/bundler/support/checksums.rb
new file mode 100644
index 0000000000..7b69bba668
--- /dev/null
+++ b/spec/bundler/support/checksums.rb
@@ -0,0 +1,135 @@
+# frozen_string_literal: true
+
+module Spec
+ module Checksums
+ class ChecksumsBuilder
+ attr_reader :bundler_registered
+
+ def initialize(enabled = true, &block)
+ @enabled = enabled
+ @checksums = {}
+ yield self if block_given?
+ end
+
+ def initialize_copy(original)
+ super
+ @checksums = @checksums.dup
+ end
+
+ def checksum(repo, name, version, platform = Gem::Platform::RUBY, folder = "gems")
+ @bundler_registered = true if name == "bundler"
+
+ name_tuple = Gem::NameTuple.new(name, version, platform)
+ gem_file = File.join(repo, folder, "#{name_tuple.full_name}.gem")
+ File.open(gem_file, "rb") do |f|
+ register(name_tuple, Bundler::Checksum.from_gem(f, "#{gem_file} (via ChecksumsBuilder#checksum)"))
+ end
+ end
+
+ def no_checksum(name, version, platform = Gem::Platform::RUBY)
+ name_tuple = Gem::NameTuple.new(name, version, platform)
+ register(name_tuple, nil)
+ end
+
+ def delete(name, platform = nil)
+ @checksums.reject! {|k, _| k.name == name && (platform.nil? || k.platform == platform) }
+ end
+
+ def to_s
+ return "" unless @enabled
+
+ locked_checksums = @checksums.map do |name_tuple, checksum|
+ checksum &&= " #{checksum.to_lock}"
+ " #{name_tuple.lock_name}#{checksum}\n"
+ end
+
+ "\nCHECKSUMS\n#{locked_checksums.sort.join}"
+ end
+
+ private
+
+ def register(name_tuple, checksum)
+ delete(name_tuple.name, name_tuple.platform)
+ @checksums[name_tuple] = checksum
+ end
+ end
+
+ def checksums_section(enabled = true, bundler_checksum: true, &block)
+ ChecksumsBuilder.new(enabled, &block).tap do |builder|
+ next if builder.bundler_registered || !bundler_checksum
+
+ next if Bundler::VERSION.to_s.end_with?(".dev")
+ builder.checksum(system_gem_path, "bundler", Bundler::VERSION, Gem::Platform::RUBY, "cache")
+ end
+ end
+
+ def checksums_section_when_enabled(target_lockfile = nil, &block)
+ begin
+ enabled = (target_lockfile || lockfile).match?(/^CHECKSUMS$/)
+ rescue Errno::ENOENT
+ enabled = true
+ end
+ checksums_section(enabled, &block)
+ end
+
+ def checksum_to_lock(*args)
+ checksums_section(true, bundler_checksum: false) do |c|
+ c.checksum(*args)
+ end.to_s.sub(/^CHECKSUMS\n/, "").strip
+ end
+
+ def checksum_digest(*args)
+ checksum_to_lock(*args).split(Bundler::Checksum::ALGO_SEPARATOR, 2).last
+ end
+
+ # if prefixes is given, removes all checksums where the line
+ # has any of the prefixes on the line before the checksum
+ # otherwise, removes all checksums from the lockfile
+ def remove_checksums_from_lockfile(lockfile, *prefixes)
+ head, remaining = lockfile.split(/^CHECKSUMS$/, 2)
+ return lockfile unless remaining
+ checksums, tail = remaining.split("\n\n", 2)
+
+ prefixes =
+ if prefixes.empty?
+ nil
+ else
+ /(#{prefixes.map {|p| Regexp.escape(p) }.join("|")})/
+ end
+
+ checksums = checksums.each_line.map do |line|
+ if prefixes.nil? || line.match?(prefixes)
+ line.gsub(/ sha256=[a-f0-9]{64}/i, "")
+ else
+ line
+ end
+ end
+
+ head.concat(
+ "CHECKSUMS",
+ checksums.join,
+ "\n\n",
+ tail
+ )
+ end
+
+ def remove_checksums_section_from_lockfile(lockfile)
+ head, remaining = lockfile.split(/^CHECKSUMS$/, 2)
+ return lockfile unless remaining
+ _checksums, tail = remaining.split("\n\n", 2)
+ head.concat(tail)
+ end
+
+ def checksum_from_package(gem_file, name, version)
+ name_tuple = Gem::NameTuple.new(name, version)
+
+ checksum = nil
+
+ File.open(gem_file, "rb") do |f|
+ checksum = Bundler::Checksum.from_gem(f, gemfile)
+ end
+
+ "#{name_tuple.lock_name} #{checksum.to_lock}"
+ end
+ end
+end
diff --git a/spec/bundler/support/command_execution.rb b/spec/bundler/support/command_execution.rb
index 68e5c56c75..e2915b996d 100644
--- a/spec/bundler/support/command_execution.rb
+++ b/spec/bundler/support/command_execution.rb
@@ -1,7 +1,36 @@
# frozen_string_literal: true
module Spec
- CommandExecution = Struct.new(:command, :working_directory, :exitstatus, :stdout, :stderr) do
+ class CommandExecution
+ def initialize(command, timeout:)
+ @command = command
+ @timeout = timeout
+ @original_stdout = String.new
+ @original_stderr = String.new
+ end
+
+ attr_accessor :exitstatus, :command, :original_stdout, :original_stderr
+ attr_reader :timeout
+ attr_writer :failure_reason
+
+ def raise_error!
+ return unless failure?
+
+ error_header = if failure_reason == :timeout
+ "Invoking `#{command}` was aborted after #{timeout} seconds with output:"
+ else
+ "Invoking `#{command}` failed with output:"
+ end
+
+ raise <<~ERROR
+ #{error_header}
+
+ ----------------------------------------------------------------------
+ #{stdboth}
+ ----------------------------------------------------------------------
+ ERROR
+ end
+
def to_s
"$ #{command}"
end
@@ -11,6 +40,14 @@ module Spec
@stdboth ||= [stderr, stdout].join("\n").strip
end
+ def stdout
+ normalize(original_stdout)
+ end
+
+ def stderr
+ normalize(original_stderr)
+ end
+
def to_s_verbose
[
to_s,
@@ -29,5 +66,13 @@ module Spec
return true unless exitstatus
exitstatus > 0
end
+
+ private
+
+ attr_reader :failure_reason
+
+ def normalize(string)
+ string.dup.force_encoding(Encoding::UTF_8).scrub.strip.gsub("\r\n", "\n")
+ end
end
end
diff --git a/spec/bundler/support/env.rb b/spec/bundler/support/env.rb
new file mode 100644
index 0000000000..0899bd82a3
--- /dev/null
+++ b/spec/bundler/support/env.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Spec
+ module Env
+ def ruby_core?
+ File.exist?(File.expand_path("../../../lib/bundler/bundler.gemspec", __dir__))
+ end
+
+ def rubylib
+ ENV["RUBYLIB"].to_s.split(File::PATH_SEPARATOR)
+ end
+ end
+end
diff --git a/spec/bundler/support/filters.rb b/spec/bundler/support/filters.rb
index 78545d2e64..2be25b4a78 100644
--- a/spec/bundler/support/filters.rb
+++ b/spec/bundler/support/filters.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
class RequirementChecker < Proc
- def self.against(present)
- provided = Gem::Version.new(present)
-
+ def self.against(provided)
new do |required|
- !Gem::Requirement.new(required).satisfied_by?(provided)
+ requirement = Gem::Requirement.new(required)
+
+ !requirement.satisfied_by?(provided)
end.tap do |checker|
checker.provided = provided
end
@@ -14,25 +14,29 @@ class RequirementChecker < Proc
attr_accessor :provided
def inspect
- "\"!= #{provided}\""
+ "\"#{provided}\""
end
end
-RSpec.configure do |config|
- config.filter_run_excluding :realworld => true
-
- git_version = Bundler::Source::Git::GitProxy.new(nil, nil).version
+git_version = Gem::Version.new(`git --version`[/(\d+\.\d+\.\d+)/, 1])
- config.filter_run_excluding :git => RequirementChecker.against(git_version)
- config.filter_run_excluding :bundler => RequirementChecker.against(Bundler::VERSION.split(".")[0])
- config.filter_run_excluding :rubygems => RequirementChecker.against(Gem::VERSION)
- config.filter_run_excluding :ruby_repo => !ENV["GEM_COMMAND"].nil?
- config.filter_run_excluding :no_color_tty => Gem.win_platform? || !ENV["GITHUB_ACTION"].nil?
- config.filter_run_excluding :permissions => Gem.win_platform?
- config.filter_run_excluding :readline => Gem.win_platform?
- config.filter_run_excluding :jruby_only => RUBY_ENGINE != "jruby"
- config.filter_run_excluding :truffleruby_only => RUBY_ENGINE != "truffleruby"
- config.filter_run_excluding :man => Gem.win_platform?
+RSpec.configure do |config|
+ config.filter_run_excluding realworld: true
+
+ config.filter_run_excluding rubygems: RequirementChecker.against(Gem.rubygems_version)
+ config.filter_run_excluding git: RequirementChecker.against(git_version)
+ config.filter_run_excluding ruby_repo: !ENV["GEM_COMMAND"].nil?
+ config.filter_run_excluding no_color_tty: Gem.win_platform? || !ENV["GITHUB_ACTION"].nil?
+ config.filter_run_excluding permissions: Gem.win_platform?
+ config.filter_run_excluding readline: Gem.win_platform?
+ config.filter_run_excluding jruby_only: RUBY_ENGINE != "jruby"
+ config.filter_run_excluding truffleruby_only: RUBY_ENGINE != "truffleruby"
+ config.filter_run_excluding man: Gem.win_platform?
+ config.filter_run_excluding mri_only: RUBY_ENGINE != "ruby"
config.filter_run_when_matching :focus unless ENV["CI"]
+
+ config.before(:each, :bundler) do |example|
+ bundle_config "simulate_version #{example.metadata[:bundler]}"
+ end
end
diff --git a/spec/bundler/support/hax.rb b/spec/bundler/support/hax.rb
index c7fe3637cc..46718f5fa4 100644
--- a/spec/bundler/support/hax.rb
+++ b/spec/bundler/support/hax.rb
@@ -19,35 +19,56 @@ module Gem
@default_specifications_dir = nil
end
- if ENV["BUNDLER_SPEC_WINDOWS"]
- @@win_platform = true # rubocop:disable Style/ClassVars
- end
+ spec_platform = ENV["BUNDLER_SPEC_PLATFORM"]
+ if spec_platform
+ if /mingw|mswin/.match?(spec_platform)
+ @@win_platform = nil # rubocop:disable Style/ClassVars
+ RbConfig::CONFIG["host_os"] = spec_platform.gsub(/^[^-]+-/, "").tr("-", "_")
+ end
- if ENV["BUNDLER_SPEC_PLATFORM"]
- previous_platforms = @platforms
- previous_local = Platform.local
+ RbConfig::CONFIG["arch"] = spec_platform
class Platform
- @local = new(ENV["BUNDLER_SPEC_PLATFORM"])
+ @local = nil
end
- @platforms = previous_platforms.map {|platform| platform == previous_local ? Platform.local : platform }
+ @platforms = []
end
if ENV["BUNDLER_SPEC_GEM_SOURCES"]
self.sources = [ENV["BUNDLER_SPEC_GEM_SOURCES"]]
end
- if ENV["BUNDLER_IGNORE_DEFAULT_GEM"]
- module RemoveDefaultBundlerStub
- def default_stubs(pattern = "*")
- super.delete_if {|stub| stub.name == "bundler" }
+ if ENV["BUNDLER_SPEC_READ_ONLY"]
+ module ReadOnly
+ def open(file, mode)
+ if file != IO::NULL && mode == "wb"
+ raise Errno::EROFS
+ else
+ super
+ end
end
end
- class Specification
- class << self
- prepend RemoveDefaultBundlerStub
+ File.singleton_class.prepend ReadOnly
+ end
+
+ if ENV["BUNDLER_SPEC_FAKE_RESOLVE"]
+ module FakeResolv
+ def getaddrinfo(host, port)
+ if host == ENV["BUNDLER_SPEC_FAKE_RESOLVE"]
+ [["AF_INET", port, "127.0.0.1", "127.0.0.1", 2, 2, 17]]
+ else
+ super
+ end
end
end
+
+ Socket.singleton_class.prepend FakeResolv
end
end
+
+# mise installed rubygems_plugin.rb to system wide `site_ruby` directory.
+# This empty module avoid to call `mise` command.
+module ReshimInstaller
+ def self.reshim; end
+end
diff --git a/spec/bundler/support/helpers.rb b/spec/bundler/support/helpers.rb
index 703c98eca3..b0d4b5008b 100644
--- a/spec/bundler/support/helpers.rb
+++ b/spec/bundler/support/helpers.rb
@@ -1,66 +1,52 @@
# frozen_string_literal: true
-require_relative "command_execution"
require_relative "the_bundle"
require_relative "path"
+require_relative "options"
+require_relative "subprocess"
module Spec
module Helpers
include Spec::Path
+ include Spec::Options
+ include Spec::Subprocess
+
+ def self.extended(mod)
+ mod.extend Spec::Path
+ mod.extend Spec::Options
+ mod.extend Spec::Subprocess
+ end
def reset!
Dir.glob("#{tmp}/{gems/*,*}", File::FNM_DOTMATCH).each do |dir|
next if %w[base base_system remote1 rubocop standard gems rubygems . ..].include?(File.basename(dir))
- FileUtils.rm_rf(dir)
+ FileUtils.rm_r(dir)
end
FileUtils.mkdir_p(home)
FileUtils.mkdir_p(tmpdir)
- reset_paths!
- end
-
- def reset_paths!
Bundler.reset!
+ Bundler::Source::Git::GitProxy.reset
Gem.clear_paths
end
- def the_bundle(*args)
- TheBundle.new(*args)
- end
-
- def command_executions
- @command_executions ||= []
+ def the_bundle
+ TheBundle.new
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
-
- MAJOR_DEPRECATION = /^\[DEPRECATED\]\s*/.freeze
+ MAJOR_DEPRECATION = /^\[DEPRECATED\]\s*/
def err_without_deprecations
err.gsub(/#{MAJOR_DEPRECATION}.+[\n]?/, "")
end
def deprecations
- err.split("\n").select {|l| l =~ MAJOR_DEPRECATION }.join("\n").split(MAJOR_DEPRECATION)
- end
-
- def exitstatus
- last_command.exitstatus
+ err.split("\n").filter_map {|l| l.sub(MAJOR_DEPRECATION, "") if l.match?(MAJOR_DEPRECATION) }
end
def run(cmd, *args)
opts = args.last.is_a?(Hash) ? args.pop : {}
groups = args.map(&:inspect).join(", ")
- setup = "require '#{entrypoint}' ; Bundler.ui.silence { Bundler.setup(#{groups}) }"
+ setup = "require 'bundler' ; Bundler.ui.silence { Bundler.setup(#{groups}) }"
ruby([setup, cmd].join(" ; "), opts)
end
@@ -69,7 +55,7 @@ module Spec
begin
#{ruby}
rescue LoadError => e
- warn "ZOMG LOAD ERROR" if e.message.include?("-- #{name}")
+ warn e.message if e.message.include?("-- #{name}")
end
RUBY
opts = args.last.is_a?(Hash) ? args.pop : {}
@@ -77,6 +63,10 @@ module Spec
run(cmd, *args)
end
+ def in_bundled_app(cmd, options = {})
+ sys_exec(cmd, dir: bundled_app, raise_on_error: options[:raise_on_error])
+ end
+
def bundle(cmd, options = {}, &block)
bundle_bin = options.delete(:bundle_bin)
bundle_bin ||= installed_bindir.join("bundle")
@@ -84,29 +74,24 @@ module Spec
env = options.delete(:env) || {}
requires = options.delete(:requires) || []
- realworld = RSpec.current_example.metadata[:realworld]
- artifice = options.delete(:artifice) do
- if realworld
- "vcr"
- else
- "fail"
- end
- end
- if artifice
- requires << "#{Path.spec_dir}/support/artifice/#{artifice}.rb"
- end
+ dir = options.delete(:dir) || bundled_app
+ custom_load_path = options.delete(:load_path)
load_path = []
- load_path << spec_dir
+ load_path << custom_load_path if custom_load_path
+
+ build_env_options = { load_path: load_path, requires: requires, env: env }
+ build_env_options.merge!(artifice: options.delete(:artifice)) if options.key?(:artifice) || cmd.start_with?("exec")
+
+ match_source(cmd)
+
+ env = build_env(build_env_options)
- dir = options.delete(:dir) || bundled_app
raise_on_error = options.delete(:raise_on_error)
args = options.map do |k, v|
case v
- when nil
- next
when true
" --#{k}"
when false
@@ -116,20 +101,31 @@ module Spec
end
end.join
- ruby_cmd = build_ruby_cmd({ :load_path => load_path, :requires => requires })
- cmd = "#{ruby_cmd} #{bundle_bin} #{cmd}#{args}"
- sys_exec(cmd, { :env => env, :dir => dir, :raise_on_error => raise_on_error }, &block)
+ cmd = "#{Gem.ruby} #{bundle_bin} #{cmd}#{args}"
+ sys_exec(cmd, { env: env, dir: dir, raise_on_error: raise_on_error }, &block)
+ end
+
+ def main_source(dir)
+ gemfile = File.expand_path("Gemfile", dir)
+ return unless File.exist?(gemfile)
+
+ match = File.readlines(gemfile).first.match(/source ["'](?<source>[^"']+)["']/)
+ return unless match
+
+ match[:source]
end
def bundler(cmd, options = {})
- options[:bundle_bin] = system_gem_path.join("bin/bundler")
+ options[:bundle_bin] = system_gem_path("bin/bundler")
bundle(cmd, options)
end
def ruby(ruby, options = {})
- ruby_cmd = build_ruby_cmd
+ env = build_env({ artifice: nil }.merge(options))
escaped_ruby = ruby.shellescape
- sys_exec(%(#{ruby_cmd} -w -e #{escaped_ruby}), options)
+ options[:env] = env if env
+ options[:dir] ||= bundled_app
+ sys_exec(%(#{Gem.ruby} -w -e #{escaped_ruby}), options)
end
def load_error_ruby(ruby, name, opts = {})
@@ -137,20 +133,49 @@ module Spec
begin
#{ruby}
rescue LoadError => e
- warn "ZOMG LOAD ERROR" if e.message.include?("-- #{name}")
+ warn e.message if e.message.include?("-- #{name}")
end
R
end
- def build_ruby_cmd(options = {})
- libs = options.delete(:load_path)
- lib_option = libs ? "-I#{libs.join(File::PATH_SEPARATOR)}" : []
+ def build_env(options = {})
+ env = options.delete(:env) || {}
+ libs = options.delete(:load_path) || []
+ env["RUBYOPT"] = opt_add("-I#{libs.join(File::PATH_SEPARATOR)}", env["RUBYOPT"]) if libs.any?
+
+ current_example = RSpec.current_example
+
+ main_source = @gemfile_source if defined?(@gemfile_source)
+ compact_index_main_source = main_source&.start_with?("https://gem.repo", "https://gems.security")
requires = options.delete(:requires) || []
- requires << "#{Path.spec_dir}/support/hax.rb"
- require_option = requires.map {|r| "-r#{r}" }
+ requires << hax
+
+ artifice = options.delete(:artifice) do
+ if current_example && current_example.metadata[:realworld]
+ "vcr"
+ elsif compact_index_main_source
+ env["BUNDLER_SPEC_GEM_REPO"] ||=
+ case main_source
+ when "https://gem.repo1" then gem_repo1.to_s
+ when "https://gem.repo2" then gem_repo2.to_s
+ when "https://gem.repo3" then gem_repo3.to_s
+ when "https://gem.repo4" then gem_repo4.to_s
+ when "https://gems.security" then security_repo.to_s
+ end
+
+ "compact_index"
+ else
+ "fail"
+ end
+ end
+ if artifice
+ requires << "#{Path.spec_dir}/support/artifice/#{artifice}.rb"
+ end
+
+ 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 = {})
@@ -158,85 +183,55 @@ module Spec
sys_exec(cmd.to_s, options)
end
- def gem_command(command, options = {})
+ def sys_exec(cmd, options = {}, &block)
env = options[:env] || {}
- env["RUBYOPT"] = opt_add(opt_add("-r#{spec_dir}/support/hax.rb", env["RUBYOPT"]), ENV["RUBYOPT"])
+ env["RUBYOPT"] = opt_add(opt_add("-r#{spec_dir}/support/switch_rubygems.rb", env["RUBYOPT"]), ENV["RUBYOPT"])
options[:env] = env
- sys_exec("#{Path.gem_bin} #{command}", options)
- end
- def rake
- "#{Gem.ruby} -S #{ENV["GEM_PATH"]}/bin/rake"
+ 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.stdout = stdout_read_thread.value.strip
- command_execution.stderr = stderr_read_thread.value.strip
-
- status = wait_thr.value
- command_execution.exitstatus = if status.exited?
- status.exitstatus
- elsif status.signaled?
- exit_status_for_signal(status.termsig)
- end
- end
-
- unless options[:raise_on_error] == false || command_execution.success?
- raise <<~ERROR
-
- Invoking `#{cmd}` failed with output:
- ----------------------------------------------------------------------
- #{command_execution.stdboth}
- ----------------------------------------------------------------------
- ERROR
+ 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
- command_executions << command_execution
+ current = File.exist?(path) ? Psych.load_file(path) : {}
+ return current unless config
- 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
@@ -244,8 +239,9 @@ module Spec
contents = args.pop
if contents.nil?
- File.open(bundled_app_gemfile, "r", &:read)
+ read_gemfile
else
+ match_source(contents)
create_file(args.pop || "Gemfile", contents)
end
end
@@ -254,12 +250,24 @@ module Spec
contents = args.pop
if contents.nil?
- File.open(bundled_app_lock, "r", &:read)
+ read_lockfile
else
create_file(args.pop || "Gemfile.lock", contents)
end
end
+ def read_gemfile(file = "Gemfile")
+ read_bundled_app_file(file)
+ end
+
+ def read_lockfile(file = "Gemfile.lock")
+ read_bundled_app_file(file)
+ end
+
+ def read_bundled_app_file(file)
+ bundled_app(file).read
+ end
+
def strip_whitespace(str)
# Trim the leading spaces
spaces = str[/\A\s+/, 0] || ""
@@ -278,62 +286,123 @@ module Spec
bundle :lock, opts
end
+ def base_system_gems(*names, **options)
+ system_gems names.map {|name| find_base_path(name) }, **options
+ end
+
def system_gems(*gems)
gems = gems.flatten
options = gems.last.is_a?(Hash) ? gems.pop : {}
- path = options.fetch(:path, system_gem_path)
+ install_dir = options.fetch(:path, system_gem_path)
default = options.fetch(:default, false)
- with_gem_path_as(path) do
- gem_repo = options.fetch(:gem_repo, gem_repo1)
- gems.each do |g|
- gem_name = g.to_s
- if gem_name.start_with?("bundler")
- version = gem_name.match(/\Abundler-(?<version>.*)\z/)[:version] if gem_name != "bundler"
- with_built_bundler(version) {|gem_path| install_gem(gem_path, default) }
- elsif %r{\A(?:[a-zA-Z]:)?/.*\.gem\z}.match?(gem_name)
- install_gem(gem_name, default)
- else
- install_gem("#{gem_repo}/gems/#{gem_name}.gem", default)
- end
+ gems.each do |g|
+ gem_name = g.to_s
+ bundler = gem_name.match(/\Abundler-(?<version>.*)\z/)
+
+ if bundler
+ with_built_bundler(bundler[:version], released: options.fetch(:released, false)) {|gem_path| install_gem(gem_path, install_dir, default) }
+ elsif %r{\A(?:[a-zA-Z]:)?/.*\.gem\z}.match?(gem_name)
+ install_gem(gem_name, install_dir, default)
+ else
+ gem_repo = options.fetch(:gem_repo, gem_repo1)
+ install_gem("#{gem_repo}/gems/#{gem_name}.gem", install_dir, default)
end
end
end
- def install_gem(path, default = false)
- raise "OMG `#{path}` does not exist!" unless File.exist?(path)
+ def self.install_dev_bundler
+ extend self
- args = "--no-document --ignore-dependencies"
- args += " --default --install-dir #{system_gem_path}" if default
+ with_built_bundler(nil, build_path: tmp_root) {|gem_path| install_gem(gem_path, pristine_system_gem_path) }
+ end
+
+ def install_gem(path, install_dir, default = false)
+ raise ArgumentError, "`#{path}` does not exist!" unless File.exist?(path)
+
+ require "rubygems/installer"
+
+ with_simulated_platform do
+ installer = Gem::Installer.at(
+ path.to_s,
+ install_dir: install_dir.to_s,
+ document: [],
+ ignore_dependencies: true,
+ wrappers: true,
+ env_shebang: true,
+ force: true
+ )
+ installer.install
+ end
- gem_command "install #{args} '#{path}'"
+ if default
+ gem = Pathname.new(path).basename.to_s.match(/(.*)\.gem/)[1]
+
+ # Revert Gem::Installer#write_spec and apply Gem::Installer#write_default_spec
+ FileUtils.mkdir_p File.join(install_dir, "specifications", "default")
+ File.rename File.join(install_dir, "specifications", gem + ".gemspec"),
+ File.join(install_dir, "specifications", "default", gem + ".gemspec")
+
+ # Revert Gem::Installer#write_cache_file
+ File.delete File.join(install_dir, "cache", gem + ".gem")
+ end
end
- def with_built_bundler(version = nil)
- version ||= Bundler::VERSION
- full_name = "bundler-#{version}"
- build_path = tmp + full_name
- bundler_path = build_path + "#{full_name}.gem"
+ def uninstall_gem(name, options = {})
+ require "rubygems/uninstaller"
- Dir.mkdir build_path
+ gem_home = options.dig(:env, "GEM_HOME") || system_gem_path.to_s
- begin
- shipped_files.each do |shipped_file|
- target_shipped_file = build_path + shipped_file
- target_shipped_dir = File.dirname(target_shipped_file)
- FileUtils.mkdir_p target_shipped_dir unless File.directory?(target_shipped_dir)
- FileUtils.cp shipped_file, target_shipped_file, :preserve => true
- end
+ with_env_vars("GEM_HOME" => gem_home) do
+ Gem.clear_paths
- replace_version_file(version, dir: build_path) # rubocop:disable Style/HashSyntax
+ uninstaller = Gem::Uninstaller.new(
+ name,
+ ignore: true,
+ executables: true,
+ all: true
+ )
+ uninstaller.uninstall
+ ensure
+ Gem.clear_paths
+ end
+ end
- Spec::BuildMetadata.write_build_metadata(dir: build_path) # rubocop:disable Style/HashSyntax
+ def installed_gems_list(options = {})
+ gem_home = options.dig(:env, "GEM_HOME") || system_gem_path.to_s
- gem_command "build #{relative_gemspec}", :dir => build_path
+ # Temporarily set GEM_HOME for the command
+ old_gem_home = ENV["GEM_HOME"]
+ ENV["GEM_HOME"] = gem_home
+ Gem.clear_paths
- yield(bundler_path)
+ begin
+ require "rubygems/commands/list_command"
+
+ # Capture output from the list command
+ require "stringio"
+ output_io = StringIO.new
+ cmd = Gem::Commands::ListCommand.new
+ cmd.ui = Gem::StreamUI.new(StringIO.new, output_io, StringIO.new, false)
+ cmd.invoke
+ output = output_io.string.strip
ensure
- build_path.rmtree
+ ENV["GEM_HOME"] = old_gem_home
+ Gem.clear_paths
end
+
+ # Create a fake command execution so `out` helper works
+ command_execution = Spec::CommandExecution.new("gem list", timeout: 60)
+ command_execution.original_stdout << output
+ command_execution.exitstatus = 0
+ command_executions << command_execution
+
+ output
+ end
+
+ def with_built_bundler(version = nil, opts = {}, &block)
+ require_relative "builders"
+
+ Builders::BundlerBuilder.new(self, "bundler", version)._build(opts, &block)
end
def with_gem_path_as(path)
@@ -361,20 +430,40 @@ module Spec
ENV.replace(backup)
end
- def with_path_added(path)
- with_path_as([path.to_s, ENV["PATH"]].join(File::PATH_SEPARATOR)) do
- yield
+ # Simulate the platform set by BUNDLER_SPEC_PLATFORM for in-process
+ # operations, mirroring what hax.rb does for subprocesses.
+ def with_simulated_platform
+ spec_platform = ENV["BUNDLER_SPEC_PLATFORM"]
+ unless spec_platform
+ return yield
end
- end
- def opt_add(option, options)
- [option.strip, options].compact.reject(&:empty?).join(" ")
- end
+ old_arch = RbConfig::CONFIG["arch"]
+ old_host_os = RbConfig::CONFIG["host_os"]
+
+ 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
- def opt_remove(option, options)
- return unless options
+ RbConfig::CONFIG["arch"] = spec_platform
+ Gem::Platform.instance_variable_set(:@local, nil)
+ Gem.instance_variable_set(:@platforms, [])
- options.split(" ").reject {|opt| opt.strip == option.strip }.join(" ")
+ yield
+ ensure
+ if spec_platform
+ RbConfig::CONFIG["arch"] = old_arch
+ RbConfig::CONFIG["host_os"] = old_host_os
+ Gem::Platform.instance_variable_set(:@local, nil)
+ Gem.instance_variable_set(:@platforms, [])
+ end
+ end
+
+ def with_path_added(path)
+ with_path_as([path.to_s, ENV["PATH"]].join(File::PATH_SEPARATOR)) do
+ yield
+ end
end
def break_git!
@@ -387,49 +476,43 @@ module Spec
end
def with_fake_man
- skip "fake_man is not a Windows friendly binstub" if Gem.win_platform?
-
FileUtils.mkdir_p(tmp("fake_man"))
- File.open(tmp("fake_man/man"), "w", 0o755) do |f|
- f.puts "#!/usr/bin/env ruby\nputs ARGV.inspect\n"
- end
+ create_file(tmp("fake_man/man"), <<~SCRIPT)
+ #!/usr/bin/env ruby
+ puts ARGV.inspect
+ SCRIPT
with_path_added(tmp("fake_man")) { yield }
end
def pristine_system_gems(*gems)
- FileUtils.rm_rf(system_gem_path)
-
- system_gems(*gems)
- end
-
- def realworld_system_gems(*gems)
- gems = gems.flatten
- opts = gems.last.is_a?(Hash) ? gems.pop : {}
- path = opts.fetch(:path, system_gem_path)
+ FileUtils.rm_r(system_gem_path)
- with_gem_path_as(path) do
- gems.each do |gem|
- gem_command "install --no-document #{gem}"
- end
+ if gems.any?
+ system_gems(*gems)
+ else
+ default_system_gems
end
end
- def cache_gems(*gems)
+ def cache_gems(*gems, gem_repo: gem_repo1)
gems = gems.flatten
- FileUtils.rm_rf("#{bundled_app}/vendor/cache")
FileUtils.mkdir_p("#{bundled_app}/vendor/cache")
gems.each do |g|
- path = "#{gem_repo1}/gems/#{g}.gem"
- raise "OMG `#{path}` does not exist!" unless File.exist?(path)
+ path = "#{gem_repo}/gems/#{g}.gem"
+ raise ArgumentError, "`#{path}` does not exist!" unless File.exist?(path)
FileUtils.cp(path, "#{bundled_app}/vendor/cache")
end
end
def simulate_new_machine
- FileUtils.rm_rf bundled_app(".bundle")
- pristine_system_gems :bundler
+ FileUtils.rm_r bundled_app(".bundle")
+ pristine_system_gems
+ end
+
+ def default_system_gems
+ FileUtils.cp_r pristine_system_gem_path, system_gem_path
end
def simulate_ruby_platform(ruby_platform)
@@ -443,39 +526,9 @@ module Spec
def simulate_platform(platform)
old = ENV["BUNDLER_SPEC_PLATFORM"]
ENV["BUNDLER_SPEC_PLATFORM"] = platform.to_s
- yield if block_given?
- ensure
- ENV["BUNDLER_SPEC_PLATFORM"] = old if block_given?
- end
-
- def simulate_windows(platform = x86_mswin32)
- old = ENV["BUNDLER_SPEC_WINDOWS"]
- ENV["BUNDLER_SPEC_WINDOWS"] = "true"
- simulate_platform platform do
- simulate_bundler_version_when_missing_prerelease_default_gem_activation do
- yield
- end
- end
- ensure
- ENV["BUNDLER_SPEC_WINDOWS"] = old
- end
-
- def simulate_bundler_version_when_missing_prerelease_default_gem_activation
- return yield unless rubygems_version_failing_to_activate_bundler_prereleases
-
- old = ENV["BUNDLER_VERSION"]
- ENV["BUNDLER_VERSION"] = Bundler::VERSION
yield
ensure
- ENV["BUNDLER_VERSION"] = old
- end
-
- def env_for_missing_prerelease_default_gem_activation
- if rubygems_version_failing_to_activate_bundler_prereleases
- { "BUNDLER_VERSION" => Bundler::VERSION }
- else
- {}
- end
+ ENV["BUNDLER_SPEC_PLATFORM"] = old if block_given?
end
def current_ruby_minor
@@ -483,17 +536,15 @@ module Spec
end
def next_ruby_minor
- Gem.ruby_version.segments[0..1].map.with_index {|s, i| i == 1 ? s + 1 : s }.join(".")
+ ruby_major_minor.map.with_index {|s, i| i == 1 ? s + 1 : s }.join(".")
end
- # versions not including
- # https://github.com/rubygems/rubygems/commit/929e92d752baad3a08f3ac92eaec162cb96aedd1
- def rubygems_version_failing_to_activate_bundler_prereleases
- Gem.rubygems_version < Gem::Version.new("3.1.0.pre.1")
+ 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)
@@ -545,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 670f3b0230..1fbdd49abe 100644
--- a/spec/bundler/support/indexes.rb
+++ b/spec/bundler/support/indexes.rb
@@ -14,26 +14,21 @@ module Spec
alias_method :platforms, :platform
- def resolve(args = [])
+ def resolve(args = [], dependency_api_available: true)
@platforms ||= ["ruby"]
- default_source = instance_double("Bundler::Source::Rubygems", :specs => @index, :to_s => "locally install gems")
- source_requirements = { :default => default_source }
- args[0] ||= Bundler::SpecSet.new([]) # base
- args[0].each {|ls| ls.source = default_source }
- args[1] ||= Bundler::GemVersionPromoter.new # gem_version_promoter
- args[2] ||= [] # additional_base_requirements
- originally_locked = args[3] || Bundler::SpecSet.new([])
- unlock = args[4] || []
- packages = Hash.new do |h, k|
- h[k] = Bundler::Resolver::Package.new(k, @platforms, originally_locked, unlock)
- end
+ default_source = instance_double("Bundler::Source::Rubygems", specs: @index, to_s: "locally install gems", dependency_api_available?: dependency_api_available)
+ source_requirements = { default: default_source }
+ base = args[0] || Bundler::SpecSet.new([])
+ base.each {|ls| ls.source = default_source }
+ gem_version_promoter = args[1] || Bundler::GemVersionPromoter.new
+ originally_locked = args[2] || Bundler::SpecSet.new([])
+ unlock = args[3] || []
@deps.each do |d|
name = d.name
- platforms = d.gem_platforms(@platforms)
source_requirements[name] = d.source = default_source
- packages[name] = Bundler::Resolver::Package.new(name, platforms, originally_locked, unlock, :dependency => d)
end
- Bundler::Resolver.new(source_requirements, *args[0..2]).start(@deps, packages)
+ packages = Bundler::Resolver::Base.new(source_requirements, @deps, base, @platforms, locked_specs: originally_locked, unlock: unlock)
+ Bundler::Resolver.new(packages, gem_version_promoter).start
end
def should_not_resolve
@@ -46,6 +41,12 @@ module Spec
expect(got).to eq(specs.sort)
end
+ def should_resolve_without_dependency_api(specs)
+ got = resolve(dependency_api_available: false)
+ got = got.map(&:full_name).sort
+ expect(got).to eq(specs.sort)
+ end
+
def should_resolve_and_include(specs, args = [])
got = resolve(args)
got = got.map(&:full_name).sort
@@ -65,19 +66,18 @@ 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
s.strict = opts.include?(:strict)
end
- should_resolve_and_include specs, [@base, search, [], @locked, unlock]
+ should_resolve_and_include specs, [@base, search, @locked, unlock]
end
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"]
@@ -88,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
@@ -122,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
@@ -303,7 +303,7 @@ module Spec
end
end
- def a_unresovable_child_index
+ def a_unresolvable_child_index
build_index do
gem "json", %w[1.8.0]
@@ -366,7 +366,7 @@ module Spec
def a_circular_index
build_index do
- gem "rack", "1.0.1"
+ gem "myrack", "1.0.1"
gem("foo", "0.2.6") do
dep "bar", ">= 0"
end
diff --git a/spec/bundler/support/matchers.rb b/spec/bundler/support/matchers.rb
index ea7c784683..5a3c38a4db 100644
--- a/spec/bundler/support/matchers.rb
+++ b/spec/bundler/support/matchers.rb
@@ -52,7 +52,7 @@ module Spec
end
def self.define_compound_matcher(matcher, preconditions, &declarations)
- raise "Must have preconditions to define a compound matcher" if preconditions.empty?
+ raise ArgumentError, "Must have preconditions to define a compound matcher" if preconditions.empty?
define_method(matcher) do |*expected, &block_arg|
Precondition.new(
RSpec::Matchers::DSL::Matcher.new(matcher, declarations, self, *expected, &block_arg),
@@ -97,18 +97,6 @@ module Spec
end
end
- RSpec::Matchers.define :take_less_than do |seconds|
- match do |actual|
- start_time = Time.now
-
- actual.call
-
- (Time.now - start_time).to_f < seconds
- end
-
- supports_block_expectations
- end
-
define_compound_matcher :read_as, [exist] do |file_contents|
diffable
@@ -128,8 +116,9 @@ module Spec
source = opts.delete(:source)
groups = Array(opts.delete(:groups)).map(&:inspect).join(", ")
opts[:raise_on_error] = false
- @errors = names.map do |full_name|
+ @errors = names.filter_map do |full_name|
name, version, platform = full_name.split(/\s+/)
+ platform ||= "ruby"
require_path = name.tr("-", "/")
version_const = name == "bundler" ? "Bundler::VERSION" : Spec::Builders.constantize(name)
source_const = "#{Spec::Builders.constantize(name)}_SOURCE"
@@ -139,6 +128,7 @@ module Spec
require '#{require_path}'
actual_version, actual_platform = #{version_const}.split(/\s+/, 2)
+ actual_platform ||= "ruby"
unless Gem::Version.new(actual_version) == Gem::Version.new('#{version}')
puts actual_version
exit 64
@@ -162,14 +152,14 @@ module Spec
end
if exitstatus == 65
actual_platform = out.split("\n").last
- next "#{name} was expected to be of platform #{platform || "ruby"} but was #{actual_platform || "ruby"}"
+ next "#{name} was expected to be of platform #{platform} but was #{actual_platform}"
end
if exitstatus == 66
actual_source = out.split("\n").last
next "Expected #{name} (#{version}) to be installed from `#{source}`, was actually from `#{actual_source}`"
end
next "Command to check for inclusion of gem #{full_name} failed"
- end.compact
+ end
@errors.empty?
end
@@ -178,7 +168,7 @@ module Spec
opts = names.last.is_a?(Hash) ? names.pop : {}
groups = Array(opts.delete(:groups)).map(&:inspect).join(", ")
opts[:raise_on_error] = false
- @errors = names.map do |name|
+ @errors = names.filter_map do |name|
name, version = name.split(/\s+/, 2)
ruby <<-R, opts
begin
@@ -204,7 +194,7 @@ module Spec
next "command to check version of #{name} installed failed" unless exitstatus == 64
next "expected #{name} to not be installed, but it was" if version.nil?
next "expected #{name} (#{version}) not to be installed, but it was"
- end.compact
+ end
@errors.empty?
end
diff --git a/spec/bundler/support/options.rb b/spec/bundler/support/options.rb
new file mode 100644
index 0000000000..551fa1acd8
--- /dev/null
+++ b/spec/bundler/support/options.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Spec
+ module Options
+ def opt_add(option, options)
+ [option.strip, options].compact.reject(&:empty?).join(" ")
+ end
+
+ def opt_remove(option, options)
+ return unless options
+
+ options.split(" ").reject {|opt| opt.strip == option.strip }.join(" ")
+ end
+ end
+end
diff --git a/spec/bundler/support/path.rb b/spec/bundler/support/path.rb
index de03b2746e..2e6486412f 100644
--- a/spec/bundler/support/path.rb
+++ b/spec/bundler/support/path.rb
@@ -1,12 +1,16 @@
# frozen_string_literal: true
-require "pathname"
+require "pathname" unless defined?(Pathname)
require "rbconfig"
+require_relative "env"
+
module Spec
module Path
+ include Spec::Env
+
def source_root
- @source_root ||= Pathname.new(ruby_core? ? "../../.." : "../..").expand_path(__dir__)
+ @source_root ||= Pathname.new(ruby_core? ? "../../.." : "../../bundler").expand_path(__dir__)
end
def root
@@ -21,12 +25,8 @@ module Spec
@relative_gemspec ||= ruby_core? ? "lib/bundler/bundler.gemspec" : "bundler.gemspec"
end
- def gemspec_dir
- @gemspec_dir ||= gemspec.parent
- end
-
def loaded_gemspec
- @loaded_gemspec ||= Gem::Specification.load(gemspec.to_s)
+ @loaded_gemspec ||= Dir.chdir(source_root) { Gem::Specification.load(gemspec.to_s) }
end
def test_gemfile
@@ -45,8 +45,16 @@ module Spec
@dev_gemfile ||= tool_dir.join("dev_gems.rb")
end
+ def dev_binstub
+ @dev_binstub ||= bindir.join("bundle")
+ end
+
def bindir
- @bindir ||= source_root.join(ruby_core? ? "libexec" : "exe")
+ @bindir ||= source_root.join(ruby_core? ? "spec/bin" : "../bin")
+ end
+
+ def exedir
+ @exedir ||= source_root.join(ruby_core? ? "libexec" : "exe")
end
def installed_bindir
@@ -58,33 +66,39 @@ module Spec
end
def gem_bin
- @gem_bin ||= ruby_core? ? ENV["GEM_COMMAND"] : "gem"
+ @gem_bin ||= ENV["GEM_COMMAND"] || "gem"
end
def path
env_path = ENV["PATH"]
- env_path = env_path.split(File::PATH_SEPARATOR).reject {|path| path == bindir.to_s }.join(File::PATH_SEPARATOR) if ruby_core?
+ env_path = env_path.split(File::PATH_SEPARATOR).reject {|path| path == exedir.to_s }.join(File::PATH_SEPARATOR) if ruby_core?
env_path
end
def spec_dir
- @spec_dir ||= source_root.join(ruby_core? ? "spec/bundler" : "spec")
- end
-
- def api_request_limit_hack_file
- spec_dir.join("support/api_request_limit_hax.rb")
+ @spec_dir ||= source_root.join(ruby_core? ? "spec/bundler" : "../spec")
end
def man_dir
@man_dir ||= lib_dir.join("bundler/man")
end
+ def hax
+ @hax ||= spec_dir.join("support/hax.rb")
+ end
+
def tracked_files
@tracked_files ||= git_ls_files(tracked_files_glob)
end
def shipped_files
- @shipped_files ||= loaded_gemspec.files
+ @shipped_files ||= if ruby_core_tarball?
+ loaded_gemspec.files.map {|f| f.gsub(%r{^exe/}, "libexec/") }
+ elsif ruby_core?
+ tracked_files
+ else
+ loaded_gemspec.files
+ end
end
def lib_tracked_files
@@ -96,7 +110,28 @@ module Spec
end
def tmp(*path)
- source_root.join("tmp", scope, *path)
+ tmp_root.join("#{test_env_version}.#{scope}").join(*path)
+ end
+
+ def tmp_root
+ if ruby_core? && (tmpdir = ENV["TMPDIR"])
+ # Use realpath to resolve any symlinks in TMPDIR (e.g., on macOS /var -> /private/var)
+ real = begin
+ File.realpath(tmpdir)
+ rescue Errno::ENOENT, Errno::EACCES
+ tmpdir
+ end
+ Pathname(real)
+ else
+ (ruby_core? ? source_root : source_root.parent).join("tmp")
+ end
+ end
+
+ # Bump this version whenever you make a breaking change to the spec setup
+ # that requires regenerating tmp/.
+
+ def test_env_version
+ 2
end
def scope
@@ -107,33 +142,29 @@ module Spec
end
def home(*path)
- tmp.join("home", *path)
+ tmp("home", *path)
end
def default_bundle_path(*path)
- if Bundler.feature_flag.default_install_uses_path?
- local_gem_path(*path)
- else
- system_gem_path(*path)
- end
+ system_gem_path(*path)
end
def default_cache_path(*path)
- if Bundler.feature_flag.global_gem_cache?
- home(".bundle/cache", *path)
- else
- default_bundle_path("cache/bundler", *path)
- end
+ default_bundle_path("cache/bundler", *path)
+ end
+
+ def compact_index_cache_path
+ home(".bundle/cache/compact_index")
end
def bundled_app(*path)
- root = tmp.join("bundled_app")
+ root = tmp("bundled_app")
FileUtils.mkdir_p(root)
root.join(*path)
end
def bundled_app2(*path)
- root = tmp.join("bundled_app2")
+ root = tmp("bundled_app2")
FileUtils.mkdir_p(root)
root.join(*path)
end
@@ -154,20 +185,20 @@ module Spec
bundled_app("Gemfile.lock")
end
- def base_system_gem_path
- scoped_gem_path(base_system_gems)
+ def scoped_base_system_gem_path
+ scoped_gem_path(base_system_gem_path)
end
- def base_system_gems
- tmp.join("gems/base")
+ def base_system_gem_path
+ tmp_root.join("gems/base")
end
- def rubocop_gems
- tmp.join("gems/rubocop")
+ def rubocop_gem_path
+ tmp_root.join("gems/rubocop")
end
- def standard_gems
- tmp.join("gems/standard")
+ def standard_gem_path
+ tmp_root.join("gems/standard")
end
def file_uri_for(path)
@@ -178,35 +209,35 @@ module Spec
end
def gem_repo1(*args)
- tmp("gems/remote1", *args)
+ gem_path("remote1", *args)
end
def gem_repo_missing(*args)
- tmp("gems/missing", *args)
+ gem_path("missing", *args)
end
def gem_repo2(*args)
- tmp("gems/remote2", *args)
+ gem_path("remote2", *args)
end
def gem_repo3(*args)
- tmp("gems/remote3", *args)
+ gem_path("remote3", *args)
end
def gem_repo4(*args)
- tmp("gems/remote4", *args)
+ gem_path("remote4", *args)
end
def security_repo(*args)
- tmp("gems/security_repo", *args)
+ gem_path("security_repo", *args)
end
def system_gem_path(*path)
- tmp("gems/system", *path)
+ gem_path("system", *path)
end
def pristine_system_gem_path
- tmp("gems/base_system")
+ tmp_root.join("gems/pristine_system")
end
def local_gem_path(*path, base: bundled_app)
@@ -217,6 +248,10 @@ module Spec
base.join(Gem.ruby_engine, RbConfig::CONFIG["ruby_version"])
end
+ def gem_path(*args)
+ tmp("gems", *args)
+ end
+
def lib_path(*args)
tmp("libs", *args)
end
@@ -229,13 +264,6 @@ module Spec
root.join("lib")
end
- # Sometimes rubygems version under test does not include
- # https://github.com/rubygems/rubygems/pull/2728 and will not always end up
- # activating the current bundler. In that case, require bundler absolutely.
- def entrypoint
- Gem.rubygems_version < Gem::Version.new("3.1.a") ? "#{lib_dir}/bundler" : "bundler"
- end
-
def global_plugin_gem(*args)
home ".bundle", "plugin", "gems", *args
end
@@ -251,35 +279,73 @@ module Spec
def replace_version_file(version, dir: source_root)
version_file = File.expand_path("lib/bundler/version.rb", dir)
contents = File.read(version_file)
- contents.sub!(/(^\s+VERSION\s*=\s*)"#{Gem::Version::VERSION_PATTERN}"/, %(\\1"#{version}"))
+ contents.sub!(/(^\s+VERSION\s*=\s*).*$/, %(\\1"#{version}"))
File.open(version_file, "w") {|f| f << contents }
end
- def ruby_core?
- # avoid to warnings
- @ruby_core ||= nil
+ def replace_required_ruby_version(version, dir:)
+ gemspec_file = File.expand_path("bundler.gemspec", dir)
+ contents = File.read(gemspec_file)
+ contents.sub!(/(^\s+s\.required_ruby_version\s*=\s*)"[^"]+"/, %(\\1"#{version}"))
+ File.open(gemspec_file, "w") {|f| f << contents }
+ end
- if @ruby_core.nil?
- @ruby_core = true & ENV["GEM_COMMAND"]
- else
- @ruby_core
- end
+ def replace_changelog(version, dir:)
+ changelog = File.expand_path("CHANGELOG.md", dir)
+ contents = File.readlines(changelog)
+ contents = [contents[0], contents[1], "## #{version} (2100-01-01)\n", *contents[3..-1]].join
+ File.open(changelog, "w") {|f| f << contents }
end
def git_root
ruby_core? ? source_root : source_root.parent
end
+ def rake_path
+ find_base_path("rake")
+ end
+
+ def rake_version
+ File.basename(rake_path).delete_prefix("rake-").delete_suffix(".gem")
+ end
+
+ def sinatra_dependency_paths
+ deps = %w[
+ mustermann
+ rack
+ rack-protection
+ rack-session
+ tilt
+ sinatra
+ base64
+ logger
+ compact_index
+ ]
+ path = if deps.all? {|dep| !Dir[scoped_base_system_gem_path.join("gems/#{dep}-*")].empty? }
+ scoped_base_system_gem_path
+ elsif ruby_core? && deps.all? {|dep| !Dir[source_root.join(".bundle/gems/#{dep}-*")].empty? }
+ source_root.join(".bundle")
+ else
+ scoped_base_system_gem_path
+ end
+
+ Dir[path.join("gems/{#{deps.join(",")}}-*/lib")].map(&:to_s)
+ end
+
private
+ def find_base_path(name)
+ Dir["#{scoped_base_system_gem_path}/**/#{name}-*.gem"].first
+ end
+
def git_ls_files(glob)
skip "Not running on a git context, since running tests from a tarball" if ruby_core_tarball?
- sys_exec("git ls-files -z -- #{glob}", :dir => source_root).split("\x0")
+ git("ls-files -z -- #{glob}", source_root).split("\x0")
end
def tracked_files_glob
- ruby_core? ? "lib/bundler lib/bundler.rb spec/bundler man/bundle*" : ""
+ ruby_core? ? "libexec/bundle* lib/bundler lib/bundler.rb spec/bundler man/bundle*" : "lib exe CHANGELOG.md LICENSE.md README.md bundler.gemspec"
end
def lib_tracked_files_glob
@@ -287,7 +353,7 @@ module Spec
end
def man_tracked_files_glob
- ruby_core? ? "man/bundle* man/gemfile*" : "lib/bundler/man/bundle*.1 lib/bundler/man/gemfile*.5"
+ "lib/bundler/man/bundle*.1.ronn lib/bundler/man/gemfile*.5.ronn"
end
def ruby_core_tarball?
@@ -295,15 +361,15 @@ module Spec
end
def rubocop_gemfile_basename
- source_root.join("tool/bundler/rubocop_gems.rb")
+ tool_dir.join("rubocop_gems.rb")
end
def standard_gemfile_basename
- source_root.join("tool/bundler/standard_gems.rb")
+ tool_dir.join("standard_gems.rb")
end
def tool_dir
- source_root.join("tool/bundler")
+ ruby_core? ? source_root.join("tool/bundler") : source_root.join("../tool/bundler")
end
def templates_dir
diff --git a/spec/bundler/support/platforms.rb b/spec/bundler/support/platforms.rb
index 3776901ce3..56a0843005 100644
--- a/spec/bundler/support/platforms.rb
+++ b/spec/bundler/support/platforms.rb
@@ -2,72 +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", "linux", nil])
- 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]
- end
-
- def all_platforms
- [rb, java, linux, windows_platforms].flatten
- end
-
- def local
- generic_local_platform
+ def not_local
+ generic_local_platform == Gem::Platform::RUBY ? "java" : Gem::Platform::RUBY
end
- def specific_local_platform
+ 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
@@ -103,12 +53,23 @@ module Spec
9999
end
- def lockfile_platforms
- lockfile_platforms_for([specific_local_platform])
+ def default_platform_list(*extra, defaults: default_locked_platforms)
+ defaults.concat(extra).map(&:to_s).uniq
+ end
+
+ def lockfile_platforms(*extra, defaults: default_locked_platforms)
+ platforms = default_platform_list(*extra, defaults: defaults)
+ platforms.sort.join("\n ")
end
- def lockfile_platforms_for(platforms)
- platforms.map(&:to_s).sort.join("\n ")
+ def default_locked_platforms
+ [local_platform, generic_default_locked_platform].compact
+ end
+
+ def generic_default_locked_platform
+ return unless Bundler::MatchPlatform.generic_local_platform_is_ruby?
+
+ Gem::Platform::RUBY
end
end
end
diff --git a/spec/bundler/support/rubygems_ext.rb b/spec/bundler/support/rubygems_ext.rb
index 4553c0606e..812dc4deaa 100644
--- a/spec/bundler/support/rubygems_ext.rb
+++ b/spec/bundler/support/rubygems_ext.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+abort "RubyGems only supports Ruby 3.2 or higher" if RUBY_VERSION < "3.2.0"
+
require_relative "path"
$LOAD_PATH.unshift(Spec::Path.source_lib_dir.to_s)
@@ -8,10 +10,6 @@ module Spec
module Rubygems
extend self
- def dev_setup
- install_gems(dev_gemfile)
- end
-
def gem_load(gem_name, bin_container)
require_relative "switch_rubygems"
@@ -24,12 +22,15 @@ module Spec
gem_load_activate_and_possibly_install(gem_name, bin_container)
end
- def gem_require(gem_name)
+ def gem_require(gem_name, entrypoint)
gem_activate(gem_name)
- require gem_name
+ require entrypoint
end
def test_setup
+ # Install test dependencies unless parallel-rspec is being used, since in that case they should be setup already
+ install_test_deps unless ENV["RSPEC_FORMATTER_OUTPUT_ID"]
+
setup_test_paths
require "fileutils"
@@ -38,42 +39,82 @@ module Spec
FileUtils.mkdir_p(Path.tmpdir)
ENV["HOME"] = Path.home.to_s
- ENV["TMPDIR"] = Path.tmpdir.to_s
+ # Remove "RUBY_CODESIGN", which is used by mkmf-generated Makefile to
+ # sign extension bundles on macOS, to avoid trying to find the specified key
+ # from the fake $HOME/Library/Keychains directory.
+ ENV.delete "RUBY_CODESIGN"
+ if Path.ruby_core?
+ if (tmpdir = ENV["TMPDIR"])
+ tmpdir_real = begin
+ File.realpath(tmpdir)
+ rescue Errno::ENOENT, Errno::EACCES
+ tmpdir
+ end
+ ENV["TMPDIR"] = tmpdir_real if tmpdir_real != tmpdir
+ end
+ else
+ ENV["TMPDIR"] = Path.tmpdir.to_s
+ end
require "rubygems/user_interaction"
Gem::DefaultUserInteraction.ui = Gem::SilentUI.new
end
- def install_parallel_test_deps
- Gem.clear_paths
-
- require "parallel"
- require "fileutils"
-
- install_test_deps
-
- (2..Parallel.processor_count).each do |n|
- source = Path.source_root.join("tmp", "1")
- destination = Path.source_root.join("tmp", n.to_s)
-
- FileUtils.rm_rf destination
- FileUtils.cp_r source, destination
- end
- end
-
def setup_test_paths
- Gem.clear_paths
-
ENV["BUNDLE_PATH"] = nil
- ENV["GEM_HOME"] = ENV["GEM_PATH"] = Path.base_system_gem_path.to_s
- ENV["PATH"] = [Path.system_gem_path.join("bin"), ENV["PATH"]].join(File::PATH_SEPARATOR)
- ENV["PATH"] = [Path.bindir, ENV["PATH"]].join(File::PATH_SEPARATOR) if Path.ruby_core?
+ ENV["PATH"] = [Path.system_gem_path("bin"), ENV["PATH"]].join(File::PATH_SEPARATOR)
+ ENV["PATH"] = [Path.exedir, ENV["PATH"]].join(File::PATH_SEPARATOR) if Path.ruby_core?
end
def install_test_deps
- install_gems(test_gemfile, Path.base_system_gems.to_s)
- install_gems(rubocop_gemfile, Path.rubocop_gems.to_s)
- install_gems(standard_gemfile, Path.standard_gems.to_s)
+ dev_bundle("install", gemfile: test_gemfile, path: Path.base_system_gem_path.to_s)
+ dev_bundle("install", gemfile: rubocop_gemfile, path: Path.rubocop_gem_path.to_s)
+ dev_bundle("install", gemfile: standard_gemfile, path: Path.standard_gem_path.to_s)
+
+ require_relative "helpers"
+ Helpers.install_dev_bundler
+
+ install_vendored_compact_index
+ end
+
+ # Vendor `rubygems/rubygems.org#lib/compact_index/` under `tmp/compact_index/`
+ # so the artifice can serve compact-index responses without a runtime gem
+ # dependency. Pinned to a reviewed commit; override with COMPACT_INDEX_REF
+ # to refresh against another ref (the existing vendor copy is discarded).
+ def install_vendored_compact_index
+ target_root = Path.tmp_root.join("compact_index")
+ require "fileutils"
+ FileUtils.mkdir_p(Path.tmp_root)
+
+ files = %w[
+ lib/compact_index.rb
+ lib/compact_index/dependency.rb
+ lib/compact_index/gem.rb
+ lib/compact_index/gem_version.rb
+ lib/compact_index/versions_file.rb
+ ]
+
+ # Serialize installs so parallel test setups don't race on the same
+ # vendor tree, and only skip the download when every file is present so
+ # an interrupted run can't leave a partial copy behind.
+ File.open(Path.tmp_root.join("compact_index.lock"), File::CREAT | File::RDWR) do |lock|
+ lock.flock(File::LOCK_EX)
+
+ FileUtils.rm_rf(target_root) if ENV["COMPACT_INDEX_REF"]
+
+ next if files.all? {|path| File.exist?(target_root.join(path)) }
+
+ require "open-uri"
+ ref = ENV["COMPACT_INDEX_REF"] || "7c68a7b39761c61a66f9299f85b889ec39afc02c"
+ files.each do |path|
+ url = "https://raw.githubusercontent.com/rubygems/rubygems.org/#{ref}/#{path}"
+ target = target_root.join(path)
+ FileUtils.mkdir_p(File.dirname(target))
+ tmp = "#{target}.tmp"
+ File.write(tmp, URI.parse(url).open(&:read))
+ File.rename(tmp, target)
+ end
+ end
end
def check_source_control_changes(success_message:, error_message:)
@@ -86,7 +127,7 @@ module Spec
puts success_message
puts
else
- system("git status --porcelain")
+ system("git diff")
puts
puts error_message
@@ -96,6 +137,36 @@ module Spec
end
end
+ def dev_bundle(*args, gemfile: dev_gemfile, path: nil)
+ old_gemfile = ENV["BUNDLE_GEMFILE"]
+ old_orig_gemfile = ENV["BUNDLER_ORIG_BUNDLE_GEMFILE"]
+ ENV["BUNDLE_GEMFILE"] = gemfile.to_s
+ ENV["BUNDLER_ORIG_BUNDLE_GEMFILE"] = nil
+
+ if path
+ old_path = ENV["BUNDLE_PATH"]
+ ENV["BUNDLE_PATH"] = path
+ else
+ old_path__system = ENV["BUNDLE_PATH__SYSTEM"]
+ ENV["BUNDLE_PATH__SYSTEM"] = "true"
+ end
+
+ require "shellwords"
+ # We don't use `Open3` here because it does not work on JRuby + Windows
+ output = `ruby #{Path.dev_binstub} #{args.shelljoin}`
+ raise output unless $?.success?
+ output
+ ensure
+ if path
+ ENV["BUNDLE_PATH"] = old_path
+ else
+ ENV["BUNDLE_PATH__SYSTEM"] = old_path__system
+ end
+
+ ENV["BUNDLER_ORIG_BUNDLE_GEMFILE"] = old_orig_gemfile
+ ENV["BUNDLE_GEMFILE"] = old_gemfile
+ end
+
private
def gem_load_and_activate(gem_name, bin_container)
@@ -118,38 +189,12 @@ module Spec
end
def gem_activate(gem_name)
+ require_relative "activate"
require "bundler"
gem_requirement = Bundler::LockfileParser.new(File.read(dev_lockfile)).specs.find {|spec| spec.name == gem_name }.version
gem gem_name, gem_requirement
end
- def install_gems(gemfile, path = nil)
- old_gemfile = ENV["BUNDLE_GEMFILE"]
- old_orig_gemfile = ENV["BUNDLER_ORIG_BUNDLE_GEMFILE"]
- ENV["BUNDLE_GEMFILE"] = gemfile.to_s
- ENV["BUNDLER_ORIG_BUNDLE_GEMFILE"] = nil
-
- if path
- old_path = ENV["BUNDLE_PATH"]
- ENV["BUNDLE_PATH"] = path
- else
- old_path__system = ENV["BUNDLE_PATH__SYSTEM"]
- ENV["BUNDLE_PATH__SYSTEM"] = "true"
- end
-
- puts `#{Gem.ruby} #{File.expand_path("support/bundle.rb", Path.spec_dir)} install --verbose`
- raise unless $?.success?
- ensure
- if path
- ENV["BUNDLE_PATH"] = old_path
- else
- ENV["BUNDLE_PATH__SYSTEM"] = old_path__system
- end
-
- ENV["BUNDLER_ORIG_BUNDLE_GEMFILE"] = old_orig_gemfile
- ENV["BUNDLE_GEMFILE"] = old_gemfile
- end
-
def test_gemfile
Path.test_gemfile
end
diff --git a/spec/bundler/support/rubygems_version_manager.rb b/spec/bundler/support/rubygems_version_manager.rb
index 5653601ae8..c174c461f0 100644
--- a/spec/bundler/support/rubygems_version_manager.rb
+++ b/spec/bundler/support/rubygems_version_manager.rb
@@ -1,12 +1,13 @@
# frozen_string_literal: true
-require "pathname"
-require_relative "helpers"
-require_relative "path"
+require_relative "options"
+require_relative "env"
+require_relative "subprocess"
class RubygemsVersionManager
- include Spec::Helpers
- include Spec::Path
+ include Spec::Options
+ include Spec::Env
+ include Spec::Subprocess
def initialize(source)
@source = source
@@ -30,11 +31,10 @@ class RubygemsVersionManager
rubygems_default_path = rubygems_path + "/defaults"
bundler_path = rubylibdir + "/bundler"
- bundler_exemptions = Gem.rubygems_version < Gem::Version.new("3.2.0") ? [bundler_path + "/errors.rb"] : []
bad_loaded_features = $LOADED_FEATURES.select do |loaded_feature|
(loaded_feature.start_with?(rubygems_path) && !loaded_feature.start_with?(rubygems_default_path)) ||
- (loaded_feature.start_with?(bundler_path) && !bundler_exemptions.any? {|bundler_exemption| loaded_feature.start_with?(bundler_exemption) })
+ loaded_feature.start_with?(bundler_path)
end
errors = if bad_loaded_features.any?
@@ -58,7 +58,7 @@ class RubygemsVersionManager
cmd = [RbConfig.ruby, $0, *ARGV].compact
- ENV["RUBYOPT"] = opt_add("-I#{local_copy_path.join("lib")}", opt_remove("--disable-gems", ENV["RUBYOPT"]))
+ ENV["RUBYOPT"] = opt_add("-I#{File.join(local_copy_path, "lib")}", opt_remove("--disable-gems", ENV["RUBYOPT"]))
exec(ENV, *cmd)
end
@@ -66,14 +66,14 @@ class RubygemsVersionManager
def switch_local_copy_if_needed
return unless local_copy_switch_needed?
- sys_exec("git checkout #{target_tag}", :dir => local_copy_path)
+ git("checkout #{target_tag}", local_copy_path)
- ENV["RGV"] = local_copy_path.to_s
+ ENV["RGV"] = local_copy_path
end
def rubygems_unrequire_needed?
require "rubygems"
- !$LOADED_FEATURES.include?(local_copy_path.join("lib/rubygems.rb").to_s)
+ !$LOADED_FEATURES.include?(File.join(local_copy_path, "lib/rubygems.rb"))
end
def local_copy_switch_needed?
@@ -85,7 +85,7 @@ class RubygemsVersionManager
end
def local_copy_tag
- sys_exec("git rev-parse --abbrev-ref HEAD", :dir => local_copy_path)
+ git("rev-parse --abbrev-ref HEAD", local_copy_path)
end
def local_copy_path
@@ -95,21 +95,25 @@ class RubygemsVersionManager
def resolve_local_copy_path
return expanded_source if source_is_path?
- rubygems_path = source_root.join("tmp/rubygems")
+ rubygems_path = File.join(source_root, "tmp/rubygems")
- unless rubygems_path.directory?
- sys_exec("git clone .. #{rubygems_path}", :dir => source_root)
+ unless File.directory?(rubygems_path)
+ git("clone .. #{rubygems_path}", source_root)
end
rubygems_path
end
def source_is_path?
- expanded_source.directory?
+ File.directory?(expanded_source)
end
def expanded_source
- @expanded_source ||= Pathname.new(@source).expand_path(source_root)
+ @expanded_source ||= File.expand_path(@source, source_root)
+ end
+
+ def source_root
+ @source_root ||= File.expand_path(ruby_core? ? "../../.." : "../..", __dir__)
end
def resolve_target_tag
diff --git a/spec/bundler/support/setup.rb b/spec/bundler/support/setup.rb
new file mode 100644
index 0000000000..4ac2e5b472
--- /dev/null
+++ b/spec/bundler/support/setup.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require_relative "switch_rubygems"
+
+require_relative "rubygems_ext"
+Spec::Rubygems.install_test_deps
+
+require_relative "path"
+$LOAD_PATH.unshift(File.expand_path("../../lib", __dir__)) if Spec::Path.ruby_core?
diff --git a/spec/bundler/support/shards.rb b/spec/bundler/support/shards.rb
new file mode 100644
index 0000000000..ce33896539
--- /dev/null
+++ b/spec/bundler/support/shards.rb
@@ -0,0 +1,200 @@
+# frozen_string_literal: true
+
+# This classifies test files into 4 shards by running `bin/rspec --profile 10000`
+# to ensure balanced execution times. When adding new test files, it is recommended to
+# re-aggregate and adjust the shards to keep them balanced.
+# For now, please add new files to shard 'shard_d'.
+
+module Spec
+ module Shards
+ EXAMPLE_MAPPINGS = {
+ shard_a: [
+ "spec/runtime/setup_spec.rb",
+ "spec/commands/install_spec.rb",
+ "spec/commands/add_spec.rb",
+ "spec/install/gems/compact_index_spec.rb",
+ "spec/commands/config_spec.rb",
+ "spec/commands/pristine_spec.rb",
+ "spec/install/gemfile/path_spec.rb",
+ "spec/update/git_spec.rb",
+ "spec/commands/open_spec.rb",
+ "spec/commands/remove_spec.rb",
+ "spec/commands/show_spec.rb",
+ "spec/plugins/source/example_spec.rb",
+ "spec/commands/console_spec.rb",
+ "spec/runtime/require_spec.rb",
+ "spec/runtime/env_helpers_spec.rb",
+ "spec/runtime/gem_tasks_spec.rb",
+ "spec/install/gemfile_spec.rb",
+ "spec/commands/fund_spec.rb",
+ "spec/commands/init_spec.rb",
+ "spec/bundler/ruby_dsl_spec.rb",
+ "spec/bundler/mirror_spec.rb",
+ "spec/bundler/source/git/git_proxy_spec.rb",
+ "spec/bundler/source_list_spec.rb",
+ "spec/bundler/plugin/installer_spec.rb",
+ "spec/bundler/errors_spec.rb",
+ "spec/bundler/friendly_errors_spec.rb",
+ "spec/resolver/platform_spec.rb",
+ "spec/bundler/fetcher/downloader_spec.rb",
+ "spec/update/force_spec.rb",
+ "spec/bundler/env_spec.rb",
+ "spec/install/gems/mirror_spec.rb",
+ "spec/install/failure_spec.rb",
+ "spec/bundler/yaml_serializer_spec.rb",
+ "spec/bundler/environment_preserver_spec.rb",
+ "spec/install/gemfile/install_if_spec.rb",
+ "spec/install/gems/gemfile_source_header_spec.rb",
+ "spec/bundler/fetcher/base_spec.rb",
+ "spec/bundler/rubygems_integration_spec.rb",
+ "spec/bundler/worker_spec.rb",
+ "spec/bundler/dependency_spec.rb",
+ "spec/bundler/ui_spec.rb",
+ "spec/bundler/plugin/source_list_spec.rb",
+ "spec/bundler/source/path_spec.rb",
+ ],
+ shard_b: [
+ "spec/install/gemfile/git_spec.rb",
+ "spec/install/gems/standalone_spec.rb",
+ "spec/commands/lock_spec.rb",
+ "spec/cache/gems_spec.rb",
+ "spec/other/major_deprecation_spec.rb",
+ "spec/install/gems/dependency_api_spec.rb",
+ "spec/install/gemfile/gemspec_spec.rb",
+ "spec/plugins/install_spec.rb",
+ "spec/commands/binstubs_spec.rb",
+ "spec/install/gems/flex_spec.rb",
+ "spec/runtime/inline_spec.rb",
+ "spec/commands/post_bundle_message_spec.rb",
+ "spec/runtime/executable_spec.rb",
+ "spec/lock/git_spec.rb",
+ "spec/plugins/hook_spec.rb",
+ "spec/install/allow_offline_install_spec.rb",
+ "spec/install/gems/post_install_spec.rb",
+ "spec/install/gemfile/ruby_spec.rb",
+ "spec/install/security_policy_spec.rb",
+ "spec/install/yanked_spec.rb",
+ "spec/update/gemfile_spec.rb",
+ "spec/runtime/load_spec.rb",
+ "spec/plugins/command_spec.rb",
+ "spec/commands/version_spec.rb",
+ "spec/install/prereleases_spec.rb",
+ "spec/bundler/uri_credentials_filter_spec.rb",
+ "spec/bundler/plugin_spec.rb",
+ "spec/install/gems/mirror_probe_spec.rb",
+ "spec/plugins/list_spec.rb",
+ "spec/bundler/compact_index_client/parser_spec.rb",
+ "spec/bundler/gem_version_promoter_spec.rb",
+ "spec/other/cli_dispatch_spec.rb",
+ "spec/bundler/source/rubygems_spec.rb",
+ "spec/cache/platform_spec.rb",
+ "spec/update/gems/fund_spec.rb",
+ "spec/bundler/stub_specification_spec.rb",
+ "spec/bundler/retry_spec.rb",
+ "spec/bundler/installer/spec_installation_spec.rb",
+ "spec/bundler/spec_set_spec.rb",
+ "spec/quality_es_spec.rb",
+ "spec/bundler/index_spec.rb",
+ "spec/other/cli_man_pages_spec.rb",
+ ],
+ shard_c: [
+ "spec/commands/newgem_spec.rb",
+ "spec/commands/exec_spec.rb",
+ "spec/commands/clean_spec.rb",
+ "spec/commands/platform_spec.rb",
+ "spec/cache/git_spec.rb",
+ "spec/install/gemfile/groups_spec.rb",
+ "spec/commands/cache_spec.rb",
+ "spec/commands/check_spec.rb",
+ "spec/commands/list_spec.rb",
+ "spec/install/path_spec.rb",
+ "spec/bundler/cli_spec.rb",
+ "spec/install/bundler_spec.rb",
+ "spec/install/git_spec.rb",
+ "spec/commands/doctor_spec.rb",
+ "spec/bundler/dsl_spec.rb",
+ "spec/install/gems/fund_spec.rb",
+ "spec/install/gems/env_spec.rb",
+ "spec/bundler/ruby_version_spec.rb",
+ "spec/bundler/definition_spec.rb",
+ "spec/install/gemfile/eval_gemfile_spec.rb",
+ "spec/plugins/source_spec.rb",
+ "spec/install/gems/dependency_api_fallback_spec.rb",
+ "spec/plugins/uninstall_spec.rb",
+ "spec/bundler/plugin/index_spec.rb",
+ "spec/bundler/bundler_spec.rb",
+ "spec/bundler/fetcher_spec.rb",
+ "spec/bundler/source/rubygems/remote_spec.rb",
+ "spec/bundler/lockfile_parser_spec.rb",
+ "spec/cache/cache_path_spec.rb",
+ "spec/bundler/source/git_spec.rb",
+ "spec/bundler/source_spec.rb",
+ "spec/commands/ssl_spec.rb",
+ "spec/bundler/fetcher/compact_index_spec.rb",
+ "spec/bundler/plugin/api_spec.rb",
+ "spec/bundler/endpoint_specification_spec.rb",
+ "spec/bundler/fetcher/index_spec.rb",
+ "spec/bundler/settings/validator_spec.rb",
+ "spec/bundler/build_metadata_spec.rb",
+ "spec/bundler/current_ruby_spec.rb",
+ "spec/bundler/installer/gem_installer_spec.rb",
+ "spec/bundler/installer/parallel_installer_spec.rb",
+ "spec/bundler/cli_common_spec.rb",
+ "spec/bundler/ci_detector_spec.rb",
+ ],
+ shard_d: [
+ "spec/bundler/rubygems_ext_spec.rb",
+ "spec/bundler/resolver/cooldown_spec.rb",
+ "spec/install/cooldown_spec.rb",
+ "spec/commands/outdated_spec.rb",
+ "spec/commands/update_spec.rb",
+ "spec/lock/lockfile_spec.rb",
+ "spec/install/deploy_spec.rb",
+ "spec/install/gemfile/sources_spec.rb",
+ "spec/runtime/self_management_spec.rb",
+ "spec/install/gemfile/specific_platform_spec.rb",
+ "spec/commands/info_spec.rb",
+ "spec/install/gems/resolving_spec.rb",
+ "spec/install/gemfile/platform_spec.rb",
+ "spec/bundler/gem_helper_spec.rb",
+ "spec/install/global_cache_spec.rb",
+ "spec/runtime/platform_spec.rb",
+ "spec/update/gems/post_install_spec.rb",
+ "spec/install/gems/native_extensions_spec.rb",
+ "spec/install/force_spec.rb",
+ "spec/cache/path_spec.rb",
+ "spec/install/gemspecs_spec.rb",
+ "spec/commands/help_spec.rb",
+ "spec/bundler/shared_helpers_spec.rb",
+ "spec/bundler/settings_spec.rb",
+ "spec/resolver/basic_spec.rb",
+ "spec/install/gemfile/force_ruby_platform_spec.rb",
+ "spec/commands/licenses_spec.rb",
+ "spec/install/gemfile/lockfile_spec.rb",
+ "spec/bundler/fetcher/dependency_spec.rb",
+ "spec/quality_spec.rb",
+ "spec/bundler/remote_specification_spec.rb",
+ "spec/install/process_lock_spec.rb",
+ "spec/install/binstubs_spec.rb",
+ "spec/bundler/compact_index_client/updater_spec.rb",
+ "spec/bundler/ui/shell_spec.rb",
+ "spec/other/ext_spec.rb",
+ "spec/commands/issue_spec.rb",
+ "spec/update/path_spec.rb",
+ "spec/bundler/plugin/api/source_spec.rb",
+ "spec/install/gems/win32_spec.rb",
+ "spec/bundler/plugin/dsl_spec.rb",
+ "spec/runtime/requiring_spec.rb",
+ "spec/bundler/plugin/events_spec.rb",
+ "spec/bundler/resolver/candidate_spec.rb",
+ "spec/bundler/digest_spec.rb",
+ "spec/bundler/fetcher/gem_remote_fetcher_spec.rb",
+ "spec/bundler/uri_normalizer_spec.rb",
+ "spec/install/gems/no_build_extension_spec.rb",
+ "spec/install/gems/no_install_plugin_spec.rb",
+ "spec/bundler/override_spec.rb",
+ "spec/install/gemfile/override_spec.rb",
+ ],
+ }.freeze
+ end
+end
diff --git a/spec/bundler/support/silent_logger.rb b/spec/bundler/support/silent_logger.rb
deleted file mode 100644
index 8665beb2c9..0000000000
--- a/spec/bundler/support/silent_logger.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-require "logger"
-module Spec
- class SilentLogger
- (::Logger.instance_methods - Object.instance_methods).each do |logger_instance_method|
- define_method(logger_instance_method) {|*args, &blk| }
- end
- end
-end
diff --git a/spec/bundler/support/subprocess.rb b/spec/bundler/support/subprocess.rb
new file mode 100644
index 0000000000..91db80da48
--- /dev/null
+++ b/spec/bundler/support/subprocess.rb
@@ -0,0 +1,115 @@
+# frozen_string_literal: true
+
+require_relative "command_execution"
+
+module Spec
+ module Subprocess
+ class TimeoutExceeded < StandardError; end
+
+ def command_executions
+ @command_executions ||= []
+ end
+
+ def last_command
+ command_executions.last || raise("There is no last command")
+ end
+
+ def out
+ last_command.stdout
+ end
+
+ def err
+ last_command.stderr
+ end
+
+ def stdboth
+ last_command.stdboth
+ end
+
+ def exitstatus
+ last_command.exitstatus
+ end
+
+ def git(cmd, path = Dir.pwd, options = {})
+ sh("git #{cmd}", options.merge(dir: path))
+ end
+
+ def sh(cmd, options = {})
+ dir = options[:dir]
+ env = options[:env] || {}
+
+ command_execution = CommandExecution.new(cmd.to_s, timeout: options[:timeout] || 60)
+
+ open3_opts = {}
+ open3_opts[:chdir] = dir if dir
+
+ require "open3"
+ require "shellwords"
+ Open3.popen3(env, *cmd.shellsplit, **open3_opts) do |stdin, stdout, stderr, wait_thr|
+ yield stdin, stdout, wait_thr if block_given?
+ stdin.close
+
+ stdout_handler = ->(data) { command_execution.original_stdout << data }
+ stderr_handler = ->(data) { command_execution.original_stderr << data }
+
+ stdout_thread = read_stream(stdout, stdout_handler, timeout: command_execution.timeout)
+ stderr_thread = read_stream(stderr, stderr_handler, timeout: command_execution.timeout)
+
+ stdout_thread.join
+ stderr_thread.join
+
+ status = wait_thr.value
+ command_execution.exitstatus = if status.exited?
+ status.exitstatus
+ elsif status.signaled?
+ exit_status_for_signal(status.termsig)
+ end
+ rescue TimeoutExceeded
+ command_execution.failure_reason = :timeout
+ command_execution.exitstatus = exit_status_for_signal(Signal.list["INT"])
+ end
+
+ unless options[:raise_on_error] == false || command_execution.success?
+ command_execution.raise_error!
+ end
+
+ command_executions << command_execution
+
+ command_execution.stdout
+ end
+
+ # Mostly copied from https://github.com/piotrmurach/tty-command/blob/49c37a895ccea107e8b78d20e4cb29de6a1a53c8/lib/tty/command/process_runner.rb#L165-L193
+ def read_stream(stream, handler, timeout:)
+ Thread.new do
+ Thread.current.report_on_exception = false
+ cmd_start = Time.now
+ readers = [stream]
+
+ while readers.any?
+ ready = IO.select(readers, nil, readers, timeout)
+ raise TimeoutExceeded if ready.nil?
+
+ ready[0].each do |reader|
+ chunk = reader.readpartial(16 * 1024)
+ handler.call(chunk)
+
+ # control total time spent reading
+ runtime = Time.now - cmd_start
+ time_left = timeout - runtime
+ raise TimeoutExceeded if time_left < 0.0
+ rescue Errno::EAGAIN, Errno::EINTR
+ rescue EOFError, Errno::EPIPE, Errno::EIO
+ readers.delete(reader)
+ reader.close
+ end
+ end
+ end
+ end
+
+ def all_commands_output
+ return "" if command_executions.empty?
+
+ "\n\nCommands:\n#{command_executions.map(&:to_s_verbose).join("\n\n")}"
+ end
+ end
+end
diff --git a/spec/bundler/support/switch_rubygems.rb b/spec/bundler/support/switch_rubygems.rb
index a138d22333..640b9f83b7 100644
--- a/spec/bundler/support/switch_rubygems.rb
+++ b/spec/bundler/support/switch_rubygems.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
require_relative "rubygems_version_manager"
+ENV["RGV"] ||= "."
RubygemsVersionManager.new(ENV["RGV"]).switch
diff --git a/spec/bundler/support/the_bundle.rb b/spec/bundler/support/the_bundle.rb
index f252a4515b..452abd7d41 100644
--- a/spec/bundler/support/the_bundle.rb
+++ b/spec/bundler/support/the_bundle.rb
@@ -8,10 +8,8 @@ module Spec
attr_accessor :bundle_dir
- def initialize(opts = {})
- opts = opts.dup
- @bundle_dir = Pathname.new(opts.delete(:bundle_dir) { bundled_app })
- raise "Too many options! #{opts}" unless opts.empty?
+ def initialize
+ @bundle_dir = Pathname.new(bundled_app)
end
def to_s
@@ -28,8 +26,16 @@ module Spec
end
def locked_gems
- raise "Cannot read lockfile if it doesn't exist" unless locked?
+ raise ArgumentError, "Cannot read lockfile if it doesn't exist" unless locked?
Bundler::LockfileParser.new(lockfile.read)
end
+
+ def locked_specs
+ locked_gems.specs.map(&:full_name)
+ end
+
+ def locked_platforms
+ locked_gems.platforms.map(&:to_s)
+ end
end
end
diff --git a/spec/bundler/support/vendored_net_http.rb b/spec/bundler/support/vendored_net_http.rb
new file mode 100644
index 0000000000..8ff2ccd1fe
--- /dev/null
+++ b/spec/bundler/support/vendored_net_http.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+# This defined? guard can be removed once RubyGems 3.4 support is dropped.
+#
+# Bundler specs load this code from `spec/support/vendored_net_http.rb` to avoid
+# activating the Bundler gem too early. Without this guard, we get redefinition
+# warnings once Bundler is actually activated and
+# `lib/bundler/vendored_net_http.rb` is required. This is not an issue in
+# RubyGems versions including `rubygems/vendored_net_http` since `require` takes
+# care of avoiding the double load.
+#
+unless defined?(Gem::Net)
+ begin
+ require "rubygems/vendored_net_http"
+ rescue LoadError
+ begin
+ require "rubygems/net/http"
+ rescue LoadError
+ require "net/http"
+ Gem::Net = Net
+ end
+ end
+end