Skip to content

Commit

Permalink
Test plan_executor (#177)
Browse files Browse the repository at this point in the history
* Refactor modified project detector and test

* Test plan_executor

* Unexport fields

* Update modified_projects_test.go
  • Loading branch information
lkysow authored and anubhavmishra committed Nov 1, 2017
1 parent 73d04fb commit eeb39c5
Show file tree
Hide file tree
Showing 9 changed files with 542 additions and 72 deletions.
76 changes: 76 additions & 0 deletions server/events/mocks/mock_modified_project_detector.go
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
}
85 changes: 85 additions & 0 deletions server/events/mocks/mock_project_pre_executor.go
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
}
87 changes: 87 additions & 0 deletions server/events/modified_projects.go
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
}
95 changes: 95 additions & 0 deletions server/events/modified_projects_test.go
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)
}
}
}
}
Loading

0 comments on commit eeb39c5

Please sign in to comment.