github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/htesting/hqt/checkers.go (about)

     1  // Copyright 2019 The Hugo Authors. All rights reserved.
     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  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package hqt
    15  
    16  import (
    17  	"errors"
    18  	"fmt"
    19  	"reflect"
    20  	"strings"
    21  
    22  	qt "github.com/frankban/quicktest"
    23  	"github.com/gohugoio/hugo/htesting"
    24  	"github.com/google/go-cmp/cmp"
    25  	"github.com/spf13/cast"
    26  )
    27  
    28  // IsSameString asserts that two strings are equal. The two strings
    29  // are normalized (whitespace removed) before doing a ==.
    30  // Also note that two strings can be the same even if they're of different
    31  // types.
    32  var IsSameString qt.Checker = &stringChecker{
    33  	argNames: []string{"got", "want"},
    34  }
    35  
    36  // IsSameType asserts that got is the same type as want.
    37  var IsSameType qt.Checker = &typeChecker{
    38  	argNames: []string{"got", "want"},
    39  }
    40  
    41  type argNames []string
    42  
    43  func (a argNames) ArgNames() []string {
    44  	return a
    45  }
    46  
    47  type typeChecker struct {
    48  	argNames
    49  }
    50  
    51  // Check implements Checker.Check by checking that got and args[0] is of the same type.
    52  func (c *typeChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) (err error) {
    53  	if want := args[0]; reflect.TypeOf(got) != reflect.TypeOf(want) {
    54  		if _, ok := got.(error); ok && want == nil {
    55  			return errors.New("got non-nil error")
    56  		}
    57  		return errors.New("values are not of same type")
    58  	}
    59  	return nil
    60  }
    61  
    62  type stringChecker struct {
    63  	argNames
    64  }
    65  
    66  // Check implements Checker.Check by checking that got and args[0] represents the same normalized text (whitespace etc. removed).
    67  func (c *stringChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) (err error) {
    68  	s1, s2 := cast.ToString(got), cast.ToString(args[0])
    69  
    70  	if s1 == s2 {
    71  		return nil
    72  	}
    73  
    74  	s1, s2 = normalizeString(s1), normalizeString(s2)
    75  
    76  	if s1 == s2 {
    77  		return nil
    78  	}
    79  
    80  	return fmt.Errorf("values are not the same text: %s", strings.Join(htesting.DiffStrings(s1, s2), " | "))
    81  }
    82  
    83  func normalizeString(s string) string {
    84  	s = strings.ReplaceAll(s, "\r\n", "\n")
    85  
    86  	lines := strings.Split(strings.TrimSpace(s), "\n")
    87  	for i, line := range lines {
    88  		lines[i] = strings.Join(strings.Fields(strings.TrimSpace(line)), "")
    89  	}
    90  	return strings.Join(lines, "\n")
    91  }
    92  
    93  // DeepAllowUnexported creates an option to allow compare of unexported types
    94  // in the given list of types.
    95  // see https://github.com/google/go-cmp/issues/40#issuecomment-328615283
    96  func DeepAllowUnexported(vs ...interface{}) cmp.Option {
    97  	m := make(map[reflect.Type]struct{})
    98  	for _, v := range vs {
    99  		structTypes(reflect.ValueOf(v), m)
   100  	}
   101  	var typs []interface{}
   102  	for t := range m {
   103  		typs = append(typs, reflect.New(t).Elem().Interface())
   104  	}
   105  	return cmp.AllowUnexported(typs...)
   106  }
   107  
   108  func structTypes(v reflect.Value, m map[reflect.Type]struct{}) {
   109  	if !v.IsValid() {
   110  		return
   111  	}
   112  	switch v.Kind() {
   113  	case reflect.Ptr:
   114  		if !v.IsNil() {
   115  			structTypes(v.Elem(), m)
   116  		}
   117  	case reflect.Interface:
   118  		if !v.IsNil() {
   119  			structTypes(v.Elem(), m)
   120  		}
   121  	case reflect.Slice, reflect.Array:
   122  		for i := 0; i < v.Len(); i++ {
   123  			structTypes(v.Index(i), m)
   124  		}
   125  	case reflect.Map:
   126  		for _, k := range v.MapKeys() {
   127  			structTypes(v.MapIndex(k), m)
   128  		}
   129  	case reflect.Struct:
   130  		m[v.Type()] = struct{}{}
   131  		for i := 0; i < v.NumField(); i++ {
   132  			structTypes(v.Field(i), m)
   133  		}
   134  	}
   135  }