This project demonstrates how to setup a development workflow with cloud-based Continuous Integration (CI) for testing an embedded application.
The embedded program implements a set of simple unit tests for execution on a Arm Fixed Virtual Platform (FVP) models. Code development and debug can be done locally, for example with CMSIS-Build and Keil MDK tools.
Automated test execution is managed with GitHub Actions and gets triggered on every code change in the repository. The program gets built and run on Arm Virtual Hardware cloud infrastructure in AWS and the test results can be then observed in repository's GitHub Actions.
Folder or Files in the example | Description |
---|---|
./ |
Folder with the Basic embedded application example |
./RTE/Device/SSE-300-MPS3/ |
Folder with target-specific configurable files provided by software components used in the project. Includes system startup files, linker scatter file, CMSIS-Driver configurations and others. See Components in Project in µVision documentation. |
./main.c ./basic/retarget_stdio.c |
Application code files |
./basic.debug.cprj |
Project file in .cprj format |
./fvp_config.txt |
Configuration file for running the Virtual Hardware model from command line |
./mdk_config.txt |
Configuration file for running the Virtual Hardware model model from within MDK |
./build.py |
Python script for project build, execution and analysis of test results |
./avh.yml |
File with instructions for AVH Client for Python |
./requirements.txt |
File with the list of Python packages required for execution of ./build.py |
The sections below list the installation and configuration requirements for both supported use cases:
- execute the tests manually on a local machine
- run tests automatically in the AWS cloud
For building, running and debugging on the local machine one needs to install the following tools.
- IDE for local build and debug (Windows only):
- Keil MDK, Professional Edition
- alternatively, for command-line build without debug (Linux, Windows):
- Arm Compiler 6 for Embedded (also available with Keil MDK (Windows) or Arm DS (Linux, Windows))
- CMSIS-Build command-line building tools provided with the CMSIS_5 release. Additionally requires for its operation:
- Python 3.9 (optional, needed only when using
build.py
)- with packages defined in
./requirements.txt
, that shall be installed with:
pip install -r requirements.txt
- with packages defined in
- Arm Virtual Hardware (AVH) model of Arm Corstone-300 sub-system.
Note that CMSIS software packs used in the project will be requested and installed automatically when using Keil MDK or CMSIS-Build.
Following setup is required for building and running the example program in the cloud as part of a CI workflow.
- Amazon Web Service (AWS) account with:
- Amazon EC2 (elastic cloud) access
- Amazon S3 (storage) access
- Registration to access AVH Amazon Machine Image AVH AMI
- AWS Authentication (either way):
- AMI User setup for scripted API access OR
- Use Assume Role with Web Identity (Please see Configuring OpenID Connect in Amazon Web Services)
- GitHub:
- Fork of this repository with at least Write access rights
- If you are using Assume Role with WebIdentify, please change the GitHub Action task
Configure AWS Credentials
to use your role (e.g.arn:aws:iam::720528183931:role/Proj-vht-assume-role
). - Following AWS configuration values stored as
GitHub Secrets
of the forked repository
Secret Name Description AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
Access key pair for the AWS account (as IAM user) that shall be used by the CI workflow for AWS access.
PLEASE NOTE: NOT NEEDED IF YOU ARE ASSUMING ROLE WITH WEB IDENTITYAWS_IAM_PROFILE
The IAM Role to be used for AWS access. AWS_S3_BUCKET_NAME
The name of the S3 storage bucket to be used for data exchange between GitHub and AWS AMI. AWS_DEFAULT_REGION
The data center region the AVH AMI will be run on. For example eu-west-1
.AWS_EC2_SECURITY_GROUP_ID
The id of the VPC security group to add the EC2 instance to. Shall have format sg-xxxxxxxx
.AWS_SUBNET_ID
The id of the VPC subnet to connect the EC2 instance to. Shall have format subnet-xxxxxxxx
.
For developing the tests on the local machine one needs to clone this repository into a local workspace.
Open a command prompt in the local workspace. The following instructions assume
Python is installed. If one doesn't want to go the Python way one can issue the
individual command, manually. The CMSIS-Build command cbuild
and the FVP executable VHT_Corstone_SSE-300_Ethos-U55
are expected in the
system PATH
.
In order to use the build.py
script to conveniently execute the required commands,
one need to install some Python requirements listed in requirements.txt
:
~/AVH-GetStarted/basic $ pip install -r requirements.txt
Once the required Python packages are installed one can run the script to build the example:
~/AVH-GetStarted/basic $ ./build.py -t debug build
[debug](build:run_cbuild) bash -c cbuild.sh --quiet basic.debug.cprj
[debug](build:run_cbuild) bash succeeded with exit code 0
Matrix Summary
==============
target build run
-------- ------- -----
debug success (skip)
~/AVH-GetStarted/basic $ ls -lah Objects/basic.axf
-rw-r--r-- 1 **** 4096 64K Nov 25 10:59 Objects/basic.axf
The build.py
script automatically creates a timestamped archive of the build results
in a file basic-<timestamp>.zip
. One can extract this into another workspace at a
later point in time to inspect the result, e.g. run the tests in a debugger.
Open a command prompt in the local workspace. The following instructions assume Python is installed. If one don't want to go the Python way one can issue the individual command, manually.
~/AVH-GetStarted/basic $ ./build.py -t debug run
[debug](run:run_fvp) VHT_Corstone_SSE-300_Ethos-U55.EXE -q --stat --simlimit 1 -f fvp_config.txt Objects/basic.axf
[debug](run:run_fvp) VHT_Corstone_SSE-300_Ethos-U55.EXE succeeded with exit code 0
::set-output name=badge::Unittest-3%20of%204%20passed-yellow
Matrix Summary
==============
target build run
-------- ------- -----
debug (skip) 3/4
The summary lists out the number of test cases passed and totally executed. This example intentionally has one failing test case. Inspect the xunit result file to see the details:
~/AVH-GetStarted/basic $ cat basic-<timestamp>.xunit
<?xml version="1.0" ?>
<testsuites disabled="0" errors="0" failures="1" tests="4" time="0.0">
<testsuite disabled="0" errors="0" failures="1" name="Cloud-CI basic tests" skipped="0" tests="4" time="0">
<testcase name="test_my_sum_pos" file="main.c" line="57"/>
<testcase name="test_my_sum_neg" file="main.c" line="58"/>
<testcase name="test_my_sum_fail" file="main.c" line="48">
<failure type="failure" message="Expected 2 Was 0"/>
</testcase>
<testcase name="test_my_sum_zero" file="main.c" line="60"/>
</testsuite>
</testsuites>
This reveals that the test assertion in main.c
line 48 failed.
The build.py
script is written using the python-matrix-runner
Python
package. Find more detailed documentation for this Python package on its
PyPI page.
This section puts some parts of the build script into the spotlight.
The class UnityReport
is implemented as a ReportFilter
so that it can
later be used in a test report chain (see section about commands below).
The relevant part here is stream
property that receives a data stream
(standard output of the model) and translates it into JUnit format. This
is done by pattern matching (using regular expression) on the expected
Unity framework output. Each found test case with its result is converted
into a junit_xml.TestCase
taken from the Python package junit-xml
.
The python-matrix-runner
has built-in support for matrix build configurations.
In case of this trivial GetStarted example only a single configuration debug
is used. This is done by specifying a project specific Enum
per degree of
freedom listing the configuration values. This example only has one axis
(target
) with one value (debug
). More elaborated use-cases may add further
axes for support of different compilers, optimization levels or target devices.
The build script can offer different top-level actions the user can trigger
from the command line. In this example these are only build
and run
. Each
action is defined by the according method.
An action method must at least yield
one shell command to be executed.
Complex actions can be composed from multiple shell commands interleaved
with additional Python code. In this basic example additional Python code
is used for some post-processing of the shell command results such as
creating an zip archive with the build output.
Each shell command (required to compose the actions) is defined by a method returning an array with the command line parts, see subprocess.run for details.
A command can be further customized, for instance by attaching a test_report
.
The test_report
is created by applying a chain of ReportFiler
's to the
output of the command. In the basic example the output of the FVP
model is captured (ConsoleReport
), the Unity output between
the known text markers is cropped (CropReport
), and the remaining data
is converted into JUnit format (UnityReport
).
Run with MDK-Professional explains in details the tool setup and project configuration for running an MDK project on Arm Virtual Hardware.
For this example, import the basic.debug.cprj
in MDK. Before launching the debug session one needs to
verify the debugger configuration:
- Bring up the Options for target... dialog from the tool bar.
- Navigate to the Debug pane and select Use: Models ARMv8-M Debugger.
- Next, click on the Settings button to bring up the Models ARMv8-M Target Driver Setup dialog.
- Select in the as the Command field the model executable for Corstone SSE-300
with Ethos-U55 (filename is:
VHT_Corstone_SSE-300_Ethos-U55.exe
in the location where Virtual Hardware models are installed). - Set
cpu0
as the Target. - Browse for the Configuration File and select
mdk_config.txt
.
Now, start the debug session and the model executable should pop up. By default,
MDK stops execution when reaching main
. Set a breakpoint to line 37 and
continue execution. Hitting the breakpoint one can single step the code under
test to figure out the issue. In this case the issue is obvious:
1 + (-1) != 2
.
The repository defines a workflow to build and run the tests using GitHub Actions on every change i.e., push and pull_request triggers.
To make this work the repository needs to be configured, see Prerequisites above.
On every change, the workflow is kicked off executing the following steps.
- Execute build and test inside an EC2 instance
The AVH Client for Python is used to- create new EC2 instance
- upload the workspace to the EC2 instance using a S3 storage bucket;
- run the command line build;
- execute the test image using the AVH model
- download the output into the workspace.
- terminate the EC2 instance
- Extract and post-process test output, including
- conversion of the log file into XUnit format.
- Archive build/test output
The image, log file and XUnit report are attached as a build artifact for later analysis. - Publish test results