# 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, nargs = 3) return File.read path.path if URI === path and 'file' == path.scheme if URI === path and "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 =~ %r'^https?://' unless @data.key? path raise Gem::RemoteFetcher::FetchError.new("no data for #{path}", path) end data = @data[path] data.flatten! and return data.shift(nargs) if data.respond_to?(:flatten!) data 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 =~ /gz$/ and not data.nil? and not 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) open(path, 'wb') { |io| io.write data } if path and update data else Gem.read_binary(path) if path end end # Thanks, FakeWeb! def open_uri_or_path(path) data = find_data(path) body, code, msg = data 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) response end def request(uri, request_class, last_modified = nil) data = find_data(uri) body, code, msg = (data.respond_to?(:call) ? data.call : data) @last_request = request_class.new uri.request_uri yield @last_request if block_given? 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) response 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 path =~ %r'^http://' 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 source_uri =~ /^http/ 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 # :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 is available when rubygems/test_utilities is required. #-- # This class was added to flush out problems in Rubinius' IO implementation. class 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