194 lines
6.1 KiB
Python
194 lines
6.1 KiB
Python
# SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
import subprocess
|
|
import time
|
|
import os
|
|
import pytest
|
|
import signal
|
|
import pty
|
|
import select
|
|
|
|
# Note: bin_path and interface fixtures are provided implicitly by conftest.py
|
|
|
|
def check_isotp_support(bin_path, interface):
|
|
"""
|
|
Helper to check if ISO-TP kernel module is loaded/available.
|
|
Reused logic to prevent failing tests on systems without can-isotp.
|
|
"""
|
|
isotpsend = os.path.join(bin_path, "isotpsend")
|
|
if not os.path.exists(isotpsend):
|
|
return False
|
|
|
|
try:
|
|
proc = subprocess.Popen(
|
|
[isotpsend, "-s", "123", "-d", "321", interface],
|
|
stdin=subprocess.PIPE,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
text=True
|
|
)
|
|
try:
|
|
outs, errs = proc.communicate(input="00", timeout=0.5)
|
|
except subprocess.TimeoutExpired:
|
|
proc.kill()
|
|
outs, errs = proc.communicate()
|
|
|
|
if "socket: Protocol not supported" in errs or "socket: Protocol not supported" in outs:
|
|
return False
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
def test_isotpperf_usage(bin_path):
|
|
"""
|
|
Test usage/help output for isotpperf.
|
|
|
|
Manual Reproduction:
|
|
1. Run: ./isotpperf -h
|
|
2. Expect: Output containing "Usage: isotpperf".
|
|
"""
|
|
isotpperf = os.path.join(bin_path, "isotpperf")
|
|
|
|
if not os.path.exists(isotpperf):
|
|
pytest.skip("isotpperf binary not found")
|
|
|
|
result = subprocess.run(
|
|
[isotpperf, "-h"],
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
text=True
|
|
)
|
|
|
|
# Check for usage info in stdout or stderr (some tools print help to stderr)
|
|
assert "Usage: isotpperf" in result.stderr or "Usage: isotpperf" in result.stdout
|
|
|
|
def test_isotpperf_measurement(bin_path, can_interface):
|
|
"""
|
|
Test isotpperf measurement functionality with a separate receiver.
|
|
|
|
Description:
|
|
1. Start isotpperf to monitor/measure the transfer.
|
|
2. Start isotprecv (in background) to act as the active receiver (providing Flow Control).
|
|
3. Send data (25 bytes) using isotpsend.
|
|
4. Verify that isotpperf correctly reports "25 byte in".
|
|
|
|
Manual Reproduction:
|
|
1. ./isotpperf -s 123 -d 321 vcan0
|
|
2. ./isotprecv -s 321 -d 123 vcan0 (Required for Flow Control on multi-frame)
|
|
3. echo "00 ... 11" | ./isotpsend -s 123 -d 321 vcan0
|
|
4. Check isotpperf output for "25 byte in".
|
|
"""
|
|
isotpperf = os.path.join(bin_path, "isotpperf")
|
|
isotprecv = os.path.join(bin_path, "isotprecv")
|
|
isotpsend = os.path.join(bin_path, "isotpsend")
|
|
|
|
for tool in [isotpperf, isotprecv, isotpsend]:
|
|
if not os.path.exists(tool):
|
|
pytest.skip(f"{tool} not found")
|
|
|
|
if not check_isotp_support(bin_path, can_interface):
|
|
pytest.skip("ISO-TP kernel support missing")
|
|
|
|
perf_src = "123"
|
|
perf_dst = "321"
|
|
|
|
# 1. Start isotpperf
|
|
# Arguments: -s <source_id> -d <dest_id> <interface>
|
|
cmd_perf = [isotpperf, "-s", perf_src, "-d", perf_dst, can_interface]
|
|
print(f"DEBUG: Starting isotpperf: {' '.join(cmd_perf)}")
|
|
|
|
# Use PTY for isotpperf to force unbuffered output behavior
|
|
master_fd, slave_fd = pty.openpty()
|
|
|
|
perf_proc = subprocess.Popen(
|
|
cmd_perf,
|
|
stdout=slave_fd,
|
|
stderr=subprocess.PIPE,
|
|
text=True
|
|
)
|
|
os.close(slave_fd) # Close slave in parent
|
|
|
|
recv_proc = None
|
|
|
|
try:
|
|
time.sleep(0.5)
|
|
if perf_proc.poll() is not None:
|
|
_, err = perf_proc.communicate()
|
|
pytest.fail(f"isotpperf failed to start. Stderr: {err}")
|
|
|
|
# 2. Start isotprecv (The Receiver / Flow Control provider)
|
|
# It needs reversed IDs relative to the sender to acknowledge frames.
|
|
# Sender (isotpsend): -s 123 -d 321
|
|
# Receiver (isotprecv): -s 321 -d 123
|
|
cmd_recv = [isotprecv, "-s", perf_dst, "-d", perf_src, can_interface]
|
|
print(f"DEBUG: Starting isotprecv: {' '.join(cmd_recv)}")
|
|
|
|
# We don't strictly need isotprecv's output, but we need it running.
|
|
# Pipe output to DEVNULL to avoid buffer blocking.
|
|
recv_proc = subprocess.Popen(
|
|
cmd_recv,
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL
|
|
)
|
|
|
|
time.sleep(0.5)
|
|
|
|
# 3. Send data (25 bytes)
|
|
# "00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE 11 11 11 11 11 11 11 11 11 11"
|
|
payload_bytes = "00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE 11 11 11 11 11 11 11 11 11 11"
|
|
cmd_send = [isotpsend, "-s", perf_src, "-d", perf_dst, can_interface]
|
|
|
|
print(f"DEBUG: Sending data via isotpsend: {' '.join(cmd_send)}")
|
|
subprocess.run(
|
|
cmd_send,
|
|
input=payload_bytes,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
text=True,
|
|
check=True
|
|
)
|
|
|
|
# Wait a bit for isotpperf to receive and print
|
|
# Reading from PTY
|
|
output = ""
|
|
start_time = time.time()
|
|
found = False
|
|
expected_msg = "25 byte in"
|
|
|
|
while time.time() - start_time < 3.0:
|
|
r, _, _ = select.select([master_fd], [], [], 0.1)
|
|
if master_fd in r:
|
|
try:
|
|
chunk = os.read(master_fd, 1024).decode('utf-8', errors='replace')
|
|
if chunk:
|
|
output += chunk
|
|
# Check continuously
|
|
if expected_msg in output:
|
|
found = True
|
|
break
|
|
except OSError:
|
|
break
|
|
|
|
if perf_proc.poll() is not None:
|
|
break
|
|
|
|
print(f"DEBUG: isotpperf output (PTY):\n{output}")
|
|
|
|
assert found, f"isotpperf did not report '{expected_msg}'. Captured: {output}"
|
|
|
|
finally:
|
|
# Cleanup
|
|
if recv_proc:
|
|
recv_proc.terminate()
|
|
recv_proc.wait()
|
|
|
|
if perf_proc.poll() is None:
|
|
perf_proc.terminate()
|
|
try:
|
|
perf_proc.wait(timeout=1)
|
|
except subprocess.TimeoutExpired:
|
|
perf_proc.kill()
|
|
|
|
if master_fd:
|
|
os.close(master_fd)
|