git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/toml/internal/toml-test/toml.go (about)

     1  //go:build go1.16
     2  // +build go1.16
     3  
     4  package tomltest
     5  
     6  import (
     7  	"math"
     8  	"reflect"
     9  )
    10  
    11  // CompareTOML compares the given arguments.
    12  //
    13  // The returned value is a copy of Test with Failure set to a (human-readable)
    14  // description of the first element that is unequal. If both arguments are equal
    15  // Test is returned unchanged.
    16  //
    17  // Reflect.DeepEqual could work here, but it won't tell us how the two
    18  // structures are different.
    19  func (r Test) CompareTOML(want, have interface{}) Test {
    20  	if isTomlValue(want) {
    21  		if !isTomlValue(have) {
    22  			return r.fail("Type for key '%s' differs:\n"+
    23  				"  Expected:     %[2]v (%[2]T)\n"+
    24  				"  Your encoder: %[3]v (%[3]T)",
    25  				r.Key, want, have)
    26  		}
    27  
    28  		if !deepEqual(want, have) {
    29  			return r.fail("Values for key '%s' differ:\n"+
    30  				"  Expected:     %[2]v (%[2]T)\n"+
    31  				"  Your encoder: %[3]v (%[3]T)",
    32  				r.Key, want, have)
    33  		}
    34  		return r
    35  	}
    36  
    37  	switch w := want.(type) {
    38  	case map[string]interface{}:
    39  		return r.cmpTOMLMap(w, have)
    40  	case []interface{}:
    41  		return r.cmpTOMLArrays(w, have)
    42  	default:
    43  		return r.fail("Unrecognized TOML structure: %T", want)
    44  	}
    45  }
    46  
    47  func (r Test) cmpTOMLMap(want map[string]interface{}, have interface{}) Test {
    48  	haveMap, ok := have.(map[string]interface{})
    49  	if !ok {
    50  		return r.mismatch("table", want, haveMap)
    51  	}
    52  
    53  	// Check that the keys of each map are equivalent.
    54  	for k := range want {
    55  		if _, ok := haveMap[k]; !ok {
    56  			bunk := r.kjoin(k)
    57  			return bunk.fail("Could not find key '%s' in encoder output", bunk.Key)
    58  		}
    59  	}
    60  	for k := range haveMap {
    61  		if _, ok := want[k]; !ok {
    62  			bunk := r.kjoin(k)
    63  			return bunk.fail("Could not find key '%s' in expected output", bunk.Key)
    64  		}
    65  	}
    66  
    67  	// Okay, now make sure that each value is equivalent.
    68  	for k := range want {
    69  		if sub := r.kjoin(k).CompareTOML(want[k], haveMap[k]); sub.Failed() {
    70  			return sub
    71  		}
    72  	}
    73  	return r
    74  }
    75  
    76  func (r Test) cmpTOMLArrays(want []interface{}, have interface{}) Test {
    77  	// Slice can be decoded to []interface{} for an array of primitives, or
    78  	// []map[string]interface{} for an array of tables.
    79  	//
    80  	// TODO: it would be nicer if it could always decode to []interface{}?
    81  	haveSlice, ok := have.([]interface{})
    82  	if !ok {
    83  		tblArray, ok := have.([]map[string]interface{})
    84  		if !ok {
    85  			return r.mismatch("array", want, have)
    86  		}
    87  
    88  		haveSlice = make([]interface{}, len(tblArray))
    89  		for i := range tblArray {
    90  			haveSlice[i] = tblArray[i]
    91  		}
    92  	}
    93  
    94  	if len(want) != len(haveSlice) {
    95  		return r.fail("Array lengths differ for key '%s'"+
    96  			"  Expected:     %[2]v (len=%[4]d)\n"+
    97  			"  Your encoder: %[3]v (len=%[5]d)",
    98  			r.Key, want, haveSlice, len(want), len(haveSlice))
    99  	}
   100  	for i := 0; i < len(want); i++ {
   101  		if sub := r.CompareTOML(want[i], haveSlice[i]); sub.Failed() {
   102  			return sub
   103  		}
   104  	}
   105  	return r
   106  }
   107  
   108  // reflect.DeepEqual() that deals with NaN != NaN
   109  func deepEqual(want, have interface{}) bool {
   110  	var wantF, haveF float64
   111  	switch f := want.(type) {
   112  	case float32:
   113  		wantF = float64(f)
   114  	case float64:
   115  		wantF = f
   116  	}
   117  	switch f := have.(type) {
   118  	case float32:
   119  		haveF = float64(f)
   120  	case float64:
   121  		haveF = f
   122  	}
   123  	if math.IsNaN(wantF) && math.IsNaN(haveF) {
   124  		return true
   125  	}
   126  
   127  	return reflect.DeepEqual(want, have)
   128  }
   129  
   130  func isTomlValue(v interface{}) bool {
   131  	switch v.(type) {
   132  	case map[string]interface{}, []interface{}:
   133  		return false
   134  	}
   135  	return true
   136  }