140 lines
4.2 KiB
Markdown
140 lines
4.2 KiB
Markdown
gm_ctflow
|
|
=====
|
|
|
|
A helper application for cross-testcase test flows in Common Test
|
|
|
|
Description
|
|
----
|
|
When testing complex systems it often feels convenient to organize test
|
|
sequences as a sequential group where a series of test cases perform a
|
|
flow of operations. A challenge may arise when the results from one
|
|
test case need to be reused by later cases - Common Test does cater for
|
|
this through the `save_config` return.
|
|
|
|
However, occasionally, one also needs to have bespoke servers running
|
|
for the duration of such a sequence, and this tends to lead to hacking
|
|
a plain process with a custom protocol.
|
|
|
|
This application attempts to make that a bit more structured.
|
|
In its initial version, there are a few different APIs:
|
|
|
|
* `gm_ctflow_fun`, where one can instantiate a stateful handler fun,
|
|
which can be called by test cases as needed.
|
|
* `gm_ctflow_worker`, a gen_server where the server logic is given
|
|
by a user-provided fun. The server is spawned under a
|
|
`simple_one_for_one` supervisor.
|
|
* `gm_ctflow_reg`, used to register and find `gm_ctflow_worker` processes.
|
|
Complies with the `via` addressing scheme for OTP behaviors.
|
|
* `gm_ctflow`, providing a shared dictionary, logging and status support.
|
|
|
|
The application `gm_ctflow` is intended to be started in
|
|
`init_per_suite/1` and stopped in `end_per_suite/1`, or in
|
|
`init_per_group/2` / `end_per_group/2`.
|
|
|
|
Some examples from `test/gm_ctflow_SUITE.erl`:
|
|
|
|
```erlang
|
|
groups() ->
|
|
[ {statefuns, [sequence], [ init_counter
|
|
, incr
|
|
, check1 %% state = 1
|
|
, incr
|
|
, check2 %% state = 2
|
|
, incr
|
|
, check3 %% state = 3
|
|
]}
|
|
, {workers, [sequence], [ init_counter
|
|
, init_worker
|
|
, append
|
|
, checkl1 %% [A0]
|
|
, append
|
|
, checkl2 %% [A0, A1]
|
|
, append
|
|
, checkl3 %% [A0, A1, A2]
|
|
]}
|
|
].
|
|
```
|
|
|
|
Here, we interleave test cases with checks of the flow fun states.
|
|
This test suite does nothing but exercize the flow funs, but normally,
|
|
these funs would be used to e.g. spawn an endpoint needed for the tests,
|
|
or thread some state through a test sequence.
|
|
|
|
The `gm_ctflow` application is started and stopped per-group here:
|
|
```erlang
|
|
init_per_group(_Grp, Config) ->
|
|
ok = application:start(gm_ctflow, permanent),
|
|
Config.
|
|
|
|
end_per_group(_Grp, _Config) ->
|
|
ok = application:stop(gm_ctflow),
|
|
ok.
|
|
```
|
|
|
|
This resets the state and removes all helper processes for each group.
|
|
|
|
A simple way to keep track of the flow state:
|
|
|
|
```erlang
|
|
init_per_testcase(_Case, Config) ->
|
|
gm_ctflow:status(),
|
|
Config.
|
|
|
|
end_per_testcase(_Case, _Config) ->
|
|
gm_ctflow:status(),
|
|
ok.
|
|
```
|
|
|
|
This gives output like this, from the `gm_ctflow_SUITE:incr/1` testcase:
|
|
```
|
|
*** User 2026-05-30 13:06:02.679 ***🔗
|
|
== Summary for flow my_counter
|
|
= Worker State: - none -
|
|
= Fun State: 0
|
|
= State: - none -
|
|
= Log History:
|
|
|
|
2026-05-30 13:06:02.654:
|
|
New flow: my_counter
|
|
|
|
|
|
|
|
*** User 2026-05-30 13:06:02.679 ***🔗
|
|
my_counter[<0.332.0>]: F(incr, 0) -> {ok,1,1}
|
|
|
|
*** User 2026-05-30 13:06:02.679 ***🔗
|
|
== Summary for flow my_counter
|
|
= Worker State: - none -
|
|
= Fun State: 1
|
|
= State: - none -
|
|
= Log History:
|
|
|
|
2026-05-30 13:06:02.654:
|
|
New flow: my_counter
|
|
|
|
2026-05-30 13:06:02.679:
|
|
F(incr, 0) -> {ok,1,1}
|
|
```
|
|
|
|
Normally, of course, there would be more going on in the test, making the
|
|
bookends a bit less dominant, but here we see the initial state of the flow
|
|
`my_counter`: The "Fun state" is `0`, while there's no worker, and no shared state.
|
|
The log history shows one previous message, from instantiating the fun.
|
|
|
|
The next log output shows the effect of calling the fun. Log messages produced
|
|
with `gm_ctflow:ct_log/[2,3]` go both to `ct:log/2` and the log history. The
|
|
flow is derived automatically, if possible, if not provided.
|
|
|
|
In the ending `status` output, we see the accumulated history, with timestamps
|
|
to make it easier to find the context in CT or SUT logs.
|
|
|
|
Build
|
|
-----
|
|
|
|
$ rebar3 compile
|
|
|
|
Test
|
|
-----
|
|
|
|
$ rebar ct
|