Files
releases/v1/topdown/graphql_bench_test.go
Stephan Renatus 8853b19a12 e2e/authz,topdown: fix benchmarks (#7980)
Before, we'd see these benchmarks die with

    benchmark.go:412: B.Loop called with timer stopped

Now, we can run them again:

```
% go test -bench=. -v -run=XXX ./v1/test/e2e/authz
{
  "level": "info",
  "msg": "Test server ready and listening on: http://127.0.0.1:64200",
  "time": "2025-10-15T11:44:06+02:00"
}
goos: darwin
goarch: arm64
pkg: github.com/open-policy-agent/opa/v1/test/e2e/authz
cpu: Apple M4 Max
BenchmarkRESTAuthzForbidAuthn
BenchmarkRESTAuthzForbidAuthn-16           22623             52375 ns/op
BenchmarkRESTAuthzForbidPath
BenchmarkRESTAuthzForbidPath-16            16729             71916 ns/op
BenchmarkRESTAuthzForbidMethod
BenchmarkRESTAuthzForbidMethod-16          16434             72963 ns/op
BenchmarkRESTAuthzAllow10Paths
BenchmarkRESTAuthzAllow10Paths-16          15883             74936 ns/op
BenchmarkRESTAuthzAllow100Paths
BenchmarkRESTAuthzAllow100Paths-16         10000            109283 ns/op
BenchmarkRESTAuthzAllow1000Paths
BenchmarkRESTAuthzAllow1000Paths-16         2731            462167 ns/op
PASS
ok      github.com/open-policy-agent/opa/v1/test/e2e/authz      13.349s
```

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2025-10-15 12:47:08 +02:00

400 lines
11 KiB
Go

// Copyright 2025 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 topdown
import (
"context"
_ "embed"
"fmt"
"testing"
"github.com/open-policy-agent/opa/v1/ast"
"github.com/open-policy-agent/opa/v1/topdown/cache"
)
// The MaxNumEntries value for the named caches
const defaultCacheEntries = 10
// The number of types to add to the existing schema which already has one type definition
const extraTypes = 999
func BenchmarkGraphQLSchemaIsValid(b *testing.B) {
benches := []struct {
desc string
schema *ast.Term
cache cache.InterQueryValueCache
result *ast.Term
}{
{
desc: "Trivial Schema - string",
schema: ast.StringTerm(employeeGQLSchema),
cache: nil,
result: ast.InternedTerm(true),
},
{
desc: "Trivial Schema with cache - string",
schema: ast.StringTerm(employeeGQLSchema),
cache: valueCacheFactory(gqlCacheName, defaultCacheEntries),
result: ast.InternedTerm(true),
},
{
desc: fmt.Sprintf("Schema w/ %d types - string", extraTypes+1),
schema: ast.StringTerm(schemaWithExtraEmployeeTypes(extraTypes)),
cache: nil,
result: ast.InternedTerm(true),
},
{
desc: fmt.Sprintf("Schema w/ %d types with cache - string", extraTypes+1),
schema: ast.StringTerm(schemaWithExtraEmployeeTypes(extraTypes)),
cache: valueCacheFactory(gqlCacheName, defaultCacheEntries),
result: ast.InternedTerm(true),
},
{
desc: "Trivial Schema - AST object",
schema: ast.NewTerm(ast.MustParseTerm(employeeGQLSchemaAST).Value.(ast.Object)),
cache: nil,
result: ast.InternedTerm(true),
},
{
desc: "Trivial Schema with cache - AST object",
schema: ast.NewTerm(ast.MustParseTerm(employeeGQLSchemaAST).Value.(ast.Object)),
cache: valueCacheFactory(gqlCacheName, defaultCacheEntries),
result: ast.InternedTerm(true),
},
}
for _, bench := range benches {
b.Run(bench.desc, func(b *testing.B) {
for b.Loop() {
var result *ast.Term
err := builtinGraphQLSchemaIsValid(
BuiltinContext{
InterQueryBuiltinValueCache: bench.cache,
},
[]*ast.Term{bench.schema},
func(term *ast.Term) error {
result = term
return nil
},
)
if err != nil {
b.Fatalf("unexpected error: %s", err)
}
if !bench.result.Equal(result) {
b.Fatalf("unexpected result: wanted: %#v got: %#v", bench.result, result)
}
}
})
}
}
func BenchmarkGraphQLParseSchema(b *testing.B) {
benches := []struct {
desc string
schema *ast.Term
cache cache.InterQueryValueCache
result *ast.Term
}{
{
desc: "Trivial Schema - string",
schema: ast.StringTerm(employeeGQLSchema),
cache: nil,
result: ast.NewTerm(employeeGQLSchemaASTObj),
},
{
desc: "Trivial Schema with cache - string",
schema: ast.StringTerm(employeeGQLSchema),
cache: valueCacheFactory(gqlCacheName, defaultCacheEntries),
result: ast.NewTerm(employeeGQLSchemaASTObj),
},
}
for _, bench := range benches {
b.Run(bench.desc, func(b *testing.B) {
for b.Loop() {
var result *ast.Term
err := builtinGraphQLParseSchema(
BuiltinContext{
InterQueryBuiltinValueCache: bench.cache,
},
[]*ast.Term{bench.schema},
func(term *ast.Term) error {
result = term
return nil
},
)
if err != nil {
b.Fatalf("unexpected error: %s", err)
}
if !bench.result.Equal(result) {
b.Errorf("Unexpected result, expected %#v, got %#v", bench.result, result)
return
}
}
})
}
}
func BenchmarkGraphQLParseQuery(b *testing.B) {
benches := []struct {
desc string
query *ast.Term
cache cache.InterQueryValueCache
result *ast.Term
}{
{
desc: "Trivial Query - string",
query: ast.StringTerm(`{ employeeByID(id: "alice") { salary } }`),
cache: nil,
result: ast.NewTerm(employeeGQLQueryASTObj),
},
{
desc: "Trivial Query with cache - string",
query: ast.StringTerm(`{ employeeByID(id: "alice") { salary } }`),
cache: valueCacheFactory(gqlCacheName, defaultCacheEntries),
result: ast.NewTerm(employeeGQLQueryASTObj),
},
}
for _, bench := range benches {
b.Run(bench.desc, func(b *testing.B) {
for b.Loop() {
var result *ast.Term
err := builtinGraphQLParseQuery(
BuiltinContext{
InterQueryBuiltinValueCache: bench.cache,
},
[]*ast.Term{bench.query},
func(term *ast.Term) error {
result = term
return nil
},
)
if err != nil {
b.Fatalf("unexpected error: %s", err)
}
if !bench.result.Equal(result) {
b.Errorf("Unexpected result, expected %#v, got %#v", bench.result, result)
return
}
}
})
}
}
func BenchmarkGraphQLIsValid(b *testing.B) {
benches := []struct {
desc string
schema *ast.Term
cache cache.InterQueryValueCache
query *ast.Term
result *ast.Term
}{
{
desc: "Trivial Schema - string",
cache: nil,
query: ast.StringTerm(`{ employeeByID(id: "alice") { salary } }`),
schema: ast.StringTerm(employeeGQLSchema),
result: ast.InternedTerm(true),
},
{
desc: "Trivial Schema with cache - string",
cache: valueCacheFactory(gqlCacheName, defaultCacheEntries),
query: ast.StringTerm(`{ employeeByID(id: "alice") { salary } }`),
schema: ast.StringTerm(employeeGQLSchema),
result: ast.InternedTerm(true),
},
{
desc: fmt.Sprintf("Schema w/ %d types - string", extraTypes+1),
cache: nil,
query: ast.StringTerm(`{ employeeByID(id: "alice") { salary } }`),
schema: ast.StringTerm(schemaWithExtraEmployeeTypes(extraTypes)),
result: ast.InternedTerm(true),
},
{
desc: fmt.Sprintf("Schema w/ %d types with cache - string", extraTypes+1),
cache: valueCacheFactory(gqlCacheName, defaultCacheEntries),
query: ast.StringTerm(`{ employeeByID(id: "alice") { salary } }`),
schema: ast.StringTerm(schemaWithExtraEmployeeTypes(extraTypes)),
result: ast.InternedTerm(true),
},
}
for _, bench := range benches {
b.Run(bench.desc, func(b *testing.B) {
for b.Loop() {
var result *ast.Term
err := builtinGraphQLIsValid(
BuiltinContext{
InterQueryBuiltinValueCache: bench.cache,
},
[]*ast.Term{bench.query, bench.schema},
func(term *ast.Term) error {
result = term
return nil
},
)
if err != nil {
b.Fatalf("unexpected error: %s", err)
}
if !bench.result.Equal(result) {
b.Errorf("Unexpected result, expected %#v, got %#v", bench.result, result)
return
}
}
})
}
}
func BenchmarkGraphQLParse(b *testing.B) {
// Use this to map result item position to purpose for better error messages
resultItemDescription := []string{"query_ast", "schema_ast"}
benches := []struct {
desc string
schema *ast.Term
cache cache.InterQueryValueCache
query *ast.Term
result *ast.Term
}{
{
desc: "Trivial Schema - string",
cache: nil,
query: ast.StringTerm(`{ employeeByID(id: "alice") { salary } }`),
schema: ast.StringTerm(employeeGQLSchema),
result: ast.ArrayTerm(
ast.NewTerm(employeeGQLQueryASTObj),
ast.NewTerm(employeeGQLSchemaASTObj),
),
},
{
desc: "Trivial Schema with cache - string",
cache: valueCacheFactory(gqlCacheName, defaultCacheEntries),
query: ast.StringTerm(`{ employeeByID(id: "alice") { salary } }`),
schema: ast.StringTerm(employeeGQLSchema),
result: ast.ArrayTerm(
ast.NewTerm(employeeGQLQueryASTObj),
ast.NewTerm(employeeGQLSchemaASTObj),
),
},
}
for _, bench := range benches {
b.Run(bench.desc, func(b *testing.B) {
for b.Loop() {
var result *ast.Term
err := builtinGraphQLParse(
BuiltinContext{
InterQueryBuiltinValueCache: bench.cache,
},
[]*ast.Term{bench.query, bench.schema},
func(term *ast.Term) error {
result = term
return nil
},
)
if err != nil {
b.Fatalf("unexpected error: %s", err)
}
if !bench.result.Equal(result) {
b.Errorf("Unexpected result, expected %#v, got %#v", bench.result, result)
return
}
// Check each item in array result
for i := range bench.result.Value.(*ast.Array).Len() {
expected := bench.result.Value.(*ast.Array).Elem(i)
actual := result.Value.(*ast.Array).Elem(i)
if !expected.Equal(actual) {
b.Errorf("Unexpected value at result[%d] (%s), expected %#v, got %#v", i, resultItemDescription[i], expected, actual)
return
}
}
}
})
}
}
func BenchmarkGraphQLParseAndVerify(b *testing.B) {
// Use this to map result item position to purpose for better error messages
resultItemDescription := []string{"is_valid", "query_ast", "schema_ast"}
benches := []struct {
desc string
schema *ast.Term
cache cache.InterQueryValueCache
query *ast.Term
result *ast.Term
}{
{
desc: "Trivial Schema - string",
cache: nil,
query: ast.StringTerm(`{ employeeByID(id: "alice") { salary } }`),
schema: ast.StringTerm(employeeGQLSchema),
result: ast.ArrayTerm(
ast.InternedTerm(true),
ast.NewTerm(employeeGQLQueryASTObj),
ast.NewTerm(employeeGQLSchemaASTObj),
),
},
{
desc: "Trivial Schema with cache - string",
cache: valueCacheFactory(gqlCacheName, defaultCacheEntries),
query: ast.StringTerm(`{ employeeByID(id: "alice") { salary } }`),
schema: ast.StringTerm(employeeGQLSchema),
result: ast.ArrayTerm(
ast.InternedTerm(true),
ast.NewTerm(employeeGQLQueryASTObj),
ast.NewTerm(employeeGQLSchemaASTObj),
),
},
}
for _, bench := range benches {
b.Run(bench.desc, func(b *testing.B) {
for b.Loop() {
var result *ast.Term
err := builtinGraphQLParseAndVerify(
BuiltinContext{
InterQueryBuiltinValueCache: bench.cache,
},
[]*ast.Term{bench.query, bench.schema},
func(term *ast.Term) error {
result = term
return nil
},
)
b.StopTimer()
if err != nil {
b.Fatalf("unexpected error: %s", err)
}
if !bench.result.Equal(result) {
b.Errorf("Unexpected result, expected %#v, got %#v", bench.result, result)
return
}
// Check each item in array result
for i := range bench.result.Value.(*ast.Array).Len() {
expected := bench.result.Value.(*ast.Array).Elem(i)
actual := result.Value.(*ast.Array).Elem(i)
if !expected.Equal(actual) {
b.Errorf("Unexpected value at result[%d] (%s), expected %#v, got %#v", i, resultItemDescription[i], expected, actual)
return
}
}
b.StartTimer() // b.Loop() requires a running timer
}
})
}
}
func valueCacheFactory(name string, maxEntries int) cache.InterQueryValueCache {
return cache.NewInterQueryValueCache(
context.Background(),
&cache.Config{
InterQueryBuiltinValueCache: cache.InterQueryBuiltinValueCacheConfig{
NamedCacheConfigs: map[string]*cache.NamedValueCacheConfig{
name: {
MaxNumEntries: &[]int{maxEntries}[0],
},
},
},
})
}