Testing¶
To allow easy testing of the generated code, Coco provides support for automatically generating GMock-compatible mock implementations of external components. These allow you to easily write tests that check that the correct function calls and signals are being produced. For example, the mock generator generates methods that make it simple to:
- Create expectations that functions are called, or signals are received;
- Send signals as GMock actions;
- Indicate that the mock state machine has terminated.
Mock Generation¶
To enable generation of mocks, either set generator.cpp.generateMocks
, or set the coco --mocks
option. This will
cause the code generator to emit mocks in separate files: for each generated file A.`h` or ``A.cc
, the code
generator will additionally generate two extra files AMock.h
and AMock.cc
. The mock files contain only
mock-related code, and should only be compiled into testing libraries, rather than release libraries.
For each external component E
, the generated mock files will contain a C++ class called EMock
that subclasses
E
and implements all of its virtual methods, as described in External Components, by using the GMock
mocking macros. Further, for each generated function of the form send_x
defined in the C++ generated code for E
,
the generated code will contain a function called send_x_action
that creates a
GMock action that, when
executed, will send the corresponding signal.
For example, consider the following port and external component:
port PPinger {
function ping(x : BoundedInt<0, 5>) : Bool
function terminate() : Nil
outgoing signal pong(x : BoundedInt<0, 5>)
machine M { ... }
}
The generated mock code will contain:
class PingerMock : public Pinger {
public:
testing::PolymorphicAction<SendMachineTerminatedAction> coco_shutdown_action() { ... }
testing::PolymorphicAction<client_pongAction> send_client_pong_action(int x) { ... }
MOCK_METHOD(bool, client_ping, (int), (override));
MOCK_METHOD(void, client_terminate, (), (override));
MOCK_METHOD(void, execute_start, (), (override));
};
This can be utilised as follows, assuming pinger_mock
is an instance of PingerMock
:
EXPECT_CALL(pinger_mock, client_ping(2))
.WillOnce(DoAll(pinger_mock.send_client_pong_action(2), Return(false)));
The above would create an expectation that:
ping
is called exactly once, passing the value 2;- When
ping
is called, will send thepong(2)
signal to all subscribers ofpinger_mock
. - Will return
false
to its caller.
The generated mocks can also be used to create expectations on signals being received. For example, suppose we have the following external component defined:
external component Client {
pinger : Required<PPinger>;
}
This will result in the following generated code:
class ClientMock : public Client {
public:
testing::PolymorphicAction<SendMachineTerminatedAction> coco_shutdown_action() { ... }
MOCK_METHOD(void, pinger_pong, (int), (override));
MOCK_METHOD(void, execute_start, (), (override));
};
This can be used as follows, assuming client_mock
is an instance of ClientMock
:
EXPECT_CALL(client_mock, pinger_pong(2)).WillOnce(client_mock.coco_shutdown_action());
This creates an expectation that:
- The
pong
signal is received exactly once with the value2
; - When the
pong
signal is received,client_mock
will terminate.
Full Example¶
The following Coco encodes a simple echo component that raises a signal with the same value as was passed to the function:
port PPinger {
function ping(x : BoundedInt<0, 5>) : Bool
function terminate() : Nil
outgoing signal pong(x : BoundedInt<0, 5>)
machine M {
terminate() = setNextState(Terminated)
ping(x : BoundedInt<0, 5>) = {
pong(x);
return x == 0;
}
}
}
component Echo {
client : Provided<PPinger>;
pinger : Required<PPinger>;
machine M {
client.ping(x : BoundedInt<0, 5>) = pinger.ping(x)
client.terminate() = {
pinger.terminate();
setNextState(Terminated);
}
pinger.pong(x : BoundedInt<0, 5>) = client.pong(x)
}
}
external component Client {
pinger : Required<PPinger>;
}
external component Pinger {
client : Provided<PPinger>;
}
component System {
client : Client;
echo : Echo;
pinger : Pinger;
init() = {
connect(client.pinger, echo.client);
connect(echo.pinger, pinger.client);
}
}
The following code creates a test that checks that if a single ping
call is made, then Client
will receive a single
signal back.
#include "gtest/gtest.h"
#include "coco/stream_logger.h"
#include "DocExampleMock_c++11.h"
TEST(DocExample, Echo) {
ClientMock client_mock;
PingerMock pinger_mock;
System::Arguments arguments;
arguments.client = &client_mock;
arguments.pinger = &pinger_mock;
System system(arguments);
system.set_name("root");
system.set_logger_recursively(coco::StreamMachineLogger::cout());
system.coco_start();
const int value = 2;
EXPECT_CALL(pinger_mock, client_terminate()).WillOnce(pinger_mock.coco_shutdown_action());
EXPECT_CALL(pinger_mock, client_ping(value))
.WillOnce(DoAll(pinger_mock.send_client_pong_action(value), Return(value == 0)));
EXPECT_CALL(client_mock, pinger_pong(value)).WillOnce(client_mock.coco_shutdown_action());
client_mock.pinger().connected().ping(value);
client_mock.pinger().connected().terminate();
}