1// Copyright 2019 Google Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package event
16
17import (
18	"errors"
19	"fmt"
20	"image"
21	"sync"
22	"testing"
23	"time"
24
25	"github.com/kylelemons/godebug/pretty"
26	"github.com/mum4k/termdash/keyboard"
27	"github.com/mum4k/termdash/private/event/testevent"
28	"github.com/mum4k/termdash/terminal/terminalapi"
29)
30
31// receiverMode defines how the receiver behaves.
32type receiverMode int
33
34const (
35	// receiverModeReceive tells the receiver to process the events
36	receiverModeReceive receiverMode = iota
37
38	// receiverModeBlock tells the receiver to block on the call to receive.
39	receiverModeBlock
40
41	// receiverModePause tells the receiver to pause before starting to
42	// receive.
43	receiverModePause
44)
45
46// receiver receives events from the distribution system.
47type receiver struct {
48	mu sync.Mutex
49
50	// mode sets how the receiver behaves when receive(0 is called.
51	mode receiverMode
52
53	// events are the received events.
54	events []terminalapi.Event
55
56	// resumed indicates if the receiver was resumed.
57	resumed bool
58}
59
60// newReceiver returns a new event receiver.
61func newReceiver(mode receiverMode) *receiver {
62	return &receiver{
63		mode: mode,
64	}
65}
66
67// receive receives an event.
68func (r *receiver) receive(ev terminalapi.Event) {
69	switch r.mode {
70	case receiverModeBlock:
71		for {
72			time.Sleep(1 * time.Minute)
73		}
74	case receiverModePause:
75		time.Sleep(3 * time.Second)
76	}
77
78	r.mu.Lock()
79	defer r.mu.Unlock()
80
81	r.events = append(r.events, ev)
82}
83
84// getEvents returns the received events.
85func (r *receiver) getEvents() map[terminalapi.Event]bool {
86	r.mu.Lock()
87	defer r.mu.Unlock()
88
89	res := map[terminalapi.Event]bool{}
90	for _, ev := range r.events {
91		res[ev] = true
92	}
93	return res
94}
95
96// subscriberCase holds test case specifics for one subscriber.
97type subscriberCase struct {
98	// filter is the subscribers filter.
99	filter []terminalapi.Event
100
101	// opts are the options to provide when subscribing.
102	opts []SubscribeOption
103
104	// rec receives the events.
105	rec *receiver
106
107	// want are the expected events that should be delivered to this subscriber.
108	want map[terminalapi.Event]bool
109
110	// wantErr asserts whether we want an error from testevent.WaitFor.
111	wantErr bool
112}
113
114func TestDistributionSystem(t *testing.T) {
115	tests := []struct {
116		desc string
117		// events will be sent down the distribution system.
118		events []terminalapi.Event
119
120		// subCase are the event subscribers and their expectations.
121		subCase []*subscriberCase
122	}{
123		{
124			desc: "no events and no subscribers",
125		},
126		{
127			desc: "events and no subscribers",
128			events: []terminalapi.Event{
129				&terminalapi.Mouse{},
130				&terminalapi.Keyboard{},
131			},
132		},
133		{
134			desc: "single subscriber, wants all events and gets them",
135			events: []terminalapi.Event{
136				&terminalapi.Keyboard{Key: keyboard.KeyEnter},
137				&terminalapi.Mouse{Position: image.Point{1, 1}},
138				&terminalapi.Resize{Size: image.Point{2, 2}},
139				terminalapi.NewError("error"),
140			},
141			subCase: []*subscriberCase{
142				{
143					filter: nil,
144					rec:    newReceiver(receiverModeReceive),
145					want: map[terminalapi.Event]bool{
146						&terminalapi.Keyboard{Key: keyboard.KeyEnter}:   true,
147						&terminalapi.Mouse{Position: image.Point{1, 1}}: true,
148						&terminalapi.Resize{Size: image.Point{2, 2}}:    true,
149						terminalapi.NewError("error"):                   true,
150					},
151				},
152			},
153		},
154		{
155			desc: "single subscriber, filters events",
156			events: []terminalapi.Event{
157				&terminalapi.Keyboard{Key: keyboard.KeyEnter},
158				&terminalapi.Mouse{Position: image.Point{1, 1}},
159				&terminalapi.Resize{Size: image.Point{2, 2}},
160			},
161			subCase: []*subscriberCase{
162				{
163					filter: []terminalapi.Event{
164						&terminalapi.Keyboard{},
165						&terminalapi.Mouse{},
166					},
167					rec: newReceiver(receiverModeReceive),
168					want: map[terminalapi.Event]bool{
169						&terminalapi.Keyboard{Key: keyboard.KeyEnter}:   true,
170						&terminalapi.Mouse{Position: image.Point{1, 1}}: true,
171					},
172				},
173			},
174		},
175		{
176			desc: "single subscriber, wants errors only",
177			events: []terminalapi.Event{
178				&terminalapi.Keyboard{Key: keyboard.KeyEnter},
179				&terminalapi.Mouse{Position: image.Point{1, 1}},
180				&terminalapi.Resize{Size: image.Point{2, 2}},
181				terminalapi.NewError("error"),
182			},
183			subCase: []*subscriberCase{
184				{
185					filter: []terminalapi.Event{
186						terminalapi.NewError(""),
187					},
188					rec: newReceiver(receiverModeReceive),
189					want: map[terminalapi.Event]bool{
190						terminalapi.NewError("error"): true,
191					},
192				},
193			},
194		},
195		{
196			desc: "multiple subscribers and events",
197			events: []terminalapi.Event{
198				&terminalapi.Keyboard{Key: keyboard.KeyEnter},
199				&terminalapi.Keyboard{Key: keyboard.KeyEsc},
200				&terminalapi.Mouse{Position: image.Point{0, 0}},
201				&terminalapi.Mouse{Position: image.Point{1, 1}},
202				&terminalapi.Resize{Size: image.Point{1, 1}},
203				&terminalapi.Resize{Size: image.Point{2, 2}},
204				terminalapi.NewError("error1"),
205				terminalapi.NewError("error2"),
206			},
207			subCase: []*subscriberCase{
208				{
209					filter: []terminalapi.Event{
210						&terminalapi.Keyboard{},
211					},
212					rec: newReceiver(receiverModeReceive),
213					want: map[terminalapi.Event]bool{
214						&terminalapi.Keyboard{Key: keyboard.KeyEnter}: true,
215						&terminalapi.Keyboard{Key: keyboard.KeyEsc}:   true,
216					},
217				},
218				{
219					filter: []terminalapi.Event{
220						&terminalapi.Mouse{},
221						&terminalapi.Resize{},
222					},
223					rec: newReceiver(receiverModeReceive),
224					want: map[terminalapi.Event]bool{
225						&terminalapi.Mouse{Position: image.Point{0, 0}}: true,
226						&terminalapi.Mouse{Position: image.Point{1, 1}}: true,
227						&terminalapi.Resize{Size: image.Point{1, 1}}:    true,
228						&terminalapi.Resize{Size: image.Point{2, 2}}:    true,
229					},
230				},
231			},
232		},
233		{
234			desc: "a misbehaving receiver only blocks itself",
235			events: []terminalapi.Event{
236				&terminalapi.Keyboard{Key: keyboard.KeyEnter},
237				&terminalapi.Keyboard{Key: keyboard.KeyEsc},
238				terminalapi.NewError("error1"),
239				terminalapi.NewError("error2"),
240			},
241			subCase: []*subscriberCase{
242				{
243					filter: []terminalapi.Event{
244						&terminalapi.Keyboard{},
245					},
246					rec: newReceiver(receiverModeReceive),
247					want: map[terminalapi.Event]bool{
248						&terminalapi.Keyboard{Key: keyboard.KeyEnter}: true,
249						&terminalapi.Keyboard{Key: keyboard.KeyEsc}:   true,
250					},
251				},
252				{
253					filter: []terminalapi.Event{
254						&terminalapi.Keyboard{},
255					},
256					rec: newReceiver(receiverModeBlock),
257					want: map[terminalapi.Event]bool{
258						&terminalapi.Keyboard{Key: keyboard.KeyEnter}: true,
259						&terminalapi.Keyboard{Key: keyboard.KeyEsc}:   true,
260					},
261					wantErr: true,
262				},
263			},
264		},
265		{
266			desc: "throttles repetitive events",
267			events: []terminalapi.Event{
268				&terminalapi.Keyboard{Key: keyboard.KeyEsc},
269				&terminalapi.Keyboard{Key: keyboard.KeyEnter},
270				&terminalapi.Keyboard{Key: keyboard.KeyEnter},
271				&terminalapi.Keyboard{Key: keyboard.KeyEnter},
272				&terminalapi.Keyboard{Key: keyboard.KeyEnter},
273				&terminalapi.Keyboard{Key: keyboard.KeyEsc},
274				terminalapi.NewError("error1"),
275				terminalapi.NewError("error2"),
276			},
277			subCase: []*subscriberCase{
278				{
279					filter: []terminalapi.Event{
280						&terminalapi.Keyboard{},
281					},
282					opts: []SubscribeOption{
283						MaxRepetitive(0),
284					},
285					rec: newReceiver(receiverModePause),
286					want: map[terminalapi.Event]bool{
287						&terminalapi.Keyboard{Key: keyboard.KeyEsc}:   true,
288						&terminalapi.Keyboard{Key: keyboard.KeyEnter}: true,
289						&terminalapi.Keyboard{Key: keyboard.KeyEsc}:   true,
290					},
291				},
292			},
293		},
294	}
295
296	for _, tc := range tests {
297		t.Run(tc.desc, func(t *testing.T) {
298			tc := tc
299			t.Parallel()
300
301			eds := NewDistributionSystem()
302			for _, sc := range tc.subCase {
303				stop := eds.Subscribe(sc.filter, sc.rec.receive, sc.opts...)
304				defer stop()
305			}
306
307			for _, ev := range tc.events {
308				eds.Event(ev)
309			}
310
311			for i, sc := range tc.subCase {
312				gotEv := map[terminalapi.Event]bool{}
313				err := testevent.WaitFor(10*time.Second, func() error {
314					ev := sc.rec.getEvents()
315					want := len(sc.want)
316					switch got := len(ev); {
317					case got == want:
318						gotEv = ev
319						return nil
320
321					default:
322						return fmt.Errorf("got %d events %v, want %d", got, ev, want)
323					}
324				})
325				if (err != nil) != sc.wantErr {
326					t.Errorf("testevent.WaitFor subscriber[%d] => unexpected error: %v, wantErr: %v", i, err, sc.wantErr)
327				}
328				if err != nil {
329					continue
330				}
331
332				if diff := pretty.Compare(sc.want, gotEv); diff != "" {
333					t.Errorf("testevent.WaitFor subscriber[%d] => unexpected diff (-want, +got):\n%s", i, diff)
334				}
335			}
336		})
337	}
338}
339
340func TestProcessed(t *testing.T) {
341	t.Parallel()
342
343	tests := []struct {
344		desc string
345		// events will be sent down the distribution system.
346		events []terminalapi.Event
347
348		// subCase are the event subscribers and their expectations.
349		subCase []*subscriberCase
350
351		want int
352	}{
353		{
354			desc: "zero without events",
355			want: 0,
356		},
357		{
358			desc: "zero without subscribers",
359			events: []terminalapi.Event{
360				&terminalapi.Keyboard{Key: keyboard.KeyEnter},
361			},
362			want: 0,
363		},
364		{
365			desc: "zero when a receiver blocks",
366			events: []terminalapi.Event{
367				&terminalapi.Keyboard{Key: keyboard.KeyEnter},
368			},
369			subCase: []*subscriberCase{
370				{
371					filter: []terminalapi.Event{
372						&terminalapi.Keyboard{},
373					},
374					rec: newReceiver(receiverModeBlock),
375				},
376			},
377			want: 0,
378		},
379		{
380			desc: "counts processed events",
381			events: []terminalapi.Event{
382				&terminalapi.Keyboard{Key: keyboard.KeyEnter},
383			},
384			subCase: []*subscriberCase{
385				{
386					filter: []terminalapi.Event{
387						&terminalapi.Keyboard{},
388					},
389					rec: newReceiver(receiverModeReceive),
390				},
391			},
392			want: 1,
393		},
394	}
395
396	for _, tc := range tests {
397		t.Run(tc.desc, func(t *testing.T) {
398			tc := tc
399			t.Parallel()
400
401			eds := NewDistributionSystem()
402			for _, sc := range tc.subCase {
403				stop := eds.Subscribe(sc.filter, sc.rec.receive)
404				defer stop()
405			}
406
407			for _, ev := range tc.events {
408				eds.Event(ev)
409			}
410
411			for _, sc := range tc.subCase {
412				testevent.WaitFor(5*time.Second, func() error {
413					if len(sc.rec.getEvents()) > 0 {
414						return nil
415					}
416					return errors.New("the receiver got no events")
417				})
418			}
419
420			if got := eds.Processed(); got != tc.want {
421				t.Errorf("Processed => %v, want %d", got, tc.want)
422			}
423		})
424	}
425}
426