quality: enforce split test-module baseline
This commit is contained in:
parent
14c3ae7bf4
commit
2e247b6782
11
README.md
11
README.md
@ -115,9 +115,14 @@ Deployment gate script:
|
|||||||
|
|
||||||
Gate order:
|
Gate order:
|
||||||
1. docs contract checks
|
1. docs contract checks
|
||||||
2. naming + LOC hygiene checks
|
2. split test-module contract (`cmd/` + `internal/` cannot grow new in-tree `_test.go` files)
|
||||||
3. pedantic lint
|
3. naming + LOC hygiene checks
|
||||||
4. per-file coverage gate (95% minimum)
|
4. pedantic lint
|
||||||
|
5. per-file coverage gate (95% minimum)
|
||||||
|
|
||||||
|
Current migration rule:
|
||||||
|
- keep new tests in the top-level `testing/` module
|
||||||
|
- legacy in-tree `_test.go` files are temporarily grandfathered through `testing/hygiene/in_tree_test_allowlist.txt` until they are migrated safely
|
||||||
|
|
||||||
Installer behavior:
|
Installer behavior:
|
||||||
- `scripts/install.sh` runs the quality gate by default
|
- `scripts/install.sh` runs the quality gate by default
|
||||||
|
|||||||
@ -145,6 +145,9 @@ go test ./hygiene -run TestHygieneContracts/naming_contract -count=1
|
|||||||
|
|
||||||
echo "[quality] hygiene: LOC limits"
|
echo "[quality] hygiene: LOC limits"
|
||||||
go test ./hygiene -run TestHygieneContracts/loc_limit -count=1
|
go test ./hygiene -run TestHygieneContracts/loc_limit -count=1
|
||||||
|
|
||||||
|
echo "[quality] hygiene: split test-module contract"
|
||||||
|
go test ./hygiene -run TestHygieneContracts/split_module_contract -count=1
|
||||||
cd "${REPO_DIR}"
|
cd "${REPO_DIR}"
|
||||||
|
|
||||||
echo "[quality] lint"
|
echo "[quality] lint"
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package hygiene
|
package hygiene
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/parser"
|
"go/parser"
|
||||||
"go/token"
|
"go/token"
|
||||||
@ -71,6 +72,9 @@ func TestHygieneContracts(t *testing.T) {
|
|||||||
t.Run("loc_limit", func(t *testing.T) {
|
t.Run("loc_limit", func(t *testing.T) {
|
||||||
checkFileLOCLimits(t, files)
|
checkFileLOCLimits(t, files)
|
||||||
})
|
})
|
||||||
|
t.Run("split_module_contract", func(t *testing.T) {
|
||||||
|
checkSplitModuleContract(t, root, files)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkDocContracts runs one orchestration or CLI step.
|
// checkDocContracts runs one orchestration or CLI step.
|
||||||
@ -137,3 +141,108 @@ func checkFileLOCLimits(t *testing.T, files []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkSplitModuleContract runs one orchestration or CLI step.
|
||||||
|
// Signature: checkSplitModuleContract(t *testing.T, root string, files []string).
|
||||||
|
// Why: The long-term test layout requirement is top-level `testing/`; this
|
||||||
|
// guard freezes the remaining in-tree baseline so new root-module tests cannot
|
||||||
|
// slip in while the migration continues incrementally and safely.
|
||||||
|
func checkSplitModuleContract(t *testing.T, root string, files []string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
allowlistPath := filepath.Join(root, "testing", "hygiene", "in_tree_test_allowlist.txt")
|
||||||
|
allowed := loadInTreeTestAllowlist(t, allowlistPath)
|
||||||
|
actual := make([]string, 0, len(files))
|
||||||
|
for _, file := range files {
|
||||||
|
if !strings.HasSuffix(file, "_test.go") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
actual = append(actual, mustRepoRelativePath(t, root, file))
|
||||||
|
}
|
||||||
|
sort.Strings(actual)
|
||||||
|
|
||||||
|
allowedSet := make(map[string]struct{}, len(allowed))
|
||||||
|
for _, file := range allowed {
|
||||||
|
allowedSet[file] = struct{}{}
|
||||||
|
}
|
||||||
|
actualSet := make(map[string]struct{}, len(actual))
|
||||||
|
for _, file := range actual {
|
||||||
|
actualSet[file] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
unexpected := make([]string, 0)
|
||||||
|
for _, file := range actual {
|
||||||
|
if _, ok := allowedSet[file]; !ok {
|
||||||
|
unexpected = append(unexpected, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
missing := make([]string, 0)
|
||||||
|
for _, file := range allowed {
|
||||||
|
if _, ok := actualSet[file]; !ok {
|
||||||
|
missing = append(missing, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(unexpected) == 0 && len(missing) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var report strings.Builder
|
||||||
|
report.WriteString("split-module contract mismatch\n")
|
||||||
|
if len(unexpected) > 0 {
|
||||||
|
report.WriteString("unexpected in-tree tests (move them under testing/ or explicitly rebaseline if this is an intentional migration checkpoint):\n")
|
||||||
|
for _, file := range unexpected {
|
||||||
|
report.WriteString("- " + file + "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(missing) > 0 {
|
||||||
|
report.WriteString("stale allowlist entries (remove them from testing/hygiene/in_tree_test_allowlist.txt after a migration):\n")
|
||||||
|
for _, file := range missing {
|
||||||
|
report.WriteString("- " + file + "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatal(report.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadInTreeTestAllowlist runs one orchestration or CLI step.
|
||||||
|
// Signature: loadInTreeTestAllowlist(t *testing.T, path string) []string.
|
||||||
|
// Why: The split-module baseline needs one canonical source of truth so the
|
||||||
|
// gate can distinguish legacy in-tree tests from accidental new ones.
|
||||||
|
func loadInTreeTestAllowlist(t *testing.T, path string) []string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("open in-tree test allowlist %s: %v", path, err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
entries := make([]string, 0, 64)
|
||||||
|
s := bufio.NewScanner(f)
|
||||||
|
for s.Scan() {
|
||||||
|
line := strings.TrimSpace(s.Text())
|
||||||
|
if line == "" || strings.HasPrefix(line, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
entries = append(entries, filepath.ToSlash(line))
|
||||||
|
}
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
t.Fatalf("scan in-tree test allowlist %s: %v", path, err)
|
||||||
|
}
|
||||||
|
sort.Strings(entries)
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
// mustRepoRelativePath runs one orchestration or CLI step.
|
||||||
|
// Signature: mustRepoRelativePath(t *testing.T, root string, path string) string.
|
||||||
|
// Why: Relative slash-normalized paths keep split-module hygiene output stable
|
||||||
|
// across machines and make allowlist diffs easy to review.
|
||||||
|
func mustRepoRelativePath(t *testing.T, root string, path string) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
rel, err := filepath.Rel(root, path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("make %s relative to %s: %v", path, root, err)
|
||||||
|
}
|
||||||
|
return filepath.ToSlash(rel)
|
||||||
|
}
|
||||||
|
|||||||
38
testing/hygiene/in_tree_test_allowlist.txt
Normal file
38
testing/hygiene/in_tree_test_allowlist.txt
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# Remaining root-module test baseline.
|
||||||
|
# New tests belong under the top-level testing/ module.
|
||||||
|
cmd/ananke/bootstrap_handoff_additional_test.go
|
||||||
|
cmd/ananke/bootstrap_handoff_test.go
|
||||||
|
cmd/ananke/builder_test.go
|
||||||
|
cmd/ananke/command_additional_test.go
|
||||||
|
cmd/ananke/command_handlers_injection_test.go
|
||||||
|
cmd/ananke/command_handlers_status_error_test.go
|
||||||
|
cmd/ananke/command_handlers_test.go
|
||||||
|
cmd/ananke/main_dispatch_additional_test.go
|
||||||
|
cmd/ananke/main_dispatch_test.go
|
||||||
|
cmd/ananke/power_safety_test.go
|
||||||
|
cmd/ananke/test_helpers_test.go
|
||||||
|
internal/cluster/orchestrator_inventory_test.go
|
||||||
|
internal/cluster/orchestrator_report_test.go
|
||||||
|
internal/cluster/orchestrator_test.go
|
||||||
|
internal/cluster/orchestrator_unit_additional_test.go
|
||||||
|
internal/cluster/orchestrator_vault_test.go
|
||||||
|
internal/config/config_test.go
|
||||||
|
internal/config/load_additional_test.go
|
||||||
|
internal/config/validate_matrix_test.go
|
||||||
|
internal/execx/runner_additional_test.go
|
||||||
|
internal/execx/runner_test.go
|
||||||
|
internal/metrics/exporter_additional_test.go
|
||||||
|
internal/metrics/exporter_test.go
|
||||||
|
internal/service/daemon_additional_test.go
|
||||||
|
internal/service/daemon_coverage_closeout_test.go
|
||||||
|
internal/service/daemon_quality_branches_test.go
|
||||||
|
internal/service/daemon_test.go
|
||||||
|
internal/sshutil/repair_test.go
|
||||||
|
internal/sshutil/sshutil_test.go
|
||||||
|
internal/state/heal_test.go
|
||||||
|
internal/state/intent_additional_test.go
|
||||||
|
internal/state/intent_test.go
|
||||||
|
internal/state/store_additional_test.go
|
||||||
|
internal/state/store_test.go
|
||||||
|
internal/ups/nut_additional_test.go
|
||||||
|
internal/ups/nut_test.go
|
||||||
Loading…
x
Reference in New Issue
Block a user