Debugger-based On-Target Testing
|
System testing with DOTT is also referred to as 'free running mode' testing. This essentially means DOTT is used to download the firmware to the target and boot it up and let the firmware run while providing external stimuli via the target's external communication interfaces.
To execute the included system test examples please use the reference target platform describe in setup section.
When implementing system tests, DOTT is used in free running mode where the target is running while external stimuli are provided and, at predefine points in time, the target's internal state is inspected using DOTT.
One way how to provide external stimuli to the external interfaces of a target is described in the section External Test Equipment. A simple system testing example which uses this approach together with the Reference Board is included in dott. The I2C connection between the reference board and the Raspberry Pi has to be established follows:
Before executing the tests you need to edit the conftest.py file locate in the examples folder. In the function add an elif branch for your host machine as shown below. Replace YOUR_HOST_NAME with the host name of your machine where you run pytest and replace AAA.BBB.CCC.DDD with the IP address of your RaspberryPi where PiGPIOD is running.
Once the I2C hardware connection is established between target and RaspberryPi and and the GPIO daemon on the RaspberryPi is set up and running, the tests can be executed. To do so, open a Python command prompt (and activate the DOTT venv if using a virtual environment). In the command prompt now navigate to the examples\02_system_testing
of the dott examples package downloaded from GitHub:
Now you can no run the tests by calling pytest:
The target firmware for the example system tests are based on code generated with the STM Cube MX tool. The code base in the target folder includes the STM32 HAL library, CMSIS support code and peripheral initialization code. The actual code under test are located in Src/example.c and implements a simple command processor which receives commands from the host via I2C. The following table summarizes the core components of the example:
Content | Source File |
---|---|
Timer functions on target | examples/02_system_testing/target/Src/examples_cntr.c |
Command functions on target | examples/02_system_testing/target/Src/examples_cmd.c |
counter tests on host | examples/02_system_testing/host/test_cntr.py |
system init tests on host | examples/02_system_testing/host/test_sysinit.py |
I2C command communication tests on host | examples/02_system_testing/host/test_i2c_comm.py |
The following test is an example how DOTT can be used to test basic system initialization functionality. In the target firmware, there is a variable called *_sample_cnt* located in the BSS (meaning that the variable shall be zero-initialized by the compiler's runtime). It is a good practice to randomly check that zero initialization is performed as expected. The following test halts the target in the reset handler (i.e., before the compiler's runtime gets control), writes a well known pattern into the variable in BSS section and then continues target execution. The target is halted again when reaching main. There it is checked that the variable was initialized to zero by the startup code of the compiler's runtime.
A similar test called test_sysinit_data_section can also be in test_sysinit.py. This one checks that a variable located in the data section is properly initialized by the compiler's runtime (i.e., the initial content of the variable was correctly copied form FLASH memory to RAM).
A standard feature of Cortex-M MCU frequently used together with an RTOS is the Systick timer. With DOTT it is easy to (roughly) check if the systick counter was configured correctly and is advancing as expected. The following test shows how to do that:
An alternative implementation shown next uses DOTT's live access feature which allows to read and write target memory while the target is running. Live access is a special pytest fixture provided by DOTT and hence tests using live_access need to include it in the test's signature.
The examples also include a simple on-target command processor component which receives command packets (consisting of an 8bit command ID and two 32bit arguments) from the host. The command packets are parsed and the respective command handler functions are then called. The following code shows the target implementation of the command processor. The reading of the command data on from the I2C peripheral is performed using DMA and and interrupt is generated when the next command packet was received. The processing of the command data is performed in the app_main loop. If a command, such as ADD, was recognized, the corresponding command handler is called.
The following test now starts the target and sends a command packet via I2C. It then halts the target using a HaltPoint when reaching a label called CMD_ADD_EXIT which is located at the end of the cmd_add function from the target code snipped above. The test then checks if the two operands a and b were correctly de-serialized and if the sum computed on the target matches the expected one.
You might also want to check how the target firmware behaves if, e.g., due to a transmission error the command ID has become invalid. The following example shows how such faults are injected into the execution flow. This is achieved using and InterceptPoint placed in the HAL_I2C_SlaveRxCpltCallback (the callback function invoked by the STM32 HAL when the next I2C command packet was received using DMA transfer). In the InterceptPoint's reached method the command ID byte is modified to an illegal value (0xff). The test then expects the target to hit the UNKNOWN_CMD label in the target's firmware. If this label is not hit, the test will fail.