From 4cd153555e4a62e75b2b67e11474fd290d7ed511 Mon Sep 17 00:00:00 2001 From: "Matt T. Proud" Date: Sun, 27 Jan 2013 19:59:20 +0100 Subject: [PATCH] Swap ``time.Now`` with testable ``Time.Now``. --- api/api.go | 2 ++ api/query.go | 4 +-- retrieval/format/processor0_0_1.go | 6 ++-- retrieval/scheduler.go | 31 ++------------------ retrieval/scheduler_test.go | 25 ++++------------- utility/test/time.go | 42 ++++++++++++++++++++++++++++ utility/time.go | 45 ++++++++++++++++++++++++++++++ 7 files changed, 102 insertions(+), 53 deletions(-) create mode 100644 utility/test/time.go create mode 100644 utility/time.go diff --git a/api/api.go b/api/api.go index 57a8ff4c1..7a6bf7ca5 100644 --- a/api/api.go +++ b/api/api.go @@ -2,6 +2,7 @@ package api import ( "code.google.com/p/gorest" + "github.com/prometheus/prometheus/utility" ) type MetricsService struct { @@ -9,4 +10,5 @@ type MetricsService struct { query gorest.EndPoint `method:"GET" path:"/query?{expr:string}&{json:string}" output:"string"` queryRange gorest.EndPoint `method:"GET" path:"/query_range?{expr:string}&{end:int64}&{range:int64}&{step:int64}" output:"string"` + time utility.Time } diff --git a/api/query.go b/api/query.go index 71853ca7a..c25082024 100644 --- a/api/query.go +++ b/api/query.go @@ -15,7 +15,7 @@ func (serv MetricsService) Query(Expr string, Json string) (result string) { return ast.ErrorToJSON(err) } - timestamp := time.Now() + timestamp := serv.time.Now() rb := serv.ResponseBuilder() var format ast.OutputFormat @@ -42,7 +42,7 @@ func (serv MetricsService) QueryRange(Expr string, End int64, Range int64, Step rb.SetContentType(gorest.Application_Json) if End == 0 { - End = time.Now().Unix() + End = serv.time.Now().Unix() } if Step < 1 { diff --git a/retrieval/format/processor0_0_1.go b/retrieval/format/processor0_0_1.go index bda8d962e..062cb1c2a 100644 --- a/retrieval/format/processor0_0_1.go +++ b/retrieval/format/processor0_0_1.go @@ -17,9 +17,9 @@ import ( "encoding/json" "fmt" "github.com/prometheus/prometheus/model" + "github.com/prometheus/prometheus/utility" "io" "io/ioutil" - "time" ) const ( @@ -41,6 +41,7 @@ var ( // processor001 is responsible for handling API version 0.0.1. type processor001 struct { + time utility.Time } // entity001 represents a the JSON structure that 0.0.1 uses. @@ -72,8 +73,7 @@ func (p *processor001) Process(stream io.ReadCloser, baseLabels model.LabelSet, return } - // Swap this to the testable timer. - now := time.Now() + now := p.time.Now() // TODO(matt): This outer loop is a great basis for parallelization. for _, entity := range entities { diff --git a/retrieval/scheduler.go b/retrieval/scheduler.go index 53ba6ebf2..062baf259 100644 --- a/retrieval/scheduler.go +++ b/retrieval/scheduler.go @@ -14,6 +14,7 @@ package retrieval import ( + "github.com/prometheus/prometheus/utility" "math" "time" ) @@ -27,32 +28,6 @@ const ( MAXIMUM_BACKOFF_VALUE = 30 * time.Minute ) -// A basic interface only useful in testing contexts for dispensing the time -// in a controlled manner. -type instantProvider interface { - // The current instant. - Now() time.Time -} - -// timer is a simple means for fluently wrapping around standard Go timekeeping -// mechanisms to enhance testability without compromising code readability. -// -// A timer is sufficient for use on bare initialization. A provider should be -// set only for test contexts. When not provided, a timer emits the current -// system time. -type timer struct { - // The underlying means through which time is provided, if supplied. - provider instantProvider -} - -// Emit the current instant. -func (t timer) Now() time.Time { - if t.provider == nil { - return time.Now() - } - return t.provider.Now() -} - // scheduler is an interface that various scheduling strategies must fulfill // in order to set the scheduling order for a target. // @@ -83,7 +58,7 @@ type scheduler interface { type healthScheduler struct { scheduledFor time.Time target healthReporter - timer timer + time utility.Time unreachableCount int } @@ -138,6 +113,6 @@ func (s *healthScheduler) Reschedule(e time.Time, f TargetState) { backoff = exponential } - s.scheduledFor = s.timer.Now().Add(backoff) + s.scheduledFor = s.time.Now().Add(backoff) } } diff --git a/retrieval/scheduler_test.go b/retrieval/scheduler_test.go index bed42929c..73283b43c 100644 --- a/retrieval/scheduler_test.go +++ b/retrieval/scheduler_test.go @@ -14,6 +14,7 @@ package retrieval import ( + "github.com/prometheus/prometheus/utility" "github.com/prometheus/prometheus/utility/test" "testing" "time" @@ -32,19 +33,6 @@ func (h fakeHealthReporter) State() (state TargetState) { return } -type fakeTimeProvider struct { - index int - timeQueue []time.Time -} - -func (t *fakeTimeProvider) Now() (time time.Time) { - time = t.timeQueue[t.index] - - t.index++ - - return -} - func testHealthScheduler(t test.Tester) { now := time.Now() var scenarios = []struct { @@ -77,10 +65,7 @@ func testHealthScheduler(t test.Tester) { } for i, scenario := range scenarios { - provider := &fakeTimeProvider{} - for _, time := range scenario.preloadedTimes { - provider.timeQueue = append(provider.timeQueue, time) - } + provider := test.NewInstantProvider(scenario.preloadedTimes) reporter := fakeHealthReporter{} for _, state := range scenario.futureHealthState { @@ -90,12 +75,12 @@ func testHealthScheduler(t test.Tester) { t.Fatalf("%d. times and health reports and next time lengths were not equal.", i) } - timer := timer{ - provider: provider, + time := utility.Time{ + Provider: provider, } scheduler := healthScheduler{ - timer: timer, + time: time, target: reporter, scheduledFor: now, } diff --git a/utility/test/time.go b/utility/test/time.go new file mode 100644 index 000000000..f787aaebd --- /dev/null +++ b/utility/test/time.go @@ -0,0 +1,42 @@ +// Copyright 2013 Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package test + +import ( + "github.com/prometheus/prometheus/utility" + "time" +) + +type instantProvider struct { + index int + timeQueue []time.Time +} + +func (t *instantProvider) Now() (time time.Time) { + time = t.timeQueue[t.index] + + t.index++ + + return +} + +// NewInstantProvider furnishes an InstantProvider with prerecorded responses +// for calls made against it. It has no validation behaviors of its own and +// will panic if times are requested more than available pre-recorded behaviors. +func NewInstantProvider(times []time.Time) utility.InstantProvider { + return &instantProvider{ + index: 0, + timeQueue: times, + } +} diff --git a/utility/time.go b/utility/time.go new file mode 100644 index 000000000..b8b450c5e --- /dev/null +++ b/utility/time.go @@ -0,0 +1,45 @@ +// Copyright 2013 Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package utility + +import ( + "time" +) + +// A basic interface only useful in testing contexts for dispensing the time +// in a controlled manner. +type InstantProvider interface { + // The current instant. + Now() time.Time +} + +// Time is a simple means for fluently wrapping around standard Go timekeeping +// mechanisms to enhance testability without compromising code readability. +// +// It is sufficient for use on bare initialization. A provider should be +// set only for test contexts. When not provided, it emits the current +// system time. +type Time struct { + // The underlying means through which time is provided, if supplied. + Provider InstantProvider +} + +// Emit the current instant. +func (t Time) Now() time.Time { + if t.Provider == nil { + return time.Now() + } + + return t.Provider.Now() +}