Skip to content

Latest commit

 

History

History
144 lines (83 loc) · 8.32 KB

state_ref.md

File metadata and controls

144 lines (83 loc) · 8.32 KB

Stateful testing - reference

All commands that are part of the state testing framework reside under the rc::state namespace. To use them, you must include the rapidcheck/state.h header. Type signatures are not to be taken literally.

Testing

void check(Model initialState, Sut sut, GenFunc f)

void check(MakeModel makeInitialState, Sut sut, GenFunc f)

Generates a valid command sequence for an initial state and then runs that sequence on sut. Any assertions triggered when running the command sequence will cause the property to fail. For more information about the generation function parameter, see rc::state::gen::commands.

There are two overloads, one that takes the initial state as an immediate value and and another that takes a callable that returns the initial state.

This function must be used inside a property, it cannot be used standalone.

Command<Model, Sut>

Represents an operation in the state testing framework. The Model type parameter is the type of the model that models Sut which is the actual System Under Test. These can also be accessed through the Model and Sut member type aliases.

virtual void checkPreconditions(const Model &s0) const

If your command is not valid for all states, you must implement this method to assert preconditions. Preconditions can be asserted using any of the discarding macros such as RC_PRE or RC_DISCARD. If the command is discarded, RapidCheck will simply try to generate a new one. This method is intended to be overridden but has a default implementation that does nothing which is what you want if your command has no preconditions.

While the model state is passed as const, this doesn't prevent modification of the model if it, for example, is a shared_ptr or similar. Regardless, modifying the model state in this method leads to undefined behavior.

virtual void apply(Model &s0) const

Applies this command to the given state. The effect of this command on the model should be equivalent to this commands effect on the System Under Test. This method is intended to be overridden but has a default implementation that does nothing, something that can be useful for commands that do not modify state.

virtual void run(const Model &s0, Sut &sut) const

Applies this command to the given System Under Test. The state before this command has been applied is also passed in. If you need the post state, you can get this using the nextState convenience method. This is the method in which to place your assertions (RC_ASSERT et al.). This method is intended to be overridden but has a default implementation that does nothing.

While the model state is passed as const, this doesn't prevent modification of the model if it, for example, is a shared_ptr or similar. Regardless, modifying the model state in this method leads to undefined behavior.

virtual void show(std::ostream &os) const

Outputs a string representation of the command to the given output stream. The default implementation outputs the type name (via RTTI) but if your command has any sort of parameters, you will likely want to override this with a custom implementation to include those.

Model nextState(const Model &s0) const

Convenience method that calls apply on a copy of the given state and returns it. Saves some keystrokes occasionally since you don't have to allocate a model on the stack. Cannot be overridden, override apply instead.

This method does not work for non-copyable models.

Command sequences

Commands<CommandType>

Type alias for a std::vector<std::shared_ptr<const CommandType>>. Useful since command sequences are generally stored in vectors and individual commands are usually passed by shared_ptr-to-const.

void applyAll(Commands commands, Model &state)

Applies the given commands in sequence to the given model state.

void runAll(Commands commands, Model state, Sut &sut)

void runAll(Commands commands, MakeModel makeState, Sut &sut)

Runs the given commands in sequence on the given System Under Test assuming it is equivalent to the given model state.

There are two overloads, one that takes the state as an immediate value and and another that takes a callable that returns the state.

bool isValidSequence(Commands commands, Model state)

bool isValidSequence(Commands commands, MakeModel makeState)

Returns true if the given command sequence is valid for the given model state, otherwise false.

There are two overloads, one that takes the state as an immediate value and and another that takes a callable that returns the state.

Generators

MakeCommandGenerator gen::execOneOfWithArgs<Ts...>()

Returns a callable that when called returns a generator that randomly constructs one of the commands in Ts. If the command has a constructor that matches the (zero or more) arguments given to the callable, that constructor will be used. Otherwise, the command is assumed to have a default constructor. The constructor can make use of the *-operator just like in a property or in gen::exec.

A common thing to do is to pass the model state as the constructor argument. Here is an example of a command designed for such use:

struct Take : rc::state::Command<MyModel, MySut> {
  std::string item;
  
  explicit Take(const MyModel &s0)
      : item(*rc::gen::elementOf(s0.items)) {}

  // ...
};

Typical usage of execOneOfWithArgs is to create the generation function argument for rc::state::gen::commands or rc::state::check:

MyModel initialState;
MySut sut;
rc::state::check(initialState,
                 sut,
                 rc::state::gen::execOneOfWithArgs<Put, Take, Frobnicate>());

If the model is non-copyable, we can still use this to pass some copyable values of the model state:

MySut sut;
rc::state::check(
    [] { return MyNonCopyableModel(); },
    sut,
    [](const MyNonCopyableModel &state) {
      return rc::state::gen::execOneOfWithArgs<Put, Take, Frobnicate>()(
          state.status(), state.remaining());
    });

With the construction shown above, the commands would take the return types of state.status() and state.remaining() as parameters to their constructors.

Some notes:

  • In the first example, we also have to assert that item exists in the model in our apply implementation since a generated command might be used for a different state, in particular during shrinking where the state may change because the commands prior in the sequence may be shrunk or removed. For more about this, see the Stateful testing section.
  • Discarding macros such as RC_PRE and RC_DISCARD can be used in the constructor of a command to immediately discard the command if it is not valid for the given state.

Gen<std::shared_ptr<const Command>> gen::execOneOf<Ts...>(Model state)

Note: This function is deprecated, use rc::gen::execOneOfWithArgs instead.

Returns a generator that randomly constructs one of the commands in Ts. The constructor can make use of the *-operator just like in a property or in gen::exec. If the command has a constructor taking a model state, state will be passed to it on construction. Otherwise, the command is assumed to have a default constructor.

Gen<Commands<Cmd>> gen::commands(Model initialState, GenFunc f)

Gen<Commands<Cmd>> gen::commands(MakeModel makeInitialState, GenFunc f)

Generates a valid sequence of commands for the given model state.

f should be a function that takes a Model as an argument and returns a Gen<std::shared_ptr<const Command<Model, Sut>>>. This type looks complicated but it is essentially a generator of shared_ptrs to appropriate commands. The generator does not have to yield only valid commands since RapidCheck will simply try again if a command is not valid. However, performance will be better if fewer commands have to be discarded, of course. The easiest way to create such a function is to use the gen::execOneOfWithArgs function.

There are two overloads of this function, one that takes the state as an immediate value and and another that takes a callable that returns the state.

Since the System Under Test is not specified, the type of the generated commands must be explicitly specified. It cannot be deduced.

Utilities

bool isValidCommand(Command command, const Model &state)

Returns true if the given command is valid for the given model state, false otherwise.