github.com/looshlee/beatles@v0.0.0-20220727174639-742810ab631c/pkg/checker/checker.go (about)

     1  // Copyright 2018-2019 Authors of Cilium
     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 checker
    16  
    17  import (
    18  	"reflect"
    19  
    20  	"github.com/cilium/cilium/pkg/comparator"
    21  
    22  	"github.com/google/go-cmp/cmp"
    23  
    24  	"gopkg.in/check.v1"
    25  )
    26  
    27  type diffChecker struct {
    28  	*check.CheckerInfo
    29  }
    30  
    31  // DeepEquals is a GoCheck checker that does a diff between two objects and
    32  // pretty-prints any difference between the two. It can act as a substitute
    33  // for DeepEquals.
    34  var (
    35  	defaultParams               = []string{"obtained", "expected"}
    36  	DeepEquals    check.Checker = &diffChecker{
    37  		&check.CheckerInfo{Name: "Diff", Params: defaultParams},
    38  	}
    39  )
    40  
    41  // Check performs a diff between two objects provided as parameters, and
    42  // returns either true if the objects are identical, or false otherwise. If
    43  // it returns false, it also returns the unified diff between the expected
    44  // and obtained output.
    45  func (checker *diffChecker) Check(params []interface{}, names []string) (result bool, error string) {
    46  	if len(params) != 2 || len(names) != 2 {
    47  		return false, "params and names must be of length 2"
    48  	}
    49  
    50  	if reflect.DeepEqual(params[0], params[1]) {
    51  		return true, ""
    52  	}
    53  
    54  	return false, comparator.CompareWithNames(params[0], params[1], names[0], names[1])
    55  }
    56  
    57  // DeepEqual tests whether two parameters are deeply equal, and returns true if
    58  // they are. If the objects are not deeply equal, then the second return value
    59  // includes a json representation of the difference between the parameters.
    60  func DeepEqual(params ...interface{}) (bool, string) {
    61  	return DeepEquals.Check(params, defaultParams)
    62  }
    63  
    64  type cmpChecker struct {
    65  	*check.CheckerInfo
    66  }
    67  
    68  // Equals is a GoCheck checker that does a diff between two objects and
    69  // pretty-prints any difference between the two. It can act as a substitute
    70  // for DeepEquals.
    71  var (
    72  	cmpParams               = []string{"obtained", "expected"}
    73  	Equals    check.Checker = &cmpChecker{
    74  		&check.CheckerInfo{Name: "Equals", Params: cmpParams},
    75  	}
    76  )
    77  
    78  // Check performs a diff between two objects provided as parameters, and
    79  // returns either true if the objects are identical, or false otherwise. If
    80  // it returns false, it also returns the unified diff between the expected
    81  // and obtained output.
    82  func (checker *cmpChecker) Check(params []interface{}, _ []string) (result bool, error string) {
    83  	if len(params) < 2 {
    84  		return false, "Parameter missing"
    85  	}
    86  
    87  	// Diff expects to receive parameters in order ("expected",
    88  	// "obtained"), but our convention is to pass them as
    89  	// ("obtained", "expected"), so reverse them here.
    90  	diff := cmp.Diff(params[1], params[0], DeepAllowUnexported(params[1], params[0]))
    91  
    92  	return diff == "", diff
    93  }
    94  
    95  // Equal tests whether two parameters are deeply equal, and returns true if
    96  // they are. If the objects are not deeply equal, then the second return value
    97  // includes a json representation of the difference between the parameters.
    98  func Equal(params ...interface{}) (bool, string) {
    99  	return Equals.Check(params, cmpParams)
   100  }
   101  
   102  func DeepAllowUnexported(vs ...interface{}) cmp.Option {
   103  	m := make(map[reflect.Type]struct{})
   104  	for _, v := range vs {
   105  		structTypes(reflect.ValueOf(v), m)
   106  	}
   107  	var typs []interface{}
   108  	for t := range m {
   109  		typs = append(typs, reflect.New(t).Elem().Interface())
   110  	}
   111  	return cmp.AllowUnexported(typs...)
   112  }
   113  
   114  func structTypes(v reflect.Value, m map[reflect.Type]struct{}) {
   115  	if !v.IsValid() {
   116  		return
   117  	}
   118  	switch v.Kind() {
   119  	case reflect.Ptr:
   120  		if !v.IsNil() {
   121  			structTypes(v.Elem(), m)
   122  		}
   123  	case reflect.Interface:
   124  		if !v.IsNil() {
   125  			structTypes(v.Elem(), m)
   126  		}
   127  	case reflect.Slice, reflect.Array:
   128  		for i := 0; i < v.Len(); i++ {
   129  			structTypes(v.Index(i), m)
   130  		}
   131  	case reflect.Map:
   132  		for _, k := range v.MapKeys() {
   133  			structTypes(v.MapIndex(k), m)
   134  		}
   135  	case reflect.Struct:
   136  		m[v.Type()] = struct{}{}
   137  		for i := 0; i < v.NumField(); i++ {
   138  			structTypes(v.Field(i), m)
   139  		}
   140  	}
   141  }