diff options
Diffstat (limited to 'spec/ruby/core/kernel')
151 files changed, 12925 insertions, 0 deletions
diff --git a/spec/ruby/core/kernel/Array_spec.rb b/spec/ruby/core/kernel/Array_spec.rb new file mode 100644 index 0000000000..b4a8bb7599 --- /dev/null +++ b/spec/ruby/core/kernel/Array_spec.rb @@ -0,0 +1,97 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel" do + it "has private instance method Array()" do + Kernel.should have_private_instance_method(:Array) + end +end + +describe :kernel_Array, shared: true do + before :each do + @array = [1, 2, 3] + end + + it "does not call #to_ary on an Array" do + @array.should_not_receive(:to_ary) + @object.send(@method, @array).should == @array + end + + it "calls #to_ary to convert the argument to an Array" do + obj = mock("Array([1,2,3])") + obj.should_receive(:to_ary).and_return(@array) + obj.should_not_receive(:to_a) + + @object.send(@method, obj).should == @array + end + + it "does not call #to_a on an Array" do + @array.should_not_receive(:to_a) + @object.send(@method, @array).should == @array + end + + it "calls #to_a if the argument does not respond to #to_ary" do + obj = mock("Array([1,2,3])") + obj.should_receive(:to_a).and_return(@array) + + @object.send(@method, obj).should == @array + end + + it "calls #to_a if #to_ary returns nil" do + obj = mock("Array([1,2,3])") + obj.should_receive(:to_ary).and_return(nil) + obj.should_receive(:to_a).and_return(@array) + + @object.send(@method, obj).should == @array + end + + it "returns an Array containing the argument if #to_a returns nil" do + obj = mock("Array([1,2,3])") + obj.should_receive(:to_a).and_return(nil) + + @object.send(@method, obj).should == [obj] + end + + it "calls #to_ary first, even if it's private" do + obj = KernelSpecs::PrivateToAry.new + + @object.send(@method, obj).should == [1, 2] + end + + it "calls #to_a if #to_ary is not defined, even if it's private" do + obj = KernelSpecs::PrivateToA.new + + @object.send(@method, obj).should == [3, 4] + end + + it "returns an Array containing the argument if it responds to neither #to_ary nor #to_a" do + obj = mock("Array(x)") + @object.send(@method, obj).should == [obj] + end + + it "returns an empty Array when passed nil" do + @object.send(@method, nil).should == [] + end + + it "raises a TypeError if #to_ary does not return an Array" do + obj = mock("Array() string") + obj.should_receive(:to_ary).and_return("string") + + -> { @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") + + -> { @object.send(@method, obj) }.should raise_error(TypeError) + end +end + +describe "Kernel.Array" do + it_behaves_like :kernel_Array, :Array_method, KernelSpecs +end + +describe "Kernel#Array" do + it_behaves_like :kernel_Array, :Array_function, KernelSpecs +end diff --git a/spec/ruby/core/kernel/Complex_spec.rb b/spec/ruby/core/kernel/Complex_spec.rb new file mode 100644 index 0000000000..346d50ab5e --- /dev/null +++ b/spec/ruby/core/kernel/Complex_spec.rb @@ -0,0 +1,276 @@ +require_relative '../../spec_helper' +require_relative '../../shared/kernel/complex' +require_relative 'fixtures/Complex' + +describe "Kernel.Complex()" do + 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 new file mode 100644 index 0000000000..9c436b05f7 --- /dev/null +++ b/spec/ruby/core/kernel/Float_spec.rb @@ -0,0 +1,413 @@ +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.should equal float + end + + it "returns a Float for Fixnums" do + @object.send(:Float, 1).should == 1.0 + end + + it "returns a Float for Complex with only a real part" do + @object.send(:Float, Complex(1)).should == 1.0 + end + + it "returns a Float for Bignums" do + @object.send(:Float, 1000000000000).should == 1000000000000.0 + end + + it "raises an ArgumentError for nil" do + -> { @object.send(:Float, nil) }.should raise_error(TypeError) + end + + it "returns the identical NaN for NaN" do + nan = nan_value + nan.nan?.should be_true + nan2 = @object.send(:Float, nan) + nan2.nan?.should be_true + nan2.should equal(nan) + end + + it "returns the same Infinity for Infinity" do + infinity = infinity_value + infinity2 = @object.send(:Float, infinity) + infinity2.should == infinity_value + infinity.should equal(infinity2) + end + + it "converts Strings to floats without calling #to_f" do + string = +"10" + string.should_not_receive(:to_f) + @object.send(:Float, string).should == 10.0 + end + + it "converts Strings with decimal points into Floats" do + @object.send(:Float, "10.0").should == 10.0 + end + + it "raises an ArgumentError for a String of word characters" do + -> { @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 + -> { @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 + -> { @object.send(:Float, "10D") }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError for a String of word characters followed by numbers" do + -> { @object.send(:Float, "D10") }.should raise_error(ArgumentError) + end + + it "is strict about the string form even across newlines" do + -> { @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 + my_string = Class.new(String) do + def to_f() 1.2 end + end + + @object.send(:Float, my_string.new("10")).should == 10.0 + end + + it "returns a positive Float if the string is prefixed with +" do + @object.send(:Float, "+10").should == 10.0 + @object.send(:Float, " +10").should == 10.0 + end + + it "returns a negative Float if the string is prefixed with +" do + @object.send(:Float, "-10").should == -10.0 + @object.send(:Float, " -10").should == -10.0 + end + + it "raises an ArgumentError if a + or - is embedded in a String" do + -> { @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 + -> { @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 + -> { @object.send(:Float, "_1") }.should raise_error(ArgumentError) + end + + it "returns a value for a String with an embedded _" do + @object.send(:Float, "1_000").should == 1000.0 + end + + it "raises an ArgumentError for a String with a trailing _" do + -> { @object.send(:Float, "10_") }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError for a String of \\0" do + -> { @object.send(:Float, "\0") }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError for a String with a leading \\0" do + -> { @object.send(:Float, "\01") }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError for a String with an embedded \\0" do + -> { @object.send(:Float, "1\01") }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError for a String with a trailing \\0" do + -> { @object.send(:Float, "1\0") }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError for a String that is just an empty space" do + -> { @object.send(:Float, " ") }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError for a String that with an embedded space" do + -> { @object.send(:Float, "1 2") }.should raise_error(ArgumentError) + end + + it "returns a value for a String with a leading space" do + @object.send(:Float, " 1").should == 1.0 + end + + it "returns a value for a String with a trailing space" do + @object.send(:Float, "1 ").should == 1.0 + end + + it "returns a value for a String with any leading whitespace" do + @object.send(:Float, "\t\n1").should == 1.0 + end + + it "returns a value for a String with any trailing whitespace" 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 + -> { @object.send(:Float, "2#{e}") }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if #{e} is the leading character" do + -> { @object.send(:Float, "#{e}2") }.should raise_error(ArgumentError) + end + + it "returns Infinity for '2#{e}1000'" do + @object.send(:Float, "2#{e}1000").should == Float::INFINITY + end + + it "returns 0 for '2#{e}-1000'" do + @object.send(:Float, "2#{e}-1000").should == 0 + end + + it "allows embedded _ in a number on either side of the #{e}" do + @object.send(:Float, "2_0#{e}100").should == 20e100 + @object.send(:Float, "20#{e}1_00").should == 20e100 + @object.send(:Float, "2_0#{e}1_00").should == 20e100 + end + + it "raises an exception if a space is embedded on either side of the '#{e}'" do + -> { @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 + -> { @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 + -> { @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 + @object.send(:Float, "2.0#{e}2").should == 2e2 + end + + it "raises an ArgumentError if there's a decimal point on the right side of the '#{e}'" do + -> { @object.send(:Float, "20#{e}2.0") }.should raise_error(ArgumentError) + end + 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 "interprets negative hex value" do + @object.send(:Float, "-0x10").should == -16.0 + end + + it "accepts embedded _ if the number does not contain a-f" do + @object.send(:Float, "0x1_0").should == 16.0 + end + + 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 + + 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 "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 "parses negative hexadecimal string as negative float" do + @object.send(:Float, "-0x7b").should == -123.0 + end + + ruby_version_is "3.4" do + it "accepts a fractional part" do + @object.send(:Float, "0x0.8").should == 0.5 + end + end + + 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 + + it "returns a Float that can be a parameter to #Float again" do + float = @object.send(:Float, "10") + @object.send(:Float, float).should == 10.0 + end + + it "otherwise, converts the given argument to a Float by calling #to_f" do + (obj = mock('1.2')).should_receive(:to_f).once.and_return(1.2) + obj.should_not_receive(:to_i) + @object.send(:Float, obj).should == 1.2 + end + + it "returns the identical NaN if to_f is called and it returns NaN" do + nan = nan_value + (nan_to_f = mock('NaN')).should_receive(:to_f).once.and_return(nan) + nan2 = @object.send(:Float, nan_to_f) + nan2.nan?.should be_true + nan2.should equal(nan) + end + + 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) + infinity2.should equal(infinity) + end + + it "raises a TypeError if #to_f is not provided" do + -> { @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!') + -> { @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) + -> { @object.send(:Float, obj) }.should raise_error(TypeError) + end + + it "raises a RangeError when passed a Complex argument" do + c = Complex(2, 3) + -> { @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 + +describe "Kernel.Float" do + it_behaves_like :kernel_float, :Float, Kernel +end + +describe "Kernel#Float" do + it_behaves_like :kernel_float, :Float, Object.new +end + +describe "Kernel#Float" do + it "is a private method" do + Kernel.should have_private_instance_method(:Float) + end +end diff --git a/spec/ruby/core/kernel/Hash_spec.rb b/spec/ruby/core/kernel/Hash_spec.rb new file mode 100644 index 0000000000..cbe098a8ac --- /dev/null +++ b/spec/ruby/core/kernel/Hash_spec.rb @@ -0,0 +1,63 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#hash" do + 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 + it "has private instance method Hash()" do + Kernel.should have_private_instance_method(:Hash) + end +end + +describe :kernel_Hash, shared: true do + before :each do + @hash = { a: 1} + end + + it "converts nil to a Hash" do + @object.send(@method, nil).should == {} + end + + it "converts an empty array to a Hash" do + @object.send(@method, []).should == {} + end + + it "does not call #to_hash on an Hash" do + @hash.should_not_receive(:to_hash) + @object.send(@method, @hash).should == @hash + end + + it "calls #to_hash to convert the argument to an Hash" do + obj = mock("Hash(a: 1)") + obj.should_receive(:to_hash).and_return(@hash) + + @object.send(@method, obj).should == @hash + end + + it "raises a TypeError if it doesn't respond to #to_hash" do + -> { @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") + + -> { @object.send(@method, obj) }.should raise_error(TypeError) + end +end + +describe "Kernel.Hash" do + it_behaves_like :kernel_Hash, :Hash_method, KernelSpecs +end + +describe "Kernel#Hash" do + it_behaves_like :kernel_Hash, :Hash_function, KernelSpecs +end diff --git a/spec/ruby/core/kernel/Integer_spec.rb b/spec/ruby/core/kernel/Integer_spec.rb new file mode 100644 index 0000000000..74dd3e0dd2 --- /dev/null +++ b/spec/ruby/core/kernel/Integer_spec.rb @@ -0,0 +1,823 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe :kernel_integer, shared: true do + it "returns a Bignum for a Bignum" do + Integer(2e100).should == 2e100 + end + + it "returns a Fixnum for a Fixnum" do + Integer(100).should == 100 + end + + 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_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 + -> { Integer(nil) }.should raise_error(TypeError) + end + + 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 + Integer(3.14).should == 3 + Integer(90.8).should == 90 + end + + it "calls to_i on Rationals" do + Integer(Rational(8,3)).should == 2 + Integer(3.quo(2)).should == 1 + end + + it "returns the value of to_int if the result is a Fixnum" do + obj = mock("object") + obj.should_receive(:to_int).and_return(1) + obj.should_not_receive(:to_i) + Integer(obj).should == 1 + end + + it "returns the value of to_int if the result is a Bignum" do + obj = mock("object") + obj.should_receive(:to_int).and_return(2 * 10**100) + obj.should_not_receive(:to_i) + Integer(obj).should == 2 * 10**100 + end + + it "calls to_i on an object whose to_int returns nil" do + obj = mock("object") + obj.should_receive(:to_int).and_return(nil) + obj.should_receive(:to_i).and_return(1) + Integer(obj).should == 1 + end + + 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") + -> { Integer(obj) }.should raise_error(TypeError) + end + + it "raises a TypeError if no to_int or to_i methods exist" do + obj = mock("object") + -> { 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) + -> { Integer(obj) }.should raise_error(TypeError) + end + + it "raises a FloatDomainError when passed NaN" do + -> { Integer(nan_value) }.should raise_error(FloatDomainError) + end + + it "raises a FloatDomainError when passed Infinity" do + -> { 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 :kernel_integer_string, shared: true do + it "raises an ArgumentError if the String is a null byte" do + -> { Integer("\0") }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if the String starts with a null byte" do + -> { Integer("\01") }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if the String ends with a null byte" do + -> { Integer("1\0") }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if the String contains a null byte" do + -> { Integer("1\01") }.should raise_error(ArgumentError) + end + + it "ignores leading whitespace" do + Integer(" 1").should == 1 + Integer(" 1").should == 1 + Integer("\t\n1").should == 1 + end + + it "ignores trailing whitespace" do + Integer("1 ").should == 1 + Integer("1 ").should == 1 + Integer("1\t\n").should == 1 + end + + it "raises an ArgumentError if there are leading _s" do + -> { Integer("_1") }.should raise_error(ArgumentError) + -> { Integer("___1") }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if there are trailing _s" do + -> { Integer("1_") }.should raise_error(ArgumentError) + -> { Integer("1___") }.should raise_error(ArgumentError) + end + + it "ignores an embedded _" do + Integer("1_1").should == 11 + end + + it "raises an ArgumentError if there are multiple embedded _s" do + -> { Integer("1__1") }.should raise_error(ArgumentError) + -> { Integer("1___1") }.should raise_error(ArgumentError) + end + + it "ignores a single leading +" do + Integer("+1").should == 1 + end + + it "raises an ArgumentError if there is a space between the + and number" do + -> { Integer("+ 1") }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if there are multiple leading +s" do + -> { Integer("++1") }.should raise_error(ArgumentError) + -> { Integer("+++1") }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if there are trailing +s" do + -> { Integer("1+") }.should raise_error(ArgumentError) + -> { Integer("1+++") }.should raise_error(ArgumentError) + end + + it "makes the number negative if there's a leading -" do + Integer("-1").should == -1 + end + + it "raises an ArgumentError if there are multiple leading -s" do + -> { Integer("--1") }.should raise_error(ArgumentError) + -> { Integer("---1") }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if there are trailing -s" do + -> { Integer("1-") }.should raise_error(ArgumentError) + -> { Integer("1---") }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if there is a period" do + -> { Integer("0.0") }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError for an empty String" do + -> { 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 + Integer("0").should == 0 + end + + %w(x X).each do |x| + it "parses the value as a hex number if there's a leading 0#{x}" do + Integer("0#{x}1").should == 0x1 + Integer("0#{x}dd").should == 0xdd + end + + it "is a positive hex number if there's a leading +0#{x}" do + Integer("+0#{x}1").should == 0x1 + Integer("+0#{x}dd").should == 0xdd + end + + it "is a negative hex number if there's a leading -0#{x}" do + Integer("-0#{x}1").should == -0x1 + Integer("-0#{x}dd").should == -0xdd + end + + it "raises an ArgumentError if the number cannot be parsed as hex" do + -> { Integer("0#{x}g") }.should raise_error(ArgumentError) + end + end + + %w(b B).each do |b| + it "parses the value as a binary number if there's a leading 0#{b}" do + Integer("0#{b}1").should == 0b1 + Integer("0#{b}10").should == 0b10 + end + + it "is a positive binary number if there's a leading +0#{b}" do + Integer("+0#{b}1").should == 0b1 + Integer("+0#{b}10").should == 0b10 + end + + it "is a negative binary number if there's a leading -0#{b}" do + Integer("-0#{b}1").should == -0b1 + Integer("-0#{b}10").should == -0b10 + end + + it "raises an ArgumentError if the number cannot be parsed as binary" do + -> { Integer("0#{b}2") }.should raise_error(ArgumentError) + end + end + + ["o", "O", ""].each do |o| + it "parses the value as an octal number if there's a leading 0#{o}" do + Integer("0#{o}1").should == 0O1 + Integer("0#{o}10").should == 0O10 + end + + it "is a positive octal number if there's a leading +0#{o}" do + Integer("+0#{o}1").should == 0O1 + Integer("+0#{o}10").should == 0O10 + end + + it "is a negative octal number if there's a leading -0#{o}" do + Integer("-0#{o}1").should == -0O1 + Integer("-0#{o}10").should == -0O10 + end + + it "raises an ArgumentError if the number cannot be parsed as octal" do + -> { Integer("0#{o}9") }.should raise_error(ArgumentError) + end + end + + %w(D d).each do |d| + it "parses the value as a decimal number if there's a leading 0#{d}" do + Integer("0#{d}1").should == 1 + Integer("0#{d}10").should == 10 + end + + it "is a positive decimal number if there's a leading +0#{d}" do + Integer("+0#{d}1").should == 1 + Integer("+0#{d}10").should == 10 + end + + it "is a negative decimal number if there's a leading -0#{d}" do + Integer("-0#{d}1").should == -1 + Integer("-0#{d}10").should == -10 + end + + it "raises an ArgumentError if the number cannot be parsed as decimal" do + -> { Integer("0#{d}a") }.should raise_error(ArgumentError) + end + end +end + +describe :kernel_integer_string_base, shared: true do + it "raises an ArgumentError if the String is a null byte" do + -> { Integer("\0", 2) }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if the String starts with a null byte" do + -> { Integer("\01", 3) }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if the String ends with a null byte" do + -> { Integer("1\0", 4) }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if the String contains a null byte" do + -> { Integer("1\01", 5) }.should raise_error(ArgumentError) + end + + it "ignores leading whitespace" do + Integer(" 16", 16).should == 22 + Integer(" 16", 16).should == 22 + Integer("\t\n16", 16).should == 22 + end + + it "ignores trailing whitespace" do + Integer("16 ", 16).should == 22 + Integer("16 ", 16).should == 22 + Integer("16\t\n", 16).should == 22 + end + + it "raises an ArgumentError if there are leading _s" do + -> { 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 + -> { Integer("1_", 12) }.should raise_error(ArgumentError) + -> { Integer("1___", 12) }.should raise_error(ArgumentError) + end + + it "ignores an embedded _" do + Integer("1_1", 4).should == 5 + end + + it "raises an ArgumentError if there are multiple embedded _s" do + -> { Integer("1__1", 4) }.should raise_error(ArgumentError) + -> { Integer("1___1", 4) }.should raise_error(ArgumentError) + end + + it "ignores a single leading +" do + Integer("+10", 3).should == 3 + end + + it "raises an ArgumentError if there is a space between the + and number" do + -> { Integer("+ 1", 3) }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if there are multiple leading +s" do + -> { 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 + -> { 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 + Integer("-19", 20).should == -29 + end + + it "raises an ArgumentError if there are multiple leading -s" do + -> { 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 + -> { 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 + -> { Integer("0.0", 3) }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError for an empty String" do + -> { Integer("", 12) }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError for a base of 1" do + -> { Integer("1", 1) }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError for a base of 37" do + -> { Integer("1", 37) }.should raise_error(ArgumentError) + end + + it "accepts wholly lowercase alphabetic strings for bases > 10" do + Integer('ab',12).should == 131 + Integer('af',20).should == 215 + Integer('ghj',30).should == 14929 + end + + it "accepts wholly uppercase alphabetic strings for bases > 10" do + Integer('AB',12).should == 131 + Integer('AF',20).should == 215 + Integer('GHJ',30).should == 14929 + end + + it "accepts mixed-case alphabetic strings for bases > 10" do + Integer('Ab',12).should == 131 + Integer('aF',20).should == 215 + Integer('GhJ',30).should == 14929 + end + + it "accepts alphanumeric strings for bases > 10" do + Integer('a3e',19).should == 3681 + Integer('12q',31).should == 1049 + Integer('c00o',29).should == 292692 + end + + it "raises an ArgumentError for letters invalid in the given base" do + -> { Integer('z',19) }.should raise_error(ArgumentError) + -> { Integer('c00o',2) }.should raise_error(ArgumentError) + end + + %w(x X).each do |x| + it "parses the value as a hex number if there's a leading 0#{x} and a base of 16" do + Integer("0#{x}10", 16).should == 16 + Integer("0#{x}dd", 16).should == 221 + end + + it "is a positive hex number if there's a leading +0#{x} and base of 16" do + Integer("+0#{x}1", 16).should == 0x1 + Integer("+0#{x}dd", 16).should == 0xdd + end + + it "is a negative hex number if there's a leading -0#{x} and a base of 16" do + Integer("-0#{x}1", 16).should == -0x1 + Integer("-0#{x}dd", 16).should == -0xdd + end + + 2.upto(15) do |base| + it "raises an ArgumentError if the number begins with 0#{x} and the base is #{base}" do + -> { 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 + -> { Integer("0#{x}g", 16) }.should raise_error(ArgumentError) + end + end + + %w(b B).each do |b| + it "parses the value as a binary number if there's a leading 0#{b} and the base is 2" do + Integer("0#{b}1", 2).should == 0b1 + Integer("0#{b}10", 2).should == 0b10 + end + + it "is a positive binary number if there's a leading +0#{b} and a base of 2" do + Integer("+0#{b}1", 2).should == 0b1 + Integer("+0#{b}10", 2).should == 0b10 + end + + it "is a negative binary number if there's a leading -0#{b} and a base of 2" do + Integer("-0#{b}1", 2).should == -0b1 + Integer("-0#{b}10", 2).should == -0b10 + end + + it "raises an ArgumentError if the number cannot be parsed as binary and the base is 2" do + -> { Integer("0#{b}2", 2) }.should raise_error(ArgumentError) + end + end + + ["o", "O"].each do |o| + it "parses the value as an octal number if there's a leading 0#{o} and a base of 8" do + Integer("0#{o}1", 8).should == 0O1 + Integer("0#{o}10", 8).should == 0O10 + end + + it "is a positive octal number if there's a leading +0#{o} and a base of 8" do + Integer("+0#{o}1", 8).should == 0O1 + Integer("+0#{o}10", 8).should == 0O10 + end + + it "is a negative octal number if there's a leading -0#{o} and a base of 8" do + Integer("-0#{o}1", 8).should == -0O1 + Integer("-0#{o}10", 8).should == -0O10 + end + + it "raises an ArgumentError if the number cannot be parsed as octal and the base is 8" do + -> { 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 + -> { Integer("0#{o}1", base) }.should raise_error(ArgumentError) + end + end + end + + %w(D d).each do |d| + it "parses the value as a decimal number if there's a leading 0#{d} and a base of 10" do + Integer("0#{d}1", 10).should == 1 + Integer("0#{d}10",10).should == 10 + end + + it "is a positive decimal number if there's a leading +0#{d} and a base of 10" do + Integer("+0#{d}1", 10).should == 1 + Integer("+0#{d}10", 10).should == 10 + end + + it "is a negative decimal number if there's a leading -0#{d} and a base of 10" do + Integer("-0#{d}1", 10).should == -1 + Integer("-0#{d}10", 10).should == -10 + end + + it "raises an ArgumentError if the number cannot be parsed as decimal and the base is 10" do + -> { 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 + -> { 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 + + 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 + +describe :kernel_Integer, shared: true do + it "raises an ArgumentError when the String contains digits out of range of radix 2" do + str = "23456789abcdefghijklmnopqrstuvwxyz" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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" + -> { @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 + -> { @object.send(@method, "{", 36) }.should raise_error(ArgumentError) + end +end + +describe "Kernel.Integer" do + it_behaves_like :kernel_Integer, :Integer_method, KernelSpecs + + # TODO: fix these specs + it_behaves_like :kernel_integer, :Integer, Kernel + it_behaves_like :kernel_integer_string, :Integer + + it_behaves_like :kernel_integer_string_base, :Integer + + it "is a public method" do + Kernel.Integer(10).should == 10 + end +end + +describe "Kernel#Integer" do + it_behaves_like :kernel_Integer, :Integer_function, KernelSpecs + + # TODO: fix these specs + it_behaves_like :kernel_integer, :Integer, Object.new + it_behaves_like :kernel_integer_string, :Integer + + it_behaves_like :kernel_integer_string_base, :Integer + + it "is a private method" do + Kernel.should have_private_instance_method(:Integer) + end +end diff --git a/spec/ruby/core/kernel/Rational_spec.rb b/spec/ruby/core/kernel/Rational_spec.rb new file mode 100644 index 0000000000..cc11a35451 --- /dev/null +++ b/spec/ruby/core/kernel/Rational_spec.rb @@ -0,0 +1,236 @@ +require_relative '../../spec_helper' +require_relative '../rational/fixtures/rational' + +describe "Kernel.Rational" do + 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 new file mode 100644 index 0000000000..7caec6eda5 --- /dev/null +++ b/spec/ruby/core/kernel/String_spec.rb @@ -0,0 +1,106 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe :kernel_String, shared: true do + it "converts nil to a String" do + @object.send(@method, nil).should == "" + end + + it "converts a Float to a String" do + @object.send(@method, 1.12).should == "1.12" + end + + it "converts a boolean to a String" do + @object.send(@method, false).should == "false" + @object.send(@method, true).should == "true" + end + + it "converts a constant to a String" do + @object.send(@method, Object).should == "Object" + end + + it "calls #to_s to convert an arbitrary object to a String" do + obj = mock('test') + obj.should_receive(:to_s).and_return("test") + + @object.send(@method, obj).should == "test" + end + + it "raises a TypeError if #to_s does not exist" do + obj = mock('to_s') + class << obj + undef_method :to_s + end + + -> { @object.send(@method, obj) }.should raise_error(TypeError) + end + + # #5158 + it "raises a TypeError if respond_to? returns false for #to_s" do + obj = mock("to_s") + class << obj + def respond_to?(meth, include_private=false) + meth == :to_s ? false : super + end + end + + -> { @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 + # cannot use a mock because of how RSpec affects #method_missing + obj = Object.new + class << obj + undef_method :to_s + def respond_to?(meth, include_private=false) + meth == :to_s ? true : super + end + end + + -> { @object.send(@method, obj) }.should raise_error(TypeError) + end + + it "calls #to_s if #respond_to?(:to_s) returns true" do + obj = mock('to_s') + class << obj + undef_method :to_s + def method_missing(meth, *args) + meth == :to_s ? "test" : super + end + end + + @object.send(@method, obj).should == "test" + end + + it "raises a TypeError if #to_s does not return a String" do + (obj = mock('123')).should_receive(:to_s).and_return(123) + -> { @object.send(@method, obj) }.should raise_error(TypeError) + end + + it "returns the same object if it is already a String" do + string = +"Hello" + string.should_not_receive(:to_s) + string2 = @object.send(@method, string) + string.should equal(string2) + end + + it "returns the same object if it is an instance of a String subclass" do + subklass = Class.new(String) + string = subklass.new("Hello") + string.should_not_receive(:to_s) + string2 = @object.send(@method, string) + string.should equal(string2) + end +end + +describe "Kernel.String" do + it_behaves_like :kernel_String, :String, Kernel +end + +describe "Kernel#String" do + it_behaves_like :kernel_String, :String, Object.new + + it "is a private method" do + Kernel.should have_private_instance_method(:String) + end +end diff --git a/spec/ruby/core/kernel/__callee___spec.rb b/spec/ruby/core/kernel/__callee___spec.rb new file mode 100644 index 0000000000..3059ea8b57 --- /dev/null +++ b/spec/ruby/core/kernel/__callee___spec.rb @@ -0,0 +1,48 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/__callee__' + +describe "Kernel.__callee__" do + it "returns the current method, even when aliased" do + KernelSpecs::CalleeTest.new.f.should == :f + end + + it "returns the aliased name when aliased method" do + KernelSpecs::CalleeTest.new.g.should == :g + end + + it "returns the caller from blocks too" do + KernelSpecs::CalleeTest.new.in_block.should == [:in_block, :in_block] + end + + it "returns the caller from define_method too" do + KernelSpecs::CalleeTest.new.dm.should == :dm + end + + it "returns the caller from block inside define_method too" do + KernelSpecs::CalleeTest.new.dm_block.should == [:dm_block, :dm_block] + end + + it "returns method name even from send" do + KernelSpecs::CalleeTest.new.from_send.should == :from_send + end + + it "returns method name even from eval" do + KernelSpecs::CalleeTest.new.from_eval.should == :from_eval + end + + it "returns nil from inside a class body" do + KernelSpecs::CalleeTest.new.from_class_body.should == nil + end + + it "returns nil when not called from a method" do + __callee__.should == nil + end + + it "returns the caller from a define_method called from the same class" do + c = Class.new do + define_method(:f) { 1.times{ break __callee__ } } + def g; f end + end + c.new.g.should == :f + end +end diff --git a/spec/ruby/core/kernel/__dir___spec.rb b/spec/ruby/core/kernel/__dir___spec.rb new file mode 100644 index 0000000000..242adbf48b --- /dev/null +++ b/spec/ruby/core/kernel/__dir___spec.rb @@ -0,0 +1,27 @@ +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 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 new file mode 100644 index 0000000000..578d25640d --- /dev/null +++ b/spec/ruby/core/kernel/__method___spec.rb @@ -0,0 +1,40 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/__method__' + +describe "Kernel.__method__" do + it "returns the current method, even when aliased" do + KernelSpecs::MethodTest.new.f.should == :f + end + + it "returns the original name when aliased method" do + KernelSpecs::MethodTest.new.g.should == :f + end + + it "returns the caller from blocks too" do + KernelSpecs::MethodTest.new.in_block.should == [:in_block, :in_block] + end + + it "returns the caller from define_method too" do + KernelSpecs::MethodTest.new.dm.should == :dm + end + + it "returns the caller from block inside define_method too" do + KernelSpecs::MethodTest.new.dm_block.should == [:dm_block, :dm_block] + end + + it "returns method name even from send" do + KernelSpecs::MethodTest.new.from_send.should == :from_send + end + + it "returns method name even from eval" do + KernelSpecs::MethodTest.new.from_eval.should == :from_eval + end + + it "returns nil from inside a class body" do + KernelSpecs::MethodTest.new.from_class_body.should == nil + end + + it "returns nil when not called from a method" do + __method__.should == nil + end +end diff --git a/spec/ruby/core/kernel/abort_spec.rb b/spec/ruby/core/kernel/abort_spec.rb new file mode 100644 index 0000000000..f8152718c5 --- /dev/null +++ b/spec/ruby/core/kernel/abort_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../shared/process/abort' + +describe "Kernel#abort" do + it "is a private method" do + Kernel.should have_private_instance_method(:abort) + end + + it_behaves_like :process_abort, :abort, KernelSpecs::Method.new +end + +describe "Kernel.abort" do + it_behaves_like :process_abort, :abort, Kernel +end diff --git a/spec/ruby/core/kernel/at_exit_spec.rb b/spec/ruby/core/kernel/at_exit_spec.rb new file mode 100644 index 0000000000..ebd9a71d15 --- /dev/null +++ b/spec/ruby/core/kernel/at_exit_spec.rb @@ -0,0 +1,19 @@ +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 "raises ArgumentError if called without a block" do + -> { at_exit }.should raise_error(ArgumentError, "called without a block") + end +end + +describe "Kernel#at_exit" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/kernel/autoload_spec.rb b/spec/ruby/core/kernel/autoload_spec.rb new file mode 100644 index 0000000000..5edb70541d --- /dev/null +++ b/spec/ruby/core/kernel/autoload_spec.rb @@ -0,0 +1,178 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +# These specs only illustrate the basic autoload cases +# and where toplevel autoload behaves differently from +# Module#autoload. See those specs for more examples. + +autoload :KSAutoloadA, "autoload_a.rb" +autoload :KSAutoloadB, fixture(__FILE__, "autoload_b.rb") +define_autoload_KSAutoloadCallsRequire = -> { + autoload :KSAutoloadCallsRequire, "main_autoload_not_exist.rb" +} + +def check_autoload(const) + autoload? const +end + +describe "Kernel#autoload" do + before :each do + @loaded_features = $".dup + end + + after :each do + $".replace @loaded_features + end + + it "is a private method" do + Kernel.should have_private_instance_method(:autoload) + end + + it "registers a file to load the first time the named constant is accessed" do + Object.autoload?(:KSAutoloadA).should == "autoload_a.rb" + end + + it "registers a file to load the first time the named constant is accessed" do + check_autoload(:KSAutoloadA).should == "autoload_a.rb" + end + + it "sets the autoload constant in Object's constant table" do + Object.should have_constant(:KSAutoloadA) + end + + it "loads the file when the constant is accessed" do + KSAutoloadB.loaded.should == :ksautoload_b + end + + 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 + Object.new.instance_eval do + autoload :KSAutoloadD, fixture(__FILE__, "autoload_d.rb") + KSAutoloadD.loaded.should == :ksautoload_d + 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 == "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 + +describe "Kernel#autoload?" do + it "is a private method" do + Kernel.should have_private_instance_method(:autoload?) + end + + it "returns the name of the file that will be autoloaded" do + check_autoload(:KSAutoloadA).should == "autoload_a.rb" + end + + it "returns nil if no file has been registered for a constant" do + check_autoload(:Manualload).should be_nil + end +end + +Kernel.autoload :KSAutoloadBB, "no_autoload.rb" + +describe "Kernel.autoload" do + before :all do + @non_existent = fixture __FILE__, "no_autoload.rb" + end + + before :each do + @loaded_features = $".dup + + ScratchPad.clear + end + + after :each do + $".replace @loaded_features + end + + it "registers a file to load the first time the toplevel constant is accessed" do + Kernel.autoload :KSAutoloadAA, @non_existent + Kernel.autoload?(:KSAutoloadAA).should == @non_existent + end + + it "sets the autoload constant in Object's constant table" do + Object.should have_constant(:KSAutoloadBB) + end + + it "calls #to_path on non-String filenames" do + p = mock('path') + 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 + it "returns the name of the file that will be autoloaded" do + Kernel.autoload :KSAutoload, "autoload.rb" + Kernel.autoload?(:KSAutoload).should == "autoload.rb" + end + + it "returns nil if no file has been registered for a constant" do + Kernel.autoload?(:Manualload).should be_nil + end +end diff --git a/spec/ruby/core/kernel/backtick_spec.rb b/spec/ruby/core/kernel/backtick_spec.rb new file mode 100644 index 0000000000..834d5636c1 --- /dev/null +++ b/spec/ruby/core/kernel/backtick_spec.rb @@ -0,0 +1,84 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#`" do + before :each do + @original_external = Encoding.default_external + end + + after :each do + Encoding.default_external = @original_external + end + + it "is a private method" do + Kernel.should have_private_instance_method(:`) + end + + it "returns the standard output of the executed sub-process" do + ip = 'world' + `echo disc #{ip}`.should == "disc world\n" + end + + it "lets the standard error stream pass through to the inherited stderr" do + cmd = ruby_cmd('STDERR.print "error stream"') + -> { + `#{cmd}`.should == "" + }.should output_to_fd("error stream", STDERR) + end + + it "produces a String in the default external encoding" do + Encoding.default_external = Encoding::SHIFT_JIS + `echo disc`.encoding.should equal(Encoding::SHIFT_JIS) + end + + it "raises an Errno::ENOENT if the command is not executable" do + -> { `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) + $?.should_not.stopped? + $?.should.exited? + $?.exitstatus.should == 0 + $?.should.success? + `echo disc #{ip}; exit 99` + $?.should be_kind_of(Process::Status) + $?.should_not.stopped? + $?.should.exited? + $?.exitstatus.should == 99 + $?.should_not.success? + end + end + + platform_is :windows do + it "sets $? to the exit status of the executed sub-process" do + ip = 'world' + `echo disc #{ip}` + $?.should be_kind_of(Process::Status) + $?.should_not.stopped? + $?.should.exited? + $?.exitstatus.should == 0 + $?.should.success? + `echo disc #{ip}& exit 99` + $?.should be_kind_of(Process::Status) + $?.should_not.stopped? + $?.should.exited? + $?.exitstatus.should == 99 + $?.should_not.success? + end + end +end + +describe "Kernel.`" do + it "tries to convert the given argument to String using #to_str" do + (obj = mock('echo test')).should_receive(:to_str).and_return("echo test") + Kernel.`(obj).should == "test\n" #` fix vim syntax highlighting + end +end diff --git a/spec/ruby/core/kernel/binding_spec.rb b/spec/ruby/core/kernel/binding_spec.rb new file mode 100644 index 0000000000..f1c9c6ec9f --- /dev/null +++ b/spec/ruby/core/kernel/binding_spec.rb @@ -0,0 +1,51 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel.binding" do + it "returns a binding for the caller" do + Kernel.binding.eval("self").should == self + end +end + +describe "Kernel#binding" do + it "is a private method" do + Kernel.should have_private_instance_method(:binding) + end + + before :each do + @b1 = KernelSpecs::Binding.new(99).get_binding + ScratchPad.clear + end + + it "returns a Binding object" do + @b1.kind_of?(Binding).should == true + end + + it "encapsulates the execution context properly" do + eval("@secret", @b1).should == 100 + eval("a", @b1).should == true + eval("b", @b1).should == true + eval("@@super_secret", @b1).should == "password" + + eval("square(2)", @b1).should == 4 + eval("self.square(2)", @b1).should == 4 + + eval("a = false", @b1) + eval("a", @b1).should == false + end + + it "raises a NameError on undefined variable" do + -> { eval("a_fake_variable", @b1) }.should raise_error(NameError) + end + + it "uses the closure's self as self in the binding" do + m = mock(:whatever) + eval('self', m.send(:binding)).should == self + end + + it "uses the class as self in a Class.new block" do + m = mock(:whatever) + cls = Class.new { ScratchPad.record eval('self', m.send(:binding)) } + ScratchPad.recorded.should == cls + end +end diff --git a/spec/ruby/core/kernel/block_given_spec.rb b/spec/ruby/core/kernel/block_given_spec.rb new file mode 100644 index 0000000000..aece4c821d --- /dev/null +++ b/spec/ruby/core/kernel/block_given_spec.rb @@ -0,0 +1,43 @@ +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 + +describe "Kernel#block_given?" do + it_behaves_like :kernel_block_given, :block_given?, KernelSpecs::BlockGiven + + it "returns false outside of a method" do + block_given?.should == false + end + + it "is a private method" do + Kernel.should have_private_instance_method(:block_given?) + end +end + +describe "Kernel.block_given?" do + it_behaves_like :kernel_block_given, :block_given?, KernelSpecs::KernelBlockGiven +end + +describe "self.send(:block_given?)" do + it_behaves_like :kernel_block_given, :block_given?, KernelSpecs::SelfBlockGiven +end diff --git a/spec/ruby/core/kernel/caller_locations_spec.rb b/spec/ruby/core/kernel/caller_locations_spec.rb new file mode 100644 index 0000000000..a917dba504 --- /dev/null +++ b/spec/ruby/core/kernel/caller_locations_spec.rb @@ -0,0 +1,110 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/caller_locations' + +describe 'Kernel#caller_locations' do + it 'is a private method' do + Kernel.should have_private_instance_method(:caller_locations) + end + + it 'returns an Array of caller locations' do + 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.should.end_with?('mspec.rb') + end + + it 'returns an Array of caller locations using a custom limit' do + locations = KernelSpecs::CallerLocationsTest.locations(1, 1) + + 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 + + locations.each do |location| + 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 new file mode 100644 index 0000000000..7cd703de5a --- /dev/null +++ b/spec/ruby/core/kernel/caller_spec.rb @@ -0,0 +1,109 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/caller' + +describe 'Kernel#caller' do + it 'is a private method' do + Kernel.should have_private_instance_method(:caller) + end + + it 'returns an Array of caller locations' do + KernelSpecs::CallerTest.locations.should_not.empty? + end + + it 'returns an Array of caller locations using a custom offset' do + locations = KernelSpecs::CallerTest.locations(2) + + locations[0].should =~ %r{runner/mspec.rb} + end + + it 'returns an Array of caller locations using a custom limit' do + locations = KernelSpecs::CallerTest.locations(1, 1) + + locations.length.should == 1 + end + + it 'returns an Array of caller locations using a range' do + locations = KernelSpecs::CallerTest.locations(1..1) + + locations.length.should == 1 + end + + it 'returns the locations as String instances' do + locations = KernelSpecs::CallerTest.locations + line = __LINE__ - 1 + + 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 new file mode 100644 index 0000000000..b8d30960e8 --- /dev/null +++ b/spec/ruby/core/kernel/case_compare_spec.rb @@ -0,0 +1,135 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + + +module Specs + module Kernel + + class HasNone + end + + class HasOpEqual + def ==(other) + other.kind_of? HasOpEqual + end + end + + class HasEqual + def equal?(other) + false + end + end + + class HasOppoOpEqual + def ==(other) + false + end + + def equal?(other) + false + end + end + end +end + + +describe "Kernel#=== for a class with default #== and #equal?" do + before :each do + @o1 = Specs::Kernel::HasNone.new + @o2 = @o1.dup + end + + it "returns true if other object has same object id" do + @o1.object_id.should == @o1.object_id + (@o1 === @o1).should == true + end + + it "returns false if other object does not have same object id" do + @o1.object_id.should_not == @o2.object_id + (@o1 === @o2).should == false + end +end + +describe "Kernel#=== for a class with #== overridden to consider other object's class" do + before :each do + @o = Object.new + @o1 = Specs::Kernel::HasOpEqual.new + @o2 = @o1.dup + end + + it "returns true if #== returns true even if #equal? is false" do + @o1.should_not equal(@o2) + (@o1 == @o2).should == true + (@o1 === @o2).should == true + end + + it "returns true if #equal? returns true" do + @o1.should equal(@o1) + (@o1 === @o1).should == true + end + + it "returns false if neither #== nor #equal? returns true" do + @o1.should_not equal(@o) + (@o1 == @o).should == false + (@o1 === @o).should == false + end +end + +describe "Kernel#=== for a class with #equal? overridden to always be false" do + before :each do + @o = Object.new + @o1 = Specs::Kernel::HasEqual.new + @o2 = @o1.dup + end + + it "returns true if #== returns true even if #equal? is false" do + @o1.should_not equal(@o1) + (@o1 == @o1).should == true + (@o1 === @o1).should == true + end + + it "returns false if neither #== nor #equal? returns true" do + @o1.should_not equal(@o) + (@o1 == @o).should == false + (@o1 === @o).should == false + end +end + +describe "Kernel#=== for a class with #== and #equal? overridden to always be false" do + before :each do + @o = Object.new + @o1 = Specs::Kernel::HasOppoOpEqual.new + @o2 = @o1.dup + end + + it "returns true if the object id is the same even if both #== and #equal? return false" do + @o1.object_id.should == @o1.object_id + + @o1.should_not equal(@o1) + (@o1 == @o1).should == false + + (@o1 === @o1).should == true + end + + it "returns false if the object id is not the same and both #== and #equal? return false" do + @o1.object_id.should_not == @o2.object_id + + @o1.should_not equal(@o2) + (@o1 == @o2).should == false + + (@o1 === @o2).should == false + end +end + +describe "Kernel#=== does not call #object_id nor #equal?" do + before :each do + @o1 = Object.new + @o1.should_not_receive(:object_id) + @o1.should_not_receive(:equal?) + end + + it "but still returns true for #== or #=== on the same object" do + (@o1 == @o1).should == true + (@o1 === @o1).should == true + end +end diff --git a/spec/ruby/core/kernel/catch_spec.rb b/spec/ruby/core/kernel/catch_spec.rb new file mode 100644 index 0000000000..9f59d3b384 --- /dev/null +++ b/spec/ruby/core/kernel/catch_spec.rb @@ -0,0 +1,127 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel.catch" do + before :each do + ScratchPad.clear + end + + it "executes its block and catches a thrown value matching its argument" do + catch :thrown_key do + ScratchPad.record :catch_block + throw :thrown_key + ScratchPad.record :throw_failed + end + ScratchPad.recorded.should == :catch_block + end + + it "returns the second value passed to throw" do + catch(:thrown_key) { throw :thrown_key, :catch_value }.should == :catch_value + end + + it "returns the last expression evaluated if throw was not called" do + catch(:thrown_key) { 1; :catch_block }.should == :catch_block + end + + it "passes the given symbol to its block" do + catch :thrown_key do |tag| + ScratchPad.record tag + end + ScratchPad.recorded.should == :thrown_key + end + + it "raises an ArgumentError if a Symbol is thrown for a String catch value" do + -> { catch("exit") { throw :exit } }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if a String with different identity is thrown" do + -> { catch("exit".dup) { throw "exit".dup } }.should raise_error(ArgumentError) + end + + it "catches a Symbol when thrown a matching Symbol" do + catch :thrown_key do + ScratchPad.record :catch_block + throw :thrown_key + end + ScratchPad.recorded.should == :catch_block + end + + it "catches a String when thrown a String with the same identity" do + key = "thrown_key" + catch key do + ScratchPad.record :catch_block + throw key + end + ScratchPad.recorded.should == :catch_block + end + + it "accepts an object as an argument" do + catch(Object.new) { :catch_block }.should == :catch_block + end + + it "yields an object when called without arguments" do + catch { |tag| tag }.should be_an_instance_of(Object) + end + + it "can be used even in a method different from where throw is called" do + class CatchSpecs + def self.throwing_method + throw :blah, :thrown_value + end + def self.catching_method + catch :blah do + throwing_method + end + end + end + CatchSpecs.catching_method.should == :thrown_value + end + + describe "when nested" do + before :each do + ScratchPad.record [] + end + + it "catches across invocation boundaries" do + catch :one do + ScratchPad << 1 + catch :two do + ScratchPad << 2 + catch :three do + ScratchPad << 3 + throw :one + ScratchPad << 4 + end + ScratchPad << 5 + end + ScratchPad << 6 + end + + ScratchPad.recorded.should == [1, 2, 3] + end + + it "catches in the nested invocation with the same key object" do + catch :thrown_key do + ScratchPad << 1 + catch :thrown_key do + ScratchPad << 2 + throw :thrown_key + ScratchPad << 3 + end + ScratchPad << 4 + end + + ScratchPad.recorded.should == [1, 2, 4] + end + end + + it "raises LocalJumpError if no block is given" do + -> { catch :blah }.should raise_error(LocalJumpError) + end +end + +describe "Kernel#catch" do + it "is a private method" do + Kernel.should have_private_instance_method(:catch) + end +end diff --git a/spec/ruby/core/kernel/chomp_spec.rb b/spec/ruby/core/kernel/chomp_spec.rb new file mode 100644 index 0000000000..d30e77c35a --- /dev/null +++ b/spec/ruby/core/kernel/chomp_spec.rb @@ -0,0 +1,65 @@ +# -*- encoding: utf-8 -*- +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe :kernel_chomp, shared: true do + it "removes the final newline of $_" do + KernelSpecs.chomp("abc\n", @method).should == "abc" + end + + it "removes the final carriage return of $_" do + KernelSpecs.chomp("abc\r", @method).should == "abc" + end + + it "removes the final carriage return, newline of $_" do + KernelSpecs.chomp("abc\r\n", @method).should == "abc" + end + + it "removes only the final newline of $_" do + KernelSpecs.chomp("abc\n\n", @method).should == "abc\n" + end + + it "removes the value of $/ from the end of $_" do + KernelSpecs.chomp("abcde", @method, "cde").should == "ab" + end +end + +describe :kernel_chomp_private, shared: true do + it "is a private method" do + KernelSpecs.has_private_method(@method).should be_true + end +end + +describe "Kernel.chomp" do + it_behaves_like :kernel_chomp, "Kernel.chomp" +end + +describe "Kernel#chomp" do + it_behaves_like :kernel_chomp, "chomp" + + it_behaves_like :kernel_chomp_private, :chomp +end + +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 +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 new file mode 100644 index 0000000000..9b91c011bc --- /dev/null +++ b/spec/ruby/core/kernel/chop_spec.rb @@ -0,0 +1,53 @@ +# -*- encoding: utf-8 -*- +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe :kernel_chop, shared: true do + it "removes the final character of $_" do + KernelSpecs.chop("abc", @method).should == "ab" + end + + it "removes the final carriage return, newline of $_" do + KernelSpecs.chop("abc\r\n", @method).should == "abc" + end +end + +describe :kernel_chop_private, shared: true do + it "is a private method" do + KernelSpecs.has_private_method(@method).should be_true + end +end + +describe "Kernel.chop" do + it_behaves_like :kernel_chop, "Kernel.chop" +end + +describe "Kernel#chop" do + it_behaves_like :kernel_chop_private, :chop + + it_behaves_like :kernel_chop, "chop" +end + +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 +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 new file mode 100644 index 0000000000..b1d9df1671 --- /dev/null +++ b/spec/ruby/core/kernel/class_spec.rb @@ -0,0 +1,26 @@ +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(Integer) + 3.14.class.should equal(Float) + :hello.class.should equal(Symbol) + "hello".class.should equal(String) + [1, 2].class.should equal(Array) + { 1 => 2 }.class.should equal(Hash) + end + + it "returns Class for a class" do + BasicObject.class.should equal(Class) + String.class.should equal(Class) + end + + it "returns the first non-singleton class" do + a = +"hello" + def a.my_singleton_method; end + a.class.should equal(String) + end +end diff --git a/spec/ruby/core/kernel/clone_spec.rb b/spec/ruby/core/kernel/clone_spec.rb new file mode 100644 index 0000000000..5adcbbe603 --- /dev/null +++ b/spec/ruby/core/kernel/clone_spec.rb @@ -0,0 +1,177 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/dup_clone' + +describe "Kernel#clone" do + it_behaves_like :kernel_dup_clone, :clone + + before :each do + ScratchPad.clear + @obj = KernelSpecs::Duplicate.new 1, :a + end + + it "calls #initialize_copy on the new instance" do + clone = @obj.clone + ScratchPad.recorded.should_not == @obj.object_id + ScratchPad.recorded.should == clone.object_id + end + + it "uses the internal allocator and does not call #allocate" do + klass = Class.new + instance = klass.new + + def klass.allocate + raise "allocate should not be called" + end + + clone = instance.clone + clone.class.should equal klass + end + + describe "with no arguments" do + it "copies frozen state from the original" do + o2 = @obj.clone + o2.should_not.frozen? + + @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 + + describe "with freeze: false" do + it 'does not freeze the copy if the original is frozen' do + @obj.freeze + @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 + + it "copies instance variables" do + clone = @obj.clone + clone.one.should == 1 + clone.two.should == :a + end + + it "copies singleton methods" do + def @obj.special() :the_one end + clone = @obj.clone + clone.special.should == :the_one + end + + it "copies modules included in the singleton class" do + class << @obj + include KernelSpecs::DuplicateM + end + + clone = @obj.clone + clone.repr.should == "KernelSpecs::Duplicate" + end + + it "copies constants defined in the singleton class" do + class << @obj + CLONE = :clone + end + + clone = @obj.clone + class << clone + CLONE.should == :clone + end + end + + it "replaces a singleton object's metaclass with a new copy with the same superclass" do + cls = Class.new do + def bar + ['a'] + end + end + + object = cls.new + object.define_singleton_method(:bar) do + ['b', *super()] + end + object.bar.should == ['b', 'a'] + + cloned = object.clone + + cloned.singleton_methods.should == [:bar] + + # bar should replace previous one + cloned.define_singleton_method(:bar) do + ['c', *super()] + end + cloned.bar.should == ['c', 'a'] + + # bar should be removed and call through to superclass + cloned.singleton_class.class_eval do + remove_method :bar + end + + cloned.bar.should == ['a'] + end +end diff --git a/spec/ruby/core/kernel/comparison_spec.rb b/spec/ruby/core/kernel/comparison_spec.rb new file mode 100644 index 0000000000..affdc5c00d --- /dev/null +++ b/spec/ruby/core/kernel/comparison_spec.rb @@ -0,0 +1,31 @@ +require_relative '../../spec_helper' + +describe "Kernel#<=>" do + it "returns 0 if self" do + obj = Object.new + obj.<=>(obj).should == 0 + end + + it "returns 0 if self is == to the argument" do + obj = mock('has ==') + obj.should_receive(:==).and_return(true) + obj.<=>(Object.new).should == 0 + end + + it "returns nil if self is eql? but not == to the argument" do + obj = mock('has eql?') + obj.should_not_receive(:eql?) + obj.<=>(Object.new).should be_nil + end + + it "returns nil if self.==(arg) returns nil" do + obj = mock('wrong ==') + obj.should_receive(:==).and_return(nil) + obj.<=>(Object.new).should be_nil + end + + it "returns nil if self is not == to the argument" do + obj = Object.new + obj.<=>(3.14).should be_nil + end +end diff --git a/spec/ruby/core/kernel/define_singleton_method_spec.rb b/spec/ruby/core/kernel/define_singleton_method_spec.rb new file mode 100644 index 0000000000..24acec84f5 --- /dev/null +++ b/spec/ruby/core/kernel/define_singleton_method_spec.rb @@ -0,0 +1,120 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#define_singleton_method" do + describe "when given an UnboundMethod" do + class DefineSingletonMethodSpecClass + MY_CONST = 42 + define_singleton_method(:another_test_method, self.method(:constants)) + end + + it "correctly calls the new method" do + klass = DefineSingletonMethodSpecClass + klass.another_test_method.should == klass.constants + end + + it "adds the new method to the methods list" do + DefineSingletonMethodSpecClass.should have_method(:another_test_method) + end + + it "defines any Child class method from any Parent's class methods" 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 + ->{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 + other = KernelSpecs::Parent.new + p = KernelSpecs::Parent.new + class << p + def singleton_method + :single + end + end + um = p.method(:singleton_method).unbind + ->{ other.send :define_singleton_method, :other_singleton_method, um }.should raise_error(TypeError) + end + + end + + 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, &-> { self }) + end + + o = DefineSingletonMethodSpecClass + o.block_test1.should == o + o.block_test2.should == o + end + + it "raises a TypeError when the given method is no Method/Proc" do + -> { + Class.new { define_singleton_method(:test, "self") } + }.should raise_error(TypeError) + + -> { + Class.new { define_singleton_method(:test, 1234) } + }.should raise_error(TypeError) + end + + it "defines a new singleton method for objects" do + obj = Object.new + obj.define_singleton_method(:test) { "world!" } + obj.test.should == "world!" + -> { + Object.new.test + }.should raise_error(NoMethodError) + end + + it "maintains the Proc's scope" do + class DefineMethodByProcClass + in_scope = true + method_proc = proc { in_scope } + + define_singleton_method(:proc_test, &method_proc) + end + + DefineMethodByProcClass.proc_test.should == true + end + + it "raises an ArgumentError when no block is given" do + obj = Object.new + -> { + obj.define_singleton_method(:test) + }.should raise_error(ArgumentError) + 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 + + 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 new file mode 100644 index 0000000000..9d429a9fac --- /dev/null +++ b/spec/ruby/core/kernel/display_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#display" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/kernel/dup_spec.rb b/spec/ruby/core/kernel/dup_spec.rb new file mode 100644 index 0000000000..70198abdb7 --- /dev/null +++ b/spec/ruby/core/kernel/dup_spec.rb @@ -0,0 +1,67 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/dup_clone' + +describe "Kernel#dup" do + it_behaves_like :kernel_dup_clone, :dup + + before :each do + ScratchPad.clear + @obj = KernelSpecs::Duplicate.new 1, :a + end + + it "calls #initialize_copy on the new instance" do + dup = @obj.dup + ScratchPad.recorded.should_not == @obj.object_id + ScratchPad.recorded.should == dup.object_id + end + + it "uses the internal allocator and does not call #allocate" do + klass = Class.new + instance = klass.new + + def klass.allocate + raise "allocate should not be called" + end + + dup = instance.dup + dup.class.should equal klass + end + + it "does not copy frozen state from the original" do + @obj.freeze + dup = @obj.dup + + dup.should_not.frozen? + end + + it "copies instance variables" do + dup = @obj.dup + dup.one.should == 1 + dup.two.should == :a + end + + it "does not copy singleton methods" do + def @obj.special() :the_one end + dup = @obj.dup + -> { dup.special }.should raise_error(NameError) + end + + it "does not copy modules included in the singleton class" do + class << @obj + include KernelSpecs::DuplicateM + end + + dup = @obj.dup + -> { dup.repr }.should raise_error(NameError) + end + + it "does not copy constants defined in the singleton class" do + class << @obj + CLONE = :clone + end + + dup = @obj.dup + -> { 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 new file mode 100644 index 0000000000..0092e20468 --- /dev/null +++ b/spec/ruby/core/kernel/enum_for_spec.rb @@ -0,0 +1,5 @@ +require_relative '../../spec_helper' + +describe "Kernel#enum_for" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/kernel/eql_spec.rb b/spec/ruby/core/kernel/eql_spec.rb new file mode 100644 index 0000000000..e62a601a79 --- /dev/null +++ b/spec/ruby/core/kernel/eql_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' +require_relative '../../shared/kernel/equal' + +describe "Kernel#eql?" do + it "is a public instance method" do + Kernel.should have_public_instance_method(:eql?) + end + + 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 new file mode 100644 index 0000000000..2be151263d --- /dev/null +++ b/spec/ruby/core/kernel/equal_value_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#==" do + it "returns true only if obj and other are the same object" do + o1 = mock('o1') + o2 = mock('o2') + (o1 == o1).should == true + (o2 == o2).should == true + (o1 == o2).should == false + (nil == nil).should == true + (o1 == nil).should == false + (nil == o2).should == false + end +end diff --git a/spec/ruby/core/kernel/eval_spec.rb b/spec/ruby/core/kernel/eval_spec.rb new file mode 100644 index 0000000000..e027294347 --- /dev/null +++ b/spec/ruby/core/kernel/eval_spec.rb @@ -0,0 +1,570 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +EvalSpecs::A.new.c + +describe "Kernel#eval" do + it "is a private method" do + Kernel.should have_private_instance_method(:eval) + end + + it "is a module function" do + Kernel.respond_to?(:eval).should == true + end + + it "evaluates the code within" do + eval("2 + 3").should == 5 + end + + it "coerces an object to string" do + eval(EvalSpecs::CoercedObject.new).should == 5 + end + + it "evaluates within the scope of the eval" do + EvalSpecs::A::B.name.should == "EvalSpecs::A::B" + end + + it "evaluates such that constants are scoped to the class of the eval" do + EvalSpecs::A::C.name.should == "EvalSpecs::A::C" + end + + it "finds a local in an enclosing scope" do + a = 1 + eval("a").should == 1 + end + + it "updates a local in an enclosing scope" do + a = 1 + eval("a = 2") + a.should == 2 + end + + it "updates a local in a surrounding block scope" do + EvalSpecs.new.f do + a = 1 + eval("a = 2") + a.should == 2 + end + end + + it "updates a local in a scope above a surrounding block scope" do + a = 1 + EvalSpecs.new.f do + eval("a = 2") + a.should == 2 + end + a.should == 2 + end + + it "updates a local in a scope above when modified in a nested block scope" do + a = 1 + es = EvalSpecs.new + eval("es.f { es.f { a = 2 } }") + a.should == 2 + end + + it "finds locals in a nested eval" do + eval('test = 10; eval("test")').should == 10 + end + + it "does not share locals across eval scopes" do + code = fixture __FILE__, "eval_locals.rb" + ruby_exe(code).chomp.should == "NameError" + end + + it "doesn't accept a Proc object as a binding" do + x = 1 + bind = proc {} + + -> { eval("x", bind) }.should raise_error(TypeError) + end + + it "does not make Proc locals visible to evaluated code" do + bind = proc { inner = 4 } + -> { eval("inner", bind.binding) }.should raise_error(NameError) + end + + # REWRITE ME: This obscures the real behavior of where locals are stored + # in eval bindings. + it "allows a binding to be captured inside an eval" do + outer_binding = binding + level1 = eval("binding", outer_binding) + level2 = eval("binding", level1) + + eval("x = 2", outer_binding) + eval("y = 3", level1) + + eval("w=1", outer_binding) + eval("w", outer_binding).should == 1 + eval("w=1", level1).should == 1 + eval("w", level1).should == 1 + eval("w=1", level2).should == 1 + eval("w", level2).should == 1 + + eval("x", outer_binding).should == 2 + eval("x=2", level1) + eval("x", level1).should == 2 + eval("x=2", level2) + eval("x", level2).should == 2 + + eval("y=3", outer_binding) + eval("y", outer_binding).should == 3 + eval("y=3", level1) + eval("y", level1).should == 3 + eval("y=3", level2) + eval("y", level2).should == 3 + end + + it "uses the same scope for local variables when given the same binding" do + outer_binding = binding + + eval("if false; a = 1; end", outer_binding) + eval("a", outer_binding).should be_nil + end + + it "allows creating a new class in a binding" do + bind = proc {} + eval("class EvalBindingProcA; end; EvalBindingProcA.name", bind.binding).should =~ /EvalBindingProcA$/ + end + + it "allows creating a new class in a binding created by #eval" do + bind = eval "binding" + eval("class EvalBindingA; end; EvalBindingA.name", bind).should =~ /EvalBindingA$/ + end + + it "includes file and line information in syntax error" do + expected = 'speccing.rb' + -> { + eval('if true', TOPLEVEL_BINDING, expected) + }.should raise_error(SyntaxError) { |e| + e.message.should =~ /#{expected}:1:.+/ + } + end + + it "evaluates string with given filename and negative linenumber" do + expected_file = 'speccing.rb' + -> { + eval('if true', TOPLEVEL_BINDING, expected_file, -100) + }.should raise_error(SyntaxError) { |e| + e.message.should =~ /#{expected_file}:-100:.+/ + } + end + + it "sets constants at the toplevel from inside a block" do + # The class Object bit is needed to workaround some mspec oddness + class Object + [1].each { eval "Const = 1"} + Const.should == 1 + remove_const :Const + end + end + + 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 + second_time = EvalSpecs.call_eval + + # This bug is seen by calling the method twice and comparing the values + # of __FILE__ each time. If the bug is present, calling eval will set the + # value of __FILE__ to the eval's "filename" argument. + + second_time.should_not == "(eval)" + first_time.should == second_time + end + + it "can be aliased" do + alias aliased_eval eval + x = 2 + aliased_eval('x += 40') + x.should == 42 + end + + # See http://jira.codehaus.org/browse/JRUBY-5163 + it "uses the receiver as self inside the eval" do + eval("self").should equal(self) + Kernel.eval("self").should equal(Kernel) + end + + it "does not pass the block to the method being eval'ed" do + -> { + eval('KernelSpecs::EvalTest.call_yield') { "content" } + }.should raise_error(LocalJumpError) + end + + it "returns from the scope calling #eval when evaluating 'return'" do + -> { 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 + code = fixture __FILE__, "eval_return_with_lambda.rb" + ruby_exe(code).chomp.should == "a,b,c,eval,f" + end + + it "raises a LocalJumpError if there is no lambda-style closure in the chain" 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 new file mode 100644 index 0000000000..3d9520ad67 --- /dev/null +++ b/spec/ruby/core/kernel/exec_spec.rb @@ -0,0 +1,18 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#exec" do + it "is a private method" do + Kernel.should have_private_instance_method(:exec) + end + + it "runs the specified command, replacing current process" do + 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"').should == "hello\n" + end +end diff --git a/spec/ruby/core/kernel/exit_spec.rb b/spec/ruby/core/kernel/exit_spec.rb new file mode 100644 index 0000000000..93cec3fee5 --- /dev/null +++ b/spec/ruby/core/kernel/exit_spec.rb @@ -0,0 +1,27 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../shared/process/exit' + +describe "Kernel#exit" do + it "is a private method" do + Kernel.should have_private_instance_method(:exit) + end + + 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!) + end + + it_behaves_like :process_exit!, :exit!, "self" +end + +describe "Kernel.exit!" do + 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 new file mode 100644 index 0000000000..6342d8cae1 --- /dev/null +++ b/spec/ruby/core/kernel/extend_spec.rb @@ -0,0 +1,91 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +module KernelSpecs::M + def self.extend_object(o) + ScratchPad << "extend_object" + super + end + + def self.extended(o) + ScratchPad << "extended" + super + end + + def self.append_features(o) + ScratchPad << "append_features" + super + end +end + +describe "Kernel#extend" do + before :each do + ScratchPad.record [] + end + + it "requires multiple arguments" do + Object.new.method(:extend).arity.should < 0 + end + + it "calls extend_object on argument" do + o = mock('o') + o.extend KernelSpecs::M + ScratchPad.recorded.include?("extend_object").should == true + end + + it "does not calls append_features on arguments metaclass" do + o = mock('o') + o.extend KernelSpecs::M + ScratchPad.recorded.include?("append_features").should == false + end + + it "calls extended on argument" do + o = mock('o') + o.extend KernelSpecs::M + ScratchPad.recorded.include?("extended").should == true + end + + it "makes the class a kind_of? the argument" do + c = Class.new do + extend KernelSpecs::M + end + (c.kind_of? KernelSpecs::M).should == true + end + + it "raises an ArgumentError when no arguments given" do + -> { 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 + -> { o.extend(klass) }.should raise_error(TypeError) + end + + describe "on frozen instance" do + before :each do + @frozen = Object.new.freeze + @module = KernelSpecs::M + end + + it "raises an ArgumentError when no arguments given" do + -> { @frozen.extend }.should raise_error(ArgumentError) + end + + 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 new file mode 100644 index 0000000000..fab622037e --- /dev/null +++ b/spec/ruby/core/kernel/fail_spec.rb @@ -0,0 +1,42 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#fail" do + it "is a private method" do + Kernel.should have_private_instance_method(:fail) + end + + it "raises a RuntimeError" do + -> { fail }.should raise_error(RuntimeError) + end + + it "accepts an Object with an exception method returning an Exception" do + obj = Object.new + def obj.exception(msg) + StandardError.new msg + end + -> { fail obj, "..." }.should raise_error(StandardError, "...") + end + + it "instantiates the specified exception class" do + error_class = Class.new(RuntimeError) + -> { fail error_class }.should raise_error(error_class) + end + + it "uses the specified message" do + -> { + begin + fail "the duck is not irish." + rescue => e + e.message.should == "the duck is not irish." + raise + else + raise Exception + end + }.should raise_error(RuntimeError) + end +end + +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/__callee__.rb b/spec/ruby/core/kernel/fixtures/__callee__.rb new file mode 100644 index 0000000000..7138dbc5aa --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/__callee__.rb @@ -0,0 +1,34 @@ +module KernelSpecs + class CalleeTest + def f + __callee__ + end + + alias_method :g, :f + + def in_block + (1..2).map { __callee__ } + end + + define_method(:dm) do + __callee__ + end + + define_method(:dm_block) do + (1..2).map { __callee__ } + end + + def from_send + send "__callee__" + end + + def from_eval + eval "__callee__" + end + + @@method = __callee__ + def from_class_body + @@method + end + 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/__method__.rb b/spec/ruby/core/kernel/fixtures/__method__.rb new file mode 100644 index 0000000000..9300366b37 --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/__method__.rb @@ -0,0 +1,34 @@ +module KernelSpecs + class MethodTest + def f + __method__ + end + + alias_method :g, :f + + def in_block + (1..2).map { __method__ } + end + + define_method(:dm) do + __method__ + end + + define_method(:dm_block) do + (1..2).map { __method__ } + end + + def from_send + send "__method__" + end + + def from_eval + eval "__method__" + end + + @@method = __method__ + def from_class_body + @@method + end + end +end diff --git a/spec/ruby/core/kernel/fixtures/autoload_b.rb b/spec/ruby/core/kernel/fixtures/autoload_b.rb new file mode 100644 index 0000000000..e8be221ec7 --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/autoload_b.rb @@ -0,0 +1,5 @@ +module KSAutoloadB + def self.loaded + :ksautoload_b + end +end diff --git a/spec/ruby/core/kernel/fixtures/autoload_d.rb b/spec/ruby/core/kernel/fixtures/autoload_d.rb new file mode 100644 index 0000000000..552cb5e82c --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/autoload_d.rb @@ -0,0 +1,5 @@ +module KSAutoloadD + def self.loaded + :ksautoload_d + 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/autoload_frozen.rb b/spec/ruby/core/kernel/fixtures/autoload_frozen.rb new file mode 100644 index 0000000000..e9dc42912b --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/autoload_frozen.rb @@ -0,0 +1,7 @@ +Object.freeze + +begin + autoload :ANY_CONSTANT, "no_autoload.rb" +rescue Exception => e + print e.class, " - ", defined?(ANY_CONSTANT).inspect +end diff --git a/spec/ruby/core/kernel/fixtures/caller.rb b/spec/ruby/core/kernel/fixtures/caller.rb new file mode 100644 index 0000000000..ae3e13e9c9 --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/caller.rb @@ -0,0 +1,7 @@ +module KernelSpecs + class CallerTest + def self.locations(*args) + caller(*args) + 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/caller_locations.rb b/spec/ruby/core/kernel/fixtures/caller_locations.rb new file mode 100644 index 0000000000..cc4e04d38f --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/caller_locations.rb @@ -0,0 +1,7 @@ +module KernelSpecs + class CallerLocationsTest + def self.locations(*args) + caller_locations(*args) + end + end +end diff --git a/spec/ruby/core/kernel/fixtures/chomp.rb b/spec/ruby/core/kernel/fixtures/chomp.rb new file mode 100644 index 0000000000..f08dbadce5 --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/chomp.rb @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- + +$_ = "あれ\r\n" +print Kernel.chomp diff --git a/spec/ruby/core/kernel/fixtures/chomp_f.rb b/spec/ruby/core/kernel/fixtures/chomp_f.rb new file mode 100644 index 0000000000..3c22cb21e7 --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/chomp_f.rb @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- + +$_ = "あれ\r\n" +print chomp diff --git a/spec/ruby/core/kernel/fixtures/chop.rb b/spec/ruby/core/kernel/fixtures/chop.rb new file mode 100644 index 0000000000..dfd0626723 --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/chop.rb @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- + +$_ = "あれ" +print Kernel.chop diff --git a/spec/ruby/core/kernel/fixtures/chop_f.rb b/spec/ruby/core/kernel/fixtures/chop_f.rb new file mode 100644 index 0000000000..4ec89eb9ec --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/chop_f.rb @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- + +$_ = "あれ" +print chop diff --git a/spec/ruby/core/kernel/fixtures/classes.rb b/spec/ruby/core/kernel/fixtures/classes.rb new file mode 100644 index 0000000000..0e2b81988f --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/classes.rb @@ -0,0 +1,558 @@ +module KernelSpecs + def self.Array_function(arg) + Array(arg) + end + + def self.Array_method(arg) + Kernel.Array(arg) + end + + def self.Hash_function(arg) + Hash(arg) + end + + def self.Hash_method(arg) + Kernel.Hash(arg) + end + + def self.Integer_function(arg) + Integer(arg) + end + + def self.Integer_method(arg) + Kernel.Integer(arg) + end + + def self.putc_function(arg) + putc arg + end + + def self.putc_method(arg) + Kernel.putc arg + end + + def self.has_private_method(name) + IO.popen([*ruby_exe, "-n", "-e", "print Kernel.private_method_defined?(#{name.inspect})"], "r+") do |io| + io.puts + io.close_write + io.read + end == "true" + end + + def self.chop(str, method) + IO.popen([*ruby_exe, "-n", "-e", "$_ = #{str.inspect}; #{method}; print $_"], "r+") do |io| + io.puts + io.close_write + io.read + end + end + + def self.chomp(str, method, sep="\n") + code = "$_ = #{str.inspect}; $/ = #{sep.inspect}; #{method}; print $_" + IO.popen([*ruby_exe, "-W0", "-n", "-e", code], "r+") do |io| + io.puts + io.close_write + io.read + end + end + + def self.run_with_dash_n(file) + IO.popen([*ruby_exe, "-n", file], "r+") do |io| + io.puts + io.close_write + io.read + end + end + + # kind_of?, is_a?, instance_of? + module SomeOtherModule; end + module AncestorModule; end + module MyModule; end + module MyPrependedModule; end + module MyExtensionModule; end + + class AncestorClass < String + include AncestorModule + end + + class InstanceClass < AncestorClass + include MyModule + end + + class KindaClass < AncestorClass + include MyModule + prepend MyPrependedModule + + def initialize + self.extend MyExtensionModule + end + end + + class Method + public :abort, :exit, :exit!, :fork, :system + end + + class Methods + + module MetaclassMethods + def peekaboo + end + + protected + + def nopeeking + end + + private + + def shoo + end + end + + def self.ichi; end + def ni; end + class << self + def san; end + end + + private + + def self.shi; end + def juu_shi; end + + class << self + def roku; end + + private + + def shichi; end + end + + protected + + def self.hachi; end + def ku; end + + class << self + def juu; end + + protected + + def juu_ichi; end + end + + public + + def self.juu_ni; end + def juu_san; end + end + + class PrivateSup + def public_in_sub + end + + private :public_in_sub + end + + class PublicSub < PrivateSup + def public_in_sub + end + end + + class A + # There is Kernel#public_method, so we don't want this one to clash + def pub_method; :public_method; end + + def undefed_method; :undefed_method; end + undef_method :undefed_method + + protected + def protected_method; :protected_method; end + + private + def private_method; :private_method; end + + public + define_method(:defined_method) { :defined } + end + + class B < A + alias aliased_pub_method pub_method + end + + class VisibilityChange + class << self + private :new + end + end + + class Binding + @@super_secret = "password" + + def initialize(n) + @secret = n + end + + def square(n) + n * n + end + + def get_binding + a = true + @bind = binding + + # Add/Change stuff + b = true + @secret += 1 + + @bind + end + end + + + module BlockGiven + def self.accept_block + block_given? + end + + def self.accept_block_as_argument(&block) + 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 + + module SelfBlockGiven + def self.accept_block + self.send(:block_given?) + end + + def self.accept_block_as_argument(&block) + 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 + + module KernelBlockGiven + def self.accept_block + Kernel.block_given? + end + + def self.accept_block_as_argument(&block) + 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 + + class EvalTest + def self.eval_yield_with_binding + eval("yield", binding) + end + def self.call_yield + yield + end + end + + module DuplicateM + def repr + self.class.name.to_s + end + end + + class Duplicate + attr_accessor :one, :two + + def initialize(one, two) + @one = one + @two = two + end + + 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 + end + end + + class CloneFreeze + def initialize_clone(other, **kwargs) + ScratchPad.record([other, kwargs]) + end + end + + class Dup + def initialize_dup(other) + ScratchPad.record other.object_id + end + end + + module ParentMixin + def parent_mixin_method; end + end + + class Parent + include ParentMixin + def parent_method; end + def another_parent_method; end + def self.parent_class_method; :foo; end + end + + class Child < Parent + undef_method :parent_method + end + + class Grandchild < Child + undef_method :parent_mixin_method + end + + # for testing lambda + class Lambda + def outer + inner + end + + def mp(&b); b; end + + def inner + b = mp { return :good } + + pr = -> x { x.call } + + pr.call(b) + + # We shouldn't be here, b should have unwinded through + return :bad + 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: #{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 + + class InstanceVariable + def initialize + @greeting = "hello" + end + end + + class PrivateToAry + private + + def to_ary + [1, 2] + end + + def to_a + [3, 4] + end + end + + class PrivateToA + private + + def to_a + [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 + class A + eval "class B; end" + def c + eval "class C; end" + end + end + + class CoercedObject + def to_str + '2 + 3' + end + + def hash + nil + end + end + + def f + yield + end + + def self.call_eval + f = __FILE__ + eval "true", binding, "(eval)", 1 + return f + end +end diff --git a/spec/ruby/core/kernel/fixtures/eval_locals.rb b/spec/ruby/core/kernel/fixtures/eval_locals.rb new file mode 100644 index 0000000000..ca8b381806 --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/eval_locals.rb @@ -0,0 +1,6 @@ +begin + eval("a = 2") + eval("p a") +rescue Object => e + puts e.class +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 new file mode 100644 index 0000000000..9e2d045bf3 --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/eval_return_with_lambda.rb @@ -0,0 +1,12 @@ +print "a," +x = -> do + print "b," + Proc.new do + print "c," + eval("return :eval") + print "d," + end.call + print "e," +end.call +print x, "," +print "f" diff --git a/spec/ruby/core/kernel/fixtures/eval_return_without_lambda.rb b/spec/ruby/core/kernel/fixtures/eval_return_without_lambda.rb new file mode 100644 index 0000000000..fc8b7f1d4a --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/eval_return_without_lambda.rb @@ -0,0 +1,14 @@ +print "a," +begin + print "b," + x = Proc.new do + print "c," + eval("return :eval") + print "d," + end.call + print x, "," +rescue LocalJumpError => e + print "e," + print e.class, "," +end +print "f" 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/test.rb b/spec/ruby/core/kernel/fixtures/test.rb new file mode 100644 index 0000000000..949948606f --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/test.rb @@ -0,0 +1,362 @@ +def foo1 +end + +def foo2 +end + +def foo3 +end + +def foo4 +end + +def foo5 +end + +def foo6 +end + +def foo7 +end + +def foo8 +end + +def foo9 +end + +def foo10 +end + +def foo11 +end + +def foo12 +end + +def foo13 +end + +def foo14 +end + +def foo15 +end + +def foo16 +end + +def foo17 +end + +def foo18 +end + +def foo19 +end + +def foo20 +end + +def foo21 +end + +def foo22 +end + +def foo23 +end + +def foo24 +end + +def foo25 +end + +def foo26 +end + +def foo27 +end + +def foo28 +end + +def foo29 +end + +def foo30 +end + +def foo31 +end + +def foo32 +end + +def foo33 +end + +def foo34 +end + +def foo35 +end + +def foo36 +end + +def foo37 +end + +def foo38 +end + +def foo39 +end + +def foo40 +end + +def foo41 +end + +def foo42 +end + +def foo43 +end + +def foo44 +end + +def foo45 +end + +def foo46 +end + +def foo47 +end + +def foo48 +end + +def foo49 +end + +def foo50 +end + +def foo51 +end + +def foo52 +end + +def foo53 +end + +def foo54 +end + +def foo55 +end + +def foo56 +end + +def foo57 +end + +def foo58 +end + +def foo59 +end + +def foo60 +end + +def foo61 +end + +def foo62 +end + +def foo63 +end + +def foo64 +end + +def foo65 +end + +def foo66 +end + +def foo67 +end + +def foo68 +end + +def foo69 +end + +def foo70 +end + +def foo71 +end + +def foo72 +end + +def foo73 +end + +def foo74 +end + +def foo75 +end + +def foo76 +end + +def foo77 +end + +def foo78 +end + +def foo79 +end + +def foo80 +end + +def foo81 +end + +def foo82 +end + +def foo83 +end + +def foo84 +end + +def foo85 +end + +def foo86 +end + +def foo87 +end + +def foo88 +end + +def foo89 +end + +def foo90 +end + +def foo91 +end + +def foo92 +end + +def foo93 +end + +def foo94 +end + +def foo95 +end + +def foo96 +end + +def foo97 +end + +def foo98 +end + +def foo99 +end + +def foo100 +end + +def foo101 +end + +def foo102 +end + +def foo103 +end + +def foo104 +end + +def foo105 +end + +def foo106 +end + +def foo107 +end + +def foo108 +end + +def foo109 +end + +def foo110 +end + +def foo111 +end + +def foo112 +end + +def foo113 +end + +def foo114 +end + +def foo115 +end + +def foo116 +end + +def foo117 +end + +def foo118 +end + +def foo119 +end + +def foo120 +end + +def foo121 +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 new file mode 100644 index 0000000000..b37f9980e0 --- /dev/null +++ b/spec/ruby/core/kernel/fork_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../shared/process/fork' + +describe "Kernel#fork" do + it "is a private method" do + Kernel.should have_private_instance_method(:fork) + end + + it_behaves_like :process_fork, :fork, KernelSpecs::Method.new +end + +describe "Kernel.fork" do + it_behaves_like :process_fork, :fork, Kernel +end diff --git a/spec/ruby/core/kernel/format_spec.rb b/spec/ruby/core/kernel/format_spec.rb new file mode 100644 index 0000000000..1d0c000c15 --- /dev/null +++ b/spec/ruby/core/kernel/format_spec.rb @@ -0,0 +1,47 @@ +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) + end +end + +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 new file mode 100644 index 0000000000..fa32d321cf --- /dev/null +++ b/spec/ruby/core/kernel/freeze_spec.rb @@ -0,0 +1,91 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#freeze" do + it "prevents self from being further modified" do + o = mock('o') + o.frozen?.should be_false + o.freeze + o.frozen?.should be_true + end + + it "returns self" do + o = Object.new + o.freeze.should equal(o) + end + + describe "on integers" do + it "has no effect since they are already frozen" do + 1.frozen?.should be_true + 1.freeze + + bignum = bignum_value + bignum.frozen?.should be_true + bignum.freeze + end + end + + describe "on a Float" do + it "has no effect since it is already frozen" do + 1.2.frozen?.should be_true + 1.2.freeze + end + end + + describe "on a Symbol" do + it "has no effect since it is already frozen" do + :sym.frozen?.should be_true + :sym.freeze + end + end + + describe "on true, false and nil" do + it "has no effect since they are already frozen" do + nil.frozen?.should be_true + true.frozen?.should be_true + false.frozen?.should be_true + + nil.freeze + true.freeze + false.freeze + end + 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 + end + + it "causes mutative calls to raise RuntimeError" do + o = Class.new do + def mutate; @foo = 1; end + end.new + o.freeze + -> {o.mutate}.should raise_error(RuntimeError) + end + + it "causes instance_variable_set to raise RuntimeError" do + o = Object.new + o.freeze + -> {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 new file mode 100644 index 0000000000..a4cec4263d --- /dev/null +++ b/spec/ruby/core/kernel/frozen_spec.rb @@ -0,0 +1,76 @@ +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.should_not.frozen? + p.should.frozen? + end + + describe "on true, false and nil" do + it "returns true" do + true.frozen?.should be_true + false.frozen?.should be_true + nil.frozen?.should be_true + end + end + + describe "on integers" do + before :each do + @fixnum = 1 + @bignum = bignum_value + end + + it "returns true" do + @fixnum.frozen?.should be_true + @bignum.frozen?.should be_true + end + end + + describe "on a Float" do + before :each do + @float = 0.1 + end + + it "returns true" do + @float.frozen?.should be_true + end + end + + describe "on a Symbol" do + before :each do + @symbol = :symbol + end + + it "returns true" do + @symbol.frozen?.should be_true + end + 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 + 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 + end +end diff --git a/spec/ruby/core/kernel/gets_spec.rb b/spec/ruby/core/kernel/gets_spec.rb new file mode 100644 index 0000000000..104613dbfa --- /dev/null +++ b/spec/ruby/core/kernel/gets_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#gets" do + it "is a private method" do + Kernel.should have_private_instance_method(:gets) + end + + it "calls ARGF.gets" do + ARGF.should_receive(:gets).and_return("spec") + gets.should == "spec" + end +end + +describe "Kernel.gets" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/kernel/global_variables_spec.rb b/spec/ruby/core/kernel/global_variables_spec.rb new file mode 100644 index 0000000000..8bce8e25b7 --- /dev/null +++ b/spec/ruby/core/kernel/global_variables_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel.global_variables" do + it "is a private method" do + Kernel.should have_private_instance_method(:global_variables) + end + + before :all do + @i = 0 + end + + it "finds subset starting with std" do + global_variables.grep(/std/).should include(:$stderr, :$stdin, :$stdout) + a = global_variables.size + gvar_name = "$foolish_global_var#{@i += 1}" + global_variables.include?(gvar_name.to_sym).should == false + eval("#{gvar_name} = 1") + global_variables.size.should == a+1 + global_variables.should include(gvar_name.to_sym) + end +end + +describe "Kernel#global_variables" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/kernel/gsub_spec.rb b/spec/ruby/core/kernel/gsub_spec.rb new file mode 100644 index 0000000000..a0cb9f2a70 --- /dev/null +++ b/spec/ruby/core/kernel/gsub_spec.rb @@ -0,0 +1,96 @@ +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. +ruby_version_is ""..."1.9" do + describe "Kernel#gsub" do + it "is a private method" do + Kernel.should have_private_instance_method(:gsub) + end + + it "raises a TypeError if $_ is not a String" do + -> { + $_ = 123 + gsub(/./, "!") + }.should raise_error(TypeError) + end + + it "when matches sets $_ to a new string, leaving the former value unaltered" do + orig_value = $_ = "hello" + gsub("ello", "ola") + $_.should_not equal(orig_value) + $_.should == "hola" + orig_value.should == "hello" + end + + it "returns a string with the same contents as $_ after the operation" do + $_ = "bye" + gsub("non-match", "?").should == "bye" + + orig_value = $_ = "bye" + gsub(/$/, "!").should == "bye!" + orig_value.should == "bye" + end + + it "accepts Regexps as patterns" do + $_ = "food" + gsub(/.$/, "l") + $_.should == "fool" + end + + it "accepts Strings as patterns, treated literally" do + $_ = "hello, world." + gsub(".", "!") + $_.should == "hello, world!" + end + + it "accepts objects which respond to #to_str as patterns and treats them as strings" do + $_ = "hello, world." + stringlike = mock(".") + stringlike.should_receive(:to_str).and_return(".") + gsub(stringlike, "!") + $_.should == "hello, world!" + end + end + + describe "Kernel#gsub with a pattern and replacement" do + it "accepts strings for replacement" do + $_ = "hello" + gsub(/./, ".") + $_.should == "....." + end + + it "accepts objects which respond to #to_str for replacement" do + o = mock("o") + o.should_receive(:to_str).and_return("o") + $_ = "ping" + gsub("i", o) + $_.should == "pong" + end + + it "replaces \\1 sequences with the regexp's corresponding capture" do + $_ = "hello!" + gsub(/(.)(.)/, '\2\1') + $_.should == "ehll!o" + end + end + + describe "Kernel#gsub with pattern and block" do + it "acts similarly to using $_.gsub" do + $_ = "olleh dlrow" + gsub(/(\w+)/){ $1.reverse } + $_.should == "hello world" + end + end + + describe "Kernel#gsub!" do + it "is a private method" do + Kernel.should have_private_instance_method(:gsub!) + end + end + + describe "Kernel.gsub!" do + it "needs to be reviewed for spec completeness" + end +end 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 new file mode 100644 index 0000000000..1fa66cab98 --- /dev/null +++ b/spec/ruby/core/kernel/inspect_spec.rb @@ -0,0 +1,90 @@ +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 "does not call #to_s if it is defined" do + # We must use a bare Object here + obj = Object.new + inspected = obj.inspect + + obj.stub!(:to_s).and_return("to_s'd") + + obj.inspect.should == inspected + end + + it "returns a String with the object class and object_id encoded" 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 new file mode 100644 index 0000000000..d1170d5047 --- /dev/null +++ b/spec/ruby/core/kernel/instance_of_spec.rb @@ -0,0 +1,40 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#instance_of?" do + before :each do + @o = KernelSpecs::InstanceClass.new + end + + it "returns true if given class is object's class" do + @o.instance_of?(KernelSpecs::InstanceClass).should == true + [].instance_of?(Array).should == true + ''.instance_of?(String).should == true + end + + it "returns false if given class is object's ancestor class" do + @o.instance_of?(KernelSpecs::AncestorClass).should == false + end + + it "returns false if given class is not object's class nor object's ancestor class" do + @o.instance_of?(Array).should == false + end + + it "returns false if given a Module that is included in object's class" do + @o.instance_of?(KernelSpecs::MyModule).should == false + end + + it "returns false if given a Module that is included one of object's ancestors only" do + @o.instance_of?(KernelSpecs::AncestorModule).should == false + end + + it "returns false if given a Module that is not included in object's class" do + @o.instance_of?(KernelSpecs::SomeOtherModule).should == false + end + + it "raises a TypeError if given an object that is not a Class nor a Module" do + -> { @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 new file mode 100644 index 0000000000..2ebb582b43 --- /dev/null +++ b/spec/ruby/core/kernel/instance_variable_defined_spec.rb @@ -0,0 +1,41 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#instance_variable_defined?" do + before do + @instance = KernelSpecs::InstanceVariable.new + end + + describe "when passed a String" do + it "returns false if the instance variable is not defined" do + @instance.instance_variable_defined?("@goodbye").should be_false + end + + it "returns true if the instance variable is defined" do + @instance.instance_variable_defined?("@greeting").should be_true + end + end + + describe "when passed a Symbol" do + it "returns false if the instance variable is not defined" do + @instance.instance_variable_defined?(:@goodbye).should be_false + end + + it "returns true if the instance variable is defined" do + @instance.instance_variable_defined?(:@greeting).should be_true + end + end + + it "raises a TypeError if passed an Object not defining #to_str" do + -> do + obj = mock("kernel instance_variable_defined?") + @instance.instance_variable_defined? obj + end.should raise_error(TypeError) + end + + it "returns false if the instance variable is not defined for different types" do + [nil, false, true, 1, 2.0, :test, "test"].each do |obj| + obj.instance_variable_defined?("@goodbye").should be_false + end + end +end diff --git a/spec/ruby/core/kernel/instance_variable_get_spec.rb b/spec/ruby/core/kernel/instance_variable_get_spec.rb new file mode 100644 index 0000000000..f1d2a45df8 --- /dev/null +++ b/spec/ruby/core/kernel/instance_variable_get_spec.rb @@ -0,0 +1,111 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#instance_variable_get" do + before :each do + @obj = Object.new + @obj.instance_variable_set("@test", :test) + end + + it "tries to convert the passed argument to a String using #to_str" do + obj = mock("to_str") + obj.should_receive(:to_str).and_return("@test") + @obj.instance_variable_get(obj) + end + + it "returns the value of the passed instance variable that is referred to by the conversion result" do + obj = mock("to_str") + obj.stub!(:to_str).and_return("@test") + @obj.instance_variable_get(obj).should == :test + end + + it "returns nil when the referred instance variable does not exist" do + @obj.instance_variable_get(:@does_not_exist).should be_nil + end + + it "raises a TypeError when the passed argument does not respond to #to_str" do + -> { @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) + -> { @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") + -> { @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('@') + -> { @obj.instance_variable_get(obj) }.should raise_error(NameError) + end +end + +describe "Kernel#instance_variable_get when passed Symbol" do + before :each do + @obj = Object.new + @obj.instance_variable_set("@test", :test) + end + + it "returns the value of the instance variable that is referred to by the passed Symbol" do + @obj.instance_variable_get(:@test).should == :test + end + + it "raises a NameError when passed :@ as an instance variable name" do + -> { @obj.instance_variable_get(:"@") }.should raise_error(NameError) + end + + it "raises a NameError when the passed Symbol does not start with an '@'" do + -> { @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 + -> { @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 + +describe "Kernel#instance_variable_get when passed String" do + before :each do + @obj = Object.new + @obj.instance_variable_set("@test", :test) + end + + it "returns the value of the instance variable that is referred to by the passed String" do + @obj.instance_variable_get("@test").should == :test + end + + it "raises a NameError when the passed String does not start with an '@'" do + -> { @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 + -> { @obj.instance_variable_get("@0") }.should raise_error(NameError) + end + + it "raises a NameError when passed '@' as an instance variable name" do + -> { @obj.instance_variable_get("@") }.should raise_error(NameError) + end +end + +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 + -> { @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 new file mode 100644 index 0000000000..2c25f4366f --- /dev/null +++ b/spec/ruby/core/kernel/instance_variable_set_spec.rb @@ -0,0 +1,105 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#instance_variable_set" do + it "sets the value of the specified instance variable" do + 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" + end + + it "sets the value of the instance variable when no instance variables exist yet" do + 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 + 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 + 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 + 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 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 + -> { "".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 + class KernelSpecs::B + def to_str + ":c" + end + end + -> { "".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 + -> { "".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 + class KernelSpecs::C + def initialize + @a = 1 + end + def to_str + "@a" + end + end + KernelSpecs::C.new.instance_variable_set(KernelSpecs::C.new, 2).should == 2 + end + + describe "on frozen objects" do + before :each do + klass = Class.new do + attr_reader :ivar + def initialize + @ivar = :origin + end + end + + @frozen = klass.new.freeze + end + + it "keeps stored object after any exceptions" do + -> { @frozen.instance_variable_set(:@ivar, :replacement) }.should raise_error(Exception) + @frozen.ivar.should equal(:origin) + end + + 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 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 new file mode 100644 index 0000000000..677d8bb7b2 --- /dev/null +++ b/spec/ruby/core/kernel/instance_variables_spec.rb @@ -0,0 +1,40 @@ +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, 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 + ->{ a.instance_variable_set("@test", 1) }.should raise_error(RuntimeError) + end + end + + describe "regular objects" do + it "returns an empty array if no instance variables are defined" do + Object.new.instance_variables.should == [] + end + + it "returns the correct array if an instance variable is added" do + a = Object.new + 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 new file mode 100644 index 0000000000..bd8c96529a --- /dev/null +++ b/spec/ruby/core/kernel/is_a_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/kind_of' + +describe "Kernel#is_a?" do + it_behaves_like :kernel_kind_of, :is_a? +end diff --git a/spec/ruby/core/kernel/itself_spec.rb b/spec/ruby/core/kernel/itself_spec.rb new file mode 100644 index 0000000000..c906d7c3e8 --- /dev/null +++ b/spec/ruby/core/kernel/itself_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#itself" do + it "returns the receiver itself" do + foo = Object.new + foo.itself.should equal foo + end +end diff --git a/spec/ruby/core/kernel/kind_of_spec.rb b/spec/ruby/core/kernel/kind_of_spec.rb new file mode 100644 index 0000000000..c988edccb5 --- /dev/null +++ b/spec/ruby/core/kernel/kind_of_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/kind_of' + +describe "Kernel#kind_of?" do + 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 new file mode 100644 index 0000000000..565536ac0d --- /dev/null +++ b/spec/ruby/core/kernel/lambda_spec.rb @@ -0,0 +1,158 @@ +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 "is a private method" do + Kernel.should have_private_instance_method(:lambda) + end + + it "creates a lambda-style Proc if given a literal block" do + l = 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 + + 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 + l = lambda { :called } + l.call.should == :called + + lambda { l.call(1) }.should raise_error(ArgumentError) + lambda { l.call(1, 2) }.should raise_error(ArgumentError) + end + + it "checks the arity when 1 arg is specified" do + l = lambda { |a| :called } + l.call(1).should == :called + + lambda { l.call }.should raise_error(ArgumentError) + lambda { l.call(1, 2) }.should raise_error(ArgumentError) + end + + it "does not check the arity when passing a Proc with &" do + l = lambda { || :called } + p = proc { || :called } + + lambda { l.call(1) }.should raise_error(ArgumentError) + p.call(1).should == :called + end + + it "accepts 0 arguments when used with ||" do + lambda { + lambda { || }.call(1) + }.should raise_error(ArgumentError) + end + + it "strictly checks the arity when 0 or 2..inf args are specified" do + l = lambda { |a,b| } + + lambda { + l.call + }.should raise_error(ArgumentError) + + lambda { + l.call(1) + }.should raise_error(ArgumentError) + + lambda { + l.call(1,2) + }.should_not raise_error(ArgumentError) + end + + it "returns from the lambda itself, not the creation site of the lambda" do + @reached_end_of_method = nil + def test + send(:lambda) { return }.call + @reached_end_of_method = true + end + test + @reached_end_of_method.should be_true + end + + it "allows long returns to flow through it" do + KernelSpecs::Lambda.new.outer.should == :good + 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 new file mode 100644 index 0000000000..a165cc4acd --- /dev/null +++ b/spec/ruby/core/kernel/load_spec.rb @@ -0,0 +1,40 @@ +require_relative '../../spec_helper' +require_relative '../../fixtures/code_loading' +require_relative 'shared/load' +require_relative 'shared/require' + +describe "Kernel#load" do + before :each do + CodeLoadingSpecs.spec_setup + end + + after :each do + CodeLoadingSpecs.spec_cleanup + end + + it "is a private method" do + Kernel.should have_private_instance_method(:load) + end + + it_behaves_like :kernel_require_basic, :load, CodeLoadingSpecs::Method.new +end + +describe "Kernel#load" do + it_behaves_like :kernel_load, :load, CodeLoadingSpecs::Method.new +end + +describe "Kernel.load" do + before :each do + CodeLoadingSpecs.spec_setup + end + + after :each do + CodeLoadingSpecs.spec_cleanup + end + + it_behaves_like :kernel_require_basic, :load, Kernel +end + +describe "Kernel.load" do + it_behaves_like :kernel_load, :load, Kernel +end diff --git a/spec/ruby/core/kernel/local_variables_spec.rb b/spec/ruby/core/kernel/local_variables_spec.rb new file mode 100644 index 0000000000..f6f1e15f52 --- /dev/null +++ b/spec/ruby/core/kernel/local_variables_spec.rb @@ -0,0 +1,48 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#local_variables" do + after :each do + ScratchPad.clear + end + + it "is a private method" do + Kernel.should have_private_instance_method(:local_variables) + end + + it "contains locals as they are added" do + a = 1 + b = 2 + local_variables.should include(:a, :b) + local_variables.length.should == 2 + end + + it "is accessible from bindings" do + def local_var_foo + a = 1 + b = 2 + binding + end + foo_binding = local_var_foo() + res = eval("local_variables",foo_binding) + res.should include(:a, :b) + res.length.should == 2 + end + + it "is accessible in eval" do + eval "a=1; b=2; ScratchPad.record local_variables" + 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 new file mode 100644 index 0000000000..7c76c7d28e --- /dev/null +++ b/spec/ruby/core/kernel/loop_spec.rb @@ -0,0 +1,79 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel.loop" do + it "is a private method" do + Kernel.should have_private_instance_method(:loop) + end + + it "calls block until it is terminated by a break" do + i = 0 + loop do + i += 1 + break if i == 10 + end + + i.should == 10 + end + + it "returns value passed to break" do + loop do + break 123 + end.should == 123 + end + + it "returns nil if no value passed to break" do + loop do + break + end.should == nil + end + + it "returns an enumerator if no block given" do + enum = loop + enum.instance_of?(Enumerator).should be_true + cnt = 0 + enum.each do |*args| + raise "Args should be empty #{args.inspect}" unless args.empty? + cnt += 1 + break cnt if cnt >= 42 + end.should == 42 + end + + it "rescues StopIteration" do + loop do + raise StopIteration + end + 42.should == 42 + end + + it "rescues StopIteration's subclasses" do + finish = Class.new StopIteration + loop do + raise finish + end + 42.should == 42 + end + + it "does not rescue other errors" do + ->{ loop do raise StandardError end }.should raise_error( StandardError ) + 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 + describe "returned Enumerator" do + describe "size" do + it "returns Float::INFINITY" do + loop.size.should == Float::INFINITY + end + end + end + end +end diff --git a/spec/ruby/core/kernel/match_spec.rb b/spec/ruby/core/kernel/match_spec.rb new file mode 100644 index 0000000000..cd6330fe91 --- /dev/null +++ b/spec/ruby/core/kernel/match_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' + +describe "Kernel#=~" do + 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 new file mode 100644 index 0000000000..3fc566d6a6 --- /dev/null +++ b/spec/ruby/core/kernel/method_spec.rb @@ -0,0 +1,80 @@ +require_relative '../../spec_helper' +require_relative 'shared/method' +require_relative 'fixtures/classes' + +describe "Kernel#method" do + it_behaves_like :kernel_method, :method + + before :each do + @obj = KernelSpecs::A.new + end + + it "can be called on a private method" do + @obj.send(:private_method).should == :private_method + @obj.method(:private_method).should be_an_instance_of(Method) + end + + it "can be called on a protected method" do + @obj.send(:protected_method).should == :protected_method + @obj.method(:protected_method).should be_an_instance_of(Method) + end + + it "will see an alias of the original method as == when in a derived class" do + obj = KernelSpecs::B.new + obj.method(:aliased_pub_method).should == obj.method(:pub_method) + end + + it "can call methods created with define_method" do + m = @obj.method(:defined_method) + m.call.should == :defined + end + + 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 new file mode 100644 index 0000000000..fb7a7e8be9 --- /dev/null +++ b/spec/ruby/core/kernel/methods_spec.rb @@ -0,0 +1,101 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../fixtures/reflection' + +# TODO: rewrite +describe "Kernel#methods" do + it "returns singleton methods defined by obj.meth" do + KernelSpecs::Methods.methods(false).should include(:ichi) + end + + it "returns singleton methods defined in 'class << self'" do + KernelSpecs::Methods.methods(false).should include(:san) + end + + it "returns private singleton methods defined by obj.meth" do + KernelSpecs::Methods.methods(false).should include(:shi) + end + + it "returns singleton methods defined in 'class << self' when it follows 'private'" do + KernelSpecs::Methods.methods(false).should include(:roku) + end + + it "does not return private singleton methods defined in 'class << self'" do + KernelSpecs::Methods.methods(false).should_not include(:shichi) + end + + it "returns the publicly accessible methods of the object" do + meths = KernelSpecs::Methods.methods(false) + meths.should include(:hachi, :ichi, :juu, :juu_ichi, + :juu_ni, :roku, :san, :shi) + + KernelSpecs::Methods.new.methods(false).should == [] + end + + it "returns the publicly accessible methods in the object, its ancestors and mixed-in modules" do + meths = KernelSpecs::Methods.methods(false) & KernelSpecs::Methods.methods + meths.should include(:hachi, :ichi, :juu, :juu_ichi, + :juu_ni, :roku, :san, :shi) + + KernelSpecs::Methods.new.methods.should include(:ku, :ni, :juu_san) + end + + it "returns methods added to the metaclass through extend" do + meth = KernelSpecs::Methods.new + meth.methods.should_not include(:peekaboo) + meth.extend(KernelSpecs::Methods::MetaclassMethods) + meth.methods.should include(:peekaboo) + end + + it "does not return undefined singleton methods defined by obj.meth" do + o = KernelSpecs::Child.new + def o.single; end + o.methods.should include(:single) + + class << o; self; end.send :undef_method, :single + o.methods.should_not include(:single) + end + + it "does not return superclass methods undefined in the object's class" do + KernelSpecs::Child.new.methods.should_not include(:parent_method) + end + + it "does not return superclass methods undefined in a superclass" do + KernelSpecs::Grandchild.new.methods.should_not include(:parent_method) + end + + it "does not return included module methods undefined in the object's class" do + KernelSpecs::Grandchild.new.methods.should_not include(:parent_mixin_method) + end +end + +describe :kernel_methods_supers, shared: true do + before :all do + @ms = [:pro, :pub] + end + + it "returns a unique list for an object extended by a module" do + m = ReflectSpecs.oed.methods(*@object) + m.select { |x| @ms.include? x }.sort.should == @ms + end + + it "returns a unique list for a class including a module" do + m = ReflectSpecs::D.new.methods(*@object) + m.select { |x| @ms.include? x }.sort.should == @ms + end + + it "returns a unique list for a subclass of a class that includes a module" do + m = ReflectSpecs::E.new.methods(*@object) + m.select { |x| @ms.include? x }.sort.should == @ms + end +end + +describe "Kernel#methods" do + describe "when not passed an argument" do + it_behaves_like :kernel_methods_supers, nil, [] + end + + describe "when passed true" do + it_behaves_like :kernel_methods_supers, nil, true + end +end diff --git a/spec/ruby/core/kernel/nil_spec.rb b/spec/ruby/core/kernel/nil_spec.rb new file mode 100644 index 0000000000..7418245f26 --- /dev/null +++ b/spec/ruby/core/kernel/nil_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +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 new file mode 100644 index 0000000000..082e56fed7 --- /dev/null +++ b/spec/ruby/core/kernel/not_match_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#!~" do + class KernelSpecs::NotMatch + def !~(obj) + :foo + end + end + + it 'calls =~ internally and negates the result' do + obj = Object.new + obj.should_receive(:=~).and_return(true) + (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 + end +end diff --git a/spec/ruby/core/kernel/object_id_spec.rb b/spec/ruby/core/kernel/object_id_spec.rb new file mode 100644 index 0000000000..ef9e80c831 --- /dev/null +++ b/spec/ruby/core/kernel/object_id_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative '../../shared/kernel/object_id' + +describe "Kernel#object_id" do + it_behaves_like :object_id, :object_id, Object +end diff --git a/spec/ruby/core/kernel/open_spec.rb b/spec/ruby/core/kernel/open_spec.rb new file mode 100644 index 0000000000..b967d5044b --- /dev/null +++ b/spec/ruby/core/kernel/open_spec.rb @@ -0,0 +1,194 @@ +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" + touch(@name) { |f| f.write @content } + @file = nil + end + + after :each do + @file.close if @file + rm_r @name + end + + it "is a private method" do + Kernel.should have_private_instance_method(:open) + end + + it "opens a file when given a valid filename" do + @file = open(@name) + @file.should be_kind_of(File) + end + + it "opens a file when called with a block" do + open(@name, "r") { |f| f.gets }.should == @content + end + + 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 + + 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 + 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 + + 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 + + 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 + -> { 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 + before :each do + ScratchPad.clear + end + + 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) + + open(obj, "r") { |f| f.gets }.should == @content + end + + it "calls #to_str to convert the argument to a String" do + obj = mock("open to_str") + obj.should_receive(:to_str).at_least(1).times.and_return(@name) + + open(obj, "r") { |f| f.gets }.should == @content + end + + it "calls #to_open on argument" do + obj = mock('fileish') + @file = File.open(@name) + obj.should_receive(:to_open).and_return(@file) + @file = open(obj) + @file.should be_kind_of(File) + end + + it "returns the value from #to_open" do + obj = mock('to_open') + obj.should_receive(:to_open).and_return(:value) + + open(obj).should == :value + end + + it "passes its arguments onto #to_open" do + obj = mock('to_open') + 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) + + open(obj) do |mock| + ScratchPad.record(mock) + end + + ScratchPad.recorded.should == :value + end + end + + it "raises a TypeError if passed a non-String that does not respond to #to_open" do + obj = mock('non-fileish') + -> { 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 + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/kernel/p_spec.rb b/spec/ruby/core/kernel/p_spec.rb new file mode 100644 index 0000000000..eae191aa54 --- /dev/null +++ b/spec/ruby/core/kernel/p_spec.rb @@ -0,0 +1,85 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#p" do + before :all do + @rs_f, @rs_b, @rs_c = $/, $\, $, + end + + after :each do + suppress_warning { + $/, $\, $, = @rs_f, @rs_b, @rs_c + } + end + + it "is a private method" do + Kernel.should have_private_instance_method(:p) + end + + # TODO: fix + it "flushes output if receiver is a File" do + filename = tmp("Kernel_p_flush") + $$.to_s + begin + File.open(filename, "w") do |f| + begin + old_stdout = $stdout + $stdout = f + p("abcde") + ensure + $stdout = old_stdout + end + + File.open(filename) do |f2| + f2.read(7).should == "\"abcde\"" + end + end + ensure + rm_r filename + end + end + + it "prints obj.inspect followed by system record separator for each argument given" do + o = mock("Inspector Gadget") + o.should_receive(:inspect).any_number_of_times.and_return "Next time, Gadget, NEXT TIME!" + + -> { 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!" + + suppress_warning { + $, = " *helicopter sound*\n" + } + -> { 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") + + 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 + -> { p }.should output("") + end + + it "prints nothing if called splatting an empty Array" do + -> { p(*[]) }.should output("") + 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 + it "needs to be reviewed for spec completeness" +end 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 new file mode 100644 index 0000000000..7e7c9b822d --- /dev/null +++ b/spec/ruby/core/kernel/print_spec.rb @@ -0,0 +1,24 @@ +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 + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/kernel/printf_spec.rb b/spec/ruby/core/kernel/printf_spec.rb new file mode 100644 index 0000000000..61bf955c25 --- /dev/null +++ b/spec/ruby/core/kernel/printf_spec.rb @@ -0,0 +1,70 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/sprintf' + +describe "Kernel#printf" do + it "is a private method" do + Kernel.should have_private_instance_method(:printf) + end +end + +describe "Kernel.printf" do + before :each do + @stdout = $stdout + @name = tmp("kernel_puts.txt") + $stdout = new_io @name + end + + after :each do + $stdout.close + $stdout = @stdout + rm_r @name + end + + it "writes to stdout when a string is the first argument" do + $stdout.should_receive(:write).with("string") + Kernel.printf("%s", "string") + end + + it "calls write on the first argument when it is not a string" do + object = mock('io') + object.should_receive(:write).with("string") + 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(+"") + Kernel.printf(io, format, *args) + io.string + } + end + + context "io is not specified" do + it_behaves_like :kernel_sprintf, -> format, *args { + stdout = $stdout + begin + $stdout = io = StringIO.new(+"") + Kernel.printf(format, *args) + io.string + ensure + $stdout = stdout + end + } + end + end +end diff --git a/spec/ruby/core/kernel/private_methods_spec.rb b/spec/ruby/core/kernel/private_methods_spec.rb new file mode 100644 index 0000000000..041634d1e5 --- /dev/null +++ b/spec/ruby/core/kernel/private_methods_spec.rb @@ -0,0 +1,69 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../fixtures/reflection' + +# TODO: rewrite +describe "Kernel#private_methods" do + it "returns a list of the names of privately accessible methods in the object" do + m = KernelSpecs::Methods.private_methods(false) + m.should include(:shichi) + m = KernelSpecs::Methods.new.private_methods(false) + m.should include(:juu_shi) + end + + it "returns a list of the names of privately accessible methods in the object and its ancestors and mixed-in modules" do + m = (KernelSpecs::Methods.private_methods(false) & KernelSpecs::Methods.private_methods) + + m.should include(:shichi) + m = KernelSpecs::Methods.new.private_methods + m.should include(:juu_shi) + end + + it "returns private methods mixed in to the metaclass" do + m = KernelSpecs::Methods.new + m.extend(KernelSpecs::Methods::MetaclassMethods) + m.private_methods.should include(:shoo) + end +end + +describe :kernel_private_methods_supers, shared: true do + it "returns a unique list for an object extended by a module" do + m = ReflectSpecs.oed.private_methods(*@object) + m.select { |x| x == :pri }.sort.should == [:pri] + end + + it "returns a unique list for a class including a module" do + m = ReflectSpecs::D.new.private_methods(*@object) + m.select { |x| x == :pri }.sort.should == [:pri] + end + + it "returns a unique list for a subclass of a class that includes a module" do + m = ReflectSpecs::E.new.private_methods(*@object) + m.select { |x| x == :pri }.sort.should == [:pri] + end +end + +describe :kernel_private_methods_with_falsy, shared: true do + it "returns a list of private methods in without its ancestors" do + ReflectSpecs::F.private_methods(@object).select{|m|/_pri\z/ =~ m}.sort.should == [:ds_pri, :fs_pri] + ReflectSpecs::F.new.private_methods(@object).should == [:f_pri] + end +end + +describe "Kernel#private_methods" do + describe "when not passed an argument" do + it_behaves_like :kernel_private_methods_supers, nil, [] + end + + describe "when passed true" do + it_behaves_like :kernel_private_methods_supers, nil, true + end + + describe "when passed false" do + it_behaves_like :kernel_private_methods_with_falsy, nil, false + end + + describe "when passed nil" do + it_behaves_like :kernel_private_methods_with_falsy, nil, nil + end +end diff --git a/spec/ruby/core/kernel/proc_spec.rb b/spec/ruby/core/kernel/proc_spec.rb new file mode 100644 index 0000000000..6553b8fd04 --- /dev/null +++ b/spec/ruby/core/kernel/proc_spec.rb @@ -0,0 +1,48 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/lambda' + +# The functionality of Proc objects is specified in core/proc + +describe "Kernel.proc" do + it "is a private method" do + Kernel.should have_private_instance_method(:proc) + end + + it "creates a proc-style Proc if given a literal block" do + l = proc { 42 } + l.lambda?.should be_false + end + + it "returned the passed Proc if given an existing Proc" do + 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 "returns from the creation site of the proc, not just the proc itself" do + @reached_end_of_method = nil + def test + proc { return }.call + @reached_end_of_method = true + end + test + @reached_end_of_method.should be_nil + end +end + +describe "Kernel#proc" do + def some_method + proc + end + + 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 new file mode 100644 index 0000000000..d3334e886b --- /dev/null +++ b/spec/ruby/core/kernel/protected_methods_spec.rb @@ -0,0 +1,69 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../fixtures/reflection' + +# TODO: rewrite + +# The reason why having include() is to show the specification explicitly. +# You should use have_protected_method() with the exception of this spec. +describe "Kernel#protected_methods" do + it "returns a list of the names of protected methods accessible in the object" do + KernelSpecs::Methods.protected_methods(false).sort.should include(:juu_ichi) + KernelSpecs::Methods.new.protected_methods(false).should include(:ku) + end + + it "returns a list of the names of protected methods accessible in the object and from its ancestors and mixed-in modules" do + l1 = KernelSpecs::Methods.protected_methods(false) + l2 = KernelSpecs::Methods.protected_methods + (l1 & l2).should include(:juu_ichi) + KernelSpecs::Methods.new.protected_methods.should include(:ku) + end + + it "returns methods mixed in to the metaclass" do + m = KernelSpecs::Methods.new + m.extend(KernelSpecs::Methods::MetaclassMethods) + m.protected_methods.should include(:nopeeking) + end +end + +describe :kernel_protected_methods_supers, shared: true do + it "returns a unique list for an object extended by a module" do + m = ReflectSpecs.oed.protected_methods(*@object) + m.select { |x| x == :pro }.sort.should == [:pro] + end + + it "returns a unique list for a class including a module" do + m = ReflectSpecs::D.new.protected_methods(*@object) + m.select { |x| x == :pro }.sort.should == [:pro] + end + + it "returns a unique list for a subclass of a class that includes a module" do + m = ReflectSpecs::E.new.protected_methods(*@object) + m.select { |x| x == :pro }.sort.should == [:pro] + end +end + +describe :kernel_protected_methods_with_falsy, shared: true do + it "returns a list of protected methods in without its ancestors" do + ReflectSpecs::F.protected_methods(@object).select{|m|/_pro\z/ =~ m}.sort.should == [:ds_pro, :fs_pro] + ReflectSpecs::F.new.protected_methods(@object).should == [:f_pro] + end +end + +describe "Kernel#protected_methods" do + describe "when not passed an argument" do + it_behaves_like :kernel_protected_methods_supers, nil, [] + end + + describe "when passed true" do + it_behaves_like :kernel_protected_methods_supers, nil, true + end + + describe "when passed false" do + it_behaves_like :kernel_protected_methods_with_falsy, nil, false + end + + describe "when passed nil" do + it_behaves_like :kernel_protected_methods_with_falsy, nil, nil + end +end diff --git a/spec/ruby/core/kernel/public_method_spec.rb b/spec/ruby/core/kernel/public_method_spec.rb new file mode 100644 index 0000000000..c5d54c777e --- /dev/null +++ b/spec/ruby/core/kernel/public_method_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/method' + +describe "Kernel#public_method" do + it_behaves_like :kernel_method, :public_method + + before :each do + @obj = KernelSpecs::A.new + end + + it "raises a NameError when called on a private method" do + @obj.send(:private_method).should == :private_method + -> 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 + -> { + @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 + -> do + obj.public_method(:handled_privately) + end.should raise_error(NameError) + end +end diff --git a/spec/ruby/core/kernel/public_methods_spec.rb b/spec/ruby/core/kernel/public_methods_spec.rb new file mode 100644 index 0000000000..a5512784fb --- /dev/null +++ b/spec/ruby/core/kernel/public_methods_spec.rb @@ -0,0 +1,76 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../fixtures/reflection' + +# TODO: rewrite +describe "Kernel#public_methods" do + it "returns a list of the names of publicly accessible methods in the object" do + KernelSpecs::Methods.public_methods(false).sort.should include(:hachi, + :ichi, :juu, :juu_ni, :roku, :san, :shi) + KernelSpecs::Methods.new.public_methods(false).sort.should include(:juu_san, :ni) + end + + it "returns a list of names without protected accessible methods in the object" do + KernelSpecs::Methods.public_methods(false).sort.should_not include(:juu_ichi) + KernelSpecs::Methods.new.public_methods(false).sort.should_not include(:ku) + end + + it "returns a list of the names of publicly accessible methods in the object and its ancestors and mixed-in modules" do + (KernelSpecs::Methods.public_methods(false) & KernelSpecs::Methods.public_methods).sort.should include( + :hachi, :ichi, :juu, :juu_ni, :roku, :san, :shi) + m = KernelSpecs::Methods.new.public_methods + m.should include(:ni, :juu_san) + end + + it "returns methods mixed in to the metaclass" do + m = KernelSpecs::Methods.new + m.extend(KernelSpecs::Methods::MetaclassMethods) + m.public_methods.should include(:peekaboo) + end + + it "returns public methods for immediates" do + 10.public_methods.should include(:divmod) + end +end + +describe :kernel_public_methods_supers, shared: true do + it "returns a unique list for an object extended by a module" do + m = ReflectSpecs.oed.public_methods(*@object) + m.select { |x| x == :pub }.sort.should == [:pub] + end + + it "returns a unique list for a class including a module" do + m = ReflectSpecs::D.new.public_methods(*@object) + m.select { |x| x == :pub }.sort.should == [:pub] + end + + it "returns a unique list for a subclass of a class that includes a module" do + m = ReflectSpecs::E.new.public_methods(*@object) + m.select { |x| x == :pub }.sort.should == [:pub] + end +end + +describe :kernel_public_methods_with_falsy, shared: true do + it "returns a list of public methods in without its ancestors" do + ReflectSpecs::F.public_methods(@object).select{|m|/_pub\z/ =~ m}.sort.should == [:ds_pub, :fs_pub] + ReflectSpecs::F.new.public_methods(@object).should == [:f_pub] + end +end + +describe "Kernel#public_methods" do + describe "when not passed an argument" do + it_behaves_like :kernel_public_methods_supers, nil, [] + end + + describe "when passed true" do + it_behaves_like :kernel_public_methods_supers, nil, true + end + + describe "when passed false" do + it_behaves_like :kernel_public_methods_with_falsy, nil, false + end + + describe "when passed nil" do + it_behaves_like :kernel_public_methods_with_falsy, nil, nil + end +end diff --git a/spec/ruby/core/kernel/public_send_spec.rb b/spec/ruby/core/kernel/public_send_spec.rb new file mode 100644 index 0000000000..b684b1729c --- /dev/null +++ b/spec/ruby/core/kernel/public_send_spec.rb @@ -0,0 +1,116 @@ +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 + class KernelSpecs::Foo + def bar + 'done' + end + end + KernelSpecs::Foo.new.public_send(:bar).should == 'done' + end + + it "invokes the named alias of a public method" do + class KernelSpecs::Foo + def bar + 'done' + end + alias :aka :bar + end + KernelSpecs::Foo.new.public_send(:aka).should == 'done' + end + + it "raises a NoMethodError if the method is protected" do + class KernelSpecs::Foo + protected + def bar + 'done' + end + end + -> { KernelSpecs::Foo.new.public_send(:bar)}.should raise_error(NoMethodError) + end + + it "raises a NoMethodError if the named method is private" do + class KernelSpecs::Foo + private + def bar + 'done2' + end + end + -> { + KernelSpecs::Foo.new.public_send(:bar) + }.should raise_error(NoMethodError) + end + + context 'called from own public method' do + before do + class << @receiver = Object.new + def call_protected_method + public_send :protected_method + end + + def call_private_method + public_send :private_method + end + + protected + + def protected_method + raise 'Should not called' + end + + private + + def private_method + raise 'Should not called' + end + end + end + + it "raises a NoMethodError if the method is protected" do + -> { @receiver.call_protected_method }.should raise_error(NoMethodError) + end + + it "raises a NoMethodError if the method is private" do + -> { @receiver.call_private_method }.should raise_error(NoMethodError) + end + end + + it "raises a NoMethodError if the named method is an alias of a private method" do + class KernelSpecs::Foo + private + def bar + 'done2' + end + alias :aka :bar + end + -> { + KernelSpecs::Foo.new.public_send(:aka) + }.should raise_error(NoMethodError) + end + + it "raises a NoMethodError if the named method is an alias of a protected method" do + class KernelSpecs::Foo + protected + def bar + 'done2' + end + alias :aka :bar + end + -> { + KernelSpecs::Foo.new.public_send(:aka) + }.should raise_error(NoMethodError) + end + + 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 new file mode 100644 index 0000000000..74bd3765db --- /dev/null +++ b/spec/ruby/core/kernel/putc_spec.rb @@ -0,0 +1,39 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../shared/io/putc' + +describe "Kernel#putc" do + it "is a private instance method" do + Kernel.should have_private_instance_method(:putc) + end +end + +describe "Kernel.putc" do + before :each do + @name = tmp("kernel_putc.txt") + @io = new_io @name + @io_object = @object + @stdout, $stdout = $stdout, @io + end + + after :each do + $stdout = @stdout + end + + it_behaves_like :io_putc, :putc_method, KernelSpecs +end + +describe "Kernel#putc" do + before :each do + @name = tmp("kernel_putc.txt") + @io = new_io @name + @io_object = @object + @stdout, $stdout = $stdout, @io + end + + after :each do + $stdout = @stdout + end + + it_behaves_like :io_putc, :putc_function, KernelSpecs +end diff --git a/spec/ruby/core/kernel/puts_spec.rb b/spec/ruby/core/kernel/puts_spec.rb new file mode 100644 index 0000000000..6eb38e8fcf --- /dev/null +++ b/spec/ruby/core/kernel/puts_spec.rb @@ -0,0 +1,29 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#puts" do + before :each do + @stdout = $stdout + @name = tmp("kernel_puts.txt") + $stdout = new_io @name + end + + after :each do + $stdout.close + $stdout = @stdout + rm_r @name + end + + it "is a private method" do + Kernel.should have_private_instance_method(:puts) + end + + it "delegates to $stdout.puts" do + $stdout.should_receive(:puts).with(:arg) + puts :arg + end +end + +describe "Kernel.puts" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/kernel/raise_spec.rb b/spec/ruby/core/kernel/raise_spec.rb new file mode 100644 index 0000000000..fcd011d4e6 --- /dev/null +++ b/spec/ruby/core/kernel/raise_spec.rb @@ -0,0 +1,292 @@ +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 + it_behaves_like :kernel_raise, :raise, Kernel +end + +describe "Kernel.raise" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/kernel/rand_spec.rb b/spec/ruby/core/kernel/rand_spec.rb new file mode 100644 index 0000000000..355e425792 --- /dev/null +++ b/spec/ruby/core/kernel/rand_spec.rb @@ -0,0 +1,197 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#rand" do + it "is a private method" do + Kernel.should have_private_instance_method(:rand) + end + + it "returns a float if no argument is passed" do + rand.should be_kind_of(Float) + end + + it "returns an integer for an integer argument" do + rand(77).should be_kind_of(Integer) + end + + it "returns an integer for a float argument greater than 1" do + rand(1.3).should be_kind_of(Integer) + end + + it "returns a float for an argument between -1 and 1" do + rand(-0.999).should be_kind_of(Float) + rand(-0.01).should be_kind_of(Float) + rand(0).should be_kind_of(Float) + rand(0.01).should be_kind_of(Float) + rand(0.999).should be_kind_of(Float) + end + + it "ignores the sign of the argument" do + [0, 1, 2, 3].should include(rand(-4)) + end + + it "never returns a value greater or equal to 1.0 with no arguments" do + 1000.times do + (0...1.0).should include(rand) + end + end + + it "never returns a value greater or equal to any passed in max argument" do + 1000.times do + (0...100).to_a.should include(rand(100)) + end + end + + it "calls to_int on its argument" do + l = mock('limit') + l.should_receive(:to_int).and_return 7 + + rand l + end + + context "given an exclusive range" do + it "returns an Integer between the two Integers" do + 1000.times do + x = rand(4...6) + x.should be_kind_of(Integer) + (4...6).should include(x) + end + end + + it "returns a Float between the given Integer and Float" do + 1000.times do + x = rand(4...6.5) + x.should be_kind_of(Float) + (4...6.5).should include(x) + end + end + + it "returns a Float between the given Float and Integer" do + 1000.times do + x = rand(3.5...6) + x.should be_kind_of(Float) + (3.5...6).should include(x) + end + end + + it "returns a Float between the two given Floats" do + 1000.times do + x = rand(3.5...6.5) + x.should be_kind_of(Float) + (3.5...6.5).should include(x) + end + end + end + + context "given an inclusive range" do + it "returns an Integer between the two Integers" do + 1000.times do + x = rand(4..6) + x.should be_kind_of(Integer) + (4..6).should include(x) + end + end + + it "returns a Float between the given Integer and Float" do + 1000.times do + x = rand(4..6.5) + x.should be_kind_of(Float) + (4..6.5).should include(x) + end + end + + it "returns a Float between the given Float and Integer" do + 1000.times do + x = rand(3.5..6) + x.should be_kind_of(Float) + (3.5..6).should include(x) + end + end + + it "returns a Float between the two given Floats" do + 1000.times do + x = rand(3.5..6.5) + x.should be_kind_of(Float) + (3.5..6.5).should include(x) + end + 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 + + it "returns nil when range is backwards" do + rand(1..0).should be_nil + end + + it "returns the range start/end when Float range is 0" do + rand(1.0..1.0).should eql(1.0) + end + + 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 + 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 new file mode 100644 index 0000000000..dce7b03dc8 --- /dev/null +++ b/spec/ruby/core/kernel/readline_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#readline" do + it "is a private method" do + Kernel.should have_private_instance_method(:readline) + end +end + +describe "Kernel.readline" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/kernel/readlines_spec.rb b/spec/ruby/core/kernel/readlines_spec.rb new file mode 100644 index 0000000000..2b6d65fff2 --- /dev/null +++ b/spec/ruby/core/kernel/readlines_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#readlines" do + it "is a private method" do + Kernel.should have_private_instance_method(:readlines) + end +end + +describe "Kernel.readlines" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/kernel/remove_instance_variable_spec.rb b/spec/ruby/core/kernel/remove_instance_variable_spec.rb new file mode 100644 index 0000000000..4e5ba5e018 --- /dev/null +++ b/spec/ruby/core/kernel/remove_instance_variable_spec.rb @@ -0,0 +1,72 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe :kernel_remove_instance_variable, shared: true do + it "returns the instance variable's value" do + value = @instance.send :remove_instance_variable, @object + value.should == "hello" + end + + it "removes the instance variable" do + @instance.send :remove_instance_variable, @object + @instance.instance_variable_defined?(@object).should be_false + end +end + +describe "Kernel#remove_instance_variable" do + before do + @instance = KernelSpecs::InstanceVariable.new + end + + it "is a public method" do + Kernel.should have_public_instance_method(:remove_instance_variable, false) + end + + it "raises a NameError if the instance variable is not defined" 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 + -> 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 + -> 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 + + describe "when passed a Symbol" do + it_behaves_like :kernel_remove_instance_variable, nil, :@greeting + end + + describe "when passed an Object" do + it "calls #to_str to convert the argument" do + name = mock("kernel remove_instance_variable") + name.should_receive(:to_str).and_return("@greeting") + @instance.send :remove_instance_variable, name + end + end +end diff --git a/spec/ruby/core/kernel/require_relative_spec.rb b/spec/ruby/core/kernel/require_relative_spec.rb new file mode 100644 index 0000000000..6188d13a4e --- /dev/null +++ b/spec/ruby/core/kernel/require_relative_spec.rb @@ -0,0 +1,437 @@ +require_relative '../../spec_helper' +require_relative '../../fixtures/code_loading' + +describe "Kernel#require_relative with a relative path" do + before :each do + CodeLoadingSpecs.spec_setup + @dir = "../../fixtures/code" + @abs_dir = File.realpath(@dir, __dir__) + @path = "#{@dir}/load_fixture.rb" + @abs_path = File.realpath(@path, __dir__) + end + + after :each do + CodeLoadingSpecs.spec_cleanup + end + + platform_is_not :windows do + describe "when file is a symlink" do + before :each do + @link = tmp("symlink.rb", false) + @real_path = "#{@abs_dir}/symlink/symlink1.rb" + File.symlink(@real_path, @link) + end + + after :each do + rm_r @link + end + + it "loads a path relative to current file" do + require_relative(@link).should be_true + ScratchPad.recorded.should == [:loaded] + end + end + end + + it "loads a path relative to the current file" do + require_relative(@path).should be_true + 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 + -> { 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 + -> { 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" + + -> { + require_relative(path) + }.should(raise_error(LoadError) { |e| + e.path.should == File.expand_path(path, @abs_dir) + }) + end + + it "calls #to_str on non-String objects" do + name = mock("load_fixture.rb mock") + name.should_receive(:to_str).and_return(@path) + require_relative(name).should be_true + ScratchPad.recorded.should == [:loaded] + end + + it "raises a TypeError if argument does not respond to #to_str" do + -> { require_relative(nil) }.should raise_error(TypeError) + -> { require_relative(42) }.should raise_error(TypeError) + -> { + require_relative([@path,@path]) + }.should raise_error(TypeError) + end + + it "raises a TypeError if passed an object that has #to_s but not #to_str" do + name = mock("load_fixture.rb mock") + name.stub!(:to_s).and_return(@path) + -> { 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) + -> { require_relative(name) }.should raise_error(TypeError) + end + + it "calls #to_path on non-String objects" do + name = mock("load_fixture.rb mock") + name.should_receive(:to_path).and_return(@path) + require_relative(name).should be_true + ScratchPad.recorded.should == [:loaded] + end + + it "calls #to_str on non-String objects returned by #to_path" do + name = mock("load_fixture.rb mock") + to_path = mock("load_fixture_rb #to_path mock") + name.should_receive(:to_path).and_return(to_path) + to_path.should_receive(:to_str).and_return(@path) + require_relative(name).should be_true + ScratchPad.recorded.should == [:loaded] + end + + describe "(file extensions)" do + it "loads a .rb extensioned file when passed a non-extensioned path" do + require_relative("#{@dir}/load_fixture").should be_true + ScratchPad.recorded.should == [:loaded] + end + + it "loads a .rb extensioned file when a C-extension file of the same name is loaded" do + $LOADED_FEATURES << "#{@abs_dir}/load_fixture.bundle" + $LOADED_FEATURES << "#{@abs_dir}/load_fixture.dylib" + $LOADED_FEATURES << "#{@abs_dir}/load_fixture.so" + $LOADED_FEATURES << "#{@abs_dir}/load_fixture.dll" + require_relative(@path).should be_true + ScratchPad.recorded.should == [:loaded] + end + + it "does not load a C-extension file if a .rb extensioned file is already loaded" do + $LOADED_FEATURES << "#{@abs_dir}/load_fixture.rb" + require_relative("#{@dir}/load_fixture").should be_false + ScratchPad.recorded.should == [] + end + + it "loads a .rb extensioned file when passed a non-.rb extensioned path" do + require_relative("#{@dir}/load_fixture.ext").should be_true + ScratchPad.recorded.should == [:loaded] + $LOADED_FEATURES.should include "#{@abs_dir}/load_fixture.ext.rb" + end + + it "loads a .rb extensioned file when a complex-extensioned C-extension file of the same name is loaded" do + $LOADED_FEATURES << "#{@abs_dir}/load_fixture.ext.bundle" + $LOADED_FEATURES << "#{@abs_dir}/load_fixture.ext.dylib" + $LOADED_FEATURES << "#{@abs_dir}/load_fixture.ext.so" + $LOADED_FEATURES << "#{@abs_dir}/load_fixture.ext.dll" + require_relative("#{@dir}/load_fixture.ext").should be_true + ScratchPad.recorded.should == [:loaded] + $LOADED_FEATURES.should include "#{@abs_dir}/load_fixture.ext.rb" + end + + it "does not load a C-extension file if a complex-extensioned .rb file is already loaded" do + $LOADED_FEATURES << "#{@abs_dir}/load_fixture.ext.rb" + require_relative("#{@dir}/load_fixture.ext").should be_false + ScratchPad.recorded.should == [] + end + end + + describe "($LOADED_FEATURES)" do + it "stores an absolute path" do + require_relative(@path).should be_true + $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 + -> { require_relative("#{@dir}/raise_fixture.rb") }.should raise_error(RuntimeError) + $LOADED_FEATURES.should == saved_loaded_features + end + + it "does not load an absolute path that is already stored" do + $LOADED_FEATURES << @abs_path + require_relative(@path).should be_false + ScratchPad.recorded.should == [] + end + + it "adds the suffix of the resolved filename" do + require_relative("#{@dir}/load_fixture").should be_true + $LOADED_FEATURES.should include("#{@abs_dir}/load_fixture.rb") + end + + it "loads a path for a file already loaded with a relative path" do + $LOAD_PATH << File.expand_path(@dir) + $LOADED_FEATURES << "load_fixture.rb" << "load_fixture" + require_relative(@path).should be_true + $LOADED_FEATURES.should include(@abs_path) + ScratchPad.recorded.should == [:loaded] + end + end +end + +describe "Kernel#require_relative with an absolute path" do + before :each do + CodeLoadingSpecs.spec_setup + @dir = File.expand_path "../../fixtures/code", __dir__ + @abs_dir = @dir + @path = File.join @dir, "load_fixture.rb" + @abs_path = @path + end + + after :each do + CodeLoadingSpecs.spec_cleanup + end + + it "loads a path relative to the current file" do + require_relative(@path).should be_true + ScratchPad.recorded.should == [:loaded] + 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 + -> { require_relative("#{@dir}/nonexistent.rb") }.should raise_error(LoadError) + ScratchPad.recorded.should == [] + end + + it "raises a LoadError if basepath does not exist" do + -> { 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" + + -> { + require_relative(path) + }.should(raise_error(LoadError) { |e| + e.path.should == File.expand_path(path, @abs_dir) + }) + end + + it "calls #to_str on non-String objects" do + name = mock("load_fixture.rb mock") + name.should_receive(:to_str).and_return(@path) + require_relative(name).should be_true + ScratchPad.recorded.should == [:loaded] + end + + it "raises a TypeError if argument does not respond to #to_str" do + -> { require_relative(nil) }.should raise_error(TypeError) + -> { require_relative(42) }.should raise_error(TypeError) + -> { + require_relative([@path,@path]) + }.should raise_error(TypeError) + end + + it "raises a TypeError if passed an object that has #to_s but not #to_str" do + name = mock("load_fixture.rb mock") + name.stub!(:to_s).and_return(@path) + -> { 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) + -> { require_relative(name) }.should raise_error(TypeError) + end + + it "calls #to_path on non-String objects" do + name = mock("load_fixture.rb mock") + name.should_receive(:to_path).and_return(@path) + require_relative(name).should be_true + ScratchPad.recorded.should == [:loaded] + end + + it "calls #to_str on non-String objects returned by #to_path" do + name = mock("load_fixture.rb mock") + to_path = mock("load_fixture_rb #to_path mock") + name.should_receive(:to_path).and_return(to_path) + to_path.should_receive(:to_str).and_return(@path) + require_relative(name).should be_true + ScratchPad.recorded.should == [:loaded] + end + + describe "(file extensions)" do + it "loads a .rb extensioned file when passed a non-extensioned path" do + require_relative("#{@dir}/load_fixture").should be_true + ScratchPad.recorded.should == [:loaded] + end + + it "loads a .rb extensioned file when a C-extension file of the same name is loaded" do + $LOADED_FEATURES << "#{@abs_dir}/load_fixture.bundle" + $LOADED_FEATURES << "#{@abs_dir}/load_fixture.dylib" + $LOADED_FEATURES << "#{@abs_dir}/load_fixture.so" + $LOADED_FEATURES << "#{@abs_dir}/load_fixture.dll" + require_relative(@path).should be_true + ScratchPad.recorded.should == [:loaded] + end + + it "does not load a C-extension file if a .rb extensioned file is already loaded" do + $LOADED_FEATURES << "#{@abs_dir}/load_fixture.rb" + require_relative("#{@dir}/load_fixture").should be_false + ScratchPad.recorded.should == [] + end + + it "loads a .rb extensioned file when passed a non-.rb extensioned path" do + require_relative("#{@dir}/load_fixture.ext").should be_true + ScratchPad.recorded.should == [:loaded] + $LOADED_FEATURES.should include "#{@abs_dir}/load_fixture.ext.rb" + end + + it "loads a .rb extensioned file when a complex-extensioned C-extension file of the same name is loaded" do + $LOADED_FEATURES << "#{@abs_dir}/load_fixture.ext.bundle" + $LOADED_FEATURES << "#{@abs_dir}/load_fixture.ext.dylib" + $LOADED_FEATURES << "#{@abs_dir}/load_fixture.ext.so" + $LOADED_FEATURES << "#{@abs_dir}/load_fixture.ext.dll" + require_relative("#{@dir}/load_fixture.ext").should be_true + ScratchPad.recorded.should == [:loaded] + $LOADED_FEATURES.should include "#{@abs_dir}/load_fixture.ext.rb" + end + + it "does not load a C-extension file if a complex-extensioned .rb file is already loaded" do + $LOADED_FEATURES << "#{@abs_dir}/load_fixture.ext.rb" + require_relative("#{@dir}/load_fixture.ext").should be_false + ScratchPad.recorded.should == [] + end + end + + describe "($LOAD_FEATURES)" do + it "stores an absolute path" do + require_relative(@path).should be_true + $LOADED_FEATURES.should include(@abs_path) + end + + it "does not store the path if the load fails" do + saved_loaded_features = $LOADED_FEATURES.dup + -> { require_relative("#{@dir}/raise_fixture.rb") }.should raise_error(RuntimeError) + $LOADED_FEATURES.should == saved_loaded_features + end + + it "does not load an absolute path that is already stored" do + $LOADED_FEATURES << @abs_path + require_relative(@path).should be_false + ScratchPad.recorded.should == [] + end + + it "adds the suffix of the resolved filename" do + require_relative("#{@dir}/load_fixture").should be_true + $LOADED_FEATURES.should include("#{@abs_dir}/load_fixture.rb") + end + + it "loads a path for a file already loaded with a relative path" do + $LOAD_PATH << File.expand_path(@dir) + $LOADED_FEATURES << "load_fixture.rb" << "load_fixture" + require_relative(@path).should be_true + $LOADED_FEATURES.should include(@abs_path) + ScratchPad.recorded.should == [:loaded] + end + end +end diff --git a/spec/ruby/core/kernel/require_spec.rb b/spec/ruby/core/kernel/require_spec.rb new file mode 100644 index 0000000000..60d17242fe --- /dev/null +++ b/spec/ruby/core/kernel/require_spec.rb @@ -0,0 +1,60 @@ +require_relative '../../spec_helper' +require_relative '../../fixtures/code_loading' +require_relative 'shared/require' + +describe "Kernel#require" do + before :each do + CodeLoadingSpecs.spec_setup + end + + after :each do + CodeLoadingSpecs.spec_cleanup + end + + # if this fails, update your rubygems + it "is a private method" do + Kernel.should have_private_instance_method(:require) + end + + 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 + +describe "Kernel.require" do + before :each do + CodeLoadingSpecs.spec_setup + end + + after :each do + CodeLoadingSpecs.spec_cleanup + 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 new file mode 100644 index 0000000000..cc82031e26 --- /dev/null +++ b/spec/ruby/core/kernel/respond_to_missing_spec.rb @@ -0,0 +1,100 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#respond_to_missing?" do + before :each do + @a = KernelSpecs::A.new + end + + it "is a private method" do + Kernel.should have_private_instance_method(:respond_to_missing?, false) + end + + it "is only an instance method" do + Kernel.method(:respond_to_missing?).owner.should == Kernel + end + + it "is not called when #respond_to? would return true" do + obj = mock('object') + obj.stub!(:glark) + obj.should_not_receive(:respond_to_missing?) + obj.respond_to?(:glark).should be_true + end + + it "is called with a 2nd argument of false when #respond_to? is" do + obj = mock('object') + obj.should_receive(:respond_to_missing?).with(:undefined_method, false) + obj.respond_to?(:undefined_method, false) + end + + it "is called a 2nd argument of false when #respond_to? is called with only 1 argument" do + obj = mock('object') + obj.should_receive(:respond_to_missing?).with(:undefined_method, false) + obj.respond_to?(:undefined_method) + end + + it "is called with true as the second argument when #respond_to? is" do + obj = mock('object') + obj.should_receive(:respond_to_missing?).with(:undefined_method, true) + obj.respond_to?(:undefined_method, true) + end + + it "is called when #respond_to? would return false" do + obj = mock('object') + obj.should_receive(:respond_to_missing?).with(:undefined_method, false) + obj.respond_to?(:undefined_method) + end + + it "causes #respond_to? to return true if called and not returning false" do + obj = mock('object') + obj.should_receive(:respond_to_missing?).with(:undefined_method, false).and_return(:glark) + obj.respond_to?(:undefined_method).should be_true + end + + it "causes #respond_to? to return false if called and returning false" do + obj = mock('object') + obj.should_receive(:respond_to_missing?).with(:undefined_method, false).and_return(false) + obj.respond_to?(:undefined_method).should be_false + end + + it "causes #respond_to? to return false if called and returning nil" do + obj = mock('object') + obj.should_receive(:respond_to_missing?).with(:undefined_method, false).and_return(nil) + obj.respond_to?(:undefined_method).should be_false + end + + it "isn't called when obj responds to the given public method" do + @a.should_not_receive(:respond_to_missing?) + @a.respond_to?(:pub_method).should be_true + end + + it "isn't called when obj responds to the given public method, include_private = true" do + @a.should_not_receive(:respond_to_missing?) + @a.respond_to?(:pub_method, true).should be_true + end + + it "is called when obj responds to the given protected method, include_private = false" do + @a.should_receive(:respond_to_missing?) + @a.respond_to?(:protected_method, false).should be_false + end + + it "isn't called when obj responds to the given protected method, include_private = true" do + @a.should_not_receive(:respond_to_missing?) + @a.respond_to?(:protected_method, true).should be_true + end + + it "is called when obj responds to the given private method, include_private = false" do + @a.should_receive(:respond_to_missing?).with(:private_method, false) + @a.respond_to?(:private_method) + end + + it "isn't called when obj responds to the given private method, include_private = true" do + @a.should_not_receive(:respond_to_missing?) + @a.respond_to?(:private_method, true).should be_true + end + + it "is called for missing class methods" do + @a.class.should_receive(:respond_to_missing?).with(:oOoOoO, false) + @a.class.respond_to?(:oOoOoO) + end +end diff --git a/spec/ruby/core/kernel/respond_to_spec.rb b/spec/ruby/core/kernel/respond_to_spec.rb new file mode 100644 index 0000000000..5b3ea3f651 --- /dev/null +++ b/spec/ruby/core/kernel/respond_to_spec.rb @@ -0,0 +1,72 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#respond_to?" do + before :each do + @a = KernelSpecs::A.new + end + + it "is a public method" do + Kernel.should have_public_instance_method(:respond_to?, false) + end + + it "is only an instance method" do + Kernel.method(:respond_to?).owner.should == Kernel + end + + it "returns false if the given method was undefined" do + @a.respond_to?(:undefed_method).should == false + @a.respond_to?("undefed_method").should == false + end + + it "returns true if obj responds to the given public method" do + @a.respond_to?(:pub_method).should == true + @a.respond_to?("pub_method").should == true + end + + it "throws a type error if argument can't be coerced into a Symbol" do + -> { @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 + @a.respond_to?(:protected_method).should == false + @a.respond_to?("protected_method").should == false + end + + it "returns false if obj responds to the given private method" do + @a.respond_to?(:private_method).should == false + @a.respond_to?("private_method").should == false + end + + it "returns true if obj responds to the given protected method (include_private = true)" do + @a.respond_to?(:protected_method, true).should == true + @a.respond_to?("protected_method", true).should == true + end + + it "returns false if obj responds to the given protected method (include_private = false)" do + @a.respond_to?(:protected_method, false).should == false + @a.respond_to?("protected_method", false).should == false + end + + it "returns false even if obj responds to the given private method (include_private = false)" do + @a.respond_to?(:private_method, false).should == false + @a.respond_to?("private_method", false).should == false + end + + it "returns true if obj responds to the given private method (include_private = true)" do + @a.respond_to?(:private_method, true).should == true + @a.respond_to?("private_method", true).should == true + end + + 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 + -> { KernelSpecs::VisibilityChange.new }.should raise_error(NoMethodError) + end + + it "indicates if an object responds to a particular message" do + class KernelSpecs::Foo; def bar; 'done'; end; end + 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 new file mode 100644 index 0000000000..df23414b28 --- /dev/null +++ b/spec/ruby/core/kernel/select_spec.rb @@ -0,0 +1,18 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#select" do + it "is a private method" do + Kernel.should have_private_instance_method(:select) + end +end + +describe "Kernel.select" do + it 'does not block when timeout is 0' do + IO.pipe do |read, write| + select([read], [], [], 0).should == nil + write.write 'data' + 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 new file mode 100644 index 0000000000..9a4d261964 --- /dev/null +++ b/spec/ruby/core/kernel/send_spec.rb @@ -0,0 +1,68 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../shared/basicobject/send' + +describe "Kernel#send" do + it "invokes the named public method" do + class KernelSpecs::Foo + def bar + 'done' + end + end + KernelSpecs::Foo.new.send(:bar).should == 'done' + end + + it "invokes the named alias of a public method" do + class KernelSpecs::Foo + def bar + 'done' + end + alias :aka :bar + end + KernelSpecs::Foo.new.send(:aka).should == 'done' + end + + it "invokes the named protected method" do + class KernelSpecs::Foo + protected + def bar + 'done' + end + end + KernelSpecs::Foo.new.send(:bar).should == 'done' + end + + it "invokes the named private method" do + class KernelSpecs::Foo + private + def bar + 'done2' + end + end + KernelSpecs::Foo.new.send(:bar).should == 'done2' + end + + it "invokes the named alias of a private method" do + class KernelSpecs::Foo + private + def bar + 'done2' + end + alias :aka :bar + end + KernelSpecs::Foo.new.send(:aka).should == 'done2' + end + + it "invokes the named alias of a protected method" do + class KernelSpecs::Foo + protected + def bar + 'done2' + end + alias :aka :bar + end + KernelSpecs::Foo.new.send(:aka).should == 'done2' + end + + 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 new file mode 100644 index 0000000000..1f43e7a009 --- /dev/null +++ b/spec/ruby/core/kernel/set_trace_func_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#set_trace_func" do + it "is a private method" do + Kernel.should have_private_instance_method(:set_trace_func) + end +end + +describe "Kernel.set_trace_func" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/kernel/shared/dup_clone.rb b/spec/ruby/core/kernel/shared/dup_clone.rb new file mode 100644 index 0000000000..4fac6006e1 --- /dev/null +++ b/spec/ruby/core/kernel/shared/dup_clone.rb @@ -0,0 +1,91 @@ +class ObjectSpecDup + def initialize() + @obj = :original + end + + attr_accessor :obj +end + +class ObjectSpecDupInitCopy + def initialize() + @obj = :original + end + + attr_accessor :obj, :original + + def initialize_copy(original) + @obj = :init_copy + @original = original + end + + private :initialize_copy +end + +describe :kernel_dup_clone, shared: true do + it "returns a new object duplicated from the original" do + o = ObjectSpecDup.new + o2 = ObjectSpecDup.new + + o.obj = 10 + + o3 = o.send(@method) + + o3.obj.should == 10 + o2.obj.should == :original + end + + it "produces a shallow copy, contained objects are not recursively dupped" do + o = ObjectSpecDup.new + array = [1, 2] + o.obj = array + + o2 = o.send(@method) + o2.obj.should equal(o.obj) + end + + it "calls #initialize_copy on the NEW object if available, passing in original object" do + o = ObjectSpecDupInitCopy.new + o2 = o.send(@method) + + o.obj.should == :original + o2.obj.should == :init_copy + o2.original.should equal(o) + end + + it "does not preserve the object_id" do + o1 = ObjectSpecDupInitCopy.new + old_object_id = o1.object_id + o2 = o1.send(@method) + o2.object_id.should_not == old_object_id + end + + it "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 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 +end diff --git a/spec/ruby/core/kernel/shared/kind_of.rb b/spec/ruby/core/kernel/shared/kind_of.rb new file mode 100644 index 0000000000..aef6f1c1d8 --- /dev/null +++ b/spec/ruby/core/kernel/shared/kind_of.rb @@ -0,0 +1,55 @@ +require_relative '../fixtures/classes' + +describe :kernel_kind_of, shared: true do + before :each do + @o = KernelSpecs::KindaClass.new + end + + it "returns true if given class is the object's class" do + @o.send(@method, KernelSpecs::KindaClass).should == true + end + + it "returns true if given class is an ancestor of the object's class" do + @o.send(@method, KernelSpecs::AncestorClass).should == true + @o.send(@method, String).should == true + @o.send(@method, Object).should == true + end + + it "returns false if the given class is not object's class nor an ancestor" do + @o.send(@method, Array).should == false + end + + it "returns true if given a Module that is included in object's class" do + @o.send(@method, KernelSpecs::MyModule).should == true + end + + it "returns true if given a Module that is included one of object's ancestors only" do + @o.send(@method, KernelSpecs::AncestorModule).should == true + end + + it "returns true if given a Module that object has been extended with" do + @o.send(@method, KernelSpecs::MyExtensionModule).should == true + end + + it "returns 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 + -> { @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 new file mode 100644 index 0000000000..c70640082a --- /dev/null +++ b/spec/ruby/core/kernel/shared/lambda.rb @@ -0,0 +1,11 @@ +describe :kernel_lambda, shared: true do + it "returns a Proc object" do + send(@method) { true }.kind_of?(Proc).should == true + end + + it "raises an ArgumentError when no block is given" do + 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 new file mode 100644 index 0000000000..62c5c7be9b --- /dev/null +++ b/spec/ruby/core/kernel/shared/load.rb @@ -0,0 +1,215 @@ +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 + @path = File.expand_path "load_fixture.rb", CODE_LOADING_DIR + end + + after :each do + CodeLoadingSpecs.spec_cleanup + 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 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 + -> { + @object.load(path).should be_true + }.should complain(/circular require considered harmful/, verbose: true) + ScratchPad.recorded.should == [:loaded, :loaded] + end + + it "loads a file that recursively loads itself" do + path = File.expand_path "recursive_load_fixture.rb", CODE_LOADING_DIR + @object.load(path).should be_true + ScratchPad.recorded.should == [:loaded, :loaded] + end + + it "loads a file each time the method is called" do + @object.load(@path).should be_true + @object.load(@path).should be_true + ScratchPad.recorded.should == [:loaded, :loaded] + end + + it "loads a file even when the name appears in $LOADED_FEATURES" do + $LOADED_FEATURES << @path + @object.load(@path).should be_true + ScratchPad.recorded.should == [:loaded] + end + + it "loads a file that has been loaded by #require" do + @object.require(@path).should be_true + @object.load(@path).should be_true + ScratchPad.recorded.should == [:loaded, :loaded] + end + + it "loads file even after $LOAD_PATH change" do + $LOAD_PATH << CODE_LOADING_DIR + @object.load("load_fixture.rb").should be_true + $LOAD_PATH.unshift CODE_LOADING_DIR + "/gem" + @object.load("load_fixture.rb").should be_true + ScratchPad.recorded.should == [:loaded, :loaded_gem] + end + + it "does not cause #require with the same path to fail" do + @object.load(@path).should be_true + @object.require(@path).should be_true + ScratchPad.recorded.should == [:loaded, :loaded] + end + + it "does not add the loaded path to $LOADED_FEATURES" do + saved_loaded_features = $LOADED_FEATURES.dup + @object.load(@path).should be_true + $LOADED_FEATURES.should == saved_loaded_features + end + + it "raises a LoadError if passed a non-extensioned path that does not exist but a .rb extensioned path does exist" do + path = File.expand_path "load_ext_fixture", CODE_LOADING_DIR + -> { @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 "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 "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 "load_wrap_fixture.rb", CODE_LOADING_DIR + @object.load(path, true) + + 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 + before :each do + path = File.expand_path "load_wrap_method_fixture.rb", CODE_LOADING_DIR + @object.load(path, true) + end + + it "allows calling top-level methods" do + ScratchPad.recorded.last.should == :load_wrap_loaded + end + + it "does not pollute the receiver" do + -> { @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"] + ENV["HOME"] = CODE_LOADING_DIR + end + + after :each do + ENV["HOME"] = @env_home + end + + it "expands a tilde to the HOME environment variable as the path to load" do + @object.require("~/load_fixture.rb").should be_true + ScratchPad.recorded.should == [:loaded] + end + end +end diff --git a/spec/ruby/core/kernel/shared/method.rb b/spec/ruby/core/kernel/shared/method.rb new file mode 100644 index 0000000000..8b6ab23fd3 --- /dev/null +++ b/spec/ruby/core/kernel/shared/method.rb @@ -0,0 +1,56 @@ +require_relative '../../../spec_helper' + +describe :kernel_method, shared: true do + it "returns a method object for a valid method" do + class KernelSpecs::Foo; def bar; 'done'; end; end + m = KernelSpecs::Foo.new.send(@method, :bar) + m.should be_an_instance_of Method + m.call.should == 'done' + end + + it "returns a method object for a valid singleton method" do + class KernelSpecs::Foo; def self.bar; 'class done'; end; end + m = KernelSpecs::Foo.send(@method, :bar) + m.should be_an_instance_of Method + m.call.should == 'class done' + end + + it "returns a method object if 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 + -> { + 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 + -> { KernelSpecs::Foo.send(@method, :baz) }.should raise_error(NameError) + end + + it "changes the method called for super on a target aliased method" do + c1 = Class.new do + def a; 'a'; end + def b; 'b'; end + end + c2 = Class.new(c1) do + def a; super; end + alias b a + end + + c2.new.a.should == 'a' + c2.new.b.should == 'a' + c2.new.send(@method, :b).call.should == 'a' + end +end diff --git a/spec/ruby/core/kernel/shared/require.rb b/spec/ruby/core/kernel/shared/require.rb new file mode 100644 index 0000000000..52f86f73e5 --- /dev/null +++ b/spec/ruby/core/kernel/shared/require.rb @@ -0,0 +1,848 @@ +describe :kernel_require_basic, shared: true do + describe "(path resolution)" do + it "loads an absolute path" do + path = File.expand_path "load_fixture.rb", CODE_LOADING_DIR + @object.send(@method, path).should be_true + ScratchPad.recorded.should == [:loaded] + end + + it "loads a non-canonical absolute path" do + path = File.join CODE_LOADING_DIR, "..", "code", "load_fixture.rb" + @object.send(@method, path).should be_true + ScratchPad.recorded.should == [:loaded] + end + + it "loads a file defining many methods" do + path = File.expand_path "methods_fixture.rb", CODE_LOADING_DIR + @object.send(@method, path).should be_true + ScratchPad.recorded.should == [:loaded] + end + + it "raises a LoadError if the file does not exist" do + path = File.expand_path "nonexistent.rb", CODE_LOADING_DIR + File.should_not.exist?(path) + -> { @object.send(@method, path) }.should raise_error(LoadError) + ScratchPad.recorded.should == [] + end + + # Can't make a file unreadable on these platforms + platform_is_not :windows, :cygwin do + as_user do + describe "with an unreadable file" do + before :each do + @path = tmp("unreadable_file.rb") + touch @path + File.chmod 0000, @path + end + + after :each do + File.chmod 0666, @path + rm_r @path + end + + it "raises a LoadError" do + File.should.exist?(@path) + -> { @object.send(@method, @path) }.should raise_error(LoadError) + end + end + end + end + + it "calls #to_str on non-String objects" do + path = File.expand_path "load_fixture.rb", CODE_LOADING_DIR + name = mock("load_fixture.rb mock") + name.should_receive(:to_str).and_return(path) + @object.send(@method, name).should be_true + ScratchPad.recorded.should == [:loaded] + end + + it "raises a TypeError if passed nil" do + -> { @object.send(@method, nil) }.should raise_error(TypeError) + end + + it "raises a TypeError if passed an Integer" do + -> { @object.send(@method, 42) }.should raise_error(TypeError) + end + + it "raises a TypeError if passed an Array" do + -> { @object.send(@method, []) }.should raise_error(TypeError) + end + + it "raises a TypeError if passed an object that does not provide #to_str" do + -> { @object.send(@method, mock("not a filename")) }.should raise_error(TypeError) + end + + it "raises a TypeError if passed an object that has #to_s but not #to_str" do + name = mock("load_fixture.rb mock") + name.stub!(:to_s).and_return("load_fixture.rb") + $LOAD_PATH << "." + Dir.chdir CODE_LOADING_DIR do + -> { @object.send(@method, name) }.should raise_error(TypeError) + end + end + + it "raises a TypeError if #to_str does not return a String" do + name = mock("#to_str returns nil") + name.should_receive(:to_str).at_least(1).times.and_return(nil) + -> { @object.send(@method, name) }.should raise_error(TypeError) + end + + it "calls #to_path on non-String objects" do + name = mock("load_fixture.rb mock") + name.stub!(:to_path).and_return("load_fixture.rb") + $LOAD_PATH << "." + Dir.chdir CODE_LOADING_DIR do + @object.send(@method, name).should be_true + end + ScratchPad.recorded.should == [:loaded] + end + + it "calls #to_path on a String" do + path = File.expand_path "load_fixture.rb", CODE_LOADING_DIR + str = mock("load_fixture.rb mock") + str.should_receive(:to_path).and_return(path) + @object.send(@method, str).should be_true + ScratchPad.recorded.should == [:loaded] + end + + it "calls #to_str on non-String objects returned by #to_path" do + path = File.expand_path "load_fixture.rb", CODE_LOADING_DIR + name = mock("load_fixture.rb mock") + to_path = mock("load_fixture_rb #to_path mock") + name.should_receive(:to_path).and_return(to_path) + to_path.should_receive(:to_str).and_return(path) + @object.send(@method, name).should be_true + ScratchPad.recorded.should == [:loaded] + end + + # "http://redmine.ruby-lang.org/issues/show/2578" + it "loads a ./ relative path from the current working directory with empty $LOAD_PATH" do + Dir.chdir CODE_LOADING_DIR do + @object.send(@method, "./load_fixture.rb").should be_true + end + ScratchPad.recorded.should == [:loaded] + end + + it "loads a ../ relative path from the current working directory with empty $LOAD_PATH" do + Dir.chdir CODE_LOADING_DIR do + @object.send(@method, "../code/load_fixture.rb").should be_true + end + ScratchPad.recorded.should == [:loaded] + end + + it "loads a ./ relative path from the current working directory with non-empty $LOAD_PATH" do + $LOAD_PATH << "an_irrelevant_dir" + Dir.chdir CODE_LOADING_DIR do + @object.send(@method, "./load_fixture.rb").should be_true + end + ScratchPad.recorded.should == [:loaded] + end + + it "loads a ../ relative path from the current working directory with non-empty $LOAD_PATH" do + $LOAD_PATH << "an_irrelevant_dir" + Dir.chdir CODE_LOADING_DIR do + @object.send(@method, "../code/load_fixture.rb").should be_true + end + ScratchPad.recorded.should == [:loaded] + end + + it "loads a non-canonical path from the current working directory with non-empty $LOAD_PATH" do + $LOAD_PATH << "an_irrelevant_dir" + Dir.chdir CODE_LOADING_DIR do + @object.send(@method, "../code/../code/load_fixture.rb").should be_true + end + ScratchPad.recorded.should == [:loaded] + end + + it "resolves a filename against $LOAD_PATH entries" do + $LOAD_PATH << CODE_LOADING_DIR + @object.send(@method, "load_fixture.rb").should be_true + ScratchPad.recorded.should == [:loaded] + end + + it "accepts an Object with #to_path in $LOAD_PATH" do + obj = mock("to_path") + obj.should_receive(:to_path).at_least(:once).and_return(CODE_LOADING_DIR) + $LOAD_PATH << obj + @object.send(@method, "load_fixture.rb").should be_true + ScratchPad.recorded.should == [:loaded] + end + + it "does not require file twice after $LOAD_PATH change" do + $LOAD_PATH << CODE_LOADING_DIR + @object.require("load_fixture.rb").should be_true + $LOAD_PATH.push CODE_LOADING_DIR + "/gem" + @object.require("load_fixture.rb").should be_false + ScratchPad.recorded.should == [:loaded] + end + + it "does not resolve a ./ relative path against $LOAD_PATH entries" do + $LOAD_PATH << CODE_LOADING_DIR + -> do + @object.send(@method, "./load_fixture.rb") + end.should raise_error(LoadError) + ScratchPad.recorded.should == [] + end + + it "does not resolve a ../ relative path against $LOAD_PATH entries" do + $LOAD_PATH << CODE_LOADING_DIR + -> do + @object.send(@method, "../code/load_fixture.rb") + end.should raise_error(LoadError) + ScratchPad.recorded.should == [] + end + + it "resolves a non-canonical path against $LOAD_PATH entries" do + $LOAD_PATH << File.dirname(CODE_LOADING_DIR) + @object.send(@method, "code/../code/load_fixture.rb").should be_true + ScratchPad.recorded.should == [:loaded] + end + + it "loads a path with duplicate path separators" do + $LOAD_PATH << "." + sep = File::Separator + File::Separator + path = ["..", "code", "load_fixture.rb"].join(sep) + Dir.chdir CODE_LOADING_DIR do + @object.send(@method, path).should be_true + end + ScratchPad.recorded.should == [:loaded] + end + end +end + +describe :kernel_require, shared: true do + describe "(path resolution)" do + it "loads .rb file when passed absolute path without extension" do + path = File.expand_path "load_fixture", CODE_LOADING_DIR + @object.send(@method, path).should be_true + # This should _not_ be [:no_ext] + ScratchPad.recorded.should == [:loaded] + end + + platform_is :linux, :darwin do + it "loads c-extension file when passed absolute path without extension when no .rb is present" do + # the error message is specific to what dlerror() returns + path = File.join CODE_LOADING_DIR, "a", "load_fixture" + -> { @object.send(@method, path) }.should raise_error(LoadError) + end + end + + platform_is :darwin do + it "loads .bundle file when passed absolute path with .so" do + # the error message is specific to what dlerror() returns + path = File.join CODE_LOADING_DIR, "a", "load_fixture.so" + -> { @object.send(@method, path) }.should raise_error(LoadError) + end + end + + it "does not try an extra .rb if the path already ends in .rb" do + path = File.join CODE_LOADING_DIR, "d", "load_fixture.rb" + -> { @object.send(@method, path) }.should raise_error(LoadError) + end + + # For reference see [ruby-core:24155] in which matz confirms this feature is + # intentional for security reasons. + it "does not load a bare filename unless the current working directory is in $LOAD_PATH" do + Dir.chdir CODE_LOADING_DIR do + -> { @object.require("load_fixture.rb") }.should raise_error(LoadError) + ScratchPad.recorded.should == [] + end + end + + it "does not load a relative path unless the current working directory is in $LOAD_PATH" do + Dir.chdir File.dirname(CODE_LOADING_DIR) do + -> do + @object.require("code/load_fixture.rb") + end.should raise_error(LoadError) + ScratchPad.recorded.should == [] + end + end + + it "loads a file that recursively requires itself" do + path = File.expand_path "recursive_require_fixture.rb", CODE_LOADING_DIR + -> { + @object.require(path).should be_true + }.should complain(/circular require considered harmful/, verbose: true) + ScratchPad.recorded.should == [:loaded] + end + + ruby_bug "#17340", ''...'3.3' do + it "loads a file concurrently" do + path = File.expand_path "concurrent_require_fixture.rb", CODE_LOADING_DIR + ScratchPad.record(@object) + -> { + @object.require(path) + }.should_not complain(/circular require considered harmful/, verbose: true) + ScratchPad.recorded.join + end + end + end + + describe "(non-extensioned path)" do + before :each do + a = File.expand_path "a", CODE_LOADING_DIR + b = File.expand_path "b", CODE_LOADING_DIR + $LOAD_PATH.replace [a, b] + end + + it "loads a .rb extensioned file when a C-extension file exists on an earlier load path" do + @object.require("load_fixture").should be_true + ScratchPad.recorded.should == [:loaded] + end + + it "does not load a feature twice when $LOAD_PATH has been modified" do + $LOAD_PATH.replace [CODE_LOADING_DIR] + @object.require("load_fixture").should be_true + $LOAD_PATH.replace [File.expand_path("b", CODE_LOADING_DIR), CODE_LOADING_DIR] + @object.require("load_fixture").should be_false + end + + it "stores the missing path in a LoadError object" do + path = "abcd1234" + + -> { + @object.send(@method, path) + }.should raise_error(LoadError) { |e| + e.path.should == path + } + end + end + + describe "(file extensions)" do + it "loads a .rb extensioned file when passed a non-extensioned path" do + path = File.expand_path "load_fixture", CODE_LOADING_DIR + File.should.exist?(path) + @object.require(path).should be_true + ScratchPad.recorded.should == [:loaded] + end + + it "loads a .rb extensioned file when a C-extension file of the same name is loaded" do + $LOADED_FEATURES << File.expand_path("load_fixture.bundle", CODE_LOADING_DIR) + $LOADED_FEATURES << File.expand_path("load_fixture.dylib", CODE_LOADING_DIR) + $LOADED_FEATURES << File.expand_path("load_fixture.so", CODE_LOADING_DIR) + $LOADED_FEATURES << File.expand_path("load_fixture.dll", CODE_LOADING_DIR) + path = File.expand_path "load_fixture", CODE_LOADING_DIR + @object.require(path).should be_true + ScratchPad.recorded.should == [:loaded] + end + + it "does not load a C-extension file if a .rb extensioned file is already loaded" do + $LOADED_FEATURES << File.expand_path("load_fixture.rb", CODE_LOADING_DIR) + path = File.expand_path "load_fixture", CODE_LOADING_DIR + @object.require(path).should be_false + ScratchPad.recorded.should == [] + end + + it "loads a .rb extensioned file when passed a non-.rb extensioned path" do + path = File.expand_path "load_fixture.ext", CODE_LOADING_DIR + File.should.exist?(path) + @object.require(path).should be_true + ScratchPad.recorded.should == [:loaded] + end + + it "loads a .rb extensioned file when a complex-extensioned C-extension file of the same name is loaded" do + $LOADED_FEATURES << File.expand_path("load_fixture.ext.bundle", CODE_LOADING_DIR) + $LOADED_FEATURES << File.expand_path("load_fixture.ext.dylib", CODE_LOADING_DIR) + $LOADED_FEATURES << File.expand_path("load_fixture.ext.so", CODE_LOADING_DIR) + $LOADED_FEATURES << File.expand_path("load_fixture.ext.dll", CODE_LOADING_DIR) + path = File.expand_path "load_fixture.ext", CODE_LOADING_DIR + @object.require(path).should be_true + ScratchPad.recorded.should == [:loaded] + end + + it "does not load a C-extension file if a complex-extensioned .rb file is already loaded" do + $LOADED_FEATURES << File.expand_path("load_fixture.ext.rb", CODE_LOADING_DIR) + path = File.expand_path "load_fixture.ext", CODE_LOADING_DIR + @object.require(path).should be_false + ScratchPad.recorded.should == [] + end + end + + describe "($LOADED_FEATURES)" do + before :each do + @path = File.expand_path("load_fixture.rb", CODE_LOADING_DIR) + end + + it "stores an absolute path" do + @object.require(@path).should be_true + $LOADED_FEATURES.should include(@path) + end + + platform_is_not :windows do + describe "with symlinks" do + before :each do + @symlink_to_code_dir = tmp("codesymlink") + File.symlink(CODE_LOADING_DIR, @symlink_to_code_dir) + + $LOAD_PATH.delete(CODE_LOADING_DIR) + $LOAD_PATH.unshift(@symlink_to_code_dir) + end + + after :each do + rm_r @symlink_to_code_dir + end + + it "does not canonicalize the path and stores a path with symlinks" do + symlink_path = "#{@symlink_to_code_dir}/load_fixture.rb" + canonical_path = "#{CODE_LOADING_DIR}/load_fixture.rb" + @object.require(symlink_path).should be_true + ScratchPad.recorded.should == [:loaded] + + features = $LOADED_FEATURES.select { |path| path.end_with?('load_fixture.rb') } + features.should include(symlink_path) + features.should_not include(canonical_path) + end + + it "stores the same path that __FILE__ returns in the required file" do + symlink_path = "#{@symlink_to_code_dir}/load_fixture_and__FILE__.rb" + @object.require(symlink_path).should be_true + loaded_feature = $LOADED_FEATURES.last + ScratchPad.recorded.should == [loaded_feature] + end + + it "requires only once when a new matching file added to path" do + @object.require('load_fixture').should be_true + ScratchPad.recorded.should == [:loaded] + + symlink_to_code_dir_two = tmp("codesymlinktwo") + File.symlink("#{CODE_LOADING_DIR}/b", symlink_to_code_dir_two) + begin + $LOAD_PATH.unshift(symlink_to_code_dir_two) + + @object.require('load_fixture').should be_false + ensure + rm_r symlink_to_code_dir_two + end + end + end + + describe "with symlinks in the required feature and $LOAD_PATH" do + before :each do + @dir = tmp("realdir") + mkdir_p @dir + @file = "#{@dir}/realfile.rb" + touch(@file) { |f| f.puts 'ScratchPad << __FILE__' } + + @symlink_to_dir = tmp("symdir").freeze + File.symlink(@dir, @symlink_to_dir) + @symlink_to_file = "#{@dir}/symfile.rb" + File.symlink("realfile.rb", @symlink_to_file) + end + + after :each do + rm_r @dir, @symlink_to_dir + end + + it "canonicalizes the entry in $LOAD_PATH but not the filename passed to #require" do + $LOAD_PATH.unshift(@symlink_to_dir) + @object.require("symfile").should be_true + loaded_feature = "#{@dir}/symfile.rb" + ScratchPad.recorded.should == [loaded_feature] + $".last.should == loaded_feature + $LOAD_PATH[0].should == @symlink_to_dir + end + end + end + + it "does not store the path if the load fails" do + $LOAD_PATH << CODE_LOADING_DIR + saved_loaded_features = $LOADED_FEATURES.dup + -> { @object.require("raise_fixture.rb") }.should raise_error(RuntimeError) + $LOADED_FEATURES.should == saved_loaded_features + end + + it "does not load an absolute path that is already stored" do + $LOADED_FEATURES << @path + @object.require(@path).should be_false + ScratchPad.recorded.should == [] + end + + it "does not load a ./ relative path that is already stored" do + $LOADED_FEATURES << "./load_fixture.rb" + Dir.chdir CODE_LOADING_DIR do + @object.require("./load_fixture.rb").should be_false + end + ScratchPad.recorded.should == [] + end + + it "does not load a ../ relative path that is already stored" do + $LOADED_FEATURES << "../load_fixture.rb" + Dir.chdir CODE_LOADING_DIR do + @object.require("../load_fixture.rb").should be_false + end + ScratchPad.recorded.should == [] + end + + it "does not load a non-canonical path that is already stored" do + $LOADED_FEATURES << "code/../code/load_fixture.rb" + $LOAD_PATH << File.dirname(CODE_LOADING_DIR) + @object.require("code/../code/load_fixture.rb").should be_false + ScratchPad.recorded.should == [] + end + + it "respects being replaced with a new array" do + prev = $LOADED_FEATURES.dup + + @object.require(@path).should be_true + $LOADED_FEATURES.should include(@path) + + $LOADED_FEATURES.replace(prev) + + $LOADED_FEATURES.should_not include(@path) + @object.require(@path).should be_true + $LOADED_FEATURES.should include(@path) + end + + it "does not load twice the same file with and without extension" do + $LOAD_PATH << CODE_LOADING_DIR + @object.require("load_fixture.rb").should be_true + @object.require("load_fixture").should be_false + end + + describe "when a non-extensioned file is in $LOADED_FEATURES" do + before :each do + $LOADED_FEATURES << "load_fixture" + end + + it "loads a .rb extensioned file when a non extensioned file is in $LOADED_FEATURES" do + $LOAD_PATH << CODE_LOADING_DIR + @object.require("load_fixture").should be_true + ScratchPad.recorded.should == [:loaded] + end + + it "loads a .rb extensioned file from a subdirectory" do + $LOAD_PATH << File.dirname(CODE_LOADING_DIR) + @object.require("code/load_fixture").should be_true + ScratchPad.recorded.should == [:loaded] + end + + it "returns false if the file is not found" do + Dir.chdir File.dirname(CODE_LOADING_DIR) do + @object.require("load_fixture").should be_false + ScratchPad.recorded.should == [] + end + end + + it "returns false when passed a path and the file is not found" do + $LOADED_FEATURES << "code/load_fixture" + Dir.chdir CODE_LOADING_DIR do + @object.require("code/load_fixture").should be_false + ScratchPad.recorded.should == [] + end + end + end + + it "stores ../ relative paths as absolute paths" do + Dir.chdir CODE_LOADING_DIR do + @object.require("../code/load_fixture.rb").should be_true + end + $LOADED_FEATURES.should include(@path) + end + + it "stores ./ relative paths as absolute paths" do + Dir.chdir CODE_LOADING_DIR do + @object.require("./load_fixture.rb").should be_true + end + $LOADED_FEATURES.should include(@path) + end + + it "collapses duplicate path separators" do + $LOAD_PATH << "." + sep = File::Separator + File::Separator + path = ["..", "code", "load_fixture.rb"].join(sep) + Dir.chdir CODE_LOADING_DIR do + @object.require(path).should be_true + end + $LOADED_FEATURES.should include(@path) + end + + it "expands absolute paths containing .." do + path = File.join CODE_LOADING_DIR, "..", "code", "load_fixture.rb" + @object.require(path).should be_true + $LOADED_FEATURES.should include(@path) + end + + it "adds the suffix of the resolved filename" do + $LOAD_PATH << CODE_LOADING_DIR + @object.require("load_fixture").should be_true + $LOADED_FEATURES.should include(@path) + end + + it "does not load a non-canonical path for a file already loaded" do + $LOADED_FEATURES << @path + $LOAD_PATH << File.dirname(CODE_LOADING_DIR) + @object.require("code/../code/load_fixture.rb").should be_false + ScratchPad.recorded.should == [] + end + + it "does not load a ./ relative path for a file already loaded" do + $LOADED_FEATURES << @path + $LOAD_PATH << "an_irrelevant_dir" + Dir.chdir CODE_LOADING_DIR do + @object.require("./load_fixture.rb").should be_false + end + ScratchPad.recorded.should == [] + end + + it "does not load a ../ relative path for a file already loaded" do + $LOADED_FEATURES << @path + $LOAD_PATH << "an_irrelevant_dir" + Dir.chdir CODE_LOADING_DIR do + @object.require("../code/load_fixture.rb").should be_false + end + ScratchPad.recorded.should == [] + end + + it "unicode_normalize is part of core and not $LOADED_FEATURES" do + features = ruby_exe("puts $LOADED_FEATURES", options: '--disable-gems') + features.lines.each { |feature| + feature.should_not include("unicode_normalize") + } + + -> { @object.require("unicode_normalize") }.should raise_error(LoadError) + end + + it "does not load a file earlier on the $LOAD_PATH when other similar features were already loaded" do + Dir.chdir CODE_LOADING_DIR do + @object.send(@method, "../code/load_fixture").should be_true + end + ScratchPad.recorded.should == [:loaded] + + $LOAD_PATH.unshift "#{CODE_LOADING_DIR}/b" + # This loads because the above load was not on the $LOAD_PATH + @object.send(@method, "load_fixture").should be_true + ScratchPad.recorded.should == [:loaded, :loaded] + + $LOAD_PATH.unshift "#{CODE_LOADING_DIR}/c" + # This does not load because the above load was on the $LOAD_PATH + @object.send(@method, "load_fixture").should be_false + ScratchPad.recorded.should == [:loaded, :loaded] + end + end + + describe "(shell expansion)" do + before :each do + @path = File.expand_path("load_fixture.rb", CODE_LOADING_DIR) + @env_home = ENV["HOME"] + ENV["HOME"] = CODE_LOADING_DIR + end + + after :each do + ENV["HOME"] = @env_home + end + + # "#3171" + it "performs tilde expansion on a .rb file before storing paths in $LOADED_FEATURES" do + @object.require("~/load_fixture.rb").should be_true + $LOADED_FEATURES.should include(@path) + end + + it "performs tilde expansion on a non-extensioned file before storing paths in $LOADED_FEATURES" do + @object.require("~/load_fixture").should be_true + $LOADED_FEATURES.should include(@path) + end + end + + describe "(concurrently)" do + before :each do + ScratchPad.record [] + @path = File.expand_path "concurrent.rb", CODE_LOADING_DIR + @path2 = File.expand_path "concurrent2.rb", CODE_LOADING_DIR + @path3 = File.expand_path "concurrent3.rb", CODE_LOADING_DIR + end + + after :each do + ScratchPad.clear + $LOADED_FEATURES.delete @path + $LOADED_FEATURES.delete @path2 + $LOADED_FEATURES.delete @path3 + end + + # Quick note about these specs: + # + # The behavior we're spec'ing requires that t2 enter #require, see t1 is + # loading @path, grab a lock, and wait on it. + # + # We do make sure that t2 starts the require once t1 is in the middle + # of concurrent.rb, but we then need to get t2 to get far enough into #require + # to see t1's lock and try to lock it. + it "blocks a second thread from returning while the 1st is still requiring" do + fin = false + + t1_res = nil + t2_res = nil + + t2 = nil + t1 = Thread.new do + Thread.pass until t2 + Thread.current[:wait_for] = t2 + t1_res = @object.require(@path) + Thread.pass until fin + ScratchPad.recorded << :t1_post + end + + t2 = Thread.new do + Thread.pass until t1[:in_concurrent_rb] + $VERBOSE, @verbose = nil, $VERBOSE + begin + t2_res = @object.require(@path) + ScratchPad.recorded << :t2_post + ensure + $VERBOSE = @verbose + fin = true + end + end + + t1.join + t2.join + + t1_res.should be_true + t2_res.should be_false + + ScratchPad.recorded.should == [:con_pre, :con_post, :t2_post, :t1_post] + end + + it "blocks based on the path" do + t1_res = nil + t2_res = nil + + t2 = nil + t1 = Thread.new do + Thread.pass until t2 + Thread.current[:concurrent_require_thread] = t2 + t1_res = @object.require(@path2) + end + + t2 = Thread.new do + Thread.pass until t1[:in_concurrent_rb2] + t2_res = @object.require(@path3) + end + + t1.join + t2.join + + t1_res.should be_true + t2_res.should be_true + + ScratchPad.recorded.should == [:con2_pre, :con3, :con2_post] + end + + it "allows a 2nd require if the 1st raised an exception" do + fin = false + + t2_res = nil + + t2 = nil + t1 = Thread.new do + Thread.pass until t2 + Thread.current[:wait_for] = t2 + Thread.current[:con_raise] = true + + -> { + @object.require(@path) + }.should raise_error(RuntimeError) + + Thread.pass until fin + ScratchPad.recorded << :t1_post + end + + t2 = Thread.new do + Thread.pass until t1[:in_concurrent_rb] + $VERBOSE, @verbose = nil, $VERBOSE + begin + t2_res = @object.require(@path) + ScratchPad.recorded << :t2_post + ensure + $VERBOSE = @verbose + fin = true + end + end + + t1.join + t2.join + + t2_res.should be_true + + ScratchPad.recorded.should == [:con_pre, :con_pre, :con_post, :t2_post, :t1_post] + end + + # "redmine #5754" + it "blocks a 3rd require if the 1st raises an exception and the 2nd is still running" do + fin = false + + t1_res = nil + t2_res = nil + + raised = false + + t2 = nil + t1 = Thread.new do + Thread.current[:con_raise] = true + + -> { + @object.require(@path) + }.should raise_error(RuntimeError) + + raised = true + + # This hits the bug. Because MRI removes its internal lock from a table + # when the exception is raised, this #require doesn't see that t2 is in + # the middle of requiring the file, so this #require runs when it should not. + Thread.pass until t2 && t2[:in_concurrent_rb] + t1_res = @object.require(@path) + + Thread.pass until fin + ScratchPad.recorded << :t1_post + end + + t2 = Thread.new do + Thread.pass until raised + Thread.current[:wait_for] = t1 + begin + t2_res = @object.require(@path) + ScratchPad.recorded << :t2_post + ensure + fin = true + end + end + + t1.join + t2.join + + t1_res.should be_false + t2_res.should be_true + + ScratchPad.recorded.should == [:con_pre, :con_pre, :con_post, :t2_post, :t1_post] + end + end + + it "stores the missing path in a LoadError object" do + path = "abcd1234" + + -> { + @object.send(@method, path) + }.should raise_error(LoadError) { |e| + e.path.should == path + } + end + + platform_is :linux, :darwin do + it "does not store the missing path in a LoadError object when c-extension file exists but loading fails and passed absolute path without extension" do + # the error message is specific to what dlerror() returns + path = File.join CODE_LOADING_DIR, "a", "load_fixture" + -> { @object.send(@method, path) }.should raise_error(LoadError) { |e| + e.path.should == nil + } + end + end + + platform_is :darwin do + it "does not store the missing path in a LoadError object when c-extension file exists but loading fails and passed absolute path with extension" do + # the error message is specific to what dlerror() returns + path = File.join CODE_LOADING_DIR, "a", "load_fixture.bundle" + -> { @object.send(@method, path) }.should raise_error(LoadError) { |e| + e.path.should == nil + } + end + end +end diff --git a/spec/ruby/core/kernel/shared/sprintf.rb b/spec/ruby/core/kernel/shared/sprintf.rb new file mode 100644 index 0000000000..2b2c6c9b63 --- /dev/null +++ b/spec/ruby/core/kernel/shared/sprintf.rb @@ -0,0 +1,987 @@ +describe :kernel_sprintf, shared: true do + describe "integer formats" do + it "converts argument into Integer with to_int" do + obj = Object.new + def obj.to_i; 10; end + def obj.to_int; 10; end + + obj.should_receive(:to_int).and_return(10) + @method.call("%b", obj).should == "1010" + end + + it "converts argument into Integer with to_i if to_int isn't available" do + obj = Object.new + def obj.to_i; 10; end + + obj.should_receive(:to_i).and_return(10) + @method.call("%b", obj).should == "1010" + end + + it "converts String argument with Kernel#Integer" do + @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 + -> { + @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 + @method.call("%#{f}", 10).should == "1010" + end + + it "displays negative number as a two's complement prefixed with '..1'" do + @method.call("%#{f}", -10).should == "..1" + "0110" + end + + it "collapse negative number representation if it equals 1" do + @method.call("%#{f}", -1).should_not == "..11" + @method.call("%#{f}", -1).should == "..1" + end + end + end + + ["d", "i", "u"].each do |f| + describe f do + it "converts argument as a decimal number" do + @method.call("%#{f}", 112).should == "112" + @method.call("%#{f}", -112).should == "-112" + end + + it "works well with large numbers" do + @method.call("%#{f}", 1234567890987654321).should == "1234567890987654321" + end + + it "converts to the empty string if precision is 0 and value is 0" do + @method.call("%.#{f}", 0).should == "" + @method.call("%.0#{f}", 0).should == "" + end + end + end + + describe "o" do + it "converts argument as an octal number" do + @method.call("%o", 87).should == "127" + end + + it "displays negative number as a two's complement prefixed with '..7'" do + @method.call("%o", -87).should == "..7" + "651" + end + + it "collapse negative number representation if it equals 7" do + @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 + @method.call("%x", 196).should == "c4" + end + + it "displays negative number as a two's complement prefixed with '..f'" do + @method.call("%x", -196).should == "..f" + "3c" + end + + it "collapse negative number representation if it equals f" do + @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 + @method.call("%X", 196).should == "C4" + end + + it "displays negative number as a two's complement prefixed with '..f'" do + @method.call("%X", -196).should == "..F" + "3C" + end + + it "collapse negative number representation if it equals F" do + @method.call("%X", -1).should_not == "..FF" + @method.call("%X", -1).should == "..F" + end + end + end + + describe "float formats" do + it "converts argument into Float" do + obj = mock("float") + obj.should_receive(:to_f).and_return(9.6) + @method.call("%f", obj).should == "9.600000" + end + + it "raises TypeError exception if cannot convert to Float" do + -> { + @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 + @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 + @method.call("%#{f}", 1.123456789).should == "1.123457#{exp}+00" + end + + it "rounds the last significant digit to the closest one" do + @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 + @method.call("%#{f}", Float::INFINITY).should == "Inf" + @method.call("%#{f}", -Float::INFINITY).should == "-Inf" + end + + it "displays Float::NAN as NaN" do + @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 + @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 + @method.call("%f", 1.123456789).should == "1.123457" + end + + it "rounds the last significant digit to the closest one" do + @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 + @method.call("%f", Float::INFINITY).should == "Inf" + @method.call("%f", -Float::INFINITY).should == "-Inf" + end + + it "displays Float::NAN as NaN" do + @method.call("%f", Float::NAN).should == "NaN" + @method.call("%f", -Float::NAN).should == "NaN" + end + end + + {"g" => "e", "G" => "E"}.each_pair do |f, exp| + describe f do + context "the exponent is less than -4" do + it "converts a floating point number using exponential form" do + @method.call("%#{f}", 0.0000123456).should == "1.23456#{exp}-05" + @method.call("%#{f}", -0.0000123456).should == "-1.23456#{exp}-05" + + @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 + @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 + @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 + @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 + @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 + @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 + @method.call("%#{f}", Float::INFINITY).should == "Inf" + @method.call("%#{f}", -Float::INFINITY).should == "-Inf" + end + + it "displays Float::NAN as NaN" do + @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 + @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 + @method.call("%a", Float::INFINITY).should == "Inf" + @method.call("%a", -Float::INFINITY).should == "-Inf" + end + + it "displays Float::NAN as NaN" do + @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 + @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 + @method.call("%A", Float::INFINITY).should == "Inf" + @method.call("%A", -Float::INFINITY).should == "-Inf" + end + + it "displays Float::NAN as NaN" do + @method.call("%A", Float::NAN).should == "NaN" + @method.call("%A", -Float::NAN).should == "NaN" + end + end + end + + describe "other formats" do + describe "c" do + it "displays character if argument is a numeric code of character" do + @method.call("%c", 97).should == "a" + end + + it "displays character if argument is a single character string" do + @method.call("%c", "a").should == "a" + end + + it "displays only the first character if argument is a string of several characters" do + @method.call("%c", "abc").should == "a" + end + + it "displays no characters if argument is an empty string" do + @method.call("%c", "").should == "" + end + + it "raises TypeError if argument is not String or Integer and cannot be converted to them" do + -> { + @method.call("%c", []) + }.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 "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 + + describe "p" do + it "displays argument.inspect value" do + obj = mock("object") + obj.should_receive(:inspect).and_return("<inspect-result>") + @method.call("%p", obj).should == "<inspect-result>" + end + + it "substitutes 'nil' for nil" do + @method.call("%p", nil).should == "nil" + end + end + + describe "s" do + it "substitute argument passes as a string" do + @method.call("%s", "abc").should == "abc" + end + + it "substitutes '' for nil" do + @method.call("%s", nil).should == "" + end + + it "converts argument to string with to_s" do + obj = mock("string") + obj.should_receive(:to_s).and_return("abc") + @method.call("%s", obj).should == "abc" + end + + it "does not try to convert with to_str" do + obj = BasicObject.new + def obj.to_str + "abc" + end + + -> { + @method.call("%s", obj) + }.should raise_error(NoMethodError) + 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 + + 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 + @method.call("%%").should == "%" + @method.call("%%d").should == "%d" + end + end + end + + describe "flags" do + describe "space" do + context "applies to numeric formats bBdiouxXeEfgGaA" do + it "leaves a space at the start of non-negative numbers" do + @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 + @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 + @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 + @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 + end + end + + describe "(digit)$" do + it "specifies the absolute argument number for this field" do + @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 + -> { + @method.call("%4$d", 1, 2, 3) + }.should raise_error(ArgumentError) + end + + it "ignores '-' sign" do + @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 + -> { + @method.call("%1$d %d", 1, 2) + }.should raise_error(ArgumentError) + end + end + + 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 + @method.call("%#o", 87).should == "0127" + end + + it "does nothing for negative argument" do + @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 + @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" + + @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 + @method.call("%#b", 0).should == "0" + @method.call("%#B", 0).should == "0" + + @method.call("%#o", 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 + @method.call("%#.0a", 16.25).should == "0x1.p+4" + @method.call("%#.0A", 16.25).should == "0X1.P+4" + + @method.call("%#.0e", 100).should == "1.e+02" + @method.call("%#.0E", 100).should == "1.E+02" + + @method.call("%#.0f", 123.4).should == "123." + + @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 + @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 + @method.call("%#g", 123.4).should == "123.400" + @method.call("%#g", 123.4).should == "123.400" + end + end + end + + describe "+" do + context "applies to numeric formats bBdiouxXaAeEfgG" do + it "adds a leading plus sign to non-negative numbers" do + @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 + @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 + @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 + end + + describe "0 (zero)" do + context "applies to numeric formats bBdiouxXaAeEfgG and width is specified" do + it "pads with zeros, not spaces" do + @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 + @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 + @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 + @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 + @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 + @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 + -> { + @method.call("%*10d", 10, 112) + }.should raise_error(ArgumentError) + end + end + end + + describe "width" do + it "specifies the minimum number of characters that will be written to the result" do + @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 + @method.call("%5d", 1234567890).should == "1234567890" + end + end + + describe "precision" do + context "integer types" do + it "controls the number of decimal places displayed" do + @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" + + @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 + @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 + @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 + @method.call("%.1p", [1]).should == "[" + @method.call("%.2p", [1]).should == "[1" + @method.call("%.10p", [1]).should == "[1]" + @method.call("%.0p", [1]).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 + + describe "reference by name" do + describe "%<name>s style" do + it "uses value passed in a hash argument" do + @method.call("%<foo>d", foo: 123).should == "123" + end + + it "supports flags, width, precision and type" do + @method.call("%+20.10<foo>f", foo: 10.952).should == " +10.9520000000" + end + + it "allows to place name in any position" do + @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 + -> { + @method.call("%d %<foo>d", 1, foo: "123") + }.should raise_error(ArgumentError) + end + end + + describe "%{name} style" do + it "uses value passed in a hash argument" do + @method.call("%{foo}", foo: 123).should == "123" + end + + it "does not support type style" do + @method.call("%{foo}d", foo: 123).should == "123d" + end + + it "supports flags, width and precision" do + @method.call("%-20.5{foo}", foo: "123456789").should == "12345 " + end + + it "cannot be mixed with unnamed style" do + -> { + @method.call("%d %{foo}", 1, foo: "123") + }.should raise_error(ArgumentError) + end + + it "raises KeyError when there is no matching key" do + -> { + @method.call("%{foo}", {}) + }.should raise_error(KeyError) + end + + it "converts value to String with to_s" do + obj = Object.new + def obj.to_s; end + def obj.to_str; end + + obj.should_receive(:to_s).and_return("42") + obj.should_not_receive(:to_str) + + @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 new file mode 100644 index 0000000000..7ec0fe4c48 --- /dev/null +++ b/spec/ruby/core/kernel/shared/sprintf_encoding.rb @@ -0,0 +1,67 @@ +# Keep encoding-related specs in a separate shared example to be able to skip them in IO/File/StringIO specs. +# It's difficult to check result's encoding in the test after writing to a file/io buffer. +describe :kernel_sprintf_encoding, shared: true do + it "can produce a string with valid encoding" do + string = @method.call("good day %{valid}", valid: "e") + string.encoding.should == Encoding::UTF_8 + string.valid_encoding?.should be_true + 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".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".dup.force_encoding(Encoding::US_ASCII) + argument = "b\303\274r".dup.force_encoding(Encoding::UTF_8) + + result = @method.call(string, argument) + result.encoding.should equal(Encoding::UTF_8) + end + + it "raises Encoding::CompatibilityError if both encodings are ASCII compatible and there are not ASCII characters" do + string = "Ä %s".encode('windows-1252') + argument = "Ђ".encode('windows-1251') + + -> { + @method.call(string, argument) + }.should raise_error(Encoding::CompatibilityError) + 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 new file mode 100644 index 0000000000..23c400f9bd --- /dev/null +++ b/spec/ruby/core/kernel/singleton_class_spec.rb @@ -0,0 +1,74 @@ +# truffleruby_primitives: true +require_relative '../../spec_helper' + +describe "Kernel#singleton_class" do + it "returns class extended from an object" do + x = Object.new + xs = class << x; self; end + xs.should == x.singleton_class + end + + it "returns NilClass for nil" do + nil.singleton_class.should == NilClass + end + + it "returns TrueClass for true" do + true.singleton_class.should == TrueClass + end + + it "returns FalseClass for false" do + false.singleton_class.should == FalseClass + end + + 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 + -> { :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 new file mode 100644 index 0000000000..7d63fa7cc6 --- /dev/null +++ b/spec/ruby/core/kernel/singleton_method_spec.rb @@ -0,0 +1,85 @@ +require_relative '../../spec_helper' + +describe "Kernel#singleton_method" 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) + end + + it "returns a Method which can be called" do + obj = Object.new + def obj.foo; 42; end + obj.singleton_method(:foo).call.should == 42 + end + + it "only looks at singleton methods and not at methods in the class" do + klass = Class.new do + def foo + 42 + end + end + obj = klass.new + obj.foo.should == 42 + -> { + obj.singleton_method(:foo) + }.should raise_error(NameError) { |e| + # a NameError and not a NoMethodError + e.class.should == NameError + } + end + + it "raises a NameError if there is no such method" do + obj = Object.new + -> { + obj.singleton_method(:not_existing) + }.should raise_error(NameError) { |e| + # a NameError and not a NoMethodError + 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 new file mode 100644 index 0000000000..a127a439de --- /dev/null +++ b/spec/ruby/core/kernel/singleton_methods_spec.rb @@ -0,0 +1,192 @@ +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 + ReflectSpecs.o.singleton_methods(*@object).should == [] + end + + it "returns the names of module methods for a module" do + ReflectSpecs::M.singleton_methods(*@object).should include(:ms_pro, :ms_pub) + end + + it "does not return private module methods for a module" do + ReflectSpecs::M.singleton_methods(*@object).should_not include(:ms_pri) + end + + it "returns the names of class methods for a class" do + ReflectSpecs::A.singleton_methods(*@object).should include(:as_pro, :as_pub) + end + + it "does not return private class methods for a class" do + ReflectSpecs::A.singleton_methods(*@object).should_not include(:as_pri) + end + + it "returns the names of singleton methods for an object" do + ReflectSpecs.os.singleton_methods(*@object).should include(:os_pro, :os_pub) + end +end + +describe :kernel_singleton_methods_modules, shared: true do + it "does not return any included methods for a module including a module" do + ReflectSpecs::N.singleton_methods(*@object).should include(:ns_pro, :ns_pub) + end + + 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 extended with a module" do + ReflectSpecs.oe.singleton_methods(*@object).should include(:m_pro, :m_pub) + end + + it "returns a unique list for an object extended with a module" do + m = ReflectSpecs.oed.singleton_methods(*@object) + r = m.select { |x| x == :pub or x == :pro }.sort + r.should == [:pro, :pub] + end + + 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 extended with a module including a module" do + ReflectSpecs.oei.singleton_methods(*@object).should include(:n_pro, :n_pub, :m_pro, :m_pub) + end + + it "returns the names of inherited singleton methods for a subclass" do + ReflectSpecs::B.singleton_methods(*@object).should include(:as_pro, :as_pub, :bs_pro, :bs_pub) + end + + it "returns a unique list for a subclass" do + m = ReflectSpecs::B.singleton_methods(*@object) + r = m.select { |x| x == :pub or x == :pro }.sort + r.should == [:pro, :pub] + end + + it "returns the names of inherited singleton methods for a subclass including a module" do + ReflectSpecs::C.singleton_methods(*@object).should include(:as_pro, :as_pub, :cs_pro, :cs_pub) + end + + it "returns a unique list for a subclass including a module" do + m = ReflectSpecs::C.singleton_methods(*@object) + r = m.select { |x| x == :pub or x == :pro }.sort + r.should == [:pro, :pub] + end + + it "returns the names of inherited singleton methods for a subclass of a class including a module" do + ReflectSpecs::E.singleton_methods(*@object).should include(:ds_pro, :ds_pub, :es_pro, :es_pub) + end + + it "returns the names of inherited singleton methods for a subclass of a class that includes a module, where the subclass also includes a module" do + ReflectSpecs::F.singleton_methods(*@object).should include(:ds_pro, :ds_pub, :fs_pro, :fs_pub) + end + + it "returns the names of inherited singleton methods for a class extended with a module" do + ReflectSpecs::P.singleton_methods(*@object).should include(:m_pro, :m_pub) + end +end + +describe :kernel_singleton_methods_private_supers, shared: true do + it "does not return private singleton methods for an object extended with a module" do + ReflectSpecs.oe.singleton_methods(*@object).should_not include(:m_pri) + end + + it "does not return private singleton methods for an object extended with two modules" do + ReflectSpecs.oee.singleton_methods(*@object).should_not include(:m_pri) + end + + 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 + + it "does not return private singleton methods for a class extended with a module" do + ReflectSpecs::P.singleton_methods(*@object).should_not include(:m_pri) + end + + it "does not return private inherited singleton methods for a module including a module" do + ReflectSpecs::N.singleton_methods(*@object).should_not include(:ns_pri) + end + + it "does not return private inherited singleton methods for a class including a module" do + ReflectSpecs::D.singleton_methods(*@object).should_not include(:ds_pri) + end + + it "does not return private inherited singleton methods for a subclass" do + ReflectSpecs::B.singleton_methods(*@object).should_not include(:as_pri, :bs_pri) + end + + it "does not return private inherited singleton methods for a subclass including a module" do + ReflectSpecs::C.singleton_methods(*@object).should_not include(:as_pri, :cs_pri) + end + + it "does not return private inherited singleton methods for a subclass of a class including a module" do + ReflectSpecs::E.singleton_methods(*@object).should_not include(:ds_pri, :es_pri) + end + + it "does not return private inherited singleton methods for a subclass of a class that includes a module, where the subclass also includes a module" do + ReflectSpecs::F.singleton_methods(*@object).should_not include(:ds_pri, :fs_pri) + end +end + +describe "Kernel#singleton_methods" do + describe "when not passed an argument" do + it_behaves_like :kernel_singleton_methods, nil, [] + it_behaves_like :kernel_singleton_methods_supers, nil, [] + it_behaves_like :kernel_singleton_methods_modules, nil, [] + it_behaves_like :kernel_singleton_methods_private_supers, nil, [] + end + + describe "when passed true" do + it_behaves_like :kernel_singleton_methods, nil, true + 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 + it_behaves_like :kernel_singleton_methods, nil, false + 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 extended with a module" do + ReflectSpecs.oe.singleton_methods(false).should == [] + end + + it "returns an empty Array for an object extended with two modules" do + ReflectSpecs.oee.singleton_methods(false).should == [] + end + + it "returns an empty Array for an object extended with a module including a module" do + ReflectSpecs.oei.singleton_methods(false).should == [] + end + + it "returns the names of singleton methods of the subclass" do + ReflectSpecs::B.singleton_methods(false).should include(:bs_pro, :bs_pub) + end + + it "does not return names of inherited singleton methods for a subclass" do + ReflectSpecs::B.singleton_methods(false).should_not include(:as_pro, :as_pub) + end + + it "does not return the names of inherited singleton methods for a class extended with a module" do + ReflectSpecs::P.singleton_methods(false).should_not include(:m_pro, :m_pub) + end + end +end diff --git a/spec/ruby/core/kernel/sleep_spec.rb b/spec/ruby/core/kernel/sleep_spec.rb new file mode 100644 index 0000000000..e9c600aac4 --- /dev/null +++ b/spec/ruby/core/kernel/sleep_spec.rb @@ -0,0 +1,126 @@ +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.001).should >= 0 + end + + it "accepts an Integer" do + sleep(0).should >= 0 + end + + it "accepts a Rational" do + sleep(Rational(1, 999)).should >= 0 + end + + 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 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 + -> { sleep('2') }.should raise_error(TypeError) + end + + it "pauses execution indefinitely if not given a duration" do + running = false + t = Thread.new do + running = true + sleep + 5 + end + + Thread.pass until running + Thread.pass while t.status and t.status != "sleep" + + 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 + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/kernel/spawn_spec.rb b/spec/ruby/core/kernel/spawn_spec.rb new file mode 100644 index 0000000000..ba05b629d5 --- /dev/null +++ b/spec/ruby/core/kernel/spawn_spec.rb @@ -0,0 +1,25 @@ +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 +# run here as it is redundant and takes too long for little gain. +describe "Kernel#spawn" do + it "is a private method" do + Kernel.should have_private_instance_method(:spawn) + end + + it "executes the given command" do + -> { + Process.wait spawn("echo spawn") + }.should output_to_fd("spawn\n") + end +end + +describe "Kernel.spawn" do + it "executes the given command" do + -> { + Process.wait Kernel.spawn("echo spawn") + }.should output_to_fd("spawn\n") + end +end diff --git a/spec/ruby/core/kernel/sprintf_spec.rb b/spec/ruby/core/kernel/sprintf_spec.rb new file mode 100644 index 0000000000..5a4a90ff7a --- /dev/null +++ b/spec/ruby/core/kernel/sprintf_spec.rb @@ -0,0 +1,64 @@ +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 { + 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_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 { + 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_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 new file mode 100644 index 0000000000..95bb406f46 --- /dev/null +++ b/spec/ruby/core/kernel/srand_spec.rb @@ -0,0 +1,73 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#srand" do + before :each do + @seed = srand + end + + after :each do + srand(@seed) + end + + it "is a private method" do + Kernel.should have_private_instance_method(:srand) + end + + it "returns the previous seed value" do + srand(10) + 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 + srand(10) + rand.should == x + end + + it "defaults number to a random value" do + -> { srand }.should_not raise_error + srand.should_not == 0 + end + + it "accepts and uses a seed of 0" do + srand(0) + srand.should == 0 + end + + it "accepts a negative seed" do + srand(-17) + srand.should == -17 + end + + it "accepts an Integer as a seed" do + srand(0x12345678901234567890) + srand.should == 0x12345678901234567890 + end + + it "calls #to_int on seed" do + srand(3.8) + srand.should == 3 + + s = mock('seed') + s.should_receive(:to_int).and_return 0 + srand(s) + end + + it "raises a TypeError when passed nil" do + -> { srand(nil) }.should raise_error(TypeError) + end + + it "raises a TypeError when passed a String" do + -> { srand("7") }.should raise_error(TypeError) + end +end + +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 new file mode 100644 index 0000000000..9130bd159c --- /dev/null +++ b/spec/ruby/core/kernel/sub_spec.rb @@ -0,0 +1,26 @@ +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. +ruby_version_is ""..."1.9" do + describe "Kernel#sub" do + it "is a private method" do + Kernel.should have_private_instance_method(:sub) + end + end + + describe "Kernel#sub!" do + it "is a private method" do + Kernel.should have_private_instance_method(:sub!) + end + end + + describe "Kernel.sub" do + it "needs to be reviewed for spec completeness" + end + + describe "Kernel.sub!" do + it "needs to be reviewed for spec completeness" + end +end diff --git a/spec/ruby/core/kernel/syscall_spec.rb b/spec/ruby/core/kernel/syscall_spec.rb new file mode 100644 index 0000000000..32d07b3ae2 --- /dev/null +++ b/spec/ruby/core/kernel/syscall_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#syscall" do + it "is a private method" do + Kernel.should have_private_instance_method(:syscall) + end +end + +describe "Kernel.syscall" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/kernel/system_spec.rb b/spec/ruby/core/kernel/system_spec.rb new file mode 100644 index 0000000000..9bc03924dd --- /dev/null +++ b/spec/ruby/core/kernel/system_spec.rb @@ -0,0 +1,132 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe :kernel_system, shared: true do + it "executes the specified command in a subprocess" do + -> { @object.system("echo a") }.should output_to_fd("a\n") + + $?.should be_an_instance_of Process::Status + $?.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 + $?.should.success? + $?.exitstatus.should == 0 + end + + it "returns false when the command exits with a non-zero exit status" do + @object.system(ruby_cmd('exit 1')).should == false + + $?.should be_an_instance_of Process::Status + $?.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) + $?.should_not.success? + end + + it "does not write to stderr when command execution fails" do + -> { @object.system("sad") }.should output_to_fd("", STDERR) + end + + platform_is_not :windows do + before :each do + @shell = ENV['SHELL'] + end + + after :each do + ENV['SHELL'] = @shell + end + + it "executes with `sh` if the command contains shell characters" do + -> { @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" + -> { @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 + + before :each do + ENV['TEST_SH_EXPANSION'] = 'foo' + @shell_var = '$TEST_SH_EXPANSION' + platform_is :windows do + @shell_var = '%TEST_SH_EXPANSION%' + end + end + + after :each do + ENV.delete('TEST_SH_EXPANSION') + end + + it "expands shell variables when given a single string argument" do + -> { @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 + -> { @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 + -> { @object.system("echo", @shell_var) }.should output_to_fd("foo\n") + end + end + + platform_is :windows do + it "runs commands starting with any number of @ using shell" do + `#{ruby_cmd("p system 'does_not_exist'")} 2>NUL`.chomp.should == "nil" + @object.system('@does_not_exist 2>NUL').should == false + @object.system("@@@#{ruby_cmd('exit 0')}").should == true + end + end +end + +describe "Kernel#system" do + it "is a private method" do + Kernel.should have_private_instance_method(:system) + end + + it_behaves_like :kernel_system, :system, KernelSpecs::Method.new +end + +describe "Kernel.system" do + it_behaves_like :kernel_system, :system, Kernel +end diff --git a/spec/ruby/core/kernel/taint_spec.rb b/spec/ruby/core/kernel/taint_spec.rb new file mode 100644 index 0000000000..9a2efbaea0 --- /dev/null +++ b/spec/ruby/core/kernel/taint_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#taint" do + 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 new file mode 100644 index 0000000000..837eb1dafb --- /dev/null +++ b/spec/ruby/core/kernel/tainted_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#tainted?" do + 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 new file mode 100644 index 0000000000..f7720a6dc7 --- /dev/null +++ b/spec/ruby/core/kernel/tap_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#tap" do + it "always yields self and returns self" do + a = KernelSpecs::A.new + a.tap{|o| o.should equal(a); 42}.should equal(a) + end + + it "raises a LocalJumpError when no block given" do + -> { 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 new file mode 100644 index 0000000000..d26dc06361 --- /dev/null +++ b/spec/ruby/core/kernel/test_spec.rb @@ -0,0 +1,109 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#test" do + before :all do + @file = __dir__ + '/fixtures/classes.rb' + @dir = __dir__ + '/fixtures' + end + + it "is a private method" do + Kernel.should have_private_instance_method(:test) + end + + it "returns true when passed ?f if the argument is a regular file" do + Kernel.test(?f, @file).should == true + end + + it "returns true when passed ?e if the argument is a file" do + Kernel.test(?e, @file).should == true + end + + it "returns true when passed ?d if the argument is a directory" do + Kernel.test(?d, @dir).should == true + end + + platform_is_not :windows do + it "returns true when passed ?l if the argument is a symlink" do + link = tmp("file_symlink.lnk") + File.symlink(@file, link) + begin + Kernel.test(?l, link).should be_true + ensure + rm_r link + end + end + end + + it "returns true when passed ?r if the argument is readable by the effective uid" do + Kernel.test(?r, @file).should be_true + end + + it "returns true when passed ?R if the argument is readable by the real uid" do + Kernel.test(?R, @file).should be_true + end + + context "writable test" do + before do + @tmp_file = tmp("file.kernel.test") + touch(@tmp_file) + end + + after do + rm_r @tmp_file + end + + it "returns true when passed ?w if the argument is readable by the effective uid" do + Kernel.test(?w, @tmp_file).should be_true + end + + it "returns true when passed ?W if the argument is readable by the real uid" do + Kernel.test(?W, @tmp_file).should be_true + end + end + + context "time commands" do + before :each do + @tmp_file = File.new(tmp("file.kernel.test"), "w") + end + + after :each do + @tmp_file.close + rm_r @tmp_file + end + + it "returns the last access time for the provided file when passed ?A" do + Kernel.test(?A, @tmp_file).should == @tmp_file.atime + end + + it "returns the time at which the file was created when passed ?C" do + Kernel.test(?C, @tmp_file).should == @tmp_file.ctime + end + + it "returns the time at which the file was modified when passed ?M" do + Kernel.test(?M, @tmp_file).should == @tmp_file.mtime + end + end + + it "calls #to_path on second argument when passed ?f and a filename" do + p = mock('path') + p.should_receive(:to_path).and_return @file + Kernel.test(?f, p) + end + + it "calls #to_path on second argument when passed ?e and a filename" do + p = mock('path') + p.should_receive(:to_path).and_return @file + Kernel.test(?e, p) + end + + it "calls #to_path on second argument when passed ?d and a directory" do + p = mock('path') + p.should_receive(:to_path).and_return @dir + Kernel.test(?d, p) + end +end + +describe "Kernel.test" do + it "needs to be reviewed for spec completeness" +end 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 new file mode 100644 index 0000000000..64bfccb413 --- /dev/null +++ b/spec/ruby/core/kernel/throw_spec.rb @@ -0,0 +1,80 @@ +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 + catch(:blah) do + :value + throw :blah + fail("throw didn't transfer the control") + end.should be_nil + end + + it "transfers control to the innermost catch block waiting for the same symbol" do + one = two = three = 0 + catch :duplicate do + catch :duplicate do + catch :duplicate do + one = 1 + throw :duplicate + end + two = 2 + throw :duplicate + end + three = 3 + throw :duplicate + end + [one, two, three].should == [1, 2, 3] + end + + it "sets the return value of the catch block to nil by default" do + res = catch :blah do + throw :blah + end + res.should == nil + end + + it "sets the return value of the catch block to a value specified as second parameter" do + res = catch :blah do + throw :blah, :return_value + end + res.should == :return_value + end + + it "raises an ArgumentError if there is no catch block for the symbol" do + -> { throw :blah }.should raise_error(ArgumentError) + end + + it "raises an UncaughtThrowError if there is no catch block for the symbol" do + -> { throw :blah }.should raise_error(UncaughtThrowError) + end + + it "raises ArgumentError if 3 or more arguments provided" do + -> { + catch :blah do + throw :blah, :return_value, 2 + end + }.should raise_error(ArgumentError) + + -> { + catch :blah do + throw :blah, :return_value, 2, 3, 4, 5 + end + }.should raise_error(ArgumentError) + end + + it "can throw an object" do + -> { + obj = Object.new + catch obj do + throw obj + end + }.should_not raise_error(NameError) + end +end + +describe "Kernel#throw" do + it "is a private method" do + Kernel.should have_private_instance_method(:throw) + end +end diff --git a/spec/ruby/core/kernel/to_enum_spec.rb b/spec/ruby/core/kernel/to_enum_spec.rb new file mode 100644 index 0000000000..9d9945450f --- /dev/null +++ b/spec/ruby/core/kernel/to_enum_spec.rb @@ -0,0 +1,5 @@ +require_relative '../../spec_helper' + +describe "Kernel#to_enum" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/kernel/to_s_spec.rb b/spec/ruby/core/kernel/to_s_spec.rb new file mode 100644 index 0000000000..ea4b00151e --- /dev/null +++ b/spec/ruby/core/kernel/to_s_spec.rb @@ -0,0 +1,8 @@ +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 +end diff --git a/spec/ruby/core/kernel/trace_var_spec.rb b/spec/ruby/core/kernel/trace_var_spec.rb new file mode 100644 index 0000000000..3c84aa5e60 --- /dev/null +++ b/spec/ruby/core/kernel/trace_var_spec.rb @@ -0,0 +1,54 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#trace_var" do + before :each do + $Kernel_trace_var_global = nil + end + + after :each do + untrace_var :$Kernel_trace_var_global + + $Kernel_trace_var_global = nil + $Kernel_trace_var_extra = nil + end + + it "is a private method" do + Kernel.should have_private_instance_method(:trace_var) + end + + it "hooks assignments to a global variable" do + captured = nil + + trace_var :$Kernel_trace_var_global do |value| + captured = value + end + + $Kernel_trace_var_global = 'foo' + captured.should == 'foo' + end + + it "accepts a proc argument instead of a block" do + captured = nil + + trace_var :$Kernel_trace_var_global, proc {|value| captured = value} + + $Kernel_trace_var_global = 'foo' + captured.should == 'foo' + end + + # String arguments should be evaluated in the context of the caller. + it "accepts a String argument instead of a Proc or block" do + trace_var :$Kernel_trace_var_global, '$Kernel_trace_var_extra = true' + + $Kernel_trace_var_global = 'foo' + + $Kernel_trace_var_extra.should == true + end + + it "raises ArgumentError if no block or proc is provided" do + -> do + trace_var :$Kernel_trace_var_global + end.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/core/kernel/trap_spec.rb b/spec/ruby/core/kernel/trap_spec.rb new file mode 100644 index 0000000000..4c801a7215 --- /dev/null +++ b/spec/ruby/core/kernel/trap_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' + +describe "Kernel#trap" do + it "is a private method" do + Kernel.should have_private_instance_method(:trap) + end + + # 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 new file mode 100644 index 0000000000..ef3fa9a3e1 --- /dev/null +++ b/spec/ruby/core/kernel/trust_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#trust" do + 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 new file mode 100644 index 0000000000..47e8544bd4 --- /dev/null +++ b/spec/ruby/core/kernel/untaint_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#untaint" do + 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 new file mode 100644 index 0000000000..1925a3a836 --- /dev/null +++ b/spec/ruby/core/kernel/untrace_var_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#untrace_var" do + it "is a private method" do + Kernel.should have_private_instance_method(:untrace_var) + end +end + +describe "Kernel.untrace_var" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/kernel/untrust_spec.rb b/spec/ruby/core/kernel/untrust_spec.rb new file mode 100644 index 0000000000..8787ab3fc9 --- /dev/null +++ b/spec/ruby/core/kernel/untrust_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#untrust" do + 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 new file mode 100644 index 0000000000..29261be9c4 --- /dev/null +++ b/spec/ruby/core/kernel/untrusted_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#untrusted?" do + 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 new file mode 100644 index 0000000000..e03498c6dc --- /dev/null +++ b/spec/ruby/core/kernel/warn_spec.rb @@ -0,0 +1,298 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Kernel#warn" do + before :each do + @before_verbose = $VERBOSE + @before_separator = $/ + end + + after :each do + $VERBOSE = nil + $/ = @before_separator + $VERBOSE = @before_verbose + end + + it "is a private method" do + Kernel.should have_private_instance_method(:warn) + end + + 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 + -> { + $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 + -> { + $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 + -> { + $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 + -> { + $VERBOSE = nil + warn("this is some simple text") + }.should output(nil, "") + end + + it "writes each argument on a line when passed multiple arguments" do + -> { + $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 + -> { + $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 + -> { + $VERBOSE = true + warn + }.should output("", "") + end + + it "writes the default record separator and NOT $/ to $stderr after the warning message" do + -> { + $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 new file mode 100644 index 0000000000..e311dcee47 --- /dev/null +++ b/spec/ruby/core/kernel/yield_self_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/then' + +describe "Kernel#yield_self" do + it_behaves_like :kernel_then, :yield_self +end |
