//go:build go1.16 // +build go1.16 package tomltest import ( "math" "reflect" ) // CompareTOML compares the given arguments. // // The returned value is a copy of Test with Failure set to a (human-readable) // description of the first element that is unequal. If both arguments are equal // Test is returned unchanged. // // Reflect.DeepEqual could work here, but it won't tell us how the two // structures are different. func (r Test) CompareTOML(want, have interface{}) Test { if isTomlValue(want) { if !isTomlValue(have) { return r.fail("Type for key '%s' differs:\n"+ " Expected: %[2]v (%[2]T)\n"+ " Your encoder: %[3]v (%[3]T)", r.Key, want, have) } if !deepEqual(want, have) { return r.fail("Values for key '%s' differ:\n"+ " Expected: %[2]v (%[2]T)\n"+ " Your encoder: %[3]v (%[3]T)", r.Key, want, have) } return r } switch w := want.(type) { case map[string]interface{}: return r.cmpTOMLMap(w, have) case []interface{}: return r.cmpTOMLArrays(w, have) default: return r.fail("Unrecognized TOML structure: %T", want) } } func (r Test) cmpTOMLMap(want map[string]interface{}, have interface{}) Test { haveMap, ok := have.(map[string]interface{}) if !ok { return r.mismatch("table", want, haveMap) } // Check that the keys of each map are equivalent. for k := range want { if _, ok := haveMap[k]; !ok { bunk := r.kjoin(k) return bunk.fail("Could not find key '%s' in encoder output", bunk.Key) } } for k := range haveMap { if _, ok := want[k]; !ok { bunk := r.kjoin(k) return bunk.fail("Could not find key '%s' in expected output", bunk.Key) } } // Okay, now make sure that each value is equivalent. for k := range want { if sub := r.kjoin(k).CompareTOML(want[k], haveMap[k]); sub.Failed() { return sub } } return r } func (r Test) cmpTOMLArrays(want []interface{}, have interface{}) Test { // Slice can be decoded to []interface{} for an array of primitives, or // []map[string]interface{} for an array of tables. // // TODO: it would be nicer if it could always decode to []interface{}? haveSlice, ok := have.([]interface{}) if !ok { tblArray, ok := have.([]map[string]interface{}) if !ok { return r.mismatch("array", want, have) } haveSlice = make([]interface{}, len(tblArray)) for i := range tblArray { haveSlice[i] = tblArray[i] } } if len(want) != len(haveSlice) { return r.fail("Array lengths differ for key '%s'"+ " Expected: %[2]v (len=%[4]d)\n"+ " Your encoder: %[3]v (len=%[5]d)", r.Key, want, haveSlice, len(want), len(haveSlice)) } for i := 0; i < len(want); i++ { if sub := r.CompareTOML(want[i], haveSlice[i]); sub.Failed() { return sub } } return r } // reflect.DeepEqual() that deals with NaN != NaN func deepEqual(want, have interface{}) bool { var wantF, haveF float64 switch f := want.(type) { case float32: wantF = float64(f) case float64: wantF = f } switch f := have.(type) { case float32: haveF = float64(f) case float64: haveF = f } if math.IsNaN(wantF) && math.IsNaN(haveF) { return true } return reflect.DeepEqual(want, have) } func isTomlValue(v interface{}) bool { switch v.(type) { case map[string]interface{}, []interface{}: return false } return true }