diff --git a/spec/ruby/.rubocop.yml b/spec/ruby/.rubocop.yml index 5db9256572..a26b525b1c 100644 --- a/spec/ruby/.rubocop.yml +++ b/spec/ruby/.rubocop.yml @@ -113,6 +113,9 @@ Lint/Debugger: Exclude: - 'core/binding/fixtures/irb.rb' +Lint/Loop: + Enabled: false + Style/Lambda: Enabled: true EnforcedStyle: literal diff --git a/spec/ruby/.rubocop_todo.yml b/spec/ruby/.rubocop_todo.yml index 7a5f9f74c5..a469213841 100644 --- a/spec/ruby/.rubocop_todo.yml +++ b/spec/ruby/.rubocop_todo.yml @@ -76,12 +76,6 @@ Lint/LiteralInInterpolation: - 'language/undef_spec.rb' - 'library/net/ftp/connect_spec.rb' -# Offense count: 16 -Lint/Loop: - Exclude: - - 'language/until_spec.rb' - - 'language/while_spec.rb' - # Offense count: 8 # Cop supports --auto-correct. Lint/MultipleComparison: diff --git a/spec/ruby/core/array/flatten_spec.rb b/spec/ruby/core/array/flatten_spec.rb index b2aa015764..2f9fb8a3ec 100644 --- a/spec/ruby/core/array/flatten_spec.rb +++ b/spec/ruby/core/array/flatten_spec.rb @@ -90,7 +90,7 @@ describe "Array#flatten" do ArraySpecs::MyArray[].flatten.should be_an_instance_of(Array) ArraySpecs::MyArray[1, 2, 3].flatten.should be_an_instance_of(Array) ArraySpecs::MyArray[1, [2], 3].flatten.should be_an_instance_of(Array) - ArraySpecs::MyArray[1, [2, 3], 4].flatten.should == Array[1, 2, 3, 4] + ArraySpecs::MyArray[1, [2, 3], 4].flatten.should == [1, 2, 3, 4] [ArraySpecs::MyArray[1, 2, 3]].flatten.should be_an_instance_of(Array) end end diff --git a/spec/ruby/core/exception/errno_spec.rb b/spec/ruby/core/exception/errno_spec.rb index 78b3eafc2a..095a926e09 100644 --- a/spec/ruby/core/exception/errno_spec.rb +++ b/spec/ruby/core/exception/errno_spec.rb @@ -47,4 +47,12 @@ describe "Errno::ENOTSUP" do it "is defined" do Errno.should have_constant(:ENOTSUP) end + + it "is the same class as Errno::EOPNOTSUPP if they represent the same errno value" do + if Errno::ENOTSUP::Errno == Errno::EOPNOTSUPP::Errno + Errno::ENOTSUP.should == Errno::EOPNOTSUPP + else + Errno::ENOTSUP.should_not == Errno::EOPNOTSUPP + end + end end diff --git a/spec/ruby/core/exception/system_exit_spec.rb b/spec/ruby/core/exception/system_exit_spec.rb new file mode 100644 index 0000000000..5c6116576b --- /dev/null +++ b/spec/ruby/core/exception/system_exit_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../spec_helper' + +describe "SystemExit" do + it "sets the exit status and exits silently when raised" do + code = 'raise SystemExit.new(7)' + result = ruby_exe(code, args: "2>&1") + result.should == "" + $?.exitstatus.should == 7 + end + + it "sets the exit status and exits silently when raised when subclassed" do + code = 'class CustomExit < SystemExit; end; raise CustomExit.new(8)' + result = ruby_exe(code, args: "2>&1") + result.should == "" + $?.exitstatus.should == 8 + end +end diff --git a/spec/ruby/core/fiber/fixtures/classes.rb b/spec/ruby/core/fiber/fixtures/classes.rb new file mode 100644 index 0000000000..c00facd6e1 --- /dev/null +++ b/spec/ruby/core/fiber/fixtures/classes.rb @@ -0,0 +1,12 @@ +module FiberSpecs + + class NewFiberToRaise + def self.raise(*args) + fiber = Fiber.new { Fiber.yield } + fiber.resume + fiber.raise(*args) + end + end + + class CustomError < StandardError; end +end diff --git a/spec/ruby/core/fiber/raise_spec.rb b/spec/ruby/core/fiber/raise_spec.rb new file mode 100644 index 0000000000..fd1cc911b5 --- /dev/null +++ b/spec/ruby/core/fiber/raise_spec.rb @@ -0,0 +1,76 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../shared/kernel/raise' + +ruby_version_is "2.7" do + describe "Fiber#raise" do + it_behaves_like :kernel_raise, :raise, FiberSpecs::NewFiberToRaise + end + + describe "Fiber#raise" do + it 'raises RuntimeError by default' do + -> { FiberSpecs::NewFiberToRaise.raise }.should raise_error(RuntimeError) + end + + it "raises FiberError if Fiber is not born" do + fiber = Fiber.new { true } + -> { fiber.raise }.should raise_error(FiberError, "cannot raise exception on unborn fiber") + end + + it "raises FiberError if Fiber is dead" do + fiber = Fiber.new { true } + fiber.resume + -> { fiber.raise }.should raise_error(FiberError, /dead fiber called|attempt to resume a terminated fiber/) + end + + it 'accepts error class' do + -> { FiberSpecs::NewFiberToRaise.raise FiberSpecs::CustomError }.should raise_error(FiberSpecs::CustomError) + end + + it 'accepts error message' do + -> { FiberSpecs::NewFiberToRaise.raise "error message" }.should raise_error(RuntimeError, "error message") + end + + it 'does not accept array of backtrace information only' do + -> { FiberSpecs::NewFiberToRaise.raise ['foo'] }.should raise_error(TypeError) + end + + it 'does not accept integer' do + -> { FiberSpecs::NewFiberToRaise.raise 100 }.should raise_error(TypeError) + end + + it 'accepts error class with error message' do + -> { FiberSpecs::NewFiberToRaise.raise FiberSpecs::CustomError, 'test error' }.should raise_error(FiberSpecs::CustomError, 'test error') + end + + it 'accepts error class with with error message and backtrace information' do + -> { + FiberSpecs::NewFiberToRaise.raise FiberSpecs::CustomError, 'test error', ['foo', 'boo'] + }.should raise_error(FiberSpecs::CustomError) { |e| + e.message.should == 'test error' + e.backtrace.should == ['foo', 'boo'] + } + end + + it 'does not accept only error message and backtrace information' do + -> { FiberSpecs::NewFiberToRaise.raise 'test error', ['foo', 'boo'] }.should raise_error(TypeError) + end + + it "raises a FiberError if invoked from a different Thread" do + fiber = Fiber.new { Fiber.yield } + fiber.resume + Thread.new do + -> { + fiber.raise + }.should raise_error(FiberError, "fiber called across threads") + end.join + end + + it "kills Fiber" do + fiber = Fiber.new { Fiber.yield :first; :second } + fiber.resume + -> { fiber.raise }.should raise_error + -> { fiber.resume }.should raise_error(FiberError, /dead fiber called|attempt to resume a terminated fiber/) + end + end +end diff --git a/spec/ruby/core/file/extname_spec.rb b/spec/ruby/core/file/extname_spec.rb index e9b53bc24d..e182ed44f2 100644 --- a/spec/ruby/core/file/extname_spec.rb +++ b/spec/ruby/core/file/extname_spec.rb @@ -12,6 +12,16 @@ describe "File.extname" do File.extname(".app.conf").should == ".conf" end + it "returns unfrozen strings" do + File.extname("foo.rb").frozen?.should == false + File.extname("/foo/bar.rb").frozen?.should == false + File.extname("/foo.rb/bar.c").frozen?.should == false + File.extname("bar").frozen?.should == false + File.extname(".bashrc").frozen?.should == false + File.extname("/foo.bar/baz").frozen?.should == false + File.extname(".app.conf").frozen?.should == false + end + it "returns the extension for edge cases" do File.extname("").should == "" File.extname(".").should == "" diff --git a/spec/ruby/core/integer/shared/comparison_coerce.rb b/spec/ruby/core/integer/shared/comparison_coerce.rb index 50437f77f5..af52f5e99b 100644 --- a/spec/ruby/core/integer/shared/comparison_coerce.rb +++ b/spec/ruby/core/integer/shared/comparison_coerce.rb @@ -1,27 +1,5 @@ require_relative '../fixtures/classes' -describe :integer_comparison_coerce_rescue, shared: true do - it "rescues exception (StandardError and subclasses) raised in other#coerce and raises ArgumentError" do - b = mock("numeric with failed #coerce") - b.should_receive(:coerce).and_raise(IntegerSpecs::CoerceError) - - # e.g. 1 > b - -> { - -> { 1.send(@method, b) }.should raise_error(ArgumentError, /comparison of Integer with MockObject failed/) - }.should complain(/Numerical comparison operators will no more rescue exceptions of #coerce/) - end - - it "does not rescue Exception and StandardError siblings raised in other#coerce" do - [Exception, NoMemoryError].each do |exception| - b = mock("numeric with failed #coerce") - b.should_receive(:coerce).and_raise(exception) - - # e.g. 1 > b - -> { 1.send(@method, b) }.should raise_error(exception) - end - end -end - describe :integer_comparison_coerce_not_rescue, shared: true do it "does not rescue exception raised in other#coerce" do b = mock("numeric with failed #coerce") diff --git a/spec/ruby/core/io/set_encoding_by_bom_spec.rb b/spec/ruby/core/io/set_encoding_by_bom_spec.rb new file mode 100644 index 0000000000..b8e4eedcb9 --- /dev/null +++ b/spec/ruby/core/io/set_encoding_by_bom_spec.rb @@ -0,0 +1,57 @@ +require_relative '../../spec_helper' + +describe "IO#set_encoding_by_bom" do + before :each do + @name = tmp('io_set_encoding_by_bom.txt') + touch(@name) + @io = new_io(@name, 'rb') + end + + after :each do + @io.close unless @io.closed? + rm_r @name + end + + ruby_version_is "2.7" do + it "returns the result encoding if found BOM UTF-8 sequence" do + File.binwrite(@name, "\u{FEFF}abc") + + @io.set_encoding_by_bom.should == Encoding::UTF_8 + @io.external_encoding.should == Encoding::UTF_8 + end + + it "returns the result encoding if found BOM UTF_16LE sequence" do + File.binwrite(@name, "\xFF\xFEabc") + + @io.set_encoding_by_bom.should == Encoding::UTF_16LE + @io.external_encoding.should == Encoding::UTF_16LE + end + + it "returns the result encoding if found BOM UTF_16BE sequence" do + File.binwrite(@name, "\xFE\xFFabc") + + @io.set_encoding_by_bom.should == Encoding::UTF_16BE + @io.external_encoding.should == Encoding::UTF_16BE + end + + it "returns nil if found BOM sequence not provided" do + File.write(@name, "abc") + + @io.set_encoding_by_bom.should == nil + end + + it 'returns exception if io not in binary mode' do + not_binary_io = new_io(@name, 'r') + + -> { not_binary_io.set_encoding_by_bom }.should raise_error(ArgumentError, 'ASCII incompatible encoding needs binmode') + ensure + not_binary_io.close + end + + it 'returns exception if encoding already set' do + @io.set_encoding("utf-8") + + -> { @io.set_encoding_by_bom }.should raise_error(ArgumentError, 'encoding is set to UTF-8 already') + end + end +end diff --git a/spec/ruby/core/kernel/respond_to_spec.rb b/spec/ruby/core/kernel/respond_to_spec.rb index e7efc9f275..5b3ea3f651 100644 --- a/spec/ruby/core/kernel/respond_to_spec.rb +++ b/spec/ruby/core/kernel/respond_to_spec.rb @@ -25,7 +25,7 @@ describe "Kernel#respond_to?" do end it "throws a type error if argument can't be coerced into a Symbol" do - -> { @a.respond_to?(Object.new) }.should raise_error(TypeError) + -> { @a.respond_to?(Object.new) }.should raise_error(TypeError, /is not a symbol nor a string/) end it "returns false if obj responds to the given protected method" do @@ -69,5 +69,4 @@ describe "Kernel#respond_to?" do KernelSpecs::Foo.new.respond_to?(:bar).should == true KernelSpecs::Foo.new.respond_to?(:invalid_and_silly_method_name).should == false end - end diff --git a/spec/ruby/core/module/const_source_location_spec.rb b/spec/ruby/core/module/const_source_location_spec.rb new file mode 100644 index 0000000000..9e1f2c1c49 --- /dev/null +++ b/spec/ruby/core/module/const_source_location_spec.rb @@ -0,0 +1,220 @@ +require_relative '../../spec_helper' +require_relative '../../fixtures/constants' + +describe "Module#const_source_location" do + before do + @constants_fixture_path = File.expand_path('../../fixtures/constants.rb', __dir__) + end + + ruby_version_is "2.7" do + describe "with dynamically assigned constants" do + it "searches a path in the immediate class or module first" do + ConstantSpecs::ClassA::CSL_CONST301 = :const301_1 + ConstantSpecs::ClassA.const_source_location(:CSL_CONST301).should == [__FILE__, __LINE__ - 1] + + ConstantSpecs::ModuleA::CSL_CONST301 = :const301_2 + ConstantSpecs::ModuleA.const_source_location(:CSL_CONST301).should == [__FILE__, __LINE__ - 1] + + ConstantSpecs::ParentA::CSL_CONST301 = :const301_3 + ConstantSpecs::ParentA.const_source_location(:CSL_CONST301).should == [__FILE__, __LINE__ - 1] + + ConstantSpecs::ContainerA::ChildA::CSL_CONST301 = :const301_5 + ConstantSpecs::ContainerA::ChildA.const_source_location(:CSL_CONST301).should == [__FILE__, __LINE__ - 1] + end + + it "searches a path in a module included in the immediate class before the superclass" do + ConstantSpecs::ParentB::CSL_CONST302 = :const302_1 + ConstantSpecs::ModuleF::CSL_CONST302 = :const302_2 + ConstantSpecs::ContainerB::ChildB.const_source_location(:CSL_CONST302).should == [__FILE__, __LINE__ - 1] + end + + it "searches a path in the superclass before a module included in the superclass" do + ConstantSpecs::ModuleE::CSL_CONST303 = :const303_1 + ConstantSpecs::ParentB::CSL_CONST303 = :const303_2 + ConstantSpecs::ContainerB::ChildB.const_source_location(:CSL_CONST303).should == [__FILE__, __LINE__ - 1] + end + + it "searches a path in a module included in the superclass" do + ConstantSpecs::ModuleA::CSL_CONST304 = :const304_1 + ConstantSpecs::ModuleE::CSL_CONST304 = :const304_2 + ConstantSpecs::ContainerB::ChildB.const_source_location(:CSL_CONST304).should == [__FILE__, __LINE__ - 1] + end + + it "searches a path in the superclass chain" do + ConstantSpecs::ModuleA::CSL_CONST305 = :const305 + ConstantSpecs::ContainerB::ChildB.const_source_location(:CSL_CONST305).should == [__FILE__, __LINE__ - 1] + end + + it "returns path to a toplevel constant when the receiver is a Class" do + Object::CSL_CONST306 = :const306 + ConstantSpecs::ContainerB::ChildB.const_source_location(:CSL_CONST306).should == [__FILE__, __LINE__ - 1] + end + + it "returns path to a toplevel constant when the receiver is a Module" do + Object::CSL_CONST308 = :const308 + ConstantSpecs.const_source_location(:CSL_CONST308).should == [__FILE__, __LINE__ - 1] + ConstantSpecs::ModuleA.const_source_location(:CSL_CONST308).should == [__FILE__, __LINE__ - 2] + end + + it "returns path to the updated value of a constant" do + ConstantSpecs::ClassB::CSL_CONST309 = :const309_1 + ConstantSpecs::ClassB.const_source_location(:CSL_CONST309).should == [__FILE__, __LINE__ - 1] + + -> { + ConstantSpecs::ClassB::CSL_CONST309 = :const309_2 + }.should complain(/already initialized constant/) + ConstantSpecs::ClassB.const_source_location(:CSL_CONST309).should == [__FILE__, __LINE__ - 2] + end + end + + describe "with statically assigned constants" do + it "searches location path the immediate class or module first" do + ConstantSpecs::ClassA.const_source_location(:CS_CONST10).should == [@constants_fixture_path, ConstantSpecs::ClassA::CS_CONST10_LINE] + ConstantSpecs::ModuleA.const_source_location(:CS_CONST10).should == [@constants_fixture_path, ConstantSpecs::ModuleA::CS_CONST10_LINE] + ConstantSpecs::ParentA.const_source_location(:CS_CONST10).should == [@constants_fixture_path, ConstantSpecs::ParentA::CS_CONST10_LINE] + ConstantSpecs::ContainerA.const_source_location(:CS_CONST10).should == [@constants_fixture_path, ConstantSpecs::ContainerA::CS_CONST10_LINE] + ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST10).should == [@constants_fixture_path, ConstantSpecs::ContainerA::ChildA::CS_CONST10_LINE] + end + + it "searches location path a module included in the immediate class before the superclass" do + ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST15).should == [@constants_fixture_path, ConstantSpecs::ModuleC::CS_CONST15_LINE] + end + + it "searches location path the superclass before a module included in the superclass" do + ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST11).should == [@constants_fixture_path, ConstantSpecs::ParentA::CS_CONST11_LINE] + end + + it "searches location path a module included in the superclass" do + ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST12).should == [@constants_fixture_path, ConstantSpecs::ModuleB::CS_CONST12_LINE] + end + + it "searches location path the superclass chain" do + ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST13).should == [@constants_fixture_path, ConstantSpecs::ModuleA::CS_CONST13_LINE] + end + + it "returns location path a toplevel constant when the receiver is a Class" do + ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST1).should == [@constants_fixture_path, CS_CONST1_LINE] + end + + it "returns location path a toplevel constant when the receiver is a Module" do + ConstantSpecs.const_source_location(:CS_CONST1).should == [@constants_fixture_path, CS_CONST1_LINE] + ConstantSpecs::ModuleA.const_source_location(:CS_CONST1).should == [@constants_fixture_path, CS_CONST1_LINE] + end + end + + it "return empty path if constant defined in C code" do + Object.const_source_location(:String).should == [] + end + + it "accepts a String or Symbol name" do + Object.const_source_location(:CS_CONST1).should == [@constants_fixture_path, CS_CONST1_LINE] + Object.const_source_location("CS_CONST1").should == [@constants_fixture_path, CS_CONST1_LINE] + end + + it "returns nil if no constant is defined in the search path" do + ConstantSpecs.const_source_location(:CS_CONSTX).should == nil + end + + it "raises a NameError if the name does not start with a capital letter" do + -> { ConstantSpecs.const_source_location "name" }.should raise_error(NameError) + end + + it "raises a NameError if the name starts with a non-alphabetic character" do + -> { ConstantSpecs.const_source_location "__CONSTX__" }.should raise_error(NameError) + -> { ConstantSpecs.const_source_location "@CS_CONST1" }.should raise_error(NameError) + -> { ConstantSpecs.const_source_location "!CS_CONST1" }.should raise_error(NameError) + end + + it "raises a NameError if the name contains non-alphabetic characters except '_'" do + Object.const_source_location("CS_CONST1").should == [@constants_fixture_path, CS_CONST1_LINE] + -> { ConstantSpecs.const_source_location "CS_CONST1=" }.should raise_error(NameError) + -> { ConstantSpecs.const_source_location "CS_CONST1?" }.should raise_error(NameError) + end + + it "calls #to_str to convert the given name to a String" do + name = mock("ClassA") + name.should_receive(:to_str).and_return("ClassA") + ConstantSpecs.const_source_location(name).should == [@constants_fixture_path, ConstantSpecs::ClassA::CS_CLASS_A_LINE] + end + + it "raises a TypeError if conversion to a String by calling #to_str fails" do + name = mock('123') + -> { ConstantSpecs.const_source_location(name) }.should raise_error(TypeError) + + name.should_receive(:to_str).and_return(123) + -> { ConstantSpecs.const_source_location(name) }.should raise_error(TypeError) + end + + it "does not search the singleton class of a Class or Module" do + ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST14).should == nil + ConstantSpecs.const_source_location(:CS_CONST14).should == nil + end + + it "does not search the containing scope" do + ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST20).should == [@constants_fixture_path, ConstantSpecs::ParentA::CS_CONST20_LINE] + ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST5) == nil + end + + it "returns nil if the constant is defined in the receiver's superclass and the inherit flag is false" do + ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST4, false).should == nil + end + + it "searches into the receiver superclasses if the inherit flag is true" do + ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST4, true).should == [@constants_fixture_path, ConstantSpecs::ParentA::CS_CONST4_LINE] + end + + it "returns nil when the receiver is a Module, the constant is defined at toplevel and the inherit flag is false" do + ConstantSpecs::ModuleA.const_source_location(:CS_CONST1, false).should == nil + end + + it "returns nil when the receiver is a Class, the constant is defined at toplevel and the inherit flag is false" do + ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST1, false).should == nil + end + + it "accepts a toplevel scope qualifier" do + ConstantSpecs.const_source_location("::CS_CONST1").should == [@constants_fixture_path, CS_CONST1_LINE] + end + + it "accepts a scoped constant name" do + ConstantSpecs.const_source_location("ClassA::CS_CONST10").should == [@constants_fixture_path, ConstantSpecs::ClassA::CS_CONST10_LINE] + end + + it "raises a NameError if the name includes two successive scope separators" do + -> { ConstantSpecs.const_source_location("ClassA::::CS_CONST10") }.should raise_error(NameError) + end + + it "raises a NameError if only '::' is passed" do + -> { ConstantSpecs.const_source_location("::") }.should raise_error(NameError) + end + + it "raises a NameError if a Symbol has a toplevel scope qualifier" do + -> { ConstantSpecs.const_source_location(:'::CS_CONST1') }.should raise_error(NameError) + end + + it "raises a NameError if a Symbol is a scoped constant name" do + -> { ConstantSpecs.const_source_location(:'ClassA::CS_CONST10') }.should raise_error(NameError) + end + + it "does search private constants path" do + ConstantSpecs.const_source_location(:CS_PRIVATE).should == [@constants_fixture_path, ConstantSpecs::CS_PRIVATE_LINE] + end + + context 'autoload' do + before :all do + ConstantSpecs.autoload :CSL_CONST1, "#{__dir__}/notexisting.rb" + @line = __LINE__ - 1 + end + + it 'returns the autoload location while not resolved' do + ConstantSpecs.const_source_location('CSL_CONST1').should == [__FILE__, @line] + end + + it 'returns where the constant was resolved when resolved' do + file = fixture(__FILE__, 'autoload_location.rb') + ConstantSpecs.autoload :CONST_LOCATION, file + line = ConstantSpecs::CONST_LOCATION + ConstantSpecs.const_source_location('CONST_LOCATION').should == [file, line] + end + end + end +end diff --git a/spec/ruby/core/module/constants_spec.rb b/spec/ruby/core/module/constants_spec.rb index 4538e828dd..beb25c6eaa 100644 --- a/spec/ruby/core/module/constants_spec.rb +++ b/spec/ruby/core/module/constants_spec.rb @@ -43,31 +43,31 @@ end describe "Module#constants" do it "returns an array of Symbol names of all constants defined in the module and all included modules" do ConstantSpecs::ContainerA.constants.sort.should == [ - :CS_CONST10, :CS_CONST23, :CS_CONST24, :CS_CONST5, :ChildA + :CS_CONST10, :CS_CONST10_LINE, :CS_CONST23, :CS_CONST24, :CS_CONST5, :ChildA ] end it "returns all constants including inherited when passed true" do ConstantSpecs::ContainerA.constants(true).sort.should == [ - :CS_CONST10, :CS_CONST23, :CS_CONST24, :CS_CONST5, :ChildA + :CS_CONST10, :CS_CONST10_LINE, :CS_CONST23, :CS_CONST24, :CS_CONST5, :ChildA ] end it "returns all constants including inherited when passed some object" do ConstantSpecs::ContainerA.constants(Object.new).sort.should == [ - :CS_CONST10, :CS_CONST23, :CS_CONST24, :CS_CONST5, :ChildA + :CS_CONST10, :CS_CONST10_LINE, :CS_CONST23, :CS_CONST24, :CS_CONST5, :ChildA ] end it "doesn't returns inherited constants when passed false" do ConstantSpecs::ContainerA.constants(false).sort.should == [ - :CS_CONST10, :CS_CONST23, :CS_CONST5, :ChildA + :CS_CONST10, :CS_CONST10_LINE, :CS_CONST23, :CS_CONST5, :ChildA ] end it "doesn't returns inherited constants when passed nil" do ConstantSpecs::ContainerA.constants(nil).sort.should == [ - :CS_CONST10, :CS_CONST23, :CS_CONST5, :ChildA + :CS_CONST10, :CS_CONST10_LINE, :CS_CONST23, :CS_CONST5, :ChildA ] end diff --git a/spec/ruby/core/module/fixtures/autoload_location.rb b/spec/ruby/core/module/fixtures/autoload_location.rb new file mode 100644 index 0000000000..318851b2df --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_location.rb @@ -0,0 +1,3 @@ +module ConstantSpecs + CONST_LOCATION = __LINE__ +end diff --git a/spec/ruby/core/process/exec_spec.rb b/spec/ruby/core/process/exec_spec.rb index 0a2c2f77e7..deb8913b6b 100644 --- a/spec/ruby/core/process/exec_spec.rb +++ b/spec/ruby/core/process/exec_spec.rb @@ -193,9 +193,11 @@ describe "Process.exec" do map_fd_fixture = fixture __FILE__, "map_fd.rb" cmd = <<-EOC f = File.open(#{@name.inspect}, "w+") - child_fd = f.fileno + 1 - File.open(#{@child_fd_file.inspect}, "w") { |io| io.print child_fd } - Process.exec "#{ruby_cmd(map_fd_fixture)} \#{child_fd}", { child_fd => f } + File.open(#{__FILE__.inspect}, "r") do |io| + child_fd = io.fileno + File.open(#{@child_fd_file.inspect}, "w") { |io| io.print child_fd } + Process.exec "#{ruby_cmd(map_fd_fixture)} \#{child_fd}", { child_fd => f } + end EOC ruby_exe(cmd, escape: true) diff --git a/spec/ruby/core/process/spawn_spec.rb b/spec/ruby/core/process/spawn_spec.rb index a068e05571..40509471ef 100644 --- a/spec/ruby/core/process/spawn_spec.rb +++ b/spec/ruby/core/process/spawn_spec.rb @@ -706,13 +706,15 @@ describe "Process.spawn" do end it "maps the key to a file descriptor in the child that inherits the file descriptor from the parent specified by the value" do - child_fd = find_unused_fd - args = ruby_cmd(fixture(__FILE__, "map_fd.rb"), args: [child_fd.to_s]) - pid = Process.spawn(*args, { child_fd => @io }) - Process.waitpid pid - @io.rewind + File.open(__FILE__, "r") do |f| + child_fd = f.fileno + args = ruby_cmd(fixture(__FILE__, "map_fd.rb"), args: [child_fd.to_s]) + pid = Process.spawn(*args, { child_fd => @io }) + Process.waitpid pid + @io.rewind - @io.read.should == "writing to fd: #{child_fd}" + @io.read.should == "writing to fd: #{child_fd}" + end end end end diff --git a/spec/ruby/core/range/minmax_spec.rb b/spec/ruby/core/range/minmax_spec.rb new file mode 100644 index 0000000000..c3973af13f --- /dev/null +++ b/spec/ruby/core/range/minmax_spec.rb @@ -0,0 +1,170 @@ +require_relative '../../spec_helper' + +# These specs use Range.new instead of the literal notation so they parse fine on Ruby < 2.6 +describe 'Range#minmax' do + before(:each) do + @x = mock('x') + @y = mock('y') + + @x.should_receive(:<=>).with(@y).any_number_of_times.and_return(-1) # x < y + @x.should_receive(:<=>).with(@x).any_number_of_times.and_return(0) # x == x + @y.should_receive(:<=>).with(@x).any_number_of_times.and_return(1) # y > x + @y.should_receive(:<=>).with(@y).any_number_of_times.and_return(0) # y == y + end + + describe 'on an inclusive range' do + ruby_version_is '2.6'...'2.7' do + it 'should try to iterate endlessly on an endless range' do + @x.should_receive(:succ).once.and_return(@y) + range = Range.new(@x, nil) + + -> { range.minmax }.should raise_error(NoMethodError, /^undefined method `succ' for/) + end + end + + ruby_version_is '2.7' do + it 'should raise RangeError on an endless range without iterating the range' do + @x.should_not_receive(:succ) + + range = Range.new(@x, nil) + + -> { range.minmax }.should raise_error(RangeError, 'cannot get the maximum of endless range') + end + + it 'raises RangeError or ArgumentError on a beginless range' do + range = Range.new(nil, @x) + + -> { range.minmax }.should raise_error(StandardError) { |e| + if RangeError === e + # error from #min + -> { raise e }.should raise_error(RangeError, 'cannot get the minimum of beginless range') + else + # error from #max + -> { raise e }.should raise_error(ArgumentError, 'comparison of NilClass with MockObject failed') + end + } + end + end + + it 'should return begining of range if beginning and end are equal without iterating the range' do + @x.should_not_receive(:succ) + + (@x..@x).minmax.should == [@x, @x] + end + + it 'should return nil pair if beginning is greater than end without iterating the range' do + @y.should_not_receive(:succ) + + (@y..@x).minmax.should == [nil, nil] + end + + ruby_version_is ''...'2.7' do + it 'should return the minimum and maximum values for a non-numeric range by iterating the range' do + @x.should_receive(:succ).once.and_return(@y) + + (@x..@y).minmax.should == [@x, @y] + end + end + + ruby_version_is '2.7' do + it 'should return the minimum and maximum values for a non-numeric range without iterating the range' do + @x.should_not_receive(:succ) + + (@x..@y).minmax.should == [@x, @y] + end + end + + it 'should return the minimum and maximum values for a numeric range' do + (1..3).minmax.should == [1, 3] + end + + ruby_version_is '2.7' do + it 'should return the minimum and maximum values for a numeric range without iterating the range' do + # We cannot set expectations on integers, + # so we "prevent" iteration by picking a value that would iterate until the spec times out. + range_end = Float::INFINITY + + (1..range_end).minmax.should == [1, range_end] + end + end + + it 'should return the minimum and maximum values according to the provided block by iterating the range' do + @x.should_receive(:succ).once.and_return(@y) + + (@x..@y).minmax { |x, y| - (x <=> y) }.should == [@y, @x] + end + end + + describe 'on an exclusive range' do + ruby_version_is '2.6'...'2.7' do + # Endless ranges introduced in 2.6 + it 'should try to iterate endlessly on an endless range' do + @x.should_receive(:succ).once.and_return(@y) + range = Range.new(@x, nil, true) + + -> { range.minmax }.should raise_error(NoMethodError, /^undefined method `succ' for/) + end + end + + ruby_version_is '2.7' do + it 'should raise RangeError on an endless range' do + @x.should_not_receive(:succ) + range = Range.new(@x, nil, true) + + -> { range.minmax }.should raise_error(RangeError, 'cannot get the maximum of endless range') + end + + it 'should raise RangeError on a beginless range' do + range = Range.new(nil, @x, true) + + -> { range.minmax }.should raise_error(RangeError, + /cannot get the maximum of beginless range with custom comparison method|cannot get the minimum of beginless range/) + end + end + + ruby_bug "#17014", "2.7.0"..."2.8" do + it 'should return nil pair if beginning and end are equal without iterating the range' do + @x.should_not_receive(:succ) + + (@x...@x).minmax.should == [nil, nil] + end + + it 'should return nil pair if beginning is greater than end without iterating the range' do + @y.should_not_receive(:succ) + + (@y...@x).minmax.should == [nil, nil] + end + + it 'should return the minimum and maximum values for a non-numeric range by iterating the range' do + @x.should_receive(:succ).once.and_return(@y) + + (@x...@y).minmax.should == [@x, @x] + end + end + + it 'should return the minimum and maximum values for a numeric range' do + (1...3).minmax.should == [1, 2] + end + + ruby_version_is '2.7' do + it 'should return the minimum and maximum values for a numeric range without iterating the range' do + # We cannot set expectations on integers, + # so we "prevent" iteration by picking a value that would iterate until the spec times out. + range_end = bignum_value + + (1...range_end).minmax.should == [1, range_end - 1] + end + + it 'raises TypeError if the end value is not an integer' do + range = (0...Float::INFINITY) + -> { range.minmax }.should raise_error(TypeError, 'cannot exclude non Integer end value') + end + end + + it 'should return the minimum and maximum values according to the provided block by iterating the range' do + @x.should_receive(:succ).once.and_return(@y) + + (@x...@y).minmax { |x, y| - (x <=> y) }.should == [@x, @x] + end + end +end diff --git a/spec/ruby/core/string/chomp_spec.rb b/spec/ruby/core/string/chomp_spec.rb index b3f64d7118..7a1118d534 100644 --- a/spec/ruby/core/string/chomp_spec.rb +++ b/spec/ruby/core/string/chomp_spec.rb @@ -55,6 +55,10 @@ describe "String#chomp" do $/ = "cdef" "abcdef".chomp.should == "ab" end + + it "removes one trailing newline for string with invalid encoding" do + "\xa0\xa1\n".chomp.should == "\xa0\xa1" + end end describe "when passed nil" do @@ -108,6 +112,10 @@ describe "String#chomp" do it "returns an empty String when self is empty" do "".chomp("").should == "" end + + it "removes one trailing newline for string with invalid encoding" do + "\xa0\xa1\n".chomp("").should == "\xa0\xa1" + end end describe "when passed '\\n'" do diff --git a/spec/ruby/core/thread/list_spec.rb b/spec/ruby/core/thread/list_spec.rb index a0bf831856..3c6f70c13e 100644 --- a/spec/ruby/core/thread/list_spec.rb +++ b/spec/ruby/core/thread/list_spec.rb @@ -43,11 +43,11 @@ describe "Thread.list" do end end - while spawner.alive? + begin Thread.list.each { |th| th.should be_kind_of(Thread) } - end + end while spawner.alive? threads = spawner.value threads.each(&:join) diff --git a/spec/ruby/fixtures/constants.rb b/spec/ruby/fixtures/constants.rb index e5b20596ef..37271ddcc8 100644 --- a/spec/ruby/fixtures/constants.rb +++ b/spec/ruby/fixtures/constants.rb @@ -1,7 +1,7 @@ # Contains all static code examples of all constants behavior in language and # library specs. The specs include language/constants_spec.rb and the specs -# for Module#const_defined?, Module#const_get, Module#const_set, -# Module#remove_const, Module#const_missing and Module#constants. +# for Module#const_defined?, Module#const_get, Module#const_set, Module#remove_const, +# Module#const_source_location, Module#const_missing and Module#constants. # # Rather than defining a class structure for each example, a canonical set of # classes is used along with numerous constants, in most cases, a unique @@ -28,14 +28,17 @@ # for completeness. No other constant of this name should be defined in the # specs. CS_CONST1 = :const1 # only defined here +CS_CONST1_LINE = __LINE__ - 1 module ConstantSpecs # Included at toplevel module ModuleA CS_CONST10 = :const10_1 + CS_CONST10_LINE = __LINE__ - 1 CS_CONST12 = :const12_2 CS_CONST13 = :const13 + CS_CONST13_LINE = __LINE__ - 1 CS_CONST21 = :const21_2 end @@ -44,12 +47,14 @@ module ConstantSpecs CS_CONST10 = :const10_9 CS_CONST11 = :const11_2 CS_CONST12 = :const12_1 + CS_CONST12_LINE = __LINE__ - 1 end # Included in ChildA module ModuleC CS_CONST10 = :const10_4 CS_CONST15 = :const15_1 + CS_CONST15_LINE = __LINE__ - 1 end # Included in ChildA metaclass @@ -75,7 +80,9 @@ module ConstantSpecs # are run. class ClassA + CS_CLASS_A_LINE = __LINE__ - 1 CS_CONST10 = :const10_10 + CS_CONST10_LINE = __LINE__ - 1 CS_CONST16 = :const16 CS_CONST17 = :const17_2 CS_CONST22 = :const22_1 @@ -97,10 +104,14 @@ module ConstantSpecs include ModuleB CS_CONST4 = :const4 + CS_CONST4_LINE = __LINE__ - 1 CS_CONST10 = :const10_5 + CS_CONST10_LINE = __LINE__ - 1 CS_CONST11 = :const11_1 + CS_CONST11_LINE = __LINE__ - 1 CS_CONST15 = :const15_2 CS_CONST20 = :const20_2 + CS_CONST20_LINE = __LINE__ - 1 CS_CONST21 = :const21_1 CS_CONST22 = :const22_2 @@ -118,6 +129,7 @@ module ConstantSpecs CS_CONST5 = :const5 CS_CONST10 = :const10_2 + CS_CONST10_LINE = __LINE__ - 1 CS_CONST23 = :const23 class ChildA < ParentA @@ -135,6 +147,7 @@ module ConstantSpecs CS_CONST6 = :const6 CS_CONST10 = :const10_3 + CS_CONST10_LINE = __LINE__ - 1 CS_CONST19 = :const19_2 def self.const10; CS_CONST10; end @@ -282,6 +295,7 @@ module ConstantSpecs end CS_PRIVATE = :cs_private + CS_PRIVATE_LINE = __LINE__ - 1 private_constant :CS_PRIVATE end diff --git a/spec/ruby/language/predefined_spec.rb b/spec/ruby/language/predefined_spec.rb index 764c96e838..65fcb1e2ac 100644 --- a/spec/ruby/language/predefined_spec.rb +++ b/spec/ruby/language/predefined_spec.rb @@ -1254,3 +1254,25 @@ describe "The predefined global constant" do end end end + +ruby_version_is "2.7" do + describe "$LOAD_PATH.resolve_feature_path" do + it "returns what will be loaded without actual loading, .rb file" do + extension, path = $LOAD_PATH.resolve_feature_path('set') + extension.should == :rb + path.should.end_with?('/set.rb') + end + + it "returns what will be loaded without actual loading, .so file" do + require 'rbconfig' + + extension, path = $LOAD_PATH.resolve_feature_path('etc') + extension.should == :so + path.should.end_with?("/etc.#{RbConfig::CONFIG['DLEXT']}") + end + + it "raises LoadError if feature cannot be found" do + -> { $LOAD_PATH.resolve_feature_path('noop') }.should raise_error(LoadError) + end + end +end diff --git a/spec/ruby/library/socket/socket/listen_spec.rb b/spec/ruby/library/socket/socket/listen_spec.rb index 6598e254d7..4d2aedab19 100644 --- a/spec/ruby/library/socket/socket/listen_spec.rb +++ b/spec/ruby/library/socket/socket/listen_spec.rb @@ -34,16 +34,10 @@ describe 'Socket#listen' do @server.close end - platform_is_not :android do - it 'raises Errno::EOPNOTSUPP' do - -> { @server.listen(1) }.should raise_error(Errno::EOPNOTSUPP) - end - end - - platform_is :android do - it 'raises Errno::EOPNOTSUPP or Errno::EACCES' do - -> { @server.listen(1) }.should raise_error(-> exc { Errno::EACCES === exc || Errno::EOPNOTSUPP === exc }) - end + it 'raises Errno::EOPNOTSUPP or Errno::EACCES' do + -> { @server.listen(1) }.should raise_error { |e| + [Errno::EOPNOTSUPP, Errno::EACCES].should.include?(e.class) + } end end diff --git a/spec/ruby/optional/capi/kernel_spec.rb b/spec/ruby/optional/capi/kernel_spec.rb index 44cf311895..7539e0c01c 100644 --- a/spec/ruby/optional/capi/kernel_spec.rb +++ b/spec/ruby/optional/capi/kernel_spec.rb @@ -396,12 +396,20 @@ describe "C-API Kernel function" do proc = -> x { x } arg_error_proc = -> *_ { raise ArgumentError, '' } run_error_proc = -> *_ { raise RuntimeError, '' } - type_error_proc = -> *_ { raise TypeError, '' } + type_error_proc = -> *_ { raise Exception, 'custom error' } @s.rb_rescue2(arg_error_proc, :no_exc, proc, :exc, ArgumentError, RuntimeError).should == :exc @s.rb_rescue2(run_error_proc, :no_exc, proc, :exc, ArgumentError, RuntimeError).should == :exc -> { @s.rb_rescue2(type_error_proc, :no_exc, proc, :exc, ArgumentError, RuntimeError) - }.should raise_error(TypeError) + }.should raise_error(Exception, 'custom error') + end + + ruby_bug "#17305", ""..."2.7" do + it "raises TypeError if one of the passed exceptions is not a Module" do + -> { + @s.rb_rescue2(-> *_ { raise RuntimeError, "foo" }, :no_exc, -> x { x }, :exc, Object.new, 42) + }.should raise_error(TypeError, /class or module required/) + end end end diff --git a/spec/ruby/optional/capi/module_spec.rb b/spec/ruby/optional/capi/module_spec.rb index f5ce7ed8de..fde86d2223 100644 --- a/spec/ruby/optional/capi/module_spec.rb +++ b/spec/ruby/optional/capi/module_spec.rb @@ -135,7 +135,7 @@ describe "CApiModule" do end it "returns a constant defined at toplevel" do - @m.rb_const_get(CApiModuleSpecs::A, :Fixnum).should == Fixnum + @m.rb_const_get(CApiModuleSpecs::A, :Integer).should == Integer end it "returns a constant defined in a superclass" do @@ -176,8 +176,8 @@ describe "CApiModule" do end it "calls #const_missing if the constant is not defined in the class or ancestors" do - CApiModuleSpecs::M.should_receive(:const_missing).with(:Fixnum) - @m.rb_const_get_from(CApiModuleSpecs::M, :Fixnum) + CApiModuleSpecs::M.should_receive(:const_missing).with(:Integer) + @m.rb_const_get_from(CApiModuleSpecs::M, :Integer) end it "resolves autoload constants" do diff --git a/spec/ruby/optional/capi/struct_spec.rb b/spec/ruby/optional/capi/struct_spec.rb index c071203673..0e9e366908 100644 --- a/spec/ruby/optional/capi/struct_spec.rb +++ b/spec/ruby/optional/capi/struct_spec.rb @@ -62,7 +62,7 @@ describe "C-API Struct function" do end describe "C-API Struct function" do - before :each do + before :all do @s = CApiStructSpecs.new @struct = @s.rb_struct_define_under(CApiStructSpecs, "CAPIStructUnder", "a", "b", "c") end diff --git a/spec/ruby/shared/enumerator/enum_for.rb b/spec/ruby/shared/enumerator/enum_for.rb index 9030ffbd7d..a67a76c461 100644 --- a/spec/ruby/shared/enumerator/enum_for.rb +++ b/spec/ruby/shared/enumerator/enum_for.rb @@ -12,6 +12,13 @@ describe :enum_for, shared: true do enum.map { |v| v }.should == [1,2].each { |v| v } end + it "sets regexp matches in the caller" do + "wawa".send(@method, :scan, /./).map {|o| $& }.should == ["w", "a", "w", "a"] + a = [] + "wawa".send(@method, :scan, /./).each {|o| a << $& } + a.should == ["w", "a", "w", "a"] + end + it "exposes multi-arg yields as an array" do o = Object.new def o.each diff --git a/spec/ruby/shared/kernel/raise.rb b/spec/ruby/shared/kernel/raise.rb index 7d9954e29a..f00a6ef294 100644 --- a/spec/ruby/shared/kernel/raise.rb +++ b/spec/ruby/shared/kernel/raise.rb @@ -50,20 +50,43 @@ describe :kernel_raise, shared: true do end it "re-raises a previously rescued exception without overwriting the backtrace" do - begin - initial_raise_line = __LINE__; @object.raise 'raised' - rescue => raised - begin - raise_again_line = __LINE__; @object.raise raised - rescue => raised_again - # This spec is written using #backtrace and matching the line number - # from the string, as backtrace_locations is a more advanced - # method that is not always supported by implementations. + # This spec is written using #backtrace and matching the line number + # from the string, as backtrace_locations is a more advanced + # method that is not always supported by implementations. + # + initial_raise_line = nil + raise_again_line = nil + raised_again = nil - raised_again.backtrace.first.should include("#{__FILE__}:#{initial_raise_line}:") - raised_again.backtrace.first.should_not include("#{__FILE__}:#{raise_again_line}:") + if defined?(FiberSpecs::NewFiberToRaise) and @object == FiberSpecs::NewFiberToRaise + fiber = Fiber.new do + begin + initial_raise_line = __LINE__; Fiber.yield + rescue => raised + begin + raise_again_line = __LINE__; Fiber.yield raised + rescue => raised_again + raised_again + end + end + end + fiber.resume + raised = fiber.raise 'raised' + raised_again = fiber.raise raised + else + begin + initial_raise_line = __LINE__; @object.raise 'raised' + rescue => raised + begin + raise_again_line = __LINE__; @object.raise raised + rescue => raised_again + raised_again + end end end + + raised_again.backtrace.first.should include("#{__FILE__}:#{initial_raise_line}:") + raised_again.backtrace.first.should_not include("#{__FILE__}:#{raise_again_line}:") end it "allows Exception, message, and backtrace parameters" do