diff options
Diffstat (limited to 'spec/ruby/core/kernel/shared')
| -rw-r--r-- | spec/ruby/core/kernel/shared/dup_clone.rb | 54 | ||||
| -rw-r--r-- | spec/ruby/core/kernel/shared/kind_of.rb | 8 | ||||
| -rw-r--r-- | spec/ruby/core/kernel/shared/lambda.rb | 4 | ||||
| -rw-r--r-- | spec/ruby/core/kernel/shared/load.rb | 123 | ||||
| -rw-r--r-- | spec/ruby/core/kernel/shared/method.rb | 18 | ||||
| -rw-r--r-- | spec/ruby/core/kernel/shared/require.rb | 345 | ||||
| -rw-r--r-- | spec/ruby/core/kernel/shared/sprintf.rb | 209 | ||||
| -rw-r--r-- | spec/ruby/core/kernel/shared/sprintf_encoding.rb | 49 | ||||
| -rw-r--r-- | spec/ruby/core/kernel/shared/then.rb | 12 |
9 files changed, 526 insertions, 296 deletions
diff --git a/spec/ruby/core/kernel/shared/dup_clone.rb b/spec/ruby/core/kernel/shared/dup_clone.rb index a52ccab359..3fbf918283 100644 --- a/spec/ruby/core/kernel/shared/dup_clone.rb +++ b/spec/ruby/core/kernel/shared/dup_clone.rb @@ -40,7 +40,7 @@ describe :kernel_dup_clone, shared: true do o.obj = array o2 = o.send(@method) - o2.obj.should equal(o.obj) + o2.obj.should.equal?(o.obj) end it "calls #initialize_copy on the NEW object if available, passing in original object" do @@ -49,19 +49,7 @@ describe :kernel_dup_clone, shared: true do o.obj.should == :original o2.obj.should == :init_copy - o2.original.should equal(o) - end - - ruby_version_is ''...'2.7' do - 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 + o2.original.should.equal?(o) end it "does not preserve the object_id" do @@ -71,18 +59,6 @@ describe :kernel_dup_clone, shared: true do o2.object_id.should_not == old_object_id end - ruby_version_is ''...'2.7' do - 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 - end - it "returns nil for NilClass" do nil.send(@method).should == nil end @@ -103,27 +79,13 @@ describe :kernel_dup_clone, shared: true do :my_symbol.send(@method).should == :my_symbol end - ruby_version_is ''...'2.5' do - it "raises a TypeError for Complex" do - c = Complex(1.3, 3.1) - -> { c.send(@method) }.should raise_error(TypeError) - end - - it "raises a TypeError for Rational" do - r = Rational(1, 3) - -> { r.send(@method) }.should raise_error(TypeError) - end + it "returns self for Complex" do + c = Complex(1.3, 3.1) + c.send(@method).should.equal? c end - ruby_version_is '2.5' do - it "returns self for Complex" do - c = Complex(1.3, 3.1) - c.send(@method).should equal c - end - - it "returns self for Rational" do - r = Rational(1, 3) - r.send(@method).should equal r - end + it "returns self for Rational" do + r = Rational(1, 3) + r.send(@method).should.equal? r end end diff --git a/spec/ruby/core/kernel/shared/kind_of.rb b/spec/ruby/core/kernel/shared/kind_of.rb index aef6f1c1d8..a5e0eab08f 100644 --- a/spec/ruby/core/kernel/shared/kind_of.rb +++ b/spec/ruby/core/kernel/shared/kind_of.rb @@ -40,10 +40,10 @@ describe :kernel_kind_of, shared: true do end it "raises a TypeError if given an object that is not a Class nor a Module" do - -> { @o.send(@method, 1) }.should raise_error(TypeError) - -> { @o.send(@method, 'KindaClass') }.should raise_error(TypeError) - -> { @o.send(@method, :KindaClass) }.should raise_error(TypeError) - -> { @o.send(@method, Object.new) }.should raise_error(TypeError) + -> { @o.send(@method, 1) }.should.raise(TypeError) + -> { @o.send(@method, 'KindaClass') }.should.raise(TypeError) + -> { @o.send(@method, :KindaClass) }.should.raise(TypeError) + -> { @o.send(@method, Object.new) }.should.raise(TypeError) end it "does not take into account `class` method overriding" do diff --git a/spec/ruby/core/kernel/shared/lambda.rb b/spec/ruby/core/kernel/shared/lambda.rb index c7180e1442..83de06bb41 100644 --- a/spec/ruby/core/kernel/shared/lambda.rb +++ b/spec/ruby/core/kernel/shared/lambda.rb @@ -4,6 +4,8 @@ describe :kernel_lambda, shared: true do end it "raises an ArgumentError when no block is given" do - -> { send(@method) }.should raise_error(ArgumentError) + suppress_warning do + -> { send(@method) }.should.raise(ArgumentError) + end end end diff --git a/spec/ruby/core/kernel/shared/load.rb b/spec/ruby/core/kernel/shared/load.rb index 120619abef..20d23a623e 100644 --- a/spec/ruby/core/kernel/shared/load.rb +++ b/spec/ruby/core/kernel/shared/load.rb @@ -1,5 +1,6 @@ main = self +# The big difference is Kernel#load does not attempt to add an extension to the passed path, unlike Kernel#require describe :kernel_load, shared: true do before :each do CodeLoadingSpecs.spec_setup @@ -10,114 +11,123 @@ describe :kernel_load, shared: true 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 + describe "(path resolution)" do + # This behavior is specific to Kernel#load, it differs for Kernel#require + 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 == 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 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 == 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] + it "loads from the current working directory" do + Dir.chdir CODE_LOADING_DIR do + @object.load("load_fixture.rb").should == true + ScratchPad.recorded.should == [:loaded] + end + end + + # This behavior is specific to Kernel#load, it differs for Kernel#require + it "does not look for a c-extension file when passed a path without extension (when no .rb is present)" do + path = File.join CODE_LOADING_DIR, "a", "load_fixture" + -> { @object.send(@method, path) }.should.raise(LoadError) end end it "loads a file that recursively requires itself" do path = File.expand_path "recursive_require_fixture.rb", CODE_LOADING_DIR -> { - @object.load(path).should be_true + @object.load(path).should == true }.should complain(/circular require considered harmful/, verbose: true) 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 + @object.load(path).should == 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 + @object.load(@path).should == true + @object.load(@path).should == 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 + @object.load(@path).should == 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 + @object.require(@path).should == true + @object.load(@path).should == 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 + @object.load("load_fixture.rb").should == true $LOAD_PATH.unshift CODE_LOADING_DIR + "/gem" - @object.load("load_fixture.rb").should be_true + @object.load("load_fixture.rb").should == 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 + @object.load(@path).should == true + @object.require(@path).should == 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 + @object.load(@path).should == 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 - -> { @object.load(path) }.should raise_error(LoadError) + -> { @object.load(path) }.should.raise(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 + path = File.expand_path "load_wrap_fixture.rb", CODE_LOADING_DIR + @object.load(path, true).should == true end it "sets the enclosing scope to an anonymous module" do - path = File.expand_path "wrap_fixture.rb", CODE_LOADING_DIR + path = File.expand_path "load_wrap_fixture.rb", CODE_LOADING_DIR @object.load(path, true) - Object.const_defined?(:LoadSpecWrap).should be_false + Object.const_defined?(:LoadSpecWrap).should == false wrap_module = ScratchPad.recorded[1] - wrap_module.should be_an_instance_of(Module) + wrap_module.should.instance_of?(Module) end it "allows referencing outside namespaces" do - path = File.expand_path "wrap_fixture.rb", CODE_LOADING_DIR + path = File.expand_path "load_wrap_fixture.rb", CODE_LOADING_DIR @object.load(path, true) - ScratchPad.recorded[0].should equal(String) + ScratchPad.recorded[0].should.equal?(String) end it "sets self as a copy of the top-level main" do - path = File.expand_path "wrap_fixture.rb", CODE_LOADING_DIR + path = File.expand_path "load_wrap_fixture.rb", CODE_LOADING_DIR @object.load(path, true) top_level = ScratchPad.recorded[2] top_level.to_s.should == "main" top_level.method(:to_s).owner.should == top_level.singleton_class - top_level.should_not equal(main) - top_level.should be_an_instance_of(Object) + top_level.should_not.equal?(main) + top_level.should.instance_of?(Object) end it "includes modules included in main's singleton class in self's class" do @@ -127,7 +137,7 @@ describe :kernel_load, shared: true do main_ancestors = main.singleton_class.ancestors[1..-1] main_ancestors.first.should == mod - path = File.expand_path "wrap_fixture.rb", CODE_LOADING_DIR + path = File.expand_path "load_wrap_fixture.rb", CODE_LOADING_DIR @object.load(path, true) top_level = ScratchPad.recorded[2] @@ -149,11 +159,44 @@ describe :kernel_load, shared: true do end it "does not pollute the receiver" do - -> { @object.send(:top_level_method) }.should raise_error(NameError) + -> { @object.send(:top_level_method) }.should.raise(NameError) end end end + describe "when passed a module for 'wrap'" do + it "sets the enclosing scope to the supplied module" do + path = File.expand_path "load_wrap_fixture.rb", CODE_LOADING_DIR + mod = Module.new + @object.load(path, mod) + + Object.const_defined?(:LoadSpecWrap).should == false + mod.const_defined?(:LoadSpecWrap).should == true + + wrap_module = ScratchPad.recorded[1] + wrap_module.should == mod + end + + it "makes constants and instance methods in the source file reachable with the supplied module" do + path = File.expand_path "load_wrap_fixture.rb", CODE_LOADING_DIR + mod = Module.new + @object.load(path, mod) + + mod::LOAD_WRAP_SPECS_TOP_LEVEL_CONSTANT.should == 1 + obj = Object.new + obj.extend(mod) + obj.send(:load_wrap_specs_top_level_method).should == :load_wrap_specs_top_level_method + end + + it "makes instance methods in the source file private" do + path = File.expand_path "load_wrap_fixture.rb", CODE_LOADING_DIR + mod = Module.new + @object.load(path, mod) + + mod.private_instance_methods.include?(:load_wrap_specs_top_level_method).should == true + end + end + describe "(shell expansion)" do before :each do @env_home = ENV["HOME"] @@ -165,7 +208,7 @@ describe :kernel_load, shared: true do end it "expands a tilde to the HOME environment variable as the path to load" do - @object.require("~/load_fixture.rb").should be_true + @object.require("~/load_fixture.rb").should == true ScratchPad.recorded.should == [:loaded] end end diff --git a/spec/ruby/core/kernel/shared/method.rb b/spec/ruby/core/kernel/shared/method.rb index 3418966b1b..82abc287d1 100644 --- a/spec/ruby/core/kernel/shared/method.rb +++ b/spec/ruby/core/kernel/shared/method.rb @@ -4,20 +4,26 @@ 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.should.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.should.instance_of? Method m.call.should == 'class done' end - it "returns a method object if we repond_to_missing? method" do + it "returns a method object if respond_to_missing?(method) is true" do m = KernelSpecs::RespondViaMissing.new.send(@method, :handled_publicly) - m.should be_an_instance_of Method + m.should.instance_of? Method + m.call(42).should == "Done handled_publicly([42])" + end + + it "the returned method object if respond_to_missing?(method) calls #method_missing with a Symbol name" do + m = KernelSpecs::RespondViaMissing.new.send(@method, "handled_publicly") + m.should.instance_of? Method m.call(42).should == "Done handled_publicly([42])" end @@ -25,12 +31,12 @@ describe :kernel_method, shared: true do class KernelSpecs::Foo; def bar; 'done'; end; end -> { KernelSpecs::Foo.new.send(@method, :invalid_and_silly_method_name) - }.should raise_error(NameError) + }.should.raise(NameError) end it "raises a NameError for an invalid singleton method name" do class KernelSpecs::Foo; def self.bar; 'done'; end; end - -> { KernelSpecs::Foo.send(@method, :baz) }.should raise_error(NameError) + -> { KernelSpecs::Foo.send(@method, :baz) }.should.raise(NameError) end it "changes the method called for super on a target aliased method" do diff --git a/spec/ruby/core/kernel/shared/require.rb b/spec/ruby/core/kernel/shared/require.rb index 52089f22fe..6272b00665 100644 --- a/spec/ruby/core/kernel/shared/require.rb +++ b/spec/ruby/core/kernel/shared/require.rb @@ -2,26 +2,26 @@ 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 + @object.send(@method, path).should == 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 + @object.send(@method, path).should == 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 + @object.send(@method, path).should == 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) + -> { @object.send(@method, path) }.should.raise(LoadError) ScratchPad.recorded.should == [] end @@ -42,7 +42,7 @@ describe :kernel_require_basic, shared: true do it "raises a LoadError" do File.should.exist?(@path) - -> { @object.send(@method, @path) }.should raise_error(LoadError) + -> { @object.send(@method, @path) }.should.raise(LoadError) end end end @@ -52,24 +52,24 @@ describe :kernel_require_basic, shared: true 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 + @object.send(@method, name).should == true ScratchPad.recorded.should == [:loaded] end it "raises a TypeError if passed nil" do - -> { @object.send(@method, nil) }.should raise_error(TypeError) + -> { @object.send(@method, nil) }.should.raise(TypeError) end - it "raises a TypeError if passed a Fixnum" do - -> { @object.send(@method, 42) }.should raise_error(TypeError) + it "raises a TypeError if passed an Integer" do + -> { @object.send(@method, 42) }.should.raise(TypeError) end it "raises a TypeError if passed an Array" do - -> { @object.send(@method, []) }.should raise_error(TypeError) + -> { @object.send(@method, []) }.should.raise(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) + -> { @object.send(@method, mock("not a filename")) }.should.raise(TypeError) end it "raises a TypeError if passed an object that has #to_s but not #to_str" do @@ -77,14 +77,14 @@ describe :kernel_require_basic, shared: true do 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) + -> { @object.send(@method, name) }.should.raise(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) + -> { @object.send(@method, name) }.should.raise(TypeError) end it "calls #to_path on non-String objects" do @@ -92,7 +92,7 @@ describe :kernel_require_basic, shared: true do name.stub!(:to_path).and_return("load_fixture.rb") $LOAD_PATH << "." Dir.chdir CODE_LOADING_DIR do - @object.send(@method, name).should be_true + @object.send(@method, name).should == true end ScratchPad.recorded.should == [:loaded] end @@ -101,7 +101,7 @@ describe :kernel_require_basic, shared: true 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 + @object.send(@method, str).should == true ScratchPad.recorded.should == [:loaded] end @@ -111,21 +111,21 @@ describe :kernel_require_basic, shared: true do 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 + @object.send(@method, name).should == 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 + @object.send(@method, "./load_fixture.rb").should == 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 + @object.send(@method, "../code/load_fixture.rb").should == true end ScratchPad.recorded.should == [:loaded] end @@ -133,7 +133,7 @@ describe :kernel_require_basic, shared: true do 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 + @object.send(@method, "./load_fixture.rb").should == true end ScratchPad.recorded.should == [:loaded] end @@ -141,7 +141,7 @@ describe :kernel_require_basic, shared: true do 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 + @object.send(@method, "../code/load_fixture.rb").should == true end ScratchPad.recorded.should == [:loaded] end @@ -149,22 +149,30 @@ describe :kernel_require_basic, shared: true do 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 + @object.send(@method, "../code/../code/load_fixture.rb").should == 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 + @object.send(@method, "load_fixture.rb").should == 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 == 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 + @object.require("load_fixture.rb").should == true $LOAD_PATH.push CODE_LOADING_DIR + "/gem" - @object.require("load_fixture.rb").should be_false + @object.require("load_fixture.rb").should == false ScratchPad.recorded.should == [:loaded] end @@ -172,7 +180,7 @@ describe :kernel_require_basic, shared: true do $LOAD_PATH << CODE_LOADING_DIR -> do @object.send(@method, "./load_fixture.rb") - end.should raise_error(LoadError) + end.should.raise(LoadError) ScratchPad.recorded.should == [] end @@ -180,13 +188,13 @@ describe :kernel_require_basic, shared: true do $LOAD_PATH << CODE_LOADING_DIR -> do @object.send(@method, "../code/load_fixture.rb") - end.should raise_error(LoadError) + end.should.raise(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 + @object.send(@method, "code/../code/load_fixture.rb").should == true ScratchPad.recorded.should == [:loaded] end @@ -195,7 +203,7 @@ describe :kernel_require_basic, shared: true do 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 + @object.send(@method, path).should == true end ScratchPad.recorded.should == [:loaded] end @@ -204,11 +212,39 @@ 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 == 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(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(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(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) + -> { @object.require("load_fixture.rb") }.should.raise(LoadError) ScratchPad.recorded.should == [] end end @@ -217,7 +253,7 @@ describe :kernel_require, shared: true do Dir.chdir File.dirname(CODE_LOADING_DIR) do -> do @object.require("code/load_fixture.rb") - end.should raise_error(LoadError) + end.should.raise(LoadError) ScratchPad.recorded.should == [] end end @@ -225,10 +261,19 @@ describe :kernel_require, shared: true do 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 + @object.require(path).should == true }.should complain(/circular require considered harmful/, verbose: true) ScratchPad.recorded.should == [:loaded] end + + 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 describe "(non-extensioned path)" do @@ -239,16 +284,33 @@ describe :kernel_require, shared: true do 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 + @object.require("load_fixture").should == 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 == true + $LOAD_PATH.replace [File.expand_path("b", CODE_LOADING_DIR), CODE_LOADING_DIR] + @object.require("load_fixture").should == false + end + + it "stores the missing path in a LoadError object" do + path = "abcd1234" + + -> { + @object.send(@method, path) + }.should.raise(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 + @object.require(path).should == true ScratchPad.recorded.should == [:loaded] end @@ -258,21 +320,21 @@ describe :kernel_require, shared: true do $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 + @object.require(path).should == 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 + @object.require(path).should == 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 + @object.require(path).should == true ScratchPad.recorded.should == [:loaded] end @@ -282,14 +344,14 @@ describe :kernel_require, shared: true do $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 + @object.require(path).should == 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 + @object.require(path).should == false ScratchPad.recorded.should == [] end end @@ -300,8 +362,8 @@ describe :kernel_require, shared: true do end it "stores an absolute path" do - @object.require(@path).should be_true - $LOADED_FEATURES.should include(@path) + @object.require(@path).should == true + $LOADED_FEATURES.should.include?(@path) end platform_is_not :windows do @@ -321,20 +383,35 @@ describe :kernel_require, shared: true do 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 + @object.require(symlink_path).should == 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) + 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 + @object.require(symlink_path).should == 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 == 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 == false + ensure + rm_r symlink_to_code_dir_two + end + end end describe "with symlinks in the required feature and $LOAD_PATH" do @@ -354,26 +431,13 @@ describe :kernel_require, shared: true 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 + 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 == 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 @@ -381,20 +445,20 @@ describe :kernel_require, shared: true do 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) + -> { @object.require("raise_fixture.rb") }.should.raise(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 + @object.require(@path).should == 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 + @object.require("./load_fixture.rb").should == false end ScratchPad.recorded.should == [] end @@ -402,7 +466,7 @@ describe :kernel_require, shared: true do 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 + @object.require("../load_fixture.rb").should == false end ScratchPad.recorded.should == [] end @@ -410,27 +474,27 @@ describe :kernel_require, shared: true do 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 + @object.require("code/../code/load_fixture.rb").should == 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) + @object.require(@path).should == 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) + $LOADED_FEATURES.should_not.include?(@path) + @object.require(@path).should == 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 + @object.require("load_fixture.rb").should == true + @object.require("load_fixture").should == false end describe "when a non-extensioned file is in $LOADED_FEATURES" do @@ -440,19 +504,19 @@ describe :kernel_require, shared: true do 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 + @object.require("load_fixture").should == 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 + @object.require("code/load_fixture").should == 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 + @object.require("load_fixture").should == false ScratchPad.recorded.should == [] end end @@ -460,7 +524,7 @@ describe :kernel_require, shared: true do 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 + @object.require("code/load_fixture").should == false ScratchPad.recorded.should == [] end end @@ -468,16 +532,16 @@ describe :kernel_require, shared: true do it "stores ../ relative paths as absolute paths" do Dir.chdir CODE_LOADING_DIR do - @object.require("../code/load_fixture.rb").should be_true + @object.require("../code/load_fixture.rb").should == true end - $LOADED_FEATURES.should include(@path) + $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 + @object.require("./load_fixture.rb").should == true end - $LOADED_FEATURES.should include(@path) + $LOADED_FEATURES.should.include?(@path) end it "collapses duplicate path separators" do @@ -485,27 +549,27 @@ describe :kernel_require, shared: true do sep = File::Separator + File::Separator path = ["..", "code", "load_fixture.rb"].join(sep) Dir.chdir CODE_LOADING_DIR do - @object.require(path).should be_true + @object.require(path).should == true end - $LOADED_FEATURES.should include(@path) + $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) + @object.require(path).should == 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) + @object.require("load_fixture").should == 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 + @object.require("code/../code/load_fixture.rb").should == false ScratchPad.recorded.should == [] end @@ -513,7 +577,7 @@ describe :kernel_require, shared: true do $LOADED_FEATURES << @path $LOAD_PATH << "an_irrelevant_dir" Dir.chdir CODE_LOADING_DIR do - @object.require("./load_fixture.rb").should be_false + @object.require("./load_fixture.rb").should == false end ScratchPad.recorded.should == [] end @@ -522,46 +586,35 @@ describe :kernel_require, shared: true do $LOADED_FEATURES << @path $LOAD_PATH << "an_irrelevant_dir" Dir.chdir CODE_LOADING_DIR do - @object.require("../code/load_fixture.rb").should be_false + @object.require("../code/load_fixture.rb").should == false end ScratchPad.recorded.should == [] end - ruby_version_is ""..."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)$/ - } + 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") + } - code = provided.map { |f| "puts require #{f.inspect}\n" }.join - required = ruby_exe(code, options: '--disable-gems') - required.should == "false\n" * provided.size - end + -> { @object.require("unicode_normalize") }.should.raise(LoadError) 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 + 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 == true end + ScratchPad.recorded.should == [:loaded] - 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") - } + $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 == true + ScratchPad.recorded.should == [:loaded, :loaded] - -> { @object.require("unicode_normalize") }.should raise_error(LoadError) - end + $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 == false + ScratchPad.recorded.should == [:loaded, :loaded] end end @@ -578,13 +631,13 @@ describe :kernel_require, shared: true do # "#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) + @object.require("~/load_fixture.rb").should == 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) + @object.require("~/load_fixture").should == true + $LOADED_FEATURES.should.include?(@path) end end @@ -641,8 +694,8 @@ describe :kernel_require, shared: true do t1.join t2.join - t1_res.should be_true - t2_res.should be_false + t1_res.should == true + t2_res.should == false ScratchPad.recorded.should == [:con_pre, :con_post, :t2_post, :t1_post] end @@ -666,8 +719,8 @@ describe :kernel_require, shared: true do t1.join t2.join - t1_res.should be_true - t2_res.should be_true + t1_res.should == true + t2_res.should == true ScratchPad.recorded.should == [:con2_pre, :con3, :con2_post] end @@ -685,7 +738,7 @@ describe :kernel_require, shared: true do -> { @object.require(@path) - }.should raise_error(RuntimeError) + }.should.raise(RuntimeError) Thread.pass until fin ScratchPad.recorded << :t1_post @@ -706,7 +759,7 @@ describe :kernel_require, shared: true do t1.join t2.join - t2_res.should be_true + t2_res.should == true ScratchPad.recorded.should == [:con_pre, :con_pre, :con_post, :t2_post, :t1_post] end @@ -726,7 +779,7 @@ describe :kernel_require, shared: true do -> { @object.require(@path) - }.should raise_error(RuntimeError) + }.should.raise(RuntimeError) raised = true @@ -754,8 +807,8 @@ describe :kernel_require, shared: true do t1.join t2.join - t1_res.should be_false - t2_res.should be_true + t1_res.should == false + t2_res.should == true ScratchPad.recorded.should == [:con_pre, :con_pre, :con_post, :t2_post, :t1_post] end @@ -766,8 +819,28 @@ describe :kernel_require, shared: true do -> { @object.send(@method, path) - }.should raise_error(LoadError) { |e| + }.should.raise(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(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(LoadError) { |e| + e.path.should == nil + } + end + end end diff --git a/spec/ruby/core/kernel/shared/sprintf.rb b/spec/ruby/core/kernel/shared/sprintf.rb index 8defbfd665..fe77b5f45b 100644 --- a/spec/ruby/core/kernel/shared/sprintf.rb +++ b/spec/ruby/core/kernel/shared/sprintf.rb @@ -22,12 +22,13 @@ describe :kernel_sprintf, shared: true do @method.call("%d", "112").should == "112" @method.call("%d", "0127").should == "87" @method.call("%d", "0xc4").should == "196" + @method.call("%d", "0").should == "0" end it "raises TypeError exception if cannot convert to Integer" do -> { @method.call("%b", Object.new) - }.should raise_error(TypeError) + }.should.raise(TypeError) end ["b", "B"].each do |f| @@ -57,6 +58,11 @@ describe :kernel_sprintf, shared: true do it "works well with large numbers" do @method.call("%#{f}", 1234567890987654321).should == "1234567890987654321" end + + it "converts to the empty string if precision is 0 and value is 0" do + @method.call("%.#{f}", 0).should == "" + @method.call("%.0#{f}", 0).should == "" + end end end @@ -116,7 +122,7 @@ describe :kernel_sprintf, shared: true do it "raises TypeError exception if cannot convert to Float" do -> { @method.call("%f", Object.new) - }.should raise_error(TypeError) + }.should.raise(TypeError) end {"e" => "e", "E" => "E"}.each_pair do |f, exp| @@ -289,21 +295,64 @@ describe :kernel_sprintf, shared: true do @method.call("%c", "a").should == "a" end - it "raises ArgumentError if argument is a string of several characters" do + it "displays only the first character if argument is a string of several characters" do + @method.call("%c", "abc").should == "a" + end + + it "displays no characters if argument is an empty string" do + @method.call("%c", "").should == "" + end + + it "raises TypeError if argument is not String or Integer and cannot be converted to them" do -> { - @method.call("%c", "abc") - }.should raise_error(ArgumentError) + @method.call("%c", []) + }.should raise_consistent_error(TypeError, /no implicit conversion of Array into Integer/) + end + + it "raises TypeError if argument is nil" do + -> { + @method.call("%c", nil) + }.should raise_consistent_error(TypeError, /no implicit conversion of nil into Integer/) + end + + it "tries to convert argument to String with to_str" do + obj = BasicObject.new + def obj.to_str + "a" + end + + @method.call("%c", obj).should == "a" + end + + it "tries to convert argument to Integer with to_int" do + obj = BasicObject.new + def obj.to_int + 90 + end + + @method.call("%c", obj).should == "Z" end - it "raises ArgumentError if argument is an empty string" do + it "raises TypeError if converting to String with to_str returns non-String" do + obj = BasicObject.new + def obj.to_str + :foo + end + -> { - @method.call("%c", "") - }.should raise_error(ArgumentError) + @method.call("%c", obj) + }.should raise_consistent_error(TypeError, /can't convert BasicObject into String/) end - it "supports Unicode characters" do - @method.call("%c", 1286).should == "Ԇ" - @method.call("%c", "ش").should == "ش" + it "raises TypeError if converting to Integer with to_int returns non-Integer" do + obj = BasicObject.new + def obj.to_int + :foo + end + + -> { + @method.call("%c", obj) + }.should raise_consistent_error(TypeError, /can't convert BasicObject into Integer/) end end @@ -313,6 +362,10 @@ describe :kernel_sprintf, shared: true do obj.should_receive(:inspect).and_return("<inspect-result>") @method.call("%p", obj).should == "<inspect-result>" end + + it "substitutes 'nil' for nil" do + @method.call("%p", nil).should == "nil" + end end describe "s" do @@ -320,6 +373,10 @@ describe :kernel_sprintf, shared: true do @method.call("%s", "abc").should == "abc" end + it "substitutes '' for nil" do + @method.call("%s", nil).should == "" + end + it "converts argument to string with to_s" do obj = mock("string") obj.should_receive(:to_s).and_return("abc") @@ -334,28 +391,65 @@ describe :kernel_sprintf, shared: true do -> { @method.call("%s", obj) - }.should raise_error(NoMethodError) + }.should.raise(NoMethodError) end - end - describe "%" do - ruby_version_is ""..."2.5" do - it "alone displays the percent sign" do - @method.call("%").should == "%" - end + it "formats a partial substring without including omitted characters" do + long_string = "aabbccddhelloddccbbaa" + sub_string = long_string[8, 5] + sprintf("%.#{1 * 3}s", sub_string).should == "hel" end - ruby_version_is "2.5" do - it "alone raises an ArgumentError" do - -> { - @method.call("%") - }.should raise_error(ArgumentError) - end + it "formats string with precision" do + Kernel.format("%.3s", "hello").should == "hel" + Kernel.format("%-3.3s", "hello").should == "hel" + end + + it "formats string with width" do + @method.call("%6s", "abc").should == " abc" + @method.call("%6s", "abcdefg").should == "abcdefg" + end + + it "formats string with width and precision" do + @method.call("%4.6s", "abc").should == " abc" + @method.call("%4.6s", "abcdefg").should == "abcdef" + end + + it "formats nil with width" do + @method.call("%6s", nil).should == " " + end + + it "formats nil with precision" do + @method.call("%.6s", nil).should == "" + end + + it "formats nil with width and precision" do + @method.call("%4.6s", nil).should == " " + end + + it "formats multibyte string with precision" do + Kernel.format("%.2s", "été").should == "ét" + end + + it "preserves encoding of the format string" do + str = format('%s'.encode(Encoding::UTF_8), 'foobar') + str.encoding.should == Encoding::UTF_8 + + str = format('%s'.encode(Encoding::US_ASCII), 'foobar') + str.encoding.should == Encoding::US_ASCII + end + end + + describe "%" do + it "alone raises an ArgumentError" do + -> { + @method.call("%") + }.should.raise(ArgumentError) end it "is escaped by %" do @method.call("%%").should == "%" - @method.call("%%d", 10).should == "%d" + @method.call("%%d").should == "%d" end end end @@ -457,7 +551,7 @@ describe :kernel_sprintf, shared: true do it "raises exception if argument number is bigger than actual arguments list" do -> { @method.call("%4$d", 1, 2, 3) - }.should raise_error(ArgumentError) + }.should.raise(ArgumentError) end it "ignores '-' sign" do @@ -468,7 +562,7 @@ describe :kernel_sprintf, shared: true do it "raises ArgumentError exception when absolute and relative argument numbers are mixed" do -> { @method.call("%1$d %d", 1, 2) - }.should raise_error(ArgumentError) + }.should.raise(ArgumentError) end end @@ -718,7 +812,7 @@ describe :kernel_sprintf, shared: true do it "raises ArgumentError when is mixed with width" do -> { @method.call("%*10d", 10, 112) - }.should raise_error(ArgumentError) + }.should.raise(ArgumentError) end end end @@ -817,7 +911,7 @@ describe :kernel_sprintf, shared: true do it "cannot be mixed with unnamed style" do -> { @method.call("%d %<foo>d", 1, foo: "123") - }.should raise_error(ArgumentError) + }.should.raise(ArgumentError) end end @@ -837,13 +931,30 @@ describe :kernel_sprintf, shared: true do it "cannot be mixed with unnamed style" do -> { @method.call("%d %{foo}", 1, foo: "123") - }.should raise_error(ArgumentError) + }.should.raise(ArgumentError) end - it "raises KeyError when there is no matching key" do + it "respects Hash#default when there is no set key" do + @method.call("%{foo}", Hash.new(123)).should == "123" + @method.call("%{foo}", Hash.new { 123 }).should == "123" + end + + it "raises KeyError when Hash#default returns nil" do -> { @method.call("%{foo}", {}) - }.should raise_error(KeyError) + }.should.raise(KeyError, 'key{foo} not found') + + -> { + @method.call("%{foo}", Hash.new(nil)) + }.should.raise(KeyError, 'key{foo} not found') + + -> { + @method.call("%{foo}", Hash.new { nil }) + }.should.raise(KeyError, 'key{foo} not found') + end + + it "accepts a nil value for an existing key" do + @method.call("%{foo}", { foo: nil }).should == "" end it "converts value to String with to_s" do @@ -867,25 +978,27 @@ describe :kernel_sprintf, shared: true do it "raises a KeyError" do -> { @method.call("%<foo>s", @object) - }.should raise_error(KeyError) + }.should.raise(KeyError) end - ruby_version_is "2.5" do - it "sets the Hash as the receiver of KeyError" do - -> { - @method.call("%<foo>s", @object) - }.should raise_error(KeyError) { |err| - err.receiver.should equal(@object) - } - end + it "sets the Hash as the receiver of KeyError" do + -> { + @method.call("%<foo>s", @object) + }.should.raise(KeyError) { |err| + err.receiver.should.equal?(@object) + } + end - it "sets the unmatched key as the key of KeyError" do - -> { - @method.call("%<foo>s", @object) - }.should raise_error(KeyError) { |err| - err.key.to_s.should == 'foo' - } - end + it "sets the unmatched key as the key of KeyError" do + -> { + @method.call("%<foo>s", @object) + }.should.raise(KeyError) { |err| + err.key.to_s.should == 'foo' + } end end + + it "does not raise error when passed more arguments than needed" do + sprintf("%s %d %c", "string", 2, "c", []).should == "string 2 c" + end end diff --git a/spec/ruby/core/kernel/shared/sprintf_encoding.rb b/spec/ruby/core/kernel/shared/sprintf_encoding.rb index 5ca66b9083..849c95cbb7 100644 --- a/spec/ruby/core/kernel/shared/sprintf_encoding.rb +++ b/spec/ruby/core/kernel/shared/sprintf_encoding.rb @@ -1,36 +1,67 @@ +# Keep encoding-related specs in a separate shared example to be able to skip them in IO/File/StringIO specs. +# It's difficult to check result's encoding in the test after writing to a file/io buffer. describe :kernel_sprintf_encoding, shared: true do it "can produce a string with valid encoding" do string = @method.call("good day %{valid}", valid: "e") string.encoding.should == Encoding::UTF_8 - string.valid_encoding?.should be_true + string.valid_encoding?.should == true end it "can produce a string with invalid encoding" do string = @method.call("good day %{invalid}", invalid: "\x80") string.encoding.should == Encoding::UTF_8 - string.valid_encoding?.should be_false + string.valid_encoding?.should == false end it "returns a String in the same encoding as the format String if compatible" do - string = "%s".force_encoding(Encoding::KOI8_U) + string = "%s".dup.force_encoding(Encoding::KOI8_U) result = @method.call(string, "dogs") - result.encoding.should equal(Encoding::KOI8_U) + result.encoding.should.equal?(Encoding::KOI8_U) end it "returns a String in the argument's encoding if format encoding is more restrictive" do - string = "foo %s".force_encoding(Encoding::US_ASCII) - argument = "b\303\274r".force_encoding(Encoding::UTF_8) + string = "foo %s".dup.force_encoding(Encoding::US_ASCII) + argument = "b\303\274r".dup.force_encoding(Encoding::UTF_8) result = @method.call(string, argument) - result.encoding.should equal(Encoding::UTF_8) + result.encoding.should.equal?(Encoding::UTF_8) end - it "raises Encoding::CompatibilityError if both encodings are ASCII compatible and there ano not ASCII characters" do + it "raises Encoding::CompatibilityError if both encodings are ASCII compatible and there are not ASCII characters" do string = "Ä %s".encode('windows-1252') argument = "Ђ".encode('windows-1251') -> { @method.call(string, argument) - }.should raise_error(Encoding::CompatibilityError) + }.should.raise(Encoding::CompatibilityError) + end + + describe "%c" do + it "supports Unicode characters" do + result = @method.call("%c", 1286) + result.should == "Ԇ" + result.bytes.should == [212, 134] + + result = @method.call("%c", "ش") + result.should == "ش" + result.bytes.should == [216, 180] + end + + it "raises error when a codepoint isn't representable in an encoding of a format string" do + format = "%c".encode("ASCII") + + -> { + @method.call(format, 1286) + }.should.raise(RangeError, /out of char range/) + end + + it "uses the encoding of the format string to interpret codepoints" do + format = "%c".dup.force_encoding("euc-jp") + result = @method.call(format, 9415601) + + result.encoding.should == Encoding::EUC_JP + result.should == "é".encode(Encoding::EUC_JP) + result.bytes.should == [143, 171, 177] + end end end diff --git a/spec/ruby/core/kernel/shared/then.rb b/spec/ruby/core/kernel/shared/then.rb index b52075371f..c71393bf50 100644 --- a/spec/ruby/core/kernel/shared/then.rb +++ b/spec/ruby/core/kernel/shared/then.rb @@ -1,20 +1,20 @@ describe :kernel_then, shared: true do it "yields self" do object = Object.new - object.send(@method) { |o| o.should equal object } + object.send(@method) { |o| o.should.equal? object } end it "returns the block return value" do object = Object.new - object.send(@method) { 42 }.should equal 42 + object.send(@method) { 42 }.should.equal? 42 end it "returns a sized Enumerator when no block given" do object = Object.new enum = object.send(@method) - enum.should be_an_instance_of Enumerator - enum.size.should equal 1 - enum.peek.should equal object - enum.first.should equal object + enum.should.instance_of? Enumerator + enum.size.should.equal? 1 + enum.peek.should.equal? object + enum.first.should.equal? object end end |
