Merge RubyGems/Bundler master

Pick from 084f7d1f21
This commit is contained in:
Hiroshi SHIBATA 2022-12-15 18:00:04 +09:00
parent 2581de112c
commit 49b0f3b024
Notes: git 2022-12-15 10:07:00 +00:00
59 changed files with 667 additions and 496 deletions

View File

@ -61,9 +61,6 @@ module Bundler
req.basic_auth(user, password)
end
connection.request(uri, req)
rescue NoMethodError => e
raise unless ["undefined method", "use_ssl="].all? {|snippet| e.message.include? snippet }
raise LoadError.new("cannot load such file -- openssl")
rescue OpenSSL::SSL::SSLError
raise CertificateFailureError.new(uri)
rescue *HTTP_ERRORS => e

View File

@ -36,9 +36,6 @@ module Bundler
end
when Thor::Error
Bundler.ui.error error.message
when LoadError
raise error unless /cannot load such file -- openssl|openssl.so|libcrypto.so/.match?(error.message)
Bundler.ui.error "\nCould not load OpenSSL. #{error.class}: #{error}\n#{error.backtrace.join("\n ")}"
when Interrupt
Bundler.ui.error "\nQuitting..."
Bundler.ui.trace error

View File

@ -148,7 +148,7 @@ module Bundler
"#{current_branch} but Gemfile specifies #{branch}"
end
changed = cached_revision && cached_revision != git_proxy.revision
changed = cached_revision && cached_revision != revision
if !Bundler.settings[:disable_local_revision_check] && changed && !@unlocked && !git_proxy.contains?(cached_revision)
raise GitError, "The Gemfile lock is pointing to revision #{shortref_for_display(cached_revision)} " \

View File

@ -61,7 +61,7 @@ module Bundler
end
def revision
@revision ||= find_local_revision
@revision ||= allowed_with_path { find_local_revision }
end
def current_branch
@ -90,16 +90,11 @@ module Bundler
Bundler.ui.info "Fetching #{credential_filtered_uri}"
unless path.exist?
SharedHelpers.filesystem_access(path.dirname) do |p|
FileUtils.mkdir_p(p)
end
git_retry "clone", "--bare", "--no-hardlinks", "--quiet", *extra_clone_args, "--", configured_uri, path.to_s
return unless extra_ref
end
extra_fetch_needed = clone_needs_extra_fetch?
unshallow_needed = clone_needs_unshallow?
return unless extra_fetch_needed || unshallow_needed
fetch_args = extra_fetch_args
fetch_args.unshift("--unshallow") if path.join("shallow").exist? && full_clone?
fetch_args = unshallow_needed ? ["--unshallow"] : depth_args
git_retry(*["fetch", "--force", "--quiet", "--no-tags", *fetch_args, "--", configured_uri, refspec].compact, :dir => path)
end
@ -123,7 +118,7 @@ module Bundler
end
end
git(*["fetch", "--force", "--quiet", *extra_fetch_args, path.to_s, revision_refspec].compact, :dir => destination)
git "fetch", "--force", "--quiet", *extra_fetch_args, :dir => destination
git "reset", "--hard", @revision, :dir => destination
@ -137,6 +132,24 @@ module Bundler
private
def clone_needs_extra_fetch?
return true if path.exist?
SharedHelpers.filesystem_access(path.dirname) do |p|
FileUtils.mkdir_p(p)
end
git_retry "clone", "--bare", "--no-hardlinks", "--quiet", *extra_clone_args, "--", configured_uri, path.to_s
extra_ref
end
def clone_needs_unshallow?
return false unless path.join("shallow").exist?
return true if full_clone?
@revision && @revision != head_revision
end
def extra_ref
return false if not_pinned?
return true unless full_clone?
@ -147,7 +160,7 @@ module Bundler
def depth
return @depth if defined?(@depth)
@depth = if legacy_locked_revision? || !supports_fetching_unreachable_refs?
@depth = if !supports_fetching_unreachable_refs?
nil
elsif not_pinned?
1
@ -241,11 +254,24 @@ module Bundler
end
def find_local_revision
allowed_with_path do
git("rev-parse", "--verify", branch || tag || ref || "HEAD", :dir => path).strip
end
options_ref = branch || tag || ref
return head_revision if options_ref.nil?
find_revision_for(options_ref)
end
def head_revision
verify("HEAD")
end
def find_revision_for(reference)
verify(reference)
rescue GitCommandError => e
raise MissingGitRevisionError.new(e.command, path, branch || tag || ref, credential_filtered_uri)
raise MissingGitRevisionError.new(e.command, path, reference, credential_filtered_uri)
end
def verify(reference)
git("rev-parse", "--verify", reference, :dir => path).strip
end
# Adds credentials to the URI
@ -325,16 +351,16 @@ module Bundler
args
end
def extra_fetch_args
def depth_args
return [] if full_clone?
["--depth", depth.to_s]
end
def revision_refspec
return if legacy_locked_revision?
revision
def extra_fetch_args
extra_args = [path.to_s, *depth_args]
extra_args.push(revision) unless legacy_locked_revision?
extra_args
end
def full_clone?

View File

