Files
releases/v1/ast/capabilities_test.go
Anders Eknert d3f34a3387 Modernize analyzer fixes (#7965)
Have done this some time in the past, but there was a few
new issues this would highlight now that we're on Go 1.24.

Mostly:
- Use `b.Loop()` in benchmarks
- Use `strings.SplitSeq` where possible
- Remove `omitempty` tag for types that can't be empty

Signed-off-by: Anders Eknert <anders@eknert.com>
2025-10-10 17:51:02 +02:00

312 lines
6.9 KiB
Go

package ast
import (
"path"
"testing"
"github.com/open-policy-agent/opa/v1/util/test"
)
func TestParserCatchesIllegalCapabilities(t *testing.T) {
tests := []struct {
note string
regoVersion RegoVersion
capabilities Capabilities
expErr string
}{
{
note: "v0, bad future keyword",
regoVersion: RegoV0,
capabilities: Capabilities{
FutureKeywords: []string{"deadbeef"},
},
expErr: "illegal capabilities: unknown keyword: deadbeef",
},
{
note: "v1, bad future keyword",
regoVersion: RegoV1,
capabilities: Capabilities{
Features: []string{FeatureRegoV1},
FutureKeywords: []string{"deadbeef"},
},
expErr: "illegal capabilities: unknown keyword: deadbeef",
},
{
note: "v1, no rego_v1 feature",
regoVersion: RegoV1,
capabilities: Capabilities{},
expErr: "illegal capabilities: rego_v1 feature required for parsing v1 Rego",
},
}
for _, tc := range tests {
t.Run(tc.note, func(t *testing.T) {
var opts ParserOptions
opts.Capabilities = &tc.capabilities
opts.RegoVersion = tc.regoVersion
_, _, err := ParseStatementsWithOpts("test.rego", "true", opts)
if err == nil {
t.Fatal("expected error")
} else if errs, ok := err.(Errors); !ok || len(errs) != 1 {
t.Fatal("expected exactly one error but got:", err)
} else if errs[0].Code != ParseErr || errs[0].Message != tc.expErr {
t.Fatal("unexpected error:", err)
}
})
}
}
func TestParserCatchesIllegalFutureKeywordsBasedOnCapabilities(t *testing.T) {
tests := []struct {
note string
regoVersion RegoVersion
}{
{
note: "v0",
regoVersion: RegoV0,
},
{
note: "v1",
regoVersion: RegoV1,
},
}
for _, tc := range tests {
t.Run(tc.note, func(t *testing.T) {
var opts ParserOptions
opts.Capabilities = CapabilitiesForThisVersion()
opts.FutureKeywords = []string{"deadbeef"}
opts.RegoVersion = tc.regoVersion
_, _, err := ParseStatementsWithOpts("test.rego", "true", opts)
if err == nil {
t.Fatal("expected error")
} else if errs, ok := err.(Errors); !ok || len(errs) != 1 {
t.Fatal("expected exactly one error but got:", err)
} else if errs[0].Code != ParseErr || errs[0].Message != "unknown future keyword: deadbeef" {
t.Fatal("unexpected error:", err)
}
})
}
}
func TestParserCapabilitiesWithSpecificOptInAndOlderOPA(t *testing.T) {
src := `
package test
import future.keywords.in
p {
1 in [3,2,1]
}
`
opts := ParserOptions{
Capabilities: &Capabilities{},
RegoVersion: RegoV0,
}
_, err := ParseModuleWithOpts("test.rego", src, opts)
if err == nil {
t.Fatal("expected error")
} else if errs, ok := err.(Errors); !ok || len(errs) != 1 {
t.Fatal("expected exactly one error but got:", err)
} else if errs[0].Code != ParseErr || errs[0].Location.Row != 4 || errs[0].Message != "unexpected keyword, must be one of []" {
t.Fatal("unexpected error:", err)
}
}
func TestParserCapabilitiesWithWildcardOptInAndOlderOPA(t *testing.T) {
src := `
package test
import future.keywords
p {
1 in [3,2,1]
}
`
opts := ParserOptions{
Capabilities: &Capabilities{},
RegoVersion: RegoV0,
}
_, err := ParseModuleWithOpts("test.rego", src, opts)
if err == nil {
t.Fatal("expected error")
} else if errs, ok := err.(Errors); !ok || len(errs) != 1 {
t.Fatal("expected exactly one error but got:", err)
} else if errs[0].Code != ParseErr || errs[0].Location.Row != 7 || errs[0].Message != "unexpected identifier token: expected \\n or ; or }" {
t.Fatal("unexpected error:", err)
}
}
func TestLoadCapabilitiesVersion(t *testing.T) {
capabilitiesVersions, err := LoadCapabilitiesVersions()
if err != nil {
t.Fatal("expected success", err)
}
if len(capabilitiesVersions) == 0 {
t.Fatal("expected a non-empty array of capabilities versions")
}
for _, cv := range capabilitiesVersions {
if _, err := LoadCapabilitiesVersion(cv); err != nil {
t.Fatal("expected success", err)
}
}
}
func TestLoadCapabilitiesFile(t *testing.T) {
files := map[string]string{
"test-capabilities.json": `
{
"builtins": []
}
`,
}
test.WithTempFS(files, func(root string) {
_, err := LoadCapabilitiesFile(path.Join(root, "test-capabilities.json"))
if err != nil {
t.Fatal("expected success", err)
}
})
}
func TestCapabilitiesAddBuiltinSorted(t *testing.T) {
c := CapabilitiesForThisVersion()
indexOfEq := findBuiltinIndex(c, "eq")
if indexOfEq < 0 {
panic("expected to find eq")
}
c.addBuiltinSorted(&Builtin{Name: "eq"})
if c.Builtins[indexOfEq].Decl != nil {
t.Fatal("expected builtin to get overwritten")
}
c.addBuiltinSorted(&Builtin{Name: "~foo"}) // non-existent but always sorts to the end
if findBuiltinIndex(c, "~foo") != len(c.Builtins)-1 {
t.Fatal("expected builtin to be last in slice")
}
c.addBuiltinSorted(&Builtin{Name: " foo"}) // non-existent but always sorts to start
if findBuiltinIndex(c, " foo") != 0 {
t.Fatal("expected builtin to be first in slice")
}
c.addBuiltinSorted(&Builtin{Name: "plus1"}) // non-existent but always after plus in middle
if findBuiltinIndex(c, "plus1") != findBuiltinIndex(c, "plus")+1 {
t.Fatal("expected builtin to be immediately after plus")
}
}
func TestCapabilitiesMinimumCompatibleVersion(t *testing.T) {
tests := []struct {
note string
module string
version string
}{
{
note: "builtins",
module: `
package x
p { array.reverse([1,2,3]) }
`,
version: "0.36.0",
},
{
note: "keywords",
module: `
package x
import future.keywords.every
`,
version: "0.38.0",
},
{
note: "features (string prefix ref)",
module: `
package x
import future.keywords.if
p.a.b.c.d if { true }
`,
version: "0.46.0",
},
{
note: "features (general ref)",
module: `
package x
import future.keywords.if
p.a.b[c].d if { c := "foo" }
`,
version: "0.59.0",
},
{
note: "features (general ref + string prefix ref)",
module: `
package x
import future.keywords.if
p.a.b.c.d if { true }
p.a.b[c].d if { c := "foo" }
`,
version: "0.59.0",
},
{
note: "rego.v1 import",
module: `
package x
import rego.v1`,
version: "0.59.0",
},
}
for _, tc := range tests {
t.Run(tc.note, func(t *testing.T) {
c := MustCompileModulesWithOpts(map[string]string{"test.rego": tc.module}, CompileOpts{
ParserOptions: ParserOptions{
RegoVersion: RegoV0,
},
})
minVersion, found := c.Required.MinimumCompatibleVersion()
if !found || minVersion != tc.version {
t.Fatal("expected", tc.version, "but got", minVersion)
}
})
}
}
func BenchmarkCapabilitiesCurrentVersion(b *testing.B) {
var caps *Capabilities
for b.Loop() {
caps = CapabilitiesForThisVersion()
}
if caps == nil {
b.Fatal("expected capabilities to be non-nil")
}
}
func findBuiltinIndex(c *Capabilities, name string) int {
for i, bi := range c.Builtins {
if bi.Name == name {
return i
}
}
return -1
}