// 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 common import ( "encoding/json" "testing" "time" "github.com/stretchr/testify/assert" "github.com/elastic/beats/libbeat/logp" ) func TestConvertNestedMapStr(t *testing.T) { logp.TestingSetup() type io struct { Input MapStr Output MapStr } type String string tests := []io{ { Input: MapStr{ "key": MapStr{ "key1": "value1", }, }, Output: MapStr{ "key": MapStr{ "key1": "value1", }, }, }, { Input: MapStr{ "key": MapStr{ "key1": String("value1"), }, }, Output: MapStr{ "key": MapStr{ "key1": "value1", }, }, }, { Input: MapStr{ "key": MapStr{ "key1": []string{"value1", "value2"}, }, }, Output: MapStr{ "key": MapStr{ "key1": []string{"value1", "value2"}, }, }, }, { Input: MapStr{ "key": MapStr{ "key1": []String{"value1", "value2"}, }, }, Output: MapStr{ "key": MapStr{ "key1": []interface{}{"value1", "value2"}, }, }, }, { Input: MapStr{ "@timestamp": MustParseTime("2015-03-01T12:34:56.123Z"), }, Output: MapStr{ "@timestamp": MustParseTime("2015-03-01T12:34:56.123Z"), }, }, { Input: MapStr{ "env": nil, "key2": uintptr(88), "key3": func() { t.Log("hello") }, }, Output: MapStr{}, }, { Input: MapStr{ "key": []MapStr{ {"keyX": []String{"value1", "value2"}}, }, }, Output: MapStr{ "key": []MapStr{ {"keyX": []interface{}{"value1", "value2"}}, }, }, }, { Input: MapStr{ "key": []interface{}{ MapStr{"key1": []string{"value1", "value2"}}, }, }, Output: MapStr{ "key": []interface{}{ MapStr{"key1": []string{"value1", "value2"}}, }, }, }, { MapStr{"k": map[string]int{"hits": 1}}, MapStr{"k": MapStr{"hits": float64(1)}}, }, } for i, test := range tests { assert.Equal(t, test.Output, ConvertToGenericEvent(test.Input), "Test case %d", i) } } func TestConvertNestedStruct(t *testing.T) { logp.TestingSetup() type io struct { Input MapStr Output MapStr } type TestStruct struct { A string B int } tests := []io{ { Input: MapStr{ "key": MapStr{ "key1": TestStruct{ A: "hello", B: 5, }, }, }, Output: MapStr{ "key": MapStr{ "key1": MapStr{ "A": "hello", "B": float64(5), }, }, }, }, { Input: MapStr{ "key": []interface{}{ TestStruct{ A: "hello", B: 5, }, }, }, Output: MapStr{ "key": []interface{}{ MapStr{ "A": "hello", "B": float64(5), }, }, }, }, } for i, test := range tests { assert.EqualValues(t, test.Output, ConvertToGenericEvent(test.Input), "Test case %v", i) } } func TestNormalizeValue(t *testing.T) { logp.TestingSetup() var nilStringPtr *string someString := "foo" type mybool bool type myint int32 type myuint uint8 var tests = []struct { in interface{} out interface{} }{ {nil, nil}, {&someString, someString}, // Pointers are dereferenced. {nilStringPtr, nil}, // Nil pointers are dropped. {NetString("test"), "test"}, // It honors the TextMarshaler contract. {true, true}, {int8(8), int8(8)}, {uint8(8), uint8(8)}, {"hello", "hello"}, {map[string]interface{}{"foo": "bar"}, MapStr{"foo": "bar"}}, // Other map types are converted using marshalUnmarshal which will lose // type information for arrays which become []interface{} and numbers // which all become float64. {map[string]string{"foo": "bar"}, MapStr{"foo": "bar"}}, {map[string][]string{"list": {"foo", "bar"}}, MapStr{"list": []interface{}{"foo", "bar"}}}, {[]string{"foo", "bar"}, []string{"foo", "bar"}}, {[]bool{true, false}, []bool{true, false}}, {[]string{"foo", "bar"}, []string{"foo", "bar"}}, {[]int{10, 11}, []int{10, 11}}, {[]MapStr{{"foo": "bar"}}, []MapStr{{"foo": "bar"}}}, {[]map[string]interface{}{{"foo": "bar"}}, []MapStr{{"foo": "bar"}}}, // Wrapper types are converted to primitives using reflection. {mybool(true), true}, {myint(32), int64(32)}, {myuint(8), uint64(8)}, // Slices of wrapper types are converted to an []interface{} of primitives. {[]mybool{true, false}, []interface{}{true, false}}, {[]myint{32}, []interface{}{int64(32)}}, {[]myuint{8}, []interface{}{uint64(8)}}, } for i, test := range tests { out, err := normalizeValue(test.in) if err != nil { t.Error(err) continue } assert.Equal(t, test.out, out, "Test case %v", i) } var floatTests = []struct { in interface{} out interface{} }{ {float32(1), float64(1)}, {float64(1), float64(1)}, } for i, test := range floatTests { out, err := normalizeValue(test.in) if err != nil { t.Error(err) continue } assert.InDelta(t, test.out, float64(out.(Float)), 0.000001, "(approximate) Test case %v", i) } } func TestNormalizeMapError(t *testing.T) { badInputs := []MapStr{ {"func": func() {}}, {"chan": make(chan struct{})}, {"uintptr": uintptr(123)}, } for i, in := range badInputs { _, errs := normalizeMap(in, "bad.type") if assert.Len(t, errs, 1) { t.Log(errs[0]) assert.Contains(t, errs[0].Error(), "key=bad.type", "Test case %v", i) } } } func TestJoinKeys(t *testing.T) { assert.Equal(t, "", joinKeys("")) assert.Equal(t, "co", joinKeys("co")) assert.Equal(t, "co.elastic", joinKeys("", "co", "elastic")) assert.Equal(t, "co.elastic", joinKeys("co", "elastic")) } func TestMarshalUnmarshalMap(t *testing.T) { tests := []struct { in MapStr out MapStr }{ {MapStr{"names": []string{"a", "b"}}, MapStr{"names": []interface{}{"a", "b"}}}, } for i, test := range tests { var out MapStr err := marshalUnmarshal(test.in, &out) if err != nil { t.Error(err) continue } assert.Equal(t, test.out, out, "Test case %v", i) } } func TestMarshalUnmarshalArray(t *testing.T) { tests := []struct { in interface{} out interface{} }{ {[]string{"a", "b"}, []interface{}{"a", "b"}}, } for i, test := range tests { var out interface{} err := marshalUnmarshal(test.in, &out) if err != nil { t.Error(err) continue } assert.Equal(t, test.out, out, "Test case %v", i) } } func TestMarshalFloatValues(t *testing.T) { assert := assert.New(t) var f float64 f = 5 a := MapStr{ "f": Float(f), } b, err := json.Marshal(a) assert.Nil(err) assert.Equal(string(b), "{\"f\":5.000000}") } func TestNormalizeTime(t *testing.T) { ny, err := time.LoadLocation("America/New_York") if err != nil { t.Fatal(err) } now := time.Now().In(ny) v, errs := normalizeValue(now, "@timestamp") if len(errs) > 0 { t.Fatal(errs) } utcCommonTime, ok := v.(Time) if !ok { t.Fatalf("expected common.Time, but got %T (%v)", v, v) } assert.Equal(t, time.UTC, time.Time(utcCommonTime).Location()) assert.True(t, now.Equal(time.Time(utcCommonTime))) } // Uses TextMarshaler interface. func BenchmarkConvertToGenericEventNetString(b *testing.B) { for i := 0; i < b.N; i++ { ConvertToGenericEvent(MapStr{"key": NetString("hola")}) } } // Uses reflection. func BenchmarkConvertToGenericEventMapStringString(b *testing.B) { for i := 0; i < b.N; i++ { ConvertToGenericEvent(MapStr{"key": map[string]string{"greeting": "hola"}}) } } // Uses recursion to step into the nested MapStr. func BenchmarkConvertToGenericEventMapStr(b *testing.B) { for i := 0; i < b.N; i++ { ConvertToGenericEvent(MapStr{"key": map[string]interface{}{"greeting": "hola"}}) } } // No reflection required. func BenchmarkConvertToGenericEventStringSlice(b *testing.B) { for i := 0; i < b.N; i++ { ConvertToGenericEvent(MapStr{"key": []string{"foo", "bar"}}) } } // Uses reflection to convert the string array. func BenchmarkConvertToGenericEventCustomStringSlice(b *testing.B) { type myString string for i := 0; i < b.N; i++ { ConvertToGenericEvent(MapStr{"key": []myString{"foo", "bar"}}) } } // Pointers require reflection to generically dereference. func BenchmarkConvertToGenericEventStringPointer(b *testing.B) { val := "foo" for i := 0; i < b.N; i++ { ConvertToGenericEvent(MapStr{"key": &val}) } } func TestDeDotJSON(t *testing.T) { var tests = []struct { input []byte output []byte valuer func() interface{} }{ { input: []byte(`[ {"key_with_dot.1":"value1_1"}, {"key_without_dot_2":"value1_2"}, {"key_with_multiple.dots.3": {"key_with_dot.2":"value2_1"}} ] `), output: []byte(`[ {"key_with_dot_1":"value1_1"}, {"key_without_dot_2":"value1_2"}, {"key_with_multiple_dots_3": {"key_with_dot_2":"value2_1"}} ] `), valuer: func() interface{} { return []interface{}{} }, }, { input: []byte(`{ "key_without_dot_l1": { "key_with_dot.l2": 1, "key.with.multiple.dots_l2": 2, "key_without_dot_l2": { "key_with_dot.l3": 3, "key.with.multiple.dots_l3": 4 } } } `), output: []byte(`{ "key_without_dot_l1": { "key_with_dot_l2": 1, "key_with_multiple_dots_l2": 2, "key_without_dot_l2": { "key_with_dot_l3": 3, "key_with_multiple_dots_l3": 4 } } } `), valuer: func() interface{} { return map[string]interface{}{} }, }, } for _, test := range tests { input, output := test.valuer(), test.valuer() assert.Nil(t, json.Unmarshal(test.input, &input)) assert.Nil(t, json.Unmarshal(test.output, &output)) assert.Equal(t, output, DeDotJSON(input)) if _, ok := test.valuer().(map[string]interface{}); ok { assert.Equal(t, MapStr(output.(map[string]interface{})), DeDotJSON(MapStr(input.(map[string]interface{})))) } } }