Files
tasks/common/clock/time_skipping_time_source_test.go
feiyangandGitHub 771c5cc0a8 Add the virtual time management of mutable state and timer task (#9985)
## What changed?
mutable state time
1) added time-skipping time-source wrapper
2) wrapped the time source of the mutable state/hbuilder with
time-skipping features
3) exposed now() from a mutable state interface
timer task time
4) added converter from virtual time to real time in task generators
5) added virtual time reference in timer task time validation 

## Why?

Principles of correct virtual time management 

 - principle-1: all time in mutable state are consistent virtual
 - principle-2: wall clock time is used timer queues
 - principle-3: timer-task (re)generation
- when mutable state skips time, non-stale timer tasks should be
regenerated
  (workflow execution timeout, pending user timer)
- when mutable state skips time, all future timer tasks should reflec
the change
 - principle-4: timer-task fire time validation
- when timer queues validate the fire time, the time of the mutable
state should be used as the reference time


## How did you test it?
- [x] built
- [x] run locally and tested manually
- [x] covered by existing tests
- [x] added new unit test(s)
- [x] added new functional test(s)
2026-04-22 20:41:08 -07:00

93 lines
2.3 KiB
Go

package clock_test
import (
"testing"
"time"
"github.com/stretchr/testify/require"
"go.temporal.io/server/common/clock"
)
func TestTimeSkippingTimeSource_Now_NilGetter(t *testing.T) {
t.Parallel()
base := clock.NewEventTimeSource()
base.Update(time.Unix(100, 0))
ts := clock.WrapTimeSourceWithTimeSkipping(base, nil)
require.Equal(t, time.Unix(100, 0), ts.Now())
}
func TestTimeSkippingTimeSource_Now_WithZeroOffset(t *testing.T) {
t.Parallel()
base := clock.NewEventTimeSource()
base.Update(time.Unix(100, 0))
ts := clock.WrapTimeSourceWithTimeSkipping(base, func() time.Duration { return 0 })
require.Equal(t, time.Unix(100, 0), ts.Now())
}
func TestTimeSkippingTimeSource_Now_ReadsOffsetLazily(t *testing.T) {
t.Parallel()
base := clock.NewEventTimeSource()
base.Update(time.Unix(100, 0))
offset := 10 * time.Second
ts := clock.WrapTimeSourceWithTimeSkipping(base, func() time.Duration { return offset })
require.Equal(t, time.Unix(110, 0), ts.Now())
offset = 120 * time.Second
require.Equal(t, time.Unix(220, 0), ts.Now())
}
func TestTimeSkippingTimeSource_Since_IncludesOffset(t *testing.T) {
t.Parallel()
base := clock.NewEventTimeSource()
base.Update(time.Unix(100, 0))
ts := clock.WrapTimeSourceWithTimeSkipping(base, func() time.Duration { return 50 * time.Second })
// Since delegates to Now(), which includes the offset: (100+50) - 90 = 60s.
require.Equal(t, 60*time.Second, ts.Since(time.Unix(90, 0)))
}
func TestTimeSkippingTimeSource_AfterFunc_DelegatesToBase(t *testing.T) {
t.Parallel()
base := clock.NewEventTimeSource()
ts := clock.WrapTimeSourceWithTimeSkipping(base, func() time.Duration { return 50 * time.Second })
fired := false
ts.AfterFunc(time.Second, func() { fired = true })
require.False(t, fired)
base.Advance(time.Second)
require.True(t, fired)
}
func TestTimeSkippingTimeSource_NewTimer_DelegatesToBase(t *testing.T) {
t.Parallel()
base := clock.NewEventTimeSource()
ts := clock.WrapTimeSourceWithTimeSkipping(base, func() time.Duration { return 50 * time.Second })
ch, _ := ts.NewTimer(time.Second)
select {
case <-ch:
t.Fatal("timer should not fire before deadline")
default:
}
base.Advance(time.Second)
select {
case <-ch:
// fired as expected
default:
t.Fatal("timer should have fired after deadline")
}
}