Skip to content

Commit c60db80

Browse files
feat: Introduce simple arch tests (#2210)
* Add naive file getting * Test naively (resources) * Split into files * Add assertion for non-acceptance tests * Reorganize * Extract files * Extract directory * Extract method * Rename to archtest * Split lib and our usage into 2 different packages * Introduce datasources tests * Start testing archtest * Parametrize directories test * Change API * Rename to architest * Add nicer assertions * Add file to method * Rename to exported methods * Test file * Test file filtering * Add tests for package assertions * Test rest of the assertions * Test invocation on every file and method * Test creation of directory and file * Add tests for sdk integration tests * Add tests integration tests matchers * Fix lint and stuff * Add sample usage * Fix test setup * Fix after review * Introduce FilesProvider * Add recipe
1 parent 6d7f833 commit c60db80

27 files changed

+782
-10
lines changed

Makefile

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,18 @@ install: ## install the binary
3838
go install -v ./...
3939

4040
lint: # Run static code analysis, check formatting. See https://golangci-lint.run/
41-
golangci-lint run ./... -v
41+
./bin/golangci-lint run ./... -v
4242

4343
lint-fix: ## Run static code analysis, check formatting and try to fix findings
44-
golangci-lint run ./... -v --fix
44+
./bin/golangci-lint run ./... -v --fix
4545

4646
mod: ## add missing and remove unused modules
4747
go mod tidy -compat=1.20
4848

4949
mod-check: mod ## check if there are any missing/unused modules
5050
git diff --exit-code -- go.mod go.sum
5151

52-
pre-push: fmt docs mod lint ## Run a few checks before pushing a change (docs, fmt, mod, etc.)
52+
pre-push: fmt docs mod lint test-architecture ## Run a few checks before pushing a change (docs, fmt, mod, etc.)
5353

5454
pre-push-check: fmt-check docs-check lint-check mod-check ## Run a few checks before pushing a change (docs, fmt, mod, etc.)
5555

@@ -68,6 +68,9 @@ test: ## run unit and integration tests
6868
test-acceptance: ## run acceptance tests
6969
TF_ACC=1 go test -run "^TestAcc_" -v -cover -timeout=30m ./...
7070

71+
test-architecture: ## check architecture constraints between packages
72+
go test ./pkg/architests/... -v
73+
7174
build-local: ## build the binary locally
7275
go build -o $(BASE_BINARY_NAME) .
7376

pkg/architest/architest_test.go

Lines changed: 357 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,357 @@
1+
package architest_test
2+
3+
import (
4+
"fmt"
5+
"regexp"
6+
"testing"
7+
8+
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/architest"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func Test_Directory(t *testing.T) {
13+
t.Run("fail to use non-existing directory", func(t *testing.T) {
14+
assert.Panics(t, func() {
15+
architest.Directory("testdata/non_existing")
16+
})
17+
})
18+
19+
t.Run("use directory", func(t *testing.T) {
20+
assert.NotPanics(t, func() {
21+
architest.Directory("testdata/dir1")
22+
})
23+
})
24+
25+
tests1 := []struct {
26+
directory string
27+
expectedFileNames []string
28+
expectedPackageNames []string
29+
}{
30+
{directory: "testdata/dir1", expectedFileNames: []string{"testdata/dir1/sample1.go", "testdata/dir1/sample2.go", "testdata/dir1/different1.go"}, expectedPackageNames: []string{"dir1"}},
31+
{directory: "testdata/dir2", expectedFileNames: []string{"testdata/dir2/sample1.go", "testdata/dir2/sample1_test.go"}, expectedPackageNames: []string{"dir2", "dir2_test"}},
32+
{directory: "testdata/dir3", expectedFileNames: []string{"testdata/dir3/sample1.go", "testdata/dir3/sample1_acceptance_test.go"}, expectedPackageNames: []string{"dir3", "dir3_test"}},
33+
{directory: "testdata/dir4", expectedFileNames: []string{"testdata/dir4/sample1.go", "testdata/dir4/sample1_integration_test.go"}, expectedPackageNames: []string{"dir4", "dir4_test"}},
34+
}
35+
for _, tt := range tests1 {
36+
t.Run("list all files in the given directory", func(t *testing.T) {
37+
dir := architest.Directory(tt.directory)
38+
39+
allFiles := dir.AllFiles()
40+
assert.Len(t, allFiles, len(tt.expectedFileNames))
41+
42+
fileNames := make([]string, 0, len(allFiles))
43+
for _, f := range allFiles {
44+
fileNames = append(fileNames, f.Name())
45+
}
46+
assert.ElementsMatch(t, fileNames, tt.expectedFileNames)
47+
48+
packageNames := make(map[string]bool)
49+
for _, f := range allFiles {
50+
packageNames[f.PackageName()] = true
51+
}
52+
assert.Len(t, packageNames, len(tt.expectedPackageNames))
53+
for _, name := range tt.expectedPackageNames {
54+
assert.Contains(t, packageNames, name)
55+
}
56+
})
57+
}
58+
59+
tests2 := []struct {
60+
directory string
61+
filter architest.FileFilter
62+
expectedFileNames []string
63+
}{
64+
{directory: "testdata/dir1", filter: architest.FileNameFilterProvider("sample"), expectedFileNames: []string{"testdata/dir1/sample1.go", "testdata/dir1/sample2.go"}},
65+
{directory: "testdata/dir1", filter: architest.FileNameRegexFilterProvider(regexp.MustCompile("sample")), expectedFileNames: []string{"testdata/dir1/sample1.go", "testdata/dir1/sample2.go"}},
66+
{directory: "testdata/dir1", filter: architest.FileNameFilterWithExclusionsProvider(regexp.MustCompile("sample"), regexp.MustCompile("sample1")), expectedFileNames: []string{"testdata/dir1/sample2.go"}},
67+
{directory: "testdata/dir2", filter: architest.PackageFilterProvider("dir2"), expectedFileNames: []string{"testdata/dir2/sample1.go"}},
68+
{directory: "testdata/dir2", filter: architest.PackageFilterProvider("dir2_test"), expectedFileNames: []string{"testdata/dir2/sample1_test.go"}},
69+
{directory: "testdata/dir2", filter: architest.FileNameRegexFilterProvider(architest.AcceptanceTestFileRegex), expectedFileNames: []string{}},
70+
{directory: "testdata/dir3", filter: architest.FileNameRegexFilterProvider(architest.AcceptanceTestFileRegex), expectedFileNames: []string{"testdata/dir3/sample1_acceptance_test.go"}},
71+
{directory: "testdata/dir4", filter: architest.FileNameRegexFilterProvider(architest.AcceptanceTestFileRegex), expectedFileNames: []string{}},
72+
{directory: "testdata/dir2", filter: architest.FileNameRegexFilterProvider(architest.IntegrationTestFileRegex), expectedFileNames: []string{}},
73+
{directory: "testdata/dir3", filter: architest.FileNameRegexFilterProvider(architest.IntegrationTestFileRegex), expectedFileNames: []string{}},
74+
{directory: "testdata/dir4", filter: architest.FileNameRegexFilterProvider(architest.IntegrationTestFileRegex), expectedFileNames: []string{"testdata/dir4/sample1_integration_test.go"}},
75+
{directory: "testdata/dir2", filter: architest.FileNameRegexFilterProvider(architest.TestFileRegex), expectedFileNames: []string{"testdata/dir2/sample1_test.go"}},
76+
{directory: "testdata/dir3", filter: architest.FileNameRegexFilterProvider(architest.TestFileRegex), expectedFileNames: []string{"testdata/dir3/sample1_acceptance_test.go"}},
77+
{directory: "testdata/dir4", filter: architest.FileNameRegexFilterProvider(architest.TestFileRegex), expectedFileNames: []string{"testdata/dir4/sample1_integration_test.go"}},
78+
}
79+
for _, tt := range tests2 {
80+
t.Run("list only files matching filter in the given directory", func(t *testing.T) {
81+
dir := architest.Directory(tt.directory)
82+
83+
filteredFiles := dir.Files(tt.filter)
84+
assert.Len(t, filteredFiles, len(tt.expectedFileNames))
85+
86+
fileNames := make([]string, 0, len(filteredFiles))
87+
for _, f := range filteredFiles {
88+
fileNames = append(fileNames, f.Name())
89+
}
90+
assert.ElementsMatch(t, fileNames, tt.expectedFileNames)
91+
92+
// now exactly the same but indirectly
93+
filteredFiles = dir.AllFiles().Filter(tt.filter)
94+
assert.Len(t, filteredFiles, len(tt.expectedFileNames))
95+
96+
fileNames = make([]string, 0, len(filteredFiles))
97+
for _, f := range filteredFiles {
98+
fileNames = append(fileNames, f.Name())
99+
}
100+
assert.ElementsMatch(t, fileNames, tt.expectedFileNames)
101+
})
102+
}
103+
}
104+
105+
func Test_Files(t *testing.T) {
106+
t.Run("fail to use non-existing file", func(t *testing.T) {
107+
assert.Panics(t, func() {
108+
architest.NewFileFromPath("testdata/dir1/non_existing.go")
109+
})
110+
})
111+
112+
t.Run("use file", func(t *testing.T) {
113+
assert.NotPanics(t, func() {
114+
architest.NewFileFromPath("testdata/dir1/sample1.go")
115+
})
116+
})
117+
118+
tests1 := []struct {
119+
filePath string
120+
expectedMethodNames []string
121+
}{
122+
{filePath: "testdata/dir1/sample1.go", expectedMethodNames: []string{}},
123+
{filePath: "testdata/dir1/sample2.go", expectedMethodNames: []string{"A"}},
124+
}
125+
for _, tt := range tests1 {
126+
t.Run("list all methods in file", func(t *testing.T) {
127+
file := architest.NewFileFromPath(tt.filePath)
128+
129+
exportedMethods := file.ExportedMethods()
130+
assert.Len(t, exportedMethods, len(tt.expectedMethodNames))
131+
132+
methodNames := make([]string, 0, len(exportedMethods))
133+
for _, m := range exportedMethods {
134+
methodNames = append(methodNames, m.Name())
135+
}
136+
assert.ElementsMatch(t, methodNames, tt.expectedMethodNames)
137+
})
138+
}
139+
140+
tests2 := []struct {
141+
fileNames []string
142+
}{
143+
{fileNames: []string{}},
144+
{fileNames: []string{"a"}},
145+
{fileNames: []string{"a", "A"}},
146+
{fileNames: []string{"A", "a"}},
147+
{fileNames: []string{"A", "B", "C"}},
148+
}
149+
for _, tt := range tests2 {
150+
t.Run("receiver invoked for every file", func(t *testing.T) {
151+
files := make(architest.Files, 0, len(tt.fileNames))
152+
for _, f := range tt.fileNames {
153+
files = append(files, *architest.NewFile("package", f, nil))
154+
}
155+
invokedFiles := make([]string, 0)
156+
receiver := func(f *architest.File) {
157+
invokedFiles = append(invokedFiles, f.Name())
158+
}
159+
160+
files.All(receiver)
161+
162+
assert.ElementsMatch(t, tt.fileNames, invokedFiles)
163+
})
164+
}
165+
}
166+
167+
func Test_Methods(t *testing.T) {
168+
tests := []struct {
169+
methodNames []string
170+
}{
171+
{methodNames: []string{}},
172+
{methodNames: []string{"a"}},
173+
{methodNames: []string{"a", "A"}},
174+
{methodNames: []string{"A", "a"}},
175+
{methodNames: []string{"A", "B", "C"}},
176+
}
177+
for _, tt := range tests {
178+
t.Run("receiver invoked for every method", func(t *testing.T) {
179+
methods := make(architest.Methods, 0, len(tt.methodNames))
180+
for _, m := range tt.methodNames {
181+
methods = append(methods, *architest.NewMethod(m, nil))
182+
}
183+
invokedMethods := make([]string, 0)
184+
receiver := func(m *architest.Method) {
185+
invokedMethods = append(invokedMethods, m.Name())
186+
}
187+
188+
methods.All(receiver)
189+
190+
assert.ElementsMatch(t, tt.methodNames, invokedMethods)
191+
})
192+
}
193+
}
194+
195+
func Test_Assertions(t *testing.T) {
196+
tests1 := []struct {
197+
filePath string
198+
expectedPackage string
199+
}{
200+
{filePath: "testdata/dir1/sample1.go", expectedPackage: "dir1"},
201+
{filePath: "testdata/dir2/sample1.go", expectedPackage: "dir2"},
202+
{filePath: "testdata/dir2/sample1_test.go", expectedPackage: "dir2_test"},
203+
}
204+
for _, tt := range tests1 {
205+
t.Run("file package assertions", func(t *testing.T) {
206+
file := architest.NewFileFromPath(tt.filePath)
207+
tut1 := &testing.T{}
208+
tut2 := &testing.T{}
209+
210+
file.AssertHasPackage(tut1, tt.expectedPackage)
211+
file.AssertHasPackage(tut2, "some_other_package")
212+
213+
assert.Equal(t, false, tut1.Failed())
214+
assert.Equal(t, true, tut2.Failed())
215+
})
216+
}
217+
218+
tests2 := []struct {
219+
methodName string
220+
correct bool
221+
}{
222+
{methodName: "TestAcc_abc", correct: true},
223+
{methodName: "TestAcc_TestAcc_Test", correct: true},
224+
{methodName: "TestAcc_", correct: false},
225+
{methodName: "ATestAcc_", correct: false},
226+
{methodName: "TestAcc", correct: false},
227+
{methodName: "Test_", correct: false},
228+
{methodName: "Test", correct: false},
229+
{methodName: "Test_asdf", correct: false},
230+
{methodName: "TestAccc_", correct: false},
231+
{methodName: "TestInt_Abc", correct: false},
232+
}
233+
for _, tt := range tests2 {
234+
t.Run(fmt.Sprintf("acceptance test name assertions for method %s", tt.methodName), func(t *testing.T) {
235+
file := architest.NewFileFromPath("testdata/dir1/sample1.go")
236+
method := architest.NewMethod(tt.methodName, file)
237+
tut := &testing.T{}
238+
239+
method.AssertAcceptanceTestNamedCorrectly(tut)
240+
241+
assert.Equal(t, !tt.correct, tut.Failed())
242+
})
243+
}
244+
245+
tests3 := []struct {
246+
methodName string
247+
regexRaw string
248+
correct bool
249+
}{
250+
{methodName: "sample1", regexRaw: "sample", correct: true},
251+
{methodName: "Sample1", regexRaw: "sample", correct: false},
252+
{methodName: "Sample1", regexRaw: "Sample", correct: true},
253+
}
254+
for _, tt := range tests3 {
255+
t.Run(fmt.Sprintf("matching and not matching method name assertions for %s", tt.methodName), func(t *testing.T) {
256+
file := architest.NewFileFromPath("testdata/dir1/sample1.go")
257+
method := architest.NewMethod(tt.methodName, file)
258+
tut1 := &testing.T{}
259+
tut2 := &testing.T{}
260+
261+
method.AssertNameMatches(tut1, regexp.MustCompile(tt.regexRaw))
262+
method.AssertNameDoesNotMatch(tut2, regexp.MustCompile(tt.regexRaw))
263+
264+
assert.Equal(t, !tt.correct, tut1.Failed())
265+
assert.Equal(t, tt.correct, tut2.Failed())
266+
})
267+
}
268+
269+
tests4 := []struct {
270+
methodName string
271+
correct bool
272+
}{
273+
{methodName: "Test", correct: true},
274+
{methodName: "aTest", correct: false},
275+
{methodName: "Test_", correct: true},
276+
{methodName: "Test_adsfadf", correct: true},
277+
}
278+
for _, tt := range tests4 {
279+
t.Run(fmt.Sprintf("test name assertions for method %s", tt.methodName), func(t *testing.T) {
280+
file := architest.NewFileFromPath("testdata/dir1/sample1.go")
281+
method := architest.NewMethod(tt.methodName, file)
282+
tut := &testing.T{}
283+
284+
method.AssertTestNamedCorrectly(tut)
285+
286+
assert.Equal(t, !tt.correct, tut.Failed())
287+
})
288+
}
289+
290+
tests5 := []struct {
291+
methodName string
292+
correct bool
293+
}{
294+
{methodName: "TestInt_abc", correct: true},
295+
{methodName: "TestInt_TestInt_Test", correct: true},
296+
{methodName: "TestInt_", correct: false},
297+
{methodName: "ATestInt_", correct: false},
298+
{methodName: "TestInt", correct: false},
299+
{methodName: "Test_", correct: false},
300+
{methodName: "Test", correct: false},
301+
{methodName: "Test_asdf", correct: false},
302+
{methodName: "TestIntt_", correct: false},
303+
{methodName: "TestAcc_Abc", correct: false},
304+
}
305+
for _, tt := range tests5 {
306+
t.Run(fmt.Sprintf("intagration test name assertions for method %s", tt.methodName), func(t *testing.T) {
307+
file := architest.NewFileFromPath("testdata/dir1/sample1.go")
308+
method := architest.NewMethod(tt.methodName, file)
309+
tut := &testing.T{}
310+
311+
method.AssertIntegrationTestNamedCorrectly(tut)
312+
313+
assert.Equal(t, !tt.correct, tut.Failed())
314+
})
315+
}
316+
}
317+
318+
func Test_SampleArchiTestUsage(t *testing.T) {
319+
t.Run("acceptance tests", func(t *testing.T) {
320+
acceptanceTestFiles := architest.Directory("testdata/dir3/").
321+
AllFiles().
322+
Filter(architest.FileNameRegexFilterProvider(architest.AcceptanceTestFileRegex))
323+
324+
acceptanceTestFiles.All(func(file *architest.File) {
325+
file.AssertHasPackage(t, "dir3_test")
326+
file.ExportedMethods().All(func(method *architest.Method) {
327+
method.AssertAcceptanceTestNamedCorrectly(t)
328+
})
329+
})
330+
})
331+
332+
t.Run("integration tests", func(t *testing.T) {
333+
integrationTestFiles := architest.Directory("testdata/dir4/").
334+
AllFiles().
335+
Filter(architest.FileNameRegexFilterProvider(architest.IntegrationTestFileRegex))
336+
337+
integrationTestFiles.All(func(file *architest.File) {
338+
file.AssertHasPackage(t, "dir4_test")
339+
file.ExportedMethods().All(func(method *architest.Method) {
340+
method.AssertIntegrationTestNamedCorrectly(t)
341+
})
342+
})
343+
})
344+
345+
t.Run("tests", func(t *testing.T) {
346+
testFiles := architest.Directory("testdata/dir2/").
347+
AllFiles().
348+
Filter(architest.FileNameRegexFilterProvider(architest.TestNameRegex))
349+
350+
testFiles.All(func(file *architest.File) {
351+
file.AssertHasPackage(t, "dir2_test")
352+
file.ExportedMethods().All(func(method *architest.Method) {
353+
method.AssertTestNamedCorrectly(t)
354+
})
355+
})
356+
})
357+
}

0 commit comments

Comments
 (0)