Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(coretesting): add test events service. #20579

Merged
merged 8 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion core/testing/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,32 @@ package coretesting
import (
"context"

"google.golang.org/protobuf/runtime/protoiface"

"cosmossdk.io/core/event"
"cosmossdk.io/core/store"
)

type dummyKey struct{}

func Context() context.Context {
dummy := &dummyCtx{
stores: map[string]store.KVStore{},
stores: map[string]store.KVStore{},
events: map[string][]event.Event{},
protoEvents: map[string][]protoiface.MessageV1{},
}

ctx := context.WithValue(context.Background(), dummyKey{}, dummy)
return ctx
}

type dummyCtx struct {
// maps store by the actor.
stores map[string]store.KVStore
// maps event emitted by the actor.
events map[string][]event.Event
Copy link
Member

@aaronc aaronc Jun 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we retain ordering a bit better? i.e. rather than a map how about an array of events + the module they came from

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aaronc currently it is possible to only extract events from the module that emitted them. We could change that ofc if needed.

// maps proto events emitted by the actor.
protoEvents map[string][]protoiface.MessageV1
}

func unwrap(ctx context.Context) *dummyCtx {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove or refactor the unused function unwrap.

The function unwrap is currently unused in the codebase. If it's intended for future use, consider commenting it out with a TODO note. Otherwise, it should be removed to keep the codebase clean.

Tools
golangci-lint

34-34: func unwrap is unused (unused)

Expand Down
50 changes: 50 additions & 0 deletions core/testing/event.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package coretesting

import (
"context"

"google.golang.org/protobuf/runtime/protoiface"

"cosmossdk.io/core/event"
)

var _ event.Service = (*MemEventsService)(nil)

// EventsService attaches an event service to the context.
// Adding an existing module will reset the events.
func EventsService(ctx context.Context, moduleName string) MemEventsService {
unwrap(ctx).events[moduleName] = nil
unwrap(ctx).protoEvents[moduleName] = nil
Comment on lines +16 to +17
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolve the undefined function unwrap.

The function unwrap is called multiple times but is not defined in this file or imported. This will cause runtime errors. Define this function or ensure it is correctly imported.

Also applies to: 26-26, 30-30, 34-34

Tools
golangci-lint

16-16: undefined: unwrap (typecheck)


17-17: undefined: unwrap (typecheck)

return MemEventsService{moduleName: moduleName}
testinginprod marked this conversation as resolved.
Show resolved Hide resolved
}

type MemEventsService struct {
moduleName string
}

func (e MemEventsService) EventManager(ctx context.Context) event.Manager {
return eventManager{moduleName: e.moduleName, ctx: unwrap(ctx)}
}
testinginprod marked this conversation as resolved.
Show resolved Hide resolved

func (e MemEventsService) GetEvents(ctx context.Context) []event.Event {
return unwrap(ctx).events[e.moduleName]
}

func (e MemEventsService) GetProtoEvents(ctx context.Context) []protoiface.MessageV1 {
return unwrap(ctx).protoEvents[e.moduleName]
}

type eventManager struct {
moduleName string
ctx *dummyCtx
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Define or import dummyCtx.

The type dummyCtx is used but not defined or imported in this file. This will cause compilation errors. Ensure that dummyCtx is accessible in this context.

Tools
golangci-lint

39-39: undefined: dummyCtx (typecheck)

}

func (e eventManager) Emit(event protoiface.MessageV1) error {
e.ctx.protoEvents[e.moduleName] = append(e.ctx.protoEvents[e.moduleName], event)
return nil
}

func (e eventManager) EmitKV(eventType string, attrs ...event.Attribute) error {
e.ctx.events[e.moduleName] = append(e.ctx.events[e.moduleName], event.NewEvent(eventType, attrs...))
return nil
}
testinginprod marked this conversation as resolved.
Show resolved Hide resolved
38 changes: 38 additions & 0 deletions core/testing/event_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package coretesting

import (
"testing"

"github.com/stretchr/testify/require"
"google.golang.org/protobuf/runtime/protoiface"
"google.golang.org/protobuf/types/known/wrapperspb"

"cosmossdk.io/core/event"
)

func TestEventsService(t *testing.T) {
ctx := Context()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure Context and EventsService are defined or imported.

The functions Context and EventsService are used but not defined or imported in this file. Ensure they are available to avoid runtime errors.

Also applies to: 15-15, 35-35

Tools
golangci-lint

14-14: undefined: Context (typecheck)

es := EventsService(ctx, "auth")

wantProtoEvent := &wrapperspb.BoolValue{Value: true}
err := es.EventManager(ctx).Emit(wantProtoEvent)
require.NoError(t, err)

wantEvent := event.NewEvent("new-account", event.Attribute{
Key: "number",
Value: "1",
})
err = es.EventManager(ctx).EmitKV(wantEvent.Type, wantEvent.Attributes...)
require.NoError(t, err)

gotProtoEvents := es.GetProtoEvents(ctx)
require.Equal(t, []protoiface.MessageV1{wantProtoEvent}, gotProtoEvents)

gotEvents := es.GetEvents(ctx)
require.Equal(t, []event.Event{wantEvent}, gotEvents)

// test reset
es = EventsService(ctx, "auth")
require.Nil(t, es.GetEvents(ctx))
require.Nil(t, es.GetProtoEvents(ctx))
}
1 change: 1 addition & 0 deletions core/testing/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
cosmossdk.io/core v0.12.0
github.com/stretchr/testify v1.9.0
github.com/tidwall/btree v1.7.0
google.golang.org/protobuf v1.34.1
)

