Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

topdown: fix re-wrapping of ndb_cache errors #5543

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions test/cases/testdata/withkeyword/test-with-and-ndbcache-issue.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
cases:
- modules:
- |
package rules

p {
time.now_ns(now)
}

q { p with data.x as 7 }
note: "with: ndb_cache-issue"
query: data.rules = x
want_result:
- x:
p: true
q: true
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the test case that @istalker2 came up with to reproduce this with NDBCache enabled.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting that this one is what breaks everything. 🤔

25 changes: 11 additions & 14 deletions topdown/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -1696,7 +1696,7 @@ func (e evalBuiltin) eval(iter unifyIterator) error {

operands := make([]*ast.Term, len(e.terms))

for i := 0; i < len(e.terms); i++ {
for i := range e.terms {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style update, immaterial

operands[i] = e.e.bindings.Plug(e.terms[i])
}

Expand All @@ -1723,25 +1723,19 @@ func (e evalBuiltin) eval(iter unifyIterator) error {
if v, ok := e.bctx.NDBuiltinCache.Get(e.bi.Name, ast.NewArray(operands[:endIndex]...)); ok {
switch {
case e.bi.Decl.Result() == nil:
err = iter()
return iter()
case len(operands) == numDeclArgs:
if v.Compare(ast.Boolean(false)) != 0 {
err = iter()
} // else: nothing to do, don't iter()
if v.Compare(ast.Boolean(false)) == 0 {
return nil // nothing to do
}
return iter()
default:
err = e.e.unify(e.terms[endIndex], ast.NewTerm(v), iter)
}

if err != nil {
return Halt{Err: err}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Halt-wrapping is what we'd like to get rid of. The rest of this section is just a small restructuring, going with straightaway

return iter()

calls instead of

err = iter()

followed by a plain

return err

below. Since the Halt-wrapping is gone, this became applicable.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a solid refactoring to me!

return e.e.unify(e.terms[endIndex], ast.NewTerm(v), iter)
}

return nil
}

e.e.instr.startTimer(evalOpBuiltinCall)

// Otherwise, we'll need to go through the normal unify flow.
e.e.instr.startTimer(evalOpBuiltinCall)
}

// Normal unification flow for builtins:
Expand Down Expand Up @@ -1770,6 +1764,9 @@ func (e evalBuiltin) eval(iter unifyIterator) error {
}

if err != nil {
// NOTE(sr): We wrap the errors here into Halt{} because we don't want to
// record them into builtinErrors below. The errors set here are coming from
// the call to iter(), not from the builtin implementation.
err = Halt{Err: err}
}

Expand Down
26 changes: 22 additions & 4 deletions topdown/exported_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/open-policy-agent/opa/storage"
inmem "github.com/open-policy-agent/opa/storage/inmem/test"
"github.com/open-policy-agent/opa/test/cases"
"github.com/open-policy-agent/opa/topdown/builtins"
)

func TestRego(t *testing.T) {
Expand All @@ -34,7 +35,19 @@ func TestOPARego(t *testing.T) {
}
}

func testRun(t *testing.T, tc cases.TestCase) {
func TestRegoWithNDBCache(t *testing.T) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was one (!!) test case in our ~1700 cases that would have broken if we did this before. But still worthwhile, we'll now run all yaml tests with NDBCache enabled. Since those tests are our safety net for anything more complex going on in topdown, it's worth it, I think.

This is the case -- but it depends on your machine's IP setup, I think:

- data:
modules:
- |
package test
# one of these should be the case on any system
p {
net.lookup_ip_addr("localhost") == {"127.0.0.1"}
}
p {
net.lookup_ip_addr("localhost") == {"127.0.0.1", "::1"}
}
p {
net.lookup_ip_addr("localhost") == {"::1"}
}
note: net.lookup_ip_addr/localhost
query: data.test.p = x
want_result:
- x: true

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised that it's just one testcase that blows up. 🤔 It's better than none though!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess that's because by their very nature, non-deterministic builtins are difficult to test? 😅

for _, tc := range cases.MustLoad("../test/cases/testdata").Sorted().Cases {
t.Run(tc.Note, func(t *testing.T) {
testRun(t, tc, func(q *Query) *Query {
return q.WithNDBuiltinCache(builtins.NDBCache{})
})
})
}
}

type opt func(*Query) *Query

func testRun(t *testing.T, tc cases.TestCase, opts ...opt) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice refactoring to allow parameterizing the test runner. 👍


ctx := context.Background()

Expand Down Expand Up @@ -69,14 +82,19 @@ func testRun(t *testing.T, tc cases.TestCase) {
}

buf := NewBufferTracer()
rs, err := NewQuery(query).
q := NewQuery(query).
WithCompiler(compiler).
WithStore(store).
WithTransaction(txn).
WithInput(input).
WithStrictBuiltinErrors(tc.StrictError).
WithTracer(buf).
Run(ctx)
WithTracer(buf)

for _, o := range opts {
q = o(q)
}

rs, err := q.Run(ctx)

if tc.WantError != nil {
testAssertErrorText(t, *tc.WantError, err)
Expand Down