github.com/crowdsecurity/crowdsec@v1.6.1/pkg/exprhelpers/debugger_test.go (about)

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