require (
Expand Down
3 changes: 3 additions & 0 deletions core/testing/go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
Expand All @@ -11,6 +12,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI=
github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
40 changes: 20 additions & 20 deletions core/testing/memdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@ const (

var errKeyEmpty = errors.New("key cannot be empty")

var _ store.KVStore = (*memDB)(nil)
var _ store.KVStore = (*MemKV)(nil)

// memDB a lightweight memory db
type memDB struct {
// MemKV a lightweight memory db
type MemKV struct {
tree *btree.BTreeG[item]
}

// newMemDB creates a wrapper around `btree.BTreeG`.
func newMemDB() memDB {
return memDB{
// NewMemKV creates a wrapper around `btree.BTreeG`.
func NewMemKV() MemKV {
return MemKV{
tree: btree.NewBTreeGOptions(byKeys, btree.Options{
Degree: bTreeDegree,
NoLocks: true,
Expand All @@ -35,35 +35,35 @@ func newMemDB() memDB {
}

// set adds a new key-value pair to the change set's tree.
func (bt memDB) set(key, value []byte) {
func (bt MemKV) set(key, value []byte) {
bt.tree.Set(newItem(key, value))
}

// get retrieves the value associated with the given key from the memDB's tree.
func (bt memDB) get(key []byte) (value []byte, found bool) {
// get retrieves the value associated with the given key from the MemKV's tree.
func (bt MemKV) get(key []byte) (value []byte, found bool) {
it, found := bt.tree.Get(item{key: key})
return it.value, found
}

// delete removes the value associated with the given key from the change set.
// If the key does not exist in the change set, this method does nothing.
func (bt memDB) delete(key []byte) {
func (bt MemKV) delete(key []byte) {
bt.tree.Delete(item{key: key})
}

// iterator returns a new iterator over the key-value pairs in the memDB
// iterator returns a new iterator over the key-value pairs in the MemKV
// that have keys greater than or equal to the start key and less than the end key.
func (bt memDB) iterator(start, end []byte) (store.Iterator, error) {
func (bt MemKV) iterator(start, end []byte) (store.Iterator, error) {
if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) {
return nil, errKeyEmpty
}
return newMemIterator(start, end, bt.tree, true), nil
}

// reverseIterator returns a new iterator that iterates over the key-value pairs in reverse order
// within the specified range [start, end) in the memDB's tree.
// within the specified range [start, end) in the MemKV's tree.
// If start or end is an empty byte slice, it returns an error indicating that the key is empty.
func (bt memDB) reverseIterator(start, end []byte) (store.Iterator, error) {
func (bt MemKV) reverseIterator(start, end []byte) (store.Iterator, error) {
if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) {
return nil, errKeyEmpty
}
Expand All @@ -72,31 +72,31 @@ func (bt memDB) reverseIterator(start, end []byte) (store.Iterator, error) {

// KV impl

func (bt memDB) Get(key []byte) ([]byte, error) {
func (bt MemKV) Get(key []byte) ([]byte, error) {
value, _ := bt.get(key)
return value, nil
}

func (bt memDB) Has(key []byte) (bool, error) {
func (bt MemKV) Has(key []byte) (bool, error) {
_, found := bt.get(key)
return found, nil
}

func (bt memDB) Set(key, value []byte) error {
func (bt MemKV) Set(key, value []byte) error {
bt.set(key, value)
return nil
}

func (bt memDB) Delete(key []byte) error {
func (bt MemKV) Delete(key []byte) error {
bt.delete(key)
return nil
}

func (bt memDB) Iterator(start, end []byte) (store.Iterator, error) {
func (bt MemKV) Iterator(start, end []byte) (store.Iterator, error) {
return bt.iterator(start, end)
}

func (bt memDB) ReverseIterator(start, end []byte) (store.Iterator, error) {
func (bt MemKV) ReverseIterator(start, end []byte) (store.Iterator, error) {
return bt.reverseIterator(start, end)
}

Expand Down
2 changes: 1 addition & 1 deletion core/testing/memdb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

func TestMemDB(t *testing.T) {
var db store.KVStore = newMemDB()
var db store.KVStore = NewMemKV()
testinginprod marked this conversation as resolved.
Show resolved Hide resolved

key, value := []byte("key"), []byte("value")
require.NoError(t, db.Set(key, value))
Expand Down
4 changes: 3 additions & 1 deletion core/testing/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import (
"cosmossdk.io/core/store"
)

var _ store.KVStoreService = (*kvStoreService)(nil)

func KVStoreService(ctx context.Context, moduleName string) store.KVStoreService {
unwrap(ctx).stores[moduleName] = newMemDB()
unwrap(ctx).stores[moduleName] = NewMemKV()
testinginprod marked this conversation as resolved.
Show resolved Hide resolved
return kvStoreService{
moduleName: moduleName,
}
Expand Down
12 changes: 6 additions & 6 deletions orm/encoding/ormfield/duration.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ const (

// DurationCodec encodes google.protobuf.Duration values with the following
// encoding:
// - nil is encoded as []byte{0xFF}
// - seconds (which can range from -315,576,000,000 to +315,576,000,000) is encoded as 5 fixed bytes
// - nanos (which can range from 0 to 999,999,999 or -999,999,999 to 0 if seconds is negative) are encoded such
// that 999,999,999 is always added to nanos. This ensures that the encoded nanos are always >= 0. Additionally,
// by adding 999,999,999 to both positive and negative nanos, we guarantee that the lexicographical order is
// preserved when comparing the encoded values of two Durations:
// - nil is encoded as []byte{0xFF}
// - seconds (which can range from -315,576,000,000 to +315,576,000,000) is encoded as 5 fixed bytes
// - nanos (which can range from 0 to 999,999,999 or -999,999,999 to 0 if seconds is negative) are encoded such
// that 999,999,999 is always added to nanos. This ensures that the encoded nanos are always >= 0. Additionally,
// by adding 999,999,999 to both positive and negative nanos, we guarantee that the lexicographical order is
// preserved when comparing the encoded values of two Durations:
// - []byte{0xBB, 0x9A, 0xC9, 0xFF} for zero nanos
// - 4 fixed bytes with the bit mask 0x80 applied to the first byte, with negative nanos scaled so that -999,999,999
// is encoded as 0 and -1 is encoded as 999,999,998
Expand Down
12 changes: 6 additions & 6 deletions orm/encoding/ormfield/timestamp.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import (

// TimestampCodec encodes google.protobuf.Timestamp values with the following
// encoding:
// - nil is encoded as []byte{0xFF}
// - seconds (which can range from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z) is encoded as 5 fixed bytes
// - nanos (which can range from 0 to 999,999,999 or -999,999,999 to 0 if seconds is negative) are encoded such
// that 999,999,999 is always added to nanos. This ensures that the encoded nanos are always >= 0. Additionally,
// by adding 999,999,999 to both positive and negative nanos, we guarantee that the lexicographical order is
// preserved when comparing the encoded values of two Timestamps.
// - nil is encoded as []byte{0xFF}
// - seconds (which can range from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z) is encoded as 5 fixed bytes
// - nanos (which can range from 0 to 999,999,999 or -999,999,999 to 0 if seconds is negative) are encoded such
// that 999,999,999 is always added to nanos. This ensures that the encoded nanos are always >= 0. Additionally,
// by adding 999,999,999 to both positive and negative nanos, we guarantee that the lexicographical order is
// preserved when comparing the encoded values of two Timestamps.
//
// When iterating over timestamp indexes, nil values will always be ordered last.
//
Expand Down
Loading