github.com/angenalZZZ/gofunc@v0.0.0-20210507121333-48ff1be3917b/http/longpoll/longpoll_test.go (about) 1 package longpoll 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "net/http" 8 "net/http/httptest" 9 "testing" 10 "time" 11 ) 12 13 type closeNotifierRecorder struct { 14 httptest.ResponseRecorder 15 CloseNotifier chan bool 16 } 17 18 // As it turns out, httptest.ResponseRecorder (returned by httptest.NewRecorder) 19 // does not support CloseNotify, so mock it to avoid panics about not supporting 20 // the interface 21 func (cnr *closeNotifierRecorder) CloseNotify() <-chan bool { 22 return cnr.CloseNotifier 23 } 24 25 func NewCloseNotifierRecorder() *closeNotifierRecorder { 26 return &closeNotifierRecorder{ 27 httptest.ResponseRecorder{ 28 HeaderMap: make(http.Header), 29 Body: new(bytes.Buffer), 30 Code: 200, 31 }, 32 make(chan bool, 1), 33 } 34 } 35 36 // DEPRECATED. Use StartLongpoll or StartLongpoll instead. 37 func createManager() (*LongpollManager, error) { 38 return StartLongpoll(Options{ 39 LoggingEnabled: true, 40 MaxLongpollTimeoutSeconds: 180, 41 MaxEventBufferSize: 250, 42 // Original behavior maintained, events are never deleted unless they're 43 // pushed out by max buffer size exceeded. 44 EventTimeToLiveSeconds: FOREVER, 45 DeleteEventAfterFirstRetrieval: false, 46 }) 47 } 48 49 // DEPRECATED. Use StartLongpoll or StartLongpoll instead. 50 func createCustomManager(maxTimeoutSeconds, eventBufferSize int, 51 loggingEnabled bool) (*LongpollManager, error) { 52 return StartLongpoll(Options{ 53 LoggingEnabled: loggingEnabled, 54 MaxLongpollTimeoutSeconds: maxTimeoutSeconds, 55 MaxEventBufferSize: eventBufferSize, 56 // Original behavior maintained, events are never deleted unless they're 57 // pushed out by max buffer size exceeded. 58 EventTimeToLiveSeconds: FOREVER, 59 DeleteEventAfterFirstRetrieval: false, 60 }) 61 } 62 63 func Test_LongpollManager_CreateManager(t *testing.T) { 64 manager, err := createManager() 65 // Confirm the create call worked, and our manager has the expected values 66 if err != nil { 67 t.Errorf("Failed to create default LongpollManager. Error was: %q", err) 68 } 69 // Channel size defaults to 100 70 if cap(manager.eventsIn) != 100 { 71 t.Errorf("Unexpected event channel capacity. Expected: %d, got: %d", 72 100, cap(manager.eventsIn)) 73 } 74 if cap(manager.subManager.clientSubscriptions) != 100 { 75 t.Errorf("Unexpected client subscription channel capacity. Expected: %d, got: %d", 76 100, cap(manager.subManager.clientSubscriptions)) 77 } 78 if cap(manager.subManager.ClientTimeouts) != 100 { 79 t.Errorf("Unexpected client timeout channel capacity. Expected: %d, got: %d", 80 100, cap(manager.subManager.ClientTimeouts)) 81 } 82 // Max event buffer size defaults to 250 83 if manager.subManager.MaxEventBufferSize != 250 { 84 t.Errorf("Unexpected client timeout channel capacity. Expected: %d, got: %d", 85 250, manager.subManager.MaxEventBufferSize) 86 } 87 // Don't forget to kill subscription manager's running goroutine 88 manager.Shutdown() 89 } 90 91 func Test_LongpollManager_CreateCustomManager(t *testing.T) { 92 manager, err := createCustomManager(360, 700, true) 93 // Confirm the create call worked, and our manager has the expected values 94 if err != nil { 95 t.Errorf("Failed to create default LongpollManager. Error was: %q", err) 96 } 97 // Channel size defaults to 100 98 if cap(manager.eventsIn) != 100 { 99 t.Errorf("Unexpected event channel capacity. Expected: %d, got: %d", 100 100, cap(manager.eventsIn)) 101 } 102 if cap(manager.subManager.clientSubscriptions) != 100 { 103 t.Errorf("Unexpected client subscription channel capacity. Expected: %d, got: %d", 104 100, cap(manager.subManager.clientSubscriptions)) 105 } 106 if cap(manager.subManager.ClientTimeouts) != 100 { 107 t.Errorf("Unexpected client timeout channel capacity. Expected: %d, got: %d", 108 100, cap(manager.subManager.ClientTimeouts)) 109 } 110 // Max event buffer size set to 700 111 if manager.subManager.MaxEventBufferSize != 700 { 112 t.Errorf("Unexpected client timeout channel capacity. Expected: %d, got: %d", 113 700, manager.subManager.MaxEventBufferSize) 114 } 115 // Don't forget to kill subscription manager's running goroutine 116 manager.Shutdown() 117 } 118 119 func Test_LongpollManager_CreateCustomManager_InvalidArgs(t *testing.T) { 120 manager, err := createCustomManager(360, -1, false) // buffer size == -1 121 if err == nil { 122 t.Errorf("Expected error when creating custom manager with invalid event buffer size ") 123 } 124 if manager != nil { 125 t.Errorf("Expected nil response for manager when create call returned error.") 126 } 127 manager, err = createCustomManager(-1, 200, false) // timeout == -1 128 if err == nil { 129 t.Errorf("Expected error when creating custom manager with invalid timeout.") 130 } 131 if manager != nil { 132 t.Errorf("Expected nil response for manager when create call returned error.") 133 } 134 } 135 136 func Test_LongpollManager_Publish(t *testing.T) { 137 manager, err := createManager() 138 // Confirm the create call worked, and our manager has the expected values 139 if err != nil { 140 t.Errorf("Failed to create default LongpollManager. Error was: %q", err) 141 } 142 if len(manager.eventsIn) != 0 { 143 t.Errorf("Expected event channel to be initially empty. Instead len: %d", 144 len(manager.eventsIn)) 145 } 146 if len(manager.subManager.SubEventBuffer) != 0 { 147 t.Errorf("Expected sub manager's event map to be initially empty. Instead len: %d", 148 len(manager.subManager.SubEventBuffer)) 149 } 150 err = manager.Publish("fruits", "apple") 151 if err != nil { 152 t.Errorf("Unexpected error publishing event: %q", err) 153 } 154 // SubscriptionManager's goroutine should not have picked up event yet: 155 // I *think* this should always work because we don't typically yield 156 // execution to other goroutines until an end of a func reached, 157 // or a channel/network/sleep call is invoked. 158 if len(manager.eventsIn) != 1 { 159 t.Errorf("Expected event channel to have 1 event. Instead len: %d", 160 len(manager.eventsIn)) 161 } 162 // Allow sub manager's goroutine time to pull from channel. 163 // This sleep should cause us to yield and let the other goroutine run 164 time.Sleep(50 * time.Millisecond) 165 if len(manager.eventsIn) != 0 { 166 t.Errorf("Expected event channel to have 0 events. Instead len: %d", 167 len(manager.eventsIn)) 168 } 169 // Confirm event wound up in the sub manager's internal map: 170 if len(manager.subManager.SubEventBuffer) != 1 { 171 t.Errorf("Expected sub manager's event map to have 1 item. Instead len: %d", 172 len(manager.subManager.SubEventBuffer)) 173 } 174 buf, found := manager.subManager.SubEventBuffer["fruits"] 175 if !found { 176 t.Errorf("Failed to find event in sub manager's category-to-eventBuffer map") 177 } 178 // Double check that the expected max buffer size and capacity were set 179 if buf.eventBuffer_ptr.MaxBufferSize != 250 { 180 t.Errorf("Expected max buffer size of %d, but got %d.", 250, buf.eventBuffer_ptr.MaxBufferSize) 181 } 182 if buf.eventBuffer_ptr.List.Len() != 1 { 183 t.Errorf("Expected buffer to be 1 item. instead: %d", buf.eventBuffer_ptr.List.Len()) 184 } 185 if buf.eventBuffer_ptr.Front().Value.(*lpEvent).Data != "apple" { 186 t.Errorf("Expected event data to be %q, but got %q", "apple", 187 buf.eventBuffer_ptr.Front().Value.(*lpEvent).Data) 188 } 189 190 // Publish two more events 191 err = manager.Publish("veggies", "potato") 192 if err != nil { 193 t.Errorf("Unexpected error publishing event: %q", err) 194 } 195 err = manager.Publish("fruits", "orange") 196 if err != nil { 197 t.Errorf("Unexpected error publishing event: %q", err) 198 } 199 // Allow other goroutine a chance to do a channel read 200 time.Sleep(50 * time.Millisecond) 201 202 if len(manager.eventsIn) != 0 { 203 t.Errorf("Expected event channel to have 0 events. Instead len: %d", 204 len(manager.eventsIn)) 205 } 206 if len(manager.subManager.SubEventBuffer) != 2 { 207 t.Errorf("Expected sub manager's event map to have 2 item. Instead len: %d", 208 len(manager.subManager.SubEventBuffer)) 209 } 210 buf, found = manager.subManager.SubEventBuffer["fruits"] 211 if !found { 212 t.Errorf("Failed to find event in sub manager's category-to-eventBuffer map") 213 } 214 if buf.eventBuffer_ptr.List.Len() != 2 { 215 t.Errorf("Expected buffer to be 2 items. instead: %d", buf.eventBuffer_ptr.List.Len()) 216 } 217 if buf.eventBuffer_ptr.Front().Value.(*lpEvent).Data != "orange" { 218 t.Errorf("Expected event data to be %q, but got %q", "orange", 219 buf.eventBuffer_ptr.Front().Value.(*lpEvent).Data) 220 } 221 buf, found = manager.subManager.SubEventBuffer["veggies"] 222 if !found { 223 t.Errorf("Failed to find event in sub manager's category-to-eventBuffer map") 224 } 225 if buf.eventBuffer_ptr.List.Len() != 1 { 226 t.Errorf("Expected buffer to be 1 item. instead: %d", buf.eventBuffer_ptr.List.Len()) 227 } 228 if buf.eventBuffer_ptr.Front().Value.(*lpEvent).Data != "potato" { 229 t.Errorf("Expected event data to be %q, but got %q", "potato", 230 buf.eventBuffer_ptr.Front().Value.(*lpEvent).Data) 231 } 232 // Don't forget to kill subscription manager's running goroutine 233 manager.Shutdown() 234 } 235 236 func Test_LongpollManager_Publish_MaxBufferSize(t *testing.T) { 237 manager, err := createCustomManager(120, 2, true) // max buffer size 3 238 if len(manager.subManager.SubEventBuffer) != 0 { 239 t.Errorf("Expected sub manager's event map to be initially empty. Instead len: %d", 240 len(manager.subManager.SubEventBuffer)) 241 } 242 err = manager.Publish("fruits", "apple") 243 if err != nil { 244 t.Errorf("Unexpected error publishing event: %q", err) 245 } 246 err = manager.Publish("fruits", "banana") 247 if err != nil { 248 t.Errorf("Unexpected error publishing event: %q", err) 249 } 250 // yield so other goroutine can do channel reads 251 time.Sleep(50 * time.Millisecond) 252 if len(manager.eventsIn) != 0 { 253 t.Errorf("Expected event channel to have 0 events. Instead len: %d", 254 len(manager.eventsIn)) 255 } 256 // Confirm events wound up in the sub manager's internal map: 257 // NOTE: only one map entry because both events the same category 258 if len(manager.subManager.SubEventBuffer) != 1 { 259 t.Errorf("Expected sub manager's event map to have 1 item. Instead len: %d", 260 len(manager.subManager.SubEventBuffer)) 261 } 262 buf, found := manager.subManager.SubEventBuffer["fruits"] 263 if !found { 264 t.Errorf("Failed to find event in sub manager's category-to-eventBuffer map") 265 } 266 // Double check that the expected max buffer size and capacity were set 267 if buf.eventBuffer_ptr.MaxBufferSize != 2 { 268 t.Errorf("Expected max buffer size of %d, but got %d.", 2, buf.eventBuffer_ptr.MaxBufferSize) 269 } 270 if buf.eventBuffer_ptr.List.Len() != 2 { 271 t.Errorf("Expected buffer to be 2 items. instead: %d", buf.eventBuffer_ptr.List.Len()) 272 } 273 if buf.eventBuffer_ptr.Front().Value.(*lpEvent).Data != "banana" { 274 t.Errorf("Expected event data to be %q, but got %q", "banana", 275 buf.eventBuffer_ptr.Front().Value.(*lpEvent).Data) 276 } 277 if buf.eventBuffer_ptr.Back().Value.(*lpEvent).Data != "apple" { 278 t.Errorf("Expected event data to be %q, but got %q", "apple", 279 buf.eventBuffer_ptr.Front().Value.(*lpEvent).Data) 280 } 281 282 // Now try and publish another event on the same fruit category, 283 // confirm that it works, but the oldest fruit is no longer in buffer 284 err = manager.Publish("fruits", "pear") 285 if err != nil { 286 t.Errorf("Unexpected error publishing event: %q", err) 287 } 288 // yield so other goroutine can do channel reads 289 time.Sleep(50 * time.Millisecond) 290 buf, found = manager.subManager.SubEventBuffer["fruits"] 291 if !found { 292 t.Errorf("Failed to find event in sub manager's category-to-eventBuffer map") 293 } 294 // Double check that the expected max buffer size and capacity were set 295 if buf.eventBuffer_ptr.MaxBufferSize != 2 { 296 t.Errorf("Expected max buffer size of %d, but got %d.", 2, buf.eventBuffer_ptr.MaxBufferSize) 297 } 298 if buf.eventBuffer_ptr.List.Len() != 2 { 299 t.Errorf("Expected buffer to be 2 items. instead: %d", buf.eventBuffer_ptr.List.Len()) 300 } 301 if buf.eventBuffer_ptr.Front().Value.(*lpEvent).Data != "pear" { 302 t.Errorf("Expected event data to be %q, but got %q", "banana", 303 buf.eventBuffer_ptr.Front().Value.(*lpEvent).Data) 304 } 305 if buf.eventBuffer_ptr.Back().Value.(*lpEvent).Data != "banana" { 306 t.Errorf("Expected event data to be %q, but got %q", "apple", 307 buf.eventBuffer_ptr.Front().Value.(*lpEvent).Data) 308 } 309 310 // Now confirm publishing on a different category still works 311 err = manager.Publish("veggies", "potato") 312 if err != nil { 313 t.Errorf("Unexpected error publishing event: %q", err) 314 } 315 // yield so other goroutine can do channel reads 316 time.Sleep(50 * time.Millisecond) 317 buf, found = manager.subManager.SubEventBuffer["veggies"] 318 if !found { 319 t.Errorf("Failed to find event in sub manager's category-to-eventBuffer map") 320 } 321 if buf.eventBuffer_ptr.List.Len() != 1 { 322 t.Errorf("Expected buffer to be 1 item. instead: %d", buf.eventBuffer_ptr.List.Len()) 323 } 324 if buf.eventBuffer_ptr.Front().Value.(*lpEvent).Data != "potato" { 325 t.Errorf("Expected event data to be %q, but got %q", "potato", 326 buf.eventBuffer_ptr.Front().Value.(*lpEvent).Data) 327 } 328 // Don't forget to kill subscription manager's running goroutine 329 manager.Shutdown() 330 } 331 332 func Test_LongpollManager_Publish_InvalidArgs(t *testing.T) { 333 manager, err := createManager() 334 // Confirm the create call worked, and our manager has the expected values 335 if err != nil { 336 t.Errorf("Failed to create default LongpollManager. Error was: %q", err) 337 } 338 // You must provide a category: 339 err = manager.Publish("", "apple") 340 if err == nil { 341 t.Errorf("Expected calls to Publish with blank category would fail.") 342 } 343 // category can't be longer than 1024: 344 // So 1024 len category should work: 345 tooLong := "" 346 for i := 0; i < 1024; i++ { 347 tooLong += "a" 348 } 349 err = manager.Publish(tooLong, "apple") 350 if err != nil { 351 t.Errorf("Expected calls to Publish with 1024 len category not to fail, but got: %q.", err) 352 } 353 // But now that we're at 1025, we're boned. 354 tooLong += "a" 355 err = manager.Publish(tooLong, "apple") 356 if err == nil { 357 t.Errorf("Expected calls to Publish with blank category would fail.") 358 } 359 } 360 361 func Test_LongpollManager_Shutdown(t *testing.T) { 362 manager, err := createManager() 363 if err != nil { 364 t.Errorf("Failed to create default LongpollManager. Error was: %q", err) 365 } 366 manager.Shutdown() 367 // Confirm the shutdown signal channel was closed (this is now the 368 // goroutines are notified to quit) 369 select { 370 case _, isOpen := <-manager.subManager.Quit: 371 if isOpen { 372 t.Errorf("Expected channel to be closed, instead it was still open") 373 } 374 default: 375 t.Errorf("Expected channel close, instead got no activity.") 376 } 377 } 378 379 func Test_LongpollManager_newclientSubscription(t *testing.T) { 380 subTime := time.Date(2015, 11, 7, 11, 33, 4, 0, time.UTC) 381 sub, err := newclientSubscription("colors", subTime) 382 if err != nil { 383 t.Errorf("Unexpected error when creating new client subscription: %q", err) 384 } 385 if sub.clientCategoryPair.SubscriptionCategory != "colors" { 386 t.Errorf("Unexpected sub category, expected: %q. got: %q", "colors", 387 sub.clientCategoryPair.SubscriptionCategory) 388 } 389 if sub.LastEventTime != subTime { 390 t.Errorf("Unexpected sub last event time, expected: %q. got: %q", subTime, 391 sub.LastEventTime) 392 } 393 if cap(sub.Events) != 1 { 394 t.Errorf("Unexpected event channel capacity. expected: %q. got: %q", 1, 395 cap(sub.Events)) 396 } 397 } 398 399 func ajaxHandler(handlerFunc func(w http.ResponseWriter, r *http.Request)) http.Handler { 400 return http.HandlerFunc(handlerFunc) 401 } 402 403 func Test_LongpollManager_WebClient_InvalidRequests(t *testing.T) { 404 manager, _ := createCustomManager(120, 100, true) 405 subscriptionHandler := ajaxHandler(manager.SubscriptionHandler) 406 407 // Empty request, this is going to result in an JSON error object: 408 req, _ := http.NewRequest("GET", "", nil) 409 w := httptest.NewRecorder() 410 subscriptionHandler.ServeHTTP(w, req) 411 if w.Code != http.StatusOK { 412 t.Errorf("SubscriptionHandler didn't return %v", http.StatusOK) 413 } 414 // Also note how it says "1-120", so our custom timeout arg of 120 was 415 // used 416 if w.Body.String() != "{\"error\": \"Invalid timeout arg. Must be 1-120.\"}" { 417 t.Errorf("Unexpected response: %q", w.Body.String()) 418 } 419 420 // Invalid timeout, not a number 421 req, _ = http.NewRequest("GET", "?timeout=adf&category=veggies", nil) 422 w = httptest.NewRecorder() 423 subscriptionHandler.ServeHTTP(w, req) 424 if w.Code != http.StatusOK { 425 t.Errorf("SubscriptionHandler didn't return %v", http.StatusOK) 426 } 427 if w.Body.String() != "{\"error\": \"Invalid timeout arg. Must be 1-120.\"}" { 428 t.Errorf("Unexpected response: %q", w.Body.String()) 429 } 430 431 // Invalid timeout, too small 432 req, _ = http.NewRequest("GET", "?timeout=0&category=veggies", nil) 433 w = httptest.NewRecorder() 434 subscriptionHandler.ServeHTTP(w, req) 435 if w.Code != http.StatusOK { 436 t.Errorf("SubscriptionHandler didn't return %v", http.StatusOK) 437 } 438 if w.Body.String() != "{\"error\": \"Invalid timeout arg. Must be 1-120.\"}" { 439 t.Errorf("Unexpected response: %q", w.Body.String()) 440 } 441 442 // Invalid timeout, too big 443 req, _ = http.NewRequest("GET", "?timeout=121&category=veggies", nil) 444 w = httptest.NewRecorder() 445 subscriptionHandler.ServeHTTP(w, req) 446 if w.Code != http.StatusOK { 447 t.Errorf("SubscriptionHandler didn't return %v", http.StatusOK) 448 } 449 if w.Body.String() != "{\"error\": \"Invalid timeout arg. Must be 1-120.\"}" { 450 t.Errorf("Unexpected response: %q", w.Body.String()) 451 } 452 453 // Valid timeout, but missing category: 454 req, _ = http.NewRequest("GET", "?timeout=30", nil) 455 w = httptest.NewRecorder() 456 subscriptionHandler.ServeHTTP(w, req) 457 if w.Code != http.StatusOK { 458 t.Errorf("SubscriptionHandler didn't return %v", http.StatusOK) 459 } 460 if w.Body.String() != "{\"error\": \"Invalid subscription category, must be 1-1024 characters long.\"}" { 461 t.Errorf("Unexpected response: %q", w.Body.String()) 462 } 463 464 // Valid timeout, but category is too small 465 req, _ = http.NewRequest("GET", "?timeout=30&category=", nil) 466 w = httptest.NewRecorder() 467 subscriptionHandler.ServeHTTP(w, req) 468 if w.Code != http.StatusOK { 469 t.Errorf("SubscriptionHandler didn't return %v", http.StatusOK) 470 } 471 if w.Body.String() != "{\"error\": \"Invalid subscription category, must be 1-1024 characters long.\"}" { 472 t.Errorf("Unexpected response: %q", w.Body.String()) 473 } 474 475 // Valid timeout, but category too long 476 tooLong := "" 477 for i := 0; i < 1025; i++ { 478 tooLong += "a" 479 } // 1025 chars long 480 req, _ = http.NewRequest("GET", "?timeout=30&category="+tooLong, nil) 481 w = httptest.NewRecorder() 482 subscriptionHandler.ServeHTTP(w, req) 483 if w.Code != http.StatusOK { 484 t.Errorf("SubscriptionHandler didn't return %v", http.StatusOK) 485 } 486 if w.Body.String() != "{\"error\": \"Invalid subscription category, must be 1-1024 characters long.\"}" { 487 t.Errorf("Unexpected response: %q", w.Body.String()) 488 } 489 490 // Valid timeout, valid category, but invalid since_time 491 req, _ = http.NewRequest("GET", "?timeout=30&category=foobar&since_time=asdf", nil) 492 w = httptest.NewRecorder() 493 subscriptionHandler.ServeHTTP(w, req) 494 if w.Code != http.StatusOK { 495 t.Errorf("SubscriptionHandler didn't return %v", http.StatusOK) 496 } 497 if w.Body.String() != "{\"error\": \"Invalid last_event_time arg.\"}" { 498 t.Errorf("Unexpected response: %q", w.Body.String()) 499 } 500 501 // Don't forget to kill our pubsub manager's run goroutine 502 manager.Shutdown() 503 } 504 505 func Test_LongpollManager_WebClient_NoEventsSoTimeout(t *testing.T) { 506 manager, _ := createCustomManager(120, 100, true) 507 subscriptionHandler := ajaxHandler(manager.SubscriptionHandler) 508 509 // Valid request, but we don't have any events published, 510 // so this will wait 2 seconds (because timeout param = 2) 511 // and then come back wtih a timeout response 512 req, _ := http.NewRequest("GET", "?timeout=2&category=veggies", nil) 513 w := NewCloseNotifierRecorder() 514 subscriptionHandler.ServeHTTP(w, req) 515 if w.Code != http.StatusOK { 516 t.Errorf("SubscriptionHandler didn't return %v", http.StatusOK) 517 } 518 approxTimeoutTime := time.Now().UnixNano() / int64(time.Millisecond) 519 var timeoutResp timeoutResponse 520 if err := json.Unmarshal(w.Body.Bytes(), &timeoutResp); err != nil { 521 t.Errorf("Failed to decode json: %q", err) 522 } 523 if timeoutResp.TimeoutMessage != "no events before timeout" { 524 t.Errorf("Unexpected timeout message: %q", timeoutResp.TimeoutMessage) 525 } 526 if timeoutResp.Timestamp < (approxTimeoutTime-100) || 527 timeoutResp.Timestamp > approxTimeoutTime { 528 t.Errorf("Unexpected timeout timestamp. Expected: %q, got: %q", 529 approxTimeoutTime, timeoutResp.Timestamp) 530 } 531 532 // Don't forget to kill our pubsub manager's run goroutine 533 manager.Shutdown() 534 } 535 536 func Test_LongpollManager_WebClient_Disconnect_RemoveClientSub(t *testing.T) { 537 manager, _ := createCustomManager(120, 100, true) 538 subscriptionHandler := ajaxHandler(manager.SubscriptionHandler) 539 if _, found := manager.subManager.ClientSubChannels["veggies"]; found { 540 t.Errorf("Expected client sub channel not to exist yet ") 541 } 542 // This request has timeout of 5 seconds, but we're going to simulate a 543 // disconnect in 2 seconds which is earlier. 544 req, _ := http.NewRequest("GET", "?timeout=5&category=veggies", nil) 545 w := NewCloseNotifierRecorder() 546 // As of go 1.7, calls to t.Error, t.Fatal, after a test exits causes a panic. 547 // Before, these were suppressed. So as it turns out, this test's goroutine 548 // that spawns here was outliving the test. Now let's make the test body 549 // explicitly wait for it. 550 // see thread here: https://github.com/golang/go/issues/15976 551 goroutine_done := make(chan bool) 552 go func() { 553 time.Sleep(time.Duration(250) * time.Millisecond) 554 // confirm subscription entry exists, with one client 555 if _, found := manager.subManager.ClientSubChannels["veggies"]; !found { 556 t.Errorf("Expected client sub channel to exist") 557 } 558 if val, _ := manager.subManager.ClientSubChannels["veggies"]; len(val) != 1 { 559 t.Errorf("Expected sub channel to have one client subscribed") 560 } 561 time.Sleep(time.Duration(1) * time.Second) 562 w.CloseNotifier <- true 563 time.Sleep(time.Duration(1) * time.Second) 564 // Confirm that the subscription entry no longer exists. 565 // Before, this test asserted that the entry ('veggie' key in the map) 566 // existed, but the value listed no clients. But code was changed to auto 567 // remove subscription keys in this map if there are no clients listening. 568 // Since these asserts were erroneously firing after the test body exited 569 // (and this is undefined behavior and pre go 1.7 it's simply ignored), 570 // this bad assertion was never failing when it should have. 571 // Once the test body was forced to wait for it's spawned goroutine to exit, 572 // the old assertion started failing. 573 // This test is now updated to assert that the key "veggies" no longer 574 // exists since we're explicitly removing map entries when the value is 575 // an empty container. 576 if _, found := manager.subManager.ClientSubChannels["veggies"]; found { 577 t.Errorf("Expected client sub channel to be auto removed when 0 clients.") 578 } 579 goroutine_done <- true 580 }() 581 582 subscriptionHandler.ServeHTTP(w, req) 583 584 // causes test body to block until the above spawned goroutine finishes. 585 // Otherwise this will panic in go 1.7 and later :) 586 <-goroutine_done 587 588 // Don't forget to kill our pubsub manager's run goroutine 589 manager.Shutdown() 590 } 591 592 func Test_LongpollManager_WebClient_Disconnect_TerminateHttp(t *testing.T) { 593 manager, _ := createCustomManager(120, 100, true) 594 testChannel := make(chan int, 2) 595 webValue := 7 596 goroutineValue := 13 597 subscriptionHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 598 manager.SubscriptionHandler(w, r) 599 testChannel <- webValue 600 }) 601 if _, found := manager.subManager.ClientSubChannels["veggies"]; found { 602 t.Errorf("Expected client sub channel not to exist yet ") 603 } 604 // This request has timeout of 5 seconds, but we're going to simulate a 605 // disconnect in 1 second which is earlier. 606 // If the wrapping subscription handler gets to the end, it will publish 607 // the number 7 on our test channel. Have a goroutine publish a different 608 // value at some time after the disconnect, but before the timeout 609 // and confirm that the disconnect forced the subscription handler 610 // to return early and thus we get the expected published value. 611 req, _ := http.NewRequest("GET", "?timeout=5&category=veggies", nil) 612 w := NewCloseNotifierRecorder() 613 614 go func() { 615 time.Sleep(time.Duration(3) * time.Second) 616 testChannel <- goroutineValue 617 }() 618 619 go func() { 620 time.Sleep(time.Duration(250) * time.Millisecond) 621 // confirm subscription entry exists, with one client 622 if _, found := manager.subManager.ClientSubChannels["veggies"]; !found { 623 t.Errorf("Expected client sub channel to exist") 624 } 625 if val, _ := manager.subManager.ClientSubChannels["veggies"]; len(val) != 1 { 626 t.Errorf("Expected sub channel to have one client subscribed") 627 } 628 time.Sleep(time.Duration(1) * time.Second) 629 w.CloseNotifier <- true 630 time.Sleep(time.Duration(2) * time.Second) 631 // Confirm that our test channel has the value from our web handler and 632 // not from our goroutine 633 select { 634 case val := <-testChannel: 635 if val != webValue { 636 t.Errorf("Expected to get channel send from http handler before the goroutine.") 637 } 638 } 639 }() 640 641 subscriptionHandler.ServeHTTP(w, req) 642 // Don't forget to kill our pubsub manager's run goroutine 643 manager.Shutdown() 644 } 645 646 func Test_LongpollManager_WebClient_HasEvents(t *testing.T) { 647 manager, _ := createCustomManager(120, 100, true) 648 subscriptionHandler := ajaxHandler(manager.SubscriptionHandler) 649 650 // Valid request, but we don't have any events published, 651 // so this will wait for a publish or timeout (in this case we'll get 652 // something) 653 req, _ := http.NewRequest("GET", "?timeout=30&category=veggies", nil) 654 w := NewCloseNotifierRecorder() 655 // Publish two events, only the second is for our subscription category 656 // Note how these events occur after the client subscribed 657 // if they occurred before, since we don't provide a since_time url param 658 // we'd default to now and skip those events. 659 startTime := time.Now() 660 go func() { 661 time.Sleep(1500 * time.Millisecond) 662 manager.Publish("fruits", "peach") 663 time.Sleep(1000 * time.Millisecond) 664 manager.Publish("veggies", "corn") 665 }() 666 subscriptionHandler.ServeHTTP(w, req) 667 668 // Confirm we got the correct event 669 if w.Code != http.StatusOK { 670 t.Errorf("SubscriptionHandler didn't return %v", http.StatusOK) 671 } 672 var eventResponse eventResponse 673 if err := json.Unmarshal(w.Body.Bytes(), &eventResponse); err != nil { 674 t.Errorf("Failed to decode json: %q", err) 675 } 676 if len(*eventResponse.Events) != 1 { 677 t.Errorf("Unexpected number of events. Expected: %d, got: %d", 1, len(*eventResponse.Events)) 678 } 679 if (*eventResponse.Events)[0].Category != "veggies" { 680 t.Errorf("Unexpected category. Expected: %q, got: %q", "veggies", (*eventResponse.Events)[0].Category) 681 } 682 if (*eventResponse.Events)[0].Data != "corn" { 683 t.Errorf("Unexpected data. Expected: %q, got: %q", "corn", (*eventResponse.Events)[0].Data) 684 } 685 686 // Make a new subscription request. 687 // Note how since there's no since_time url param, we default to now, 688 // and thus don't see the previous event from our last http request 689 req, _ = http.NewRequest("GET", "?timeout=2&category=veggies", nil) 690 w = NewCloseNotifierRecorder() 691 subscriptionHandler.ServeHTTP(w, req) 692 if w.Code != http.StatusOK { 693 t.Errorf("SubscriptionHandler didn't return %v", http.StatusOK) 694 } 695 approxTimeoutTime := time.Now().UnixNano() / int64(time.Millisecond) 696 var timeoutResp timeoutResponse 697 if err := json.Unmarshal(w.Body.Bytes(), &timeoutResp); err != nil { 698 t.Errorf("Failed to decode json: %q", err) 699 } 700 if timeoutResp.TimeoutMessage != "no events before timeout" { 701 t.Errorf("Unexpected timeout message: %q", timeoutResp.TimeoutMessage) 702 } 703 if timeoutResp.Timestamp < (approxTimeoutTime-100) || 704 timeoutResp.Timestamp > approxTimeoutTime { 705 t.Errorf("Unexpected timeout timestamp. Expected: %q, got: %q", 706 approxTimeoutTime, timeoutResp.Timestamp) 707 } 708 709 // Now ask for events since the start of our test, which will include 710 // our previously seen event 711 req, _ = http.NewRequest("GET", fmt.Sprintf("?timeout=2&category=veggies&since_time=%d", 712 startTime.UnixNano()/int64(time.Millisecond)), nil) 713 w = NewCloseNotifierRecorder() 714 subscriptionHandler.ServeHTTP(w, req) 715 if w.Code != http.StatusOK { 716 t.Errorf("SubscriptionHandler didn't return %v", http.StatusOK) 717 } 718 if err := json.Unmarshal(w.Body.Bytes(), &eventResponse); err != nil { 719 t.Errorf("Failed to decode json: %q", err) 720 } 721 if len(*eventResponse.Events) != 1 { 722 t.Errorf("Unexpected number of events. Expected: %d, got: %d", 1, len(*eventResponse.Events)) 723 } 724 if (*eventResponse.Events)[0].Category != "veggies" { 725 t.Errorf("Unexpected category. Expected: %q, got: %q", "veggies", (*eventResponse.Events)[0].Category) 726 } 727 if (*eventResponse.Events)[0].Data != "corn" { 728 t.Errorf("Unexpected data. Expected: %q, got: %q", "corn", (*eventResponse.Events)[0].Data) 729 } 730 731 firstEventTime := (*eventResponse.Events)[0].Timestamp 732 manager.Publish("veggies", "carrot") 733 time.Sleep(50 * time.Millisecond) // allow yield for goroutine channel reads 734 735 // Now ask for any events since our first one, and confirm we get the second 736 // 'veggie' category event 737 req, _ = http.NewRequest("GET", fmt.Sprintf("?timeout=2&category=veggies&since_time=%d", firstEventTime), nil) 738 w = NewCloseNotifierRecorder() 739 subscriptionHandler.ServeHTTP(w, req) 740 if w.Code != http.StatusOK { 741 t.Errorf("SubscriptionHandler didn't return %v", http.StatusOK) 742 } 743 if err := json.Unmarshal(w.Body.Bytes(), &eventResponse); err != nil { 744 t.Errorf("Failed to decode json: %q", err) 745 } 746 if len(*eventResponse.Events) != 1 { 747 t.Errorf("Unexpected number of events. Expected: %d, got: %d", 1, len(*eventResponse.Events)) 748 } 749 if (*eventResponse.Events)[0].Category != "veggies" { 750 t.Errorf("Unexpected category. Expected: %q, got: %q", "veggies", (*eventResponse.Events)[0].Category) 751 } 752 if (*eventResponse.Events)[0].Data != "carrot" { 753 t.Errorf("Unexpected data. Expected: %q, got: %q", "carrot", (*eventResponse.Events)[0].Data) 754 } 755 756 // Confirm we get both events when asking for any events since start of test run 757 req, _ = http.NewRequest("GET", fmt.Sprintf("?timeout=2&category=veggies&since_time=%d", 758 startTime.UnixNano()/int64(time.Millisecond)), nil) 759 w = NewCloseNotifierRecorder() 760 subscriptionHandler.ServeHTTP(w, req) 761 if w.Code != http.StatusOK { 762 t.Errorf("SubscriptionHandler didn't return %v", http.StatusOK) 763 } 764 if err := json.Unmarshal(w.Body.Bytes(), &eventResponse); err != nil { 765 t.Errorf("Failed to decode json: %q", err) 766 } 767 if len(*eventResponse.Events) != 2 { 768 t.Errorf("Unexpected number of events. Expected: %d, got: %d", 2, len(*eventResponse.Events)) 769 } 770 if (*eventResponse.Events)[0].Data != "corn" { 771 t.Errorf("Unexpected data. Expected: %q, got: %q", "corn", (*eventResponse.Events)[0].Data) 772 } 773 if (*eventResponse.Events)[1].Data != "carrot" { 774 t.Errorf("Unexpected data. Expected: %q, got: %q", "carrot", (*eventResponse.Events)[0].Data) 775 } 776 777 // Don't forget to kill our pubsub manager's run goroutine 778 manager.Shutdown() 779 } 780 781 func Test_LongpollManager_WebClient_HasBufferedEvents(t *testing.T) { 782 // Test behavior where clients can see events that happened before 783 // they started their longpoll by accessing events in the 784 // subscriptionManager's eventBuffer containers. 785 // Of course, clients only see this if they request events with a 786 // 'since_time' argument of a time earlier than the events occurred. 787 manager, _ := createCustomManager(120, 100, true) 788 subscriptionHandler := ajaxHandler(manager.SubscriptionHandler) 789 790 startTime := time.Now() 791 time.Sleep(500 * time.Millisecond) 792 manager.Publish("veggies", "broccoli") 793 time.Sleep(500 * time.Millisecond) 794 manager.Publish("veggies", "corn") 795 time.Sleep(500 * time.Millisecond) 796 797 // This request clearly takes place after the two events were published. 798 // But we ask for any events since the start of this test case 799 req, _ := http.NewRequest("GET", fmt.Sprintf("?timeout=2&category=veggies&since_time=%d", 800 startTime.UnixNano()/int64(time.Millisecond)), nil) 801 w := NewCloseNotifierRecorder() 802 subscriptionHandler.ServeHTTP(w, req) 803 804 // Confirm we got the correct event 805 if w.Code != http.StatusOK { 806 t.Errorf("SubscriptionHandler didn't return %v", http.StatusOK) 807 } 808 var eventResponse eventResponse 809 if err := json.Unmarshal(w.Body.Bytes(), &eventResponse); err != nil { 810 t.Errorf("Failed to decode json: %q", err) 811 } 812 if len(*eventResponse.Events) != 2 { 813 t.Errorf("Unexpected number of events. Expected: %d, got: %d", 2, len(*eventResponse.Events)) 814 } 815 if (*eventResponse.Events)[0].Category != "veggies" { 816 t.Errorf("Unexpected category. Expected: %q, got: %q", "veggies", (*eventResponse.Events)[0].Category) 817 } 818 if (*eventResponse.Events)[0].Data != "broccoli" { 819 t.Errorf("Unexpected data. Expected: %q, got: %q", "broccoli", (*eventResponse.Events)[0].Data) 820 } 821 if (*eventResponse.Events)[1].Category != "veggies" { 822 t.Errorf("Unexpected category. Expected: %q, got: %q", "veggies", (*eventResponse.Events)[1].Category) 823 } 824 if (*eventResponse.Events)[1].Data != "corn" { 825 t.Errorf("Unexpected data. Expected: %q, got: %q", "corn", (*eventResponse.Events)[1].Data) 826 } 827 828 // Don't forget to kill our pubsub manager's run goroutine 829 manager.Shutdown() 830 831 } 832 833 func Test_LongpollManager_makeTimeoutResponse(t *testing.T) { 834 now := time.Now() 835 timeoutResp := makeTimeoutResponse(now) 836 timeoutTime := now.UnixNano() / int64(time.Millisecond) 837 if timeoutResp.TimeoutMessage != "no events before timeout" { 838 t.Errorf("Unexpected timeout message: %q", timeoutResp.TimeoutMessage) 839 } 840 if timeoutResp.Timestamp != timeoutTime { 841 t.Errorf("Unexpected timeout timestamp. Expected: %q, got: %q", 842 timeoutTime, timeoutResp.Timestamp) 843 } 844 } 845 846 func Test_LongpollManager_StartLongpoll_Options(t *testing.T) { 847 // Error cases due to invalid options: 848 if _, err := StartLongpoll(Options{ 849 LoggingEnabled: true, 850 MaxLongpollTimeoutSeconds: 120, 851 MaxEventBufferSize: -1, 852 EventTimeToLiveSeconds: 1, 853 }); err == nil { 854 t.Errorf("Expected error when passing MaxEventBufferSize that was < 0") 855 } 856 if _, err := StartLongpoll(Options{ 857 LoggingEnabled: true, 858 MaxLongpollTimeoutSeconds: -1, 859 MaxEventBufferSize: 100, 860 EventTimeToLiveSeconds: 1, 861 }); err == nil { 862 t.Errorf("Expected error when passing MaxLongpollTimeoutSeconds that was < 0") 863 } 864 if _, err := StartLongpoll(Options{ 865 LoggingEnabled: true, 866 MaxLongpollTimeoutSeconds: 120, 867 MaxEventBufferSize: 100, 868 EventTimeToLiveSeconds: -1, 869 }); err == nil { 870 t.Errorf("Expected error when passing EventTimeToLiveSeconds that was < 0") 871 } 872 // Confirm valid options work 873 // actual TTL 874 if manager, err := StartLongpoll(Options{ 875 LoggingEnabled: true, 876 MaxLongpollTimeoutSeconds: 120, 877 MaxEventBufferSize: 100, 878 EventTimeToLiveSeconds: 30, 879 }); err != nil { 880 t.Errorf("Unxpected error when calling StartLongpoll with valid options") 881 } else { 882 manager.Shutdown() 883 } 884 885 // Forever 886 if manager, err := StartLongpoll(Options{ 887 LoggingEnabled: true, 888 MaxLongpollTimeoutSeconds: 120, 889 MaxEventBufferSize: 100, 890 EventTimeToLiveSeconds: FOREVER, 891 }); err != nil { 892 t.Errorf("Unxpected error when calling StartLongpoll with valid options") 893 } else { 894 manager.Shutdown() 895 } 896 // Confirm zero TTL converts to forever 897 if manager, err := StartLongpoll(Options{ 898 LoggingEnabled: true, 899 MaxLongpollTimeoutSeconds: 120, 900 MaxEventBufferSize: 100, 901 EventTimeToLiveSeconds: 0, 902 }); err != nil { 903 t.Errorf("Unxpected error when calling StartLongpoll with valid options") 904 } else { 905 if manager.subManager.EventTimeToLiveSeconds != FOREVER { 906 t.Errorf("Expected default of FOREVER when EventTimeToLiveSeconds is 0. instead: %d", 907 manager.subManager.EventTimeToLiveSeconds) 908 } 909 manager.Shutdown() 910 } 911 // Confirm defaults for options set to zero value 912 // either explicitly like so: 913 if manager, err := StartLongpoll(Options{ 914 LoggingEnabled: false, 915 MaxLongpollTimeoutSeconds: 0, 916 MaxEventBufferSize: 0, 917 EventTimeToLiveSeconds: 0, 918 DeleteEventAfterFirstRetrieval: false, 919 }); err != nil { 920 t.Errorf("Unxpected error when calling StartLongpoll with valid options") 921 } else { 922 if manager.subManager.EventTimeToLiveSeconds != FOREVER { 923 t.Errorf("Expected default of FOREVER when EventTimeToLiveSeconds is 0. instead: %d", 924 manager.subManager.EventTimeToLiveSeconds) 925 } 926 if manager.subManager.MaxLongpollTimeoutSeconds != 120 { 927 t.Errorf("Expected default of 120 when MaxLongpollTimeoutSeconds is 0. instead: %d", 928 manager.subManager.MaxLongpollTimeoutSeconds) 929 } 930 if manager.subManager.MaxEventBufferSize != 250 { 931 t.Errorf("Expected default of 250 when MaxEventBufferSize is 0. instead: %d", 932 manager.subManager.MaxEventBufferSize) 933 } 934 if manager.subManager.LoggingEnabled != false { 935 t.Errorf("Expected default of false when LoggingEnabled is left out. instead: %t", 936 manager.subManager.LoggingEnabled) 937 } 938 if manager.subManager.DeleteEventAfterFirstRetrieval != false { 939 t.Errorf("Expected default of false when DeleteEventAfterFirstRetrieval is left out. instead: %t", 940 manager.subManager.DeleteEventAfterFirstRetrieval) 941 } 942 manager.Shutdown() 943 } 944 945 // or implicitly by never defining them: 946 if manager, err := StartLongpoll(Options{}); err != nil { 947 t.Errorf("Unxpected error when calling StartLongpoll with valid options") 948 } else { 949 if manager.subManager.EventTimeToLiveSeconds != FOREVER { 950 t.Errorf("Expected default of FOREVER when EventTimeToLiveSeconds is 0. instead: %d", 951 manager.subManager.EventTimeToLiveSeconds) 952 } 953 if manager.subManager.MaxLongpollTimeoutSeconds != 120 { 954 t.Errorf("Expected default of 120 when MaxLongpollTimeoutSeconds is 0. instead: %d", 955 manager.subManager.MaxLongpollTimeoutSeconds) 956 } 957 if manager.subManager.MaxEventBufferSize != 250 { 958 t.Errorf("Expected default of 250 when MaxEventBufferSize is 0. instead: %d", 959 manager.subManager.MaxEventBufferSize) 960 } 961 if manager.subManager.LoggingEnabled != false { 962 t.Errorf("Expected default of false when LoggingEnabled is left out. instead: %t", 963 manager.subManager.LoggingEnabled) 964 } 965 if manager.subManager.DeleteEventAfterFirstRetrieval != false { 966 t.Errorf("Expected default of false when DeleteEventAfterFirstRetrieval is left out. instead: %t", 967 manager.subManager.DeleteEventAfterFirstRetrieval) 968 } 969 manager.Shutdown() 970 } 971 972 } 973 974 func Test_LongpollManager_EventExpiration(t *testing.T) { 975 manager, _ := StartLongpoll(Options{ 976 LoggingEnabled: true, 977 MaxLongpollTimeoutSeconds: 120, 978 MaxEventBufferSize: 100, 979 EventTimeToLiveSeconds: 1, 980 }) 981 sm := manager.subManager 982 if len(sm.SubEventBuffer) != 0 { 983 t.Errorf("Unexpected category-to-buffer map size. was: %d, expected %d", 984 len(sm.SubEventBuffer), 0) 985 } 986 if sm.bufferPriorityQueue.Len() != 0 { 987 t.Errorf("Unexpected heap size. was: %d, expected: %d", sm.bufferPriorityQueue.Len(), 0) 988 } 989 manager.Publish("fruit", "apple") 990 time.Sleep(10 * time.Millisecond) 991 manager.Publish("veggie", "corn") 992 time.Sleep(750 * time.Millisecond) 993 manager.Publish("fruit", "orange") 994 // Allow sub manager's goroutine time to pull from channel. 995 // This sleep should cause us to yield and let the other goroutine run 996 time.Sleep(50 * time.Millisecond) 997 // Only ~800ms has went by, nothing should be expired out yet, so confirm 998 // all data is there 999 if len(sm.SubEventBuffer) != 2 { 1000 t.Errorf("Unexpected category-to-buffer map size. was: %d, expected %d", 1001 len(sm.SubEventBuffer), 2) 1002 } 1003 fruit_buffer, fruit_found := sm.SubEventBuffer["fruit"] 1004 veggie_buffer, veggies_found := sm.SubEventBuffer["veggie"] 1005 if !fruit_found || !veggies_found { 1006 t.Errorf("failed to find fruit and veggie category event buffers") 1007 } 1008 if fruit_buffer.eventBuffer_ptr.List.Len() != 2 { 1009 t.Errorf("Unexpected number of fruit events. was: %d, expected %d", 1010 fruit_buffer.eventBuffer_ptr.List.Len(), 2) 1011 } 1012 if veggie_buffer.eventBuffer_ptr.List.Len() != 1 { 1013 t.Errorf("Unexpected number of veggie events. was: %d, expected %d", 1014 veggie_buffer.eventBuffer_ptr.List.Len(), 1) 1015 } 1016 if sm.bufferPriorityQueue.Len() != 2 { 1017 t.Errorf("Unexpected heap size. was: %d, expected: %d", sm.bufferPriorityQueue.Len(), 2) 1018 } 1019 // Confirm top of heap is the veggie category since veggie is the category 1020 // with the oldest last-event (even tho fruit was started first, it has a 1021 // more recent event published on it--the heap sorts categories by how old 1022 // each categories most recent event is) 1023 if priority, peakErr := sm.bufferPriorityQueue.peakTopPriority(); peakErr != nil { 1024 t.Errorf("Unexpected error checking top priority: %v", peakErr) 1025 } else { 1026 if priority != veggie_buffer.eventBuffer_ptr.List.Front().Value.(*lpEvent).Timestamp { 1027 t.Errorf("Expected priority to be: %d, was: %d", priority, 1028 veggie_buffer.eventBuffer_ptr.List.Front().Value.(*lpEvent).Timestamp) 1029 } 1030 } 1031 // Now wait long enough for the first two published events to expire 1032 time.Sleep(220 * time.Millisecond) 1033 // NOTE how nothing was removed yet, because we do lazy-eval and only remove 1034 // expired stuff when activity occurs (but we do do a periodic check in 1035 // addition, but thats every 3 min by default.) 1036 if len(sm.SubEventBuffer) != 2 { 1037 t.Errorf("Unexpected category-to-buffer map size. was: %d, expected %d", 1038 len(sm.SubEventBuffer), 2) 1039 } 1040 fruit_buffer, fruit_found = sm.SubEventBuffer["fruit"] 1041 veggie_buffer, veggies_found = sm.SubEventBuffer["veggie"] 1042 if !fruit_found || !veggies_found { 1043 t.Errorf("failed to find fruit and veggie category event buffers") 1044 } 1045 if fruit_buffer.eventBuffer_ptr.List.Len() != 2 { 1046 t.Errorf("Unexpected number of fruit events. was: %d, expected %d", 1047 fruit_buffer.eventBuffer_ptr.List.Len(), 2) 1048 } 1049 if veggie_buffer.eventBuffer_ptr.List.Len() != 1 { 1050 t.Errorf("Unexpected number of veggie events. was: %d, expected %d", 1051 veggie_buffer.eventBuffer_ptr.List.Len(), 1) 1052 } 1053 if sm.bufferPriorityQueue.Len() != 2 { 1054 t.Errorf("Unexpected heap size. was: %d, expected: %d", sm.bufferPriorityQueue.Len(), 2) 1055 } 1056 if priority, peakErr := sm.bufferPriorityQueue.peakTopPriority(); peakErr != nil { 1057 t.Errorf("Unexpected error checking top priority: %v", peakErr) 1058 } else { 1059 if priority != veggie_buffer.eventBuffer_ptr.List.Front().Value.(*lpEvent).Timestamp { 1060 t.Errorf("Expected priority to be: %d, was: %d", priority, 1061 veggie_buffer.eventBuffer_ptr.List.Front().Value.(*lpEvent).Timestamp) 1062 } 1063 } 1064 // Force the fruit event to be expired out by introducing activity on the 1065 // fruit category. (In this case, a new event) 1066 manager.Publish("fruit", "pear") 1067 // Allow sub manager's goroutine time to pull from channel. 1068 // This sleep should cause us to yield and let the other goroutine run 1069 time.Sleep(50 * time.Millisecond) 1070 if len(sm.SubEventBuffer) != 2 { 1071 t.Errorf("Unexpected category-to-buffer map size. was: %d, expected %d", 1072 len(sm.SubEventBuffer), 2) 1073 } 1074 fruit_buffer, fruit_found = sm.SubEventBuffer["fruit"] 1075 veggie_buffer, veggies_found = sm.SubEventBuffer["veggie"] 1076 if !fruit_found || !veggies_found { 1077 t.Errorf("failed to find fruit and veggie category event buffers") 1078 } 1079 // NOTE: fruit buffer has still only 2 events, not 3, because the one was 1080 // expired out 1081 if fruit_buffer.eventBuffer_ptr.List.Len() != 2 { 1082 t.Errorf("Unexpected number of fruit events. was: %d, expected %d", 1083 fruit_buffer.eventBuffer_ptr.List.Len(), 2) 1084 } 1085 if veggie_buffer.eventBuffer_ptr.List.Len() != 1 { 1086 t.Errorf("Unexpected number of veggie events. was: %d, expected %d", 1087 veggie_buffer.eventBuffer_ptr.List.Len(), 1) 1088 } 1089 if sm.bufferPriorityQueue.Len() != 2 { 1090 t.Errorf("Unexpected heap size. was: %d, expected: %d", sm.bufferPriorityQueue.Len(), 2) 1091 } 1092 // veggie buffer is still the oldest-newest-event category 1093 if priority, peakErr := sm.bufferPriorityQueue.peakTopPriority(); peakErr != nil { 1094 t.Errorf("Unexpected error checking top priority: %v", peakErr) 1095 } else { 1096 if priority != veggie_buffer.eventBuffer_ptr.List.Front().Value.(*lpEvent).Timestamp { 1097 t.Errorf("Expected priority to be: %d, was: %d", priority, 1098 veggie_buffer.eventBuffer_ptr.List.Front().Value.(*lpEvent).Timestamp) 1099 } 1100 } 1101 // Force the veggie event to be expired out by introducing activity on the 1102 // veggie category. (In this case, a client requests a longpoll) 1103 // also confirm longpoll doesn't return the now-expired events, even if 1104 // client using a really old since param 1105 subscriptionHandler := ajaxHandler(manager.SubscriptionHandler) 1106 req, _ := http.NewRequest("GET", "?timeout=2&category=veggie", nil) 1107 w := NewCloseNotifierRecorder() 1108 subscriptionHandler.ServeHTTP(w, req) 1109 if w.Code != http.StatusOK { 1110 t.Errorf("SubscriptionHandler didn't return %v", http.StatusOK) 1111 } 1112 approxTimeoutTime := time.Now().UnixNano() / int64(time.Millisecond) 1113 var timeoutResp timeoutResponse 1114 if err := json.Unmarshal(w.Body.Bytes(), &timeoutResp); err != nil { 1115 t.Errorf("Failed to decode json: %q", err) 1116 } 1117 if timeoutResp.TimeoutMessage != "no events before timeout" { 1118 t.Errorf("Unexpected timeout message: %q", timeoutResp.TimeoutMessage) 1119 } 1120 if timeoutResp.Timestamp < (approxTimeoutTime-100) || 1121 timeoutResp.Timestamp > approxTimeoutTime { 1122 t.Errorf("Unexpected timeout timestamp. Expected: %q, got: %q", 1123 approxTimeoutTime, timeoutResp.Timestamp) 1124 } 1125 // Now confirm veggie category removed--only one category: fruit 1126 // and in that category, only 2 events left 1127 if len(sm.SubEventBuffer) != 1 { 1128 t.Errorf("Unexpected category-to-buffer map size. was: %d, expected %d", 1129 len(sm.SubEventBuffer), 1) 1130 } 1131 fruit_buffer, fruit_found = sm.SubEventBuffer["fruit"] 1132 veggie_buffer, veggies_found = sm.SubEventBuffer["veggie"] 1133 1134 if !fruit_found || veggies_found { 1135 t.Errorf("Fruit should be found, veggies should not.") 1136 } 1137 // NOTE: fruit buffer has still has last two published fruit events. 1138 if fruit_buffer.eventBuffer_ptr.List.Len() != 2 { 1139 t.Errorf("Unexpected number of fruit events. was: %d, expected %d", 1140 fruit_buffer.eventBuffer_ptr.List.Len(), 2) 1141 } 1142 if sm.bufferPriorityQueue.Len() != 1 { 1143 t.Errorf("Unexpected heap size. was: %d, expected: %d", sm.bufferPriorityQueue.Len(), 1) 1144 } 1145 // fruit buffer is now the oldest most-recent-event-time buffer (and the only one) 1146 if priority, peakErr := sm.bufferPriorityQueue.peakTopPriority(); peakErr != nil { 1147 t.Errorf("Unexpected error checking top priority: %v", peakErr) 1148 } else { 1149 if priority != fruit_buffer.eventBuffer_ptr.List.Front().Value.(*lpEvent).Timestamp { 1150 t.Errorf("Expected priority to be: %d, was: %d", priority, 1151 fruit_buffer.eventBuffer_ptr.List.Front().Value.(*lpEvent).Timestamp) 1152 } 1153 } 1154 // Now force the expire check on the last two fruit events. 1155 // Enough time has elapsed that everything should be gone by now. 1156 req, _ = http.NewRequest("GET", "?timeout=1&category=fruit", nil) 1157 w = NewCloseNotifierRecorder() 1158 subscriptionHandler.ServeHTTP(w, req) 1159 if w.Code != http.StatusOK { 1160 t.Errorf("SubscriptionHandler didn't return %v", http.StatusOK) 1161 } 1162 approxTimeoutTime = time.Now().UnixNano() / int64(time.Millisecond) 1163 if err := json.Unmarshal(w.Body.Bytes(), &timeoutResp); err != nil { 1164 t.Errorf("Failed to decode json: %q", err) 1165 } 1166 if timeoutResp.TimeoutMessage != "no events before timeout" { 1167 t.Errorf("Unexpected timeout message: %q", timeoutResp.TimeoutMessage) 1168 } 1169 if timeoutResp.Timestamp < (approxTimeoutTime-100) || 1170 timeoutResp.Timestamp > approxTimeoutTime { 1171 t.Errorf("Unexpected timeout timestamp. Expected: %q, got: %q", 1172 approxTimeoutTime, timeoutResp.Timestamp) 1173 } 1174 // Now confirm veggie category removed--only one category: fruit 1175 // and in that category, only 2 events left 1176 if len(sm.SubEventBuffer) != 0 { 1177 t.Errorf("Unexpected category-to-buffer map size. was: %d, expected %d", 1178 len(sm.SubEventBuffer), 0) 1179 } 1180 _, fruit_found = sm.SubEventBuffer["fruit"] 1181 _, veggies_found = sm.SubEventBuffer["veggie"] 1182 1183 if fruit_found || veggies_found { 1184 t.Errorf("Both fruit and veggie buffers should no longer exist in map.") 1185 } 1186 if sm.bufferPriorityQueue.Len() != 0 { 1187 t.Errorf("Unexpected heap size. was: %d, expected: %d", sm.bufferPriorityQueue.Len(), 0) 1188 } 1189 // fruit buffer is now the oldest most-recent-event-time buffer (and the only one) 1190 if priority, peakErr := sm.bufferPriorityQueue.peakTopPriority(); peakErr == nil { 1191 t.Errorf("Expected error when peaking at top of empty queue.") 1192 } else { 1193 if priority != -1 { 1194 t.Errorf("Expected priority to be the -1 error value, instead it was: %d", priority) 1195 } 1196 } 1197 manager.Shutdown() 1198 } 1199 1200 // Shared by multiple tests with manager configured different ways: 1201 func deleteOnFetchTest(manager *LongpollManager, t *testing.T) { 1202 sm := manager.subManager 1203 if len(sm.SubEventBuffer) != 0 { 1204 t.Errorf("Unexpected category-to-buffer map size. was: %d, expected %d", 1205 len(sm.SubEventBuffer), 0) 1206 } 1207 if sm.EventTimeToLiveSeconds != FOREVER && sm.bufferPriorityQueue.Len() != 0 { 1208 t.Errorf("Unexpected heap size. was: %d, expected: %d", sm.bufferPriorityQueue.Len(), 0) 1209 } 1210 manager.Publish("fruit", "apple") 1211 time.Sleep(10 * time.Millisecond) 1212 manager.Publish("veggie", "corn") 1213 time.Sleep(1150 * time.Millisecond) 1214 manager.Publish("fruit", "orange") 1215 time.Sleep(10 * time.Millisecond) 1216 manager.Publish("veggie", "carrot") 1217 // small wait so yield occurs and other goroutine gets a chance 1218 time.Sleep(50 * time.Millisecond) 1219 // Confirm expected state: 1220 if len(sm.SubEventBuffer) != 2 { 1221 t.Errorf("Unexpected category-to-buffer map size. was: %d, expected %d", 1222 len(sm.SubEventBuffer), 2) 1223 } 1224 fruit_buffer, fruit_found := sm.SubEventBuffer["fruit"] 1225 veggie_buffer, veggies_found := sm.SubEventBuffer["veggie"] 1226 if !fruit_found || !veggies_found { 1227 t.Errorf("failed to find fruit and veggie category event buffers") 1228 } 1229 if fruit_buffer.eventBuffer_ptr.List.Len() != 2 { 1230 t.Errorf("Unexpected number of fruit events. was: %d, expected %d", 1231 fruit_buffer.eventBuffer_ptr.List.Len(), 2) 1232 } 1233 if veggie_buffer.eventBuffer_ptr.List.Len() != 2 { 1234 t.Errorf("Unexpected number of veggie events. was: %d, expected %d", 1235 veggie_buffer.eventBuffer_ptr.List.Len(), 2) 1236 } 1237 if sm.EventTimeToLiveSeconds != FOREVER && sm.bufferPriorityQueue.Len() != 2 { 1238 t.Errorf("Unexpected heap size. was: %d, expected: %d", sm.bufferPriorityQueue.Len(), 2) 1239 } 1240 // Confirm top of heap is the fruit category since fruit is the category 1241 // with the oldest last-event 1242 var priority_before_removal int64 1243 // Only check heap if we're using it. When no TTL, heap is not used. 1244 if sm.EventTimeToLiveSeconds != FOREVER { 1245 if priority, peakErr := sm.bufferPriorityQueue.peakTopPriority(); peakErr != nil { 1246 t.Errorf("Unexpected error checking top priority: %v", peakErr) 1247 } else { 1248 if priority != fruit_buffer.eventBuffer_ptr.List.Front().Value.(*lpEvent).Timestamp { 1249 t.Errorf("Expected priority to be: %d, was: %d", priority, 1250 fruit_buffer.eventBuffer_ptr.List.Front().Value.(*lpEvent).Timestamp) 1251 } 1252 priority_before_removal = priority 1253 } 1254 } 1255 1256 // Now let's do a longpoll on the fruit category asking for events less 1257 // than 1s old, confirm the most recent fruit (orange) was removed 1258 since_time := time.Now().Add(-1*time.Second).UnixNano() / int64(time.Millisecond) 1259 subscriptionHandler := ajaxHandler(manager.SubscriptionHandler) 1260 req, _ := http.NewRequest("GET", fmt.Sprintf("?timeout=1&category=fruit&since_time=%d", since_time), nil) 1261 w := NewCloseNotifierRecorder() 1262 subscriptionHandler.ServeHTTP(w, req) 1263 if w.Code != http.StatusOK { 1264 t.Errorf("SubscriptionHandler didn't return %v", http.StatusOK) 1265 } 1266 var eventResponse eventResponse 1267 if err := json.Unmarshal(w.Body.Bytes(), &eventResponse); err != nil { 1268 t.Errorf("Failed to decode json: %q", err) 1269 } 1270 if len(*eventResponse.Events) != 1 { 1271 t.Errorf("Unexpected number of events. Expected: %d, got: %d", 1, len(*eventResponse.Events)) 1272 } 1273 if (*eventResponse.Events)[0].Category != "fruit" { 1274 t.Errorf("Unexpected category. Expected: %q, got: %q", "fruit", (*eventResponse.Events)[0].Category) 1275 } 1276 if (*eventResponse.Events)[0].Data != "orange" { 1277 t.Errorf("Unexpected data. Expected: %q, got: %q", "orange", (*eventResponse.Events)[0].Data) 1278 } 1279 // Also confirm that orange is now gone out of the buffer 1280 if len(sm.SubEventBuffer) != 2 { 1281 t.Errorf("Unexpected category-to-buffer map size. was: %d, expected %d", 1282 len(sm.SubEventBuffer), 2) 1283 } 1284 fruit_buffer, fruit_found = sm.SubEventBuffer["fruit"] 1285 veggie_buffer, veggies_found = sm.SubEventBuffer["veggie"] 1286 if !fruit_found || !veggies_found { 1287 t.Errorf("failed to find fruit and veggie category event buffers") 1288 } 1289 if fruit_buffer.eventBuffer_ptr.List.Len() != 1 { 1290 t.Errorf("Unexpected number of fruit events. was: %d, expected %d", 1291 fruit_buffer.eventBuffer_ptr.List.Len(), 1) 1292 } 1293 if fruit_buffer.eventBuffer_ptr.List.Front().Value.(*lpEvent).Data.(string) != "apple" { 1294 t.Errorf("Unexpected event left in fruit buffer. was: %s, expected: %s.", 1295 fruit_buffer.eventBuffer_ptr.List.Front().Value.(*lpEvent).Data.(string), "apple") 1296 } 1297 if veggie_buffer.eventBuffer_ptr.List.Len() != 2 { 1298 t.Errorf("Unexpected number of veggie events. was: %d, expected %d", 1299 veggie_buffer.eventBuffer_ptr.List.Len(), 2) 1300 } 1301 if sm.EventTimeToLiveSeconds != FOREVER && sm.bufferPriorityQueue.Len() != 2 { 1302 t.Errorf("Unexpected heap size. was: %d, expected: %d", sm.bufferPriorityQueue.Len(), 2) 1303 } 1304 if sm.EventTimeToLiveSeconds != FOREVER { 1305 // NOTE: the heap priority doesn't change when an event is removed due 1306 // to the DeleteEventAfterFirstRetrieval setting. This is by design because 1307 // it is complicated to know what to update the priority to, and it doesn't 1308 // harm or break the other behavior to skip updating it, the worst that 1309 // happens is a frivolous expiration check that removes nothing. 1310 if priority, peakErr := sm.bufferPriorityQueue.peakTopPriority(); peakErr != nil { 1311 t.Errorf("Unexpected error checking top priority: %v", peakErr) 1312 } else { 1313 if priority != priority_before_removal { 1314 t.Errorf("Expected priority to be: %d, was: %d", priority, 1315 priority_before_removal) 1316 } 1317 } 1318 } 1319 1320 // Now request all veggie events (since beginning of time), confirm all 1321 // veggies removed 1322 since_time = time.Now().Add(-60*time.Second).UnixNano() / int64(time.Millisecond) 1323 subscriptionHandler = ajaxHandler(manager.SubscriptionHandler) 1324 req, _ = http.NewRequest("GET", fmt.Sprintf("?timeout=1&category=veggie&since_time=%d", since_time), nil) 1325 w = NewCloseNotifierRecorder() 1326 subscriptionHandler.ServeHTTP(w, req) 1327 if w.Code != http.StatusOK { 1328 t.Errorf("SubscriptionHandler didn't return %v", http.StatusOK) 1329 } 1330 if err := json.Unmarshal(w.Body.Bytes(), &eventResponse); err != nil { 1331 t.Errorf("Failed to decode json: %q", err) 1332 } 1333 if len(*eventResponse.Events) != 2 { 1334 t.Errorf("Unexpected number of events. Expected: %d, got: %d", 2, len(*eventResponse.Events)) 1335 } 1336 if (*eventResponse.Events)[0].Category != "veggie" { 1337 t.Errorf("Unexpected category. Expected: %q, got: %q", "veggie", (*eventResponse.Events)[0].Category) 1338 } 1339 if (*eventResponse.Events)[0].Data != "corn" { 1340 t.Errorf("Unexpected data. Expected: %q, got: %q", "corn", (*eventResponse.Events)[0].Data) 1341 } 1342 if (*eventResponse.Events)[1].Category != "veggie" { 1343 t.Errorf("Unexpected category. Expected: %q, got: %q", "veggie", (*eventResponse.Events)[0].Category) 1344 } 1345 if (*eventResponse.Events)[1].Data != "carrot" { 1346 t.Errorf("Unexpected data. Expected: %q, got: %q", "carrot", (*eventResponse.Events)[0].Data) 1347 } 1348 time.Sleep(50 * time.Millisecond) 1349 if len(sm.SubEventBuffer) != 1 { 1350 t.Errorf("Unexpected category-to-buffer map size. was: %d, expected %d", 1351 len(sm.SubEventBuffer), 1) 1352 } 1353 fruit_buffer, fruit_found = sm.SubEventBuffer["fruit"] 1354 veggie_buffer, veggies_found = sm.SubEventBuffer["veggie"] 1355 if !fruit_found || veggies_found { 1356 t.Errorf("expected fruit to be found but not veggies") 1357 } 1358 1359 if fruit_buffer.eventBuffer_ptr.List.Len() != 1 { 1360 t.Errorf("Unexpected number of fruit events. was: %d, expected %d", 1361 fruit_buffer.eventBuffer_ptr.List.Len(), 1) 1362 } 1363 if fruit_buffer.eventBuffer_ptr.List.Front().Value.(*lpEvent).Data.(string) != "apple" { 1364 t.Errorf("Unexpected event left in fruit buffer. was: %s, expected: %s.", 1365 fruit_buffer.eventBuffer_ptr.List.Front().Value.(*lpEvent).Data.(string), "apple") 1366 } 1367 if sm.EventTimeToLiveSeconds != FOREVER { 1368 // Heap still not changed for same reasons as before 1369 if priority, peakErr := sm.bufferPriorityQueue.peakTopPriority(); peakErr != nil { 1370 t.Errorf("Unexpected error checking top priority: %v", peakErr) 1371 } else { 1372 if priority != priority_before_removal { 1373 t.Errorf("Expected priority to be: %d, was: %d", priority, 1374 priority_before_removal) 1375 } 1376 } 1377 } 1378 } 1379 1380 func Test_LongpollManager_DeleteOnFetch(t *testing.T) { 1381 manager, _ := StartLongpoll(Options{ 1382 LoggingEnabled: true, 1383 MaxLongpollTimeoutSeconds: 120, 1384 MaxEventBufferSize: 100, 1385 EventTimeToLiveSeconds: 60, 1386 DeleteEventAfterFirstRetrieval: true, 1387 }) 1388 deleteOnFetchTest(manager, t) 1389 } 1390 1391 func Test_LongpollManager_DeleteOnFetch_ForeverTTL(t *testing.T) { 1392 manager, _ := StartLongpoll(Options{ 1393 LoggingEnabled: true, 1394 MaxLongpollTimeoutSeconds: 120, 1395 MaxEventBufferSize: 100, 1396 EventTimeToLiveSeconds: FOREVER, 1397 DeleteEventAfterFirstRetrieval: true, 1398 }) 1399 deleteOnFetchTest(manager, t) 1400 } 1401 1402 func Test_LongpollManager_DeleteOnFetch_SkipBuffering(t *testing.T) { 1403 // Publish an event while a client is in the middle of a longpoll and 1404 // confirm that the event was received by the client and never buffered 1405 // on the server. 1406 manager, _ := StartLongpoll(Options{ 1407 LoggingEnabled: true, 1408 MaxLongpollTimeoutSeconds: 120, 1409 MaxEventBufferSize: 100, 1410 EventTimeToLiveSeconds: 60 * 10, 1411 DeleteEventAfterFirstRetrieval: true, 1412 }) 1413 sm := manager.subManager 1414 subscriptionHandler := ajaxHandler(manager.SubscriptionHandler) 1415 1416 if len(sm.SubEventBuffer) != 0 { 1417 t.Errorf("Unexpected category-to-buffer map size. was: %d, expected %d", 1418 len(sm.SubEventBuffer), 0) 1419 } 1420 if sm.EventTimeToLiveSeconds != FOREVER && sm.bufferPriorityQueue.Len() != 0 { 1421 t.Errorf("Unexpected heap size. was: %d, expected: %d", sm.bufferPriorityQueue.Len(), 0) 1422 } 1423 1424 // Valid request, but we don't have any events published, 1425 // so this will wait until a publish or a timeout, in this case we'll get 1426 // an event. 1427 req, _ := http.NewRequest("GET", "?timeout=30&category=fruit", nil) 1428 w := NewCloseNotifierRecorder() 1429 // Publish two events, only the second is for our subscription category 1430 // Note how these events occur after the client subscribed 1431 // if they occurred before, since we don't provide a since_time url param 1432 // we'd default to now and skip those events. 1433 go func() { 1434 time.Sleep(1500 * time.Millisecond) 1435 manager.Publish("fruit", "peach") 1436 }() 1437 subscriptionHandler.ServeHTTP(w, req) 1438 // Confirm we got the correct event 1439 if w.Code != http.StatusOK { 1440 t.Errorf("SubscriptionHandler didn't return %v", http.StatusOK) 1441 } 1442 var eventResponse eventResponse 1443 if err := json.Unmarshal(w.Body.Bytes(), &eventResponse); err != nil { 1444 t.Errorf("Failed to decode json: %q", err) 1445 } 1446 if len(*eventResponse.Events) != 1 { 1447 t.Errorf("Unexpected number of events. Expected: %d, got: %d", 1, len(*eventResponse.Events)) 1448 } 1449 if (*eventResponse.Events)[0].Category != "fruit" { 1450 t.Errorf("Unexpected category. Expected: %q, got: %q", "fruit", (*eventResponse.Events)[0].Category) 1451 } 1452 if (*eventResponse.Events)[0].Data != "peach" { 1453 t.Errorf("Unexpected data. Expected: %q, got: %q", "peach", (*eventResponse.Events)[0].Data) 1454 } 1455 // Ensure nothing was buffered: 1456 if len(sm.SubEventBuffer) != 0 { 1457 t.Errorf("Unexpected category-to-buffer map size. was: %d, expected %d", 1458 len(sm.SubEventBuffer), 0) 1459 } 1460 if sm.EventTimeToLiveSeconds != FOREVER && sm.bufferPriorityQueue.Len() != 0 { 1461 t.Errorf("Unexpected heap size. was: %d, expected: %d", sm.bufferPriorityQueue.Len(), 0) 1462 } 1463 // Don't forget to kill our pubsub manager's run goroutine 1464 manager.Shutdown() 1465 } 1466 1467 func Test_LongpollManager_PurgingOldCategories(t *testing.T) { 1468 // Confirm that old categories get removed by purge. 1469 // After any activity we check to see if it's time to call purge. 1470 manager, _ := StartLongpoll(Options{ 1471 LoggingEnabled: true, 1472 MaxLongpollTimeoutSeconds: 120, 1473 MaxEventBufferSize: 100, 1474 EventTimeToLiveSeconds: 1, 1475 }) 1476 sm := manager.subManager 1477 sm.staleCategoryPurgePeriodSeconds = 2 1478 if len(sm.SubEventBuffer) != 0 { 1479 t.Errorf("Unexpected category-to-buffer map size. was: %d, expected %d", 1480 len(sm.SubEventBuffer), 0) 1481 } 1482 if sm.bufferPriorityQueue.Len() != 0 { 1483 t.Errorf("Unexpected heap size. was: %d, expected: %d", sm.bufferPriorityQueue.Len(), 0) 1484 } 1485 manager.Publish("fruit", "apple") 1486 time.Sleep(10 * time.Millisecond) 1487 manager.Publish("fruit", "orange") 1488 time.Sleep(2000 * time.Millisecond) 1489 // It's now been over 2s since both fruit events published, and since our 1490 // TTL setting is 1s, these are both expired. 1491 // But confirm purge didn't happen yet even tho 2s (the purge period) has 1492 // elapsed. 1493 if len(sm.SubEventBuffer) != 1 { 1494 t.Errorf("Unexpected category-to-buffer map size. was: %d, expected %d", 1495 len(sm.SubEventBuffer), 1) 1496 } 1497 fruit_buffer, fruit_found := sm.SubEventBuffer["fruit"] 1498 veggie_buffer, veggies_found := sm.SubEventBuffer["veggie"] 1499 if !fruit_found || veggies_found { 1500 t.Errorf("should find fruit but not veggie buffer") 1501 } 1502 if fruit_buffer.eventBuffer_ptr.List.Len() != 2 { 1503 t.Errorf("Unexpected number of fruit events. was: %d, expected %d", 1504 fruit_buffer.eventBuffer_ptr.List.Len(), 2) 1505 } 1506 if sm.bufferPriorityQueue.Len() != 1 { 1507 t.Errorf("Unexpected heap size. was: %d, expected: %d", sm.bufferPriorityQueue.Len(), 1) 1508 } 1509 1510 // Publish an event which will force us to check if elapsed time is greater 1511 // than purge period and kick off a purge. 1512 manager.Publish("veggie", "corn") 1513 time.Sleep(50 * time.Millisecond) 1514 1515 // Confirm fruit events were destroyed 1516 if len(sm.SubEventBuffer) != 1 { 1517 t.Errorf("Unexpected category-to-buffer map size. was: %d, expected %d", 1518 len(sm.SubEventBuffer), 1) 1519 } 1520 fruit_buffer, fruit_found = sm.SubEventBuffer["fruit"] 1521 veggie_buffer, veggies_found = sm.SubEventBuffer["veggie"] 1522 if fruit_found || !veggies_found { 1523 t.Errorf("Expected to find veggies but not fruit buffer.") 1524 } 1525 if veggie_buffer.eventBuffer_ptr.List.Len() != 1 { 1526 t.Errorf("Unexpected number of veggie events. was: %d, expected %d", 1527 veggie_buffer.eventBuffer_ptr.List.Len(), 1) 1528 } 1529 if sm.bufferPriorityQueue.Len() != 1 { 1530 t.Errorf("Unexpected heap size. was: %d, expected: %d", sm.bufferPriorityQueue.Len(), 1) 1531 } 1532 } 1533 1534 func Test_LongpollManager_PurgingOldCategories_Inactivity(t *testing.T) { 1535 // Confirm that old categories get removed by purge even if there is no 1536 // activity going on. This purge is accomplished via the periodic 1537 // check regardless of activity. 1538 manager, _ := StartLongpoll(Options{ 1539 LoggingEnabled: true, 1540 MaxLongpollTimeoutSeconds: 120, 1541 MaxEventBufferSize: 100, 1542 EventTimeToLiveSeconds: 1, 1543 }) 1544 sm := manager.subManager 1545 sm.staleCategoryPurgePeriodSeconds = 2 1546 if len(sm.SubEventBuffer) != 0 { 1547 t.Errorf("Unexpected category-to-buffer map size. was: %d, expected %d", 1548 len(sm.SubEventBuffer), 0) 1549 } 1550 if sm.bufferPriorityQueue.Len() != 0 { 1551 t.Errorf("Unexpected heap size. was: %d, expected: %d", sm.bufferPriorityQueue.Len(), 0) 1552 } 1553 manager.Publish("fruit", "apple") 1554 time.Sleep(10 * time.Millisecond) 1555 manager.Publish("fruit", "orange") 1556 time.Sleep(2000 * time.Millisecond) 1557 // It's now been over 2s since both fruit events published, and since our 1558 // TTL setting is 1s, these are both expired. 1559 // But confirm purge didn't happen yet even tho 2s (the purge period) has 1560 // elapsed. 1561 if len(sm.SubEventBuffer) != 1 { 1562 t.Errorf("Unexpected category-to-buffer map size. was: %d, expected %d", 1563 len(sm.SubEventBuffer), 1) 1564 } 1565 fruit_buffer, fruit_found := sm.SubEventBuffer["fruit"] 1566 if !fruit_found { 1567 t.Errorf("should find fruit but not veggie buffer") 1568 } 1569 if fruit_buffer.eventBuffer_ptr.List.Len() != 2 { 1570 t.Errorf("Unexpected number of fruit events. was: %d, expected %d", 1571 fruit_buffer.eventBuffer_ptr.List.Len(), 2) 1572 } 1573 if sm.bufferPriorityQueue.Len() != 1 { 1574 t.Errorf("Unexpected heap size. was: %d, expected: %d", sm.bufferPriorityQueue.Len(), 1) 1575 } 1576 // Wait until we do our purge check due to inactivity, then confirm 1577 // data was cleared out. 1578 time.Sleep(3100 * time.Millisecond) 1579 1580 // Confirm fruit events were destroyed 1581 if len(sm.SubEventBuffer) != 0 { 1582 t.Errorf("Unexpected category-to-buffer map size. was: %d, expected %d", 1583 len(sm.SubEventBuffer), 0) 1584 } 1585 fruit_buffer, fruit_found = sm.SubEventBuffer["fruit"] 1586 if fruit_found { 1587 t.Errorf("Expected to not find fruit buffer.") 1588 } 1589 if sm.bufferPriorityQueue.Len() != 0 { 1590 t.Errorf("Unexpected heap size. was: %d, expected: %d", sm.bufferPriorityQueue.Len(), 0) 1591 } 1592 }