Skip to content

Commit

Permalink
Cherry-pick elastic#19227 to 7.x: Add statestore test helpers and uni…
Browse files Browse the repository at this point in the history
…t tests (elastic#19689)

Add statestore test helpers and unit tests

This change introduces the libbeat/statestore/storetest package and unit
tests to the statestore frontend itself.
The storetest package provides helpers for writing tests. For example
does it emulate a key value store in memory by storing all k/v-pairs
in a map[string]interface{}, that can optionally provided by users.
The internal storecompliance test-suite is used to validate the
storetest package to be fully compatible with the statestore
requirements.

The addition of the statestore package is split up into multiple
changeset to ease review. The final version of the package can be found
[here](https://github.com/urso/beats/tree/fb-input-v2-combined/libbeat/statestore).

Once finalized, the libbeat/statestore package contains:
- The statestore frontend and interface for use within Beats
- Interfaces for the store backend
- A common set of tests store backends need to support
- a storetest package for testing new features that require a store. The
  testing helpers use map[string]interface{} that can be initialized or
  queried after the test run for validation purposes.
- The default memlog backend + tests

This change introduces the second last item to libbeat: test helpers and
additional unit tests.

(cherry picked from commit 372c3ae)
  • Loading branch information
Steffen Siering authored Jul 7, 2020
1 parent db27fe0 commit d2c02aa
Show file tree
Hide file tree
Showing 5 changed files with 695 additions and 0 deletions.
89 changes: 89 additions & 0 deletions libbeat/statestore/mock_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// 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 statestore

import (
"github.com/stretchr/testify/mock"

"github.com/elastic/beats/v7/libbeat/statestore/backend"
)

type mockRegistry struct {
mock.Mock
}

type mockStore struct {
mock.Mock
}

func newMockRegistry() *mockRegistry { return &mockRegistry{} }

func (m *mockRegistry) OnAccess(name string) *mock.Call { return m.On("Access", name) }
func (m *mockRegistry) Access(name string) (backend.Store, error) {
args := m.Called(name)

var store backend.Store
if ifc := args.Get(0); ifc != nil {
store = ifc.(backend.Store)
}

return store, args.Error(1)
}

func (m *mockRegistry) OnClose() *mock.Call { return m.On("Close") }
func (m *mockRegistry) Close() error {
args := m.Called()
return args.Error(0)
}

func newMockStore() *mockStore { return &mockStore{} }

func (m *mockStore) OnClose() *mock.Call { return m.On("Close") }
func (m *mockStore) Close() error {
args := m.Called()
return args.Error(0)
}

func (m *mockStore) OnHas(key string) *mock.Call { return m.On("Has", key) }
func (m *mockStore) Has(key string) (bool, error) {
args := m.Called(key)
return args.Bool(0), args.Error(1)
}

func (m *mockStore) OnGet(key string) *mock.Call { return m.On("Get", key) }
func (m *mockStore) Get(key string, into interface{}) error {
args := m.Called(key)
return args.Error(0)
}

func (m *mockStore) OnRemove(key string) *mock.Call { return m.On("Remove", key) }
func (m *mockStore) Remove(key string) error {
args := m.Called(key)
return args.Error(0)
}

func (m *mockStore) OnSet(key string) *mock.Call { return m.On("Set", key) }
func (m *mockStore) Set(key string, from interface{}) error {
args := m.Called(key)
return args.Error(0)
}

func (m *mockStore) Each(fn func(string, backend.ValueDecoder) (bool, error)) error {
args := m.Called(fn)
return args.Error(0)
}
113 changes: 113 additions & 0 deletions libbeat/statestore/registry_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// 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 statestore

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestAccessStore(t *testing.T) {
t.Run("single access", func(t *testing.T) {
mr := newMockRegistry()
ms := newMockStore()
mr.OnClose().Once().Return(nil)
mr.OnAccess("test").Once().Return(ms, nil)
ms.OnClose().Once().Return(nil)

reg := NewRegistry(mr)
store, _ := reg.Get("test")
assert.NoError(t, store.Close())
assert.NoError(t, reg.Close())

mr.AssertExpectations(t)
ms.AssertExpectations(t)
})

t.Run("shared store instance", func(t *testing.T) {
mr := newMockRegistry()
ms := newMockStore()
mr.OnClose().Once().Return(nil)

// test instance sharing. Store must be opened and closed only once
mr.OnAccess("test").Once().Return(ms, nil)
ms.OnClose().Once().Return(nil)

reg := NewRegistry(mr)
s1, _ := reg.Get("test")
s2, _ := reg.Get("test")
assert.NoError(t, s1.Close())
assert.NoError(t, s2.Close())
assert.NoError(t, reg.Close())

mr.AssertExpectations(t)
ms.AssertExpectations(t)
})

t.Run("close non-shared store needs open", func(t *testing.T) {
mr := newMockRegistry()
ms := newMockStore()
mr.OnClose().Once().Return(nil)

// test instance sharing. Store must be opened and closed only once
mr.OnAccess("test").Twice().Return(ms, nil)
ms.OnClose().Twice().Return(nil)

reg := NewRegistry(mr)

store, err := reg.Get("test")
assert.NoError(t, err)
assert.NoError(t, store.Close())

store, err = reg.Get("test")
assert.NoError(t, err)
assert.NoError(t, store.Close())

assert.NoError(t, reg.Close())

mr.AssertExpectations(t)
ms.AssertExpectations(t)
})

t.Run("separate stores are not shared", func(t *testing.T) {
mr := newMockRegistry()
mr.OnClose().Once().Return(nil)

ms1 := newMockStore()
ms1.OnClose().Once().Return(nil)
mr.OnAccess("s1").Once().Return(ms1, nil)

ms2 := newMockStore()
ms2.OnClose().Once().Return(nil)
mr.OnAccess("s2").Once().Return(ms2, nil)

reg := NewRegistry(mr)
s1, err := reg.Get("s1")
assert.NoError(t, err)
s2, err := reg.Get("s2")
assert.NoError(t, err)
assert.NoError(t, s1.Close())
assert.NoError(t, s2.Close())
assert.NoError(t, reg.Close())

mr.AssertExpectations(t)
ms1.AssertExpectations(t)
ms2.AssertExpectations(t)
})
}
Loading

0 comments on commit d2c02aa

Please sign in to comment.