diff --git a/Makefile b/Makefile index 0b2a2fc7d9..0dd62fbd35 100644 --- a/Makefile +++ b/Makefile @@ -38,10 +38,10 @@ install: ## install the binary go install -v ./... lint: # Run static code analysis, check formatting. See https://golangci-lint.run/ - golangci-lint run ./... -v + ./bin/golangci-lint run ./... -v lint-fix: ## Run static code analysis, check formatting and try to fix findings - golangci-lint run ./... -v --fix + ./bin/golangci-lint run ./... -v --fix mod: ## add missing and remove unused modules go mod tidy -compat=1.20 @@ -49,7 +49,7 @@ mod: ## add missing and remove unused modules mod-check: mod ## check if there are any missing/unused modules git diff --exit-code -- go.mod go.sum -pre-push: fmt docs mod lint ## Run a few checks before pushing a change (docs, fmt, mod, etc.) +pre-push: fmt docs mod lint test-architecture ## Run a few checks before pushing a change (docs, fmt, mod, etc.) pre-push-check: fmt-check docs-check lint-check mod-check ## Run a few checks before pushing a change (docs, fmt, mod, etc.) @@ -68,6 +68,9 @@ test: ## run unit and integration tests test-acceptance: ## run acceptance tests TF_ACC=1 go test -run "^TestAcc_" -v -cover -timeout=30m ./... +test-architecture: ## check architecture constraints between packages + go test ./pkg/architests/... -v + build-local: ## build the binary locally go build -o $(BASE_BINARY_NAME) . diff --git a/pkg/architest/architest_test.go b/pkg/architest/architest_test.go new file mode 100644 index 0000000000..419f78c503 --- /dev/null +++ b/pkg/architest/architest_test.go @@ -0,0 +1,357 @@ +package architest_test + +import ( + "fmt" + "regexp" + "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/architest" + "github.com/stretchr/testify/assert" +) + +func Test_Directory(t *testing.T) { + t.Run("fail to use non-existing directory", func(t *testing.T) { + assert.Panics(t, func() { + architest.Directory("testdata/non_existing") + }) + }) + + t.Run("use directory", func(t *testing.T) { + assert.NotPanics(t, func() { + architest.Directory("testdata/dir1") + }) + }) + + tests1 := []struct { + directory string + expectedFileNames []string + expectedPackageNames []string + }{ + {directory: "testdata/dir1", expectedFileNames: []string{"testdata/dir1/sample1.go", "testdata/dir1/sample2.go", "testdata/dir1/different1.go"}, expectedPackageNames: []string{"dir1"}}, + {directory: "testdata/dir2", expectedFileNames: []string{"testdata/dir2/sample1.go", "testdata/dir2/sample1_test.go"}, expectedPackageNames: []string{"dir2", "dir2_test"}}, + {directory: "testdata/dir3", expectedFileNames: []string{"testdata/dir3/sample1.go", "testdata/dir3/sample1_acceptance_test.go"}, expectedPackageNames: []string{"dir3", "dir3_test"}}, + {directory: "testdata/dir4", expectedFileNames: []string{"testdata/dir4/sample1.go", "testdata/dir4/sample1_integration_test.go"}, expectedPackageNames: []string{"dir4", "dir4_test"}}, + } + for _, tt := range tests1 { + t.Run("list all files in the given directory", func(t *testing.T) { + dir := architest.Directory(tt.directory) + + allFiles := dir.AllFiles() + assert.Len(t, allFiles, len(tt.expectedFileNames)) + + fileNames := make([]string, 0, len(allFiles)) + for _, f := range allFiles { + fileNames = append(fileNames, f.Name()) + } + assert.ElementsMatch(t, fileNames, tt.expectedFileNames) + + packageNames := make(map[string]bool) + for _, f := range allFiles { + packageNames[f.PackageName()] = true + } + assert.Len(t, packageNames, len(tt.expectedPackageNames)) + for _, name := range tt.expectedPackageNames { + assert.Contains(t, packageNames, name) + } + }) + } + + tests2 := []struct { + directory string + filter architest.FileFilter + expectedFileNames []string + }{ + {directory: "testdata/dir1", filter: architest.FileNameFilterProvider("sample"), expectedFileNames: []string{"testdata/dir1/sample1.go", "testdata/dir1/sample2.go"}}, + {directory: "testdata/dir1", filter: architest.FileNameRegexFilterProvider(regexp.MustCompile("sample")), expectedFileNames: []string{"testdata/dir1/sample1.go", "testdata/dir1/sample2.go"}}, + {directory: "testdata/dir1", filter: architest.FileNameFilterWithExclusionsProvider(regexp.MustCompile("sample"), regexp.MustCompile("sample1")), expectedFileNames: []string{"testdata/dir1/sample2.go"}}, + {directory: "testdata/dir2", filter: architest.PackageFilterProvider("dir2"), expectedFileNames: []string{"testdata/dir2/sample1.go"}}, + {directory: "testdata/dir2", filter: architest.PackageFilterProvider("dir2_test"), expectedFileNames: []string{"testdata/dir2/sample1_test.go"}}, + {directory: "testdata/dir2", filter: architest.FileNameRegexFilterProvider(architest.AcceptanceTestFileRegex), expectedFileNames: []string{}}, + {directory: "testdata/dir3", filter: architest.FileNameRegexFilterProvider(architest.AcceptanceTestFileRegex), expectedFileNames: []string{"testdata/dir3/sample1_acceptance_test.go"}}, + {directory: "testdata/dir4", filter: architest.FileNameRegexFilterProvider(architest.AcceptanceTestFileRegex), expectedFileNames: []string{}}, + {directory: "testdata/dir2", filter: architest.FileNameRegexFilterProvider(architest.IntegrationTestFileRegex), expectedFileNames: []string{}}, + {directory: "testdata/dir3", filter: architest.FileNameRegexFilterProvider(architest.IntegrationTestFileRegex), expectedFileNames: []string{}}, + {directory: "testdata/dir4", filter: architest.FileNameRegexFilterProvider(architest.IntegrationTestFileRegex), expectedFileNames: []string{"testdata/dir4/sample1_integration_test.go"}}, + {directory: "testdata/dir2", filter: architest.FileNameRegexFilterProvider(architest.TestFileRegex), expectedFileNames: []string{"testdata/dir2/sample1_test.go"}}, + {directory: "testdata/dir3", filter: architest.FileNameRegexFilterProvider(architest.TestFileRegex), expectedFileNames: []string{"testdata/dir3/sample1_acceptance_test.go"}}, + {directory: "testdata/dir4", filter: architest.FileNameRegexFilterProvider(architest.TestFileRegex), expectedFileNames: []string{"testdata/dir4/sample1_integration_test.go"}}, + } + for _, tt := range tests2 { + t.Run("list only files matching filter in the given directory", func(t *testing.T) { + dir := architest.Directory(tt.directory) + + filteredFiles := dir.Files(tt.filter) + assert.Len(t, filteredFiles, len(tt.expectedFileNames)) + + fileNames := make([]string, 0, len(filteredFiles)) + for _, f := range filteredFiles { + fileNames = append(fileNames, f.Name()) + } + assert.ElementsMatch(t, fileNames, tt.expectedFileNames) + + // now exactly the same but indirectly + filteredFiles = dir.AllFiles().Filter(tt.filter) + assert.Len(t, filteredFiles, len(tt.expectedFileNames)) + + fileNames = make([]string, 0, len(filteredFiles)) + for _, f := range filteredFiles { + fileNames = append(fileNames, f.Name()) + } + assert.ElementsMatch(t, fileNames, tt.expectedFileNames) + }) + } +} + +func Test_Files(t *testing.T) { + t.Run("fail to use non-existing file", func(t *testing.T) { + assert.Panics(t, func() { + architest.NewFileFromPath("testdata/dir1/non_existing.go") + }) + }) + + t.Run("use file", func(t *testing.T) { + assert.NotPanics(t, func() { + architest.NewFileFromPath("testdata/dir1/sample1.go") + }) + }) + + tests1 := []struct { + filePath string + expectedMethodNames []string + }{ + {filePath: "testdata/dir1/sample1.go", expectedMethodNames: []string{}}, + {filePath: "testdata/dir1/sample2.go", expectedMethodNames: []string{"A"}}, + } + for _, tt := range tests1 { + t.Run("list all methods in file", func(t *testing.T) { + file := architest.NewFileFromPath(tt.filePath) + + exportedMethods := file.ExportedMethods() + assert.Len(t, exportedMethods, len(tt.expectedMethodNames)) + + methodNames := make([]string, 0, len(exportedMethods)) + for _, m := range exportedMethods { + methodNames = append(methodNames, m.Name()) + } + assert.ElementsMatch(t, methodNames, tt.expectedMethodNames) + }) + } + + tests2 := []struct { + fileNames []string + }{ + {fileNames: []string{}}, + {fileNames: []string{"a"}}, + {fileNames: []string{"a", "A"}}, + {fileNames: []string{"A", "a"}}, + {fileNames: []string{"A", "B", "C"}}, + } + for _, tt := range tests2 { + t.Run("receiver invoked for every file", func(t *testing.T) { + files := make(architest.Files, 0, len(tt.fileNames)) + for _, f := range tt.fileNames { + files = append(files, *architest.NewFile("package", f, nil)) + } + invokedFiles := make([]string, 0) + receiver := func(f *architest.File) { + invokedFiles = append(invokedFiles, f.Name()) + } + + files.All(receiver) + + assert.ElementsMatch(t, tt.fileNames, invokedFiles) + }) + } +} + +func Test_Methods(t *testing.T) { + tests := []struct { + methodNames []string + }{ + {methodNames: []string{}}, + {methodNames: []string{"a"}}, + {methodNames: []string{"a", "A"}}, + {methodNames: []string{"A", "a"}}, + {methodNames: []string{"A", "B", "C"}}, + } + for _, tt := range tests { + t.Run("receiver invoked for every method", func(t *testing.T) { + methods := make(architest.Methods, 0, len(tt.methodNames)) + for _, m := range tt.methodNames { + methods = append(methods, *architest.NewMethod(m, nil)) + } + invokedMethods := make([]string, 0) + receiver := func(m *architest.Method) { + invokedMethods = append(invokedMethods, m.Name()) + } + + methods.All(receiver) + + assert.ElementsMatch(t, tt.methodNames, invokedMethods) + }) + } +} + +func Test_Assertions(t *testing.T) { + tests1 := []struct { + filePath string + expectedPackage string + }{ + {filePath: "testdata/dir1/sample1.go", expectedPackage: "dir1"}, + {filePath: "testdata/dir2/sample1.go", expectedPackage: "dir2"}, + {filePath: "testdata/dir2/sample1_test.go", expectedPackage: "dir2_test"}, + } + for _, tt := range tests1 { + t.Run("file package assertions", func(t *testing.T) { + file := architest.NewFileFromPath(tt.filePath) + tut1 := &testing.T{} + tut2 := &testing.T{} + + file.AssertHasPackage(tut1, tt.expectedPackage) + file.AssertHasPackage(tut2, "some_other_package") + + assert.Equal(t, false, tut1.Failed()) + assert.Equal(t, true, tut2.Failed()) + }) + } + + tests2 := []struct { + methodName string + correct bool + }{ + {methodName: "TestAcc_abc", correct: true}, + {methodName: "TestAcc_TestAcc_Test", correct: true}, + {methodName: "TestAcc_", correct: false}, + {methodName: "ATestAcc_", correct: false}, + {methodName: "TestAcc", correct: false}, + {methodName: "Test_", correct: false}, + {methodName: "Test", correct: false}, + {methodName: "Test_asdf", correct: false}, + {methodName: "TestAccc_", correct: false}, + {methodName: "TestInt_Abc", correct: false}, + } + for _, tt := range tests2 { + t.Run(fmt.Sprintf("acceptance test name assertions for method %s", tt.methodName), func(t *testing.T) { + file := architest.NewFileFromPath("testdata/dir1/sample1.go") + method := architest.NewMethod(tt.methodName, file) + tut := &testing.T{} + + method.AssertAcceptanceTestNamedCorrectly(tut) + + assert.Equal(t, !tt.correct, tut.Failed()) + }) + } + + tests3 := []struct { + methodName string + regexRaw string + correct bool + }{ + {methodName: "sample1", regexRaw: "sample", correct: true}, + {methodName: "Sample1", regexRaw: "sample", correct: false}, + {methodName: "Sample1", regexRaw: "Sample", correct: true}, + } + for _, tt := range tests3 { + t.Run(fmt.Sprintf("matching and not matching method name assertions for %s", tt.methodName), func(t *testing.T) { + file := architest.NewFileFromPath("testdata/dir1/sample1.go") + method := architest.NewMethod(tt.methodName, file) + tut1 := &testing.T{} + tut2 := &testing.T{} + + method.AssertNameMatches(tut1, regexp.MustCompile(tt.regexRaw)) + method.AssertNameDoesNotMatch(tut2, regexp.MustCompile(tt.regexRaw)) + + assert.Equal(t, !tt.correct, tut1.Failed()) + assert.Equal(t, tt.correct, tut2.Failed()) + }) + } + + tests4 := []struct { + methodName string + correct bool + }{ + {methodName: "Test", correct: true}, + {methodName: "aTest", correct: false}, + {methodName: "Test_", correct: true}, + {methodName: "Test_adsfadf", correct: true}, + } + for _, tt := range tests4 { + t.Run(fmt.Sprintf("test name assertions for method %s", tt.methodName), func(t *testing.T) { + file := architest.NewFileFromPath("testdata/dir1/sample1.go") + method := architest.NewMethod(tt.methodName, file) + tut := &testing.T{} + + method.AssertTestNamedCorrectly(tut) + + assert.Equal(t, !tt.correct, tut.Failed()) + }) + } + + tests5 := []struct { + methodName string + correct bool + }{ + {methodName: "TestInt_abc", correct: true}, + {methodName: "TestInt_TestInt_Test", correct: true}, + {methodName: "TestInt_", correct: false}, + {methodName: "ATestInt_", correct: false}, + {methodName: "TestInt", correct: false}, + {methodName: "Test_", correct: false}, + {methodName: "Test", correct: false}, + {methodName: "Test_asdf", correct: false}, + {methodName: "TestIntt_", correct: false}, + {methodName: "TestAcc_Abc", correct: false}, + } + for _, tt := range tests5 { + t.Run(fmt.Sprintf("intagration test name assertions for method %s", tt.methodName), func(t *testing.T) { + file := architest.NewFileFromPath("testdata/dir1/sample1.go") + method := architest.NewMethod(tt.methodName, file) + tut := &testing.T{} + + method.AssertIntegrationTestNamedCorrectly(tut) + + assert.Equal(t, !tt.correct, tut.Failed()) + }) + } +} + +func Test_SampleArchiTestUsage(t *testing.T) { + t.Run("acceptance tests", func(t *testing.T) { + acceptanceTestFiles := architest.Directory("testdata/dir3/"). + AllFiles(). + Filter(architest.FileNameRegexFilterProvider(architest.AcceptanceTestFileRegex)) + + acceptanceTestFiles.All(func(file *architest.File) { + file.AssertHasPackage(t, "dir3_test") + file.ExportedMethods().All(func(method *architest.Method) { + method.AssertAcceptanceTestNamedCorrectly(t) + }) + }) + }) + + t.Run("integration tests", func(t *testing.T) { + integrationTestFiles := architest.Directory("testdata/dir4/"). + AllFiles(). + Filter(architest.FileNameRegexFilterProvider(architest.IntegrationTestFileRegex)) + + integrationTestFiles.All(func(file *architest.File) { + file.AssertHasPackage(t, "dir4_test") + file.ExportedMethods().All(func(method *architest.Method) { + method.AssertIntegrationTestNamedCorrectly(t) + }) + }) + }) + + t.Run("tests", func(t *testing.T) { + testFiles := architest.Directory("testdata/dir2/"). + AllFiles(). + Filter(architest.FileNameRegexFilterProvider(architest.TestNameRegex)) + + testFiles.All(func(file *architest.File) { + file.AssertHasPackage(t, "dir2_test") + file.ExportedMethods().All(func(method *architest.Method) { + method.AssertTestNamedCorrectly(t) + }) + }) + }) +} diff --git a/pkg/architest/assertions.go b/pkg/architest/assertions.go new file mode 100644 index 0000000000..41859482de --- /dev/null +++ b/pkg/architest/assertions.go @@ -0,0 +1,38 @@ +package architest + +import ( + "regexp" + "testing" + + "github.com/stretchr/testify/assert" +) + +func (f *File) AssertHasPackage(t *testing.T, expectedPackage string) { + t.Helper() + assert.Equalf(t, expectedPackage, f.packageName, "file %s has package %s, expected package %s", f.Name(), f.PackageName(), expectedPackage) +} + +func (method *Method) AssertAcceptanceTestNamedCorrectly(t *testing.T) { + t.Helper() + method.AssertNameMatches(t, AcceptanceTestNameRegex) +} + +func (method *Method) AssertIntegrationTestNamedCorrectly(t *testing.T) { + t.Helper() + method.AssertNameMatches(t, IntegrationTestNameRegex) +} + +func (method *Method) AssertTestNamedCorrectly(t *testing.T) { + t.Helper() + method.AssertNameMatches(t, TestNameRegex) +} + +func (method *Method) AssertNameMatches(t *testing.T, regex *regexp.Regexp) { + t.Helper() + assert.Truef(t, regex.MatchString(method.Name()), "file %s contains exported method %s which does not match %s", method.FileName(), method.Name(), regex.String()) +} + +func (method *Method) AssertNameDoesNotMatch(t *testing.T, regex *regexp.Regexp) { + t.Helper() + assert.Falsef(t, regex.MatchString(method.Name()), "file %s contains exported method %s which matches %s", method.FileName(), method.Name(), regex.String()) +} diff --git a/pkg/architest/directory.go b/pkg/architest/directory.go new file mode 100644 index 0000000000..d289765632 --- /dev/null +++ b/pkg/architest/directory.go @@ -0,0 +1,41 @@ +package architest + +import ( + "go/parser" + "go/token" +) + +type directory struct { + path string + files Files +} + +type FilesProvider interface { + AllFiles() Files + Files(filter FileFilter) Files +} + +func Directory(path string) FilesProvider { + packagesDict, err := parser.ParseDir(token.NewFileSet(), path, nil, 0) + if err != nil { + panic(err) + } + files := make(Files, 0) + for packageName, astPackage := range packagesDict { + for fileName, fileSrc := range astPackage.Files { + files = append(files, *NewFile(packageName, fileName, fileSrc)) + } + } + return &directory{ + path: path, + files: files, + } +} + +func (d *directory) AllFiles() Files { + return d.files +} + +func (d *directory) Files(filter FileFilter) Files { + return d.AllFiles().Filter(filter) +} diff --git a/pkg/architest/file.go b/pkg/architest/file.go new file mode 100644 index 0000000000..a2d781a719 --- /dev/null +++ b/pkg/architest/file.go @@ -0,0 +1,51 @@ +package architest + +import ( + "go/ast" + "go/parser" + "go/token" + "path/filepath" +) + +type File struct { + packageName string + fileName string + fileSrc *ast.File +} + +func NewFile(packageName string, fileName string, fileSrc *ast.File) *File { + return &File{ + packageName: packageName, + fileName: fileName, + fileSrc: fileSrc, + } +} + +func NewFileFromPath(path string) *File { + file, err := parser.ParseFile(token.NewFileSet(), path, nil, 0) + if err != nil { + panic(err) + } + return NewFile(file.Name.Name, filepath.Base(path), file) +} + +func (f *File) PackageName() string { + return f.packageName +} + +func (f *File) Name() string { + return f.fileName +} + +func (f *File) ExportedMethods() Methods { + allExportedMethods := make(Methods, 0) + for _, d := range f.fileSrc.Decls { + if v, ok := d.(*ast.FuncDecl); ok { + name := v.Name.Name + if ast.IsExported(name) { + allExportedMethods = append(allExportedMethods, *NewMethod(name, f)) + } + } + } + return allExportedMethods +} diff --git a/pkg/architest/file_filter_providers.go b/pkg/architest/file_filter_providers.go new file mode 100644 index 0000000000..4f59d07dc0 --- /dev/null +++ b/pkg/architest/file_filter_providers.go @@ -0,0 +1,35 @@ +package architest + +import ( + "fmt" + "regexp" +) + +func FileNameFilterProvider(text string) FileFilter { + regex := regexp.MustCompile(fmt.Sprintf(`^.*%s.*$`, text)) + return func(f *File) bool { + return regex.Match([]byte(f.fileName)) + } +} + +func FileNameRegexFilterProvider(regex *regexp.Regexp) FileFilter { + return func(f *File) bool { + return regex.Match([]byte(f.fileName)) + } +} + +func FileNameFilterWithExclusionsProvider(regex *regexp.Regexp, exclusionRegex ...*regexp.Regexp) FileFilter { + return func(f *File) bool { + matches := regex.MatchString(f.fileName) + for _, e := range exclusionRegex { + matches = matches && !e.MatchString(f.fileName) + } + return matches + } +} + +func PackageFilterProvider(packageName string) FileFilter { + return func(f *File) bool { + return f.packageName == packageName + } +} diff --git a/pkg/architest/files.go b/pkg/architest/files.go new file mode 100644 index 0000000000..6b73d2b039 --- /dev/null +++ b/pkg/architest/files.go @@ -0,0 +1,25 @@ +package architest + +type ( + FileFilter = func(*File) bool + FileReceiver = func(*File) + Files []File +) + +func (files Files) Filter(filter FileFilter) Files { + filteredFiles := make([]File, 0) + for _, f := range files { + f := f + if filter(&f) { + filteredFiles = append(filteredFiles, f) + } + } + return filteredFiles +} + +func (files Files) All(receiver FileReceiver) { + for _, file := range files { + file := file + receiver(&file) + } +} diff --git a/pkg/architest/method.go b/pkg/architest/method.go new file mode 100644 index 0000000000..17bbe597c3 --- /dev/null +++ b/pkg/architest/method.go @@ -0,0 +1,21 @@ +package architest + +type Method struct { + name string + file *File +} + +func (method *Method) Name() string { + return method.name +} + +func (method *Method) FileName() string { + return method.file.Name() +} + +func NewMethod(name string, file *File) *Method { + return &Method{ + name: name, + file: file, + } +} diff --git a/pkg/architest/methods.go b/pkg/architest/methods.go new file mode 100644 index 0000000000..b94f8a8d75 --- /dev/null +++ b/pkg/architest/methods.go @@ -0,0 +1,13 @@ +package architest + +type ( + MethodReceiver = func(method *Method) + Methods []Method +) + +func (methods Methods) All(receiver MethodReceiver) { + for _, method := range methods { + method := method + receiver(&method) + } +} diff --git a/pkg/architest/regex.go b/pkg/architest/regex.go new file mode 100644 index 0000000000..2876dfed79 --- /dev/null +++ b/pkg/architest/regex.go @@ -0,0 +1,12 @@ +package architest + +import "regexp" + +var ( + AcceptanceTestFileRegex = regexp.MustCompile("^.*_acceptance_test.go$") + AcceptanceTestNameRegex = regexp.MustCompile("^TestAcc_.+$") + IntegrationTestFileRegex = regexp.MustCompile("^.*_integration_test.go$") + IntegrationTestNameRegex = regexp.MustCompile("^TestInt_.+$") + TestFileRegex = regexp.MustCompile("^.*_test.go$") + TestNameRegex = regexp.MustCompile("^Test.*$") +) diff --git a/pkg/architest/testdata/dir1/different1.go b/pkg/architest/testdata/dir1/different1.go new file mode 100644 index 0000000000..b719eadc09 --- /dev/null +++ b/pkg/architest/testdata/dir1/different1.go @@ -0,0 +1 @@ +package dir1 diff --git a/pkg/architest/testdata/dir1/sample1.go b/pkg/architest/testdata/dir1/sample1.go new file mode 100644 index 0000000000..b719eadc09 --- /dev/null +++ b/pkg/architest/testdata/dir1/sample1.go @@ -0,0 +1 @@ +package dir1 diff --git a/pkg/architest/testdata/dir1/sample2.go b/pkg/architest/testdata/dir1/sample2.go new file mode 100644 index 0000000000..7ba18f8743 --- /dev/null +++ b/pkg/architest/testdata/dir1/sample2.go @@ -0,0 +1,5 @@ +package dir1 + +func A() {} + +func a() {} diff --git a/pkg/architest/testdata/dir2/sample1.go b/pkg/architest/testdata/dir2/sample1.go new file mode 100644 index 0000000000..6fe35e9e59 --- /dev/null +++ b/pkg/architest/testdata/dir2/sample1.go @@ -0,0 +1 @@ +package dir2 diff --git a/pkg/architest/testdata/dir2/sample1_test.go b/pkg/architest/testdata/dir2/sample1_test.go new file mode 100644 index 0000000000..f263d411f3 --- /dev/null +++ b/pkg/architest/testdata/dir2/sample1_test.go @@ -0,0 +1,7 @@ +package dir2_test + +import "testing" + +func Test(t *testing.T) { + t.Skip("Test for example purposes") +} diff --git a/pkg/architest/testdata/dir3/sample1.go b/pkg/architest/testdata/dir3/sample1.go new file mode 100644 index 0000000000..6ea203550b --- /dev/null +++ b/pkg/architest/testdata/dir3/sample1.go @@ -0,0 +1 @@ +package dir3 diff --git a/pkg/architest/testdata/dir3/sample1_acceptance_test.go b/pkg/architest/testdata/dir3/sample1_acceptance_test.go new file mode 100644 index 0000000000..0eab424337 --- /dev/null +++ b/pkg/architest/testdata/dir3/sample1_acceptance_test.go @@ -0,0 +1,7 @@ +package dir3_test + +import "testing" + +func TestAcc_Abc(t *testing.T) { + t.Skip("Test for example purposes") +} diff --git a/pkg/architest/testdata/dir4/sample1.go b/pkg/architest/testdata/dir4/sample1.go new file mode 100644 index 0000000000..5f6819e3fe --- /dev/null +++ b/pkg/architest/testdata/dir4/sample1.go @@ -0,0 +1 @@ +package dir4 diff --git a/pkg/architest/testdata/dir4/sample1_integration_test.go b/pkg/architest/testdata/dir4/sample1_integration_test.go new file mode 100644 index 0000000000..33cedbbf4e --- /dev/null +++ b/pkg/architest/testdata/dir4/sample1_integration_test.go @@ -0,0 +1,7 @@ +package dir4_test + +import "testing" + +func TestInt_Abc(t *testing.T) { + t.Skip("Test for example purposes") +} diff --git a/pkg/architests/datasources_acceptance_tests_arch_test.go b/pkg/architests/datasources_acceptance_tests_arch_test.go new file mode 100644 index 0000000000..8131db1b8c --- /dev/null +++ b/pkg/architests/datasources_acceptance_tests_arch_test.go @@ -0,0 +1,47 @@ +package architests + +import ( + "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/architest" +) + +func TestArchCheck_AcceptanceTests_DataSources(t *testing.T) { + datasourcesFiles := architest.Directory("../datasources/").AllFiles() + acceptanceTestFiles := datasourcesFiles.Filter(architest.FileNameRegexFilterProvider(architest.AcceptanceTestFileRegex)) + + t.Run("acceptance tests files have the right package", func(t *testing.T) { + acceptanceTestFiles.All(func(file *architest.File) { + file.AssertHasPackage(t, "datasources_test") + }) + }) + + t.Run("acceptance tests are named correctly", func(t *testing.T) { + acceptanceTestFiles.All(func(file *architest.File) { + file.ExportedMethods().All(func(method *architest.Method) { + method.AssertAcceptanceTestNamedCorrectly(t) + }) + }) + }) + + t.Run("there are no acceptance tests in other test files in the directory", func(t *testing.T) { + otherTestFiles := datasourcesFiles.Filter(architest.FileNameFilterWithExclusionsProvider(architest.TestFileRegex, architest.AcceptanceTestFileRegex)) + + otherTestFiles.All(func(file *architest.File) { + file.ExportedMethods().All(func(method *architest.Method) { + method.AssertNameDoesNotMatch(t, architest.AcceptanceTestNameRegex) + method.AssertTestNamedCorrectly(t) + }) + }) + }) + + t.Run("there are only acceptance tests in package datasources_test", func(t *testing.T) { + packageFiles := datasourcesFiles.Filter(architest.PackageFilterProvider("datasources_test")) + + packageFiles.All(func(file *architest.File) { + file.ExportedMethods().All(func(method *architest.Method) { + method.AssertAcceptanceTestNamedCorrectly(t) + }) + }) + }) +} diff --git a/pkg/architests/resources_acceptance_tests_arch_test.go b/pkg/architests/resources_acceptance_tests_arch_test.go new file mode 100644 index 0000000000..008df3df01 --- /dev/null +++ b/pkg/architests/resources_acceptance_tests_arch_test.go @@ -0,0 +1,48 @@ +package architests + +import ( + "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/architest" +) + +func TestArchCheck_AcceptanceTests_Resources(t *testing.T) { + resourcesFiles := architest.Directory("../resources/").AllFiles() + acceptanceTestFiles := resourcesFiles.Filter(architest.FileNameRegexFilterProvider(architest.AcceptanceTestFileRegex)) + + t.Run("acceptance tests files have the right package", func(t *testing.T) { + acceptanceTestFiles.All(func(file *architest.File) { + file.AssertHasPackage(t, "resources_test") + }) + }) + + t.Run("acceptance tests are named correctly", func(t *testing.T) { + acceptanceTestFiles.All(func(file *architest.File) { + file.ExportedMethods().All(func(method *architest.Method) { + method.AssertAcceptanceTestNamedCorrectly(t) + }) + }) + }) + + t.Run("there are no acceptance tests in other test files in the directory", func(t *testing.T) { + otherTestFiles := resourcesFiles.Filter(architest.FileNameFilterWithExclusionsProvider(architest.TestFileRegex, architest.AcceptanceTestFileRegex)) + + otherTestFiles.All(func(file *architest.File) { + file.ExportedMethods().All(func(method *architest.Method) { + method.AssertNameDoesNotMatch(t, architest.AcceptanceTestNameRegex) + method.AssertTestNamedCorrectly(t) + }) + }) + }) + + t.Run("there are only acceptance tests in package resources_test", func(t *testing.T) { + t.Skipf("Currently there are non-acceptance tests in resources_test package") + packageFiles := resourcesFiles.Filter(architest.PackageFilterProvider("resources_test")) + + packageFiles.All(func(file *architest.File) { + file.ExportedMethods().All(func(method *architest.Method) { + method.AssertAcceptanceTestNamedCorrectly(t) + }) + }) + }) +} diff --git a/pkg/architests/sdk_integration_tests_arch_test.go b/pkg/architests/sdk_integration_tests_arch_test.go new file mode 100644 index 0000000000..2afb157dfe --- /dev/null +++ b/pkg/architests/sdk_integration_tests_arch_test.go @@ -0,0 +1,50 @@ +package architests + +import ( + "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/architest" +) + +func TestArchCheck_IntegrationTests_Sdk(t *testing.T) { + sdkIntegrationTestFiles := architest.Directory("../sdk/testint/").AllFiles() + integrationTestFiles := sdkIntegrationTestFiles.Filter(architest.FileNameRegexFilterProvider(architest.IntegrationTestFileRegex)) + + t.Run("integration tests files have the right package", func(t *testing.T) { + integrationTestFiles.All(func(file *architest.File) { + file.AssertHasPackage(t, "testint") + }) + }) + + t.Run("integration tests are named correctly", func(t *testing.T) { + integrationTestFiles.All(func(file *architest.File) { + file.ExportedMethods().All(func(method *architest.Method) { + method.AssertIntegrationTestNamedCorrectly(t) + }) + }) + }) + + t.Run("there are no integration tests in other test files in the directory", func(t *testing.T) { + otherTestFiles := sdkIntegrationTestFiles.Filter(architest.FileNameFilterWithExclusionsProvider(architest.TestFileRegex, architest.IntegrationTestFileRegex)) + + otherTestFiles.All(func(file *architest.File) { + file.ExportedMethods().All(func(method *architest.Method) { + method.AssertNameDoesNotMatch(t, architest.IntegrationTestNameRegex) + method.AssertTestNamedCorrectly(t) + }) + }) + }) + + t.Run("there are only integration tests in package testint", func(t *testing.T) { + packageFiles := sdkIntegrationTestFiles.Filter(architest.PackageFilterProvider("testint")) + + packageFiles.All(func(file *architest.File) { + file.ExportedMethods().All(func(method *architest.Method) { + // our integration tests have TestMain, let's filter it out now (maybe later we can support in in architest) + if method.Name() != "TestMain" { + method.AssertIntegrationTestNamedCorrectly(t) + } + }) + }) + }) +} diff --git a/pkg/resources/database_grant_acceptance_test.go b/pkg/resources/database_grant_acceptance_test.go index 1bb7e9e040..55de7bf4c3 100644 --- a/pkg/resources/database_grant_acceptance_test.go +++ b/pkg/resources/database_grant_acceptance_test.go @@ -16,7 +16,7 @@ func testRolesAndShares(t *testing.T, path string, roles []string) func(*terrafo return func(state *terraform.State) error { is := state.RootModule().Resources[path].Primary - if c, ok := is.Attributes["roles.#"]; !ok || MustParseInt(t, c) != int64(len(roles)) { + if c, ok := is.Attributes["roles.#"]; !ok || mustParseInt(t, c) != int64(len(roles)) { return fmt.Errorf("expected roles.# to equal %d but got %s", len(roles), c) } r, err := extractList(is.Attributes, "roles") diff --git a/pkg/resources/role_grants_acceptance_test.go b/pkg/resources/role_grants_acceptance_test.go index 8bb7f0f403..0c143abd6a 100644 --- a/pkg/resources/role_grants_acceptance_test.go +++ b/pkg/resources/role_grants_acceptance_test.go @@ -15,7 +15,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/terraform" ) -func MustParseInt(t *testing.T, input string) int64 { +func mustParseInt(t *testing.T, input string) int64 { t.Helper() i, err := strconv.ParseInt(input, 10, 64) if err != nil { @@ -61,7 +61,7 @@ func testCheckRolesAndUsers(t *testing.T, path string, roles, users []string) fu t.Helper() return func(state *terraform.State) error { is := state.RootModule().Resources[path].Primary - if c, ok := is.Attributes["roles.#"]; !ok || MustParseInt(t, c) != int64(len(roles)) { + if c, ok := is.Attributes["roles.#"]; !ok || mustParseInt(t, c) != int64(len(roles)) { return fmt.Errorf("expected roles.# to equal %d but got %s", len(roles), c) } r, err := extractList(is.Attributes, "roles") @@ -74,7 +74,7 @@ func testCheckRolesAndUsers(t *testing.T, path string, roles, users []string) fu return fmt.Errorf("expected roles %#v but got %#v", roles, r) } - if c, ok := is.Attributes["users.#"]; !ok || MustParseInt(t, c) != int64(len(users)) { + if c, ok := is.Attributes["users.#"]; !ok || mustParseInt(t, c) != int64(len(users)) { return fmt.Errorf("expected users.# to equal %d but got %s", len(users), c) } u, err := extractList(is.Attributes, "users") diff --git a/pkg/sdk/testint/parsers.go b/pkg/sdk/testint/parsers.go index 86d8a90fef..99114fb599 100644 --- a/pkg/sdk/testint/parsers.go +++ b/pkg/sdk/testint/parsers.go @@ -2,7 +2,7 @@ package testint import "time" -func ParseTimestampWithOffset(s string) (*time.Time, error) { +func parseTimestampWithOffset(s string) (*time.Time, error) { t, err := time.Parse("2006-01-02T15:04:05-07:00", s) if err != nil { return nil, err diff --git a/pkg/sdk/testint/resource_monitors_integration_test.go b/pkg/sdk/testint/resource_monitors_integration_test.go index 86e19aa5fc..904a198c8d 100644 --- a/pkg/sdk/testint/resource_monitors_integration_test.go +++ b/pkg/sdk/testint/resource_monitors_integration_test.go @@ -235,9 +235,9 @@ func TestInt_ResourceMonitorAlter(t *testing.T) { assert.Equal(t, 1, len(resourceMonitors)) resourceMonitor = &resourceMonitors[0] assert.Equal(t, *frequency, resourceMonitor.Frequency) - startTime, err := ParseTimestampWithOffset(resourceMonitor.StartTime) + startTime, err := parseTimestampWithOffset(resourceMonitor.StartTime) require.NoError(t, err) - endTime, err := ParseTimestampWithOffset(resourceMonitor.EndTime) + endTime, err := parseTimestampWithOffset(resourceMonitor.EndTime) require.NoError(t, err) assert.Equal(t, startTimeStamp, startTime.Format("2006-01-01 15:04")) assert.Equal(t, endTimeStamp, endTime.Format("2006-01-01 15:04")) diff --git a/pkg/sdk/testint/setup_integration_test.go b/pkg/sdk/testint/setup_test.go similarity index 100% rename from pkg/sdk/testint/setup_integration_test.go rename to pkg/sdk/testint/setup_test.go