syncclock

package module
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 14, 2026 License: MIT Imports: 3 Imported by: 1

README

syncclock

A near drop-in replacement for clockwork.FakeClock backed by Go 1.25's testing/synctest package.

Instead of maintaining its own fake time and waiter lists, syncclock delegates to the real time package inside a synctest bubble and uses synctest.Wait to synchronise goroutines. Time only advances when Advance is called, or if you are Sleeping and calling synctest.Wait() yourself.

Install

go get github.com/AnomalRoil/syncclock@latest

Requires Go 1.25 or later.

Usage

package syncclock_test

import (
	"sync"
	"sync/atomic"
	"testing"
	"testing/synctest"
	"time"

	"github.com/AnomalRoil/syncclock"
	"github.com/jonboulle/clockwork"
)

func myFunc(clock clockwork.Clock, i *atomic.Int64) {
	clock.Sleep(3 * time.Second)
	i.Add(1)
}

func TestMyFunc(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		var i atomic.Int64
		c := syncclock.NewFakeClock()

		var wg sync.WaitGroup
		wg.Add(1)
		go func() {
			myFunc(c, &i)
			wg.Done()
		}()
		synctest.Wait()

		if v := i.Load(); v != 0 {
			t.Fatalf("expected 0, got %d", v)
		}

		c.Advance(1 * time.Hour)
		wg.Wait()
		synctest.Wait()

		if v := i.Load(); v != 1 {
			t.Fatalf("expected 1, got %d", v)
		}
	})
}
Migration from clockwork
  1. Wrap your test function with synctest.Test.
  2. Replace clockwork.NewFakeClock() with syncclock.NewFakeClock() (or clockwork.NewFakeClockAt(t) with syncclock.NewFakeClockAt(t)).

Production code can keep using clockwork.NewRealClock() unchanged because SyncClock implements clockwork.Clock.

Not implemented

The following clockwork.FakeClock methods have no equivalent in SyncClock because synctest's cooperative scheduling model makes them unnecessary:

Method Replacement
BlockUntil(n int) Use synctest.Wait() as it blocks until all goroutines in the bubble are durably blocked, which is a stronger guarantee than counting waiters.
BlockUntilContext(ctx context.Context, n int) error Use synctest.Wait() for the same reason.

NewRealClock() is also not provided; keep using clockwork.NewRealClock() in production since SyncClock is only meaningful inside a synctest bubble.

Limitations

  • A SyncClock must be created inside a synctest.Test bubble.
  • NewFakeClockAt panics if the requested time is before midnight UTC 2000-01-01 (a synctest limitation).
  • synctest does not consider blocking I/O operations (e.g. network reads) as durable blocks, so HTTP servers and similar code must be mocked using net.Pipe or equivalent. See the synctest docs for details.

License

MIT

Documentation

Overview

Package syncclock provides a near drop-in replacement for clockwork.FakeClock that is backed by Go 1.25's testing/synctest package instead of maintaining its own fake time.

Rather than tracking waiters and manually expiring timers, SyncClock delegates to the real time package and uses synctest.Wait to synchronise goroutines inside a synctest bubble. This means all standard time functions (After, Sleep, NewTimer, …) work as expected and time only advances when SyncClock.Advance is called.

Migration from clockwork

In most cases migrating is a two-step process:

  1. Wrap your test function with synctest.Test.
  2. Replace clockwork.NewFakeClock with NewFakeClock (or clockwork.NewFakeClockAt with NewFakeClockAt).

Production code can keep using clockwork.NewRealClock unchanged because SyncClock implements clockwork.Clock.

Not implemented

The following clockwork.FakeClock methods have no equivalent in SyncClock because synctest's cooperative scheduling model makes them unnecessary:

Limitations

A SyncClock must be created inside a synctest.Test bubble.

Because synctest does not consider blocking I/O operations (e.g. network reads) as durable blocks, HTTP servers and similar code must be mocked using net.Pipe or equivalent. See the synctest documentation for details.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type FakeClock

type FakeClock interface {
	clockwork.Clock
	Advance(d time.Duration)
}

FakeClock is a minimal interface satisfied by both clockwork.FakeClock and SyncClock. It allows code that only needs the Advance method to accept either implementation without a type assertion.

type SyncClock

type SyncClock struct{}

SyncClock implements clockwork.Clock on top of testing/synctest. All time operations delegate to the standard time package; advancing the clock is achieved by sleeping inside the synctest bubble and then calling synctest.Wait to let other goroutines observe the new time.

A SyncClock must only be used inside a synctest.Test bubble.

func NewFakeClock

func NewFakeClock() *SyncClock

NewFakeClock creates a SyncClock whose initial time is time.Now inside the synctest bubble. It must be called from within a synctest.Test bubble.

func NewFakeClockAt

func NewFakeClockAt(t time.Time) *SyncClock

NewFakeClockAt creates a SyncClock whose initial time is t. It must be called from within a synctest.Test bubble.

It panics if t is before midnight UTC 2000-01-01 because synctest does not support rewinding time before that point.

func (*SyncClock) Advance

func (s *SyncClock) Advance(d time.Duration)

Advance moves the fake clock forward by d and waits for all goroutines in the synctest bubble to reach a durable blocking state. This is the equivalent of clockwork.FakeClock.Advance.

func (*SyncClock) After

func (s *SyncClock) After(d time.Duration) <-chan time.Time

After mimics time.After; it waits for d to elapse on the fake clock, then sends the current time on the returned channel.

func (*SyncClock) AfterFunc

func (s *SyncClock) AfterFunc(d time.Duration, f func()) clockwork.Timer

AfterFunc returns a clockwork.Timer that invokes f after d has elapsed.

func (*SyncClock) NewTicker

func (s *SyncClock) NewTicker(d time.Duration) clockwork.Ticker

NewTicker returns a clockwork.Ticker that ticks every d.

func (*SyncClock) NewTimer

func (s *SyncClock) NewTimer(d time.Duration) clockwork.Timer

NewTimer returns a clockwork.Timer that fires after d.

func (*SyncClock) Now

func (s *SyncClock) Now() time.Time

Now returns the current time of the fake clock.

func (*SyncClock) Since

func (s *SyncClock) Since(t time.Time) time.Duration

Since returns the time elapsed since t on the fake clock.

func (*SyncClock) Sleep

func (s *SyncClock) Sleep(d time.Duration)

Sleep blocks until d has elapsed on the fake clock.

func (*SyncClock) Until

func (s *SyncClock) Until(t time.Time) time.Duration

Until returns the duration until t on the fake clock.

type SyncTicker

type SyncTicker struct{ *time.Ticker }

SyncTicker wraps a time.Ticker and implements clockwork.Ticker.

func (*SyncTicker) Chan

func (st *SyncTicker) Chan() <-chan time.Time

Chan returns the channel on which ticks are delivered.

type SyncTimer

type SyncTimer struct{ *time.Timer }

SyncTimer wraps a time.Timer and implements clockwork.Timer.

func (*SyncTimer) Chan

func (st *SyncTimer) Chan() <-chan time.Time

Chan returns the channel on which the timer fires.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL