From 7bec7dc38d6f076523652d99f641467970ac6cca Mon Sep 17 00:00:00 2001 From: stacknil Date: Fri, 15 May 2026 18:04:44 +0800 Subject: [PATCH] Improve CLI error reporting --- src/telemetry_window_demo/cli.py | 11 ++++--- src/telemetry_window_demo/io.py | 19 +++++++----- tests/test_cli_errors.py | 50 ++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 11 deletions(-) create mode 100644 tests/test_cli_errors.py diff --git a/src/telemetry_window_demo/cli.py b/src/telemetry_window_demo/cli.py index 6b4f2cc..e66c218 100644 --- a/src/telemetry_window_demo/cli.py +++ b/src/telemetry_window_demo/cli.py @@ -42,10 +42,13 @@ } -def main() -> None: - parser = build_parser() - args = parser.parse_args() - args.func(args) +def main(argv: Sequence[str] | None = None) -> None: + parser = build_parser() + args = parser.parse_args(argv) + try: + args.func(args) + except (OSError, ValueError) as exc: + parser.exit(status=1, message=f"error: {exc}\n") def build_parser() -> argparse.ArgumentParser: diff --git a/src/telemetry_window_demo/io.py b/src/telemetry_window_demo/io.py index a04647d..377482f 100644 --- a/src/telemetry_window_demo/io.py +++ b/src/telemetry_window_demo/io.py @@ -10,13 +10,18 @@ from .schema import validate_event_frame -def load_config(path: str | Path) -> dict[str, Any]: - config_path = Path(path) - with config_path.open("r", encoding="utf-8") as handle: - config = yaml.safe_load(handle) or {} - if not isinstance(config, dict): - raise ValueError("Configuration must deserialize to a mapping.") - return config +def load_config(path: str | Path) -> dict[str, Any]: + config_path = Path(path) + if not config_path.exists(): + raise FileNotFoundError(f"Config file not found: {config_path}") + try: + with config_path.open("r", encoding="utf-8") as handle: + config = yaml.safe_load(handle) or {} + except yaml.YAMLError as exc: + raise ValueError(f"Invalid YAML config in {config_path}: {exc}") from exc + if not isinstance(config, dict): + raise ValueError("Configuration must deserialize to a mapping.") + return config def resolve_config_path(config_path: str | Path, value: str | Path) -> Path: diff --git a/tests/test_cli_errors.py b/tests/test_cli_errors.py new file mode 100644 index 0000000..5699af7 --- /dev/null +++ b/tests/test_cli_errors.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +import pytest +import yaml + +from telemetry_window_demo.cli import main + + +def test_main_reports_config_errors_without_traceback(tmp_path, capsys) -> None: + config_path = tmp_path / "missing-input-path.yaml" + config_path.write_text( + yaml.safe_dump({"output_dir": str(tmp_path / "processed")}), + encoding="utf-8", + ) + + with pytest.raises(SystemExit) as excinfo: + main(["run", "--config", str(config_path)]) + + assert excinfo.value.code == 1 + stderr = capsys.readouterr().err + assert stderr.startswith("error: ") + assert "input_path" in stderr + assert "Traceback" not in stderr + + +def test_main_reports_missing_config_without_traceback(tmp_path, capsys) -> None: + config_path = tmp_path / "missing.yaml" + + with pytest.raises(SystemExit) as excinfo: + main(["run", "--config", str(config_path)]) + + assert excinfo.value.code == 1 + stderr = capsys.readouterr().err + assert stderr.startswith("error: ") + assert "Config file not found" in stderr + assert "Traceback" not in stderr + + +def test_main_reports_invalid_yaml_config_without_traceback(tmp_path, capsys) -> None: + config_path = tmp_path / "broken.yaml" + config_path.write_text("input_path: [\n", encoding="utf-8") + + with pytest.raises(SystemExit) as excinfo: + main(["run", "--config", str(config_path)]) + + assert excinfo.value.code == 1 + stderr = capsys.readouterr().err + assert stderr.startswith("error: ") + assert "Invalid YAML config" in stderr + assert "Traceback" not in stderr