diff options
Diffstat (limited to 'spec')
38 files changed, 733 insertions, 44 deletions
diff --git a/spec/ruby/.mspec.constants b/spec/ruby/.mspec.constants index d7e09ca2d9..3e3bdbef8a 100644 --- a/spec/ruby/.mspec.constants +++ b/spec/ruby/.mspec.constants @@ -152,6 +152,7 @@ RegexpSpecsSubclassTwo Reline RescueInClassExample Resolv +Ripper SHA1Constants SHA256Constants SHA384Constants diff --git a/spec/ruby/.rubocop.yml b/spec/ruby/.rubocop.yml index 5370d996b8..5db9256572 100644 --- a/spec/ruby/.rubocop.yml +++ b/spec/ruby/.rubocop.yml @@ -92,6 +92,7 @@ Lint/UnreachableCode: Exclude: - 'core/enumerator/lazy/fixtures/classes.rb' - 'core/kernel/catch_spec.rb' + - 'core/kernel/raise_spec.rb' - 'core/kernel/throw_spec.rb' - 'language/break_spec.rb' - 'language/fixtures/break.rb' diff --git a/spec/ruby/.rubocop_todo.yml b/spec/ruby/.rubocop_todo.yml index 8d9cd82f0b..7a5f9f74c5 100644 --- a/spec/ruby/.rubocop_todo.yml +++ b/spec/ruby/.rubocop_todo.yml @@ -59,6 +59,7 @@ Lint/InheritException: - 'core/enumerator/lazy/fixtures/classes.rb' - 'core/exception/fixtures/common.rb' - 'core/module/fixtures/autoload_ex1.rb' + - 'shared/kernel/raise.rb' # Offense count: 72 # Cop supports --auto-correct. @@ -115,6 +116,7 @@ Lint/RescueException: - 'core/exception/cause_spec.rb' - 'core/exception/no_method_error_spec.rb' - 'core/kernel/fixtures/autoload_frozen.rb' + - 'core/kernel/raise_spec.rb' - 'core/module/autoload_spec.rb' - 'core/mutex/sleep_spec.rb' - 'core/thread/abort_on_exception_spec.rb' diff --git a/spec/ruby/CONTRIBUTING.md b/spec/ruby/CONTRIBUTING.md index 8d18f1102d..9a2a341ae7 100644 --- a/spec/ruby/CONTRIBUTING.md +++ b/spec/ruby/CONTRIBUTING.md @@ -136,11 +136,11 @@ Here is a list of the most commonly-used guards: #### Version guards ```ruby -ruby_version_is ""..."2.6 do +ruby_version_is ""..."2.6" do # Specs for RUBY_VERSION < 2.6 end -ruby_version_is "2.6 do +ruby_version_is "2.6" do # Specs for RUBY_VERSION >= 2.6 end ``` diff --git a/spec/ruby/command_line/dash_r_spec.rb b/spec/ruby/command_line/dash_r_spec.rb index b29895bd26..46c000b9e7 100644 --- a/spec/ruby/command_line/dash_r_spec.rb +++ b/spec/ruby/command_line/dash_r_spec.rb @@ -7,7 +7,22 @@ describe "The -r command line option" do end it "requires the specified file" do - result = ruby_exe(@script, options: "-r #{@test_file}") - result.should include(@test_file + ".rb") + out = ruby_exe(@script, options: "-r #{@test_file}") + out.should include("REQUIRED") + out.should include(@test_file + ".rb") + end + + it "requires the file before parsing the main script" do + out = ruby_exe(fixture(__FILE__, "bad_syntax.rb"), options: "-r #{@test_file}", args: "2>&1") + $?.should_not.success? + out.should include("REQUIRED") + out.should include("syntax error") + end + + it "does not require the file if the main script file does not exist" do + out = `#{ruby_exe.to_a.join(' ')} -r #{@test_file} #{fixture(__FILE__, "does_not_exist.rb")} 2>&1` + $?.should_not.success? + out.should_not.include?("REQUIRED") + out.should.include?("No such file or directory") end end diff --git a/spec/ruby/command_line/fixtures/test_file.rb b/spec/ruby/command_line/fixtures/test_file.rb index 961e3c0b0c..30a832299e 100644 --- a/spec/ruby/command_line/fixtures/test_file.rb +++ b/spec/ruby/command_line/fixtures/test_file.rb @@ -1 +1 @@ -"test file" +puts "REQUIRED" diff --git a/spec/ruby/core/array/fill_spec.rb b/spec/ruby/core/array/fill_spec.rb index 96c8862208..6745bc8d09 100644 --- a/spec/ruby/core/array/fill_spec.rb +++ b/spec/ruby/core/array/fill_spec.rb @@ -207,8 +207,9 @@ describe "Array#fill with (filler, index, length)" do not_supported_on :opal do it "raises an ArgumentError or RangeError for too-large sizes" do + error_types = [RangeError, ArgumentError] arr = [1, 2, 3] - -> { arr.fill(10, 1, fixnum_max) }.should raise_error(ArgumentError) + -> { arr.fill(10, 1, fixnum_max) }.should raise_error { |err| error_types.should include(err.class) } -> { arr.fill(10, 1, bignum_value) }.should raise_error(RangeError) end end diff --git a/spec/ruby/core/exception/backtrace_spec.rb b/spec/ruby/core/exception/backtrace_spec.rb index 2d6825180a..3f74c4cefe 100644 --- a/spec/ruby/core/exception/backtrace_spec.rb +++ b/spec/ruby/core/exception/backtrace_spec.rb @@ -43,7 +43,7 @@ describe "Exception#backtrace" do # This regexp is deliberately imprecise to account for the need to abstract out # the paths of the included mspec files and the desire to avoid specifying in any # detail what the in `...' portion looks like. - line.should =~ /^[^ ]+\:\d+(:in `[^`]+')?$/ + line.should =~ /^.+:\d+:in `[^`]+'$/ end end diff --git a/spec/ruby/core/kernel/__dir___spec.rb b/spec/ruby/core/kernel/__dir___spec.rb index 64b439161f..0686b31e97 100644 --- a/spec/ruby/core/kernel/__dir___spec.rb +++ b/spec/ruby/core/kernel/__dir___spec.rb @@ -5,6 +5,13 @@ describe "Kernel#__dir__" do __dir__.should == File.realpath(File.dirname(__FILE__)) end + it "returns the expanded path of the directory when used in the main script" do + fixtures_dir = File.dirname(fixture(__FILE__, '__dir__.rb')) + Dir.chdir(fixtures_dir) do + ruby_exe("__dir__.rb").should == "__dir__.rb\n#{fixtures_dir}\n" + end + end + context "when used in eval with a given filename" do it "returns File.dirname(filename)" do eval("__dir__", nil, "foo.rb").should == "." diff --git a/spec/ruby/core/kernel/at_exit_spec.rb b/spec/ruby/core/kernel/at_exit_spec.rb index 21149f965b..7bdb5391fe 100644 --- a/spec/ruby/core/kernel/at_exit_spec.rb +++ b/spec/ruby/core/kernel/at_exit_spec.rb @@ -23,7 +23,7 @@ describe "Kernel.at_exit" do it "gives access to the last raised exception" do code = <<-EOC at_exit do - puts "The exception matches: \#{$! == $exception}" + puts "The exception matches: \#{$! == $exception} (message=\#{$!.message})" end begin @@ -33,10 +33,35 @@ describe "Kernel.at_exit" do end EOC - result = ruby_exe(code, args: "2>&1", escape: true) - result.should =~ /The exception matches: true/ + result = ruby_exe(code, args: "2>&1") + result.lines.should.include?("The exception matches: true (message=foo)\n") end + it "both exceptions in at_exit and in the main script are printed" do + result = ruby_exe('at_exit { raise "at_exit_error" }; raise "main_script_error"', args: "2>&1") + result.should.include?('at_exit_error (RuntimeError)') + result.should.include?('main_script_error (RuntimeError)') + end + + it "decides the exit status if both at_exit and the main script raise SystemExit" do + ruby_exe('at_exit { exit 43 }; exit 42', args: "2>&1") + $?.exitstatus.should == 43 + end + + it "runs all at_exit even if some raise exceptions" do + code = 'at_exit { STDERR.puts "last" }; at_exit { exit 43 }; at_exit { STDERR.puts "first" }; exit 42' + result = ruby_exe(code, args: "2>&1") + result.should == "first\nlast\n" + $?.exitstatus.should == 43 + end + + it "runs at_exit handlers even if the main script fails to parse" do + script = fixture(__FILE__, "at_exit.rb") + result = ruby_exe('{', options: "-r#{script}", args: "2>&1") + $?.should_not.success? + result.should.include?("at_exit ran\n") + result.should.include?("syntax error") + end end describe "Kernel#at_exit" do diff --git a/spec/ruby/core/kernel/fixtures/__dir__.rb b/spec/ruby/core/kernel/fixtures/__dir__.rb new file mode 100644 index 0000000000..bf9a15e3c8 --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/__dir__.rb @@ -0,0 +1,2 @@ +puts __FILE__ +puts __dir__ diff --git a/spec/ruby/core/kernel/fixtures/at_exit.rb b/spec/ruby/core/kernel/fixtures/at_exit.rb new file mode 100644 index 0000000000..9c11a7ad6c --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/at_exit.rb @@ -0,0 +1,3 @@ +at_exit do + STDERR.puts "at_exit ran" +end diff --git a/spec/ruby/core/kernel/fixtures/warn_require.rb b/spec/ruby/core/kernel/fixtures/warn_require.rb new file mode 100644 index 0000000000..c4b0733233 --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/warn_require.rb @@ -0,0 +1 @@ +warn 'warn-require-warning', uplevel: 1 diff --git a/spec/ruby/core/kernel/fixtures/warn_require_caller.rb b/spec/ruby/core/kernel/fixtures/warn_require_caller.rb new file mode 100644 index 0000000000..35a0f969f9 --- /dev/null +++ b/spec/ruby/core/kernel/fixtures/warn_require_caller.rb @@ -0,0 +1,2 @@ +# Use a different line than just 1 +require "#{__dir__}/warn_require" diff --git a/spec/ruby/core/kernel/raise_spec.rb b/spec/ruby/core/kernel/raise_spec.rb index bf26560246..591daa03cf 100644 --- a/spec/ruby/core/kernel/raise_spec.rb +++ b/spec/ruby/core/kernel/raise_spec.rb @@ -6,6 +6,27 @@ describe "Kernel#raise" do it "is a private method" do Kernel.should have_private_instance_method(:raise) end + + it "re-raises the previously rescued exception if no exception is specified" do + ScratchPad.record nil + + -> do + begin + raise Exception, "outer" + ScratchPad.record :no_abort + rescue Exception + begin + raise StandardError, "inner" + rescue StandardError + end + + raise + ScratchPad.record :no_reraise + end + end.should raise_error(Exception, "outer") + + ScratchPad.recorded.should be_nil + end end describe "Kernel#raise" do diff --git a/spec/ruby/core/kernel/warn_spec.rb b/spec/ruby/core/kernel/warn_spec.rb index 84cfa78672..79e78dd4a3 100644 --- a/spec/ruby/core/kernel/warn_spec.rb +++ b/spec/ruby/core/kernel/warn_spec.rb @@ -101,6 +101,19 @@ describe "Kernel#warn" do -> { w.f4("foo", 3) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.f3_call_lineno}: warning: foo|) end + # Test both explicitly without and with RubyGems as RubyGems overrides Kernel#warn + it "shows the caller of #require and not #require itself without RubyGems" do + file = fixture(__FILE__ , "warn_require_caller.rb") + ruby_exe(file, options: "--disable-gems", args: "2>&1").should == "#{file}:2: warning: warn-require-warning\n" + end + + ruby_version_is "2.6" do + it "shows the caller of #require and not #require itself with RubyGems loaded" do + file = fixture(__FILE__ , "warn_require_caller.rb") + ruby_exe(file, options: "-rrubygems", args: "2>&1").should == "#{file}:2: warning: warn-require-warning\n" + end + end + it "converts first arg using to_s" do w = KernelSpecs::WarnInNestedCall.new diff --git a/spec/ruby/core/module/fixtures/refine.rb b/spec/ruby/core/module/fixtures/refine.rb index 79e2e80197..e8215aa640 100644 --- a/spec/ruby/core/module/fixtures/refine.rb +++ b/spec/ruby/core/module/fixtures/refine.rb @@ -3,6 +3,10 @@ module ModuleSpecs def foo; "foo" end end + class ClassWithSuperFoo + def foo; [:C] end + end + module PrependedModule def foo; "foo from prepended module"; end end @@ -11,7 +15,11 @@ module ModuleSpecs def foo; "foo from included module"; end end - def self.build_refined_class - Class.new(ClassWithFoo) + def self.build_refined_class(for_super: false) + if for_super + Class.new(ClassWithSuperFoo) + else + Class.new(ClassWithFoo) + end end end diff --git a/spec/ruby/core/module/refine_spec.rb b/spec/ruby/core/module/refine_spec.rb index ca12d5d13b..ebb7111d82 100644 --- a/spec/ruby/core/module/refine_spec.rb +++ b/spec/ruby/core/module/refine_spec.rb @@ -731,16 +731,67 @@ describe "Module#refine" do result.should == "foo" end + it "looks in the refined class from included module" do + refined_class = ModuleSpecs.build_refined_class(for_super: true) + + a = Module.new do + def foo + [:A] + super + end + end + + refinement = Module.new do + refine refined_class do + include a + end + end + + result = nil + Module.new do + using refinement + + result = refined_class.new.foo + end + + result.should == [:A, :C] + end + + it "looks in the refined ancestors from included module" do + refined_class = ModuleSpecs.build_refined_class(for_super: true) + subclass = Class.new(refined_class) + + a = Module.new do + def foo + [:A] + super + end + end + + refinement = Module.new do + refine refined_class do + include a + end + end + + result = nil + Module.new do + using refinement + + result = subclass.new.foo + end + + result.should == [:A, :C] + end + # super in a method of a refinement invokes the method in the refined # class even if there is another refinement which has been activated # in the same context. - it "looks in the refined class even if there is another active refinement" do - refined_class = ModuleSpecs.build_refined_class + it "looks in the refined class first if called from refined method" do + refined_class = ModuleSpecs.build_refined_class(for_super: true) refinement = Module.new do refine refined_class do def foo - "foo from refinement" + [:R1] end end end @@ -748,7 +799,7 @@ describe "Module#refine" do refinement_with_super = Module.new do refine refined_class do def foo - super + [:R2] + super end end end @@ -760,7 +811,246 @@ describe "Module#refine" do result = refined_class.new.foo end - result.should == "foo" + result.should == [:R2, :C] + end + + it "looks only in the refined class even if there is another active refinement" do + refined_class = ModuleSpecs.build_refined_class(for_super: true) + + refinement = Module.new do + refine refined_class do + def bar + "you cannot see me from super because I belong to another active R" + end + end + end + + refinement_with_super = Module.new do + refine refined_class do + def bar + super + end + end + end + + + Module.new do + using refinement + using refinement_with_super + -> { + refined_class.new.bar + }.should raise_error(NoMethodError) + end + end + + it "does't have access to active refinements for C from included module" do + refined_class = ModuleSpecs.build_refined_class + + a = Module.new do + def foo + super + bar + end + end + + refinement = Module.new do + refine refined_class do + include a + + def bar + "bar is not seen from A methods" + end + end + end + + Module.new do + using refinement + -> { + refined_class.new.foo + }.should raise_error(NameError) { |e| e.name.should == :bar } + end + end + + it "does't have access to other active refinements from included module" do + refined_class = ModuleSpecs.build_refined_class + + refinement_integer = Module.new do + refine Integer do + def bar + "bar is not seen from A methods" + end + end + end + + a = Module.new do + def foo + super + 1.bar + end + end + + refinement = Module.new do + refine refined_class do + include a + end + end + + Module.new do + using refinement + using refinement_integer + -> { + refined_class.new.foo + }.should raise_error(NameError) { |e| e.name.should == :bar } + end + end + + # https://bugs.ruby-lang.org/issues/16977 + it "looks in the another active refinement if super called from included modules" do + refined_class = ModuleSpecs.build_refined_class(for_super: true) + + a = Module.new do + def foo + [:A] + super + end + end + + b = Module.new do + def foo + [:B] + super + end + end + + refinement_a = Module.new do + refine refined_class do + include a + end + end + + refinement_b = Module.new do + refine refined_class do + include b + end + end + + result = nil + Module.new do + using refinement_a + using refinement_b + result = refined_class.new.foo + end + + result.should == [:B, :A, :C] + end + + it "looks in the current active refinement from included modules" do + refined_class = ModuleSpecs.build_refined_class(for_super: true) + + a = Module.new do + def foo + [:A] + super + end + end + + b = Module.new do + def foo + [:B] + super + end + end + + refinement = Module.new do + refine refined_class do + def foo + [:LAST] + super + end + end + end + + refinement_a_b = Module.new do + refine refined_class do + include a + include b + end + end + + result = nil + Module.new do + using refinement + using refinement_a_b + result = refined_class.new.foo + end + + result.should == [:B, :A, :LAST, :C] + end + + ruby_version_is ""..."2.8" do + it "looks in the lexical scope refinements before other active refinements" do + refined_class = ModuleSpecs.build_refined_class(for_super: true) + + refinement_local = Module.new do + refine refined_class do + def foo + [:LOCAL] + super + end + end + end + + a = Module.new do + using refinement_local + + def foo + [:A] + super + end + end + + refinement = Module.new do + refine refined_class do + include a + end + end + + result = nil + Module.new do + using refinement + result = refined_class.new.foo + end + + result.should == [:A, :LOCAL, :C] + end + end + + ruby_version_is "2.8" do + # https://bugs.ruby-lang.org/issues/17007 + it "does not look in the lexical scope refinements before other active refinements" do + refined_class = ModuleSpecs.build_refined_class(for_super: true) + + refinement_local = Module.new do + refine refined_class do + def foo + [:LOCAL] + super + end + end + end + + a = Module.new do + using refinement_local + + def foo + [:A] + super + end + end + + refinement = Module.new do + refine refined_class do + include a + end + end + + result = nil + Module.new do + using refinement + result = refined_class.new.foo + end + + result.should == [:A, :C] + end end end diff --git a/spec/ruby/core/string/split_spec.rb b/spec/ruby/core/string/split_spec.rb index 34a50a5f13..7ac8d7815c 100644 --- a/spec/ruby/core/string/split_spec.rb +++ b/spec/ruby/core/string/split_spec.rb @@ -426,12 +426,70 @@ describe "String#split with Regexp" do end ruby_version_is "2.6" do - it "yields each split substrings if a block is given" do - a = [] - returned_object = "chunky bacon".split(" ") { |str| a << str.capitalize } + context "when a block is given" do + it "yields each split substring with default pattern" do + a = [] + returned_object = "chunky bacon".split { |str| a << str.capitalize } + + returned_object.should == "chunky bacon" + a.should == ["Chunky", "Bacon"] + end + + it "yields the string when limit is 1" do + a = [] + returned_object = "chunky bacon".split("", 1) { |str| a << str.capitalize } + + returned_object.should == "chunky bacon" + a.should == ["Chunky bacon"] + end + + it "yields each split letter" do + a = [] + returned_object = "chunky".split("", 0) { |str| a << str.capitalize } + + returned_object.should == "chunky" + a.should == %w(C H U N K Y) + end + + it "yields each split substring with a pattern" do + a = [] + returned_object = "chunky-bacon".split("-", 0) { |str| a << str.capitalize } + + returned_object.should == "chunky-bacon" + a.should == ["Chunky", "Bacon"] + end - returned_object.should == "chunky bacon" - a.should == ["Chunky", "Bacon"] + it "yields each split substring with empty regexp pattern" do + a = [] + returned_object = "chunky".split(//) { |str| a << str.capitalize } + + returned_object.should == "chunky" + a.should == %w(C H U N K Y) + end + + it "yields each split substring with empty regexp pattern and limit" do + a = [] + returned_object = "chunky".split(//, 3) { |str| a << str.capitalize } + + returned_object.should == "chunky" + a.should == %w(C H Unky) + end + + it "yields each split substring with a regexp pattern" do + a = [] + returned_object = "chunky:bacon".split(/:/) { |str| a << str.capitalize } + + returned_object.should == "chunky:bacon" + a.should == ["Chunky", "Bacon"] + end + + it "returns a string as is (and doesn't call block) if it is empty" do + a = [] + returned_object = "".split { |str| a << str.capitalize } + + returned_object.should == "" + a.should == [] + end end describe "for a String subclass" do diff --git a/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb b/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb index 67f9274f32..90839baa0f 100644 --- a/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb +++ b/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb @@ -10,6 +10,13 @@ describe 'Thread::Backtrace::Location#absolute_path' do @frame.absolute_path.should == File.realpath(__FILE__) end + it 'returns an absolute path when using a relative main script path' do + script = fixture(__FILE__, 'absolute_path_main.rb') + Dir.chdir(File.dirname(script)) do + ruby_exe('absolute_path_main.rb').should == "absolute_path_main.rb\n#{script}\n" + end + end + context "when used in eval with a given filename" do it "returns filename" do code = "caller_locations(0)[0].absolute_path" diff --git a/spec/ruby/core/thread/backtrace/location/fixtures/absolute_path_main.rb b/spec/ruby/core/thread/backtrace/location/fixtures/absolute_path_main.rb new file mode 100644 index 0000000000..d2b23393d4 --- /dev/null +++ b/spec/ruby/core/thread/backtrace/location/fixtures/absolute_path_main.rb @@ -0,0 +1,2 @@ +puts __FILE__ +puts caller_locations(0)[0].absolute_path diff --git a/spec/ruby/core/thread/backtrace/location/lineno_spec.rb b/spec/ruby/core/thread/backtrace/location/lineno_spec.rb index dc93d32d75..d14cf17514 100644 --- a/spec/ruby/core/thread/backtrace/location/lineno_spec.rb +++ b/spec/ruby/core/thread/backtrace/location/lineno_spec.rb @@ -10,4 +10,14 @@ describe 'Thread::Backtrace::Location#lineno' do it 'returns the absolute path of the call frame' do @frame.lineno.should == @line end + + it 'should be the same line number as in #to_s, including for core methods' do + # Get the caller_locations from a call made into a core library method + locations = [:non_empty].map { caller_locations }[0] + + locations.each do |location| + line_number = location.to_s[/:(\d+):/, 1] + location.lineno.should == Integer(line_number) + end + end end diff --git a/spec/ruby/core/thread/backtrace/location/path_spec.rb b/spec/ruby/core/thread/backtrace/location/path_spec.rb index 93eb4982d8..7863c055d3 100644 --- a/spec/ruby/core/thread/backtrace/location/path_spec.rb +++ b/spec/ruby/core/thread/backtrace/location/path_spec.rb @@ -87,6 +87,18 @@ describe 'Thread::Backtrace::Location#path' do end end + it 'should be the same path as in #to_s, including for core methods' do + # Get the caller_locations from a call made into a core library method + locations = [:non_empty].map { caller_locations }[0] + + locations.each do |location| + filename = location.to_s[/^(.+):\d+:/, 1] + path = location.path + + path.should == filename + end + end + context "canonicalization" do platform_is_not :windows do before :each do diff --git a/spec/ruby/core/thread/raise_spec.rb b/spec/ruby/core/thread/raise_spec.rb index 88a96d5f4e..27de3cc627 100644 --- a/spec/ruby/core/thread/raise_spec.rb +++ b/spec/ruby/core/thread/raise_spec.rb @@ -203,6 +203,6 @@ describe "Thread#raise on same thread" do Thread.current.raise end end - -> { t.value }.should raise_error(RuntimeError) + -> { t.value }.should raise_error(RuntimeError, '') end end diff --git a/spec/ruby/language/break_spec.rb b/spec/ruby/language/break_spec.rb index 754d5d3c49..627cb4a071 100644 --- a/spec/ruby/language/break_spec.rb +++ b/spec/ruby/language/break_spec.rb @@ -362,4 +362,22 @@ describe "Executing break from within a block" do bt2.three ScratchPad.recorded.should == [:two_ensure, :three_post, :three_ensure] end + + it "works when passing through a super call" do + cls1 = Class.new { def foo; yield; end } + cls2 = Class.new(cls1) { def foo; super { break 1 }; end } + + -> do + cls2.new.foo.should == 1 + end.should_not raise_error + end + + it "raises LocalJumpError when converted into a proc during a a super call" do + cls1 = Class.new { def foo(&b); b; end } + cls2 = Class.new(cls1) { def foo; super { break 1 }.call; end } + + -> do + cls2.new.foo + end.should raise_error(LocalJumpError) + end end diff --git a/spec/ruby/language/numbered_parameters_spec.rb b/spec/ruby/language/numbered_parameters_spec.rb index 5b0f74b3ab..9dd79f44b8 100644 --- a/spec/ruby/language/numbered_parameters_spec.rb +++ b/spec/ruby/language/numbered_parameters_spec.rb @@ -42,7 +42,7 @@ ruby_version_is "2.7" do end end - it "warns when numbered parameter is overriten with local variable" do + it "warns when numbered parameter is overwritten with local variable" do -> { eval("_1 = 0") }.should complain(/warning: `_1' is reserved for numbered parameter; consider another name/) @@ -59,7 +59,7 @@ ruby_version_is "2.7" do }.should raise_error(SyntaxError, /_1 is reserved for numbered parameter/) end - it "errors when numbered parameter is overriten with local variable" do + it "errors when numbered parameter is overwritten with local variable" do -> { eval("_1 = 0") }.should raise_error(SyntaxError, /_1 is reserved for numbered parameter/) diff --git a/spec/ruby/library/bigdecimal/to_s_spec.rb b/spec/ruby/library/bigdecimal/to_s_spec.rb index 7f741ca8b6..f2851976e2 100644 --- a/spec/ruby/library/bigdecimal/to_s_spec.rb +++ b/spec/ruby/library/bigdecimal/to_s_spec.rb @@ -8,6 +8,11 @@ describe "BigDecimal#to_s" do @bigneg_str = "-3.1415926535897932384626433832795028841971693993" @bigdec = BigDecimal(@bigdec_str) @bigneg = BigDecimal(@bigneg_str) + @internal = Encoding.default_internal + end + + after :each do + Encoding.default_internal = @internal end it "return type is of class String" do @@ -78,4 +83,15 @@ describe "BigDecimal#to_s" do end end + ruby_version_is "2.8" do + it "returns a String in US-ASCII encoding when Encoding.default_internal is nil" do + Encoding.default_internal = nil + BigDecimal('1.23').to_s.encoding.should equal(Encoding::US_ASCII) + end + + it "returns a String in US-ASCII encoding when Encoding.default_internal is not nil" do + Encoding.default_internal = Encoding::IBM437 + BigDecimal('1.23').to_s.encoding.should equal(Encoding::US_ASCII) + end + end end diff --git a/spec/ruby/library/digest/instance/new_spec.rb b/spec/ruby/library/digest/instance/new_spec.rb new file mode 100644 index 0000000000..3f7939844b --- /dev/null +++ b/spec/ruby/library/digest/instance/new_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../../spec_helper' +require 'digest' +require_relative '../md5/shared/constants' + +describe "Digest::Instance#new" do + it "returns a copy of the digest instance" do + digest = Digest::MD5.new + copy = digest.new + copy.should_not.equal?(digest) + end + + it "calls reset" do + digest = Digest::MD5.new + digest << "test" + digest.hexdigest.should != MD5Constants::BlankHexdigest + copy = digest.new + copy.hexdigest.should == MD5Constants::BlankHexdigest + end +end diff --git a/spec/ruby/library/ripper/lex_spec.rb b/spec/ruby/library/ripper/lex_spec.rb new file mode 100644 index 0000000000..97cfb06904 --- /dev/null +++ b/spec/ruby/library/ripper/lex_spec.rb @@ -0,0 +1,23 @@ +require_relative '../../spec_helper' +require 'ripper' + +describe "Ripper.lex" do + it "lexes a simple method declaration" do + expected = [ + [[1, 0], :on_kw, "def", 'FNAME'], + [[1, 3], :on_sp, " ", 'FNAME'], + [[1, 4], :on_ident, "m", 'ENDFN'], + [[1, 5], :on_lparen, "(", 'BEG|LABEL'], + [[1, 6], :on_ident, "a", 'ARG'], + [[1, 7], :on_rparen, ")", 'ENDFN'], + [[1, 8], :on_sp, " ", 'BEG'], + [[1, 9], :on_kw, "nil", 'END'], + [[1, 12], :on_sp, " ", 'END'], + [[1, 13], :on_kw, "end", 'END'] + ] + lexed = Ripper.lex("def m(a) nil end") + lexed.map { |e| + e[0...-1] + [e[-1].to_s.split('|').map { |s| s.sub(/^EXPR_/, '') }.join('|')] + }.should == expected + end +end diff --git a/spec/ruby/library/ripper/sexp_spec.rb b/spec/ruby/library/ripper/sexp_spec.rb new file mode 100644 index 0000000000..6c69624c65 --- /dev/null +++ b/spec/ruby/library/ripper/sexp_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../spec_helper' +require 'ripper' + +describe "Ripper.sexp" do + it "returns an s-expression for a method declaration" do + expected = [:program, + [[:def, + [:@ident, "hello", [1, 4]], + [:params, nil, nil, nil, nil, nil, nil, nil], + [:bodystmt, [[:@int, "42", [1, 11]]], nil, nil, nil]]]] + Ripper.sexp("def hello; 42; end").should == expected + end +end diff --git a/spec/ruby/optional/capi/encoding_spec.rb b/spec/ruby/optional/capi/encoding_spec.rb index 8e59ee8b1b..93bde54069 100644 --- a/spec/ruby/optional/capi/encoding_spec.rb +++ b/spec/ruby/optional/capi/encoding_spec.rb @@ -58,6 +58,15 @@ describe "C-API Encoding function" do end end + describe "rb_enc_codelen" do + it "returns the correct length for the given codepoint" do + @s.rb_enc_codelen(0x24, Encoding::UTF_8).should == 1 + @s.rb_enc_codelen(0xA2, Encoding::UTF_8).should == 2 + @s.rb_enc_codelen(0x20AC, Encoding::UTF_8).should == 3 + @s.rb_enc_codelen(0x24B62, Encoding::UTF_8).should == 4 + end + end + describe "rb_enc_find" do it "returns the encoding of an Encoding" do @s.rb_enc_find("UTF-8").should == "UTF-8" @@ -130,6 +139,18 @@ describe "C-API Encoding function" do end end + describe "rb_enc_mbcput" do + it "writes the correct bytes to the buffer" do + @s.rb_enc_mbcput(0x24, Encoding::UTF_8).should == "$" + @s.rb_enc_mbcput(0xA2, Encoding::UTF_8).should == "¢" + @s.rb_enc_mbcput(0x20AC, Encoding::UTF_8).should == "€" + @s.rb_enc_mbcput(0x24B62, Encoding::UTF_8).should == "𤭢" + + @s.rb_enc_mbcput(0x24, Encoding::UTF_16BE).bytes.should == [0, 0x24] + @s.rb_enc_mbcput(0x24B62, Encoding::UTF_16LE).bytes.should == [82, 216, 98, 223] + end + end + describe "rb_usascii_encoding" do it "returns the encoding for Encoding::US_ASCII" do @s.rb_usascii_encoding.should == "US-ASCII" @@ -610,4 +631,21 @@ describe "C-API Encoding function" do end end end + + describe "ONIGENC_MBC_CASE_FOLD" do + it "returns the correct case fold for the given string" do + @s.ONIGENC_MBC_CASE_FOLD("lower").should == ["l", 1] + @s.ONIGENC_MBC_CASE_FOLD("Upper").should == ["u", 1] + end + + it "works with other encodings" do + @s.ONIGENC_MBC_CASE_FOLD("lower".force_encoding("binary")).should == ["l", 1] + @s.ONIGENC_MBC_CASE_FOLD("Upper".force_encoding("binary")).should == ["u", 1] + @s.ONIGENC_MBC_CASE_FOLD("É").should == ["é", 2] + + str, length = @s.ONIGENC_MBC_CASE_FOLD('$'.encode(Encoding::UTF_16BE)) + length.should == 2 + str.bytes.should == [0, 0x24] + end + end end diff --git a/spec/ruby/optional/capi/ext/encoding_spec.c b/spec/ruby/optional/capi/ext/encoding_spec.c index 8935db966e..cde4d0c351 100644 --- a/spec/ruby/optional/capi/ext/encoding_spec.c +++ b/spec/ruby/optional/capi/ext/encoding_spec.c @@ -127,6 +127,18 @@ static VALUE encoding_spec_rb_enc_mbc_to_codepoint(VALUE self, VALUE str, VALUE return INT2FIX(rb_enc_mbc_to_codepoint(p, e, rb_enc_get(str))); } +static VALUE encoding_spec_rb_enc_mbcput(VALUE self, VALUE code, VALUE encoding) { + unsigned int c = FIX2UINT(code); + rb_encoding *enc = rb_to_encoding(encoding); + char buf[ONIGENC_CODE_TO_MBC_MAXLEN]; + memset(buf, '\1', sizeof(buf)); + int len = rb_enc_mbcput(c, buf, enc); + if (buf[len] != '\1') { + rb_raise(rb_eRuntimeError, "should not change bytes after len"); + } + return rb_enc_str_new(buf, len, enc); +} + static VALUE encoding_spec_rb_enc_from_encoding(VALUE self, VALUE name) { return rb_enc_from_encoding(rb_enc_find(RSTRING_PTR(name))); } @@ -266,6 +278,28 @@ static VALUE encoding_spec_rb_uv_to_utf8(VALUE self, VALUE buf, VALUE num) { return INT2NUM(rb_uv_to_utf8(RSTRING_PTR(buf), NUM2INT(num))); } +static VALUE encoding_spec_ONIGENC_MBC_CASE_FOLD(VALUE self, VALUE str) { + char *beg = RSTRING_PTR(str); + char *beg_initial = beg; + char *end = beg + 2; + OnigUChar fold[ONIGENC_GET_CASE_FOLD_CODES_MAX_NUM]; + memset(fold, '\1', sizeof(fold)); + rb_encoding *enc = rb_enc_get(str); + int r = ONIGENC_MBC_CASE_FOLD(enc, ONIGENC_CASE_FOLD, &beg, (const OnigUChar *)end, fold); + if (r > 0 && fold[r] != '\1') { + rb_raise(rb_eRuntimeError, "should not change bytes after len"); + } + VALUE str_result = r <= 0 ? Qnil : rb_enc_str_new((char *)fold, r, enc); + long bytes_used = beg - beg_initial; + return rb_ary_new3(2, str_result, INT2FIX(bytes_used)); +} + +static VALUE encoding_spec_rb_enc_codelen(VALUE self, VALUE code, VALUE encoding) { + unsigned int c = FIX2UINT(code); + rb_encoding *enc = rb_to_encoding(encoding); + return INT2FIX(rb_enc_codelen(c, enc)); +} + void Init_encoding_spec(void) { VALUE cls; native_rb_encoding_pointer = (rb_encoding**) malloc(sizeof(rb_encoding*)); @@ -299,12 +333,14 @@ void Init_encoding_spec(void) { rb_define_method(cls, "rb_enc_associate_index", encoding_spec_rb_enc_associate_index, 2); rb_define_method(cls, "rb_enc_compatible", encoding_spec_rb_enc_compatible, 2); rb_define_method(cls, "rb_enc_copy", encoding_spec_rb_enc_copy, 2); + rb_define_method(cls, "rb_enc_codelen", encoding_spec_rb_enc_codelen, 2); rb_define_method(cls, "rb_enc_find", encoding_spec_rb_enc_find, 1); rb_define_method(cls, "rb_enc_find_index", encoding_spec_rb_enc_find_index, 1); rb_define_method(cls, "rb_enc_isalnum", encoding_spec_rb_enc_isalnum, 2); rb_define_method(cls, "rb_enc_isspace", encoding_spec_rb_enc_isspace, 2); rb_define_method(cls, "rb_enc_from_index", encoding_spec_rb_enc_from_index, 1); rb_define_method(cls, "rb_enc_mbc_to_codepoint", encoding_spec_rb_enc_mbc_to_codepoint, 2); + rb_define_method(cls, "rb_enc_mbcput", encoding_spec_rb_enc_mbcput, 2); rb_define_method(cls, "rb_enc_from_encoding", encoding_spec_rb_enc_from_encoding, 1); rb_define_method(cls, "rb_enc_get", encoding_spec_rb_enc_get, 1); rb_define_method(cls, "rb_enc_precise_mbclen", encoding_spec_rb_enc_precise_mbclen, 2); @@ -326,6 +362,7 @@ void Init_encoding_spec(void) { rb_define_method(cls, "rb_enc_codepoint_len", encoding_spec_rb_enc_codepoint_len, 1); rb_define_method(cls, "rb_enc_str_asciionly_p", encoding_spec_rb_enc_str_asciionly_p, 1); rb_define_method(cls, "rb_uv_to_utf8", encoding_spec_rb_uv_to_utf8, 2); + rb_define_method(cls, "ONIGENC_MBC_CASE_FOLD", encoding_spec_ONIGENC_MBC_CASE_FOLD, 1); } #ifdef __cplusplus diff --git a/spec/ruby/optional/capi/ext/typed_data_spec.c b/spec/ruby/optional/capi/ext/typed_data_spec.c index 6eb91b76bf..70f21ce36f 100644 --- a/spec/ruby/optional/capi/ext/typed_data_spec.c +++ b/spec/ruby/optional/capi/ext/typed_data_spec.c @@ -148,6 +148,18 @@ VALUE sws_typed_change_struct(VALUE self, VALUE obj, VALUE new_val) { return Qnil; } +VALUE sws_typed_rb_check_typeddata_same_type(VALUE self, VALUE obj) { + return rb_check_typeddata(obj, &sample_typed_wrapped_struct_data_type) == DATA_PTR(obj) ? Qtrue : Qfalse; +} + +VALUE sws_typed_rb_check_typeddata_same_type_parent(VALUE self, VALUE obj) { + return rb_check_typeddata(obj, &sample_typed_wrapped_struct_parent_data_type) == DATA_PTR(obj) ? Qtrue : Qfalse; +} + +VALUE sws_typed_rb_check_typeddata_different_type(VALUE self, VALUE obj) { + return rb_check_typeddata(obj, &sample_typed_wrapped_struct_other_data_type) == DATA_PTR(obj) ? Qtrue : Qfalse; +} + void Init_typed_data_spec(void) { VALUE cls = rb_define_class("CApiAllocTypedSpecs", rb_cObject); rb_define_alloc_func(cls, sdaf_alloc_typed_func); @@ -160,6 +172,9 @@ void Init_typed_data_spec(void) { rb_define_method(cls, "typed_get_struct_rdata", sws_typed_get_struct_rdata, 1); rb_define_method(cls, "typed_get_struct_data_ptr", sws_typed_get_struct_data_ptr, 1); rb_define_method(cls, "typed_change_struct", sws_typed_change_struct, 2); + rb_define_method(cls, "rb_check_typeddata_same_type", sws_typed_rb_check_typeddata_same_type, 1); + rb_define_method(cls, "rb_check_typeddata_same_type_parent", sws_typed_rb_check_typeddata_same_type_parent, 1); + rb_define_method(cls, "rb_check_typeddata_different_type", sws_typed_rb_check_typeddata_different_type, 1); } #ifdef __cplusplus diff --git a/spec/ruby/optional/capi/object_spec.rb b/spec/ruby/optional/capi/object_spec.rb index bbcaec2ba8..484dfb851c 100644 --- a/spec/ruby/optional/capi/object_spec.rb +++ b/spec/ruby/optional/capi/object_spec.rb @@ -831,6 +831,7 @@ describe "CApiObject" do it "returns nil if the instance variable has not been initialized and is not a valid Ruby name" do @o.rb_ivar_get(@test, :bar).should == nil + @o.rb_ivar_get(@test, :mesg).should == nil end it 'returns the instance variable when it is not a valid Ruby name' do @@ -866,6 +867,7 @@ describe "CApiObject" do it "does not throw an error if the instance variable is not a valid Ruby name" do @o.rb_ivar_defined(@test, :bar).should == false + @o.rb_ivar_defined(@test, :mesg).should == false end end diff --git a/spec/ruby/optional/capi/spec_helper.rb b/spec/ruby/optional/capi/spec_helper.rb index a7029a74ed..3b13e0854d 100644 --- a/spec/ruby/optional/capi/spec_helper.rb +++ b/spec/ruby/optional/capi/spec_helper.rb @@ -76,6 +76,7 @@ def compile_extension(name) $ruby = ENV.values_at('RUBY_EXE', 'RUBY_FLAGS').join(' ') # MRI magic to consider building non-bundled extensions $extout = nil + $warnflags << ' -Wno-declaration-after-statement' create_makefile(#{ext.inspect}) RUBY output = ruby_exe("extconf.rb") diff --git a/spec/ruby/optional/capi/typed_data_spec.rb b/spec/ruby/optional/capi/typed_data_spec.rb index da56a050fd..9ccfa562d1 100644 --- a/spec/ruby/optional/capi/typed_data_spec.rb +++ b/spec/ruby/optional/capi/typed_data_spec.rb @@ -57,4 +57,21 @@ describe "CApiWrappedTypedStruct" do @s.typed_get_struct_data_ptr(a).should == 1024 end end + + describe "rb_check_typeddata" do + it "returns data pointer when the struct has the given type" do + a = @s.typed_wrap_struct(1024) + @s.rb_check_typeddata_same_type(a).should == true + end + + it "returns data pointer when the parent struct has the given type" do + a = @s.typed_wrap_struct(1024) + @s.rb_check_typeddata_same_type_parent(a).should == true + end + + it "raises an error for different types" do + a = @s.typed_wrap_struct(1024) + -> { @s.rb_check_typeddata_different_type(a) }.should raise_error(TypeError) + end + end end diff --git a/spec/ruby/shared/kernel/raise.rb b/spec/ruby/shared/kernel/raise.rb index d4553775f4..7d9954e29a 100644 --- a/spec/ruby/shared/kernel/raise.rb +++ b/spec/ruby/shared/kernel/raise.rb @@ -25,6 +25,14 @@ describe :kernel_raise, shared: true do -> { @object.raise("a bad thing") }.should raise_error(RuntimeError) end + it "passes no arguments to the constructor when given only an exception class" do + klass = Class.new(Exception) do + def initialize + end + end + -> { @object.raise(klass) }.should raise_error(klass) { |e| e.message.should == klass.to_s } + end + it "raises a TypeError when passed a non-Exception object" do -> { @object.raise(Object.new) }.should raise_error(TypeError) end @@ -41,25 +49,6 @@ describe :kernel_raise, shared: true do -> { @object.raise(nil) }.should raise_error(TypeError) end - it "re-raises the previously rescued exception if no exception is specified" do - -> do - begin - @object.raise Exception, "outer" - ScratchPad.record :no_abort - rescue - begin - @object.raise StandardError, "inner" - rescue - end - - @object.raise - ScratchPad.record :no_reraise - end - end.should raise_error(Exception, "outer") - - ScratchPad.recorded.should be_nil - end - it "re-raises a previously rescued exception without overwriting the backtrace" do begin initial_raise_line = __LINE__; @object.raise 'raised' diff --git a/spec/ruby/shared/process/exit.rb b/spec/ruby/shared/process/exit.rb index 1820dd17fd..e633afc73a 100644 --- a/spec/ruby/shared/process/exit.rb +++ b/spec/ruby/shared/process/exit.rb @@ -91,4 +91,24 @@ describe :process_exit!, shared: true do out.should == "" $?.exitstatus.should == 21 end + + it "skips at_exit handlers" do + out = ruby_exe("at_exit { STDERR.puts 'at_exit' }; #{@object}.send(:exit!, 21)", args: '2>&1') + out.should == "" + $?.exitstatus.should == 21 + end + + it "overrides the original exception and exit status when called from #at_exit" do + code = <<-RUBY + at_exit do + STDERR.puts 'in at_exit' + STDERR.puts "$! is \#{$!.class}:\#{$!.message}" + #{@object}.send(:exit!, 21) + end + raise 'original error' + RUBY + out = ruby_exe(code, args: '2>&1') + out.should == "in at_exit\n$! is RuntimeError:original error\n" + $?.exitstatus.should == 21 + end end |