mirror of
https://github.com/AdaCore/OpenUxAS.git
synced 2026-02-12 13:07:16 -08:00
This commit makes significant effort towards refactoring the repository so that it is in a cleaner and more consistent state going forward. Since we are now explicitly expecting multiple languages to be used for OpenUxAS, we have reorganized `src` accordingly. Likewise, we have reorganized `tests`. This is a candidate for the rebaseline of afrl-rq/OpenUxAS.
285 lines
10 KiB
Markdown
285 lines
10 KiB
Markdown
|
||
# 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)
|
||
|
||
|
||
|
||
<a id="org2ee62c4"></a>
|
||
|
||
# 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).
|
||
|
||
|
||
<a id="orgaca9272"></a>
|
||
|
||
# Integration of GCOV in OpenUxAS
|
||
|
||
|
||
<a id="org9e8e07e"></a>
|
||
|
||
## Compilation Options
|
||
|
||
The following compilation options were used: **-fprofile-arcs
|
||
-ftestcoverage**.
|
||
|
||
|
||
<a id="orgd43fff9"></a>
|
||
|
||
## 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 <thread>
|
||
#include <locale>
|
||
|
||
+#ifdef GCOV_MODE
|
||
+#include <csignal>
|
||
+#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).
|
||
|
||
|
||
<a id="org3b5fd4b"></a>
|
||
|
||
## 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.
|
||
|
||
|
||
<a id="org21977c7"></a>
|
||
|
||
# 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.
|
||
|
||
|
||
<a id="org4a0f819"></a>
|
||
|
||
## 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.
|
||
|
||
|
||
<a id="orge1dfed2"></a>
|
||
|
||
## 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.
|
||
|
||
|
||
<a id="orgc3ce8fd"></a>
|
||
|
||
## 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.wait<sub>for</sub><sub>msg</sub> 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.
|