Split status checks into plan and apply.

Split single top-level status check into one plan and one apply check.
These checks will also now track the whole pull request status rather
than just the last action.
This commit is contained in:
Luke Kysow
2019-03-05 13:37:47 -05:00
parent f98fe8ef80
commit 4fc22aedeb
11 changed files with 475 additions and 163 deletions

View File

@@ -166,7 +166,7 @@ func getAtlantisStatus(t *E2ETester, branchName string) (string, error) {
}
for _, status := range combinedStatus.Statuses {
if status.GetContext() == "Atlantis" {
if status.GetContext() == "plan/atlantis" {
return status.GetState(), nil
}
}

View File

@@ -91,18 +91,26 @@ func (c *DefaultCommandRunner) RunAutoplanCommand(baseRepo models.Repo, headRepo
if !c.validateCtxAndComment(ctx) {
return
}
if err := c.CommitStatusUpdater.Update(ctx.BaseRepo, ctx.Pull, models.PendingCommitStatus, models.PlanCommand); err != nil {
if err := c.CommitStatusUpdater.UpdateCombined(ctx.BaseRepo, ctx.Pull, models.PendingCommitStatus, models.PlanCommand); err != nil {
ctx.Log.Warn("unable to update commit status: %s", err)
}
projectCmds, err := c.ProjectCommandBuilder.BuildAutoplanCommands(ctx)
if err != nil {
if statusErr := c.CommitStatusUpdater.UpdateCombined(ctx.BaseRepo, ctx.Pull, models.FailedCommitStatus, models.PlanCommand); statusErr != nil {
ctx.Log.Warn("unable to update commit status: %s", statusErr)
}
c.updatePull(ctx, AutoplanCommand{}, CommandResult{Error: err})
return
}
if len(projectCmds) == 0 {
log.Info("determined there was no project to run plan in")
if err := c.CommitStatusUpdater.Update(baseRepo, pull, models.SuccessCommitStatus, models.PlanCommand); err != nil {
// If there were no projects modified, we set a successful commit status
// with 0/0 projects planned successfully because we've already set an
// in-progress status and we don't want that to be "in progress" forever.
if err := c.CommitStatusUpdater.UpdateCombinedCount(baseRepo, pull, models.SuccessCommitStatus, models.PlanCommand, 0, 0); err != nil {
ctx.Log.Warn("unable to update commit status: %s", err)
}
return
@@ -115,10 +123,12 @@ func (c *DefaultCommandRunner) RunAutoplanCommand(baseRepo models.Repo, headRepo
result.PlansDeleted = true
}
c.updatePull(ctx, AutoplanCommand{}, result)
_, err = c.updateDB(ctx, ctx.Pull, result.ProjectResults)
pullStatus, err := c.updateDB(ctx, ctx.Pull, result.ProjectResults)
if err != nil {
c.Logger.Err("writing results: %s", err)
}
c.updateCommitStatus(ctx, models.PlanCommand, pullStatus)
}
// RunCommentCommand executes the command.
@@ -184,7 +194,7 @@ func (c *DefaultCommandRunner) RunCommentCommand(baseRepo models.Repo, maybeHead
ctx.Log.Info("pull request mergeable status: %t", ctx.PullMergeable)
}
if err = c.CommitStatusUpdater.Update(baseRepo, pull, models.PendingCommitStatus, cmd.CommandName()); err != nil {
if err = c.CommitStatusUpdater.UpdateCombined(baseRepo, pull, models.PendingCommitStatus, cmd.CommandName()); err != nil {
ctx.Log.Warn("unable to update commit status: %s", err)
}
@@ -199,6 +209,9 @@ func (c *DefaultCommandRunner) RunCommentCommand(baseRepo models.Repo, maybeHead
return
}
if err != nil {
if statusErr := c.CommitStatusUpdater.UpdateCombined(ctx.BaseRepo, ctx.Pull, models.FailedCommitStatus, cmd.CommandName()); statusErr != nil {
ctx.Log.Warn("unable to update commit status: %s", statusErr)
}
c.updatePull(ctx, cmd, CommandResult{Error: err})
return
}
@@ -220,11 +233,45 @@ func (c *DefaultCommandRunner) RunCommentCommand(baseRepo models.Repo, maybeHead
return
}
c.updateCommitStatus(ctx, cmd.Name, pullStatus)
if cmd.Name == models.ApplyCommand && c.automergeEnabled(ctx, projectCmds) {
c.automerge(ctx, pullStatus)
}
}
func (c *DefaultCommandRunner) updateCommitStatus(ctx *CommandContext, cmd models.CommandName, pullStatus models.PullStatus) {
var numSuccess int
var status models.CommitStatus
if cmd == models.PlanCommand {
// We consider anything that isn't a plan error as a plan success.
// For example, if there is an apply error, that means that at least a
// plan was generated successfully.
numSuccess = len(pullStatus.Projects) - pullStatus.StatusCount(models.ErroredPlanStatus)
status = models.SuccessCommitStatus
if numSuccess != len(pullStatus.Projects) {
status = models.FailedCommitStatus
}
} else {
numSuccess = pullStatus.StatusCount(models.AppliedPlanStatus)
numErrored := pullStatus.StatusCount(models.ErroredApplyStatus)
status = models.SuccessCommitStatus
if numErrored > 0 {
status = models.FailedCommitStatus
} else if numSuccess < len(pullStatus.Projects) {
// If there are plans that haven't been applied yet, we'll use a pending
// status.
status = models.PendingCommitStatus
}
}
if err := c.CommitStatusUpdater.UpdateCombinedCount(ctx.BaseRepo, ctx.Pull, status, cmd, numSuccess, len(pullStatus.Projects)); err != nil {
ctx.Log.Warn("unable to update commit status: %s", err)
}
}
func (c *DefaultCommandRunner) automerge(ctx *CommandContext, pullStatus models.PullStatus) {
// We only automerge if all projects have been successfully applied.
for _, p := range pullStatus.Projects {
@@ -328,10 +375,6 @@ func (c *DefaultCommandRunner) updatePull(ctx *CommandContext, command PullComma
ctx.Log.Warn(res.Failure)
}
// Update the pull request's status icon and comment back.
if err := c.CommitStatusUpdater.UpdateProjectResult(ctx, command.CommandName(), res); err != nil {
ctx.Log.Warn("unable to update commit status: %s", err)
}
comment := c.MarkdownRenderer.Render(res, command.CommandName(), ctx.Log.History.String(), command.IsVerbose(), ctx.BaseRepo.VCSHost.Type)
if err := c.VCSClient.CreateComment(ctx.BaseRepo, ctx.Pull.Num, comment); err != nil {
ctx.Log.Err("unable to comment: %s", err)

View File

@@ -0,0 +1,145 @@
package events
import (
"github.com/runatlantis/atlantis/server/events/models"
. "github.com/runatlantis/atlantis/testing"
"testing"
)
func TestUpdateCommitStatus(t *testing.T) {
cases := map[string]struct {
cmd models.CommandName
pullStatus models.PullStatus
expStatus models.CommitStatus
expNumSuccess int
expNumTotal int
}{
"single plan success": {
cmd: models.PlanCommand,
pullStatus: models.PullStatus{
Projects: []models.ProjectStatus{
{
Status: models.PlannedPlanStatus,
},
},
},
expStatus: models.SuccessCommitStatus,
expNumSuccess: 1,
expNumTotal: 1,
},
"one plan error, other errors": {
cmd: models.PlanCommand,
pullStatus: models.PullStatus{
Projects: []models.ProjectStatus{
{
Status: models.ErroredPlanStatus,
},
{
Status: models.PlannedPlanStatus,
},
{
Status: models.AppliedPlanStatus,
},
{
Status: models.ErroredApplyStatus,
},
},
},
expStatus: models.FailedCommitStatus,
expNumSuccess: 3,
expNumTotal: 4,
},
"apply, one pending": {
cmd: models.ApplyCommand,
pullStatus: models.PullStatus{
Projects: []models.ProjectStatus{
{
Status: models.PlannedPlanStatus,
},
{
Status: models.AppliedPlanStatus,
},
},
},
expStatus: models.PendingCommitStatus,
expNumSuccess: 1,
expNumTotal: 2,
},
"apply, all successful": {
cmd: models.ApplyCommand,
pullStatus: models.PullStatus{
Projects: []models.ProjectStatus{
{
Status: models.AppliedPlanStatus,
},
{
Status: models.AppliedPlanStatus,
},
},
},
expStatus: models.SuccessCommitStatus,
expNumSuccess: 2,
expNumTotal: 2,
},
"apply, one errored, one pending": {
cmd: models.ApplyCommand,
pullStatus: models.PullStatus{
Projects: []models.ProjectStatus{
{
Status: models.AppliedPlanStatus,
},
{
Status: models.ErroredApplyStatus,
},
{
Status: models.PlannedPlanStatus,
},
},
},
expStatus: models.FailedCommitStatus,
expNumSuccess: 1,
expNumTotal: 3,
},
}
for name, c := range cases {
t.Run(name, func(t *testing.T) {
csu := &MockCSU{}
cr := &DefaultCommandRunner{
CommitStatusUpdater: csu,
}
cr.updateCommitStatus(&CommandContext{}, c.cmd, c.pullStatus)
Equals(t, models.Repo{}, csu.CalledRepo)
Equals(t, models.PullRequest{}, csu.CalledPull)
Equals(t, c.expStatus, csu.CalledStatus)
Equals(t, c.cmd, csu.CalledCommand)
Equals(t, c.expNumSuccess, csu.CalledNumSuccess)
Equals(t, c.expNumTotal, csu.CalledNumTotal)
})
}
}
type MockCSU struct {
CalledRepo models.Repo
CalledPull models.PullRequest
CalledStatus models.CommitStatus
CalledCommand models.CommandName
CalledNumSuccess int
CalledNumTotal int
}
func (m *MockCSU) UpdateCombinedCount(repo models.Repo, pull models.PullRequest, status models.CommitStatus, command models.CommandName, numSuccess int, numTotal int) error {
m.CalledRepo = repo
m.CalledPull = pull
m.CalledStatus = status
m.CalledCommand = command
m.CalledNumSuccess = numSuccess
m.CalledNumTotal = numTotal
return nil
}
func (m *MockCSU) UpdateCombined(repo models.Repo, pull models.PullRequest, status models.CommitStatus, command models.CommandName) error {
return nil
}
func (m *MockCSU) UpdateProject(ctx models.ProjectCommandContext, cmdName models.CommandName, status models.CommitStatus, url string) error {
return nil
}

View File

@@ -36,7 +36,6 @@ import (
var projectCommandBuilder *mocks.MockProjectCommandBuilder
var projectCommandRunner *mocks.MockProjectCommandRunner
var eventParsing *mocks.MockEventParsing
var ghStatus *mocks.MockCommitStatusUpdater
var githubGetter *mocks.MockGithubPullGetter
var gitlabGetter *mocks.MockGitlabMergeRequestGetter
var ch events.DefaultCommandRunner
@@ -48,7 +47,6 @@ func setup(t *testing.T) *vcsmocks.MockClient {
RegisterMockTestingT(t)
projectCommandBuilder = mocks.NewMockProjectCommandBuilder()
eventParsing = mocks.NewMockEventParsing()
ghStatus = mocks.NewMockCommitStatusUpdater()
vcsClient := vcsmocks.NewMockClient()
githubGetter = mocks.NewMockGithubPullGetter()
gitlabGetter = mocks.NewMockGitlabMergeRequestGetter()
@@ -62,7 +60,7 @@ func setup(t *testing.T) *vcsmocks.MockClient {
ThenReturn(pullLogger)
ch = events.DefaultCommandRunner{
VCSClient: vcsClient,
CommitStatusUpdater: ghStatus,
CommitStatusUpdater: &events.DefaultCommitStatusUpdater{vcsClient},
EventParser: eventParsing,
MarkdownRenderer: &events.MarkdownRenderer{},
GithubPullGetter: githubGetter,

View File

@@ -26,12 +26,12 @@ import (
// CommitStatusUpdater updates the status of a commit with the VCS host. We set
// the status to signify whether the plan/apply succeeds.
type CommitStatusUpdater interface {
// Update updates the status of the head commit of pull.
Update(repo models.Repo, pull models.PullRequest, status models.CommitStatus, command models.CommandName) error
// UpdateProjectResult updates the status of the head commit given the
// state of response.
// todo: rename this so it doesn't conflict with UpdateProject
UpdateProjectResult(ctx *CommandContext, commandName models.CommandName, res CommandResult) error
// UpdateCombined updates the combined status of the head commit of pull.
// A combined status represents all the projects modified in the pull.
UpdateCombined(repo models.Repo, pull models.PullRequest, status models.CommitStatus, command models.CommandName) error
// UpdateCombinedCount updates the combined status to reflect the
// numSuccess out of numTotal.
UpdateCombinedCount(repo models.Repo, pull models.PullRequest, status models.CommitStatus, command models.CommandName, numSuccess int, numTotal int) error
// UpdateProject sets the commit status for the project represented by
// ctx.
UpdateProject(ctx models.ProjectCommandContext, cmdName models.CommandName, status models.CommitStatus, url string) error
@@ -42,25 +42,28 @@ type DefaultCommitStatusUpdater struct {
Client vcs.Client
}
// Update updates the commit status.
func (d *DefaultCommitStatusUpdater) Update(repo models.Repo, pull models.PullRequest, status models.CommitStatus, command models.CommandName) error {
description := fmt.Sprintf("%s %s", strings.Title(command.String()), strings.Title(status.String()))
return d.Client.UpdateStatus(repo, pull, status, "Atlantis", description, "")
func (d *DefaultCommitStatusUpdater) UpdateCombined(repo models.Repo, pull models.PullRequest, status models.CommitStatus, command models.CommandName) error {
src := fmt.Sprintf("%s/atlantis", command.String())
var descripWords string
switch status {
case models.PendingCommitStatus:
descripWords = "in progress..."
case models.FailedCommitStatus:
descripWords = "failed."
case models.SuccessCommitStatus:
descripWords = "succeeded."
}
descrip := fmt.Sprintf("%s %s", strings.Title(command.String()), descripWords)
return d.Client.UpdateStatus(repo, pull, status, src, descrip, "")
}
// UpdateProjectResult updates the commit status based on the status of res.
func (d *DefaultCommitStatusUpdater) UpdateProjectResult(ctx *CommandContext, commandName models.CommandName, res CommandResult) error {
var status models.CommitStatus
if res.Error != nil || res.Failure != "" {
status = models.FailedCommitStatus
} else {
var statuses []models.CommitStatus
for _, p := range res.ProjectResults {
statuses = append(statuses, p.CommitStatus())
}
status = d.worstStatus(statuses)
func (d *DefaultCommitStatusUpdater) UpdateCombinedCount(repo models.Repo, pull models.PullRequest, status models.CommitStatus, command models.CommandName, numSuccess int, numTotal int) error {
src := fmt.Sprintf("%s/atlantis", command.String())
cmdVerb := "planned"
if command == models.ApplyCommand {
cmdVerb = "applied"
}
return d.Update(ctx.BaseRepo, ctx.Pull, status, commandName)
return d.Client.UpdateStatus(repo, pull, status, src, fmt.Sprintf("%d/%d projects %s successfully.", numSuccess, numTotal, cmdVerb), "")
}
func (d *DefaultCommitStatusUpdater) UpdateProject(ctx models.ProjectCommandContext, cmdName models.CommandName, status models.CommitStatus, url string) error {
@@ -81,12 +84,3 @@ func (d *DefaultCommitStatusUpdater) UpdateProject(ctx models.ProjectCommandCont
descrip := fmt.Sprintf("%s %s", strings.Title(cmdName.String()), descripWords)
return d.Client.UpdateStatus(ctx.BaseRepo, ctx.Pull, status, src, descrip, url)
}
func (d *DefaultCommitStatusUpdater) worstStatus(ss []models.CommitStatus) models.CommitStatus {
for _, s := range ss {
if s == models.FailedCommitStatus {
return models.FailedCommitStatus
}
}
return models.SuccessCommitStatus
}

View File

@@ -14,10 +14,8 @@
package events_test
import (
"errors"
"fmt"
"github.com/runatlantis/atlantis/server/events/yaml/valid"
"strings"
"testing"
. "github.com/petergtz/pegomock"
@@ -27,113 +25,120 @@ import (
. "github.com/runatlantis/atlantis/testing"
)
var repoModel = models.Repo{}
var pullModel = models.PullRequest{}
var status = models.SuccessCommitStatus
func TestUpdate(t *testing.T) {
RegisterMockTestingT(t)
client := mocks.NewMockClient()
s := events.DefaultCommitStatusUpdater{Client: client}
err := s.Update(repoModel, pullModel, status, models.PlanCommand)
Ok(t, err)
client.VerifyWasCalledOnce().UpdateStatus(repoModel, pullModel, status, "Atlantis", "Plan Success", "")
}
func TestUpdateProjectResult_Error(t *testing.T) {
RegisterMockTestingT(t)
ctx := &events.CommandContext{
BaseRepo: repoModel,
Pull: pullModel,
}
client := mocks.NewMockClient()
s := events.DefaultCommitStatusUpdater{Client: client}
err := s.UpdateProjectResult(ctx, models.PlanCommand, events.CommandResult{Error: errors.New("err")})
Ok(t, err)
client.VerifyWasCalledOnce().UpdateStatus(repoModel, pullModel, models.FailedCommitStatus, "Atlantis", "Plan Failed", "")
}
func TestUpdateProjectResult_Failure(t *testing.T) {
RegisterMockTestingT(t)
ctx := &events.CommandContext{
BaseRepo: repoModel,
Pull: pullModel,
}
client := mocks.NewMockClient()
s := events.DefaultCommitStatusUpdater{Client: client}
err := s.UpdateProjectResult(ctx, models.PlanCommand, events.CommandResult{Failure: "failure"})
Ok(t, err)
client.VerifyWasCalledOnce().UpdateStatus(repoModel, pullModel, models.FailedCommitStatus, "Atlantis", "Plan Failed", "")
}
func TestUpdateProjectResult(t *testing.T) {
RegisterMockTestingT(t)
ctx := &events.CommandContext{
BaseRepo: repoModel,
Pull: pullModel,
}
func TestUpdateCombined(t *testing.T) {
cases := []struct {
Statuses []string
Expected models.CommitStatus
status models.CommitStatus
command models.CommandName
expDescrip string
}{
{
[]string{"success", "failure", "error"},
models.FailedCommitStatus,
status: models.PendingCommitStatus,
command: models.PlanCommand,
expDescrip: "Plan in progress...",
},
{
[]string{"failure", "error", "success"},
models.FailedCommitStatus,
status: models.FailedCommitStatus,
command: models.PlanCommand,
expDescrip: "Plan failed.",
},
{
[]string{"success", "failure"},
models.FailedCommitStatus,
status: models.SuccessCommitStatus,
command: models.PlanCommand,
expDescrip: "Plan succeeded.",
},
{
[]string{"success", "error"},
models.FailedCommitStatus,
status: models.PendingCommitStatus,
command: models.ApplyCommand,
expDescrip: "Apply in progress...",
},
{
[]string{"failure", "error"},
models.FailedCommitStatus,
status: models.FailedCommitStatus,
command: models.ApplyCommand,
expDescrip: "Apply failed.",
},
{
[]string{"success"},
models.SuccessCommitStatus,
},
{
[]string{"success", "success"},
models.SuccessCommitStatus,
status: models.SuccessCommitStatus,
command: models.ApplyCommand,
expDescrip: "Apply succeeded.",
},
}
for _, c := range cases {
t.Run(strings.Join(c.Statuses, "-"), func(t *testing.T) {
var results []models.ProjectResult
for _, statusStr := range c.Statuses {
var result models.ProjectResult
switch statusStr {
case "failure":
result = models.ProjectResult{
Failure: "failure",
}
case "error":
result = models.ProjectResult{
Error: errors.New("err"),
}
default:
result = models.ProjectResult{}
}
results = append(results, result)
}
resp := events.CommandResult{ProjectResults: results}
t.Run(c.expDescrip, func(t *testing.T) {
RegisterMockTestingT(t)
client := mocks.NewMockClient()
s := events.DefaultCommitStatusUpdater{Client: client}
err := s.UpdateProjectResult(ctx, models.PlanCommand, resp)
err := s.UpdateCombined(models.Repo{}, models.PullRequest{}, c.status, c.command)
Ok(t, err)
client.VerifyWasCalledOnce().UpdateStatus(repoModel, pullModel, c.Expected, "Atlantis", "Plan "+strings.Title(c.Expected.String()), "")
expSrc := fmt.Sprintf("%s/atlantis", c.command)
client.VerifyWasCalledOnce().UpdateStatus(models.Repo{}, models.PullRequest{}, c.status, expSrc, c.expDescrip, "")
})
}
}
func TestUpdateCombinedCount(t *testing.T) {
cases := []struct {
status models.CommitStatus
command models.CommandName
numSuccess int
numTotal int
expDescrip string
}{
{
status: models.PendingCommitStatus,
command: models.PlanCommand,
numSuccess: 0,
numTotal: 2,
expDescrip: "0/2 projects planned successfully.",
},
{
status: models.FailedCommitStatus,
command: models.PlanCommand,
numSuccess: 1,
numTotal: 2,
expDescrip: "1/2 projects planned successfully.",
},
{
status: models.SuccessCommitStatus,
command: models.PlanCommand,
numSuccess: 2,
numTotal: 2,
expDescrip: "2/2 projects planned successfully.",
},
{
status: models.FailedCommitStatus,
command: models.ApplyCommand,
numSuccess: 0,
numTotal: 2,
expDescrip: "0/2 projects applied successfully.",
},
{
status: models.PendingCommitStatus,
command: models.ApplyCommand,
numSuccess: 1,
numTotal: 2,
expDescrip: "1/2 projects applied successfully.",
},
{
status: models.SuccessCommitStatus,
command: models.ApplyCommand,
numSuccess: 2,
numTotal: 2,
expDescrip: "2/2 projects applied successfully.",
},
}
for _, c := range cases {
t.Run(c.expDescrip, func(t *testing.T) {
RegisterMockTestingT(t)
client := mocks.NewMockClient()
s := events.DefaultCommitStatusUpdater{Client: client}
err := s.UpdateCombinedCount(models.Repo{}, models.PullRequest{}, c.status, c.command, c.numSuccess, c.numTotal)
Ok(t, err)
expSrc := fmt.Sprintf("%s/atlantis", c.command)
client.VerifyWasCalledOnce().UpdateStatus(models.Repo{}, models.PullRequest{}, c.status, expSrc, c.expDescrip, "")
})
}
}
@@ -177,13 +182,13 @@ func TestDefaultCommitStatusUpdater_UpdateProjectSrc(t *testing.T) {
models.PendingCommitStatus,
"url")
Ok(t, err)
client.VerifyWasCalledOnce().UpdateStatus(repoModel, pullModel, models.PendingCommitStatus, c.expSrc, "Plan in progress...", "url")
client.VerifyWasCalledOnce().UpdateStatus(models.Repo{}, models.PullRequest{}, models.PendingCommitStatus, c.expSrc, "Plan in progress...", "url")
})
}
}
// Test that it uses the right words in the description.
func TestDefaultCommitStatusUpdater_UpdateProjectStatus(t *testing.T) {
func TestDefaultCommitStatusUpdater_UpdateProject(t *testing.T) {
RegisterMockTestingT(t)
cases := []struct {
status models.CommitStatus
@@ -234,7 +239,7 @@ func TestDefaultCommitStatusUpdater_UpdateProjectStatus(t *testing.T) {
c.status,
"url")
Ok(t, err)
client.VerifyWasCalledOnce().UpdateStatus(repoModel, pullModel, c.status, fmt.Sprintf("%s/atlantis: ./default", c.cmd.String()), c.expDescrip, "url")
client.VerifyWasCalledOnce().UpdateStatus(models.Repo{}, models.PullRequest{}, c.status, fmt.Sprintf("%s/atlantis: ./default", c.cmd.String()), c.expDescrip, "url")
})
}
}

View File

@@ -381,6 +381,7 @@ func TestPullStatus_UpdateGet(t *testing.T) {
pull,
[]models.ProjectResult{
{
Command: models.PlanCommand,
RepoRelDir: ".",
Workspace: "default",
Failure: "failure",
@@ -600,11 +601,20 @@ func TestPullStatus_UpdateMerge(t *testing.T) {
pull,
[]models.ProjectResult{
{
Command: models.PlanCommand,
RepoRelDir: "mergeme",
Workspace: "default",
Failure: "failure",
},
{
Command: models.PlanCommand,
RepoRelDir: "projectname",
Workspace: "default",
ProjectName: "projectname",
Failure: "failure",
},
{
Command: models.PlanCommand,
RepoRelDir: "staythesame",
Workspace: "default",
PlanSuccess: &models.PlanSuccess{
@@ -620,11 +630,20 @@ func TestPullStatus_UpdateMerge(t *testing.T) {
updateStatus, err := b.UpdatePullWithResults(pull,
[]models.ProjectResult{
{
Command: models.ApplyCommand,
RepoRelDir: "mergeme",
Workspace: "default",
ApplySuccess: "applied!",
},
{
Command: models.ApplyCommand,
RepoRelDir: "projectname",
Workspace: "default",
ProjectName: "projectname",
Error: errors.New("apply error"),
},
{
Command: models.ApplyCommand,
RepoRelDir: "newresult",
Workspace: "default",
ApplySuccess: "success!",
@@ -645,6 +664,12 @@ func TestPullStatus_UpdateMerge(t *testing.T) {
Workspace: "default",
Status: models.AppliedPlanStatus,
},
{
RepoRelDir: "projectname",
Workspace: "default",
ProjectName: "projectname",
Status: models.ErroredApplyStatus,
},
{
RepoRelDir: "staythesame",
Workspace: "default",

View File

@@ -5,7 +5,6 @@ package mocks
import (
pegomock "github.com/petergtz/pegomock"
events "github.com/runatlantis/atlantis/server/events"
models "github.com/runatlantis/atlantis/server/events/models"
"reflect"
"time"
@@ -19,12 +18,12 @@ func NewMockCommitStatusUpdater() *MockCommitStatusUpdater {
return &MockCommitStatusUpdater{fail: pegomock.GlobalFailHandler}
}
func (mock *MockCommitStatusUpdater) Update(repo models.Repo, pull models.PullRequest, status models.CommitStatus, command models.CommandName) error {
func (mock *MockCommitStatusUpdater) UpdateCombined(repo models.Repo, pull models.PullRequest, status models.CommitStatus, command models.CommandName) error {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockCommitStatusUpdater().")
}
params := []pegomock.Param{repo, pull, status, command}
result := pegomock.GetGenericMockFrom(mock).Invoke("Update", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
result := pegomock.GetGenericMockFrom(mock).Invoke("UpdateCombined", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
var ret0 error
if len(result) != 0 {
if result[0] != nil {
@@ -34,12 +33,12 @@ func (mock *MockCommitStatusUpdater) Update(repo models.Repo, pull models.PullRe
return ret0
}
func (mock *MockCommitStatusUpdater) UpdateProjectResult(ctx *events.CommandContext, commandName models.CommandName, res events.CommandResult) error {
func (mock *MockCommitStatusUpdater) UpdateCombinedCount(repo models.Repo, pull models.PullRequest, status models.CommitStatus, command models.CommandName, numSuccess int, numTotal int) error {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockCommitStatusUpdater().")
}
params := []pegomock.Param{ctx, commandName, res}
result := pegomock.GetGenericMockFrom(mock).Invoke("UpdateProjectResult", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
params := []pegomock.Param{repo, pull, status, command, numSuccess, numTotal}
result := pegomock.GetGenericMockFrom(mock).Invoke("UpdateCombinedCount", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
var ret0 error
if len(result) != 0 {
if result[0] != nil {
@@ -101,23 +100,23 @@ type VerifierCommitStatusUpdater struct {
timeout time.Duration
}
func (verifier *VerifierCommitStatusUpdater) Update(repo models.Repo, pull models.PullRequest, status models.CommitStatus, command models.CommandName) *CommitStatusUpdater_Update_OngoingVerification {
func (verifier *VerifierCommitStatusUpdater) UpdateCombined(repo models.Repo, pull models.PullRequest, status models.CommitStatus, command models.CommandName) *CommitStatusUpdater_UpdateCombined_OngoingVerification {
params := []pegomock.Param{repo, pull, status, command}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Update", params, verifier.timeout)
return &CommitStatusUpdater_Update_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "UpdateCombined", params, verifier.timeout)
return &CommitStatusUpdater_UpdateCombined_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type CommitStatusUpdater_Update_OngoingVerification struct {
type CommitStatusUpdater_UpdateCombined_OngoingVerification struct {
mock *MockCommitStatusUpdater
methodInvocations []pegomock.MethodInvocation
}
func (c *CommitStatusUpdater_Update_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest, models.CommitStatus, models.CommandName) {
func (c *CommitStatusUpdater_UpdateCombined_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest, models.CommitStatus, models.CommandName) {
repo, pull, status, command := c.GetAllCapturedArguments()
return repo[len(repo)-1], pull[len(pull)-1], status[len(status)-1], command[len(command)-1]
}
func (c *CommitStatusUpdater_Update_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest, _param2 []models.CommitStatus, _param3 []models.CommandName) {
func (c *CommitStatusUpdater_UpdateCombined_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest, _param2 []models.CommitStatus, _param3 []models.CommandName) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([]models.Repo, len(params[0]))
@@ -140,36 +139,48 @@ func (c *CommitStatusUpdater_Update_OngoingVerification) GetAllCapturedArguments
return
}
func (verifier *VerifierCommitStatusUpdater) UpdateProjectResult(ctx *events.CommandContext, commandName models.CommandName, res events.CommandResult) *CommitStatusUpdater_UpdateProjectResult_OngoingVerification {
params := []pegomock.Param{ctx, commandName, res}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "UpdateProjectResult", params, verifier.timeout)
return &CommitStatusUpdater_UpdateProjectResult_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
func (verifier *VerifierCommitStatusUpdater) UpdateCombinedCount(repo models.Repo, pull models.PullRequest, status models.CommitStatus, command models.CommandName, numSuccess int, numTotal int) *CommitStatusUpdater_UpdateCombinedCount_OngoingVerification {
params := []pegomock.Param{repo, pull, status, command, numSuccess, numTotal}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "UpdateCombinedCount", params, verifier.timeout)
return &CommitStatusUpdater_UpdateCombinedCount_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type CommitStatusUpdater_UpdateProjectResult_OngoingVerification struct {
type CommitStatusUpdater_UpdateCombinedCount_OngoingVerification struct {
mock *MockCommitStatusUpdater
methodInvocations []pegomock.MethodInvocation
}
func (c *CommitStatusUpdater_UpdateProjectResult_OngoingVerification) GetCapturedArguments() (*events.CommandContext, models.CommandName, events.CommandResult) {
ctx, commandName, res := c.GetAllCapturedArguments()
return ctx[len(ctx)-1], commandName[len(commandName)-1], res[len(res)-1]
func (c *CommitStatusUpdater_UpdateCombinedCount_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest, models.CommitStatus, models.CommandName, int, int) {
repo, pull, status, command, numSuccess, numTotal := c.GetAllCapturedArguments()
return repo[len(repo)-1], pull[len(pull)-1], status[len(status)-1], command[len(command)-1], numSuccess[len(numSuccess)-1], numTotal[len(numTotal)-1]
}
func (c *CommitStatusUpdater_UpdateProjectResult_OngoingVerification) GetAllCapturedArguments() (_param0 []*events.CommandContext, _param1 []models.CommandName, _param2 []events.CommandResult) {
func (c *CommitStatusUpdater_UpdateCombinedCount_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest, _param2 []models.CommitStatus, _param3 []models.CommandName, _param4 []int, _param5 []int) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([]*events.CommandContext, len(params[0]))
_param0 = make([]models.Repo, len(params[0]))
for u, param := range params[0] {
_param0[u] = param.(*events.CommandContext)
_param0[u] = param.(models.Repo)
}
_param1 = make([]models.CommandName, len(params[1]))
_param1 = make([]models.PullRequest, len(params[1]))
for u, param := range params[1] {
_param1[u] = param.(models.CommandName)
_param1[u] = param.(models.PullRequest)
}
_param2 = make([]events.CommandResult, len(params[2]))
_param2 = make([]models.CommitStatus, len(params[2]))
for u, param := range params[2] {
_param2[u] = param.(events.CommandResult)
_param2[u] = param.(models.CommitStatus)
}
_param3 = make([]models.CommandName, len(params[3]))
for u, param := range params[3] {
_param3[u] = param.(models.CommandName)
}
_param4 = make([]int, len(params[4]))
for u, param := range params[4] {
_param4[u] = param.(int)
}
_param5 = make([]int, len(params[5]))
for u, param := range params[5] {
_param5[u] = param.(int)
}
}
return

View File

@@ -405,6 +405,17 @@ type PullStatus struct {
Pull PullRequest
}
// StatusCount returns the number of projects that have status.
func (p PullStatus) StatusCount(status ProjectPlanStatus) int {
c := 0
for _, pr := range p.Projects {
if pr.Status == status {
c++
}
}
return c
}
// ProjectStatus is the status of a specific project.
type ProjectStatus struct {
Workspace string

View File

@@ -294,3 +294,83 @@ func TestProjectResult_IsSuccessful(t *testing.T) {
})
}
}
func TestProjectResult_PlanStatus(t *testing.T) {
cases := []struct {
p models.ProjectResult
expStatus models.ProjectPlanStatus
}{
{
p: models.ProjectResult{
Command: models.PlanCommand,
Error: errors.New("err"),
},
expStatus: models.ErroredPlanStatus,
},
{
p: models.ProjectResult{
Command: models.PlanCommand,
Failure: "failure",
},
expStatus: models.ErroredPlanStatus,
},
{
p: models.ProjectResult{
Command: models.PlanCommand,
PlanSuccess: &models.PlanSuccess{},
},
expStatus: models.PlannedPlanStatus,
},
{
p: models.ProjectResult{
Command: models.ApplyCommand,
Error: errors.New("err"),
},
expStatus: models.ErroredApplyStatus,
},
{
p: models.ProjectResult{
Command: models.ApplyCommand,
Failure: "failure",
},
expStatus: models.ErroredApplyStatus,
},
{
p: models.ProjectResult{
Command: models.ApplyCommand,
ApplySuccess: "success",
},
expStatus: models.AppliedPlanStatus,
},
}
for _, c := range cases {
t.Run(c.expStatus.String(), func(t *testing.T) {
Equals(t, c.expStatus, c.p.PlanStatus())
})
}
}
func TestPullStatus_StatusCount(t *testing.T) {
ps := models.PullStatus{
Projects: []models.ProjectStatus{
{
Status: models.PlannedPlanStatus,
},
{
Status: models.PlannedPlanStatus,
},
{
Status: models.AppliedPlanStatus,
},
{
Status: models.ErroredApplyStatus,
},
},
}
Equals(t, 2, ps.StatusCount(models.PlannedPlanStatus))
Equals(t, 1, ps.StatusCount(models.AppliedPlanStatus))
Equals(t, 1, ps.StatusCount(models.ErroredApplyStatus))
Equals(t, 0, ps.StatusCount(models.ErroredPlanStatus))
}

View File

@@ -355,7 +355,7 @@ func setupE2E(t *testing.T) (server.EventsController, *vcsmocks.MockClient, *moc
// Mocks.
e2eVCSClient := vcsmocks.NewMockClient()
e2eStatusUpdater := mocks.NewMockCommitStatusUpdater()
e2eStatusUpdater := &events.DefaultCommitStatusUpdater{Client: e2eVCSClient}
e2eGithubGetter := mocks.NewMockGithubPullGetter()
e2eGitlabGetter := mocks.NewMockGitlabMergeRequestGetter()