Files
expr/test/deref/deref_test.go
2025-03-09 13:07:38 +01:00

340 lines
7.1 KiB
Go

package deref_test
import (
"context"
"testing"
"time"
"github.com/expr-lang/expr/internal/testify/assert"
"github.com/expr-lang/expr/internal/testify/require"
"github.com/expr-lang/expr"
)
func TestDeref_binary(t *testing.T) {
i := 1
env := map[string]any{
"i": &i,
"obj": map[string]any{
"i": &i,
},
}
t.Run("==", func(t *testing.T) {
program, err := expr.Compile(`i == 1 && obj.i == 1`, expr.Env(env))
require.NoError(t, err)
out, err := expr.Run(program, env)
require.NoError(t, err)
require.Equal(t, true, out)
})
t.Run("><", func(t *testing.T) {
program, err := expr.Compile(`i > 0 && obj.i < 99`, expr.Env(env))
require.NoError(t, err)
out, err := expr.Run(program, env)
require.NoError(t, err)
require.Equal(t, true, out)
})
t.Run("??+", func(t *testing.T) {
program, err := expr.Compile(`(i ?? obj.i) + 1`, expr.Env(env))
require.NoError(t, err)
out, err := expr.Run(program, env)
require.NoError(t, err)
require.Equal(t, 2, out)
})
}
func TestDeref_unary(t *testing.T) {
i := 1
ok := true
env := map[string]any{
"i": &i,
"obj": map[string]any{
"ok": &ok,
},
}
program, err := expr.Compile(`-i < 0 && !!obj.ok`, expr.Env(env))
require.NoError(t, err)
out, err := expr.Run(program, env)
require.NoError(t, err)
require.Equal(t, true, out)
}
func TestDeref_eval(t *testing.T) {
i := 1
env := map[string]any{
"i": &i,
"obj": map[string]any{
"i": &i,
},
}
out, err := expr.Eval(`i == 1 && obj.i == 1`, env)
require.NoError(t, err)
require.Equal(t, true, out)
}
func TestDeref_emptyCtx(t *testing.T) {
program, err := expr.Compile(`ctx`)
require.NoError(t, err)
output, err := expr.Run(program, map[string]any{
"ctx": context.Background(),
})
require.NoError(t, err)
require.Implements(t, new(context.Context), output)
}
func TestDeref_emptyCtx_Eval(t *testing.T) {
output, err := expr.Eval(`ctx`, map[string]any{
"ctx": context.Background(),
})
require.NoError(t, err)
require.Implements(t, new(context.Context), output)
}
func TestDeref_context_WithValue(t *testing.T) {
program, err := expr.Compile(`ctxWithValue`)
require.NoError(t, err)
output, err := expr.Run(program, map[string]any{
"ctxWithValue": context.WithValue(context.Background(), "value", "test"),
})
require.NoError(t, err)
require.Implements(t, new(context.Context), output)
}
func TestDeref_method_on_int_pointer(t *testing.T) {
output, err := expr.Eval(`foo.Bar()`, map[string]any{
"foo": new(foo),
})
require.NoError(t, err)
require.Equal(t, 42, output)
}
type foo int
func (f *foo) Bar() int {
return 42
}
func TestDeref_multiple_pointers(t *testing.T) {
a := 42
b := &a
c := &b
t.Run("returned as is", func(t *testing.T) {
output, err := expr.Eval(`c`, map[string]any{
"c": c,
})
require.NoError(t, err)
require.Equal(t, c, output)
require.IsType(t, (**int)(nil), output)
})
t.Run("+ works", func(t *testing.T) {
output, err := expr.Eval(`c+2`, map[string]any{
"c": c,
})
require.NoError(t, err)
require.Equal(t, 44, output)
})
}
func TestDeref_pointer_of_interface(t *testing.T) {
v := 42
a := &v
b := any(a)
c := any(&b)
t.Run("returned as is", func(t *testing.T) {
output, err := expr.Eval(`c`, map[string]any{
"c": c,
})
require.NoError(t, err)
require.Equal(t, c, output)
require.IsType(t, (*interface{})(nil), output)
})
t.Run("+ works", func(t *testing.T) {
output, err := expr.Eval(`c+2`, map[string]any{
"c": c,
})
require.NoError(t, err)
require.Equal(t, 44, output)
})
}
func TestDeref_nil(t *testing.T) {
var b *int = nil
c := &b
t.Run("returned as is", func(t *testing.T) {
output, err := expr.Eval(`c`, map[string]any{
"c": c,
})
require.NoError(t, err)
require.Equal(t, c, output)
require.IsType(t, (**int)(nil), output)
})
t.Run("== nil works", func(t *testing.T) {
output, err := expr.Eval(`c == nil`, map[string]any{
"c": c,
})
require.NoError(t, err)
require.Equal(t, true, output)
})
}
func TestDeref_nil_in_pointer_of_interface(t *testing.T) {
var a *int32 = nil
b := any(a)
c := any(&b)
t.Run("returned as is", func(t *testing.T) {
output, err := expr.Eval(`c`, map[string]any{
"c": c,
})
require.NoError(t, err)
require.Equal(t, c, output)
require.IsType(t, (*interface{})(nil), output)
})
t.Run("== nil works", func(t *testing.T) {
output, err := expr.Eval(`c == nil`, map[string]any{
"c": c,
})
require.NoError(t, err)
require.Equal(t, true, output)
})
}
func TestDeref_commutative(t *testing.T) {
a := "ok"
b := "ok"
type Env struct {
A string
B *string
}
env := Env{
A: a,
B: &b,
}
tests := []struct {
code string
want bool
}{
{`A == B`, true},
{`B == A`, true},
{`A != B`, false},
{`B != A`, false},
}
for _, test := range tests {
t.Run(test.code, func(t *testing.T) {
program, err := expr.Compile(test.code, expr.Env(env))
require.NoError(t, err)
out, err := expr.Run(program, env)
require.NoError(t, err)
require.Equal(t, test.want, out)
})
}
}
func TestDeref_fetch_from_interface_mix_pointer(t *testing.T) {
type FooBar struct {
Value string
}
foobar := &FooBar{"waldo"}
var foobarAny any = foobar
var foobarPtrAny any = &foobarAny
res, err := expr.Eval("foo.Value", map[string]any{
"foo": foobarPtrAny,
})
assert.NoError(t, err)
assert.Equal(t, "waldo", res)
}
func TestDeref_func_args(t *testing.T) {
i := 20
env := map[string]any{
"var": &i,
"fn": func(p int) int {
return p + 1
},
}
program, err := expr.Compile(`fn(var) + fn(var + 0)`, expr.Env(env))
require.NoError(t, err)
out, err := expr.Run(program, env)
require.NoError(t, err)
require.Equal(t, 42, out)
}
func TestDeref_struct_func_args(t *testing.T) {
n, _ := time.Parse(time.RFC3339, "2024-05-12T18:30:00+00:00")
duration := 30 * time.Minute
env := map[string]any{
"time": n,
"duration": &duration,
}
program, err := expr.Compile(`time.Add(duration).Format('2006-01-02T15:04:05Z07:00')`, expr.Env(env))
require.NoError(t, err)
out, err := expr.Run(program, env)
require.NoError(t, err)
require.Equal(t, "2024-05-12T19:00:00Z", out)
}
func TestDeref_ignore_func_args(t *testing.T) {
f := foo(1)
env := map[string]any{
"foo": &f,
"fn": func(f *foo) int {
return f.Bar()
},
}
program, err := expr.Compile(`fn(foo)`, expr.Env(env))
require.NoError(t, err)
out, err := expr.Run(program, env)
require.NoError(t, err)
require.Equal(t, 42, out)
}
func TestDeref_ignore_struct_func_args(t *testing.T) {
n := time.Now()
location, _ := time.LoadLocation("UTC")
env := map[string]any{
"time": n,
"location": location,
}
program, err := expr.Compile(`time.In(location).Location().String()`, expr.Env(env))
require.NoError(t, err)
out, err := expr.Run(program, env)
require.NoError(t, err)
require.Equal(t, "UTC", out)
}
func TestDeref_keep_pointer_if_arg_in_interface(t *testing.T) {
x := 42
env := map[string]any{
"x": &x,
"fn": func(p any) int {
return *p.(*int) + 1
},
}
program, err := expr.Compile(`fn(x)`, expr.Env(env))
require.NoError(t, err)
out, err := expr.Run(program, env)
require.NoError(t, err)
require.Equal(t, 43, out)
}