summaryrefslogtreecommitdiff
path: root/spec/ruby/language/ensure_spec.rb
blob: 064627b2f73f2983bb3ab82bd38ef10c13915ad6 (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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
require_relative '../spec_helper'
require_relative 'fixtures/ensure'

describe "An ensure block inside a begin block" do
  before :each do
    ScratchPad.record []
  end

  it "is executed when an exception is raised in it's corresponding begin block" do
    lambda {
      begin
        ScratchPad << :begin
        raise EnsureSpec::Error
      ensure
        ScratchPad << :ensure
      end
    }.should raise_error(EnsureSpec::Error)

    ScratchPad.recorded.should == [:begin, :ensure]
  end

  it "is executed when an exception is raised and rescued in it's corresponding begin block" do
    begin
      ScratchPad << :begin
      raise "An exception occurred!"
    rescue
      ScratchPad << :rescue
    ensure
      ScratchPad << :ensure
    end

    ScratchPad.recorded.should == [:begin, :rescue, :ensure]
  end

  it "is executed even when a symbol is thrown in it's corresponding begin block" do
    catch(:symbol) do
      begin
        ScratchPad << :begin
        throw(:symbol)
      rescue
        ScratchPad << :rescue
      ensure
        ScratchPad << :ensure
      end
    end

    ScratchPad.recorded.should == [:begin, :ensure]
  end

  it "is executed when nothing is raised or thrown in it's corresponding begin block" do
    begin
      ScratchPad << :begin
    rescue
      ScratchPad << :rescue
    ensure
      ScratchPad << :ensure
    end

    ScratchPad.recorded.should == [:begin, :ensure]
  end

  it "has no return value" do
    begin
      :begin
    ensure
      :ensure
    end.should == :begin
  end

  it "sets exception cause if raises exception in block and in ensure" do
    -> {
      begin
        raise "from block"
      ensure
        raise "from ensure"
      end
    }.should raise_error(RuntimeError, "from ensure") do |e|
      e.cause.message.should == "from block"
    end
  end
end

describe "The value of an ensure expression," do
  it "in no-exception scenarios, is the value of the last statement of the protected body" do
    begin
      v = 1
      eval('x=1') # to prevent opts from triggering
      v
    ensure
      v = 2
    end.should == 1
  end

  it "when an exception is rescued, is the value of the rescuing block" do
    begin
      raise 'foo'
    rescue
      v = 3
    ensure
      v = 2
    end.should == 3
  end
end

describe "An ensure block inside a method" do
  before :each do
    @obj = EnsureSpec::Container.new
  end

  it "is executed when an exception is raised in the method" do
    lambda { @obj.raise_in_method_with_ensure }.should raise_error(EnsureSpec::Error)
    @obj.executed.should == [:method, :ensure]
  end

  it "is executed when an exception is raised and rescued in the method" do
    @obj.raise_and_rescue_in_method_with_ensure
    @obj.executed.should == [:method, :rescue, :ensure]
  end

  it "is executed even when a symbol is thrown in the method" do
    catch(:symbol) { @obj.throw_in_method_with_ensure }
    @obj.executed.should == [:method, :ensure]
  end

  it "has no impact on the method's implicit return value" do
    @obj.implicit_return_in_method_with_ensure.should == :method
  end

  it "has an impact on the method's explicit return value" do
    @obj.explicit_return_in_method_with_ensure.should == :ensure
  end

  it "has an impact on the method's explicit return value from rescue if returns explicitly" do
    @obj.explicit_return_in_rescue_and_explicit_return_in_ensure.should == "returned in ensure"
  end

  it "has no impact on the method's explicit return value from rescue if returns implicitly" do
    @obj.explicit_return_in_rescue_and_implicit_return_in_ensure.should == "returned in rescue"
  end

  it "suppresses exception raised in method if returns value explicitly" do
    @obj.raise_and_explicit_return_in_ensure.should == "returned in ensure"
  end

  it "suppresses exception raised in rescue if returns value explicitly" do
    @obj.raise_in_rescue_and_explicit_return_in_ensure.should == "returned in ensure"
  end

  it "overrides exception raised in rescue if raises exception itself" do
    -> {
      @obj.raise_in_rescue_and_raise_in_ensure
    }.should raise_error(RuntimeError, "raised in ensure")
  end

  it "suppresses exception raised in method if raises exception itself" do
    -> {
      @obj.raise_in_method_and_raise_in_ensure
    }.should raise_error(RuntimeError, "raised in ensure")
  end
end

describe "An ensure block inside a class" do
  before :each do
    ScratchPad.record []
  end

  it "is executed when an exception is raised" do
    lambda {
      eval <<-ruby
        class EnsureInClassExample
          ScratchPad << :class
          raise EnsureSpec::Error
        ensure
          ScratchPad << :ensure
        end
      ruby
    }.should raise_error(EnsureSpec::Error)

    ScratchPad.recorded.should == [:class, :ensure]
  end

  it "is executed when an exception is raised and rescued" do
    eval <<-ruby
      class EnsureInClassExample
        ScratchPad << :class
        raise
      rescue
        ScratchPad << :rescue
      ensure
        ScratchPad << :ensure
      end
    ruby

    ScratchPad.recorded.should == [:class, :rescue, :ensure]
  end

  it "is executed even when a symbol is thrown" do
    catch(:symbol) do
      eval <<-ruby
        class EnsureInClassExample
          ScratchPad << :class
          throw(:symbol)
        rescue
          ScratchPad << :rescue
        ensure
          ScratchPad << :ensure
        end
      ruby
    end

    ScratchPad.recorded.should == [:class, :ensure]
  end

  it "is executed when nothing is raised or thrown" do
    eval <<-ruby
      class EnsureInClassExample
        ScratchPad << :class
      rescue
        ScratchPad << :rescue
      ensure
        ScratchPad << :ensure
      end
    ruby

    ScratchPad.recorded.should == [:class, :ensure]
  end

  it "has no return value" do
    result = eval <<-ruby
      class EnsureInClassExample
        :class
      ensure
        :ensure
      end
    ruby

    result.should == :class
  end
end

describe "An ensure block inside {} block" do
  it "is not allowed" do
    lambda {
      eval <<-ruby
        lambda {
          raise
        ensure
        }
      ruby
    }.should raise_error(SyntaxError)
  end
end

ruby_version_is "2.5" do
  describe "An ensure block inside 'do end' block" do
    before :each do
      ScratchPad.record []
    end

    it "is executed when an exception is raised in it's corresponding begin block" do
      lambda {
        eval(<<-ruby).call
          lambda do
            ScratchPad << :begin
            raise EnsureSpec::Error
          ensure
            ScratchPad << :ensure
          end
        ruby
      }.should raise_error(EnsureSpec::Error)

      ScratchPad.recorded.should == [:begin, :ensure]
    end

    it "is executed when an exception is raised and rescued in it's corresponding begin block" do
      eval(<<-ruby).call
        lambda do
          ScratchPad << :begin
          raise "An exception occurred!"
        rescue
          ScratchPad << :rescue
        ensure
          ScratchPad << :ensure
        end
      ruby

      ScratchPad.recorded.should == [:begin, :rescue, :ensure]
    end

    it "is executed even when a symbol is thrown in it's corresponding begin block" do
      catch(:symbol) do
        eval(<<-ruby).call
          lambda do
            ScratchPad << :begin
            throw(:symbol)
          rescue
            ScratchPad << :rescue
          ensure
            ScratchPad << :ensure
          end
        ruby
      end

      ScratchPad.recorded.should == [:begin, :ensure]
    end

    it "is executed when nothing is raised or thrown in it's corresponding begin block" do
      eval(<<-ruby).call
        lambda do
          ScratchPad << :begin
        rescue
          ScratchPad << :rescue
        ensure
          ScratchPad << :ensure
        end
      ruby

      ScratchPad.recorded.should == [:begin, :ensure]
    end

    it "has no return value" do
      result = eval(<<-ruby).call
        lambda do
          :begin
        ensure
          :ensure
        end
      ruby

      result.should == :begin
    end
  end
end