diff options
Diffstat (limited to 'lib/eunit/src/eunit_test.erl')
-rw-r--r-- | lib/eunit/src/eunit_test.erl | 320 |
1 files changed, 320 insertions, 0 deletions
diff --git a/lib/eunit/src/eunit_test.erl b/lib/eunit/src/eunit_test.erl new file mode 100644 index 0000000000..d322c4b420 --- /dev/null +++ b/lib/eunit/src/eunit_test.erl @@ -0,0 +1,320 @@ +%% This library is free software; you can redistribute it and/or modify +%% it under the terms of the GNU Lesser General Public License as +%% published by the Free Software Foundation; either version 2 of the +%% License, or (at your option) any later version. +%% +%% This library is distributed in the hope that it will be useful, but +%% WITHOUT ANY WARRANTY; without even the implied warranty of +%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%% Lesser General Public License for more details. +%% +%% You should have received a copy of the GNU Lesser General Public +%% License along with this library; if not, write to the Free Software +%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +%% USA +%% +%% $Id: eunit_test.erl 336 2009-03-06 14:43:21Z rcarlsson $ +%% +%% @author Richard Carlsson <[email protected]> +%% @copyright 2006 Richard Carlsson +%% @private +%% @see eunit +%% @doc Test running functionality + +-module(eunit_test). + +-export([run_testfun/1, function_wrapper/2, enter_context/4, + multi_setup/1]). + + +-include("eunit.hrl"). +-include("eunit_internal.hrl"). + + +%% --------------------------------------------------------------------- +%% Getting a cleaned up stack trace. (We don't want it to include +%% eunit's own internal functions. This complicates self-testing +%% somewhat, but you can't have everything.) Note that we assume that +%% this particular module is the boundary between eunit and user code. + +get_stacktrace() -> + get_stacktrace([]). + +get_stacktrace(Ts) -> + eunit_lib:uniq(prune_trace(erlang:get_stacktrace(), Ts)). + +prune_trace([{eunit_data, _, _} | Rest], Tail) -> + prune_trace(Rest, Tail); +prune_trace([{?MODULE, _, _} | _Rest], Tail) -> + Tail; +prune_trace([T | Ts], Tail) -> + [T | prune_trace(Ts, Tail)]; +prune_trace([], Tail) -> + Tail. + + +%% --------------------------------------------------------------------- +%% Test runner + +%% @spec ((any()) -> any()) -> {ok, Value} | {error, eunit_lib:exception()} +%% @throws wrapperError() + +run_testfun(F) -> + try + F() + of Value -> + {ok, Value} + catch + {eunit_internal, Term} -> + %% Internally generated: re-throw Term (lose the trace) + throw(Term); + Class:Reason -> + {error, {Class, Reason, get_stacktrace()}} + end. + + +-ifdef(TEST). +macro_test_() -> + {"macro definitions", + [{?LINE, fun () -> + {?LINE, F} = ?_test(undefined), + {ok, undefined} = run_testfun(F) + end}, + ?_test(begin + {?LINE, F} = ?_assert(true), + {ok, ok} = run_testfun(F) + end), + ?_test(begin + {?LINE, F} = ?_assert(false), + {error,{error,{assertion_failed, + [{module,_}, + {line,_}, + {expression,_}, + {expected,true}, + {value,false}]}, + _}} + = run_testfun(F) + end), + ?_test(begin + {?LINE, F} = ?_assert([]), + {error,{error,{assertion_failed, + [{module,_}, + {line,_}, + {expression,_}, + {expected,true}, + {value,{not_a_boolean,[]}}]}, + _}} + = run_testfun(F) + end), + ?_test(begin + {?LINE, F} = ?_assertNot(false), + {ok, ok} = run_testfun(F) + end), + ?_test(begin + {?LINE, F} = ?_assertNot(true), + {error,{error,{assertion_failed, + [{module,_}, + {line,_}, + {expression,_}, + {expected,true}, + {value,false}]}, + _}} + = run_testfun(F) + end), + ?_test(begin + {?LINE, F} = ?_assertMatch(ok, ok), + {ok, ok} = run_testfun(F) + end), + ?_test(begin + {?LINE, F} = ?_assertMatch([_], []), + {error,{error,{assertMatch_failed, + [{module,_}, + {line,_}, + {expression,_}, + {expected,"[ _ ]"}, + {value,[]}]}, + _}} + = run_testfun(F) + end), + ?_test(begin + {?LINE, F} = ?_assertEqual(ok, ok), + {ok, ok} = run_testfun(F) + end), + ?_test(begin + {?LINE, F} = ?_assertEqual(3, 1+1), + {error,{error,{assertEqual_failed, + [{module,_}, + {line,_}, + {expression,_}, + {expected,3}, + {value,2}]}, + _}} + = run_testfun(F) + end), + ?_test(begin + {?LINE, F} = ?_assertException(error, badarith, + erlang:error(badarith)), + {ok, ok} = run_testfun(F) + end), + ?_test(begin + {?LINE, F} = ?_assertException(error, badarith, ok), + {error,{error,{assertException_failed, + [{module,_}, + {line,_}, + {expression,_}, + {expected,_}, + {unexpected_success,ok}]}, + _}} + = run_testfun(F) + end), + ?_test(begin + {?LINE, F} = ?_assertException(error, badarg, + erlang:error(badarith)), + {error,{error,{assertException_failed, + [{module,_}, + {line,_}, + {expression,_}, + {expected,_}, + {unexpected_exception, + {error,badarith,_}}]}, + _}} + = run_testfun(F) + end) + ]}. + +under_eunit_test() -> ?assert(?UNDER_EUNIT). +-endif. + + +%% --------------------------------------------------------------------- +%% Wrapper for simple "named function" tests ({M,F}), which provides +%% better error reporting when the function is missing at test time. +%% +%% Note that the wrapper fun is usually called by run_testfun/1, and the +%% special exceptions thrown here are expected to be handled there. +%% +%% @throws {eunit_internal, wrapperError()} +%% +%% @type wrapperError() = {no_such_function, mfa()} +%% | {module_not_found, moduleName()} + +function_wrapper(M, F) -> + fun () -> + try M:F() + catch + error:undef -> + %% Check if it was M:F/0 that was undefined + case erlang:module_loaded(M) of + false -> + fail({module_not_found, M}); + true -> + case erlang:function_exported(M, F, 0) of + false -> + fail({no_such_function, {M,F,0}}); + true -> + rethrow(error, undef, [{M,F,0}]) + end + end + end + end. + +rethrow(Class, Reason, Trace) -> + erlang:raise(Class, Reason, get_stacktrace(Trace)). + +fail(Term) -> + throw({eunit_internal, Term}). + + +-ifdef(TEST). +wrapper_test_() -> + {"error handling in function wrapper", + [?_assertException(throw, {module_not_found, eunit_nonexisting}, + run_testfun(function_wrapper(eunit_nonexisting,test))), + ?_assertException(throw, + {no_such_function, {?MODULE,nonexisting_test,0}}, + run_testfun(function_wrapper(?MODULE,nonexisting_test))), + ?_test({error, {error, undef, _T}} + = run_testfun(function_wrapper(?MODULE,wrapper_test_exported_))) + ]}. + +%% this must be exported (done automatically by the autoexport transform) +wrapper_test_exported_() -> + {ok, ?MODULE:nonexisting_function()}. +-endif. + + +%% --------------------------------------------------------------------- +%% Entering a setup-context, with guaranteed cleanup. + +%% @spec (Setup, Cleanup, Instantiate, Callback) -> any() +%% Setup = () -> any() +%% Cleanup = (any()) -> any() +%% Instantiate = (any()) -> tests() +%% Callback = (tests()) -> any() +%% @throws {context_error, Error, eunit_lib:exception()} +%% Error = setup_failed | instantiation_failed | cleanup_failed + +enter_context(Setup, Cleanup, Instantiate, Callback) -> + try Setup() of + R -> + try Instantiate(R) of + T -> + try Callback(T) %% call back to client code + after + %% Always run cleanup; client may be an idiot + try Cleanup(R) + catch + Class:Term -> + context_error(cleanup_failed, Class, Term) + end + end + catch + Class:Term -> + context_error(instantiation_failed, Class, Term) + end + catch + Class:Term -> + context_error(setup_failed, Class, Term) + end. + +context_error(Type, Class, Term) -> + throw({context_error, Type, {Class, Term, get_stacktrace()}}). + +%% This generates single setup/cleanup functions from a list of tuples +%% on the form {Tag, Setup, Cleanup}, where the setup function always +%% backs out correctly from partial completion. + +multi_setup(List) -> + {SetupAll, CleanupAll} = multi_setup(List, fun ok/1), + %% must reverse back and forth here in order to present the list in + %% "natural" order to the test instantiation function + {fun () -> lists:reverse(SetupAll([])) end, + fun (Rs) -> CleanupAll(lists:reverse(Rs)) end}. + +multi_setup([{Tag, S, C} | Es], CleanupPrev) -> + Cleanup = fun ([R | Rs]) -> + try C(R) of + _ -> CleanupPrev(Rs) + catch + Class:Term -> + throw({Tag, {Class, Term, get_stacktrace()}}) + end + end, + {SetupRest, CleanupAll} = multi_setup(Es, Cleanup), + {fun (Rs) -> + try S() of + R -> + SetupRest([R|Rs]) + catch + Class:Term -> + CleanupPrev(Rs), + throw({Tag, {Class, Term, get_stacktrace()}}) + end + end, + CleanupAll}; +multi_setup([{Tag, S} | Es], CleanupPrev) -> + multi_setup([{Tag, S, fun ok/1} | Es], CleanupPrev); +multi_setup([], CleanupAll) -> + {fun (Rs) -> Rs end, CleanupAll}. + +ok(_) -> ok. |