Skip to content

build(images): run the tests of the packages the patches touch - #4326

Merged
Aleksei Sviridkin (lexfrei) merged 12 commits into
mainfrom
build/go-test-patched-images
Oct 3, 2026
Merged

Aleksei Sviridkin (lexfrei) merged 12 commits into
mainfrom
build/go-test-patched-images

Conversation

@lexfrei

@lexfrei Aleksei Sviridkin (lexfrei) commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does

Twelve first-party images that are built from an upstream tree with patches on top now run go test on the packages those patches touch. The step sits in the builder stage, right after the patches are applied. Before this, ten of them ran go build only, and go build never compiles a _test.go, so a test carried inside a patch was proven once by hand and never again. The other two, opensearch-operator and kube-ovn, ran a go test line written by hand, which a patch reaching a new directory would slip past; the loop replaces both lines.

The package list is read from the patches themselves with git apply --numstat, so a new patch is covered without an edit to the Dockerfile. Each package is tested from its own directory, which finds nested modules: in objectstorage, internal/ is a module of its own and the root module cannot name ./internal/runtime. An empty list runs nothing. The list is read into a variable before the loop, because a for loop drops the exit status of a command substitution, and a patch that git cannot read would otherwise give an empty list and a green step.

A few images need more than the plain loop:

  • opensearch-operator skips controllers/. A patch touches it, but its suite starts an envtest control plane from etcd and kube-apiserver binaries the builder does not have. The loop runs with -race and replaces the hand-picked go test -race line, which also covered pkg/reconcilers/util, a package no patch touches.
  • kube-ovn drops its go test -run '^TestVMIMigrationSchedulingRetry$' line. The test it named is copied into pkg/controller, which a patch touches, so the loop runs it along with the rest of that package.
  • redis-operator also runs go vet -tags integration in each touched directory. Two of the files its patches edit are upstream integration tests behind that tag, and go test compiles none of them.
  • cluster-autoscaler and kubevirt-cloud-provider export GOOS and GOARCH for the target in the whole builder stage, so their tests unset both. A local make image builds amd64 and arm64 by default, and a test binary for the other architecture runs on the build host only under binfmt emulation. CI is not affected either way: the jobs that build package images each pin one PLATFORM that matches the runner's architecture.

The first run of the step on cluster-autoscaler found the carried fix-downscale.diff out of step with the upstream tests it leaves in place. The patch removes the maxSize refusal from SetSize, and one case of TestNodeGroupIncreaseSizeErrors still expected that refusal. The patch now drops that case and carries TestNodeGroupDeleteNodesAboveMaxSize instead. That test covers the downscale the patch is named for: DeleteNodes lowers a group through SetSize, so with the refusal in place a group standing above its max size could not shed a node. Against the unpatched SetSize the new test fails. The image's behaviour does not change, and scale-up stays bounded by the core scale-up orchestrator, which skips a node group already at its max size.

CI builds all twelve images, and the step reports ok or no test files for every package it tests. Locally, the redis-operator, opensearch-operator and kube-ovn builder stages pass. The cluster-autoscaler package was checked from the cluster-autoscaler-1.32.0 tag tarball, because the Dockerfile's full clone of kubernetes/autoscaler does not survive my network. As negative controls, an undefined call added to a redis integration test fails go vet -tags integration while go test still passes, and the new autoscaler test fails without the patch.

Left out on purpose, and the Dockerfile says so: multus. Its only touched package, pkg/multus, creates network namespaces in its tests, which a build container cannot do (mount --make-rshared /var/run/netns: operation not permitted). cilium (C, no compile step) and piraeus-server (Java) are out of scope.

Closes #4318

Screenshots

Not a UI change.

Downstream repositories

