Add test execution CLI commands
This commit is contained in:
@@ -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()
|
||||||
|
|||||||
175
src/py_dvt_ate/app/test_commands.py
Normal file
175
src/py_dvt_ate/app/test_commands.py
Normal 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
|
||||||
Reference in New Issue
Block a user