Skip to content

Commit ec07cc1

Browse files
committed
fix(tests): make tests work with tag expr_noreflectmethod
If all tests of a file are failing, just add a build constraint. If only some tests are failing, skip them with the SkipNoReflectMethod helper. Failing examples are moved into a dedicated test file with a build constraint.
1 parent ebfc047 commit ec07cc1

28 files changed

Lines changed: 334 additions & 227 deletions

.github/workflows/test.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@ jobs:
1212
strategy:
1313
matrix:
1414
go-versions: [ '1.18', '1.19', '1.20', '1.21', '1.22', '1.23', '1.24', '1.25', '1.26' ]
15+
tags: [ '', 'expr_noreflectmethod' ]
1516
steps:
1617
- uses: actions/checkout@v3
1718
- name: Setup Go ${{ matrix.go-version }}
1819
uses: actions/setup-go@v4
1920
with:
2021
go-version: ${{ matrix.go-version }}
2122
- name: Test
22-
run: go test ./...
23+
run: go test -tags=${{ matrix.tags }} ./...
2324

2425
debug:
2526
runs-on: ubuntu-latest

builtin/builtin_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
)
2020

2121
func TestBuiltin(t *testing.T) {
22+
assert.SkipNoReflectMethod(t)
2223
ArrayWithNil := []any{42}
2324
env := map[string]any{
2425
"ArrayOfString": []string{"foo", "bar", "baz"},
@@ -345,6 +346,7 @@ func TestBuiltin_types(t *testing.T) {
345346
}
346347

347348
func TestBuiltin_memory_limits(t *testing.T) {
349+
assert.SkipNoReflectMethod(t)
348350
tests := []struct {
349351
input string
350352
}{
@@ -696,6 +698,7 @@ func Test_int_unwraps_underlying_value(t *testing.T) {
696698
}
697699

698700
func TestBuiltin_with_deref(t *testing.T) {
701+
assert.SkipNoReflectMethod(t)
699702
x := 42
700703
arr := []int{1, 2, 3}
701704
arrStr := []string{"1", "2", "3"}

checker/checker_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
)
2222

2323
func TestCheck(t *testing.T) {
24+
assert.SkipNoReflectMethod(t)
2425
var tests = []struct {
2526
input string
2627
}{
@@ -155,6 +156,7 @@ func TestCheck(t *testing.T) {
155156
}
156157

157158
func TestCheck_error(t *testing.T) {
159+
assert.SkipNoReflectMethod(t)
158160
errorTests := []struct{ code, err string }{
159161
{
160162
`Foo.Bar.Not`,

compiler/compiler_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ func (e Env) Func() B {
4949
}
5050

5151
func TestCompile(t *testing.T) {
52+
assert.SkipNoReflectMethod(t)
5253
var tests = []struct {
5354
code string
5455
want vm.Program
@@ -436,6 +437,7 @@ func TestCompile_FuncTypes(t *testing.T) {
436437
}
437438

438439
func TestCompile_FuncTypes_with_Method(t *testing.T) {
440+
assert.SkipNoReflectMethod(t)
439441
env := mock.Env{}
440442
program, err := expr.Compile("FuncTyped('bar')", expr.Env(env))
441443
require.NoError(t, err)
@@ -647,6 +649,7 @@ func TestCompile_optimizes_jumps(t *testing.T) {
647649
}
648650

649651
func TestCompile_IntegerArgsFunc(t *testing.T) {
652+
assert.SkipNoReflectMethod(t)
650653
env := mock.Env{}
651654
tests := []struct{ code string }{
652655
{"FuncInt(0)"},
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
//go:build !expr_noreflectmethod
2+
3+
package expr_test
4+
5+
import (
6+
"fmt"
7+
"strings"
8+
"time"
9+
10+
"github.com/expr-lang/expr"
11+
"github.com/expr-lang/expr/test/mock"
12+
)
13+
14+
func ExampleEval() {
15+
output, err := expr.Eval("greet + name", map[string]any{
16+
"greet": "Hello, ",
17+
"name": "world!",
18+
})
19+
if err != nil {
20+
fmt.Printf("err: %v", err)
21+
return
22+
}
23+
24+
fmt.Printf("%v", output)
25+
26+
// Output: Hello, world!
27+
}
28+
29+
func ExampleEval_runtime_error() {
30+
_, err := expr.Eval(`map(1..3, {1 % (# - 3)})`, nil)
31+
fmt.Print(err)
32+
33+
// Output: runtime error: integer divide by zero (1:14)
34+
// | map(1..3, {1 % (# - 3)})
35+
// | .............^
36+
}
37+
38+
func ExampleEval_bytes_literal() {
39+
// Bytes literal returns []byte.
40+
output, err := expr.Eval(`b"abc"`, nil)
41+
if err != nil {
42+
fmt.Printf("%v", err)
43+
return
44+
}
45+
46+
fmt.Printf("%v", output)
47+
48+
// Output: [97 98 99]
49+
}
50+
51+
func ExampleEnv_tagged_field_names() {
52+
env := struct {
53+
FirstWord string
54+
Separator string `expr:"Space"`
55+
SecondWord string `expr:"second_word"`
56+
}{
57+
FirstWord: "Hello",
58+
Separator: " ",
59+
SecondWord: "World",
60+
}
61+
62+
output, err := expr.Eval(`FirstWord + Space + second_word`, env)
63+
if err != nil {
64+
fmt.Printf("%v", err)
65+
return
66+
}
67+
68+
fmt.Printf("%v", output)
69+
70+
// Output: Hello World
71+
}
72+
73+
func ExampleEnv_hidden_tagged_field_names() {
74+
type Internal struct {
75+
Visible string
76+
Hidden string `expr:"-"`
77+
}
78+
type environment struct {
79+
Visible string
80+
Hidden string `expr:"-"`
81+
HiddenInternal Internal `expr:"-"`
82+
VisibleInternal Internal
83+
}
84+
85+
env := environment{
86+
Hidden: "First level secret",
87+
HiddenInternal: Internal{
88+
Visible: "Second level secret",
89+
Hidden: "Also hidden",
90+
},
91+
VisibleInternal: Internal{
92+
Visible: "Not a secret",
93+
Hidden: "Hidden too",
94+
},
95+
}
96+
97+
hiddenValues := []string{
98+
`Hidden`,
99+
`HiddenInternal`,
100+
`HiddenInternal.Visible`,
101+
`HiddenInternal.Hidden`,
102+
`VisibleInternal["Hidden"]`,
103+
}
104+
for _, expression := range hiddenValues {
105+
output, err := expr.Eval(expression, env)
106+
if err == nil || !strings.Contains(err.Error(), "cannot fetch") {
107+
fmt.Printf("unexpected output: %v; err: %v\n", output, err)
108+
return
109+
}
110+
fmt.Printf("%q is hidden as expected\n", expression)
111+
}
112+
113+
visibleValues := []string{
114+
`Visible`,
115+
`VisibleInternal`,
116+
`VisibleInternal["Visible"]`,
117+
}
118+
for _, expression := range visibleValues {
119+
_, err := expr.Eval(expression, env)
120+
if err != nil {
121+
fmt.Printf("unexpected error: %v\n", err)
122+
return
123+
}
124+
fmt.Printf("%q is visible as expected\n", expression)
125+
}
126+
127+
testWithIn := []string{
128+
`not ("Hidden" in $env)`,
129+
`"Visible" in $env`,
130+
`not ("Hidden" in VisibleInternal)`,
131+
`"Visible" in VisibleInternal`,
132+
}
133+
for _, expression := range testWithIn {
134+
val, err := expr.Eval(expression, env)
135+
shouldBeTrue, ok := val.(bool)
136+
if err != nil || !ok || !shouldBeTrue {
137+
fmt.Printf("unexpected result; value: %v; error: %v\n", val, err)
138+
return
139+
}
140+
}
141+
142+
// Output: "Hidden" is hidden as expected
143+
// "HiddenInternal" is hidden as expected
144+
// "HiddenInternal.Visible" is hidden as expected
145+
// "HiddenInternal.Hidden" is hidden as expected
146+
// "VisibleInternal[\"Hidden\"]" is hidden as expected
147+
// "Visible" is visible as expected
148+
// "VisibleInternal" is visible as expected
149+
// "VisibleInternal[\"Visible\"]" is visible as expected
150+
}
151+
152+
func ExampleOperator() {
153+
code := `
154+
Now() > CreatedAt &&
155+
(Now() - CreatedAt).Hours() > 24
156+
`
157+
158+
type Env struct {
159+
CreatedAt time.Time
160+
Now func() time.Time
161+
Sub func(a, b time.Time) time.Duration
162+
After func(a, b time.Time) bool
163+
}
164+
165+
options := []expr.Option{
166+
expr.Env(Env{}),
167+
expr.Operator(">", "After"),
168+
expr.Operator("-", "Sub"),
169+
}
170+
171+
program, err := expr.Compile(code, options...)
172+
if err != nil {
173+
fmt.Printf("%v", err)
174+
return
175+
}
176+
177+
env := Env{
178+
CreatedAt: time.Date(2018, 7, 14, 0, 0, 0, 0, time.UTC),
179+
Now: func() time.Time { return time.Now() },
180+
Sub: func(a, b time.Time) time.Duration { return a.Sub(b) },
181+
After: func(a, b time.Time) bool { return a.After(b) },
182+
}
183+
184+
output, err := expr.Run(program, env)
185+
if err != nil {
186+
fmt.Printf("%v", err)
187+
return
188+
}
189+
190+
fmt.Printf("%v", output)
191+
192+
// Output: true
193+
}
194+
195+
func ExampleAllowUndefinedVariables_zero_value_functions() {
196+
code := `words == "" ? Split("foo,bar", ",") : Split(words, ",")`
197+
198+
// Env is map[string]string type on which methods are defined.
199+
env := mock.MapStringStringEnv{}
200+
201+
options := []expr.Option{
202+
expr.Env(env),
203+
expr.AllowUndefinedVariables(), // Allow to use undefined variables.
204+
}
205+
206+
program, err := expr.Compile(code, options...)
207+
if err != nil {
208+
fmt.Printf("%v", err)
209+
return
210+
}
211+
212+
output, err := expr.Run(program, env)
213+
if err != nil {
214+
fmt.Printf("%v", err)
215+
return
216+
}
217+
fmt.Printf("%v", output)
218+
219+
// Output: [foo bar]
220+
}
221+
222+
func ExampleTimezone() {
223+
program, err := expr.Compile(`now().Location().String()`, expr.Timezone("Asia/Kamchatka"))
224+
if err != nil {
225+
fmt.Printf("%v", err)
226+
return
227+
}
228+
229+
output, err := expr.Run(program, nil)
230+
if err != nil {
231+
fmt.Printf("%v", err)
232+
return
233+
}
234+
235+
fmt.Printf("%v", output)
236+
// Output: Asia/Kamchatka
237+
}

0 commit comments

Comments
 (0)