Skip to content

Commit 4696667

Browse files
authored
Merge pull request kubernetes#130543 from thockin/error_matcher_and_origin
Fix up ErrorMatcher from feedback
2 parents d6c615d + 0a9f492 commit 4696667

File tree

4 files changed

+82
-76
lines changed

4 files changed

+82
-76
lines changed

pkg/apis/core/validation/validation_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20894,8 +20894,8 @@ func TestValidateEndpointsCreate(t *testing.T) {
2089420894
t.Run(k, func(t *testing.T) {
2089520895
errs := ValidateEndpointsCreate(&v.endpoints)
2089620896
// TODO: set .RequireOriginWhenInvalid() once metadata is done
20897-
matcher := fldtest.Match().ByType().ByField().ByOrigin()
20898-
fldtest.MatchErrors(t, v.expectedErrs, errs, matcher)
20897+
matcher := fldtest.ErrorMatcher{}.ByType().ByField().ByOrigin()
20898+
matcher.Test(t, v.expectedErrs, errs)
2089920899
})
2090020900
}
2090120901
}
@@ -22891,8 +22891,8 @@ func TestValidateTopologySpreadConstraints(t *testing.T) {
2289122891
for _, tc := range testCases {
2289222892
t.Run(tc.name, func(t *testing.T) {
2289322893
errs := validateTopologySpreadConstraints(tc.constraints, fieldPath, tc.opts)
22894-
matcher := fldtest.Match().ByType().ByField().ByOrigin().RequireOriginWhenInvalid()
22895-
fldtest.MatchErrors(t, tc.wantFieldErrors, errs, matcher)
22894+
matcher := fldtest.ErrorMatcher{}.ByType().ByField().ByOrigin().RequireOriginWhenInvalid()
22895+
matcher.Test(t, tc.wantFieldErrors, errs)
2289622896
})
2289722897
}
2289822898
}

staging/src/k8s.io/apimachinery/pkg/runtime/scheme_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1089,7 +1089,8 @@ func TestRegisterValidate(t *testing.T) {
10891089
} else {
10901090
results = s.ValidateUpdate(ctx, tc.options, tc.object, tc.oldObject, tc.subresource...)
10911091
}
1092-
fieldtesting.MatchErrors(t, tc.expected, results, fieldtesting.Match().ByType().ByField().ByOrigin())
1092+
matcher := fieldtesting.ErrorMatcher{}.ByType().ByField().ByOrigin()
1093+
matcher.Test(t, tc.expected, results)
10931094
})
10941095
}
10951096
}

staging/src/k8s.io/apimachinery/pkg/util/validation/field/testing/match.go renamed to staging/src/k8s.io/apimachinery/pkg/util/validation/field/testing/error_matcher.go

Lines changed: 74 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -26,50 +26,16 @@ import (
2626
field "k8s.io/apimachinery/pkg/util/validation/field"
2727
)
2828

