From bd0a699ff61945751b52e683da5d3ee88f8496ef Mon Sep 17 00:00:00 2001 From: Phillip Wittrock Date: Mon, 16 Nov 2020 09:08:34 -0800 Subject: [PATCH] Refactor fn/framework tests to use a common library. --- kyaml/fn/framework/framework_test.go | 39 ++-- .../frameworktestutil/frameworktestutil.go | 172 ++++++++++++++++++ 2 files changed, 185 insertions(+), 26 deletions(-) create mode 100644 kyaml/fn/framework/frameworktestutil/frameworktestutil.go diff --git a/kyaml/fn/framework/framework_test.go b/kyaml/fn/framework/framework_test.go index 05f993891..85440f4dc 100644 --- a/kyaml/fn/framework/framework_test.go +++ b/kyaml/fn/framework/framework_test.go @@ -4,14 +4,15 @@ package framework_test import ( - "bytes" "io/ioutil" "os" "path/filepath" "testing" + "github.com/spf13/cobra" "github.com/stretchr/testify/assert" "sigs.k8s.io/kustomize/kyaml/fn/framework" + "sigs.k8s.io/kustomize/kyaml/fn/framework/frameworktestutil" "sigs.k8s.io/kustomize/kyaml/testutil" "sigs.k8s.io/kustomize/kyaml/yaml" ) @@ -65,8 +66,9 @@ func TestCommand_standalone(t *testing.T) { var config api resourceList := &framework.ResourceList{FunctionConfig: &config} - cmd := framework.Command(resourceList, func() error { - resourceList.Items = append(resourceList.Items, yaml.MustParse(` + cmdFn := func() cobra.Command { + return framework.Command(resourceList, func() error { + resourceList.Items = append(resourceList.Items, yaml.MustParse(` apiVersion: apps/v1 kind: Deployment metadata: @@ -75,31 +77,16 @@ metadata: annotations: foo: bar1 `)) - - for i := range resourceList.Items { - err := resourceList.Items[i].PipeE(yaml.SetAnnotation("a", config.A)) - if err != nil { - return err + for i := range resourceList.Items { + err := resourceList.Items[i].PipeE(yaml.SetAnnotation("a", config.A)) + if err != nil { + return err + } } - } - return nil - }) - cmd.SetArgs([]string{ - filepath.Join("testdata", "command", "config.yaml"), - filepath.Join("testdata", "command", "input.yaml"), - }) - var out bytes.Buffer - cmd.SetOutput(&out) - if !assert.NoError(t, cmd.Execute()) { - t.FailNow() + return nil + }) } - expected, err := ioutil.ReadFile(filepath.Join("testdata", "command", "expected.yaml")) - if !assert.NoError(t, err) { - t.FailNow() - } - if !assert.Equal(t, string(expected), out.String()) { - t.FailNow() - } + frameworktestutil.ResultsChecker{Command: cmdFn}.Assert(t) } diff --git a/kyaml/fn/framework/frameworktestutil/frameworktestutil.go b/kyaml/fn/framework/frameworktestutil/frameworktestutil.go new file mode 100644 index 000000000..a6dfabda5 --- /dev/null +++ b/kyaml/fn/framework/frameworktestutil/frameworktestutil.go @@ -0,0 +1,172 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +// Package frameworktestutil contains utilities for testing functions written using the framework. +package frameworktestutil + +import ( + "bytes" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" +) + +// ResultsChecker tests a function by running it with predefined inputs and comparing +// the outputs to expected results. +type ResultsChecker struct { + // TestDataDirectory is the directory containing the testdata subdirectories. + // ResultsChecker will recurse into each test directory and run the Command + // if the directory contains both the ConfigInputFilename and at least one + // of ExpectedOutputFilname or ExpectedErrorFilename. + // Defaults to "testdata" + TestDataDirectory string + + // ConfigInputFilename is the name of the config file provided as the first + // argument to the function. Directories without this file will be skipped. + // Defaults to "config.yaml" + ConfigInputFilename string + + // InputFilenameGlob matches function inputs + // Defaults to "input*.yaml" + InputFilenameGlob string + + // ExpectedOutputFilname is the file with the expected output of the function + // Defaults to "expected.yaml". Directories containing neither this file + // nore ExpectedErrorFilename will be skipped. + ExpectedOutputFilname string + + // ExpectedErrorFilename is the file containing part of an expected error message + // Defaults to "error.yaml". Directories containing neither this file + // nore ExpectedOutputFilname will be skipped. + ExpectedErrorFilename string + + // Command provides the function to run. + Command func() cobra.Command +} + +// Assert asserts the results for functions +func (rc ResultsChecker) Assert(t *testing.T) bool { + if rc.TestDataDirectory == "" { + rc.TestDataDirectory = "testdata" + } + if rc.ConfigInputFilename == "" { + rc.ConfigInputFilename = "config.yaml" + } + if rc.ExpectedOutputFilname == "" { + rc.ExpectedOutputFilname = "expected.yaml" + } + if rc.ExpectedErrorFilename == "" { + rc.ExpectedErrorFilename = "error.yaml" + } + if rc.InputFilenameGlob == "" { + rc.InputFilenameGlob = "input*.yaml" + } + + _ = filepath.Walk(rc.TestDataDirectory, func(path string, info os.FileInfo, err error) error { + if err != nil { + t.FailNow() + } + if !info.IsDir() { + // skip non-directories + return nil + } + rc.compare(t, path) + return nil + }) + + return true +} + +func (rc ResultsChecker) compare(t *testing.T, path string) { + // make sure this directory contains test data + configPath := filepath.Join(path, rc.ConfigInputFilename) + _, err := os.Stat(configPath) + if os.IsNotExist(err) { + // missing input + return + } + if !assert.NoError(t, err) { + t.FailNow() + } + args := []string{configPath} + + if rc.InputFilenameGlob != "" { + inputs, err := filepath.Glob(filepath.Join(path, rc.InputFilenameGlob)) + if !assert.NoError(t, err) { + t.FailNow() + } + args = append(args, inputs...) + } + + var actualOutput, actualError bytes.Buffer + cmd := rc.Command() + cmd.SetArgs(args) + cmd.SetOut(&actualOutput) + cmd.SetErr(&actualError) + + expectedOutput, expectedError := rc.getExpected(t, path) + if expectedError == "" && expectedOutput == "" { + // missing expected + return + } + + err = cmd.Execute() + + // Compae the results + if expectedError != "" && !assert.Error(t, err, actualOutput.String()) { + t.FailNow() + } + if expectedError == "" && !assert.NoError(t, err, actualError.String()) { + t.FailNow() + } + if !assert.Equal(t, + strings.TrimSpace(expectedOutput), + strings.TrimSpace(actualOutput.String()), actualError.String()) { + t.FailNow() + } + if !assert.Contains(t, + strings.TrimSpace(actualError.String()), + strings.TrimSpace(expectedError), actualOutput.String()) { + t.FailNow() + } +} + +// getExpected reads the expected results and error files +func (rc ResultsChecker) getExpected(t *testing.T, path string) (string, string) { + // read the expected results + var expectedOutput, expectedError string + if rc.ExpectedOutputFilname != "" { + _, err := os.Stat(filepath.Join(path, rc.ExpectedOutputFilname)) + if !os.IsNotExist(err) && err != nil { + t.FailNow() + } + if err == nil { + // only read the file if it exists + b, err := ioutil.ReadFile(filepath.Join(path, rc.ExpectedOutputFilname)) + if !assert.NoError(t, err) { + t.FailNow() + } + expectedOutput = string(b) + } + } + if rc.ExpectedErrorFilename != "" { + _, err := os.Stat(filepath.Join(path, rc.ExpectedErrorFilename)) + if !os.IsNotExist(err) && err != nil { + t.FailNow() + } + if err == nil { + // only read the file if it exists + b, err := ioutil.ReadFile(filepath.Join(path, rc.ExpectedErrorFilename)) + if !assert.NoError(t, err) { + t.FailNow() + } + expectedError = string(b) + } + } + return expectedOutput, expectedError +}