-
Notifications
You must be signed in to change notification settings - Fork 1
/
action.advancetime.go
138 lines (114 loc) · 3.38 KB
/
action.advancetime.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package testkit
import (
"context"
"fmt"
"time"
"github.com/dogmatiq/testkit/engine"
"github.com/dogmatiq/testkit/location"
)
// AdvanceTime returns an Action that simulates the passage of time by advancing
// the test's virtual clock.
//
// This allows testing of application logic that depends on time, such as
// processes that use timeout messages and projections that use the "recorded
// at" time of events.
//
// It accepts a TimeAdjustment which calculates the amount of time that the
// clock is advanced.
//
// There are two built-in adjustment types; ToTime() and ByDuration(). Users may
// provide their own TimeAdjustment implementations that model time-related
// concepts within the application's business domain.
func AdvanceTime(adj TimeAdjustment) Action {
if adj == nil {
panic("AdvanceTime(<nil>): adjustment must not be nil")
}
return advanceTimeAction{
adj,
location.OfCall(),
}
}
// A TimeAdjustment describes a change to the test's virtual clock.
type TimeAdjustment interface {
// Description returns a human-readable string that describes how the clock
// will be advanced.
//
// It should complete the sentence "The clock is being advanced...". For
// example, "by 10 seconds".
Description() string
// Step returns the time that the virtual clock should be set to as a result
// of the adjustment.
//
// t is the virtual clock's current time.
Step(t time.Time) time.Time
}
// ToTime returns a TimeAdjustment that advances the virtual clock to a specific
// time.
func ToTime(t time.Time) TimeAdjustment {
return toTime(t)
}
// ByDuration returns a TimeAdjustment that advances the virtual clock by a
// fixed duration.
func ByDuration(d time.Duration) TimeAdjustment {
if d < 0 {
panic(fmt.Sprintf("ByDuration(%s): duration must not be negative", d))
}
return byDuration(d)
}
// advanceTimeAction is an implementation of Action that advances the virtual
// clock.
type advanceTimeAction struct {
adj TimeAdjustment
loc location.Location
}
func (a advanceTimeAction) Caption() string {
return fmt.Sprintf(
"advancing time %s",
a.adj.Description(),
)
}
func (a advanceTimeAction) Location() location.Location {
return a.loc
}
func (a advanceTimeAction) ConfigurePredicate(*PredicateOptions) {
}
func (a advanceTimeAction) Do(ctx context.Context, s ActionScope) error {
now := a.adj.Step(*s.VirtualClock)
if now.Before(*s.VirtualClock) {
return fmt.Errorf(
"adjusting the clock %s would reverse time",
a.adj.Description(),
)
}
*s.VirtualClock = now
// There is already an engine.WithCurrentTime() based on the virtual clock
// in options slice. Because we have just updated the clock we need to
// override it for this one engine tick.
s.OperationOptions = append(
s.OperationOptions,
engine.WithCurrentTime(now),
)
return s.Engine.Tick(ctx, s.OperationOptions...)
}
// toTime is a ClockMutation that advances the clock to a specific time.
type toTime time.Time
func (t toTime) Description() string {
return fmt.Sprintf(
"to %s",
time.Time(t).Format(time.RFC3339),
)
}
func (t toTime) Step(time.Time) time.Time {
return time.Time(t)
}
// ByDuration is a ClockMutation that advances the clock by a fixed duration.
type byDuration time.Duration
func (d byDuration) Description() string {
return fmt.Sprintf(
"by %s",
time.Duration(d),
)
}
func (d byDuration) Step(before time.Time) time.Time {
return before.Add(time.Duration(d))
}