mqls_do.go 20 KB

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