Skip to content

Commit 5580cda

Browse files
committed
Add testing for valid zero value utils
1 parent efca1f9 commit 5580cda

File tree

8 files changed

+363
-3
lines changed

8 files changed

+363
-3
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package b
2+
3+
type ZeroValueTestArrays struct {
4+
Array []string // want "zero value is valid" "validation is not complete"
5+
6+
ArrayPtr []*string // want "zero value is valid" "validation is not complete"
7+
8+
// +kubebuilder:validation:MinItems=1
9+
ArrayWithPositiveMinItems []string // want "zero value is not valid" "validation is complete"
10+
11+
// +kubebuilder:validation:MinItems=0
12+
ArrayWithZeroMinItems []string // want "zero value is valid" "validation is complete"
13+
14+
ByteArray []byte // want "zero value is valid" "validation is not complete"
15+
16+
// +kubebuilder:validation:MinLength=1
17+
ByteArrayWithMinLength []byte // want "zero value is not valid" "validation is complete"
18+
19+
// +kubebuilder:validation:MinLength=0
20+
ByteArrayWithMinLength0 []byte // want "zero value is valid" "validation is complete"
21+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package b
2+
3+
type ZeroValueTestBools struct {
4+
Bool bool // want "zero value is valid" "validation is complete"
5+
6+
BoolPtr *bool // want "zero value is valid" "validation is complete"
7+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package b
2+
3+
type ZeroValueTestMaps struct {
4+
Map map[string]string // want "zero value is valid" "validation is not complete"
5+
6+
MapPtr *map[string]string // want "zero value is valid" "validation is not complete"
7+
8+
// +kubebuilder:validation:MinProperties=1
9+
MapWithPositiveMinProperties map[string]string // want "zero value is not valid" "validation is complete"
10+
11+
// +kubebuilder:validation:MinProperties=0
12+
MapWithZeroMinProperties map[string]string // want "zero value is valid" "validation is complete"
13+
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package b
2+
3+
type ZeroValueTestNumbers struct {
4+
Int int // want "zero value is valid" "validation is not complete"
5+
6+
// +kubebuilder:validation:Minimum=1
7+
IntWithPositiveMinimum int // want "zero value is not valid" "validation is complete"
8+
9+
// +kubebuilder:validation:Minimum=0
10+
IntWithZeroMinimum int // want "zero value is valid" "validation is complete"
11+
12+
// +kubebuilder:validation:Minimum=-1
13+
IntWithNegativeMinimum int // want "zero value is valid" "validation is not complete"
14+
15+
// +kubebuilder:validation:Maximum=1
16+
IntWithPositiveMaximum int // want "zero value is valid" "validation is not complete"
17+
18+
// +kubebuilder:validation:Maximum=0
19+
IntWithZeroMaximum int // want "zero value is valid" "validation is complete"
20+
21+
// +kubebuilder:validation:Maximum=-1
22+
IntWithNegativeMaximum int // want "zero value is not valid" "validation is complete"
23+
24+
// +kubebuilder:validation:Minimum=-1
25+
// +kubebuilder:validation:Maximum=1
26+
IntWithRangeIncludingZero int // want "zero value is valid" "validation is complete"
27+
28+
IntPtr *int // want "zero value is valid" "validation is not complete"
29+
30+
// +kubebuilder:validation:Minimum=1
31+
IntPtrWithPositiveMinimum *int // want "zero value is not valid" "validation is complete"
32+
33+
// +kubebuilder:validation:Minimum=0
34+
IntPtrWithZeroMinimum *int // want "zero value is valid" "validation is complete"
35+
36+
// +kubebuilder:validation:Minimum=-1
37+
IntPtrWithNegativeMinimum *int // want "zero value is valid" "validation is not complete"
38+
39+
// +kubebuilder:validation:Maximum=1
40+
IntPtrWithPositiveMaximum *int // want "zero value is valid" "validation is not complete"
41+
42+
// +kubebuilder:validation:Maximum=0
43+
IntPtrWithZeroMaximum *int // want "zero value is valid" "validation is complete"
44+
45+
// +kubebuilder:validation:Maximum=-1
46+
IntPtrWithNegativeMaximum *int // want "zero value is not valid" "validation is complete"
47+
48+
// +kubebuilder:validation:Minimum=-1
49+
// +kubebuilder:validation:Maximum=1
50+
IntPtrWithRangeIncludingZero *int // want "zero value is valid" "validation is complete"
51+
52+
Int32 int32 // want "zero value is valid" "validation is not complete"
53+
54+
// +kubebuilder:validation:Minimum=1
55+
Int32WithPositiveMinimum int32 // want "zero value is not valid" "validation is complete"
56+
57+
// +kubebuilder:validation:Minimum=0
58+
Int32WithZeroMinimum int32 // want "zero value is valid" "validation is complete"
59+
60+
// +kubebuilder:validation:Minimum=-1
61+
Int32WithNegativeMinimum int32 // want "zero value is valid" "validation is not complete"
62+
63+
// +kubebuilder:validation:Maximum=1
64+
Int32WithPositiveMaximum int32 // want "zero value is valid" "validation is not complete"
65+
66+
// +kubebuilder:validation:Maximum=0
67+
Int32WithZeroMaximum int32 // want "zero value is valid" "validation is complete"
68+
69+
// +kubebuilder:validation:Maximum=-1
70+
Int32WithNegativeMaximum int32 // want "zero value is not valid" "validation is complete"
71+
72+
// +kubebuilder:validation:Minimum=-1
73+
// +kubebuilder:validation:Maximum=1
74+
Int32WithRangeIncludingZero int32 // want "zero value is valid" "validation is complete"
75+
76+
Int64 int64 // want "zero value is valid" "validation is not complete"
77+
78+
// +kubebuilder:validation:Minimum=1
79+
Int64WithPositiveMinimum int64 // want "zero value is not valid" "validation is complete"
80+
81+
// +kubebuilder:validation:Minimum=0
82+
Int64WithZeroMinimum int64 // want "zero value is valid" "validation is complete"
83+
84+
// +kubebuilder:validation:Minimum=-1
85+
Int64WithNegativeMinimum int64 // want "zero value is valid" "validation is not complete"
86+
87+
// +kubebuilder:validation:Maximum=1
88+
Int64WithPositiveMaximum int64 // want "zero value is valid" "validation is not complete"
89+
90+
// +kubebuilder:validation:Maximum=0
91+
Int64WithZeroMaximum int64 // want "zero value is valid" "validation is complete"
92+
93+
// +kubebuilder:validation:Maximum=-1
94+
Int64WithNegativeMaximum int64 // want "zero value is not valid" "validation is complete"
95+
96+
// +kubebuilder:validation:Minimum=-1
97+
// +kubebuilder:validation:Maximum=1
98+
Int64WithRangeIncludingZero int64 // want "zero value is valid" "validation is complete"
99+
100+
Float32 float32 // want "zero value is valid" "validation is not complete"
101+
102+
// +kubebuilder:validation:Minimum=1
103+
Float32WithPositiveMinimum float32 // want "zero value is not valid" "validation is complete"
104+
105+
// +kubebuilder:validation:Minimum=0
106+
Float32WithZeroMinimum float32 // want "zero value is valid" "validation is complete"
107+
108+
// +kubebuilder:validation:Minimum=-1
109+
Float32WithNegativeMinimum float32 // want "zero value is valid" "validation is not complete"
110+
111+
// +kubebuilder:validation:Maximum=1
112+
Float32WithPositiveMaximum float32 // want "zero value is valid" "validation is not complete"
113+
114+
// +kubebuilder:validation:Maximum=0
115+
Float32WithZeroMaximum float32 // want "zero value is valid" "validation is complete"
116+
117+
// +kubebuilder:validation:Maximum=-1
118+
Float32WithNegativeMaximum float32 // want "zero value is not valid" "validation is complete"
119+
120+
// +kubebuilder:validation:Minimum=-1
121+
// +kubebuilder:validation:Maximum=1
122+
Float32WithRangeIncludingZero float32 // want "zero value is valid" "validation is complete"
123+
124+
Float64 float64 // want "zero value is valid" "validation is not complete"
125+
126+
// +kubebuilder:validation:Minimum=1
127+
Float64WithPositiveMinimum float64 // want "zero value is not valid" "validation is complete"
128+
129+
// +kubebuilder:validation:Minimum=0
130+
Float64WithZeroMinimum float64 // want "zero value is valid" "validation is complete"
131+
132+
// +kubebuilder:validation:Minimum=-1
133+
Float64WithNegativeMinimum float64 // want "zero value is valid" "validation is not complete"
134+
135+
// +kubebuilder:validation:Maximum=1
136+
Float64WithPositiveMaximum float64 // want "zero value is valid" "validation is not complete"
137+
138+
// +kubebuilder:validation:Maximum=0
139+
Float64WithZeroMaximum float64 // want "zero value is valid" "validation is complete"
140+
141+
// +kubebuilder:validation:Maximum=-1
142+
Float64WithNegativeMaximum float64 // want "zero value is not valid" "validation is complete"
143+
144+
// +kubebuilder:validation:Minimum=-1
145+
// +kubebuilder:validation:Maximum=1
146+
Float64WithRangeIncludingZero float64 // want "zero value is valid" "validation is complete"
147+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package b
2+
3+
type ZeroValueTestStrings struct {
4+
String string // want "zero value is valid" "validation is not complete"
5+
6+
StringPtr *string // want "zero value is valid" "validation is not complete"
7+
8+
// +kubebuilder:validation:MinLength=1
9+
StringWithMinLength string // want "zero value is not valid" "validation is complete"
10+
11+
// +kubebuilder:validation:MinLength=1
12+
StringPtrWithMinLength *string // want "zero value is not valid" "validation is complete"
13+
14+
// +kubebuilder:validation:MinLength=0
15+
StringWithMinLength0 string // want "zero value is valid" "validation is complete"
16+
17+
// +kubebuilder:validation:Enum=a;b;c
18+
EnumString string // want "zero value is not valid" "validation is complete"
19+
20+
// +kubebuilder:validation:Enum=a;b;c
21+
EnumStringPtr *string // want "zero value is not valid" "validation is complete"
22+
23+
// +kubebuilder:validation:Enum=a;b;c;""
24+
EnumValidEmptytring string // want "zero value is valid" "validation is complete"
25+
26+
// +kubebuilder:validation:Enum=a;b;c;""
27+
EnumValidEmptyStringPtr *string // want "zero value is valid" "validation is complete"
28+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package b
2+
3+
type ZeroValueTestStructs struct {
4+
StructWithAllOptionalFields StructWithAllOptionalFields `json:"structWithAllOptionalFields,omitempty"` // want "zero value is valid" "validation is not complete"
5+
6+
StructWithMinProperties StructWithMinProperties `json:"structWithMinProperties,omitempty"` // want "zero value is not valid" "validation is complete"
7+
8+
StructWithNonOmittedFieldsAndMinProperties StructWithNonOmittedFieldsAndMinProperties `json:"structWithOneNonOmittedFieldAndMinProperties,omitempty"` // want "zero value is valid" "validation is complete"
9+
10+
StructWithOneNonOmittedFieldAndMinProperties StructWithOneNonOmittedFieldAndMinProperties `json:"structWithOneNonOmittedFieldAndMinPropertiesAndOmitEmpty,omitempty"` // want "zero value is not valid" "validation is complete"
11+
}
12+
13+
type StructWithAllOptionalFields struct {
14+
// +optional
15+
String string `json:"string,omitempty"` // want "zero value is valid" "validation is not complete"
16+
17+
// +optional
18+
StringPtr *string `json:"stringPtr,omitempty"` // want "zero value is valid" "validation is not complete"
19+
20+
// +optional
21+
Int int `json:"int,omitempty"` // want "zero value is valid" "validation is not complete"
22+
23+
// +optional
24+
IntPtr *int `json:"intPtr,omitempty"` // want "zero value is valid" "validation is not complete"
25+
}
26+
27+
// +kubebuilder:validation:MinProperties=1
28+
type StructWithMinProperties struct {
29+
// +kubebuilder:validation:MinProperties=1
30+
// +optional
31+
Map map[string]string `json:"map,omitempty"` // want "zero value is not valid" "validation is complete"
32+
}
33+
34+
type StructWithNonOmittedFields struct {
35+
// +required
36+
String string `json:"string"` // want "zero value is valid" "validation is not complete"
37+
38+
// +required
39+
Int int32 `json:"int"` // want "zero value is valid" "validation is not complete"
40+
}
41+
42+
// Struct with non-omitted fields and minProperties marker.
43+
// Because there is no omitempty, and the zero values are valid, the zero value here is `{"string:"", "int":0}`.
44+
// This means the MinProperties marker is satisfied even when the object is the zero value.
45+
// +kubebuilder:validation:MinProperties=2
46+
type StructWithNonOmittedFieldsAndMinProperties struct {
47+
// +required
48+
String string `json:"string"` // want "zero value is valid" "validation is not complete"
49+
50+
// +required
51+
Int int32 `json:"int"` // want "zero value is valid" "validation is not complete"
52+
}
53+
54+
// Struct with one non-omitted field, and one omitted field and minProperties marker.
55+
// The zero value of the struct is `{"string:""}` which is not valid because it does not satisfy the MinProperties marker.
56+
// +kubebuilder:validation:MinProperties=2
57+
type StructWithOneNonOmittedFieldAndMinProperties struct {
58+
// +required
59+
String string `json:"string"` // want "zero value is valid" "validation is not complete"
60+
61+
// +optional
62+
Int int32 `json:"int,omitempty"` // want "zero value is valid" "validation is not complete"
63+
}

pkg/analysis/utils/zero_value.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,6 @@ func stringFieldIsEnum(fieldMarkers markershelper.MarkerSet) bool {
216216
func enumFieldAllowsEmpty(fieldMarkers markershelper.MarkerSet) bool {
217217
// Check if the field has a kubebuilder enum marker with an empty value.
218218
enumMarker := fieldMarkers.Get(markers.KubebuilderEnumMarker)
219-
if len(enumMarker) == 0 {
220-
return false
221-
}
222219

223220
for _, marker := range enumMarker {
224221
return slices.Contains(strings.Split(marker.Expressions[""], ";"), "\"\"")

pkg/analysis/utils/zero_value_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package utils_test
17+
18+
import (
19+
"errors"
20+
"go/ast"
21+
"testing"
22+
23+
"golang.org/x/tools/go/analysis"
24+
"golang.org/x/tools/go/analysis/analysistest"
25+
"golang.org/x/tools/go/analysis/passes/inspect"
26+
"golang.org/x/tools/go/ast/inspector"
27+
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/extractjsontags"
28+
markershelper "sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/markers"
29+
"sigs.k8s.io/kube-api-linter/pkg/analysis/utils"
30+
)
31+
32+
var (
33+
errCouldNotGetMarkers = errors.New("could not get markers")
34+
)
35+
36+
func TestZeroValue(t *testing.T) {
37+
testdata := analysistest.TestData()
38+
analysistest.Run(t, testdata, testZeroValueAnalyzer(), "b")
39+
}
40+
41+
func testZeroValueAnalyzer() *analysis.Analyzer {
42+
return &analysis.Analyzer{
43+
Name: "test",
44+
Doc: "test",
45+
Requires: []*analysis.Analyzer{inspect.Analyzer, markershelper.Analyzer, extractjsontags.Analyzer},
46+
Run: func(pass *analysis.Pass) (any, error) {
47+
inspect, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
48+
if !ok {
49+
return nil, errCouldNotGetInspector
50+
}
51+
52+
markers, ok := pass.ResultOf[markershelper.Analyzer].(markershelper.Markers)
53+
if !ok {
54+
return nil, errCouldNotGetMarkers
55+
}
56+
57+
// Filter to structs so that we can iterate over fields in a struct.
58+
nodeFilter := []ast.Node{
59+
(*ast.Field)(nil),
60+
}
61+
62+
inspect.Preorder(nodeFilter, func(n ast.Node) {
63+
field, ok := n.(*ast.Field)
64+
if !ok {
65+
return
66+
}
67+
68+
zeroValueValid, complete := utils.IsZeroValueValid(pass, field, field.Type, markers)
69+
if !zeroValueValid {
70+
pass.Reportf(field.Pos(), "zero value is not valid")
71+
} else {
72+
pass.Reportf(field.Pos(), "zero value is valid")
73+
}
74+
if !complete {
75+
pass.Reportf(field.Pos(), "validation is not complete")
76+
} else {
77+
pass.Reportf(field.Pos(), "validation is complete")
78+
}
79+
})
80+
81+
return nil, nil
82+
},
83+
}
84+
}

0 commit comments

Comments
 (0)