diff options
Diffstat (limited to 'spec/ruby/core/kernel/shared/require.rb')
| -rw-r--r-- | spec/ruby/core/kernel/shared/require.rb | 848 |
1 files changed, 848 insertions, 0 deletions
diff --git a/spec/ruby/core/kernel/shared/require.rb b/spec/ruby/core/kernel/shared/require.rb new file mode 100644 index 0000000000..52f86f73e5 --- /dev/null +++ b/spec/ruby/core/kernel/shared/require.rb @@ -0,0 +1,848 @@ +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.should_not.exist?(path) + -> { @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 + as_user 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.should.exist?(@path) + -> { @object.send(@method, @path) }.should raise_error(LoadError) + end + 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 + -> { @object.send(@method, nil) }.should raise_error(TypeError) + end + + it "raises a TypeError if passed an Integer" do + -> { @object.send(@method, 42) }.should raise_error(TypeError) + end + + it "raises a TypeError if passed an Array" do + -> { @object.send(@method, []) }.should raise_error(TypeError) + end + + it "raises a TypeError if passed an object that does not provide #to_str" do + -> { @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 + -> { @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) + -> { @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 "accepts an Object with #to_path in $LOAD_PATH" do + obj = mock("to_path") + obj.should_receive(:to_path).at_least(:once).and_return(CODE_LOADING_DIR) + $LOAD_PATH << obj + @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.push 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 + -> 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 + -> 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 + it "loads .rb file when passed absolute path without extension" do + path = File.expand_path "load_fixture", CODE_LOADING_DIR + @object.send(@method, path).should be_true + # This should _not_ be [:no_ext] + ScratchPad.recorded.should == [:loaded] + end + + platform_is :linux, :darwin do + it "loads c-extension file when passed absolute path without extension when no .rb is present" do + # the error message is specific to what dlerror() returns + path = File.join CODE_LOADING_DIR, "a", "load_fixture" + -> { @object.send(@method, path) }.should raise_error(LoadError) + end + end + + platform_is :darwin do + it "loads .bundle file when passed absolute path with .so" do + # the error message is specific to what dlerror() returns + path = File.join CODE_LOADING_DIR, "a", "load_fixture.so" + -> { @object.send(@method, path) }.should raise_error(LoadError) + end + end + + it "does not try an extra .rb if the path already ends in .rb" do + path = File.join CODE_LOADING_DIR, "d", "load_fixture.rb" + -> { @object.send(@method, path) }.should raise_error(LoadError) + end + + # 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 + -> { @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 + -> 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 + -> { + @object.require(path).should be_true + }.should complain(/circular require considered harmful/, verbose: true) + ScratchPad.recorded.should == [:loaded] + end + + ruby_bug "#17340", ''...'3.3' do + it "loads a file concurrently" do + path = File.expand_path "concurrent_require_fixture.rb", CODE_LOADING_DIR + ScratchPad.record(@object) + -> { + @object.require(path) + }.should_not complain(/circular require considered harmful/, verbose: true) + ScratchPad.recorded.join + end + 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 + + it "does not load a feature twice when $LOAD_PATH has been modified" do + $LOAD_PATH.replace [CODE_LOADING_DIR] + @object.require("load_fixture").should be_true + $LOAD_PATH.replace [File.expand_path("b", CODE_LOADING_DIR), CODE_LOADING_DIR] + @object.require("load_fixture").should be_false + end + + it "stores the missing path in a LoadError object" do + path = "abcd1234" + + -> { + @object.send(@method, path) + }.should raise_error(LoadError) { |e| + e.path.should == path + } + 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.should.exist?(path) + @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.should.exist?(path) + @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 + + 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 + + it "requires only once when a new matching file added to path" do + @object.require('load_fixture').should be_true + ScratchPad.recorded.should == [:loaded] + + symlink_to_code_dir_two = tmp("codesymlinktwo") + File.symlink("#{CODE_LOADING_DIR}/b", symlink_to_code_dir_two) + begin + $LOAD_PATH.unshift(symlink_to_code_dir_two) + + @object.require('load_fixture').should be_false + ensure + rm_r symlink_to_code_dir_two + end + 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 + + 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 + + it "does not store the path if the load fails" do + $LOAD_PATH << CODE_LOADING_DIR + saved_loaded_features = $LOADED_FEATURES.dup + -> { @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 "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) + 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 + + it "unicode_normalize is part of core and not $LOADED_FEATURES" do + features = ruby_exe("puts $LOADED_FEATURES", options: '--disable-gems') + features.lines.each { |feature| + feature.should_not include("unicode_normalize") + } + + -> { @object.require("unicode_normalize") }.should raise_error(LoadError) + end + + it "does not load a file earlier on the $LOAD_PATH when other similar features were already loaded" do + Dir.chdir CODE_LOADING_DIR do + @object.send(@method, "../code/load_fixture").should be_true + end + ScratchPad.recorded.should == [:loaded] + + $LOAD_PATH.unshift "#{CODE_LOADING_DIR}/b" + # This loads because the above load was not on the $LOAD_PATH + @object.send(@method, "load_fixture").should be_true + ScratchPad.recorded.should == [:loaded, :loaded] + + $LOAD_PATH.unshift "#{CODE_LOADING_DIR}/c" + # This does not load because the above load was on the $LOAD_PATH + @object.send(@method, "load_fixture").should be_false + ScratchPad.recorded.should == [:loaded, :loaded] + 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 + + -> { + @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 + + -> { + @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" + + -> { + @object.send(@method, path) + }.should raise_error(LoadError) { |e| + e.path.should == path + } + end + + platform_is :linux, :darwin do + it "does not store the missing path in a LoadError object when c-extension file exists but loading fails and passed absolute path without extension" do + # the error message is specific to what dlerror() returns + path = File.join CODE_LOADING_DIR, "a", "load_fixture" + -> { @object.send(@method, path) }.should raise_error(LoadError) { |e| + e.path.should == nil + } + end + end + + platform_is :darwin do + it "does not store the missing path in a LoadError object when c-extension file exists but loading fails and passed absolute path with extension" do + # the error message is specific to what dlerror() returns + path = File.join CODE_LOADING_DIR, "a", "load_fixture.bundle" + -> { @object.send(@method, path) }.should raise_error(LoadError) { |e| + e.path.should == nil + } + end + end +end |
