package odbcmql

import (
	"context"
	"encoding/json"
	"fmt"
	"math/rand"
	"regexp"
	"strings"
	"testing"
	"time"

	odb "git.wecise.com/wecise/odb-go/odb"
	"gitee.com/wecisecode/util/cast"
	"gitee.com/wecisecode/util/merrs"
	"gitee.com/wecisecode/util/spliter"
	"github.com/stretchr/testify/assert"
)

// 将字符串中的计数标记 000000009999 替换为循环序列号
// 替换标记为 000000009999 的一部分，要求 以9结束至少5位 或 至少4位 0000
func (mt *MQLTest) ReplaceLoopSN(str string,
	global *GlobalVars,
	topvars, dirvars, filevars, mqlvars *CurrentVars,
	top_loopi, dir_loopi, file_loopi, mql_loopi int,
	basedir, ffpath, mqlkey, mqlsn string,
) string {
	// 计数标记替换
	// mt.scopevars.RLock()
	// // top_loopi := mt.scopevars.top.loop_from + (topvars.loop_i-1)*mt.scopevars.top.loop_step
	// // dir_loopi := mt.scopevars.dir[basedir].loop_from + (dirvars.loop_i-1)*mt.scopevars.dir[basedir].loop_step
	// // file_loopi := mt.scopevars.file[ffpath].loop_from + (filevars.loop_i-1)*mt.scopevars.file[ffpath].loop_step
	// // mql_loopi := mt.scopevars.mql[mqlkey].loop_from + (mqlvars.loop_i-1)*mt.scopevars.mql[mqlkey].loop_step
	// top_loopi := mt.scopevars.mql[mqlsn].vars["topi"].(int)
	// dir_loopi := mt.scopevars.mql[mqlsn].vars["diri"].(int)
	// file_loopi := mt.scopevars.mql[mqlsn].vars["filei"].(int)
	// mql_loopi := mt.scopevars.mql[mqlsn].vars["mqli"].(int)
	// mt.scopevars.RUnlock()
	// if false {
	// 	// 替换前编码，避免重复替换
	// 	str = strings.ReplaceAll(str, "&", "&=;")
	// 	str = strings.ReplaceAll(str, "0", "&0;")
	// 	str = strings.ReplaceAll(str, "9", "&9;")
	// 	// 替换 00009999
	// 	loopn := fmt.Sprintf("&0;%09d", (mql_loopi % 1000000000))
	// 	str = strings.ReplaceAll(str, "&0;&9;&9;&9;&9;&9;&9;&9;&9;&9;", loopn)
	// 	loopn = fmt.Sprintf("&0;%08d", (mql_loopi % 100000000))
	// 	str = strings.ReplaceAll(str, "&0;&9;&9;&9;&9;&9;&9;&9;&9;", loopn)
	// 	loopn = fmt.Sprintf("&0;%07d", (mql_loopi % 10000000))
	// 	str = strings.ReplaceAll(str, "&0;&9;&9;&9;&9;&9;&9;&9;", loopn)
	// 	loopn = fmt.Sprintf("&0;%06d", (mql_loopi % 1000000))
	// 	str = strings.ReplaceAll(str, "&0;&9;&9;&9;&9;&9;&9;", loopn)
	// 	loopn = fmt.Sprintf("&0;%05d", (mql_loopi % 100000))
	// 	str = strings.ReplaceAll(str, "&0;&9;&9;&9;&9;&9;", loopn)
	// 	loopn = fmt.Sprintf("&0;%04d", (mql_loopi % 10000))
	// 	str = strings.ReplaceAll(str, "&0;&9;&9;&9;&9;", loopn)
	// 	loopn = fmt.Sprintf("&0;&0;%03d", (mql_loopi % 1000))
	// 	str = strings.ReplaceAll(str, "&0;&0;&9;&9;&9;", loopn)
	// 	loopn = fmt.Sprintf("&0;&0;&0;%02d", (mql_loopi % 100))
	// 	str = strings.ReplaceAll(str, "&0;&0;&0;&9;&9;", loopn)
	// 	loopn = fmt.Sprintf("&0;&0;&0;&0;%d", (mql_loopi % 10))
	// 	str = strings.ReplaceAll(str, "&0;&0;&0;&0;&9;", loopn)
	// 	// 替换 00000000
	// 	all_1 := fmt.Sprintf("%d", (top_loopi % 10))
	// 	all_2 := fmt.Sprintf("%02d", (top_loopi % 100))
	// 	dir_1 := fmt.Sprintf("%d", (dir_loopi % 10))
	// 	dir_2 := fmt.Sprintf("%02d", (dir_loopi % 100))
	// 	file_4 := fmt.Sprintf("%04d", (file_loopi % 10000))
	// 	//
	// 	str = strings.ReplaceAll(str, "&0;&0;&0;&0;&0;&0;&0;&0;", all_2+dir_2+file_4)
	// 	str = strings.ReplaceAll(str, "&0;&0;&0;&0;&0;&0;&0;", all_1+dir_2+file_4)
	// 	str = strings.ReplaceAll(str, "&0;&0;&0;&0;&0;&0;", dir_2+file_4)
	// 	str = strings.ReplaceAll(str, "&0;&0;&0;&0;&0;", dir_1+file_4)
	// 	str = strings.ReplaceAll(str, "&0;&0;&0;&0;", file_4)
	// 	// 替换后解码
	// 	str = strings.ReplaceAll(str, "&9;", "9")
	// 	str = strings.ReplaceAll(str, "&0;", "0")
	// 	str = strings.ReplaceAll(str, "&=;", "&")
	// }
	// 正则表达式实现，内部计数变量替换
	idxs := refmtvar.FindAllStringSubmatchIndex(str, -1)
	if len(idxs) > 0 {
		nstr := ""
		is := 0
		for _, idx := range idxs {
			if len(idx) == 6 {
				// 标记前内容
				ie := idx[0]
				nstr += str[is:ie]
				// 格式化变量标记
				format := str[idx[2]:idx[3]]
				varname := str[idx[4]:idx[5]]
				if strings.HasPrefix(format, "%%") {
					// escape flag
					nstr += "{" + format[1:] + "," + varname + "}"
				} else {
					switch varname {
					case "now":
						tf := format[1:]
						switch tf {
						case "ns":
							nstr = fmt.Sprint(time.Now().UnixNano())
						case "us":
							nstr = fmt.Sprint(time.Now().UnixMicro())
						case "ms":
							nstr = fmt.Sprint(time.Now().UnixMilli())
						case "t":
							tf = "2006-01-02 15:04:05"
							nstr += time.Now().Format(tf)
						default:
							nstr += time.Now().Format(tf)
						}
					case "rand":
						nstr += fmt.Sprintf(format, rand.Intn(1000000))
					case "mqli":
						nstr += fmt.Sprintf(format, mql_loopi)
					case "filei":
						nstr += fmt.Sprintf(format, file_loopi)
					case "diri":
						nstr += fmt.Sprintf(format, dir_loopi)
					case "topi":
						nstr += fmt.Sprintf(format, top_loopi)
					case "mqlcount":
						nstr += fmt.Sprintf(format, mqlvars.mqlcount)
					case "filemqls":
						nstr += fmt.Sprintf(format, filevars.mqlcount)
					case "dirmqls":
						nstr += fmt.Sprintf(format, dirvars.mqlcount)
					case "topmqls":
						nstr += fmt.Sprintf(format, topvars.mqlcount)
					case "keyspace":
						nstr += fmt.Sprintf(format, ODBC.Config().Keyspace)
					case "ksnative":
						nstr += fmt.Sprintf(format, ksnative)
					default:
						mt.scopevars.RLock()
						v, has := mt.scopevars.mql[mqlsn].vars[varname]
						if !has {
							v, has = mt.scopevars.mql[mqlkey].vars[varname]
							if !has {
								v, has = mt.scopevars.file[ffpath].vars[varname]
								if !has {
									v, has = mt.scopevars.dir[basedir].vars[varname]
									if !has {
										v, has = mt.scopevars.top.vars[varname]
									}
								}
							}
						}
						if !has {
							nstr += "{" + format + "," + varname + "}"
						} else {
							if t, ok := v.(time.Time); ok {
								tf := format[1:]
								if tf == "t" {
									tf = "2006-01-02 15:04:05"
								}
								nstr += t.Format(tf)
							} else {
								nstr += fmt.Sprintf(format, v)
							}
						}
						mt.scopevars.RUnlock()
					}
				}
				is = idx[1]
			}
		}
		nstr += str[is:]
		str = nstr
	}
	return str
}

