go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/clock/clockcontext_test.go (about) 1 // Copyright 2015 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package clock 16 17 import ( 18 "context" 19 "testing" 20 "time" 21 22 . "github.com/smartystreets/goconvey/convey" 23 ) 24 25 // manualClock is a partial Clock implementation that allows us to release 26 // blocking calls. 27 type manualClock struct { 28 Clock 29 30 now time.Time 31 timeoutCallback func(time.Duration) bool 32 testFinishedC chan struct{} 33 } 34 35 func (mc *manualClock) Now() time.Time { 36 return mc.now 37 } 38 39 func (mc *manualClock) NewTimer(ctx context.Context) Timer { 40 return &manualTimer{ 41 ctx: ctx, 42 mc: mc, 43 resultC: make(chan TimerResult), 44 } 45 } 46 47 type manualTimer struct { 48 Timer 49 50 ctx context.Context 51 mc *manualClock 52 resultC chan TimerResult 53 54 running bool 55 stopC chan struct{} 56 } 57 58 func (mt *manualTimer) GetC() <-chan TimerResult { return mt.resultC } 59 60 func (mt *manualTimer) Reset(d time.Duration) bool { 61 running := mt.Stop() 62 mt.stopC, mt.running = make(chan struct{}), true 63 64 go func() { 65 ar := TimerResult{} 66 defer func() { 67 mt.resultC <- ar 68 }() 69 70 // If we are instructed to immediately timeout, do so. 71 if cb := mt.mc.timeoutCallback; cb != nil && cb(d) { 72 return 73 } 74 75 select { 76 case <-mt.ctx.Done(): 77 ar.Err = mt.ctx.Err() 78 case <-mt.mc.testFinishedC: 79 break 80 } 81 }() 82 return running 83 } 84 85 func (mt *manualTimer) Stop() bool { 86 if !mt.running { 87 return false 88 } 89 90 mt.running = false 91 close(mt.stopC) 92 return true 93 } 94 95 func wait(ctx context.Context) error { 96 <-ctx.Done() 97 return ctx.Err() 98 } 99 100 func TestClockContext(t *testing.T) { 101 t.Parallel() 102 103 Convey(`A manual testing clock`, t, func() { 104 mc := manualClock{ 105 now: time.Date(2016, 1, 1, 0, 0, 0, 0, time.Local), 106 testFinishedC: make(chan struct{}), 107 } 108 defer close(mc.testFinishedC) 109 110 Convey(`A context with a deadline wrapping a cancellable parent`, func() { 111 Convey(`Successfully reports its deadline.`, func() { 112 ctx, cancel := WithTimeout(Set(context.Background(), &mc), 10*time.Millisecond) 113 defer cancel() 114 115 deadline, ok := ctx.Deadline() 116 So(ok, ShouldBeTrue) 117 So(deadline.After(mc.now), ShouldBeTrue) 118 }) 119 120 Convey(`Will successfully time out.`, func() { 121 mc.timeoutCallback = func(time.Duration) bool { 122 return true 123 } 124 125 cctx, cancel := context.WithCancel(Set(context.Background(), &mc)) 126 defer cancel() 127 ctx, cancel := WithTimeout(cctx, 10*time.Millisecond) 128 defer cancel() 129 So(wait(ctx).Error(), ShouldEqual, context.DeadlineExceeded.Error()) 130 }) 131 132 Convey(`Will successfully cancel with its cancel func.`, func() { 133 cctx, cancel := context.WithCancel(Set(context.Background(), &mc)) 134 defer cancel() 135 ctx, cf := WithTimeout(cctx, 10*time.Millisecond) 136 go cf() 137 So(wait(ctx), ShouldEqual, context.Canceled) 138 }) 139 140 Convey(`Will successfully cancel if the parent is canceled.`, func() { 141 cctx, pcf := context.WithCancel(Set(context.Background(), &mc)) 142 ctx, cancel := WithTimeout(cctx, 10*time.Millisecond) 143 defer cancel() 144 go pcf() 145 So(wait(ctx), ShouldEqual, context.Canceled) 146 }) 147 }) 148 149 Convey(`A context with a deadline wrapping a parent with a shorter deadline`, func() { 150 cctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) 151 defer cancel() 152 ctx, cf := WithTimeout(cctx, 1*time.Hour) 153 defer cf() 154 155 Convey(`Will successfully time out.`, func() { 156 mc.timeoutCallback = func(d time.Duration) bool { 157 return d == 10*time.Millisecond 158 } 159 160 So(wait(ctx).Error(), ShouldEqual, context.DeadlineExceeded.Error()) 161 }) 162 163 Convey(`Will successfully cancel with its cancel func.`, func() { 164 go cf() 165 So(wait(ctx), ShouldEqual, context.Canceled) 166 }) 167 }) 168 169 Convey(`A context with a deadline in the past`, func() { 170 ctx, _ := WithDeadline(context.Background(), mc.now.Add(-time.Second)) 171 172 Convey(`Will time out immediately.`, func() { 173 So(wait(ctx).Error(), ShouldEqual, context.DeadlineExceeded.Error()) 174 }) 175 }) 176 }) 177 }