Skip to content

Commit

Permalink
vastly improved matcher output #3
Browse files Browse the repository at this point in the history
  • Loading branch information
Onsi Fakhouri committed Mar 6, 2014
1 parent 0ae2dac commit 3da99a5
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 43 deletions.
99 changes: 58 additions & 41 deletions format/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

var Indent = " "
var longFormThreshold = 20
var maxIndent = uint(20)

func Message(actual interface{}, message string, expected ...interface{}) string {
if len(expected) == 0 {
Expand All @@ -19,7 +20,8 @@ func Message(actual interface{}, message string, expected ...interface{}) string

func Object(object interface{}, indentation uint) string {
indent := strings.Repeat(Indent, int(indentation))
return fmt.Sprintf("%s<%s>: %s", indent, formatType(object), formatValue(object, indentation))
value := reflect.ValueOf(object)
return fmt.Sprintf("%s<%s>: %s", indent, formatType(object), formatValue(value, indentation))
}

func IndentString(s string, indentation uint) string {
Expand Down Expand Up @@ -58,42 +60,53 @@ func formatType(object interface{}) string {
}
}

func formatValue(object interface{}, indentation uint) string {
if isNil(object) {
func formatValue(value reflect.Value, indentation uint) string {
if indentation > maxIndent {
return "Too deep for me, man..."
}
if isNilValue(value) {
return "nil"
}
t := reflect.TypeOf(object)
switch t.Kind() {
switch value.Kind() {
case reflect.Bool:
return fmt.Sprintf("%v", object)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return fmt.Sprintf("%v", object)
return fmt.Sprintf("%v", value.Bool())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return fmt.Sprintf("%v", value.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return fmt.Sprintf("%v", value.Uint())
case reflect.Uintptr:
return fmt.Sprintf("%#v", object)
case reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
return fmt.Sprintf("%v", object)
return fmt.Sprintf("0x%x", value.Uint())
case reflect.Float32, reflect.Float64:
return fmt.Sprintf("%v", value.Float())
case reflect.Complex64, reflect.Complex128:
return fmt.Sprintf("%v", value.Complex())
case reflect.Chan:
return fmt.Sprintf("%v", object)
return fmt.Sprintf("0x%x", value.Pointer())
case reflect.Func:
return fmt.Sprintf("%v", object)
return fmt.Sprintf("0x%x", value.Pointer())
case reflect.Ptr:
v := reflect.ValueOf(object)
return formatValue(v.Elem().Interface(), indentation)
return formatValue(value.Elem(), indentation)
case reflect.Slice:
if t.Elem().Kind() == reflect.Uint8 {
return formatString(object, indentation)
if value.Type().Elem().Kind() == reflect.Uint8 {
return formatString(value.Bytes(), indentation)
}
return formatSlice(object, indentation)
return formatSlice(value, indentation)
case reflect.String:
return formatString(object, indentation)
return formatString(value.String(), indentation)
case reflect.Array:
return formatSlice(object, indentation)
return formatSlice(value, indentation)
case reflect.Map:
return formatMap(object, indentation)
return formatMap(value, indentation)
case reflect.Struct:
return formatStruct(object, indentation)
return formatStruct(value, indentation)
case reflect.Interface:
return formatValue(value.Elem(), indentation)
default:
return fmt.Sprintf("%#v", object)
if value.CanInterface() {
return fmt.Sprintf("%#v", value.Interface())
} else {
return fmt.Sprintf("%#v", value)
}
}
}

Expand All @@ -119,14 +132,12 @@ func formatString(object interface{}, indentation uint) string {
}
}

func formatSlice(object interface{}, indentation uint) string {
v := reflect.ValueOf(object)

func formatSlice(v reflect.Value, indentation uint) string {
l := v.Len()
result := make([]string, l)
longest := 0
for i := 0; i < l; i++ {
result[i] = formatValue(v.Index(i).Interface(), indentation+1)
result[i] = formatValue(v.Index(i), indentation+1)
if len(result[i]) > longest {
longest = len(result[i])
}
Expand All @@ -140,16 +151,14 @@ func formatSlice(object interface{}, indentation uint) string {
}
}

func formatMap(object interface{}, indentation uint) string {
v := reflect.ValueOf(object)

func formatMap(v reflect.Value, indentation uint) string {
l := v.Len()
result := make([]string, l)

longest := 0
for i, key := range v.MapKeys() {
value := v.MapIndex(key)
result[i] = fmt.Sprintf("%s: %s", formatValue(key.Interface(), 0), formatValue(value.Interface(), indentation+1))
result[i] = fmt.Sprintf("%s: %s", formatValue(key, 0), formatValue(value, indentation+1))
if len(result[i]) > longest {
longest = len(result[i])
}
Expand All @@ -163,22 +172,19 @@ func formatMap(object interface{}, indentation uint) string {
}
}

func formatStruct(object interface{}, indentation uint) string {
v := reflect.ValueOf(object)
t := reflect.TypeOf(object)
func formatStruct(v reflect.Value, indentation uint) string {
t := v.Type()

l := v.NumField()
result := []string{}
longest := 0
for i := 0; i < l; i++ {
structField := t.Field(i)
if structField.PkgPath == "" { //implies the field is exported
fieldEntry := v.Field(i).Interface()
representation := fmt.Sprintf("%s: %s", structField.Name, formatValue(fieldEntry, indentation+1))
result = append(result, representation)
if len(representation) > longest {
longest = len(representation)
}
fieldEntry := v.Field(i)
representation := fmt.Sprintf("%s: %s", structField.Name, formatValue(fieldEntry, indentation+1))
result = append(result, representation)
if len(representation) > longest {
longest = len(representation)
}
}
if longest > longFormThreshold {
Expand All @@ -189,6 +195,17 @@ func formatStruct(object interface{}, indentation uint) string {
}
}

func isNilValue(a reflect.Value) bool {
switch a.Kind() {
case reflect.Invalid:
return true
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
return a.IsNil()
}

return false
}

func isNil(a interface{}) bool {
if a == nil {
return true
Expand Down
104 changes: 102 additions & 2 deletions format/format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
. "github.com/onsi/gomega/format"
)

//recursive struct

type StringAlias string
type ByteAlias []byte
type IntAlias int
Expand All @@ -29,6 +31,26 @@ type ComplexStruct struct {
DataMaps map[int]ByteAlias
}

type SecretiveStruct struct {
boolValue bool
intValue int
uintValue uint
uintptrValue uintptr
floatValue float32
complexValue complex64
chanValue chan bool
funcValue func()
pointerValue *int
sliceValue []string
byteSliceValue []byte
stringValue string
arrValue [3]int
byteArrValue [3]byte
mapValue map[string]int
structValue AStruct
interfaceValue interface{}
}

var _ = Describe("Format", func() {
match := func(typeRepresentation string, valueRepresentation string, args ...interface{}) OmegaMatcher {
if len(args) > 0 {
Expand Down Expand Up @@ -154,6 +176,13 @@ var _ = Describe("Format", func() {
w := [3]string{"Jed Bartlet", "Toby Ziegler", "CJ Cregg"}
Ω(Object(w, 1)).Should(match("[3]string", `["Jed Bartlet", "Toby Ziegler", "CJ Cregg"]`))
})

Context("with byte arrays", func() {
It("should give the type and format values correctly", func() {
w := [3]byte{17, 28, 19}
Ω(Object(w, 1)).Should(match("[3]uint8", `[17, 28, 19]`))
})
})
})

Describe("formatting slices", func() {
Expand Down Expand Up @@ -209,7 +238,7 @@ var _ = Describe("Format", func() {
secret: 1983,
}

Ω(Object(s, 1)).Should(match("format_test.SimpleStruct", `{Name: "Oswald", Enumeration: 17, Veritas: true, Data: "datum"}`))
Ω(Object(s, 1)).Should(match("format_test.SimpleStruct", `{Name: "Oswald", Enumeration: 17, Veritas: true, Data: "datum", secret: 1983}`))
})

Context("when the struct contains long entries", func() {
Expand All @@ -227,6 +256,7 @@ var _ = Describe("Format", func() {
Enumeration: 2021,
Veritas: true,
Data: "wizard",
secret: 3,
}`))
})
})
Expand Down Expand Up @@ -270,12 +300,13 @@ var _ = Describe("Format", func() {
expected := `{
Strings: ["lots", "of", "short", "strings"],
SimpleThings: [
{Name: "short", Enumeration: 7, Veritas: true, Data: "succinct"},
{Name: "short", Enumeration: 7, Veritas: true, Data: "succinct", secret: 17},
{
Name: "something longer",
Enumeration: 427,
Veritas: true,
Data: "designed to wrap around nicely",
secret: 30,
},
],
DataMaps: {
Expand All @@ -287,4 +318,73 @@ var _ = Describe("Format", func() {
})
})
})

Describe("Handling unexported fields in structs", func() {
It("should handle all the various types correctly", func() {
a := int(5)
s := SecretiveStruct{
boolValue: true,
intValue: 3,
uintValue: 4,
uintptrValue: 5,
floatValue: 6.0,
complexValue: complex(5.0, 3.0),
chanValue: make(chan bool, 2),
funcValue: func() {},
pointerValue: &a,
sliceValue: []string{"string", "slice"},
byteSliceValue: []byte("bytes"),
stringValue: "a string",
arrValue: [3]int{11, 12, 13},
byteArrValue: [3]byte{17, 20, 32},
mapValue: map[string]int{"a key": 20, "b key": 30},
structValue: AStruct{"exported"},
interfaceValue: map[string]int{"a key": 17},
}

expected := fmt.Sprintf(`{
boolValue: true,
intValue: 3,
uintValue: 4,
uintptrValue: 0x5,
floatValue: 6,
complexValue: (5+3i),
chanValue: %p,
funcValue: %p,
pointerValue: 5,
sliceValue: ["string", "slice"],
byteSliceValue: "bytes",
stringValue: "a string",
arrValue: [11, 12, 13],
byteArrValue: [17, 20, 32],
mapValue: {"a key": 20, "b key": 30},
structValue: {Exported: "exported"},
interfaceValue: {"a key": 17},
}`, s.chanValue, s.funcValue)

Ω(Object(s, 1)).Should(match("format_test.SecretiveStruct", expected))
})
})

Describe("Handling interfaces", func() {
It("should unpack the interface", func() {
outerHash := map[string]interface{}{}
innerHash := map[string]int{}

innerHash["inner"] = 3
outerHash["integer"] = 2
outerHash["map"] = innerHash

Ω(Object(outerHash, 1)).Should(match("map[string]interface {} | len:2", `{"integer": 2, "map": {"inner": 3}}`))
})
})

Describe("Handling recursive things", func() {
It("should not go crazy...", func() {
m := map[string]interface{}{}
m["integer"] = 2
m["map"] = m
Ω(Object(m, 1)).Should(ContainSubstring("Too deep for me, man..."))
})
})
})

0 comments on commit 3da99a5

Please sign in to comment.