1Code.require_file("../test_helper.exs", __DIR__)
2
3defmodule Kernel.RaiseTest do
4  use ExUnit.Case, async: true
5
6  # Silence warnings
7  defp atom, do: RuntimeError
8  defp binary, do: "message"
9  defp opts, do: [message: "message"]
10  defp struct, do: %RuntimeError{message: "message"}
11
12  @compile {:no_warn_undefined, DoNotExist}
13  @trace [{:foo, :bar, 0, []}]
14
15  test "raise preserves the stacktrace" do
16    stacktrace =
17      try do
18        raise "a"
19      rescue
20        _ -> hd(__STACKTRACE__)
21      end
22
23    file = __ENV__.file |> Path.relative_to_cwd() |> String.to_charlist()
24
25    assert {__MODULE__, :"test raise preserves the stacktrace", _, [file: ^file, line: 18] ++ _} =
26             stacktrace
27  end
28
29  test "raise message" do
30    assert_raise RuntimeError, "message", fn ->
31      raise "message"
32    end
33
34    assert_raise RuntimeError, "message", fn ->
35      var = binary()
36      raise var
37    end
38  end
39
40  test "raise with no arguments" do
41    assert_raise RuntimeError, fn ->
42      raise RuntimeError
43    end
44
45    assert_raise RuntimeError, fn ->
46      var = atom()
47      raise var
48    end
49  end
50
51  test "raise with arguments" do
52    assert_raise RuntimeError, "message", fn ->
53      raise RuntimeError, message: "message"
54    end
55
56    assert_raise RuntimeError, "message", fn ->
57      atom = atom()
58      opts = opts()
59      raise atom, opts
60    end
61  end
62
63  test "raise existing exception" do
64    assert_raise RuntimeError, "message", fn ->
65      raise %RuntimeError{message: "message"}
66    end
67
68    assert_raise RuntimeError, "message", fn ->
69      var = struct()
70      raise var
71    end
72  end
73
74  if :erlang.system_info(:otp_release) >= '24' do
75    test "raise with error_info" do
76      {exception, stacktrace} =
77        try do
78          raise "a"
79        rescue
80          e -> {e, __STACKTRACE__}
81        end
82
83      assert [{__MODULE__, _, _, meta} | _] = stacktrace
84      assert meta[:error_info] == %{module: Exception}
85
86      assert Exception.format_error(exception, stacktrace) ==
87               %{general: "a", reason: "#Elixir.RuntimeError"}
88    end
89  end
90
91  test "reraise message" do
92    try do
93      reraise "message", @trace
94      flunk("should not reach")
95    rescue
96      RuntimeError ->
97        assert @trace == __STACKTRACE__
98    end
99
100    try do
101      var = binary()
102      reraise var, @trace
103      flunk("should not reach")
104    rescue
105      RuntimeError ->
106        assert @trace == __STACKTRACE__
107    end
108  end
109
110  test "reraise with no arguments" do
111    try do
112      reraise RuntimeError, @trace
113      flunk("should not reach")
114    rescue
115      RuntimeError ->
116        assert @trace == __STACKTRACE__
117    end
118
119    try do
120      var = atom()
121      reraise var, @trace
122      flunk("should not reach")
123    rescue
124      RuntimeError ->
125        assert @trace == __STACKTRACE__
126    end
127  end
128
129  test "reraise with arguments" do
130    try do
131      reraise RuntimeError, [message: "message"], @trace
132      flunk("should not reach")
133    rescue
134      RuntimeError ->
135        assert @trace == __STACKTRACE__
136    end
137
138    try do
139      atom = atom()
140      opts = opts()
141      reraise atom, opts, @trace
142      flunk("should not reach")
143    rescue
144      RuntimeError ->
145        assert @trace == __STACKTRACE__
146    end
147  end
148
149  test "reraise existing exception" do
150    try do
151      reraise %RuntimeError{message: "message"}, @trace
152      flunk("should not reach")
153    rescue
154      RuntimeError ->
155        assert @trace == __STACKTRACE__
156    end
157
158    try do
159      var = struct()
160      reraise var, @trace
161      flunk("should not reach")
162    rescue
163      RuntimeError ->
164        assert @trace == __STACKTRACE__
165    end
166  end
167
168  test "reraise with invalid stacktrace" do
169    try do
170      reraise %RuntimeError{message: "message"}, {:oops, @trace}
171    rescue
172      ArgumentError ->
173        {name, arity} = __ENV__.function
174        assert [{__MODULE__, ^name, ^arity, _} | _] = __STACKTRACE__
175    end
176  end
177
178  describe "rescue" do
179    test "runtime error" do
180      result =
181        try do
182          raise "an exception"
183        rescue
184          RuntimeError -> true
185        catch
186          :error, _ -> false
187        end
188
189      assert result
190
191      result =
192        try do
193          raise "an exception"
194        rescue
195          AnotherError -> true
196        catch
197          :error, _ -> false
198        end
199
200      refute result
201    end
202
203    test "named runtime error" do
204      result =
205        try do
206          raise "an exception"
207        rescue
208          x in [RuntimeError] -> Exception.message(x)
209        catch
210          :error, _ -> false
211        end
212
213      assert result == "an exception"
214    end
215
216    test "named runtime or argument error" do
217      result =
218        try do
219          raise "an exception"
220        rescue
221          x in [ArgumentError, RuntimeError] -> Exception.message(x)
222        catch
223          :error, _ -> false
224        end
225
226      assert result == "an exception"
227    end
228
229    test "with higher precedence than catch" do
230      result =
231        try do
232          raise "an exception"
233        rescue
234          _ -> true
235        catch
236          _, _ -> false
237        end
238
239      assert result
240    end
241
242    test "argument error from Erlang" do
243      result =
244        try do
245          :erlang.error(:badarg)
246        rescue
247          ArgumentError -> true
248        end
249
250      assert result
251    end
252
253    test "argument error from Elixir" do
254      result =
255        try do
256          raise ArgumentError, ""
257        rescue
258          ArgumentError -> true
259        end
260
261      assert result
262    end
263
264    test "catch-all variable" do
265      result =
266        try do
267          raise "an exception"
268        rescue
269          x -> Exception.message(x)
270        end
271
272      assert result == "an exception"
273    end
274
275    test "catch-all underscore" do
276      result =
277        try do
278          raise "an exception"
279        rescue
280          _ -> true
281        end
282
283      assert result
284    end
285
286    test "catch-all unused variable" do
287      result =
288        try do
289          raise "an exception"
290        rescue
291          _any -> true
292        end
293
294      assert result
295    end
296
297    test "catch-all with \"x in _\" syntax" do
298      result =
299        try do
300          raise "an exception"
301        rescue
302          exception in _ ->
303            Exception.message(exception)
304        end
305
306      assert result == "an exception"
307    end
308  end
309
310  describe "normalize" do
311    test "wrap custom Erlang error" do
312      result =
313        try do
314          :erlang.error(:sample)
315        rescue
316          x in [ErlangError] -> Exception.message(x)
317        end
318
319      assert result == "Erlang error: :sample"
320    end
321
322    test "undefined function error" do
323      result =
324        try do
325          DoNotExist.for_sure()
326        rescue
327          x in [UndefinedFunctionError] -> Exception.message(x)
328        end
329
330      assert result ==
331               "function DoNotExist.for_sure/0 is undefined (module DoNotExist is not available)"
332    end
333
334    test "function clause error" do
335      result =
336        try do
337          zero(1)
338        rescue
339          x in [FunctionClauseError] -> Exception.message(x)
340        end
341
342      assert result == "no function clause matching in Kernel.RaiseTest.zero/1"
343    end
344
345    test "badarg error" do
346      result =
347        try do
348          :erlang.error(:badarg)
349        rescue
350          x in [ArgumentError] -> Exception.message(x)
351        end
352
353      assert result == "argument error"
354    end
355
356    test "tuple badarg error" do
357      result =
358        try do
359          :erlang.error({:badarg, [1, 2, 3]})
360        rescue
361          x in [ArgumentError] -> Exception.message(x)
362        end
363
364      assert result == "argument error: [1, 2, 3]"
365    end
366
367    test "badarith error" do
368      result =
369        try do
370          :erlang.error(:badarith)
371        rescue
372          x in [ArithmeticError] -> Exception.message(x)
373        end
374
375      assert result == "bad argument in arithmetic expression"
376    end
377
378    test "badarity error" do
379      fun = fn x -> x end
380      string = "#{inspect(fun)} with arity 1 called with 2 arguments (1, 2)"
381
382      result =
383        try do
384          fun.(1, 2)
385        rescue
386          x in [BadArityError] -> Exception.message(x)
387        end
388
389      assert result == string
390    end
391
392    test "badfun error" do
393      # Avoid "invalid function call" warning
394      x = fn -> :example end
395
396      result =
397        try do
398          x.().(2)
399        rescue
400          x in [BadFunctionError] -> Exception.message(x)
401        end
402
403      assert result == "expected a function, got: :example"
404    end
405
406    test "badfun error when the function is gone" do
407      defmodule BadFunction.Missing do
408        def fun, do: fn -> :ok end
409      end
410
411      fun = BadFunction.Missing.fun()
412
413      :code.delete(BadFunction.Missing)
414
415      defmodule BadFunction.Missing do
416        def fun, do: fn -> :another end
417      end
418
419      :code.purge(BadFunction.Missing)
420
421      result =
422        try do
423          fun.()
424        rescue
425          x in [BadFunctionError] -> Exception.message(x)
426        end
427
428      assert result =~
429               ~r/function #Function<[0-9]\.[0-9]*\/0 in Kernel.RaiseTest.BadFunction.Missing> is invalid, likely because it points to an old version of the code/
430    end
431
432    test "badmatch error" do
433      x = :example
434
435      result =
436        try do
437          ^x = zero(0)
438        rescue
439          x in [MatchError] -> Exception.message(x)
440        end
441
442      assert result == "no match of right hand side value: 0"
443    end
444
445    defp empty_map(), do: %{}
446
447    test "bad key error" do
448      result =
449        try do
450          %{empty_map() | foo: :bar}
451        rescue
452          x in [KeyError] -> Exception.message(x)
453        end
454
455      assert result == "key :foo not found"
456
457      result =
458        try do
459          empty_map().foo
460        rescue
461          x in [KeyError] -> Exception.message(x)
462        end
463
464      assert result == "key :foo not found in: %{}"
465    end
466
467    test "bad map error" do
468      result =
469        try do
470          %{zero(0) | foo: :bar}
471        rescue
472          x in [BadMapError] -> Exception.message(x)
473        end
474
475      assert result == "expected a map, got: 0"
476    end
477
478    test "bad boolean error" do
479      result =
480        try do
481          1 and true
482        rescue
483          x in [BadBooleanError] -> Exception.message(x)
484        end
485
486      assert result == "expected a boolean on left-side of \"and\", got: 1"
487    end
488
489    test "case clause error" do
490      x = :example
491
492      result =
493        try do
494          case zero(0) do
495            ^x -> nil
496          end
497        rescue
498          x in [CaseClauseError] -> Exception.message(x)
499        end
500
501      assert result == "no case clause matching: 0"
502    end
503
504    test "cond clause error" do
505      result =
506        try do
507          cond do
508            !zero(0) -> :ok
509          end
510        rescue
511          x in [CondClauseError] -> Exception.message(x)
512        end
513
514      assert result == "no cond clause evaluated to a truthy value"
515    end
516
517    test "try clause error" do
518      f = fn -> :example end
519
520      result =
521        try do
522          try do
523            f.()
524          rescue
525            _exception ->
526              :ok
527          else
528            :other ->
529              :ok
530          end
531        rescue
532          x in [TryClauseError] -> Exception.message(x)
533        end
534
535      assert result == "no try clause matching: :example"
536    end
537
538    test "undefined function error as Erlang error" do
539      result =
540        try do
541          DoNotExist.for_sure()
542        rescue
543          x in [ErlangError] -> Exception.message(x)
544        end
545
546      assert result ==
547               "function DoNotExist.for_sure/0 is undefined (module DoNotExist is not available)"
548    end
549  end
550
551  defmacrop exceptions do
552    [ErlangError]
553  end
554
555  test "with macros" do
556    result =
557      try do
558        DoNotExist.for_sure()
559      rescue
560        x in exceptions() -> Exception.message(x)
561      end
562
563    assert result ==
564             "function DoNotExist.for_sure/0 is undefined (module DoNotExist is not available)"
565  end
566
567  defp zero(0), do: 0
568end
569