github.com/haraldrudell/parl@v0.4.176/nb-rare-chan_test.go (about)

     1  /*
     2  © 2024–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  package parl
     7  
     8  import (
     9  	"testing"
    10  	"time"
    11  )
    12  
    13  func TestNBRareChan(t *testing.T) {
    14  	var value1, value2 = 1, 2
    15  	const shortTime = time.Millisecond
    16  	var expLength = 2
    17  
    18  	var ch <-chan int
    19  	var actual int
    20  	var actualValues []int
    21  	var emptyAwaitable AwaitableCh
    22  	var isClose, ok bool
    23  	var timer *time.Timer
    24  	var timerC = func() (C <-chan time.Time) {
    25  		if timer == nil {
    26  			timer = time.NewTimer(shortTime)
    27  		} else {
    28  			timer.Stop()
    29  			if len(timer.C) > 0 {
    30  				<-timer.C
    31  			}
    32  			timer.Reset(shortTime)
    33  		}
    34  		C = timer.C
    35  		return
    36  	}
    37  
    38  	// Ch Close IsClose PanicCh Send StopSend
    39  	var nbChan *NBRareChan[int]
    40  	var reset = func() {
    41  		nbChan = &NBRareChan[int]{}
    42  	}
    43  
    44  	// Send one value should work
    45  	//	- also tests Ch for returning non-nil value
    46  	reset()
    47  	nbChan.Send(value1)
    48  	select {
    49  	case actual = <-nbChan.Ch():
    50  	case <-timerC():
    51  		t.Fatal("nbChan.Ch timeout")
    52  	}
    53  	if actual != value1 {
    54  		t.Errorf("Send: %d exp %d", actual, value1)
    55  	}
    56  
    57  	// Send two values should work
    58  	reset()
    59  	nbChan.Send(value1)
    60  	nbChan.Send(value2)
    61  	select {
    62  	case actual = <-nbChan.Ch():
    63  	case <-timerC():
    64  		t.Fatal("nbChan.Ch timeout")
    65  	}
    66  	if actual != value1 {
    67  		t.Errorf("Send: %d exp %d", actual, value1)
    68  	}
    69  	select {
    70  	case actual = <-nbChan.Ch():
    71  	case <-timerC():
    72  		t.Fatal("nbChan.Ch timeout")
    73  	}
    74  	if actual != value2 {
    75  		t.Errorf("Send: %d exp %d", actual, value2)
    76  	}
    77  
    78  	// StopSend should be effective
    79  	reset()
    80  	emptyAwaitable = nbChan.StopSend()
    81  	_ = emptyAwaitable
    82  	nbChan.Send(value1)
    83  	select {
    84  	case <-nbChan.Ch():
    85  		t.Error("StopSend not disabling Send")
    86  	case <-timerC():
    87  	}
    88  
    89  	// StopSend: emptyAwaitable should work
    90  	reset()
    91  	nbChan.Send(value1)
    92  	emptyAwaitable = nbChan.StopSend()
    93  	// StopSend should return non-nil channel
    94  	if emptyAwaitable == nil {
    95  		t.Fatal("StopSend emptyawaitable nil")
    96  	}
    97  	// StopSend should return untriggered open awaitable for non-empty channel
    98  	select {
    99  	case <-emptyAwaitable:
   100  		t.Error("StopSend emptyAwaitable closed before channel empty")
   101  	default:
   102  	}
   103  	select {
   104  	case <-nbChan.Ch():
   105  	case <-timerC():
   106  		t.Fatal("nbChan.Ch timeout")
   107  	}
   108  	// StopSend-provided awaitable should close upon channel emptying
   109  	//	- in race condition with exiting sendThread
   110  	//	- use sync.WaitGroup to provide synchronization
   111  	nbChan.threadWait.Wait()
   112  	select {
   113  	case <-emptyAwaitable:
   114  	default:
   115  		t.Error("StopSend emptyAwaitable not closing on empty channel")
   116  	}
   117  
   118  	// Close should not hang
   119  	reset()
   120  	nbChan.Close(nil, nil)
   121  	// Close should close underlying channel
   122  	ch = nbChan.Ch()
   123  	// if ch is closed:
   124  	//	- channel receive will happen, not default
   125  	//	- ok will be false
   126  	ok = true
   127  	select {
   128  	case actual, ok = <-ch:
   129  	default:
   130  		t.Error("nbChanClose ch receive default")
   131  	}
   132  	if ok {
   133  		t.Error("nbChanClose ch ok true")
   134  	}
   135  
   136  	// Close should return thread-value and queue
   137  	reset()
   138  	nbChan.Send(value1)
   139  	nbChan.Send(value2)
   140  	nbChan.Close(&actualValues, nil)
   141  	if len(actualValues) != expLength {
   142  		t.Fatalf("Close bad length %d exp %d", len(actualValues), expLength)
   143  	}
   144  	if actualValues[0] != value1 {
   145  		t.Errorf("Close first value %d exp %d", actualValues[0], value1)
   146  	}
   147  	if actualValues[1] != value2 {
   148  		t.Errorf("Close second value %d exp %d", actualValues[1], value2)
   149  	}
   150  
   151  	// IsClose should start false
   152  	reset()
   153  	isClose = nbChan.IsClose()
   154  	if isClose {
   155  		t.Error("IsClose started true")
   156  	}
   157  	nbChan.Close(nil, nil)
   158  	// IsClose should be true after Close
   159  	isClose = nbChan.IsClose()
   160  	if !isClose {
   161  		t.Error("IsClose ineffective")
   162  	}
   163  
   164  	// PanicCh should return non-nil emptyAwaitable
   165  	reset()
   166  	emptyAwaitable = nbChan.PanicCh()
   167  	if emptyAwaitable == nil {
   168  		t.Fatal("PanicCh nil emptyAwaitable")
   169  	}
   170  	// PanicCh emptyAwaitable should not be triggered
   171  	select {
   172  	case <-emptyAwaitable:
   173  		t.Error("PanicCh emptyAwaitable triggered")
   174  	default:
   175  	}
   176  }