Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(253)

Unified Diff: go/src/infra/libs/clock/testclock/testclock.go

Issue 1154213012: Add context-aware "time" library wrapper. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Added coverage files. Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: go/src/infra/libs/clock/testclock/testclock.go
diff --git a/go/src/infra/libs/clock/testclock/testclock.go b/go/src/infra/libs/clock/testclock/testclock.go
new file mode 100644
index 0000000000000000000000000000000000000000..10d17d65efe896cc1d543b420e5f559971a75c43
--- /dev/null
+++ b/go/src/infra/libs/clock/testclock/testclock.go
@@ -0,0 +1,178 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package testclock
+
+import (
+ "sync"
+ "time"
+
+ "infra/libs/clock"
+)
+
+// TestClock is a Clock interface with additional methods to help instrument it.
+type TestClock interface {
+ clock.Clock
+ Set(time.Time)
+ Add(time.Duration)
+ SetTimerCallback(TimerCallback)
+}
+
+// TimerCallback that can be invoked when a timer has been set. This is useful
+// for sychronizing state when testing.
+type TimerCallback func(clock.Timer)
+
+type cancelFunc func()
+
+// testClock is a test-oriented implementation of the 'Clock' interface.
+//
+// This implementation's Clock responses are configurable by modifying its
+// member variables. Time-based events are explicitly triggered by sending on a
+// Timer instance's channel.
+type testClock struct {
+ sync.RWMutex
+
+ now time.Time // The current clock time.
+ timerCond *sync.Cond // Condition used to manage timer blocking.
+
+ timerCallback TimerCallback // Optional callback when a timer has been set.
+}
+
+var _ TestClock = (*testClock)(nil)
+
+// New returns a TestClock instance set at the specified time.
+func New(now time.Time) TestClock {
+ c := testClock{
+ now: now,
+ }
+ c.timerCond = sync.NewCond(&c)
+ return &c
+}
+
+func (c *testClock) Now() time.Time {
+ c.RLock()
+ defer c.RUnlock()
+
+ return c.now
+}
+
+func (c *testClock) Sleep(d time.Duration) {
+ <-c.After(d)
+}
+
+func (c *testClock) NewTimer() clock.Timer {
+ return newTimer(c)
+}
+
+func (c *testClock) After(d time.Duration) <-chan time.Time {
+ t := c.NewTimer()
+ t.Reset(d)
+ return t.GetC()
+}
+
+func (c *testClock) Set(t time.Time) {
+ c.Lock()
+ defer c.Unlock()
+
+ c.setTimeLocked(t)
+}
+
+func (c *testClock) Add(d time.Duration) {
+ c.Lock()
+ defer c.Unlock()
+
+ c.setTimeLocked(c.now.Add(d))
+}
+
+func (c *testClock) setTimeLocked(t time.Time) {
+ if t.Before(c.now) {
+ panic("Cannot go backwards in time. You're not Doc Brown.")
+ }
+ c.now = t
+
+ // Unblock any blocking timers that are waiting on our lock.
+ c.pokeTimers(true)
+}
+
+func (c *testClock) SetTimerCallback(callback TimerCallback) {
+ c.Lock()
+ defer c.Unlock()
+
+ c.timerCallback = callback
+}
+
+func (c *testClock) getTimerCallback() TimerCallback {
+ c.Lock()
+ defer c.Unlock()
+
+ return c.timerCallback
+}
+
+func (c *testClock) signalTimerSet(t clock.Timer) {
+ callback := c.getTimerCallback()
+ if callback != nil {
+ callback(t)
+ }
+}
+
+func (c *testClock) pokeTimers(locked bool) {
+ if !locked {
+ c.Lock()
+ defer c.Unlock()
+ }
+
+ c.timerCond.Broadcast()
+}
+
+// invokeAt invokes the specified callback when the Clock has advanced at
+// or after the specified threshold.
+//
+// The returned cancelFunc can be used to cancel the blocking. If the cancel
+// function is invoked before the callback, the callback will not be invoked.
+func (c *testClock) invokeAt(threshold time.Time, callback func(time.Time)) cancelFunc {
+ stopC := make(chan struct{})
+ finishedC := make(chan struct{})
+
+ // Our control goroutine will monitor both time and stop signals. It will only
+ // terminate when stopC has been closed.
+ //
+ // The lock that we take our here is owned by the following goroutine.
+ c.Lock()
+ go func() {
+ defer func() {
+ now := c.now
+ c.Unlock()
+ close(finishedC)
+ callback(now)
+ }()
+
+ for {
+ // Determine if we are past our signalling threshold. We can safely access
+ // our clock's time member directly, since we hold its lock from the
+ // condition firing.
+ if !c.now.Before(threshold) {
+ return
+ }
+
+ // Wait for a signal from our clock's condition.
+ c.timerCond.Wait()
+
+ // Have we been stopped?
+ select {
+ case <-stopC:
+ return
+
+ default:
+ // Nope.
+ }
+ }
+ }()
+
+ return func() {
+ // Close our stop channel and block pending goroutine termination.
+ close(stopC)
+ c.pokeTimers(false)
+ <-finishedC
+ }
+}
« no previous file with comments | « go/src/infra/libs/clock/testclock/context.go ('k') | go/src/infra/libs/clock/testclock/testclock.infra_testing » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698