diff --git a/samples/Eternal.fs b/samples/Eternal.fs index 070cf7c..8594056 100644 --- a/samples/Eternal.fs +++ b/samples/Eternal.fs @@ -4,18 +4,17 @@ open System open Microsoft.Azure.WebJobs open DurableFunctions.FSharp -let printTime = - fun (d: DateTime) -> sprintf "Printing at %s!" (d.ToShortTimeString()) - |> Activity.define "PrintTime" +let printTime = + Activity.define "PrintTime" (fun (d: DateTime) -> sprintf "Printing at %s!" (d.ToShortTimeString())) let workflow = orchestrator { - let! s = Activity.call printTime DateTime.Now + let! (s:string) = Activity.call printTime DateTime.Now do! Orchestrator.delay (TimeSpan.FromSeconds 5.0) return if s.Contains "00" then Stop else ContinueAsNew () } let workflowWithParam delay = orchestrator { - let! s = Activity.call printTime DateTime.Now + let! (s:string) = Activity.call printTime DateTime.Now do! Orchestrator.delay (TimeSpan.FromSeconds delay) return if s.Contains "00" then Stop else ContinueAsNew (delay + 1.) } diff --git a/samples/Hello.fs b/samples/Hello.fs index 2bc471a..8aa846f 100644 --- a/samples/Hello.fs +++ b/samples/Hello.fs @@ -7,14 +7,16 @@ open DurableFunctions.FSharp let SayHello([] name) = sprintf "Hello %s!" name -[] -let Run ([] context: DurableOrchestrationContext) = - context |> - orchestrator { +let workflow = orchestrator { let! hello1 = Activity.callByName "SayHello" "Tokyo" let! hello2 = Activity.callByName "SayHello" "Seattle" let! hello3 = Activity.callByName "SayHello" "London" // returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"] return [hello1; hello2; hello3] - } \ No newline at end of file + } + +[] +let Run ([] context: DurableOrchestrationContext) = + Orchestrator.run (workflow, context) + \ No newline at end of file diff --git a/samples/samples.fsproj b/samples/samples.fsproj index 6f0777d..04c6894 100644 --- a/samples/samples.fsproj +++ b/samples/samples.fsproj @@ -16,12 +16,15 @@ + + + diff --git a/samples/testing.fs b/samples/testing.fs new file mode 100644 index 0000000..5393227 --- /dev/null +++ b/samples/testing.fs @@ -0,0 +1,45 @@ +module samples.unittest + +open Microsoft.Azure.WebJobs +open DurableFunctions.FSharp +open System + +[] +let SayHello([] name) = + sprintf "Hello %s!" name + +[] +let RunWorkflow ([] context: DurableOrchestrationContextBase) = + context |> + orchestrator { + let! hello1 = Activity.callByName "SayHello" "Tokyo" + let! hello2 = Activity.callByName "SayHello" "Seattle" + let! hello3 = Activity.callByName "SayHello" "London" + + // returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"] + return [hello1; hello2; hello3] + } + +module WorkflowTests = + + open Moq + open Expecto + + + [] + let tests = + testList "orchestration tests" [ + testAsync "Orchestration test no records" { + + let mockContext = Mock() + mockContext.Setup(fun c -> c.CallActivityAsync("SayHello","Tokyo")).ReturnsAsync("Hello Tokyo") |> ignore + mockContext.Setup(fun c -> c.CallActivityAsync("SayHello","Seattle")).ReturnsAsync("Hello Seattle") |> ignore + mockContext.Setup(fun c -> c.CallActivityAsync("SayHello","London")).ReturnsAsync("Hello London") |> ignore + let! results = RunWorkflow mockContext.Object |> Async.AwaitTask + + Expect.hasLength results 3 "should be 3 results" + Expect.equal results.[0] "Hello Tokyo!" "Should be Hello Tokyo!" + Expect.equal results.[1] "Hello Seattle!" "Should be Hello Seattle!" + Expect.equal results.[2] "Hello London!" "Should be Hello London!" + } + ] \ No newline at end of file diff --git a/src/DurableFunctions.FSharp/Activity.fs b/src/DurableFunctions.FSharp/Activity.fs index 40732c9..5c1e19f 100644 --- a/src/DurableFunctions.FSharp/Activity.fs +++ b/src/DurableFunctions.FSharp/Activity.fs @@ -47,11 +47,11 @@ module Activity = /// Call an activity by name, passing an object as its input argument /// and specifying the type to expect for the activity output. - let callByName<'a> (name: string) arg (c: DurableOrchestrationContext) = + let callByName<'a> (name: string) arg (c: DurableOrchestrationContextBase) = c.CallActivityAsync<'a> (name, arg) /// Call the activity with given input parameter and return its result. - let call (activity: Activity<'a, 'b>) (arg: 'a) (c: DurableOrchestrationContext) = + let call (activity: Activity<'a, 'b>) (arg: 'a) (c: DurableOrchestrationContextBase) = c.CallActivityAsync<'b> (activity.name, arg) let optionsBuilder = function @@ -63,13 +63,13 @@ module Activity = /// Call the activity with given input parameter and return its result. Apply retry /// policy in case of call failure(s). - let callWithRetries (policy: RetryPolicy) (activity: Activity<'a, 'b>) (arg: 'a) (c: DurableOrchestrationContext) = + let callWithRetries (policy: RetryPolicy) (activity: Activity<'a, 'b>) (arg: 'a) (c: DurableOrchestrationContextBase) = c.CallActivityWithRetryAsync<'b> (activity.name, (optionsBuilder policy), arg) /// Call the activity by name passing an object as its input argument /// and specifying the type to expect for the activity output. Apply retry /// policy in case of call failure(s). - let callByNameWithRetries<'a> (policy: RetryPolicy) (name:string) arg (c: DurableOrchestrationContext) = + let callByNameWithRetries<'a> (policy: RetryPolicy) (name:string) arg (c: DurableOrchestrationContextBase) = c.CallActivityWithRetryAsync<'a> (name, (optionsBuilder policy), arg) /// Call all specified tasks in parallel and combine the results together. To be used diff --git a/src/DurableFunctions.FSharp/Orchestrator.fs b/src/DurableFunctions.FSharp/Orchestrator.fs index 21aab87..eb58fcd 100644 --- a/src/DurableFunctions.FSharp/Orchestrator.fs +++ b/src/DurableFunctions.FSharp/Orchestrator.fs @@ -14,12 +14,12 @@ type Orchestrator = class /// Runs a workflow which expects an input parameter by reading this parameter from /// the orchestration context. - static member run (workflow : ContextTask<'b>, context : DurableOrchestrationContext) : Task<'b> = + static member run (workflow : ContextTask<'b>, context : DurableOrchestrationContextBase) : Task<'b> = workflow context /// Runs a workflow which expects an input parameter by reading this parameter from /// the orchestration context. - static member run (workflow : 'a -> ContextTask<'b>, context : DurableOrchestrationContext) : Task<'b> = + static member run (workflow : 'a -> ContextTask<'b>, context : DurableOrchestrationContextBase) : Task<'b> = let input = context.GetInput<'a> () workflow input context @@ -27,7 +27,7 @@ type Orchestrator = class /// [ContinueAsNew] calls. The orchestrator will keep running until Stop command is /// returned from one of the workflow iterations. /// This overload always passes [null] to [ContinueAsNew] calls. - static member runEternal (workflow : ContextTask>, context : DurableOrchestrationContext) : Task = + static member runEternal (workflow : ContextTask>, context : DurableOrchestrationContextBase) : Task = let task = workflow context task.ContinueWith ( fun (t: Task>) -> @@ -40,7 +40,7 @@ type Orchestrator = class /// [ContinueAsNew] calls. The orchestrator will keep running until Stop command is /// returned from one of the workflow iterations. /// This overload always passes the returned value to [ContinueAsNew] calls. - static member runEternal (workflow : 'a -> ContextTask>, context : DurableOrchestrationContext) : Task = + static member runEternal (workflow : 'a -> ContextTask>, context : DurableOrchestrationContextBase) : Task = let input = context.GetInput<'a> () let task = workflow input context task.ContinueWith ( @@ -51,17 +51,17 @@ type Orchestrator = class ) /// Returns a fixed value as a orchestrator. - static member ret value (_: DurableOrchestrationContext) = + static member ret value (_: DurableOrchestrationContextBase) = Task.FromResult value /// Delays orchestrator execution by the specified timespan. - static member delay (timespan: TimeSpan) (context: DurableOrchestrationContext) = + static member delay (timespan: TimeSpan) (context: DurableOrchestrationContextBase) = let deadline = context.CurrentUtcDateTime.Add timespan context.CreateTimer(deadline, CancellationToken.None) /// Wait for an external event. maxTimeToWait specifies the longest period to wait: /// the call will return an Error if timeout is reached. - static member waitForEvent<'a> (maxTimeToWait: TimeSpan) (eventName: string) (context: DurableOrchestrationContext) = + static member waitForEvent<'a> (maxTimeToWait: TimeSpan) (eventName: string) (context: DurableOrchestrationContextBase) = let deadline = context.CurrentUtcDateTime.Add maxTimeToWait let timer = context.CreateTimer(deadline, CancellationToken.None) let event = context.WaitForExternalEvent<'a> eventName diff --git a/src/DurableFunctions.FSharp/OrchestratorCE.fs b/src/DurableFunctions.FSharp/OrchestratorCE.fs index 597c2c8..12283f5 100644 --- a/src/DurableFunctions.FSharp/OrchestratorCE.fs +++ b/src/DurableFunctions.FSharp/OrchestratorCE.fs @@ -7,8 +7,8 @@ open Microsoft.Azure.WebJobs module OrchestratorBuilder = - type ContextTask = DurableOrchestrationContext -> Task - type ContextTask<'a> = DurableOrchestrationContext -> Task<'a> + type ContextTask = DurableOrchestrationContextBase -> Task + type ContextTask<'a> = DurableOrchestrationContextBase -> Task<'a> /// Represents the state of a computation: /// either awaiting something with a continuation, @@ -19,7 +19,7 @@ module OrchestratorBuilder = /// We model tail calls explicitly, but still can't run them without O(n) memory usage. | ReturnFrom of ContextTask<'a> /// Implements the machinery of running a `Step<'m, 'm>` as a task returning a continuation task. - and StepStateMachine<'a>(firstStep, c: DurableOrchestrationContext) as this = + and StepStateMachine<'a>(firstStep, c: DurableOrchestrationContextBase) as this = let methodBuilder = AsyncTaskMethodBuilder<'a Task>() /// The continuation we left off awaiting on our last MoveNext(). let mutable continuation = fun () -> firstStep @@ -112,7 +112,7 @@ module OrchestratorBuilder = /// Chains together a step with its following step. /// Note that this requires that the first step has no result. /// This prevents constructs like `task { return 1; return 2; }`. - let rec combine (step : Step) (continuation : unit -> Step<'b>) (c: DurableOrchestrationContext) = + let rec combine (step : Step) (continuation : unit -> Step<'b>) (c: DurableOrchestrationContextBase) = match step with | Return _ -> continuation () | ReturnFrom t -> @@ -121,7 +121,7 @@ module OrchestratorBuilder = Await (awaitable, fun () -> combine (next()) continuation c) /// Runs a step as a task -- with a short-circuit for immediately completed steps. - let run (firstStep : unit -> Step<'a>) (c: DurableOrchestrationContext) = + let run (firstStep : unit -> Step<'a>) (c: DurableOrchestrationContextBase) = try match firstStep() with | Return x -> Task.FromResult(x)