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  }