diff --git a/.gitignore b/.gitignore
index 16efb5f9b..8a865504c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@
bin/
obj/
packages/
+pkg/
TestResults/
# Packages #
diff --git a/DurableTask.ServiceFabric.sln b/DurableTask.ServiceFabric.sln
new file mode 100644
index 000000000..b4c1c08c0
--- /dev/null
+++ b/DurableTask.ServiceFabric.sln
@@ -0,0 +1,142 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26403.7
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{501E1168-418C-4832-B88C-617735BD02C9}"
+ ProjectSection(SolutionItems) = preProject
+ .nuget\NuGet.Config = .nuget\NuGet.Config
+ .nuget\NuGet.exe = .nuget\NuGet.exe
+ .nuget\NuGet.targets = .nuget\NuGet.targets
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0AE737F5-4AA1-402F-A61C-258CA00E282C}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{5177D1DE-AC86-41B9-8D04-41988A7C2208}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestFabricApplication", "TestFabricApplication", "{507C0CC2-082F-4EB7-8984-E2648976EB04}"
+EndProject
+Project("{A07B5EB6-E848-4116-A8D0-A826331D98C6}") = "TestFabricApplication", "Test\TestFabricApplication\TestFabricApplication\TestFabricApplication.sfproj", "{CA924781-AE52-45FA-A817-C39C822FB782}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestStatefulService", "Test\TestFabricApplication\TestStatefulService\TestStatefulService.csproj", "{95D6C9C7-F7A0-4125-840D-1854BEC24978}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApplication.Common", "Test\TestFabricApplication\TestApplication.Common\TestApplication.Common.csproj", "{EA90334D-3194-4266-81EB-EECA9C760315}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DurableTask.ServiceFabric", "src\DurableTask.ServiceFabric\DurableTask.ServiceFabric.csproj", "{5C617B24-4902-4992-8F71-AB14F00B74A8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DurableTask.ServiceFabric.Test", "Test\DurableTask.ServiceFabric.Test\DurableTask.ServiceFabric.Test.csproj", "{EDC63DDB-8397-4AEC-BF54-6395B2527075}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DurableTask.Test.Orchestrations", "Test\DurableTask.Test.Orchestrations\DurableTask.Test.Orchestrations.csproj", "{2BC2A5AD-4200-41B9-A034-332BB1D7187B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DurableTask.ServiceFabric.Failover.Tests", "Test\DurableTask.ServiceFabric.Stress.Tests\DurableTask.ServiceFabric.Failover.Tests.csproj", "{BE4624F3-E565-428F-9EED-E6847530746A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DurableTask.ServiceFabric.UnitTests", "Test\DurableTask.ServiceFabric.UnitTests\DurableTask.ServiceFabric.UnitTests.csproj", "{31C9C932-6376-46D4-A68A-A0A7F2726D38}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DurableTask.ServiceFabric.Tests.Tooling", "Test\DurableTask.ServiceFabric.Tests.Tooling\DurableTask.ServiceFabric.Tests.Tooling.csproj", "{ED6817FD-A301-40CA-B287-1C40E7AAC7D1}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DurableTask.Core", "src\DurableTask.Core\DurableTask.Core.csproj", "{6B386E8F-20EB-43DE-BBD7-6408A232B4BA}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {CA924781-AE52-45FA-A817-C39C822FB782}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {CA924781-AE52-45FA-A817-C39C822FB782}.Debug|x64.ActiveCfg = Debug|x64
+ {CA924781-AE52-45FA-A817-C39C822FB782}.Debug|x64.Build.0 = Debug|x64
+ {CA924781-AE52-45FA-A817-C39C822FB782}.Debug|x64.Deploy.0 = Debug|x64
+ {CA924781-AE52-45FA-A817-C39C822FB782}.Release|Any CPU.ActiveCfg = Release|x64
+ {CA924781-AE52-45FA-A817-C39C822FB782}.Release|x64.ActiveCfg = Release|x64
+ {CA924781-AE52-45FA-A817-C39C822FB782}.Release|x64.Build.0 = Release|x64
+ {CA924781-AE52-45FA-A817-C39C822FB782}.Release|x64.Deploy.0 = Release|x64
+ {95D6C9C7-F7A0-4125-840D-1854BEC24978}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {95D6C9C7-F7A0-4125-840D-1854BEC24978}.Debug|x64.ActiveCfg = Debug|x64
+ {95D6C9C7-F7A0-4125-840D-1854BEC24978}.Debug|x64.Build.0 = Debug|x64
+ {95D6C9C7-F7A0-4125-840D-1854BEC24978}.Release|Any CPU.ActiveCfg = Release|x64
+ {95D6C9C7-F7A0-4125-840D-1854BEC24978}.Release|x64.ActiveCfg = Release|x64
+ {95D6C9C7-F7A0-4125-840D-1854BEC24978}.Release|x64.Build.0 = Release|x64
+ {EA90334D-3194-4266-81EB-EECA9C760315}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EA90334D-3194-4266-81EB-EECA9C760315}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EA90334D-3194-4266-81EB-EECA9C760315}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {EA90334D-3194-4266-81EB-EECA9C760315}.Debug|x64.Build.0 = Debug|Any CPU
+ {EA90334D-3194-4266-81EB-EECA9C760315}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EA90334D-3194-4266-81EB-EECA9C760315}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EA90334D-3194-4266-81EB-EECA9C760315}.Release|x64.ActiveCfg = Release|Any CPU
+ {EA90334D-3194-4266-81EB-EECA9C760315}.Release|x64.Build.0 = Release|Any CPU
+ {5C617B24-4902-4992-8F71-AB14F00B74A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5C617B24-4902-4992-8F71-AB14F00B74A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5C617B24-4902-4992-8F71-AB14F00B74A8}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {5C617B24-4902-4992-8F71-AB14F00B74A8}.Debug|x64.Build.0 = Debug|Any CPU
+ {5C617B24-4902-4992-8F71-AB14F00B74A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5C617B24-4902-4992-8F71-AB14F00B74A8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5C617B24-4902-4992-8F71-AB14F00B74A8}.Release|x64.ActiveCfg = Release|Any CPU
+ {5C617B24-4902-4992-8F71-AB14F00B74A8}.Release|x64.Build.0 = Release|Any CPU
+ {EDC63DDB-8397-4AEC-BF54-6395B2527075}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EDC63DDB-8397-4AEC-BF54-6395B2527075}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EDC63DDB-8397-4AEC-BF54-6395B2527075}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {EDC63DDB-8397-4AEC-BF54-6395B2527075}.Debug|x64.Build.0 = Debug|Any CPU
+ {EDC63DDB-8397-4AEC-BF54-6395B2527075}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EDC63DDB-8397-4AEC-BF54-6395B2527075}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EDC63DDB-8397-4AEC-BF54-6395B2527075}.Release|x64.ActiveCfg = Release|Any CPU
+ {EDC63DDB-8397-4AEC-BF54-6395B2527075}.Release|x64.Build.0 = Release|Any CPU
+ {2BC2A5AD-4200-41B9-A034-332BB1D7187B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2BC2A5AD-4200-41B9-A034-332BB1D7187B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2BC2A5AD-4200-41B9-A034-332BB1D7187B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {2BC2A5AD-4200-41B9-A034-332BB1D7187B}.Debug|x64.Build.0 = Debug|Any CPU
+ {2BC2A5AD-4200-41B9-A034-332BB1D7187B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2BC2A5AD-4200-41B9-A034-332BB1D7187B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2BC2A5AD-4200-41B9-A034-332BB1D7187B}.Release|x64.ActiveCfg = Release|Any CPU
+ {2BC2A5AD-4200-41B9-A034-332BB1D7187B}.Release|x64.Build.0 = Release|Any CPU
+ {BE4624F3-E565-428F-9EED-E6847530746A}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {BE4624F3-E565-428F-9EED-E6847530746A}.Debug|x64.ActiveCfg = Debug|x64
+ {BE4624F3-E565-428F-9EED-E6847530746A}.Debug|x64.Build.0 = Debug|x64
+ {BE4624F3-E565-428F-9EED-E6847530746A}.Release|Any CPU.ActiveCfg = Release|x64
+ {BE4624F3-E565-428F-9EED-E6847530746A}.Release|x64.ActiveCfg = Release|x64
+ {BE4624F3-E565-428F-9EED-E6847530746A}.Release|x64.Build.0 = Release|x64
+ {31C9C932-6376-46D4-A68A-A0A7F2726D38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {31C9C932-6376-46D4-A68A-A0A7F2726D38}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {31C9C932-6376-46D4-A68A-A0A7F2726D38}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {31C9C932-6376-46D4-A68A-A0A7F2726D38}.Debug|x64.Build.0 = Debug|Any CPU
+ {31C9C932-6376-46D4-A68A-A0A7F2726D38}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {31C9C932-6376-46D4-A68A-A0A7F2726D38}.Release|Any CPU.Build.0 = Release|Any CPU
+ {31C9C932-6376-46D4-A68A-A0A7F2726D38}.Release|x64.ActiveCfg = Release|Any CPU
+ {31C9C932-6376-46D4-A68A-A0A7F2726D38}.Release|x64.Build.0 = Release|Any CPU
+ {ED6817FD-A301-40CA-B287-1C40E7AAC7D1}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {ED6817FD-A301-40CA-B287-1C40E7AAC7D1}.Debug|x64.ActiveCfg = Debug|x64
+ {ED6817FD-A301-40CA-B287-1C40E7AAC7D1}.Debug|x64.Build.0 = Debug|x64
+ {ED6817FD-A301-40CA-B287-1C40E7AAC7D1}.Release|Any CPU.ActiveCfg = Release|x64
+ {ED6817FD-A301-40CA-B287-1C40E7AAC7D1}.Release|x64.ActiveCfg = Release|x64
+ {ED6817FD-A301-40CA-B287-1C40E7AAC7D1}.Release|x64.Build.0 = Release|x64
+ {6B386E8F-20EB-43DE-BBD7-6408A232B4BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6B386E8F-20EB-43DE-BBD7-6408A232B4BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6B386E8F-20EB-43DE-BBD7-6408A232B4BA}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {6B386E8F-20EB-43DE-BBD7-6408A232B4BA}.Debug|x64.Build.0 = Debug|Any CPU
+ {6B386E8F-20EB-43DE-BBD7-6408A232B4BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6B386E8F-20EB-43DE-BBD7-6408A232B4BA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6B386E8F-20EB-43DE-BBD7-6408A232B4BA}.Release|x64.ActiveCfg = Release|Any CPU
+ {6B386E8F-20EB-43DE-BBD7-6408A232B4BA}.Release|x64.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {507C0CC2-082F-4EB7-8984-E2648976EB04} = {5177D1DE-AC86-41B9-8D04-41988A7C2208}
+ {CA924781-AE52-45FA-A817-C39C822FB782} = {507C0CC2-082F-4EB7-8984-E2648976EB04}
+ {95D6C9C7-F7A0-4125-840D-1854BEC24978} = {507C0CC2-082F-4EB7-8984-E2648976EB04}
+ {EA90334D-3194-4266-81EB-EECA9C760315} = {507C0CC2-082F-4EB7-8984-E2648976EB04}
+ {5C617B24-4902-4992-8F71-AB14F00B74A8} = {0AE737F5-4AA1-402F-A61C-258CA00E282C}
+ {EDC63DDB-8397-4AEC-BF54-6395B2527075} = {5177D1DE-AC86-41B9-8D04-41988A7C2208}
+ {2BC2A5AD-4200-41B9-A034-332BB1D7187B} = {5177D1DE-AC86-41B9-8D04-41988A7C2208}
+ {BE4624F3-E565-428F-9EED-E6847530746A} = {5177D1DE-AC86-41B9-8D04-41988A7C2208}
+ {31C9C932-6376-46D4-A68A-A0A7F2726D38} = {5177D1DE-AC86-41B9-8D04-41988A7C2208}
+ {ED6817FD-A301-40CA-B287-1C40E7AAC7D1} = {5177D1DE-AC86-41B9-8D04-41988A7C2208}
+ {6B386E8F-20EB-43DE-BBD7-6408A232B4BA} = {0AE737F5-4AA1-402F-A61C-258CA00E282C}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ EnterpriseLibraryConfigurationToolBinariesPath = packages\TransientFaultHandling.Core.5.1.1209.1\lib\NET4
+ SolutionGuid = {AB50E5E3-EAB4-4EBA-AE13-551034A521F8}
+ EndGlobalSection
+EndGlobal
diff --git a/Test/DurableTask.ServiceFabric.Stress.Tests/ChaosRunner.cs b/Test/DurableTask.ServiceFabric.Stress.Tests/ChaosRunner.cs
new file mode 100644
index 000000000..7e780d6c4
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.Stress.Tests/ChaosRunner.cs
@@ -0,0 +1,63 @@
+// ----------------------------------------------------------------------------------
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+namespace DurableTask.ServiceFabric.Failover.Tests
+{
+ using System;
+ using System.ComponentModel;
+ using System.Fabric;
+ using System.Fabric.Testability.Scenario;
+ using System.Threading;
+ using System.Threading.Tasks;
+
+ class ChaosRunner
+ {
+ public async Task RunFailoverScenario(TimeSpan timeToRun, CancellationToken token)
+ {
+ var partitionSelector = PartitionSelector.PartitionKeyOf(new Uri("fabric:/TestFabricApplication/TestStatefulService"), 1);
+ TimeSpan maxServiceStabilizationTimeout = TimeSpan.FromMinutes(3);
+ var client = new FabricClient("localhost:19000");
+
+ var parameters = new FailoverTestScenarioParameters(
+ partitionSelector,
+ timeToRun,
+ maxServiceStabilizationTimeout);
+
+ Console.WriteLine($"FailoverTest Info : TimeToRun = {parameters.TimeToRun}");
+ Console.WriteLine($"FailoverTest Info : WaitTimeBetweenFaults = {parameters.WaitTimeBetweenFaults}");
+ Console.WriteLine($"FailoverTest Info : MaxServiceStabilizationTimeout = {parameters.MaxServiceStabilizationTimeout}");
+
+ var testScenario = new FailoverTestScenario(client, parameters);
+ testScenario.ProgressChanged += OnTestProgressChanged;
+
+ try
+ {
+ await testScenario.ExecuteAsync(token);
+ }
+ catch (TaskCanceledException)
+ {
+ }
+ catch (AggregateException ae)
+ {
+ Console.Error.WriteLine($"{DateTime.UtcNow} : Exception inside the failover test scenario : {ae.InnerException}");
+ }
+ }
+
+ private void OnTestProgressChanged(object sender, ProgressChangedEventArgs e)
+ {
+ var utcNow = DateTime.UtcNow;
+ Console.WriteLine($"{utcNow} : FailoverTest Progress Percentage {e.ProgressPercentage}");
+ Console.WriteLine($"{utcNow} : FailoverTest UserState {e.UserState}");
+ }
+ }
+}
diff --git a/Test/DurableTask.ServiceFabric.Stress.Tests/DurableTask.ServiceFabric.Failover.Tests.csproj b/Test/DurableTask.ServiceFabric.Stress.Tests/DurableTask.ServiceFabric.Failover.Tests.csproj
new file mode 100644
index 000000000..af9297a5c
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.Stress.Tests/DurableTask.ServiceFabric.Failover.Tests.csproj
@@ -0,0 +1,118 @@
+
+
+
+
+
+ Debug
+ AnyCPU
+ {BE4624F3-E565-428F-9EED-E6847530746A}
+ Exe
+ Properties
+ DurableTask.ServiceFabric.Failover.Tests
+ DurableTask.ServiceFabric.Failover.Tests
+ 512
+ true
+ v4.5.1
+
+
+
+ x64
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ false
+
+
+ x64
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ false
+
+
+
+ ..\..\packages\Microsoft.ServiceFabric.Data.2.5.216\lib\net45\Microsoft.ServiceFabric.Data.dll
+ True
+
+
+ ..\..\packages\Microsoft.ServiceFabric.Data.2.5.216\lib\net45\Microsoft.ServiceFabric.Data.Interfaces.dll
+ True
+
+
+ ..\..\packages\Microsoft.ServiceFabric.FabricTransport.Internal.2.5.216\lib\net45\Microsoft.ServiceFabric.FabricTransport.dll
+
+
+ ..\..\packages\Microsoft.ServiceFabric.5.5.216\lib\net45\Microsoft.ServiceFabric.Internal.dll
+ True
+
+
+ ..\..\packages\Microsoft.ServiceFabric.5.5.216\lib\net45\Microsoft.ServiceFabric.Internal.Strings.dll
+ True
+
+
+ ..\..\packages\Microsoft.ServiceFabric.Services.2.5.216\lib\net45\Microsoft.ServiceFabric.Services.dll
+ True
+
+
+ ..\..\packages\Microsoft.ServiceFabric.Services.Remoting.2.5.216\lib\net45\Microsoft.ServiceFabric.Services.Remoting.dll
+
+
+ ..\..\packages\Microsoft.ServiceFabric.Services.2.5.216\lib\net45\ServiceFabricServiceModel.dll
+ True
+
+
+
+
+ ..\..\packages\Microsoft.ServiceFabric.5.5.216\lib\net45\System.Fabric.dll
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {6B386E8F-20EB-43DE-BBD7-6408A232B4BA}
+ DurableTask.Core
+
+
+ {2bc2a5ad-4200-41b9-a034-332bb1d7187b}
+ DurableTask.Test.Orchestrations
+
+
+ {ea90334d-3194-4266-81eb-eeca9c760315}
+ TestApplication.Common
+
+
+
+
+
\ No newline at end of file
diff --git a/Test/DurableTask.ServiceFabric.Stress.Tests/OrchestrationRunner.cs b/Test/DurableTask.ServiceFabric.Stress.Tests/OrchestrationRunner.cs
new file mode 100644
index 000000000..7d26bff37
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.Stress.Tests/OrchestrationRunner.cs
@@ -0,0 +1,163 @@
+// ----------------------------------------------------------------------------------
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+namespace DurableTask.ServiceFabric.Failover.Tests
+{
+ using System;
+ using System.Collections.Concurrent;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using DurableTask.Core;
+ using DurableTask.Test.Orchestrations.Perf;
+ using Microsoft.ServiceFabric.Services.Client;
+ using Microsoft.ServiceFabric.Services.Remoting.Client;
+ using TestApplication.Common;
+
+ class OrchestrationRunner
+ {
+ ConcurrentBag instances = new ConcurrentBag();
+ IRemoteClient serviceClient = ServiceProxy.Create(new Uri("fabric:/TestFabricApplication/TestStatefulService"), new ServicePartitionKey(1));
+ Dictionary outcomeFrequencies = new Dictionary();
+
+ public async Task RunTestAsync(CancellationToken token)
+ {
+ CancellationTokenSource cts2 = new CancellationTokenSource();
+
+ Stopwatch watch = Stopwatch.StartNew();
+ var programTask = RunOrchestrations(token);
+ var statusTask = PollState(cts2.Token);
+
+ await programTask;
+
+ cts2.Cancel();
+ await statusTask;
+ watch.Stop();
+
+ Func elapsedTimeFormatter = timeSpan => $"{timeSpan.Hours:00}:{timeSpan.Minutes:00}:{timeSpan.Seconds:00}.{timeSpan.Milliseconds / 10:00}";
+ Console.WriteLine($"{nameof(RunTestAsync)}: Total elapsed time for the program : {elapsedTimeFormatter(watch.Elapsed)}");
+ }
+
+ async Task PollState(CancellationToken cancellationToken)
+ {
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ await PrintStatusAggregation();
+ await Task.Delay(TimeSpan.FromSeconds(10));
+ }
+ await PrintStatusAggregation(printFailedInstances: true);
+ }
+
+ async Task PrintStatusAggregation(bool printFailedInstances = false)
+ {
+ Console.WriteLine($"{nameof(PrintStatusAggregation)}: Number of instances so far : {instances.Count}");
+
+ var failedInstances = new Dictionary();
+
+ foreach (var instance in instances)
+ {
+ var state = await Utils.ExceptionWrapper(() => serviceClient.GetOrchestrationState(instance), $"Get Orchestration State {instance}");
+ var key = state?.OrchestrationStatus.ToString() ?? "NullState";
+
+ if (state?.OrchestrationStatus != OrchestrationStatus.Completed)
+ {
+ failedInstances.Add(instance, state);
+ }
+
+ if (!outcomeFrequencies.ContainsKey(key))
+ {
+ outcomeFrequencies.Add(key, 0);
+ }
+
+ outcomeFrequencies[key]++;
+ }
+
+ foreach (var kvp in outcomeFrequencies)
+ {
+ Console.WriteLine($"{nameof(PrintStatusAggregation)}: {kvp.Key} : {kvp.Value}");
+ }
+
+ if (printFailedInstances && failedInstances.Count > 0)
+ {
+ Console.WriteLine("Unsuccessful Orchestrations:");
+ foreach (var kvp in failedInstances)
+ {
+ Console.WriteLine($"{kvp.Key} : {kvp.Value?.OrchestrationStatus}");
+ }
+ }
+
+ outcomeFrequencies.Clear();
+ }
+
+ async Task RunOrchestrations(CancellationToken cancellationToken)
+ {
+ int totalRequests = 0;
+ List tasks = new List();
+ Console.WriteLine($"{nameof(RunOrchestrations)}: Starting orchestrations");
+
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ var newOrchData = new TestOrchestrationData()
+ {
+ NumberOfParallelTasks = 15,
+ NumberOfSerialTasks = 5,
+ MaxDelay = 500,
+ MinDelay = 50,
+ DelayUnit = TimeSpan.FromMilliseconds(1),
+ UseTimeoutTask = false, //Change to true when testing timers
+ ExecutionTimeout = TimeSpan.FromMinutes(5)
+ };
+
+ totalRequests++;
+ var instance = await Utils.ExceptionWrapper(() => serviceClient.StartTestOrchestrationAsync(newOrchData), $"StartOrchestration {totalRequests}");
+ if (instance != null)
+ {
+ instances.Add(instance);
+ var waitTask = Utils.ExceptionWrapper(() => serviceClient.WaitForOrchestration(instance, TimeSpan.FromMinutes(10)), $"WaitForOrchestration {totalRequests}");
+ tasks.Add(waitTask);
+ }
+
+ var delayBetweenRequests = 50;
+ try
+ {
+ await Task.Delay(TimeSpan.FromMilliseconds(delayBetweenRequests), cancellationToken);
+ }
+ catch (TaskCanceledException)
+ {
+ break;
+ }
+ }
+
+ Console.WriteLine($"{nameof(RunOrchestrations)}: Total orchestrations : {totalRequests}");
+ Console.WriteLine($"{nameof(RunOrchestrations)}: Waiting for pending orchestrations");
+ await Task.WhenAll(tasks);
+
+ Console.WriteLine($"{nameof(RunOrchestrations)}: Done");
+ }
+
+ int GetVariableDelayForTotalRequests(int totalRequests)
+ {
+ int delay = 0;
+ if (totalRequests % 10 == 0)
+ {
+ delay = 1000;
+ }
+ if (totalRequests % 100 == 0)
+ {
+ delay = 3000;
+ }
+ return delay;
+ }
+ }
+}
diff --git a/Test/DurableTask.ServiceFabric.Stress.Tests/Program.cs b/Test/DurableTask.ServiceFabric.Stress.Tests/Program.cs
new file mode 100644
index 000000000..1587587c4
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.Stress.Tests/Program.cs
@@ -0,0 +1,78 @@
+// ----------------------------------------------------------------------------------
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+namespace DurableTask.ServiceFabric.Failover.Tests
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Threading;
+ using System.Threading.Tasks;
+
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ MainAsync(args).Wait();
+ }
+
+ static async Task MainAsync(string[] args)
+ {
+ var timeToRun = ValidateArgs(args);
+
+ var tasks = new List();
+ if (timeToRun != TimeSpan.Zero)
+ {
+ CancellationTokenSource cts = new CancellationTokenSource();
+ if (timeToRun <= TimeSpan.FromMinutes(Int32.MaxValue)) //Cancellation token can't take more time than Int32.MaxValue
+ {
+ cts.CancelAfter(timeToRun);
+ }
+
+ Console.WriteLine("Press CTRL+C to stop running orchestrations and print results");
+ Console.CancelKeyPress += (sender, e) =>
+ {
+ cts.Cancel();
+ e.Cancel = true;
+ };
+
+ var runner = new OrchestrationRunner();
+ tasks.Add(runner.RunTestAsync(cts.Token));
+
+ await Task.Delay(TimeSpan.FromMinutes(2));
+
+ var chaosRunner = new ChaosRunner();
+ tasks.Add(chaosRunner.RunFailoverScenario(timeToRun, cts.Token));
+ }
+
+ await Task.WhenAll(tasks);
+ }
+
+ static TimeSpan ValidateArgs(string[] args)
+ {
+ if (args == null || args.Length == 0)
+ {
+ return TimeSpan.MaxValue; //Indicates that test should run till user stops it.
+ }
+
+ var first = args[0];
+ int toBeRunTime = 0;
+ if (!int.TryParse(first, out toBeRunTime))
+ {
+ Console.WriteLine($"{Process.GetCurrentProcess().ProcessName} [Time to run (minutes)]");
+ }
+
+ return TimeSpan.FromMinutes(toBeRunTime);
+ }
+ }
+}
diff --git a/Test/DurableTask.ServiceFabric.Stress.Tests/Properties/AssemblyInfo.cs b/Test/DurableTask.ServiceFabric.Stress.Tests/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..05adeb4bc
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.Stress.Tests/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("DurableTask.ServiceFabric.Failover.Tests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("DurableTask.ServiceFabric.Failover.Tests")]
+[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("be4624f3-e565-428f-9eed-e6847530746a")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Test/DurableTask.ServiceFabric.Stress.Tests/Utils.cs b/Test/DurableTask.ServiceFabric.Stress.Tests/Utils.cs
new file mode 100644
index 000000000..54b7e57e8
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.Stress.Tests/Utils.cs
@@ -0,0 +1,34 @@
+// ----------------------------------------------------------------------------------
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+namespace DurableTask.ServiceFabric.Failover.Tests
+{
+ using System;
+ using System.Threading.Tasks;
+
+ class Utils
+ {
+ public static async Task ExceptionWrapper(Func> action, string actionName)
+ {
+ try
+ {
+ return await action();
+ }
+ catch (Exception e)
+ {
+ Console.Error.WriteLine($"Exception caught from method {actionName} : {e}");
+ return default(TResult);
+ }
+ }
+ }
+}
diff --git a/Test/DurableTask.ServiceFabric.Stress.Tests/app.config b/Test/DurableTask.ServiceFabric.Stress.Tests/app.config
new file mode 100644
index 000000000..877e0c4b1
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.Stress.Tests/app.config
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Test/DurableTask.ServiceFabric.Stress.Tests/packages.config b/Test/DurableTask.ServiceFabric.Stress.Tests/packages.config
new file mode 100644
index 000000000..792498cb5
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.Stress.Tests/packages.config
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Test/DurableTask.ServiceFabric.Test/AssemblySetup.cs b/Test/DurableTask.ServiceFabric.Test/AssemblySetup.cs
new file mode 100644
index 000000000..ce7be91a8
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.Test/AssemblySetup.cs
@@ -0,0 +1,62 @@
+// ----------------------------------------------------------------------------------
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+namespace DurableTask.ServiceFabric.Test
+{
+ using System;
+ using System.IO;
+ using DurableTask.ServiceFabric.Test.DeploymentUtil;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class AssemblySetup
+ {
+ [AssemblyInitialize]
+ public static void AssemblyInitialize(TestContext testContext)
+ {
+ //Todo: This will fail if local cluster is not setup, currently the test code does not automatically
+ //start a local cluster and that's a manual pre-req step.
+ //Todo: Also this will assume that latest SF test application and service code is packaged, if that's not true
+ //the issue might be hard to detect :-(
+ DeploymentHelper.CleanAsync().Wait();
+ DeploymentHelper.DeployAsync(TestApplicationRootPath).Wait();
+ }
+
+ [AssemblyCleanup]
+ public static void AssemblyCleanup()
+ {
+ DeploymentHelper.CleanAsync().Wait();
+ }
+
+ static string TestApplicationRootPath
+ {
+ get
+ {
+ var sourceRoot = Environment.GetEnvironmentVariable("SourceRoot") ?? string.Empty;
+ var applicationPath = Path.Combine(sourceRoot.Trim(), "Test", "TestFabricApplication", "TestFabricApplication");
+
+ if (!Directory.Exists(applicationPath))
+ {
+ throw new Exception("Could not find test application path, define SourceRoot environment variable to the source path");
+ }
+
+ if (!Directory.Exists(Path.Combine(applicationPath, "pkg", "Debug")))
+ {
+ throw new Exception("Could not find test application package, make sure the test application is built and package generated before running the tests");
+ }
+
+ return applicationPath;
+ }
+ }
+ }
+}
diff --git a/Test/DurableTask.ServiceFabric.Test/Constants.cs b/Test/DurableTask.ServiceFabric.Test/Constants.cs
new file mode 100644
index 000000000..23d7c95f8
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.Test/Constants.cs
@@ -0,0 +1,20 @@
+// ----------------------------------------------------------------------------------
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+namespace DurableTask.ServiceFabric.Test
+{
+ static class Constants
+ {
+ public const string TestFabricApplicationAddress = "fabric:/TestFabricApplicationType/TestStatefulService";
+ }
+}
diff --git a/Test/DurableTask.ServiceFabric.Test/DeploymentUtil/ApplicationInfoReader.cs b/Test/DurableTask.ServiceFabric.Test/DeploymentUtil/ApplicationInfoReader.cs
new file mode 100644
index 000000000..34d66d046
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.Test/DeploymentUtil/ApplicationInfoReader.cs
@@ -0,0 +1,108 @@
+// ----------------------------------------------------------------------------------
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+namespace DurableTask.ServiceFabric.Test.DeploymentUtil
+{
+ using System.Collections.Specialized;
+ using System.IO;
+ using System.Linq;
+ using System.Xml;
+
+ class ApplicationInfoReader
+ {
+ string applicationRootPath;
+ string applicationPackagePath;
+
+ readonly XmlElement applicationManifestRoot;
+ readonly XmlNamespaceManager applicationManifestNamespaceManager;
+ readonly XmlElement serviceManifestRoot;
+ readonly XmlNamespaceManager serviceManifestNamespaceManager;
+
+ const string ApplicationManifestNodePath = "/sf:ApplicationManifest";
+ const string ApplicationNameAttribute = "ApplicationTypeName";
+ const string ApplicationVersionAttribute = "ApplicationTypeVersion";
+
+ const string StatefulServiceNodePath = "/sf:ServiceManifest/sf:ServiceTypes/sf:StatefulServiceType";
+ const string StatelessServiceNodePath = "/sf:ServiceManifest/sf:ServiceTypes/sf:StatelessServiceType";
+ const string ServiceTypeNameAttribute = "ServiceTypeName";
+
+ public ApplicationInfoReader(string applicationRootPath)
+ {
+ this.applicationRootPath = applicationRootPath;
+ this.applicationPackagePath = Path.Combine(applicationRootPath, @"pkg\Debug");
+
+ var applicationManifest = new XmlDocument();
+ applicationManifest.Load(Path.Combine(this.applicationPackagePath, "ApplicationManifest.xml"));
+ this.applicationManifestNamespaceManager = GetXmlNamespaceManager(applicationManifest.NameTable);
+ this.applicationManifestRoot = applicationManifest.DocumentElement;
+
+ var serviceManifest = new XmlDocument();
+ var serviceDirectory = Directory.EnumerateDirectories(this.applicationPackagePath).First();
+ serviceManifest.Load(Path.Combine(Path.Combine(this.applicationPackagePath, serviceDirectory), "ServiceManifest.xml"));
+ this.serviceManifestNamespaceManager = GetXmlNamespaceManager(serviceManifest.NameTable);
+ this.serviceManifestRoot = serviceManifest.DocumentElement;
+ }
+
+ public string GetApplicationName()
+ {
+ return GetSingleNodeAttributeValue(applicationManifestRoot, applicationManifestNamespaceManager, ApplicationManifestNodePath, ApplicationNameAttribute);
+ }
+
+ public string GetApplicationVersion()
+ {
+ return GetSingleNodeAttributeValue(applicationManifestRoot, applicationManifestNamespaceManager, ApplicationManifestNodePath, ApplicationVersionAttribute);
+ }
+
+ public string GetServiceName()
+ {
+ return GetSingleNodeAttributeValue(serviceManifestRoot, serviceManifestNamespaceManager, StatefulServiceNodePath, ServiceTypeNameAttribute) ??
+ GetSingleNodeAttributeValue(serviceManifestRoot, serviceManifestNamespaceManager, StatelessServiceNodePath, ServiceTypeNameAttribute);
+ }
+
+ public NameValueCollection GetApplicationParameters()
+ {
+ var applicationParametersPath = Path.Combine(applicationRootPath, @"ApplicationParameters\Local.5Node.xml");
+
+ var applicationParameters = new XmlDocument();
+ applicationParameters.Load(applicationParametersPath);
+
+ var parametersPath = "/sf:Application/sf:Parameters/sf:Parameter";
+ var parameters = new NameValueCollection();
+
+ foreach (var parameterNode in applicationParameters.DocumentElement.SelectNodes(parametersPath, GetXmlNamespaceManager(applicationParameters.NameTable)).OfType())
+ {
+ parameters.Add(parameterNode.Attributes["Name"].Value, parameterNode.Attributes["Value"].Value);
+ }
+
+ return parameters;
+ }
+
+ public string ApplicationPackagePath
+ {
+ get { return this.applicationPackagePath; }
+ }
+
+ string GetSingleNodeAttributeValue(XmlElement root, XmlNamespaceManager namespaceManager, string queryPath, string attributeName)
+ {
+ var node = root.SelectSingleNode(queryPath, namespaceManager);
+ return node?.Attributes?[attributeName].Value;
+ }
+
+ XmlNamespaceManager GetXmlNamespaceManager(XmlNameTable nameTable)
+ {
+ var result = new XmlNamespaceManager(nameTable);
+ result.AddNamespace("sf", "http://schemas.microsoft.com/2011/01/fabric");
+ return result;
+ }
+ }
+}
diff --git a/Test/DurableTask.ServiceFabric.Test/DeploymentUtil/DeploymentHelper.cs b/Test/DurableTask.ServiceFabric.Test/DeploymentUtil/DeploymentHelper.cs
new file mode 100644
index 000000000..1335bea37
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.Test/DeploymentUtil/DeploymentHelper.cs
@@ -0,0 +1,106 @@
+// ----------------------------------------------------------------------------------
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+namespace DurableTask.ServiceFabric.Test.DeploymentUtil
+{
+ using System;
+ using System.Fabric;
+ using System.Fabric.Description;
+ using System.Fabric.Health;
+ using System.Fabric.Query;
+ using System.Linq;
+ using System.Threading.Tasks;
+ using System.Xml;
+
+ static class DeploymentHelper
+ {
+ static readonly FabricClient client = new FabricClient();
+
+ public static async Task DeployAsync(string applicationRootPath)
+ {
+ var appInfoReader = new ApplicationInfoReader(applicationRootPath);
+ var serviceName = new Uri($"fabric:/{appInfoReader.GetApplicationName()}/{appInfoReader.GetServiceName()}");
+ var applicationDescription =
+ new ApplicationDescription(
+ new Uri($"fabric:/{appInfoReader.GetApplicationName()}"),
+ appInfoReader.GetApplicationName(),
+ appInfoReader.GetApplicationVersion(),
+ appInfoReader.GetApplicationParameters());
+
+ await DeployAsync(appInfoReader.ApplicationPackagePath, applicationDescription);
+ await WaitForHealthStatusAsync(applicationDescription, serviceName);
+ }
+
+ public static async Task CleanAsync()
+ {
+ var applications = await client.QueryManager.GetApplicationListAsync();
+ foreach (var application in applications)
+ {
+ await client.ApplicationManager.DeleteApplicationAsync(new DeleteApplicationDescription(application.ApplicationName) { ForceDelete = true });
+
+ foreach (var node in await client.QueryManager.GetNodeListAsync())
+ {
+ var replicas = (await client.QueryManager.GetDeployedReplicaListAsync(node.NodeName, application.ApplicationName)).OfType();
+ foreach (var replica in replicas)
+ {
+ await client.ServiceManager.RemoveReplicaAsync(node.NodeName, replica.Partitionid, replica.ReplicaId);
+ }
+ }
+ }
+
+ var applicationTypeList = await client.QueryManager.GetApplicationTypeListAsync();
+ foreach (var applicationType in applicationTypeList)
+ {
+ await client.ApplicationManager.UnprovisionApplicationAsync(applicationType.ApplicationTypeName, applicationType.ApplicationTypeVersion);
+ }
+ }
+
+ static async Task DeployAsync(string applicationPackagePath, ApplicationDescription applicationDescription)
+ {
+ var clusterManifest = new XmlDocument();
+ var clusterManifestXml = await client.ClusterManager.GetClusterManifestAsync();
+ clusterManifest.LoadXml(clusterManifestXml);
+ var namespaceManager = new XmlNamespaceManager(clusterManifest.NameTable);
+ namespaceManager.AddNamespace("sf", "http://schemas.microsoft.com/2011/01/fabric");
+ var imageStoreConnectionStringPath = "/sf:ClusterManifest/sf:FabricSettings/sf:Section[@Name='Management']/sf:Parameter[@Name='ImageStoreConnectionString']";
+ var imageStoreConnectionStringNode = clusterManifest.DocumentElement.SelectSingleNode(imageStoreConnectionStringPath, namespaceManager);
+ var imageStoreConnectionString = imageStoreConnectionStringNode.Attributes["Value"].Value;
+
+ var path = Guid.NewGuid().ToString();
+ client.ApplicationManager.CopyApplicationPackage(imageStoreConnectionString, applicationPackagePath, path);
+
+ await client.ApplicationManager.ProvisionApplicationAsync(path);
+
+ client.ApplicationManager.RemoveApplicationPackage(imageStoreConnectionString, path);
+
+ await client.ApplicationManager.CreateApplicationAsync(applicationDescription);
+ }
+
+ static async Task WaitForHealthStatusAsync(ApplicationDescription applicationDescription, Uri serviceName)
+ {
+ while (true)
+ {
+ await Task.Delay(TimeSpan.FromSeconds(2));
+ var states = (await client.HealthManager.GetApplicationHealthAsync(applicationDescription.ApplicationName)).ServiceHealthStates;
+ if (states.Any() && states.All(s => s.AggregatedHealthState == HealthState.Ok))
+ {
+ return;
+ }
+ if (states.Any() && states.Any(s => s.AggregatedHealthState == HealthState.Error || s.AggregatedHealthState == HealthState.Invalid))
+ {
+ throw new Exception($"Deployment of {serviceName} failed");
+ }
+ }
+ }
+ }
+}
diff --git a/Test/DurableTask.ServiceFabric.Test/DurableTask.ServiceFabric.Test.csproj b/Test/DurableTask.ServiceFabric.Test/DurableTask.ServiceFabric.Test.csproj
new file mode 100644
index 000000000..d741c8e25
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.Test/DurableTask.ServiceFabric.Test.csproj
@@ -0,0 +1,135 @@
+
+
+
+
+
+ Debug
+ AnyCPU
+ {EDC63DDB-8397-4AEC-BF54-6395B2527075}
+ Library
+ Properties
+ DurableTask.ServiceFabric.Test
+ DurableTask.ServiceFabric.Test
+ 512
+ v4.5.1
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ x64
+ false
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ false
+
+
+
+ ..\..\packages\Microsoft.ServiceFabric.Data.2.5.216\lib\net45\Microsoft.ServiceFabric.Data.dll
+ True
+
+
+ False
+ ..\..\packages\Microsoft.ServiceFabric.Data.2.5.216\lib\net45\Microsoft.ServiceFabric.Data.Interfaces.dll
+ True
+
+
+ ..\..\packages\Microsoft.ServiceFabric.FabricTransport.Internal.2.5.216\lib\net45\Microsoft.ServiceFabric.FabricTransport.dll
+
+
+ ..\..\packages\Microsoft.ServiceFabric.5.5.216\lib\net45\Microsoft.ServiceFabric.Internal.dll
+ True
+
+
+ ..\..\packages\Microsoft.ServiceFabric.5.5.216\lib\net45\Microsoft.ServiceFabric.Internal.Strings.dll
+ True
+
+
+ ..\..\packages\Microsoft.ServiceFabric.Services.2.5.216\lib\net45\Microsoft.ServiceFabric.Services.dll
+ True
+
+
+ ..\..\packages\Microsoft.ServiceFabric.Services.Remoting.2.5.216\lib\net45\Microsoft.ServiceFabric.Services.Remoting.dll
+
+
+
+ ..\..\packages\Microsoft.ServiceFabric.Services.2.5.216\lib\net45\ServiceFabricServiceModel.dll
+ True
+
+
+
+
+ ..\..\packages\Microsoft.ServiceFabric.5.5.216\lib\net45\System.Fabric.dll
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {6B386E8F-20EB-43DE-BBD7-6408A232B4BA}
+ DurableTask.Core
+
+
+ {5c617b24-4902-4992-8f71-ab14f00b74a8}
+ DurableTask.ServiceFabric
+
+
+ {2bc2a5ad-4200-41b9-a034-332bb1d7187b}
+ DurableTask.Test.Orchestrations
+
+
+ {ea90334d-3194-4266-81eb-eeca9c760315}
+ TestApplication.Common
+
+
+ {95d6c9c7-f7a0-4125-840d-1854bec24978}
+ TestStatefulService
+
+
+
+
+
+ Designer
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Test/DurableTask.ServiceFabric.Test/FunctionalTests.cs b/Test/DurableTask.ServiceFabric.Test/FunctionalTests.cs
new file mode 100644
index 000000000..37701516b
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.Test/FunctionalTests.cs
@@ -0,0 +1,371 @@
+// ----------------------------------------------------------------------------------
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+namespace DurableTask.ServiceFabric.Test
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Threading.Tasks;
+ using DurableTask.Core;
+ using DurableTask.Test.Orchestrations.Perf;
+ using Microsoft.ServiceFabric.Services.Client;
+ using Microsoft.ServiceFabric.Services.Remoting.Client;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+ using TestApplication.Common;
+ using TestStatefulService.TestOrchestrations;
+
+ [TestClass]
+ public class FunctionalTests
+ {
+ IRemoteClient serviceClient;
+
+ [TestInitialize]
+ public void TestInitialize()
+ {
+ this.serviceClient = ServiceProxy.Create(new Uri(Constants.TestFabricApplicationAddress), new ServicePartitionKey(1));
+ }
+
+ [TestMethod]
+ public async Task Orchestration_With_ScheduledTasks_Finishes()
+ {
+ var result = await this.serviceClient.RunOrchestrationAsync(typeof(SimpleOrchestrationWithTasks).Name, null, TimeSpan.FromMinutes(2));
+
+ Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus);
+ Assert.AreEqual("\"Hello Gabbar\"", result.Output);
+ }
+
+ [TestMethod]
+ public async Task Orchestration_With_Timer_Finishes_After_The_Wait_Time()
+ {
+ var waitTime = 37;
+ var result = await this.serviceClient.RunOrchestrationAsync(typeof(SimpleOrchestrationWithTimer).Name, waitTime, TimeSpan.FromMinutes(2));
+
+ Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus);
+ Assert.AreEqual("\"Hello Gabbar\"", result.Output);
+ var orchestrationTime = result.CompletedTime - result.CreatedTime;
+ Assert.IsTrue(orchestrationTime > TimeSpan.FromSeconds(waitTime));
+ Console.WriteLine($"Time for Orchestration : {orchestrationTime}, Timer Wait time : {waitTime}");
+ }
+
+ [TestMethod]
+ public async Task GenerationBasicTest()
+ {
+ int generationsToRun = 2;
+
+ var result = await this.serviceClient.RunOrchestrationAsync(typeof(GenerationBasicOrchestration).Name, generationsToRun, TimeSpan.FromMinutes(2));
+
+ Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus);
+ Assert.AreEqual(generationsToRun.ToString(), result.Output, "Orchestration Result is wrong!!!");
+ }
+
+ [TestMethod]
+ public async Task Orchestration_With_SubOrchestration_Finishes()
+ {
+ var result = await this.serviceClient.RunOrchestrationAsync(typeof(SimpleOrchestrationWithSubOrchestration).Name, null, TimeSpan.FromMinutes(2));
+
+ Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus);
+ Assert.AreEqual($"\"TaskResult = Hello World , SubOrchestration1Result = Hello Gabbar, SubOrchestration2Result = Hello Gabbar\"", result.Output);
+ }
+
+ [TestMethod]
+ public async Task Orchestration_With_TimeoutWrapper_Test()
+ {
+ // Task finishes within timeout
+ var instance = await this.serviceClient.StartTestOrchestrationAsync(new TestOrchestrationData()
+ {
+ NumberOfParallelTasks = 0,
+ NumberOfSerialTasks = 1,
+ MaxDelay = 0,
+ MinDelay = 0,
+ DelayUnit = TimeSpan.FromMilliseconds(1),
+ UseTimeoutTask = true,
+ ExecutionTimeout = TimeSpan.FromMinutes(1)
+ });
+ var result = await this.serviceClient.WaitForOrchestration(instance, TimeSpan.FromMinutes(2));
+
+ Assert.IsNotNull(result);
+ Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus);
+ Console.WriteLine($"Time for Orchestration with no delay running task wrapped in 1 minute timeout : {result.CompletedTime - result.CreatedTime}");
+
+ // Task does not finish within timeout
+ instance = await this.serviceClient.StartTestOrchestrationAsync(new TestOrchestrationData()
+ {
+ NumberOfParallelTasks = 0,
+ NumberOfSerialTasks = 1,
+ MaxDelay = 5,
+ MinDelay = 5,
+ DelayUnit = TimeSpan.FromSeconds(1),
+ UseTimeoutTask = true,
+ ExecutionTimeout = TimeSpan.FromSeconds(1)
+ });
+ result = await this.serviceClient.WaitForOrchestration(instance, TimeSpan.FromMinutes(2));
+
+ Assert.IsNotNull(result);
+ Assert.AreEqual(OrchestrationStatus.Failed, result.OrchestrationStatus);
+ Console.WriteLine($"Time for Orchestration with 5 second running task wrapped in 1 second timeout : {result.CompletedTime - result.CreatedTime}");
+ }
+
+ [TestMethod]
+ public async Task Orchestration_With_Same_Id_Cant_Be_Started_While_Running()
+ {
+ var instanceId = nameof(Orchestration_With_Same_Id_Cant_Be_Started_While_Running);
+ var testData = new TestOrchestrationData()
+ {
+ NumberOfParallelTasks = 0,
+ NumberOfSerialTasks = 1,
+ MaxDelay = 5,
+ MinDelay = 5,
+ DelayUnit = TimeSpan.FromSeconds(1),
+ };
+
+ Func>> startFunc = async () =>
+ {
+ OrchestrationInstance instance = null;
+ Exception exception = null;
+
+ try
+ {
+ instance = await this.serviceClient.StartTestOrchestrationWithInstanceIdAsync(instanceId, testData);
+ }
+ catch (Exception e)
+ {
+ while (e is AggregateException)
+ {
+ e = e.InnerException;
+ }
+
+ exception = e;
+ }
+
+ return Tuple.Create(instance, exception);
+ };
+
+ var allResults = await Task.WhenAll(startFunc(), startFunc(), startFunc(), startFunc());
+
+ OrchestrationInstance createdInstance = null;
+
+ for(int i = 0; i < allResults.Length; i++)
+ {
+ var result = allResults[i];
+ if (result.Item1 != null)
+ {
+ if (createdInstance != null)
+ {
+ Assert.Fail($"Multiple orchestrations were started with the instance id {instanceId} at the same time");
+ }
+ else
+ {
+ createdInstance = result.Item1;
+ }
+ }
+ else
+ {
+ Assert.IsInstanceOfType(result.Item2, typeof(InvalidOperationException),
+ $"Exception Type Check Failed : Task {i} returned an unexpected exception {result.Item2}");
+ Assert.AreEqual($"An orchestration with id '{instanceId}' is already running.", result.Item2.Message,
+ $"Exception Message Check Failed : Task {i} returned an unexpected exception {result.Item2}");
+ }
+ }
+
+ Assert.IsNotNull(createdInstance, $"No task was able to create an orchestration with the given instance id {instanceId}");
+ var orchestrationResult = await this.serviceClient.WaitForOrchestration(createdInstance, TimeSpan.FromMinutes(2));
+ Assert.AreEqual(OrchestrationStatus.Completed, orchestrationResult.OrchestrationStatus);
+ }
+
+ [TestMethod]
+ public async Task FabricProviderClient_ReturnsRunningOrchestrations()
+ {
+ var instanceIdPrefix = nameof(FabricProviderClient_ReturnsRunningOrchestrations);
+ int numberOfInstances = 5;
+ var startTasks = new List>();
+
+ for (int i = 0; i < numberOfInstances; i++)
+ {
+ var testData = new TestOrchestrationData()
+ {
+ NumberOfParallelTasks = 0,
+ NumberOfSerialTasks = 1,
+ MaxDelay = 5,
+ MinDelay = 5,
+ DelayUnit = TimeSpan.FromSeconds(1),
+ };
+
+ startTasks.Add(this.serviceClient.StartTestOrchestrationWithInstanceIdAsync(instanceIdPrefix + i, testData));
+ }
+
+ var scheduledInstances = await Task.WhenAll(startTasks);
+ await Task.Delay(TimeSpan.FromSeconds(2));
+
+ var allInstances = (await this.serviceClient.GetRunningOrchestrations()).ToList();
+ Assert.AreEqual(numberOfInstances, allInstances.Count);
+
+ foreach (var scheduledInstance in scheduledInstances)
+ {
+ var returnedInstance = allInstances.FirstOrDefault(i => string.Equals(i.InstanceId, scheduledInstance.InstanceId));
+ Assert.IsNotNull(returnedInstance);
+ Assert.AreEqual(scheduledInstance.ExecutionId, returnedInstance.ExecutionId);
+ var runtimeState = await this.serviceClient.GetOrchestrationRuntimeState(scheduledInstance.InstanceId);
+ Console.WriteLine($"Runtime Orchestration State for {scheduledInstance} : {runtimeState}");
+ }
+
+ var waitTasks = new List>();
+ foreach (var scheduledInstance in scheduledInstances)
+ {
+ waitTasks.Add(this.serviceClient.WaitForOrchestration(scheduledInstance, TimeSpan.FromMinutes(2)));
+ }
+
+ var results = await Task.WhenAll(waitTasks);
+ foreach(var result in results)
+ {
+ Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus);
+ }
+ }
+
+ [TestMethod]
+ public async Task Orchestration_With_Long_InstanceId_Name_Test()
+ {
+ var instanceId = $"MySomewhatLongNamespaceName|MyRelativelyLongRootEntityName|{Guid.NewGuid().ToString("N")}|MyRatherLongRuleName";
+ var instance = await this.serviceClient.StartTestOrchestrationWithInstanceIdAsync(instanceId, new TestOrchestrationData()
+ {
+ NumberOfParallelTasks = 16,
+ NumberOfSerialTasks = 5,
+ MaxDelay = 0,
+ MinDelay = 0,
+ DelayUnit = TimeSpan.FromMilliseconds(1)
+ });
+
+ var result = await this.serviceClient.WaitForOrchestration(instance, TimeSpan.FromMinutes(1));
+ Assert.IsNotNull(result);
+ Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus);
+ }
+
+ [TestMethod]
+ public async Task QueryState_For_Latest_Execution()
+ {
+ var instanceId = nameof(QueryState_For_Latest_Execution);
+ var instance = await this.serviceClient.StartTestOrchestrationWithInstanceIdAsync(instanceId, new TestOrchestrationData()
+ {
+ NumberOfParallelTasks = 0,
+ NumberOfSerialTasks = 1,
+ MaxDelay = 15,
+ MinDelay = 15,
+ DelayUnit = TimeSpan.FromSeconds(1),
+ });
+
+ var state = await this.serviceClient.GetOrchestrationStateWithInstanceId(instanceId);
+
+ Assert.IsNotNull(state);
+ Assert.AreEqual(instance.ExecutionId, state.OrchestrationInstance.ExecutionId);
+
+ var result = await this.serviceClient.WaitForOrchestration(instance, TimeSpan.FromMinutes(2));
+ Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus);
+
+ var newInstance = await this.serviceClient.StartTestOrchestrationWithInstanceIdAsync(instanceId, new TestOrchestrationData()
+ {
+ NumberOfParallelTasks = 0,
+ NumberOfSerialTasks = 1,
+ MaxDelay = 0,
+ MinDelay = 0,
+ DelayUnit = TimeSpan.FromSeconds(1),
+ });
+
+ // We want to make sure that once an orchestration is complete, we can create another instance with the same id.
+ var newState = await this.serviceClient.GetOrchestrationStateWithInstanceId(instanceId);
+
+ Assert.IsNotNull(newState);
+ Assert.AreEqual(newInstance.ExecutionId, newState.OrchestrationInstance.ExecutionId);
+ Assert.AreNotEqual(state.OrchestrationInstance.ExecutionId, newState.OrchestrationInstance.ExecutionId);
+
+ result = await this.serviceClient.WaitForOrchestration(instance, TimeSpan.FromMinutes(2));
+ Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus);
+ }
+
+ [TestMethod]
+ public async Task Retry_OnException_Test()
+ {
+ int retryAttempts = 3;
+ var minumumExpectedTime = TimeSpan.FromSeconds(7); //This should be calculated based on RetryOptions in the Orchestration code.
+
+ var result = await this.serviceClient.RunOrchestrationAsync(typeof(OrchestrationRunningIntoRetry).Name, retryAttempts, TimeSpan.FromMinutes(2));
+ Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus);
+ Assert.AreEqual(retryAttempts.ToString(), result.Output);
+ var orchestrationTime = result.CompletedTime - result.CreatedTime;
+ Console.WriteLine($"Time for Orchestration : {orchestrationTime}, Minumum expected time : {minumumExpectedTime}");
+ Assert.IsTrue(orchestrationTime > minumumExpectedTime);
+ }
+
+ [TestMethod]
+ public async Task ForceTerminate_Terminates_LatestExecution()
+ {
+ var testData = new TestOrchestrationData()
+ {
+ NumberOfParallelTasks = 0,
+ NumberOfSerialTasks = 2,
+ MaxDelay = 5,
+ MinDelay = 5,
+ DelayUnit = TimeSpan.FromSeconds(1),
+ };
+
+ var instance = await this.serviceClient.StartTestOrchestrationAsync(testData);
+ await Task.Delay(TimeSpan.FromSeconds(1));
+
+ var reason = "Testing Terminate Functionality";
+ await this.serviceClient.TerminateOrchestration(instance.InstanceId, reason);
+ var result = await this.serviceClient.WaitForOrchestration(instance, TimeSpan.FromMinutes(1));
+
+ Assert.AreEqual(OrchestrationStatus.Terminated, result.OrchestrationStatus);
+ Assert.AreEqual(reason, result.Output);
+ }
+
+ [TestMethod]
+ public async Task ForceTerminate_Twice_Terminates_LatestExecution()
+ {
+ var testData = new TestOrchestrationData()
+ {
+ NumberOfParallelTasks = 0,
+ NumberOfSerialTasks = 2,
+ MaxDelay = 5,
+ MinDelay = 5,
+ DelayUnit = TimeSpan.FromSeconds(1),
+ };
+
+ var instance = await this.serviceClient.StartTestOrchestrationAsync(testData);
+ await Task.Delay(TimeSpan.FromSeconds(1));
+
+ var reason = "Testing Terminate (Twice) Functionality";
+ var tasks = new List();
+ tasks.Add(this.serviceClient.TerminateOrchestration(instance.InstanceId, reason));
+ tasks.Add(this.serviceClient.TerminateOrchestration(instance.InstanceId, reason));
+ await Task.WhenAll(tasks);
+ var result = await this.serviceClient.WaitForOrchestration(instance, TimeSpan.FromMinutes(1));
+
+ Assert.AreEqual(OrchestrationStatus.Terminated, result.OrchestrationStatus);
+ Assert.AreEqual(reason, result.Output);
+ }
+
+ [TestMethod]
+ public async Task Purge_Removes_State()
+ {
+ var result = await this.serviceClient.RunOrchestrationAsync(typeof(SimpleOrchestrationWithTasks).Name, null, TimeSpan.FromMinutes(2));
+ Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus);
+
+ var instance = result.OrchestrationInstance;
+
+ await this.serviceClient.PurgeOrchestrationHistoryEventsAsync();
+
+ var state = await this.serviceClient.GetOrchestrationState(instance);
+ Assert.IsNull(state);
+ }
+ }
+}
diff --git a/Test/DurableTask.ServiceFabric.Test/Properties/AssemblyInfo.cs b/Test/DurableTask.ServiceFabric.Test/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..61fc9be86
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.Test/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("DurableTask.ServiceFabric.Test")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("DurableTask.ServiceFabric.Test")]
+[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("edc63ddb-8397-4aec-bf54-6395b2527075")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Test/DurableTask.ServiceFabric.Test/StressTests.cs b/Test/DurableTask.ServiceFabric.Test/StressTests.cs
new file mode 100644
index 000000000..bddd4c53f
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.Test/StressTests.cs
@@ -0,0 +1,257 @@
+// ----------------------------------------------------------------------------------
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+namespace DurableTask.ServiceFabric.Test
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Threading.Tasks;
+ using DurableTask.Core;
+ using DurableTask.Test.Orchestrations.Perf;
+ using Microsoft.ServiceFabric.Services.Client;
+ using Microsoft.ServiceFabric.Services.Remoting.Client;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+ using TestApplication.Common;
+
+ [TestClass]
+ public class StressTests
+ {
+ [TestMethod]
+ public async Task ExecuteStressTest()
+ {
+ var driverConfig = new DriverOrchestrationData()
+ {
+ NumberOfParallelOrchestrations = 40,
+ SubOrchestrationData = new TestOrchestrationData()
+ {
+ NumberOfParallelTasks = 10,
+ NumberOfSerialTasks = 5,
+ MaxDelay = 5,
+ MinDelay = 0,
+ DelayUnit = TimeSpan.FromSeconds(1)
+ }
+ };
+
+ await RunDriverOrchestrationHelper(driverConfig);
+ Console.WriteLine();
+
+ driverConfig.SubOrchestrationData.UseTimeoutTask = true;
+ driverConfig.SubOrchestrationData.ExecutionTimeout = TimeSpan.FromMinutes(2);
+
+ await RunDriverOrchestrationHelper(driverConfig);
+ }
+
+ // This test uses an implementation detail of client - the polling interval in the client for WaitForOrchestration API.
+ // The sleep time is chosen carefully to be close to polling interval to simulate WaitForOrchestration succeeding while
+ // the session could still be there in the store (with a previous implementation which was fixed when the test was introduced).
+ // The test should be modified if the polling interval changes or should be re thought if the polling logic itself changes.
+ // Todo : The above logic did change since the test was initially developed. Need to re-think this test.
+ [TestMethod]
+ public async Task ExecuteSameOrchestrationBackToBack()
+ {
+ int numberOfAttempts = 10; //Try a few times to make sure there is no failure.
+ var instanceId = nameof(ExecuteSameOrchestrationBackToBack);
+ var serviceClient = ServiceProxy.Create(new Uri(Constants.TestFabricApplicationAddress), new ServicePartitionKey(1));
+
+ Dictionary> results = new Dictionary>();
+
+ for (int attemptNumber = 0; attemptNumber < numberOfAttempts; attemptNumber++)
+ {
+ var instance = await serviceClient.StartTestOrchestrationWithInstanceIdAsync(instanceId, new TestOrchestrationData()
+ {
+ NumberOfParallelTasks = 0,
+ NumberOfSerialTasks = 1,
+ MaxDelay = 1980,
+ MinDelay = 1900,
+ DelayUnit = TimeSpan.FromMilliseconds(1),
+ });
+
+ var state = await serviceClient.WaitForOrchestration(instance, TimeSpan.FromMinutes(1));
+ results.Add(attemptNumber, new Tuple(instance, state));
+ }
+
+ for(int attemptNumber = 0; attemptNumber < numberOfAttempts; attemptNumber++)
+ {
+ var instance = results[attemptNumber].Item1;
+ var state = results[attemptNumber].Item2;
+ Assert.IsNotNull(state, $"Attempt number {attemptNumber} with execution id {instance.ExecutionId} Failed with null value for WaitForOrchestration");
+ Assert.AreEqual(OrchestrationStatus.Completed, state.OrchestrationStatus, $"Attempt number {attemptNumber} with execution id {instance.ExecutionId} did not reach 'Completed' state.");
+ Console.WriteLine($"Time for orchestration in attempt number {attemptNumber} : {state.CompletedTime - state.CreatedTime}");
+ }
+ }
+
+ [TestMethod]
+ public async Task Test_Lot_Of_Activities_Per_Orchestration()
+ {
+ var tests = new[] { 256, 1024, 2048, 3500, 4000, 8000, 10000, 15000, 20000 };
+ foreach (var numberOfActivities in tests)
+ {
+ Console.WriteLine($"Begin testing orchestration with {numberOfActivities} parallel activities");
+ var serviceClient = ServiceProxy.Create(new Uri(Constants.TestFabricApplicationAddress), new ServicePartitionKey(1));
+ var state = await serviceClient.RunOrchestrationAsync(typeof(ExecutionCountingOrchestration).Name, numberOfActivities, TimeSpan.FromMinutes(3));
+ Assert.IsNotNull(state);
+ Assert.AreEqual(OrchestrationStatus.Completed, state.OrchestrationStatus);
+ Console.WriteLine($"Time for orchestration {state.OrchestrationInstance.InstanceId} with {numberOfActivities} parallel activities : {state.CompletedTime - state.CreatedTime}");
+ Assert.AreEqual(numberOfActivities.ToString(), state.Output);
+ }
+ }
+
+ [TestMethod]
+ public async Task SimplePerformanceExperiment()
+ {
+ var testOrchestratorInput = new TestOrchestrationData()
+ {
+ NumberOfParallelTasks = 0,
+ NumberOfSerialTasks = 3,
+ MaxDelay = 3,
+ MinDelay = 3,
+ DelayUnit = TimeSpan.FromSeconds(1)
+ };
+
+ await RunTestOrchestrationsHelper(100, testOrchestratorInput);
+ Console.WriteLine();
+
+ testOrchestratorInput.UseTimeoutTask = true;
+ testOrchestratorInput.ExecutionTimeout = TimeSpan.FromMinutes(2);
+
+ await RunTestOrchestrationsHelper(100, testOrchestratorInput);
+ }
+
+ [TestMethod]
+ public async Task RunThousandOrchsWithHundredMilliSecondDelayInBetweenStartingEach()
+ {
+ var testOrchestratorInput = new TestOrchestrationData()
+ {
+ NumberOfParallelTasks = 16,
+ NumberOfSerialTasks = 4,
+ MaxDelay = 500,
+ MinDelay = 50,
+ DelayUnit = TimeSpan.FromMilliseconds(1)
+ };
+
+ await RunTestOrchestrationsHelper(1000, testOrchestratorInput, delayGeneratorFunction: (totalRequestsSoFar) => TimeSpan.FromMilliseconds(100));
+ Console.WriteLine();
+
+ testOrchestratorInput.UseTimeoutTask = true;
+ testOrchestratorInput.ExecutionTimeout = TimeSpan.FromMinutes(2);
+
+ await RunTestOrchestrationsHelper(1000, testOrchestratorInput, delayGeneratorFunction: (totalRequestsSoFar) => TimeSpan.FromMilliseconds(100));
+ }
+
+ [Ignore] //Long running test and not too interesting anyway. (The test is passing - enable as needed to test and ensure).
+ [TestMethod]
+ public async Task InfrequentLoadScenario()
+ {
+ var testOrchestratorInput = new TestOrchestrationData()
+ {
+ NumberOfParallelTasks = 0,
+ NumberOfSerialTasks = 2,
+ MaxDelay = 0,
+ MinDelay = 0,
+ };
+
+ await RunTestOrchestrationsHelper(200, testOrchestratorInput, delayGeneratorFunction: (totalRequestsSoFar) => TimeSpan.FromSeconds(1));
+ Console.WriteLine();
+
+ testOrchestratorInput.UseTimeoutTask = true;
+ testOrchestratorInput.ExecutionTimeout = TimeSpan.FromMinutes(2);
+
+ await RunTestOrchestrationsHelper(200, testOrchestratorInput, delayGeneratorFunction: (totalRequestsSoFar) => TimeSpan.FromSeconds(1));
+ }
+
+ async Task RunDriverOrchestrationHelper(DriverOrchestrationData driverConfig)
+ {
+ var serviceClient = ServiceProxy.Create(new Uri(Constants.TestFabricApplicationAddress), new ServicePartitionKey(1));
+ Console.WriteLine($"Orchestration getting scheduled: {DateTime.Now}");
+
+ Stopwatch stopWatch = Stopwatch.StartNew();
+ var state = await serviceClient.RunDriverOrchestrationAsync(driverConfig, TimeSpan.FromMinutes(5));
+ stopWatch.Stop();
+
+ Console.WriteLine($"Orchestration Status: {state.OrchestrationStatus}");
+ Console.WriteLine($"Orchestration Result: {state.Output}");
+
+ TimeSpan totalTime = stopWatch.Elapsed;
+ TimeSpan orchestrationTime = state.CompletedTime - state.CreatedTime;
+
+ Func elapsedTimeFormatter = timeSpan => $"{timeSpan.Hours:00}:{timeSpan.Minutes:00}:{timeSpan.Seconds:00}.{timeSpan.Milliseconds / 10:00}";
+ Console.WriteLine($"Total Meastured Time: {elapsedTimeFormatter(totalTime)}");
+ Console.WriteLine($"Top level Orchestration Time: {elapsedTimeFormatter(orchestrationTime)}");
+
+ var expectedResult = driverConfig.NumberOfParallelOrchestrations * (driverConfig.SubOrchestrationData.NumberOfParallelTasks + driverConfig.SubOrchestrationData.NumberOfSerialTasks);
+
+ Assert.AreEqual(expectedResult.ToString(), state.Output);
+ }
+
+ async Task RunTestOrchestrationsHelper(int numberOfInstances, TestOrchestrationData orchestrationInput, Func delayGeneratorFunction = null)
+ {
+ var serviceClient = ServiceProxy.Create(new Uri(Constants.TestFabricApplicationAddress), new ServicePartitionKey(1));
+
+ List> results = new List>();
+ List waitTasks = new List();
+ Stopwatch stopWatch = Stopwatch.StartNew();
+ for (int i = 0; i < numberOfInstances; i++)
+ {
+ var instance = await serviceClient.StartTestOrchestrationAsync(orchestrationInput);
+ waitTasks.Add(Task.Run(async () =>
+ {
+ var state = await serviceClient.WaitForOrchestration(instance, TimeSpan.FromMinutes(2));
+ results.Add(Tuple.Create(instance, state));
+ }));
+ if (delayGeneratorFunction != null)
+ {
+ await Task.Delay(delayGeneratorFunction(i));
+ }
+ }
+
+ await Task.WhenAll(waitTasks);
+ stopWatch.Stop();
+
+ Func elapsedTimeFormatter = timeSpan => $"{timeSpan.Hours:00}:{timeSpan.Minutes:00}:{timeSpan.Seconds:00}.{timeSpan.Milliseconds / 10:00}";
+ Console.WriteLine($"Total Meastured Time For All Orchestrations: {elapsedTimeFormatter(stopWatch.Elapsed)}");
+
+ var expectedResult = (orchestrationInput.NumberOfParallelTasks + orchestrationInput.NumberOfSerialTasks).ToString();
+ TimeSpan minTime = TimeSpan.MaxValue;
+ TimeSpan maxTime = TimeSpan.FromMilliseconds(0);
+
+ int failedOrchestrations = 0;
+ foreach (var kvp in results)
+ {
+ if (kvp.Item2 == null)
+ {
+ failedOrchestrations++;
+ var state = await serviceClient.GetOrchestrationState(kvp.Item1);
+ Console.WriteLine($"Unfinished orchestration {kvp.Item1}, state : {kvp.Item2?.OrchestrationStatus}");
+ continue;
+ }
+
+ Assert.AreEqual(expectedResult, kvp.Item2.Output, $"Unexpected output for Orchestration : {kvp.Item1.InstanceId}");
+ TimeSpan orchestrationTime = kvp.Item2.CompletedTime - kvp.Item2.CreatedTime;
+ if (minTime > orchestrationTime)
+ {
+ minTime = orchestrationTime;
+ }
+ if (maxTime < orchestrationTime)
+ {
+ maxTime = orchestrationTime;
+ }
+ }
+
+ Console.WriteLine($"Minimum time taken for any orchestration instance : {elapsedTimeFormatter(minTime)}");
+ Console.WriteLine($"Maximum time taken for any orchestration instance : {elapsedTimeFormatter(maxTime)}");
+
+ Assert.AreEqual(0, failedOrchestrations, $"{failedOrchestrations} orchestrations failed out of {numberOfInstances}.");
+ }
+ }
+}
diff --git a/Test/DurableTask.ServiceFabric.Test/Utilities.cs b/Test/DurableTask.ServiceFabric.Test/Utilities.cs
new file mode 100644
index 000000000..ccfad3fe7
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.Test/Utilities.cs
@@ -0,0 +1,58 @@
+// ----------------------------------------------------------------------------------
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+namespace DurableTask.ServiceFabric.Test
+{
+ using System;
+ using System.Diagnostics;
+ using System.Threading.Tasks;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ public static class Utilities
+ {
+ public static async Task ThrowsException(Func action, string expectedMessage) where TException : Exception
+ {
+ try
+ {
+ await action();
+ Assert.Fail($"Method {action.Method} did not throw the expected exception of type {typeof(TException).Name}");
+ }
+ catch (Exception ex)
+ {
+ AggregateException aggregate = ex as AggregateException;
+ if (aggregate != null)
+ {
+ ex = aggregate.InnerException;
+ }
+
+ TException expected = ex as TException;
+ if (expected == null)
+ {
+ Assert.Fail($"Method {action.Method} is expected to throw exception of type {typeof(TException).Name} but has thrown {ex.GetType().Name} instead.");
+ }
+ else if (!string.Equals(expected.Message, expectedMessage, StringComparison.Ordinal))
+ {
+ Assert.Fail($"Method {action.Method} is expected to throw exception with message '{expectedMessage}' but has thrown the message '{expected.Message}' instead.");
+ }
+ }
+ }
+
+ public static async Task MeasureAsync(Func asyncAction)
+ {
+ var timer = Stopwatch.StartNew();
+ await asyncAction();
+ timer.Stop();
+ return timer.Elapsed;
+ }
+ }
+}
diff --git a/Test/DurableTask.ServiceFabric.Test/WaitForOrchestrationTests.cs b/Test/DurableTask.ServiceFabric.Test/WaitForOrchestrationTests.cs
new file mode 100644
index 000000000..29ac3f2cc
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.Test/WaitForOrchestrationTests.cs
@@ -0,0 +1,147 @@
+// ----------------------------------------------------------------------------------
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+namespace DurableTask.ServiceFabric.Test
+{
+ using System;
+ using System.Threading.Tasks;
+ using DurableTask.Core;
+ using DurableTask.Test.Orchestrations.Perf;
+ using Microsoft.ServiceFabric.Services.Client;
+ using Microsoft.ServiceFabric.Services.Remoting.Client;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+ using TestApplication.Common;
+
+ [TestClass]
+ public class WaitForOrchestrationTests
+ {
+ IRemoteClient serviceClient;
+
+ [TestInitialize]
+ public void TestInitialize()
+ {
+ this.serviceClient = ServiceProxy.Create(new Uri(Constants.TestFabricApplicationAddress), new ServicePartitionKey(1));
+ }
+
+ [TestMethod]
+ public async Task WaitForOrchestration_Returns_Null_When_Orchestration_Not_Started()
+ {
+ var instance = new OrchestrationInstance() { InstanceId = "NonExisting", ExecutionId = "NonExisting" };
+ OrchestrationState result = null;
+ var waitTime = TimeSpan.FromMinutes(1);
+ var time = await Utilities.MeasureAsync(async () =>
+ {
+ result = await this.serviceClient.WaitForOrchestration(instance, waitTime);
+ });
+
+ Assert.IsNull(result);
+ Assert.IsTrue(time < waitTime);
+ Console.WriteLine($"Full WaitTime : {waitTime}, Actual time taken for Wait {time}");
+ }
+
+ [TestMethod]
+ public async Task WaitForOrchestration_Returns_State_When_Orchestration_Already_Finished()
+ {
+ var instance = await this.serviceClient.StartTestOrchestrationAsync(new TestOrchestrationData()
+ {
+ NumberOfParallelTasks = 0,
+ NumberOfSerialTasks = 1,
+ MaxDelay = 0,
+ MinDelay = 0,
+ DelayUnit = TimeSpan.FromMilliseconds(1),
+ UseTimeoutTask = false,
+ });
+ await this.serviceClient.WaitForOrchestration(instance, TimeSpan.FromMinutes(1)); //Wait till orchestration is finished
+
+ var waitTime = TimeSpan.FromMilliseconds(100); //Let this be very low for the test to be effective.
+ OrchestrationState result = null;
+ var time = await Utilities.MeasureAsync(async () =>
+ {
+ result = await this.serviceClient.WaitForOrchestration(instance, waitTime);
+ });
+
+ Assert.IsNotNull(result);
+ Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus);
+ Assert.IsTrue(time < waitTime);
+ Console.WriteLine($"Full WaitTime : {waitTime}, Actual time taken for Wait {time}, Actual Time taken for Orchestration : {result.CompletedTime - result.CreatedTime}");
+ }
+
+ [TestMethod]
+ public async Task WaitForOrchestration_Waits_And_Returns_State_When_Orchestration_IsRunning()
+ {
+ var waitTime = TimeSpan.FromMinutes(1);
+ var orchestrationTimeInSeconds = 5;
+ var instance = await this.serviceClient.StartTestOrchestrationAsync(new TestOrchestrationData()
+ {
+ NumberOfParallelTasks = 0,
+ NumberOfSerialTasks = 1,
+ MaxDelay = orchestrationTimeInSeconds,
+ MinDelay = orchestrationTimeInSeconds,
+ DelayUnit = TimeSpan.FromSeconds(1),
+ UseTimeoutTask = false,
+ });
+
+ OrchestrationState result = null;
+ var time = await Utilities.MeasureAsync(async () =>
+ {
+ result = await this.serviceClient.WaitForOrchestration(instance, waitTime);
+ });
+
+ Assert.IsNotNull(result);
+ Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus);
+ Assert.IsTrue(time < waitTime);
+ Assert.IsTrue(time > TimeSpan.FromSeconds(orchestrationTimeInSeconds));
+ Console.WriteLine($"Full WaitTime : {waitTime}, Actual time taken for Wait : {time}, Expected Minimum time for orchestration : {orchestrationTimeInSeconds} seconds, Actual Time taken for Orchestration : {result.CompletedTime - result.CreatedTime}");
+ }
+
+ [TestMethod]
+ public async Task WaitForOrchestration_TimesOut_And_Returns_Null_When_Orchestration_DidNotFinishYet()
+ {
+ var initialwaitTimeInSeconds = 2;
+ var orchestrationTimeInSeconds = 5;
+ var waitTime = TimeSpan.FromSeconds(initialwaitTimeInSeconds);
+ var instance = await this.serviceClient.StartTestOrchestrationAsync(new TestOrchestrationData()
+ {
+ NumberOfParallelTasks = 0,
+ NumberOfSerialTasks = 1,
+ MaxDelay = orchestrationTimeInSeconds,
+ MinDelay = orchestrationTimeInSeconds,
+ DelayUnit = TimeSpan.FromSeconds(1),
+ UseTimeoutTask = false,
+ });
+
+ OrchestrationState result = null;
+ var time = await Utilities.MeasureAsync(async () =>
+ {
+ result = await this.serviceClient.WaitForOrchestration(instance, waitTime);
+ });
+
+ Assert.IsNull(result);
+ Assert.IsTrue(time > waitTime);
+ Assert.IsTrue(time < TimeSpan.FromSeconds(orchestrationTimeInSeconds));
+ Console.WriteLine($"Full WaitTime : {waitTime}, Actual time taken for Wait : {time}, Expected Orchestration Running Time : {orchestrationTimeInSeconds} seconds");
+
+ var newWaitTime = TimeSpan.FromMinutes(1);
+ time = await Utilities.MeasureAsync(async () =>
+ {
+ result = await this.serviceClient.WaitForOrchestration(instance, newWaitTime);
+ });
+
+ Assert.IsNotNull(result);
+ Assert.IsTrue(time < newWaitTime);
+ Assert.IsTrue(time > TimeSpan.FromSeconds(orchestrationTimeInSeconds - initialwaitTimeInSeconds)); //Theoritically, this could be flakey. The right way is to check for the difference being within a small internval, adjust this if needed based on the output below.
+ Assert.AreEqual(OrchestrationStatus.Completed, result.OrchestrationStatus);
+ Console.WriteLine($"Full WaitTime : {newWaitTime}, Actual time taken for Wait : {time}, Time taken for Orchestration : {result.CompletedTime - result.CreatedTime}");
+ }
+ }
+}
diff --git a/Test/DurableTask.ServiceFabric.Test/app.config b/Test/DurableTask.ServiceFabric.Test/app.config
new file mode 100644
index 000000000..534e63b33
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.Test/app.config
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Test/DurableTask.ServiceFabric.Test/packages.config b/Test/DurableTask.ServiceFabric.Test/packages.config
new file mode 100644
index 000000000..e1a44ae1b
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.Test/packages.config
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Test/DurableTask.ServiceFabric.Tests.Tooling/DurableTask.ServiceFabric.Tests.Tooling.csproj b/Test/DurableTask.ServiceFabric.Tests.Tooling/DurableTask.ServiceFabric.Tests.Tooling.csproj
new file mode 100644
index 000000000..9cca2b322
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.Tests.Tooling/DurableTask.ServiceFabric.Tests.Tooling.csproj
@@ -0,0 +1,103 @@
+
+
+
+
+
+ Debug
+ AnyCPU
+ {ED6817FD-A301-40CA-B287-1C40E7AAC7D1}
+ Exe
+ DurableTask.ServiceFabric.Tests.Tooling
+ DurableTask.ServiceFabric.Tests.Tooling
+ v4.5.1
+ 512
+ true
+
+
+
+ x64
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ x64
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\..\packages\Microsoft.ServiceFabric.Data.2.5.216\lib\net45\Microsoft.ServiceFabric.Data.dll
+
+
+ ..\..\packages\Microsoft.ServiceFabric.Data.2.5.216\lib\net45\Microsoft.ServiceFabric.Data.Interfaces.dll
+ True
+
+
+ ..\..\packages\Microsoft.ServiceFabric.FabricTransport.Internal.2.5.216\lib\net45\Microsoft.ServiceFabric.FabricTransport.dll
+
+
+ ..\..\packages\Microsoft.ServiceFabric.5.5.216\lib\net45\Microsoft.ServiceFabric.Internal.dll
+
+
+ ..\..\packages\Microsoft.ServiceFabric.5.5.216\lib\net45\Microsoft.ServiceFabric.Internal.Strings.dll
+
+
+ ..\..\packages\Microsoft.ServiceFabric.Services.2.5.216\lib\net45\Microsoft.ServiceFabric.Services.dll
+
+
+ ..\..\packages\Microsoft.ServiceFabric.Services.Remoting.2.5.216\lib\net45\Microsoft.ServiceFabric.Services.Remoting.dll
+
+
+
+
+ ..\..\packages\Microsoft.ServiceFabric.5.5.216\lib\net45\System.Fabric.dll
+ True
+
+
+ ..\..\packages\Microsoft.ServiceFabric.5.5.216\lib\net45\System.Fabric.Management.ServiceModel.dll
+
+
+ ..\..\packages\Microsoft.ServiceFabric.5.5.216\lib\net45\System.Fabric.Management.ServiceModel.XmlSerializers.dll
+
+
+ ..\..\packages\Microsoft.ServiceFabric.5.5.216\lib\net45\System.Fabric.Strings.dll
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {6B386E8F-20EB-43DE-BBD7-6408A232B4BA}
+ DurableTask.Core
+
+
+ {ea90334d-3194-4266-81eb-eeca9c760315}
+ TestApplication.Common
+
+
+
+
\ No newline at end of file
diff --git a/Test/DurableTask.ServiceFabric.Tests.Tooling/Program.cs b/Test/DurableTask.ServiceFabric.Tests.Tooling/Program.cs
new file mode 100644
index 000000000..e6e21f7c2
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.Tests.Tooling/Program.cs
@@ -0,0 +1,108 @@
+// ----------------------------------------------------------------------------------
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+namespace DurableTask.ServiceFabric.Tests.Tooling
+{
+ using System;
+ using System.Threading.Tasks;
+ using DurableTask.Core;
+ using Microsoft.ServiceFabric.Services.Client;
+ using Microsoft.ServiceFabric.Services.Remoting.Client;
+ using TestApplication.Common;
+
+ class Program
+ {
+ static IRemoteClient serviceClient = ServiceProxy.Create(new Uri("fabric:/TestFabricApplication/TestStatefulService"), new ServicePartitionKey(1));
+
+ static void Main(string[] args)
+ {
+ MainAsync().Wait();
+ }
+
+ static void PrintMenu()
+ {
+ Console.WriteLine("-----------------------------------");
+ Console.WriteLine(" 1. List Orchestrations");
+ Console.WriteLine(" 2. Get runtime state");
+ Console.WriteLine(" 3. Terminate instance");
+ Console.WriteLine("-----------------------------------");
+ }
+
+ static string Prompt(string message)
+ {
+ Console.WriteLine(message);
+ return Console.ReadLine();
+ }
+
+ static async Task MainAsync()
+ {
+ while (true)
+ {
+ try
+ {
+ PrintMenu();
+ var action = Prompt("Type an option or hit enter to exit");
+
+ switch (action)
+ {
+ case "1":
+ await ListOrchestrations();
+ break;
+ case "2":
+ await GetOrchestrationRuntimeState();
+ break;
+ case "3":
+ await TerminateOrchestration();
+ break;
+ default:
+ return;
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Unhandled exception caught from the operation : {ex}");
+ }
+ }
+ }
+
+ private static async Task TerminateOrchestration()
+ {
+ var instanceId = Prompt("InstanceId?");
+ var executionId = Prompt("ExecutionId?");
+ var instance = new OrchestrationInstance() { InstanceId = instanceId, ExecutionId = executionId };
+ await serviceClient.TerminateOrchestration(instanceId, executionId);
+ var state = await serviceClient.WaitForOrchestration(instance, TimeSpan.FromMinutes(1));
+ if (state == null)
+ {
+ Console.WriteLine("Orchestration did not complete within 1 minute after termination, trying to get current state");
+ state = await serviceClient.GetOrchestrationState(instance);
+ Console.WriteLine($"Current status : {state?.OrchestrationStatus}");
+ }
+ }
+
+ private static async Task GetOrchestrationRuntimeState()
+ {
+ var instanceId = Prompt("InstanceId?");
+ var state = await serviceClient.GetOrchestrationRuntimeState(instanceId);
+ Console.WriteLine(state);
+ }
+
+ private static async Task ListOrchestrations()
+ {
+ foreach (var instance in await serviceClient.GetRunningOrchestrations())
+ {
+ Console.WriteLine(instance);
+ }
+ }
+ }
+}
diff --git a/Test/DurableTask.ServiceFabric.Tests.Tooling/Properties/AssemblyInfo.cs b/Test/DurableTask.ServiceFabric.Tests.Tooling/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..54cd56a06
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.Tests.Tooling/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("DurableTask.ServiceFabric.Tests.Tooling")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("DurableTask.ServiceFabric.Tests.Tooling")]
+[assembly: AssemblyCopyright("Copyright © 2017")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("ed6817fd-a301-40ca-b287-1c40e7aac7d1")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Test/DurableTask.ServiceFabric.Tests.Tooling/app.config b/Test/DurableTask.ServiceFabric.Tests.Tooling/app.config
new file mode 100644
index 000000000..884f9844f
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.Tests.Tooling/app.config
@@ -0,0 +1,3 @@
+
+
+
diff --git a/Test/DurableTask.ServiceFabric.Tests.Tooling/packages.config b/Test/DurableTask.ServiceFabric.Tests.Tooling/packages.config
new file mode 100644
index 000000000..4a980a8a5
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.Tests.Tooling/packages.config
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Test/DurableTask.ServiceFabric.UnitTests/CountBasedFixedDelayRetryPolicyTests.cs b/Test/DurableTask.ServiceFabric.UnitTests/CountBasedFixedDelayRetryPolicyTests.cs
new file mode 100644
index 000000000..3856a91c0
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.UnitTests/CountBasedFixedDelayRetryPolicyTests.cs
@@ -0,0 +1,65 @@
+// ----------------------------------------------------------------------------------
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+namespace DurableTask.ServiceFabric.UnitTests
+{
+ using System;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class CountBasedFixedDelayRetryPolicyTests
+ {
+ [TestMethod]
+ public void CountBasedFixedDelayRetry_Attempts_MaxCount_Times()
+ {
+ var testRetryPolicy = new CountBasedFixedDelayRetryPolicy(3, TimeSpan.Zero);
+ int attempts = 0;
+ while (testRetryPolicy.ShouldExecute())
+ {
+ attempts++;
+ }
+ Assert.AreEqual(3, attempts);
+ }
+
+ [TestMethod]
+ public void CountBasedFixedDelayRetry_Last_Retry_Delay_Is_Zero()
+ {
+ var testRetryPolicy = new CountBasedFixedDelayRetryPolicy(1, TimeSpan.FromSeconds(1));
+ TimeSpan lastDelay = TimeSpan.MaxValue;
+ while (testRetryPolicy.ShouldExecute())
+ {
+ lastDelay = testRetryPolicy.GetNextDelay();
+ }
+ Assert.AreEqual(TimeSpan.Zero, lastDelay);
+ }
+
+ [TestMethod]
+ public void CountBasedFixedDelayRetry_Non_Last_Retry_Delay_Is_Fixed()
+ {
+ TimeSpan fixedDelay = TimeSpan.FromSeconds(23);
+ var testRetryPolicy = new CountBasedFixedDelayRetryPolicy(3, fixedDelay);
+ int attemptNumber = 1;
+ TimeSpan nextDelay = TimeSpan.MaxValue;
+ while (testRetryPolicy.ShouldExecute())
+ {
+ nextDelay = testRetryPolicy.GetNextDelay();
+ if (attemptNumber < 3)
+ {
+ Assert.AreEqual(fixedDelay, nextDelay);
+ }
+ attemptNumber++;
+ }
+ Assert.AreEqual(TimeSpan.Zero, nextDelay);
+ }
+ }
+}
diff --git a/Test/DurableTask.ServiceFabric.UnitTests/DurableTask.ServiceFabric.UnitTests.csproj b/Test/DurableTask.ServiceFabric.UnitTests/DurableTask.ServiceFabric.UnitTests.csproj
new file mode 100644
index 000000000..be7bec0a9
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.UnitTests/DurableTask.ServiceFabric.UnitTests.csproj
@@ -0,0 +1,90 @@
+
+
+
+
+
+ Debug
+ AnyCPU
+ {31C9C932-6376-46D4-A68A-A0A7F2726D38}
+ Library
+ Properties
+ DurableTask.ServiceFabric.UnitTests
+ DurableTask.ServiceFabric.UnitTests
+ 512
+ v4.5.1
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ false
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ false
+
+
+
+
+ False
+ ..\..\packages\System.Collections.Immutable.1.5.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {6B386E8F-20EB-43DE-BBD7-6408A232B4BA}
+ DurableTask.Core
+
+
+ {5c617b24-4902-4992-8f71-ab14f00b74a8}
+ DurableTask.ServiceFabric
+
+
+
+
+
+ Designer
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Test/DurableTask.ServiceFabric.UnitTests/Measure.cs b/Test/DurableTask.ServiceFabric.UnitTests/Measure.cs
new file mode 100644
index 000000000..b8558834b
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.UnitTests/Measure.cs
@@ -0,0 +1,53 @@
+// ----------------------------------------------------------------------------------
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+namespace DurableTask.ServiceFabric.UnitTests
+{
+ using System;
+ using System.Diagnostics;
+ using System.IO;
+ using System.Runtime.Serialization;
+
+ class Measure
+ {
+ public static T DataContractSerialization(T testObject)
+ {
+ return (T)DataContractSerialization(typeof(T), testObject);
+ }
+
+ public static object DataContractSerialization(Type testType, object testObject)
+ {
+ using (var stream = new MemoryStream())
+ {
+ var serializer = new DataContractSerializer(testType);
+ var time = MeasureTime(() => serializer.WriteObject(stream, testObject));
+ Console.WriteLine($"Time for serialization : {time.TotalMilliseconds} ms");
+ Console.WriteLine($"Size of serialized stream : {stream.Length} bytes.");
+
+ stream.Position = 0;
+ object deserialized = null;
+ time = MeasureTime(() => { deserialized = serializer.ReadObject(stream); });
+ Console.WriteLine($"Time for deserialization : {time.TotalMilliseconds} ms");
+ return deserialized;
+ }
+ }
+
+ public static TimeSpan MeasureTime(Action action)
+ {
+ Stopwatch timer = Stopwatch.StartNew();
+ action();
+ timer.Stop();
+ return timer.Elapsed;
+ }
+ }
+}
diff --git a/Test/DurableTask.ServiceFabric.UnitTests/OrchestrationInstanceComparerTests.cs b/Test/DurableTask.ServiceFabric.UnitTests/OrchestrationInstanceComparerTests.cs
new file mode 100644
index 000000000..307b22a76
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.UnitTests/OrchestrationInstanceComparerTests.cs
@@ -0,0 +1,85 @@
+// ----------------------------------------------------------------------------------
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+namespace DurableTask.ServiceFabric.UnitTests
+{
+ using DurableTask.Core;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class OrchestrationInstanceComparerTests
+ {
+ [TestMethod]
+ public void OrchestrationInstanceComparer_NullInstanceTests()
+ {
+ Assert.IsTrue(OrchestrationInstanceComparer.Default.Equals(null, null));
+ Assert.IsFalse(OrchestrationInstanceComparer.Default.Equals(null, new OrchestrationInstance()));
+ Assert.IsFalse(OrchestrationInstanceComparer.Default.Equals(new OrchestrationInstance(), null));
+ }
+
+ [TestMethod]
+ public void OrchestrationInstanceComparer_SameOrchestrationInstances()
+ {
+ var first = new OrchestrationInstance()
+ {
+ InstanceId = "InstanceId",
+ ExecutionId = "ExecutionId"
+ };
+
+ var second = new OrchestrationInstance()
+ {
+ InstanceId = "InstanceId",
+ ExecutionId = "ExecutionId"
+ };
+
+ Assert.IsTrue(OrchestrationInstanceComparer.Default.Equals(first, first));
+ Assert.IsTrue(OrchestrationInstanceComparer.Default.Equals(first, second));
+ }
+
+ [TestMethod]
+ public void OrchestrationInstanceComparer_DifferentExecutionIdInstances()
+ {
+ var first = new OrchestrationInstance()
+ {
+ InstanceId = "InstanceId",
+ ExecutionId = "ExecutionId1"
+ };
+
+ var second = new OrchestrationInstance()
+ {
+ InstanceId = "InstanceId",
+ ExecutionId = "ExecutionId2"
+ };
+
+ Assert.IsFalse(OrchestrationInstanceComparer.Default.Equals(first, second));
+ }
+
+ [TestMethod]
+ public void OrchestrationInstanceComparer_DifferentOrchestrationInstances()
+ {
+ var first = new OrchestrationInstance()
+ {
+ InstanceId = "InstanceId1",
+ ExecutionId = "ExecutionId1"
+ };
+
+ var second = new OrchestrationInstance()
+ {
+ InstanceId = "InstanceId2",
+ ExecutionId = "ExecutionId2"
+ };
+
+ Assert.IsFalse(OrchestrationInstanceComparer.Default.Equals(first, second));
+ }
+ }
+}
diff --git a/Test/DurableTask.ServiceFabric.UnitTests/PersistentSessionTests.cs b/Test/DurableTask.ServiceFabric.UnitTests/PersistentSessionTests.cs
new file mode 100644
index 000000000..bdc74243e
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.UnitTests/PersistentSessionTests.cs
@@ -0,0 +1,56 @@
+// ----------------------------------------------------------------------------------
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+namespace DurableTask.ServiceFabric.UnitTests
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.Immutable;
+ using DurableTask.Core;
+ using DurableTask.Core.History;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class PersistentSessionTests
+ {
+ [TestMethod]
+ public void PersistentSession_SerializationTest()
+ {
+ int numberOfHistoryEvents = 256;
+
+ var events = new List();
+ events.Add(new ExecutionStartedEvent(-1, "TestInput"));
+ for (int i = 0; i < numberOfHistoryEvents; i++)
+ {
+ events.Add(new TaskScheduledEvent(-1));
+ events.Add(new TaskCompletedEvent(-1, -1, $"Task {i} Result"));
+ }
+ events.Add(new ExecutionCompletedEvent(-1, "FinalResult", OrchestrationStatus.Completed));
+
+ var instance = new OrchestrationInstance()
+ {
+ InstanceId = "testSession",
+ ExecutionId = Guid.NewGuid().ToString("N")
+ };
+
+ PersistentSession testSession = PersistentSession.Create(instance, events.ToImmutableList());
+
+ var actual = Measure.DataContractSerialization(testSession);
+
+ Assert.IsNotNull(actual);
+ Assert.AreEqual(instance.InstanceId, actual.SessionId.InstanceId);
+ Assert.AreEqual(instance.ExecutionId, actual.SessionId.ExecutionId);
+ Assert.AreEqual(numberOfHistoryEvents * 2 + 2, actual.SessionState.Count);
+ }
+ }
+}
diff --git a/Test/DurableTask.ServiceFabric.UnitTests/Properties/AssemblyInfo.cs b/Test/DurableTask.ServiceFabric.UnitTests/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..e73ff9a61
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.UnitTests/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("DurableTask.ServiceFabric.UnitTests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("DurableTask.ServiceFabric.UnitTests")]
+[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("31c9c932-6376-46d4-a68a-a0a7f2726d38")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Test/DurableTask.ServiceFabric.UnitTests/SerializationPerformanceExperiments.cs b/Test/DurableTask.ServiceFabric.UnitTests/SerializationPerformanceExperiments.cs
new file mode 100644
index 000000000..df2bc5e17
--- /dev/null
+++ b/Test/DurableTask.ServiceFabric.UnitTests/SerializationPerformanceExperiments.cs
@@ -0,0 +1,65 @@
+// ----------------------------------------------------------------------------------
+// Copyright Microsoft Corporation
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------------
+
+namespace DurableTask.ServiceFabric.UnitTests
+{
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Runtime.Serialization;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class SerializationPerformanceExperiments
+ {
+ [TestMethod]
+ public void DataContractSerializationSizeExperiment_PrimitiveTypes()
+ {
+ var testObjects = new List