◐ Off-By-One · answer catalog

go-goconst-test-literal-regression

1 answer(s)godocker

go-goconst-test-literal-regression

📦 Source in repository (JSON)

Answer

Root cause. Post-campaign commits put repeated string literals back into test files: /api/gap/016 (3 goconst-visible occurrences in routes/routes_test.go) and 0 2 * * * (7 in cron/cron_test.go). The diff-scoped gitreins guard only lints changed hunks, so the reintroduction slipped through; the canonical gate is a full-repo goconst run.

Gate (golangci-lint v2). --disable-all was removed in v2 (Error: unknown flag: --disable-all); the equivalent is --enable-only:

golangci-lint run --enable-only goconst --output.json.path goconst.json   # exit 1 = violations

Before fix: 2 issues: string "0 2 * * *" has 7 occurrences ... string "/api/gap/016" has 3 occurrences (exit 1).

Fix 1 — package-level Str* const in routes/constants_test.go:

package routes

// StrGAP016Route is the canonical route path for the GAP-016 endpoint.
const StrGAP016Route = "/api/gap/016"

routes/routes_test.go — replace all uses (call arg included):

func TestGAP016Route(t *testing.T) {
    req := httptest.NewRequest(http.MethodGet, StrGAP016Route, nil)
    if req.URL.Path != StrGAP016Route {
        t.Fatalf("unexpected path: got %q want %q", req.URL.Path, StrGAP016Route)
    }
    byPath := map[string]bool{StrGAP016Route: true}
    if !byPath[req.URL.Path] {
        t.Fatal("route not registered")
    }
    rr := httptest.NewRecorder()
    Handler().ServeHTTP(rr, req)
    if rr.Code != http.StatusNoContent {
        t.Fatalf("status = %d, want %d", rr.Code, http.StatusNoContent)
    }
}

Fix 2 — function-local test* const in cron/cron_test.go (used only by this test):

func TestDogfood003Schedule(t *testing.T) {
    const testDogfood003Schedule = "0 2 * * *"

    defs := []Schedule{
        {Job: "dogfood-003", Schedule: testDogfood003Schedule, Enabled: true},
        {Job: "dogfood-003-staging", Schedule: testDogfood003Schedule, Enabled: true},
        {Job: "dogfood-003-canary", Schedule: testDogfood003Schedule, Enabled: true},
    }
    got := EnabledSchedules(defs)
    want := []string{testDogfood003Schedule, testDogfood003Schedule, testDogfood003Schedule}
    if !reflect.DeepEqual(got, want) {
        t.Fatalf("schedules = %v, want %v", got, want)
    }

    // Backtick JSON body: interpolate with %q — never blind-replace inside it.
    body := fmt.Sprintf(`{"job":"dogfood-003","schedule":%q,"enabled":true}`, testDogfood003Schedule)
    var payload map[string]any
    if err := json.Unmarshal([]byte(body), &payload); err != nil {
        t.Fatalf("unmarshal: %v", err)
    }
    if payload["schedule"] != testDogfood003Schedule {
        t.Fatalf("payload schedule = %v", payload["schedule"])
    }
}

replace_all pitfalls (why you cannot blanket-replace): 1. Init cycle — a naive replace rewrites the const declaration itself: const StrGAP016Route = StrGAP016Route → build fails with initialization cycle: StrGAP016Route refers to itself. 2. Backtick JSON bodies — a substring replace rewrites body :={"job":"dogfood-003","schedule":"0 2 * * *","enabled":true}` into"schedule":"testDogfood003Schedule", injecting the identifier as a *value* and corrupting the payload/assertion. goconst never counts it anyway (it compares whole literal values; the schedule is a substring of the JSON document), so the body must be rebuilt viafmt.Sprintf(..., %q, const)` or left as data — never substituted textually.

Evidence & signatures

Reproduced in `/tmp/goconst-repro` (module `example.com/goconst-repro`, golangci-lint v2.12.2, Go 1.26.0).

| Check | Result |
|---|---|
| `golangci-lint run --disable-all` | `Error: unknown flag: --disable-all` (v2 removed it) |
| Gate BEFORE fix (`--enable-only goconst --output.json.path ...`) | exit 1 — `"0 2 * * *"` x7 @ `cron/cron_test.go:13`, `"/api/gap/016"` x3 @ `routes/routes_test.go:15` |
| Pitfall 1: `const StrGAP016Route = StrGAP016Route` | `go build` → `initialization cycle: StrGAP016Route refers to itself` (exit 1) |
| Pitfall 2: `sed 's/0 2 \* \* \*/testDogfood003Schedule/g'` on a copy | backtick body became `"schedule":"testDogfood003Schedule"` — identifier injected as JSON value |
| `go build ./...` (fixed) | OK |
| `go vet ./...` (fixed) | OK |
| `go test ./...` (fixed) | `ok example.com/goconst-repro/cron`, `ok example.com/goconst-repro/routes` |
| Gate AFTER fix | exit 0 — **0 issues** (verified via `output.json.path` JSON `Issues` count) |

Edge cases tested: goconst's `ignore-calls` default (call args like `httptest.NewRequest(..., "/api/gap/016", ...)` and `t.Fatalf` args are excluded from the occurrence count — the route path was reworked to 3 non-call occurrences so the gate actually catches it); raw-string (`backtick`) bodies are counted only when the *whole* literal value matches, so JSON substrings are invisible to goconst but still get corrupted by naive replaces; threshold is `min-occurrences: 3` with `min-len: 3`; final state leaves exactly one occurrence each in the const declarations and the production `HandleFunc` registration (legitimately below threshold). Tests: 6 verification steps above.
{"model": "deepseek-v4-flash", "problem_class": "go-goconst-test-literal-regression", "result": "passed", "tests": 6}
Generated from the verified corpus · MIT licensedBack to the catalog