From ad5196420eb0201e5ceacfe24a8d37f728c14e48 Mon Sep 17 00:00:00 2001 From: Micheal Wilkinson Date: Sat, 21 Mar 2026 11:14:40 +0000 Subject: [PATCH] chore(go): enforce package coverage gates --- .gitea/workflows/pr-validation.yml | 60 +++++++++++++++++++++++++++- .gitea/workflows/push-validation.yml | 60 +++++++++++++++++++++++++++- cmd/homesick/main.go | 7 +++- cmd/homesick/main_test.go | 53 ++++++++++++++++++++++++ 4 files changed, 177 insertions(+), 3 deletions(-) create mode 100644 cmd/homesick/main_test.go diff --git a/.gitea/workflows/pr-validation.yml b/.gitea/workflows/pr-validation.yml index 5141ed3..e49ccae 100644 --- a/.gitea/workflows/pr-validation.yml +++ b/.gitea/workflows/pr-validation.yml @@ -59,13 +59,68 @@ jobs: run: | set -euo pipefail - go test -covermode=atomic -coverprofile=coverage.out ./... + go test -covermode=atomic -coverprofile=coverage.out ./... | tee go-test-coverage.log go tool cover -html=coverage.out -o coverage.html total="$(go tool cover -func=coverage.out | awk '/^total:/ {sub(/%/, "", $3); print $3}')" printf '{\n "total": "%s"\n}\n' "$total" > coverage-summary.json printf 'total=%s\n' "$total" >> "$GITHUB_OUTPUT" + set +e + awk ' + /^ok[[:space:]]/ && /coverage: [0-9.]+% of statements/ { + pkg = $2 + cov = $0 + sub(/^.*coverage: /, "", cov) + sub(/% of statements.*$/, "", cov) + status = "target" + if (cov + 0 < 50) { + status = "fail" + fail = 1 + } else if (cov + 0 < 65) { + status = "high-risk" + } else if (cov + 0 < 80) { + status = "warning" + } + printf "%s %.1f %s\n", pkg, cov + 0, status + } + END { + if (fail) { + exit 2 + } + } + ' go-test-coverage.log > coverage-packages.raw + package_gate_status=$? + set -e + + { + echo '| Package | Coverage | Status |' + echo '| --- | ---: | --- |' + } > coverage-packages.md + + while read -r pkg cov status; do + case "$status" in + fail) + pretty='FAIL (<50%)' + ;; + high-risk) + pretty='High risk (50%-64.99%)' + ;; + warning) + pretty='Warning (65%-79.99%)' + ;; + *) + pretty='Target (>=80%)' + ;; + esac + printf '| `%s` | %.1f%% | %s |\n' "$pkg" "$cov" "$pretty" >> coverage-packages.md + done < coverage-packages.raw + + if [[ "$package_gate_status" -ne 0 ]]; then + echo "Per-package coverage gate failed: one or more packages are below 50%." >&2 + exit 1 + fi + - name: Run security analysis run: | set -euo pipefail @@ -172,6 +227,9 @@ jobs: echo '- Total: `${{ steps.coverage.outputs.total }}%`' echo '- Report: ${{ steps.upload.outputs.report_url }}' echo '- Badge: ${{ steps.upload.outputs.badge_url }}' + echo + echo '### Package Coverage' + cat coverage-packages.md } >> "$GITHUB_STEP_SUMMARY" - name: Run behavior suite diff --git a/.gitea/workflows/push-validation.yml b/.gitea/workflows/push-validation.yml index 4cb5a9c..23e27a7 100644 --- a/.gitea/workflows/push-validation.yml +++ b/.gitea/workflows/push-validation.yml @@ -51,13 +51,68 @@ jobs: run: | set -euo pipefail - go test -covermode=atomic -coverprofile=coverage.out ./... + go test -covermode=atomic -coverprofile=coverage.out ./... | tee go-test-coverage.log go tool cover -html=coverage.out -o coverage.html total="$(go tool cover -func=coverage.out | awk '/^total:/ {sub(/%/, "", $3); print $3}')" printf '{\n "total": "%s"\n}\n' "$total" > coverage-summary.json printf 'total=%s\n' "$total" >> "$GITHUB_OUTPUT" + set +e + awk ' + /^ok[[:space:]]/ && /coverage: [0-9.]+% of statements/ { + pkg = $2 + cov = $0 + sub(/^.*coverage: /, "", cov) + sub(/% of statements.*$/, "", cov) + status = "target" + if (cov + 0 < 50) { + status = "fail" + fail = 1 + } else if (cov + 0 < 65) { + status = "high-risk" + } else if (cov + 0 < 80) { + status = "warning" + } + printf "%s %.1f %s\n", pkg, cov + 0, status + } + END { + if (fail) { + exit 2 + } + } + ' go-test-coverage.log > coverage-packages.raw + package_gate_status=$? + set -e + + { + echo '| Package | Coverage | Status |' + echo '| --- | ---: | --- |' + } > coverage-packages.md + + while read -r pkg cov status; do + case "$status" in + fail) + pretty='FAIL (<50%)' + ;; + high-risk) + pretty='High risk (50%-64.99%)' + ;; + warning) + pretty='Warning (65%-79.99%)' + ;; + *) + pretty='Target (>=80%)' + ;; + esac + printf '| `%s` | %.1f%% | %s |\n' "$pkg" "$cov" "$pretty" >> coverage-packages.md + done < coverage-packages.raw + + if [[ "$package_gate_status" -ne 0 ]]; then + echo "Per-package coverage gate failed: one or more packages are below 50%." >&2 + exit 1 + fi + - name: Run security analysis run: | set -euo pipefail @@ -128,6 +183,9 @@ jobs: echo '- Total: `${{ steps.coverage.outputs.total }}%`' echo '- Report: ${{ steps.upload.outputs.report_url }}' echo '- Badge: ${{ steps.upload.outputs.badge_url }}' + echo + echo '### Package Coverage' + cat coverage-packages.md } >> "$GITHUB_STEP_SUMMARY" - name: Run behavior suite on main pushes diff --git a/cmd/homesick/main.go b/cmd/homesick/main.go index 3eea38d..5af55ed 100644 --- a/cmd/homesick/main.go +++ b/cmd/homesick/main.go @@ -1,12 +1,17 @@ package main import ( + "io" "os" "git.hrafn.xyz/aether/gosick/internal/homesick/cli" ) func main() { - exitCode := cli.Run(os.Args[1:], os.Stdout, os.Stderr) + exitCode := run(os.Args[1:], os.Stdout, os.Stderr) os.Exit(exitCode) } + +func run(args []string, stdout io.Writer, stderr io.Writer) int { + return cli.Run(args, stdout, stderr) +} diff --git a/cmd/homesick/main_test.go b/cmd/homesick/main_test.go new file mode 100644 index 0000000..c307e7e --- /dev/null +++ b/cmd/homesick/main_test.go @@ -0,0 +1,53 @@ +package main + +import ( + "bytes" + "os" + "os/exec" + "testing" + + "git.hrafn.xyz/aether/gosick/internal/homesick/version" +) + +func TestRunVersionCommand(t *testing.T) { + stdout := &bytes.Buffer{} + stderr := &bytes.Buffer{} + + exitCode := run([]string{"version"}, stdout, stderr) + if exitCode != 0 { + t.Fatalf("run(version) exit code = %d, want 0", exitCode) + } + if got := stdout.String(); got != version.String+"\n" { + t.Fatalf("stdout = %q, want %q", got, version.String+"\n") + } + if got := stderr.String(); got != "" { + t.Fatalf("stderr = %q, want empty", got) + } + +} + +func TestMainVersionCommand(t *testing.T) { + if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" { + os.Args = []string{"gosick", "version"} + main() + return + } + + cmd := exec.Command(os.Args[0], "-test.run=TestMainVersionCommand") + cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1") + + stdout := &bytes.Buffer{} + stderr := &bytes.Buffer{} + cmd.Stdout = stdout + cmd.Stderr = stderr + + if err := cmd.Run(); err != nil { + t.Fatalf("helper process failed: %v, stderr: %s", err, stderr.String()) + } + if got := stdout.String(); got != version.String+"\n" { + t.Fatalf("stdout = %q, want %q", got, version.String+"\n") + } + if got := stderr.String(); got != "" { + t.Fatalf("stderr = %q, want empty", got) + } +}