Blame vendor/github.com/google/go-cmp/cmp/report_text.go

Packit 63bb0d
// Copyright 2019, The Go Authors. All rights reserved.
Packit 63bb0d
// Use of this source code is governed by a BSD-style
Packit 63bb0d
// license that can be found in the LICENSE.md file.
Packit 63bb0d
Packit 63bb0d
package cmp
Packit 63bb0d
Packit 63bb0d
import (
Packit 63bb0d
	"bytes"
Packit 63bb0d
	"fmt"
Packit 63bb0d
	"math/rand"
Packit 63bb0d
	"strings"
Packit 63bb0d
	"time"
Packit 63bb0d
Packit 63bb0d
	"github.com/google/go-cmp/cmp/internal/flags"
Packit 63bb0d
)
Packit 63bb0d
Packit 63bb0d
var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0
Packit 63bb0d
Packit 63bb0d
type indentMode int
Packit 63bb0d
Packit 63bb0d
func (n indentMode) appendIndent(b []byte, d diffMode) []byte {
Packit 63bb0d
	// The output of Diff is documented as being unstable to provide future
Packit 63bb0d
	// flexibility in changing the output for more humanly readable reports.
Packit 63bb0d
	// This logic intentionally introduces instability to the exact output
Packit 63bb0d
	// so that users can detect accidental reliance on stability early on,
Packit 63bb0d
	// rather than much later when an actual change to the format occurs.
Packit 63bb0d
	if flags.Deterministic || randBool {
Packit 63bb0d
		// Use regular spaces (U+0020).
Packit 63bb0d
		switch d {
Packit 63bb0d
		case diffUnknown, diffIdentical:
Packit 63bb0d
			b = append(b, "  "...)
Packit 63bb0d
		case diffRemoved:
Packit 63bb0d
			b = append(b, "- "...)
Packit 63bb0d
		case diffInserted:
Packit 63bb0d
			b = append(b, "+ "...)
Packit 63bb0d
		}
Packit 63bb0d
	} else {
Packit 63bb0d
		// Use non-breaking spaces (U+00a0).
Packit 63bb0d
		switch d {
Packit 63bb0d
		case diffUnknown, diffIdentical:
Packit 63bb0d
			b = append(b, "  "...)
Packit 63bb0d
		case diffRemoved:
Packit 63bb0d
			b = append(b, "- "...)
Packit 63bb0d
		case diffInserted:
Packit 63bb0d
			b = append(b, "+ "...)
Packit 63bb0d
		}
Packit 63bb0d
	}
Packit 63bb0d
	return repeatCount(n).appendChar(b, '\t')
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
type repeatCount int
Packit 63bb0d
Packit 63bb0d
func (n repeatCount) appendChar(b []byte, c byte) []byte {
Packit 63bb0d
	for ; n > 0; n-- {
Packit 63bb0d
		b = append(b, c)
Packit 63bb0d
	}
Packit 63bb0d
	return b
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// textNode is a simplified tree-based representation of structured text.
Packit 63bb0d
// Possible node types are textWrap, textList, or textLine.
Packit 63bb0d
type textNode interface {
Packit 63bb0d
	// Len reports the length in bytes of a single-line version of the tree.
Packit 63bb0d
	// Nested textRecord.Diff and textRecord.Comment fields are ignored.
Packit 63bb0d
	Len() int
Packit 63bb0d
	// Equal reports whether the two trees are structurally identical.
Packit 63bb0d
	// Nested textRecord.Diff and textRecord.Comment fields are compared.
Packit 63bb0d
	Equal(textNode) bool
Packit 63bb0d
	// String returns the string representation of the text tree.
Packit 63bb0d
	// It is not guaranteed that len(x.String()) == x.Len(),
Packit 63bb0d
	// nor that x.String() == y.String() implies that x.Equal(y).
Packit 63bb0d
	String() string
Packit 63bb0d
Packit 63bb0d
	// formatCompactTo formats the contents of the tree as a single-line string
Packit 63bb0d
	// to the provided buffer. Any nested textRecord.Diff and textRecord.Comment
Packit 63bb0d
	// fields are ignored.
Packit 63bb0d
	//
Packit 63bb0d
	// However, not all nodes in the tree should be collapsed as a single-line.
Packit 63bb0d
	// If a node can be collapsed as a single-line, it is replaced by a textLine
Packit 63bb0d
	// node. Since the top-level node cannot replace itself, this also returns
Packit 63bb0d
	// the current node itself.
Packit 63bb0d
	//
Packit 63bb0d
	// This does not mutate the receiver.
Packit 63bb0d
	formatCompactTo([]byte, diffMode) ([]byte, textNode)
Packit 63bb0d
	// formatExpandedTo formats the contents of the tree as a multi-line string
Packit 63bb0d
	// to the provided buffer. In order for column alignment to operate well,
Packit 63bb0d
	// formatCompactTo must be called before calling formatExpandedTo.
Packit 63bb0d
	formatExpandedTo([]byte, diffMode, indentMode) []byte
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// textWrap is a wrapper that concatenates a prefix and/or a suffix
Packit 63bb0d
// to the underlying node.
Packit 63bb0d
type textWrap struct {
Packit 63bb0d
	Prefix string   // e.g., "bytes.Buffer{"
Packit 63bb0d
	Value  textNode // textWrap | textList | textLine
Packit 63bb0d
	Suffix string   // e.g., "}"
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func (s textWrap) Len() int {
Packit 63bb0d
	return len(s.Prefix) + s.Value.Len() + len(s.Suffix)
Packit 63bb0d
}
Packit 63bb0d
func (s1 textWrap) Equal(s2 textNode) bool {
Packit 63bb0d
	if s2, ok := s2.(textWrap); ok {
Packit 63bb0d
		return s1.Prefix == s2.Prefix && s1.Value.Equal(s2.Value) && s1.Suffix == s2.Suffix
Packit 63bb0d
	}
Packit 63bb0d
	return false
Packit 63bb0d
}
Packit 63bb0d
func (s textWrap) String() string {
Packit 63bb0d
	var d diffMode
Packit 63bb0d
	var n indentMode
Packit 63bb0d
	_, s2 := s.formatCompactTo(nil, d)
Packit 63bb0d
	b := n.appendIndent(nil, d)      // Leading indent
Packit 63bb0d
	b = s2.formatExpandedTo(b, d, n) // Main body
Packit 63bb0d
	b = append(b, '\n')              // Trailing newline
Packit 63bb0d
	return string(b)
Packit 63bb0d
}
Packit 63bb0d
func (s textWrap) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
Packit 63bb0d
	n0 := len(b) // Original buffer length
Packit 63bb0d
	b = append(b, s.Prefix...)
Packit 63bb0d
	b, s.Value = s.Value.formatCompactTo(b, d)
Packit 63bb0d
	b = append(b, s.Suffix...)
Packit 63bb0d
	if _, ok := s.Value.(textLine); ok {
Packit 63bb0d
		return b, textLine(b[n0:])
Packit 63bb0d
	}
Packit 63bb0d
	return b, s
Packit 63bb0d
}
Packit 63bb0d
func (s textWrap) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte {
Packit 63bb0d
	b = append(b, s.Prefix...)
Packit 63bb0d
	b = s.Value.formatExpandedTo(b, d, n)
Packit 63bb0d
	b = append(b, s.Suffix...)
Packit 63bb0d
	return b
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// textList is a comma-separated list of textWrap or textLine nodes.
Packit 63bb0d
// The list may be formatted as multi-lines or single-line at the discretion
Packit 63bb0d
// of the textList.formatCompactTo method.
Packit 63bb0d
type textList []textRecord
Packit 63bb0d
type textRecord struct {
Packit 63bb0d
	Diff    diffMode     // e.g., 0 or '-' or '+'
Packit 63bb0d
	Key     string       // e.g., "MyField"
Packit 63bb0d
	Value   textNode     // textWrap | textLine
Packit 63bb0d
	Comment fmt.Stringer // e.g., "6 identical fields"
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// AppendEllipsis appends a new ellipsis node to the list if none already
Packit 63bb0d
// exists at the end. If cs is non-zero it coalesces the statistics with the
Packit 63bb0d
// previous diffStats.
Packit 63bb0d
func (s *textList) AppendEllipsis(ds diffStats) {
Packit 63bb0d
	hasStats := ds != diffStats{}
Packit 63bb0d
	if len(*s) == 0 || !(*s)[len(*s)-1].Value.Equal(textEllipsis) {
Packit 63bb0d
		if hasStats {
Packit 63bb0d
			*s = append(*s, textRecord{Value: textEllipsis, Comment: ds})
Packit 63bb0d
		} else {
Packit 63bb0d
			*s = append(*s, textRecord{Value: textEllipsis})
Packit 63bb0d
		}
Packit 63bb0d
		return
Packit 63bb0d
	}
Packit 63bb0d
	if hasStats {
Packit 63bb0d
		(*s)[len(*s)-1].Comment = (*s)[len(*s)-1].Comment.(diffStats).Append(ds)
Packit 63bb0d
	}
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func (s textList) Len() (n int) {
Packit 63bb0d
	for i, r := range s {
Packit 63bb0d
		n += len(r.Key)
Packit 63bb0d
		if r.Key != "" {
Packit 63bb0d
			n += len(": ")
Packit 63bb0d
		}
Packit 63bb0d
		n += r.Value.Len()
Packit 63bb0d
		if i < len(s)-1 {
Packit 63bb0d
			n += len(", ")
Packit 63bb0d
		}
Packit 63bb0d
	}
Packit 63bb0d
	return n
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func (s1 textList) Equal(s2 textNode) bool {
Packit 63bb0d
	if s2, ok := s2.(textList); ok {
Packit 63bb0d
		if len(s1) != len(s2) {
Packit 63bb0d
			return false
Packit 63bb0d
		}
Packit 63bb0d
		for i := range s1 {
Packit 63bb0d
			r1, r2 := s1[i], s2[i]
Packit 63bb0d
			if !(r1.Diff == r2.Diff && r1.Key == r2.Key && r1.Value.Equal(r2.Value) && r1.Comment == r2.Comment) {
Packit 63bb0d
				return false
Packit 63bb0d
			}
Packit 63bb0d
		}
Packit 63bb0d
		return true
Packit 63bb0d
	}
Packit 63bb0d
	return false
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func (s textList) String() string {
Packit 63bb0d
	return textWrap{"{", s, "}"}.String()
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func (s textList) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
Packit 63bb0d
	s = append(textList(nil), s...) // Avoid mutating original
Packit 63bb0d
Packit 63bb0d
	// Determine whether we can collapse this list as a single line.
Packit 63bb0d
	n0 := len(b) // Original buffer length
Packit 63bb0d
	var multiLine bool
Packit 63bb0d
	for i, r := range s {
Packit 63bb0d
		if r.Diff == diffInserted || r.Diff == diffRemoved {
Packit 63bb0d
			multiLine = true
Packit 63bb0d
		}
Packit 63bb0d
		b = append(b, r.Key...)
Packit 63bb0d
		if r.Key != "" {
Packit 63bb0d
			b = append(b, ": "...)
Packit 63bb0d
		}
Packit 63bb0d
		b, s[i].Value = r.Value.formatCompactTo(b, d|r.Diff)
Packit 63bb0d
		if _, ok := s[i].Value.(textLine); !ok {
Packit 63bb0d
			multiLine = true
Packit 63bb0d
		}
Packit 63bb0d
		if r.Comment != nil {
Packit 63bb0d
			multiLine = true
Packit 63bb0d
		}
Packit 63bb0d
		if i < len(s)-1 {
Packit 63bb0d
			b = append(b, ", "...)
Packit 63bb0d
		}
Packit 63bb0d
	}
Packit 63bb0d
	// Force multi-lined output when printing a removed/inserted node that
Packit 63bb0d
	// is sufficiently long.
Packit 63bb0d
	if (d == diffInserted || d == diffRemoved) && len(b[n0:]) > 80 {
Packit 63bb0d
		multiLine = true
Packit 63bb0d
	}
Packit 63bb0d
	if !multiLine {
Packit 63bb0d
		return b, textLine(b[n0:])
Packit 63bb0d
	}
Packit 63bb0d
	return b, s
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func (s textList) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte {
Packit 63bb0d
	alignKeyLens := s.alignLens(
Packit 63bb0d
		func(r textRecord) bool {
Packit 63bb0d
			_, isLine := r.Value.(textLine)
Packit 63bb0d
			return r.Key == "" || !isLine
Packit 63bb0d
		},
Packit 63bb0d
		func(r textRecord) int { return len(r.Key) },
Packit 63bb0d
	)
Packit 63bb0d
	alignValueLens := s.alignLens(
Packit 63bb0d
		func(r textRecord) bool {
Packit 63bb0d
			_, isLine := r.Value.(textLine)
Packit 63bb0d
			return !isLine || r.Value.Equal(textEllipsis) || r.Comment == nil
Packit 63bb0d
		},
Packit 63bb0d
		func(r textRecord) int { return len(r.Value.(textLine)) },
Packit 63bb0d
	)
Packit 63bb0d
Packit 63bb0d
	// Format the list as a multi-lined output.
Packit 63bb0d
	n++
Packit 63bb0d
	for i, r := range s {
Packit 63bb0d
		b = n.appendIndent(append(b, '\n'), d|r.Diff)
Packit 63bb0d
		if r.Key != "" {
Packit 63bb0d
			b = append(b, r.Key+": "...)
Packit 63bb0d
		}
Packit 63bb0d
		b = alignKeyLens[i].appendChar(b, ' ')
Packit 63bb0d
Packit 63bb0d
		b = r.Value.formatExpandedTo(b, d|r.Diff, n)
Packit 63bb0d
		if !r.Value.Equal(textEllipsis) {
Packit 63bb0d
			b = append(b, ',')
Packit 63bb0d
		}
Packit 63bb0d
		b = alignValueLens[i].appendChar(b, ' ')
Packit 63bb0d
Packit 63bb0d
		if r.Comment != nil {
Packit 63bb0d
			b = append(b, " // "+r.Comment.String()...)
Packit 63bb0d
		}
Packit 63bb0d
	}
Packit 63bb0d
	n--
Packit 63bb0d
Packit 63bb0d
	return n.appendIndent(append(b, '\n'), d)
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func (s textList) alignLens(
Packit 63bb0d
	skipFunc func(textRecord) bool,
Packit 63bb0d
	lenFunc func(textRecord) int,
Packit 63bb0d
) []repeatCount {
Packit 63bb0d
	var startIdx, endIdx, maxLen int
Packit 63bb0d
	lens := make([]repeatCount, len(s))
Packit 63bb0d
	for i, r := range s {
Packit 63bb0d
		if skipFunc(r) {
Packit 63bb0d
			for j := startIdx; j < endIdx && j < len(s); j++ {
Packit 63bb0d
				lens[j] = repeatCount(maxLen - lenFunc(s[j]))
Packit 63bb0d
			}
Packit 63bb0d
			startIdx, endIdx, maxLen = i+1, i+1, 0
Packit 63bb0d
		} else {
Packit 63bb0d
			if maxLen < lenFunc(r) {
Packit 63bb0d
				maxLen = lenFunc(r)
Packit 63bb0d
			}
Packit 63bb0d
			endIdx = i + 1
Packit 63bb0d
		}
Packit 63bb0d
	}
Packit 63bb0d
	for j := startIdx; j < endIdx && j < len(s); j++ {
Packit 63bb0d
		lens[j] = repeatCount(maxLen - lenFunc(s[j]))
Packit 63bb0d
	}
Packit 63bb0d
	return lens
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// textLine is a single-line segment of text and is always a leaf node
Packit 63bb0d
// in the textNode tree.
Packit 63bb0d
type textLine []byte
Packit 63bb0d
Packit 63bb0d
var (
Packit 63bb0d
	textNil      = textLine("nil")
Packit 63bb0d
	textEllipsis = textLine("...")
Packit 63bb0d
)
Packit 63bb0d
Packit 63bb0d
func (s textLine) Len() int {
Packit 63bb0d
	return len(s)
Packit 63bb0d
}
Packit 63bb0d
func (s1 textLine) Equal(s2 textNode) bool {
Packit 63bb0d
	if s2, ok := s2.(textLine); ok {
Packit 63bb0d
		return bytes.Equal([]byte(s1), []byte(s2))
Packit 63bb0d
	}
Packit 63bb0d
	return false
Packit 63bb0d
}
Packit 63bb0d
func (s textLine) String() string {
Packit 63bb0d
	return string(s)
Packit 63bb0d
}
Packit 63bb0d
func (s textLine) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
Packit 63bb0d
	return append(b, s...), s
Packit 63bb0d
}
Packit 63bb0d
func (s textLine) formatExpandedTo(b []byte, _ diffMode, _ indentMode) []byte {
Packit 63bb0d
	return append(b, s...)
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
type diffStats struct {
Packit 63bb0d
	Name         string
Packit 63bb0d
	NumIgnored   int
Packit 63bb0d
	NumIdentical int
Packit 63bb0d
	NumRemoved   int
Packit 63bb0d
	NumInserted  int
Packit 63bb0d
	NumModified  int
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func (s diffStats) NumDiff() int {
Packit 63bb0d
	return s.NumRemoved + s.NumInserted + s.NumModified
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func (s diffStats) Append(ds diffStats) diffStats {
Packit 63bb0d
	assert(s.Name == ds.Name)
Packit 63bb0d
	s.NumIgnored += ds.NumIgnored
Packit 63bb0d
	s.NumIdentical += ds.NumIdentical
Packit 63bb0d
	s.NumRemoved += ds.NumRemoved
Packit 63bb0d
	s.NumInserted += ds.NumInserted
Packit 63bb0d
	s.NumModified += ds.NumModified
Packit 63bb0d
	return s
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// String prints a humanly-readable summary of coalesced records.
Packit 63bb0d
//
Packit 63bb0d
// Example:
Packit 63bb0d
//	diffStats{Name: "Field", NumIgnored: 5}.String() => "5 ignored fields"
Packit 63bb0d
func (s diffStats) String() string {
Packit 63bb0d
	var ss []string
Packit 63bb0d
	var sum int
Packit 63bb0d
	labels := [...]string{"ignored", "identical", "removed", "inserted", "modified"}
Packit 63bb0d
	counts := [...]int{s.NumIgnored, s.NumIdentical, s.NumRemoved, s.NumInserted, s.NumModified}
Packit 63bb0d
	for i, n := range counts {
Packit 63bb0d
		if n > 0 {
Packit 63bb0d
			ss = append(ss, fmt.Sprintf("%d %v", n, labels[i]))
Packit 63bb0d
		}
Packit 63bb0d
		sum += n
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	// Pluralize the name (adjusting for some obscure English grammar rules).
Packit 63bb0d
	name := s.Name
Packit 63bb0d
	if sum > 1 {
Packit 63bb0d
		name += "s"
Packit 63bb0d
		if strings.HasSuffix(name, "ys") {
Packit 63bb0d
			name = name[:len(name)-2] + "ies" // e.g., "entrys" => "entries"
Packit 63bb0d
		}
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	// Format the list according to English grammar (with Oxford comma).
Packit 63bb0d
	switch n := len(ss); n {
Packit 63bb0d
	case 0:
Packit 63bb0d
		return ""
Packit 63bb0d
	case 1, 2:
Packit 63bb0d
		return strings.Join(ss, " and ") + " " + name
Packit 63bb0d
	default:
Packit 63bb0d
		return strings.Join(ss[:n-1], ", ") + ", and " + ss[n-1] + " " + name
Packit 63bb0d
	}
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
type commentString string
Packit 63bb0d
Packit 63bb0d
func (s commentString) String() string { return string(s) }