var refmtvar = regexp.MustCompile(`\{(%[^,]+),(\w+)\}`)
var matchall = regexp.MustCompile(".*")

func (mt *MQLTest) RunMQLTryDo(t *testing.T, ctx context.Context,
	global *GlobalVars,
	topvars *CurrentVars,
	dirvars *CurrentVars,
	filevars *CurrentVars,
	mqlvars *CurrentVars,
	basedir, ffpath, mqlkey string,
	testname string, mqri *MQLRequestInstance, values []interface{}, staticactions *StaticActions, actionexprs *ActionExprs) (err error) {
	retry_limit := staticactions.RetryLimit
	mt.scopevars.RLock()
	timeout := mt.scopevars.mql[mqlkey].timeout
	if timeout == 0 {
		timeout = mt.scopevars.file[ffpath].timeout
	}
	if timeout == 0 {
		timeout = mt.scopevars.dir[basedir].timeout
	}
	if timeout == 0 {
		timeout = mt.scopevars.top.timeout
	}
	qmeta := mt.scopevars.mql[mqlkey].qmeta
	if qmeta == nil {
		qmeta = mt.scopevars.file[ffpath].qmeta
	}
	if qmeta == nil {
		qmeta = mt.scopevars.dir[basedir].qmeta
	}
	if qmeta == nil {
		qmeta = mt.scopevars.top.qmeta
	}
	mt.scopevars.RUnlock()
	if timeout <= 0 {
		timeout = config.GetDuration("odbc.client.timeout", default_timeout)
	}
	for retry_count := 0; retry_count <= retry_limit; retry_count++ {
		tn := testname
		if retry_count > 0 {
			sleeptime := 1 * time.Second
			if staticactions.SleepTime != nil {
				sleeptime = *staticactions.SleepTime
			}
			global.sleeptime += sleeptime
			topvars.sleeptime += sleeptime
			dirvars.sleeptime += sleeptime
			filevars.sleeptime += sleeptime
			mqlvars.sleeptime += sleeptime
			time.Sleep(sleeptime)
			tn = fmt.Sprint(tn, "(retry ", retry_count, ")")
		}
		var seriouserror bool
		seriouserror, err = mt.RunMQLTryOnce(t, ctx,
			global,
			topvars,
			dirvars,
			filevars,
			mqlvars,
			tn, mqri, values, qmeta, timeout, actionexprs)
		if seriouserror || err == nil {
			// 严重错误，终止重试，直接结束
			// 没有错误，正常结束
			// 其它错误，重试
			return
		}
	}
	// 达到重试次数，仍然有错
	return
}

