Help us shape the future of Go! Take the 2026 Go Developer Survey

clock

package
v0.11.0 Latest Latest
Warning

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

Go to latest
Published: Oct 12, 2026 License: Apache-2.0 Imports: 2 Imported by: 0

README

Clock Package

The clock package provides time abstractions for testability, enabling precise control over time-dependent behavior in tests.

Overview

Time is a form of IO that needs to be controlled in tests. The Clock interface allows code to use an injectable time source rather than depending directly on time.Now().

Interface

type Clock interface {
    Now() time.Time
}

Implementations

SystemClock

For production use - wraps the real system clock:

clock := clock.NewSystemClock()
now := clock.Now() // Returns time.Now()
FixtureClock

For testing - provides controllable time:

// Start at a specific time
fixedTime := time.Date(2024, 6, 15, 10, 0, 0, 0, time.UTC)
clk := clock.NewFixtureClock(fixedTime)

// Get current time (frozen)
now := clk.Now() // Returns fixedTime

// Manipulate time
clk.Set(time.Date(2024, 12, 25, 12, 0, 0, 0, time.UTC))
clk.Advance(2 * time.Hour)
clk.Rewind(30 * time.Minute)

API

FixtureClock Methods
  • NewFixtureClock(startTime time.Time) - Create clock at specific time (uses time.Now() if zero time provided)
  • Now() - Get current fixture time (frozen unless explicitly manipulated)
  • Set(t time.Time) - Set clock to specific time
  • Advance(d time.Duration) - Move time forward by duration
  • Rewind(d time.Duration) - Move time backward by duration

Usage

In Production Code

Accept a Clock interface and default to SystemClock:

type TokenIssuer struct {
    clock clock.Clock
}

func NewTokenIssuer(clk clock.Clock) *TokenIssuer {
    if clk == nil {
        clk = clock.NewSystemClock()
    }
    return &TokenIssuer{clock: clk}
}

func (i *TokenIssuer) IssueToken() string {
    now := i.clock.Now()
    // Use now for timestamps...
}
In Tests

Inject a FixtureClock for precise control:

func TestTokenExpiration(t *testing.T) {
    // Create fixture clock
    fixedTime := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC)
    clk := clock.NewFixtureClock(fixedTime)
    
    // Inject into components
    issuer := NewTokenIssuer(clk)
    validator := NewTokenValidator(clk)
    
    // Issue token valid for 1 hour
    token := issuer.IssueToken()
    
    // Token is valid now
    if err := validator.Validate(token); err != nil {
        t.Error("token should be valid")
    }
    
    // Advance time past expiration
    clk.Advance(2 * time.Hour)
    
    // Token should now be expired
    if err := validator.Validate(token); err == nil {
        t.Error("token should be expired")
    }
}

Benefits

  • Deterministic Tests: Tests don't depend on system time or wall clock
  • Fast Tests: No need to wait for time to pass
  • Precise Control: Test exact time boundaries and edge cases
  • Reproducible: Same behavior every test run
  • Edge Case Testing: Easy to test scenarios like:
    • Tokens expiring during validation
    • Time-based rate limiting
    • Scheduled task execution
    • TTL expiration

Integration

The clock package is integrated with:

  • JWKS Fixtures: Accept Clock for token timestamp generation
  • JWT Validators: Accept Clock for token validation
  • Future Components: Any time-dependent component should accept a Clock

Example: Testing Token Expiration

func TestJWTExpiration(t *testing.T) {
    // Create shared clock
    fixedTime := time.Date(2024, 6, 15, 10, 0, 0, 0, time.UTC)
    clk := clock.NewFixtureClock(fixedTime)
    
    // Create fixture with clock
    jwksFixture, _ := httpfixture.NewJWKSFixture(httpfixture.JWKSFixtureConfig{
        Issuer:  "https://auth.example.com",
        JWKSURL: "https://auth.example.com/.well-known/jwks.json",
        Clock:   clk,
    })
    
    // Create validator with same clock
    validator, _ := trust.NewJWTValidator(trust.JWTValidatorConfig{
        Issuer:      jwksFixture.Issuer(),
        JWKSURL:     jwksFixture.JWKSURL(),
        TrustDomain: "test",
        HTTPClient:  httpClientWithFixture(jwksFixture),
        Clock:       clk, // Same clock!
    })
    
    // Create token (expires 1 hour from fixedTime)
    token, _ := jwksFixture.CreateAndSignToken(map[string]interface{}{
        "sub": "user@example.com",
    })
    
    // Valid at fixedTime
    _, err := validator.Validate(ctx, createCredential(token))
    assert.NoError(t, err)
    
    // Still valid 30 minutes later
    clk.Advance(30 * time.Minute)
    _, err = validator.Validate(ctx, createCredential(token))
    assert.NoError(t, err)
    
    // Expired 1 hour 1 minute later
    clk.Advance(31 * time.Minute)
    _, err = validator.Validate(ctx, createCredential(token))
    assert.Error(t, err)
    assert.Equal(t, trust.ErrExpiredToken, err)
}

