Autotest Framework

../_images/autotest.jpg

ArduPilot’s AutoTest suite allows for the creation of repeatable tests which help prevent regressions in ArduPilot’s behavior. It is based on ArduPilot’s SITL architecture - i.e. a fully-software-based solution.

Using ArduPilot’s AutoTest can:

  • make your development process more efficient by reducing time spent repeatedly running the same scenario in sim_vehicle.py

  • allow you to repeatedly replicate bad behavior in ArduPilot, and possibly ship that test to a developer capable of fixing the issue (“test-driven-development”)

  • reduce the chances of a regression in ArduPilot’s behavior by locking in tests for that behavior

Overview

The AutoTest suite is run on ArduPilot’s autotest server on most commits to the master branch but can be run locally to vet software changes. Adding tests is straightforward and encouraged to show how patches improve flight behavior.

Running AutoTest

Warning

Don’t run autotest.py with no parameters - unless you know what you are doing or like cleaning up large messes.

AutoTest requires a valid SITL environment to run. Use the SITL instructions (SITL) to obtain a valid environment. It is suggested the ArduPilot Vagrant virtual machine configuration files be used to obtain a working environment.

Invocation

Note

running autotest with high-levels of --speedup can result in enough network traffic that MAVProxy can’t keep up. An error such as “Set RC override timeout” or the vehicle entering GCS failsafe are typical of these failures. Re-running will often allow the tests to pass. Reducing the --speedup factor is typically sufficient to avoid this problem.

Help is available:

pbarker@bluebottle:~/rc/ardupilot(master)$ ./Tools/autotest/autotest.py --helpUsage: autotest

Options:
  -h, --help            show this help message and exit
  --skip=SKIP           list of steps to skip (comma separated)
  --list                list the available steps
  --viewerip=VIEWERIP   IP address to send MAVLink and fg packets to
  --map                 show map
  --experimental        enable experimental tests
  --timeout=TIMEOUT     maximum runtime in seconds
  --frame=FRAME         specify frame type
  --show-test-timings   show how long each test took to run

  Build options:
    --no-configure      do not configure before building
    --waf-configure-args=WAF_CONFIGURE_ARGS
                        extra arguments passed to waf in configure
    -j J                build CPUs
    --no-clean          do not clean before building
    --debug             make built binaries debug binaries

  Simulation options:
    --speedup=SPEEDUP   speedup to run the simulations at
    --valgrind          run ArduPilot binaries under valgrind
    --gdb               run ArduPilot binaries under gdb
    --gdbserver         run ArduPilot binaries under gdbserver
    -B BREAKPOINT, --breakpoint=BREAKPOINT
                        add a breakpoint at given location in debugger
pbarker@bluebottle:~/rc/ardupilot(master)$

autotest.py is invoked with a sequence of “steps” which will be executed in order:

./Tools/autotest/autotest.py build.Copter test.Copter

This command is valid in the root directory of an ArduPilot checkout. It instructs AutoTest to build the ArduCopter SITL binary, start that binary, test it and then kill it. The output (sample) is extremely verbose, but a summary is given once all steps have been run.

Note

Older versions of autotest.py used these steps:

build.ArduPlane build.ArduCopter build.APMrover2 build.ArduSub build.AntennaTracker fly.ArduCopter fly.ArduPlane fly.QuadPlane dive.ArduSub drive.APMrover2 drive.BalanceBot drive.balancebot fly.CopterAVC

Note

--list : list available steps (build, test, defaults, examples)

Note

The --no-clean option can greatly reduce your development-cycle-time

Note

When developing tests, consider omitting the “build” step - unless you are changing ArduPilot code.

Complex Invocation

./Tools/autotest/autotest.py --no-clean build.Copter test.Copter build.Rover test.Rover test.Balancebot build.Plane test.Plane test.QuadPlane build.Sub test.Sub build.Helicopter test.Helicopter build.Tracker test.Tracker

At the time of writing, these invoke all the vehicle tests. Expect these to take about 40 minutes to run.

Running a specific sub-test

To run a specific sub-test just add the test name with a ‘.’ between the test and sub-test names.

