зеркало из https://github.com/github/ruby.git
437 строки
10 KiB
Ruby
437 строки
10 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "tempfile"
|
|
require "rubygems"
|
|
require "rubygems/remote_fetcher"
|
|
|
|
##
|
|
# A fake Gem::RemoteFetcher for use in tests or to avoid real live HTTP
|
|
# requests when testing code that uses RubyGems.
|
|
#
|
|
# Example:
|
|
#
|
|
# @fetcher = Gem::FakeFetcher.new
|
|
# @fetcher.data['http://gems.example.com/yaml'] = source_index.to_yaml
|
|
# Gem::RemoteFetcher.fetcher = @fetcher
|
|
#
|
|
# use nested array if multiple response is needed
|
|
#
|
|
# @fetcher.data['http://gems.example.com/sequence'] = [['Success', 200, 'OK'], ['Failed', 401, 'Unauthorized']]
|
|
#
|
|
# @fetcher.fetch_path('http://gems.example.com/sequence') # => ['Success', 200, 'OK']
|
|
# @fetcher.fetch_path('http://gems.example.com/sequence') # => ['Failed', 401, 'Unauthorized']
|
|
#
|
|
# # invoke RubyGems code
|
|
#
|
|
# paths = @fetcher.paths
|
|
# assert_equal 'http://gems.example.com/yaml', paths.shift
|
|
# assert paths.empty?, paths.join(', ')
|
|
#
|
|
# See RubyGems' tests for more examples of FakeFetcher.
|
|
|
|
class Gem::FakeFetcher
|
|
attr_reader :data
|
|
attr_reader :last_request
|
|
attr_accessor :paths
|
|
|
|
def initialize
|
|
@data = {}
|
|
@paths = []
|
|
end
|
|
|
|
def find_data(path)
|
|
return Gem.read_binary path.path if URI === path && path.scheme == "file"
|
|
|
|
if URI === path && "URI::#{path.scheme.upcase}" != path.class.name
|
|
raise ArgumentError,
|
|
"mismatch for scheme #{path.scheme} and class #{path.class}"
|
|
end
|
|
|
|
path = path.to_s
|
|
@paths << path
|
|
raise ArgumentError, "need full URI" unless path.start_with?("https://", "http://")
|
|
|
|
unless @data.key? path
|
|
raise Gem::RemoteFetcher::FetchError.new("no data for #{path}", path)
|
|
end
|
|
|
|
if @data[path].is_a?(Array)
|
|
@data[path].shift
|
|
else
|
|
@data[path]
|
|
end
|
|
end
|
|
|
|
def create_response(uri)
|
|
data = find_data(uri)
|
|
response = data.respond_to?(:call) ? data.call : data
|
|
raise TypeError, "#{response.class} is not a type of Net::HTTPResponse" unless response.is_a?(Net::HTTPResponse)
|
|
|
|
response
|
|
end
|
|
|
|
def fetch_path(path, mtime = nil, head = false)
|
|
data = find_data(path)
|
|
|
|
if data.respond_to?(:call)
|
|
data.call
|
|
else
|
|
if path.to_s.end_with?(".gz") && !data.nil? && !data.empty?
|
|
data = Gem::Util.gunzip data
|
|
end
|
|
data
|
|
end
|
|
end
|
|
|
|
def cache_update_path(uri, path = nil, update = true)
|
|
if data = fetch_path(uri)
|
|
File.open(path, "wb") {|io| io.write data } if path && update
|
|
data
|
|
else
|
|
Gem.read_binary(path) if path
|
|
end
|
|
end
|
|
|
|
# Thanks, FakeWeb!
|
|
def open_uri_or_path(path)
|
|
find_data(path)
|
|
|
|
create_response(uri)
|
|
end
|
|
|
|
def request(uri, request_class, last_modified = nil)
|
|
@last_request = request_class.new uri.request_uri
|
|
yield @last_request if block_given?
|
|
|
|
create_response(uri)
|
|
end
|
|
|
|
def pretty_print(q) # :nodoc:
|
|
q.group 2, "[FakeFetcher", "]" do
|
|
q.breakable
|
|
q.text "URIs:"
|
|
|
|
q.breakable
|
|
q.pp @data.keys
|
|
end
|
|
end
|
|
|
|
def fetch_size(path)
|
|
path = path.to_s
|
|
@paths << path
|
|
|
|
raise ArgumentError, "need full URI" unless %r{^http://}.match?(path)
|
|
|
|
unless @data.key? path
|
|
raise Gem::RemoteFetcher::FetchError.new("no data for #{path}", path)
|
|
end
|
|
|
|
data = @data[path]
|
|
|
|
data.respond_to?(:call) ? data.call : data.length
|
|
end
|
|
|
|
def download(spec, source_uri, install_dir = Gem.dir)
|
|
name = File.basename spec.cache_file
|
|
path = if Dir.pwd == install_dir # see fetch_command
|
|
install_dir
|
|
else
|
|
File.join install_dir, "cache"
|
|
end
|
|
|
|
path = File.join path, name
|
|
|
|
if /^http/.match?(source_uri)
|
|
File.open(path, "wb") do |f|
|
|
f.write fetch_path(File.join(source_uri, "gems", name))
|
|
end
|
|
else
|
|
FileUtils.cp source_uri, path
|
|
end
|
|
|
|
path
|
|
end
|
|
|
|
def download_to_cache(dependency)
|
|
found, _ = Gem::SpecFetcher.fetcher.spec_for_dependency dependency
|
|
|
|
return if found.empty?
|
|
|
|
spec, source = found.first
|
|
|
|
download spec, source.uri.to_s
|
|
end
|
|
end
|
|
|
|
##
|
|
# The HTTPResponseFactory allows easy creation of Net::HTTPResponse instances in RubyGems tests:
|
|
#
|
|
# Example:
|
|
#
|
|
# Gem::HTTPResponseFactory.create(
|
|
# body: "",
|
|
# code: 301,
|
|
# msg: "Moved Permanently",
|
|
# headers: { "location" => "http://example.com" }
|
|
# )
|
|
#
|
|
|
|
class Gem::HTTPResponseFactory
|
|
def self.create(body:, code:, msg:, headers: {})
|
|
response = Net::HTTPResponse.send(:response_class, code.to_s).new("1.0", code.to_s, msg)
|
|
response.instance_variable_set(:@body, body)
|
|
response.instance_variable_set(:@read, true)
|
|
headers.each {|name, value| response[name] = value }
|
|
|
|
response
|
|
end
|
|
end
|
|
|
|
##
|
|
# A Gem::MockBrowser is used in tests to mock a browser in that it can
|
|
# send HTTP requests to the defined URI.
|
|
#
|
|
# Example:
|
|
#
|
|
# # Sends a get request to http://localhost:5678
|
|
# Gem::MockBrowser.get URI("http://localhost:5678")
|
|
#
|
|
# See RubyGems' tests for more examples of MockBrowser.
|
|
#
|
|
|
|
class Gem::MockBrowser
|
|
def self.options(uri)
|
|
options = Net::HTTP::Options.new(uri)
|
|
Net::HTTP.start(uri.hostname, uri.port) do |http|
|
|
http.request(options)
|
|
end
|
|
end
|
|
|
|
def self.get(uri)
|
|
get = Net::HTTP::Get.new(uri)
|
|
Net::HTTP.start(uri.hostname, uri.port) do |http|
|
|
http.request(get)
|
|
end
|
|
end
|
|
|
|
def self.post(uri, content_type: "application/x-www-form-urlencoded")
|
|
headers = { "content-type" => content_type } if content_type
|
|
post = Net::HTTP::Post.new(uri, headers)
|
|
Net::HTTP.start(uri.hostname, uri.port) do |http|
|
|
http.request(post)
|
|
end
|
|
end
|
|
end
|
|
|
|
# :stopdoc:
|
|
class Gem::RemoteFetcher
|
|
def self.fetcher=(fetcher)
|
|
@fetcher = fetcher
|
|
end
|
|
end
|
|
# :startdoc:
|
|
|
|
##
|
|
# The SpecFetcherSetup allows easy setup of a remote source in RubyGems tests:
|
|
#
|
|
# spec_fetcher do |f|
|
|
# f.gem 'a', 1
|
|
# f.spec 'a', 2
|
|
# f.gem 'b', 1' 'a' => '~> 1.0'
|
|
# end
|
|
#
|
|
# The above declaration creates two gems, a-1 and b-1, with a dependency from
|
|
# b to a. The declaration creates an additional spec a-2, but no gem for it
|
|
# (so it cannot be installed).
|
|
#
|
|
# After the gems are created they are removed from Gem.dir.
|
|
|
|
class Gem::TestCase::SpecFetcherSetup
|
|
##
|
|
# Executes a SpecFetcher setup block. Yields an instance then creates the
|
|
# gems and specifications defined in the instance.
|
|
|
|
def self.declare(test, repository)
|
|
setup = new test, repository
|
|
|
|
yield setup
|
|
|
|
setup.execute
|
|
end
|
|
|
|
def initialize(test, repository) # :nodoc:
|
|
@test = test
|
|
@repository = repository
|
|
|
|
@gems = {}
|
|
@downloaded = []
|
|
@installed = []
|
|
@operations = []
|
|
end
|
|
|
|
##
|
|
# Returns a Hash of created Specification full names and the corresponding
|
|
# Specification.
|
|
|
|
def created_specs
|
|
created = {}
|
|
|
|
@gems.keys.each do |spec|
|
|
created[spec.full_name] = spec
|
|
end
|
|
|
|
created
|
|
end
|
|
|
|
##
|
|
# Creates any defined gems or specifications
|
|
|
|
def execute # :nodoc:
|
|
execute_operations
|
|
|
|
setup_fetcher
|
|
|
|
created_specs
|
|
end
|
|
|
|
def execute_operations # :nodoc:
|
|
@operations.each do |operation, *arguments|
|
|
block = arguments.pop
|
|
case operation
|
|
when :gem then
|
|
spec, gem = @test.util_gem(*arguments, &block)
|
|
|
|
write_spec spec
|
|
|
|
@gems[spec] = gem
|
|
@installed << spec
|
|
when :download then
|
|
spec, gem = @test.util_gem(*arguments, &block)
|
|
|
|
@gems[spec] = gem
|
|
@downloaded << spec
|
|
when :spec then
|
|
spec = @test.util_spec(*arguments, &block)
|
|
|
|
write_spec spec
|
|
|
|
@gems[spec] = nil
|
|
@installed << spec
|
|
end
|
|
end
|
|
end
|
|
|
|
##
|
|
# Creates a gem with +name+, +version+ and +deps+. The created gem can be
|
|
# downloaded and installed.
|
|
#
|
|
# The specification will be yielded before gem creation for customization,
|
|
# but only the block or the dependencies may be set, not both.
|
|
|
|
def gem(name, version, dependencies = nil, &block)
|
|
@operations << [:gem, name, version, dependencies, block]
|
|
end
|
|
|
|
##
|
|
# Creates a gem with +name+, +version+ and +deps+. The created gem is
|
|
# downloaded in to the cache directory but is not installed
|
|
#
|
|
# The specification will be yielded before gem creation for customization,
|
|
# but only the block or the dependencies may be set, not both.
|
|
|
|
def download(name, version, dependencies = nil, &block)
|
|
@operations << [:download, name, version, dependencies, block]
|
|
end
|
|
|
|
##
|
|
# Creates a legacy platform spec with the name 'pl' and version 1
|
|
|
|
def legacy_platform
|
|
spec "pl", 1 do |s|
|
|
s.platform = Gem::Platform.new "i386-linux"
|
|
s.instance_variable_set :@original_platform, "i386-linux"
|
|
end
|
|
end
|
|
|
|
def setup_fetcher # :nodoc:
|
|
require "zlib"
|
|
require "socket"
|
|
require "rubygems/remote_fetcher"
|
|
|
|
unless @test.fetcher
|
|
@test.fetcher = Gem::FakeFetcher.new
|
|
Gem::RemoteFetcher.fetcher = @test.fetcher
|
|
end
|
|
|
|
Gem::Specification.reset
|
|
|
|
begin
|
|
gem_repo = @test.gem_repo
|
|
@test.gem_repo = @repository
|
|
@test.uri = URI @repository
|
|
|
|
@test.util_setup_spec_fetcher(*@downloaded)
|
|
ensure
|
|
@test.gem_repo = gem_repo
|
|
@test.uri = URI gem_repo
|
|
end
|
|
|
|
@gems.each do |spec, gem|
|
|
next unless gem
|
|
|
|
@test.fetcher.data["#{@repository}gems/#{spec.file_name}"] =
|
|
Gem.read_binary(gem)
|
|
|
|
FileUtils.cp gem, spec.cache_file
|
|
end
|
|
end
|
|
|
|
##
|
|
# Creates a spec with +name+, +version+ and +deps+. The created gem can be
|
|
# downloaded and installed.
|
|
#
|
|
# The specification will be yielded before creation for customization,
|
|
# but only the block or the dependencies may be set, not both.
|
|
|
|
def spec(name, version, dependencies = nil, &block)
|
|
@operations << [:spec, name, version, dependencies, block]
|
|
end
|
|
|
|
def write_spec(spec) # :nodoc:
|
|
File.open spec.spec_file, "w" do |io|
|
|
io.write spec.to_ruby_for_cache
|
|
end
|
|
end
|
|
end
|
|
|
|
##
|
|
# A StringIO duck-typed class that uses Tempfile instead of String as the
|
|
# backing store.
|
|
#
|
|
# This class was added to flush out problems in Rubinius' IO implementation.
|
|
|
|
class Gem::TempIO < Tempfile
|
|
##
|
|
# Creates a new TempIO that will be initialized to contain +string+.
|
|
|
|
def initialize(string = "")
|
|
super "TempIO"
|
|
binmode
|
|
write string
|
|
rewind
|
|
end
|
|
|
|
##
|
|
# The content of the TempIO as a String.
|
|
|
|
def string
|
|
flush
|
|
Gem.read_binary path
|
|
end
|
|
end
|
|
|
|
class Gem::TestCase
|
|
TempIO = Gem::TempIO
|
|
HTTPResponseFactory = Gem::HTTPResponseFactory
|
|
end
|