diff --git a/cmd/root.go b/cmd/root.go index be6b6eb..19ec00c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -53,7 +53,7 @@ func GetRemote(ctx context.Context, connection shared.Connection) (shared.Remote } } -func GetBranches(ctx context.Context, remote shared.Remote, connection shared.Connection, dryRun bool) ([]shared. +func GetBranches(ctx context.Context, remote shared.Remote, connection shared.Connection, state shared.PullRequestState, dryRun bool) ([]shared. Branch, error) { var repoNames []string var defaultBranchName string @@ -76,7 +76,7 @@ func GetBranches(ctx context.Context, remote shared.Remote, connection shared.Co return nil, err } - branches = checkDeletion(branches) + branches = checkDeletion(branches, state) branches, err = switchToDefaultBranchIfDeleted(ctx, branches, defaultBranchName, connection, dryRun) if err != nil { @@ -441,16 +441,16 @@ func toUncommittedChange(changes []string) []UncommittedChange { return results } -func checkDeletion(branches []shared.Branch) []shared.Branch { +func checkDeletion(branches []shared.Branch, state shared.PullRequestState) []shared.Branch { results := []shared.Branch{} for _, branch := range branches { - branch.State = getDeleteStatus(branch) + branch.State = getDeleteStatus(branch, state) results = append(results, branch) } return results } -func getDeleteStatus(branch shared.Branch) shared.BranchState { +func getDeleteStatus(branch shared.Branch, state shared.PullRequestState) shared.BranchState { if branch.IsProtected { return shared.NotDeletable } @@ -468,7 +468,7 @@ func getDeleteStatus(branch shared.Branch) shared.BranchState { if pr.State == shared.Open { return shared.NotDeletable } - if isFullyMerged(branch, pr) { + if isFullyMerged(branch, pr, state) { fullyMergedCnt++ } } @@ -479,8 +479,14 @@ func getDeleteStatus(branch shared.Branch) shared.BranchState { return shared.Deletable } -func isFullyMerged(branch shared.Branch, pr shared.PullRequest) bool { - if pr.State != shared.Merged || len(branch.Commits) == 0 { +func isFullyMerged(branch shared.Branch, pr shared.PullRequest, state shared.PullRequestState) bool { + if len(branch.Commits) == 0 { + return false + } + if (state == shared.Merged && pr.State != shared.Merged) || + // In the GitHub interface, closed status includes merged status, so we make it behave the same way. + // https://github.com/cli/cli/issues/8102 + (state == shared.Closed && pr.State != shared.Closed && pr.State != shared.Merged) { return false } diff --git a/cmd/root_test.go b/cmd/root_test.go index b1dbe83..9995cd7 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -40,7 +40,7 @@ func Test_ShouldBeDeletableWhenRemoteBranchesAssociatedWithMergedPR(t *testing.T }, nil, nil) remote, _ := GetRemote(context.Background(), s.Conn) - actual, _ := GetBranches(context.Background(), remote, s.Conn, false) + actual, _ := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.Equal(t, 2, len(actual)) assert.Equal(t, "issue1", actual[0].Name) @@ -78,7 +78,7 @@ func Test_ShouldBeDeletableWhenLsRemoteBranchesAssociatedWithMergedPR(t *testing }, nil, nil) remote, _ := GetRemote(context.Background(), s.Conn) - actual, _ := GetBranches(context.Background(), remote, s.Conn, false) + actual, _ := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.Equal(t, 2, len(actual)) assert.Equal(t, "issue1", actual[0].Name) @@ -119,7 +119,7 @@ func Test_ShouldBeDeletableWhenBranchesAssociatedWithMergedPR(t *testing.T) { }, nil, nil) remote, _ := GetRemote(context.Background(), s.Conn) - actual, _ := GetBranches(context.Background(), remote, s.Conn, false) + actual, _ := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.Equal(t, 2, len(actual)) assert.Equal(t, "issue1", actual[0].Name) @@ -159,7 +159,7 @@ func Test_ShouldBeDeletableWhenBranchesAssociatedWithSquashAndMergedPR(t *testin }, nil, nil) remote, _ := GetRemote(context.Background(), s.Conn) - actual, _ := GetBranches(context.Background(), remote, s.Conn, false) + actual, _ := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.Equal(t, 2, len(actual)) assert.Equal(t, "issue1", actual[0].Name) @@ -199,7 +199,7 @@ func Test_ShouldBeDeletableWhenBranchesAssociatedWithUpstreamSquashAndMergedPR(t }, nil, nil) remote, _ := GetRemote(context.Background(), s.Conn) - actual, _ := GetBranches(context.Background(), remote, s.Conn, false) + actual, _ := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.Equal(t, 2, len(actual)) assert.Equal(t, "issue1", actual[0].Name) @@ -239,7 +239,7 @@ func Test_ShouldBeDeletableWhenPRCheckoutBranchesAssociatedWithUpstreamSquashAnd }, nil, nil) remote, _ := GetRemote(context.Background(), s.Conn) - actual, _ := GetBranches(context.Background(), remote, s.Conn, false) + actual, _ := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.Equal(t, 2, len(actual)) assert.Equal(t, "fork/main", actual[0].Name) @@ -280,7 +280,7 @@ func Test_ShouldBeDeletableWhenBranchIsCheckedOutWithCheckIsFalse(t *testing.T) CheckoutBranch(nil, conn.NewConf(&conn.Times{N: 1})) remote, _ := GetRemote(context.Background(), s.Conn) - actual, _ := GetBranches(context.Background(), remote, s.Conn, false) + actual, _ := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.Equal(t, 2, len(actual)) assert.Equal(t, "issue1", actual[0].Name) @@ -321,7 +321,7 @@ func Test_ShouldBeDeletableWhenBranchIsCheckedOutWithCheckIsTrue(t *testing.T) { CheckoutBranch(nil, conn.NewConf(&conn.Times{N: 0})) remote, _ := GetRemote(context.Background(), s.Conn) - actual, _ := GetBranches(context.Background(), remote, s.Conn, true) + actual, _ := GetBranches(context.Background(), remote, s.Conn, shared.Merged, true) assert.Equal(t, 2, len(actual)) assert.Equal(t, "issue1", actual[0].Name) @@ -360,7 +360,7 @@ func Test_ShouldBeDeletableWhenBranchIsCheckedOutWithoutDefaultBranch(t *testing CheckoutBranch(nil, nil) remote, _ := GetRemote(context.Background(), s.Conn) - actual, _ := GetBranches(context.Background(), remote, s.Conn, false) + actual, _ := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.Equal(t, 2, len(actual)) assert.Equal(t, "issue1", actual[0].Name) @@ -401,7 +401,7 @@ func Test_ShouldNotDeletableWhenBranchHasModifiedUncommittedChanges(t *testing.T CheckoutBranch(nil, nil) remote, _ := GetRemote(context.Background(), s.Conn) - actual, _ := GetBranches(context.Background(), remote, s.Conn, false) + actual, _ := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.Equal(t, 2, len(actual)) assert.Equal(t, "issue1", actual[0].Name) @@ -442,7 +442,7 @@ func Test_ShouldBeDeletableWhenBranchHasUntrackedUncommittedChanges(t *testing.T CheckoutBranch(nil, nil) remote, _ := GetRemote(context.Background(), s.Conn) - actual, _ := GetBranches(context.Background(), remote, s.Conn, false) + actual, _ := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.Equal(t, 2, len(actual)) assert.Equal(t, "issue1", actual[0].Name) @@ -451,7 +451,7 @@ func Test_ShouldBeDeletableWhenBranchHasUntrackedUncommittedChanges(t *testing.T assert.Equal(t, shared.NotDeletable, actual[1].State) } -func Test_ShouldNotDeletableWhenBranchesAssociatedWithClosedPR(t *testing.T) { +func Test_ShouldNotDeletableWhenPRIsClosedAndStateOptionIsMerged(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -482,7 +482,7 @@ func Test_ShouldNotDeletableWhenBranchesAssociatedWithClosedPR(t *testing.T) { }, nil, nil) remote, _ := GetRemote(context.Background(), s.Conn) - actual, _ := GetBranches(context.Background(), remote, s.Conn, false) + actual, _ := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.Equal(t, 2, len(actual)) assert.Equal(t, "issue1", actual[0].Name) @@ -491,7 +491,87 @@ func Test_ShouldNotDeletableWhenBranchesAssociatedWithClosedPR(t *testing.T) { assert.Equal(t, shared.NotDeletable, actual[1].State) } -func Test_ShouldBeDeletableWhenBranchesAssociatedWithSquashAndMergedAndClosedPRs(t *testing.T) { +func Test_ShouldDeletableWhenPRIsClosedAndStateOptionIsClosed(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + s := conn.Setup(ctrl). + CheckRepos(nil, nil). + GetRemoteNames("origin", nil, nil). + GetSshConfig("github.com", nil, nil). + GetRepoNames("origin", nil, nil). + GetBranchNames("@main_issue1", nil, nil). + GetMergedBranchNames("@main", nil, nil). + GetRemoteHeadOid(nil, ErrCommand, nil). + GetLsRemoteHeadOid(nil, nil, nil). + GetLog([]conn.LogStub{ + {BranchName: "main", Filename: "main"}, {BranchName: "issue1", Filename: "issue1"}, + }, nil, nil). + GetAssociatedRefNames([]conn.AssociatedBranchNamesStub{ + {Oid: "a97e9630426df5d34ca9ee77ae1159bdfd5ff8f0", Filename: "issue1"}, + {Oid: "6ebe3d30d23531af56bd23b5a098d3ccae2a534a", Filename: "main_issue1"}, + }, nil, nil). + GetPullRequests("issue1Closed", nil, nil). + GetUncommittedChanges("", nil, nil). + GetConfig([]conn.ConfigStub{ + {BranchName: "branch.main.merge", Filename: "mergeMain"}, + {BranchName: "branch.main.gh-poi-protected", Filename: "empty"}, + {BranchName: "branch.issue1.merge", Filename: "mergeIssue1"}, + {BranchName: "branch.issue1.remote", Filename: "remote"}, + {BranchName: "branch.issue1.gh-poi-protected", Filename: "empty"}, + }, nil, nil) + remote, _ := GetRemote(context.Background(), s.Conn) + + actual, _ := GetBranches(context.Background(), remote, s.Conn, shared.Closed, false) + + assert.Equal(t, 2, len(actual)) + assert.Equal(t, "issue1", actual[0].Name) + assert.Equal(t, shared.Deletable, actual[0].State) + assert.Equal(t, "main", actual[1].Name) + assert.Equal(t, shared.NotDeletable, actual[1].State) +} + +func Test_ShouldBeDeletableWhenPRHasMergedAndClosedAndStateOptionIsMerged(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + s := conn.Setup(ctrl). + CheckRepos(nil, nil). + GetRemoteNames("origin", nil, nil). + GetSshConfig("github.com", nil, nil). + GetRepoNames("origin", nil, nil). + GetBranchNames("@main_issue1", nil, nil). + GetMergedBranchNames("@main", nil, nil). + GetRemoteHeadOid(nil, ErrCommand, nil). + GetLsRemoteHeadOid(nil, nil, nil). + GetLog([]conn.LogStub{ + {BranchName: "main", Filename: "main"}, {BranchName: "issue1", Filename: "issue1"}, + }, nil, nil). + GetAssociatedRefNames([]conn.AssociatedBranchNamesStub{ + {Oid: "a97e9630426df5d34ca9ee77ae1159bdfd5ff8f0", Filename: "issue1"}, + {Oid: "6ebe3d30d23531af56bd23b5a098d3ccae2a534a", Filename: "main_issue1"}, + }, nil, nil). + GetPullRequests("issue1Merged_issue1Closed", nil, nil). + GetUncommittedChanges("", nil, nil). + GetConfig([]conn.ConfigStub{ + {BranchName: "branch.main.merge", Filename: "mergeMain"}, + {BranchName: "branch.main.gh-poi-protected", Filename: "empty"}, + {BranchName: "branch.issue1.merge", Filename: "mergeIssue1"}, + {BranchName: "branch.issue1.remote", Filename: "remote"}, + {BranchName: "branch.issue1.gh-poi-protected", Filename: "empty"}, + }, nil, nil) + remote, _ := GetRemote(context.Background(), s.Conn) + + actual, _ := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) + + assert.Equal(t, 2, len(actual)) + assert.Equal(t, "issue1", actual[0].Name) + assert.Equal(t, shared.Deletable, actual[0].State) + assert.Equal(t, "main", actual[1].Name) + assert.Equal(t, shared.NotDeletable, actual[1].State) +} + +func Test_ShouldBeDeletableWhenPRHasMergedAndClosedAndStateOptionIsClosed(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -522,7 +602,7 @@ func Test_ShouldBeDeletableWhenBranchesAssociatedWithSquashAndMergedAndClosedPRs }, nil, nil) remote, _ := GetRemote(context.Background(), s.Conn) - actual, _ := GetBranches(context.Background(), remote, s.Conn, false) + actual, _ := GetBranches(context.Background(), remote, s.Conn, shared.Closed, false) assert.Equal(t, 2, len(actual)) assert.Equal(t, "issue1", actual[0].Name) @@ -564,7 +644,7 @@ func Test_ShouldNotDeletableWhenBranchesAssociatedWithNotFullyMergedPR(t *testin }, nil, nil) remote, _ := GetRemote(context.Background(), s.Conn) - actual, _ := GetBranches(context.Background(), remote, s.Conn, false) + actual, _ := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.Equal(t, 2, len(actual)) assert.Equal(t, "issue1", actual[0].Name) @@ -604,7 +684,7 @@ func Test_ShouldNotDeletableWhenDefaultBranchAssociatedWithMergedPR(t *testing.T }, nil, nil) remote, _ := GetRemote(context.Background(), s.Conn) - actual, _ := GetBranches(context.Background(), remote, s.Conn, false) + actual, _ := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.Equal(t, 2, len(actual)) assert.Equal(t, "issue1", actual[0].Name) @@ -640,7 +720,7 @@ func Test_ShouldNotDeletableWhenBranchIsProtected(t *testing.T) { }, nil, nil) remote, _ := GetRemote(context.Background(), s.Conn) - actual, _ := GetBranches(context.Background(), remote, s.Conn, false) + actual, _ := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.Equal(t, 2, len(actual)) assert.Equal(t, "issue1", actual[0].Name) @@ -684,7 +764,7 @@ func Test_BranchesAndPRsAreNotAssociatedWhenManyLocalCommitsAreAhead(t *testing. }, nil, nil) remote, _ := GetRemote(context.Background(), s.Conn) - actual, _ := GetBranches(context.Background(), remote, s.Conn, false) + actual, _ := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.Equal(t, 2, len(actual)) assert.Equal(t, "issue1", actual[0].Name) @@ -724,7 +804,7 @@ func Test_ShouldBeNoCommitHistoryWhenFirstCommitOfTopicBranchIsAssociatedWithDef }, nil, nil) remote, _ := GetRemote(context.Background(), s.Conn) - actual, _ := GetBranches(context.Background(), remote, s.Conn, false) + actual, _ := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.Equal(t, 2, len(actual)) assert.Equal(t, "issue1", actual[0].Name) @@ -762,7 +842,7 @@ func Test_ShouldBeNoCommitHistoryWhenDetachedBranch(t *testing.T) { }, nil, nil) remote, _ := GetRemote(context.Background(), s.Conn) - actual, _ := GetBranches(context.Background(), remote, s.Conn, false) + actual, _ := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.Equal(t, 2, len(actual)) assert.Equal(t, "(HEAD detached at a97e963)", actual[0].Name) @@ -815,7 +895,7 @@ func Test_DoesNotReturnsErrorWhenGetSshConfigFails(t *testing.T) { }, nil, nil) remote, _ := GetRemote(context.Background(), s.Conn) - _, err := GetBranches(context.Background(), remote, s.Conn, false) + _, err := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.Nil(t, err) } @@ -830,7 +910,7 @@ func Test_ReturnsErrorWhenGetRepoNamesFails(t *testing.T) { GetRepoNames("origin", ErrCommand, nil) remote, _ := GetRemote(context.Background(), s.Conn) - _, err := GetBranches(context.Background(), remote, s.Conn, false) + _, err := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.NotNil(t, err) } @@ -846,7 +926,7 @@ func Test_ReturnsErrorWhenCheckReposFails(t *testing.T) { GetRepoNames("origin", nil, nil) remote, _ := GetRemote(context.Background(), s.Conn) - _, err := GetBranches(context.Background(), remote, s.Conn, false) + _, err := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.NotNil(t, err) } @@ -863,7 +943,7 @@ func Test_ReturnsErrorWhenGetBranchNamesFails(t *testing.T) { GetBranchNames("@main_issue1", ErrCommand, nil) remote, _ := GetRemote(context.Background(), s.Conn) - _, err := GetBranches(context.Background(), remote, s.Conn, false) + _, err := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.NotNil(t, err) } @@ -881,7 +961,7 @@ func Test_ReturnsErrorWhenGetMergedBranchNames(t *testing.T) { GetMergedBranchNames("@main", ErrCommand, nil) remote, _ := GetRemote(context.Background(), s.Conn) - _, err := GetBranches(context.Background(), remote, s.Conn, false) + _, err := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.NotNil(t, err) } @@ -909,7 +989,7 @@ func Test_ReturnsErrorWhenGetLogFails(t *testing.T) { }, nil, nil) remote, _ := GetRemote(context.Background(), s.Conn) - _, err := GetBranches(context.Background(), remote, s.Conn, false) + _, err := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.NotNil(t, err) } @@ -941,7 +1021,7 @@ func Test_ReturnsErrorWhenGetAssociatedRefNamesFails(t *testing.T) { }, nil, nil) remote, _ := GetRemote(context.Background(), s.Conn) - _, err := GetBranches(context.Background(), remote, s.Conn, false) + _, err := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.NotNil(t, err) } @@ -975,7 +1055,7 @@ func Test_ReturnsErrorWhenGetPullRequestsFails(t *testing.T) { }, nil, nil) remote, _ := GetRemote(context.Background(), s.Conn) - _, err := GetBranches(context.Background(), remote, s.Conn, false) + _, err := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.NotNil(t, err) } @@ -1011,7 +1091,7 @@ func Test_ReturnsErrorWhenGetUncommittedChangesFails(t *testing.T) { }, nil, nil) remote, _ := GetRemote(context.Background(), s.Conn) - _, err := GetBranches(context.Background(), remote, s.Conn, false) + _, err := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.NotNil(t, err) } @@ -1048,7 +1128,7 @@ func Test_ReturnsErrorWhenCheckoutBranchFails(t *testing.T) { }, nil, nil) remote, _ := GetRemote(context.Background(), s.Conn) - _, err := GetBranches(context.Background(), remote, s.Conn, false) + _, err := GetBranches(context.Background(), remote, s.Conn, shared.Merged, false) assert.NotNil(t, err) } diff --git a/main.go b/main.go index b61edd0..0ee993d 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( "github.com/briandowns/spinner" "github.com/fatih/color" + "github.com/pkg/errors" "github.com/seachicken/gh-poi/cmd" "github.com/seachicken/gh-poi/cmd/protect" "github.com/seachicken/gh-poi/conn" @@ -24,9 +25,41 @@ var ( red = color.New(color.FgRed).SprintFunc() ) +type StateFlag string + +const ( + Closed StateFlag = "closed" + Merged StateFlag = "merged" +) + +func (s *StateFlag) String() string { + return string(*s) +} + +func (s *StateFlag) Set(value string) error { + for _, state := range []StateFlag{Closed, Merged} { + if value == string(state) { + *s = StateFlag(value) + return nil + } + } + return errors.New("invalid state") +} + +func (s StateFlag) toModel() shared.PullRequestState { + switch s { + case Closed: + return shared.Closed + default: + return shared.Merged + } +} + func main() { + state := Merged var dryRun bool var debug bool + flag.Var(&state, "state", "Filter by state: {closed|merged}") flag.BoolVar(&dryRun, "dry-run", false, "Show branches to delete") flag.BoolVar(&debug, "debug", false, "Enable debug logs") flag.Usage = func() { @@ -46,7 +79,7 @@ func main() { args := flag.Args() if len(args) == 0 { - runMain(dryRun, debug) + runMain(state, dryRun, debug) } else { subcmd, args := args[0], args[1:] switch subcmd { @@ -76,7 +109,7 @@ func main() { } } -func runMain(dryRun bool, debug bool) { +func runMain(state StateFlag, dryRun bool, debug bool) { ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() @@ -101,7 +134,7 @@ func runMain(dryRun bool, debug bool) { return } - branches, fetchingErr := cmd.GetBranches(ctx, remote, connection, dryRun) + branches, fetchingErr := cmd.GetBranches(ctx, remote, connection, state.toModel(), dryRun) sp.Stop() diff --git a/main_test.go b/main_test.go index bc4c4df..6dd3c92 100644 --- a/main_test.go +++ b/main_test.go @@ -14,7 +14,7 @@ import ( func Test_DeletingBranchesWhenDryRunOptionIsFalse(t *testing.T) { onlyCI(t) - results := captureOutput(func() { runMain(false, false) }) + results := captureOutput(func() { runMain(Merged, false, false) }) expected := fmt.Sprintf("%s %s", green("✔"), "Deleting branches...") assert.Contains(t, results, expected) @@ -23,7 +23,7 @@ func Test_DeletingBranchesWhenDryRunOptionIsFalse(t *testing.T) { func Test_DoNotDeleteBranchesWhenDryRunOptionIsTrue(t *testing.T) { onlyCI(t) - results := captureOutput(func() { runMain(true, false) }) + results := captureOutput(func() { runMain(Merged, true, false) }) expected := fmt.Sprintf("%s %s", hiBlack("-"), "Deleting branches...") assert.Contains(t, results, expected) @@ -33,12 +33,12 @@ func Test_ProtectAndUnprotect(t *testing.T) { onlyCI(t) runProtect([]string{"main"}, false) - protectResults := captureOutput(func() { runMain(true, false) }) + protectResults := captureOutput(func() { runMain(Merged, true, false) }) expected := fmt.Sprintf("main %s", hiBlack("[protected]")) assert.Contains(t, protectResults, expected) runUnprotect([]string{"main"}, false) - unprotectResults := captureOutput(func() { runMain(true, false) }) + unprotectResults := captureOutput(func() { runMain(Merged, true, false) }) assert.NotContains(t, unprotectResults, expected) }