diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7aa19972f..ed5c2929c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -232,6 +232,22 @@ guide](https://helm.sh/docs/community/developers/) to get started. Coding conventions and standards are explained in the [official developer docs](https://helm.sh/docs/developers/). +### Running tests + +Use the Makefile targets (`make test`, `make test-unit`, `make test-coverage`) +rather than invoking `go test ./...` directly. The Makefile passes the +`helmtest` build tag, which is required: it selects +`internal/testmode/mode_on.go` (setting `const testMode = true`), enabling +`testmode.IsTestMode()` so production code paths in `internal/version` and +`pkg/chart/common` substitute stable values instead of reading build info. +Test binaries have no module info, so without this tag those code paths +panic during package init. + +If you run tests outside the Makefile (IDE test runners, `go test` directly, +custom CI), pass `-tags helmtest`. The tag is omitted from release builds so +the `testing` package and its dependencies stay out of shipped binaries, and +branches gated on `testmode.IsTestMode()` are dead-code-eliminated by the compiler. + ## Pull Requests Like any good open source project, we use Pull Requests (PRs) to track code changes. diff --git a/Makefile b/Makefile index 81b149a68..bf78d5511 100644 --- a/Makefile +++ b/Makefile @@ -90,6 +90,13 @@ test: test-style test: test-unit .PHONY: test-unit +# The `helmtest` build tag selects internal/testmode/mode_on.go, flipping +# testmode.IsTestMode() to true so production code paths in internal/version +# and pkg/chart/common substitute stable values instead of reading missing +# module info. Attached to test-unit (not the file-level TESTFLAGS) so the +# tag is guaranteed regardless of any target-specific TESTFLAGS overrides +# in upstream targets (e.g. gen-test-golden). +test-unit: TESTFLAGS += -tags helmtest test-unit: @echo @echo "==> Running unit tests <==" @@ -150,7 +157,7 @@ format: $(GOIMPORTS) .PHONY: gen-test-golden gen-test-golden: gen-test-golden: PKG = ./pkg/cmd ./pkg/action -gen-test-golden: TESTFLAGS = -update +gen-test-golden: TESTFLAGS += -update gen-test-golden: test-unit # ------------------------------------------------------------------------------ diff --git a/internal/testmode/mode_off.go b/internal/testmode/mode_off.go new file mode 100644 index 000000000..ea3e52560 --- /dev/null +++ b/internal/testmode/mode_off.go @@ -0,0 +1,21 @@ +//go:build !helmtest + +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package testmode + +const testMode = false diff --git a/internal/testmode/mode_on.go b/internal/testmode/mode_on.go new file mode 100644 index 000000000..045d6e2c1 --- /dev/null +++ b/internal/testmode/mode_on.go @@ -0,0 +1,21 @@ +//go:build helmtest + +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package testmode + +const testMode = true diff --git a/internal/testmode/testmode.go b/internal/testmode/testmode.go new file mode 100644 index 000000000..a2be02709 --- /dev/null +++ b/internal/testmode/testmode.go @@ -0,0 +1,31 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package testmode exposes a compile-time test-mode signal that production +// code paths may consult when they need to behave differently in a `go +// test` binary. The package has no imports and no init side effects, so it +// can be safely linked from release builds: branches gated on IsTestMode() +// are dead-code-eliminated by the compiler. +package testmode + +// IsTestMode reports whether the binary was built with -tags helmtest. +// General-purpose signal that the binary was built for tests; consult it +// from production code paths that need to behave differently under test. +// Backed by a compile-time const (see mode_on.go / mode_off.go) so branches +// gated on it dead-code-eliminate in release builds. +func IsTestMode() bool { + return testMode +} diff --git a/internal/version/version.go b/internal/version/version.go index 572938c96..165d4f22c 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -17,14 +17,14 @@ limitations under the License. package version import ( - "flag" "fmt" "log/slog" "runtime" "strings" - "testing" "github.com/Masterminds/semver/v3" + + "helm.sh/helm/v4/internal/testmode" ) var ( @@ -44,8 +44,14 @@ var ( gitTreeState = "" ) +// Stub Kubernetes version values for use by any test path that needs a +// stable kube major/minor — for example, substituting into capabilities or +// client-go version strings so test output doesn't drift with the +// k8s.io/client-go version pinned in go.mod. Callers decide when to use +// them; they are not tied to any particular build tag or gating mechanism. const ( - kubeClientGoVersionTesting = "v1.20" + KubeVersionMajorTesting uint64 = 1 + KubeVersionMinorTesting uint64 = 20 ) // BuildInfo describes the compile time information. @@ -81,8 +87,8 @@ func Get() BuildInfo { // Test builds don't include debug info / module info // (And even if they did, we probably want a stable version during tests anyway) // Return a default value for test builds - if testing.Testing() { - return kubeClientGoVersionTesting + if testmode.IsTestMode() { + return fmt.Sprintf("v%d.%d", KubeVersionMajorTesting, KubeVersionMinorTesting) } vstr, err := K8sIOClientGoModVersion() @@ -112,7 +118,7 @@ func Get() BuildInfo { } // HACK(bacongobbler): strip out GoVersion during a test run for consistent test output - if flag.Lookup("test.v") != nil { + if testmode.IsTestMode() { v.GoVersion = "" } return v diff --git a/pkg/chart/common/capabilities.go b/pkg/chart/common/capabilities.go index 16910acaa..1b165c281 100644 --- a/pkg/chart/common/capabilities.go +++ b/pkg/chart/common/capabilities.go @@ -20,7 +20,6 @@ import ( "slices" "strconv" "strings" - "testing" "github.com/Masterminds/semver/v3" "k8s.io/client-go/kubernetes/scheme" @@ -29,14 +28,10 @@ import ( apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" k8sversion "k8s.io/apimachinery/pkg/util/version" + "helm.sh/helm/v4/internal/testmode" helmversion "helm.sh/helm/v4/internal/version" ) -const ( - kubeVersionMajorTesting = 1 - kubeVersionMinorTesting = 20 -) - var ( // DefaultVersionSet is the default version set, which includes only Core V1 ("v1"). DefaultVersionSet = allKnownVersions() @@ -146,8 +141,8 @@ func makeDefaultCapabilities() (*Capabilities, error) { // Test builds don't include debug info / module info // (And even if they did, we probably want stable capabilities for tests anyway) // Return a default value for test builds - if testing.Testing() { - return newCapabilities(kubeVersionMajorTesting, kubeVersionMinorTesting) + if testmode.IsTestMode() { + return newCapabilities(helmversion.KubeVersionMajorTesting, helmversion.KubeVersionMinorTesting) } vstr, err := helmversion.K8sIOClientGoModVersion() diff --git a/scripts/coverage.sh b/scripts/coverage.sh index 4a29a68ad..b07d5d7fd 100755 --- a/scripts/coverage.sh +++ b/scripts/coverage.sh @@ -37,7 +37,7 @@ generate_cover_data() { for d in $(go list "$target"); do ( local output="${coverdir}/${d//\//-}.cover" - go test -coverprofile="${output}" -covermode="$covermode" "$d" + go test -tags helmtest -coverprofile="${output}" -covermode="$covermode" "$d" ) done