Skip to content

Commit 321d46a

Browse files
committed
WIP: Add golden test for function output
1 parent fd60853 commit 321d46a

File tree

12 files changed

+601
-0
lines changed

12 files changed

+601
-0
lines changed

functions/go/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.18
44

55
require (
66
github.com/GoogleContainerTools/kpt-functions-sdk/go/fn v0.0.0-20220927151351-53b04ca4b731
7+
github.com/google/go-cmp v0.5.8
78
k8s.io/apimachinery v0.25.2
89
)
910

functions/go/go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
151151
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
152152
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
153153
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
154+
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
154155
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
155156
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
156157
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"testing"
19+
20+
"github.com/GoogleContainerTools/kpt-functions-catalog/functions/go/set-name-prefix/pkg/krmfunctiontests"
21+
"github.com/GoogleContainerTools/kpt-functions-sdk/go/fn"
22+
)
23+
24+
func TestFunctions(t *testing.T) {
25+
krmfunctiontests.RunFunctionTests(t, "testdata", fn.ResourceListProcessorFunc(Run))
26+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package krmfunctiontests
16+
17+
import (
18+
"io/ioutil"
19+
"path/filepath"
20+
"sort"
21+
"strings"
22+
"testing"
23+
24+
"github.com/GoogleContainerTools/kpt-functions-catalog/functions/go/set-name-prefix/pkg/testhelpers"
25+
"github.com/GoogleContainerTools/kpt-functions-sdk/go/fn"
26+
)
27+
28+
func RunFunctionTests(t *testing.T, testdata string, krmFunction fn.ResourceListProcessorFunc) {
29+
dirs, err := ioutil.ReadDir(testdata)
30+
if err != nil {
31+
t.Fatalf("failed to read directory %q: %v", testdata, err)
32+
}
33+
34+
for _, d := range dirs {
35+
dir := filepath.Join(testdata, d.Name())
36+
if !d.IsDir() {
37+
t.Errorf("expected directory, found %s", dir)
38+
continue
39+
}
40+
41+
t.Run(dir, func(t *testing.T) {
42+
files, err := ioutil.ReadDir(dir)
43+
if err != nil {
44+
t.Fatalf("failed to read directory %q: %v", testdata, err)
45+
}
46+
sort.Slice(files, func(i, j int) bool { return files[i].Name() < files[j].Name() })
47+
var items []*fn.KubeObject
48+
for _, f := range files {
49+
if strings.HasPrefix(f.Name(), "_") {
50+
continue
51+
}
52+
fileItems := mustParseFile(t, filepath.Join(dir, f.Name()))
53+
items = append(items, fileItems...)
54+
}
55+
56+
config := mustParseFile(t, filepath.Join(dir, "_fnconfig.yaml"))
57+
58+
var functionConfig *fn.KubeObject
59+
if len(config) == 0 {
60+
functionConfig = nil
61+
} else if len(config) == 1 {
62+
functionConfig = config[0]
63+
} else {
64+
t.Fatalf("found multiple config objects in %s", filepath.Join(dir, "_fnconfig.yaml"))
65+
}
66+
67+
rl, err := buildResourceList(items, functionConfig)
68+
if err != nil {
69+
t.Fatalf("failed to build resource list: %v", err)
70+
}
71+
72+
success, err := krmFunction(rl)
73+
if err != nil {
74+
t.Fatalf("Run failed unexpectedly: %v", err)
75+
}
76+
if !success {
77+
t.Fatalf("Run did not succeed")
78+
}
79+
80+
rlYAML, err := rl.ToYAML()
81+
if err != nil {
82+
t.Fatalf("failed to convert resource list to yaml: %v", err)
83+
}
84+
85+
p := filepath.Join(dir, "_expected.yaml")
86+
testhelpers.CompareGoldenFile(t, p, rlYAML)
87+
})
88+
}
89+
}
90+
91+
func mustParseFile(t *testing.T, p string) []*fn.KubeObject {
92+
b := testhelpers.MustReadFile(t, p)
93+
objects, err := fn.ParseKubeObjects(b)
94+
if err != nil {
95+
t.Fatalf("failed to parse objects from file %q: %v", p, err)
96+
}
97+
return objects
98+
}
99+
100+
func buildResourceList(items []*fn.KubeObject, functionConfig *fn.KubeObject) (*fn.ResourceList, error) {
101+
rl := &fn.ResourceList{}
102+
rl.Items = items
103+
rl.FunctionConfig = functionConfig
104+
105+
return rl, nil
106+
}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package testhelpers
16+
17+
import (
18+
"bytes"
19+
"fmt"
20+
"io"
21+
"io/fs"
22+
"io/ioutil"
23+
"os"
24+
"path/filepath"
25+
"testing"
26+
27+
"github.com/google/go-cmp/cmp"
28+
)
29+
30+
func RunGoldenTests(t *testing.T, basedir string, fn func(t *testing.T, dir string)) {
31+
files, err := ioutil.ReadDir(basedir)
32+
if err != nil {
33+
t.Fatalf("ReadDir(%q) failed: %v", basedir, err)
34+
}
35+
for _, file := range files {
36+
name := file.Name()
37+
absPath := filepath.Join(basedir, name)
38+
t.Run(name, func(t *testing.T) {
39+
fn(t, absPath)
40+
})
41+
}
42+
}
43+
44+
func MustReadFile(t *testing.T, p string) []byte {
45+
b, err := ioutil.ReadFile(p)
46+
if err != nil {
47+
t.Fatalf("failed to read file %q: %v", p, err)
48+
}
49+
return b
50+
}
51+
52+
func CompareGoldenFile(t *testing.T, p string, got []byte) {
53+
if os.Getenv("WRITE_GOLDEN_OUTPUT") != "" {
54+
// Short-circuit when the output is correct
55+
b, err := ioutil.ReadFile(p)
56+
if err == nil && bytes.Equal(b, got) {
57+
return
58+
}
59+
60+
if err := ioutil.WriteFile(p, got, 0644); err != nil {
61+
t.Fatalf("failed to write golden output %s: %v", p, err)
62+
}
63+
t.Errorf("wrote output to %s", p)
64+
} else {
65+
want := MustReadFile(t, p)
66+
if diff := cmp.Diff(string(want), string(got)); diff != "" {
67+
t.Errorf("unexpected diff in %s: %s", p, diff)
68+
}
69+
}
70+
}
71+
72+
func CopyDir(src, dest string) error {
73+
srcFiles, err := ioutil.ReadDir(src)
74+
if err != nil {
75+
return fmt.Errorf("ReadDir(%q) failed: %w", src, err)
76+
}
77+
for _, srcFile := range srcFiles {
78+
srcPath := filepath.Join(src, srcFile.Name())
79+
destPath := filepath.Join(dest, srcFile.Name())
80+
if srcFile.IsDir() {
81+
if err := CopyDir(srcPath, destPath); err != nil {
82+
return err
83+
}
84+
} else {
85+
if err := CopyFile(srcPath, destPath); err != nil {
86+
return err
87+
}
88+
}
89+
}
90+
return nil
91+
}
92+
93+
func CopyFile(src, dest string) error {
94+
in, err := os.Open(src)
95+
if err != nil {
96+
return fmt.Errorf("OpenFile(%q) failed: %w", src, err)
97+
}
98+
defer in.Close()
99+
100+
out, err := os.Create(dest)
101+
if err != nil {
102+
return fmt.Errorf("Create(%q) failed: %w", dest, err)
103+
}
104+
105+
if _, err := io.Copy(out, in); err != nil {
106+
out.Close()
107+
return fmt.Errorf("byte copy from %s to %s failed: %w", src, dest, err)
108+
}
109+
110+
if err := out.Close(); err != nil {
111+
return fmt.Errorf("Close(%q) failed: %w", dest, err)
112+
}
113+
114+
return nil
115+
}
116+
117+
func CompareDir(t *testing.T, expectDir, actualDir string) {
118+
expectFiles, err := ioutil.ReadDir(expectDir)
119+
if err != nil {
120+
t.Fatalf("failed to read expectation directory %q: %v", expectDir, err)
121+
}
122+
expectFileMap := make(map[string]fs.FileInfo)
123+
for _, expectFile := range expectFiles {
124+
expectFileMap[expectFile.Name()] = expectFile
125+
}
126+
127+
actualFiles, err := ioutil.ReadDir(actualDir)
128+
if err != nil {
129+
t.Fatalf("failed to read actual directory %q: %v", actualDir, err)
130+
}
131+
actualFileMap := make(map[string]fs.FileInfo)
132+
for _, actualFile := range actualFiles {
133+
actualFileMap[actualFile.Name()] = actualFile
134+
}
135+
136+
for _, expectFile := range expectFiles {
137+
name := expectFile.Name()
138+
actualFile := actualFileMap[name]
139+
if actualFile == nil {
140+
t.Errorf("expected file %s not found", name)
141+
continue
142+
}
143+
144+
if expectFile.IsDir() {
145+
if !actualFile.IsDir() {
146+
t.Errorf("expected file %s was not a directory", name)
147+
continue
148+
}
149+
CompareDir(t, filepath.Join(expectDir, name), filepath.Join(actualDir, name))
150+
} else {
151+
if actualFile.IsDir() {
152+
t.Errorf("expected file %s was not a file", name)
153+
continue
154+
}
155+
CompareFile(t, expectDir, actualDir, name)
156+
}
157+
}
158+
159+
for _, actualFile := range actualFiles {
160+
name := actualFile.Name()
161+
expectFile := expectFileMap[name]
162+
if expectFile == nil {
163+
t.Errorf("additional file %s found in output", name)
164+
continue
165+
}
166+
}
167+
}
168+
169+
func CompareFile(t *testing.T, expectDir, actualDir string, relPath string) {
170+
expectAbs := filepath.Join(expectDir, relPath)
171+
172+
actualAbs := filepath.Join(actualDir, relPath)
173+
actualBytes, err := ioutil.ReadFile(actualAbs)
174+
if err != nil {
175+
if os.IsNotExist(err) {
176+
t.Errorf("expected file %s not found", relPath)
177+
} else {
178+
t.Fatalf("error reading file %s: %v", actualAbs, err)
179+
}
180+
}
181+
182+
CompareGoldenFile(t, expectAbs, actualBytes)
183+
}

0 commit comments

Comments
 (0)