You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
4.3 KiB
Go
191 lines
4.3 KiB
Go
1 year ago
|
package assert
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"git.noahlan.cn/noahlan/ntool/nfs"
|
||
|
"git.noahlan.cn/noahlan/ntool/nmath"
|
||
|
"git.noahlan.cn/noahlan/ntool/nreflect"
|
||
|
"git.noahlan.cn/noahlan/ntool/nstd/io"
|
||
|
"git.noahlan.cn/noahlan/ntool/nstr"
|
||
|
"github.com/gookit/color"
|
||
|
"reflect"
|
||
|
"runtime"
|
||
|
"strings"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
// isEmpty value check
|
||
|
func isEmpty(v any) bool {
|
||
|
if v == nil {
|
||
|
return true
|
||
|
}
|
||
|
return nreflect.IsEmpty(reflect.ValueOf(v))
|
||
|
}
|
||
|
|
||
|
func checkEqualArgs(expected, actual any) error {
|
||
|
if expected == nil && actual == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
if nreflect.IsFunc(expected) || nreflect.IsFunc(actual) {
|
||
|
return errors.New("cannot take func type as argument")
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// formatUnequalValues takes two values of arbitrary types and returns string
|
||
|
// representations appropriate to be presented to the user.
|
||
|
//
|
||
|
// If the values are not of like type, the returned strings will be prefixed
|
||
|
// with the type name, and the value will be enclosed in parentheses similar
|
||
|
// to a type conversion in the Go grammar.
|
||
|
func formatUnequalValues(expected, actual any) (e string, a string) {
|
||
|
if reflect.TypeOf(expected) != reflect.TypeOf(actual) {
|
||
|
return truncatingFormat(expected), truncatingFormat(actual)
|
||
|
// return fmt.Sprintf("%T(%s)", expected, truncatingFormat(expected)),
|
||
|
// fmt.Sprintf("%T(%s)", actual, truncatingFormat(actual))
|
||
|
}
|
||
|
|
||
|
switch expected.(type) {
|
||
|
case time.Duration:
|
||
|
return fmt.Sprintf("%v", expected), fmt.Sprintf("%v", actual)
|
||
|
}
|
||
|
|
||
|
return truncatingFormat(expected), truncatingFormat(actual)
|
||
|
}
|
||
|
|
||
|
// truncatingFormat formats the data and truncates it if it's too long.
|
||
|
//
|
||
|
// This helps keep formatted error messages lines from exceeding the
|
||
|
// bufio.MaxScanTokenSize max line length that the go testing framework imposes.
|
||
|
func truncatingFormat(data any) string {
|
||
|
if data == nil {
|
||
|
return "<nil>"
|
||
|
}
|
||
|
|
||
|
var value string
|
||
|
switch data.(type) {
|
||
|
case string:
|
||
|
value = fmt.Sprintf("string(%q)", data)
|
||
|
default:
|
||
|
value = fmt.Sprintf("%T(%v)", data, data)
|
||
|
}
|
||
|
|
||
|
// Give us some space the type info too if needed.
|
||
|
max := bufio.MaxScanTokenSize - 100
|
||
|
if len(value) > max {
|
||
|
value = value[0:max] + "<... truncated>"
|
||
|
}
|
||
|
return value
|
||
|
}
|
||
|
|
||
|
func formatTplAndArgs(fmtAndArgs ...any) string {
|
||
|
if len(fmtAndArgs) == 0 || fmtAndArgs == nil {
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
ln := len(fmtAndArgs)
|
||
|
first := fmtAndArgs[0]
|
||
|
|
||
|
if ln == 1 {
|
||
|
if msgAsStr, ok := first.(string); ok {
|
||
|
return msgAsStr
|
||
|
}
|
||
|
return fmt.Sprintf("%+v", first)
|
||
|
}
|
||
|
|
||
|
// is template string.
|
||
|
if tplStr, ok := first.(string); ok {
|
||
|
return fmt.Sprintf(tplStr, fmtAndArgs[1:]...)
|
||
|
}
|
||
|
return fmt.Sprint(fmtAndArgs...)
|
||
|
}
|
||
|
|
||
|
func callerInfos() []string {
|
||
|
num := 3
|
||
|
skip := 2
|
||
|
ss := make([]string, 0, num)
|
||
|
|
||
|
for i := skip; i < skip+num; i++ {
|
||
|
pc, file, line, ok := runtime.Caller(i)
|
||
|
if !ok {
|
||
|
// The breaks below failed to terminate the loop, and we ran off the
|
||
|
// end of the call stack.
|
||
|
break
|
||
|
}
|
||
|
|
||
|
fc := runtime.FuncForPC(pc)
|
||
|
if fc == nil {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// This is a huge edge case, but it will panic if this is the case
|
||
|
if file == "<autogenerated>" {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
fcName := fc.Name()
|
||
|
if fcName == "testing.tRunner" || strings.Contains(fcName, "goutil/testutil/assert") {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// eg: runtime.goexit
|
||
|
if strings.HasPrefix(fcName, "runtime.") {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
filePath := file
|
||
|
if !ShowFullPath {
|
||
|
filePath = nfs.Name(filePath)
|
||
|
}
|
||
|
|
||
|
ss = append(ss, fmt.Sprintf("%s:%d", filePath, line))
|
||
|
}
|
||
|
|
||
|
return ss
|
||
|
}
|
||
|
|
||
|
// refers from stretchr/testify/assert
|
||
|
type labeledText struct {
|
||
|
label string
|
||
|
message string
|
||
|
}
|
||
|
|
||
|
func formatLabeledTexts(lts []labeledText) string {
|
||
|
labelWidth := 0
|
||
|
elemSize := len(lts)
|
||
|
for _, lt := range lts {
|
||
|
labelWidth = nmath.MaxInt(len(lt.label), labelWidth)
|
||
|
}
|
||
|
|
||
|
var sb strings.Builder
|
||
|
for i, lt := range lts {
|
||
|
label := lt.label
|
||
|
if EnableColor {
|
||
|
label = color.Green.Sprint(label)
|
||
|
}
|
||
|
|
||
|
sb.WriteString(" " + label + nstr.Repeat(" ", labelWidth-len(lt.label)) + ": ")
|
||
|
formatMessage(lt.message, labelWidth, &sb)
|
||
|
if i+1 != elemSize {
|
||
|
sb.WriteByte('\n')
|
||
|
}
|
||
|
}
|
||
|
return sb.String()
|
||
|
}
|
||
|
|
||
|
func formatMessage(message string, labelWidth int, buf io.StringWriteStringer) string {
|
||
|
for i, scanner := 0, bufio.NewScanner(strings.NewReader(message)); scanner.Scan(); i++ {
|
||
|
// skip add prefix for first line.
|
||
|
if i != 0 {
|
||
|
// +3: is len of ": "
|
||
|
_, _ = buf.WriteString("\n " + strings.Repeat(" ", labelWidth+3))
|
||
|
}
|
||
|
_, _ = buf.WriteString(scanner.Text())
|
||
|
}
|
||
|
|
||
|
return buf.String()
|
||
|
}
|