diff options
Diffstat (limited to 'test/ruby/test_autoload.rb')
| -rw-r--r-- | test/ruby/test_autoload.rb | 632 |
1 files changed, 538 insertions, 94 deletions
diff --git a/test/ruby/test_autoload.rb b/test/ruby/test_autoload.rb index 8ca2187970..82bf2d9d2c 100644 --- a/test/ruby/test_autoload.rb +++ b/test/ruby/test_autoload.rb @@ -1,14 +1,13 @@ +# frozen_string_literal: false require 'test/unit' require 'tempfile' -require 'thread' -require_relative 'envutil' class TestAutoload < Test::Unit::TestCase def test_autoload_so - # Continuation is always available, unless excluded intentionally. + # Date is always available, unless excluded intentionally. assert_in_out_err([], <<-INPUT, [], []) - autoload :Continuation, "continuation" - begin Continuation; rescue LoadError; end + autoload :Date, "date" + begin Date; rescue LoadError; end INPUT end @@ -43,8 +42,10 @@ p Foo::Bar require 'tmpdir' Dir.mktmpdir('autoload') {|tmpdir| tmpfile = tmpdir + '/foo.rb' + tmpfile2 = tmpdir + '/bar.rb' a = Module.new do autoload :X, tmpfile + autoload :Y, tmpfile2 end b = Module.new do include a @@ -53,130 +54,573 @@ p Foo::Bar assert_equal(true, b.const_defined?(:X)) assert_equal(tmpfile, a.autoload?(:X), bug4565) assert_equal(tmpfile, b.autoload?(:X), bug4565) + assert_equal(tmpfile, a.autoload?(:X, false)) + assert_equal(tmpfile, a.autoload?(:X, nil)) + assert_nil(b.autoload?(:X, false)) + assert_nil(b.autoload?(:X, nil)) + assert_equal(true, a.const_defined?("Y")) + assert_equal(true, b.const_defined?("Y")) + assert_equal(tmpfile2, a.autoload?("Y")) + assert_equal(tmpfile2, b.autoload?("Y")) } end - def test_require_explicit - file = Tempfile.open(['autoload', '.rb']) - file.puts 'class Object; AutoloadTest = 1; end' - file.close - add_autoload(file.path) + def test_autoload_p_with_static_extensions + require 'rbconfig' + omit unless RbConfig::CONFIG['EXTSTATIC'] == 'static' begin - assert_nothing_raised do - assert(require file.path) - assert_equal(1, ::AutoloadTest) - end - ensure - remove_autoload_constant + require 'fcntl.so' + rescue LoadError + omit('fcntl not included in the build') end + + assert_separately(['--disable-all'], <<~RUBY) + autoload :Fcntl, 'fcntl.so' + + assert_equal('fcntl.so', autoload?(:Fcntl)) + assert(Object.const_defined?(:Fcntl)) + assert_equal('constant', defined?(Fcntl), '[Bug #19115]') + RUBY + end + + def test_autoload_with_unqualified_file_name # [ruby-core:69206] + Object.send(:remove_const, :A) if Object.const_defined?(:A) + + lp = $LOAD_PATH.dup + lf = $LOADED_FEATURES.dup + + Dir.mktmpdir('autoload') { |tmpdir| + $LOAD_PATH << tmpdir + + Dir.chdir(tmpdir) do + eval <<-END + class ::Object + module A + autoload :C, 'test-ruby-core-69206' + end + end + END + + File.write("test-ruby-core-69206.rb", 'module A; class C; end; end') + assert_kind_of Class, ::A::C + end + } ensure - file.unlink + $LOAD_PATH.replace lp + $LOADED_FEATURES.replace lf + Object.send(:remove_const, :A) if Object.const_defined?(:A) + end + + def test_require_explicit + Tempfile.create(['autoload', '.rb']) {|file| + file.puts 'class Object; AutoloadTest = 1; end' + file.close + add_autoload(file.path) + begin + assert_nothing_raised do + assert(require file.path) + assert_equal(1, ::AutoloadTest) + end + ensure + remove_autoload_constant + end + } end def test_threaded_accessing_constant - file = Tempfile.open(['autoload', '.rb']) - file.puts 'sleep 0.5; class AutoloadTest; X = 1; end' - file.close - add_autoload(file.path) - begin - assert_nothing_raised do - t1 = Thread.new { ::AutoloadTest::X } - t2 = Thread.new { ::AutoloadTest::X } - [t1, t2].each(&:join) + # Suppress "warning: loading in progress, circular require considered harmful" + EnvUtil.default_warning { + Tempfile.create(['autoload', '.rb']) {|file| + file.puts 'sleep 0.5; class AutoloadTest; X = 1; end' + file.close + add_autoload(file.path) + begin + assert_nothing_raised do + t1 = Thread.new { ::AutoloadTest::X } + t2 = Thread.new { ::AutoloadTest::X } + [t1, t2].each(&:join) + end + ensure + remove_autoload_constant + end + } + } + end + + def test_threaded_accessing_inner_constant + # Suppress "warning: loading in progress, circular require considered harmful" + EnvUtil.default_warning { + Tempfile.create(['autoload', '.rb']) {|file| + file.puts 'class AutoloadTest; sleep 0.5; X = 1; end' + file.close + add_autoload(file.path) + begin + assert_nothing_raised do + t1 = Thread.new { ::AutoloadTest::X } + t2 = Thread.new { ::AutoloadTest::X } + [t1, t2].each(&:join) + end + ensure + remove_autoload_constant + end + } + } + end + + def test_nameerror_when_autoload_did_not_define_the_constant + verbose_bak, $VERBOSE = $VERBOSE, nil + Tempfile.create(['autoload', '.rb']) {|file| + file.puts '' + file.close + add_autoload(file.path) + begin + assert_raise(NameError) do + AutoloadTest + end + ensure + remove_autoload_constant end - ensure - remove_autoload_constant - end + } ensure - file.unlink + $VERBOSE = verbose_bak end - def test_threaded_accessing_inner_constant - file = Tempfile.open(['autoload', '.rb']) - file.puts 'class AutoloadTest; sleep 0.5; X = 1; end' - file.close - add_autoload(file.path) - begin - assert_nothing_raised do - t1 = Thread.new { ::AutoloadTest::X } - t2 = Thread.new { ::AutoloadTest::X } - [t1, t2].each(&:join) + def test_override_autoload + Tempfile.create(['autoload', '.rb']) {|file| + file.puts '' + file.close + add_autoload(file.path) + begin + eval %q(class AutoloadTest; end) + assert_equal(Class, AutoloadTest.class) + ensure + remove_autoload_constant + end + } + end + + def test_override_while_autoloading + Tempfile.create(['autoload', '.rb']) {|file| + file.puts 'class AutoloadTest; sleep 0.5; end' + file.close + add_autoload(file.path) + begin + # while autoloading... + t = Thread.new { AutoloadTest } + sleep 0.1 + # override it + EnvUtil.suppress_warning { + eval %q(AutoloadTest = 1) + } + t.join + assert_equal(1, AutoloadTest) + ensure + remove_autoload_constant end - ensure - remove_autoload_constant + } + end + + def ruby_impl_require + Kernel.module_eval do + alias old_require require + end + Ruby::Box.module_eval do + alias old_require require end + called_with = [] + Kernel.send :define_method, :require do |path| + called_with << path + old_require path + end + Ruby::Box.send :define_method, :require do |path| + called_with << path + old_require path + end + yield called_with ensure - file.unlink + Kernel.module_eval do + undef require + alias require old_require + undef old_require + end + Ruby::Box.module_eval do + undef require + alias require old_require + undef old_require + end end - def test_nameerror_when_autoload_did_not_define_the_constant - file = Tempfile.open(['autoload', '.rb']) - file.puts '' - file.close - add_autoload(file.path) - begin - assert_raise(NameError) do - AutoloadTest + def test_require_implemented_in_ruby_is_called + ruby_impl_require do |called_with| + Tempfile.create(['autoload', '.rb']) {|file| + file.puts 'class AutoloadTest; end' + file.close + add_autoload(file.path) + begin + assert(Object::AutoloadTest) + ensure + remove_autoload_constant + end + # .dup to prevent breaking called_with by autoloading pp, etc + assert_equal [file.path], called_with.dup + } + end + end + + def test_autoload_while_autoloading + ruby_impl_require do |called_with| + Tempfile.create(%w(a .rb)) do |a| + Tempfile.create(%w(b .rb)) do |b| + a.puts "require '#{b.path}'; class AutoloadTest; end" + b.puts "class AutoloadTest; module B; end; end" + [a, b].each(&:flush) + add_autoload(a.path) + begin + assert(Object::AutoloadTest) + ensure + remove_autoload_constant + end + # .dup to prevent breaking called_with by autoloading pp, etc + assert_equal [a.path, b.path], called_with.dup + end end - ensure - remove_autoload_constant end - ensure - file.unlink end - def test_override_autoload - file = Tempfile.open(['autoload', '.rb']) - file.puts '' - file.close - add_autoload(file.path) - begin - eval %q(class AutoloadTest; end) - assert_equal(Class, AutoloadTest.class) - ensure - remove_autoload_constant + def test_bug_13526 + # Skip this on macOS 10.13 because of the following error: + # http://rubyci.s3.amazonaws.com/osx1013/ruby-master/log/20231011T014505Z.fail.html.gz + require "rbconfig" + + script = File.join(__dir__, 'bug-13526.rb') + assert_ruby_status([script], '', '[ruby-core:81016] [Bug #13526]') + end + + def test_autoload_private_constant + Dir.mktmpdir('autoload') do |tmpdir| + File.write(tmpdir+"/test-bug-14469.rb", "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class AutoloadTest + ZZZ = :ZZZ + private_constant :ZZZ + end + end; + assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-'end;'}") + bug = '[ruby-core:85516] [Bug #14469]' + begin; + class AutoloadTest + autoload :ZZZ, "test-bug-14469.rb" + end + assert_raise(NameError, bug) {AutoloadTest::ZZZ} + end; end - ensure - file.unlink end - def test_override_while_autoloading - file = Tempfile.open(['autoload', '.rb']) - file.puts 'class AutoloadTest; sleep 0.5; end' - file.close - add_autoload(file.path) - begin - # while autoloading... - t = Thread.new { AutoloadTest } - sleep 0.1 - # override it - EnvUtil.suppress_warning { - eval %q(AutoloadTest = 1) + def test_autoload_deprecate_constant + Dir.mktmpdir('autoload') do |tmpdir| + File.write(tmpdir+"/test-bug-14469.rb", "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class AutoloadTest + ZZZ = :ZZZ + deprecate_constant :ZZZ + end + end; + assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-'end;'}") + bug = '[ruby-core:85516] [Bug #14469]' + begin; + class AutoloadTest + autoload :ZZZ, "test-bug-14469.rb" + end + assert_warning(/ZZZ is deprecated/, bug) {AutoloadTest::ZZZ} + end; + end + end + + def test_autoload_private_constant_before_autoload + Dir.mktmpdir('autoload') do |tmpdir| + File.write(tmpdir+"/test-bug-11055.rb", "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class AutoloadTest + ZZZ = :ZZZ + end + end; + assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-'end;'}") + bug = '[Bug #11055]' + begin; + class AutoloadTest + autoload :ZZZ, "test-bug-11055.rb" + private_constant :ZZZ + ZZZ + end + assert_raise(NameError, bug) {AutoloadTest::ZZZ} + end; + assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-'end;'}") + bug = '[Bug #11055]' + begin; + class AutoloadTest + autoload :ZZZ, "test-bug-11055.rb" + private_constant :ZZZ + end + assert_raise(NameError, bug) {AutoloadTest::ZZZ} + end; + end + end + + def test_autoload_deprecate_constant_before_autoload + Dir.mktmpdir('autoload') do |tmpdir| + File.write(tmpdir+"/test-bug-11055.rb", "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class AutoloadTest + ZZZ = :ZZZ + end + end; + assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-'end;'}") + bug = '[Bug #11055]' + begin; + class AutoloadTest + autoload :ZZZ, "test-bug-11055.rb" + deprecate_constant :ZZZ + end + assert_warning(/ZZZ is deprecated/, bug) {class AutoloadTest; ZZZ; end} + assert_warning(/ZZZ is deprecated/, bug) {AutoloadTest::ZZZ} + end; + assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-'end;'}") + bug = '[Bug #11055]' + begin; + class AutoloadTest + autoload :ZZZ, "test-bug-11055.rb" + deprecate_constant :ZZZ + end + assert_warning(/ZZZ is deprecated/, bug) {AutoloadTest::ZZZ} + end; + end + end + + def test_autoload_fork + EnvUtil.default_warning do + Tempfile.create(['autoload', '.rb']) {|file| + file.puts 'sleep 0.3; class AutoloadTest; end' + file.close + add_autoload(file.path) + begin + thrs = [] + 3.times do + thrs << Thread.new { AutoloadTest && nil } + thrs << Thread.new { fork { AutoloadTest } } + end + thrs.each(&:join) + thrs.each do |th| + pid = th.value or next + _, status = Process.waitpid2(pid) + assert_predicate status, :success? + end + ensure + remove_autoload_constant + assert_nil $!, '[ruby-core:86410] [Bug #14634]' + end } - t.join - assert_equal(1, AutoloadTest) - ensure - remove_autoload_constant end - ensure - file.unlink + end if Process.respond_to?(:fork) + + def test_autoload_same_file + Dir.mktmpdir('autoload') do |tmpdir| + File.write("#{tmpdir}/test-bug-14742.rb", "#{<<~'begin;'}\n#{<<~'end;'}") + begin; + module Foo; end + module Bar; end + end; + 3.times do # timing-dependent, needs a few times to hit [Bug #14742] + assert_separately(%W[-I #{tmpdir}], "#{<<-'begin;'}\n#{<<-'end;'}") + begin; + autoload :Foo, 'test-bug-14742' + autoload :Bar, 'test-bug-14742' + t1 = Thread.new do Foo end + t2 = Thread.new do Bar end + t1.join + t2.join + bug = '[ruby-core:86935] [Bug #14742]' + assert_instance_of Module, t1.value, bug + assert_instance_of Module, t2.value, bug + end; + end + end + end + + def test_autoload_same_file_with_raise + Dir.mktmpdir('autoload') do |tmpdir| + File.write("#{tmpdir}/test-bug-16177.rb", "#{<<~'begin;'}\n#{<<~'end;'}") + begin; + raise '[ruby-core:95055] [Bug #16177]' + end; + assert_raise(RuntimeError, '[ruby-core:95055] [Bug #16177]') do + assert_separately(%W[-I #{tmpdir}], "#{<<-'begin;'}\n#{<<-'end;'}") + begin; + autoload :Foo, 'test-bug-16177' + autoload :Bar, 'test-bug-16177' + t1 = Thread.new do Foo end + t2 = Thread.new do Bar end + t1.join + t2.join + end; + end + end + end + + def test_source_location + bug = "Bug16764" + Dir.mktmpdir('autoload') do |tmpdir| + path = "#{tmpdir}/test-#{bug}.rb" + File.write(path, "C::#{bug} = __FILE__\n") + assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + class C; end + C.autoload(:Bug16764, #{path.dump}) + assert_equal [__FILE__, __LINE__-1], C.const_source_location(#{bug.dump}) + assert_equal #{path.dump}, C.const_get(#{bug.dump}) + assert_equal [#{path.dump}, 1], C.const_source_location(#{bug.dump}) + end; + end + end + + def test_source_location_after_require + bug = "Bug18624" + Dir.mktmpdir('autoload') do |tmpdir| + path = "#{tmpdir}/test-#{bug}.rb" + File.write(path, "C::#{bug} = __FILE__\n") + assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + class C; end + C.autoload(:Bug18624, #{path.dump}) + require #{path.dump} + assert_equal [#{path.dump}, 1], C.const_source_location(#{bug.dump}) + assert_equal #{path.dump}, C.const_get(#{bug.dump}) + assert_equal [#{path.dump}, 1], C.const_source_location(#{bug.dump}) + end; + end + end + + def test_no_memory_leak + assert_no_memory_leak([], '', "#{<<~"begin;"}\n#{<<~'end;'}", 'many autoloads', timeout: 60) + begin; + 200000.times do |i| + m = Module.new + m.instance_eval do + autoload :Foo, 'x' + autoload :Bar, i.to_s + end + end + end; + end + + def test_autoload_after_failed_and_removed_from_loaded_features + Dir.mktmpdir('autoload') do |tmpdir| + autoload_path = File.join(tmpdir, "test-bug-15790.rb") + File.write(autoload_path, '') + + assert_separately(%W[-I #{tmpdir}], <<-RUBY) + $VERBOSE = nil + path = #{File.realpath(autoload_path).inspect} + autoload :X, path + assert_equal(path, Object.autoload?(:X)) + + assert_raise(NameError){X} + assert_nil(Object.autoload?(:X)) + assert_equal(false, Object.const_defined?(:X)) + + $LOADED_FEATURES.delete(path) + assert_equal(false, Object.const_defined?(:X)) + assert_nil(Object.autoload?(:X)) + + assert_raise(NameError){X} + assert_equal(false, Object.const_defined?(:X)) + assert_nil(Object.autoload?(:X)) + RUBY + end end def add_autoload(path) (@autoload_paths ||= []) << path - eval <<-END - class ::Object - autoload :AutoloadTest, #{path.dump} - end - END + ::Object.class_eval {autoload(:AutoloadTest, path)} end def remove_autoload_constant $".replace($" - @autoload_paths) - eval <<-END - class ::Object - remove_const(:AutoloadTest) + ::Object.class_eval {remove_const(:AutoloadTest)} if defined? Object::AutoloadTest + TestAutoload.class_eval {remove_const(:AutoloadTest)} if defined? TestAutoload::AutoloadTest + end + + def test_autoload_module_gc + Dir.mktmpdir('autoload') do |tmpdir| + autoload_path = File.join(tmpdir, "autoload_module_gc.rb") + File.write(autoload_path, "X = 1; Y = 2;") + + x = Module.new + x.autoload :X, "./feature.rb" + + 1000.times do + y = Module.new + y.autoload :Y, "./feature.rb" end - END + + x = y = nil + + # Ensure the internal data structures are cleaned up correctly / don't crash: + GC.start + end + end + + def test_autoload_parallel_race + Dir.mktmpdir('autoload') do |tmpdir| + autoload_path = File.join(tmpdir, "autoload_parallel_race.rb") + File.write(autoload_path, 'module Foo; end; module Bar; end') + + assert_ruby_status([], <<-RUBY, timeout: 100) + autoload_path = #{File.realpath(autoload_path).inspect} + + # This should work with no errors or failures. + 1000.times do + autoload :Foo, autoload_path + autoload :Bar, autoload_path + + t1 = Thread.new {Foo} + t2 = Thread.new {Bar} + + t1.join + GC.start # force GC. + t2.join + + Object.send(:remove_const, :Foo) + Object.send(:remove_const, :Bar) + + $LOADED_FEATURES.delete(autoload_path) + end + RUBY + end + end + + def test_autoload_parent_namespace + Dir.mktmpdir('autoload') do |tmpdir| + autoload_path = File.join(tmpdir, "some_const.rb") + File.write(autoload_path, 'class SomeConst; end') + + assert_separately(%W[-I #{tmpdir}], <<-RUBY) + module SomeNamespace + autoload :SomeConst, #{File.realpath(autoload_path).inspect} + assert_warning(%r{/some_const\.rb to define SomeNamespace::SomeConst but it didn't}) do + assert_not_nil SomeConst + end + end + RUBY + end + end + + private + + def assert_separately(*args, **kwargs) + super(*args, timeout: 60, **kwargs) + end + + def assert_ruby_status(*args, **kwargs) + super(*args, timeout: 60, **kwargs) end end |
