Back to Repositories

Testing PlotJuggler Data Visualization Integration in OpenPilot

This test suite validates the functionality of PlotJuggler integration in the OpenPilot project, focusing on demo route playback and layout validation. It ensures proper data visualization and layout handling while preventing common integration issues.

Test Coverage Overview

The test suite covers two main areas of PlotJuggler functionality:

Key functionality tested:
  • Demo route playback and data reading verification
  • Layout file validation and integrity checks
  • Error handling for raw file reading
  • Process management and cleanup
Edge cases include timeout handling and platform-specific display configurations.

Implementation Analysis

The testing approach utilizes Python’s subprocess management for running PlotJuggler in a headless environment. It implements process isolation through session management and proper signal handling.

Notable patterns include:
  • Offscreen rendering configuration for CI compatibility
  • Stderr output parsing for process state verification
  • Layout file validation using string pattern matching

Technical Details

Testing tools and configuration:
  • Python’s subprocess module for process management
  • Custom Timeout implementation for process monitoring
  • QT_QPA_PLATFORM configuration for headless testing
  • Signal handling for clean process termination
  • Glob pattern matching for layout file discovery

Best Practices Demonstrated

The test suite demonstrates several quality testing practices:

  • Proper resource cleanup with process termination
  • Timeout handling for long-running operations
  • Subtests implementation for granular layout testing
  • Clear separation of demo and layout validation tests
  • Effective error message capture and validation

commaai/openpilot

tools/plotjuggler/test_plotjuggler.py

            
import os
import glob
import signal
import subprocess
import time

from openpilot.common.basedir import BASEDIR
from openpilot.common.timeout import Timeout
from openpilot.tools.plotjuggler.juggle import DEMO_ROUTE, install

PJ_DIR = os.path.join(BASEDIR, "tools/plotjuggler")

class TestPlotJuggler:

  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"',
                           stderr=subprocess.PIPE, shell=True, start_new_session=True) as p:
      # Wait for "Done reading Rlog data" signal from the plugin
      output = "
"
      with Timeout(180, error_msg=output):
        while output.splitlines()[-1] != "Done reading Rlog data":
          output += p.stderr.readline().decode("utf-8")

      # ensure plotjuggler didn't crash after exiting the plugin
      time.sleep(2)
      assert p.poll() is None
      os.killpg(os.getpgid(p.pid), signal.SIGTERM)

      assert "Raw file read failed" not in output

  # 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)}"