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.
ntool/ntest/assert/util.go

191 lines
4.3 KiB
Go

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()
}