123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383 |
- /*
- Gomega's format package pretty-prints objects. It explores input objects recursively and generates formatted, indented output with type information.
- */
- package format
- import (
- "fmt"
- "reflect"
- "strconv"
- "strings"
- "time"
- )
- // Use MaxDepth to set the maximum recursion depth when printing deeply nested objects
- var MaxDepth = uint(10)
- /*
- By default, all objects (even those that implement fmt.Stringer and fmt.GoStringer) are recursively inspected to generate output.
- Set UseStringerRepresentation = true to use GoString (for fmt.GoStringers) or String (for fmt.Stringer) instead.
- Note that GoString and String don't always have all the information you need to understand why a test failed!
- */
- var UseStringerRepresentation = false
- /*
- Print the content of context objects. By default it will be suppressed.
- Set PrintContextObjects = true to enable printing of the context internals.
- */
- var PrintContextObjects = false
- // TruncatedDiff choose if we should display a truncated pretty diff or not
- var TruncatedDiff = true
- // Ctx interface defined here to keep backwards compatability with go < 1.7
- // It matches the context.Context interface
- type Ctx interface {
- Deadline() (deadline time.Time, ok bool)
- Done() <-chan struct{}
- Err() error
- Value(key interface{}) interface{}
- }
- var contextType = reflect.TypeOf((*Ctx)(nil)).Elem()
- var timeType = reflect.TypeOf(time.Time{})
- //The default indentation string emitted by the format package
- var Indent = " "
- var longFormThreshold = 20
- /*
- Generates a formatted matcher success/failure message of the form:
- Expected
- <pretty printed actual>
- <message>
- <pretty printed expected>
- If expected is omited, then the message looks like:
- Expected
- <pretty printed actual>
- <message>
- */
- func Message(actual interface{}, message string, expected ...interface{}) string {
- if len(expected) == 0 {
- return fmt.Sprintf("Expected\n%s\n%s", Object(actual, 1), message)
- }
- return fmt.Sprintf("Expected\n%s\n%s\n%s", Object(actual, 1), message, Object(expected[0], 1))
- }
- /*
- Generates a nicely formatted matcher success / failure message
- Much like Message(...), but it attempts to pretty print diffs in strings
- Expected
- <string>: "...aaaaabaaaaa..."
- to equal |
- <string>: "...aaaaazaaaaa..."
- */
- func MessageWithDiff(actual, message, expected string) string {
- if TruncatedDiff && len(actual) >= truncateThreshold && len(expected) >= truncateThreshold {
- diffPoint := findFirstMismatch(actual, expected)
- formattedActual := truncateAndFormat(actual, diffPoint)
- formattedExpected := truncateAndFormat(expected, diffPoint)
- spacesBeforeFormattedMismatch := findFirstMismatch(formattedActual, formattedExpected)
- tabLength := 4
- spaceFromMessageToActual := tabLength + len("<string>: ") - len(message)
- padding := strings.Repeat(" ", spaceFromMessageToActual+spacesBeforeFormattedMismatch) + "|"
- return Message(formattedActual, message+padding, formattedExpected)
- }
- return Message(actual, message, expected)
- }
- func truncateAndFormat(str string, index int) string {
- leftPadding := `...`
- rightPadding := `...`
- start := index - charactersAroundMismatchToInclude
- if start < 0 {
- start = 0
- leftPadding = ""
- }
- // slice index must include the mis-matched character
- lengthOfMismatchedCharacter := 1
- end := index + charactersAroundMismatchToInclude + lengthOfMismatchedCharacter
- if end > len(str) {
- end = len(str)
- rightPadding = ""
- }
- return fmt.Sprintf("\"%s\"", leftPadding+str[start:end]+rightPadding)
- }
- func findFirstMismatch(a, b string) int {
- aSlice := strings.Split(a, "")
- bSlice := strings.Split(b, "")
- for index, str := range aSlice {
- if index > len(bSlice)-1 {
- return index
- }
- if str != bSlice[index] {
- return index
- }
- }
- if len(b) > len(a) {
- return len(a) + 1
- }
- return 0
- }
- const (
- truncateThreshold = 50
- charactersAroundMismatchToInclude = 5
- )
- /*
- Pretty prints the passed in object at the passed in indentation level.
- Object recurses into deeply nested objects emitting pretty-printed representations of their components.
- Modify format.MaxDepth to control how deep the recursion is allowed to go
- Set format.UseStringerRepresentation to true to return object.GoString() or object.String() when available instead of
- recursing into the object.
- Set PrintContextObjects to true to print the content of objects implementing context.Context
- */
- func Object(object interface{}, indentation uint) string {
- indent := strings.Repeat(Indent, int(indentation))
- value := reflect.ValueOf(object)
- return fmt.Sprintf("%s<%s>: %s", indent, formatType(object), formatValue(value, indentation))
- }
- /*
- IndentString takes a string and indents each line by the specified amount.
- */
- func IndentString(s string, indentation uint) string {
- components := strings.Split(s, "\n")
- result := ""
- indent := strings.Repeat(Indent, int(indentation))
- for i, component := range components {
- result += indent + component
- if i < len(components)-1 {
- result += "\n"
- }
- }
- return result
- }
- func formatType(object interface{}) string {
- t := reflect.TypeOf(object)
- if t == nil {
- return "nil"
- }
- switch t.Kind() {
- case reflect.Chan:
- v := reflect.ValueOf(object)
- return fmt.Sprintf("%T | len:%d, cap:%d", object, v.Len(), v.Cap())
- case reflect.Ptr:
- return fmt.Sprintf("%T | %p", object, object)
- case reflect.Slice:
- v := reflect.ValueOf(object)
- return fmt.Sprintf("%T | len:%d, cap:%d", object, v.Len(), v.Cap())
- case reflect.Map:
- v := reflect.ValueOf(object)
- return fmt.Sprintf("%T | len:%d", object, v.Len())
- default:
- return fmt.Sprintf("%T", object)
- }
- }
- func formatValue(value reflect.Value, indentation uint) string {
- if indentation > MaxDepth {
- return "..."
- }
- if isNilValue(value) {
- return "nil"
- }
- if UseStringerRepresentation {
- if value.CanInterface() {
- obj := value.Interface()
- switch x := obj.(type) {
- case fmt.GoStringer:
- return x.GoString()
- case fmt.Stringer:
- return x.String()
- }
- }
- }
- if !PrintContextObjects {
- if value.Type().Implements(contextType) && indentation > 1 {
- return "<suppressed context>"
- }
- }
- switch value.Kind() {
- case reflect.Bool:
- 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("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("0x%x", value.Pointer())
- case reflect.Func:
- return fmt.Sprintf("0x%x", value.Pointer())
- case reflect.Ptr:
- return formatValue(value.Elem(), indentation)
- case reflect.Slice:
- return formatSlice(value, indentation)
- case reflect.String:
- return formatString(value.String(), indentation)
- case reflect.Array:
- return formatSlice(value, indentation)
- case reflect.Map:
- return formatMap(value, indentation)
- case reflect.Struct:
- if value.Type() == timeType && value.CanInterface() {
- t, _ := value.Interface().(time.Time)
- return t.Format(time.RFC3339Nano)
- }
- return formatStruct(value, indentation)
- case reflect.Interface:
- return formatValue(value.Elem(), indentation)
- default:
- if value.CanInterface() {
- return fmt.Sprintf("%#v", value.Interface())
- }
- return fmt.Sprintf("%#v", value)
- }
- }
- func formatString(object interface{}, indentation uint) string {
- if indentation == 1 {
- s := fmt.Sprintf("%s", object)
- components := strings.Split(s, "\n")
- result := ""
- for i, component := range components {
- if i == 0 {
- result += component
- } else {
- result += Indent + component
- }
- if i < len(components)-1 {
- result += "\n"
- }
- }
- return fmt.Sprintf("%s", result)
- } else {
- return fmt.Sprintf("%q", object)
- }
- }
- func formatSlice(v reflect.Value, indentation uint) string {
- if v.Kind() == reflect.Slice && v.Type().Elem().Kind() == reflect.Uint8 && isPrintableString(string(v.Bytes())) {
- return formatString(v.Bytes(), indentation)
- }
- l := v.Len()
- result := make([]string, l)
- longest := 0
- for i := 0; i < l; i++ {
- result[i] = formatValue(v.Index(i), indentation+1)
- if len(result[i]) > longest {
- longest = len(result[i])
- }
- }
- if longest > longFormThreshold {
- indenter := strings.Repeat(Indent, int(indentation))
- return fmt.Sprintf("[\n%s%s,\n%s]", indenter+Indent, strings.Join(result, ",\n"+indenter+Indent), indenter)
- }
- return fmt.Sprintf("[%s]", strings.Join(result, ", "))
- }
- 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, indentation+1), formatValue(value, indentation+1))
- if len(result[i]) > longest {
- longest = len(result[i])
- }
- }
- if longest > longFormThreshold {
- indenter := strings.Repeat(Indent, int(indentation))
- return fmt.Sprintf("{\n%s%s,\n%s}", indenter+Indent, strings.Join(result, ",\n"+indenter+Indent), indenter)
- }
- return fmt.Sprintf("{%s}", strings.Join(result, ", "))
- }
- 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)
- 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 {
- indenter := strings.Repeat(Indent, int(indentation))
- return fmt.Sprintf("{\n%s%s,\n%s}", indenter+Indent, strings.Join(result, ",\n"+indenter+Indent), indenter)
- }
- return fmt.Sprintf("{%s}", strings.Join(result, ", "))
- }
- 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
- }
- /*
- Returns true when the string is entirely made of printable runes, false otherwise.
- */
- func isPrintableString(str string) bool {
- for _, runeValue := range str {
- if !strconv.IsPrint(runeValue) {
- return false
- }
- }
- return true
- }
|