Repository navigation
test: load every unit and envtest case from YAML files - #20
Conversation
Each case now lives in its own file under a testdata/ directory next to its package (testdata/<suite>/<case-name>.yaml). The Go test code keeps only the loaders, runners, fixtures and assertions. internal/testutil/cases walks a directory for *.yaml, decodes each file strictly with sigs.k8s.io/yaml (unknown fields fail), orders the files by relative path and fails on an empty directory. cases.Run starts one subtest per file; cases.Entries builds one Ginkgo entry per file. Covered: autoscaling (guards, helpers, custom provider, LLMScaler envtest tables), serving (LLMService envtest specs, predicate, repo, values), chart (version, repo) and helm (client, namespace guard). Case counts per table and spec names are unchanged; per-package coverage is unchanged. DEVELOPMENT.md explains how to add a case.
huskcass
left a comment
There was a problem hiding this comment.
Blocking: cases.LoadFiles accepts an empty, comment-only, null or multi-document YAML file without an error (see inline). A blank file dropped into four existing suites ran as a passing subtest, so the claim that strict decoding stops a broken case file from passing does not hold yet.
Verdict: request changes
— Review Golden
| return nil, fmt.Errorf("cases: read %s: %w", rel, err) | ||
| } | ||
| var tc T | ||
| if err := yaml.UnmarshalStrict(body, &tc); err != nil { |
There was a problem hiding this comment.
UnmarshalStrict only checks what is in the document. It does not check that a document exists, or that there is only one:
- An empty file, a comment-only file,
nullor a bare---decodes to the zeroTwitherr == nil. I addedzz-empty.yaml(0 bytes) totestdata/helpers/scale-guard,helpers/decision-misconfiguredandhelpers/stabilization, and a----only file tohelpers/compute-desired. All four ran as passing subtests:scaleGuard(0,0,0)==0;strings.Contains(err, "")is always true; there are no ops; and NaN-style "no metric" matches the zero expectation. a: 1\n---\nbogus: 2decodes only the first document. The second is dropped without any strict check.
Please have the loader reject a file with no document or a null document, and a file with more than one document. For example, decode with a yaml.NewYAMLOrJSONDecoder loop and require exactly one non-null object. A testdata/ fixture for each case in cases_test.go would keep it covered.
There was a problem hiding this comment.
Fixed in 6391612 (new commit on top of 03f5a70).
Blocking: loader
- A case file must hold exactly one non-null YAML mapping. Each of these now fails the load, and the error names the file:
- empty, comment-only,
null/~, or a lone---:no document/document is null - a non-mapping document:
document must be a mapping - more than one document:
N documents, want 1
- empty, comment-only,
cases_test.gocovers each case with fixtures written tot.TempDir(), including a multi-doc file whose second document has an unknown field.- I checked it by hand: dropping a 0-byte file into
helpers/scale-guard,---intohelpers/compute-desired, a comment-only file intohelpers/decision-misconfigured, and a two-doc file intohelpers/stabilizationmakes all four tests fail with those errors.
Lows
.ymlfiles fail the load with "rename it to .yaml". Only.yamlis a case. This is documented in the package doc and inDEVELOPMENT.md.- Ginkgo flags:
Entriesmoved tointernal/testutil/cases/ginkgocases. The corecasespackage no longer imports Ginkgo, sogo test ./internal/chart -args -ginkgo.vis an unknown flag again. - Key case: keys must match the json name exactly. After the strict decode,
keys.gowalks the generic tree against the target type. Self-decoding types such as Duration, Quantity and IntOrString are treated as leaves. - Durations: the docs now match behavior. Only a pointer duration may be omitted or
null; a non-pointernullfails, and there's a test for that. - Omitted fields:
- Decision-server cases need
status; the existing files now saystatus: 200. - Error cases need
errorContains. - Chart repo cases need
allowed(an empty list means allow all) ornilAllowlist: true. - An empty entry in any substring list (
contains,messageContains, events,notContains,errorNotContains) fails the case and names the field.
- Decision-server cases need
- Conditions:
messageandobservedGenerationare asserted when set. - Dead schema:
capture: generationandupdate.labelsare removed; no case file used them.
Checks
make test,go vetand golangci-lint are clean.make manifests generategives no diff.-racepasses, and-ginkgo.randomize-allpasses with several seeds.- There are still 235 case files, and spec and subtest names are unchanged.
- Coverage is unchanged for chart, autoscaling, serving and helm. The loader is at 87.7%, and
ginkgocasesis at 90.9%. - CI is green.
The case loader now requires exactly one non-null YAML mapping per file. An empty, comment-only, null or "---"-only file, and a file with more than one document, fail the load and name the file. A .yml file fails the load instead of being skipped. Keys must match the json name exactly; encoding/json alone matched them case-insensitively. Types that decode themselves (durations, quantities and the like) are checked as leaves. The Ginkgo entry helper moves to cases/ginkgocases so the core loader no longer pulls Ginkgo flags into the chart and helm test binaries. Runners no longer treat an omitted field as "anything passes": decision server cases need a status, error cases need errorContains, chart repo cases set allowed (or nilAllowlist) explicitly, and an empty entry in any substring list fails the case. Condition message and observedGeneration are asserted when set. The unused "capture: generation" step and update labels are removed, and the duration docs now match what the loader accepts.
huskcass
left a comment
There was a problem hiding this comment.
Verdict: approve
— Review Golden
Summary
Every unit and envtest case now lives in its own YAML file under a
testdata/directory next to its package. The Go test code keeps only the loaders, runners, fixtures and assertions. The in-Go case tables (guardHoldCases,workloadStateCases,targetGuardCases, the helper tables, the LLMScalerDescribeTableentries, and others) are gone. Kind e2e undertest/e2eis unchanged.internal/testutil/casesis the shared loader. It reads every*.yamlunder a directory and decodes each one strictly withsigs.k8s.io/yaml, so an unknown field, a duplicate key or a wrong type fails the run. Files run in relative-path order, and an empty directory is an error.cases.Runstarts onet.Runper file andcases.Entriesbuilds one GinkgoEntryper file.Layout
The file name is the case name and the directory is the suite or table. Each document has
name(optional; if set, it must match the file name),description(the subtest or spec text),inputsandexpected.Adding a case
Copy a nearby file in the right
testdata/<suite>/directory, changedescription,inputsandexpected, then runmake test. See "File-based tests" inDEVELOPMENT.md. Quote keys such asn,y,onandoff, because YAML 1.1 reads them as booleans.Case counts (before → after)
The Ginkgo spec texts are unchanged: 28 autoscaling specs and 30 serving specs, though they now run in file order. Cases that used to loop inside a single test function now show up as named subtests. Every subtest name from before is still there.
Coverage
make test,go vetand golangci-lint are clean.make manifests generateleaves no diff.