diff options
Diffstat (limited to 'spec/ruby/core/module/autoload_spec.rb')
| -rw-r--r-- | spec/ruby/core/module/autoload_spec.rb | 424 |
1 files changed, 312 insertions, 112 deletions
diff --git a/spec/ruby/core/module/autoload_spec.rb b/spec/ruby/core/module/autoload_spec.rb index 224d5fd57b..057237a92f 100644 --- a/spec/ruby/core/module/autoload_spec.rb +++ b/spec/ruby/core/module/autoload_spec.rb @@ -1,6 +1,6 @@ require_relative '../../spec_helper' +require_relative '../../fixtures/code_loading' require_relative 'fixtures/classes' -require 'thread' describe "Module#autoload?" do it "returns the name of the file that will be autoloaded" do @@ -9,24 +9,43 @@ describe "Module#autoload?" do end it "returns nil if no file has been registered for a constant" do - ModuleSpecs::Autoload.autoload?(:Manualload).should be_nil + ModuleSpecs::Autoload.autoload?(:Manualload).should == nil + end + + it "returns the name of the file that will be autoloaded if an ancestor defined that autoload" do + ModuleSpecs::Autoload::Parent.autoload :AnotherAutoload, "another_autoload.rb" + ModuleSpecs::Autoload::Child.autoload?(:AnotherAutoload).should == "another_autoload.rb" + end + + it "returns nil if an ancestor defined that autoload but recursion is disabled" do + ModuleSpecs::Autoload::Parent.autoload :InheritedAutoload, "inherited_autoload.rb" + ModuleSpecs::Autoload::Child.autoload?(:InheritedAutoload, false).should == nil + end + + it "returns the name of the file that will be loaded if recursion is disabled but the autoload is defined on the class itself" do + ModuleSpecs::Autoload::Child.autoload :ChildAutoload, "child_autoload.rb" + ModuleSpecs::Autoload::Child.autoload?(:ChildAutoload, false).should == "child_autoload.rb" end end describe "Module#autoload" do before :all do @non_existent = fixture __FILE__, "no_autoload.rb" + CodeLoadingSpecs.preload_rubygems end before :each do @loaded_features = $".dup - @frozen_module = Module.new.freeze ScratchPad.clear + @remove = [] end after :each do $".replace @loaded_features + @remove.each { |const| + ModuleSpecs::Autoload.send :remove_const, const + } end it "registers a file to load the first time the named constant is accessed" do @@ -36,21 +55,34 @@ describe "Module#autoload" do it "sets the autoload constant in the constants table" do ModuleSpecs::Autoload.autoload :B, @non_existent - ModuleSpecs::Autoload.should have_constant(:B) + ModuleSpecs::Autoload.should.const_defined?(:B, false) + end + + it "can be overridden with a second autoload on the same constant" do + ModuleSpecs::Autoload.autoload :Overridden, @non_existent + @remove << :Overridden + ModuleSpecs::Autoload.autoload?(:Overridden).should == @non_existent + + path = fixture(__FILE__, "autoload_overridden.rb") + ModuleSpecs::Autoload.autoload :Overridden, path + ModuleSpecs::Autoload.autoload?(:Overridden).should == path + + ModuleSpecs::Autoload::Overridden.should == :overridden end it "loads the registered constant when it is accessed" do - ModuleSpecs::Autoload.should_not have_constant(:X) + ModuleSpecs::Autoload.should_not.const_defined?(:X) ModuleSpecs::Autoload.autoload :X, fixture(__FILE__, "autoload_x.rb") + @remove << :X ModuleSpecs::Autoload::X.should == :x - ModuleSpecs::Autoload.send(:remove_const, :X) end it "loads the registered constant into a dynamically created class" do cls = Class.new { autoload :C, fixture(__FILE__, "autoload_c.rb") } ModuleSpecs::Autoload::DynClass = cls + @remove << :DynClass - ScratchPad.recorded.should be_nil + ScratchPad.recorded.should == nil ModuleSpecs::Autoload::DynClass::C.new.loaded.should == :dynclass_c ScratchPad.recorded.should == :loaded end @@ -58,8 +90,9 @@ describe "Module#autoload" do it "loads the registered constant into a dynamically created module" do mod = Module.new { autoload :D, fixture(__FILE__, "autoload_d.rb") } ModuleSpecs::Autoload::DynModule = mod + @remove << :DynModule - ScratchPad.recorded.should be_nil + ScratchPad.recorded.should == nil ModuleSpecs::Autoload::DynModule::D.new.loaded.should == :dynmodule_d ScratchPad.recorded.should == :loaded end @@ -95,9 +128,10 @@ describe "Module#autoload" do it "does not load the file when the constant is already set" do ModuleSpecs::Autoload.autoload :I, fixture(__FILE__, "autoload_i.rb") + @remove << :I ModuleSpecs::Autoload.const_set :I, 3 ModuleSpecs::Autoload::I.should == 3 - ScratchPad.recorded.should be_nil + ScratchPad.recorded.should == nil end it "loads a file with .rb extension when passed the name without the extension" do @@ -110,20 +144,21 @@ describe "Module#autoload" do main = TOPLEVEL_BINDING.eval("self") main.should_receive(:require).with("module_autoload_not_exist.rb") # The constant won't be defined since require is mocked to do nothing - -> { ModuleSpecs::Autoload::ModuleAutoloadCallsRequire }.should raise_error(NameError) + -> { ModuleSpecs::Autoload::ModuleAutoloadCallsRequire }.should.raise(NameError) end it "does not load the file if the file is manually required" do filename = fixture(__FILE__, "autoload_k.rb") ModuleSpecs::Autoload.autoload :KHash, filename + @remove << :KHash require filename ScratchPad.recorded.should == :loaded ScratchPad.clear - ModuleSpecs::Autoload::KHash.should be_kind_of(Class) + ModuleSpecs::Autoload::KHash.should.is_a?(Class) ModuleSpecs::Autoload::KHash::K.should == :autoload_k - ScratchPad.recorded.should be_nil + ScratchPad.recorded.should == nil end it "ignores the autoload request if the file is already loaded" do @@ -135,8 +170,8 @@ describe "Module#autoload" do ScratchPad.clear ModuleSpecs::Autoload.autoload :S, filename - ModuleSpecs::Autoload.autoload?(:S).should be_nil - ModuleSpecs::Autoload.send(:remove_const, :S) + @remove << :S + ModuleSpecs::Autoload.autoload?(:S).should == nil end it "retains the autoload even if the request to require fails" do @@ -145,9 +180,9 @@ describe "Module#autoload" do ModuleSpecs::Autoload.autoload :NotThere, filename ModuleSpecs::Autoload.autoload?(:NotThere).should == filename - lambda { + -> { require filename - }.should raise_error(LoadError) + }.should.raise(LoadError) ModuleSpecs::Autoload.autoload?(:NotThere).should == filename end @@ -166,6 +201,13 @@ describe "Module#autoload" do ModuleSpecs::Autoload.use_ex1.should == :good end + it "considers an autoload constant as loaded when autoload is called for/from the current file" do + filename = fixture(__FILE__, "autoload_during_require_current_file.rb") + require filename + + ScratchPad.recorded.should == nil + end + describe "interacting with defined?" do it "does not load the file when referring to the constant in defined?" do module ModuleSpecs::Autoload::Dog @@ -173,20 +215,19 @@ describe "Module#autoload" do end defined?(ModuleSpecs::Autoload::Dog::R).should == "constant" - ScratchPad.recorded.should be_nil + ScratchPad.recorded.should == nil - ModuleSpecs::Autoload::Dog.should have_constant(:R) + ModuleSpecs::Autoload::Dog.should.const_defined?(:R, false) end it "loads an autoloaded parent when referencing a nested constant" do module ModuleSpecs::Autoload autoload :GoodParent, fixture(__FILE__, "autoload_nested.rb") end + @remove << :GoodParent defined?(ModuleSpecs::Autoload::GoodParent::Nested).should == 'constant' ScratchPad.recorded.should == :loaded - - ModuleSpecs::Autoload.send(:remove_const, :GoodParent) end it "returns nil when it fails to load an autoloaded parent when referencing a nested constant" do @@ -194,16 +235,17 @@ describe "Module#autoload" do autoload :BadParent, fixture(__FILE__, "autoload_exception.rb") end - defined?(ModuleSpecs::Autoload::BadParent::Nested).should be_nil + defined?(ModuleSpecs::Autoload::BadParent::Nested).should == nil ScratchPad.recorded.should == :exception end end - describe "the autoload is removed when the same file is required directly without autoload" do + describe "the autoload is triggered when the same file is required directly" do before :each do module ModuleSpecs::Autoload autoload :RequiredDirectly, fixture(__FILE__, "autoload_required_directly.rb") end + @remove << :RequiredDirectly @path = fixture(__FILE__, "autoload_required_directly.rb") @check = -> { [ @@ -214,10 +256,6 @@ describe "Module#autoload" do ScratchPad.record @check end - after :each do - ModuleSpecs::Autoload.send(:remove_const, :RequiredDirectly) - end - it "with a full path" do @check.call.should == ["constant", @path] require @path @@ -242,7 +280,7 @@ describe "Module#autoload" do nested_require = -> { result = nil ScratchPad.record -> { - result = [@check.call, Thread.new { @check.call }.value] + result = @check.call } require nested result @@ -251,90 +289,181 @@ describe "Module#autoload" do @check.call.should == ["constant", @path] require @path - cur, other = ScratchPad.recorded - cur.should == [nil, nil] - other.should == [nil, nil] + ScratchPad.recorded.should == [nil, nil] @check.call.should == ["constant", nil] end + + it "does not raise an error if the autoload constant was not defined" do + module ModuleSpecs::Autoload + autoload :RequiredDirectlyNoConstant, fixture(__FILE__, "autoload_required_directly_no_constant.rb") + end + @path = fixture(__FILE__, "autoload_required_directly_no_constant.rb") + @remove << :RequiredDirectlyNoConstant + @check = -> { + [ + defined?(ModuleSpecs::Autoload::RequiredDirectlyNoConstant), + ModuleSpecs::Autoload.constants(false).include?(:RequiredDirectlyNoConstant), + ModuleSpecs::Autoload.const_defined?(:RequiredDirectlyNoConstant), + ModuleSpecs::Autoload.autoload?(:RequiredDirectlyNoConstant) + ] + } + ScratchPad.record @check + @check.call.should == ["constant", true, true, @path] + $:.push File.dirname(@path) + begin + require "autoload_required_directly_no_constant.rb" + ensure + $:.pop + end + ScratchPad.recorded.should == [nil, true, false, nil] + @check.call.should == [nil, true, false, nil] + end end - describe "during the autoload before the constant is assigned" do + describe "after the autoload is triggered by require" do before :each do - @path = fixture(__FILE__, "autoload_during_autoload.rb") - ModuleSpecs::Autoload.autoload :DuringAutoload, @path - raise unless ModuleSpecs::Autoload.autoload?(:DuringAutoload) == @path + @path = tmp("autoload.rb") end after :each do - ModuleSpecs::Autoload.send(:remove_const, :DuringAutoload) + rm_r @path end - def check_before_during_thread_after(&check) - before = check.call - to_autoload_thread, from_autoload_thread = Queue.new, Queue.new - ScratchPad.record -> { - from_autoload_thread.push check.call - to_autoload_thread.pop - } - t = Thread.new { - in_loading_thread = from_autoload_thread.pop - in_other_thread = check.call - to_autoload_thread.push :done - [in_loading_thread, in_other_thread] - } - in_loading_thread, in_other_thread = nil - begin - ModuleSpecs::Autoload::DuringAutoload - ensure - in_loading_thread, in_other_thread = t.value - end - after = check.call - [before, in_loading_thread, in_other_thread, after] + it "the mapping feature to autoload is removed, and a new autoload with the same path is considered" do + ModuleSpecs::Autoload.autoload :RequireMapping1, @path + touch(@path) { |f| f.puts "ModuleSpecs::Autoload::RequireMapping1 = 1" } + ModuleSpecs::Autoload::RequireMapping1.should == 1 + + $LOADED_FEATURES.delete(@path) + ModuleSpecs::Autoload.autoload :RequireMapping2, @path[0...-3] + @remove << :RequireMapping2 + touch(@path) { |f| f.puts "ModuleSpecs::Autoload::RequireMapping2 = 2" } + ModuleSpecs::Autoload::RequireMapping2.should == 2 + end + end + + def check_before_during_thread_after(const, &check) + before = check.call + to_autoload_thread, from_autoload_thread = Queue.new, Queue.new + ScratchPad.record -> { + from_autoload_thread.push check.call + to_autoload_thread.pop + } + t = Thread.new { + in_loading_thread = from_autoload_thread.pop + in_other_thread = check.call + to_autoload_thread.push :done + [in_loading_thread, in_other_thread] + } + in_loading_thread, in_other_thread = nil + begin + ModuleSpecs::Autoload.const_get(const) + ensure + in_loading_thread, in_other_thread = t.value + end + after = check.call + [before, in_loading_thread, in_other_thread, after] + end + + describe "during the autoload before the constant is assigned" do + before :each do + @path = fixture(__FILE__, "autoload_during_autoload.rb") + ModuleSpecs::Autoload.autoload :DuringAutoload, @path + @remove << :DuringAutoload + raise unless ModuleSpecs::Autoload.autoload?(:DuringAutoload) == @path end it "returns nil in autoload thread and 'constant' otherwise for defined?" do - results = check_before_during_thread_after { + results = check_before_during_thread_after(:DuringAutoload) { defined?(ModuleSpecs::Autoload::DuringAutoload) } results.should == ['constant', nil, 'constant', 'constant'] end it "keeps the constant in Module#constants" do - results = check_before_during_thread_after { + results = check_before_during_thread_after(:DuringAutoload) { ModuleSpecs::Autoload.constants(false).include?(:DuringAutoload) } results.should == [true, true, true, true] end it "returns false in autoload thread and true otherwise for Module#const_defined?" do - results = check_before_during_thread_after { + results = check_before_during_thread_after(:DuringAutoload) { ModuleSpecs::Autoload.const_defined?(:DuringAutoload, false) } results.should == [true, false, true, true] end it "returns nil in autoload thread and returns the path in other threads for Module#autoload?" do - results = check_before_during_thread_after { + results = check_before_during_thread_after(:DuringAutoload) { ModuleSpecs::Autoload.autoload?(:DuringAutoload) } results.should == [@path, nil, @path, nil] end end + describe "during the autoload after the constant is assigned" do + before :each do + @path = fixture(__FILE__, "autoload_during_autoload_after_define.rb") + ModuleSpecs::Autoload.autoload :DuringAutoloadAfterDefine, @path + @autoload_location = [__FILE__, __LINE__ - 1] + @const_location = [@path, 2] + @remove << :DuringAutoloadAfterDefine + raise unless ModuleSpecs::Autoload.autoload?(:DuringAutoloadAfterDefine) == @path + end + + it "returns 'constant' in both threads" do + results = check_before_during_thread_after(:DuringAutoloadAfterDefine) { + defined?(ModuleSpecs::Autoload::DuringAutoloadAfterDefine) + } + results.should == ['constant', 'constant', 'constant', 'constant'] + end + + it "Module#constants include the autoloaded in both threads" do + results = check_before_during_thread_after(:DuringAutoloadAfterDefine) { + ModuleSpecs::Autoload.constants(false).include?(:DuringAutoloadAfterDefine) + } + results.should == [true, true, true, true] + end + + it "Module#const_defined? returns true in both threads" do + results = check_before_during_thread_after(:DuringAutoloadAfterDefine) { + ModuleSpecs::Autoload.const_defined?(:DuringAutoloadAfterDefine, false) + } + results.should == [true, true, true, true] + end + + it "returns nil in autoload thread and returns the path in other threads for Module#autoload?" do + results = check_before_during_thread_after(:DuringAutoloadAfterDefine) { + ModuleSpecs::Autoload.autoload?(:DuringAutoloadAfterDefine) + } + results.should == [@path, nil, @path, nil] + end + + ruby_bug("#20188", ""..."3.4") do + it "returns the real constant location in autoload thread and returns the autoload location in other threads for Module#const_source_location" do + results = check_before_during_thread_after(:DuringAutoloadAfterDefine) { + ModuleSpecs::Autoload.const_source_location(:DuringAutoloadAfterDefine) + } + results.should == [@autoload_location, @const_location, @autoload_location, @const_location] + end + end + end + it "does not remove the constant from Module#constants if load fails and keeps it as an autoload" do ModuleSpecs::Autoload.autoload :Fail, @non_existent ModuleSpecs::Autoload.const_defined?(:Fail).should == true - ModuleSpecs::Autoload.should have_constant(:Fail) + ModuleSpecs::Autoload.should.const_defined?(:Fail, false) ModuleSpecs::Autoload.autoload?(:Fail).should == @non_existent - lambda { ModuleSpecs::Autoload::Fail }.should raise_error(LoadError) + -> { ModuleSpecs::Autoload::Fail }.should.raise(LoadError) - ModuleSpecs::Autoload.should have_constant(:Fail) + ModuleSpecs::Autoload.should.const_defined?(:Fail, false) ModuleSpecs::Autoload.const_defined?(:Fail).should == true ModuleSpecs::Autoload.autoload?(:Fail).should == @non_existent - lambda { ModuleSpecs::Autoload::Fail }.should raise_error(LoadError) + -> { ModuleSpecs::Autoload::Fail }.should.raise(LoadError) end it "does not remove the constant from Module#constants if load raises a RuntimeError and keeps it as an autoload" do @@ -343,35 +472,35 @@ describe "Module#autoload" do ModuleSpecs::Autoload.autoload :Raise, path ModuleSpecs::Autoload.const_defined?(:Raise).should == true - ModuleSpecs::Autoload.should have_constant(:Raise) + ModuleSpecs::Autoload.should.const_defined?(:Raise, false) ModuleSpecs::Autoload.autoload?(:Raise).should == path - lambda { ModuleSpecs::Autoload::Raise }.should raise_error(RuntimeError) + -> { ModuleSpecs::Autoload::Raise }.should.raise(RuntimeError) ScratchPad.recorded.should == [:raise] - ModuleSpecs::Autoload.should have_constant(:Raise) + ModuleSpecs::Autoload.should.const_defined?(:Raise, false) ModuleSpecs::Autoload.const_defined?(:Raise).should == true ModuleSpecs::Autoload.autoload?(:Raise).should == path - lambda { ModuleSpecs::Autoload::Raise }.should raise_error(RuntimeError) + -> { ModuleSpecs::Autoload::Raise }.should.raise(RuntimeError) ScratchPad.recorded.should == [:raise, :raise] end - it "does not remove the constant from Module#constants if the loaded file does not define it, but leaves it as 'undefined'" do + it "removes the constant from Module#constants if the loaded file does not define it" do path = fixture(__FILE__, "autoload_o.rb") ScratchPad.record [] ModuleSpecs::Autoload.autoload :O, path ModuleSpecs::Autoload.const_defined?(:O).should == true - ModuleSpecs::Autoload.should have_constant(:O) + ModuleSpecs::Autoload.should.const_defined?(:O, false) ModuleSpecs::Autoload.autoload?(:O).should == path - lambda { ModuleSpecs::Autoload::O }.should raise_error(NameError) + -> { ModuleSpecs::Autoload::O }.should.raise(NameError) - ModuleSpecs::Autoload.should have_constant(:O) ModuleSpecs::Autoload.const_defined?(:O).should == false + ModuleSpecs::Autoload.should_not.const_defined?(:O) ModuleSpecs::Autoload.autoload?(:O).should == nil - -> { ModuleSpecs::Autoload.const_get(:O) }.should raise_error(NameError) + -> { ModuleSpecs::Autoload.const_get(:O) }.should.raise(NameError) end it "does not try to load the file again if the loaded file did not define the constant" do @@ -379,13 +508,13 @@ describe "Module#autoload" do ScratchPad.record [] ModuleSpecs::Autoload.autoload :NotDefinedByFile, path - -> { ModuleSpecs::Autoload::NotDefinedByFile }.should raise_error(NameError) + -> { ModuleSpecs::Autoload::NotDefinedByFile }.should.raise(NameError) ScratchPad.recorded.should == [:loaded] - -> { ModuleSpecs::Autoload::NotDefinedByFile }.should raise_error(NameError) + -> { ModuleSpecs::Autoload::NotDefinedByFile }.should.raise(NameError) ScratchPad.recorded.should == [:loaded] Thread.new { - -> { ModuleSpecs::Autoload::NotDefinedByFile }.should raise_error(NameError) + -> { ModuleSpecs::Autoload::NotDefinedByFile }.should.raise(NameError) }.join ScratchPad.recorded.should == [:loaded] end @@ -395,7 +524,7 @@ describe "Module#autoload" do autoload :R, fixture(__FILE__, "autoload.rb") defined?(R).should == 'constant' end - ModuleSpecs::Autoload::Q.should have_constant(:R) + ModuleSpecs::Autoload::Q.should.const_defined?(:R, false) end it "does not load the file when removing an autoload constant" do @@ -403,12 +532,13 @@ describe "Module#autoload" do autoload :R, fixture(__FILE__, "autoload.rb") remove_const :R end - ModuleSpecs::Autoload::Q.should_not have_constant(:R) + ModuleSpecs::Autoload::Q.should_not.const_defined?(:R) end it "does not load the file when accessing the constants table of the module" do ModuleSpecs::Autoload.autoload :P, @non_existent - ModuleSpecs::Autoload.const_defined?(:P).should be_true + ModuleSpecs::Autoload.const_defined?(:P).should == true + ModuleSpecs::Autoload.const_defined?("P").should == true end it "loads the file when opening a module that is the autoloaded constant" do @@ -419,6 +549,7 @@ describe "Module#autoload" do X = get_value end end + @remove << :U ModuleSpecs::Autoload::U::V::X.should == :autoload_uvx end @@ -442,8 +573,8 @@ describe "Module#autoload" do DeclaredAndDefinedInParent.should == :declared_and_defined_in_parent # The constant is really in Autoload, not Autoload::LexicalScope - self.should_not have_constant(:DeclaredAndDefinedInParent) - -> { const_get(:DeclaredAndDefinedInParent) }.should raise_error(NameError) + self.should_not.const_defined?(:DeclaredAndDefinedInParent) + -> { const_get(:DeclaredAndDefinedInParent) }.should.raise(NameError) end DeclaredAndDefinedInParent.should == :declared_and_defined_in_parent end @@ -466,14 +597,42 @@ describe "Module#autoload" do # Basically, the parent autoload constant remains in a "undefined" state self.autoload?(:DeclaredInParentDefinedInCurrent).should == nil const_defined?(:DeclaredInParentDefinedInCurrent).should == false - self.should have_constant(:DeclaredInParentDefinedInCurrent) - -> { DeclaredInParentDefinedInCurrent }.should raise_error(NameError) + -> { DeclaredInParentDefinedInCurrent }.should.raise(NameError) ModuleSpecs::Autoload::LexicalScope.send(:remove_const, :DeclaredInParentDefinedInCurrent) end end - it "and fails when finding the undefined autoload constant in the the current scope when declared in current and defined in parent" do + it "warns once in verbose mode if the constant was defined in a parent scope" do + ScratchPad.record -> { + ModuleSpecs::DeclaredInCurrentDefinedInParent = :declared_in_current_defined_in_parent + } + + module ModuleSpecs + module Autoload + autoload :DeclaredInCurrentDefinedInParent, fixture(__FILE__, "autoload_callback.rb") + self.autoload?(:DeclaredInCurrentDefinedInParent).should == fixture(__FILE__, "autoload_callback.rb") + const_defined?(:DeclaredInCurrentDefinedInParent).should == true + + -> { + DeclaredInCurrentDefinedInParent + }.should complain( + /Expected .*autoload_callback.rb to define ModuleSpecs::Autoload::DeclaredInCurrentDefinedInParent but it didn't/, + verbose: true, + ) + + -> { + DeclaredInCurrentDefinedInParent + }.should_not complain(/.*/, verbose: true) + self.autoload?(:DeclaredInCurrentDefinedInParent).should == nil + const_defined?(:DeclaredInCurrentDefinedInParent).should == false + ModuleSpecs.const_defined?(:DeclaredInCurrentDefinedInParent).should == true + end + end + end + + it "looks up in parent scope after failed autoload" do + @remove << :DeclaredInCurrentDefinedInParent module ModuleSpecs::Autoload ScratchPad.record -> { DeclaredInCurrentDefinedInParent = :declared_in_current_defined_in_parent @@ -481,12 +640,11 @@ describe "Module#autoload" do class LexicalScope autoload :DeclaredInCurrentDefinedInParent, fixture(__FILE__, "autoload_callback.rb") - -> { DeclaredInCurrentDefinedInParent }.should raise_error(NameError) + -> { DeclaredInCurrentDefinedInParent }.should_not.raise(NameError) # Basically, the autoload constant remains in a "undefined" state self.autoload?(:DeclaredInCurrentDefinedInParent).should == nil const_defined?(:DeclaredInCurrentDefinedInParent).should == false - self.should have_constant(:DeclaredInCurrentDefinedInParent) - -> { const_get(:DeclaredInCurrentDefinedInParent) }.should raise_error(NameError) + -> { const_get(:DeclaredInCurrentDefinedInParent) }.should.raise(NameError) end DeclaredInCurrentDefinedInParent.should == :declared_in_current_defined_in_parent @@ -494,6 +652,7 @@ describe "Module#autoload" do end it "in the included modules" do + @remove << :DefinedInIncludedModule module ModuleSpecs::Autoload ScratchPad.record -> { module DefinedInIncludedModule @@ -507,6 +666,7 @@ describe "Module#autoload" do end it "in the included modules of the superclass" do + @remove << :DefinedInSuperclassIncludedModule module ModuleSpecs::Autoload class LookupAfterAutoloadSuper end @@ -528,6 +688,7 @@ describe "Module#autoload" do end it "in the prepended modules" do + @remove << :DefinedInPrependedModule module ModuleSpecs::Autoload ScratchPad.record -> { module DefinedInPrependedModule @@ -553,8 +714,23 @@ describe "Module#autoload" do end end end - ModuleSpecs::Autoload.r.should be_kind_of(ModuleSpecs::Autoload::MetaScope) + ModuleSpecs::Autoload.r.should.is_a?(ModuleSpecs::Autoload::MetaScope) + end + end + + it "should trigger the autoload when using `private_constant`" do + @remove << :DynClass + module ModuleSpecs::Autoload + autoload :DynClass, fixture(__FILE__, "autoload_c.rb") + private_constant :DynClass + + ScratchPad.recorded.should == nil + + DynClass::C.new.loaded.should == :dynclass_c + ScratchPad.recorded.should == :loaded end + + -> { ModuleSpecs::Autoload::DynClass }.should.raise(NameError, /private constant/) end # [ruby-core:19127] [ruby-core:29941] @@ -567,10 +743,10 @@ describe "Module#autoload" do end end end + @remove << :W - ModuleSpecs::Autoload::W::Y.should be_kind_of(Class) + ModuleSpecs::Autoload::W::Y.should.is_a?(Class) ScratchPad.recorded.should == :loaded - ModuleSpecs::Autoload::W.send(:remove_const, :Y) end it "does not call #require a second time and does not warn if already loading the same feature with #require" do @@ -584,7 +760,28 @@ describe "Module#autoload" do -> { Kernel.require fixture(__FILE__, "autoload_during_require.rb") }.should_not complain(verbose: true) - ModuleSpecs::Autoload::AutoloadDuringRequire.should be_kind_of(Class) + ModuleSpecs::Autoload::AutoloadDuringRequire.should.is_a?(Class) + end + + it "does not call #require a second time and does not warn if feature sets and trigger autoload on itself" do + main = TOPLEVEL_BINDING.eval("self") + main.should_not_receive(:require) + + -> { + Kernel.require fixture(__FILE__, "autoload_self_during_require.rb") + }.should_not complain(verbose: true) + ModuleSpecs::Autoload::AutoloadSelfDuringRequire.should.is_a?(Class) + end + + it "handles multiple autoloads in the same file" do + $LOAD_PATH.unshift(File.expand_path('../fixtures/multi', __FILE__)) + begin + require 'foo/bar_baz' + ModuleSpecs::Autoload::Foo::Bar.should.is_a?(Class) + ModuleSpecs::Autoload::Foo::Baz.should.is_a?(Class) + ensure + $LOAD_PATH.shift + end end it "calls #to_path on non-string filenames" do @@ -594,26 +791,27 @@ describe "Module#autoload" do end it "raises an ArgumentError when an empty filename is given" do - lambda { ModuleSpecs.autoload :A, "" }.should raise_error(ArgumentError) + -> { ModuleSpecs.autoload :A, "" }.should.raise(ArgumentError) end it "raises a NameError when the constant name starts with a lower case letter" do - lambda { ModuleSpecs.autoload "a", @non_existent }.should raise_error(NameError) + -> { ModuleSpecs.autoload "a", @non_existent }.should.raise(NameError) end it "raises a NameError when the constant name starts with a number" do - lambda { ModuleSpecs.autoload "1two", @non_existent }.should raise_error(NameError) + -> { ModuleSpecs.autoload "1two", @non_existent }.should.raise(NameError) end it "raises a NameError when the constant name has a space in it" do - lambda { ModuleSpecs.autoload "a name", @non_existent }.should raise_error(NameError) + -> { ModuleSpecs.autoload "a name", @non_existent }.should.raise(NameError) end it "shares the autoload request across dup'ed copies of modules" do require fixture(__FILE__, "autoload_s.rb") + @remove << :S filename = fixture(__FILE__, "autoload_t.rb") mod1 = Module.new { autoload :T, filename } - lambda { + -> { ModuleSpecs::Autoload::S = mod1 }.should complain(/already initialized constant/) mod2 = mod1.dup @@ -622,7 +820,7 @@ describe "Module#autoload" do mod2.autoload?(:T).should == filename mod1::T.should == :autoload_t - lambda { mod2::T }.should raise_error(NameError) + -> { mod2::T }.should.raise(NameError) end it "raises a TypeError if opening a class with a different superclass than the class defined in the autoload file" do @@ -630,29 +828,30 @@ describe "Module#autoload" do class ModuleSpecs::Autoload::ZZ end - lambda do + -> do class ModuleSpecs::Autoload::Z < ModuleSpecs::Autoload::ZZ end - end.should raise_error(TypeError) + end.should.raise(TypeError) end it "raises a TypeError if not passed a String or object responding to #to_path for the filename" do name = mock("autoload_name.rb") - lambda { ModuleSpecs::Autoload.autoload :Str, name }.should raise_error(TypeError) + -> { ModuleSpecs::Autoload.autoload :Str, name }.should.raise(TypeError) end it "calls #to_path on non-String filename arguments" do name = mock("autoload_name.rb") name.should_receive(:to_path).and_return("autoload_name.rb") - lambda { ModuleSpecs::Autoload.autoload :Str, name }.should_not raise_error + -> { ModuleSpecs::Autoload.autoload :Str, name }.should_not.raise end describe "on a frozen module" do - it "raises a #{frozen_error_class} before setting the name" do - lambda { @frozen_module.autoload :Foo, @non_existent }.should raise_error(frozen_error_class) - @frozen_module.should_not have_constant(:Foo) + it "raises a FrozenError before setting the name" do + frozen_module = Module.new.freeze + -> { frozen_module.autoload :Foo, @non_existent }.should.raise(FrozenError) + frozen_module.should_not.const_defined?(:Foo) end end @@ -675,6 +874,7 @@ describe "Module#autoload" do describe "(concurrently)" do it "blocks a second thread while a first is doing the autoload" do ModuleSpecs::Autoload.autoload :Concur, fixture(__FILE__, "autoload_concur.rb") + @remove << :Concur start = false @@ -716,9 +916,7 @@ describe "Module#autoload" do t1_val.should == 1 t2_val.should == t1_val - t2_exc.should be_nil - - ModuleSpecs::Autoload.send(:remove_const, :Concur) + t2_exc.should == nil end # https://bugs.ruby-lang.org/issues/10892 @@ -750,7 +948,7 @@ describe "Module#autoload" do begin Object.const_get(mod_name).foo - rescue NoMethodError + rescue NameError, NoMethodError # rubocop:disable Lint/ShadowedException barrier.disable! break false end @@ -758,8 +956,8 @@ describe "Module#autoload" do end end - # check that no thread got a NoMethodError because of partially loaded module - threads.all? {|t| t.value}.should be_true + # check that no thread got a NameError or NoMethodError because of partially loaded module + threads.all? {|t| t.value}.should == true # check that the autoloaded file was evaled exactly once ScratchPad.recorded.get.should == mod_count @@ -767,6 +965,8 @@ describe "Module#autoload" do mod_names.each do |mod_name| Object.send(:remove_const, mod_name) end + ensure + threads.each(&:join) if threads end it "raises a NameError in each thread if the constant is not set" do @@ -790,7 +990,7 @@ describe "Module#autoload" do start = true threads.each { |t| - t.value.should be_an_instance_of(NameError) + t.value.should.instance_of?(NameError) } end @@ -815,7 +1015,7 @@ describe "Module#autoload" do start = true threads.each { |t| - t.value.should be_an_instance_of(LoadError) + t.value.should.instance_of?(LoadError) } end end |
