| // Copyright 2018 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package fmtsort_test | |
| import ( | |
| "fmt" | |
| "internal/fmtsort" | |
| "math" | |
| "reflect" | |
| "sort" | |
| "strings" | |
| "testing" | |
| "unsafe" | |
| ) | |
| var compareTests = [][]reflect.Value{ | |
| ct(reflect.TypeOf(int(0)), -1, 0, 1), | |
| ct(reflect.TypeOf(int8(0)), -1, 0, 1), | |
| ct(reflect.TypeOf(int16(0)), -1, 0, 1), | |
| ct(reflect.TypeOf(int32(0)), -1, 0, 1), | |
| ct(reflect.TypeOf(int64(0)), -1, 0, 1), | |
| ct(reflect.TypeOf(uint(0)), 0, 1, 5), | |
| ct(reflect.TypeOf(uint8(0)), 0, 1, 5), | |
| ct(reflect.TypeOf(uint16(0)), 0, 1, 5), | |
| ct(reflect.TypeOf(uint32(0)), 0, 1, 5), | |
| ct(reflect.TypeOf(uint64(0)), 0, 1, 5), | |
| ct(reflect.TypeOf(uintptr(0)), 0, 1, 5), | |
| ct(reflect.TypeOf(string("")), "", "a", "ab"), | |
| ct(reflect.TypeOf(float32(0)), math.NaN(), math.Inf(-1), -1e10, 0, 1e10, math.Inf(1)), | |
| ct(reflect.TypeOf(float64(0)), math.NaN(), math.Inf(-1), -1e10, 0, 1e10, math.Inf(1)), | |
| ct(reflect.TypeOf(complex64(0+1i)), -1-1i, -1+0i, -1+1i, 0-1i, 0+0i, 0+1i, 1-1i, 1+0i, 1+1i), | |
| ct(reflect.TypeOf(complex128(0+1i)), -1-1i, -1+0i, -1+1i, 0-1i, 0+0i, 0+1i, 1-1i, 1+0i, 1+1i), | |
| ct(reflect.TypeOf(false), false, true), | |
| ct(reflect.TypeOf(&ints[0]), &ints[0], &ints[1], &ints[2]), | |
| ct(reflect.TypeOf(unsafe.Pointer(&ints[0])), unsafe.Pointer(&ints[0]), unsafe.Pointer(&ints[1]), unsafe.Pointer(&ints[2])), | |
| ct(reflect.TypeOf(chans[0]), chans[0], chans[1], chans[2]), | |
| ct(reflect.TypeOf(toy{}), toy{0, 1}, toy{0, 2}, toy{1, -1}, toy{1, 1}), | |
| ct(reflect.TypeOf([2]int{}), [2]int{1, 1}, [2]int{1, 2}, [2]int{2, 0}), | |
| ct(reflect.TypeOf(any(any(0))), iFace, 1, 2, 3), | |
| } | |
| var iFace any | |
| func ct(typ reflect.Type, args ...any) []reflect.Value { | |
| value := make([]reflect.Value, len(args)) | |
| for i, v := range args { | |
| x := reflect.ValueOf(v) | |
| if !x.IsValid() { // Make it a typed nil. | |
| x = reflect.Zero(typ) | |
| } else { | |
| x = x.Convert(typ) | |
| } | |
| value[i] = x | |
| } | |
| return value | |
| } | |
| func TestCompare(t *testing.T) { | |
| for _, test := range compareTests { | |
| for i, v0 := range test { | |
| for j, v1 := range test { | |
| c := fmtsort.Compare(v0, v1) | |
| var expect int | |
| switch { | |
| case i == j: | |
| expect = 0 | |
| // NaNs are tricky. | |
| if typ := v0.Type(); (typ.Kind() == reflect.Float32 || typ.Kind() == reflect.Float64) && math.IsNaN(v0.Float()) { | |
| expect = -1 | |
| } | |
| case i < j: | |
| expect = -1 | |
| case i > j: | |
| expect = 1 | |
| } | |
| if c != expect { | |
| t.Errorf("%s: compare(%v,%v)=%d; expect %d", v0.Type(), v0, v1, c, expect) | |
| } | |
| } | |
| } | |
| } | |
| } | |
| type sortTest struct { | |
| data any // Always a map. | |
| print string // Printed result using our custom printer. | |
| } | |
| var sortTests = []sortTest{ | |
| { | |
| map[int]string{7: "bar", -3: "foo"}, | |
| "-3:foo 7:bar", | |
| }, | |
| { | |
| map[uint8]string{7: "bar", 3: "foo"}, | |
| "3:foo 7:bar", | |
| }, | |
| { | |
| map[string]string{"7": "bar", "3": "foo"}, | |
| "3:foo 7:bar", | |
| }, | |
| { | |
| map[float64]string{7: "bar", -3: "foo", math.NaN(): "nan", math.Inf(0): "inf"}, | |
| "NaN:nan -3:foo 7:bar +Inf:inf", | |
| }, | |
| { | |
| map[complex128]string{7 + 2i: "bar2", 7 + 1i: "bar", -3: "foo", complex(math.NaN(), 0i): "nan", complex(math.Inf(0), 0i): "inf"}, | |
| "(NaN+0i):nan (-3+0i):foo (7+1i):bar (7+2i):bar2 (+Inf+0i):inf", | |
| }, | |
| { | |
| map[bool]string{true: "true", false: "false"}, | |
| "false:false true:true", | |
| }, | |
| { | |
| chanMap(), | |
| "CHAN0:0 CHAN1:1 CHAN2:2", | |
| }, | |
| { | |
| pointerMap(), | |
| "PTR0:0 PTR1:1 PTR2:2", | |
| }, | |
| { | |
| unsafePointerMap(), | |
| "UNSAFEPTR0:0 UNSAFEPTR1:1 UNSAFEPTR2:2", | |
| }, | |
| { | |
| map[toy]string{{7, 2}: "72", {7, 1}: "71", {3, 4}: "34"}, | |
| "{3 4}:34 {7 1}:71 {7 2}:72", | |
| }, | |
| { | |
| map[[2]int]string{{7, 2}: "72", {7, 1}: "71", {3, 4}: "34"}, | |
| "[3 4]:34 [7 1]:71 [7 2]:72", | |
| }, | |
| } | |
| func sprint(data any) string { | |
| om := fmtsort.Sort(reflect.ValueOf(data)) | |
| if om == nil { | |
| return "nil" | |
| } | |
| b := new(strings.Builder) | |
| for i, key := range om.Key { | |
| if i > 0 { | |
| b.WriteRune(' ') | |
| } | |
| b.WriteString(sprintKey(key)) | |
| b.WriteRune(':') | |
| b.WriteString(fmt.Sprint(om.Value[i])) | |
| } | |
| return b.String() | |
| } | |
| // sprintKey formats a reflect.Value but gives reproducible values for some | |
| // problematic types such as pointers. Note that it only does special handling | |
| // for the troublesome types used in the test cases; it is not a general | |
| // printer. | |
| func sprintKey(key reflect.Value) string { | |
| switch str := key.Type().String(); str { | |
| case "*int": | |
| ptr := key.Interface().(*int) | |
| for i := range ints { | |
| if ptr == &ints[i] { | |
| return fmt.Sprintf("PTR%d", i) | |
| } | |
| } | |
| return "PTR???" | |
| case "unsafe.Pointer": | |
| ptr := key.Interface().(unsafe.Pointer) | |
| for i := range ints { | |
| if ptr == unsafe.Pointer(&ints[i]) { | |
| return fmt.Sprintf("UNSAFEPTR%d", i) | |
| } | |
| } | |
| return "UNSAFEPTR???" | |
| case "chan int": | |
| c := key.Interface().(chan int) | |
| for i := range chans { | |
| if c == chans[i] { | |
| return fmt.Sprintf("CHAN%d", i) | |
| } | |
| } | |
| return "CHAN???" | |
| default: | |
| return fmt.Sprint(key) | |
| } | |
| } | |
| var ( | |
| ints [3]int | |
| chans = makeChans() | |
| ) | |
| func makeChans() []chan int { | |
| cs := []chan int{make(chan int), make(chan int), make(chan int)} | |
| // Order channels by address. See issue #49431. | |
| // TODO: pin these pointers once pinning is available (#46787). | |
| sort.Slice(cs, func(i, j int) bool { | |
| return uintptr(reflect.ValueOf(cs[i]).UnsafePointer()) < uintptr(reflect.ValueOf(cs[j]).UnsafePointer()) | |
| }) | |
| return cs | |
| } | |
| func pointerMap() map[*int]string { | |
| m := make(map[*int]string) | |
| for i := 2; i >= 0; i-- { | |
| m[&ints[i]] = fmt.Sprint(i) | |
| } | |
| return m | |
| } | |
| func unsafePointerMap() map[unsafe.Pointer]string { | |
| m := make(map[unsafe.Pointer]string) | |
| for i := 2; i >= 0; i-- { | |
| m[unsafe.Pointer(&ints[i])] = fmt.Sprint(i) | |
| } | |
| return m | |
| } | |
| func chanMap() map[chan int]string { | |
| m := make(map[chan int]string) | |
| for i := 2; i >= 0; i-- { | |
| m[chans[i]] = fmt.Sprint(i) | |
| } | |
| return m | |
| } | |
| type toy struct { | |
| A int // Exported. | |
| b int // Unexported. | |
| } | |
| func TestOrder(t *testing.T) { | |
| for _, test := range sortTests { | |
| got := sprint(test.data) | |
| if got != test.print { | |
| t.Errorf("%s: got %q, want %q", reflect.TypeOf(test.data), got, test.print) | |
| } | |
| } | |
| } | |
| func TestInterface(t *testing.T) { | |
| // A map containing multiple concrete types should be sorted by type, | |
| // then value. However, the relative ordering of types is unspecified, | |
| // so test this by checking the presence of sorted subgroups. | |
| m := map[any]string{ | |
| [2]int{1, 0}: "", | |
| [2]int{0, 1}: "", | |
| true: "", | |
| false: "", | |
| 3.1: "", | |
| 2.1: "", | |
| 1.1: "", | |
| math.NaN(): "", | |
| 3: "", | |
| 2: "", | |
| 1: "", | |
| "c": "", | |
| "b": "", | |
| "a": "", | |
| struct{ x, y int }{1, 0}: "", | |
| struct{ x, y int }{0, 1}: "", | |
| } | |
| got := sprint(m) | |
| typeGroups := []string{ | |
| "NaN: 1.1: 2.1: 3.1:", // float64 | |
| "false: true:", // bool | |
| "1: 2: 3:", // int | |
| "a: b: c:", // string | |
| "[0 1]: [1 0]:", // [2]int | |
| "{0 1}: {1 0}:", // struct{ x int; y int } | |
| } | |
| for _, g := range typeGroups { | |
| if !strings.Contains(got, g) { | |
| t.Errorf("sorted map should contain %q", g) | |
| } | |
| } | |
| } |