1require 'test/unit'
2
3require 'tempfile'
4require_relative 'envutil'
5require 'tmpdir'
6
7class TestRequire < Test::Unit::TestCase
8  def test_load_error_path
9    filename = "should_not_exist"
10    error = assert_raise(LoadError) do
11      require filename
12    end
13    assert_equal filename, error.path
14  end
15
16  def test_require_invalid_shared_object
17    t = Tempfile.new(["test_ruby_test_require", ".so"])
18    t.puts "dummy"
19    t.close
20
21    assert_in_out_err([], <<-INPUT, %w(:ok), [])
22      $:.replace([IO::NULL])
23      begin
24        require \"#{ t.path }\"
25      rescue LoadError
26        p :ok
27      end
28    INPUT
29    t.close(true)
30  end
31
32  def test_require_too_long_filename
33    assert_in_out_err(["RUBYOPT"=>nil], <<-INPUT, %w(:ok), [])
34      $:.replace([IO::NULL])
35      begin
36        require '#{ "foo/" * 10000 }foo'
37      rescue LoadError
38        p :ok
39      end
40    INPUT
41
42    begin
43      assert_in_out_err(["-S", "-w", "foo/" * 1024 + "foo"], "") do |r, e|
44        assert_equal([], r)
45        assert_operator(2, :<=, e.size)
46        assert_match(/warning: openpath: pathname too long \(ignored\)/, e.first)
47        assert_match(/\(LoadError\)/, e.last)
48      end
49    rescue Errno::EINVAL
50      # too long commandline may be blocked by OS.
51    end
52  end
53
54  def test_require_nonascii
55    bug3758 = '[ruby-core:31915]'
56    ["\u{221e}", "\x82\xa0".force_encoding("cp932")].each do |path|
57      e = assert_raise(LoadError, bug3758) {require path}
58      assert_match(/#{path}\z/, e.message, bug3758)
59    end
60  end
61
62  def test_require_nonascii_path
63    bug8165 = '[ruby-core:53733] [Bug #8165]'
64    Dir.mktmpdir {|tmp|
65      encoding = /mswin|mingw/ =~ RUBY_PLATFORM ? 'filesystem' : 'UTF-8'
66      dir = "\u3042" * 5
67      begin
68        require_path = File.join(tmp, dir, 'foo.rb').encode(encoding)
69      rescue
70        skip "cannot convert path encoding to #{encoding}"
71      end
72      Dir.mkdir(File.dirname(require_path))
73      open(require_path, "wb") {}
74      assert_separately(%w[--disable=gems], <<-INPUT)
75        # leave paths for require encoding objects
76        bug = "#{bug8165} require #{encoding} path"
77        require_path = "#{require_path}"
78        enc_path = Regexp.new(Regexp.escape(RUBY_PLATFORM))
79        $:.replace([IO::NULL] + $:.reject {|path| enc_path !~ path})
80        assert_nothing_raised(LoadError, bug) {
81          assert(require(require_path), bug)
82          assert(!require(require_path), bug)
83        }
84      INPUT
85    }
86  end
87
88  def test_require_path_home_1
89    env_rubypath, env_home = ENV["RUBYPATH"], ENV["HOME"]
90    pathname_too_long = /pathname too long \(ignored\).*\(LoadError\)/m
91
92    ENV["RUBYPATH"] = "~"
93    ENV["HOME"] = "/foo" * 1024
94    assert_in_out_err(%w(-S -w test_ruby_test_require), "", [], pathname_too_long)
95
96  ensure
97    env_rubypath ? ENV["RUBYPATH"] = env_rubypath : ENV.delete("RUBYPATH")
98    env_home ? ENV["HOME"] = env_home : ENV.delete("HOME")
99  end
100
101  def test_require_path_home_2
102    env_rubypath, env_home = ENV["RUBYPATH"], ENV["HOME"]
103    pathname_too_long = /pathname too long \(ignored\).*\(LoadError\)/m
104
105    ENV["RUBYPATH"] = "~" + "/foo" * 1024
106    ENV["HOME"] = "/foo"
107    assert_in_out_err(%w(-S -w test_ruby_test_require), "", [], pathname_too_long)
108
109  ensure
110    env_rubypath ? ENV["RUBYPATH"] = env_rubypath : ENV.delete("RUBYPATH")
111    env_home ? ENV["HOME"] = env_home : ENV.delete("HOME")
112  end
113
114  def test_require_path_home_3
115    env_rubypath, env_home = ENV["RUBYPATH"], ENV["HOME"]
116
117    t = Tempfile.new(["test_ruby_test_require", ".rb"])
118    t.puts "p :ok"
119    t.close
120
121    ENV["RUBYPATH"] = "~"
122    ENV["HOME"] = t.path
123    assert_in_out_err(%w(-S test_ruby_test_require), "", [], /\(LoadError\)/)
124
125    ENV["HOME"], name = File.split(t.path)
126    assert_in_out_err(["-S", name], "", %w(:ok), [])
127
128  ensure
129    env_rubypath ? ENV["RUBYPATH"] = env_rubypath : ENV.delete("RUBYPATH")
130    env_home ? ENV["HOME"] = env_home : ENV.delete("HOME")
131    t.close(true)
132  end
133
134  def test_require_with_unc
135    ruby = File.expand_path(EnvUtil.rubybin).sub(/\A(\w):/, '//127.0.0.1/\1$/')
136    skip "local drive #$1: is not shared" unless File.exist?(ruby)
137    pid = nil
138    assert_nothing_raised {pid = spawn(ruby, "-rabbrev", "-e0")}
139    ret, status = Process.wait2(pid)
140    assert_equal(pid, ret)
141    assert_predicate(status, :success?)
142  end if /mswin|mingw/ =~ RUBY_PLATFORM
143
144  def test_require_twice
145    Dir.mktmpdir do |tmp|
146      req = File.join(tmp, "very_long_file_name.rb")
147      File.write(req, "p :ok\n")
148      assert_file.exist?(req)
149      req[/.rb$/i] = ""
150      assert_in_out_err(['--disable-gems'], <<-INPUT, %w(:ok), [])
151        require "#{req}"
152        require "#{req}"
153      INPUT
154    end
155  end
156
157  def test_define_class
158    begin
159      require "socket"
160    rescue LoadError
161      return
162    end
163
164    assert_in_out_err([], <<-INPUT, %w(:ok), [])
165      BasicSocket = 1
166      begin
167        require 'socket'
168        p :ng
169      rescue TypeError
170        p :ok
171      end
172    INPUT
173
174    assert_in_out_err([], <<-INPUT, %w(:ok), [])
175      class BasicSocket; end
176      begin
177        require 'socket'
178        p :ng
179      rescue TypeError
180        p :ok
181      end
182    INPUT
183
184    assert_in_out_err([], <<-INPUT, %w(:ok), [])
185      class BasicSocket < IO; end
186      begin
187        require 'socket'
188        p :ok
189      rescue Exception
190        p :ng
191      end
192    INPUT
193  end
194
195  def test_define_class_under
196    begin
197      require "zlib"
198    rescue LoadError
199      return
200    end
201
202    assert_in_out_err([], <<-INPUT, %w(:ok), [])
203      module Zlib; end
204      Zlib::Error = 1
205      begin
206        require 'zlib'
207        p :ng
208      rescue TypeError
209        p :ok
210      end
211    INPUT
212
213    assert_in_out_err([], <<-INPUT, %w(:ok), [])
214      module Zlib; end
215      class Zlib::Error; end
216      begin
217        require 'zlib'
218        p :ng
219      rescue NameError
220        p :ok
221      end
222    INPUT
223
224    assert_in_out_err([], <<-INPUT, %w(:ok), [])
225      module Zlib; end
226      class Zlib::Error < StandardError; end
227      begin
228        require 'zlib'
229        p :ok
230      rescue Exception
231        p :ng
232      end
233    INPUT
234  end
235
236  def test_define_module
237    begin
238      require "zlib"
239    rescue LoadError
240      return
241    end
242
243    assert_in_out_err([], <<-INPUT, %w(:ok), [])
244      Zlib = 1
245      begin
246        require 'zlib'
247        p :ng
248      rescue TypeError
249        p :ok
250      end
251    INPUT
252  end
253
254  def test_define_module_under
255    begin
256      require "socket"
257    rescue LoadError
258      return
259    end
260
261    assert_in_out_err([], <<-INPUT, %w(:ok), [])
262      class BasicSocket < IO; end
263      class Socket < BasicSocket; end
264      Socket::Constants = 1
265      begin
266        require 'socket'
267        p :ng
268      rescue TypeError
269        p :ok
270      end
271    INPUT
272  end
273
274  def test_load
275    t = Tempfile.new(["test_ruby_test_require", ".rb"])
276    t.puts "module Foo; end"
277    t.puts "at_exit { p :wrap_end }"
278    t.puts "at_exit { raise 'error in at_exit test' }"
279    t.puts "p :ok"
280    t.close
281
282    assert_in_out_err([], <<-INPUT, %w(:ok :end :wrap_end), /error in at_exit test/)
283      load(#{ t.path.dump }, true)
284      GC.start
285      p :end
286    INPUT
287
288    assert_raise(ArgumentError) { at_exit }
289    t.close(true)
290  end
291
292  def test_load2  # [ruby-core:25039]
293    t = Tempfile.new(["test_ruby_test_require", ".rb"])
294    t.puts "Hello = 'hello'"
295    t.puts "class Foo"
296    t.puts "  p Hello"
297    t.puts "end"
298    t.close
299
300    assert_in_out_err([], <<-INPUT, %w("hello"), [])
301      load(#{ t.path.dump }, true)
302    INPUT
303    t.close(true)
304  end
305
306  def test_tainted_loadpath
307    t = Tempfile.new(["test_ruby_test_require", ".rb"])
308    abs_dir, file = File.split(t.path)
309    abs_dir = File.expand_path(abs_dir).untaint
310
311    assert_in_out_err([], <<-INPUT, %w(:ok), [])
312      abs_dir = "#{ abs_dir }"
313      $: << abs_dir
314      require "#{ file }"
315      p :ok
316    INPUT
317
318    assert_in_out_err([], <<-INPUT, %w(:ok), [])
319      abs_dir = "#{ abs_dir }"
320      $: << abs_dir.taint
321      require "#{ file }"
322      p :ok
323    INPUT
324
325    assert_in_out_err([], <<-INPUT, %w(:ok), [])
326      abs_dir = "#{ abs_dir }"
327      $: << abs_dir.taint
328      $SAFE = 1
329      begin
330        require "#{ file }"
331      rescue SecurityError
332        p :ok
333      end
334    INPUT
335
336    assert_in_out_err([], <<-INPUT, %w(:ok), [])
337      abs_dir = "#{ abs_dir }"
338      $: << abs_dir.taint
339      $SAFE = 1
340      begin
341        require "#{ file }"
342      rescue SecurityError
343        p :ok
344      end
345    INPUT
346
347    assert_in_out_err([], <<-INPUT, %w(:ok), [])
348      abs_dir = "#{ abs_dir }"
349      $: << abs_dir << 'elsewhere'.taint
350      require "#{ file }"
351      p :ok
352    INPUT
353
354    t.close(true)
355  end
356
357  def test_relative
358    load_path = $:.dup
359    $:.delete(".")
360    Dir.mktmpdir do |tmp|
361      Dir.chdir(tmp) do
362        Dir.mkdir('x')
363        File.open('x/t.rb', 'wb') {}
364        File.open('x/a.rb', 'wb') {|f| f.puts("require_relative('t.rb')")}
365        assert require('./x/t.rb')
366        assert !require(File.expand_path('x/t.rb'))
367        assert_nothing_raised(LoadError) {require('./x/a.rb')}
368        assert_raise(LoadError) {require('x/t.rb')}
369        File.unlink(*Dir.glob('x/*'))
370        Dir.rmdir("#{tmp}/x")
371        $:.replace(load_path)
372        load_path = nil
373        assert(!require('tmpdir'))
374      end
375    end
376  ensure
377    $:.replace(load_path) if load_path
378  end
379
380  def test_relative_symlink
381    Dir.mktmpdir {|tmp|
382      Dir.chdir(tmp) {
383        Dir.mkdir "a"
384        Dir.mkdir "b"
385        File.open("a/lib.rb", "w") {|f| f.puts 'puts "a/lib.rb"' }
386        File.open("b/lib.rb", "w") {|f| f.puts 'puts "b/lib.rb"' }
387        File.open("a/tst.rb", "w") {|f| f.puts 'require_relative "lib"' }
388        begin
389          File.symlink("../a/tst.rb", "b/tst.rb")
390          result = IO.popen([EnvUtil.rubybin, "b/tst.rb"]).read
391          assert_equal("a/lib.rb\n", result, "[ruby-dev:40040]")
392        rescue NotImplementedError
393          skip "File.symlink is not implemented"
394        end
395      }
396    }
397  end
398
399  def test_frozen_loaded_features
400    bug3756 = '[ruby-core:31913]'
401    assert_in_out_err(['-e', '$LOADED_FEATURES.freeze; require "ostruct"'], "",
402                      [], /\$LOADED_FEATURES is frozen; cannot append feature \(RuntimeError\)$/,
403                      bug3756)
404  end
405
406  def test_race_exception
407    bug5754 = '[ruby-core:41618]'
408    tmp = Tempfile.new(%w"bug5754 .rb")
409    path = tmp.path
410    tmp.print %{\
411      th = Thread.current
412      t = th[:t]
413      scratch = th[:scratch]
414
415      if scratch.empty?
416        scratch << :pre
417        Thread.pass until t.stop?
418        raise RuntimeError
419      else
420        scratch << :post
421      end
422    }
423    tmp.close
424
425    # "circular require" warnings to $stderr, but backtraces to stderr
426    # in C-level.  And redirecting stderr to a pipe seems to change
427    # some blocking timings and causes a deadlock, so run in a
428    # separated process for the time being.
429    assert_separately(["-w", "-", path, bug5754], <<-'end;', ignore_stderr: true)
430    path, bug5754 = *ARGV
431    start = false
432
433    scratch = []
434    t1_res = nil
435    t2_res = nil
436
437    t1 = Thread.new do
438      Thread.pass until start
439      begin
440        require(path)
441      rescue RuntimeError
442      end
443
444      t1_res = require(path)
445    end
446
447    t2 = Thread.new do
448      Thread.pass until scratch[0]
449      t2_res = require(path)
450    end
451
452    t1[:scratch] = t2[:scratch] = scratch
453    t1[:t] = t2
454    t2[:t] = t1
455
456    start = true
457
458    assert_nothing_raised(ThreadError, bug5754) {t1.join}
459    assert_nothing_raised(ThreadError, bug5754) {t2.join}
460
461    assert_equal(true, (t1_res ^ t2_res), bug5754 + " t1:#{t1_res} t2:#{t2_res}")
462    assert_equal([:pre, :post], scratch, bug5754)
463    end;
464  ensure
465    $".delete(path)
466    tmp.close(true) if tmp
467  end
468
469  def test_loaded_features_encoding
470    bug6377 = '[ruby-core:44750]'
471    loadpath = $:.dup
472    features = $".dup
473    $".clear
474    $:.clear
475    Dir.mktmpdir {|tmp|
476      $: << tmp
477      open(File.join(tmp, "foo.rb"), "w") {}
478      require "foo"
479      assert(Encoding.compatible?(tmp, $"[0]), bug6377)
480    }
481  ensure
482    $:.replace(loadpath)
483    $".replace(features)
484  end
485
486  def test_require_changed_current_dir
487    bug7158 = '[ruby-core:47970]'
488    Dir.mktmpdir {|tmp|
489      Dir.chdir(tmp) {
490        Dir.mkdir("a")
491        Dir.mkdir("b")
492        open(File.join("a", "foo.rb"), "w") {}
493        open(File.join("b", "bar.rb"), "w") {|f|
494          f.puts "p :ok"
495        }
496        assert_in_out_err([], <<-INPUT, %w(:ok), [], bug7158)
497          $:.replace([IO::NULL])
498          $: << "."
499          Dir.chdir("a")
500          require "foo"
501          Dir.chdir("../b")
502          p :ng unless require "bar"
503          Dir.chdir("..")
504          p :ng if require "b/bar"
505        INPUT
506      }
507    }
508  end
509
510  def test_require_not_modified_load_path
511    bug7158 = '[ruby-core:47970]'
512    Dir.mktmpdir {|tmp|
513      Dir.chdir(tmp) {
514        open("foo.rb", "w") {}
515        assert_in_out_err([], <<-INPUT, %w(:ok), [], bug7158)
516          $:.replace([IO::NULL])
517          a = Object.new
518          def a.to_str
519            "#{tmp}"
520          end
521          $: << a
522          require "foo"
523          last_path = $:.pop
524          p :ok if last_path == a && last_path.class == Object
525        INPUT
526      }
527    }
528  end
529
530  def test_require_changed_home
531    bug7158 = '[ruby-core:47970]'
532    Dir.mktmpdir {|tmp|
533      Dir.chdir(tmp) {
534        open("foo.rb", "w") {}
535        Dir.mkdir("a")
536        open(File.join("a", "bar.rb"), "w") {}
537        assert_in_out_err([], <<-INPUT, %w(:ok), [], bug7158)
538          $:.replace([IO::NULL])
539          $: << '~'
540          ENV['HOME'] = "#{tmp}"
541          require "foo"
542          ENV['HOME'] = "#{tmp}/a"
543          p :ok if require "bar"
544        INPUT
545      }
546    }
547  end
548
549  def test_require_to_path_redefined_in_load_path
550    bug7158 = '[ruby-core:47970]'
551    Dir.mktmpdir {|tmp|
552      Dir.chdir(tmp) {
553        open("foo.rb", "w") {}
554        assert_in_out_err(["RUBYOPT"=>nil], <<-INPUT, %w(:ok), [], bug7158)
555          $:.replace([IO::NULL])
556          a = Object.new
557          def a.to_path
558            "bar"
559          end
560          $: << a
561          begin
562            require "foo"
563            p [:ng, $LOAD_PATH, ENV['RUBYLIB']]
564          rescue LoadError
565          end
566          def a.to_path
567            "#{tmp}"
568          end
569          p :ok if require "foo"
570        INPUT
571      }
572    }
573  end
574
575  def test_require_to_str_redefined_in_load_path
576    bug7158 = '[ruby-core:47970]'
577    Dir.mktmpdir {|tmp|
578      Dir.chdir(tmp) {
579        open("foo.rb", "w") {}
580        assert_in_out_err(["RUBYOPT"=>nil], <<-INPUT, %w(:ok), [], bug7158)
581          $:.replace([IO::NULL])
582          a = Object.new
583          def a.to_str
584            "foo"
585          end
586          $: << a
587          begin
588            require "foo"
589            p [:ng, $LOAD_PATH, ENV['RUBYLIB']]
590          rescue LoadError
591          end
592          def a.to_str
593            "#{tmp}"
594          end
595          p :ok if require "foo"
596        INPUT
597      }
598    }
599  end
600
601  def assert_require_with_shared_array_modified(add, del)
602    bug7383 = '[ruby-core:49518]'
603    Dir.mktmpdir {|tmp|
604      Dir.chdir(tmp) {
605        open("foo.rb", "w") {}
606        Dir.mkdir("a")
607        open(File.join("a", "bar.rb"), "w") {}
608        assert_in_out_err([], <<-INPUT, %w(:ok), [], bug7383)
609          $:.replace([IO::NULL])
610          $:.#{add} "#{tmp}"
611          $:.#{add} "#{tmp}/a"
612          require "foo"
613          $:.#{del}
614          # Expanded load path cache should be rebuilt.
615          begin
616            require "bar"
617          rescue LoadError
618            p :ok
619          end
620        INPUT
621      }
622    }
623  end
624
625  def test_require_with_array_pop
626    assert_require_with_shared_array_modified("push", "pop")
627  end
628
629  def test_require_with_array_shift
630    assert_require_with_shared_array_modified("unshift", "shift")
631  end
632
633  def test_require_local_var_on_toplevel
634    bug7536 = '[ruby-core:50701]'
635    Dir.mktmpdir {|tmp|
636      Dir.chdir(tmp) {
637        open("bar.rb", "w") {|f| f.puts 'TOPLEVEL_BINDING.eval("lib = 2")' }
638        assert_in_out_err(%w[-r./bar.rb], <<-INPUT, %w([:lib] 2), [], bug7536)
639          puts TOPLEVEL_BINDING.eval("local_variables").inspect
640          puts TOPLEVEL_BINDING.eval("lib").inspect
641        INPUT
642      }
643    }
644  end
645
646  def test_require_with_loaded_features_pop
647    bug7530 = '[ruby-core:50645]'
648    script = Tempfile.new(%w'bug-7530- .rb')
649    script.close
650    assert_in_out_err([{"RUBYOPT" => nil}, "-", script.path], <<-INPUT, %w(:ok), [], bug7530)
651      PATH = ARGV.shift
652      THREADS = 2
653      ITERATIONS_PER_THREAD = 1000
654
655      THREADS.times.map {
656        Thread.new do
657          ITERATIONS_PER_THREAD.times do
658            require PATH
659            $".pop
660          end
661        end
662      }.each(&:join)
663      p :ok
664    INPUT
665  ensure
666    script.close(true) if script
667  end
668end
669