Files
releases/cmd/bench_jsonv2_test.go
Anders Eknert db035b09fc Add support for Go 1.27 & jsonv2 (#8947)
Makes OPA build and pass its tests on Go 1.27, while keeping Go 1.25 and
1.26 working. JSON output is unchanged on every supported version.

Go 1.27 json package honours `encoding.TextAppender`. Many v1 ast types
implement AppendText to build their Rego string cheaply, so on 1.27 they
would have marshalled as Rego text. Files built only with 1.27 now
implement MarshalJSONTo.

Library users should keep using `json.Marshal` etc. The MarshalJSONTo
methods are implementation details, are absent from 1.25 and 1.26
builds, and may change.

---------

Signed-off-by: Anders Eknert <anders.eknert@apple.com>
Signed-off-by: Charlie Egan <charlie_egan@apple.com>
Co-authored-by: Charlie Egan <charlie_egan@apple.com>
2026-07-30 17:41:28 +01:00

1571 lines
38 KiB
Go

//go:build go1.27
// Copyright 2020 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package cmd
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"maps"
"os"
"path/filepath"
"strings"
"testing"
"github.com/open-policy-agent/opa/cmd/formats"
"github.com/open-policy-agent/opa/internal/presentation"
"github.com/open-policy-agent/opa/v1/ast"
"github.com/open-policy-agent/opa/v1/bundle"
"github.com/open-policy-agent/opa/v1/rego"
"github.com/open-policy-agent/opa/v1/util"
"github.com/open-policy-agent/opa/v1/util/test"
)
// Minimize the number of tests that *actually* run the benchmarks, they are pretty slow.
// Have one test that exercises the whole flow.
func TestRunBenchmark(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
t.Parallel()
params := testBenchParams()
args := []string{"1 + 1"}
var buf bytes.Buffer
rc, err := benchMain(args, params, &buf, nil, &goBenchRunner{})
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if rc != 0 {
t.Fatalf("Unexpected return code %d, expected 0", rc)
}
// Expect a json serialized benchmark result with histogram fields
var br testing.BenchmarkResult
err = util.UnmarshalJSON(buf.Bytes(), &br)
if err != nil {
t.Fatalf("Unexpected error unmarshalling output: %s", err)
}
if br.N == 0 || br.T == 0 || br.MemAllocs == 0 || br.MemBytes == 0 {
t.Fatalf("Expected benchmark results to be non-zero, got: %+v", br)
}
if _, ok := br.Extra["histogram_timer_rego_query_eval_ns_count"]; !ok {
t.Fatalf("Expected benchmark results to contain histogram_timer_rego_query_eval_ns_count, got: %+v", br)
}
if float64(br.N) != br.Extra["histogram_timer_rego_query_eval_ns_count"] {
t.Fatalf("Expected 'histogram_timer_rego_query_eval_ns_count' to be equal to N")
}
}
func TestRunBenchmarkWithQueryImport(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
t.Parallel()
params := testBenchParams()
// We add the rego.v1 import ..
params.imports = newrepeatedStringFlag([]string{"rego.v1"})
// .. which provides the 'in' keyword
args := []string{`"a" in ["a", "b", "c"]`}
var buf bytes.Buffer
rc, err := benchMain(args, params, &buf, nil, &goBenchRunner{})
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if rc != 0 {
t.Fatalf("Unexpected return code %d, expected 0", rc)
}
// Expect a json serialized benchmark result with histogram fields
var br testing.BenchmarkResult
err = util.UnmarshalJSON(buf.Bytes(), &br)
if err != nil {
t.Fatalf("Unexpected error unmarshalling output: %s", err)
}
if br.N == 0 || br.T == 0 || br.MemAllocs == 0 || br.MemBytes == 0 {
t.Fatalf("Expected benchmark results to be non-zero, got: %+v", br)
}
if _, ok := br.Extra["histogram_timer_rego_query_eval_ns_count"]; !ok {
t.Fatalf("Expected benchmark results to contain histogram_timer_rego_query_eval_ns_count, got: %+v", br)
}
if float64(br.N) != br.Extra["histogram_timer_rego_query_eval_ns_count"] {
t.Fatalf("Expected 'histogram_timer_rego_query_eval_ns_count' to be equal to N")
}
}
func TestRunBenchmarkE2E(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
t.Parallel()
params := testBenchParams()
params.e2e = true
args := []string{"1 + 1"}
var buf bytes.Buffer
rc, err := benchMain(args, params, &buf, nil, &goBenchRunner{})
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if rc != 0 {
t.Fatalf("Unexpected return code %d, expected 0", rc)
}
// Expect a json serialized benchmark result with histogram fields
var br testing.BenchmarkResult
err = util.UnmarshalJSON(buf.Bytes(), &br)
if err != nil {
t.Fatalf("Unexpected error unmarshalling output: %s", err)
}
if br.N == 0 || br.T == 0 || br.MemAllocs == 0 || br.MemBytes == 0 {
t.Fatalf("Expected benchmark results to be non-zero, got: %+v", br)
}
if _, ok := br.Extra["histogram_timer_rego_query_eval_ns_count"]; !ok {
t.Fatalf("Expected benchmark results to contain 'histogram_timer_rego_query_eval_ns_count', got: %+v", br)
}
if float64(br.N) != br.Extra["histogram_timer_rego_query_eval_ns_count"] {
t.Fatalf("Expected 'histogram_timer_rego_query_eval_ns_count' to be equal to N")
}
if _, ok := br.Extra["histogram_timer_server_handler_ns_count"]; !ok {
t.Fatalf("Expected benchmark results to contain 'histogram_timer_server_handler_ns_count', got: %+v", br)
}
if float64(br.N) != br.Extra["histogram_timer_server_handler_ns_count"] {
t.Fatalf("Expected 'histogram_timer_server_handler_ns_count' to be equal to N")
}
}
func TestRunBenchmarkE2EWithOPAConfigFile(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
t.Parallel()
fs := map[string]string{
"/config.yaml": `{"decision_logs": {"console": true}}`,
}
test.WithTempFS(fs, func(testDirRoot string) {
params := testBenchParams()
params.e2e = true
params.configFile = filepath.Join(testDirRoot, "config.yaml")
args := []string{"1 + 1"}
var buf bytes.Buffer
rc, err := benchMain(args, params, &buf, nil, &goBenchRunner{})
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if rc != 0 {
t.Fatalf("Unexpected return code %d, expected 0", rc)
}
// Expect a json serialized benchmark result with histogram fields
var br testing.BenchmarkResult
err = util.UnmarshalJSON(buf.Bytes(), &br)
if err != nil {
t.Fatalf("Unexpected error unmarshalling output: %s", err)
}
if br.N == 0 || br.T == 0 || br.MemAllocs == 0 || br.MemBytes == 0 {
t.Fatalf("Expected benchmark results to be non-zero, got: %+v", br)
}
if _, ok := br.Extra["histogram_timer_rego_query_eval_ns_count"]; !ok {
t.Fatalf("Expected benchmark results to contain 'histogram_timer_rego_query_eval_ns_count', got: %+v", br)
}
if float64(br.N) != br.Extra["histogram_timer_rego_query_eval_ns_count"] {
t.Fatalf("Expected 'histogram_timer_rego_query_eval_ns_count' to be equal to N")
}
if _, ok := br.Extra["histogram_timer_server_handler_ns_count"]; !ok {
t.Fatalf("Expected benchmark results to contain 'histogram_timer_server_handler_ns_count', got: %+v", br)
}
if float64(br.N) != br.Extra["histogram_timer_server_handler_ns_count"] {
t.Fatalf("Expected 'histogram_timer_server_handler_ns_count' to be equal to N")
}
})
}
func TestRunBenchmarkFailFastE2E(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
t.Parallel()
params := testBenchParams()
params.fail = true // configured to fail on undefined results
params.e2e = true
args := []string{"a := 1; a > 2"}
var buf bytes.Buffer
rc, err := benchMain(args, params, &buf, nil, &goBenchRunner{})
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if rc != 1 {
t.Fatalf("Unexpected return code %d, expected 1", rc)
}
// Expect a json serialized benchmark result with histogram fields
var pr presentation.Output
err = util.UnmarshalJSON(buf.Bytes(), &pr)
if err != nil {
t.Fatalf("Unexpected error unmarshalling output: %s", err)
}
if len(pr.Errors) != 1 {
t.Fatalf("Expected 1 error in result, got:\n\n%s\n", buf.String())
}
}
func TestBenchPartialE2E(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
t.Parallel()
params := testBenchParams()
params.partial = true
params.fail = true
params.e2e = true
params.unknowns = []string{"input"}
args := []string{"input.x > 0"}
var buf bytes.Buffer
rc, err := benchMain(args, params, &buf, nil, &goBenchRunner{})
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if rc != 0 {
t.Fatalf("Unexpected return code %d, expected 0", rc)
}
var br testing.BenchmarkResult
err = util.UnmarshalJSON(buf.Bytes(), &br)
if err != nil {
t.Fatalf("Unexpected error unmarshalling output: %s", err)
}
if br.N == 0 || br.T == 0 || br.MemAllocs == 0 || br.MemBytes == 0 {
t.Fatalf("Expected benchmark results to be non-zero, got: %+v", br)
}
if _, ok := br.Extra["histogram_timer_rego_partial_eval_ns_count"]; !ok {
t.Fatalf("Expected benchmark results to contain 'histogram_timer_rego_partial_eval_ns_count', got: %+v", br)
}
if float64(br.N) != br.Extra["histogram_timer_rego_partial_eval_ns_count"] {
t.Fatalf("Expected 'histogram_timer_rego_partial_eval_ns_count' to be equal to N")
}
if _, ok := br.Extra["histogram_timer_server_handler_ns_count"]; !ok {
t.Fatalf("Expected benchmark results to contain 'histogram_timer_server_handler_ns_count', got: %+v", br)
}
if float64(br.N) != br.Extra["histogram_timer_server_handler_ns_count"] {
t.Fatalf("Expected 'histogram_timer_server_handler_ns_count' to be equal to N")
}
}
func TestRunBenchmarkPartialFailFastE2E(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
t.Parallel()
params := testBenchParams()
params.partial = true
params.unknowns = []string{}
params.fail = true
params.e2e = true
args := []string{"1 == 2"}
var buf bytes.Buffer
rc, err := benchMain(args, params, &buf, nil, &goBenchRunner{})
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if rc != 1 {
t.Fatalf("Unexpected return code %d, expected 1", rc)
}
actual := buf.String()
expected := `{
"errors": [
{
"message": "undefined result"
}
]
}
`
if actual != expected {
t.Fatalf("\nExpected:\n%s\n\nGot:\n%s\n", expected, actual)
}
}
func TestRunBenchmarkFailFast(t *testing.T) {
t.Parallel()
params := testBenchParams()
params.fail = true // configured to fail on undefined results
args := []string{"a := 1; a > 2"}
var buf bytes.Buffer
rc, err := benchMain(args, params, &buf, nil, &goBenchRunner{})
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if rc != 1 {
t.Fatalf("Unexpected return code %d, expected 1", rc)
}
// Expect a json serialized benchmark result with histogram fields
var pr presentation.Output
err = util.UnmarshalJSON(buf.Bytes(), &pr)
if err != nil {
t.Fatalf("Unexpected error unmarshalling output: %s", err)
}
if len(pr.Errors) != 1 {
t.Fatalf("Expected 1 error in result, got:\n\n%s\n", buf.String())
}
}
// mockBenchRunner lets us test the bench CLI operations without having to wait ~10 seconds
// while the actual benchmark runner does its thing.
type mockBenchRunner struct {
onRun func(ctx context.Context, ectx *evalContext, params benchmarkCommandParams, f func(context.Context, ...rego.EvalOption) error) (testing.BenchmarkResult, error)
}
func (r *mockBenchRunner) run(ctx context.Context, ectx *evalContext, params benchmarkCommandParams, f func(context.Context, ...rego.EvalOption) error) (testing.BenchmarkResult, error) {
if r.onRun != nil {
return r.onRun(ctx, ectx, params, f)
}
return testing.BenchmarkResult{}, nil
}
func TestBenchPartial(t *testing.T) {
t.Parallel()
params := testBenchParams()
params.partial = true
params.fail = true
args := []string{"input=1"}
var buf bytes.Buffer
rc, err := benchMain(args, params, &buf, nil, &mockBenchRunner{})
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if rc != 0 {
t.Fatalf("Unexpected return code %d, expected 0", rc)
}
}
func TestBenchMainErrPreparing(t *testing.T) {
t.Parallel()
params := testBenchParams()
args := []string{"???"} // query compile error
var buf bytes.Buffer
rc, err := benchMain(args, params, &buf, nil, &mockBenchRunner{})
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if rc != 1 {
t.Fatalf("Unexpected return code %d, expected 1", rc)
}
}
func TestBenchMainErrRunningBenchmark(t *testing.T) {
t.Parallel()
params := testBenchParams()
args := []string{"1+1"}
var buf bytes.Buffer
mockRunner := &mockBenchRunner{}
mockRunner.onRun = func(_ context.Context, _ *evalContext, _ benchmarkCommandParams, _ func(context.Context, ...rego.EvalOption) error) (testing.BenchmarkResult, error) {
return testing.BenchmarkResult{}, errors.New("error error error")
}
rc, err := benchMain(args, params, &buf, nil, mockRunner)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if rc != 1 {
t.Fatalf("Unexpected return code %d, expected 1", rc)
}
}
func TestBenchMainWithCount(t *testing.T) {
t.Parallel()
params := testBenchParams()
args := []string{"1+1"}
var buf bytes.Buffer
mockRunner := &mockBenchRunner{}
params.count = 25
actualCount := 0
mockRunner.onRun = func(_ context.Context, _ *evalContext, _ benchmarkCommandParams, _ func(context.Context, ...rego.EvalOption) error) (testing.BenchmarkResult, error) {
actualCount++
return testing.BenchmarkResult{}, nil
}
rc, err := benchMain(args, params, &buf, nil, mockRunner)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if rc != 0 {
t.Fatalf("Unexpected return code %d, expected 0", rc)
}
if actualCount != params.count {
t.Fatalf("Expected benchmark to be run %d times, only ran %d", params.count, actualCount)
}
}
func TestBenchMainWithNegativeCount(t *testing.T) {
t.Parallel()
params := testBenchParams()
args := []string{"1+1"}
var buf bytes.Buffer
mockRunner := &mockBenchRunner{}
params.count = -1
actualCount := 0
mockRunner.onRun = func(_ context.Context, _ *evalContext, _ benchmarkCommandParams, _ func(context.Context, ...rego.EvalOption) error) (testing.BenchmarkResult, error) {
actualCount++
return testing.BenchmarkResult{}, nil
}
rc, err := benchMain(args, params, &buf, nil, mockRunner)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if rc != 0 {
t.Fatalf("Unexpected return code %d, expected 0", rc)
}
if actualCount != 0 {
t.Fatalf("Expected benchmark to not be run, instead ran %d times", actualCount)
}
}
func validateBenchMainPrep(t *testing.T, args []string, params benchmarkCommandParams) {
t.Helper()
var buf bytes.Buffer
mockRunner := &mockBenchRunner{}
mockRunner.onRun = func(ctx context.Context, ectx *evalContext, _ benchmarkCommandParams, _ func(context.Context, ...rego.EvalOption) error) (testing.BenchmarkResult, error) {
// cheat and use the ectx to evalute the query to ensure the input setup on it was valid
r := rego.New(ectx.regoArgs...)
pq, err := r.PrepareForEval(ctx)
if err != nil {
return testing.BenchmarkResult{}, err
}
rs, err := pq.Eval(ctx, ectx.evalArgs...)
if err != nil {
return testing.BenchmarkResult{}, err
}
if len(rs) == 0 {
return testing.BenchmarkResult{}, errors.New("expected result, got none")
}
return testing.BenchmarkResult{}, nil
}
rc, err := benchMain(args, params, &buf, nil, mockRunner)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if rc != 0 {
t.Fatalf("Unexpected return code %d, expected 0", rc)
}
}
func TestBenchMainWithJSONInputFile(t *testing.T) {
t.Parallel()
params := testBenchParams()
files := map[string]string{
"/input.json": `{"x": 42}`,
}
args := []string{"input.x == 42"}
test.WithTempFS(files, func(path string) {
params.inputPath = filepath.Join(path, "input.json")
validateBenchMainPrep(t, args, params)
})
}
func TestBenchMainWithYAMLInputFile(t *testing.T) {
t.Parallel()
params := testBenchParams()
files := map[string]string{
"/input.yaml": `x: 42`,
}
args := []string{"input.x == 42"}
test.WithTempFS(files, func(path string) {
params.inputPath = filepath.Join(path, "input.yaml")
validateBenchMainPrep(t, args, params)
})
}
func TestBenchMainInvalidInputFile(t *testing.T) {
t.Parallel()
params := testBenchParams()
files := map[string]string{
"/input.yaml": `x: 42`,
}
args := []string{"1+1"}
test.WithTempFS(files, func(path string) {
params.inputPath = filepath.Join(path, "definitely", "not", "input.yaml")
var buf bytes.Buffer
rc, err := benchMain(args, params, &buf, nil, &mockBenchRunner{})
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if rc != 1 {
t.Fatalf("Unexpected return code %d, expected 1", rc)
}
})
}
func TestBenchMainWithJSONInputFileE2E(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
t.Parallel()
params := testBenchParams()
params.e2e = true
files := map[string]string{
"/input.json": `{"x": 42}`,
}
args := []string{"input.x == 42"}
test.WithTempFS(files, func(path string) {
params.inputPath = filepath.Join(path, "input.json")
var buf bytes.Buffer
rc, err := benchMain(args, params, &buf, nil, &goBenchRunner{})
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if rc != 0 {
t.Fatalf("Unexpected return code %d, expected 0", rc)
}
})
}
func TestBenchMainWithYAMLInputFileE2E(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
t.Parallel()
params := testBenchParams()
params.e2e = true
files := map[string]string{
"/input.yaml": `x: 42`,
}
args := []string{"input.x == 42"}
test.WithTempFS(files, func(path string) {
params.inputPath = filepath.Join(path, "input.yaml")
var buf bytes.Buffer
rc, err := benchMain(args, params, &buf, nil, &goBenchRunner{})
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if rc != 0 {
t.Fatalf("Unexpected return code %d, expected 0", rc)
}
})
}
func TestBenchMainInvalidInputFileE2E(t *testing.T) {
t.Parallel()
params := testBenchParams()
params.e2e = true
files := map[string]string{
"/input.yaml": `x: 42`,
}
args := []string{"1+1"}
test.WithTempFS(files, func(path string) {
params.inputPath = filepath.Join(path, "definitely", "not", "input.yaml")
var buf bytes.Buffer
rc, err := benchMain(args, params, &buf, nil, &goBenchRunner{})
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if rc != 1 {
t.Fatalf("Unexpected return code %d, expected 1", rc)
}
})
}
func TestBenchMainWithBundleData(t *testing.T) {
t.Parallel()
params := testBenchParams()
b := testBundle()
files := map[string]string{
"bundle.tar.gz": "",
}
test.WithTempFS(files, func(path string) {
bundlePath := filepath.Join(path, "bundle.tar.gz")
f, err := os.OpenFile(bundlePath, os.O_WRONLY, os.ModePerm)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
err = bundle.Write(f, b)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
err = params.bundlePaths.Set(bundlePath)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
args := []string{"data.a.b.x"}
validateBenchMainPrep(t, args, params)
})
}
func TestBenchMainWithBundleDataE2E(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
t.Parallel()
params := testBenchParams()
params.e2e = true
b := testBundle()
files := map[string]string{
"bundle.tar.gz": "",
}
test.WithTempFS(files, func(path string) {
bundlePath := filepath.Join(path, "bundle.tar.gz")
f, err := os.OpenFile(bundlePath, os.O_WRONLY, os.ModePerm)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
err = bundle.Write(f, b)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
err = params.bundlePaths.Set(bundlePath)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
args := []string{"data.a.b.x"}
var buf bytes.Buffer
rc, err := benchMain(args, params, &buf, nil, &goBenchRunner{})
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if rc != 0 {
t.Fatalf("Unexpected return code %d, expected 0", rc)
}
})
}
func TestBenchMainWithDataE2E(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
t.Parallel()
params := testBenchParams()
params.e2e = true
mod := `package a.b
import rego.v1
x if {
data.a.b.c == 42
}
`
files := map[string]string{
"p.rego": mod,
}
test.WithTempFS(files, func(path string) {
err := params.dataPaths.Set(filepath.Join(path, "p.rego"))
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
args := []string{"data.a.b.x"}
var buf bytes.Buffer
rc, err := benchMain(args, params, &buf, nil, &goBenchRunner{})
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if rc != 0 {
t.Fatalf("Unexpected return code %d, expected 0", rc)
}
})
}
func TestBenchMainBadQueryE2E(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
t.Parallel()
params := testBenchParams()
params.e2e = true
args := []string{"foo.bar"}
var buf bytes.Buffer
rc, err := benchMain(args, params, &buf, nil, &goBenchRunner{})
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if rc != 1 {
t.Fatalf("Unexpected return code %d, expected 1", rc)
}
}
func TestBenchMain_DefaultRegoVersion(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
t.Parallel()
tests := []struct {
note string
module string
query string
expErrs []string
}{
// These tests are slow, so we're not being completely exhaustive here.
{
note: "v0 module",
module: `package test
a[x] {
x := 42
}`,
query: `data.test.a`,
expErrs: []string{
"mod.rego:2: rego_parse_error: `if` keyword is required before rule body",
"mod.rego:2: rego_parse_error: `contains` keyword is required for partial set rules",
},
},
{
note: "v1 module",
module: `package test
a contains x if {
x := 42
}`,
query: `data.test.a`,
},
}
modes := []struct {
name string
e2e bool
}{
{
name: "run",
},
{
name: "e2e",
e2e: true,
},
}
for _, mode := range modes {
for _, tc := range tests {
t.Run(fmt.Sprintf("%s, %s", tc.note, mode.name), func(t *testing.T) {
t.Parallel()
files := map[string]string{
"mod.rego": tc.module,
}
test.WithTempFS(files, func(path string) {
params := testBenchParams()
_ = params.outputFormat.Set(formats.Pretty)
params.e2e = mode.e2e
for n := range files {
err := params.dataPaths.Set(filepath.Join(path, n))
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
}
args := []string{tc.query}
var buf, errBuf bytes.Buffer
rc, err := benchMain(args, params, &buf, &errBuf, &goBenchRunner{})
if len(tc.expErrs) > 0 {
if rc == 0 {
t.Fatalf("Expected non-zero return code")
}
output := errBuf.String()
for _, expErr := range tc.expErrs {
if !strings.Contains(output, expErr) {
t.Fatalf("Expected error:\n\n%s\n\ngot:\n\n%s", expErr, output)
}
}
} else {
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if rc != 0 {
t.Fatalf("Unexpected return code %d, expected 0", rc)
}
}
})
})
}
}
}
func TestBenchMainCompatibleFlags(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
t.Parallel()
tests := []struct {
note string
v0Compatible bool
v1Compatible bool
module string
query string
expErrs []string
}{
// These tests are slow, so we're not being completely exhaustive here.
{
note: "v0, keywords not used",
v0Compatible: true,
module: `package test
a[4] {
1 == 1
}`,
query: `data.test.a`,
},
{
note: "v0, no keywords imported",
v0Compatible: true,
module: `package test
a contains 4 if {
1 == 1
}`,
query: `data.test.a`,
expErrs: []string{
"rego_parse_error: var cannot be used for rule name",
"rego_parse_error: number cannot be used for rule name",
},
},
{
note: "v1, keywords not used",
v1Compatible: true,
module: `package test
a[4] {
1 == 1
}`,
query: `data.test.a`,
expErrs: []string{
"rego_parse_error: `if` keyword is required before rule body",
"rego_parse_error: `contains` keyword is required for partial set rules",
},
},
{
note: "v1, no keywords imported",
v1Compatible: true,
module: `package test
a contains 4 if {
1 == 1
}`,
query: `data.test.a`,
},
{
note: "v0+v1, keywords not used (v0 takes precedence)",
v0Compatible: true,
v1Compatible: true,
module: `package test
a[4] {
1 == 1
}`,
query: `data.test.a`,
},
}
modes := []struct {
name string
e2e bool
}{
{
name: "run",
},
{
name: "e2e",
e2e: true,
},
}
for _, mode := range modes {
for _, tc := range tests {
t.Run(fmt.Sprintf("%s, %s", tc.note, mode.name), func(t *testing.T) {
t.Parallel()
files := map[string]string{
"mod.rego": tc.module,
}
test.WithTempFS(files, func(path string) {
params := testBenchParams()
_ = params.outputFormat.Set(formats.Pretty)
params.v0Compatible = tc.v0Compatible
params.v1Compatible = tc.v1Compatible
params.e2e = mode.e2e
for n := range files {
err := params.dataPaths.Set(filepath.Join(path, n))
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
}
args := []string{tc.query}
var buf, errBuf bytes.Buffer
rc, err := benchMain(args, params, &buf, &errBuf, &goBenchRunner{})
if len(tc.expErrs) > 0 {
if rc == 0 {
t.Fatalf("Expected non-zero return code")
}
output := errBuf.String()
for _, expErr := range tc.expErrs {
if !strings.Contains(output, expErr) {
t.Fatalf("Expected error:\n\n%s\n\ngot:\n\n%s", expErr, output)
}
}
} else {
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if rc != 0 {
t.Fatalf("Unexpected return code %d, expected 0", rc)
}
}
})
})
}
}
}
func TestBenchMainWithBundleRegoVersion(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
t.Parallel()
tests := []struct {
note string
bundleRegoVersion int
bundleFileRegoVersions map[string]int
modules map[string]string
query string
expErrs []string
}{
// These tests are slow, so we're not being completely exhaustive here.
{
note: "v0 bundle",
bundleRegoVersion: 0,
modules: map[string]string{
"test.rego": `package test
a[4] {
1 == 1
}`,
},
query: `data.test.a`,
},
{
note: "v0 bundle, no keywords imported",
bundleRegoVersion: 0,
modules: map[string]string{
"test.rego": `package test
a contains 4 if {
1 == 1
}`,
},
query: `data.test.a`,
expErrs: []string{
"rego_parse_error: var cannot be used for rule name",
"rego_parse_error: number cannot be used for rule name",
},
},
{
note: "v0 bundle, v1 per-file override",
bundleRegoVersion: 0,
bundleFileRegoVersions: map[string]int{
"*/test2.rego": 1,
},
modules: map[string]string{
"test1.rego": `package test
a[4] {
1 == 1
}`,
"test2.rego": `package test
b contains 4 if {
1 == 1
}`,
},
query: `data.test.a`,
},
{
note: "v1 bundle, keywords not used",
bundleRegoVersion: 1,
modules: map[string]string{
"test.rego": `package test
a[4] {
1 == 1
}`,
},
query: `data.test.a`,
expErrs: []string{
"rego_parse_error: `if` keyword is required before rule body",
"rego_parse_error: `contains` keyword is required for partial set rules",
},
},
{
note: "v1, no keywords imported",
bundleRegoVersion: 1,
modules: map[string]string{
"test.rego": `package test
a contains 4 if {
1 == 1
}`,
},
query: `data.test.a`,
},
}
bundleTypeCases := []struct {
note string
tar bool
}{
{
"bundle dir", false,
},
{
"bundle tar", true,
},
}
modes := []struct {
name string
e2e bool
}{
{
name: "run",
},
{
name: "e2e",
e2e: true,
},
}
for _, bundleType := range bundleTypeCases {
for _, mode := range modes {
for _, tc := range tests {
t.Run(fmt.Sprintf("%s, %s, %s", bundleType.note, tc.note, mode.name), func(t *testing.T) {
t.Parallel()
files := map[string]string{}
if bundleType.tar {
files["bundle.tar.gz"] = ""
} else {
maps.Copy(files, tc.modules)
manifest := bundle.Manifest{
RegoVersion: &tc.bundleRegoVersion,
FileRegoVersions: tc.bundleFileRegoVersions,
}
manifest.Init()
if b, err := json.Marshal(manifest); err != nil {
t.Fatalf("Unexpected error: %s", err)
} else {
files[".manifest"] = string(b)
}
}
test.WithTempFS(files, func(root string) {
p := root
if bundleType.tar {
b := bundle.Bundle{
Manifest: bundle.Manifest{
RegoVersion: &tc.bundleRegoVersion,
FileRegoVersions: tc.bundleFileRegoVersions,
},
Data: map[string]any{},
}
for k, v := range tc.modules {
b.Modules = append(b.Modules, bundle.ModuleFile{
Path: k,
Raw: []byte(v),
})
}
p = filepath.Join(root, "bundle.tar.gz")
f, err := os.OpenFile(p, os.O_WRONLY, os.ModePerm)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
err = bundle.Write(f, b)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
}
params := testBenchParams()
_ = params.outputFormat.Set(formats.Pretty)
params.e2e = mode.e2e
err := params.bundlePaths.Set(p)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
args := []string{tc.query}
var buf, errBuf bytes.Buffer
rc, err := benchMain(args, params, &buf, &errBuf, &goBenchRunner{})
if len(tc.expErrs) > 0 {
if rc == 0 {
t.Fatalf("Expected non-zero return code")
}
output := errBuf.String()
for _, expErr := range tc.expErrs {
if !strings.Contains(output, expErr) {
t.Fatalf("Expected error:\n\n%s\n\ngot:\n\n%s", expErr, output)
}
}
} else {
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if rc != 0 {
t.Fatalf("Unexpected return code %d, expected 0", rc)
}
}
})
})
}
}
}
}
func TestRenderBenchmarkResultJSONOutput(t *testing.T) {
t.Parallel()
params := testBenchParams()
err := params.outputFormat.Set(formats.JSON)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
br := fakeBenchResults()
var buf bytes.Buffer
renderBenchmarkResult(params, br, &buf)
actual := buf.String()
expected := `{
"N": 134844,
"T": 1088294120,
"Bytes": 0,
"MemAllocs": 8360721,
"MemBytes": 449906736,
"Extra": {
"histogram_timer_rego_query_eval_ns_75%": 4953.75,
"histogram_timer_rego_query_eval_ns_90%": 6309.6,
"histogram_timer_rego_query_eval_ns_95%": 7872.55,
"histogram_timer_rego_query_eval_ns_99%": 14947.34000000001,
"histogram_timer_rego_query_eval_ns_99.9%": 174377.08200000023,
"histogram_timer_rego_query_eval_ns_99.99%": 176301,
"histogram_timer_rego_query_eval_ns_count": 134844,
"histogram_timer_rego_query_eval_ns_max": 176301,
"histogram_timer_rego_query_eval_ns_mean": 5118.3706225680935,
"histogram_timer_rego_query_eval_ns_median": 4312,
"histogram_timer_rego_query_eval_ns_min": 3553,
"histogram_timer_rego_query_eval_ns_stddev": 6587.830963916497
}
}
`
if actual != expected {
t.Fatalf("\nExpected:\n%s\n\nGot:\n%s\n", expected, actual)
}
}
func TestRenderBenchmarkResultPrettyOutput(t *testing.T) {
t.Parallel()
params := testBenchParams()
params.benchMem = false
err := params.outputFormat.Set(formats.Pretty)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
br := fakeBenchResults()
var buf bytes.Buffer
renderBenchmarkResult(params, br, &buf)
actual := buf.String()
expected := `┌───────────────────────────────────────────┬────────┐
│ samples │ 134844 │
│ ns/op │ 8071 │
│ histogram_timer_rego_query_eval_ns_75% │ 4954 │
│ histogram_timer_rego_query_eval_ns_90% │ 6310 │
│ histogram_timer_rego_query_eval_ns_95% │ 7873 │
│ histogram_timer_rego_query_eval_ns_99% │ 14947 │
│ histogram_timer_rego_query_eval_ns_99.9% │ 174377 │
│ histogram_timer_rego_query_eval_ns_99.99% │ 176301 │
│ histogram_timer_rego_query_eval_ns_count │ 134844 │
│ histogram_timer_rego_query_eval_ns_max │ 176301 │
│ histogram_timer_rego_query_eval_ns_mean │ 5118 │
│ histogram_timer_rego_query_eval_ns_median │ 4312 │
│ histogram_timer_rego_query_eval_ns_min │ 3553 │
│ histogram_timer_rego_query_eval_ns_stddev │ 6588 │
└───────────────────────────────────────────┴────────┘
`
if actual != expected {
t.Fatalf("\nExpected:\n%s\n\nGot:\n%s\n", expected, actual)
}
}
func TestRenderBenchmarkResultPrettyOutputShowAllocs(t *testing.T) {
t.Parallel()
params := testBenchParams()
params.benchMem = true
err := params.outputFormat.Set(formats.Pretty)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
br := fakeBenchResults()
var buf bytes.Buffer
renderBenchmarkResult(params, br, &buf)
actual := buf.String()
expected := `┌───────────────────────────────────────────┬────────┐
│ samples │ 134844 │
│ ns/op │ 8071 │
│ B/op │ 3336 │
│ allocs/op │ 62 │
│ histogram_timer_rego_query_eval_ns_75% │ 4954 │
│ histogram_timer_rego_query_eval_ns_90% │ 6310 │
│ histogram_timer_rego_query_eval_ns_95% │ 7873 │
│ histogram_timer_rego_query_eval_ns_99% │ 14947 │
│ histogram_timer_rego_query_eval_ns_99.9% │ 174377 │
│ histogram_timer_rego_query_eval_ns_99.99% │ 176301 │
│ histogram_timer_rego_query_eval_ns_count │ 134844 │
│ histogram_timer_rego_query_eval_ns_max │ 176301 │
│ histogram_timer_rego_query_eval_ns_mean │ 5118 │
│ histogram_timer_rego_query_eval_ns_median │ 4312 │
│ histogram_timer_rego_query_eval_ns_min │ 3553 │
│ histogram_timer_rego_query_eval_ns_stddev │ 6588 │
└───────────────────────────────────────────┴────────┘
`
if actual != expected {
t.Fatalf("\nExpected:\n%s\n\nGot:\n%s\n", expected, actual)
}
}
func TestRenderBenchmarkResultGoBenchOutputShowAllocs(t *testing.T) {
t.Parallel()
params := testBenchParams()
params.benchMem = true
err := params.outputFormat.Set(formats.GoBench)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
br := fakeBenchResults()
var buf bytes.Buffer
renderBenchmarkResult(params, br, &buf)
actual := buf.String()
if !strings.HasPrefix(actual, "Benchmark") {
t.Fatalf("Expected line output to start with 'Benchmark', got: \n\n%s\n", actual)
}
if len(strings.Split(strings.TrimSpace(actual), "\n")) != 1 {
t.Fatalf("Expected only a single line of output")
}
}
func TestRenderBenchmarkErrorJSONOutput(t *testing.T) {
t.Parallel()
params := testBenchParams()
err := params.outputFormat.Set(formats.JSON)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
var buf bytes.Buffer
_, err = ast.ParseBody("???")
err = renderBenchmarkError(params, err, &buf, nil)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
actual := buf.String()
expected := `{
"errors": [
{
"message": "illegal token",
"code": "rego_parse_error",
"location": {
"file": "",
"row": 1,
"col": 1
},
"details": {
"line": "???",
"idx": 0
}
}
]
}
`
if actual != expected {
t.Fatalf("\nExpected:\n%s\n\nGot:\n%s\n", expected, actual)
}
}
func TestRenderBenchmarkErrorPrettyOutput(t *testing.T) {
t.Parallel()
params := testBenchParams()
err := params.outputFormat.Set(formats.Pretty)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
testPrettyBenchmarkOutput(t, params)
}
func TestRenderBenchmarkErrorGoBenchOutput(t *testing.T) {
t.Parallel()
params := testBenchParams()
err := params.outputFormat.Set(formats.GoBench)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
testPrettyBenchmarkOutput(t, params)
}
func testPrettyBenchmarkOutput(t *testing.T, params benchmarkCommandParams) {
var buf bytes.Buffer
_, err := ast.ParseBody("???")
err = renderBenchmarkError(params, err, &buf, &buf)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
actual := buf.String()
expected := `1 error occurred: 1:1: rego_parse_error: illegal token
???
^
`
if actual != expected {
t.Fatalf("\nExpected:\n%s\n\nGot:\n%s\n", expected, actual)
}
}
func testBenchParams() benchmarkCommandParams {
params := newBenchmarkEvalParams()
params.benchMem = true
params.metrics = true
_ = params.outputFormat.Set(formats.JSON)
params.count = 1
return params
}
func fakeBenchResults() testing.BenchmarkResult {
return testing.BenchmarkResult{
N: 134844,
T: 1088294120,
Bytes: 0,
MemAllocs: 8360721,
MemBytes: 449906736,
Extra: map[string]float64{
"histogram_timer_rego_query_eval_ns_75%": 4953.75,
"histogram_timer_rego_query_eval_ns_90%": 6309.6,
"histogram_timer_rego_query_eval_ns_95%": 7872.55,
"histogram_timer_rego_query_eval_ns_99%": 14947.34000000001,
"histogram_timer_rego_query_eval_ns_99.9%": 174377.08200000023,
"histogram_timer_rego_query_eval_ns_99.99%": 176301,
"histogram_timer_rego_query_eval_ns_count": 134844,
"histogram_timer_rego_query_eval_ns_max": 176301,
"histogram_timer_rego_query_eval_ns_mean": 5118.3706225680935,
"histogram_timer_rego_query_eval_ns_median": 4312,
"histogram_timer_rego_query_eval_ns_min": 3553,
"histogram_timer_rego_query_eval_ns_stddev": 6587.830963916497,
},
}
}
func testBundle() bundle.Bundle {
mod := `package a.b
import rego.v1
x if {
data.a.b.c == 42
}
`
return bundle.Bundle{
Manifest: bundle.Manifest{},
Data: map[string]any{
"a": map[string]any{
"b": map[string]any{
"c": 42,
},
},
},
Modules: []bundle.ModuleFile{
{
Path: "/a/b/policy.rego",
Raw: []byte(mod),
Parsed: ast.MustParseModule(mod),
},
},
}
}