diff --git a/model/modeldecoder/modeldecodertest/populator.go b/model/modeldecoder/modeldecodertest/populator.go index a79ac58d7ae..34a31f422fc 100644 --- a/model/modeldecoder/modeldecodertest/populator.go +++ b/model/modeldecoder/modeldecodertest/populator.go @@ -34,82 +34,150 @@ import ( "github.com/elastic/apm-server/model/modeldecoder/nullable" ) +// Values used for populating the model structs +type Values struct { + Str string + Int int + Float float64 + Bool bool + Time time.Time + IP net.IP + HTTPHeader http.Header + // 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 { + initTime, _ := time.Parse(time.RFC3339, "2020-10-10T10:00:00Z") + return &Values{ + Str: "init", + Int: 1, + Float: 0.5, + Bool: true, + Time: initTime, + IP: net.ParseIP("127.0.0.1"), + HTTPHeader: http.Header{http.CanonicalHeaderKey("user-agent"): []string{"a", "b", "c"}}, + N: 3, + } +} + +// NonDefaultValues returns a Values struct initialized with non-zero values +func NonDefaultValues() *Values { + updatedTime, _ := time.Parse(time.RFC3339, "2020-12-10T10:00:00Z") + return &Values{ + Str: "overwritten", + Int: 12, + Float: 3.5, + Bool: false, + Time: updatedTime, + IP: net.ParseIP("192.168.0.1"), + HTTPHeader: http.Header{http.CanonicalHeaderKey("user-agent"): []string{"d", "e"}}, + 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 time.Time: + v.Time = a + case net.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, "unknown", 1, true, time.Now()) + SetStructValues(i, DefaultValues()) } // SetStructValues iterates through the struct fields represented by -// the given reflect.Value and initializes all fields with -// the given values for strings and integers. -func SetStructValues(in interface{}, vStr string, vInt int, vBool bool, vTime time.Time) { +// the given reflect.Value and initializes all fields with the provided values +func SetStructValues(in interface{}, values *Values) { IterateStruct(in, func(f reflect.Value, key string) { - var newVal interface{} switch fKind := f.Kind(); fKind { case reflect.Slice: + if f.IsNil() { + f.Set(reflect.MakeSlice(f.Type(), 0, values.N)) + } + var newVal reflect.Value switch v := f.Interface().(type) { case []string: - newVal = []string{vStr} + newVal = reflect.ValueOf(values.Str) case []int: - newVal = []int{vInt, vInt} + newVal = reflect.ValueOf(values.Int) default: if f.Type().Elem().Kind() != reflect.Struct { panic(fmt.Sprintf("unhandled type %s for key %s", v, key)) } - if f.IsNil() { - f.Set(reflect.MakeSlice(f.Type(), 1, 1)) - } - f.Index(0).Set(reflect.Zero(f.Type().Elem())) - return + newVal = reflect.Zero(f.Type().Elem()) + } + for i := 0; i < values.N; i++ { + f.Set(reflect.Append(f, newVal)) } case reflect.Map: + if f.IsNil() { + f.Set(reflect.MakeMapWithSize(f.Type(), values.N)) + } + var newVal reflect.Value switch v := f.Interface().(type) { - case map[string]interface{}: - newVal = map[string]interface{}{vStr: vStr} - case common.MapStr: - newVal = common.MapStr{vStr: vStr} + case map[string]interface{}, common.MapStr: + newVal = reflect.ValueOf(values.Str) case map[string]float64: - newVal = map[string]float64{vStr: float64(vInt) + 0.5} + newVal = reflect.ValueOf(values.Float) default: if f.Type().Elem().Kind() != reflect.Struct { panic(fmt.Sprintf("unhandled type %s for key %s", v, key)) } - if f.IsNil() { - f.Set(reflect.MakeMap(f.Type())) - } - mKey := reflect.Zero(f.Type().Key()) - mVal := reflect.Zero(f.Type().Elem()) - f.SetMapIndex(mKey, mVal) - return + newVal = reflect.Zero(f.Type().Elem()) + } + for i := 0; i < values.N; i++ { + f.SetMapIndex(reflect.ValueOf(fmt.Sprintf("%s%v", values.Str, i)), newVal) } case reflect.Struct: + var newVal interface{} switch v := f.Interface().(type) { case nullable.String: - v.Set(vStr) + v.Set(values.Str) newVal = v case nullable.Int: - v.Set(vInt) + v.Set(values.Int) newVal = v case nullable.Interface: if strings.Contains(key, "port") { - v.Set(vInt) + v.Set(values.Int) } else { - v.Set(vStr) + v.Set(values.Str) } newVal = v case nullable.Bool: - v.Set(vBool) + v.Set(values.Bool) newVal = v case nullable.Float64: - v.Set(float64(vInt) + 0.5) + v.Set(values.Float) newVal = v case nullable.TimeMicrosUnix: - v.Set(vTime) + v.Set(values.Time) newVal = v case nullable.HTTPHeader: - v.Set(http.Header{vStr: []string{vStr, vStr}}) + v.Set(values.HTTPHeader.Clone()) newVal = v default: if f.IsZero() { @@ -117,6 +185,7 @@ func SetStructValues(in interface{}, vStr string, vInt int, vBool bool, vTime ti } return } + f.Set(reflect.ValueOf(newVal)) case reflect.Ptr: if f.IsNil() { f.Set(reflect.Zero(f.Type())) @@ -125,7 +194,6 @@ func SetStructValues(in interface{}, vStr string, vInt int, vBool bool, vTime ti default: panic(fmt.Sprintf("unhandled type %s for key %s", fKind, key)) } - f.Set(reflect.ValueOf(newVal)) }) } @@ -152,7 +220,7 @@ func SetZeroStructValue(i interface{}, callback func(string)) { // 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, - vStr string, vInt int, vBool bool, vIP net.IP, vTime time.Time) { + values *Values) { IterateStruct(i, func(f reflect.Value, key string) { if isException(key) { return @@ -161,42 +229,58 @@ func AssertStructValues(t *testing.T, i interface{}, isException func(string) bo var newVal interface{} switch fVal.(type) { case map[string]interface{}: - newVal = map[string]interface{}{vStr: vStr} - case map[string]float64: - newVal = map[string]float64{vStr: float64(vInt) + 0.5} + m := map[string]interface{}{} + for i := 0; i < values.N; i++ { + m[fmt.Sprintf("%s%v", values.Str, i)] = values.Str + } + newVal = m case common.MapStr: - newVal = common.MapStr{vStr: vStr} + m := common.MapStr{} + for i := 0; i < values.N; i++ { + m.Put(fmt.Sprintf("%s%v", values.Str, i), values.Str) + } + newVal = m case *model.Labels: - newVal = &model.Labels{vStr: vStr} + m := model.Labels{} + for i := 0; i < values.N; i++ { + m[fmt.Sprintf("%s%v", values.Str, i)] = values.Str + } + newVal = &m case *model.Custom: - newVal = &model.Custom{vStr: vStr} + m := model.Custom{} + for i := 0; i < values.N; i++ { + m[fmt.Sprintf("%s%v", values.Str, i)] = values.Str + } + newVal = &m case []string: - newVal = []string{vStr} - case []int: - newVal = []int{vInt, vInt} + m := make([]string, values.N) + for i := 0; i < values.N; i++ { + m[i] = values.Str + } + newVal = m case string: - newVal = vStr + newVal = values.Str case *string: - newVal = &vStr + newVal = &values.Str case int: - newVal = vInt + newVal = values.Int case *int: - newVal = &vInt + newVal = &values.Int case float64: - newVal = float64(vInt) + 0.5 + newVal = values.Float case *float64: - val := float64(vInt) + 0.5 + val := values.Float newVal = &val case net.IP: - newVal = vIP + newVal = values.IP case bool: - newVal = vBool + newVal = values.Bool case *bool: - newVal = &vBool + newVal = &values.Bool case http.Header: - newVal = http.Header{vStr: []string{vStr, vStr}} + newVal = values.HTTPHeader case time.Time: - newVal = vTime + newVal = values.Time default: // the populator recursively iterates over struct and structPtr // calling this function for all fields; @@ -308,6 +392,7 @@ func jsonName(f reflect.StructField) string { parts := strings.Split(tag, ",") if len(parts) == 0 { return "" + } return parts[0] } diff --git a/model/modeldecoder/rumv3/decoder.go b/model/modeldecoder/rumv3/decoder.go index 5c413801a8b..5b74c54ede7 100644 --- a/model/modeldecoder/rumv3/decoder.go +++ b/model/modeldecoder/rumv3/decoder.go @@ -78,7 +78,7 @@ func DecodeNestedTransaction(d decoder.Decoder, input *modeldecoder.Input, out * if err := root.validate(); err != nil { return fmt.Errorf("validation error %w", err) } - mapToTransactionModel(&root.Transaction, &input.Metadata, input.RequestTime, input.Config.Experimental, out) + mapToTransactionModel(&root.Transaction, &input.Metadata, input.RequestTime, input.Config, out) return nil } @@ -152,7 +152,7 @@ func mapToMetadataModel(m *metadata, out *model.Metadata) { } } -func mapToTransactionModel(t *transaction, metadata *model.Metadata, reqTime time.Time, experimental bool, out *model.Transaction) { +func mapToTransactionModel(t *transaction, metadata *model.Metadata, reqTime time.Time, config modeldecoder.Config, out *model.Transaction) { if t == nil { return } @@ -377,7 +377,7 @@ func mapToTransactionModel(t *transaction, metadata *model.Metadata, reqTime tim out.Custom = &custom } } - if experimental { + if config.Experimental { out.Experimental = t.Experimental.Val } } diff --git a/model/modeldecoder/rumv3/metadata_test.go b/model/modeldecoder/rumv3/metadata_test.go index 4a186c8ec59..150ec85618d 100644 --- a/model/modeldecoder/rumv3/metadata_test.go +++ b/model/modeldecoder/rumv3/metadata_test.go @@ -18,9 +18,10 @@ package rumv3 import ( + "fmt" + "net" "strings" "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -31,6 +32,18 @@ import ( "github.com/elastic/beats/v7/libbeat/common" ) +// initializedMetadata returns a metadata model populated with default values +func initializedMetadata() *model.Metadata { + var input metadata + var out model.Metadata + modeldecodertest.SetStructValues(&input, modeldecodertest.DefaultValues()) + mapToMetadataModel(&input, &out) + // initialize values that are not set by input + out.UserAgent = model.UserAgent{Name: "init", Original: "init"} + out.Client.IP = net.ParseIP("127.0.0.1") + return &out +} + func TestMetadataResetModelOnRelease(t *testing.T) { inp := `{"m":{"se":{"n":"service-a"}}}` m := fetchMetadataRoot() @@ -66,39 +79,72 @@ func TestDecodeNestedMetadata(t *testing.T) { } func TestDecodeMetadataMappingToModel(t *testing.T) { - expected := func(s string) model.Metadata { - return model.Metadata{ + expected := func(s string, ip net.IP, n int) *model.Metadata { + labels := common.MapStr{} + for i := 0; i < n; i++ { + labels.Put(fmt.Sprintf("%s%v", s, i), s) + } + return &model.Metadata{ Service: model.Service{Name: s, Version: s, Environment: s, Agent: model.Agent{Name: s, Version: s}, Language: model.Language{Name: s, Version: s}, Runtime: model.Runtime{Name: s, Version: s}, Framework: model.Framework{Name: s, Version: s}}, User: model.User{Name: s, Email: s, ID: s}, - Labels: common.MapStr{s: s}, + Labels: labels, + // these values are not set from http headers and + // are not expected change with updated input data + UserAgent: model.UserAgent{Original: "init", Name: "init"}, + Client: model.Client{IP: net.ParseIP("127.0.0.1")}, } } - // setup: - // create initialized modeldecoder and empty model metadata - // map modeldecoder to model metadata and manually set - // enhanced data that are never set by the modeldecoder - var m metadata - modeldecodertest.SetStructValues(&m, "init", 5000, false, time.Now()) - var modelM model.Metadata - mapToMetadataModel(&m, &modelM) - // iterate through model and assert values are set - assert.Equal(t, expected("init"), modelM) - - // overwrite model metadata with specified Values - // then iterate through model and assert values are overwritten - modeldecodertest.SetStructValues(&m, "overwritten", 12, false, time.Now()) - mapToMetadataModel(&m, &modelM) - assert.Equal(t, expected("overwritten"), modelM) - - // map an empty modeldecoder metadata to the model - // and assert values are unchanged - modeldecodertest.SetZeroStructValues(&m) - mapToMetadataModel(&m, &modelM) - assert.Equal(t, expected("overwritten"), modelM) + t.Run("overwrite", func(t *testing.T) { + // setup: + // create initialized modeldecoder and empty model metadata + // map modeldecoder to model metadata and manually set + // enhanced data that are never set by the modeldecoder + out := initializedMetadata() + // iterate through model and assert values are set + defaultVal := modeldecodertest.DefaultValues() + assert.Equal(t, expected(defaultVal.Str, defaultVal.IP, defaultVal.N), out) + + // overwrite model metadata with specified Values + // then iterate through model and assert values are overwritten + var input metadata + otherVal := modeldecodertest.NonDefaultValues() + modeldecodertest.SetStructValues(&input, otherVal) + mapToMetadataModel(&input, out) + assert.Equal(t, expected(otherVal.Str, otherVal.IP, otherVal.N), out) + // map an empty modeldecoder metadata to the model + // and assert values are unchanged + input.Reset() + modeldecodertest.SetZeroStructValues(&input) + mapToMetadataModel(&input, out) + assert.Equal(t, expected(otherVal.Str, otherVal.IP, otherVal.N), out) + }) + + t.Run("reused-memory", func(t *testing.T) { + var input metadata + var out1, out2 model.Metadata + defaultVal := modeldecodertest.DefaultValues() + modeldecodertest.SetStructValues(&input, defaultVal) + mapToMetadataModel(&input, &out1) + // initialize values that are not set by input + out1.UserAgent = model.UserAgent{Name: "init", Original: "init"} + out1.Client.IP = net.ParseIP("127.0.0.1") + assert.Equal(t, expected(defaultVal.Str, defaultVal.IP, defaultVal.N), &out1) + + // overwrite model metadata with specified Values + // then iterate through model and assert values are overwritten + otherVal := modeldecodertest.NonDefaultValues() + input.Reset() + modeldecodertest.SetStructValues(&input, otherVal) + mapToMetadataModel(&input, &out2) + out2.UserAgent = model.UserAgent{Name: "init", Original: "init"} + out2.Client.IP = net.ParseIP("127.0.0.1") + assert.Equal(t, expected(otherVal.Str, otherVal.IP, otherVal.N), &out2) + assert.Equal(t, expected(defaultVal.Str, defaultVal.IP, defaultVal.N), &out1) + }) } diff --git a/model/modeldecoder/rumv3/model_test.go b/model/modeldecoder/rumv3/model_test.go index 276ef95e6bc..dd89d1215cd 100644 --- a/model/modeldecoder/rumv3/model_test.go +++ b/model/modeldecoder/rumv3/model_test.go @@ -175,6 +175,7 @@ func TestTransactionRequiredValidationRules(t *testing.T) { // setup: create full metadata struct with arbitrary values set var event transaction modeldecodertest.InitStructValues(&event) + event.Outcome.Set("success") // test vanilla struct is valid require.NoError(t, event.validate()) diff --git a/model/modeldecoder/rumv3/transaction_test.go b/model/modeldecoder/rumv3/transaction_test.go index 3fbfeba6c7a..ce75e2bbf74 100644 --- a/model/modeldecoder/rumv3/transaction_test.go +++ b/model/modeldecoder/rumv3/transaction_test.go @@ -77,7 +77,6 @@ func TestDecodeNestedTransaction(t *testing.T) { func TestDecodeMapToTransactionModel(t *testing.T) { localhostIP := net.ParseIP("127.0.0.1") - gatewayIP := net.ParseIP("192.168.0.1") exceptions := func(key string) bool { // values not set for rumV3: for _, k := range []string{"Cloud", "System", "Process", "Service.Node", "Node"} { @@ -94,47 +93,36 @@ func TestDecodeMapToTransactionModel(t *testing.T) { return false } - initializedMeta := func() *model.Metadata { - var inputMeta metadata - var meta model.Metadata - modeldecodertest.SetStructValues(&inputMeta, "meta", 1, false, time.Now()) - mapToMetadataModel(&inputMeta, &meta) - // initialize values that are not set by input - meta.UserAgent = model.UserAgent{Name: "meta", Original: "meta"} - meta.Client.IP = localhostIP - return &meta - } - - t.Run("set-metadata", func(t *testing.T) { + t.Run("metadata-set", func(t *testing.T) { // do not overwrite metadata with zero transaction values - var inputTr transaction - var tr model.Transaction - mapToTransactionModel(&inputTr, initializedMeta(), time.Now(), true, &tr) + var input transaction + var out model.Transaction + mapToTransactionModel(&input, initializedMetadata(), time.Now(), modeldecoder.Config{}, &out) // iterate through metadata model and assert values are set - modeldecodertest.AssertStructValues(t, &tr.Metadata, exceptions, "meta", 1, false, localhostIP, time.Now()) + modeldecodertest.AssertStructValues(t, &out.Metadata, exceptions, modeldecodertest.DefaultValues()) }) - t.Run("overwrite-metadata", func(t *testing.T) { + t.Run("metadata-overwrite", func(t *testing.T) { // overwrite defined metadata with transaction metadata values - var inputTr transaction - var tr model.Transaction - modeldecodertest.SetStructValues(&inputTr, "overwritten", 5000, false, time.Now()) - inputTr.Context.Request.Headers.Val.Add("user-agent", "first") - inputTr.Context.Request.Headers.Val.Add("user-agent", "second") - inputTr.Context.Request.Headers.Val.Add("x-real-ip", gatewayIP.String()) - mapToTransactionModel(&inputTr, initializedMeta(), time.Now(), true, &tr) + var input transaction + var out model.Transaction + otherVal := modeldecodertest.NonDefaultValues() + modeldecodertest.SetStructValues(&input, otherVal) + mapToTransactionModel(&input, initializedMetadata(), time.Now(), modeldecoder.Config{}, &out) // user-agent should be set to context request header values - assert.Equal(t, "first, second", tr.Metadata.UserAgent.Original) + assert.Equal(t, "d, e", out.Metadata.UserAgent.Original) // do not overwrite client.ip if already set in metadata - assert.Equal(t, localhostIP, tr.Metadata.Client.IP, tr.Metadata.Client.IP.String()) + assert.Equal(t, localhostIP, out.Metadata.Client.IP, out.Metadata.Client.IP.String()) // metadata labels and transaction labels should not be merged - assert.Equal(t, common.MapStr{"meta": "meta"}, tr.Metadata.Labels) - assert.Equal(t, &model.Labels{"overwritten": "overwritten"}, tr.Labels) + mLabels := common.MapStr{"init0": "init", "init1": "init", "init2": "init"} + assert.Equal(t, mLabels, out.Metadata.Labels) + tLabels := model.Labels{"overwritten0": "overwritten", "overwritten1": "overwritten"} + assert.Equal(t, &tLabels, out.Labels) // service values should be set - modeldecodertest.AssertStructValues(t, &tr.Metadata.Service, exceptions, "overwritten", 100, true, localhostIP, time.Now()) + modeldecodertest.AssertStructValues(t, &out.Metadata.Service, exceptions, otherVal) // user values should be set - modeldecodertest.AssertStructValues(t, &tr.Metadata.User, exceptions, "overwritten", 100, true, localhostIP, time.Now()) + modeldecodertest.AssertStructValues(t, &out.Metadata.User, exceptions, otherVal) }) t.Run("overwrite-user", func(t *testing.T) { @@ -142,13 +130,13 @@ func TestDecodeMapToTransactionModel(t *testing.T) { var inputTr transaction var tr model.Transaction inputTr.Context.User.Email.Set("test@user.com") - mapToTransactionModel(&inputTr, initializedMeta(), time.Now(), false, &tr) + mapToTransactionModel(&inputTr, initializedMetadata(), time.Now(), modeldecoder.Config{}, &tr) assert.Equal(t, "test@user.com", tr.Metadata.User.Email) assert.Zero(t, tr.Metadata.User.ID) assert.Zero(t, tr.Metadata.User.Name) }) - t.Run("other-transaction-values", func(t *testing.T) { + t.Run("transaction-values", func(t *testing.T) { exceptions := func(key string) bool { // metadata are tested separately // Page.URL parts are derived from url (separately tested) @@ -163,19 +151,30 @@ func TestDecodeMapToTransactionModel(t *testing.T) { return false } - var inputTr transaction - var tr model.Transaction - eventTime, reqTime := time.Now(), time.Now().Add(time.Second) - modeldecodertest.SetStructValues(&inputTr, "overwritten", 5000, true, eventTime) - mapToTransactionModel(&inputTr, initializedMeta(), reqTime, true, &tr) - modeldecodertest.AssertStructValues(t, &tr, exceptions, "overwritten", 5000, true, localhostIP, reqTime) + var input transaction + var out1, out2 model.Transaction + reqTime := time.Now().Add(time.Second) + defaultVal := modeldecodertest.DefaultValues() + modeldecodertest.SetStructValues(&input, defaultVal) + mapToTransactionModel(&input, initializedMetadata(), reqTime, modeldecoder.Config{}, &out1) + input.Reset() + defaultVal.Update(reqTime) //for rumv3 the timestamp is always set from the request time + modeldecodertest.AssertStructValues(t, &out1, exceptions, defaultVal) + + // ensure memory is not shared by reusing input model + otherVal := modeldecodertest.NonDefaultValues() + otherVal.Update(reqTime) //for rumv3 the timestamp is always set from the request time + modeldecodertest.SetStructValues(&input, otherVal) + mapToTransactionModel(&input, initializedMetadata(), reqTime, modeldecoder.Config{Experimental: true}, &out2) + modeldecodertest.AssertStructValues(t, &out2, exceptions, otherVal) + modeldecodertest.AssertStructValues(t, &out1, exceptions, defaultVal) }) t.Run("page.URL", func(t *testing.T) { var inputTr transaction inputTr.Context.Page.URL.Set("https://my.site.test:9201") var tr model.Transaction - mapToTransactionModel(&inputTr, initializedMeta(), time.Now(), false, &tr) + mapToTransactionModel(&inputTr, initializedMetadata(), time.Now(), modeldecoder.Config{}, &tr) assert.Equal(t, "https://my.site.test:9201", *tr.Page.URL.Full) assert.Equal(t, 9201, *tr.Page.URL.Port) assert.Equal(t, "https", *tr.Page.URL.Scheme) diff --git a/model/modeldecoder/v2/decoder.go b/model/modeldecoder/v2/decoder.go index dfd08f33b2b..d6faaf40abf 100644 --- a/model/modeldecoder/v2/decoder.go +++ b/model/modeldecoder/v2/decoder.go @@ -940,10 +940,12 @@ func mapToStracktraceModel(from []stacktraceFrame, out model.Stacktrace) { fr.Module = &val } if len(eventFrame.PostContext) > 0 { - fr.PostContext = eventFrame.PostContext + fr.PostContext = make([]string, len(eventFrame.PostContext)) + copy(fr.PostContext, eventFrame.PostContext) } if len(eventFrame.PreContext) > 0 { - fr.PreContext = eventFrame.PreContext + fr.PreContext = make([]string, len(eventFrame.PreContext)) + copy(fr.PreContext, eventFrame.PreContext) } if len(eventFrame.Vars) > 0 { fr.Vars = eventFrame.Vars.Clone() @@ -974,7 +976,7 @@ func mapToTransactionModel(from *transaction, metadata *model.Metadata, reqTime if config.Experimental && from.Context.Experimental.IsSet() { out.Experimental = from.Context.Experimental.Val } - // metadata labels and context labels are merged only in the output model + // metadata labels and context labels are merged when transforming the output model if len(from.Context.Tags) > 0 { labels := model.Labels(from.Context.Tags.Clone()) out.Labels = &labels diff --git a/model/modeldecoder/v2/error_test.go b/model/modeldecoder/v2/error_test.go index 12e1c26854d..27cea6af69f 100644 --- a/model/modeldecoder/v2/error_test.go +++ b/model/modeldecoder/v2/error_test.go @@ -78,53 +78,44 @@ func TestDecodeNestedError(t *testing.T) { } func TestDecodeMapToErrorModel(t *testing.T) { - localhostIP := net.ParseIP("127.0.0.1") gatewayIP := net.ParseIP("192.168.0.1") randomIP := net.ParseIP("71.0.54.1") exceptions := func(key string) bool { return false } - initializedMeta := func() *model.Metadata { - var inputMeta metadata - var meta model.Metadata - modeldecodertest.SetStructValues(&inputMeta, "meta", 1, false, time.Now()) - mapToMetadataModel(&inputMeta, &meta) - // initialize values that are not set by input - meta.UserAgent = model.UserAgent{Name: "meta", Original: "meta"} - meta.Client.IP = localhostIP - meta.System.IP = localhostIP - return &meta - } - - t.Run("set-metadata", func(t *testing.T) { - // do not overwrite metadata with zero transaction values + t.Run("metadata-set", func(t *testing.T) { + // do not overwrite metadata with zero event values var input errorEvent var out model.Error - mapToErrorModel(&input, initializedMeta(), time.Now(), modeldecoder.Config{}, &out) + mapToErrorModel(&input, initializedMetadata(), time.Now(), modeldecoder.Config{}, &out) // iterate through metadata model and assert values are set - modeldecodertest.AssertStructValues(t, &out.Metadata, exceptions, "meta", 1, false, localhostIP, time.Now()) + defaultVal := modeldecodertest.DefaultValues() + modeldecodertest.AssertStructValues(t, &out.Metadata, exceptions, defaultVal) }) - t.Run("overwrite-metadata", func(t *testing.T) { - // overwrite defined metadata with transaction metadata values + t.Run("metadata-overwrite", func(t *testing.T) { + // overwrite defined metadata with event metadata values var input errorEvent var out model.Error - modeldecodertest.SetStructValues(&input, "overwritten", 5000, false, time.Now()) - input.Context.Request.Headers.Val.Add("user-agent", "first") - input.Context.Request.Headers.Val.Add("user-agent", "second") - input.Context.Request.Headers.Val.Add("x-real-ip", gatewayIP.String()) - mapToErrorModel(&input, initializedMeta(), time.Now(), modeldecoder.Config{}, &out) + otherVal := modeldecodertest.NonDefaultValues() + modeldecodertest.SetStructValues(&input, otherVal) + mapToErrorModel(&input, initializedMetadata(), time.Now(), modeldecoder.Config{Experimental: true}, &out) + input.Reset() - // user-agent should be set to context request header values - assert.Equal(t, "first, second", out.Metadata.UserAgent.Original) + // ensure event Metadata are updated where expected + otherVal = modeldecodertest.NonDefaultValues() + userAgent := strings.Join(otherVal.HTTPHeader.Values("User-Agent"), ", ") + assert.Equal(t, userAgent, out.Metadata.UserAgent.Original) // do not overwrite client.ip if already set in metadata - assert.Equal(t, localhostIP, out.Metadata.Client.IP, out.Metadata.Client.IP.String()) - // metadata labels and transaction labels should not be merged - assert.Equal(t, common.MapStr{"meta": "meta"}, out.Metadata.Labels) - assert.Equal(t, &model.Labels{"overwritten": "overwritten"}, out.Labels) - // service values should be set - modeldecodertest.AssertStructValues(t, &out.Metadata.Service, exceptions, "overwritten", 100, true, localhostIP, time.Now()) - // user values should be set - modeldecodertest.AssertStructValues(t, &out.Metadata.User, exceptions, "overwritten", 100, true, localhostIP, time.Now()) + ip := modeldecodertest.DefaultValues().IP + assert.Equal(t, ip, out.Metadata.Client.IP, out.Metadata.Client.IP.String()) + // metadata labels and event labels should not be merged + mLabels := common.MapStr{"init0": "init", "init1": "init", "init2": "init"} + tLabels := model.Labels{"overwritten0": "overwritten", "overwritten1": "overwritten"} + assert.Equal(t, mLabels, out.Metadata.Labels) + assert.Equal(t, &tLabels, out.Labels) + // service and user values should be set + modeldecodertest.AssertStructValues(t, &out.Metadata.Service, exceptions, otherVal) + modeldecodertest.AssertStructValues(t, &out.Metadata.User, exceptions, otherVal) }) t.Run("client-ip-header", func(t *testing.T) { @@ -147,15 +138,23 @@ func TestDecodeMapToErrorModel(t *testing.T) { t.Run("error-values", func(t *testing.T) { exceptions := func(key string) bool { - // metadata are tested separately - // URL parts are derived from url (separately tested) - // exception.parent is only set after calling `flattenExceptionTree` (not part of decoding) - // experimental is tested separately - // stacktrace original values are set when sourcemapping is applied, not in the decoder - // ExcludeFromGrouping is set when processing the event, not in the decoder - for _, s := range []string{"Metadata", "Page.URL", "Exception.Parent", "RUM", - "Exception.Stacktrace.Original", "Exception.Stacktrace.Sourcemap", "Exception.Stacktrace.ExcludeFromGrouping", - "Log.Stacktrace.Original", "Log.Stacktrace.Sourcemap", "Log.Stacktrace.ExcludeFromGrouping"} { + for _, s := range []string{ + // metadata are tested separately + "Metadata", + // URL parts are derived from url (separately tested) + "Page.URL", + // RUM is set in stream processor + "RUM", + // exception.parent is only set after calling `flattenExceptionTree` (not part of decoding) + "Exception.Parent", + // stacktrace original and sourcemap values are set when sourcemapping is applied + "Exception.Stacktrace.Original", + "Exception.Stacktrace.Sourcemap", + "Log.Stacktrace.Original", + "Log.Stacktrace.Sourcemap", + // ExcludeFromGrouping is set when processing the event + "Exception.Stacktrace.ExcludeFromGrouping", + "Log.Stacktrace.ExcludeFromGrouping"} { if strings.HasPrefix(key, s) { return true } @@ -163,20 +162,36 @@ func TestDecodeMapToErrorModel(t *testing.T) { return false } var input errorEvent - var out model.Error - eventTime, reqTime := time.Now(), time.Now().Add(time.Second) - modeldecodertest.SetStructValues(&input, "overwritten", 5000, true, eventTime) - mapToErrorModel(&input, initializedMeta(), reqTime, modeldecoder.Config{Experimental: true}, &out) + var out1, out2 model.Error + reqTime := time.Now().Add(time.Second) + defaultVal := modeldecodertest.DefaultValues() + modeldecodertest.SetStructValues(&input, defaultVal) + mapToErrorModel(&input, initializedMetadata(), reqTime, modeldecoder.Config{Experimental: true}, &out1) + input.Reset() + modeldecodertest.AssertStructValues(t, &out1, exceptions, defaultVal) + + // set Timestamp to requestTime if eventTime is zero + defaultVal.Update(time.Time{}) + modeldecodertest.SetStructValues(&input, defaultVal) + mapToErrorModel(&input, initializedMetadata(), reqTime, modeldecoder.Config{Experimental: true}, &out1) + defaultVal.Update(reqTime) input.Reset() - modeldecodertest.AssertStructValues(t, &out, exceptions, "overwritten", 5000, true, localhostIP, eventTime) - assert.False(t, out.RUM) + modeldecodertest.AssertStructValues(t, &out1, exceptions, defaultVal) + + // reuse input model for different event + // ensure memory is not shared by reusing input model + otherVal := modeldecodertest.NonDefaultValues() + modeldecodertest.SetStructValues(&input, otherVal) + mapToErrorModel(&input, initializedMetadata(), reqTime, modeldecoder.Config{Experimental: true}, &out2) + modeldecodertest.AssertStructValues(t, &out2, exceptions, otherVal) + modeldecodertest.AssertStructValues(t, &out1, exceptions, defaultVal) }) t.Run("page.URL", func(t *testing.T) { var input errorEvent input.Context.Page.URL.Set("https://my.site.test:9201") var out model.Error - mapToErrorModel(&input, initializedMeta(), time.Now(), modeldecoder.Config{}, &out) + mapToErrorModel(&input, initializedMetadata(), time.Now(), modeldecoder.Config{}, &out) assert.Equal(t, "https://my.site.test:9201", *out.Page.URL.Full) assert.Equal(t, 9201, *out.Page.URL.Port) assert.Equal(t, "https", *out.Page.URL.Scheme) diff --git a/model/modeldecoder/v2/metadata_test.go b/model/modeldecoder/v2/metadata_test.go index 999db60e2e4..ea549d6b37f 100644 --- a/model/modeldecoder/v2/metadata_test.go +++ b/model/modeldecoder/v2/metadata_test.go @@ -21,7 +21,6 @@ import ( "net" "strings" "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -31,6 +30,18 @@ import ( "github.com/elastic/apm-server/model/modeldecoder/modeldecodertest" ) +// initializedMetadata returns a metadata model with +func initializedMetadata() *model.Metadata { + var input metadata + var out model.Metadata + modeldecodertest.SetStructValues(&input, modeldecodertest.DefaultValues()) + mapToMetadataModel(&input, &out) + // initialize values that are not set by input + out.UserAgent = model.UserAgent{Name: "init", Original: "init"} + out.Client.IP = net.ParseIP("127.0.0.1") + out.System.IP = net.ParseIP("127.0.0.1") + return &out +} func TestResetMetadataOnRelease(t *testing.T) { inp := `{"metadata":{"service":{"name":"service-a"}}}` m := fetchMetadataRoot() @@ -75,33 +86,69 @@ func TestDecodeMetadata(t *testing.T) { } func TestDecodeMapToMetadataModel(t *testing.T) { - // setup: - // create initialized modeldecoder and empty model metadata - // map modeldecoder to model metadata and manually set - // enhanced data that are never set by the modeldecoder - var m metadata - modeldecodertest.SetStructValues(&m, "init", 5000, false, time.Now()) - var modelM model.Metadata - ip := net.ParseIP("127.0.0.1") - modelM.System.IP, modelM.Client.IP = ip, ip - mapToMetadataModel(&m, &modelM) - - exceptions := func(key string) bool { - return strings.HasPrefix(key, "UserAgent") - } - // iterate through model and assert values are set - modeldecodertest.AssertStructValues(t, &modelM, exceptions, "init", 5000, false, ip, time.Now()) + t.Run("overwrite", func(t *testing.T) { + // setup: + // create initialized modeldecoder and empty model metadata + // map modeldecoder to model metadata and manually set + // enhanced data that are never set by the modeldecoder + var input metadata + var out model.Metadata + defaultVal := modeldecodertest.DefaultValues() + modeldecodertest.SetStructValues(&input, defaultVal) + out.System.IP, out.Client.IP = defaultVal.IP, defaultVal.IP + mapToMetadataModel(&input, &out) + + exceptions := func(key string) bool { + return strings.HasPrefix(key, "UserAgent") + } + // iterate through model and assert values are set + modeldecodertest.AssertStructValues(t, &out, exceptions, defaultVal) + + // overwrite model metadata with specified Values + // then iterate through model and assert values are overwritten + otherVal := modeldecodertest.NonDefaultValues() + // System.IP and Client.IP are not set by decoder, + // therefore their values are not updated + otherVal.Update(defaultVal.IP) + input.Reset() + modeldecodertest.SetStructValues(&input, otherVal) + mapToMetadataModel(&input, &out) + modeldecodertest.AssertStructValues(t, &out, exceptions, otherVal) + + // map an empty modeldecoder metadata to the model + // and assert values are unchanged + input.Reset() + modeldecodertest.SetZeroStructValues(&input) + mapToMetadataModel(&input, &out) + modeldecodertest.AssertStructValues(t, &out, exceptions, otherVal) + }) + + t.Run("reused-memory", func(t *testing.T) { + var input metadata + var out1, out2 model.Metadata + defaultVal := modeldecodertest.DefaultValues() + modeldecodertest.SetStructValues(&input, defaultVal) + mapToMetadataModel(&input, &out1) + out1.System.IP, out1.Client.IP = defaultVal.IP, defaultVal.IP //not set by decoder - // overwrite model metadata with specified Values - // then iterate through model and assert values are overwritten - modeldecodertest.SetStructValues(&m, "overwritten", 12, true, time.Now()) - mapToMetadataModel(&m, &modelM) - modeldecodertest.AssertStructValues(t, &modelM, exceptions, "overwritten", 12, true, ip, time.Now()) + exceptions := func(key string) bool { + return strings.HasPrefix(key, "UserAgent") + } + // iterate through model and assert values are set + modeldecodertest.AssertStructValues(t, &out1, exceptions, defaultVal) - // map an empty modeldecoder metadata to the model - // and assert values are unchanged - modeldecodertest.SetZeroStructValues(&m) - mapToMetadataModel(&m, &modelM) - modeldecodertest.AssertStructValues(t, &modelM, exceptions, "overwritten", 12, true, ip, time.Now()) + // overwrite model metadata with specified Values + // then iterate through model and assert values are overwritten + otherVal := modeldecodertest.NonDefaultValues() + // System.IP and Client.IP are not set by decoder, + // therefore their values are not updated + otherVal.Update(defaultVal.IP) + input.Reset() + modeldecodertest.SetStructValues(&input, otherVal) + mapToMetadataModel(&input, &out2) + out2.System.IP, out2.Client.IP = defaultVal.IP, defaultVal.IP + modeldecodertest.AssertStructValues(t, &out2, exceptions, otherVal) + modeldecodertest.AssertStructValues(t, &out1, exceptions, defaultVal) + }) } diff --git a/model/modeldecoder/v2/model_test.go b/model/modeldecoder/v2/model_test.go index 992f5db32d2..394e1278c47 100644 --- a/model/modeldecoder/v2/model_test.go +++ b/model/modeldecoder/v2/model_test.go @@ -517,6 +517,7 @@ func TestSpanRequiredValidationRules(t *testing.T) { // setup: create full struct with arbitrary values set var event span modeldecodertest.InitStructValues(&event) + event.Outcome.Set("failure") // test vanilla struct is valid require.NoError(t, event.validate()) @@ -551,6 +552,7 @@ func TestTransactionRequiredValidationRules(t *testing.T) { // setup: create full struct with arbitrary values set var event transaction modeldecodertest.InitStructValues(&event) + event.Outcome.Set("success") // test vanilla struct is valid require.NoError(t, event.validate()) diff --git a/model/modeldecoder/v2/span_test.go b/model/modeldecoder/v2/span_test.go index 6783a831018..81164762c2a 100644 --- a/model/modeldecoder/v2/span_test.go +++ b/model/modeldecoder/v2/span_test.go @@ -18,7 +18,6 @@ package v2 import ( - "net" "strings" "testing" "time" @@ -63,29 +62,14 @@ func TestDecodeNestedSpan(t *testing.T) { } func TestDecodeMapToSpanModel(t *testing.T) { - ip := net.ParseIP("127.0.0.1") - - initializedMeta := func() *model.Metadata { - var inputMeta metadata - var meta model.Metadata - modeldecodertest.SetStructValues(&inputMeta, "meta", 1, false, time.Now()) - mapToMetadataModel(&inputMeta, &meta) - // initialize values that are not set by input - meta.UserAgent = model.UserAgent{Name: "meta", Original: "meta"} - meta.Client.IP = ip - meta.System.IP = ip - return &meta - } - - t.Run("metadata", func(t *testing.T) { - // do not overwrite metadata with zero span values + t.Run("set-metadata", func(t *testing.T) { exceptions := func(key string) bool { return false } var input span var out model.Span - modeldecodertest.SetStructValues(&input, "overwritten", 5000, true, time.Now().Add(time.Hour)) - mapToSpanModel(&input, initializedMeta(), time.Now(), modeldecoder.Config{}, &out) + mapToSpanModel(&input, initializedMetadata(), time.Now(), modeldecoder.Config{}, &out) // iterate through metadata model and assert values are set - modeldecodertest.AssertStructValues(t, &out.Metadata, exceptions, "meta", 1, false, ip, time.Now()) + defaultVal := modeldecodertest.DefaultValues() + modeldecodertest.AssertStructValues(t, &out.Metadata, exceptions, defaultVal) }) t.Run("experimental", func(t *testing.T) { @@ -109,48 +93,66 @@ func TestDecodeMapToSpanModel(t *testing.T) { t.Run("span-values", func(t *testing.T) { exceptions := func(key string) bool { - // metadata are tested in the 'metadata' test - // experimental is tested in test 'experimental' - // RUM is tested in test 'span-values' - // RepresentativeCount is tested further down in test 'sample-rate' - for _, s := range []string{"Experimental", "RUM", "RepresentativeCount"} { + for _, s := range []string{ + // experimental is tested in test 'experimental' + "Experimental", + // RUM is set in stream processor + "RUM", + // RepresentativeCount is tested further down in test 'sample-rate' + "RepresentativeCount"} { if key == s { return true } } - for _, s := range []string{"Metadata", - "Stacktrace.Original", "Stacktrace.Sourcemap", "Stacktrace.ExcludeFromGrouping"} { + for _, s := range []string{ + //tested in the 'metadata' test + "Metadata", + // stacktrace values are set when sourcemapping is applied + "Stacktrace.Original", + "Stacktrace.Sourcemap", + "Stacktrace.ExcludeFromGrouping"} { if strings.HasPrefix(key, s) { return true } } return false } + var input span - var out model.Span - eventTime, reqTime := time.Now(), time.Now().Add(time.Second) - modeldecodertest.SetStructValues(&input, "overwritten", 5000, true, eventTime) - mapToSpanModel(&input, initializedMeta(), reqTime, modeldecoder.Config{Experimental: true}, &out) + var out1, out2 model.Span + reqTime := time.Now().Add(time.Second) + defaultVal := modeldecodertest.DefaultValues() + modeldecodertest.SetStructValues(&input, defaultVal) + mapToSpanModel(&input, initializedMetadata(), reqTime, modeldecoder.Config{Experimental: true}, &out1) + input.Reset() + modeldecodertest.AssertStructValues(t, &out1, exceptions, defaultVal) + + // reuse input model for different event + // ensure memory is not shared by reusing input model + otherVal := modeldecodertest.NonDefaultValues() + modeldecodertest.SetStructValues(&input, otherVal) + mapToSpanModel(&input, initializedMetadata(), reqTime, modeldecoder.Config{Experimental: true}, &out2) input.Reset() - modeldecodertest.AssertStructValues(t, &out, exceptions, "overwritten", 5000, true, ip, eventTime) - assert.False(t, out.RUM) + modeldecodertest.AssertStructValues(t, &out2, exceptions, otherVal) + modeldecodertest.AssertStructValues(t, &out1, exceptions, defaultVal) }) t.Run("timestamp", func(t *testing.T) { var input span var out model.Span - i := 5000 reqTime := time.Now().Add(time.Hour) // add start to requestTime if eventTime is zero and start is given - modeldecodertest.SetStructValues(&input, "init", i, true, time.Time{}) - mapToSpanModel(&input, initializedMeta(), reqTime, modeldecoder.Config{}, &out) - timestamp := reqTime.Add(time.Duration((float64(i) + 0.5) * float64(time.Millisecond))) + defaultVal := modeldecodertest.DefaultValues() + defaultVal.Update(20.5, time.Time{}) + modeldecodertest.SetStructValues(&input, defaultVal) + mapToSpanModel(&input, initializedMetadata(), reqTime, modeldecoder.Config{}, &out) + timestamp := reqTime.Add(time.Duration((20.5) * float64(time.Millisecond))) assert.Equal(t, timestamp, out.Timestamp) // set requestTime if eventTime is zero and start is not set out = model.Span{} - modeldecodertest.SetStructValues(&input, "init", i, true, time.Time{}) + modeldecodertest.SetStructValues(&input, defaultVal) input.Start.Reset() - mapToSpanModel(&input, initializedMeta(), reqTime, modeldecoder.Config{}, &out) + mapToSpanModel(&input, initializedMetadata(), reqTime, modeldecoder.Config{}, &out) require.Nil(t, out.Start) assert.Equal(t, reqTime, out.Timestamp) }) @@ -158,19 +160,19 @@ func TestDecodeMapToSpanModel(t *testing.T) { t.Run("sample-rate", func(t *testing.T) { var input span var out model.Span - modeldecodertest.SetStructValues(&input, "init", 5000, true, time.Now()) + modeldecodertest.SetStructValues(&input, modeldecodertest.DefaultValues()) // sample rate is set to > 0 input.SampleRate.Set(0.25) - mapToSpanModel(&input, initializedMeta(), time.Now(), modeldecoder.Config{}, &out) + mapToSpanModel(&input, initializedMetadata(), time.Now(), modeldecoder.Config{}, &out) assert.Equal(t, 4.0, out.RepresentativeCount) // sample rate is not set out.RepresentativeCount = 0.0 input.SampleRate.Reset() - mapToSpanModel(&input, initializedMeta(), time.Now(), modeldecoder.Config{}, &out) + mapToSpanModel(&input, initializedMetadata(), time.Now(), modeldecoder.Config{}, &out) assert.Equal(t, 0.0, out.RepresentativeCount) // sample rate is set to 0 input.SampleRate.Set(0) - mapToSpanModel(&input, initializedMeta(), time.Now(), modeldecoder.Config{}, &out) + mapToSpanModel(&input, initializedMetadata(), time.Now(), modeldecoder.Config{}, &out) assert.Equal(t, 0.0, out.RepresentativeCount) }) @@ -195,7 +197,8 @@ func TestDecodeMapToSpanModel(t *testing.T) { } { t.Run(tc.name, func(t *testing.T) { var input span - modeldecodertest.SetStructValues(&input, "init", 5000, true, time.Now()) + defaultVal := modeldecodertest.DefaultValues() + modeldecodertest.SetStructValues(&input, defaultVal) input.Type.Set(tc.inputType) if tc.inputSubtype != "" { input.Subtype.Set(tc.inputSubtype) @@ -208,7 +211,7 @@ func TestDecodeMapToSpanModel(t *testing.T) { input.Action.Reset() } var out model.Span - mapToSpanModel(&input, initializedMeta(), time.Now(), modeldecoder.Config{}, &out) + mapToSpanModel(&input, initializedMetadata(), time.Now(), modeldecoder.Config{}, &out) assert.Equal(t, tc.typ, out.Type) if tc.subtype == "" { assert.Nil(t, out.Subtype) diff --git a/model/modeldecoder/v2/transaction_test.go b/model/modeldecoder/v2/transaction_test.go index 4d9127da39c..f5f7ac2c5bb 100644 --- a/model/modeldecoder/v2/transaction_test.go +++ b/model/modeldecoder/v2/transaction_test.go @@ -79,55 +79,45 @@ func TestDecodeNestedTransaction(t *testing.T) { } func TestDecodeMapToTransactionModel(t *testing.T) { - localhostIP := net.ParseIP("127.0.0.1") gatewayIP := net.ParseIP("192.168.0.1") randomIP := net.ParseIP("71.0.54.1") exceptions := func(key string) bool { return key == "RepresentativeCount" } - initializedMeta := func() *model.Metadata { - var inputMeta metadata - var meta model.Metadata - modeldecodertest.SetStructValues(&inputMeta, "meta", 1, false, time.Now()) - mapToMetadataModel(&inputMeta, &meta) - // initialize values that are not set by input - meta.UserAgent = model.UserAgent{Name: "meta", Original: "meta"} - meta.Client.IP = localhostIP - meta.System.IP = localhostIP - return &meta - } - - t.Run("set-metadata", func(t *testing.T) { - // do not overwrite metadata with zero transaction values + t.Run("metadata-set", func(t *testing.T) { + // do not overwrite metadata with zero event values var input transaction var out model.Transaction - mapToTransactionModel(&input, initializedMeta(), time.Now(), modeldecoder.Config{Experimental: true}, &out) + mapToTransactionModel(&input, initializedMetadata(), time.Now(), modeldecoder.Config{Experimental: true}, &out) // iterate through metadata model and assert values are set - modeldecodertest.AssertStructValues(t, &out.Metadata, exceptions, "meta", 1, false, localhostIP, time.Now()) + modeldecodertest.AssertStructValues(t, &out.Metadata, exceptions, modeldecodertest.DefaultValues()) }) - t.Run("overwrite-metadata", func(t *testing.T) { - // overwrite defined metadata with transaction metadata values + t.Run("metadata-overwrite", func(t *testing.T) { + // overwrite defined metadata with event metadata values var input transaction var out model.Transaction - modeldecodertest.SetStructValues(&input, "overwritten", 5000, false, time.Now()) - input.Context.Request.Headers.Val.Add("user-agent", "first") - input.Context.Request.Headers.Val.Add("user-agent", "second") - input.Context.Request.Headers.Val.Add("x-real-ip", gatewayIP.String()) - mapToTransactionModel(&input, initializedMeta(), time.Now(), modeldecoder.Config{Experimental: true}, &out) + otherVal := modeldecodertest.NonDefaultValues() + modeldecodertest.SetStructValues(&input, otherVal) + mapToTransactionModel(&input, initializedMetadata(), time.Now(), modeldecoder.Config{Experimental: true}, &out) + input.Reset() - // user-agent should be set to context request header values - assert.Equal(t, "first, second", out.Metadata.UserAgent.Original) + // ensure event Metadata are updated where expected + otherVal = modeldecodertest.NonDefaultValues() + userAgent := strings.Join(otherVal.HTTPHeader.Values("User-Agent"), ", ") + assert.Equal(t, userAgent, out.Metadata.UserAgent.Original) // do not overwrite client.ip if already set in metadata - assert.Equal(t, localhostIP, out.Metadata.Client.IP, out.Metadata.Client.IP.String()) - // metadata labels and transaction labels should not be merged - assert.Equal(t, common.MapStr{"meta": "meta"}, out.Metadata.Labels) - assert.Equal(t, &model.Labels{"overwritten": "overwritten"}, out.Labels) - // service values should be set - modeldecodertest.AssertStructValues(t, &out.Metadata.Service, exceptions, "overwritten", 100, true, localhostIP, time.Now()) - // user values should be set - modeldecodertest.AssertStructValues(t, &out.Metadata.User, exceptions, "overwritten", 100, true, localhostIP, time.Now()) + ip := modeldecodertest.DefaultValues().IP + assert.Equal(t, ip, out.Metadata.Client.IP, out.Metadata.Client.IP.String()) + // metadata labels and event labels should not be merged + mLabels := common.MapStr{"init0": "init", "init1": "init", "init2": "init"} + tLabels := model.Labels{"overwritten0": "overwritten", "overwritten1": "overwritten"} + assert.Equal(t, mLabels, out.Metadata.Labels) + assert.Equal(t, &tLabels, out.Labels) + // service and user values should be set + modeldecodertest.AssertStructValues(t, &out.Metadata.Service, exceptions, otherVal) + modeldecodertest.AssertStructValues(t, &out.Metadata.User, exceptions, otherVal) }) t.Run("client-ip-header", func(t *testing.T) { @@ -153,7 +143,7 @@ func TestDecodeMapToTransactionModel(t *testing.T) { var input transaction var out model.Transaction input.Context.User.Email.Set("test@user.com") - mapToTransactionModel(&input, initializedMeta(), time.Now(), modeldecoder.Config{Experimental: false}, &out) + mapToTransactionModel(&input, initializedMetadata(), time.Now(), modeldecoder.Config{Experimental: false}, &out) assert.Equal(t, "test@user.com", out.Metadata.User.Email) assert.Zero(t, out.Metadata.User.ID) assert.Zero(t, out.Metadata.User.Name) @@ -162,38 +152,48 @@ func TestDecodeMapToTransactionModel(t *testing.T) { t.Run("transaction-values", func(t *testing.T) { exceptions := func(key string) bool { // metadata are tested separately - // URL parts are derived from url (separately tested) - // experimental is tested separately - // RepresentativeCount is not set by decoder - if strings.HasPrefix(key, "Metadata") || strings.HasPrefix(key, "Page.URL") || - key == "Experimental" || key == "RepresentativeCount" { + if strings.HasPrefix(key, "Metadata") || + // URL parts are derived from url (separately tested) + strings.HasPrefix(key, "Page.URL") || + // experimental is tested separately + key == "Experimental" || + // RepresentativeCount is not set by decoder + key == "RepresentativeCount" { return true } - return false } var input transaction - var out model.Transaction - eventTime, reqTime := time.Now(), time.Now().Add(time.Second) - modeldecodertest.SetStructValues(&input, "overwritten", 5000, true, eventTime) - mapToTransactionModel(&input, initializedMeta(), reqTime, modeldecoder.Config{Experimental: true}, &out) - modeldecodertest.AssertStructValues(t, &out, exceptions, "overwritten", 5000, true, localhostIP, eventTime) - - // set requestTime if eventTime is zero - modeldecodertest.SetStructValues(&input, "overwritten", 5000, true, time.Time{}) - out = model.Transaction{} - mapToTransactionModel(&input, initializedMeta(), reqTime, modeldecoder.Config{Experimental: true}, &out) + var out1, out2 model.Transaction + reqTime := time.Now().Add(time.Second) + defaultVal := modeldecodertest.DefaultValues() + modeldecodertest.SetStructValues(&input, defaultVal) + mapToTransactionModel(&input, initializedMetadata(), reqTime, modeldecoder.Config{Experimental: true}, &out1) input.Reset() - modeldecodertest.AssertStructValues(t, &out, exceptions, "overwritten", 5000, true, localhostIP, reqTime) + modeldecodertest.AssertStructValues(t, &out1, exceptions, defaultVal) + // set Timestamp to requestTime if eventTime is zero + defaultVal.Update(time.Time{}) + modeldecodertest.SetStructValues(&input, defaultVal) + mapToTransactionModel(&input, initializedMetadata(), reqTime, modeldecoder.Config{Experimental: true}, &out1) + defaultVal.Update(reqTime) + input.Reset() + modeldecodertest.AssertStructValues(t, &out1, exceptions, defaultVal) + + // ensure memory is not shared by reusing input model + otherVal := modeldecodertest.NonDefaultValues() + modeldecodertest.SetStructValues(&input, otherVal) + mapToTransactionModel(&input, initializedMetadata(), reqTime, modeldecoder.Config{Experimental: true}, &out2) + modeldecodertest.AssertStructValues(t, &out2, exceptions, otherVal) + modeldecodertest.AssertStructValues(t, &out1, exceptions, defaultVal) }) t.Run("page.URL", func(t *testing.T) { var input transaction input.Context.Page.URL.Set("https://my.site.test:9201") var out model.Transaction - mapToTransactionModel(&input, initializedMeta(), time.Now(), modeldecoder.Config{Experimental: false}, &out) + mapToTransactionModel(&input, initializedMetadata(), time.Now(), modeldecoder.Config{Experimental: false}, &out) assert.Equal(t, "https://my.site.test:9201", *out.Page.URL.Full) assert.Equal(t, 9201, *out.Page.URL.Port) assert.Equal(t, "https", *out.Page.URL.Scheme) @@ -202,20 +202,20 @@ func TestDecodeMapToTransactionModel(t *testing.T) { t.Run("sample-rate", func(t *testing.T) { var input transaction var out model.Transaction - modeldecodertest.SetStructValues(&input, "init", 5000, true, time.Now()) + modeldecodertest.SetStructValues(&input, modeldecodertest.DefaultValues()) // sample rate is set to > 0 input.SampleRate.Set(0.25) - mapToTransactionModel(&input, initializedMeta(), time.Now(), modeldecoder.Config{}, &out) + mapToTransactionModel(&input, initializedMetadata(), time.Now(), modeldecoder.Config{}, &out) assert.Equal(t, 4.0, out.RepresentativeCount) // sample rate is not set -> Representative Count should be 1 by default out.RepresentativeCount = 0.0 //reset to zero value input.SampleRate.Reset() - mapToTransactionModel(&input, initializedMeta(), time.Now(), modeldecoder.Config{}, &out) + mapToTransactionModel(&input, initializedMetadata(), time.Now(), modeldecoder.Config{}, &out) assert.Equal(t, 1.0, out.RepresentativeCount) // sample rate is set to 0 out.RepresentativeCount = 0.0 //reset to zero value input.SampleRate.Set(0) - mapToTransactionModel(&input, initializedMeta(), time.Now(), modeldecoder.Config{}, &out) + mapToTransactionModel(&input, initializedMetadata(), time.Now(), modeldecoder.Config{}, &out) assert.Equal(t, 0.0, out.RepresentativeCount) })