2019-10-30 02:01:42 +01:00
|
|
|
package check
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"reflect"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"unicode/utf8"
|
|
|
|
|
|
|
|
"github.com/davecgh/go-spew/spew"
|
|
|
|
"github.com/pmezard/go-difflib/difflib"
|
|
|
|
)
|
|
|
|
|
2021-07-03 10:56:53 +02:00
|
|
|
//nolint:gochecknoglobals // Const.
|
2019-10-30 02:01:42 +01:00
|
|
|
var spewCfg = spew.ConfigState{
|
|
|
|
Indent: " ",
|
|
|
|
DisablePointerAddresses: true,
|
|
|
|
DisableCapacities: true,
|
|
|
|
SortKeys: true,
|
|
|
|
SpewKeys: true,
|
|
|
|
}
|
|
|
|
|
|
|
|
type dump struct {
|
|
|
|
dump string
|
|
|
|
indirectType reflect.Type
|
|
|
|
}
|
|
|
|
|
|
|
|
// String returns dump of value given to newDump.
|
|
|
|
func (v dump) String() string {
|
|
|
|
return v.dump
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v dump) diff(expected dump) string {
|
|
|
|
if v.indirectType != expected.indirectType {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
if !strings.ContainsRune(v.dump[:len(v.dump)-1], '\n') &&
|
|
|
|
!strings.ContainsRune(expected.dump[:len(expected.dump)-1], '\n') {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
diff, err := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
|
|
|
|
A: difflib.SplitLines(expected.dump),
|
|
|
|
B: difflib.SplitLines(v.dump),
|
|
|
|
FromFile: "Expected",
|
|
|
|
FromDate: "",
|
|
|
|
ToFile: "Actual",
|
|
|
|
ToDate: "",
|
|
|
|
Context: 1,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return "Diff:\n" + diff
|
|
|
|
}
|
|
|
|
|
|
|
|
// newDump prepare i dump using spew.Sdump in most cases and custom
|
|
|
|
// improved dump for these cases:
|
|
|
|
// - nil: remove "(interface{})" prefix
|
|
|
|
// - byte: use 0xFF instead of decimal
|
|
|
|
// - rune: use quoted char instead of number for valid runes
|
|
|
|
// - string: use this instead of quoted single-line:
|
|
|
|
// - valid utf8: don't quote ", show multiline strings on separate lines
|
|
|
|
// - invalid utf8: use hexdump like for []byte
|
|
|
|
// - []byte: same as string instead of hexdump for valid utf8
|
|
|
|
// - []rune: use quoted char instead of number for valid runes in list
|
2021-07-03 10:56:53 +02:00
|
|
|
// - json.RawMessage: indent, then same as string.
|
|
|
|
func newDump(i interface{}) (d dump) { //nolint:gocyclo,gocognit,funlen,cyclop // By design.
|
2019-10-30 02:01:42 +01:00
|
|
|
d.dump = spewCfg.Sdump(i)
|
|
|
|
|
|
|
|
if i == nil {
|
|
|
|
d.dump = "<nil>\n"
|
|
|
|
return d
|
|
|
|
}
|
|
|
|
|
|
|
|
val := reflect.ValueOf(i)
|
|
|
|
typ := reflect.TypeOf(i)
|
|
|
|
kind := typ.Kind()
|
|
|
|
if kind == reflect.Ptr {
|
|
|
|
if val.IsNil() {
|
|
|
|
return d
|
|
|
|
}
|
|
|
|
val = val.Elem()
|
|
|
|
typ = typ.Elem()
|
|
|
|
kind = typ.Kind()
|
|
|
|
}
|
|
|
|
d.indirectType = typ
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case typ == reflect.TypeOf(json.RawMessage(nil)):
|
|
|
|
v := val.Bytes()
|
|
|
|
var buf bytes.Buffer
|
|
|
|
if json.Indent(&buf, v, "", " ") == nil {
|
|
|
|
d.dump = fmt.Sprintf("(%T) (len=%d) '\n%s\n'\n", i, len(v), buf.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
case kind == reflect.Uint8:
|
|
|
|
v := byte(val.Uint())
|
|
|
|
d.dump = fmt.Sprintf("(%T) 0x%02X\n", i, v)
|
|
|
|
|
|
|
|
case kind == reflect.Int32:
|
|
|
|
v := rune(val.Int())
|
|
|
|
if utf8.ValidRune(v) {
|
|
|
|
d.dump = fmt.Sprintf("(%T) %q\n", i, v)
|
|
|
|
}
|
|
|
|
|
|
|
|
case kind == reflect.Slice && typ.Elem().Kind() == reflect.Int32:
|
|
|
|
valid := true
|
|
|
|
for k := 0; k < val.Len() && valid; k++ {
|
|
|
|
valid = valid && utf8.ValidRune(rune(val.Index(k).Int()))
|
|
|
|
}
|
|
|
|
if valid {
|
|
|
|
d.dump = fmt.Sprintf("(%T) %q\n", i, i)
|
|
|
|
}
|
|
|
|
|
|
|
|
case kind == reflect.String:
|
|
|
|
v := val.String()
|
|
|
|
if utf8.ValidString(v) {
|
|
|
|
d.dump = fmt.Sprintf("(%T) (len=%d) %s\n", i, len(v), quote(v))
|
|
|
|
} else {
|
|
|
|
d.dump = strings.Replace(spewCfg.Sdump([]byte(v)), "([]uint8)", fmt.Sprintf("(%T)", i), 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
case kind == reflect.Slice && typ.Elem().Kind() == reflect.Uint8:
|
|
|
|
v := val.Bytes()
|
|
|
|
if len(v) > 0 && utf8.Valid(v) || len(v) == 0 && !val.IsNil() {
|
|
|
|
d.dump = fmt.Sprintf("(%T) (len=%d) %s\n", i, len(v), quote(string(v)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return d
|
|
|
|
}
|
|
|
|
|
|
|
|
// quote like %#v, except keep \n and " unquoted for readability.
|
|
|
|
func quote(s string) string {
|
|
|
|
r := []rune(strconv.Quote(s))
|
|
|
|
q := r[:0]
|
|
|
|
var multiline, esc bool
|
|
|
|
for _, c := range r[1 : len(r)-1] {
|
|
|
|
if esc {
|
|
|
|
esc = false
|
|
|
|
switch c {
|
|
|
|
case 'n':
|
|
|
|
c = '\n'
|
|
|
|
multiline = true
|
|
|
|
case '"':
|
|
|
|
default:
|
|
|
|
q = append(q, '\\')
|
|
|
|
}
|
|
|
|
} else if c == '\\' {
|
|
|
|
esc = true
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
q = append(q, c)
|
|
|
|
}
|
|
|
|
if multiline {
|
|
|
|
return fmt.Sprintf("'\n%s\n'", string(q))
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("'%s'", string(q))
|
|
|
|
}
|