From afa9bdf3337404911b11e0b78d77ee5c92476b77 Mon Sep 17 00:00:00 2001 From: James Harris Date: Sat, 19 Jan 2019 14:26:45 +1000 Subject: [PATCH] Add support for rendering unexported struct fields. --- context.go | 21 +++++---- shallow.go | 49 ++++++++++++++++----- shallow_test.go | 22 +++++----- struct_test.go | 114 ++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 172 insertions(+), 34 deletions(-) diff --git a/context.go b/context.go index cd6eb15..4d30b2d 100644 --- a/context.go +++ b/context.go @@ -32,14 +32,19 @@ func (c *context) visit( defer iago.Recover(&err) switch rv.Kind() { - case reflect.String, reflect.Bool: - // note that type names are never included for these types, as they can never - // be ambiguous - c.writef(w, "%#v", rv.Interface()) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, - reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, - reflect.Float32, reflect.Float64: - c.write(w, formatNumber(rv, knownType)) + // type name is not rendered for these types, as the literals are unambiguous. + case reflect.String: + c.writef(w, "%#v", rv.String()) + case reflect.Bool: + c.writef(w, "%#v", rv.Bool()) + + // the rest of the types can be amgiuous unless type information is included. + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + c.write(w, formatInt(rv, knownType)) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + c.write(w, formatUint(rv, knownType)) + case reflect.Float32, reflect.Float64: + c.write(w, formatFloat(rv, knownType)) case reflect.Complex64, reflect.Complex128: c.write(w, formatComplex(rv, knownType)) case reflect.Uintptr: diff --git a/shallow.go b/shallow.go index 0bf592c..53c9327 100644 --- a/shallow.go +++ b/shallow.go @@ -5,9 +5,9 @@ import ( "reflect" ) -// formatNumber formats integers and floating point numbers. -func formatNumber(rv reflect.Value, knownType bool) string { - s := fmt.Sprintf("%v", rv.Interface()) +// formatInt formats signed integers. +func formatInt(rv reflect.Value, knownType bool) string { + s := fmt.Sprintf("%v", rv.Int()) if knownType { return s @@ -20,23 +20,50 @@ func formatNumber(rv reflect.Value, knownType bool) string { ) } -// formatComplex formats complex numbers. -func formatComplex(rv reflect.Value, knownType bool) string { +// formatUint formats unsigned integers. +func formatUint(rv reflect.Value, knownType bool) string { + s := fmt.Sprintf("%v", rv.Uint()) + if knownType { - s := fmt.Sprintf("%v", rv.Interface()) - return s[1 : len(s)-1] // trim the opening and closing parenthesis + return s } return fmt.Sprintf( - "%s%v", + "%s(%s)", formatTypeName(rv.Type()), - rv.Interface(), + s, ) } +// formatFloat formats floating point numbers. +func formatFloat(rv reflect.Value, knownType bool) string { + s := fmt.Sprintf("%v", rv.Float()) + + if knownType { + return s + } + + return fmt.Sprintf( + "%s(%s)", + formatTypeName(rv.Type()), + s, + ) +} + +// formatComplex formats complex numbers. +func formatComplex(rv reflect.Value, knownType bool) string { + s := fmt.Sprintf("%v", rv.Complex()) + + if knownType { + return s[1 : len(s)-1] // trim the opening and closing parenthesis + } + + return formatTypeName(rv.Type()) + s +} + // formatUintptr formats uintptr values. func formatUintptr(rv reflect.Value, knownType bool) string { - s := formatPointerHex(rv.Interface(), false) + s := formatPointerHex(rv.Uint(), false) if knownType { return s @@ -51,7 +78,7 @@ func formatUintptr(rv reflect.Value, knownType bool) string { // formatUnsafePointer formats unsafe.Pointer values. func formatUnsafePointer(rv reflect.Value, knownType bool) string { - s := formatPointerHex(rv.Interface(), true) + s := formatPointerHex(rv.Pointer(), true) if knownType { return s diff --git a/shallow_test.go b/shallow_test.go index d70d33c..931509f 100644 --- a/shallow_test.go +++ b/shallow_test.go @@ -49,7 +49,7 @@ var shallowValues = shallow{ Float32: 1.23, Float64: 1.23, Uintptr: 0xABCD, - UnsafePointer: unsafe.Pointer(&unsafePointerTarget), + UnsafePointer: unsafe.Pointer(&pointerTarget), Channel: make(chan string), Func: func(int, string) (bool, error) { panic("not implemented") @@ -57,10 +57,10 @@ var shallowValues = shallow{ } var ( - unsafePointerTarget int - unsafePointerTargetHex = fmt.Sprintf("0x%x", &unsafePointerTarget) - channelHex = fmt.Sprintf("0x%x", shallowValues.Channel) - funcHex = fmt.Sprintf("0x%x", reflect.ValueOf(shallowValues.Func).Pointer()) + pointerTarget int = 123 + pointerTargetHex = fmt.Sprintf("0x%x", &pointerTarget) + channelHex = fmt.Sprintf("0x%x", shallowValues.Channel) + funcHex = fmt.Sprintf("0x%x", reflect.ValueOf(shallowValues.Func).Pointer()) ) // This test verifies the formatting of "shallow" values. @@ -84,10 +84,10 @@ func TestPrinter_ShallowValues(t *testing.T) { test(t, "uint64", shallowValues.Uint64, "uint64(100)") test(t, "complex64", shallowValues.Complex64, "complex64(100+5i)") test(t, "complex128", shallowValues.Complex128, "complex128(100+5i)") - test(t, "float32", shallowValues.Float32, "float32(1.23)") + test(t, "float32", shallowValues.Float32, "float32(1.2300000190734863)") test(t, "float64", shallowValues.Float64, "float64(1.23)") test(t, "uintptr", shallowValues.Uintptr, "uintptr(0xabcd)") - test(t, "unsafe.Pointer", shallowValues.UnsafePointer, "unsafe.Pointer("+unsafePointerTargetHex+")") + test(t, "unsafe.Pointer", shallowValues.UnsafePointer, "unsafe.Pointer("+pointerTargetHex+")") test(t, "channel", shallowValues.Channel, "(chan string)("+channelHex+")") test(t, "func", shallowValues.Func, "(func(int, string) (bool, error))("+funcHex+")") } @@ -116,10 +116,10 @@ func TestPrinter_ShallowValuesInNamedStruct(t *testing.T) { " Uint64: 100", " Complex64: 100+5i", " Complex128: 100+5i", - " Float32: 1.23", + " Float32: 1.2300000190734863", " Float64: 1.23", " Uintptr: 0xabcd", - " UnsafePointer: "+unsafePointerTargetHex, + " UnsafePointer: "+pointerTargetHex, " Channel: "+channelHex, " Func: "+funcHex, "}", @@ -175,10 +175,10 @@ func TestPrinter_ShallowValuesInAnonymousStruct(t *testing.T) { " Uint64: uint64(100)", " Complex64: complex64(100+5i)", " Complex128: complex128(100+5i)", - " Float32: float32(1.23)", + " Float32: float32(1.2300000190734863)", " Float64: float64(1.23)", " Uintptr: uintptr(0xabcd)", - " UnsafePointer: unsafe.Pointer("+unsafePointerTargetHex+")", + " UnsafePointer: unsafe.Pointer("+pointerTargetHex+")", " Channel: (chan string)("+channelHex+")", " Func: (func(int, string) (bool, error))("+funcHex+")", "}", diff --git a/struct_test.go b/struct_test.go index 95b0173..c0e3d21 100644 --- a/struct_test.go +++ b/struct_test.go @@ -1,6 +1,9 @@ package dapper_test -import "testing" +import ( + "testing" + "unsafe" +) type empty struct{} @@ -9,12 +12,41 @@ type named struct { Iface interface{} } -type namedWithAnonymousField struct { +type anonymous struct { Anon struct { Int int } } +type unexported struct { + vString string + vBool bool + vInt int + vInt8 int8 + vInt16 int16 + vInt32 int32 + vInt64 int64 + vUint uint + vUint8 uint8 + vUint16 uint16 + vUint32 uint32 + vUint64 uint64 + vComplex64 complex64 + vComplex128 complex128 + vFloat32 float32 + vFloat64 float64 + vUintptr uintptr + vUnsafePointer unsafe.Pointer + vChannel chan string + vFunc func(int, string) (bool, error) + vIface interface{} + vStruct struct{} + vPtr *int + vSlice []int + vArray [1]int + vMap map[int]int +} + // This test verifies that empty structs are rendered on a single line. func TestPrinter_EmptyStruct(t *testing.T) { test(t, "empty struct", empty{}, "dapper_test.empty{}") @@ -56,15 +88,89 @@ func TestPrinter_StructFieldTypes(t *testing.T) { test( t, "types are only included for interface fields of anonymous struct inside a named struct", - namedWithAnonymousField{ + anonymous{ Anon: struct{ Int int }{ Int: 100, }, }, - "dapper_test.namedWithAnonymousField{", + "dapper_test.anonymous{", " Anon: {", " Int: 100", " }", "}", ) } + +// This test verifies that all types can be formatted when obtained from +// unexported fields. +// +// This is important because reflect.Value().Interface() panics if called on +// such a value. +func TestPrinter_StructUnexportedFields(t *testing.T) { + test( + t, + "unexported fields can be formatted", + unexported{ + vString: shallowValues.String, + vBool: shallowValues.Bool, + vInt: shallowValues.Int, + vInt8: shallowValues.Int8, + vInt16: shallowValues.Int16, + vInt32: shallowValues.Int32, + vInt64: shallowValues.Int64, + vUint: shallowValues.Uint, + vUint8: shallowValues.Uint8, + vUint16: shallowValues.Uint16, + vUint32: shallowValues.Uint32, + vUint64: shallowValues.Uint64, + vComplex64: shallowValues.Complex64, + vComplex128: shallowValues.Complex128, + vFloat32: shallowValues.Float32, + vFloat64: shallowValues.Float64, + vUintptr: shallowValues.Uintptr, + vUnsafePointer: shallowValues.UnsafePointer, + vChannel: shallowValues.Channel, + vFunc: shallowValues.Func, + vIface: 100, + vStruct: struct{}{}, + vPtr: &pointerTarget, + vSlice: []int{100}, + vArray: [1]int{200}, + vMap: map[int]int{300: 400}, + }, + "dapper_test.unexported{", + ` vString: "foo\nbar"`, + " vBool: true", + " vInt: -100", + " vInt8: -100", + " vInt16: -100", + " vInt32: -100", + " vInt64: -100", + " vUint: 100", + " vUint8: 100", + " vUint16: 100", + " vUint32: 100", + " vUint64: 100", + " vComplex64: 100+5i", + " vComplex128: 100+5i", + " vFloat32: 1.2300000190734863", + " vFloat64: 1.23", + " vUintptr: 0xabcd", + " vUnsafePointer: "+pointerTargetHex, + " vChannel: "+channelHex, + " vFunc: "+funcHex, + " vIface: int(100)", + " vStruct: {}", + " vPtr: 123", + " vSlice: {", + " 100", + " }", + " vArray: {", + " 200", + " }", + " vMap: {", + " 300: 400", + " }", + "}", + ) +}