Skip to content

Commit 6d26f98

Browse files
jalsethashutosh-narkar
authored andcommitted
feat: Support raising builtin errors in the test runner
It can be useful to know if Rego unit tests fail due to logic errors, or if a builtin raised an error, such as parsing JSON input that was typo'd. Signed-off-by: James Alseth <james@jalseth.me>
1 parent 414df4e commit 6d26f98

2 files changed

Lines changed: 88 additions & 4 deletions

File tree

‎tester/runner.go‎

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ type Runner struct {
118118
cover topdown.QueryTracer
119119
trace bool
120120
enablePrintStatements bool
121+
raiseBuiltinErrors bool
121122
runtime *ast.Term
122123
timeout time.Duration
123124
modules map[string]*ast.Module
@@ -140,6 +141,13 @@ func (r *Runner) SetCompiler(compiler *ast.Compiler) *Runner {
140141
return r
141142
}
142143

144+
// RaiseBuiltinErrors sets the runner to raise errors encountered by builtins
145+
// such as parsing input.
146+
func (r *Runner) RaiseBuiltinErrors(enabled bool) *Runner {
147+
r.raiseBuiltinErrors = enabled
148+
return r
149+
}
150+
143151
type Builtin struct {
144152
Decl *ast.Builtin
145153
Func func(*rego.Rego)
@@ -461,7 +469,7 @@ func (r *Runner) runTest(ctx context.Context, txn storage.Transaction, mod *ast.
461469
}
462470

463471
printbuf := bytes.NewBuffer(nil)
464-
472+
var builtinErrors []topdown.Error
465473
rg := rego.New(
466474
rego.Store(r.store),
467475
rego.Transaction(txn),
@@ -471,6 +479,7 @@ func (r *Runner) runTest(ctx context.Context, txn storage.Transaction, mod *ast.
471479
rego.Runtime(r.runtime),
472480
rego.Target(r.target),
473481
rego.PrintHook(topdown.NewPrintHook(printbuf)),
482+
rego.BuiltinErrorList(&builtinErrors),
474483
)
475484

476485
// Register custom builtins on rego instance
@@ -483,15 +492,24 @@ func (r *Runner) runTest(ctx context.Context, txn storage.Transaction, mod *ast.
483492
dt := time.Since(t0)
484493

485494
var trace []*topdown.Event
486-
487495
if bufferTracer != nil {
488496
trace = *bufferTracer
489497
}
490498

491499
tr := newResult(rule.Loc(), mod.Package.Path.String(), rule.Head.Ref().String(), dt, trace, printbuf.Bytes())
492-
tr.Error = err
493-
var stop bool
494500

501+
// If there was an error other than errors from builtins, prefer that error.
502+
if err != nil {
503+
tr.Error = err
504+
} else if r.raiseBuiltinErrors && len(builtinErrors) > 0 {
505+
if len(builtinErrors) == 1 {
506+
tr.Error = &builtinErrors[0]
507+
} else {
508+
tr.Error = fmt.Errorf("%v", builtinErrors)
509+
}
510+
}
511+
512+
var stop bool
495513
if err != nil {
496514
if topdown.IsCancel(err) || wasm_errors.IsCancel(err) {
497515
stop = ctx.Err() != context.DeadlineExceeded

‎tester/runner_test.go‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package tester_test
66

77
import (
88
"context"
9+
"fmt"
910
"reflect"
1011
"testing"
1112
"time"
@@ -609,3 +610,68 @@ func TestRunnerWithCustomBuiltin(t *testing.T) {
609610
}
610611
})
611612
}
613+
614+
func TestRunnerWithBuiltinErrors(t *testing.T) {
615+
const ruleTemplate = `package test
616+
test_json_parsing {
617+
x := json.unmarshal("%s")
618+
x.test == 123
619+
}`
620+
621+
testCases := []struct {
622+
desc string
623+
json string
624+
builtinErrors bool
625+
wantErr bool
626+
}{
627+
{
628+
desc: "Valid JSON with flag enabled does not raise an error",
629+
json: `{\"test\": 123}`,
630+
builtinErrors: true,
631+
},
632+
{
633+
desc: "Invalid JSON with flag enabled raises an error",
634+
json: `test: 123`,
635+
builtinErrors: true,
636+
wantErr: true,
637+
},
638+
{
639+
desc: "Invalid JSON with flag disabled does not raise an error",
640+
json: `test: 123`,
641+
},
642+
}
643+
644+
ctx := context.Background()
645+
646+
for _, tc := range testCases {
647+
t.Run(tc.desc, func(t *testing.T) {
648+
files := map[string]string{
649+
"builtin_error_test.rego": fmt.Sprintf(ruleTemplate, tc.json),
650+
}
651+
652+
test.WithTempFS(files, func(d string) {
653+
paths := []string{d}
654+
modules, store, err := tester.Load(paths, nil)
655+
if err != nil {
656+
t.Fatal(err)
657+
}
658+
txn := storage.NewTransactionOrDie(ctx, store)
659+
runner := tester.
660+
NewRunner().
661+
SetStore(store).
662+
SetModules(modules).
663+
RaiseBuiltinErrors(tc.builtinErrors)
664+
665+
ch, err := runner.RunTests(ctx, txn)
666+
if err != nil {
667+
t.Fatal(err)
668+
}
669+
for result := range ch {
670+
if gotErr := result.Error != nil; gotErr != tc.wantErr {
671+
t.Errorf("wantErr = %v, gotErr = %v", tc.wantErr, gotErr)
672+
}
673+
}
674+
})
675+
})
676+
}
677+
}

0 commit comments

Comments
 (0)