@ -178,26 +178,6 @@ RSpec.describe Bundler::Fetcher::Downloader do
end
end
context "when the request response causes a NoMethodError" do
before { allow(connection).to receive(:request).with(uri, net_http_get) { raise NoMethodError.new(message) } }
context "and the error message is about use_ssl=" do
let(:message) { "undefined method 'use_ssl='" }
it "should raise a LoadError about openssl" do
expect { subject.request(uri, options) }.to raise_error(LoadError, "cannot load such file -- openssl")
end
end
context "and the error message is not about use_ssl=" do
let(:message) { "undefined method 'undefined_method_call'" }
it "should raise the original NoMethodError" do
expect { subject.request(uri, options) }.to raise_error(NoMethodError, /undefined method 'undefined_method_call'/)
end
end
end
context "when the request response causes a OpenSSL::SSL::SSLError" do
before { allow(connection).to receive(:request).with(uri, net_http_get) { raise OpenSSL::SSL::SSLError.new } }

View File

@ -110,19 +110,6 @@ RSpec.describe Bundler, "friendly errors" do
it_behaves_like "Bundler.ui receive error", Bundler::Thor::Error.new
end
context "LoadError" do
let(:error) { LoadError.new("cannot load such file -- openssl") }
before do
allow(error).to receive(:backtrace).and_return(["backtrace"])
end
it "Bundler.ui receive error" do
expect(Bundler.ui).to receive(:error).with("\nCould not load OpenSSL. LoadError: cannot load such file -- openssl\nbacktrace")
Bundler::FriendlyErrors.log_error(error)
end
end
context "Interrupt" do
it "Bundler.ui receive error" do
expect(Bundler.ui).to receive(:error).with("\nQuitting...")

View File

@ -34,6 +34,13 @@ RSpec.describe "bundle lock with git gems" do
expect(out).to eq("WIN")
end
it "properly clones a git source locked to an out of date ref" do
update_git "foo"
bundle :install, :env => { "BUNDLE_PATH" => "foo" }
expect(err).to be_empty
end
it "provides correct #full_gem_path" do
run <<-RUBY
puts Bundler.rubygems.find_name('foo').first.full_gem_path

View File

@ -1,120 +1,6 @@
# frozen_string_literal: true
require_relative "endpoint"
$LOAD_PATH.unshift Dir[Spec::Path.base_system_gem_path.join("gems/compact_index*/lib")].first.to_s
require "compact_index"
class CompactIndexAPI < Endpoint
helpers do
include Spec::Path
def load_spec(name, version, platform, gem_repo)
full_name = "#{name}-#{version}"
full_name += "-#{platform}" if platform != "ruby"
Marshal.load(Bundler.rubygems.inflate(File.binread(gem_repo.join("quick/Marshal.4.8/#{full_name}.gemspec.rz"))))
end
def etag_response
response_body = yield
checksum = Digest(:MD5).hexdigest(response_body)
return if not_modified?(checksum)
headers "ETag" => quote(checksum)
headers "Surrogate-Control" => "max-age=2592000, stale-while-revalidate=60"
content_type "text/plain"
requested_range_for(response_body)
rescue StandardError => e
puts e
puts e.backtrace
raise
end
def not_modified?(checksum)
etags = parse_etags(request.env["HTTP_IF_NONE_MATCH"])
return unless etags.include?(checksum)
headers "ETag" => quote(checksum)
status 304
body ""
end
def requested_range_for(response_body)
ranges = Rack::Utils.byte_ranges(env, response_body.bytesize)
if ranges
status 206
body ranges.map! {|range| slice_body(response_body, range) }.join
else
status 200
body response_body
end
end
def quote(string)
%("#{string}")
end
def parse_etags(value)
value ? value.split(/, ?/).select {|s| s.sub!(/"(.*)"/, '\1') } : []
end
def slice_body(body, range)
body.byteslice(range)
end
def gems(gem_repo = default_gem_repo)
@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|
load_spec(name, version, platform, gem_repo)
end
end.flatten
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|
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)
end
CompactIndex::Gem.new(name, gem_versions)
end
end
end
end
get "/names" do
etag_response do
CompactIndex.names(gems.map(&:name))
end
end
get "/versions" do
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
get "/info/:name" do
etag_response do
gem = gems.find {|g| g.name == params[:name] }
CompactIndex.info(gem ? gem.versions : [])
end
end
end
require_relative "helpers/compact_index"
require_relative "helpers/artifice"
Artifice.activate_with(CompactIndexAPI)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "compact_index"
Artifice.deactivate
require_relative "helpers/compact_index"
class CompactIndexApiMissing < CompactIndexAPI
get "/fetch/actual/gem/:id" do
@ -10,4 +8,6 @@ class CompactIndexApiMissing < CompactIndexAPI
end
end
require_relative "helpers/artifice"
Artifice.activate_with(CompactIndexApiMissing)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "compact_index"
Artifice.deactivate
require_relative "helpers/compact_index"
class CompactIndexBasicAuthentication < CompactIndexAPI
before do
@ -12,4 +10,6 @@ class CompactIndexBasicAuthentication < CompactIndexAPI
end
end
require_relative "helpers/artifice"
Artifice.activate_with(CompactIndexBasicAuthentication)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "compact_index"
Artifice.deactivate
require_relative "helpers/compact_index"
class CompactIndexChecksumMismatch < CompactIndexAPI
get "/versions" do
@ -13,4 +11,6 @@ class CompactIndexChecksumMismatch < CompactIndexAPI
end
end
require_relative "helpers/artifice"
Artifice.activate_with(CompactIndexChecksumMismatch)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "compact_index"
Artifice.deactivate
require_relative "helpers/compact_index"
class CompactIndexConcurrentDownload < CompactIndexAPI
get "/versions" do
@ -29,4 +27,6 @@ class CompactIndexConcurrentDownload < CompactIndexAPI
end
end
require_relative "helpers/artifice"
Artifice.activate_with(CompactIndexConcurrentDownload)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "compact_index"
Artifice.deactivate
require_relative "helpers/compact_index"
class CompactIndexCredsDiffHost < CompactIndexAPI
helpers do
@ -36,4 +34,6 @@ class CompactIndexCredsDiffHost < CompactIndexAPI
end
end
require_relative "helpers/artifice"
Artifice.activate_with(CompactIndexCredsDiffHost)

