Skip to content

Commit c8c5815

Browse files
authored
Merge pull request #144 from strongdm/2023-03-19-corpus-update
Replace coverage exceptions with corpus-driven validate coverage
2 parents a4d0ae8 + 2a24272 commit c8c5815

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+984
-396
lines changed

Makefile

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ linters:
99
golangci-lint run
1010
go run github.com/alecthomas/go-check-sumtype/cmd/go-check-sumtype@latest -default-signifies-exhaustive=false ./...
1111
go test -coverprofile=coverage.out ./...
12-
sed -i '' '/^github.com\/cedar-policy\/cedar-go\/internal\/schema\/parser\/cedarschema.go/d' coverage.out
13-
go tool cover -func=coverage.out | sed 's/%$$//' | awk '$$2 == "isCedarType" { next } $$2 == "Entity" && $$1 ~ /entity\.go/ { next } $$2 == "typeOfExtensionCall" { next } { if ($$3 < 100.0) { printf "Insufficient code coverage for %s\n", $$0; failed=1 } } END { exit failed }'
12+
go tool cover -func=coverage.out | sed 's/%$$//' | awk '{ if ($$3 < 100.0) { printf "Insufficient code coverage for %s\n", $$0; failed=1 } } END { exit failed }'
1413

1514
# Download the latest corpus tests tarball and overwrite corpus-tests.tar.gz if changed
1615
check-upstream-corpus:

corpus-tests-json-schemas.tar.gz

-155 KB
Binary file not shown.

corpus-tests-validation.tar.gz

41.4 KB
Binary file not shown.

corpus-tests.tar.gz

-350 KB
Binary file not shown.

test/cedar-validation-tool/src/main.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ fn make_entity_uid(r: &EntityRef) -> EntityUid {
9696
)
9797
}
9898

