1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
|
# 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)
post = Net::HTTP::Post.new(uri)
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
|