summaryrefslogtreecommitdiff
path: root/test/ruby/test_backtrace.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/ruby/test_backtrace.rb')
-rw-r--r--test/ruby/test_backtrace.rb469
1 files changed, 469 insertions, 0 deletions
diff --git a/test/ruby/test_backtrace.rb b/test/ruby/test_backtrace.rb
new file mode 100644
index 0000000000..dad7dfcb55
--- /dev/null
+++ b/test/ruby/test_backtrace.rb
@@ -0,0 +1,469 @@
+# frozen_string_literal: false
+require 'test/unit'
+require 'tempfile'
+
+class TestBacktrace < Test::Unit::TestCase
+ def test_exception
+ bt = Fiber.new{
+ begin
+ raise
+ rescue => e
+ e.backtrace
+ end
+ }.resume
+ assert_equal(1, bt.size)
+ assert_match(/.+:\d+:.+/, bt[0])
+ end
+
+ def helper_test_exception_backtrace_locations
+ raise
+ end
+
+ def test_exception_backtrace_locations
+ backtrace, backtrace_locations = Fiber.new{
+ begin
+ raise
+ rescue => e
+ [e.backtrace, e.backtrace_locations]
+ end
+ }.resume
+ assert_equal(backtrace, backtrace_locations.map{|e| e.to_s})
+
+ backtrace, backtrace_locations = Fiber.new{
+ begin
+ begin
+ helper_test_exception_backtrace_locations
+ rescue
+ raise
+ end
+ rescue => e
+ [e.backtrace, e.backtrace_locations]
+ end
+ }.resume
+ assert_equal(backtrace, backtrace_locations.map{|e| e.to_s})
+ end
+
+ def call_helper_test_exception_backtrace_locations
+ helper_test_exception_backtrace_locations(:bad_argument)
+ end
+
+ def test_argument_error_backtrace_locations
+ backtrace, backtrace_locations = Fiber.new{
+ begin
+ helper_test_exception_backtrace_locations(1)
+ rescue ArgumentError => e
+ [e.backtrace, e.backtrace_locations]
+ end
+ }.resume
+ assert_equal(backtrace, backtrace_locations.map{|e| e.to_s})
+
+ backtrace, backtrace_locations = Fiber.new{
+ begin
+ call_helper_test_exception_backtrace_locations
+ rescue ArgumentError => e
+ [e.backtrace, e.backtrace_locations]
+ end
+ }.resume
+ assert_equal(backtrace, backtrace_locations.map{|e| e.to_s})
+ end
+
+ def test_caller_lev
+ cs = []
+ Fiber.new{
+ Proc.new{
+ cs << caller(0)
+ cs << caller(1)
+ cs << caller(2)
+ cs << caller(3)
+ cs << caller(4)
+ cs << caller(5)
+ }.call
+ }.resume
+ assert_equal(2, cs[0].size)
+ assert_equal(1, cs[1].size)
+ assert_equal(0, cs[2].size)
+ assert_equal(nil, cs[3])
+ assert_equal(nil, cs[4])
+
+ #
+ max = 7
+ rec = lambda{|n|
+ if n > 0
+ 1.times{
+ rec[n-1]
+ }
+ else
+ (max*3).times{|i|
+ total_size = caller(0).size
+ c = caller(i)
+ if c
+ assert_equal(total_size - i, caller(i).size, "[ruby-dev:45673]")
+ end
+ }
+ end
+ }
+ Fiber.new{
+ rec[max]
+ }.resume
+ end
+
+ def test_caller_lev_and_n
+ m = 10
+ rec = lambda{|n|
+ if n < 0
+ (m*6).times{|lev|
+ (m*6).times{|i|
+ t = caller(0).size
+ r = caller(lev, i)
+ r = r.size if r.respond_to? :size
+
+ # STDERR.puts [t, lev, i, r].inspect
+ if i == 0
+ assert_equal(0, r, [t, lev, i, r].inspect)
+ elsif t < lev
+ assert_equal(nil, r, [t, lev, i, r].inspect)
+ else
+ if t - lev > i
+ assert_equal(i, r, [t, lev, i, r].inspect)
+ else
+ assert_equal(t - lev, r, [t, lev, i, r].inspect)
+ end
+ end
+ }
+ }
+ else
+ rec[n-1]
+ end
+ }
+ rec[m]
+ end
+
+ def test_caller_with_limit
+ x = nil
+ c = Class.new do
+ define_method(:bar) do
+ x = caller(1, 1)
+ end
+ end
+ [c.new].group_by(&:bar)
+ assert_equal 1, x.length
+ assert_equal caller(0), caller(0, nil)
+ end
+
+ def test_caller_with_nil_length
+ assert_equal caller(0), caller(0, nil)
+ end
+
+ def test_each_backtrace_location
+ assert_nil(Thread.each_caller_location {})
+
+ assert_raise(LocalJumpError) {Thread.each_caller_location}
+
+ i = 0
+ cl = caller_locations(1, 1)[0]; ecl = Thread.each_caller_location{|x| i+=1; break x if i == 1}
+ assert_equal(cl.to_s, ecl.to_s)
+ assert_kind_of(Thread::Backtrace::Location, ecl)
+
+ i = 0
+ ary = []
+ cllr = caller_locations(1, 2); last = Thread.each_caller_location{|x| ary << x; i+=1; break x if i == 2}
+ assert_equal(cllr.map(&:to_s), ary.map(&:to_s))
+ assert_kind_of(Thread::Backtrace::Location, last)
+
+ i = 0
+ ary = []
+ ->{->{
+ cllr = caller_locations(1, 2); last = Thread.each_caller_location{|x| ary << x; i+=1; break x if i == 2}
+ }.()}.()
+ assert_equal(cllr.map(&:to_s), ary.map(&:to_s))
+ assert_kind_of(Thread::Backtrace::Location, last)
+
+ cllr = caller_locations(1, 2); ary = Thread.to_enum(:each_caller_location).to_a[2..3]
+ assert_equal(cllr.map(&:to_s), ary.map(&:to_s))
+
+ ecl = Thread.to_enum(:each_caller_location)
+ assert_raise(StopIteration) {
+ ecl.next
+ }
+
+ ary = []
+ cl = caller_locations(1, 2); Thread.each_caller_location(1, 2) {|x| ary << x}
+ assert_equal(cl.map(&:to_s), ary.map(&:to_s))
+ end
+
+ def test_caller_locations_first_label
+ def self.label
+ caller_locations.first.label
+ end
+
+ def self.label_caller
+ label
+ end
+
+ assert_equal 'label_caller', label_caller
+
+ [1].group_by do
+ assert_equal 'label_caller', label_caller
+ end
+ end
+
+ def test_caller_limit_cfunc_iseq_no_pc
+ def self.a; [1].group_by { b } end
+ def self.b
+ [
+ caller_locations(2, 1).first.base_label,
+ caller_locations(3, 1).first.base_label
+ ]
+ end
+ assert_equal({["each", "group_by"]=>[1]}, a)
+ end
+
+ def test_caller_location_inspect_cfunc_iseq_no_pc
+ def self.foo
+ @res = caller_locations(2, 1).inspect
+ end
+ @line = __LINE__ + 1
+ [1].map.map { [1].map.map { foo } }
+ assert_equal("[\"#{__FILE__}:#{@line}:in 'Array#map'\"]", @res)
+ end
+
+ def test_caller_location_path_cfunc_iseq_no_pc
+ def self.foo
+ @res = caller_locations(2, 1)[0].path
+ end
+ [1].map.map { [1].map.map { foo } }
+ assert_equal(__FILE__, @res)
+ end
+
+ def test_caller_locations
+ cs = caller(0); locs = caller_locations(0).map{|loc|
+ loc.to_s
+ }
+ assert_equal(cs, locs)
+ end
+
+ def test_caller_locations_with_range
+ cs = caller(0,2); locs = caller_locations(0..1).map { |loc|
+ loc.to_s
+ }
+ assert_equal(cs, locs)
+ end
+
+ def test_caller_locations_to_s_inspect
+ cs = caller(0); locs = caller_locations(0)
+ cs.zip(locs){|str, loc|
+ assert_equal(str, loc.to_s)
+ assert_equal(str.inspect, loc.inspect)
+ }
+ end
+
+ def test_caller_locations_path
+ loc, = caller_locations(0, 1)
+ assert_equal(__FILE__, loc.path)
+ Tempfile.create(%w"caller_locations .rb") do |f|
+ f.puts "caller_locations(0, 1)[0].tap {|loc| puts loc.path}"
+ f.close
+ dir, base = File.split(f.path)
+ assert_in_out_err(["-C", dir, base], "", [base])
+ end
+ end
+
+ def test_caller_locations_absolute_path
+ loc, = caller_locations(0, 1)
+ assert_equal(__FILE__, loc.absolute_path)
+ Tempfile.create(%w"caller_locations .rb") do |f|
+ f.puts "caller_locations(0, 1)[0].tap {|loc| puts loc.absolute_path}"
+ f.close
+ assert_in_out_err(["-C", *File.split(f.path)], "", [File.realpath(f.path)])
+ end
+ end
+
+ def test_caller_locations_lineno
+ loc, = caller_locations(0, 1)
+ assert_equal(__LINE__-1, loc.lineno)
+ Tempfile.create(%w"caller_locations .rb") do |f|
+ f.puts "caller_locations(0, 1)[0].tap {|loc| puts loc.lineno}"
+ f.close
+ assert_in_out_err(["-C", *File.split(f.path)], "", ["1"])
+ end
+ end
+
+ def test_caller_locations_base_label
+ assert_equal("#{__method__}", caller_locations(0, 1)[0].base_label)
+ loc, = tap {break caller_locations(0, 1)}
+ assert_equal("#{__method__}", loc.base_label)
+ begin
+ raise
+ rescue
+ assert_equal("#{__method__}", caller_locations(0, 1)[0].base_label)
+ end
+ end
+
+ def test_caller_locations_label
+ assert_equal("TestBacktrace##{__method__}", caller_locations(0, 1)[0].label)
+ loc, = tap {break caller_locations(0, 1)}
+ assert_equal("block in TestBacktrace##{__method__}", loc.label)
+ begin
+ raise
+ rescue
+ assert_equal("TestBacktrace##{__method__}", caller_locations(0, 1)[0].label)
+ end
+ end
+
+ def th_rec q, n=10
+ if n > 1
+ th_rec q, n-1
+ else
+ q.pop
+ end
+ end
+
+ def test_thread_backtrace
+ begin
+ q = Thread::Queue.new
+ th = Thread.new{
+ th_rec q
+ }
+ sleep 0.5
+ th_backtrace = th.backtrace
+ th_locations = th.backtrace_locations
+
+ assert_equal(10, th_backtrace.count{|e| e =~ /th_rec/})
+ assert_equal(th_backtrace, th_locations.map{|e| e.to_s})
+ assert_equal(th_backtrace, th.backtrace(0))
+ assert_equal(th_locations.map{|e| e.to_s},
+ th.backtrace_locations(0).map{|e| e.to_s})
+ th_backtrace.size.times{|n|
+ assert_equal(n, th.backtrace(0, n).size)
+ assert_equal(n, th.backtrace_locations(0, n).size)
+ }
+ n = th_backtrace.size
+ assert_equal(n, th.backtrace(0, n + 1).size)
+ assert_equal(n, th.backtrace_locations(0, n + 1).size)
+ ensure
+ q << true
+ th.join
+ end
+ end
+
+ def test_thread_backtrace_locations_with_range
+ begin
+ q = Thread::Queue.new
+ th = Thread.new{
+ th_rec q
+ }
+ sleep 0.5
+ bt = th.backtrace(0,2)
+ locs = th.backtrace_locations(0..1).map { |loc|
+ loc.to_s
+ }
+ assert_equal(bt, locs)
+ ensure
+ q << true
+ th.join
+ end
+ end
+
+ def test_core_backtrace_alias
+ obj = BasicObject.new
+ e = assert_raise(NameError) do
+ class << obj
+ alias foo bar
+ end
+ end
+ assert_not_match(/\Acore#/, e.backtrace_locations[0].base_label)
+ end
+
+ def test_core_backtrace_undef
+ obj = BasicObject.new
+ e = assert_raise(NameError) do
+ class << obj
+ undef foo
+ end
+ end
+ assert_not_match(/\Acore#/, e.backtrace_locations[0].base_label)
+ end
+
+ def test_core_backtrace_hash_merge
+ e = assert_raise(TypeError) do
+ {**1}
+ end
+ assert_not_match(/\Acore#/, e.backtrace_locations[0].base_label)
+ end
+
+ def test_notty_backtrace
+ err = ["-:1:in '<main>': unhandled exception"]
+ assert_in_out_err([], "raise", [], err)
+
+ err = ["-:2:in 'Object#foo': foo! (RuntimeError)",
+ "\tfrom -:4:in '<main>'"]
+ assert_in_out_err([], <<-"end;", [], err)
+ def foo
+ raise "foo!"
+ end
+ foo
+ end;
+
+ err = ["-:7:in 'Object#bar': bar! (RuntimeError)",
+ "\tfrom -:9:in '<main>'",
+ "-:2:in 'Object#foo': foo! (RuntimeError)",
+ "\tfrom -:5:in 'Object#bar'",
+ "\tfrom -:9:in '<main>'"]
+ assert_in_out_err([], <<-"end;", [], err)
+ def foo
+ raise "foo!"
+ end
+ def bar
+ foo
+ rescue
+ raise "bar!"
+ end
+ bar
+ end;
+ end
+
+ def test_caller_to_enum
+ err = ["-:3:in 'Object#foo': unhandled exception", "\tfrom -:in 'Enumerator#each'"]
+ assert_in_out_err([], <<-"end;", [], err, "[ruby-core:91911]")
+ def foo
+ return to_enum(__method__) unless block_given?
+ raise
+ yield 1
+ end
+
+ enum = foo
+ enum.next
+ end;
+ end
+
+ def test_no_receiver_for_anonymous_class
+ err = ["-:2:in 'bar': unhandled exception", # Not '#<Class:0xXXX>.bar'
+ "\tfrom -:3:in '<main>'"]
+ assert_in_out_err([], <<-"end;", [], err)
+ foo = Class.new
+ def foo.bar = raise
+ foo.bar
+ end;
+
+ err = ["-:3:in 'baz': unhandled exception", # Not '#<Class:0xXXX>::Bar.baz'
+ "\tfrom -:4:in '<main>'"]
+ assert_in_out_err([], <<-"end;", [], err)
+ foo = Class.new
+ foo::Bar = Class.new
+ def (foo::Bar).baz = raise
+ foo::Bar.baz
+ end;
+ end
+
+ def test_backtrace_internal_frame
+ backtrace = tap { break caller_locations(0) }
+ assert_equal(__FILE__, backtrace[1].path) # not "<internal:kernel>"
+ assert_equal("Kernel#tap", backtrace[1].label)
+ end
+
+ def test_backtrace_on_argument_error
+ lineno = __LINE__; [1, 2].inject(:tap)
+ rescue ArgumentError
+ assert_equal("#{ __FILE__ }:#{ lineno }:in 'Kernel#tap'", $!.backtrace[0].to_s)
+ end
+end