Skip to content

Commit 4093d8f

Browse files
authored
Merge pull request #154 from cschleiden/cschleiden/tester-cancel-timers
Cancel timers during unit testing
2 parents e1cd688 + 0422572 commit 4093d8f

File tree

3 files changed

+156
-85
lines changed

3 files changed

+156
-85
lines changed

tester/tester.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ type WorkflowTester[TResult any] interface {
6969
}
7070

7171
type testTimer struct {
72+
// Instance is the workflow instance this timer is for
73+
Instance *core.WorkflowInstance
74+
75+
// ScheduleEventID is the ID of the schedule event for this timer
76+
ScheduleEventID int64
77+
7278
// At is the time this timer is scheduled for. This will advance the mock clock
7379
// to this timestamp
7480
At time.Time
@@ -283,6 +289,9 @@ func (wt *workflowTester[TResult]) Execute(args ...interface{}) {
283289
wt.workflowResult = a.Result
284290
wt.workflowErr = a.Error
285291
}
292+
293+
case history.EventType_TimerCanceled:
294+
wt.cancelTimer(tw.instance, event)
286295
}
287296
}
288297

@@ -521,7 +530,9 @@ func (wt *workflowTester[TResult]) scheduleTimer(instance *core.WorkflowInstance
521530
e := event.Attributes.(*history.TimerFiredAttributes)
522531

523532
wt.timers = append(wt.timers, &testTimer{
524-
At: e.At,
533+
Instance: instance,
534+
ScheduleEventID: event.ScheduleEventID,
535+
At: e.At,
525536
Callback: func() {
526537
wt.callbacks <- func() *history.WorkflowEvent {
527538
return &history.WorkflowEvent{
@@ -533,6 +544,15 @@ func (wt *workflowTester[TResult]) scheduleTimer(instance *core.WorkflowInstance
533544
})
534545
}
535546

547+
func (wt *workflowTester[TResult]) cancelTimer(instance *core.WorkflowInstance, event history.Event) {
548+
for i, t := range wt.timers {
549+
if t.Instance.InstanceID == instance.InstanceID && t.ScheduleEventID == event.ScheduleEventID {
550+
wt.timers = append(wt.timers[:i], wt.timers[i+1:]...)
551+
break
552+
}
553+
}
554+
}
555+
536556
func (wt *workflowTester[TResult]) getWorkflow(instance *core.WorkflowInstance) *testWorkflow {
537557
wt.mtw.RLock()
538558
defer wt.mtw.RUnlock()

tester/tester_test.go

Lines changed: 0 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -148,90 +148,6 @@ func activityLongRunning(ctx context.Context) (int, error) {
148148
return 42, nil
149149
}
150150

151-
func Test_Timer(t *testing.T) {
152-
tester := NewWorkflowTester[timerResult](workflowTimer)
153-
start := tester.Now()
154-
155-
tester.Execute()
156-
157-
require.True(t, tester.WorkflowFinished())
158-
wr, _ := tester.WorkflowResult()
159-
require.True(t, start.Equal(wr.T1))
160-
161-
e := start.Add(30 * time.Second)
162-
require.True(t, e.Equal(wr.T2), "expected %v, got %v", e, wr.T2)
163-
}
164-
165-
type timerResult struct {
166-
T1 time.Time
167-
T2 time.Time
168-
}
169-
170-
func workflowTimer(ctx workflow.Context) (timerResult, error) {
171-
t1 := workflow.Now(ctx)
172-
173-
workflow.ScheduleTimer(ctx, 30*time.Second).Get(ctx)
174-
175-
t2 := workflow.Now(ctx)
176-
177-
workflow.ScheduleTimer(ctx, 30*time.Second).Get(ctx)
178-
179-
return timerResult{
180-
T1: t1,
181-
T2: t2,
182-
}, nil
183-
}
184-
185-
func Test_TimerCancellation(t *testing.T) {
186-
tester := NewWorkflowTester[time.Time](workflowTimerCancellation)
187-
start := tester.Now()
188-
189-
tester.Execute()
190-
191-
require.True(t, tester.WorkflowFinished())
192-
193-
wfR, _ := tester.WorkflowResult()
194-
require.True(t, start.Equal(wfR), "expected %v, got %v", start, wfR)
195-
}
196-
197-
func workflowTimerCancellation(ctx workflow.Context) (time.Time, error) {
198-
tctx, cancel := workflow.WithCancel(ctx)
199-
t := workflow.ScheduleTimer(tctx, 30*time.Second)
200-
cancel()
201-
202-
_, _ = t.Get(ctx)
203-
204-
return workflow.Now(ctx), nil
205-
}
206-
207-
func Test_TimerRespondingWithoutNewEvents(t *testing.T) {
208-
tester := NewWorkflowTester[time.Time](workflowTimerRespondingWithoutNewEvents)
209-
210-
tester.ScheduleCallback(time.Duration(2*time.Second), func() {
211-
tester.SignalWorkflow("signal", "s42")
212-
})
213-
214-
tester.Execute()
215-
216-
require.True(t, tester.WorkflowFinished())
217-
218-
_, err := tester.WorkflowResult()
219-
require.Empty(t, err)
220-
}
221-
222-
func workflowTimerRespondingWithoutNewEvents(ctx workflow.Context) error {
223-
workflow.ScheduleTimer(ctx, 1*time.Second).Get(ctx)
224-
225-
workflow.Select(
226-
ctx,
227-
workflow.Receive(workflow.NewSignalChannel[any](ctx, "signal"), func(ctx workflow.Context, signal any, ok bool) {
228-
// do nothing
229-
}),
230-
)
231-
232-
return nil
233-
}
234-
235151
func Test_Signals(t *testing.T) {
236152
tester := NewWorkflowTester[string](workflowSignal)
237153
tester.ScheduleCallback(time.Duration(5*time.Second), func() {

tester/tester_timers_test.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package tester
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/cschleiden/go-workflows/workflow"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func Test_Timer(t *testing.T) {
12+
tester := NewWorkflowTester[timerResult](workflowTimer)
13+
start := tester.Now()
14+
15+
tester.Execute()
16+
17+
require.True(t, tester.WorkflowFinished())
18+
wr, _ := tester.WorkflowResult()
19+
require.True(t, start.Equal(wr.T1))
20+
21+
e := start.Add(30 * time.Second)
22+
require.True(t, e.Equal(wr.T2), "expected %v, got %v", e, wr.T2)
23+
}
24+
25+
type timerResult struct {
26+
T1 time.Time
27+
T2 time.Time
28+
}
29+
30+
func workflowTimer(ctx workflow.Context) (timerResult, error) {
31+
t1 := workflow.Now(ctx)
32+
33+
workflow.ScheduleTimer(ctx, 30*time.Second).Get(ctx)
34+
35+
t2 := workflow.Now(ctx)
36+
37+
workflow.ScheduleTimer(ctx, 30*time.Second).Get(ctx)
38+
39+
return timerResult{
40+
T1: t1,
41+
T2: t2,
42+
}, nil
43+
}
44+
45+
func Test_TimerCancellation(t *testing.T) {
46+
tester := NewWorkflowTester[time.Time](workflowTimerCancellation)
47+
start := tester.Now()
48+
49+
tester.Execute()
50+
51+
require.True(t, tester.WorkflowFinished())
52+
53+
wfR, _ := tester.WorkflowResult()
54+
require.True(t, start.Equal(wfR), "expected %v, got %v", start, wfR)
55+
}
56+
57+
func workflowTimerCancellation(ctx workflow.Context) (time.Time, error) {
58+
tctx, cancel := workflow.WithCancel(ctx)
59+
t := workflow.ScheduleTimer(tctx, 30*time.Second)
60+
cancel()
61+
62+
_, _ = t.Get(ctx)
63+
64+
return workflow.Now(ctx), nil
65+
}
66+
67+
func Test_TimerSubworkflowCancellation(t *testing.T) {
68+
tester := NewWorkflowTester[time.Time](workflowSubWorkflowTimerCancellation)
69+
tester.Registry().RegisterWorkflow(timerCancellationSubWorkflow)
70+
71+
tester.Execute()
72+
73+
require.True(t, tester.WorkflowFinished())
74+
75+
_, wfErr := tester.WorkflowResult()
76+
require.Empty(t, wfErr)
77+
}
78+
79+
func workflowSubWorkflowTimerCancellation(ctx workflow.Context) error {
80+
_, err := workflow.CreateSubWorkflowInstance[any](ctx, workflow.DefaultSubWorkflowOptions, timerCancellationSubWorkflow).Get(ctx)
81+
82+
// Wait long enough for the second timer in timerCancellationSubWorkflow to fire
83+
workflow.Sleep(ctx, 20*time.Second)
84+
85+
return err
86+
}
87+
88+
func timerCancellationSubWorkflow(ctx workflow.Context) error {
89+
tctx, cancel := workflow.WithCancel(ctx)
90+
91+
t := workflow.ScheduleTimer(ctx, 2*time.Second)
92+
t2 := workflow.ScheduleTimer(tctx, 10*time.Second)
93+
94+
workflow.Select(
95+
ctx,
96+
workflow.Await(t, func(ctx workflow.Context, f workflow.Future[struct{}]) {
97+
// Cancel t2
98+
cancel()
99+
}),
100+
workflow.Await(t2, func(ctx workflow.Context, f workflow.Future[struct{}]) {
101+
// do nothing here, should never fire
102+
panic("timer should have been cancelled")
103+
}),
104+
)
105+
106+
return nil
107+
}
108+
109+
func Test_TimerRespondingWithoutNewEvents(t *testing.T) {
110+
tester := NewWorkflowTester[time.Time](workflowTimerRespondingWithoutNewEvents)
111+
112+
tester.ScheduleCallback(time.Duration(2*time.Second), func() {
113+
tester.SignalWorkflow("signal", "s42")
114+
})
115+
116+
tester.Execute()
117+
118+
require.True(t, tester.WorkflowFinished())
119+
120+
_, err := tester.WorkflowResult()
121+
require.Empty(t, err)
122+
}
123+
124+
func workflowTimerRespondingWithoutNewEvents(ctx workflow.Context) error {
125+
workflow.ScheduleTimer(ctx, 1*time.Second).Get(ctx)
126+
127+
workflow.Select(
128+
ctx,
129+
workflow.Receive(workflow.NewSignalChannel[any](ctx, "signal"), func(ctx workflow.Context, signal any, ok bool) {
130+
// do nothing
131+
}),
132+
)
133+
134+
return nil
135+
}

0 commit comments

Comments
 (0)