Change soft-fail to use the config, rather than env
[rbx.git] / test / rubygems / test_gem_remote_fetcher.rb
blob1d2103bd06033e971aa9f8eb836f36228e766002
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.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.connection_for(uri)
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\tfetching size (#{uri})",
157                  e.message
158   end
160   def test_no_proxy
161     use_ui @ui do
162       fetcher = Gem::RemoteFetcher.new nil
163       assert_data_from_server fetcher.fetch_path(@server_uri)
164       assert_equal SERVER_DATA.size, fetcher.fetch_size(@server_uri)
165     end
166   end
168   def util_fuck_with_fetcher data, blow = false
169     fetcher = Gem::RemoteFetcher.fetcher
170     fetcher.instance_variable_set :@test_data, data
172     unless blow then
173       def fetcher.fetch_path arg
174         @test_arg = arg
175         @test_data
176       end
177     else
178       def fetcher.fetch_path arg
179         # OMG I'm such an ass
180         class << self; remove_method :fetch_path; end
181         def self.fetch_path arg
182           @test_arg = arg
183           @test_data
184         end
186         raise Gem::RemoteFetcher::FetchError.new("haha!", nil)
187       end
188     end
190     fetcher
191   end
193   def test_download
194     a1_data = nil
195     File.open @a1_gem, 'rb' do |fp|
196       a1_data = fp.read
197     end
199     fetcher = util_fuck_with_fetcher a1_data
201     a1_cache_gem = File.join(@gemhome, 'cache', "#{@a1.full_name}.gem")
202     assert_equal a1_cache_gem, fetcher.download(@a1, 'http://gems.example.com')
203     assert_equal("http://gems.example.com/gems/a-1.gem",
204                  fetcher.instance_variable_get(:@test_arg).to_s)
205     assert File.exist?(a1_cache_gem)
206   end
208   def test_download_cached
209     FileUtils.mv @a1_gem, @cache_dir
211     inst = Gem::RemoteFetcher.fetcher
213     assert_equal File.join(@gemhome, 'cache', "#{@a1.full_name}.gem"),
214                  inst.download(@a1, 'http://gems.example.com')
215   end
217   def test_download_local
218     FileUtils.mv @a1_gem, @tempdir
219     local_path = File.join @tempdir, "#{@a1.full_name}.gem"
220     inst = nil
222     Dir.chdir @tempdir do
223       inst = Gem::RemoteFetcher.fetcher
224     end
226     assert_equal File.join(@gemhome, 'cache', "#{@a1.full_name}.gem"),
227                  inst.download(@a1, local_path)
228   end
230   def test_download_install_dir
231     a1_data = nil
232     File.open @a1_gem, 'rb' do |fp|
233       a1_data = fp.read
234     end
236     fetcher = util_fuck_with_fetcher a1_data
238     install_dir = File.join @tempdir, 'more_gems'
240     a1_cache_gem = File.join install_dir, 'cache', "#{@a1.full_name}.gem"
241     actual = fetcher.download(@a1, 'http://gems.example.com', install_dir)
243     assert_equal a1_cache_gem, actual
244     assert_equal("http://gems.example.com/gems/a-1.gem",
245                  fetcher.instance_variable_get(:@test_arg).to_s)
247     assert File.exist?(a1_cache_gem)
248   end
250   unless win_platform? then # File.chmod doesn't work
251     def test_download_local_read_only
252       FileUtils.mv @a1_gem, @tempdir
253       local_path = File.join @tempdir, "#{@a1.full_name}.gem"
254       inst = nil
255       File.chmod 0555, File.join(@gemhome, 'cache')
257       Dir.chdir @tempdir do
258         inst = Gem::RemoteFetcher.fetcher
259       end
261       assert_equal File.join(@tempdir, "#{@a1.full_name}.gem"),
262         inst.download(@a1, local_path)
263     ensure
264       File.chmod 0755, File.join(@gemhome, 'cache')
265     end
266   end
268   def test_download_platform_legacy
269     original_platform = 'old-platform'
271     e1, e1_gem = util_gem 'e', '1' do |s|
272       s.platform = Gem::Platform::CURRENT
273       s.instance_variable_set :@original_platform, original_platform
274     end
276     e1_data = nil
277     File.open e1_gem, 'rb' do |fp|
278       e1_data = fp.read
279     end
281     fetcher = util_fuck_with_fetcher e1_data, :blow_chunks
283     e1_cache_gem = File.join(@gemhome, 'cache', "#{e1.full_name}.gem")
285     assert_equal e1_cache_gem, fetcher.download(e1, 'http://gems.example.com')
287     assert_equal("http://gems.example.com/gems/#{e1.original_name}.gem",
288                  fetcher.instance_variable_get(:@test_arg).to_s)
289     assert File.exist?(e1_cache_gem)
290   end
292   def test_download_unsupported
293     inst = Gem::RemoteFetcher.fetcher
295     e = assert_raise Gem::InstallError do
296       inst.download @a1, 'ftp://gems.rubyforge.org'
297     end
299     assert_equal 'unsupported URI scheme ftp', e.message
300   end
302   def test_explicit_proxy
303     use_ui @ui do
304       fetcher = Gem::RemoteFetcher.new @proxy_uri
305       assert_equal PROXY_DATA.size, fetcher.fetch_size(@server_uri)
306       assert_data_from_proxy fetcher.fetch_path(@server_uri)
307     end
308   end
310   def test_explicit_proxy_with_user_auth
311     use_ui @ui do
312       uri = URI.parse @proxy_uri
313       uri.user, uri.password = 'foo', 'bar'
314       fetcher = Gem::RemoteFetcher.new uri.to_s
315       proxy = fetcher.instance_variable_get("@proxy_uri")
316       assert_equal 'foo', proxy.user
317       assert_equal 'bar', proxy.password
318       assert_data_from_proxy fetcher.fetch_path(@server_uri)
319     end
321     use_ui @ui do
322       uri = URI.parse @proxy_uri
323       uri.user, uri.password = 'domain%5Cuser', 'bar'
324       fetcher = Gem::RemoteFetcher.new uri.to_s
325       proxy = fetcher.instance_variable_get("@proxy_uri")
326       assert_equal 'domain\user', URI.unescape(proxy.user)
327       assert_equal 'bar', proxy.password
328       assert_data_from_proxy fetcher.fetch_path(@server_uri)
329     end
331     use_ui @ui do
332       uri = URI.parse @proxy_uri
333       uri.user, uri.password = 'user', 'my%20pass'
334       fetcher = Gem::RemoteFetcher.new uri.to_s
335       proxy = fetcher.instance_variable_get("@proxy_uri")
336       assert_equal 'user', proxy.user
337       assert_equal 'my pass', URI.unescape(proxy.password)
338       assert_data_from_proxy fetcher.fetch_path(@server_uri)
339     end
340   end
342   def test_explicit_proxy_with_user_auth_in_env
343     use_ui @ui do
344       ENV['http_proxy'] = @proxy_uri
345       ENV['http_proxy_user'] = 'foo'
346       ENV['http_proxy_pass'] = 'bar'
347       fetcher = Gem::RemoteFetcher.new nil
348       proxy = fetcher.instance_variable_get("@proxy_uri")
349       assert_equal 'foo', proxy.user
350       assert_equal 'bar', proxy.password
351       assert_data_from_proxy fetcher.fetch_path(@server_uri)
352     end
354     use_ui @ui do
355       ENV['http_proxy'] = @proxy_uri
356       ENV['http_proxy_user'] = 'foo\user'
357       ENV['http_proxy_pass'] = 'my bar'
358       fetcher = Gem::RemoteFetcher.new nil
359       proxy = fetcher.instance_variable_get("@proxy_uri")
360       assert_equal 'foo\user', URI.unescape(proxy.user)
361       assert_equal 'my bar', URI.unescape(proxy.password)
362       assert_data_from_proxy fetcher.fetch_path(@server_uri)
363     end
364   end
366   def test_fetch_path_io_error
367     fetcher = Gem::RemoteFetcher.new nil
369     def fetcher.open_uri_or_path(uri) raise EOFError; end
371     e = assert_raise Gem::RemoteFetcher::FetchError do
372       fetcher.fetch_path 'uri'
373     end
375     assert_equal 'EOFError: EOFError (uri)', e.message
376     assert_equal 'uri', e.uri
377   end
379   def test_fetch_path_socket_error
380     fetcher = Gem::RemoteFetcher.new nil
382     def fetcher.open_uri_or_path(uri) raise SocketError; end
384     e = assert_raise Gem::RemoteFetcher::FetchError do
385       fetcher.fetch_path 'uri'
386     end
388     assert_equal 'SocketError: SocketError (uri)', e.message
389     assert_equal 'uri', e.uri
390   end
392   def test_fetch_path_system_call_error
393     fetcher = Gem::RemoteFetcher.new nil
395     def fetcher.open_uri_or_path(uri);
396       raise Errno::ECONNREFUSED, 'connect(2)'
397     end
399     e = assert_raise Gem::RemoteFetcher::FetchError do
400       fetcher.fetch_path 'uri'
401     end
403     assert_match %r|ECONNREFUSED:.*connect\(2\) \(uri\)\z|,
404                  e.message
405     assert_equal 'uri', e.uri
406   end
408   def test_get_proxy_from_env_empty
409     orig_env_HTTP_PROXY = ENV['HTTP_PROXY']
410     orig_env_http_proxy = ENV['http_proxy']
412     ENV['HTTP_PROXY'] = ''
413     ENV.delete 'http_proxy'
415     fetcher = Gem::RemoteFetcher.new nil
417     assert_equal nil, fetcher.send(:get_proxy_from_env)
419   ensure
420     orig_env_HTTP_PROXY.nil? ? ENV.delete('HTTP_PROXY') :
421                                ENV['HTTP_PROXY'] = orig_env_HTTP_PROXY
422     orig_env_http_proxy.nil? ? ENV.delete('http_proxy') :
423                                ENV['http_proxy'] = orig_env_http_proxy
424   end
426   def test_implicit_no_proxy
427     use_ui @ui do
428       ENV['http_proxy'] = 'http://fakeurl:12345'
429       fetcher = Gem::RemoteFetcher.new :no_proxy
430       assert_data_from_server fetcher.fetch_path(@server_uri)
431     end
432   end
434   def test_implicit_proxy
435     use_ui @ui do
436       ENV['http_proxy'] = @proxy_uri
437       fetcher = Gem::RemoteFetcher.new nil
438       assert_data_from_proxy fetcher.fetch_path(@server_uri)
439     end
440   end
442   def test_implicit_upper_case_proxy
443     use_ui @ui do
444       ENV['HTTP_PROXY'] = @proxy_uri
445       fetcher = Gem::RemoteFetcher.new nil
446       assert_data_from_proxy fetcher.fetch_path(@server_uri)
447     end
448   end
450   def test_implicit_proxy_no_env
451     use_ui @ui do
452       fetcher = Gem::RemoteFetcher.new nil
453       assert_data_from_server fetcher.fetch_path(@server_uri)
454     end
455   end
457   def test_open_uri_or_path
458     fetcher = Gem::RemoteFetcher.new nil
460     conn = Object.new
461     def conn.started?() true end
462     def conn.request(req)
463       unless defined? @requested then
464         @requested = true
465         res = Net::HTTPRedirection.new nil, 301, nil
466         res.add_field 'Location', 'http://gems.example.com/real_path'
467         res
468       else
469         res = Net::HTTPOK.new nil, 200, nil
470         def res.body() 'real_path' end
471         res
472       end
473     end
475     conn = { 'gems.example.com:80' => conn }
476     fetcher.instance_variable_set :@connections, conn
478     fetcher.send :open_uri_or_path, 'http://gems.example.com/redirect' do |io|
479       assert_equal 'real_path', io.read
480     end
481   end
483   def test_open_uri_or_path_limited_redirects
484     fetcher = Gem::RemoteFetcher.new nil
486     conn = Object.new
487     def conn.started?() true end
488     def conn.request(req)
489       res = Net::HTTPRedirection.new nil, 301, nil
490       res.add_field 'Location', 'http://gems.example.com/redirect'
491       res
492     end
494     conn = { 'gems.example.com:80' => conn }
495     fetcher.instance_variable_set :@connections, conn
497     e = assert_raise Gem::RemoteFetcher::FetchError do
498       fetcher.send :open_uri_or_path, 'http://gems.example.com/redirect'
499     end
501     assert_equal 'too many redirects (http://gems.example.com/redirect)',
502                  e.message
503   end
505   def test_zip
506     use_ui @ui do
507       self.class.enable_zip = true
508       fetcher = Gem::RemoteFetcher.new nil
509       assert_equal SERVER_DATA.size, fetcher.fetch_size(@server_uri), "probably not from proxy"
510       zip_data = fetcher.fetch_path(@server_z_uri)
511       assert zip_data.size < SERVER_DATA.size, "Zipped data should be smaller"
512     end
513   end
515   def test_no_zip
516     use_ui @ui do
517       self.class.enable_zip = false
518       fetcher = Gem::RemoteFetcher.new nil
519       assert_error { fetcher.fetch_path(@server_z_uri) }
520     end
521   end
523   def test_yaml_error_on_size
524     use_ui @ui do
525       self.class.enable_yaml = false
526       fetcher = Gem::RemoteFetcher.new nil
527       assert_error { fetcher.size }
528     end
529   end
531   private
533   def assert_error(exception_class=Exception)
534     got_exception = false
535     begin
536       yield
537     rescue exception_class => ex
538       got_exception = true
539     end
540     assert got_exception, "Expected exception conforming to #{exception_class}"
541   end
543   def assert_data_from_server(data)
544     assert_block("Data is not from server") { data =~ /0\.4\.11/ }
545   end
547   def assert_data_from_proxy(data)
548     assert_block("Data is not from proxy") { data =~ /0\.4\.2/ }
549   end
551   class NilLog < WEBrick::Log
552     def log(level, data) #Do nothing
553     end
554   end
556   class << self
557     attr_reader :normal_server, :proxy_server
558     attr_accessor :enable_zip, :enable_yaml
560     def start_servers
561       @normal_server ||= start_server(SERVER_PORT, SERVER_DATA)
562       @proxy_server  ||= start_server(PROXY_PORT, PROXY_DATA)
563       @enable_yaml = true
564       @enable_zip = false
565     end
567     private
569     def start_server(port, data)
570       Thread.new do
571         begin
572           null_logger = NilLog.new
573           s = WEBrick::HTTPServer.new(
574             :Port            => port,
575             :DocumentRoot    => nil,
576             :Logger          => null_logger,
577             :AccessLog       => null_logger
578             )
579           s.mount_proc("/kill") { |req, res| s.shutdown }
580           s.mount_proc("/yaml") { |req, res|
581             if @enable_yaml
582               res.body = data
583               res['Content-Type'] = 'text/plain'
584               res['content-length'] = data.size
585             else
586               res.status = "404"
587               res.body = "<h1>NOT FOUND</h1>"
588               res['Content-Type'] = 'text/html'
589             end
590           }
591           s.mount_proc("/yaml.Z") { |req, res|
592             if @enable_zip
593               res.body = Zlib::Deflate.deflate(data)
594               res['Content-Type'] = 'text/plain'
595             else
596               res.status = "404"
597               res.body = "<h1>NOT FOUND</h1>"
598               res['Content-Type'] = 'text/html'
599             end
600           }
601           s.start
602         rescue Exception => ex
603           abort ex.message
604           puts "ERROR during server thread: #{ex.message}"
605         end
606       end
607       sleep 0.2                 # Give the servers time to startup
608     end
609   end