github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/testhelpers/comparehelpers/deep_compare.go (about)

     1  package comparehelpers
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  )
     7  
     8  // Returns true if 'containee' is contained in 'container'
     9  // Note this method searches all objects in 'container' for containee
    10  // Contains is defined by the following relationship
    11  // basic data types (string, float, int,...):
    12  //
    13  //	container == containee
    14  //
    15  // maps:
    16  //
    17  //	every key-value pair from containee is in container
    18  //	Ex: {"a": 1, "b": 2, "c": 3} contains {"a": 1, "c": 3}
    19  //
    20  // arrays:
    21  //
    22  //	every element in containee is present and ordered in an array in container
    23  //	Ex: [1, 1, 4, 3, 10, 4] contains [1, 3, 4 ]
    24  //
    25  // Limitaions:
    26  // Cannot handle the following types: Pointers, Func
    27  // Assumes we are compairing structs generated from JSON, YAML, or TOML.
    28  func DeepContains(container, containee interface{}) bool {
    29  	if container == nil || containee == nil {
    30  		return container == containee
    31  	}
    32  	v1 := reflect.ValueOf(container)
    33  	v2 := reflect.ValueOf(containee)
    34  
    35  	return deepContains(v1, v2, 0)
    36  }
    37  
    38  func deepContains(v1, v2 reflect.Value, depth int) bool {
    39  	if depth > 200 {
    40  		panic("deep Contains depth exceeded, likely a circular reference")
    41  	}
    42  	if !v1.IsValid() || !v2.IsValid() {
    43  		return v1.IsValid() == v2.IsValid()
    44  	}
    45  
    46  	switch v1.Kind() {
    47  	case reflect.Array, reflect.Slice:
    48  		// check for subset matches in arrays
    49  		return arrayLikeContains(v1, v2, depth+1)
    50  	case reflect.Map:
    51  		return mapContains(v1, v2, depth+1)
    52  	case reflect.Interface:
    53  		return deepContains(v1.Elem(), v2, depth+1)
    54  	case reflect.Ptr, reflect.Struct, reflect.Func:
    55  		panic(fmt.Sprintf("unimplmemented comparison for type: %s", v1.Kind().String()))
    56  	default: // assume it is a atomic datatype
    57  		return reflect.DeepEqual(v1, v2)
    58  	}
    59  }
    60  
    61  func mapContains(v1, v2 reflect.Value, depth int) bool {
    62  	t2 := v2.Kind()
    63  	if t2 == reflect.Interface {
    64  		return mapContains(v1, v2.Elem(), depth+1)
    65  	} else if t2 == reflect.Map {
    66  		result := true
    67  		for _, k := range v2.MapKeys() {
    68  			k2Val := v2.MapIndex(k)
    69  			k1Val := v1.MapIndex(k)
    70  			if !k1Val.IsValid() || !reflect.DeepEqual(k1Val.Interface(), k2Val.Interface()) {
    71  				result = false
    72  				break
    73  			}
    74  		}
    75  		if result {
    76  			return true
    77  		}
    78  	}
    79  	for _, k := range v1.MapKeys() {
    80  		val := v1.MapIndex(k)
    81  		if deepContains(val, v2, depth+1) {
    82  			return true
    83  		}
    84  	}
    85  	return false
    86  }
    87  
    88  func arrayLikeContains(v1, v2 reflect.Value, depth int) bool {
    89  	t2 := v2.Kind()
    90  	if t2 == reflect.Interface {
    91  		return mapContains(v1, v2.Elem(), depth+1)
    92  	} else if t2 == reflect.Array || t2 == reflect.Slice {
    93  		v1Index := 0
    94  		v2Index := 0
    95  		for v1Index < v1.Len() && v2Index < v2.Len() {
    96  			if reflect.DeepEqual(v1.Index(v1Index).Interface(), v2.Index(v2Index).Interface()) {
    97  				v2Index++
    98  			}
    99  			v1Index++
   100  		}
   101  		if v2Index == v2.Len() {
   102  			return true
   103  		}
   104  	}
   105  	for i := 0; i < v1.Len(); i++ {
   106  		if deepContains(v1.Index(i), v2, depth+1) {
   107  			return true
   108  		}
   109  	}
   110  	return false
   111  }