Refactor: rename SimDUT, restructure DUT simulation and test flow
This commit is contained in:
40
.gitignore
vendored
40
.gitignore
vendored
@@ -1,3 +1,39 @@
|
||||
.vscode/
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
build/
|
||||
dist/
|
||||
*.egg-info/
|
||||
|
||||
# Virtual environments
|
||||
.venv/
|
||||
env/
|
||||
venv/
|
||||
|
||||
# Pytest cache
|
||||
.pytest_cache/
|
||||
|
||||
# Test results and logs
|
||||
*.log
|
||||
*.csv
|
||||
*.html
|
||||
|
||||
# VS Code settings
|
||||
.vscode/
|
||||
|
||||
# Jupyter and IPython
|
||||
.ipynb_checkpoints/
|
||||
.profile
|
||||
|
||||
# Sphinx documentation build
|
||||
docs/_build/
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
13
Makefile
13
Makefile
@@ -0,0 +1,13 @@
|
||||
# docs/Makefile
|
||||
|
||||
SPHINXBUILD = sphinx-build
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
.PHONY: html clean
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(SOURCEDIR) $(BUILDDIR)/html
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)/*
|
||||
|
||||
76
README.md
76
README.md
@@ -0,0 +1,76 @@
|
||||
# Hardware Test Framework Template
|
||||
|
||||
A reusable, Python-based testing framework designed for validating electronic hardware and DUTs. Built around `pytest`, this template supports serial, SCPI, and simulated devices, with structured output for automated test reporting.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- ✅ Pytest integration for structured and repeatable test execution
|
||||
- ⚡ Interfaces with bench equipment via SCPI or serial
|
||||
- 🔄 Optional simulator backend for offline development
|
||||
- 📊 HTML and CSV test reports
|
||||
- 🧩 Modular architecture: easy to extend for new instruments or DUTs
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
src/
|
||||
interfaces/ # Abstract communication backends (SCPI, Serial, Dummy)
|
||||
instruments/ # Power supplies, eloads, DUT control logic
|
||||
utils/ # Logging, timing, CSV writing
|
||||
config/ # TOML/YAML config for sim/real hardware
|
||||
|
||||
tests/ # Pytest test cases
|
||||
docs/ # Sphinx documentation
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
# Set up venv (optional but recommended)
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
|
||||
or
|
||||
|
||||
.venv\Scripts\Activate.ps1 on Windows powershell
|
||||
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Run tests
|
||||
pytest
|
||||
|
||||
or
|
||||
|
||||
pytest --sim
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
To build HTML docs:
|
||||
|
||||
cd docs
|
||||
make html
|
||||
|
||||
in windows:
|
||||
|
||||
cd docs
|
||||
sphinx-build -b html . _build/html
|
||||
|
||||
Generated docs will be in docs/_build/html/index.html
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
---
|
||||
|
||||
## Author
|
||||
|
||||
Nathan Corwin — nathan@corwin.life
|
||||
|
||||
25
conftest.py
25
conftest.py
@@ -0,0 +1,25 @@
|
||||
import pytest
|
||||
from src.instruments.real_dut import RealDUT
|
||||
from src.instruments.simulated_dut import SimulatedDUT
|
||||
from src.instruments.power_supply import PowerSupply
|
||||
from src.interfaces.dummy import DummyBackend
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption("--sim", action="store_true", help="Use simulation mode")
|
||||
|
||||
@pytest.fixture
|
||||
def sim_mode(request):
|
||||
return request.config.getoption("--sim")
|
||||
|
||||
@pytest.fixture
|
||||
def dut(sim_mode):
|
||||
if sim_mode:
|
||||
return SimulatedDUT()
|
||||
return RealDUT()
|
||||
|
||||
@pytest.fixture
|
||||
def power_supply(sim_mode):
|
||||
if sim_mode:
|
||||
return PowerSupply(DummyBackend())
|
||||
# TODO: Replace with real SCPI interface when ready
|
||||
raise NotImplementedError("Real power supply interface not yet implemented")
|
||||
43
docs/_static/custom.css
vendored
Normal file
43
docs/_static/custom.css
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
/* custom.css: Modernized Sphinx Theme Tweaks */
|
||||
|
||||
body {
|
||||
font-family: "Segoe UI", "Helvetica Neue", Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
background-color: #fefefe;
|
||||
color: #333;
|
||||
max-width: 1000px;
|
||||
margin: auto;
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
color: #005f73;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0a9396;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
code, pre {
|
||||
background-color: #f0f0f0;
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 4px;
|
||||
font-family: Consolas, monospace;
|
||||
color: #1e1e1e;
|
||||
}
|
||||
|
||||
.rst-content table.docutils {
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.rst-content table.docutils th,
|
||||
.rst-content table.docutils td {
|
||||
border: 1px solid #ccc;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
Architecture
|
||||
============
|
||||
|
||||
(coming soon)
|
||||
|
||||
20
docs/conf.py
20
docs/conf.py
@@ -0,0 +1,20 @@
|
||||
# docs/conf.py
|
||||
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
|
||||
project = 'Hardware Test Framework'
|
||||
author = 'Nathan Corwin'
|
||||
release = '0.1'
|
||||
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
]
|
||||
|
||||
templates_path = ['_templates']
|
||||
exclude_patterns = []
|
||||
|
||||
html_theme = 'alabaster'
|
||||
html_static_path = ['_static']
|
||||
html_css_files = ['custom.css']
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
Getting Started
|
||||
===============
|
||||
|
||||
This guide helps you bootstrap your first test project using the framework.
|
||||
|
||||
Setup
|
||||
-----
|
||||
|
||||
1. Clone the repo:
|
||||
.. code-block:: bash
|
||||
|
||||
git clone ssh://git@git.corwin.life:23231/hw-test-template.git
|
||||
|
||||
2. Create a virtual environment and install dependencies:
|
||||
.. code-block:: bash
|
||||
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
3. Run a simulation-mode test:
|
||||
.. code-block:: bash
|
||||
|
||||
pytest --sim
|
||||
|
||||
Next Steps
|
||||
----------
|
||||
|
||||
- Edit or extend the `SimDUT` class for your DUT logic
|
||||
- Add custom test cases in `tests/`
|
||||
- Build docs using:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sphinx-build -b html . _build/html
|
||||
|
||||
31
docs/hardware.rst
Normal file
31
docs/hardware.rst
Normal file
@@ -0,0 +1,31 @@
|
||||
Real Hardware Integration
|
||||
=========================
|
||||
|
||||
This page describes how to implement real instrument interfaces.
|
||||
|
||||
Backends
|
||||
--------
|
||||
|
||||
Interfaces expected:
|
||||
- Serial-based DUT communication
|
||||
- SCPI commands over USB or LAN
|
||||
- Future support for Modbus, I2C, or CAN (if needed)
|
||||
|
||||
Adding a Real DUT
|
||||
------------------
|
||||
|
||||
1. Create a `RealDUT` class in `src/instruments/dut_controller.py`
|
||||
2. Match the interface used in `SimDUT`
|
||||
3. Uncomment the fixture in `conftest.py` to enable
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# return RealDUT(port="/dev/ttyUSB0")
|
||||
|
||||
Safety Tips
|
||||
-----------
|
||||
|
||||
- Use proper delays when enabling outputs
|
||||
- Confirm settings before triggering loads
|
||||
- Log all commands during development
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
Welcome to the Hardware Test Framework!
|
||||
=======================================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
getting_started
|
||||
usage
|
||||
architecture
|
||||
simulation
|
||||
hardware
|
||||
modules
|
||||
|
||||
----
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
This project is a reusable Python test harness for validating electronic hardware designs using Pytest, SCPI/serial interfaces, and optional simulation mode.
|
||||
|
||||
* Easy to extend
|
||||
* Supports both bench instruments and DUT interaction
|
||||
* Includes automatic HTML test reporting
|
||||
|
||||
22
docs/modules.rst
Normal file
22
docs/modules.rst
Normal file
@@ -0,0 +1,22 @@
|
||||
Module Reference
|
||||
================
|
||||
|
||||
This section provides an overview of Python modules in the framework.
|
||||
|
||||
Instrument Interfaces
|
||||
---------------------
|
||||
|
||||
.. automodule:: src.instruments.dut_controller
|
||||
:members:
|
||||
|
||||
Utilities
|
||||
---------
|
||||
|
||||
.. automodule:: src.utils.logger
|
||||
:members:
|
||||
|
||||
Serial Interfaces
|
||||
-----------------
|
||||
|
||||
.. automodule:: src.interfaces.serial
|
||||
:members:
|
||||
23
docs/simulation.rst
Normal file
23
docs/simulation.rst
Normal file
@@ -0,0 +1,23 @@
|
||||
Simulation Mode
|
||||
===============
|
||||
|
||||
The simulation backend allows you to test framework logic without connecting real hardware.
|
||||
|
||||
How It Works
|
||||
------------
|
||||
|
||||
Fixtures like `dut` use mock classes defined in `src/instruments/dut_controller.py`.
|
||||
|
||||
Example Methods:
|
||||
- `get_version()`
|
||||
- `read_temperature()`
|
||||
- `set_output_enabled()`
|
||||
|
||||
Extending Sim Mode
|
||||
------------------
|
||||
|
||||
You can expand simulation by:
|
||||
- Creating additional dummy instruments
|
||||
- Returning randomized or patterned values
|
||||
- Adding logs to mimic hardware responses
|
||||
|
||||
31
docs/usage.rst
Normal file
31
docs/usage.rst
Normal file
@@ -0,0 +1,31 @@
|
||||
Usage Guide
|
||||
===========
|
||||
|
||||
This section describes how to set up and run the test framework.
|
||||
|
||||
Quick Start
|
||||
-----------
|
||||
|
||||
1. Clone the repo and create a virtual environment:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
2. Run tests in simulation mode:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest --sim
|
||||
|
||||
3. View the report:
|
||||
|
||||
- Open `report.html` in your browser.
|
||||
|
||||
Switching Between Sim and Hardware
|
||||
----------------------------------
|
||||
|
||||
Use the `--sim` flag to control which backend is used.
|
||||
The system defaults to skipping hardware tests
|
||||
17
pytest.ini
17
pytest.ini
@@ -0,0 +1,17 @@
|
||||
[pytest]
|
||||
# Add command-line options by default
|
||||
addopts = --tb=short --strict-markers --html=report.html --self-contained-html
|
||||
|
||||
# Optional: set default HTML report location (if you use pytest-html)
|
||||
# addopts = --tb=short --strict-markers --html=report.html
|
||||
|
||||
# Markers you plan to use in test code
|
||||
markers =
|
||||
sim: mark test to run in simulation mode only
|
||||
hardware: mark test that requires real hardware
|
||||
slow: mark test as slow (e.g., long soak or ramp tests)
|
||||
sanity: mark test as quick smoke/sanity check
|
||||
|
||||
# Logging settings (optional)
|
||||
log_cli = true
|
||||
log_level = INFO
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Core testing
|
||||
pytest
|
||||
tomli
|
||||
pytest-html
|
||||
|
||||
# Documentation
|
||||
sphinx
|
||||
#pyvisa
|
||||
#pyserial
|
||||
|
||||
# Instrumentation (comment in when needed)
|
||||
pyvisa
|
||||
pyserial
|
||||
|
||||
17
src/instruments/dut_controller.py
Normal file
17
src/instruments/dut_controller.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# src/instruments/dut_controller.py
|
||||
|
||||
from src.utils.logger import init_logger
|
||||
from src.instruments.simulated_dut import SimulatedDUT
|
||||
# from src.instruments.real_dut import RealDUT # Enable when ready
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
def get_dut(sim: bool = False):
|
||||
"""Returns a DUT instance based on sim mode."""
|
||||
if sim:
|
||||
logger.info("Using SimDUT")
|
||||
return SimulatedDUT()
|
||||
else:
|
||||
logger.info("Using RealDUT")
|
||||
raise NotImplementedError("Real DUT is not yet implemented.")
|
||||
# return RealDUT() # Uncomment when ready
|
||||
@@ -0,0 +1,59 @@
|
||||
"""
|
||||
Power Supply controller
|
||||
|
||||
This module provides a high-level interface to control a programmable power supply.
|
||||
It supports setting voltage, enabling/disabling output, and measuring values from the supply.
|
||||
Communication is delegated to a backend (e.g., SCPI, dummy simulator).
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PowerSupply:
|
||||
def __init__(self, comm):
|
||||
"""
|
||||
Initialize the power supply interface.
|
||||
|
||||
Args:
|
||||
comm: A communication backend object that implements `write()` and `query()` methods.
|
||||
"""
|
||||
self.comm = comm
|
||||
logger.info("PowerSupply interface initialized.")
|
||||
|
||||
def set_voltage(self, voltage: float):
|
||||
"""
|
||||
Set the output voltage of the power supply.
|
||||
|
||||
Args:
|
||||
voltage: The desired voltage in volts.
|
||||
"""
|
||||
logger.debug(f"Setting voltage to {voltage:.2f}V")
|
||||
self.comm.write(f"VOLT {voltage}")
|
||||
|
||||
def enable_output(self, enable: bool):
|
||||
"""
|
||||
Enable or disable the power supply output.
|
||||
|
||||
Args:
|
||||
enable: True to enable output, False to disable.
|
||||
"""
|
||||
command = "OUTP ON" if enable else "OUTP OFF"
|
||||
logger.debug(f"Setting output state: {command}")
|
||||
self.comm.write(command)
|
||||
|
||||
def measure_voltage(self) -> float:
|
||||
"""
|
||||
Measure and return the current output voltage.
|
||||
|
||||
Returns:
|
||||
The measured voltage in volts.
|
||||
"""
|
||||
response = self.comm.query("MEAS:VOLT?")
|
||||
logger.debug(f"Measured voltage response: {response}")
|
||||
try:
|
||||
return float(response.strip())
|
||||
except ValueError:
|
||||
logger.error(f"Invalid voltage response: {response}")
|
||||
return float('nan')
|
||||
|
||||
22
src/instruments/real_dut.py
Normal file
22
src/instruments/real_dut.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# src/instruments/real_dut.py
|
||||
|
||||
from src.utils.logger import init_logger
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
class RealDUT:
|
||||
"""Real Device Under Test (stub)."""
|
||||
|
||||
def __init__(self, port="/dev/ttyUSB0"):
|
||||
self.port = port
|
||||
logger.info(f"Connecting to real DUT on port {port}")
|
||||
# Set up serial or network connection here
|
||||
|
||||
def get_version(self):
|
||||
raise NotImplementedError("Real DUT not implemented yet.")
|
||||
|
||||
def read_temperature(self):
|
||||
raise NotImplementedError("Real DUT not implemented yet.")
|
||||
|
||||
def set_output_enabled(self, enabled: bool):
|
||||
raise NotImplementedError("Real DUT not implemented yet.")
|
||||
27
src/instruments/simulated_dut.py
Normal file
27
src/instruments/simulated_dut.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# src/instruments/simulated_dut.py
|
||||
|
||||
from src.utils.logger import init_logger
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
class SimulatedDUT:
|
||||
"""Simulated Device Under Test."""
|
||||
|
||||
def __init__(self):
|
||||
logger.info("SimDUT initialized")
|
||||
self.version = "sim-1.0.0"
|
||||
|
||||
def get_version(self):
|
||||
"""Return simulated firmware version string."""
|
||||
return self.version
|
||||
|
||||
def read_temperature(self):
|
||||
"""Simulated temperature sensor readout."""
|
||||
logger.debug("SimDUT returning temperature")
|
||||
return 42.5
|
||||
|
||||
def set_output_enabled(self, enabled: bool):
|
||||
"""Simulated control for DUT output."""
|
||||
state = "enabled" if enabled else "disabled"
|
||||
logger.debug(f"SimDUT setting output: {state}")
|
||||
return f"Output {state}"
|
||||
@@ -0,0 +1,51 @@
|
||||
"""
|
||||
Dummy backend for instrument simulation.
|
||||
|
||||
This backend emulates SCPI-compatible instruments by printing or returning canned responses.
|
||||
Used for simulation/testing when hardware is not connected.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DummyBackend:
|
||||
def __init__(self):
|
||||
self.state = {
|
||||
"VOLT": 12.0,
|
||||
"OUTP": False,
|
||||
}
|
||||
logger.info("Initialized DummyBackend")
|
||||
|
||||
def write(self, command: str):
|
||||
logger.debug(f"[SIM WRITE] {command}")
|
||||
parts = command.strip().split()
|
||||
|
||||
if not parts:
|
||||
return
|
||||
|
||||
cmd = parts[0].upper()
|
||||
|
||||
if cmd == "VOLT" and len(parts) == 2:
|
||||
try:
|
||||
self.state["VOLT"] = float(parts[1])
|
||||
logger.info(f"[SIM] Voltage set to {self.state['VOLT']} V")
|
||||
except ValueError:
|
||||
logger.warning(f"[SIM] Invalid voltage value: {parts[1]}")
|
||||
|
||||
elif cmd == "OUTP" and len(parts) == 2:
|
||||
state = parts[1].upper()
|
||||
self.state["OUTP"] = state == "ON"
|
||||
logger.info(f"[SIM] Output {'enabled' if self.state['OUTP'] else 'disabled'}")
|
||||
|
||||
def query(self, command: str) -> str:
|
||||
logger.debug(f"[SIM QUERY] {command}")
|
||||
cmd = command.strip().upper()
|
||||
|
||||
if cmd == "MEAS:VOLT?":
|
||||
# Simulated slight fluctuation
|
||||
voltage = self.state["VOLT"] + 0.01
|
||||
return f"{voltage:.2f}"
|
||||
|
||||
return "0"
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
"""
|
||||
SCPI communication backend for test instruments using VISA.
|
||||
|
||||
This module provides a communication layer to talk to SCPI-compatible devices via USB, GPIB, or TCP/IP.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import pyvisa
|
||||
except ImportError:
|
||||
pyvisa = None
|
||||
logger.warning("pyvisa not available. SCPI interface will not work until it's installed.")
|
||||
|
||||
|
||||
class SCPIDevice:
|
||||
def __init__(self, resource_name: str, timeout: int = 2000):
|
||||
"""
|
||||
Initialize SCPI communication with the specified VISA resource.
|
||||
|
||||
Args:
|
||||
resource_name: VISA resource string (e.g., "USB0::0x1234::0x5678::INSTR").
|
||||
timeout: Communication timeout in milliseconds.
|
||||
"""
|
||||
if not pyvisa:
|
||||
raise ImportError("pyvisa is required for SCPI backend.")
|
||||
|
||||
self.rm = pyvisa.ResourceManager()
|
||||
self.instrument = self.rm.open_resource(resource_name)
|
||||
self.instrument.timeout = timeout
|
||||
logger.info(f"SCPI instrument initialized: {resource_name}")
|
||||
|
||||
def write(self, command: str):
|
||||
logger.debug(f"[SCPI WRITE] {command}")
|
||||
self.instrument.write(command)
|
||||
|
||||
def query(self, command: str) -> str:
|
||||
logger.debug(f"[SCPI QUERY] {command}")
|
||||
return self.instrument.query(command)
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Serial communication backend for DUTs and microcontrollers (e.g. Arduino).
|
||||
|
||||
Provides a simple wrapper around pyserial for consistent I/O behavior.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import serial
|
||||
except ImportError:
|
||||
serial = None
|
||||
logger.warning("pyserial not available. Serial interface will not work until it's installed.")
|
||||
|
||||
|
||||
class SerialDevice:
|
||||
def __init__(self, port: str, baudrate: int = 115200, timeout: float = 1.0):
|
||||
"""
|
||||
Initialize serial communication with the device.
|
||||
|
||||
Args:
|
||||
port: Serial port (e.g., COM3, /dev/ttyUSB0)
|
||||
baudrate: Baud rate (default: 115200)
|
||||
timeout: Read timeout in seconds (default: 1.0)
|
||||
"""
|
||||
if not serial:
|
||||
raise ImportError("pyserial is required for SerialDevice backend.")
|
||||
|
||||
self.ser = serial.Serial(port=port, baudrate=baudrate, timeout=timeout)
|
||||
logger.info(f"Serial port {port} opened at {baudrate} baud")
|
||||
|
||||
def write(self, data: str):
|
||||
"""
|
||||
Send a string over the serial connection.
|
||||
"""
|
||||
logger.debug(f"[SERIAL WRITE] {data.strip()}")
|
||||
self.ser.write(data.encode('utf-8'))
|
||||
|
||||
def read_line(self) -> str:
|
||||
"""
|
||||
Read a single line from the serial connection.
|
||||
"""
|
||||
line = self.ser.readline().decode('utf-8').strip()
|
||||
logger.debug(f"[SERIAL READ] {line}")
|
||||
return line
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Close the serial port connection.
|
||||
"""
|
||||
self.ser.close()
|
||||
logger.info("Serial port closed")
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# src/utils/logger.py
|
||||
|
||||
import logging
|
||||
|
||||
def init_logger(name: str = "test_framework", level: int = logging.INFO) -> logging.Logger:
|
||||
"""
|
||||
Initializes and returns a logger instance with a standard format.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
Name of the logger.
|
||||
level : int
|
||||
Logging level (e.g., logging.INFO, logging.DEBUG).
|
||||
|
||||
Returns
|
||||
-------
|
||||
logging.Logger
|
||||
Configured logger instance.
|
||||
"""
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(level)
|
||||
|
||||
if not logger.handlers:
|
||||
# Console handler
|
||||
ch = logging.StreamHandler()
|
||||
ch.setLevel(level)
|
||||
|
||||
# Formatter
|
||||
formatter = logging.Formatter('[%(levelname)s] %(name)s: %(message)s')
|
||||
ch.setFormatter(formatter)
|
||||
|
||||
logger.addHandler(ch)
|
||||
|
||||
return logger
|
||||
|
||||
14
tests/test_dut.py
Normal file
14
tests/test_dut.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# tests/test_dut.py
|
||||
|
||||
import pytest
|
||||
|
||||
def test_dut_version(dut):
|
||||
"""Ensure the DUT returns a version string in sim mode."""
|
||||
version = dut.get_version()
|
||||
assert version.startswith("sim-")
|
||||
|
||||
def test_dut_temperature(dut):
|
||||
"""Test readback of simulated temperature."""
|
||||
temp = dut.read_temperature()
|
||||
assert 20 <= temp <= 100 # Acceptable range
|
||||
|
||||
20
tests/test_power_supply.py
Normal file
20
tests/test_power_supply.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# tests/test_power_supply.py
|
||||
"""
|
||||
Basic functional tests for the PowerSupply
|
||||
"""
|
||||
|
||||
def test_set_and_measure_voltage(power_supply):
|
||||
test_voltage = 5.0
|
||||
power_supply.set_voltage(test_voltage)
|
||||
measured = power_supply.measure_voltage()
|
||||
assert abs(measured - test_voltage) < 0.05
|
||||
|
||||
def test_output_toggle(power_supply):
|
||||
power_supply.enable_output(True)
|
||||
# Only works for DummyBackend which keeps state
|
||||
if hasattr(power_supply.comm, "state"):
|
||||
assert power_supply.comm.state["OUTP"] is True
|
||||
|
||||
power_supply.enable_output(False)
|
||||
if hasattr(power_supply.comm, "state"):
|
||||
assert power_supply.comm.state["OUTP"] is False
|
||||
Reference in New Issue
Block a user