./Tools/autotest/autotest.py build.Plane test.Plane.ThrottleFailsafe

Using with GDB

AutoTest can run the ArduPilot binary under gdb:

./Tools/autotest/autotest.py --no-clean --gdb --debug build.Copter test.Copter

In an X Windowing System environment, an xterm window will contain the GDB terminal; stderr from the ArduPilot binary will also appear in this window. Where X is not available but GNU screen is, a detached screen will be created with the same content.

You can insert breakpoints on the command-line (use multiple times to insert multiple breakpoints):

./Tools/autotest/autotest.py --no-clean --gdb --debug -B Copter::update build.Copter test.Copter

You can insert a Python method call into your test to cause the autopilot to enter the attached debugger:

self.send_debug_trap()

This feature works well when combined with the --disable-breakpoints command-line option as you can enable the breakpoints when the debug trap is sent.

Using with Valgrind

AutoTest can run the ArduPilot binary under the Valgrind memcheck tool. This is useful for finding the reading of uninitialised memory and the like.

Warning

ArduPilot initializes most of its dynamically-allocated memory to zero by overriding the new function. Some versions of Valgrind do not understand this. The supplied xenial32 Vagrant virtual machine contains a version of Valgrind which does not suffer from this issue.

./Tools/autotest/autotest.py --no-clean --valgrind --debug build.Rover test.Rover

Special log files (e.g. arducopter-+-valgrind.log) are created by autotest when run with this tool. They should always be empty at the end of an autotest run.

Extracting Results

After AutoTest has run, several log files are available.

The log on autotest.py’s stdout is obvious!

DataFlash files are available in the “logs” directory:

pbarker@bluebottle:~/rc/ardupilot(master)$ ls -lt logs
total 21356
-rw-r--r-- 1 pbarker pbarker  8474624 Jul 27 12:07 00000003.BIN
-rw-r--r-- 1 pbarker pbarker        3 Jul 27 12:06 LASTLOG.TXT
-rw-r--r-- 1 pbarker pbarker 13307904 Jul 27 12:06 00000002.BIN
-rw-r--r-- 1 pbarker pbarker    73728 Jul 27 12:05 00000001.BIN
pbarker@bluebottle:~/rc/ardupilot(master)$

The MAVLink telemetry logs are present in the “buildlogs” directory. This directory is typically created one-level-higher than the ArduPilot root directory.

