""" NLProxy SDK test runner CLI. This command runs the SDK test suite using pytest and supports class-level and flow/marker-based filtering so tests can be executed in a scalable, reproducible way. Examples: python -m nlproxy tests python -m nlproxy tests --class TestPromptShield python -m nlproxy tests --flow unit python -m nlproxy tests --flow integration --pytest-args="-v --maxfail=1" The runner defaults to `tests.py` in the project root and delegates to the Python interpreter environment that executes the CLI. """ from __future__ import annotations import argparse import shlex import subprocess import sys from typing import List, Optional def create_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( prog="nlproxy tests", description="Run NLProxy SDK tests with optional class or flow filtering.", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: python -m nlproxy tests python -m nlproxy tests --class TestPromptShield python -m nlproxy tests --flow unit python -m nlproxy tests --flow integration --pytest-args='-v --maxfail=1' """, ) parser.add_argument( "-c", "--class", dest="test_class", help="Run only a single test class or filter expression from tests.py", ) parser.add_argument( "-f", "--flow", dest="flow", help="Run tests by pytest marker/flow name (e.g. unit, integration, performance, asyncio)", ) parser.add_argument( "--path", type=str, default="tests.py", help="Path to the test module or directory to execute", ) parser.add_argument( "--pytest-args", type=str, default="", help="Additional pytest arguments to pass through", ) parser.add_argument( "-q", "--quiet", action="store_true", help="Suppress output from the test runner when possible", ) return parser def build_pytest_args(args: argparse.Namespace) -> List[str]: pytest_args: List[str] = [sys.executable, "-m", "pytest", args.path] if args.test_class: pytest_args.extend(["-k", args.test_class]) if args.flow: pytest_args.extend(["-m", args.flow]) if args.pytest_args: pytest_args.extend(shlex.split(args.pytest_args)) if args.quiet: pytest_args.append("-q") return pytest_args def cmd_tests(args: argparse.Namespace) -> int: pytest_args = build_pytest_args(args) print(f"Running tests: {' '.join(pytest_args)}") result = subprocess.run(pytest_args, env=sys.environ.copy()) return result.returncode def main(argv: Optional[List[str]] = None) -> int: parser = create_parser() args = parser.parse_args(argv) try: return cmd_tests(args) except KeyboardInterrupt: print("Test execution interrupted by user.", file=sys.stderr) return 130 except FileNotFoundError as exc: print(f"Test runner failed: {exc}", file=sys.stderr) return 1 except Exception as exc: print(f"Unexpected error while running tests: {exc}", file=sys.stderr) return 1 if __name__ == "__main__": raise SystemExit(main())