473 lines
10 KiB
Go
473 lines
10 KiB
Go
|
// 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{}))))
|
||
|
}
|
||
|
}
|
||
|
}
|