diff --git a/.changelog/unreleased/55-go-ci-selftest.md b/.changelog/unreleased/55-go-ci-selftest.md new file mode 100644 index 0000000..85d916f --- /dev/null +++ b/.changelog/unreleased/55-go-ci-selftest.md @@ -0,0 +1 @@ +- **Go lane self-test.** New `self-test-go.yml` exercises `go-ci.yml` end-to-end in CI — calling it via a local reusable ref against fixture modules under `test/fixtures/go/` to prove discovery, `go build`/`go vet`/`go test`, and `exclude-modules` (including a self-validating intentionally-broken excluded module). Closes the workflow-level test gap the Go lane shipped with. (#55) diff --git a/.github/workflows/self-test-go.yml b/.github/workflows/self-test-go.yml new file mode 100644 index 0000000..1706e8c --- /dev/null +++ b/.github/workflows/self-test-go.yml @@ -0,0 +1,45 @@ +name: Self-test (go lane) + +# Workflow-level proof that go-ci.yml actually executes in Actions — module +# discovery, `go build` / `go vet` / `go test`, and `exclude-modules` — by +# calling it through a LOCAL (./) reusable ref so the in-PR version is the one +# under test. self-test.yml covers only the classifier scripts (vitest); this +# is the missing execution proof for the reusable Go lane. See issue #55. + +on: + pull_request: + paths: + - ".github/workflows/go-ci.yml" + - ".github/workflows/self-test-go.yml" + - "test/fixtures/go/**" + push: + branches: [main] + paths: + - ".github/workflows/go-ci.yml" + - ".github/workflows/self-test-go.yml" + - "test/fixtures/go/**" + +concurrency: + group: self-test-go-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + go-lane: + # Calls the in-PR go-ci.yml. It discovers the fixtures under + # test/fixtures/go/ via `git ls-files`, then builds/vets/tests `alpha` and + # `beta` and MUST skip the intentionally-broken `excluded` module. A green + # run proves the lane's shell works AND that exclude-modules skips as + # documented (a broken module that wasn't skipped would fail the build). + uses: ./.github/workflows/go-ci.yml + with: + go-versions: '["stable"]' + # Fixtures are dependency-less, so they have no go.sum. actions/setup-go + # with cache:true + cache-dependency-path '**/go.sum' errors when that + # glob matches nothing — and caching isn't what this self-test proves + # (the lane's discovery/build/vet/test/exclude shell is). Disable it. + cache: false + exclude-modules: | + test/fixtures/go/excluded/ diff --git a/test/fixtures/go/README.md b/test/fixtures/go/README.md new file mode 100644 index 0000000..37614f6 --- /dev/null +++ b/test/fixtures/go/README.md @@ -0,0 +1,19 @@ +# Go lane self-test fixtures + +These modules exist only to exercise `.github/workflows/go-ci.yml` from +`.github/workflows/self-test-go.yml`. They are **not** shipped or imported by +anything; they are discovered by `go-ci.yml` via `git ls-files '**/go.mod'`. + +| Module | Purpose | +| --- | --- | +| `alpha/` | Valid, vet-clean module **with a passing test** — proves `go build`, `go vet`, and `go test` all run. | +| `beta/` | A second valid module — proves multi-module discovery (more than one tracked `go.mod` in one run). | +| `excluded/` | **Intentionally does not compile.** The self-test passes `exclude-modules: test/fixtures/go/excluded/`. If exclusion regresses, this module builds and the job fails — a self-validating proof that `exclude-modules` works. | + +**Go version:** each `go.mod` declares `go 1.21` — a modern baseline that +`actions/setup-go` with `go-version: stable` always satisfies (stable is the +latest release). The modules have no dependencies, so `go mod download` is a +no-op and no `go.sum` is needed. + +If you change `go-ci.yml`, the self-test re-runs automatically (its `paths` +filter watches `go-ci.yml`, this directory, and the self-test workflow itself). diff --git a/test/fixtures/go/alpha/alpha.go b/test/fixtures/go/alpha/alpha.go new file mode 100644 index 0000000..4f17574 --- /dev/null +++ b/test/fixtures/go/alpha/alpha.go @@ -0,0 +1,9 @@ +// Package alpha is a self-test fixture: a valid, vet-clean Go module that +// go-ci.yml should discover, build, vet, and test. Paired with alpha_test.go +// so the self-test proves `go test` actually runs (not just build/vet). +package alpha + +// Add returns the sum of two integers. +func Add(a, b int) int { + return a + b +} diff --git a/test/fixtures/go/alpha/alpha_test.go b/test/fixtures/go/alpha/alpha_test.go new file mode 100644 index 0000000..9d8fd6d --- /dev/null +++ b/test/fixtures/go/alpha/alpha_test.go @@ -0,0 +1,12 @@ +package alpha + +import "testing" + +// TestAdd gives the self-test a real test to execute, proving go-ci.yml's +// `go test` step runs and reports pass/fail (a module with no tests would +// exit 0 regardless). +func TestAdd(t *testing.T) { + if got := Add(2, 3); got != 5 { + t.Fatalf("Add(2, 3) = %d, want 5", got) + } +} diff --git a/test/fixtures/go/alpha/go.mod b/test/fixtures/go/alpha/go.mod new file mode 100644 index 0000000..a662187 --- /dev/null +++ b/test/fixtures/go/alpha/go.mod @@ -0,0 +1,3 @@ +module example.com/selftest/alpha + +go 1.21 diff --git a/test/fixtures/go/beta/beta.go b/test/fixtures/go/beta/beta.go new file mode 100644 index 0000000..b1899b2 --- /dev/null +++ b/test/fixtures/go/beta/beta.go @@ -0,0 +1,9 @@ +// Package beta is a second valid fixture module. Its only job is to prove +// go-ci.yml discovers and verifies MORE THAN ONE tracked go.mod in a single +// run (multi-module discovery via `git ls-files`). +package beta + +// Greeting returns a fixed greeting. +func Greeting() string { + return "beta" +} diff --git a/test/fixtures/go/beta/go.mod b/test/fixtures/go/beta/go.mod new file mode 100644 index 0000000..44516ec --- /dev/null +++ b/test/fixtures/go/beta/go.mod @@ -0,0 +1,3 @@ +module example.com/selftest/beta + +go 1.21 diff --git a/test/fixtures/go/excluded/broken.go b/test/fixtures/go/excluded/broken.go new file mode 100644 index 0000000..d54dbdc --- /dev/null +++ b/test/fixtures/go/excluded/broken.go @@ -0,0 +1,12 @@ +// Package excluded is INTENTIONALLY non-compiling. It exists solely to prove +// that go-ci.yml's `exclude-modules` input actually skips a module: the +// self-test passes `exclude-modules: test/fixtures/go/excluded/`, so this file +// must never be compiled. If exclusion ever regresses, the undefined reference +// below fails `go build`/`go vet` and the self-test job goes red — loudly — +// instead of silently passing. See test/fixtures/go/README.md. +package excluded + +// Broken references an undefined identifier on purpose. +func Broken() int { + return undefinedOnPurpose +} diff --git a/test/fixtures/go/excluded/go.mod b/test/fixtures/go/excluded/go.mod new file mode 100644 index 0000000..91d4548 --- /dev/null +++ b/test/fixtures/go/excluded/go.mod @@ -0,0 +1,3 @@ +module example.com/selftest/excluded + +go 1.21