From 6f5900f9b136ae9aa6b89877727f6dac9e782ea6 Mon Sep 17 00:00:00 2001 From: Richard Carlsson Date: Sun, 11 Dec 2011 17:43:28 +0100 Subject: Add option 'no_tty' to silent the default tty report --- lib/eunit/src/eunit.erl | 7 ++++-- lib/eunit/src/eunit_tty.erl | 59 ++++++++++++++++++++++++++------------------- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/lib/eunit/src/eunit.erl b/lib/eunit/src/eunit.erl index 95857e83c8..51846d73b3 100644 --- a/lib/eunit/src/eunit.erl +++ b/lib/eunit/src/eunit.erl @@ -139,7 +139,7 @@ test(Tests, Options) -> %% @private %% @doc See {@link test/2}. test(Server, Tests, Options) -> - Listeners = [eunit_tty:start(Options) | listeners(Options)], + Listeners = listeners(Options), Serial = eunit_serial:start(Listeners), case eunit_server:start_test(Server, Serial, Tests, Options) of {ok, Reference} -> test_run(Reference, Listeners); @@ -194,7 +194,10 @@ submit(Server, T, Options) -> eunit_server:start_test(Server, Dummy, T, Options). listeners(Options) -> - Ps = start_listeners(proplists:get_all_values(report, Options)), + %% note that eunit_tty must always run, because it sends the final + %% {result,...} message that the test_run() function is waiting for + Ls = [{eunit_tty, Options} | proplists:get_all_values(report, Options)], + Ps = start_listeners(Ls), %% the event_log option is for debugging, to view the raw events case proplists:get_value(event_log, Options) of undefined -> diff --git a/lib/eunit/src/eunit_tty.erl b/lib/eunit/src/eunit_tty.erl index e3e7b710b2..a8cec86ade 100644 --- a/lib/eunit/src/eunit_tty.erl +++ b/lib/eunit/src/eunit_tty.erl @@ -44,6 +44,7 @@ start(Options) -> init(Options) -> St = #state{verbose = proplists:get_bool(verbose, Options)}, + put(no_tty, proplists:get_bool(no_tty, Options)), receive {start, _Reference} -> if St#state.verbose -> print_header(); @@ -59,30 +60,30 @@ terminate({ok, Data}, St) -> Cancel = proplists:get_value(cancel, Data, 0), if Fail =:= 0, Skip =:= 0, Cancel =:= 0 -> if Pass =:= 0 -> - io:fwrite(" There were no tests to run.\n"); + fwrite(" There were no tests to run.\n"); true -> if St#state.verbose -> print_bar(); true -> ok end, if Pass =:= 1 -> - io:fwrite(" Test passed.\n"); + fwrite(" Test passed.\n"); true -> - io:fwrite(" All ~w tests passed.\n", [Pass]) + fwrite(" All ~w tests passed.\n", [Pass]) end end, sync_end(ok); true -> print_bar(), - io:fwrite(" Failed: ~w. Skipped: ~w. Passed: ~w.\n", - [Fail, Skip, Pass]), + fwrite(" Failed: ~w. Skipped: ~w. Passed: ~w.\n", + [Fail, Skip, Pass]), if Cancel =/= 0 -> - io:fwrite("One or more tests were cancelled.\n"); + fwrite("One or more tests were cancelled.\n"); true -> ok end, sync_end(error) end; terminate({error, Reason}, _St) -> - io:fwrite("Internal error: ~P.\n", [Reason, 25]), + fwrite("Internal error: ~P.\n", [Reason, 25]), sync_end(error). sync_end(Result) -> @@ -93,10 +94,10 @@ sync_end(Result) -> end. print_header() -> - io:fwrite("======================== EUnit ========================\n"). + fwrite("======================== EUnit ========================\n"). print_bar() -> - io:fwrite("=======================================================\n"). + fwrite("=======================================================\n"). handle_begin(group, Data, St) -> @@ -170,18 +171,18 @@ handle_cancel(test, Data, St) -> indent(N) when is_integer(N), N >= 1 -> - io:put_chars(lists:duplicate(N * 2, $\s)); + fwrite(lists:duplicate(N * 2, $\s)); indent(_N) -> ok. print_group_start(I, Desc) -> indent(I), - io:fwrite("~s\n", [Desc]). + fwrite("~s\n", [Desc]). print_group_end(I, Time) -> if Time > 0 -> indent(I), - io:fwrite("[done in ~.3f s]\n", [Time/1000]); + fwrite("[done in ~.3f s]\n", [Time/1000]); true -> ok end. @@ -198,9 +199,9 @@ print_test_begin(I, Data) -> end, case proplists:get_value(source, Data) of {Module, Name, _Arity} -> - io:fwrite("~s:~s ~s~s...", [Module, L, Name, D]); + fwrite("~s:~s ~s~s...", [Module, L, Name, D]); _ -> - io:fwrite("~s~s...", [L, D]) + fwrite("~s~s...", [L, D]) end. print_test_end(Data) -> @@ -208,36 +209,35 @@ print_test_end(Data) -> T = if Time > 0 -> io_lib:fwrite("[~.3f s] ", [Time/1000]); true -> "" end, - io:fwrite("~sok\n", [T]). + fwrite("~sok\n", [T]). print_test_error({error, Exception}, Data) -> Output = proplists:get_value(output, Data), - io:fwrite("*failed*\n::~s", - [eunit_lib:format_exception(Exception)]), + fwrite("*failed*\n::~s", [eunit_lib:format_exception(Exception)]), case Output of <<>> -> - io:put_chars("\n\n"); + fwrite("\n\n"); <> -> - io:fwrite(" output:<<\"~s\">>...\n\n", [Text]); + fwrite(" output:<<\"~s\">>...\n\n", [Text]); _ -> - io:fwrite(" output:<<\"~s\">>\n\n", [Output]) + fwrite(" output:<<\"~s\">>\n\n", [Output]) end; print_test_error({skipped, Reason}, _) -> - io:fwrite("*did not run*\n::~s\n", [format_skipped(Reason)]). + fwrite("*did not run*\n::~s\n", [format_skipped(Reason)]). format_skipped({module_not_found, M}) -> - io_lib:format("missing module: ~w", [M]); + io_lib:fwrite("missing module: ~w", [M]); format_skipped({no_such_function, {M,F,A}}) -> - io_lib:format("no such function: ~w:~w/~w", [M,F,A]). + io_lib:fwrite("no such function: ~w:~w/~w", [M,F,A]). print_test_cancel(Reason) -> - io:fwrite(format_cancel(Reason)). + fwrite(format_cancel(Reason)). print_group_cancel(_I, {blame, _}) -> ok; print_group_cancel(I, Reason) -> indent(I), - io:fwrite(format_cancel(Reason)). + fwrite(format_cancel(Reason)). format_cancel(undefined) -> "*skipped*\n"; @@ -253,3 +253,12 @@ format_cancel({exit, Reason}) -> [Reason, 15]); format_cancel({abort, Reason}) -> eunit_lib:format_error(Reason). + +fwrite(String) -> + fwrite(String, []). + +fwrite(String, Args) -> + case get(no_tty) of + false -> io:fwrite(String, Args); + true -> ok + end. -- cgit v1.2.3 From fea574bac8464f07a72862e1a4f92e9b62e464e4 Mon Sep 17 00:00:00 2001 From: Klas Johansson Date: Sun, 12 Feb 2012 18:24:53 +0100 Subject: Make EUnit print stacktraces with location information The format of stacktraces was changed in Erlang/OTP R15, adding location information. This had the effect that EUnit did not recognize stack traces as such and only printed the exception term. This patch makes Eunit recognize and print the new stacktrace format as well as the old. --- lib/eunit/src/eunit_lib.erl | 43 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/lib/eunit/src/eunit_lib.erl b/lib/eunit/src/eunit_lib.erl index 1c41e229c5..c700989bae 100644 --- a/lib/eunit/src/eunit_lib.erl +++ b/lib/eunit/src/eunit_lib.erl @@ -86,6 +86,12 @@ analyze_exit_term(Term) -> is_stacktrace([]) -> true; +is_stacktrace([{M,F,A,L}|Fs]) + when is_atom(M), is_atom(F), is_integer(A), is_list(L) -> + is_stacktrace(Fs); +is_stacktrace([{M,F,As,L}|Fs]) + when is_atom(M), is_atom(F), is_list(As), is_list(L) -> + is_stacktrace(Fs); is_stacktrace([{M,F,A}|Fs]) when is_atom(M), is_atom(F), is_integer(A) -> is_stacktrace(Fs); is_stacktrace([{M,F,As}|Fs]) when is_atom(M), is_atom(F), is_list(As) -> @@ -96,10 +102,11 @@ is_stacktrace(_) -> format_stacktrace(Trace) -> format_stacktrace(Trace, "in function", "in call from"). -format_stacktrace([{M,F,A}|Fs], Pre, Pre1) when is_integer(A) -> - [io_lib:fwrite(" ~s ~w:~w/~w\n", [Pre, M, F, A]) +format_stacktrace([{M,F,A,L}|Fs], Pre, Pre1) when is_integer(A) -> + [io_lib:fwrite(" ~s ~w:~w/~w~s\n", + [Pre, M, F, A, format_stacktrace_location(L)]) | format_stacktrace(Fs, Pre1, Pre1)]; -format_stacktrace([{M,F,As}|Fs], Pre, Pre1) when is_list(As) -> +format_stacktrace([{M,F,As,L}|Fs], Pre, Pre1) when is_list(As) -> A = length(As), C = case is_op(M,F,A) of true when A =:= 1 -> @@ -112,12 +119,23 @@ format_stacktrace([{M,F,As}|Fs], Pre, Pre1) when is_list(As) -> false -> io_lib:fwrite("~w(~s)", [F,format_arglist(As)]) end, - [io_lib:fwrite(" ~s ~w:~w/~w\n called as ~s\n", - [Pre,M,F,A,C]) + [io_lib:fwrite(" ~s ~w:~w/~w~s\n called as ~s\n", + [Pre,M,F,A,format_stacktrace_location(L),C]) | format_stacktrace(Fs,Pre1,Pre1)]; +format_stacktrace([{M,F,As}|Fs], Pre, Pre1) -> + format_stacktrace([{M,F,As,[]}|Fs], Pre, Pre1); format_stacktrace([],_Pre,_Pre1) -> "". +format_stacktrace_location(Location) -> + File = proplists:get_value(file, Location), + Line = proplists:get_value(line, Location), + if File =/= undefined, Line =/= undefined -> + io_lib:format(" (~s, line ~w)", [File, Line]); + true -> + "" + end. + format_arg(A) -> io_lib:format("~P",[A,15]). @@ -166,6 +184,21 @@ error_msg(Title, Fmt, Args) -> Msg = io_lib:format("::"++Fmt, Args), % gets indentation right io_lib:fwrite("*** ~s ***\n~s\n\n", [Title, Msg]). +-ifdef(TEST). +format_exception_test_() -> + [?_assertMatch( + "error:dummy"++_, + lists:flatten( + format_exception(try erlang:error(dummy) + catch C:R -> {C, R, erlang:get_stacktrace()} + end))), + ?_assertMatch( + "error:dummy"++_, + lists:flatten( + format_exception(try erlang:error(dummy, [a]) + catch C:R -> {C, R, erlang:get_stacktrace()} + end)))]. +-endif. %% --------------------------------------------------------------------- %% Deep list iterator; accepts improper lists/sublists, and also accepts -- cgit v1.2.3 From b91207f58f9c52ae09c42947029f28932e5991c5 Mon Sep 17 00:00:00 2001 From: Richard Carlsson Date: Mon, 13 Feb 2012 10:35:57 +0100 Subject: make stack trace pruning know about the new format --- lib/eunit/src/eunit_test.erl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/eunit/src/eunit_test.erl b/lib/eunit/src/eunit_test.erl index bca49ae626..3457b5ee21 100644 --- a/lib/eunit/src/eunit_test.erl +++ b/lib/eunit/src/eunit_test.erl @@ -43,8 +43,12 @@ get_stacktrace(Ts) -> prune_trace([{eunit_data, _, _} | Rest], Tail) -> prune_trace(Rest, Tail); +prune_trace([{eunit_data, _, _, _} | Rest], Tail) -> + prune_trace(Rest, Tail); prune_trace([{?MODULE, _, _} | _Rest], Tail) -> Tail; +prune_trace([{?MODULE, _, _, _} | _Rest], Tail) -> + Tail; prune_trace([T | Ts], Tail) -> [T | prune_trace(Ts, Tail)]; prune_trace([], Tail) -> -- cgit v1.2.3 From 975a3d513f3e112fa12b4de366ad54ac68b46b6e Mon Sep 17 00:00:00 2001 From: Richard Carlsson Date: Mon, 13 Feb 2012 20:49:42 +0100 Subject: bumped revision --- lib/eunit/vsn.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/eunit/vsn.mk b/lib/eunit/vsn.mk index 445c070e96..174d197117 100644 --- a/lib/eunit/vsn.mk +++ b/lib/eunit/vsn.mk @@ -1 +1 @@ -EUNIT_VSN = 2.2.2 +EUNIT_VSN = 2.2.3 -- cgit v1.2.3 From dab05b72fa5cfd383f1eac5525ac671a15c5cbf6 Mon Sep 17 00:00:00 2001 From: Richard Carlsson Date: Wed, 15 Feb 2012 16:43:31 +0100 Subject: improved layout of error messages - stack trace before error term --- lib/eunit/src/eunit_lib.erl | 23 ++++++++++++----------- lib/eunit/src/eunit_tty.erl | 4 ++-- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/lib/eunit/src/eunit_lib.erl b/lib/eunit/src/eunit_lib.erl index c700989bae..5d7025f038 100644 --- a/lib/eunit/src/eunit_lib.erl +++ b/lib/eunit/src/eunit_lib.erl @@ -59,8 +59,9 @@ format_exception({Class,Term,Trace}, Depth) when is_atom(Class), is_list(Trace) -> case is_stacktrace(Trace) of true -> - io_lib:format("~w:~P\n~s", - [Class, Term, Depth, format_stacktrace(Trace)]); + io_lib:format("~s**~w:~s", + [format_stacktrace(Trace), Class, + format_term(Term, Depth)]); false -> format_term(Term, Depth) end; @@ -103,7 +104,7 @@ format_stacktrace(Trace) -> format_stacktrace(Trace, "in function", "in call from"). format_stacktrace([{M,F,A,L}|Fs], Pre, Pre1) when is_integer(A) -> - [io_lib:fwrite(" ~s ~w:~w/~w~s\n", + [io_lib:fwrite("~s ~w:~w/~w~s\n", [Pre, M, F, A, format_stacktrace_location(L)]) | format_stacktrace(Fs, Pre1, Pre1)]; format_stacktrace([{M,F,As,L}|Fs], Pre, Pre1) when is_list(As) -> @@ -119,7 +120,7 @@ format_stacktrace([{M,F,As,L}|Fs], Pre, Pre1) when is_list(As) -> false -> io_lib:fwrite("~w(~s)", [F,format_arglist(As)]) end, - [io_lib:fwrite(" ~s ~w:~w/~w~s\n called as ~s\n", + [io_lib:fwrite("~s ~w:~w/~w~s\n called as ~s\n", [Pre,M,F,A,format_stacktrace_location(L),C]) | format_stacktrace(Fs,Pre1,Pre1)]; format_stacktrace([{M,F,As}|Fs], Pre, Pre1) -> @@ -181,23 +182,23 @@ format_error({instantiation_failed, Exception}) -> [format_exception(Exception)]). error_msg(Title, Fmt, Args) -> - Msg = io_lib:format("::"++Fmt, Args), % gets indentation right + Msg = io_lib:format("**"++Fmt, Args), % gets indentation right io_lib:fwrite("*** ~s ***\n~s\n\n", [Title, Msg]). -ifdef(TEST). format_exception_test_() -> [?_assertMatch( - "error:dummy"++_, - lists:flatten( + "\nymmud:rorre"++_, + lists:reverse(lists:flatten( format_exception(try erlang:error(dummy) catch C:R -> {C, R, erlang:get_stacktrace()} - end))), + end)))), ?_assertMatch( - "error:dummy"++_, - lists:flatten( + "\nymmud:rorre"++_, + lists:reverse(lists:flatten( format_exception(try erlang:error(dummy, [a]) catch C:R -> {C, R, erlang:get_stacktrace()} - end)))]. + end))))]. -endif. %% --------------------------------------------------------------------- diff --git a/lib/eunit/src/eunit_tty.erl b/lib/eunit/src/eunit_tty.erl index a8cec86ade..f21b2da3d3 100644 --- a/lib/eunit/src/eunit_tty.erl +++ b/lib/eunit/src/eunit_tty.erl @@ -213,7 +213,7 @@ print_test_end(Data) -> print_test_error({error, Exception}, Data) -> Output = proplists:get_value(output, Data), - fwrite("*failed*\n::~s", [eunit_lib:format_exception(Exception)]), + fwrite("*failed*\n~s", [eunit_lib:format_exception(Exception)]), case Output of <<>> -> fwrite("\n\n"); @@ -228,7 +228,7 @@ print_test_error({skipped, Reason}, _) -> format_skipped({module_not_found, M}) -> io_lib:fwrite("missing module: ~w", [M]); format_skipped({no_such_function, {M,F,A}}) -> - io_lib:fwrite("no such function: ~w:~w/~w", [M,F,A]). + io_lib:fwrite("no such function: ~w:~w/~w", [M,F,A]). print_test_cancel(Reason) -> fwrite(format_cancel(Reason)). -- cgit v1.2.3 From e2aaefc509b52eaeb0416ecc603806f1416c17ea Mon Sep 17 00:00:00 2001 From: Richard Carlsson Date: Tue, 17 Apr 2012 14:00:24 +0200 Subject: eunit_tests should not be listed in eunit.app --- lib/eunit/src/eunit.app.src | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/eunit/src/eunit.app.src b/lib/eunit/src/eunit.app.src index 5e16dfa2ce..431abac98b 100644 --- a/lib/eunit/src/eunit.app.src +++ b/lib/eunit/src/eunit.app.src @@ -14,7 +14,6 @@ eunit_striptests, eunit_surefire, eunit_test, - eunit_tests, eunit_tty]}, {registered,[]}, {applications, [kernel,stdlib]}, -- cgit v1.2.3 From 1a90915b4305afb9ee097e03c8a40b3ff5f8e33d Mon Sep 17 00:00:00 2001 From: Richard Carlsson Date: Tue, 17 Apr 2012 15:21:05 +0200 Subject: detect and report bad return values from generators and instantiators --- lib/eunit/src/eunit_data.erl | 21 ++++++++++++++++----- lib/eunit/src/eunit_lib.erl | 39 +++++++++++++++++++++++++++++++++++---- lib/eunit/src/eunit_test.erl | 22 ++++++++++++++++------ 3 files changed, 67 insertions(+), 15 deletions(-) diff --git a/lib/eunit/src/eunit_data.erl b/lib/eunit/src/eunit_data.erl index 392d378a0e..da66864439 100644 --- a/lib/eunit/src/eunit_data.erl +++ b/lib/eunit/src/eunit_data.erl @@ -156,7 +156,8 @@ iter_prev(#iter{prev = [T | Ts]} = I) -> %% @spec (tests()) -> none | {testItem(), tests()} %% @type testItem() = #test{} | #group{} %% @throws {bad_test, term()} -%% | {generator_failed, exception()} +%% | {generator_failed, {{M::atom(),F::atom(),A::integer()}, +%% exception()}} %% | {no_such_function, eunit_lib:mfa()} %% | {module_not_found, moduleName()} %% | {application_not_found, appName()} @@ -221,17 +222,27 @@ parse({foreachx, P, S1, C1, Ps} = T) [] -> {data, []} end; -parse({generator, F} = T) when is_function(F) -> +parse({generator, F}) when is_function(F) -> + {module, M} = erlang:fun_info(F, module), + {name, N} = erlang:fun_info(F, name), + {arity, A} = erlang:fun_info(F, arity), + parse({generator, F, {M,N,A}}); +parse({generator, F, {M,N,A}} = T) + when is_function(F), is_atom(M), is_atom(N), is_integer(A) -> check_arity(F, 0, T), %% use run_testfun/1 to handle wrapper exceptions case eunit_test:run_testfun(F) of {ok, T1} -> + case eunit_lib:is_not_test(T1) of + true -> throw({bad_generator, {{M,N,A}, T1}}); + false -> ok + end, {data, T1}; {error, {Class, Reason, Trace}} -> - throw({generator_failed, {Class, Reason, Trace}}) + throw({generator_failed, {{M,N,A}, {Class, Reason, Trace}}}) end; parse({generator, M, F}) when is_atom(M), is_atom(F) -> - parse({generator, eunit_test:function_wrapper(M, F)}); + parse({generator, eunit_test:mf_wrapper(M, F), {M,F,0}}); parse({inorder, T}) -> group(#group{tests = T, order = inorder}); parse({inparallel, T}) -> @@ -422,7 +433,7 @@ parse_function(F) when is_function(F) -> check_arity(F, 0, F), #test{f = F, location = eunit_lib:fun_parent(F)}; parse_function({M,F}) when is_atom(M), is_atom(F) -> - #test{f = eunit_test:function_wrapper(M, F), location = {M, F, 0}}; + #test{f = eunit_test:mf_wrapper(M, F), location = {M, F, 0}}; parse_function(F) -> bad_test(F). diff --git a/lib/eunit/src/eunit_lib.erl b/lib/eunit/src/eunit_lib.erl index 5d7025f038..f0fd26eb5d 100644 --- a/lib/eunit/src/eunit_lib.erl +++ b/lib/eunit/src/eunit_lib.erl @@ -30,7 +30,8 @@ -export([dlist_next/1, uniq/1, fun_parent/1, is_string/1, command/1, command/2, command/3, trie_new/0, trie_store/2, trie_match/2, split_node/1, consult_file/1, list_dir/1, format_exit_term/1, - format_exception/1, format_exception/2, format_error/1]). + format_exception/1, format_exception/2, format_error/1, + is_not_test/1]). %% Type definitions for describing exceptions @@ -158,9 +159,13 @@ is_op(_M, _F, _A) -> format_error({bad_test, Term}) -> error_msg("bad test descriptor", "~P", [Term, 15]); -format_error({generator_failed, Exception}) -> - error_msg("test generator failed", "~s", - [format_exception(Exception)]); +format_error({bad_generator, {{M,F,A}, Term}}) -> + error_msg(io_lib:format("result from generator ~w:~w/~w is not a test", + [M,F,A]), + "~P", [Term, 15]); +format_error({generator_failed, {{M,F,A}, Exception}}) -> + error_msg(io_lib:format("test generator ~w:~w/~w failed",[M,F,A]), + "~s", [format_exception(Exception)]); format_error({no_such_function, {M,F,A}}) when is_atom(M), is_atom(F), is_integer(A) -> error_msg(io_lib:format("no such function: ~w:~w/~w", [M,F,A]), @@ -177,6 +182,10 @@ format_error({setup_failed, Exception}) -> format_error({cleanup_failed, Exception}) -> error_msg("context cleanup failed", "~s", [format_exception(Exception)]); +format_error({{bad_instantiator, {{M,F,A}, Term}}, _DummyException}) -> + error_msg(io_lib:format("result from instantiator ~w:~w/~w is not a test", + [M,F,A]), + "~P", [Term, 15]); format_error({instantiation_failed, Exception}) -> error_msg("instantiation of subtests failed", "~s", [format_exception(Exception)]). @@ -201,6 +210,28 @@ format_exception_test_() -> end))))]. -endif. +%% --------------------------------------------------------------------- +%% detect common return values that are definitely not tests + +is_not_test(T) -> + case T of + ok -> true; + error -> true; + true -> true; + false -> true; + undefined -> true; + {ok, _} -> true; + {error, _} -> true; + {'EXIT', _} -> true; + N when is_number(N) -> true; + [N|_] when is_number(N) -> true; + X when is_binary(X) -> true; + X when is_pid(X) -> true; + X when is_port(X) -> true; + X when is_reference(X) -> true; + _ -> false + end. + %% --------------------------------------------------------------------- %% Deep list iterator; accepts improper lists/sublists, and also accepts %% non-lists on the top level. Nonempty strings (not deep strings) are diff --git a/lib/eunit/src/eunit_test.erl b/lib/eunit/src/eunit_test.erl index 3457b5ee21..9cf40a738d 100644 --- a/lib/eunit/src/eunit_test.erl +++ b/lib/eunit/src/eunit_test.erl @@ -21,8 +21,7 @@ -module(eunit_test). --export([run_testfun/1, function_wrapper/2, enter_context/4, - multi_setup/1]). +-export([run_testfun/1, mf_wrapper/2, enter_context/4, multi_setup/1]). -include("eunit.hrl"). @@ -262,7 +261,7 @@ macro_test_() -> %% @type wrapperError() = {no_such_function, mfa()} %% | {module_not_found, moduleName()} -function_wrapper(M, F) -> +mf_wrapper(M, F) -> fun () -> try M:F() catch @@ -293,12 +292,12 @@ fail(Term) -> wrapper_test_() -> {"error handling in function wrapper", [?_assertException(throw, {module_not_found, eunit_nonexisting}, - run_testfun(function_wrapper(eunit_nonexisting,test))), + run_testfun(mf_wrapper(eunit_nonexisting,test))), ?_assertException(throw, {no_such_function, {?MODULE,nonexisting_test,0}}, - run_testfun(function_wrapper(?MODULE,nonexisting_test))), + run_testfun(mf_wrapper(?MODULE,nonexisting_test))), ?_test({error, {error, undef, _T}} - = run_testfun(function_wrapper(?MODULE,wrapper_test_exported_))) + = run_testfun(mf_wrapper(?MODULE,wrapper_test_exported_))) ]}. %% this must be exported (done automatically by the autoexport transform) @@ -323,6 +322,17 @@ enter_context(Setup, Cleanup, Instantiate, Callback) -> R -> try Instantiate(R) of T -> + case eunit_lib:is_not_test(T) of + true -> + catch throw(error), % generate a stack trace + {module,M} = erlang:fun_info(Instantiate, module), + {name,N} = erlang:fun_info(Instantiate, name), + {arity,A} = erlang:fun_info(Instantiate, arity), + context_error({bad_instantiator, {{M,N,A},T}}, + error, badarg); + false -> + ok + end, try Callback(T) %% call back to client code after %% Always run cleanup; client may be an idiot -- cgit v1.2.3 From c4065f1b503d97cd05e9c5c791ec387a93875021 Mon Sep 17 00:00:00 2001 From: Lukas Larsson Date: Wed, 30 May 2012 17:16:16 +0200 Subject: Write chars as UTF-8 to file --- lib/eunit/src/eunit_surefire.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/eunit/src/eunit_surefire.erl b/lib/eunit/src/eunit_surefire.erl index 2a6cbca14d..69f471d130 100644 --- a/lib/eunit/src/eunit_surefire.erl +++ b/lib/eunit/src/eunit_surefire.erl @@ -232,7 +232,7 @@ write_reports(TestSuites, XmlDir) -> write_report(#testsuite{name = Name} = TestSuite, XmlDir) -> Filename = filename:join(XmlDir, lists:flatten(["TEST-", escape_suitename(Name)], ".xml")), - case file:open(Filename, [write, raw]) of + case file:open(Filename, [write,{encoding,utf8}]) of {ok, FileDescriptor} -> try write_report_to(TestSuite, FileDescriptor) -- cgit v1.2.3 From 99070064280a7141220fac2ccad8e73acde16091 Mon Sep 17 00:00:00 2001 From: Richard Carlsson Date: Thu, 5 Jul 2012 19:15:58 +0200 Subject: minor cleanup --- lib/eunit/include/eunit.hrl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/eunit/include/eunit.hrl b/lib/eunit/include/eunit.hrl index db68d8ae60..fba840c3bd 100644 --- a/lib/eunit/include/eunit.hrl +++ b/lib/eunit/include/eunit.hrl @@ -25,11 +25,12 @@ %% will become undefined. NODEBUG also implies NOASSERT, unless testing %% is enabled. %% -%% If including this file causes TEST to be defined, then NOASSERT will -%% be undefined, even if it was previously defined and even if NODEBUG -%% is defined. If both ASSERT and NOASSERT are defined before the file -%% is included, then ASSERT takes precedence, and NOASSERT will become -%% undefined regardless of TEST. +%% Defining NOASSERT disables asserts. NODEBUG implies NOASSERT unless +%% testing is enabled. If including this file causes TEST to be defined, +%% then NOASSERT will be undefined, even if it was previously defined and +%% even if NODEBUG is defined. If both ASSERT and NOASSERT are defined +%% before the file is included, then ASSERT takes precedence, and NOASSERT +%% will become undefined regardless of TEST. %% %% After including this file, EUNIT will be defined if and only if TEST %% is defined. @@ -127,9 +128,9 @@ current_function)))). -endif. --ifdef(NOASSERT). %% The plain assert macro should be defined to do nothing if this file %% is included when debugging/testing is turned off. +-ifdef(NOASSERT). -ifndef(assert). -define(assert(BoolExpr),ok). -endif. -- cgit v1.2.3 From 7b1897b9cf0ef125a567004745b211698a976bd9 Mon Sep 17 00:00:00 2001 From: Richard Carlsson Date: Tue, 17 Jul 2012 18:27:29 +0200 Subject: add {test,M,F} as a better variant of {M,F} and make the latter obsolete --- lib/eunit/doc/overview.edoc | 7 +++++-- lib/eunit/src/eunit_data.erl | 15 +++++++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/eunit/doc/overview.edoc b/lib/eunit/doc/overview.edoc index ad449cb6fc..42d70a6963 100644 --- a/lib/eunit/doc/overview.edoc +++ b/lib/eunit/doc/overview.edoc @@ -723,8 +723,11 @@ A simple test object is one of the following: ```fun some_function/0''' ```fun some_module:some_function/0''' -
  • A pair of atoms `{ModuleName, FunctionName}', referring to the - function `ModuleName:FunctionName/0'
  • +
  • A tuple `{test, ModuleName, FunctionName}', where `ModuleName' and + `FunctionName' are atoms, referring to the function + `ModuleName:FunctionName/0'
  • +
  • Obsolete: a pair of atoms `{ModuleName, FunctionName}', equivalent to + `{test, ModuleName, FunctionName}' if nothing else matches first.
  • A pair `{LineNumber, SimpleTest}', where `LineNumber' is a nonnegative integer and `SimpleTest' is another simple test object. `LineNumber' should indicate the source line of the test. diff --git a/lib/eunit/src/eunit_data.erl b/lib/eunit/src/eunit_data.erl index da66864439..0222becb18 100644 --- a/lib/eunit/src/eunit_data.erl +++ b/lib/eunit/src/eunit_data.erl @@ -83,6 +83,7 @@ %% SimpleTest = TestFunction | {Line::integer(), SimpleTest} %% %% TestFunction = () -> any() +%% | {test, M::moduleName(), F::functionName()} %% | {M::moduleName(), F::functionName()}. %% %% AbstractTestFunction = (X::any()) -> any() @@ -432,8 +433,11 @@ parse_simple(F) -> parse_function(F) when is_function(F) -> check_arity(F, 0, F), #test{f = F, location = eunit_lib:fun_parent(F)}; -parse_function({M,F}) when is_atom(M), is_atom(F) -> +parse_function({test, M, F}) when is_atom(M), is_atom(F) -> #test{f = eunit_test:mf_wrapper(M, F), location = {M, F, 0}}; +parse_function({M, F}) when is_atom(M), is_atom(F) -> + %% {M,F} is now considered obsolete; use {test,M,F} instead + parse_function({test, M, F}); parse_function(F) -> bad_test(F). @@ -591,7 +595,7 @@ testfuns(Es, M, TestSuffix, GeneratorSuffix) -> N = atom_to_list(F), case lists:suffix(TestSuffix, N) of true -> - [{M,F} | Fs]; + [{test, M, F} | Fs]; false -> case lists:suffix(GeneratorSuffix, N) of true -> @@ -734,6 +738,7 @@ data_test_() -> Tests = [T,T,T], [?_assertMatch(ok, eunit:test(T)), ?_assertMatch(error, eunit:test(Fail)), + ?_assertMatch(ok, eunit:test({test, ?MODULE, trivial_test})), ?_assertMatch(ok, eunit:test({generator, fun () -> Tests end})), ?_assertMatch(ok, eunit:test({generator, fun generator/0})), ?_assertMatch(ok, eunit:test({generator, ?MODULE, generator_exported_})), @@ -751,6 +756,12 @@ data_test_() -> %%?_test({foreach, Setup, [T, T, T]}) ]. +trivial_test() -> + ok. + +trivial_generator_test_() -> + [?_test(ok)]. + lazy_test_() -> {spawn, [?_test(undefined = put(count, 0)), lazy_gen(7), -- cgit v1.2.3 From 5f9ad541eb520162d86bdc5bcf58e235ced7e786 Mon Sep 17 00:00:00 2001 From: Richard Carlsson Date: Tue, 17 Jul 2012 18:32:31 +0200 Subject: documentation fixes --- lib/eunit/doc/overview.edoc | 5 +++-- lib/eunit/src/eunit_data.erl | 3 +-- lib/eunit/src/eunit_lib.erl | 5 +---- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/eunit/doc/overview.edoc b/lib/eunit/doc/overview.edoc index 42d70a6963..b4af31ae6a 100644 --- a/lib/eunit/doc/overview.edoc +++ b/lib/eunit/doc/overview.edoc @@ -726,8 +726,9 @@ A simple test object is one of the following:
  • A tuple `{test, ModuleName, FunctionName}', where `ModuleName' and `FunctionName' are atoms, referring to the function `ModuleName:FunctionName/0'
  • -
  • Obsolete: a pair of atoms `{ModuleName, FunctionName}', equivalent to - `{test, ModuleName, FunctionName}' if nothing else matches first.
  • +
  • (Obsolete) A pair of atoms `{ModuleName, FunctionName}', equivalent to + `{test, ModuleName, FunctionName}' if nothing else matches first. This + might be removed in a future version.
  • A pair `{LineNumber, SimpleTest}', where `LineNumber' is a nonnegative integer and `SimpleTest' is another simple test object. `LineNumber' should indicate the source line of the test. diff --git a/lib/eunit/src/eunit_data.erl b/lib/eunit/src/eunit_data.erl index 0222becb18..0350f9bf6e 100644 --- a/lib/eunit/src/eunit_data.erl +++ b/lib/eunit/src/eunit_data.erl @@ -96,7 +96,6 @@ %% %% @type moduleName() = atom() %% @type functionName() = atom() -%% @type arity() = integer() %% @type appName() = atom() %% @type fileName() = string() @@ -159,7 +158,7 @@ iter_prev(#iter{prev = [T | Ts]} = I) -> %% @throws {bad_test, term()} %% | {generator_failed, {{M::atom(),F::atom(),A::integer()}, %% exception()}} -%% | {no_such_function, eunit_lib:mfa()} +%% | {no_such_function, mfa()} %% | {module_not_found, moduleName()} %% | {application_not_found, appName()} %% | {file_read_error, {Reason::atom(), Message::string(), diff --git a/lib/eunit/src/eunit_lib.erl b/lib/eunit/src/eunit_lib.erl index f0fd26eb5d..ea9e944d7e 100644 --- a/lib/eunit/src/eunit_lib.erl +++ b/lib/eunit/src/eunit_lib.erl @@ -40,13 +40,10 @@ %% %% @type exceptionClass() = error | exit | throw %% -%% @type stackTrace() = [{moduleName(), functionName(), -%% arity() | argList()}] +%% @type stackTrace() = [{moduleName(), functionName(), arity() | argList()}] %% %% @type moduleName() = atom() %% @type functionName() = atom() -%% @type arity() = integer() -%% @type mfa() = {moduleName(), functionName(), arity()} %% @type argList() = [term()] %% @type fileName() = string() -- cgit v1.2.3 From 95a78de4810ca25f7175d8eb7bb8f05e38034122 Mon Sep 17 00:00:00 2001 From: Magnus Henoch Date: Thu, 10 Mar 2011 10:18:37 +0000 Subject: Include fixture setup and cleanup errors in Eunit Surefire report An error during fixture setup means that some tests could not be run, and therefore needs to be highlighted in the test report. Likewise, a cleanup failure is often a problem that needs to be looked into. Since setup and cleanup are not part of any single test in Eunit's view, I include them as phantom test cases in the report whenever they fail. --- lib/eunit/src/eunit_surefire.erl | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/lib/eunit/src/eunit_surefire.erl b/lib/eunit/src/eunit_surefire.erl index 69f471d130..46b8c8b503 100644 --- a/lib/eunit/src/eunit_surefire.erl +++ b/lib/eunit/src/eunit_surefire.erl @@ -156,9 +156,33 @@ handle_end(test, Data, St) -> St#state{testsuites=store_suite(NewTestSuite, TestSuites)}. %% Cancel group does not give information on the individual cancelled test case -%% We ignore this event -handle_cancel(group, _Data, St) -> - St; +%% We ignore this event... +handle_cancel(group, Data, St) -> + %% ...except when it tells us that a fixture setup or cleanup failed. + case proplists:get_value(reason, Data) of + {abort, {SomethingFailed, Exception}} + when SomethingFailed =:= setup_failed; + SomethingFailed =:= cleanup_failed -> + [GroupId|_] = proplists:get_value(id, Data), + TestSuites = St#state.testsuites, + TestSuite = lookup_suite_by_group_id(GroupId, TestSuites), + + %% We don't have any proper name. Let's give all the + %% clues that we have. + Name = case SomethingFailed of + setup_failed -> "fixture setup "; + cleanup_failed -> "fixture cleanup " + end + ++ io_lib:format("~p", [proplists:get_value(id, Data)]), + Desc = format_desc(proplists:get_value(desc, Data)), + TestCase = #testcase{ + name = Name, description = Desc, + time = 0, output = <<>>}, + NewTestSuite = add_testcase_to_testsuite({error, Exception}, TestCase, TestSuite), + St#state{testsuites=store_suite(NewTestSuite, TestSuites)}; + _ -> + St + end; handle_cancel(test, Data, St) -> %% Retrieve existing test suite: [GroupId|_] = proplists:get_value(id, Data), -- cgit v1.2.3