@@ -21,6 +21,7 @@ import (
21
21
"os"
22
22
"path/filepath"
23
23
"reflect"
24
+ "slices"
24
25
"strings"
25
26
"testing"
26
27
@@ -133,7 +134,7 @@ func TriggerTests(t *testing.T, testRunnerOpts ...TestRunnerOption) {
133
134
if err != nil {
134
135
t .Fatalf ("error creating test runner: %v" , err )
135
136
}
136
- programs , err := tr .Programs (t )
137
+ programs , err := tr .Programs (t , tr . testProgramOptions ... )
137
138
if err != nil {
138
139
t .Fatalf ("error creating programs: %v" , err )
139
140
}
@@ -275,6 +276,7 @@ func DefaultTestSuiteParser(path string) TestRunnerOption {
275
276
// - Test Suite File Path: The path to the test suite file.
276
277
// - File Descriptor Set Path: The path to the file descriptor set file.
277
278
// - test Suite Parser: A parser for a test suite file serialized in Textproto/YAML format.
279
+ // - test Program Options: A list of options to be used when creating the CEL programs.
278
280
//
279
281
// The TestRunner provides the following methods:
280
282
// - Programs: Creates a list of CEL programs from the input expressions.
@@ -286,6 +288,7 @@ type TestRunner struct {
286
288
TestSuiteFilePath string
287
289
FileDescriptorSetPath string
288
290
testSuiteParser TestSuiteParser
291
+ testProgramOptions []cel.ProgramOption
289
292
}
290
293
291
294
// Test represents a single test case to be executed. It encompasses the following:
@@ -295,12 +298,12 @@ type TestRunner struct {
295
298
// returns a TestResult.
296
299
type Test struct {
297
300
name string
298
- input interpreter.Activation
301
+ input interpreter.PartialActivation
299
302
resultMatcher func (ref.Val , error ) TestResult
300
303
}
301
304
302
305
// NewTest creates a new Test with the provided name, input and result matcher.
303
- func NewTest (name string , input interpreter.Activation , resultMatcher func (ref.Val , error ) TestResult ) * Test {
306
+ func NewTest (name string , input interpreter.PartialActivation , resultMatcher func (ref.Val , error ) TestResult ) * Test {
304
307
return & Test {
305
308
name : name ,
306
309
input : input ,
@@ -417,6 +420,17 @@ func fileDescriptorSet(path string) (*descpb.FileDescriptorSet, error) {
417
420
return fds , nil
418
421
}
419
422
423
+ // PartialEvalProgramOption returns a TestRunnerOption which enables partial evaluation for the CEL
424
+ // program by setting the OptPartialEval eval option.
425
+ //
426
+ // Note: The test setup uses env.PartialVars() for creating PartialActivation.
427
+ func PartialEvalProgramOption () TestRunnerOption {
428
+ return func (tr * TestRunner ) (* TestRunner , error ) {
429
+ tr .testProgramOptions = append (tr .testProgramOptions , cel .EvalOptions (cel .OptPartialEval ))
430
+ return tr , nil
431
+ }
432
+ }
433
+
420
434
// Program represents the result of creating CEL programs for the configured expressions in the
421
435
// test runner. It encompasses the following:
422
436
// - CELProgram - the evaluable CEL program
@@ -461,6 +475,8 @@ func (tr *TestRunner) Programs(t *testing.T, opts ...cel.ProgramOption) ([]Progr
461
475
462
476
// Tests creates a list of tests from the test suite file and test suite parser configured in the
463
477
// test runner.
478
+ //
479
+ // Note: The test setup uses env.PartialVars() for creating PartialActivation.
464
480
func (tr * TestRunner ) Tests (t * testing.T ) ([]* Test , error ) {
465
481
if tr .Compiler == nil {
466
482
return nil , fmt .Errorf ("compiler is not set" )
@@ -507,13 +523,14 @@ func (tr *TestRunner) createTestsFromTextproto(t *testing.T, testSuite *conforma
507
523
return tests , nil
508
524
}
509
525
510
- func (tr * TestRunner ) createTestInputFromPB (t * testing.T , testCase * conformancepb.TestCase ) (interpreter.Activation , error ) {
526
+ func (tr * TestRunner ) createTestInputFromPB (t * testing.T , testCase * conformancepb.TestCase ) (interpreter.PartialActivation , error ) {
511
527
t .Helper ()
512
528
input := map [string ]any {}
513
529
e , err := tr .CreateEnv ()
514
530
if err != nil {
515
531
return nil , err
516
532
}
533
+ var activation interpreter.Activation
517
534
if testCase .GetInputContext () != nil {
518
535
if len (testCase .GetInput ()) != 0 {
519
536
return nil , fmt .Errorf ("only one of input and input_context can be provided at a time" )
@@ -529,15 +546,22 @@ func (tr *TestRunner) createTestInputFromPB(t *testing.T, testCase *conformancep
529
546
if err != nil {
530
547
return nil , fmt .Errorf ("context variable is not a valid proto: %w" , err )
531
548
}
532
- return cel .ContextProtoVars (ctx .(proto.Message ))
549
+ activation , err = cel .ContextProtoVars (ctx .(proto.Message ))
550
+ if err != nil {
551
+ return nil , fmt .Errorf ("cel.ContextProtoVars() failed: %w" , err )
552
+ }
533
553
case * conformancepb.InputContext_ContextMessage :
534
554
refVal := e .CELTypeAdapter ().NativeToValue (testInput .ContextMessage )
535
555
ctx , err := refVal .ConvertToNative (reflect .TypeOf ((* proto .Message )(nil )).Elem ())
536
556
if err != nil {
537
557
return nil , fmt .Errorf ("context variable is not a valid proto: %w" , err )
538
558
}
539
- return cel .ContextProtoVars (ctx .(proto.Message ))
559
+ activation , err = cel .ContextProtoVars (ctx .(proto.Message ))
560
+ if err != nil {
561
+ return nil , fmt .Errorf ("cel.ContextProtoVars() failed: %w" , err )
562
+ }
540
563
}
564
+ return e .PartialVars (activation )
541
565
}
542
566
for k , v := range testCase .GetInput () {
543
567
switch v .GetKind ().(type ) {
@@ -553,7 +577,11 @@ func (tr *TestRunner) createTestInputFromPB(t *testing.T, testCase *conformancep
553
577
}
554
578
}
555
579
}
556
- return interpreter .NewActivation (input )
580
+ activation , err = interpreter .NewActivation (input )
581
+ if err != nil {
582
+ return nil , fmt .Errorf ("interpreter.NewActivation(%q) failed: %w" , input , err )
583
+ }
584
+ return e .PartialVars (activation )
557
585
}
558
586
559
587
func (tr * TestRunner ) createResultMatcherFromPB (t * testing.T , testCase * conformancepb.TestCase ) (func (ref.Val , error ) TestResult , error ) {
@@ -627,11 +655,34 @@ func (tr *TestRunner) createResultMatcherFromPB(t *testing.T, testCase *conforma
627
655
return failureResult
628
656
}, nil
629
657
case * conformancepb.TestOutput_Unknown :
630
- // TODO: to implement
658
+ // Validate that all expected unknown expression ids are returned by the evaluation result.
659
+ return func (out ref.Val , err error ) TestResult {
660
+ expectedUnknownIDs := testOutput .Unknown .GetExprs ()
661
+ if err == nil && types .IsUnknown (out ) {
662
+ actualUnknownIDs := out .Value ().(* types.Unknown ).IDs ()
663
+ return compareUnknownIDs (expectedUnknownIDs , actualUnknownIDs )
664
+ }
665
+ return TestResult {Success : false , Wanted : fmt .Sprintf ("unknown value %v" , expectedUnknownIDs ), Error : err }
666
+ }, nil
631
667
}
632
668
return nil , nil
633
669
}
634
670
671
+ func compareUnknownIDs (expectedUnknownIDs , actualUnknownIDs []int64 ) TestResult {
672
+ sortOption := cmp .Transformer ("Sort" , func (in []int64 ) []int64 {
673
+ out := append ([]int64 {}, in ... )
674
+ slices .Sort (out )
675
+ return out
676
+ })
677
+ if diff := cmp .Diff (expectedUnknownIDs , actualUnknownIDs , sortOption ); diff != "" {
678
+ return TestResult {
679
+ Success : false ,
680
+ Wanted : fmt .Sprintf ("unknown value %v" , expectedUnknownIDs ),
681
+ Error : fmt .Errorf ("mismatched test output with diff (-got +want):\n %s" , diff )}
682
+ }
683
+ return TestResult {Success : true }
684
+ }
685
+
635
686
func refValueToExprValue (refVal ref.Val ) (* exprpb.ExprValue , error ) {
636
687
if types .IsUnknown (refVal ) {
637
688
return & exprpb.ExprValue {
@@ -704,8 +755,13 @@ func (tr *TestRunner) createTestsFromYAML(t *testing.T, testSuite *test.Suite) (
704
755
return tests , nil
705
756
}
706
757
707
- func (tr * TestRunner ) createTestInput (t * testing.T , testCase * test.Case ) (interpreter.Activation , error ) {
758
+ func (tr * TestRunner ) createTestInput (t * testing.T , testCase * test.Case ) (interpreter.PartialActivation , error ) {
708
759
t .Helper ()
760
+ e , err := tr .CreateEnv ()
761
+ if err != nil {
762
+ return nil , err
763
+ }
764
+ var activation interpreter.Activation
709
765
if testCase .InputContext != nil && testCase .InputContext .ContextExpr != "" {
710
766
if len (testCase .Input ) != 0 {
711
767
return nil , fmt .Errorf ("only one of input and input_context can be provided at a time" )
@@ -719,7 +775,11 @@ func (tr *TestRunner) createTestInput(t *testing.T, testCase *test.Case) (interp
719
775
if err != nil {
720
776
return nil , fmt .Errorf ("context variable is not a valid proto: %w" , err )
721
777
}
722
- return cel .ContextProtoVars (ctx .(proto.Message ))
778
+ activation , err = cel .ContextProtoVars (ctx .(proto.Message ))
779
+ if err != nil {
780
+ return nil , fmt .Errorf ("cel.ContextProtoVars() failed: %w" , err )
781
+ }
782
+ return e .PartialVars (activation )
723
783
}
724
784
input := map [string ]any {}
725
785
for k , v := range testCase .Input {
@@ -733,7 +793,11 @@ func (tr *TestRunner) createTestInput(t *testing.T, testCase *test.Case) (interp
733
793
}
734
794
input [k ] = v .Value
735
795
}
736
- return interpreter .NewActivation (input )
796
+ activation , err = interpreter .NewActivation (input )
797
+ if err != nil {
798
+ return nil , fmt .Errorf ("interpreter.NewActivation(%q) failed: %w" , input , err )
799
+ }
800
+ return e .PartialVars (activation )
737
801
}
738
802
739
803
func (tr * TestRunner ) createResultMatcher (t * testing.T , testOutput * test.Output ) (func (ref.Val , error ) TestResult , error ) {
@@ -793,7 +857,13 @@ func (tr *TestRunner) createResultMatcher(t *testing.T, testOutput *test.Output)
793
857
}, nil
794
858
}
795
859
if testOutput .UnknownSet != nil {
796
- // TODO: to implement
860
+ return func (out ref.Val , err error ) TestResult {
861
+ if err == nil && types .IsUnknown (out ) {
862
+ unknownIDs := out .Value ().(* types.Unknown ).IDs ()
863
+ return compareUnknownIDs (testOutput .UnknownSet , unknownIDs )
864
+ }
865
+ return TestResult {Success : false , Wanted : fmt .Sprintf ("unknown value %v" , testOutput .UnknownSet ), Error : err }
866
+ }, nil
797
867
}
798
868
return nil , nil
799
869
}
0 commit comments