Files
ardupilot/tests/test_run_in_terminal_window.py
James HarveyandClaude Fable 5 a3cd6b6a58 tests: add a test for the SITL console log path
run_in_terminal_window.sh's headless fallback logs the vehicle console to
a hardcoded /tmp/$name.log. Every instance and every user on the machine
gets the same path. Concurrent instances overwrite each other's logs, and
on a multi-user machine a second user can't write the file at all (/tmp is
sticky and fs.protected_regular blocks the open). The redirect fails
before the binary is exec'd, the script exits 0 anyway, and SITL never
comes up, with no error output anywhere.

Two tests: the log goes to TMPDIR when it is set, and still goes to /tmp
when it isn't. The TMPDIR test fails until the next commit fixes the
script.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-04 12:07:49 +10:00

76 lines
2.5 KiB
Python

"""Tests for Tools/autotest/run_in_terminal_window.sh."""
import os
import pathlib
import subprocess
import time
import pytest
REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent
SCRIPT = REPO_ROOT / "Tools" / "autotest" / "run_in_terminal_window.sh"
# The script chooses how to launch by probing the environment. Clear
# everything it looks at, so it takes the headless branch under test instead
# of opening a terminal on the machine running the tests.
TERMINAL_VARS = ("SITL_RITW_TERMINAL", "TMUX", "DISPLAY", "STY", "ZELLIJ")
def run_headless(name, command, tmpdir=None):
"""Run the script with no terminal available and return the result."""
env = {k: v for k, v in os.environ.items() if k not in TERMINAL_VARS}
if tmpdir is None:
env.pop("TMPDIR", None)
else:
env["TMPDIR"] = str(tmpdir)
return subprocess.run(
[str(SCRIPT), name] + command,
env=env,
capture_output=True,
text=True,
timeout=30,
)
def wait_for(path, timeout=10):
"""The script backgrounds the command, so the log appears asynchronously."""
deadline = time.time() + timeout
while time.time() < deadline:
if path.exists() and path.stat().st_size > 0:
return True
time.sleep(0.05)
return False
@pytest.fixture
def vehicle_name():
"""A name unique to this process, so a stale log cannot mask a result."""
name = "RiTWTestVehicle%u" % os.getpid()
yield name
# A version that ignores TMPDIR writes here; do not leave that behind.
pathlib.Path("/tmp", name + ".log").unlink(missing_ok=True)
def test_console_log_goes_to_tmpdir(tmp_path, vehicle_name):
"""TMPDIR decides where the console log is written.
A caller running several vehicles, or sharing a machine with another
user, needs to control where these logs go.
"""
result = run_headless(vehicle_name, ["/bin/echo", "hello"], tmpdir=tmp_path)
assert result.returncode == 0
log = tmp_path / (vehicle_name + ".log")
assert wait_for(log), "no console log in TMPDIR; stderr: %s" % result.stderr
assert "hello" in log.read_text()
def test_console_log_falls_back_to_tmp(vehicle_name):
"""With TMPDIR unset the log still lands in /tmp (the current behavior)."""
result = run_headless(vehicle_name, ["/bin/echo", "hello"])
assert result.returncode == 0
log = pathlib.Path("/tmp", vehicle_name + ".log")
assert wait_for(log), "no console log in /tmp; stderr: %s" % result.stderr
assert "hello" in log.read_text()