Files
M. Anthony Aiello 5712513b30 Bootstrap Integration (#24)
* Integrate OpenUxAS-bootstrap into OpenUxAS

This commit represents a complete, working integration along with
several key enhancements. Note that the README is entirely unchanged and
not suitable.

***NOTE: the build will fail currently because of a problem with e3***
e3 provides no way to modify the list of excluded globs for sync_tree;
this will need to be fixed.

The bootstrap script works and will download and install needed support
for anod. The instructions printed at the end of the install are out of
date; the user doesn't need to do anything more than call anod with

    ./anod build uxas

The top-level anod script takes care of activating the python venv and
then calls the python anod script.

The user can also `source anod`, which will then register anod as a
shell function, allowing simpler invocation and a working setenv
command.

Anod's devel setup no longer supports uxas, since that's superfluous. It
does support lmcpgen and amase. They are placed in `develop` as before,
although finding a better name for the containing directory seems nice.

run-example works, as does resources/RunLmcpGen

make works

VS Code integration works

There is definite room for improvement. Specifically, factoring the
informtion about repository structure into some central set of
includable scripts seems like it should be really useful. Likewise,
anod's support should be a real python module so that it's easier to
use.

Finally, there's no reason not to automatically install the python venv
and all needed support the first time anod is invokved (including from
make). This would eliminate a needed step by the user. We could still
retain the bootstrap script if desired.

* Rename and create constants for repo structure

This commit reorganizes the directories a bit and introduces better
handling of pathnames through constants defined in paths.sh.
Unfortunately, because of the way python handles environment variables,
(and because we can't necessarily assume that python scripts are always
invoked through shell scripts that call paths.sh) we have to duplicate a
lot of the path information; this will hopefully be addressed in a
future commit.

* Refactor infrastructure and add github workflows.

OpenUxAS anod support is now a proper python module named `uxas`. Paths
within the uxas module are refactored. The module directory under
infrastructure/uxas has the expected layout for a python module project
and includes a tox.ini file so that tox can be used for development and
in CI.

infrastructure/install is now a script that calls into python scripts in
infrastructure/install-libexec; these are not a proper python module,
since that would create a chicken-and-egg problem. There is a tox.ini
file here, too, for development and CI.

infrastructure/bootstrap is the new bootstrap script, which is less
useful now but still usable.

Scripts have been updated as needed to take advantage of the uxas python
module. In particular, anod and run-example check for the python venv
and call into infrastructure/install if needed.

* Add Ada support and improve scripts

Added support for Ada back in by reactivating and updating
install-gnat.py Added Ada build-prove workflow

Refactored venv activation to paths.sh

Added ensure_gnat, which will check for gnat on the path (via which) or
will add a local install of GNAT CE to the path (if it's there) or will
offer to install GNAT CE via install-gnat.py This means the user can
always choose a different GNAT and it will be used; otherwise, the local
install of GNAT CE will be used.

Added wrapper shell scripts for the python testing and proof scripts so
that the environment can be automatically configured.

Updated the C++ workflow

Note: sourcing anod and then running anod as a shell function isn't
behaving quite right in either bash or zsh (and for different reasons).
Needs further investigation.

* Add debug output to print commands executed.

This should address concern that users won't be able to figure out what
the scripts are doing if things break and require manual intervention.

Also modify the workflows so that they will run if critical scripts are
updated.

* Enhance run-example to better search for binaries.

run-example now follows similar logic to run-tests in searching for
binaries. uxas and uxas-ada are also special-cased so that searching
locally and in anod should succeed as expected. The search order is:

1. path (using `which`)
2. local (obj/cpp/uxas or src/ada/uxas-ada)
3. anod (inside uxas-release or uxas-ada-release)

run-example also follows similar logic in search for OpenAMASE:

1. specified via argument
2. local (develop/OpenAMASE)
3. anod (inside amse)

I believe these have all been tested.

Help / error strings have been updated so that they are consistent with
the new infrastructure.

Debug output has been improved to be more consistent with that provided
by e3 (but doesn't actually directly use e3, still).

This commit also improves the .gitignore for ada support and updates the
Makefile so that it removes the binary on clean.

* Update README and scripts.

Create a temporary README with testing instructions.

Also fix some issues in the debug output of the scripts (the $ was not
escaped properly in some instances).

* Relativize path to anod in run-example

This looks nicer and better supports the common case: in which the user
runs run-example from the repo root.

* Fix python version in Makefile

Makefile was using python to determine the platform; this was one of a
few remaining non-python3 uses of `python`. Having fixed it, things
seem to work smoothly on a machine where python ≠ python3.

* Don't install GNAT CE by default.

Most users won't want to build Ada, so offering to install GNAT CE by
default is likely to just be confusing.

* Change the way Java is installed.

Rather than installing Java (OpenJDK 13) by way of anod in the sandbox,
instead install Java (OpenJDK 11) by way of apt. This results in Java
being available system-wide and avoids having to put ant/java on the
path using anod commands for things like generating LMCP.

Update workflows to install Java using the appropriate github action.
This should reduce the number of spurious build failures we've
encountered.

* Fix run-example issue for uxas-ada.

The shared library for uxas-ada wasn't being placed on the path.
Additionally, there was an unconditional and unlogged exit leftover from
testing in the python script.

* Update anod self-install process to be automatic.

Rather than running the install script in interactive mode, give the
user a full message up front that explains what will happen, followed by
a choice to continue. Then, run the install script in automatic mode.

This avoids some confusion identified whereby the script seems to ask
multiple times to do things in a way that's not particularly helpful
when the expectation is that it will just "take care of things" for the
user.

* Update the README

This new version of the README is tailored to the changes that were made
to integrated -bootstrap into this repository. It is intended to be
minimal and simple. Detail will be provided later to the documentation
site that AFRL is setting up. Once that site is live, links will be made
from this README to that site.

* Improve script with debug_and_run

Rather than printing the string for a command and then separately
issuing the command, we pass the string form of the command to a debug
print method and then eval the string. This removes duplication and
ensures that the printed command really is the command that is executed.

* Minor improvements to README

Fix a couple of typos.
2021-04-01 15:30:51 -04:00
..
2020-07-24 08:39:41 -04:00
2020-07-24 08:39:41 -04:00
2020-07-24 08:39:41 -04:00
2021-04-01 15:30:51 -04:00
2021-04-01 15:30:51 -04:00

Table of Contents

  1. Background
  2. Integration of GCOV in OpenUxAS
    1. Compilation Options
    2. Modifications to UxAS executable
    3. Usage during testsuite run
  3. How to write tests using the pylmcp module
    1. Setup UxAS
    2. Launch server
    3. Execute the test

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).

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 wont 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 <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).

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. Lets 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 doesnt 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.