| Index: common/clock/testclock/testtimer_test.go
|
| diff --git a/common/clock/testclock/testtimer_test.go b/common/clock/testclock/testtimer_test.go
|
| index 90234e43ada67a3c9208c30444d22721c9b8e2a6..7e652ab0c10ad6114b81a735e083c06d849e524c 100644
|
| --- a/common/clock/testclock/testtimer_test.go
|
| +++ b/common/clock/testclock/testtimer_test.go
|
| @@ -5,25 +5,40 @@
|
| package testclock
|
|
|
| import (
|
| + "fmt"
|
| + "runtime"
|
| + "strings"
|
| + "sync"
|
| "testing"
|
| "time"
|
|
|
| "github.com/luci/luci-go/common/clock"
|
| + "golang.org/x/net/context"
|
| +
|
| . "github.com/smartystreets/goconvey/convey"
|
| )
|
|
|
| +// trashTimer is a useless implementation of clock.Timer specifically designed
|
| +// to exist and not be a test timer type.
|
| +type trashTimer struct {
|
| + clock.Timer
|
| +}
|
| +
|
| func TestTestTimer(t *testing.T) {
|
| t.Parallel()
|
|
|
| Convey(`A testing clock instance`, t, func() {
|
| - now := time.Date(2015, 01, 01, 00, 00, 00, 00, time.UTC)
|
| - c := New(now)
|
| + ctx, cancelFunc := context.WithCancel(context.Background())
|
| + defer cancelFunc()
|
| +
|
| + now := TestTimeLocal
|
| + clk := New(now)
|
|
|
| Convey(`A timer instance`, func() {
|
| - t := c.NewTimer()
|
| + t := clk.NewTimer(ctx)
|
|
|
| Convey(`Should have a non-nil C.`, func() {
|
| - So(t.GetC(), ShouldBeNil)
|
| + So(t.GetC(), ShouldNotBeNil)
|
| })
|
|
|
| Convey(`When activated`, func() {
|
| @@ -36,16 +51,16 @@ func TestTestTimer(t *testing.T) {
|
|
|
| Convey(`When stopped, should return active.`, func() {
|
| So(t.Stop(), ShouldBeTrue)
|
| - So(t.GetC(), ShouldBeNil)
|
| + So(t.GetC(), ShouldNotBeNil)
|
|
|
| Convey(`And when stopped again, should return inactive.`, func() {
|
| So(t.Stop(), ShouldBeFalse)
|
| - So(t.GetC(), ShouldBeNil)
|
| + So(t.GetC(), ShouldNotBeNil)
|
| })
|
| })
|
|
|
| Convey(`When stopped after expiring, should not have a signal.`, func() {
|
| - c.Add(1 * time.Second)
|
| + clk.Add(1 * time.Second)
|
| So(t.Stop(), ShouldBeTrue)
|
|
|
| var signalled bool
|
| @@ -61,25 +76,120 @@ func TestTestTimer(t *testing.T) {
|
|
|
| Convey(`Should successfully signal.`, func() {
|
| So(t.Reset(1*time.Second), ShouldBeFalse)
|
| - c.Add(1 * time.Second)
|
| + clk.Add(1 * time.Second)
|
|
|
| - So(t.GetC(), ShouldNotBeNil)
|
| - So(<-t.GetC(), ShouldResemble, now.Add(1*time.Second))
|
| + So(<-t.GetC(), ShouldResemble, clock.TimerResult{Time: now.Add(1 * time.Second)})
|
| + })
|
| +
|
| + Convey(`Should signal immediately if the timer is in the past.`, func() {
|
| + So(t.Reset(-1*time.Second), ShouldBeFalse)
|
| + So(<-t.GetC(), ShouldResemble, clock.TimerResult{Time: now})
|
| + })
|
| +
|
| + Convey(`Will trigger immediately if the Context is canceled when reset.`, func() {
|
| + cancelFunc()
|
| +
|
| + // Works for the first timer?
|
| + So(t.Reset(time.Hour), ShouldBeFalse)
|
| + So((<-t.GetC()).Err, ShouldEqual, context.Canceled)
|
| +
|
| + // Works for the second timer?
|
| + So(t.Reset(time.Hour), ShouldBeFalse)
|
| + So((<-t.GetC()).Err, ShouldEqual, context.Canceled)
|
| + })
|
| +
|
| + Convey(`Will trigger when the Context is canceled.`, func() {
|
| + clk.SetTimerCallback(func(time.Duration, clock.Timer) {
|
| + cancelFunc()
|
| + })
|
| +
|
| + // Works for the first timer?
|
| + So(t.Reset(time.Hour), ShouldBeFalse)
|
| + So((<-t.GetC()).Err, ShouldEqual, context.Canceled)
|
| +
|
| + // Works for the second timer?
|
| + So(t.Reset(time.Hour), ShouldBeFalse)
|
| + So((<-t.GetC()).Err, ShouldEqual, context.Canceled)
|
| + })
|
| +
|
| + Convey(`Will use the same channel when reset.`, func() {
|
| + timerC := t.GetC()
|
| + t.Reset(time.Second)
|
| + clk.Add(time.Second)
|
| + So(<-timerC, ShouldResemble, clock.TimerResult{Time: now.Add(1 * time.Second)})
|
| + })
|
| +
|
| + Convey(`Will not signal the timer channel if stopped.`, func() {
|
| + t.Reset(time.Second)
|
| + t.Stop()
|
| + clk.Add(time.Second)
|
| +
|
| + runtime.Gosched()
|
| +
|
| + triggered := false
|
| + select {
|
| + case <-t.GetC():
|
| + triggered = true
|
| + default:
|
| + break
|
| + }
|
| + So(triggered, ShouldBeFalse)
|
| + })
|
| +
|
| + Convey(`Can set and retrieve timer tags.`, func() {
|
| + var tagMu sync.Mutex
|
| + tagMap := map[string]struct{}{}
|
| +
|
| + // On the last timer callback, advance time past the timer threshold.
|
| + timers := make([]clock.Timer, 10)
|
| + count := 0
|
| + clk.SetTimerCallback(func(_ time.Duration, t clock.Timer) {
|
| + tagMu.Lock()
|
| + defer tagMu.Unlock()
|
| +
|
| + tagMap[strings.Join(GetTags(t), "")] = struct{}{}
|
| + count++
|
| + if count == len(timers) {
|
| + clk.Add(time.Second)
|
| + }
|
| + })
|
| +
|
| + for i := range timers {
|
| + t := clk.NewTimer(clock.Tag(ctx, fmt.Sprintf("%d", i)))
|
| + t.Reset(time.Second)
|
| + timers[i] = t
|
| + }
|
| +
|
| + // Wait until all timers have expired.
|
| + for _, t := range timers {
|
| + <-t.GetC()
|
| + }
|
| +
|
| + // Did we see all of their tags?
|
| + for i := range timers {
|
| + _, ok := tagMap[fmt.Sprintf("%d", i)]
|
| + So(ok, ShouldBeTrue)
|
| + }
|
| + })
|
| +
|
| + Convey(`A non-test timer has a nil tag.`, func() {
|
| + t := trashTimer{}
|
| + So(GetTags(t), ShouldBeNil)
|
| })
|
| })
|
|
|
| - Convey(`Multiple goroutines using the timer...`, func() {
|
| + Convey(`Multiple goroutines using timers...`, func() {
|
| // Mark when timers are started, so we can ensure that our signalling
|
| // happens after the timers have been instantiated.
|
| timerStartedC := make(chan bool)
|
| - c.SetTimerCallback(func(_ time.Duration, _ clock.Timer) {
|
| + clk.SetTimerCallback(func(_ time.Duration, _ clock.Timer) {
|
| timerStartedC <- true
|
| })
|
|
|
| - resultC := make(chan time.Time)
|
| + resultC := make(chan clock.TimerResult)
|
| for i := time.Duration(0); i < 5; i++ {
|
| go func(d time.Duration) {
|
| - timer := c.NewTimer()
|
| + timer := clk.NewTimer(ctx)
|
| timer.Reset(d)
|
| resultC <- <-timer.GetC()
|
| }(i * time.Second)
|
| @@ -96,21 +206,57 @@ func TestTestTimer(t *testing.T) {
|
| }
|
|
|
| // Advance the clock to +2s. Three timers should signal.
|
| - c.Set(now.Add(2 * time.Second))
|
| + clk.Set(now.Add(2 * time.Second))
|
| <-resultC
|
| <-resultC
|
| <-resultC
|
| So(moreResults(), ShouldBeFalse)
|
|
|
| // Advance clock to +3s. One timer should signal.
|
| - c.Set(now.Add(3 * time.Second))
|
| + clk.Set(now.Add(3 * time.Second))
|
| <-resultC
|
| So(moreResults(), ShouldBeFalse)
|
|
|
| // Advance clock to +10s. One final timer should signal.
|
| - c.Set(now.Add(10 * time.Second))
|
| + clk.Set(now.Add(10 * time.Second))
|
| <-resultC
|
| So(moreResults(), ShouldBeFalse)
|
| })
|
| })
|
| }
|
| +
|
| +func TestTimerTags(t *testing.T) {
|
| + t.Parallel()
|
| +
|
| + Convey(`A context with tags {"A", "B"}`, t, func() {
|
| + c := clock.Tag(clock.Tag(context.Background(), "A"), "B")
|
| +
|
| + Convey(`Has tags, {"A", "B"}`, func() {
|
| + So(clock.Tags(c), ShouldResemble, []string{"A", "B"})
|
| + })
|
| +
|
| + Convey(`Will be retained by a testclock.Timer.`, func() {
|
| + tc := New(TestTimeUTC)
|
| + t := tc.NewTimer(c)
|
| +
|
| + So(GetTags(t), ShouldResemble, []string{"A", "B"})
|
| +
|
| + Convey(`The timer tests positive for tags {"A", "B"}`, func() {
|
| + So(HasTags(t, "A", "B"), ShouldBeTrue)
|
| + })
|
| +
|
| + Convey(`The timer tests negative for tags {"A"}, {"B"}, and {"A", "C"}`, func() {
|
| + So(HasTags(t, "A"), ShouldBeFalse)
|
| + So(HasTags(t, "B"), ShouldBeFalse)
|
| + So(HasTags(t, "A", "C"), ShouldBeFalse)
|
| + })
|
| + })
|
| +
|
| + Convey(`A non-test timer tests negative for {"A"} and {"A", "B"}.`, func() {
|
| + t := &trashTimer{}
|
| +
|
| + So(HasTags(t, "A"), ShouldBeFalse)
|
| + So(HasTags(t, "A", "B"), ShouldBeFalse)
|
| + })
|
| + })
|
| +}
|
|
|