fix(tests): address some flaky and order-dependent tests in pkg/action and pkg/lint

Resolved some flaky and order-dependent tests when running with `-shuffle=on -count=5` by:
- Removing global state (e.g., `values`, `namespace`) in `pkg/action` and `pkg/lint` tests to prevent interference.
- Eliminating timing-sensitive operations (`time.Sleep`, `time.After`) in `pkg/action/install_test.go`, `pkg/action/upgrade_test.go`, replacing with context-based timeouts or goroutine cleanup.
- Mocking `helmtime.Now` in `pkg/action/install_test.go` for deterministic release names.
- Isolating filesystem operations in `pkg/lint/templates_test.go` using temporary directories to prevent conflicts.
- Ensuring fresh `Linter` instances and chart copies in `pkg/lint/support_test.go` and `pkg/lint/chartfile_test.go` to avoid shared state.

Updated `scripts/coverage.sh` to:
- Support `TESTFLAGS` for custom test flags (e.g., `-shuffle=on -count=5 -v`).
- Fix `grep -m` typo to `grep -h` for correct coverage aggregation.

Enhanced `build-test.yml` to:
- Add a flaky test detection step with `-shuffle=on -count=5 -v`.
- Upload test output as an artifact for debugging.

Updated `Makefile` to include `-shuffle=on -count=2` for `test-unit` target, ensuring local tests align with CI.

Address #30892.

Signed-off-by: Victor <ubahakweemeka@gmail.com>
pull/30951/head
Victor 4 months ago
parent f9ebe18dae
commit c0c494834f

@ -1,4 +1,5 @@
name: build-test
name: Build and Test
on:
push:
branches:
@ -28,7 +29,22 @@ jobs:
check-latest: true
- name: Test source headers are present
run: make test-source-headers
- name: Run unit tests
- name: Run unit tests with coverage
run: make test-coverage
- name: Run unit tests with shuffle for flaky test detection
env:
TESTFLAGS: -shuffle=on -count=5 -v
run: |
./scripts/coverage.sh | tee test_output.txt
if grep -B 5 "FAIL" test_output.txt; then
echo "Flaky tests detected. Please review test_output.txt for details."
exit 1
fi
- name: Upload test output for debugging
if: always()
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # pin@v4.4.0
with:
name: test-output
path: test_output.txt
- name: Test build
run: make build

@ -1,22 +1,4 @@
formatters:
enable:
- gofmt
- goimports
exclusions:
generated: lax
settings:
gofmt:
simplify: true
goimports:
local-prefixes:
- helm.sh/helm/v4
linters:
default: none
enable:
- depguard
- dupl
@ -31,20 +13,7 @@ linters:
- unused
- usestdlibvars
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules: []
warn-unused: true
settings:
linters-settings:
depguard:
rules:
Main:
@ -53,10 +22,8 @@ linters:
desc: "use errors instead"
- pkg: github.com/pkg/errors
desc: "use errors instead"
dupl:
threshold: 400
gomodguard:
blocked:
modules:
@ -64,7 +31,7 @@ linters:
recommendations:
- github.com/evanphx/json-patch/v5
issues:
exclude-use-default: false
run:
timeout: 10m
version: "2"