View File

@ -1,37 +1,6 @@
# frozen_string_literal: true
require_relative "compact_index"
Artifice.deactivate
class CompactIndexExtra < CompactIndexAPI
get "/extra/versions" do
halt 404
end
get "/extra/api/v1/dependencies" do
halt 404
end
get "/extra/specs.4.8.gz" do
File.binread("#{gem_repo2}/specs.4.8.gz")
end
get "/extra/prerelease_specs.4.8.gz" do
File.binread("#{gem_repo2}/prerelease_specs.4.8.gz")
end
get "/extra/quick/Marshal.4.8/:id" do
redirect "/extra/fetch/actual/gem/#{params[:id]}"
end
get "/extra/fetch/actual/gem/:id" do
File.binread("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}")
end
get "/extra/gems/:id" do
File.binread("#{gem_repo2}/gems/#{params[:id]}")
end
end
require_relative "helpers/compact_index_extra"
require_relative "helpers/artifice"
Artifice.activate_with(CompactIndexExtra)

View File

@ -1,52 +1,6 @@
# frozen_string_literal: true
require_relative "compact_index"
Artifice.deactivate
class CompactIndexExtraApi < CompactIndexAPI
get "/extra/names" do
etag_response do
CompactIndex.names(gems(gem_repo4).map(&:name))
end
end
get "/extra/versions" do
etag_response do
file = tmp("versions.list")
FileUtils.rm_f(file)
file = CompactIndex::VersionsFile.new(file.to_s)
file.create(gems(gem_repo4))
file.contents
end
end
get "/extra/info/:name" do
etag_response do
gem = gems(gem_repo4).find {|g| g.name == params[:name] }
CompactIndex.info(gem ? gem.versions : [])
end
end
get "/extra/specs.4.8.gz" do
File.binread("#{gem_repo4}/specs.4.8.gz")
end
get "/extra/prerelease_specs.4.8.gz" do
File.binread("#{gem_repo4}/prerelease_specs.4.8.gz")
end
get "/extra/quick/Marshal.4.8/:id" do
redirect "/extra/fetch/actual/gem/#{params[:id]}"
end
get "/extra/fetch/actual/gem/:id" do
File.binread("#{gem_repo4}/quick/Marshal.4.8/#{params[:id]}")
end
get "/extra/gems/:id" do
File.binread("#{gem_repo4}/gems/#{params[:id]}")
end
end
require_relative "helpers/compact_index_extra_api"
require_relative "helpers/artifice"
Artifice.activate_with(CompactIndexExtraApi)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "compact_index_extra_api"
Artifice.deactivate
require_relative "helpers/compact_index_extra_api"
class CompactIndexExtraAPIMissing < CompactIndexExtraApi
get "/extra/fetch/actual/gem/:id" do
@ -14,4 +12,6 @@ class CompactIndexExtraAPIMissing < CompactIndexExtraApi
end
end
require_relative "helpers/artifice"
Artifice.activate_with(CompactIndexExtraAPIMissing)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "compact_index_extra"
Artifice.deactivate
require_relative "helpers/compact_index_extra"
class CompactIndexExtraMissing < CompactIndexExtra
get "/extra/fetch/actual/gem/:id" do
@ -14,4 +12,6 @@ class CompactIndexExtraMissing < CompactIndexExtra
end
end
require_relative "helpers/artifice"
Artifice.activate_with(CompactIndexExtraMissing)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "compact_index"
Artifice.deactivate
require_relative "helpers/compact_index"
class CompactIndexForbidden < CompactIndexAPI
get "/versions" do
@ -10,4 +8,6 @@ class CompactIndexForbidden < CompactIndexAPI
end
end
require_relative "helpers/artifice"
Artifice.activate_with(CompactIndexForbidden)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "compact_index"
Artifice.deactivate
require_relative "helpers/compact_index"
class CompactIndexHostRedirect < CompactIndexAPI
get "/fetch/actual/gem/:id", :host_name => "localgemserver.test" do
@ -18,4 +16,6 @@ class CompactIndexHostRedirect < CompactIndexAPI
end
end
require_relative "helpers/artifice"
Artifice.activate_with(CompactIndexHostRedirect)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "compact_index"
Artifice.deactivate
require_relative "helpers/compact_index"
class CompactIndexNoGem < CompactIndexAPI
get "/gems/:id" do
@ -10,4 +8,6 @@ class CompactIndexNoGem < CompactIndexAPI
end
end
require_relative "helpers/artifice"
Artifice.activate_with(CompactIndexNoGem)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "compact_index"
Artifice.deactivate
require_relative "helpers/compact_index"
class CompactIndexPartialUpdate < CompactIndexAPI
# Stub the server to never return 304s. This simulates the behaviour of
@ -35,4 +33,6 @@ class CompactIndexPartialUpdate < CompactIndexAPI
end
end
require_relative "helpers/artifice"
Artifice.activate_with(CompactIndexPartialUpdate)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "compact_index"
Artifice.deactivate
require_relative "helpers/compact_index"
class CompactIndexPartialUpdateNoEtagNotIncremental < CompactIndexAPI
def partial_update_no_etag
@ -37,4 +35,6 @@ class CompactIndexPartialUpdateNoEtagNotIncremental < CompactIndexAPI
end
end
require_relative "helpers/artifice"
Artifice.activate_with(CompactIndexPartialUpdateNoEtagNotIncremental)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "compact_index"
Artifice.deactivate
require_relative "helpers/compact_index"
class CompactIndexPrecompiledBefore < CompactIndexAPI
get "/info/:name" do
@ -22,4 +20,6 @@ class CompactIndexPrecompiledBefore < CompactIndexAPI
end
end
require_relative "helpers/artifice"
Artifice.activate_with(CompactIndexPrecompiledBefore)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "compact_index"
Artifice.deactivate
require_relative "helpers/compact_index"
class CompactIndexRangeNotSatisfiable < CompactIndexAPI
get "/versions" do
@ -31,4 +29,6 @@ class CompactIndexRangeNotSatisfiable < CompactIndexAPI
end
end
require_relative "helpers/artifice"
Artifice.activate_with(CompactIndexRangeNotSatisfiable)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "compact_index"
Artifice.deactivate
require_relative "helpers/compact_index"
class CompactIndexRateLimited < CompactIndexAPI
class RequestCounter
@ -45,4 +43,6 @@ class CompactIndexRateLimited < CompactIndexAPI
end
end
require_relative "helpers/artifice"
Artifice.activate_with(CompactIndexRateLimited)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "compact_index"
Artifice.deactivate
require_relative "helpers/compact_index"
class CompactIndexRedirect < CompactIndexAPI
get "/fetch/actual/gem/:id" do
@ -18,4 +16,6 @@ class CompactIndexRedirect < CompactIndexAPI
end
end
require_relative "helpers/artifice"
Artifice.activate_with(CompactIndexRedirect)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "compact_index"
Artifice.deactivate
require_relative "helpers/compact_index"
class CompactIndexStrictBasicAuthentication < CompactIndexAPI
before do
@ -17,4 +15,6 @@ class CompactIndexStrictBasicAuthentication < CompactIndexAPI
end
end
require_relative "helpers/artifice"
Artifice.activate_with(CompactIndexStrictBasicAuthentication)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "compact_index"
Artifice.deactivate
require_relative "helpers/compact_index"
class CompactIndexWrongDependencies < CompactIndexAPI
get "/info/:name" do
@ -14,4 +12,6 @@ class CompactIndexWrongDependencies < CompactIndexAPI
end
end
require_relative "helpers/artifice"
Artifice.activate_with(CompactIndexWrongDependencies)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "compact_index"
Artifice.deactivate
require_relative "helpers/compact_index"
class CompactIndexWrongGemChecksum < CompactIndexAPI
get "/info/:name" do
@ -17,4 +15,6 @@ class CompactIndexWrongGemChecksum < CompactIndexAPI
end
end
require_relative "helpers/artifice"
Artifice.activate_with(CompactIndexWrongGemChecksum)

