Repository: kubernetes/klog
Pull request #310 (2022-03-18 17:10:27)

dependencies: avoid k8s.io/utils, fork clock code instead
**What this PR does / why we need it**:

The test for the flush daemon needs the simulated clock package, but we cannot
use k8s.io/utils in klog because it causes a circular dependency.

We could drop the test, but it is useful, so instead we fork the package and
carry a copy in "internal".

**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:

Fixes https://github.com/kubernetes/kubernetes/pull/108725#discussion_r830102042

**Release note**:
```release-note
Instead of depending on k8s.io/utils/clock (a circular dependency!), that copy of that code is used.
```


Patch:
diff --git a/OWNERS b/OWNERS
index 8cccebf..ad5063f 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,13 +1,19 @@
 # See the OWNERS docs at https://go.k8s.io/owners
 reviewers:
+  - jayunit100
+  - hoegaarden
+  - andyxning
+  - neolit123
   - pohly
+  - yagonobre
+  - vincepri
+  - detiber
 approvers:
   - dims
   - thockin
-  - serathius
-emeritus_approvers:
-  - brancz
   - justinsb
-  - lavalamp
-  - piosz
   - tallclair
+  - piosz
+  - brancz
+  - lavalamp
+  - serathius
diff --git a/contextual.go b/contextual.go
index 33743ff..bb03808 100644
--- a/contextual.go
+++ b/contextual.go
@@ -46,6 +46,10 @@ var (
 	// globalLogger.
 	globalLoggerOptions loggerOptions
 
+	// contextualLogger defines whether globalLogger may get called
+	// directly.
+	contextualLogger bool
+
 	// klogLogger is used as fallback for logging through the normal klog code
 	// when no Logger is set.
 	klogLogger logr.Logger = logr.New(&klogger{})
@@ -133,6 +137,15 @@ type loggerOptions struct {
 	flush            func()
 }
 
+// SetContextualLogger does the same as SetLogger, but in addition the
+// logger may also get called directly by code that retrieves it
+// with FromContext, TODO or Background. The logger therefore must
+// implements its own verbosity checking.
+func SetContextualLogger(logger logr.Logger) {
+	globalLogger = &logger
+	contextualLogger = true
+}
+
 // ClearLogger removes a backing Logger implementation if one was set earlier
 // with SetLogger.
 //
diff --git a/go.mod b/go.mod
index 6385c6c..31aefba 100644
--- a/go.mod
+++ b/go.mod
@@ -2,7 +2,4 @@ module k8s.io/klog/v2
 
 go 1.13
 
-require (
-	github.com/go-logr/logr v1.2.0
-	k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
-)
+require github.com/go-logr/logr v1.2.0
diff --git a/go.sum b/go.sum
index ef73139..919fbad 100644
--- a/go.sum
+++ b/go.sum
@@ -1,13 +1,2 @@
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
 github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE=
 github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
-k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc=
-k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
diff --git a/internal/clock/README.md b/internal/clock/README.md
new file mode 100644
index 0000000..03d692c
--- /dev/null
+++ b/internal/clock/README.md
@@ -0,0 +1,7 @@
+# Clock
+
+This package provides an interface for time-based operations.  It allows
+mocking time for testing.
+
+This is a copy of k8s.io/utils/clock. We have to copy it to avoid a circular
+dependency (k8s.io/klog -> k8s.io/utils -> k8s.io/klog).
diff --git a/internal/clock/clock.go b/internal/clock/clock.go
new file mode 100644
index 0000000..b8b6af5
--- /dev/null
+++ b/internal/clock/clock.go
@@ -0,0 +1,178 @@
+/*
+Copyright 2014 The Kubernetes Authors.
+
+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 clock
+
+import "time"
+
+// PassiveClock allows for injecting fake or real clocks into code
+// that needs to read the current time but does not support scheduling
+// activity in the future.
+type PassiveClock interface {
+	Now() time.Time
+	Since(time.Time) time.Duration
+}
+
+// Clock allows for injecting fake or real clocks into code that
+// needs to do arbitrary things based on time.
+type Clock interface {
+	PassiveClock
+	// After returns the channel of a new Timer.
+	// This method does not allow to free/GC the backing timer before it fires. Use
+	// NewTimer instead.
+	After(d time.Duration) <-chan time.Time
+	// NewTimer returns a new Timer.
+	NewTimer(d time.Duration) Timer
+	// Sleep sleeps for the provided duration d.
+	// Consider making the sleep interruptible by using 'select' on a context channel and a timer channel.
+	Sleep(d time.Duration)
+	// Tick returns the channel of a new Ticker.
+	// This method does not allow to free/GC the backing ticker. Use
+	// NewTicker from WithTicker instead.
+	Tick(d time.Duration) <-chan time.Time
+}
+
+// WithTicker allows for injecting fake or real clocks into code that
+// needs to do arbitrary things based on time.
+type WithTicker interface {
+	Clock
+	// NewTicker returns a new Ticker.
+	NewTicker(time.Duration) Ticker
+}
+
+// WithDelayedExecution allows for injecting fake or real clocks into
+// code that needs to make use of AfterFunc functionality.
+type WithDelayedExecution interface {
+	Clock
+	// AfterFunc executes f in its own goroutine after waiting
+	// for d duration and returns a Timer whose channel can be
+	// closed by calling Stop() on the Timer.
+	AfterFunc(d time.Duration, f func()) Timer
+}
+
+// WithTickerAndDelayedExecution allows for injecting fake or real clocks
+// into code that needs Ticker and AfterFunc functionality
+type WithTickerAndDelayedExecution interface {
+	WithTicker
+	// AfterFunc executes f in its own goroutine after waiting
+	// for d duration and returns a Timer whose channel can be
+	// closed by calling Stop() on the Timer.
+	AfterFunc(d time.Duration, f func()) Timer
+}
+
+// Ticker defines the Ticker interface.
+type Ticker interface {
+	C() <-chan time.Time
+	Stop()
+}
+
+var _ = WithTicker(RealClock{})
+
+// RealClock really calls time.Now()
+type RealClock struct{}
+
+// Now returns the current time.
+func (RealClock) Now() time.Time {
+	return time.Now()
+}
+
+// Since returns time since the specified timestamp.
+func (RealClock) Since(ts time.Time) time.Duration {
+	return time.Since(ts)
+}
+
+// After is the same as time.After(d).
+// This method does not allow to free/GC the backing timer before it fires. Use
+// NewTimer instead.
+func (RealClock) After(d time.Duration) <-chan time.Time {
+	return time.After(d)
+}
+
+// NewTimer is the same as time.NewTimer(d)
+func (RealClock) NewTimer(d time.Duration) Timer {
+	return &realTimer{
+		timer: time.NewTimer(d),
+	}
+}
+
+// AfterFunc is the same as time.AfterFunc(d, f).
+func (RealClock) AfterFunc(d time.Duration, f func()) Timer {
+	return &realTimer{
+		timer: time.AfterFunc(d, f),
+	}
+}
+
+// Tick is the same as time.Tick(d)
+// This method does not allow to free/GC the backing ticker. Use
+// NewTicker instead.
+func (RealClock) Tick(d time.Duration) <-chan time.Time {
+	return time.Tick(d)
+}
+
+// NewTicker returns a new Ticker.
+func (RealClock) NewTicker(d time.Duration) Ticker {
+	return &realTicker{
+		ticker: time.NewTicker(d),
+	}
+}
+
+// Sleep is the same as time.Sleep(d)
+// Consider making the sleep interruptible by using 'select' on a context channel and a timer channel.
+func (RealClock) Sleep(d time.Duration) {
+	time.Sleep(d)
+}
+
+// Timer allows for injecting fake or real timers into code that
+// needs to do arbitrary things based on time.
+type Timer interface {
+	C() <-chan time.Time
+	Stop() bool
+	Reset(d time.Duration) bool
+}
+
+var _ = Timer(&realTimer{})
+
+// realTimer is backed by an actual time.Timer.
+type realTimer struct {
+	timer *time.Timer
+}
+
+// C returns the underlying timer's channel.
+func (r *realTimer) C() <-chan time.Time {
+	return r.timer.C
+}
+
+// Stop calls Stop() on the underlying timer.
+func (r *realTimer) Stop() bool {
+	return r.timer.Stop()
+}
+
+// Reset calls Reset() on the underlying timer.
+func (r *realTimer) Reset(d time.Duration) bool {
+	return r.timer.Reset(d)
+}
+
+type realTicker struct {
+	ticker *time.Ticker
+}
+
+func (r *realTicker) C() <-chan time.Time {
+	return r.ticker.C
+}
+
+func (r *realTicker) Stop() {
+	r.ticker.Stop()
+}
diff --git a/klog.go b/klog.go
index bb6f64b..cb04590 100644
--- a/klog.go
+++ b/klog.go
@@ -91,9 +91,9 @@ import (
 	"github.com/go-logr/logr"
 
 	"k8s.io/klog/v2/internal/buffer"
+	"k8s.io/klog/v2/internal/clock"
 	"k8s.io/klog/v2/internal/serialize"
 	"k8s.io/klog/v2/internal/severity"
-	"k8s.io/utils/clock"
 )
 
 // severityValue identifies the sort of log: info, warning etc. It also implements


Test patch:
diff --git a/internal/clock/testing/fake_clock.go b/internal/clock/testing/fake_clock.go
new file mode 100644
index 0000000..a91114a
--- /dev/null
+++ b/internal/clock/testing/fake_clock.go
@@ -0,0 +1,361 @@
+/*
+Copyright 2014 The Kubernetes Authors.
+
+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 testing
+
+import (
+	"sync"
+	"time"
+
+	"k8s.io/klog/v2/internal/clock"
+)
+
+var (
+	_ = clock.PassiveClock(&FakePassiveClock{})
+	_ = clock.WithTicker(&FakeClock{})
+	_ = clock.Clock(&IntervalClock{})
+)
+
+// FakePassiveClock implements PassiveClock, but returns an arbitrary time.
+type FakePassiveClock struct {
+	lock sync.RWMutex
+	time time.Time
+}
+
+// FakeClock implements clock.Clock, but returns an arbitrary time.
+type FakeClock struct {
+	FakePassiveClock
+
+	// waiters are waiting for the fake time to pass their specified time
+	waiters []*fakeClockWaiter
+}
+
+type fakeClockWaiter struct {
+	targetTime    time.Time
+	stepInterval  time.Duration
+	skipIfBlocked bool
+	destChan      chan time.Time
+	fired         bool
+	afterFunc     func()
+}
+
+// NewFakePassiveClock returns a new FakePassiveClock.
+func NewFakePassiveClock(t time.Time) *FakePassiveClock {
+	return &FakePassiveClock{
+		time: t,
+	}
+}
+
+// NewFakeClock constructs a fake clock set to the provided time.
+func NewFakeClock(t time.Time) *FakeClock {
+	return &FakeClock{
+		FakePassiveClock: *NewFakePassiveClock(t),
+	}
+}
+
+// Now returns f's time.
+func (f *FakePassiveClock) Now() time.Time {
+	f.lock.RLock()
+	defer f.lock.RUnlock()
+	return f.time
+}
+
+// Since returns time since the time in f.
+func (f *FakePassiveClock) Since(ts time.Time) time.Duration {
+	f.lock.RLock()
+	defer f.lock.RUnlock()
+	return f.time.Sub(ts)
+}
+
+// SetTime sets the time on the FakePassiveClock.
+func (f *FakePassiveClock) SetTime(t time.Time) {
+	f.lock.Lock()
+	defer f.lock.Unlock()
+	f.time = t
+}
+
+// After is the fake version of time.After(d).
+func (f *FakeClock) After(d time.Duration) <-chan time.Time {
+	f.lock.Lock()
+	defer f.lock.Unlock()
+	stopTime := f.time.Add(d)
+	ch := make(chan time.Time, 1) // Don't block!
+	f.waiters = append(f.waiters, &fakeClockWaiter{
+		targetTime: stopTime,
+		destChan:   ch,
+	})
+	return ch
+}
+
+// NewTimer constructs a fake timer, akin to time.NewTimer(d).
+func (f *FakeClock) NewTimer(d time.Duration) clock.Timer {
+	f.lock.Lock()
+	defer f.lock.Unlock()
+	stopTime := f.time.Add(d)
+	ch := make(chan time.Time, 1) // Don't block!
+	timer := &fakeTimer{
+		fakeClock: f,
+		waiter: fakeClockWaiter{
+			targetTime: stopTime,
+			destChan:   ch,
+		},
+	}
+	f.waiters = append(f.waiters, &timer.waiter)
+	return timer
+}
+
+// AfterFunc is the Fake version of time.AfterFunc(d, cb).
+func (f *FakeClock) AfterFunc(d time.Duration, cb func()) clock.Timer {
+	f.lock.Lock()
+	defer f.lock.Unlock()
+	stopTime := f.time.Add(d)
+	ch := make(chan time.Time, 1) // Don't block!
+
+	timer := &fakeTimer{
+		fakeClock: f,
+		waiter: fakeClockWaiter{
+			targetTime: stopTime,
+			destChan:   ch,
+			afterFunc:  cb,
+		},
+	}
+	f.waiters = append(f.waiters, &timer.waiter)
+	return timer
+}
+
+// Tick constructs a fake ticker, akin to time.Tick
+func (f *FakeClock) Tick(d time.Duration) <-chan time.Time {
+	if d <= 0 {
+		return nil
+	}
+	f.lock.Lock()
+	defer f.lock.Unlock()
+	tickTime := f.time.Add(d)
+	ch := make(chan time.Time, 1) // hold one tick
+	f.waiters = append(f.waiters, &fakeClockWaiter{
+		targetTime:    tickTime,
+		stepInterval:  d,
+		skipIfBlocked: true,
+		destChan:      ch,
+	})
+
+	return ch
+}
+
+// NewTicker returns a new Ticker.
+func (f *FakeClock) NewTicker(d time.Duration) clock.Ticker {
+	f.lock.Lock()
+	defer f.lock.Unlock()
+	tickTime := f.time.Add(d)
+	ch := make(chan time.Time, 1) // hold one tick
+	f.waiters = append(f.waiters, &fakeClockWaiter{
+		targetTime:    tickTime,
+		stepInterval:  d,
+		skipIfBlocked: true,
+		destChan:      ch,
+	})
+
+	return &fakeTicker{
+		c: ch,
+	}
+}
+
+// Step moves the clock by Duration and notifies anyone that's called After,
+// Tick, or NewTimer.
+func (f *FakeClock) Step(d time.Duration) {
+	f.lock.Lock()
+	defer f.lock.Unlock()
+	f.setTimeLocked(f.time.Add(d))
+}
+
+// SetTime sets the time.
+func (f *FakeClock) SetTime(t time.Time) {
+	f.lock.Lock()
+	defer f.lock.Unlock()
+	f.setTimeLocked(t)
+}
+
+// Actually changes the time and checks any waiters. f must be write-locked.
+func (f *FakeClock) setTimeLocked(t time.Time) {
+	f.time = t
+	newWaiters := make([]*fakeClockWaiter, 0, len(f.waiters))
+	for i := range f.waiters {
+		w := f.waiters[i]
+		if !w.targetTime.After(t) {
+			if w.skipIfBlocked {
+				select {
+				case w.destChan <- t:
+					w.fired = true
+				default:
+				}
+			} else {
+				w.destChan <- t
+				w.fired = true
+			}
+
+			if w.afterFunc != nil {
+				w.afterFunc()
+			}
+
+			if w.stepInterval > 0 {
+				for !w.targetTime.After(t) {
+					w.targetTime = w.targetTime.Add(w.stepInterval)
+				}
+				newWaiters = append(newWaiters, w)
+			}
+
+		} else {
+			newWaiters = append(newWaiters, f.waiters[i])
+		}
+	}
+	f.waiters = newWaiters
+}
+
+// HasWaiters returns true if After or AfterFunc has been called on f but not yet satisfied (so you can
+// write race-free tests).
+func (f *FakeClock) HasWaiters() bool {
+	f.lock.RLock()
+	defer f.lock.RUnlock()
+	return len(f.waiters) > 0
+}
+
+// Sleep is akin to time.Sleep
+func (f *FakeClock) Sleep(d time.Duration) {
+	f.Step(d)
+}
+
+// IntervalClock implements clock.PassiveClock, but each invocation of Now steps the clock forward the specified duration.
+// IntervalClock technically implements the other methods of clock.Clock, but each implementation is just a panic.
+//
+// Deprecated: See SimpleIntervalClock for an alternative that only has the methods of PassiveClock.
+type IntervalClock struct {
+	Time     time.Time
+	Duration time.Duration
+}
+
+// Now returns i's time.
+func (i *IntervalClock) Now() time.Time {
+	i.Time = i.Time.Add(i.Duration)
+	return i.Time
+}
+
+// Since returns time since the time in i.
+func (i *IntervalClock) Since(ts time.Time) time.Duration {
+	return i.Time.Sub(ts)
+}
+
+// After is unimplemented, will panic.
+// TODO: make interval clock use FakeClock so this can be implemented.
+func (*IntervalClock) After(d time.Duration) <-chan time.Time {
+	panic("IntervalClock doesn't implement After")
+}
+
+// NewTimer is unimplemented, will panic.
+// TODO: make interval clock use FakeClock so this can be implemented.
+func (*IntervalClock) NewTimer(d time.Duration) clock.Timer {
+	panic("IntervalClock doesn't implement NewTimer")
+}
+
+// AfterFunc is unimplemented, will panic.
+// TODO: make interval clock use FakeClock so this can be implemented.
+func (*IntervalClock) AfterFunc(d time.Duration, f func()) clock.Timer {
+	panic("IntervalClock doesn't implement AfterFunc")
+}
+
+// Tick is unimplemented, will panic.
+// TODO: make interval clock use FakeClock so this can be implemented.
+func (*IntervalClock) Tick(d time.Duration) <-chan time.Time {
+	panic("IntervalClock doesn't implement Tick")
+}
+
+// NewTicker has no implementation yet and is omitted.
+// TODO: make interval clock use FakeClock so this can be implemented.
+func (*IntervalClock) NewTicker(d time.Duration) clock.Ticker {
+	panic("IntervalClock doesn't implement NewTicker")
+}
+
+// Sleep is unimplemented, will panic.
+func (*IntervalClock) Sleep(d time.Duration) {
+	panic("IntervalClock doesn't implement Sleep")
+}
+
+var _ = clock.Timer(&fakeTimer{})
+
+// fakeTimer implements clock.Timer based on a FakeClock.
+type fakeTimer struct {
+	fakeClock *FakeClock
+	waiter    fakeClockWaiter
+}
+
+// C returns the channel that notifies when this timer has fired.
+func (f *fakeTimer) C() <-chan time.Time {
+	return f.waiter.destChan
+}
+
+// Stop stops the timer and returns true if the timer has not yet fired, or false otherwise.
+func (f *fakeTimer) Stop() bool {
+	f.fakeClock.lock.Lock()
+	defer f.fakeClock.lock.Unlock()
+
+	newWaiters := make([]*fakeClockWaiter, 0, len(f.fakeClock.waiters))
+	for i := range f.fakeClock.waiters {
+		w := f.fakeClock.waiters[i]
+		if w != &f.waiter {
+			newWaiters = append(newWaiters, w)
+		}
+	}
+
+	f.fakeClock.waiters = newWaiters
+
+	return !f.waiter.fired
+}
+
+// Reset resets the timer to the fake clock's "now" + d. It returns true if the timer has not yet
+// fired, or false otherwise.
+func (f *fakeTimer) Reset(d time.Duration) bool {
+	f.fakeClock.lock.Lock()
+	defer f.fakeClock.lock.Unlock()
+
+	active := !f.waiter.fired
+
+	f.waiter.fired = false
+	f.waiter.targetTime = f.fakeClock.time.Add(d)
+
+	var isWaiting bool
+	for i := range f.fakeClock.waiters {
+		w := f.fakeClock.waiters[i]
+		if w == &f.waiter {
+			isWaiting = true
+			break
+		}
+	}
+	if !isWaiting {
+		f.fakeClock.waiters = append(f.fakeClock.waiters, &f.waiter)
+	}
+
+	return active
+}
+
+type fakeTicker struct {
+	c <-chan time.Time
+}
+
+func (t *fakeTicker) C() <-chan time.Time {
+	return t.c
+}
+
+func (t *fakeTicker) Stop() {
+}
diff --git a/internal/clock/testing/simple_interval_clock.go b/internal/clock/testing/simple_interval_clock.go
new file mode 100644
index 0000000..be26773
--- /dev/null
+++ b/internal/clock/testing/simple_interval_clock.go
@@ -0,0 +1,44 @@
+/*
+Copyright 2021 The Kubernetes Authors.
+
+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 testing
+
+import (
+	"time"
+
+	"k8s.io/klog/v2/internal/clock"
+)
+
+var (
+	_ = clock.PassiveClock(&SimpleIntervalClock{})
+)
+
+// SimpleIntervalClock implements clock.PassiveClock, but each invocation of Now steps the clock forward the specified duration
+type SimpleIntervalClock struct {
+	Time     time.Time
+	Duration time.Duration
+}
+
+// Now returns i's time.
+func (i *SimpleIntervalClock) Now() time.Time {
+	i.Time = i.Time.Add(i.Duration)
+	return i.Time
+}
+
+// Since returns time since the time in i.
+func (i *SimpleIntervalClock) Since(ts time.Time) time.Duration {
+	return i.Time.Sub(ts)
+}
diff --git a/klog_test.go b/klog_test.go
index 0a16574..a23f571 100644
--- a/klog_test.go
+++ b/klog_test.go
@@ -37,9 +37,9 @@ import (
 	"github.com/go-logr/logr"
 
 	"k8s.io/klog/v2/internal/buffer"
+	testingclock "k8s.io/klog/v2/internal/clock/testing"
 	"k8s.io/klog/v2/internal/severity"
 	"k8s.io/klog/v2/internal/test"
-	testingclock "k8s.io/utils/clock/testing"
 )
 
 // TODO: This test package should be refactored so that tests cannot