Path: blob/main/vendor/github.com/onsi/gomega/format/format.go
2880 views
/*1Gomega's format package pretty-prints objects. It explores input objects recursively and generates formatted, indented output with type information.2*/34// untested sections: 456package format78import (9"context"10"fmt"11"reflect"12"strconv"13"strings"14"time"15)1617// Use MaxDepth to set the maximum recursion depth when printing deeply nested objects18var MaxDepth = uint(10)1920// MaxLength of the string representation of an object.21// If MaxLength is set to 0, the Object will not be truncated.22var MaxLength = 40002324/*25By default, all objects (even those that implement fmt.Stringer and fmt.GoStringer) are recursively inspected to generate output.2627Set UseStringerRepresentation = true to use GoString (for fmt.GoStringers) or String (for fmt.Stringer) instead.2829Note that GoString and String don't always have all the information you need to understand why a test failed!30*/31var UseStringerRepresentation = false3233/*34Print the content of context objects. By default it will be suppressed.3536Set PrintContextObjects = true to enable printing of the context internals.37*/38var PrintContextObjects = false3940// TruncatedDiff choose if we should display a truncated pretty diff or not41var TruncatedDiff = true4243// TruncateThreshold (default 50) specifies the maximum length string to print in string comparison assertion error44// messages.45var TruncateThreshold uint = 504647// CharactersAroundMismatchToInclude (default 5) specifies how many contextual characters should be printed before and48// after the first diff location in a truncated string assertion error message.49var CharactersAroundMismatchToInclude uint = 55051var contextType = reflect.TypeOf((*context.Context)(nil)).Elem()52var timeType = reflect.TypeOf(time.Time{})5354// The default indentation string emitted by the format package55var Indent = " "5657var longFormThreshold = 205859// GomegaStringer allows for custom formatting of objects for gomega.60type GomegaStringer interface {61// GomegaString will be used to custom format an object.62// It does not follow UseStringerRepresentation value and will always be called regardless.63// It also ignores the MaxLength value.64GomegaString() string65}6667/*68CustomFormatters can be registered with Gomega via RegisterCustomFormatter()69Any value to be rendered by Gomega is passed to each registered CustomFormatters.70The CustomFormatter signals that it will handle formatting the value by returning (formatted-string, true)71If the CustomFormatter does not want to handle the object it should return ("", false)7273Strings returned by CustomFormatters are not truncated74*/75type CustomFormatter func(value any) (string, bool)76type CustomFormatterKey uint7778var customFormatterKey CustomFormatterKey = 17980type customFormatterKeyPair struct {81CustomFormatter82CustomFormatterKey83}8485/*86RegisterCustomFormatter registers a CustomFormatter and returns a CustomFormatterKey8788You can call UnregisterCustomFormatter with the returned key to unregister the associated CustomFormatter89*/90func RegisterCustomFormatter(customFormatter CustomFormatter) CustomFormatterKey {91key := customFormatterKey92customFormatterKey += 193customFormatters = append(customFormatters, customFormatterKeyPair{customFormatter, key})94return key95}9697/*98UnregisterCustomFormatter unregisters a previously registered CustomFormatter. You should pass in the key returned by RegisterCustomFormatter99*/100func UnregisterCustomFormatter(key CustomFormatterKey) {101formatters := []customFormatterKeyPair{}102for _, f := range customFormatters {103if f.CustomFormatterKey == key {104continue105}106formatters = append(formatters, f)107}108customFormatters = formatters109}110111var customFormatters = []customFormatterKeyPair{}112113/*114Generates a formatted matcher success/failure message of the form:115116Expected117<pretty printed actual>118<message>119<pretty printed expected>120121If expected is omitted, then the message looks like:122123Expected124<pretty printed actual>125<message>126*/127func Message(actual any, message string, expected ...any) string {128if len(expected) == 0 {129return fmt.Sprintf("Expected\n%s\n%s", Object(actual, 1), message)130}131return fmt.Sprintf("Expected\n%s\n%s\n%s", Object(actual, 1), message, Object(expected[0], 1))132}133134/*135136Generates a nicely formatted matcher success / failure message137138Much like Message(...), but it attempts to pretty print diffs in strings139140Expected141<string>: "...aaaaabaaaaa..."142to equal |143<string>: "...aaaaazaaaaa..."144145*/146147func MessageWithDiff(actual, message, expected string) string {148if TruncatedDiff && len(actual) >= int(TruncateThreshold) && len(expected) >= int(TruncateThreshold) {149diffPoint := findFirstMismatch(actual, expected)150formattedActual := truncateAndFormat(actual, diffPoint)151formattedExpected := truncateAndFormat(expected, diffPoint)152153spacesBeforeFormattedMismatch := findFirstMismatch(formattedActual, formattedExpected)154155tabLength := 4156spaceFromMessageToActual := tabLength + len("<string>: ") - len(message)157158paddingCount := spaceFromMessageToActual + spacesBeforeFormattedMismatch159if paddingCount < 0 {160return Message(formattedActual, message, formattedExpected)161}162163padding := strings.Repeat(" ", paddingCount) + "|"164return Message(formattedActual, message+padding, formattedExpected)165}166167actual = escapedWithGoSyntax(actual)168expected = escapedWithGoSyntax(expected)169170return Message(actual, message, expected)171}172173func escapedWithGoSyntax(str string) string {174withQuotes := fmt.Sprintf("%q", str)175return withQuotes[1 : len(withQuotes)-1]176}177178func truncateAndFormat(str string, index int) string {179leftPadding := `...`180rightPadding := `...`181182start := index - int(CharactersAroundMismatchToInclude)183if start < 0 {184start = 0185leftPadding = ""186}187188// slice index must include the mis-matched character189lengthOfMismatchedCharacter := 1190end := index + int(CharactersAroundMismatchToInclude) + lengthOfMismatchedCharacter191if end > len(str) {192end = len(str)193rightPadding = ""194195}196return fmt.Sprintf("\"%s\"", leftPadding+str[start:end]+rightPadding)197}198199func findFirstMismatch(a, b string) int {200aSlice := strings.Split(a, "")201bSlice := strings.Split(b, "")202203for index, str := range aSlice {204if index > len(bSlice)-1 {205return index206}207if str != bSlice[index] {208return index209}210}211212if len(b) > len(a) {213return len(a) + 1214}215216return 0217}218219const truncateHelpText = `220Gomega truncated this representation as it exceeds 'format.MaxLength'.221Consider having the object provide a custom 'GomegaStringer' representation222or adjust the parameters in Gomega's 'format' package.223224Learn more here: https://onsi.github.io/gomega/#adjusting-output225`226227func truncateLongStrings(s string) string {228if MaxLength > 0 && len(s) > MaxLength {229var sb strings.Builder230for i, r := range s {231if i < MaxLength {232sb.WriteRune(r)233continue234}235break236}237238sb.WriteString("...\n")239sb.WriteString(truncateHelpText)240241return sb.String()242}243return s244}245246/*247Pretty prints the passed in object at the passed in indentation level.248249Object recurses into deeply nested objects emitting pretty-printed representations of their components.250251Modify format.MaxDepth to control how deep the recursion is allowed to go252Set format.UseStringerRepresentation to true to return object.GoString() or object.String() when available instead of253recursing into the object.254255Set PrintContextObjects to true to print the content of objects implementing context.Context256*/257func Object(object any, indentation uint) string {258indent := strings.Repeat(Indent, int(indentation))259value := reflect.ValueOf(object)260commonRepresentation := ""261if err, ok := object.(error); ok && !isNilValue(value) { // isNilValue check needed here to avoid nil deref due to boxed nil262commonRepresentation += "\n" + IndentString(err.Error(), indentation) + "\n" + indent263}264return fmt.Sprintf("%s<%s>: %s%s", indent, formatType(value), commonRepresentation, formatValue(value, indentation))265}266267/*268IndentString takes a string and indents each line by the specified amount.269*/270func IndentString(s string, indentation uint) string {271return indentString(s, indentation, true)272}273274func indentString(s string, indentation uint, indentFirstLine bool) string {275result := &strings.Builder{}276components := strings.Split(s, "\n")277indent := strings.Repeat(Indent, int(indentation))278for i, component := range components {279if i > 0 || indentFirstLine {280result.WriteString(indent)281}282result.WriteString(component)283if i < len(components)-1 {284result.WriteString("\n")285}286}287288return result.String()289}290291func formatType(v reflect.Value) string {292switch v.Kind() {293case reflect.Invalid:294return "nil"295case reflect.Chan:296return fmt.Sprintf("%s | len:%d, cap:%d", v.Type(), v.Len(), v.Cap())297case reflect.Ptr:298return fmt.Sprintf("%s | 0x%x", v.Type(), v.Pointer())299case reflect.Slice:300return fmt.Sprintf("%s | len:%d, cap:%d", v.Type(), v.Len(), v.Cap())301case reflect.Map:302return fmt.Sprintf("%s | len:%d", v.Type(), v.Len())303default:304return v.Type().String()305}306}307308func formatValue(value reflect.Value, indentation uint) string {309if indentation > MaxDepth {310return "..."311}312313if isNilValue(value) {314return "nil"315}316317if value.CanInterface() {318obj := value.Interface()319320// if a CustomFormatter handles this values, we'll go with that321for _, customFormatter := range customFormatters {322formatted, handled := customFormatter.CustomFormatter(obj)323// do not truncate a user-provided CustomFormatter()324if handled {325return indentString(formatted, indentation+1, false)326}327}328329// GomegaStringer will take precedence to other representations and disregards UseStringerRepresentation330if x, ok := obj.(GomegaStringer); ok {331// do not truncate a user-defined GomegaString() value332return indentString(x.GomegaString(), indentation+1, false)333}334335if UseStringerRepresentation {336switch x := obj.(type) {337case fmt.GoStringer:338return indentString(truncateLongStrings(x.GoString()), indentation+1, false)339case fmt.Stringer:340return indentString(truncateLongStrings(x.String()), indentation+1, false)341}342}343}344345if !PrintContextObjects {346if value.Type().Implements(contextType) && indentation > 1 {347return "<suppressed context>"348}349}350351switch value.Kind() {352case reflect.Bool:353return fmt.Sprintf("%v", value.Bool())354case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:355return fmt.Sprintf("%v", value.Int())356case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:357return fmt.Sprintf("%v", value.Uint())358case reflect.Uintptr:359return fmt.Sprintf("0x%x", value.Uint())360case reflect.Float32, reflect.Float64:361return fmt.Sprintf("%v", value.Float())362case reflect.Complex64, reflect.Complex128:363return fmt.Sprintf("%v", value.Complex())364case reflect.Chan:365return fmt.Sprintf("0x%x", value.Pointer())366case reflect.Func:367return fmt.Sprintf("0x%x", value.Pointer())368case reflect.Ptr:369return formatValue(value.Elem(), indentation)370case reflect.Slice:371return truncateLongStrings(formatSlice(value, indentation))372case reflect.String:373return truncateLongStrings(formatString(value.String(), indentation))374case reflect.Array:375return truncateLongStrings(formatSlice(value, indentation))376case reflect.Map:377return truncateLongStrings(formatMap(value, indentation))378case reflect.Struct:379if value.Type() == timeType && value.CanInterface() {380t, _ := value.Interface().(time.Time)381return t.Format(time.RFC3339Nano)382}383return truncateLongStrings(formatStruct(value, indentation))384case reflect.Interface:385return formatInterface(value, indentation)386default:387if value.CanInterface() {388return truncateLongStrings(fmt.Sprintf("%#v", value.Interface()))389}390return truncateLongStrings(fmt.Sprintf("%#v", value))391}392}393394func formatString(object any, indentation uint) string {395if indentation == 1 {396s := fmt.Sprintf("%s", object)397components := strings.Split(s, "\n")398result := ""399for i, component := range components {400if i == 0 {401result += component402} else {403result += Indent + component404}405if i < len(components)-1 {406result += "\n"407}408}409410return result411} else {412return fmt.Sprintf("%q", object)413}414}415416func formatSlice(v reflect.Value, indentation uint) string {417if v.Kind() == reflect.Slice && v.Type().Elem().Kind() == reflect.Uint8 && isPrintableString(string(v.Bytes())) {418return formatString(v.Bytes(), indentation)419}420421l := v.Len()422result := make([]string, l)423longest := 0424for i := 0; i < l; i++ {425result[i] = formatValue(v.Index(i), indentation+1)426if len(result[i]) > longest {427longest = len(result[i])428}429}430431if longest > longFormThreshold {432indenter := strings.Repeat(Indent, int(indentation))433return fmt.Sprintf("[\n%s%s,\n%s]", indenter+Indent, strings.Join(result, ",\n"+indenter+Indent), indenter)434}435return fmt.Sprintf("[%s]", strings.Join(result, ", "))436}437438func formatMap(v reflect.Value, indentation uint) string {439l := v.Len()440result := make([]string, l)441442longest := 0443for i, key := range v.MapKeys() {444value := v.MapIndex(key)445result[i] = fmt.Sprintf("%s: %s", formatValue(key, indentation+1), formatValue(value, indentation+1))446if len(result[i]) > longest {447longest = len(result[i])448}449}450451if longest > longFormThreshold {452indenter := strings.Repeat(Indent, int(indentation))453return fmt.Sprintf("{\n%s%s,\n%s}", indenter+Indent, strings.Join(result, ",\n"+indenter+Indent), indenter)454}455return fmt.Sprintf("{%s}", strings.Join(result, ", "))456}457458func formatStruct(v reflect.Value, indentation uint) string {459t := v.Type()460461l := v.NumField()462result := []string{}463longest := 0464for i := 0; i < l; i++ {465structField := t.Field(i)466fieldEntry := v.Field(i)467representation := fmt.Sprintf("%s: %s", structField.Name, formatValue(fieldEntry, indentation+1))468result = append(result, representation)469if len(representation) > longest {470longest = len(representation)471}472}473if longest > longFormThreshold {474indenter := strings.Repeat(Indent, int(indentation))475return fmt.Sprintf("{\n%s%s,\n%s}", indenter+Indent, strings.Join(result, ",\n"+indenter+Indent), indenter)476}477return fmt.Sprintf("{%s}", strings.Join(result, ", "))478}479480func formatInterface(v reflect.Value, indentation uint) string {481return fmt.Sprintf("<%s>%s", formatType(v.Elem()), formatValue(v.Elem(), indentation))482}483484func isNilValue(a reflect.Value) bool {485switch a.Kind() {486case reflect.Invalid:487return true488case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:489return a.IsNil()490}491492return false493}494495/*496Returns true when the string is entirely made of printable runes, false otherwise.497*/498func isPrintableString(str string) bool {499for _, runeValue := range str {500if !strconv.IsPrint(runeValue) {501return false502}503}504return true505}506507508