...

Source file src/github.com/onsi/gomega/format/format.go

Documentation: github.com/onsi/gomega/format

     1  /*
     2  Gomega's format package pretty-prints objects.  It explores input objects recursively and generates formatted, indented output with type information.
     3  */
     4  
     5  // untested sections: 4
     6  
     7  package format
     8  
     9  import (
    10  	"context"
    11  	"fmt"
    12  	"reflect"
    13  	"strconv"
    14  	"strings"
    15  	"time"
    16  )
    17  
    18  // Use MaxDepth to set the maximum recursion depth when printing deeply nested objects
    19  var MaxDepth = uint(10)
    20  
    21  // MaxLength of the string representation of an object.
    22  // If MaxLength is set to 0, the Object will not be truncated.
    23  var MaxLength = 4000
    24  
    25  /*
    26  By default, all objects (even those that implement fmt.Stringer and fmt.GoStringer) are recursively inspected to generate output.
    27  
    28  Set UseStringerRepresentation = true to use GoString (for fmt.GoStringers) or String (for fmt.Stringer) instead.
    29  
    30  Note that GoString and String don't always have all the information you need to understand why a test failed!
    31  */
    32  var UseStringerRepresentation = false
    33  
    34  /*
    35  Print the content of context objects. By default it will be suppressed.
    36  
    37  Set PrintContextObjects = true to enable printing of the context internals.
    38  */
    39  var PrintContextObjects = false
    40  
    41  // TruncatedDiff choose if we should display a truncated pretty diff or not
    42  var TruncatedDiff = true
    43  
    44  // TruncateThreshold (default 50) specifies the maximum length string to print in string comparison assertion error
    45  // messages.
    46  var TruncateThreshold uint = 50
    47  
    48  // CharactersAroundMismatchToInclude (default 5) specifies how many contextual characters should be printed before and
    49  // after the first diff location in a truncated string assertion error message.
    50  var CharactersAroundMismatchToInclude uint = 5
    51  
    52  var contextType = reflect.TypeOf((*context.Context)(nil)).Elem()
    53  var timeType = reflect.TypeOf(time.Time{})
    54  
    55  // The default indentation string emitted by the format package
    56  var Indent = "    "
    57  
    58  var longFormThreshold = 20
    59  
    60  // GomegaStringer allows for custom formating of objects for gomega.
    61  type GomegaStringer interface {
    62  	// GomegaString will be used to custom format an object.
    63  	// It does not follow UseStringerRepresentation value and will always be called regardless.
    64  	// It also ignores the MaxLength value.
    65  	GomegaString() string
    66  }
    67  
    68  /*
    69  CustomFormatters can be registered with Gomega via RegisterCustomFormatter()
    70  Any value to be rendered by Gomega is passed to each registered CustomFormatters.
    71  The CustomFormatter signals that it will handle formatting the value by returning (formatted-string, true)
    72  If the CustomFormatter does not want to handle the object it should return ("", false)
    73  
    74  Strings returned by CustomFormatters are not truncated
    75  */
    76  type CustomFormatter func(value interface{}) (string, bool)
    77  type CustomFormatterKey uint
    78  
    79  var customFormatterKey CustomFormatterKey = 1
    80  
    81  type customFormatterKeyPair struct {
    82  	CustomFormatter
    83  	CustomFormatterKey
    84  }
    85  
    86  /*
    87  RegisterCustomFormatter registers a CustomFormatter and returns a CustomFormatterKey
    88  
    89  You can call UnregisterCustomFormatter with the returned key to unregister the associated CustomFormatter
    90  */
    91  func RegisterCustomFormatter(customFormatter CustomFormatter) CustomFormatterKey {
    92  	key := customFormatterKey
    93  	customFormatterKey += 1
    94  	customFormatters = append(customFormatters, customFormatterKeyPair{customFormatter, key})
    95  	return key
    96  }
    97  
    98  /*
    99  UnregisterCustomFormatter unregisters a previously registered CustomFormatter.  You should pass in the key returned by RegisterCustomFormatter
   100  */
   101  func UnregisterCustomFormatter(key CustomFormatterKey) {
   102  	formatters := []customFormatterKeyPair{}
   103  	for _, f := range customFormatters {
   104  		if f.CustomFormatterKey == key {
   105  			continue
   106  		}
   107  		formatters = append(formatters, f)
   108  	}
   109  	customFormatters = formatters
   110  }
   111  
   112  var customFormatters = []customFormatterKeyPair{}
   113  
   114  /*
   115  Generates a formatted matcher success/failure message of the form:
   116  
   117  	Expected
   118  		<pretty printed actual>
   119  	<message>
   120  		<pretty printed expected>
   121  
   122  If expected is omitted, then the message looks like:
   123  
   124  	Expected
   125  		<pretty printed actual>
   126  	<message>
   127  */
   128  func Message(actual interface{}, message string, expected ...interface{}) string {
   129  	if len(expected) == 0 {
   130  		return fmt.Sprintf("Expected\n%s\n%s", Object(actual, 1), message)
   131  	}
   132  	return fmt.Sprintf("Expected\n%s\n%s\n%s", Object(actual, 1), message, Object(expected[0], 1))
   133  }
   134  
   135  /*
   136  
   137  Generates a nicely formatted matcher success / failure message
   138  
   139  Much like Message(...), but it attempts to pretty print diffs in strings
   140  
   141  Expected
   142      <string>: "...aaaaabaaaaa..."
   143  to equal               |
   144      <string>: "...aaaaazaaaaa..."
   145  
   146  */
   147  
   148  func MessageWithDiff(actual, message, expected string) string {
   149  	if TruncatedDiff && len(actual) >= int(TruncateThreshold) && len(expected) >= int(TruncateThreshold) {
   150  		diffPoint := findFirstMismatch(actual, expected)
   151  		formattedActual := truncateAndFormat(actual, diffPoint)
   152  		formattedExpected := truncateAndFormat(expected, diffPoint)
   153  
   154  		spacesBeforeFormattedMismatch := findFirstMismatch(formattedActual, formattedExpected)
   155  
   156  		tabLength := 4
   157  		spaceFromMessageToActual := tabLength + len("<string>: ") - len(message)
   158  
   159  		paddingCount := spaceFromMessageToActual + spacesBeforeFormattedMismatch
   160  		if paddingCount < 0 {
   161  			return Message(formattedActual, message, formattedExpected)
   162  		}
   163  
   164  		padding := strings.Repeat(" ", paddingCount) + "|"
   165  		return Message(formattedActual, message+padding, formattedExpected)
   166  	}
   167  
   168  	actual = escapedWithGoSyntax(actual)
   169  	expected = escapedWithGoSyntax(expected)
   170  
   171  	return Message(actual, message, expected)
   172  }
   173  
   174  func escapedWithGoSyntax(str string) string {
   175  	withQuotes := fmt.Sprintf("%q", str)
   176  	return withQuotes[1 : len(withQuotes)-1]
   177  }
   178  
   179  func truncateAndFormat(str string, index int) string {
   180  	leftPadding := `...`
   181  	rightPadding := `...`
   182  
   183  	start := index - int(CharactersAroundMismatchToInclude)
   184  	if start < 0 {
   185  		start = 0
   186  		leftPadding = ""
   187  	}
   188  
   189  	// slice index must include the mis-matched character
   190  	lengthOfMismatchedCharacter := 1
   191  	end := index + int(CharactersAroundMismatchToInclude) + lengthOfMismatchedCharacter
   192  	if end > len(str) {
   193  		end = len(str)
   194  		rightPadding = ""
   195  
   196  	}
   197  	return fmt.Sprintf("\"%s\"", leftPadding+str[start:end]+rightPadding)
   198  }
   199  
   200  func findFirstMismatch(a, b string) int {
   201  	aSlice := strings.Split(a, "")
   202  	bSlice := strings.Split(b, "")
   203  
   204  	for index, str := range aSlice {
   205  		if index > len(bSlice)-1 {
   206  			return index
   207  		}
   208  		if str != bSlice[index] {
   209  			return index
   210  		}
   211  	}
   212  
   213  	if len(b) > len(a) {
   214  		return len(a) + 1
   215  	}
   216  
   217  	return 0
   218  }
   219  
   220  const truncateHelpText = `
   221  Gomega truncated this representation as it exceeds 'format.MaxLength'.
   222  Consider having the object provide a custom 'GomegaStringer' representation
   223  or adjust the parameters in Gomega's 'format' package.
   224  
   225  Learn more here: https://onsi.github.io/gomega/#adjusting-output
   226  `
   227  
   228  func truncateLongStrings(s string) string {
   229  	if MaxLength > 0 && len(s) > MaxLength {
   230  		var sb strings.Builder
   231  		for i, r := range s {
   232  			if i < MaxLength {
   233  				sb.WriteRune(r)
   234  				continue
   235  			}
   236  			break
   237  		}
   238  
   239  		sb.WriteString("...\n")
   240  		sb.WriteString(truncateHelpText)
   241  
   242  		return sb.String()
   243  	}
   244  	return s
   245  }
   246  
   247  /*
   248  Pretty prints the passed in object at the passed in indentation level.
   249  
   250  Object recurses into deeply nested objects emitting pretty-printed representations of their components.
   251  
   252  Modify format.MaxDepth to control how deep the recursion is allowed to go
   253  Set format.UseStringerRepresentation to true to return object.GoString() or object.String() when available instead of
   254  recursing into the object.
   255  
   256  Set PrintContextObjects to true to print the content of objects implementing context.Context
   257  */
   258  func Object(object interface{}, indentation uint) string {
   259  	indent := strings.Repeat(Indent, int(indentation))
   260  	value := reflect.ValueOf(object)
   261  	commonRepresentation := ""
   262  	if err, ok := object.(error); ok && !isNilValue(value) { // isNilValue check needed here to avoid nil deref due to boxed nil
   263  		commonRepresentation += "\n" + IndentString(err.Error(), indentation) + "\n" + indent
   264  	}
   265  	return fmt.Sprintf("%s<%s>: %s%s", indent, formatType(value), commonRepresentation, formatValue(value, indentation))
   266  }
   267  
   268  /*
   269  IndentString takes a string and indents each line by the specified amount.
   270  */
   271  func IndentString(s string, indentation uint) string {
   272  	return indentString(s, indentation, true)
   273  }
   274  
   275  func indentString(s string, indentation uint, indentFirstLine bool) string {
   276  	result := &strings.Builder{}
   277  	components := strings.Split(s, "\n")
   278  	indent := strings.Repeat(Indent, int(indentation))
   279  	for i, component := range components {
   280  		if i > 0 || indentFirstLine {
   281  			result.WriteString(indent)
   282  		}
   283  		result.WriteString(component)
   284  		if i < len(components)-1 {
   285  			result.WriteString("\n")
   286  		}
   287  	}
   288  
   289  	return result.String()
   290  }
   291  
   292  func formatType(v reflect.Value) string {
   293  	switch v.Kind() {
   294  	case reflect.Invalid:
   295  		return "nil"
   296  	case reflect.Chan:
   297  		return fmt.Sprintf("%s | len:%d, cap:%d", v.Type(), v.Len(), v.Cap())
   298  	case reflect.Ptr:
   299  		return fmt.Sprintf("%s | 0x%x", v.Type(), v.Pointer())
   300  	case reflect.Slice:
   301  		return fmt.Sprintf("%s | len:%d, cap:%d", v.Type(), v.Len(), v.Cap())
   302  	case reflect.Map:
   303  		return fmt.Sprintf("%s | len:%d", v.Type(), v.Len())
   304  	default:
   305  		return v.Type().String()
   306  	}
   307  }
   308  
   309  func formatValue(value reflect.Value, indentation uint) string {
   310  	if indentation > MaxDepth {
   311  		return "..."
   312  	}
   313  
   314  	if isNilValue(value) {
   315  		return "nil"
   316  	}
   317  
   318  	if value.CanInterface() {
   319  		obj := value.Interface()
   320  
   321  		// if a CustomFormatter handles this values, we'll go with that
   322  		for _, customFormatter := range customFormatters {
   323  			formatted, handled := customFormatter.CustomFormatter(obj)
   324  			// do not truncate a user-provided CustomFormatter()
   325  			if handled {
   326  				return indentString(formatted, indentation+1, false)
   327  			}
   328  		}
   329  
   330  		// GomegaStringer will take precedence to other representations and disregards UseStringerRepresentation
   331  		if x, ok := obj.(GomegaStringer); ok {
   332  			// do not truncate a user-defined GomegaString() value
   333  			return indentString(x.GomegaString(), indentation+1, false)
   334  		}
   335  
   336  		if UseStringerRepresentation {
   337  			switch x := obj.(type) {
   338  			case fmt.GoStringer:
   339  				return indentString(truncateLongStrings(x.GoString()), indentation+1, false)
   340  			case fmt.Stringer:
   341  				return indentString(truncateLongStrings(x.String()), indentation+1, false)
   342  			}
   343  		}
   344  	}
   345  
   346  	if !PrintContextObjects {
   347  		if value.Type().Implements(contextType) && indentation > 1 {
   348  			return "<suppressed context>"
   349  		}
   350  	}
   351  
   352  	switch value.Kind() {
   353  	case reflect.Bool:
   354  		return fmt.Sprintf("%v", value.Bool())
   355  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   356  		return fmt.Sprintf("%v", value.Int())
   357  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   358  		return fmt.Sprintf("%v", value.Uint())
   359  	case reflect.Uintptr:
   360  		return fmt.Sprintf("0x%x", value.Uint())
   361  	case reflect.Float32, reflect.Float64:
   362  		return fmt.Sprintf("%v", value.Float())
   363  	case reflect.Complex64, reflect.Complex128:
   364  		return fmt.Sprintf("%v", value.Complex())
   365  	case reflect.Chan:
   366  		return fmt.Sprintf("0x%x", value.Pointer())
   367  	case reflect.Func:
   368  		return fmt.Sprintf("0x%x", value.Pointer())
   369  	case reflect.Ptr:
   370  		return formatValue(value.Elem(), indentation)
   371  	case reflect.Slice:
   372  		return truncateLongStrings(formatSlice(value, indentation))
   373  	case reflect.String:
   374  		return truncateLongStrings(formatString(value.String(), indentation))
   375  	case reflect.Array:
   376  		return truncateLongStrings(formatSlice(value, indentation))
   377  	case reflect.Map:
   378  		return truncateLongStrings(formatMap(value, indentation))
   379  	case reflect.Struct:
   380  		if value.Type() == timeType && value.CanInterface() {
   381  			t, _ := value.Interface().(time.Time)
   382  			return t.Format(time.RFC3339Nano)
   383  		}
   384  		return truncateLongStrings(formatStruct(value, indentation))
   385  	case reflect.Interface:
   386  		return formatInterface(value, indentation)
   387  	default:
   388  		if value.CanInterface() {
   389  			return truncateLongStrings(fmt.Sprintf("%#v", value.Interface()))
   390  		}
   391  		return truncateLongStrings(fmt.Sprintf("%#v", value))
   392  	}
   393  }
   394  
   395  func formatString(object interface{}, indentation uint) string {
   396  	if indentation == 1 {
   397  		s := fmt.Sprintf("%s", object)
   398  		components := strings.Split(s, "\n")
   399  		result := ""
   400  		for i, component := range components {
   401  			if i == 0 {
   402  				result += component
   403  			} else {
   404  				result += Indent + component
   405  			}
   406  			if i < len(components)-1 {
   407  				result += "\n"
   408  			}
   409  		}
   410  
   411  		return result
   412  	} else {
   413  		return fmt.Sprintf("%q", object)
   414  	}
   415  }
   416  
   417  func formatSlice(v reflect.Value, indentation uint) string {
   418  	if v.Kind() == reflect.Slice && v.Type().Elem().Kind() == reflect.Uint8 && isPrintableString(string(v.Bytes())) {
   419  		return formatString(v.Bytes(), indentation)
   420  	}
   421  
   422  	l := v.Len()
   423  	result := make([]string, l)
   424  	longest := 0
   425  	for i := 0; i < l; i++ {
   426  		result[i] = formatValue(v.Index(i), indentation+1)
   427  		if len(result[i]) > longest {
   428  			longest = len(result[i])
   429  		}
   430  	}
   431  
   432  	if longest > longFormThreshold {
   433  		indenter := strings.Repeat(Indent, int(indentation))
   434  		return fmt.Sprintf("[\n%s%s,\n%s]", indenter+Indent, strings.Join(result, ",\n"+indenter+Indent), indenter)
   435  	}
   436  	return fmt.Sprintf("[%s]", strings.Join(result, ", "))
   437  }
   438  
   439  func formatMap(v reflect.Value, indentation uint) string {
   440  	l := v.Len()
   441  	result := make([]string, l)
   442  
   443  	longest := 0
   444  	for i, key := range v.MapKeys() {
   445  		value := v.MapIndex(key)
   446  		result[i] = fmt.Sprintf("%s: %s", formatValue(key, indentation+1), formatValue(value, indentation+1))
   447  		if len(result[i]) > longest {
   448  			longest = len(result[i])
   449  		}
   450  	}
   451  
   452  	if longest > longFormThreshold {
   453  		indenter := strings.Repeat(Indent, int(indentation))
   454  		return fmt.Sprintf("{\n%s%s,\n%s}", indenter+Indent, strings.Join(result, ",\n"+indenter+Indent), indenter)
   455  	}
   456  	return fmt.Sprintf("{%s}", strings.Join(result, ", "))
   457  }
   458  
   459  func formatStruct(v reflect.Value, indentation uint) string {
   460  	t := v.Type()
   461  
   462  	l := v.NumField()
   463  	result := []string{}
   464  	longest := 0
   465  	for i := 0; i < l; i++ {
   466  		structField := t.Field(i)
   467  		fieldEntry := v.Field(i)
   468  		representation := fmt.Sprintf("%s: %s", structField.Name, formatValue(fieldEntry, indentation+1))
   469  		result = append(result, representation)
   470  		if len(representation) > longest {
   471  			longest = len(representation)
   472  		}
   473  	}
   474  	if longest > longFormThreshold {
   475  		indenter := strings.Repeat(Indent, int(indentation))
   476  		return fmt.Sprintf("{\n%s%s,\n%s}", indenter+Indent, strings.Join(result, ",\n"+indenter+Indent), indenter)
   477  	}
   478  	return fmt.Sprintf("{%s}", strings.Join(result, ", "))
   479  }
   480  
   481  func formatInterface(v reflect.Value, indentation uint) string {
   482  	return fmt.Sprintf("<%s>%s", formatType(v.Elem()), formatValue(v.Elem(), indentation))
   483  }
   484  
   485  func isNilValue(a reflect.Value) bool {
   486  	switch a.Kind() {
   487  	case reflect.Invalid:
   488  		return true
   489  	case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
   490  		return a.IsNil()
   491  	}
   492  
   493  	return false
   494  }
   495  
   496  /*
   497  Returns true when the string is entirely made of printable runes, false otherwise.
   498  */
   499  func isPrintableString(str string) bool {
   500  	for _, runeValue := range str {
   501  		if !strconv.IsPrint(runeValue) {
   502  			return false
   503  		}
   504  	}
   505  	return true
   506  }
   507  

View as plain text