Blame vendor/github.com/google/go-cmp/cmp/report_compare.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
	"fmt"
Packit 63bb0d
	"reflect"
Packit 63bb0d
Packit 63bb0d
	"github.com/google/go-cmp/cmp/internal/value"
Packit 63bb0d
)
Packit 63bb0d
Packit 63bb0d
// TODO: Enforce limits?
Packit 63bb0d
//	* Enforce maximum number of records to print per node?
Packit 63bb0d
//	* Enforce maximum size in bytes allowed?
Packit 63bb0d
//	* As a heuristic, use less verbosity for equal nodes than unequal nodes.
Packit 63bb0d
// TODO: Enforce unique outputs?
Packit 63bb0d
//	* Avoid Stringer methods if it results in same output?
Packit 63bb0d
//	* Print pointer address if outputs still equal?
Packit 63bb0d
Packit 63bb0d
// numContextRecords is the number of surrounding equal records to print.
Packit 63bb0d
const numContextRecords = 2
Packit 63bb0d
Packit 63bb0d
type diffMode byte
Packit 63bb0d
Packit 63bb0d
const (
Packit 63bb0d
	diffUnknown   diffMode = 0
Packit 63bb0d
	diffIdentical diffMode = ' '
Packit 63bb0d
	diffRemoved   diffMode = '-'
Packit 63bb0d
	diffInserted  diffMode = '+'
Packit 63bb0d
)
Packit 63bb0d
Packit 63bb0d
type typeMode int
Packit 63bb0d
Packit 63bb0d
const (
Packit 63bb0d
	// emitType always prints the type.
Packit 63bb0d
	emitType typeMode = iota
Packit 63bb0d
	// elideType never prints the type.
Packit 63bb0d
	elideType
Packit 63bb0d
	// autoType prints the type only for composite kinds
Packit 63bb0d
	// (i.e., structs, slices, arrays, and maps).
Packit 63bb0d
	autoType
Packit 63bb0d
)
Packit 63bb0d
Packit 63bb0d
type formatOptions struct {
Packit 63bb0d
	// DiffMode controls the output mode of FormatDiff.
Packit 63bb0d
	//
Packit 63bb0d
	// If diffUnknown,   then produce a diff of the x and y values.
Packit 63bb0d
	// If diffIdentical, then emit values as if they were equal.
Packit 63bb0d
	// If diffRemoved,   then only emit x values (ignoring y values).
Packit 63bb0d
	// If diffInserted,  then only emit y values (ignoring x values).
Packit 63bb0d
	DiffMode diffMode
Packit 63bb0d
Packit 63bb0d
	// TypeMode controls whether to print the type for the current node.
Packit 63bb0d
	//
Packit 63bb0d
	// As a general rule of thumb, we always print the type of the next node
Packit 63bb0d
	// after an interface, and always elide the type of the next node after
Packit 63bb0d
	// a slice or map node.
Packit 63bb0d
	TypeMode typeMode
Packit 63bb0d
Packit 63bb0d
	// formatValueOptions are options specific to printing reflect.Values.
Packit 63bb0d
	formatValueOptions
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func (opts formatOptions) WithDiffMode(d diffMode) formatOptions {
Packit 63bb0d
	opts.DiffMode = d
Packit 63bb0d
	return opts
Packit 63bb0d
}
Packit 63bb0d
func (opts formatOptions) WithTypeMode(t typeMode) formatOptions {
Packit 63bb0d
	opts.TypeMode = t
Packit 63bb0d
	return opts
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// FormatDiff converts a valueNode tree into a textNode tree, where the later
Packit 63bb0d
// is a textual representation of the differences detected in the former.
Packit 63bb0d
func (opts formatOptions) FormatDiff(v *valueNode) textNode {
Packit 63bb0d
	// Check whether we have specialized formatting for this node.
Packit 63bb0d
	// This is not necessary, but helpful for producing more readable outputs.
Packit 63bb0d
	if opts.CanFormatDiffSlice(v) {
Packit 63bb0d
		return opts.FormatDiffSlice(v)
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	// For leaf nodes, format the value based on the reflect.Values alone.
Packit 63bb0d
	if v.MaxDepth == 0 {
Packit 63bb0d
		switch opts.DiffMode {
Packit 63bb0d
		case diffUnknown, diffIdentical:
Packit 63bb0d
			// Format Equal.
Packit 63bb0d
			if v.NumDiff == 0 {
Packit 63bb0d
				outx := opts.FormatValue(v.ValueX, visitedPointers{})
Packit 63bb0d
				outy := opts.FormatValue(v.ValueY, visitedPointers{})
Packit 63bb0d
				if v.NumIgnored > 0 && v.NumSame == 0 {
Packit 63bb0d
					return textEllipsis
Packit 63bb0d
				} else if outx.Len() < outy.Len() {
Packit 63bb0d
					return outx
Packit 63bb0d
				} else {
Packit 63bb0d
					return outy
Packit 63bb0d
				}
Packit 63bb0d
			}
Packit 63bb0d
Packit 63bb0d
			// Format unequal.
Packit 63bb0d
			assert(opts.DiffMode == diffUnknown)
Packit 63bb0d
			var list textList
Packit 63bb0d
			outx := opts.WithTypeMode(elideType).FormatValue(v.ValueX, visitedPointers{})
Packit 63bb0d
			outy := opts.WithTypeMode(elideType).FormatValue(v.ValueY, visitedPointers{})
Packit 63bb0d
			if outx != nil {
Packit 63bb0d
				list = append(list, textRecord{Diff: '-', Value: outx})
Packit 63bb0d
			}
Packit 63bb0d
			if outy != nil {
Packit 63bb0d
				list = append(list, textRecord{Diff: '+', Value: outy})
Packit 63bb0d
			}
Packit 63bb0d
			return opts.WithTypeMode(emitType).FormatType(v.Type, list)
Packit 63bb0d
		case diffRemoved:
Packit 63bb0d
			return opts.FormatValue(v.ValueX, visitedPointers{})
Packit 63bb0d
		case diffInserted:
Packit 63bb0d
			return opts.FormatValue(v.ValueY, visitedPointers{})
Packit 63bb0d
		default:
Packit 63bb0d
			panic("invalid diff mode")
Packit 63bb0d
		}
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	// Descend into the child value node.
Packit 63bb0d
	if v.TransformerName != "" {
Packit 63bb0d
		out := opts.WithTypeMode(emitType).FormatDiff(v.Value)
Packit 63bb0d
		out = textWrap{"Inverse(" + v.TransformerName + ", ", out, ")"}
Packit 63bb0d
		return opts.FormatType(v.Type, out)
Packit 63bb0d
	} else {
Packit 63bb0d
		switch k := v.Type.Kind(); k {
Packit 63bb0d
		case reflect.Struct, reflect.Array, reflect.Slice, reflect.Map:
Packit 63bb0d
			return opts.FormatType(v.Type, opts.formatDiffList(v.Records, k))
Packit 63bb0d
		case reflect.Ptr:
Packit 63bb0d
			return textWrap{"&", opts.FormatDiff(v.Value), ""}
Packit 63bb0d
		case reflect.Interface:
Packit 63bb0d
			return opts.WithTypeMode(emitType).FormatDiff(v.Value)
Packit 63bb0d
		default:
Packit 63bb0d
			panic(fmt.Sprintf("%v cannot have children", k))
Packit 63bb0d
		}
Packit 63bb0d
	}
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind) textNode {
Packit 63bb0d
	// Derive record name based on the data structure kind.
Packit 63bb0d
	var name string
Packit 63bb0d
	var formatKey func(reflect.Value) string
Packit 63bb0d
	switch k {
Packit 63bb0d
	case reflect.Struct:
Packit 63bb0d
		name = "field"
Packit 63bb0d
		opts = opts.WithTypeMode(autoType)
Packit 63bb0d
		formatKey = func(v reflect.Value) string { return v.String() }
Packit 63bb0d
	case reflect.Slice, reflect.Array:
Packit 63bb0d
		name = "element"
Packit 63bb0d
		opts = opts.WithTypeMode(elideType)
Packit 63bb0d
		formatKey = func(reflect.Value) string { return "" }
Packit 63bb0d
	case reflect.Map:
Packit 63bb0d
		name = "entry"
Packit 63bb0d
		opts = opts.WithTypeMode(elideType)
Packit 63bb0d
		formatKey = formatMapKey
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	// Handle unification.
Packit 63bb0d
	switch opts.DiffMode {
Packit 63bb0d
	case diffIdentical, diffRemoved, diffInserted:
Packit 63bb0d
		var list textList
Packit 63bb0d
		var deferredEllipsis bool // Add final "..." to indicate records were dropped
Packit 63bb0d
		for _, r := range recs {
Packit 63bb0d
			// Elide struct fields that are zero value.
Packit 63bb0d
			if k == reflect.Struct {
Packit 63bb0d
				var isZero bool
Packit 63bb0d
				switch opts.DiffMode {
Packit 63bb0d
				case diffIdentical:
Packit 63bb0d
					isZero = value.IsZero(r.Value.ValueX) || value.IsZero(r.Value.ValueY)
Packit 63bb0d
				case diffRemoved:
Packit 63bb0d
					isZero = value.IsZero(r.Value.ValueX)
Packit 63bb0d
				case diffInserted:
Packit 63bb0d
					isZero = value.IsZero(r.Value.ValueY)
Packit 63bb0d
				}
Packit 63bb0d
				if isZero {
Packit 63bb0d
					continue
Packit 63bb0d
				}
Packit 63bb0d
			}
Packit 63bb0d
			// Elide ignored nodes.
Packit 63bb0d
			if r.Value.NumIgnored > 0 && r.Value.NumSame+r.Value.NumDiff == 0 {
Packit 63bb0d
				deferredEllipsis = !(k == reflect.Slice || k == reflect.Array)
Packit 63bb0d
				if !deferredEllipsis {
Packit 63bb0d
					list.AppendEllipsis(diffStats{})
Packit 63bb0d
				}
Packit 63bb0d
				continue
Packit 63bb0d
			}
Packit 63bb0d
			if out := opts.FormatDiff(r.Value); out != nil {
Packit 63bb0d
				list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
Packit 63bb0d
			}
Packit 63bb0d
		}
Packit 63bb0d
		if deferredEllipsis {
Packit 63bb0d
			list.AppendEllipsis(diffStats{})
Packit 63bb0d
		}
Packit 63bb0d
		return textWrap{"{", list, "}"}
Packit 63bb0d
	case diffUnknown:
Packit 63bb0d
	default:
Packit 63bb0d
		panic("invalid diff mode")
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	// Handle differencing.
Packit 63bb0d
	var list textList
Packit 63bb0d
	groups := coalesceAdjacentRecords(name, recs)
Packit 63bb0d
	for i, ds := range groups {
Packit 63bb0d
		// Handle equal records.
Packit 63bb0d
		if ds.NumDiff() == 0 {
Packit 63bb0d
			// Compute the number of leading and trailing records to print.
Packit 63bb0d
			var numLo, numHi int
Packit 63bb0d
			numEqual := ds.NumIgnored + ds.NumIdentical
Packit 63bb0d
			for numLo < numContextRecords && numLo+numHi < numEqual && i != 0 {
Packit 63bb0d
				if r := recs[numLo].Value; r.NumIgnored > 0 && r.NumSame+r.NumDiff == 0 {
Packit 63bb0d
					break
Packit 63bb0d
				}
Packit 63bb0d
				numLo++
Packit 63bb0d
			}
Packit 63bb0d
			for numHi < numContextRecords && numLo+numHi < numEqual && i != len(groups)-1 {
Packit 63bb0d
				if r := recs[numEqual-numHi-1].Value; r.NumIgnored > 0 && r.NumSame+r.NumDiff == 0 {
Packit 63bb0d
					break
Packit 63bb0d
				}
Packit 63bb0d
				numHi++
Packit 63bb0d
			}
Packit 63bb0d
			if numEqual-(numLo+numHi) == 1 && ds.NumIgnored == 0 {
Packit 63bb0d
				numHi++ // Avoid pointless coalescing of a single equal record
Packit 63bb0d
			}
Packit 63bb0d
Packit 63bb0d
			// Format the equal values.
Packit 63bb0d
			for _, r := range recs[:numLo] {
Packit 63bb0d
				out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value)
Packit 63bb0d
				list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
Packit 63bb0d
			}
Packit 63bb0d
			if numEqual > numLo+numHi {
Packit 63bb0d
				ds.NumIdentical -= numLo + numHi
Packit 63bb0d
				list.AppendEllipsis(ds)
Packit 63bb0d
			}
Packit 63bb0d
			for _, r := range recs[numEqual-numHi : numEqual] {
Packit 63bb0d
				out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value)
Packit 63bb0d
				list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
Packit 63bb0d
			}
Packit 63bb0d
			recs = recs[numEqual:]
Packit 63bb0d
			continue
Packit 63bb0d
		}
Packit 63bb0d
Packit 63bb0d
		// Handle unequal records.
Packit 63bb0d
		for _, r := range recs[:ds.NumDiff()] {
Packit 63bb0d
			switch {
Packit 63bb0d
			case opts.CanFormatDiffSlice(r.Value):
Packit 63bb0d
				out := opts.FormatDiffSlice(r.Value)
Packit 63bb0d
				list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
Packit 63bb0d
			case r.Value.NumChildren == r.Value.MaxDepth:
Packit 63bb0d
				outx := opts.WithDiffMode(diffRemoved).FormatDiff(r.Value)
Packit 63bb0d
				outy := opts.WithDiffMode(diffInserted).FormatDiff(r.Value)
Packit 63bb0d
				if outx != nil {
Packit 63bb0d
					list = append(list, textRecord{Diff: diffRemoved, Key: formatKey(r.Key), Value: outx})
Packit 63bb0d
				}
Packit 63bb0d
				if outy != nil {
Packit 63bb0d
					list = append(list, textRecord{Diff: diffInserted, Key: formatKey(r.Key), Value: outy})
Packit 63bb0d
				}
Packit 63bb0d
			default:
Packit 63bb0d
				out := opts.FormatDiff(r.Value)
Packit 63bb0d
				list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
Packit 63bb0d
			}
Packit 63bb0d
		}
Packit 63bb0d
		recs = recs[ds.NumDiff():]
Packit 63bb0d
	}
Packit 63bb0d
	assert(len(recs) == 0)
Packit 63bb0d
	return textWrap{"{", list, "}"}
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// coalesceAdjacentRecords coalesces the list of records into groups of
Packit 63bb0d
// adjacent equal, or unequal counts.
Packit 63bb0d
func coalesceAdjacentRecords(name string, recs []reportRecord) (groups []diffStats) {
Packit 63bb0d
	var prevCase int // Arbitrary index into which case last occurred
Packit 63bb0d
	lastStats := func(i int) *diffStats {
Packit 63bb0d
		if prevCase != i {
Packit 63bb0d
			groups = append(groups, diffStats{Name: name})
Packit 63bb0d
			prevCase = i
Packit 63bb0d
		}
Packit 63bb0d
		return &groups[len(groups)-1]
Packit 63bb0d
	}
Packit 63bb0d
	for _, r := range recs {
Packit 63bb0d
		switch rv := r.Value; {
Packit 63bb0d
		case rv.NumIgnored > 0 && rv.NumSame+rv.NumDiff == 0:
Packit 63bb0d
			lastStats(1).NumIgnored++
Packit 63bb0d
		case rv.NumDiff == 0:
Packit 63bb0d
			lastStats(1).NumIdentical++
Packit 63bb0d
		case rv.NumDiff > 0 && !rv.ValueY.IsValid():
Packit 63bb0d
			lastStats(2).NumRemoved++
Packit 63bb0d
		case rv.NumDiff > 0 && !rv.ValueX.IsValid():
Packit 63bb0d
			lastStats(2).NumInserted++
Packit 63bb0d
		default:
Packit 63bb0d
			lastStats(2).NumModified++
Packit 63bb0d
		}
Packit 63bb0d
	}
Packit 63bb0d
	return groups
Packit 63bb0d
}