CI / test (push) Canceled after 0s
## Summary epic r04 - begin refactor ## Change Type - [x] Cowork feature - [ ] Bug fix - [ ] Core AI contribution - [ ] Test / hardening - [ ] Performance - [ ] Documentation ## Related Work Cowork Task: Core Repo: http://34.143.229.138/gitea-admin/fsg-ai-core-assets Core AI Issue: Core Task: Related PR: ## Scope What is intentionally included? What is intentionally NOT included? ## Validation - [ ] Unit tests - [ ] Integration tests - [ ] Manual verification - [ ] Regression check Commands / evidence: ## Security Impact Permission / credential / network / customer data impact: ## Compatibility - [ ] No breaking change - [ ] Breaking change documented ## Reviewer Notes Anything Cowork reviewers should pay attention to. --------- Co-authored-by: Anh Tran Nguyen Minh <anhtnm1@fpt.com> Co-authored-by: Huong Le Thi Thien <huongltt35@fpt.com> Co-authored-by: Nam Pham Dinh Thanh <nampdt@fpt.com> Co-authored-by: Vu Dam Tuan <vudt15@fpt.com> Co-authored-by: Hiep Ha Van <hiephv3@fpt.com> Co-authored-by: Lam Hoang Van <lamhv7@fpt.com> Reviewed-on: #7 Co-authored-by: Duy Le Huu <duylh19@fpt.com>
152 lines
4.8 KiB
Python
152 lines
4.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Unified CASAN Quality Gate Orchestrator (EPIC R10 - Quality Assurance).
|
|
|
|
Runs all verification gates to validate architecture, security, single responsibility,
|
|
and test suite compliance before merging PRs or cutting a release.
|
|
|
|
Verification Stages (CASAN):
|
|
1. [C] Clean Architecture Guard (scripts/check_imports.py)
|
|
2. [A] Atomic & Secrets Audit (scripts/audit_security.py)
|
|
3. [S] Single Responsibility / LOC Guard (scripts/check_loc.py)
|
|
4. [O] Orphan Module Guard (scripts/check_orphan_modules.py)
|
|
5. [A/N] Automated Tests & No-Regression Suite (pytest)
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
from typing import List, Tuple
|
|
|
|
# Ensure stdout handles UTF-8 on Windows consoles without codec crash
|
|
if hasattr(sys.stdout, "reconfigure"):
|
|
try:
|
|
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
except Exception:
|
|
pass
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
|
|
|
|
def run_stage(title: str, cmd: List[str], cwd: Path) -> Tuple[bool, float, str]:
|
|
"""Execute a single quality gate command and measure elapsed duration.
|
|
|
|
Returns:
|
|
A tuple of (success_boolean, elapsed_seconds, combined_output)
|
|
"""
|
|
print(f"\n>> Running Gate: {title} ...")
|
|
start_time = time.time()
|
|
try:
|
|
proc = subprocess.run(
|
|
cmd,
|
|
cwd=cwd,
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
errors="replace",
|
|
)
|
|
elapsed = time.time() - start_time
|
|
success = proc.returncode == 0
|
|
output = proc.stdout + ("\n" + proc.stderr if proc.stderr else "")
|
|
return success, elapsed, output
|
|
except Exception as exc:
|
|
elapsed = time.time() - start_time
|
|
return False, elapsed, f"Exception occurred while running {cmd}: {exc}"
|
|
|
|
|
|
def main() -> int:
|
|
"""Main CLI orchestrator for CASAN quality gates."""
|
|
parser = argparse.ArgumentParser(description="Run CASAN Quality Gates on the repository.")
|
|
parser.add_argument(
|
|
"--skip-tests",
|
|
action="store_true",
|
|
help="Skip running pytest (run static and architectural guards only)",
|
|
)
|
|
parser.add_argument(
|
|
"--verbose",
|
|
"-v",
|
|
action="store_true",
|
|
help="Print detailed command output for passing gates as well",
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
print("=" * 75)
|
|
print("COWORK LOCAL - CASAN QUALITY GATE RUNNER")
|
|
print("=" * 75)
|
|
|
|
stages = [
|
|
(
|
|
"C - Clean Architecture Boundary Check",
|
|
[sys.executable, str(REPO_ROOT / "scripts" / "check_imports.py")],
|
|
),
|
|
(
|
|
"A - Secrets & Plaintext Credentials Audit",
|
|
[sys.executable, str(REPO_ROOT / "scripts" / "audit_security.py")],
|
|
),
|
|
(
|
|
"S - Single Responsibility LOC Limit (<= 400 LOC)",
|
|
[sys.executable, str(REPO_ROOT / "scripts" / "check_loc.py"), "--max-lines", "400"],
|
|
),
|
|
# Ba cổng trên không cổng nào bắt được mã chết: một file không ai import
|
|
# vẫn đúng chiều phụ thuộc, vẫn không có credential, vẫn dưới 400 dòng.
|
|
# Cổng O đi tìm đúng khoảng trống đó.
|
|
(
|
|
"O - Orphan Module Guard (moi module phai co noi import)",
|
|
[sys.executable, str(REPO_ROOT / "scripts" / "check_orphan_modules.py")],
|
|
),
|
|
]
|
|
|
|
if not args.skip_tests:
|
|
stages.append(
|
|
(
|
|
"A/N - Automated Pytest Suite (No-Regression)",
|
|
[sys.executable, "-m", "pytest", "-q"],
|
|
)
|
|
)
|
|
|
|
results = []
|
|
all_passed = True
|
|
total_start = time.time()
|
|
|
|
for title, cmd in stages:
|
|
success, elapsed, output = run_stage(title, cmd, cwd=REPO_ROOT)
|
|
results.append((title, success, elapsed, output))
|
|
|
|
if success:
|
|
print(f" [PASS] {title} ({elapsed:.2f}s)")
|
|
if args.verbose:
|
|
print(output.strip())
|
|
else:
|
|
all_passed = False
|
|
print(f" [FAIL] {title} ({elapsed:.2f}s)")
|
|
print("\n--- Output ---")
|
|
print(output.strip())
|
|
print("--------------")
|
|
|
|
total_elapsed = time.time() - total_start
|
|
|
|
print("\n" + "=" * 75)
|
|
print("QUALITY GATE SUMMARY REPORT")
|
|
print("=" * 75)
|
|
for title, success, elapsed, _ in results:
|
|
status_str = "[PASS]" if success else "[FAIL]"
|
|
print(f" {status_str:<8} | {elapsed:>6.2f}s | {title}")
|
|
|
|
print("-" * 75)
|
|
print(f"Total Execution Time: {total_elapsed:.2f}s")
|
|
|
|
if all_passed:
|
|
print("\nALL CASAN QUALITY GATES PASSED! Ready for PR merge or release.")
|
|
return 0
|
|
else:
|
|
print("\nQUALITY GATE FAILED! Please resolve the issues above before proceeding.")
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|