github.com/Laisky/zap@v1.27.0/internal/ztest/clock.go (about)

     1  // Copyright (c) 2023 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package ztest
    22  
    23  import (
    24  	"sort"
    25  	"sync"
    26  	"time"
    27  )
    28  
    29  // MockClock is a fake source of time.
    30  // It implements standard time operations,
    31  // but allows the user to control the passage of time.
    32  //
    33  // Use the [Add] method to progress time.
    34  type MockClock struct {
    35  	mu  sync.RWMutex
    36  	now time.Time
    37  
    38  	// The MockClock works by maintaining a list of waiters.
    39  	// Each waiter knows the time at which it should be resolved.
    40  	// When the clock advances, all waiters that are in range are resolved
    41  	// in chronological order.
    42  	waiters []waiter
    43  }
    44  
    45  // NewMockClock builds a new mock clock
    46  // using the current actual time as the initial time.
    47  func NewMockClock() *MockClock {
    48  	return &MockClock{
    49  		now: time.Now(),
    50  	}
    51  }
    52  
    53  // Now reports the current time.
    54  func (c *MockClock) Now() time.Time {
    55  	c.mu.RLock()
    56  	defer c.mu.RUnlock()
    57  	return c.now
    58  }
    59  
    60  // NewTicker returns a time.Ticker that ticks at the specified frequency.
    61  //
    62  // As with [time.NewTicker],
    63  // the ticker will drop ticks if the receiver is slow,
    64  // and the channel is never closed.
    65  //
    66  // Calling Stop on the returned ticker is a no-op.
    67  // The ticker only runs when the clock is advanced.
    68  func (c *MockClock) NewTicker(d time.Duration) *time.Ticker {
    69  	ch := make(chan time.Time, 1)
    70  
    71  	var tick func(time.Time)
    72  	tick = func(now time.Time) {
    73  		next := now.Add(d)
    74  		c.runAt(next, func() {
    75  			defer tick(next)
    76  
    77  			select {
    78  			case ch <- next:
    79  				// ok
    80  			default:
    81  				// The receiver is slow.
    82  				// Drop the tick and continue.
    83  			}
    84  		})
    85  	}
    86  	tick(c.Now())
    87  
    88  	return &time.Ticker{C: ch}
    89  }
    90  
    91  // runAt schedules the given function to be run at the given time.
    92  // The function runs without a lock held, so it may schedule more work.
    93  func (c *MockClock) runAt(t time.Time, fn func()) {
    94  	c.mu.Lock()
    95  	defer c.mu.Unlock()
    96  	c.waiters = append(c.waiters, waiter{until: t, fn: fn})
    97  }
    98  
    99  type waiter struct {
   100  	until time.Time
   101  	fn    func()
   102  }
   103  
   104  // Add progresses time by the given duration.
   105  // Other operations waiting for the time to advance
   106  // will be resolved if they are within range.
   107  //
   108  // Side effects of operations waiting for the time to advance
   109  // will take effect on a best-effort basis.
   110  // Avoid racing with operations that have side effects.
   111  //
   112  // Panics if the duration is negative.
   113  func (c *MockClock) Add(d time.Duration) {
   114  	if d < 0 {
   115  		panic("cannot add negative duration")
   116  	}
   117  
   118  	c.mu.Lock()
   119  	defer c.mu.Unlock()
   120  
   121  	sort.Slice(c.waiters, func(i, j int) bool {
   122  		return c.waiters[i].until.Before(c.waiters[j].until)
   123  	})
   124  
   125  	newTime := c.now.Add(d)
   126  	// newTime won't be recorded until the end of this method.
   127  	// This ensures that any waiters that are resolved
   128  	// are resolved at the time they were expecting.
   129  
   130  	for len(c.waiters) > 0 {
   131  		w := c.waiters[0]
   132  		if w.until.After(newTime) {
   133  			break
   134  		}
   135  		c.waiters[0] = waiter{} // avoid memory leak
   136  		c.waiters = c.waiters[1:]
   137  
   138  		// The waiter is within range.
   139  		// Travel to the time of the waiter and resolve it.
   140  		c.now = w.until
   141  
   142  		// The waiter may schedule more work
   143  		// so we must release the lock.
   144  		c.mu.Unlock()
   145  		w.fn()
   146  		// Sleeping here is necessary to let the side effects of waiters
   147  		// take effect before we continue.
   148  		time.Sleep(1 * time.Millisecond)
   149  		c.mu.Lock()
   150  	}
   151  
   152  	c.now = newTime
   153  }