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  }