Files
releases/cmd/bench_test.go
T
Jacob Hochstetler 2cc948d872 TLM: Upgrade to v1 tablewriter (#7937)
- Updated test fixtures and various test fixes.

Signed-off-by: Jacob Hochstetler <jacob.hochstetler@gmail.com>
2025-10-01 10:44:42 +02:00

1569 lines
38 KiB
Go

// 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),
},
},
}
}