Files
releases/v1/cover/cover_test.go
T
Charlie Egan e7909e1875 cover: Update report to include ranges (#8752)
Coverage Range records the source span of each expression instead of
just row.

Additive API Changes:
- cover.Position.Col field
- cover.Range.Compare function
- ast.Location End() function
- ast.Location HasFile() function

covered_lines / not_covered_lines count unique rows across ranges, same
numbers as before.

I have also broken down the cover package a bit to aid future work.

Fixes https://github.com/open-policy-agent/opa/issues/8748

---------

Signed-off-by: Charlie Egan <charlie_egan@apple.com>
2026-06-09 17:05:19 +01:00

246 lines
5.0 KiB
Go

// Copyright 2018 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 cover
import (
"encoding/json"
"fmt"
"reflect"
"testing"
"github.com/open-policy-agent/opa/v1/ast"
"github.com/open-policy-agent/opa/v1/rego"
"github.com/open-policy-agent/opa/v1/topdown"
)
func TestCover(t *testing.T) {
cover := New()
module := `package test
import data.deadbeef # expect not reported
foo if {
bar
p
not baz
}
bar if {
a := 1
b := 2
a != b
}
baz if { # expect no exit
true
false # expect eval but fail
true # expect not covered
}
p if {
some bar # should not be included in coverage report
bar = 1
bar + 1 == 2
}
`
parsedModule, err := ast.ParseModuleWithOpts("test.rego", module, ast.ParserOptions{AllFutureKeywords: true})
if err != nil {
t.Fatal(err)
}
eval := rego.New(
rego.ParsedModule(parsedModule),
rego.Query("data.test.foo"),
rego.QueryTracer(cover),
)
ctx := t.Context()
_, err = eval.Eval(ctx)
if err != nil {
t.Fatal(err)
}
report := cover.Report(map[string]*ast.Module{
"test.rego": parsedModule,
})
fr, ok := report.Files["test.rego"]
if !ok {
t.Fatal("Expected file report for test.rego")
}
expectedCovered := []Position{
{Row: 5}, // foo head
{Row: 6}, {Row: 7}, {Row: 8}, // foo body
{Row: 11}, // bar head
{Row: 12}, {Row: 13}, {Row: 14}, // bar body
{Row: 18}, {Row: 19}, // baz body hits
{Row: 23}, // p head
{Row: 25}, {Row: 26}, // p body
}
expectedNotCovered := []Position{
{Row: 17}, // baz head
{Row: 20}, // baz body miss
}
for _, exp := range expectedCovered {
if !fr.IsCovered(exp.Row) {
t.Errorf("Expected %v to be covered", exp)
}
}
for _, exp := range expectedNotCovered {
if !fr.IsNotCovered(exp.Row) {
t.Errorf("Expected %v to NOT be covered", exp)
}
}
if len(expectedCovered) != fr.locCovered() {
t.Errorf(
"Expected %d loc to be covered, got %d instead",
len(expectedCovered),
fr.locCovered())
}
if len(expectedNotCovered) != fr.locNotCovered() {
t.Errorf(
"Expected %d loc to not be covered, got %d instead",
len(expectedNotCovered),
fr.locNotCovered())
}
expectedCoveragePercentage := 100.0 * float64(len(expectedCovered)) / float64(len(expectedCovered)+len(expectedNotCovered))
if expectedCoveragePercentage != fr.Coverage {
t.Errorf("Expected coverage %v != %v", expectedCoveragePercentage, fr.Coverage)
}
// there's just one file, hence the overall coverage is equal to the
// one of the only file report we have
if expectedCoveragePercentage != report.Coverage {
t.Errorf("Expected report coverage %f != %f",
expectedCoveragePercentage,
report.Coverage)
}
if t.Failed() {
bs, err := json.MarshalIndent(fr, "", " ")
if err != nil {
t.Fatal(err)
}
fmt.Println(string(bs))
}
}
func TestCoverNoDuplicates(t *testing.T) {
cover := New()
module := `package test
# Both a rule and an expression, but should not be counted twice
foo := 1
allow if { true }
`
parsedModule, err := ast.ParseModuleWithOpts("test.rego", module, ast.ParserOptions{AllFutureKeywords: true})
if err != nil {
t.Fatal(err)
}
eval := rego.New(
rego.ParsedModule(parsedModule),
rego.Query("data.test.allow"),
rego.QueryTracer(cover),
)
ctx := t.Context()
_, err = eval.Eval(ctx)
if err != nil {
t.Fatal(err)
}
report := cover.Report(map[string]*ast.Module{
"test.rego": parsedModule,
})
fr, ok := report.Files["test.rego"]
if !ok {
t.Fatal("Expected file report for test.rego")
}
expectedCovered := []Position{
{Row: 6}, // allow
}
expectedNotCovered := []Position{
{Row: 4}, // foo
}
for _, exp := range expectedCovered {
if !fr.IsCovered(exp.Row) {
t.Errorf("Expected %v to be covered", exp)
}
}
for _, exp := range expectedNotCovered {
if !fr.IsNotCovered(exp.Row) {
t.Errorf("Expected %v to NOT be covered", exp)
}
}
if len(expectedCovered) != fr.locCovered() {
t.Errorf(
"Expected %d loc to be covered, got %d instead",
len(expectedCovered),
fr.locCovered())
}
if len(expectedNotCovered) != fr.locNotCovered() {
t.Errorf(
"Expected %d loc to not be covered, got %d instead",
len(expectedNotCovered),
fr.locNotCovered())
}
expectedCoveragePercentage := 100.0 * float64(len(expectedCovered)) / float64(len(expectedCovered)+len(expectedNotCovered))
if expectedCoveragePercentage != fr.Coverage {
t.Errorf("Expected coverage %f != %f", expectedCoveragePercentage, fr.Coverage)
}
if expectedCoveragePercentage != report.Coverage {
t.Errorf("Expected report coverage %f != %f",
expectedCoveragePercentage,
report.Coverage)
}
if t.Failed() {
bs, err := json.MarshalIndent(fr, "", " ")
if err != nil {
t.Fatal(err)
}
fmt.Println(string(bs))
}
}
func TestCoverTraceConfig(t *testing.T) {
ct := topdown.QueryTracer(New())
conf := ct.Config()
expected := topdown.TraceConfig{
PlugLocalVars: false,
}
if !reflect.DeepEqual(expected, conf) {
t.Fatalf("Expected config: %+v, got %+v", expected, conf)
}
}