summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorFabian Bergström <fabian.bergstrom@gmail.com>2022-11-06 10:06:33 +0100
committerFabian <fabian@fmbb.se>2022-12-28 22:17:36 +0100
commit0631c7f74f1c3abac463c0d2f6d47c0bc7c6876f (patch)
tree34b9e8bccb2e17c21a547229b6d48813aea177b5 /lib
parent19bce3c6b734538dc7d5c3482f48703bd13691e6 (diff)
downloaderlang-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.xml203
-rw-r--r--lib/tools/emacs/erlang-skels.el103
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