Files
releases/v1/tester/runner_test.go
T
Ville Vesilehto f77322b3fb build: bump Go version requirement to 1.24 (#7839)
Go 1.23 is no longer supported as per Go release policy.

Changes:

- Use Go v1.24.6 as the project SDK requirement
- Apply lint fixes for Go 1.24
- Fix "non-constant format string in call" issues as seen in CI.

Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>
2025-08-24 09:02:09 +02:00

1096 lines
27 KiB
Go

// Copyright 2017 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 tester_test
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"maps"
"path/filepath"
"strings"
"testing"
"time"
"github.com/open-policy-agent/opa/v1/ast"
"github.com/open-policy-agent/opa/v1/cover"
"github.com/open-policy-agent/opa/v1/rego"
"github.com/open-policy-agent/opa/v1/storage"
"github.com/open-policy-agent/opa/v1/storage/inmem"
"github.com/open-policy-agent/opa/v1/tester"
"github.com/open-policy-agent/opa/v1/topdown"
"github.com/open-policy-agent/opa/v1/types"
"github.com/open-policy-agent/opa/v1/util/test"
)
func TestRun(t *testing.T) {
testRun(t, testRunConfig{})
}
func TestRunBenchmark(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
testRun(t, testRunConfig{bench: true})
}
func TestRunWithCoverage(t *testing.T) {
cov := cover.New()
modules := testRun(t, testRunConfig{coverTracer: cov})
report := cov.Report(modules)
if len(report.Files) != len(modules) {
t.Errorf("Expected %d files in coverage report, got %d", len(modules), len(report.Files))
}
if report.Coverage == 0 {
t.Error("Expected test coverage")
}
}
type expectedTestResult struct {
wantErr bool
wantFail bool
// nolint: structcheck // The test doesn't check this value, but should.
wantSkip bool
cases map[string]expectedTestResult
}
type testRunConfig struct {
bench bool
filter string
coverTracer topdown.QueryTracer
}
type expectedTestResults map[[2]string]expectedTestResult
func testRun(t *testing.T, conf testRunConfig) map[string]*ast.Module {
files := map[string]string{
"/a.rego": `package foo
import rego.v1
allow if { true }
`,
"/a_test.rego": `package foo
import rego.v1
test_pass if { allow }
non_test if { true }
test_fail if { not allow }
test_fail_non_bool = 100
test_err if { conflict }
conflict = true
conflict = false
test_duplicate if { false }
test_duplicate if { true }
test_duplicate if { true }
todo_test_skip if { true }
`,
"/b_test.rego": `package bar
import rego.v1
test_duplicate if { true }`,
"/c_test.rego": `package baz
import rego.v1
a.b.test_duplicate if { false }
a.b.test_duplicate if { true }
a.b.test_duplicate if { true }`,
// Regression test for issue #5496.
"/d_test.rego": `package test
import rego.v1
a[0] := 1
test_pass if { true }`,
"/e_test.rego": `package qux
import rego.v1
test_cases_pass[x] if { some x in ["foo", "bar"] }
test_cases_fail[x] if { some x in ["foo", "bar"]; false }
test_cases_partial_fail[x] if { some x in ["foo", "bar", "baz"]; x != "bar" }
test_cases_nested[x][y] if { some x in ["foo", "bar"]; some y in ["do", "re", "mi"]; not f(x, y) }
f(x, y) if { x == "foo"; y == "re" }`,
}
tests := expectedTestResults{
{"data.foo", "test_pass"}: {false, false, false, nil},
{"data.foo", "test_fail"}: {false, true, false, nil},
{"data.foo", "test_fail_non_bool"}: {false, true, false, nil},
{"data.foo", "test_duplicate"}: {false, true, false, nil},
{"data.foo", "test_duplicate#01"}: {false, false, false, nil},
{"data.foo", "test_duplicate#02"}: {false, false, false, nil},
{"data.foo", "test_err"}: {true, false, false, nil},
{"data.foo", "todo_test_skip"}: {false, false, true, nil},
{"data.bar", "test_duplicate"}: {false, false, false, nil},
{"data.baz", "a.b.test_duplicate"}: {false, true, false, nil},
{"data.baz", "a.b[\"test_duplicate#01\"]"}: {false, false, false, nil},
{"data.baz", "a.b[\"test_duplicate#02\"]"}: {false, false, false, nil},
{"data.test", "test_pass"}: {false, false, false, nil},
{"data.qux", "test_cases_pass"}: {false, false, false, map[string]expectedTestResult{
"foo": {false, false, false, nil},
"bar": {false, false, false, nil},
}},
{"data.qux", "test_cases_fail"}: {false, true, false, map[string]expectedTestResult{
"foo": {false, true, false, nil},
"bar": {false, true, false, nil},
}},
{"data.qux", "test_cases_partial_fail"}: {false, true, false, map[string]expectedTestResult{
"foo": {false, false, false, nil},
"bar": {false, true, false, nil},
"baz": {false, false, false, nil},
}},
{"data.qux", "test_cases_nested"}: {false, true, false, map[string]expectedTestResult{
"foo": {false, true, false, map[string]expectedTestResult{
"do": {false, false, false, nil},
"re": {false, true, false, nil},
"mi": {false, false, false, nil},
}},
"bar": {false, false, false, map[string]expectedTestResult{
"do": {false, false, false, nil},
"re": {false, false, false, nil},
"mi": {false, false, false, nil},
}},
}},
}
var modules map[string]*ast.Module
test.WithTempFS(files, func(d string) {
var rs []*tester.Result
rs, modules = doTestRunWithTmpDir(t, d, conf)
validateTestResults(t, tests, rs, conf)
})
return modules
}
func doTestRunWithTmpDir(t *testing.T, dir string, conf testRunConfig) ([]*tester.Result, map[string]*ast.Module) {
t.Helper()
ctx := t.Context()
paths := []string{dir}
modules, store, err := tester.Load(paths, nil)
if err != nil {
t.Fatal(err)
}
txn := storage.NewTransactionOrDie(ctx, store)
defer store.Abort(ctx, txn)
runner := tester.NewRunner().
SetStore(store).
SetModules(modules).
Filter(conf.filter).
SetTimeout(60 * time.Second).
SetCoverageQueryTracer(conf.coverTracer)
var ch chan *tester.Result
if conf.bench {
ch, err = runner.RunBenchmarks(ctx, txn, tester.BenchmarkOptions{})
} else {
ch, err = runner.RunTests(ctx, txn)
}
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
var rs []*tester.Result
for r := range ch {
rs = append(rs, r)
}
return rs, modules
}
func validateTestResults(t *testing.T, tests expectedTestResults, rs []*tester.Result, conf testRunConfig) {
t.Helper()
seen := map[[2]string]struct{}{}
for _, r := range rs {
k := [2]string{r.Package, r.Name}
seen[k] = struct{}{}
exp, ok := tests[k]
if !ok {
t.Errorf("Unexpected result for %v", k)
continue
} else if exp.wantErr != (r.Error != nil) || exp.wantFail != r.Fail {
t.Errorf("Expected %+v for %v but got: %v", exp, k, r)
} else {
// Test passed
if conf.bench && r.BenchmarkResult == nil {
t.Errorf("Expected BenchmarkResult for test %v, got nil", k)
} else if !conf.bench && r.BenchmarkResult != nil {
t.Errorf("Unexpected BenchmarkResult for test %v, expected nil", k)
}
}
if exp.cases != nil {
validateSubTestResults(t, exp.cases, r.SubResults)
}
}
for k := range tests {
if _, ok := seen[k]; !ok {
t.Errorf("Expected result for %v", k)
}
}
}
func validateSubTestResults(t *testing.T, tests map[string]expectedTestResult, srs tester.SubResultMap) {
t.Helper()
seen := map[string]struct{}{}
for k, exp := range tests {
seen[k] = struct{}{}
sr, ok := srs[k]
if !ok {
t.Errorf("Expected sub-result for %v", k)
continue
}
if exp.wantFail != sr.Fail {
t.Errorf("Expected %+v for %v but got: %v", exp, k, sr)
}
}
for k, v := range srs {
if _, ok := seen[k]; !ok {
t.Errorf("Expected sub-result for %v", k)
}
if v.SubResults != nil {
validateSubTestResults(t, tests[k].cases, v.SubResults)
}
}
}
func TestRunWithFilterRegex(t *testing.T) {
files := map[string]string{
"/a.rego": `package foo
import rego.v1
allow if { true }
`,
"/a_test.rego": `package foo
import rego.v1
test_pass if { allow }
non_test if { true }
test_fail if { not allow }
test_fail_non_bool = 100
test_err if { conflict }
conflict = true
conflict = false
test_duplicate if { false }
test_duplicate if { true }
test_duplicate if { true }
todo_test_skip if { true }
todo_test_skip_too if { false }
test_cases[x][y] if { x := "foo"; y := "bar" }
test_duplicate.foo[y] if { x := "foo"; y := "bar" }
test_duplicate[x][y] if { x := "foo"; y := "bar" }
`,
"/b_test.rego": `package bar
import rego.v1
test_duplicate if { true }`,
"/c_test.rego": `package baz
import rego.v1
a.b.test_duplicate if { false }
a.b.test_duplicate if { true }
a.b.test_duplicate if { true }`,
}
cases := []struct {
note string
regex string
tests expectedTestResults
}{
{
note: "all tests match",
regex: ".*",
tests: expectedTestResults{
{"data.foo", "test_pass"}: {false, false, false, nil},
{"data.foo", "test_fail"}: {false, true, false, nil},
{"data.foo", "test_fail_non_bool"}: {false, true, false, nil},
{"data.foo", "test_duplicate"}: {false, true, false, nil},
{"data.foo", "test_duplicate#01"}: {false, false, false, nil},
{"data.foo", "test_duplicate#02"}: {false, false, false, nil},
{"data.foo", "test_err"}: {true, false, false, nil},
{"data.foo", "todo_test_skip"}: {false, false, true, nil},
{"data.foo", "todo_test_skip_too"}: {false, false, true, nil},
{"data.foo", "test_cases"}: {false, false, false, nil},
{"data.foo", "test_duplicate#03"}: {false, false, false, nil},
{"data.foo", "test_duplicate#04"}: {false, false, false, nil},
{"data.bar", "test_duplicate"}: {false, false, false, nil},
{"data.baz", "a.b.test_duplicate"}: {false, true, false, nil},
{"data.baz", "a.b[\"test_duplicate#01\"]"}: {false, false, false, nil},
{"data.baz", "a.b[\"test_duplicate#02\"]"}: {false, false, false, nil},
},
},
{
note: "no filter",
regex: "",
tests: expectedTestResults{
{"data.foo", "test_pass"}: {false, false, false, nil},
{"data.foo", "test_fail"}: {false, true, false, nil},
{"data.foo", "test_fail_non_bool"}: {false, true, false, nil},
{"data.foo", "test_duplicate"}: {false, true, false, nil},
{"data.foo", "test_duplicate#01"}: {false, false, false, nil},
{"data.foo", "test_duplicate#02"}: {false, false, false, nil},
{"data.foo", "test_err"}: {true, false, false, nil},
{"data.foo", "todo_test_skip"}: {false, false, true, nil},
{"data.foo", "todo_test_skip_too"}: {false, false, true, nil},
{"data.foo", "test_cases"}: {false, false, false, nil},
{"data.foo", "test_duplicate#03"}: {false, false, false, nil},
{"data.foo", "test_duplicate#04"}: {false, false, false, nil},
{"data.bar", "test_duplicate"}: {false, false, false, nil},
{"data.baz", "a.b.test_duplicate"}: {false, true, false, nil},
{"data.baz", "a.b[\"test_duplicate#01\"]"}: {false, false, false, nil},
{"data.baz", "a.b[\"test_duplicate#02\"]"}: {false, false, false, nil},
},
},
{
note: "no tests match",
regex: "^$",
tests: nil,
},
{
note: "single package name",
regex: "bar",
tests: expectedTestResults{
{"data.bar", "test_duplicate"}: {false, false, false, nil},
},
},
{
note: "single package explicit",
regex: "data.bar.test_duplicate",
tests: expectedTestResults{
{"data.bar", "test_duplicate"}: {false, false, false, nil},
},
},
{
note: "single test",
regex: "test_pass",
tests: expectedTestResults{
{"data.foo", "test_pass"}: {false, false, false, nil},
},
},
{
note: "single test explicit",
regex: "data.foo.test_pass",
tests: expectedTestResults{
{"data.foo", "test_pass"}: {false, false, false, nil},
},
},
{
note: "single test skipped explicit",
regex: "data.foo.todo_test_skip_too",
tests: expectedTestResults{
{"data.foo", "todo_test_skip_too"}: {false, false, true, nil},
},
},
{
note: "wildcards",
regex: "^.*foo.*_fail.*$",
tests: expectedTestResults{
{"data.foo", "test_fail"}: {false, true, false, nil},
{"data.foo", "test_fail_non_bool"}: {false, true, false, nil},
},
},
{
note: "mixed",
regex: "(bar|data.foo.test_pass)",
tests: expectedTestResults{
{"data.foo", "test_pass"}: {false, false, false, nil},
{"data.bar", "test_duplicate"}: {false, false, false, nil},
},
},
{
note: "case insensitive",
regex: "(?i)DATA.BAR",
tests: expectedTestResults{
{"data.bar", "test_duplicate"}: {false, false, false, nil},
},
},
{
note: "matching ref rule halfways",
regex: "data.baz.a",
tests: expectedTestResults{
{"data.baz", "a.b.test_duplicate"}: {false, true, false, nil},
{"data.baz", "a.b[\"test_duplicate#01\"]"}: {false, false, false, nil},
{"data.baz", "a.b[\"test_duplicate#02\"]"}: {false, false, false, nil},
},
},
{
note: "matching sub-test rule",
regex: "data.foo.test_cases",
tests: expectedTestResults{
{"data.foo", "test_cases"}: {false, false, false, nil},
},
},
}
test.WithTempFS(files, func(d string) {
for _, tc := range cases {
t.Run(tc.note, func(t *testing.T) {
conf := testRunConfig{filter: tc.regex}
rs, _ := doTestRunWithTmpDir(t, d, conf)
validateTestResults(t, tc.tests, rs, conf)
})
}
})
}
func TestRunnerCancel(t *testing.T) {
testCancel(t, false)
}
func TestRunnerCancelBenchmark(t *testing.T) {
testCancel(t, true)
}
func testCancel(t *testing.T, bench bool) {
registerSleepBuiltin()
ctx, cancel := context.WithCancel(t.Context())
module := `package foo
import rego.v1
test_1 if { test.sleep("100ms") }
test_2 if { true }`
files := map[string]string{
"/a_test.rego": module,
}
test.WithTempFS(files, func(d string) {
paths := []string{d}
modules, store, err := tester.Load(paths, nil)
if err != nil {
t.Fatal(err)
}
txn := storage.NewTransactionOrDie(ctx, store)
runner := tester.NewRunner().SetStore(store).SetModules(modules)
// Everything below uses a canceled context..
cancel()
var ch chan *tester.Result
if bench {
ch, err = runner.RunBenchmarks(ctx, txn, tester.BenchmarkOptions{})
} else {
ch, err = runner.RunTests(ctx, txn)
}
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
var results []*tester.Result
for r := range ch {
results = append(results, r)
}
if len(results) != 0 {
t.Fatalf("Expected no tests to be run but, got: %d", len(results))
}
})
}
func TestRunnerTimeout(t *testing.T) {
test.Skip(t)
testTimeout(t, false)
}
func TestRunnerTimeoutBenchmark(t *testing.T) {
testTimeout(t, true)
}
func testTimeout(t *testing.T, bench bool) {
registerSleepBuiltin()
ctx := t.Context()
files := map[string]string{
"/a_test.rego": `package foo
import rego.v1
test_1 if { test.sleep("100ms") }
# 1ms is low enough for a single test to pass,
# but long enough for benchmark to timeout
test_2 if { test.sleep("1ms") }`,
}
test.WithTempFS(files, func(d string) {
paths := []string{d}
modules, store, err := tester.Load(paths, nil)
if err != nil {
t.Fatal(err)
}
duration, err := time.ParseDuration("15ms")
if err != nil {
t.Fatal(err)
}
txn := storage.NewTransactionOrDie(ctx, store)
runner := tester.NewRunner().SetTimeout(duration).SetStore(store).SetModules(modules)
var ch chan *tester.Result
if bench {
ch, err = runner.RunBenchmarks(ctx, txn, tester.BenchmarkOptions{})
} else {
ch, err = runner.RunTests(ctx, txn)
}
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
var results []*tester.Result
for r := range ch {
results = append(results, r)
}
if bench {
if !topdown.IsCancel(results[1].Error) {
t.Fatalf("Expected cancel error for second test but got: %v", results[1].Error)
}
} else {
if !topdown.IsCancel(results[1].Error) {
t.Fatalf("Expected test to have timed out")
}
}
})
}
func TestRunnerPrintOutput(t *testing.T) {
files := map[string]string{
"/test.rego": `package test
import rego.v1
test_a if { print("A") }
test_b if { false; print("B") }
test_c if { print("C"); false }
p.q.r.test_d if { print("D") }`,
"/test2.rego": `package test
import rego.v1
test_d if { print("D") }
test_e if { false; print("E") }
test_f if { print("F"); false }
p.q.r.test_g if { print("G") }`,
"/test3.rego": `package test
import rego.v1
test_h if { print("H") }
test_i if { false; print("I") }
test_j if { print("J"); false }
p.q.r.test_k if { print("K") }`,
}
ctx := t.Context()
test.WithTempFS(files, func(d string) {
paths := []string{d}
modules, store, err := tester.Load(paths, nil)
if err != nil {
t.Fatal(err)
}
txn := storage.NewTransactionOrDie(ctx, store)
runner := tester.NewRunner().SetStore(store).SetModules(modules).CapturePrintOutput(true)
ch, err := runner.RunTests(ctx, txn)
if err != nil {
t.Fatal(err)
}
exp := map[string]map[string]string{
"test.rego": {
"test_a": "A\n",
"test_b": "",
"test_c": "C\n",
"p.q.r.test_d": "D\n",
},
"test2.rego": {
"test_d": "D\n",
"test_e": "",
"test_f": "F\n",
"p.q.r.test_g": "G\n",
},
"test3.rego": {
"test_h": "H\n",
"test_i": "",
"test_j": "J\n",
"p.q.r.test_k": "K\n",
},
}
got := map[string]string{}
var lastFile string
for r := range ch {
if lastFile == "" {
lastFile = filepath.Base(r.Location.File)
} else if lastFile != filepath.Base(r.Location.File) {
// assert that all expected results for the file has been received
// the individual files could be out of order, but it has to be grouped by file
if !maps.Equal(exp[lastFile], got) {
t.Fatal("expected:", exp, "got:", got)
}
// clear got for the next file
got = map[string]string{}
lastFile = filepath.Base(r.Location.File)
}
got[r.Name] = string(r.Output)
}
// check the last file
if !maps.Equal(exp[lastFile], got) {
t.Fatal("expected:", exp, "got:", got)
}
})
}
func registerSleepBuiltin() {
ast.RegisterBuiltin(&ast.Builtin{
Name: "test.sleep",
Decl: types.NewFunction(
types.Args(types.S),
types.Nl,
),
})
topdown.RegisterBuiltinFunc("test.sleep", func(_ topdown.BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
d, _ := time.ParseDuration(string(operands[0].Value.(ast.String)))
time.Sleep(d)
return iter(ast.NullTerm())
})
}
func TestRunnerWithCustomBuiltin(t *testing.T) {
var myBuiltinDecl = &ast.Builtin{
Name: "my_sum",
Decl: types.NewFunction(
types.Args(
types.N,
types.N,
),
types.N,
),
}
var myBuiltin = &tester.Builtin{
Decl: myBuiltinDecl,
Func: rego.Function2(
&rego.Function{
Name: myBuiltinDecl.Name,
Decl: myBuiltinDecl.Decl,
},
func(_ rego.BuiltinContext, a, b *ast.Term) (*ast.Term, error) {
var num1, num2 int
if err := ast.As(a.Value, &num1); err != nil {
return nil, err
}
if err := ast.As(b.Value, &num2); err != nil {
return nil, err
}
return ast.IntNumberTerm(num1 + num2), nil
},
),
}
files := map[string]string{
"/test.rego": `package test
import rego.v1
test_a if { my_sum(2,3) == 5 }
test_b if { my_sum(5,4) == 1 }
test_c if { my_sum(4,1.0) == 5 }`,
}
ctx := t.Context()
test.WithTempFS(files, func(d string) {
paths := []string{d}
modules, store, err := tester.Load(paths, nil)
if err != nil {
t.Fatal(err)
}
txn := storage.NewTransactionOrDie(ctx, store)
runner := tester.NewRunner().SetStore(store).SetModules(modules).AddCustomBuiltins([]*tester.Builtin{myBuiltin})
ch, err := runner.RunTests(ctx, txn)
if err != nil {
t.Fatal(err)
}
var results []*tester.Result
for r := range ch {
results = append(results, r)
}
exp := map[string]bool{
"test_a": true,
"test_b": false,
"test_c": false,
}
got := map[string]bool{}
for _, tr := range results {
got[tr.Name] = tr.Pass()
}
if !maps.Equal(exp, got) {
t.Fatal("expected:", exp, "got:", got)
}
})
}
func TestRunnerWithBuiltinErrors(t *testing.T) {
const ruleTemplate = `package test
import rego.v1
test_json_parsing if {
x := json.unmarshal("%s")
x.test == 123
}`
testCases := []struct {
desc string
json string
builtinErrors bool
wantErr bool
}{
{
desc: "Valid JSON with flag enabled does not raise an error",
json: `{\"test\": 123}`,
builtinErrors: true,
},
{
desc: "Invalid JSON with flag enabled raises an error",
json: `test: 123`,
builtinErrors: true,
wantErr: true,
},
{
desc: "Invalid JSON with flag disabled does not raise an error",
json: `test: 123`,
},
}
ctx := t.Context()
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
files := map[string]string{
"builtin_error_test.rego": fmt.Sprintf(ruleTemplate, tc.json),
}
test.WithTempFS(files, func(d string) {
paths := []string{d}
modules, store, err := tester.Load(paths, nil)
if err != nil {
t.Fatal(err)
}
txn := storage.NewTransactionOrDie(ctx, store)
runner := tester.
NewRunner().
SetStore(store).
SetModules(modules).
RaiseBuiltinErrors(tc.builtinErrors)
ch, err := runner.RunTests(ctx, txn)
if err != nil {
t.Fatal(err)
}
for result := range ch {
if gotErr := result.Error != nil; gotErr != tc.wantErr {
t.Errorf("wantErr = %v, gotErr = %v", tc.wantErr, gotErr)
}
}
})
})
}
}
func TestLoad_DefaultRegoVersion(t *testing.T) {
tests := []struct {
note string
module string
expErrs []string
}{
{
note: "v0 module", // NOT default rego-version
module: `package test
p[x] {
x = "a"
}
test_p {
p["a"]
}`,
expErrs: []string{
"test.rego:3: rego_parse_error: `if` keyword is required before rule body",
"test.rego:3: rego_parse_error: `contains` keyword is required for partial set rules",
"test.rego:7: rego_parse_error: `if` keyword is required before rule body",
},
},
{
note: "import rego.v1",
module: `package test
import rego.v1
p contains x if {
x := "a"
}
test_p if {
"a" in p
}`,
},
{
note: "v1 module", // default rego-version
module: `package test
p contains x if {
x := "a"
}
test_p if {
"a" in p
}`,
},
}
for _, tc := range tests {
t.Run(tc.note, func(t *testing.T) {
files := map[string]string{
"test.rego": tc.module,
}
test.WithTempFS(files, func(root string) {
modules, store, err := tester.Load([]string{root}, nil)
if len(tc.expErrs) > 0 {
if err == nil {
t.Fatalf("Expected error but got nil")
}
for _, expErr := range tc.expErrs {
if !strings.Contains(err.Error(), expErr) {
t.Fatalf("Expected error to contain:\n\n%q\n\nbut got:\n\n%v", expErr, err)
}
}
} else {
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if modules == nil {
t.Fatalf("Expected modules to be non-nil")
}
if store == nil {
t.Fatalf("Expected store to be non-nil")
}
}
})
})
}
}
func TestRun_DefaultRegoVersion(t *testing.T) {
tests := []struct {
note string
module ast.Module
expErrs []string
}{
{
note: "no v1 violations",
module: ast.Module{
Package: ast.MustParsePackage(`package test`),
Rules: []*ast.Rule{
ast.MustParseRule(`p[x] { x = "a" }`),
ast.MustParseRule(`test_p { p["a"] }`),
},
},
},
{
note: "v1 violations",
module: ast.Module{
Package: ast.MustParsePackage(`package test`),
Imports: ast.MustParseImports(`
import data.foo
import data.bar as foo
`),
Rules: []*ast.Rule{
ast.MustParseRule(`p[x] { x = "a" }`),
ast.MustParseRule(`test_p { p["a"] }`),
},
},
expErrs: []string{
"rego_compile_error: import must not shadow import data.foo",
},
},
}
for _, tc := range tests {
t.Run(tc.note, func(t *testing.T) {
ctx := t.Context()
modules := map[string]*ast.Module{
"test": &tc.module,
}
store := inmem.New()
txn := storage.NewTransactionOrDie(ctx, store)
defer store.Abort(ctx, txn)
runner := tester.NewRunner().
SetStore(store).
SetModules(modules).
SetTimeout(10 * time.Second)
ch, err := runner.RunTests(ctx, txn)
if len(tc.expErrs) > 0 {
if err == nil {
t.Fatalf("Expected error but got nil")
}
for _, expErr := range tc.expErrs {
if !strings.Contains(err.Error(), expErr) {
t.Fatalf("Expected error to contain:\n\n%q\n\nbut got:\n\n%v", expErr, err)
}
}
} else {
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var rs []*tester.Result
for r := range ch {
rs = append(rs, r)
}
if len(rs) != 1 {
t.Fatalf("Expected exactly one result but got: %v", rs)
}
if rs[0].Fail {
t.Fatalf("Expected test to pass but it failed")
}
}
})
}
}
func TestReporterFormatsWithExplicitParallel(t *testing.T) {
tests := []struct {
note string
parallel int
r func(writer io.Writer) tester.Reporter
exp func(string)
}{
{
note: "Pretty Format",
parallel: 10,
r: func(w io.Writer) tester.Reporter {
return tester.PrettyReporter{
Output: w,
}
},
exp: func(output string) {
exp := `PASS: 4/4
`
if exp != output {
t.Fatalf("Expected (%d bytes):\n\n%v\n\nGot (%d bytes):\n\n%v", len(exp), exp, len(output), output)
}
},
},
{
note: "JSON Format",
parallel: 10,
r: func(w io.Writer) tester.Reporter {
return tester.JSONReporter{
Output: w,
}
},
exp: func(output string) {
// the order of the tests and filepath and duration will be different each execution
var r []*tester.Result
if err := json.Unmarshal([]byte(output), &r); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if len(r) != 4 {
t.Fatalf("Expected exactly 4 results but got: %v", r)
}
},
},
{
note: "Go Bench Format",
parallel: 10,
r: func(w io.Writer) tester.Reporter {
return tester.PrettyReporter{
Output: w,
BenchMarkGoBenchFormat: true,
}
},
exp: func(output string) {
exp := `PASS: 4/4
`
if exp != output {
t.Fatalf("Expected (%d bytes):\n\n%v\n\nGot (%d bytes):\n\n%v", len(exp), exp, len(output), output)
}
},
},
}
files := map[string]string{
"/test.rego": `package test
import rego.v1
test_a if { print("A") }
test_a if { print("A") }
test_a if { print("A") }
test_a if { print("A") }`,
}
for _, tc := range tests {
t.Run(tc.note, func(t *testing.T) {
ctx := t.Context()
test.WithTempFS(files, func(d string) {
paths := []string{d}
modules, store, err := tester.Load(paths, nil)
if err != nil {
t.Fatal(err)
}
txn := storage.NewTransactionOrDie(ctx, store)
runner := tester.NewRunner().SetStore(store).SetModules(modules).CapturePrintOutput(true)
ch, err := runner.RunTests(ctx, txn)
if err != nil {
t.Fatal(err)
}
var buf bytes.Buffer
r := tc.r(&buf)
if err := r.Report(ch); err != nil {
t.Fatal(err)
}
str := buf.String()
tc.exp(str)
})
})
}
}