diff --git a/Akka.Persistence.SqlServer.sln b/Akka.Persistence.SqlServer.sln
index 3675782..abdfbb2 100644
--- a/Akka.Persistence.SqlServer.sln
+++ b/Akka.Persistence.SqlServer.sln
@@ -24,6 +24,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Akka.Persistence.SqlServer"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Akka.Persistence.SqlServer.Tests", "src\Akka.Persistence.SqlServer.Tests\Akka.Persistence.SqlServer.Tests.csproj", "{DEAC600E-5B71-4124-BE4B-5BF26C21575E}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.Persistence.SqlServer.Performance.Tests", "src\Akka.Persistence.SqlServer.Performance.Tests\Akka.Persistence.SqlServer.Performance.Tests.csproj", "{5B792D05-2CCE-4513-BAAD-100394E9E161}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -38,6 +40,10 @@ Global
{DEAC600E-5B71-4124-BE4B-5BF26C21575E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DEAC600E-5B71-4124-BE4B-5BF26C21575E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DEAC600E-5B71-4124-BE4B-5BF26C21575E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5B792D05-2CCE-4513-BAAD-100394E9E161}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5B792D05-2CCE-4513-BAAD-100394E9E161}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5B792D05-2CCE-4513-BAAD-100394E9E161}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5B792D05-2CCE-4513-BAAD-100394E9E161}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/build.fsx b/build.fsx
index 94bec65..7db2f42 100644
--- a/build.fsx
+++ b/build.fsx
@@ -95,7 +95,9 @@ Target "RunTests" (fun _ ->
let projects =
match (isWindows) with
| true -> !! "./src/**/*.Tests.csproj"
+ -- "./src/**/*.Performance.Tests.csproj"
| _ -> !! "./src/**/*.Tests.csproj" // if you need to filter specs for Linux vs. Windows, do it here
+ -- "./src/**/*.Performance.Tests.csproj"
let runSingleProject project =
let arguments =
diff --git a/src/Akka.Persistence.SqlServer.Performance.Tests/Akka.Persistence.SqlServer.Performance.Tests.csproj b/src/Akka.Persistence.SqlServer.Performance.Tests/Akka.Persistence.SqlServer.Performance.Tests.csproj
new file mode 100644
index 0000000..4b7efaa
--- /dev/null
+++ b/src/Akka.Persistence.SqlServer.Performance.Tests/Akka.Persistence.SqlServer.Performance.Tests.csproj
@@ -0,0 +1,25 @@
+
+
+ $(NetFrameworkTestVersion);$(NetCoreTestVersion)
+ false
+
+
+
+
+ $(NetCoreTestVersion)
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Akka.Persistence.SqlServer.Tests/Batching/BatchingSqlServerJournalPerfSpec.cs b/src/Akka.Persistence.SqlServer.Performance.Tests/BatchingSqlServerJournalPerfSpec.cs
similarity index 97%
rename from src/Akka.Persistence.SqlServer.Tests/Batching/BatchingSqlServerJournalPerfSpec.cs
rename to src/Akka.Persistence.SqlServer.Performance.Tests/BatchingSqlServerJournalPerfSpec.cs
index 170a3f9..febe5c4 100644
--- a/src/Akka.Persistence.SqlServer.Tests/Batching/BatchingSqlServerJournalPerfSpec.cs
+++ b/src/Akka.Persistence.SqlServer.Performance.Tests/BatchingSqlServerJournalPerfSpec.cs
@@ -10,7 +10,7 @@
using Xunit;
using Xunit.Abstractions;
-namespace Akka.Persistence.SqlServer.Tests
+namespace Akka.Persistence.SqlServer.Performance.Tests
{
[Collection("SqlServerSpec")]
public class BatchingSqlServerJournalPerfSpec : JournalPerfSpec, IDisposable
diff --git a/src/Akka.Persistence.SqlServer.Performance.Tests/DbUtils.cs b/src/Akka.Persistence.SqlServer.Performance.Tests/DbUtils.cs
new file mode 100644
index 0000000..537e9ce
--- /dev/null
+++ b/src/Akka.Persistence.SqlServer.Performance.Tests/DbUtils.cs
@@ -0,0 +1,84 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2013 - 2023 .NET Foundation
+//
+// -----------------------------------------------------------------------
+
+using System;
+using System.IO;
+using Microsoft.Data.SqlClient;
+
+namespace Akka.Persistence.SqlServer.Performance.Tests
+{
+ public static class DbUtils
+ {
+ private static SqlConnectionStringBuilder _builder;
+ public static string ConnectionString => _builder.ToString();
+
+ public static void Initialize(string connectionString)
+ {
+ _builder = new SqlConnectionStringBuilder(connectionString);
+ var databaseName = $"akka_persistence_tests_{Guid.NewGuid()}";
+ _builder.InitialCatalog = databaseName;
+
+ var connectionBuilder = new SqlConnectionStringBuilder(connectionString)
+ {
+ InitialCatalog = "master"
+ };
+
+ using (var conn = new SqlConnection(connectionBuilder.ToString()))
+ {
+ conn.Open();
+
+ using (var cmd = new SqlCommand())
+ {
+ cmd.CommandText = $@"
+IF db_id('{databaseName}') IS NULL
+BEGIN
+ CREATE DATABASE [{databaseName}];
+END";
+ cmd.Connection = conn;
+ cmd.ExecuteScalar();
+ }
+ }
+
+ // Delete local snapshot flat file database
+ var path = "./snapshots";
+ if (Directory.Exists(path))
+ Directory.Delete(path, true);
+ }
+
+ public static void Clean()
+ {
+ var databaseName = $"akka_persistence_tests_{Guid.NewGuid()}";
+ _builder.InitialCatalog = databaseName;
+
+ var connectionBuilder = new SqlConnectionStringBuilder(ConnectionString)
+ {
+ InitialCatalog = "master"
+ };
+
+ using (var conn = new SqlConnection(connectionBuilder.ToString()))
+ {
+ conn.Open();
+
+ using (var cmd = new SqlCommand())
+ {
+ cmd.CommandText = $@"
+IF db_id('{databaseName}') IS NULL
+BEGIN
+ CREATE DATABASE [{databaseName}];
+END
+";
+ cmd.Connection = conn;
+ cmd.ExecuteScalar();
+ }
+ }
+
+ // Delete local snapshot flat file database
+ var path = "./snapshots";
+ if (Directory.Exists(path))
+ Directory.Delete(path, true);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Akka.Persistence.SqlServer.Performance.Tests/SqlServerFixture.cs b/src/Akka.Persistence.SqlServer.Performance.Tests/SqlServerFixture.cs
new file mode 100644
index 0000000..13230cf
--- /dev/null
+++ b/src/Akka.Persistence.SqlServer.Performance.Tests/SqlServerFixture.cs
@@ -0,0 +1,186 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2013 - 2023 .NET Foundation
+//
+// -----------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Data.Common;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using Akka.Util;
+using Docker.DotNet;
+using Docker.DotNet.Models;
+using Xunit;
+using Xunit.Sdk;
+
+namespace Akka.Persistence.SqlServer.Performance.Tests
+{
+ [CollectionDefinition("SqlServerSpec")]
+ public sealed class SqlServerSpecsFixture : ICollectionFixture
+ {
+ }
+
+ ///
+ /// Fixture used to run SQL Server
+ ///
+ public class SqlServerFixture : IAsyncLifetime
+ {
+ protected readonly string SqlContainerName = $"sqlserver-{Guid.NewGuid():N}";
+ protected DockerClient Client;
+
+ public SqlServerFixture()
+ {
+ DockerClientConfiguration config;
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ config = new DockerClientConfiguration(new Uri("unix://var/run/docker.sock"));
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ config = new DockerClientConfiguration(new Uri("npipe://./pipe/docker_engine"));
+ else
+ throw new NotSupportedException($"Unsupported OS [{RuntimeInformation.OSDescription}]");
+
+ Client = config.CreateClient();
+ }
+
+ protected string ImageName => "mcr.microsoft.com/mssql/server";
+ protected string Tag => "2019-latest";
+
+ protected string SqlServerImageName => $"{ImageName}:{Tag}";
+
+ public string ConnectionString { get; private set; }
+
+ public async Task InitializeAsync()
+ {
+ var sysInfo = await Client.System.GetSystemInfoAsync();
+ if (sysInfo.OSType.ToLowerInvariant() != "linux")
+ throw new TestClassException("MSSQL docker image only available for linux containers");
+
+ var images = await Client.Images.ListImagesAsync(new ImagesListParameters
+ {
+ Filters = new Dictionary>
+ {
+ {
+ "reference",
+ new Dictionary
+ {
+ { SqlServerImageName, true }
+ }
+ }
+ }
+ });
+
+ if (images.Count == 0)
+ await Client.Images.CreateImageAsync(
+ new ImagesCreateParameters { FromImage = ImageName, Tag = Tag }, null,
+ new Progress(message =>
+ {
+ Console.WriteLine(!string.IsNullOrEmpty(message.ErrorMessage)
+ ? message.ErrorMessage
+ : $"{message.ID} {message.Status} {message.ProgressMessage}");
+ }));
+
+ var sqlServerHostPort = ThreadLocalRandom.Current.Next(9000, 10000);
+
+ // create the container
+ await Client.Containers.CreateContainerAsync(new CreateContainerParameters
+ {
+ Image = SqlServerImageName,
+ Name = SqlContainerName,
+ Tty = true,
+ ExposedPorts = new Dictionary
+ {
+ { "1433/tcp", new EmptyStruct() }
+ },
+ HostConfig = new HostConfig
+ {
+ PortBindings = new Dictionary>
+ {
+ {
+ "1433/tcp",
+ new List
+ {
+ new PortBinding
+ {
+ HostPort = $"{sqlServerHostPort}"
+ }
+ }
+ }
+ }
+ },
+ Env = new[]
+ {
+ "ACCEPT_EULA=Y",
+ "MSSQL_SA_PASSWORD=l0l!Th1sIsOpenSource"
+ }
+ });
+
+ // start the container
+ await Client.Containers.StartContainerAsync(SqlContainerName, new ContainerStartParameters());
+
+ // Wait until MSSQL is completely ready
+ var logStream = await Client.Containers.GetContainerLogsAsync(SqlContainerName, new ContainerLogsParameters
+ {
+ Follow = true,
+ ShowStdout = true,
+ ShowStderr = true
+ });
+
+ string line = null;
+ var timeoutInMilis = 60000;
+ using (var reader = new StreamReader(logStream))
+ {
+ var stopwatch = Stopwatch.StartNew();
+ while (stopwatch.ElapsedMilliseconds < timeoutInMilis && (line = await reader.ReadLineAsync()) != null)
+ {
+ if (!string.IsNullOrWhiteSpace(line))
+ Console.WriteLine(line);
+ if (line.Contains("SQL Server is now ready for client connections.")) break;
+ }
+
+ stopwatch.Stop();
+ }
+#if NETCOREAPP3_1_OR_GREATER
+ await logStream.DisposeAsync();
+#else
+ logStream.Dispose();
+#endif
+ if (!line?.Contains("SQL Server is now ready for client connections.") ?? false)
+ throw new Exception("MSSQL docker image failed to run.");
+
+ var connectionString = new DbConnectionStringBuilder
+ {
+ ["Server"] = $"localhost,{sqlServerHostPort}",
+ ["Database"] = "akka_persistence_tests",
+ ["User Id"] = "sa",
+ ["Password"] = "l0l!Th1sIsOpenSource"
+ };
+
+ ConnectionString = connectionString.ToString();
+ Console.WriteLine($"Connection string: [{ConnectionString}]");
+
+ await Task.Delay(10000);
+ }
+
+ public async Task DisposeAsync()
+ {
+ if (Client != null)
+ try
+ {
+ await Client.Containers.StopContainerAsync(SqlContainerName, new ContainerStopParameters());
+ await Client.Containers.RemoveContainerAsync(SqlContainerName,
+ new ContainerRemoveParameters { Force = true });
+ }
+ catch (DockerContainerNotFoundException)
+ {
+ // no-op
+ }
+ finally
+ {
+ Client.Dispose();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Akka.Persistence.SqlServer.Tests/SqlServerJournalPerfSpec.cs b/src/Akka.Persistence.SqlServer.Performance.Tests/SqlServerJournalPerfSpec.cs
similarity index 97%
rename from src/Akka.Persistence.SqlServer.Tests/SqlServerJournalPerfSpec.cs
rename to src/Akka.Persistence.SqlServer.Performance.Tests/SqlServerJournalPerfSpec.cs
index d4f8f47..c70e60f 100644
--- a/src/Akka.Persistence.SqlServer.Tests/SqlServerJournalPerfSpec.cs
+++ b/src/Akka.Persistence.SqlServer.Performance.Tests/SqlServerJournalPerfSpec.cs
@@ -10,7 +10,7 @@
using Xunit;
using Xunit.Abstractions;
-namespace Akka.Persistence.SqlServer.Tests
+namespace Akka.Persistence.SqlServer.Performance.Tests
{
[Collection("SqlServerSpec")]
public class SqlServerJournalPerfSpec : JournalPerfSpec, IDisposable
diff --git a/src/Akka.Persistence.SqlServer.Tests/Akka.Persistence.SqlServer.Tests.csproj b/src/Akka.Persistence.SqlServer.Tests/Akka.Persistence.SqlServer.Tests.csproj
index 877aad1..8901b40 100644
--- a/src/Akka.Persistence.SqlServer.Tests/Akka.Persistence.SqlServer.Tests.csproj
+++ b/src/Akka.Persistence.SqlServer.Tests/Akka.Persistence.SqlServer.Tests.csproj
@@ -2,23 +2,25 @@
$(NetFrameworkTestVersion);$(NetCoreTestVersion)
+ false
- $(NetCoreTestVersion)
+ $(NetCoreTestVersion)
+ false
-
-
-
-
-
+
+
+
+
+
-
+