Skip to content

Commit 06bd270

Browse files
test(hack): correct the render sweep after an independent review
An independent review of the branch found the sweep's central claim did not survive measurement, and four factual claims wrong. Fixed here rather than argued with. The fixtures set only _namespace.host, and the platform emits seven keys there (packages/apps/tenant/templates/namespace.yaml). The value is not a boolean but the NAMESPACE providing the service, empty when the tenant has none, and charts gate on it with a bare `if`. So every fixture rendered the "no shared services" side of nine such branches and nothing rendered the other, which in packages/apps/kubernetes is 15 manifests and 40% of the chart. Both non-fresh fixtures now carry the on side, and that chart renders 30 documents under fresh against 45 under configured. That was the only axis with real reach: measured across all 21 swept charts, exactly one renders differently between fresh and configured. The three states are worth their three seconds, but the claim that they buy broad branch coverage was not true and no longer appears. Two of the seven tests could not fail for the property they named. The skip-reason test asserted every SKIP line contains parentheses, which the only emitter guarantees; it now renders each skipped chart and requires it to still fail, so a skip that stops being necessary shows up red instead of silently excluding a chart forever. The fixture-difference test compared the fixture FILES on three key names and passed while the sweep rendered 19 of 21 charts identically; it now compares renders. The type check only caught top-level bools, so an int, a null or a nested bool passed. It now walks _cluster and _namespace and rejects any non-string scalar, which is what "the platform quotes every scalar" actually implies. Four claims were wrong and are corrected: "46 of the 55 lookup call sites" counted prose in comments and cannot be a subset of the 40 real template calls (13 sit inside a conditional); "every value is a string" ignored that scheduling and branding are maps; the kubernetes-nodes skip named the instancetype lookup, which is its THIRD blocker, after two ordinary fixture problems; and "10/23 render without a fixture" is 11/23 with the release names this script actually uses. The invented scheduling map went too: it carried nodeSelector and tolerations, which no chart reads, so the branch it flipped had an empty body. It now carries globalAppTopologySpreadConstraints, which is what the platform emits. Also: the fixture-directory guard used a recursive find while the loop globs one level, so a fixture in a subdirectory passed the guard and then failed every chart on the literal glob; a dead rm after the inner loop; and $BATS_TMPDIR in a test, which cozytest.sh does not define and `set -u` turns into an aborted suite. Assisted-By: Claude <[email protected]> Signed-off-by: Myasnikov Daniil <[email protected]>
1 parent dfdb8af commit 06bd270

6 files changed

Lines changed: 123 additions & 43 deletions

File tree

‎hack/check-render-matrix.bats‎

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -56,19 +56,30 @@
5656
rm -rf "$tmp"
5757
}
5858

59-
# Every skipped chart has to carry a reason, so a skip cannot quietly become the
60-
# way a chart avoids the check.
61-
@test "every skip names a reason" {
62-
output=$(hack/check-render-matrix.sh)
63-
printf '%s\n' "$output" | grep '^SKIP' > /tmp/render-skips.$$ || true
64-
while read -r line; do
65-
[ -n "$line" ] || continue
66-
case "$line" in
67-
*"("*")"*) ;;
68-
*) echo "skip without a reason: $line" >&2; rm -f /tmp/render-skips.$$; exit 1 ;;
69-
esac
70-
done < /tmp/render-skips.$$
71-
rm -f /tmp/render-skips.$$
59+
# A skip that has stopped being necessary is worse than no skip: it silently
60+
# drops a chart from the sweep forever. This renders each skipped chart directly
61+
# and requires it to STILL fail, so a chart that becomes renderable shows up as a
62+
# red test rather than as permanent invisible exclusion.
63+
#
64+
# Replaces an earlier test that asserted every SKIP line contains parentheses.
65+
# The only line that emits one is `echo "SKIP $name ($reason)"` guarded on a
66+
# non-empty reason, so that assertion was unreachable by construction: it could
67+
# not fail for the property it named.
68+
@test "every skipped chart still genuinely fails to render" {
69+
# mktemp, not $BATS_TMPDIR: cozytest.sh is not real bats and defines none of
70+
# bats's variables, so under `set -u` that name aborts the suite.
71+
skips=$(mktemp)
72+
hack/check-render-matrix.sh | grep '^SKIP' | sed 's/^SKIP \([^ ]*\) .*/\1/' > "$skips" || true
73+
while read -r name; do
74+
[ -n "$name" ] || continue
75+
if helm template "$name-render-check" "packages/apps/$name" -n tenant-test \
76+
-f hack/testdata/render-fixtures/fresh.yaml >/dev/null 2>&1; then
77+
echo "chart '$name' is on the skip list but renders fine now — remove the skip" >&2
78+
rm -f "$skips"
79+
exit 1
80+
fi
81+
done < "$skips"
82+
rm -f "$skips"
7283
}
7384

