func TestInstallReleaseReuseName(t *testing.T) { c := helm.NewContext() rs := rsFixture() rel := releaseStub() rel.Info.Status.Code = release.Status_DELETED rs.env.Releases.Create(rel) req := &services.InstallReleaseRequest{ Chart: chartStub(), ReuseName: true, Name: rel.Name, } res, err := rs.InstallRelease(c, req) if err != nil { t.Fatalf("Failed install: %s", err) } if res.Release.Name != rel.Name { t.Errorf("expected %q, got %q", rel.Name, res.Release.Name) } getreq := &services.GetReleaseStatusRequest{Name: rel.Name, Version: 1} getres, err := rs.GetReleaseStatus(c, getreq) if err != nil { t.Errorf("Failed to retrieve release: %s", err) } if getres.Info.Status.Code != release.Status_DEPLOYED { t.Errorf("Release status is %q", getres.Info.Status.Code) } }
func TestGetHistory_WithNoRevisions(t *testing.T) { tests := []struct { desc string req *tpb.GetHistoryRequest }{ { desc: "get release with no history", req: &tpb.GetHistoryRequest{Name: "sad-panda", Max: 256}, }, } // create release 'sad-panda' with no revision history rls := namedReleaseStub("sad-panda", rpb.Status_DEPLOYED) srv := rsFixture() srv.env.Releases.Create(rls) for _, tt := range tests { res, err := srv.GetHistory(helm.NewContext(), tt.req) if err != nil { t.Fatalf("%s:\nFailed to get History of %q: %s", tt.desc, tt.req.Name, err) } if len(res.Releases) > 1 { t.Fatalf("%s:\nExpected zero items, got %d", tt.desc, len(res.Releases)) } } }
func TestUpdateReleaseNoHooks(t *testing.T) { c := helm.NewContext() rs := rsFixture() rel := releaseStub() rs.env.Releases.Create(rel) req := &services.UpdateReleaseRequest{ Name: rel.Name, DisableHooks: true, Chart: &chart.Chart{ Metadata: &chart.Metadata{Name: "hello"}, Templates: []*chart.Template{ {Name: "hello", Data: []byte("hello: world")}, {Name: "hooks", Data: []byte(manifestWithUpgradeHooks)}, }, }, } res, err := rs.UpdateRelease(c, req) if err != nil { t.Fatalf("Failed updated: %s", err) } if hl := res.Release.Hooks[0].LastRun; hl != nil { t.Errorf("Expected that no hooks were run. Got %d", hl) } }
func TestUninstallPurgeRelease(t *testing.T) { c := helm.NewContext() rs := rsFixture() rs.env.Releases.Create(releaseStub()) req := &services.UninstallReleaseRequest{ Name: "angry-panda", Purge: true, } res, err := rs.UninstallRelease(c, req) if err != nil { t.Fatalf("Failed uninstall: %s", err) } if res.Release.Name != "angry-panda" { t.Errorf("Expected angry-panda, got %q", res.Release.Name) } if res.Release.Info.Status.Code != release.Status_DELETED { t.Errorf("Expected status code to be DELETED, got %d", res.Release.Info.Status.Code) } if res.Release.Hooks[0].LastRun.Seconds == 0 { t.Error("Expected LastRun to be greater than zero.") } if res.Release.Info.Deleted.Seconds <= 0 { t.Errorf("Expected valid UNIX date, got %d", res.Release.Info.Deleted.Seconds) } }
func TestRollbackReleaseNoHooks(t *testing.T) { c := helm.NewContext() rs := rsFixture() rel := releaseStub() rel.Hooks = []*release.Hook{ { Name: "test-cm", Kind: "ConfigMap", Path: "test-cm", Manifest: manifestWithRollbackHooks, Events: []release.Hook_Event{ release.Hook_PRE_ROLLBACK, release.Hook_POST_ROLLBACK, }, }, } rs.env.Releases.Create(rel) upgradedRel := upgradeReleaseVersion(rel) rs.env.Releases.Update(rel) rs.env.Releases.Create(upgradedRel) req := &services.RollbackReleaseRequest{ Name: rel.Name, DisableHooks: true, } res, err := rs.RollbackRelease(c, req) if err != nil { t.Fatalf("Failed rollback: %s", err) } if hl := res.Release.Hooks[0].LastRun; hl != nil { t.Errorf("Expected that no hooks were run. Got %d", hl) } }
func TestRollbackReleaseFailure(t *testing.T) { c := helm.NewContext() rs := rsFixture() rel := releaseStub() rs.env.Releases.Create(rel) upgradedRel := upgradeReleaseVersion(rel) rs.env.Releases.Update(rel) rs.env.Releases.Create(upgradedRel) req := &services.RollbackReleaseRequest{ Name: rel.Name, DisableHooks: true, } rs.env.KubeClient = newUpdateFailingKubeClient() res, err := rs.RollbackRelease(c, req) if err == nil { t.Error("Expected failed rollback") } if targetStatus := res.Release.Info.Status.Code; targetStatus != release.Status_FAILED { t.Errorf("Expected FAILED release. Got %v", targetStatus) } oldRelease, err := rs.env.Releases.Get(rel.Name, rel.Version) if err != nil { t.Errorf("Expected to be able to get previous release") } if oldStatus := oldRelease.Info.Status.Code; oldStatus != release.Status_SUPERSEDED { t.Errorf("Expected SUPERSEDED status on previous Release version. Got %v", oldStatus) } }
func TestUpdateReleaseFailure(t *testing.T) { c := helm.NewContext() rs := rsFixture() rel := releaseStub() rs.env.Releases.Create(rel) rs.env.KubeClient = newUpdateFailingKubeClient() req := &services.UpdateReleaseRequest{ Name: rel.Name, DisableHooks: true, Chart: &chart.Chart{ Metadata: &chart.Metadata{Name: "hello"}, Templates: []*chart.Template{ {Name: "something", Data: []byte("hello: world")}, }, }, } res, err := rs.UpdateRelease(c, req) if err == nil { t.Error("Expected failed update") } if updatedStatus := res.Release.Info.Status.Code; updatedStatus != release.Status_FAILED { t.Errorf("Expected FAILED release. Got %d", updatedStatus) } oldRelease, err := rs.env.Releases.Get(rel.Name, rel.Version) if err != nil { t.Errorf("Expected to be able to get previous release") } if oldStatus := oldRelease.Info.Status.Code; oldStatus != release.Status_SUPERSEDED { t.Errorf("Expected SUPERSEDED status on previous Release version. Got %v", oldStatus) } }
func TestUninstallReleaseWithKeepPolicy(t *testing.T) { c := helm.NewContext() rs := rsFixture() name := "angry-bunny" rs.env.Releases.Create(releaseWithKeepStub(name)) req := &services.UninstallReleaseRequest{ Name: name, } res, err := rs.UninstallRelease(c, req) if err != nil { t.Fatalf("Failed uninstall: %s", err) } if res.Release.Name != name { t.Errorf("Expected angry-bunny, got %q", res.Release.Name) } if res.Release.Info.Status.Code != release.Status_DELETED { t.Errorf("Expected status code to be DELETED, got %d", res.Release.Info.Status.Code) } if res.Info == "" { t.Errorf("Expected response info to not be empty") } else { if !strings.Contains(res.Info, "[ConfigMap] test-cm-keep") { t.Errorf("unexpected output: %s", res.Info) } } }
func TestInstallRelease(t *testing.T) { c := helm.NewContext() rs := rsFixture() // TODO: Refactor this into a mock. req := &services.InstallReleaseRequest{ Namespace: "spaced", Chart: &chart.Chart{ Metadata: &chart.Metadata{Name: "hello"}, Templates: []*chart.Template{ {Name: "hello", Data: []byte("hello: world")}, {Name: "hooks", Data: []byte(manifestWithHook)}, }, }, } res, err := rs.InstallRelease(c, req) if err != nil { t.Fatalf("Failed install: %s", err) } if res.Release.Name == "" { t.Errorf("Expected release name.") } if res.Release.Namespace != "spaced" { t.Errorf("Expected release namespace 'spaced', got '%s'.", res.Release.Namespace) } rel, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) if err != nil { t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) } t.Logf("rel: %v", rel) if len(rel.Hooks) != 1 { t.Fatalf("Expected 1 hook, got %d", len(rel.Hooks)) } if rel.Hooks[0].Manifest != manifestWithHook { t.Errorf("Unexpected manifest: %v", rel.Hooks[0].Manifest) } if rel.Hooks[0].Events[0] != release.Hook_POST_INSTALL { t.Errorf("Expected event 0 is post install") } if rel.Hooks[0].Events[1] != release.Hook_PRE_DELETE { t.Errorf("Expected event 0 is pre-delete") } if len(res.Release.Manifest) == 0 { t.Errorf("No manifest returned: %v", res.Release) } if len(rel.Manifest) == 0 { t.Errorf("Expected manifest in %v", res) } if !strings.Contains(rel.Manifest, "---\n# Source: hello/hello\nhello: world") { t.Errorf("unexpected output: %s", rel.Manifest) } }
func TestUninstallPurgeRelease(t *testing.T) { c := helm.NewContext() rs := rsFixture() rel := releaseStub() rs.env.Releases.Create(rel) upgradedRel := upgradeReleaseVersion(rel) rs.env.Releases.Update(rel) rs.env.Releases.Create(upgradedRel) req := &services.UninstallReleaseRequest{ Name: "angry-panda", Purge: true, } res, err := rs.UninstallRelease(c, req) if err != nil { t.Fatalf("Failed uninstall: %s", err) } if res.Release.Name != "angry-panda" { t.Errorf("Expected angry-panda, got %q", res.Release.Name) } if res.Release.Info.Status.Code != release.Status_DELETED { t.Errorf("Expected status code to be DELETED, got %d", res.Release.Info.Status.Code) } if res.Release.Info.Deleted.Seconds <= 0 { t.Errorf("Expected valid UNIX date, got %d", res.Release.Info.Deleted.Seconds) } rels, err := rs.GetHistory(helm.NewContext(), &services.GetHistoryRequest{Name: "angry-panda"}) if err != nil { t.Fatal(err) } if len(rels.Releases) != 0 { t.Errorf("Expected no releases in storage, got %d", len(rels.Releases)) } }
func TestInstallReleaseDryRun(t *testing.T) { c := helm.NewContext() rs := rsFixture() req := &services.InstallReleaseRequest{ Chart: chartStub(), DryRun: true, } res, err := rs.InstallRelease(c, req) if err != nil { t.Errorf("Failed install: %s", err) } if res.Release.Name == "" { t.Errorf("Expected release name.") } if !strings.Contains(res.Release.Manifest, "---\n# Source: hello/hello\nhello: world") { t.Errorf("unexpected output: %s", res.Release.Manifest) } if !strings.Contains(res.Release.Manifest, "---\n# Source: hello/goodbye\ngoodbye: world") { t.Errorf("unexpected output: %s", res.Release.Manifest) } if !strings.Contains(res.Release.Manifest, "hello: Earth") { t.Errorf("Should contain partial content. %s", res.Release.Manifest) } if strings.Contains(res.Release.Manifest, "hello: {{ template \"_planet\" . }}") { t.Errorf("Should not contain partial templates itself. %s", res.Release.Manifest) } if strings.Contains(res.Release.Manifest, "empty") { t.Errorf("Should not contain template data for an empty file. %s", res.Release.Manifest) } if _, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version); err == nil { t.Errorf("Expected no stored release.") } if l := len(res.Release.Hooks); l != 1 { t.Fatalf("Expected 1 hook, got %d", l) } if res.Release.Hooks[0].LastRun != nil { t.Error("Expected hook to not be marked as run.") } }
func TestGetReleaseStatus(t *testing.T) { c := helm.NewContext() rs := rsFixture() rel := releaseStub() if err := rs.env.Releases.Create(rel); err != nil { t.Fatalf("Could not store mock release: %s", err) } res, err := rs.GetReleaseStatus(c, &services.GetReleaseStatusRequest{Name: rel.Name, Version: 1}) if err != nil { t.Errorf("Error getting release content: %s", err) } if res.Info.Status.Code != release.Status_DEPLOYED { t.Errorf("Expected %d, got %d", release.Status_DEPLOYED, res.Info.Status.Code) } }
func TestGetReleaseContent(t *testing.T) { c := helm.NewContext() rs := rsFixture() rel := releaseStub() if err := rs.env.Releases.Create(rel); err != nil { t.Fatalf("Could not store mock release: %s", err) } res, err := rs.GetReleaseContent(c, &services.GetReleaseContentRequest{Name: rel.Name, Version: 1}) if err != nil { t.Errorf("Error getting release content: %s", err) } if res.Release.Chart.Metadata.Name != rel.Chart.Metadata.Name { t.Errorf("Expected %q, got %q", rel.Chart.Metadata.Name, res.Release.Chart.Metadata.Name) } }
func TestUpdateReleaseNoChanges(t *testing.T) { c := helm.NewContext() rs := rsFixture() rel := releaseStub() rs.env.Releases.Create(rel) req := &services.UpdateReleaseRequest{ Name: rel.Name, DisableHooks: true, Chart: rel.GetChart(), } _, err := rs.UpdateRelease(c, req) if err != nil { t.Fatalf("Failed updated: %s", err) } }
func TestInstallReleaseWithChartAndDependencyNotes(t *testing.T) { c := helm.NewContext() rs := rsFixture() // TODO: Refactor this into a mock. req := &services.InstallReleaseRequest{ Namespace: "spaced", Chart: &chart.Chart{ Metadata: &chart.Metadata{Name: "hello"}, Templates: []*chart.Template{ {Name: "templates/hello", Data: []byte("hello: world")}, {Name: "templates/hooks", Data: []byte(manifestWithHook)}, {Name: "templates/NOTES.txt", Data: []byte(notesText)}, }, Dependencies: []*chart.Chart{ { Metadata: &chart.Metadata{Name: "hello"}, Templates: []*chart.Template{ {Name: "templates/hello", Data: []byte("hello: world")}, {Name: "templates/hooks", Data: []byte(manifestWithHook)}, {Name: "templates/NOTES.txt", Data: []byte(notesText + " child")}, }, }, }, }, } res, err := rs.InstallRelease(c, req) if err != nil { t.Fatalf("Failed install: %s", err) } if res.Release.Name == "" { t.Errorf("Expected release name.") } rel, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) if err != nil { t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) } t.Logf("rel: %v", rel) if rel.Info.Status.Notes != notesText { t.Fatalf("Expected '%s', got '%s'", notesText, rel.Info.Status.Notes) } }
func TestInstallReleaseFailedHooks(t *testing.T) { c := helm.NewContext() rs := rsFixture() rs.env.Releases.Create(releaseStub()) rs.env.KubeClient = newHookFailingKubeClient() req := &services.InstallReleaseRequest{ Chart: chartStub(), } res, err := rs.InstallRelease(c, req) if err == nil { t.Error("Expected failed install") } if hl := res.Release.Info.Status.Code; hl != release.Status_FAILED { t.Errorf("Expected FAILED release. Got %d", hl) } }
func TestInstallReleaseNoHooks(t *testing.T) { c := helm.NewContext() rs := rsFixture() rs.env.Releases.Create(releaseStub()) req := &services.InstallReleaseRequest{ Chart: chartStub(), DisableHooks: true, } res, err := rs.InstallRelease(c, req) if err != nil { t.Errorf("Failed install: %s", err) } if hl := res.Release.Hooks[0].LastRun; hl != nil { t.Errorf("Expected that no hooks were run. Got %d", hl) } }
func TestUninstallReleaseNoHooks(t *testing.T) { c := helm.NewContext() rs := rsFixture() rs.env.Releases.Create(releaseStub()) req := &services.UninstallReleaseRequest{ Name: "angry-panda", DisableHooks: true, } res, err := rs.UninstallRelease(c, req) if err != nil { t.Errorf("Failed uninstall: %s", err) } // The default value for a protobuf timestamp is nil. if res.Release.Hooks[0].LastRun != nil { t.Errorf("Expected LastRun to be zero, got %d.", res.Release.Hooks[0].LastRun.Seconds) } }
func TestRollbackWithReleaseVersion(t *testing.T) { c := helm.NewContext() rs := rsFixture() rel := releaseStub() rs.env.Releases.Create(rel) upgradedRel := upgradeReleaseVersion(rel) rs.env.Releases.Update(rel) rs.env.Releases.Create(upgradedRel) req := &services.RollbackReleaseRequest{ Name: rel.Name, DisableHooks: true, Version: 1, } _, err := rs.RollbackRelease(c, req) if err != nil { t.Fatalf("Failed rollback: %s", err) } }
func TestUninstallPurgeDeleteRelease(t *testing.T) { c := helm.NewContext() rs := rsFixture() rs.env.Releases.Create(releaseStub()) req := &services.UninstallReleaseRequest{ Name: "angry-panda", } _, err := rs.UninstallRelease(c, req) if err != nil { t.Fatalf("Failed uninstall: %s", err) } req2 := &services.UninstallReleaseRequest{ Name: "angry-panda", Purge: true, } _, err2 := rs.UninstallRelease(c, req2) if err2 != nil && err2.Error() != "'angry-panda' has no deployed releases" { t.Errorf("Failed uninstall: %s", err2) } }
func TestUpdateRelease(t *testing.T) { c := helm.NewContext() rs := rsFixture() rel := releaseStub() rs.env.Releases.Create(rel) req := &services.UpdateReleaseRequest{ Name: rel.Name, Chart: &chart.Chart{ Metadata: &chart.Metadata{Name: "hello"}, Templates: []*chart.Template{ {Name: "hello", Data: []byte("hello: world")}, {Name: "hooks", Data: []byte(manifestWithUpgradeHooks)}, }, }, } res, err := rs.UpdateRelease(c, req) if err != nil { t.Fatalf("Failed updated: %s", err) } if res.Release.Name == "" { t.Errorf("Expected release name.") } if res.Release.Name != rel.Name { t.Errorf("Updated release name does not match previous release name. Expected %s, got %s", rel.Name, res.Release.Name) } if res.Release.Namespace != rel.Namespace { t.Errorf("Expected release namespace '%s', got '%s'.", rel.Namespace, res.Release.Namespace) } updated, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) if err != nil { t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) } if len(updated.Hooks) != 1 { t.Fatalf("Expected 1 hook, got %d", len(updated.Hooks)) } if updated.Hooks[0].Manifest != manifestWithUpgradeHooks { t.Errorf("Unexpected manifest: %v", updated.Hooks[0].Manifest) } if updated.Hooks[0].Events[0] != release.Hook_POST_UPGRADE { t.Errorf("Expected event 0 to be post upgrade") } if updated.Hooks[0].Events[1] != release.Hook_PRE_UPGRADE { t.Errorf("Expected event 0 to be pre upgrade") } if len(res.Release.Manifest) == 0 { t.Errorf("No manifest returned: %v", res.Release) } if len(updated.Manifest) == 0 { t.Errorf("Expected manifest in %v", res) } if !strings.Contains(updated.Manifest, "---\n# Source: hello/hello\nhello: world") { t.Errorf("unexpected output: %s", rel.Manifest) } if res.Release.Version != 2 { t.Errorf("Expected release version to be %v, got %v", 2, res.Release.Version) } }
func TestGetHistory_WithRevisions(t *testing.T) { mk := func(name string, vers int32, code rpb.Status_Code) *rpb.Release { return &rpb.Release{ Name: name, Version: vers, Info: &rpb.Info{Status: &rpb.Status{Code: code}}, } } // GetReleaseHistoryTests tests := []struct { desc string req *tpb.GetHistoryRequest res *tpb.GetHistoryResponse }{ { desc: "get release with history and default limit (max=256)", req: &tpb.GetHistoryRequest{Name: "angry-bird", Max: 256}, res: &tpb.GetHistoryResponse{Releases: []*rpb.Release{ mk("angry-bird", 4, rpb.Status_DEPLOYED), mk("angry-bird", 3, rpb.Status_SUPERSEDED), mk("angry-bird", 2, rpb.Status_SUPERSEDED), mk("angry-bird", 1, rpb.Status_SUPERSEDED), }}, }, { desc: "get release with history using result limit (max=2)", req: &tpb.GetHistoryRequest{Name: "angry-bird", Max: 2}, res: &tpb.GetHistoryResponse{Releases: []*rpb.Release{ mk("angry-bird", 4, rpb.Status_DEPLOYED), mk("angry-bird", 3, rpb.Status_SUPERSEDED), }}, }, } // test release history for release 'angry-bird' hist := []*rpb.Release{ mk("angry-bird", 4, rpb.Status_DEPLOYED), mk("angry-bird", 3, rpb.Status_SUPERSEDED), mk("angry-bird", 2, rpb.Status_SUPERSEDED), mk("angry-bird", 1, rpb.Status_SUPERSEDED), } srv := rsFixture() for _, rls := range hist { if err := srv.env.Releases.Create(rls); err != nil { t.Fatalf("Failed to create release: %s", err) } } // run tests for _, tt := range tests { res, err := srv.GetHistory(helm.NewContext(), tt.req) if err != nil { t.Fatalf("%s:\nFailed to get History of %q: %s", tt.desc, tt.req.Name, err) } if !reflect.DeepEqual(res, tt.res) { t.Fatalf("%s:\nExpected:\n\t%+v\nActual\n\t%+v", tt.desc, tt.res, res) } } }
func (l *mockListServer) Context() context.Context { return helm.NewContext() }
func TestRollbackRelease(t *testing.T) { c := helm.NewContext() rs := rsFixture() rel := releaseStub() rs.env.Releases.Create(rel) upgradedRel := upgradeReleaseVersion(rel) upgradedRel.Hooks = []*release.Hook{ { Name: "test-cm", Kind: "ConfigMap", Path: "test-cm", Manifest: manifestWithRollbackHooks, Events: []release.Hook_Event{ release.Hook_PRE_ROLLBACK, release.Hook_POST_ROLLBACK, }, }, } upgradedRel.Manifest = "hello world" rs.env.Releases.Update(rel) rs.env.Releases.Create(upgradedRel) req := &services.RollbackReleaseRequest{ Name: rel.Name, } res, err := rs.RollbackRelease(c, req) if err != nil { t.Fatalf("Failed rollback: %s", err) } if res.Release.Name == "" { t.Errorf("Expected release name.") } if res.Release.Name != rel.Name { t.Errorf("Updated release name does not match previous release name. Expected %s, got %s", rel.Name, res.Release.Name) } if res.Release.Namespace != rel.Namespace { t.Errorf("Expected release namespace '%s', got '%s'.", rel.Namespace, res.Release.Namespace) } if res.Release.Version != 3 { t.Errorf("Expected release version to be %v, got %v", 3, res.Release.Version) } updated, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) if err != nil { t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) } if len(updated.Hooks) != 1 { t.Fatalf("Expected 1 hook, got %d", len(updated.Hooks)) } if updated.Hooks[0].Manifest != manifestWithHook { t.Errorf("Unexpected manifest: %v", updated.Hooks[0].Manifest) } anotherUpgradedRelease := upgradeReleaseVersion(upgradedRel) rs.env.Releases.Update(upgradedRel) rs.env.Releases.Create(anotherUpgradedRelease) res, err = rs.RollbackRelease(c, req) if err != nil { t.Fatalf("Failed rollback: %s", err) } updated, err = rs.env.Releases.Get(res.Release.Name, res.Release.Version) if err != nil { t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) } if len(updated.Hooks) != 1 { t.Fatalf("Expected 1 hook, got %d", len(updated.Hooks)) } if updated.Hooks[0].Manifest != manifestWithRollbackHooks { t.Errorf("Unexpected manifest: %v", updated.Hooks[0].Manifest) } if res.Release.Version != 4 { t.Errorf("Expected release version to be %v, got %v", 3, res.Release.Version) } if updated.Hooks[0].Events[0] != release.Hook_PRE_ROLLBACK { t.Errorf("Expected event 0 to be pre rollback") } if updated.Hooks[0].Events[1] != release.Hook_POST_ROLLBACK { t.Errorf("Expected event 1 to be post rollback") } if len(res.Release.Manifest) == 0 { t.Errorf("No manifest returned: %v", res.Release) } if len(updated.Manifest) == 0 { t.Errorf("Expected manifest in %v", res) } if !strings.Contains(updated.Manifest, "hello world") { t.Errorf("unexpected output: %s", rel.Manifest) } }