193 lines
6.8 KiB
Python
193 lines
6.8 KiB
Python
# SPDX-License-Identifier: GPL-2.0-only
|
|
#
|
|
# Copyright (c) 2023 Linux CAN project
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License for more details.
|
|
|
|
import pytest
|
|
import subprocess
|
|
import os
|
|
import time
|
|
import signal
|
|
import re
|
|
|
|
# --- Helper Functions ---
|
|
|
|
class TrafficMonitor:
|
|
"""
|
|
Context manager to run candump in the background.
|
|
Captures the bus traffic to verify what cansequence sends.
|
|
"""
|
|
def __init__(self, bin_path, interface, args=None):
|
|
self.cmd = [os.path.join(bin_path, "candump"), "-L", interface]
|
|
if args:
|
|
self.cmd.extend(args)
|
|
self.process = None
|
|
self.output = ""
|
|
|
|
def __enter__(self):
|
|
self.process = subprocess.Popen(
|
|
self.cmd,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
text=True,
|
|
preexec_fn=os.setsid
|
|
)
|
|
time.sleep(0.2)
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
if self.process:
|
|
os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
|
|
self.output, _ = self.process.communicate()
|
|
|
|
def run_cansequence_sender(bin_path, interface, count=None, args=None):
|
|
"""Runs cansequence in sender mode."""
|
|
cmd = [os.path.join(bin_path, "cansequence"), interface]
|
|
if count is not None:
|
|
cmd.append(f"--loop={count}")
|
|
if args:
|
|
cmd.extend(args)
|
|
|
|
subprocess.run(cmd, check=True)
|
|
|
|
class CanSequenceReceiver:
|
|
"""Context manager for cansequence receiver."""
|
|
def __init__(self, bin_path, interface, args=None):
|
|
self.cmd = [os.path.join(bin_path, "cansequence"), interface, "--receive"]
|
|
if args:
|
|
self.cmd.extend(args)
|
|
self.process = None
|
|
self.stdout = ""
|
|
self.stderr = ""
|
|
|
|
def __enter__(self):
|
|
self.process = subprocess.Popen(
|
|
self.cmd,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
text=True,
|
|
preexec_fn=os.setsid
|
|
)
|
|
time.sleep(0.2)
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
if self.process:
|
|
if self.process.poll() is None:
|
|
os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
|
|
self.stdout, self.stderr = self.process.communicate()
|
|
|
|
# --- Tests for cansequence ---
|
|
|
|
def test_help_option(bin_path):
|
|
"""Test -h option."""
|
|
result = subprocess.run([os.path.join(bin_path, "cansequence"), "-h"], capture_output=True, text=True)
|
|
assert "Usage: cansequence" in result.stdout or "Usage: cansequence" in result.stderr
|
|
|
|
def test_sender_payload_increment(bin_path, can_interface):
|
|
"""
|
|
Test 1: Sender + Candump.
|
|
Verify that cansequence sends frames with incrementing payload.
|
|
"""
|
|
count = 5
|
|
with TrafficMonitor(bin_path, can_interface) as monitor:
|
|
run_cansequence_sender(bin_path, can_interface, count=count)
|
|
|
|
# Parse output to find payloads
|
|
# candump format: (timestamp) iface ID#DATA
|
|
# cansequence default ID is 2. Payload is sequence number (hex).
|
|
# e.g. 00, 01, 02...
|
|
|
|
lines = monitor.output.strip().splitlines()
|
|
assert len(lines) == count
|
|
|
|
# Extract data bytes. Assuming standard classic CAN frame.
|
|
# Regex to capture the first byte of data: ID#<Byte0>...
|
|
data_bytes = []
|
|
for line in lines:
|
|
match = re.search(r'#([0-9A-Fa-f]{2})', line)
|
|
if match:
|
|
data_bytes.append(int(match.group(1), 16))
|
|
|
|
# Verify increment
|
|
for i in range(len(data_bytes)):
|
|
assert data_bytes[i] == i % 256, f"Payload mismatch at index {i}"
|
|
|
|
def test_sender_receiver_success(bin_path, can_interface):
|
|
"""
|
|
Test 2: Sender + Receiver.
|
|
Verify that receiver accepts the stream from sender without error.
|
|
"""
|
|
# Start receiver
|
|
with CanSequenceReceiver(bin_path, can_interface, args=["-v"]) as receiver:
|
|
# Run sender
|
|
run_cansequence_sender(bin_path, can_interface, count=10)
|
|
|
|
# Give receiver a moment to process
|
|
time.sleep(0.5)
|
|
|
|
# Receiver should NOT have exited with error (if we didn't use -q)
|
|
# and shouldn't have printed "sequence mismatch" errors.
|
|
# Note: cansequence prints to stderr usually on error.
|
|
|
|
assert "sequence number mismatch" not in receiver.stderr
|
|
assert "sequence number mismatch" not in receiver.stdout
|
|
|
|
def test_receiver_detects_error(bin_path, can_interface):
|
|
"""
|
|
Test 3: Cansend (Injection) + Receiver.
|
|
Verify receiver detects missing sequence number.
|
|
"""
|
|
# Use -q 1 to quit immediately on first error
|
|
with CanSequenceReceiver(bin_path, can_interface, args=["--quit", "1"]) as receiver:
|
|
|
|
# Manually send sequence 0 (valid)
|
|
# Sequence number is usually little endian integer in payload.
|
|
# ID 2 is default for cansequence.
|
|
subprocess.run([os.path.join(bin_path, "cansend"), can_interface, "002#00"], check=True)
|
|
time.sleep(0.1)
|
|
|
|
# Manually send sequence 2 (skipping 1) -> Invalid!
|
|
subprocess.run([os.path.join(bin_path, "cansend"), can_interface, "002#02"], check=True)
|
|
time.sleep(0.5)
|
|
|
|
# Check if receiver exited
|
|
assert receiver.process.poll() is not None, "Receiver did not exit on sequence error"
|
|
assert receiver.process.returncode != 0
|
|
|
|
def test_extended_id(bin_path, can_interface):
|
|
"""Test sending extended frames (-e)."""
|
|
with TrafficMonitor(bin_path, can_interface) as monitor:
|
|
# Send 1 frame, extended mode
|
|
run_cansequence_sender(bin_path, can_interface, count=1, args=["-e"])
|
|
|
|
# Output should contain 8-character ID (padded) or match extended syntax.
|
|
# Default ID is 2, so extended is usually 00000002.
|
|
assert "00000002#" in monitor.output or "2#" in monitor.output
|
|
# Depending on candump formatting, we might verify extended flag logic if needed,
|
|
# but existence of traffic is the main check here.
|
|
|
|
def test_custom_identifier(bin_path, can_interface):
|
|
"""Test custom CAN ID (-i)."""
|
|
custom_id = "0x123" # Hex string ensures strtoul interprets as hex
|
|
with TrafficMonitor(bin_path, can_interface) as monitor:
|
|
run_cansequence_sender(bin_path, can_interface, count=1, args=["-i", custom_id])
|
|
|
|
# Check for ID 123
|
|
assert "123#" in monitor.output
|
|
|
|
def test_can_fd_mode(bin_path, can_interface):
|
|
"""Test CAN-FD mode (-f)."""
|
|
# Requires hardware/vcan support for FD
|
|
try:
|
|
with TrafficMonitor(bin_path, can_interface) as monitor:
|
|
run_cansequence_sender(bin_path, can_interface, count=1, args=["-f"])
|
|
|
|
# FD frames usually appear with ## in candump -L (if not strict classic view)
|
|
# or we verify the output exists.
|
|
assert "##" in monitor.output or "#" in monitor.output
|
|
except subprocess.CalledProcessError:
|
|
pytest.skip("CAN-FD not supported or failed")
|