Add test execution CLI commands

This commit is contained in:
2025-09-22 13:32:05 +00:00
parent 0676d4bdbd
commit 6520a02ef6
2 changed files with 217 additions and 0 deletions

View File

@@ -83,5 +83,47 @@ def serve(
) )
@app.command(name="list-tests")
def list_tests_cmd() -> None:
"""List all available DVT tests."""
from py_dvt_ate.app.test_commands import list_tests
list_tests()
@app.command(name="run-test")
def run_test_cmd(
test_name: Annotated[
str,
typer.Argument(help="Name of the test to run (use list-tests to see available tests)."),
],
config_file: Annotated[
str | None,
typer.Option("--config", "-c", help="Path to configuration YAML file."),
] = None,
operator: Annotated[
str | None,
typer.Option("--operator", "-o", help="Operator identifier (e.g., email address)."),
] = None,
description: Annotated[
str | None,
typer.Option("--description", "-d", help="Test run description."),
] = None,
) -> None:
"""Run a specific DVT test.
The test will connect to instruments based on the configuration file
(default: config/default.yaml). Results are stored in the data directory.
"""
from py_dvt_ate.app.test_commands import run_test
run_test(
test_name=test_name,
config_file=config_file,
operator=operator,
description=description,
)
if __name__ == "__main__": if __name__ == "__main__":
app() app()

View File

@@ -0,0 +1,175 @@
"""Test execution commands for CLI."""
import importlib
import inspect
from pathlib import Path
import typer
from py_dvt_ate.app.config import load_config
from py_dvt_ate.data.repository import SQLiteRepository
from py_dvt_ate.framework.context import ITest
from py_dvt_ate.framework.runner import TestRunner
from py_dvt_ate.instruments.factory import InstrumentConfig, InstrumentFactory
def _discover_tests() -> dict[str, type]:
"""Discover all available tests by scanning the tests package.
Returns:
Dictionary mapping test names to test classes.
"""
tests: dict[str, type] = {}
# Find the tests package directory
import py_dvt_ate.tests
tests_pkg_path = Path(py_dvt_ate.tests.__file__).parent
# Scan all Python files in the tests package
for py_file in tests_pkg_path.rglob("*.py"):
if py_file.name.startswith("_"):
continue
# Convert file path to module name
rel_path = py_file.relative_to(tests_pkg_path.parent)
module_name = "py_dvt_ate." + str(rel_path.with_suffix("")).replace("/", ".").replace("\\", ".")
try:
module = importlib.import_module(module_name)
# Find all classes that implement ITest
for _name, obj in inspect.getmembers(module, inspect.isclass):
if (
obj is not ITest
and issubclass(obj, ITest)
and not inspect.isabstract(obj)
and hasattr(obj, "name")
):
# Create instance to get the name property
instance = obj()
tests[instance.name] = obj
except (ImportError, AttributeError):
continue
return tests
def list_tests() -> None:
"""List all available DVT tests."""
tests = _discover_tests()
if not tests:
typer.echo("No tests found.")
return
typer.echo("Available DVT tests:")
typer.echo("")
for test_name in sorted(tests.keys()):
test_class = tests[test_name]
instance = test_class()
typer.echo(f" {test_name:15s} {instance.description}")
def run_test(
test_name: str,
config_file: str | None = None,
operator: str | None = None,
description: str | None = None,
) -> None:
"""Run a specific DVT test.
Args:
test_name: Name of the test to run.
config_file: Path to configuration YAML file.
operator: Operator identifier (e.g., email address).
description: Test run description.
"""
# Discover available tests
tests = _discover_tests()
if test_name not in tests:
typer.echo(f"Error: Test \'{test_name}\' not found.", err=True)
typer.echo("", err=True)
typer.echo("Available tests:", err=True)
for name in sorted(tests.keys()):
typer.echo(f" - {name}", err=True)
raise typer.Exit(code=1)
# Load configuration
config_path = config_file or "config/default.yaml"
try:
config = load_config(config_path)
except FileNotFoundError as err:
typer.echo(f"Error: Configuration file not found: {config_path}", err=True)
typer.echo("Run with --config to specify a different config file.", err=True)
raise typer.Exit(code=1) from err
except Exception as e:
typer.echo(f"Error loading configuration: {e}", err=True)
raise typer.Exit(code=1) from e
# Create repository
try:
repository = SQLiteRepository(config.data.database_path)
except Exception as e:
typer.echo(f"Error initialising repository: {e}", err=True)
raise typer.Exit(code=1) from e
# Create instruments
typer.echo(f"Connecting to instruments ({config.instruments.backend})...")
try:
# Convert AppConfig to InstrumentConfig
inst_config = InstrumentConfig(
backend=config.instruments.backend,
simulator_host=config.instruments.simulator.host,
chamber_port=config.instruments.simulator.thermal_chamber_port,
psu_port=config.instruments.simulator.power_supply_port,
dmm_port=config.instruments.simulator.multimeter_port,
chamber_visa=config.instruments.pyvisa.thermal_chamber,
psu_visa=config.instruments.pyvisa.power_supply,
dmm_visa=config.instruments.pyvisa.multimeter,
)
instruments = InstrumentFactory.create(inst_config)
except Exception as e:
typer.echo(f"Error connecting to instruments: {e}", err=True)
raise typer.Exit(code=1) from e
# Create test instance
test_class = tests[test_name]
test = test_class()
# Run test
typer.echo(f"Running test: {test.name}")
typer.echo(f"Description: {test.description}")
typer.echo("")
try:
runner = TestRunner(repository)
run_id = runner.run_test(
test=test,
instruments=instruments,
operator=operator,
description=description,
)
# Retrieve final status
run = repository.get_run(run_id)
typer.echo("")
typer.echo(f"Test completed: {run.status.value}")
typer.echo(f"Run ID: {run_id}")
# Exit with appropriate code
if run.status.value == "PASSED":
raise typer.Exit(code=0)
else:
raise typer.Exit(code=1)
except KeyboardInterrupt:
typer.echo("")
typer.echo("Test interrupted by user.")
raise typer.Exit(code=130) from None
except Exception as e:
typer.echo(f"Error running test: {e}", err=True)
raise typer.Exit(code=1) from e