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) |
+ }) |
+ }) |
+} |