Updated RubySpec submodule to 9f66d0b1.
[rbx.git] / test / rubygems / test_gem_remote_fetcher.rb
blobddadeb9fcfbe146c74faf7fc4f1e1e1e9ad91ce6
1 #!/usr/bin/env ruby
2 #--
3 # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
4 # All rights reserved.
5 # See LICENSE.txt for permissions.
6 #++
8 require 'test/unit'
9 require File.join(File.expand_path(File.dirname(__FILE__)), 'gemutilities')
10 require 'webrick'
11 require 'zlib'
12 require 'rubygems/remote_fetcher'
14 # = Testing Proxy Settings
16 # These tests check the proper proxy server settings by running two
17 # web servers.  The web server at http://localhost:#{SERVER_PORT}
18 # represents the normal gem server and returns a gemspec with a rake
19 # version of 0.4.11.  The web server at http://localhost:#{PROXY_PORT}
20 # represents the proxy server and returns a different dataset where
21 # rake has version 0.4.2.  This allows us to detect which server is
22 # returning the data.
24 # Note that the proxy server is not a *real* proxy server.  But our
25 # software doesn't really care, as long as we hit the proxy URL when a
26 # proxy is configured.
28 class TestGemRemoteFetcher < RubyGemTestCase
30   include Gem::DefaultUserInteraction
32   SERVER_DATA = <<-EOY
33 --- !ruby/object:Gem::Cache
34 gems:
35   rake-0.4.11: !ruby/object:Gem::Specification
36     rubygems_version: "0.7"
37     specification_version: 1
38     name: rake
39     version: !ruby/object:Gem::Version
40       version: 0.4.11
41     date: 2004-11-12
42     summary: Ruby based make-like utility.
43     require_paths:
44       - lib
45     author: Jim Weirich
46     email: jim@weirichhouse.org
47     homepage: http://rake.rubyforge.org
48     rubyforge_project: rake
49     description: Rake is a Make-like program implemented in Ruby. Tasks and dependencies are specified in standard Ruby syntax.
50     autorequire:
51     default_executable: rake
52     bindir: bin
53     has_rdoc: true
54     required_ruby_version: !ruby/object:Gem::Version::Requirement
55       requirements:
56         -
57           - ">"
58           - !ruby/object:Gem::Version
59             version: 0.0.0
60       version:
61     platform: ruby
62     files:
63       - README
64     test_files: []
65     library_stubs:
66     rdoc_options:
67     extra_rdoc_files:
68     executables:
69       - rake
70     extensions: []
71     requirements: []
72     dependencies: []
73   EOY
75   PROXY_DATA = SERVER_DATA.gsub(/0.4.11/, '0.4.2')
77   # don't let 1.8 and 1.9 autotest collide
78   RUBY_VERSION =~ /(\d+)\.(\d+)\.(\d+)/
79   # don't let parallel runners collide
80   PROXY_PORT = process_based_port + 100 + $1.to_i * 100 + $2.to_i * 10 + $3.to_i
81   SERVER_PORT = process_based_port + 200 + $1.to_i * 100 + $2.to_i * 10 + $3.to_i
83   def setup
84     super
85     self.class.start_servers
86     self.class.enable_yaml = true
87     self.class.enable_zip = false
88     ENV.delete 'http_proxy'
89     ENV.delete 'HTTP_PROXY'
90     ENV.delete 'http_proxy_user'
91     ENV.delete 'HTTP_PROXY_USER'
92     ENV.delete 'http_proxy_pass'
93     ENV.delete 'HTTP_PROXY_PASS'
95     base_server_uri = "http://localhost:#{SERVER_PORT}"
96     @proxy_uri = "http://localhost:#{PROXY_PORT}"
98     @server_uri = base_server_uri + "/yaml"
99     @server_z_uri = base_server_uri + "/yaml.Z"
101     # REFACTOR: copied from test_gem_dependency_installer.rb
102     @gems_dir = File.join @tempdir, 'gems'
103     @cache_dir = File.join @gemhome, 'cache'
104     FileUtils.mkdir @gems_dir
106     @a1, @a1_gem = util_gem 'a', '1' do |s| s.executables << 'a_bin' end
108     Gem::RemoteFetcher.instance_variable_set :@fetcher, nil
109   end
111   def test_self_fetcher
112     fetcher = Gem::RemoteFetcher.fetcher
113     assert_not_nil fetcher
114     assert_kind_of Gem::RemoteFetcher, fetcher
115   end
117   def test_self_fetcher_with_proxy
118     proxy_uri = 'http://proxy.example.com'
119     Gem.configuration[:http_proxy] = proxy_uri
120     fetcher = Gem::RemoteFetcher.fetcher
121     assert_not_nil fetcher
122     assert_kind_of Gem::RemoteFetcher, fetcher
123     assert_equal proxy_uri, fetcher.instance_variable_get(:@proxy_uri).to_s
124   end
126   def test_self_fetcher_with_proxy_URI
127     proxy_uri = URI.parse 'http://proxy.example.com'
128     Gem.configuration[:http_proxy] = proxy_uri
129     fetcher = Gem::RemoteFetcher.fetcher
130     assert_not_nil fetcher
131     assert_kind_of Gem::RemoteFetcher, fetcher
132     assert_equal proxy_uri, fetcher.instance_variable_get(:@proxy_uri)
133   end
135   def test_fetch_size_bad_uri
136     fetcher = Gem::RemoteFetcher.new nil
138     e = assert_raise ArgumentError do
139       fetcher.fetch_size 'gems.example.com/yaml'
140     end
142     assert_equal 'uri is not an HTTP URI', e.message
143   end
145   def test_fetch_size_socket_error
146     fetcher = Gem::RemoteFetcher.new nil
147     def fetcher.connect_to(host, port)
148       raise SocketError
149     end
151     uri = 'http://gems.example.com/yaml'
152     e = assert_raise Gem::RemoteFetcher::FetchError do
153       fetcher.fetch_size uri
154     end
156     assert_equal "SocketError (SocketError)\n\tgetting size of #{uri}", e.message
157   end
159   def test_no_proxy
160     use_ui @ui do
161       fetcher = Gem::RemoteFetcher.new nil
162       assert_data_from_server fetcher.fetch_path(@server_uri)
163       assert_equal SERVER_DATA.size, fetcher.fetch_size(@server_uri)
164     end
165   end
167   def util_fuck_with_fetcher data, blow = false
168     fetcher = Gem::RemoteFetcher.fetcher
169     fetcher.instance_variable_set :@test_data, data
171     unless blow then
172       def fetcher.fetch_path arg
173         @test_arg = arg
174         @test_data
175       end
176     else
177       def fetcher.fetch_path arg
178         # OMG I'm such an ass
179         class << self; remove_method :fetch_path; end
180         def self.fetch_path arg
181           @test_arg = arg
182           @test_data
183         end
185         raise Gem::RemoteFetcher::FetchError, "haha!"
186       end
187     end
189     fetcher
190   end
192   def test_download
193     a1_data = nil
194     File.open @a1_gem, 'rb' do |fp|
195       a1_data = fp.read
196     end
198     fetcher = util_fuck_with_fetcher a1_data
200     a1_cache_gem = File.join(@gemhome, 'cache', "#{@a1.full_name}.gem")
201     assert_equal a1_cache_gem, fetcher.download(@a1, 'http://gems.example.com')
202     assert_equal("http://gems.example.com/gems/a-1.gem",
203                  fetcher.instance_variable_get(:@test_arg).to_s)
204     assert File.exist?(a1_cache_gem)
205   end
207   def test_download_cached
208     FileUtils.mv @a1_gem, @cache_dir
210     inst = Gem::RemoteFetcher.fetcher
212     assert_equal File.join(@gemhome, 'cache', "#{@a1.full_name}.gem"),
213                  inst.download(@a1, 'http://gems.example.com')
214   end
216   def test_download_local
217     FileUtils.mv @a1_gem, @tempdir
218     local_path = File.join @tempdir, "#{@a1.full_name}.gem"
219     inst = nil
221     Dir.chdir @tempdir do
222       inst = Gem::RemoteFetcher.fetcher
223     end
225     assert_equal File.join(@gemhome, 'cache', "#{@a1.full_name}.gem"),
226                  inst.download(@a1, local_path)
227   end
229   def test_download_install_dir
230     a1_data = nil
231     File.open @a1_gem, 'rb' do |fp|
232       a1_data = fp.read
233     end
235     fetcher = util_fuck_with_fetcher a1_data
237     install_dir = File.join @tempdir, 'more_gems'
239     a1_cache_gem = File.join install_dir, 'cache', "#{@a1.full_name}.gem"
240     actual = fetcher.download(@a1, 'http://gems.example.com', install_dir)
242     assert_equal a1_cache_gem, actual
243     assert_equal("http://gems.example.com/gems/a-1.gem",
244                  fetcher.instance_variable_get(:@test_arg).to_s)
246     assert File.exist?(a1_cache_gem)
247   end
249   unless win_platform? then # File.chmod doesn't work
250     def test_download_local_read_only
251       FileUtils.mv @a1_gem, @tempdir
252       local_path = File.join @tempdir, "#{@a1.full_name}.gem"
253       inst = nil
254       File.chmod 0555, File.join(@gemhome, 'cache')
256       Dir.chdir @tempdir do
257         inst = Gem::RemoteFetcher.fetcher
258       end
260       assert_equal File.join(@tempdir, "#{@a1.full_name}.gem"),
261         inst.download(@a1, local_path)
262     ensure
263       File.chmod 0755, File.join(@gemhome, 'cache')
264     end
265   end
267   def test_download_platform_legacy
268     original_platform = 'old-platform'
270     e1, e1_gem = util_gem 'e', '1' do |s|
271       s.platform = Gem::Platform::CURRENT
272       s.instance_variable_set :@original_platform, original_platform
273     end
275     e1_data = nil
276     File.open e1_gem, 'rb' do |fp|
277       e1_data = fp.read
278     end
280     fetcher = util_fuck_with_fetcher e1_data, :blow_chunks
282     e1_cache_gem = File.join(@gemhome, 'cache', "#{e1.full_name}.gem")
284     assert_equal e1_cache_gem, fetcher.download(e1, 'http://gems.example.com')
286     assert_equal("http://gems.example.com/gems/#{e1.original_name}.gem",
287                  fetcher.instance_variable_get(:@test_arg).to_s)
288     assert File.exist?(e1_cache_gem)
289   end
291   def test_download_unsupported
292     inst = Gem::RemoteFetcher.fetcher
294     e = assert_raise Gem::InstallError do
295       inst.download @a1, 'ftp://gems.rubyforge.org'
296     end
298     assert_equal 'unsupported URI scheme ftp', e.message
299   end
301   def test_explicit_proxy
302     use_ui @ui do
303       fetcher = Gem::RemoteFetcher.new @proxy_uri
304       assert_equal PROXY_DATA.size, fetcher.fetch_size(@server_uri)
305       assert_data_from_proxy fetcher.fetch_path(@server_uri)
306     end
307   end
309   def test_explicit_proxy_with_user_auth
310     use_ui @ui do
311       uri = URI.parse @proxy_uri
312       uri.user, uri.password = 'foo', 'bar'
313       fetcher = Gem::RemoteFetcher.new uri.to_s
314       proxy = fetcher.instance_variable_get("@proxy_uri")
315       assert_equal 'foo', proxy.user
316       assert_equal 'bar', proxy.password
317       assert_data_from_proxy fetcher.fetch_path(@server_uri)
318     end
320     use_ui @ui do
321       uri = URI.parse @proxy_uri
322       uri.user, uri.password = 'domain%5Cuser', 'bar'
323       fetcher = Gem::RemoteFetcher.new uri.to_s
324       proxy = fetcher.instance_variable_get("@proxy_uri")
325       assert_equal 'domain\user', URI.unescape(proxy.user)
326       assert_equal 'bar', proxy.password
327       assert_data_from_proxy fetcher.fetch_path(@server_uri)
328     end
330     use_ui @ui do
331       uri = URI.parse @proxy_uri
332       uri.user, uri.password = 'user', 'my%20pass'
333       fetcher = Gem::RemoteFetcher.new uri.to_s
334       proxy = fetcher.instance_variable_get("@proxy_uri")
335       assert_equal 'user', proxy.user
336       assert_equal 'my pass', URI.unescape(proxy.password)
337       assert_data_from_proxy fetcher.fetch_path(@server_uri)
338     end
339   end
341   def test_explicit_proxy_with_user_auth_in_env
342     use_ui @ui do
343       ENV['http_proxy'] = @proxy_uri
344       ENV['http_proxy_user'] = 'foo'
345       ENV['http_proxy_pass'] = 'bar'
346       fetcher = Gem::RemoteFetcher.new nil
347       proxy = fetcher.instance_variable_get("@proxy_uri")
348       assert_equal 'foo', proxy.user
349       assert_equal 'bar', proxy.password
350       assert_data_from_proxy fetcher.fetch_path(@server_uri)
351     end
353     use_ui @ui do
354       ENV['http_proxy'] = @proxy_uri
355       ENV['http_proxy_user'] = 'foo\user'
356       ENV['http_proxy_pass'] = 'my bar'
357       fetcher = Gem::RemoteFetcher.new nil
358       proxy = fetcher.instance_variable_get("@proxy_uri")
359       assert_equal 'foo\user', URI.unescape(proxy.user)
360       assert_equal 'my bar', URI.unescape(proxy.password)
361       assert_data_from_proxy fetcher.fetch_path(@server_uri)
362     end
363   end
365   def test_fetch_path_io_error
366     fetcher = Gem::RemoteFetcher.new nil
368     def fetcher.open_uri_or_path(uri) raise EOFError; end
370     e = assert_raise Gem::RemoteFetcher::FetchError do
371       fetcher.fetch_path 'uri'
372     end
374     assert_equal 'EOFError: EOFError reading uri', e.message
375   end
377   def test_fetch_path_socket_error
378     fetcher = Gem::RemoteFetcher.new nil
380     def fetcher.open_uri_or_path(uri) raise SocketError; end
382     e = assert_raise Gem::RemoteFetcher::FetchError do
383       fetcher.fetch_path 'uri'
384     end
386     assert_equal 'SocketError: SocketError reading uri', e.message
387   end
389   def test_fetch_path_system_call_error
390     fetcher = Gem::RemoteFetcher.new nil
392     def fetcher.open_uri_or_path(uri);
393       raise Errno::ECONNREFUSED, 'connect(2)'
394     end
396     e = assert_raise Gem::RemoteFetcher::FetchError do
397       fetcher.fetch_path 'uri'
398     end
400     assert_match %r|ECONNREFUSED:.*connect\(2\) reading uri\z|,
401                  e.message
402   end
404   def test_get_proxy_from_env_empty
405     orig_env_HTTP_PROXY = ENV['HTTP_PROXY']
406     orig_env_http_proxy = ENV['http_proxy']
408     ENV['HTTP_PROXY'] = ''
409     ENV.delete 'http_proxy'
411     fetcher = Gem::RemoteFetcher.new nil
413     assert_equal nil, fetcher.send(:get_proxy_from_env)
415   ensure
416     orig_env_HTTP_PROXY.nil? ? ENV.delete('HTTP_PROXY') :
417                                ENV['HTTP_PROXY'] = orig_env_HTTP_PROXY
418     orig_env_http_proxy.nil? ? ENV.delete('http_proxy') :
419                                ENV['http_proxy'] = orig_env_http_proxy
420   end
422   def test_implicit_no_proxy
423     use_ui @ui do
424       ENV['http_proxy'] = 'http://fakeurl:12345'
425       fetcher = Gem::RemoteFetcher.new :no_proxy
426       assert_data_from_server fetcher.fetch_path(@server_uri)
427     end
428   end
430   def test_implicit_proxy
431     use_ui @ui do
432       ENV['http_proxy'] = @proxy_uri
433       fetcher = Gem::RemoteFetcher.new nil
434       assert_data_from_proxy fetcher.fetch_path(@server_uri)
435     end
436   end
438   def test_implicit_upper_case_proxy
439     use_ui @ui do
440       ENV['HTTP_PROXY'] = @proxy_uri
441       fetcher = Gem::RemoteFetcher.new nil
442       assert_data_from_proxy fetcher.fetch_path(@server_uri)
443     end
444   end
446   def test_implicit_proxy_no_env
447     use_ui @ui do
448       fetcher = Gem::RemoteFetcher.new nil
449       assert_data_from_server fetcher.fetch_path(@server_uri)
450     end
451   end
453   def test_open_uri_or_path
454     fetcher = Gem::RemoteFetcher.new nil
456     conn = Object.new
457     def conn.started?() true end
458     def conn.request(req)
459       unless defined? @requested then
460         @requested = true
461         res = Net::HTTPRedirection.new nil, 301, nil
462         res.add_field 'Location', 'http://gems.example.com/real_path'
463         res
464       else
465         res = Net::HTTPOK.new nil, 200, nil
466         def res.body() 'real_path' end
467         res
468       end
469     end
471     conn = { 'gems.example.com:80' => conn }
472     fetcher.instance_variable_set :@connections, conn
474     fetcher.send :open_uri_or_path, 'http://gems.example.com/redirect' do |io|
475       assert_equal 'real_path', io.read
476     end
477   end
479   def test_open_uri_or_path_limited_redirects
480     fetcher = Gem::RemoteFetcher.new nil
482     conn = Object.new
483     def conn.started?() true end
484     def conn.request(req)
485       res = Net::HTTPRedirection.new nil, 301, nil
486       res.add_field 'Location', 'http://gems.example.com/redirect'
487       res
488     end
490     conn = { 'gems.example.com:80' => conn }
491     fetcher.instance_variable_set :@connections, conn
493     e = assert_raise Gem::RemoteFetcher::FetchError do
494       fetcher.send :open_uri_or_path, 'http://gems.example.com/redirect'
495     end
497     assert_equal 'too many redirects', e.message
498   end
500   def test_zip
501     use_ui @ui do
502       self.class.enable_zip = true
503       fetcher = Gem::RemoteFetcher.new nil
504       assert_equal SERVER_DATA.size, fetcher.fetch_size(@server_uri), "probably not from proxy"
505       zip_data = fetcher.fetch_path(@server_z_uri)
506       assert zip_data.size < SERVER_DATA.size, "Zipped data should be smaller"
507     end
508   end
510   def test_no_zip
511     use_ui @ui do
512       self.class.enable_zip = false
513       fetcher = Gem::RemoteFetcher.new nil
514       assert_error { fetcher.fetch_path(@server_z_uri) }
515     end
516   end
518   def test_yaml_error_on_size
519     use_ui @ui do
520       self.class.enable_yaml = false
521       fetcher = Gem::RemoteFetcher.new nil
522       assert_error { fetcher.size }
523     end
524   end
526   private
528   def assert_error(exception_class=Exception)
529     got_exception = false
530     begin
531       yield
532     rescue exception_class => ex
533       got_exception = true
534     end
535     assert got_exception, "Expected exception conforming to #{exception_class}"
536   end
538   def assert_data_from_server(data)
539     assert_block("Data is not from server") { data =~ /0\.4\.11/ }
540   end
542   def assert_data_from_proxy(data)
543     assert_block("Data is not from proxy") { data =~ /0\.4\.2/ }
544   end
546   class NilLog < WEBrick::Log
547     def log(level, data) #Do nothing
548     end
549   end
551   class << self
552     attr_reader :normal_server, :proxy_server
553     attr_accessor :enable_zip, :enable_yaml
555     def start_servers
556       @normal_server ||= start_server(SERVER_PORT, SERVER_DATA)
557       @proxy_server  ||= start_server(PROXY_PORT, PROXY_DATA)
558       @enable_yaml = true
559       @enable_zip = false
560     end
562     private
564     def start_server(port, data)
565       Thread.new do
566         begin
567           null_logger = NilLog.new
568           s = WEBrick::HTTPServer.new(
569             :Port            => port,
570             :DocumentRoot    => nil,
571             :Logger          => null_logger,
572             :AccessLog       => null_logger
573             )
574           s.mount_proc("/kill") { |req, res| s.shutdown }
575           s.mount_proc("/yaml") { |req, res|
576             if @enable_yaml
577               res.body = data
578               res['Content-Type'] = 'text/plain'
579               res['content-length'] = data.size
580             else
581               res.status = "404"
582               res.body = "<h1>NOT FOUND</h1>"
583               res['Content-Type'] = 'text/html'
584             end
585           }
586           s.mount_proc("/yaml.Z") { |req, res|
587             if @enable_zip
588               res.body = Zlib::Deflate.deflate(data)
589               res['Content-Type'] = 'text/plain'
590             else
591               res.status = "404"
592               res.body = "<h1>NOT FOUND</h1>"
593               res['Content-Type'] = 'text/html'
594             end
595           }
596           s.start
597         rescue Exception => ex
598           abort ex.message
599           puts "ERROR during server thread: #{ex.message}"
600         end
601       end
602       sleep 0.2                 # Give the servers time to startup
603     end
604   end