Skip to content

Commit b77cbf5

Browse files
committed
test infra
1 parent 5fa523b commit b77cbf5

File tree

3 files changed

+254
-2
lines changed

3 files changed

+254
-2
lines changed

go/fn/object.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ func NewFromTypedObject(v interface{}) (*KubeObject, error) {
431431
}
432432

433433
// String serializes the object in yaml format.
434-
func (o *KubeObject) String() string {
434+
func (o *SubObject) String() string {
435435
doc := internal.NewDoc([]*yaml.Node{o.obj.Node()}...)
436436
s, _ := doc.ToYAML()
437437
return string(s)

go/fn/run.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,15 @@ func AsMain(input interface{}) error {
5959

6060
// Run evaluates the function. input must be a resourceList in yaml format. An
6161
// updated resourceList will be returned.
62-
func Run(p ResourceListProcessor, input []byte) (out []byte, err error) {
62+
func Run(p ResourceListProcessor, input []byte) ([]byte, error) {
63+
switch input := p.(type) {
64+
case runnerProcessor:
65+
p = input
66+
case ResourceListProcessorFunc:
67+
p = input
68+
default:
69+
return nil, fmt.Errorf("unknown input type %T", input)
70+
}
6371
rl, err := ParseResourceList(input)
6472
if err != nil {
6573
return nil, err

go/fn/testhelpers/golden.go

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
package testhelpers
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"io"
7+
"os"
8+
"path/filepath"
9+
"sort"
10+
"strings"
11+
"testing"
12+
13+
"github.com/GoogleContainerTools/kpt-functions-sdk/go/fn"
14+
"github.com/google/go-cmp/cmp"
15+
)
16+
17+
// RunGoldenTests provides the test infra to run golden test.
18+
// - "basedir" should be the parent directory, under where the sub-directories contains test data.
19+
// For example, the "testdata" is the basedir. It contains two cases "test1" and "test2"
20+
// └── testdata
21+
// └── test1
22+
// ├── _expected.yaml
23+
// ├── _fnconfig.yaml
24+
// └── resources.yaml
25+
// └── test2
26+
// ├── _expected.yaml
27+
// ├── _fnconfig.yaml
28+
// └── resources.yaml
29+
// - "krmFunction" should be your ResourceListProcessor implementation.
30+
func RunGoldenTests(t *testing.T, basedir string, krmFunction fn.ResourceListProcessor) {
31+
dirEntries, err := os.ReadDir(basedir)
32+
if err != nil {
33+
t.Fatalf("ReadDir(%q) failed: %v", basedir, err)
34+
}
35+
36+
for _, dirEntry := range dirEntries {
37+
dir := filepath.Join(basedir, dirEntry.Name())
38+
if !dirEntry.IsDir() {
39+
t.Errorf("expected directory, found %s", dir)
40+
continue
41+
}
42+
43+
t.Run(dir, func(t *testing.T) {
44+
files, err := os.ReadDir(dir)
45+
if err != nil {
46+
t.Fatalf("failed to read directory %q: %v", basedir, err)
47+
}
48+
sort.Slice(files, func(i, j int) bool { return files[i].Name() < files[j].Name() })
49+
var items []*fn.KubeObject
50+
for _, f := range files {
51+
if strings.HasPrefix(f.Name(), "_") {
52+
continue
53+
}
54+
fileItems := mustParseFile(t, filepath.Join(dir, f.Name()))
55+
items = append(items, fileItems...)
56+
}
57+
58+
config := mustParseFile(t, filepath.Join(dir, "_fnconfig.yaml"))
59+
60+
var functionConfig *fn.KubeObject
61+
if len(config) == 0 {
62+
functionConfig = nil
63+
} else if len(config) == 1 {
64+
functionConfig = config[0]
65+
} else {
66+
t.Fatalf("found multiple config objects in %s", filepath.Join(dir, "_fnconfig.yaml"))
67+
}
68+
69+
rl := &fn.ResourceList{Items: items, FunctionConfig: functionConfig}
70+
success, err := krmFunction.Process(rl)
71+
if err != nil {
72+
t.Fatalf("run failed unexpectedly: %v", err)
73+
}
74+
if !success {
75+
t.Fatalf("run did not succeed")
76+
}
77+
78+
rlYAML, err := rl.ToYAML()
79+
if err != nil {
80+
t.Fatalf("failed to convert resource list to yaml: %v", err)
81+
}
82+
83+
p := filepath.Join(dir, "_expected.yaml")
84+
CompareGoldenFile(t, p, rlYAML)
85+
})
86+
}
87+
}
88+
89+
// MustReadFile reads the data from "expectedPath"
90+
func MustReadFile(t *testing.T, expectedPath string) []byte {
91+
b, err := os.ReadFile(expectedPath)
92+
if err != nil {
93+
t.Fatalf("failed to read file %q: %v", expectedPath, err)
94+
}
95+
return b
96+
}
97+
98+
// CompareGoldenFile compares the "got" data with the data stored in a "expectedPath".
99+
func CompareGoldenFile(t *testing.T, expectedPath string, got []byte) {
100+
if os.Getenv("WRITE_GOLDEN_OUTPUT") != "" {
101+
// Short-circuit when the output is correct
102+
b, err := os.ReadFile(expectedPath)
103+
if err == nil && bytes.Equal(b, got) {
104+
return
105+
}
106+
107+
if err := os.WriteFile(expectedPath, got, 0600); err != nil {
108+
t.Fatalf("failed to write golden output %s: %v", expectedPath, err)
109+
}
110+
t.Errorf("wrote output to %s", expectedPath)
111+
} else {
112+
want := MustReadFile(t, expectedPath)
113+
if diff := cmp.Diff(string(want), string(got)); diff != "" {
114+
t.Errorf("unexpected diff in %s: %s", expectedPath, diff)
115+
}
116+
}
117+
}
118+
119+
// CopyDir copies an entire directory from "src" to "dest"
120+
func CopyDir(src, dest string) error {
121+
srcFiles, err := os.ReadDir(src)
122+
if err != nil {
123+
return fmt.Errorf("ReadDir(%q) failed: %w", src, err)
124+
}
125+
for _, srcFile := range srcFiles {
126+
srcPath := filepath.Join(src, srcFile.Name())
127+
destPath := filepath.Join(dest, srcFile.Name())
128+
if srcFile.IsDir() {
129+
if err = CopyDir(srcPath, destPath); err != nil {
130+
return err
131+
}
132+
} else {
133+
if err = CopyFile(srcPath, destPath); err != nil {
134+
return err
135+
}
136+
}
137+
}
138+
return nil
139+
}
140+
141+
// CopyFile copies a single file from "src" to "dest"
142+
func CopyFile(src, dest string) error {
143+
in, err := os.Open(src)
144+
if err != nil {
145+
return fmt.Errorf("OpenFile(%q) failed: %w", src, err)
146+
}
147+
defer in.Close()
148+
149+
out, err := os.Create(dest)
150+
if err != nil {
151+
return fmt.Errorf("create(%q) failed: %w", dest, err)
152+
}
153+
154+
if _, err := io.Copy(out, in); err != nil {
155+
out.Close()
156+
return fmt.Errorf("byte copy from %s to %s failed: %w", src, dest, err)
157+
}
158+
159+
if err := out.Close(); err != nil {
160+
return fmt.Errorf("close(%q) failed: %w", dest, err)
161+
}
162+
163+
return nil
164+
}
165+
166+
// CompareDir compares the contents of two directories
167+
// only compare KRM YAML resources?
168+
func CompareDir(t *testing.T, expectDir, actualDir string) {
169+
expectFiles, err := os.ReadDir(expectDir)
170+
if err != nil {
171+
t.Fatalf("failed to read expectation directory %q: %v", expectDir, err)
172+
}
173+
expectFileMap := make(map[string]os.DirEntry)
174+
for _, expectFile := range expectFiles {
175+
expectFileMap[expectFile.Name()] = expectFile
176+
}
177+
178+
actualFiles, err := os.ReadDir(actualDir)
179+
if err != nil {
180+
t.Fatalf("failed to read actual directory %q: %v", actualDir, err)
181+
}
182+
actualFileMap := make(map[string]os.DirEntry)
183+
for _, actualFile := range actualFiles {
184+
actualFileMap[actualFile.Name()] = actualFile
185+
}
186+
187+
for _, expectFile := range expectFiles {
188+
name := expectFile.Name()
189+
actualFile := actualFileMap[name]
190+
if actualFile == nil {
191+
t.Errorf("expected file %s not found", name)
192+
continue
193+
}
194+
195+
if expectFile.IsDir() {
196+
if !actualFile.IsDir() {
197+
t.Errorf("expected file %s was not a directory", name)
198+
continue
199+
}
200+
CompareDir(t, filepath.Join(expectDir, name), filepath.Join(actualDir, name))
201+
} else {
202+
if actualFile.IsDir() {
203+
t.Errorf("expected file %s was not a file", name)
204+
continue
205+
}
206+
CompareFile(t, expectDir, actualDir, name)
207+
}
208+
}
209+
210+
for _, actualFile := range actualFiles {
211+
name := actualFile.Name()
212+
expectFile := expectFileMap[name]
213+
if expectFile == nil {
214+
t.Errorf("additional file %s found in output", name)
215+
continue
216+
}
217+
}
218+
}
219+
220+
// CompareFile compares a single file of the same relative path between "expectDir" and "actualDir"
221+
func CompareFile(t *testing.T, expectDir, actualDir string, relPath string) {
222+
expectAbs := filepath.Join(expectDir, relPath)
223+
224+
actualAbs := filepath.Join(actualDir, relPath)
225+
actualBytes, err := os.ReadFile(actualAbs)
226+
if err != nil {
227+
if os.IsNotExist(err) {
228+
t.Errorf("expected file %s not found", relPath)
229+
} else {
230+
t.Fatalf("error reading file %s: %v", actualAbs, err)
231+
}
232+
}
233+
234+
CompareGoldenFile(t, expectAbs, actualBytes)
235+
}
236+
237+
func mustParseFile(t *testing.T, path string) fn.KubeObjects {
238+
b := MustReadFile(t, path)
239+
objects, err := fn.ParseKubeObjects(b)
240+
if err != nil {
241+
t.Fatalf("failed to parse objects from file %q: %v", path, err)
242+
}
243+
return objects
244+
}

0 commit comments

Comments
 (0)