Design Principles

  1. Simple Interface: Just one method (Now()) makes it easy to implement and use
  2. Zero Overhead: Using SystemClock in production has negligible overhead
  3. Explicit Control: Time only changes when you explicitly manipulate the fixture clock
  4. No Hidden State: Time is frozen in tests unless you advance it
  5. Composability: Same clock can be shared across multiple components for consistent time

Testing the Clock

The clock package itself has comprehensive tests demonstrating all functionality:

  • clock_test.go - All clock implementations and manipulation methods
  • internal/httpfixture/jwks_fixture_test.go - Integration with JWKS fixtures
  • internal/trust/jwt_validator_test.go - Integration with JWT validators

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Clock

type Clock interface {
	// Now returns the current time
	Now() time.Time
	// Since returns the time elapsed since t
	Since(t time.Time) time.Duration
	// Sleep pauses execution for the given duration
	Sleep(d time.Duration)
	// Ticker creates a new ticker that ticks at the given interval
	Ticker(d time.Duration) Ticker
}

Clock abstracts time operations for testability This allows tests to control time without relying on the system clock

type FixtureClock

type FixtureClock struct {
	// contains filtered or unexported fields
}

FixtureClock is a controllable clock for testing It allows tests to set specific times and advance time programmatically

func NewFixtureClock

func NewFixtureClock(startTime time.Time) *FixtureClock

NewFixtureClock creates a fixture clock starting at the given time If zero time is provided, uses time.Now()

func (*FixtureClock) Advance

func (c *FixtureClock) Advance(d time.Duration)

Advance moves the fixture clock forward by the given duration and triggers any tickers that should fire

func (*FixtureClock) Now

func (c *FixtureClock) Now() time.Time

Now returns the current fixture time

func (*FixtureClock) Rewind

func (c *FixtureClock) Rewind(d time.Duration)

Rewind moves the fixture clock backward by the given duration

func (*FixtureClock) Set

func (c *FixtureClock) Set(t time.Time)

Set sets the fixture clock to a specific time

func (*FixtureClock) Since added in v0.5.0

func (c *FixtureClock) Since(t time.Time) time.Duration

Since returns the elapsed time since t relative to the fixture's current time

func (*FixtureClock) Sleep

func (c *FixtureClock) Sleep(d time.Duration)

Sleep advances the fixture clock by the given duration without actually sleeping This simulates the passage of time deterministically in tests

func (*FixtureClock) Ticker

func (c *FixtureClock) Ticker(d time.Duration) Ticker

Ticker creates a fixture ticker for testing

type SystemClock

type SystemClock struct{}

SystemClock uses the real system clock

func NewSystemClock

func NewSystemClock() *SystemClock

NewSystemClock creates a clock that uses the real system time

func (*SystemClock) Now

func (c *SystemClock) Now() time.Time

Now returns the current system time

func (*SystemClock) Since added in v0.5.0

func (c *SystemClock) Since(t time.Time) time.Duration

Since returns the time elapsed since t using time.Since

func (*SystemClock) Sleep

func (c *SystemClock) Sleep(d time.Duration)

Sleep pauses for the given duration using time.Sleep

func (*SystemClock) Ticker

func (c *SystemClock) Ticker(d time.Duration) Ticker

Ticker creates a real time.Ticker wrapped in our interface

type Ticker

type Ticker interface {
	// Start begins calling the function periodically
	// The function is called with a context that is cancelled when Stop is called
	Start(fn func(context.Context)) error
	// Stop stops the ticker and cancels any in-flight function calls
	Stop()
}

Ticker is an interface for a ticker that periodically calls a function

Jump to

Keyboard shortcuts

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