github.com/haraldrudell/parl@v0.4.176/moderator-core_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  	"sync"
    10  	"sync/atomic"
    11  	"testing"
    12  	"time"
    13  )
    14  
    15  const (
    16  	shortTime = time.Millisecond
    17  )
    18  
    19  type ModeratorStatus struct {
    20  	parallelism, active, waiting, transferBehindLock uint64
    21  }
    22  
    23  func NewModeratorStatus(m *ModeratorCore) (status *ModeratorStatus) {
    24  	s := ModeratorStatus{}
    25  	s.parallelism, s.active, s.waiting = m.Status()
    26  	s.transferBehindLock = m.transferBehindLock.Load()
    27  	return &s
    28  }
    29  
    30  type ModeratorLocker struct {
    31  	isReady, isDone sync.WaitGroup
    32  	mo              *ModeratorCore
    33  	haveTicket      atomic.Bool
    34  	returnTicket    func()
    35  	t               *testing.T
    36  }
    37  
    38  func NewModeratorLocker(mo *ModeratorCore, t *testing.T) (m *ModeratorLocker) {
    39  	return &ModeratorLocker{mo: mo, t: t}
    40  }
    41  func (m *ModeratorLocker) Ticket() {
    42  	m.isReady.Add(1)
    43  	m.isDone.Add(1)
    44  	go m.ticket()
    45  }
    46  func (m *ModeratorLocker) ticket() {
    47  	defer m.isDone.Done()
    48  
    49  	var t = m.t
    50  	m.isReady.Done()
    51  	t.Log("thread invoking Ticket")
    52  	m.returnTicket = m.mo.Ticket()
    53  	t.Log("thread got ticket")
    54  	m.haveTicket.Store(true)
    55  }
    56  
    57  func TestModeratorCore(t *testing.T) {
    58  	var parallelism = uint64(1)
    59  	var exp1_1Ticket = ModeratorStatus{
    60  		parallelism: parallelism,
    61  		active:      1,
    62  	}
    63  	var exp2_Ticket2Wait = ModeratorStatus{
    64  		parallelism: parallelism,
    65  		active:      1,
    66  		waiting:     1,
    67  	}
    68  	var exp3_1Returned = ModeratorStatus{
    69  		parallelism: parallelism,
    70  		active:      1,
    71  	}
    72  	var exp4_2returned = ModeratorStatus{
    73  		parallelism: parallelism,
    74  	}
    75  
    76  	var moderator *ModeratorCore
    77  	var returnTicket1 func()
    78  	var actual ModeratorStatus
    79  
    80  	// one thread should be atomic access
    81  	moderator = NewModeratorCore(parallelism)
    82  	returnTicket1 = moderator.Ticket()
    83  	if returnTicket1 == nil {
    84  		t.Fatal("returnTicket1 nil")
    85  	}
    86  	actual = *NewModeratorStatus(moderator)
    87  	if actual != exp1_1Ticket {
    88  		t.Errorf("1: one ticket:\n%#v exp:\n%#v", actual, exp1_1Ticket)
    89  	}
    90  
    91  	// thread2 should use lock mode and block
    92  	var ml = NewModeratorLocker(moderator, t)
    93  	ml.Ticket()
    94  	ml.isReady.Wait() // wait for thread Ticket invocation
    95  	// wait 1 ms for thread to reach m.queue.Wait
    96  	<-time.NewTimer(shortTime).C
    97  	if ml.haveTicket.Load() {
    98  		t.Errorf("ml.haveTicket true")
    99  	}
   100  	actual = *NewModeratorStatus(moderator)
   101  	if actual != exp2_Ticket2Wait {
   102  		t.Errorf("2: ticket2 waiting:\n%#v exp:\n%#v", actual, exp2_Ticket2Wait)
   103  	}
   104  
   105  	// return first ticket
   106  	returnTicket1()
   107  	ml.isDone.Wait()
   108  	if !ml.haveTicket.Load() {
   109  		t.Errorf("ml.haveTicket false")
   110  	}
   111  	actual = *NewModeratorStatus(moderator)
   112  	if actual != exp3_1Returned {
   113  		t.Errorf("3: after return1:\n%#v exp:\n%#v", actual, exp3_1Returned)
   114  	}
   115  	if ml.returnTicket == nil {
   116  		t.Fatal("returnTicket2 nil")
   117  	}
   118  
   119  	// return second ticket
   120  	ml.returnTicket()
   121  	actual = *NewModeratorStatus(moderator)
   122  	if actual != exp4_2returned {
   123  		t.Errorf("4: after return2:\n%#v exp:\n%#v", actual, exp4_2returned)
   124  	}
   125  }