Documentation ¶
Overview ¶
Package check provides validation helpers for ygnmi queries.
These helpers wrap the .Lookup() and .Watch()/.Await() methods of Ondatra's ygnmi structs, eliminating a great deal of boilerplate and allowing table-driven testing of paths of different types.
Overview ¶
This package provides a set of functions for creating Validator objects, each of which represents a check to be performed against some gNMI path. A Validator can then be invoked in several different ways to actually perform the check. Typical usage looks like this:
// Create a table of Validators. validators := []check.Validator{ check.Equal(ocpath.Some().Path().State(), someValue), check.Present(ocpath.Some().OtherPath().Config()), check.NotEqual(ocpath.Another().Path().State(), anotherValue), } // Check each one and report any failures. for _, vd:= range validators { t.Run(vd.Path(), func(t *testing.T) { if err := vd.Check(gnmiClient); err != nil { // err will already look like e.g. "some/path/state: got 12, want 0" // so no further formatting is necessary here. t.Error(err) } }) }
Validator functions ¶
The most generic validation function is
check.Validate(query, validationFn func(Value[T]) error)
This Validator validates by running validationFn on the query's value and returning any resulting error.
check also provides a number of shorthands for common cases:
- check.Equal(query, want) checks that the query's value is want.
- check.NotEqual(query, wantNot) checks that the query has any value other than wantNot.
- check.Present(query) checks that the query has any value at all.
- check.NotPresent(query) checks that the query is unset.
- check.EqualOrNil(query, want) checks that the query's value is want OR that the query is unset.
- check.Predicate[T](query PathStruct[T], wantMsg string, predicate func(T) bool) checks that the value at query is present and satisfies the given predicate function; wantMsg is used in the resulting error if it fails.
These helpers all have prewritten validation functions that return sensible errors of the form "<path>: <got>, <want>", such as:
/system/some/path: got no value, want 12 /system/hostname: got "wrongname", want "node1" or nil /some/other/path: got 100, want no value
Validating a Validator ¶
Given a Validator, there are several ways to test its condition:
- vd.Check(client) executes the query immediately, tests the result, and returns any error generated by the validation function.
- vd.Await(ctx, client) will watch the specified path and return nil as soon as the validation passes; if this never happens, it will continue blocking until the context expires or is canceled.
- vd.AwaitUntil(deadline, client) is almost the same as creating a context with the given deadline and calling Await(), except that if the deadline is in the past it will call Check() instead.
- vd.AwaitFor(timeout, client) is AwaitUntil(time.Now().Add(timeout), client)
Accommodating latency ¶
There will often be some small latency between when a configuration variable is set via gNMI and when the corresponding operational state reflects the change. As a result, it's common to want to test several values with the expectation that all of them will be correct within some short window of time. The preferred way to do this is:
deadline := time.Now().Add(time.Second()) for _, vd:= range[]check.Validator { check.Equal(root.Some().Path(), someValue), check.Present(root.Some().OtherPath()), ... check.NotEqual(root.Another().Path(), anotherValue), } { t.Run(vd.Path(), func(t *testing.T) { if err := vd.AwaitUntil(deadline, client); err != nil { t.Error(err) } }) }
The above code expects that every validation will pass within one second of the start of the block. Don't use AwaitFor this, or else on a failing device the test could wait one second *per validator* instead of total.
Note that the above is also preferable to
ctx, _ := context.WithTimeout(context.Background(), time.Second()) for _, vd:= range[]check.Validator { ... } { t.Run(vd.Path(), func(t *testing.T) { if err := vd.Await(ctx, client); err != nil { t.Error(err) } }) }
This is because if the first validator times out, the other validators won't run because Await(ctx, client) aborts if the ctx has already expired, whereas AwaitUntil and AwaitFor will both be equivalent to Check if given a 0 or negative timeout or a deadline in the past.
Error Messages ¶
The error messages generated by failing checks will include the path, the value at that path, and a description of what the validator wanted, e.g.
some/path: got 12, want 19
Index ¶
- func FormatPath(path ygnmi.PathStruct) string
- func FormatRelativePath(base, path ygnmi.PathStruct) string
- func FormatValue[T any](v *ygnmi.Value[T]) string
- type Validator
- func Equal[T any, QT ygnmi.SingletonQuery[T]](query QT, want T) Validator
- func EqualOrNil[T any, QT ygnmi.SingletonQuery[T]](query QT, want T) Validator
- func NotEqual[T any, QT ygnmi.SingletonQuery[T]](query QT, wantNot T) Validator
- func NotPresent[T any, QT ygnmi.SingletonQuery[T]](query QT) Validator
- func Predicate[T any, QT ygnmi.SingletonQuery[T]](query QT, wantMsg string, predicate func(T) bool) Validator
- func Present[T any, QT ygnmi.SingletonQuery[T]](query QT) Validator
- func UnorderedEqual[T any, QT ygnmi.SingletonQuery[[]T]](query QT, want []T, less func(a, b T) bool) Validator
- func Validate[T any, QT ygnmi.SingletonQuery[T]](query QT, validationFn func(*ygnmi.Value[T]) error) Validator
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func FormatPath ¶
func FormatPath(path ygnmi.PathStruct) string
FormatPath formats a PathStruct for display. On unresolvable or otherwise broken path structs, it will return a string indicating that this is an unprintable path.
func FormatRelativePath ¶
func FormatRelativePath(base, path ygnmi.PathStruct) string
FormatRelativePath formats a path relative to base.
Types ¶
type Validator ¶
type Validator interface { Check(*ygnmi.Client) error Await(context.Context, *ygnmi.Client) error AwaitFor(time.Duration, *ygnmi.Client) error AwaitUntil(time.Time, *ygnmi.Client) error Path() string RelPath(ygnmi.PathStruct) string }
Validator is an interface representing a validation operation that could have latency. The Check method fetches a value and validates it immediately; the Await* methods watch the query's value waiting for a value that passes validation. Note that AwaitFor and AwaitUntil are equivalent to Check if you pass in a negative duration or deadline in the past.
func Equal ¶
func Equal[T any, QT ygnmi.SingletonQuery[T]](query QT, want T) Validator
Equal expects the query's value to be want.
func EqualOrNil ¶
func EqualOrNil[T any, QT ygnmi.SingletonQuery[T]](query QT, want T) Validator
EqualOrNil expects the query to be unset or have value want.
func NotEqual ¶
func NotEqual[T any, QT ygnmi.SingletonQuery[T]](query QT, wantNot T) Validator
NotEqual expects the query to have a value other than wantNot.
func NotPresent ¶
func NotPresent[T any, QT ygnmi.SingletonQuery[T]](query QT) Validator
NotPresent expects the query to not have a value set.
func Predicate ¶
func Predicate[T any, QT ygnmi.SingletonQuery[T]](query QT, wantMsg string, predicate func(T) bool) Validator
Predicate expects that the query has a value and the given predicate returns true on that value. The wantMsg will be included in any validation failure, e.g. if wantMsg is "want a multiple of 4", the error might read:
"/some/path: got 13, want a multiple of 4".
func Present ¶
func Present[T any, QT ygnmi.SingletonQuery[T]](query QT) Validator
Present expects the query to have any value.
func UnorderedEqual ¶
func UnorderedEqual[T any, QT ygnmi.SingletonQuery[[]T]](query QT, want []T, less func(a, b T) bool) Validator
UnorderedEqual function is used to compare slices of type T in unordered way.