diff options
Diffstat (limited to 'lib/eunit')
-rw-r--r-- | lib/eunit/doc/overview.edoc | 8 | ||||
-rw-r--r-- | lib/eunit/doc/src/notes.xml | 34 | ||||
-rw-r--r-- | lib/eunit/include/eunit.hrl | 170 | ||||
-rw-r--r-- | lib/eunit/src/Makefile | 4 | ||||
-rw-r--r-- | lib/eunit/src/eunit.app.src | 1 | ||||
-rw-r--r-- | lib/eunit/src/eunit.erl | 12 | ||||
-rw-r--r-- | lib/eunit/src/eunit_autoexport.erl | 7 | ||||
-rw-r--r-- | lib/eunit/src/eunit_data.erl | 39 | ||||
-rw-r--r-- | lib/eunit/src/eunit_lib.erl | 99 | ||||
-rw-r--r-- | lib/eunit/src/eunit_surefire.erl | 37 | ||||
-rw-r--r-- | lib/eunit/src/eunit_test.erl | 26 | ||||
-rw-r--r-- | lib/eunit/src/eunit_tty.erl | 59 | ||||
-rw-r--r-- | lib/eunit/test/Makefile | 2 | ||||
-rw-r--r-- | lib/eunit/vsn.mk | 2 |
14 files changed, 335 insertions, 165 deletions
diff --git a/lib/eunit/doc/overview.edoc b/lib/eunit/doc/overview.edoc index ad449cb6fc..b4af31ae6a 100644 --- a/lib/eunit/doc/overview.edoc +++ b/lib/eunit/doc/overview.edoc @@ -723,8 +723,12 @@ A <em>simple test object</em> is one of the following: ```fun some_function/0''' ```fun some_module:some_function/0''' </li> - <li>A pair of atoms `{ModuleName, FunctionName}', referring to the - function `ModuleName:FunctionName/0'</li> + <li>A tuple `{test, ModuleName, FunctionName}', where `ModuleName' and + `FunctionName' are atoms, referring to the function + `ModuleName:FunctionName/0'</li> + <li>(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.</li> <li>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/doc/src/notes.xml b/lib/eunit/doc/src/notes.xml index 34e8a47e3d..b797be0ccb 100644 --- a/lib/eunit/doc/src/notes.xml +++ b/lib/eunit/doc/src/notes.xml @@ -5,7 +5,7 @@ <header> <copyright> <year>2008</year> - <year>2011</year> + <year>2012</year> <holder>Ericsson AB, All Rights Reserved</holder> </copyright> <legalnotice> @@ -32,6 +32,38 @@ </header> <p>This document describes the changes made to the EUnit application.</p> +<section><title>Eunit 2.2.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + New option 'no_tty' to silent the default tty report.</p> + <p> + Recognize the new stacktrace format introduced in R15, + adding location information. (Thanks to Klas Johansson.)</p> + <p> + Improve layout of error messages, printing the stack + trace before the error term.</p> + <p> + Heuristically detect and report bad return values from + generators and instantiators. E.g., "ok" will not be + interpreted as a module name, and a warning will be + printed.</p> + <p> + New test representation {test,M,F} for completeness along + with {generator,M,F}. Tuples {M,F} are deprecated.</p> + <p> + Use UTF-8 as encoding in Surefire output files. (Thanks + to Lukas Larsson.)</p> + <p> + Own Id: OTP-10173</p> + </item> + </list> + </section> + +</section> + <section><title>Eunit 2.2.2</title> <section><title>Improvements and New Features</title> diff --git a/lib/eunit/include/eunit.hrl b/lib/eunit/include/eunit.hrl index db68d8ae60..8ebdb6ba16 100644 --- a/lib/eunit/include/eunit.hrl +++ b/lib/eunit/include/eunit.hrl @@ -1,3 +1,4 @@ +%% -*- coding: utf-8 -*- %% 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 @@ -13,7 +14,7 @@ %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 %% USA %% -%% Copyright (C) 2004-2006 Micka�l R�mond, Richard Carlsson +%% Copyright (C) 2004-2006 Mickaël Rémond, Richard Carlsson %% Including this file turns on testing and defines TEST, unless NOTEST %% is defined before the file is included. If both NOTEST and TEST are @@ -25,11 +26,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. @@ -123,13 +125,13 @@ -ifndef(UNDER_EUNIT). -define(UNDER_EUNIT, (?MATCHES({current_function,{eunit_proc,_,_}}, - .erlang:process_info(.erlang:group_leader(), - current_function)))). + erlang:process_info(erlang:group_leader(), + 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. @@ -141,14 +143,14 @@ ((fun () -> case (BoolExpr) of true -> ok; - __V -> .erlang:error({assertion_failed, - [{module, ?MODULE}, - {line, ?LINE}, - {expression, (??BoolExpr)}, - {expected, true}, - {value, case __V of false -> __V; - _ -> {not_a_boolean,__V} - end}]}) + __V -> erlang:error({assertion_failed, + [{module, ?MODULE}, + {line, ?LINE}, + {expression, (??BoolExpr)}, + {expected, true}, + {value, case __V of false -> __V; + _ -> {not_a_boolean,__V} + end}]}) end end)())). -endif. @@ -169,12 +171,12 @@ ((fun () -> case (Expr) of Guard -> ok; - __V -> .erlang:error({assertMatch_failed, - [{module, ?MODULE}, - {line, ?LINE}, - {expression, (??Expr)}, - {pattern, (??Guard)}, - {value, __V}]}) + __V -> erlang:error({assertMatch_failed, + [{module, ?MODULE}, + {line, ?LINE}, + {expression, (??Expr)}, + {pattern, (??Guard)}, + {value, __V}]}) end end)())). -endif. @@ -188,12 +190,12 @@ ((fun () -> __V = (Expr), case __V of - Guard -> .erlang:error({assertNotMatch_failed, - [{module, ?MODULE}, - {line, ?LINE}, - {expression, (??Expr)}, - {pattern, (??Guard)}, - {value, __V}]}); + Guard -> erlang:error({assertNotMatch_failed, + [{module, ?MODULE}, + {line, ?LINE}, + {expression, (??Expr)}, + {pattern, (??Guard)}, + {value, __V}]}); _ -> ok end end)())). @@ -209,12 +211,12 @@ ((fun (__X) -> case (Expr) of __X -> ok; - __V -> .erlang:error({assertEqual_failed, - [{module, ?MODULE}, - {line, ?LINE}, - {expression, (??Expr)}, - {expected, __X}, - {value, __V}]}) + __V -> erlang:error({assertEqual_failed, + [{module, ?MODULE}, + {line, ?LINE}, + {expression, (??Expr)}, + {expected, __X}, + {value, __V}]}) end end)(Expect))). -endif. @@ -227,11 +229,11 @@ -define(assertNotEqual(Unexpected, Expr), ((fun (__X) -> case (Expr) of - __X -> .erlang:error({assertNotEqual_failed, - [{module, ?MODULE}, - {line, ?LINE}, - {expression, (??Expr)}, - {value, __X}]}); + __X -> erlang:error({assertNotEqual_failed, + [{module, ?MODULE}, + {line, ?LINE}, + {expression, (??Expr)}, + {value, __X}]}); _ -> ok end end)(Unexpected))). @@ -247,7 +249,7 @@ -define(assertException(Class, Term, Expr), ((fun () -> try (Expr) of - __V -> .erlang:error({assertException_failed, + __V -> erlang:error({assertException_failed, [{module, ?MODULE}, {line, ?LINE}, {expression, (??Expr)}, @@ -258,16 +260,16 @@ catch Class:Term -> ok; __C:__T -> - .erlang:error({assertException_failed, - [{module, ?MODULE}, - {line, ?LINE}, - {expression, (??Expr)}, - {pattern, - "{ "++(??Class)++" , "++(??Term) - ++" , [...] }"}, - {unexpected_exception, - {__C, __T, - .erlang:get_stacktrace()}}]}) + erlang:error({assertException_failed, + [{module, ?MODULE}, + {line, ?LINE}, + {expression, (??Expr)}, + {pattern, + "{ "++(??Class)++" , "++(??Term) + ++" , [...] }"}, + {unexpected_exception, + {__C, __T, + erlang:get_stacktrace()}}]}) end end)())). -endif. @@ -298,17 +300,17 @@ Class -> case __T of Term -> - .erlang:error({assertNotException_failed, - [{module, ?MODULE}, - {line, ?LINE}, - {expression, (??Expr)}, - {pattern, - "{ "++(??Class)++" , " - ++(??Term)++" , [...] }"}, - {unexpected_exception, - {__C, __T, - .erlang:get_stacktrace() - }}]}); + erlang:error({assertNotException_failed, + [{module, ?MODULE}, + {line, ?LINE}, + {expression, (??Expr)}, + {pattern, + "{ "++(??Class)++" , " + ++(??Term)++" , [...] }"}, + {unexpected_exception, + {__C, __T, + erlang:get_stacktrace() + }}]}); _ -> ok end; _ -> ok @@ -323,17 +325,17 @@ %% require EUnit to be present at runtime, or at least eunit_lib.) %% these can be used for simply running commands in a controlled way --define(_cmd_(Cmd), (.eunit_lib:command(Cmd))). +-define(_cmd_(Cmd), (eunit_lib:command(Cmd))). -define(cmdStatus(N, Cmd), ((fun () -> case ?_cmd_(Cmd) of {(N), __Out} -> __Out; - {__N, _} -> .erlang:error({command_failed, - [{module, ?MODULE}, - {line, ?LINE}, - {command, (Cmd)}, - {expected_status,(N)}, - {status,__N}]}) + {__N, _} -> erlang:error({command_failed, + [{module, ?MODULE}, + {line, ?LINE}, + {command, (Cmd)}, + {expected_status,(N)}, + {status,__N}]}) end end)())). -define(_cmdStatus(N, Cmd), ?_test(?cmdStatus(N, Cmd))). @@ -349,12 +351,12 @@ ((fun () -> case ?_cmd_(Cmd) of {(N), _} -> ok; - {__N, _} -> .erlang:error({assertCmd_failed, - [{module, ?MODULE}, - {line, ?LINE}, - {command, (Cmd)}, - {expected_status,(N)}, - {status,__N}]}) + {__N, _} -> erlang:error({assertCmd_failed, + [{module, ?MODULE}, + {line, ?LINE}, + {command, (Cmd)}, + {expected_status,(N)}, + {status,__N}]}) end end)())). -endif. @@ -367,12 +369,12 @@ ((fun () -> case ?_cmd_(Cmd) of {_, (T)} -> ok; - {_, __T} -> .erlang:error({assertCmdOutput_failed, - [{module, ?MODULE}, - {line, ?LINE}, - {command,(Cmd)}, - {expected_output,(T)}, - {output,__T}]}) + {_, __T} -> erlang:error({assertCmdOutput_failed, + [{module, ?MODULE}, + {line, ?LINE}, + {command,(Cmd)}, + {expected_output,(T)}, + {output,__T}]}) end end)())). -endif. @@ -393,12 +395,12 @@ -else. -define(debugMsg(S), (begin - .io:fwrite(user, <<"~s:~w:~w: ~s\n">>, - [?FILE, ?LINE, self(), S]), + io:fwrite(user, <<"~s:~w:~w: ~s\n">>, + [?FILE, ?LINE, self(), S]), ok end)). -define(debugHere, (?debugMsg("<-"))). --define(debugFmt(S, As), (?debugMsg(.io_lib:format((S), (As))))). +-define(debugFmt(S, As), (?debugMsg(io_lib:format((S), (As))))). -define(debugVal(E), ((fun (__V) -> ?debugFmt(<<"~s = ~P">>, [(??E), __V, 15]), diff --git a/lib/eunit/src/Makefile b/lib/eunit/src/Makefile index 0a2e71cf7b..e88e28df83 100644 --- a/lib/eunit/src/Makefile +++ b/lib/eunit/src/Makefile @@ -104,10 +104,10 @@ $(OBJECTS): $(PARSE_TRANSFORM_BIN) # ---------------------------------------------------- $(APP_TARGET): $(APP_SRC) ../vsn.mk - sed -e 's;%VSN%;$(VSN);' $< > $@ + $(vsn_verbose)sed -e 's;%VSN%;$(VSN);' $< > $@ $(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk - sed -e 's;%VSN%;$(VSN);' $< > $@ + $(vsn_verbose)sed -e 's;%VSN%;$(VSN);' $< > $@ # ---------------------------------------------------- # Release Target 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]}, diff --git a/lib/eunit/src/eunit.erl b/lib/eunit/src/eunit.erl index 95857e83c8..5763949519 100644 --- a/lib/eunit/src/eunit.erl +++ b/lib/eunit/src/eunit.erl @@ -1,3 +1,4 @@ +%% -*- coding: utf-8 -*- %% 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 @@ -13,8 +14,8 @@ %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 %% USA %% -%% @copyright 2004-2009 Micka�l R�mond, Richard Carlsson -%% @author Micka�l R�mond <[email protected]> +%% @copyright 2004-2009 Mickaël Rémond, Richard Carlsson +%% @author Mickaël Rémond <[email protected]> %% [http://www.process-one.net/] %% @author Richard Carlsson <[email protected]> %% @version {@version}, {@date} {@time} @@ -139,7 +140,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 +195,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_autoexport.erl b/lib/eunit/src/eunit_autoexport.erl index 099bcb222e..36ae3b71d7 100644 --- a/lib/eunit/src/eunit_autoexport.erl +++ b/lib/eunit/src/eunit_autoexport.erl @@ -80,10 +80,9 @@ rewrite([F | Fs], As, Module, Test) -> rewrite(Fs, [F | As], Module, Test); rewrite([], As, Module, Test) -> {if Test -> - EUnit = {record_field,0,{atom,0,''},{atom,0,eunit}}, [{function,0,test,0, [{clause,0,[],[], - [{call,0,{remote,0,EUnit,{atom,0,test}}, + [{call,0,{remote,0,{atom,0,eunit},{atom,0,test}}, [{atom,0,Module}]}]}]} | As]; true -> @@ -92,9 +91,7 @@ rewrite([], As, Module, Test) -> Test}. module_decl(Name, M, Fs, Exports) -> - Module = if is_atom(Name) -> Name; - true -> list_to_atom(packages:concat(Name)) - end, + Module = Name, {Fs1, Test} = rewrite(Fs, [], Module, true), Es = if Test -> [{test,0} | Exports]; true -> Exports diff --git a/lib/eunit/src/eunit_data.erl b/lib/eunit/src/eunit_data.erl index 392d378a0e..0350f9bf6e 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() @@ -95,7 +96,6 @@ %% %% @type moduleName() = atom() %% @type functionName() = atom() -%% @type arity() = integer() %% @type appName() = atom() %% @type fileName() = string() @@ -156,8 +156,9 @@ iter_prev(#iter{prev = [T | Ts]} = I) -> %% @spec (tests()) -> none | {testItem(), tests()} %% @type testItem() = #test{} | #group{} %% @throws {bad_test, term()} -%% | {generator_failed, exception()} -%% | {no_such_function, eunit_lib:mfa()} +%% | {generator_failed, {{M::atom(),F::atom(),A::integer()}, +%% exception()}} +%% | {no_such_function, mfa()} %% | {module_not_found, moduleName()} %% | {application_not_found, appName()} %% | {file_read_error, {Reason::atom(), Message::string(), @@ -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}) -> @@ -421,8 +432,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) -> - #test{f = eunit_test:function_wrapper(M, F), location = {M, F, 0}}; +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). @@ -580,7 +594,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 -> @@ -723,6 +737,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_})), @@ -740,6 +755,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), diff --git a/lib/eunit/src/eunit_lib.erl b/lib/eunit/src/eunit_lib.erl index 1c41e229c5..809cb7ab7b 100644 --- a/lib/eunit/src/eunit_lib.erl +++ b/lib/eunit/src/eunit_lib.erl @@ -1,3 +1,4 @@ +%% -*- coding: utf-8 -*- %% 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 @@ -13,8 +14,8 @@ %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 %% USA %% -%% @copyright 2004-2007 Micka�l R�mond, Richard Carlsson -%% @author Micka�l R�mond <[email protected]> +%% @copyright 2004-2007 Mickaël Rémond, Richard Carlsson +%% @author Mickaël Rémond <[email protected]> %% [http://www.process-one.net/] %% @author Richard Carlsson <[email protected]> %% @private @@ -30,7 +31,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 @@ -39,13 +41,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() @@ -59,8 +58,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; @@ -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]). @@ -139,9 +157,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]), @@ -158,14 +180,55 @@ 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)]). 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( + "\nymmud:rorre"++_, + lists:reverse(lists:flatten( + format_exception(try erlang:error(dummy) + catch C:R -> {C, R, erlang:get_stacktrace()} + end)))), + ?_assertMatch( + "\nymmud:rorre"++_, + lists:reverse(lists:flatten( + format_exception(try erlang:error(dummy, [a]) + catch C:R -> {C, R, erlang:get_stacktrace()} + 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 diff --git a/lib/eunit/src/eunit_surefire.erl b/lib/eunit/src/eunit_surefire.erl index 2a6cbca14d..cc021625d5 100644 --- a/lib/eunit/src/eunit_surefire.erl +++ b/lib/eunit/src/eunit_surefire.erl @@ -1,3 +1,4 @@ +%% -*- coding: utf-8 -*- %% 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 @@ -13,8 +14,8 @@ %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 %% USA %% -%% @author Micka�l R�mond <[email protected]> -%% @copyright 2009 Micka�l R�mond, Paul Guyot +%% @author Mickaël Rémond <[email protected]> +%% @copyright 2009 Mickaël Rémond, Paul Guyot %% @see eunit %% @doc Surefire reports for EUnit (Format used by Maven and Atlassian %% Bamboo for example to integrate test results). Based on initial code @@ -156,9 +157,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), @@ -232,7 +257,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) diff --git a/lib/eunit/src/eunit_test.erl b/lib/eunit/src/eunit_test.erl index bca49ae626..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"). @@ -43,8 +42,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) -> @@ -258,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 @@ -289,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) @@ -319,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 diff --git a/lib/eunit/src/eunit_tty.erl b/lib/eunit/src/eunit_tty.erl index e3e7b710b2..f21b2da3d3 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"); <<Text:800/binary, _:1/binary, _/binary>> -> - 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. diff --git a/lib/eunit/test/Makefile b/lib/eunit/test/Makefile index 1586dd8857..e4ddf4e42c 100644 --- a/lib/eunit/test/Makefile +++ b/lib/eunit/test/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 2009-2010. All Rights Reserved. +# Copyright Ericsson AB 2009-2012. All Rights Reserved. # # The contents of this file are subject to the Erlang Public License, # Version 1.1, (the "License"); you may not use this file except in 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 |