github.com/Aoi-hosizora/ahlib@v1.5.1-0.20230404072829-241b93cf91c7/xtesting/xtesting_internal.go (about)

     1  package xtesting
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"github.com/Aoi-hosizora/ahlib/xreflect"
     7  	"math"
     8  	"reflect"
     9  	"regexp"
    10  	"strings"
    11  )
    12  
    13  // ==================
    14  // internal functions
    15  // ==================
    16  
    17  // validateArgsAreNotFunc checks whether provided arguments are not function type, and can be safely used in Equal, NotEqual,
    18  // EqualValue, NotEqualValue functions.
    19  func validateArgsAreNotFunc(give, want interface{}) error {
    20  	if give == nil || want == nil {
    21  		return nil
    22  	}
    23  
    24  	giveKind := reflect.TypeOf(give).Kind()
    25  	wantKind := reflect.TypeOf(want).Kind()
    26  	if giveKind == reflect.Func || wantKind == reflect.Func {
    27  		return errors.New("cannot take func type as argument")
    28  	}
    29  
    30  	return nil
    31  }
    32  
    33  // validateArgsAreSameKind checks whether provided arguments are all non-nil, and have the same reflect.Kind.
    34  func validateArgsAreSameKind(give, want interface{}, kind reflect.Kind) error {
    35  	if give == nil || want == nil {
    36  		return errors.New("cannot take nil value as argument")
    37  	}
    38  
    39  	giveType := reflect.TypeOf(give)
    40  	wantType := reflect.TypeOf(want)
    41  	if giveType.Kind() != kind {
    42  		return fmt.Errorf("cannot take non-%s type (%T) as argument", kind.String(), give)
    43  	}
    44  	if wantType.Kind() != kind {
    45  		return fmt.Errorf("cannot take non-%s type (%T) as argument", kind.String(), want)
    46  	}
    47  
    48  	return nil
    49  }
    50  
    51  // matchRegexp returns true if a specified regexp matches a string.
    52  func matchRegexp(rx interface{}, str string) (matched bool, re *regexp.Regexp, err error) {
    53  	var r *regexp.Regexp
    54  	if rr, ok := rx.(*regexp.Regexp); ok {
    55  		r = rr
    56  	} else if s, ok := rx.(string); ok {
    57  		r, err = regexp.Compile(s)
    58  		if err != nil {
    59  			return false, nil, errors.New("invalid regexp")
    60  		}
    61  	} else {
    62  		return false, nil, errors.New("must be *regexp.Regexp or string")
    63  	}
    64  
    65  	return r.FindStringIndex(str) != nil, r, nil
    66  }
    67  
    68  // calcDiffInDelta calculates the different between given values.
    69  func calcDiffInDelta(give, want interface{}, delta float64) (inDelta bool, actualDiff float64, err error) {
    70  	giveFloat, ok1 := xreflect.Float64Value(give)
    71  	wantFloat, ok2 := xreflect.Float64Value(want)
    72  
    73  	if !ok1 || !ok2 {
    74  		return false, 0, errors.New("parameters must be numerical")
    75  	}
    76  	if math.IsNaN(giveFloat) || math.IsNaN(wantFloat) {
    77  		return false, 0, errors.New("numbers must not be NaN")
    78  	}
    79  	if math.IsNaN(delta) {
    80  		return false, 0, errors.New("delta must not be NaN")
    81  	}
    82  
    83  	actualDiff = math.Abs(giveFloat - wantFloat)
    84  	return actualDiff <= math.Abs(delta), actualDiff, nil
    85  }
    86  
    87  // calcRelativeError calculates the relative error between given values.
    88  func calcRelativeError(give, want interface{}, epsilon float64) (inEps bool, actualRee float64, err error) {
    89  	giveFloat, ok1 := xreflect.Float64Value(give)
    90  	wantFloat, ok2 := xreflect.Float64Value(want)
    91  
    92  	if !ok1 || !ok2 {
    93  		return false, 0, errors.New("parameters must be numerical")
    94  	}
    95  	if math.IsNaN(giveFloat) || math.IsNaN(wantFloat) {
    96  		return false, 0, errors.New("numbers must not be NaN")
    97  	}
    98  	if math.IsNaN(epsilon) {
    99  		return false, 0, errors.New("epsilon must not be NaN")
   100  	}
   101  	if wantFloat == 0 {
   102  		return false, 0, fmt.Errorf("wanted value must not be zero")
   103  	}
   104  
   105  	actualRee = math.Abs(giveFloat-wantFloat) / math.Abs(wantFloat)
   106  	return actualRee <= math.Abs(epsilon), actualRee, nil
   107  }
   108  
   109  // containElement try loop over the list check if the list includes the element.
   110  func containElement(list interface{}, element interface{}) (found bool, err error) {
   111  	if list == nil || element == nil {
   112  		return false, errors.New("cannot take nil as argument")
   113  	}
   114  
   115  	listValue := reflect.ValueOf(list)
   116  	elemType := reflect.TypeOf(element)
   117  	listType := listValue.Type()
   118  	listKind := listValue.Kind()
   119  
   120  	if listKind == reflect.String && elemType.Kind() != reflect.String {
   121  		return false, fmt.Errorf("cannot take incompatible element type `%T` as argument", element)
   122  	}
   123  	if listKind == reflect.Array || listKind == reflect.Slice || listKind == reflect.Map {
   124  		listElemType := listType.Elem() // allow interface{} values compare with all other values
   125  		if listElemType.Kind() != reflect.Interface && !reflect.DeepEqual(elemType, listElemType) {
   126  			return false, fmt.Errorf("cannot take incompatible element type `%T` as argument", element)
   127  		}
   128  	}
   129  
   130  	switch listKind {
   131  	case reflect.String:
   132  		elementValue := reflect.ValueOf(element)
   133  		return strings.Contains(listValue.String(), elementValue.String()), nil
   134  
   135  	case reflect.Map:
   136  		mapKeys := listValue.MapKeys()
   137  		for i := 0; i < len(mapKeys); i++ {
   138  			if reflect.DeepEqual(mapKeys[i].Interface(), element) {
   139  				return true, nil
   140  			}
   141  		}
   142  		return false, nil
   143  
   144  	case reflect.Array, reflect.Slice:
   145  		for i := 0; i < listValue.Len(); i++ {
   146  			if reflect.DeepEqual(listValue.Index(i).Interface(), element) {
   147  				return true, nil
   148  			}
   149  		}
   150  		return false, nil
   151  
   152  	default:
   153  		return false, fmt.Errorf("cannot take a non-list type `%T` as argument", list)
   154  	}
   155  }
   156  
   157  // validateArgsAreSameList checks that the provided value is array or slice, and their types are the same.
   158  func validateArgsAreSameList(listA, listB interface{}) error {
   159  	if listA == nil || listB == nil {
   160  		return errors.New("cannot take nil as argument")
   161  	}
   162  
   163  	typeA, typeB := reflect.TypeOf(listA), reflect.TypeOf(listB)
   164  	kindA, kindB := typeA.Kind(), typeB.Kind()
   165  
   166  	if kindA != reflect.Array && kindA != reflect.Slice {
   167  		return fmt.Errorf("cannot take a non-list type `%T` as argument", listA)
   168  	}
   169  	if kindB != reflect.Array && kindB != reflect.Slice {
   170  		return fmt.Errorf("cannot take a non-list type `%T` as argument", listB)
   171  	}
   172  	if !reflect.DeepEqual(typeA, typeB) {
   173  		return fmt.Errorf("cannot take two lists in different-types `%T` and `%T` as argument", listA, listB)
   174  	}
   175  
   176  	return nil
   177  }
   178  
   179  // containAllElements checks the specified list contains all elements given in the specified subset.
   180  func containAllElements(list, subset interface{}) (allFound bool, element interface{}) {
   181  	if xreflect.IsEmptyCollection(list) && xreflect.IsEmptyCollection(subset) {
   182  		return true, nil
   183  	}
   184  
   185  	subsetValue := reflect.ValueOf(subset)
   186  	for i := 0; i < subsetValue.Len(); i++ {
   187  		el := subsetValue.Index(i).Interface()
   188  		found, err := containElement(list, el)
   189  
   190  		if err != nil || !found {
   191  			return false, el
   192  		}
   193  	}
   194  
   195  	return true, nil
   196  }
   197  
   198  // diffLists diffs two arrays/slices and returns slices of elements that are only in A and only in B. If some element is
   199  // present multiple times, each instance is counted separately. The order of items in both lists is ignored.
   200  func diffLists(listA, listB interface{}) (extraA []interface{}, extraB []interface{}) {
   201  	if (xreflect.IsEmptyCollection(listA)) && (xreflect.IsEmptyCollection(listB)) {
   202  		return nil, nil
   203  	}
   204  
   205  	valueA, valueB := reflect.ValueOf(listA), reflect.ValueOf(listB)
   206  	lenA, lenB := valueA.Len(), valueB.Len()
   207  	visited := make([]bool, lenB) // Mark indexes in valueB that we already used
   208  
   209  	for i := 0; i < lenA; i++ {
   210  		element := valueA.Index(i).Interface()
   211  		found := false
   212  		for j := 0; j < lenB; j++ {
   213  			if visited[j] {
   214  				continue
   215  			}
   216  			if reflect.DeepEqual(valueB.Index(j).Interface(), element) {
   217  				visited[j] = true
   218  				found = true
   219  				break
   220  			}
   221  		}
   222  		if !found {
   223  			extraA = append(extraA, element)
   224  		}
   225  	}
   226  
   227  	for j := 0; j < lenB; j++ {
   228  		if visited[j] {
   229  			continue
   230  		}
   231  		extraB = append(extraB, valueB.Index(j).Interface())
   232  	}
   233  
   234  	return extraA, extraB
   235  }
   236  
   237  // validateArgsForImplement checks the value of object is not nil, and checks the type of interfacePtr is *SomeInterface.
   238  func validateArgsForImplement(value, interfacePtr interface{}) (interfaceType reflect.Type, err error) {
   239  	interfaceType = reflect.TypeOf(interfacePtr)
   240  	if interfaceType == nil {
   241  		return nil, errors.New("cannot take nil as interfacePtr argument")
   242  	}
   243  	if interfaceType.Kind() != reflect.Ptr {
   244  		return nil, errors.New("cannot take non-interface-pointer type as interfacePtr argument")
   245  	}
   246  	interfaceType = interfaceType.Elem()
   247  	if interfaceType.Kind() != reflect.Interface {
   248  		return nil, errors.New("cannot take non-interface-pointer type as interfacePtr argument")
   249  	}
   250  
   251  	if value == nil {
   252  		return nil, fmt.Errorf("cannot check whether nil value implements `%s` or not", interfaceType.String())
   253  	}
   254  
   255  	return interfaceType, nil
   256  }
   257  
   258  // checkPanic returns true if the function passed to it panics. Otherwise, it returns false.
   259  func checkPanic(f func()) (didPanic bool, message interface{}) {
   260  	// set to true first, used to detect panic(nil)
   261  	didPanic = true
   262  
   263  	defer func() {
   264  		message = recover()
   265  	}()
   266  
   267  	// call the target function
   268  	f()
   269  	didPanic = false
   270  
   271  	return
   272  }