435c0d9fc8
This PR switches the Nomad repository from using govendor to Go modules for managing dependencies. Aspects of the Nomad workflow remain pretty much the same. The usual Makefile targets should continue to work as they always did. The API submodule simply defers to the parent Nomad version on the repository, keeping the semantics of API versioning that currently exists.
279 lines
7.9 KiB
Go
279 lines
7.9 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"
|
|
"strconv"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"github.com/google/go-cmp/cmp/internal/flags"
|
|
"github.com/google/go-cmp/cmp/internal/value"
|
|
)
|
|
|
|
type formatValueOptions struct {
|
|
// AvoidStringer controls whether to avoid calling custom stringer
|
|
// methods like error.Error or fmt.Stringer.String.
|
|
AvoidStringer bool
|
|
|
|
// ShallowPointers controls whether to avoid descending into pointers.
|
|
// Useful when printing map keys, where pointer comparison is performed
|
|
// on the pointer address rather than the pointed-at value.
|
|
ShallowPointers bool
|
|
|
|
// PrintAddresses controls whether to print the address of all pointers,
|
|
// slice elements, and maps.
|
|
PrintAddresses bool
|
|
}
|
|
|
|
// FormatType prints the type as if it were wrapping s.
|
|
// This may return s as-is depending on the current type and TypeMode mode.
|
|
func (opts formatOptions) FormatType(t reflect.Type, s textNode) textNode {
|
|
// Check whether to emit the type or not.
|
|
switch opts.TypeMode {
|
|
case autoType:
|
|
switch t.Kind() {
|
|
case reflect.Struct, reflect.Slice, reflect.Array, reflect.Map:
|
|
if s.Equal(textNil) {
|
|
return s
|
|
}
|
|
default:
|
|
return s
|
|
}
|
|
case elideType:
|
|
return s
|
|
}
|
|
|
|
// Determine the type label, applying special handling for unnamed types.
|
|
typeName := t.String()
|
|
if t.Name() == "" {
|
|
// According to Go grammar, certain type literals contain symbols that
|
|
// do not strongly bind to the next lexicographical token (e.g., *T).
|
|
switch t.Kind() {
|
|
case reflect.Chan, reflect.Func, reflect.Ptr:
|
|
typeName = "(" + typeName + ")"
|
|
}
|
|
typeName = strings.Replace(typeName, "struct {", "struct{", -1)
|
|
typeName = strings.Replace(typeName, "interface {", "interface{", -1)
|
|
}
|
|
|
|
// Avoid wrap the value in parenthesis if unnecessary.
|
|
if s, ok := s.(textWrap); ok {
|
|
hasParens := strings.HasPrefix(s.Prefix, "(") && strings.HasSuffix(s.Suffix, ")")
|
|
hasBraces := strings.HasPrefix(s.Prefix, "{") && strings.HasSuffix(s.Suffix, "}")
|
|
if hasParens || hasBraces {
|
|
return textWrap{typeName, s, ""}
|
|
}
|
|
}
|
|
return textWrap{typeName + "(", s, ")"}
|
|
}
|
|
|
|
// FormatValue prints the reflect.Value, taking extra care to avoid descending
|
|
// into pointers already in m. As pointers are visited, m is also updated.
|
|
func (opts formatOptions) FormatValue(v reflect.Value, m visitedPointers) (out textNode) {
|
|
if !v.IsValid() {
|
|
return nil
|
|
}
|
|
t := v.Type()
|
|
|
|
// Check whether there is an Error or String method to call.
|
|
if !opts.AvoidStringer && v.CanInterface() {
|
|
// Avoid calling Error or String methods on nil receivers since many
|
|
// implementations crash when doing so.
|
|
if (t.Kind() != reflect.Ptr && t.Kind() != reflect.Interface) || !v.IsNil() {
|
|
switch v := v.Interface().(type) {
|
|
case error:
|
|
return textLine("e" + formatString(v.Error()))
|
|
case fmt.Stringer:
|
|
return textLine("s" + formatString(v.String()))
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check whether to explicitly wrap the result with the type.
|
|
var skipType bool
|
|
defer func() {
|
|
if !skipType {
|
|
out = opts.FormatType(t, out)
|
|
}
|
|
}()
|
|
|
|
var ptr string
|
|
switch t.Kind() {
|
|
case reflect.Bool:
|
|
return textLine(fmt.Sprint(v.Bool()))
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
return textLine(fmt.Sprint(v.Int()))
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
// Unnamed uints are usually bytes or words, so use hexadecimal.
|
|
if t.PkgPath() == "" || t.Kind() == reflect.Uintptr {
|
|
return textLine(formatHex(v.Uint()))
|
|
}
|
|
return textLine(fmt.Sprint(v.Uint()))
|
|
case reflect.Float32, reflect.Float64:
|
|
return textLine(fmt.Sprint(v.Float()))
|
|
case reflect.Complex64, reflect.Complex128:
|
|
return textLine(fmt.Sprint(v.Complex()))
|
|
case reflect.String:
|
|
return textLine(formatString(v.String()))
|
|
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
|
return textLine(formatPointer(v))
|
|
case reflect.Struct:
|
|
var list textList
|
|
for i := 0; i < v.NumField(); i++ {
|
|
vv := v.Field(i)
|
|
if value.IsZero(vv) {
|
|
continue // Elide fields with zero values
|
|
}
|
|
s := opts.WithTypeMode(autoType).FormatValue(vv, m)
|
|
list = append(list, textRecord{Key: t.Field(i).Name, Value: s})
|
|
}
|
|
return textWrap{"{", list, "}"}
|
|
case reflect.Slice:
|
|
if v.IsNil() {
|
|
return textNil
|
|
}
|
|
if opts.PrintAddresses {
|
|
ptr = formatPointer(v)
|
|
}
|
|
fallthrough
|
|
case reflect.Array:
|
|
var list textList
|
|
for i := 0; i < v.Len(); i++ {
|
|
vi := v.Index(i)
|
|
if vi.CanAddr() { // Check for cyclic elements
|
|
p := vi.Addr()
|
|
if m.Visit(p) {
|
|
var out textNode
|
|
out = textLine(formatPointer(p))
|
|
out = opts.WithTypeMode(emitType).FormatType(p.Type(), out)
|
|
out = textWrap{"*", out, ""}
|
|
list = append(list, textRecord{Value: out})
|
|
continue
|
|
}
|
|
}
|
|
s := opts.WithTypeMode(elideType).FormatValue(vi, m)
|
|
list = append(list, textRecord{Value: s})
|
|
}
|
|
return textWrap{ptr + "{", list, "}"}
|
|
case reflect.Map:
|
|
if v.IsNil() {
|
|
return textNil
|
|
}
|
|
if m.Visit(v) {
|
|
return textLine(formatPointer(v))
|
|
}
|
|
|
|
var list textList
|
|
for _, k := range value.SortKeys(v.MapKeys()) {
|
|
sk := formatMapKey(k)
|
|
sv := opts.WithTypeMode(elideType).FormatValue(v.MapIndex(k), m)
|
|
list = append(list, textRecord{Key: sk, Value: sv})
|
|
}
|
|
if opts.PrintAddresses {
|
|
ptr = formatPointer(v)
|
|
}
|
|
return textWrap{ptr + "{", list, "}"}
|
|
case reflect.Ptr:
|
|
if v.IsNil() {
|
|
return textNil
|
|
}
|
|
if m.Visit(v) || opts.ShallowPointers {
|
|
return textLine(formatPointer(v))
|
|
}
|
|
if opts.PrintAddresses {
|
|
ptr = formatPointer(v)
|
|
}
|
|
skipType = true // Let the underlying value print the type instead
|
|
return textWrap{"&" + ptr, opts.FormatValue(v.Elem(), m), ""}
|
|
case reflect.Interface:
|
|
if v.IsNil() {
|
|
return textNil
|
|
}
|
|
// Interfaces accept different concrete types,
|
|
// so configure the underlying value to explicitly print the type.
|
|
skipType = true // Print the concrete type instead
|
|
return opts.WithTypeMode(emitType).FormatValue(v.Elem(), m)
|
|
default:
|
|
panic(fmt.Sprintf("%v kind not handled", v.Kind()))
|
|
}
|
|
}
|
|
|
|
// formatMapKey formats v as if it were a map key.
|
|
// The result is guaranteed to be a single line.
|
|
func formatMapKey(v reflect.Value) string {
|
|
var opts formatOptions
|
|
opts.TypeMode = elideType
|
|
opts.ShallowPointers = true
|
|
s := opts.FormatValue(v, visitedPointers{}).String()
|
|
return strings.TrimSpace(s)
|
|
}
|
|
|
|
// formatString prints s as a double-quoted or backtick-quoted string.
|
|
func formatString(s string) string {
|
|
// Use quoted string if it the same length as a raw string literal.
|
|
// Otherwise, attempt to use the raw string form.
|
|
qs := strconv.Quote(s)
|
|
if len(qs) == 1+len(s)+1 {
|
|
return qs
|
|
}
|
|
|
|
// Disallow newlines to ensure output is a single line.
|
|
// Only allow printable runes for readability purposes.
|
|
rawInvalid := func(r rune) bool {
|
|
return r == '`' || r == '\n' || !(unicode.IsPrint(r) || r == '\t')
|
|
}
|
|
if strings.IndexFunc(s, rawInvalid) < 0 {
|
|
return "`" + s + "`"
|
|
}
|
|
return qs
|
|
}
|
|
|
|
// formatHex prints u as a hexadecimal integer in Go notation.
|
|
func formatHex(u uint64) string {
|
|
var f string
|
|
switch {
|
|
case u <= 0xff:
|
|
f = "0x%02x"
|
|
case u <= 0xffff:
|
|
f = "0x%04x"
|
|
case u <= 0xffffff:
|
|
f = "0x%06x"
|
|
case u <= 0xffffffff:
|
|
f = "0x%08x"
|
|
case u <= 0xffffffffff:
|
|
f = "0x%010x"
|
|
case u <= 0xffffffffffff:
|
|
f = "0x%012x"
|
|
case u <= 0xffffffffffffff:
|
|
f = "0x%014x"
|
|
case u <= 0xffffffffffffffff:
|
|
f = "0x%016x"
|
|
}
|
|
return fmt.Sprintf(f, u)
|
|
}
|
|
|
|
// formatPointer prints the address of the pointer.
|
|
func formatPointer(v reflect.Value) string {
|
|
p := v.Pointer()
|
|
if flags.Deterministic {
|
|
p = 0xdeadf00f // Only used for stable testing purposes
|
|
}
|
|
return fmt.Sprintf("⟪0x%x⟫", p)
|
|
}
|
|
|
|
type visitedPointers map[value.Pointer]struct{}
|
|
|
|
// Visit inserts pointer v into the visited map and reports whether it had
|
|
// already been visited before.
|
|
func (m visitedPointers) Visit(v reflect.Value) bool {
|
|
p := value.PointerOf(v)
|
|
_, visited := m[p]
|
|
m[p] = struct{}{}
|
|
return visited
|
|
}
|