mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
078db98da4
related: [Bugs #18813] and eca31d24d6
580 lines
16 KiB
Ruby
580 lines
16 KiB
Ruby
# frozen_string_literal: false
|
|
require 'test/unit'
|
|
require 'tempfile'
|
|
|
|
class TestAutoload < Test::Unit::TestCase
|
|
def test_autoload_so
|
|
# Date is always available, unless excluded intentionally.
|
|
assert_in_out_err([], <<-INPUT, [], [])
|
|
autoload :Date, "date"
|
|
begin Date; rescue LoadError; end
|
|
INPUT
|
|
end
|
|
|
|
def test_non_realpath_in_loadpath
|
|
require 'tmpdir'
|
|
tmpdir = Dir.mktmpdir('autoload')
|
|
tmpdirs = [tmpdir]
|
|
tmpdirs.unshift(tmpdir + '/foo')
|
|
Dir.mkdir(tmpdirs[0])
|
|
tmpfiles = [tmpdir + '/foo.rb', tmpdir + '/foo/bar.rb']
|
|
open(tmpfiles[0] , 'w') do |f|
|
|
f.puts <<-INPUT
|
|
$:.unshift(File.expand_path('..', __FILE__)+'/./foo')
|
|
module Foo
|
|
autoload :Bar, 'bar'
|
|
end
|
|
p Foo::Bar
|
|
INPUT
|
|
end
|
|
open(tmpfiles[1], 'w') do |f|
|
|
f.puts 'class Foo::Bar; end'
|
|
end
|
|
assert_in_out_err([tmpfiles[0]], "", ["Foo::Bar"], [])
|
|
ensure
|
|
File.unlink(*tmpfiles) rescue nil if tmpfiles
|
|
tmpdirs.each {|dir| Dir.rmdir(dir)}
|
|
end
|
|
|
|
def test_autoload_p
|
|
bug4565 = '[ruby-core:35679]'
|
|
|
|
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
|
|
end
|
|
assert_equal(true, a.const_defined?(:X))
|
|
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_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
|
|
$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
|
|
# 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
|
|
$VERBOSE = verbose_bak
|
|
end
|
|
|
|
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
|
|
}
|
|
end
|
|
|
|
def ruby_impl_require
|
|
Kernel.module_eval do
|
|
alias old_require require
|
|
end
|
|
called_with = []
|
|
Kernel.send :define_method, :require do |path|
|
|
called_with << path
|
|
old_require path
|
|
end
|
|
yield called_with
|
|
ensure
|
|
Kernel.module_eval do
|
|
undef require
|
|
alias require old_require
|
|
undef old_require
|
|
end
|
|
end
|
|
|
|
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
|
|
assert_equal [file.path], called_with
|
|
}
|
|
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
|
|
assert_equal [a.path, b.path], called_with
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_bug_13526
|
|
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
|
|
end
|
|
|
|
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
|
|
}
|
|
end
|
|
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
|
|
::Object.class_eval {autoload(:AutoloadTest, path)}
|
|
end
|
|
|
|
def remove_autoload_constant
|
|
$".replace($" - @autoload_paths)
|
|
::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
|
|
|
|
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_separately([], <<-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
|
|
end
|