Automation framework for validating CAN (Controller Area Network) bus message flows and controller-level communication scenarios.
A Controller Area Network (CAN) is the nervous system inside vehicles. Every ECU (Electronic Control Unit)—brake module, infotainment, etc.—broadcasts short messages over a shared bus. Testing CAN messages means verifying that message communication is correct, reliable, and safe.
CAN messages contain:
- ID (message priority)
- Data payload
- Timing (how often it appears)
CAN testing goals:
- What is sent
- When it is sent
- How systems react
Functional correctness:
- Does the right message ID appear?
- Are the signal values encoded/decoded correctly?
- Do ECUs react correctly to received messages?
Python tools:
- python-can (CAN interaction in Python)
- cantools (decoding/encoding CAN messages based on DBC files)
- pytest
- SocketCAN on Linux for kernel-level integration tests
- Simulated ECU or message responder
Checking that the virtual CAN interface is working on Linux:
- Confirm the interface exists and is up ip -details link show vcan0 -> Look for "state UP" and "link/can"
- Confirm CAN type -> Look for "vcan" / CAN-specific details ip -d link show vcan0
- Live traffic check (sudo apt install can-utils) candump vcan0
- In another terminal, send a test frame: cansend vcan0 123#DEADBEEF -> first terminal: vcan0 123 [4] DE AD BE EF If "vcan0" is missing, run: ./scripts/setup_vcan.sh
Simulated ECU Helper: For kernel-level tests (SocketCAN + vcan0), a mock ECU helper is implemented as a simulated ECU process/thread that listens/responds on vcan0. This helper consists of the following:
- opens its own SocketCAN bus on vcan0
- recv() requests
- applies a small state machine / handler
- send() response frames This SocketCAN setup uses the kernel CAN stack to test real timing/loopback/routing behavior while still controlling ECU logic deterministically in Python.
can-bus-automation-framework/
├── .dockerignore
├── Dockerfile
├── Jenkinsfile
├── configs/
│ └── test_environment.example.json
├── dbc/
├── docs/
│ ├── Exploratory_Test_CAN_messages.md
│ ├── STEP_BY_STEP.md
│ ├── Test_Plan_CAN_messages.md
│ ├── Test_Strategy_CAN_messages.md
│ └── AI_TRIAGE_FUNDATION_V1.md
├── scripts/
│ ├── docker_entrypoint.sh
│ ├── run_tests_in_docker.sh
│ ├── setup_vcan.py
│ └── setup_vcan.sh
├── src/
│ └── can_framework/
│ ├── __init__.py
│ ├── bus.py
│ ├── message.py
│ ├── simulated_ecu.py
│ └── validators.py
├── tests/
│ ├── conftest.py
│ ├── integration/
│ │ ├── simple_example.py
│ │ ├── test_vcan_loopback.py
│ │ └── test_simulated_ecu_reaction.py
│ ├── smoke/
│ │ └── test_framework_smoke.py
│ └── unit/
│ └── test_validators.py
├── pytest.ini
└── requirements.txt
docs/STEP_BY_STEP.md: implementation-oriented build notes for the framework evolution.docs/Test_Plan_CAN_messages.md: formal test plan covering objectives, scope, resources, coverage, and deliverables.docs/Test_Strategy_CAN_messages.md: overall validation approach, test levels, test types, risks, automation, and CI strategy.docs/Exploratory_Test_CAN_messages.md: manual exploratory scenarios for local, Docker, Docker Compose, and Linux SocketCAN workflows.docs/LOG_READING_GUIDE.md: short guide for reading the structured CAN failure logs emitted by the tests.docs/AI_TRIAGE_FOUNDATION_V1.md: step-by-step design notes for the V1 triage taxonomy and report schema.
- Install dependencies:
pip install -r requirements.txt- Run smoke/unit tests:
pytest -m "smoke or unit"This creates a timestamped structured log under log/. By default the file log uses newline-delimited JSON (.jsonl) so it stays easy to parse and closer to production-style structured logging.
- Run SocketCAN integration tests natively on Linux:
./scripts/setup_vcan.sh
RUN_VCAN_TESTS=1 pytest -m integration- Run the same test flows through Docker on any machine (e.g. macOS) with Docker, where tests are run directly on the host OS. For faster setup and easier to debug:
./scripts/run_tests_in_docker.sh smoke-unit
./scripts/run_tests_in_docker.sh integration
./scripts/run_tests_in_docker.sh integration-virtual
./scripts/run_tests_in_docker.sh all- Run the same flows with Docker Compose, where Docker runs the tests inside the repo's container image. For consistency, portability, and CI-like execution. :
docker compose build smoke-unit
docker compose run --rm smoke-unit
docker compose build integration-virtual
docker compose run --rm integration-virtual
docker compose build integration
docker compose run --rm integration
docker compose build integration-privileged
docker compose run --rm integration-privilegedNote on docker compose execution
- Use
integration-virtualon macOS/Docker Desktop - Use
integrationon a real Linux host or Linux Jenkins agent
- Run the integration tests on real Linux machine or a Linux Jenkins agent. For real SocketCAN/vcan integration, where real Linux host or Linux Jenkins agent are needed:
./scripts/run_tests_in_docker.sh integrationIf NET_ADMIN is not enough on that machine, try:
INTEGRATION_CONTAINER_MODE=privileged ./scripts/run_tests_in_docker.sh integrationNote: During development run the following:
pytest -m "smoke or unit"or
docker compose build smoke-unit
docker compose run --rm smoke-unitThe framework now emits richer test evidence so local runs and CI logs look closer to production:
- each pytest run gets a
run_id,vehicle_program,environment,component, and CAN interface context - CAN bus, transmit, receive, validator, and ECU handler events emit structured
event_typevalues - file logs default to JSONL so they can be parsed without brittle regexes
Useful environment variables:
PYTEST_LOG_FORMAT=jsonkeeps file logs machine-readablePYTEST_FILE_LOG_FORMAT=textswitches file logs back to plain text if neededCAN_VEHICLE_PROGRAM=MY_PROGRAMCAN_ENVIRONMENT=hil-benchCAN_COMPONENT=body-controller-testsCAN_RUN_ID=nightly-20260610-01
For intentional learning scenarios that are meant to fail and generate rich logs:
RUN_FAILURE_SCENARIOS=1
The current opt-in failure scenarios live in tests/integration/test_failure_scenarios.py.
They are useful when you want to practice reading timeout and wrong-response logs without changing the normal green test suite.
Why this helps:
- structured event names are easier to classify than raw free-text logs
- consistent CAN fields such as
arbitration_id_hex,payload_hex, andtimeout_smake prompt building much cleaner - the same log format can later be reused for failure analysis work without changing the test framework again
The Docker image provides a Linux userspace with python-can and a Python-based vcan0 setup helper via pyroute2.
This makes smoke/unit tests portable across macOS, Windows, and Linux, and it gives integration tests the same runtime shape used by CI.
scripts/run_tests_in_docker.sh smoke-unitruns fast tests without extra container privileges.scripts/run_tests_in_docker.sh integrationruns the trueSocketCANtests in a Linux container with--cap-add=NET_ADMIN.scripts/run_tests_in_docker.sh integration-virtualruns the same integration test files againstpython-can's portablevirtualbackend.docker compose build ...followed bydocker compose run --rm ...is the most version-compatible Compose flow.- The
socketcanintegration runner exportsRUN_VCAN_TESTS=1and provisionsvcan0inside the container beforepyteststarts.
Note:
--cap-add=NET_ADMIN: is a Docker runtime permission. It gives the container network admin capabilities.RUN_VCAN_TESTS=1: is an env variable flag. It means "yes, run the opt-in CAN integration tests". Without this flag, integration tests are skipped andvcan0setup is not attempted.
Why this changed:
- Some Docker Desktop environments fail while unpacking Debian packages during
apt-get install iproute2. - The container no longer needs
aptforvcansetup, which avoids that class of build failure entirely.
Run split approach:
- Local macOS/Windows development:
smoke-unitplusintegration-virtual - Linux or Linux-based Jenkins agent:
integrationfor trueSocketCAN/vcan
If you are on Docker Desktop for macOS and see:
unknown flag: --buildusedocker compose build <service>first, thendocker compose run --rm <service>Failed to create 'vcan0': (95, 'Operation not supported')that means Docker Desktop's Linux VM does not expose thevcanlink type, even withprivileged: trueuseintegration-virtuallocally, and reserveintegration/integration-privilegedfor real Linux hosts or Linux Jenkins agents
If Docker runtime reports Operation not permitted or Unknown device type while creating vcan0, the Linux kernel behind Docker does not currently expose vcan. In that case:
- switch to
integration-virtualfor local non-Linux development, or - retry the integration container with
INTEGRATION_CONTAINER_MODE=privileged ./scripts/run_tests_in_docker.sh integration, or - enable the
vcanmodule on the Linux host or Docker Desktop VM, or - run the integration stage on a Linux Jenkins agent.
The included Jenkinsfile builds the same Docker test image and runs:
- smoke and unit tests
- SocketCAN integration tests with
--cap-add=NET_ADMIN
That gives local Docker runs and Jenkins the same execution path, which is the key part for portability and repeatability.
Parts of this project were developed with AI assistance (OpenAI Codex/LLM tools) for scaffolding, code suggestions, and documentation drafting. All generated content was reviewed, tested, and validated by the project maintainer before commit/merge.
Copyright (C) 2026 Carlos Alberto Quiroz
All rights reserved.
This repository is published publicly for portfolio review only. Except for viewing and forking as enabled by GitHub platform functionality, no permission is granted to reuse, modify, redistribute, republish, or present this work as your own, in whole or in part, without prior written permission.