github.com/haraldrudell/parl@v0.4.176/awaitable-slice_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  	"slices"
    10  	"sync"
    11  	"testing"
    12  )
    13  
    14  func TestAwaitableSlice(t *testing.T) {
    15  	var value1, value2, value3 = 1, 2, 3
    16  	var values = []int{value1, value2, value3}
    17  	var size = 25
    18  
    19  	var actual int
    20  	var actuals []int
    21  	var hasValue, isOpen bool
    22  	var ch AwaitableCh
    23  
    24  	// DataWaitCh EmptyCh Get Get1 GetAll Send SendSlice SetSize
    25  	var slice *AwaitableSlice[int]
    26  	var reset = func() {
    27  		slice = &AwaitableSlice[int]{}
    28  	}
    29  
    30  	// Get1 should return one value at a a time
    31  	//	- Send SendSlice should work
    32  	reset()
    33  	slice.Send(value1)
    34  	slice.SendSlice([]int{value2})
    35  	slice.Send(value3)
    36  	// populated Slice: q: [1] slices: [[2] [3]]
    37  	t.Logf("populated Slice: q: %v slices: %v", slice.queue, slice.slices)
    38  	for i, v := range values {
    39  		actual, hasValue = slice.Get()
    40  		if !hasValue {
    41  			t.Errorf("Get1#%d hasValue false", i)
    42  		}
    43  		if actual != v {
    44  			t.Errorf("Get1#%d %d exp %d", i, actual, v)
    45  		}
    46  	}
    47  	// Get1 empty should returns hasValue false
    48  	actual, hasValue = slice.Get()
    49  	_ = actual
    50  	if hasValue {
    51  		t.Error("Get1 hasValue true")
    52  	}
    53  
    54  	// Get should return one slice at a a time
    55  	reset()
    56  	slice.Send(value1)
    57  	slice.SendSlice([]int{value2})
    58  	slice.Send(value3)
    59  	for i, v := range values {
    60  		actuals = slice.GetSlice()
    61  		if len(actuals) != 1 {
    62  			t.Fatalf("Get#%d hasValue false", i)
    63  		}
    64  		if actuals[0] != v {
    65  			t.Errorf("Get#%d %d exp %d", i, actuals[0], v)
    66  		}
    67  	}
    68  	// Get empty returns nil
    69  	actuals = slice.GetSlice()
    70  	if actuals != nil {
    71  		t.Errorf("Get actuals not nil: %d%v", len(actuals), actuals)
    72  	}
    73  
    74  	// GetAll should return all values in a single slice
    75  	reset()
    76  	slice.Send(value1)
    77  	slice.SendSlice([]int{value2})
    78  	slice.Send(value3)
    79  	actuals = slice.GetAll()
    80  	if !slices.Equal(actuals, values) {
    81  		t.Errorf("GetAll %v exp %v", actuals, values)
    82  	}
    83  	actuals = slice.GetAll()
    84  	if actuals != nil {
    85  		t.Errorf("GetAll empty not nil: %d%v", len(actuals), actuals)
    86  	}
    87  
    88  	// SetSize should be effective
    89  	reset()
    90  	slice.SetSize(size)
    91  	slice.Send(value1)
    92  	actuals = slice.GetSlice()
    93  	if cap(actuals) != size {
    94  		t.Errorf("SetSize %d exp %d", cap(actuals), size)
    95  	}
    96  
    97  	// DataWaitCh
    98  	reset()
    99  	// DataWaitCh on creation should return non-nil, open channel
   100  	ch = slice.DataWaitCh()
   101  	if ch == nil {
   102  		t.Error("DataWaitCh nil")
   103  	}
   104  	isOpen = true
   105  	select {
   106  	case <-ch:
   107  		isOpen = false
   108  	default:
   109  	}
   110  	if !isOpen {
   111  		t.Error("DataWaitCh ch not open")
   112  	}
   113  	// hasData true should close the returned channel
   114  	slice.Send(value1)
   115  	isOpen = true
   116  	select {
   117  	case <-ch:
   118  		isOpen = false
   119  	default:
   120  	}
   121  	if isOpen {
   122  		t.Error("DataWaitCh hasData ch not closed")
   123  	}
   124  
   125  	// EndCh on creation should return non-nil closed channel
   126  	reset()
   127  	ch = slice.EmptyCh()
   128  	isOpen = true
   129  	select {
   130  	case <-ch:
   131  		isOpen = false
   132  	default:
   133  	}
   134  	if isOpen {
   135  		t.Error("EndCh empty ch not closed")
   136  	}
   137  
   138  	// EndCh for hasData true returns open channel
   139  	reset()
   140  	slice.Send(value1)
   141  	ch = slice.EmptyCh()
   142  	isOpen = true
   143  	select {
   144  	case <-ch:
   145  		isOpen = false
   146  	default:
   147  	}
   148  	if !isOpen {
   149  		t.Error("EmptyCh hasData ch closed")
   150  	}
   151  	// hasData to false should close the returned channel
   152  	actual, hasValue = slice.Get()
   153  	_ = actual
   154  	_ = hasValue
   155  	isOpen = true
   156  	select {
   157  	case <-ch:
   158  		isOpen = false
   159  	default:
   160  	}
   161  	if isOpen {
   162  		t.Error("EmptyCh empty ch not closed")
   163  	}
   164  
   165  	// EmptyCh CloseAwaiter should defer empty detection
   166  	reset()
   167  	ch = slice.EmptyCh(CloseAwaiter)
   168  	isOpen = true
   169  	select {
   170  	case <-ch:
   171  		isOpen = false
   172  	default:
   173  	}
   174  	if !isOpen {
   175  		t.Error("EmptyCh CloseAwaiter doe not defer empty detection")
   176  	}
   177  	// EmptyCh without CloseAwaiter should close the returned channel
   178  	_ = slice.EmptyCh()
   179  	isOpen = true
   180  	select {
   181  	case <-ch:
   182  		isOpen = false
   183  	default:
   184  	}
   185  	if isOpen {
   186  		t.Error("subsequent EmptyCh does not close the channel")
   187  	}
   188  }
   189  
   190  func TestAwaitableSliceFor(t *testing.T) {
   191  	var value1 = 1
   192  	var expValue1 = []int{value1}
   193  
   194  	var actual, zeroValue int
   195  	var a *AwaitableForTester
   196  	var actuals []int
   197  
   198  	var slice *AwaitableSlice[int]
   199  	var reset = func() {
   200  		slice = &AwaitableSlice[int]{}
   201  	}
   202  
   203  	// Init should return zero-value
   204  	reset()
   205  	actual = slice.Init()
   206  	if actual != zeroValue {
   207  		t.Errorf("Init %d exp %d", actual, zeroValue)
   208  	}
   209  
   210  	// Condition active on value appearing and slice closing
   211  	reset()
   212  	// start for loop in other thread
   213  	a = NewAwaitableForTester(slice)
   214  	go a.GoFor()
   215  	<-a.IsReady.Ch()
   216  	// Condition should block not receiving value
   217  	if a.IsValues.IsClosed() {
   218  		t.Fatal("Condition unexpectedly received value")
   219  	}
   220  	// Condition should not end due to close
   221  	if a.IsClosed.IsClosed() {
   222  		t.Fatal("Condition IsClosed")
   223  	}
   224  	// Condition should receive appearing values
   225  	slice.Send(value1)
   226  	// Condition should receive value
   227  	<-a.IsValues.Ch()
   228  	actuals = a.Values()
   229  	if !slices.Equal(actuals, expValue1) {
   230  		t.Errorf("Condition %v exp %v", actuals, expValue1)
   231  	}
   232  	// Condition should not detect a close
   233  	if a.IsClosed.IsClosed() {
   234  		t.Fatal("Condition IsClosed")
   235  	}
   236  	// Condition should detect occuring close
   237  	slice.EmptyCh()
   238  	<-a.IsClosed.Ch()
   239  
   240  	// Condition deferred close
   241  	//	- slice has value and is closed on entering Condition
   242  	reset()
   243  	slice.Send(value1)
   244  	slice.EmptyCh()
   245  	// start for loop in other thread
   246  	a = NewAwaitableForTester(slice)
   247  	go a.GoFor()
   248  	<-a.IsReady.Ch()
   249  	// Condition should detect close
   250  	<-a.IsClosed.Ch()
   251  	// Condition should have received value
   252  	actuals = a.Values()
   253  	if !slices.Equal(actuals, expValue1) {
   254  		t.Errorf("Condition %v exp %v", actuals, expValue1)
   255  	}
   256  }
   257  
   258  // cover 100% of Send
   259  func TestAwaitableSliceSend(t *testing.T) {
   260  	//t.Error("loggin on")
   261  	var value = 1
   262  	var values = []int{value}
   263  
   264  	var slice *AwaitableSlice[int]
   265  	var reset = func() {
   266  		slice = &AwaitableSlice[int]{}
   267  	}
   268  
   269  	// use cachedInput in Send
   270  	reset()
   271  	// 2 Get should cover append to s.queue
   272  	slice.Send(value)
   273  	slice.Send(value)
   274  	// Get to empty initializes cachedInput
   275  	slice.GetAll()
   276  	// len(s.slices): 0 s.queue: true s.cachedInput: true
   277  	t.Logf("len(s.slices): %d s.queue: %t s.cachedInput: %t",
   278  		len(slice.slices), slice.queue != nil, slice.cachedInput != nil,
   279  	)
   280  	slice.queue = nil
   281  	// Send should cover using cachedInput
   282  	slice.Send(value)
   283  
   284  	// SendSlice and 2 Sends should cover append to local slice
   285  	reset()
   286  	slice.SendSlice(slices.Clone(values))
   287  	slice.Send(value)
   288  	slice.Send(value)
   289  
   290  	// use cachedInput for local slice
   291  	reset()
   292  	// Get to empty initializes cachedInput
   293  	slice.Send(value)
   294  	slice.Get()
   295  	// SendSlice then Send should creates a local slice from cachedInput
   296  	slice.SendSlice(values)
   297  	slice.Send(value)
   298  }
   299  
   300  // 100% coverage GetSlice
   301  func TestAwaitableSliceGetSlice(t *testing.T) {
   302  	//t.Error("loggin on")
   303  	var value1, value2 = 1, 2
   304  	var value2Slice = []int{value2}
   305  
   306  	var actual int
   307  	var hasValue bool
   308  	var actuals []int
   309  
   310  	var slice *AwaitableSlice[int]
   311  	var reset = func() {
   312  		slice = &AwaitableSlice[int]{}
   313  	}
   314  
   315  	// use cachedInput in Send
   316  	reset()
   317  	// Send Send Get creates non-empty s.output
   318  	slice.Send(value1)
   319  	slice.Send(value2)
   320  	if actual, hasValue = slice.Get(); actual != value1 {
   321  		t.Errorf("Get %d exp %d", actual, value1)
   322  	}
   323  	_ = hasValue
   324  	// GetSlice should return s.output
   325  	if actuals = slice.GetSlice(); !slices.Equal(actuals, value2Slice) {
   326  		t.Errorf("GetSlice %v exp %v", actuals, value2Slice)
   327  	}
   328  }
   329  
   330  // 100% coverage GetAll
   331  func TestAwaitableSliceGetAll(t *testing.T) {
   332  	//t.Error("loggin on")
   333  	// value1 1
   334  	var value1, value2, value3 = 1, 2, 3
   335  	// slice of value 1
   336  	var values1, values2, values3 = []int{value1}, []int{value2}, []int{value3}
   337  	var values23 = []int{value2, value3}
   338  
   339  	var actual int
   340  	var actuals []int
   341  	var hasValue bool
   342  
   343  	var slice *AwaitableSlice[int]
   344  	var reset = func() {
   345  		slice = &AwaitableSlice[int]{}
   346  	}
   347  
   348  	// aggregate outputs
   349  	reset()
   350  	// SendSlice SendSlice Get creates non-empty s.outputs
   351  	slice.SendSlice(slices.Clone(values1))
   352  	slice.SendSlice(slices.Clone(values2))
   353  	if actual, hasValue = slice.Get(); actual != value1 {
   354  		t.Errorf("Get %d exp %d", actual, value1)
   355  	}
   356  	_ = hasValue
   357  	// GetAll should return s.outputs
   358  	if actuals = slice.GetAll(); !slices.Equal(actuals, values2) {
   359  		t.Errorf("GetAll %v exp %v", actuals, values2)
   360  	}
   361  
   362  	// aggregate output and outputs
   363  	reset()
   364  	// Send Send SendSlice Get creates non-empty s.output s.outputs
   365  	slice.Send(value1)
   366  	slice.Send(value2)
   367  	slice.SendSlice(slices.Clone(values3))
   368  	if actual, hasValue = slice.Get(); actual != value1 {
   369  		t.Errorf("Get %d exp %d", actual, value1)
   370  	}
   371  	_ = hasValue
   372  	// GetAll should aggregate output and outputs
   373  	if actuals = slice.GetAll(); !slices.Equal(actuals, values23) {
   374  		t.Errorf("GetAll %v exp %v", actuals, values23)
   375  	}
   376  
   377  	// GetAll only output
   378  	reset()
   379  	// Send Send Get creates non-empty output
   380  	slice.Send(value1)
   381  	slice.Send(value2)
   382  	if actual, hasValue = slice.Get(); actual != value1 {
   383  		t.Errorf("Get %d exp %d", actual, value1)
   384  	}
   385  	_ = hasValue
   386  	// GetAll should return output single-slice
   387  	if actuals = slice.GetAll(); !slices.Equal(actuals, values2) {
   388  		t.Errorf("GetAll %v exp %v", actuals, values2)
   389  	}
   390  
   391  	// only s.slices[0]
   392  	reset()
   393  	// SendSlice creates single-slice s.slices
   394  	slice.SendSlice(values1)
   395  	// GetAll should return s.slices[0] single-slice
   396  	if actuals = slice.GetAll(); !slices.Equal(actuals, values1) {
   397  		t.Errorf("GetAll %v exp %v", actuals, values1)
   398  	}
   399  }
   400  
   401  type AwaitableForTester struct {
   402  	slice     *AwaitableSlice[int]
   403  	IsReady   Awaitable
   404  	IsClosed  Awaitable
   405  	IsValues  CyclicAwaitable
   406  	valueLock sync.Mutex
   407  	values    []int
   408  }
   409  
   410  func NewAwaitableForTester(slice *AwaitableSlice[int]) (a *AwaitableForTester) {
   411  	return &AwaitableForTester{slice: slice}
   412  }
   413  
   414  func (a *AwaitableForTester) GoFor() {
   415  	a.IsReady.Close()
   416  	for value := a.slice.Init(); a.slice.Condition(&value); {
   417  		a.addValue(value)
   418  	}
   419  	a.IsClosed.Close()
   420  }
   421  
   422  func (a *AwaitableForTester) addValue(value int) {
   423  	a.valueLock.Lock()
   424  	defer a.IsValues.Close()
   425  	defer a.valueLock.Unlock()
   426  
   427  	a.values = append(a.values, value)
   428  }
   429  
   430  func (a *AwaitableForTester) Values() (values []int) {
   431  	a.valueLock.Lock()
   432  	defer a.valueLock.Unlock()
   433  
   434  	values = slices.Clone(a.values)
   435  	return
   436  }