| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706 |
- package mql
- 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 "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 = 1 * time.Minute
- }
- 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 = mqls[0]
- }
- 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 matchvalue(matcher any, matchingvalue any) (bool, string) {
- switch m := matcher.(type) {
- case *regexp.Regexp:
- switch v := matchingvalue.(type) {
- case string:
- return m.MatchString(v), v
- default:
- sv := fmt.Sprintf("%#v", matchingvalue)
- return m.MatchString(sv), sv
- }
- case string:
- sv := ""
- switch matchingvalue.(type) {
- case int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64, float32, float64, bool:
- sv = fmt.Sprintf("%v", matchingvalue)
- default:
- sv = fmt.Sprintf("%#v", matchingvalue)
- }
- return sv == m, sv
- default:
- panic("未定义的匹配类型")
- }
- }
- 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
- }
- }
- 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 := 0
- var matchingvalues [][]any
- var matchingvalue any
- for _, dat := range rtn.Data {
- matchingvalues = append(matchingvalues, []any{})
- m := false
- sv := ""
- match := true
- for i, k := range ks {
- matchingvalue = dat[k]
- m, sv = matchvalue(ms[i], matchingvalue)
- if !m {
- ks := strings.Split(k, ".")
- if len(ks) <= 1 {
- match = false
- break
- } else {
- k := ""
- for i := 0; match && i < len(ks); i++ {
- if matchingvalue == nil {
- if k != "" {
- k += "."
- }
- k += ks[i]
- matchingvalue = dat[k]
- if matchingvalue == nil && ks[i] == "len" {
- matchingvalue = 0
- }
- } else {
- switch mv := matchingvalue.(type) {
- case map[string]interface{}:
- matchingvalue = mv[ks[i]]
- if matchingvalue == nil && ks[i] == "len" {
- matchingvalue = len(mv)
- }
- case []interface{}:
- if ks[i] == "len" {
- matchingvalue = len(mv)
- } else {
- n, e := cast.ToIntE(ks[i])
- if e != nil {
- match = false
- break
- }
- matchingvalue = mv[n]
- }
- case string:
- if ks[i] == "len" {
- matchingvalue = len(mv)
- } else {
- n, e := cast.ToIntE(ks[i])
- if e != nil {
- match = false
- break
- }
- matchingvalue = mv[n]
- }
- default:
- if ks[i] == "len" {
- matchingvalue = 0
- } else {
- matchingvalue = nil
- }
- }
- }
- }
- m, sv = matchvalue(ms[i], matchingvalue)
- if !m {
- match = false
- break
- }
- }
- }
- matchingvalues[len(matchingvalues)-1] = append(matchingvalues[len(matchingvalues)-1], sv)
- }
- if match {
- x++
- matchingvalues = matchingvalues[:len(matchingvalues)-1]
- } else {
- matchingvalues[len(matchingvalues)-1] = append(matchingvalues[len(matchingvalues)-1], sv)
- }
- }
- 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
- }
|