func (mt *MQLTest) RunMQLTryOnce(t *testing.T, ctx context.Context,
	global *GlobalVars,
	topvars *CurrentVars,
	dirvars *CurrentVars,
	filevars *CurrentVars,
	mqlvars *CurrentVars,
	testname string,
	mqri *MQLRequestInstance,
	values []interface{},
	qmeta odb.QueryMeta,
	timeout time.Duration,
	actionexprs *ActionExprs) (seriouserror bool, err error) {
	toption := actionexprs.onerrorOption
	mqlstr := mqri.PreparedQueryString
	logger.Info(fmt.Sprint("mql ", testname, ":\n", mqlstr))
	if len(values) > 0 {
		bs, _ := json.MarshalIndent(values, "", "  ")
		logger.Info("values:", string(bs))
	}
	// 去注释
	mqls := spliter.MQLSplitClean(mqlstr)
	if len(mqls) == 0 {
		mqlstr = ""
	} else {
		mqlstr = strings.Join(mqls, ";\r\n")
	}
	mqlstr = strings.TrimSpace(mqlstr)
	if mqlstr == "" {
		rtn := &odb.Result{}
		seriouserror, err = mt.doFollowThroughActions(t, testname, toption, rtn, actionexprs.FollowThroughActions(), mqri.PreparedQueryString)
		if err != nil {
			logger.Info(fmt.Sprint("mql ", testname, " done, empty mql ignore usetime"))
			return
		}
		logger.Info(fmt.Sprint("mql ", testname, " ok, empty mql ignore usetime"))
		return
	}
	ts := time.Now()
	rtn, e := ODBCQueryWithTimeoutDo(ctx, qmeta, timeout, mqlstr, values...)
	ut := time.Since(ts)
	if toption.regex[OnErrorPass] != nil {
		if e == nil {
			e = merrs.NewError(fmt.Sprint("expect error ", toption.regex[OnErrorPass].String(), ", but no error occurs"))
			assert.Nil(t, "error", e)
			// 不直接输出错误信息，返回错误信息，中断循环， 在 testing.T 中报告错误，中断测试
			return false, e
		}
		if toption.regex[OnErrorPass].MatchString(e.Error()) {
			e = nil
		}
	}
	if e != nil {
		if e == context.Canceled {
			return false, nil
		}
		if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(e.Error()) {
			// 直接输出错误信息，返回错误信息，中断循环，不在 testing.T 中报告，不中断测试
			if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
				logger.Info("error:", e)
			}
			return false, e
		}
		if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(e.Error()) {
			// 直接输出错误信息，不返回错误信息，不中断循环， 不在 testing.T 中报告错误，不中断测试
			if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
				logger.Info("pass_with_error:", e)
			}
			return false, nil
		}
		if !assert.Nil(t, "error", e) {
			// 不直接输出错误信息，返回错误信息，中断循环， 在 testing.T 中报告错误，中断测试
			return false, e
		}
		return false, nil
	}

	mt.scopevars.Lock()
	global.totalusetime += ut
	if ut > global.maxusetime {
		global.maxusetime = ut
	}
	if ut < global.minusetime || global.minusetime == 0 {
		global.minusetime = ut
	}
	topvars.totalusetime += ut
	if ut > topvars.maxusetime {
		topvars.maxusetime = ut
	}
	if ut < topvars.minusetime || topvars.minusetime == 0 {
		topvars.minusetime = ut
	}
	dirvars.totalusetime += ut
	if ut > dirvars.maxusetime {
		dirvars.maxusetime = ut
	}
	if ut < dirvars.minusetime || dirvars.minusetime == 0 {
		dirvars.minusetime = ut
	}
	filevars.totalusetime += ut
	if ut > filevars.maxusetime {
		filevars.maxusetime = ut
	}
	if ut < filevars.minusetime || filevars.minusetime == 0 {
		filevars.minusetime = ut
	}
	mqlvars.totalusetime += ut
	if ut > mqlvars.maxusetime {
		mqlvars.maxusetime = ut
	}
	if ut < mqlvars.minusetime || mqlvars.minusetime == 0 {
		mqlvars.minusetime = ut
	}
	mt.scopevars.Unlock()

	seriouserror, err = mt.doFollowThroughActions(t, testname, toption, rtn, actionexprs.FollowThroughActions(), mqlstr)
	if err != nil {
		logger.Info(fmt.Sprint("mql ", testname, " done, usetime=", ut))
		return
	}
	logger.Info(fmt.Sprint("mql ", testname, " ok, usetime=", ut))
	return
}

