diff options
Diffstat (limited to 'spec/ruby/core/kernel')
139 files changed, 4897 insertions, 1763 deletions
diff --git a/spec/ruby/core/kernel/Array_spec.rb b/spec/ruby/core/kernel/Array_spec.rb index 6031a828f6..b4a8bb7599 100644 --- a/spec/ruby/core/kernel/Array_spec.rb +++ b/spec/ruby/core/kernel/Array_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel" do it "has private instance method Array()" do @@ -77,14 +77,14 @@ describe :kernel_Array, shared: true do obj = mock("Array() string") obj.should_receive(:to_ary).and_return("string") - lambda { @object.send(@method, obj) }.should raise_error(TypeError) + -> { @object.send(@method, obj) }.should raise_error(TypeError) end it "raises a TypeError if #to_a does not return an Array" do obj = mock("Array() string") obj.should_receive(:to_a).and_return("string") - lambda { @object.send(@method, obj) }.should raise_error(TypeError) + -> { @object.send(@method, obj) }.should raise_error(TypeError) end end diff --git a/spec/ruby/core/kernel/Complex_spec.rb b/spec/ruby/core/kernel/Complex_spec.rb index b156cc4549..346d50ab5e 100644 --- a/spec/ruby/core/kernel/Complex_spec.rb +++ b/spec/ruby/core/kernel/Complex_spec.rb @@ -1,6 +1,276 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../../../shared/complex/Complex', __FILE__) +require_relative '../../spec_helper' +require_relative '../../shared/kernel/complex' +require_relative 'fixtures/Complex' describe "Kernel.Complex()" do - it_behaves_like :kernel_Complex, :Complex + describe "when passed [Complex, Complex]" do + it "returns a new Complex number based on the two given numbers" do + Complex(Complex(3, 4), Complex(5, 6)).should == Complex(3 - 6, 4 + 5) + Complex(Complex(1.5, 2), Complex(-5, 6.3)).should == Complex(1.5 - 6.3, 2 - 5) + end + end + + describe "when passed [Complex]" do + it "returns the passed Complex number" do + Complex(Complex(1, 2)).should == Complex(1, 2) + Complex(Complex(-3.4, bignum_value)).should == Complex(-3.4, bignum_value) + end + end + + describe "when passed [Integer, Integer]" do + it "returns a new Complex number" do + Complex(1, 2).should be_an_instance_of(Complex) + Complex(1, 2).real.should == 1 + Complex(1, 2).imag.should == 2 + + Complex(-3, -5).should be_an_instance_of(Complex) + Complex(-3, -5).real.should == -3 + Complex(-3, -5).imag.should == -5 + + Complex(3.5, -4.5).should be_an_instance_of(Complex) + Complex(3.5, -4.5).real.should == 3.5 + Complex(3.5, -4.5).imag.should == -4.5 + + Complex(bignum_value, 30).should be_an_instance_of(Complex) + Complex(bignum_value, 30).real.should == bignum_value + Complex(bignum_value, 30).imag.should == 30 + end + end + + describe "when passed [Integer/Float]" do + it "returns a new Complex number with 0 as the imaginary component" do + # Guard against the Mathn library + guard -> { !defined?(Math.rsqrt) } do + Complex(1).should be_an_instance_of(Complex) + Complex(1).imag.should == 0 + Complex(1).real.should == 1 + + Complex(-3).should be_an_instance_of(Complex) + Complex(-3).imag.should == 0 + Complex(-3).real.should == -3 + + Complex(-4.5).should be_an_instance_of(Complex) + Complex(-4.5).imag.should == 0 + Complex(-4.5).real.should == -4.5 + + Complex(bignum_value).should be_an_instance_of(Complex) + Complex(bignum_value).imag.should == 0 + Complex(bignum_value).real.should == bignum_value + end + end + end + + describe "when passed [String]" do + it_behaves_like :kernel_complex, :Complex_method, KernelSpecs + + context "invalid argument" do + it "raises Encoding::CompatibilityError if String is in not ASCII-compatible encoding" do + -> { + Complex("79+4i".encode("UTF-16")) + }.should raise_error(Encoding::CompatibilityError, "ASCII incompatible encoding: UTF-16") + end + + it "raises ArgumentError for unrecognised Strings" do + -> { + Complex("ruby") + }.should raise_error(ArgumentError, 'invalid value for convert(): "ruby"') + end + + it "raises ArgumentError for trailing garbage" do + -> { + Complex("79+4iruby") + }.should raise_error(ArgumentError, 'invalid value for convert(): "79+4iruby"') + end + + it "does not understand Float::INFINITY" do + -> { + Complex("Infinity") + }.should raise_error(ArgumentError, 'invalid value for convert(): "Infinity"') + + -> { + Complex("-Infinity") + }.should raise_error(ArgumentError, 'invalid value for convert(): "-Infinity"') + end + + it "does not understand Float::NAN" do + -> { + Complex("NaN") + }.should raise_error(ArgumentError, 'invalid value for convert(): "NaN"') + end + + it "does not understand a sequence of _" do + -> { + Complex("7__9+4__0i") + }.should raise_error(ArgumentError, 'invalid value for convert(): "7__9+4__0i"') + end + + it "does not allow null-byte" do + -> { + Complex("1-2i\0") + }.should raise_error(ArgumentError, "string contains null byte") + end + end + + context "invalid argument and exception: false passed" do + it "raises Encoding::CompatibilityError if String is in not ASCII-compatible encoding" do + -> { + Complex("79+4i".encode("UTF-16"), exception: false) + }.should raise_error(Encoding::CompatibilityError, "ASCII incompatible encoding: UTF-16") + end + + it "returns nil for unrecognised Strings" do + Complex("ruby", exception: false).should == nil + end + + it "returns nil when trailing garbage" do + Complex("79+4iruby", exception: false).should == nil + end + + it "returns nil for Float::INFINITY" do + Complex("Infinity", exception: false).should == nil + Complex("-Infinity", exception: false).should == nil + end + + it "returns nil for Float::NAN" do + Complex("NaN", exception: false).should == nil + end + + it "returns nil when there is a sequence of _" do + Complex("7__9+4__0i", exception: false).should == nil + end + + it "returns nil when String contains null-byte" do + Complex("1-2i\0", exception: false).should == nil + end + end + end + + describe "when passes [String, String]" do + it "needs to be reviewed for spec completeness" + end + + describe "when passed an Object which responds to #to_c" do + it "returns the passed argument" do + obj = Object.new; def obj.to_c; 1i end + Complex(obj).should == Complex(0, 1) + end + end + + describe "when passed a Numeric which responds to #real? with false" do + it "returns the passed argument" do + n = mock_numeric("unreal") + n.should_receive(:real?).any_number_of_times.and_return(false) + Complex(n).should equal(n) + end + end + + describe "when passed a Numeric which responds to #real? with true" do + it "returns a Complex with the passed argument as the real component and 0 as the imaginary component" do + n = mock_numeric("real") + n.should_receive(:real?).any_number_of_times.and_return(true) + result = Complex(n) + result.real.should equal(n) + result.imag.should equal(0) + end + end + + describe "when passed Numerics n1 and n2 and at least one responds to #real? with false" do + [[false, false], [false, true], [true, false]].each do |r1, r2| + it "returns n1 + n2 * Complex(0, 1)" do + n1 = mock_numeric("n1") + n2 = mock_numeric("n2") + n3 = mock_numeric("n3") + n4 = mock_numeric("n4") + n1.should_receive(:real?).any_number_of_times.and_return(r1) + n2.should_receive(:real?).any_number_of_times.and_return(r2) + n2.should_receive(:*).with(Complex(0, 1)).and_return(n3) + n1.should_receive(:+).with(n3).and_return(n4) + Complex(n1, n2).should equal(n4) + end + end + end + + describe "when passed two Numerics and both respond to #real? with true" do + it "returns a Complex with the passed arguments as real and imaginary components respectively" do + n1 = mock_numeric("n1") + n2 = mock_numeric("n2") + n1.should_receive(:real?).any_number_of_times.and_return(true) + n2.should_receive(:real?).any_number_of_times.and_return(true) + result = Complex(n1, n2) + result.real.should equal(n1) + result.imag.should equal(n2) + end + end + + describe "when passed a single non-Numeric" do + it "coerces the passed argument using #to_c" do + n = mock("n") + c = Complex(0, 0) + n.should_receive(:to_c).and_return(c) + Complex(n).should equal(c) + end + end + + describe "when passed a non-Numeric second argument" do + it "raises TypeError" do + -> { Complex(:sym, :sym) }.should raise_error(TypeError) + -> { Complex(0, :sym) }.should raise_error(TypeError) + end + end + + describe "when passed nil" do + it "raises TypeError" do + -> { Complex(nil) }.should raise_error(TypeError, "can't convert nil into Complex") + -> { Complex(0, nil) }.should raise_error(TypeError, "can't convert nil into Complex") + -> { Complex(nil, 0) }.should raise_error(TypeError, "can't convert nil into Complex") + end + end + + describe "when passed exception: false" do + describe "and [Numeric]" do + it "returns a complex number" do + Complex("123", exception: false).should == Complex(123) + end + end + + describe "and [non-Numeric]" do + it "swallows an error" do + Complex(:sym, exception: false).should == nil + end + end + + describe "and [non-Numeric, Numeric] argument" do + it "throws a TypeError" do + -> { Complex(:sym, 0, exception: false) }.should raise_error(TypeError, "not a real") + end + end + + describe "and [anything, non-Numeric] argument" do + it "swallows an error" do + Complex("a", :sym, exception: false).should == nil + Complex(:sym, :sym, exception: false).should == nil + Complex(0, :sym, exception: false).should == nil + end + end + + describe "and non-numeric String arguments" do + it "swallows an error" do + Complex("a", "b", exception: false).should == nil + Complex("a", 0, exception: false).should == nil + Complex(0, "b", exception: false).should == nil + end + end + + describe "and nil arguments" do + it "swallows an error" do + Complex(nil, exception: false).should == nil + Complex(0, nil, exception: false).should == nil + Complex(nil, 0, exception: false).should == nil + end + end + end + + it "freezes its result" do + Complex(1).frozen?.should == true + end end diff --git a/spec/ruby/core/kernel/Float_spec.rb b/spec/ruby/core/kernel/Float_spec.rb index ee20190094..9c436b05f7 100644 --- a/spec/ruby/core/kernel/Float_spec.rb +++ b/spec/ruby/core/kernel/Float_spec.rb @@ -1,12 +1,12 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe :kernel_float, shared: true do it "returns the identical Float for numeric Floats" do float = 1.12 float2 = @object.send(:Float, float) float2.should == float - float2.object_id.should == float.object_id + float2.should equal float end it "returns a Float for Fixnums" do @@ -22,7 +22,7 @@ describe :kernel_float, shared: true do end it "raises an ArgumentError for nil" do - lambda { @object.send(:Float, nil) }.should raise_error(TypeError) + -> { @object.send(:Float, nil) }.should raise_error(TypeError) end it "returns the identical NaN for NaN" do @@ -41,7 +41,7 @@ describe :kernel_float, shared: true do end it "converts Strings to floats without calling #to_f" do - string = "10" + string = +"10" string.should_not_receive(:to_f) @object.send(:Float, string).should == 10.0 end @@ -51,24 +51,30 @@ describe :kernel_float, shared: true do end it "raises an ArgumentError for a String of word characters" do - lambda { @object.send(:Float, "float") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "float") }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError for a String with string in error message" do + -> { @object.send(:Float, "foo") }.should raise_error(ArgumentError) { |e| + e.message.should == 'invalid value for Float(): "foo"' + } end it "raises an ArgumentError if there are two decimal points in the String" do - lambda { @object.send(:Float, "10.0.0") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "10.0.0") }.should raise_error(ArgumentError) end it "raises an ArgumentError for a String of numbers followed by word characters" do - lambda { @object.send(:Float, "10D") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "10D") }.should raise_error(ArgumentError) end it "raises an ArgumentError for a String of word characters followed by numbers" do - lambda { @object.send(:Float, "D10") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "D10") }.should raise_error(ArgumentError) end it "is strict about the string form even across newlines" do - lambda { @object.send(:Float, "not a number\n10") }.should raise_error(ArgumentError) - lambda { @object.send(:Float, "10\nnot a number") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "not a number\n10") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "10\nnot a number") }.should raise_error(ArgumentError) end it "converts String subclasses to floats without calling #to_f" do @@ -90,17 +96,17 @@ describe :kernel_float, shared: true do end it "raises an ArgumentError if a + or - is embedded in a String" do - lambda { @object.send(:Float, "1+1") }.should raise_error(ArgumentError) - lambda { @object.send(:Float, "1-1") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "1+1") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "1-1") }.should raise_error(ArgumentError) end it "raises an ArgumentError if a String has a trailing + or -" do - lambda { @object.send(:Float, "11+") }.should raise_error(ArgumentError) - lambda { @object.send(:Float, "11-") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "11+") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "11-") }.should raise_error(ArgumentError) end it "raises an ArgumentError for a String with a leading _" do - lambda { @object.send(:Float, "_1") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "_1") }.should raise_error(ArgumentError) end it "returns a value for a String with an embedded _" do @@ -108,31 +114,31 @@ describe :kernel_float, shared: true do end it "raises an ArgumentError for a String with a trailing _" do - lambda { @object.send(:Float, "10_") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "10_") }.should raise_error(ArgumentError) end it "raises an ArgumentError for a String of \\0" do - lambda { @object.send(:Float, "\0") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "\0") }.should raise_error(ArgumentError) end it "raises an ArgumentError for a String with a leading \\0" do - lambda { @object.send(:Float, "\01") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "\01") }.should raise_error(ArgumentError) end it "raises an ArgumentError for a String with an embedded \\0" do - lambda { @object.send(:Float, "1\01") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "1\01") }.should raise_error(ArgumentError) end it "raises an ArgumentError for a String with a trailing \\0" do - lambda { @object.send(:Float, "1\0") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "1\0") }.should raise_error(ArgumentError) end it "raises an ArgumentError for a String that is just an empty space" do - lambda { @object.send(:Float, " ") }.should raise_error(ArgumentError) + -> { @object.send(:Float, " ") }.should raise_error(ArgumentError) end it "raises an ArgumentError for a String that with an embedded space" do - lambda { @object.send(:Float, "1 2") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "1 2") }.should raise_error(ArgumentError) end it "returns a value for a String with a leading space" do @@ -151,13 +157,33 @@ describe :kernel_float, shared: true do @object.send(:Float, "1\t\n").should == 1.0 end + ruby_version_is ""..."3.4" do + it "raises ArgumentError if a fractional part is missing" do + -> { @object.send(:Float, "1.") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "+1.") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "-1.") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "1.e+0") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "1.e-2") }.should raise_error(ArgumentError) + end + end + + ruby_version_is "3.4" do + it "allows String representation without a fractional part" do + @object.send(:Float, "1.").should == 1.0 + @object.send(:Float, "+1.").should == 1.0 + @object.send(:Float, "-1.").should == -1.0 + @object.send(:Float, "1.e+0").should == 1.0 + @object.send(:Float, "1.e-2").should be_close(0.01, TOLERANCE) + end + end + %w(e E).each do |e| it "raises an ArgumentError if #{e} is the trailing character" do - lambda { @object.send(:Float, "2#{e}") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "2#{e}") }.should raise_error(ArgumentError) end it "raises an ArgumentError if #{e} is the leading character" do - lambda { @object.send(:Float, "#{e}2") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "#{e}2") }.should raise_error(ArgumentError) end it "returns Infinity for '2#{e}1000'" do @@ -175,18 +201,18 @@ describe :kernel_float, shared: true do end it "raises an exception if a space is embedded on either side of the '#{e}'" do - lambda { @object.send(:Float, "2 0#{e}100") }.should raise_error(ArgumentError) - lambda { @object.send(:Float, "20#{e}1 00") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "2 0#{e}100") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "20#{e}1 00") }.should raise_error(ArgumentError) end it "raises an exception if there's a leading _ on either side of the '#{e}'" do - lambda { @object.send(:Float, "_20#{e}100") }.should raise_error(ArgumentError) - lambda { @object.send(:Float, "20#{e}_100") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "_20#{e}100") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "20#{e}_100") }.should raise_error(ArgumentError) end it "raises an exception if there's a trailing _ on either side of the '#{e}'" do - lambda { @object.send(:Float, "20_#{e}100") }.should raise_error(ArgumentError) - lambda { @object.send(:Float, "20#{e}100_") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "20_#{e}100") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "20#{e}100_") }.should raise_error(ArgumentError) end it "allows decimal points on the left side of the '#{e}'" do @@ -194,63 +220,111 @@ describe :kernel_float, shared: true do end it "raises an ArgumentError if there's a decimal point on the right side of the '#{e}'" do - lambda { @object.send(:Float, "20#{e}2.0") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "20#{e}2.0") }.should raise_error(ArgumentError) end end - describe "for hexadecimal literals with binary exponent" do - %w(p P).each do |p| - it "interprets the fractional part (on the left side of '#{p}') in hexadecimal" do - @object.send(:Float, "0x10#{p}0").should == 16.0 - end - - it "interprets the exponent (on the right of '#{p}') in decimal" do - @object.send(:Float, "0x1#{p}10").should == 1024.0 - end - - it "raises an ArgumentError if #{p} is the trailing character" do - lambda { @object.send(:Float, "0x1#{p}") }.should raise_error(ArgumentError) - end - - it "raises an ArgumentError if #{p} is the leading character" do - lambda { @object.send(:Float, "0x#{p}1") }.should raise_error(ArgumentError) - end + context "for hexadecimal literals" do + it "interprets the 0x prefix as hexadecimal" do + @object.send(:Float, "0x10").should == 16.0 + @object.send(:Float, "0x0F").should == 15.0 + @object.send(:Float, "0x0f").should == 15.0 + end - it "returns Infinity for '0x1#{p}10000'" do - @object.send(:Float, "0x1#{p}10000").should == Float::INFINITY - end + it "interprets negative hex value" do + @object.send(:Float, "-0x10").should == -16.0 + end - it "returns 0 for '0x1#{p}-10000'" do - @object.send(:Float, "0x1#{p}-10000").should == 0 - end + it "accepts embedded _ if the number does not contain a-f" do + @object.send(:Float, "0x1_0").should == 16.0 + end - it "allows embedded _ in a number on either side of the #{p}" do - @object.send(:Float, "0x1_0#{p}10").should == 16384.0 - @object.send(:Float, "0x10#{p}1_0").should == 16384.0 - @object.send(:Float, "0x1_0#{p}1_0").should == 16384.0 + ruby_version_is ""..."3.4.3" do + it "does not accept embedded _ if the number contains a-f" do + -> { @object.send(:Float, "0x1_0a") }.should raise_error(ArgumentError) + @object.send(:Float, "0x1_0a", exception: false).should be_nil end + end - it "raises an exception if a space is embedded on either side of the '#{p}'" do - lambda { @object.send(:Float, "0x1 0#{p}10") }.should raise_error(ArgumentError) - lambda { @object.send(:Float, "0x10#{p}1 0") }.should raise_error(ArgumentError) + ruby_version_is "3.4.3" do + it "accepts embedded _ if the number contains a-f" do + @object.send(:Float, "0x1_0a").should == 0x10a.to_f end + end - it "raises an exception if there's a leading _ on either side of the '#{p}'" do - lambda { @object.send(:Float, "0x_10#{p}10") }.should raise_error(ArgumentError) - lambda { @object.send(:Float, "0x10#{p}_10") }.should raise_error(ArgumentError) - end + it "does not accept _ before, after or inside the 0x prefix" do + -> { @object.send(:Float, "_0x10") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "0_x10") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "0x_10") }.should raise_error(ArgumentError) + @object.send(:Float, "_0x10", exception: false).should be_nil + @object.send(:Float, "0_x10", exception: false).should be_nil + @object.send(:Float, "0x_10", exception: false).should be_nil + end - it "raises an exception if there's a trailing _ on either side of the '#{p}'" do - lambda { @object.send(:Float, "0x10_#{p}10") }.should raise_error(ArgumentError) - lambda { @object.send(:Float, "0x10#{p}10_") }.should raise_error(ArgumentError) - end + it "parses negative hexadecimal string as negative float" do + @object.send(:Float, "-0x7b").should == -123.0 + end - it "allows hexadecimal points on the left side of the '#{p}'" do - @object.send(:Float, "0x1.8#{p}0").should == 1.5 + ruby_version_is "3.4" do + it "accepts a fractional part" do + @object.send(:Float, "0x0.8").should == 0.5 end + end - it "raises an ArgumentError if there's a decimal point on the right side of the '#{p}'" do - lambda { @object.send(:Float, "0x1#{p}1.0") }.should raise_error(ArgumentError) + describe "with binary exponent" do + %w(p P).each do |p| + it "interprets the fractional part (on the left side of '#{p}') in hexadecimal" do + @object.send(:Float, "0x10#{p}0").should == 16.0 + end + + it "interprets the exponent (on the right of '#{p}') in decimal" do + @object.send(:Float, "0x1#{p}10").should == 1024.0 + end + + it "raises an ArgumentError if #{p} is the trailing character" do + -> { @object.send(:Float, "0x1#{p}") }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if #{p} is the leading character" do + -> { @object.send(:Float, "0x#{p}1") }.should raise_error(ArgumentError) + end + + it "returns Infinity for '0x1#{p}10000'" do + @object.send(:Float, "0x1#{p}10000").should == Float::INFINITY + end + + it "returns 0 for '0x1#{p}-10000'" do + @object.send(:Float, "0x1#{p}-10000").should == 0 + end + + it "allows embedded _ in a number on either side of the #{p}" do + @object.send(:Float, "0x1_0#{p}10").should == 16384.0 + @object.send(:Float, "0x10#{p}1_0").should == 16384.0 + @object.send(:Float, "0x1_0#{p}1_0").should == 16384.0 + end + + it "raises an exception if a space is embedded on either side of the '#{p}'" do + -> { @object.send(:Float, "0x1 0#{p}10") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "0x10#{p}1 0") }.should raise_error(ArgumentError) + end + + it "raises an exception if there's a leading _ on either side of the '#{p}'" do + -> { @object.send(:Float, "0x_10#{p}10") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "0x10#{p}_10") }.should raise_error(ArgumentError) + end + + it "raises an exception if there's a trailing _ on either side of the '#{p}'" do + -> { @object.send(:Float, "0x10_#{p}10") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "0x10#{p}10_") }.should raise_error(ArgumentError) + end + + it "allows hexadecimal points on the left side of the '#{p}'" do + @object.send(:Float, "0x1.8#{p}0").should == 1.5 + end + + it "raises an ArgumentError if there's a decimal point on the right side of the '#{p}'" do + -> { @object.send(:Float, "0x1#{p}1.0") }.should raise_error(ArgumentError) + end end end end @@ -274,7 +348,7 @@ describe :kernel_float, shared: true do nan2.should equal(nan) end - it "returns the identical Infinity if to_f is called and it returns Infinity" do + it "returns the identical Infinity if #to_f is called and it returns Infinity" do infinity = infinity_value (infinity_to_f = mock('Infinity')).should_receive(:to_f).once.and_return(infinity) infinity2 = @object.send(:Float, infinity_to_f) @@ -282,22 +356,45 @@ describe :kernel_float, shared: true do end it "raises a TypeError if #to_f is not provided" do - lambda { @object.send(:Float, mock('x')) }.should raise_error(TypeError) + -> { @object.send(:Float, mock('x')) }.should raise_error(TypeError) end it "raises a TypeError if #to_f returns a String" do (obj = mock('ha!')).should_receive(:to_f).once.and_return('ha!') - lambda { @object.send(:Float, obj) }.should raise_error(TypeError) + -> { @object.send(:Float, obj) }.should raise_error(TypeError) end it "raises a TypeError if #to_f returns an Integer" do (obj = mock('123')).should_receive(:to_f).once.and_return(123) - lambda { @object.send(:Float, obj) }.should raise_error(TypeError) + -> { @object.send(:Float, obj) }.should raise_error(TypeError) end it "raises a RangeError when passed a Complex argument" do c = Complex(2, 3) - lambda { @object.send(:Float, c) }.should raise_error(RangeError) + -> { @object.send(:Float, c) }.should raise_error(RangeError) + end + + describe "when passed exception: false" do + describe "and valid input" do + it "returns a Float number" do + @object.send(:Float, 1, exception: false).should == 1.0 + @object.send(:Float, "1", exception: false).should == 1.0 + @object.send(:Float, "1.23", exception: false).should == 1.23 + end + end + + describe "and invalid input" do + it "swallows an error" do + @object.send(:Float, "abc", exception: false).should == nil + @object.send(:Float, :sym, exception: false).should == nil + end + end + + describe "and nil" do + it "swallows it" do + @object.send(:Float, nil, exception: false).should == nil + end + end end end diff --git a/spec/ruby/core/kernel/Hash_spec.rb b/spec/ruby/core/kernel/Hash_spec.rb index 8d51316c75..cbe098a8ac 100644 --- a/spec/ruby/core/kernel/Hash_spec.rb +++ b/spec/ruby/core/kernel/Hash_spec.rb @@ -1,8 +1,14 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#hash" do - it "needs to be reviewed for spec completeness" + it "is provided" do + 1.respond_to?(:hash).should == true + end + + it "is stable" do + 1.hash.should == 1.hash + end end describe "Kernel" do @@ -37,14 +43,14 @@ describe :kernel_Hash, shared: true do end it "raises a TypeError if it doesn't respond to #to_hash" do - lambda { @object.send(@method, mock("")) }.should raise_error(TypeError) + -> { @object.send(@method, mock("")) }.should raise_error(TypeError) end it "raises a TypeError if #to_hash does not return an Hash" do obj = mock("Hash() string") obj.should_receive(:to_hash).and_return("string") - lambda { @object.send(@method, obj) }.should raise_error(TypeError) + -> { @object.send(@method, obj) }.should raise_error(TypeError) end end diff --git a/spec/ruby/core/kernel/Integer_spec.rb b/spec/ruby/core/kernel/Integer_spec.rb index 1e95fc9151..74dd3e0dd2 100644 --- a/spec/ruby/core/kernel/Integer_spec.rb +++ b/spec/ruby/core/kernel/Integer_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe :kernel_integer, shared: true do it "returns a Bignum for a Bignum" do @@ -10,20 +10,27 @@ describe :kernel_integer, shared: true do Integer(100).should == 100 end - it "uncritically return the value of to_int even if it is not an Integer" do + it "raises a TypeError when to_int returns not-an-Integer object and to_i returns nil" do obj = mock("object") obj.should_receive(:to_int).and_return("1") - obj.should_not_receive(:to_i) - Integer(obj).should == "1" + obj.should_receive(:to_i).and_return(nil) + -> { Integer(obj) }.should raise_error(TypeError) + end + + it "return a result of to_i when to_int does not return an Integer" do + obj = mock("object") + obj.should_receive(:to_int).and_return("1") + obj.should_receive(:to_i).and_return(42) + Integer(obj).should == 42 end it "raises a TypeError when passed nil" do - lambda { Integer(nil) }.should raise_error(TypeError) + -> { Integer(nil) }.should raise_error(TypeError) end - it "returns a Fixnum or Bignum object" do - Integer(2).should be_an_instance_of(Fixnum) - Integer(9**99).should be_an_instance_of(Bignum) + it "returns an Integer object" do + Integer(2).should be_an_instance_of(Integer) + Integer(9**99).should be_an_instance_of(Integer) end it "truncates Floats" do @@ -45,9 +52,9 @@ describe :kernel_integer, shared: true do it "returns the value of to_int if the result is a Bignum" do obj = mock("object") - obj.should_receive(:to_int).and_return(2e100) + obj.should_receive(:to_int).and_return(2 * 10**100) obj.should_not_receive(:to_i) - Integer(obj).should == 2e100 + Integer(obj).should == 2 * 10**100 end it "calls to_i on an object whose to_int returns nil" do @@ -60,44 +67,99 @@ describe :kernel_integer, shared: true do it "raises a TypeError if to_i returns a value that is not an Integer" do obj = mock("object") obj.should_receive(:to_i).and_return("1") - lambda { Integer(obj) }.should raise_error(TypeError) + -> { Integer(obj) }.should raise_error(TypeError) end it "raises a TypeError if no to_int or to_i methods exist" do obj = mock("object") - lambda { Integer(obj) }.should raise_error(TypeError) + -> { Integer(obj) }.should raise_error(TypeError) end it "raises a TypeError if to_int returns nil and no to_i exists" do obj = mock("object") obj.should_receive(:to_i).and_return(nil) - lambda { Integer(obj) }.should raise_error(TypeError) + -> { Integer(obj) }.should raise_error(TypeError) end it "raises a FloatDomainError when passed NaN" do - lambda { Integer(nan_value) }.should raise_error(FloatDomainError) + -> { Integer(nan_value) }.should raise_error(FloatDomainError) end it "raises a FloatDomainError when passed Infinity" do - lambda { Integer(infinity_value) }.should raise_error(FloatDomainError) + -> { Integer(infinity_value) }.should raise_error(FloatDomainError) + end + + describe "when passed exception: false" do + describe "and to_i returns a value that is not an Integer" do + it "swallows an error" do + obj = mock("object") + obj.should_receive(:to_i).and_return("1") + Integer(obj, exception: false).should == nil + end + end + + describe "and no to_int or to_i methods exist" do + it "swallows an error" do + obj = mock("object") + Integer(obj, exception: false).should == nil + end + end + + describe "and to_int returns nil and no to_i exists" do + it "swallows an error" do + obj = mock("object") + obj.should_receive(:to_i).and_return(nil) + Integer(obj, exception: false).should == nil + end + end + + describe "and passed NaN" do + it "swallows an error" do + Integer(nan_value, exception: false).should == nil + end + end + + describe "and passed Infinity" do + it "swallows an error" do + Integer(infinity_value, exception: false).should == nil + end + end + + describe "and passed nil" do + it "swallows an error" do + Integer(nil, exception: false).should == nil + end + end + + describe "and passed a String that contains numbers" do + it "normally parses it and returns an Integer" do + Integer("42", exception: false).should == 42 + end + end + + describe "and passed a String that can't be converted to an Integer" do + it "swallows an error" do + Integer("abc", exception: false).should == nil + end + end end end -describe "Integer() given a String", shared: true do +describe :kernel_integer_string, shared: true do it "raises an ArgumentError if the String is a null byte" do - lambda { Integer("\0") }.should raise_error(ArgumentError) + -> { Integer("\0") }.should raise_error(ArgumentError) end it "raises an ArgumentError if the String starts with a null byte" do - lambda { Integer("\01") }.should raise_error(ArgumentError) + -> { Integer("\01") }.should raise_error(ArgumentError) end it "raises an ArgumentError if the String ends with a null byte" do - lambda { Integer("1\0") }.should raise_error(ArgumentError) + -> { Integer("1\0") }.should raise_error(ArgumentError) end it "raises an ArgumentError if the String contains a null byte" do - lambda { Integer("1\01") }.should raise_error(ArgumentError) + -> { Integer("1\01") }.should raise_error(ArgumentError) end it "ignores leading whitespace" do @@ -113,13 +175,13 @@ describe "Integer() given a String", shared: true do end it "raises an ArgumentError if there are leading _s" do - lambda { Integer("_1") }.should raise_error(ArgumentError) - lambda { Integer("___1") }.should raise_error(ArgumentError) + -> { Integer("_1") }.should raise_error(ArgumentError) + -> { Integer("___1") }.should raise_error(ArgumentError) end it "raises an ArgumentError if there are trailing _s" do - lambda { Integer("1_") }.should raise_error(ArgumentError) - lambda { Integer("1___") }.should raise_error(ArgumentError) + -> { Integer("1_") }.should raise_error(ArgumentError) + -> { Integer("1___") }.should raise_error(ArgumentError) end it "ignores an embedded _" do @@ -127,8 +189,8 @@ describe "Integer() given a String", shared: true do end it "raises an ArgumentError if there are multiple embedded _s" do - lambda { Integer("1__1") }.should raise_error(ArgumentError) - lambda { Integer("1___1") }.should raise_error(ArgumentError) + -> { Integer("1__1") }.should raise_error(ArgumentError) + -> { Integer("1___1") }.should raise_error(ArgumentError) end it "ignores a single leading +" do @@ -136,17 +198,17 @@ describe "Integer() given a String", shared: true do end it "raises an ArgumentError if there is a space between the + and number" do - lambda { Integer("+ 1") }.should raise_error(ArgumentError) + -> { Integer("+ 1") }.should raise_error(ArgumentError) end it "raises an ArgumentError if there are multiple leading +s" do - lambda { Integer("++1") }.should raise_error(ArgumentError) - lambda { Integer("+++1") }.should raise_error(ArgumentError) + -> { Integer("++1") }.should raise_error(ArgumentError) + -> { Integer("+++1") }.should raise_error(ArgumentError) end it "raises an ArgumentError if there are trailing +s" do - lambda { Integer("1+") }.should raise_error(ArgumentError) - lambda { Integer("1+++") }.should raise_error(ArgumentError) + -> { Integer("1+") }.should raise_error(ArgumentError) + -> { Integer("1+++") }.should raise_error(ArgumentError) end it "makes the number negative if there's a leading -" do @@ -154,21 +216,47 @@ describe "Integer() given a String", shared: true do end it "raises an ArgumentError if there are multiple leading -s" do - lambda { Integer("--1") }.should raise_error(ArgumentError) - lambda { Integer("---1") }.should raise_error(ArgumentError) + -> { Integer("--1") }.should raise_error(ArgumentError) + -> { Integer("---1") }.should raise_error(ArgumentError) end it "raises an ArgumentError if there are trailing -s" do - lambda { Integer("1-") }.should raise_error(ArgumentError) - lambda { Integer("1---") }.should raise_error(ArgumentError) + -> { Integer("1-") }.should raise_error(ArgumentError) + -> { Integer("1---") }.should raise_error(ArgumentError) end it "raises an ArgumentError if there is a period" do - lambda { Integer("0.0") }.should raise_error(ArgumentError) + -> { Integer("0.0") }.should raise_error(ArgumentError) end it "raises an ArgumentError for an empty String" do - lambda { Integer("") }.should raise_error(ArgumentError) + -> { Integer("") }.should raise_error(ArgumentError) + end + + describe "when passed exception: false" do + describe "and multiple leading -s" do + it "swallows an error" do + Integer("---1", exception: false).should == nil + end + end + + describe "and multiple trailing -s" do + it "swallows an error" do + Integer("1---", exception: false).should == nil + end + end + + describe "and an argument that contains a period" do + it "swallows an error" do + Integer("0.0", exception: false).should == nil + end + end + + describe "and an empty string" do + it "swallows an error" do + Integer("", exception: false).should == nil + end + end end it "parses the value as 0 if the string consists of a single zero character" do @@ -192,7 +280,7 @@ describe "Integer() given a String", shared: true do end it "raises an ArgumentError if the number cannot be parsed as hex" do - lambda { Integer("0#{x}g") }.should raise_error(ArgumentError) + -> { Integer("0#{x}g") }.should raise_error(ArgumentError) end end @@ -213,7 +301,7 @@ describe "Integer() given a String", shared: true do end it "raises an ArgumentError if the number cannot be parsed as binary" do - lambda { Integer("0#{b}2") }.should raise_error(ArgumentError) + -> { Integer("0#{b}2") }.should raise_error(ArgumentError) end end @@ -234,7 +322,7 @@ describe "Integer() given a String", shared: true do end it "raises an ArgumentError if the number cannot be parsed as octal" do - lambda { Integer("0#{o}9") }.should raise_error(ArgumentError) + -> { Integer("0#{o}9") }.should raise_error(ArgumentError) end end @@ -255,26 +343,26 @@ describe "Integer() given a String", shared: true do end it "raises an ArgumentError if the number cannot be parsed as decimal" do - lambda { Integer("0#{d}a") }.should raise_error(ArgumentError) + -> { Integer("0#{d}a") }.should raise_error(ArgumentError) end end end -describe "Integer() given a String and base", shared: true do +describe :kernel_integer_string_base, shared: true do it "raises an ArgumentError if the String is a null byte" do - lambda { Integer("\0", 2) }.should raise_error(ArgumentError) + -> { Integer("\0", 2) }.should raise_error(ArgumentError) end it "raises an ArgumentError if the String starts with a null byte" do - lambda { Integer("\01", 3) }.should raise_error(ArgumentError) + -> { Integer("\01", 3) }.should raise_error(ArgumentError) end it "raises an ArgumentError if the String ends with a null byte" do - lambda { Integer("1\0", 4) }.should raise_error(ArgumentError) + -> { Integer("1\0", 4) }.should raise_error(ArgumentError) end it "raises an ArgumentError if the String contains a null byte" do - lambda { Integer("1\01", 5) }.should raise_error(ArgumentError) + -> { Integer("1\01", 5) }.should raise_error(ArgumentError) end it "ignores leading whitespace" do @@ -290,13 +378,13 @@ describe "Integer() given a String and base", shared: true do end it "raises an ArgumentError if there are leading _s" do - lambda { Integer("_1", 7) }.should raise_error(ArgumentError) - lambda { Integer("___1", 7) }.should raise_error(ArgumentError) + -> { Integer("_1", 7) }.should raise_error(ArgumentError) + -> { Integer("___1", 7) }.should raise_error(ArgumentError) end it "raises an ArgumentError if there are trailing _s" do - lambda { Integer("1_", 12) }.should raise_error(ArgumentError) - lambda { Integer("1___", 12) }.should raise_error(ArgumentError) + -> { Integer("1_", 12) }.should raise_error(ArgumentError) + -> { Integer("1___", 12) }.should raise_error(ArgumentError) end it "ignores an embedded _" do @@ -304,8 +392,8 @@ describe "Integer() given a String and base", shared: true do end it "raises an ArgumentError if there are multiple embedded _s" do - lambda { Integer("1__1", 4) }.should raise_error(ArgumentError) - lambda { Integer("1___1", 4) }.should raise_error(ArgumentError) + -> { Integer("1__1", 4) }.should raise_error(ArgumentError) + -> { Integer("1___1", 4) }.should raise_error(ArgumentError) end it "ignores a single leading +" do @@ -313,17 +401,17 @@ describe "Integer() given a String and base", shared: true do end it "raises an ArgumentError if there is a space between the + and number" do - lambda { Integer("+ 1", 3) }.should raise_error(ArgumentError) + -> { Integer("+ 1", 3) }.should raise_error(ArgumentError) end it "raises an ArgumentError if there are multiple leading +s" do - lambda { Integer("++1", 3) }.should raise_error(ArgumentError) - lambda { Integer("+++1", 3) }.should raise_error(ArgumentError) + -> { Integer("++1", 3) }.should raise_error(ArgumentError) + -> { Integer("+++1", 3) }.should raise_error(ArgumentError) end it "raises an ArgumentError if there are trailing +s" do - lambda { Integer("1+", 3) }.should raise_error(ArgumentError) - lambda { Integer("1+++", 12) }.should raise_error(ArgumentError) + -> { Integer("1+", 3) }.should raise_error(ArgumentError) + -> { Integer("1+++", 12) }.should raise_error(ArgumentError) end it "makes the number negative if there's a leading -" do @@ -331,29 +419,29 @@ describe "Integer() given a String and base", shared: true do end it "raises an ArgumentError if there are multiple leading -s" do - lambda { Integer("--1", 9) }.should raise_error(ArgumentError) - lambda { Integer("---1", 9) }.should raise_error(ArgumentError) + -> { Integer("--1", 9) }.should raise_error(ArgumentError) + -> { Integer("---1", 9) }.should raise_error(ArgumentError) end it "raises an ArgumentError if there are trailing -s" do - lambda { Integer("1-", 12) }.should raise_error(ArgumentError) - lambda { Integer("1---", 12) }.should raise_error(ArgumentError) + -> { Integer("1-", 12) }.should raise_error(ArgumentError) + -> { Integer("1---", 12) }.should raise_error(ArgumentError) end it "raises an ArgumentError if there is a period" do - lambda { Integer("0.0", 3) }.should raise_error(ArgumentError) + -> { Integer("0.0", 3) }.should raise_error(ArgumentError) end it "raises an ArgumentError for an empty String" do - lambda { Integer("", 12) }.should raise_error(ArgumentError) + -> { Integer("", 12) }.should raise_error(ArgumentError) end it "raises an ArgumentError for a base of 1" do - lambda { Integer("1", 1) }.should raise_error(ArgumentError) + -> { Integer("1", 1) }.should raise_error(ArgumentError) end it "raises an ArgumentError for a base of 37" do - lambda { Integer("1", 37) }.should raise_error(ArgumentError) + -> { Integer("1", 37) }.should raise_error(ArgumentError) end it "accepts wholly lowercase alphabetic strings for bases > 10" do @@ -381,8 +469,8 @@ describe "Integer() given a String and base", shared: true do end it "raises an ArgumentError for letters invalid in the given base" do - lambda { Integer('z',19) }.should raise_error(ArgumentError) - lambda { Integer('c00o',2) }.should raise_error(ArgumentError) + -> { Integer('z',19) }.should raise_error(ArgumentError) + -> { Integer('c00o',2) }.should raise_error(ArgumentError) end %w(x X).each do |x| @@ -403,12 +491,12 @@ describe "Integer() given a String and base", shared: true do 2.upto(15) do |base| it "raises an ArgumentError if the number begins with 0#{x} and the base is #{base}" do - lambda { Integer("0#{x}1", base) }.should raise_error(ArgumentError) + -> { Integer("0#{x}1", base) }.should raise_error(ArgumentError) end end it "raises an ArgumentError if the number cannot be parsed as hex and the base is 16" do - lambda { Integer("0#{x}g", 16) }.should raise_error(ArgumentError) + -> { Integer("0#{x}g", 16) }.should raise_error(ArgumentError) end end @@ -429,7 +517,7 @@ describe "Integer() given a String and base", shared: true do end it "raises an ArgumentError if the number cannot be parsed as binary and the base is 2" do - lambda { Integer("0#{b}2", 2) }.should raise_error(ArgumentError) + -> { Integer("0#{b}2", 2) }.should raise_error(ArgumentError) end end @@ -450,12 +538,12 @@ describe "Integer() given a String and base", shared: true do end it "raises an ArgumentError if the number cannot be parsed as octal and the base is 8" do - lambda { Integer("0#{o}9", 8) }.should raise_error(ArgumentError) + -> { Integer("0#{o}9", 8) }.should raise_error(ArgumentError) end 2.upto(7) do |base| it "raises an ArgumentError if the number begins with 0#{o} and the base is #{base}" do - lambda { Integer("0#{o}1", base) }.should raise_error(ArgumentError) + -> { Integer("0#{o}1", base) }.should raise_error(ArgumentError) end end end @@ -477,17 +565,55 @@ describe "Integer() given a String and base", shared: true do end it "raises an ArgumentError if the number cannot be parsed as decimal and the base is 10" do - lambda { Integer("0#{d}a", 10) }.should raise_error(ArgumentError) + -> { Integer("0#{d}a", 10) }.should raise_error(ArgumentError) end 2.upto(9) do |base| it "raises an ArgumentError if the number begins with 0#{d} and the base is #{base}" do - lambda { Integer("0#{d}1", base) }.should raise_error(ArgumentError) + -> { Integer("0#{d}1", base) }.should raise_error(ArgumentError) + end + end + end + + it "raises an ArgumentError if a base is given for a non-String value" do + -> { Integer(98, 15) }.should raise_error(ArgumentError) + end + + it "tries to convert the base to an integer using to_int" do + obj = mock('8') + obj.should_receive(:to_int).and_return(8) + + Integer("777", obj).should == 0777 + end + + # https://bugs.ruby-lang.org/issues/19349 + ruby_version_is ''...'3.3' do + it "ignores the base if it is not an integer and does not respond to #to_i" do + Integer("777", "8").should == 777 + end + end + + ruby_version_is '3.3' do + it "raises a TypeError if it is not an integer and does not respond to #to_i" do + -> { + Integer("777", "8") + }.should raise_error(TypeError, "no implicit conversion of String into Integer") + end + end + + describe "when passed exception: false" do + describe "and valid argument" do + it "returns an Integer number" do + Integer("100", 10, exception: false).should == 100 + Integer("100", 2, exception: false).should == 4 end end - it "raises an ArgumentError if a base is given for a non-String value" do - lambda { Integer(98, 15) }.should raise_error(ArgumentError) + describe "and invalid argument" do + it "swallows an error" do + Integer("999", 2, exception: false).should == nil + Integer("abc", 10, exception: false).should == nil + end end end end @@ -495,176 +621,176 @@ end describe :kernel_Integer, shared: true do it "raises an ArgumentError when the String contains digits out of range of radix 2" do str = "23456789abcdefghijklmnopqrstuvwxyz" - lambda { @object.send(@method, str, 2) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 2) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 3" do str = "3456789abcdefghijklmnopqrstuvwxyz" - lambda { @object.send(@method, str, 3) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 3) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 4" do str = "456789abcdefghijklmnopqrstuvwxyz" - lambda { @object.send(@method, str, 4) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 4) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 5" do str = "56789abcdefghijklmnopqrstuvwxyz" - lambda { @object.send(@method, str, 5) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 5) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 6" do str = "6789abcdefghijklmnopqrstuvwxyz" - lambda { @object.send(@method, str, 6) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 6) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 7" do str = "789abcdefghijklmnopqrstuvwxyz" - lambda { @object.send(@method, str, 7) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 7) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 8" do str = "89abcdefghijklmnopqrstuvwxyz" - lambda { @object.send(@method, str, 8) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 8) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 9" do str = "9abcdefghijklmnopqrstuvwxyz" - lambda { @object.send(@method, str, 9) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 9) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 10" do str = "abcdefghijklmnopqrstuvwxyz" - lambda { @object.send(@method, str, 10) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 10) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 11" do str = "bcdefghijklmnopqrstuvwxyz" - lambda { @object.send(@method, str, 11) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 11) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 12" do str = "cdefghijklmnopqrstuvwxyz" - lambda { @object.send(@method, str, 12) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 12) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 13" do str = "defghijklmnopqrstuvwxyz" - lambda { @object.send(@method, str, 13) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 13) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 14" do str = "efghijklmnopqrstuvwxyz" - lambda { @object.send(@method, str, 14) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 14) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 15" do str = "fghijklmnopqrstuvwxyz" - lambda { @object.send(@method, str, 15) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 15) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 16" do str = "ghijklmnopqrstuvwxyz" - lambda { @object.send(@method, str, 16) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 16) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 17" do str = "hijklmnopqrstuvwxyz" - lambda { @object.send(@method, str, 17) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 17) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 18" do str = "ijklmnopqrstuvwxyz" - lambda { @object.send(@method, str, 18) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 18) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 19" do str = "jklmnopqrstuvwxyz" - lambda { @object.send(@method, str, 19) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 19) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 20" do str = "klmnopqrstuvwxyz" - lambda { @object.send(@method, str, 20) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 20) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 21" do str = "lmnopqrstuvwxyz" - lambda { @object.send(@method, str, 21) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 21) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 22" do str = "mnopqrstuvwxyz" - lambda { @object.send(@method, str, 22) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 22) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 23" do str = "nopqrstuvwxyz" - lambda { @object.send(@method, str, 23) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 23) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 24" do str = "opqrstuvwxyz" - lambda { @object.send(@method, str, 24) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 24) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 25" do str = "pqrstuvwxyz" - lambda { @object.send(@method, str, 25) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 25) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 26" do str = "qrstuvwxyz" - lambda { @object.send(@method, str, 26) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 26) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 27" do str = "rstuvwxyz" - lambda { @object.send(@method, str, 27) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 27) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 28" do str = "stuvwxyz" - lambda { @object.send(@method, str, 28) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 28) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 29" do str = "tuvwxyz" - lambda { @object.send(@method, str, 29) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 29) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 30" do str = "uvwxyz" - lambda { @object.send(@method, str, 30) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 30) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 31" do str = "vwxyz" - lambda { @object.send(@method, str, 31) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 31) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 32" do str = "wxyz" - lambda { @object.send(@method, str, 32) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 32) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 33" do str = "xyz" - lambda { @object.send(@method, str, 33) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 33) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 34" do str = "yz" - lambda { @object.send(@method, str, 34) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 34) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 35" do str = "z" - lambda { @object.send(@method, str, 35) }.should raise_error(ArgumentError) + -> { @object.send(@method, str, 35) }.should raise_error(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 36" do - lambda { @object.send(@method, "{", 36) }.should raise_error(ArgumentError) + -> { @object.send(@method, "{", 36) }.should raise_error(ArgumentError) end end @@ -673,9 +799,9 @@ describe "Kernel.Integer" do # TODO: fix these specs it_behaves_like :kernel_integer, :Integer, Kernel - it_behaves_like "Integer() given a String", :Integer + it_behaves_like :kernel_integer_string, :Integer - it_behaves_like "Integer() given a String and base", :Integer + it_behaves_like :kernel_integer_string_base, :Integer it "is a public method" do Kernel.Integer(10).should == 10 @@ -687,9 +813,9 @@ describe "Kernel#Integer" do # TODO: fix these specs it_behaves_like :kernel_integer, :Integer, Object.new - it_behaves_like "Integer() given a String", :Integer + it_behaves_like :kernel_integer_string, :Integer - it_behaves_like "Integer() given a String and base", :Integer + it_behaves_like :kernel_integer_string_base, :Integer it "is a private method" do Kernel.should have_private_instance_method(:Integer) diff --git a/spec/ruby/core/kernel/Rational_spec.rb b/spec/ruby/core/kernel/Rational_spec.rb index 38f1da6333..cc11a35451 100644 --- a/spec/ruby/core/kernel/Rational_spec.rb +++ b/spec/ruby/core/kernel/Rational_spec.rb @@ -1,6 +1,236 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../../../shared/rational/Rational', __FILE__) +require_relative '../../spec_helper' +require_relative '../rational/fixtures/rational' describe "Kernel.Rational" do - it_behaves_like :kernel_Rational, :Rational + describe "passed Integer" do + # Guard against the Mathn library + guard -> { !defined?(Math.rsqrt) } do + it "returns a new Rational number with 1 as the denominator" do + Rational(1).should eql(Rational(1, 1)) + Rational(-3).should eql(Rational(-3, 1)) + Rational(bignum_value).should eql(Rational(bignum_value, 1)) + end + end + end + + describe "passed two integers" do + it "returns a new Rational number" do + rat = Rational(1, 2) + rat.numerator.should == 1 + rat.denominator.should == 2 + rat.should be_an_instance_of(Rational) + + rat = Rational(-3, -5) + rat.numerator.should == 3 + rat.denominator.should == 5 + rat.should be_an_instance_of(Rational) + + rat = Rational(bignum_value, 3) + rat.numerator.should == bignum_value + rat.denominator.should == 3 + rat.should be_an_instance_of(Rational) + end + + it "reduces the Rational" do + rat = Rational(2, 4) + rat.numerator.should == 1 + rat.denominator.should == 2 + + rat = Rational(3, 9) + rat.numerator.should == 1 + rat.denominator.should == 3 + end + end + + describe "when passed a String" do + it "converts the String to a Rational using the same method as String#to_r" do + r = Rational(13, 25) + s_r = ".52".to_r + r_s = Rational(".52") + + r_s.should == r + r_s.should == s_r + end + + it "scales the Rational value of the first argument by the Rational value of the second" do + Rational(".52", ".6").should == Rational(13, 15) + Rational(".52", "1.6").should == Rational(13, 40) + end + + it "does not use the same method as Float#to_r" do + r = Rational(3, 5) + f_r = 0.6.to_r + r_s = Rational("0.6") + + r_s.should == r + r_s.should_not == f_r + end + end + + describe "when passed a Numeric" do + it "calls #to_r to convert the first argument to a Rational" do + num = RationalSpecs::SubNumeric.new(2) + + Rational(num).should == Rational(2) + end + end + + describe "when passed a Complex" do + context "[Complex]" do + it "returns a Rational from the real part if the imaginary part is 0" do + Rational(Complex(1, 0)).should == Rational(1) + end + + it "raises a RangeError if the imaginary part is not 0" do + -> { Rational(Complex(1, 2)) }.should raise_error(RangeError, "can't convert 1+2i into Rational") + end + end + + context "[Numeric, Complex]" do + it "uses the real part if the imaginary part is 0" do + Rational(1, Complex(2, 0)).should == Rational(1, 2) + end + + it "divides a numerator by the Complex denominator if the imaginary part is not 0" do + Rational(1, Complex(2, 1)).should == Complex(2/5r, -1/5r) + end + end + end + + context "when passed neither a Numeric nor a String" do + it "converts to Rational with #to_r method" do + obj = Object.new + def obj.to_r; 1/2r; end + + Rational(obj).should == 1/2r + end + + it "tries to convert to Integer with #to_int method if it does not respond to #to_r" do + obj = Object.new + def obj.to_int; 1; end + + Rational(obj).should == 1r + end + + it "raises TypeError if it neither responds to #to_r nor #to_int method" do + -> { Rational([]) }.should raise_error(TypeError, "can't convert Array into Rational") + -> { Rational({}) }.should raise_error(TypeError, "can't convert Hash into Rational") + -> { Rational(nil) }.should raise_error(TypeError, "can't convert nil into Rational") + end + + it "swallows exception raised in #to_int method" do + object = Object.new + def object.to_int() raise NoMethodError; end + + -> { Rational(object) }.should raise_error(TypeError) + -> { Rational(object, 1) }.should raise_error(TypeError) + -> { Rational(1, object) }.should raise_error(TypeError) + end + + it "raises TypeError if #to_r does not return Rational" do + obj = Object.new + def obj.to_r; []; end + + -> { Rational(obj) }.should raise_error(TypeError, "can't convert Object to Rational (Object#to_r gives Array)") + end + end + + it "raises a ZeroDivisionError if the second argument is 0" do + -> { Rational(1, 0) }.should raise_error(ZeroDivisionError, "divided by 0") + -> { Rational(1, 0.0) }.should raise_error(ZeroDivisionError, "divided by 0") + end + + it "raises a TypeError if the first argument is nil" do + -> { Rational(nil) }.should raise_error(TypeError, "can't convert nil into Rational") + end + + it "raises a TypeError if the second argument is nil" do + -> { Rational(1, nil) }.should raise_error(TypeError, "can't convert nil into Rational") + end + + it "raises a TypeError if the first argument is a Symbol" do + -> { Rational(:sym) }.should raise_error(TypeError) + end + + it "raises a TypeError if the second argument is a Symbol" do + -> { Rational(1, :sym) }.should raise_error(TypeError) + end + + describe "when passed exception: false" do + describe "and [non-Numeric]" do + it "swallows an error" do + Rational(:sym, exception: false).should == nil + Rational("abc", exception: false).should == nil + end + + it "swallows an exception raised in #to_r" do + obj = Object.new + def obj.to_r; raise; end + Rational(obj, exception: false).should == nil + end + + it "swallows an exception raised in #to_int" do + obj = Object.new + def obj.to_int; raise; end + Rational(obj, exception: false).should == nil + end + end + + describe "and [non-Numeric, Numeric]" do + it "swallows an error" do + Rational(:sym, 1, exception: false).should == nil + Rational("abc", 1, exception: false).should == nil + end + + it "swallows an exception raised in #to_r" do + obj = Object.new + def obj.to_r; raise; end + Rational(obj, 1, exception: false).should == nil + end + + it "swallows an exception raised in #to_int" do + obj = Object.new + def obj.to_int; raise; end + Rational(obj, 1, exception: false).should == nil + end + end + + describe "and [anything, non-Numeric]" do + it "swallows an error" do + Rational(:sym, :sym, exception: false).should == nil + Rational("abc", :sym, exception: false).should == nil + end + + it "swallows an exception raised in #to_r" do + obj = Object.new + def obj.to_r; raise; end + Rational(obj, obj, exception: false).should == nil + end + + it "swallows an exception raised in #to_int" do + obj = Object.new + def obj.to_int; raise; end + Rational(obj, obj, exception: false).should == nil + end + end + + describe "and non-Numeric String arguments" do + it "swallows an error" do + Rational("a", "b", exception: false).should == nil + Rational("a", 0, exception: false).should == nil + Rational(0, "b", exception: false).should == nil + end + end + + describe "and nil arguments" do + it "swallows an error" do + Rational(nil, exception: false).should == nil + Rational(nil, nil, exception: false).should == nil + end + end + end + + it "freezes its result" do + Rational(1).frozen?.should == true + end end diff --git a/spec/ruby/core/kernel/String_spec.rb b/spec/ruby/core/kernel/String_spec.rb index b24bc798e5..7caec6eda5 100644 --- a/spec/ruby/core/kernel/String_spec.rb +++ b/spec/ruby/core/kernel/String_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe :kernel_String, shared: true do it "converts nil to a String" do @@ -32,7 +32,7 @@ describe :kernel_String, shared: true do undef_method :to_s end - lambda { @object.send(@method, obj) }.should raise_error(TypeError) + -> { @object.send(@method, obj) }.should raise_error(TypeError) end # #5158 @@ -44,7 +44,7 @@ describe :kernel_String, shared: true do end end - lambda { @object.send(@method, obj) }.should raise_error(TypeError) + -> { @object.send(@method, obj) }.should raise_error(TypeError) end it "raises a TypeError if #to_s is not defined, even though #respond_to?(:to_s) returns true" do @@ -57,7 +57,7 @@ describe :kernel_String, shared: true do end end - lambda { @object.send(@method, obj) }.should raise_error(TypeError) + -> { @object.send(@method, obj) }.should raise_error(TypeError) end it "calls #to_s if #respond_to?(:to_s) returns true" do @@ -74,11 +74,11 @@ describe :kernel_String, shared: true do it "raises a TypeError if #to_s does not return a String" do (obj = mock('123')).should_receive(:to_s).and_return(123) - lambda { @object.send(@method, obj) }.should raise_error(TypeError) + -> { @object.send(@method, obj) }.should raise_error(TypeError) end it "returns the same object if it is already a String" do - string = "Hello" + string = +"Hello" string.should_not_receive(:to_s) string2 = @object.send(@method, string) string.should equal(string2) diff --git a/spec/ruby/core/kernel/__callee___spec.rb b/spec/ruby/core/kernel/__callee___spec.rb index 91cc4cdafa..3059ea8b57 100644 --- a/spec/ruby/core/kernel/__callee___spec.rb +++ b/spec/ruby/core/kernel/__callee___spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/__callee__', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/__callee__' describe "Kernel.__callee__" do it "returns the current method, even when aliased" do diff --git a/spec/ruby/core/kernel/__dir___spec.rb b/spec/ruby/core/kernel/__dir___spec.rb index 395d30f494..242adbf48b 100644 --- a/spec/ruby/core/kernel/__dir___spec.rb +++ b/spec/ruby/core/kernel/__dir___spec.rb @@ -1,13 +1,27 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Kernel#__dir__" do it "returns the real name of the directory containing the currently-executing file" do __dir__.should == File.realpath(File.dirname(__FILE__)) end + it "returns the expanded path of the directory when used in the main script" do + fixtures_dir = File.dirname(fixture(__FILE__, '__dir__.rb')) + Dir.chdir(fixtures_dir) do + ruby_exe("__dir__.rb").should == "__dir__.rb\n#{fixtures_dir}\n" + end + end + + context "when used in eval with a given filename" do + it "returns File.dirname(filename)" do + eval("__dir__", nil, "foo.rb").should == "." + eval("__dir__", nil, "foo/bar.rb").should == "foo" + end + end + context "when used in eval with top level binding" do - it "returns the real name of the directory containing the currently-executing file" do - eval("__dir__", binding).should == File.realpath(File.dirname(__FILE__)) + it "returns nil" do + eval("__dir__", binding).should == nil end end end diff --git a/spec/ruby/core/kernel/__method___spec.rb b/spec/ruby/core/kernel/__method___spec.rb index 936a6b2f00..578d25640d 100644 --- a/spec/ruby/core/kernel/__method___spec.rb +++ b/spec/ruby/core/kernel/__method___spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/__method__', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/__method__' describe "Kernel.__method__" do it "returns the current method, even when aliased" do diff --git a/spec/ruby/core/kernel/abort_spec.rb b/spec/ruby/core/kernel/abort_spec.rb index eb9c1c30c7..f8152718c5 100644 --- a/spec/ruby/core/kernel/abort_spec.rb +++ b/spec/ruby/core/kernel/abort_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../../../shared/process/abort', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../shared/process/abort' describe "Kernel#abort" do it "is a private method" do diff --git a/spec/ruby/core/kernel/at_exit_spec.rb b/spec/ruby/core/kernel/at_exit_spec.rb index 9fcb99148c..ebd9a71d15 100644 --- a/spec/ruby/core/kernel/at_exit_spec.rb +++ b/spec/ruby/core/kernel/at_exit_spec.rb @@ -1,42 +1,17 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../shared/kernel/at_exit' describe "Kernel.at_exit" do + it_behaves_like :kernel_at_exit, :at_exit + it "is a private method" do Kernel.should have_private_instance_method(:at_exit) end - it "runs after all other code" do - ruby_exe("at_exit {print 5}; print 6").should == "65" - end - - it "runs in reverse order of registration" do - code = "at_exit {print 4};at_exit {print 5}; print 6; at_exit {print 7}" - ruby_exe(code).should == "6754" - end - - it "allows calling exit inside at_exit handler" do - code = "at_exit {print 3}; at_exit {print 4; exit; print 5}; at_exit {print 6}" - ruby_exe(code).should == "643" + it "raises ArgumentError if called without a block" do + -> { at_exit }.should raise_error(ArgumentError, "called without a block") end - - it "gives access to the last raised exception" do - code = <<-EOC - at_exit do - puts "The exception matches: \#{$! == $exception}" - end - - begin - raise "foo" - rescue => $exception - raise - end - EOC - - result = ruby_exe(code, args: "2>&1", escape: true) - result.should =~ /The exception matches: true/ - end - end describe "Kernel#at_exit" do diff --git a/spec/ruby/core/kernel/autoload_spec.rb b/spec/ruby/core/kernel/autoload_spec.rb index b2aab5a895..5edb70541d 100644 --- a/spec/ruby/core/kernel/autoload_spec.rb +++ b/spec/ruby/core/kernel/autoload_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' # These specs only illustrate the basic autoload cases # and where toplevel autoload behaves differently from @@ -7,7 +7,9 @@ require File.expand_path('../fixtures/classes', __FILE__) autoload :KSAutoloadA, "autoload_a.rb" autoload :KSAutoloadB, fixture(__FILE__, "autoload_b.rb") -autoload :KSAutoloadC, fixture(__FILE__, "autoload_c.rb") +define_autoload_KSAutoloadCallsRequire = -> { + autoload :KSAutoloadCallsRequire, "main_autoload_not_exist.rb" +} def check_autoload(const) autoload? const @@ -42,10 +44,12 @@ describe "Kernel#autoload" do KSAutoloadB.loaded.should == :ksautoload_b end - it "does not call Kernel.require or Kernel.load to load the file" do - Kernel.should_not_receive(:require) - Kernel.should_not_receive(:load) - KSAutoloadC.loaded.should == :ksautoload_c + it "calls main.require(path) to load the file" do + define_autoload_KSAutoloadCallsRequire.call + main = TOPLEVEL_BINDING.eval("self") + main.should_receive(:require).with("main_autoload_not_exist.rb") + # The constant won't be defined since require is mocked to do nothing + -> { KSAutoloadCallsRequire }.should raise_error(NameError) end it "can autoload in instance_eval" do @@ -55,9 +59,43 @@ describe "Kernel#autoload" do end end + describe "inside a Class.new method body" do + # NOTE: this spec is being discussed in https://github.com/ruby/spec/pull/839 + it "should define on the new anonymous class" do + cls = Class.new do + def go + autoload :Object, 'bogus' + autoload? :Object + end + end + + cls.new.go.should == 'bogus' + cls.autoload?(:Object).should == 'bogus' + end + end + describe "when Object is frozen" do it "raises a FrozenError before defining the constant" do - ruby_exe(fixture(__FILE__, "autoload_frozen.rb")).should == "#{frozen_error_class} - nil" + ruby_exe(fixture(__FILE__, "autoload_frozen.rb")).should == "FrozenError - nil" + end + end + + describe "when called from included module's method" do + before :all do + @path = fixture(__FILE__, "autoload_from_included_module.rb") + KernelSpecs::AutoloadMethodIncluder.new.setup_autoload(@path) + end + + it "setups the autoload on the included module" do + KernelSpecs::AutoloadMethod.autoload?(:AutoloadFromIncludedModule).should == @path + end + + it "the autoload is reachable from the class too" do + KernelSpecs::AutoloadMethodIncluder.autoload?(:AutoloadFromIncludedModule).should == @path + end + + it "the autoload relative to the included module works" do + KernelSpecs::AutoloadMethod::AutoloadFromIncludedModule.loaded.should == :autoload_from_included_module end end end @@ -107,6 +145,25 @@ describe "Kernel.autoload" do p.should_receive(:to_path).and_return @non_existent Kernel.autoload :KSAutoloadAA, p end + + describe "when called from included module's method" do + before :all do + @path = fixture(__FILE__, "autoload_from_included_module2.rb") + KernelSpecs::AutoloadMethodIncluder2.new.setup_autoload(@path) + end + + it "setups the autoload on the included module" do + KernelSpecs::AutoloadMethod2.autoload?(:AutoloadFromIncludedModule2).should == @path + end + + it "the autoload is reachable from the class too" do + KernelSpecs::AutoloadMethodIncluder2.autoload?(:AutoloadFromIncludedModule2).should == @path + end + + it "the autoload relative to the included module works" do + KernelSpecs::AutoloadMethod2::AutoloadFromIncludedModule2.loaded.should == :autoload_from_included_module2 + end + end end describe "Kernel.autoload?" do diff --git a/spec/ruby/core/kernel/backtick_spec.rb b/spec/ruby/core/kernel/backtick_spec.rb index eb25750bc2..834d5636c1 100644 --- a/spec/ruby/core/kernel/backtick_spec.rb +++ b/spec/ruby/core/kernel/backtick_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#`" do before :each do @@ -21,7 +21,7 @@ describe "Kernel#`" do it "lets the standard error stream pass through to the inherited stderr" do cmd = ruby_cmd('STDERR.print "error stream"') - lambda { + -> { `#{cmd}`.should == "" }.should output_to_fd("error stream", STDERR) end @@ -32,24 +32,28 @@ describe "Kernel#`" do end it "raises an Errno::ENOENT if the command is not executable" do - lambda { `nonexistent_command` }.should raise_error(Errno::ENOENT) + -> { `nonexistent_command` }.should raise_error(Errno::ENOENT) end platform_is_not :windows do + it "handles invalid UTF-8 bytes in command" do + `echo "testing\xC2 a non UTF-8 string"`.b.should == "testing\xC2 a non UTF-8 string\n".b + end + it "sets $? to the exit status of the executed sub-process" do ip = 'world' `echo disc #{ip}` $?.should be_kind_of(Process::Status) - $?.stopped?.should == false - $?.exited?.should == true + $?.should_not.stopped? + $?.should.exited? $?.exitstatus.should == 0 - $?.success?.should == true + $?.should.success? `echo disc #{ip}; exit 99` $?.should be_kind_of(Process::Status) - $?.stopped?.should == false - $?.exited?.should == true + $?.should_not.stopped? + $?.should.exited? $?.exitstatus.should == 99 - $?.success?.should == false + $?.should_not.success? end end @@ -58,16 +62,16 @@ describe "Kernel#`" do ip = 'world' `echo disc #{ip}` $?.should be_kind_of(Process::Status) - $?.stopped?.should == false - $?.exited?.should == true + $?.should_not.stopped? + $?.should.exited? $?.exitstatus.should == 0 - $?.success?.should == true + $?.should.success? `echo disc #{ip}& exit 99` $?.should be_kind_of(Process::Status) - $?.stopped?.should == false - $?.exited?.should == true + $?.should_not.stopped? + $?.should.exited? $?.exitstatus.should == 99 - $?.success?.should == false + $?.should_not.success? end end end diff --git a/spec/ruby/core/kernel/binding_spec.rb b/spec/ruby/core/kernel/binding_spec.rb index 0b33c86d3c..f1c9c6ec9f 100644 --- a/spec/ruby/core/kernel/binding_spec.rb +++ b/spec/ruby/core/kernel/binding_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel.binding" do it "returns a binding for the caller" do @@ -35,7 +35,7 @@ describe "Kernel#binding" do end it "raises a NameError on undefined variable" do - lambda { eval("a_fake_variable", @b1) }.should raise_error(NameError) + -> { eval("a_fake_variable", @b1) }.should raise_error(NameError) end it "uses the closure's self as self in the binding" do diff --git a/spec/ruby/core/kernel/block_given_spec.rb b/spec/ruby/core/kernel/block_given_spec.rb index 9454c938ae..aece4c821d 100644 --- a/spec/ruby/core/kernel/block_given_spec.rb +++ b/spec/ruby/core/kernel/block_given_spec.rb @@ -1,19 +1,24 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe :kernel_block_given, shared: true do it "returns true if and only if a block is supplied" do @object.accept_block {}.should == true @object.accept_block_as_argument {}.should == true + @object.accept_block_inside_block {}.should == true + @object.accept_block_as_argument_inside_block {}.should == true @object.accept_block.should == false @object.accept_block_as_argument.should == false + @object.accept_block_inside_block.should == false + @object.accept_block_as_argument_inside_block.should == false end # Clarify: Based on http://www.ruby-forum.com/topic/137822 it appears # that Matz wanted this to be true in 1.9. it "returns false when a method defined by define_method is called with a block" do @object.defined_block {}.should == false + @object.defined_block_inside_block {}.should == false end end diff --git a/spec/ruby/core/kernel/caller_locations_spec.rb b/spec/ruby/core/kernel/caller_locations_spec.rb index 69993c3ec0..a917dba504 100644 --- a/spec/ruby/core/kernel/caller_locations_spec.rb +++ b/spec/ruby/core/kernel/caller_locations_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/caller_locations', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/caller_locations' describe 'Kernel#caller_locations' do it 'is a private method' do @@ -7,13 +7,13 @@ describe 'Kernel#caller_locations' do end it 'returns an Array of caller locations' do - KernelSpecs::CallerLocationsTest.locations.empty?.should == false + KernelSpecs::CallerLocationsTest.locations.should_not.empty? end it 'returns an Array of caller locations using a custom offset' do locations = KernelSpecs::CallerLocationsTest.locations(2) - locations[0].absolute_path.end_with?('mspec.rb').should == true + locations[0].absolute_path.should.end_with?('mspec.rb') end it 'returns an Array of caller locations using a custom limit' do @@ -22,6 +22,42 @@ describe 'Kernel#caller_locations' do locations.length.should == 1 end + it "can be called with a range" do + locations1 = caller_locations(0) + locations2 = caller_locations(2..4) + locations1[2..4].map(&:to_s).should == locations2.map(&:to_s) + end + + it "works with endless ranges" do + locations1 = caller_locations(0) + locations2 = caller_locations(eval("(2..)")) + locations2.map(&:to_s).should == locations1[2..-1].map(&:to_s) + end + + it "works with beginless ranges" do + locations1 = caller_locations(0) + locations2 = caller_locations((...5)) + locations2.map(&:to_s)[eval("(2..)")].should == locations1[(...5)].map(&:to_s)[eval("(2..)")] + end + + it "can be called with a range whose end is negative" do + locations1 = caller_locations(0) + locations2 = caller_locations(2..-1) + locations3 = caller_locations(2..-2) + locations1[2..-1].map(&:to_s).should == locations2.map(&:to_s) + locations1[2..-2].map(&:to_s).should == locations3.map(&:to_s) + end + + it "must return nil if omitting more locations than available" do + caller_locations(100).should == nil + caller_locations(100..-1).should == nil + end + + it "must return [] if omitting exactly the number of locations available" do + omit = caller_locations(0).length + caller_locations(omit).should == [] + end + it 'returns the locations as Thread::Backtrace::Location instances' do locations = KernelSpecs::CallerLocationsTest.locations @@ -29,4 +65,46 @@ describe 'Kernel#caller_locations' do location.kind_of?(Thread::Backtrace::Location).should == true end end + + it "must return the same locations when called with 1..-1 and when called with no arguments" do + caller_locations.map(&:to_s).should == caller_locations(1..-1).map(&:to_s) + end + + guard -> { Kernel.instance_method(:tap).source_location } do + ruby_version_is ""..."3.4" do + it "includes core library methods defined in Ruby" do + file, line = Kernel.instance_method(:tap).source_location + file.should.start_with?('<internal:') + + loc = nil + tap { loc = caller_locations(1, 1)[0] } + loc.label.should == "tap" + loc.path.should.start_with? "<internal:" + end + end + + ruby_version_is "3.4"..."4.0" do + it "includes core library methods defined in Ruby" do + file, line = Kernel.instance_method(:tap).source_location + file.should.start_with?('<internal:') + + loc = nil + tap { loc = caller_locations(1, 1)[0] } + loc.label.should == "Kernel#tap" + loc.path.should.start_with? "<internal:" + end + end + + ruby_version_is "4.0" do + it "does not include core library methods defined in Ruby" do + file, line = Kernel.instance_method(:tap).source_location + file.should.start_with?('<internal:') + + loc = nil + tap { loc = caller_locations(1, 1)[0] } + loc.label.should == "Kernel#tap" + loc.path.should == __FILE__ + end + end + end end diff --git a/spec/ruby/core/kernel/caller_spec.rb b/spec/ruby/core/kernel/caller_spec.rb index d9be29a8db..7cd703de5a 100644 --- a/spec/ruby/core/kernel/caller_spec.rb +++ b/spec/ruby/core/kernel/caller_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/caller', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/caller' describe 'Kernel#caller' do it 'is a private method' do @@ -7,7 +7,7 @@ describe 'Kernel#caller' do end it 'returns an Array of caller locations' do - KernelSpecs::CallerTest.locations.empty?.should == false + KernelSpecs::CallerTest.locations.should_not.empty? end it 'returns an Array of caller locations using a custom offset' do @@ -34,4 +34,76 @@ describe 'Kernel#caller' do locations[0].should include("#{__FILE__}:#{line}:in") end + + it "returns an Array with the block given to #at_exit at the base of the stack" do + path = fixture(__FILE__, "caller_at_exit.rb") + lines = ruby_exe(path).lines + lines.size.should == 2 + lines[0].should =~ /\A#{path}:6:in [`'](?:Object#)?foo'\n\z/ + lines[1].should =~ /\A#{path}:2:in [`']block in <main>'\n\z/ + end + + it "can be called with a range" do + locations1 = caller(0) + locations2 = caller(2..4) + locations1[2..4].should == locations2 + end + + it "works with endless ranges" do + locations1 = KernelSpecs::CallerTest.locations(0) + locations2 = KernelSpecs::CallerTest.locations(eval("(2..)")) + locations2.should == locations1[2..-1] + end + + it "works with beginless ranges" do + locations1 = KernelSpecs::CallerTest.locations(0) + locations2 = KernelSpecs::CallerTest.locations((..5)) + locations2[eval("(2..)")].should == locations1[(..5)][eval("(2..)")] + end + + it "can be called with a range whose end is negative" do + locations1 = caller(0) + locations2 = caller(2..-1) + locations3 = caller(2..-2) + locations1[2..-1].should == locations2 + locations1[2..-2].should == locations3 + end + + it "must return nil if omitting more locations than available" do + caller(100).should == nil + caller(100..-1).should == nil + end + + it "must return [] if omitting exactly the number of locations available" do + omit = caller(0).length + caller(omit).should == [] + end + + it "must return the same locations when called with 1..-1 and when called with no arguments" do + caller.should == caller(1..-1) + end + + guard -> { Kernel.instance_method(:tap).source_location } do + ruby_version_is ""..."4.0" do + it "includes core library methods defined in Ruby" do + file, line = Kernel.instance_method(:tap).source_location + file.should.start_with?('<internal:') + + loc = nil + tap { loc = caller(1, 1)[0] } + loc.should =~ /\A<internal:.*in [`'](?:Kernel#)?tap'\z/ + end + end + + ruby_version_is "4.0" do + it "includes core library methods defined in Ruby" do + file, line = Kernel.instance_method(:tap).source_location + file.should.start_with?('<internal:') + + loc = nil + tap { loc = caller(1, 1)[0] } + loc.should =~ /\A#{ __FILE__ }:.*in [`'](?:Kernel#)?tap'\z/ + end + end + end end diff --git a/spec/ruby/core/kernel/case_compare_spec.rb b/spec/ruby/core/kernel/case_compare_spec.rb index 5332aa7647..b8d30960e8 100644 --- a/spec/ruby/core/kernel/case_compare_spec.rb +++ b/spec/ruby/core/kernel/case_compare_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' module Specs diff --git a/spec/ruby/core/kernel/catch_spec.rb b/spec/ruby/core/kernel/catch_spec.rb index 35a4860f38..9f59d3b384 100644 --- a/spec/ruby/core/kernel/catch_spec.rb +++ b/spec/ruby/core/kernel/catch_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel.catch" do before :each do @@ -31,11 +31,11 @@ describe "Kernel.catch" do end it "raises an ArgumentError if a Symbol is thrown for a String catch value" do - lambda { catch("exit") { throw :exit } }.should raise_error(ArgumentError) + -> { catch("exit") { throw :exit } }.should raise_error(ArgumentError) end it "raises an ArgumentError if a String with different identity is thrown" do - lambda { catch("exit") { throw "exit" } }.should raise_error(ArgumentError) + -> { catch("exit".dup) { throw "exit".dup } }.should raise_error(ArgumentError) end it "catches a Symbol when thrown a matching Symbol" do @@ -116,7 +116,7 @@ describe "Kernel.catch" do end it "raises LocalJumpError if no block is given" do - lambda { catch :blah }.should raise_error(LocalJumpError) + -> { catch :blah }.should raise_error(LocalJumpError) end end diff --git a/spec/ruby/core/kernel/chomp_spec.rb b/spec/ruby/core/kernel/chomp_spec.rb index 524a4c8b1d..d30e77c35a 100644 --- a/spec/ruby/core/kernel/chomp_spec.rb +++ b/spec/ruby/core/kernel/chomp_spec.rb @@ -1,6 +1,6 @@ # -*- encoding: utf-8 -*- -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe :kernel_chomp, shared: true do it "removes the final newline of $_" do @@ -40,28 +40,26 @@ describe "Kernel#chomp" do it_behaves_like :kernel_chomp_private, :chomp end -with_feature :encoding do - describe :kernel_chomp_encoded, shared: true do - before :each do - @external = Encoding.default_external - Encoding.default_external = Encoding::UTF_8 - end - - after :each do - Encoding.default_external = @external - end - - it "removes the final carriage return, newline from a multi-byte $_" do - script = fixture __FILE__, "#{@method}.rb" - KernelSpecs.run_with_dash_n(script).should == "あれ" - end +describe :kernel_chomp_encoded, shared: true do + before :each do + @external = Encoding.default_external + Encoding.default_external = Encoding::UTF_8 end - describe "Kernel.chomp" do - it_behaves_like :kernel_chomp_encoded, "chomp" + after :each do + Encoding.default_external = @external end - describe "Kernel#chomp" do - it_behaves_like :kernel_chomp_encoded, "chomp_f" + it "removes the final carriage return, newline from a multi-byte $_" do + script = fixture __FILE__, "#{@method}.rb" + KernelSpecs.run_with_dash_n(script).should == "あれ" end end + +describe "Kernel.chomp" do + it_behaves_like :kernel_chomp_encoded, "chomp" +end + +describe "Kernel#chomp" do + it_behaves_like :kernel_chomp_encoded, "chomp_f" +end diff --git a/spec/ruby/core/kernel/chop_spec.rb b/spec/ruby/core/kernel/chop_spec.rb index 5106fefee8..9b91c011bc 100644 --- a/spec/ruby/core/kernel/chop_spec.rb +++ b/spec/ruby/core/kernel/chop_spec.rb @@ -1,6 +1,6 @@ # -*- encoding: utf-8 -*- -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe :kernel_chop, shared: true do it "removes the final character of $_" do @@ -28,28 +28,26 @@ describe "Kernel#chop" do it_behaves_like :kernel_chop, "chop" end -with_feature :encoding do - describe :kernel_chop_encoded, shared: true do - before :each do - @external = Encoding.default_external - Encoding.default_external = Encoding::UTF_8 - end - - after :each do - Encoding.default_external = @external - end - - it "removes the final multi-byte character from $_" do - script = fixture __FILE__, "#{@method}.rb" - KernelSpecs.run_with_dash_n(script).should == "あ" - end +describe :kernel_chop_encoded, shared: true do + before :each do + @external = Encoding.default_external + Encoding.default_external = Encoding::UTF_8 end - describe "Kernel.chop" do - it_behaves_like :kernel_chop_encoded, "chop" + after :each do + Encoding.default_external = @external end - describe "Kernel#chop" do - it_behaves_like :kernel_chop_encoded, "chop_f" + it "removes the final multi-byte character from $_" do + script = fixture __FILE__, "#{@method}.rb" + KernelSpecs.run_with_dash_n(script).should == "あ" end end + +describe "Kernel.chop" do + it_behaves_like :kernel_chop_encoded, "chop" +end + +describe "Kernel#chop" do + it_behaves_like :kernel_chop_encoded, "chop_f" +end diff --git a/spec/ruby/core/kernel/class_spec.rb b/spec/ruby/core/kernel/class_spec.rb index 0d7b40c366..b1d9df1671 100644 --- a/spec/ruby/core/kernel/class_spec.rb +++ b/spec/ruby/core/kernel/class_spec.rb @@ -1,11 +1,11 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#class" do it "returns the class of the object" do Object.new.class.should equal(Object) - 1.class.should equal(Fixnum) + 1.class.should equal(Integer) 3.14.class.should equal(Float) :hello.class.should equal(Symbol) "hello".class.should equal(String) @@ -19,7 +19,7 @@ describe "Kernel#class" do end it "returns the first non-singleton class" do - a = "hello" + a = +"hello" def a.my_singleton_method; end a.class.should equal(String) end diff --git a/spec/ruby/core/kernel/clone_spec.rb b/spec/ruby/core/kernel/clone_spec.rb index 48b3c24c7f..5adcbbe603 100644 --- a/spec/ruby/core/kernel/clone_spec.rb +++ b/spec/ruby/core/kernel/clone_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/dup_clone', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/dup_clone' describe "Kernel#clone" do it_behaves_like :kernel_dup_clone, :clone @@ -28,22 +28,87 @@ describe "Kernel#clone" do clone.class.should equal klass end - it "copies frozen state from the original" do - o2 = @obj.clone - @obj.freeze - o3 = @obj.clone + describe "with no arguments" do + it "copies frozen state from the original" do + o2 = @obj.clone + o2.should_not.frozen? - o2.frozen?.should == false - o3.frozen?.should == true + @obj.freeze + o3 = @obj.clone + o3.should.frozen? + end + + it 'copies frozen?' do + o = ''.freeze.clone + o.frozen?.should be_true + end + end + + describe "with freeze: nil" do + it "copies frozen state from the original, like #clone without arguments" do + o2 = @obj.clone(freeze: nil) + o2.should_not.frozen? + + @obj.freeze + o3 = @obj.clone(freeze: nil) + o3.should.frozen? + end + + it "copies frozen?" do + o = "".freeze.clone(freeze: nil) + o.frozen?.should be_true + end + end + + describe "with freeze: true" do + it 'makes a frozen copy if the original is frozen' do + @obj.freeze + @obj.clone(freeze: true).should.frozen? + end + + it 'freezes the copy even if the original was not frozen' do + @obj.clone(freeze: true).should.frozen? + end + + it "calls #initialize_clone with kwargs freeze: true" do + obj = KernelSpecs::CloneFreeze.new + obj.clone(freeze: true) + ScratchPad.recorded.should == [obj, { freeze: true }] + end + + it "calls #initialize_clone with kwargs freeze: true even if #initialize_clone only takes a single argument" do + obj = KernelSpecs::Clone.new + -> { obj.clone(freeze: true) }.should raise_error(ArgumentError, 'wrong number of arguments (given 2, expected 1)') + end end - ruby_version_is '2.4' do - it 'takes an option to copy freeze state or not' do - @obj.clone(freeze: true).frozen?.should == false - @obj.clone(freeze: false).frozen?.should == false + describe "with freeze: false" do + it 'does not freeze the copy if the original is frozen' do @obj.freeze - @obj.clone(freeze: true).frozen?.should == true - @obj.clone(freeze: false).frozen?.should == false + @obj.clone(freeze: false).should_not.frozen? + end + + it 'does not freeze the copy if the original is not frozen' do + @obj.clone(freeze: false).should_not.frozen? + end + + it "calls #initialize_clone with kwargs freeze: false" do + obj = KernelSpecs::CloneFreeze.new + obj.clone(freeze: false) + ScratchPad.recorded.should == [obj, { freeze: false }] + end + + it "calls #initialize_clone with kwargs freeze: false even if #initialize_clone only takes a single argument" do + obj = KernelSpecs::Clone.new + -> { obj.clone(freeze: false) }.should raise_error(ArgumentError, 'wrong number of arguments (given 2, expected 1)') + end + end + + describe "with freeze: anything else" do + it 'raises ArgumentError when passed not true/false/nil' do + -> { @obj.clone(freeze: 1) }.should raise_error(ArgumentError, /unexpected value for freeze: Integer/) + -> { @obj.clone(freeze: "") }.should raise_error(ArgumentError, /unexpected value for freeze: String/) + -> { @obj.clone(freeze: Object.new) }.should raise_error(ArgumentError, /unexpected value for freeze: Object/) end end @@ -109,10 +174,4 @@ describe "Kernel#clone" do cloned.bar.should == ['a'] end - - it 'copies frozen? and tainted?' do - o = ''.taint.freeze.clone - o.frozen?.should be_true - o.tainted?.should be_true - end end diff --git a/spec/ruby/core/kernel/comparison_spec.rb b/spec/ruby/core/kernel/comparison_spec.rb index 2aa4358600..affdc5c00d 100644 --- a/spec/ruby/core/kernel/comparison_spec.rb +++ b/spec/ruby/core/kernel/comparison_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Kernel#<=>" do it "returns 0 if self" do diff --git a/spec/ruby/core/kernel/define_singleton_method_spec.rb b/spec/ruby/core/kernel/define_singleton_method_spec.rb index de6f7fc286..24acec84f5 100644 --- a/spec/ruby/core/kernel/define_singleton_method_spec.rb +++ b/spec/ruby/core/kernel/define_singleton_method_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#define_singleton_method" do describe "when given an UnboundMethod" do @@ -21,7 +21,7 @@ describe "Kernel#define_singleton_method" do um = KernelSpecs::Parent.method(:parent_class_method).unbind KernelSpecs::Child.send :define_singleton_method, :child_class_method, um KernelSpecs::Child.child_class_method.should == :foo - lambda{KernelSpecs::Parent.child_class_method}.should raise_error(NoMethodError) + ->{KernelSpecs::Parent.child_class_method}.should raise_error(NoMethodError) end it "will raise when attempting to define an object's singleton method from another object's singleton method" do @@ -33,7 +33,7 @@ describe "Kernel#define_singleton_method" do end end um = p.method(:singleton_method).unbind - lambda{ other.send :define_singleton_method, :other_singleton_method, um }.should raise_error(TypeError) + ->{ other.send :define_singleton_method, :other_singleton_method, um }.should raise_error(TypeError) end end @@ -41,7 +41,7 @@ describe "Kernel#define_singleton_method" do it "defines a new method with the given name and the given block as body in self" do class DefineSingletonMethodSpecClass define_singleton_method(:block_test1) { self } - define_singleton_method(:block_test2, &lambda { self }) + define_singleton_method(:block_test2, &-> { self }) end o = DefineSingletonMethodSpecClass @@ -50,11 +50,11 @@ describe "Kernel#define_singleton_method" do end it "raises a TypeError when the given method is no Method/Proc" do - lambda { + -> { Class.new { define_singleton_method(:test, "self") } }.should raise_error(TypeError) - lambda { + -> { Class.new { define_singleton_method(:test, 1234) } }.should raise_error(TypeError) end @@ -63,7 +63,7 @@ describe "Kernel#define_singleton_method" do obj = Object.new obj.define_singleton_method(:test) { "world!" } obj.test.should == "world!" - lambda { + -> { Object.new.test }.should raise_error(NoMethodError) end @@ -81,21 +81,40 @@ describe "Kernel#define_singleton_method" do it "raises an ArgumentError when no block is given" do obj = Object.new - lambda { + -> { obj.define_singleton_method(:test) }.should raise_error(ArgumentError) end - ruby_version_is "2.3" do - it "does not use the caller block when no block is given" do - o = Object.new - def o.define(name) - define_singleton_method(name) - end + it "does not use the caller block when no block is given" do + o = Object.new + def o.define(name) + define_singleton_method(name) + end + + -> { + o.define(:foo) { raise "not used" } + }.should raise_error(ArgumentError) + end - lambda { - o.define(:foo) { raise "not used" } - }.should raise_error(ArgumentError) + it "always defines the method with public visibility" do + cls = Class.new + def cls.define(name, &block) + private + define_singleton_method(name, &block) end + + -> { + suppress_warning do + cls.define(:foo) { :ok } + end + cls.foo.should == :ok + }.should_not raise_error(NoMethodError) + end + + it "cannot define a singleton method with a frozen singleton class" do + o = Object.new + o.freeze + -> { o.define_singleton_method(:foo) { 1 } }.should raise_error(FrozenError) end end diff --git a/spec/ruby/core/kernel/display_spec.rb b/spec/ruby/core/kernel/display_spec.rb index e771e14cdb..9d429a9fac 100644 --- a/spec/ruby/core/kernel/display_spec.rb +++ b/spec/ruby/core/kernel/display_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#display" do it "needs to be reviewed for spec completeness" diff --git a/spec/ruby/core/kernel/dup_spec.rb b/spec/ruby/core/kernel/dup_spec.rb index af7e924a66..70198abdb7 100644 --- a/spec/ruby/core/kernel/dup_spec.rb +++ b/spec/ruby/core/kernel/dup_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/dup_clone', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/dup_clone' describe "Kernel#dup" do it_behaves_like :kernel_dup_clone, :dup @@ -32,7 +32,7 @@ describe "Kernel#dup" do @obj.freeze dup = @obj.dup - dup.frozen?.should == false + dup.should_not.frozen? end it "copies instance variables" do @@ -44,7 +44,7 @@ describe "Kernel#dup" do it "does not copy singleton methods" do def @obj.special() :the_one end dup = @obj.dup - lambda { dup.special }.should raise_error(NameError) + -> { dup.special }.should raise_error(NameError) end it "does not copy modules included in the singleton class" do @@ -53,7 +53,7 @@ describe "Kernel#dup" do end dup = @obj.dup - lambda { dup.repr }.should raise_error(NameError) + -> { dup.repr }.should raise_error(NameError) end it "does not copy constants defined in the singleton class" do @@ -62,6 +62,6 @@ describe "Kernel#dup" do end dup = @obj.dup - lambda { class << dup; CLONE; end }.should raise_error(NameError) + -> { class << dup; CLONE; end }.should raise_error(NameError) end end diff --git a/spec/ruby/core/kernel/enum_for_spec.rb b/spec/ruby/core/kernel/enum_for_spec.rb index 819140e0e4..0092e20468 100644 --- a/spec/ruby/core/kernel/enum_for_spec.rb +++ b/spec/ruby/core/kernel/enum_for_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Kernel#enum_for" do it "needs to be reviewed for spec completeness" diff --git a/spec/ruby/core/kernel/eql_spec.rb b/spec/ruby/core/kernel/eql_spec.rb index 39c9fea7eb..e62a601a79 100644 --- a/spec/ruby/core/kernel/eql_spec.rb +++ b/spec/ruby/core/kernel/eql_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../../../shared/kernel/equal', __FILE__) +require_relative '../../spec_helper' +require_relative '../../shared/kernel/equal' describe "Kernel#eql?" do it "is a public instance method" do @@ -8,4 +8,3 @@ describe "Kernel#eql?" do it_behaves_like :object_equal, :eql? end - diff --git a/spec/ruby/core/kernel/equal_value_spec.rb b/spec/ruby/core/kernel/equal_value_spec.rb index a66493f283..2be151263d 100644 --- a/spec/ruby/core/kernel/equal_value_spec.rb +++ b/spec/ruby/core/kernel/equal_value_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#==" do it "returns true only if obj and other are the same object" do diff --git a/spec/ruby/core/kernel/eval_spec.rb b/spec/ruby/core/kernel/eval_spec.rb index 96fc0f5b71..e027294347 100644 --- a/spec/ruby/core/kernel/eval_spec.rb +++ b/spec/ruby/core/kernel/eval_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' EvalSpecs::A.new.c @@ -24,7 +24,7 @@ describe "Kernel#eval" do EvalSpecs::A::B.name.should == "EvalSpecs::A::B" end - it "evaluates such that consts are scoped to the class of the eval" do + it "evaluates such that constants are scoped to the class of the eval" do EvalSpecs::A::C.name.should == "EvalSpecs::A::C" end @@ -76,12 +76,12 @@ describe "Kernel#eval" do x = 1 bind = proc {} - lambda { eval("x", bind) }.should raise_error(TypeError) + -> { eval("x", bind) }.should raise_error(TypeError) end it "does not make Proc locals visible to evaluated code" do bind = proc { inner = 4 } - lambda { eval("inner", bind.binding) }.should raise_error(NameError) + -> { eval("inner", bind.binding) }.should raise_error(NameError) end # REWRITE ME: This obscures the real behavior of where locals are stored @@ -134,8 +134,8 @@ describe "Kernel#eval" do it "includes file and line information in syntax error" do expected = 'speccing.rb' - lambda { - eval('if true',TOPLEVEL_BINDING, expected) + -> { + eval('if true', TOPLEVEL_BINDING, expected) }.should raise_error(SyntaxError) { |e| e.message.should =~ /#{expected}:1:.+/ } @@ -143,8 +143,8 @@ describe "Kernel#eval" do it "evaluates string with given filename and negative linenumber" do expected_file = 'speccing.rb' - lambda { - eval('if true',TOPLEVEL_BINDING, expected_file, -100) + -> { + eval('if true', TOPLEVEL_BINDING, expected_file, -100) }.should raise_error(SyntaxError) { |e| e.message.should =~ /#{expected_file}:-100:.+/ } @@ -159,15 +159,106 @@ describe "Kernel#eval" do end end - it "uses the filename of the binding if none is provided" do - eval("__FILE__").should == "(eval)" - eval("__FILE__", binding).should == __FILE__ - eval("__FILE__", binding, "success").should == "success" - eval("eval '__FILE__', binding").should == "(eval)" - eval("eval '__FILE__', binding", binding).should == __FILE__ - eval("eval '__FILE__', binding", binding, 'success').should == 'success' + ruby_version_is ""..."3.3" do + it "uses (eval) filename if none is provided" do + eval("__FILE__").should == "(eval)" + eval("__FILE__", binding).should == "(eval)" + eval("__FILE__", binding, "success").should == "success" + eval("eval '__FILE__', binding").should == "(eval)" + eval("eval '__FILE__', binding", binding).should == "(eval)" + eval("eval '__FILE__', binding", binding, 'success').should == '(eval)' + eval("eval '__FILE__', binding, 'success'", binding).should == 'success' + end + + it 'uses (eval) for __FILE__ and 1 for __LINE__ with a binding argument' do + eval("[__FILE__, __LINE__]", binding).should == ["(eval)", 1] + end end + context "parameter forwarding" do + it "allows anonymous rest parameter forwarding" do + object = Object.new + def object.foo(a, b, c) + [a, b, c] + end + def object.bar(*) + eval "foo(*)" + end + + object.bar(1, 2, 3).should == [1, 2, 3] + end + + it "allows anonymous keyword parameters forwarding" do + object = Object.new + def object.foo(a:, b:, c:) + [a, b, c] + end + def object.bar(**) + eval "foo(**)" + end + + object.bar(a: 1, b: 2, c: 3).should == [1, 2, 3] + end + + it "allows anonymous block parameter forwarding" do + object = Object.new + def object.foo(&block) + block.call + end + def object.bar(&) + eval "foo(&)" + end + + object.bar { :foobar }.should == :foobar + end + + it "allows ... forwarding" do + object = Object.new + def object.foo(a, b:, &block) + [a, b, block.call] + end + def object.bar(...) + eval "foo(...)" + end + + object.bar(1, b: 2) { 3 }.should == [1, 2, 3] + end + + it "allows parameter forwarding to super" do + m = Module.new do + def foo(a, b:, &block) + [a, b, block.call] + end + end + + c = Class.new do + include m + + def foo(a, b:, &block) + eval "super" + end + end + + object = c.new + object.foo(1, b: 2) { 3 }.should == [1, 2, 3] + end + end + + ruby_version_is "3.3" do + it "uses (eval at __FILE__:__LINE__) if none is provided" do + eval("__FILE__").should == "(eval at #{__FILE__}:#{__LINE__})" + eval("__FILE__", binding).should == "(eval at #{__FILE__}:#{__LINE__})" + eval("__FILE__", binding, "success").should == "success" + eval("eval '__FILE__', binding").should == "(eval at (eval at #{__FILE__}:#{__LINE__}):1)" + eval("eval '__FILE__', binding", binding).should == "(eval at (eval at #{__FILE__}:#{__LINE__}):1)" + eval("eval '__FILE__', binding", binding, 'success').should == "(eval at success:1)" + eval("eval '__FILE__', binding, 'success'", binding).should == 'success' + end + + it 'uses (eval at __FILE__:__LINE__) for __FILE__ and 1 for __LINE__ with a binding argument' do + eval("[__FILE__, __LINE__]", binding).should == ["(eval at #{__FILE__}:#{__LINE__})", 1] + end + end # Found via Rubinius bug github:#149 it "does not alter the value of __FILE__ in the binding" do first_time = EvalSpecs.call_eval @@ -195,13 +286,27 @@ describe "Kernel#eval" do end it "does not pass the block to the method being eval'ed" do - lambda { + -> { eval('KernelSpecs::EvalTest.call_yield') { "content" } }.should raise_error(LocalJumpError) end it "returns from the scope calling #eval when evaluating 'return'" do - lambda { eval("return :eval") }.call.should == :eval + -> { eval("return :eval") }.call.should == :eval + end + + it "returns from the method calling #eval when evaluating 'return'" do + def eval_return(n) + eval("return n*2") + end + -> { eval_return(3) }.call.should == 6 + end + + it "returns from the method calling #eval when evaluating 'return' in BEGIN" do + def eval_return(n) + eval("BEGIN {return n*3}") + end + -> { eval_return(4) }.call.should == 12 end it "unwinds through a Proc-style closure and returns from a lambda-style closure in the closure chain" do @@ -213,4 +318,253 @@ describe "Kernel#eval" do code = fixture __FILE__, "eval_return_without_lambda.rb" ruby_exe(code).chomp.should == "a,b,c,e,LocalJumpError,f" end + + it "can be called with Method#call" do + method(:eval).call("2 * 3").should == 6 + end + + it "has the correct default definee when called through Method#call" do + class EvalSpecs + method(:eval).call("def eval_spec_method_call; end") + EvalSpecs.should have_instance_method(:eval_spec_method_call) + end + end + + it "makes flip-flop operator work correctly" do + ScratchPad.record [] + + eval "10.times { |i| ScratchPad << i if (i == 4)...(i == 4) }" + ScratchPad.recorded.should == [4, 5, 6, 7, 8, 9] + + ScratchPad.clear + end + + it "returns nil if given an empty string" do + eval("").should == nil + end + + context "with shebang" do + it "ignores shebang with ruby interpreter" do + pid = eval(<<~CODE.b) + #!/usr/bin/env ruby + Process.pid + CODE + + pid.should == Process.pid + end + + it "ignores shebang with non-ruby interpreter" do + pid = eval(<<~CODE.b) + #!/usr/bin/env puma + Process.pid + CODE + + pid.should == Process.pid + end + end + + # See language/magic_comment_spec.rb for more magic comments specs + describe "with a magic encoding comment" do + it "uses the magic comment encoding for the encoding of literal strings" do + code = "# encoding: UTF-8\n'é'.encoding".b + code.encoding.should == Encoding::BINARY + eval(code).should == Encoding::UTF_8 + end + + it "uses the magic comment encoding for parsing constants" do + code = <<CODE.b +# encoding: UTF-8 +class EvalSpecs + Vπ = 3.14 +end +CODE + code.encoding.should == Encoding::BINARY + eval(code) + EvalSpecs.constants(false).should include(:"Vπ") + EvalSpecs::Vπ.should == 3.14 + ensure + EvalSpecs.send(:remove_const, :Vπ) + end + + it "allows an emacs-style magic comment encoding" do + code = <<CODE.b +# -*- encoding: UTF-8 -*- +class EvalSpecs +Vπemacs = 3.14 +end +CODE + code.encoding.should == Encoding::BINARY + eval(code) + EvalSpecs.constants(false).should include(:"Vπemacs") + EvalSpecs::Vπemacs.should == 3.14 + ensure + EvalSpecs.send(:remove_const, :Vπemacs) + end + + it "allows spaces before the magic encoding comment" do + code = <<CODE.b +\t \t # encoding: UTF-8 +class EvalSpecs + Vπspaces = 3.14 +end +CODE + code.encoding.should == Encoding::BINARY + eval(code) + EvalSpecs.constants(false).should include(:"Vπspaces") + EvalSpecs::Vπspaces.should == 3.14 + ensure + EvalSpecs.send(:remove_const, :Vπspaces) + end + + it "allows a shebang line before the magic encoding comment" do + code = <<CODE.b +#!/usr/bin/env ruby +# encoding: UTF-8 +class EvalSpecs + Vπshebang = 3.14 +end +CODE + code.encoding.should == Encoding::BINARY + eval(code) + EvalSpecs.constants(false).should include(:"Vπshebang") + EvalSpecs::Vπshebang.should == 3.14 + ensure + EvalSpecs.send(:remove_const, :Vπshebang) + end + + it "allows a shebang line and some spaces before the magic encoding comment" do + code = <<CODE.b +#!/usr/bin/env ruby + # encoding: UTF-8 +class EvalSpecs + Vπshebang_spaces = 3.14 +end +CODE + code.encoding.should == Encoding::BINARY + eval(code) + EvalSpecs.constants(false).should include(:"Vπshebang_spaces") + EvalSpecs::Vπshebang_spaces.should == 3.14 + ensure + EvalSpecs.send(:remove_const, :Vπshebang_spaces) + end + + it "allows a magic encoding comment and a subsequent frozen_string_literal magic comment" do + frozen_string_default = "test".frozen? + + code = <<CODE.b +# encoding: UTF-8 +# frozen_string_literal: #{!frozen_string_default} +class EvalSpecs + Vπstring = "frozen" +end +CODE + code.encoding.should == Encoding::BINARY + eval(code) + EvalSpecs.constants(false).should include(:"Vπstring") + EvalSpecs::Vπstring.should == "frozen" + EvalSpecs::Vπstring.encoding.should == Encoding::UTF_8 + EvalSpecs::Vπstring.frozen?.should == !frozen_string_default + ensure + EvalSpecs.send(:remove_const, :Vπstring) + end + + it "allows a magic encoding comment and a frozen_string_literal magic comment on the same line in emacs style" do + code = <<CODE.b +# -*- encoding: UTF-8; frozen_string_literal: true -*- +class EvalSpecs +Vπsame_line = "frozen" +end +CODE + code.encoding.should == Encoding::BINARY + eval(code) + EvalSpecs.constants(false).should include(:"Vπsame_line") + EvalSpecs::Vπsame_line.should == "frozen" + EvalSpecs::Vπsame_line.encoding.should == Encoding::UTF_8 + EvalSpecs::Vπsame_line.frozen?.should be_true + ensure + EvalSpecs.send(:remove_const, :Vπsame_line) + end + + it "ignores the magic encoding comment if it is after a frozen_string_literal magic comment" do + frozen_string_default = "test".frozen? + code = <<CODE.b +# frozen_string_literal: #{!frozen_string_default} +# encoding: UTF-8 +class EvalSpecs + Vπfrozen_first = "frozen" +end +CODE + code.encoding.should == Encoding::BINARY + eval(code) + EvalSpecs.constants(false).should_not include(:"Vπfrozen_first") + binary_constant = "Vπfrozen_first".b.to_sym + EvalSpecs.constants(false).should include(binary_constant) + value = EvalSpecs.const_get(binary_constant) + value.should == "frozen" + value.encoding.should == Encoding::BINARY + value.frozen?.should == !frozen_string_default + ensure + EvalSpecs.send(:remove_const, binary_constant) + end + + it "ignores the frozen_string_literal magic comment if it appears after a token and warns if $VERBOSE is true" do + frozen_string_default = "test".frozen? + code = <<CODE +some_token_before_magic_comment = :anything +# frozen_string_literal: #{!frozen_string_default} +class EvalSpecs + Vπstring_not_frozen = "not frozen" +end +CODE + -> { eval(code) }.should complain(/warning: [`']frozen_string_literal' is ignored after any tokens/, verbose: true) + EvalSpecs::Vπstring_not_frozen.frozen?.should == frozen_string_default + EvalSpecs.send :remove_const, :Vπstring_not_frozen + + -> { eval(code) }.should_not complain(verbose: false) + EvalSpecs::Vπstring_not_frozen.frozen?.should == frozen_string_default + EvalSpecs.send :remove_const, :Vπstring_not_frozen + end + end + + describe 'with refinements' do + it "activates refinements from the eval scope" do + refinery = Module.new do + refine EvalSpecs::A do + def foo + "bar" + end + end + end + + result = nil + + Module.new do + using refinery + + result = eval "EvalSpecs::A.new.foo" + end + + result.should == "bar" + end + + it "activates refinements from the binding" do + refinery = Module.new do + refine EvalSpecs::A do + def foo + "bar" + end + end + end + + b = nil + m = Module.new do + using refinery + b = binding + end + + result = eval "EvalSpecs::A.new.foo", b + + result.should == "bar" + end + end end diff --git a/spec/ruby/core/kernel/exec_spec.rb b/spec/ruby/core/kernel/exec_spec.rb index 3a7b656914..3d9520ad67 100644 --- a/spec/ruby/core/kernel/exec_spec.rb +++ b/spec/ruby/core/kernel/exec_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#exec" do it "is a private method" do @@ -7,12 +7,12 @@ describe "Kernel#exec" do end it "runs the specified command, replacing current process" do - ruby_exe('exec "echo hello"; puts "fail"', escape: true).should == "hello\n" + ruby_exe('exec "echo hello"; puts "fail"').should == "hello\n" end end describe "Kernel.exec" do it "runs the specified command, replacing current process" do - ruby_exe('Kernel.exec "echo hello"; puts "fail"', escape: true).should == "hello\n" + ruby_exe('Kernel.exec "echo hello"; puts "fail"').should == "hello\n" end end diff --git a/spec/ruby/core/kernel/exit_spec.rb b/spec/ruby/core/kernel/exit_spec.rb index 61a6670cfd..93cec3fee5 100644 --- a/spec/ruby/core/kernel/exit_spec.rb +++ b/spec/ruby/core/kernel/exit_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../../../shared/process/exit', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../shared/process/exit' describe "Kernel#exit" do it "is a private method" do @@ -10,6 +10,10 @@ describe "Kernel#exit" do it_behaves_like :process_exit, :exit, KernelSpecs::Method.new end +describe "Kernel.exit" do + it_behaves_like :process_exit, :exit, Kernel +end + describe "Kernel#exit!" do it "is a private method" do Kernel.should have_private_instance_method(:exit!) @@ -18,10 +22,6 @@ describe "Kernel#exit!" do it_behaves_like :process_exit!, :exit!, "self" end -describe "Kernel.exit" do - it_behaves_like :process_exit, :exit, Kernel -end - describe "Kernel.exit!" do - it_behaves_like :process_exit!, :exit!, Kernel + it_behaves_like :process_exit!, :exit!, "Kernel" end diff --git a/spec/ruby/core/kernel/extend_spec.rb b/spec/ruby/core/kernel/extend_spec.rb index 482eef32e9..6342d8cae1 100644 --- a/spec/ruby/core/kernel/extend_spec.rb +++ b/spec/ruby/core/kernel/extend_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' module KernelSpecs::M def self.extend_object(o) @@ -46,20 +46,20 @@ describe "Kernel#extend" do end it "makes the class a kind_of? the argument" do - class C + c = Class.new do extend KernelSpecs::M end - (C.kind_of? KernelSpecs::M).should == true + (c.kind_of? KernelSpecs::M).should == true end it "raises an ArgumentError when no arguments given" do - lambda { Object.new.extend }.should raise_error(ArgumentError) + -> { Object.new.extend }.should raise_error(ArgumentError) end it "raises a TypeError when the argument is not a Module" do o = mock('o') klass = Class.new - lambda { o.extend(klass) }.should raise_error(TypeError) + -> { o.extend(klass) }.should raise_error(TypeError) end describe "on frozen instance" do @@ -69,11 +69,23 @@ describe "Kernel#extend" do end it "raises an ArgumentError when no arguments given" do - lambda { @frozen.extend }.should raise_error(ArgumentError) + -> { @frozen.extend }.should raise_error(ArgumentError) end - it "raises a RuntimeError" do - lambda { @frozen.extend @module }.should raise_error(RuntimeError) + it "raises a FrozenError" do + -> { @frozen.extend @module }.should raise_error(FrozenError) end end + + it "updated class methods of a module when it extends self and includes another module" do + a = Module.new do + extend self + end + b = Module.new do + def foo; :foo; end + end + + a.include b + a.foo.should == :foo + end end diff --git a/spec/ruby/core/kernel/fail_spec.rb b/spec/ruby/core/kernel/fail_spec.rb index 9f3601c233..fab622037e 100644 --- a/spec/ruby/core/kernel/fail_spec.rb +++ b/spec/ruby/core/kernel/fail_spec.rb @@ -1,31 +1,30 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' -describe "Kernel.fail" do +describe "Kernel#fail" do it "is a private method" do Kernel.should have_private_instance_method(:fail) end it "raises a RuntimeError" do - lambda { fail }.should raise_error(RuntimeError) + -> { fail }.should raise_error(RuntimeError) end it "accepts an Object with an exception method returning an Exception" do - class Boring - def self.exception(msg) - StandardError.new msg - end + obj = Object.new + def obj.exception(msg) + StandardError.new msg end - lambda { fail Boring, "..." }.should raise_error(StandardError) + -> { fail obj, "..." }.should raise_error(StandardError, "...") end it "instantiates the specified exception class" do - class LittleBunnyFooFoo < RuntimeError; end - lambda { fail LittleBunnyFooFoo }.should raise_error(LittleBunnyFooFoo) + error_class = Class.new(RuntimeError) + -> { fail error_class }.should raise_error(error_class) end it "uses the specified message" do - lambda { + -> { begin fail "the duck is not irish." rescue => e @@ -38,6 +37,6 @@ describe "Kernel.fail" do end end -describe "Kernel#fail" do +describe "Kernel.fail" do it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/core/kernel/fixtures/Complex.rb b/spec/ruby/core/kernel/fixtures/Complex.rb new file mode 100644 index 0000000000..bf14d55ad5 --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/Complex.rb @@ -0,0 +1,5 @@ +module KernelSpecs + def self.Complex_method(string) + Complex(string) + end +end diff --git a/spec/ruby/core/kernel/fixtures/__dir__.rb b/spec/ruby/core/kernel/fixtures/__dir__.rb new file mode 100644 index 0000000000..bf9a15e3c8 --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/__dir__.rb @@ -0,0 +1,2 @@ +puts __FILE__ +puts __dir__ diff --git a/spec/ruby/core/kernel/fixtures/autoload_c.rb b/spec/ruby/core/kernel/fixtures/autoload_c.rb deleted file mode 100644 index 4569b23669..0000000000 --- a/spec/ruby/core/kernel/fixtures/autoload_c.rb +++ /dev/null @@ -1,5 +0,0 @@ -module KSAutoloadC - def self.loaded - :ksautoload_c - end -end diff --git a/spec/ruby/core/kernel/fixtures/autoload_from_included_module.rb b/spec/ruby/core/kernel/fixtures/autoload_from_included_module.rb new file mode 100644 index 0000000000..f5bedc6992 --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/autoload_from_included_module.rb @@ -0,0 +1,9 @@ +module KernelSpecs + module AutoloadMethod + module AutoloadFromIncludedModule + def self.loaded + :autoload_from_included_module + end + end + end +end diff --git a/spec/ruby/core/kernel/fixtures/autoload_from_included_module2.rb b/spec/ruby/core/kernel/fixtures/autoload_from_included_module2.rb new file mode 100644 index 0000000000..f4f1cfbf7c --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/autoload_from_included_module2.rb @@ -0,0 +1,9 @@ +module KernelSpecs + module AutoloadMethod2 + module AutoloadFromIncludedModule2 + def self.loaded + :autoload_from_included_module2 + end + end + end +end diff --git a/spec/ruby/core/kernel/fixtures/caller_at_exit.rb b/spec/ruby/core/kernel/fixtures/caller_at_exit.rb new file mode 100644 index 0000000000..ca9d808597 --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/caller_at_exit.rb @@ -0,0 +1,7 @@ +at_exit { + foo +} + +def foo + puts caller(0) +end diff --git a/spec/ruby/core/kernel/fixtures/classes.rb b/spec/ruby/core/kernel/fixtures/classes.rb index afa2bec12f..0e2b81988f 100644 --- a/spec/ruby/core/kernel/fixtures/classes.rb +++ b/spec/ruby/core/kernel/fixtures/classes.rb @@ -49,7 +49,7 @@ module KernelSpecs def self.chomp(str, method, sep="\n") code = "$_ = #{str.inspect}; $/ = #{sep.inspect}; #{method}; print $_" - IO.popen([*ruby_exe, "-n", "-e", code], "r+") do |io| + IO.popen([*ruby_exe, "-W0", "-n", "-e", code], "r+") do |io| io.puts io.close_write io.read @@ -68,6 +68,7 @@ module KernelSpecs module SomeOtherModule; end module AncestorModule; end module MyModule; end + module MyPrependedModule; end module MyExtensionModule; end class AncestorClass < String @@ -80,6 +81,8 @@ module KernelSpecs class KindaClass < AncestorClass include MyModule + prepend MyPrependedModule + def initialize self.extend MyExtensionModule end @@ -216,10 +219,28 @@ module KernelSpecs block_given? end + def self.accept_block_inside_block() + yield_self { + block_given? + } + end + + def self.accept_block_as_argument_inside_block(&block) + yield_self { + block_given? + } + end + class << self define_method(:defined_block) do block_given? end + + define_method(:defined_block_inside_block) do + yield_self { + block_given? + } + end end end @@ -232,10 +253,28 @@ module KernelSpecs self.send(:block_given?) end + def self.accept_block_inside_block + yield_self { + self.send(:block_given?) + } + end + + def self.accept_block_as_argument_inside_block(&block) + yield_self { + self.send(:block_given?) + } + end + class << self define_method(:defined_block) do self.send(:block_given?) end + + define_method(:defined_block_inside_block) do + yield_self { + self.send(:block_given?) + } + end end end @@ -248,10 +287,28 @@ module KernelSpecs Kernel.block_given? end + def self.accept_block_inside_block + yield_self { + Kernel.block_given? + } + end + + def self.accept_block_as_argument_inside_block(&block) + yield_self { + Kernel.block_given? + } + end + class << self define_method(:defined_block) do Kernel.block_given? end + + define_method(:defined_block_inside_block) do + yield_self { + Kernel.block_given? + } + end end end @@ -278,14 +335,25 @@ module KernelSpecs @two = two end - def initialize_copy(other) + def initialize_copy(other, **kw) ScratchPad.record object_id end + + # define to support calling #clone with optional :freeze keyword argument + def initialize_clone(other, **kw) + super(other) # to call #initialize_copy + end end class Clone def initialize_clone(other) - ScratchPad.record other.object_id + ScratchPad.record other + end + end + + class CloneFreeze + def initialize_clone(other, **kwargs) + ScratchPad.record([other, kwargs]) end end @@ -325,7 +393,7 @@ module KernelSpecs def inner b = mp { return :good } - pr = lambda { |x| x.call } + pr = -> x { x.call } pr.call(b) @@ -334,21 +402,44 @@ module KernelSpecs end end + module LambdaSpecs + module ZSuper + def lambda + super + end + end + + class ForwardBlockWithZSuper + prepend(ZSuper) + end + + module Ampersand + def lambda(&block) + suppress_warning {super(&block)} + end + end + + class SuperAmpersand + prepend(Ampersand) + end + end + class RespondViaMissing def respond_to_missing?(method, priv=false) case method - when :handled_publicly - true - when :handled_privately - priv - when :not_handled - false - else - raise "Typo in method name" + when :handled_publicly + true + when :handled_privately + priv + when :not_handled + false + else + raise "Typo in method name: #{method.inspect}" end end def method_missing(method, *args) + raise "the method name should be a Symbol" unless Symbol === method "Done #{method}(#{args})" end end @@ -378,6 +469,63 @@ module KernelSpecs [3, 4] end end + + module AutoloadMethod + def setup_autoload(file) + autoload :AutoloadFromIncludedModule, file + end + end + + class AutoloadMethodIncluder + include AutoloadMethod + end + + module AutoloadMethod2 + def setup_autoload(file) + Kernel.autoload :AutoloadFromIncludedModule2, file + end + end + + class AutoloadMethodIncluder2 + include AutoloadMethod2 + end + + class WarnInNestedCall + def f4(s = "", n) + f3(s, n) + end + + def f3(s, n) + f2(s, n) + end + + def f2(s, n) + f1(s, n) + end + + def f1(s, n) + warn(s, uplevel: n) + end + + def warn_call_lineno; method(:f1).source_location[1] + 1; end + def f1_call_lineno; method(:f2).source_location[1] + 1; end + def f2_call_lineno; method(:f3).source_location[1] + 1; end + def f3_call_lineno; method(:f4).source_location[1] + 1; end + end + + CustomRangeInteger = Struct.new(:value) do + def to_int; value; end + def <=>(other); to_int <=> other.to_int; end + def -(other); self.class.new(to_int - other.to_int); end + def +(other); self.class.new(to_int + other.to_int); end + end + + CustomRangeFloat = Struct.new(:value) do + def to_f; value; end + def <=>(other); to_f <=> other.to_f; end + def -(other); to_f - other.to_f; end + def +(other); self.class.new(to_f + other.to_f); end + end end class EvalSpecs @@ -408,12 +556,3 @@ class EvalSpecs return f end end - -# for Kernel#sleep to have Channel in it's specs -# TODO: switch directly to queue for both Kernel#sleep and Thread specs? -unless defined? Channel - require 'thread' - class Channel < Queue - alias receive shift - end -end diff --git a/spec/ruby/core/kernel/fixtures/eval_return_with_lambda.rb b/spec/ruby/core/kernel/fixtures/eval_return_with_lambda.rb index a48b5685f3..9e2d045bf3 100644 --- a/spec/ruby/core/kernel/fixtures/eval_return_with_lambda.rb +++ b/spec/ruby/core/kernel/fixtures/eval_return_with_lambda.rb @@ -1,5 +1,5 @@ print "a," -x = lambda do +x = -> do print "b," Proc.new do print "c," diff --git a/spec/ruby/core/kernel/fixtures/singleton_methods.rb b/spec/ruby/core/kernel/fixtures/singleton_methods.rb new file mode 100644 index 0000000000..32ea0adf89 --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/singleton_methods.rb @@ -0,0 +1,13 @@ +module SingletonMethodsSpecs + module Prepended + def mspec_test_kernel_singleton_methods + end + public :mspec_test_kernel_singleton_methods + end + + ::Module.prepend Prepended + + module SelfExtending + extend self + end +end diff --git a/spec/ruby/core/kernel/fixtures/warn_core_method.rb b/spec/ruby/core/kernel/fixtures/warn_core_method.rb new file mode 100644 index 0000000000..fd82562404 --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/warn_core_method.rb @@ -0,0 +1,14 @@ +raise 'should be run without RubyGems' if defined?(Gem) + +public def deprecated(n=1) + # puts nil, caller(0), nil + warn "use X instead", uplevel: n +end + +1.times do # to test with a non-empty stack above the reported locations + deprecated + tap(&:deprecated) + tap { deprecated(2) } + # eval sources with a <internal: file are also ignored + eval "tap(&:deprecated)", nil, "<internal:should-be-skipped-by-warn-uplevel>" +end diff --git a/spec/ruby/core/kernel/fixtures/warn_require.rb b/spec/ruby/core/kernel/fixtures/warn_require.rb new file mode 100644 index 0000000000..c4b0733233 --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/warn_require.rb @@ -0,0 +1 @@ +warn 'warn-require-warning', uplevel: 1 diff --git a/spec/ruby/core/kernel/fixtures/warn_require_caller.rb b/spec/ruby/core/kernel/fixtures/warn_require_caller.rb new file mode 100644 index 0000000000..35a0f969f9 --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/warn_require_caller.rb @@ -0,0 +1,2 @@ +# Use a different line than just 1 +require "#{__dir__}/warn_require" diff --git a/spec/ruby/core/kernel/fork_spec.rb b/spec/ruby/core/kernel/fork_spec.rb index 8919d0914d..b37f9980e0 100644 --- a/spec/ruby/core/kernel/fork_spec.rb +++ b/spec/ruby/core/kernel/fork_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../../../shared/process/fork', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../shared/process/fork' describe "Kernel#fork" do it "is a private method" do diff --git a/spec/ruby/core/kernel/format_spec.rb b/spec/ruby/core/kernel/format_spec.rb index c73d5f0efb..1d0c000c15 100644 --- a/spec/ruby/core/kernel/format_spec.rb +++ b/spec/ruby/core/kernel/format_spec.rb @@ -1,6 +1,7 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +# NOTE: most specs are in sprintf_spec.rb, this is just an alias describe "Kernel#format" do it "is a private method" do Kernel.should have_private_instance_method(:format) @@ -11,4 +12,36 @@ describe "Kernel.format" do it "is accessible as a module function" do Kernel.format("%s", "hello").should == "hello" end + + describe "when $VERBOSE is true" do + it "warns if too many arguments are passed" do + code = <<~RUBY + $VERBOSE = true + format("test", 1) + RUBY + + ruby_exe(code, args: "2>&1").should include("warning: too many arguments for format string") + end + + it "does not warns if too many keyword arguments are passed" do + code = <<~RUBY + $VERBOSE = true + format("test %{test}", test: 1, unused: 2) + RUBY + + ruby_exe(code, args: "2>&1").should_not include("warning") + end + + ruby_bug "#20593", ""..."3.4" do + it "doesn't warns if keyword arguments are passed and none are used" do + code = <<~RUBY + $VERBOSE = true + format("test", test: 1) + format("test", {}) + RUBY + + ruby_exe(code, args: "2>&1").should_not include("warning") + end + end + end end diff --git a/spec/ruby/core/kernel/freeze_spec.rb b/spec/ruby/core/kernel/freeze_spec.rb index cf16512094..fa32d321cf 100644 --- a/spec/ruby/core/kernel/freeze_spec.rb +++ b/spec/ruby/core/kernel/freeze_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#freeze" do it "prevents self from being further modified" do @@ -51,21 +51,19 @@ describe "Kernel#freeze" do end end - ruby_version_is "2.5" do - describe "on a Complex" do - it "has no effect since it is already frozen" do - c = Complex(1.3, 3.1) - c.frozen?.should be_true - c.freeze - end + describe "on a Complex" do + it "has no effect since it is already frozen" do + c = Complex(1.3, 3.1) + c.frozen?.should be_true + c.freeze end + end - describe "on a Rational" do - it "has no effect since it is already frozen" do - r = Rational(1, 3) - r.frozen?.should be_true - r.freeze - end + describe "on a Rational" do + it "has no effect since it is already frozen" do + r = Rational(1, 3) + r.frozen?.should be_true + r.freeze end end @@ -74,12 +72,20 @@ describe "Kernel#freeze" do def mutate; @foo = 1; end end.new o.freeze - lambda {o.mutate}.should raise_error(RuntimeError) + -> {o.mutate}.should raise_error(RuntimeError) end it "causes instance_variable_set to raise RuntimeError" do o = Object.new o.freeze - lambda {o.instance_variable_set(:@foo, 1)}.should raise_error(RuntimeError) + -> {o.instance_variable_set(:@foo, 1)}.should raise_error(RuntimeError) + end + + it "freezes an object's singleton class" do + o = Object.new + c = o.singleton_class + c.frozen?.should == false + o.freeze + c.frozen?.should == true end end diff --git a/spec/ruby/core/kernel/frozen_spec.rb b/spec/ruby/core/kernel/frozen_spec.rb index fad1971985..a4cec4263d 100644 --- a/spec/ruby/core/kernel/frozen_spec.rb +++ b/spec/ruby/core/kernel/frozen_spec.rb @@ -1,13 +1,13 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#frozen?" do it "returns true if self is frozen" do o = mock('o') p = mock('p') p.freeze - o.frozen?.should == false - p.frozen?.should == true + o.should_not.frozen? + p.should.frozen? end describe "on true, false and nil" do @@ -50,29 +50,27 @@ describe "Kernel#frozen?" do end end - ruby_version_is "2.5" do - describe "on a Complex" do - it "returns true" do - c = Complex(1.3, 3.1) - c.frozen?.should be_true - end + describe "on a Complex" do + it "returns true" do + c = Complex(1.3, 3.1) + c.frozen?.should be_true + end - it "literal returns true" do - c = eval "1.3i" - c.frozen?.should be_true - end + it "literal returns true" do + c = eval "1.3i" + c.frozen?.should be_true end + end - describe "on a Rational" do - it "returns true" do - r = Rational(1, 3) - r.frozen?.should be_true - end + describe "on a Rational" do + it "returns true" do + r = Rational(1, 3) + r.frozen?.should be_true + end - it "literal returns true" do - r = eval "1/3r" - r.frozen?.should be_true - end + it "literal returns true" do + r = eval "1/3r" + r.frozen?.should be_true end end end diff --git a/spec/ruby/core/kernel/gets_spec.rb b/spec/ruby/core/kernel/gets_spec.rb index c775b2b7a4..104613dbfa 100644 --- a/spec/ruby/core/kernel/gets_spec.rb +++ b/spec/ruby/core/kernel/gets_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#gets" do it "is a private method" do diff --git a/spec/ruby/core/kernel/global_variables_spec.rb b/spec/ruby/core/kernel/global_variables_spec.rb index 739b800938..8bce8e25b7 100644 --- a/spec/ruby/core/kernel/global_variables_spec.rb +++ b/spec/ruby/core/kernel/global_variables_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel.global_variables" do it "is a private method" do diff --git a/spec/ruby/core/kernel/gsub_spec.rb b/spec/ruby/core/kernel/gsub_spec.rb index 005ed0063d..a0cb9f2a70 100644 --- a/spec/ruby/core/kernel/gsub_spec.rb +++ b/spec/ruby/core/kernel/gsub_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' # FIXME: These methods exist only when the -n or -p option is passed to # ruby, but we currently don't have a way of specifying that. @@ -10,7 +10,7 @@ ruby_version_is ""..."1.9" do end it "raises a TypeError if $_ is not a String" do - lambda { + -> { $_ = 123 gsub(/./, "!") }.should raise_error(TypeError) diff --git a/spec/ruby/core/kernel/initialize_clone_spec.rb b/spec/ruby/core/kernel/initialize_clone_spec.rb new file mode 100644 index 0000000000..21a90c19f0 --- /dev/null +++ b/spec/ruby/core/kernel/initialize_clone_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../spec_helper' + +describe "Kernel#initialize_clone" do + it "is a private instance method" do + Kernel.should have_private_instance_method(:initialize_clone) + end + + it "returns the receiver" do + a = Object.new + b = Object.new + a.send(:initialize_clone, b).should == a + end + + it "calls #initialize_copy" do + a = Object.new + b = Object.new + a.should_receive(:initialize_copy).with(b) + a.send(:initialize_clone, b) + end + + it "accepts a :freeze keyword argument for obj.clone(freeze: value)" do + a = Object.new + b = Object.new + a.send(:initialize_clone, b, freeze: true).should == a + end +end diff --git a/spec/ruby/core/kernel/initialize_copy_spec.rb b/spec/ruby/core/kernel/initialize_copy_spec.rb new file mode 100644 index 0000000000..d71ca9f60f --- /dev/null +++ b/spec/ruby/core/kernel/initialize_copy_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../spec_helper' + +describe "Kernel#initialize_copy" do + it "returns self" do + obj = Object.new + obj.send(:initialize_copy, obj).should.equal?(obj) + end + + it "does nothing if the argument is the same as the receiver" do + obj = Object.new + obj.send(:initialize_copy, obj).should.equal?(obj) + + obj = Object.new.freeze + obj.send(:initialize_copy, obj).should.equal?(obj) + + 1.send(:initialize_copy, 1).should.equal?(1) + end + + it "raises FrozenError if the receiver is frozen" do + -> { Object.new.freeze.send(:initialize_copy, Object.new) }.should raise_error(FrozenError) + -> { 1.send(:initialize_copy, Object.new) }.should raise_error(FrozenError) + end + + it "raises TypeError if the objects are of different class" do + klass = Class.new + sub = Class.new(klass) + a = klass.new + b = sub.new + message = 'initialize_copy should take same class object' + -> { a.send(:initialize_copy, b) }.should raise_error(TypeError, message) + -> { b.send(:initialize_copy, a) }.should raise_error(TypeError, message) + + -> { a.send(:initialize_copy, 1) }.should raise_error(TypeError, message) + -> { a.send(:initialize_copy, 1.0) }.should raise_error(TypeError, message) + end +end diff --git a/spec/ruby/core/kernel/initialize_dup_spec.rb b/spec/ruby/core/kernel/initialize_dup_spec.rb new file mode 100644 index 0000000000..6dff34b7ad --- /dev/null +++ b/spec/ruby/core/kernel/initialize_dup_spec.rb @@ -0,0 +1,20 @@ +require_relative '../../spec_helper' + +describe "Kernel#initialize_dup" do + it "is a private instance method" do + Kernel.should have_private_instance_method(:initialize_dup) + end + + it "returns the receiver" do + a = Object.new + b = Object.new + a.send(:initialize_dup, b).should == a + end + + it "calls #initialize_copy" do + a = Object.new + b = Object.new + a.should_receive(:initialize_copy).with(b) + a.send(:initialize_dup, b) + end +end diff --git a/spec/ruby/core/kernel/inspect_spec.rb b/spec/ruby/core/kernel/inspect_spec.rb index 92129ebbc5..1fa66cab98 100644 --- a/spec/ruby/core/kernel/inspect_spec.rb +++ b/spec/ruby/core/kernel/inspect_spec.rb @@ -1,19 +1,11 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#inspect" do it "returns a String" do Object.new.inspect.should be_an_instance_of(String) end - it "returns a tainted string if self is tainted" do - Object.new.taint.inspect.tainted?.should be_true - end - - it "returns an untrusted string if self is untrusted" do - Object.new.untrust.inspect.untrusted?.should be_true - end - it "does not call #to_s if it is defined" do # We must use a bare Object here obj = Object.new @@ -28,4 +20,71 @@ describe "Kernel#inspect" do obj = Object.new obj.inspect.should =~ /^#<Object:0x[0-9a-f]+>$/ end + + it "returns a String for an object without #class method" do + obj = Object.new + class << obj + undef_method :class + end + obj.inspect.should be_kind_of(String) + end + + ruby_version_is "4.0" do + it "calls #instance_variables_to_inspect private method to know which variables to display" do + obj = Object.new + obj.instance_eval do + @host = "localhost" + @user = "root" + @password = "hunter2" + end + obj.singleton_class.class_eval do + private def instance_variables_to_inspect = %i[@host @user @does_not_exist] + end + + inspected = obj.inspect.sub(/^#<Object:0x[0-9a-f]+/, '#<Object:0x00') + inspected.should == '#<Object:0x00 @host="localhost", @user="root">' + + obj = Object.new + obj.instance_eval do + @host = "localhost" + @user = "root" + @password = "hunter2" + end + obj.singleton_class.class_eval do + private def instance_variables_to_inspect = [] + end + + inspected = obj.inspect.sub(/^#<Object:0x[0-9a-f]+/, '#<Object:0x00') + inspected.should == "#<Object:0x00>" + end + + it "displays all instance variables if #instance_variables_to_inspect returns nil" do + obj = Object.new + obj.instance_eval do + @host = "localhost" + @user = "root" + @password = "hunter2" + end + obj.singleton_class.class_eval do + private def instance_variables_to_inspect = nil + end + + inspected = obj.inspect.sub(/^#<Object:0x[0-9a-f]+/, '#<Object:0x00') + inspected.should == %{#<Object:0x00 @host="localhost", @user="root", @password="hunter2">} + end + + it "raises an error if #instance_variables_to_inspect returns an invalid value" do + obj = Object.new + obj.instance_eval do + @host = "localhost" + @user = "root" + @password = "hunter2" + end + obj.singleton_class.class_eval do + private def instance_variables_to_inspect = {} + end + + ->{ obj.inspect }.should raise_error(TypeError, "Expected #instance_variables_to_inspect to return an Array or nil, but it returned Hash") + end + end end diff --git a/spec/ruby/core/kernel/instance_of_spec.rb b/spec/ruby/core/kernel/instance_of_spec.rb index 4801a1ff96..d1170d5047 100644 --- a/spec/ruby/core/kernel/instance_of_spec.rb +++ b/spec/ruby/core/kernel/instance_of_spec.rb @@ -1,7 +1,7 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' -describe Kernel, "#instance_of?" do +describe "Kernel#instance_of?" do before :each do @o = KernelSpecs::InstanceClass.new end @@ -33,8 +33,8 @@ describe Kernel, "#instance_of?" do end it "raises a TypeError if given an object that is not a Class nor a Module" do - lambda { @o.instance_of?(Object.new) }.should raise_error(TypeError) - lambda { @o.instance_of?('KernelSpecs::InstanceClass') }.should raise_error(TypeError) - lambda { @o.instance_of?(1) }.should raise_error(TypeError) + -> { @o.instance_of?(Object.new) }.should raise_error(TypeError) + -> { @o.instance_of?('KernelSpecs::InstanceClass') }.should raise_error(TypeError) + -> { @o.instance_of?(1) }.should raise_error(TypeError) end end diff --git a/spec/ruby/core/kernel/instance_variable_defined_spec.rb b/spec/ruby/core/kernel/instance_variable_defined_spec.rb index 969d36c731..2ebb582b43 100644 --- a/spec/ruby/core/kernel/instance_variable_defined_spec.rb +++ b/spec/ruby/core/kernel/instance_variable_defined_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#instance_variable_defined?" do before do @@ -27,7 +27,7 @@ describe "Kernel#instance_variable_defined?" do end it "raises a TypeError if passed an Object not defining #to_str" do - lambda do + -> do obj = mock("kernel instance_variable_defined?") @instance.instance_variable_defined? obj end.should raise_error(TypeError) diff --git a/spec/ruby/core/kernel/instance_variable_get_spec.rb b/spec/ruby/core/kernel/instance_variable_get_spec.rb index 0c564f11b6..f1d2a45df8 100644 --- a/spec/ruby/core/kernel/instance_variable_get_spec.rb +++ b/spec/ruby/core/kernel/instance_variable_get_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#instance_variable_get" do before :each do @@ -24,25 +24,25 @@ describe "Kernel#instance_variable_get" do end it "raises a TypeError when the passed argument does not respond to #to_str" do - lambda { @obj.instance_variable_get(Object.new) }.should raise_error(TypeError) + -> { @obj.instance_variable_get(Object.new) }.should raise_error(TypeError) end it "raises a TypeError when the passed argument can't be converted to a String" do obj = mock("to_str") obj.stub!(:to_str).and_return(123) - lambda { @obj.instance_variable_get(obj) }.should raise_error(TypeError) + -> { @obj.instance_variable_get(obj) }.should raise_error(TypeError) end it "raises a NameError when the conversion result does not start with an '@'" do obj = mock("to_str") obj.stub!(:to_str).and_return("test") - lambda { @obj.instance_variable_get(obj) }.should raise_error(NameError) + -> { @obj.instance_variable_get(obj) }.should raise_error(NameError) end it "raises a NameError when passed just '@'" do obj = mock("to_str") obj.stub!(:to_str).and_return('@') - lambda { @obj.instance_variable_get(obj) }.should raise_error(NameError) + -> { @obj.instance_variable_get(obj) }.should raise_error(NameError) end end @@ -57,15 +57,21 @@ describe "Kernel#instance_variable_get when passed Symbol" do end it "raises a NameError when passed :@ as an instance variable name" do - lambda { @obj.instance_variable_get(:"@") }.should raise_error(NameError) + -> { @obj.instance_variable_get(:"@") }.should raise_error(NameError) end it "raises a NameError when the passed Symbol does not start with an '@'" do - lambda { @obj.instance_variable_get(:test) }.should raise_error(NameError) + -> { @obj.instance_variable_get(:test) }.should raise_error(NameError) end it "raises a NameError when the passed Symbol is an invalid instance variable name" do - lambda { @obj.instance_variable_get(:"@0") }.should raise_error(NameError) + -> { @obj.instance_variable_get(:"@0") }.should raise_error(NameError) + end + + it "returns nil or raises for frozen objects" do + nil.instance_variable_get(:@foo).should == nil + -> { nil.instance_variable_get(:foo) }.should raise_error(NameError) + :foo.instance_variable_get(:@foo).should == nil end end @@ -80,26 +86,26 @@ describe "Kernel#instance_variable_get when passed String" do end it "raises a NameError when the passed String does not start with an '@'" do - lambda { @obj.instance_variable_get("test") }.should raise_error(NameError) + -> { @obj.instance_variable_get("test") }.should raise_error(NameError) end it "raises a NameError when the passed String is an invalid instance variable name" do - lambda { @obj.instance_variable_get("@0") }.should raise_error(NameError) + -> { @obj.instance_variable_get("@0") }.should raise_error(NameError) end it "raises a NameError when passed '@' as an instance variable name" do - lambda { @obj.instance_variable_get("@") }.should raise_error(NameError) + -> { @obj.instance_variable_get("@") }.should raise_error(NameError) end end -describe "Kernel#instance_variable_get when passed Fixnum" do +describe "Kernel#instance_variable_get when passed Integer" do before :each do @obj = Object.new @obj.instance_variable_set("@test", :test) end it "raises a TypeError" do - lambda { @obj.instance_variable_get(10) }.should raise_error(TypeError) - lambda { @obj.instance_variable_get(-10) }.should raise_error(TypeError) + -> { @obj.instance_variable_get(10) }.should raise_error(TypeError) + -> { @obj.instance_variable_get(-10) }.should raise_error(TypeError) end end diff --git a/spec/ruby/core/kernel/instance_variable_set_spec.rb b/spec/ruby/core/kernel/instance_variable_set_spec.rb index bac1bb5f99..2c25f4366f 100644 --- a/spec/ruby/core/kernel/instance_variable_set_spec.rb +++ b/spec/ruby/core/kernel/instance_variable_set_spec.rb @@ -1,43 +1,43 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#instance_variable_set" do it "sets the value of the specified instance variable" do - class Dog + dog = Class.new do def initialize(p1, p2) @a, @b = p1, p2 end end - Dog.new('cat', 99).instance_variable_set(:@a, 'dog').should == "dog" + dog.new('cat', 99).instance_variable_set(:@a, 'dog').should == "dog" end it "sets the value of the instance variable when no instance variables exist yet" do - class NoVariables; end - NoVariables.new.instance_variable_set(:@a, "new").should == "new" + no_variables = Class.new + no_variables.new.instance_variable_set(:@a, "new").should == "new" end it "raises a NameError exception if the argument is not of form '@x'" do - class NoDog; end - lambda { NoDog.new.instance_variable_set(:c, "cat") }.should raise_error(NameError) + no_dog = Class.new + -> { no_dog.new.instance_variable_set(:c, "cat") }.should raise_error(NameError) end it "raises a NameError exception if the argument is an invalid instance variable name" do - class DigitDog; end - lambda { DigitDog.new.instance_variable_set(:"@0", "cat") }.should raise_error(NameError) + digit_dog = Class.new + -> { digit_dog.new.instance_variable_set(:"@0", "cat") }.should raise_error(NameError) end it "raises a NameError when the argument is '@'" do - class DogAt; end - lambda { DogAt.new.instance_variable_set(:"@", "cat") }.should raise_error(NameError) + dog_at = Class.new + -> { dog_at.new.instance_variable_set(:"@", "cat") }.should raise_error(NameError) end - it "raises a TypeError if the instance variable name is a Fixnum" do - lambda { "".instance_variable_set(1, 2) }.should raise_error(TypeError) + it "raises a TypeError if the instance variable name is an Integer" do + -> { "".instance_variable_set(1, 2) }.should raise_error(TypeError) end it "raises a TypeError if the instance variable name is an object that does not respond to to_str" do class KernelSpecs::A; end - lambda { "".instance_variable_set(KernelSpecs::A.new, 3) }.should raise_error(TypeError) + -> { "".instance_variable_set(KernelSpecs::A.new, 3) }.should raise_error(TypeError) end it "raises a NameError if the passed object, when coerced with to_str, does not start with @" do @@ -46,11 +46,11 @@ describe "Kernel#instance_variable_set" do ":c" end end - lambda { "".instance_variable_set(KernelSpecs::B.new, 4) }.should raise_error(NameError) + -> { "".instance_variable_set(KernelSpecs::B.new, 4) }.should raise_error(NameError) end it "raises a NameError if pass an object that cannot be a symbol" do - lambda { "".instance_variable_set(:c, 1) }.should raise_error(NameError) + -> { "".instance_variable_set(:c, 1) }.should raise_error(NameError) end it "accepts as instance variable name any instance of a class that responds to to_str" do @@ -78,16 +78,28 @@ describe "Kernel#instance_variable_set" do end it "keeps stored object after any exceptions" do - lambda { @frozen.instance_variable_set(:@ivar, :replacement) }.should raise_error(Exception) + -> { @frozen.instance_variable_set(:@ivar, :replacement) }.should raise_error(Exception) @frozen.ivar.should equal(:origin) end - it "raises a RuntimeError when passed replacement is identical to stored object" do - lambda { @frozen.instance_variable_set(:@ivar, :origin) }.should raise_error(RuntimeError) + it "raises a FrozenError when passed replacement is identical to stored object" do + -> { @frozen.instance_variable_set(:@ivar, :origin) }.should raise_error(FrozenError) end - it "raises a RuntimeError when passed replacement is different from stored object" do - lambda { @frozen.instance_variable_set(:@ivar, :replacement) }.should raise_error(RuntimeError) + it "raises a FrozenError when passed replacement is different from stored object" do + -> { @frozen.instance_variable_set(:@ivar, :replacement) }.should raise_error(FrozenError) + end + + it "accepts unicode instance variable names" do + o = Object.new + o.instance_variable_set(:@💙, 42) + o.instance_variable_get(:@💙).should == 42 + end + + it "raises for frozen objects" do + -> { nil.instance_variable_set(:@foo, 42) }.should raise_error(FrozenError) + -> { nil.instance_variable_set(:foo, 42) }.should raise_error(NameError) + -> { :foo.instance_variable_set(:@foo, 42) }.should raise_error(FrozenError) end end end diff --git a/spec/ruby/core/kernel/instance_variables_spec.rb b/spec/ruby/core/kernel/instance_variables_spec.rb index f744ee3c7a..677d8bb7b2 100644 --- a/spec/ruby/core/kernel/instance_variables_spec.rb +++ b/spec/ruby/core/kernel/instance_variables_spec.rb @@ -1,15 +1,17 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#instance_variables" do describe "immediate values" do it "returns an empty array if no instance variables are defined" do - 0.instance_variables.should == [] + [0, 0.5, true, false, nil].each do |value| + value.instance_variables.should == [] + end end it "returns the correct array if an instance variable is added" do a = 0 - lambda{ a.instance_variable_set("@test", 1) }.should raise_error(RuntimeError) + ->{ a.instance_variable_set("@test", 1) }.should raise_error(RuntimeError) end end @@ -23,5 +25,16 @@ describe "Kernel#instance_variables" do a.instance_variable_set("@test", 1) a.instance_variables.should == [:@test] end + + it "returns the instances variables in the order declared" do + c = Class.new do + def initialize + @c = 1 + @a = 2 + @b = 3 + end + end + c.new.instance_variables.should == [:@c, :@a, :@b] + end end end diff --git a/spec/ruby/core/kernel/is_a_spec.rb b/spec/ruby/core/kernel/is_a_spec.rb index c67c6552a0..bd8c96529a 100644 --- a/spec/ruby/core/kernel/is_a_spec.rb +++ b/spec/ruby/core/kernel/is_a_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/kind_of', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/kind_of' describe "Kernel#is_a?" do - it_behaves_like(:kernel_kind_of , :is_a?) + it_behaves_like :kernel_kind_of, :is_a? end diff --git a/spec/ruby/core/kernel/iterator_spec.rb b/spec/ruby/core/kernel/iterator_spec.rb deleted file mode 100644 index e85f0dc612..0000000000 --- a/spec/ruby/core/kernel/iterator_spec.rb +++ /dev/null @@ -1,12 +0,0 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) - -describe "Kernel#iterator?" do - it "is a private method" do - Kernel.should have_private_instance_method(:iterator?) - end -end - -describe "Kernel.iterator?" do - it "needs to be reviewed for spec completeness" -end diff --git a/spec/ruby/core/kernel/itself_spec.rb b/spec/ruby/core/kernel/itself_spec.rb index 722d75d718..c906d7c3e8 100644 --- a/spec/ruby/core/kernel/itself_spec.rb +++ b/spec/ruby/core/kernel/itself_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#itself" do it "returns the receiver itself" do diff --git a/spec/ruby/core/kernel/kind_of_spec.rb b/spec/ruby/core/kernel/kind_of_spec.rb index 56a54ec859..c988edccb5 100644 --- a/spec/ruby/core/kernel/kind_of_spec.rb +++ b/spec/ruby/core/kernel/kind_of_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/kind_of', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/kind_of' describe "Kernel#kind_of?" do - it_behaves_like(:kernel_kind_of , :kind_of?) + it_behaves_like :kernel_kind_of, :kind_of? end diff --git a/spec/ruby/core/kernel/lambda_spec.rb b/spec/ruby/core/kernel/lambda_spec.rb index 8fa0075675..565536ac0d 100644 --- a/spec/ruby/core/kernel/lambda_spec.rb +++ b/spec/ruby/core/kernel/lambda_spec.rb @@ -1,11 +1,11 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/lambda', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/lambda' # The functionality of lambdas is specified in core/proc describe "Kernel.lambda" do - it_behaves_like(:kernel_lambda, :lambda) + it_behaves_like :kernel_lambda, :lambda it "is a private method" do Kernel.should have_private_instance_method(:lambda) @@ -16,11 +16,54 @@ describe "Kernel.lambda" do l.lambda?.should be_true end - it "returned the passed Proc if given an existing Proc" do - some_proc = proc {} - l = lambda(&some_proc) - l.should equal(some_proc) - l.lambda?.should be_false + it "creates a lambda-style Proc if given a literal block via #send" do + l = send(:lambda) { 42 } + l.lambda?.should be_true + end + + it "creates a lambda-style Proc if given a literal block via #__send__" do + l = __send__(:lambda) { 42 } + l.lambda?.should be_true + end + + ruby_version_is ""..."3.3" do + it "creates a lambda-style Proc if given a literal block via Kernel.public_send" do + suppress_warning do + l = Kernel.public_send(:lambda) { 42 } + l.lambda?.should be_true + end + end + + it "returns the passed Proc if given an existing Proc" do + some_proc = proc {} + l = suppress_warning {lambda(&some_proc)} + l.should equal(some_proc) + l.lambda?.should be_false + end + + it "creates a lambda-style Proc when called with zsuper" do + suppress_warning do + l = KernelSpecs::LambdaSpecs::ForwardBlockWithZSuper.new.lambda { 42 } + l.lambda?.should be_true + l.call.should == 42 + + lambda { l.call(:extra) }.should raise_error(ArgumentError) + end + end + + it "returns the passed Proc if given an existing Proc through super" do + some_proc = proc { } + l = KernelSpecs::LambdaSpecs::SuperAmpersand.new.lambda(&some_proc) + l.should equal(some_proc) + l.lambda?.should be_false + end + + it "does not create lambda-style Procs when captured with #method" do + kernel_lambda = method(:lambda) + l = suppress_warning {kernel_lambda.call { 42 }} + l.lambda?.should be_false + l.call(:extra).should == 42 + end end it "checks the arity of the call when no args are specified" do @@ -82,5 +125,34 @@ describe "Kernel.lambda" do it "allows long returns to flow through it" do KernelSpecs::Lambda.new.outer.should == :good end -end + it "treats the block as a Proc when lambda is re-defined" do + klass = Class.new do + def lambda (&block); block; end + def ret + lambda { return 1 }.call + 2 + end + end + klass.new.lambda { 42 }.should be_an_instance_of Proc + klass.new.ret.should == 1 + end + + context "when called without a literal block" do + ruby_version_is ""..."3.3" do + it "warns when proc isn't a lambda" do + -> { lambda(&proc{}) }.should complain("#{__FILE__}:#{__LINE__}: warning: lambda without a literal block is deprecated; use the proc without lambda instead\n") + end + end + + ruby_version_is "3.3" do + it "raises when proc isn't a lambda" do + -> { lambda(&proc{}) }.should raise_error(ArgumentError, /the lambda method requires a literal block/) + end + end + + it "doesn't warn when proc is lambda" do + -> { lambda(&lambda{}) }.should_not complain(verbose: true) + end + end +end diff --git a/spec/ruby/core/kernel/load_spec.rb b/spec/ruby/core/kernel/load_spec.rb index 36cc07e38a..a165cc4acd 100644 --- a/spec/ruby/core/kernel/load_spec.rb +++ b/spec/ruby/core/kernel/load_spec.rb @@ -1,7 +1,7 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../../../fixtures/code_loading', __FILE__) -require File.expand_path('../shared/load', __FILE__) -require File.expand_path('../shared/require', __FILE__) +require_relative '../../spec_helper' +require_relative '../../fixtures/code_loading' +require_relative 'shared/load' +require_relative 'shared/require' describe "Kernel#load" do before :each do diff --git a/spec/ruby/core/kernel/local_variables_spec.rb b/spec/ruby/core/kernel/local_variables_spec.rb index 7e8b364b4f..f6f1e15f52 100644 --- a/spec/ruby/core/kernel/local_variables_spec.rb +++ b/spec/ruby/core/kernel/local_variables_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#local_variables" do after :each do @@ -34,4 +34,15 @@ describe "Kernel#local_variables" do ScratchPad.recorded.should include(:a, :b) ScratchPad.recorded.length.should == 2 end + + it "includes only unique variable names" do + def local_var_method + a = 1 + 1.times do |;a| + return local_variables + end + end + + local_var_method.should == [:a] + end end diff --git a/spec/ruby/core/kernel/loop_spec.rb b/spec/ruby/core/kernel/loop_spec.rb index f23e3afb02..7c76c7d28e 100644 --- a/spec/ruby/core/kernel/loop_spec.rb +++ b/spec/ruby/core/kernel/loop_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel.loop" do it "is a private method" do @@ -55,18 +55,16 @@ describe "Kernel.loop" do end it "does not rescue other errors" do - lambda{ loop do raise StandardError end }.should raise_error( StandardError ) + ->{ loop do raise StandardError end }.should raise_error( StandardError ) end - ruby_version_is "2.3" do - it "returns StopIteration#result, the result value of a finished iterator" do - e = Enumerator.new { |y| - y << 1 - y << 2 - :stopped - } - loop { e.next }.should == :stopped - end + it "returns StopIteration#result, the result value of a finished iterator" do + e = Enumerator.new { |y| + y << 1 + y << 2 + :stopped + } + loop { e.next }.should == :stopped end describe "when no block is given" do diff --git a/spec/ruby/core/kernel/match_spec.rb b/spec/ruby/core/kernel/match_spec.rb index 8a117ed497..cd6330fe91 100644 --- a/spec/ruby/core/kernel/match_spec.rb +++ b/spec/ruby/core/kernel/match_spec.rb @@ -1,14 +1,7 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Kernel#=~" do - it "returns nil matching any object" do - o = Object.new - - (o =~ /Object/).should be_nil - (o =~ 'Object').should be_nil - (o =~ Object).should be_nil - (o =~ Object.new).should be_nil - (o =~ nil).should be_nil - (o =~ true).should be_nil + it "is no longer defined" do + Object.new.should_not.respond_to?(:=~) end end diff --git a/spec/ruby/core/kernel/method_spec.rb b/spec/ruby/core/kernel/method_spec.rb index 09a3f940ca..3fc566d6a6 100644 --- a/spec/ruby/core/kernel/method_spec.rb +++ b/spec/ruby/core/kernel/method_spec.rb @@ -1,9 +1,9 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/method', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/method' +require_relative 'fixtures/classes' describe "Kernel#method" do - it_behaves_like(:kernel_method, :method) + it_behaves_like :kernel_method, :method before :each do @obj = KernelSpecs::A.new @@ -29,9 +29,52 @@ describe "Kernel#method" do m.call.should == :defined end - it "can be called even if we only repond_to_missing? method, true" do + it "can be called even if we only respond_to_missing? method, true" do m = KernelSpecs::RespondViaMissing.new.method(:handled_privately) m.should be_an_instance_of(Method) m.call(1, 2, 3).should == "Done handled_privately([1, 2, 3])" end + + it "can call a #method_missing accepting zero or one arguments" do + cls = Class.new do + def respond_to_missing?(name, *) + name == :foo or super + end + def method_missing + :no_args + end + end + m = cls.new.method(:foo) + -> { m.call }.should raise_error(ArgumentError) + + cls = Class.new do + def respond_to_missing?(name, *) + name == :bar or super + end + def method_missing(m) + m + end + end + m = cls.new.method(:bar) + m.call.should == :bar + end + + describe "converts the given name to a String using #to_str" do + it "calls #to_str to convert the given name to a String" do + name = mock("method-name") + name.should_receive(:to_str).and_return("hash") + Object.method(name).should == Object.method(:hash) + end + + it "raises a TypeError if the given name can't be converted to a String" do + -> { Object.method(nil) }.should raise_error(TypeError) + -> { Object.method([]) }.should raise_error(TypeError) + end + + it "raises a NoMethodError if the given argument raises a NoMethodError during type coercion to a String" do + name = mock("method-name") + name.should_receive(:to_str).and_raise(NoMethodError) + -> { Object.method(name) }.should raise_error(NoMethodError) + end + end end diff --git a/spec/ruby/core/kernel/methods_spec.rb b/spec/ruby/core/kernel/methods_spec.rb index 5dfb17d4cb..fb7a7e8be9 100644 --- a/spec/ruby/core/kernel/methods_spec.rb +++ b/spec/ruby/core/kernel/methods_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../../../fixtures/reflection', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../fixtures/reflection' # TODO: rewrite describe "Kernel#methods" do diff --git a/spec/ruby/core/kernel/nil_spec.rb b/spec/ruby/core/kernel/nil_spec.rb index 0b5e34f7f1..7418245f26 100644 --- a/spec/ruby/core/kernel/nil_spec.rb +++ b/spec/ruby/core/kernel/nil_spec.rb @@ -1,6 +1,12 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' -describe "Kernel#nil?" do - it "needs to be reviewed for spec completeness" +describe 'Kernel#nil?' do + it 'returns false' do + Object.should_not.nil? + Object.new.should_not.nil? + ''.should_not.nil? + [].should_not.nil? + {}.should_not.nil? + end end diff --git a/spec/ruby/core/kernel/not_match_spec.rb b/spec/ruby/core/kernel/not_match_spec.rb index 42bd45c106..082e56fed7 100644 --- a/spec/ruby/core/kernel/not_match_spec.rb +++ b/spec/ruby/core/kernel/not_match_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#!~" do class KernelSpecs::NotMatch @@ -14,6 +14,10 @@ describe "Kernel#!~" do (obj !~ :foo).should == false end + it "raises NoMethodError if self does not respond to #=~" do + -> { Object.new !~ :foo }.should raise_error(NoMethodError) + end + it 'can be overridden in subclasses' do obj = KernelSpecs::NotMatch.new (obj !~ :bar).should == :foo diff --git a/spec/ruby/core/kernel/object_id_spec.rb b/spec/ruby/core/kernel/object_id_spec.rb index 0a12415a40..ef9e80c831 100644 --- a/spec/ruby/core/kernel/object_id_spec.rb +++ b/spec/ruby/core/kernel/object_id_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../../../shared/kernel/object_id', __FILE__) +require_relative '../../spec_helper' +require_relative '../../shared/kernel/object_id' describe "Kernel#object_id" do it_behaves_like :object_id, :object_id, Object diff --git a/spec/ruby/core/kernel/open_spec.rb b/spec/ruby/core/kernel/open_spec.rb index ff56dfa03b..b967d5044b 100644 --- a/spec/ruby/core/kernel/open_spec.rb +++ b/spec/ruby/core/kernel/open_spec.rb @@ -1,8 +1,7 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#open" do - before :each do @name = tmp("kernel_open.txt") @content = "This is a test" @@ -28,47 +27,80 @@ describe "Kernel#open" do open(@name, "r") { |f| f.gets }.should == @content end - platform_is_not :windows do - it "opens an io when path starts with a pipe" do - @io = open("|date") - begin - @io.should be_kind_of(IO) - @io.read - ensure - @io.close + ruby_version_is ""..."4.0" do + platform_is_not :windows, :wasi do + it "opens an io when path starts with a pipe" do + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + @io = open("|date") + end + begin + @io.should be_kind_of(IO) + @io.read + ensure + @io.close + end end - end - it "opens an io when called with a block" do - @output = open("|date") { |f| f.read } - @output.should_not == '' - end + it "opens an io when called with a block" do + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + @output = open("|date") { |f| f.read } + end + @output.should_not == '' + end - it "opens an io for writing" do - bytes = open("|cat", "w") { |io| io.write(".") } - bytes.should == 1 + it "opens an io for writing" do + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + -> { + bytes = open("|cat", "w") { |io| io.write(".") } + bytes.should == 1 + }.should output_to_fd(".") + end + end end - end - platform_is :windows do - it "opens an io when path starts with a pipe" do - @io = open("|date /t") - begin - @io.should be_kind_of(IO) - @io.read - ensure - @io.close + platform_is :windows do + it "opens an io when path starts with a pipe" do + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + @io = open("|date /t") + end + begin + @io.should be_kind_of(IO) + @io.read + ensure + @io.close + end + end + + it "opens an io when called with a block" do + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + @output = open("|date /t") { |f| f.read } + end + @output.should_not == '' end end - it "opens an io when called with a block" do - @output = open("|date /t") { |f| f.read } - @output.should_not == '' + ruby_version_is "3.3" do + # https://bugs.ruby-lang.org/issues/19630 + it "warns about deprecation given a path with a pipe" do + cmd = "|echo ok" + -> { + open(cmd) { |f| f.read } + }.should complain(/Kernel#open with a leading '\|'/) + end end end it "raises an ArgumentError if not passed one argument" do - lambda { open }.should raise_error(ArgumentError) + -> { open }.should raise_error(ArgumentError) + end + + it "accepts options as keyword arguments" do + @file = open(@name, "r", 0666, flags: File::CREAT) + @file.should be_kind_of(File) + + -> { + open(@name, "r", 0666, {flags: File::CREAT}) + }.should raise_error(ArgumentError, "wrong number of arguments (given 4, expected 1..3)") end describe "when given an object that responds to to_open" do @@ -76,7 +108,7 @@ describe "Kernel#open" do ScratchPad.clear end - it "calls #to_path to covert the argument to a String before calling #to_str" do + it "calls #to_path to convert the argument to a String before calling #to_str" do obj = mock("open to_path") obj.should_receive(:to_path).at_least(1).times.and_return(@name) obj.should_not_receive(:to_str) @@ -108,11 +140,21 @@ describe "Kernel#open" do it "passes its arguments onto #to_open" do obj = mock('to_open') - obj.should_receive(:to_open).with(1,2,3) - + obj.should_receive(:to_open).with(1, 2, 3) open(obj, 1, 2, 3) end + it "passes keyword arguments onto #to_open as keyword arguments if to_open accepts them" do + obj = Object.new + def obj.to_open(*args, **kw) + ScratchPad << {args: args, kw: kw} + end + + ScratchPad.record [] + open(obj, 1, 2, 3, a: "b") + ScratchPad.recorded.should == [args: [1, 2, 3], kw: {a: "b"}] + end + it "passes the return value from #to_open to a block" do obj = mock('to_open') obj.should_receive(:to_open).and_return(:value) @@ -127,14 +169,24 @@ describe "Kernel#open" do it "raises a TypeError if passed a non-String that does not respond to #to_open" do obj = mock('non-fileish') - lambda { open(obj) }.should raise_error(TypeError) - lambda { open(nil) }.should raise_error(TypeError) - lambda { open(7) }.should raise_error(TypeError) + -> { open(obj) }.should raise_error(TypeError) + -> { open(nil) }.should raise_error(TypeError) + -> { open(7) }.should raise_error(TypeError) end it "accepts nil for mode and permission" do open(@name, nil, nil) { |f| f.gets }.should == @content end + + it "is not redefined by open-uri" do + code = <<~RUBY + before = Kernel.instance_method(:open) + require 'open-uri' + after = Kernel.instance_method(:open) + p before == after + RUBY + ruby_exe(code, args: "2>&1").should == "true\n" + end end describe "Kernel.open" do diff --git a/spec/ruby/core/kernel/p_spec.rb b/spec/ruby/core/kernel/p_spec.rb index c451f5952a..eae191aa54 100644 --- a/spec/ruby/core/kernel/p_spec.rb +++ b/spec/ruby/core/kernel/p_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#p" do before :all do @@ -7,7 +7,9 @@ describe "Kernel#p" do end after :each do - $/, $\, $, = @rs_f, @rs_b, @rs_c + suppress_warning { + $/, $\, $, = @rs_f, @rs_b, @rs_c + } end it "is a private method" do @@ -40,38 +42,42 @@ describe "Kernel#p" do o = mock("Inspector Gadget") o.should_receive(:inspect).any_number_of_times.and_return "Next time, Gadget, NEXT TIME!" - lambda { p(o) }.should output("Next time, Gadget, NEXT TIME!\n") - lambda { p(*[o]) }.should output("Next time, Gadget, NEXT TIME!\n") - lambda { p(*[o, o]) }.should output("Next time, Gadget, NEXT TIME!\nNext time, Gadget, NEXT TIME!\n") - lambda { p([o])}.should output("[#{o.inspect}]\n") + -> { p(o) }.should output("Next time, Gadget, NEXT TIME!\n") + -> { p(*[o]) }.should output("Next time, Gadget, NEXT TIME!\n") + -> { p(*[o, o]) }.should output("Next time, Gadget, NEXT TIME!\nNext time, Gadget, NEXT TIME!\n") + -> { p([o])}.should output("[#{o.inspect}]\n") end it "is not affected by setting $\\, $/ or $," do o = mock("Inspector Gadget") o.should_receive(:inspect).any_number_of_times.and_return "Next time, Gadget, NEXT TIME!" - $, = " *helicopter sound*\n" - lambda { p(o) }.should output_to_fd("Next time, Gadget, NEXT TIME!\n") + suppress_warning { + $, = " *helicopter sound*\n" + } + -> { p(o) }.should output_to_fd("Next time, Gadget, NEXT TIME!\n") - $\ = " *helicopter sound*\n" - lambda { p(o) }.should output_to_fd("Next time, Gadget, NEXT TIME!\n") + suppress_warning { + $\ = " *helicopter sound*\n" + } + -> { p(o) }.should output_to_fd("Next time, Gadget, NEXT TIME!\n") - $/ = " *helicopter sound*\n" - lambda { p(o) }.should output_to_fd("Next time, Gadget, NEXT TIME!\n") + suppress_warning { + $/ = " *helicopter sound*\n" + } + -> { p(o) }.should output_to_fd("Next time, Gadget, NEXT TIME!\n") end it "prints nothing if no argument is given" do - lambda { p }.should output("") + -> { p }.should output("") end it "prints nothing if called splatting an empty Array" do - lambda { p(*[]) }.should output("") + -> { p(*[]) }.should output("") end -=begin Not sure how to spec this, but wanted to note the behavior here - it "does not flush if receiver is not a TTY or a File" do - end -=end + # Not sure how to spec this, but wanted to note the behavior here + it "does not flush if receiver is not a TTY or a File" end describe "Kernel.p" do diff --git a/spec/ruby/core/kernel/pp_spec.rb b/spec/ruby/core/kernel/pp_spec.rb new file mode 100644 index 0000000000..b5b1c98d38 --- /dev/null +++ b/spec/ruby/core/kernel/pp_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' + +describe "Kernel#pp" do + it "lazily loads the 'pp' library and delegates the call to that library" do + # Run in child process to ensure 'pp' hasn't been loaded yet. + output = ruby_exe("pp [1, 2, 3]") + output.should == "[1, 2, 3]\n" + end +end diff --git a/spec/ruby/core/kernel/print_spec.rb b/spec/ruby/core/kernel/print_spec.rb index 3b642538cb..7e7c9b822d 100644 --- a/spec/ruby/core/kernel/print_spec.rb +++ b/spec/ruby/core/kernel/print_spec.rb @@ -1,10 +1,22 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#print" do it "is a private method" do Kernel.should have_private_instance_method(:print) end + + it "delegates to $stdout" do + -> { print :arg }.should output("arg") + end + + it "prints $_ when no arguments are given" do + orig_value = $_ + $_ = 'foo' + -> { print }.should output("foo") + ensure + $_ = orig_value + end end describe "Kernel.print" do diff --git a/spec/ruby/core/kernel/printf_spec.rb b/spec/ruby/core/kernel/printf_spec.rb index ed134027c0..61bf955c25 100644 --- a/spec/ruby/core/kernel/printf_spec.rb +++ b/spec/ruby/core/kernel/printf_spec.rb @@ -1,7 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/sprintf', __FILE__) -require "stringio" +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/sprintf' describe "Kernel#printf" do it "is a private method" do @@ -10,7 +9,6 @@ describe "Kernel#printf" do end describe "Kernel.printf" do - before :each do @stdout = $stdout @name = tmp("kernel_puts.txt") @@ -34,22 +32,34 @@ describe "Kernel.printf" do Kernel.printf(object, "%s", "string") end + it "calls #to_str to convert the format object to a String" do + object = mock('format string') + object.should_receive(:to_str).and_return("to_str: %i") + $stdout.should_receive(:write).with("to_str: 42") + Kernel.printf($stdout, object, 42) + end +end + +describe "Kernel.printf" do describe "formatting" do + before :each do + require "stringio" + end + context "io is specified" do - it_behaves_like :kernel_sprintf, -> (format, *args) { - io = StringIO.new - printf(io, format, *args) + it_behaves_like :kernel_sprintf, -> format, *args { + io = StringIO.new(+"") + Kernel.printf(io, format, *args) io.string } end context "io is not specified" do - it_behaves_like :kernel_sprintf, -> (format, *args) { + it_behaves_like :kernel_sprintf, -> format, *args { stdout = $stdout - begin - $stdout = io = StringIO.new - printf(format, *args) + $stdout = io = StringIO.new(+"") + Kernel.printf(format, *args) io.string ensure $stdout = stdout @@ -58,4 +68,3 @@ describe "Kernel.printf" do end end end - diff --git a/spec/ruby/core/kernel/private_methods_spec.rb b/spec/ruby/core/kernel/private_methods_spec.rb index d0603c72b8..041634d1e5 100644 --- a/spec/ruby/core/kernel/private_methods_spec.rb +++ b/spec/ruby/core/kernel/private_methods_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../../../fixtures/reflection', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../fixtures/reflection' # TODO: rewrite describe "Kernel#private_methods" do diff --git a/spec/ruby/core/kernel/proc_spec.rb b/spec/ruby/core/kernel/proc_spec.rb index 4e4854f97d..6553b8fd04 100644 --- a/spec/ruby/core/kernel/proc_spec.rb +++ b/spec/ruby/core/kernel/proc_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/lambda', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/lambda' # The functionality of Proc objects is specified in core/proc @@ -15,14 +15,14 @@ describe "Kernel.proc" do end it "returned the passed Proc if given an existing Proc" do - some_lambda = lambda {} + some_lambda = -> {} some_lambda.lambda?.should be_true l = proc(&some_lambda) l.should equal(some_lambda) l.lambda?.should be_true end - it_behaves_like(:kernel_lambda, :proc) + it_behaves_like :kernel_lambda, :proc it "returns from the creation site of the proc, not just the proc itself" do @reached_end_of_method = nil @@ -36,15 +36,13 @@ describe "Kernel.proc" do end describe "Kernel#proc" do - it "uses the implicit block from an enclosing method" do - def some_method - proc - end - - prc = some_method { "hello" } - - prc.call.should == "hello" + def some_method + proc end - it "needs to be reviewed for spec completeness" + it "raises an ArgumentError when passed no block" do + -> { + some_method { "hello" } + }.should raise_error(ArgumentError, 'tried to create Proc object without a block') + end end diff --git a/spec/ruby/core/kernel/protected_methods_spec.rb b/spec/ruby/core/kernel/protected_methods_spec.rb index 2e09cead53..d3334e886b 100644 --- a/spec/ruby/core/kernel/protected_methods_spec.rb +++ b/spec/ruby/core/kernel/protected_methods_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../../../fixtures/reflection', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../fixtures/reflection' # TODO: rewrite diff --git a/spec/ruby/core/kernel/public_method_spec.rb b/spec/ruby/core/kernel/public_method_spec.rb index f1cc168420..c5d54c777e 100644 --- a/spec/ruby/core/kernel/public_method_spec.rb +++ b/spec/ruby/core/kernel/public_method_spec.rb @@ -1,9 +1,9 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/method', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/method' describe "Kernel#public_method" do - it_behaves_like(:kernel_method, :public_method) + it_behaves_like :kernel_method, :public_method before :each do @obj = KernelSpecs::A.new @@ -11,21 +11,21 @@ describe "Kernel#public_method" do it "raises a NameError when called on a private method" do @obj.send(:private_method).should == :private_method - lambda do + -> do @obj.public_method(:private_method) end.should raise_error(NameError) end it "raises a NameError when called on a protected method" do @obj.send(:protected_method).should == :protected_method - lambda { + -> { @obj.public_method(:protected_method) }.should raise_error(NameError) end it "raises a NameError if we only repond_to_missing? method, true" do obj = KernelSpecs::RespondViaMissing.new - lambda do + -> do obj.public_method(:handled_privately) end.should raise_error(NameError) end diff --git a/spec/ruby/core/kernel/public_methods_spec.rb b/spec/ruby/core/kernel/public_methods_spec.rb index b72775483c..a5512784fb 100644 --- a/spec/ruby/core/kernel/public_methods_spec.rb +++ b/spec/ruby/core/kernel/public_methods_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../../../fixtures/reflection', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../fixtures/reflection' # TODO: rewrite describe "Kernel#public_methods" do diff --git a/spec/ruby/core/kernel/public_send_spec.rb b/spec/ruby/core/kernel/public_send_spec.rb index 2eabbc7dc9..b684b1729c 100644 --- a/spec/ruby/core/kernel/public_send_spec.rb +++ b/spec/ruby/core/kernel/public_send_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../../../shared/basicobject/send', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../shared/basicobject/send' describe "Kernel#public_send" do it "invokes the named public method" do @@ -29,7 +29,7 @@ describe "Kernel#public_send" do 'done' end end - lambda { KernelSpecs::Foo.new.public_send(:bar)}.should raise_error(NoMethodError) + -> { KernelSpecs::Foo.new.public_send(:bar)}.should raise_error(NoMethodError) end it "raises a NoMethodError if the named method is private" do @@ -39,7 +39,7 @@ describe "Kernel#public_send" do 'done2' end end - lambda { + -> { KernelSpecs::Foo.new.public_send(:bar) }.should raise_error(NoMethodError) end @@ -70,11 +70,11 @@ describe "Kernel#public_send" do end it "raises a NoMethodError if the method is protected" do - lambda { @receiver.call_protected_method }.should raise_error(NoMethodError) + -> { @receiver.call_protected_method }.should raise_error(NoMethodError) end it "raises a NoMethodError if the method is private" do - lambda { @receiver.call_private_method }.should raise_error(NoMethodError) + -> { @receiver.call_private_method }.should raise_error(NoMethodError) end end @@ -86,7 +86,7 @@ describe "Kernel#public_send" do end alias :aka :bar end - lambda { + -> { KernelSpecs::Foo.new.public_send(:aka) }.should raise_error(NoMethodError) end @@ -99,10 +99,18 @@ describe "Kernel#public_send" do end alias :aka :bar end - lambda { + -> { KernelSpecs::Foo.new.public_send(:aka) }.should raise_error(NoMethodError) end - it_behaves_like(:basicobject_send, :public_send) + it "includes `public_send` in the backtrace when passed not enough arguments" do + -> { public_send() }.should raise_error(ArgumentError) { |e| e.backtrace[0].should =~ /[`'](?:Kernel#)?public_send'/ } + end + + it "includes `public_send` in the backtrace when passed a single incorrect argument" do + -> { public_send(Object.new) }.should raise_error(TypeError) { |e| e.backtrace[0].should =~ /[`'](?:Kernel#)?public_send'/ } + end + + it_behaves_like :basicobject_send, :public_send end diff --git a/spec/ruby/core/kernel/putc_spec.rb b/spec/ruby/core/kernel/putc_spec.rb index 1f5e9b6d63..74bd3765db 100644 --- a/spec/ruby/core/kernel/putc_spec.rb +++ b/spec/ruby/core/kernel/putc_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../../../shared/io/putc', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../shared/io/putc' describe "Kernel#putc" do it "is a private instance method" do diff --git a/spec/ruby/core/kernel/puts_spec.rb b/spec/ruby/core/kernel/puts_spec.rb index c5297f1cb2..6eb38e8fcf 100644 --- a/spec/ruby/core/kernel/puts_spec.rb +++ b/spec/ruby/core/kernel/puts_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#puts" do before :each do diff --git a/spec/ruby/core/kernel/raise_spec.rb b/spec/ruby/core/kernel/raise_spec.rb index 6efffd9366..fcd011d4e6 100644 --- a/spec/ruby/core/kernel/raise_spec.rb +++ b/spec/ruby/core/kernel/raise_spec.rb @@ -1,11 +1,286 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../../../shared/kernel/raise', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../shared/kernel/raise' describe "Kernel#raise" do it "is a private method" do Kernel.should have_private_instance_method(:raise) end + + it "re-raises the previously rescued exception if no exception is specified" do + ScratchPad.record nil + + -> do + begin + raise Exception, "outer" + ScratchPad.record :no_abort + rescue Exception + begin + raise StandardError, "inner" + rescue StandardError + end + + raise + ScratchPad.record :no_reraise + end + end.should raise_error(Exception, "outer") + + ScratchPad.recorded.should be_nil + end + + it "accepts a cause keyword argument that sets the cause" do + cause = StandardError.new + -> { raise("error", cause: cause) }.should raise_error(RuntimeError) { |e| e.cause.should == cause } + end + + it "accepts a cause keyword argument that overrides the last exception" do + begin + raise "first raise" + rescue => ignored + cause = StandardError.new + -> { raise("error", cause: cause) }.should raise_error(RuntimeError) { |e| e.cause.should == cause } + end + end + + it "raises an ArgumentError when only cause is given" do + cause = StandardError.new + -> { raise(cause: cause) }.should raise_error(ArgumentError, "only cause is given with no arguments") + end + + it "raises an ArgumentError when only cause is given even if it has nil value" do + -> { raise(cause: nil) }.should raise_error(ArgumentError, "only cause is given with no arguments") + end + + it "raises a TypeError when given cause is not an instance of Exception" do + -> { raise "message", cause: Object.new }.should raise_error(TypeError, "exception object expected") + end + + it "doesn't raise a TypeError when given cause is nil" do + -> { raise "message", cause: nil }.should raise_error(RuntimeError, "message") + end + + it "allows cause equal an exception" do + e = RuntimeError.new("message") + -> { raise e, cause: e }.should raise_error(e) + end + + it "doesn't set given cause when it equals an exception" do + e = RuntimeError.new("message") + + begin + raise e, cause: e + rescue + end + + e.cause.should == nil + end + + it "raises ArgumentError when exception is part of the cause chain" do + -> { + begin + raise "Error 1" + rescue => e1 + begin + raise "Error 2" + rescue => e2 + begin + raise "Error 3" + rescue => e3 + raise e1, cause: e3 + end + end + end + }.should raise_error(ArgumentError, "circular causes") + end + + it "re-raises a rescued exception" do + -> do + begin + raise StandardError, "aaa" + rescue Exception + begin + raise ArgumentError + rescue ArgumentError + end + + # should raise StandardError "aaa" + raise + end + end.should raise_error(StandardError, "aaa") + end + + it "re-raises a previously rescued exception without overwriting the cause" do + begin + begin + begin + begin + raise "Error 1" + rescue => e1 + raise "Error 2" + end + rescue => e2 + raise "Error 3" + end + rescue + e2.cause.should == e1 + raise e2 + end + rescue => e + e.cause.should == e1 + end + end + + it "re-raises a previously rescued exception with overwriting the cause when it's explicitly specified with :cause option" do + e4 = RuntimeError.new("Error 4") + + begin + begin + begin + begin + raise "Error 1" + rescue => e1 + raise "Error 2" + end + rescue => e2 + raise "Error 3" + end + rescue + e2.cause.should == e1 + raise e2, cause: e4 + end + rescue => e + e.cause.should == e4 + end + end + + it "re-raises a previously rescued exception without overwriting the cause when it's explicitly specified with :cause option and has nil value" do + begin + begin + begin + begin + raise "Error 1" + rescue => e1 + raise "Error 2" + end + rescue => e2 + raise "Error 3" + end + rescue + e2.cause.should == e1 + raise e2, cause: nil + end + rescue => e + e.cause.should == e1 + end + end + + it "re-raises a previously rescued exception without setting a cause implicitly" do + begin + begin + raise "Error 1" + rescue => e1 + raise + end + rescue => e + e.should == e1 + e.cause.should == nil + end + end + + it "re-raises a previously rescued exception that has a cause without setting a cause implicitly" do + begin + begin + raise "Error 1" + rescue => e1 + begin + raise "Error 2" + rescue => e2 + raise + end + end + rescue => e + e.should == e2 + e.cause.should == e1 + end + end + + it "re-raises a previously rescued exception that doesn't have a cause and isn't a cause of any other exception with setting a cause implicitly" do + begin + begin + raise "Error 1" + rescue => e1 + begin + raise "Error 2" + rescue => e2 + raise "Error 3" + end + end + rescue => e + e.message.should == "Error 3" + e.cause.should == e2 + end + end + + it "re-raises a previously rescued exception that doesn't have a cause and is a cause of other exception without setting a cause implicitly" do + begin + begin + raise "Error 1" + rescue => e1 + begin + raise "Error 2" + rescue => e2 + e1.cause.should == nil + e2.cause.should == e1 + raise e1 + end + end + rescue => e + e.should == e1 + e.cause.should == nil + end + end + + it "re-raises a previously rescued exception that doesn't have a cause and is a cause of other exception (that wasn't raised explicitly) without setting a cause implicitly" do + begin + begin + raise "Error 1" + rescue => e1 + begin + foo # raises NameError + rescue => e2 + e1.cause.should == nil + e2.cause.should == e1 + raise e1 + end + end + rescue => e + e.should == e1 + e.cause.should == nil + end + end + + it "re-raises a previously rescued exception that has a cause but isn't a cause of any other exception without setting a cause implicitly" do + begin + begin + raise "Error 1" + rescue => e1 + begin + raise "Error 2" + rescue => e2 + begin + raise "Error 3", cause: RuntimeError.new("Error 4") + rescue => e3 + e2.cause.should == e1 + e3.cause.should_not == e2 + raise e2 + end + end + end + rescue => e + e.should == e2 + e.cause.should == e1 + end + end end describe "Kernel#raise" do diff --git a/spec/ruby/core/kernel/rand_spec.rb b/spec/ruby/core/kernel/rand_spec.rb index f52b5f75b5..355e425792 100644 --- a/spec/ruby/core/kernel/rand_spec.rb +++ b/spec/ruby/core/kernel/rand_spec.rb @@ -1,7 +1,7 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' -describe "Kernel.rand" do +describe "Kernel#rand" do it "is a private method" do Kernel.should have_private_instance_method(:rand) end @@ -117,6 +117,48 @@ describe "Kernel.rand" do end end + context "given an inclusive range between 0 and 1" do + it "returns an Integer between the two Integers" do + x = rand(0..1) + x.should be_kind_of(Integer) + (0..1).should include(x) + end + + it "returns a Float if at least one side is Float" do + seed = 42 + x1 = Random.new(seed).rand(0..1.0) + x2 = Random.new(seed).rand(0.0..1.0) + x3 = Random.new(seed).rand(0.0..1) + + x3.should be_kind_of(Float) + x1.should eql(x3) + x2.should eql(x3) + + (0.0..1.0).should include(x3) + end + end + + context "given an exclusive range between 0 and 1" do + it "returns zero as an Integer" do + x = rand(0...1) + x.should be_kind_of(Integer) + x.should eql(0) + end + + it "returns a Float if at least one side is Float" do + seed = 42 + x1 = Random.new(seed).rand(0...1.0) + x2 = Random.new(seed).rand(0.0...1.0) + x3 = Random.new(seed).rand(0.0...1) + + x3.should be_kind_of(Float) + x1.should eql(x3) + x2.should eql(x3) + + (0.0...1.0).should include(x3) + end + end + it "returns a numeric for an range argument where max is < 1" do rand(0.25..0.75).should be_kind_of(Numeric) end @@ -132,8 +174,24 @@ describe "Kernel.rand" do it "returns the range start/end when Integer range is 0" do rand(42..42).should eql(42) end + + it "supports custom object types" do + rand(KernelSpecs::CustomRangeInteger.new(1)..KernelSpecs::CustomRangeInteger.new(42)).should be_an_instance_of(KernelSpecs::CustomRangeInteger) + rand(KernelSpecs::CustomRangeFloat.new(1.0)..KernelSpecs::CustomRangeFloat.new(42.0)).should be_an_instance_of(KernelSpecs::CustomRangeFloat) + rand(Time.now..Time.now).should be_an_instance_of(Time) + end + + it "is random on boot" do + results = 2.times.map { + out = ruby_exe('p rand', options: '--disable-gems') + Float(out) + } + results.size.should == 2 + # this is technically flaky, but very unlikely in a good distribution + results[0].should_not == results[1] + end end -describe "Kernel#rand" do +describe "Kernel.rand" do it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/core/kernel/readline_spec.rb b/spec/ruby/core/kernel/readline_spec.rb index c69eee0726..dce7b03dc8 100644 --- a/spec/ruby/core/kernel/readline_spec.rb +++ b/spec/ruby/core/kernel/readline_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#readline" do it "is a private method" do diff --git a/spec/ruby/core/kernel/readlines_spec.rb b/spec/ruby/core/kernel/readlines_spec.rb index d5e07f8f75..2b6d65fff2 100644 --- a/spec/ruby/core/kernel/readlines_spec.rb +++ b/spec/ruby/core/kernel/readlines_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#readlines" do it "is a private method" do diff --git a/spec/ruby/core/kernel/remove_instance_variable_spec.rb b/spec/ruby/core/kernel/remove_instance_variable_spec.rb index 6a9f78b9bc..4e5ba5e018 100644 --- a/spec/ruby/core/kernel/remove_instance_variable_spec.rb +++ b/spec/ruby/core/kernel/remove_instance_variable_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe :kernel_remove_instance_variable, shared: true do it "returns the instance variable's value" do @@ -23,24 +23,37 @@ describe "Kernel#remove_instance_variable" do end it "raises a NameError if the instance variable is not defined" do - lambda do + -> do @instance.send :remove_instance_variable, :@unknown end.should raise_error(NameError) end it "raises a NameError if the argument is not a valid instance variable name" do - lambda do + -> do @instance.send :remove_instance_variable, :"@0" end.should raise_error(NameError) end it "raises a TypeError if passed an Object not defining #to_str" do - lambda do + -> do obj = mock("kernel remove_instance_variable") @instance.send :remove_instance_variable, obj end.should raise_error(TypeError) end + it "raises a FrozenError if self is frozen" do + o = Object.new + o.freeze + -> { o.remove_instance_variable(:@foo) }.should raise_error(FrozenError) + -> { o.remove_instance_variable(:foo) }.should raise_error(NameError) + end + + it "raises for frozen objects" do + -> { nil.remove_instance_variable(:@foo) }.should raise_error(FrozenError) + -> { nil.remove_instance_variable(:foo) }.should raise_error(NameError) + -> { :foo.remove_instance_variable(:@foo) }.should raise_error(FrozenError) + end + describe "when passed a String" do it_behaves_like :kernel_remove_instance_variable, nil, "@greeting" end diff --git a/spec/ruby/core/kernel/require_relative_spec.rb b/spec/ruby/core/kernel/require_relative_spec.rb index 04cf5444d2..6188d13a4e 100644 --- a/spec/ruby/core/kernel/require_relative_spec.rb +++ b/spec/ruby/core/kernel/require_relative_spec.rb @@ -1,15 +1,13 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../../../fixtures/code_loading', __FILE__) +require_relative '../../spec_helper' +require_relative '../../fixtures/code_loading' describe "Kernel#require_relative with a relative path" do - it "needs to be reviewed for spec completeness" - before :each do CodeLoadingSpecs.spec_setup @dir = "../../fixtures/code" - @abs_dir = File.realpath(@dir, File.dirname(__FILE__)) + @abs_dir = File.realpath(@dir, __dir__) @path = "#{@dir}/load_fixture.rb" - @abs_path = File.realpath(@path, File.dirname(__FILE__)) + @abs_path = File.realpath(@path, __dir__) end after :each do @@ -40,24 +38,76 @@ describe "Kernel#require_relative with a relative path" do ScratchPad.recorded.should == [:loaded] end + describe "in an #instance_eval with a" do + + it "synthetic file base name loads a file base name relative to the working directory" do + Dir.chdir @abs_dir do + Object.new.instance_eval("require_relative(#{File.basename(@path).inspect})", "foo.rb").should be_true + end + ScratchPad.recorded.should == [:loaded] + end + + it "synthetic file path loads a relative path relative to the working directory plus the directory of the synthetic path" do + Dir.chdir @abs_dir do + Object.new.instance_eval("require_relative(File.join('..', #{File.basename(@path).inspect}))", "bar/foo.rb").should be_true + end + ScratchPad.recorded.should == [:loaded] + end + + platform_is_not :windows do + it "synthetic relative file path with a Windows path separator specified loads a relative path relative to the working directory" do + Dir.chdir @abs_dir do + Object.new.instance_eval("require_relative(#{File.basename(@path).inspect})", "bar\\foo.rb").should be_true + end + ScratchPad.recorded.should == [:loaded] + end + end + + it "absolute file path loads a path relative to the absolute path" do + Object.new.instance_eval("require_relative(#{@path.inspect})", __FILE__).should be_true + ScratchPad.recorded.should == [:loaded] + end + + it "absolute file path loads a path relative to the root directory" do + root = @abs_path + until File.dirname(root) == root + root = File.dirname(root) + end + root_relative = @abs_path[root.size..-1] + Object.new.instance_eval("require_relative(#{root_relative.inspect})", "/").should be_true + ScratchPad.recorded.should == [:loaded] + end + + end + it "loads a file defining many methods" do require_relative("#{@dir}/methods_fixture.rb").should be_true ScratchPad.recorded.should == [:loaded] end it "raises a LoadError if the file does not exist" do - lambda { require_relative("#{@dir}/nonexistent.rb") }.should raise_error(LoadError) + -> { require_relative("#{@dir}/nonexistent.rb") }.should raise_error(LoadError) + ScratchPad.recorded.should == [] + end + + it "raises a LoadError that includes the missing path" do + missing_path = "#{@dir}/nonexistent.rb" + expanded_missing_path = File.expand_path(missing_path, __dir__) + -> { require_relative(missing_path) }.should raise_error(LoadError) { |e| + e.message.should include(expanded_missing_path) + e.path.should == expanded_missing_path + } ScratchPad.recorded.should == [] end it "raises a LoadError if basepath does not exist" do - lambda { eval("require_relative('#{@dir}/nonexistent.rb')") }.should raise_error(LoadError) + -> { eval("require_relative('#{@dir}/nonexistent.rb')") }.should raise_error(LoadError) end it "stores the missing path in a LoadError object" do path = "#{@dir}/nonexistent.rb" - lambda { + -> { require_relative(path) }.should(raise_error(LoadError) { |e| e.path.should == File.expand_path(path, @abs_dir) @@ -72,9 +122,9 @@ describe "Kernel#require_relative with a relative path" do end it "raises a TypeError if argument does not respond to #to_str" do - lambda { require_relative(nil) }.should raise_error(TypeError) - lambda { require_relative(42) }.should raise_error(TypeError) - lambda { + -> { require_relative(nil) }.should raise_error(TypeError) + -> { require_relative(42) }.should raise_error(TypeError) + -> { require_relative([@path,@path]) }.should raise_error(TypeError) end @@ -82,13 +132,13 @@ describe "Kernel#require_relative with a relative path" do it "raises a TypeError if passed an object that has #to_s but not #to_str" do name = mock("load_fixture.rb mock") name.stub!(:to_s).and_return(@path) - lambda { require_relative(name) }.should raise_error(TypeError) + -> { require_relative(name) }.should raise_error(TypeError) 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 { require_relative(name) }.should raise_error(TypeError) + -> { require_relative(name) }.should raise_error(TypeError) end it "calls #to_path on non-String objects" do @@ -157,9 +207,49 @@ describe "Kernel#require_relative with a relative path" do $LOADED_FEATURES.should include(@abs_path) end + platform_is_not :windows, :wasi do + describe "with symlinks" do + before :each do + @symlink_to_code_dir = tmp("codesymlink") + File.symlink(CODE_LOADING_DIR, @symlink_to_code_dir) + @symlink_basename = File.basename(@symlink_to_code_dir) + @requiring_file = tmp("requiring") + end + + after :each do + rm_r @symlink_to_code_dir, @requiring_file + end + + it "does not canonicalize the path and stores a path with symlinks" do + symlink_path = "#{@symlink_basename}/load_fixture.rb" + absolute_path = "#{tmp("")}#{symlink_path}" + canonical_path = "#{CODE_LOADING_DIR}/load_fixture.rb" + touch(@requiring_file) { |f| + f.puts "require_relative #{symlink_path.inspect}" + } + load(@requiring_file) + ScratchPad.recorded.should == [:loaded] + + features = $LOADED_FEATURES.select { |path| path.end_with?('load_fixture.rb') } + features.should include(absolute_path) + features.should_not include(canonical_path) + end + + it "stores the same path that __FILE__ returns in the required file" do + symlink_path = "#{@symlink_basename}/load_fixture_and__FILE__.rb" + touch(@requiring_file) { |f| + f.puts "require_relative #{symlink_path.inspect}" + } + load(@requiring_file) + loaded_feature = $LOADED_FEATURES.last + ScratchPad.recorded.should == [loaded_feature] + end + end + end + it "does not store the path if the load fails" do saved_loaded_features = $LOADED_FEATURES.dup - lambda { require_relative("#{@dir}/raise_fixture.rb") }.should raise_error(RuntimeError) + -> { require_relative("#{@dir}/raise_fixture.rb") }.should raise_error(RuntimeError) $LOADED_FEATURES.should == saved_loaded_features end @@ -185,11 +275,9 @@ describe "Kernel#require_relative with a relative path" do end describe "Kernel#require_relative with an absolute path" do - it "needs to be reviewed for spec completeness" - before :each do CodeLoadingSpecs.spec_setup - @dir = File.expand_path "../../fixtures/code", File.dirname(__FILE__) + @dir = File.expand_path "../../fixtures/code", __dir__ @abs_dir = @dir @path = File.join @dir, "load_fixture.rb" @abs_path = @path @@ -210,18 +298,18 @@ describe "Kernel#require_relative with an absolute path" do end it "raises a LoadError if the file does not exist" do - lambda { require_relative("#{@dir}/nonexistent.rb") }.should raise_error(LoadError) + -> { require_relative("#{@dir}/nonexistent.rb") }.should raise_error(LoadError) ScratchPad.recorded.should == [] end it "raises a LoadError if basepath does not exist" do - lambda { eval("require_relative('#{@dir}/nonexistent.rb')") }.should raise_error(LoadError) + -> { eval("require_relative('#{@dir}/nonexistent.rb')") }.should raise_error(LoadError) end it "stores the missing path in a LoadError object" do path = "#{@dir}/nonexistent.rb" - lambda { + -> { require_relative(path) }.should(raise_error(LoadError) { |e| e.path.should == File.expand_path(path, @abs_dir) @@ -236,9 +324,9 @@ describe "Kernel#require_relative with an absolute path" do end it "raises a TypeError if argument does not respond to #to_str" do - lambda { require_relative(nil) }.should raise_error(TypeError) - lambda { require_relative(42) }.should raise_error(TypeError) - lambda { + -> { require_relative(nil) }.should raise_error(TypeError) + -> { require_relative(42) }.should raise_error(TypeError) + -> { require_relative([@path,@path]) }.should raise_error(TypeError) end @@ -246,13 +334,13 @@ describe "Kernel#require_relative with an absolute path" do it "raises a TypeError if passed an object that has #to_s but not #to_str" do name = mock("load_fixture.rb mock") name.stub!(:to_s).and_return(@path) - lambda { require_relative(name) }.should raise_error(TypeError) + -> { require_relative(name) }.should raise_error(TypeError) 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 { require_relative(name) }.should raise_error(TypeError) + -> { require_relative(name) }.should raise_error(TypeError) end it "calls #to_path on non-String objects" do @@ -323,7 +411,7 @@ describe "Kernel#require_relative with an absolute path" do it "does not store the path if the load fails" do saved_loaded_features = $LOADED_FEATURES.dup - lambda { require_relative("#{@dir}/raise_fixture.rb") }.should raise_error(RuntimeError) + -> { require_relative("#{@dir}/raise_fixture.rb") }.should raise_error(RuntimeError) $LOADED_FEATURES.should == saved_loaded_features end diff --git a/spec/ruby/core/kernel/require_spec.rb b/spec/ruby/core/kernel/require_spec.rb index 75cea7565e..60d17242fe 100644 --- a/spec/ruby/core/kernel/require_spec.rb +++ b/spec/ruby/core/kernel/require_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../../../fixtures/code_loading', __FILE__) -require File.expand_path('../shared/require', __FILE__) +require_relative '../../spec_helper' +require_relative '../../fixtures/code_loading' +require_relative 'shared/require' describe "Kernel#require" do before :each do @@ -16,8 +16,33 @@ describe "Kernel#require" do Kernel.should have_private_instance_method(:require) end - it_behaves_like :kernel_require_basic, :require, CodeLoadingSpecs::Method.new + provided = %w[complex enumerator fiber rational thread ruby2_keywords] + ruby_version_is "4.0" do + provided << "set" + provided << "pathname" + end + + it "#{provided.join(', ')} are already required" do + out = ruby_exe("puts $LOADED_FEATURES", options: '--disable-gems --disable-did-you-mean') + features = out.lines.map { |line| File.basename(line.chomp, '.*') } + + # Ignore CRuby internals + features -= %w[encdb transdb windows_1252 windows_31j] + features.reject! { |feature| feature.end_with?('-fake') } + + features.sort.should == provided.sort + requires = provided + ruby_version_is "4.0" do + requires = requires.map { |f| f == "pathname" ? "pathname.so" : f } + end + + code = requires.map { |f| "puts require #{f.inspect}\n" }.join + required = ruby_exe(code, options: '--disable-gems') + required.should == "false\n" * requires.size + end + + it_behaves_like :kernel_require_basic, :require, CodeLoadingSpecs::Method.new it_behaves_like :kernel_require, :require, CodeLoadingSpecs::Method.new end @@ -31,6 +56,5 @@ describe "Kernel.require" do end it_behaves_like :kernel_require_basic, :require, Kernel - it_behaves_like :kernel_require, :require, Kernel end diff --git a/spec/ruby/core/kernel/respond_to_missing_spec.rb b/spec/ruby/core/kernel/respond_to_missing_spec.rb index f116f19dbd..cc82031e26 100644 --- a/spec/ruby/core/kernel/respond_to_missing_spec.rb +++ b/spec/ruby/core/kernel/respond_to_missing_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#respond_to_missing?" do before :each do diff --git a/spec/ruby/core/kernel/respond_to_spec.rb b/spec/ruby/core/kernel/respond_to_spec.rb index aa4379277b..5b3ea3f651 100644 --- a/spec/ruby/core/kernel/respond_to_spec.rb +++ b/spec/ruby/core/kernel/respond_to_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#respond_to?" do before :each do @@ -25,7 +25,7 @@ describe "Kernel#respond_to?" do end it "throws a type error if argument can't be coerced into a Symbol" do - lambda { @a.respond_to?(Object.new) }.should raise_error(TypeError) + -> { @a.respond_to?(Object.new) }.should raise_error(TypeError, /is not a symbol nor a string/) end it "returns false if obj responds to the given protected method" do @@ -61,7 +61,7 @@ describe "Kernel#respond_to?" do it "does not change method visibility when finding private method" do KernelSpecs::VisibilityChange.respond_to?(:new, false).should == false KernelSpecs::VisibilityChange.respond_to?(:new, true).should == true - lambda { KernelSpecs::VisibilityChange.new }.should raise_error(NoMethodError) + -> { KernelSpecs::VisibilityChange.new }.should raise_error(NoMethodError) end it "indicates if an object responds to a particular message" do @@ -69,5 +69,4 @@ describe "Kernel#respond_to?" do KernelSpecs::Foo.new.respond_to?(:bar).should == true KernelSpecs::Foo.new.respond_to?(:invalid_and_silly_method_name).should == false end - end diff --git a/spec/ruby/core/kernel/select_spec.rb b/spec/ruby/core/kernel/select_spec.rb index c37c621ae7..df23414b28 100644 --- a/spec/ruby/core/kernel/select_spec.rb +++ b/spec/ruby/core/kernel/select_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#select" do it "is a private method" do @@ -8,13 +8,11 @@ describe "Kernel#select" do end describe "Kernel.select" do - it "needs to be reviewed for spec completeness" - it 'does not block when timeout is 0' do IO.pipe do |read, write| - IO.select([read], [], [], 0).should == nil + select([read], [], [], 0).should == nil write.write 'data' - IO.select([read], [], [], 0).should == [[read], [], []] + select([read], [], [], 0).should == [[read], [], []] end end end diff --git a/spec/ruby/core/kernel/send_spec.rb b/spec/ruby/core/kernel/send_spec.rb index 8afd16e97c..9a4d261964 100644 --- a/spec/ruby/core/kernel/send_spec.rb +++ b/spec/ruby/core/kernel/send_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../../../shared/basicobject/send', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../shared/basicobject/send' describe "Kernel#send" do it "invokes the named public method" do @@ -64,5 +64,5 @@ describe "Kernel#send" do KernelSpecs::Foo.new.send(:aka).should == 'done2' end - it_behaves_like(:basicobject_send, :send) + it_behaves_like :basicobject_send, :send end diff --git a/spec/ruby/core/kernel/set_trace_func_spec.rb b/spec/ruby/core/kernel/set_trace_func_spec.rb index 5dafa8b5d3..1f43e7a009 100644 --- a/spec/ruby/core/kernel/set_trace_func_spec.rb +++ b/spec/ruby/core/kernel/set_trace_func_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#set_trace_func" do it "is a private method" do 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 diff --git a/spec/ruby/core/kernel/singleton_class_spec.rb b/spec/ruby/core/kernel/singleton_class_spec.rb index b5e0703905..23c400f9bd 100644 --- a/spec/ruby/core/kernel/singleton_class_spec.rb +++ b/spec/ruby/core/kernel/singleton_class_spec.rb @@ -1,3 +1,6 @@ +# truffleruby_primitives: true +require_relative '../../spec_helper' + describe "Kernel#singleton_class" do it "returns class extended from an object" do x = Object.new @@ -17,11 +20,55 @@ describe "Kernel#singleton_class" do false.singleton_class.should == FalseClass end - it "raises TypeError for Fixnum" do - lambda { 123.singleton_class }.should raise_error(TypeError) + it "raises TypeError for Integer" do + -> { 123.singleton_class }.should raise_error(TypeError, "can't define singleton") + end + + it "raises TypeError for Float" do + -> { 3.14.singleton_class }.should raise_error(TypeError, "can't define singleton") end it "raises TypeError for Symbol" do - lambda { :foo.singleton_class }.should raise_error(TypeError) + -> { :foo.singleton_class }.should raise_error(TypeError, "can't define singleton") + end + + it "raises TypeError for a frozen deduplicated String" do + -> { (-"string").singleton_class }.should raise_error(TypeError, "can't define singleton") + -> { a = -"string"; a.singleton_class }.should raise_error(TypeError, "can't define singleton") + -> { a = "string"; (-a).singleton_class }.should raise_error(TypeError, "can't define singleton") + end + + it "returns a frozen singleton class if object is frozen" do + obj = Object.new + obj.freeze + obj.singleton_class.frozen?.should be_true + end + + context "for an IO object with a replaced singleton class" do + it "looks up singleton methods from the fresh singleton class after an object instance got a new one" do + proxy = -> io { io.foo } + if RUBY_ENGINE == 'truffleruby' + # We need an inline cache with only this object seen, the best way to do that is to use a Primitive + sclass = -> io { Primitive.singleton_class(io) } + else + sclass = -> io { io.singleton_class } + end + + io = File.new(__FILE__) + io.define_singleton_method(:foo) { "old" } + sclass1 = sclass.call(io) + proxy.call(io).should == "old" + + # IO#reopen is the only method which can replace an object's singleton class + io2 = File.new(__FILE__) + io.reopen(io2) + io.define_singleton_method(:foo) { "new" } + sclass2 = sclass.call(io) + sclass2.should_not.equal?(sclass1) + proxy.call(io).should == "new" + ensure + io2.close + io.close + end end end diff --git a/spec/ruby/core/kernel/singleton_method_spec.rb b/spec/ruby/core/kernel/singleton_method_spec.rb index 77022b40c2..7d63fa7cc6 100644 --- a/spec/ruby/core/kernel/singleton_method_spec.rb +++ b/spec/ruby/core/kernel/singleton_method_spec.rb @@ -1,7 +1,7 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Kernel#singleton_method" do - it "find a method defined on the singleton class" do + it "finds a method defined on the singleton class" do obj = Object.new def obj.foo; end obj.singleton_method(:foo).should be_an_instance_of(Method) @@ -38,4 +38,48 @@ describe "Kernel#singleton_method" do e.class.should == NameError } end + + ruby_bug "#20620", ""..."3.4" do + it "finds a method defined in a module included in the singleton class" do + m = Module.new do + def foo + :foo + end + end + + obj = Object.new + obj.singleton_class.include(m) + + obj.singleton_method(:foo).should be_an_instance_of(Method) + obj.singleton_method(:foo).call.should == :foo + end + + it "finds a method defined in a module prepended in the singleton class" do + m = Module.new do + def foo + :foo + end + end + + obj = Object.new + obj.singleton_class.prepend(m) + + obj.singleton_method(:foo).should be_an_instance_of(Method) + obj.singleton_method(:foo).call.should == :foo + end + + it "finds a method defined in a module that an object is extended with" do + m = Module.new do + def foo + :foo + end + end + + obj = Object.new + obj.extend(m) + + obj.singleton_method(:foo).should be_an_instance_of(Method) + obj.singleton_method(:foo).call.should == :foo + end + end end diff --git a/spec/ruby/core/kernel/singleton_methods_spec.rb b/spec/ruby/core/kernel/singleton_methods_spec.rb index 596e5ddad2..a127a439de 100644 --- a/spec/ruby/core/kernel/singleton_methods_spec.rb +++ b/spec/ruby/core/kernel/singleton_methods_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../../../fixtures/reflection', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative '../../fixtures/reflection' +require_relative 'fixtures/classes' describe :kernel_singleton_methods, shared: true do it "returns an empty Array for an object with no singleton methods" do @@ -36,10 +36,23 @@ describe :kernel_singleton_methods_modules, shared: true do it "does not return any included methods for a class including a module" do ReflectSpecs::D.singleton_methods(*@object).should include(:ds_pro, :ds_pub) end + + it "for a module does not return methods in a module prepended to Module itself" do + require_relative 'fixtures/singleton_methods' + mod = SingletonMethodsSpecs::SelfExtending + mod.method(:mspec_test_kernel_singleton_methods).owner.should == SingletonMethodsSpecs::Prepended + + ancestors = mod.singleton_class.ancestors + ancestors[0...2].should == [ mod.singleton_class, mod ] + ancestors.should include(SingletonMethodsSpecs::Prepended) + + # Do not search prepended modules of `Module`, as that's a non-singleton class + mod.singleton_methods.should == [] + end end describe :kernel_singleton_methods_supers, shared: true do - it "returns the names of singleton methods for an object extented with a module" do + it "returns the names of singleton methods for an object extended with a module" do ReflectSpecs.oe.singleton_methods(*@object).should include(:m_pro, :m_pub) end @@ -49,11 +62,11 @@ describe :kernel_singleton_methods_supers, shared: true do r.should == [:pro, :pub] end - it "returns the names of singleton methods for an object extented with two modules" do + it "returns the names of singleton methods for an object extended with two modules" do ReflectSpecs.oee.singleton_methods(*@object).should include(:m_pro, :m_pub, :n_pro, :n_pub) end - it "returns the names of singleton methods for an object extented with a module including a module" do + it "returns the names of singleton methods for an object extended with a module including a module" do ReflectSpecs.oei.singleton_methods(*@object).should include(:n_pro, :n_pub, :m_pro, :m_pub) end @@ -99,7 +112,7 @@ describe :kernel_singleton_methods_private_supers, shared: true do ReflectSpecs.oee.singleton_methods(*@object).should_not include(:m_pri) end - it "does not return private singleton methods for an object extented with a module including a module" do + it "does not return private singleton methods for an object extended with a module including a module" do ReflectSpecs.oei.singleton_methods(*@object).should_not include(:n_pri, :m_pri) end @@ -145,7 +158,6 @@ describe "Kernel#singleton_methods" do it_behaves_like :kernel_singleton_methods_supers, nil, true it_behaves_like :kernel_singleton_methods_modules, nil, true it_behaves_like :kernel_singleton_methods_private_supers, nil, true - end describe "when passed false" do @@ -153,11 +165,11 @@ describe "Kernel#singleton_methods" do it_behaves_like :kernel_singleton_methods_modules, nil, false it_behaves_like :kernel_singleton_methods_private_supers, nil, false - it "returns an empty Array for an object extented with a module" do + it "returns an empty Array for an object extended with a module" do ReflectSpecs.oe.singleton_methods(false).should == [] end - it "returns an empty Array for an object extented with two modules" do + it "returns an empty Array for an object extended with two modules" do ReflectSpecs.oee.singleton_methods(false).should == [] end diff --git a/spec/ruby/core/kernel/sleep_spec.rb b/spec/ruby/core/kernel/sleep_spec.rb index bcb0060aa3..e9c600aac4 100644 --- a/spec/ruby/core/kernel/sleep_spec.rb +++ b/spec/ruby/core/kernel/sleep_spec.rb @@ -1,34 +1,40 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative '../fiber/fixtures/scheduler' describe "Kernel#sleep" do it "is a private method" do Kernel.should have_private_instance_method(:sleep) end + it "returns an Integer" do + sleep(0.001).should be_kind_of(Integer) + end + it "accepts a Float" do - sleep(0.1).should be_close(0, 2) + sleep(0.001).should >= 0 end - it "accepts a Fixnum" do - sleep(0).should be_close(0, 2) + it "accepts an Integer" do + sleep(0).should >= 0 end it "accepts a Rational" do - sleep(Rational(1, 9)).should be_close(0, 2) + sleep(Rational(1, 999)).should >= 0 end - it "raises an ArgumentError when passed a negative duration" do - lambda { sleep(-0.1) }.should raise_error(ArgumentError) - lambda { sleep(-1) }.should raise_error(ArgumentError) + it "accepts any Object that responds to divmod" do + o = Object.new + def o.divmod(*); [0, 0.001]; end + sleep(o).should >= 0 end - it "raises a TypeError when passed nil" do - lambda { sleep(nil) }.should raise_error(TypeError) + it "raises an ArgumentError when passed a negative duration" do + -> { sleep(-0.1) }.should raise_error(ArgumentError) + -> { sleep(-1) }.should raise_error(ArgumentError) end it "raises a TypeError when passed a String" do - lambda { sleep('2') }.should raise_error(TypeError) + -> { sleep('2') }.should raise_error(TypeError) end it "pauses execution indefinitely if not given a duration" do @@ -45,6 +51,74 @@ describe "Kernel#sleep" do t.wakeup t.value.should == 5 end + + it "sleeps with nanosecond precision" do + start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 100.times do + sleep(0.0001) + end + end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + + actual_duration = end_time - start_time + actual_duration.should > 0.01 # 100 * 0.0001 => 0.01 + end + + ruby_version_is ""..."3.3" do + it "raises a TypeError when passed nil" do + -> { sleep(nil) }.should raise_error(TypeError) + end + end + + ruby_version_is "3.3" do + it "accepts a nil duration" do + running = false + t = Thread.new do + running = true + sleep(nil) + 5 + end + + Thread.pass until running + Thread.pass while t.status and t.status != "sleep" + + t.wakeup + t.value.should == 5 + end + end + + context "Kernel.sleep with Fiber scheduler" do + before :each do + Fiber.set_scheduler(FiberSpecs::LoggingScheduler.new) + end + + after :each do + Fiber.set_scheduler(nil) + end + + it "calls the scheduler without arguments when no duration is given" do + sleeper = Fiber.new(blocking: false) do + sleep + end + sleeper.resume + Fiber.scheduler.events.should == [{ event: :kernel_sleep, fiber: sleeper, args: [] }] + end + + it "calls the scheduler with the given duration" do + sleeper = Fiber.new(blocking: false) do + sleep(0.01) + end + sleeper.resume + Fiber.scheduler.events.should == [{ event: :kernel_sleep, fiber: sleeper, args: [0.01] }] + end + + it "does not call the scheduler if the fiber is blocking" do + sleeper = Fiber.new(blocking: true) do + sleep(0.01) + end + sleeper.resume + Fiber.scheduler.events.should == [] + end + end end describe "Kernel.sleep" do diff --git a/spec/ruby/core/kernel/spawn_spec.rb b/spec/ruby/core/kernel/spawn_spec.rb index ad937b17dd..ba05b629d5 100644 --- a/spec/ruby/core/kernel/spawn_spec.rb +++ b/spec/ruby/core/kernel/spawn_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' # These specs only run a basic usage of #spawn. # Process.spawn has more complete specs and they are not @@ -10,7 +10,7 @@ describe "Kernel#spawn" do end it "executes the given command" do - lambda { + -> { Process.wait spawn("echo spawn") }.should output_to_fd("spawn\n") end @@ -18,7 +18,7 @@ end describe "Kernel.spawn" do it "executes the given command" do - lambda { + -> { Process.wait Kernel.spawn("echo spawn") }.should output_to_fd("spawn\n") end diff --git a/spec/ruby/core/kernel/sprintf_spec.rb b/spec/ruby/core/kernel/sprintf_spec.rb index a89b253803..5a4a90ff7a 100644 --- a/spec/ruby/core/kernel/sprintf_spec.rb +++ b/spec/ruby/core/kernel/sprintf_spec.rb @@ -1,24 +1,64 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/sprintf', __FILE__) -require File.expand_path('../shared/sprintf_encoding', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/sprintf' +require_relative 'shared/sprintf_encoding' + +describe :kernel_sprintf_to_str, shared: true do + it "calls #to_str to convert the format object to a String" do + obj = mock('format string') + obj.should_receive(:to_str).and_return("to_str: %i") + @method.call(obj, 42).should == "to_str: 42" + end +end describe "Kernel#sprintf" do - it_behaves_like :kernel_sprintf, -> (format, *args) { - sprintf(format, *args) + it_behaves_like :kernel_sprintf, -> format, *args { + r = nil + -> { + r = sprintf(format, *args) + }.should_not complain(verbose: true) + r + } + + it_behaves_like :kernel_sprintf_encoding, -> format, *args { + r = nil + -> { + r = sprintf(format, *args) + }.should_not complain(verbose: true) + r } - it_behaves_like :kernel_sprintf_encoding, -> (format, *args) { - sprintf(format, *args) + it_behaves_like :kernel_sprintf_to_str, -> format, *args { + r = nil + -> { + r = sprintf(format, *args) + }.should_not complain(verbose: true) + r } end describe "Kernel.sprintf" do - it_behaves_like :kernel_sprintf, -> (format, *args) { - Kernel.sprintf(format, *args) + it_behaves_like :kernel_sprintf, -> format, *args { + r = nil + -> { + r = Kernel.sprintf(format, *args) + }.should_not complain(verbose: true) + r + } + + it_behaves_like :kernel_sprintf_encoding, -> format, *args { + r = nil + -> { + r = Kernel.sprintf(format, *args) + }.should_not complain(verbose: true) + r } - it_behaves_like :kernel_sprintf_encoding, -> (format, *args) { - Kernel.sprintf(format, *args) + it_behaves_like :kernel_sprintf_to_str, -> format, *args { + r = nil + -> { + r = Kernel.sprintf(format, *args) + }.should_not complain(verbose: true) + r } end diff --git a/spec/ruby/core/kernel/srand_spec.rb b/spec/ruby/core/kernel/srand_spec.rb index 33f99f5ac4..95bb406f46 100644 --- a/spec/ruby/core/kernel/srand_spec.rb +++ b/spec/ruby/core/kernel/srand_spec.rb @@ -1,7 +1,15 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#srand" do + before :each do + @seed = srand + end + + after :each do + srand(@seed) + end -describe "Kernel.srand" do it "is a private method" do Kernel.should have_private_instance_method(:srand) end @@ -11,6 +19,10 @@ describe "Kernel.srand" do srand(20).should == 10 end + it "returns the system-initialized seed value on the first call" do + ruby_exe('print srand(10)', options: '--disable-gems').should =~ /\A\d+\z/ + end + it "seeds the RNG correctly and repeatably" do srand(10) x = rand @@ -19,7 +31,7 @@ describe "Kernel.srand" do end it "defaults number to a random value" do - lambda { srand }.should_not raise_error + -> { srand }.should_not raise_error srand.should_not == 0 end @@ -33,7 +45,7 @@ describe "Kernel.srand" do srand.should == -17 end - it "accepts a Bignum as a seed" do + it "accepts an Integer as a seed" do srand(0x12345678901234567890) srand.should == 0x12345678901234567890 end @@ -48,14 +60,14 @@ describe "Kernel.srand" do end it "raises a TypeError when passed nil" do - lambda { srand(nil) }.should raise_error(TypeError) + -> { srand(nil) }.should raise_error(TypeError) end it "raises a TypeError when passed a String" do - lambda { srand("7") }.should raise_error(TypeError) + -> { srand("7") }.should raise_error(TypeError) end end -describe "Kernel#srand" do +describe "Kernel.srand" do it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/core/kernel/sub_spec.rb b/spec/ruby/core/kernel/sub_spec.rb index 78e5eec4a8..9130bd159c 100644 --- a/spec/ruby/core/kernel/sub_spec.rb +++ b/spec/ruby/core/kernel/sub_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' # FIXME: These methods exist only when the -n or -p option is passed to # ruby, but we currently don't have a way of specifying that. diff --git a/spec/ruby/core/kernel/syscall_spec.rb b/spec/ruby/core/kernel/syscall_spec.rb index bcea833f1f..32d07b3ae2 100644 --- a/spec/ruby/core/kernel/syscall_spec.rb +++ b/spec/ruby/core/kernel/syscall_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#syscall" do it "is a private method" do diff --git a/spec/ruby/core/kernel/system_spec.rb b/spec/ruby/core/kernel/system_spec.rb index aee75441a2..9bc03924dd 100644 --- a/spec/ruby/core/kernel/system_spec.rb +++ b/spec/ruby/core/kernel/system_spec.rb @@ -1,19 +1,19 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe :kernel_system, shared: true do it "executes the specified command in a subprocess" do - lambda { @object.system("echo a") }.should output_to_fd("a\n") + -> { @object.system("echo a") }.should output_to_fd("a\n") $?.should be_an_instance_of Process::Status - $?.success?.should == true + $?.should.success? end it "returns true when the command exits with a zero exit status" do @object.system(ruby_cmd('exit 0')).should == true $?.should be_an_instance_of Process::Status - $?.success?.should == true + $?.should.success? $?.exitstatus.should == 0 end @@ -21,20 +21,28 @@ describe :kernel_system, shared: true do @object.system(ruby_cmd('exit 1')).should == false $?.should be_an_instance_of Process::Status - $?.success?.should == false + $?.should_not.success? $?.exitstatus.should == 1 end + it "raises RuntimeError when `exception: true` is given and the command exits with a non-zero exit status" do + -> { @object.system(ruby_cmd('exit 1'), exception: true) }.should raise_error(RuntimeError) + end + + it "raises Errno::ENOENT when `exception: true` is given and the specified command does not exist" do + -> { @object.system('feature_14386', exception: true) }.should raise_error(Errno::ENOENT) + end + it "returns nil when command execution fails" do @object.system("sad").should be_nil $?.should be_an_instance_of Process::Status $?.pid.should be_kind_of(Integer) - $?.exitstatus.should == 127 + $?.should_not.success? end it "does not write to stderr when command execution fails" do - lambda { @object.system("sad") }.should output_to_fd("", STDERR) + -> { @object.system("sad") }.should output_to_fd("", STDERR) end platform_is_not :windows do @@ -47,12 +55,29 @@ describe :kernel_system, shared: true do end it "executes with `sh` if the command contains shell characters" do - lambda { @object.system("echo $0") }.should output_to_fd("sh\n") + -> { @object.system("echo $0") }.should output_to_fd("sh\n") end it "ignores SHELL env var and always uses `sh`" do ENV['SHELL'] = "/bin/fakeshell" - lambda { @object.system("echo $0") }.should output_to_fd("sh\n") + -> { @object.system("echo $0") }.should output_to_fd("sh\n") + end + end + + platform_is_not :windows do + before :each do + require 'tmpdir' + @shell_command = File.join(Dir.mktmpdir, "noshebang.cmd") + File.write(@shell_command, %[echo "$PATH"\n], perm: 0o700) + end + + after :each do + File.unlink(@shell_command) + Dir.rmdir(File.dirname(@shell_command)) + end + + it "executes with `sh` if the command is executable but not binary and there is no shebang" do + -> { @object.system(@shell_command) }.should output_to_fd(ENV['PATH'] + "\n") end end @@ -69,19 +94,19 @@ describe :kernel_system, shared: true do end it "expands shell variables when given a single string argument" do - lambda { @object.system("echo #{@shell_var}") }.should output_to_fd("foo\n") + -> { @object.system("echo #{@shell_var}") }.should output_to_fd("foo\n") end platform_is_not :windows do it "does not expand shell variables when given multiples arguments" do - lambda { @object.system("echo", @shell_var) }.should output_to_fd("#{@shell_var}\n") + -> { @object.system("echo", @shell_var) }.should output_to_fd("#{@shell_var}\n") end end platform_is :windows do it "does expand shell variables when given multiples arguments" do # See https://bugs.ruby-lang.org/issues/12231 - lambda { @object.system("echo", @shell_var) }.should output_to_fd("foo\n") + -> { @object.system("echo", @shell_var) }.should output_to_fd("foo\n") end end diff --git a/spec/ruby/core/kernel/taint_spec.rb b/spec/ruby/core/kernel/taint_spec.rb index 0c2fb3286b..9a2efbaea0 100644 --- a/spec/ruby/core/kernel/taint_spec.rb +++ b/spec/ruby/core/kernel/taint_spec.rb @@ -1,45 +1,8 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#taint" do - it "returns self" do - o = Object.new - o.taint.should equal(o) - end - - it "sets the tainted bit" do - o = Object.new - o.taint - o.tainted?.should == true - end - - it "raises RuntimeError on an untainted, frozen object" do - o = Object.new.freeze - lambda { o.taint }.should raise_error(RuntimeError) - end - - it "does not raise an error on a tainted, frozen object" do - o = Object.new.taint.freeze - o.taint.should equal(o) - end - - it "has no effect on immediate values" do - [nil, true, false].each do |v| - v.taint - v.tainted?.should == false - end - end - - it "no raises a RuntimeError on symbols" do - v = :sym - lambda { v.taint }.should_not raise_error(RuntimeError) - v.tainted?.should == false - end - - it "no raises error on fixnum values" do - [1].each do |v| - lambda { v.taint }.should_not raise_error(RuntimeError) - v.tainted?.should == false - end + it "has been removed" do + Object.new.should_not.respond_to?(:taint) end end diff --git a/spec/ruby/core/kernel/tainted_spec.rb b/spec/ruby/core/kernel/tainted_spec.rb index efb31be9d8..837eb1dafb 100644 --- a/spec/ruby/core/kernel/tainted_spec.rb +++ b/spec/ruby/core/kernel/tainted_spec.rb @@ -1,12 +1,8 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#tainted?" do - it "returns true if Object is tainted" do - o = mock('o') - p = mock('p') - p.taint - o.tainted?.should == false - p.tainted?.should == true + it "has been removed" do + Object.new.should_not.respond_to?(:tainted?) end end diff --git a/spec/ruby/core/kernel/tap_spec.rb b/spec/ruby/core/kernel/tap_spec.rb index 312a34426c..f7720a6dc7 100644 --- a/spec/ruby/core/kernel/tap_spec.rb +++ b/spec/ruby/core/kernel/tap_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#tap" do it "always yields self and returns self" do @@ -8,6 +8,6 @@ describe "Kernel#tap" do end it "raises a LocalJumpError when no block given" do - lambda { 3.tap }.should raise_error(LocalJumpError) + -> { 3.tap }.should raise_error(LocalJumpError) end end diff --git a/spec/ruby/core/kernel/test_spec.rb b/spec/ruby/core/kernel/test_spec.rb index 43e3a963e3..d26dc06361 100644 --- a/spec/ruby/core/kernel/test_spec.rb +++ b/spec/ruby/core/kernel/test_spec.rb @@ -1,10 +1,10 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#test" do before :all do - @file = File.dirname(__FILE__) + '/fixtures/classes.rb' - @dir = File.dirname(__FILE__) + '/fixtures' + @file = __dir__ + '/fixtures/classes.rb' + @dir = __dir__ + '/fixtures' end it "is a private method" do diff --git a/spec/ruby/core/kernel/then_spec.rb b/spec/ruby/core/kernel/then_spec.rb new file mode 100644 index 0000000000..8109a2960a --- /dev/null +++ b/spec/ruby/core/kernel/then_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/then' + +describe "Kernel#then" do + it_behaves_like :kernel_then, :then +end diff --git a/spec/ruby/core/kernel/throw_spec.rb b/spec/ruby/core/kernel/throw_spec.rb index 3f8d272d6d..64bfccb413 100644 --- a/spec/ruby/core/kernel/throw_spec.rb +++ b/spec/ruby/core/kernel/throw_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel.throw" do it "transfers control to the end of the active catch block waiting for symbol" do @@ -10,7 +10,7 @@ describe "Kernel.throw" do end.should be_nil end - it "transfers control to the innermost catch block waiting for the same sympol" do + it "transfers control to the innermost catch block waiting for the same symbol" do one = two = three = 0 catch :duplicate do catch :duplicate do @@ -42,21 +42,21 @@ describe "Kernel.throw" do end it "raises an ArgumentError if there is no catch block for the symbol" do - lambda { throw :blah }.should raise_error(ArgumentError) + -> { throw :blah }.should raise_error(ArgumentError) end it "raises an UncaughtThrowError if there is no catch block for the symbol" do - lambda { throw :blah }.should raise_error(UncaughtThrowError) + -> { throw :blah }.should raise_error(UncaughtThrowError) end it "raises ArgumentError if 3 or more arguments provided" do - lambda { + -> { catch :blah do throw :blah, :return_value, 2 end }.should raise_error(ArgumentError) - lambda { + -> { catch :blah do throw :blah, :return_value, 2, 3, 4, 5 end @@ -64,7 +64,7 @@ describe "Kernel.throw" do end it "can throw an object" do - lambda { + -> { obj = Object.new catch obj do throw obj diff --git a/spec/ruby/core/kernel/to_enum_spec.rb b/spec/ruby/core/kernel/to_enum_spec.rb index 9fb228f318..9d9945450f 100644 --- a/spec/ruby/core/kernel/to_enum_spec.rb +++ b/spec/ruby/core/kernel/to_enum_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Kernel#to_enum" do it "needs to be reviewed for spec completeness" diff --git a/spec/ruby/core/kernel/to_s_spec.rb b/spec/ruby/core/kernel/to_s_spec.rb index c6fcca54a2..ea4b00151e 100644 --- a/spec/ruby/core/kernel/to_s_spec.rb +++ b/spec/ruby/core/kernel/to_s_spec.rb @@ -1,16 +1,8 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#to_s" do it "returns a String containing the name of self's class" do Object.new.to_s.should =~ /Object/ end - - it "returns a tainted result if self is tainted" do - Object.new.taint.to_s.tainted?.should be_true - end - - it "returns an untrusted result if self is untrusted" do - Object.new.untrust.to_s.untrusted?.should be_true - end end diff --git a/spec/ruby/core/kernel/trace_var_spec.rb b/spec/ruby/core/kernel/trace_var_spec.rb index 07e02feb72..3c84aa5e60 100644 --- a/spec/ruby/core/kernel/trace_var_spec.rb +++ b/spec/ruby/core/kernel/trace_var_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#trace_var" do before :each do @@ -47,7 +47,7 @@ describe "Kernel#trace_var" do end it "raises ArgumentError if no block or proc is provided" do - lambda do + -> do trace_var :$Kernel_trace_var_global end.should raise_error(ArgumentError) end diff --git a/spec/ruby/core/kernel/trap_spec.rb b/spec/ruby/core/kernel/trap_spec.rb index 98f386dc85..4c801a7215 100644 --- a/spec/ruby/core/kernel/trap_spec.rb +++ b/spec/ruby/core/kernel/trap_spec.rb @@ -1,12 +1,9 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' describe "Kernel#trap" do it "is a private method" do Kernel.should have_private_instance_method(:trap) end -end -describe "Kernel.trap" do - it "needs to be reviewed for spec completeness" + # Behaviour is specified for Signal.trap end diff --git a/spec/ruby/core/kernel/trust_spec.rb b/spec/ruby/core/kernel/trust_spec.rb index a9fda5c5c6..ef3fa9a3e1 100644 --- a/spec/ruby/core/kernel/trust_spec.rb +++ b/spec/ruby/core/kernel/trust_spec.rb @@ -1,25 +1,8 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#trust" do - it "returns self" do - o = Object.new - o.trust.should equal(o) - end - - it "clears the untrusted bit" do - o = Object.new.untrust - o.trust - o.untrusted?.should == false - end - - it "raises RuntimeError on an untrusted, frozen object" do - o = Object.new.untrust.freeze - lambda { o.trust }.should raise_error(RuntimeError) - end - - it "does not raise an error on a trusted, frozen object" do - o = Object.new.freeze - o.trust.should equal(o) + it "has been removed" do + Object.new.should_not.respond_to?(:trust) end end diff --git a/spec/ruby/core/kernel/untaint_spec.rb b/spec/ruby/core/kernel/untaint_spec.rb index 5abe5d63fc..47e8544bd4 100644 --- a/spec/ruby/core/kernel/untaint_spec.rb +++ b/spec/ruby/core/kernel/untaint_spec.rb @@ -1,25 +1,8 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#untaint" do - it "returns self" do - o = Object.new - o.untaint.should equal(o) - end - - it "clears the tainted bit" do - o = Object.new.taint - o.untaint - o.tainted?.should == false - end - - it "raises RuntimeError on a tainted, frozen object" do - o = Object.new.taint.freeze - lambda { o.untaint }.should raise_error(RuntimeError) - end - - it "does not raise an error on an untainted, frozen object" do - o = Object.new.freeze - o.untaint.should equal(o) + it "has been removed" do + Object.new.should_not.respond_to?(:untaint) end end diff --git a/spec/ruby/core/kernel/untrace_var_spec.rb b/spec/ruby/core/kernel/untrace_var_spec.rb index 3af1348ffd..1925a3a836 100644 --- a/spec/ruby/core/kernel/untrace_var_spec.rb +++ b/spec/ruby/core/kernel/untrace_var_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#untrace_var" do it "is a private method" do diff --git a/spec/ruby/core/kernel/untrust_spec.rb b/spec/ruby/core/kernel/untrust_spec.rb index 280a465807..8787ab3fc9 100644 --- a/spec/ruby/core/kernel/untrust_spec.rb +++ b/spec/ruby/core/kernel/untrust_spec.rb @@ -1,25 +1,8 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#untrust" do - it "returns self" do - o = Object.new - o.untrust.should equal(o) - end - - it "sets the untrusted bit" do - o = Object.new - o.untrust - o.untrusted?.should == true - end - - it "raises RuntimeError on a trusted, frozen object" do - o = Object.new.freeze - lambda { o.untrust }.should raise_error(RuntimeError) - end - - it "does not raise an error on an untrusted, frozen object" do - o = Object.new.untrust.freeze - o.untrust.should equal(o) + it "has been removed" do + Object.new.should_not.respond_to?(:untrust) end end diff --git a/spec/ruby/core/kernel/untrusted_spec.rb b/spec/ruby/core/kernel/untrusted_spec.rb index 43c4c0aa18..29261be9c4 100644 --- a/spec/ruby/core/kernel/untrusted_spec.rb +++ b/spec/ruby/core/kernel/untrusted_spec.rb @@ -1,28 +1,8 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#untrusted?" do - it "returns the untrusted status of an object" do - o = mock('o') - o.untrusted?.should == false - o.untrust - o.untrusted?.should == true - end - - it "has no effect on immediate values" do - a = nil - b = true - c = false - a.untrust - b.untrust - c.untrust - a.untrusted?.should == false - b.untrusted?.should == false - c.untrusted?.should == false - end - - it "has effect on immediate values" do - d = 1 - lambda { d.untrust }.should_not raise_error(RuntimeError) + it "has been removed" do + Object.new.should_not.respond_to?(:untrusted?) end end diff --git a/spec/ruby/core/kernel/warn_spec.rb b/spec/ruby/core/kernel/warn_spec.rb index c44116dc21..e03498c6dc 100644 --- a/spec/ruby/core/kernel/warn_spec.rb +++ b/spec/ruby/core/kernel/warn_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Kernel#warn" do before :each do @@ -8,72 +8,291 @@ describe "Kernel#warn" do end after :each do - $VERBOSE = @before_verbose + $VERBOSE = nil $/ = @before_separator + $VERBOSE = @before_verbose end it "is a private method" do Kernel.should have_private_instance_method(:warn) end - it "requires multiple arguments" do + it "accepts multiple arguments" do Kernel.method(:warn).arity.should < 0 end it "does not append line-end if last character is line-end" do - lambda { + -> { $VERBOSE = true warn("this is some simple text with line-end\n") }.should output(nil, "this is some simple text with line-end\n") end it "calls #write on $stderr if $VERBOSE is true" do - lambda { + -> { $VERBOSE = true warn("this is some simple text") }.should output(nil, "this is some simple text\n") end it "calls #write on $stderr if $VERBOSE is false" do - lambda { + -> { $VERBOSE = false warn("this is some simple text") }.should output(nil, "this is some simple text\n") end it "does not call #write on $stderr if $VERBOSE is nil" do - lambda { + -> { $VERBOSE = nil warn("this is some simple text") }.should output(nil, "") end it "writes each argument on a line when passed multiple arguments" do - lambda { + -> { $VERBOSE = true warn("line 1", "line 2") }.should output(nil, "line 1\nline 2\n") end it "writes each array element on a line when passes an array" do - lambda { + -> { $VERBOSE = true warn(["line 1", "line 2"]) }.should output(nil, "line 1\nline 2\n") end it "does not write strings when passed no arguments" do - lambda { + -> { $VERBOSE = true warn }.should output("", "") end it "writes the default record separator and NOT $/ to $stderr after the warning message" do - lambda { + -> { $VERBOSE = true $/ = 'rs' warn("") }.should output(nil, /\n/) end + + it "writes to_s representation if passed a non-string" do + obj = mock("obj") + obj.should_receive(:to_s).and_return("to_s called") + -> { + $VERBOSE = true + warn(obj) + }.should output(nil, "to_s called\n") + end + + describe ":uplevel keyword argument" do + before :each do + $VERBOSE = true + end + + it "prepends a message with specified line from the backtrace" do + w = KernelSpecs::WarnInNestedCall.new + + -> { w.f4("foo", 0) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.warn_call_lineno}: warning: foo|) + -> { w.f4("foo", 1) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.f1_call_lineno}: warning: foo|) + -> { w.f4("foo", 2) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.f2_call_lineno}: warning: foo|) + -> { w.f4("foo", 3) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.f3_call_lineno}: warning: foo|) + end + + # Test both explicitly without and with RubyGems as RubyGems overrides Kernel#warn + it "shows the caller of #require and not #require itself without RubyGems" do + file = fixture(__FILE__ , "warn_require_caller.rb") + ruby_exe(file, options: "--disable-gems", args: "2>&1").should == "#{file}:2: warning: warn-require-warning\n" + end + + it "shows the caller of #require and not #require itself with RubyGems loaded" do + file = fixture(__FILE__ , "warn_require_caller.rb") + ruby_exe(file, options: "-rrubygems", args: "2>&1").should == "#{file}:2: warning: warn-require-warning\n" + end + + it "doesn't show the caller when the uplevel is `nil`" do + w = KernelSpecs::WarnInNestedCall.new + + -> { w.f4("foo", nil) }.should output(nil, "foo\n") + end + + guard -> { Kernel.instance_method(:tap).source_location } do + it "skips <internal: core library methods defined in Ruby" do + file, line = Kernel.instance_method(:tap).source_location + file.should.start_with?('<internal:') + + file = fixture(__FILE__ , "warn_core_method.rb") + n = 9 + ruby_exe(file, options: "--disable-gems", args: "2>&1").lines.should == [ + "#{file}:#{n+0}: warning: use X instead\n", + "#{file}:#{n+1}: warning: use X instead\n", + "#{file}:#{n+2}: warning: use X instead\n", + "#{file}:#{n+4}: warning: use X instead\n", + ] + end + end + + it "accepts :category keyword with a symbol" do + -> { + $VERBOSE = true + warn("message", category: :deprecated) + }.should output(nil, "message\n") + end + + it "accepts :category keyword with nil" do + -> { + $VERBOSE = true + warn("message", category: nil) + }.should output(nil, "message\n") + end + + it "accepts :category keyword with object convertible to symbol" do + o = Object.new + def o.to_sym; :deprecated; end + -> { + $VERBOSE = true + warn("message", category: o) + }.should output(nil, "message\n") + end + + it "raises if :category keyword is not nil and not convertible to symbol" do + -> { + $VERBOSE = true + warn("message", category: Object.new) + }.should raise_error(TypeError) + end + + it "converts first arg using to_s" do + w = KernelSpecs::WarnInNestedCall.new + + -> { w.f4(false, 0) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.warn_call_lineno}: warning: false|) + -> { w.f4(nil, 1) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.f1_call_lineno}: warning: |) + obj = mock("obj") + obj.should_receive(:to_s).and_return("to_s called") + -> { w.f4(obj, 2) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.f2_call_lineno}: warning: to_s called|) + end + + it "does not prepend caller information if the uplevel argument is too large" do + w = KernelSpecs::WarnInNestedCall.new + -> { w.f4("foo", 100) }.should output(nil, "warning: foo\n") + end + + it "prepends even if a message is empty or nil" do + w = KernelSpecs::WarnInNestedCall.new + + -> { w.f4("", 0) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.warn_call_lineno}: warning: \n$|) + -> { w.f4(nil, 0) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.warn_call_lineno}: warning: \n$|) + end + + it "converts value to Integer" do + w = KernelSpecs::WarnInNestedCall.new + + -> { w.f4(0.1) }.should output(nil, %r|classes.rb:#{w.warn_call_lineno}:|) + -> { w.f4(Rational(1, 2)) }.should output(nil, %r|classes.rb:#{w.warn_call_lineno}:|) + end + + it "raises ArgumentError if passed negative value" do + -> { warn "", uplevel: -2 }.should raise_error(ArgumentError) + -> { warn "", uplevel: -100 }.should raise_error(ArgumentError) + end + + it "raises ArgumentError if passed -1" do + -> { warn "", uplevel: -1 }.should raise_error(ArgumentError) + end + + it "raises TypeError if passed not Integer" do + -> { warn "", uplevel: "" }.should raise_error(TypeError) + -> { warn "", uplevel: [] }.should raise_error(TypeError) + -> { warn "", uplevel: {} }.should raise_error(TypeError) + -> { warn "", uplevel: Object.new }.should raise_error(TypeError) + end + end + + it "treats empty hash as no keyword argument" do + h = {} + -> { warn(**h) }.should_not complain(verbose: true) + -> { warn('foo', **h) }.should complain("foo\n") + end + + it "calls Warning.warn without keyword arguments if Warning.warn does not accept keyword arguments" do + verbose = $VERBOSE + $VERBOSE = false + class << Warning + alias_method :_warn, :warn + def warn(message) + ScratchPad.record(message) + end + end + + begin + ScratchPad.clear + Kernel.warn("Chunky bacon!") + ScratchPad.recorded.should == "Chunky bacon!\n" + + Kernel.warn("Deprecated bacon!", category: :deprecated) + ScratchPad.recorded.should == "Deprecated bacon!\n" + ensure + class << Warning + remove_method :warn + alias_method :warn, :_warn + remove_method :_warn + end + $VERBOSE = verbose + end + end + + it "calls Warning.warn with category: nil if Warning.warn accepts keyword arguments" do + Warning.should_receive(:warn).with("Chunky bacon!\n", category: nil) + verbose = $VERBOSE + $VERBOSE = false + begin + Kernel.warn("Chunky bacon!") + ensure + $VERBOSE = verbose + end + end + + it "calls Warning.warn with given category keyword converted to a symbol" do + Warning.should_receive(:warn).with("Chunky bacon!\n", category: :deprecated) + verbose = $VERBOSE + $VERBOSE = false + begin + Kernel.warn("Chunky bacon!", category: 'deprecated') + ensure + $VERBOSE = verbose + end + end + + it "does not call Warning.warn if self is the Warning module" do + # RubyGems redefines Kernel#warn so we need to use a subprocess and disable RubyGems here + code = <<-RUBY + def Warning.warn(*args, **kwargs) + raise 'should not be called' + end + Kernel.instance_method(:warn).bind(Warning).call('Kernel#warn spec edge case') + RUBY + out = ruby_exe(code, args: "2>&1", options: "--disable-gems") + out.should == "Kernel#warn spec edge case\n" + $?.should.success? + end + + it "avoids recursion if Warning#warn is redefined and calls super" do + # This works because of the spec above, which is the workaround for it. + # Note that redefining Warning#warn is a mistake which would naturally end in infinite recursion, + # Warning.extend Module.new { def warn } should be used instead. + # RubyGems redefines Kernel#warn so we need to use a subprocess and disable RubyGems here + code = <<-RUBY + module Warning + def warn(*args, **kwargs) + super + end + end + warn "avoid infinite recursion" + RUBY + out = ruby_exe(code, args: "2>&1", options: "--disable-gems") + out.should == "avoid infinite recursion\n" + $?.should.success? + end end diff --git a/spec/ruby/core/kernel/yield_self_spec.rb b/spec/ruby/core/kernel/yield_self_spec.rb index 817c01f288..e311dcee47 100644 --- a/spec/ruby/core/kernel/yield_self_spec.rb +++ b/spec/ruby/core/kernel/yield_self_spec.rb @@ -1,26 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/then' -has_yield_self = VersionGuard.new("2.5").match? || PlatformGuard.implementation?(:truffleruby) - -if has_yield_self - describe "Kernel#yield_self" do - it "yields self" do - object = Object.new - object.yield_self { |o| o.should equal object } - end - - it "returns the block return value" do - object = Object.new - object.yield_self { 42 }.should equal 42 - end - - it "returns a sized Enumerator when no block given" do - object = Object.new - enum = object.yield_self - enum.should be_an_instance_of Enumerator - enum.size.should equal 1 - enum.peek.should equal object - enum.first.should equal object - end - end +describe "Kernel#yield_self" do + it_behaves_like :kernel_then, :yield_self end |
