debugger_test.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. package exprhelpers
  2. import (
  3. "reflect"
  4. "strings"
  5. "testing"
  6. "github.com/antonmedv/expr"
  7. "github.com/crowdsecurity/crowdsec/pkg/types"
  8. "github.com/davecgh/go-spew/spew"
  9. log "github.com/sirupsen/logrus"
  10. )
  11. type ExprDbgTest struct {
  12. Name string
  13. Expr string
  14. ExpectedOutputs []OpOutput
  15. ExpectedFailedCompile bool
  16. ExpectedFailRuntime bool
  17. Env map[string]interface{}
  18. LogLevel log.Level
  19. }
  20. // For the sake of testing functions with 2, 3 and N args
  21. func UpperTwo(params ...any) (any, error) {
  22. s := params[0].(string)
  23. v := params[1].(string)
  24. return strings.ToUpper(s) + strings.ToUpper(v), nil
  25. }
  26. func UpperThree(params ...any) (any, error) {
  27. s := params[0].(string)
  28. v := params[1].(string)
  29. x := params[2].(string)
  30. return strings.ToUpper(s) + strings.ToUpper(v) + strings.ToUpper(x), nil
  31. }
  32. func UpperN(params ...any) (any, error) {
  33. s := params[0].(string)
  34. v := params[1].(string)
  35. x := params[2].(string)
  36. y := params[3].(string)
  37. return strings.ToUpper(s) + strings.ToUpper(v) + strings.ToUpper(x) + strings.ToUpper(y), nil
  38. }
  39. func boolPtr(b bool) *bool {
  40. return &b
  41. }
  42. type teststruct struct {
  43. Foo string
  44. }
  45. func TestBaseDbg(t *testing.T) {
  46. defaultEnv := map[string]interface{}{
  47. "queue": &types.Queue{},
  48. "evt": &types.Event{},
  49. "sample_array": []string{"a", "b", "c", "ZZ"},
  50. "base_string": "hello world",
  51. "base_int": 42,
  52. "base_float": 42.42,
  53. "nillvar": &teststruct{},
  54. "base_struct": struct {
  55. Foo string
  56. Bar int
  57. Myarr []string
  58. }{
  59. Foo: "bar",
  60. Bar: 42,
  61. Myarr: []string{"a", "b", "c"},
  62. },
  63. }
  64. // tips for the tests:
  65. // use '%#v' to dump in golang syntax
  66. // use regexp to clear empty/default fields:
  67. // [a-z]+: (false|\[\]string\(nil\)|""),
  68. //ConditionResult:(*bool)
  69. //Missing multi parametes function
  70. tests := []ExprDbgTest{
  71. {
  72. Name: "nill deref",
  73. Expr: "Upper('1') == '1' && nillvar.Foo == '42'",
  74. Env: defaultEnv,
  75. ExpectedFailRuntime: true,
  76. ExpectedOutputs: []OpOutput{
  77. {Code: "Upper('1')", CodeDepth: 0, Func: true, FuncName: "Upper", Args: []string{"\"1\""}, FuncResults: []string{"\"1\""}, ConditionResult: (*bool)(nil), Finalized: true},
  78. {Code: "== '1'", CodeDepth: 0, Comparison: true, Left: "\"1\"", Right: "\"1\"", StrConditionResult: "[true]", ConditionResult: boolPtr(true), Finalized: true},
  79. {Code: "&&", CodeDepth: 0, JumpIf: true, IfFalse: true, StrConditionResult: "<nil>", ConditionResult: boolPtr(true), Finalized: true},
  80. },
  81. },
  82. {
  83. Name: "OpCall2",
  84. Expr: "UpperTwo('hello', 'world') == 'HELLOWORLD'",
  85. Env: defaultEnv,
  86. ExpectedOutputs: []OpOutput{
  87. {Code: "UpperTwo('hello', 'world')", CodeDepth: 0, Func: true, FuncName: "UpperTwo", Args: []string{"\"world\"", "\"hello\""}, FuncResults: []string{"\"HELLOWORLD\""}, ConditionResult: (*bool)(nil), Finalized: true},
  88. {Code: "== 'HELLOWORLD'", CodeDepth: 0, Comparison: true, Left: "\"HELLOWORLD\"", Right: "\"HELLOWORLD\"", StrConditionResult: "true", ConditionResult: boolPtr(true), Finalized: true},
  89. },
  90. },
  91. {
  92. Name: "OpCall3",
  93. Expr: "UpperThree('hello', 'world', 'foo') == 'HELLOWORLDFOO'",
  94. Env: defaultEnv,
  95. ExpectedOutputs: []OpOutput{
  96. {Code: "UpperThree('hello', 'world', 'foo')", CodeDepth: 0, Func: true, FuncName: "UpperThree", Args: []string{"\"foo\"", "\"world\"", "\"hello\""}, FuncResults: []string{"\"HELLOWORLDFOO\""}, ConditionResult: (*bool)(nil), Finalized: true},
  97. {Code: "== 'HELLOWORLDFOO'", CodeDepth: 0, Comparison: true, Left: "\"HELLOWORLDFOO\"", Right: "\"HELLOWORLDFOO\"", StrConditionResult: "true", ConditionResult: boolPtr(true), Finalized: true},
  98. },
  99. },
  100. {
  101. Name: "OpCallN",
  102. Expr: "UpperN('hello', 'world', 'foo', 'lol') == UpperN('hello', 'world', 'foo', 'lol')",
  103. Env: defaultEnv,
  104. ExpectedOutputs: []OpOutput{
  105. {Code: "UpperN('hello', 'world', 'foo', 'lol')", CodeDepth: 0, Func: true, FuncName: "OpCallN", Args: []string{"\"lol\"", "\"foo\"", "\"world\"", "\"hello\""}, FuncResults: []string{"\"HELLOWORLDFOOLOL\""}, ConditionResult: (*bool)(nil), Finalized: true},
  106. {Code: "UpperN('hello', 'world', 'foo', 'lol')", CodeDepth: 0, Func: true, FuncName: "OpCallN", Args: []string{"\"lol\"", "\"foo\"", "\"world\"", "\"hello\""}, FuncResults: []string{"\"HELLOWORLDFOOLOL\""}, ConditionResult: (*bool)(nil), Finalized: true},
  107. {Code: "== UpperN('hello', 'world', 'foo', 'lol')", CodeDepth: 0, Comparison: true, Left: "\"HELLOWORLDFOOLOL\"", Right: "\"HELLOWORLDFOOLOL\"", StrConditionResult: "true", ConditionResult: boolPtr(true), Finalized: true},
  108. },
  109. },
  110. {
  111. Name: "base string cmp",
  112. Expr: "base_string == 'hello world'",
  113. Env: defaultEnv,
  114. ExpectedOutputs: []OpOutput{
  115. {Code: "== 'hello world'", CodeDepth: 0, Comparison: true, Left: "\"hello world\"", Right: "\"hello world\"", StrConditionResult: "true", ConditionResult: boolPtr(true), Finalized: true},
  116. },
  117. },
  118. {
  119. Name: "loop with func call",
  120. Expr: "count(base_struct.Myarr, {Upper(#) == 'C'}) == 1",
  121. Env: defaultEnv,
  122. ExpectedOutputs: []OpOutput{
  123. {Code: "count(base_struct.Myarr, {", CodeDepth: 4, BlockStart: true, ConditionResult: (*bool)(nil), Finalized: false},
  124. {Code: "Upper(#)", CodeDepth: 4, Func: true, FuncName: "Upper", Args: []string{"\"a\""}, FuncResults: []string{"\"A\""}, ConditionResult: (*bool)(nil), Finalized: true},
  125. {Code: "== 'C'})", CodeDepth: 4, Comparison: true, Left: "\"A\"", Right: "\"C\"", StrConditionResult: "[false]", ConditionResult: boolPtr(false), Finalized: true},
  126. {Code: "count(base_struct.Myarr, {Upper(#) == 'C'})", CodeDepth: 4, JumpIf: true, IfFalse: true, StrConditionResult: "false", ConditionResult: boolPtr(false), Finalized: false},
  127. {Code: "Upper(#)", CodeDepth: 4, Func: true, FuncName: "Upper", Args: []string{"\"b\""}, FuncResults: []string{"\"B\""}, ConditionResult: (*bool)(nil), Finalized: true},
  128. {Code: "== 'C'})", CodeDepth: 4, Comparison: true, Left: "\"B\"", Right: "\"C\"", StrConditionResult: "[false]", ConditionResult: boolPtr(false), Finalized: true},
  129. {Code: "count(base_struct.Myarr, {Upper(#) == 'C'})", CodeDepth: 4, JumpIf: true, IfFalse: true, StrConditionResult: "false", ConditionResult: boolPtr(false), Finalized: false},
  130. {Code: "Upper(#)", CodeDepth: 4, Func: true, FuncName: "Upper", Args: []string{"\"c\""}, FuncResults: []string{"\"C\""}, ConditionResult: (*bool)(nil), Finalized: true},
  131. {Code: "== 'C'})", CodeDepth: 4, Comparison: true, Left: "\"C\"", Right: "\"C\"", StrConditionResult: "[true]", ConditionResult: boolPtr(true), Finalized: true},
  132. {Code: "count(base_struct.Myarr, {Upper(#) == 'C'})", CodeDepth: 4, JumpIf: true, IfFalse: true, StrConditionResult: "true", ConditionResult: boolPtr(true), Finalized: false},
  133. {Code: "count(base_struct.Myarr, {Upper(#) == 'C'})", CodeDepth: 0, BlockEnd: true, StrConditionResult: "[1]", ConditionResult: (*bool)(nil), Finalized: false},
  134. {Code: "== 1", CodeDepth: 0, Comparison: true, Left: "1", Right: "1", StrConditionResult: "true", ConditionResult: boolPtr(true), Finalized: true},
  135. },
  136. },
  137. {
  138. Name: "loop with func call and extra check",
  139. Expr: "count(base_struct.Myarr, {Upper(#) == 'C'}) == 1 && Upper(base_struct.Foo) == 'BAR'",
  140. Env: defaultEnv,
  141. ExpectedOutputs: []OpOutput{
  142. {Code: "count(base_struct.Myarr, {", CodeDepth: 4, BlockStart: true, ConditionResult: (*bool)(nil), Finalized: false},
  143. {Code: "Upper(#)", CodeDepth: 4, Func: true, FuncName: "Upper", Args: []string{"\"a\""}, FuncResults: []string{"\"A\""}, ConditionResult: (*bool)(nil), Finalized: true},
  144. {Code: "== 'C'})", CodeDepth: 4, Comparison: true, Left: "\"A\"", Right: "\"C\"", StrConditionResult: "[false]", ConditionResult: boolPtr(false), Finalized: true},
  145. {Code: "count(base_struct.Myarr, {Upper(#) == 'C'})", CodeDepth: 4, JumpIf: true, IfFalse: true, StrConditionResult: "false", ConditionResult: boolPtr(false), Finalized: false},
  146. {Code: "Upper(#)", CodeDepth: 4, Func: true, FuncName: "Upper", Args: []string{"\"b\""}, FuncResults: []string{"\"B\""}, ConditionResult: (*bool)(nil), Finalized: true},
  147. {Code: "== 'C'})", CodeDepth: 4, Comparison: true, Left: "\"B\"", Right: "\"C\"", StrConditionResult: "[false]", ConditionResult: boolPtr(false), Finalized: true},
  148. {Code: "count(base_struct.Myarr, {Upper(#) == 'C'})", CodeDepth: 4, JumpIf: true, IfFalse: true, StrConditionResult: "false", ConditionResult: boolPtr(false), Finalized: false},
  149. {Code: "Upper(#)", CodeDepth: 4, Func: true, FuncName: "Upper", Args: []string{"\"c\""}, FuncResults: []string{"\"C\""}, ConditionResult: (*bool)(nil), Finalized: true},
  150. {Code: "== 'C'})", CodeDepth: 4, Comparison: true, Left: "\"C\"", Right: "\"C\"", StrConditionResult: "[true]", ConditionResult: boolPtr(true), Finalized: true},
  151. {Code: "count(base_struct.Myarr, {Upper(#) == 'C'})", CodeDepth: 4, JumpIf: true, IfFalse: true, StrConditionResult: "true", ConditionResult: boolPtr(true), Finalized: false},
  152. {Code: "count(base_struct.Myarr, {Upper(#) == 'C'})", CodeDepth: 0, BlockEnd: true, StrConditionResult: "[1]", ConditionResult: (*bool)(nil), Finalized: false},
  153. {Code: "== 1", CodeDepth: 0, Comparison: true, Left: "1", Right: "1", StrConditionResult: "[true]", ConditionResult: boolPtr(true), Finalized: true},
  154. {Code: "&&", CodeDepth: 0, JumpIf: true, IfFalse: true, StrConditionResult: "true", ConditionResult: boolPtr(true), Finalized: false},
  155. {Code: "Upper(base_struct.Foo)", CodeDepth: 0, Func: true, FuncName: "Upper", Args: []string{"\"bar\""}, FuncResults: []string{"\"BAR\""}, ConditionResult: (*bool)(nil), Finalized: true},
  156. {Code: "== 'BAR'", CodeDepth: 0, Comparison: true, Left: "\"BAR\"", Right: "\"BAR\"", StrConditionResult: "true", ConditionResult: boolPtr(true), Finalized: true},
  157. },
  158. },
  159. {
  160. Name: "base 'in' test",
  161. Expr: "base_int in [1,2,3,4,42]",
  162. Env: defaultEnv,
  163. ExpectedOutputs: []OpOutput{
  164. {Code: "in [1,2,3,4,42]", CodeDepth: 0, Args: []string{"42", "map[1:{} 2:{} 3:{} 4:{} 42:{}]"}, Condition: true, ConditionIn: true, StrConditionResult: "true", ConditionResult: boolPtr(true), Finalized: true},
  165. },
  166. },
  167. {
  168. Name: "base string cmp",
  169. Expr: "base_string == 'hello world'",
  170. Env: defaultEnv,
  171. ExpectedOutputs: []OpOutput{
  172. {Code: "== 'hello world'", CodeDepth: 0, Comparison: true, Left: "\"hello world\"", Right: "\"hello world\"", StrConditionResult: "true", ConditionResult: boolPtr(true), Finalized: true},
  173. },
  174. },
  175. {
  176. Name: "base int cmp",
  177. Expr: "base_int == 42",
  178. Env: defaultEnv,
  179. ExpectedOutputs: []OpOutput{
  180. {Code: "== 42", CodeDepth: 0, Comparison: true, Left: "42", Right: "42", StrConditionResult: "true", ConditionResult: boolPtr(true), Finalized: true},
  181. },
  182. },
  183. {
  184. Name: "negative check",
  185. Expr: "base_int != 43",
  186. Env: defaultEnv,
  187. ExpectedOutputs: []OpOutput{
  188. {Code: "!= 43", CodeDepth: 0, Negated: true, Comparison: true, Left: "42", Right: "43", StrConditionResult: "true", ConditionResult: boolPtr(true), Finalized: true},
  189. },
  190. },
  191. {
  192. Name: "testing ORs",
  193. Expr: "base_int == 43 || base_int == 42",
  194. Env: defaultEnv,
  195. ExpectedOutputs: []OpOutput{
  196. {Code: "== 43", CodeDepth: 0, Comparison: true, Left: "42", Right: "43", StrConditionResult: "[false]", ConditionResult: boolPtr(false), Finalized: true},
  197. {Code: "||", CodeDepth: 0, JumpIf: true, IfTrue: true, StrConditionResult: "false", ConditionResult: boolPtr(false), Finalized: false},
  198. {Code: "== 42", CodeDepth: 0, Comparison: true, Left: "42", Right: "42", StrConditionResult: "true", ConditionResult: boolPtr(true), Finalized: true},
  199. },
  200. },
  201. {
  202. Name: "testing basic true",
  203. Expr: "true",
  204. Env: defaultEnv,
  205. ExpectedOutputs: []OpOutput{
  206. {Code: "true", CodeDepth: 0, Condition: true, StrConditionResult: "true", ConditionResult: boolPtr(true), Finalized: true},
  207. },
  208. },
  209. {
  210. Name: "testing basic false",
  211. Expr: "false",
  212. Env: defaultEnv,
  213. ExpectedOutputs: []OpOutput{
  214. {Code: "false", CodeDepth: 0, Condition: true, StrConditionResult: "false", ConditionResult: boolPtr(false), Finalized: true},
  215. },
  216. },
  217. {
  218. Name: "testing multi lines",
  219. Expr: `base_int == 42 &&
  220. base_string == 'hello world' &&
  221. (base_struct.Bar == 41 || base_struct.Bar == 42)`,
  222. Env: defaultEnv,
  223. ExpectedOutputs: []OpOutput{
  224. {Code: "== 42", CodeDepth: 0, Comparison: true, Left: "42", Right: "42", StrConditionResult: "[true]", ConditionResult: boolPtr(true), Finalized: true},
  225. {Code: "&&", CodeDepth: 0, JumpIf: true, IfFalse: true, StrConditionResult: "true", ConditionResult: boolPtr(true), Finalized: false},
  226. {Code: "== 'hello world'", CodeDepth: 0, Comparison: true, Left: "\"hello world\"", Right: "\"hello world\"", StrConditionResult: "[true]", ConditionResult: boolPtr(true), Finalized: true},
  227. {Code: "&& (", CodeDepth: 0, JumpIf: true, IfFalse: true, StrConditionResult: "true", ConditionResult: boolPtr(true), Finalized: false},
  228. {Code: "== 41", CodeDepth: 0, Comparison: true, Left: "42", Right: "41", StrConditionResult: "[false]", ConditionResult: boolPtr(false), Finalized: true},
  229. {Code: "||", CodeDepth: 0, JumpIf: true, IfTrue: true, StrConditionResult: "false", ConditionResult: boolPtr(false), Finalized: false},
  230. {Code: "== 42)", CodeDepth: 0, Comparison: true, Left: "42", Right: "42", StrConditionResult: "true", ConditionResult: boolPtr(true), Finalized: true},
  231. },
  232. },
  233. {
  234. Name: "upper + in",
  235. Expr: "Upper(base_string) contains Upper('wOrlD')",
  236. Env: defaultEnv,
  237. ExpectedOutputs: []OpOutput{
  238. {Code: "Upper(base_string)", CodeDepth: 0, Func: true, FuncName: "Upper", Args: []string{"\"hello world\""}, FuncResults: []string{"\"HELLO WORLD\""}, ConditionResult: (*bool)(nil), Finalized: true},
  239. {Code: "Upper('wOrlD')", CodeDepth: 0, Func: true, FuncName: "Upper", Args: []string{"\"wOrlD\""}, FuncResults: []string{"\"WORLD\""}, ConditionResult: (*bool)(nil), Finalized: true},
  240. {Code: "contains Upper('wOrlD')", CodeDepth: 0, Args: []string{"\"HELLO WORLD\"", "\"WORLD\""}, Condition: true, ConditionContains: true, StrConditionResult: "true", ConditionResult: boolPtr(true), Finalized: true},
  241. },
  242. },
  243. {
  244. Name: "upper + complex",
  245. Expr: `( Upper(base_string) contains Upper('/someurl?x=1') ||
  246. Upper(base_string) contains Upper('/someotherurl?account-name=admin&account-status=1&ow=cmd') )
  247. and base_string startsWith ('40') and Upper(base_string) == 'POST'`,
  248. Env: defaultEnv,
  249. ExpectedOutputs: []OpOutput{
  250. {Code: "Upper(base_string)", CodeDepth: 0, Func: true, FuncName: "Upper", Args: []string{"\"hello world\""}, FuncResults: []string{"\"HELLO WORLD\""}, ConditionResult: (*bool)(nil), Finalized: true},
  251. {Code: "Upper('/someurl?x=1')", CodeDepth: 0, Func: true, FuncName: "Upper", Args: []string{"\"/someurl?x=1\""}, FuncResults: []string{"\"/SOMEURL?X=1\""}, ConditionResult: (*bool)(nil), Finalized: true},
  252. {Code: "contains Upper('/someurl?x=1')", CodeDepth: 0, Args: []string{"\"HELLO WORLD\"", "\"/SOMEURL?X=1\""}, Condition: true, ConditionContains: true, StrConditionResult: "[false]", ConditionResult: boolPtr(false), Finalized: true},
  253. {Code: "||", CodeDepth: 0, JumpIf: true, IfTrue: true, StrConditionResult: "false", ConditionResult: boolPtr(false), Finalized: false},
  254. {Code: "Upper(base_string)", CodeDepth: 0, Func: true, FuncName: "Upper", Args: []string{"\"hello world\""}, FuncResults: []string{"\"HELLO WORLD\""}, ConditionResult: (*bool)(nil), Finalized: true},
  255. {Code: "Upper('/someotherurl?account-name=admin&account-status=1&ow=cmd') )", CodeDepth: 0, Func: true, FuncName: "Upper", Args: []string{"\"/someotherurl?account-name=admin&account...\""}, FuncResults: []string{"\"/SOMEOTHERURL?ACCOUNT-NAME=ADMIN&ACCOUNT...\""}, ConditionResult: (*bool)(nil), Finalized: true},
  256. {Code: "contains Upper('/someotherurl?account-name=admin&account-status=1&ow=cmd') )", CodeDepth: 0, Args: []string{"\"HELLO WORLD\"", "\"/SOMEOTHERURL?ACCOUNT-NAME=ADMIN&ACCOUNT...\""}, Condition: true, ConditionContains: true, StrConditionResult: "[false]", ConditionResult: boolPtr(false), Finalized: true},
  257. {Code: "and", CodeDepth: 0, JumpIf: true, IfFalse: true, StrConditionResult: "false", ConditionResult: boolPtr(false), Finalized: false},
  258. {Code: "and", CodeDepth: 0, JumpIf: true, IfFalse: true, StrConditionResult: "false", ConditionResult: boolPtr(false), Finalized: true},
  259. },
  260. },
  261. }
  262. logger := log.WithField("test", "exprhelpers")
  263. for _, test := range tests {
  264. if test.LogLevel != 0 {
  265. log.SetLevel(test.LogLevel)
  266. } else {
  267. log.SetLevel(log.DebugLevel)
  268. }
  269. extraFuncs := []expr.Option{}
  270. extraFuncs = append(extraFuncs,
  271. expr.Function("UpperTwo",
  272. UpperTwo,
  273. []interface{}{new(func(string, string) string)}...,
  274. ))
  275. extraFuncs = append(extraFuncs,
  276. expr.Function("UpperThree",
  277. UpperThree,
  278. []interface{}{new(func(string, string, string) string)}...,
  279. ))
  280. extraFuncs = append(extraFuncs,
  281. expr.Function("UpperN",
  282. UpperN,
  283. []interface{}{new(func(string, string, string, string) string)}...,
  284. ))
  285. supaEnv := GetExprOptions(test.Env)
  286. supaEnv = append(supaEnv, extraFuncs...)
  287. prog, err := expr.Compile(test.Expr, supaEnv...)
  288. if test.ExpectedFailedCompile {
  289. if err == nil {
  290. t.Fatalf("test %s : expected compile error", test.Name)
  291. }
  292. } else {
  293. if err != nil {
  294. t.Fatalf("test %s : unexpected compile error : %s", test.Name, err)
  295. }
  296. }
  297. if test.Name == "nill deref" {
  298. test.Env["nillvar"] = nil
  299. }
  300. outdbg, ret, err := RunWithDebug(prog, test.Env, logger)
  301. if test.ExpectedFailRuntime {
  302. if err == nil {
  303. t.Fatalf("test %s : expected runtime error", test.Name)
  304. }
  305. } else {
  306. if err != nil {
  307. t.Fatalf("test %s : unexpected runtime error : %s", test.Name, err)
  308. }
  309. }
  310. log.SetLevel(log.DebugLevel)
  311. DisplayExprDebug(prog, outdbg, logger, ret)
  312. if len(outdbg) != len(test.ExpectedOutputs) {
  313. t.Errorf("failed test %s", test.Name)
  314. t.Errorf("%#v", outdbg)
  315. //out, _ := yaml.Marshal(outdbg)
  316. //fmt.Printf("%s", string(out))
  317. t.Fatalf("test %s : expected %d outputs, got %d", test.Name, len(test.ExpectedOutputs), len(outdbg))
  318. }
  319. for i, out := range outdbg {
  320. if !reflect.DeepEqual(out, test.ExpectedOutputs[i]) {
  321. spew.Config.DisableMethods = true
  322. t.Errorf("failed test %s", test.Name)
  323. t.Errorf("expected : %#v", test.ExpectedOutputs[i])
  324. t.Errorf("got : %#v", out)
  325. t.Fatalf("%d/%d : mismatch", i, len(outdbg))
  326. }
  327. //DisplayExprDebug(prog, outdbg, logger, ret)
  328. }
  329. }
  330. }