View File

@ -1,115 +1,6 @@
# frozen_string_literal: true
require_relative "../path"
$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gem_path.join("gems/{artifice,mustermann,rack,tilt,sinatra,ruby2_keywords}-*/lib")].map(&:to_s))
require "artifice"
require "sinatra/base"
ALL_REQUESTS = [] # rubocop:disable Style/MutableConstant
ALL_REQUESTS_MUTEX = Thread::Mutex.new
at_exit do
if expected = ENV["BUNDLER_SPEC_ALL_REQUESTS"]
expected = expected.split("\n").sort
actual = ALL_REQUESTS.sort
unless expected == actual
raise "Unexpected requests!\nExpected:\n\t#{expected.join("\n\t")}\n\nActual:\n\t#{actual.join("\n\t")}"
end
end
end
class Endpoint < Sinatra::Base
def self.all_requests
@all_requests ||= []
end
set :raise_errors, true
set :show_exceptions, false
def call!(*)
super.tap do
ALL_REQUESTS_MUTEX.synchronize do
ALL_REQUESTS << @request.url
end
end
end
helpers do
include Spec::Path
def default_gem_repo
if ENV["BUNDLER_SPEC_GEM_REPO"]
Pathname.new(ENV["BUNDLER_SPEC_GEM_REPO"])
else
case request.host
when "gem.repo1"
Spec::Path.gem_repo1
when "gem.repo2"
Spec::Path.gem_repo2
when "gem.repo3"
Spec::Path.gem_repo3
when "gem.repo4"
Spec::Path.gem_repo4
else
Spec::Path.gem_repo1
end
end
end
def dependencies_for(gem_names, gem_repo = default_gem_repo)
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)
end.inject(:+)
all_specs.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|
[dep.name, dep.requirement.requirements.map {|a| a.join(" ") }.join(", ")]
end,
}
end.compact
end
def load_spec(name, version, platform, gem_repo)
full_name = "#{name}-#{version}"
full_name += "-#{platform}" if platform != "ruby"
Marshal.load(Bundler.rubygems.inflate(File.binread(gem_repo.join("quick/Marshal.4.8/#{full_name}.gemspec.rz"))))
end
end
get "/quick/Marshal.4.8/:id" do
redirect "/fetch/actual/gem/#{params[:id]}"
end
get "/fetch/actual/gem/:id" do
File.binread("#{default_gem_repo}/quick/Marshal.4.8/#{params[:id]}")
end
get "/gems/:id" do
File.binread("#{default_gem_repo}/gems/#{params[:id]}")
end
get "/api/v1/dependencies" do
Marshal.dump(dependencies_for(params[:gems]))
end
get "/specs.4.8.gz" do
File.binread("#{default_gem_repo}/specs.4.8.gz")
end
get "/prerelease_specs.4.8.gz" do
File.binread("#{default_gem_repo}/prerelease_specs.4.8.gz")
end
end
require_relative "helpers/endpoint"
require_relative "helpers/artifice"
Artifice.activate_with(Endpoint)