29-
// MatchErrors compares two ErrorLists with the specified matcher, and fails
30-
// the test if they don't match. If a given "want" error matches multiple "got"
31-
// errors, they will all be consumed. This might be OK (e.g. if there are
32-
// multiple errors on the same field from the same origin) or it might be an
33-
// insufficiently specific matcher, so these will be logged.
34-
func MatchErrors(t *testing.T, want, got field.ErrorList, matcher *Matcher) {
35-
t.Helper()
36-
37-
remaining := got
38-
for _, w := range want {
39-
tmp := make(field.ErrorList, 0, len(remaining))
40-
n := 0
41-
for _, g := range remaining {
42-
if matcher.Matches(w, g) {
43-
n++
44-
} else {
45-
tmp = append(tmp, g)
46-
}
47-
}
48-
if n == 0 {
49-
t.Errorf("expected an error matching:\n%s", matcher.Render(w))
50-
} else if n > 1 {
51-
// This is not necessarily and error, but it's worth logging in
52-
// case it's not what the test author intended.
53-
t.Logf("multiple errors matched:\n%s", matcher.Render(w))
54-
}
55-
remaining = tmp
56-
}
57-
if len(remaining) > 0 {
58-
for _, e := range remaining {
59-
t.Errorf("unmatched error:\n%s", Match().Exactly().Render(e))
60-
}
61-
}
62-
}
63-
64-
// Match returns a new Matcher.
65-
func Match() *Matcher {
66-
return &Matcher{}
67-
}
68-
69-
// Matcher is a helper for comparing field.Error objects.
70-
type Matcher struct {
71-
matchType bool
72-
matchField bool
29+
// ErrorMatcher is a helper for comparing field.Error objects.
30+
type ErrorMatcher struct {
31+
// TODO(thockin): consider whether type is ever NOT required, maybe just
32+
// assume it.
33+
matchType bool
34+
// TODO(thockin): consider whether field could be assumed - if the
35+
// "want" error has a nil field, don't match on field.
36+
matchField bool
37+
// TODO(thockin): consider whether value could be assumed - if the
38+
// "want" error has a nil value, don't match on field.
7339
matchValue bool
7440
matchOrigin bool
7541
matchDetail func(want, got string) bool
@@ -78,7 +44,7 @@ type Matcher struct {
7844

7945
// Matches returns true if the two field.Error objects match according to the
8046
// configured criteria.
81-
func (m *Matcher) Matches(want, got *field.Error) bool {
47+
func (m ErrorMatcher) Matches(want, got *field.Error) bool {
8248
if m.matchType && want.Type != got.Type {
8349
return false
8450
}
@@ -105,8 +71,8 @@ func (m *Matcher) Matches(want, got *field.Error) bool {
10571
}
10672

10773
// Render returns a string representation of the specified Error object,
108-
// according to the criteria configured in the Matcher.
109-
func (m *Matcher) Render(e *field.Error) string {
74+
// according to the criteria configured in the ErrorMatcher.
75+
func (m ErrorMatcher) Render(e *field.Error) string {
11076
buf := strings.Builder{}
11177

11278
comma := func() {
@@ -138,66 +104,104 @@ func (m *Matcher) Render(e *field.Error) string {
138104
return "{" + buf.String() + "}"
139105
}
140106

141-
// Exactly configures the matcher to match all fields exactly.
142-
func (m *Matcher) Exactly() *Matcher {
107+
// Exactly returns a derived ErrorMatcher which matches all fields exactly.
108+
func (m ErrorMatcher) Exactly() ErrorMatcher {
143109
return m.ByType().ByField().ByValue().ByOrigin().ByDetailExact()
144110
}
145111

146-
// Exactly configures the matcher to match errors by type.
147-
func (m *Matcher) ByType() *Matcher {
112+
// ByType returns a derived ErrorMatcher which also matches by type.
113+
func (m ErrorMatcher) ByType() ErrorMatcher {
148114
m.matchType = true
149115
return m
150116
}
151117

152-
// ByField configures the matcher to match errors by field path.
153-
func (m *Matcher) ByField() *Matcher {
118+
// ByField returns a derived ErrorMatcher which also matches by field path.
119+
func (m ErrorMatcher) ByField() ErrorMatcher {
154120
m.matchField = true
155121
return m
156122
}
157123

158-
// ByValue configures the matcher to match errors by the errant value.
159-
func (m *Matcher) ByValue() *Matcher {
124+
// ByValue returns a derived ErrorMatcher which also matches by the errant
125+
// value.
126+
func (m ErrorMatcher) ByValue() ErrorMatcher {
160127
m.matchValue = true
161128
return m
162129
}
163130

164-
// ByOrigin configures the matcher to match errors by the origin.
165-
func (m *Matcher) ByOrigin() *Matcher {
131+
// ByOrigin returns a derived ErrorMatcher which also matches by the origin.
132+
func (m ErrorMatcher) ByOrigin() ErrorMatcher {
166133
m.matchOrigin = true
167134
return m
168135
}
169136

170-
// RequireOriginWhenInvalid configures the matcher to require the Origin field
171-
// to be set when the Type is Invalid and the matcher is matching by Origin.
172-
func (m *Matcher) RequireOriginWhenInvalid() *Matcher {
137+
// RequireOriginWhenInvalid returns a derived ErrorMatcher which also requires
138+
// the Origin field to be set when the Type is Invalid and the matcher is
139+
// matching by Origin.
140+
func (m ErrorMatcher) RequireOriginWhenInvalid() ErrorMatcher {
173141
m.requireOriginWhenInvalid = true
174142
return m
175143
}
176144

177-
// ByDetailExact configures the matcher to match errors by the exact detail
178-
// string.
179-
func (m *Matcher) ByDetailExact() *Matcher {
145+
// ByDetailExact returns a derived ErrorMatcher which also matches errors by
146+
// the exact detail string.
147+
func (m ErrorMatcher) ByDetailExact() ErrorMatcher {
180148
m.matchDetail = func(want, got string) bool {
181149
return got == want
182150
}
183151
return m
184152
}
185153

186-
// ByDetailSubstring configures the matcher to match errors by a substring of
187-
// the detail string.
188-
func (m *Matcher) ByDetailSubstring() *Matcher {
154+
// ByDetailSubstring returns a derived ErrorMatcher which also matches errors
155+
// by a substring of the detail string.
156+
func (m ErrorMatcher) ByDetailSubstring() ErrorMatcher {
189157
m.matchDetail = func(want, got string) bool {
190158
return strings.Contains(got, want)
191159
}
192160
return m
193161
}
194162

195-
// ByDetailRegexp configures the matcher to match errors by a regular
196-
// expression of the detail string, where the "want" string is assumed to be a
197-
// valid regular expression.
198-
func (m *Matcher) ByDetailRegexp() *Matcher {
163+
// ByDetailRegexp returns a derived ErrorMatcher which also matches errors by a
164+
// regular expression of the detail string, where the "want" string is assumed
165+
// to be a valid regular expression.
166+
func (m ErrorMatcher) ByDetailRegexp() ErrorMatcher {
199167
m.matchDetail = func(want, got string) bool {
200168
return regexp.MustCompile(want).MatchString(got)
201169
}
202170
return m
203171
}
172+
173+
// Test compares two ErrorLists by the criteria configured in this matcher, and
174+
// fails the test if they don't match. If a given "want" error matches multiple
175+
// "got" errors, they will all be consumed. This might be OK (e.g. if there are
176+
// multiple errors on the same field from the same origin) or it might be an
177+
// insufficiently specific matcher, so these will be logged.
178+
func (m ErrorMatcher) Test(tb testing.TB, want, got field.ErrorList) {
179+
tb.Helper()
180+
181+
remaining := got
182+
for _, w := range want {
183+
tmp := make(field.ErrorList, 0, len(remaining))
184+
n := 0
185+
for _, g := range remaining {
186+
if m.Matches(w, g) {
187+
n++
188+
} else {
189+
tmp = append(tmp, g)
190+
}
191+
}
192+
if n == 0 {
193+
tb.Errorf("expected an error matching:\n%s", m.Render(w))
194+
} else if n > 1 {
195+
// This is not necessarily and error, but it's worth logging in
196+
// case it's not what the test author intended.
197+
tb.Logf("multiple errors matched:\n%s", m.Render(w))
198+
}
199+
remaining = tmp
200+
}
201+
if len(remaining) > 0 {
202+
for _, e := range remaining {
203+
exactly := m.Exactly() // makes a copy
204+
tb.Errorf("unmatched error:\n%s", exactly.Render(e))
205+
}
206+
}
207+
}

staging/src/k8s.io/apiserver/pkg/registry/rest/validate_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,8 @@ func TestValidateDeclaratively(t *testing.T) {
154154
} else {
155155
results = ValidateUpdateDeclaratively(ctx, tc.options, scheme, tc.object, tc.oldObject)
156156
}
157-
fieldtesting.MatchErrors(t, tc.expected, results, fieldtesting.Match().ByType().ByField().ByOrigin())
157+
matcher := fieldtesting.ErrorMatcher{}.ByType().ByField().ByOrigin()
158+
matcher.Test(t, tc.expected, results)
158159
})
159160
}
160161
}

0 commit comments

Comments
 (0)