feat(action): Refactors unit tests with better fakes

This also adds unit tests for the Atomic and Wait functionality

Signed-off-by: Taylor Thomas <taylor.thomas@microsoft.com>
pull/6011/head
Taylor Thomas 6 years ago
parent 93d07c862d
commit 29c343278e

469
Gopkg.lock generated

File diff suppressed because it is too large Load Diff

@ -113,11 +113,11 @@ $(GOIMPORTS):
# install vendored dependencies
vendor: Gopkg.lock
$(DEP) ensure -v --vendor-only
$(DEP) ensure --vendor-only
# update vendored dependencies
Gopkg.lock: Gopkg.toml
$(DEP) ensure -v --no-vendor
$(DEP) ensure --no-vendor
Gopkg.toml: $(DEP)

@ -31,7 +31,7 @@ import (
"helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/chartutil"
"helm.sh/helm/pkg/helmpath"
"helm.sh/helm/pkg/kube"
kubefake "helm.sh/helm/pkg/kube/fake"
"helm.sh/helm/pkg/release"
"helm.sh/helm/pkg/repo"
"helm.sh/helm/pkg/storage"
@ -116,7 +116,7 @@ func executeActionCommandC(store *storage.Storage, cmd string) (*cobra.Command,
actionConfig := &action.Configuration{
Releases: store,
KubeClient: &kube.PrintingKubeClient{Out: ioutil.Discard},
KubeClient: &kubefake.PrintingKubeClient{Out: ioutil.Discard},
Capabilities: chartutil.DefaultCapabilities,
Log: func(format string, v ...interface{}) {},
}

@ -28,7 +28,7 @@ import (
"helm.sh/helm/cmd/helm/require"
"helm.sh/helm/pkg/action"
"helm.sh/helm/pkg/chartutil"
"helm.sh/helm/pkg/kube"
kubefake "helm.sh/helm/pkg/kube/fake"
"helm.sh/helm/pkg/storage"
"helm.sh/helm/pkg/storage/driver"
)
@ -46,7 +46,7 @@ func newTemplateCmd(out io.Writer) *cobra.Command {
customConfig := &action.Configuration{
// Add mock objects in here so it doesn't use Kube API server
Releases: storage.Init(driver.NewMemory()),
KubeClient: &kube.PrintingKubeClient{Out: ioutil.Discard},
KubeClient: &kubefake.PrintingKubeClient{Out: ioutil.Discard},
Capabilities: chartutil.DefaultCapabilities,
Log: func(format string, v ...interface{}) {
fmt.Fprintf(out, format, v...)

@ -17,17 +17,15 @@ package action
import (
"flag"
"io"
"io/ioutil"
"testing"
"time"
"github.com/pkg/errors"
fakeclientset "k8s.io/client-go/kubernetes/fake"
"helm.sh/helm/pkg/chart"
"helm.sh/helm/pkg/chartutil"
"helm.sh/helm/pkg/kube"
kubefake "helm.sh/helm/pkg/kube/fake"
"helm.sh/helm/pkg/release"
"helm.sh/helm/pkg/storage"
"helm.sh/helm/pkg/storage/driver"
@ -40,7 +38,7 @@ func actionConfigFixture(t *testing.T) *Configuration {
return &Configuration{
Releases: storage.Init(driver.NewMemory()),
KubeClient: &kube.PrintingKubeClient{Out: ioutil.Discard},
KubeClient: &kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: ioutil.Discard}},
Capabilities: chartutil.DefaultCapabilities,
Log: func(format string, v ...interface{}) {
t.Helper()
@ -55,7 +53,7 @@ var manifestWithHook = `kind: ConfigMap
metadata:
name: test-cm
annotations:
"helm.sh/hook": post-install,pre-delete
"helm.sh/hook": post-install,pre-delete,post-upgrade
data:
name: value`
@ -175,20 +173,6 @@ func namedReleaseStub(name string, status release.Status) *release.Release {
}
}
func newHookFailingKubeClient() *hookFailingKubeClient {
return &hookFailingKubeClient{
PrintingKubeClient: kube.PrintingKubeClient{Out: ioutil.Discard},
}
}
type hookFailingKubeClient struct {
kube.PrintingKubeClient
}
func (h *hookFailingKubeClient) WatchUntilReady(r io.Reader, timeout time.Duration) error {
return errors.New("Failed watch")
}
func TestGetVersionSet(t *testing.T) {
client := fakeclientset.NewSimpleClientset()

@ -226,15 +226,15 @@ func (i *Install) Run(chrt *chart.Chart) (*release.Release, error) {
func (i *Install) failRelease(rel *release.Release, err error) (*release.Release, error) {
rel.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", i.ReleaseName, err.Error()))
if i.Atomic {
i.cfg.Log("Install failed and atomic is set, purging release")
i.cfg.Log("Install failed and atomic is set, uninstalling release")
uninstall := NewUninstall(i.cfg)
uninstall.DisableHooks = i.DisableHooks
uninstall.KeepHistory = false
uninstall.Timeout = i.Timeout
if _, uninstallErr := uninstall.Run(i.ReleaseName); uninstallErr != nil {
return rel, errors.Wrapf(uninstallErr, "an error occurred while purging the release. original install error: %s", err)
return rel, errors.Wrapf(uninstallErr, "an error occurred while uninstalling the release. original install error: %s", err)
}
return rel, errors.Wrapf(err, "release %s failed, and has been purged due to atomic being set", i.ReleaseName)
return rel, errors.Wrapf(err, "release %s failed, and has been uninstalled due to atomic being set", i.ReleaseName)
}
i.recordRelease(rel) // Ignore the error, since we have another error to deal with.
return rel, err

@ -29,6 +29,8 @@ import (
"github.com/stretchr/testify/assert"
"helm.sh/helm/pkg/release"
"helm.sh/helm/pkg/storage/driver"
kubefake "helm.sh/helm/pkg/kube/fake"
)
type nameTemplateTestCase struct {
@ -188,7 +190,9 @@ func TestInstallRelease_FailedHooks(t *testing.T) {
is := assert.New(t)
instAction := installAction(t)
instAction.ReleaseName = "failed-hooks"
instAction.cfg.KubeClient = newHookFailingKubeClient()
failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
failer.WatchUntilReadyError = fmt.Errorf("Failed watch")
instAction.cfg.KubeClient = failer
instAction.rawValues = map[string]interface{}{}
res, err := instAction.Run(buildChart())
@ -236,11 +240,60 @@ func TestInstallRelease_KubeVersion(t *testing.T) {
}
func TestInstallRelease_Wait(t *testing.T) {
t.Fail("Implement me")
is := assert.New(t)
instAction := installAction(t)
instAction.ReleaseName = "come-fail-away"
failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
failer.WaitError = fmt.Errorf("I timed out")
instAction.cfg.KubeClient = failer
instAction.Wait = true
instAction.rawValues = map[string]interface{}{}
res, err := instAction.Run(buildChart())
is.Error(err)
is.Contains(res.Info.Description, "I timed out")
is.Equal(res.Info.Status, release.StatusFailed)
}
func TestInstallRelease_Atomic(t *testing.T) {
t.Fail("Implement me")
is := assert.New(t)
t.Run("atomic uninstall succeeds", func(t *testing.T) {
instAction := installAction(t)
instAction.ReleaseName = "come-fail-away"
failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
failer.WaitError = fmt.Errorf("I timed out")
instAction.cfg.KubeClient = failer
instAction.Atomic = true
instAction.rawValues = map[string]interface{}{}
res, err := instAction.Run(buildChart())
is.Error(err)
is.Contains(err.Error(), "I timed out")
is.Contains(err.Error(), "atomic")
// Now make sure it isn't in storage any more
_, err = instAction.cfg.Releases.Get(res.Name, res.Version)
is.Error(err)
is.Equal(err, driver.ErrReleaseNotFound)
})
t.Run("atomic uninstall fails", func(t *testing.T) {
instAction := installAction(t)
instAction.ReleaseName = "come-fail-away-with-me"
failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
failer.WaitError = fmt.Errorf("I timed out")
failer.DeleteError = fmt.Errorf("uninstall fail")
instAction.cfg.KubeClient = failer
instAction.Atomic = true
instAction.rawValues = map[string]interface{}{}
_, err := instAction.Run(buildChart())
is.Error(err)
is.Contains(err.Error(), "I timed out")
is.Contains(err.Error(), "uninstall fail")
is.Contains(err.Error(), "an error occurred while uninstalling the release")
})
}
func TestNameTemplate(t *testing.T) {

@ -1,3 +1,19 @@
/*
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 action
import (

@ -122,7 +122,16 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
if !u.KeepHistory {
u.cfg.Log("purge requested for %s", name)
err := u.purgeReleases(rels...)
return res, errors.Wrap(err, "uninstall: Failed to purge the release")
if err != nil {
errs = append(errs, errors.Wrap(err, "uninstall: Failed to purge the release"))
}
// Return the errors that occurred while deleting the release, if any
if len(errs) > 0 {
return res, errors.Errorf("uninstallation completed with %d error(s): %s", len(errs), joinErrors(errs))
}
return res, nil
}
if err := u.cfg.Releases.Update(rel); err != nil {

@ -257,7 +257,9 @@ func (u *Upgrade) failRelease(rel *release.Release, err error) (*release.Release
// There isn't a way to tell if a previous release was successful, but
// generally failed releases do not get superseded unless the next
// release is successful, so this should be relatively safe
filteredHistory := releaseutil.StatusFilter(release.StatusSuperseded).Filter(fullHistory)
filteredHistory := releaseutil.FilterFunc(func(r *release.Release) bool {
return r.Info.Status == release.StatusSuperseded || r.Info.Status == release.StatusDeployed
}).Filter(fullHistory)
if len(filteredHistory) == 0 {
return rel, errors.Wrap(err, "unable to find a previously successful release when attempting to rollback. original upgrade error")
}
@ -266,13 +268,12 @@ func (u *Upgrade) failRelease(rel *release.Release, err error) (*release.Release
rollin := NewRollback(u.cfg)
rollin.Version = filteredHistory[0].Version
rollin.Wait = u.Wait
rollin.Wait = true
rollin.DisableHooks = u.DisableHooks
rollin.Recreate = u.Recreate
rollin.Force = u.Force
rollin.Timeout = u.Timeout
if _, rollErr := rollin.Run(rel.Name); err != nil {
if _, rollErr := rollin.Run(rel.Name); rollErr != nil {
return rel, errors.Wrapf(rollErr, "an error occurred while rolling back the release. original upgrade error: %s", err)
}
return rel, errors.Wrapf(err, "release %s failed, and has been rolled back due to atomic being set", rel.Name)
@ -347,7 +348,6 @@ func (u *Upgrade) execHook(hs []*release.Hook, hook string) error {
}
sort.Sort(hookByWeight(executingHooks))
for _, h := range executingHooks {
if err := deleteHookByPolicy(u.cfg, h, hooks.BeforeHookCreation); err != nil {
return err

@ -0,0 +1,109 @@
/*
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 action
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
kubefake "helm.sh/helm/pkg/kube/fake"
"helm.sh/helm/pkg/release"
)
func upgradeAction(t *testing.T) *Upgrade {
config := actionConfigFixture(t)
upAction := NewUpgrade(config)
upAction.Namespace = "spaced"
return upAction
}
func TestUpgradeRelease_Wait(t *testing.T) {
is := assert.New(t)
req := require.New(t)
upAction := upgradeAction(t)
rel := releaseStub()
rel.Name = "come-fail-away"
rel.Info.Status = release.StatusDeployed
upAction.cfg.Releases.Create(rel)
failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
failer.WaitError = fmt.Errorf("I timed out")
upAction.cfg.KubeClient = failer
upAction.Wait = true
upAction.rawValues = map[string]interface{}{}
res, err := upAction.Run(rel.Name, buildChart())
req.Error(err)
is.Contains(res.Info.Description, "I timed out")
is.Equal(res.Info.Status, release.StatusFailed)
}
func TestUpgradeRelease_Atomic(t *testing.T) {
is := assert.New(t)
req := require.New(t)
t.Run("atomic rollback succeeds", func(t *testing.T) {
upAction := upgradeAction(t)
rel := releaseStub()
rel.Name = "nuketown"
rel.Info.Status = release.StatusDeployed
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
upAction.rawValues = map[string]interface{}{}
res, err := upAction.Run(rel.Name, buildChart())
req.Error(err)
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)
})
t.Run("atomic uninstall fails", func(t *testing.T) {
upAction := upgradeAction(t)
rel := releaseStub()
rel.Name = "fallout"
rel.Info.Status = release.StatusDeployed
upAction.cfg.Releases.Create(rel)
failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
failer.UpdateError = fmt.Errorf("update fail")
upAction.cfg.KubeClient = failer
upAction.Atomic = true
upAction.rawValues = map[string]interface{}{}
_, err := upAction.Run(rel.Name, buildChart())
req.Error(err)
is.Contains(err.Error(), "update fail")
is.Contains(err.Error(), "an error occurred while rolling back the release")
})
}

@ -0,0 +1,116 @@
/*
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 fake implements various fake KubeClients for use in testing
package fake
import (
"io"
"time"
v1 "k8s.io/api/core/v1"
"k8s.io/cli-runtime/pkg/resource"
"helm.sh/helm/pkg/kube"
)
// FailingKubeClient implements KubeClient for testing purposes. It also has
// additional errors you can set to fail different functions, otherwise it
// delegates all its calls to `PrintingKubeClient`
type FailingKubeClient struct {
PrintingKubeClient
CreateError error
WaitError error
GetError error
DeleteError error
WatchUntilReadyError error
UpdateError error
BuildError error
BuildUnstructuredError error
WaitAndGetCompletedPodPhaseError error
}
// Create returns the configured error if set or prints
func (f *FailingKubeClient) Create(r io.Reader) error {
if f.CreateError != nil {
return f.CreateError
}
return f.PrintingKubeClient.Create(r)
}
// Wait returns the configured error if set or prints
func (f *FailingKubeClient) Wait(r io.Reader, d time.Duration) error {
if f.WaitError != nil {
return f.WaitError
}
return f.PrintingKubeClient.Wait(r, d)
}
// Create returns the configured error if set or prints
func (f *FailingKubeClient) Get(r io.Reader) (string, error) {
if f.GetError != nil {
return "", f.GetError
}
return f.PrintingKubeClient.Get(r)
}
// Delete returns the configured error if set or prints
func (f *FailingKubeClient) Delete(r io.Reader) error {
if f.DeleteError != nil {
return f.DeleteError
}
return f.PrintingKubeClient.Delete(r)
}
// WatchUntilReady returns the configured error if set or prints
func (f *FailingKubeClient) WatchUntilReady(r io.Reader, d time.Duration) error {
if f.WatchUntilReadyError != nil {
return f.WatchUntilReadyError
}
return f.PrintingKubeClient.WatchUntilReady(r, d)
}
// Update returns the configured error if set or prints
func (f *FailingKubeClient) Update(r, modifiedReader io.Reader, not, needed bool) error {
if f.UpdateError != nil {
return f.UpdateError
}
return f.PrintingKubeClient.Update(r, modifiedReader, not, needed)
}
// Build returns the configured error if set or prints
func (f *FailingKubeClient) Build(r io.Reader) (kube.Result, error) {
if f.BuildError != nil {
return []*resource.Info{}, f.BuildError
}
return f.PrintingKubeClient.Build(r)
}
// BuildUnstructured returns the configured error if set or prints
func (f *FailingKubeClient) BuildUnstructured(r io.Reader) (kube.Result, error) {
if f.BuildUnstructuredError != nil {
return []*resource.Info{}, f.BuildUnstructuredError
}
return f.PrintingKubeClient.Build(r)
}
// WaitAndGetCompletedPodPhase returns the configured error if set or prints
func (f *FailingKubeClient) WaitAndGetCompletedPodPhase(s string, d time.Duration) (v1.PodPhase, error) {
if f.WaitAndGetCompletedPodPhaseError != nil {
return v1.PodSucceeded, f.WaitAndGetCompletedPodPhaseError
}
return f.PrintingKubeClient.WaitAndGetCompletedPodPhase(s, d)
}

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package kube
package fake
import (
"io"
@ -22,6 +22,8 @@ import (
v1 "k8s.io/api/core/v1"
"k8s.io/cli-runtime/pkg/resource"
"helm.sh/helm/pkg/kube"
)
// PrintingKubeClient implements KubeClient, but simply prints the reader to
@ -68,11 +70,11 @@ func (p *PrintingKubeClient) Update(_, modifiedReader io.Reader, _, _ bool) erro
}
// Build implements KubeClient Build.
func (p *PrintingKubeClient) Build(_ io.Reader) (Result, error) {
func (p *PrintingKubeClient) Build(_ io.Reader) (kube.Result, error) {
return []*resource.Info{}, nil
}
func (p *PrintingKubeClient) BuildUnstructured(_ io.Reader) (Result, error) {
func (p *PrintingKubeClient) BuildUnstructured(_ io.Reader) (kube.Result, error) {
return p.Build(nil)
}
Loading…
Cancel
Save