Files
glint/internal/linter/fuzz_test.go
T
k3nny d6afb148ca
ci / vet, staticcheck, test, build (push) Failing after 3m15s
feat(build): expand fuzz coverage to expression evaluator and linter; fix empty-key parser bug
Run fuzz tests found a real bug: a bare '?' YAML input (null mapping key)
caused ParseBytes to store p.Jobs[""] — fixed in internal/model/parser.go by
rejecting empty keys with an explicit error.

New fuzz targets added:
- FuzzEvalIf / FuzzExpandVarRefs (internal/cicontext) — exercises the
  hand-rolled recursive-descent rules:if: parser and variable expander
- FuzzLint (internal/linter) — drives the full Parse → Lint path against
  arbitrary YAML; triggers every type-assertion in the lint rules

task fuzz now runs all 5 targets sequentially (30 s each by default).
All targets clean: 90-120 s runs, 400k-3.5M executions, zero failures.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 23:58:47 +02:00

70 lines
2.8 KiB
Go

package linter
import (
"testing"
"git.k3nny.fr/glint/internal/model"
)
// FuzzLint ensures that the full Parse → Lint pipeline never panics on
// arbitrary YAML input. Lint rules type-assert model fields extensively;
// this fuzzer drives those assertions against malformed-but-parseable YAML.
// Run with: go test -fuzz=FuzzLint ./internal/linter/
func FuzzLint(f *testing.F) {
seeds := []string{
// Minimal valid pipeline
"stages: [build]\njob:\n stage: build\n script: echo ok\n",
// Manual / delayed / trigger / on_failure job types
"job:\n script: ok\n when: manual\n",
"job:\n script: ok\n when: delayed\n start_in: 5 minutes\n",
"job:\n trigger:\n project: group/repo\n",
"job:\n script: ok\n when: on_failure\n",
// needs: and dependencies:
"stages: [a,b]\na:\n stage: a\n script: ok\nb:\n stage: b\n needs: [a]\n script: ok\n",
"stages: [a,b]\na:\n stage: a\n script: ok\nb:\n stage: b\n dependencies: [a]\n script: ok\n",
// rules:
"job:\n script: ok\n rules:\n - if: '$CI_COMMIT_BRANCH == \"main\"'\n when: on_success\n",
// parallel matrix
"job:\n script: ok\n parallel:\n matrix:\n - ARCH: [amd64, arm64]\n",
// image as string / map
"job:\n script: ok\n image: golang:1.21\n",
"job:\n script: ok\n image:\n name: golang:1.21\n entrypoint: ['']\n",
// artifacts / cache with both string and map when:
"job:\n script: ok\n artifacts:\n when: on_success\n paths: [dist/]\n",
"job:\n script: ok\n cache:\n key: $CI_COMMIT_REF_SLUG\n paths: [vendor/]\n",
// environment / release / coverage
"job:\n script: ok\n environment:\n name: production\n url: https://example.com\n",
"job:\n script: ok\n release:\n tag_name: $CI_COMMIT_TAG\n description: Release\n",
"job:\n script: ok\n coverage: '/^TOTAL.*?(\\d+%)$/'\n",
// retry / timeout
"job:\n script: ok\n retry: 2\n",
"job:\n script: ok\n timeout: 2h30m\n",
// workflow
"workflow:\n rules:\n - if: '$CI_COMMIT_BRANCH'\n when: always\njob:\n script: ok\n",
// extends
".base:\n script: ok\nchild:\n extends: .base\n stage: test\n",
// id_tokens / secrets
"job:\n script: ok\n id_tokens:\n TOKEN:\n aud: https://example.com\n",
// services
"job:\n script: ok\n services:\n - name: postgres:14\n alias: db\n",
// pages job
"pages:\n script: make docs\n artifacts:\n paths: [public/]\n",
// inherit
"default:\n retry: 1\njob:\n script: ok\n inherit:\n default: false\n",
// allow_failure
"job:\n script: ok\n allow_failure:\n exit_codes: [1, 2]\n",
}
for _, s := range seeds {
f.Add([]byte(s))
}
f.Fuzz(func(t *testing.T, data []byte) {
p, err := model.ParseBytes(data)
if err != nil {
return
}
// Lint must never panic regardless of pipeline content.
_ = Lint(p, nil)
})
}