View File

@ -2,17 +2,16 @@
require_relative "../path"
$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gem_path.join("gems/{artifice,mustermann,rack,tilt,sinatra,ruby2_keywords}-*/lib")].map(&:to_s))
$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gem_path.join("gems/{mustermann,rack,tilt,sinatra,ruby2_keywords}-*/lib")].map(&:to_s))
require "artifice"
require "sinatra/base"
Artifice.deactivate
class Endpoint500 < Sinatra::Base
before do
halt 500
end
end
require_relative "helpers/artifice"
Artifice.activate_with(Endpoint500)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "endpoint"
Artifice.deactivate
require_relative "helpers/endpoint"
class EndpointApiForbidden < Endpoint
get "/api/v1/dependencies" do
@ -10,4 +8,6 @@ class EndpointApiForbidden < Endpoint
end
end
require_relative "helpers/artifice"
Artifice.activate_with(EndpointApiForbidden)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "endpoint"
Artifice.deactivate
require_relative "helpers/endpoint"
class EndpointBasicAuthentication < Endpoint
before do
@ -12,4 +10,6 @@ class EndpointBasicAuthentication < Endpoint
end
end
require_relative "helpers/artifice"
Artifice.activate_with(EndpointBasicAuthentication)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "endpoint"
Artifice.deactivate
require_relative "helpers/endpoint"
class EndpointCredsDiffHost < Endpoint
helpers do
@ -36,4 +34,6 @@ class EndpointCredsDiffHost < Endpoint
end
end
require_relative "helpers/artifice"
Artifice.activate_with(EndpointCredsDiffHost)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "endpoint"
Artifice.deactivate
require_relative "helpers/endpoint"
class EndpointExtra < Endpoint
get "/extra/api/v1/dependencies" do
@ -30,4 +28,6 @@ class EndpointExtra < Endpoint
end
end
require_relative "helpers/artifice"
Artifice.activate_with(EndpointExtra)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "endpoint"
Artifice.deactivate
require_relative "helpers/endpoint"
class EndpointExtraApi < Endpoint
get "/extra/api/v1/dependencies" do
@ -31,4 +29,6 @@ class EndpointExtraApi < Endpoint
end
end
require_relative "helpers/artifice"
Artifice.activate_with(EndpointExtraApi)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "endpoint_extra"
Artifice.deactivate
require_relative "helpers/endpoint_extra"
class EndpointExtraMissing < EndpointExtra
get "/extra/fetch/actual/gem/:id" do
@ -14,4 +12,6 @@ class EndpointExtraMissing < EndpointExtra
end
end
require_relative "helpers/artifice"
Artifice.activate_with(EndpointExtraMissing)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "endpoint"
Artifice.deactivate
require_relative "helpers/endpoint"
class EndpointFallback < Endpoint
DEPENDENCY_LIMIT = 60
@ -16,4 +14,6 @@ class EndpointFallback < Endpoint
end
end
require_relative "helpers/artifice"
Artifice.activate_with(EndpointFallback)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "endpoint"
Artifice.deactivate
require_relative "helpers/endpoint"
class EndpointHostRedirect < Endpoint
get "/fetch/actual/gem/:id", :host_name => "localgemserver.test" do
@ -14,4 +12,6 @@ class EndpointHostRedirect < Endpoint
end
end
require_relative "helpers/artifice"
Artifice.activate_with(EndpointHostRedirect)

View File

