github.com/expr-lang/expr@v1.16.9/vm/vm_test.go (about)

     1  package vm_test
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"reflect"
     7  	"testing"
     8  
     9  	"github.com/expr-lang/expr/internal/testify/require"
    10  
    11  	"github.com/expr-lang/expr"
    12  	"github.com/expr-lang/expr/checker"
    13  	"github.com/expr-lang/expr/compiler"
    14  	"github.com/expr-lang/expr/conf"
    15  	"github.com/expr-lang/expr/parser"
    16  	"github.com/expr-lang/expr/vm"
    17  )
    18  
    19  func TestRun_NilProgram(t *testing.T) {
    20  	_, err := vm.Run(nil, nil)
    21  	require.Error(t, err)
    22  }
    23  
    24  func TestRun_ReuseVM(t *testing.T) {
    25  	node, err := parser.Parse(`map(1..2, {#})`)
    26  	require.NoError(t, err)
    27  
    28  	program, err := compiler.Compile(node, nil)
    29  	require.NoError(t, err)
    30  
    31  	reuse := vm.VM{}
    32  	_, err = reuse.Run(program, nil)
    33  	require.NoError(t, err)
    34  	_, err = reuse.Run(program, nil)
    35  	require.NoError(t, err)
    36  }
    37  
    38  func TestRun_ReuseVM_for_different_variables(t *testing.T) {
    39  	v := vm.VM{}
    40  
    41  	program, err := expr.Compile(`let a = 1; a + 1`)
    42  	require.NoError(t, err)
    43  	out, err := v.Run(program, nil)
    44  	require.NoError(t, err)
    45  	require.Equal(t, 2, out)
    46  
    47  	program, err = expr.Compile(`let a = 2; a + 1`)
    48  	require.NoError(t, err)
    49  	out, err = v.Run(program, nil)
    50  	require.NoError(t, err)
    51  	require.Equal(t, 3, out)
    52  
    53  	program, err = expr.Compile(`let a = 2; let b = 2; a + b`)
    54  	require.NoError(t, err)
    55  	out, err = v.Run(program, nil)
    56  	require.NoError(t, err)
    57  	require.Equal(t, 4, out)
    58  }
    59  
    60  func TestRun_Cast(t *testing.T) {
    61  	input := `1`
    62  
    63  	tree, err := parser.Parse(input)
    64  	require.NoError(t, err)
    65  
    66  	program, err := compiler.Compile(tree, &conf.Config{Expect: reflect.Float64})
    67  	require.NoError(t, err)
    68  
    69  	out, err := vm.Run(program, nil)
    70  	require.NoError(t, err)
    71  
    72  	require.Equal(t, float64(1), out)
    73  }
    74  
    75  func TestRun_Helpers(t *testing.T) {
    76  	values := []any{
    77  		uint(1),
    78  		uint8(1),
    79  		uint16(1),
    80  		uint32(1),
    81  		uint64(1),
    82  		1,
    83  		int8(1),
    84  		int16(1),
    85  		int32(1),
    86  		int64(1),
    87  		float32(1),
    88  		float64(1),
    89  	}
    90  	ops := []string{"+", "-", "*", "/", "%", "==", ">=", "<=", "<", ">"}
    91  
    92  	for _, a := range values {
    93  		for _, b := range values {
    94  			for _, op := range ops {
    95  
    96  				if op == "%" {
    97  					switch a.(type) {
    98  					case float32, float64:
    99  						continue
   100  					}
   101  					switch b.(type) {
   102  					case float32, float64:
   103  						continue
   104  					}
   105  				}
   106  
   107  				input := fmt.Sprintf("a %v b", op)
   108  				env := map[string]any{
   109  					"a": a,
   110  					"b": b,
   111  				}
   112  
   113  				config := conf.CreateNew()
   114  
   115  				tree, err := parser.Parse(input)
   116  				require.NoError(t, err)
   117  
   118  				_, err = checker.Check(tree, config)
   119  				require.NoError(t, err)
   120  
   121  				program, err := compiler.Compile(tree, config)
   122  				require.NoError(t, err)
   123  
   124  				_, err = vm.Run(program, env)
   125  				require.NoError(t, err)
   126  			}
   127  		}
   128  	}
   129  }
   130  
   131  type ErrorEnv struct {
   132  	InnerEnv InnerEnv
   133  }
   134  type InnerEnv struct{}
   135  
   136  func (ErrorEnv) WillError(param string) (bool, error) {
   137  	if param == "yes" {
   138  		return false, errors.New("error")
   139  	}
   140  	return true, nil
   141  }
   142  
   143  func (InnerEnv) WillError(param string) (bool, error) {
   144  	if param == "yes" {
   145  		return false, errors.New("inner error")
   146  	}
   147  	return true, nil
   148  }
   149  
   150  func TestRun_MethodWithError(t *testing.T) {
   151  	input := `WillError("yes")`
   152  
   153  	tree, err := parser.Parse(input)
   154  	require.NoError(t, err)
   155  
   156  	env := ErrorEnv{}
   157  	funcConf := conf.New(env)
   158  	_, err = checker.Check(tree, funcConf)
   159  	require.NoError(t, err)
   160  
   161  	program, err := compiler.Compile(tree, funcConf)
   162  	require.NoError(t, err)
   163  
   164  	out, err := vm.Run(program, env)
   165  	require.EqualError(t, err, "error (1:1)\n | WillError(\"yes\")\n | ^")
   166  	require.Equal(t, nil, out)
   167  
   168  	selfErr := errors.Unwrap(err)
   169  	require.NotNil(t, err)
   170  	require.Equal(t, "error", selfErr.Error())
   171  }
   172  
   173  func TestRun_FastMethods(t *testing.T) {
   174  	input := `hello() + world()`
   175  
   176  	tree, err := parser.Parse(input)
   177  	require.NoError(t, err)
   178  
   179  	env := map[string]any{
   180  		"hello": func(...any) any { return "hello " },
   181  		"world": func(...any) any { return "world" },
   182  	}
   183  	funcConf := conf.New(env)
   184  	_, err = checker.Check(tree, funcConf)
   185  	require.NoError(t, err)
   186  
   187  	program, err := compiler.Compile(tree, funcConf)
   188  	require.NoError(t, err)
   189  
   190  	out, err := vm.Run(program, env)
   191  	require.NoError(t, err)
   192  
   193  	require.Equal(t, "hello world", out)
   194  }
   195  
   196  func TestRun_InnerMethodWithError(t *testing.T) {
   197  	input := `InnerEnv.WillError("yes")`
   198  
   199  	tree, err := parser.Parse(input)
   200  	require.NoError(t, err)
   201  
   202  	env := ErrorEnv{}
   203  	funcConf := conf.New(env)
   204  	program, err := compiler.Compile(tree, funcConf)
   205  	require.NoError(t, err)
   206  
   207  	out, err := vm.Run(program, env)
   208  	require.EqualError(t, err, "inner error (1:10)\n | InnerEnv.WillError(\"yes\")\n | .........^")
   209  	require.Equal(t, nil, out)
   210  }
   211  
   212  func TestRun_InnerMethodWithError_NilSafe(t *testing.T) {
   213  	input := `InnerEnv?.WillError("yes")`
   214  
   215  	tree, err := parser.Parse(input)
   216  	require.NoError(t, err)
   217  
   218  	env := ErrorEnv{}
   219  	funcConf := conf.New(env)
   220  	program, err := compiler.Compile(tree, funcConf)
   221  	require.NoError(t, err)
   222  
   223  	out, err := vm.Run(program, env)
   224  	require.EqualError(t, err, "inner error (1:11)\n | InnerEnv?.WillError(\"yes\")\n | ..........^")
   225  	require.Equal(t, nil, out)
   226  }
   227  
   228  func TestRun_TaggedFieldName(t *testing.T) {
   229  	input := `value`
   230  
   231  	tree, err := parser.Parse(input)
   232  	require.NoError(t, err)
   233  
   234  	env := struct {
   235  		V string `expr:"value"`
   236  	}{
   237  		V: "hello world",
   238  	}
   239  
   240  	funcConf := conf.New(env)
   241  	_, err = checker.Check(tree, funcConf)
   242  	require.NoError(t, err)
   243  
   244  	program, err := compiler.Compile(tree, funcConf)
   245  	require.NoError(t, err)
   246  
   247  	out, err := vm.Run(program, env)
   248  	require.NoError(t, err)
   249  
   250  	require.Equal(t, "hello world", out)
   251  }
   252  
   253  func TestRun_OpInvalid(t *testing.T) {
   254  	program := &vm.Program{
   255  		Bytecode:  []vm.Opcode{vm.OpInvalid},
   256  		Arguments: []int{0},
   257  	}
   258  
   259  	_, err := vm.Run(program, nil)
   260  	require.EqualError(t, err, "invalid opcode")
   261  }