summaryrefslogtreecommitdiff
path: root/spec/ruby/core/thread/report_on_exception_spec.rb
blob: 9279fa1da57e47b79b8c673aaa1070ee6f13c088 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
require_relative '../../spec_helper'

describe "Thread.report_on_exception" do
  it "defaults to true" do
    ruby_exe("p Thread.report_on_exception").should == "true\n"
  end
end

describe "Thread.report_on_exception=" do
  before :each do
    @report_on_exception = Thread.report_on_exception
  end

  after :each do
    Thread.report_on_exception = @report_on_exception
  end

  it "changes the default value for new threads" do
    Thread.report_on_exception = true
    Thread.report_on_exception.should == true
    t = Thread.new {}
    t.join
    t.report_on_exception.should == true
  end
end

describe "Thread#report_on_exception" do
  it "returns true for the main Thread" do
    Thread.current.report_on_exception.should == true
  end

  it "returns true for new Threads" do
    Thread.new { Thread.current.report_on_exception }.value.should == true
  end

  it "returns whether the Thread will print a backtrace if it exits with an exception" do
    t = Thread.new { Thread.current.report_on_exception = true }
    t.join
    t.report_on_exception.should == true

    t = Thread.new { Thread.current.report_on_exception = false }
    t.join
    t.report_on_exception.should == false
  end
end

describe "Thread#report_on_exception=" do
  describe "when set to true" do
    it "prints a backtrace on $stderr if it terminates with an exception" do
      t = nil
      -> {
        t = Thread.new {
          Thread.current.report_on_exception = true
          raise RuntimeError, "Thread#report_on_exception specs"
        }
        Thread.pass while t.alive?
      }.should output("", /Thread.+terminated with exception.+Thread#report_on_exception specs/m)

      -> {
        t.join
      }.should raise_error(RuntimeError, "Thread#report_on_exception specs")
    end

    ruby_version_is "3.0" do
      it "prints a backtrace on $stderr in the regular backtrace order" do
        line_raise = __LINE__ + 2
        def foo
          raise RuntimeError, "Thread#report_on_exception specs backtrace order"
        end

        line_call_foo = __LINE__ + 5
        go = false
        t = Thread.new {
          Thread.current.report_on_exception = true
          Thread.pass until go
          foo
        }

        -> {
          go = true
          Thread.pass while t.alive?
        }.should output("", <<ERR)
#{t.inspect} terminated with exception (report_on_exception is true):
#{__FILE__}:#{line_raise}:in `foo': Thread#report_on_exception specs backtrace order (RuntimeError)
\tfrom #{__FILE__}:#{line_call_foo}:in `block (5 levels) in <top (required)>'
ERR

        -> {
          t.join
        }.should raise_error(RuntimeError, "Thread#report_on_exception specs backtrace order")
      end
    end

    it "prints the backtrace even if the thread was killed just after Thread#raise" do
      t = nil
      ready = false
      -> {
        t = Thread.new {
          Thread.current.report_on_exception = true
          ready = true
          sleep
        }

        Thread.pass until ready and t.stop?
        t.raise RuntimeError, "Thread#report_on_exception before kill spec"
        t.kill
        Thread.pass while t.alive?
      }.should output("", /Thread.+terminated with exception.+Thread#report_on_exception before kill spec/m)

      -> {
        t.join
      }.should raise_error(RuntimeError, "Thread#report_on_exception before kill spec")
    end
  end

  describe "when set to false" do
    it "lets the thread terminates silently with an exception" do
      t = nil
      -> {
        t = Thread.new {
          Thread.current.report_on_exception = false
          raise RuntimeError, "Thread#report_on_exception specs"
        }
        Thread.pass while t.alive?
      }.should output("", "")

      -> {
        t.join
      }.should raise_error(RuntimeError, "Thread#report_on_exception specs")
    end
  end

  describe "when used in conjunction with Thread#abort_on_exception" do
    it "first reports then send the exception back to the main Thread" do
      t = nil
      mutex = Mutex.new
      mutex.lock
      -> {
        t = Thread.new {
          Thread.current.abort_on_exception = true
          Thread.current.report_on_exception = true
          mutex.lock
          mutex.unlock
          raise RuntimeError, "Thread#report_on_exception specs"
        }

        -> {
          mutex.sleep(5)
        }.should raise_error(RuntimeError, "Thread#report_on_exception specs")
      }.should output("", /Thread.+terminated with exception.+Thread#report_on_exception specs/m)

      -> {
        t.join
      }.should raise_error(RuntimeError, "Thread#report_on_exception specs")
    end
  end
end