diff options
author | eregon <eregon@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2018-08-03 16:19:40 +0000 |
---|---|---|
committer | eregon <eregon@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2018-08-03 16:19:40 +0000 |
commit | b53cf149ad8d7c46572e4567ca949b4f82ebb22c (patch) | |
tree | ee5032bcb38573dade8ba2c46acbcc0d5f3ddfe3 /spec/ruby/core | |
parent | aeeaadaad08038217440c1e9e7c5ca126d7dc633 (diff) | |
download | ruby-b53cf149ad8d7c46572e4567ca949b4f82ebb22c.tar.gz |
Update to ruby/spec@9be7c7e
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@64180 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'spec/ruby/core')
35 files changed, 709 insertions, 93 deletions
diff --git a/spec/ruby/core/array/reject_spec.rb b/spec/ruby/core/array/reject_spec.rb index 77835ef5cd..e6e5e851b6 100644 --- a/spec/ruby/core/array/reject_spec.rb +++ b/spec/ruby/core/array/reject_spec.rb @@ -111,6 +111,31 @@ describe "Array#reject!" do lambda { ArraySpecs.empty_frozen_array.reject! {} }.should raise_error(frozen_error_class) end + it "does not truncate the array is the block raises an exception" do + a = [1, 2, 3] + begin + a.reject! { raise StandardError, 'Oops' } + rescue + end + + a.should == [1, 2, 3] + end + + ruby_version_is "2.4" do + it "only removes elements for which the block returns true, keeping the element which raised an error." do + a = [1, 2, 3, 4] + begin + a.reject! do |x| + return true if x == 2 + raise raise StandardError, 'Oops' if x == 3 + end + rescue + end + + a.should == [1, 3, 4] + end + end + it_behaves_like :enumeratorize, :reject! it_behaves_like :enumeratorized_with_origin_size, :reject!, [1,2,3] it_behaves_like :delete_if, :reject! diff --git a/spec/ruby/core/binding/eval_spec.rb b/spec/ruby/core/binding/eval_spec.rb index 9a397757e9..c0264192b8 100644 --- a/spec/ruby/core/binding/eval_spec.rb +++ b/spec/ruby/core/binding/eval_spec.rb @@ -23,6 +23,54 @@ describe "Binding#eval" do bind2.local_variables.should == [] end + it "inherits __LINE__ from the enclosing scope" do + obj = BindingSpecs::Demo.new(1) + bind = obj.get_binding + bind.eval("__LINE__").should == obj.get_line_of_binding + end + + it "preserves __LINE__ across multiple calls to eval" do + obj = BindingSpecs::Demo.new(1) + bind = obj.get_binding + bind.eval("__LINE__").should == obj.get_line_of_binding + bind.eval("__LINE__").should == obj.get_line_of_binding + end + + it "increments __LINE__ on each line of a multiline eval" do + obj = BindingSpecs::Demo.new(1) + bind = obj.get_binding + bind.eval("#foo\n__LINE__").should == obj.get_line_of_binding + 1 + end + + it "inherits __LINE__ from the enclosing scope even if the Binding is created with #send" do + obj = BindingSpecs::Demo.new(1) + bind, line = obj.get_binding_with_send_and_line + bind.eval("__LINE__").should == line + end + + it "starts with a __LINE__ of 1 if a filename is passed" do + bind = BindingSpecs::Demo.new(1).get_binding + bind.eval("__LINE__", "(test)").should == 1 + bind.eval("#foo\n__LINE__", "(test)").should == 2 + end + + it "starts with a __LINE__ from the third argument if passed" do + bind = BindingSpecs::Demo.new(1).get_binding + bind.eval("__LINE__", "(test)", 88).should == 88 + bind.eval("#foo\n__LINE__", "(test)", 88).should == 89 + end + + it "inherits __FILE__ from the enclosing scope" do + obj = BindingSpecs::Demo.new(1) + bind = obj.get_binding + bind.eval("__FILE__").should == obj.get_file_of_binding + end + + it "uses the __FILE__ that is passed in" do + bind = BindingSpecs::Demo.new(1).get_binding + bind.eval("__FILE__", "(test)").should == "(test)" + end + describe "with a file given" do it "does not store the filename permanently" do obj = BindingSpecs::Demo.new(1) @@ -33,5 +81,15 @@ describe "Binding#eval" do end end - it "needs to be reviewed for spec completeness" + it "with __method__ returns the method where the Binding was created" do + obj = BindingSpecs::Demo.new(1) + bind, meth = obj.get_binding_and_method + bind.eval("__method__").should == meth + end + + it "with __method__ returns the method where the Binding was created, ignoring #send" do + obj = BindingSpecs::Demo.new(1) + bind, meth = obj.get_binding_with_send_and_method + bind.eval("__method__").should == meth + end end diff --git a/spec/ruby/core/binding/fixtures/classes.rb b/spec/ruby/core/binding/fixtures/classes.rb index 05ca2479ba..43e32cacf6 100644 --- a/spec/ruby/core/binding/fixtures/classes.rb +++ b/spec/ruby/core/binding/fixtures/classes.rb @@ -25,6 +25,18 @@ module BindingSpecs __FILE__ end + def get_binding_with_send_and_line + [send(:binding), __LINE__] + end + + def get_binding_and_method + [binding, :get_binding_and_method] + end + + def get_binding_with_send_and_method + [send(:binding), :get_binding_with_send_and_method] + end + def get_empty_binding binding end diff --git a/spec/ruby/core/binding/location_spec.rb b/spec/ruby/core/binding/location_spec.rb deleted file mode 100644 index 2ae0c5e9f1..0000000000 --- a/spec/ruby/core/binding/location_spec.rb +++ /dev/null @@ -1,46 +0,0 @@ -require_relative '../../spec_helper' -require_relative 'fixtures/classes' - -describe "Binding#eval" do - it "inherits __LINE__ from the enclosing scope" do - obj = BindingSpecs::Demo.new(1) - bind = obj.get_binding - bind.eval("__LINE__").should == obj.get_line_of_binding - end - - it "preserves __LINE__ across multiple calls to eval" do - obj = BindingSpecs::Demo.new(1) - bind = obj.get_binding - bind.eval("__LINE__").should == obj.get_line_of_binding - bind.eval("__LINE__").should == obj.get_line_of_binding - end - - it "increments __LINE__ on each line of a multiline eval" do - obj = BindingSpecs::Demo.new(1) - bind = obj.get_binding - bind.eval("#foo\n__LINE__").should == obj.get_line_of_binding + 1 - end - - it "starts with a __LINE__ of 1 if a filename is passed" do - bind = BindingSpecs::Demo.new(1).get_binding - bind.eval("__LINE__", "(test)").should == 1 - bind.eval("#foo\n__LINE__", "(test)").should == 2 - end - - it "starts with a __LINE__ from the third argument if passed" do - bind = BindingSpecs::Demo.new(1).get_binding - bind.eval("__LINE__", "(test)", 88).should == 88 - bind.eval("#foo\n__LINE__", "(test)", 88).should == 89 - end - - it "inherits __FILE__ from the enclosing scope" do - obj = BindingSpecs::Demo.new(1) - bind = obj.get_binding - bind.eval("__FILE__").should == obj.get_file_of_binding - end - - it "uses the __FILE__ that is passed in" do - bind = BindingSpecs::Demo.new(1).get_binding - bind.eval("__FILE__", "(test)").should == "(test)" - end -end diff --git a/spec/ruby/core/enumerable/sort_by_spec.rb b/spec/ruby/core/enumerable/sort_by_spec.rb index 46e1135412..8fdd923fb4 100644 --- a/spec/ruby/core/enumerable/sort_by_spec.rb +++ b/spec/ruby/core/enumerable/sort_by_spec.rb @@ -32,5 +32,12 @@ describe "Enumerable#sort_by" do b.sort_by{ |x| -x }.should == [3, 2, 1] end + it "calls #each to iterate over the elements to be sorted" do + b = EnumerableSpecs::Numerous.new( 1, 2, 3 ) + b.should_receive(:each).once.and_yield(1).and_yield(2).and_yield(3) + b.should_not_receive :map + b.sort_by { |x| -x }.should == [3, 2, 1] + end + it_behaves_like :enumerable_enumeratorized_with_origin_size, :sort_by end diff --git a/spec/ruby/core/io/read_spec.rb b/spec/ruby/core/io/read_spec.rb index dffa79f10e..3bb581f430 100644 --- a/spec/ruby/core/io/read_spec.rb +++ b/spec/ruby/core/io/read_spec.rb @@ -27,6 +27,10 @@ describe "IO.read" do IO.read(@fname, {}).should == @contents end + it "accepts a length, and empty options Hash" do + IO.read(@fname, 3, {}).should == @contents[0, 3] + end + it "accepts a length, offset, and empty options Hash" do IO.read(@fname, 3, 0, {}).should == @contents[0, 3] end diff --git a/spec/ruby/core/io/shared/write.rb b/spec/ruby/core/io/shared/write.rb index bca96da81c..140eeb04ab 100644 --- a/spec/ruby/core/io/shared/write.rb +++ b/spec/ruby/core/io/shared/write.rb @@ -85,9 +85,9 @@ describe :io_write, shared: true do @r.read.should == "foo" end - it "raises Errno::EPIPE if the read end is closed" do + it "raises Errno::EPIPE if the read end is closed and does not die from SIGPIPE" do @r.close - -> { @w.send(@method, "foo") }.should raise_error(Errno::EPIPE, "Broken pipe") + -> { @w.send(@method, "foo") }.should raise_error(Errno::EPIPE, /Broken pipe/) end end end diff --git a/spec/ruby/core/kernel/__dir___spec.rb b/spec/ruby/core/kernel/__dir___spec.rb index 25df92a15d..3c34277277 100644 --- a/spec/ruby/core/kernel/__dir___spec.rb +++ b/spec/ruby/core/kernel/__dir___spec.rb @@ -5,6 +5,13 @@ describe "Kernel#__dir__" do __dir__.should == File.realpath(File.dirname(__FILE__)) end + context "when used in eval with a given filename" do + it "returns File.dirname(filename)" do + eval("__dir__", nil, "foo.rb").should == "." + eval("__dir__", nil, "foo/bar.rb").should == "foo" + end + end + context "when used in eval with top level binding" do it "returns the real name of the directory containing the currently-executing file" do eval("__dir__", binding).should == File.realpath(File.dirname(__FILE__)) diff --git a/spec/ruby/core/kernel/autoload_spec.rb b/spec/ruby/core/kernel/autoload_spec.rb index c2573ffac6..5fa8fa92b3 100644 --- a/spec/ruby/core/kernel/autoload_spec.rb +++ b/spec/ruby/core/kernel/autoload_spec.rb @@ -7,7 +7,7 @@ require_relative 'fixtures/classes' autoload :KSAutoloadA, "autoload_a.rb" autoload :KSAutoloadB, fixture(__FILE__, "autoload_b.rb") -autoload :KSAutoloadC, fixture(__FILE__, "autoload_c.rb") +autoload :KSAutoloadCallsRequire, "main_autoload_not_exist.rb" def check_autoload(const) autoload? const @@ -42,10 +42,11 @@ describe "Kernel#autoload" do KSAutoloadB.loaded.should == :ksautoload_b end - it "does not call Kernel.require or Kernel.load to load the file" do - Kernel.should_not_receive(:require) - Kernel.should_not_receive(:load) - KSAutoloadC.loaded.should == :ksautoload_c + it "calls main.require(path) to load the file" do + main = TOPLEVEL_BINDING.eval("self") + main.should_receive(:require).with("main_autoload_not_exist.rb") + # The constant won't be defined since require is mocked to do nothing + -> { KSAutoloadCallsRequire }.should raise_error(NameError) end it "can autoload in instance_eval" do diff --git a/spec/ruby/core/kernel/fixtures/autoload_c.rb b/spec/ruby/core/kernel/fixtures/autoload_c.rb deleted file mode 100644 index 4569b23669..0000000000 --- a/spec/ruby/core/kernel/fixtures/autoload_c.rb +++ /dev/null @@ -1,5 +0,0 @@ -module KSAutoloadC - def self.loaded - :ksautoload_c - end -end diff --git a/spec/ruby/core/kernel/require_relative_spec.rb b/spec/ruby/core/kernel/require_relative_spec.rb index 4a2ef87357..a16e7164e6 100644 --- a/spec/ruby/core/kernel/require_relative_spec.rb +++ b/spec/ruby/core/kernel/require_relative_spec.rb @@ -199,6 +199,46 @@ describe "Kernel#require_relative with a relative path" do $LOADED_FEATURES.should include(@abs_path) end + platform_is_not :windows do + describe "with symlinks" do + before :each do + @symlink_to_code_dir = tmp("codesymlink") + File.symlink(CODE_LOADING_DIR, @symlink_to_code_dir) + @symlink_basename = File.basename(@symlink_to_code_dir) + @requiring_file = tmp("requiring") + end + + after :each do + rm_r @symlink_to_code_dir, @requiring_file + end + + it "does not canonicalize the path and stores a path with symlinks" do + symlink_path = "#{@symlink_basename}/load_fixture.rb" + absolute_path = "#{tmp("")}#{symlink_path}" + canonical_path = "#{CODE_LOADING_DIR}/load_fixture.rb" + touch(@requiring_file) { |f| + f.puts "require_relative #{symlink_path.inspect}" + } + load(@requiring_file) + ScratchPad.recorded.should == [:loaded] + + features = $LOADED_FEATURES.select { |path| path.end_with?('load_fixture.rb') } + features.should include(absolute_path) + features.should_not include(canonical_path) + end + + it "stores the same path that __FILE__ returns in the required file" do + symlink_path = "#{@symlink_basename}/load_fixture_and__FILE__.rb" + touch(@requiring_file) { |f| + f.puts "require_relative #{symlink_path.inspect}" + } + load(@requiring_file) + loaded_feature = $LOADED_FEATURES.last + ScratchPad.recorded.should == [loaded_feature] + end + end + end + it "does not store the path if the load fails" do saved_loaded_features = $LOADED_FEATURES.dup lambda { require_relative("#{@dir}/raise_fixture.rb") }.should raise_error(RuntimeError) diff --git a/spec/ruby/core/kernel/require_spec.rb b/spec/ruby/core/kernel/require_spec.rb index 362acf8003..dc3da4b7e6 100644 --- a/spec/ruby/core/kernel/require_spec.rb +++ b/spec/ruby/core/kernel/require_spec.rb @@ -17,7 +17,6 @@ describe "Kernel#require" do end it_behaves_like :kernel_require_basic, :require, CodeLoadingSpecs::Method.new - it_behaves_like :kernel_require, :require, CodeLoadingSpecs::Method.new end @@ -31,6 +30,5 @@ describe "Kernel.require" do end it_behaves_like :kernel_require_basic, :require, Kernel - it_behaves_like :kernel_require, :require, Kernel end diff --git a/spec/ruby/core/kernel/shared/require.rb b/spec/ruby/core/kernel/shared/require.rb index 5e3f98f813..a81a68088a 100644 --- a/spec/ruby/core/kernel/shared/require.rb +++ b/spec/ruby/core/kernel/shared/require.rb @@ -305,6 +305,80 @@ describe :kernel_require, shared: true do $LOADED_FEATURES.should include(@path) end + platform_is_not :windows do + describe "with symlinks" do + before :each do + @symlink_to_code_dir = tmp("codesymlink") + File.symlink(CODE_LOADING_DIR, @symlink_to_code_dir) + + $LOAD_PATH.delete(CODE_LOADING_DIR) + $LOAD_PATH.unshift(@symlink_to_code_dir) + end + + after :each do + rm_r @symlink_to_code_dir + end + + it "does not canonicalize the path and stores a path with symlinks" do + symlink_path = "#{@symlink_to_code_dir}/load_fixture.rb" + canonical_path = "#{CODE_LOADING_DIR}/load_fixture.rb" + @object.require(symlink_path).should be_true + ScratchPad.recorded.should == [:loaded] + + features = $LOADED_FEATURES.select { |path| path.end_with?('load_fixture.rb') } + features.should include(symlink_path) + features.should_not include(canonical_path) + end + + it "stores the same path that __FILE__ returns in the required file" do + symlink_path = "#{@symlink_to_code_dir}/load_fixture_and__FILE__.rb" + @object.require(symlink_path).should be_true + loaded_feature = $LOADED_FEATURES.last + ScratchPad.recorded.should == [loaded_feature] + end + end + + describe "with symlinks in the required feature and $LOAD_PATH" do + before :each do + @dir = tmp("realdir") + mkdir_p @dir + @file = "#{@dir}/realfile.rb" + touch(@file) { |f| f.puts 'ScratchPad << __FILE__' } + + @symlink_to_dir = tmp("symdir").freeze + File.symlink(@dir, @symlink_to_dir) + @symlink_to_file = "#{@dir}/symfile.rb" + File.symlink("realfile.rb", @symlink_to_file) + end + + after :each do + rm_r @dir, @symlink_to_dir + end + + ruby_version_is ""..."2.4.4" do + it "canonicalizes neither the entry in $LOAD_PATH nor the filename passed to #require" do + $LOAD_PATH.unshift(@symlink_to_dir) + @object.require("symfile").should be_true + loaded_feature = "#{@symlink_to_dir}/symfile.rb" + ScratchPad.recorded.should == [loaded_feature] + $".last.should == loaded_feature + $LOAD_PATH[0].should == @symlink_to_dir + end + end + + ruby_version_is "2.4.4" do + it "canonicalizes the entry in $LOAD_PATH but not the filename passed to #require" do + $LOAD_PATH.unshift(@symlink_to_dir) + @object.require("symfile").should be_true + loaded_feature = "#{@dir}/symfile.rb" + ScratchPad.recorded.should == [loaded_feature] + $".last.should == loaded_feature + $LOAD_PATH[0].should == @symlink_to_dir + end + end + end + end + it "does not store the path if the load fails" do $LOAD_PATH << CODE_LOADING_DIR saved_loaded_features = $LOADED_FEATURES.dup @@ -417,7 +491,7 @@ describe :kernel_require, shared: true do $LOADED_FEATURES.should include(@path) end - it "canonicalizes non-unique absolute paths" do + it "expands absolute paths containing .." do path = File.join CODE_LOADING_DIR, "..", "code", "load_fixture.rb" @object.require(path).should be_true $LOADED_FEATURES.should include(@path) diff --git a/spec/ruby/core/module/autoload_spec.rb b/spec/ruby/core/module/autoload_spec.rb index f355a5968d..89c061ff47 100644 --- a/spec/ruby/core/module/autoload_spec.rb +++ b/spec/ruby/core/module/autoload_spec.rb @@ -105,6 +105,14 @@ describe "Module#autoload" do ModuleSpecs::Autoload::J.should == :autoload_j end + it "calls main.require(path) to load the file" do + ModuleSpecs::Autoload.autoload :ModuleAutoloadCallsRequire, "module_autoload_not_exist.rb" + 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) + 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 @@ -158,28 +166,169 @@ describe "Module#autoload" do ModuleSpecs::Autoload.use_ex1.should == :good end - it "does not load the file when referring to the constant in defined?" do - module ModuleSpecs::Autoload::Q - autoload :R, fixture(__FILE__, "autoload.rb") - defined?(R).should == "constant" + describe "interacting with defined?" do + it "does not load the file when referring to the constant in defined?" do + module ModuleSpecs::Autoload::Dog + autoload :R, fixture(__FILE__, "autoload_exception.rb") + end + + defined?(ModuleSpecs::Autoload::Dog::R).should == "constant" + ScratchPad.recorded.should be_nil + + ModuleSpecs::Autoload::Dog.should have_constant(:R) + end + + it "loads an autoloaded parent when referencing a nested constant" do + module ModuleSpecs::Autoload + autoload :GoodParent, fixture(__FILE__, "autoload_nested.rb") + end + + 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 + module ModuleSpecs::Autoload + autoload :BadParent, fixture(__FILE__, "autoload_exception.rb") + end + + defined?(ModuleSpecs::Autoload::BadParent::Nested).should be_nil + ScratchPad.recorded.should == :exception + end + 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 + raise unless ModuleSpecs::Autoload.autoload?(:DuringAutoload) == @path + end + + after :each do + ModuleSpecs::Autoload.send(:remove_const, :DuringAutoload) + 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] + end + + it "returns nil in autoload thread and 'constant' otherwise for defined?" do + results = check_before_during_thread_after { + 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 { + 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 { + 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 { + ModuleSpecs::Autoload.autoload?(:DuringAutoload) + } + results.should == [@path, nil, @path, nil] end - ModuleSpecs::Autoload::Q.should have_constant(:R) end - it "does not remove the constant from the constant table if load fails" do + 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.autoload?(:Fail).should == @non_existent lambda { ModuleSpecs::Autoload::Fail }.should raise_error(LoadError) + ModuleSpecs::Autoload.should have_constant(:Fail) + ModuleSpecs::Autoload.const_defined?(:Fail).should == true + ModuleSpecs::Autoload.autoload?(:Fail).should == @non_existent + + lambda { ModuleSpecs::Autoload::Fail }.should raise_error(LoadError) end - it "does not remove the constant from the constant table if the loaded files does not define it" do - ModuleSpecs::Autoload.autoload :O, fixture(__FILE__, "autoload_o.rb") + it "does not remove the constant from Module#constants if load raises a RuntimeError and keeps it as an autoload" do + path = fixture(__FILE__, "autoload_raise.rb") + ScratchPad.record [] + ModuleSpecs::Autoload.autoload :Raise, path + + ModuleSpecs::Autoload.const_defined?(:Raise).should == true + ModuleSpecs::Autoload.should have_constant(:Raise) + ModuleSpecs::Autoload.autoload?(:Raise).should == path + + lambda { ModuleSpecs::Autoload::Raise }.should raise_error(RuntimeError) + ScratchPad.recorded.should == [:raise] + + ModuleSpecs::Autoload.should have_constant(:Raise) + ModuleSpecs::Autoload.const_defined?(:Raise).should == true + ModuleSpecs::Autoload.autoload?(:Raise).should == path + + lambda { ModuleSpecs::Autoload::Raise }.should raise_error(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 + 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.autoload?(:O).should == path lambda { ModuleSpecs::Autoload::O }.should raise_error(NameError) + ModuleSpecs::Autoload.should have_constant(:O) + ModuleSpecs::Autoload.const_defined?(:O).should == false + ModuleSpecs::Autoload.autoload?(:O).should == nil + -> { ModuleSpecs::Autoload.const_get(:O) }.should raise_error(NameError) + end + + it "does not try to load the file again if the loaded file did not define the constant" do + path = fixture(__FILE__, "autoload_o.rb") + ScratchPad.record [] + ModuleSpecs::Autoload.autoload :NotDefinedByFile, path + + -> { ModuleSpecs::Autoload::NotDefinedByFile }.should raise_error(NameError) + ScratchPad.recorded.should == [:loaded] + -> { ModuleSpecs::Autoload::NotDefinedByFile }.should raise_error(NameError) + ScratchPad.recorded.should == [:loaded] + + Thread.new { + -> { ModuleSpecs::Autoload::NotDefinedByFile }.should raise_error(NameError) + }.join + ScratchPad.recorded.should == [:loaded] end it "returns 'constant' on referring the constant with defined?()" do @@ -216,7 +365,6 @@ describe "Module#autoload" do end it "loads the file that defines subclass XX::YY < YY and YY is a top level constant" do - module ModuleSpecs::Autoload::XX autoload :YY, fixture(__FILE__, "autoload_subclass.rb") end @@ -224,30 +372,130 @@ describe "Module#autoload" do ModuleSpecs::Autoload::XX::YY.superclass.should == YY end + describe "after autoloading searches for the constant like the original lookup" do + it "in lexical scopes if both declared and defined in parent" do + module ModuleSpecs::Autoload + ScratchPad.record -> { + DeclaredAndDefinedInParent = :declared_and_defined_in_parent + } + autoload :DeclaredAndDefinedInParent, fixture(__FILE__, "autoload_callback.rb") + class LexicalScope + DeclaredAndDefinedInParent.should == :declared_and_defined_in_parent - it "looks up the constant in the scope where it is referred" do - module ModuleSpecs - module Autoload - autoload :QQ, fixture(__FILE__, "autoload_scope.rb") - class PP - QQ.new.should be_kind_of(ModuleSpecs::Autoload::PP::QQ) + # The constant is really in Autoload, not Autoload::LexicalScope + self.should_not have_constant(:DeclaredAndDefinedInParent) + -> { const_get(:DeclaredAndDefinedInParent) }.should raise_error(NameError) end + DeclaredAndDefinedInParent.should == :declared_and_defined_in_parent end end - end - it "looks up the constant when in a meta class scope" do - module ModuleSpecs - module Autoload - autoload :R, fixture(__FILE__, "autoload_r.rb") + it "in lexical scopes if declared in parent and defined in current" do + module ModuleSpecs::Autoload + ScratchPad.record -> { + class LexicalScope + DeclaredInParentDefinedInCurrent = :declared_in_parent_defined_in_current + end + } + autoload :DeclaredInParentDefinedInCurrent, fixture(__FILE__, "autoload_callback.rb") + + class LexicalScope + DeclaredInParentDefinedInCurrent.should == :declared_in_parent_defined_in_current + LexicalScope::DeclaredInParentDefinedInCurrent.should == :declared_in_parent_defined_in_current + end + + # 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) + + 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 + module ModuleSpecs::Autoload + ScratchPad.record -> { + DeclaredInCurrentDefinedInParent = :declared_in_current_defined_in_parent + } + + class LexicalScope + autoload :DeclaredInCurrentDefinedInParent, fixture(__FILE__, "autoload_callback.rb") + -> { DeclaredInCurrentDefinedInParent }.should raise_error(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) + end + + DeclaredInCurrentDefinedInParent.should == :declared_in_current_defined_in_parent + end + end + + it "in the included modules" do + module ModuleSpecs::Autoload + ScratchPad.record -> { + module DefinedInIncludedModule + Incl = :defined_in_included_module + end + include DefinedInIncludedModule + } + autoload :Incl, fixture(__FILE__, "autoload_callback.rb") + Incl.should == :defined_in_included_module + end + end + + it "in the included modules of the superclass" do + module ModuleSpecs::Autoload + class LookupAfterAutoloadSuper + end + class LookupAfterAutoloadChild < LookupAfterAutoloadSuper + end + + ScratchPad.record -> { + module DefinedInSuperclassIncludedModule + InclS = :defined_in_superclass_included_module + end + LookupAfterAutoloadSuper.include DefinedInSuperclassIncludedModule + } + + class LookupAfterAutoloadChild + autoload :InclS, fixture(__FILE__, "autoload_callback.rb") + InclS.should == :defined_in_superclass_included_module + end + end + end + + it "in the prepended modules" do + module ModuleSpecs::Autoload + ScratchPad.record -> { + module DefinedInPrependedModule + Prep = :defined_in_prepended_module + end + include DefinedInPrependedModule + } + autoload :Prep, fixture(__FILE__, "autoload_callback.rb") + Prep.should == :defined_in_prepended_module + end + end + + it "in a meta class scope" do + module ModuleSpecs::Autoload + ScratchPad.record -> { + class MetaScope + end + } + autoload :MetaScope, fixture(__FILE__, "autoload_callback.rb") class << self def r - R.new + MetaScope.new end end end + ModuleSpecs::Autoload.r.should be_kind_of(ModuleSpecs::Autoload::MetaScope) end - ModuleSpecs::Autoload.r.should be_kind_of(ModuleSpecs::Autoload::R) end # [ruby-core:19127] [ruby-core:29941] @@ -266,6 +514,21 @@ describe "Module#autoload" do 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 + main = TOPLEVEL_BINDING.eval("self") + main.should_not_receive(:require) + + module ModuleSpecs::Autoload + autoload :AutoloadDuringRequire, fixture(__FILE__, "autoload_during_require.rb") + end + + -> { + $VERBOSE = true + Kernel.require fixture(__FILE__, "autoload_during_require.rb") + }.should_not complain + ModuleSpecs::Autoload::AutoloadDuringRequire.should be_kind_of(Class) + end + it "calls #to_path on non-string filenames" do p = mock('path') p.should_receive(:to_path).and_return @non_existent diff --git a/spec/ruby/core/module/const_get_spec.rb b/spec/ruby/core/module/const_get_spec.rb index 74fe94aaed..461b303d6d 100644 --- a/spec/ruby/core/module/const_get_spec.rb +++ b/spec/ruby/core/module/const_get_spec.rb @@ -1,5 +1,6 @@ require_relative '../../spec_helper' require_relative '../../fixtures/constants' +require_relative 'fixtures/constants_autoload' describe "Module#const_get" do it "accepts a String or Symbol name" do @@ -95,6 +96,10 @@ describe "Module#const_get" do ConstantSpecs.const_get("ClassA::CS_CONST10").should == :const10_10 end + it "raises a NameError if the name includes two successive scope separators" do + lambda { ConstantSpecs.const_get("ClassA::::CS_CONST10") }.should raise_error(NameError) + end + it "raises a NameError if only '::' is passed" do lambda { ConstantSpecs.const_get("::") }.should raise_error(NameError) end @@ -111,6 +116,22 @@ describe "Module#const_get" do ConstantSpecs.const_get(:CS_PRIVATE).should == :cs_private end + it 'does autoload a constant' do + Object.const_get('CSAutoloadA').name.should == 'CSAutoloadA' + end + + it 'does autoload a constant with a toplevel scope qualifier' do + Object.const_get('::CSAutoloadB').name.should == 'CSAutoloadB' + end + + it 'does autoload a module and resolve a constant within' do + Object.const_get('CSAutoloadC::CONST').should == 7 + end + + it 'does autoload a non-toplevel module' do + Object.const_get('CSAutoloadD::InnerModule').name.should == 'CSAutoloadD::InnerModule' + end + describe "with statically assigned constants" do it "searches the immediate class or module first" do ConstantSpecs::ClassA.const_get(:CS_CONST10).should == :const10_10 diff --git a/spec/ruby/core/module/fixtures/autoload_callback.rb b/spec/ruby/core/module/fixtures/autoload_callback.rb new file mode 100644 index 0000000000..51d53eb580 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_callback.rb @@ -0,0 +1,2 @@ +block = ScratchPad.recorded +block.call diff --git a/spec/ruby/core/module/fixtures/autoload_during_autoload.rb b/spec/ruby/core/module/fixtures/autoload_during_autoload.rb new file mode 100644 index 0000000000..5202bd8b23 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_during_autoload.rb @@ -0,0 +1,7 @@ +block = ScratchPad.recorded +ScratchPad.record(block.call) + +module ModuleSpecs::Autoload + class DuringAutoload + end +end diff --git a/spec/ruby/core/module/fixtures/autoload_during_require.rb b/spec/ruby/core/module/fixtures/autoload_during_require.rb new file mode 100644 index 0000000000..6fd81592e3 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_during_require.rb @@ -0,0 +1,4 @@ +module ModuleSpecs::Autoload + class AutoloadDuringRequire + end +end diff --git a/spec/ruby/core/module/fixtures/autoload_exception.rb b/spec/ruby/core/module/fixtures/autoload_exception.rb new file mode 100644 index 0000000000..09acf9f537 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_exception.rb @@ -0,0 +1,3 @@ +ScratchPad.record(:exception) + +raise 'intentional error to test failure conditions during autoloading' diff --git a/spec/ruby/core/module/fixtures/autoload_nested.rb b/spec/ruby/core/module/fixtures/autoload_nested.rb new file mode 100644 index 0000000000..073cec0dce --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_nested.rb @@ -0,0 +1,8 @@ +module ModuleSpecs::Autoload + module GoodParent + class Nested + end + end +end + +ScratchPad.record(:loaded) diff --git a/spec/ruby/core/module/fixtures/autoload_o.rb b/spec/ruby/core/module/fixtures/autoload_o.rb index 6d54ddaf12..7d88f969b2 100644 --- a/spec/ruby/core/module/fixtures/autoload_o.rb +++ b/spec/ruby/core/module/fixtures/autoload_o.rb @@ -1 +1,2 @@ # does not define ModuleSpecs::Autoload::O +ScratchPad << :loaded diff --git a/spec/ruby/core/module/fixtures/autoload_raise.rb b/spec/ruby/core/module/fixtures/autoload_raise.rb new file mode 100644 index 0000000000..f6051e3ba2 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_raise.rb @@ -0,0 +1,2 @@ +ScratchPad << :raise +raise "exception during autoload" diff --git a/spec/ruby/core/module/fixtures/autoload_scope.rb b/spec/ruby/core/module/fixtures/autoload_scope.rb deleted file mode 100644 index 04193687b5..0000000000 --- a/spec/ruby/core/module/fixtures/autoload_scope.rb +++ /dev/null @@ -1,8 +0,0 @@ -module ModuleSpecs - module Autoload - class PP - class QQ - end - end - end -end diff --git a/spec/ruby/core/module/fixtures/constants_autoload.rb b/spec/ruby/core/module/fixtures/constants_autoload.rb new file mode 100644 index 0000000000..8e9aa8de0c --- /dev/null +++ b/spec/ruby/core/module/fixtures/constants_autoload.rb @@ -0,0 +1,6 @@ +autoload :CSAutoloadA, fixture(__FILE__, 'constants_autoload_a.rb') +autoload :CSAutoloadB, fixture(__FILE__, 'constants_autoload_b.rb') +autoload :CSAutoloadC, fixture(__FILE__, 'constants_autoload_c.rb') +module CSAutoloadD + autoload :InnerModule, fixture(__FILE__, 'constants_autoload_d.rb') +end diff --git a/spec/ruby/core/module/fixtures/constants_autoload_a.rb b/spec/ruby/core/module/fixtures/constants_autoload_a.rb new file mode 100644 index 0000000000..48d3b63681 --- /dev/null +++ b/spec/ruby/core/module/fixtures/constants_autoload_a.rb @@ -0,0 +1,2 @@ +module CSAutoloadA +end diff --git a/spec/ruby/core/module/fixtures/constants_autoload_b.rb b/spec/ruby/core/module/fixtures/constants_autoload_b.rb new file mode 100644 index 0000000000..29cd742d03 --- /dev/null +++ b/spec/ruby/core/module/fixtures/constants_autoload_b.rb @@ -0,0 +1,2 @@ +module CSAutoloadB +end diff --git a/spec/ruby/core/module/fixtures/constants_autoload_c.rb b/spec/ruby/core/module/fixtures/constants_autoload_c.rb new file mode 100644 index 0000000000..9d6a6bf4d7 --- /dev/null +++ b/spec/ruby/core/module/fixtures/constants_autoload_c.rb @@ -0,0 +1,3 @@ +module CSAutoloadC + CONST = 7 +end diff --git a/spec/ruby/core/module/fixtures/constants_autoload_d.rb b/spec/ruby/core/module/fixtures/constants_autoload_d.rb new file mode 100644 index 0000000000..52d550bab0 --- /dev/null +++ b/spec/ruby/core/module/fixtures/constants_autoload_d.rb @@ -0,0 +1,4 @@ +module CSAutoloadD + module InnerModule + end +end diff --git a/spec/ruby/core/module/initialize_copy_spec.rb b/spec/ruby/core/module/initialize_copy_spec.rb index 412f1c511b..7ae48f85a9 100644 --- a/spec/ruby/core/module/initialize_copy_spec.rb +++ b/spec/ruby/core/module/initialize_copy_spec.rb @@ -7,4 +7,12 @@ describe "Module#initialize_copy" do end mod.dup.methods(false).should == [:hello] end + + # jruby/jruby#5245, https://bugs.ruby-lang.org/issues/3461 + it "should produce a duped module with inspectable class methods" do + mod = Module.new + def mod.hello + end + mod.dup.method(:hello).inspect.should =~ /Module.*hello/ + end end diff --git a/spec/ruby/core/signal/fixtures/trap_all.rb b/spec/ruby/core/signal/fixtures/trap_all.rb new file mode 100644 index 0000000000..b2e85df247 --- /dev/null +++ b/spec/ruby/core/signal/fixtures/trap_all.rb @@ -0,0 +1,8 @@ +reserved_signals = ARGV + +(Signal.list.keys - reserved_signals).each do |signal| + Signal.trap(signal, -> {}) + Signal.trap(signal, "DEFAULT") +end + +puts "OK" diff --git a/spec/ruby/core/signal/trap_spec.rb b/spec/ruby/core/signal/trap_spec.rb index d621b6ae6d..66101ee5a0 100644 --- a/spec/ruby/core/signal/trap_spec.rb +++ b/spec/ruby/core/signal/trap_spec.rb @@ -115,6 +115,49 @@ platform_is_not :windows do end describe "Signal.trap" do + cannot_be_trapped = %w[KILL STOP] # See man 2 signal + reserved_signals = %w[VTALRM] + + if PlatformGuard.implementation?(:ruby) + reserved_signals += %w[SEGV ILL FPE BUS] + end + + if PlatformGuard.implementation?(:truffleruby) + if !TruffleRuby.native? + reserved_signals += %w[SEGV ILL FPE USR1 QUIT] + end + end + + if PlatformGuard.implementation?(:jruby) + reserved_signals += %w[SEGV ILL FPE BUS USR1 QUIT] + end + + cannot_be_trapped.each do |signal| + it "raises ArgumentError or Errno::EINVAL for SIG#{signal}" do + -> { + trap(signal, -> {}) + }.should raise_error(StandardError) { |e| + [ArgumentError, Errno::EINVAL].should include(e.class) + e.message.should =~ /Invalid argument|Signal already used by VM or OS/ + } + end + end + + reserved_signals.each do |signal| + it "raises ArgumentError for reserved signal SIG#{signal}" do + -> { + trap(signal, -> {}) + }.should raise_error(ArgumentError, /can't trap reserved signal|Signal already used by VM or OS/) + end + end + + it "allows to register a handler for all known signals, except reserved signals" do + excluded = cannot_be_trapped + reserved_signals + out = ruby_exe(fixture(__FILE__, "trap_all.rb"), args: [*excluded, "2>&1"]) + out.should == "OK\n" + $?.exitstatus.should == 0 + end + it "returns SYSTEM_DEFAULT if passed DEFAULT and no handler was ever set" do Signal.trap("PROF", "DEFAULT").should == "SYSTEM_DEFAULT" end diff --git a/spec/ruby/core/string/force_encoding_spec.rb b/spec/ruby/core/string/force_encoding_spec.rb index c317c84c1f..06e04b8d95 100644 --- a/spec/ruby/core/string/force_encoding_spec.rb +++ b/spec/ruby/core/string/force_encoding_spec.rb @@ -6,6 +6,26 @@ with_feature :encoding do "abc".force_encoding('shift_jis').encoding.should == Encoding::Shift_JIS end + describe "with a special encoding name" do + before :each do + @original_encoding = Encoding.default_internal + end + + after :each do + Encoding.default_internal = @original_encoding + end + + it "accepts valid special encoding names" do + Encoding.default_internal = "US-ASCII" + "abc".force_encoding("internal").encoding.should == Encoding::US_ASCII + end + + it "defaults to ASCII-8BIT if special encoding name is not set" do + Encoding.default_internal = nil + "abc".force_encoding("internal").encoding.should == Encoding::ASCII_8BIT + end + end + it "accepts an Encoding instance" do "abc".force_encoding(Encoding::SHIFT_JIS).encoding.should == Encoding::Shift_JIS end diff --git a/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb b/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb index aeae6b77c6..5bd97442d9 100644 --- a/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb +++ b/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb @@ -9,4 +9,42 @@ describe 'Thread::Backtrace::Location#absolute_path' do it 'returns the absolute path of the call frame' do @frame.absolute_path.should == File.realpath(__FILE__) end + + context "when used in eval with a given filename" do + it "returns filename" do + code = "caller_locations(0)[0].absolute_path" + eval(code, nil, "foo.rb").should == "foo.rb" + eval(code, nil, "foo/bar.rb").should == "foo/bar.rb" + end + end + + platform_is_not :windows do + before :each do + @file = fixture(__FILE__, "absolute_path.rb") + @symlink = tmp("symlink.rb") + File.symlink(@file, @symlink) + ScratchPad.record [] + end + + after :each do + rm_r @symlink + end + + it "returns a canonical path without symlinks, even when __FILE__ does not" do + realpath = File.realpath(@symlink) + realpath.should_not == @symlink + + load @symlink + ScratchPad.recorded.should == [@symlink, realpath] + end + + it "returns a canonical path without symlinks, even when __FILE__ is removed" do + realpath = File.realpath(@symlink) + realpath.should_not == @symlink + + ScratchPad << -> { rm_r(@symlink) } + load @symlink + ScratchPad.recorded.should == [@symlink, realpath] + end + end end diff --git a/spec/ruby/core/thread/backtrace/location/fixtures/absolute_path.rb b/spec/ruby/core/thread/backtrace/location/fixtures/absolute_path.rb new file mode 100644 index 0000000000..875e97ffac --- /dev/null +++ b/spec/ruby/core/thread/backtrace/location/fixtures/absolute_path.rb @@ -0,0 +1,4 @@ +action = ScratchPad.recorded.pop +ScratchPad << __FILE__ +action.call if action +ScratchPad << caller_locations(0)[0].absolute_path diff --git a/spec/ruby/core/time/localtime_spec.rb b/spec/ruby/core/time/localtime_spec.rb index 5592150ca2..7942653c78 100644 --- a/spec/ruby/core/time/localtime_spec.rb +++ b/spec/ruby/core/time/localtime_spec.rb @@ -93,7 +93,7 @@ describe "Time#localtime" do it "does nothing if already in a local time zone" do time = with_timezone("America/New_York") do - break Time.new(2005, 2, 27, 22, 50, 0) + Time.new(2005, 2, 27, 22, 50, 0) end zone = time.zone |