7485
# The sweep must cover the charts that exist rather than a number frozen when
@@ -92,21 +103,31 @@
92103
# `eq $x "true"`, so a real YAML bool makes helm fail on incompatible types.
93104
@test "fixtures carry the platform's string-typed values" {
94105
for f in hack/testdata/render-fixtures/*.yaml; do
95-
python3 -c 'import sys,yaml; d=yaml.safe_load(open(sys.argv[1])) or {}; c=d.get("_cluster") or {}; bad=[k for k,v in c.items() if isinstance(v,bool)]; sys.exit(f"{sys.argv[1]}: _cluster keys are YAML booleans, the platform injects strings: {bad}") if bad else None; sys.exit(f"{sys.argv[1]}: no _cluster map") if not c else None' "$f"
106+
python3 -c 'import sys,yaml;d=yaml.safe_load(open(sys.argv[1])) or {};bad=[];[bad.append(f"{sec}.{k}") for sec in ("_cluster","_namespace") for k,v in (d.get(sec) or {}).items() if isinstance(v,(bool,int,float)) or v is None];sys.exit(f"{sys.argv[1]}: scalars must be strings, the platform quotes them: {bad}") if bad else None;sys.exit(f"{sys.argv[1]}: no _cluster map") if not (d.get("_cluster") or {}) else None' "$f"
96107
done
97108
}
98109

99-
# Each fixture is one cluster state and they must actually differ, or the sweep
100-
# renders the same shape three times and reports triple the coverage it has.
101-
@test "the fixtures represent different cluster states" {
102-
count=$(find hack/testdata/render-fixtures -name '*.yaml' | wc -l | tr -d ' ')
110+
# Each fixture is one cluster state, and what matters is that they produce
111+
# DIFFERENT RENDERS -- otherwise the sweep runs helm three times over the same
112+
# shape and reports triple the coverage it has.
113+
#
114+
# An earlier version compared the fixture FILES on three key names. That passed
115+
# while the sweep was in fact rendering 19 of 21 charts identically across all
116+
# three states, which is the exact condition it claimed to prevent. Comparing
117+
# output is the only form of this test that can fail for the right reason.
118+
@test "the fixtures produce different renders for at least one chart" {
119+
count=$(find hack/testdata/render-fixtures -maxdepth 1 -name '*.yaml' | wc -l | tr -d ' ')
103120
[ "$count" -ge 2 ]
104-
distinct=$(for f in hack/testdata/render-fixtures/*.yaml; do
105-
grep -E '^ (oidc-enabled|solver|wildcard-issue):' "$f" | tr -d ' ' | sort | tr '\n' ','
106-
echo
107-
done | sort -u | wc -l | tr -d ' ')
108-
if [ "$distinct" -lt "$count" ]; then
109-
echo "fixtures do not differ on the keys charts branch on ($distinct distinct of $count files)" >&2
121+
122+
# packages/apps/kubernetes is the chart that branches most on these values
123+
# (the _namespace.<service> flags gate ~15 manifests). If a future fixture
124+
# change stops moving even this one, the states have collapsed.
125+
a=$(helm template kubernetes-render-check packages/apps/kubernetes -n tenant-test \
126+
-f hack/testdata/render-fixtures/fresh.yaml 2>/dev/null | grep -c '^kind:')
127+
b=$(helm template kubernetes-render-check packages/apps/kubernetes -n tenant-test \
128+
-f hack/testdata/render-fixtures/configured.yaml 2>/dev/null | grep -c '^kind:')
129+
if [ "$a" = "$b" ]; then
130+
echo "fresh and configured render the same number of documents ($a) for packages/apps/kubernetes — the fixtures no longer represent different cluster states" >&2
110131
exit 1
111132
fi
112133
}

‎hack/check-render-matrix.sh‎

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
# What it does NOT cover, so the number is not read as more than it is.
1010
#
1111
# `lookup` returns nothing here. Without a cluster helm gives an empty result and
12-
# charts carry on -- which is why this works at all -- but 46 of the 55 lookup
13-
# call sites in packages/apps/*/templates/ sit inside a conditional on that
14-
# result, so this sweep renders one side of each of them and never the other.
12+
# charts carry on -- which is why this works at all -- but the sweep then renders
13+
# only the empty-result side. There are 40 template lookup calls under
14+
# packages/apps/*/templates/ (`grep -rho 'lookup "'`; a plain `grep -c 'lookup '`
15+
# returns 55 by counting prose in comments), and 13 of them sit inside a
16+
# conditional on the result, so those 13 branches have a side this never renders.
1517
# The unrendered side is where a whole class of real bugs has lived: the
1618
# lookup-preserve pattern that keeps an immutable PVC storageClass across
1719
# reconciles, and adopt-in-place migrations.
@@ -37,9 +39,11 @@ set -eu
3739

3840
# Cluster states to render each chart under. Each file reproduces the `_cluster`
3941
# map the platform injects, taken from packages/core/platform/templates/apps.yaml
40-
# rather than guessed -- including that every value is a STRING, because charts
41-
# compare them with `eq $oidcEnabled "true"` and a real bool makes helm fail on
42-
# incompatible types. hack/testdata/render-fixtures/README.md carries the detail.
42+
# rather than guessed. Every SCALAR there goes through `| quote`, so the booleans
43+
# arrive as the strings "true"/"false" -- tenant compares one with
44+
# `eq $oidcEnabled "true"` and a real bool makes helm fail on incompatible types.
45+
# `scheduling` and `branding` are the exceptions: the platform emits those as
46+
# maps. hack/testdata/render-fixtures/README.md carries the detail.
4347
#
4448
# More than one, and that is the difference between a check and a formality:
4549
# charts branch on these values, so a single state renders one side of each
@@ -67,9 +71,18 @@ extra_values() {
6771
# VirtualMachineClusterInstancetype and fail when it finds
6872
# nothing ("specified instanceType u1.medium not found in
6973
# cluster"). That check is the point of those templates in
70-
# production, so the charts are skipped here rather than weakened
71-
# for a test. Covering them needs a cluster holding the
72-
# instancetype CRs, which is a different lane, not a fixture.
74+
# production, so the charts are skipped rather than weakened.
75+
#
76+
# Worth being precise, because an earlier version of this comment
77+
# was not: for kubernetes-nodes that lookup is the THIRD blocker,
78+
# not the first. It also needs `--set cluster=<name>` and a
79+
# release name starting with `kubernetes-nodes-<cluster>-`, and
80+
# both of those are ordinary fixture work that extra_values() and
81+
# release_name() below exist for. Only after supplying them does
82+
# the chart reach the lookup. vm-instance likewise renders with
83+
# explicit resources.* values; the lookup is what stops it on
84+
# defaults. So "these need a cluster" is true of the last blocker
85+
# in each, not of the charts as a whole.
7386
#
7487
# Every skip costs coverage, so keep the list short and say why.
7588
skip_reason() {
@@ -98,7 +111,10 @@ if [ -z "$charts" ]; then
98111
exit 1
99112
fi
100113

101-
if [ -z "$(find "$FIXTURE_DIR" -name '*.yaml' 2>/dev/null)" ]; then
114+
# -maxdepth 1 so the guard tests exactly what the loop below globs; a recursive
115+
# find here would pass on a fixture in a subdirectory the glob never reaches, and
116+
# every chart would then fail on the literal `*.yaml`.
117+
if [ -z "$(find "$FIXTURE_DIR" -maxdepth 1 -name '*.yaml' 2>/dev/null)" ]; then
102118
echo "check-render-matrix: no fixtures under $FIXTURE_DIR — every chart would render under nothing and the sweep would report success having checked no state" >&2
103119
exit 1
104120
fi
@@ -135,7 +151,6 @@ for dir in $charts; do
135151
fi
136152

137153
rendered=$((rendered + 1))
138-
rm -f "$out" "$out.err"
139154
done
140155

141156
echo "check-render-matrix: $rendered rendered, $skipped skipped"

‎hack/testdata/render-fixtures/README.md‎

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,24 @@ Values the platform injects into every app chart at install time, reproduced
44
here so `hack/check-render-matrix.sh` can render a chart the way it is actually
55
rendered in a cluster.
66

7-
The authority is `packages/core/platform/templates/apps.yaml`, which builds the
8-
`_cluster` map into the `cozystack-values` Secret, plus
9-
`templates/bundles/system.yaml` for `oidc-enabled`. **Every value there goes
10-
through `| quote`, so every value here is a string** — including the booleans.
11-
That is not cosmetic: charts compare them with `eq $oidcEnabled "true"`, and a
12-
real bool makes helm fail with "incompatible types for comparison". A fixture
13-
that guesses the type tests a shape the platform never produces.
7+
The authority is `packages/core/platform/templates/apps.yaml`, which builds the `_cluster` map
8+
into the `cozystack-values` Secret, plus `templates/bundles/system.yaml` for
9+
`oidc-enabled`. `_namespace` comes from
10+
`packages/apps/tenant/templates/namespace.yaml`.
11+
12+
**Every scalar there goes through `| quote`, so every scalar here is a string** —
13+
including the booleans. That is not cosmetic: `tenant` compares one with
14+
`eq $oidcEnabled "true"`, and a real bool makes helm fail with "incompatible
15+
types for comparison". `scheduling` and `branding` are the exceptions: the
16+
platform emits those as maps.
17+
18+
`_namespace.<service>` is not a boolean at all, which is easy to get wrong: the
19+
value is the NAMESPACE providing that service, or an empty string when the tenant
20+
has none (`$etcd = $tenantName` in namespace.yaml). Charts test it with a bare
21+
`{{- if .Values._namespace.etcd }}`, so `""` is the off side and any name is the
22+
on side. Omitting the key entirely renders the off side, which is a state the
23+
platform does produce — but then nothing renders the on side, and in
24+
`packages/apps/kubernetes` that is 15 manifests.
1425

1526
Each file is one cluster state, and a chart is rendered under all of them. The
1627
states exist because charts branch on these values, so a single fixture renders
@@ -27,5 +38,12 @@ three. A key absent from every fixture is a branch nothing renders, and it fails
2738
silently by passing. To list what the charts read:
2839

2940
```sh
30-
grep -rhoE '_cluster "[a-z-]*"|_cluster\.[a-zA-Z-]*' packages/apps/*/templates/
41+
grep -rn '_cluster' packages/apps/*/templates/
3142
```
43+
44+
Grep for the bare word rather than a shape: the reads are written four different
45+
ways (`.Values._cluster.foo`, `index .Values._cluster "foo"`, `dig "foo" ...`,
46+
and `(.Values._cluster | default dict)`), so any pattern narrow enough to look
47+
tidy misses some of them. A key absent from every fixture is a branch nothing
48+
renders, and it fails silently by passing, so a false negative here is the
49+
expensive kind.

‎hack/testdata/render-fixtures/configured.yaml‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,14 @@ _cluster:
1717
scheduling:
1818
nodeSelector: {}
1919
tolerations: []
20+
# The value is the NAMESPACE providing the service, not a boolean: tenant's
21+
# namespace.yaml sets `$etcd = $tenantName`. A non-empty value is the on side of
22+
# the nine `{{- if .Values._namespace.<svc> }}` branches, and it renders 15
23+
# manifests in packages/apps/kubernetes that the off side does not.
2024
_namespace:
25+
etcd: "tenant-test"
26+
ingress: "tenant-test"
27+
gateway: "tenant-test"
28+
monitoring: "tenant-test"
29+
seaweedfs: "tenant-test"
2130
host: "example.org"

‎hack/testdata/render-fixtures/fresh.yaml‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,13 @@ _cluster:
1313
expose-ingress: ""
1414
expose-services: ""
1515
cluster-domain: "cluster.local"
16+
# Empty string means "this tenant has no such shared service"; the charts test it
17+
# with a bare `{{- if .Values._namespace.etcd }}`, and "" is false. This is the
18+
# off side of nine such branches.
1619
_namespace:
20+
etcd: ""
21+
ingress: ""
22+
gateway: ""
23+
monitoring: ""
24+
seaweedfs: ""
1725
host: "example.org"

‎hack/testdata/render-fixtures/wildcard.yaml‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,14 @@ _cluster:
1414
expose-ingress: "tenant-root"
1515
expose-services: "tenant-root"
1616
cluster-domain: "cluster.local"
17+
# The value is the NAMESPACE providing the service, not a boolean: tenant's
18+
# namespace.yaml sets `$etcd = $tenantName`. A non-empty value is the on side of
19+
# the nine `{{- if .Values._namespace.<svc> }}` branches, and it renders 15
20+
# manifests in packages/apps/kubernetes that the off side does not.
1721
_namespace:
22+
etcd: "tenant-test"
23+
ingress: "tenant-test"
24+
gateway: "tenant-test"
25+
monitoring: "tenant-test"
26+
seaweedfs: "tenant-test"
1827
host: "example.org"

0 commit comments

Comments
 (0)