-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Refactor modified project detector and test * Test plan_executor * Unexport fields * Update modified_projects_test.go
- Loading branch information
1 parent
73d04fb
commit eeb39c5
Showing
9 changed files
with
542 additions
and
72 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
// Automatically generated by pegomock. DO NOT EDIT! | ||
// Source: github.com/hootsuite/atlantis/server/events (interfaces: ModifiedProjectDetector) | ||
|
||
package mocks | ||
|
||
import ( | ||
"reflect" | ||
|
||
models "github.com/hootsuite/atlantis/server/events/models" | ||
pegomock "github.com/petergtz/pegomock" | ||
) | ||
|
||
type MockModifiedProjectDetector struct { | ||
fail func(message string, callerSkip ...int) | ||
} | ||
|
||
func NewMockModifiedProjectDetector() *MockModifiedProjectDetector { | ||
return &MockModifiedProjectDetector{fail: pegomock.GlobalFailHandler} | ||
} | ||
|
||
func (mock *MockModifiedProjectDetector) GetModified(files []string) []models.Project { | ||
params := []pegomock.Param{files} | ||
result := pegomock.GetGenericMockFrom(mock).Invoke("GetModified", params, []reflect.Type{reflect.TypeOf((*[]models.Project)(nil)).Elem()}) | ||
var ret0 []models.Project | ||
if len(result) != 0 { | ||
if result[0] != nil { | ||
ret0 = result[0].([]models.Project) | ||
} | ||
} | ||
return ret0 | ||
} | ||
|
||
func (mock *MockModifiedProjectDetector) VerifyWasCalledOnce() *VerifierModifiedProjectDetector { | ||
return &VerifierModifiedProjectDetector{mock, pegomock.Times(1), nil} | ||
} | ||
|
||
func (mock *MockModifiedProjectDetector) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierModifiedProjectDetector { | ||
return &VerifierModifiedProjectDetector{mock, invocationCountMatcher, nil} | ||
} | ||
|
||
func (mock *MockModifiedProjectDetector) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierModifiedProjectDetector { | ||
return &VerifierModifiedProjectDetector{mock, invocationCountMatcher, inOrderContext} | ||
} | ||
|
||
type VerifierModifiedProjectDetector struct { | ||
mock *MockModifiedProjectDetector | ||
invocationCountMatcher pegomock.Matcher | ||
inOrderContext *pegomock.InOrderContext | ||
} | ||
|
||
func (verifier *VerifierModifiedProjectDetector) GetModified(files []string) *ModifiedProjectDetector_GetModified_OngoingVerification { | ||
params := []pegomock.Param{files} | ||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "GetModified", params) | ||
return &ModifiedProjectDetector_GetModified_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} | ||
} | ||
|
||
type ModifiedProjectDetector_GetModified_OngoingVerification struct { | ||
mock *MockModifiedProjectDetector | ||
methodInvocations []pegomock.MethodInvocation | ||
} | ||
|
||
func (c *ModifiedProjectDetector_GetModified_OngoingVerification) GetCapturedArguments() []string { | ||
files := c.GetAllCapturedArguments() | ||
return files[len(files)-1] | ||
} | ||
|
||
func (c *ModifiedProjectDetector_GetModified_OngoingVerification) GetAllCapturedArguments() (_param0 [][]string) { | ||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) | ||
if len(params) > 0 { | ||
_param0 = make([][]string, len(params[0])) | ||
for u, param := range params[0] { | ||
_param0[u] = param.([]string) | ||
} | ||
} | ||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
// Automatically generated by pegomock. DO NOT EDIT! | ||
// Source: github.com/hootsuite/atlantis/server/events (interfaces: ProjectPreExecutor) | ||
|
||
package mocks | ||
|
||
import ( | ||
"reflect" | ||
|
||
events "github.com/hootsuite/atlantis/server/events" | ||
models "github.com/hootsuite/atlantis/server/events/models" | ||
pegomock "github.com/petergtz/pegomock" | ||
) | ||
|
||
type MockProjectPreExecutor struct { | ||
fail func(message string, callerSkip ...int) | ||
} | ||
|
||
func NewMockProjectPreExecutor() *MockProjectPreExecutor { | ||
return &MockProjectPreExecutor{fail: pegomock.GlobalFailHandler} | ||
} | ||
|
||
func (mock *MockProjectPreExecutor) Execute(ctx *events.CommandContext, repoDir string, project models.Project) events.PreExecuteResult { | ||
params := []pegomock.Param{ctx, repoDir, project} | ||
result := pegomock.GetGenericMockFrom(mock).Invoke("Execute", params, []reflect.Type{reflect.TypeOf((*events.PreExecuteResult)(nil)).Elem()}) | ||
var ret0 events.PreExecuteResult | ||
if len(result) != 0 { | ||
if result[0] != nil { | ||
ret0 = result[0].(events.PreExecuteResult) | ||
} | ||
} | ||
return ret0 | ||
} | ||
|
||
func (mock *MockProjectPreExecutor) VerifyWasCalledOnce() *VerifierProjectPreExecutor { | ||
return &VerifierProjectPreExecutor{mock, pegomock.Times(1), nil} | ||
} | ||
|
||
func (mock *MockProjectPreExecutor) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierProjectPreExecutor { | ||
return &VerifierProjectPreExecutor{mock, invocationCountMatcher, nil} | ||
} | ||
|
||
func (mock *MockProjectPreExecutor) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierProjectPreExecutor { | ||
return &VerifierProjectPreExecutor{mock, invocationCountMatcher, inOrderContext} | ||
} | ||
|
||
type VerifierProjectPreExecutor struct { | ||
mock *MockProjectPreExecutor | ||
invocationCountMatcher pegomock.Matcher | ||
inOrderContext *pegomock.InOrderContext | ||
} | ||
|
||
func (verifier *VerifierProjectPreExecutor) Execute(ctx *events.CommandContext, repoDir string, project models.Project) *ProjectPreExecutor_Execute_OngoingVerification { | ||
params := []pegomock.Param{ctx, repoDir, project} | ||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Execute", params) | ||
return &ProjectPreExecutor_Execute_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} | ||
} | ||
|
||
type ProjectPreExecutor_Execute_OngoingVerification struct { | ||
mock *MockProjectPreExecutor | ||
methodInvocations []pegomock.MethodInvocation | ||
} | ||
|
||
func (c *ProjectPreExecutor_Execute_OngoingVerification) GetCapturedArguments() (*events.CommandContext, string, models.Project) { | ||
ctx, repoDir, project := c.GetAllCapturedArguments() | ||
return ctx[len(ctx)-1], repoDir[len(repoDir)-1], project[len(project)-1] | ||
} | ||
|
||
func (c *ProjectPreExecutor_Execute_OngoingVerification) GetAllCapturedArguments() (_param0 []*events.CommandContext, _param1 []string, _param2 []models.Project) { | ||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) | ||
if len(params) > 0 { | ||
_param0 = make([]*events.CommandContext, len(params[0])) | ||
for u, param := range params[0] { | ||
_param0[u] = param.(*events.CommandContext) | ||
} | ||
_param1 = make([]string, len(params[1])) | ||
for u, param := range params[1] { | ||
_param1[u] = param.(string) | ||
} | ||
_param2 = make([]models.Project, len(params[2])) | ||
for u, param := range params[2] { | ||
_param2[u] = param.(models.Project) | ||
} | ||
} | ||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package events | ||
|
||
import ( | ||
"path" | ||
"strings" | ||
|
||
"github.com/hootsuite/atlantis/server/events/models" | ||
"github.com/hootsuite/atlantis/server/logging" | ||
) | ||
|
||
//go:generate pegomock generate --use-experimental-model-gen --package mocks -o mocks/mock_modified_project_detector.go ModifiedProjectDetector | ||
|
||
type ModifiedProjectDetector interface { | ||
GetModified(log *logging.SimpleLogger, modifiedFiles []string, repoFullName string) []models.Project | ||
} | ||
|
||
// ModifiedProject handles figuring out which projects were modified in a pull | ||
// request. | ||
type ModifiedProject struct{} | ||
|
||
var excludeList = []string{"terraform.tfstate", "terraform.tfstate.backup", "_modules", "modules"} | ||
|
||
func (m *ModifiedProject) GetModified(log *logging.SimpleLogger, modifiedFiles []string, repoFullName string) []models.Project { | ||
var projects []models.Project | ||
|
||
modifiedTerraformFiles := m.filterToTerraform(modifiedFiles) | ||
if len(modifiedTerraformFiles) == 0 { | ||
return projects | ||
} | ||
log.Info("filtered modified files to %d non-module .tf files: %v", | ||
len(modifiedTerraformFiles), modifiedTerraformFiles) | ||
|
||
var paths []string | ||
for _, modifiedFile := range modifiedFiles { | ||
paths = append(paths, m.getProjectPath(modifiedFile)) | ||
} | ||
uniquePaths := m.unique(paths) | ||
for _, uniquePath := range uniquePaths { | ||
projects = append(projects, models.NewProject(repoFullName, uniquePath)) | ||
} | ||
log.Info("there are %d modified project(s) at path(s): %v", | ||
len(projects), strings.Join(uniquePaths, ", ")) | ||
return projects | ||
} | ||
|
||
func (m *ModifiedProject) filterToTerraform(files []string) []string { | ||
var filtered []string | ||
for _, fileName := range files { | ||
if !m.isInExcludeList(fileName) && strings.Contains(fileName, ".tf") { | ||
filtered = append(filtered, fileName) | ||
} | ||
} | ||
return filtered | ||
} | ||
|
||
func (m *ModifiedProject) isInExcludeList(fileName string) bool { | ||
for _, s := range excludeList { | ||
if strings.Contains(fileName, s) { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
// getProjectPath returns the path to the project relative to the repo root | ||
// if the project is at the root returns "." | ||
func (m *ModifiedProject) getProjectPath(modifiedFilePath string) string { | ||
dir := path.Dir(modifiedFilePath) | ||
if path.Base(dir) == "env" { | ||
// if the modified file was inside an env/ directory, we treat this specially and | ||
// run plan one level up | ||
return path.Dir(dir) | ||
} | ||
return dir | ||
} | ||
|
||
func (m *ModifiedProject) unique(strs []string) []string { | ||
hash := make(map[string]bool) | ||
var unique []string | ||
for _, s := range strs { | ||
if !hash[s] { | ||
unique = append(unique, s) | ||
hash[s] = true | ||
} | ||
} | ||
return unique | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package events_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/hootsuite/atlantis/server/events" | ||
"github.com/hootsuite/atlantis/server/logging" | ||
. "github.com/hootsuite/atlantis/testing" | ||
) | ||
|
||
var log = logging.NewNoopLogger() | ||
var modifiedRepo = "owner/repo" | ||
var m = events.ModifiedProject{} | ||
|
||
func TestGetModified_NoFiles(t *testing.T) { | ||
cases := []struct { | ||
description string | ||
files []string | ||
expProjectPaths []string | ||
}{ | ||
{ | ||
"If no files were modified then should return an empty list", | ||
nil, | ||
nil, | ||
}, | ||
{ | ||
"Should ignore non .tf files and return an empty list", | ||
[]string{"non-tf"}, | ||
nil, | ||
}, | ||
{ | ||
"Should ignore .tf files in a module directory and return an empty list", | ||
[]string{"_modules/file.tf", "modules/file.tf", "parent/_modules/file.tf", "parent/modules/file.tf"}, | ||
nil, | ||
}, | ||
{ | ||
"Should ignore tfstate files and return an empty list", | ||
[]string{"terraform.tfstate", "terraform.tfstate.backup", "parent/terraform.tfstate", "parent/terraform.tfstate.backup"}, | ||
nil, | ||
}, | ||
{ | ||
"Should ignore tfstate files and return an empty list", | ||
[]string{"terraform.tfstate", "terraform.tfstate.backup", "parent/terraform.tfstate", "parent/terraform.tfstate.backup"}, | ||
nil, | ||
}, | ||
{ | ||
"Should return '.' when changed file is at root", | ||
[]string{"a.tf"}, | ||
[]string{"."}, | ||
}, | ||
{ | ||
"Should return directory when changed file is in a dir", | ||
[]string{"parent/a.tf"}, | ||
[]string{"parent"}, | ||
}, | ||
{ | ||
"Should return parent dir when changed file is in an env/ dir", | ||
[]string{"env/a.tfvars"}, | ||
[]string{"."}, | ||
}, | ||
{ | ||
"Should de-duplicate when multiple files changed in the same dir", | ||
[]string{"root.tf", "env/env.tfvars", "parent/parent.tf", "parent/parent2.tf", "parent/child/child.tf", "parent/child/env/env.tfvars"}, | ||
[]string{".", "parent", "parent/child"}, | ||
}, | ||
} | ||
for _, c := range cases { | ||
t.Log(c.description) | ||
projects := m.GetModified(log, c.files, modifiedRepo) | ||
|
||
// Extract the paths from the projects. We use a slice here instead of a | ||
// map so we can test whether there are duplicates returned. | ||
var paths []string | ||
for _, project := range projects { | ||
paths = append(paths, project.Path) | ||
// Check that the project object has the repo set properly. | ||
Equals(t, modifiedRepo, project.RepoFullName) | ||
} | ||
Assert(t, len(c.expProjectPaths) == len(paths), | ||
"exp %d paths but found %d. They were %v", len(c.expProjectPaths), len(paths), paths) | ||
|
||
for _, expPath := range c.expProjectPaths { | ||
found := false | ||
for _, actPath := range paths { | ||
if expPath == actPath { | ||
found = true | ||
break | ||
} | ||
} | ||
if !found { | ||
t.Fatalf("exp %q but was not in paths %v", expPath, paths) | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.