Walked the trigger map against the diff: Dockerfiles under packages/*/images/ and one patch file. No values key, schema, make target, hack/ file or documented workflow changes.

Release note

NONE

@github-actions github-actions Bot added area/uncategorized PR auto-labeler could not map title scope to a known area/*; please review size/L This PR changes 100-499 lines, ignoring generated files labels Sep 18, 2026
@coderabbitai

coderabbitai Bot commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: cozystack/cozystack/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 4f924e8e-699c-4853-995a-12eb2c8e27d8

📥 Commits

Reviewing files that changed from the base of the PR and between 533c7cd and 89a1fe5.

📒 Files selected for processing (1)
  • packages/system/kubeovn/images/kubeovn/Dockerfile

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 4 remain after this review.


📝 Walkthrough

Walkthrough

Dockerfiles now test Go packages touched by applied patches before building images. The Redis operator also runs integration-tagged vet checks. The cluster autoscaler patch removes the upper-bound replica-size check and adds coverage for node deletion above the annotated maximum. The Multus Dockerfile explains why its package tests are not run during the image build.

Changes

Patched package validation

Layer / File(s) Summary
Build-time package checks
packages/apps/kubernetes/images/cluster-autoscaler/Dockerfile, packages/apps/kubernetes/images/kubevirt-cloud-provider/Dockerfile, packages/system/*/images/*/Dockerfile
Dockerfiles derive package directories from patched Go files and run package tests before building. The Redis operator also runs go vet -tags integration .. The Multus Dockerfile documents why it does not run the patched package tests.

Replica size validation

Layer / File(s) Summary
Replica size behavior and tests
packages/apps/kubernetes/images/cluster-autoscaler/fix-downscale.diff
SetSize no longer rejects sizes above maxSize, while the minimum-size check remains. Tests cover node deletion when a MachineSet or MachineDeployment is already above its annotated maximum.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature

Merge Risk: ⚪ Minimal · up to 89a1f

The PR adds patch-scoped build checks, retains the KubeOVN migration-retry test, and allows scale-down of oversized autoscaler resources without bypassing the scale-up maximum. No actionable merge-blocking risk remains.

Architecture Summary

Architecture risk: 🔵 Low · up to 89a1f

The change affects 2 systems.

Changed systems: packages/system, packages/apps

Architecture concerns
No architecture-level concerns identified.

Review details

Systems and components

  • observed — packages/system (library) was modified; 11 changed files map to changed impact.
  • observed — packages/apps (library) was modified; 3 changed files map to changed impact.

Before / after behavior

  • observed — Modified behavior in packages/system/multus/images/multus-cni/Dockerfile: Added a comment before RUN ./hack/build-go.sh documenting that the patched pkg/multus tests create network namespaces (mount --make-rshared /var/run/netns) that the build container cannot perform, so the test suite is intentionally not run during the image build.
  • observed — Modified behavior in packages/apps/kubernetes/images/cluster-autoscaler/Dockerfile: Adds a build-stage test step that derives unique package directories from Go files listed by git apply --numstat and runs go test . in each. Failure to read the patch list or any test failure stops the build; tests run with GOOS and GOARCH removed from their environment.
  • observed — Modified behavior in packages/apps/kubernetes/images/cluster-autoscaler/fix-downscale.diff: The SetSize maximum-size rejection was removed, while the minimum-size rejection remains. The node-group test also removes the case expecting an error when the requested size exceeds maxSize.
  • observed — Modified behavior in packages/apps/kubernetes/images/cluster-autoscaler/fix-downscale.diff: Adds a test that creates a MachineSet or MachineDeployment with 4 replicas and a maximum-size annotation of 2, deletes one node, and checks that the scale resource has 3 replicas without an error.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR addresses #4318 by adding builder-stage go test runs for packages changed by applied patches in twelve first-party images. The Dockerfiles collect patch paths before the loop and fail on patc…
Out of Scope Changes check ✅ Passed The changes remain within #4318. Dockerfile changes implement patched-tree validation. The cluster-autoscaler patch adds coverage for deleting nodes when the group exceeds its configured maximum and a…
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: running tests for packages touched by applied patches across the images.
✨ Finishing Touches 💡 1
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

IvanHunters
IvanHunters previously approved these changes Sep 18, 2026

@IvanHunters IvanHunters left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict

LGTM with non-blocking notes

Build-stage only. Nothing here touches a chart, a values file, a schema, a CRD, an RBAC rule or an image digest, and the one patch edit lands on a _test.go hunk that make build never compiles, so none of it reaches a running cluster.

The mechanism holds up. I applied the modified fix-downscale.diff against a real cluster-autoscaler-1.32.0 checkout and git apply --check came back clean, which is worth saying because that image had not actually been built at the reviewed revision. git apply --numstat also works outside a git repository, which is what the keycloak-operator and objectstorage builder stages are, and on a rename it prints only the new path. So the enumeration is sound in every shape the repo's 21 patch files currently take.

Findings

  • [MINOR] packages/system/keycloak-operator/images/keycloak-operator/Dockerfile:29, the gate reports success when its own input command fails
  • [MINOR] packages/system/redis-operator/images/redis-operator/Dockerfile:29, the integration arm is hardcoded while the derived loop already visits that directory vacuously
  • [MINOR] packages/apps/kubernetes/images/cluster-autoscaler/fix-downscale.diff:22, the first real signal from the new runner is answered by deleting the assertion

Caveats

  • kilo, kubeovn and keycloak-operator are absent from the root Makefile build: recipe, so their new step only runs on a manual make image. Those are also two of the images whose patches carry the most test code, so the step pays least where it would pay most. Worth a follow-up to get them into a build lane.
  • Build packages/apps/kubernetes was still pending on ba53e15d. I checked that image statically instead: the patch applies clean at the pinned tag and the enumeration resolves to the right directory. I did not run the suite itself.
  • Checked and sound: a rename in a patch is safe, an empty list runs nothing, go test is never cross-compiled since no workflow sets PLATFORM, and no patch in the tree deletes a file, so the loop never meets a directory git apply has pruned.

# packages the patches touch. Each package is tested from its own directory:
# that finds the module it belongs to, and an empty list runs nothing.
COPY --from=source /patches /patches
RUN for dir in $(git apply --numstat /patches/*.diff | cut -f3 | grep '\.go$' | xargs -n1 dirname | sort -u); do \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MINOR] the gate reports success when its own input command fails

git apply --numstat feeds the loop through a command substitution, so its exit status is dropped. When it errors the list comes back empty, the loop body never runs, and the step passes having tested nothing. The git apply a few lines up fails closed on the same argument, so the two disagree about the same input; in this image and in objectstorage they do not even read the same copy of it, since the gate reads a second COPY --from=source /patches /patches in another stage.

Reproduced with the argument pointed at a path that does not resolve:

$ sh -c 'for dir in $(git apply --numstat /nonexistent/*.diff | cut -f3 | grep "\.go$" | xargs -n1 dirname | sort -u); do
  echo "RAN FOR: $dir"
done
echo "LOOP FINISHED, RUN EXIT WOULD BE 0"'
error: can't open patch '/nonexistent/*.diff': No such file or directory
LOOP FINISHED, RUN EXIT WOULD BE 0
shell exit=0

Capturing the list on its own line under set -e closes it, and I checked both halves behave:

RUN set -e; \
    files=$(git apply --numstat /patches/*.diff); \
    for dir in $(printf '%s\n' "$files" | cut -f3 | grep '\.go$' | xargs -n1 -r dirname | sort -u); do \
      (cd "$dir" && go test .); \
    done

set -e does abort on the assignment (exit=128, the loop is never reached), and an empty .go list still iterates zero times without tripping it. The -r on xargs is worth adding for its own reason: without it GNU xargs runs dirname once with no operands on an empty list, which is where the stray dirname: missing operand in the build log comes from.

The same line appears in the other 11 images.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a08ccca, same change as in the kamaji thread. It also removes the stray dirname: missing operand. I used || exit 1 on the assignment instead of set -e: it fails in the same cases and matches the || exit 1 already in the loop body.


# The integration tests the patches edit sit behind a build tag and need a
# cluster, so they are only type-checked here.
RUN go vet -tags integration ./test/integration/redisfailover

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MINOR] the integration arm is hardcoded while the derived loop already visits that directory vacuously

valkey-cli-auth.diff touches test/integration/redisfailover/database_engine_test.go, so the derived list already contains that directory. At v3.3.5 it holds creation_test.go and database_engine_test.go, both behind //go:build integration in package redisfailover_test, plus an untagged doc.go in package redisfailover. So the loop's go test . there compiles only doc.go, reports no test files and asserts nothing; this hardcoded go vet line is the real check.

Two consequences. Unlike everything else in this change the path is not derived from the patches, so a later patch touching build-tagged tests in any other directory is silently uncovered. And the directory's coverage rests on doc.go continuing to exist: if upstream drops it, the loop's own visit fails outright rather than passing empty.

$ mkdir -p /tmp/tagtest/pkg && cd /tmp/tagtest
$ printf 'module tagtest\n\ngo 1.21\n' > go.mod
$ printf '//go:build integration\n\npackage pkg\n\nimport "testing"\n\nfunc TestX(t *testing.T) {}\n' > pkg/only_tagged_test.go
$ cd pkg && go test .
# tagtest/pkg
package tagtest/pkg: build constraints exclude all Go files in /tmp/tagtest/pkg
FAIL	tagtest/pkg [setup failed]
FAIL
exit=1

Deriving the tagged arm from the patches the same way the untagged one is derived would close both.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 357f57a, details in the newer thread on this line. The tagged arm is now derived from the patches like the untagged one.

The doc.go dependency is still there. If upstream drops it, go test . in that directory fails the build with "build constraints exclude all Go files". That failure is loud, so I left it as is.

initial: 3,
errorMsg: "size increase must be positive",
- }, {
- description: "errors because initial+delta > maxSize",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MINOR] the first real signal from the new runner is answered by deleting the assertion

This hunk drops the errors because initial+delta > maxSize case from TestNodeGroupIncreaseSizeErrors, because the carried patch's other hunk removes the case nreplicas > r.maxSize refusal from SetSize and the test caught the mismatch. The image's behaviour does not change, and the commit message says so plainly, so this is not a regression and it is not why the note is here.

It is here because of what the assertion was holding. At cluster-autoscaler-1.32.0, IncreaseSize in clusterapi_nodegroup.go checks only that the delta is positive and then delegates:

func (ng *nodegroup) IncreaseSize(delta int) error {
	if delta <= 0 {
		return fmt.Errorf("size increase must be positive")
	}
	size, err := ng.scalableResource.Replicas()
	if err != nil {
		return err
	}
	return ng.scalableResource.SetSize(size + delta)
}

So the SetSize guard was the only enforcement of maxSize on this path, and that deleted case was its only assertion across the package's test files. The patch is named fix-downscale, yet the hunk it carries removes the size-increase refusal and leaves the size-decrease one intact, which reads more like the wrong case was deleted in January 2025 than like a decision. That predates this PR by twenty months and is not this PR's to fix.

What is this PR's: the change whose whole purpose is to stop carried patches from drifting away from what upstream asserts resolves its own first finding by removing the assertion, inside a build(...) commit, without a line on whether the guard removal was intended. Suggest keeping the deletion so the build goes green, and opening a separate issue against fix-downscale.diff so the question does not disappear with the test that raised it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the wrong case was deleted. DeleteNodes lowers a group through SetSize(replicas - 1), so with the old nreplicas > r.maxSize check a group already above its max size could not shed a node at all. Every step was refused as an increase, and that is the downscale the patch is named for.

On the increase side, the core scale-up orchestrator skips a group that is already at MaxSize() before it calls IncreaseSize (the currentTargetSize >= nodeGroup.MaxSize() check in core/scaleup/orchestrator/orchestrator.go). So the provider check was a second line of defence, not the only one. 70667a8 adds a test for the downscale path, so the patched behaviour now has an assertion, and I don't think a separate issue is needed.

@IvanHunters IvanHunters left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict

NOT LGTM

The new generic test loop breaks the opensearch-operator image build on this head, so the package can no longer be built.

Findings

  • [MAJOR] packages/system/opensearch-operator/images/opensearch-operator/Dockerfile:84, generic go test . loop drags the envtest controller suite into the build and fails it
  • [MINOR] packages/system/kamaji/images/kamaji/Dockerfile:18, for dir in $(git apply --numstat ...) swallows the substitution's exit status (same pattern in every image)
  • [MINOR] packages/system/redis-operator/images/redis-operator/Dockerfile:29, the integration arm is hardcoded while the loop already visits that directory vacuously
  • [MINOR] packages/apps/kubernetes/images/cluster-autoscaler/fix-downscale.diff:22, the first real failure from the new runner is answered by deleting the assertion

Caveats

  • Dockerfile-only diff: no chart, CRD, or values change, so upgrade and config-matrix paths are not exercised by this PR.

# go build never compiles a _test.go, so nothing else runs the tests of the
# packages the patches touch. Each package is tested from its own directory:
# that finds the module it belongs to, and an empty list runs nothing.
RUN for dir in $(git apply --numstat /patches/*.diff | cut -f3 | grep '\.go$' | xargs -n1 dirname | sort -u); do \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MAJOR] generic go test . loop drags the envtest controller suite into the build and fails it

The loop runs go test . in every directory the patch touches. The opensearch patch edits controllers/opensearchController.go, so the loop enters opensearch-operator/controllers and compiles its pre-existing suite_test.go, an envtest BeforeSuite that execs /usr/local/kubebuilder/bin/etcd. That binary is absent in the builder, TestAPIs fails, and the RUN exits 1. All 11 other image builds on this head pass; only this one is red, for exactly this step. The curated line just below (go test -race ./opensearch-gateway/... ./pkg/reconcilers/...) already runs the patch's real unit tests, and its comment says both trees run without a cluster. The generic loop is redundant here and additionally pulls in the one tree that needs a cluster. Drop the loop for this image, or exclude cluster-bound suites from it.

$ gh api repos/cozystack/cozystack/actions/jobs/109706697882/logs
unable to start control plane itself: failed to start the controlplane. retried 5 times: fork/exec /usr/local/kubebuilder/bin/etcd: no such file or directory
--- FAIL: TestAPIs (0.01s)
FAIL	github.com/Opster/opensearch-k8s-operator/opensearch-operator/controllers	0.023s
#14 ERROR: process for the added test loop did not complete successfully: exit code: 1

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0979736. After the stall-tracing patch landed on main, the loop started entering controllers/, and that suite starts an envtest control plane the builder has no binaries for. The step now skips that one directory.

I also moved the -race flag into the loop and dropped the hand-picked go test -race line, so the same packages are not tested twice and a patch in a new directory is still picked up. Locally the builder stage builds and reports ok for opensearch-gateway/services and pkg/reconcilers.

# go build never compiles a _test.go, so nothing else runs the tests of the
# packages the patches touch. Each package is tested from its own directory:
# that finds the module it belongs to, and an empty list runs nothing.
RUN for dir in $(git apply --numstat /patches/*.diff | cut -f3 | grep '\.go$' | xargs -n1 dirname | sort -u); do \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MINOR] for dir in $(git apply --numstat ...) swallows the substitution's exit status (same pattern in every image)

If the patch glob is missing or git apply --numstat errors, the substitution yields an empty list, the loop body never runs, and the RUN still exits 0. A runner that silently runs zero tests reads as green, which defeats the point of the change. Capture the list first and fail when the apply itself errored or the list is empty when a test was expected.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in all 12 images, for kamaji in 509ce5d. The list goes into a variable with || exit 1 before the loop, and xargs -r keeps dirname from running with no operand.

Checked under dash in the golang builder image: a patch path that does not resolve exits 1, a patch with no Go file exits 0 and runs nothing, and a normal patch runs the test. I didn't add a "list must not be empty" check. Once git has read the patches, the list depends only on what they contain, so an empty list just means no Go file was touched.


# The integration tests the patches edit sit behind a build tag and need a
# cluster, so they are only type-checked here.
RUN go vet -tags integration ./test/integration/redisfailover

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MINOR] the integration arm is hardcoded while the loop already visits that directory vacuously

The derived loop runs go test . in test/integration/redisfailover, where the tests sit behind a build tag, so it compiles and asserts nothing there; the separate go vet -tags integration ./test/integration/redisfailover then covers the same directory by hand. Keep one path, not both.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 357f57a. The loop now runs go vet -tags integration . next to go test . in every directory it visits, and the hand-written go vet line is gone. All 11 touched directories vet clean with the tag.

To check it, I added an undefined call to database_engine_test.go. go vet -tags integration fails on it, while go test . still reports no test files. The tag name is still written in the Dockerfile, so a test behind some other tag would need an edit.

initial: 3,
errorMsg: "size increase must be positive",
- }, {
- description: "errors because initial+delta > maxSize",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MINOR] the first real failure from the new runner is answered by deleting the assertion

The new hunk removes the errors because initial+delta > maxSize case rather than updating it to the patched scale-up behavior, so the exact path the production patch changes loses its only assertion.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 70667a8. The patch now also carries TestNodeGroupDeleteNodesAboveMaxSize, which deletes a node from a group with 4 replicas and max size 2 and expects 3.

Against the unpatched SetSize the test fails with size increase too large - desired:3 max:2. With the patch the touched package passes. I ran both from the cluster-autoscaler-1.32.0 tag tarball, and the CI build of the image passes the step too. The deleted case still has to go, because it asserts the exact refusal the patch removes.

@lexfrei
Aleksei Sviridkin (lexfrei) force-pushed the build/go-test-patched-images branch 2 times, most recently from 6fb9ad4 to 533c7cd Compare October 2, 2026 08:03
@lexfrei

Aleksei Sviridkin (lexfrei) commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor Author

The branch has been rebased since my replies of 2026-10-01, so the commit hashes in them no longer appear in this PR's history. The fixes they describe now live in 293a780 (opensearch-operator), e2757ba (redis-operator), 1ffc858 (keycloak-operator), 59c3174 (kamaji) and b2a5f12 (kubernetes, fix-downscale.diff), unchanged. Two more changes since your last review: 070d235 drops the kube-ovn go test -run '^TestVMIMigrationSchedulingRetry$' line, because the loop already runs that test with the rest of pkg/controller, and the opensearch-operator and kube-ovn commit messages no longer claim those images ran go build only.

… touch

The image build ran go test on two package trees listed by hand, and go
build never compiles a _test.go. A patch that reaches a directory
nobody listed brings tests that are proven once, by hand, when the
patch is cut, and nothing notices them failing after the next version
bump.

Run go test in the builder stage on the packages the patches touch. The
list is read from the patches themselves, so a new patch is covered
without an edit to the Dockerfile. It replaces the hand-picked go test
line, keeping its -race flag: two lines testing the same packages is
one too many. The hand-picked line also covered pkg/reconcilers/util,
which no patch touches.

controllers/ is left out. The stall-tracing patch touches it, and its
suite starts an envtest control plane from etcd and kube-apiserver
binaries the builder does not have, so testing it fails the build.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
The image build ran go build only, and go build never compiles a
_test.go, so nothing ran the tests of the code the patches change. Run
go test in the builder stage on the packages the patches touch. The list
is read from the patches themselves, so a new patch is covered without
an edit to the Dockerfile.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
The image build ran go build only, and go build never compiles a
_test.go, so nothing ran the tests of the code the patches change. Run
go test in the builder stage on the packages the patches touch. The list
is read from the patches themselves, so a new patch is covered without
an edit to the Dockerfile.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
The image build ran go build only, and go build never compiles a
_test.go, so nothing ran the tests of the code the patches change. Run
go test in the builder stage on the packages the patches touch. The list
is read from the patches themselves, so a new patch is covered without
an edit to the Dockerfile.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
The image build ran go build only, and go build never compiles a
_test.go, so nothing ran the tests of the code the patches change. Run
go test in the builder stage on the packages the patches touch. The list
is read from the patches themselves, so a new patch is covered without
an edit to the Dockerfile.

Two of the files the patches edit are upstream integration tests behind
the integration build tag, which go test compiles none of, so each
directory is also vetted with that tag. Doing it per derived directory
rather than for the one directory those tests live in today keeps a
later patch to a tagged test elsewhere covered.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
…ouch

The image build ran go build only, and go build never compiles a
_test.go, so nothing ran the tests of the code the patches change. Run
go test in the builder stage on the packages the patches touch. The list
is read from the patches themselves, so a new patch is covered without
an edit to the Dockerfile.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
…atches touch

The image build ran go build only, and go build never compiles a
_test.go, so nothing ran the tests of the code the patches change. Run
go test in the builder stage on the packages the patches touch. The list
is read from the patches themselves, so a new patch is covered without
an edit to the Dockerfile.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
The image build ran go build only, and go build never compiles a
_test.go, so nothing ran the tests of the code the patches change. Run
go test in the builder stage on the packages the patches touch. The list
is read from the patches themselves, so a new patch is covered without
an edit to the Dockerfile.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
The image build ran one test by name, the one shipped next to the
migration patch, and go build never compiles a _test.go, so the rest of
the tests in the packages the patches change never ran.

Run go test in the builder stage on the packages the patches touch. The
list is read from the patches themselves, so a new patch is covered
without an edit to the Dockerfile. It replaces the hand-picked go test
line: the shipped test lives in pkg/controller, which a patch touches,
so the loop runs it with the rest of that package.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
The cluster-autoscaler and kubevirt-cloud-provider image builds ran go
build only, and go build never compiles a _test.go, so nothing ran the
tests of the code the patches change. Run go test in the builder stage
on the packages the patches touch. The list is read from the patches
themselves, so a new patch is covered without an edit to the Dockerfile.

Both builder stages export GOOS and GOARCH for the target, so the tests
unset them: a local make image builds amd64 and arm64 by default, and a
test binary for the other architecture runs on the build host only
under binfmt emulation.

The first run of that step on cluster-autoscaler found the carried
downscale patch out of step with the upstream tests it leaves in place:
SetSize no longer refuses a size above maxSize, while one case of
TestNodeGroupIncreaseSizeErrors still expected that refusal. The patch
now drops that case, and carries a test for the downscale it is named
for instead. DeleteNodes lowers a group one node at a time through
SetSize, so with the refusal in place a group standing above its max
size could not shed a node at all. The new test deletes a node from
such a group and expects the replica count to drop; against the
unpatched SetSize it fails with the refusal the patch removes. Scale-up
past maxSize stays bounded by the core scale-up orchestrator, which
skips a node group already at MaxSize.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
…tches touch

The image build ran go build only, and go build never compiles a
_test.go, so nothing ran the tests of the code the patches change. Run
go test in the builder stage on the packages the patches touch. The list
is read from the patches themselves, so a new patch is covered without
an edit to the Dockerfile.

Each package is tested from its own directory rather than as a ./...
path of the root module, because internal/ is a module of its own and
the root module cannot name ./internal/runtime.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
…ackage

The other patched Go images now test the packages their patches touch
in the builder stage. The one package the multus patch touches,
pkg/multus, creates network namespaces in its tests, which a build
container is not allowed to do, so that image keeps building without a
test step. The Dockerfile records the reason, so the absence reads as a
decision and not as an omission.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>

@IvanHunters IvanHunters left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict

LGTM

This round is build-stage only: every edit lands inside an image Dockerfile, plus a _test.go added to the cluster-autoscaler patch. Nothing touches a chart, values file, schema, CRD, RBAC rule or image digest. The added step runs the unit tests of the Go packages each image's patch touches, at build time, so a patch that breaks a test file or its assertions now fails the image build instead of shipping silently.

I checked the core shell semantics against a real git/go rather than by reading. git apply --numstat on an already-applied patch lists the files without touching the tree (exit 0), and works outside a git work tree too, which the keycloak-operator and objectstorage stages rely on. files=$(...) || exit 1 captures a git-apply error (rc 128). The cut -f3 | grep '\.go$' | xargs -r -n1 dirname | sort -u pipeline yields the right package dirs, and an empty list runs nothing and exits 0 by design. go test . without a build tag skips an integration-tagged file while go vet -tags integration . type-checks it and fails on an undefined symbol, so the redis-operator change is not vacuous.

GOOS/GOARCH handling is correct per stage: only cluster-autoscaler and kubevirt-cloud-provider set them stage-wide, and both drop them with env -u for the test run; every other image sets them inline on the go build line only, so its test runs on the native builder. All twelve touched image builds, including opensearch-operator, pass on this head.

The branch advanced from the revision I first read to the current head while I was reviewing (a rebase onto newer main). I re-checked the content: of this PR's own files only packages/system/kubeovn/images/kubeovn/Dockerfile changed, dropping the explicit go test ./pkg/controller -run ... line in favour of the generic loop. That is correct: the carried patch fix-vmim-target-pod-retry.diff touches pkg/controller/kubevirt.go, so the loop derives pkg/controller and runs the copied kubevirt_migration_test.go there, and pkg/controller is not envtest so a plain go test . runs it. Everything else is unchanged.

Non-blocking notes

  • [NIT] packages/system/opensearch-operator/images/opensearch-operator/Dockerfile:84 — the envtest suite is skipped with grep -vx 'opensearch-operator/controllers', which is exactly right for the current patches (they touch controllers, opensearch-gateway/responses, opensearch-gateway/services and pkg/reconcilers, and only controllers starts a control plane). Because it is an exact match, a future patch touching a sub-package of controllers, or another envtest-bound package, would not be excluded and would break this build. Current CI is green.

  • [MINOR] packages/system/multus/images/multus-cni/Dockerfile:306 — this PR's only multus change is the comment: pkg/multus tests create network namespaces the builder cannot, so the image does not run them (true: 55 of 56 specs fail on mount --make-rshared /var/run/netns). Separately, the pre-existing carried patch fix-del-without-cache.diff rewrites CmdDel to return nil when the cache file is missing, while upstream v4.3.0 still ships three Delete pod without cache specs asserting DEL reaches both delegates; a focused privileged run shows them red (0 passed, 3 failed). That mismatch is pre-existing and this PR does not introduce it, but if the netns limitation is ever lifted the suite will be red for that reason, not only un-runnable. Worth either updating those three specs in the patch or noting the divergence in the comment.

auto-merge was automatically disabled October 2, 2026 22:41

Pull request was closed

@lexfrei
Aleksei Sviridkin (lexfrei) merged commit 091b854 into main Oct 3, 2026
59 of 60 checks passed
@lexfrei
Aleksei Sviridkin (lexfrei) deleted the build/go-test-patched-images branch October 3, 2026 01:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/uncategorized PR auto-labeler could not map title scope to a known area/*; please review size/L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Patched first-party images run go build only, so tests carried in a patch never run

2 participants