From 52e0853bb8cceffdd92d2e8a01d2348374682ca5 Mon Sep 17 00:00:00 2001 From: Torin Sandall Date: Thu, 16 May 2019 15:35:00 -0700 Subject: [PATCH] wasm: Add minimal coverage for complete rule definitions These changes also extend the test runner to allow disabling partial evaluation (which is needed since complete definitons are inlined normally), error checking, and module fixtures. Signed-off-by: Torin Sandall --- test/wasm/assets/007_complete.yaml | 170 +++++++++++++++++++++++++++++ test/wasm/assets/test.js | 35 +++--- test/wasm/cmd/testgen.go | 20 +++- 3 files changed, 206 insertions(+), 19 deletions(-) create mode 100644 test/wasm/assets/007_complete.yaml diff --git a/test/wasm/assets/007_complete.yaml b/test/wasm/assets/007_complete.yaml new file mode 100644 index 0000000000..479099e2a1 --- /dev/null +++ b/test/wasm/assets/007_complete.yaml @@ -0,0 +1,170 @@ +cases: + - note: constants + disable_partial: true + query: data.x.p = 1 + modules: + - | + package x + p = 1 + return_code: 1 + - note: constants (negative) + disable_partial: true + query: data.x.p = 1 + modules: + - | + package x + p = 2 + return_code: 0 + - note: variable + disable_partial: true + query: data.x.p = 1 + modules: + - | + package x + p = y { x = 1; y = x } + return_code: 1 + - note: variable (negative) + disable_partial: true + query: data.x.p = 1 + modules: + - | + package x + p = y { x = 2; y = x } + return_code: 0 + - note: conjunction + disable_partial: true + query: data.x.p = 1 + modules: + - | + package x + p = 1 { q; r } + q { true } + r { true } + return_code: 1 + - note: conjunction (negative) + disable_partial: true + query: data.x.p = 1 + modules: + - | + package x + p = 1 { q; r } + q { true } + r { false } + return_code: 0 + - note: disjunction + disable_partial: true + query: data.x.p = 2 + modules: + - | + package x + p = 1 { false } + p = 2 { true } + p = 3 { false } + return_code: 1 + - note: disjunction (negative) + disable_partial: true + query: data.x.p = 1 + modules: + - | + package x + p = 1 { false } + p = 2 { false } + p = 3 { true } + return_code: 0 + - note: negation + disable_partial: true + query: not data.x.p = 1 + modules: + - | + package x + p = 1 { false } # undefined + return_code: 1 + - note: negation (negative) + disable_partial: true + query: not data.x.p = 1 + modules: + - | + package x + p = 1 + return_code: 0 + - note: chain + disable_partial: true + query: data.x.p = 1 + modules: + - | + package x + p = q + q = r + r = 1 + return_code: 1 + - note: chain (negative) + disable_partial: true + query: data.x.p = 1 + modules: + - | + package x + p = q + q = r + r = 2 + return_code: 0 + - note: chain input + disable_partial: true + query: data.x.p = true + modules: + - | + package x + p = q + q = r + r { input.x = 1 } + input: {"x": 1} + return_code: 1 + - note: chain input (negative) + disable_partial: true + query: data.x.p = true + modules: + - | + package x + p = q + q = r + r { input.x = 2 } + input: {"x": 1} + return_code: 0 + - note: iteration + disable_partial: true + query: data.x.p = 1 + modules: + - | + package x + p = 1 { input[x] = 1 } + input: [3,2,1] + return_code: 1 + - note: iteration (negative) + disable_partial: true + query: data.x.p = 1 + modules: + - | + package x + p = 1 { input[x] = 4 } + input: [3,2,1] + return_code: 0 + - note: conflict error + disable_partial: true + query: data.x.p = 1 + modules: + - | + package x + p = 1 + p = 2 + want_error: "unreachable" # TODO(tsandall): replace with conflict error. + - note: packages + disable_partial: true + query: data.x.p = 1 + modules: + - | + package x + import data.y.p + p = p + - | + package y + p = 1 + return_code: 1 \ No newline at end of file diff --git a/test/wasm/assets/test.js b/test/wasm/assets/test.js index 34700fd116..2383ebc739 100644 --- a/test/wasm/assets/test.js +++ b/test/wasm/assets/test.js @@ -1,7 +1,7 @@ const { readFileSync, readdirSync } = require('fs'); function stringDecoder(mem) { - return function(addr) { + return function (addr) { const i8 = new Int8Array(mem.buffer); const start = addr; var s = ""; @@ -47,10 +47,10 @@ function now() { function formatMicros(us) { if (us <= 1000) { return us + 'µs' - } else if (us <= 1000*1000) { + } else if (us <= 1000 * 1000) { return (us / 1000).toFixed(4) + 'ms' } else { - return (us / (1000*1000)).toFixed(4) + 's' + return (us / (1000 * 1000)).toFixed(4) + 's' } } @@ -60,14 +60,14 @@ function evaluate(mem, policy, input) { const addr = policy.instance.exports.opa_malloc(str.length); const buf = new Uint8Array(mem.buffer); - for(let i = 0; i < str.length; i++) { - buf[addr+i] = str.charCodeAt(i); + for (let i = 0; i < str.length; i++) { + buf[addr + i] = str.charCodeAt(i); } const returnCode = policy.instance.exports.eval(addr, str.length); - return {returnCode: returnCode}; + return { returnCode: returnCode }; } function namespace(cache, key) { @@ -82,7 +82,7 @@ function namespace(cache, key) { async function test() { - const mem = new WebAssembly.Memory({initial: 5}); + const mem = new WebAssembly.Memory({ initial: 5 }); const addr2string = stringDecoder(mem); const t0 = now(); @@ -115,12 +115,12 @@ async function test() { let dirty = false; let cache = {}; - for(let i = 0; i < testCases.length; i++) { + for (let i = 0; i < testCases.length; i++) { const policy = await WebAssembly.instantiate(testCases[i].wasmBytes, { env: { memory: mem, - opa_abort: function(addr) { + opa_abort: function (addr) { throw addr2string(addr); }, }, @@ -132,9 +132,16 @@ async function test() { try { const result = evaluate(mem, policy, testCases[i].input); passed = result.returnCode === testCases[i].return_code; - } catch(e) { - passed = false; - error = e; + } catch (e) { + if (testCases[i].want_error === undefined) { + passed = false; + error = e; + } else if (e.message.includes(testCases[i].want_error)) { + passed = true; + } else { + passed = false; + error = e; + } } if (passed) { @@ -152,7 +159,7 @@ async function test() { const dt_end = t_end - t_load; if (dirty) { - console.log(); + console.log(); } console.log('SUMMARY:'); @@ -170,7 +177,7 @@ async function test() { console.log(); console.log('TOOK:', formatMicros(dt_end)); - if ((numFailed+numErrors) > 0) { + if ((numFailed + numErrors) > 0) { process.exit(1); } } diff --git a/test/wasm/cmd/testgen.go b/test/wasm/cmd/testgen.go index a75deb96c8..dd590d196e 100644 --- a/test/wasm/cmd/testgen.go +++ b/test/wasm/cmd/testgen.go @@ -9,6 +9,7 @@ import ( "compress/gzip" "context" "encoding/json" + "fmt" "io" "io/ioutil" "os" @@ -32,10 +33,13 @@ type testCaseSet struct { } type testCase struct { - Note string `json:"note"` - Query string `json:"query"` - Input *interface{} `json:"input"` - ReturnCode int `json:"return_code"` + Note string `json:"note"` + Query string `json:"query"` + Modules []string `json:"modules"` + DisablePartial bool `json:"disable_partial"` + Input *interface{} `json:"input"` + ReturnCode int `json:"return_code"` + WantError string `json:"want_error"` } type compiledTestCaseSet struct { @@ -50,7 +54,13 @@ type compiledTestCase struct { func compileTestCases(ctx context.Context, tests testCaseSet) (*compiledTestCaseSet, error) { var result []compiledTestCase for _, tc := range tests.Cases { - cr, err := rego.New(rego.Query(tc.Query)).Compile(ctx) + args := []func(*rego.Rego){ + rego.Query(tc.Query), + } + for idx, module := range tc.Modules { + args = append(args, rego.Module(fmt.Sprintf("module%d.rego", idx), module)) + } + cr, err := rego.New(args...).Compile(ctx, rego.CompilePartial(!tc.DisablePartial)) if err != nil { return nil, err }