mqls_do.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  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 = config.GetDuration("odbc.client.timeout", default_timeout)
  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. report := retry_count >= retry_limit
  224. seriouserror, err = mt.RunMQLTryOnce(t, report, ctx,
  225. global,
  226. topvars,
  227. dirvars,
  228. filevars,
  229. mqlvars,
  230. tn, mqri, values, qmeta, timeout, actionexprs)
  231. if seriouserror || err == nil {
  232. // 严重错误,终止重试,直接结束
  233. // 没有错误,正常结束
  234. // 其它错误,重试
  235. return
  236. }
  237. }
  238. // 达到重试次数,仍然有错
  239. return
  240. }
  241. func (mt *MQLTest) RunMQLTryOnce(t *testing.T, report bool, ctx context.Context,
  242. global *GlobalVars,
  243. topvars *CurrentVars,
  244. dirvars *CurrentVars,
  245. filevars *CurrentVars,
  246. mqlvars *CurrentVars,
  247. testname string,
  248. mqri *MQLRequestInstance,
  249. values []interface{},
  250. qmeta odb.QueryMeta,
  251. timeout time.Duration,
  252. actionexprs *ActionExprs) (seriouserror bool, err error) {
  253. toption := actionexprs.onerrorOption
  254. mqlstr := mqri.PreparedQueryString
  255. logger.Info(fmt.Sprint("mql ", testname, ":\n", mqlstr))
  256. if len(values) > 0 {
  257. bs, _ := json.MarshalIndent(values, "", " ")
  258. logger.Info("values:", string(bs))
  259. }
  260. // 去注释
  261. mqls := spliter.MQLSplitClean(mqlstr)
  262. if len(mqls) == 0 {
  263. mqlstr = ""
  264. } else {
  265. mqlstr = strings.Join(mqls, ";\r\n")
  266. }
  267. mqlstr = strings.TrimSpace(mqlstr)
  268. if mqlstr == "" {
  269. rtn := &odb.Result{}
  270. seriouserror, err = mt.doFollowThroughActions(t, report, testname, toption, rtn, actionexprs.FollowThroughActions(), mqri.PreparedQueryString)
  271. if err != nil {
  272. logger.Info(fmt.Sprint("mql ", testname, " done, empty mql ignore usetime"))
  273. return
  274. }
  275. logger.Info(fmt.Sprint("mql ", testname, " ok, empty mql ignore usetime"))
  276. return
  277. }
  278. ts := time.Now()
  279. rtn, e := ODBCQueryWithTimeoutDo(ctx, qmeta, timeout, mqlstr, values...)
  280. ut := time.Since(ts)
  281. if toption.regex[OnErrorPass] != nil {
  282. if e == nil {
  283. e = merrs.NewError(fmt.Sprint("expect error ", toption.regex[OnErrorPass].String(), ", but no error occurs"))
  284. if report {
  285. assert.Nil(t, "error", e)
  286. }
  287. return false, e
  288. }
  289. if toption.regex[OnErrorPass].MatchString(e.Error()) {
  290. e = nil
  291. }
  292. }
  293. if e != nil {
  294. if e == context.Canceled {
  295. return false, nil
  296. }
  297. if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(e.Error()) {
  298. // 直接输出错误信息,返回错误信息,中断循环,不在 testing.T 中报告,不中断测试
  299. if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
  300. logger.Info("error:", e)
  301. }
  302. return false, e
  303. }
  304. if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(e.Error()) {
  305. // 直接输出错误信息,不返回错误信息,不中断循环, 不在 testing.T 中报告错误,不中断测试
  306. if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
  307. logger.Info("pass_with_error:", e)
  308. }
  309. return false, nil
  310. }
  311. if report {
  312. assert.Nil(t, "error", e)
  313. }
  314. return false, e
  315. }
  316. mt.scopevars.Lock()
  317. global.totalusetime += ut
  318. if ut > global.maxusetime {
  319. global.maxusetime = ut
  320. }
  321. if ut < global.minusetime || global.minusetime == 0 {
  322. global.minusetime = ut
  323. }
  324. topvars.totalusetime += ut
  325. if ut > topvars.maxusetime {
  326. topvars.maxusetime = ut
  327. }
  328. if ut < topvars.minusetime || topvars.minusetime == 0 {
  329. topvars.minusetime = ut
  330. }
  331. dirvars.totalusetime += ut
  332. if ut > dirvars.maxusetime {
  333. dirvars.maxusetime = ut
  334. }
  335. if ut < dirvars.minusetime || dirvars.minusetime == 0 {
  336. dirvars.minusetime = ut
  337. }
  338. filevars.totalusetime += ut
  339. if ut > filevars.maxusetime {
  340. filevars.maxusetime = ut
  341. }
  342. if ut < filevars.minusetime || filevars.minusetime == 0 {
  343. filevars.minusetime = ut
  344. }
  345. mqlvars.totalusetime += ut
  346. if ut > mqlvars.maxusetime {
  347. mqlvars.maxusetime = ut
  348. }
  349. if ut < mqlvars.minusetime || mqlvars.minusetime == 0 {
  350. mqlvars.minusetime = ut
  351. }
  352. mt.scopevars.Unlock()
  353. seriouserror, err = mt.doFollowThroughActions(t, report, testname, toption, rtn, actionexprs.FollowThroughActions(), mqlstr)
  354. if err != nil {
  355. logger.Info(fmt.Sprint("mql ", testname, " done, usetime=", ut))
  356. return
  357. }
  358. logger.Info(fmt.Sprint("mql ", testname, " ok, usetime=", ut))
  359. return
  360. }
  361. func (mt *MQLTest) doFollowThroughActions(t *testing.T, report bool, testname string, toption *OnErrorOption, rtn *odb.Result, actions []*Action, mql string) (seriouserror bool, err error) {
  362. if len(actions) > 0 {
  363. if rtn == nil {
  364. s := "返回值为空"
  365. err = merrs.New("%s", s)
  366. if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(err.Error()) {
  367. if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
  368. logger.Info("error:", err)
  369. }
  370. return
  371. }
  372. if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(err.Error()) {
  373. // 直接输出错误信息,不返回错误信息,不中断循环, 不在 testing.T 中报告错误,不中断测试
  374. if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
  375. logger.Info("pass_with_error:", err)
  376. }
  377. err = nil
  378. return
  379. }
  380. if report {
  381. assert.NotNil(t, rtn, err)
  382. }
  383. return
  384. }
  385. for _, act := range actions {
  386. breakup := false
  387. action := act.Name
  388. args := act.Args
  389. switch action {
  390. case "schema":
  391. if len(args) < 1 || strings.TrimSpace(cast.ToString(args[0])) == "" {
  392. // 标记语法错误
  393. s := "schema(C) 需要一个参数"
  394. err = merrs.New("%s", s)
  395. assert.Nil(t, s, err)
  396. seriouserror = true
  397. return
  398. }
  399. res, _ := ODBC.Command("schema", args[0]).Do()
  400. if res == nil || len(res.Data) == 0 {
  401. s := fmt.Sprintf("没有发现类 %s", args[0])
  402. err = merrs.New("%s", s)
  403. if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(err.Error()) {
  404. if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
  405. logger.Info("error:", err)
  406. }
  407. return
  408. }
  409. if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(err.Error()) {
  410. // 直接输出错误信息,不返回错误信息,不中断循环, 不在 testing.T 中报告错误,不中断测试
  411. if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
  412. logger.Info("pass_with_error:", err)
  413. }
  414. err = nil
  415. return
  416. }
  417. if report {
  418. assert.Nil(t, s, err)
  419. }
  420. return
  421. }
  422. case "metainfo":
  423. bs, e := json.MarshalIndent(rtn.Meta, "", " ")
  424. if e != nil {
  425. err = merrs.New("%s", e.Error())
  426. if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(err.Error()) {
  427. if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
  428. logger.Info("error:", err)
  429. }
  430. return
  431. }
  432. if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(err.Error()) {
  433. // 直接输出错误信息,不返回错误信息,不中断循环, 不在 testing.T 中报告错误,不中断测试
  434. if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
  435. logger.Info("pass_with_error:", err)
  436. }
  437. err = nil
  438. return
  439. }
  440. if report {
  441. assert.Nil(t, "error", err)
  442. }
  443. return
  444. }
  445. logger.Info(fmt.Sprint("mql ", testname, " meta info:\n", string(bs)))
  446. case "output":
  447. bs, e := json.MarshalIndent(rtn.Data, "", " ")
  448. if e != nil {
  449. err = merrs.New("%s", e.Error())
  450. if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(err.Error()) {
  451. if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
  452. logger.Info("error:", err)
  453. }
  454. return
  455. }
  456. if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(err.Error()) {
  457. // 直接输出错误信息,不返回错误信息,不中断循环, 不在 testing.T 中报告错误,不中断测试
  458. if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
  459. logger.Info("pass_with_error:", err)
  460. }
  461. err = nil
  462. return
  463. }
  464. if report {
  465. assert.Nil(t, "error", err)
  466. }
  467. return
  468. }
  469. total := ""
  470. if len(rtn.Data) > 0 {
  471. total = fmt.Sprint(" 共 ", len(rtn.Data), "")
  472. }
  473. logger.Info(fmt.Sprint("mql ", testname, " result:\n", string(bs), total))
  474. case "outputcount":
  475. total := fmt.Sprint(" 共 ", len(rtn.Data), "条")
  476. logger.Info(fmt.Sprint("mql ", testname, " result: ", total))
  477. case "count":
  478. if len(args) < 1 || strings.TrimSpace(cast.ToString(args[0])) == "" {
  479. s := "count(N) 需要一个参数"
  480. err = merrs.New("%s", s)
  481. assert.Nil(t, s, err)
  482. seriouserror = true
  483. return
  484. }
  485. n := cast.ToInt(strings.TrimSpace(cast.ToString(args[0])))
  486. if n != len(rtn.Data) {
  487. s := fmt.Sprint("记录数(", len(rtn.Data), ")与期望值(", n, ")不符")
  488. err = merrs.NewError(s, merrs.SSMaps{{"mql": mql}})
  489. if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(err.Error()) {
  490. if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
  491. logger.Info("error:", err)
  492. }
  493. return
  494. }
  495. if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(err.Error()) {
  496. // 直接输出错误信息,不返回错误信息,不中断循环, 不在 testing.T 中报告错误,不中断测试
  497. if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
  498. logger.Info("pass_with_error:", err)
  499. }
  500. err = nil
  501. return
  502. }
  503. if report {
  504. assert.Equal(t, n, len(rtn.Data), err)
  505. }
  506. return
  507. }
  508. case "match":
  509. breakup, seriouserror, err = DoActionMatch(t, report, args, mql, rtn, toption)
  510. if breakup {
  511. return seriouserror, err
  512. }
  513. case "matchcount":
  514. if len(args) < 3 {
  515. s := "matchcount(Kn,Mn,N) 需要至少三个参数"
  516. err = merrs.New("%s", s)
  517. assert.Nil(t, s, err)
  518. seriouserror = true
  519. return
  520. }
  521. ks := []string{}
  522. ms := []any{}
  523. i := 0
  524. for ; i+1 < len(args); i += 2 {
  525. k := strings.TrimSpace(cast.ToString(args[i]))
  526. ks = append(ks, k)
  527. var m any
  528. switch arg := args[i+1].(type) {
  529. case string:
  530. if regexp.MustCompile(`^\(\?[^\)]*\).*`).MatchString(arg) {
  531. r, e := regexp.Compile(arg)
  532. if e != nil {
  533. s := "match参数正则表达式错误:" + e.Error()
  534. err = merrs.New("%s", s)
  535. assert.Nil(t, s, err)
  536. seriouserror = true
  537. return
  538. }
  539. m = r
  540. }
  541. case *Action:
  542. switch arg.Name {
  543. case "<FUZZY>":
  544. m = arg
  545. }
  546. }
  547. if m == nil {
  548. m = fmt.Sprintf("%#v", args[i+1])
  549. }
  550. ms = append(ms, m)
  551. }
  552. if i >= len(args) {
  553. err = merrs.New("matchcount(Kn,Mn,N) 参数 Kn,Mn 需要成对出现,N为自然数")
  554. assert.Nil(t, "参数错误", err)
  555. seriouserror = true
  556. return
  557. }
  558. sn := strings.TrimSpace(cast.ToString(args[i]))
  559. n := cast.ToInt(sn)
  560. if fmt.Sprint(n) != sn {
  561. err = merrs.New("matchcount(Kn,Mn,N) 参数 Kn,Mn 需要成对出现,N为自然数 args%d:'%s'", i, args[i])
  562. assert.Nil(t, "参数错误", err)
  563. seriouserror = true
  564. return
  565. }
  566. x, matchingvalues := MatchValues(rtn, ks, ms)
  567. if n != x {
  568. if len(rtn.Data) == 0 {
  569. err = merrs.New("%s", fmt.Sprint("没有找到记录与期望值(", n, ")不符"))
  570. } else {
  571. argsbs, _ := json.MarshalIndent(args, "", " ")
  572. matchingbs, _ := json.MarshalIndent(matchingvalues, "", " ")
  573. err = merrs.NewError(fmt.Sprint("共", len(rtn.Data), "记录,匹配记录数(", x, ")与期望值(", n, ")不符"),
  574. merrs.SSMaps{{"mql": mql},
  575. {"matchcount args": fmt.Sprint(string(argsbs))},
  576. {"lastmatchingvalues": string(matchingbs)}})
  577. }
  578. if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(err.Error()) {
  579. if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
  580. logger.Info("error:", err)
  581. }
  582. return
  583. }
  584. if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(err.Error()) {
  585. // 直接输出错误信息,不返回错误信息,不中断循环, 不在 testing.T 中报告错误,不中断测试
  586. if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
  587. logger.Info("pass_with_error:", err)
  588. }
  589. err = nil
  590. return
  591. }
  592. if report {
  593. assert.Equal(t, n, x, err)
  594. }
  595. return
  596. }
  597. case "equal":
  598. breakup, seriouserror, err := DoActionEqual(
  599. t,
  600. report,
  601. toption,
  602. rtn,
  603. cast.ToStringSlice(args)...)
  604. if breakup {
  605. return seriouserror, err
  606. }
  607. default:
  608. err = merrs.New("%s", fmt.Sprint("unsupported action ", action))
  609. assert.Nil(t, "unsupported action", err)
  610. seriouserror = true
  611. return
  612. }
  613. }
  614. }
  615. return
  616. }