Skip to content

Commit 23ba7a2

Browse files
authored
Workflow unit testing changes for 1.10 release (#1038)
* Update DurableTask SDK dependency to get ARM64 compatibility (#1024) * Update DurableTask SDK dependency to get ARM64 compatibility Signed-off-by: Chris Gillum <[email protected]> * Fix issue with gRPC address override behavior Signed-off-by: Chris Gillum <[email protected]> * Workflow SDK changes to enable unit testing Signed-off-by: Chris Gillum <[email protected]> --------- Signed-off-by: Chris Gillum <[email protected]>
1 parent 0f1e1bf commit 23ba7a2

File tree

3 files changed

+120
-57
lines changed

3 files changed

+120
-57
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// ------------------------------------------------------------------------
2+
// Copyright 2023 The Dapr Authors
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
// Unless required by applicable law or agreed to in writing, software
8+
// distributed under the License is distributed on an "AS IS" BASIS,
9+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
// See the License for the specific language governing permissions and
11+
// limitations under the License.
12+
// ------------------------------------------------------------------------
13+
14+
namespace Dapr.Workflow
15+
{
16+
using System;
17+
using Microsoft.DurableTask;
18+
using System.Threading.Tasks;
19+
using System.Threading;
20+
21+
class DaprWorkflowContext : WorkflowContext
22+
{
23+
readonly TaskOrchestrationContext innerContext;
24+
25+
internal DaprWorkflowContext(TaskOrchestrationContext innerContext)
26+
{
27+
this.innerContext = innerContext ?? throw new ArgumentNullException(nameof(innerContext));
28+
}
29+
30+
public override string Name => this.innerContext.Name;
31+
32+
public override string InstanceId => this.innerContext.InstanceId;
33+
34+
public override DateTime CurrentUtcDateTime => this.innerContext.CurrentUtcDateTime;
35+
36+
public override bool IsReplaying => this.innerContext.IsReplaying;
37+
38+
public override Task CallActivityAsync(string name, object? input = null, TaskOptions? options = null)
39+
{
40+
return this.innerContext.CallActivityAsync(name, input, options);
41+
}
42+
43+
public override Task<T> CallActivityAsync<T>(string name, object? input = null, TaskOptions? options = null)
44+
{
45+
return this.innerContext.CallActivityAsync<T>(name, input, options);
46+
}
47+
48+
public override Task CreateTimer(TimeSpan delay, CancellationToken cancellationToken = default)
49+
{
50+
return this.innerContext.CreateTimer(delay, cancellationToken);
51+
}
52+
53+
public override Task CreateTimer(DateTime fireAt, CancellationToken cancellationToken)
54+
{
55+
return this.innerContext.CreateTimer(fireAt, cancellationToken);
56+
}
57+
58+
public override Task<T> WaitForExternalEventAsync<T>(string eventName, CancellationToken cancellationToken = default)
59+
{
60+
return this.innerContext.WaitForExternalEvent<T>(eventName, cancellationToken);
61+
}
62+
63+
public override Task<T> WaitForExternalEventAsync<T>(string eventName, TimeSpan timeout)
64+
{
65+
return this.innerContext.WaitForExternalEvent<T>(eventName, timeout);
66+
}
67+
68+
public override void SendEvent(string instanceId, string eventName, object payload)
69+
{
70+
this.innerContext.SendEvent(instanceId, eventName, payload);
71+
}
72+
73+
public override void SetCustomStatus(object? customStatus)
74+
{
75+
this.innerContext.SetCustomStatus(customStatus);
76+
}
77+
78+
public override Task<TResult> CallChildWorkflowAsync<TResult>(string workflowName, object? input = null, TaskOptions? options = null)
79+
{
80+
return this.innerContext.CallSubOrchestratorAsync<TResult>(workflowName, input, options);
81+
}
82+
83+
public override Task CallChildWorkflowAsync(string workflowName, object? input = null, TaskOptions? options = null)
84+
{
85+
return this.innerContext.CallSubOrchestratorAsync(workflowName, input, options);
86+
}
87+
88+
public override void ContinueAsNew(object? newInput = null, bool preserveUnprocessedEvents = true)
89+
{
90+
this.innerContext.ContinueAsNew(newInput!, preserveUnprocessedEvents);
91+
}
92+
93+
public override Guid NewGuid()
94+
{
95+
return this.innerContext.NewGuid();
96+
}
97+
}
98+
}

src/Dapr.Workflow/WorkflowContext.cs

Lines changed: 20 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,17 @@ namespace Dapr.Workflow
2222
/// Context object used by workflow implementations to perform actions such as scheduling activities, durable timers, waiting for
2323
/// external events, and for getting basic information about the current workflow instance.
2424
/// </summary>
25-
public class WorkflowContext
25+
public abstract class WorkflowContext
2626
{
27-
readonly TaskOrchestrationContext innerContext;
28-
29-
internal WorkflowContext(TaskOrchestrationContext innerContext)
30-
{
31-
this.innerContext = innerContext ?? throw new ArgumentNullException(nameof(innerContext));
32-
}
33-
3427
/// <summary>
3528
/// Gets the name of the current workflow.
3629
/// </summary>
37-
public string Name => this.innerContext.Name;
30+
public abstract string Name { get; }
3831

3932
/// <summary>
4033
/// Gets the instance ID of the current workflow.
4134
/// </summary>
42-
public string InstanceId => this.innerContext.InstanceId;
35+
public abstract string InstanceId { get; }
4336

4437
/// <summary>
4538
/// Gets the current workflow time in UTC.
@@ -51,7 +44,7 @@ internal WorkflowContext(TaskOrchestrationContext innerContext)
5144
/// the current time, such as <see cref="DateTime.UtcNow"/> and <see cref="DateTimeOffset.UtcNow"/>
5245
/// (which should not be used).
5346
/// </remarks>
54-
public DateTime CurrentUtcDateTime => this.innerContext.CurrentUtcDateTime;
47+
public abstract DateTime CurrentUtcDateTime { get; }
5548

5649
/// <summary>
5750
/// Gets a value indicating whether the workflow is currently replaying a previous execution.
@@ -72,7 +65,7 @@ internal WorkflowContext(TaskOrchestrationContext innerContext)
7265
/// <value>
7366
/// <c>true</c> if the workflow is currently replaying a previous execution; otherwise <c>false</c>.
7467
/// </value>
75-
public bool IsReplaying => this.innerContext.IsReplaying;
68+
public abstract bool IsReplaying { get; }
7669

7770
/// <summary>
7871
/// Asynchronously invokes an activity by name and with the specified input value.
@@ -108,19 +101,16 @@ internal WorkflowContext(TaskOrchestrationContext innerContext)
108101
/// The activity failed with an unhandled exception. The details of the failure can be found in the
109102
/// <see cref="TaskFailedException.FailureDetails"/> property.
110103
/// </exception>
111-
public Task CallActivityAsync(string name, object? input = null, TaskOptions? options = null)
104+
public virtual Task CallActivityAsync(string name, object? input = null, TaskOptions? options = null)
112105
{
113-
return this.innerContext.CallActivityAsync(name, input, options);
106+
return this.CallActivityAsync<object>(name, input, options);
114107
}
115108

116109
/// <returns>
117110
/// A task that completes when the activity completes or fails. The result of the task is the activity's return value.
118111
/// </returns>
119112
/// <inheritdoc cref="CallActivityAsync"/>
120-
public Task<T> CallActivityAsync<T>(string name, object? input = null, TaskOptions? options = null)
121-
{
122-
return this.innerContext.CallActivityAsync<T>(name, input, options);
123-
}
113+
public abstract Task<T> CallActivityAsync<T>(string name, object? input = null, TaskOptions? options = null);
124114

125115
/// <summary>
126116
/// Creates a durable timer that expires after the specified delay.
@@ -131,9 +121,9 @@ public Task<T> CallActivityAsync<T>(string name, object? input = null, TaskOptio
131121
/// <exception cref="InvalidOperationException">
132122
/// Thrown if the calling thread is not the workflow dispatch thread.
133123
/// </exception>
134-
public Task CreateTimer(TimeSpan delay, CancellationToken cancellationToken = default)
124+
public virtual Task CreateTimer(TimeSpan delay, CancellationToken cancellationToken = default)
135125
{
136-
return this.innerContext.CreateTimer(delay, cancellationToken);
126+
return this.CreateTimer(this.CurrentUtcDateTime.Add(delay), cancellationToken);
137127
}
138128

139129
/// <summary>
@@ -142,10 +132,7 @@ public Task CreateTimer(TimeSpan delay, CancellationToken cancellationToken = de
142132
/// <param name="fireAt">The time at which the timer should expire.</param>
143133
/// <param name="cancellationToken">Used to cancel the durable timer.</param>
144134
/// <inheritdoc cref="CreateTimer(TimeSpan, CancellationToken)"/>
145-
public Task CreateTimer(DateTime fireAt, CancellationToken cancellationToken)
146-
{
147-
return this.innerContext.CreateTimer(fireAt, cancellationToken);
148-
}
135+
public abstract Task CreateTimer(DateTime fireAt, CancellationToken cancellationToken);
149136

150137
/// <summary>
151138
/// Waits for an event to be raised with name <paramref name="eventName"/> and returns the event data.
@@ -176,10 +163,7 @@ public Task CreateTimer(DateTime fireAt, CancellationToken cancellationToken)
176163
/// <exception cref="InvalidOperationException">
177164
/// Thrown if the calling thread is not the workflow dispatch thread.
178165
/// </exception>
179-
public Task<T> WaitForExternalEventAsync<T>(string eventName, CancellationToken cancellationToken = default)
180-
{
181-
return this.innerContext.WaitForExternalEvent<T>(eventName, cancellationToken);
182-
}
166+
public abstract Task<T> WaitForExternalEventAsync<T>(string eventName, CancellationToken cancellationToken = default);
183167

184168
/// <summary>
185169
/// Waits for an event to be raised with name <paramref name="eventName"/> and returns the event data.
@@ -190,10 +174,7 @@ public Task<T> WaitForExternalEventAsync<T>(string eventName, CancellationToken
190174
/// </param>
191175
/// <param name="timeout">The amount of time to wait before cancelling the external event task.</param>
192176
/// <inheritdoc cref="WaitForExternalEventAsync{T}(string, CancellationToken)"/>
193-
public Task<T> WaitForExternalEventAsync<T>(string eventName, TimeSpan timeout)
194-
{
195-
return this.innerContext.WaitForExternalEvent<T>(eventName, timeout);
196-
}
177+
public abstract Task<T> WaitForExternalEventAsync<T>(string eventName, TimeSpan timeout);
197178

198179
/// <summary>
199180
/// Raises an external event for the specified workflow instance.
@@ -208,10 +189,7 @@ public Task<T> WaitForExternalEventAsync<T>(string eventName, TimeSpan timeout)
208189
/// <param name="instanceId">The ID of the workflow instance to send the event to.</param>
209190
/// <param name="eventName">The name of the event to wait for. Event names are case-insensitive.</param>
210191
/// <param name="payload">The serializable payload of the external event.</param>
211-
public void SendEvent(string instanceId, string eventName, object payload)
212-
{
213-
this.SendEvent(instanceId, eventName, payload);
214-
}
192+
public abstract void SendEvent(string instanceId, string eventName, object payload);
215193

216194
/// <summary>
217195
/// Assigns a custom status value to the current workflow.
@@ -226,10 +204,7 @@ public void SendEvent(string instanceId, string eventName, object payload)
226204
/// <exception cref="InvalidOperationException">
227205
/// Thrown if the calling thread is not the workflow dispatch thread.
228206
/// </exception>
229-
public void SetCustomStatus(object? customStatus)
230-
{
231-
this.innerContext.SetCustomStatus(customStatus);
232-
}
207+
public abstract void SetCustomStatus(object? customStatus);
233208

234209
/// <summary>
235210
/// Executes the specified workflow as a child workflow and returns the result.
@@ -238,10 +213,7 @@ public void SetCustomStatus(object? customStatus)
238213
/// The type into which to deserialize the child workflow's output.
239214
/// </typeparam>
240215
/// <inheritdoc cref="CallChildWorkflowAsync(string, object?, TaskOptions?)"/>
241-
public Task<TResult> CallChildWorkflowAsync<TResult>(string workflowName, object? input = null, TaskOptions? options = null)
242-
{
243-
return this.innerContext.CallSubOrchestratorAsync<TResult>(workflowName, input, options);
244-
}
216+
public abstract Task<TResult> CallChildWorkflowAsync<TResult>(string workflowName, object? input = null, TaskOptions? options = null);
245217

246218
/// <summary>
247219
/// Executes the specified workflow as a child workflow.
@@ -284,9 +256,9 @@ public Task<TResult> CallChildWorkflowAsync<TResult>(string workflowName, object
284256
/// The child workflow failed with an unhandled exception. The details of the failure can be found in the
285257
/// <see cref="TaskFailedException.FailureDetails"/> property.
286258
/// </exception>
287-
public Task CallChildWorkflowAsync(string workflowName, object? input = null, TaskOptions? options = null)
259+
public virtual Task CallChildWorkflowAsync(string workflowName, object? input = null, TaskOptions? options = null)
288260
{
289-
return this.innerContext.CallSubOrchestratorAsync(workflowName, input, options);
261+
return this.CallChildWorkflowAsync<object>(workflowName, input, options);
290262
}
291263

292264
/// <summary>
@@ -320,10 +292,7 @@ public Task CallChildWorkflowAsync(string workflowName, object? input = null, Ta
320292
/// history when the workflow instance restarts. If <c>false</c>, any unprocessed
321293
/// external events will be discarded when the workflow instance restarts.
322294
/// </param>
323-
public void ContinueAsNew(object? newInput = null, bool preserveUnprocessedEvents = true)
324-
{
325-
this.innerContext.ContinueAsNew(newInput!, preserveUnprocessedEvents);
326-
}
295+
public abstract void ContinueAsNew(object? newInput = null, bool preserveUnprocessedEvents = true);
327296

328297
/// <summary>
329298
/// Creates a new GUID that is safe for replay within a workflow.
@@ -334,9 +303,6 @@ public void ContinueAsNew(object? newInput = null, bool preserveUnprocessedEvent
334303
/// and an internally managed sequence number.
335304
/// </remarks>
336305
/// <returns>The new <see cref="Guid"/> value.</returns>
337-
public Guid NewGuid()
338-
{
339-
return this.innerContext.NewGuid();
340-
}
306+
public abstract Guid NewGuid();
341307
}
342308
}

src/Dapr.Workflow/WorkflowRuntimeOptions.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ namespace Dapr.Workflow
1818
using System.Threading.Tasks;
1919
using Microsoft.DurableTask;
2020
using Microsoft.Extensions.DependencyInjection;
21-
using Microsoft.Extensions.Logging;
2221

2322
/// <summary>
2423
/// Defines runtime options for workflows.
@@ -52,7 +51,7 @@ public void RegisterWorkflow<TInput, TOutput>(string name, Func<WorkflowContext,
5251
{
5352
registry.AddOrchestratorFunc<TInput, TOutput>(name, (innerContext, input) =>
5453
{
55-
WorkflowContext workflowContext = new(innerContext);
54+
WorkflowContext workflowContext = new DaprWorkflowContext(innerContext);
5655
return implementation(workflowContext, input);
5756
});
5857
});
@@ -145,7 +144,7 @@ public OrchestratorWrapper(IWorkflow workflow)
145144

146145
public Task<object?> RunAsync(TaskOrchestrationContext context, object? input)
147146
{
148-
return this.workflow.RunAsync(new WorkflowContext(context), input);
147+
return this.workflow.RunAsync(new DaprWorkflowContext(context), input);
149148
}
150149
}
151150

0 commit comments

Comments
 (0)