From 1b62d2032091e55f84793e887629145e6d856812 Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Fri, 17 Apr 2026 04:31:21 -0300 Subject: [PATCH] quality: strengthen metis hygiene contracts and platform metrics --- scripts/publish_test_metrics.py | 27 +++++++ testing/gate_test.go | 129 ++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) diff --git a/scripts/publish_test_metrics.py b/scripts/publish_test_metrics.py index 77e2b32..0effd37 100644 --- a/scripts/publish_test_metrics.py +++ b/scripts/publish_test_metrics.py @@ -5,6 +5,7 @@ from __future__ import annotations import json import os +from pathlib import Path import urllib.request import xml.etree.ElementTree as ET @@ -80,6 +81,25 @@ def _post_text(url: str, payload: str) -> None: raise RuntimeError(f"metrics push failed status={resp.status}") +def _count_source_files_over_limit(repo_root: Path, max_lines: int = 500) -> int: + """Count source files above the configured line budget.""" + + count = 0 + for rel_root in ("cmd", "pkg", "scripts", "testing"): + base = repo_root / rel_root + if not base.exists(): + continue + for path in base.rglob("*"): + if not path.is_file(): + continue + if path.suffix not in {".go", ".py", ".sh"}: + continue + lines = len(path.read_text(encoding="utf-8", errors="ignore").splitlines()) + if lines > max_lines: + count += 1 + return count + + def main() -> int: coverage_path = os.getenv("COVERAGE_JSON", "build/coverage.json") junit_path = os.getenv("JUNIT_XML", "build/junit.xml") @@ -92,6 +112,7 @@ def main() -> int: build_number = os.getenv("BUILD_NUMBER", "") commit = os.getenv("GIT_COMMIT", "") strict = os.getenv("METRICS_STRICT", "") == "1" + repo_root = Path(__file__).resolve().parents[1] if not os.path.exists(coverage_path): raise RuntimeError(f"missing coverage file {coverage_path}") @@ -101,6 +122,7 @@ def main() -> int: coverage = _load_coverage(coverage_path) totals = _load_junit(junit_path) test_exit_code = _load_exit_code(test_exit_code_path) + source_lines_over_500 = _count_source_files_over_limit(repo_root, max_lines=500) passed = max(totals["tests"] - totals["failures"] - totals["errors"] - totals["skipped"], 0) outcome = "ok" @@ -131,6 +153,10 @@ def main() -> int: f'metis_quality_gate_run_status{{suite="{suite}",status="failed"}} {1 if outcome == "failed" else 0}', "# TYPE metis_quality_gate_coverage_percent gauge", f'metis_quality_gate_coverage_percent{{suite="{suite}"}} {coverage:.3f}', + "# TYPE platform_quality_gate_workspace_line_coverage_percent gauge", + f'platform_quality_gate_workspace_line_coverage_percent{{suite="{suite}"}} {coverage:.3f}', + "# TYPE platform_quality_gate_source_lines_over_500_total gauge", + f'platform_quality_gate_source_lines_over_500_total{{suite="{suite}"}} {source_lines_over_500}', "# TYPE metis_quality_gate_build_info gauge", f"metis_quality_gate_build_info{_label_str(labels)} 1", ] @@ -154,6 +180,7 @@ def main() -> int: "tests_errors": totals["errors"], "tests_skipped": totals["skipped"], "coverage_percent": round(coverage, 3), + "source_lines_over_500": source_lines_over_500, "test_exit_code": test_exit_code, }, indent=2, diff --git a/testing/gate_test.go b/testing/gate_test.go index 274d07e..1a7f7ed 100644 --- a/testing/gate_test.go +++ b/testing/gate_test.go @@ -11,6 +11,7 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "sort" "strconv" "strings" @@ -151,6 +152,134 @@ func TestCoveragePolicy(t *testing.T) { } } +func TestStructureHygiene(t *testing.T) { + root := repoRoot(t) + genericNames := map[string]struct{}{ + "tmp": {}, + "temp": {}, + "foo": {}, + "bar": {}, + "baz": {}, + "misc": {}, + "new": {}, + "old": {}, + "final": {}, + "wip": {}, + } + maxDepth := 8 + + var violations []string + for _, relRoot := range []string{"cmd", "pkg", "scripts", "testing"} { + base := filepath.Join(root, relRoot) + walkSourceFiles(t, base, func(path string, info os.DirEntry) error { + if info.IsDir() { + return nil + } + switch filepath.Ext(path) { + case ".go", ".py", ".sh": + default: + return nil + } + relative := rel(root, path) + depth := len(strings.Split(relative, "/")) + if depth > maxDepth { + violations = append(violations, fmt.Sprintf("%s: depth %d > %d", relative, depth, maxDepth)) + } + + baseName := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)) + for _, tok := range strings.FieldsFunc(baseName, func(r rune) bool { + return r == '_' || r == '-' + }) { + if _, found := genericNames[strings.ToLower(tok)]; found { + violations = append(violations, fmt.Sprintf("%s: non-descriptive token %q", relative, tok)) + } + } + return nil + }) + } + if len(violations) > 0 { + sort.Strings(violations) + t.Fatalf("structure hygiene violations: %s", strings.Join(violations, ", ")) + } +} + +func TestCodeSmellContracts(t *testing.T) { + root := repoRoot(t) + patterns := []struct { + re *regexp.Regexp + scope []string + message string + }{ + { + re: regexp.MustCompile(`\bpanic\(`), + scope: []string{"cmd", "pkg"}, + message: "avoid panic in production Go code", + }, + { + re: regexp.MustCompile(`\blog\.Fatalf\(`), + scope: []string{"pkg"}, + message: "avoid log.Fatalf in pkg code", + }, + { + re: regexp.MustCompile(`\bfmt\.Print(f|ln)?\(`), + scope: []string{"pkg"}, + message: "avoid fmt.Print* in pkg code", + }, + } + + type hit struct { + path string + line int + text string + msg string + } + var hits []hit + for _, rule := range patterns { + for _, relRoot := range rule.scope { + base := filepath.Join(root, relRoot) + walkSourceFiles(t, base, func(path string, info os.DirEntry) error { + if info.IsDir() || filepath.Ext(path) != ".go" || strings.HasSuffix(path, "_test.go") { + return nil + } + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + scanner := bufio.NewScanner(file) + lineNo := 0 + for scanner.Scan() { + lineNo++ + line := scanner.Text() + if rule.re.MatchString(line) { + hits = append(hits, hit{ + path: rel(root, path), + line: lineNo, + text: strings.TrimSpace(line), + msg: rule.message, + }) + } + } + return scanner.Err() + }) + } + } + + if len(hits) > 0 { + sort.Slice(hits, func(i, j int) bool { + if hits[i].path == hits[j].path { + return hits[i].line < hits[j].line + } + return hits[i].path < hits[j].path + }) + lines := make([]string, 0, len(hits)) + for _, h := range hits { + lines = append(lines, fmt.Sprintf("%s:%d (%s) %s", h.path, h.line, h.msg, h.text)) + } + t.Fatalf("code smell violations: %s", strings.Join(lines, ", ")) + } +} + func countLines(path string) (int, error) { f, err := os.Open(path) if err != nil {