Skip to content

Commit

Permalink
config: add SortKeys option to sort native map keys before display
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Patrick Mezard committed Oct 27, 2013
1 parent 1fe9f5c commit 1fdf49f
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 0 deletions.
6 changes: 6 additions & 0 deletions spew/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ type ConfigState struct {
// NOTE: This flag does not have any effect if method invocation is disabled
// via the DisableMethods or DisablePointerMethods options.
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.
Expand Down
56 changes: 56 additions & 0 deletions spew/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"os"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
)
Expand Down Expand Up @@ -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
// 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
Expand Down Expand Up @@ -349,6 +402,9 @@ func (d *dumpState) dump(v reflect.Value) {
} else {
numEntries := v.Len()
keys := v.MapKeys()
if d.cs.SortKeys {
SortValues(keys)
}
for i, key := range keys {
d.dump(d.unpackValue(key))
d.w.Write(colonSpaceBytes)
Expand Down
42 changes: 42 additions & 0 deletions spew/dump_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import (
"bytes"
"fmt"
"github.com/davecgh/go-spew/spew"
"reflect"
"testing"
"unsafe"
)
Expand Down Expand Up @@ -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)
}
}
3 changes: 3 additions & 0 deletions spew/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,9 @@ func (f *formatState) format(v reflect.Value) {
f.fs.Write(maxShortBytes)
} else {
keys := v.MapKeys()
if f.cs.SortKeys {
SortValues(keys)
}
for i, key := range keys {
if i > 0 {
f.fs.Write(spaceBytes)
Expand Down
9 changes: 9 additions & 0 deletions spew/format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

0 comments on commit 1fdf49f

Please sign in to comment.