tests: Add/Fix tests for new flag and fix docker wait, make fmt issue

Signed-off-by: yxxhero <aiopsclub@163.com>
This commit is contained in:
Hubertbits 2025-04-16 15:52:26 +02:00 committed by yxxhero
parent 1c59d0a539
commit acaaa34d69
11 changed files with 743 additions and 217 deletions

View File

@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"github.com/helmfile/helmfile/pkg/common"
"github.com/helmfile/helmfile/pkg/exectest"
"github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/helmexec"
@ -416,3 +417,138 @@ releases:
})
})
}
func TestDiffWithIncludeCRDs(t *testing.T) {
type fields struct {
includeCRDs common.BoolFlag
skipCRDs common.BoolFlag
}
type testcase struct {
fields fields
ns string
error string
selectors []string
diffed []exectest.Release
}
check := func(t *testing.T, tc testcase) {
t.Helper()
wantDiffs := tc.diffed
var helm = &exectest.Helm{
FailOnUnexpectedList: true,
FailOnUnexpectedDiff: true,
DiffMutex: &sync.Mutex{},
ChartsMutex: &sync.Mutex{},
ReleasesMutex: &sync.Mutex{},
Helm3: true,
}
bs := runWithLogCapture(t, "debug", func(t *testing.T, logger *zap.SugaredLogger) {
t.Helper()
valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
if err != nil {
t.Errorf("unexpected error creating vals runtime: %v", err)
}
files := map[string]string{
"/path/to/helmfile.yaml": `
releases:
- name: include-crds
chart: incubator/raw
namespace: default
`,
}
app := appWithFs(&App{
OverrideHelmBinary: DefaultHelmBinary,
fs: filesystem.DefaultFileSystem(),
OverrideKubeContext: "default",
Env: "default",
Logger: logger,
helms: map[helmKey]helmexec.Interface{
createHelmKey("helm", "default"): helm,
},
valsRuntime: valsRuntime,
}, files)
if tc.ns != "" {
app.Namespace = tc.ns
}
if tc.selectors != nil {
app.Selectors = tc.selectors
}
diffConfig := NewApplyConfigWithDefaults(&applyConfig{
// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
concurrency: 1,
logger: logger,
includeCRDs: tc.fields.includeCRDs,
skipCRDs: tc.fields.skipCRDs,
})
diffErr := app.Diff(diffConfig)
var gotErr string
if diffErr != nil {
gotErr = diffErr.Error()
}
if d := cmp.Diff(tc.error, gotErr); d != "" {
t.Fatalf("unexpected error: want (-), got (+): %s", d)
}
require.Equal(t, wantDiffs, helm.Diffed)
})
testhelper.RequireLog(t, "app_diff_test", bs)
}
t.Run("include-crds", func(t *testing.T) {
includeCRDs := common.NewBoolFlag(false)
includeCRDs.Set(true)
check(t, testcase{
fields: fields{
skipCRDs: common.NewBoolFlag(false),
includeCRDs: includeCRDs,
},
diffed: []exectest.Release{
{Name: "include-crds", Flags: []string{"--kube-context", "default", "--namespace", "default", "--reset-values", "--include-crds"}},
},
})
})
t.Run("include-or-skip-crds-unset", func(t *testing.T) {
includeCRDs := common.NewBoolFlag(false)
includeCRDs.Set(true)
check(t, testcase{
fields: fields{
skipCRDs: common.NewBoolFlag(false),
includeCRDs: common.NewBoolFlag(false),
},
diffed: []exectest.Release{
{Name: "include-crds", Flags: []string{"--kube-context", "default", "--namespace", "default", "--reset-values"}},
},
})
})
t.Run("skip-crds", func(t *testing.T) {
skipCRDs := common.NewBoolFlag(false)
skipCRDs.Set(true)
check(t, testcase{
fields: fields{
skipCRDs: skipCRDs,
includeCRDs: common.NewBoolFlag(false),
},
diffed: []exectest.Release{
{Name: "include-crds", Flags: []string{"--kube-context", "default", "--namespace", "default", "--reset-values", "--skip-crds"}},
},
})
})
}

View File

@ -2172,11 +2172,18 @@ func (c configImpl) IncludeCRDs() bool {
return c.includeCRDs.Value()
}
// ShouldIncludeCRDs determines if CRDs should be included in the operation.
// It returns true only when:
// - includeCRDs flag is explicitly provided on the command line and set to true
// - AND skipCRDs flag is not provided on the command line
//
// This ensures that CRDs are only included when explicitly requested and not
// contradicted by the skipCRDs flag.
func (c configImpl) ShouldIncludeCRDs() bool {
includeCRDsExplicit := c.includeCRDs.WasExplicitlySet() && c.includeCRDs.Value()
skipCRDsExplicit := c.skipCRDs.WasExplicitlySet() && !c.skipCRDs.Value()
skipCRDsNotProvided := !c.skipCRDs.WasExplicitlySet()
return includeCRDsExplicit || skipCRDsExplicit
return includeCRDsExplicit && skipCRDsNotProvided
}
func (c configImpl) SkipRefresh() bool {
@ -2324,203 +2331,210 @@ func NewApplyConfigWithDefaults(existing *applyConfig) *applyConfig {
return existing
}
func (a applyConfig) Args() string {
return a.args
func (c applyConfig) Args() string {
return c.args
}
func (a applyConfig) Cascade() string {
return a.cascade
func (c applyConfig) Cascade() string {
return c.cascade
}
func (a applyConfig) Wait() bool {
return a.wait
func (c applyConfig) Wait() bool {
return c.wait
}
func (a applyConfig) WaitRetries() int {
return a.waitRetries
func (c applyConfig) WaitRetries() int {
return c.waitRetries
}
func (a applyConfig) WaitForJobs() bool {
return a.waitForJobs
func (c applyConfig) WaitForJobs() bool {
return c.waitForJobs
}
func (a applyConfig) Values() []string {
return a.values
func (c applyConfig) Values() []string {
return c.values
}
func (a applyConfig) Set() []string {
return a.set
func (c applyConfig) Set() []string {
return c.set
}
func (a applyConfig) Validate() bool {
return a.validate
func (c applyConfig) Validate() bool {
return c.validate
}
func (a applyConfig) SkipCleanup() bool {
return a.skipCleanup
func (c applyConfig) SkipCleanup() bool {
return c.skipCleanup
}
func (a applyConfig) SkipCRDs() bool {
return a.skipCRDs.Value()
func (c applyConfig) SkipCRDs() bool {
return c.skipCRDs.Value()
}
func (a applyConfig) IncludeCRDs() bool {
return a.includeCRDs.Value()
func (c applyConfig) IncludeCRDs() bool {
return c.includeCRDs.Value()
}
// ShouldIncludeCRDs determines if CRDs should be included in the operation.
// It returns true only when:
// - includeCRDs flag is explicitly provided on the command line and set to true
// - AND skipCRDs flag is not provided on the command line
//
// This ensures that CRDs are only included when explicitly requested and not
// contradicted by the skipCRDs flag.
func (c applyConfig) ShouldIncludeCRDs() bool {
includeCRDsExplicit := c.includeCRDs.WasExplicitlySet() && c.includeCRDs.Value()
skipCRDsExplicit := c.skipCRDs.WasExplicitlySet() && !c.skipCRDs.Value()
skipCRDsNotProvided := !c.skipCRDs.WasExplicitlySet()
return includeCRDsExplicit || skipCRDsExplicit
return includeCRDsExplicit && skipCRDsNotProvided
}
func (a applyConfig) SkipDeps() bool {
return a.skipDeps
func (c applyConfig) SkipDeps() bool {
return c.skipDeps
}
func (a applyConfig) SkipRefresh() bool {
return a.skipRefresh
func (c applyConfig) SkipRefresh() bool {
return c.skipRefresh
}
func (a applyConfig) SkipNeeds() bool {
return a.skipNeeds
func (c applyConfig) SkipNeeds() bool {
return c.skipNeeds
}
func (a applyConfig) IncludeNeeds() bool {
return a.includeNeeds || a.IncludeTransitiveNeeds()
func (c applyConfig) IncludeNeeds() bool {
return c.includeNeeds || c.IncludeTransitiveNeeds()
}
func (a applyConfig) IncludeTransitiveNeeds() bool {
return a.includeTransitiveNeeds
func (c applyConfig) IncludeTransitiveNeeds() bool {
return c.includeTransitiveNeeds
}
func (a applyConfig) IncludeTests() bool {
return a.includeTests
func (c applyConfig) IncludeTests() bool {
return c.includeTests
}
func (a applyConfig) Suppress() []string {
return a.suppress
func (c applyConfig) Suppress() []string {
return c.suppress
}
func (a applyConfig) SuppressSecrets() bool {
return a.suppressSecrets
func (c applyConfig) SuppressSecrets() bool {
return c.suppressSecrets
}
func (a applyConfig) ShowSecrets() bool {
return a.showSecrets
func (c applyConfig) ShowSecrets() bool {
return c.showSecrets
}
func (a applyConfig) NoHooks() bool {
return a.noHooks
func (c applyConfig) NoHooks() bool {
return c.noHooks
}
func (a applyConfig) SuppressDiff() bool {
return a.suppressDiff
func (c applyConfig) SuppressDiff() bool {
return c.suppressDiff
}
func (a applyConfig) Color() bool {
return a.color
func (c applyConfig) Color() bool {
return c.color
}
func (a applyConfig) NoColor() bool {
return a.noColor
func (c applyConfig) NoColor() bool {
return c.noColor
}
func (a applyConfig) Context() int {
return a.context
func (c applyConfig) Context() int {
return c.context
}
func (a applyConfig) DiffOutput() string {
return a.diffOutput
func (c applyConfig) DiffOutput() string {
return c.diffOutput
}
func (a applyConfig) Concurrency() int {
return a.concurrency
func (c applyConfig) Concurrency() int {
return c.concurrency
}
func (a applyConfig) DetailedExitcode() bool {
return a.detailedExitcode
func (c applyConfig) DetailedExitcode() bool {
return c.detailedExitcode
}
func (a applyConfig) StripTrailingCR() bool {
return a.stripTrailingCR
func (c applyConfig) StripTrailingCR() bool {
return c.stripTrailingCR
}
func (a applyConfig) Interactive() bool {
return a.interactive
func (c applyConfig) Interactive() bool {
return c.interactive
}
func (a applyConfig) Logger() *zap.SugaredLogger {
return a.logger
func (c applyConfig) Logger() *zap.SugaredLogger {
return c.logger
}
func (a applyConfig) SkipDiffOnInstall() bool {
return a.skipDiffOnInstall
func (c applyConfig) SkipDiffOnInstall() bool {
return c.skipDiffOnInstall
}
func (a applyConfig) SyncArgs() string {
return a.syncArgs
func (c applyConfig) SyncArgs() string {
return c.syncArgs
}
func (a applyConfig) DiffArgs() string {
return a.diffArgs
func (c applyConfig) DiffArgs() string {
return c.diffArgs
}
// helmfile-template-only flags
func (a applyConfig) SkipTests() bool {
return a.skipTests
func (c applyConfig) SkipTests() bool {
return c.skipTests
}
func (a applyConfig) OutputDir() string {
return a.outputDir
func (c applyConfig) OutputDir() string {
return c.outputDir
}
func (a applyConfig) OutputDirTemplate() string {
return a.outputDirTemplate
func (c applyConfig) OutputDirTemplate() string {
return c.outputDirTemplate
}
func (a applyConfig) ReuseValues() bool {
return a.reuseValues
func (c applyConfig) ReuseValues() bool {
return c.reuseValues
}
func (a applyConfig) ResetValues() bool {
return !a.reuseValues
func (c applyConfig) ResetValues() bool {
return !c.reuseValues
}
func (a applyConfig) PostRenderer() string {
return a.postRenderer
func (c applyConfig) PostRenderer() string {
return c.postRenderer
}
func (a applyConfig) PostRendererArgs() []string {
return a.postRendererArgs
func (c applyConfig) PostRendererArgs() []string {
return c.postRendererArgs
}
func (a applyConfig) SuppressOutputLineRegex() []string {
return a.suppressOutputLineRegex
func (c applyConfig) SuppressOutputLineRegex() []string {
return c.suppressOutputLineRegex
}
func (a applyConfig) KubeVersion() string {
return a.kubeVersion
func (c applyConfig) KubeVersion() string {
return c.kubeVersion
}
func (a applyConfig) SkipSchemaValidation() bool {
return a.skipSchemaValidation
func (c applyConfig) SkipSchemaValidation() bool {
return c.skipSchemaValidation
}
func (a applyConfig) ShowOnly() []string {
return a.showOnly
func (c applyConfig) ShowOnly() []string {
return c.showOnly
}
func (a applyConfig) HideNotes() bool {
return a.hideNotes
func (c applyConfig) HideNotes() bool {
return c.hideNotes
}
func (a applyConfig) TakeOwnership() bool {
return a.takeOwnership
func (c applyConfig) TakeOwnership() bool {
return c.takeOwnership
}
func (a applyConfig) SyncReleaseLabels() bool {
return a.syncReleaseLabels
func (c applyConfig) SyncReleaseLabels() bool {
return c.syncReleaseLabels
}
type depsConfig struct {
@ -2528,19 +2542,19 @@ type depsConfig struct {
includeTransitiveNeeds bool
}
func (d depsConfig) SkipRepos() bool {
return d.skipRepos
func (c depsConfig) SkipRepos() bool {
return c.skipRepos
}
func (d depsConfig) IncludeTransitiveNeeds() bool {
return d.includeTransitiveNeeds
func (c depsConfig) IncludeTransitiveNeeds() bool {
return c.includeTransitiveNeeds
}
func (d depsConfig) Args() string {
func (c depsConfig) Args() string {
return ""
}
func (d depsConfig) Concurrency() int {
func (c depsConfig) Concurrency() int {
return 2
}
@ -4077,10 +4091,10 @@ releases:
assert.NoError(t, err)
expected := "NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION\n" +
"myrelease1 testNamespace true false chart:mychart1,common:label,id:myrelease1,name:myrelease1,namespace:testNamespace mychart1\t \n" +
"myrelease2 testNamespace false true chart:mychart1,common:label,name:myrelease2,namespace:testNamespace mychart1\t \n" +
"myrelease3 testNamespace true true chart:mychart1,name:myrelease3,namespace:testNamespace mychart1\t \n" +
"myrelease4 testNamespace true true chart:mychart1,id:myrelease1,name:myrelease4,namespace:testNamespace mychart1\t \n"
"myrelease1 testNamespace true false chart:mychart1,common:label,id:myrelease1,name:myrelease1,namespace:testNamespace mychart1\t \n" +
"myrelease2 testNamespace false true chart:mychart1,common:label,name:myrelease2,namespace:testNamespace mychart1\t \n" +
"myrelease3 testNamespace true true chart:mychart1,name:myrelease3,namespace:testNamespace mychart1\t \n" +
"myrelease4 testNamespace true true chart:mychart1,id:myrelease1,name:myrelease4,namespace:testNamespace mychart1\t \n"
assert.Equal(t, expected, out)
}

View File

@ -75,151 +75,158 @@ func NewDiffConfigWithDefaults(existing *diffConfig) *diffConfig {
return existing
}
func (a diffConfig) Args() string {
return a.args
func (c diffConfig) Args() string {
return c.args
}
func (a diffConfig) DiffArgs() string {
return a.diffArgs
func (c diffConfig) DiffArgs() string {
return c.diffArgs
}
func (a diffConfig) Values() []string {
return a.values
func (c diffConfig) Values() []string {
return c.values
}
func (a diffConfig) Set() []string {
return a.set
func (c diffConfig) Set() []string {
return c.set
}
func (a diffConfig) Validate() bool {
return a.validate
func (c diffConfig) Validate() bool {
return c.validate
}
func (a diffConfig) SkipCRDs() bool {
return a.skipCRDs.Value()
func (c diffConfig) SkipCRDs() bool {
return c.skipCRDs.Value()
}
func (a diffConfig) IncludeCRDs() bool {
return a.includeCRDs.Value()
func (c diffConfig) IncludeCRDs() bool {
return c.includeCRDs.Value()
}
func (a diffConfig) ShouldIncludeCRDs() bool {
includeCRDsExplicit := a.includeCRDs.WasExplicitlySet() && a.includeCRDs.Value()
skipCRDsExplicit := a.skipCRDs.WasExplicitlySet() && !a.skipCRDs.Value()
// ShouldIncludeCRDs determines if CRDs should be included in the operation.
// It returns true only when:
// - includeCRDs flag is explicitly provided on the command line and set to true
// - AND skipCRDs flag is not provided on the command line
//
// This ensures that CRDs are only included when explicitly requested and not
// contradicted by the skipCRDs flag.
func (c diffConfig) ShouldIncludeCRDs() bool {
includeCRDsExplicit := c.includeCRDs.WasExplicitlySet() && c.includeCRDs.Value()
skipCRDsNotProvided := !c.skipCRDs.WasExplicitlySet()
return includeCRDsExplicit || skipCRDsExplicit
return includeCRDsExplicit && skipCRDsNotProvided
}
func (a diffConfig) SkipDeps() bool {
return a.skipDeps
func (c diffConfig) SkipDeps() bool {
return c.skipDeps
}
func (a diffConfig) SkipRefresh() bool {
return a.skipRefresh
func (c diffConfig) SkipRefresh() bool {
return c.skipRefresh
}
func (a diffConfig) IncludeTests() bool {
return a.includeTests
func (c diffConfig) IncludeTests() bool {
return c.includeTests
}
func (a diffConfig) SkipNeeds() bool {
return a.skipNeeds
func (c diffConfig) SkipNeeds() bool {
return c.skipNeeds
}
func (a diffConfig) IncludeNeeds() bool {
return a.includeNeeds || a.IncludeTransitiveNeeds()
func (c diffConfig) IncludeNeeds() bool {
return c.includeNeeds || c.IncludeTransitiveNeeds()
}
func (a diffConfig) IncludeTransitiveNeeds() bool {
return a.includeTransitiveNeeds
func (c diffConfig) IncludeTransitiveNeeds() bool {
return c.includeTransitiveNeeds
}
func (a diffConfig) Suppress() []string {
return a.suppress
func (c diffConfig) Suppress() []string {
return c.suppress
}
func (a diffConfig) SuppressSecrets() bool {
return a.suppressSecrets
func (c diffConfig) SuppressSecrets() bool {
return c.suppressSecrets
}
func (a diffConfig) ShowSecrets() bool {
return a.showSecrets
func (c diffConfig) ShowSecrets() bool {
return c.showSecrets
}
func (a diffConfig) NoHooks() bool {
return a.noHooks
func (c diffConfig) NoHooks() bool {
return c.noHooks
}
func (a diffConfig) SuppressDiff() bool {
return a.suppressDiff
func (c diffConfig) SuppressDiff() bool {
return c.suppressDiff
}
func (a diffConfig) Color() bool {
func (c diffConfig) Color() bool {
return false
}
func (a diffConfig) NoColor() bool {
return a.noColor
func (c diffConfig) NoColor() bool {
return c.noColor
}
func (a diffConfig) Context() int {
return a.context
func (c diffConfig) Context() int {
return c.context
}
func (a diffConfig) DiffOutput() string {
return a.diffOutput
func (c diffConfig) DiffOutput() string {
return c.diffOutput
}
func (a diffConfig) Concurrency() int {
return a.concurrency
func (c diffConfig) Concurrency() int {
return c.concurrency
}
func (a diffConfig) DetailedExitcode() bool {
return a.detailedExitcode
func (c diffConfig) DetailedExitcode() bool {
return c.detailedExitcode
}
func (a diffConfig) StripTrailingCR() bool {
return a.stripTrailingCR
func (c diffConfig) StripTrailingCR() bool {
return c.stripTrailingCR
}
func (a diffConfig) Interactive() bool {
return a.interactive
func (c diffConfig) Interactive() bool {
return c.interactive
}
func (a diffConfig) SkipDiffOnInstall() bool {
return a.skipDiffOnInstall
func (c diffConfig) SkipDiffOnInstall() bool {
return c.skipDiffOnInstall
}
func (a diffConfig) Logger() *zap.SugaredLogger {
return a.logger
func (c diffConfig) Logger() *zap.SugaredLogger {
return c.logger
}
func (a diffConfig) RetainValuesFiles() bool {
return a.retainValuesFiles
func (c diffConfig) RetainValuesFiles() bool {
return c.retainValuesFiles
}
func (a diffConfig) ReuseValues() bool {
return a.reuseValues
func (c diffConfig) ReuseValues() bool {
return c.reuseValues
}
func (a diffConfig) ResetValues() bool {
return !a.reuseValues
func (c diffConfig) ResetValues() bool {
return !c.reuseValues
}
func (a diffConfig) PostRenderer() string {
func (c diffConfig) PostRenderer() string {
return ""
}
func (a diffConfig) PostRendererArgs() []string {
func (c diffConfig) PostRendererArgs() []string {
return nil
}
func (a diffConfig) SkipSchemaValidation() bool {
return a.skipSchemaValidation
func (c diffConfig) SkipSchemaValidation() bool {
return c.skipSchemaValidation
}
func (a diffConfig) SuppressOutputLineRegex() []string {
return a.suppressOutputLineRegex
func (c diffConfig) SuppressOutputLineRegex() []string {
return c.suppressOutputLineRegex
}
func TestDiff(t *testing.T) {

View File

@ -0,0 +1,11 @@
processing file "helmfile.yaml" in directory "."
changing working directory to "/path/to"
merged environment: &{default map[] map[]}
1 release(s) found in helmfile.yaml
processing 1 groups of releases in this order:
GROUP RELEASES
1 default/default/include-crds
processing releases in group 1/1: default/default/include-crds
changing working directory back to "/path/to"

View File

@ -0,0 +1,11 @@
processing file "helmfile.yaml" in directory "."
changing working directory to "/path/to"
merged environment: &{default map[] map[]}
1 release(s) found in helmfile.yaml
processing 1 groups of releases in this order:
GROUP RELEASES
1 default/default/include-crds
processing releases in group 1/1: default/default/include-crds
changing working directory back to "/path/to"

View File

@ -0,0 +1,11 @@
processing file "helmfile.yaml" in directory "."
changing working directory to "/path/to"
merged environment: &{default map[] map[]}
1 release(s) found in helmfile.yaml
processing 1 groups of releases in this order:
GROUP RELEASES
1 default/default/include-crds
processing releases in group 1/1: default/default/include-crds
changing working directory back to "/path/to"

View File

@ -7,6 +7,7 @@ import (
"github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/testutil"
"github.com/helmfile/helmfile/pkg/version"
)
func TestAppendWaitForJobsFlags(t *testing.T) {
@ -432,6 +433,74 @@ func TestAppendTakeOwnershipFlags(t *testing.T) {
}
}
func TestAppendCRDFlags(t *testing.T) {
type args struct {
flags []string
helm helmexec.Interface
helmSpec HelmSpec
opt *DiffOpts
expected []string
}
tests := []struct {
name string
args args
}{
{
name: "no include-crds nor skip-crds provided",
args: args{
flags: []string{},
helm: testutil.NewVersionHelmExec(version.HelmRequiredVersion),
opt: &DiffOpts{},
expected: []string{},
},
},
{
name: "include-crds set but no skip-crds",
args: args{
flags: []string{},
helm: testutil.NewVersionHelmExec(version.HelmRequiredVersion),
opt: &DiffOpts{
SkipCRDs: false,
IncludeCRDs: true,
},
expected: []string{"--include-crds"},
},
},
{
name: "include-crds and skip-crds set",
args: args{
flags: []string{},
helm: testutil.NewVersionHelmExec(version.HelmRequiredVersion),
opt: &DiffOpts{
SkipCRDs: true,
IncludeCRDs: true,
},
expected: []string{"--skip-crds"},
},
},
{
name: "include-crds not set but skip-crds is",
args: args{
flags: []string{},
helm: testutil.NewVersionHelmExec(version.HelmRequiredVersion),
opt: &DiffOpts{
SkipCRDs: true,
IncludeCRDs: false,
},
expected: []string{"--skip-crds"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
st := &HelmState{}
st.HelmDefaults = tt.args.helmSpec
got := st.appendCRDFlags(tt.args.flags, tt.args.opt.SkipCRDs, tt.args.opt.IncludeCRDs)
require.Equalf(t, tt.args.expected, got, "appendCRDFlags() = %v, want %v", got, tt.args.expected)
})
}
}
func TestFormatLabels(t *testing.T) {
tests := []struct {
name string

65
pkg/testcmd/README.md Normal file
View File

@ -0,0 +1,65 @@
# Helmfile Test Command Package
## Overview
The `testcommand` package provides utilities for testing Helmfile commands in a controlled environment. This package simplifies the creation and configuration of command objects for testing purposes, allowing developers to verify command behavior without executing the full application.
## Components
### CommandTestHelper
The core structure that encapsulates the components needed for testing commands:
- `Cmd`: The Cobra command instance
- `Registry`: Flag registry for managing command flags
- `Options`: Command-specific options
### Available Test Commands
The package provides helper functions to create test instances of the following Helmfile commands:
- **TestDiffCmd()**: Creates a test instance of the `diff` command
- **TestApplyCmd()**: Creates a test instance of the `apply` command
- **TestTemplateCmd()**: Creates a test instance of the `template` command
- **TestSyncCmd()**: Creates a test instance of the `sync` command
## Usage
```go
import (
"testing"
"github.com/helmfile/helmfile/pkg/testcommand"
)
func TestMyDiffCommand(t *testing.T) {
// Create a test diff command
helper := testcommand.TestDiffCmd()
// Access the command components
cmd := helper.Cmd
options := helper.Options.(*config.DiffOptions)
// Set up test flags
cmd.Flags().Set("concurrency", "5")
// Test command behavior
// ...
}
```
## Implementation Details
Each test command function:
1. Creates an options factory for the specific command
2. Instantiates the command options
3. Gets the flag registry
4. Creates a Cobra command instance
5. Registers the appropriate flags
6. Returns a helper with all components
For the `diff` command, flag values are automatically transferred to the options object.
## Notes
This package is intended for testing purposes only and should not be used in production code.

111
pkg/testcmd/helper.go Normal file
View File

@ -0,0 +1,111 @@
package testcmd
import (
"github.com/spf13/cobra"
"github.com/helmfile/helmfile/pkg/config"
"github.com/helmfile/helmfile/pkg/factory"
"github.com/helmfile/helmfile/pkg/flags"
)
// CommandTestHelper provides utilities for testing commands
type CommandTestHelper struct {
Cmd *cobra.Command
Registry flags.FlagRegistry
Options config.Options
}
// TestDiffCmd creates a diff command for testing and returns a helper with its components
func TestDiffCmd() *CommandTestHelper {
// Create command components
optionsFactory := factory.NewDiffOptionsFactory()
options := optionsFactory.CreateOptions().(*config.DiffOptions)
registry := optionsFactory.GetFlagRegistry()
// Create command manually
cmd := &cobra.Command{
Use: "diff",
Short: "Diff releases defined in state file",
}
// Register flags
registry.RegisterFlags(cmd)
// Transfer flags to options
registry.TransferFlags(cmd, options)
return &CommandTestHelper{
Cmd: cmd,
Registry: registry,
Options: options,
}
}
// TestApplyCmd creates an apply command for testing and returns a helper with its components
func TestApplyCmd() *CommandTestHelper {
// Create command components
optionsFactory := factory.NewApplyOptionsFactory()
options := optionsFactory.CreateOptions().(*config.ApplyOptions)
registry := optionsFactory.GetFlagRegistry()
// Create command manually
cmd := &cobra.Command{
Use: "apply",
Short: "Apply all resources from state file only when there are changes",
}
// Register flags
registry.RegisterFlags(cmd)
return &CommandTestHelper{
Cmd: cmd,
Registry: registry,
Options: options,
}
}
// TestTemplateCmd creates a template command for testing and returns a helper with its components
func TestTemplateCmd() *CommandTestHelper {
// Create command components
optionsFactory := factory.NewTemplateOptionsFactory()
options := optionsFactory.CreateOptions().(*config.TemplateOptions)
registry := optionsFactory.GetFlagRegistry()
// Create command manually
cmd := &cobra.Command{
Use: "template",
Short: "Template releases defined in state file",
}
// Register flags
registry.RegisterFlags(cmd)
return &CommandTestHelper{
Cmd: cmd,
Registry: registry,
Options: options,
}
}
// TestSyncCmd creates a sync command for testing and returns a helper with its components
func TestSyncCmd() *CommandTestHelper {
// Create command components
optionsFactory := factory.NewSyncOptionsFactory()
options := optionsFactory.CreateOptions().(*config.SyncOptions)
registry := optionsFactory.GetFlagRegistry()
// Create command manually
cmd := &cobra.Command{
Use: "sync",
Short: "Sync all resources from state file",
}
// Register flags
registry.RegisterFlags(cmd)
return &CommandTestHelper{
Cmd: cmd,
Registry: registry,
Options: options,
}
}

91
pkg/testutil/README.md Normal file
View File

@ -0,0 +1,91 @@
# Helmfile Test Utilities
This package provides testing utilities for the Helmfile project, making it easier to write unit tests for Helm-related functionality.
## Overview
The `testutil` package contains:
1. Mock implementations for Helm execution
2. Utility functions for testing
## Components
### Mock Helm Executors
The package provides mock implementations of Helm executors that can be used in tests:
- `V3HelmExec`: A mock that can be configured to simulate Helm 3 behavior
- `VersionHelmExec`: A mock that can be configured with a specific Helm version
```go
// Create a mock for Helm 3
helmExec := testutil.NewV3HelmExec(true)
// Create a mock for a specific Helm version
versionExec := testutil.NewVersionHelmExec("3.8.0")
```
These mocks implement the Helm executor interface but will panic if any unexpected methods are called, making them useful for strict testing scenarios.
### Utility Functions
#### CaptureStdout
Captures stdout output during the execution of a function:
```go
output, err := testutil.CaptureStdout(func() {
fmt.Println("Hello, world!")
})
// output will contain "Hello, world!\n"
```
This is useful for testing functions that write to stdout.
## Usage Examples
### Testing with V3HelmExec
```go
func TestMyFunction(t *testing.T) {
// Create a mock Helm executor configured as Helm 3
helmExec := testutil.NewV3HelmExec(true)
// Use in your test
result := myFunctionThatChecksHelmVersion(helmExec)
// Assert that the result is as expected for Helm 3
assert.True(t, result)
}
```
### Testing with VersionHelmExec
```go
func TestVersionCompatibility(t *testing.T) {
// Create a mock with specific version
helmExec := testutil.NewVersionHelmExec("3.7.1")
// Test version comparison
assert.True(t, helmExec.IsVersionAtLeast("3.7.0"))
assert.False(t, helmExec.IsVersionAtLeast("3.8.0"))
}
```
### Capturing Output
```go
func TestOutputFunction(t *testing.T) {
output, err := testutil.CaptureStdout(func() {
MyFunctionThatPrintsOutput()
})
assert.NoError(t, err)
assert.Contains(t, output, "Expected output")
}
```
## Contributing
When adding new test utilities, please ensure they are well-documented and include appropriate tests.