pbarker@bluebottle:~/rc/ardupilot(master)$ ls -l ../buildlogs/*tlog
-rw-r--r-- 2 pbarker pbarker 2541216 Jul 27 12:11 ../buildlogs/Rover-test.tlog
pbarker@bluebottle:~/rc/ardupilot(master)$

Note

On the Vagrant virtual machine, the ArduPilot root directory is mounted on /vagrant. The “vagrant” user has no permission to create the “buildlogs” directory in “/”, so instead, the buildlogs directory appears at /tmp/buildlogs

Warning

Not all MAVLink traffic involved in the testing is present in the buildlogs tlog file. Only traffic to/from MAVProxy itself (as opposed to additional MAVProxy –outputs) is present. See AutoTest Structure for more information.

Correlation of Output Files with the autotest server

ArduPilot’s autotest server displays the results of the most recent AutoTest run. If a test is failing on the autotest server, it should be possible to replicate that failure locally using autotest.py

AutoTest’s “Test Results” section reflects autotest.py’s return value for each of the steps.

AutoTest’s “Test Logs” section reflects the contents of the buildlogs directory.

AutoTest’s “Flight Tracks” section’s images can be created using the “convertgpx” step.

AutoTest Structure

File Structure

Tools/autotest/autotest.py

the main entry point to the autotest suite

Tools/autotest/antennatracker.py

contains tests for AntennaTracker

Tools/autotest/arducopter.py

contains tests for ArduCopter in both multicopter and helicopter form

Tools/autotest/rover.py

contains tests for ArduRover

Tools/autotest/ardusub.py

contains tests for ArduSub

Tools/autotest/arduplane.py

contains tests for ArduPlane

Tools/autotest/balancebot.py

contains tests for Balance Bots/Self Balancing Robots

Tools/autotest/quadplane.py

contains tests for ArduPlane’s QuadPlane code

Tools/autotest/pysim/util.py

various utility functions used by AutoTest

Tools/autotest/vehicle_test_suite.py

Contains a base class inherited by the per-vehicle testing routines

Network Structure

The AutoTest network plumbing is complicated.

From a test’s perspective:

self.mavproxy

An pexpect object used to interact with the MAVProxy process. All MAVProxy commands are valid when sent to this object - e.g. set shownoise 0. This is not always available - self.start_mavproxy() and self.stop_mavproxy() should be used by a test if it requires MAVProxy.

self.mav

A mavudp object connected to a the primary output port of the simulation (usually TCP port 5760).

self.mav.mav

The mavudp’s MAVLink object. Can be used to send messages via MAVLink to the SITL binary: self.mav.mav.system_time_send(time.time() * 1000000, 0)

RC Overrides

A test’s call to self.set_rc(ch, value) effectively sets the RC inputs for the simulated vehicle. It is important to note that these are not “RC overrides” - it is “real” simulated RC input. The SITL binary listens on a network port for packets of 8-bit or 16-bit quantities representing the RC input. MAVProxy is invoked in such a way that data which it would otherwise have sent as MAVLink RC override packets are delivered to that network socket instead.

Adding a Test

Note

The autotest script is in flux. This documentation may be out of date.

This git commit is a reasonable example of adding an entirely new test to the ArduPilot suite.

Conducting an automated git bisect with an autotest

Tools/autotest/bisect-helper.py can be used as the script argument to git bisect run. It can run an autotest test - by name - and tell you which commit broke that test.

To accomplish this:

  • make sure you’re not already running a bisect - git bisect reset

  • create a topic branch for your new test (based on master) which fails now but you know would have passed at some stage in the past

  • write your test - which should fail on your topic branch, and commit it

  • you can test your branch by creating a branch at some stage in the past and cherry-picking your test into that branch. This may not be trivial depending on what changes have been made in the autotest framework

  • cp Tools/autotest/bisect-helper.py /tmp # always use modern helper

  • git bisect reset

  • git bisect start

  • git bisect bad - we know the test fails where it was written

  • git bisect good HEAD~1024 - this is where we know the test passes

  • time git bisect run /tmp/bisect-helper.py --autotest --autotest-vehicle=Plane --autotest-test=NeedEKFToArm --autotest-branch=wip/bisection-using-named-test

In the last command, you need to specify the vehicle, new test name, and the name of the topic branch which contains your new test.

After this has run you should know which commit broke the functionality being tested. And you also have a new test for the regression suite which you should PR!

Using with callgrind / kcachegrind

Valgrind is actually a collection of tools - mostly it is used with the “memcheck” tool to find memory read/write problems.

Valgrind’s callgrind tool allows for performance analysis. autotest.py has built-in support for running ArduPilot under callgrind.

pbarker@bluebottle:~/rc/ardupilot(tmp/kcachegrind)$ ./Tools/autotest/autotest.py build.Copter
lckfile='/home/pbarker/rc/buildlogs/autotest.lck'
step=build.Copter
Running: ("git rev-parse HEAD") in (/home/pbarker/rc/ardupilot)
.
.
bin/arducopter  4795907  173448  172104  5141459

Build commands will be stored in build/sitl/compile_commands.json
'build' finished successfully (15.974s)
pbarker@bluebottle:~/rc/ardupilot(tmp/kcachegrind)$ rm callgrind.out.*
pbarker@bluebottle:~/rc/ardupilot(tmp/kcachegrind)$ ./Tools/autotest/autotest.py --callgrind test.Copter.Callisto
lckfile='/home/pbarker/rc/buildlogs/autotest.lck'
step=test.Copter.Callisto
Running: ("git rev-parse HEAD") in (/home/pbarker/rc/ardupilot)
.
.
AT-0368.6: Stopping SITL
>>>> PASSED STEP: test.Copter.Callisto at Tue Nov 30 08:55:20 2021
pbarker@bluebottle:~/rc/ardupilot(tmp/kcachegrind)$ kcachegrind
../_images/kcachegrind.png