mirror of
https://git.mirrors.martin98.com/https://github.com/ceph/ceph-csi.git
synced 2025-08-04 14:20:43 +08:00

Uses github.com/libopenstorage/secrets to communicate with Vault. This removes the need for maintaining our own limited Vault APIs. By adding the new dependency, several other packages got updated in the process. Unused indirect dependencies have been removed from go.mod. Signed-off-by: Niels de Vos <ndevos@redhat.com>
433 lines
13 KiB
Go
433 lines
13 KiB
Go
// Copyright 2019, The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE.md file.
|
|
|
|
package cmp
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"github.com/google/go-cmp/cmp/internal/value"
|
|
)
|
|
|
|
// numContextRecords is the number of surrounding equal records to print.
|
|
const numContextRecords = 2
|
|
|
|
type diffMode byte
|
|
|
|
const (
|
|
diffUnknown diffMode = 0
|
|
diffIdentical diffMode = ' '
|
|
diffRemoved diffMode = '-'
|
|
diffInserted diffMode = '+'
|
|
)
|
|
|
|
type typeMode int
|
|
|
|
const (
|
|
// emitType always prints the type.
|
|
emitType typeMode = iota
|
|
// elideType never prints the type.
|
|
elideType
|
|
// autoType prints the type only for composite kinds
|
|
// (i.e., structs, slices, arrays, and maps).
|
|
autoType
|
|
)
|
|
|
|
type formatOptions struct {
|
|
// DiffMode controls the output mode of FormatDiff.
|
|
//
|
|
// If diffUnknown, then produce a diff of the x and y values.
|
|
// If diffIdentical, then emit values as if they were equal.
|
|
// If diffRemoved, then only emit x values (ignoring y values).
|
|
// If diffInserted, then only emit y values (ignoring x values).
|
|
DiffMode diffMode
|
|
|
|
// TypeMode controls whether to print the type for the current node.
|
|
//
|
|
// As a general rule of thumb, we always print the type of the next node
|
|
// after an interface, and always elide the type of the next node after
|
|
// a slice or map node.
|
|
TypeMode typeMode
|
|
|
|
// formatValueOptions are options specific to printing reflect.Values.
|
|
formatValueOptions
|
|
}
|
|
|
|
func (opts formatOptions) WithDiffMode(d diffMode) formatOptions {
|
|
opts.DiffMode = d
|
|
return opts
|
|
}
|
|
func (opts formatOptions) WithTypeMode(t typeMode) formatOptions {
|
|
opts.TypeMode = t
|
|
return opts
|
|
}
|
|
func (opts formatOptions) WithVerbosity(level int) formatOptions {
|
|
opts.VerbosityLevel = level
|
|
opts.LimitVerbosity = true
|
|
return opts
|
|
}
|
|
func (opts formatOptions) verbosity() uint {
|
|
switch {
|
|
case opts.VerbosityLevel < 0:
|
|
return 0
|
|
case opts.VerbosityLevel > 16:
|
|
return 16 // some reasonable maximum to avoid shift overflow
|
|
default:
|
|
return uint(opts.VerbosityLevel)
|
|
}
|
|
}
|
|
|
|
const maxVerbosityPreset = 3
|
|
|
|
// verbosityPreset modifies the verbosity settings given an index
|
|
// between 0 and maxVerbosityPreset, inclusive.
|
|
func verbosityPreset(opts formatOptions, i int) formatOptions {
|
|
opts.VerbosityLevel = int(opts.verbosity()) + 2*i
|
|
if i > 0 {
|
|
opts.AvoidStringer = true
|
|
}
|
|
if i >= maxVerbosityPreset {
|
|
opts.PrintAddresses = true
|
|
opts.QualifiedNames = true
|
|
}
|
|
return opts
|
|
}
|
|
|
|
// FormatDiff converts a valueNode tree into a textNode tree, where the later
|
|
// is a textual representation of the differences detected in the former.
|
|
func (opts formatOptions) FormatDiff(v *valueNode, ptrs *pointerReferences) (out textNode) {
|
|
if opts.DiffMode == diffIdentical {
|
|
opts = opts.WithVerbosity(1)
|
|
} else {
|
|
opts = opts.WithVerbosity(3)
|
|
}
|
|
|
|
// Check whether we have specialized formatting for this node.
|
|
// This is not necessary, but helpful for producing more readable outputs.
|
|
if opts.CanFormatDiffSlice(v) {
|
|
return opts.FormatDiffSlice(v)
|
|
}
|
|
|
|
var parentKind reflect.Kind
|
|
if v.parent != nil && v.parent.TransformerName == "" {
|
|
parentKind = v.parent.Type.Kind()
|
|
}
|
|
|
|
// For leaf nodes, format the value based on the reflect.Values alone.
|
|
if v.MaxDepth == 0 {
|
|
switch opts.DiffMode {
|
|
case diffUnknown, diffIdentical:
|
|
// Format Equal.
|
|
if v.NumDiff == 0 {
|
|
outx := opts.FormatValue(v.ValueX, parentKind, ptrs)
|
|
outy := opts.FormatValue(v.ValueY, parentKind, ptrs)
|
|
if v.NumIgnored > 0 && v.NumSame == 0 {
|
|
return textEllipsis
|
|
} else if outx.Len() < outy.Len() {
|
|
return outx
|
|
} else {
|
|
return outy
|
|
}
|
|
}
|
|
|
|
// Format unequal.
|
|
assert(opts.DiffMode == diffUnknown)
|
|
var list textList
|
|
outx := opts.WithTypeMode(elideType).FormatValue(v.ValueX, parentKind, ptrs)
|
|
outy := opts.WithTypeMode(elideType).FormatValue(v.ValueY, parentKind, ptrs)
|
|
for i := 0; i <= maxVerbosityPreset && outx != nil && outy != nil && outx.Equal(outy); i++ {
|
|
opts2 := verbosityPreset(opts, i).WithTypeMode(elideType)
|
|
outx = opts2.FormatValue(v.ValueX, parentKind, ptrs)
|
|
outy = opts2.FormatValue(v.ValueY, parentKind, ptrs)
|
|
}
|
|
if outx != nil {
|
|
list = append(list, textRecord{Diff: '-', Value: outx})
|
|
}
|
|
if outy != nil {
|
|
list = append(list, textRecord{Diff: '+', Value: outy})
|
|
}
|
|
return opts.WithTypeMode(emitType).FormatType(v.Type, list)
|
|
case diffRemoved:
|
|
return opts.FormatValue(v.ValueX, parentKind, ptrs)
|
|
case diffInserted:
|
|
return opts.FormatValue(v.ValueY, parentKind, ptrs)
|
|
default:
|
|
panic("invalid diff mode")
|
|
}
|
|
}
|
|
|
|
// Register slice element to support cycle detection.
|
|
if parentKind == reflect.Slice {
|
|
ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, true)
|
|
defer ptrs.Pop()
|
|
defer func() { out = wrapTrunkReferences(ptrRefs, out) }()
|
|
}
|
|
|
|
// Descend into the child value node.
|
|
if v.TransformerName != "" {
|
|
out := opts.WithTypeMode(emitType).FormatDiff(v.Value, ptrs)
|
|
out = &textWrap{Prefix: "Inverse(" + v.TransformerName + ", ", Value: out, Suffix: ")"}
|
|
return opts.FormatType(v.Type, out)
|
|
} else {
|
|
switch k := v.Type.Kind(); k {
|
|
case reflect.Struct, reflect.Array, reflect.Slice:
|
|
out = opts.formatDiffList(v.Records, k, ptrs)
|
|
out = opts.FormatType(v.Type, out)
|
|
case reflect.Map:
|
|
// Register map to support cycle detection.
|
|
ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, false)
|
|
defer ptrs.Pop()
|
|
|
|
out = opts.formatDiffList(v.Records, k, ptrs)
|
|
out = wrapTrunkReferences(ptrRefs, out)
|
|
out = opts.FormatType(v.Type, out)
|
|
case reflect.Ptr:
|
|
// Register pointer to support cycle detection.
|
|
ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, false)
|
|
defer ptrs.Pop()
|
|
|
|
out = opts.FormatDiff(v.Value, ptrs)
|
|
out = wrapTrunkReferences(ptrRefs, out)
|
|
out = &textWrap{Prefix: "&", Value: out}
|
|
case reflect.Interface:
|
|
out = opts.WithTypeMode(emitType).FormatDiff(v.Value, ptrs)
|
|
default:
|
|
panic(fmt.Sprintf("%v cannot have children", k))
|
|
}
|
|
return out
|
|
}
|
|
}
|
|
|
|
func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind, ptrs *pointerReferences) textNode {
|
|
// Derive record name based on the data structure kind.
|
|
var name string
|
|
var formatKey func(reflect.Value) string
|
|
switch k {
|
|
case reflect.Struct:
|
|
name = "field"
|
|
opts = opts.WithTypeMode(autoType)
|
|
formatKey = func(v reflect.Value) string { return v.String() }
|
|
case reflect.Slice, reflect.Array:
|
|
name = "element"
|
|
opts = opts.WithTypeMode(elideType)
|
|
formatKey = func(reflect.Value) string { return "" }
|
|
case reflect.Map:
|
|
name = "entry"
|
|
opts = opts.WithTypeMode(elideType)
|
|
formatKey = func(v reflect.Value) string { return formatMapKey(v, false, ptrs) }
|
|
}
|
|
|
|
maxLen := -1
|
|
if opts.LimitVerbosity {
|
|
if opts.DiffMode == diffIdentical {
|
|
maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
|
|
} else {
|
|
maxLen = (1 << opts.verbosity()) << 1 // 2, 4, 8, 16, 32, 64, etc...
|
|
}
|
|
opts.VerbosityLevel--
|
|
}
|
|
|
|
// Handle unification.
|
|
switch opts.DiffMode {
|
|
case diffIdentical, diffRemoved, diffInserted:
|
|
var list textList
|
|
var deferredEllipsis bool // Add final "..." to indicate records were dropped
|
|
for _, r := range recs {
|
|
if len(list) == maxLen {
|
|
deferredEllipsis = true
|
|
break
|
|
}
|
|
|
|
// Elide struct fields that are zero value.
|
|
if k == reflect.Struct {
|
|
var isZero bool
|
|
switch opts.DiffMode {
|
|
case diffIdentical:
|
|
isZero = value.IsZero(r.Value.ValueX) || value.IsZero(r.Value.ValueY)
|
|
case diffRemoved:
|
|
isZero = value.IsZero(r.Value.ValueX)
|
|
case diffInserted:
|
|
isZero = value.IsZero(r.Value.ValueY)
|
|
}
|
|
if isZero {
|
|
continue
|
|
}
|
|
}
|
|
// Elide ignored nodes.
|
|
if r.Value.NumIgnored > 0 && r.Value.NumSame+r.Value.NumDiff == 0 {
|
|
deferredEllipsis = !(k == reflect.Slice || k == reflect.Array)
|
|
if !deferredEllipsis {
|
|
list.AppendEllipsis(diffStats{})
|
|
}
|
|
continue
|
|
}
|
|
if out := opts.FormatDiff(r.Value, ptrs); out != nil {
|
|
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
|
}
|
|
}
|
|
if deferredEllipsis {
|
|
list.AppendEllipsis(diffStats{})
|
|
}
|
|
return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
|
case diffUnknown:
|
|
default:
|
|
panic("invalid diff mode")
|
|
}
|
|
|
|
// Handle differencing.
|
|
var numDiffs int
|
|
var list textList
|
|
var keys []reflect.Value // invariant: len(list) == len(keys)
|
|
groups := coalesceAdjacentRecords(name, recs)
|
|
maxGroup := diffStats{Name: name}
|
|
for i, ds := range groups {
|
|
if maxLen >= 0 && numDiffs >= maxLen {
|
|
maxGroup = maxGroup.Append(ds)
|
|
continue
|
|
}
|
|
|
|
// Handle equal records.
|
|
if ds.NumDiff() == 0 {
|
|
// Compute the number of leading and trailing records to print.
|
|
var numLo, numHi int
|
|
numEqual := ds.NumIgnored + ds.NumIdentical
|
|
for numLo < numContextRecords && numLo+numHi < numEqual && i != 0 {
|
|
if r := recs[numLo].Value; r.NumIgnored > 0 && r.NumSame+r.NumDiff == 0 {
|
|
break
|
|
}
|
|
numLo++
|
|
}
|
|
for numHi < numContextRecords && numLo+numHi < numEqual && i != len(groups)-1 {
|
|
if r := recs[numEqual-numHi-1].Value; r.NumIgnored > 0 && r.NumSame+r.NumDiff == 0 {
|
|
break
|
|
}
|
|
numHi++
|
|
}
|
|
if numEqual-(numLo+numHi) == 1 && ds.NumIgnored == 0 {
|
|
numHi++ // Avoid pointless coalescing of a single equal record
|
|
}
|
|
|
|
// Format the equal values.
|
|
for _, r := range recs[:numLo] {
|
|
out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value, ptrs)
|
|
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
|
keys = append(keys, r.Key)
|
|
}
|
|
if numEqual > numLo+numHi {
|
|
ds.NumIdentical -= numLo + numHi
|
|
list.AppendEllipsis(ds)
|
|
for len(keys) < len(list) {
|
|
keys = append(keys, reflect.Value{})
|
|
}
|
|
}
|
|
for _, r := range recs[numEqual-numHi : numEqual] {
|
|
out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value, ptrs)
|
|
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
|
keys = append(keys, r.Key)
|
|
}
|
|
recs = recs[numEqual:]
|
|
continue
|
|
}
|
|
|
|
// Handle unequal records.
|
|
for _, r := range recs[:ds.NumDiff()] {
|
|
switch {
|
|
case opts.CanFormatDiffSlice(r.Value):
|
|
out := opts.FormatDiffSlice(r.Value)
|
|
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
|
keys = append(keys, r.Key)
|
|
case r.Value.NumChildren == r.Value.MaxDepth:
|
|
outx := opts.WithDiffMode(diffRemoved).FormatDiff(r.Value, ptrs)
|
|
outy := opts.WithDiffMode(diffInserted).FormatDiff(r.Value, ptrs)
|
|
for i := 0; i <= maxVerbosityPreset && outx != nil && outy != nil && outx.Equal(outy); i++ {
|
|
opts2 := verbosityPreset(opts, i)
|
|
outx = opts2.WithDiffMode(diffRemoved).FormatDiff(r.Value, ptrs)
|
|
outy = opts2.WithDiffMode(diffInserted).FormatDiff(r.Value, ptrs)
|
|
}
|
|
if outx != nil {
|
|
list = append(list, textRecord{Diff: diffRemoved, Key: formatKey(r.Key), Value: outx})
|
|
keys = append(keys, r.Key)
|
|
}
|
|
if outy != nil {
|
|
list = append(list, textRecord{Diff: diffInserted, Key: formatKey(r.Key), Value: outy})
|
|
keys = append(keys, r.Key)
|
|
}
|
|
default:
|
|
out := opts.FormatDiff(r.Value, ptrs)
|
|
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
|
keys = append(keys, r.Key)
|
|
}
|
|
}
|
|
recs = recs[ds.NumDiff():]
|
|
numDiffs += ds.NumDiff()
|
|
}
|
|
if maxGroup.IsZero() {
|
|
assert(len(recs) == 0)
|
|
} else {
|
|
list.AppendEllipsis(maxGroup)
|
|
for len(keys) < len(list) {
|
|
keys = append(keys, reflect.Value{})
|
|
}
|
|
}
|
|
assert(len(list) == len(keys))
|
|
|
|
// For maps, the default formatting logic uses fmt.Stringer which may
|
|
// produce ambiguous output. Avoid calling String to disambiguate.
|
|
if k == reflect.Map {
|
|
var ambiguous bool
|
|
seenKeys := map[string]reflect.Value{}
|
|
for i, currKey := range keys {
|
|
if currKey.IsValid() {
|
|
strKey := list[i].Key
|
|
prevKey, seen := seenKeys[strKey]
|
|
if seen && prevKey.CanInterface() && currKey.CanInterface() {
|
|
ambiguous = prevKey.Interface() != currKey.Interface()
|
|
if ambiguous {
|
|
break
|
|
}
|
|
}
|
|
seenKeys[strKey] = currKey
|
|
}
|
|
}
|
|
if ambiguous {
|
|
for i, k := range keys {
|
|
if k.IsValid() {
|
|
list[i].Key = formatMapKey(k, true, ptrs)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
|
}
|
|
|
|
// coalesceAdjacentRecords coalesces the list of records into groups of
|
|
// adjacent equal, or unequal counts.
|
|
func coalesceAdjacentRecords(name string, recs []reportRecord) (groups []diffStats) {
|
|
var prevCase int // Arbitrary index into which case last occurred
|
|
lastStats := func(i int) *diffStats {
|
|
if prevCase != i {
|
|
groups = append(groups, diffStats{Name: name})
|
|
prevCase = i
|
|
}
|
|
return &groups[len(groups)-1]
|
|
}
|
|
for _, r := range recs {
|
|
switch rv := r.Value; {
|
|
case rv.NumIgnored > 0 && rv.NumSame+rv.NumDiff == 0:
|
|
lastStats(1).NumIgnored++
|
|
case rv.NumDiff == 0:
|
|
lastStats(1).NumIdentical++
|
|
case rv.NumDiff > 0 && !rv.ValueY.IsValid():
|
|
lastStats(2).NumRemoved++
|
|
case rv.NumDiff > 0 && !rv.ValueX.IsValid():
|
|
lastStats(2).NumInserted++
|
|
default:
|
|
lastStats(2).NumModified++
|
|
}
|
|
}
|
|
return groups
|
|
}
|