@ -1,13 +1,6 @@
# frozen_string_literal: true
require_relative "endpoint_fallback"
Artifice.deactivate
class EndpointMarshalFail < EndpointFallback
get "/api/v1/dependencies" do
"f0283y01hasf"
end
end
require_relative "helpers/endpoint_marshal_fail"
require_relative "helpers/artifice"
Artifice.activate_with(EndpointMarshalFail)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "endpoint_marshal_fail"
Artifice.deactivate
require_relative "helpers/endpoint_marshal_fail"
class EndpointMarshalFailBasicAuthentication < EndpointMarshalFail
before do
@ -12,4 +10,6 @@ class EndpointMarshalFailBasicAuthentication < EndpointMarshalFail
end
end
require_relative "helpers/artifice"
Artifice.activate_with(EndpointMarshalFailBasicAuthentication)

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require_relative "endpoint"
require_relative "helpers/endpoint"
class EndpointMirrorSource < Endpoint
get "/gems/:id" do
@ -12,4 +12,6 @@ class EndpointMirrorSource < Endpoint
end
end
require_relative "helpers/artifice"
Artifice.activate_with(EndpointMirrorSource)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "endpoint"
Artifice.deactivate
require_relative "helpers/endpoint"
class EndpointRedirect < Endpoint
get "/fetch/actual/gem/:id" do
@ -14,4 +12,6 @@ class EndpointRedirect < Endpoint
end
end
require_relative "helpers/artifice"
Artifice.activate_with(EndpointRedirect)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "endpoint"
Artifice.deactivate
require_relative "helpers/endpoint"
class EndpointStrictBasicAuthentication < Endpoint
before do
@ -17,4 +15,6 @@ class EndpointStrictBasicAuthentication < Endpoint
end
end
require_relative "helpers/artifice"
Artifice.activate_with(EndpointStrictBasicAuthentication)

View File

@ -1,8 +1,6 @@
# frozen_string_literal: true
require_relative "endpoint_fallback"
Artifice.deactivate
require_relative "helpers/endpoint_fallback"
class EndpointTimeout < EndpointFallback
SLEEP_TIMEOUT = 3
@ -12,4 +10,6 @@ class EndpointTimeout < EndpointFallback
end
end
require_relative "helpers/artifice"
Artifice.activate_with(EndpointTimeout)

View File

@ -2,10 +2,6 @@
require "net/http"
# We can't use artifice here because it uses rack
module Artifice; end # for < 2.0, Net::HTTP::Persistent::SSLReuse
class Fail < Net::HTTP
# Net::HTTP uses a @newimpl instance variable to decide whether
# to use a legacy implementation. Since we are subclassing
@ -27,8 +23,7 @@ class Fail < Net::HTTP
end
end
require_relative "helpers/artifice"
# Replace Net::HTTP with our failing subclass
::Net.class_eval do
remove_const(:HTTP)
const_set(:HTTP, ::Fail)
end
Artifice.replace_net_http(::Fail)

View File

@ -0,0 +1,30 @@
# frozen_string_literal: true
# This module was initially borrowed from https://github.com/wycats/artifice
module Artifice
# Activate Artifice with a particular Rack endpoint.
#
# Calling this method will replace the Net::HTTP system
# with a replacement that routes all requests to the
# Rack endpoint.
#
# @param [#call] endpoint A valid Rack endpoint
def self.activate_with(endpoint)
require_relative "rack_request"
Net::HTTP.endpoint = endpoint
replace_net_http(Artifice::Net::HTTP)
end
# Deactivate the Artifice replacement.
def self.deactivate
replace_net_http(::Net::HTTP)
end
def self.replace_net_http(value)
::Net.class_eval do
remove_const(:HTTP)
const_set(:HTTP, value)
end
end
end

View File

@ -0,0 +1,118 @@
# frozen_string_literal: true
require_relative "endpoint"
$LOAD_PATH.unshift Dir[Spec::Path.base_system_gem_path.join("gems/compact_index*/lib")].first.to_s
require "compact_index"
class CompactIndexAPI < Endpoint
helpers do
include Spec::Path
def load_spec(name, version, platform, gem_repo)
full_name = "#{name}-#{version}"
full_name += "-#{platform}" if platform != "ruby"
Marshal.load(Bundler.rubygems.inflate(File.binread(gem_repo.join("quick/Marshal.4.8/#{full_name}.gemspec.rz"))))
end
def etag_response
response_body = yield
checksum = Digest(:MD5).hexdigest(response_body)
return if not_modified?(checksum)
headers "ETag" => quote(checksum)
headers "Surrogate-Control" => "max-age=2592000, stale-while-revalidate=60"
content_type "text/plain"
requested_range_for(response_body)
rescue StandardError => e
puts e
puts e.backtrace
raise
end
def not_modified?(checksum)
etags = parse_etags(request.env["HTTP_IF_NONE_MATCH"])
return unless etags.include?(checksum)
headers "ETag" => quote(checksum)
status 304
body ""
end
def requested_range_for(response_body)
ranges = Rack::Utils.byte_ranges(env, response_body.bytesize)
if ranges
status 206
body ranges.map! {|range| slice_body(response_body, range) }.join
else
status 200
body response_body
end
end
def quote(string)
%("#{string}")
end
def parse_etags(value)
value ? value.split(/, ?/).select {|s| s.sub!(/"(.*)"/, '\1') } : []
end
def slice_body(body, range)
body.byteslice(range)
end
def gems(gem_repo = default_gem_repo)
@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|
load_spec(name, version, platform, gem_repo)
end
end.flatten
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|
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)
end
CompactIndex::Gem.new(name, gem_versions)
end
end
end
end
get "/names" do
etag_response do
CompactIndex.names(gems.map(&:name))
end
end
get "/versions" do
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
get "/info/:name" do
etag_response do
gem = gems.find {|g| g.name == params[:name] }
CompactIndex.info(gem ? gem.versions : [])
end
end
end

