Module eqc_cluster

This module provides functions for testing operations with side-effects, which are specified via a collection of abstract state machines with external interactions.

Copyright © Quviq AB, 2012-2023

Version: 1.46.3

Description

This module provides functions for testing operations with side-effects, which are specified via a collection of abstract state machines with external interactions. I.e. it is possible to compose several eqc_component into one cluster.

Modules using eqc_cluster should include -include_lib("eqc/include/eqc_cluster.hrl") to import the functions that eqc_cluster provides.

The majority of the modelling work is put into the models of the components. To cluster components (given that they actually fit together) is straighforward; all that is needed is to define two simple callbacks, and to write a property.

Callback Functions

The client module specifies an abstract state machine by defining (either directly in the ungrouped style, or indirectly by the grouped style parse transform) the following functions:

Respecting API's

When the components are tested in isolation, all of its API functions are normally tested. However, once components are composed into a cluster it is often the case that some API functions are internal. For example, in the fictive communication stack the frame layer has a send function. However, this send function is only called from the packet layer; thus, once the two modules are clustered it should not be called directly from the outside.

To specify this, we should add (given that we are using the grouped modelling style for the component):
send_callers() -> [packet_eqc].

I.e. we specify that this API function is only called from packet_eqc. The callers definition is only taken into account in a cluster.

What Property Should We Test?

This module does not define any properties to test, it only provides functions to make defining such properties easy. A client module will normally contain a property resembling this one, which generates a command sequence using the client state machine, and then tests it:
prop_cluster_correct() ->
   ?SETUP(fun() -> start_mocking(api_spec(), components()), fun() -> ok end end,
   ?FORALL(Cmds,commands(?MODULE),
     begin {H,S,Result} = run_commands(Cmds),
           pretty_commands(?MODULE,Cmds,{H,S,Result},
                           Result==ok)
     end)).
However, in any particular case we may wish to add a little to this basic form, for example to collect statistics, to clean up after test execution, or to setup mocking before test execution. It is to allow this flexibility that the properties to test are placed in the client module, rather than in this one.

Data Types

api_arg_c()

api_arg_c() = #api_arg_c{type = atom() | string(), stored_type = atom() | string(), name = atom() | string() | {atom(), string()}, dir = in | out, buffer = false | true | {true, non_neg_integer()} | {true, non_neg_integer(), string()}, phantom = boolean(), matched = boolean(), default_val = no | string(), code = no | string()}

api_fun_c()

api_fun_c() = #api_fun_c{name = atom(), classify = any(), ret = atom() | api_arg_c(), args = [api_arg_c()], silent = false | {true, any()}}

api_fun_erl()

api_fun_erl() = #api_fun{name = atom(), classify = any(), arity = non_neg_integer(), fallback = boolean(), matched = [non_neg_integer()] | fun((any(), any()) -> boolean()) | all}

api_module()

api_module() = #api_module{name = atom(), fallback = atom(), functions = [api_fun_erl()] | [api_fun_c()]}

Function Index

api_spec/1Computes a cluster api_spec by doing a merge of the api_specs of the components.
commands/1Generates a list of commands, using the cluster defined in module Mod.
commands/2Generates a list of commands, using the cluster defined in module Mod, where custom initial states for some or all components are given by S.
run_commands/1Runs a list of commands, checking preconditions before, and postconditions after executing each command.
run_commands/2Behaves like run_commands/1, but also takes an environment containing values for additional variables that may be referred to in test cases.

Function Details

api_spec/1

api_spec(Mod::atom()) -> #api_spec{language = erlang | c, mocking = atom(), config = any(), modules = [api_module()]}

Computes a cluster api_spec by doing a merge of the api_specs of the components. Components are given by Mod:components().

commands/1

commands(Mod::module()) -> eqc_gen:gen([eqc_statem:command()])

Generates a list of commands, using the cluster defined in module Mod.

commands/2

commands(Mod::module(), S::[{atom(), term()}]) -> eqc_gen:gen([eqc_statem:command()])

Generates a list of commands, using the cluster defined in module Mod, where custom initial states for some or all components are given by S.

run_commands/1

run_commands(Cmds::[eqc_statem:command()]) -> {eqc_statem:history(), eqc_statem:dynamic_state(), eqc_statem:reason()}

Runs a list of commands, checking preconditions before, and postconditions after executing each command. The result contains the history of execution, the state after the last command that was executed successfully, and the reason execution stopped.

run_commands/2

run_commands(Cmds::[eqc_statem:command()], Env::[{atom(), term()}]) -> {eqc_statem:history(), eqc_statem:dynamic_state(), eqc_statem:reason()}

Behaves like run_commands/1, but also takes an environment containing values for additional variables that may be referred to in test cases. For example, if Env is [{x,32}], then {var,x} may appear in the commands, and will evaluate to 32. The variables names must be atoms (unlike generated variable names, which are numbers).


Generated by EDoc