github.com/haraldrudell/parl@v0.4.176/cancel-context_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 "errors" 11 "strings" 12 "sync/atomic" 13 "testing" 14 "time" 15 16 "github.com/haraldrudell/parl/perrors/errorglue" 17 ) 18 19 // tests: 20 // NewCancelContext 21 // NewCancelContextFunc 22 // InvokeCancel 23 // HasCancel 24 func TestInvokeCancel(t *testing.T) { 25 // nil parent” 26 var messageNilParent = "nil parent" 27 // “ctx cannot be nil” 28 var messageCtxNil = "ctx cannot be nil" 29 30 var ctx context.Context 31 var err error 32 var isPanic bool 33 34 // regular context 35 ctx = context.Background() 36 // HasCancel should be false 37 if HasCancel(ctx) { 38 t.Error("context HasCancel") 39 } 40 // InvokeCancel should panic 41 isPanic, err = invokeInvokeCancel(ctx) 42 if !isPanic || err == nil { 43 t.Error("InvokeCancel no panic") 44 } 45 if !errors.Is(err, ErrNotCancelContext) { 46 t.Errorf("err not ErrNotCancelContext %s “%+[1]v”", 47 errorglue.DumpChain(err), 48 err, 49 ) 50 } 51 52 // cancelContext 53 ctx = NewCancelContext(ctx) 54 // should not be canceled 55 if ctx.Err() != nil { 56 t.Error("context was canceled") 57 } 58 // HasCancel should be true 59 if !HasCancel(ctx) { 60 t.Error("context not HasCancel") 61 } 62 63 // InvokeCancel should cancel the context 64 InvokeCancel(ctx) 65 if ctx.Err() == nil { 66 t.Error("InvokeCancel did not cancel") 67 } 68 69 ctx = nil 70 _, isPanic, err = invokeNewCancelContext(ctx) 71 if !isPanic { 72 t.Error("NewCancelContext nil no panic") 73 } 74 if err == nil || !strings.Contains(err.Error(), messageNilParent) { 75 t.Errorf("InvokeCancel bad error: %v exp %q", err, messageNilParent) 76 } 77 78 isPanic, err = invokeInvokeCancel(ctx) 79 if !isPanic { 80 t.Error("InvokeCancel nil no panic") 81 } 82 if err == nil || !strings.Contains(err.Error(), messageCtxNil) { 83 t.Errorf("InvokeCancel bad error: '%v' exp %q", err, messageCtxNil) 84 } 85 } 86 87 func TestCancelOnError(t *testing.T) { 88 var err error 89 90 // nil,nil should not panic 91 CancelOnError(nil, nil) 92 93 CancelOnError(&err, nil) 94 ctx := NewCancelContext(context.Background()) 95 err = errors.New("x") 96 CancelOnError(&err, ctx) 97 if ctx.Err() == nil { 98 t.Log("CancelOnError failed") 99 } 100 } 101 102 func TestAfterFunc(t *testing.T) { 103 var duration = time.Second 104 105 var ctx = NewCancelContext(context.Background()) 106 var counter = newInvokeDetector() 107 context.AfterFunc(ctx, counter.Func) 108 109 // invokeCancel should start counter.Func in separate goroutine 110 InvokeCancel(ctx) 111 counter.waitForCh(duration) 112 if c := counter.u32.Load(); c != 1 { 113 t.Errorf("Bad number of invocations: %d exp 1", c) 114 } 115 } 116 117 // invokeInvokeCancel calls InvokeCancel recovering panic 118 func invokeInvokeCancel(ctx context.Context) (isPanic bool, err error) { 119 defer PanicToErr(&err, &isPanic) 120 121 InvokeCancel(ctx) 122 return 123 } 124 125 // invokeNewCancelContext invokes NewCancelContext recovering panic 126 func invokeNewCancelContext(ctx context.Context) (ctx2 context.Context, isPanic bool, err error) { 127 defer PanicToErr(&err, &isPanic) 128 129 ctx2 = NewCancelContext(ctx) 130 131 return 132 } 133 134 // invokeDetector counts invocations 135 type invokeDetector struct { 136 u32 atomic.Uint32 137 ch chan struct{} 138 } 139 140 // newInvokeDetector retruns an object counting invocations 141 func newInvokeDetector() (i *invokeDetector) { return &invokeDetector{ch: make(chan struct{})} } 142 143 func (i *invokeDetector) Func() { 144 if i.u32.Add(1) == 1 { 145 close(i.ch) 146 } 147 } 148 149 // waitForCh wait up to d for first Func invocation 150 func (i *invokeDetector) waitForCh(d time.Duration) { 151 var timer = time.NewTimer(d) 152 defer timer.Stop() 153 154 select { 155 case <-i.ch: 156 case <-timer.C: 157 } 158 } 159 160 func TestChildCancel(t *testing.T) { 161 var v any 162 var a func() 163 var ok bool 164 a, ok = v.(func()) 165 _ = a 166 _ = ok 167 168 // create ctx0…3 169 var ctx0 = context.Background() 170 var ctx1 = AddNotifier(ctx0, func(stack Stack) { 171 t.Log(stack) 172 }) 173 var ctx2 = NewCancelContext(ctx1) 174 var ctx3 = NewCancelContext(ctx2) 175 176 // cancel ctx3 should cancel only ctx3 177 invokeCancel(ctx3) 178 if ctx3.Err() == nil { 179 t.Error("ctx3 not canceled") 180 } 181 if ctx2.Err() != nil { 182 t.Error("ctx2 canceled") 183 } 184 if ctx1.Err() != nil { 185 t.Error("ctx1 canceled") 186 } 187 if ctx0.Err() != nil { 188 t.Error("ctx0 canceled") 189 } 190 }