View File

@ -0,0 +1,33 @@
# frozen_string_literal: true
require_relative "compact_index"
class CompactIndexExtra < CompactIndexAPI
get "/extra/versions" do
halt 404
end
get "/extra/api/v1/dependencies" do
halt 404
end
get "/extra/specs.4.8.gz" do
File.binread("#{gem_repo2}/specs.4.8.gz")
end
get "/extra/prerelease_specs.4.8.gz" do
File.binread("#{gem_repo2}/prerelease_specs.4.8.gz")
end
get "/extra/quick/Marshal.4.8/:id" do
redirect "/extra/fetch/actual/gem/#{params[:id]}"
end
get "/extra/fetch/actual/gem/:id" do
File.binread("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}")
end
get "/extra/gems/:id" do
File.binread("#{gem_repo2}/gems/#{params[:id]}")
end
end

View File

@ -0,0 +1,48 @@
# frozen_string_literal: true
require_relative "compact_index"
class CompactIndexExtraApi < CompactIndexAPI
get "/extra/names" do
etag_response do
CompactIndex.names(gems(gem_repo4).map(&:name))
end
end
get "/extra/versions" do
etag_response do
file = tmp("versions.list")
FileUtils.rm_f(file)
file = CompactIndex::VersionsFile.new(file.to_s)
file.create(gems(gem_repo4))
file.contents
end
end
get "/extra/info/:name" do
etag_response do
gem = gems(gem_repo4).find {|g| g.name == params[:name] }
CompactIndex.info(gem ? gem.versions : [])
end
end
get "/extra/specs.4.8.gz" do
File.binread("#{gem_repo4}/specs.4.8.gz")
end
get "/extra/prerelease_specs.4.8.gz" do
File.binread("#{gem_repo4}/prerelease_specs.4.8.gz")
end
get "/extra/quick/Marshal.4.8/:id" do
redirect "/extra/fetch/actual/gem/#{params[:id]}"
end
get "/extra/fetch/actual/gem/:id" do
File.binread("#{gem_repo4}/quick/Marshal.4.8/#{params[:id]}")
end
get "/extra/gems/:id" do
File.binread("#{gem_repo4}/gems/#{params[:id]}")
end
end

View File

@ -0,0 +1,112 @@
# frozen_string_literal: true
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))
require "sinatra/base"
ALL_REQUESTS = [] # rubocop:disable Style/MutableConstant
ALL_REQUESTS_MUTEX = Thread::Mutex.new
at_exit do
if expected = ENV["BUNDLER_SPEC_ALL_REQUESTS"]
expected = expected.split("\n").sort
actual = ALL_REQUESTS.sort
unless expected == actual
raise "Unexpected requests!\nExpected:\n\t#{expected.join("\n\t")}\n\nActual:\n\t#{actual.join("\n\t")}"
end
end
end
class Endpoint < Sinatra::Base
def self.all_requests
@all_requests ||= []
end
set :raise_errors, true
set :show_exceptions, false
def call!(*)
super.tap do
ALL_REQUESTS_MUTEX.synchronize do
ALL_REQUESTS << @request.url
end
end
end
helpers do
include Spec::Path
def default_gem_repo
if ENV["BUNDLER_SPEC_GEM_REPO"]
Pathname.new(ENV["BUNDLER_SPEC_GEM_REPO"])
else
case request.host
when "gem.repo1"
Spec::Path.gem_repo1
when "gem.repo2"
Spec::Path.gem_repo2
when "gem.repo3"
Spec::Path.gem_repo3
when "gem.repo4"
Spec::Path.gem_repo4
else
Spec::Path.gem_repo1
end
end
end
def dependencies_for(gem_names, gem_repo = default_gem_repo)
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)
end.inject(:+)
all_specs.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|
[dep.name, dep.requirement.requirements.map {|a| a.join(" ") }.join(", ")]
end,
}
end.compact
end
def load_spec(name, version, platform, gem_repo)
full_name = "#{name}-#{version}"
full_name += "-#{platform}" if platform != "ruby"
Marshal.load(Bundler.rubygems.inflate(File.binread(gem_repo.join("quick/Marshal.4.8/#{full_name}.gemspec.rz"))))
end
end
get "/quick/Marshal.4.8/:id" do
redirect "/fetch/actual/gem/#{params[:id]}"
end
get "/fetch/actual/gem/:id" do
File.binread("#{default_gem_repo}/quick/Marshal.4.8/#{params[:id]}")
end
get "/gems/:id" do
File.binread("#{default_gem_repo}/gems/#{params[:id]}")
end
get "/api/v1/dependencies" do
Marshal.dump(dependencies_for(params[:gems]))
end
get "/specs.4.8.gz" do
File.binread("#{default_gem_repo}/specs.4.8.gz")
end
get "/prerelease_specs.4.8.gz" do
File.binread("#{default_gem_repo}/prerelease_specs.4.8.gz")
end
end

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
require_relative "endpoint"
class EndpointExtra < Endpoint
get "/extra/api/v1/dependencies" do
halt 404
end
get "/extra/specs.4.8.gz" do
File.binread("#{gem_repo2}/specs.4.8.gz")
end
get "/extra/prerelease_specs.4.8.gz" do
File.binread("#{gem_repo2}/prerelease_specs.4.8.gz")
end
get "/extra/quick/Marshal.4.8/:id" do
redirect "/extra/fetch/actual/gem/#{params[:id]}"
end
get "/extra/fetch/actual/gem/:id" do
File.binread("#{gem_repo2}/quick/Marshal.4.8/#{params[:id]}")
end
get "/extra/gems/:id" do
File.binread("#{gem_repo2}/gems/#{params[:id]}")
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
require_relative "endpoint"
class EndpointFallback < Endpoint
DEPENDENCY_LIMIT = 60
get "/api/v1/dependencies" do
if params[:gems] && params[:gems].size <= DEPENDENCY_LIMIT
Marshal.dump(dependencies_for(params[:gems]))
else
halt 413, "Too many gems to resolve, please request less than #{DEPENDENCY_LIMIT} gems"
end
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
require_relative "endpoint_fallback"
class EndpointMarshalFail < EndpointFallback
get "/api/v1/dependencies" do
"f0283y01hasf"
end
end

