Skip to content

Commit f4cee07

Browse files
Restoring tests pruned during rebase
1 parent 80a95d3 commit f4cee07

File tree

1 file changed

+303
-0
lines changed

1 file changed

+303
-0
lines changed

cel/env_test.go

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
package cel
1616

1717
import (
18+
"errors"
1819
"fmt"
1920
"math"
2021
"reflect"
22+
"strings"
2123
"sync"
2224
"testing"
2325

@@ -419,6 +421,307 @@ func TestEnvToConfig(t *testing.T) {
419421
}
420422
}
421423

424+
func TestEnvFromConfig(t *testing.T) {
425+
type exprCase struct {
426+
name string
427+
in any
428+
expr string
429+
iss error
430+
out ref.Val
431+
}
432+
tests := []struct {
433+
name string
434+
beforeOpts []EnvOption
435+
afterOpts []EnvOption
436+
conf *env.Config
437+
exprs []exprCase
438+
}{
439+
{
440+
name: "std env",
441+
conf: env.NewConfig("std env"),
442+
exprs: []exprCase{
443+
{
444+
name: "literal",
445+
expr: "'hello world'",
446+
out: types.String("hello world"),
447+
},
448+
{
449+
name: "size",
450+
expr: "'hello world'.size()",
451+
out: types.Int(11),
452+
},
453+
},
454+
},
455+
{
456+
name: "std env - imports",
457+
beforeOpts: []EnvOption{Types(&proto3pb.TestAllTypes{})},
458+
conf: env.NewConfig("std env - context proto").
459+
AddImports(env.NewImport("google.expr.proto3.test.TestAllTypes")),
460+
exprs: []exprCase{
461+
{
462+
name: "literal",
463+
expr: "TestAllTypes{single_int64: 15}.single_int64",
464+
out: types.Int(15),
465+
},
466+
},
467+
},
468+
{
469+
name: "std env - context proto",
470+
beforeOpts: []EnvOption{Types(&proto3pb.TestAllTypes{})},
471+
conf: env.NewConfig("std env - context proto").
472+
SetContainer("google.expr.proto3.test").
473+
SetContextVariable(env.NewContextVariable("google.expr.proto3.test.TestAllTypes")),
474+
exprs: []exprCase{
475+
{
476+
name: "field select literal",
477+
in: mustContextProto(t, &proto3pb.TestAllTypes{SingleInt64: 10}),
478+
expr: "TestAllTypes{single_int64: single_int64}.single_int64",
479+
out: types.Int(10),
480+
},
481+
},
482+
},
483+
{
484+
name: "custom env - variables",
485+
beforeOpts: []EnvOption{Types(&proto3pb.TestAllTypes{})},
486+
conf: env.NewConfig("custom env - variables").
487+
SetStdLib(env.NewLibrarySubset().SetDisabled(true)).
488+
SetContainer("google.expr.proto3.test").
489+
AddVariables(env.NewVariable("single_int64", env.NewTypeDesc("int"))),
490+
exprs: []exprCase{
491+
{
492+
name: "field select literal",
493+
in: map[string]any{"single_int64": 42},
494+
expr: "TestAllTypes{single_int64: single_int64}.single_int64",
495+
out: types.Int(42),
496+
},
497+
{
498+
name: "invalid operator",
499+
in: map[string]any{"single_int64": 42},
500+
expr: "TestAllTypes{single_int64: single_int64}.single_int64 + 1",
501+
iss: errors.New("undeclared reference"),
502+
},
503+
},
504+
},
505+
{
506+
name: "custom env - functions",
507+
afterOpts: []EnvOption{
508+
Function("plus",
509+
MemberOverload("int_plus_int", []*Type{IntType, IntType}, IntType,
510+
BinaryBinding(func(lhs, rhs ref.Val) ref.Val {
511+
l := lhs.(types.Int)
512+
r := rhs.(types.Int)
513+
return l + r
514+
}),
515+
),
516+
)},
517+
conf: env.NewConfig("custom env - functions").
518+
SetStdLib(env.NewLibrarySubset().SetDisabled(true)).
519+
AddVariables(env.NewVariable("x", env.NewTypeDesc("int"))).
520+
AddFunctions(env.NewFunction("plus",
521+
env.NewMemberOverload("int_plus_int",
522+
env.NewTypeDesc("int"),
523+
[]*env.TypeDesc{env.NewTypeDesc("int")},
524+
env.NewTypeDesc("int"),
525+
),
526+
)),
527+
exprs: []exprCase{
528+
{
529+
name: "plus",
530+
in: map[string]any{"x": 42},
531+
expr: "x.plus(2)",
532+
out: types.Int(44),
533+
},
534+
{
535+
name: "plus invalid type",
536+
in: map[string]any{"x": 42},
537+
expr: "x.plus(2.0)",
538+
iss: errors.New("no matching overload"),
539+
},
540+
},
541+
},
542+
{
543+
name: "pure custom env",
544+
beforeOpts: []EnvOption{func(*Env) (*Env, error) {
545+
return NewCustomEnv()
546+
}},
547+
conf: env.NewConfig("pure custom env").SetStdLib(
548+
env.NewLibrarySubset().AddIncludedFunctions([]*env.Function{{Name: "_==_"}}...),
549+
),
550+
exprs: []exprCase{
551+
{
552+
name: "equals",
553+
expr: "'hello world' == 'hello'",
554+
out: types.False,
555+
},
556+
{
557+
name: "not equals - invalid",
558+
expr: "'hello world' != 'hello'",
559+
iss: errors.New("undeclared reference"),
560+
},
561+
},
562+
},
563+
{
564+
name: "std env - allow subset",
565+
conf: env.NewConfig("std env - allow subset").SetStdLib(
566+
env.NewLibrarySubset().AddIncludedFunctions([]*env.Function{{Name: "_==_"}}...),
567+
),
568+
exprs: []exprCase{
569+
{
570+
name: "equals",
571+
expr: "'hello world' == 'hello'",
572+
out: types.False,
573+
},
574+
{
575+
name: "not equals - invalid",
576+
expr: "'hello world' != 'hello'",
577+
iss: errors.New("undeclared reference"),
578+
},
579+
},
580+
},
581+
{
582+
name: "std env - deny subset",
583+
conf: env.NewConfig("std env - deny subset").SetStdLib(
584+
env.NewLibrarySubset().AddExcludedFunctions([]*env.Function{{Name: "size"}}...),
585+
),
586+
exprs: []exprCase{
587+
{
588+
name: "size - invalid",
589+
expr: "'hello world'.size()",
590+
iss: errors.New("undeclared reference"),
591+
},
592+
{
593+
name: "equals",
594+
expr: "'hello world' == 'hello'",
595+
out: types.False,
596+
},
597+
},
598+
},
599+
{
600+
name: "extensions",
601+
conf: env.NewConfig("extensions").
602+
AddVariables(
603+
env.NewVariable("m",
604+
env.NewTypeDesc("map", env.NewTypeDesc("string"), env.NewTypeDesc("string")))).
605+
AddExtensions(env.NewExtension("optional", math.MaxUint32)),
606+
exprs: []exprCase{
607+
{
608+
name: "optional none",
609+
expr: "optional.none()",
610+
out: types.OptionalNone,
611+
},
612+
{
613+
name: "optional key",
614+
expr: "m.?key.hasValue()",
615+
in: map[string]any{"m": map[string]string{"key": "value"}},
616+
out: types.True,
617+
},
618+
},
619+
},
620+
}
621+
for _, tst := range tests {
622+
tc := tst
623+
t.Run(tc.name, func(t *testing.T) {
624+
opts := tc.beforeOpts
625+
opts = append(opts, FromConfig(tc.conf, func(elem any) (EnvOption, bool) {
626+
if ext, ok := elem.(*env.Extension); ok && ext.Name == "optional" {
627+
ver, _ := ext.GetVersion()
628+
return OptionalTypes(OptionalTypesVersion(ver)), true
629+
}
630+
return nil, false
631+
}))
632+
opts = append(opts, tc.afterOpts...)
633+
var e *Env
634+
var err error
635+
if tc.conf.StdLib != nil {
636+
e, err = NewCustomEnv(opts...)
637+
} else {
638+
e, err = NewEnv(opts...)
639+
}
640+
if err != nil {
641+
t.Fatalf("NewEnv(FromConfig()) failed: %v", err)
642+
}
643+
for _, ex := range tc.exprs {
644+
t.Run(ex.name, func(t *testing.T) {
645+
ast, iss := e.Compile(ex.expr)
646+
if iss.Err() != nil {
647+
if ex.iss == nil || !strings.Contains(iss.Err().Error(), ex.iss.Error()) {
648+
t.Errorf("e.Compile() failed with %v, wanted %v", iss.Err(), ex.iss)
649+
}
650+
return
651+
}
652+
if ex.iss != nil {
653+
t.Fatalf("e.Compile() succeeded, wanted error %v", ex.iss)
654+
}
655+
prg, err := e.Program(ast)
656+
if err != nil {
657+
t.Fatalf("e.Program() failed: %v", err)
658+
}
659+
var in any = map[string]any{}
660+
if ex.in != nil {
661+
in = ex.in
662+
}
663+
out, _, err := prg.Eval(in)
664+
if err != nil {
665+
t.Fatalf("prg.Eval() failed: %v", err)
666+
}
667+
if out.Equal(ex.out) != types.True {
668+
t.Errorf("prg.Eval() got %v, wanted %v", out, ex.out)
669+
}
670+
})
671+
}
672+
})
673+
}
674+
}
675+
676+
func TestEnvFromConfigErrors(t *testing.T) {
677+
tests := []struct {
678+
name string
679+
conf *env.Config
680+
want error
681+
}{
682+
{
683+
name: "invalid subset",
684+
conf: env.NewConfig("invalid subset").SetStdLib(env.NewLibrarySubset().SetDisableMacros(true)),
685+
want: errors.New("invalid subset"),
686+
},
687+
{
688+
name: "invalid import",
689+
conf: env.NewConfig("invalid import").AddImports(env.NewImport("")),
690+
want: errors.New("invalid import"),
691+
},
692+
{
693+
name: "invalid context proto",
694+
conf: env.NewConfig("invalid context proto").SetContextVariable(env.NewContextVariable("invalid")),
695+
want: errors.New("invalid context proto type"),
696+
},
697+
{
698+
name: "undefined variable type",
699+
conf: env.NewConfig("undefined variable type").AddVariables(env.NewVariable("undef", env.NewTypeDesc("undefined"))),
700+
want: errors.New("invalid variable"),
701+
},
702+
{
703+
name: "undefined function type",
704+
conf: env.NewConfig("undefined function type").AddFunctions(env.NewFunction("invalid", env.NewOverload("invalid", []*env.TypeDesc{}, env.NewTypeDesc("undefined")))),
705+
want: errors.New("invalid function"),
706+
},
707+
{
708+
name: "unrecognized extension",
709+
conf: env.NewConfig("unrecognized extension").
710+
AddExtensions(env.NewExtension("optional", math.MaxUint32)),
711+
want: errors.New("unrecognized extension"),
712+
},
713+
}
714+
for _, tst := range tests {
715+
tc := tst
716+
t.Run(tc.name, func(t *testing.T) {
717+
_, err := NewEnv(FromConfig(tc.conf))
718+
if err == nil || !strings.Contains(err.Error(), tc.want.Error()) {
719+
t.Fatalf("NewEnv(FromConfig()) got %v, wanted error containing %v", err, tc.want)
720+
}
721+
})
722+
}
723+
}
724+
422725
func BenchmarkNewCustomEnvLazy(b *testing.B) {
423726
b.ResetTimer()
424727
for i := 0; i < b.N; i++ {

0 commit comments

Comments
 (0)