diff options
Diffstat (limited to 'spec/ruby/core/kernel/shared')
| -rw-r--r-- | spec/ruby/core/kernel/shared/dup_clone.rb | 94 | ||||
| -rw-r--r-- | spec/ruby/core/kernel/shared/kind_of.rb | 23 | ||||
| -rw-r--r-- | spec/ruby/core/kernel/shared/lambda.rb | 4 | ||||
| -rw-r--r-- | spec/ruby/core/kernel/shared/load.rb | 120 | ||||
| -rw-r--r-- | spec/ruby/core/kernel/shared/method.rb | 14 | ||||
| -rw-r--r-- | spec/ruby/core/kernel/shared/require.rb | 289 | ||||
| -rw-r--r-- | spec/ruby/core/kernel/shared/sprintf.rb | 1004 | ||||
| -rw-r--r-- | spec/ruby/core/kernel/shared/sprintf_encoding.rb | 59 | ||||
| -rw-r--r-- | spec/ruby/core/kernel/shared/then.rb | 20 |
9 files changed, 992 insertions, 635 deletions
diff --git a/spec/ruby/core/kernel/shared/dup_clone.rb b/spec/ruby/core/kernel/shared/dup_clone.rb index 116989958b..4fac6006e1 100644 --- a/spec/ruby/core/kernel/shared/dup_clone.rb +++ b/spec/ruby/core/kernel/shared/dup_clone.rb @@ -52,16 +52,6 @@ describe :kernel_dup_clone, shared: true do 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 @@ -69,81 +59,33 @@ describe :kernel_dup_clone, shared: true do 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 + it "returns nil for NilClass" do + nil.send(@method).should == nil 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 + it "returns true for TrueClass" do + true.send(@method).should == true 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 + it "returns false for FalseClass" do + false.send(@method).should == false end - ruby_version_is ''...'2.5' do - it "raises a TypeError for Complex" do - c = Complex(1.3, 3.1) - lambda { c.send(@method) }.should raise_error(TypeError) - end + it "returns the same Integer for Integer" do + 1.send(@method).should == 1 + end - it "raises a TypeError for Rational" do - r = Rational(1, 3) - lambda { r.send(@method) }.should raise_error(TypeError) - end + it "returns the same Symbol for Symbol" do + :my_symbol.send(@method).should == :my_symbol 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 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 e99f46aa14..aef6f1c1d8 100644 --- a/spec/ruby/core/kernel/shared/kind_of.rb +++ b/spec/ruby/core/kernel/shared/kind_of.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../fixtures/classes', __FILE__) +require_relative '../fixtures/classes' describe :kernel_kind_of, shared: true do before :each do @@ -31,14 +31,25 @@ describe :kernel_kind_of, shared: true 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 + it "returns true if given a Module that object has been prepended with" do + @o.send(@method, KernelSpecs::MyPrependedModule).should == true + end + + it "returns false if given a Module not included nor prepended 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) + -> { @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) + end + + it "does not take into account `class` method overriding" do + def @o.class; Integer; end + + @o.send(@method, Integer).should == false + @o.send(@method, KernelSpecs::KindaClass).should == true end end diff --git a/spec/ruby/core/kernel/shared/lambda.rb b/spec/ruby/core/kernel/shared/lambda.rb index bebb111c43..c70640082a 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 - lambda { send(@method) }.should raise_error(ArgumentError) + suppress_warning do + -> { send(@method) }.should raise_error(ArgumentError) + end end end diff --git a/spec/ruby/core/kernel/shared/load.rb b/spec/ruby/core/kernel/shared/load.rb index 0ce7d58d2c..62c5c7be9b 100644 --- a/spec/ruby/core/kernel/shared/load.rb +++ b/spec/ruby/core/kernel/shared/load.rb @@ -1,3 +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 @@ -8,31 +11,39 @@ 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 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 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] + 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 + + # 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_error(LoadError) 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/) + }.should complain(/circular require considered harmful/, verbose: true) ScratchPad.recorded.should == [:loaded, :loaded] end @@ -82,27 +93,59 @@ describe :kernel_load, shared: true do 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) + -> { @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 + path = File.expand_path "load_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 + path = File.expand_path "load_wrap_fixture.rb", CODE_LOADING_DIR @object.load(path, true) Object.const_defined?(:LoadSpecWrap).should be_false + + wrap_module = ScratchPad.recorded[1] + wrap_module.should be_an_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.first.should be_an_instance_of(Class) + ScratchPad.recorded[0].should equal(String) + end + + it "sets self as a copy of the top-level main" do + 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) + end + + it "includes modules included in main's singleton class in self's class" do + mod = Module.new + main.extend(mod) + + main_ancestors = main.singleton_class.ancestors[1..-1] + main_ancestors.first.should == mod + + path = File.expand_path "load_wrap_fixture.rb", CODE_LOADING_DIR + @object.load(path, true) + + top_level = ScratchPad.recorded[2] + top_level_ancestors = top_level.singleton_class.ancestors[-main_ancestors.size..-1] + top_level_ancestors.should == main_ancestors + + wrap_module = ScratchPad.recorded[1] + top_level.singleton_class.ancestors.should == [top_level.singleton_class, wrap_module, *main_ancestors] end describe "with top-level methods" do @@ -116,11 +159,44 @@ describe :kernel_load, shared: true do end it "does not pollute the receiver" do - lambda { @object.send(:top_level_method) }.should raise_error(NameError) + -> { @object.send(:top_level_method) }.should raise_error(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 be_false + mod.const_defined?(:LoadSpecWrap).should be_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"] diff --git a/spec/ruby/core/kernel/shared/method.rb b/spec/ruby/core/kernel/shared/method.rb index 1566c6ab09..8b6ab23fd3 100644 --- a/spec/ruby/core/kernel/shared/method.rb +++ b/spec/ruby/core/kernel/shared/method.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../../spec_helper', __FILE__) +require_relative '../../../spec_helper' describe :kernel_method, shared: true do it "returns a method object for a valid method" do @@ -15,22 +15,28 @@ describe :kernel_method, shared: true do 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.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 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) + -> { KernelSpecs::Foo.send(@method, :baz) }.should raise_error(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 3296c7f42a..52f86f73e5 100644 --- a/spec/ruby/core/kernel/shared/require.rb +++ b/spec/ruby/core/kernel/shared/require.rb @@ -20,28 +20,30 @@ describe :kernel_require_basic, shared: true do 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) + 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 - 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) + 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 @@ -55,19 +57,19 @@ describe :kernel_require_basic, shared: true do end it "raises a TypeError if passed nil" do - lambda { @object.send(@method, nil) }.should raise_error(TypeError) + -> { @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) + 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 - lambda { @object.send(@method, []) }.should raise_error(TypeError) + -> { @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) + -> { @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 @@ -75,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 - lambda { @object.send(@method, name) }.should raise_error(TypeError) + -> { @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) + -> { @object.send(@method, name) }.should raise_error(TypeError) end it "calls #to_path on non-String objects" do @@ -158,17 +160,25 @@ describe :kernel_require_basic, shared: true do 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.unshift CODE_LOADING_DIR + "/gem" + $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 - lambda do + -> do @object.send(@method, "./load_fixture.rb") end.should raise_error(LoadError) ScratchPad.recorded.should == [] @@ -176,7 +186,7 @@ describe :kernel_require_basic, shared: true do it "does not resolve a ../ relative path against $LOAD_PATH entries" do $LOAD_PATH << CODE_LOADING_DIR - lambda do + -> do @object.send(@method, "../code/load_fixture.rb") end.should raise_error(LoadError) ScratchPad.recorded.should == [] @@ -202,18 +212,46 @@ 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 - lambda { @object.require("load_fixture.rb") }.should raise_error(LoadError) + -> { @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 + -> do @object.require("code/load_fixture.rb") end.should raise_error(LoadError) ScratchPad.recorded.should == [] @@ -223,11 +261,21 @@ 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 -> { - $VERBOSE = true @object.require(path).should be_true - }.should complain(/circular require considered harmful/) + }.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 @@ -241,12 +289,29 @@ describe :kernel_require, shared: true 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.exist?(path).should be_true + File.should.exist?(path) @object.require(path).should be_true ScratchPad.recorded.should == [:loaded] end @@ -270,7 +335,7 @@ describe :kernel_require, shared: true do 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 + File.should.exist?(path) @object.require(path).should be_true ScratchPad.recorded.should == [:loaded] end @@ -303,10 +368,86 @@ describe :kernel_require, shared: true do $LOADED_FEATURES.should include(@path) end + platform_is_not :windows do + describe "with symlinks" do + before :each do + @symlink_to_code_dir = tmp("codesymlink") + File.symlink(CODE_LOADING_DIR, @symlink_to_code_dir) + + $LOAD_PATH.delete(CODE_LOADING_DIR) + $LOAD_PATH.unshift(@symlink_to_code_dir) + end + + after :each do + rm_r @symlink_to_code_dir + end + + it "does not canonicalize the path and stores a path with symlinks" do + symlink_path = "#{@symlink_to_code_dir}/load_fixture.rb" + canonical_path = "#{CODE_LOADING_DIR}/load_fixture.rb" + @object.require(symlink_path).should be_true + ScratchPad.recorded.should == [:loaded] + + features = $LOADED_FEATURES.select { |path| path.end_with?('load_fixture.rb') } + features.should include(symlink_path) + features.should_not include(canonical_path) + end + + it "stores the same path that __FILE__ returns in the required file" do + symlink_path = "#{@symlink_to_code_dir}/load_fixture_and__FILE__.rb" + @object.require(symlink_path).should be_true + loaded_feature = $LOADED_FEATURES.last + ScratchPad.recorded.should == [loaded_feature] + end + + 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 - lambda { @object.require("raise_fixture.rb") }.should raise_error(RuntimeError) + -> { @object.require("raise_fixture.rb") }.should raise_error(RuntimeError) $LOADED_FEATURES.should == saved_loaded_features end @@ -415,7 +556,7 @@ describe :kernel_require, shared: true do $LOADED_FEATURES.should include(@path) end - it "canonicalizes non-unique absolute paths" do + it "expands absolute paths containing .." do path = File.join CODE_LOADING_DIR, "..", "code", "load_fixture.rb" @object.require(path).should be_true $LOADED_FEATURES.should include(@path) @@ -452,46 +593,30 @@ describe :kernel_require, shared: true do 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)$/ - } + 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_error(LoadError) 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 + 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 - end + ScratchPad.recorded.should == [:loaded] - 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)$/ - } + $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] - code = provided.map { |f| "puts require #{f.inspect}\n" }.join - required = ruby_exe(code, options: '--disable-gems') - required.should == "false\n" * provided.size - 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 be_false + ScratchPad.recorded.should == [:loaded, :loaded] end end @@ -613,7 +738,7 @@ describe :kernel_require, shared: true do Thread.current[:wait_for] = t2 Thread.current[:con_raise] = true - lambda { + -> { @object.require(@path) }.should raise_error(RuntimeError) @@ -654,7 +779,7 @@ describe :kernel_require, shared: true do t1 = Thread.new do Thread.current[:con_raise] = true - lambda { + -> { @object.require(@path) }.should raise_error(RuntimeError) @@ -694,10 +819,30 @@ describe :kernel_require, shared: true do 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 + + 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 diff --git a/spec/ruby/core/kernel/shared/sprintf.rb b/spec/ruby/core/kernel/shared/sprintf.rb index e595a06640..2b2c6c9b63 100644 --- a/spec/ruby/core/kernel/shared/sprintf.rb +++ b/spec/ruby/core/kernel/shared/sprintf.rb @@ -1,8 +1,4 @@ describe :kernel_sprintf, shared: true do - def format(*args) - @method.call(*args) - end - describe "integer formats" do it "converts argument into Integer with to_int" do obj = Object.new @@ -10,7 +6,7 @@ describe :kernel_sprintf, shared: true do def obj.to_int; 10; end obj.should_receive(:to_int).and_return(10) - format("%b", obj).should == "1010" + @method.call("%b", obj).should == "1010" end it "converts argument into Integer with to_i if to_int isn't available" do @@ -18,35 +14,36 @@ describe :kernel_sprintf, shared: true do def obj.to_i; 10; end obj.should_receive(:to_i).and_return(10) - format("%b", obj).should == "1010" + @method.call("%b", obj).should == "1010" end it "converts String argument with Kernel#Integer" do - format("%d", "0b1010").should == "10" - format("%d", "112").should == "112" - format("%d", "0127").should == "87" - format("%d", "0xc4").should == "196" + @method.call("%d", "0b1010").should == "10" + @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 - -> () { - format("%b", Object.new) + -> { + @method.call("%b", Object.new) }.should raise_error(TypeError) end ["b", "B"].each do |f| describe f do it "converts argument as a binary number" do - format("%#{f}", 10).should == "1010" + @method.call("%#{f}", 10).should == "1010" end it "displays negative number as a two's complement prefixed with '..1'" do - format("%#{f}", -10).should == "..1" + "0110" + @method.call("%#{f}", -10).should == "..1" + "0110" end it "collapse negative number representation if it equals 1" do - format("%#{f}", -1).should_not == "..11" - format("%#{f}", -1).should == "..1" + @method.call("%#{f}", -1).should_not == "..11" + @method.call("%#{f}", -1).should == "..1" end end end @@ -54,58 +51,63 @@ describe :kernel_sprintf, shared: true do ["d", "i", "u"].each do |f| describe f do it "converts argument as a decimal number" do - format("%#{f}", 112).should == "112" - format("%#{f}", -112).should == "-112" + @method.call("%#{f}", 112).should == "112" + @method.call("%#{f}", -112).should == "-112" end it "works well with large numbers" do - format("%#{f}", 1234567890987654321).should == "1234567890987654321" + @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 describe "o" do it "converts argument as an octal number" do - format("%o", 87).should == "127" + @method.call("%o", 87).should == "127" end it "displays negative number as a two's complement prefixed with '..7'" do - format("%o", -87).should == "..7" + "651" + @method.call("%o", -87).should == "..7" + "651" end it "collapse negative number representation if it equals 7" do - format("%o", -1).should_not == "..77" - format("%o", -1).should == "..7" + @method.call("%o", -1).should_not == "..77" + @method.call("%o", -1).should == "..7" end end describe "x" do it "converts argument as a hexadecimal number" do - format("%x", 196).should == "c4" + @method.call("%x", 196).should == "c4" end it "displays negative number as a two's complement prefixed with '..f'" do - format("%x", -196).should == "..f" + "3c" + @method.call("%x", -196).should == "..f" + "3c" end it "collapse negative number representation if it equals f" do - format("%x", -1).should_not == "..ff" - format("%x", -1).should == "..f" + @method.call("%x", -1).should_not == "..ff" + @method.call("%x", -1).should == "..f" end end describe "X" do it "converts argument as a hexadecimal number with uppercase letters" do - format("%X", 196).should == "C4" + @method.call("%X", 196).should == "C4" end it "displays negative number as a two's complement prefixed with '..f'" do - format("%X", -196).should == "..F" + "3C" + @method.call("%X", -196).should == "..F" + "3C" end it "collapse negative number representation if it equals F" do - format("%X", -1).should_not == "..FF" - format("%X", -1).should == "..F" + @method.call("%X", -1).should_not == "..FF" + @method.call("%X", -1).should == "..F" end end end @@ -114,70 +116,70 @@ describe :kernel_sprintf, shared: true do it "converts argument into Float" do obj = mock("float") obj.should_receive(:to_f).and_return(9.6) - format("%f", obj).should == "9.600000" + @method.call("%f", obj).should == "9.600000" end it "raises TypeError exception if cannot convert to Float" do - -> () { - format("%f", Object.new) + -> { + @method.call("%f", Object.new) }.should raise_error(TypeError) end {"e" => "e", "E" => "E"}.each_pair do |f, exp| describe f do it "converts argument into exponential notation [-]d.dddddde[+-]dd" do - format("%#{f}", 109.52).should == "1.095200#{exp}+02" - format("%#{f}", -109.52).should == "-1.095200#{exp}+02" - format("%#{f}", 0.10952).should == "1.095200#{exp}-01" - format("%#{f}", -0.10952).should == "-1.095200#{exp}-01" + @method.call("%#{f}", 109.52).should == "1.095200#{exp}+02" + @method.call("%#{f}", -109.52).should == "-1.095200#{exp}+02" + @method.call("%#{f}", 0.10952).should == "1.095200#{exp}-01" + @method.call("%#{f}", -0.10952).should == "-1.095200#{exp}-01" end it "cuts excessive digits and keeps only 6 ones" do - format("%#{f}", 1.123456789).should == "1.123457#{exp}+00" + @method.call("%#{f}", 1.123456789).should == "1.123457#{exp}+00" end it "rounds the last significant digit to the closest one" do - format("%#{f}", 1.555555555).should == "1.555556#{exp}+00" - format("%#{f}", -1.555555555).should == "-1.555556#{exp}+00" - format("%#{f}", 1.444444444).should == "1.444444#{exp}+00" + @method.call("%#{f}", 1.555555555).should == "1.555556#{exp}+00" + @method.call("%#{f}", -1.555555555).should == "-1.555556#{exp}+00" + @method.call("%#{f}", 1.444444444).should == "1.444444#{exp}+00" end it "displays Float::INFINITY as Inf" do - format("%#{f}", Float::INFINITY).should == "Inf" - format("%#{f}", -Float::INFINITY).should == "-Inf" + @method.call("%#{f}", Float::INFINITY).should == "Inf" + @method.call("%#{f}", -Float::INFINITY).should == "-Inf" end it "displays Float::NAN as NaN" do - format("%#{f}", Float::NAN).should == "NaN" - format("%#{f}", -Float::NAN).should == "NaN" + @method.call("%#{f}", Float::NAN).should == "NaN" + @method.call("%#{f}", -Float::NAN).should == "NaN" end end end describe "f" do it "converts floating point argument as [-]ddd.dddddd" do - format("%f", 10.952).should == "10.952000" - format("%f", -10.952).should == "-10.952000" + @method.call("%f", 10.952).should == "10.952000" + @method.call("%f", -10.952).should == "-10.952000" end it "cuts excessive digits and keeps only 6 ones" do - format("%f", 1.123456789).should == "1.123457" + @method.call("%f", 1.123456789).should == "1.123457" end it "rounds the last significant digit to the closest one" do - format("%f", 1.555555555).should == "1.555556" - format("%f", -1.555555555).should == "-1.555556" - format("%f", 1.444444444).should == "1.444444" + @method.call("%f", 1.555555555).should == "1.555556" + @method.call("%f", -1.555555555).should == "-1.555556" + @method.call("%f", 1.444444444).should == "1.444444" end it "displays Float::INFINITY as Inf" do - format("%f", Float::INFINITY).should == "Inf" - format("%f", -Float::INFINITY).should == "-Inf" + @method.call("%f", Float::INFINITY).should == "Inf" + @method.call("%f", -Float::INFINITY).should == "-Inf" end it "displays Float::NAN as NaN" do - format("%f", Float::NAN).should == "NaN" - format("%f", -Float::NAN).should == "NaN" + @method.call("%f", Float::NAN).should == "NaN" + @method.call("%f", -Float::NAN).should == "NaN" end end @@ -185,100 +187,100 @@ describe :kernel_sprintf, shared: true do describe f do context "the exponent is less than -4" do it "converts a floating point number using exponential form" do - format("%#{f}", 0.0000123456).should == "1.23456#{exp}-05" - format("%#{f}", -0.0000123456).should == "-1.23456#{exp}-05" + @method.call("%#{f}", 0.0000123456).should == "1.23456#{exp}-05" + @method.call("%#{f}", -0.0000123456).should == "-1.23456#{exp}-05" - format("%#{f}", 0.000000000123456).should == "1.23456#{exp}-10" - format("%#{f}", -0.000000000123456).should == "-1.23456#{exp}-10" + @method.call("%#{f}", 0.000000000123456).should == "1.23456#{exp}-10" + @method.call("%#{f}", -0.000000000123456).should == "-1.23456#{exp}-10" end end context "the exponent is greater than or equal to the precision (6 by default)" do it "converts a floating point number using exponential form" do - format("%#{f}", 1234567).should == "1.23457#{exp}+06" - format("%#{f}", 1234567890123).should == "1.23457#{exp}+12" - format("%#{f}", -1234567).should == "-1.23457#{exp}+06" + @method.call("%#{f}", 1234567).should == "1.23457#{exp}+06" + @method.call("%#{f}", 1234567890123).should == "1.23457#{exp}+12" + @method.call("%#{f}", -1234567).should == "-1.23457#{exp}+06" end end context "otherwise" do it "converts a floating point number in dd.dddd form" do - format("%#{f}", 0.0001).should == "0.0001" - format("%#{f}", -0.0001).should == "-0.0001" - format("%#{f}", 123456).should == "123456" - format("%#{f}", -123456).should == "-123456" + @method.call("%#{f}", 0.0001).should == "0.0001" + @method.call("%#{f}", -0.0001).should == "-0.0001" + @method.call("%#{f}", 123456).should == "123456" + @method.call("%#{f}", -123456).should == "-123456" end it "cuts excessive digits in fractional part and keeps only 4 ones" do - format("%#{f}", 12.12341111).should == "12.1234" - format("%#{f}", -12.12341111).should == "-12.1234" + @method.call("%#{f}", 12.12341111).should == "12.1234" + @method.call("%#{f}", -12.12341111).should == "-12.1234" end it "rounds the last significant digit to the closest one in fractional part" do - format("%#{f}", 1.555555555).should == "1.55556" - format("%#{f}", -1.555555555).should == "-1.55556" - format("%#{f}", 1.444444444).should == "1.44444" + @method.call("%#{f}", 1.555555555).should == "1.55556" + @method.call("%#{f}", -1.555555555).should == "-1.55556" + @method.call("%#{f}", 1.444444444).should == "1.44444" end it "cuts fraction part to have only 6 digits at all" do - format("%#{f}", 1.1234567).should == "1.12346" - format("%#{f}", 12.1234567).should == "12.1235" - format("%#{f}", 123.1234567).should == "123.123" - format("%#{f}", 1234.1234567).should == "1234.12" - format("%#{f}", 12345.1234567).should == "12345.1" - format("%#{f}", 123456.1234567).should == "123456" + @method.call("%#{f}", 1.1234567).should == "1.12346" + @method.call("%#{f}", 12.1234567).should == "12.1235" + @method.call("%#{f}", 123.1234567).should == "123.123" + @method.call("%#{f}", 1234.1234567).should == "1234.12" + @method.call("%#{f}", 12345.1234567).should == "12345.1" + @method.call("%#{f}", 123456.1234567).should == "123456" end end it "displays Float::INFINITY as Inf" do - format("%#{f}", Float::INFINITY).should == "Inf" - format("%#{f}", -Float::INFINITY).should == "-Inf" + @method.call("%#{f}", Float::INFINITY).should == "Inf" + @method.call("%#{f}", -Float::INFINITY).should == "-Inf" end it "displays Float::NAN as NaN" do - format("%#{f}", Float::NAN).should == "NaN" - format("%#{f}", -Float::NAN).should == "NaN" + @method.call("%#{f}", Float::NAN).should == "NaN" + @method.call("%#{f}", -Float::NAN).should == "NaN" end end end describe "a" do it "converts floating point argument as [-]0xh.hhhhp[+-]dd" do - format("%a", 196).should == "0x1.88p+7" - format("%a", -196).should == "-0x1.88p+7" - format("%a", 196.1).should == "0x1.8833333333333p+7" - format("%a", 0.01).should == "0x1.47ae147ae147bp-7" - format("%a", -0.01).should == "-0x1.47ae147ae147bp-7" + @method.call("%a", 196).should == "0x1.88p+7" + @method.call("%a", -196).should == "-0x1.88p+7" + @method.call("%a", 196.1).should == "0x1.8833333333333p+7" + @method.call("%a", 0.01).should == "0x1.47ae147ae147bp-7" + @method.call("%a", -0.01).should == "-0x1.47ae147ae147bp-7" end it "displays Float::INFINITY as Inf" do - format("%a", Float::INFINITY).should == "Inf" - format("%a", -Float::INFINITY).should == "-Inf" + @method.call("%a", Float::INFINITY).should == "Inf" + @method.call("%a", -Float::INFINITY).should == "-Inf" end it "displays Float::NAN as NaN" do - format("%a", Float::NAN).should == "NaN" - format("%a", -Float::NAN).should == "NaN" + @method.call("%a", Float::NAN).should == "NaN" + @method.call("%a", -Float::NAN).should == "NaN" end end describe "A" do it "converts floating point argument as [-]0xh.hhhhp[+-]dd and use uppercase X and P" do - format("%A", 196).should == "0X1.88P+7" - format("%A", -196).should == "-0X1.88P+7" - format("%A", 196.1).should == "0X1.8833333333333P+7" - format("%A", 0.01).should == "0X1.47AE147AE147BP-7" - format("%A", -0.01).should == "-0X1.47AE147AE147BP-7" + @method.call("%A", 196).should == "0X1.88P+7" + @method.call("%A", -196).should == "-0X1.88P+7" + @method.call("%A", 196.1).should == "0X1.8833333333333P+7" + @method.call("%A", 0.01).should == "0X1.47AE147AE147BP-7" + @method.call("%A", -0.01).should == "-0X1.47AE147AE147BP-7" end it "displays Float::INFINITY as Inf" do - format("%A", Float::INFINITY).should == "Inf" - format("%A", -Float::INFINITY).should == "-Inf" + @method.call("%A", Float::INFINITY).should == "Inf" + @method.call("%A", -Float::INFINITY).should == "-Inf" end it "displays Float::NAN as NaN" do - format("%A", Float::NAN).should == "NaN" - format("%A", -Float::NAN).should == "NaN" + @method.call("%A", Float::NAN).should == "NaN" + @method.call("%A", -Float::NAN).should == "NaN" end end end @@ -286,28 +288,71 @@ describe :kernel_sprintf, shared: true do describe "other formats" do describe "c" do it "displays character if argument is a numeric code of character" do - format("%c", 97).should == "a" + @method.call("%c", 97).should == "a" end it "displays character if argument is a single character string" do - format("%c", "a").should == "a" + @method.call("%c", "a").should == "a" end - it "raises ArgumentError if argument is a string of several characters" do - -> () { - format("%c", "abc") - }.should raise_error(ArgumentError) + it "displays only the first character if argument is a string of several characters" do + @method.call("%c", "abc").should == "a" end - it "raises ArgumentError if argument is an empty string" do - -> () { - format("%c", "") - }.should raise_error(ArgumentError) + 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", []) + }.should raise_error(TypeError, /no implicit conversion of Array into Integer/) + end + + it "raises TypeError if argument is nil" do + -> { + @method.call("%c", nil) + }.should raise_error(TypeError, /no implicit conversion from nil to integer/) end - it "supports Unicode characters" do - format("%c", 1286).should == "Ԇ" - format("%c", "ش").should == "ش" + 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 TypeError if converting to String with to_str returns non-String" do + obj = BasicObject.new + def obj.to_str + :foo + end + + -> { + @method.call("%c", obj) + }.should raise_error(TypeError, /can't convert BasicObject to String/) + end + + 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_error(TypeError, /can't convert BasicObject to Integer/) end end @@ -315,19 +360,27 @@ describe :kernel_sprintf, shared: true do it "displays argument.inspect value" do obj = mock("object") obj.should_receive(:inspect).and_return("<inspect-result>") - format("%p", obj).should == "<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 it "substitute argument passes as a string" do - format("%s", "abc").should == "abc" + @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") - format("%s", obj).should == "abc" + @method.call("%s", obj).should == "abc" end it "does not try to convert with to_str" do @@ -336,30 +389,67 @@ describe :kernel_sprintf, shared: true do "abc" end - -> () { - format("%s", obj) + -> { + @method.call("%s", obj) }.should raise_error(NoMethodError) end - end - describe "%" do - ruby_version_is ""..."2.5" do - it "alone displays the percent sign" do - format("%").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 - -> { - format("%") - }.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_error(ArgumentError) end it "is escaped by %" do - format("%%").should == "%" - format("%%d", 10).should == "%d" + @method.call("%%").should == "%" + @method.call("%%d").should == "%d" end end end @@ -368,110 +458,110 @@ describe :kernel_sprintf, shared: true do describe "space" do context "applies to numeric formats bBdiouxXeEfgGaA" do it "leaves a space at the start of non-negative numbers" do - format("% b", 10).should == " 1010" - format("% B", 10).should == " 1010" - format("% d", 112).should == " 112" - format("% i", 112).should == " 112" - format("% o", 87).should == " 127" - format("% u", 112).should == " 112" - format("% x", 196).should == " c4" - format("% X", 196).should == " C4" - - format("% e", 109.52).should == " 1.095200e+02" - format("% E", 109.52).should == " 1.095200E+02" - format("% f", 10.952).should == " 10.952000" - format("% g", 12.1234).should == " 12.1234" - format("% G", 12.1234).should == " 12.1234" - format("% a", 196).should == " 0x1.88p+7" - format("% A", 196).should == " 0X1.88P+7" + @method.call("% b", 10).should == " 1010" + @method.call("% B", 10).should == " 1010" + @method.call("% d", 112).should == " 112" + @method.call("% i", 112).should == " 112" + @method.call("% o", 87).should == " 127" + @method.call("% u", 112).should == " 112" + @method.call("% x", 196).should == " c4" + @method.call("% X", 196).should == " C4" + + @method.call("% e", 109.52).should == " 1.095200e+02" + @method.call("% E", 109.52).should == " 1.095200E+02" + @method.call("% f", 10.952).should == " 10.952000" + @method.call("% g", 12.1234).should == " 12.1234" + @method.call("% G", 12.1234).should == " 12.1234" + @method.call("% a", 196).should == " 0x1.88p+7" + @method.call("% A", 196).should == " 0X1.88P+7" end it "does not leave a space at the start of negative numbers" do - format("% b", -10).should == "-1010" - format("% B", -10).should == "-1010" - format("% d", -112).should == "-112" - format("% i", -112).should == "-112" - format("% o", -87).should == "-127" - format("% u", -112).should == "-112" - format("% x", -196).should == "-c4" - format("% X", -196).should == "-C4" - - format("% e", -109.52).should == "-1.095200e+02" - format("% E", -109.52).should == "-1.095200E+02" - format("% f", -10.952).should == "-10.952000" - format("% g", -12.1234).should == "-12.1234" - format("% G", -12.1234).should == "-12.1234" - format("% a", -196).should == "-0x1.88p+7" - format("% A", -196).should == "-0X1.88P+7" + @method.call("% b", -10).should == "-1010" + @method.call("% B", -10).should == "-1010" + @method.call("% d", -112).should == "-112" + @method.call("% i", -112).should == "-112" + @method.call("% o", -87).should == "-127" + @method.call("% u", -112).should == "-112" + @method.call("% x", -196).should == "-c4" + @method.call("% X", -196).should == "-C4" + + @method.call("% e", -109.52).should == "-1.095200e+02" + @method.call("% E", -109.52).should == "-1.095200E+02" + @method.call("% f", -10.952).should == "-10.952000" + @method.call("% g", -12.1234).should == "-12.1234" + @method.call("% G", -12.1234).should == "-12.1234" + @method.call("% a", -196).should == "-0x1.88p+7" + @method.call("% A", -196).should == "-0X1.88P+7" end it "prevents converting negative argument to two's complement form" do - format("% b", -10).should == "-1010" - format("% B", -10).should == "-1010" - format("% o", -87).should == "-127" - format("% x", -196).should == "-c4" - format("% X", -196).should == "-C4" + @method.call("% b", -10).should == "-1010" + @method.call("% B", -10).should == "-1010" + @method.call("% o", -87).should == "-127" + @method.call("% x", -196).should == "-c4" + @method.call("% X", -196).should == "-C4" end it "treats several white spaces as one" do - format("% b", 10).should == " 1010" - format("% B", 10).should == " 1010" - format("% d", 112).should == " 112" - format("% i", 112).should == " 112" - format("% o", 87).should == " 127" - format("% u", 112).should == " 112" - format("% x", 196).should == " c4" - format("% X", 196).should == " C4" + @method.call("% b", 10).should == " 1010" + @method.call("% B", 10).should == " 1010" + @method.call("% d", 112).should == " 112" + @method.call("% i", 112).should == " 112" + @method.call("% o", 87).should == " 127" + @method.call("% u", 112).should == " 112" + @method.call("% x", 196).should == " c4" + @method.call("% X", 196).should == " C4" - format("% e", 109.52).should == " 1.095200e+02" - format("% E", 109.52).should == " 1.095200E+02" - format("% f", 10.952).should == " 10.952000" - format("% g", 12.1234).should == " 12.1234" - format("% G", 12.1234).should == " 12.1234" - format("% a", 196).should == " 0x1.88p+7" - format("% A", 196).should == " 0X1.88P+7" + @method.call("% e", 109.52).should == " 1.095200e+02" + @method.call("% E", 109.52).should == " 1.095200E+02" + @method.call("% f", 10.952).should == " 10.952000" + @method.call("% g", 12.1234).should == " 12.1234" + @method.call("% G", 12.1234).should == " 12.1234" + @method.call("% a", 196).should == " 0x1.88p+7" + @method.call("% A", 196).should == " 0X1.88P+7" end end end describe "(digit)$" do it "specifies the absolute argument number for this field" do - format("%2$b", 0, 10).should == "1010" - format("%2$B", 0, 10).should == "1010" - format("%2$d", 0, 112).should == "112" - format("%2$i", 0, 112).should == "112" - format("%2$o", 0, 87).should == "127" - format("%2$u", 0, 112).should == "112" - format("%2$x", 0, 196).should == "c4" - format("%2$X", 0, 196).should == "C4" - - format("%2$e", 0, 109.52).should == "1.095200e+02" - format("%2$E", 0, 109.52).should == "1.095200E+02" - format("%2$f", 0, 10.952).should == "10.952000" - format("%2$g", 0, 12.1234).should == "12.1234" - format("%2$G", 0, 12.1234).should == "12.1234" - format("%2$a", 0, 196).should == "0x1.88p+7" - format("%2$A", 0, 196).should == "0X1.88P+7" - - format("%2$c", 1, 97).should == "a" - format("%2$p", "a", []).should == "[]" - format("%2$s", "-", "abc").should == "abc" + @method.call("%2$b", 0, 10).should == "1010" + @method.call("%2$B", 0, 10).should == "1010" + @method.call("%2$d", 0, 112).should == "112" + @method.call("%2$i", 0, 112).should == "112" + @method.call("%2$o", 0, 87).should == "127" + @method.call("%2$u", 0, 112).should == "112" + @method.call("%2$x", 0, 196).should == "c4" + @method.call("%2$X", 0, 196).should == "C4" + + @method.call("%2$e", 0, 109.52).should == "1.095200e+02" + @method.call("%2$E", 0, 109.52).should == "1.095200E+02" + @method.call("%2$f", 0, 10.952).should == "10.952000" + @method.call("%2$g", 0, 12.1234).should == "12.1234" + @method.call("%2$G", 0, 12.1234).should == "12.1234" + @method.call("%2$a", 0, 196).should == "0x1.88p+7" + @method.call("%2$A", 0, 196).should == "0X1.88P+7" + + @method.call("%2$c", 1, 97).should == "a" + @method.call("%2$p", "a", []).should == "[]" + @method.call("%2$s", "-", "abc").should == "abc" end it "raises exception if argument number is bigger than actual arguments list" do - -> () { - format("%4$d", 1, 2, 3) + -> { + @method.call("%4$d", 1, 2, 3) }.should raise_error(ArgumentError) end it "ignores '-' sign" do - format("%2$d", 1, 2, 3).should == "2" - format("%-2$d", 1, 2, 3).should == "2" + @method.call("%2$d", 1, 2, 3).should == "2" + @method.call("%-2$d", 1, 2, 3).should == "2" end it "raises ArgumentError exception when absolute and relative argument numbers are mixed" do - -> () { - format("%1$d %d", 1, 2) + -> { + @method.call("%1$d %d", 1, 2) }.should raise_error(ArgumentError) end end @@ -479,62 +569,62 @@ describe :kernel_sprintf, shared: true do describe "#" do context "applies to format o" do it "increases the precision until the first digit will be `0' if it is not formatted as complements" do - format("%#o", 87).should == "0127" + @method.call("%#o", 87).should == "0127" end it "does nothing for negative argument" do - format("%#o", -87).should == "..7651" + @method.call("%#o", -87).should == "..7651" end end context "applies to formats bBxX" do it "prefixes the result with 0x, 0X, 0b and 0B respectively for non-zero argument" do - format("%#b", 10).should == "0b1010" - format("%#b", -10).should == "0b..10110" - format("%#B", 10).should == "0B1010" - format("%#B", -10).should == "0B..10110" + @method.call("%#b", 10).should == "0b1010" + @method.call("%#b", -10).should == "0b..10110" + @method.call("%#B", 10).should == "0B1010" + @method.call("%#B", -10).should == "0B..10110" - format("%#x", 196).should == "0xc4" - format("%#x", -196).should == "0x..f3c" - format("%#X", 196).should == "0XC4" - format("%#X", -196).should == "0X..F3C" + @method.call("%#x", 196).should == "0xc4" + @method.call("%#x", -196).should == "0x..f3c" + @method.call("%#X", 196).should == "0XC4" + @method.call("%#X", -196).should == "0X..F3C" end it "does nothing for zero argument" do - format("%#b", 0).should == "0" - format("%#B", 0).should == "0" + @method.call("%#b", 0).should == "0" + @method.call("%#B", 0).should == "0" - format("%#o", 0).should == "0" + @method.call("%#o", 0).should == "0" - format("%#x", 0).should == "0" - format("%#X", 0).should == "0" + @method.call("%#x", 0).should == "0" + @method.call("%#X", 0).should == "0" end end context "applies to formats aAeEfgG" do it "forces a decimal point to be added, even if no digits follow" do - format("%#.0a", 16.25).should == "0x1.p+4" - format("%#.0A", 16.25).should == "0X1.P+4" + @method.call("%#.0a", 16.25).should == "0x1.p+4" + @method.call("%#.0A", 16.25).should == "0X1.P+4" - format("%#.0e", 100).should == "1.e+02" - format("%#.0E", 100).should == "1.E+02" + @method.call("%#.0e", 100).should == "1.e+02" + @method.call("%#.0E", 100).should == "1.E+02" - format("%#.0f", 123.4).should == "123." + @method.call("%#.0f", 123.4).should == "123." - format("%#g", 123456).should == "123456." - format("%#G", 123456).should == "123456." + @method.call("%#g", 123456).should == "123456." + @method.call("%#G", 123456).should == "123456." end it "changes format from dd.dddd to exponential form for gG" do - format("%#.0g", 123.4).should_not == "123." - format("%#.0g", 123.4).should == "1.e+02" + @method.call("%#.0g", 123.4).should_not == "123." + @method.call("%#.0g", 123.4).should == "1.e+02" end end context "applies to gG" do it "does not remove trailing zeros" do - format("%#g", 123.4).should == "123.400" - format("%#g", 123.4).should == "123.400" + @method.call("%#g", 123.4).should == "123.400" + @method.call("%#g", 123.4).should == "123.400" end end end @@ -542,186 +632,186 @@ describe :kernel_sprintf, shared: true do describe "+" do context "applies to numeric formats bBdiouxXaAeEfgG" do it "adds a leading plus sign to non-negative numbers" do - format("%+b", 10).should == "+1010" - format("%+B", 10).should == "+1010" - format("%+d", 112).should == "+112" - format("%+i", 112).should == "+112" - format("%+o", 87).should == "+127" - format("%+u", 112).should == "+112" - format("%+x", 196).should == "+c4" - format("%+X", 196).should == "+C4" - - format("%+e", 109.52).should == "+1.095200e+02" - format("%+E", 109.52).should == "+1.095200E+02" - format("%+f", 10.952).should == "+10.952000" - format("%+g", 12.1234).should == "+12.1234" - format("%+G", 12.1234).should == "+12.1234" - format("%+a", 196).should == "+0x1.88p+7" - format("%+A", 196).should == "+0X1.88P+7" + @method.call("%+b", 10).should == "+1010" + @method.call("%+B", 10).should == "+1010" + @method.call("%+d", 112).should == "+112" + @method.call("%+i", 112).should == "+112" + @method.call("%+o", 87).should == "+127" + @method.call("%+u", 112).should == "+112" + @method.call("%+x", 196).should == "+c4" + @method.call("%+X", 196).should == "+C4" + + @method.call("%+e", 109.52).should == "+1.095200e+02" + @method.call("%+E", 109.52).should == "+1.095200E+02" + @method.call("%+f", 10.952).should == "+10.952000" + @method.call("%+g", 12.1234).should == "+12.1234" + @method.call("%+G", 12.1234).should == "+12.1234" + @method.call("%+a", 196).should == "+0x1.88p+7" + @method.call("%+A", 196).should == "+0X1.88P+7" end it "does not use two's complement form for negative numbers for formats bBoxX" do - format("%+b", -10).should == "-1010" - format("%+B", -10).should == "-1010" - format("%+o", -87).should == "-127" - format("%+x", -196).should == "-c4" - format("%+X", -196).should == "-C4" + @method.call("%+b", -10).should == "-1010" + @method.call("%+B", -10).should == "-1010" + @method.call("%+o", -87).should == "-127" + @method.call("%+x", -196).should == "-c4" + @method.call("%+X", -196).should == "-C4" end end end describe "-" do it "left-justifies the result of conversion if width is specified" do - format("%-10b", 10).should == "1010 " - format("%-10B", 10).should == "1010 " - format("%-10d", 112).should == "112 " - format("%-10i", 112).should == "112 " - format("%-10o", 87).should == "127 " - format("%-10u", 112).should == "112 " - format("%-10x", 196).should == "c4 " - format("%-10X", 196).should == "C4 " + @method.call("%-10b", 10).should == "1010 " + @method.call("%-10B", 10).should == "1010 " + @method.call("%-10d", 112).should == "112 " + @method.call("%-10i", 112).should == "112 " + @method.call("%-10o", 87).should == "127 " + @method.call("%-10u", 112).should == "112 " + @method.call("%-10x", 196).should == "c4 " + @method.call("%-10X", 196).should == "C4 " - format("%-20e", 109.52).should == "1.095200e+02 " - format("%-20E", 109.52).should == "1.095200E+02 " - format("%-20f", 10.952).should == "10.952000 " - format("%-20g", 12.1234).should == "12.1234 " - format("%-20G", 12.1234).should == "12.1234 " - format("%-20a", 196).should == "0x1.88p+7 " - format("%-20A", 196).should == "0X1.88P+7 " + @method.call("%-20e", 109.52).should == "1.095200e+02 " + @method.call("%-20E", 109.52).should == "1.095200E+02 " + @method.call("%-20f", 10.952).should == "10.952000 " + @method.call("%-20g", 12.1234).should == "12.1234 " + @method.call("%-20G", 12.1234).should == "12.1234 " + @method.call("%-20a", 196).should == "0x1.88p+7 " + @method.call("%-20A", 196).should == "0X1.88P+7 " - format("%-10c", 97).should == "a " - format("%-10p", []).should == "[] " - format("%-10s", "abc").should == "abc " + @method.call("%-10c", 97).should == "a " + @method.call("%-10p", []).should == "[] " + @method.call("%-10s", "abc").should == "abc " end end describe "0 (zero)" do context "applies to numeric formats bBdiouxXaAeEfgG and width is specified" do it "pads with zeros, not spaces" do - format("%010b", 10).should == "0000001010" - format("%010B", 10).should == "0000001010" - format("%010d", 112).should == "0000000112" - format("%010i", 112).should == "0000000112" - format("%010o", 87).should == "0000000127" - format("%010u", 112).should == "0000000112" - format("%010x", 196).should == "00000000c4" - format("%010X", 196).should == "00000000C4" - - format("%020e", 109.52).should == "000000001.095200e+02" - format("%020E", 109.52).should == "000000001.095200E+02" - format("%020f", 10.952).should == "0000000000010.952000" - format("%020g", 12.1234).should == "000000000000012.1234" - format("%020G", 12.1234).should == "000000000000012.1234" - format("%020a", 196).should == "0x000000000001.88p+7" - format("%020A", 196).should == "0X000000000001.88P+7" + @method.call("%010b", 10).should == "0000001010" + @method.call("%010B", 10).should == "0000001010" + @method.call("%010d", 112).should == "0000000112" + @method.call("%010i", 112).should == "0000000112" + @method.call("%010o", 87).should == "0000000127" + @method.call("%010u", 112).should == "0000000112" + @method.call("%010x", 196).should == "00000000c4" + @method.call("%010X", 196).should == "00000000C4" + + @method.call("%020e", 109.52).should == "000000001.095200e+02" + @method.call("%020E", 109.52).should == "000000001.095200E+02" + @method.call("%020f", 10.952).should == "0000000000010.952000" + @method.call("%020g", 12.1234).should == "000000000000012.1234" + @method.call("%020G", 12.1234).should == "000000000000012.1234" + @method.call("%020a", 196).should == "0x000000000001.88p+7" + @method.call("%020A", 196).should == "0X000000000001.88P+7" end it "uses radix-1 when displays negative argument as a two's complement" do - format("%010b", -10).should == "..11110110" - format("%010B", -10).should == "..11110110" - format("%010o", -87).should == "..77777651" - format("%010x", -196).should == "..ffffff3c" - format("%010X", -196).should == "..FFFFFF3C" + @method.call("%010b", -10).should == "..11110110" + @method.call("%010B", -10).should == "..11110110" + @method.call("%010o", -87).should == "..77777651" + @method.call("%010x", -196).should == "..ffffff3c" + @method.call("%010X", -196).should == "..FFFFFF3C" end end end describe "*" do it "uses the previous argument as the field width" do - format("%*b", 10, 10).should == " 1010" - format("%*B", 10, 10).should == " 1010" - format("%*d", 10, 112).should == " 112" - format("%*i", 10, 112).should == " 112" - format("%*o", 10, 87).should == " 127" - format("%*u", 10, 112).should == " 112" - format("%*x", 10, 196).should == " c4" - format("%*X", 10, 196).should == " C4" - - format("%*e", 20, 109.52).should == " 1.095200e+02" - format("%*E", 20, 109.52).should == " 1.095200E+02" - format("%*f", 20, 10.952).should == " 10.952000" - format("%*g", 20, 12.1234).should == " 12.1234" - format("%*G", 20, 12.1234).should == " 12.1234" - format("%*a", 20, 196).should == " 0x1.88p+7" - format("%*A", 20, 196).should == " 0X1.88P+7" - - format("%*c", 10, 97).should == " a" - format("%*p", 10, []).should == " []" - format("%*s", 10, "abc").should == " abc" + @method.call("%*b", 10, 10).should == " 1010" + @method.call("%*B", 10, 10).should == " 1010" + @method.call("%*d", 10, 112).should == " 112" + @method.call("%*i", 10, 112).should == " 112" + @method.call("%*o", 10, 87).should == " 127" + @method.call("%*u", 10, 112).should == " 112" + @method.call("%*x", 10, 196).should == " c4" + @method.call("%*X", 10, 196).should == " C4" + + @method.call("%*e", 20, 109.52).should == " 1.095200e+02" + @method.call("%*E", 20, 109.52).should == " 1.095200E+02" + @method.call("%*f", 20, 10.952).should == " 10.952000" + @method.call("%*g", 20, 12.1234).should == " 12.1234" + @method.call("%*G", 20, 12.1234).should == " 12.1234" + @method.call("%*a", 20, 196).should == " 0x1.88p+7" + @method.call("%*A", 20, 196).should == " 0X1.88P+7" + + @method.call("%*c", 10, 97).should == " a" + @method.call("%*p", 10, []).should == " []" + @method.call("%*s", 10, "abc").should == " abc" end it "left-justifies the result if width is negative" do - format("%*b", -10, 10).should == "1010 " - format("%*B", -10, 10).should == "1010 " - format("%*d", -10, 112).should == "112 " - format("%*i", -10, 112).should == "112 " - format("%*o", -10, 87).should == "127 " - format("%*u", -10, 112).should == "112 " - format("%*x", -10, 196).should == "c4 " - format("%*X", -10, 196).should == "C4 " - - format("%*e", -20, 109.52).should == "1.095200e+02 " - format("%*E", -20, 109.52).should == "1.095200E+02 " - format("%*f", -20, 10.952).should == "10.952000 " - format("%*g", -20, 12.1234).should == "12.1234 " - format("%*G", -20, 12.1234).should == "12.1234 " - format("%*a", -20, 196).should == "0x1.88p+7 " - format("%*A", -20, 196).should == "0X1.88P+7 " - - format("%*c", -10, 97).should == "a " - format("%*p", -10, []).should == "[] " - format("%*s", -10, "abc").should == "abc " + @method.call("%*b", -10, 10).should == "1010 " + @method.call("%*B", -10, 10).should == "1010 " + @method.call("%*d", -10, 112).should == "112 " + @method.call("%*i", -10, 112).should == "112 " + @method.call("%*o", -10, 87).should == "127 " + @method.call("%*u", -10, 112).should == "112 " + @method.call("%*x", -10, 196).should == "c4 " + @method.call("%*X", -10, 196).should == "C4 " + + @method.call("%*e", -20, 109.52).should == "1.095200e+02 " + @method.call("%*E", -20, 109.52).should == "1.095200E+02 " + @method.call("%*f", -20, 10.952).should == "10.952000 " + @method.call("%*g", -20, 12.1234).should == "12.1234 " + @method.call("%*G", -20, 12.1234).should == "12.1234 " + @method.call("%*a", -20, 196).should == "0x1.88p+7 " + @method.call("%*A", -20, 196).should == "0X1.88P+7 " + + @method.call("%*c", -10, 97).should == "a " + @method.call("%*p", -10, []).should == "[] " + @method.call("%*s", -10, "abc").should == "abc " end it "uses the specified argument as the width if * is followed by a number and $" do - format("%1$*2$b", 10, 10).should == " 1010" - format("%1$*2$B", 10, 10).should == " 1010" - format("%1$*2$d", 112, 10).should == " 112" - format("%1$*2$i", 112, 10).should == " 112" - format("%1$*2$o", 87, 10).should == " 127" - format("%1$*2$u", 112, 10).should == " 112" - format("%1$*2$x", 196, 10).should == " c4" - format("%1$*2$X", 196, 10).should == " C4" - - format("%1$*2$e", 109.52, 20).should == " 1.095200e+02" - format("%1$*2$E", 109.52, 20).should == " 1.095200E+02" - format("%1$*2$f", 10.952, 20).should == " 10.952000" - format("%1$*2$g", 12.1234, 20).should == " 12.1234" - format("%1$*2$G", 12.1234, 20).should == " 12.1234" - format("%1$*2$a", 196, 20).should == " 0x1.88p+7" - format("%1$*2$A", 196, 20).should == " 0X1.88P+7" - - format("%1$*2$c", 97, 10).should == " a" - format("%1$*2$p", [], 10).should == " []" - format("%1$*2$s", "abc", 10).should == " abc" + @method.call("%1$*2$b", 10, 10).should == " 1010" + @method.call("%1$*2$B", 10, 10).should == " 1010" + @method.call("%1$*2$d", 112, 10).should == " 112" + @method.call("%1$*2$i", 112, 10).should == " 112" + @method.call("%1$*2$o", 87, 10).should == " 127" + @method.call("%1$*2$u", 112, 10).should == " 112" + @method.call("%1$*2$x", 196, 10).should == " c4" + @method.call("%1$*2$X", 196, 10).should == " C4" + + @method.call("%1$*2$e", 109.52, 20).should == " 1.095200e+02" + @method.call("%1$*2$E", 109.52, 20).should == " 1.095200E+02" + @method.call("%1$*2$f", 10.952, 20).should == " 10.952000" + @method.call("%1$*2$g", 12.1234, 20).should == " 12.1234" + @method.call("%1$*2$G", 12.1234, 20).should == " 12.1234" + @method.call("%1$*2$a", 196, 20).should == " 0x1.88p+7" + @method.call("%1$*2$A", 196, 20).should == " 0X1.88P+7" + + @method.call("%1$*2$c", 97, 10).should == " a" + @method.call("%1$*2$p", [], 10).should == " []" + @method.call("%1$*2$s", "abc", 10).should == " abc" end it "left-justifies the result if specified with $ argument is negative" do - format("%1$*2$b", 10, -10).should == "1010 " - format("%1$*2$B", 10, -10).should == "1010 " - format("%1$*2$d", 112, -10).should == "112 " - format("%1$*2$i", 112, -10).should == "112 " - format("%1$*2$o", 87, -10).should == "127 " - format("%1$*2$u", 112, -10).should == "112 " - format("%1$*2$x", 196, -10).should == "c4 " - format("%1$*2$X", 196, -10).should == "C4 " - - format("%1$*2$e", 109.52, -20).should == "1.095200e+02 " - format("%1$*2$E", 109.52, -20).should == "1.095200E+02 " - format("%1$*2$f", 10.952, -20).should == "10.952000 " - format("%1$*2$g", 12.1234, -20).should == "12.1234 " - format("%1$*2$G", 12.1234, -20).should == "12.1234 " - format("%1$*2$a", 196, -20).should == "0x1.88p+7 " - format("%1$*2$A", 196, -20).should == "0X1.88P+7 " - - format("%1$*2$c", 97, -10).should == "a " - format("%1$*2$p", [], -10).should == "[] " - format("%1$*2$s", "abc", -10).should == "abc " + @method.call("%1$*2$b", 10, -10).should == "1010 " + @method.call("%1$*2$B", 10, -10).should == "1010 " + @method.call("%1$*2$d", 112, -10).should == "112 " + @method.call("%1$*2$i", 112, -10).should == "112 " + @method.call("%1$*2$o", 87, -10).should == "127 " + @method.call("%1$*2$u", 112, -10).should == "112 " + @method.call("%1$*2$x", 196, -10).should == "c4 " + @method.call("%1$*2$X", 196, -10).should == "C4 " + + @method.call("%1$*2$e", 109.52, -20).should == "1.095200e+02 " + @method.call("%1$*2$E", 109.52, -20).should == "1.095200E+02 " + @method.call("%1$*2$f", 10.952, -20).should == "10.952000 " + @method.call("%1$*2$g", 12.1234, -20).should == "12.1234 " + @method.call("%1$*2$G", 12.1234, -20).should == "12.1234 " + @method.call("%1$*2$a", 196, -20).should == "0x1.88p+7 " + @method.call("%1$*2$A", 196, -20).should == "0X1.88P+7 " + + @method.call("%1$*2$c", 97, -10).should == "a " + @method.call("%1$*2$p", [], -10).should == "[] " + @method.call("%1$*2$s", "abc", -10).should == "abc " end it "raises ArgumentError when is mixed with width" do - -> () { - format("%*10d", 10, 112) + -> { + @method.call("%*10d", 10, 112) }.should raise_error(ArgumentError) end end @@ -729,74 +819,74 @@ describe :kernel_sprintf, shared: true do describe "width" do it "specifies the minimum number of characters that will be written to the result" do - format("%10b", 10).should == " 1010" - format("%10B", 10).should == " 1010" - format("%10d", 112).should == " 112" - format("%10i", 112).should == " 112" - format("%10o", 87).should == " 127" - format("%10u", 112).should == " 112" - format("%10x", 196).should == " c4" - format("%10X", 196).should == " C4" - - format("%20e", 109.52).should == " 1.095200e+02" - format("%20E", 109.52).should == " 1.095200E+02" - format("%20f", 10.952).should == " 10.952000" - format("%20g", 12.1234).should == " 12.1234" - format("%20G", 12.1234).should == " 12.1234" - format("%20a", 196).should == " 0x1.88p+7" - format("%20A", 196).should == " 0X1.88P+7" - - format("%10c", 97).should == " a" - format("%10p", []).should == " []" - format("%10s", "abc").should == " abc" + @method.call("%10b", 10).should == " 1010" + @method.call("%10B", 10).should == " 1010" + @method.call("%10d", 112).should == " 112" + @method.call("%10i", 112).should == " 112" + @method.call("%10o", 87).should == " 127" + @method.call("%10u", 112).should == " 112" + @method.call("%10x", 196).should == " c4" + @method.call("%10X", 196).should == " C4" + + @method.call("%20e", 109.52).should == " 1.095200e+02" + @method.call("%20E", 109.52).should == " 1.095200E+02" + @method.call("%20f", 10.952).should == " 10.952000" + @method.call("%20g", 12.1234).should == " 12.1234" + @method.call("%20G", 12.1234).should == " 12.1234" + @method.call("%20a", 196).should == " 0x1.88p+7" + @method.call("%20A", 196).should == " 0X1.88P+7" + + @method.call("%10c", 97).should == " a" + @method.call("%10p", []).should == " []" + @method.call("%10s", "abc").should == " abc" end it "is ignored if argument's actual length is greater" do - format("%5d", 1234567890).should == "1234567890" + @method.call("%5d", 1234567890).should == "1234567890" end end describe "precision" do context "integer types" do it "controls the number of decimal places displayed" do - format("%.6b", 10).should == "001010" - format("%.6B", 10).should == "001010" - format("%.5d", 112).should == "00112" - format("%.5i", 112).should == "00112" - format("%.5o", 87).should == "00127" - format("%.5u", 112).should == "00112" + @method.call("%.6b", 10).should == "001010" + @method.call("%.6B", 10).should == "001010" + @method.call("%.5d", 112).should == "00112" + @method.call("%.5i", 112).should == "00112" + @method.call("%.5o", 87).should == "00127" + @method.call("%.5u", 112).should == "00112" - format("%.5x", 196).should == "000c4" - format("%.5X", 196).should == "000C4" + @method.call("%.5x", 196).should == "000c4" + @method.call("%.5X", 196).should == "000C4" end end context "float types" do it "controls the number of decimal places displayed in fraction part" do - format("%.10e", 109.52).should == "1.0952000000e+02" - format("%.10E", 109.52).should == "1.0952000000E+02" - format("%.10f", 10.952).should == "10.9520000000" - format("%.10a", 196).should == "0x1.8800000000p+7" - format("%.10A", 196).should == "0X1.8800000000P+7" + @method.call("%.10e", 109.52).should == "1.0952000000e+02" + @method.call("%.10E", 109.52).should == "1.0952000000E+02" + @method.call("%.10f", 10.952).should == "10.9520000000" + @method.call("%.10a", 196).should == "0x1.8800000000p+7" + @method.call("%.10A", 196).should == "0X1.8800000000P+7" end it "does not affect G format" do - format("%.10g", 12.1234).should == "12.1234" - format("%.10g", 123456789).should == "123456789" + @method.call("%.10g", 12.1234).should == "12.1234" + @method.call("%.10g", 123456789).should == "123456789" end end context "string formats" do it "determines the maximum number of characters to be copied from the string" do - format("%.1p", [1]).should == "[" - format("%.2p", [1]).should == "[1" - format("%.10p", [1]).should == "[1]" - format("%.0p", [1]).should == "" + @method.call("%.1p", [1]).should == "[" + @method.call("%.2p", [1]).should == "[1" + @method.call("%.10p", [1]).should == "[1]" + @method.call("%.0p", [1]).should == "" - format("%.1s", "abc").should == "a" - format("%.2s", "abc").should == "ab" - format("%.10s", "abc").should == "abc" - format("%.0s", "abc").should == "" + @method.call("%.1s", "abc").should == "a" + @method.call("%.2s", "abc").should == "ab" + @method.call("%.10s", "abc").should == "abc" + @method.call("%.0s", "abc").should == "" end end end @@ -804,55 +894,49 @@ describe :kernel_sprintf, shared: true do describe "reference by name" do describe "%<name>s style" do it "uses value passed in a hash argument" do - format("%<foo>d", foo: 123).should == "123" + @method.call("%<foo>d", foo: 123).should == "123" end it "supports flags, width, precision and type" do - format("%+20.10<foo>f", foo: 10.952).should == " +10.9520000000" + @method.call("%+20.10<foo>f", foo: 10.952).should == " +10.9520000000" end it "allows to place name in any position" do - format("%+15.5<foo>f", foo: 10.952).should == " +10.95200" - format("%+15<foo>.5f", foo: 10.952).should == " +10.95200" - format("%+<foo>15.5f", foo: 10.952).should == " +10.95200" - format("%<foo>+15.5f", foo: 10.952).should == " +10.95200" + @method.call("%+15.5<foo>f", foo: 10.952).should == " +10.95200" + @method.call("%+15<foo>.5f", foo: 10.952).should == " +10.95200" + @method.call("%+<foo>15.5f", foo: 10.952).should == " +10.95200" + @method.call("%<foo>+15.5f", foo: 10.952).should == " +10.95200" end it "cannot be mixed with unnamed style" do - -> () { - format("%d %<foo>d", 1, foo: "123") + -> { + @method.call("%d %<foo>d", 1, foo: "123") }.should raise_error(ArgumentError) end - - it "raises KeyError when there is no matching key" do - -> () { - format("%<foo>s", {}) - }.should raise_error(KeyError) - end end describe "%{name} style" do it "uses value passed in a hash argument" do - format("%{foo}", foo: 123).should == "123" + @method.call("%{foo}", foo: 123).should == "123" end it "does not support type style" do - format("%{foo}d", foo: 123).should == "123d" + @method.call("%{foo}d", foo: 123).should == "123d" end it "supports flags, width and precision" do - format("%-20.5{foo}", foo: "123456789").should == "12345 " + @method.call("%-20.5{foo}", foo: "123456789").should == "12345 " end it "cannot be mixed with unnamed style" do - -> () { - format("%d %{foo}", 1, foo: "123") + -> { + @method.call("%d %{foo}", 1, foo: "123") }.should raise_error(ArgumentError) end it "raises KeyError when there is no matching key" do - -> () { - format("%{foo}", {}) + -> { + @method.call("%{foo}", {}) }.should raise_error(KeyError) end @@ -864,8 +948,40 @@ describe :kernel_sprintf, shared: true do obj.should_receive(:to_s).and_return("42") obj.should_not_receive(:to_str) - format("%{foo}", foo: obj).should == "42" + @method.call("%{foo}", foo: obj).should == "42" end end end + + describe "faulty key" do + before :each do + @object = { foooo: 1 } + end + + it "raises a KeyError" do + -> { + @method.call("%<foo>s", @object) + }.should raise_error(KeyError) + end + + 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 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 + 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 a92f3c10cd..7ec0fe4c48 100644 --- a/spec/ruby/core/kernel/shared/sprintf_encoding.rb +++ b/spec/ruby/core/kernel/shared/sprintf_encoding.rb @@ -1,28 +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 - def format(*args) - @method.call(*args) + 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 + 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 end it "returns a String in the same encoding as the format String if compatible" do - string = "%s".force_encoding(Encoding::KOI8_U) - result = format(string, "dogs") + string = "%s".dup.force_encoding(Encoding::KOI8_U) + result = @method.call(string, "dogs") 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 = format(string, argument) + result = @method.call(string, argument) 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') - -> () { - format(string, argument) + -> { + @method.call(string, argument) }.should raise_error(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_error(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 new file mode 100644 index 0000000000..b52075371f --- /dev/null +++ b/spec/ruby/core/kernel/shared/then.rb @@ -0,0 +1,20 @@ +describe :kernel_then, shared: true do + it "yields self" do + object = Object.new + 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 + 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 + end +end |
