#!/usr/bin/env bash # Does a repo generated from this template pass its own gate? # # WHY THIS EXISTS # # `task check` on the template is green and has been for months, while children # generated from it were red from their first commit (#1, #2). The template's # own gate cannot see the defect, for a reason that is invisible unless you go # looking: # # Go ignores directories whose names begin with "_" or "." when expanding # package patterns like ./... # # The entrypoint lives in cmd/__PROJECT_NAME__, which begins with an underscore. # So `go build ./...`, `go vet ./...`, `go test ./...` AND # `golangci-lint run ./...` all silently skip the one package every generated # repo is built around. Confirmed: # # $ go list ./... # __MODULE_PATH__/internal/agent # __MODULE_PATH__/pkg/litellm <- no cmd package # # After substitution the directory has a real name, becomes visible, and the # defects appear in the child. Template green, children red, by construction. # # WHY NOT JUST LINT THE DIRECTORY DIRECTLY # # Tried: `golangci-lint run ./cmd/__PROJECT_NAME__` does work (naming a path # directly bypasses the pattern exclusion). But it reports a gofumpt violation # on the import block that does NOT exist in a generated repo -- the formatter's # import grouping depends on the module path, and the template's is a # placeholder rather than a real path. Wiring that into `check` would turn the # template's gate red over an artifact of being a template. # # So this simulates the substitution and gates on the RESULT, which is the only # artifact anyone actually runs. Same discipline as template-go-web's # scripts/tests/is-template_test.sh: a test for transformation-sensitive code # has to undergo the transformation. # # Usage: ./scripts/tests/generated-child_test.sh set -uo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" pass=0; fail=0 ok() { printf ' ok %s\n' "$1"; pass=$((pass+1)); } bad() { printf ' FAIL %s\n %s\n' "$1" "${2:-}"; fail=$((fail+1)); } echo "generated-child tests" if ! command -v golangci-lint >/dev/null 2>&1; then echo " SKIP golangci-lint not installed — cannot gate the generated child" >&2 exit 0 fi tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' EXIT # Build the child exactly as a scaffolder does: the tracked tree, the entrypoint # directory renamed, every placeholder substituted. # # Tracked files from the WORKING TREE, not `git archive HEAD`. This runs inside # `task check`, so it has to gate what is about to be committed -- archiving # HEAD tests the previous commit and reports stale results against a fix sitting # unstaged, which it did on first use here. `git ls-files` keeps the file set # honest (no build output, no local scratch) while taking current content. ( cd "$ROOT" && git ls-files -z ) | while IFS= read -r -d '' f; do mkdir -p "$tmp/$(dirname "$f")" cp "$ROOT/$f" "$tmp/$f" done if [ ! -d "$tmp/cmd/__PROJECT_NAME__" ]; then bad "the template still has a placeholder entrypoint directory" \ "cmd/__PROJECT_NAME__ not found — has the layout changed?" echo; echo "$pass passed, $fail failed"; exit 1 fi mv "$tmp/cmd/__PROJECT_NAME__" "$tmp/cmd/childproj" find "$tmp" -type f \( -name '*.go' -o -name '*.mod' -o -name '*.yml' -o -name '*.md' \) \ -exec sed -i \ -e 's|__MODULE_PATH__|git.d-ma.be/mathias/childproj|g' \ -e 's|__PROJECT_NAME__|childproj|g' {} + # The whole point: the entrypoint must be visible to Go tooling in the child. # If this regresses, everything below it passes for the wrong reason. if ( cd "$tmp" && go list ./... 2>/dev/null ) | grep -q '/cmd/childproj$'; then ok "the generated child's entrypoint is visible to go list" else bad "the generated child's entrypoint is visible to go list" \ "cmd package missing from ./... — the child would be unchecked too" fi out="$(cd "$tmp" && go build ./... 2>&1)" [ -z "$out" ] && ok "the generated child builds" \ || bad "the generated child builds" "$(printf '%s' "$out" | head -3)" out="$(cd "$tmp" && go vet ./... 2>&1)" [ -z "$out" ] && ok "the generated child passes go vet" \ || bad "the generated child passes go vet" "$(printf '%s' "$out" | head -3)" # The one that caught #1/#2: errcheck on the deferred shutdown, in a package the # template's own `golangci-lint run ./...` never analysed. out="$(cd "$tmp" && golangci-lint run ./... 2>&1)" if printf '%s' "$out" | grep -qE '^[0-9]+ issues:|issues:$'; then bad "the generated child passes golangci-lint" "$(printf '%s' "$out" | grep -E '\.go:[0-9]+' | head -3)" else ok "the generated child passes golangci-lint" fi echo echo "$pass passed, $fail failed" [ "$fail" -eq 0 ]