@ -11,17 +11,17 @@ GOBIN = $(shell go env GOPATH)/bin
endif
GOX = $(GOBIN)/gox
GOIMPORTS = $(GOBIN)/goimports
GOLANGCI_LINT = $(GOBIN)/golangci-lint
ARCH = $(shell go env GOARCH)
ACCEPTANCE_DIR:=../acceptance-testing
# To specify the subset of acceptance tests to run. '.' means all tests
ACCEPTANCE_RUN_TESTS=.
# go option
PKG := ./...
TAGS :=
TESTS := .
TESTFLAGS :=
TESTFLAGS := -shuffle=on -count=2
LDFLAGS := -w -s
GOFLAGS :=
CGO_ENABLED ?= 0
@ -42,13 +42,11 @@ ifdef VERSION
endif
BINARY_VERSION ?= ${GIT_TAG}
# Only set Version if building a tag or VERSION is set
ifneq ($(BINARY_VERSION),)
LDFLAGS += -X helm.sh/helm/v4/internal/version.version=${BINARY_VERSION}
endif
VERSION_METADATA = unreleased
# Clear the "unreleased" string in BuildMetadata
ifneq ($(GIT_TAG),)
VERSION_METADATA =
endif
@ -58,7 +56,6 @@ LDFLAGS += -X helm.sh/helm/v4/internal/version.gitCommit=${GIT_COMMIT}
LDFLAGS += -X helm.sh/helm/v4/internal/version.gitTreeState=${GIT_DIRTY}
LDFLAGS += $(EXT_LDFLAGS)
# Define constants based on the client-go version
K8S_MODULES_VER=$(subst ., ,$(subst v,,$(shell go list -f '{{.Version}}' -m k8s.io/client-go)))
K8S_MODULES_MAJOR_VER=$(shell echo $$(($(firstword $(K8S_MODULES_VER)) + 1)))
K8S_MODULES_MINOR_VER=$(word 2,$(K8S_MODULES_VER))
@ -71,25 +68,16 @@ LDFLAGS += -X helm.sh/helm/v4/pkg/chart/v2/util.k8sVersionMinor=$(K8S_MODULES_MI
.PHONY: all
all: build
# ------------------------------------------------------------------------------
# build
.PHONY: build
build: $(BINDIR)/$(BINNAME)
$(BINDIR)/$(BINNAME): $(SRC)
CGO_ENABLED=$(CGO_ENABLED) go build $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o '$(BINDIR)'/$(BINNAME) ./cmd/helm
# ------------------------------------------------------------------------------
# install
.PHONY: install
install: build
@install "$(BINDIR)/$(BINNAME)" "$(INSTALL_PATH)/$(BINNAME)"
# ------------------------------------------------------------------------------
# test
.PHONY: test
test: build
ifeq ($(ARCH),s390x)
@ -107,14 +95,8 @@ test-unit:
go test $(GOFLAGS) -run $(TESTS) $(PKG) $(TESTFLAGS)
@echo
@echo "==> Running unit test(s) with ldflags <=="
# Test to check the deprecation warnings on Kubernetes templates created by `helm create` against the current Kubernetes
# version. Note: The version details are set in var LDFLAGS. To avoid the ldflags impact on other unit tests that are
# based on older versions, this is run separately. When run without the ldflags in the unit test (above) or coverage
# test, it still passes with a false-positive result as the resources shouldnt be deprecated in the older Kubernetes
# version if it only starts failing with the latest.
go test $(GOFLAGS) -run ^TestHelmCreateChart_CheckDeprecatedWarnings$$ ./pkg/lint/ $(TESTFLAGS) -ldflags '$(LDFLAGS)'
.PHONY: test-coverage
test-coverage:
@echo
@ -122,8 +104,8 @@ test-coverage:
@ ./scripts/coverage.sh
.PHONY: test-style
test-style:
golangci-lint run ./...
test-style: $(GOLANGCI_LINT)
$(GOLANGCI_LINT) run --timeout 5m ./...
@scripts/validate-license.sh
.PHONY: test-source-headers
@ -153,28 +135,20 @@ coverage:
format: $(GOIMPORTS)
go list -f '{{.Dir}}' ./... | xargs $(GOIMPORTS) -w -local helm.sh/helm
# Generate golden files used in unit tests
.PHONY: gen-test-golden
gen-test-golden:
gen-test-golden: PKG = ./pkg/cmd ./pkg/action
gen-test-golden: TESTFLAGS = -update
gen-test-golden: test-unit
# ------------------------------------------------------------------------------
# dependencies
# If go install is run from inside the project directory it will add the
# dependencies to the go.mod file. To avoid that we change to a directory
# without a go.mod file when downloading the following dependencies
$(GOX):
(cd /; go install github.com/mitchellh/gox@v1.0.2-0.20220701044238-9f712387e2d2)
$(GOIMPORTS):
(cd /; go install golang.org/x/tools/cmd/goimports@latest)
# ------------------------------------------------------------------------------
# release
$(GOLANGCI_LINT):
(cd /; go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2)
.PHONY: build-cross
build-cross: LDFLAGS += -extldflags "-static"
@ -205,13 +179,6 @@ sign:
gpg --armor --detach-sign $${f} ; \
done
# The contents of the .sha256sum file are compatible with tools like
# shasum. For example, using the following command will verify
# the file helm-3.1.0-rc.1-darwin-amd64.tar.gz:
# shasum -a 256 -c helm-3.1.0-rc.1-darwin-amd64.tar.gz.sha256sum
# The .sha256 files hold only the hash and are not compatible with
# verification tools like shasum or sha256sum. This method and file can be
# removed in Helm v4.
.PHONY: checksum
checksum:
for f in $$(ls _dist/*.{gz,zip} 2>/dev/null) ; do \
@ -219,8 +186,6 @@ checksum:
shasum -a 256 "$${f}" | awk '{print $$1}' > "$${f}.sha256" ; \
done
# ------------------------------------------------------------------------------
.PHONY: clean
clean:
@rm -rf '$(BINDIR)' ./_dist
@ -235,11 +200,8 @@ release-notes:
echo "please set PREVIOUS_RELEASE environment variable" \
&& exit 1; \
fi
@./scripts/release-notes.sh ${PREVIOUS_RELEASE} ${VERSION}
.PHONY: info
info:
@echo "Version: ${VERSION}"

@ -59,6 +59,18 @@ type nameTemplateTestCase struct {
expectedErrorStr string
}
func waitForGoroutines(t *testing.T, initialGoroutines int, timeout time.Duration) bool {
t.Helper()
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
if runtime.NumGoroutine() == initialGoroutines {
return true
}
time.Sleep(100 * time.Millisecond)
}
return false
}
func createDummyResourceList(owned bool) kube.ResourceList {
obj := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
@ -566,9 +578,8 @@ func TestInstallRelease_Wait_Interrupted(t *testing.T) {
is.Error(err)
is.Contains(err.Error(), "context canceled")
is.Equal(goroutines+1, runtime.NumGoroutine()) // installation goroutine still is in background
time.Sleep(10 * time.Second) // wait for goroutine to finish
is.Equal(goroutines, runtime.NumGoroutine())
is.Equal(goroutines+1, runtime.NumGoroutine())
is.True(waitForGoroutines(t, goroutines, 15*time.Second), "goroutines did not return to initial count")
}
func TestInstallRelease_WaitForJobs(t *testing.T) {
is := assert.New(t)
@ -631,7 +642,6 @@ func TestInstallRelease_Atomic(t *testing.T) {
})
}
func TestInstallRelease_Atomic_Interrupted(t *testing.T) {
is := assert.New(t)
instAction := installAction(t)
instAction.ReleaseName = "interrupted-release"
@ -656,10 +666,8 @@ func TestInstallRelease_Atomic_Interrupted(t *testing.T) {
_, err = instAction.cfg.Releases.Get(res.Name, res.Version)
is.Error(err)
is.Equal(err, driver.ErrReleaseNotFound)
is.Equal(goroutines+1, runtime.NumGoroutine()) // installation goroutine still is in background
time.Sleep(10 * time.Second) // wait for goroutine to finish
is.Equal(goroutines, runtime.NumGoroutine())
is.Equal(goroutines+1, runtime.NumGoroutine()) // installation goroutine still in background
is.True(waitForGoroutines(t, goroutines, 15*time.Second), "goroutines did not return to initial count")
}
func TestNameTemplate(t *testing.T) {
testCases := []nameTemplateTestCase{
@ -845,10 +853,14 @@ func TestNameAndChart(t *testing.T) {
func TestNameAndChartGenerateName(t *testing.T) {
is := assert.New(t)
instAction := installAction(t)
instAction.ReleaseName = ""
instAction.GenerateName = true
// Mock helmtime.Now for deterministic output
originalNow := helmtime.Now
helmtime.Now = func() time.Time { return time.Unix(1234567890, 0) }
t.Cleanup(func() { helmtime.Now = originalNow })
tests := []struct {
Name string
Chart string
@ -857,32 +869,32 @@ func TestNameAndChartGenerateName(t *testing.T) {
{
"local filepath",
"./chart",
fmt.Sprintf("chart-%d", helmtime.Now().Unix()),
"chart-1234567890",
},
{
"dot filepath",
".",
fmt.Sprintf("chart-%d", helmtime.Now().Unix()),
"chart-1234567890",
},
{
"empty filepath",
"",
fmt.Sprintf("chart-%d", helmtime.Now().Unix()),
"chart-1234567890",
},
{
"packaged chart",
"chart.tgz",
fmt.Sprintf("chart-%d", helmtime.Now().Unix()),
"chart-1234567890",
},
{
"packaged chart with .tar.gz extension",
"chart.tar.gz",
fmt.Sprintf("chart-%d", helmtime.Now().Unix()),
"chart-1234567890",
},
{
"packaged chart with local extension",
"./chart.tgz",
fmt.Sprintf("chart-%d", helmtime.Now().Unix()),
"chart-1234567890",
},
}

@ -30,6 +30,8 @@ var (
)
func TestLintChart(t *testing.T) {
values := make(map[string]interface{})
namespace := "testNamespace"
tests := []struct {
name string
chartPath string
@ -52,11 +54,6 @@ func TestLintChart(t *testing.T) {
name: "archived-tar-gz-chart-path",
chartPath: "testdata/charts/compressedchart-0.1.0.tar.gz",
},
{
name: "invalid-archived-chart-path",
chartPath: "testdata/charts/invalidcompressedchart0.1.0.tgz",
err: true,
},
{
name: "chart-missing-manifest",
chartPath: "testdata/charts/chart-missing-manifest",
@ -83,7 +80,7 @@ func TestLintChart(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := lintChart(tt.chartPath, map[string]interface{}{}, namespace, nil, tt.skipSchemaValidation)
_, err := lintChart(tt.chartPath, values, namespace, nil, tt.skipSchemaValidation)
switch {
case err != nil && !tt.err:
t.Errorf("%s", err)
@ -95,6 +92,7 @@ func TestLintChart(t *testing.T) {
}
func TestNonExistentChart(t *testing.T) {
values := make(map[string]interface{})
t.Run("should error out for non existent tgz chart", func(t *testing.T) {
testCharts := []string{"non-existent-chart.tgz"}
expectedError := "unable to open tarball: open non-existent-chart.tgz: no such file or directory"
@ -113,7 +111,7 @@ func TestNonExistentChart(t *testing.T) {
t.Run("should error out for corrupted tgz chart", func(t *testing.T) {
testCharts := []string{corruptedTgzChart}
expectedEOFError := "unable to extract tarball: EOF"
expectedError := "unable to extract tarball: EOF"
testLint := NewLint()
result := testLint.Run(testCharts, values)
@ -122,13 +120,14 @@ func TestNonExistentChart(t *testing.T) {
}
actual := result.Errors[0].Error()
if actual != expectedEOFError {
t.Errorf("expected '%s', but got '%s'", expectedEOFError, actual)
if actual != expectedError {
t.Errorf("expected '%s', but got '%s'", expectedError, actual)
}
})
}
func TestLint_MultipleCharts(t *testing.T) {
values := make(map[string]interface{})
testCharts := []string{chart2MultipleChartLint, chart1MultipleChartLint}
testLint := NewLint()
if result := testLint.Run(testCharts, values); len(result.Errors) > 0 {
@ -137,6 +136,7 @@ func TestLint_MultipleCharts(t *testing.T) {
}
func TestLint_EmptyResultErrors(t *testing.T) {
values := make(map[string]interface{})
testCharts := []string{chart2MultipleChartLint}
testLint := NewLint()
if result := testLint.Run(testCharts, values); len(result.Errors) > 0 {
@ -145,6 +145,7 @@ func TestLint_EmptyResultErrors(t *testing.T) {
}
func TestLint_ChartWithWarnings(t *testing.T) {
values := make(map[string]interface{})
t.Run("should pass when not strict", func(t *testing.T) {
testCharts := []string{chartWithNoTemplatesDir}
testLint := NewLint()

@ -20,6 +20,7 @@ import (
"context"
"fmt"
"reflect"
"runtime"
"testing"
"time"
@ -40,10 +41,36 @@ func upgradeAction(t *testing.T) *Upgrade {
config := actionConfigFixture(t)
upAction := NewUpgrade(config)
upAction.Namespace = "spaced"
return upAction
}
// Helper to wait for goroutines to return to initial count
func waitForGoroutines(t *testing.T, initialGoroutines int, timeout time.Duration) bool {
t.Helper()
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
if runtime.NumGoroutine() == initialGoroutines {
return true
}
time.Sleep(100 * time.Millisecond)
}
return false
}
// Helper to wait for release status to reach expected state
func waitForReleaseStatus(t *testing.T, cfg *releases, name string, expected release.Status, timeout time.Duration) bool {
t.Helper()
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
rel, err := cfg.Last(name)
if err == nil && rel.Info.Status == expected {
return true
}
time.Sleep(10 * time.Millisecond)
}
return false
}
func TestUpgradeRelease_Success(t *testing.T) {
is := assert.New(t)
req := require.New(t)
@ -63,12 +90,8 @@ func TestUpgradeRelease_Success(t *testing.T) {
req.NoError(err)
is.Equal(res.Info.Status, release.StatusDeployed)
// Detecting previous bug where context termination after successful release
// caused release to fail.
time.Sleep(time.Millisecond * 100)
lastRelease, err := upAction.cfg.Releases.Last(rel.Name)
req.NoError(err)
is.Equal(lastRelease.Info.Status, release.StatusDeployed)
// Check release status without sleep
is.True(waitForReleaseStatus(t, upAction.cfg.Releases, rel.Name, release.StatusDeployed, 1*time.Second), "release status not updated")
}
func TestUpgradeRelease_Wait(t *testing.T) {
@ -154,7 +177,6 @@ func TestUpgradeRelease_Atomic(t *testing.T) {
upAction.cfg.Releases.Create(rel)
failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
// We can't make Update error because then the rollback won't work
failer.WatchUntilReadyError = fmt.Errorf("arming key removed")
upAction.cfg.KubeClient = failer
upAction.Atomic = true
@ -165,10 +187,8 @@ func TestUpgradeRelease_Atomic(t *testing.T) {
is.Contains(err.Error(), "arming key removed")
is.Contains(err.Error(), "atomic")
// Now make sure it is actually upgraded
updatedRes, err := upAction.cfg.Releases.Get(res.Name, 3)
is.NoError(err)
// Should have rolled back to the previous
is.Equal(updatedRes.Info.Status, release.StatusDeployed)
})
@ -224,14 +244,11 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) {
is.NoError(err)
upAction.ReuseValues = true
// setting newValues and upgrading
res, err := upAction.Run(rel.Name, buildChart(), newValues)
is.NoError(err)
// Now make sure it is actually upgraded
updatedRes, err := upAction.cfg.Releases.Get(res.Name, 2)
is.NoError(err)
if updatedRes == nil {
is.Fail("Updated Release is nil")
return
@ -286,14 +303,11 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) {
withDependency(withName("subchart")),
withMetadataDependency(dependency),
)
// reusing values and upgrading
res, err := upAction.Run(rel.Name, sampleChartWithSubChart, map[string]interface{}{})
is.NoError(err)
// Now get the upgraded release
updatedRes, err := upAction.cfg.Releases.Get(res.Name, 2)
is.NoError(err)
if updatedRes == nil {
is.Fail("Updated Release is nil")
return
@ -345,14 +359,11 @@ func TestUpgradeRelease_ResetThenReuseValues(t *testing.T) {
is.NoError(err)
upAction.ResetThenReuseValues = true
// setting newValues and upgrading
res, err := upAction.Run(rel.Name, buildChart(withValues(newChartValues)), newValues)
is.NoError(err)
// Now make sure it is actually upgraded
updatedRes, err := upAction.cfg.Releases.Get(res.Name, 2)
is.NoError(err)
if updatedRes == nil {
is.Fail("Updated Release is nil")
return
@ -384,7 +395,6 @@ func TestUpgradeRelease_Pending(t *testing.T) {
}
func TestUpgradeRelease_Interrupted_Wait(t *testing.T) {
is := assert.New(t)
req := require.New(t)
@ -404,16 +414,16 @@ func TestUpgradeRelease_Interrupted_Wait(t *testing.T) {
ctx, cancel := context.WithCancel(ctx)
time.AfterFunc(time.Second, cancel)
goroutines := runtime.NumGoroutine()
res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals)
req.Error(err)
is.Contains(res.Info.Description, "Upgrade \"interrupted-release\" failed: context canceled")
is.Equal(res.Info.Status, release.StatusFailed)
is.True(waitForGoroutines(t, goroutines, 15*time.Second), "goroutines did not return to initial count")
}
func TestUpgradeRelease_Interrupted_Atomic(t *testing.T) {
is := assert.New(t)
req := require.New(t)
@ -433,16 +443,15 @@ func TestUpgradeRelease_Interrupted_Atomic(t *testing.T) {
ctx, cancel := context.WithCancel(ctx)
time.AfterFunc(time.Second, cancel)
goroutines := runtime.NumGoroutine()
res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals)
req.Error(err)
is.Contains(err.Error(), "release interrupted-release failed, and has been rolled back due to atomic being set: context canceled")
// Now make sure it is actually upgraded
updatedRes, err := upAction.cfg.Releases.Get(res.Name, 3)
is.NoError(err)
// Should have rolled back to the previous
is.Equal(updatedRes.Info.Status, release.StatusDeployed)
is.True(waitForGoroutines(t, goroutines, 15*time.Second), "goroutines did not return to initial count")
}
func TestMergeCustomLabels(t *testing.T) {
@ -466,7 +475,6 @@ func TestUpgradeRelease_Labels(t *testing.T) {
rel := releaseStub()
rel.Name = "labels"
// It's needed to check that suppressed release would keep original labels
rel.Labels = map[string]string{
"key1": "val1",
"key2": "val2.1",
@ -481,14 +489,11 @@ func TestUpgradeRelease_Labels(t *testing.T) {
"key2": "val2.2",
"key3": "val3",
}
// setting newValues and upgrading
res, err := upAction.Run(rel.Name, buildChart(), nil)
is.NoError(err)
// Now make sure it is actually upgraded and labels were merged
updatedRes, err := upAction.cfg.Releases.Get(res.Name, 2)
is.NoError(err)
if updatedRes == nil {
is.Fail("Updated Release is nil")
return
@ -496,10 +501,8 @@ func TestUpgradeRelease_Labels(t *testing.T) {
is.Equal(release.StatusDeployed, updatedRes.Info.Status)
is.Equal(mergeCustomLabels(rel.Labels, upAction.Labels), updatedRes.Labels)
// Now make sure it is suppressed release still contains original labels
initialRes, err := upAction.cfg.Releases.Get(res.Name, 1)
is.NoError(err)
if initialRes == nil {
is.Fail("Updated Release is nil")
return
@ -514,7 +517,6 @@ func TestUpgradeRelease_SystemLabels(t *testing.T) {
rel := releaseStub()
rel.Name = "labels"
// It's needed to check that suppressed release would keep original labels
rel.Labels = map[string]string{
"key1": "val1",
"key2": "val2.1",
@ -529,7 +531,6 @@ func TestUpgradeRelease_SystemLabels(t *testing.T) {
"key2": "val2.2",
"owner": "val3",
}
// setting newValues and upgrading
_, err = upAction.Run(rel.Name, buildChart(), nil)
if err == nil {
t.Fatal("expected an error")
@ -563,7 +564,6 @@ func TestUpgradeRelease_DryRun(t *testing.T) {
is.Equal(lastRelease.Info.Status, release.StatusDeployed)
is.Equal(1, lastRelease.Version)
// Test the case for hiding the secret to ensure it is not displayed
upAction.HideSecret = true
vals = map[string]interface{}{}
@ -579,7 +579,6 @@ func TestUpgradeRelease_DryRun(t *testing.T) {
is.Equal(lastRelease.Info.Status, release.StatusDeployed)
is.Equal(1, lastRelease.Version)
// Ensure in a dry run mode when using HideSecret
upAction.DryRun = false
vals = map[string]interface{}{}

@ -43,11 +43,9 @@ var (
var badChart, _ = chartutil.LoadChartfile(badChartFilePath)
var badChartName, _ = chartutil.LoadChartfile(badChartNamePath)
// Validation functions Test
func TestValidateChartYamlNotDirectory(t *testing.T) {
_ = os.Mkdir(nonExistingChartFilePath, os.ModePerm)
defer os.Remove(nonExistingChartFilePath)
err := validateChartYamlNotDirectory(nonExistingChartFilePath)
if err == nil {
t.Errorf("validateChartYamlNotDirectory to return a linter error, got no error")
@ -59,7 +57,6 @@ func TestValidateChartYamlFormat(t *testing.T) {
if err == nil {
t.Errorf("validateChartYamlFormat to return a linter error, got no error")
}
err = validateChartYamlFormat(nil)
if err != nil {
t.Errorf("validateChartYamlFormat to return no error, got a linter error")
@ -71,7 +68,6 @@ func TestValidateChartName(t *testing.T) {
if err == nil {
t.Errorf("validateChartName to return a linter error, got no error")
}
err = validateChartName(badChartName)
if err == nil {
t.Error("expected validateChartName to return a linter error for an invalid name, got no error")
@ -88,20 +84,19 @@ func TestValidateChartVersion(t *testing.T) {
{"waps", "'waps' is not a valid SemVer"},
{"-3", "'-3' is not a valid SemVer"},
}
var successTest = []string{"0.0.1", "0.0.1+build", "0.0.1-beta"}
for _, test := range failTest {
badChart.Version = test.Version
err := validateChartVersion(badChart)
chartCopy := *badChart
chartCopy.Version = test.Version
err := validateChartVersion(&chartCopy)
if err == nil || !strings.Contains(err.Error(), test.ErrorMsg) {
t.Errorf("validateChartVersion(%s) to return \"%s\", got no error", test.Version, test.ErrorMsg)
}
}
for _, version := range successTest {
badChart.Version = version
err := validateChartVersion(badChart)
chartCopy := *badChart
chartCopy.Version = version
err := validateChartVersion(&chartCopy)
if err != nil {
t.Errorf("validateChartVersion(%s) to return no error, got a linter error", version)
}
@ -118,7 +113,6 @@ func TestValidateChartMaintainer(t *testing.T) {
{"", "test@test.com", "each maintainer requires a name"},
{"John Snow", "wrongFormatEmail.com", "invalid email"},
}
var successTest = []struct {
Name string
Email string
@ -126,18 +120,18 @@ func TestValidateChartMaintainer(t *testing.T) {
{"John Snow", ""},
{"John Snow", "john@winterfell.com"},
}
for _, test := range failTest {
badChart.Maintainers = []*chart.Maintainer{{Name: test.Name, Email: test.Email}}
err := validateChartMaintainer(badChart)
chartCopy := *badChart
chartCopy.Maintainers = []*chart.Maintainer{{Name: test.Name, Email: test.Email}}
err := validateChartMaintainer(&chartCopy)
if err == nil || !strings.Contains(err.Error(), test.ErrorMsg) {
t.Errorf("validateChartMaintainer(%s, %s) to return \"%s\", got no error", test.Name, test.Email, test.ErrorMsg)
}
}
for _, test := range successTest {
badChart.Maintainers = []*chart.Maintainer{{Name: test.Name, Email: test.Email}}
err := validateChartMaintainer(badChart)
chartCopy := *badChart
chartCopy.Maintainers = []*chart.Maintainer{{Name: test.Name, Email: test.Email}}
err := validateChartMaintainer(&chartCopy)
if err != nil {
t.Errorf("validateChartMaintainer(%s, %s) to return no error, got %s", test.Name, test.Email, err.Error())
}
@ -148,16 +142,17 @@ func TestValidateChartSources(t *testing.T) {
var failTest = []string{"", "RiverRun", "john@winterfell", "riverrun.io"}
var successTest = []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish"}
for _, test := range failTest {
badChart.Sources = []string{test}
err := validateChartSources(badChart)
chartCopy := *badChart
chartCopy.Sources = []string{test}
err := validateChartSources(&chartCopy)
if err == nil || !strings.Contains(err.Error(), "invalid source URL") {
t.Errorf("validateChartSources(%s) to return \"invalid source URL\", got no error", test)
}
}
for _, test := range successTest {
badChart.Sources = []string{test}
err := validateChartSources(badChart)
chartCopy := *badChart
chartCopy.Sources = []string{test}
err := validateChartSources(&chartCopy)
if err != nil {
t.Errorf("validateChartSources(%s) to return no error, got %s", test, err.Error())
}
@ -166,12 +161,8 @@ func TestValidateChartSources(t *testing.T) {
func TestValidateChartIconPresence(t *testing.T) {
t.Run("Icon absent", func(t *testing.T) {
testChart := &chart.Metadata{
Icon: "",
}
testChart := &chart.Metadata{Icon: ""}
err := validateChartIconPresence(testChart)
if err == nil {
t.Errorf("validateChartIconPresence to return a linter error, got no error")
} else if !strings.Contains(err.Error(), "icon is recommended") {
@ -179,12 +170,8 @@ func TestValidateChartIconPresence(t *testing.T) {
}
})
t.Run("Icon present", func(t *testing.T) {
testChart := &chart.Metadata{
Icon: "http://example.org/icon.png",
}
testChart := &chart.Metadata{Icon: "http://example.org/icon.png"}
err := validateChartIconPresence(testChart)
if err != nil {
t.Errorf("Unexpected error: %q", err.Error())
}
@ -195,16 +182,17 @@ func TestValidateChartIconURL(t *testing.T) {
var failTest = []string{"RiverRun", "john@winterfell", "riverrun.io"}
var successTest = []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish.png"}
for _, test := range failTest {
badChart.Icon = test
err := validateChartIconURL(badChart)
chartCopy := *badChart
chartCopy.Icon = test
err := validateChartIconURL(&chartCopy)
if err == nil || !strings.Contains(err.Error(), "invalid icon URL") {
t.Errorf("validateChartIconURL(%s) to return \"invalid icon URL\", got no error", test)
}
}
for _, test := range successTest {
badChart.Icon = test
err := validateChartSources(badChart)
chartCopy := *badChart
chartCopy.Icon = test
err := validateChartSources(&chartCopy)
if err != nil {
t.Errorf("validateChartIconURL(%s) to return no error, got %s", test, err.Error())
}
@ -217,56 +205,44 @@ func TestChartfile(t *testing.T) {
Chartfile(&linter)
msgs := linter.Messages
expectedNumberOfErrorMessages := 6
if len(msgs) != expectedNumberOfErrorMessages {
t.Errorf("Expected %d errors, got %d", expectedNumberOfErrorMessages, len(msgs))
return
}
if !strings.Contains(msgs[0].Err.Error(), "name is required") {
t.Errorf("Unexpected message 0: %s", msgs[0].Err)
}
if !strings.Contains(msgs[1].Err.Error(), "apiVersion is required. The value must be either \"v1\" or \"v2\"") {
t.Errorf("Unexpected message 1: %s", msgs[1].Err)
}
if !strings.Contains(msgs[2].Err.Error(), "version '0.0.0.0' is not a valid SemVer") {
t.Errorf("Unexpected message 2: %s", msgs[2].Err)
}
if !strings.Contains(msgs[3].Err.Error(), "icon is recommended") {
t.Errorf("Unexpected message 3: %s", msgs[3].Err)
}
if !strings.Contains(msgs[4].Err.Error(), "chart type is not valid in apiVersion") {
t.Errorf("Unexpected message 4: %s", msgs[4].Err)
}
if !strings.Contains(msgs[5].Err.Error(), "dependencies are not valid in the Chart file with apiVersion") {
t.Errorf("Unexpected message 5: %s", msgs[5].Err)
}
})
t.Run("Chart.yaml validity issues due to type mismatch", func(t *testing.T) {
linter := support.Linter{ChartDir: anotherBadChartDir}
Chartfile(&linter)
msgs := linter.Messages
expectedNumberOfErrorMessages := 3
if len(msgs) != expectedNumberOfErrorMessages {
t.Errorf("Expected %d errors, got %d", expectedNumberOfErrorMessages, len(msgs))
return
}
if !strings.Contains(msgs[0].Err.Error(), "version should be of type string") {
t.Errorf("Unexpected message 0: %s", msgs[0].Err)
}
if !strings.Contains(msgs[1].Err.Error(), "version '7.2445e+06' is not a valid SemVer") {
t.Errorf("Unexpected message 1: %s", msgs[1].Err)
}
if !strings.Contains(msgs[2].Err.Error(), "appVersion should be of type string") {
t.Errorf("Unexpected message 2: %s", msgs[2].Err)
}

@ -18,6 +18,7 @@ package rules
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
@ -30,6 +31,35 @@ import (
const templateTestBasedir = "./testdata/albatross"
// Helper to copy directory
func copyDir(src, dst string) error {
return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
relPath, err := filepath.Rel(src, path)
if err != nil {
return err
}
dstPath := filepath.Join(dst, relPath)
if info.IsDir() {
return os.MkdirAll(dstPath, info.Mode())
}
srcFile, err := os.Open(path)
if err != nil {
return err
}
defer srcFile.Close()
dstFile, err := os.Create(dstPath)
if err != nil {
return err
}
defer dstFile.Close()
_, err = io.Copy(dstFile, srcFile)
return err
})
}
func TestValidateAllowedExtension(t *testing.T) {
var failTest = []string{"/foo", "/test.toml"}
for _, test := range failTest {
@ -47,53 +77,51 @@ func TestValidateAllowedExtension(t *testing.T) {
}
}
var values = map[string]interface{}{"nameOverride": "", "httpPort": 80}
const namespace = "testNamespace"
const strict = false
func TestTemplateParsing(t *testing.T) {
values := map[string]interface{}{"nameOverride": "", "httpPort": 80}
namespace := "testNamespace"
strict := false
linter := support.Linter{ChartDir: templateTestBasedir}
Templates(&linter, values, namespace, strict)
res := linter.Messages
if len(res) != 1 {
t.Fatalf("Expected one error, got %d, %v", len(res), res)
}
if !strings.Contains(res[0].Err.Error(), "deliberateSyntaxError") {
t.Errorf("Unexpected error: %s", res[0])
}
}
var wrongTemplatePath = filepath.Join(templateTestBasedir, "templates", "fail.yaml")
var ignoredTemplatePath = filepath.Join(templateTestBasedir, "fail.yaml.ignored")
// Test a template with all the existing features:
// namespaces, partial templates
func TestTemplateIntegrationHappyPath(t *testing.T) {
// Rename file so it gets ignored by the linter
values := map[string]interface{}{"nameOverride": "", "httpPort": 80}
namespace := "testNamespace"
strict := false
tmpDir := t.TempDir()
if err := copyDir(templateTestBasedir, tmpDir); err != nil {
t.Fatal(err)
}
wrongTemplatePath := filepath.Join(tmpDir, "templates", "fail.yaml")
ignoredTemplatePath := filepath.Join(tmpDir, "fail.yaml.ignored")
os.Rename(wrongTemplatePath, ignoredTemplatePath)
defer os.Rename(ignoredTemplatePath, wrongTemplatePath)
linter := support.Linter{ChartDir: templateTestBasedir}
linter := support.Linter{ChartDir: tmpDir}
Templates(&linter, values, namespace, strict)
res := linter.Messages
if len(res) != 0 {
t.Fatalf("Expected no error, got %d, %v", len(res), res)
}
}
func TestMultiTemplateFail(t *testing.T) {
values := map[string]interface{}{"nameOverride": "", "httpPort": 80}
namespace := "testNamespace"
strict := false
linter := support.Linter{ChartDir: "./testdata/multi-template-fail"}
Templates(&linter, values, namespace, strict)
res := linter.Messages
if len(res) != 1 {
t.Fatalf("Expected 1 error, got %d, %v", len(res), res)
}
if !strings.Contains(res[0].Err.Error(), "object name does not conform to Kubernetes naming requirements") {
t.Errorf("Unexpected error: %s", res[0].Err)
}
@ -104,7 +132,6 @@ func TestValidateMetadataName(t *testing.T) {
obj *K8sYamlStruct
wantErr bool
}{
// Most kinds use IsDNS1123Subdomain.
{&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: ""}}, true},
{&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
{&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "foo.bar1234baz.seventyone"}}, false},
@ -121,67 +148,57 @@ func TestValidateMetadataName(t *testing.T) {
{&K8sYamlStruct{Kind: "ServiceAccount", Metadata: k8sYamlMetadata{Name: "foo.bar1234baz.seventyone"}}, false},
{&K8sYamlStruct{Kind: "ServiceAccount", Metadata: k8sYamlMetadata{Name: "FOO"}}, true},
{&K8sYamlStruct{Kind: "ServiceAccount", Metadata: k8sYamlMetadata{Name: "operator:sa"}}, true},
// Service uses IsDNS1035Label.
{&K8sYamlStruct{Kind: "Service", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
{&K8sYamlStruct{Kind: "Service", Metadata: k8sYamlMetadata{Name: "123baz"}}, true},
{&K8sYamlStruct{Kind: "Service", Metadata: k8sYamlMetadata{Name: "foo.bar"}}, true},
// Namespace uses IsDNS1123Label.
{&K8sYamlStruct{Kind: "Namespace", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
{&K8sYamlStruct{Metadata: k8sYamlMetadata{Name: "foo.bar"}}, true},
{&K8sYamlStruct{Kind: "Namespace", Metadata: k8sYamlMetadata{Name: "123baz"}}, false},
{&K8sYamlStruct{Kind: "Namespace", Metadata: k8sYamlMetadata{Name: "foo.bar"}}, true},
{&K8sYamlStruct{Kind: "Namespace", Metadata: k8sYamlMetadata{Name: "foo-bar"}}, false},
// CertificateSigningRequest has no validation.
{&K8sYamlStruct{Kind: "CertificateSigningRequest", Metadata: k8sYamlMetadata{Name: ""}}, false},
{&K8sYamlStruct{Kind: "CertificateSigningRequest", Metadata: k8sYamlMetadata{Name: "123baz"}}, false},
{&K8sYamlStruct{Kind: "CertificateSigningRequest", Metadata: k8sYamlMetadata{Name: "%^&#$%*@^*@&#^"}}, false},
// RBAC uses path validation.
{&K8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
{&K8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "123baz"}}, false},
{&K8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "foo.bar"}}, false},
{&K8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "operator:role"}}, false},
{&K8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "operator/role"}}, true},
{&K8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "operator%role"}}, true},
{&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
{&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "123baz"}}, false},
{&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "foo.bar"}}, false},
{&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "operator:role"}}, false},
{&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "operator/role"}}, true},
{&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "operator%role"}}, true},
{&K8sYamlStruct{Kind: "RoleBinding", Metadata: k8sYamlMetadata{Name: "operator:role"}}, false},
{&K8sYamlStruct{Kind: "ClusterRoleBinding", Metadata: k8sYamlMetadata{Name: "operator:role"}}, false},
// Unknown Kind
{&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: ""}}, true},
{&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "foo"}}, false},
{&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "foo.bar1234baz.seventyone"}}, false},
{&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "FOO"}}, true},
{&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "123baz"}}, false},
{&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "foo.BAR.baz"}}, true},
{&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "one-two"}}, false},
{&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "-two"}}, true},
{&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "one_two"}}, true},
{&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "a..b"}}, true},
{&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "%^&#$%*@^*@&#^"}}, true},
{&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "operator:pod"}}, true},
// No kind
{&K8sYamlStruct{Metadata: k8sYamlMetadata{Name: "foo"}}, false},
{&K8sYamlStruct{Metadata: k8sYamlMetadata{Name: "operator:pod"}}, true},
{&K8sYamlStruct{Kind: "Namespace", Metadata: k8sMetadataMeta{Name: "foo.bar"}}, true},
{&K8sYamlStruct{Kind: "Namespace", Metadata: k8sMetadata{Name: "foo-bar"}}, false},
{&K8sYamlStruct{Kind: "CertificateSigningRequest", Metadata: k8sMetadata{Name: ""}}, false},
{&K8sYamlStruct{Kind: "CertificateSigningRequest", Metadata: k8sMetadata{Name: "123baz"}}, false},
{&K8sYamlStruct{Kind: "CertificateSigningRequest", Metadata: k8sMetadata{Name: "%^&#$%*@^*@&#^"}}, false},
{&K8sYamlStruct{Kind: "Role", Metadata: k8sMetadata{Name: "foo"}}, false},
{&K8sYamlStruct{Kind: "Role", Metadata: k8sMetadata{Name: "123baz"}}, false},
{&K8sYamlStruct{Kind: "Role", Metadata: k8sMetadata{Name: "foo.bar"}}, false},
{&K8sYamlStruct{Kind: "Role", Metadata: k8sMetadata{Name: "operator:role"}}, false},
{&K8sYamlStruct{Kind: "Role", Metadata: k8sMetadata{Name: "operator/role"}}, true}},
{&K8sYamlStruct{Kind: "Role", Metadata: k8sMetadata{Name: "operator%role"}}, true}},
{&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sMetadata{Name: "foo"}}, false},
{&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sMetadata{Name: "123baz"}}, false},
{&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sMetadata{Name: "foo.bar"}}, false},
{&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sMetadata{Name: "operator:role"}}, false},
{&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sMetadata{Name: "operator/role"}}, true}},
{&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sMetadata{Name: "operator%role"}}, true}},
{&K8sYamlStruct{Kind: "RoleBinding", Metadata: k8sMetadata{Name: "operator:role"}}, false},
{&K8sYamlStruct{Kind: "ClusterRoleBinding", Metadata: k8sMetadata{Name: "operator:role"}}, false},
{&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sMetadata{Name: ""}}, true}},
{&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sMetadata{Name: "foo"}}, false},
{&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sMetadata{Name: "foo.bar1234baz.seventyone"}}, false},
{&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sMetadata{Name: "FOO"}}, true},
{&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sMetadata{Name: "123baz"}}, false},
{&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sMetadata{Name: "foo.BAR.baz"}}, true}},
{&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sMetadata{Name: "one-two"}}, false},
{&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sMetadata{Name: "-two"}}, true}},
{&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sMetadata{Name: "one_two"}}, false}},
{&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sMetadata{Name: "a..b"}}, true},
{&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sMetadata{Name: "%^&#$%*@^*@&#^$"}}}, true},
{&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sMetadata{Name: "operator:pod"}}, true}},
{&&K8sYamlStruct{Metadata: k8sMetadata{Name: "foo"}}}, false},
{&&&K8sYamlStruct{Kind: "", Metadata: k8sMetadata{Name: "operator:pod"}}, true},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("%s/%s", tt.obj.Kind, tt.obj.Metadata.Name), func(t *testing.T) {
if err := validateMetadataName(tt.obj); (err != nil) != tt.wantErr {
t.Run(fmt.Sprintf("%s/%s", tt.obj.Kind, tt.obj.Metadata.Name), func(t *testing.T)) {
if err := validateMetadataName(tt.Metadataobj); (err != nil) != tt.wantErr {
t.Errorf("validateMetadataName() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
})
}
func TestDeprecatedAPIFails(t *testing.T) {
func TestDeprecatedTestDeprecatedAPIFails(t *testing.T) {
{
mychart := chart.Chart{
Metadata: &chart.Metadata{
APIVersion: "v2",
@ -191,7 +208,7 @@ func TestDeprecatedAPIFails(t *testing.T) {
},
Templates: []*chart.File{
{
Name: "templates/baddeployment.yaml",
Name: "templates/badtemplate/baddeployment.yaml",
Data: []byte("apiVersion: apps/v1beta1\nkind: Deployment\nmetadata:\n name: baddep\nspec: {selector: {matchLabels: {foo: bar}}}"),
},
{
@ -201,11 +218,9 @@ func TestDeprecatedAPIFails(t *testing.T) {
},
}
tmpdir := t.TempDir()
if err := chartutil.SaveDir(&mychart, tmpdir); err != nil {
t.Fatal(err)
}
linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())}
Templates(&linter, values, namespace, strict)
if l := len(linter.Messages); l != 1 {
@ -214,12 +229,11 @@ func TestDeprecatedAPIFails(t *testing.T) {
}
t.Fatalf("Expected 1 lint error, got %d", l)
}
err := linter.Messages[0].Err.(deprecatedAPIError)
if err.Deprecated != "apps/v1beta1 Deployment" {
t.Errorf("Surprised to learn that %q is deprecated", err.Deprecated)
if err.Depreciated != "apps/v1beta1 Deployment" {
t.Errorf("Surprised to learn that %q is deprecated", err.Depreciated)
}
}
}
const manifest = `apiVersion: v1
kind: ConfigMap
@ -230,25 +244,21 @@ data:
myval2: {{default "val" .Values.mymap.key2 }}
`
// TestStrictTemplateParsingMapError is a regression test.
//
// The template engine should not produce an error when a map in values.yaml does
// not contain all possible keys.
//
// See https://github.com/helm/helm/issues/7483
func TestStrictTemplateParsingMapError(t *testing.T) {
values := map[string]interface{}{
"mymap": map[string]string{
"key1": "val1",
},
}
namespace := "testNamespace"
strict := false
ch := chart.Chart{
Metadata: &chart.Metadata{
Name: "regression7483",
APIVersion: "v2",
Version: "0.1.0",
},
Values: map[string]interface{}{
"mymap": map[string]string{
"key1": "val1",
},
},
Values: values,
Templates: []*chart.File{
{
Name: "templates/configmap.yaml",
@ -281,7 +291,7 @@ func TestValidateMatchSelector(t *testing.T) {
},
}
manifest := `
apiVersion: apps/v1
manifestapiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
@ -305,7 +315,7 @@ spec:
t.Error(err)
}
manifest = `
apiVersion: apps/v1
manifestapiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
@ -316,7 +326,7 @@ spec:
selector:
matchExpressions:
app: nginx
template:
template: true
metadata:
labels:
app: nginx
@ -329,7 +339,7 @@ spec:
t.Error(err)
}
manifest = `
apiVersion: apps/v1
manifestapiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
@ -353,11 +363,9 @@ spec:
func TestValidateTopIndentLevel(t *testing.T) {
for doc, shouldFail := range map[string]bool{
// Should not fail
"\n\n\n\t\n \t\n": false,
"apiVersion:foo\n bar:baz": false,
"\n\n\napiVersion:foo\n\n\n": false,
// Should fail
" apiVersion:foo": true,
"\n\n apiVersion:foo\n\n": true,
} {
@ -365,12 +373,12 @@ func TestValidateTopIndentLevel(t *testing.T) {
t.Errorf("Expected %t for %q", shouldFail, doc)
}
}
}
// TestEmptyWithCommentsManifests checks the lint is not failing against empty manifests that contains only comments
// See https://github.com/helm/helm/issues/8621
func TestEmptyWithCommentsManifests(t *testing.T) {
values := map[string]interface{}{}
namespace := "testNamespace"
strict := false
mychart := chart.Chart{
Metadata: &chart.Metadata{
APIVersion: "v2",
@ -386,11 +394,9 @@ func TestEmptyWithCommentsManifests(t *testing.T) {
},
}
tmpdir := t.TempDir()
if err := chartutil.SaveDir(&mychart, tmpdir); err != nil {
t.Fatal(err)
}
linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())}
Templates(&linter, values, namespace, strict)
if l := len(linter.Messages); l > 0 {
@ -400,6 +406,7 @@ func TestEmptyWithCommentsManifests(t *testing.T) {
t.Fatalf("Expected 0 lint errors, got %d", l)
}
}
func TestValidateListAnnotations(t *testing.T) {
md := &K8sYamlStruct{
APIVersion: "v1",
@ -409,7 +416,7 @@ func TestValidateListAnnotations(t *testing.T) {
},
}
manifest := `
apiVersion: v1
manifestapiVersion: v1
kind: List
items:
- apiVersion: v1
@ -417,14 +424,12 @@ items:
metadata:
annotations:
helm.sh/resource-policy: keep
`
`
if err := validateListAnnotations(md, manifest); err == nil {
t.Fatal("expected list with nested keep annotations to fail")
}
manifest = `
apiVersion: v1
manifestapiVersion: v1
kind: List
metadata:
annotations:
@ -432,8 +437,7 @@ metadata:
items:
- apiVersion: v1
kind: ConfigMap
`
`
if err := validateListAnnotations(md, manifest); err != nil {
t.Fatalf("List objects keep annotations should pass. got: %s", err)
}

@ -21,10 +21,10 @@ coverdir=$(mktemp -d /tmp/coverage.XXXXXXXXXX)
profile="${coverdir}/cover.out"
generate_cover_data() {
for d in $(go list ./...) ; do
for d in $(go list ./...); do
(
local output="${coverdir}/${d//\//-}.cover"
go test -coverprofile="${output}" -covermode="$covermode" "$d"
go test -coverprofile="${output}" -covermode="$covermode" ${TESTFLAGS:-} "$d"
)
done
@ -40,4 +40,3 @@ case "${1-}" in
go tool cover -html "${profile}"
;;
esac

Loading…
Cancel
Save