# Table of Contents
1. [Background](#org2ee62c4)
2. [Integration of GCOV in OpenUxAS](#orgaca9272)
1. [Compilation Options](#org9e8e07e)
2. [Modifications to UxAS executable](#orgd43fff9)
3. [Usage during testsuite run](#org3b5fd4b)
3. [How to write tests using the pylmcp module](#org21977c7)
1. [Setup UxAS](#org4a0f819)
2. [Launch server](#orge1dfed2)
3. [Execute the test](#orgc3ce8fd)
# Background
**Gcov** is a test coverage program. Use it in concert with **GCC** to
analyze your programs to help create more efficient, faster running
code and to discover untested parts of your program. You can use
**gcov** as a profiling tool to help discover where your optimization
efforts will best affect your code. You can also use gcov along with
the other profiling tool, gprof, to assess which parts of your code
use the greatest amount of computing time. (from gcov documentation at
[link](https://gcc.gnu.org/onlinedocs/gcc/Gcov-Intro.html#Gcov-Intro)).
The main advantage of **gcov** is that it is freely available as part of
the **GCC** toolchain. The tool can be used for statement coverage at
source level and decision coverage at object level. An important note
is that this tool cannot be used in the context of DO178 certification
because of some limitations.
As **gcov** uses instrumentation to gather coverage information the
program must be compiled with **GCC** using some special options. Also,
**gcov** requires a file system to dump coverage information (so it won’t
be suitable for purely embedded development).
# Integration of GCOV in OpenUxAS
## Compilation Options
The following compilation options were used: **-fprofile-arcs
-ftestcoverage**.
## Modifications to UxAS executable
The UxAS executable is meant to run indefinitely. **Gcov** by default
dumps all the coverage information at program termination. To ensure
that the UxAS program dumps the coverage information during testing, we
had to add some code that ensures that when receiving a SIGINT signal
we do manually the dumps. The resulting patch was:
diff --git a/src/UxAS_Main.cpp b/src/UxAS_Main.cpp
index 9e3fe289..0b4951d9 100644
--- a/src/UxAS_Main.cpp
+++ b/src/UxAS_Main.cpp
@@ -37,6 +37,10 @@
#include
#include
+#ifdef GCOV_MODE
+#include
+#endif
+
#define ARG_CFG_PATH "-cfgPath"
#define ARG_VERSION "-version"
#define ARG_RUN_UNTIL "-runUntil"
@@ -47,6 +51,15 @@
#define BEFORE_LOG_MANAGER_INITIALIZATION_LOG_MESSAGE(message) std::cout << message << std::endl; std::cout.flush();
+#ifdef GCOV_MODE
+extern "C" void __gcov_flush();
+
+void signalHandler( int signum ) {
+ __gcov_flush();
+ std::exit(signum);
+}
+#endif
+
int
main(int argc, char** argv)
{
@@ -67,6 +80,10 @@ main(int argc, char** argv)
// declare relative paths of configuration files with default values
// example arguments: -cfgBasePath ./cfg/cfgbase2.xml
//
+
+ #ifdef GCOV_MODE
+ signal (SIGINT, signalHandler);
+ #endif
std::string cfgPath{"cfg.xml"};
uint32_t runUntil_sec = 0;
In order not to include the code when not performing coverage
activities, the additional code is controlled by the C macro `GCOV_MODE`
(code is enabled by adding `-DGCOV_MODE=1` during compilation).
## Usage during testsuite run
Gcov produces various files both at compilation time and execution
time:
1. \*.gcda: this is the coverage information produce during the execution
2. \*.gcno: contains information to reconstruct the basic block graphs and assign source line numbers to blocks. This file is produced during compilation and placed with the object files
3. \*.gcov: file produced by gcov once the gcda and gcno files are available. These files are basically annotated source files
During test-suite runs, we use the options: `GCOV_PREFIX` and
`GCOV_PREFIX_STRIP` in order to produce the gcda files inside the
directory specific to the testsuite. We ensure that at the beginning
of each testsuite run, the gcda files are reset.
Once all tests have been executed, we gather gcno files along with the
produced gcda files and call the gcov tool to generate \*.gcov files
(one for each source). Some of the produced files are then ignored as
not part of the project (system includes, …, …)
For more details see the function `dump_gcov_summary` in `run-tests`. The
function is quite generic. The only part that might need adjustment is
the invocation of gcov itself. The directory from where it should be
invoked might depend on the build itself as some path information (to
locate sources) stored in the gcno files might be relative paths.
# How to write tests using the pylmcp module
In the testsuite, each test case will define a specific scenario. In
the following paragraphs, we will explain how the test
`arv/correct_automation_request/test.py` has been created. It can be
modified to create other tests based on the same pattern.
## Setup UxAS
The first thing to do is to configure the bridge.
bridge_cfg = UxASConfig()
bridge_cfg += AutomationRequestValidator()
We initialize the `bridge_cfg` variable to the base Object that contains
the configuration, `UxASConfig()`. Then, the desired service is added to
the configuration. Here, it is the AutomationRequestValidator. The
services are defined in `pylmcp/uxas.py`. It is possible to add more
service just by creating a new class in this package. The script will
create the corresponding `config.xml` passed to UxAS.
## Launch server
After adding the configuration to `bridge_cfg`, the server can be
launched along with UxAS
with Server(bridge_cfg=bridge_cfg) as server:
try:
After this line, the python script UxAS is running and it is possible
to send messages on the bridge with the python code.
## Execute the test
The first thing done is to send configuration messages to the ARV
service so it can respond to the Automation Request.
for obj in (Object(class_name='AirVehicleConfiguration', ID=400,
randomize=True),
Object(class_name='AirVehicleConfiguration', ID=500,
randomize=True),
Object(class_name='AirVehicleState', ID=400,
randomize=True),
Object(class_name='AirVehicleState', ID=500,
randomize=True),
Object(class_name='KeepInZone', ZoneID=1,
randomize=True),
Object(class_name='KeepOutZone', ZoneID=2,
randomize=True),
Object(class_name='OperatingRegion', ID=3,
KeepInAreas=[1], KeepOutAreas=[2]),
Object(class_name='cmasi.LineSearchTask', TaskID=1000,
randomize=True),
Object(class_name='TaskInitialized', TaskID=1000,
randomize=True)):
server.send_msg(obj)
time.sleep(0.1)
We send nine messages on the bridge. Each message corresponds to an
instance of an Object. Let’s focus on the first one, and decompose
it:
Object(class_name='AirVehicleConfiguration', ID=400,
randomize=True)
The class name corresponds to the message name in the mdms files. When
it is the only message with this name, it is possible to only put the
name, and not the full path. However, when message names can clash, it
is needed to put the path that disambiguates it, e.g. for
LineSearchTask, we use `class_name='cmasi.LineSearchTask'`. Then, it is
possible to set the attributes that need to be re-used later, or that
we want to keep fixed. Here, it is done with the ID attribute that
will be used in the Automation Request (in the requested `EntityList`).
Finally, all attributes that are not set can be randomized. This is
enabled by adding randomize=True to the object initialization.
For each created `Object`, we send it on the bridge using the
`server.send_msg(Object)` method, and wait for 0.1 second to give some
time to UxAS to process the message.
After the setup of UxAS internal data (Vehicle states, configurations,
regions, …) has been done, we send the Automation Request:
obj = Object(class_name='cmasi.AutomationRequest',
TaskList=[1000], EntityList=[400, 500],
OperatingRegion=3, randomize=True)
server.send_msg(obj)
As said before, we can reuse the fixed attributes in the message to
create a correct Automation Request to send to the ARV service. Since
it is well formed, the service is supposed to send a corresponding
Unique Automation Request. We can catch it with the
server.waitformsg method:
msg = server.wait_for_msg(
descriptor='uxas.messages.task.UniqueAutomationRequest',
timeout=10.0)
If the server doesn’t see an UniqueAutomationRequest message within 10
seconds, it will return an error and the test will end. When it is
sent, the message is stored in the msg variable.
It enables the user two things:
- First, it is possible to assert properties on the received message. In the test, we assert the fact that the field OriginalRequest of the UniqueAutomationRequest is equal to the sent AutomationRequest:
assert(msg.obj['OriginalRequest'] == obj)
- Second, it can be used to store data to further send coherent messages. In the test, the ARV service generates a unique ID corresponding to the Automation Request. If we want to check the nominal behavior of the ARV, it is necessary for it to receive a UniqueAutomationResponse with a ResponseID that is equal to the RequestID of the UniqueAutomationRequest within 5 seconds.
unique_id = msg.obj.data["RequestID"]
obj = Object(
class_name='uxas.messages.task.UniqueAutomationResponse',
ResponseID=unique_id, randomize=True)
server.send_msg(obj)
By storing the received RequestID, we are able to create a correct
UniqueAutomationResponse.
The only action left is waiting for the corresponding
AutomationResponse, and test that it is equal to the OriginalResponse
in the sent UniqueAutomationResponse.
msg = server.wait_for_msg(descriptor="afrl.cmasi.AutomationResponse",
timeout=10.0)
assert (msg.descriptor == "afrl.cmasi.AutomationResponse")
assert (msg.obj == obj['OriginalResponse'])
After executing the test, gcov and lcov can generate a coverage report
that will point to the unread lines. It is possible to create as many
tests as needed to cover the entire file.