// 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. // +build !integration package common import ( "encoding/json" "fmt" "strings" "testing" "github.com/stretchr/testify/assert" "go.uber.org/zap/zapcore" "github.com/elastic/beats/libbeat/logp" ) func TestMapStrUpdate(t *testing.T) { assert := assert.New(t) a := MapStr{ "a": 1, "b": 2, } b := MapStr{ "b": 3, "c": 4, } a.Update(b) assert.Equal(a, MapStr{"a": 1, "b": 3, "c": 4}) } func TestMapStrDeepUpdate(t *testing.T) { tests := []struct { a, b, expected MapStr }{ { MapStr{"a": 1}, MapStr{"b": 2}, MapStr{"a": 1, "b": 2}, }, { MapStr{"a": 1}, MapStr{"a": 2}, MapStr{"a": 2}, }, { MapStr{"a": 1}, MapStr{"a": MapStr{"b": 1}}, MapStr{"a": MapStr{"b": 1}}, }, { MapStr{"a": MapStr{"b": 1}}, MapStr{"a": MapStr{"c": 2}}, MapStr{"a": MapStr{"b": 1, "c": 2}}, }, { MapStr{"a": MapStr{"b": 1}}, MapStr{"a": 1}, MapStr{"a": 1}, }, { MapStr{"a.b": 1}, MapStr{"a": 1}, MapStr{"a": 1, "a.b": 1}, }, { MapStr{"a": 1}, MapStr{"a.b": 1}, MapStr{"a": 1, "a.b": 1}, }, } for i, test := range tests { a, b, expected := test.a, test.b, test.expected name := fmt.Sprintf("%v: %v + %v = %v", i, a, b, expected) t.Run(name, func(t *testing.T) { a.DeepUpdate(b) assert.Equal(t, expected, a) }) } } func TestMapStrUnion(t *testing.T) { assert := assert.New(t) a := MapStr{ "a": 1, "b": 2, } b := MapStr{ "b": 3, "c": 4, } c := MapStrUnion(a, b) assert.Equal(c, MapStr{"a": 1, "b": 3, "c": 4}) } func TestMapStrCopyFieldsTo(t *testing.T) { assert := assert.New(t) m := MapStr{ "a": MapStr{ "a1": 2, "a2": 3, }, "b": 2, "c": MapStr{ "c1": 1, "c2": 2, "c3": MapStr{ "c31": 1, "c32": 2, }, }, } c := MapStr{} err := m.CopyFieldsTo(c, "dd") assert.Error(err) assert.Equal(MapStr{}, c) err = m.CopyFieldsTo(c, "a") assert.Equal(nil, err) assert.Equal(MapStr{"a": MapStr{"a1": 2, "a2": 3}}, c) err = m.CopyFieldsTo(c, "c.c1") assert.Equal(nil, err) assert.Equal(MapStr{"a": MapStr{"a1": 2, "a2": 3}, "c": MapStr{"c1": 1}}, c) err = m.CopyFieldsTo(c, "b") assert.Equal(nil, err) assert.Equal(MapStr{"a": MapStr{"a1": 2, "a2": 3}, "c": MapStr{"c1": 1}, "b": 2}, c) err = m.CopyFieldsTo(c, "c.c3.c32") assert.Equal(nil, err) assert.Equal(MapStr{"a": MapStr{"a1": 2, "a2": 3}, "c": MapStr{"c1": 1, "c3": MapStr{"c32": 2}}, "b": 2}, c) } func TestMapStrDelete(t *testing.T) { assert := assert.New(t) m := MapStr{ "c": MapStr{ "c1": 1, "c2": 2, "c3": MapStr{ "c31": 1, "c32": 2, }, }, } err := m.Delete("c.c2") assert.Equal(nil, err) assert.Equal(MapStr{"c": MapStr{"c1": 1, "c3": MapStr{"c31": 1, "c32": 2}}}, m) err = m.Delete("c.c2.c21") assert.NotEqual(nil, err) assert.Equal(MapStr{"c": MapStr{"c1": 1, "c3": MapStr{"c31": 1, "c32": 2}}}, m) err = m.Delete("c.c3.c31") assert.Equal(nil, err) assert.Equal(MapStr{"c": MapStr{"c1": 1, "c3": MapStr{"c32": 2}}}, m) err = m.Delete("c") assert.Equal(nil, err) assert.Equal(MapStr{}, m) } func TestHasKey(t *testing.T) { assert := assert.New(t) m := MapStr{ "c": MapStr{ "c1": 1, "c2": 2, "c3": MapStr{ "c31": 1, "c32": 2, }, "c4.f": 19, }, "d.f": 1, } hasKey, err := m.HasKey("c.c2") assert.Equal(nil, err) assert.Equal(true, hasKey) hasKey, err = m.HasKey("c.c4") assert.Equal(nil, err) assert.Equal(false, hasKey) hasKey, err = m.HasKey("c.c3.c32") assert.Equal(nil, err) assert.Equal(true, hasKey) hasKey, err = m.HasKey("dd") assert.Equal(nil, err) assert.Equal(false, hasKey) hasKey, err = m.HasKey("d.f") assert.Equal(nil, err) assert.Equal(true, hasKey) hasKey, err = m.HasKey("c.c4.f") assert.Equal(nil, err) assert.Equal(true, hasKey) } func TestMapStrPut(t *testing.T) { m := MapStr{ "subMap": MapStr{ "a": 1, }, } // Add new value to the top-level. v, err := m.Put("a", "ok") assert.NoError(t, err) assert.Nil(t, v) assert.Equal(t, MapStr{"a": "ok", "subMap": MapStr{"a": 1}}, m) // Add new value to subMap. v, err = m.Put("subMap.b", 2) assert.NoError(t, err) assert.Nil(t, v) assert.Equal(t, MapStr{"a": "ok", "subMap": MapStr{"a": 1, "b": 2}}, m) // Overwrite a value in subMap. v, err = m.Put("subMap.a", 2) assert.NoError(t, err) assert.Equal(t, 1, v) assert.Equal(t, MapStr{"a": "ok", "subMap": MapStr{"a": 2, "b": 2}}, m) // Add value to map that does not exist. m = MapStr{} v, err = m.Put("subMap.newMap.a", 1) assert.NoError(t, err) assert.Nil(t, v) assert.Equal(t, MapStr{"subMap": MapStr{"newMap": MapStr{"a": 1}}}, m) } func TestMapStrGetValue(t *testing.T) { tests := []struct { input MapStr key string output interface{} error bool }{ { MapStr{"a": 1}, "a", 1, false, }, { MapStr{"a": MapStr{"b": 1}}, "a", MapStr{"b": 1}, false, }, { MapStr{"a": MapStr{"b": 1}}, "a.b", 1, false, }, { MapStr{"a": MapStr{"b.c": 1}}, "a", MapStr{"b.c": 1}, false, }, { MapStr{"a": MapStr{"b.c": 1}}, "a.b", nil, true, }, { MapStr{"a.b": MapStr{"c": 1}}, "a.b", MapStr{"c": 1}, false, }, { MapStr{"a.b": MapStr{"c": 1}}, "a.b.c", nil, true, }, { MapStr{"a": MapStr{"b.c": 1}}, "a.b.c", 1, false, }, } for _, test := range tests { v, err := test.input.GetValue(test.key) if test.error { assert.Error(t, err) } else { assert.NoError(t, err) } assert.Equal(t, test.output, v) } } func TestClone(t *testing.T) { assert := assert.New(t) m := MapStr{ "c1": 1, "c2": 2, "c3": MapStr{ "c31": 1, "c32": 2, }, } c := m.Clone() assert.Equal(MapStr{"c31": 1, "c32": 2}, c["c3"]) } func TestString(t *testing.T) { type io struct { Input MapStr Output string } tests := []io{ { Input: MapStr{ "a": "b", }, Output: `{"a":"b"}`, }, { Input: MapStr{ "a": []int{1, 2, 3}, }, Output: `{"a":[1,2,3]}`, }, } for _, test := range tests { assert.Equal(t, test.Output, test.Input.String()) } } // Smoke test. The method has no observable outputs so this // is only verifying there are no panics. func TestStringToPrint(t *testing.T) { m := MapStr{} assert.Equal(t, "{}", m.StringToPrint()) assert.Equal(t, true, len(m.StringToPrint()) > 0) } func TestMergeFields(t *testing.T) { type io struct { UnderRoot bool Event MapStr Fields MapStr Output MapStr Err string } tests := []io{ // underRoot = true, merges { UnderRoot: true, Event: MapStr{ "a": "1", }, Fields: MapStr{ "b": 2, }, Output: MapStr{ "a": "1", "b": 2, }, }, // underRoot = true, overwrites existing { UnderRoot: true, Event: MapStr{ "a": "1", }, Fields: MapStr{ "a": 2, }, Output: MapStr{ "a": 2, }, }, // underRoot = false, adds new 'fields' when it doesn't exist { UnderRoot: false, Event: MapStr{ "a": "1", }, Fields: MapStr{ "a": 2, }, Output: MapStr{ "a": "1", "fields": MapStr{ "a": 2, }, }, }, // underRoot = false, merge with existing 'fields' and overwrites existing keys { UnderRoot: false, Event: MapStr{ "fields": MapStr{ "a": "1", "b": 2, }, }, Fields: MapStr{ "a": 3, "c": 4, }, Output: MapStr{ "fields": MapStr{ "a": 3, "b": 2, "c": 4, }, }, }, // underRoot = false, error when 'fields' is wrong type { UnderRoot: false, Event: MapStr{ "fields": "not a MapStr", }, Fields: MapStr{ "a": 3, }, Output: MapStr{ "fields": "not a MapStr", }, Err: "expected map", }, } for _, test := range tests { err := MergeFields(test.Event, test.Fields, test.UnderRoot) assert.Equal(t, test.Output, test.Event) if test.Err != "" { assert.Contains(t, err.Error(), test.Err) } else { assert.NoError(t, err) } } } func TestAddTag(t *testing.T) { type io struct { Event MapStr Tags []string Output MapStr Err string } tests := []io{ // No existing tags, creates new tag array { Event: MapStr{}, Tags: []string{"json"}, Output: MapStr{ "tags": []string{"json"}, }, }, // Existing tags is a []string, appends { Event: MapStr{ "tags": []string{"json"}, }, Tags: []string{"docker"}, Output: MapStr{ "tags": []string{"json", "docker"}, }, }, // Existing tags is a []interface{}, appends { Event: MapStr{ "tags": []interface{}{"json"}, }, Tags: []string{"docker"}, Output: MapStr{ "tags": []interface{}{"json", "docker"}, }, }, // Existing tags is not a []string or []interface{} { Event: MapStr{ "tags": "not a slice", }, Tags: []string{"docker"}, Output: MapStr{ "tags": "not a slice", }, Err: "expected string array", }, } for _, test := range tests { err := AddTags(test.Event, test.Tags) assert.Equal(t, test.Output, test.Event) if test.Err != "" { assert.Contains(t, err.Error(), test.Err) } else { assert.NoError(t, err) } } } func TestAddTagsWithKey(t *testing.T) { type io struct { Event MapStr Key string Tags []string Output MapStr Err string } tests := []io{ // No existing tags, creates new tag array { Event: MapStr{}, Key: "tags", Tags: []string{"json"}, Output: MapStr{ "tags": []string{"json"}, }, }, // Existing tags is a []string, appends { Event: MapStr{ "tags": []string{"json"}, }, Key: "tags", Tags: []string{"docker"}, Output: MapStr{ "tags": []string{"json", "docker"}, }, }, // Existing tags are in submap and is a []interface{}, appends { Event: MapStr{ "log": MapStr{ "flags": []interface{}{"json"}, }, }, Key: "log.flags", Tags: []string{"docker"}, Output: MapStr{ "log": MapStr{ "flags": []interface{}{"json", "docker"}, }, }, }, // Existing tags are in a submap and is not a []string or []interface{} { Event: MapStr{ "log": MapStr{ "flags": "not a slice", }, }, Key: "log.flags", Tags: []string{"docker"}, Output: MapStr{ "log": MapStr{ "flags": "not a slice", }, }, Err: "expected string array", }, } for _, test := range tests { err := AddTagsWithKey(test.Event, test.Key, test.Tags) assert.Equal(t, test.Output, test.Event) if test.Err != "" { assert.Contains(t, err.Error(), test.Err) } else { assert.NoError(t, err) } } } func TestFlatten(t *testing.T) { type data struct { Event MapStr Expected MapStr } tests := []data{ { Event: MapStr{ "hello": MapStr{ "world": 15, }, }, Expected: MapStr{ "hello.world": 15, }, }, { Event: MapStr{ "test": 15, }, Expected: MapStr{ "test": 15, }, }, { Event: MapStr{ "test": 15, "hello": MapStr{ "world": MapStr{ "ok": "test", }, }, "elastic": MapStr{ "for": "search", }, }, Expected: MapStr{ "test": 15, "hello.world.ok": "test", "elastic.for": "search", }, }, } for _, test := range tests { assert.Equal(t, test.Expected, test.Event.Flatten()) } } func BenchmarkMapStrFlatten(b *testing.B) { m := MapStr{ "test": 15, "hello": MapStr{ "world": MapStr{ "ok": "test", }, }, "elastic": MapStr{ "for": "search", }, } b.ResetTimer() for i := 0; i < b.N; i++ { _ = m.Flatten() } } // Ensure the MapStr is marshaled in logs the same way it is by json.Marshal. func TestMapStrJSONLog(t *testing.T) { logp.DevelopmentSetup(logp.ToObserverOutput()) m := MapStr{ "test": 15, "hello": MapStr{ "world": MapStr{ "ok": "test", }, }, "elastic": MapStr{ "for": "search", }, } data, err := json.Marshal(MapStr{"m": m}) if err != nil { t.Fatal(err) } expectedJSON := string(data) logp.NewLogger("test").Infow("msg", "m", m) logs := logp.ObserverLogs().TakeAll() if assert.Len(t, logs, 1) { log := logs[0] // Encode like zap does. e := zapcore.NewJSONEncoder(zapcore.EncoderConfig{}) buf, err := e.EncodeEntry(log.Entry, log.Context) if err != nil { t.Fatal(err) } // Zap adds a newline to end the JSON object. actualJSON := strings.TrimSpace(buf.String()) assert.Equal(t, string(expectedJSON), actualJSON) } } func BenchmarkMapStrLogging(b *testing.B) { logp.DevelopmentSetup(logp.ToDiscardOutput()) logger := logp.NewLogger("benchtest") m := MapStr{ "test": 15, "hello": MapStr{ "world": MapStr{ "ok": "test", }, }, "elastic": MapStr{ "for": "search", }, } b.ResetTimer() for i := 0; i < b.N; i++ { logger.Infow("test", "mapstr", m) } } func BenchmarkWalkMap(b *testing.B) { globalM := MapStr{ "hello": MapStr{ "world": MapStr{ "ok": "test", }, }, } b.Run("Get", func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { globalM.GetValue("test.world.ok") } }) b.Run("Put", func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { m := MapStr{ "hello": MapStr{ "world": MapStr{ "ok": "test", }, }, } m.Put("hello.world.new", 17) } }) b.Run("PutMissing", func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { m := MapStr{} m.Put("a.b.c", 17) } }) b.Run("HasKey", func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { globalM.HasKey("hello.world.ok") globalM.HasKey("hello.world.no_ok") } }) b.Run("HasKeyFirst", func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { globalM.HasKey("hello") } }) b.Run("Delete", func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { m := MapStr{ "hello": MapStr{ "world": MapStr{ "ok": "test", }, }, } m.Put("hello.world.test", 17) } }) }