99+
fn sort_errors(errors: &mut Vec<String>) {
100+
errors.sort();
101+
}
102+
99103
fn main() {
100104
let args: Vec<String> = env::args().collect();
101105
if args.len() != 3 {
@@ -136,14 +140,16 @@ fn main() {
136140
let permissive_policy = validator.validate(&policy_set, ValidationMode::Permissive);
137141

138142
// Aggregate results
139-
let strict_all_errors: Vec<String> = strict_policy
143+
let mut strict_all_errors: Vec<String> = strict_policy
140144
.validation_errors()
141145
.map(|e| format!("{e}"))
142146
.collect();
143-
let permissive_all_errors: Vec<String> = permissive_policy
147+
let mut permissive_all_errors: Vec<String> = permissive_policy
144148
.validation_errors()
145149
.map(|e| format!("{e}"))
146150
.collect();
151+
sort_errors(&mut strict_all_errors);
152+
sort_errors(&mut permissive_all_errors);
147153

148154
// Per-policy breakdown
149155
let mut per_policy = BTreeMap::new();
@@ -158,16 +164,18 @@ fn main() {
158164
let permissive_errors_all: Vec<_> = permissive_result2.validation_errors().collect();
159165

160166
for pid_str in &policy_ids {
161-
let strict_for_policy: Vec<String> = strict_errors_all
167+
let mut strict_for_policy: Vec<String> = strict_errors_all
162168
.iter()
163169
.filter(|e| e.policy_id().to_string() == *pid_str)
164170
.map(|e| format!("{e}"))
165171
.collect();
166-
let permissive_for_policy: Vec<String> = permissive_errors_all
172+
let mut permissive_for_policy: Vec<String> = permissive_errors_all
167173
.iter()
168174
.filter(|e| e.policy_id().to_string() == *pid_str)
169175
.map(|e| format!("{e}"))
170176
.collect();
177+
sort_errors(&mut strict_for_policy);
178+
sort_errors(&mut permissive_for_policy);
171179
per_policy.insert(
172180
pid_str.clone(),
173181
PerPolicyResult {

x/exp/schema/validate/cedar_type.go

Lines changed: 88 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package validate
22

33
import (
4+
"cmp"
45
"fmt"
56
"slices"
67
"strings"
@@ -51,7 +52,7 @@ func (typeExtension) isCedarType() { _ = "hack for code coverage" }
5152
func typeIncompatErr(a, b cedarType) *typeIncompatError {
5253
nameA := cedarTypeName(a)
5354
nameB := cedarTypeName(b)
54-
if cedarTypeSortKey(a) > cedarTypeSortKey(b) {
55+
if compareCedarType(a, b) > 0 {
5556
nameA, nameB = nameB, nameA
5657
}
5758
return &typeIncompatError{msg: fmt.Sprintf("the types %s and %s are not compatible", nameA, nameB)}
@@ -64,14 +65,7 @@ func typeIncompatErrMulti(types []cedarType) *typeIncompatError {
6465
sorted := make([]cedarType, len(types))
6566
copy(sorted, types)
6667
slices.SortFunc(sorted, func(a, b cedarType) int {
67-
ka, kb := cedarTypeSortKey(a), cedarTypeSortKey(b)
68-
if ka < kb {
69-
return -1
70-
}
71-
if ka > kb {
72-
return 1
73-
}
74-
return 0
68+
return compareCedarType(a, b)
7569
})
7670
names := make([]string, len(sorted))
7771
for i, t := range sorted {
@@ -99,57 +93,104 @@ func typeIncompatErrMulti(types []cedarType) *typeIncompatError {
9993
return &typeIncompatError{msg: sb.String()}
10094
}
10195

102-
// cedarTypeSortKey returns a sort key for ordering types in error messages.
103-
// Matches Rust's structural type ordering (True < False < Never < Long < String < Set < Record < Entity < Extension).
104-
func cedarTypeSortKey(t cedarType) string {
105-
switch tv := t.(type) {
96+
func compareCedarType(a, b cedarType) int {
97+
ak, bk := cedarTypeKindRank(a), cedarTypeKindRank(b)
98+
if ak != bk {
99+
return ak - bk
100+
}
101+
102+
if av, ok := a.(typeSet); ok {
103+
return compareCedarType(av.element, b.(typeSet).element)
104+
}
105+
if av, ok := a.(typeRecord); ok {
106+
return compareRecordTypes(av, b.(typeRecord))
107+
}
108+
if av, ok := a.(typeEntity); ok {
109+
return compareEntityLUB(av.lub, b.(typeEntity).lub)
110+
}
111+
return strings.Compare(cedarTypeName(a), cedarTypeName(b))
112+
}
113+
114+
func cedarTypeKindRank(t cedarType) int {
115+
switch t.(type) {
106116
case typeTrue:
107-
return "0a"
117+
return 0
108118
case typeFalse:
109-
return "0b"
119+
return 1
110120
case typeBool:
111-
return "0c"
112-
case typeNever:
113-
return "1"
121+
return 2
114122
case typeLong:
115-
return "2"
123+
return 4
124+
case typeNever:
125+
return 3
116126
case typeString:
117-
return "3"
127+
return 5
118128
case typeSet:
119-
return "4:" + cedarTypeSortKey(tv.element)
129+
return 6
120130
case typeRecord:
121-
// Sort by attribute keys/types (matches Rust BTreeMap ordering)
122-
key := "5"
123-
keys := make([]string, 0, len(tv.attrs))
124-
for k := range tv.attrs {
125-
keys = append(keys, string(k))
126-
}
127-
slices.Sort(keys)
128-
for _, k := range keys {
129-
at := tv.attrs[types.String(k)]
130-
key += ":" + k + ":" + cedarTypeSortKey(at.typ)
131-
}
132-
return key
131+
return 7
133132
case typeEntity:
134-
return "6:" + cedarEntityTypeName(tv.lub)
133+
return 8
135134
case typeExtension:
135+
return 9
136+
default:
137+
return -1
136138
}
137-
return "7:" + string(t.(typeExtension).name)
139+
}
140+
141+
func compareRecordTypes(a, b typeRecord) int {
142+
ak := make([]string, 0, len(a.attrs))
143+
for k := range a.attrs {
144+
ak = append(ak, string(k))
145+
}
146+
bk := make([]string, 0, len(b.attrs))
147+
for k := range b.attrs {
148+
bk = append(bk, string(k))
149+
}
150+
slices.Sort(ak)
151+
slices.Sort(bk)
152+
153+
n := len(ak)
154+
if len(bk) < n {
155+
n = len(bk)
156+
}
157+
for i := 0; i < n; i++ {
158+
if c := strings.Compare(ak[i], bk[i]); c != 0 {
159+
return c
160+
}
161+
aat := a.attrs[types.String(ak[i])]
162+
bat := b.attrs[types.String(bk[i])]
163+
if c := compareCedarType(aat.typ, bat.typ); c != 0 {
164+
return c
165+
}
166+
}
167+
return cmp.Compare(len(ak), len(bk))
168+
}
169+
170+
func compareEntityLUB(a, b entityLUB) int {
171+
n := min(len(a.elements), len(b.elements))
172+
for i := 0; i < n; i++ {
173+
as, bs := string(a.elements[i]), string(b.elements[i])
174+
if c := strings.Compare(as, bs); c != 0 {
175+
return c
176+
}
177+
}
178+
return cmp.Compare(len(a.elements), len(b.elements))
138179
}
139180

140181
// cedarTypeName returns the Rust Cedar display name for a type.
141182
func cedarTypeName(t cedarType) string {
142183
switch tv := t.(type) {
143184
case typeNever:
144-
return "__cedar::internal::Any"
185+
return "__cedar::internal::Never"
186+
case typeLong:
187+
return "Long"
145188
case typeTrue:
146189
return "__cedar::internal::True"
147190
case typeFalse:
148191
return "__cedar::internal::False"
149192
case typeBool:
150193
return "Bool"
151-
case typeLong:
152-
return "Long"
153194
case typeString:
154195
return "String"
155196
case typeSet:
@@ -159,14 +200,13 @@ func cedarTypeName(t cedarType) string {
159200
case typeEntity:
160201
return cedarEntityTypeName(tv.lub)
161202
case typeExtension:
203+
return string(tv.name)
204+
default:
205+
return "?"
162206
}
163-
return string(t.(typeExtension).name)
164207
}
165208

166209
func cedarEntityTypeName(lub entityLUB) string {
167-
if len(lub.elements) == 0 {
168-
return "__cedar::internal::AnyEntity"
169-
}
170210
if len(lub.elements) == 1 {
171211
return string(lub.elements[0])
172212
}
@@ -192,6 +232,9 @@ func cedarRecordTypeName(r typeRecord) string {
192232
for _, k := range keys {
193233
at := r.attrs[types.String(k)]
194234
sb.WriteString(k)
235+
if !at.required {
236+
sb.WriteRune('?')
237+
}
195238
sb.WriteString(": ")
196239
sb.WriteString(cedarTypeName(at.typ))
197240
sb.WriteRune(',')
@@ -254,14 +297,13 @@ func (v *Validator) isSubtype(a, b cedarType) bool {
254297

255298
// leastUpperBound computes the LUB of two types.
256299
func (v *Validator) leastUpperBound(a, b cedarType) (cedarType, error) {
257-
if _, ok := a.(typeNever); ok {
258-
return b, nil
259-
}
260300
if _, ok := b.(typeNever); ok {
261301
return a, nil
262302
}
263303

264304
switch av := a.(type) {
305+
case typeNever:
306+
return b, nil
265307
case typeTrue:
266308
switch b.(type) {
267309
case typeTrue:
@@ -279,10 +321,8 @@ func (v *Validator) leastUpperBound(a, b cedarType) (cedarType, error) {
279321
case typeNever, typeLong, typeString, typeSet, typeRecord, typeEntity, typeExtension:
280322
}
281323
case typeBool:
282-
switch b.(type) {
283-
case typeTrue, typeFalse, typeBool:
324+
if isBoolType(b) {
284325
return typeBool{}, nil
285-
case typeNever, typeLong, typeString, typeSet, typeRecord, typeEntity, typeExtension:
286326
}
287327
case typeLong:
288328
if _, ok := b.(typeLong); ok {
@@ -312,8 +352,6 @@ func (v *Validator) leastUpperBound(a, b cedarType) (cedarType, error) {
312352
if bv, ok := b.(typeExtension); ok && av.name == bv.name {
313353
return av, nil
314354
}
315-
case typeNever:
316-
// Already handled above; unreachable.
317355
}
318356

319357
return nil, fmt.Errorf("incompatible types for least upper bound")

x/exp/schema/validate/marker_test.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,22 @@ func TestMarkerMethods(t *testing.T) {
1515
typeRecord{}.isCedarType()
1616
typeEntity{}.isCedarType()
1717
typeExtension{}.isCedarType()
18+
}
1819

19-
// Defensive type name/sort key paths for types that rarely appear in error messages
20-
cedarTypeSortKey(typeNever{})
21-
cedarTypeSortKey(typeBool{})
22-
cedarTypeName(typeNever{})
23-
cedarEntityTypeName(entityLUB{})
20+
func TestCedarTypeKindRankDefault(t *testing.T) {
21+
// nil cannot occur in normal type flow, but we keep a default for defensive
22+
// behavior and exercise it explicitly here.
23+
var nilType cedarType
24+
if got := cedarTypeKindRank(nilType); got != -1 {
25+
t.Fatalf("cedarTypeKindRank(nil) = %d, want -1", got)
26+
}
27+
}
2428

29+
func TestCedarTypeNameDefault(t *testing.T) {
30+
// nil cannot occur in normal type flow, but we keep a default for defensive
31+
// behavior and exercise it explicitly here.
32+
var nilType cedarType
33+
if got := cedarTypeName(nilType); got != "?" {
34+
t.Fatalf("cedarTypeName(nil) = %q, want %q", got, "?")
35+
}
2536
}

0 commit comments

Comments
 (0)