...

Source file src/github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/resourcefixture/resourcefixture.go

Documentation: github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/resourcefixture

     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 resourcefixture
    16  
    17  import (
    18  	"fmt"
    19  	"io/ioutil"
    20  	"path"
    21  	"path/filepath"
    22  	"strings"
    23  	"testing"
    24  
    25  	testcontroller "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/controller"
    26  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util/repo"
    27  
    28  	"github.com/ghodss/yaml"
    29  	"github.com/golang-collections/go-datastructures/queue"
    30  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    31  	"k8s.io/apimachinery/pkg/runtime/schema"
    32  )
    33  
    34  type TestType string
    35  
    36  const (
    37  	Unknown                     TestType = "unknown"
    38  	Basic                       TestType = "basic"
    39  	ContainerAnnotations        TestType = "containerannotations"
    40  	Directives                  TestType = "directives"
    41  	ExternalRef                 TestType = "externalref"
    42  	SensitiveField              TestType = "sensitivefield"
    43  	IAMExternalOnlyRef          TestType = "iamexternalonlyref"
    44  	IAMMemberReferences         TestType = "iammemberreferences"
    45  	ResourceID                  TestType = "resourceid"
    46  	StateAbsentInSpec           TestType = "stateabsentinspec"
    47  	ResourceOverrides           TestType = "resourceoverrides"
    48  	ReconcileIntervalAnnotation TestType = "reconcileintervalannotations"
    49  )
    50  
    51  type ResourceFixture struct {
    52  	GVK          schema.GroupVersionKind
    53  	Name         string
    54  	Create       []byte
    55  	Update       []byte
    56  	Dependencies []byte
    57  	Type         TestType
    58  }
    59  
    60  // Load loads all test cases found in the testdata directory. A
    61  // test case is any directory in the tree that contains a create.yaml file (and
    62  // optionally a dependencies.yaml and update.yaml). The name of the directory
    63  // containing the YAMLs is used as the name of the test case.
    64  func Load(t *testing.T) []ResourceFixture {
    65  	return LoadWithFilter(t, nil, nil)
    66  }
    67  
    68  type LightFilter func(name string, testType TestType) bool
    69  type HeavyFilter func(fixture ResourceFixture) bool
    70  
    71  // LoadWithFilter returns all fixtures that match the filter functions - a filter function matches by returning 'true'
    72  // * use 'lightFilterFunc' for filtering based on test names and types (determining these values is 'lightweight' as it
    73  // only relies on directory and file names)
    74  // * use 'heavyFilterFunc' for filtering based on the contents of the YAML file(s)
    75  //
    76  // if a 'nil' value is supplied for a given filter function then it is assumed that all fixtures match the filter
    77  func LoadWithFilter(t *testing.T, lightFilterFunc LightFilter, heavyFilterFunc HeavyFilter) []ResourceFixture {
    78  	t.Helper()
    79  	allCases := make([]ResourceFixture, 0)
    80  	q := queue.New(1)
    81  	rootDir := getTestDataPath(t)
    82  	q.Put(rootDir)
    83  	for !q.Empty() {
    84  		items, err := q.Get(1)
    85  		if err != nil {
    86  			t.Fatalf("error retreiving an item from queue: %v", err)
    87  		}
    88  		dir := items[0].(string)
    89  		fileInfos, err := ioutil.ReadDir(dir)
    90  		if err != nil {
    91  			t.Fatalf("error reading directory '%v': %v", dir, err)
    92  		}
    93  		testToFileName := make(map[string]string)
    94  		for _, fi := range fileInfos {
    95  			if fi.IsDir() {
    96  				q.Put(path.Join(dir, fi.Name()))
    97  				continue
    98  			}
    99  			if !strings.HasSuffix(fi.Name(), ".yaml") {
   100  				continue
   101  			}
   102  			fileNameNoExt := strings.TrimSuffix(fi.Name(), ".yaml")
   103  			if value, ok := testToFileName[fileNameNoExt]; ok {
   104  				t.Fatalf("error, conflicting files for test '%v' in '%v': {%v, %v}", fileNameNoExt, dir, value, fi.Name())
   105  			}
   106  			testToFileName[fileNameNoExt] = fi.Name()
   107  		}
   108  		// TODO: something about tags here
   109  		if createFile, ok := testToFileName["create"]; ok {
   110  			updateFile := testToFileName["update"]
   111  			depFile := testToFileName["dependencies"]
   112  			name := path.Base(dir)
   113  			testType := parseTestTypeFromPath(t, dir)
   114  			if lightFilterFunc != nil && !lightFilterFunc(name, testType) {
   115  				continue
   116  			}
   117  			rf := loadResourceFixture(t, name, testType, dir, createFile, updateFile, depFile)
   118  			if heavyFilterFunc != nil && !heavyFilterFunc(rf) {
   119  				continue
   120  			}
   121  			allCases = append(allCases, rf)
   122  		}
   123  	}
   124  	return allCases
   125  }
   126  
   127  func loadResourceFixture(t *testing.T, testName string, testType TestType, dir, createFile, updateFile, depFile string) ResourceFixture {
   128  	t.Helper()
   129  	createConfig := testcontroller.ReadFileToBytes(t, path.Join(dir, createFile))
   130  	gvk, err := readGroupVersionKind(t, createConfig)
   131  	if err != nil {
   132  		t.Fatalf("unable to determine GroupVersionKind for test case named %v: %v", testName, err)
   133  	}
   134  
   135  	rf := ResourceFixture{
   136  		Name:   testName,
   137  		GVK:    gvk,
   138  		Create: createConfig,
   139  		Type:   testType,
   140  	}
   141  
   142  	if updateFile != "" {
   143  		rf.Update = testcontroller.ReadFileToBytes(t, path.Join(dir, updateFile))
   144  	}
   145  	if depFile != "" {
   146  		rf.Dependencies = testcontroller.ReadFileToBytes(t, path.Join(dir, depFile))
   147  	}
   148  	return rf
   149  }
   150  
   151  func readGroupVersionKind(t *testing.T, config []byte) (schema.GroupVersionKind, error) {
   152  	t.Helper()
   153  	u := &unstructured.Unstructured{}
   154  	err := yaml.Unmarshal(config, u)
   155  	if err != nil {
   156  		return schema.GroupVersionKind{}, fmt.Errorf("error unmarshalling bytes to CRD: %v", err)
   157  	}
   158  	return u.GroupVersionKind(), nil
   159  }
   160  
   161  func parseTestTypeFromPath(t *testing.T, path string) TestType {
   162  	t.Helper()
   163  	switch parseTestDataSubdirFromPath(t, path) {
   164  	case "basic":
   165  		return Basic
   166  	case "containerannotations":
   167  		return ContainerAnnotations
   168  	case "directives":
   169  		return Directives
   170  	case "externalref":
   171  		return ExternalRef
   172  	case "sensitivefield":
   173  		return SensitiveField
   174  	case "iamexternalonlyref":
   175  		return IAMExternalOnlyRef
   176  	case "iammemberreferences":
   177  		return IAMMemberReferences
   178  	case "resourceid":
   179  		return ResourceID
   180  	case "stateabsentinspec":
   181  		return StateAbsentInSpec
   182  	case "resourceoverrides":
   183  		return ResourceOverrides
   184  	case "reconcileintervalannotations":
   185  		return ReconcileIntervalAnnotation
   186  	default:
   187  		t.Fatalf("failed to parse test type for path %v", path)
   188  		return Unknown
   189  	}
   190  }
   191  
   192  func parseTestDataSubdirFromPath(t *testing.T, path string) string {
   193  	t.Helper()
   194  	testDataPath := getTestDataPath(t)
   195  	pathWithoutTestDataPath := strings.TrimPrefix(path, testDataPath+"/")
   196  	pathTokens := strings.Split(pathWithoutTestDataPath, "/")
   197  	return pathTokens[0]
   198  }
   199  
   200  func getTestDataPath(t *testing.T) string {
   201  	t.Helper()
   202  	packagePath := repo.GetCallerPackagePathOrTestFatal(t)
   203  	return filepath.Join(packagePath, "testdata")
   204  }
   205  

View as plain text