Skip to content
This repository has been archived by the owner on Feb 16, 2024. It is now read-only.

Commit

Permalink
fix few change
Browse files Browse the repository at this point in the history
Signed-off-by: Shubham Gupta <iamshubhamgupta2001@gmail.com>
  • Loading branch information
shubham-cmyk committed Sep 22, 2023
1 parent 3ce04f5 commit 9162533
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 125 deletions.
146 changes: 32 additions & 114 deletions pkg/test/step.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"fmt"
"os"
"path/filepath"
"reflect"
"regexp"
"strings"
"testing"
Expand All @@ -16,7 +15,6 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/discovery"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand All @@ -25,6 +23,7 @@ import (
"github.com/kyverno/kuttl/pkg/env"
kfile "github.com/kyverno/kuttl/pkg/file"
"github.com/kyverno/kuttl/pkg/http"
"github.com/kyverno/kuttl/pkg/test/utils"
testutils "github.com/kyverno/kuttl/pkg/test/utils"
)

Expand Down Expand Up @@ -291,7 +290,7 @@ func list(cl client.Client, gvk schema.GroupVersionKind, namespace string) ([]un
}

// CheckResource checks if the expected resource's state in Kubernetes is correct.
func (s *Step) CheckResource(expected runtime.Object, namespace string) []error {
func (s *Step) CheckResource(expected assertArray, namespace string) []error {
cl, err := s.Client(false)
if err != nil {
return []error{err}
Expand All @@ -304,12 +303,12 @@ func (s *Step) CheckResource(expected runtime.Object, namespace string) []error

testErrors := []error{}

name, namespace, err := testutils.Namespaced(dClient, expected, namespace)
name, namespace, err := testutils.Namespaced(dClient, expected.object, namespace)
if err != nil {
return append(testErrors, err)
}

gvk := expected.GetObjectKind().GroupVersionKind()
gvk := expected.object.GetObjectKind().GroupVersionKind()

actuals := []unstructured.Unstructured{}

Expand All @@ -333,25 +332,40 @@ func (s *Step) CheckResource(expected runtime.Object, namespace string) []error
return append(testErrors, err)
}

expectedObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(expected)
expectedObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(expected.object)
if err != nil {
return append(testErrors, err)
}

strategyFactory := func(path string) utils.ArrayComparisonStrategy {
for _, assertArr := range expected.options.AssertArray {
if assertArr.Path == path {
switch assertArr.Strategy {
case harness.StrategyExact:
return utils.StrategyExact(path)
case harness.StrategyAnywhere:
return utils.StrategyAnywhere(path)
}
}
}
// Default strategy if no match is found
return utils.StrategyExact(path)
}

for _, actual := range actuals {
actual := actual

tmpTestErrors := []error{}

if err := testutils.IsSubset(expectedObj, actual.UnstructuredContent()); err != nil {
diff, diffErr := testutils.PrettyDiff(expected, &actual)
if err := testutils.IsSubset(expectedObj, actual.UnstructuredContent(), "", strategyFactory); err != nil {
diff, diffErr := testutils.PrettyDiff(expected.object, &actual)
if diffErr == nil {
tmpTestErrors = append(tmpTestErrors, fmt.Errorf(diff))
} else {
tmpTestErrors = append(tmpTestErrors, diffErr)
}

tmpTestErrors = append(tmpTestErrors, fmt.Errorf("resource %s: %s", testutils.ResourceID(expected), err))
tmpTestErrors = append(tmpTestErrors, fmt.Errorf("resource %s: %s", testutils.ResourceID(expected.object), err))
}

if len(tmpTestErrors) == 0 {
Expand All @@ -375,56 +389,6 @@ func (s *Step) extractDataFromObject(obj client.Object, path string, resourceTyp
return data, nil
}

// validateAssertArray contains the logic to validate an individual AssertArray entry.
func (s *Step) validateAssertArray(obj client.Object, option harness.AssertArray) []error {
var validationErrors []error

actualObject, err := s.GetCurrentResource(obj)
if err != nil {
return append(validationErrors, err)
}

// Extract the data from the current object based on the assert.path
actualData, err := s.extractDataFromObject(actualObject, option.Path, actualObject.GetObjectKind().GroupVersionKind().String())
if err != nil {
validationErrors = append(validationErrors, err)
}

// Match the expected data (from assert.object) with the extracted data
expectedData, err := s.extractDataFromObject(obj, option.Path, obj.GetObjectKind().GroupVersionKind().String())
if err != nil {
validationErrors = append(validationErrors, err)
}

// If we had no errors extracting both actual and expected data, proceed with the equality check.
if len(validationErrors) == 0 {
return append(validationErrors, checkEquals(option, actualData, expectedData))
}
return validationErrors
}

func checkEquals(option harness.AssertArray, actualData, expectedData []interface{}) error {
var validationError error
if option.Strategy == "" {
option.Strategy = harness.StrategyAnywhere
}

switch option.Strategy {
case harness.StrategyAnywhere:
if !contains(actualData, expectedData) {
validationError = fmt.Errorf("expected data not found in current resource")
}
case harness.StrategyExact:
if !reflect.DeepEqual(actualData, expectedData) {
validationError = fmt.Errorf("data in current resource doesn't match exactly with expected data")
}
default:
validationError = fmt.Errorf("unknown strategy: %s", option.Strategy)
}

return validationError
}

// CheckResourceAbsent checks if the expected resource's state is absent in Kubernetes.
func (s *Step) CheckResourceAbsent(expected runtime.Object, namespace string) error {
cl, err := s.Client(false)
Expand Down Expand Up @@ -476,7 +440,8 @@ func (s *Step) CheckResourceAbsent(expected runtime.Object, namespace string) er

var unexpectedObjects []unstructured.Unstructured
for _, actual := range actuals {
if err := testutils.IsSubset(expectedObj, actual.UnstructuredContent()); err == nil {
// To be fixed later
if err := testutils.IsSubset(expectedObj, actual.UnstructuredContent(), "", nil); err == nil {
unexpectedObjects = append(unexpectedObjects, actual)
}
}
Expand Down Expand Up @@ -505,26 +470,13 @@ func (s *Step) Check(namespace string, timeout int) []error {
testErrors := []error{}

for _, expected := range s.Asserts {
if expected.options != nil {
for _, option := range expected.options.AssertArray {
err := s.validateAssertArray(expected.object, option)
if err != nil && !expected.shouldfail {
testErrors = append(testErrors, err...)
}
// if there was no error but we expected one
if err == nil && expected.shouldfail {
testErrors = append(testErrors, errors.New("an error was expected but didn't happen"))
}
}
} else {
err := s.CheckResource(expected.object, namespace)
if err != nil && !expected.shouldfail {
testErrors = append(testErrors, err...)
}
// if there was no error but we expected one
if err == nil && expected.shouldfail {
testErrors = append(testErrors, errors.New("an error was expected but didn't happen"))
}
err := append(testErrors, s.CheckResource(expected, namespace)...)
if err != nil && !expected.shouldfail {
testErrors = append(testErrors, err...)
}
// if there was no error but we expected one
if err == nil && expected.shouldfail {
testErrors = append(testErrors, errors.New("an error was expected but didn't happen"))
}
}

Expand Down Expand Up @@ -748,40 +700,6 @@ func (s *Step) populateObjectsByFileName(fileName string, objects []client.Objec
return nil
}

func (s *Step) GetCurrentResource(expectedObj client.Object) (client.Object, error) {
c, err := s.Client(false)
if err != nil {
return nil, fmt.Errorf("failed to get client: %v", err)
}

// Fetch the object based on its GVK and namespace/name
gvk := expectedObj.GetObjectKind().GroupVersionKind()
namespacedName := types.NamespacedName{
Namespace: expectedObj.GetNamespace(),
Name: expectedObj.GetName(),
}

// Create an empty object to receive the fetched data
fetchedObj := &unstructured.Unstructured{}
fetchedObj.SetGroupVersionKind(gvk)

err = c.Get(context.TODO(), namespacedName, fetchedObj)
if err != nil {
return nil, fmt.Errorf("failed to get %s resource: %v", gvk.String(), err)
}

return fetchedObj, nil
}

func contains(mainSlice, subSlice []interface{}) bool {
for _, m := range mainSlice {
if reflect.DeepEqual(m, subSlice) {
return true
}
}
return false
}

// ObjectsFromPath returns an array of runtime.Objects for files / urls provided
func ObjectsFromPath(path, dir string) ([]client.Object, error) {
if http.IsURL(path) {
Expand Down
64 changes: 53 additions & 11 deletions pkg/test/utils/subset.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import (
"reflect"
)

type ArrayComparisonStrategyFactory func(path string) ArrayComparisonStrategy
type ArrayComparisonStrategy func(actualData, expectedData []interface{}) error

// SubsetError is an error type used by IsSubset for tracking the path in the struct.
type SubsetError struct {
path []string
Expand Down Expand Up @@ -36,7 +39,7 @@ func (e *SubsetError) Error() string {

// IsSubset checks to see if `expected` is a subset of `actual`. A "subset" is an object that is equivalent to
// the other object, but where map keys found in actual that are not defined in expected are ignored.
func IsSubset(expected, actual interface{}) error {
func IsSubset(expected, actual interface{}, currentPath string, strategyFactory ArrayComparisonStrategyFactory) error {
if reflect.TypeOf(expected) != reflect.TypeOf(actual) {
return &SubsetError{
message: fmt.Sprintf("type mismatch: %v != %v", reflect.TypeOf(expected), reflect.TypeOf(actual)),
Expand All @@ -49,17 +52,21 @@ func IsSubset(expected, actual interface{}) error {

switch reflect.TypeOf(expected).Kind() {
case reflect.Slice:
if reflect.ValueOf(expected).Len() != reflect.ValueOf(actual).Len() {
return &SubsetError{
message: fmt.Sprintf("slice length mismatch: %d != %d", reflect.ValueOf(expected).Len(), reflect.ValueOf(actual).Len()),
}
var strategy ArrayComparisonStrategy
if strategyFactory != nil {
strategy = strategyFactory(currentPath)
} else {
strategy = StrategyExact(currentPath)
}

expectedData := make([]interface{}, reflect.ValueOf(expected).Len())
actualData := make([]interface{}, reflect.ValueOf(actual).Len())

for i := 0; i < reflect.ValueOf(expected).Len(); i++ {
if err := IsSubset(reflect.ValueOf(expected).Index(i).Interface(), reflect.ValueOf(actual).Index(i).Interface()); err != nil {
return err
}
expectedData[i] = reflect.ValueOf(expected).Index(i).Interface()
actualData[i] = reflect.ValueOf(actual).Index(i).Interface()
}
return strategy(actualData, expectedData)
case reflect.Map:
iter := reflect.ValueOf(expected).MapRange()

Expand All @@ -68,15 +75,16 @@ func IsSubset(expected, actual interface{}) error {

if !actualValue.IsValid() {
return &SubsetError{
path: []string{iter.Key().String()},
path: []string{currentPath},
message: "key is missing from map",
}
}

if err := IsSubset(iter.Value().Interface(), actualValue.Interface()); err != nil {
newPath := currentPath + "/" + iter.Key().String()

if err := IsSubset(iter.Value().Interface(), actualValue.Interface(), newPath, strategyFactory); err != nil {
subsetErr, ok := err.(*SubsetError)
if ok {
subsetErr.AppendPath(iter.Key().String())
return subsetErr
}
return err
Expand All @@ -90,3 +98,37 @@ func IsSubset(expected, actual interface{}) error {

return nil
}

func StrategyAnywhere(path string) ArrayComparisonStrategy {
return func(actualData, expectedData []interface{}) error {
for i, expectedItem := range expectedData {
matched := false
for _, actualItem := range actualData {
newPath := path + fmt.Sprintf("[%d]", i)
if err := IsSubset(expectedItem, actualItem, newPath, nil); err == nil {
matched = true
break
}
}
if !matched {
return &SubsetError{message: fmt.Sprintf("expected item %v not found in actual slice at path %s", expectedItem, path)}
}
}
return nil
}
}

func StrategyExact(path string) ArrayComparisonStrategy {
return func(actualData, expectedData []interface{}) error {
if len(expectedData) != len(actualData) {
return &SubsetError{message: fmt.Sprintf("slice length mismatch at path %s: %d != %d", path, len(expectedData), len(actualData))}
}
for i, v := range expectedData {
newPath := path + fmt.Sprintf("[%d]", i)
if err := IsSubset(v, actualData[i], newPath, nil); err != nil {
return err
}
}
return nil
}
}

0 comments on commit 9162533

Please sign in to comment.