Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
All services: admin-v2, studio-v2, website, ai-compliance-sdk, consent-service, klausur-service, voice-service, and infrastructure. Large PDFs and compiled binaries excluded via .gitignore.
230 lines
7.1 KiB
Python
230 lines
7.1 KiB
Python
"""
|
|
Go Test Runner
|
|
|
|
Fuehrt Go-Tests aus und parsed die Ergebnisse.
|
|
"""
|
|
|
|
import subprocess
|
|
import json
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Dict, List, Optional
|
|
from dataclasses import dataclass, field
|
|
|
|
|
|
@dataclass
|
|
class GoTestResult:
|
|
"""Ergebnis eines einzelnen Go-Tests"""
|
|
package: str
|
|
test_name: str
|
|
passed: bool
|
|
duration_seconds: float
|
|
output: str = ""
|
|
|
|
|
|
@dataclass
|
|
class GoTestSummary:
|
|
"""Zusammenfassung eines Go-Test-Runs"""
|
|
total: int = 0
|
|
passed: int = 0
|
|
failed: int = 0
|
|
skipped: int = 0
|
|
duration_seconds: float = 0.0
|
|
coverage_percent: Optional[float] = None
|
|
results: List[GoTestResult] = field(default_factory=list)
|
|
raw_output: str = ""
|
|
|
|
|
|
class GoTestRunner:
|
|
"""
|
|
Runner fuer Go-Tests.
|
|
|
|
Verwendet `go test -json` fuer strukturierte Ausgabe.
|
|
"""
|
|
|
|
def __init__(self, base_path: Path):
|
|
self.base_path = base_path
|
|
|
|
async def run(self, with_coverage: bool = True, timeout: int = 300) -> GoTestSummary:
|
|
"""
|
|
Fuehrt Go-Tests aus.
|
|
|
|
Args:
|
|
with_coverage: Coverage erfassen
|
|
timeout: Timeout in Sekunden
|
|
|
|
Returns:
|
|
GoTestSummary mit allen Ergebnissen
|
|
"""
|
|
if not self.base_path.exists():
|
|
return GoTestSummary(raw_output="Pfad existiert nicht")
|
|
|
|
cmd = ["go", "test", "-v", "-json"]
|
|
if with_coverage:
|
|
cmd.extend(["-cover", "-coverprofile=coverage.out"])
|
|
cmd.append("./...")
|
|
|
|
try:
|
|
result = subprocess.run(
|
|
cmd,
|
|
cwd=str(self.base_path),
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=timeout,
|
|
)
|
|
|
|
return self._parse_output(result.stdout, result.stderr)
|
|
|
|
except subprocess.TimeoutExpired:
|
|
return GoTestSummary(raw_output=f"Timeout nach {timeout} Sekunden")
|
|
except FileNotFoundError:
|
|
return GoTestSummary(raw_output="Go nicht installiert")
|
|
except Exception as e:
|
|
return GoTestSummary(raw_output=str(e))
|
|
|
|
def _parse_output(self, stdout: str, stderr: str) -> GoTestSummary:
|
|
"""Parsed die JSON-Ausgabe von go test"""
|
|
summary = GoTestSummary(raw_output=stdout[:10000] if stdout else stderr[:10000])
|
|
|
|
current_test: Dict[str, str] = {}
|
|
test_outputs: Dict[str, List[str]] = {}
|
|
|
|
for line in stdout.split("\n"):
|
|
if not line.strip():
|
|
continue
|
|
|
|
try:
|
|
event = json.loads(line)
|
|
action = event.get("Action")
|
|
package = event.get("Package", "")
|
|
test = event.get("Test", "")
|
|
elapsed = event.get("Elapsed", 0)
|
|
output = event.get("Output", "")
|
|
|
|
# Test-Output sammeln
|
|
if test and output:
|
|
key = f"{package}:{test}"
|
|
if key not in test_outputs:
|
|
test_outputs[key] = []
|
|
test_outputs[key].append(output)
|
|
|
|
# Test-Ergebnis
|
|
if action == "pass" and test:
|
|
summary.passed += 1
|
|
summary.total += 1
|
|
summary.results.append(GoTestResult(
|
|
package=package,
|
|
test_name=test,
|
|
passed=True,
|
|
duration_seconds=elapsed,
|
|
output="".join(test_outputs.get(f"{package}:{test}", [])),
|
|
))
|
|
|
|
elif action == "fail" and test:
|
|
summary.failed += 1
|
|
summary.total += 1
|
|
summary.results.append(GoTestResult(
|
|
package=package,
|
|
test_name=test,
|
|
passed=False,
|
|
duration_seconds=elapsed,
|
|
output="".join(test_outputs.get(f"{package}:{test}", [])),
|
|
))
|
|
|
|
elif action == "skip" and test:
|
|
summary.skipped += 1
|
|
summary.total += 1
|
|
|
|
# Package-Ergebnis (Gesamtdauer)
|
|
elif action in ["pass", "fail"] and not test and elapsed:
|
|
summary.duration_seconds = max(summary.duration_seconds, elapsed)
|
|
|
|
except json.JSONDecodeError:
|
|
# Nicht-JSON-Zeilen ignorieren (z.B. Coverage-Output)
|
|
if "coverage:" in line.lower():
|
|
# z.B. "coverage: 75.2% of statements"
|
|
try:
|
|
parts = line.split("coverage:")
|
|
if len(parts) > 1:
|
|
percent_str = parts[1].strip().split("%")[0]
|
|
summary.coverage_percent = float(percent_str)
|
|
except (ValueError, IndexError):
|
|
pass
|
|
|
|
return summary
|
|
|
|
async def run_single_test(self, test_name: str, timeout: int = 60) -> GoTestResult:
|
|
"""
|
|
Fuehrt einen einzelnen Test aus.
|
|
|
|
Args:
|
|
test_name: Name des Tests (z.B. "TestMyFunction")
|
|
timeout: Timeout in Sekunden
|
|
|
|
Returns:
|
|
GoTestResult fuer den spezifischen Test
|
|
"""
|
|
cmd = ["go", "test", "-v", "-run", test_name, "./..."]
|
|
|
|
try:
|
|
result = subprocess.run(
|
|
cmd,
|
|
cwd=str(self.base_path),
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=timeout,
|
|
)
|
|
|
|
passed = "PASS" in result.stdout
|
|
return GoTestResult(
|
|
package=str(self.base_path),
|
|
test_name=test_name,
|
|
passed=passed,
|
|
duration_seconds=0.0,
|
|
output=result.stdout + result.stderr,
|
|
)
|
|
|
|
except Exception as e:
|
|
return GoTestResult(
|
|
package=str(self.base_path),
|
|
test_name=test_name,
|
|
passed=False,
|
|
duration_seconds=0.0,
|
|
output=str(e),
|
|
)
|
|
|
|
async def get_coverage_report(self) -> Optional[Dict]:
|
|
"""
|
|
Liest den Coverage-Bericht.
|
|
|
|
Returns:
|
|
Dict mit Coverage-Details oder None
|
|
"""
|
|
coverage_file = self.base_path / "coverage.out"
|
|
if not coverage_file.exists():
|
|
return None
|
|
|
|
try:
|
|
result = subprocess.run(
|
|
["go", "tool", "cover", "-func=coverage.out"],
|
|
cwd=str(self.base_path),
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
|
|
# Parse "total:" Zeile
|
|
for line in result.stdout.split("\n"):
|
|
if "total:" in line:
|
|
parts = line.split()
|
|
if len(parts) >= 3:
|
|
percent_str = parts[-1].replace("%", "")
|
|
return {
|
|
"total_coverage": float(percent_str),
|
|
"raw_output": result.stdout,
|
|
}
|
|
|
|
except Exception:
|
|
pass
|
|
|
|
return None
|