Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Ledest:erlang:23
erlang
5161-eunit-exact_execution-option.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 5161-eunit-exact_execution-option.patch of Package erlang
From c194380712236c301188f0b401352f14c608c145 Mon Sep 17 00:00:00 2001 From: Jakub Witczak <kuba@erlang.org> Date: Fri, 2 Sep 2022 09:37:30 +0200 Subject: [PATCH] eunit: exact_execution option - option for avoiding automatic execution of a module with "_tests" suffix --- lib/eunit/src/eunit.erl | 5 + lib/eunit/src/eunit_data.erl | 267 +++++++++++++------------ lib/eunit/src/eunit_internal.hrl | 1 + lib/eunit/src/eunit_proc.erl | 12 +- lib/eunit/src/eunit_server.erl | 3 +- lib/eunit/test/Makefile | 1 + lib/eunit/test/eunit_SUITE.erl | 39 +++- lib/eunit/test/eunit_test_listener.erl | 85 ++++++++ 8 files changed, 268 insertions(+), 145 deletions(-) create mode 100644 lib/eunit/test/eunit_test_listener.erl diff --git a/lib/eunit/src/eunit.erl b/lib/eunit/src/eunit.erl index 0238885708..f30c238366 100644 --- a/lib/eunit/src/eunit.erl +++ b/lib/eunit/src/eunit.erl @@ -135,6 +135,11 @@ test(Tests) -> %% <dd>Displays more details about the running tests.</dd> %% <dt>`print_depth'</dt> %% <dd>Maximum depth to which terms are printed in case of error.</dd> +%% <dt>`exact_execution'</dt> +%% <dd>If this boolean flag is set to `true' framework will +%% not automatically execute tests found in related module suffixed with "_tests". +%% This behaviour might be unwanted if execution of modules found in a folder +%% is ordered while it contains both source and test modules.</dd> %% </dl> %% %% Options in the environment variable EUNIT are also included last in diff --git a/lib/eunit/src/eunit_data.erl b/lib/eunit/src/eunit_data.erl index e36f6cad13..dfc6662a96 100644 --- a/lib/eunit/src/eunit_data.erl +++ b/lib/eunit/src/eunit_data.erl @@ -31,10 +31,8 @@ -include_lib("kernel/include/file.hrl"). --export([iter_init/2, iter_next/1, iter_prev/1, iter_id/1, - enter_context/3, get_module_tests/1]). - --import(lists, [foldr/3]). +-export([iter_init/3, iter_next/1, iter_prev/1, iter_id/1, + enter_context/3, get_module_tests/2]). -define(TICKS_PER_SECOND, 1000). @@ -115,14 +113,15 @@ {prev = [], next = [], tests = [], + options, pos = 0, parent = []}). %% @spec (tests(), [integer()]) -> testIterator() %% @type testIterator() -iter_init(Tests, ParentID) -> - #iter{tests = Tests, parent = lists:reverse(ParentID)}. +iter_init(Tests, ParentID, Options) -> + #iter{tests = Tests, parent = lists:reverse(ParentID), options = Options}. %% @spec (testIterator()) -> [integer()] @@ -131,8 +130,8 @@ iter_id(#iter{pos = N, parent = Ns}) -> %% @spec (testIterator()) -> none | {testItem(), testIterator()} -iter_next(I = #iter{next = []}) -> - case next(I#iter.tests) of +iter_next(I = #iter{next = [], options = Options}) -> + case next(I#iter.tests, Options) of {T, Tests} -> {T, I#iter{prev = [T | I#iter.prev], tests = Tests, @@ -169,12 +168,12 @@ iter_prev(#iter{prev = [T | Ts]} = I) -> %% | {file_read_error, {Reason::atom(), Message::string(), %% fileName()}} -next(Tests) -> +next(Tests, Options) -> case eunit_lib:dlist_next(Tests) of [T | Ts] -> - case parse(T) of + case parse(T, Options) of {data, T1} -> - next([T1 | Ts]); + next([T1 | Ts], Options); T1 -> {T1, Ts} end; @@ -188,15 +187,15 @@ next(Tests) -> %% this returns either a #test{} or #group{} record, or {data, T} to %% signal that T has been substituted for the given representation -parse({foreach, S, Fs}) when is_function(S), is_list(Fs) -> - parse({foreach, S, fun ok/1, Fs}); -parse({foreach, S, C, Fs}) +parse({foreach, S, Fs}, Options) when is_function(S), is_list(Fs) -> + parse({foreach, S, fun ok/1, Fs}, Options); +parse({foreach, S, C, Fs}, Options) when is_function(S), is_function(C), is_list(Fs) -> - parse({foreach, ?DEFAULT_SETUP_PROCESS, S, C, Fs}); -parse({foreach, P, S, Fs}) + parse({foreach, ?DEFAULT_SETUP_PROCESS, S, C, Fs}, Options); +parse({foreach, P, S, Fs}, Options) when is_function(S), is_list(Fs) -> - parse({foreach, P, S, fun ok/1, Fs}); -parse({foreach, P, S, C, Fs} = T) + parse({foreach, P, S, fun ok/1, Fs}, Options); +parse({foreach, P, S, C, Fs} = T, _Options) when is_function(S), is_function(C), is_list(Fs) -> check_arity(S, 0, T), check_arity(C, 1, T), @@ -206,15 +205,15 @@ parse({foreach, P, S, C, Fs} = T) [] -> {data, []} end; -parse({foreachx, S1, Ps}) when is_function(S1), is_list(Ps) -> - parse({foreachx, S1, fun ok/2, Ps}); -parse({foreachx, S1, C1, Ps}) +parse({foreachx, S1, Ps}, Options) when is_function(S1), is_list(Ps) -> + parse({foreachx, S1, fun ok/2, Ps}, Options); +parse({foreachx, S1, C1, Ps}, Options) when is_function(S1), is_function(C1), is_list(Ps) -> - parse({foreachx, ?DEFAULT_SETUP_PROCESS, S1, C1, Ps}); -parse({foreachx, P, S1, Ps}) + parse({foreachx, ?DEFAULT_SETUP_PROCESS, S1, C1, Ps}, Options); +parse({foreachx, P, S1, Ps}, Options) when is_function(S1), is_list(Ps) -> - parse({foreachx, P, S1, fun ok/2, Ps}); -parse({foreachx, P, S1, C1, Ps} = T) + parse({foreachx, P, S1, fun ok/2, Ps}, Options); +parse({foreachx, P, S1, C1, Ps} = T, _Options) when is_function(S1), is_function(C1), is_list(Ps) -> check_arity(S1, 1, T), check_arity(C1, 2, T), @@ -230,12 +229,12 @@ parse({foreachx, P, S1, C1, Ps} = T) [] -> {data, []} end; -parse({generator, F}) when is_function(F) -> +parse({generator, F}, Options) 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) + parse({generator, F, {M,N,A}}, Options); +parse({generator, F, {M,N,A}} = T, _Options) 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 @@ -249,38 +248,38 @@ parse({generator, F, {M,N,A}} = T) {error, {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:mf_wrapper(M, F), {M,F,0}}); -parse({inorder, T}) -> - group(#group{tests = T, order = inorder}); -parse({inparallel, T}) -> - parse({inparallel, 0, T}); -parse({inparallel, N, T}) when is_integer(N), N >= 0 -> - group(#group{tests = T, order = {inparallel, N}}); -parse({timeout, N, T}) when is_number(N), N >= 0 -> - group(#group{tests = T, timeout = round(N * ?TICKS_PER_SECOND)}); -parse({spawn, T}) -> - group(#group{tests = T, spawn = local}); -parse({spawn, N, T}) when is_atom(N) -> - group(#group{tests = T, spawn = {remote, N}}); -parse({setup, S, I}) when is_function(S); is_list(S) -> - parse({setup, ?DEFAULT_SETUP_PROCESS, S, I}); -parse({setup, S, C, I}) when is_function(S), is_function(C) -> - parse({setup, ?DEFAULT_SETUP_PROCESS, S, C, I}); -parse({setup, P, S, I}) when is_function(S) -> - parse({setup, P, S, fun ok/1, I}); -parse({setup, P, L, I} = T) when is_list(L) -> +parse({generator, M, F}, Options) when is_atom(M), is_atom(F) -> + parse({generator, eunit_test:mf_wrapper(M, F), {M,F,0}}, Options); +parse({inorder, T}, Options) -> + group(#group{tests = T, options = Options, order = inorder}); +parse({inparallel, T}, Options) -> + parse({inparallel, 0, T}, Options); +parse({inparallel, N, T}, Options) when is_integer(N), N >= 0 -> + group(#group{tests = T, options = Options, order = {inparallel, N}}); +parse({timeout, N, T}, Options) when is_number(N), N >= 0 -> + group(#group{tests = T, options = Options, timeout = round(N * ?TICKS_PER_SECOND)}); +parse({spawn, T}, Options) -> + group(#group{tests = T, options = Options, spawn = local}); +parse({spawn, N, T}, Options) when is_atom(N) -> + group(#group{tests = T, options = Options, spawn = {remote, N}}); +parse({setup, S, I}, Options) when is_function(S); is_list(S) -> + parse({setup, ?DEFAULT_SETUP_PROCESS, S, I}, Options); +parse({setup, S, C, I}, Options) when is_function(S), is_function(C) -> + parse({setup, ?DEFAULT_SETUP_PROCESS, S, C, I}, Options); +parse({setup, P, S, I}, Options) when is_function(S) -> + parse({setup, P, S, fun ok/1, I}, Options); +parse({setup, P, L, I} = T, Options) when is_list(L) -> check_setup_list(L, T), {S, C} = eunit_test:multi_setup(L), - parse({setup, P, S, C, I}); -parse({setup, P, S, C, I} = T) + parse({setup, P, S, C, I}, Options); +parse({setup, P, S, C, I} = T, Options) when is_function(S), is_function(C), is_function(I) -> check_arity(S, 0, T), check_arity(C, 1, T), case erlang:fun_info(I, arity) of {arity, 0} -> %% if I is nullary, it is a plain test - parse({setup, S, C, fun (_) -> I end}); + parse({setup, S, C, fun (_) -> I end}, Options); _ -> %% otherwise, I must be an instantiator function check_arity(I, 1, T), @@ -294,13 +293,13 @@ parse({setup, P, S, C, I} = T) context = #context{setup = S, cleanup = C, process = P}}) end; -parse({setup, P, S, C, {with, As}}) when is_list(As) -> - parse({setup, P, S, C, fun (X) -> {with, X, As} end}); -parse({setup, P, S, C, T}) when is_function(S), is_function(C) -> - parse({setup, P, S, C, fun (_) -> T end}); -parse({node, N, T}) when is_atom(N) -> - parse({node, N, "", T}); -parse({node, N, A, T1}=T) when is_atom(N) -> +parse({setup, P, S, C, {with, As}}, Options) when is_list(As) -> + parse({setup, P, S, C, fun (X) -> {with, X, As} end}, Options); +parse({setup, P, S, C, T}, Options) when is_function(S), is_function(C) -> + parse({setup, P, S, C, fun (_) -> T end}, Options); +parse({node, N, T}, Options) when is_atom(N) -> + parse({node, N, "", T}, Options); +parse({node, N, A, T1}=T, Options) when is_atom(N) -> case eunit_lib:is_string(A) of true -> %% TODO: better stack traces for internal funs like these @@ -336,14 +335,14 @@ parse({node, N, A, T1}=T) when is_atom(N) -> false -> ok end end, - T1}); + T1}, Options); false -> bad_test(T) end; -parse({module, M}) when is_atom(M) -> - {data, {"module '" ++ atom_to_list(M) ++ "'", get_module_tests(M)}}; -parse({application, A}) when is_atom(A) -> - try parse({file, atom_to_list(A)++".app"}) +parse({module, M}, Options) when is_atom(M) -> + {data, {"module '" ++ atom_to_list(M) ++ "'", get_module_tests(M, Options)}}; +parse({application, A}, Options) when is_atom(A) -> + try parse({file, atom_to_list(A)++".app"}, Options) catch {file_read_error,{enoent,_,_}} -> case code:lib_dir(A) of @@ -352,15 +351,15 @@ parse({application, A}) when is_atom(A) -> BinDir = filename:join(Dir, "ebin"), case file:read_file_info(BinDir) of {ok, #file_info{type=directory}} -> - parse({dir, BinDir}); + parse({dir, BinDir}, Options); _ -> - parse({dir, Dir}) + parse({dir, Dir}, Options) end; _ -> throw({application_not_found, A}) end end; -parse({application, A, Info}=T) when is_atom(A) -> +parse({application, A, Info}=T, _Options) when is_atom(A) -> case proplists:get_value(modules, Info) of Ms when is_list(Ms) -> case [M || M <- Ms, not is_atom(M)] of @@ -372,14 +371,14 @@ parse({application, A, Info}=T) when is_atom(A) -> _ -> bad_test(T) end; -parse({file, F} = T) when is_list(F) -> +parse({file, F} = T, _Options) when is_list(F) -> case eunit_lib:is_string(F) of true -> {data, {"file \"" ++ F ++ "\"", get_file_tests(F)}}; false -> bad_test(T) end; -parse({dir, D}=T) when is_list(D) -> +parse({dir, D}=T, _Options) when is_list(D) -> case eunit_lib:is_string(D) of true -> {data, {"directory \"" ++ D ++ "\"", @@ -387,7 +386,7 @@ parse({dir, D}=T) when is_list(D) -> false -> bad_test(T) end; -parse({with, X, As}=T) when is_list(As) -> +parse({with, X, As}=T, _Options) when is_list(As) -> case As of [A | As1] -> check_arity(A, 1, T), @@ -396,36 +395,36 @@ parse({with, X, As}=T) when is_list(As) -> [] -> {data, []} end; -parse({S, T1} = T) when is_list(S) -> +parse({S, T1} = T, _Options) when is_list(S) -> case eunit_lib:is_string(S) of true -> group(#group{tests = T1, desc = unicode:characters_to_binary(S)}); false -> bad_test(T) end; -parse({S, T1}) when is_binary(S) -> +parse({S, T1}, _Options) when is_binary(S) -> group(#group{tests = T1, desc = S}); -parse(T) when tuple_size(T) > 2, is_list(element(1, T)) -> +parse(T, Options) when tuple_size(T) > 2, is_list(element(1, T)) -> [S | Es] = tuple_to_list(T), - parse({S, list_to_tuple(Es)}); -parse(T) when tuple_size(T) > 2, is_binary(element(1, T)) -> + parse({S, list_to_tuple(Es)}, Options); +parse(T, Options) when tuple_size(T) > 2, is_binary(element(1, T)) -> [S | Es] = tuple_to_list(T), - parse({S, list_to_tuple(Es)}); -parse(M) when is_atom(M) -> - parse({module, M}); -parse(T) when is_list(T) -> + parse({S, list_to_tuple(Es)}, Options); +parse(M, Options) when is_atom(M) -> + parse({module, M}, Options); +parse(T, Options) when is_list(T) -> case eunit_lib:is_string(T) of true -> - try parse({dir, T}) + try parse({dir, T}, Options) catch {file_read_error,{R,_,_}} when R =:= enotdir; R =:= enoent -> - parse({file, T}) + parse({file, T}, Options) end; false -> bad_test(T) end; -parse(T) -> +parse(T, _Options) -> parse_simple(T). %% parse_simple always produces a #test{} record @@ -481,10 +480,10 @@ group(#group{context = #context{}} = G) -> %% suitable for lookahead (and anyway, properties of the setup %% should not be merged with properties of its body, e.g. spawn) G; -group(#group{tests = T0, desc = Desc, order = Order, context = Context, - spawn = Spawn, timeout = Timeout} = G) -> - {T1, Ts} = lookahead(T0), - {T2, _} = lookahead(Ts), +group(#group{tests = T0, desc = Desc, options = Options, order = Order, + context = Context, spawn = Spawn, timeout = Timeout} = G) -> + {T1, Ts} = lookahead(T0, Options), + {T2, _} = lookahead(Ts, Options), case T1 of #test{desc = Desc1, timeout = Timeout1} when T2 =:= none, Spawn =:= undefined, Context =:= undefined, @@ -529,8 +528,8 @@ group(#group{tests = T0, desc = Desc, order = Order, context = Context, G end. -lookahead(T) -> - case next(T) of +lookahead(T, Options) -> + case next(T, Options) of {T1, Ts} -> {T1, Ts}; none -> {none, []} end. @@ -559,58 +558,62 @@ push_order(_, _, G) -> %% @throws {module_not_found, moduleName()} -get_module_tests(M) -> - try M:module_info(exports) of - Es -> - Fs = get_module_tests_1(M, Es), - W = ?DEFAULT_MODULE_WRAPPER_NAME, - case lists:member({W,1}, Es) of - false -> Fs; - true -> {generator, fun () -> M:W(Fs) end} +get_module_tests(Module, Options) -> + try Module:module_info(exports) of + Exports -> + TestFuns = extract_module_tests(Module, Exports, Options), + case lists:member({?DEFAULT_MODULE_WRAPPER_NAME, 1}, Exports) of + false -> TestFuns; + true -> {generator, + fun() -> + Module:?DEFAULT_MODULE_WRAPPER_NAME(TestFuns) + end} end catch - error:undef -> - throw({module_not_found, M}) + error:undef -> + throw({module_not_found, Module}) end. -get_module_tests_1(M, Es) -> - Fs = testfuns(Es, M, ?DEFAULT_TEST_SUFFIX, - ?DEFAULT_GENERATOR_SUFFIX), - Name = atom_to_list(M), - case lists:suffix(?DEFAULT_TESTMODULE_SUFFIX, Name) of - false -> - Name1 = Name ++ ?DEFAULT_TESTMODULE_SUFFIX, - M1 = list_to_atom(Name1), - try get_module_tests(M1) of - Fs1 -> - Fs ++ [{"module '" ++ Name1 ++ "'", Fs1}] +extract_module_tests(Module, Exports, Options) -> + TestFuns = extract_testfuns(Exports, Module, ?DEFAULT_TEST_SUFFIX, + ?DEFAULT_GENERATOR_SUFFIX), + ModuleName = atom_to_list(Module), + Exact = proplists:get_bool(exact_execution, Options), + case {lists:suffix(?DEFAULT_TESTMODULE_SUFFIX, ModuleName), + Exact} of + {false, false} -> + ModuleNameWithSuffix = ModuleName ++ ?DEFAULT_TESTMODULE_SUFFIX, + ModuleWithSuffix = list_to_atom(ModuleNameWithSuffix), + try get_module_tests(ModuleWithSuffix, Options) of + MoreTestFuns -> + TestFuns ++ [{"module '" ++ ModuleNameWithSuffix ++ "'", + MoreTestFuns}] catch - {module_not_found, M1} -> - Fs + {module_not_found, ModuleWithSuffix} -> + TestFuns end; - true -> - Fs + _ -> + TestFuns end. -testfuns(Es, M, TestSuffix, GeneratorSuffix) -> - foldr(fun ({F, 0}, Fs) -> - N = atom_to_list(F), - case lists:suffix(TestSuffix, N) of - true -> - [{test, M, F} | Fs]; - false -> - case lists:suffix(GeneratorSuffix, N) of - true -> - [{generator, M, F} | Fs]; - false -> - Fs - end - end; - (_, Fs) -> - Fs - end, - [], - Es). +extract_testfuns(Exports, Module, TestSuffix, GeneratorSuffix) -> + GetTestFun = fun({Function, 0}, Acc) -> + FunctionName = atom_to_list(Function), + case lists:suffix(TestSuffix, FunctionName) of + true -> + [{test, Module, Function} | Acc]; + false -> + case lists:suffix(GeneratorSuffix, FunctionName) of + true -> + [{generator, Module, Function} | Acc]; + false -> + Acc + end + end; + (_, Acc) -> + Acc + end, + lists:foldr(GetTestFun, [], Exports). %% --------------------------------------------------------------------- diff --git a/lib/eunit/src/eunit_internal.hrl b/lib/eunit/src/eunit_internal.hrl index 65f4c433e7..217d4a304c 100644 --- a/lib/eunit/src/eunit_internal.hrl +++ b/lib/eunit/src/eunit_internal.hrl @@ -50,6 +50,7 @@ }). -record(group, {desc = undefined, + options = undefined, order = undefined, % run in order or in parallel timeout = undefined, context = undefined, % setup-context record diff --git a/lib/eunit/src/eunit_proc.erl b/lib/eunit/src/eunit_proc.erl index 7622bab2a9..48254f53a3 100644 --- a/lib/eunit/src/eunit_proc.erl +++ b/lib/eunit/src/eunit_proc.erl @@ -29,13 +29,12 @@ -include("eunit.hrl"). -include("eunit_internal.hrl"). --export([start/4, get_output/0]). +-export([start/5, get_output/0]). %% This must be exported; see new_group_leader/1 for details. -export([group_leader_process/1]). --record(procstate, {ref, id, super, insulator, parent, order}). - +-record(procstate, {ref, id, super, insulator, parent, order, options}). %% Spawns test process and returns the process Pid; sends {done, %% Reference, Pid} to caller when finished. See the function @@ -44,12 +43,13 @@ %% The `Super' process receives a stream of status messages; see %% message_super/3 for details. -start(Tests, Order, Super, Reference) +start(Tests, Order, Super, Reference, Options) when is_pid(Super), is_reference(Reference) -> St = #procstate{ref = Reference, id = [], super = Super, - order = Order}, + order = Order, + options = Options}, spawn_group(local, #group{tests = Tests}, St). %% Fetches the output captured by the eunit group leader. This is @@ -436,7 +436,7 @@ wait_for_tasks(PidSet, St) -> %% TODO: Flow control, starting new job as soon as slot is available tests(T, St) -> - I = eunit_data:iter_init(T, St#procstate.id), + I = eunit_data:iter_init(T, St#procstate.id, St#procstate.options), case St#procstate.order of inorder -> tests_inorder(I, St); inparallel -> tests_inparallel(I, 0, St); diff --git a/lib/eunit/src/eunit_server.erl b/lib/eunit/src/eunit_server.erl index e46394d88c..6fb44e0170 100644 --- a/lib/eunit/src/eunit_server.erl +++ b/lib/eunit/src/eunit_server.erl @@ -243,7 +243,8 @@ start_job(Job, From, Reference, St) -> From ! {start, Reference}, %% The default is to run tests in order unless otherwise specified Order = proplists:get_value(order, Job#job.options, inorder), - eunit_proc:start(Job#job.test, Order, Job#job.super, Reference), + eunit_proc:start(Job#job.test, Order, Job#job.super, Reference, + Job#job.options), St#state{jobs = dict:store(Reference, From, St#state.jobs)}. handle_done(Reference, St) -> diff --git a/lib/eunit/test/Makefile b/lib/eunit/test/Makefile index 1bd9c0a07c..a5b1b719ad 100644 --- a/lib/eunit/test/Makefile +++ b/lib/eunit/test/Makefile @@ -21,6 +21,7 @@ include $(ERL_TOP)/make/target.mk include $(ERL_TOP)/make/$(TARGET)/otp.mk MODULES = \ + eunit_test_listener \ eunit_SUITE \ tc0 \ tlatin \ diff --git a/lib/eunit/test/eunit_SUITE.erl b/lib/eunit/test/eunit_SUITE.erl index b637b5f314..b0c2259971 100644 --- a/lib/eunit/test/eunit_SUITE.erl +++ b/lib/eunit/test/eunit_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2021. All Rights Reserved. +%% Copyright Ericsson AB 2010-2022. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -19,19 +19,22 @@ %% -module(eunit_SUITE). --export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, - init_per_group/2,end_per_group/2, - app_test/1,appup_test/1,eunit_test/1,surefire_utf8_test/1,surefire_latin_test/1, +-export([all/0, suite/0, groups/0, init_per_suite/1, end_per_suite/1, + init_per_group/2, end_per_group/2, + app_test/1, appup_test/1, eunit_test/1, eunit_exact_test/1, + surefire_utf8_test/1, surefire_latin_test/1, surefire_c0_test/1, surefire_ensure_dir_test/1, stacktrace_at_timeout_test/1]). -include_lib("common_test/include/ct.hrl"). +-define(TIMEOUT, 1000). suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> - [app_test, appup_test, eunit_test, surefire_utf8_test, surefire_latin_test, - surefire_c0_test, surefire_ensure_dir_test, stacktrace_at_timeout_test]. + [app_test, appup_test, eunit_test, eunit_exact_test, surefire_utf8_test, + surefire_latin_test, surefire_c0_test, surefire_ensure_dir_test, + stacktrace_at_timeout_test]. groups() -> []. @@ -58,6 +61,30 @@ eunit_test(Config) when is_list(Config) -> ok = file:set_cwd(code:lib_dir(eunit)), ok = eunit:test(eunit). +eunit_exact_test(Config) when is_list(Config) -> + ok = file:set_cwd(code:lib_dir(eunit)), + ok = eunit:test([eunit, eunit_tests], + [{report, {eunit_test_listener, [self()]}}]), + check_test_results(14, 0, 0, 0), + ok = eunit:test([eunit, eunit_tests], + [{report, {eunit_test_listener, [self()]}}, + {exact_execution, false}]), + check_test_results(14, 0, 0, 0), + ok = eunit:test([eunit, eunit_tests], + [{report, {eunit_test_listener, [self()]}}, + {exact_execution, true}]), + check_test_results(7, 0, 0, 0), + ok. + +check_test_results(Pass, Fail, Skip, Cancel) -> + receive + {test_report, TestReport} -> + #{pass := Pass, fail := Fail, + skip := Skip, cancel := Cancel} = TestReport + after ?TIMEOUT -> + ct:fail(no_test_report_not_received) + end. + surefire_latin_test(Config) when is_list(Config) -> ok = file:set_cwd(proplists:get_value(priv_dir, Config, ".")), check_surefire(tlatin), diff --git a/lib/eunit/test/eunit_test_listener.erl b/lib/eunit/test/eunit_test_listener.erl new file mode 100644 index 0000000000..95894a02ff --- /dev/null +++ b/lib/eunit/test/eunit_test_listener.erl @@ -0,0 +1,85 @@ +%% Licensed under the Apache License, Version 2.0 (the "License"); you may +%% not use this file except in compliance with the License. You may obtain +%% a copy of the License at <http://www.apache.org/licenses/LICENSE-2.0> +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% Alternatively, you may use this file under the terms of the GNU Lesser +%% General Public License (the "LGPL") as published by the Free Software +%% Foundation; either version 2.1, or (at your option) any later version. +%% If you wish to allow use of your version of this file only under the +%% terms of the LGPL, you should delete the provisions above and replace +%% them with the notice and other provisions required by the LGPL; see +%% <http://www.gnu.org/licenses/>. If you do not delete the provisions +%% above, a recipient may use your version of this file under the terms of +%% either the Apache License or the LGPL. +%% +%% @author The OTP team at Ericsson +%% @copyright Ericsson AB 2022 +%% @private +%% @see eunit +%% @doc Module for receiving EUnit events for verification purposes + +-module(eunit_test_listener). +-behaviour(eunit_listener). + +-export([start/0, start/1]). +-export([init/1, handle_begin/3, handle_end/3, handle_cancel/3, + terminate/2]). + +-record(state, {test_process = undefined}). + +start() -> + start([]). + +start(Options) -> + eunit_listener:start(?MODULE, Options). + +init(Options) -> + [TestProcess] = Options, + receive + {start, _Reference} -> + #state{test_process = TestProcess} + end. + +terminate({ok, Data}, #state{test_process = TestProcess}) -> + Pass = proplists:get_value(pass, Data, 0), + Fail = proplists:get_value(fail, Data, 0), + Skip = proplists:get_value(skip, Data, 0), + Cancel = proplists:get_value(cancel, Data, 0), + TestProcess ! {test_report, + #{pass => Pass, fail => Fail, skip => Skip, + cancel => Cancel}}, + if Fail =:= 0, Skip =:= 0, Cancel =:= 0 -> + sync_end(ok); + true -> + sync_end(error) + end; +terminate({error, _Reason}, #state{}) -> + sync_end(error). + +sync_end(Result) -> + receive + {stop, Reference, ReplyTo} -> + ReplyTo ! {result, Reference, Result}, + ok + end. + +handle_begin(group, _Data, St) -> + St; +handle_begin(test, _Data, St) -> + St. + +handle_end(group, _Data, St) -> + St; +handle_end(test, _Data, St) -> + St. + +handle_cancel(group, _Data, St) -> + St; +handle_cancel(test, _Data, St) -> + St. -- 2.35.3
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor