mqls_do.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. package odbcmql
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "math/rand"
  7. "regexp"
  8. "strings"
  9. "testing"
  10. "time"
  11. odb "git.wecise.com/wecise/odb-go/odb"
  12. "gitee.com/wecisecode/util/cast"
  13. "gitee.com/wecisecode/util/merrs"
  14. "gitee.com/wecisecode/util/spliter"
  15. "github.com/stretchr/testify/assert"
  16. )
  17. // 将字符串中的计数标记 000000009999 替换为循环序列号
  18. // 替换标记为 000000009999 的一部分,要求 以9结束至少5位 或 至少4位 0000
  19. func (mt *MQLTest) ReplaceLoopSN(str string,
  20. global *GlobalVars,
  21. topvars, dirvars, filevars, mqlvars *CurrentVars,
  22. top_loopi, dir_loopi, file_loopi, mql_loopi int,
  23. basedir, ffpath, mqlkey, mqlsn string,
  24. ) string {
  25. // 计数标记替换
  26. // mt.scopevars.RLock()
  27. // // top_loopi := mt.scopevars.top.loop_from + (topvars.loop_i-1)*mt.scopevars.top.loop_step
  28. // // dir_loopi := mt.scopevars.dir[basedir].loop_from + (dirvars.loop_i-1)*mt.scopevars.dir[basedir].loop_step
  29. // // file_loopi := mt.scopevars.file[ffpath].loop_from + (filevars.loop_i-1)*mt.scopevars.file[ffpath].loop_step
  30. // // mql_loopi := mt.scopevars.mql[mqlkey].loop_from + (mqlvars.loop_i-1)*mt.scopevars.mql[mqlkey].loop_step
  31. // top_loopi := mt.scopevars.mql[mqlsn].vars["topi"].(int)
  32. // dir_loopi := mt.scopevars.mql[mqlsn].vars["diri"].(int)
  33. // file_loopi := mt.scopevars.mql[mqlsn].vars["filei"].(int)
  34. // mql_loopi := mt.scopevars.mql[mqlsn].vars["mqli"].(int)
  35. // mt.scopevars.RUnlock()
  36. // if false {
  37. // // 替换前编码,避免重复替换
  38. // str = strings.ReplaceAll(str, "&", "&=;")
  39. // str = strings.ReplaceAll(str, "0", "&0;")
  40. // str = strings.ReplaceAll(str, "9", "&9;")
  41. // // 替换 00009999
  42. // loopn := fmt.Sprintf("&0;%09d", (mql_loopi % 1000000000))
  43. // str = strings.ReplaceAll(str, "&0;&9;&9;&9;&9;&9;&9;&9;&9;&9;", loopn)
  44. // loopn = fmt.Sprintf("&0;%08d", (mql_loopi % 100000000))
  45. // str = strings.ReplaceAll(str, "&0;&9;&9;&9;&9;&9;&9;&9;&9;", loopn)
  46. // loopn = fmt.Sprintf("&0;%07d", (mql_loopi % 10000000))
  47. // str = strings.ReplaceAll(str, "&0;&9;&9;&9;&9;&9;&9;&9;", loopn)
  48. // loopn = fmt.Sprintf("&0;%06d", (mql_loopi % 1000000))
  49. // str = strings.ReplaceAll(str, "&0;&9;&9;&9;&9;&9;&9;", loopn)
  50. // loopn = fmt.Sprintf("&0;%05d", (mql_loopi % 100000))
  51. // str = strings.ReplaceAll(str, "&0;&9;&9;&9;&9;&9;", loopn)
  52. // loopn = fmt.Sprintf("&0;%04d", (mql_loopi % 10000))
  53. // str = strings.ReplaceAll(str, "&0;&9;&9;&9;&9;", loopn)
  54. // loopn = fmt.Sprintf("&0;&0;%03d", (mql_loopi % 1000))
  55. // str = strings.ReplaceAll(str, "&0;&0;&9;&9;&9;", loopn)
  56. // loopn = fmt.Sprintf("&0;&0;&0;%02d", (mql_loopi % 100))
  57. // str = strings.ReplaceAll(str, "&0;&0;&0;&9;&9;", loopn)
  58. // loopn = fmt.Sprintf("&0;&0;&0;&0;%d", (mql_loopi % 10))
  59. // str = strings.ReplaceAll(str, "&0;&0;&0;&0;&9;", loopn)
  60. // // 替换 00000000
  61. // all_1 := fmt.Sprintf("%d", (top_loopi % 10))
  62. // all_2 := fmt.Sprintf("%02d", (top_loopi % 100))
  63. // dir_1 := fmt.Sprintf("%d", (dir_loopi % 10))
  64. // dir_2 := fmt.Sprintf("%02d", (dir_loopi % 100))
  65. // file_4 := fmt.Sprintf("%04d", (file_loopi % 10000))
  66. // //
  67. // str = strings.ReplaceAll(str, "&0;&0;&0;&0;&0;&0;&0;&0;", all_2+dir_2+file_4)
  68. // str = strings.ReplaceAll(str, "&0;&0;&0;&0;&0;&0;&0;", all_1+dir_2+file_4)
  69. // str = strings.ReplaceAll(str, "&0;&0;&0;&0;&0;&0;", dir_2+file_4)
  70. // str = strings.ReplaceAll(str, "&0;&0;&0;&0;&0;", dir_1+file_4)
  71. // str = strings.ReplaceAll(str, "&0;&0;&0;&0;", file_4)
  72. // // 替换后解码
  73. // str = strings.ReplaceAll(str, "&9;", "9")
  74. // str = strings.ReplaceAll(str, "&0;", "0")
  75. // str = strings.ReplaceAll(str, "&=;", "&")
  76. // }
  77. // 正则表达式实现,内部计数变量替换
  78. idxs := refmtvar.FindAllStringSubmatchIndex(str, -1)
  79. if len(idxs) > 0 {
  80. nstr := ""
  81. is := 0
  82. for _, idx := range idxs {
  83. if len(idx) == 6 {
  84. // 标记前内容
  85. ie := idx[0]
  86. nstr += str[is:ie]
  87. // 格式化变量标记
  88. format := str[idx[2]:idx[3]]
  89. varname := str[idx[4]:idx[5]]
  90. if strings.HasPrefix(format, "%%") {
  91. // escape flag
  92. nstr += "{" + format[1:] + "," + varname + "}"
  93. } else {
  94. switch varname {
  95. case "now":
  96. tf := format[1:]
  97. switch tf {
  98. case "ns":
  99. nstr = fmt.Sprint(time.Now().UnixNano())
  100. case "us":
  101. nstr = fmt.Sprint(time.Now().UnixMicro())
  102. case "ms":
  103. nstr = fmt.Sprint(time.Now().UnixMilli())
  104. case "t":
  105. tf = "2006-01-02 15:04:05"
  106. nstr += time.Now().Format(tf)
  107. default:
  108. nstr += time.Now().Format(tf)
  109. }
  110. case "rand":
  111. nstr += fmt.Sprintf(format, rand.Intn(1000000))
  112. case "mqli":
  113. nstr += fmt.Sprintf(format, mql_loopi)
  114. case "filei":
  115. nstr += fmt.Sprintf(format, file_loopi)
  116. case "diri":
  117. nstr += fmt.Sprintf(format, dir_loopi)
  118. case "topi":
  119. nstr += fmt.Sprintf(format, top_loopi)
  120. case "mqlcount":
  121. nstr += fmt.Sprintf(format, mqlvars.mqlcount)
  122. case "filemqls":
  123. nstr += fmt.Sprintf(format, filevars.mqlcount)
  124. case "dirmqls":
  125. nstr += fmt.Sprintf(format, dirvars.mqlcount)
  126. case "topmqls":
  127. nstr += fmt.Sprintf(format, topvars.mqlcount)
  128. case "keyspace":
  129. nstr += fmt.Sprintf(format, ODBC.Config().Keyspace)
  130. case "ksnative":
  131. nstr += fmt.Sprintf(format, ksnative)
  132. default:
  133. mt.scopevars.RLock()
  134. v, has := mt.scopevars.mql[mqlsn].vars[varname]
  135. if !has {
  136. v, has = mt.scopevars.mql[mqlkey].vars[varname]
  137. if !has {
  138. v, has = mt.scopevars.file[ffpath].vars[varname]
  139. if !has {
  140. v, has = mt.scopevars.dir[basedir].vars[varname]
  141. if !has {
  142. v, has = mt.scopevars.top.vars[varname]
  143. }
  144. }
  145. }
  146. }
  147. if !has {
  148. nstr += "{" + format + "," + varname + "}"
  149. } else {
  150. if t, ok := v.(time.Time); ok {
  151. tf := format[1:]
  152. if tf == "t" {
  153. tf = "2006-01-02 15:04:05"
  154. }
  155. nstr += t.Format(tf)
  156. } else {
  157. nstr += fmt.Sprintf(format, v)
  158. }
  159. }
  160. mt.scopevars.RUnlock()
  161. }
  162. }
  163. is = idx[1]
  164. }
  165. }
  166. nstr += str[is:]
  167. str = nstr
  168. }
  169. return str
  170. }
  171. var refmtvar = regexp.MustCompile(`\{(%[^,]+),(\w+)\}`)
  172. var matchall = regexp.MustCompile(".*")
  173. func (mt *MQLTest) RunMQLTryDo(t *testing.T, ctx context.Context,
  174. global *GlobalVars,
  175. topvars *CurrentVars,
  176. dirvars *CurrentVars,
  177. filevars *CurrentVars,
  178. mqlvars *CurrentVars,
  179. basedir, ffpath, mqlkey string,
  180. testname string, mqri *MQLRequestInstance, values []interface{}, staticactions *StaticActions, actionexprs *ActionExprs) (err error) {
  181. retry_limit := staticactions.RetryLimit
  182. mt.scopevars.RLock()
  183. timeout := mt.scopevars.mql[mqlkey].timeout
  184. if timeout == 0 {
  185. timeout = mt.scopevars.file[ffpath].timeout
  186. }
  187. if timeout == 0 {
  188. timeout = mt.scopevars.dir[basedir].timeout
  189. }
  190. if timeout == 0 {
  191. timeout = mt.scopevars.top.timeout
  192. }
  193. qmeta := mt.scopevars.mql[mqlkey].qmeta
  194. if qmeta == nil {
  195. qmeta = mt.scopevars.file[ffpath].qmeta
  196. }
  197. if qmeta == nil {
  198. qmeta = mt.scopevars.dir[basedir].qmeta
  199. }
  200. if qmeta == nil {
  201. qmeta = mt.scopevars.top.qmeta
  202. }
  203. mt.scopevars.RUnlock()
  204. if timeout <= 0 {
  205. timeout = 1 * time.Minute
  206. }
  207. for retry_count := 0; retry_count <= retry_limit; retry_count++ {
  208. tn := testname
  209. if retry_count > 0 {
  210. sleeptime := 1 * time.Second
  211. if staticactions.SleepTime != nil {
  212. sleeptime = *staticactions.SleepTime
  213. }
  214. global.sleeptime += sleeptime
  215. topvars.sleeptime += sleeptime
  216. dirvars.sleeptime += sleeptime
  217. filevars.sleeptime += sleeptime
  218. mqlvars.sleeptime += sleeptime
  219. time.Sleep(sleeptime)
  220. tn = fmt.Sprint(tn, "(retry ", retry_count, ")")
  221. }
  222. var seriouserror bool
  223. seriouserror, err = mt.RunMQLTryOnce(t, ctx,
  224. global,
  225. topvars,
  226. dirvars,
  227. filevars,
  228. mqlvars,
  229. tn, mqri, values, qmeta, timeout, actionexprs)
  230. if seriouserror || err == nil {
  231. // 严重错误,终止重试,直接结束
  232. // 没有错误,正常结束
  233. // 其它错误,重试
  234. return
  235. }
  236. }
  237. // 达到重试次数,仍然有错
  238. return
  239. }
  240. func (mt *MQLTest) RunMQLTryOnce(t *testing.T, ctx context.Context,
  241. global *GlobalVars,
  242. topvars *CurrentVars,
  243. dirvars *CurrentVars,
  244. filevars *CurrentVars,
  245. mqlvars *CurrentVars,
  246. testname string,
  247. mqri *MQLRequestInstance,
  248. values []interface{},
  249. qmeta odb.QueryMeta,
  250. timeout time.Duration,
  251. actionexprs *ActionExprs) (seriouserror bool, err error) {
  252. toption := actionexprs.onerrorOption
  253. mqlstr := mqri.PreparedQueryString
  254. logger.Info(fmt.Sprint("mql ", testname, ":\n", mqlstr))
  255. if len(values) > 0 {
  256. bs, _ := json.MarshalIndent(values, "", " ")
  257. logger.Info("values:", string(bs))
  258. }
  259. // 去注释
  260. mqls := spliter.MQLSplitClean(mqlstr)
  261. if len(mqls) == 0 {
  262. mqlstr = ""
  263. } else {
  264. mqlstr = mqls[0]
  265. }
  266. mqlstr = strings.TrimSpace(mqlstr)
  267. if mqlstr == "" {
  268. rtn := &odb.Result{}
  269. seriouserror, err = mt.doFollowThroughActions(t, testname, toption, rtn, actionexprs.FollowThroughActions(), mqri.PreparedQueryString)
  270. if err != nil {
  271. logger.Info(fmt.Sprint("mql ", testname, " done, empty mql ignore usetime"))
  272. return
  273. }
  274. logger.Info(fmt.Sprint("mql ", testname, " ok, empty mql ignore usetime"))
  275. return
  276. }
  277. ts := time.Now()
  278. rtn, e := ODBCQueryWithTimeoutDo(ctx, qmeta, timeout, mqlstr, values...)
  279. ut := time.Since(ts)
  280. if toption.regex[OnErrorPass] != nil {
  281. if e == nil {
  282. e = merrs.NewError(fmt.Sprint("expect error ", toption.regex[OnErrorPass].String(), ", but no error occurs"))
  283. assert.Nil(t, "error", e)
  284. // 不直接输出错误信息,返回错误信息,中断循环, 在 testing.T 中报告错误,中断测试
  285. return false, e
  286. }
  287. if toption.regex[OnErrorPass].MatchString(e.Error()) {
  288. e = nil
  289. }
  290. }
  291. if e != nil {
  292. if e == context.Canceled {
  293. return false, nil
  294. }
  295. if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(e.Error()) {
  296. // 直接输出错误信息,返回错误信息,中断循环,不在 testing.T 中报告,不中断测试
  297. if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
  298. logger.Info("error:", e)
  299. }
  300. return false, e
  301. }
  302. if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(e.Error()) {
  303. // 直接输出错误信息,不返回错误信息,不中断循环, 不在 testing.T 中报告错误,不中断测试
  304. if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
  305. logger.Info("pass_with_error:", e)
  306. }
  307. return false, nil
  308. }
  309. if !assert.Nil(t, "error", e) {
  310. // 不直接输出错误信息,返回错误信息,中断循环, 在 testing.T 中报告错误,中断测试
  311. return false, e
  312. }
  313. return false, nil
  314. }
  315. mt.scopevars.Lock()
  316. global.totalusetime += ut
  317. if ut > global.maxusetime {
  318. global.maxusetime = ut
  319. }
  320. if ut < global.minusetime || global.minusetime == 0 {
  321. global.minusetime = ut
  322. }
  323. topvars.totalusetime += ut
  324. if ut > topvars.maxusetime {
  325. topvars.maxusetime = ut
  326. }
  327. if ut < topvars.minusetime || topvars.minusetime == 0 {
  328. topvars.minusetime = ut
  329. }
  330. dirvars.totalusetime += ut
  331. if ut > dirvars.maxusetime {
  332. dirvars.maxusetime = ut
  333. }
  334. if ut < dirvars.minusetime || dirvars.minusetime == 0 {
  335. dirvars.minusetime = ut
  336. }
  337. filevars.totalusetime += ut
  338. if ut > filevars.maxusetime {
  339. filevars.maxusetime = ut
  340. }
  341. if ut < filevars.minusetime || filevars.minusetime == 0 {
  342. filevars.minusetime = ut
  343. }
  344. mqlvars.totalusetime += ut
  345. if ut > mqlvars.maxusetime {
  346. mqlvars.maxusetime = ut
  347. }
  348. if ut < mqlvars.minusetime || mqlvars.minusetime == 0 {
  349. mqlvars.minusetime = ut
  350. }
  351. mt.scopevars.Unlock()
  352. seriouserror, err = mt.doFollowThroughActions(t, testname, toption, rtn, actionexprs.FollowThroughActions(), mqlstr)
  353. if err != nil {
  354. logger.Info(fmt.Sprint("mql ", testname, " done, usetime=", ut))
  355. return
  356. }
  357. logger.Info(fmt.Sprint("mql ", testname, " ok, usetime=", ut))
  358. return
  359. }
  360. func (mt *MQLTest) doFollowThroughActions(t *testing.T, testname string, toption *OnErrorOption, rtn *odb.Result, actions []*Action, mql string) (seriouserror bool, err error) {
  361. if len(actions) > 0 {
  362. if rtn == nil {
  363. s := "返回值为空"
  364. err = merrs.New("%s", s)
  365. if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(err.Error()) {
  366. if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
  367. logger.Info("error:", err)
  368. }
  369. return
  370. }
  371. if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(err.Error()) {
  372. // 直接输出错误信息,不返回错误信息,不中断循环, 不在 testing.T 中报告错误,不中断测试
  373. if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
  374. logger.Info("pass_with_error:", err)
  375. }
  376. err = nil
  377. return
  378. }
  379. if !assert.NotNil(t, rtn, err) {
  380. return
  381. }
  382. }
  383. for _, act := range actions {
  384. breakup := false
  385. action := act.Name
  386. args := act.Args
  387. switch action {
  388. case "schema":
  389. if len(args) < 1 || strings.TrimSpace(cast.ToString(args[0])) == "" {
  390. // 标记语法错误
  391. s := "schema(C) 需要一个参数"
  392. err = merrs.New("%s", s)
  393. assert.Nil(t, s, err)
  394. seriouserror = true
  395. return
  396. }
  397. res, _ := ODBC.Command("schema", args[0]).Do()
  398. if res == nil || len(res.Data) == 0 {
  399. s := fmt.Sprintf("没有发现类 %s", args[0])
  400. err = merrs.New("%s", s)
  401. if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(err.Error()) {
  402. if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
  403. logger.Info("error:", err)
  404. }
  405. return
  406. }
  407. if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(err.Error()) {
  408. // 直接输出错误信息,不返回错误信息,不中断循环, 不在 testing.T 中报告错误,不中断测试
  409. if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
  410. logger.Info("pass_with_error:", err)
  411. }
  412. err = nil
  413. return
  414. }
  415. if !assert.Nil(t, s, err) {
  416. return
  417. }
  418. }
  419. case "metainfo":
  420. bs, e := json.MarshalIndent(rtn.Meta, "", " ")
  421. if e != nil {
  422. err = merrs.New("%s", e.Error())
  423. if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(err.Error()) {
  424. if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
  425. logger.Info("error:", err)
  426. }
  427. return
  428. }
  429. if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(err.Error()) {
  430. // 直接输出错误信息,不返回错误信息,不中断循环, 不在 testing.T 中报告错误,不中断测试
  431. if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
  432. logger.Info("pass_with_error:", err)
  433. }
  434. err = nil
  435. return
  436. }
  437. if !assert.Nil(t, "error", err) {
  438. // 不直接输出错误信息,返回错误信息,中断循环, 在 testing.T 中报告错误,中断测试
  439. return
  440. }
  441. }
  442. logger.Info(fmt.Sprint("mql ", testname, " meta info:\n", string(bs)))
  443. case "output":
  444. bs, e := json.MarshalIndent(rtn.Data, "", " ")
  445. if e != nil {
  446. err = merrs.New("%s", e.Error())
  447. if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(err.Error()) {
  448. if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
  449. logger.Info("error:", err)
  450. }
  451. return
  452. }
  453. if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(err.Error()) {
  454. // 直接输出错误信息,不返回错误信息,不中断循环, 不在 testing.T 中报告错误,不中断测试
  455. if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
  456. logger.Info("pass_with_error:", err)
  457. }
  458. err = nil
  459. return
  460. }
  461. if !assert.Nil(t, "error", err) {
  462. // 不直接输出错误信息,返回错误信息,中断循环, 在 testing.T 中报告错误,中断测试
  463. return
  464. }
  465. }
  466. total := ""
  467. if len(rtn.Data) > 0 {
  468. total = fmt.Sprint(" 共 ", len(rtn.Data), "")
  469. }
  470. logger.Info(fmt.Sprint("mql ", testname, " result:\n", string(bs), total))
  471. case "outputcount":
  472. total := fmt.Sprint(" 共 ", len(rtn.Data), "条")
  473. logger.Info(fmt.Sprint("mql ", testname, " result: ", total))
  474. case "count":
  475. if len(args) < 1 || strings.TrimSpace(cast.ToString(args[0])) == "" {
  476. s := "count(N) 需要一个参数"
  477. err = merrs.New("%s", s)
  478. assert.Nil(t, s, err)
  479. seriouserror = true
  480. return
  481. }
  482. n := cast.ToInt(strings.TrimSpace(cast.ToString(args[0])))
  483. if n != len(rtn.Data) {
  484. s := fmt.Sprint("记录数(", len(rtn.Data), ")与期望值(", n, ")不符")
  485. err = merrs.NewError(s, merrs.SSMaps{{"mql": mql}})
  486. if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(err.Error()) {
  487. if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
  488. logger.Info("error:", err)
  489. }
  490. return
  491. }
  492. if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(err.Error()) {
  493. // 直接输出错误信息,不返回错误信息,不中断循环, 不在 testing.T 中报告错误,不中断测试
  494. if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
  495. logger.Info("pass_with_error:", err)
  496. }
  497. err = nil
  498. return
  499. }
  500. if !assert.Equal(t, n, len(rtn.Data), err) {
  501. // 不直接输出错误信息,返回错误信息,中断循环, 在 testing.T 中报告错误,中断测试
  502. return
  503. }
  504. }
  505. case "match":
  506. breakup, seriouserror, err = DoActionMatch(t, args, mql, rtn, toption)
  507. if breakup {
  508. return seriouserror, err
  509. }
  510. case "matchcount":
  511. if len(args) < 3 {
  512. s := "matchcount(Kn,Mn,N) 需要至少三个参数"
  513. err = merrs.New("%s", s)
  514. assert.Nil(t, s, err)
  515. seriouserror = true
  516. return
  517. }
  518. ks := []string{}
  519. ms := []any{}
  520. i := 0
  521. for ; i+1 < len(args); i += 2 {
  522. k := strings.TrimSpace(cast.ToString(args[i]))
  523. ks = append(ks, k)
  524. var m any
  525. switch arg := args[i+1].(type) {
  526. case string:
  527. if regexp.MustCompile(`^\(\?[^\)]*\).*`).MatchString(arg) {
  528. r, e := regexp.Compile(arg)
  529. if e != nil {
  530. s := "match参数正则表达式错误:" + e.Error()
  531. err = merrs.New("%s", s)
  532. assert.Nil(t, s, err)
  533. seriouserror = true
  534. return
  535. }
  536. m = r
  537. }
  538. case *Action:
  539. switch arg.Name {
  540. case "<FUZZY>":
  541. m = arg
  542. }
  543. }
  544. if m == nil {
  545. m = fmt.Sprintf("%#v", args[i+1])
  546. }
  547. ms = append(ms, m)
  548. }
  549. if i >= len(args) {
  550. err = merrs.New("matchcount(Kn,Mn,N) 参数 Kn,Mn 需要成对出现,N为自然数")
  551. assert.Nil(t, "参数错误", err)
  552. seriouserror = true
  553. return
  554. }
  555. sn := strings.TrimSpace(cast.ToString(args[i]))
  556. n := cast.ToInt(sn)
  557. if fmt.Sprint(n) != sn {
  558. err = merrs.New("matchcount(Kn,Mn,N) 参数 Kn,Mn 需要成对出现,N为自然数 args%d:'%s'", i, args[i])
  559. assert.Nil(t, "参数错误", err)
  560. seriouserror = true
  561. return
  562. }
  563. x, matchingvalues := MatchValues(rtn, ks, ms)
  564. if n != x {
  565. if len(rtn.Data) == 0 {
  566. err = merrs.New("%s", fmt.Sprint("没有找到记录与期望值(", n, ")不符"))
  567. } else {
  568. argsbs, _ := json.MarshalIndent(args, "", " ")
  569. matchingbs, _ := json.MarshalIndent(matchingvalues, "", " ")
  570. err = merrs.NewError(fmt.Sprint("共", len(rtn.Data), "记录,匹配记录数(", x, ")与期望值(", n, ")不符"),
  571. merrs.SSMaps{{"mql": mql},
  572. {"matchcount args": fmt.Sprint(string(argsbs))},
  573. {"lastmatchingvalues": string(matchingbs)}})
  574. }
  575. if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(err.Error()) {
  576. if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
  577. logger.Info("error:", err)
  578. }
  579. return
  580. }
  581. if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(err.Error()) {
  582. // 直接输出错误信息,不返回错误信息,不中断循环, 不在 testing.T 中报告错误,不中断测试
  583. if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
  584. logger.Info("pass_with_error:", err)
  585. }
  586. err = nil
  587. return
  588. }
  589. if !assert.Equal(t, n, x, err) {
  590. // 不直接输出错误信息,返回错误信息,中断循环, 在 testing.T 中报告错误,中断测试
  591. return
  592. }
  593. }
  594. case "equal":
  595. breakup, seriouserror, err := DoActionEqual(
  596. t,
  597. toption,
  598. rtn,
  599. cast.ToStringSlice(args)...)
  600. if breakup {
  601. return seriouserror, err
  602. }
  603. default:
  604. err = merrs.New("%s", fmt.Sprint("unsupported action ", action))
  605. assert.Nil(t, "unsupported action", err)
  606. seriouserror = true
  607. return
  608. }
  609. }
  610. }
  611. return
  612. }