mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-01 07:47:56 +00:00
Merge pull request #130543 from thockin/error_matcher_and_origin
Fix up ErrorMatcher from feedback
This commit is contained in:
commit
4696667025
@ -20894,8 +20894,8 @@ func TestValidateEndpointsCreate(t *testing.T) {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
errs := ValidateEndpointsCreate(&v.endpoints)
|
||||
// TODO: set .RequireOriginWhenInvalid() once metadata is done
|
||||
matcher := fldtest.Match().ByType().ByField().ByOrigin()
|
||||
fldtest.MatchErrors(t, v.expectedErrs, errs, matcher)
|
||||
matcher := fldtest.ErrorMatcher{}.ByType().ByField().ByOrigin()
|
||||
matcher.Test(t, v.expectedErrs, errs)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -22891,8 +22891,8 @@ func TestValidateTopologySpreadConstraints(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
errs := validateTopologySpreadConstraints(tc.constraints, fieldPath, tc.opts)
|
||||
matcher := fldtest.Match().ByType().ByField().ByOrigin().RequireOriginWhenInvalid()
|
||||
fldtest.MatchErrors(t, tc.wantFieldErrors, errs, matcher)
|
||||
matcher := fldtest.ErrorMatcher{}.ByType().ByField().ByOrigin().RequireOriginWhenInvalid()
|
||||
matcher.Test(t, tc.wantFieldErrors, errs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1089,7 +1089,8 @@ func TestRegisterValidate(t *testing.T) {
|
||||
} else {
|
||||
results = s.ValidateUpdate(ctx, tc.options, tc.object, tc.oldObject, tc.subresource...)
|
||||
}
|
||||
fieldtesting.MatchErrors(t, tc.expected, results, fieldtesting.Match().ByType().ByField().ByOrigin())
|
||||
matcher := fieldtesting.ErrorMatcher{}.ByType().ByField().ByOrigin()
|
||||
matcher.Test(t, tc.expected, results)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -26,50 +26,16 @@ import (
|
||||
field "k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// MatchErrors compares two ErrorLists with the specified matcher, and fails
|
||||
// the test if they don't match. If a given "want" error matches multiple "got"
|
||||
// errors, they will all be consumed. This might be OK (e.g. if there are
|
||||
// multiple errors on the same field from the same origin) or it might be an
|
||||
// insufficiently specific matcher, so these will be logged.
|
||||
func MatchErrors(t *testing.T, want, got field.ErrorList, matcher *Matcher) {
|
||||
t.Helper()
|
||||
|
||||
remaining := got
|
||||
for _, w := range want {
|
||||
tmp := make(field.ErrorList, 0, len(remaining))
|
||||
n := 0
|
||||
for _, g := range remaining {
|
||||
if matcher.Matches(w, g) {
|
||||
n++
|
||||
} else {
|
||||
tmp = append(tmp, g)
|
||||
}
|
||||
}
|
||||
if n == 0 {
|
||||
t.Errorf("expected an error matching:\n%s", matcher.Render(w))
|
||||
} else if n > 1 {
|
||||
// This is not necessarily and error, but it's worth logging in
|
||||
// case it's not what the test author intended.
|
||||
t.Logf("multiple errors matched:\n%s", matcher.Render(w))
|
||||
}
|
||||
remaining = tmp
|
||||
}
|
||||
if len(remaining) > 0 {
|
||||
for _, e := range remaining {
|
||||
t.Errorf("unmatched error:\n%s", Match().Exactly().Render(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Match returns a new Matcher.
|
||||
func Match() *Matcher {
|
||||
return &Matcher{}
|
||||
}
|
||||
|
||||
// Matcher is a helper for comparing field.Error objects.
|
||||
type Matcher struct {
|
||||
matchType bool
|
||||
matchField bool
|
||||
// ErrorMatcher is a helper for comparing field.Error objects.
|
||||
type ErrorMatcher struct {
|
||||
// TODO(thockin): consider whether type is ever NOT required, maybe just
|
||||
// assume it.
|
||||
matchType bool
|
||||
// TODO(thockin): consider whether field could be assumed - if the
|
||||
// "want" error has a nil field, don't match on field.
|
||||
matchField bool
|
||||
// TODO(thockin): consider whether value could be assumed - if the
|
||||
// "want" error has a nil value, don't match on field.
|
||||
matchValue bool
|
||||
matchOrigin bool
|
||||
matchDetail func(want, got string) bool
|
||||
@ -78,7 +44,7 @@ type Matcher struct {
|
||||
|
||||
// Matches returns true if the two field.Error objects match according to the
|
||||
// configured criteria.
|
||||
func (m *Matcher) Matches(want, got *field.Error) bool {
|
||||
func (m ErrorMatcher) Matches(want, got *field.Error) bool {
|
||||
if m.matchType && want.Type != got.Type {
|
||||
return false
|
||||
}
|
||||
@ -105,8 +71,8 @@ func (m *Matcher) Matches(want, got *field.Error) bool {
|
||||
}
|
||||
|
||||
// Render returns a string representation of the specified Error object,
|
||||
// according to the criteria configured in the Matcher.
|
||||
func (m *Matcher) Render(e *field.Error) string {
|
||||
// according to the criteria configured in the ErrorMatcher.
|
||||
func (m ErrorMatcher) Render(e *field.Error) string {
|
||||
buf := strings.Builder{}
|
||||
|
||||
comma := func() {
|
||||
@ -138,66 +104,104 @@ func (m *Matcher) Render(e *field.Error) string {
|
||||
return "{" + buf.String() + "}"
|
||||
}
|
||||
|
||||
// Exactly configures the matcher to match all fields exactly.
|
||||
func (m *Matcher) Exactly() *Matcher {
|
||||
// Exactly returns a derived ErrorMatcher which matches all fields exactly.
|
||||
func (m ErrorMatcher) Exactly() ErrorMatcher {
|
||||
return m.ByType().ByField().ByValue().ByOrigin().ByDetailExact()
|
||||
}
|
||||
|
||||
// Exactly configures the matcher to match errors by type.
|
||||
func (m *Matcher) ByType() *Matcher {
|
||||
// ByType returns a derived ErrorMatcher which also matches by type.
|
||||
func (m ErrorMatcher) ByType() ErrorMatcher {
|
||||
m.matchType = true
|
||||
return m
|
||||
}
|
||||
|
||||
// ByField configures the matcher to match errors by field path.
|
||||
func (m *Matcher) ByField() *Matcher {
|
||||
// ByField returns a derived ErrorMatcher which also matches by field path.
|
||||
func (m ErrorMatcher) ByField() ErrorMatcher {
|
||||
m.matchField = true
|
||||
return m
|
||||
}
|
||||
|
||||
// ByValue configures the matcher to match errors by the errant value.
|
||||
func (m *Matcher) ByValue() *Matcher {
|
||||
// ByValue returns a derived ErrorMatcher which also matches by the errant
|
||||
// value.
|
||||
func (m ErrorMatcher) ByValue() ErrorMatcher {
|
||||
m.matchValue = true
|
||||
return m
|
||||
}
|
||||
|
||||
// ByOrigin configures the matcher to match errors by the origin.
|
||||
func (m *Matcher) ByOrigin() *Matcher {
|
||||
// ByOrigin returns a derived ErrorMatcher which also matches by the origin.
|
||||
func (m ErrorMatcher) ByOrigin() ErrorMatcher {
|
||||
m.matchOrigin = true
|
||||
return m
|
||||
}
|
||||
|
||||
// RequireOriginWhenInvalid configures the matcher to require the Origin field
|
||||
// to be set when the Type is Invalid and the matcher is matching by Origin.
|
||||
func (m *Matcher) RequireOriginWhenInvalid() *Matcher {
|
||||
// RequireOriginWhenInvalid returns a derived ErrorMatcher which also requires
|
||||
// the Origin field to be set when the Type is Invalid and the matcher is
|
||||
// matching by Origin.
|
||||
func (m ErrorMatcher) RequireOriginWhenInvalid() ErrorMatcher {
|
||||
m.requireOriginWhenInvalid = true
|
||||
return m
|
||||
}
|
||||
|
||||
// ByDetailExact configures the matcher to match errors by the exact detail
|
||||
// string.
|
||||
func (m *Matcher) ByDetailExact() *Matcher {
|
||||
// ByDetailExact returns a derived ErrorMatcher which also matches errors by
|
||||
// the exact detail string.
|
||||
func (m ErrorMatcher) ByDetailExact() ErrorMatcher {
|
||||
m.matchDetail = func(want, got string) bool {
|
||||
return got == want
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// ByDetailSubstring configures the matcher to match errors by a substring of
|
||||
// the detail string.
|
||||
func (m *Matcher) ByDetailSubstring() *Matcher {
|
||||
// ByDetailSubstring returns a derived ErrorMatcher which also matches errors
|
||||
// by a substring of the detail string.
|
||||
func (m ErrorMatcher) ByDetailSubstring() ErrorMatcher {
|
||||
m.matchDetail = func(want, got string) bool {
|
||||
return strings.Contains(got, want)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// ByDetailRegexp configures the matcher to match errors by a regular
|
||||
// expression of the detail string, where the "want" string is assumed to be a
|
||||
// valid regular expression.
|
||||
func (m *Matcher) ByDetailRegexp() *Matcher {
|
||||
// ByDetailRegexp returns a derived ErrorMatcher which also matches errors by a
|
||||
// regular expression of the detail string, where the "want" string is assumed
|
||||
// to be a valid regular expression.
|
||||
func (m ErrorMatcher) ByDetailRegexp() ErrorMatcher {
|
||||
m.matchDetail = func(want, got string) bool {
|
||||
return regexp.MustCompile(want).MatchString(got)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Test compares two ErrorLists by the criteria configured in this matcher, and
|
||||
// fails the test if they don't match. If a given "want" error matches multiple
|
||||
// "got" errors, they will all be consumed. This might be OK (e.g. if there are
|
||||
// multiple errors on the same field from the same origin) or it might be an
|
||||
// insufficiently specific matcher, so these will be logged.
|
||||
func (m ErrorMatcher) Test(tb testing.TB, want, got field.ErrorList) {
|
||||
tb.Helper()
|
||||
|
||||
remaining := got
|
||||
for _, w := range want {
|
||||
tmp := make(field.ErrorList, 0, len(remaining))
|
||||
n := 0
|
||||
for _, g := range remaining {
|
||||
if m.Matches(w, g) {
|
||||
n++
|
||||
} else {
|
||||
tmp = append(tmp, g)
|
||||
}
|
||||
}
|
||||
if n == 0 {
|
||||
tb.Errorf("expected an error matching:\n%s", m.Render(w))
|
||||
} else if n > 1 {
|
||||
// This is not necessarily and error, but it's worth logging in
|
||||
// case it's not what the test author intended.
|
||||
tb.Logf("multiple errors matched:\n%s", m.Render(w))
|
||||
}
|
||||
remaining = tmp
|
||||
}
|
||||
if len(remaining) > 0 {
|
||||
for _, e := range remaining {
|
||||
exactly := m.Exactly() // makes a copy
|
||||
tb.Errorf("unmatched error:\n%s", exactly.Render(e))
|
||||
}
|
||||
}
|
||||
}
|
@ -154,7 +154,8 @@ func TestValidateDeclaratively(t *testing.T) {
|
||||
} else {
|
||||
results = ValidateUpdateDeclaratively(ctx, tc.options, scheme, tc.object, tc.oldObject)
|
||||
}
|
||||
fieldtesting.MatchErrors(t, tc.expected, results, fieldtesting.Match().ByType().ByField().ByOrigin())
|
||||
matcher := fieldtesting.ErrorMatcher{}.ByType().ByField().ByOrigin()
|
||||
matcher.Test(t, tc.expected, results)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user