summaryrefslogtreecommitdiff
path: root/spec/rubyspec/core/kernel/shared
diff options
context:
space:
mode:
Diffstat (limited to 'spec/rubyspec/core/kernel/shared')
-rw-r--r--spec/rubyspec/core/kernel/shared/dup_clone.rb125
-rw-r--r--spec/rubyspec/core/kernel/shared/kind_of.rb44
-rw-r--r--spec/rubyspec/core/kernel/shared/lambda.rb9
-rw-r--r--spec/rubyspec/core/kernel/shared/load.rb139
-rw-r--r--spec/rubyspec/core/kernel/shared/method.rb50
-rw-r--r--spec/rubyspec/core/kernel/shared/require.rb703
6 files changed, 1070 insertions, 0 deletions
diff --git a/spec/rubyspec/core/kernel/shared/dup_clone.rb b/spec/rubyspec/core/kernel/shared/dup_clone.rb
new file mode 100644
index 0000000000..320da8fc38
--- /dev/null
+++ b/spec/rubyspec/core/kernel/shared/dup_clone.rb
@@ -0,0 +1,125 @@
+class ObjectSpecDup
+ def initialize()
+ @obj = :original
+ end
+
+ attr_accessor :obj
+end
+
+class ObjectSpecDupInitCopy
+ def initialize()
+ @obj = :original
+ end
+
+ attr_accessor :obj, :original
+
+ def initialize_copy(original)
+ @obj = :init_copy
+ @original = original
+ end
+
+ private :initialize_copy
+end
+
+describe :kernel_dup_clone, shared: true do
+ it "returns a new object duplicated from the original" do
+ o = ObjectSpecDup.new
+ o2 = ObjectSpecDup.new
+
+ o.obj = 10
+
+ o3 = o.send(@method)
+
+ o3.obj.should == 10
+ o2.obj.should == :original
+ end
+
+ it "produces a shallow copy, contained objects are not recursively dupped" do
+ o = ObjectSpecDup.new
+ array = [1, 2]
+ o.obj = array
+
+ o2 = o.send(@method)
+ o2.obj.should equal(o.obj)
+ end
+
+ it "calls #initialize_copy on the NEW object if available, passing in original object" do
+ o = ObjectSpecDupInitCopy.new
+ o2 = o.send(@method)
+
+ o.obj.should == :original
+ o2.obj.should == :init_copy
+ o2.original.should equal(o)
+ end
+
+ it "preserves tainted state from the original" do
+ o = ObjectSpecDupInitCopy.new
+ o2 = o.send(@method)
+ o.taint
+ o3 = o.send(@method)
+
+ o2.tainted?.should == false
+ o3.tainted?.should == true
+ end
+
+ it "does not preserve the object_id" do
+ o1 = ObjectSpecDupInitCopy.new
+ old_object_id = o1.object_id
+ o2 = o1.send(@method)
+ o2.object_id.should_not == old_object_id
+ end
+
+ it "preserves untrusted state from the original" do
+ o = ObjectSpecDupInitCopy.new
+ o2 = o.send(@method)
+ o.untrust
+ o3 = o.send(@method)
+
+ o2.untrusted?.should == false
+ o3.untrusted?.should == true
+ end
+
+ ruby_version_is ''...'2.4' do
+ it "raises a TypeError for NilClass" do
+ lambda { nil.send(@method) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError for TrueClass" do
+ lambda { true.send(@method) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError for FalseClass" do
+ lambda { false.send(@method) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError for Fixnum" do
+ lambda { 1.send(@method) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError for Symbol" do
+ lambda { :my_symbol.send(@method) }.should raise_error(TypeError)
+ end
+ end
+
+ ruby_version_is '2.4' do
+ it "returns nil for NilClass" do
+ nil.send(@method).should == nil
+ end
+
+ it "returns true for TrueClass" do
+ true.send(@method).should == true
+ end
+
+ it "returns false for FalseClass" do
+ false.send(@method).should == false
+ end
+
+ it "returns the same Integer for Integer" do
+ 1.send(@method).should == 1
+ end
+
+ it "returns the same Symbol for Symbol" do
+ :my_symbol.send(@method).should == :my_symbol
+ end
+ end
+end
diff --git a/spec/rubyspec/core/kernel/shared/kind_of.rb b/spec/rubyspec/core/kernel/shared/kind_of.rb
new file mode 100644
index 0000000000..e99f46aa14
--- /dev/null
+++ b/spec/rubyspec/core/kernel/shared/kind_of.rb
@@ -0,0 +1,44 @@
+require File.expand_path('../../fixtures/classes', __FILE__)
+
+describe :kernel_kind_of, shared: true do
+ before :each do
+ @o = KernelSpecs::KindaClass.new
+ end
+
+ it "returns true if given class is the object's class" do
+ @o.send(@method, KernelSpecs::KindaClass).should == true
+ end
+
+ it "returns true if given class is an ancestor of the object's class" do
+ @o.send(@method, KernelSpecs::AncestorClass).should == true
+ @o.send(@method, String).should == true
+ @o.send(@method, Object).should == true
+ end
+
+ it "returns false if the given class is not object's class nor an ancestor" do
+ @o.send(@method, Array).should == false
+ end
+
+ it "returns true if given a Module that is included in object's class" do
+ @o.send(@method, KernelSpecs::MyModule).should == true
+ end
+
+ it "returns true if given a Module that is included one of object's ancestors only" do
+ @o.send(@method, KernelSpecs::AncestorModule).should == true
+ end
+
+ it "returns true if given a Module that object has been extended with" do
+ @o.send(@method, KernelSpecs::MyExtensionModule).should == true
+ end
+
+ it "returns false if given a Module not included in object's class nor ancestors" do
+ @o.send(@method, KernelSpecs::SomeOtherModule).should == false
+ end
+
+ it "raises a TypeError if given an object that is not a Class nor a Module" do
+ lambda { @o.send(@method, 1) }.should raise_error(TypeError)
+ lambda { @o.send(@method, 'KindaClass') }.should raise_error(TypeError)
+ lambda { @o.send(@method, :KindaClass) }.should raise_error(TypeError)
+ lambda { @o.send(@method, Object.new) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/rubyspec/core/kernel/shared/lambda.rb b/spec/rubyspec/core/kernel/shared/lambda.rb
new file mode 100644
index 0000000000..bebb111c43
--- /dev/null
+++ b/spec/rubyspec/core/kernel/shared/lambda.rb
@@ -0,0 +1,9 @@
+describe :kernel_lambda, shared: true do
+ it "returns a Proc object" do
+ send(@method) { true }.kind_of?(Proc).should == true
+ end
+
+ it "raises an ArgumentError when no block is given" do
+ lambda { send(@method) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/rubyspec/core/kernel/shared/load.rb b/spec/rubyspec/core/kernel/shared/load.rb
new file mode 100644
index 0000000000..0ce7d58d2c
--- /dev/null
+++ b/spec/rubyspec/core/kernel/shared/load.rb
@@ -0,0 +1,139 @@
+describe :kernel_load, shared: true do
+ before :each do
+ CodeLoadingSpecs.spec_setup
+ @path = File.expand_path "load_fixture.rb", CODE_LOADING_DIR
+ end
+
+ after :each do
+ CodeLoadingSpecs.spec_cleanup
+ end
+
+ it "loads a non-extensioned file as a Ruby source file" do
+ path = File.expand_path "load_fixture", CODE_LOADING_DIR
+ @object.load(path).should be_true
+ ScratchPad.recorded.should == [:no_ext]
+ end
+
+ it "loads a non .rb extensioned file as a Ruby source file" do
+ path = File.expand_path "load_fixture.ext", CODE_LOADING_DIR
+ @object.load(path).should be_true
+ ScratchPad.recorded.should == [:no_rb_ext]
+ end
+
+ it "loads from the current working directory" do
+ Dir.chdir CODE_LOADING_DIR do
+ @object.load("load_fixture.rb").should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+ end
+
+ it "loads a file that recursively requires itself" do
+ path = File.expand_path "recursive_require_fixture.rb", CODE_LOADING_DIR
+ -> {
+ $VERBOSE = true
+ @object.load(path).should be_true
+ }.should complain(/circular require considered harmful/)
+ ScratchPad.recorded.should == [:loaded, :loaded]
+ end
+
+ it "loads a file that recursively loads itself" do
+ path = File.expand_path "recursive_load_fixture.rb", CODE_LOADING_DIR
+ @object.load(path).should be_true
+ ScratchPad.recorded.should == [:loaded, :loaded]
+ end
+
+ it "loads a file each time the method is called" do
+ @object.load(@path).should be_true
+ @object.load(@path).should be_true
+ ScratchPad.recorded.should == [:loaded, :loaded]
+ end
+
+ it "loads a file even when the name appears in $LOADED_FEATURES" do
+ $LOADED_FEATURES << @path
+ @object.load(@path).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "loads a file that has been loaded by #require" do
+ @object.require(@path).should be_true
+ @object.load(@path).should be_true
+ ScratchPad.recorded.should == [:loaded, :loaded]
+ end
+
+ it "loads file even after $LOAD_PATH change" do
+ $LOAD_PATH << CODE_LOADING_DIR
+ @object.load("load_fixture.rb").should be_true
+ $LOAD_PATH.unshift CODE_LOADING_DIR + "/gem"
+ @object.load("load_fixture.rb").should be_true
+ ScratchPad.recorded.should == [:loaded, :loaded_gem]
+ end
+
+ it "does not cause #require with the same path to fail" do
+ @object.load(@path).should be_true
+ @object.require(@path).should be_true
+ ScratchPad.recorded.should == [:loaded, :loaded]
+ end
+
+ it "does not add the loaded path to $LOADED_FEATURES" do
+ saved_loaded_features = $LOADED_FEATURES.dup
+ @object.load(@path).should be_true
+ $LOADED_FEATURES.should == saved_loaded_features
+ end
+
+ it "raises a LoadError if passed a non-extensioned path that does not exist but a .rb extensioned path does exist" do
+ path = File.expand_path "load_ext_fixture", CODE_LOADING_DIR
+ lambda { @object.load(path) }.should raise_error(LoadError)
+ end
+
+ describe "when passed true for 'wrap'" do
+ it "loads from an existing path" do
+ path = File.expand_path "wrap_fixture.rb", CODE_LOADING_DIR
+ @object.load(path, true).should be_true
+ end
+
+ it "sets the enclosing scope to an anonymous module" do
+ path = File.expand_path "wrap_fixture.rb", CODE_LOADING_DIR
+ @object.load(path, true)
+
+ Object.const_defined?(:LoadSpecWrap).should be_false
+ end
+
+ it "allows referencing outside namespaces" do
+ path = File.expand_path "wrap_fixture.rb", CODE_LOADING_DIR
+ @object.load(path, true)
+
+ ScratchPad.recorded.first.should be_an_instance_of(Class)
+ end
+
+ describe "with top-level methods" do
+ before :each do
+ path = File.expand_path "load_wrap_method_fixture.rb", CODE_LOADING_DIR
+ @object.load(path, true)
+ end
+
+ it "allows calling top-level methods" do
+ ScratchPad.recorded.last.should == :load_wrap_loaded
+ end
+
+ it "does not pollute the receiver" do
+ lambda { @object.send(:top_level_method) }.should raise_error(NameError)
+ end
+ end
+ end
+
+ describe "(shell expansion)" do
+ before :each do
+ @env_home = ENV["HOME"]
+ ENV["HOME"] = CODE_LOADING_DIR
+ end
+
+ after :each do
+ ENV["HOME"] = @env_home
+ end
+
+ it "expands a tilde to the HOME environment variable as the path to load" do
+ @object.require("~/load_fixture.rb").should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+ end
+end
diff --git a/spec/rubyspec/core/kernel/shared/method.rb b/spec/rubyspec/core/kernel/shared/method.rb
new file mode 100644
index 0000000000..1566c6ab09
--- /dev/null
+++ b/spec/rubyspec/core/kernel/shared/method.rb
@@ -0,0 +1,50 @@
+require File.expand_path('../../../../spec_helper', __FILE__)
+
+describe :kernel_method, shared: true do
+ it "returns a method object for a valid method" do
+ class KernelSpecs::Foo; def bar; 'done'; end; end
+ m = KernelSpecs::Foo.new.send(@method, :bar)
+ m.should be_an_instance_of Method
+ m.call.should == 'done'
+ end
+
+ it "returns a method object for a valid singleton method" do
+ class KernelSpecs::Foo; def self.bar; 'class done'; end; end
+ m = KernelSpecs::Foo.send(@method, :bar)
+ m.should be_an_instance_of Method
+ m.call.should == 'class done'
+ end
+
+ it "returns a method object if we repond_to_missing? method" do
+ m = KernelSpecs::RespondViaMissing.new.send(@method, :handled_publicly)
+ m.should be_an_instance_of Method
+ m.call(42).should == "Done handled_publicly([42])"
+ end
+
+ it "raises a NameError for an invalid method name" do
+ class KernelSpecs::Foo; def bar; 'done'; end; end
+ lambda {
+ KernelSpecs::Foo.new.send(@method, :invalid_and_silly_method_name)
+ }.should raise_error(NameError)
+ end
+
+ it "raises a NameError for an invalid singleton method name" do
+ class KernelSpecs::Foo; def self.bar; 'done'; end; end
+ lambda { KernelSpecs::Foo.send(@method, :baz) }.should raise_error(NameError)
+ end
+
+ it "changes the method called for super on a target aliased method" do
+ c1 = Class.new do
+ def a; 'a'; end
+ def b; 'b'; end
+ end
+ c2 = Class.new(c1) do
+ def a; super; end
+ alias b a
+ end
+
+ c2.new.a.should == 'a'
+ c2.new.b.should == 'a'
+ c2.new.send(@method, :b).call.should == 'a'
+ end
+end
diff --git a/spec/rubyspec/core/kernel/shared/require.rb b/spec/rubyspec/core/kernel/shared/require.rb
new file mode 100644
index 0000000000..3296c7f42a
--- /dev/null
+++ b/spec/rubyspec/core/kernel/shared/require.rb
@@ -0,0 +1,703 @@
+describe :kernel_require_basic, shared: true do
+ describe "(path resolution)" do
+ it "loads an absolute path" do
+ path = File.expand_path "load_fixture.rb", CODE_LOADING_DIR
+ @object.send(@method, path).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "loads a non-canonical absolute path" do
+ path = File.join CODE_LOADING_DIR, "..", "code", "load_fixture.rb"
+ @object.send(@method, path).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "loads a file defining many methods" do
+ path = File.expand_path "methods_fixture.rb", CODE_LOADING_DIR
+ @object.send(@method, path).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "raises a LoadError if the file does not exist" do
+ path = File.expand_path "nonexistent.rb", CODE_LOADING_DIR
+ File.exist?(path).should be_false
+ lambda { @object.send(@method, path) }.should raise_error(LoadError)
+ ScratchPad.recorded.should == []
+ end
+
+ # Can't make a file unreadable on these platforms
+ platform_is_not :windows, :cygwin do
+ describe "with an unreadable file" do
+ before :each do
+ @path = tmp("unreadable_file.rb")
+ touch @path
+ File.chmod 0000, @path
+ end
+
+ after :each do
+ File.chmod 0666, @path
+ rm_r @path
+ end
+
+ it "raises a LoadError" do
+ File.exist?(@path).should be_true
+ lambda { @object.send(@method, @path) }.should raise_error(LoadError)
+ end
+ end
+ end
+
+ it "calls #to_str on non-String objects" do
+ path = File.expand_path "load_fixture.rb", CODE_LOADING_DIR
+ name = mock("load_fixture.rb mock")
+ name.should_receive(:to_str).and_return(path)
+ @object.send(@method, name).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "raises a TypeError if passed nil" do
+ lambda { @object.send(@method, nil) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed a Fixnum" do
+ lambda { @object.send(@method, 42) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed an Array" do
+ lambda { @object.send(@method, []) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed an object that does not provide #to_str" do
+ lambda { @object.send(@method, mock("not a filename")) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if passed an object that has #to_s but not #to_str" do
+ name = mock("load_fixture.rb mock")
+ name.stub!(:to_s).and_return("load_fixture.rb")
+ $LOAD_PATH << "."
+ Dir.chdir CODE_LOADING_DIR do
+ lambda { @object.send(@method, name) }.should raise_error(TypeError)
+ end
+ end
+
+ it "raises a TypeError if #to_str does not return a String" do
+ name = mock("#to_str returns nil")
+ name.should_receive(:to_str).at_least(1).times.and_return(nil)
+ lambda { @object.send(@method, name) }.should raise_error(TypeError)
+ end
+
+ it "calls #to_path on non-String objects" do
+ name = mock("load_fixture.rb mock")
+ name.stub!(:to_path).and_return("load_fixture.rb")
+ $LOAD_PATH << "."
+ Dir.chdir CODE_LOADING_DIR do
+ @object.send(@method, name).should be_true
+ end
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "calls #to_path on a String" do
+ path = File.expand_path "load_fixture.rb", CODE_LOADING_DIR
+ str = mock("load_fixture.rb mock")
+ str.should_receive(:to_path).and_return(path)
+ @object.send(@method, str).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "calls #to_str on non-String objects returned by #to_path" do
+ path = File.expand_path "load_fixture.rb", CODE_LOADING_DIR
+ name = mock("load_fixture.rb mock")
+ to_path = mock("load_fixture_rb #to_path mock")
+ name.should_receive(:to_path).and_return(to_path)
+ to_path.should_receive(:to_str).and_return(path)
+ @object.send(@method, name).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ # "http://redmine.ruby-lang.org/issues/show/2578"
+ it "loads a ./ relative path from the current working directory with empty $LOAD_PATH" do
+ Dir.chdir CODE_LOADING_DIR do
+ @object.send(@method, "./load_fixture.rb").should be_true
+ end
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "loads a ../ relative path from the current working directory with empty $LOAD_PATH" do
+ Dir.chdir CODE_LOADING_DIR do
+ @object.send(@method, "../code/load_fixture.rb").should be_true
+ end
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "loads a ./ relative path from the current working directory with non-empty $LOAD_PATH" do
+ $LOAD_PATH << "an_irrelevant_dir"
+ Dir.chdir CODE_LOADING_DIR do
+ @object.send(@method, "./load_fixture.rb").should be_true
+ end
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "loads a ../ relative path from the current working directory with non-empty $LOAD_PATH" do
+ $LOAD_PATH << "an_irrelevant_dir"
+ Dir.chdir CODE_LOADING_DIR do
+ @object.send(@method, "../code/load_fixture.rb").should be_true
+ end
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "loads a non-canonical path from the current working directory with non-empty $LOAD_PATH" do
+ $LOAD_PATH << "an_irrelevant_dir"
+ Dir.chdir CODE_LOADING_DIR do
+ @object.send(@method, "../code/../code/load_fixture.rb").should be_true
+ end
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "resolves a filename against $LOAD_PATH entries" do
+ $LOAD_PATH << CODE_LOADING_DIR
+ @object.send(@method, "load_fixture.rb").should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "does not require file twice after $LOAD_PATH change" do
+ $LOAD_PATH << CODE_LOADING_DIR
+ @object.require("load_fixture.rb").should be_true
+ $LOAD_PATH.unshift CODE_LOADING_DIR + "/gem"
+ @object.require("load_fixture.rb").should be_false
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "does not resolve a ./ relative path against $LOAD_PATH entries" do
+ $LOAD_PATH << CODE_LOADING_DIR
+ lambda do
+ @object.send(@method, "./load_fixture.rb")
+ end.should raise_error(LoadError)
+ ScratchPad.recorded.should == []
+ end
+
+ it "does not resolve a ../ relative path against $LOAD_PATH entries" do
+ $LOAD_PATH << CODE_LOADING_DIR
+ lambda do
+ @object.send(@method, "../code/load_fixture.rb")
+ end.should raise_error(LoadError)
+ ScratchPad.recorded.should == []
+ end
+
+ it "resolves a non-canonical path against $LOAD_PATH entries" do
+ $LOAD_PATH << File.dirname(CODE_LOADING_DIR)
+ @object.send(@method, "code/../code/load_fixture.rb").should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "loads a path with duplicate path separators" do
+ $LOAD_PATH << "."
+ sep = File::Separator + File::Separator
+ path = ["..", "code", "load_fixture.rb"].join(sep)
+ Dir.chdir CODE_LOADING_DIR do
+ @object.send(@method, path).should be_true
+ end
+ ScratchPad.recorded.should == [:loaded]
+ end
+ end
+end
+
+describe :kernel_require, shared: true do
+ describe "(path resolution)" do
+ # For reference see [ruby-core:24155] in which matz confirms this feature is
+ # intentional for security reasons.
+ it "does not load a bare filename unless the current working directory is in $LOAD_PATH" do
+ Dir.chdir CODE_LOADING_DIR do
+ lambda { @object.require("load_fixture.rb") }.should raise_error(LoadError)
+ ScratchPad.recorded.should == []
+ end
+ end
+
+ it "does not load a relative path unless the current working directory is in $LOAD_PATH" do
+ Dir.chdir File.dirname(CODE_LOADING_DIR) do
+ lambda do
+ @object.require("code/load_fixture.rb")
+ end.should raise_error(LoadError)
+ ScratchPad.recorded.should == []
+ end
+ end
+
+ it "loads a file that recursively requires itself" do
+ path = File.expand_path "recursive_require_fixture.rb", CODE_LOADING_DIR
+ -> {
+ $VERBOSE = true
+ @object.require(path).should be_true
+ }.should complain(/circular require considered harmful/)
+ ScratchPad.recorded.should == [:loaded]
+ end
+ end
+
+ describe "(non-extensioned path)" do
+ before :each do
+ a = File.expand_path "a", CODE_LOADING_DIR
+ b = File.expand_path "b", CODE_LOADING_DIR
+ $LOAD_PATH.replace [a, b]
+ end
+
+ it "loads a .rb extensioned file when a C-extension file exists on an earlier load path" do
+ @object.require("load_fixture").should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+ end
+
+ describe "(file extensions)" do
+ it "loads a .rb extensioned file when passed a non-extensioned path" do
+ path = File.expand_path "load_fixture", CODE_LOADING_DIR
+ File.exist?(path).should be_true
+ @object.require(path).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "loads a .rb extensioned file when a C-extension file of the same name is loaded" do
+ $LOADED_FEATURES << File.expand_path("load_fixture.bundle", CODE_LOADING_DIR)
+ $LOADED_FEATURES << File.expand_path("load_fixture.dylib", CODE_LOADING_DIR)
+ $LOADED_FEATURES << File.expand_path("load_fixture.so", CODE_LOADING_DIR)
+ $LOADED_FEATURES << File.expand_path("load_fixture.dll", CODE_LOADING_DIR)
+ path = File.expand_path "load_fixture", CODE_LOADING_DIR
+ @object.require(path).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "does not load a C-extension file if a .rb extensioned file is already loaded" do
+ $LOADED_FEATURES << File.expand_path("load_fixture.rb", CODE_LOADING_DIR)
+ path = File.expand_path "load_fixture", CODE_LOADING_DIR
+ @object.require(path).should be_false
+ ScratchPad.recorded.should == []
+ end
+
+ it "loads a .rb extensioned file when passed a non-.rb extensioned path" do
+ path = File.expand_path "load_fixture.ext", CODE_LOADING_DIR
+ File.exist?(path).should be_true
+ @object.require(path).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "loads a .rb extensioned file when a complex-extensioned C-extension file of the same name is loaded" do
+ $LOADED_FEATURES << File.expand_path("load_fixture.ext.bundle", CODE_LOADING_DIR)
+ $LOADED_FEATURES << File.expand_path("load_fixture.ext.dylib", CODE_LOADING_DIR)
+ $LOADED_FEATURES << File.expand_path("load_fixture.ext.so", CODE_LOADING_DIR)
+ $LOADED_FEATURES << File.expand_path("load_fixture.ext.dll", CODE_LOADING_DIR)
+ path = File.expand_path "load_fixture.ext", CODE_LOADING_DIR
+ @object.require(path).should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "does not load a C-extension file if a complex-extensioned .rb file is already loaded" do
+ $LOADED_FEATURES << File.expand_path("load_fixture.ext.rb", CODE_LOADING_DIR)
+ path = File.expand_path "load_fixture.ext", CODE_LOADING_DIR
+ @object.require(path).should be_false
+ ScratchPad.recorded.should == []
+ end
+ end
+
+ describe "($LOADED_FEATURES)" do
+ before :each do
+ @path = File.expand_path("load_fixture.rb", CODE_LOADING_DIR)
+ end
+
+ it "stores an absolute path" do
+ @object.require(@path).should be_true
+ $LOADED_FEATURES.should include(@path)
+ end
+
+ it "does not store the path if the load fails" do
+ $LOAD_PATH << CODE_LOADING_DIR
+ saved_loaded_features = $LOADED_FEATURES.dup
+ lambda { @object.require("raise_fixture.rb") }.should raise_error(RuntimeError)
+ $LOADED_FEATURES.should == saved_loaded_features
+ end
+
+ it "does not load an absolute path that is already stored" do
+ $LOADED_FEATURES << @path
+ @object.require(@path).should be_false
+ ScratchPad.recorded.should == []
+ end
+
+ it "does not load a ./ relative path that is already stored" do
+ $LOADED_FEATURES << "./load_fixture.rb"
+ Dir.chdir CODE_LOADING_DIR do
+ @object.require("./load_fixture.rb").should be_false
+ end
+ ScratchPad.recorded.should == []
+ end
+
+ it "does not load a ../ relative path that is already stored" do
+ $LOADED_FEATURES << "../load_fixture.rb"
+ Dir.chdir CODE_LOADING_DIR do
+ @object.require("../load_fixture.rb").should be_false
+ end
+ ScratchPad.recorded.should == []
+ end
+
+ it "does not load a non-canonical path that is already stored" do
+ $LOADED_FEATURES << "code/../code/load_fixture.rb"
+ $LOAD_PATH << File.dirname(CODE_LOADING_DIR)
+ @object.require("code/../code/load_fixture.rb").should be_false
+ ScratchPad.recorded.should == []
+ end
+
+ it "respects being replaced with a new array" do
+ prev = $LOADED_FEATURES.dup
+
+ @object.require(@path).should be_true
+ $LOADED_FEATURES.should include(@path)
+
+ $LOADED_FEATURES.replace(prev)
+
+ $LOADED_FEATURES.should_not include(@path)
+ @object.require(@path).should be_true
+ $LOADED_FEATURES.should include(@path)
+ end
+
+ it "does not load twice the same file with and without extension" do
+ $LOAD_PATH << CODE_LOADING_DIR
+ @object.require("load_fixture.rb").should be_true
+ @object.require("load_fixture").should be_false
+ end
+
+ describe "when a non-extensioned file is in $LOADED_FEATURES" do
+ before :each do
+ $LOADED_FEATURES << "load_fixture"
+ end
+
+ it "loads a .rb extensioned file when a non extensioned file is in $LOADED_FEATURES" do
+ $LOAD_PATH << CODE_LOADING_DIR
+ @object.require("load_fixture").should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "loads a .rb extensioned file from a subdirectory" do
+ $LOAD_PATH << File.dirname(CODE_LOADING_DIR)
+ @object.require("code/load_fixture").should be_true
+ ScratchPad.recorded.should == [:loaded]
+ end
+
+ it "returns false if the file is not found" do
+ Dir.chdir File.dirname(CODE_LOADING_DIR) do
+ @object.require("load_fixture").should be_false
+ ScratchPad.recorded.should == []
+ end
+ end
+
+ it "returns false when passed a path and the file is not found" do
+ $LOADED_FEATURES << "code/load_fixture"
+ Dir.chdir CODE_LOADING_DIR do
+ @object.require("code/load_fixture").should be_false
+ ScratchPad.recorded.should == []
+ end
+ end
+ end
+
+ it "stores ../ relative paths as absolute paths" do
+ Dir.chdir CODE_LOADING_DIR do
+ @object.require("../code/load_fixture.rb").should be_true
+ end
+ $LOADED_FEATURES.should include(@path)
+ end
+
+ it "stores ./ relative paths as absolute paths" do
+ Dir.chdir CODE_LOADING_DIR do
+ @object.require("./load_fixture.rb").should be_true
+ end
+ $LOADED_FEATURES.should include(@path)
+ end
+
+ it "collapses duplicate path separators" do
+ $LOAD_PATH << "."
+ sep = File::Separator + File::Separator
+ path = ["..", "code", "load_fixture.rb"].join(sep)
+ Dir.chdir CODE_LOADING_DIR do
+ @object.require(path).should be_true
+ end
+ $LOADED_FEATURES.should include(@path)
+ end
+
+ it "canonicalizes non-unique absolute paths" do
+ path = File.join CODE_LOADING_DIR, "..", "code", "load_fixture.rb"
+ @object.require(path).should be_true
+ $LOADED_FEATURES.should include(@path)
+ end
+
+ it "adds the suffix of the resolved filename" do
+ $LOAD_PATH << CODE_LOADING_DIR
+ @object.require("load_fixture").should be_true
+ $LOADED_FEATURES.should include(@path)
+ end
+
+ it "does not load a non-canonical path for a file already loaded" do
+ $LOADED_FEATURES << @path
+ $LOAD_PATH << File.dirname(CODE_LOADING_DIR)
+ @object.require("code/../code/load_fixture.rb").should be_false
+ ScratchPad.recorded.should == []
+ end
+
+ it "does not load a ./ relative path for a file already loaded" do
+ $LOADED_FEATURES << @path
+ $LOAD_PATH << "an_irrelevant_dir"
+ Dir.chdir CODE_LOADING_DIR do
+ @object.require("./load_fixture.rb").should be_false
+ end
+ ScratchPad.recorded.should == []
+ end
+
+ it "does not load a ../ relative path for a file already loaded" do
+ $LOADED_FEATURES << @path
+ $LOAD_PATH << "an_irrelevant_dir"
+ Dir.chdir CODE_LOADING_DIR do
+ @object.require("../code/load_fixture.rb").should be_false
+ end
+ ScratchPad.recorded.should == []
+ end
+
+ ruby_version_is "2.2"..."2.3" do
+ it "complex, enumerator, rational and unicode_normalize are already required" do
+ provided = %w[complex enumerator rational unicode_normalize]
+ features = ruby_exe("puts $LOADED_FEATURES", options: '--disable-gems')
+ provided.each { |feature|
+ features.should =~ /\b#{feature}\.(rb|so)$/
+ }
+
+ code = provided.map { |f| "puts require #{f.inspect}\n" }.join
+ required = ruby_exe(code, options: '--disable-gems')
+ required.should == "false\n" * provided.size
+ end
+ end
+
+ ruby_version_is "2.3"..."2.5" do
+ it "complex, enumerator, rational, thread and unicode_normalize are already required" do
+ provided = %w[complex enumerator rational thread unicode_normalize]
+ features = ruby_exe("puts $LOADED_FEATURES", options: '--disable-gems')
+ provided.each { |feature|
+ features.should =~ /\b#{feature}\.(rb|so|jar)$/
+ }
+
+ code = provided.map { |f| "puts require #{f.inspect}\n" }.join
+ required = ruby_exe(code, options: '--disable-gems')
+ required.should == "false\n" * provided.size
+ end
+ end
+
+ ruby_version_is "2.5" do
+ it "complex, enumerator, rational and thread are already required" do
+ provided = %w[complex enumerator rational thread]
+ features = ruby_exe("puts $LOADED_FEATURES", options: '--disable-gems')
+ provided.each { |feature|
+ features.should =~ /\b#{feature}\.(rb|so|jar)$/
+ }
+
+ code = provided.map { |f| "puts require #{f.inspect}\n" }.join
+ required = ruby_exe(code, options: '--disable-gems')
+ required.should == "false\n" * provided.size
+ end
+ end
+ end
+
+ describe "(shell expansion)" do
+ before :each do
+ @path = File.expand_path("load_fixture.rb", CODE_LOADING_DIR)
+ @env_home = ENV["HOME"]
+ ENV["HOME"] = CODE_LOADING_DIR
+ end
+
+ after :each do
+ ENV["HOME"] = @env_home
+ end
+
+ # "#3171"
+ it "performs tilde expansion on a .rb file before storing paths in $LOADED_FEATURES" do
+ @object.require("~/load_fixture.rb").should be_true
+ $LOADED_FEATURES.should include(@path)
+ end
+
+ it "performs tilde expansion on a non-extensioned file before storing paths in $LOADED_FEATURES" do
+ @object.require("~/load_fixture").should be_true
+ $LOADED_FEATURES.should include(@path)
+ end
+ end
+
+ describe "(concurrently)" do
+ before :each do
+ ScratchPad.record []
+ @path = File.expand_path "concurrent.rb", CODE_LOADING_DIR
+ @path2 = File.expand_path "concurrent2.rb", CODE_LOADING_DIR
+ @path3 = File.expand_path "concurrent3.rb", CODE_LOADING_DIR
+ end
+
+ after :each do
+ ScratchPad.clear
+ $LOADED_FEATURES.delete @path
+ $LOADED_FEATURES.delete @path2
+ $LOADED_FEATURES.delete @path3
+ end
+
+ # Quick note about these specs:
+ #
+ # The behavior we're spec'ing requires that t2 enter #require, see t1 is
+ # loading @path, grab a lock, and wait on it.
+ #
+ # We do make sure that t2 starts the require once t1 is in the middle
+ # of concurrent.rb, but we then need to get t2 to get far enough into #require
+ # to see t1's lock and try to lock it.
+ it "blocks a second thread from returning while the 1st is still requiring" do
+ fin = false
+
+ t1_res = nil
+ t2_res = nil
+
+ t2 = nil
+ t1 = Thread.new do
+ Thread.pass until t2
+ Thread.current[:wait_for] = t2
+ t1_res = @object.require(@path)
+ Thread.pass until fin
+ ScratchPad.recorded << :t1_post
+ end
+
+ t2 = Thread.new do
+ Thread.pass until t1[:in_concurrent_rb]
+ $VERBOSE, @verbose = nil, $VERBOSE
+ begin
+ t2_res = @object.require(@path)
+ ScratchPad.recorded << :t2_post
+ ensure
+ $VERBOSE = @verbose
+ fin = true
+ end
+ end
+
+ t1.join
+ t2.join
+
+ t1_res.should be_true
+ t2_res.should be_false
+
+ ScratchPad.recorded.should == [:con_pre, :con_post, :t2_post, :t1_post]
+ end
+
+ it "blocks based on the path" do
+ t1_res = nil
+ t2_res = nil
+
+ t2 = nil
+ t1 = Thread.new do
+ Thread.pass until t2
+ Thread.current[:concurrent_require_thread] = t2
+ t1_res = @object.require(@path2)
+ end
+
+ t2 = Thread.new do
+ Thread.pass until t1[:in_concurrent_rb2]
+ t2_res = @object.require(@path3)
+ end
+
+ t1.join
+ t2.join
+
+ t1_res.should be_true
+ t2_res.should be_true
+
+ ScratchPad.recorded.should == [:con2_pre, :con3, :con2_post]
+ end
+
+ it "allows a 2nd require if the 1st raised an exception" do
+ fin = false
+
+ t2_res = nil
+
+ t2 = nil
+ t1 = Thread.new do
+ Thread.pass until t2
+ Thread.current[:wait_for] = t2
+ Thread.current[:con_raise] = true
+
+ lambda {
+ @object.require(@path)
+ }.should raise_error(RuntimeError)
+
+ Thread.pass until fin
+ ScratchPad.recorded << :t1_post
+ end
+
+ t2 = Thread.new do
+ Thread.pass until t1[:in_concurrent_rb]
+ $VERBOSE, @verbose = nil, $VERBOSE
+ begin
+ t2_res = @object.require(@path)
+ ScratchPad.recorded << :t2_post
+ ensure
+ $VERBOSE = @verbose
+ fin = true
+ end
+ end
+
+ t1.join
+ t2.join
+
+ t2_res.should be_true
+
+ ScratchPad.recorded.should == [:con_pre, :con_pre, :con_post, :t2_post, :t1_post]
+ end
+
+ # "redmine #5754"
+ it "blocks a 3rd require if the 1st raises an exception and the 2nd is still running" do
+ fin = false
+
+ t1_res = nil
+ t2_res = nil
+
+ raised = false
+
+ t2 = nil
+ t1 = Thread.new do
+ Thread.current[:con_raise] = true
+
+ lambda {
+ @object.require(@path)
+ }.should raise_error(RuntimeError)
+
+ raised = true
+
+ # This hits the bug. Because MRI removes its internal lock from a table
+ # when the exception is raised, this #require doesn't see that t2 is in
+ # the middle of requiring the file, so this #require runs when it should not.
+ Thread.pass until t2 && t2[:in_concurrent_rb]
+ t1_res = @object.require(@path)
+
+ Thread.pass until fin
+ ScratchPad.recorded << :t1_post
+ end
+
+ t2 = Thread.new do
+ Thread.pass until raised
+ Thread.current[:wait_for] = t1
+ begin
+ t2_res = @object.require(@path)
+ ScratchPad.recorded << :t2_post
+ ensure
+ fin = true
+ end
+ end
+
+ t1.join
+ t2.join
+
+ t1_res.should be_false
+ t2_res.should be_true
+
+ ScratchPad.recorded.should == [:con_pre, :con_pre, :con_post, :t2_post, :t1_post]
+ end
+ end
+
+ it "stores the missing path in a LoadError object" do
+ path = "abcd1234"
+
+ lambda {
+ @object.send(@method, path)
+ }.should raise_error(LoadError) { |e|
+ e.path.should == path
+ }
+ end
+end