add framework
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run C#/.NET Harness validation commands."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class ValidationConfigurationError(Exception):
|
||||
"""Raised when automatic target discovery is ambiguous."""
|
||||
|
||||
|
||||
def load_env_commands() -> list[str]:
|
||||
raw = os.environ.get("HARNESS_VALIDATION_COMMANDS", "")
|
||||
return [line for line in raw.splitlines() if line.strip()]
|
||||
|
||||
|
||||
def _root_files_with_suffix(root: Path, suffix: str) -> list[Path]:
|
||||
return sorted(
|
||||
(
|
||||
path.resolve()
|
||||
for path in root.iterdir()
|
||||
if path.is_file() and path.suffix.lower() == suffix
|
||||
),
|
||||
key=lambda path: path.name.casefold(),
|
||||
)
|
||||
|
||||
|
||||
def _select_single_target(candidates: list[Path], candidate_kind: str) -> Path | None:
|
||||
if len(candidates) > 1:
|
||||
names = ", ".join(path.name for path in candidates)
|
||||
raise ValidationConfigurationError(
|
||||
f"Multiple {candidate_kind} targets found at repository root: {names}. "
|
||||
"Keep exactly one target or set HARNESS_VALIDATION_COMMANDS "
|
||||
"to define validation explicitly."
|
||||
)
|
||||
return candidates[0] if candidates else None
|
||||
|
||||
|
||||
def discover_target(root: Path) -> Path | None:
|
||||
solutions = _root_files_with_suffix(root, ".sln")
|
||||
if solutions:
|
||||
return _select_single_target(solutions, "solution (.sln)")
|
||||
|
||||
projects = _root_files_with_suffix(root, ".csproj")
|
||||
return _select_single_target(projects, "project (.csproj)")
|
||||
|
||||
|
||||
def load_dotnet_commands(root: Path) -> list[str]:
|
||||
target = discover_target(root)
|
||||
if target is None:
|
||||
return []
|
||||
|
||||
quoted_target = f'"{target}"'
|
||||
return [
|
||||
f"dotnet restore {quoted_target}",
|
||||
f"dotnet build {quoted_target} --configuration Debug --no-restore",
|
||||
f"dotnet test {quoted_target} --configuration Debug --no-build --no-restore",
|
||||
]
|
||||
|
||||
|
||||
def discover_commands(root: Path) -> list[str]:
|
||||
env_commands = load_env_commands()
|
||||
if env_commands:
|
||||
return env_commands
|
||||
return load_dotnet_commands(root)
|
||||
|
||||
|
||||
def run_command(command: str, root: Path) -> subprocess.CompletedProcess:
|
||||
return subprocess.run(
|
||||
command,
|
||||
cwd=root,
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
)
|
||||
|
||||
|
||||
def emit_stream(prefix: str, content: str, *, stream) -> None:
|
||||
text = (content or "").strip()
|
||||
if not text:
|
||||
return
|
||||
print(prefix, file=stream)
|
||||
print(text, file=stream)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
root = Path(__file__).resolve().parent.parent
|
||||
try:
|
||||
commands = discover_commands(root)
|
||||
except ValidationConfigurationError as error:
|
||||
print(f"Validation configuration error: {error}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
if not commands:
|
||||
print("No C#/.NET validation target found.")
|
||||
print("Add a root .sln or .csproj, or set HARNESS_VALIDATION_COMMANDS.")
|
||||
return 0
|
||||
|
||||
for command in commands:
|
||||
print(f"$ {command}")
|
||||
result = run_command(command, root)
|
||||
emit_stream("[stdout]", result.stdout, stream=sys.stdout)
|
||||
emit_stream("[stderr]", result.stderr, stream=sys.stderr)
|
||||
if result.returncode != 0:
|
||||
print(f"Validation failed: {command}", file=sys.stderr)
|
||||
return result.returncode
|
||||
|
||||
print("Validation succeeded.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user