Add cost stability tests for chained and nested CEL comprehensions

This commit is contained in:
Joe Betz 2024-02-28 12:00:41 -05:00
parent d49949b642
commit 31f7efab20
2 changed files with 70 additions and 20 deletions

View File

@ -25,6 +25,7 @@ import (
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
celconfig "k8s.io/apiserver/pkg/apis/cel"
"k8s.io/utils/ptr"
)
func TestCelCostStability(t *testing.T) {
@ -1093,6 +1094,20 @@ func TestCelCostStability(t *testing.T) {
"self.listOfMap[0]['z'] == 'g'": 5,
"self.listOfObj[0].field3 == 'h'": 5,
"self.listOfListMap[0].exists(e, e.k3 == '3' && e.v3 == 'i')": 14,
// chained comprehensions
"self.mapOfMap.map(k, k).map(k, k).size() == 1": 32,
"self.mapOfListMap.map(k, k).map(k, k).size() == 1": 32,
"self.mapOfList.map(k, k).map(k, k).size() == 1": 32,
"self.listOfMap.map(e, e).map(e, e).size() == 1": 32,
"self.listOfListMap.map(e, e).map(e, e).size() == 1": 32,
// nested comprehensions
"self.mapOfMap.map(k, self.mapOfMap[k].map(m, m)).size() == 1": 34,
"self.mapOfListMap.map(k, self.mapOfListMap[k].map(m, m)).size() == 1": 34,
"self.mapOfList.map(k, self.mapOfList[k].map(l, l)).size() == 1": 34,
"self.listOfMap.map(e, e.map(m, m)).size() == 1": 32,
"self.listOfListMap.map(e, e.map(e, e)).size() == 1": 32,
},
},
{name: "optionals",
@ -1520,7 +1535,7 @@ func TestCelEstimatedCostStability(t *testing.T) {
"!self.listMap.exists_one(m, m.k.startsWith('a'))": 5242880,
"size(self.listMap.filter(m, m.k == 'a1')) == 1": 16777215,
"self.listMap.exists(m, m.k == 'a1' && m.v == 'b1')": 10485753,
"self.listMap.map(m, m.v).exists(v, v == 'b1')": uint64(18446744073709551615),
"self.listMap.map(m, m.v).exists(v, v == 'b1')": uint64(19922939),
// test comprehensions where the field used in predicates is unset on all but one of the elements:
// - with has checks:
@ -1531,7 +1546,7 @@ func TestCelEstimatedCostStability(t *testing.T) {
"self.listMap.filter(m, has(m.v2) && m.v2 == 'z').size() == 1": 17825790,
// undocumented overload of map that takes a filter argument. This is the same as .filter().map()
"self.listMap.map(m, has(m.v2) && m.v2 == 'z', m.v2).size() == 1": 18874365,
"self.listMap.filter(m, has(m.v2) && m.v2 == 'z').map(m, m.v2).size() == 1": uint64(18446744073709551615),
"self.listMap.filter(m, has(m.v2) && m.v2 == 'z').map(m, m.v2).size() == 1": uint64(32505851),
// - without has checks:
// all() and exists() macros ignore errors from predicates so long as the condition holds for at least one element
@ -1553,7 +1568,7 @@ func TestCelEstimatedCostStability(t *testing.T) {
"!self.array.exists_one(e, e == 2)": 4718594,
"self.array.all(e, e < 100)": 7864318,
"size(self.array.filter(e, e%2 == 0)) == 3": 25165823,
"self.array.map(e, e * 20).filter(e, e > 50).exists(e, e == 60)": uint64(18446744073709551615),
"self.array.map(e, e * 20).filter(e, e > 50).exists(e, e == 60)": uint64(53477367),
"size(self.array) == 8": 4,
},
},
@ -1571,7 +1586,7 @@ func TestCelEstimatedCostStability(t *testing.T) {
"!self.set.exists_one(e, e > 3)": 6291457,
"self.set.all(e, e < 10)": 7864318,
"size(self.set.filter(e, e%2 == 0)) == 2": 25165823,
"self.set.map(e, e * 20).filter(e, e > 50).exists_one(e, e == 60)": uint64(18446744073709551615),
"self.set.map(e, e * 20).filter(e, e > 50).exists_one(e, e == 60)": uint64(50331642),
"size(self.set) == 5": 4,
},
},
@ -1901,38 +1916,65 @@ func TestCelEstimatedCostStability(t *testing.T) {
"obj": objectType(map[string]schema.Structural{
"field": stringType,
}),
"mapOfMap": mapType(mapTypePtr(&stringType)),
"mapOfMap": withMaxProperties(mapType(ptr.To(
withMaxProperties(mapType(&stringType), ptr.To[int64](10)))), ptr.To[int64](10)),
"mapOfObj": mapType(objectTypePtr(map[string]schema.Structural{
"field2": stringType,
})),
"mapOfListMap": mapType(listMapTypePtr([]string{"k"}, objectTypePtr(map[string]schema.Structural{
"k": stringType,
"v": stringType,
}))),
"mapOfList": mapType(listTypePtr(&stringType)),
"listMapOfObj": listMapType([]string{"k2"}, objectTypePtr(map[string]schema.Structural{
"mapOfListMap": withMaxProperties(mapType(
ptr.To(withMaxItems(listMapType([]string{"k"},
objectTypePtr(map[string]schema.Structural{
"k": stringType,
"v": stringType,
}),
), ptr.To[int64](10))),
), ptr.To[int64](10)),
"mapOfList": withMaxProperties(mapType(
ptr.To(withMaxItems(listType(&stringType), ptr.To[int64](10))),
), ptr.To[int64](10)),
"listMapOfObj": withMaxItems(listMapType([]string{"k2"}, objectTypePtr(map[string]schema.Structural{
"k2": stringType,
"v2": stringType,
})),
"listOfMap": listType(mapTypePtr(&stringType)),
})), ptr.To[int64](10)),
"listOfMap": withMaxItems(listType(
ptr.To(withMaxProperties(mapType(&stringType), ptr.To[int64](10))),
), ptr.To[int64](10)),
"listOfObj": listType(objectTypePtr(map[string]schema.Structural{
"field3": stringType,
})),
"listOfListMap": listType(listMapTypePtr([]string{"k3"}, objectTypePtr(map[string]schema.Structural{
"k3": stringType,
"v3": stringType,
}))),
"listOfListMap": withMaxItems(listType(
ptr.To(withMaxItems(listMapType([]string{"k"},
objectTypePtr(map[string]schema.Structural{
"k3": stringType,
"v3": stringType,
}),
), ptr.To[int64](10))),
), ptr.To[int64](10)),
}),
expectCost: map[string]uint64{
"self.obj.field == 'a'": 4,
"self.mapOfMap['x']['y'] == 'b'": 5,
"self.mapOfObj['k'].field2 == 'c'": 5,
"self.mapOfListMap['o'].exists(e, e.k == '1' && e.v == 'd')": 10485754,
"self.mapOfListMap['o'].exists(e, e.k == '1' && e.v == 'd')": 104,
"self.mapOfList['l'][0] == 'e'": 5,
"self.listMapOfObj.exists(e, e.k2 == '2' && e.v2 == 'f')": 10485753,
"self.listMapOfObj.exists(e, e.k2 == '2' && e.v2 == 'f')": 103,
"self.listOfMap[0]['z'] == 'g'": 5,
"self.listOfObj[0].field3 == 'h'": 5,
"self.listOfListMap[0].exists(e, e.k3 == '3' && e.v3 == 'i')": 10485754,
"self.listOfListMap[0].exists(e, e.k3 == '3' && e.v3 == 'i')": 104,
// chained comprehensions
"self.mapOfMap.map(k, k).map(k, k).size() == 1": 286,
"self.mapOfListMap.map(k, k).map(k, k).size() == 1": 286,
"self.mapOfList.map(k, k).map(k, k).size() == 1": 286,
"self.listOfMap.map(e, e).map(e, e).size() == 1": 286,
"self.listOfListMap.map(e, e).map(e, e).size() == 1": 286,
// nested comprehensions
"self.mapOfMap.map(k, self.mapOfMap[k].map(m, m)).size() == 1": 1585,
"self.mapOfListMap.map(k, self.mapOfListMap[k].map(m, m)).size() == 1": 1585,
"self.mapOfList.map(k, self.mapOfList[k].map(l, l)).size() == 1": 1585,
"self.listOfMap.map(e, e.map(m, m)).size() == 1": 1555,
"self.listOfListMap.map(e, e.map(e, e)).size() == 1": 1555,
},
},
{name: "optionals",

View File

@ -4569,6 +4569,14 @@ func withMaxItems(s schema.Structural, maxItems *int64) schema.Structural {
return s
}
func withMaxProperties(s schema.Structural, maxProperties *int64) schema.Structural {
if s.ValueValidation == nil {
s.ValueValidation = &schema.ValueValidation{}
}
s.ValueValidation.MaxProperties = maxProperties
return s
}
func withDefault(dflt interface{}, s schema.Structural) schema.Structural {
s.Generic.Default = schema.JSON{Object: dflt}
return s