autotest: skip MAVFTPCrcCompareMAVProxy only where crccmp is missing

The test drives "ftp crclocal" and "ftp crccmp".  MAVProxy grew those in
328d7de20 (2026-07-27) and has not cut a release since - the newest tag,
v1.8.74, is from 2025-08-02 and contains neither - so on a released
MAVProxy "ftp crclocal" falls through to the usage print and the test
waits out its 60s pexpect timeout.

Skipping it unconditionally would cost real coverage: build_ci.sh
installs MAVProxy by cloning master, which does have both commands, so
the test runs and passes in CI today.  A version gate would not work
either, because MAVProxy master still calls itself 1.8.74 - the same
version the release reports - so any mavproxy_version_gt() test would
disable the test in CI as well.

Ask the ftp module whether it implements the command instead, and skip
only where it does not.  Nothing has to be un-done later: the skip stops
applying by itself once the local MAVProxy has crccmp.  Keeping this in
disabled_tests() rather than returning early inside the test means the
skip is still reported in the run summary and the JUnit report.

Ask the MAVProxy we are actually going to run, not the one this process
could import.  MAVPROXY_CMD can name a MAVProxy in another virtualenv,
which is the whole point of the variable, and an in-process "import
MAVProxy" would then answer for the wrong install - saying the command
is present when the MAVProxy under test lacks it, which is exactly the
60s timeout this is meant to avoid.  Take the interpreter out of the
mavproxy script's shebang and put the question to that, keeping the
lookup next to mavproxy_cmd() in util.py where MAVPROXY_CMD is read.

Verified four ways: with crccmp present the entry is absent and
test.Plane.MAVFTPCrcCompareMAVProxy passes; with MAVPROXY_CMD pointing
at a stub install whose ftp module has no crccmp the probe reports False
and the entry appears, while an in-process import in that same run still
says True; and with MAVPROXY_CMD naming a path that does not exist the
probe returns "don't know" and the test is left enabled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Peter Barker
2026-09-15 09:47:09 +10:00
committed by Andrew Tridgell
co-authored by Claude Opus 5
parent 9189701872
commit cd8a2df5fa
3 changed files with 77 additions and 1 deletions
+7 -1
View File
@@ -9790,7 +9790,7 @@ class AutoTestPlane(vehicle_test_suite.TestSuite):
]
def disabled_tests(self):
return {
ret = {
"LandingDrift": "Flapping test. See https://github.com/ArduPilot/ardupilot/issues/20054",
"TerrainRally": "Passes vacuously due to helper alt-frame bugs. See https://github.com/ArduPilot/ardupilot/issues/33740", # noqa
"InteractTest": "requires user interaction",
@@ -9800,6 +9800,12 @@ class AutoTestPlane(vehicle_test_suite.TestSuite):
"MAVFTPListDirectoryInterleavedGet": "needs a MAVProxy which does not continue a listing by mutating the last op sent; see https://github.com/ArduPilot/MAVProxy", # noqa:E501
"MAVFTPListDirectoryTabInNameMAVProxy": "needs a MAVProxy which takes the size from the end of a listing entry; see https://github.com/ArduPilot/MAVProxy", # noqa:E501
}
if not self.mavproxy_ftp_module_has_command("crccmp"):
# added to MAVProxy in 328d7de20 (2026-07-27) and not in any
# release up to v1.8.74; skipped only where it is missing, so
# CI - which installs MAVProxy from git master - still runs it
ret["MAVFTPCrcCompareMAVProxy"] = "needs a MAVProxy which has the ftp crclocal and crccmp commands; see https://github.com/ArduPilot/MAVProxy" # noqa:E501
return ret
class AutoTestPlaneTests1a(AutoTestPlane):
+52
View File
@@ -9,6 +9,7 @@ import math
import os
import re
import shlex
import shutil
import signal
import socket
import subprocess
@@ -835,6 +836,57 @@ def MAVProxy_version():
return int(match.group(1)), int(match.group(2)), int(match.group(3))
def mavproxy_python():
"""return the interpreter which runs mavproxy_cmd(), as an argv list.
MAVPROXY_CMD can name a MAVProxy installed somewhere other than the
interpreter running the test suite - a virtualenv, say - so
importing MAVProxy here would answer questions about the wrong
MAVProxy. Take the interpreter out of the script's shebang line
instead. Returns None if it can't be worked out.
"""
path = shutil.which(mavproxy_cmd())
if path is None:
return None
try:
with open(path, "rb") as f:
first_line = f.readline()
except OSError:
return None
if not first_line.startswith(b"#!"):
# not a script at all; a compiled wrapper, perhaps
return None
return shlex.split(first_line[2:].strip().decode("utf-8"))
def MAVProxy_ftp_module_has_command(command):
"""return True if MAVProxy's ftp module implements "ftp <command>".
Asks the MAVProxy which mavproxy_cmd() will run, not the one this
process happens to be able to import. Returns None if that can't be
asked.
"""
python = mavproxy_python()
if python is None:
return None
program = (
"from MAVProxy.modules import mavproxy_ftp;"
"print(hasattr(mavproxy_ftp.FTPModule, %s))" % repr("cmd_%s" % command)
)
try:
completed = subprocess.run(
python + ["-c", program],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
timeout=60,
)
except (OSError, subprocess.SubprocessError):
return None
if completed.returncode != 0:
return None
return completed.stdout.decode("ascii").strip() == "True"
def start_MAVProxy_SITL(atype,
aircraft=None,
setup=False,
+18
View File
@@ -2289,6 +2289,24 @@ class TestSuite(abc.ABC):
'''return the current version of mavproxy as a tuple e.g. (1,8,8)'''
return util.MAVProxy_version()
def mavproxy_ftp_module_has_command(self, command):
'''return True if MAVProxy's ftp module implements "ftp <command>".
MAVProxy's version is no use for this: master and the newest
release both call themselves 1.8.74, so a version gate would
disable the test everywhere, including CI - which installs
MAVProxy from git master and so does have these commands. Ask
the module what it can do instead; the answer changes by itself
when the local MAVProxy is updated.
'''
ret = util.MAVProxy_ftp_module_has_command(command)
if ret is None:
# couldn't ask the MAVProxy we will be running. Assume the
# command is there and let the test run rather than silently
# dropping coverage:
return True
return ret
def mavproxy_version_gt(self, major, minor, point):
if os.getenv("AUTOTEST_FORCE_MAVPROXY_VERSION", None) is not None:
return True