config: add SortKeys option to sort native map keys before display
If ConfigState.SortKeys is true, then dump and format will sort map keys before displaying them. Only native types (bool, ints, uint, uintptr, string) are supported, other slices are left unchanged. The motivation is to have more diffable output, mostly for test purpose.
This commit is contained in:
parent
1fe9f5ca4b
commit
1fdf49f1b9
|
@ -73,6 +73,12 @@ type ConfigState struct {
|
||||||
// NOTE: This flag does not have any effect if method invocation is disabled
|
// NOTE: This flag does not have any effect if method invocation is disabled
|
||||||
// via the DisableMethods or DisablePointerMethods options.
|
// via the DisableMethods or DisablePointerMethods options.
|
||||||
ContinueOnMethod bool
|
ContinueOnMethod bool
|
||||||
|
|
||||||
|
// SortKeys specifies map keys should be sorted before being printed. Use
|
||||||
|
// this to have a more deterministic, diffable output. Note that only
|
||||||
|
// native types (bool, int, uint, floats, uintptr and string) are supported,
|
||||||
|
// other key sequences will displayed in the original order.
|
||||||
|
SortKeys bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config is the active configuration of the top-level functions.
|
// Config is the active configuration of the top-level functions.
|
||||||
|
|
56
spew/dump.go
56
spew/dump.go
|
@ -24,6 +24,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -241,6 +242,58 @@ func (d *dumpState) dumpSlice(v reflect.Value) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type valuesSorter struct {
|
||||||
|
values []reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *valuesSorter) Len() int {
|
||||||
|
return len(s.values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *valuesSorter) Swap(i, j int) {
|
||||||
|
s.values[i], s.values[j] = s.values[j], s.values[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *valuesSorter) Less(i, j int) bool {
|
||||||
|
switch s.values[i].Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return !s.values[i].Bool() && s.values[j].Bool()
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return s.values[i].Float() < s.values[j].Float()
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
return s.values[i].Int() < s.values[j].Int()
|
||||||
|
case reflect.String:
|
||||||
|
return s.values[i].String() < s.values[j].String()
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
return s.values[i].Uint() < s.values[j].Uint()
|
||||||
|
case reflect.Uintptr:
|
||||||
|
return s.values[i].UnsafeAddr() < s.values[j].UnsafeAddr()
|
||||||
|
}
|
||||||
|
panic("notimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic sort function for native types: int, uint, bool, string and uintptr.
|
||||||
|
// Other inputs are left unchanged.
|
||||||
|
func SortValues(values []reflect.Value) {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch values[0].Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
sort.Sort(&valuesSorter{values})
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
sort.Sort(&valuesSorter{values})
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
sort.Sort(&valuesSorter{values})
|
||||||
|
case reflect.String:
|
||||||
|
sort.Sort(&valuesSorter{values})
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
sort.Sort(&valuesSorter{values})
|
||||||
|
case reflect.Uintptr:
|
||||||
|
sort.Sort(&valuesSorter{values})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// dump is the main workhorse for dumping a value. It uses the passed reflect
|
// dump is the main workhorse for dumping a value. It uses the passed reflect
|
||||||
// value to figure out what kind of object we are dealing with and formats it
|
// value to figure out what kind of object we are dealing with and formats it
|
||||||
// appropriately. It is a recursive function, however circular data structures
|
// appropriately. It is a recursive function, however circular data structures
|
||||||
|
@ -349,6 +402,9 @@ func (d *dumpState) dump(v reflect.Value) {
|
||||||
} else {
|
} else {
|
||||||
numEntries := v.Len()
|
numEntries := v.Len()
|
||||||
keys := v.MapKeys()
|
keys := v.MapKeys()
|
||||||
|
if d.cs.SortKeys {
|
||||||
|
SortValues(keys)
|
||||||
|
}
|
||||||
for i, key := range keys {
|
for i, key := range keys {
|
||||||
d.dump(d.unpackValue(key))
|
d.dump(d.unpackValue(key))
|
||||||
d.w.Write(colonSpaceBytes)
|
d.w.Write(colonSpaceBytes)
|
||||||
|
|
|
@ -65,6 +65,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
@ -896,3 +897,44 @@ func TestDump(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSortValues(t *testing.T) {
|
||||||
|
v := reflect.ValueOf
|
||||||
|
|
||||||
|
a := v("a")
|
||||||
|
b := v("b")
|
||||||
|
c := v("c")
|
||||||
|
tests := []struct {
|
||||||
|
input []reflect.Value
|
||||||
|
expected []reflect.Value
|
||||||
|
}{
|
||||||
|
{[]reflect.Value{v(2), v(1), v(3)},
|
||||||
|
[]reflect.Value{v(1), v(2), v(3)}},
|
||||||
|
{[]reflect.Value{v(2.), v(1.), v(3.)},
|
||||||
|
[]reflect.Value{v(1.), v(2.), v(3.)}},
|
||||||
|
{[]reflect.Value{v(false), v(true), v(false)},
|
||||||
|
[]reflect.Value{v(false), v(false), v(true)}},
|
||||||
|
{[]reflect.Value{b, a, c},
|
||||||
|
[]reflect.Value{a, b, c}},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
spew.SortValues(test.input)
|
||||||
|
if !reflect.DeepEqual(test.input, test.expected) {
|
||||||
|
t.Errorf("Sort mismatch:\n %v != %v", test.input, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDumpSortedKeys(t *testing.T) {
|
||||||
|
cfg := spew.ConfigState{SortKeys: true}
|
||||||
|
s := cfg.Sdump(map[int]string{1: "1", 3: "3", 2: "2"})
|
||||||
|
expected := `(map[int]string) {
|
||||||
|
(int) 1: (string) "1",
|
||||||
|
(int) 2: (string) "2",
|
||||||
|
(int) 3: (string) "3"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
if s != expected {
|
||||||
|
t.Errorf("Sorted keys mismatch:\n %v %v", s, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -302,6 +302,9 @@ func (f *formatState) format(v reflect.Value) {
|
||||||
f.fs.Write(maxShortBytes)
|
f.fs.Write(maxShortBytes)
|
||||||
} else {
|
} else {
|
||||||
keys := v.MapKeys()
|
keys := v.MapKeys()
|
||||||
|
if f.cs.SortKeys {
|
||||||
|
SortValues(keys)
|
||||||
|
}
|
||||||
for i, key := range keys {
|
for i, key := range keys {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
f.fs.Write(spaceBytes)
|
f.fs.Write(spaceBytes)
|
||||||
|
|
|
@ -1472,3 +1472,12 @@ func TestFormatter(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPrintSortedKeys(t *testing.T) {
|
||||||
|
cfg := spew.ConfigState{SortKeys: true}
|
||||||
|
s := cfg.Sprint(map[int]string{1: "1", 3: "3", 2: "2"})
|
||||||
|
expected := "map[1:1 2:2 3:3]"
|
||||||
|
if s != expected {
|
||||||
|
t.Errorf("Sorted keys mismatch:\n %v %v", s, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue