input/elasticapm/internal/modeldecoder/modeldecodertest/populator.go (402 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package modeldecodertest import ( "fmt" "net/http" "reflect" "strings" "testing" "time" "github.com/stretchr/testify/assert" "google.golang.org/protobuf/types/known/structpb" "github.com/elastic/apm-data/input/elasticapm/internal/modeldecoder/nullable" "github.com/elastic/apm-data/model/modelpb" ) // Values used for populating the model structs type Values struct { Str string Int int Float float64 Bool bool Duration time.Duration IP *modelpb.IP HTTPHeader http.Header LabelVal *modelpb.LabelValue NumericLabelVal *modelpb.NumericLabelValue // N controls how many elements are added to a slice or a map N int } // DefaultValues returns a Values struct initialized with non-zero values func DefaultValues() *Values { return &Values{ Str: "init", Int: 1, Float: 0.5, Bool: true, Duration: time.Second, IP: modelpb.MustParseIP("127.0.0.1"), HTTPHeader: http.Header{http.CanonicalHeaderKey("user-agent"): []string{"a", "b", "c"}}, LabelVal: &modelpb.LabelValue{Value: "init"}, NumericLabelVal: &modelpb.NumericLabelValue{Value: 0.5}, N: 3, } } // NonDefaultValues returns a Values struct initialized with non-zero values func NonDefaultValues() *Values { return &Values{ Str: "overwritten", Int: 12, Float: 3.5, Bool: false, Duration: time.Minute, IP: modelpb.MustParseIP("192.168.0.1"), HTTPHeader: http.Header{http.CanonicalHeaderKey("user-agent"): []string{"d", "e"}}, LabelVal: &modelpb.LabelValue{Value: "overwritten"}, NumericLabelVal: &modelpb.NumericLabelValue{Value: 3.5}, N: 2, } } // Update arbitrary values func (v *Values) Update(args ...interface{}) { for _, arg := range args { switch a := arg.(type) { case string: v.Str = a case int: v.Int = a case float64: v.Float = a case bool: v.Bool = a case *modelpb.IP: v.IP = a case http.Header: v.HTTPHeader = a default: panic(fmt.Sprintf("Values Merge: value type for %v not implemented", a)) } } } // InitStructValues iterates through the struct fields represented by // the given reflect.Value and initializes all fields with // some arbitrary value. func InitStructValues(i interface{}) { SetStructValues(i, DefaultValues()) } // SetStructValuesOption is the type of an option which may be passed into // SetStructValues to override the value to which a field is set. If the // option returns false, then the field will not be updated and no more options // will be invoked. type SetStructValuesOption func(key string, field, value reflect.Value) bool // SetStructValues iterates through the struct fields represented by // the given reflect.Value and initializes all fields with the provided values func SetStructValues(in interface{}, values *Values, opts ...SetStructValuesOption) { IterateStruct(in, func(f reflect.Value, key string) { fieldVal := f switch fKind := f.Kind(); fKind { case reflect.String: fieldVal = reflect.ValueOf(values.Str) case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint64: fieldVal = reflect.ValueOf(values.Int).Convert(f.Type()) case reflect.Float64: fieldVal = reflect.ValueOf(values.Float).Convert(f.Type()) case reflect.Slice: var elemVal reflect.Value switch v := f.Interface().(type) { case []string: elemVal = reflect.ValueOf(values.Str) case []int: elemVal = reflect.ValueOf(values.Int) case []int64: elemVal = reflect.ValueOf(int64(values.Int)) case []uint: elemVal = reflect.ValueOf(uint(values.Int)) case []uint64: elemVal = reflect.ValueOf(uint64(values.Int)) case []uint8: elemVal = reflect.ValueOf(uint8(values.Int)) case []float64: elemVal = reflect.ValueOf(values.Float) case []*modelpb.IP: elemVal = reflect.ValueOf(values.IP) default: if f.Type().Elem().Kind() != reflect.Struct { panic(fmt.Sprintf("unhandled type %s for key %s", v, key)) } elemVal = reflect.Zero(f.Type().Elem()) } if elemVal.IsValid() { fieldVal = fieldVal.Slice(0, 0) for i := 0; i < values.N; i++ { fieldVal = reflect.Append(fieldVal, elemVal) } } case reflect.Map: fieldVal = reflect.MakeMapWithSize(f.Type(), values.N) var elemVal reflect.Value switch v := f.Interface().(type) { case map[string]interface{}: elemVal = reflect.ValueOf(values.Str) case map[string]float64: elemVal = reflect.ValueOf(values.Float) case map[string]*modelpb.LabelValue: elemVal = reflect.ValueOf(values.LabelVal) case map[string]*modelpb.NumericLabelValue: elemVal = reflect.ValueOf(values.NumericLabelVal) default: if f.Type().Elem().Kind() != reflect.Struct { panic(fmt.Sprintf("unhandled type %s for key %s", v, key)) } elemVal = reflect.Zero(f.Type().Elem()) } for i := 0; i < values.N; i++ { fieldVal.SetMapIndex(reflect.ValueOf(fmt.Sprintf("%s%v", values.Str, i)), elemVal) } case reflect.Struct: switch v := f.Interface().(type) { case nullable.String: v.Set(values.Str) fieldVal = reflect.ValueOf(v) case nullable.Int: v.Set(values.Int) fieldVal = reflect.ValueOf(v) case nullable.Int64: v.Set(int64(values.Int)) fieldVal = reflect.ValueOf(v) case nullable.Interface: if strings.Contains(key, "port") { v.Set(values.Int) } else { v.Set(values.Str) } fieldVal = reflect.ValueOf(v) case nullable.Bool: v.Set(values.Bool) fieldVal = reflect.ValueOf(v) case nullable.Float64: v.Set(values.Float) fieldVal = reflect.ValueOf(v) case nullable.HTTPHeader: v.Set(values.HTTPHeader.Clone()) fieldVal = reflect.ValueOf(v) case *modelpb.IP: fieldVal = reflect.ValueOf(values.IP) default: if f.IsZero() { fieldVal = reflect.Zero(f.Type()) } else { return } } case reflect.Ptr: return default: fieldVal = reflect.Value{} } // Run through options, giving an opportunity to disable // setting the field, or change the value. setField := true for _, opt := range opts { if !opt(key, f, fieldVal) { setField = false break } } if setField { if !fieldVal.IsValid() { panic(fmt.Sprintf("unhandled type %s for key %s", f.Kind(), key)) } f.Set(fieldVal) } }) } // SetZeroStructValues iterates through the struct fields represented by // the given reflect.Value and sets all fields to their zero values. func SetZeroStructValues(i interface{}) { IterateStruct(i, func(f reflect.Value, key string) { f.Set(reflect.Zero(f.Type())) }) } // SetZeroStructValue iterates through the struct fields represented by // the given reflect.Value, sets a field to its zero value, // calls the callback function and resets the field to its original value func SetZeroStructValue(i interface{}, callback func(string)) { IterateStruct(i, func(f reflect.Value, key string) { original := reflect.ValueOf(f.Interface()) defer f.Set(original) // reset original value f.Set(reflect.Zero(f.Type())) callback(key) }) } // AssertStructValues recursively walks through the given struct and asserts // that values are equal to expected values func AssertStructValues(t *testing.T, i interface{}, isException func(string) bool, values *Values) { IterateStruct(i, func(f reflect.Value, key string) { if isException(key) { return } fVal := f.Interface() var newVal interface{} switch fVal.(type) { case map[string]interface{}: m := map[string]interface{}{} for i := 0; i < values.N; i++ { m[fmt.Sprintf("%s%v", values.Str, i)] = values.Str } newVal = m case map[string]*modelpb.LabelValue: m := modelpb.Labels{} for i := 0; i < values.N; i++ { m[fmt.Sprintf("%s%v", values.Str, i)] = &modelpb.LabelValue{Value: values.Str} } newVal = m case []*modelpb.KeyValue: m := []*modelpb.KeyValue{} for i := 0; i < values.N; i++ { value, _ := structpb.NewValue(values.Str) m = append(m, &modelpb.KeyValue{ Key: fmt.Sprintf("%s%v", values.Str, i), Value: value, }) } newVal = m case []string: m := make([]string, values.N) for i := 0; i < values.N; i++ { m[i] = values.Str } newVal = m case string: newVal = values.Str case *string: newVal = &values.Str case int: newVal = values.Int case int64: newVal = int64(values.Int) case uint64: newVal = uint64(values.Int) case *int64: val := int64(values.Int) newVal = &val case *uint64: val := uint64(values.Int) newVal = &val case *int: newVal = &values.Int case int32: newVal = int32(values.Int) case uint32: switch true { case strings.Contains(key, "v4"): newVal = uint32(values.IP.V4) default: newVal = uint32(values.Int) } case *uint32: val := uint32(values.Int) newVal = &val case float64: newVal = values.Float case *float64: val := values.Float newVal = &val case bool: newVal = values.Bool case *bool: newVal = &values.Bool case []byte: newVal = values.IP.V6 case http.Header: newVal = values.HTTPHeader case time.Duration: newVal = values.Duration default: // the populator recursively iterates over struct and structPtr // calling this function for all fields; // it is enough to only assert they are not zero here if f.Type().Kind() == reflect.Struct { assert.NotZero(t, fVal, key) return } if f.Type().Kind() == reflect.Ptr && f.Type().Elem().Kind() == reflect.Struct { assert.NotZero(t, fVal, key) return } if f.Type().Kind() == reflect.Map || f.Type().Kind() == reflect.Slice { assert.NotZero(t, fVal, key) return } panic(fmt.Sprintf("unhandled type %s %s for key %s", f.Kind(), f.Type(), key)) } if f.Kind() == reflect.Map || f.Kind() == reflect.Slice { assert.ElementsMatch(t, newVal, fVal, key) return } assert.Equal(t, newVal, fVal, key) }) } // IterateStruct iterates through the struct fields represented by // the given reflect.Value and calls the given function on every field. func IterateStruct(i interface{}, fn func(reflect.Value, string)) { val := reflect.ValueOf(i) if val.Kind() != reflect.Ptr { panic("expected pointer to struct as parameter") } iterateStruct(val.Elem(), "", fn) } func iterateStruct(v reflect.Value, key string, fn func(f reflect.Value, fKey string)) { if v.Type().Kind() == reflect.Ptr { v = v.Elem() } t := v.Type() if t.Kind() != reflect.Struct { panic(fmt.Sprintf("iterateStruct: invalid type %s", t.Kind())) } if key != "" { key += "." } var fKey string for i := 0; i < v.NumField(); i++ { f := v.Field(i) if !f.CanSet() { continue } stf := t.Field(i) fTyp := stf.Type name := jsonName(stf) if name == "" { name = stf.Name } fKey = fmt.Sprintf("%s%s", key, name) // call the given function with every field fn(f, fKey) // check field type for recursive iteration switch f.Kind() { case reflect.Ptr: if !f.IsZero() && fTyp.Elem().Kind() == reflect.Struct { iterateStruct(f.Elem(), fKey, fn) } case reflect.Struct: switch f.Interface().(type) { case nullable.String, nullable.Int, nullable.Bool, nullable.Float64, nullable.Interface, nullable.HTTPHeader: default: iterateStruct(f, fKey, fn) } case reflect.Map: if f.Type().Elem().Kind() != reflect.Struct { continue } iter := f.MapRange() for iter.Next() { mKey := iter.Key() mVal := iter.Value() ptr := reflect.New(mVal.Type()) ptr.Elem().Set(mVal) iterateStruct(ptr.Elem(), fmt.Sprintf("%s.[%s]", fKey, mKey), fn) f.SetMapIndex(mKey, ptr.Elem()) } case reflect.Slice, reflect.Array: if v.Type() == f.Type().Elem() { continue } switch f.Interface().(type) { case []*modelpb.KeyValue: continue } for j := 0; j < f.Len(); j++ { sliceField := f.Index(j) switch sliceField.Kind() { case reflect.Struct: iterateStruct(sliceField, fmt.Sprintf("%s.[%v]", fKey, j), fn) case reflect.Ptr: if !sliceField.IsZero() && sliceField.Type().Elem().Kind() == reflect.Struct { iterateStruct(sliceField.Elem(), fKey, fn) } } } } } } func jsonName(f reflect.StructField) string { tag, ok := f.Tag.Lookup("json") if !ok || tag == "-" { return "" } parts := strings.Split(tag, ",") if len(parts) == 0 { return "" } return parts[0] }