summaryrefslogtreecommitdiff
path: root/spec/ruby/shared/kernel/raise.rb
blob: 82fb0333c8933b428c69aada930f4099b9892b2f (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
describe :kernel_raise, shared: true do
  before :each do
    ScratchPad.clear
  end

  it "aborts execution" do
    -> do
      @object.raise Exception, "abort"
      ScratchPad.record :no_abort
    end.should raise_error(Exception, "abort")

    ScratchPad.recorded.should be_nil
  end

  it "accepts an exception that implements to_hash" do
    custom_error = Class.new(StandardError) do
      def to_hash
        {}
      end
    end
    error = custom_error.new
    -> { @object.raise(error) }.should raise_error(custom_error)
  end

  it "allows the message parameter to be a hash" do
    data_error = Class.new(StandardError) do
      attr_reader :data
      def initialize(data)
        @data = data
      end
    end

    -> { @object.raise(data_error, {data: 42}) }.should raise_error(data_error) do |ex|
      ex.data.should == {data: 42}
    end
  end

  # https://bugs.ruby-lang.org/issues/8257#note-36
  it "allows extra keyword arguments for compatibility" do
    data_error = Class.new(StandardError) do
      attr_reader :data
      def initialize(data)
        @data = data
      end
    end

    -> { @object.raise(data_error, data: 42) }.should raise_error(data_error) do |ex|
      ex.data.should == {data: 42}
    end
  end

  it "does not allow message and extra keyword arguments" do
    data_error = Class.new(StandardError) do
      attr_reader :data
      def initialize(data)
        @data = data
      end
    end

    -> { @object.raise(data_error, {a: 1}, b: 2) }.should raise_error(StandardError) do |e|
      [TypeError, ArgumentError].should.include?(e.class)
    end

    -> { @object.raise(data_error, {a: 1}, [], b: 2) }.should raise_error(ArgumentError)
  end

  it "raises RuntimeError if no exception class is given" do
    -> { @object.raise }.should raise_error(RuntimeError, "")
  end

  it "raises a given Exception instance" do
    error = RuntimeError.new
    -> { @object.raise(error) }.should raise_error(error)
  end

  it "raises a RuntimeError if string given" 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

  it "raises a TypeError when passed true" do
    -> { @object.raise(true) }.should raise_error(TypeError)
  end

  it "raises a TypeError when passed false" do
    -> { @object.raise(false) }.should raise_error(TypeError)
  end

  it "raises a TypeError when passed nil" do
    -> { @object.raise(nil) }.should raise_error(TypeError)
  end

  it "re-raises a previously rescued exception without overwriting the backtrace" do
    # 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

    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
    -> do
      @object.raise(ArgumentError, "message", caller)
    end.should raise_error(ArgumentError, "message")
  end
end