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