diff options
author | Fabian Bergström <fabian.bergstrom@gmail.com> | 2022-11-06 10:06:33 +0100 |
---|---|---|
committer | Fabian <fabian@fmbb.se> | 2022-12-28 22:17:36 +0100 |
commit | 0631c7f74f1c3abac463c0d2f6d47c0bc7c6876f (patch) | |
tree | 34b9e8bccb2e17c21a547229b6d48813aea177b5 /lib | |
parent | 19bce3c6b734538dc7d5c3482f48703bd13691e6 (diff) | |
download | erlang-0631c7f74f1c3abac463c0d2f6d47c0bc7c6876f.tar.gz |
Fix example code in ct_hooks_chapter.xml
The example code has a syntax error (missing a period),
and uses the deprecated erlang:now/0 function.
* prefix unused variable names with _
* use a more minimal example
* move file:open to terminate
* 'suite_total' and 'total' were mixed up
* put complete CT hook example in erlang-skels.el
* fix indentation for the example CTH code
* remove some spaces and align to 80 chars rule
Diffstat (limited to 'lib')
-rw-r--r-- | lib/common_test/doc/src/ct_hooks_chapter.xml | 203 | ||||
-rw-r--r-- | lib/tools/emacs/erlang-skels.el | 103 |
2 files changed, 194 insertions, 112 deletions
diff --git a/lib/common_test/doc/src/ct_hooks_chapter.xml b/lib/common_test/doc/src/ct_hooks_chapter.xml index aab8671f9e..327acd8c87 100644 --- a/lib/common_test/doc/src/ct_hooks_chapter.xml +++ b/lib/common_test/doc/src/ct_hooks_chapter.xml @@ -99,6 +99,10 @@ <item><c>{ct_hooks,[{my_cth_module,[{debug,true}],500}]}</c></item> </list> + <p>Note that regardless of how you install a CTH, its BEAM file + must be available in the code path when Common Test runs. + <c>ct_run</c> accepts the <c>-pa</c> command line option.</p> + <section> <title>Overriding CTHs</title> <p>By default, each installation of a CTH causes a new instance of it @@ -373,118 +377,93 @@ <section> <marker id="example"/> - <title>Example CTH</title> - <p>The following CTH logs information about a test run into a format - parseable by <seemfa marker="kernel:file#consult/1">file:consult/1</seemfa> - (in Kernel): - </p> - <code> - %%% Common Test Example Common Test Hook module. - -module(example_cth). - - %% Callbacks - -export([id/1]). - -export([init/2]). - - -export([pre_init_per_suite/3]). - -export([post_init_per_suite/4]). - -export([pre_end_per_suite/3]). - -export([post_end_per_suite/4]). - - -export([pre_init_per_group/4]). - -export([post_init_per_group/5]). - -export([pre_end_per_group/4]). - -export([post_end_per_group/5]). - - -export([pre_init_per_testcase/4]). - -export([post_init_per_testcase/5]). - -export([pre_end_per_testcase/4]). - -export([post_end_per_testcase/5]). - - -export([on_tc_fail/4]). - -export([on_tc_skip/4]). - - -export([terminate/1]). - - -record(state, { file_handle, total, suite_total, ts, tcs, data }). - - %% Return a unique id for this CTH. - id(Opts) -> - proplists:get_value(filename, Opts, "/tmp/file.log"). - - %% Always called before any other callback function. Use this to initiate - %% any common state. - init(Id, Opts) -> - {ok,D} = file:open(Id,[write]), - {ok, #state{ file_handle = D, total = 0, data = [] }}. - - %% Called before init_per_suite is called. - pre_init_per_suite(Suite,Config,State) -> - {Config, State#state{ suite_total = 0, tcs = [] }}. - - %% Called after init_per_suite. - post_init_per_suite(Suite,Config,Return,State) -> - {Return, State}. - - %% Called before end_per_suite. - pre_end_per_suite(Suite,Config,State) -> - {Config, State}. - - %% Called after end_per_suite. - post_end_per_suite(Suite,Config,Return,State) -> - Data = {suites, Suite, State#state.suite_total, lists:reverse(State#state.tcs)}, - {Return, State#state{ data = [Data | State#state.data] , - total = State#state.total + State#state.suite_total } }. - - %% Called before each init_per_group. - pre_init_per_group(Suite,Group,Config,State) -> - {Config, State}. - - %% Called after each init_per_group. - post_init_per_group(Suite,Group,Config,Return,State) -> - {Return, State}. - - %% Called before each end_per_group. - pre_end_per_group(Suite,Group,Config,State) -> - {Config, State}. - - %% Called after each end_per_group. - post_end_per_group(Suite,Group,Config,Return,State) -> - {Return, State}. - - %% Called before each init_per_testcase. - pre_init_per_testcase(Suite,TC,Config,State) -> - {Config, State#state{ ts = now(), total = State#state.suite_total + 1 } }. - - %% Called after each init_per_testcase (immediately before the test case). - post_init_per_testcase(Suite,TC,Config,Return,State) -> - {Return, State} - -%% Called before each end_per_testcase (immediately after the test case). - pre_end_per_testcase(Suite,TC,Config,State) -> - {Config, State}. - - %% Called after each end_per_testcase. - post_end_per_testcase(Suite,TC,Config,Return,State) -> - TCInfo = {testcase, Suite, TC, Return, timer:now_diff(now(), State#state.ts)}, - {Return, State#state{ ts = undefined, tcs = [TCInfo | State#state.tcs] } }. - - %% Called after post_init_per_suite, post_end_per_suite, post_init_per_group, - %% post_end_per_group and post_end_per_testcase if the suite, group or test case failed. - on_tc_fail(Suite, TC, Reason, State) -> - State. - - %% Called when a test case is skipped by either user action - %% or due to an init function failing. - on_tc_skip(Suite, TC, Reason, State) -> - State. - - %% Called when the scope of the CTH is done - terminate(State) -> - io:format(State#state.file_handle, "~p.~n", - [{test_run, State#state.total, State#state.data}]), - file:close(State#state.file_handle), - ok.</code> + <title>Example CTH</title> + <p>The following CTH logs information about a test run into a format + parseable by <seemfa marker="kernel:file#consult/1">file:consult/1</seemfa> + (in Kernel): + </p> + <code> +%%% Common Test Example Common Test Hook module. +%%% +%%% To use this hook, on the command line: +%%% ct_run -suite example_SUITE -pa . -ct_hooks example_cth +%%% +%%% Note `-pa .`: the hook beam file must be in the code path when installing. +-module(example_cth). + +%% Mandatory Callbacks +-export([init/2]). + +%% Optional Callbacks +-export([id/1]). + +-export([pre_init_per_suite/3]). +-export([post_end_per_suite/4]). + +-export([pre_init_per_testcase/4]). +-export([post_end_per_testcase/5]). + +-export([on_tc_skip/4]). + +-export([terminate/1]). + +%% This hook state is threaded through all the callbacks. +-record(state, {filename, total, suite_total, ts, tcs, data, skipped}). +%% This example hook prints its results to a file, see terminate/1. +-record(test_run, {total, skipped, suites}). + +%% Return a unique id for this CTH. +%% Using the filename means the hook can be used with different +%% log files to separate timing data within the same test run. +%% See Installing a CTH for more information. +id(Opts) -> + %% the path is relative to the test run directory + proplists:get_value(filename, Opts, "example_cth.log"). + +%% Always called before any other callback function. Use this to initiate +%% any common state. +init(Id, _Opts) -> + {ok, #state{filename = Id, total = 0, data = []}}. + +%% Called before init_per_suite is called. +pre_init_per_suite(_Suite,Config,State) -> + {Config, State#state{suite_total = 0, tcs = []}}. + +%% Called after end_per_suite. +post_end_per_suite(Suite,_Config,Return,State) -> + Data = {suites, Suite, State#state.suite_total, + lists:reverse(State#state.tcs)}, + {Return, State#state{data = [Data | State#state.data], + total = State#state.total + State#state.suite_total}}. + +%% Called before each init_per_testcase. +pre_init_per_testcase(_Suite,_TC,Config,State) -> + Now = erlang:monotonic_time(microsecond), + {Config, State#state{ts = Now, suite_total = State#state.suite_total + 1}}. + +%% Called after each end_per_testcase. +post_end_per_testcase(Suite,TC,_Config,Return,State) -> + Now = erlang:monotonic_time(microsecond), + TCInfo = {testcase, Suite, TC, Return, Now - State#state.ts}, + {Return, State#state{ts = undefined, tcs = [TCInfo | State#state.tcs]}}. + +%% Called when a test case is skipped by either user action +%% or due to an init function failing. +on_tc_skip(_Suite, _TC, _Reason, State) -> + State#state{skipped = State#state.skipped + 1}. + +%% Called when the scope of the CTH is done. +terminate(State) -> + %% use append to avoid data loss if the path is reused + {ok, File} = file:open(State#state.filename, [write, append]), + io:format(File, "~p.~n", [results(State)]), + file:close(File), + ok. + +results(State) -> + #state{skipped = Skipped, data = Data, total = Total} = State, + #test_run{total = Total, skipped = Skipped, suites = lists:reverse(Data)}. + </code> </section> <section> diff --git a/lib/tools/emacs/erlang-skels.el b/lib/tools/emacs/erlang-skels.el index 3202f43790..2d2429b050 100644 --- a/lib/tools/emacs/erlang-skels.el +++ b/lib/tools/emacs/erlang-skels.el @@ -70,6 +70,8 @@ erlang-skel-ct-test-suite-s erlang-skel-header) ("Large Common Test suite" "ct-test-suite-l" erlang-skel-ct-test-suite-l erlang-skel-header) + ("Common Test Hook" "ct-hook" + erlang-skel-ct-hook erlang-skel-header) ("Erlang TS test suite" "ts-test-suite" erlang-skel-ts-test-suite erlang-skel-header) ) @@ -1845,6 +1847,107 @@ Please see the function `tempo-define-template'.") "*The template of a library module. Please see the function `tempo-define-template'.") +(defvar erlang-skel-ct-hook + '((erlang-skel-include erlang-skel-normal-header) + "%% Mandatory callbacks" n + "-export([init/2])." n + n + "%% Optional callbacks" n + "-export([id/1])." n + n + "-export([pre_init_per_suite/3])." n + "-export([post_init_per_suite/4])." n + n + "-export([pre_end_per_suite/3])." n + "-export([post_end_per_suite/4])." n + n + "-export([pre_init_per_group/4])." n + "-export([post_init_per_group/5])." n + n + "-export([pre_end_per_group/4])." n + "-export([post_end_per_group/5])." n + n + "-export([pre_init_per_testcase/4])." n + "-export([post_init_per_testcase/5])." n + n + "-export([pre_end_per_testcase/4])." n + "-export([post_end_per_testcase/5])." n + n + "-export([on_tc_fail/4])." n + "-export([on_tc_skip/4])." n + n + "-export([terminate/1])." n + n + "%% The hook state is threaded through all callbacks," n + "%% but can be anything the hook needs, replace as desired." n + "-record(state, { cases=0, suites=0, groups=0, skips=0, fails=0 })." n + n + "%% Return a unique id for this CTH." n + "id(Opts) ->" n + " %% A reference is the default implementation, this can be removed" n + " %% or some value can be read from Opts instead." n + " erlang:make_ref()." n + n + "%% Always called before any other callback function, once per installed hook." n + "init(Id, Opts) ->" n + " {ok, #state{}}." n + n + "pre_init_per_suite(Suite, Config, State) ->" n + " {Config, State}." n + n + "post_init_per_suite(Suite, Config, Return, State) ->" n + " {Return, State}." n + n + "pre_end_per_suite(Suite, Config, State) ->" n + " {Config, State}." n + n + "post_end_per_suite(Suite, Config, Return, State) ->" n + " {Return, State#state{suites = State#state.suites + 1}}." n + n + "pre_init_per_group(Suite, Group, Config, State) ->" n + " {Config, State}." n + n + "post_init_per_group(Suite, Group, Config, Return, State) ->" n + " {Return, State}." n + n + "pre_end_per_group(Suite, Group, Config, State) ->" n + " {Config, State}." n + n + "post_end_per_group(Suite, Group, Config, Return, State) ->" n + " {Return, State#state{groups = State#state.groups + 1}}." n + n + "pre_init_per_testcase(Suite, TC, Config, State) ->" n + " {Config, State}." n + n + "%% Called after each init_per_testcase (immediately before the test case)." n + "post_init_per_testcase(Suite, TC, Config, Return, State) ->" n + " {Return, State}." n + n + "%% Called before each end_per_testcase (immediately after the test case)." n + "pre_end_per_testcase(Suite, TC, Config, State) ->" n + " {Config, State}." n + n + "post_end_per_testcase(Suite, TC, Config, Return, State) ->" n + " {Return, State#state{cases = State#state.cases + 1}}." n + n + "%% Called after post_init_per_suite, post_end_per_suite, post_init_per_group," n + "%% post_end_per_group and post_end_per_testcase if the suite, group or test case failed." n + "on_tc_fail(Suite, TC, Reason, State) ->" n + " State#state{fails = State#state.fails + 1}." n + n + "%% Called when a test case is skipped by either user action" n + "%% or due to an init function failing." n + "on_tc_skip(Suite, TC, Reason, State) ->" n + " State#state{skips = State#state.skips + 1}." n + n + "%% Called when the scope of the CTH is done" n + "terminate(State) ->" n + " logger:notice(\"~s is done: ~p~n\", [?MODULE, State])." n + n + ) + "*The template of a library module. + Please see the function `tempo-define-template'.") + ;; Skeleton code: ;; This code is based on the package `tempo' which is part of modern |