%%% -*- erlang-indent-level: 2 -*-
%%%-------------------------------------------------------------------
%%% Author: Kostis Sagonas
%%%
%%% Contains tests that raise exceptions and catch them.
%%%-------------------------------------------------------------------
-module(basic_exceptions).
-export([test/0, test_catches/0]).
%% functions used as arguments to spawn/3
-export([bad_guy/2]).
test() ->
ok = test_catch_exit(42),
ok = test_catch_throw(42),
ok = test_catch_element(),
ok = test_catch_crash(),
ok = test_catch_empty(),
ok = test_catch_merge(),
ok = test_catches_merged(),
ok = test_pending_errors(),
ok = test_bad_fun_call(),
ok.
%%--------------------------------------------------------------------
%% Written in 2001 by Erik Johansson.
test_catches() ->
ExitBar = {'EXIT', bar},
L1 = [ExitBar, ok, ExitBar, {ok, ExitBar}],
L1 = [t1(), t2(), t3(), t4()],
badarith = (catch element(1, element(2, t5(a, b)))),
L2 = [42, ExitBar, ExitBar, {no_exception, ok}],
L2 = [t5(21, 21), t6(), t7(), t8()],
ok.
t1() ->
catch foo().
t2() ->
V = (catch ok()),
s(),
V.
t3() ->
V = (catch foo()),
V.
t4() ->
V1 = ok(),
V2 = (catch foo()),
{V1, V2}.
t5(A, B) ->
catch A + B.
t6() ->
catch {no_exception, ok(), foo()}.
t7() ->
catch {no_exception, foo(), ok()}.
t8() ->
catch {no_exception, ok()}.
foo() ->
s(),
exit(bar).
ok() -> s(), ok.
s() -> nada.
%%--------------------------------------------------------------------
test_catch_exit(N) ->
{'EXIT', N} = (catch exit(N)),
{'EXIT', 42} = (catch exit(42)),
42 = try exit(N) catch exit:R1 -> R1 end,
42 = try exit(42) catch exit:R2 -> R2 end,
ok.
%%--------------------------------------------------------------------
test_catch_throw(N) ->
N = (catch throw(N)),
42 = (catch throw(42)),
42 = try throw(N) catch throw:R1 -> R1 end,
42 = try throw(42) catch throw:R2 -> R2 end,
ok.
%%--------------------------------------------------------------------
test_catch_element() ->
'EXIT' = test_catch_element([]),
'EXIT' = test_catch_element(42),
ok.
test_catch_element(N) ->
element(1, catch element(N, {1,2,3,4,5,6,7,8,9,10,11})).
%%--------------------------------------------------------------------
-define(try_match(E),
catch ?MODULE:non_existing(),
{'EXIT', {{badmatch, nomatch}, _}} = (catch E = no_match())).
test_catch_crash() ->
?try_match(a),
?try_match(42),
?try_match({a, b, c}),
?try_match([]),
?try_match(1.0),
ok.
no_match() -> nomatch.
%% small_test() ->
%% catch ?MODULE:non_existing(),
%% io:format("Before\n",[]),
%% hipe_bifs:show_nstack(self()),
%% io:format("After\n",[]),
%% garbage_collect().
%%--------------------------------------------------------------------
%% Tests whether the HiPE compiler optimizes catches in a way that
%% does not result in an infinite loop.
%%--------------------------------------------------------------------
test_catch_empty() ->
badmatch().
badmatch() ->
Big = ret_big(),
Float = ret_float(),
catch a = Big,
catch b = Float,
ok = case Big of Big -> ok end,
ok = case Float of Float -> ok end,
ok.
ret_big() ->
329847987298478924982978248748729829487298292982972978239874.
ret_float() ->
3.1415927.
%%--------------------------------------------------------------------
%% Test that shows how BEAM can merge catch-end blocks that belong to
%% different catch-start instructions. Written by Richard Carlsson.
%%--------------------------------------------------------------------
test_catch_merge() ->
merge(get(whatever)).
merge(foo=X) ->
catch f(X),
catch g(X);
merge(X) ->
catch f(X),
catch g(X).
f(_) -> ok.
g(_) -> ok.
%%--------------------------------------------------------------------
%% Written by Tobias Lindahl.
test_catches_merged() ->
{'EXIT', _} = merged_catches(foo),
{'EXIT', {badarith, _}} = merged_catches(bar),
{'EXIT', _} = merged_catches(baz),
ok.
merged_catches(X) ->
case X of
foo -> catch fail1(0);
bar -> catch {catch(1 = X), fail2(0)};
baz -> catch fail3(0)
end.
fail1(X) -> 1/X.
fail2(X) -> 1/X.
fail3(X) -> 1/X.
%%--------------------------------------------------------------------
%% Taken from exception_SUITE.erl
%%--------------------------------------------------------------------
test_pending_errors() ->
error_logger:tty(false), % disable printouts of error reports
pending_errors().
%% Test various exceptions, in the presence of a previous error
%% suppressed in a guard.
pending_errors() ->
pending(e_badmatch, {badmatch, b}),
pending(x, function_clause),
pending(e_case, {case_clause, xxx}),
pending(e_if, if_clause),
%% pending(e_badarith, badarith),
%% pending(e_undef, undef),
pending(e_timeoutval, timeout_value),
%% pending(e_badarg, badarg),
%% pending(e_badarg_spawn, badarg),
ok.
bad_guy(pe_badarith, Other) when Other+1 =:= 0 -> % badarith (suppressed)
ok;
bad_guy(pe_badarg, Other) when length(Other) > 0 -> % badarg (suppressed)
ok;
bad_guy(_, e_case) ->
case xxx() of
ok -> ok
end; % case_clause
bad_guy(_, e_if) ->
B = b(),
if
a == B -> ok
end; % if_clause
%% bad_guy(_, e_badarith) ->
%% 1+b; % badarith
bad_guy(_, e_undef) ->
non_existing_module:foo(); % undef
bad_guy(_, e_timeoutval) ->
receive
after gazonk -> ok % timeout_value
end;
bad_guy(_, e_badarg) ->
node(xxx); % badarg
bad_guy(_, e_badarg_spawn) ->
spawn({}, {}, {}); % badarg
bad_guy(_, e_badmatch) ->
a = b(). % badmatch
xxx() -> xxx.
b() -> b.
pending(Arg, Expected) ->
pending(pe_badarith, Arg, Expected),
pending(pe_badarg, Arg, Expected).
pending(First, Second, Expected) ->
pending_catched(First, Second, Expected),
pending_exit_message([First, Second], Expected).
pending_catched(First, Second, Expected) ->
%% ok = io:format("Catching bad_guy(~p, ~p)\n", [First, Second]),
case catch bad_guy(First, Second) of
{'EXIT', Reason} ->
pending(Reason, bad_guy, [First, Second], Expected);
Other ->
exit({not_exit, Other})
end.
pending_exit_message(Args, Expected) ->
%% ok = io:format("Trapping exits from spawn_link(~p, ~p, ~p)\n",
%% [?MODULE, bad_guy, Args]),
process_flag(trap_exit, true),
Pid = spawn_link(?MODULE, bad_guy, Args),
receive
{'EXIT', Pid, Reason} ->
pending(Reason, bad_guy, Args, Expected);
Other ->
exit({unexpected_message, Other})
after 10000 ->
exit(timeout)
end,
process_flag(trap_exit, false).
%% pending({badarg, [{erlang,Bif,BifArgs},{?MODULE,Func,Arity}|_]},
%% Func, Args, _Code)
%% when atom(Bif), list(BifArgs), length(Args) =:= Arity ->
%% ok;
pending({badarg,Trace}, _, _, _) when is_list(Trace) ->
ok;
%% pending({undef,[{non_existing_module,foo,[]}|_]}, _, _, _) ->
%% ok;
pending({undef,Trace}, _, _, _) when is_list(Trace) ->
ok;
%% pending({function_clause,[{?MODULE,Func,Args}|_]}, Func, Args, _Code) ->
%% ok;
pending({function_clause,Trace}, _, _, _) when is_list(Trace) ->
ok;
%% pending({Code,[{?MODULE,Func,Arity}|_]}, Func, Args, Code)
%% when length(Args) =:= Arity ->
%% ok;
pending({Code,Trace}, _, _, Code) when is_list(Trace) ->
ok;
pending(Reason, _Function, _Args, _Code) ->
exit({bad_exit_reason, Reason}).
%%--------------------------------------------------------------------
%% Taken from fun_SUITE.erl
%%
%% Checks correct exception throwing when calling a bad fun.
%%--------------------------------------------------------------------
test_bad_fun_call() ->
ok = bad_call_fc(42),
ok = bad_call_fc(xx),
ok = bad_call_fc({}),
ok = bad_call_fc({1}),
ok = bad_call_fc({1,2,3}),
ok = bad_call_fc({1,2,3}),
ok = bad_call_fc({1,2,3,4}),
ok = bad_call_fc({1,2,3,4,5,6}),
ok = bad_call_fc({1,2,3,4,5}),
ok = bad_call_fc({1,2}),
ok.
bad_call_fc(Fun) ->
Args = [some, stupid, args],
Res = (catch Fun(Fun(Args))),
case Res of
{'EXIT', {{badfun, Fun} ,_Where}} ->
ok; %% = io:format("~p(~p) -> ~p\n", [Fun, Args, Res]);
Other ->
io:format("~p(~p) -> ~p\n", [Fun, Args, Res]),
exit({bad_result, Other})
end.