View File

@ -0,0 +1,100 @@
# frozen_string_literal: true
require "rack/test"
require "net/http"
module Artifice
module Net
# This is an internal object that can receive Rack requests
# to the application using the Rack::Test API
class RackRequest
include Rack::Test::Methods
attr_reader :app
def initialize(app)
@app = app
end
end
class HTTP < ::Net::HTTP
class << self
attr_accessor :endpoint
end
# 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
@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
# that converts the request into a Rack request and
# dispatches it to the Rack endpoint.
#
# @param [Net::HTTPRequest] req A 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
# be converted to a StringIO.
# @return [Net::HTTPResponse]
#
# @yield [Net::HTTPResponse] If a block is provided,
# this method will yield the Net::HTTPResponse to
# it after the body is read.
def request(req, body = nil, &block)
rack_request = RackRequest.new(self.class.endpoint)
req.each_header do |header, value|
rack_request.header(header, value)
end
scheme = use_ssl? ? "https" : "http"
prefix = "#{scheme}://#{addr_port}"
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 })
make_net_http_response(response, &block)
end
private
# This method takes a Rack response and creates a 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
#
# @param [Array(#to_i, Hash, #each)] response a Rack response
# @return [Net::HTTPResponse]
# @yield [Net::HTTPResponse] If a block is provided, yield the
# response to it after the body is read
def make_net_http_response(response)
status = response.status
headers = response.headers
body = response.body
response_string = []
response_string << "HTTP/1.1 #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]}"
headers.each do |header, value|
response_string << "#{header}: #{value}"
end
response_string << "" << body
response_io = ::Net::BufferedIO.new(StringIO.new(response_string.join("\n")))
res = ::Net::HTTPResponse.read_new(response_io)
res.reading_body(response_io, true) do
yield res if block_given?
end
res
end
end
end
end

View File

@ -158,8 +158,7 @@ class BundlerVCRHTTP < Net::HTTP
alias_method :request, :request_with_vcr
end
require_relative "helpers/artifice"
# Replace Net::HTTP with our VCR subclass
::Net.class_eval do
remove_const(:HTTP)
const_set(:HTTP, BundlerVCRHTTP)
end
Artifice.replace_net_http(BundlerVCRHTTP)

View File

@ -2,13 +2,10 @@
require_relative "../path"
$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gem_path.join("gems/{artifice,mustermann,rack,tilt,sinatra,ruby2_keywords}-*/lib")].map(&:to_s))
$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gem_path.join("gems/{mustermann,rack,tilt,sinatra,ruby2_keywords}-*/lib")].map(&:to_s))
require "artifice"
require "sinatra/base"
Artifice.deactivate
class Windows < Sinatra::Base
set :raise_errors, true
set :show_exceptions, false
@ -43,4 +40,6 @@ class Windows < Sinatra::Base
end
end
require_relative "helpers/artifice"
Artifice.activate_with(Windows)

View File

@ -5,7 +5,6 @@ source "https://rubygems.org"
gem "rack", "2.0.8"
gem "webrick", "1.7.0"
gem "rack-test", "~> 1.1"
gem "artifice", "~> 0.6.0"
gem "compact_index", "~> 0.13.0"
gem "sinatra", "~> 2.0"
gem "rake", "13.0.1"

View File

@ -1,8 +1,6 @@
GEM
remote: https://rubygems.org/
specs:
artifice (0.6)
rack-test
builder (3.2.4)
compact_index (0.13.0)
mustermann (1.1.2)
@ -33,7 +31,6 @@ PLATFORMS
x86_64-linux
DEPENDENCIES
artifice (~> 0.6.0)
builder (~> 3.2)
compact_index (~> 0.13.0)
rack (= 2.0.8)