1# frozen_string_literal: false
2require 'test/unit'
3
4require 'tempfile'
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    Tempfile.create(["test_ruby_test_require", ".so"]) {|t|
18      t.puts "dummy"
19      t.close
20
21      assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}")
22      begin;
23        $:.replace([IO::NULL])
24        assert_raise(LoadError) do
25          require \"#{ t.path }\"
26        end
27      end;
28    }
29  end
30
31  def test_require_too_long_filename
32    assert_separately(["RUBYOPT"=>nil], "#{<<~"begin;"}\n#{<<~"end;"}")
33    begin;
34      $:.replace([IO::NULL])
35      assert_raise(LoadError) do
36        require '#{ "foo/" * 10000 }foo'
37      end
38    end;
39
40    begin
41      assert_in_out_err(["-S", "-w", "foo/" * 1024 + "foo"], "") do |r, e|
42        assert_equal([], r)
43        assert_operator(2, :<=, e.size)
44        assert_match(/warning: openpath: pathname too long \(ignored\)/, e.first)
45        assert_match(/\(LoadError\)/, e.last)
46      end
47    rescue Errno::EINVAL
48      # too long commandline may be blocked by OS.
49    end
50  end
51
52  def test_require_nonascii
53    bug3758 = '[ruby-core:31915]'
54    ["\u{221e}", "\x82\xa0".force_encoding("cp932")].each do |path|
55      assert_raise_with_message(LoadError, /#{path}\z/, bug3758) {require path}
56    end
57  end
58
59  def test_require_nonascii_path
60    bug8165 = '[ruby-core:53733] [Bug #8165]'
61    encoding = 'filesystem'
62    assert_require_nonascii_path(encoding, bug8165)
63  end
64
65  def test_require_insecure_path
66    assert_require_insecure_path("foo")
67    encoding = 'filesystem'
68    assert_require_insecure_path(nil, encoding)
69  end
70
71  def test_require_nonascii_path_utf8
72    bug8676 = '[ruby-core:56136] [Bug #8676]'
73    encoding = Encoding::UTF_8
74    return if Encoding.find('filesystem') == encoding
75    assert_require_nonascii_path(encoding, bug8676)
76  end
77
78  def test_require_insecure_path_utf8
79    encoding = Encoding::UTF_8
80    return if Encoding.find('filesystem') == encoding
81    assert_require_insecure_path(nil, encoding)
82  end
83
84  def test_require_nonascii_path_shift_jis
85    bug8676 = '[ruby-core:56136] [Bug #8676]'
86    encoding = Encoding::Shift_JIS
87    return if Encoding.find('filesystem') == encoding
88    assert_require_nonascii_path(encoding, bug8676)
89  end
90
91  def test_require_insecure_path_shift_jis
92    encoding = Encoding::Shift_JIS
93    return if Encoding.find('filesystem') == encoding
94    assert_require_insecure_path(nil, encoding)
95  end
96
97  case RUBY_PLATFORM
98  when /cygwin/, /mswin/, /mingw/, /darwin/
99    def self.ospath_encoding(path)
100      Encoding::UTF_8
101    end
102  else
103    def self.ospath_encoding(path)
104      path.encoding
105    end
106  end
107
108  SECURITY_WARNING =
109    if /mswin|mingw/ =~ RUBY_PLATFORM
110      nil
111    else
112      proc do |require_path|
113        $SAFE = 1
114        require(require_path)
115      ensure
116        $SAFE = 0
117      end
118    end
119
120  def prepare_require_path(dir, encoding)
121    Dir.mktmpdir {|tmp|
122      begin
123        require_path = File.join(tmp, dir, 'foo.rb').encode(encoding)
124      rescue
125        skip "cannot convert path encoding to #{encoding}"
126      end
127      Dir.mkdir(File.dirname(require_path))
128      open(require_path, "wb") {|f| f.puts '$:.push __FILE__'}
129      begin
130        load_path = $:.dup
131        features = $".dup
132        yield require_path
133      ensure
134        $:.replace(load_path)
135        $".replace(features)
136      end
137    }
138  end
139
140  def assert_require_nonascii_path(encoding, bug)
141    prepare_require_path("\u3042" * 5, encoding) {|require_path|
142      begin
143        # leave paths for require encoding objects
144        bug = "#{bug} require #{encoding} path"
145        require_path = "#{require_path}"
146        $:.clear
147        assert_nothing_raised(LoadError, bug) {
148          assert(require(require_path), bug)
149          assert_equal(self.class.ospath_encoding(require_path), $:.last.encoding, '[Bug #8753]')
150          assert(!require(require_path), bug)
151        }
152      end
153    }
154  end
155
156  def assert_require_insecure_path(dirname, encoding = nil)
157    return unless SECURITY_WARNING
158    dirname ||= "\u3042" * 5
159    encoding ||= dirname.encoding
160    prepare_require_path(dirname, encoding) {|require_path|
161      require_path.untaint
162      require(require_path)
163      $".pop
164      File.chmod(0777, File.dirname(require_path))
165      ospath = (require_path.encode('filesystem') rescue
166                require_path.encode(self.class.ospath_encoding(require_path)))
167      e = nil
168      stderr = EnvUtil.verbose_warning do
169        e = assert_raise(SecurityError) do
170          SECURITY_WARNING.call(require_path)
171        end
172      end
173      assert_include(e.message, "loading from unsafe path")
174      assert_include(stderr, "Insecure world writable dir")
175      require_path = require_path.encode(self.class.ospath_encoding(require_path))
176      assert_include(e.message, require_path)
177      assert_include(stderr, File.dirname(require_path))
178    }
179  end
180
181  def test_require_path_home_1
182    env_rubypath, env_home = ENV["RUBYPATH"], ENV["HOME"]
183    pathname_too_long = /pathname too long \(ignored\).*\(LoadError\)/m
184
185    ENV["RUBYPATH"] = "~"
186    ENV["HOME"] = "/foo" * 1024
187    assert_in_out_err(%w(-S -w test_ruby_test_require), "", [], pathname_too_long)
188
189  ensure
190    env_rubypath ? ENV["RUBYPATH"] = env_rubypath : ENV.delete("RUBYPATH")
191    env_home ? ENV["HOME"] = env_home : ENV.delete("HOME")
192  end
193
194  def test_require_path_home_2
195    env_rubypath, env_home = ENV["RUBYPATH"], ENV["HOME"]
196    pathname_too_long = /pathname too long \(ignored\).*\(LoadError\)/m
197
198    ENV["RUBYPATH"] = "~" + "/foo" * 1024
199    ENV["HOME"] = "/foo"
200    assert_in_out_err(%w(-S -w test_ruby_test_require), "", [], pathname_too_long)
201
202  ensure
203    env_rubypath ? ENV["RUBYPATH"] = env_rubypath : ENV.delete("RUBYPATH")
204    env_home ? ENV["HOME"] = env_home : ENV.delete("HOME")
205  end
206
207  def test_require_path_home_3
208    env_rubypath, env_home = ENV["RUBYPATH"], ENV["HOME"]
209
210    Tempfile.create(["test_ruby_test_require", ".rb"]) {|t|
211      t.puts "p :ok"
212      t.close
213
214      ENV["RUBYPATH"] = "~"
215      ENV["HOME"] = t.path
216      assert_in_out_err(%w(-S test_ruby_test_require), "", [], /\(LoadError\)/)
217
218      ENV["HOME"], name = File.split(t.path)
219      assert_in_out_err(["-S", name], "", %w(:ok), [])
220    }
221  ensure
222    env_rubypath ? ENV["RUBYPATH"] = env_rubypath : ENV.delete("RUBYPATH")
223    env_home ? ENV["HOME"] = env_home : ENV.delete("HOME")
224  end
225
226  def test_require_with_unc
227    Tempfile.create(["test_ruby_test_require", ".rb"]) {|t|
228      t.puts "puts __FILE__"
229      t.close
230
231      path = File.expand_path(t.path).sub(/\A(\w):/, '//127.0.0.1/\1$')
232      skip "local drive #$1: is not shared" unless File.exist?(path)
233      args = ['--disable-gems', "-I#{File.dirname(path)}"]
234      assert_in_out_err(args, "#{<<~"END;"}", [path], [])
235      begin
236        require '#{File.basename(path)}'
237      rescue Errno::EPERM
238      end
239      END;
240    }
241  end if /mswin|mingw/ =~ RUBY_PLATFORM
242
243  def test_require_twice
244    Dir.mktmpdir do |tmp|
245      req = File.join(tmp, "very_long_file_name.rb")
246      File.write(req, "p :ok\n")
247      assert_file.exist?(req)
248      req[/.rb$/i] = ""
249      assert_in_out_err(['--disable-gems'], <<-INPUT, %w(:ok), [])
250        require "#{req}"
251        require "#{req}"
252      INPUT
253    end
254  end
255
256  def assert_syntax_error_backtrace
257    Dir.mktmpdir do |tmp|
258      req = File.join(tmp, "test.rb")
259      File.write(req, "'\n")
260      e = assert_raise_with_message(SyntaxError, /unterminated/) {
261        yield req
262      }
263      assert_not_nil(bt = e.backtrace)
264      assert_not_empty(bt.find_all {|b| b.start_with? __FILE__})
265    end
266  end
267
268  def test_require_syntax_error
269    assert_syntax_error_backtrace {|req| require req}
270  end
271
272  def test_load_syntax_error
273    assert_syntax_error_backtrace {|req| load req}
274  end
275
276  def test_define_class
277    begin
278      require "socket"
279    rescue LoadError
280      return
281    end
282
283    assert_separately([], <<-INPUT)
284      BasicSocket = 1
285      assert_raise(TypeError) do
286        require 'socket'
287      end
288    INPUT
289
290    assert_separately([], <<-INPUT)
291      class BasicSocket; end
292      assert_raise(TypeError) do
293        require 'socket'
294      end
295    INPUT
296
297    assert_separately([], <<-INPUT)
298      class BasicSocket < IO; end
299      assert_nothing_raised do
300        require 'socket'
301      end
302    INPUT
303  end
304
305  def test_define_class_under
306    begin
307      require "zlib"
308    rescue LoadError
309      return
310    end
311
312    assert_separately([], <<-INPUT)
313      module Zlib; end
314      Zlib::Error = 1
315      assert_raise(TypeError) do
316        require 'zlib'
317      end
318    INPUT
319
320    assert_separately([], <<-INPUT)
321      module Zlib; end
322      class Zlib::Error; end
323      assert_raise(TypeError) do
324        require 'zlib'
325      end
326    INPUT
327
328    assert_separately([], <<-INPUT)
329      module Zlib; end
330      class Zlib::Error < StandardError; end
331      assert_nothing_raised do
332        require 'zlib'
333      end
334    INPUT
335  end
336
337  def test_define_module
338    begin
339      require "zlib"
340    rescue LoadError
341      return
342    end
343
344    assert_separately([], <<-INPUT)
345      Zlib = 1
346      assert_raise(TypeError) do
347        require 'zlib'
348      end
349    INPUT
350  end
351
352  def test_define_module_under
353    begin
354      require "socket"
355    rescue LoadError
356      return
357    end
358
359    assert_separately([], <<-INPUT)
360      class BasicSocket < IO; end
361      class Socket < BasicSocket; end
362      Socket::Constants = 1
363      assert_raise(TypeError) do
364        require 'socket'
365      end
366    INPUT
367  end
368
369  def test_load
370    Tempfile.create(["test_ruby_test_require", ".rb"]) {|t|
371      t.puts "module Foo; end"
372      t.puts "at_exit { p :wrap_end }"
373      t.puts "at_exit { raise 'error in at_exit test' }"
374      t.puts "p :ok"
375      t.close
376
377      assert_in_out_err([], <<-INPUT, %w(:ok :end :wrap_end), /error in at_exit test/)
378        load(#{ t.path.dump }, true)
379        GC.start
380        p :end
381      INPUT
382
383      assert_raise(ArgumentError) { at_exit }
384    }
385  end
386
387  def test_load_scope
388    bug1982 = '[ruby-core:25039] [Bug #1982]'
389    Tempfile.create(["test_ruby_test_require", ".rb"]) {|t|
390      t.puts "Hello = 'hello'"
391      t.puts "class Foo"
392      t.puts "  p Hello"
393      t.puts "end"
394      t.close
395
396      assert_in_out_err([], <<-INPUT, %w("hello"), [], bug1982)
397        load(#{ t.path.dump }, true)
398      INPUT
399    }
400  end
401
402  def test_load_ospath
403    bug = '[ruby-list:49994] path in ospath'
404    base = "test_load\u{3042 3044 3046 3048 304a}".encode(Encoding::Windows_31J)
405    path = nil
406    Tempfile.create([base, ".rb"]) do |t|
407      path = t.path
408
409      assert_raise_with_message(LoadError, /#{base}/) {
410        load(File.join(File.dirname(path), base))
411      }
412
413      t.puts "warn 'ok'"
414      t.close
415      assert_include(path, base)
416      assert_warn("ok\n", bug) {
417        assert_nothing_raised(LoadError, bug) {
418          load(path)
419        }
420      }
421    end
422  end
423
424  def test_tainted_loadpath
425    Tempfile.create(["test_ruby_test_require", ".rb"]) {|t|
426      abs_dir, file = File.split(t.path)
427      abs_dir = File.expand_path(abs_dir).untaint
428
429      assert_separately([], <<-INPUT)
430        abs_dir = "#{ abs_dir }"
431        $: << abs_dir
432        assert_nothing_raised {require "#{ file }"}
433      INPUT
434
435      assert_separately([], <<-INPUT)
436        abs_dir = "#{ abs_dir }"
437        $: << abs_dir.taint
438        assert_nothing_raised {require "#{ file }"}
439      INPUT
440
441      assert_separately([], <<-INPUT)
442        abs_dir = "#{ abs_dir }"
443        $: << abs_dir.taint
444        $SAFE = 1
445        assert_raise(SecurityError) {require "#{ file }"}
446      INPUT
447
448      assert_separately([], <<-INPUT)
449        abs_dir = "#{ abs_dir }"
450        $: << abs_dir << 'elsewhere'.taint
451        assert_nothing_raised {require "#{ file }"}
452      INPUT
453    }
454  end
455
456  def test_relative
457    load_path = $:.dup
458    $:.delete(".")
459    Dir.mktmpdir do |tmp|
460      Dir.chdir(tmp) do
461        Dir.mkdir('x')
462        File.open('x/t.rb', 'wb') {}
463        File.open('x/a.rb', 'wb') {|f| f.puts("require_relative('t.rb')")}
464        assert require('./x/t.rb')
465        assert !require(File.expand_path('x/t.rb'))
466        assert_nothing_raised(LoadError) {require('./x/a.rb')}
467        assert_raise(LoadError) {require('x/t.rb')}
468        File.unlink(*Dir.glob('x/*'))
469        Dir.rmdir("#{tmp}/x")
470        $:.replace(load_path)
471        load_path = nil
472        assert(!require('tmpdir'))
473      end
474    end
475  ensure
476    $:.replace(load_path) if load_path
477  end
478
479  def test_relative_symlink
480    Dir.mktmpdir {|tmp|
481      Dir.chdir(tmp) {
482        Dir.mkdir "a"
483        Dir.mkdir "b"
484        File.open("a/lib.rb", "w") {|f| f.puts 'puts "a/lib.rb"' }
485        File.open("b/lib.rb", "w") {|f| f.puts 'puts "b/lib.rb"' }
486        File.open("a/tst.rb", "w") {|f| f.puts 'require_relative "lib"' }
487        begin
488          File.symlink("../a/tst.rb", "b/tst.rb")
489          result = IO.popen([EnvUtil.rubybin, "b/tst.rb"], &:read)
490          assert_equal("a/lib.rb\n", result, "[ruby-dev:40040]")
491        rescue NotImplementedError, Errno::EACCES
492          skip "File.symlink is not implemented"
493        end
494      }
495    }
496  end
497
498  def test_frozen_loaded_features
499    bug3756 = '[ruby-core:31913]'
500    assert_in_out_err(['-e', '$LOADED_FEATURES.freeze; require "ostruct"'], "",
501                      [], /\$LOADED_FEATURES is frozen; cannot append feature \(RuntimeError\)$/,
502                      bug3756)
503  end
504
505  def test_race_exception
506    bug5754 = '[ruby-core:41618]'
507    path = nil
508    stderr = $stderr
509    verbose = $VERBOSE
510    Tempfile.create(%w"bug5754 .rb") {|tmp|
511      path = tmp.path
512      tmp.print "#{<<~"begin;"}\n#{<<~"end;"}"
513      begin;
514        th = Thread.current
515        t = th[:t]
516        scratch = th[:scratch]
517
518        if scratch.empty?
519          scratch << :pre
520          Thread.pass until t.stop?
521          raise RuntimeError
522        else
523          scratch << :post
524        end
525      end;
526      tmp.close
527
528      class << (output = "")
529        alias write concat
530      end
531      $stderr = output
532
533      start = false
534
535      scratch = []
536      t1_res = nil
537      t2_res = nil
538
539      t1 = Thread.new do
540        Thread.pass until start
541        begin
542          require(path)
543        rescue RuntimeError
544        end
545
546        t1_res = require(path)
547      end
548
549      t2 = Thread.new do
550        Thread.pass until scratch[0]
551        t2_res = require(path)
552      end
553
554      t1[:scratch] = t2[:scratch] = scratch
555      t1[:t] = t2
556      t2[:t] = t1
557
558      $VERBOSE = true
559      start = true
560
561      assert_nothing_raised(ThreadError, bug5754) {t1.join}
562      assert_nothing_raised(ThreadError, bug5754) {t2.join}
563
564      $VERBOSE = false
565
566      assert_equal(true, (t1_res ^ t2_res), bug5754 + " t1:#{t1_res} t2:#{t2_res}")
567      assert_equal([:pre, :post], scratch, bug5754)
568
569      assert_match(/circular require/, output)
570      assert_match(/in #{__method__}'$/o, output)
571    }
572  ensure
573    $VERBOSE = verbose
574    $stderr = stderr
575    $".delete(path)
576  end
577
578  def test_loaded_features_encoding
579    bug6377 = '[ruby-core:44750]'
580    loadpath = $:.dup
581    features = $".dup
582    $".clear
583    $:.clear
584    Dir.mktmpdir {|tmp|
585      $: << tmp
586      open(File.join(tmp, "foo.rb"), "w") {}
587      require "foo"
588      assert_send([Encoding, :compatible?, tmp, $"[0]], bug6377)
589    }
590  ensure
591    $:.replace(loadpath)
592    $".replace(features)
593  end
594
595  def test_require_changed_current_dir
596    bug7158 = '[ruby-core:47970]'
597    Dir.mktmpdir {|tmp|
598      Dir.chdir(tmp) {
599        Dir.mkdir("a")
600        Dir.mkdir("b")
601        open(File.join("a", "foo.rb"), "w") {}
602        open(File.join("b", "bar.rb"), "w") {|f|
603          f.puts "p :ok"
604        }
605        assert_in_out_err([], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7158)
606        begin;
607          $:.replace([IO::NULL])
608          $: << "."
609          Dir.chdir("a")
610          require "foo"
611          Dir.chdir("../b")
612          p :ng unless require "bar"
613          Dir.chdir("..")
614          p :ng if require "b/bar"
615        end;
616      }
617    }
618  end
619
620  def test_require_not_modified_load_path
621    bug7158 = '[ruby-core:47970]'
622    Dir.mktmpdir {|tmp|
623      Dir.chdir(tmp) {
624        open("foo.rb", "w") {}
625        assert_in_out_err([], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7158)
626        begin;
627          $:.replace([IO::NULL])
628          a = Object.new
629          def a.to_str
630            "#{tmp}"
631          end
632          $: << a
633          require "foo"
634          last_path = $:.pop
635          p :ok if last_path == a && last_path.class == Object
636        end;
637      }
638    }
639  end
640
641  def test_require_changed_home
642    bug7158 = '[ruby-core:47970]'
643    Dir.mktmpdir {|tmp|
644      Dir.chdir(tmp) {
645        open("foo.rb", "w") {}
646        Dir.mkdir("a")
647        open(File.join("a", "bar.rb"), "w") {}
648        assert_in_out_err([], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7158)
649        begin;
650          $:.replace([IO::NULL])
651          $: << '~'
652          ENV['HOME'] = "#{tmp}"
653          require "foo"
654          ENV['HOME'] = "#{tmp}/a"
655          p :ok if require "bar"
656        end;
657      }
658    }
659  end
660
661  def test_require_to_path_redefined_in_load_path
662    bug7158 = '[ruby-core:47970]'
663    Dir.mktmpdir {|tmp|
664      Dir.chdir(tmp) {
665        open("foo.rb", "w") {}
666        assert_in_out_err([{"RUBYOPT"=>nil}, '--disable-gems'], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7158)
667        begin;
668          $:.replace([IO::NULL])
669          a = Object.new
670          def a.to_path
671            "bar"
672          end
673          $: << a
674          begin
675            require "foo"
676            p [:ng, $LOAD_PATH, ENV['RUBYLIB']]
677          rescue LoadError => e
678            raise unless e.path == "foo"
679          end
680          def a.to_path
681            "#{tmp}"
682          end
683          p :ok if require "foo"
684        end;
685      }
686    }
687  end
688
689  def test_require_to_str_redefined_in_load_path
690    bug7158 = '[ruby-core:47970]'
691    Dir.mktmpdir {|tmp|
692      Dir.chdir(tmp) {
693        open("foo.rb", "w") {}
694        assert_in_out_err([{"RUBYOPT"=>nil}, '--disable-gems'], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7158)
695        begin;
696          $:.replace([IO::NULL])
697          a = Object.new
698          def a.to_str
699            "foo"
700          end
701          $: << a
702          begin
703            require "foo"
704            p [:ng, $LOAD_PATH, ENV['RUBYLIB']]
705          rescue LoadError => e
706            raise unless e.path == "foo"
707          end
708          def a.to_str
709            "#{tmp}"
710          end
711          p :ok if require "foo"
712        end;
713      }
714    }
715  end
716
717  def assert_require_with_shared_array_modified(add, del)
718    bug7383 = '[ruby-core:49518]'
719    Dir.mktmpdir {|tmp|
720      Dir.chdir(tmp) {
721        open("foo.rb", "w") {}
722        Dir.mkdir("a")
723        open(File.join("a", "bar.rb"), "w") {}
724        assert_in_out_err(['--disable-gems'], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7383)
725        begin;
726          $:.replace([IO::NULL])
727          $:.#{add} "#{tmp}"
728          $:.#{add} "#{tmp}/a"
729          require "foo"
730          $:.#{del}
731          # Expanded load path cache should be rebuilt.
732          begin
733            require "bar"
734          rescue LoadError => e
735            if e.path == "bar"
736              p :ok
737            else
738              raise
739            end
740          end
741        end;
742      }
743    }
744  end
745
746  def test_require_with_array_pop
747    assert_require_with_shared_array_modified("push", "pop")
748  end
749
750  def test_require_with_array_shift
751    assert_require_with_shared_array_modified("unshift", "shift")
752  end
753
754  def test_require_local_var_on_toplevel
755    bug7536 = '[ruby-core:50701]'
756    Dir.mktmpdir {|tmp|
757      Dir.chdir(tmp) {
758        open("bar.rb", "w") {|f| f.puts 'TOPLEVEL_BINDING.eval("lib = 2")' }
759        assert_in_out_err(%w[-r./bar.rb], "#{<<~"begin;"}\n#{<<~"end;"}", %w([:lib] 2), [], bug7536)
760        begin;
761          puts TOPLEVEL_BINDING.eval("local_variables").inspect
762          puts TOPLEVEL_BINDING.eval("lib").inspect
763        end;
764      }
765    }
766  end
767
768  def test_require_with_loaded_features_pop
769    bug7530 = '[ruby-core:50645]'
770    Tempfile.create(%w'bug-7530- .rb') {|script|
771      script.close
772      assert_in_out_err([{"RUBYOPT" => nil}, "-", script.path], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7530, timeout: 60)
773      begin;
774        PATH = ARGV.shift
775        THREADS = 4
776        ITERATIONS_PER_THREAD = 1000
777
778        THREADS.times.map {
779          Thread.new do
780            ITERATIONS_PER_THREAD.times do
781              require PATH
782              $".delete_if {|p| Regexp.new(PATH) =~ p}
783            end
784          end
785        }.each(&:join)
786        p :ok
787      end;
788    }
789  end
790
791  def test_loading_fifo_threading_raise
792    Tempfile.create(%w'fifo .rb') {|f|
793      f.close
794      File.unlink(f.path)
795      File.mkfifo(f.path)
796      assert_separately(["-", f.path], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 3)
797      begin;
798        th = Thread.current
799        Thread.start {begin sleep(0.001) end until th.stop?; th.raise(IOError)}
800        assert_raise(IOError) do
801          load(ARGV[0])
802        end
803      end;
804    }
805  end if File.respond_to?(:mkfifo)
806
807  def test_loading_fifo_threading_success
808    Tempfile.create(%w'fifo .rb') {|f|
809      f.close
810      File.unlink(f.path)
811      File.mkfifo(f.path)
812
813      assert_separately(["-", f.path], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 3)
814      begin;
815        path = ARGV[0]
816        th = Thread.current
817        $ok = false
818        Thread.start {
819          begin
820            sleep(0.001)
821          end until th.stop?
822          open(path, File::WRONLY | File::NONBLOCK) {|fifo_w|
823            fifo_w.print "$ok = true\n__END__\n" # ensure finishing
824          }
825        }
826
827        load(path)
828        assert($ok)
829      end;
830    }
831  end if File.respond_to?(:mkfifo)
832
833  def test_loading_fifo_fd_leak
834    Tempfile.create(%w'fifo .rb') {|f|
835      f.close
836      File.unlink(f.path)
837      File.mkfifo(f.path)
838      assert_separately(["-", f.path], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 3)
839      begin;
840        Process.setrlimit(Process::RLIMIT_NOFILE, 50)
841        th = Thread.current
842        100.times do |i|
843          Thread.start {begin sleep(0.001) end until th.stop?; th.raise(IOError)}
844          assert_raise(IOError, "\#{i} time") do
845            begin
846              tap {tap {tap {load(ARGV[0])}}}
847            rescue LoadError
848              GC.start
849              retry
850            end
851          end
852        end
853      end;
854    }
855  end if File.respond_to?(:mkfifo) and defined?(Process::RLIMIT_NOFILE)
856
857  def test_throw_while_loading
858    Tempfile.create(%w'bug-11404 .rb') do |f|
859      f.puts 'sleep'
860      f.close
861
862      assert_separately(["-", f.path], "#{<<~"begin;"}\n#{<<~'end;'}")
863      begin;
864        path = ARGV[0]
865        class Error < RuntimeError
866          def exception(*)
867            begin
868              throw :blah
869            rescue UncaughtThrowError
870            end
871            self
872          end
873        end
874
875        assert_throw(:blah) do
876          x = Thread.current
877          Thread.start {
878            sleep 0.00001
879            x.raise Error.new
880          }
881          load path
882        end
883      end;
884    end
885  end
886
887  def test_symlink_load_path
888    Dir.mktmpdir {|tmp|
889      Dir.mkdir(File.join(tmp, "real"))
890      begin
891        File.symlink "real", File.join(tmp, "symlink")
892      rescue NotImplementedError, Errno::EACCES
893        skip "File.symlink is not implemented"
894      end
895      File.write(File.join(tmp, "real/a.rb"), "print __FILE__")
896      result = IO.popen([EnvUtil.rubybin, "-I#{tmp}/symlink", "-e", "require 'a.rb'"], &:read)
897      assert_operator(result, :end_with?, "/real/a.rb")
898    }
899  end
900end
901