can-utils/tests/test_can_basics.py

115 lines
3.9 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 as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
import subprocess
import time
import os
import pytest
import signal
def run_tool(bin_path, tool_name, args):
"""Helper function to run a tool"""
full_path = os.path.join(bin_path, tool_name)
cmd = [full_path] + args
return subprocess.run(cmd, capture_output=True, text=True)
def test_tools_executable(bin_path):
"""Simply checks if the tools show help (existence & executability)"""
for tool in ["cansend", "candump", "cangen"]:
result = run_tool(bin_path, tool, ["-?"]) # -? is often help in can-utils, otherwise invalid args
# We do not expect a crash (Segfault), Exit code may vary depending on arg parsing
# It is important that stderr or stdout contains something meaningful or the process exits cleanly
assert result.returncode != -11, f"{tool} crashed (Segfault)"
def test_send_and_receive(bin_path, can_interface):
"""
Integration test:
1. Starts candump in the background on the interface.
2. Sends a CAN frame with cansend.
3. Checks if candump saw the frame.
"""
candump_path = os.path.join(bin_path, "candump")
cansend_path = os.path.join(bin_path, "cansend")
# Unique ID and data for this test
can_id = "123"
can_data = "11223344"
can_frame = f"{can_id}#{can_data}"
# 1. Start candump in the background
# -L for Raw output, -x for extra details (optional), interface
with subprocess.Popen(
[candump_path, "-L", can_interface],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
preexec_fn=os.setsid # Process group for clean killing
) as dumpproc:
time.sleep(0.5) # Wait briefly until candump is ready
# 2. Send frame
# cansend <device> <can_frame>
send_result = subprocess.run(
[cansend_path, can_interface, can_frame],
capture_output=True,
text=True
)
assert send_result.returncode == 0, f"cansend failed: {send_result.stderr}"
time.sleep(0.5) # Wait until frame is processed
# Terminate candump
os.killpg(os.getpgid(dumpproc.pid), signal.SIGTERM)
stdout, stderr = dumpproc.communicate()
# 3. Analysis
# candump -L Output Format (approx): (timestamp) vcan0 123#11223344
print(f"Candump Output: {stdout}")
assert can_id in stdout, "CAN-ID was not received"
assert can_data in stdout, "CAN data were not received"
def test_cangen_generation(bin_path, can_interface):
"""
Checks if cangen generates traffic.
"""
cangen_path = os.path.join(bin_path, "cangen")
candump_path = os.path.join(bin_path, "candump")
# Start candump
with subprocess.Popen(
[candump_path, "-L", can_interface],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
preexec_fn=os.setsid
) as dumpproc:
# Start cangen: Send 5 frames (-n 5) and then terminate
subprocess.run(
[cangen_path, "-n", "5", can_interface],
check=True
)
time.sleep(1)
os.killpg(os.getpgid(dumpproc.pid), signal.SIGTERM)
stdout, _ = dumpproc.communicate()
# We expect 5 lines of output (or more, if other traffic is present)
lines = [l for l in stdout.splitlines() if can_interface in l]
assert len(lines) >= 5, "cangen did not generate enough frames/candump did not see them"