From dfd8ed0d1fb7a0e5a86246a7dde574d1e9e1c11b Mon Sep 17 00:00:00 2001 From: Andrew Cholakian Date: Fri, 6 Sep 2019 13:03:04 -0500 Subject: [PATCH] [libbeat] Update lookslike to 0.3.0 (#13498) Updates the go-lookslike library we maintain to 0.3.0 which uses a much improved reflection strategy. This fixed an issue in the last release where Strict() which checked for extra fields wasn't working. As a result some of our tests developed since then didn't assert that the new fields added in #13022 were actually valid. Those tests are fixed in this PR. --- NOTICE.txt | 4 +- heartbeat/monitors/active/http/http_test.go | 18 +++- .../github.com/elastic/go-lookslike/README.md | 4 +- .../elastic/go-lookslike/compiled_schema.go | 10 ++- .../github.com/elastic/go-lookslike/core.go | 41 ++++----- .../go-lookslike/internal/llreflect/chase.go | 13 +++ .../elastic/go-lookslike/llpath/path.go | 34 ++++--- .../github.com/elastic/go-lookslike/walk.go | 90 ++++++++++--------- vendor/vendor.json | 15 ++-- 9 files changed, 139 insertions(+), 90 deletions(-) create mode 100644 vendor/github.com/elastic/go-lookslike/internal/llreflect/chase.go diff --git a/NOTICE.txt b/NOTICE.txt index 29e4ce42667e..907b79cf3b35 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -861,8 +861,8 @@ Apache License 2.0 -------------------------------------------------------------------- Dependency: github.com/elastic/go-lookslike -Version: v0.2.0 -Revision: 807124eb9fc6684949aa99744577175fd6bac4fd +Version: =v0.3.0 +Revision: 747dc7db1c961662d8e225a42af6c3859a1a0f1d License type (autodetected): Apache-2.0 ./vendor/github.com/elastic/go-lookslike/LICENSE: -------------------------------------------------------------------- diff --git a/heartbeat/monitors/active/http/http_test.go b/heartbeat/monitors/active/http/http_test.go index af6deac5c603..be5055e33069 100644 --- a/heartbeat/monitors/active/http/http_test.go +++ b/heartbeat/monitors/active/http/http_test.go @@ -41,6 +41,8 @@ import ( btesting "github.com/elastic/beats/libbeat/testing" "github.com/elastic/go-lookslike" "github.com/elastic/go-lookslike/isdef" + "github.com/elastic/go-lookslike/llpath" + "github.com/elastic/go-lookslike/llresult" "github.com/elastic/go-lookslike/testslike" "github.com/elastic/go-lookslike/validator" ) @@ -102,7 +104,21 @@ func respondingHTTPChecks(url string, statusCode int) validator.Validator { httpBaseChecks(url), lookslike.MustCompile(map[string]interface{}{ "http": map[string]interface{}{ - "response.status_code": statusCode, + "response.status_code": statusCode, + "response.body.hash": isdef.IsString, + // TODO add this isdef to lookslike in a robust way + "response.body.bytes": isdef.Is("an int64 greater than 0", func(path llpath.Path, v interface{}) *llresult.Results { + raw, ok := v.(int64) + if !ok { + return llresult.SimpleResult(path, false, "%s is not an int64", reflect.TypeOf(v)) + } + if raw >= 0 { + return llresult.ValidResult(path) + } + + return llresult.SimpleResult(path, false, "value %v not >= 0 ", raw) + + }), "rtt.content.us": isdef.IsDuration, "rtt.response_header.us": isdef.IsDuration, "rtt.total.us": isdef.IsDuration, diff --git a/vendor/github.com/elastic/go-lookslike/README.md b/vendor/github.com/elastic/go-lookslike/README.md index 4c6486b6c3a1..eea0d0dda9f7 100644 --- a/vendor/github.com/elastic/go-lookslike/README.md +++ b/vendor/github.com/elastic/go-lookslike/README.md @@ -7,7 +7,7 @@ This library is here to help you with all your data validation needs. It's ideal ## Quick Links * [GoDoc](https://godoc.org/github.com/elastic/go-lookslike) for this library. -* [Runnable Examples](https://github.com/elastic/go-lookslike/blob/master/lookslike/doc_test.go). +* [Runnable Examples](https://github.com/elastic/go-lookslike/blob/master/doc_test.go). ## Install @@ -25,7 +25,7 @@ If using govendor run: ## Real World Usage Examples -lookslike was created to improve the testing of various structures in [elastic/beats](https://github.com/elastic/beats). Searching the tests for `lookslike` will show real world usage. +lookslike was created to improve the testing of various structures in [elastic/beats](https://github.com/elastic/beats/search?q=lookslike.MustCompile&unscoped_q=lookslike.MustCompile). Searching the tests for `lookslike.MustCompile` will show real world usage. ## Call for More `isdef`s! diff --git a/vendor/github.com/elastic/go-lookslike/compiled_schema.go b/vendor/github.com/elastic/go-lookslike/compiled_schema.go index 5139ed38d5d8..919ca283312b 100644 --- a/vendor/github.com/elastic/go-lookslike/compiled_schema.go +++ b/vendor/github.com/elastic/go-lookslike/compiled_schema.go @@ -21,6 +21,7 @@ import ( "github.com/elastic/go-lookslike/isdef" "github.com/elastic/go-lookslike/llpath" "github.com/elastic/go-lookslike/llresult" + "reflect" ) type flatValidator struct { @@ -35,11 +36,16 @@ type CompiledSchema []flatValidator func (cs CompiledSchema) Check(actual interface{}) *llresult.Results { res := llresult.NewResults() for _, pv := range cs { - actualV, actualKeyExists := pv.path.GetFrom(actual) + actualVal, actualKeyExists := pv.path.GetFrom(reflect.ValueOf(actual)) + var actualInter interface{} + zero := reflect.Value{} + if actualVal != zero { + actualInter = actualVal.Interface() + } if !pv.isDef.Optional || pv.isDef.Optional && actualKeyExists { var checkRes *llresult.Results - checkRes = pv.isDef.Check(pv.path, actualV, actualKeyExists) + checkRes = pv.isDef.Check(pv.path, actualInter, actualKeyExists) res.Merge(checkRes) } } diff --git a/vendor/github.com/elastic/go-lookslike/core.go b/vendor/github.com/elastic/go-lookslike/core.go index bf33df31e9f8..3068d8e68996 100644 --- a/vendor/github.com/elastic/go-lookslike/core.go +++ b/vendor/github.com/elastic/go-lookslike/core.go @@ -74,7 +74,7 @@ func Strict(laxValidator validator.Validator) validator.Validator { } sort.Strings(validatedPaths) - walk(actual, false, func(woi walkObserverInfo) error { + walk(reflect.ValueOf(actual), false, func(woi walkObserverInfo) error { _, validatedExactly := res.Fields[woi.path.String()] if validatedExactly { return nil // This key was tested, passes strict test @@ -98,33 +98,36 @@ func Strict(laxValidator validator.Validator) validator.Validator { func compile(in interface{}) (validator.Validator, error) { switch in.(type) { - case map[string]interface{}: - return compileMap(in.(map[string]interface{})) - case []interface{}: - return compileSlice(in.([]interface{})) case isdef.IsDef: return compileIsDef(in.(isdef.IsDef)) case nil: // nil can't be handled by the default case of IsEqual return compileIsDef(isdef.IsNil) default: - // By default we just check reflection equality - return compileIsDef(isdef.IsEqual(in)) + inVal := reflect.ValueOf(in) + switch inVal.Kind() { + case reflect.Map: + return compileMap(inVal) + case reflect.Slice, reflect.Array: + return compileSlice(inVal) + default: + return compileIsDef(isdef.IsEqual(in)) + } } } -func compileMap(in map[string]interface{}) (validator validator.Validator, err error) { +func compileMap(inVal reflect.Value) (validator validator.Validator, err error) { wo, compiled := setupWalkObserver() - err = walkMap(in, true, wo) + err = walkMap(inVal, true, wo) return func(actual interface{}) *llresult.Results { return compiled.Check(actual) }, err } -func compileSlice(in []interface{}) (validator validator.Validator, err error) { +func compileSlice(inVal reflect.Value) (validator validator.Validator, err error) { wo, compiled := setupWalkObserver() - err = walkSlice(in, true, wo) + err = walkSlice(inVal, true, wo) // Slices are always strict in validation because // it would be surprising to only validate the first specified values @@ -142,18 +145,16 @@ func compileIsDef(def isdef.IsDef) (validator validator.Validator, err error) { func setupWalkObserver() (walkObserver, *CompiledSchema) { compiled := make(CompiledSchema, 0) return func(current walkObserverInfo) error { - // Determine whether we should test this value - // We want to test all values except collections that contain a value - // If a collection contains a value, we Check those 'leaf' values instead - rv := reflect.ValueOf(current.value) - kind := rv.Kind() + kind := current.value.Kind() isCollection := kind == reflect.Map || kind == reflect.Slice - isNonEmptyCollection := isCollection && rv.Len() > 0 + isEmptyCollection := isCollection && current.value.Len() == 0 - if !isNonEmptyCollection { - isDef, isIsDef := current.value.(isdef.IsDef) + // We do comparisons on all leaf nodes. If the leaf is an empty collection + // we do a comparison to let us test empty structures. + if !isCollection || isEmptyCollection { + isDef, isIsDef := current.value.Interface().(isdef.IsDef) if !isIsDef { - isDef = isdef.IsEqual(current.value) + isDef = isdef.IsEqual(current.value.Interface()) } compiled = append(compiled, flatValidator{current.path, isDef}) diff --git a/vendor/github.com/elastic/go-lookslike/internal/llreflect/chase.go b/vendor/github.com/elastic/go-lookslike/internal/llreflect/chase.go new file mode 100644 index 000000000000..b5f6c12b394f --- /dev/null +++ b/vendor/github.com/elastic/go-lookslike/internal/llreflect/chase.go @@ -0,0 +1,13 @@ +package llreflect + +import ( + "reflect" +) + +// ChaseValue takes a value and returns the underlying type even if it is nested inpointers or wrapped in interface{} +func ChaseValue(v reflect.Value) reflect.Value { + for (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface) && !v.IsNil() { + v = v.Elem() + } + return v +} diff --git a/vendor/github.com/elastic/go-lookslike/llpath/path.go b/vendor/github.com/elastic/go-lookslike/llpath/path.go index 0ac5f910d739..293cf29cc77d 100644 --- a/vendor/github.com/elastic/go-lookslike/llpath/path.go +++ b/vendor/github.com/elastic/go-lookslike/llpath/path.go @@ -117,46 +117,44 @@ func (p Path) Last() *PathComponent { } // GetFrom takes a map and fetches the given Path from it. -func (p Path) GetFrom(m interface{}) (value interface{}, exists bool) { +func (p Path) GetFrom(source reflect.Value) (result reflect.Value, exists bool) { // nil values are handled specially. If we're fetching from a nil // there's one case where it exists, when comparing it to another nil. - if m == nil { + if (source.Kind() == reflect.Map || source.Kind() == reflect.Slice) && source.IsNil() { // since another nil would be scalar, we just check that the // path length is 0. - return nil, len(p) == 0 + return source, len(p) == 0 } - value = m + result = source exists = true for _, pc := range p { - rt := reflect.TypeOf(value) - switch rt.Kind() { + switch result.Kind() { case reflect.Map: - converted := llreflect.InterfaceToMap(value) - value, exists = converted[pc.Key] - case reflect.Slice: - converted := llreflect.InterfaceToSliceOfInterfaces(value) - if pc.Index < len(converted) { - exists = true - value = converted[pc.Index] + result = llreflect.ChaseValue(result.MapIndex(reflect.ValueOf(pc.Key))) + exists = result != reflect.Value{} + case reflect.Slice, reflect.Array: + if pc.Index < result.Len() { + result = llreflect.ChaseValue(result.Index(pc.Index)) + exists = result != reflect.Value{} } else { + result = reflect.ValueOf(nil) exists = false - value = nil } default: // If this case has been reached this means the expected type, say a map, // is actually something else, like a string or an array. In this case we - // simply say the value doesn't exist. From a practical perspective this is + // simply say the result doesn't exist. From a practical perspective this is // the right behavior since it will cause validation to fail. - return nil, false + return reflect.ValueOf(nil), false } if exists == false { - return nil, exists + return reflect.ValueOf(nil), exists } } - return value, exists + return result, exists } var arrMatcher = regexp.MustCompile("\\[(\\d+)\\]") diff --git a/vendor/github.com/elastic/go-lookslike/walk.go b/vendor/github.com/elastic/go-lookslike/walk.go index 2c0189a685e7..3ac05fdf71a4 100644 --- a/vendor/github.com/elastic/go-lookslike/walk.go +++ b/vendor/github.com/elastic/go-lookslike/walk.go @@ -18,17 +18,17 @@ package lookslike import ( + "fmt" "reflect" - "github.com/elastic/go-lookslike/internal/llreflect" "github.com/elastic/go-lookslike/llpath" ) type walkObserverInfo struct { - key llpath.PathComponent - value interface{} - root map[string]interface{} - path llpath.Path + key llpath.PathComponent + value reflect.Value + rootVal reflect.Value + path llpath.Path } // walkObserver functions run once per object in the tree. @@ -36,65 +36,68 @@ type walkObserver func(info walkObserverInfo) error // walk determine if in is a `map[string]interface{}` or a `Slice` and traverse it if so, otherwise will // treat it as a scalar and invoke the walk observer on the input value directly. -func walk(in interface{}, expandPaths bool, wo walkObserver) error { - switch in.(type) { - case map[string]interface{}: - return walkMap(in.(map[string]interface{}), expandPaths, wo) - case []interface{}: - return walkSlice(in.([]interface{}), expandPaths, wo) +func walk(inVal reflect.Value, expandPaths bool, wo walkObserver) error { + switch inVal.Kind() { + case reflect.Map: + return walkMap(inVal, expandPaths, wo) + case reflect.Slice: + return walkSlice(inVal, expandPaths, wo) default: - return walkInterface(in, expandPaths, wo) + return walkInterface(inVal, expandPaths, wo) } } // walkmap[string]interface{} is a shorthand way to walk a tree with a map as the root. -func walkMap(m map[string]interface{}, expandPaths bool, wo walkObserver) error { - return walkFullMap(m, m, llpath.Path{}, expandPaths, wo) +func walkMap(mVal reflect.Value, expandPaths bool, wo walkObserver) error { + return walkFullMap(mVal, mVal, llpath.Path{}, expandPaths, wo) } // walkSlice walks the provided root slice. -func walkSlice(s []interface{}, expandPaths bool, wo walkObserver) error { - return walkFullSlice(s, map[string]interface{}{}, llpath.Path{}, expandPaths, wo) +func walkSlice(sVal reflect.Value, expandPaths bool, wo walkObserver) error { + return walkFullSlice(sVal, reflect.ValueOf(map[string]interface{}{}), llpath.Path{}, expandPaths, wo) } -func walkInterface(s interface{}, expandPaths bool, wo walkObserver) error { +func walkInterface(s reflect.Value, expandPaths bool, wo walkObserver) error { return wo(walkObserverInfo{ - value: s, - key: llpath.PathComponent{}, - root: map[string]interface{}{}, - path: llpath.Path{}, + value: s, + key: llpath.PathComponent{}, + rootVal: reflect.ValueOf(map[string]interface{}{}), + path: llpath.Path{}, }) } -func walkFull(o interface{}, root map[string]interface{}, path llpath.Path, expandPaths bool, wo walkObserver) (err error) { +func walkFull(oVal, rootVal reflect.Value, path llpath.Path, expandPaths bool, wo walkObserver) (err error) { + + // Unpack any wrapped interfaces + for oVal.Kind() == reflect.Interface { + oVal = reflect.ValueOf(oVal.Interface()) + } + lastPathComponent := path.Last() if lastPathComponent == nil { // In the case of a slice we can have an empty path - if _, ok := o.([]interface{}); ok { + if oVal.Kind() == reflect.Slice || oVal.Kind() == reflect.Array { lastPathComponent = &llpath.PathComponent{} } else { - panic("Attempted to traverse an empty Path on a map[string]interface{} in lookslike.walkFull, this should never happen.") + panic("Attempted to traverse an empty Path on non array/slice in lookslike.walkFull, this should never happen.") } } - err = wo(walkObserverInfo{*lastPathComponent, o, root, path}) + err = wo(walkObserverInfo{*lastPathComponent, oVal, rootVal, path}) if err != nil { return err } - switch reflect.TypeOf(o).Kind() { + switch oVal.Kind() { case reflect.Map: - converted := llreflect.InterfaceToMap(o) - err := walkFullMap(converted, root, path, expandPaths, wo) + err := walkFullMap(oVal, rootVal, path, expandPaths, wo) if err != nil { return err } case reflect.Slice: - converted := llreflect.InterfaceToSliceOfInterfaces(o) - - for idx, v := range converted { - newPath := path.ExtendSlice(idx) - err := walkFull(v, root, newPath, expandPaths, wo) + for i := 0; i < oVal.Len(); i++ { + newPath := path.ExtendSlice(i) + err := walkFull(oVal.Index(i), rootVal, newPath, expandPaths, wo) if err != nil { return err } @@ -105,8 +108,15 @@ func walkFull(o interface{}, root map[string]interface{}, path llpath.Path, expa } // walkFull walks the given map[string]interface{} tree. -func walkFullMap(m map[string]interface{}, root map[string]interface{}, p llpath.Path, expandPaths bool, wo walkObserver) (err error) { - for k, v := range m { +func walkFullMap(mVal, rootVal reflect.Value, p llpath.Path, expandPaths bool, wo walkObserver) (err error) { + if mVal.Kind() != reflect.Map { + return fmt.Errorf("could not walk not map type for %s", mVal) + } + + for _, kVal := range mVal.MapKeys() { + vVal := mVal.MapIndex(kVal) + k := kVal.String() + var newPath llpath.Path if !expandPaths { newPath = p.ExtendMap(k) @@ -118,7 +128,7 @@ func walkFullMap(m map[string]interface{}, root map[string]interface{}, p llpath newPath = p.Concat(additionalPath) } - err = walkFull(v, root, newPath, expandPaths, wo) + err = walkFull(vVal, rootVal, newPath, expandPaths, wo) if err != nil { return err } @@ -127,12 +137,12 @@ func walkFullMap(m map[string]interface{}, root map[string]interface{}, p llpath return nil } -func walkFullSlice(s []interface{}, root map[string]interface{}, p llpath.Path, expandPaths bool, wo walkObserver) (err error) { - for idx, v := range s { +func walkFullSlice(sVal reflect.Value, rootVal reflect.Value, p llpath.Path, expandPaths bool, wo walkObserver) (err error) { + for i := 0; i < sVal.Len(); i++ { var newPath llpath.Path - newPath = p.ExtendSlice(idx) + newPath = p.ExtendSlice(i) - err = walkFull(v, root, newPath, expandPaths, wo) + err = walkFull(sVal.Index(i), rootVal, newPath, expandPaths, wo) if err != nil { return err } diff --git a/vendor/vendor.json b/vendor/vendor.json index 86bf14db2b3d..78b6d1cd0d10 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -1367,13 +1367,18 @@ "versionExact": "v0.4.0" }, { - "checksumSHA1": "c54eehjtxPjBUbM4aQuDPgc7G9Q=", + "checksumSHA1": "+nIsRlnG94bUQI6IQd4Mcj4diTY=", "path": "github.com/elastic/go-lookslike", - "revision": "807124eb9fc6684949aa99744577175fd6bac4fd", - "revisionTime": "2019-06-17T15:05:19Z", + "revision": "747dc7db1c961662d8e225a42af6c3859a1a0f1d", + "revisionTime": "2019-09-04T15:56:46Z", "tree": true, - "version": "v0.2.0", - "versionExact": "v0.2.0" + "version": "=v0.3.0", + "versionExact": "v0.3.0" + }, + { + "path": "github.com/elastic/go-lookslike^", + "revision": "=v0.3.0", + "version": "=v0.3.0" }, { "checksumSHA1": "3jizmlZPCyo6FAZY8Trk9jA8NH4=",