From 40642cd3bc581d3bb402ea5e8e61cdfb868b4f68 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 5 Feb 2024 16:29:57 +0100 Subject: Update to ruby/spec@3fc4444 --- spec/ruby/.rubocop.yml | 6 +- spec/ruby/command_line/dash_r_spec.rb | 5 +- spec/ruby/command_line/syntax_error_spec.rb | 10 +- spec/ruby/core/class/attached_object_spec.rb | 8 +- spec/ruby/core/conditionvariable/broadcast_spec.rb | 1 - .../core/conditionvariable/marshal_dump_spec.rb | 1 - spec/ruby/core/conditionvariable/signal_spec.rb | 1 - spec/ruby/core/conditionvariable/wait_spec.rb | 1 - spec/ruby/core/data/initialize_spec.rb | 7 + spec/ruby/core/data/with_spec.rb | 35 +++ spec/ruby/core/exception/frozen_error_spec.rb | 16 ++ spec/ruby/core/hash/delete_spec.rb | 18 +- spec/ruby/core/hash/rehash_spec.rb | 30 +++ spec/ruby/core/integer/coerce_spec.rb | 142 +++++----- spec/ruby/core/integer/div_spec.rb | 8 + spec/ruby/core/io/read_spec.rb | 2 +- spec/ruby/core/io/select_spec.rb | 33 +++ spec/ruby/core/kernel/eval_spec.rb | 13 + spec/ruby/core/matchdata/begin_spec.rb | 28 ++ spec/ruby/core/matchdata/byteoffset_spec.rb | 4 +- spec/ruby/core/module/attr_accessor_spec.rb | 3 + spec/ruby/core/module/attr_reader_spec.rb | 3 + spec/ruby/core/module/attr_spec.rb | 3 + spec/ruby/core/module/attr_writer_spec.rb | 3 + spec/ruby/core/module/autoload_spec.rb | 1 - spec/ruby/core/module/prepend_spec.rb | 11 + spec/ruby/core/module/shared/attr_added.rb | 34 +++ spec/ruby/core/proc/arity_spec.rb | 16 ++ spec/ruby/core/proc/parameters_spec.rb | 5 + spec/ruby/core/proc/shared/to_s.rb | 14 +- spec/ruby/core/process/fixtures/kill.rb | 2 - spec/ruby/core/process/status/termsig_spec.rb | 2 +- spec/ruby/core/range/bsearch_spec.rb | 44 ++- spec/ruby/core/rational/coerce_spec.rb | 8 +- spec/ruby/core/regexp/shared/new.rb | 40 ++- spec/ruby/core/signal/trap_spec.rb | 8 + spec/ruby/core/string/fixtures/utf-8-encoding.rb | 7 - spec/ruby/core/string/rindex_spec.rb | 1 - .../core/thread/backtrace/location/lineno_spec.rb | 2 +- spec/ruby/core/thread/fetch_spec.rb | 30 +++ spec/ruby/core/thread/thread_variable_get_spec.rb | 2 +- spec/ruby/core/time/deconstruct_keys_spec.rb | 5 +- spec/ruby/language/assignments_spec.rb | 150 +++++++++++ spec/ruby/language/block_spec.rb | 81 +++++- spec/ruby/language/case_spec.rb | 16 ++ spec/ruby/language/defined_spec.rb | 121 ++++++++- spec/ruby/language/delegation_spec.rb | 36 ++- spec/ruby/language/fixtures/rescue/top_level.rb | 7 + spec/ruby/language/fixtures/super.rb | 48 ++++ spec/ruby/language/method_spec.rb | 1 + spec/ruby/language/optional_assignments_spec.rb | 294 +++++++++++++++++++-- spec/ruby/language/pattern_matching_spec.rb | 222 +++++++++++----- spec/ruby/language/rescue_spec.rb | 72 +++++ spec/ruby/language/safe_navigator_spec.rb | 80 ++++-- spec/ruby/language/super_spec.rb | 7 + spec/ruby/language/variables_spec.rb | 9 +- spec/ruby/library/coverage/result_spec.rb | 266 +++++++++++++++++-- spec/ruby/library/coverage/start_spec.rb | 81 +++++- .../objectspace/reachable_objects_from_spec.rb | 2 - spec/ruby/library/socket/shared/pack_sockaddr.rb | 3 + .../library/socket/socket/pack_sockaddr_in_spec.rb | 2 +- spec/ruby/library/socket/socket/pair_spec.rb | 2 +- spec/ruby/library/socket/socket/socketpair_spec.rb | 2 +- spec/ruby/library/socket/tcpserver/accept_spec.rb | 13 + .../library/socket/tcpsocket/initialize_spec.rb | 13 + .../library/socket/udpsocket/initialize_spec.rb | 13 + spec/ruby/library/socket/unixserver/accept_spec.rb | 11 + spec/ruby/library/socket/unixserver/for_fd_spec.rb | 2 +- .../library/socket/unixsocket/initialize_spec.rb | 10 + spec/ruby/library/socket/unixsocket/pair_spec.rb | 2 +- spec/ruby/library/yaml/fixtures/strings.rb | 56 ++-- spec/ruby/library/yaml/shared/each_document.rb | 2 +- spec/ruby/library/yaml/shared/load.rb | 2 +- spec/ruby/optional/capi/debug_spec.rb | 9 +- spec/ruby/optional/capi/ext/io_spec.c | 14 + spec/ruby/optional/capi/ext/kernel_spec.c | 10 + spec/ruby/optional/capi/ext/thread_spec.c | 4 +- spec/ruby/optional/capi/fixtures/kernel.rb | 6 +- spec/ruby/optional/capi/integer_spec.rb | 17 ++ spec/ruby/optional/capi/io_spec.rb | 19 +- spec/ruby/optional/capi/kernel_spec.rb | 43 ++- spec/ruby/shared/kernel/at_exit.rb | 5 +- spec/ruby/shared/queue/deque.rb | 34 ++- spec/ruby/shared/rational/coerce.rb | 46 ++-- spec/ruby/shared/sizedqueue/enque.rb | 34 ++- 85 files changed, 2082 insertions(+), 398 deletions(-) create mode 100644 spec/ruby/core/data/with_spec.rb create mode 100644 spec/ruby/core/module/shared/attr_added.rb delete mode 100644 spec/ruby/core/string/fixtures/utf-8-encoding.rb create mode 100644 spec/ruby/language/assignments_spec.rb create mode 100644 spec/ruby/language/fixtures/rescue/top_level.rb (limited to 'spec') diff --git a/spec/ruby/.rubocop.yml b/spec/ruby/.rubocop.yml index 9ad57dd51c..be32ce8900 100644 --- a/spec/ruby/.rubocop.yml +++ b/spec/ruby/.rubocop.yml @@ -43,9 +43,6 @@ Lint/InterpolationCheck: Lint/LiteralAsCondition: Enabled: false -Lint/RedundantRequireStatement: - Enabled: false - Lint/RedundantSplatExpansion: Enabled: false @@ -106,6 +103,9 @@ Lint/OutOfRangeRegexpRef: Lint/InheritException: Enabled: false +Lint/SafeNavigationChain: + Enabled: false + Lint/ElseLayout: Exclude: - 'language/if_spec.rb' diff --git a/spec/ruby/command_line/dash_r_spec.rb b/spec/ruby/command_line/dash_r_spec.rb index ea5bde5adf..9f673c53dc 100644 --- a/spec/ruby/command_line/dash_r_spec.rb +++ b/spec/ruby/command_line/dash_r_spec.rb @@ -16,7 +16,10 @@ describe "The -r command line option" do out = ruby_exe(fixture(__FILE__, "bad_syntax.rb"), options: "-r #{@test_file}", args: "2>&1", exit_status: 1) $?.should_not.success? out.should include("REQUIRED") - out.should include("syntax error") + + # it's tempting not to rely on error message and rely only on exception class name, + # but CRuby before 3.2 doesn't print class name for syntax error + out.should include_any_of("syntax error", "SyntaxError") end it "does not require the file if the main script file does not exist" do diff --git a/spec/ruby/command_line/syntax_error_spec.rb b/spec/ruby/command_line/syntax_error_spec.rb index 444ea9635c..9ba87b9e22 100644 --- a/spec/ruby/command_line/syntax_error_spec.rb +++ b/spec/ruby/command_line/syntax_error_spec.rb @@ -3,11 +3,17 @@ require_relative '../spec_helper' describe "The interpreter" do it "prints an error when given a file with invalid syntax" do out = ruby_exe(fixture(__FILE__, "bad_syntax.rb"), args: "2>&1", exit_status: 1) - out.should include "syntax error" + + # it's tempting not to rely on error message and rely only on exception class name, + # but CRuby before 3.2 doesn't print class name for syntax error + out.should include_any_of("syntax error", "SyntaxError") end it "prints an error when given code via -e with invalid syntax" do out = ruby_exe(nil, args: "-e 'a{' 2>&1", exit_status: 1) - out.should include "syntax error" + + # it's tempting not to rely on error message and rely only on exception class name, + # but CRuby before 3.2 doesn't print class name for syntax error + out.should include_any_of("syntax error", "SyntaxError") end end diff --git a/spec/ruby/core/class/attached_object_spec.rb b/spec/ruby/core/class/attached_object_spec.rb index 115d5fa563..d97f9cc93e 100644 --- a/spec/ruby/core/class/attached_object_spec.rb +++ b/spec/ruby/core/class/attached_object_spec.rb @@ -19,13 +19,13 @@ ruby_version_is '3.2' do it "raises TypeError if the class is not a singleton class" do a = Class.new - -> { a.attached_object }.should raise_error(TypeError) + -> { a.attached_object }.should raise_error(TypeError, /is not a singleton class/) end it "raises TypeError for special singleton classes" do - -> { nil.singleton_class.attached_object }.should raise_error(TypeError) - -> { true.singleton_class.attached_object }.should raise_error(TypeError) - -> { false.singleton_class.attached_object }.should raise_error(TypeError) + -> { nil.singleton_class.attached_object }.should raise_error(TypeError, /`NilClass' is not a singleton class/) + -> { true.singleton_class.attached_object }.should raise_error(TypeError, /`TrueClass' is not a singleton class/) + -> { false.singleton_class.attached_object }.should raise_error(TypeError, /`FalseClass' is not a singleton class/) end end end diff --git a/spec/ruby/core/conditionvariable/broadcast_spec.rb b/spec/ruby/core/conditionvariable/broadcast_spec.rb index d88159df23..55a7b89c72 100644 --- a/spec/ruby/core/conditionvariable/broadcast_spec.rb +++ b/spec/ruby/core/conditionvariable/broadcast_spec.rb @@ -1,5 +1,4 @@ require_relative '../../spec_helper' -require 'thread' describe "ConditionVariable#broadcast" do it "releases all threads waiting in line for this resource" do diff --git a/spec/ruby/core/conditionvariable/marshal_dump_spec.rb b/spec/ruby/core/conditionvariable/marshal_dump_spec.rb index f951a13e28..88b1cc38c1 100644 --- a/spec/ruby/core/conditionvariable/marshal_dump_spec.rb +++ b/spec/ruby/core/conditionvariable/marshal_dump_spec.rb @@ -1,5 +1,4 @@ require_relative '../../spec_helper' -require 'thread' describe "ConditionVariable#marshal_dump" do it "raises a TypeError" do diff --git a/spec/ruby/core/conditionvariable/signal_spec.rb b/spec/ruby/core/conditionvariable/signal_spec.rb index 86383073f1..43a9cc611b 100644 --- a/spec/ruby/core/conditionvariable/signal_spec.rb +++ b/spec/ruby/core/conditionvariable/signal_spec.rb @@ -1,5 +1,4 @@ require_relative '../../spec_helper' -require 'thread' describe "ConditionVariable#signal" do it "releases the first thread waiting in line for this resource" do diff --git a/spec/ruby/core/conditionvariable/wait_spec.rb b/spec/ruby/core/conditionvariable/wait_spec.rb index 9a68c2b5a1..fe73e513c0 100644 --- a/spec/ruby/core/conditionvariable/wait_spec.rb +++ b/spec/ruby/core/conditionvariable/wait_spec.rb @@ -1,5 +1,4 @@ require_relative '../../spec_helper' -require 'thread' describe "ConditionVariable#wait" do it "calls #sleep on the given object" do diff --git a/spec/ruby/core/data/initialize_spec.rb b/spec/ruby/core/data/initialize_spec.rb index 94470cd108..2c36bd3ac4 100644 --- a/spec/ruby/core/data/initialize_spec.rb +++ b/spec/ruby/core/data/initialize_spec.rb @@ -31,6 +31,13 @@ ruby_version_is "3.2" do data.unit.should == "km" end + it "accepts String keyword arguments" do + data = DataSpecs::Measure.new("amount" => 42, "unit" => "km") + + data.amount.should == 42 + data.unit.should == "km" + end + it "raises ArgumentError if no arguments are given" do -> { DataSpecs::Measure.new diff --git a/spec/ruby/core/data/with_spec.rb b/spec/ruby/core/data/with_spec.rb new file mode 100644 index 0000000000..97e34c951f --- /dev/null +++ b/spec/ruby/core/data/with_spec.rb @@ -0,0 +1,35 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +ruby_version_is "3.2" do + describe "Data#with" do + it "returns self if given no arguments" do + data = DataSpecs::Measure.new(amount: 42, unit: "km") + data = data.with.should.equal?(data) + end + + it "accepts keyword arguments" do + data = DataSpecs::Measure.new(amount: 42, unit: "km") + data = data.with(amount: 4, unit: "m") + + data.amount.should == 4 + data.unit.should == "m" + end + + it "accepts String keyword arguments" do + data = DataSpecs::Measure.new(amount: 42, unit: "km") + data = data.with("amount" => 4, "unit" => "m") + + data.amount.should == 4 + data.unit.should == "m" + end + + it "raises ArgumentError if no keyword arguments are given" do + data = DataSpecs::Measure.new(amount: 42, unit: "km") + + -> { + data.with(4, "m") + }.should raise_error(ArgumentError, "wrong number of arguments (given 2, expected 0)") + end + end +end diff --git a/spec/ruby/core/exception/frozen_error_spec.rb b/spec/ruby/core/exception/frozen_error_spec.rb index 2efdc239d8..979ec2ff98 100644 --- a/spec/ruby/core/exception/frozen_error_spec.rb +++ b/spec/ruby/core/exception/frozen_error_spec.rb @@ -20,3 +20,19 @@ describe "FrozenError#receiver" do end end end + +describe "Modifying a frozen object" do + context "#inspect is redefined and modifies the object" do + it "returns ... instead of String representation of object" do + object = Object.new + def object.inspect; @a = 1 end + def object.modify; @a = 2 end + + object.freeze + + # CRuby's message contains multiple whitespaces before '...'. + # So handle both multiple and single whitespace. + -> { object.modify }.should raise_error(FrozenError, /can't modify frozen .*?: \s*.../) + end + end +end diff --git a/spec/ruby/core/hash/delete_spec.rb b/spec/ruby/core/hash/delete_spec.rb index b262e8846b..3e3479c69c 100644 --- a/spec/ruby/core/hash/delete_spec.rb +++ b/spec/ruby/core/hash/delete_spec.rb @@ -24,7 +24,7 @@ describe "Hash#delete" do it "allows removing a key while iterating" do h = { a: 1, b: 2 } visited = [] - h.each_pair { |k,v| + h.each_pair { |k, v| visited << k h.delete(k) } @@ -32,13 +32,27 @@ describe "Hash#delete" do h.should == {} end + it "allows removing a key while iterating for big hashes" do + h = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10, + k: 11, l: 12, m: 13, n: 14, o: 15, p: 16, q: 17, r: 18, s: 19, t: 20, + u: 21, v: 22, w: 23, x: 24, y: 25, z: 26 } + visited = [] + h.each_pair { |k, v| + visited << k + h.delete(k) + } + visited.should == [:a, :b, :c, :d, :e, :f, :g, :h, :i, :j, :k, :l, :m, + :n, :o, :p, :q, :r, :s, :t, :u, :v, :w, :x, :y, :z] + h.should == {} + end + it "accepts keys with private #hash method" do key = HashSpecs::KeyWithPrivateHash.new { key => 5 }.delete(key).should == 5 end it "raises a FrozenError if called on a frozen instance" do - -> { HashSpecs.frozen_hash.delete("foo") }.should raise_error(FrozenError) + -> { HashSpecs.frozen_hash.delete("foo") }.should raise_error(FrozenError) -> { HashSpecs.empty_frozen_hash.delete("foo") }.should raise_error(FrozenError) end end diff --git a/spec/ruby/core/hash/rehash_spec.rb b/spec/ruby/core/hash/rehash_spec.rb index 0049080456..db3e91b166 100644 --- a/spec/ruby/core/hash/rehash_spec.rb +++ b/spec/ruby/core/hash/rehash_spec.rb @@ -77,6 +77,36 @@ describe "Hash#rehash" do h.keys.should_not.include? [1] end + it "iterates keys in insertion order" do + key = Class.new do + attr_reader :name + + def initialize(name) + @name = name + end + + def hash + 123 + end + end + + a, b, c, d = key.new('a'), key.new('b'), key.new('c'), key.new('d') + h = { a => 1, b => 2, c => 3, d => 4 } + h.size.should == 4 + + key.class_exec do + def eql?(other) + true + end + end + + h.rehash + h.size.should == 1 + k, v = h.first + k.name.should == 'a' + v.should == 4 + end + it "raises a FrozenError if called on a frozen instance" do -> { HashSpecs.frozen_hash.rehash }.should raise_error(FrozenError) -> { HashSpecs.empty_frozen_hash.rehash }.should raise_error(FrozenError) diff --git a/spec/ruby/core/integer/coerce_spec.rb b/spec/ruby/core/integer/coerce_spec.rb index c967b0dea3..13fe7b3321 100644 --- a/spec/ruby/core/integer/coerce_spec.rb +++ b/spec/ruby/core/integer/coerce_spec.rb @@ -1,98 +1,96 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do - - require 'bigdecimal' - - describe "Integer#coerce" do - context "fixnum" do - describe "when given a Fixnum" do - it "returns an array containing two Fixnums" do - 1.coerce(2).should == [2, 1] - 1.coerce(2).map { |i| i.class }.should == [Integer, Integer] - end +describe "Integer#coerce" do + context "fixnum" do + describe "when given a Fixnum" do + it "returns an array containing two Fixnums" do + 1.coerce(2).should == [2, 1] + 1.coerce(2).map { |i| i.class }.should == [Integer, Integer] end + end - describe "when given a String" do - it "raises an ArgumentError when trying to coerce with a non-number String" do - -> { 1.coerce(":)") }.should raise_error(ArgumentError) - end - - it "returns an array containing two Floats" do - 1.coerce("2").should == [2.0, 1.0] - 1.coerce("-2").should == [-2.0, 1.0] - end + describe "when given a String" do + it "raises an ArgumentError when trying to coerce with a non-number String" do + -> { 1.coerce(":)") }.should raise_error(ArgumentError) end - it "raises a TypeError when trying to coerce with nil" do - -> { 1.coerce(nil) }.should raise_error(TypeError) + it "returns an array containing two Floats" do + 1.coerce("2").should == [2.0, 1.0] + 1.coerce("-2").should == [-2.0, 1.0] end + end - it "tries to convert the given Object into a Float by using #to_f" do - (obj = mock('1.0')).should_receive(:to_f).and_return(1.0) - 2.coerce(obj).should == [1.0, 2.0] + it "raises a TypeError when trying to coerce with nil" do + -> { 1.coerce(nil) }.should raise_error(TypeError) + end - (obj = mock('0')).should_receive(:to_f).and_return('0') - -> { 2.coerce(obj).should == [1.0, 2.0] }.should raise_error(TypeError) - end + it "tries to convert the given Object into a Float by using #to_f" do + (obj = mock('1.0')).should_receive(:to_f).and_return(1.0) + 2.coerce(obj).should == [1.0, 2.0] - it "raises a TypeError when given an Object that does not respond to #to_f" do - -> { 1.coerce(mock('x')) }.should raise_error(TypeError) - -> { 1.coerce(1..4) }.should raise_error(TypeError) - -> { 1.coerce(:test) }.should raise_error(TypeError) - end + (obj = mock('0')).should_receive(:to_f).and_return('0') + -> { 2.coerce(obj).should == [1.0, 2.0] }.should raise_error(TypeError) end - context "bignum" do - it "coerces other to a Bignum and returns [other, self] when passed a Fixnum" do - a = bignum_value - ary = a.coerce(2) + it "raises a TypeError when given an Object that does not respond to #to_f" do + -> { 1.coerce(mock('x')) }.should raise_error(TypeError) + -> { 1.coerce(1..4) }.should raise_error(TypeError) + -> { 1.coerce(:test) }.should raise_error(TypeError) + end + end - ary[0].should be_kind_of(Integer) - ary[1].should be_kind_of(Integer) - ary.should == [2, a] - end + context "bignum" do + it "coerces other to a Bignum and returns [other, self] when passed a Fixnum" do + a = bignum_value + ary = a.coerce(2) - it "returns [other, self] when passed a Bignum" do - a = bignum_value - b = bignum_value - ary = a.coerce(b) + ary[0].should be_kind_of(Integer) + ary[1].should be_kind_of(Integer) + ary.should == [2, a] + end - ary[0].should be_kind_of(Integer) - ary[1].should be_kind_of(Integer) - ary.should == [b, a] - end + it "returns [other, self] when passed a Bignum" do + a = bignum_value + b = bignum_value + ary = a.coerce(b) - it "raises a TypeError when not passed a Fixnum or Bignum" do - a = bignum_value + ary[0].should be_kind_of(Integer) + ary[1].should be_kind_of(Integer) + ary.should == [b, a] + end - -> { a.coerce(nil) }.should raise_error(TypeError) - -> { a.coerce(mock('str')) }.should raise_error(TypeError) - -> { a.coerce(1..4) }.should raise_error(TypeError) - -> { a.coerce(:test) }.should raise_error(TypeError) - end + it "raises a TypeError when not passed a Fixnum or Bignum" do + a = bignum_value - it "coerces both values to Floats and returns [other, self] when passed a Float" do - a = bignum_value - a.coerce(1.2).should == [1.2, a.to_f] - end + -> { a.coerce(nil) }.should raise_error(TypeError) + -> { a.coerce(mock('str')) }.should raise_error(TypeError) + -> { a.coerce(1..4) }.should raise_error(TypeError) + -> { a.coerce(:test) }.should raise_error(TypeError) + end - it "coerces both values to Floats and returns [other, self] when passed a String" do - a = bignum_value - a.coerce("123").should == [123.0, a.to_f] - end + it "coerces both values to Floats and returns [other, self] when passed a Float" do + a = bignum_value + a.coerce(1.2).should == [1.2, a.to_f] + end - it "calls #to_f to coerce other to a Float" do - b = mock("bignum value") - b.should_receive(:to_f).and_return(1.2) + it "coerces both values to Floats and returns [other, self] when passed a String" do + a = bignum_value + a.coerce("123").should == [123.0, a.to_f] + end - a = bignum_value - ary = a.coerce(b) + it "calls #to_f to coerce other to a Float" do + b = mock("bignum value") + b.should_receive(:to_f).and_return(1.2) - ary.should == [1.2, a.to_f] - end + a = bignum_value + ary = a.coerce(b) + + ary.should == [1.2, a.to_f] end + end + ruby_version_is ""..."3.4" do + require 'bigdecimal' context "bigdecimal" do it "produces Floats" do x, y = 3.coerce(BigDecimal("3.4")) @@ -102,6 +100,6 @@ ruby_version_is ""..."3.4" do y.should == 3.0 end end - end + end diff --git a/spec/ruby/core/integer/div_spec.rb b/spec/ruby/core/integer/div_spec.rb index 344e095179..2eb9c0623b 100644 --- a/spec/ruby/core/integer/div_spec.rb +++ b/spec/ruby/core/integer/div_spec.rb @@ -143,4 +143,12 @@ describe "Integer#div" do -> { @bignum.div(-0) }.should raise_error(ZeroDivisionError) end end + + context "rational" do + it "returns self divided by the given argument as an Integer" do + 2.div(6/5r).should == 1 + 1.div(6/5r).should == 0 + 5.div(6/5r).should == 4 + end + end end diff --git a/spec/ruby/core/io/read_spec.rb b/spec/ruby/core/io/read_spec.rb index db11468ea4..b37c6c7121 100644 --- a/spec/ruby/core/io/read_spec.rb +++ b/spec/ruby/core/io/read_spec.rb @@ -331,7 +331,7 @@ describe "IO#read" do @io.read(0).should == '' @io.pos.should == 0 - @io.getc.chr.should == '1' + @io.getc.should == '1' end it "is at end-of-file when everything has been read" do diff --git a/spec/ruby/core/io/select_spec.rb b/spec/ruby/core/io/select_spec.rb index 1e4e50a81b..3893e7620f 100644 --- a/spec/ruby/core/io/select_spec.rb +++ b/spec/ruby/core/io/select_spec.rb @@ -114,6 +114,39 @@ describe "IO.select" do it "raises an ArgumentError when passed a negative timeout" do -> { IO.select(nil, nil, nil, -5)}.should raise_error(ArgumentError) end + + describe "returns the available descriptors when the file descriptor" do + it "is in both read and error arrays" do + @wr.write("foobar") + result = IO.select([@rd], nil, [@rd]) + result.should == [[@rd], [], []] + end + + it "is in both write and error arrays" do + result = IO.select(nil, [@wr], [@wr]) + result.should == [[], [@wr], []] + end + + it "is in both read and write arrays" do + filename = tmp("IO_select_read_write_file") + w = File.open(filename, 'w+') + begin + IO.select([w], [w], []).should == [[w], [w], []] + ensure + w.close + rm_r filename + end + + IO.select([@wr], [@wr], []).should == [[], [@wr], []] + + @wr.write("foobar") + # CRuby on macOS returns [[@rd], [@rd], []], weird but we accept it here, probably only for pipe read-end + [ + [[@rd], [], []], + [[@rd], [@rd], []] + ].should.include? IO.select([@rd], [@rd], []) + end + end end describe "IO.select when passed nil for timeout" do diff --git a/spec/ruby/core/kernel/eval_spec.rb b/spec/ruby/core/kernel/eval_spec.rb index 3dfc863368..f3b025fb7b 100644 --- a/spec/ruby/core/kernel/eval_spec.rb +++ b/spec/ruby/core/kernel/eval_spec.rb @@ -261,6 +261,19 @@ describe "Kernel#eval" do 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 + # 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 diff --git a/spec/ruby/core/matchdata/begin_spec.rb b/spec/ruby/core/matchdata/begin_spec.rb index 85c454da56..54b4e0a33f 100644 --- a/spec/ruby/core/matchdata/begin_spec.rb +++ b/spec/ruby/core/matchdata/begin_spec.rb @@ -36,6 +36,18 @@ describe "MatchData#begin" do match_data = /(.)(.)(\d+)(\d)/.match("THX1138.") match_data.begin(obj).should == 2 end + + it "raises IndexError if index is out of bounds" do + match_data = /(?foo)(?bar)/.match("foobar") + + -> { + match_data.begin(-1) + }.should raise_error(IndexError, "index -1 out of matches") + + -> { + match_data.begin(3) + }.should raise_error(IndexError, "index 3 out of matches") + end end context "when passed a String argument" do @@ -68,6 +80,14 @@ describe "MatchData#begin" do match_data = /(?<æ>.)(.)(?\d+)(\d)/.match("THX1138.") match_data.begin("æ").should == 1 end + + it "raises IndexError if there is no group with the provided name" do + match_data = /(?foo)(?bar)/.match("foobar") + + -> { + match_data.begin("y") + }.should raise_error(IndexError, "undefined group name reference: y") + end end context "when passed a Symbol argument" do @@ -100,5 +120,13 @@ describe "MatchData#begin" do match_data = /(?<æ>.)(.)(?\d+)(\d)/.match("THX1138.") match_data.begin(:æ).should == 1 end + + it "raises IndexError if there is no group with the provided name" do + match_data = /(?foo)(?bar)/.match("foobar") + + -> { + match_data.begin(:y) + }.should raise_error(IndexError, "undefined group name reference: y") + end end end diff --git a/spec/ruby/core/matchdata/byteoffset_spec.rb b/spec/ruby/core/matchdata/byteoffset_spec.rb index 6036097834..b27267fd0e 100644 --- a/spec/ruby/core/matchdata/byteoffset_spec.rb +++ b/spec/ruby/core/matchdata/byteoffset_spec.rb @@ -60,7 +60,7 @@ describe "MatchData#byteoffset" do m.byteoffset(obj).should == [3, 6] end - it "raises IndexError if there is no group with provided name" do + it "raises IndexError if there is no group with the provided name" do m = /(?foo)(?bar)/.match("foobar") -> { @@ -72,7 +72,7 @@ describe "MatchData#byteoffset" do }.should raise_error(IndexError, "undefined group name reference: y") end - it "raises IndexError if index is out of matches" do + it "raises IndexError if index is out of bounds" do m = /(?foo)(?bar)/.match("foobar") -> { diff --git a/spec/ruby/core/module/attr_accessor_spec.rb b/spec/ruby/core/module/attr_accessor_spec.rb index eea5b4b64b..503dccc61e 100644 --- a/spec/ruby/core/module/attr_accessor_spec.rb +++ b/spec/ruby/core/module/attr_accessor_spec.rb @@ -1,5 +1,6 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' +require_relative 'shared/attr_added' describe "Module#attr_accessor" do it "creates a getter and setter for each given attribute name" do @@ -106,4 +107,6 @@ describe "Module#attr_accessor" do 1.foobar.should be_nil end end + + it_behaves_like :module_attr_added, :attr_accessor end diff --git a/spec/ruby/core/module/attr_reader_spec.rb b/spec/ruby/core/module/attr_reader_spec.rb index 0b6d996719..37fd537ff5 100644 --- a/spec/ruby/core/module/attr_reader_spec.rb +++ b/spec/ruby/core/module/attr_reader_spec.rb @@ -1,5 +1,6 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' +require_relative 'shared/attr_added' describe "Module#attr_reader" do it "creates a getter for each given attribute name" do @@ -67,4 +68,6 @@ describe "Module#attr_reader" do (attr_reader :foo, 'bar').should == [:foo, :bar] end end + + it_behaves_like :module_attr_added, :attr_reader end diff --git a/spec/ruby/core/module/attr_spec.rb b/spec/ruby/core/module/attr_spec.rb index 72a6646b1e..2f9f4e26dc 100644 --- a/spec/ruby/core/module/attr_spec.rb +++ b/spec/ruby/core/module/attr_spec.rb @@ -1,5 +1,6 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' +require_relative 'shared/attr_added' describe "Module#attr" do before :each do @@ -153,4 +154,6 @@ describe "Module#attr" do (attr :qux, true).should == [:qux, :qux=] end end + + it_behaves_like :module_attr_added, :attr end diff --git a/spec/ruby/core/module/attr_writer_spec.rb b/spec/ruby/core/module/attr_writer_spec.rb index aaea0ce43f..5b863ef88c 100644 --- a/spec/ruby/core/module/attr_writer_spec.rb +++ b/spec/ruby/core/module/attr_writer_spec.rb @@ -1,5 +1,6 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' +require_relative 'shared/attr_added' describe "Module#attr_writer" do it "creates a setter for each given attribute name" do @@ -77,4 +78,6 @@ describe "Module#attr_writer" do (attr_writer :foo, 'bar').should == [:foo=, :bar=] end end + + it_behaves_like :module_attr_added, :attr_writer end diff --git a/spec/ruby/core/module/autoload_spec.rb b/spec/ruby/core/module/autoload_spec.rb index ab4df7600e..271c55ebf0 100644 --- a/spec/ruby/core/module/autoload_spec.rb +++ b/spec/ruby/core/module/autoload_spec.rb @@ -1,7 +1,6 @@ require_relative '../../spec_helper' require_relative '../../fixtures/code_loading' require_relative 'fixtures/classes' -require 'thread' describe "Module#autoload?" do it "returns the name of the file that will be autoloaded" do diff --git a/spec/ruby/core/module/prepend_spec.rb b/spec/ruby/core/module/prepend_spec.rb index 96598e7209..f80cfbcca1 100644 --- a/spec/ruby/core/module/prepend_spec.rb +++ b/spec/ruby/core/module/prepend_spec.rb @@ -726,6 +726,17 @@ describe "Module#prepend" do ary.should == [3, 2, 1] end + it "does not prepend a second copy if the module already indirectly exists in the hierarchy" do + mod = Module.new do; end + submod = Module.new do; end + klass = Class.new do; end + klass.include(mod) + mod.prepend(submod) + klass.include(mod) + + klass.ancestors.take(4).should == [klass, submod, mod, Object] + end + describe "called on a module" do describe "included into a class" it "does not obscure the module's methods from reflective access" do diff --git a/spec/ruby/core/module/shared/attr_added.rb b/spec/ruby/core/module/shared/attr_added.rb new file mode 100644 index 0000000000..ce832cdcff --- /dev/null +++ b/spec/ruby/core/module/shared/attr_added.rb @@ -0,0 +1,34 @@ +describe :module_attr_added, shared: true do + it "calls method_added for normal classes" do + ScratchPad.record [] + + cls = Class.new do + class << self + def method_added(name) + ScratchPad.recorded << name + end + end + end + + cls.send(@method, :foo) + + ScratchPad.recorded.each {|name| name.to_s.should =~ /foo[=]?/} + end + + it "calls singleton_method_added for singleton classes" do + ScratchPad.record [] + cls = Class.new do + class << self + def singleton_method_added(name) + # called for this def so ignore it + return if name == :singleton_method_added + ScratchPad.recorded << name + end + end + end + + cls.singleton_class.send(@method, :foo) + + ScratchPad.recorded.each {|name| name.to_s.should =~ /foo[=]?/} + end +end diff --git a/spec/ruby/core/proc/arity_spec.rb b/spec/ruby/core/proc/arity_spec.rb index f7cb5ad0f8..5c7728cb30 100644 --- a/spec/ruby/core/proc/arity_spec.rb +++ b/spec/ruby/core/proc/arity_spec.rb @@ -268,6 +268,14 @@ describe "Proc#arity" do @a.arity.should == 3 @b.arity.should == 3 end + + # implicit rest + evaluate <<-ruby do + @a = lambda { |a, | } + ruby + + @a.arity.should == 1 + end end context "returns negative values" do @@ -530,6 +538,14 @@ describe "Proc#arity" do @a.arity.should == 1 @b.arity.should == 5 end + + # implicit rest + evaluate <<-ruby do + @a = proc { |a, | } + ruby + + @a.arity.should == 1 + end end context "returns negative values" do diff --git a/spec/ruby/core/proc/parameters_spec.rb b/spec/ruby/core/proc/parameters_spec.rb index 2a4dcc36b3..6c21784ab6 100644 --- a/spec/ruby/core/proc/parameters_spec.rb +++ b/spec/ruby/core/proc/parameters_spec.rb @@ -95,6 +95,11 @@ describe "Proc#parameters" do -> x {}.parameters.should == [[:req, :x]] end + it "ignores implicit rest arguments" do + proc { |x, | }.parameters.should == [[:opt, :x]] + -> x { }.parameters.should == [[:req, :x]] + end + ruby_version_is '3.2' do it "adds rest arg with name * for \"star\" argument" do -> * {}.parameters.should == [[:rest, :*]] diff --git a/spec/ruby/core/proc/shared/to_s.rb b/spec/ruby/core/proc/shared/to_s.rb index f1e2f416fc..a52688a89f 100644 --- a/spec/ruby/core/proc/shared/to_s.rb +++ b/spec/ruby/core/proc/shared/to_s.rb @@ -31,13 +31,13 @@ describe :proc_to_s, shared: true do describe "for a proc created with UnboundMethod#to_proc" do it "returns a description including '(lambda)' and optionally including file and line number" do - def hello; end - s = method("hello").to_proc.send(@method) - if s.include? __FILE__ - s.should =~ /^#$/ - else - s.should =~ /^#$/ - end + def hello; end + s = method("hello").to_proc.send(@method) + if s.include? __FILE__ + s.should =~ /^#$/ + else + s.should =~ /^#$/ + end end it "has a binary encoding" do diff --git a/spec/ruby/core/process/fixtures/kill.rb b/spec/ruby/core/process/fixtures/kill.rb index 0b88f8ee1f..b922a043f1 100644 --- a/spec/ruby/core/process/fixtures/kill.rb +++ b/spec/ruby/core/process/fixtures/kill.rb @@ -1,5 +1,3 @@ -require 'thread' - pid_file = ARGV.shift scenario = ARGV.shift diff --git a/spec/ruby/core/process/status/termsig_spec.rb b/spec/ruby/core/process/status/termsig_spec.rb index 5d286950f8..9a22dbea71 100644 --- a/spec/ruby/core/process/status/termsig_spec.rb +++ b/spec/ruby/core/process/status/termsig_spec.rb @@ -6,7 +6,7 @@ describe "Process::Status#termsig" do ruby_exe("exit(0)") end - it "returns true" do + it "returns nil" do $?.termsig.should be_nil end end diff --git a/spec/ruby/core/range/bsearch_spec.rb b/spec/ruby/core/range/bsearch_spec.rb index 9c93671d85..5254ab756c 100644 --- a/spec/ruby/core/range/bsearch_spec.rb +++ b/spec/ruby/core/range/bsearch_spec.rb @@ -9,22 +9,30 @@ describe "Range#bsearch" do it_behaves_like :enumeratorized_with_unknown_size, :bsearch, (1..3) it "raises a TypeError if the block returns an Object" do - -> { (0..1).bsearch { Object.new } }.should raise_error(TypeError) + -> { (0..1).bsearch { Object.new } }.should raise_error(TypeError, "wrong argument type Object (must be numeric, true, false or nil)") end - it "raises a TypeError if the block returns a String" do - -> { (0..1).bsearch { "1" } }.should raise_error(TypeError) + it "raises a TypeError if the block returns a String and boundaries are Integer values" do + -> { (0..1).bsearch { "1" } }.should raise_error(TypeError, "wrong argument type String (must be numeric, true, false or nil)") + end + + it "raises a TypeError if the block returns a String and boundaries are Float values" do + -> { (0.0..1.0).bsearch { "1" } }.should raise_error(TypeError, "wrong argument type String (must be numeric, true, false or nil)") end it "raises a TypeError if the Range has Object values" do value = mock("range bsearch") r = Range.new value, value - -> { r.bsearch { true } }.should raise_error(TypeError) + -> { r.bsearch { true } }.should raise_error(TypeError, "can't do binary search for MockObject") end it "raises a TypeError if the Range has String values" do - -> { ("a".."e").bsearch { true } }.should raise_error(TypeError) + -> { ("a".."e").bsearch { true } }.should raise_error(TypeError, "can't do binary search for String") + end + + it "raises TypeError when non-Numeric begin/end and block not passed" do + -> { ("a".."e").bsearch }.should raise_error(TypeError, "can't do binary search for String") end context "with Integer values" do @@ -94,6 +102,10 @@ describe "Range#bsearch" do (4..2).bsearch { 0 }.should == nil (4..2).bsearch { -1 }.should == nil end + + it "returns enumerator when block not passed" do + (0...3).bsearch.kind_of?(Enumerator).should == true + end end context "with Float values" do @@ -156,7 +168,6 @@ describe "Range#bsearch" do it "returns nil if the block returns greater than zero for every element" do (0.3..3.0).bsearch { |x| x <=> -1 }.should be_nil - end it "returns nil if the block never returns zero" do @@ -213,6 +224,10 @@ describe "Range#bsearch" do (0...inf).bsearch { |x| x >= Float::MAX ? 0 : 1 }.should == Float::MAX end end + + it "returns enumerator when block not passed" do + (0.1...2.3).bsearch.kind_of?(Enumerator).should == true + end end context "with endless ranges and Integer values" do @@ -250,6 +265,10 @@ describe "Range#bsearch" do [1, 2, 3].should include(result) end end + + it "returns enumerator when block not passed" do + eval("(-2..)").bsearch.kind_of?(Enumerator).should == true + end end context "with endless ranges and Float values" do @@ -327,8 +346,11 @@ describe "Range#bsearch" do eval("(0.0...)").bsearch { 0 }.should != inf end end - end + it "returns enumerator when block not passed" do + eval("(0.1..)").bsearch.kind_of?(Enumerator).should == true + end + end context "with beginless ranges and Integer values" do context "with a block returning true or false" do @@ -361,6 +383,10 @@ describe "Range#bsearch" do [1, 2, 3].should include(result) end end + + it "returns enumerator when block not passed" do + (..10).bsearch.kind_of?(Enumerator).should == true + end end context "with beginless ranges and Float values" do @@ -432,5 +458,9 @@ describe "Range#bsearch" do (...inf).bsearch { |x| 3 - x }.should == 3 end end + + it "returns enumerator when block not passed" do + (..-0.1).bsearch.kind_of?(Enumerator).should == true + end end end diff --git a/spec/ruby/core/rational/coerce_spec.rb b/spec/ruby/core/rational/coerce_spec.rb index bba0c810cc..7aea31aea9 100644 --- a/spec/ruby/core/rational/coerce_spec.rb +++ b/spec/ruby/core/rational/coerce_spec.rb @@ -1,9 +1,7 @@ require_relative "../../spec_helper" -ruby_version_is ""..."3.4" do - require_relative '../../shared/rational/coerce' +require_relative '../../shared/rational/coerce' - describe "Rational#coerce" do - it_behaves_like :rational_coerce, :coerce - end +describe "Rational#coerce" do + it_behaves_like :rational_coerce, :coerce end diff --git a/spec/ruby/core/regexp/shared/new.rb b/spec/ruby/core/regexp/shared/new.rb index 773882e495..06c32e36cd 100644 --- a/spec/ruby/core/regexp/shared/new.rb +++ b/spec/ruby/core/regexp/shared/new.rb @@ -123,14 +123,30 @@ describe :regexp_new_string, shared: true do (r.options & Regexp::EXTENDED).should_not == 0 end - it "does not try to convert the second argument to Integer with #to_int method call" do - ScratchPad.clear - obj = Object.new - def obj.to_int() ScratchPad.record(:called) end + ruby_version_is ""..."3.2" do + it "does not try to convert the second argument to Integer with #to_int method call" do + ScratchPad.clear + obj = Object.new + def obj.to_int() ScratchPad.record(:called) end + + Regexp.send(@method, "Hi", obj) - Regexp.send(@method, "Hi", obj) + ScratchPad.recorded.should == nil + end + end - ScratchPad.recorded.should == nil + ruby_version_is "3.2" do + it "does not try to convert the second argument to Integer with #to_int method call" do + ScratchPad.clear + obj = Object.new + def obj.to_int() ScratchPad.record(:called) end + + -> { + Regexp.send(@method, "Hi", obj) + }.should complain(/expected true or false as ignorecase/, {verbose: true}) + + ScratchPad.recorded.should == nil + end end ruby_version_is ""..."3.2" do @@ -188,12 +204,12 @@ describe :regexp_new_string, shared: true do end it "raises an Argument error if the second argument contains unsupported chars" do - -> { Regexp.send(@method, 'Hi', 'e') }.should raise_error(ArgumentError) - -> { Regexp.send(@method, 'Hi', 'n') }.should raise_error(ArgumentError) - -> { Regexp.send(@method, 'Hi', 's') }.should raise_error(ArgumentError) - -> { Regexp.send(@method, 'Hi', 'u') }.should raise_error(ArgumentError) - -> { Regexp.send(@method, 'Hi', 'j') }.should raise_error(ArgumentError) - -> { Regexp.send(@method, 'Hi', 'mjx') }.should raise_error(ArgumentError) + -> { Regexp.send(@method, 'Hi', 'e') }.should raise_error(ArgumentError, "unknown regexp option: e") + -> { Regexp.send(@method, 'Hi', 'n') }.should raise_error(ArgumentError, "unknown regexp option: n") + -> { Regexp.send(@method, 'Hi', 's') }.should raise_error(ArgumentError, "unknown regexp option: s") + -> { Regexp.send(@method, 'Hi', 'u') }.should raise_error(ArgumentError, "unknown regexp option: u") + -> { Regexp.send(@method, 'Hi', 'j') }.should raise_error(ArgumentError, "unknown regexp option: j") + -> { Regexp.send(@method, 'Hi', 'mjx') }.should raise_error(ArgumentError, /unknown regexp option: mjx\b/) end end diff --git a/spec/ruby/core/signal/trap_spec.rb b/spec/ruby/core/signal/trap_spec.rb index b3186cda92..e238da3ca2 100644 --- a/spec/ruby/core/signal/trap_spec.rb +++ b/spec/ruby/core/signal/trap_spec.rb @@ -264,6 +264,14 @@ describe "Signal.trap" do end end + %w[SEGV BUS ILL FPE VTALRM].each do |signal| + it "raises ArgumentError for SIG#{signal} which is reserved by Ruby" do + -> { + Signal.trap(signal, -> {}) + }.should raise_error(ArgumentError, "can't trap reserved signal: SIG#{signal}") + end + end + it "allows to register a handler for all known signals, except reserved signals for which it raises ArgumentError" do out = ruby_exe(fixture(__FILE__, "trap_all.rb"), args: "2>&1") out.should == "OK\n" diff --git a/spec/ruby/core/string/fixtures/utf-8-encoding.rb b/spec/ruby/core/string/fixtures/utf-8-encoding.rb deleted file mode 100644 index fd243ec522..0000000000 --- a/spec/ruby/core/string/fixtures/utf-8-encoding.rb +++ /dev/null @@ -1,7 +0,0 @@ -# -*- encoding: utf-8 -*- -module StringSpecs - class UTF8Encoding - def self.source_encoding; __ENCODING__; end - def self.egrave; "é"; end - end -end diff --git a/spec/ruby/core/string/rindex_spec.rb b/spec/ruby/core/string/rindex_spec.rb index 45ff13a006..f6271b270d 100644 --- a/spec/ruby/core/string/rindex_spec.rb +++ b/spec/ruby/core/string/rindex_spec.rb @@ -1,7 +1,6 @@ # -*- encoding: utf-8 -*- require_relative '../../spec_helper' require_relative 'fixtures/classes' -require_relative 'fixtures/utf-8-encoding' describe "String#rindex with object" do it "raises a TypeError if obj isn't a String or Regexp" do diff --git a/spec/ruby/core/thread/backtrace/location/lineno_spec.rb b/spec/ruby/core/thread/backtrace/location/lineno_spec.rb index d14cf17514..10457f80f0 100644 --- a/spec/ruby/core/thread/backtrace/location/lineno_spec.rb +++ b/spec/ruby/core/thread/backtrace/location/lineno_spec.rb @@ -7,7 +7,7 @@ describe 'Thread::Backtrace::Location#lineno' do @line = __LINE__ - 1 end - it 'returns the absolute path of the call frame' do + it 'returns the line number of the call frame' do @frame.lineno.should == @line end diff --git a/spec/ruby/core/thread/fetch_spec.rb b/spec/ruby/core/thread/fetch_spec.rb index 6b37d4cfc5..85ffb71874 100644 --- a/spec/ruby/core/thread/fetch_spec.rb +++ b/spec/ruby/core/thread/fetch_spec.rb @@ -29,6 +29,36 @@ describe 'Thread#fetch' do end end + describe 'with a block' do + it 'returns the value of the fiber-local variable if value has been assigned' do + th = Thread.new { Thread.current[:cat] = 'meow' } + th.join + th.fetch(:cat) { true }.should == 'meow' + end + + it "returns the block value if fiber-local variable hasn't been assigned" do + th = Thread.new {} + th.join + th.fetch(:cat) { true }.should == true + end + + it "does not call the block if value has been assigned" do + th = Thread.new { Thread.current[:cat] = 'meow' } + th.join + var = :not_updated + th.fetch(:cat) { var = :updated }.should == 'meow' + var.should == :not_updated + end + + it "uses the block if a default is given and warns about it" do + th = Thread.new {} + th.join + -> { + th.fetch(:cat, false) { true }.should == true + }.should complain(/warning: block supersedes default value argument/) + end + end + it 'raises an ArgumentError when not passed one or two arguments' do -> { Thread.current.fetch() }.should raise_error(ArgumentError) -> { Thread.current.fetch(1, 2, 3) }.should raise_error(ArgumentError) diff --git a/spec/ruby/core/thread/thread_variable_get_spec.rb b/spec/ruby/core/thread/thread_variable_get_spec.rb index 38f90d5830..0ad19bfd88 100644 --- a/spec/ruby/core/thread/thread_variable_get_spec.rb +++ b/spec/ruby/core/thread/thread_variable_get_spec.rb @@ -13,7 +13,7 @@ describe "Thread#thread_variable_get" do @t.thread_variable_get(:a).should be_nil end - it "returns the value previously set by #[]=" do + it "returns the value previously set by #thread_variable_set" do @t.thread_variable_set :a, 49 @t.thread_variable_get(:a).should == 49 end diff --git a/spec/ruby/core/time/deconstruct_keys_spec.rb b/spec/ruby/core/time/deconstruct_keys_spec.rb index fbb0ec2164..ee17e7dbd4 100644 --- a/spec/ruby/core/time/deconstruct_keys_spec.rb +++ b/spec/ruby/core/time/deconstruct_keys_spec.rb @@ -37,8 +37,9 @@ ruby_version_is "3.2" do Time.new(2022, 10, 5, 13, 30).deconstruct_keys(['year', []]).should == {} end - it "ignores not existing Symbol keys" do - Time.new(2022, 10, 5, 13, 30).deconstruct_keys([:year, :a]).should == { year: 2022 } + it "ignores not existing Symbol keys and processes keys after the first non-existing one" do + d = Time.utc(2022, 10, 5, 13, 30) + d.deconstruct_keys([:year, :a, :month, :b, :day]).should == { year: 2022, month: 10, day: 5 } end end end diff --git a/spec/ruby/language/assignments_spec.rb b/spec/ruby/language/assignments_spec.rb new file mode 100644 index 0000000000..005c1f0dc3 --- /dev/null +++ b/spec/ruby/language/assignments_spec.rb @@ -0,0 +1,150 @@ +require_relative '../spec_helper' + +# Should be synchronized with spec/ruby/language/optional_assignments_spec.rb +describe 'Assignments' do + describe 'using +=' do + describe 'using an accessor' do + before do + klass = Class.new { attr_accessor :b } + @a = klass.new + end + + it 'does evaluate receiver only once when assigns' do + ScratchPad.record [] + @a.b = 1 + + (ScratchPad << :evaluated; @a).b += 2 + + ScratchPad.recorded.should == [:evaluated] + @a.b.should == 3 + end + + it 'ignores method visibility when receiver is self' do + klass_with_private_methods = Class.new do + def initialize(n) @a = n end + def public_method(n); self.a += n end + private + def a; @a end + def a=(n) @a = n; 42 end + end + + a = klass_with_private_methods.new(0) + a.public_method(2).should == 2 + end + end + + describe 'using a #[]' do + before do + klass = Class.new do + def [](k) + @hash ||= {} + @hash[k] + end + + def []=(k, v) + @hash ||= {} + @hash[k] = v + 7 + end + end + @b = klass.new + end + + it 'evaluates receiver only once when assigns' do + ScratchPad.record [] + a = {k: 1} + + (ScratchPad << :evaluated; a)[:k] += 2 + + ScratchPad.recorded.should == [:evaluated] + a[:k].should == 3 + end + + it 'ignores method visibility when receiver is self' do + klass_with_private_methods = Class.new do + def initialize(h) @a = h end + def public_method(k, n); self[k] += n end + private + def [](k) @a[k] end + def []=(k, v) @a[k] = v; 42 end + end + + a = klass_with_private_methods.new(k: 0) + a.public_method(:k, 2).should == 2 + end + + context 'splatted argument' do + it 'correctly handles it' do + @b[:m] = 10 + (@b[*[:m]] += 10).should == 20 + @b[:m].should == 20 + + @b[:n] = 10 + (@b[*(1; [:n])] += 10).should == 20 + @b[:n].should == 20 + + @b[:k] = 10 + (@b[*begin 1; [:k] end] += 10).should == 20 + @b[:k].should == 20 + end + + it 'calls #to_a only once' do + k = Object.new + def k.to_a + ScratchPad << :to_a + [:k] + end + + ScratchPad.record [] + @b[:k] = 10 + (@b[*k] += 10).should == 20 + @b[:k].should == 20 + ScratchPad.recorded.should == [:to_a] + end + + it 'correctly handles a nested splatted argument' do + @b[:k] = 10 + (@b[*[*[:k]]] += 10).should == 20 + @b[:k].should == 20 + end + + it 'correctly handles multiple nested splatted arguments' do + klass_with_multiple_parameters = Class.new do + def [](k1, k2, k3) + @hash ||= {} + @hash[:"#{k1}#{k2}#{k3}"] + end + + def []=(k1, k2, k3, v) + @hash ||= {} + @hash[:"#{k1}#{k2}#{k3}"] = v + 7 + end + end + a = klass_with_multiple_parameters.new + + a[:a, :b, :c] = 10 + (a[*[:a], *[:b], *[:c]] += 10).should == 20 + a[:a, :b, :c].should == 20 + end + end + end + + describe 'using compounded constants' do + it 'causes side-effects of the module part to be applied only once (when assigns)' do + module ConstantSpecs + OpAssignTrue = 1 + end + + suppress_warning do # already initialized constant + x = 0 + (x += 1; ConstantSpecs)::OpAssignTrue += 2 + x.should == 1 + ConstantSpecs::OpAssignTrue.should == 3 + end + + ConstantSpecs.send :remove_const, :OpAssignTrue + end + end + end +end diff --git a/spec/ruby/language/block_spec.rb b/spec/ruby/language/block_spec.rb index 90329e2f6f..578d9cb3b0 100644 --- a/spec/ruby/language/block_spec.rb +++ b/spec/ruby/language/block_spec.rb @@ -40,10 +40,42 @@ describe "A block yielded a single" do m([1, 2]) { |a=5, b, c, d| [a, b, c, d] }.should == [5, 1, 2, nil] end + it "assigns elements to pre arguments" do + m([1, 2]) { |a, b, c, d=5| [a, b, c, d] }.should == [1, 2, nil, 5] + end + + it "assigns elements to pre and post arguments" do + m([1 ]) { |a, b=5, c=6, d, e| [a, b, c, d, e] }.should == [1, 5, 6, nil, nil] + m([1, 2 ]) { |a, b=5, c=6, d, e| [a, b, c, d, e] }.should == [1, 5, 6, 2, nil] + m([1, 2, 3 ]) { |a, b=5, c=6, d, e| [a, b, c, d, e] }.should == [1, 5, 6, 2, 3] + m([1, 2, 3, 4 ]) { |a, b=5, c=6, d, e| [a, b, c, d, e] }.should == [1, 2, 6, 3, 4] + m([1, 2, 3, 4, 5 ]) { |a, b=5, c=6, d, e| [a, b, c, d, e] }.should == [1, 2, 3, 4, 5] + m([1, 2, 3, 4, 5, 6]) { |a, b=5, c=6, d, e| [a, b, c, d, e] }.should == [1, 2, 3, 4, 5] + end + + it "assigns elements to pre and post arguments when *rest is present" do + m([1 ]) { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.should == [1, 5, 6, [], nil, nil] + m([1, 2 ]) { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.should == [1, 5, 6, [], 2, nil] + m([1, 2, 3 ]) { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.should == [1, 5, 6, [], 2, 3] + m([1, 2, 3, 4 ]) { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.should == [1, 2, 6, [], 3, 4] + m([1, 2, 3, 4, 5 ]) { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.should == [1, 2, 3, [], 4, 5] + m([1, 2, 3, 4, 5, 6]) { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.should == [1, 2, 3, [4], 5, 6] + end + ruby_version_is "3.2" do it "does not autosplat single argument to required arguments when a keyword rest argument is present" do m([1, 2]) { |a, **k| [a, k] }.should == [[1, 2], {}] end + + it "does not autosplat single argument to required arguments when keyword arguments are present" do + m([1, 2]) { |a, b: :b, c: :c| [a, b, c] }.should == [[1, 2], :b, :c] + end + + it "raises error when required keyword arguments are present" do + -> { + m([1, 2]) { |a, b:, c:| [a, b, c] } + }.should raise_error(ArgumentError, "missing keywords: :b, :c") + end end ruby_version_is ''..."3.2" do @@ -51,6 +83,16 @@ describe "A block yielded a single" do it "autosplats single argument to required arguments when a keyword rest argument is present" do m([1, 2]) { |a, **k| [a, k] }.should == [1, {}] end + + it "autosplats single argument to required arguments when optional keyword arguments are present" do + m([1, 2]) { |a, b: :b, c: :c| [a, b, c] }.should == [1, :b, :c] + end + + it "raises error when required keyword arguments are present" do + -> { + m([1, 2]) { |a, b:, c:| [a, b, c] } + }.should raise_error(ArgumentError, "missing keywords: :b, :c") + end end it "assigns elements to mixed argument types" do @@ -368,7 +410,6 @@ describe "A block" do -> { @y.s(obj) { |a, b| } }.should raise_error(ZeroDivisionError) end - end describe "taking |a, *b| arguments" do @@ -701,6 +742,42 @@ describe "A block" do eval("Proc.new { |_,_| }").should be_an_instance_of(Proc) end end + + describe 'pre and post parameters' do + it "assigns nil to unassigned required arguments" do + proc { |a, *b, c, d| [a, b, c, d] }.call(1, 2).should == [1, [], 2, nil] + end + + it "assigns elements to optional arguments" do + proc { |a=5, b=4, c=3| [a, b, c] }.call(1, 2).should == [1, 2, 3] + end + + it "assigns elements to post arguments" do + proc { |a=5, b, c, d| [a, b, c, d] }.call(1, 2).should == [5, 1, 2, nil] + end + + it "assigns elements to pre arguments" do + proc { |a, b, c, d=5| [a, b, c, d] }.call(1, 2).should == [1, 2, nil, 5] + end + + it "assigns elements to pre and post arguments" do + proc { |a, b=5, c=6, d, e| [a, b, c, d, e] }.call(1 ).should == [1, 5, 6, nil, nil] + proc { |a, b=5, c=6, d, e| [a, b, c, d, e] }.call(1, 2 ).should == [1, 5, 6, 2, nil] + proc { |a, b=5, c=6, d, e| [a, b, c, d, e] }.call(1, 2, 3 ).should == [1, 5, 6, 2, 3] + proc { |a, b=5, c=6, d, e| [a, b, c, d, e] }.call(1, 2, 3, 4 ).should == [1, 2, 6, 3, 4] + proc { |a, b=5, c=6, d, e| [a, b, c, d, e] }.call(1, 2, 3, 4, 5 ).should == [1, 2, 3, 4, 5] + proc { |a, b=5, c=6, d, e| [a, b, c, d, e] }.call(1, 2, 3, 4, 5, 6).should == [1, 2, 3, 4, 5] + end + + it "assigns elements to pre and post arguments when *rest is present" do + proc { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.call(1 ).should == [1, 5, 6, [], nil, nil] + proc { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.call(1, 2 ).should == [1, 5, 6, [], 2, nil] + proc { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.call(1, 2, 3 ).should == [1, 5, 6, [], 2, 3] + proc { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.call(1, 2, 3, 4 ).should == [1, 2, 6, [], 3, 4] + proc { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.call(1, 2, 3, 4, 5 ).should == [1, 2, 3, [], 4, 5] + proc { |a, b=5, c=6, *d, e, f| [a, b, c, d, e, f] }.call(1, 2, 3, 4, 5, 6).should == [1, 2, 3, [4], 5, 6] + end + end end describe "Block-local variables" do @@ -921,7 +998,7 @@ end describe "Anonymous block forwarding" do ruby_version_is "3.1" do - it "forwards blocks to other functions that formally declare anonymous blocks" do + it "forwards blocks to other method that formally declares anonymous block" do eval <<-EOF def b(&); c(&) end def c(&); yield :non_null end diff --git a/spec/ruby/language/case_spec.rb b/spec/ruby/language/case_spec.rb index 1a3925c9c6..cfa612b93a 100644 --- a/spec/ruby/language/case_spec.rb +++ b/spec/ruby/language/case_spec.rb @@ -399,6 +399,22 @@ describe "The 'case'-construct" do :called end.should == :called end + + it "supports declaring variables in the case target expression" do + def test(v) + case new_variable_in_expression = v + when true + # This extra block is a test that `new_variable_in_expression` is declared outside of it and not inside + self.then { new_variable_in_expression } + else + # Same + self.then { new_variable_in_expression.casecmp?("foo") } + end + end + + self.test("bar").should == false + self.test(true).should == true + end end describe "The 'case'-construct with no target expression" do diff --git a/spec/ruby/language/defined_spec.rb b/spec/ruby/language/defined_spec.rb index b84fe9394a..34408c0190 100644 --- a/spec/ruby/language/defined_spec.rb +++ b/spec/ruby/language/defined_spec.rb @@ -116,6 +116,11 @@ describe "The defined? keyword when called with a method name" do defined?(obj.a_defined_method).should == "method" end + it "returns 'method' for []=" do + a = [] + defined?(a[0] = 1).should == "method" + end + it "returns nil if the method is not defined" do obj = DefinedSpecs::Basic.new defined?(obj.an_undefined_method).should be_nil @@ -231,6 +236,14 @@ describe "The defined? keyword for an expression" do defined?(@@defined_specs_x = 2).should == "assignment" end + it "returns 'assignment' for assigning a constant" do + defined?(A = 2).should == "assignment" + end + + it "returns 'assignment' for assigning a fully qualified constant" do + defined?(Object::A = 2).should == "assignment" + end + it "returns 'assignment' for assigning multiple variables" do defined?((a, b = 1, 2)).should == "assignment" end @@ -248,7 +261,27 @@ describe "The defined? keyword for an expression" do end it "returns 'assignment' for an expression with '+='" do - defined?(x += 2).should == "assignment" + defined?(a += 1).should == "assignment" + defined?(@a += 1).should == "assignment" + defined?(@@a += 1).should == "assignment" + defined?($a += 1).should == "assignment" + defined?(A += 1).should == "assignment" + # fully qualified constant check is moved out into a separate test case + defined?(a.b += 1).should == "assignment" + defined?(a[:b] += 1).should == "assignment" + end + + # https://bugs.ruby-lang.org/issues/20111 + ruby_version_is ""..."3.4" do + it "returns 'expression' for an assigning a fully qualified constant with '+='" do + defined?(Object::A += 1).should == "expression" + end + end + + ruby_version_is "3.4" do + it "returns 'assignment' for an assigning a fully qualified constant with '+='" do + defined?(Object::A += 1).should == "assignment" + end end it "returns 'assignment' for an expression with '*='" do @@ -279,12 +312,90 @@ describe "The defined? keyword for an expression" do defined?(x >>= 2).should == "assignment" end - it "returns 'assignment' for an expression with '||='" do - defined?(x ||= 2).should == "assignment" + context "||=" do + it "returns 'assignment' for assigning a local variable with '||='" do + defined?(a ||= true).should == "assignment" + end + + it "returns 'assignment' for assigning an instance variable with '||='" do + defined?(@a ||= true).should == "assignment" + end + + it "returns 'assignment' for assigning a class variable with '||='" do + defined?(@@a ||= true).should == "assignment" + end + + it "returns 'assignment' for assigning a global variable with '||='" do + defined?($a ||= true).should == "assignment" + end + + it "returns 'assignment' for assigning a constant with '||='" do + defined?(A ||= true).should == "assignment" + end + + # https://bugs.ruby-lang.org/issues/20111 + ruby_version_is ""..."3.4" do + it "returns 'expression' for assigning a fully qualified constant with '||='" do + defined?(Object::A ||= true).should == "expression" + end + end + + ruby_version_is "3.4" do + it "returns 'assignment' for assigning a fully qualified constant with '||='" do + defined?(Object::A ||= true).should == "assignment" + end + end + + it "returns 'assignment' for assigning an attribute with '||='" do + defined?(a.b ||= true).should == "assignment" + end + + it "returns 'assignment' for assigning a referenced element with '||='" do + defined?(a[:b] ||= true).should == "assignment" + end end - it "returns 'assignment' for an expression with '&&='" do - defined?(x &&= 2).should == "assignment" + context "&&=" do + it "returns 'assignment' for assigning a local variable with '&&='" do + defined?(a &&= true).should == "assignment" + end + + it "returns 'assignment' for assigning an instance variable with '&&='" do + defined?(@a &&= true).should == "assignment" + end + + it "returns 'assignment' for assigning a class variable with '&&='" do + defined?(@@a &&= true).should == "assignment" + end + + it "returns 'assignment' for assigning a global variable with '&&='" do + defined?($a &&= true).should == "assignment" + end + + it "returns 'assignment' for assigning a constant with '&&='" do + defined?(A &&= true).should == "assignment" + end + + # https://bugs.ruby-lang.org/issues/20111 + ruby_version_is ""..."3.4" do + it "returns 'expression' for assigning a fully qualified constant with '&&='" do + defined?(Object::A &&= true).should == "expression" + end + end + + ruby_version_is "3.4" do + it "returns 'assignment' for assigning a fully qualified constant with '&&='" do + defined?(Object::A &&= true).should == "assignment" + end + end + + it "returns 'assignment' for assigning an attribute with '&&='" do + defined?(a.b &&= true).should == "assignment" + end + + it "returns 'assignment' for assigning a referenced element with '&&='" do + defined?(a[:b] &&= true).should == "assignment" + end end it "returns 'assignment' for an expression with '**='" do diff --git a/spec/ruby/language/delegation_spec.rb b/spec/ruby/language/delegation_spec.rb index 020787aff6..d780506421 100644 --- a/spec/ruby/language/delegation_spec.rb +++ b/spec/ruby/language/delegation_spec.rb @@ -31,10 +31,10 @@ describe "delegation with def(...)" do def delegate(...) target ... end - RUBY - end + RUBY + end - a.new.delegate(1, b: 2).should == Range.new([[], {}], nil, true) + a.new.delegate(1, b: 2).should == Range.new([[], {}], nil, true) end end @@ -61,3 +61,33 @@ describe "delegation with def(x, ...)" do a.new.delegate_block(0, 1, b: 2) { |x| x }.should == [{b: 2}, [1]] end end + +ruby_version_is "3.2" do + describe "delegation with def(*)" do + it "delegates rest" do + a = Class.new(DelegationSpecs::Target) + a.class_eval(<<-RUBY) + def delegate(*) + target(*) + end + RUBY + + a.new.delegate(0, 1).should == [[0, 1], {}] + end + end +end + +ruby_version_is "3.2" do + describe "delegation with def(**)" do + it "delegates kwargs" do + a = Class.new(DelegationSpecs::Target) + a.class_eval(<<-RUBY) + def delegate(**) + target(**) + end + RUBY + + a.new.delegate(a: 1) { |x| x }.should == [[], {a: 1}] + end + end +end diff --git a/spec/ruby/language/fixtures/rescue/top_level.rb b/spec/ruby/language/fixtures/rescue/top_level.rb new file mode 100644 index 0000000000..59e78ef1d6 --- /dev/null +++ b/spec/ruby/language/fixtures/rescue/top_level.rb @@ -0,0 +1,7 @@ +# capturing in local variable at top-level + +begin + raise "message" +rescue => e + ScratchPad << e.message +end diff --git a/spec/ruby/language/fixtures/super.rb b/spec/ruby/language/fixtures/super.rb index 94a2a91be0..c5bdcf0e40 100644 --- a/spec/ruby/language/fixtures/super.rb +++ b/spec/ruby/language/fixtures/super.rb @@ -539,6 +539,30 @@ module SuperSpecs args end + def m3(*args) + args + end + + def m4(*args) + args + end + + def m_default(*args) + args + end + + def m_rest(*args) + args + end + + def m_pre_default_rest_post(*args) + args + end + + def m_kwrest(**kw) + kw + end + def m_modified(*args) args end @@ -549,6 +573,30 @@ module SuperSpecs super end + def m3(_, _, _) + super + end + + def m4(_, _, _, _) + super + end + + def m_default(_ = 0) + super + end + + def m_rest(*_) + super + end + + def m_pre_default_rest_post(_, _, _=:a, _=:b, *_, _, _) + super + end + + def m_kwrest(**_) + super + end + def m_modified(_, _) _ = 14 super diff --git a/spec/ruby/language/method_spec.rb b/spec/ruby/language/method_spec.rb index 5f42c52341..e34ff7e1a6 100644 --- a/spec/ruby/language/method_spec.rb +++ b/spec/ruby/language/method_spec.rb @@ -1335,6 +1335,7 @@ describe "An endless method definition" do end end + # tested more thoroughly in language/delegation_spec.rb context "with args forwarding" do evaluate <<-ruby do def mm(word, num:) diff --git a/spec/ruby/language/optional_assignments_spec.rb b/spec/ruby/language/optional_assignments_spec.rb index 02461655d6..2443cc6b79 100644 --- a/spec/ruby/language/optional_assignments_spec.rb +++ b/spec/ruby/language/optional_assignments_spec.rb @@ -57,7 +57,7 @@ describe 'Optional variable assignments' do end end - describe 'using a accessor' do + describe 'using an accessor' do before do klass = Class.new { attr_accessor :b } @a = klass.new @@ -103,6 +103,16 @@ describe 'Optional variable assignments' do @a.b.should == 10 end + it 'does evaluate receiver only once when assigns' do + ScratchPad.record [] + @a.b = nil + + (ScratchPad << :evaluated; @a).b ||= 10 + + ScratchPad.recorded.should == [:evaluated] + @a.b.should == 10 + end + it 'returns the new value if set to false' do def @a.b=(x) :v @@ -122,29 +132,148 @@ describe 'Optional variable assignments' do (@a.b ||= 20).should == 10 end - it 'works when writer is private' do + it 'ignores method visibility when receiver is self' do + klass_with_private_methods = Class.new do + def initialize(v) @a = v end + def public_method(v); self.a ||= v end + private + def a; @a end + def a=(v) @a = v; 42 end + end + + a = klass_with_private_methods.new(false) + a.public_method(10).should == 10 + end + end + + describe 'using a #[]' do + before do + @a = {} klass = Class.new do - def t - self.b = false - (self.b ||= 10).should == 10 - (self.b ||= 20).should == 10 + def [](k) + @hash ||= {} + @hash[k] + end + + def []=(k, v) + @hash ||= {} + @hash[k] = v + 7 end + end + @b = klass.new + end + + it 'returns the assigned value, not the result of the []= method with ||=' do + (@b[:k] ||= 12).should == 12 + end + + it "evaluates the index precisely once" do + ary = [:x, :y] + @a[:x] = 15 + @a[ary.pop] ||= 25 + ary.should == [:x] + @a.should == { x: 15, y: 25 } + end - def b - @b + it "evaluates the index arguments in the correct order" do + ary = Class.new(Array) do + def [](x, y) + super(x + 3 * y) end - def b=(x) - @b = x - :v + def []=(x, y, value) + super(x + 3 * y, value) end + end.new + ary[0, 0] = 1 + ary[1, 0] = 1 + ary[2, 0] = nil + ary[3, 0] = 1 + ary[4, 0] = 1 + ary[5, 0] = 1 + ary[6, 0] = nil + + foo = [0, 2] + + ary[foo.pop, foo.pop] ||= 2 # expected `ary[2, 0] ||= 2` + + ary[2, 0].should == 2 + ary[6, 0].should == nil # returns the same element as `ary[0, 2]` + end + + it 'evaluates receiver only once when assigns' do + ScratchPad.record [] + @a[:k] = nil - private :b= + (ScratchPad << :evaluated; @a)[:k] ||= 2 + + ScratchPad.recorded.should == [:evaluated] + @a[:k].should == 2 + end + + it 'ignores method visibility when receiver is self' do + klass_with_private_methods = Class.new do + def initialize(h) @a = h end + def public_method(k, v); self[k] ||= v end + private + def [](k) @a[k] end + def []=(k, v) @a[k] = v; 42 end end - klass.new.t + a = klass_with_private_methods.new(k: false) + a.public_method(:k, 10).should == 10 end + context 'splatted argument' do + it 'correctly handles it' do + (@b[*[:m]] ||= 10).should == 10 + @b[:m].should == 10 + + (@b[*(1; [:n])] ||= 10).should == 10 + @b[:n].should == 10 + + (@b[*begin 1; [:k] end] ||= 10).should == 10 + @b[:k].should == 10 + end + + it 'calls #to_a only once' do + k = Object.new + def k.to_a + ScratchPad << :to_a + [:k] + end + + ScratchPad.record [] + (@b[*k] ||= 20).should == 20 + @b[:k].should == 20 + ScratchPad.recorded.should == [:to_a] + end + + it 'correctly handles a nested splatted argument' do + (@b[*[*[:k]]] ||= 20).should == 20 + @b[:k].should == 20 + end + + it 'correctly handles multiple nested splatted arguments' do + klass_with_multiple_parameters = Class.new do + def [](k1, k2, k3) + @hash ||= {} + @hash[:"#{k1}#{k2}#{k3}"] + end + + def []=(k1, k2, k3, v) + @hash ||= {} + @hash[:"#{k1}#{k2}#{k3}"] = v + 7 + end + end + a = klass_with_multiple_parameters.new + + (a[*[:a], *[:b], *[:c]] ||= 20).should == 20 + a[:a, :b, :c].should == 20 + end + end end end @@ -191,7 +320,7 @@ describe 'Optional variable assignments' do end end - describe 'using a single variable' do + describe 'using an accessor' do before do klass = Class.new { attr_accessor :b } @a = klass.new @@ -236,6 +365,29 @@ describe 'Optional variable assignments' do @a.b.should == 20 end + + it 'does evaluate receiver only once when assigns' do + ScratchPad.record [] + @a.b = 10 + + (ScratchPad << :evaluated; @a).b &&= 20 + + ScratchPad.recorded.should == [:evaluated] + @a.b.should == 20 + end + + it 'ignores method visibility when receiver is self' do + klass_with_private_methods = Class.new do + def initialize(v) @a = v end + def public_method(v); self.a &&= v end + private + def a; @a end + def a=(v) @a = v; 42 end + end + + a = klass_with_private_methods.new(true) + a.public_method(10).should == 10 + end end describe 'using a #[]' do @@ -297,17 +449,15 @@ describe 'Optional variable assignments' do end it 'returns the assigned value, not the result of the []= method with ||=' do - (@b[:k] ||= 12).should == 12 - end - - it 'correctly handles a splatted argument for the index' do - (@b[*[:k]] ||= 12).should == 12 + @b[:k] = 10 + (@b[:k] &&= 12).should == 12 end it "evaluates the index precisely once" do ary = [:x, :y] @a[:x] = 15 - @a[ary.pop] ||= 25 + @a[:y] = 20 + @a[ary.pop] &&= 25 ary.should == [:x] @a.should == { x: 15, y: 25 } end @@ -324,24 +474,103 @@ describe 'Optional variable assignments' do end.new ary[0, 0] = 1 ary[1, 0] = 1 - ary[2, 0] = nil + ary[2, 0] = 1 ary[3, 0] = 1 ary[4, 0] = 1 ary[5, 0] = 1 - ary[6, 0] = nil + ary[6, 0] = 1 foo = [0, 2] - ary[foo.pop, foo.pop] ||= 2 + ary[foo.pop, foo.pop] &&= 2 # expected `ary[2, 0] &&= 2` ary[2, 0].should == 2 - ary[6, 0].should == nil + ary[6, 0].should == 1 # returns the same element as `ary[0, 2]` + end + + it 'evaluates receiver only once when assigns' do + ScratchPad.record [] + @a[:k] = 1 + + (ScratchPad << :evaluated; @a)[:k] &&= 2 + + ScratchPad.recorded.should == [:evaluated] + @a[:k].should == 2 end it 'returns the assigned value, not the result of the []= method with +=' do @b[:k] = 17 (@b[:k] += 12).should == 29 end + + it 'ignores method visibility when receiver is self' do + klass_with_private_methods = Class.new do + def initialize(h) @a = h end + def public_method(k, v); self[k] &&= v end + private + def [](k) @a[k] end + def []=(k, v) @a[k] = v; 42 end + end + + a = klass_with_private_methods.new(k: true) + a.public_method(:k, 10).should == 10 + end + + context 'splatted argument' do + it 'correctly handles it' do + @b[:m] = 0 + (@b[*[:m]] &&= 10).should == 10 + @b[:m].should == 10 + + @b[:n] = 0 + (@b[*(1; [:n])] &&= 10).should == 10 + @b[:n].should == 10 + + @b[:k] = 0 + (@b[*begin 1; [:k] end] &&= 10).should == 10 + @b[:k].should == 10 + end + + it 'calls #to_a only once' do + k = Object.new + def k.to_a + ScratchPad << :to_a + [:k] + end + + ScratchPad.record [] + @b[:k] = 10 + (@b[*k] &&= 20).should == 20 + @b[:k].should == 20 + ScratchPad.recorded.should == [:to_a] + end + + it 'correctly handles a nested splatted argument' do + @b[:k] = 10 + (@b[*[*[:k]]] &&= 20).should == 20 + @b[:k].should == 20 + end + + it 'correctly handles multiple nested splatted arguments' do + klass_with_multiple_parameters = Class.new do + def [](k1, k2, k3) + @hash ||= {} + @hash[:"#{k1}#{k2}#{k3}"] + end + + def []=(k1, k2, k3, v) + @hash ||= {} + @hash[:"#{k1}#{k2}#{k3}"] = v + 7 + end + end + a = klass_with_multiple_parameters.new + + a[:a, :b, :c] = 10 + (a[*[:a], *[:b], *[:c]] &&= 20).should == 20 + a[:a, :b, :c].should == 20 + end + end end end @@ -434,7 +663,7 @@ describe 'Optional constant assignment' do ConstantSpecs::ClassA::OR_ASSIGNED_CONSTANT2.should == :assigned end - it 'causes side-effects of the module part to be applied (for nil constant)' do + it 'causes side-effects of the module part to be applied only once (for nil constant)' do suppress_warning do # already initialized constant ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT2 = nil x = 0 @@ -492,5 +721,20 @@ describe 'Optional constant assignment' do ConstantSpecs::OpAssignFalse.should == false ConstantSpecs.send :remove_const, :OpAssignFalse end + + it 'causes side-effects of the module part to be applied only once (when assigns)' do + module ConstantSpecs + OpAssignTrue = true + end + + suppress_warning do # already initialized constant + x = 0 + (x += 1; ConstantSpecs)::OpAssignTrue &&= :assigned + x.should == 1 + ConstantSpecs::OpAssignTrue.should == :assigned + end + + ConstantSpecs.send :remove_const, :OpAssignTrue + end end end diff --git a/spec/ruby/language/pattern_matching_spec.rb b/spec/ruby/language/pattern_matching_spec.rb index 050a8a052d..a8ec078cd0 100644 --- a/spec/ruby/language/pattern_matching_spec.rb +++ b/spec/ruby/language/pattern_matching_spec.rb @@ -207,7 +207,7 @@ describe "Pattern matching" do in [] end RUBY - }.should raise_error(SyntaxError, /syntax error, unexpected `in'|\(eval\):3: syntax error, unexpected keyword_in/) + }.should raise_error(SyntaxError, /syntax error, unexpected `in'|\(eval\):3: syntax error, unexpected keyword_in|unexpected 'in'/) -> { eval <<~RUBY @@ -216,7 +216,7 @@ describe "Pattern matching" do when 1 == 1 end RUBY - }.should raise_error(SyntaxError, /syntax error, unexpected `when'|\(eval\):3: syntax error, unexpected keyword_when/) + }.should raise_error(SyntaxError, /syntax error, unexpected `when'|\(eval\):3: syntax error, unexpected keyword_when|unexpected 'when'/) end it "checks patterns until the first matching" do @@ -273,7 +273,7 @@ describe "Pattern matching" do true end RUBY - }.should raise_error(SyntaxError, /unexpected/) + }.should raise_error(SyntaxError, /unexpected|expected a delimiter after the predicates of a `when` clause/) end it "evaluates the case expression once for multiple patterns, caching the result" do @@ -739,6 +739,20 @@ describe "Pattern matching" do RUBY end + it "checks Constant === object before calling #deconstruct" do + c1 = Class.new + obj = c1.new + obj.should_not_receive(:deconstruct) + eval(<<~RUBY).should == false + case obj + in String[1] + true + else + false + end + RUBY + end + it "does not match object without #deconstruct method" do obj = Object.new obj.should_receive(:respond_to?).with(:deconstruct) @@ -770,11 +784,7 @@ describe "Pattern matching" do it "accepts a subclass of Array from #deconstruct" do obj = Object.new def obj.deconstruct - subarray = Class.new(Array).new(2) - def subarray.[](n) - n - end - subarray + Class.new(Array).new([0, 1]) end eval(<<~RUBY).should == true @@ -1004,7 +1014,7 @@ describe "Pattern matching" do in {"a" => 1} end RUBY - }.should raise_error(SyntaxError, /unexpected/) + }.should raise_error(SyntaxError, /unexpected|expected a label as the key in the hash pattern/) end it "does not support string interpolation in keys" do @@ -1016,7 +1026,7 @@ describe "Pattern matching" do in {"#{x}": 1} end RUBY - }.should raise_error(SyntaxError, /symbol literal with interpolation is not allowed/) + }.should raise_error(SyntaxError, /symbol literal with interpolation is not allowed|expected a label as the key in the hash pattern/) end it "raise SyntaxError when keys duplicate in pattern" do @@ -1072,6 +1082,20 @@ describe "Pattern matching" do RUBY end + it "checks Constant === object before calling #deconstruct_keys" do + c1 = Class.new + obj = c1.new + obj.should_not_receive(:deconstruct_keys) + eval(<<~RUBY).should == false + case obj + in String(a: 1) + true + else + false + end + RUBY + end + it "does not match object without #deconstruct_keys method" do obj = Object.new obj.should_receive(:respond_to?).with(:deconstruct_keys) @@ -1232,6 +1256,37 @@ describe "Pattern matching" do RUBY end + it "in {} only matches empty hashes" do + eval(<<~RUBY).should == false + case {a: 1} + in {} + true + else + false + end + RUBY + end + + it "in {**nil} only matches empty hashes" do + eval(<<~RUBY).should == true + case {} + in {**nil} + true + else + false + end + RUBY + + eval(<<~RUBY).should == false + case {a: 1} + in {**nil} + true + else + false + end + RUBY + end + it "matches anything with **" do eval(<<~RUBY).should == true case {a: 1} @@ -1340,76 +1395,115 @@ describe "Pattern matching" do end end - ruby_version_is "3.1" do - it "can omit parentheses in one line pattern matching" do - eval(<<~RUBY).should == [1, 2] - [1, 2] => a, b - [a, b] - RUBY + describe "Ruby 3.1 improvements" do + ruby_version_is "3.1" do + it "can omit parentheses in one line pattern matching" do + eval(<<~RUBY).should == [1, 2] + [1, 2] => a, b + [a, b] + RUBY - eval(<<~RUBY).should == 1 - {a: 1} => a: - a - RUBY - end + eval(<<~RUBY).should == 1 + {a: 1} => a: + a + RUBY + end - it "supports pinning instance variables" do - eval(<<~RUBY).should == true - @a = /a/ - case 'abc' - in ^@a - true + it "supports pinning instance variables" do + eval(<<~RUBY).should == true + @a = /a/ + case 'abc' + in ^@a + true + end + RUBY + end + + it "supports pinning class variables" do + result = nil + Module.new do + result = module_eval(<<~RUBY) + @@a = 0..10 + + case 2 + in ^@@a + true + end + RUBY end - RUBY - end - it "supports pinning class variables" do - result = nil - Module.new do - result = module_eval(<<~RUBY) - @@a = 0..10 + result.should == true + end - case 2 - in ^@@a + it "supports pinning global variables" do + eval(<<~RUBY).should == true + $a = /a/ + case 'abc' + in ^$a true end RUBY end - result.should == true + it "supports pinning expressions" do + eval(<<~RUBY).should == true + case 'abc' + in ^(/a/) + true + end + RUBY + + eval(<<~RUBY).should == true + case 0 + in ^(0+0) + true + end + RUBY + end + + it "supports pinning expressions in array pattern" do + eval(<<~RUBY).should == true + case [3] + in [^(1+2)] + true + end + RUBY + end + + it "supports pinning expressions in hash pattern" do + eval(<<~RUBY).should == true + case {name: '2.6', released_at: Time.new(2018, 12, 25)} + in {released_at: ^(Time.new(2010)..Time.new(2020))} + true + end + RUBY + end end + end - it "supports pinning global variables" do - eval(<<~RUBY).should == true - $a = /a/ - case 'abc' - in ^$a - true - end - RUBY + describe "value in pattern" do + it "returns true if the pattern matches" do + eval("1 in 1").should == true + + eval("1 in Integer").should == true + + e = nil + eval("[1, 2] in [1, e]").should == true + e.should == 2 + + k = nil + eval("{k: 1} in {k:}").should == true + k.should == 1 end - it "supports pinning expressions" do - eval(<<~RUBY).should == true - case 'abc' - in ^(/a/) - true - end - RUBY + it "returns false if the pattern does not match" do + eval("1 in 2").should == false - eval(<<~RUBY).should == true - case {name: '2.6', released_at: Time.new(2018, 12, 25)} - in {released_at: ^(Time.new(2010)..Time.new(2020))} - true - end - RUBY + eval("1 in Float").should == false - eval(<<~RUBY).should == true - case 0 - in ^(0+0) - true - end - RUBY + eval("[1, 2] in [2, e]").should == false + + eval("{k: 1} in {k: 2}").should == false end end end diff --git a/spec/ruby/language/rescue_spec.rb b/spec/ruby/language/rescue_spec.rb index b91b52fa0f..69ed038fda 100644 --- a/spec/ruby/language/rescue_spec.rb +++ b/spec/ruby/language/rescue_spec.rb @@ -61,6 +61,78 @@ describe "The rescue keyword" do end end + describe 'capturing in a local variable (that defines it)' do + it 'captures successfully in a method' do + ScratchPad.record [] + + def a + raise "message" + rescue => e + ScratchPad << e.message + end + + a + ScratchPad.recorded.should == ["message"] + end + + it 'captures successfully in a block' do + ScratchPad.record [] + + p = proc do + raise "message" + rescue => e + ScratchPad << e.message + end + + p.call + ScratchPad.recorded.should == ["message"] + end + + it 'captures successfully in a class' do + ScratchPad.record [] + + class RescueSpecs::C + raise "message" + rescue => e + ScratchPad << e.message + end + + ScratchPad.recorded.should == ["message"] + end + + it 'captures successfully in a module' do + ScratchPad.record [] + + module RescueSpecs::M + raise "message" + rescue => e + ScratchPad << e.message + end + + ScratchPad.recorded.should == ["message"] + end + + it 'captures sucpcessfully in a singleton class' do + ScratchPad.record [] + + class << Object.new + raise "message" + rescue => e + ScratchPad << e.message + end + + ScratchPad.recorded.should == ["message"] + end + + it 'captures successfully at the top-level' do + ScratchPad.record [] + + require_relative 'fixtures/rescue/top_level' + + ScratchPad.recorded.should == ["message"] + end + end + it "returns value from `rescue` if an exception was raised" do begin raise diff --git a/spec/ruby/language/safe_navigator_spec.rb b/spec/ruby/language/safe_navigator_spec.rb index c3aecff2dd..b1e28c3963 100644 --- a/spec/ruby/language/safe_navigator_spec.rb +++ b/spec/ruby/language/safe_navigator_spec.rb @@ -7,43 +7,43 @@ describe "Safe navigator" do context "when context is nil" do it "always returns nil" do - eval("nil&.unknown").should == nil - eval("[][10]&.unknown").should == nil + nil&.unknown.should == nil + [][10]&.unknown.should == nil end it "can be chained" do - eval("nil&.one&.two&.three").should == nil + nil&.one&.two&.three.should == nil end it "doesn't evaluate arguments" do obj = Object.new obj.should_not_receive(:m) - eval("nil&.unknown(obj.m) { obj.m }") + nil&.unknown(obj.m) { obj.m } end end context "when context is false" do it "calls the method" do - eval("false&.to_s").should == "false" + false&.to_s.should == "false" - -> { eval("false&.unknown") }.should raise_error(NoMethodError) + -> { false&.unknown }.should raise_error(NoMethodError) end end context "when context is truthy" do it "calls the method" do - eval("1&.to_s").should == "1" + 1&.to_s.should == "1" - -> { eval("1&.unknown") }.should raise_error(NoMethodError) + -> { 1&.unknown }.should raise_error(NoMethodError) end end it "takes a list of arguments" do - eval("[1,2,3]&.first(2)").should == [1,2] + [1,2,3]&.first(2).should == [1,2] end it "takes a block" do - eval("[1,2]&.map { |i| i * 2 }").should == [2, 4] + [1,2]&.map { |i| i * 2 }.should == [2, 4] end it "allows assignment methods" do @@ -56,29 +56,77 @@ describe "Safe navigator" do end obj = klass.new - eval("obj&.foo = 3").should == 3 + (obj&.foo = 3).should == 3 obj.foo.should == 3 obj = nil - eval("obj&.foo = 3").should == nil + (obj&.foo = 3).should == nil end it "allows assignment operators" do klass = Class.new do - attr_accessor :m + attr_reader :m def initialize @m = 0 end + + def m=(v) + @m = v + 42 + end end obj = klass.new - eval("obj&.m += 3") + obj&.m += 3 obj.m.should == 3 obj = nil - eval("obj&.m += 3").should == nil + (obj&.m += 3).should == nil + end + + it "allows ||= operator" do + klass = Class.new do + attr_reader :m + + def initialize + @m = false + end + + def m=(v) + @m = v + 42 + end + end + + obj = klass.new + + (obj&.m ||= true).should == true + obj.m.should == true + + obj = nil + (obj&.m ||= true).should == nil + obj.should == nil + end + + it "allows &&= operator" do + klass = Class.new do + attr_accessor :m + + def initialize + @m = true + end + end + + obj = klass.new + + (obj&.m &&= false).should == false + obj.m.should == false + + obj = nil + (obj&.m &&= false).should == nil + obj.should == nil end it "does not call the operator method lazily with an assignment operator" do @@ -91,7 +139,7 @@ describe "Safe navigator" do obj = klass.new -> { - eval("obj&.foo += 3") + obj&.foo += 3 }.should raise_error(NoMethodError) { |e| e.name.should == :+ } diff --git a/spec/ruby/language/super_spec.rb b/spec/ruby/language/super_spec.rb index d22c603605..a98b3b3091 100644 --- a/spec/ruby/language/super_spec.rb +++ b/spec/ruby/language/super_spec.rb @@ -335,6 +335,13 @@ describe "The super keyword" do it "without explicit arguments that are '_'" do SuperSpecs::ZSuperWithUnderscores::B.new.m(1, 2).should == [1, 2] + SuperSpecs::ZSuperWithUnderscores::B.new.m3(1, 2, 3).should == [1, 2, 3] + SuperSpecs::ZSuperWithUnderscores::B.new.m4(1, 2, 3, 4).should == [1, 2, 3, 4] + SuperSpecs::ZSuperWithUnderscores::B.new.m_default(1).should == [1] + SuperSpecs::ZSuperWithUnderscores::B.new.m_default.should == [0] + SuperSpecs::ZSuperWithUnderscores::B.new.m_pre_default_rest_post(1, 2, 3, 4, 5, 6, 7).should == [1, 2, 3, 4, 5, 6, 7] + SuperSpecs::ZSuperWithUnderscores::B.new.m_rest(1, 2).should == [1, 2] + SuperSpecs::ZSuperWithUnderscores::B.new.m_kwrest(a: 1).should == {a: 1} end it "without explicit arguments that are '_' including any modifications" do diff --git a/spec/ruby/language/variables_spec.rb b/spec/ruby/language/variables_spec.rb index 23c2cdb557..53d191b456 100644 --- a/spec/ruby/language/variables_spec.rb +++ b/spec/ruby/language/variables_spec.rb @@ -367,8 +367,13 @@ describe "Multiple assignment" do it "assigns indexed elements" do a = [] - a[1], a[2] = 1 - a.should == [nil, 1, nil] + a[1], a[2] = 1, 2 + a.should == [nil, 1, 2] + + # with splatted argument + a = [] + a[*[1]], a[*[2]] = 1, 2 + a.should == [nil, 1, 2] end it "assigns constants" do diff --git a/spec/ruby/library/coverage/result_spec.rb b/spec/ruby/library/coverage/result_spec.rb index 4bcce00cd7..33276778e8 100644 --- a/spec/ruby/library/coverage/result_spec.rb +++ b/spec/ruby/library/coverage/result_spec.rb @@ -8,10 +8,16 @@ describe 'Coverage.result' do @eval_code_file = fixture __FILE__, 'eval_code.rb' end + before :each do + Coverage.running?.should == false + end + after :each do $LOADED_FEATURES.delete(@class_file) $LOADED_FEATURES.delete(@config_file) $LOADED_FEATURES.delete(@eval_code_file) + + Coverage.result if Coverage.running? end it 'gives the covered files as a hash with arrays of count or nil' do @@ -26,6 +32,41 @@ describe 'Coverage.result' do } end + ruby_version_is "3.2" do + it 'returns results for each mode separately when enabled :all modes' do + Coverage.start(:all) + require @class_file.chomp('.rb') + result = Coverage.result + + result.should == { + @class_file => { + lines: [ + nil, nil, 1, nil, nil, 1, nil, nil, 0, nil, nil, nil, nil, nil, nil, nil + ], + branches: {}, + methods: { + [SomeClass, :some_method, 6, 2, 11, 5] => 0 + } + } + } + end + + it 'returns results for each mode separately when enabled any mode explicitly' do + Coverage.start(lines: true) + require @class_file.chomp('.rb') + result = Coverage.result + + result.should == { + @class_file => + { + lines: [ + nil, nil, 1, nil, nil, 1, nil, nil, 0, nil, nil, nil, nil, nil, nil, nil + ] + } + } + end + end + it 'no requires/loads should give empty hash' do Coverage.start result = Coverage.result @@ -75,17 +116,6 @@ describe 'Coverage.result' do end end - ruby_version_is '3.1' do - it 'second Coverage.start give exception' do - Coverage.start - -> { - require @config_file.chomp('.rb') - }.should raise_error(RuntimeError, 'coverage measurement is already setup') - ensure - Coverage.result - end - end - it 'does not include the file starting coverage since it is not tracked' do require @config_file.chomp('.rb') Coverage.result.should_not include(@config_file) @@ -98,18 +128,14 @@ describe 'Coverage.result' do result = Coverage.result result.should == { - @eval_code_file => [ - 1, nil, 1, nil, 1, nil, nil, nil, nil, nil, 1 - ] + @eval_code_file => [ + 1, nil, 1, nil, 1, nil, nil, nil, nil, nil, 1 + ] } end end ruby_version_is '3.2' do - it 'indicates support for different features' do - Coverage.supported?(:lines).should == true - end - it 'returns the correct results when eval coverage is enabled' do Coverage.supported?(:eval).should == true @@ -118,13 +144,13 @@ describe 'Coverage.result' do result = Coverage.result result.should == { - @eval_code_file => { - lines: [1, nil, 1, nil, 1, 1, nil, nil, nil, nil, 1] - } + @eval_code_file => { + lines: [1, nil, 1, nil, 1, 1, nil, nil, nil, nil, 1] + } } end - it 'returns the correct results when eval coverage is enabled' do + it 'returns the correct results when eval coverage is disabled' do Coverage.supported?(:eval).should == true Coverage.start(lines: true, eval: false) @@ -132,10 +158,200 @@ describe 'Coverage.result' do result = Coverage.result result.should == { - @eval_code_file => { - lines: [1, nil, 1, nil, 1, nil, nil, nil, nil, nil, 1] - } + @eval_code_file => { + lines: [1, nil, 1, nil, 1, nil, nil, nil, nil, nil, 1] + } } end end + + it "disables coverage measurement when stop option is not specified" do + Coverage.start + require @class_file.chomp('.rb') + + Coverage.result + Coverage.running?.should == false + end + + it "disables coverage measurement when stop: true option is specified" do + Coverage.start + require @class_file.chomp('.rb') + + -> { + Coverage.result(stop: true) + }.should complain(/warning: stop implies clear/) + + Coverage.running?.should == false + end + + it "does not disable coverage measurement when stop: false option is specified" do + Coverage.start + require @class_file.chomp('.rb') + + Coverage.result(stop: false) + Coverage.running?.should == true + end + + it "does not disable coverage measurement when stop option is not specified but clear: true specified" do + Coverage.start + require @class_file.chomp('.rb') + + Coverage.result(clear: true) + Coverage.running?.should == true + end + + it "does not disable coverage measurement when stop option is not specified but clear: false specified" do + Coverage.start + require @class_file.chomp('.rb') + + Coverage.result(clear: false) + Coverage.running?.should == true + end + + it "disables coverage measurement when stop: true and clear: true specified" do + Coverage.start + require @class_file.chomp('.rb') + + Coverage.result(stop: true, clear: true) + Coverage.running?.should == false + end + + it "disables coverage measurement when stop: true and clear: false specified" do + Coverage.start + require @class_file.chomp('.rb') + + -> { + Coverage.result(stop: true, clear: false) + }.should complain(/warning: stop implies clear/) + + Coverage.running?.should == false + end + + it "does not disable coverage measurement when stop: false and clear: true specified" do + Coverage.start + require @class_file.chomp('.rb') + + Coverage.result(stop: false, clear: true) + Coverage.running?.should == true + end + + it "does not disable coverage measurement when stop: false and clear: false specified" do + Coverage.start + require @class_file.chomp('.rb') + + Coverage.result(stop: false, clear: false) + Coverage.running?.should == true + end + + it "resets counters (remove them) when stop: true specified but clear option is not specified" do + Coverage.start + require @class_file.chomp('.rb') + + -> { + Coverage.result(stop: true) # clears counters + }.should complain(/warning: stop implies clear/) + + Coverage.start + Coverage.peek_result.should == {} + end + + it "resets counters (remove them) when stop: true and clear: true specified" do + Coverage.start + require @class_file.chomp('.rb') + + Coverage.result(stop: true, clear: true) # clears counters + + Coverage.start + Coverage.peek_result.should == {} + end + + it "resets counters (remove them) when stop: true and clear: false specified" do + Coverage.start + require @class_file.chomp('.rb') + + -> { + Coverage.result(stop: true, clear: false) # clears counters + }.should complain(/warning: stop implies clear/) + + Coverage.start + Coverage.peek_result.should == {} + end + + it "resets counters (remove them) when both stop and clear options are not specified" do + Coverage.start + require @class_file.chomp('.rb') + + Coverage.result # clears counters + + Coverage.start + Coverage.peek_result.should == {} + end + + it "clears counters (sets 0 values) when stop is not specified but clear: true specified" do + Coverage.start + require @class_file.chomp('.rb') + + Coverage.result(clear: true) # clears counters + + Coverage.peek_result.should == { + @class_file => [ + nil, nil, 0, nil, nil, 0, nil, nil, 0, nil, nil, nil, nil, nil, nil, nil + ] + } + end + + it "does not clear counters when stop is not specified but clear: false specified" do + Coverage.start + require @class_file.chomp('.rb') + + result = Coverage.result(clear: false) # doesn't clear counters + result.should == { + @class_file => [ + nil, nil, 1, nil, nil, 1, nil, nil, 0, nil, nil, nil, nil, nil, nil, nil + ] + } + + Coverage.peek_result.should == result + end + + it "does not clear counters when stop: false and clear is not specified" do + Coverage.start + require @class_file.chomp('.rb') + + result = Coverage.result(stop: false) # doesn't clear counters + result.should == { + @class_file => [ + nil, nil, 1, nil, nil, 1, nil, nil, 0, nil, nil, nil, nil, nil, nil, nil + ] + } + + Coverage.peek_result.should == result + end + + it "clears counters (sets 0 values) when stop: false and clear: true specified" do + Coverage.start + require @class_file.chomp('.rb') + + Coverage.result(stop: false, clear: true) # clears counters + + Coverage.peek_result.should == { + @class_file => [ + nil, nil, 0, nil, nil, 0, nil, nil, 0, nil, nil, nil, nil, nil, nil, nil + ] + } + end + + it "does not clear counters when stop: false and clear: false specified" do + Coverage.start + require @class_file.chomp('.rb') + + result = Coverage.result(stop: false, clear: false) # doesn't clear counters + result.should == { + @class_file => [ + nil, nil, 1, nil, nil, 1, nil, nil, 0, nil, nil, nil, nil, nil, nil, nil + ] + } + + Coverage.peek_result.should == result + end end diff --git a/spec/ruby/library/coverage/start_spec.rb b/spec/ruby/library/coverage/start_spec.rb index a993abbf4e..7fccf2d5cf 100644 --- a/spec/ruby/library/coverage/start_spec.rb +++ b/spec/ruby/library/coverage/start_spec.rb @@ -2,8 +2,87 @@ require_relative '../../spec_helper' require 'coverage' describe 'Coverage.start' do + before :each do + Coverage.should_not.running? + end + + after :each do + Coverage.result(stop: true, clear: true) if Coverage.running? + end + + it "enables the coverage measurement" do + Coverage.start + Coverage.should.running? + end + + it "returns nil" do + Coverage.start.should == nil + end + + ruby_version_is '3.1' do + it 'raises error when repeated Coverage.start call happens' do + Coverage.start + + -> { + Coverage.start + }.should raise_error(RuntimeError, 'coverage measurement is already setup') + end + end + ruby_version_is '3.2' do - it "can measure coverage within eval" do + it "accepts :all optional argument" do + Coverage.start(:all) + Coverage.should.running? + end + + it "accepts lines: optional keyword argument" do + Coverage.start(lines: true) + Coverage.should.running? + end + + it "accepts branches: optional keyword argument" do + Coverage.start(branches: true) + Coverage.should.running? + end + + it "accepts methods: optional keyword argument" do + Coverage.start(methods: true) + Coverage.should.running? + end + + it "accepts eval: optional keyword argument" do + Coverage.start(eval: true) + Coverage.should.running? + end + + it "accepts oneshot_lines: optional keyword argument" do + Coverage.start(oneshot_lines: true) + Coverage.should.running? + end + + it "ignores unknown keyword arguments" do + Coverage.start(foo: true) + Coverage.should.running? + end + + it "expects a Hash if not passed :all" do + -> { + Coverage.start(42) + }.should raise_error(TypeError, "no implicit conversion of Integer into Hash") + end + + it "does not accept both lines: and oneshot_lines: keyword arguments" do + -> { + Coverage.start(lines: true, oneshot_lines: true) + }.should raise_error(RuntimeError, "cannot enable lines and oneshot_lines simultaneously") + end + + it "enables the coverage measurement if passed options with `false` value" do + Coverage.start(lines: false, branches: false, methods: false, eval: false, oneshot_lines: false) + Coverage.should.running? + end + + it "measures coverage within eval" do Coverage.start(lines: true, eval: true) eval("Object.new\n"*3, binding, "test.rb", 1) Coverage.result["test.rb"].should == {lines: [1, 1, 1]} diff --git a/spec/ruby/library/objectspace/reachable_objects_from_spec.rb b/spec/ruby/library/objectspace/reachable_objects_from_spec.rb index 7e70bc8569..dee5961663 100644 --- a/spec/ruby/library/objectspace/reachable_objects_from_spec.rb +++ b/spec/ruby/library/objectspace/reachable_objects_from_spec.rb @@ -38,7 +38,6 @@ describe "ObjectSpace.reachable_objects_from" do end it "finds an object stored in a Queue" do - require 'thread' o = Object.new q = Queue.new q << o @@ -49,7 +48,6 @@ describe "ObjectSpace.reachable_objects_from" do end it "finds an object stored in a SizedQueue" do - require 'thread' o = Object.new q = SizedQueue.new(3) q << o diff --git a/spec/ruby/library/socket/shared/pack_sockaddr.rb b/spec/ruby/library/socket/shared/pack_sockaddr.rb index 9f6238e7bc..26fdf682b1 100644 --- a/spec/ruby/library/socket/shared/pack_sockaddr.rb +++ b/spec/ruby/library/socket/shared/pack_sockaddr.rb @@ -17,6 +17,9 @@ describe :socket_pack_sockaddr_in, shared: true do sockaddr_in = Socket.public_send(@method, nil, '127.0.0.1') Socket.unpack_sockaddr_in(sockaddr_in).should == [0, '127.0.0.1'] + + sockaddr_in = Socket.public_send(@method, 80, Socket::INADDR_ANY) + Socket.unpack_sockaddr_in(sockaddr_in).should == [80, '0.0.0.0'] end platform_is_not :solaris do diff --git a/spec/ruby/library/socket/socket/pack_sockaddr_in_spec.rb b/spec/ruby/library/socket/socket/pack_sockaddr_in_spec.rb index 63d4724453..ef2a2d4ba9 100644 --- a/spec/ruby/library/socket/socket/pack_sockaddr_in_spec.rb +++ b/spec/ruby/library/socket/socket/pack_sockaddr_in_spec.rb @@ -2,6 +2,6 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' require_relative '../shared/pack_sockaddr' -describe "Socket#pack_sockaddr_in" do +describe "Socket.pack_sockaddr_in" do it_behaves_like :socket_pack_sockaddr_in, :pack_sockaddr_in end diff --git a/spec/ruby/library/socket/socket/pair_spec.rb b/spec/ruby/library/socket/socket/pair_spec.rb index 292eacd38d..8dd470a95e 100644 --- a/spec/ruby/library/socket/socket/pair_spec.rb +++ b/spec/ruby/library/socket/socket/pair_spec.rb @@ -2,6 +2,6 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' require_relative '../shared/socketpair' -describe "Socket#pair" do +describe "Socket.pair" do it_behaves_like :socket_socketpair, :pair end diff --git a/spec/ruby/library/socket/socket/socketpair_spec.rb b/spec/ruby/library/socket/socket/socketpair_spec.rb index 5b8311124e..551c376d49 100644 --- a/spec/ruby/library/socket/socket/socketpair_spec.rb +++ b/spec/ruby/library/socket/socket/socketpair_spec.rb @@ -2,6 +2,6 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' require_relative '../shared/socketpair' -describe "Socket#socketpair" do +describe "Socket.socketpair" do it_behaves_like :socket_socketpair, :socketpair end diff --git a/spec/ruby/library/socket/tcpserver/accept_spec.rb b/spec/ruby/library/socket/tcpserver/accept_spec.rb index d38d95e0e1..8f1f0016f0 100644 --- a/spec/ruby/library/socket/tcpserver/accept_spec.rb +++ b/spec/ruby/library/socket/tcpserver/accept_spec.rb @@ -114,6 +114,19 @@ describe 'TCPServer#accept' do @socket = @server.accept @socket.should be_an_instance_of(TCPSocket) end + + platform_is_not :windows do + it "returns a TCPSocket which is set to nonblocking" do + require 'io/nonblock' + @socket = @server.accept + @socket.should.nonblock? + end + end + + it "returns a TCPSocket which is set to close on exec" do + @socket = @server.accept + @socket.should.close_on_exec? + end end end end diff --git a/spec/ruby/library/socket/tcpsocket/initialize_spec.rb b/spec/ruby/library/socket/tcpsocket/initialize_spec.rb index 3bec06c697..d7feb9751b 100644 --- a/spec/ruby/library/socket/tcpsocket/initialize_spec.rb +++ b/spec/ruby/library/socket/tcpsocket/initialize_spec.rb @@ -72,6 +72,19 @@ describe 'TCPSocket#initialize' do @client.remote_address.ip_port.should == @server.local_address.ip_port end + platform_is_not :windows do + it "creates a socket which is set to nonblocking" do + require 'io/nonblock' + @client = TCPSocket.new(ip_address, @port) + @client.should.nonblock? + end + end + + it "creates a socket which is set to close on exec" do + @client = TCPSocket.new(ip_address, @port) + @client.should.close_on_exec? + end + describe 'using a local address and service' do it 'binds the client socket to the local address and service' do @client = TCPSocket.new(ip_address, @port, ip_address, 0) diff --git a/spec/ruby/library/socket/udpsocket/initialize_spec.rb b/spec/ruby/library/socket/udpsocket/initialize_spec.rb index 1d635149f7..ecf0043c10 100644 --- a/spec/ruby/library/socket/udpsocket/initialize_spec.rb +++ b/spec/ruby/library/socket/udpsocket/initialize_spec.rb @@ -30,6 +30,19 @@ describe 'UDPSocket#initialize' do @socket.binmode?.should be_true end + platform_is_not :windows do + it 'sets the socket to nonblock' do + require 'io/nonblock' + @socket = UDPSocket.new(:INET) + @socket.should.nonblock? + end + end + + it 'sets the socket to close on exec' do + @socket = UDPSocket.new(:INET) + @socket.should.close_on_exec? + end + it 'raises Errno::EAFNOSUPPORT or Errno::EPROTONOSUPPORT when given an invalid address family' do -> { UDPSocket.new(666) diff --git a/spec/ruby/library/socket/unixserver/accept_spec.rb b/spec/ruby/library/socket/unixserver/accept_spec.rb index 624782d6b9..1305bc6220 100644 --- a/spec/ruby/library/socket/unixserver/accept_spec.rb +++ b/spec/ruby/library/socket/unixserver/accept_spec.rb @@ -110,6 +110,17 @@ with_feature :unix_socket do @socket = @server.accept @socket.recv(5).should == 'hello' end + + it "is set to nonblocking" do + require 'io/nonblock' + @socket = @server.accept + @socket.should.nonblock? + end + + it "is set to close on exec" do + @socket = @server.accept + @socket.should.close_on_exec? + end end end end diff --git a/spec/ruby/library/socket/unixserver/for_fd_spec.rb b/spec/ruby/library/socket/unixserver/for_fd_spec.rb index e00c4d9526..8cc55ef391 100644 --- a/spec/ruby/library/socket/unixserver/for_fd_spec.rb +++ b/spec/ruby/library/socket/unixserver/for_fd_spec.rb @@ -2,7 +2,7 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' with_feature :unix_socket do - describe "UNIXServer#for_fd" do + describe "UNIXServer.for_fd" do before :each do @unix_path = SocketSpecs.socket_path @unix = UNIXServer.new(@unix_path) diff --git a/spec/ruby/library/socket/unixsocket/initialize_spec.rb b/spec/ruby/library/socket/unixsocket/initialize_spec.rb index 13b6972f03..bf7896ab0e 100644 --- a/spec/ruby/library/socket/unixsocket/initialize_spec.rb +++ b/spec/ruby/library/socket/unixsocket/initialize_spec.rb @@ -33,6 +33,16 @@ with_feature :unix_socket do it 'sets the socket to binmode' do @socket.binmode?.should be_true end + + it 'sets the socket to nonblock' do + require 'io/nonblock' + @socket.should.nonblock? + end + + it 'sets the socket to close on exec' do + @socket.should.close_on_exec? + end + end end end diff --git a/spec/ruby/library/socket/unixsocket/pair_spec.rb b/spec/ruby/library/socket/unixsocket/pair_spec.rb index 9a66c56c10..d80b60894d 100644 --- a/spec/ruby/library/socket/unixsocket/pair_spec.rb +++ b/spec/ruby/library/socket/unixsocket/pair_spec.rb @@ -3,7 +3,7 @@ require_relative '../fixtures/classes' require_relative '../shared/partially_closable_sockets' with_feature :unix_socket do - describe "UNIXSocket#pair" do + describe "UNIXSocket.pair" do it_should_behave_like :partially_closable_sockets before :each do diff --git a/spec/ruby/library/yaml/fixtures/strings.rb b/spec/ruby/library/yaml/fixtures/strings.rb index 6f66dc3659..f478f89823 100644 --- a/spec/ruby/library/yaml/fixtures/strings.rb +++ b/spec/ruby/library/yaml/fixtures/strings.rb @@ -1,36 +1,26 @@ -$complex_key_1 = <&1", exit_status: 1) $?.should_not.success? result.should.include?("handler ran\n") - result.should.include?("syntax error") + + # it's tempting not to rely on error message and rely only on exception class name, + # but CRuby before 3.2 doesn't print class name for syntax error + result.should include_any_of("syntax error", "SyntaxError") end it "calls the nested handler right after the outer one if a handler is nested into another handler" do diff --git a/spec/ruby/shared/queue/deque.rb b/spec/ruby/shared/queue/deque.rb index 9e6b45009d..0abba5301e 100644 --- a/spec/ruby/shared/queue/deque.rb +++ b/spec/ruby/shared/queue/deque.rb @@ -70,7 +70,7 @@ describe :queue_deq, shared: true do q = @object.call t = Thread.new { - q.send(@method, timeout: 1).should == 1 + q.send(@method, timeout: TIME_TOLERANCE).should == 1 } Thread.pass until t.status == "sleep" && q.num_waiting == 1 q << 1 @@ -80,10 +80,9 @@ describe :queue_deq, shared: true do it "returns nil if no item is available in time" do q = @object.call - t = Thread.new { - q.send(@method, timeout: 0.1).should == nil - } - t.join + Thread.new { + q.send(@method, timeout: 0.001).should == nil + }.join end it "does nothing if the timeout is nil" do @@ -91,7 +90,7 @@ describe :queue_deq, shared: true do t = Thread.new { q.send(@method, timeout: nil).should == 1 } - t.join(0.2).should == nil + Thread.pass until t.status == "sleep" && q.num_waiting == 1 q << 1 t.join end @@ -105,23 +104,20 @@ describe :queue_deq, shared: true do it "raise TypeError if timeout is not a valid numeric" do q = @object.call - -> { q.send(@method, timeout: "1") }.should raise_error( - TypeError, - "no implicit conversion to float from string", - ) - - -> { q.send(@method, timeout: false) }.should raise_error( - TypeError, - "no implicit conversion to float from false", - ) + -> { + q.send(@method, timeout: "1") + }.should raise_error(TypeError, "no implicit conversion to float from string") + + -> { + q.send(@method, timeout: false) + }.should raise_error(TypeError, "no implicit conversion to float from false") end it "raise ArgumentError if non_block = true is passed too" do q = @object.call - -> { q.send(@method, true, timeout: 1) }.should raise_error( - ArgumentError, - "can't set a timeout if non_block is enabled", - ) + -> { + q.send(@method, true, timeout: 1) + }.should raise_error(ArgumentError, "can't set a timeout if non_block is enabled") end it "returns nil for a closed empty queue" do diff --git a/spec/ruby/shared/rational/coerce.rb b/spec/ruby/shared/rational/coerce.rb index 1650668fe6..38925721ed 100644 --- a/spec/ruby/shared/rational/coerce.rb +++ b/spec/ruby/shared/rational/coerce.rb @@ -1,33 +1,31 @@ require_relative '../../spec_helper' -ruby_version_is ""..."3.4" do - - require 'bigdecimal' - - describe :rational_coerce, shared: true do - it "returns the passed argument, self as Float, when given a Float" do - result = Rational(3, 4).coerce(1.0) - result.should == [1.0, 0.75] - result.first.is_a?(Float).should be_true - result.last.is_a?(Float).should be_true - end +describe :rational_coerce, shared: true do + it "returns the passed argument, self as Float, when given a Float" do + result = Rational(3, 4).coerce(1.0) + result.should == [1.0, 0.75] + result.first.is_a?(Float).should be_true + result.last.is_a?(Float).should be_true + end - it "returns the passed argument, self as Rational, when given an Integer" do - result = Rational(3, 4).coerce(10) - result.should == [Rational(10, 1), Rational(3, 4)] - result.first.is_a?(Rational).should be_true - result.last.is_a?(Rational).should be_true - end + it "returns the passed argument, self as Rational, when given an Integer" do + result = Rational(3, 4).coerce(10) + result.should == [Rational(10, 1), Rational(3, 4)] + result.first.is_a?(Rational).should be_true + result.last.is_a?(Rational).should be_true + end - it "coerces to Rational, when given a Complex" do - Rational(3, 4).coerce(Complex(5)).should == [Rational(5, 1), Rational(3, 4)] - Rational(12, 4).coerce(Complex(5, 1)).should == [Complex(5, 1), Complex(3)] - end + it "coerces to Rational, when given a Complex" do + Rational(3, 4).coerce(Complex(5)).should == [Rational(5, 1), Rational(3, 4)] + Rational(12, 4).coerce(Complex(5, 1)).should == [Complex(5, 1), Complex(3)] + end - it "returns [argument, self] when given a Rational" do - Rational(3, 7).coerce(Rational(9, 2)).should == [Rational(9, 2), Rational(3, 7)] - end + it "returns [argument, self] when given a Rational" do + Rational(3, 7).coerce(Rational(9, 2)).should == [Rational(9, 2), Rational(3, 7)] + end + ruby_version_is ""..."3.4" do + require 'bigdecimal' it "raises an error when passed a BigDecimal" do -> { Rational(500, 3).coerce(BigDecimal('166.666666666')) diff --git a/spec/ruby/shared/sizedqueue/enque.rb b/spec/ruby/shared/sizedqueue/enque.rb index 6307f3c3ca..2f25517675 100644 --- a/spec/ruby/shared/sizedqueue/enque.rb +++ b/spec/ruby/shared/sizedqueue/enque.rb @@ -55,7 +55,7 @@ describe :sizedqueue_enq, shared: true do q << 1 t = Thread.new { - q.send(@method, 2, timeout: 1).should == q + q.send(@method, 2, timeout: TIME_TOLERANCE).should == q } Thread.pass until t.status == "sleep" && q.num_waiting == 1 q.pop @@ -82,31 +82,27 @@ describe :sizedqueue_enq, shared: true do it "returns nil if no space is available in time" do q = @object.call(1) q << 1 - t = Thread.new { - q.send(@method, 2, timeout: 0.1).should == nil - } - t.join + Thread.new { + q.send(@method, 2, timeout: 0.001).should == nil + }.join end it "raise TypeError if timeout is not a valid numeric" do q = @object.call(1) - -> { q.send(@method, 2, timeout: "1") }.should raise_error( - TypeError, - "no implicit conversion to float from string", - ) - - -> { q.send(@method, 2, timeout: false) }.should raise_error( - TypeError, - "no implicit conversion to float from false", - ) + -> { + q.send(@method, 2, timeout: "1") + }.should raise_error(TypeError, "no implicit conversion to float from string") + + -> { + q.send(@method, 2, timeout: false) + }.should raise_error(TypeError, "no implicit conversion to float from false") end it "raise ArgumentError if non_block = true is passed too" do q = @object.call(1) - -> { q.send(@method, 2, true, timeout: 1) }.should raise_error( - ArgumentError, - "can't set a timeout if non_block is enabled", - ) + -> { + q.send(@method, 2, true, timeout: 1) + }.should raise_error(ArgumentError, "can't set a timeout if non_block is enabled") end it "raise ClosedQueueError when closed before enqueued" do @@ -120,7 +116,7 @@ describe :sizedqueue_enq, shared: true do q << 1 t = Thread.new { - -> { q.send(@method, 1, timeout: 10) }.should raise_error(ClosedQueueError, "queue closed") + -> { q.send(@method, 1, timeout: TIME_TOLERANCE) }.should raise_error(ClosedQueueError, "queue closed") } Thread.pass until q.num_waiting == 1 -- cgit v1.2.3