1
0
mirror of https://github.com/mum4k/termdash.git synced 2025-05-01 22:17:51 +08:00
termdash/event/event_test.go
Jakub Sobon 2ffbe5be94
An event distribution system.
Allows multiple subscribers, supports filtering and doesn't block on
slow subscribers.
2019-02-19 00:12:59 -05:00

246 lines
6.4 KiB
Go

package event
import (
"context"
"fmt"
"image"
"sync"
"testing"
"time"
"github.com/kylelemons/godebug/pretty"
"github.com/mum4k/termdash/keyboard"
"github.com/mum4k/termdash/terminalapi"
)
// receiver receives events from the distribution system.
type receiver struct {
mu sync.Mutex
// events are the received events.
events []terminalapi.Event
}
// newReceiver returns a new event receiver.
func newReceiver() *receiver {
return &receiver{}
}
// receive receives an event.
func (r *receiver) receive(ev terminalapi.Event) {
r.mu.Lock()
defer r.mu.Unlock()
r.events = append(r.events, ev)
}
// getEvents returns the received events.
func (r *receiver) getEvents() map[terminalapi.Event]bool {
r.mu.Lock()
defer r.mu.Unlock()
res := map[terminalapi.Event]bool{}
for _, ev := range r.events {
res[ev] = true
}
return res
}
// waitFor waits until the receiver receives the specified number of events or
// the timeout.
// Returns the received events in an unspecified order.
func (r *receiver) waitFor(want int, timeout time.Duration) (map[terminalapi.Event]bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
tick := time.NewTimer(5 * time.Millisecond)
defer tick.Stop()
for {
select {
case <-tick.C:
ev := r.getEvents()
switch got := len(ev); {
case got > want:
return nil, fmt.Errorf("got %d events %v, want %d", got, ev, want)
case got == want:
return ev, nil
}
case <-ctx.Done():
ev := r.getEvents()
return nil, fmt.Errorf("while waiting for events, got %d so far: %v, err: %v", len(ev), ev, ctx.Err())
}
}
}
// subscriberCase holds test case specifics for one subscriber.
type subscriberCase struct {
// filter is the subscribers filter.
filter []terminalapi.Event
// rec receives the events.
rec *receiver
// want are the expected events that should be delivered to this subscriber.
want map[terminalapi.Event]bool
// wantErr asserts whether we want an error from waitFor.
wantErr bool
}
func TestDistributionSystem(t *testing.T) {
tests := []struct {
desc string
// events will be sent down the distribution system.
events []terminalapi.Event
// subCase are the event subscribers and their expectations.
subCase []*subscriberCase
}{
{
desc: "no events and no subscribers",
},
{
desc: "events and no subscribers",
events: []terminalapi.Event{
&terminalapi.Mouse{},
&terminalapi.Keyboard{},
},
},
{
desc: "single subscriber, wants all events and gets them",
events: []terminalapi.Event{
&terminalapi.Keyboard{Key: keyboard.KeyEnter},
&terminalapi.Mouse{Position: image.Point{1, 1}},
&terminalapi.Resize{Size: image.Point{2, 2}},
terminalapi.NewError("error"),
},
subCase: []*subscriberCase{
{
filter: nil,
rec: newReceiver(),
want: map[terminalapi.Event]bool{
&terminalapi.Keyboard{Key: keyboard.KeyEnter}: true,
&terminalapi.Mouse{Position: image.Point{1, 1}}: true,
&terminalapi.Resize{Size: image.Point{2, 2}}: true,
terminalapi.NewError("error"): true,
},
},
},
},
{
desc: "single subscriber, filters events",
events: []terminalapi.Event{
&terminalapi.Keyboard{Key: keyboard.KeyEnter},
&terminalapi.Mouse{Position: image.Point{1, 1}},
&terminalapi.Resize{Size: image.Point{2, 2}},
},
subCase: []*subscriberCase{
{
filter: []terminalapi.Event{
&terminalapi.Keyboard{},
&terminalapi.Mouse{},
},
rec: newReceiver(),
want: map[terminalapi.Event]bool{
&terminalapi.Keyboard{Key: keyboard.KeyEnter}: true,
&terminalapi.Mouse{Position: image.Point{1, 1}}: true,
},
},
},
},
{
desc: "single subscriber, errors are always received",
events: []terminalapi.Event{
&terminalapi.Keyboard{Key: keyboard.KeyEnter},
&terminalapi.Mouse{Position: image.Point{1, 1}},
&terminalapi.Resize{Size: image.Point{2, 2}},
terminalapi.NewError("error"),
},
subCase: []*subscriberCase{
{
filter: []terminalapi.Event{
&terminalapi.Keyboard{},
&terminalapi.Mouse{},
},
rec: newReceiver(),
want: map[terminalapi.Event]bool{
&terminalapi.Keyboard{Key: keyboard.KeyEnter}: true,
&terminalapi.Mouse{Position: image.Point{1, 1}}: true,
terminalapi.NewError("error"): true,
},
},
},
},
{
desc: "multiple subscribers and events",
events: []terminalapi.Event{
&terminalapi.Keyboard{Key: keyboard.KeyEnter},
&terminalapi.Keyboard{Key: keyboard.KeyEsc},
&terminalapi.Mouse{Position: image.Point{0, 0}},
&terminalapi.Mouse{Position: image.Point{1, 1}},
&terminalapi.Resize{Size: image.Point{1, 1}},
&terminalapi.Resize{Size: image.Point{2, 2}},
terminalapi.NewError("error1"),
terminalapi.NewError("error2"),
},
subCase: []*subscriberCase{
{
filter: []terminalapi.Event{
&terminalapi.Keyboard{},
},
rec: newReceiver(),
want: map[terminalapi.Event]bool{
&terminalapi.Keyboard{Key: keyboard.KeyEnter}: true,
&terminalapi.Keyboard{Key: keyboard.KeyEsc}: true,
terminalapi.NewError("error1"): true,
terminalapi.NewError("error2"): true,
},
},
{
filter: []terminalapi.Event{
&terminalapi.Mouse{},
&terminalapi.Resize{},
},
rec: newReceiver(),
want: map[terminalapi.Event]bool{
&terminalapi.Mouse{Position: image.Point{0, 0}}: true,
&terminalapi.Mouse{Position: image.Point{1, 1}}: true,
&terminalapi.Resize{Size: image.Point{1, 1}}: true,
&terminalapi.Resize{Size: image.Point{2, 2}}: true,
terminalapi.NewError("error1"): true,
terminalapi.NewError("error2"): true,
},
},
},
},
}
for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) {
eds := NewDistributionSystem()
for _, sc := range tc.subCase {
stop := eds.Subscribe(sc.filter, sc.rec.receive)
defer stop()
}
for _, ev := range tc.events {
eds.Event(ev)
}
for i, sc := range tc.subCase {
got, err := sc.rec.waitFor(len(sc.want), 5*time.Second)
if err != nil {
t.Fatalf("sc.rec.waitFor[%d] => unexpected error: %v", i, err)
}
if diff := pretty.Compare(sc.want, got); diff != "" {
t.Errorf("sc.rec.waitFor[%d] => unexpected diff (-want, +got):\n%s", i, diff)
}
}
})
}
}