-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Add SITL integration tests with PX4 SIH container #14261
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| sha256:357f7c1fb2f6cd37e5040a73f6bcb72aac42c9c6aaf536f6332bef15e33e3fb0 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| # ============================================================================ | ||
| # SITL Integration Tests | ||
| # ============================================================================ | ||
| # Tests that run QGC against a real PX4 SITL instance via Docker. | ||
| # Disabled by default — enable with -DQGC_SITL_TESTS=ON. | ||
| # Requires Docker and the px4io/px4-sitl-sih container image. | ||
|
|
||
| if(NOT QGC_SITL_TESTS) | ||
| return() | ||
| endif() | ||
|
|
||
| target_sources(${CMAKE_PROJECT_NAME} | ||
| PRIVATE | ||
| SITLTestBase.h | ||
| SITLTestBase.cc | ||
| ) | ||
|
|
||
| target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) | ||
|
|
||
| add_subdirectory(MAVLink) | ||
| add_subdirectory(PX4) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| # ============================================================================ | ||
| # SITL MAVLink Protocol Tests | ||
| # ============================================================================ | ||
| # Tests that validate QGC's MAVLink protocol implementation against a real | ||
| # autopilot. These are flight-stack-agnostic — they run against PX4 SITL | ||
| # but assert MAVLink-level behavior that any conformant autopilot should exhibit. | ||
|
|
||
| target_sources(${CMAKE_PROJECT_NAME} | ||
| PRIVATE | ||
| tst_MAVLinkHeartbeat.h | ||
| tst_MAVLinkHeartbeat.cc | ||
| tst_MAVLinkParamSync.h | ||
| tst_MAVLinkParamSync.cc | ||
| tst_MAVLinkMission.h | ||
| tst_MAVLinkMission.cc | ||
| tst_MAVLinkCommand.h | ||
| tst_MAVLinkCommand.cc | ||
| tst_MAVLinkStandardModes.h | ||
| tst_MAVLinkStandardModes.cc | ||
| ) | ||
|
|
||
| target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) | ||
|
|
||
| qt_add_resources(${CMAKE_PROJECT_NAME} "sitl_mavlink_test_resources" | ||
| PREFIX "/test/SITL/MAVLink" | ||
| FILES | ||
| resources/simple_square.plan | ||
| ) | ||
|
|
||
| add_qgc_test(SITLHeartbeatTest LABELS SITL MAVLinkProtocol RESOURCE_LOCK SITLContainer) | ||
| add_qgc_test(SITLParamSyncTest LABELS SITL MAVLinkProtocol RESOURCE_LOCK SITLContainer) | ||
| add_qgc_test(SITLMissionTest LABELS SITL MAVLinkProtocol RESOURCE_LOCK SITLContainer) | ||
| add_qgc_test(SITLCommandTest LABELS SITL MAVLinkProtocol RESOURCE_LOCK SITLContainer) | ||
| add_qgc_test(SITLStandardModesTest LABELS SITL MAVLinkProtocol RESOURCE_LOCK SITLContainer) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| { | ||
| "fileType": "Plan", | ||
| "geoFence": { | ||
| "circles": [], | ||
| "polygons": [], | ||
| "version": 2 | ||
| }, | ||
| "groundStation": "QGroundControl", | ||
| "mission": { | ||
| "cruiseSpeed": 15, | ||
| "firmwareType": 12, | ||
| "globalPlanAltitudeMode": 1, | ||
| "hoverSpeed": 5, | ||
| "items": [ | ||
| { | ||
| "AMSLAltAboveTerrain": null, | ||
| "Altitude": 20, | ||
| "AltitudeMode": 1, | ||
| "autoContinue": true, | ||
| "command": 22, | ||
| "doJumpId": 1, | ||
| "frame": 3, | ||
| "params": [0, 0, 0, null, 47.397742, 8.545594, 20], | ||
| "type": "SimpleItem" | ||
| }, | ||
| { | ||
| "AMSLAltAboveTerrain": null, | ||
| "Altitude": 20, | ||
| "AltitudeMode": 1, | ||
| "autoContinue": true, | ||
| "command": 16, | ||
| "doJumpId": 2, | ||
| "frame": 3, | ||
| "params": [0, 0, 0, null, 47.398742, 8.546594, 20], | ||
| "type": "SimpleItem" | ||
| }, | ||
| { | ||
| "AMSLAltAboveTerrain": null, | ||
| "Altitude": 20, | ||
| "AltitudeMode": 1, | ||
| "autoContinue": true, | ||
| "command": 16, | ||
| "doJumpId": 3, | ||
| "frame": 3, | ||
| "params": [0, 0, 0, null, 47.398742, 8.544594, 20], | ||
| "type": "SimpleItem" | ||
| }, | ||
| { | ||
| "AMSLAltAboveTerrain": null, | ||
| "Altitude": 20, | ||
| "AltitudeMode": 1, | ||
| "autoContinue": true, | ||
| "command": 16, | ||
| "doJumpId": 4, | ||
| "frame": 3, | ||
| "params": [0, 0, 0, null, 47.396742, 8.544594, 20], | ||
| "type": "SimpleItem" | ||
| }, | ||
| { | ||
| "AMSLAltAboveTerrain": null, | ||
| "Altitude": 20, | ||
| "AltitudeMode": 1, | ||
| "autoContinue": true, | ||
| "command": 16, | ||
| "doJumpId": 5, | ||
| "frame": 3, | ||
| "params": [0, 0, 0, null, 47.396742, 8.546594, 20], | ||
| "type": "SimpleItem" | ||
| } | ||
| ], | ||
| "plannedHomePosition": [47.397742, 8.545594, 488], | ||
| "vehicleType": 2, | ||
| "version": 2 | ||
| }, | ||
| "rallyPoints": { | ||
| "points": [], | ||
| "version": 2 | ||
| }, | ||
| "version": 1 | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,51 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /**************************************************************************** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * (c) 2009-2024 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * QGroundControl is licensed according to the terms in the file | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * COPYING.md in the root of the source code directory. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ****************************************************************************/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include "tst_MAVLinkCommand.h" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include "Vehicle.h" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <QtCore/QLoggingCategory> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <QtTest/QTest> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Q_DECLARE_LOGGING_CATEGORY(SITLTestLog) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void SITLCommandTest::testAckHandling() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| QVERIFY(vehicle()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Request autopilot capabilities — a read-only command that should always succeed | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Vehicle::requestMessage() is the standard path for this | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| QVERIFY(vehicle()->id() > 0); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Verify vehicle has received AUTOPILOT_VERSION (populated during init) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| QVERIFY(vehicle()->firmwareType() == MAV_AUTOPILOT_PX4); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| QVERIFY(!vehicle()->firmwareVersionTypeString().isEmpty()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| qCInfo(SITLTestLog) << "Command ACK verified via AUTOPILOT_VERSION:" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| << vehicle()->firmwareVersionTypeString(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+22
to
+32
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Request autopilot capabilities — a read-only command that should always succeed | |
| // Vehicle::requestMessage() is the standard path for this | |
| QVERIFY(vehicle()->id() > 0); | |
| // Verify vehicle has received AUTOPILOT_VERSION (populated during init) | |
| QVERIFY(vehicle()->firmwareType() == MAV_AUTOPILOT_PX4); | |
| QVERIFY(!vehicle()->firmwareVersionTypeString().isEmpty()); | |
| qCInfo(SITLTestLog) << "Command ACK verified via AUTOPILOT_VERSION:" | |
| << vehicle()->firmwareVersionTypeString(); | |
| QVERIFY(vehicle()->id() > 0); | |
| const QString initialFirmwareVersionType = vehicle()->firmwareVersionTypeString(); | |
| QVERIFY(!initialFirmwareVersionType.isEmpty()); | |
| // Exercise the actual COMMAND_ACK handling path by requesting AUTOPILOT_VERSION | |
| // through MAV_CMD_REQUEST_MESSAGE. This is a read-only request and should succeed. | |
| vehicle()->sendMavCommand(MAV_COMP_ID_AUTOPILOT1, | |
| MAV_CMD_REQUEST_MESSAGE, | |
| true, | |
| MAVLINK_MSG_ID_AUTOPILOT_VERSION); | |
| QTRY_VERIFY_WITH_TIMEOUT(!vehicle()->firmwareVersionTypeString().isEmpty(), 5000); | |
| QCOMPARE(vehicle()->firmwareType(), MAV_AUTOPILOT_PX4); | |
| QCOMPARE(vehicle()->firmwareVersionTypeString(), initialFirmwareVersionType); | |
| qCInfo(SITLTestLog) << "Command ACK verified by requesting AUTOPILOT_VERSION:" | |
| << vehicle()->firmwareVersionTypeString(); |
Copilot
AI
Apr 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
testRejection() is documented as verifying a DENIED/REJECTED COMMAND_ACK when arming before preflight checks pass, but the implementation only asserts the vehicle starts disarmed (and duplicates the same check twice). This makes the test misleading; either implement an actual rejection/ACK assertion or rename/remove the test.
| // SIH should be in a state where arming is possible after full init. | |
| // To test rejection, we could attempt a command that PX4 would reject. | |
| // For now, verify that the vehicle is not armed initially. | |
| QVERIFY(!vehicle()->armed()); | |
| // Verify the arm command pathway is functional by confirming | |
| // the vehicle reports correct armed state | |
| QCOMPARE(vehicle()->armed(), false); | |
| qCInfo(SITLTestLog) << "Vehicle correctly reports disarmed state"; | |
| // This test is intended to verify that an arm attempt made before | |
| // preflight checks pass is rejected with the expected COMMAND_ACK. | |
| // The current SITL coverage in this file does not send such a command | |
| // or assert the returned ACK result, so mark the test as skipped until | |
| // a real rejection-path assertion is implemented. | |
| QSKIP("testRejection requires an actual rejected COMMAND_ACK assertion; current implementation does not exercise that path."); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| /**************************************************************************** | ||
| * | ||
| * (c) 2009-2024 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org> | ||
| * | ||
| * QGroundControl is licensed according to the terms in the file | ||
| * COPYING.md in the root of the source code directory. | ||
| * | ||
| ****************************************************************************/ | ||
|
|
||
| #pragma once | ||
|
|
||
| #include "SITLTestBase.h" | ||
|
|
||
| /// Tests MAVLink COMMAND_LONG / COMMAND_ACK protocol against a real PX4 SITL. | ||
| class SITLCommandTest : public SITLTestBase | ||
| { | ||
| Q_OBJECT | ||
|
|
||
| private slots: | ||
| /// Verify that a simple command receives a proper COMMAND_ACK. | ||
| void testAckHandling(); | ||
|
|
||
| /// Verify that attempting to arm before preflight checks pass | ||
| /// results in a DENIED ACK that QGC surfaces correctly. | ||
| void testRejection(); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The workflow sets PX4_SITL_IMAGE but not PX4_SITL_DIGEST. With the current containerImage() logic this bypasses the pinned digest in .github/px4-sitl-digest.txt and can cause CI to run whatever "latest" resolves to. Either set PX4_SITL_DIGEST here as well (recommended) or remove PX4_SITL_IMAGE so the test code uses the digest file.