mirror of
https://github.com/golang/go
synced 2024-11-08 06:36:14 -07:00
9d40fadb1c
Previously, the result of sorting a map[interface{}] containing
multiple concrete types was non-deterministic. To ensure consistent
results, sort first by type name, then by concrete value.
Fixes #30398
Change-Id: I10fd4b6a74eefbc87136853af6b2e689bc76ae9d
GitHub-Last-Rev: 1b07f0c275
GitHub-Pull-Request: golang/go#30406
Reviewed-on: https://go-review.googlesource.com/c/163745
Reviewed-by: Rob Pike <r@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
Run-TryBot: Keith Randall <khr@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
247 lines
6.2 KiB
Go
247 lines
6.2 KiB
Go
// 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"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
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(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(interface{}(interface{}(0))), iFace, 1, 2, 3),
|
|
}
|
|
|
|
var iFace interface{}
|
|
|
|
func ct(typ reflect.Type, args ...interface{}) []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 interface{} // 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",
|
|
},
|
|
{
|
|
map[toy]string{toy{7, 2}: "72", toy{7, 1}: "71", toy{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 interface{}) 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 "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 = [3]chan int{make(chan int), make(chan int), make(chan int)}
|
|
)
|
|
|
|
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 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[interface{}]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)
|
|
}
|
|
}
|
|
}
|