termdash/internal/event/event_test.go

426 lines
11 KiB
Go

// Copyright 2019 Google Inc.
//
// Licensed 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 event
import (
"errors"
"fmt"
"image"
"sync"
"testing"
"time"
"github.com/kylelemons/godebug/pretty"
"github.com/mum4k/termdash/internal/event/testevent"
"github.com/mum4k/termdash/keyboard"
"github.com/mum4k/termdash/terminal/terminalapi"
)
// receiverMode defines how the receiver behaves.
type receiverMode int
const (
// receiverModeReceive tells the receiver to process the events
receiverModeReceive receiverMode = iota
// receiverModeBlock tells the receiver to block on the call to receive.
receiverModeBlock
// receiverModePause tells the receiver to pause before starting to
// receive.
receiverModePause
)
// receiver receives events from the distribution system.
type receiver struct {
mu sync.Mutex
// mode sets how the receiver behaves when receive(0 is called.
mode receiverMode
// events are the received events.
events []terminalapi.Event
// resumed indicates if the receiver was resumed.
resumed bool
}
// newReceiver returns a new event receiver.
func newReceiver(mode receiverMode) *receiver {
return &receiver{
mode: mode,
}
}
// receive receives an event.
func (r *receiver) receive(ev terminalapi.Event) {
switch r.mode {
case receiverModeBlock:
for {
time.Sleep(1 * time.Minute)
}
case receiverModePause:
time.Sleep(3 * time.Second)
}
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
}
// subscriberCase holds test case specifics for one subscriber.
type subscriberCase struct {
// filter is the subscribers filter.
filter []terminalapi.Event
// opts are the options to provide when subscribing.
opts []SubscribeOption
// 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 testevent.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(receiverModeReceive),
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(receiverModeReceive),
want: map[terminalapi.Event]bool{
&terminalapi.Keyboard{Key: keyboard.KeyEnter}: true,
&terminalapi.Mouse{Position: image.Point{1, 1}}: true,
},
},
},
},
{
desc: "single subscriber, wants errors only",
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.NewError(""),
},
rec: newReceiver(receiverModeReceive),
want: map[terminalapi.Event]bool{
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(receiverModeReceive),
want: map[terminalapi.Event]bool{
&terminalapi.Keyboard{Key: keyboard.KeyEnter}: true,
&terminalapi.Keyboard{Key: keyboard.KeyEsc}: true,
},
},
{
filter: []terminalapi.Event{
&terminalapi.Mouse{},
&terminalapi.Resize{},
},
rec: newReceiver(receiverModeReceive),
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,
},
},
},
},
{
desc: "a misbehaving receiver only blocks itself",
events: []terminalapi.Event{
&terminalapi.Keyboard{Key: keyboard.KeyEnter},
&terminalapi.Keyboard{Key: keyboard.KeyEsc},
terminalapi.NewError("error1"),
terminalapi.NewError("error2"),
},
subCase: []*subscriberCase{
{
filter: []terminalapi.Event{
&terminalapi.Keyboard{},
},
rec: newReceiver(receiverModeReceive),
want: map[terminalapi.Event]bool{
&terminalapi.Keyboard{Key: keyboard.KeyEnter}: true,
&terminalapi.Keyboard{Key: keyboard.KeyEsc}: true,
},
},
{
filter: []terminalapi.Event{
&terminalapi.Keyboard{},
},
rec: newReceiver(receiverModeBlock),
want: map[terminalapi.Event]bool{
&terminalapi.Keyboard{Key: keyboard.KeyEnter}: true,
&terminalapi.Keyboard{Key: keyboard.KeyEsc}: true,
},
wantErr: true,
},
},
},
{
desc: "throttles repetitive events",
events: []terminalapi.Event{
&terminalapi.Keyboard{Key: keyboard.KeyEsc},
&terminalapi.Keyboard{Key: keyboard.KeyEnter},
&terminalapi.Keyboard{Key: keyboard.KeyEnter},
&terminalapi.Keyboard{Key: keyboard.KeyEnter},
&terminalapi.Keyboard{Key: keyboard.KeyEnter},
&terminalapi.Keyboard{Key: keyboard.KeyEsc},
terminalapi.NewError("error1"),
terminalapi.NewError("error2"),
},
subCase: []*subscriberCase{
{
filter: []terminalapi.Event{
&terminalapi.Keyboard{},
},
opts: []SubscribeOption{
MaxRepetitive(0),
},
rec: newReceiver(receiverModePause),
want: map[terminalapi.Event]bool{
&terminalapi.Keyboard{Key: keyboard.KeyEsc}: true,
&terminalapi.Keyboard{Key: keyboard.KeyEnter}: true,
&terminalapi.Keyboard{Key: keyboard.KeyEsc}: true,
},
},
},
},
}
for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) {
tc := tc
t.Parallel()
eds := NewDistributionSystem()
for _, sc := range tc.subCase {
stop := eds.Subscribe(sc.filter, sc.rec.receive, sc.opts...)
defer stop()
}
for _, ev := range tc.events {
eds.Event(ev)
}
for i, sc := range tc.subCase {
gotEv := map[terminalapi.Event]bool{}
err := testevent.WaitFor(10*time.Second, func() error {
ev := sc.rec.getEvents()
want := len(sc.want)
switch got := len(ev); {
case got == want:
gotEv = ev
return nil
default:
return fmt.Errorf("got %d events %v, want %d", got, ev, want)
}
})
if (err != nil) != sc.wantErr {
t.Errorf("testevent.WaitFor subscriber[%d] => unexpected error: %v, wantErr: %v", i, err, sc.wantErr)
}
if err != nil {
continue
}
if diff := pretty.Compare(sc.want, gotEv); diff != "" {
t.Errorf("testevent.WaitFor subscriber[%d] => unexpected diff (-want, +got):\n%s", i, diff)
}
}
})
}
}
func TestProcessed(t *testing.T) {
t.Parallel()
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
want int
}{
{
desc: "zero without events",
want: 0,
},
{
desc: "zero without subscribers",
events: []terminalapi.Event{
&terminalapi.Keyboard{Key: keyboard.KeyEnter},
},
want: 0,
},
{
desc: "zero when a receiver blocks",
events: []terminalapi.Event{
&terminalapi.Keyboard{Key: keyboard.KeyEnter},
},
subCase: []*subscriberCase{
{
filter: []terminalapi.Event{
&terminalapi.Keyboard{},
},
rec: newReceiver(receiverModeBlock),
},
},
want: 0,
},
{
desc: "counts processed events",
events: []terminalapi.Event{
&terminalapi.Keyboard{Key: keyboard.KeyEnter},
},
subCase: []*subscriberCase{
{
filter: []terminalapi.Event{
&terminalapi.Keyboard{},
},
rec: newReceiver(receiverModeReceive),
},
},
want: 1,
},
}
for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) {
tc := tc
t.Parallel()
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 _, sc := range tc.subCase {
testevent.WaitFor(5*time.Second, func() error {
if len(sc.rec.getEvents()) > 0 {
return nil
}
return errors.New("the receiver got no events")
})
}
if got := eds.Processed(); got != tc.want {
t.Errorf("Processed => %v, want %d", got, tc.want)
}
})
}
}