func (mt *MQLTest) doFollowThroughActions(t *testing.T, testname string, toption *OnErrorOption, rtn *odb.Result, actions []*Action, mql string) (seriouserror bool, err error) {
	if len(actions) > 0 {
		if rtn == nil {
			s := "返回值为空"
			err = merrs.New("%s", s)
			if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(err.Error()) {
				if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
					logger.Info("error:", err)
				}
				return
			}
			if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(err.Error()) {
				// 直接输出错误信息，不返回错误信息，不中断循环， 不在 testing.T 中报告错误，不中断测试
				if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
					logger.Info("pass_with_error:", err)
				}
				err = nil
				return
			}
			if !assert.NotNil(t, rtn, err) {
				return
			}
		}
		for _, act := range actions {
			breakup := false
			action := act.Name
			args := act.Args
			switch action {
			case "schema":
				if len(args) < 1 || strings.TrimSpace(cast.ToString(args[0])) == "" {
					// 标记语法错误
					s := "schema(C) 需要一个参数"
					err = merrs.New("%s", s)
					assert.Nil(t, s, err)
					seriouserror = true
					return
				}
				res, _ := ODBC.Command("schema", args[0]).Do()
				if res == nil || len(res.Data) == 0 {
					s := fmt.Sprintf("没有发现类 %s", args[0])
					err = merrs.New("%s", s)
					if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(err.Error()) {
						if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
							logger.Info("error:", err)
						}
						return
					}
					if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(err.Error()) {
						// 直接输出错误信息，不返回错误信息，不中断循环， 不在 testing.T 中报告错误，不中断测试
						if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
							logger.Info("pass_with_error:", err)
						}
						err = nil
						return
					}
					if !assert.Nil(t, s, err) {
						return
					}
				}
			case "metainfo":
				bs, e := json.MarshalIndent(rtn.Meta, "", "    ")
				if e != nil {
					err = merrs.New("%s", e.Error())
					if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(err.Error()) {
						if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
							logger.Info("error:", err)
						}
						return
					}
					if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(err.Error()) {
						// 直接输出错误信息，不返回错误信息，不中断循环， 不在 testing.T 中报告错误，不中断测试
						if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
							logger.Info("pass_with_error:", err)
						}
						err = nil
						return
					}
					if !assert.Nil(t, "error", err) {
						// 不直接输出错误信息，返回错误信息，中断循环， 在 testing.T 中报告错误，中断测试
						return
					}
				}
				logger.Info(fmt.Sprint("mql ", testname, " meta info:\n", string(bs)))
			case "output":
				bs, e := json.MarshalIndent(rtn.Data, "", "    ")
				if e != nil {
					err = merrs.New("%s", e.Error())
					if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(err.Error()) {
						if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
							logger.Info("error:", err)
						}
						return
					}
					if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(err.Error()) {
						// 直接输出错误信息，不返回错误信息，不中断循环， 不在 testing.T 中报告错误，不中断测试
						if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
							logger.Info("pass_with_error:", err)
						}
						err = nil
						return
					}
					if !assert.Nil(t, "error", err) {
						// 不直接输出错误信息，返回错误信息，中断循环， 在 testing.T 中报告错误，中断测试
						return
					}
				}
				total := ""
				if len(rtn.Data) > 0 {
					total = fmt.Sprint(" 共 ", len(rtn.Data), "")
				}
				logger.Info(fmt.Sprint("mql ", testname, " result:\n", string(bs), total))
			case "outputcount":
				total := fmt.Sprint(" 共 ", len(rtn.Data), "条")
				logger.Info(fmt.Sprint("mql ", testname, " result: ", total))
			case "count":
				if len(args) < 1 || strings.TrimSpace(cast.ToString(args[0])) == "" {
					s := "count(N) 需要一个参数"
					err = merrs.New("%s", s)
					assert.Nil(t, s, err)
					seriouserror = true
					return
				}
				n := cast.ToInt(strings.TrimSpace(cast.ToString(args[0])))
				if n != len(rtn.Data) {
					s := fmt.Sprint("记录数(", len(rtn.Data), ")与期望值(", n, ")不符")
					err = merrs.NewError(s, merrs.SSMaps{{"mql": mql}})
					if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(err.Error()) {
						if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
							logger.Info("error:", err)
						}
						return
					}
					if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(err.Error()) {
						// 直接输出错误信息，不返回错误信息，不中断循环， 不在 testing.T 中报告错误，不中断测试
						if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
							logger.Info("pass_with_error:", err)
						}
						err = nil
						return
					}
					if !assert.Equal(t, n, len(rtn.Data), err) {
						// 不直接输出错误信息，返回错误信息，中断循环， 在 testing.T 中报告错误，中断测试
						return
					}
				}
			case "match":
				breakup, seriouserror, err = DoActionMatch(t, args, mql, rtn, toption)
				if breakup {
					return seriouserror, err
				}
			case "matchcount":
				if len(args) < 3 {
					s := "matchcount(Kn,Mn,N) 需要至少三个参数"
					err = merrs.New("%s", s)
					assert.Nil(t, s, err)
					seriouserror = true
					return
				}
				ks := []string{}
				ms := []any{}
				i := 0
				for ; i+1 < len(args); i += 2 {
					k := strings.TrimSpace(cast.ToString(args[i]))
					ks = append(ks, k)
					var m any
					switch arg := args[i+1].(type) {
					case string:
						if regexp.MustCompile(`^\(\?[^\)]*\).*`).MatchString(arg) {
							r, e := regexp.Compile(arg)
							if e != nil {
								s := "match参数正则表达式错误：" + e.Error()
								err = merrs.New("%s", s)
								assert.Nil(t, s, err)
								seriouserror = true
								return
							}
							m = r
						}
					case *Action:
						switch arg.Name {
						case "<FUZZY>":
							m = arg
						}
					}
					if m == nil {
						m = fmt.Sprintf("%#v", args[i+1])
					}
					ms = append(ms, m)
				}
				if i >= len(args) {
					err = merrs.New("matchcount(Kn,Mn,N) 参数 Kn,Mn 需要成对出现，N为自然数")
					assert.Nil(t, "参数错误", err)
					seriouserror = true
					return
				}
				sn := strings.TrimSpace(cast.ToString(args[i]))
				n := cast.ToInt(sn)
				if fmt.Sprint(n) != sn {
					err = merrs.New("matchcount(Kn,Mn,N) 参数 Kn,Mn 需要成对出现，N为自然数 args%d:'%s'", i, args[i])
					assert.Nil(t, "参数错误", err)
					seriouserror = true
					return
				}
				x, matchingvalues := MatchValues(rtn, ks, ms)
				if n != x {
					if len(rtn.Data) == 0 {
						err = merrs.New("%s", fmt.Sprint("没有找到记录与期望值(", n, ")不符"))
					} else {
						argsbs, _ := json.MarshalIndent(args, "", "  ")
						matchingbs, _ := json.MarshalIndent(matchingvalues, "", "  ")
						err = merrs.NewError(fmt.Sprint("共", len(rtn.Data), "记录，匹配记录数(", x, ")与期望值(", n, ")不符"),
							merrs.SSMaps{{"mql": mql},
								{"matchcount args": fmt.Sprint(string(argsbs))},
								{"lastmatchingvalues": string(matchingbs)}})
					}
					if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(err.Error()) {
						if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
							logger.Info("error:", err)
						}
						return
					}
					if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(err.Error()) {
						// 直接输出错误信息，不返回错误信息，不中断循环， 不在 testing.T 中报告错误，不中断测试
						if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
							logger.Info("pass_with_error:", err)
						}
						err = nil
						return
					}
					if !assert.Equal(t, n, x, err) {
						// 不直接输出错误信息，返回错误信息，中断循环， 在 testing.T 中报告错误，中断测试
						return
					}
				}
			case "equal":
				breakup, seriouserror, err := DoActionEqual(
					t,
					toption,
					rtn,
					cast.ToStringSlice(args)...)
				if breakup {
					return seriouserror, err
				}
			default:
				err = merrs.New("%s", fmt.Sprint("unsupported action ", action))
				assert.Nil(t, "unsupported action", err)
				seriouserror = true
				return
			}
		}
	}
	return
}
