github.com/haraldrudell/parl@v0.4.176/add-notifier_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  	"context"
    10  	"slices"
    11  	"testing"
    12  
    13  	"github.com/haraldrudell/parl/perrors"
    14  	"github.com/haraldrudell/parl/pruntime"
    15  )
    16  
    17  func TestAddNotifier(t *testing.T) {
    18  	var countersCount = 4
    19  	var expCounters = []int{1, 0, 1, 1}
    20  
    21  	// check expCounters
    22  	if len(expCounters) != countersCount {
    23  		panic(perrors.NewPF("bad expcounters length"))
    24  	}
    25  
    26  	var ctx context.Context
    27  	var actCounters = make([]int, countersCount)
    28  
    29  	// get packFunc
    30  	var packFunc = notifierInvokeCancel(ctx)
    31  	// packFunc: parl.notifierInvokeCancel
    32  	t.Logf("packFunc: %s", packFunc)
    33  
    34  	// create counters
    35  	var counters = make([]*notifierCounter, countersCount)
    36  	for i := range counters {
    37  		counters[i] = newNotifierCounter(packFunc, t)
    38  	}
    39  
    40  	// create contexts ctx…ctx4
    41  	ctx = context.Background()
    42  	// counters[0] is notified of all context cancels
    43  	var ctx0 = AddNotifier(ctx, counters[0].notifierFunc)
    44  	// counters[1] is notified of context cancels in
    45  	// child contexts without a notifier1
    46  	//	- ie. no cancelations
    47  	var ctx1 = AddNotifier1(ctx0, counters[1].notifierFunc)
    48  	// counters[2] is notified of context cancels in ctx2…
    49  	var ctx2 = AddNotifier1(ctx1, counters[2].notifierFunc)
    50  	// counters[3] is notified of all context cancels
    51  	var ctx3 = AddNotifier(ctx2, counters[3].notifierFunc)
    52  	// ctx4 is CancelContext
    53  	var ctx4 = NewCancelContext(ctx3)
    54  
    55  	// cancel a context
    56  	notifierInvokeCancel(ctx4)
    57  
    58  	// counter invocations should match expCounters
    59  	for i, c := range counters {
    60  		actCounters[i] = c.count
    61  	}
    62  	if !slices.Equal(actCounters, expCounters) {
    63  		t.Errorf("counters bad:\n%v exp:\n%v",
    64  			actCounters,
    65  			expCounters,
    66  		)
    67  	}
    68  }
    69  
    70  // notifierCounter is a fixture counting invocations
    71  type notifierCounter struct {
    72  	count    int
    73  	packFunc string
    74  	t        *testing.T
    75  }
    76  
    77  // newNotifierCounter returns a notifier that counts its invocations
    78  func newNotifierCounter(packFunc string, t *testing.T) (c *notifierCounter) {
    79  	return &notifierCounter{
    80  		packFunc: packFunc,
    81  		t:        t,
    82  	}
    83  }
    84  
    85  // notifierFunc is a notifierFunc function for child or all contexts
    86  func (c *notifierCounter) notifierFunc(stack Stack) {
    87  	t := c.t
    88  
    89  	// count invocation
    90  	c.count++
    91  
    92  	// check stack trace
    93  	var frames = stack.Frames()
    94  	if len(frames) < 2 {
    95  		panic(perrors.ErrorfPF("bad stack slice: %s"))
    96  	}
    97  	var tracePackFunc = frames[1].Loc().PackFunc()
    98  	if tracePackFunc == c.packFunc {
    99  		return // stack trace OK return
   100  	}
   101  	t.Logf("TRACE: %s", stack)
   102  	panic(perrors.New("Bad stack slice"))
   103  }
   104  
   105  // notifierInvokeCancel retrieves packFunc and invokes InvokeCancel
   106  func notifierInvokeCancel(ctx context.Context) (packFunc string) {
   107  	if ctx != nil {
   108  		// if ctx present, cancel it
   109  		InvokeCancel(ctx)
   110  	} else {
   111  		// if ctx not present, return packFunc for cancellation stack trace
   112  		packFunc = pruntime.NewCodeLocation(0).PackFunc()
   113  	}
   114  	return
   115  }