Files
StarPilot/openpilot/tools/plotjuggler/test_plotjuggler.py
T
firestar5683 6d742eb343 CI
Add control trace and platform qualification tools that exercise explicit safety modes and vehicle coverage.

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com>
Co-authored-by: Kacper Rączy <gfw.kra@gmail.com>
Co-authored-by: YassineYousfi <yyousfi1@binghamton.edu>
Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com>

Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com>

Co-authored-by: prabhaavp <prabhaav@msu.edu>
2026-10-14 19:56:17 -05:00

82 lines
2.9 KiB
Python

import os
import glob
import shutil
import signal
import selectors
import subprocess
import time
import unittest
from openpilot.common.test import OpenpilotTestCase
from openpilot.common.basedir import BASEDIR
from openpilot.common.timeout import Timeout, TimeoutException
from openpilot.tools.plotjuggler.juggle import DEMO_ROUTE, install
PJ_DIR = os.path.join(BASEDIR, "openpilot/tools/plotjuggler")
class TestPlotJuggler(OpenpilotTestCase):
@unittest.skipIf(not shutil.which('qmake'), "Qt not installed")
def test_demo(self):
install()
pj = os.path.join(PJ_DIR, "juggle.py")
with subprocess.Popen(f'QT_QPA_PLATFORM=offscreen {pj} "{DEMO_ROUTE}/:2"',
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, start_new_session=True) as p:
output = ""
try:
assert p.stdout is not None
with selectors.DefaultSelector() as selector:
selector.register(p.stdout, selectors.EVENT_READ)
try:
with Timeout(180):
while "Done reading Rlog data" not in output.splitlines():
for key, _ in selector.select(0.1):
chunk = os.read(key.fileobj.fileno(), 65536)
if chunk:
output += chunk.decode("utf-8", errors="replace")
else:
selector.unregister(key.fileobj)
if p.poll() is not None:
self.fail(f"PlotJuggler exited before startup (status {p.returncode}):\n{output}")
except TimeoutException:
self.fail(f"PlotJuggler did not report startup within 180 seconds:\n{output}")
time.sleep(2)
for key, _ in selector.select(0):
chunk = os.read(key.fileobj.fileno(), 65536)
output += chunk.decode("utf-8", errors="replace")
self.assertIsNone(p.poll(), f"PlotJuggler exited after startup (status {p.returncode}):\n{output}")
self.assertNotIn("Raw file read failed", output)
finally:
try:
os.killpg(p.pid, signal.SIGTERM)
except ProcessLookupError:
pass
try:
p.wait(timeout=5)
except subprocess.TimeoutExpired:
pass
finally:
try:
os.killpg(p.pid, signal.SIGKILL)
except ProcessLookupError:
pass
p.wait(timeout=5)
# TODO: also test that layouts successfully load
def test_layouts(self, subtests):
bad_strings = (
# if a previously loaded file is defined,
# PJ will throw a warning when loading the layout
"fileInfo",
"previouslyLoaded_Datafiles",
)
for fn in glob.glob(os.path.join(PJ_DIR, "layouts/*")):
name = os.path.basename(fn)
with subtests.test(layout=name):
with open(fn) as f:
layout = f.read()
violations = [s for s in bad_strings if s in layout]
assert len(violations) == 0, f"These should be stripped out of the layout: {str(violations)}"