mqls_do.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706
  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 matchvalue(matcher any, matchingvalue any) (bool, string) {
  346. switch m := matcher.(type) {
  347. case *regexp.Regexp:
  348. switch v := matchingvalue.(type) {
  349. case string:
  350. return m.MatchString(v), v
  351. default:
  352. sv := fmt.Sprintf("%#v", matchingvalue)
  353. return m.MatchString(sv), sv
  354. }
  355. case string:
  356. sv := ""
  357. switch matchingvalue.(type) {
  358. case int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64, float32, float64, bool:
  359. sv = fmt.Sprintf("%v", matchingvalue)
  360. default:
  361. sv = fmt.Sprintf("%#v", matchingvalue)
  362. }
  363. return sv == m, sv
  364. default:
  365. panic("未定义的匹配类型")
  366. }
  367. }
  368. func (mt *MQLTest) doFollowThroughActions(t *testing.T, testname string, toption *OnErrorOption, rtn *odb.Result, actions []*Action, mql string) (seriouserror bool, err error) {
  369. if len(actions) > 0 {
  370. if rtn == nil {
  371. s := "返回值为空"
  372. err = merrs.New("%s", s)
  373. if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(err.Error()) {
  374. if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
  375. logger.Info("error:", err)
  376. }
  377. return
  378. }
  379. if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(err.Error()) {
  380. // 直接输出错误信息,不返回错误信息,不中断循环, 不在 testing.T 中报告错误,不中断测试
  381. if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
  382. logger.Info("pass_with_error:", err)
  383. }
  384. err = nil
  385. return
  386. }
  387. if !assert.NotNil(t, rtn, err) {
  388. return
  389. }
  390. }
  391. for _, act := range actions {
  392. breakup := false
  393. action := act.Name
  394. args := act.Args
  395. switch action {
  396. case "schema":
  397. if len(args) < 1 || strings.TrimSpace(cast.ToString(args[0])) == "" {
  398. // 标记语法错误
  399. s := "schema(C) 需要一个参数"
  400. err = merrs.New("%s", s)
  401. assert.Nil(t, s, err)
  402. seriouserror = true
  403. return
  404. }
  405. res, _ := ODBC.Command("schema", args[0]).Do()
  406. if res == nil || len(res.Data) == 0 {
  407. s := fmt.Sprintf("没有发现类 %s", args[0])
  408. err = merrs.New("%s", s)
  409. if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(err.Error()) {
  410. if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
  411. logger.Info("error:", err)
  412. }
  413. return
  414. }
  415. if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(err.Error()) {
  416. // 直接输出错误信息,不返回错误信息,不中断循环, 不在 testing.T 中报告错误,不中断测试
  417. if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
  418. logger.Info("pass_with_error:", err)
  419. }
  420. err = nil
  421. return
  422. }
  423. if !assert.Nil(t, s, err) {
  424. return
  425. }
  426. }
  427. case "metainfo":
  428. bs, e := json.MarshalIndent(rtn.Meta, "", " ")
  429. if e != nil {
  430. err = merrs.New("%s", e.Error())
  431. if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(err.Error()) {
  432. if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
  433. logger.Info("error:", err)
  434. }
  435. return
  436. }
  437. if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(err.Error()) {
  438. // 直接输出错误信息,不返回错误信息,不中断循环, 不在 testing.T 中报告错误,不中断测试
  439. if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
  440. logger.Info("pass_with_error:", err)
  441. }
  442. err = nil
  443. return
  444. }
  445. if !assert.Nil(t, "error", err) {
  446. // 不直接输出错误信息,返回错误信息,中断循环, 在 testing.T 中报告错误,中断测试
  447. return
  448. }
  449. }
  450. logger.Info(fmt.Sprint("mql ", testname, " meta info:\n", string(bs)))
  451. case "output":
  452. bs, e := json.MarshalIndent(rtn.Data, "", " ")
  453. if e != nil {
  454. err = merrs.New("%s", e.Error())
  455. if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(err.Error()) {
  456. if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
  457. logger.Info("error:", err)
  458. }
  459. return
  460. }
  461. if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(err.Error()) {
  462. // 直接输出错误信息,不返回错误信息,不中断循环, 不在 testing.T 中报告错误,不中断测试
  463. if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
  464. logger.Info("pass_with_error:", err)
  465. }
  466. err = nil
  467. return
  468. }
  469. if !assert.Nil(t, "error", err) {
  470. // 不直接输出错误信息,返回错误信息,中断循环, 在 testing.T 中报告错误,中断测试
  471. return
  472. }
  473. }
  474. total := ""
  475. if len(rtn.Data) > 0 {
  476. total = fmt.Sprint(" 共 ", len(rtn.Data), "")
  477. }
  478. logger.Info(fmt.Sprint("mql ", testname, " result:\n", string(bs), total))
  479. case "outputcount":
  480. total := fmt.Sprint(" 共 ", len(rtn.Data), "条")
  481. logger.Info(fmt.Sprint("mql ", testname, " result: ", total))
  482. case "count":
  483. if len(args) < 1 || strings.TrimSpace(cast.ToString(args[0])) == "" {
  484. s := "count(N) 需要一个参数"
  485. err = merrs.New("%s", s)
  486. assert.Nil(t, s, err)
  487. seriouserror = true
  488. return
  489. }
  490. n := cast.ToInt(strings.TrimSpace(cast.ToString(args[0])))
  491. if n != len(rtn.Data) {
  492. s := fmt.Sprint("记录数(", len(rtn.Data), ")与期望值(", n, ")不符")
  493. err = merrs.NewError(s, merrs.SSMaps{{"mql": mql}})
  494. if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(err.Error()) {
  495. if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
  496. logger.Info("error:", err)
  497. }
  498. return
  499. }
  500. if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(err.Error()) {
  501. // 直接输出错误信息,不返回错误信息,不中断循环, 不在 testing.T 中报告错误,不中断测试
  502. if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
  503. logger.Info("pass_with_error:", err)
  504. }
  505. err = nil
  506. return
  507. }
  508. if !assert.Equal(t, n, len(rtn.Data), err) {
  509. // 不直接输出错误信息,返回错误信息,中断循环, 在 testing.T 中报告错误,中断测试
  510. return
  511. }
  512. }
  513. case "match":
  514. breakup, seriouserror, err = DoActionMatch(t, args, mql, rtn, toption)
  515. if breakup {
  516. return seriouserror, err
  517. }
  518. case "matchcount":
  519. if len(args) < 3 {
  520. s := "matchcount(Kn,Mn,N) 需要至少三个参数"
  521. err = merrs.New("%s", s)
  522. assert.Nil(t, s, err)
  523. seriouserror = true
  524. return
  525. }
  526. ks := []string{}
  527. ms := []any{}
  528. i := 0
  529. for ; i+1 < len(args); i += 2 {
  530. k := strings.TrimSpace(cast.ToString(args[i]))
  531. ks = append(ks, k)
  532. var m any
  533. switch arg := args[i+1].(type) {
  534. case string:
  535. if regexp.MustCompile(`^\(\?[^\)]*\).*`).MatchString(arg) {
  536. r, e := regexp.Compile(arg)
  537. if e != nil {
  538. s := "match参数正则表达式错误:" + e.Error()
  539. err = merrs.New("%s", s)
  540. assert.Nil(t, s, err)
  541. seriouserror = true
  542. return
  543. }
  544. m = r
  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 := 0
  567. var matchingvalues [][]any
  568. var matchingvalue any
  569. for _, dat := range rtn.Data {
  570. matchingvalues = append(matchingvalues, []any{})
  571. m := false
  572. sv := ""
  573. match := true
  574. for i, k := range ks {
  575. matchingvalue = dat[k]
  576. m, sv = matchvalue(ms[i], matchingvalue)
  577. if !m {
  578. ks := strings.Split(k, ".")
  579. if len(ks) <= 1 {
  580. match = false
  581. break
  582. } else {
  583. k := ""
  584. for i := 0; match && i < len(ks); i++ {
  585. if matchingvalue == nil {
  586. if k != "" {
  587. k += "."
  588. }
  589. k += ks[i]
  590. matchingvalue = dat[k]
  591. if matchingvalue == nil && ks[i] == "len" {
  592. matchingvalue = 0
  593. }
  594. } else {
  595. switch mv := matchingvalue.(type) {
  596. case map[string]interface{}:
  597. matchingvalue = mv[ks[i]]
  598. if matchingvalue == nil && ks[i] == "len" {
  599. matchingvalue = len(mv)
  600. }
  601. case []interface{}:
  602. if ks[i] == "len" {
  603. matchingvalue = len(mv)
  604. } else {
  605. n, e := cast.ToIntE(ks[i])
  606. if e != nil {
  607. match = false
  608. break
  609. }
  610. matchingvalue = mv[n]
  611. }
  612. case string:
  613. if ks[i] == "len" {
  614. matchingvalue = len(mv)
  615. } else {
  616. n, e := cast.ToIntE(ks[i])
  617. if e != nil {
  618. match = false
  619. break
  620. }
  621. matchingvalue = mv[n]
  622. }
  623. default:
  624. if ks[i] == "len" {
  625. matchingvalue = 0
  626. } else {
  627. matchingvalue = nil
  628. }
  629. }
  630. }
  631. }
  632. m, sv = matchvalue(ms[i], matchingvalue)
  633. if !m {
  634. match = false
  635. break
  636. }
  637. }
  638. }
  639. matchingvalues[len(matchingvalues)-1] = append(matchingvalues[len(matchingvalues)-1], sv)
  640. }
  641. if match {
  642. x++
  643. matchingvalues = matchingvalues[:len(matchingvalues)-1]
  644. } else {
  645. matchingvalues[len(matchingvalues)-1] = append(matchingvalues[len(matchingvalues)-1], sv)
  646. }
  647. }
  648. if n != x {
  649. if len(rtn.Data) == 0 {
  650. err = merrs.New("%s", fmt.Sprint("没有找到记录与期望值(", n, ")不符"))
  651. } else {
  652. argsbs, _ := json.MarshalIndent(args, "", " ")
  653. matchingbs, _ := json.MarshalIndent(matchingvalues, "", " ")
  654. err = merrs.NewError(fmt.Sprint("共", len(rtn.Data), "记录,匹配记录数(", x, ")与期望值(", n, ")不符"),
  655. merrs.SSMaps{{"mql": mql},
  656. {"matchcount args": fmt.Sprint(string(argsbs))},
  657. {"lastmatchingvalues": string(matchingbs)}})
  658. }
  659. if toption.regex[OnErrorBreak] != nil && toption.regex[OnErrorBreak].MatchString(err.Error()) {
  660. if toption.regex[OnErrorBreak] == matchall && !toption.noerrorinfo {
  661. logger.Info("error:", err)
  662. }
  663. return
  664. }
  665. if toption.regex[OnErrorIgnore] != nil && toption.regex[OnErrorIgnore].MatchString(err.Error()) {
  666. // 直接输出错误信息,不返回错误信息,不中断循环, 不在 testing.T 中报告错误,不中断测试
  667. if toption.regex[OnErrorIgnore] == matchall && !toption.noerrorinfo {
  668. logger.Info("pass_with_error:", err)
  669. }
  670. err = nil
  671. return
  672. }
  673. if !assert.Equal(t, n, x, err) {
  674. // 不直接输出错误信息,返回错误信息,中断循环, 在 testing.T 中报告错误,中断测试
  675. return
  676. }
  677. }
  678. case "equal":
  679. breakup, seriouserror, err := DoActionEqual(
  680. t,
  681. toption,
  682. rtn,
  683. cast.ToStringSlice(args)...)
  684. if breakup {
  685. return seriouserror, err
  686. }
  687. default:
  688. err = merrs.New("%s", fmt.Sprint("unsupported action ", action))
  689. assert.Nil(t, "unsupported action", err)
  690. seriouserror = true
  691. return
  692. }
  693. }
  694. }
  695. return
  696. }