github.com/haraldrudell/parl@v0.4.176/nb-chan_test.go (about) 1 /* 2 © 2022–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package parl 7 8 import ( 9 "strconv" 10 "sync" 11 "sync/atomic" 12 "testing" 13 "time" 14 15 "github.com/haraldrudell/parl/perrors" 16 "github.com/haraldrudell/parl/pruntime" 17 "golang.org/x/exp/slices" 18 ) 19 20 // TestNBChan initialization-free [NBChan], [NBChan.Ch] 21 func TestNBChanNoNew(t *testing.T) { 22 var ch <-chan int 23 24 // NBChan no initialization, Ch() 25 var nbChan NBChan[int] 26 if ch = nbChan.Ch(); ch == nil { 27 t.Errorf("Ch returned nil") 28 } 29 30 // check for channel errors 31 if err := nbChan.GetError(); err != nil { 32 cL := pruntime.NewCodeLocation(0) 33 loc := cL.FuncIdentifier() + ":" + strconv.Itoa(cL.Line) 34 t.Errorf("%s nbChan.GetError: %s", loc, perrors.Short(err)) 35 } 36 } 37 38 // TestNBChanNew: [NewNBChan], [NBChan], [NBChan.Ch] 39 func TestNBChanNew(t *testing.T) { 40 var nbChan NBChan[int] 41 var ch <-chan int 42 43 nbChan = *NewNBChan[int]() 44 if ch = nbChan.Ch(); ch == nil { 45 t.Errorf("NewNBChan Ch returned nil") 46 } 47 48 // check for channel errors 49 if err := nbChan.GetError(); err != nil { 50 cL := pruntime.NewCodeLocation(0) 51 loc := cL.FuncIdentifier() + ":" + strconv.Itoa(cL.Line) 52 t.Errorf("%s nbChan.GetError: %s", loc, perrors.Short(err)) 53 } 54 } 55 56 // TestNBChanCount: [NBChan.Count] [NBChan.Send] 57 func TestNBChanCount(t *testing.T) { 58 var expCount0 = 0 59 var value1 = 3 60 var expCount1 = 1 61 62 var actualInt int 63 var nbChan NBChan[int] 64 65 // Count() 66 if actualInt = nbChan.Count(); actualInt != expCount0 { 67 t.Errorf("count0 %d exp %d", actualInt, expCount0) 68 } 69 nbChan.Send(value1) 70 if actualInt = nbChan.Count(); actualInt != expCount1 { 71 t.Errorf("count1 %d exp %d", actualInt, expCount1) 72 } 73 74 if err := nbChan.GetError(); err != nil { 75 cL := pruntime.NewCodeLocation(0) 76 loc := cL.FuncIdentifier() + ":" + strconv.Itoa(cL.Line) 77 t.Errorf("%s nbChan.GetError: %s", loc, perrors.Short(err)) 78 } 79 } 80 81 // TestNBChanScavenge: [NBChan.Scavenge] [NBChan.SendMany] 82 func TestNBChanScavenge(t *testing.T) { 83 var expLength0 = 0 84 var expCapacity0 = 0 85 var value1 = []int{3, 4} 86 var expLength1 = len(value1) 87 var expCapacity1 = defaultNBChanSize 88 var scavenge2 = 1 // minimum Scavenge value 89 var expLength2 = len(value1) 90 var expCapacity2 = len(value1) - 1 // 1 with thread, outputQueue unallocated 91 92 var actualLength, actualCapacity int 93 94 var nbChan NBChan[int] 95 96 // initial Scavenge 97 actualLength = nbChan.Count() 98 actualCapacity = nbChan.Capacity() 99 if actualLength != expLength0 { 100 t.Errorf("expLength0 %d exp %d", actualLength, expLength0) 101 } 102 if actualCapacity != expCapacity0 { 103 t.Errorf("expCapacity0 %d exp %d", actualLength, expCapacity0) 104 } 105 106 // default allocation size 10 107 nbChan.SendMany(value1) 108 actualLength = nbChan.Count() 109 actualCapacity = nbChan.Capacity() 110 if actualLength != expLength1 { 111 t.Errorf("expLength1 %d exp %d", actualLength, expLength1) 112 } 113 if actualCapacity != expCapacity1 { 114 t.Errorf("expCapacity1 %d exp %d", actualCapacity, expCapacity1) 115 } 116 nbChan.Scavenge(scavenge2) // scavenge to minimum size 117 actualLength = nbChan.Count() 118 actualCapacity = nbChan.Capacity() 119 if actualLength != expLength2 { 120 t.Errorf("expLength2 %d exp %d", actualLength, expLength2) 121 } 122 if actualCapacity != expCapacity2 { 123 t.Errorf("expCapacity2 %d exp %d", actualCapacity, expCapacity2) 124 } 125 126 if err := nbChan.GetError(); err != nil { 127 cL := pruntime.NewCodeLocation(0) 128 loc := cL.FuncIdentifier() + ":" + strconv.Itoa(cL.Line) 129 t.Errorf("%s nbChan.GetError: %s", loc, perrors.Short(err)) 130 } 131 } 132 133 // [NBChan.SetAllocationSize] 134 func TestNBChanSetAllocationSize(t *testing.T) { 135 var size = 100 136 var value1 = []int{3, 4} 137 var expLength1 = len(value1) 138 var expCapacity1 = size 139 140 var actualLength, actualCapacity int 141 142 var nbChan NBChan[int] 143 nbChan.SetAllocationSize(size).SendMany(value1) 144 actualLength = nbChan.Count() 145 actualCapacity = nbChan.Capacity() 146 if actualLength != expLength1 { 147 t.Errorf("expLength1 %d exp %d", actualLength, expLength1) 148 } 149 if actualCapacity != expCapacity1 { 150 t.Errorf("expCapacity1 %d exp %d", actualCapacity, expCapacity1) 151 } 152 153 if err := nbChan.GetError(); err != nil { 154 cL := pruntime.NewCodeLocation(0) 155 loc := cL.FuncIdentifier() + ":" + strconv.Itoa(cL.Line) 156 t.Errorf("%s nbChan.GetError: %s", loc, perrors.Short(err)) 157 } 158 } 159 160 // [NBChan.Ch] channel-read 161 func TestNBChanReceive(t *testing.T) { 162 var value = 3 163 164 var nbChan NBChan[int] 165 var n = NewNBChanReceiver[int]().Receive(nbChan.Ch()) 166 n.isReady.Wait() 167 if n.didRecive { 168 t.Error("empty NBChan receive") 169 } 170 nbChan.Send(value) 171 n.isExit.Wait() 172 if !n.didRecive { 173 t.Error("didReceive false") 174 } 175 if !n.valueIsValid { 176 t.Error("valueIsValid false") 177 } 178 if n.value != value { 179 t.Errorf("received %d exp %d", n.value, value) 180 } 181 if err := nbChan.GetError(); err != nil { 182 cL := pruntime.NewCodeLocation(0) 183 loc := cL.FuncIdentifier() + ":" + strconv.Itoa(cL.Line) 184 t.Errorf("%s nbChan.GetError: %s", loc, perrors.Short(err)) 185 } 186 } 187 188 // [NBChan.Get] 189 func TestNBChanGet(t *testing.T) { 190 var values = []int{3, 4, 5, 6} 191 var getArg = 2 192 var exp1 = values[:getArg] 193 var exp2 = values[getArg:] 194 195 var actual []int 196 197 var nbChan NBChan[int] 198 199 Log("— GET2 SendMany") 200 // Get with limit 201 // send 3 4 5 6 202 nbChan.SendMany(values) 203 // get 2 first: 3 4 204 actual = nbChan.Get(getArg) 205 if !slices.Equal(actual, exp1) { 206 t.Errorf("Get %d: '%v' exp '%v'", getArg, actual, exp1) 207 } 208 209 // Get all: 5 6 210 Log("— GETALL") 211 actual = nbChan.Get() 212 if !slices.Equal(actual, exp2) { 213 t.Errorf("Get all: '%v' exp '%v'", actual, exp2) 214 } 215 216 if err := nbChan.GetError(); err != nil { 217 cL := pruntime.NewCodeLocation(0) 218 loc := cL.FuncIdentifier() + ":" + strconv.Itoa(cL.Line) 219 t.Errorf("%s nbChan.GetError: %s", loc, perrors.Short(err)) 220 } 221 } 222 223 // [NBChan.Close] [NBChan.DidClose] [NBChan.IsClosed] [NBChan.WaitForClose] 224 func TestNBChanClose(t *testing.T) { 225 var value = 3 226 227 var didClose bool 228 var timer *time.Timer 229 var ok bool 230 var actValue int 231 232 var nbChan NBChan[int] 233 234 // NBChan with value: 235 // - Close was not invoked 236 // - underlying channel is not closed 237 var n = NewNBChanWaitForClose[int]().Wait(&nbChan) 238 t.Log("n.isReady.Wait…") 239 n.isReady.Wait() 240 t.Log("n.isReady.Wait complete") 241 // send a value to launch a thread 242 nbChan.Send(value) 243 // Close should not have been invoked 244 if nbChan.DidClose() { 245 t.Error("DidClose0") 246 } 247 // underlying channel should not be closed 248 if nbChan.IsClosed() { 249 t.Error("IsClosed0") 250 } 251 // internal close state should be false 252 if n.didClose { 253 t.Error("n.didClose0") 254 } 255 256 // non-empty closed NBChan 257 // - close is deferred 258 // - the channel isn’t actually closed yet 259 didClose = nbChan.Close() 260 // close should be deferred 261 if didClose { 262 t.Error("didClose1 true") 263 } 264 // Close should have been invoked 265 if !nbChan.DidClose() { 266 t.Error("DidClose1 false") 267 } 268 // underlying channel should not be closed 269 if nbChan.IsClosed() { 270 t.Error("IsClosed1") 271 } 272 // internal close state should be false 273 if n.didClose { 274 t.Error("n.didClose1") 275 } 276 277 // NBChan after deferred close: 278 // - reading the channel will cause thread exit 279 // - thread exit executes deferred close 280 // empty the channel: executes deferred close 281 if actValue, ok = <-nbChan.Ch(); !ok { 282 t.Error("Sent item not on channel") 283 } 284 if actValue != value { 285 t.Errorf("received %d exp %d", actValue, value) 286 } 287 // the channel should then close 288 if _, ok = <-nbChan.Ch(); ok { 289 t.Error("Channel did not close") 290 } 291 // Close should have been invoked 292 if !nbChan.DidClose() { 293 t.Error("DidClose2 false") 294 } 295 t.Log("n.isExit.Wait…") 296 n.isExit.Wait() 297 t.Log("n.isExit.Wait complete") 298 // internal close state should be true 299 if !n.didClose { 300 t.Error("n.didClose2 false") 301 } 302 if n.err != nil { 303 t.Errorf("WaitForClose err: %s", perrors.Short(n.err)) 304 } 305 // underlying channel should be closed 306 if !nbChan.IsClosed() { 307 t.Error("IsClosed2") 308 } 309 310 // a deferred close should close the data channel 311 // - max 1 ms wait to error 312 timer = time.NewTimer(time.Millisecond) 313 select { 314 case <-timer.C: 315 t.Error("DataWaitCh not closed by deferred Close") 316 case <-nbChan.DataWaitCh(): 317 } 318 timer.Stop() 319 320 // subsequent close should not do anything 321 didClose = nbChan.Close() 322 if didClose { 323 t.Error("didClose3 true") 324 } 325 326 // there should be no errors 327 if err := nbChan.GetError(); err != nil { 328 cL := pruntime.NewCodeLocation(0) 329 loc := cL.FuncIdentifier() + ":" + strconv.Itoa(cL.Line) 330 t.Errorf("%s nbChan.GetError: %s", loc, perrors.Short(err)) 331 } 332 } 333 334 // [NBChan.CloseNow] 335 func TestNBChanCloseNow(t *testing.T) { 336 var value = 3 337 338 var didClose bool 339 var err error 340 var timer *time.Timer 341 342 // NBChan with thread in channel send: 343 // - should not be closed and 344 // - channel should not be closed 345 var nbChan NBChan[int] 346 nbChan.Send(value) 347 // Close should not have been invoked 348 if nbChan.DidClose() { 349 t.Error("DidClose0") 350 } 351 // underlying channel should not be closed 352 if nbChan.IsClosed() { 353 t.Error("IsClosed0") 354 } 355 356 // after CloseNow on non-empty channel: 357 // - didClose true for first CloseNow invocation 358 // - DidClose true 359 // - channel is closed 360 // - NBChan is empty 361 // - queues are nil 362 // - thread is exited 363 didClose, err = nbChan.CloseNow() 364 // internal close should be true 365 if !didClose { 366 t.Error("didClose1 false") 367 } 368 // there should be no errors 369 if err != nil { 370 t.Errorf("CloseNow err: %s", perrors.Short(err)) 371 } 372 // Close should have been invoked 373 if !nbChan.DidClose() { 374 t.Error("DidClose1 false") 375 } 376 // underlying channel should be closed 377 if !nbChan.IsClosed() { 378 t.Error("IsClosed1 false") 379 } 380 // NBChan should be empty 381 if nbChan.Count() != 0 { 382 t.Errorf("Count %d exp %d", nbChan.Count(), 0) 383 } 384 // inputQueue should be discarded 385 if nbChan.inputQueue != nil { 386 t.Error("inputQueue not nil") 387 } 388 // outputQueue should be discarded 389 if nbChan.outputQueue != nil { 390 t.Error("outputQueue not nil") 391 } 392 // thread should have exit 393 if nbChan.tcRunningThread.Load() { 394 t.Error("isRunningThread true") 395 } 396 // thread waiter should not wait 397 t.Log("threadWait.Wait…") 398 <-nbChan.tcThreadExitAwaitable.Ch() 399 t.Log("threadWait.Wait complete") 400 // data waiter should not wait 401 // - error in 1 ms 402 timer = time.NewTimer(time.Millisecond) 403 select { 404 case <-timer.C: 405 t.Error("DataWaitCh not closed") 406 case <-nbChan.DataWaitCh(): 407 } 408 timer.Stop() 409 410 // subsequent CloseNow 411 // - didClose is false 412 // - no errors 413 didClose, err = nbChan.CloseNow() 414 if didClose { 415 t.Error("didClose2 true") 416 } 417 if err != nil { 418 t.Errorf("CloseNow2 err: %s", perrors.Short(err)) 419 } 420 421 // check for any errors 422 if err := nbChan.GetError(); err != nil { 423 cL := pruntime.NewCodeLocation(0) 424 loc := cL.FuncIdentifier() + ":" + strconv.Itoa(cL.Line) 425 t.Errorf("%s nbChan.GetError: %s", loc, perrors.Short(err)) 426 } 427 } 428 429 // exit with unused NBChan 430 func TestNBChanExit1(t *testing.T) { 431 var nbChan = *NewNBChan[int]() 432 _ = &nbChan 433 } 434 435 // exit with thread at channel send 436 func TestNBChanExit2(t *testing.T) { 437 var nbChan NBChan[int] 438 nbChan.Send(1) 439 } 440 441 // exit after thread shutdown 442 func TestNBChanExit3(t *testing.T) { 443 var nbChan NBChan[int] 444 nbChan.Send(1) 445 nbChan.Get() 446 } 447 448 // [NBChanAlways] 449 func TestNBChanAlways(t *testing.T) { 450 var value, value2 = 3, 4 451 452 var nbChan *NBChan[int] 453 var actualValue int 454 var threadState NBChanTState 455 var didClose bool 456 var err error 457 458 // send and receive a value to cause 459 // always thread to enter NBChanAlert static state 460 nbChan = NewNBChan[int](NBChanAlways) 461 // send value 3 and receive to make sure thread is up 462 nbChan.Send(value) 463 // wait for thread to launch, then receive 464 actualValue = <-nbChan.Ch() 465 466 // should have received value 467 if actualValue != value { 468 t.Errorf("Get %d exp %d", actualValue, value) 469 } 470 471 // thread state should be NBChanAlert 472 threadState = nbChan.ThreadStatus(AwaitThread) 473 if threadState != NBChanAlert { 474 t.Errorf("ThreadStatus %s exp %s", threadState, NBChanAlert) 475 } 476 477 // send another value to cause 478 // always thread to switch to NBChanSendBlock 479 nbChan.Send(value2) 480 481 // thread state should be NBChanSendBlock 482 threadState = nbChan.ThreadStatus(AwaitThread) 483 if threadState != NBChanSendBlock { 484 t.Errorf("ThreadStatus %s exp %s", threadState, NBChanSendBlock) 485 } 486 487 // ensure error-free exit 488 didClose, err = nbChan.CloseNow() 489 if !didClose { 490 t.Error("didClose false") 491 } 492 if err != nil { 493 t.Errorf("CloseNow error: %s", perrors.Long(err)) 494 } 495 } 496 497 type ChWaiter struct { 498 isReady, isClosed sync.WaitGroup 499 didClose atomic.Bool 500 } 501 502 func NewChWaiter() (c *ChWaiter) { 503 return &ChWaiter{} 504 } 505 func (c *ChWaiter) Wait(ch <-chan struct{}) (c2 *ChWaiter) { 506 c2 = c 507 c.isReady.Add(1) 508 c.isClosed.Add(1) 509 go c.WaitThread(ch) 510 return 511 } 512 func (c *ChWaiter) WaitThread(ch <-chan struct{}) { 513 defer c.isClosed.Done() 514 defer c.didClose.Store(true) 515 516 c.isReady.Done() 517 <-ch 518 } 519 520 // [NBChanNone] [NBChan.DataWaitCh] 521 func TestNBChanNone(t *testing.T) { 522 var value = 3 523 524 var nbChan NBChan[int] 525 var waiter *ChWaiter 526 //var timer *time.Timer 527 528 // empty channel should causes wait 529 nbChan = *NewNBChan[int](NBChanNone) 530 waiter = NewChWaiter().Wait(nbChan.DataWaitCh()) 531 // await ChWaiter thread 532 waiter.isReady.Wait() 533 // ensure ChWaiter is waiting 534 if waiter.didClose.Load() { 535 t.Fatal("DataWaitCh0 closed") 536 } 537 538 // closed empty channel should not wait 539 nbChan.Close() 540 if waiter.didClose.Load() { 541 t.Fatal("DataWaitCh0 still closed") 542 } 543 544 // channel with item should not wait 545 nbChan = *NewNBChan[int](NBChanNone) 546 nbChan.Send(value) 547 waiter = NewChWaiter().Wait(nbChan.DataWaitCh()) 548 // await ChWaiter thread 549 waiter.isReady.Wait() 550 if !waiter.didClose.Load() { 551 t.Fatal("DataWaitCh1 not closed") 552 } 553 554 // channel becoming empty should wait 555 nbChan = *NewNBChan[int](NBChanNone) 556 nbChan.Send(value) 557 nbChan.Get() 558 waiter = NewChWaiter().Wait(nbChan.DataWaitCh()) 559 // await ChWaiter thread 560 waiter.isReady.Wait() 561 if waiter.didClose.Load() { 562 t.Fatal("DataWaitCh2 closed") 563 } 564 } 565 566 type NBChanReceiver[T any] struct { 567 isReady sync.WaitGroup 568 value T 569 valueIsValid bool 570 didRecive bool 571 isExit sync.WaitGroup 572 } 573 574 func NewNBChanReceiver[T any]() (n *NBChanReceiver[T]) { return &NBChanReceiver[T]{} } 575 func (n *NBChanReceiver[T]) Receive(ch <-chan T) (n2 *NBChanReceiver[T]) { 576 n2 = n 577 n.isReady.Add(1) 578 n.isExit.Add(1) 579 go n.thread(ch) 580 return 581 } 582 func (n *NBChanReceiver[T]) thread(ch <-chan T) { 583 defer n.isExit.Done() 584 585 n.isReady.Done() 586 n.value, n.valueIsValid = <-ch 587 n.didRecive = true 588 } 589 590 type NBChanWaitForClose[T any] struct { 591 isReady sync.WaitGroup 592 err error 593 didClose bool 594 isExit sync.WaitGroup 595 } 596 597 func NewNBChanWaitForClose[T any]() (n *NBChanWaitForClose[T]) { return &NBChanWaitForClose[T]{} } 598 func (n *NBChanWaitForClose[T]) Wait(nbChan *NBChan[T]) (n2 *NBChanWaitForClose[T]) { 599 n2 = n 600 n.isReady.Add(1) 601 n.isExit.Add(1) 602 go n.thread(nbChan) 603 return 604 } 605 func (n *NBChanWaitForClose[T]) thread(nbChan *NBChan[T]) { 606 defer n.isExit.Done() 607 608 n.isReady.Done() 609 nbChan.WaitForClose(&n.err) 610 n.didClose = true 611 }