Support omitting duplicates and using ordinals instead of pointers when dumping
If this is considered reasonable, I'll be happy to make the corresponding changes to the formatting code as well.
This commit is contained in:
parent
d8f796af33
commit
ba57249c50
|
@ -51,6 +51,8 @@ var (
|
|||
maxShortBytes = []byte("<max>")
|
||||
circularBytes = []byte("<already shown>")
|
||||
circularShortBytes = []byte("<shown>")
|
||||
duplicateBytes = []byte("<already seen>")
|
||||
duplicateShortBytes = []byte("<seen>")
|
||||
invalidAngleBytes = []byte("<invalid>")
|
||||
openBracketBytes = []byte("[")
|
||||
closeBracketBytes = []byte("]")
|
||||
|
@ -214,6 +216,24 @@ func printHexPtr(w io.Writer, p uintptr) {
|
|||
w.Write(buf)
|
||||
}
|
||||
|
||||
func printOrdinal(w io.Writer, ord uint) {
|
||||
buf := make([]byte, 18)
|
||||
base := uint(10)
|
||||
i := len(buf) - 1
|
||||
for ord >= base {
|
||||
buf[i] = hexDigits[ord%base]
|
||||
ord /= base
|
||||
i--
|
||||
}
|
||||
buf[i] = hexDigits[ord]
|
||||
|
||||
i--
|
||||
buf[i] = '#'
|
||||
|
||||
buf = buf[i:]
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
|
||||
// elements to be sorted.
|
||||
type valuesSorter struct {
|
||||
|
|
|
@ -98,6 +98,19 @@ type ConfigState struct {
|
|||
// be spewed to strings and sorted by those strings. This is only
|
||||
// considered if SortKeys is true.
|
||||
SpewKeys bool
|
||||
|
||||
// NoDuplicates specifies that any given pointer should have
|
||||
// its dereference dumped only once. This is similar to
|
||||
// circularity detection, but applies to all pointers across a
|
||||
// given dump action. This can consume much memory.
|
||||
NoDuplicates bool
|
||||
|
||||
// UseOrdinals specifies that pointer values are to be
|
||||
// replaced with monotonically increasing integers. It has no
|
||||
// effect if DisablePointerAddresses is true; else, it
|
||||
// provides some degree of stability across runs versus
|
||||
// printing out raw pointers. This can consume much memory.
|
||||
UseOrdinals bool
|
||||
}
|
||||
|
||||
// Config is the active configuration of the top-level functions.
|
||||
|
|
45
spew/dump.go
45
spew/dump.go
|
@ -52,9 +52,11 @@ type dumpState struct {
|
|||
w io.Writer
|
||||
depth int
|
||||
pointers map[uintptr]int
|
||||
allPointers map[uintptr]uint
|
||||
ignoreNextType bool
|
||||
ignoreNextIndent bool
|
||||
cs *ConfigState
|
||||
nextOrdinal uint
|
||||
}
|
||||
|
||||
// indent performs indentation according to the depth level and cs.Indent
|
||||
|
@ -95,7 +97,9 @@ func (d *dumpState) dumpPtr(v reflect.Value) {
|
|||
// references.
|
||||
nilFound := false
|
||||
cycleFound := false
|
||||
duplicateFound := false
|
||||
indirects := 0
|
||||
ordinal := uint(0)
|
||||
ve := v
|
||||
for ve.Kind() == reflect.Ptr {
|
||||
if ve.IsNil() {
|
||||
|
@ -104,12 +108,39 @@ func (d *dumpState) dumpPtr(v reflect.Value) {
|
|||
}
|
||||
indirects++
|
||||
addr := ve.Pointer()
|
||||
pointerChain = append(pointerChain, addr)
|
||||
|
||||
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
|
||||
cycleFound = true
|
||||
}
|
||||
|
||||
if d.allPointers != nil { // d.cs.NoDuplicates || d.cs.UseOrdinals
|
||||
if ord, ok := d.allPointers[addr]; ok {
|
||||
if d.cs.UseOrdinals {
|
||||
ordinal = ord
|
||||
}
|
||||
if d.cs.NoDuplicates {
|
||||
duplicateFound = true
|
||||
}
|
||||
} else {
|
||||
if d.cs.UseOrdinals {
|
||||
d.nextOrdinal++
|
||||
ordinal = d.nextOrdinal
|
||||
}
|
||||
d.allPointers[addr] = ordinal
|
||||
}
|
||||
}
|
||||
|
||||
if ordinal > 0 {
|
||||
pointerChain = append(pointerChain, uintptr(ordinal))
|
||||
} else {
|
||||
pointerChain = append(pointerChain, addr)
|
||||
}
|
||||
|
||||
if cycleFound || duplicateFound {
|
||||
indirects--
|
||||
break
|
||||
}
|
||||
|
||||
d.pointers[addr] = d.depth
|
||||
|
||||
ve = ve.Elem()
|
||||
|
@ -135,7 +166,11 @@ func (d *dumpState) dumpPtr(v reflect.Value) {
|
|||
if i > 0 {
|
||||
d.w.Write(pointerChainBytes)
|
||||
}
|
||||
printHexPtr(d.w, addr)
|
||||
if d.cs.UseOrdinals {
|
||||
printOrdinal(d.w, uint(addr))
|
||||
} else {
|
||||
printHexPtr(d.w, addr)
|
||||
}
|
||||
}
|
||||
d.w.Write(closeParenBytes)
|
||||
}
|
||||
|
@ -149,6 +184,9 @@ func (d *dumpState) dumpPtr(v reflect.Value) {
|
|||
case cycleFound:
|
||||
d.w.Write(circularBytes)
|
||||
|
||||
case duplicateFound:
|
||||
d.w.Write(duplicateBytes)
|
||||
|
||||
default:
|
||||
d.ignoreNextType = true
|
||||
d.dump(ve)
|
||||
|
@ -462,6 +500,9 @@ func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
|
|||
|
||||
d := dumpState{w: w, cs: cs}
|
||||
d.pointers = make(map[uintptr]int)
|
||||
if cs.NoDuplicates || cs.UseOrdinals {
|
||||
d.allPointers = make(map[uintptr]uint)
|
||||
}
|
||||
d.dump(reflect.ValueOf(arg))
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
|
|
|
@ -1040,3 +1040,105 @@ func TestDumpSortedKeys(t *testing.T) {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
func TestDumpDuplicatePointers(t *testing.T) {
|
||||
cfg := spew.ConfigState{NoDuplicates: true}
|
||||
type info struct {
|
||||
a int
|
||||
b int
|
||||
}
|
||||
type twice struct {
|
||||
info1 *info
|
||||
info2 *info
|
||||
info3 *info
|
||||
}
|
||||
i1 := &info{a: 1, b: 2}
|
||||
ip1 := fmt.Sprintf("%p", i1)
|
||||
i2 := &info{a: 3, b: 4}
|
||||
ip2 := fmt.Sprintf("%p", i2)
|
||||
v := twice{info1: i1, info2: i2, info3: i1}
|
||||
// vt := "spew_test.twice"
|
||||
s := cfg.Sdump(v)
|
||||
expected := `(spew_test.twice) {
|
||||
info1: (*spew_test.info)(` + ip1 + `)({
|
||||
a: (int) 1,
|
||||
b: (int) 2
|
||||
}),
|
||||
info2: (*spew_test.info)(` + ip2 + `)({
|
||||
a: (int) 3,
|
||||
b: (int) 4
|
||||
}),
|
||||
info3: (*spew_test.info)(` + ip1 + `)(<already seen>)
|
||||
}
|
||||
`
|
||||
if s != expected {
|
||||
t.Errorf("Duplicate-pointers mismatch:\n %v %v", s, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDumpOrdinals(t *testing.T) {
|
||||
cfg := spew.ConfigState{UseOrdinals: true}
|
||||
type info struct {
|
||||
a int
|
||||
b int
|
||||
}
|
||||
type twice struct {
|
||||
info1 *info
|
||||
info2 *info
|
||||
info3 *info
|
||||
}
|
||||
i := &info{a: 1, b: 2}
|
||||
v := twice{info1: i, info2: &info{a: 3, b: 4}, info3: i}
|
||||
// vt := "spew_test.twice"
|
||||
s := cfg.Sdump(v)
|
||||
expected := `(spew_test.twice) {
|
||||
info1: (*spew_test.info)(#1)({
|
||||
a: (int) 1,
|
||||
b: (int) 2
|
||||
}),
|
||||
info2: (*spew_test.info)(#2)({
|
||||
a: (int) 3,
|
||||
b: (int) 4
|
||||
}),
|
||||
info3: (*spew_test.info)(#1)({
|
||||
a: (int) 1,
|
||||
b: (int) 2
|
||||
})
|
||||
}
|
||||
`
|
||||
if s != expected {
|
||||
t.Errorf("Ordinals mismatch:\n %v %v", s, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDumpDuplicateOrdinals(t *testing.T) {
|
||||
cfg := spew.ConfigState{NoDuplicates: true, UseOrdinals: true}
|
||||
type info struct {
|
||||
a int
|
||||
b int
|
||||
}
|
||||
type twice struct {
|
||||
info1 *info
|
||||
info2 *info
|
||||
info3 *info
|
||||
}
|
||||
i := &info{a: 1, b: 2}
|
||||
v := twice{info1: i, info2: &info{a: 3, b: 4}, info3: i}
|
||||
// vt := "spew_test.twice"
|
||||
s := cfg.Sdump(v)
|
||||
expected := `(spew_test.twice) {
|
||||
info1: (*spew_test.info)(#1)({
|
||||
a: (int) 1,
|
||||
b: (int) 2
|
||||
}),
|
||||
info2: (*spew_test.info)(#2)({
|
||||
a: (int) 3,
|
||||
b: (int) 4
|
||||
}),
|
||||
info3: (*spew_test.info)(#1)(<already seen>)
|
||||
}
|
||||
`
|
||||
if s != expected {
|
||||
t.Errorf("Duplicate-pointers mismatch:\n %v %v", s, expected)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue