From 5f3f995ce905511475947944695ebdb59b556022 Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Fri, 24 May 2024 11:00:47 +0300 Subject: [PATCH 01/27] chore: upload postgres dependency --- src/Directory.Packages.props | 2 +- .../packages.lock.json | 18 +++++++------- src/DistributedLock.Tests/packages.lock.json | 16 ++++++------- src/DistributedLock/packages.lock.json | 24 +++++++++---------- src/DistributedLockTaker/packages.lock.json | 22 ++++++++--------- 5 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index a7d2f366..2d27f132 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -12,7 +12,7 @@ - + diff --git a/src/DistributedLock.Postgres/packages.lock.json b/src/DistributedLock.Postgres/packages.lock.json index 4fd8add1..8379fce3 100644 --- a/src/DistributedLock.Postgres/packages.lock.json +++ b/src/DistributedLock.Postgres/packages.lock.json @@ -29,9 +29,9 @@ }, "Npgsql": { "type": "Direct", - "requested": "[8.0.2, )", - "resolved": "8.0.2", - "contentHash": "MuJzLoWCaQhQAR3oh66YR0Ir6mxuezncGX3f8wxvAc21g0+9HICktJQlqMoODhxztZKXE5k9GxRxqUAN+vPb4g==", + "requested": "[8.0.3, )", + "resolved": "8.0.3", + "contentHash": "6WEmzsQJCZAlUG1pThKg/RmeF6V+I0DmBBBE/8YzpRtEzhyZzKcK7ulMANDm5CkxrALBEC8H+5plxHWtIL7xnA==", "dependencies": { "Microsoft.Bcl.HashCode": "1.1.1", "Microsoft.Extensions.Logging.Abstractions": "8.0.0", @@ -215,9 +215,9 @@ }, "Npgsql": { "type": "Direct", - "requested": "[8.0.2, )", - "resolved": "8.0.2", - "contentHash": "MuJzLoWCaQhQAR3oh66YR0Ir6mxuezncGX3f8wxvAc21g0+9HICktJQlqMoODhxztZKXE5k9GxRxqUAN+vPb4g==", + "requested": "[8.0.3, )", + "resolved": "8.0.3", + "contentHash": "6WEmzsQJCZAlUG1pThKg/RmeF6V+I0DmBBBE/8YzpRtEzhyZzKcK7ulMANDm5CkxrALBEC8H+5plxHWtIL7xnA==", "dependencies": { "Microsoft.Bcl.HashCode": "1.1.1", "Microsoft.Extensions.Logging.Abstractions": "8.0.0", @@ -384,9 +384,9 @@ }, "Npgsql": { "type": "Direct", - "requested": "[8.0.2, )", - "resolved": "8.0.2", - "contentHash": "MuJzLoWCaQhQAR3oh66YR0Ir6mxuezncGX3f8wxvAc21g0+9HICktJQlqMoODhxztZKXE5k9GxRxqUAN+vPb4g==", + "requested": "[8.0.3, )", + "resolved": "8.0.3", + "contentHash": "6WEmzsQJCZAlUG1pThKg/RmeF6V+I0DmBBBE/8YzpRtEzhyZzKcK7ulMANDm5CkxrALBEC8H+5plxHWtIL7xnA==", "dependencies": { "Microsoft.Extensions.Logging.Abstractions": "8.0.0", "System.Collections.Immutable": "8.0.0", diff --git a/src/DistributedLock.Tests/packages.lock.json b/src/DistributedLock.Tests/packages.lock.json index a04de49f..4c1a9111 100644 --- a/src/DistributedLock.Tests/packages.lock.json +++ b/src/DistributedLock.Tests/packages.lock.json @@ -471,7 +471,7 @@ "type": "Project", "dependencies": { "DistributedLock.Core": "[1.0.6, )", - "Npgsql": "[8.0.2, )" + "Npgsql": "[8.0.3, )" } }, "distributedlock.redis": { @@ -541,9 +541,9 @@ }, "Npgsql": { "type": "CentralTransitive", - "requested": "[8.0.2, )", - "resolved": "8.0.2", - "contentHash": "MuJzLoWCaQhQAR3oh66YR0Ir6mxuezncGX3f8wxvAc21g0+9HICktJQlqMoODhxztZKXE5k9GxRxqUAN+vPb4g==", + "requested": "[8.0.3, )", + "resolved": "8.0.3", + "contentHash": "6WEmzsQJCZAlUG1pThKg/RmeF6V+I0DmBBBE/8YzpRtEzhyZzKcK7ulMANDm5CkxrALBEC8H+5plxHWtIL7xnA==", "dependencies": { "Microsoft.Bcl.HashCode": "1.1.1", "Microsoft.Extensions.Logging.Abstractions": "8.0.0", @@ -1118,7 +1118,7 @@ "type": "Project", "dependencies": { "DistributedLock.Core": "[1.0.6, )", - "Npgsql": "[8.0.2, )" + "Npgsql": "[8.0.3, )" } }, "distributedlock.redis": { @@ -1186,9 +1186,9 @@ }, "Npgsql": { "type": "CentralTransitive", - "requested": "[8.0.2, )", - "resolved": "8.0.2", - "contentHash": "MuJzLoWCaQhQAR3oh66YR0Ir6mxuezncGX3f8wxvAc21g0+9HICktJQlqMoODhxztZKXE5k9GxRxqUAN+vPb4g==", + "requested": "[8.0.3, )", + "resolved": "8.0.3", + "contentHash": "6WEmzsQJCZAlUG1pThKg/RmeF6V+I0DmBBBE/8YzpRtEzhyZzKcK7ulMANDm5CkxrALBEC8H+5plxHWtIL7xnA==", "dependencies": { "Microsoft.Extensions.Logging.Abstractions": "8.0.0" } diff --git a/src/DistributedLock/packages.lock.json b/src/DistributedLock/packages.lock.json index b2d434bb..fa4bf4de 100644 --- a/src/DistributedLock/packages.lock.json +++ b/src/DistributedLock/packages.lock.json @@ -453,7 +453,7 @@ "type": "Project", "dependencies": { "DistributedLock.Core": "[1.0.6, )", - "Npgsql": "[8.0.2, )" + "Npgsql": "[8.0.3, )" } }, "distributedlock.redis": { @@ -532,9 +532,9 @@ }, "Npgsql": { "type": "CentralTransitive", - "requested": "[8.0.2, )", - "resolved": "8.0.2", - "contentHash": "MuJzLoWCaQhQAR3oh66YR0Ir6mxuezncGX3f8wxvAc21g0+9HICktJQlqMoODhxztZKXE5k9GxRxqUAN+vPb4g==", + "requested": "[8.0.3, )", + "resolved": "8.0.3", + "contentHash": "6WEmzsQJCZAlUG1pThKg/RmeF6V+I0DmBBBE/8YzpRtEzhyZzKcK7ulMANDm5CkxrALBEC8H+5plxHWtIL7xnA==", "dependencies": { "Microsoft.Bcl.HashCode": "1.1.1", "Microsoft.Extensions.Logging.Abstractions": "8.0.0", @@ -1092,7 +1092,7 @@ "type": "Project", "dependencies": { "DistributedLock.Core": "[1.0.6, )", - "Npgsql": "[8.0.2, )" + "Npgsql": "[8.0.3, )" } }, "distributedlock.redis": { @@ -1179,9 +1179,9 @@ }, "Npgsql": { "type": "CentralTransitive", - "requested": "[8.0.2, )", - "resolved": "8.0.2", - "contentHash": "MuJzLoWCaQhQAR3oh66YR0Ir6mxuezncGX3f8wxvAc21g0+9HICktJQlqMoODhxztZKXE5k9GxRxqUAN+vPb4g==", + "requested": "[8.0.3, )", + "resolved": "8.0.3", + "contentHash": "6WEmzsQJCZAlUG1pThKg/RmeF6V+I0DmBBBE/8YzpRtEzhyZzKcK7ulMANDm5CkxrALBEC8H+5plxHWtIL7xnA==", "dependencies": { "Microsoft.Bcl.HashCode": "1.1.1", "Microsoft.Extensions.Logging.Abstractions": "8.0.0", @@ -1744,7 +1744,7 @@ "type": "Project", "dependencies": { "DistributedLock.Core": "[1.0.6, )", - "Npgsql": "[8.0.2, )" + "Npgsql": "[8.0.3, )" } }, "distributedlock.redis": { @@ -1820,9 +1820,9 @@ }, "Npgsql": { "type": "CentralTransitive", - "requested": "[8.0.2, )", - "resolved": "8.0.2", - "contentHash": "MuJzLoWCaQhQAR3oh66YR0Ir6mxuezncGX3f8wxvAc21g0+9HICktJQlqMoODhxztZKXE5k9GxRxqUAN+vPb4g==", + "requested": "[8.0.3, )", + "resolved": "8.0.3", + "contentHash": "6WEmzsQJCZAlUG1pThKg/RmeF6V+I0DmBBBE/8YzpRtEzhyZzKcK7ulMANDm5CkxrALBEC8H+5plxHWtIL7xnA==", "dependencies": { "Microsoft.Extensions.Logging.Abstractions": "8.0.0", "System.Collections.Immutable": "8.0.0", diff --git a/src/DistributedLockTaker/packages.lock.json b/src/DistributedLockTaker/packages.lock.json index bc719454..79dacffd 100644 --- a/src/DistributedLockTaker/packages.lock.json +++ b/src/DistributedLockTaker/packages.lock.json @@ -412,7 +412,7 @@ "type": "Project", "dependencies": { "DistributedLock.Core": "[1.0.6, )", - "Npgsql": "[8.0.2, )" + "Npgsql": "[8.0.3, )" } }, "distributedlock.redis": { @@ -482,9 +482,9 @@ }, "Npgsql": { "type": "CentralTransitive", - "requested": "[8.0.2, )", - "resolved": "8.0.2", - "contentHash": "MuJzLoWCaQhQAR3oh66YR0Ir6mxuezncGX3f8wxvAc21g0+9HICktJQlqMoODhxztZKXE5k9GxRxqUAN+vPb4g==", + "requested": "[8.0.3, )", + "resolved": "8.0.3", + "contentHash": "6WEmzsQJCZAlUG1pThKg/RmeF6V+I0DmBBBE/8YzpRtEzhyZzKcK7ulMANDm5CkxrALBEC8H+5plxHWtIL7xnA==", "dependencies": { "Microsoft.Bcl.HashCode": "1.1.1", "Microsoft.Extensions.Logging.Abstractions": "8.0.0", @@ -590,9 +590,9 @@ "net8.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.2, )", - "resolved": "8.0.2", - "contentHash": "hKTrehpfVzOhAz0mreaTAZgbz0DrMEbWq4n3hAo8Ks6WdxdqQhNPvzOqn9VygKuWf1bmxPdraqzTaXriO/sn0A==" + "requested": "[8.0.5, )", + "resolved": "8.0.5", + "contentHash": "4ISW1Ndgz86FkIbu5rVlMqhhtojdy5rUlxC/N+9kPh9qbYcvHiCvYbHKzAPVIx9OPYIjT9trXt7JI42Y5Ukq6A==" }, "Azure.Core": { "type": "Transitive", @@ -998,7 +998,7 @@ "type": "Project", "dependencies": { "DistributedLock.Core": "[1.0.6, )", - "Npgsql": "[8.0.2, )" + "Npgsql": "[8.0.3, )" } }, "distributedlock.redis": { @@ -1066,9 +1066,9 @@ }, "Npgsql": { "type": "CentralTransitive", - "requested": "[8.0.2, )", - "resolved": "8.0.2", - "contentHash": "MuJzLoWCaQhQAR3oh66YR0Ir6mxuezncGX3f8wxvAc21g0+9HICktJQlqMoODhxztZKXE5k9GxRxqUAN+vPb4g==", + "requested": "[8.0.3, )", + "resolved": "8.0.3", + "contentHash": "6WEmzsQJCZAlUG1pThKg/RmeF6V+I0DmBBBE/8YzpRtEzhyZzKcK7ulMANDm5CkxrALBEC8H+5plxHWtIL7xnA==", "dependencies": { "Microsoft.Extensions.Logging.Abstractions": "8.0.0" } From c46cffb3c36215cbc4b607534e6283586476db08 Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Mon, 27 May 2024 21:51:57 +0300 Subject: [PATCH 02/27] chore: Use testcontainers for postgres tests --- src/Directory.Packages.props | 1 + src/DistributedLock.Core/packages.lock.json | 30 +- .../DistributedLock.Tests.csproj | 1 + .../Infrastructure/Postgres/PostgresDb.cs | 13 + .../Postgres/TestingPostgresDb.cs | 2 +- .../Tests/GlobalSetupTest.cs | 20 + src/DistributedLock.Tests/packages.lock.json | 635 ++---------------- 7 files changed, 101 insertions(+), 601 deletions(-) create mode 100644 src/DistributedLock.Tests/Infrastructure/Postgres/PostgresDb.cs create mode 100644 src/DistributedLock.Tests/Tests/GlobalSetupTest.cs diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 2d27f132..8e978369 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -22,6 +22,7 @@ + diff --git a/src/DistributedLock.Core/packages.lock.json b/src/DistributedLock.Core/packages.lock.json index 0584aab6..65a05592 100644 --- a/src/DistributedLock.Core/packages.lock.json +++ b/src/DistributedLock.Core/packages.lock.json @@ -11,12 +11,6 @@ "System.Threading.Tasks.Extensions": "4.5.4" } }, - "Microsoft.CodeAnalysis.PublicApiAnalyzers": { - "type": "Direct", - "requested": "[3.3.4, )", - "resolved": "3.3.4", - "contentHash": "kNLTfXtXUWDHVt5iaPkkiPuyHYlMgLI6SOFT4w88bfeI2vqSeGgHunFkdvlaCM8RDfcY0t2+jnesQtidRJJ/DA==" - }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", "requested": "[1.0.3, )", @@ -81,12 +75,6 @@ "System.Threading.Tasks.Extensions": "4.5.4" } }, - "Microsoft.CodeAnalysis.PublicApiAnalyzers": { - "type": "Direct", - "requested": "[3.3.4, )", - "resolved": "3.3.4", - "contentHash": "kNLTfXtXUWDHVt5iaPkkiPuyHYlMgLI6SOFT4w88bfeI2vqSeGgHunFkdvlaCM8RDfcY0t2+jnesQtidRJJ/DA==" - }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", @@ -136,12 +124,6 @@ } }, ".NETStandard,Version=v2.1": { - "Microsoft.CodeAnalysis.PublicApiAnalyzers": { - "type": "Direct", - "requested": "[3.3.4, )", - "resolved": "3.3.4", - "contentHash": "kNLTfXtXUWDHVt5iaPkkiPuyHYlMgLI6SOFT4w88bfeI2vqSeGgHunFkdvlaCM8RDfcY0t2+jnesQtidRJJ/DA==" - }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", @@ -164,17 +146,11 @@ } }, "net8.0": { - "Microsoft.CodeAnalysis.PublicApiAnalyzers": { - "type": "Direct", - "requested": "[3.3.4, )", - "resolved": "3.3.4", - "contentHash": "kNLTfXtXUWDHVt5iaPkkiPuyHYlMgLI6SOFT4w88bfeI2vqSeGgHunFkdvlaCM8RDfcY0t2+jnesQtidRJJ/DA==" - }, "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.2, )", - "resolved": "8.0.2", - "contentHash": "hKTrehpfVzOhAz0mreaTAZgbz0DrMEbWq4n3hAo8Ks6WdxdqQhNPvzOqn9VygKuWf1bmxPdraqzTaXriO/sn0A==" + "requested": "[8.0.5, )", + "resolved": "8.0.5", + "contentHash": "4ISW1Ndgz86FkIbu5rVlMqhhtojdy5rUlxC/N+9kPh9qbYcvHiCvYbHKzAPVIx9OPYIjT9trXt7JI42Y5Ukq6A==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", diff --git a/src/DistributedLock.Tests/DistributedLock.Tests.csproj b/src/DistributedLock.Tests/DistributedLock.Tests.csproj index be82219e..3b5b484e 100644 --- a/src/DistributedLock.Tests/DistributedLock.Tests.csproj +++ b/src/DistributedLock.Tests/DistributedLock.Tests.csproj @@ -27,6 +27,7 @@ + diff --git a/src/DistributedLock.Tests/Infrastructure/Postgres/PostgresDb.cs b/src/DistributedLock.Tests/Infrastructure/Postgres/PostgresDb.cs new file mode 100644 index 00000000..515bd5f3 --- /dev/null +++ b/src/DistributedLock.Tests/Infrastructure/Postgres/PostgresDb.cs @@ -0,0 +1,13 @@ +using Testcontainers.PostgreSql; + +namespace Medallion.Threading.Tests.Postgres; + +public class PostgresDb +{ + public static PostgreSqlContainer Container { get; } = new PostgreSqlBuilder().Build(); + + static PostgresDb() + { + Container.StartAsync().Wait(); + } +} \ No newline at end of file diff --git a/src/DistributedLock.Tests/Infrastructure/Postgres/TestingPostgresDb.cs b/src/DistributedLock.Tests/Infrastructure/Postgres/TestingPostgresDb.cs index 46a137aa..e47157f9 100644 --- a/src/DistributedLock.Tests/Infrastructure/Postgres/TestingPostgresDb.cs +++ b/src/DistributedLock.Tests/Infrastructure/Postgres/TestingPostgresDb.cs @@ -10,7 +10,7 @@ namespace Medallion.Threading.Tests.Postgres; public sealed class TestingPostgresDb : TestingPrimaryClientDb { - internal static readonly string DefaultConnectionString = PostgresCredentials.GetConnectionString(TestContext.CurrentContext.TestDirectory); + internal static readonly string DefaultConnectionString = PostgresDb.Container.GetConnectionString(); private readonly NpgsqlConnectionStringBuilder _connectionStringBuilder = new(DefaultConnectionString); diff --git a/src/DistributedLock.Tests/Tests/GlobalSetupTest.cs b/src/DistributedLock.Tests/Tests/GlobalSetupTest.cs new file mode 100644 index 00000000..f9686f26 --- /dev/null +++ b/src/DistributedLock.Tests/Tests/GlobalSetupTest.cs @@ -0,0 +1,20 @@ +using Medallion.Threading.Tests.Postgres; +using NUnit.Framework; + +namespace Medallion.Threading.Tests; + +public class GlobalSetupTest +{ + + [OneTimeSetUp] + public async Task GlobalSetup() + { + // await PostgresDb.Container.StartAsync(); + } + + [OneTimeTearDown] + public async Task GlobalTeardown() + { + await PostgresDb.Container.StopAsync(); + } +} \ No newline at end of file diff --git a/src/DistributedLock.Tests/packages.lock.json b/src/DistributedLock.Tests/packages.lock.json index 4c1a9111..72f442b6 100644 --- a/src/DistributedLock.Tests/packages.lock.json +++ b/src/DistributedLock.Tests/packages.lock.json @@ -1,579 +1,6 @@ { "version": 2, "dependencies": { - ".NETFramework,Version=v4.7.2": { - "MedallionShell.StrongName": { - "type": "Direct", - "requested": "[1.6.2, )", - "resolved": "1.6.2", - "contentHash": "x7kIh8HiLHQrm5tcLEwNXhYfIHjQoK8ZS9MPx/LcCgNubtfFVJZm8Kk5/FSOalHjlXizcLAm6733L691l8cr/Q==" - }, - "Microsoft.NET.Test.Sdk": { - "type": "Direct", - "requested": "[17.9.0, )", - "resolved": "17.9.0", - "contentHash": "7GUNAUbJYn644jzwLm5BD3a2p9C1dmP8Hr6fDPDxgItQk9hBs1Svdxzz07KQ/UphMSmgza9AbijBJGmw5D658A==", - "dependencies": { - "Microsoft.CodeCoverage": "17.9.0" - } - }, - "Moq": { - "type": "Direct", - "requested": "[4.20.70, )", - "resolved": "4.20.70", - "contentHash": "4rNnAwdpXJBuxqrOCzCyICXHSImOTRktCgCWXWykuF1qwoIsVvEnR7PjbMk/eLOxWvhmj5Kwt+kDV3RGUYcNwg==", - "dependencies": { - "Castle.Core": "5.1.1", - "System.Threading.Tasks.Extensions": "4.5.4" - } - }, - "NUnit": { - "type": "Direct", - "requested": "[3.14.0, )", - "resolved": "3.14.0", - "contentHash": "R7iPwD7kbOaP3o2zldWJbWeMQAvDKD0uld27QvA3PAALl1unl7x0v2J7eGiJOYjimV/BuGT4VJmr45RjS7z4LA==" - }, - "NUnit.Analyzers": { - "type": "Direct", - "requested": "[4.1.0, )", - "resolved": "4.1.0", - "contentHash": "Odd1RusSMnfswIiCPbokAqmlcCCXjQ20poaXWrw+CWDnBY1vQ/x6ZGqgyJXpebPq5Uf8uEBe5iOAySsCdSrWdQ==" - }, - "NUnit3TestAdapter": { - "type": "Direct", - "requested": "[4.5.0, )", - "resolved": "4.5.0", - "contentHash": "s8JpqTe9bI2f49Pfr3dFRfoVSuFQyraTj68c3XXjIS/MRGvvkLnrg6RLqnTjdShX+AdFUCCU/4Xex58AdUfs6A==" - }, - "System.Data.SqlClient": { - "type": "Direct", - "requested": "[4.8.6, )", - "resolved": "4.8.6", - "contentHash": "2Ij/LCaTQRyAi5lAv7UUTV9R2FobC8xN9mE0fXBZohum/xLl8IZVmE98Rq5ugQHjCgTBRKqpXRb4ORulRdA6Ig==" - }, - "Azure.Core": { - "type": "Transitive", - "resolved": "1.36.0", - "contentHash": "vwqFZdHS4dzPlI7FFRkPx9ctA+aGGeRev3gnzG8lntWvKMmBhAmulABi1O9CEvS3/jzYt7yA+0pqVdxkpAd7dQ==", - "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "1.1.1", - "System.Diagnostics.DiagnosticSource": "6.0.1", - "System.Memory.Data": "1.0.2", - "System.Numerics.Vectors": "4.5.0", - "System.Text.Encodings.Web": "4.7.2", - "System.Text.Json": "4.7.2", - "System.Threading.Tasks.Extensions": "4.5.4" - } - }, - "Azure.Identity": { - "type": "Transitive", - "resolved": "1.10.3", - "contentHash": "l1Xm2MWOF2Mzcwuarlw8kWQXLZk3UeB55aQXVyjj23aBfDwOZ3gu5GP2kJ6KlmZeZv2TCzw7x4L3V36iNr3gww==", - "dependencies": { - "Azure.Core": "1.35.0", - "Microsoft.Identity.Client": "4.56.0", - "Microsoft.Identity.Client.Extensions.Msal": "4.56.0", - "System.Memory": "4.5.4", - "System.Security.Cryptography.ProtectedData": "4.7.0", - "System.Text.Json": "4.7.2", - "System.Threading.Tasks.Extensions": "4.5.4" - } - }, - "Azure.Storage.Common": { - "type": "Transitive", - "resolved": "12.18.1", - "contentHash": "ohCslqP9yDKIn+DVjBEOBuieB1QwsUCz+BwHYNaJ3lcIsTSiI4Evnq81HcKe8CqM8qvdModbipVQKpnxpbdWqA==", - "dependencies": { - "Azure.Core": "1.36.0", - "System.IO.Hashing": "6.0.0" - } - }, - "Castle.Core": { - "type": "Transitive", - "resolved": "5.1.1", - "contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==" - }, - "Microsoft.Bcl.AsyncInterfaces": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==", - "dependencies": { - "System.Threading.Tasks.Extensions": "4.5.4" - } - }, - "Microsoft.Bcl.HashCode": { - "type": "Transitive", - "resolved": "1.1.1", - "contentHash": "MalY0Y/uM/LjXtHfX/26l2VtN4LDNZ2OE3aumNOHDLsT4fNYy2hiHXI4CXCqKpNUNm7iJ2brrc4J89UdaL56FA==" - }, - "Microsoft.CodeCoverage": { - "type": "Transitive", - "resolved": "17.9.0", - "contentHash": "RGD37ZSrratfScYXm7M0HjvxMxZyWZL4jm+XgMZbkIY1UPgjUpbNA/t+WTGj/rC/0Hm9A3IrH3ywbKZkOCnoZA==" - }, - "Microsoft.Data.SqlClient.SNI": { - "type": "Transitive", - "resolved": "5.2.0", - "contentHash": "0p2KMVc8WSC5JWgO+OdhYJiRM41dp6w2Dsd9JfEiHLPc6nyxBQgSrx9TYlbC8fRT2RK+HyWzDlv9ofFtxMOwQg==" - }, - "Microsoft.Extensions.DependencyInjection.Abstractions": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg==", - "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "8.0.0", - "System.Threading.Tasks.Extensions": "4.5.4" - } - }, - "Microsoft.Extensions.Logging.Abstractions": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "System.Buffers": "4.5.1", - "System.Memory": "4.5.5" - } - }, - "Microsoft.Identity.Client": { - "type": "Transitive", - "resolved": "4.56.0", - "contentHash": "rr4zbidvHy9r4NvOAs5hdd964Ao2A0pAeFBJKR95u1CJAVzbd1p6tPTXUZ+5ld0cfThiVSGvz6UHwY6JjraTpA==", - "dependencies": { - "Microsoft.IdentityModel.Abstractions": "6.22.0" - } - }, - "Microsoft.Identity.Client.Extensions.Msal": { - "type": "Transitive", - "resolved": "4.56.0", - "contentHash": "H12YAzEGK55vZ+QpxUzozhW8ZZtgPDuWvgA0JbdIR9UhMUplj29JhIgE2imuH8W2Nw9D8JKygR1uxRFtpSNcrg==", - "dependencies": { - "Microsoft.Identity.Client": "4.56.0", - "System.IO.FileSystem.AccessControl": "5.0.0", - "System.Security.Cryptography.ProtectedData": "4.5.0" - } - }, - "Microsoft.IdentityModel.Abstractions": { - "type": "Transitive", - "resolved": "6.35.0", - "contentHash": "xuR8E4Rd96M41CnUSCiOJ2DBh+z+zQSmyrYHdYhD6K4fXBcQGVnRCFQ0efROUYpP+p0zC1BLKr0JRpVuujTZSg==" - }, - "Microsoft.IdentityModel.JsonWebTokens": { - "type": "Transitive", - "resolved": "6.35.0", - "contentHash": "9wxai3hKgZUb4/NjdRKfQd0QJvtXKDlvmGMYACbEC8DFaicMFCFhQFZq9ZET1kJLwZahf2lfY5Gtcpsx8zYzbg==", - "dependencies": { - "Microsoft.IdentityModel.Tokens": "6.35.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encodings.Web": "4.7.2", - "System.Text.Json": "4.7.2" - } - }, - "Microsoft.IdentityModel.Logging": { - "type": "Transitive", - "resolved": "6.35.0", - "contentHash": "jePrSfGAmqT81JDCNSY+fxVWoGuJKt9e6eJ+vT7+quVS55nWl//jGjUQn4eFtVKt4rt5dXaleZdHRB9J9AJZ7Q==", - "dependencies": { - "Microsoft.IdentityModel.Abstractions": "6.35.0" - } - }, - "Microsoft.IdentityModel.Protocols": { - "type": "Transitive", - "resolved": "6.35.0", - "contentHash": "BPQhlDzdFvv1PzaUxNSk+VEPwezlDEVADIKmyxubw7IiELK18uJ06RQ9QKKkds30XI+gDu9n8j24XQ8w7fjWcg==", - "dependencies": { - "Microsoft.IdentityModel.Logging": "6.35.0", - "Microsoft.IdentityModel.Tokens": "6.35.0" - } - }, - "Microsoft.IdentityModel.Protocols.OpenIdConnect": { - "type": "Transitive", - "resolved": "6.35.0", - "contentHash": "LMtVqnECCCdSmyFoCOxIE5tXQqkOLrvGrL7OxHg41DIm1bpWtaCdGyVcTAfOQpJXvzND9zUKIN/lhngPkYR8vg==", - "dependencies": { - "Microsoft.IdentityModel.Protocols": "6.35.0", - "System.IdentityModel.Tokens.Jwt": "6.35.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encodings.Web": "4.7.2", - "System.Text.Json": "4.7.2" - } - }, - "Microsoft.IdentityModel.Tokens": { - "type": "Transitive", - "resolved": "6.35.0", - "contentHash": "RN7lvp7s3Boucg1NaNAbqDbxtlLj5Qeb+4uSS1TeK5FSBVM40P4DKaTKChT43sHyKfh7V0zkrMph6DdHvyA4bg==", - "dependencies": { - "Microsoft.IdentityModel.Logging": "6.35.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encodings.Web": "4.7.2", - "System.Text.Json": "4.7.2" - } - }, - "Oracle.ManagedDataAccess": { - "type": "Transitive", - "resolved": "21.13.0", - "contentHash": "3OaqQzmx5aRrjBfPu44BaGy9Jz+NiO8Q7x+jPrSL91nSc/M4JYv6j/aX1dwdq4rlSHAw6Ct9rUsd0z7xjvq0Gw==", - "dependencies": { - "System.Formats.Asn1": "7.0.0", - "System.Text.Json": "6.0.1" - } - }, - "Pipelines.Sockets.Unofficial": { - "type": "Transitive", - "resolved": "2.2.8", - "contentHash": "zG2FApP5zxSx6OcdJQLbZDk2AVlN2BNQD6MorwIfV6gVj0RRxWPEp2LXAxqDGZqeNV1Zp0BNPcNaey/GXmTdvQ==", - "dependencies": { - "System.IO.Pipelines": "5.0.1" - } - }, - "System.Buffers": { - "type": "Transitive", - "resolved": "4.5.1", - "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" - }, - "System.Collections.Immutable": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==", - "dependencies": { - "System.Memory": "4.5.5", - "System.Runtime.CompilerServices.Unsafe": "6.0.0" - } - }, - "System.Configuration.ConfigurationManager": { - "type": "Transitive", - "resolved": "6.0.1", - "contentHash": "jXw9MlUu/kRfEU0WyTptAVueupqIeE3/rl0EZDMlf8pcvJnitQ8HeVEp69rZdaStXwTV72boi/Bhw8lOeO+U2w==", - "dependencies": { - "System.Security.Permissions": "6.0.0" - } - }, - "System.Diagnostics.DiagnosticSource": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "c9xLpVz6PL9lp/djOWtk5KPDZq3cSYpmXoJQY524EOtuFl5z9ZtsotpsyrDW40U1DRnQSYvcPKEUV0X//u6gkQ==", - "dependencies": { - "System.Memory": "4.5.5", - "System.Runtime.CompilerServices.Unsafe": "6.0.0" - } - }, - "System.Formats.Asn1": { - "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "+nfpV0afLmvJW8+pLlHxRjz3oZJw4fkyU9MMEaMhCsHi/SN9bGF9q79ROubDiwTiCHezmK0uCWkPP7tGFP/4yg==", - "dependencies": { - "System.Buffers": "4.5.1", - "System.Memory": "4.5.5", - "System.ValueTuple": "4.5.0" - } - }, - "System.IdentityModel.Tokens.Jwt": { - "type": "Transitive", - "resolved": "6.35.0", - "contentHash": "yxGIQd3BFK7F6S62/7RdZk3C/mfwyVxvh6ngd1VYMBmbJ1YZZA9+Ku6suylVtso0FjI0wbElpJ0d27CdsyLpBQ==", - "dependencies": { - "Microsoft.IdentityModel.JsonWebTokens": "6.35.0", - "Microsoft.IdentityModel.Tokens": "6.35.0" - } - }, - "System.IO.Compression": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==" - }, - "System.IO.FileSystem.AccessControl": { - "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "SxHB3nuNrpptVk+vZ/F+7OHEpoHUIKKMl02bUmYHQr1r+glbZQxs7pRtsf4ENO29TVm2TH3AEeep2fJcy92oYw==", - "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" - } - }, - "System.IO.Hashing": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "Rfm2jYCaUeGysFEZjDe7j1R4x6Z6BzumS/vUT5a1AA/AWJuGX71PoGB0RmpyX3VmrGqVnAwtfMn39OHR8Y/5+g==", - "dependencies": { - "System.Buffers": "4.5.1", - "System.Memory": "4.5.4" - } - }, - "System.IO.Pipelines": { - "type": "Transitive", - "resolved": "5.0.1", - "contentHash": "qEePWsaq9LoEEIqhbGe6D5J8c9IqQOUuTzzV6wn1POlfdLkJliZY3OlB0j0f17uMWlqZYjH7txj+2YbyrIA8Yg==", - "dependencies": { - "System.Buffers": "4.5.1", - "System.Memory": "4.5.4", - "System.Threading.Tasks.Extensions": "4.5.4" - } - }, - "System.Memory": { - "type": "Transitive", - "resolved": "4.5.5", - "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==", - "dependencies": { - "System.Buffers": "4.5.1", - "System.Numerics.Vectors": "4.5.0", - "System.Runtime.CompilerServices.Unsafe": "4.5.3" - } - }, - "System.Memory.Data": { - "type": "Transitive", - "resolved": "1.0.2", - "contentHash": "JGkzeqgBsiZwKJZ1IxPNsDFZDhUvuEdX8L8BDC8N3KOj+6zMcNU28CNN59TpZE/VJYy9cP+5M+sbxtWJx3/xtw==", - "dependencies": { - "System.Text.Encodings.Web": "4.7.2", - "System.Text.Json": "4.6.0" - } - }, - "System.Numerics.Vectors": { - "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" - }, - "System.Runtime.CompilerServices.Unsafe": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" - }, - "System.Runtime.InteropServices.RuntimeInformation": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==" - }, - "System.Security.AccessControl": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "AUADIc0LIEQe7MzC+I0cl0rAT8RrTAKFHl53yHjEUzNVIaUlhFY11vc2ebiVJzVBuOzun6F7FBA+8KAbGTTedQ==", - "dependencies": { - "System.Security.Principal.Windows": "5.0.0" - } - }, - "System.Security.Cryptography.ProtectedData": { - "type": "Transitive", - "resolved": "4.7.0", - "contentHash": "ehYW0m9ptxpGWvE4zgqongBVWpSDU/JCFD4K7krxkQwSz/sFQjEXCUqpvencjy6DYDbn7Ig09R8GFffu8TtneQ==" - }, - "System.Security.Permissions": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "T/uuc7AklkDoxmcJ7LGkyX1CcSviZuLCa4jg3PekfJ7SU0niF0IVTXwUiNVP9DSpzou2PpxJ+eNY2IfDM90ZCg==", - "dependencies": { - "System.Security.AccessControl": "6.0.0" - } - }, - "System.Security.Principal.Windows": { - "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" - }, - "System.Text.Encoding": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==" - }, - "System.Text.Encodings.Web": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==", - "dependencies": { - "System.Buffers": "4.5.1", - "System.Memory": "4.5.5", - "System.Runtime.CompilerServices.Unsafe": "6.0.0" - } - }, - "System.Text.Json": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "OdrZO2WjkiEG6ajEFRABTRCi/wuXQPxeV6g8xvUJqdxMvvuCCEk86zPla8UiIQJz3durtUEbNyY/3lIhS0yZvQ==", - "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "8.0.0", - "System.Buffers": "4.5.1", - "System.Memory": "4.5.5", - "System.Runtime.CompilerServices.Unsafe": "6.0.0", - "System.Text.Encodings.Web": "8.0.0", - "System.Threading.Tasks.Extensions": "4.5.4", - "System.ValueTuple": "4.5.0" - } - }, - "System.Threading.Channels": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "CMaFr7v+57RW7uZfZkPExsPB6ljwzhjACWW1gfU35Y56rk72B/Wu+sTqxVmGSk4SFUlPc3cjeKND0zktziyjBA==", - "dependencies": { - "System.Threading.Tasks.Extensions": "4.5.4" - } - }, - "System.Threading.Tasks.Extensions": { - "type": "Transitive", - "resolved": "4.5.4", - "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "4.5.3" - } - }, - "System.ValueTuple": { - "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ==" - }, - "distributedlock": { - "type": "Project", - "dependencies": { - "DistributedLock.Azure": "[1.0.1, )", - "DistributedLock.FileSystem": "[1.0.2, )", - "DistributedLock.MySql": "[1.0.2, )", - "DistributedLock.Oracle": "[1.0.3, )", - "DistributedLock.Postgres": "[1.1.0, )", - "DistributedLock.Redis": "[1.0.3, )", - "DistributedLock.SqlServer": "[1.0.4, )", - "DistributedLock.WaitHandles": "[1.0.1, )", - "DistributedLock.ZooKeeper": "[1.0.0, )" - } - }, - "distributedlock.azure": { - "type": "Project", - "dependencies": { - "Azure.Storage.Blobs": "[12.19.1, )", - "DistributedLock.Core": "[1.0.6, )" - } - }, - "distributedlock.core": { - "type": "Project", - "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "[8.0.0, )", - "System.ValueTuple": "[4.5.0, )" - } - }, - "distributedlock.filesystem": { - "type": "Project", - "dependencies": { - "DistributedLock.Core": "[1.0.6, )" - } - }, - "distributedlock.mysql": { - "type": "Project", - "dependencies": { - "DistributedLock.Core": "[1.0.6, )", - "MySqlConnector": "[2.3.5, )" - } - }, - "distributedlock.oracle": { - "type": "Project", - "dependencies": { - "DistributedLock.Core": "[1.0.6, )", - "Oracle.ManagedDataAccess": "[21.13.0, )" - } - }, - "distributedlock.postgres": { - "type": "Project", - "dependencies": { - "DistributedLock.Core": "[1.0.6, )", - "Npgsql": "[8.0.3, )" - } - }, - "distributedlock.redis": { - "type": "Project", - "dependencies": { - "DistributedLock.Core": "[1.0.6, )", - "StackExchange.Redis": "[2.7.27, )" - } - }, - "distributedlock.sqlserver": { - "type": "Project", - "dependencies": { - "DistributedLock.Core": "[1.0.6, )", - "Microsoft.Data.SqlClient": "[5.2.0, )" - } - }, - "distributedlock.waithandles": { - "type": "Project", - "dependencies": { - "DistributedLock.Core": "[1.0.6, )" - } - }, - "distributedlock.zookeeper": { - "type": "Project", - "dependencies": { - "DistributedLock.Core": "[1.0.6, )", - "ZooKeeperNetEx": "[3.4.12.4, )" - } - }, - "Azure.Storage.Blobs": { - "type": "CentralTransitive", - "requested": "[12.19.1, )", - "resolved": "12.19.1", - "contentHash": "x43hWFJ4sPQ23TD4piCwT+KlQpZT8pNDAzqj6yUCqh+WJ2qcQa17e1gh6ZOeT2QNFQTTDSuR56fm2bIV7i11/w==", - "dependencies": { - "Azure.Storage.Common": "12.18.1", - "System.Text.Json": "4.7.2" - } - }, - "Microsoft.Data.SqlClient": { - "type": "CentralTransitive", - "requested": "[5.2.0, )", - "resolved": "5.2.0", - "contentHash": "3alfyqRN3ELRtdvU1dGtLBRNQqprr3TJ0WrUJfMISPwg1nPUN2P3Lelah68IKWuV27Ceb7ig95hWNHFTSXfxMg==", - "dependencies": { - "Azure.Identity": "1.10.3", - "Microsoft.Data.SqlClient.SNI": "5.2.0", - "Microsoft.Identity.Client": "4.56.0", - "Microsoft.IdentityModel.JsonWebTokens": "6.35.0", - "Microsoft.IdentityModel.Protocols.OpenIdConnect": "6.35.0", - "System.Buffers": "4.5.1", - "System.Configuration.ConfigurationManager": "6.0.1", - "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", - "System.Text.Encodings.Web": "6.0.0" - } - }, - "MySqlConnector": { - "type": "CentralTransitive", - "requested": "[2.3.5, )", - "resolved": "2.3.5", - "contentHash": "AmEfUPkFl+Ev6jJ8Dhns3CYHBfD12RHzGYWuLt6DfG6/af6YvOMyPz74ZPPjBYQGRJkumD2Z48Kqm8s5DJuhLA==", - "dependencies": { - "Microsoft.Extensions.Logging.Abstractions": "7.0.1", - "System.Diagnostics.DiagnosticSource": "7.0.2", - "System.Threading.Tasks.Extensions": "4.5.4" - } - }, - "Npgsql": { - "type": "CentralTransitive", - "requested": "[8.0.3, )", - "resolved": "8.0.3", - "contentHash": "6WEmzsQJCZAlUG1pThKg/RmeF6V+I0DmBBBE/8YzpRtEzhyZzKcK7ulMANDm5CkxrALBEC8H+5plxHWtIL7xnA==", - "dependencies": { - "Microsoft.Bcl.HashCode": "1.1.1", - "Microsoft.Extensions.Logging.Abstractions": "8.0.0", - "System.Collections.Immutable": "8.0.0", - "System.Diagnostics.DiagnosticSource": "8.0.0", - "System.Runtime.CompilerServices.Unsafe": "6.0.0", - "System.Text.Json": "8.0.0", - "System.Threading.Channels": "8.0.0" - } - }, - "StackExchange.Redis": { - "type": "CentralTransitive", - "requested": "[2.7.27, )", - "resolved": "2.7.27", - "contentHash": "Uqc2OQHglqj9/FfGQ6RkKFkZfHySfZlfmbCl+hc+u2I/IqunfelQ7QJi7ZhvAJxUtu80pildVX6NPLdDaUffOw==", - "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "5.0.0", - "Microsoft.Extensions.Logging.Abstractions": "6.0.0", - "Pipelines.Sockets.Unofficial": "2.2.8", - "System.IO.Compression": "4.3.0", - "System.Threading.Channels": "5.0.0" - } - }, - "ZooKeeperNetEx": { - "type": "CentralTransitive", - "requested": "[3.4.12.4, )", - "resolved": "3.4.12.4", - "contentHash": "YECtByVSH7TRjQKplwOWiKyanCqYE5eEkGk5YtHJgsnbZ6+p1o0Gvs5RIsZLotiAVa6Niez1BJyKY/RDY/L6zg==" - } - }, "net8.0": { "MedallionShell.StrongName": { "type": "Direct", @@ -632,6 +59,15 @@ "runtime.native.System.Data.SqlClient.sni": "4.7.0" } }, + "Testcontainers.PostgreSql": { + "type": "Direct", + "requested": "[3.8.0, )", + "resolved": "3.8.0", + "contentHash": "5sKpj6z4+Z92BFBILvjHqDry4OH9xyngqkeSM3ZrW5wTOJfu8GL0z5p/ZwYBQySPHQBbDa5et5r51Ga1i02ahQ==", + "dependencies": { + "Testcontainers": "3.8.0" + } + }, "Azure.Core": { "type": "Transitive", "resolved": "1.36.0", @@ -677,6 +113,24 @@ "System.Diagnostics.EventLog": "6.0.0" } }, + "Docker.DotNet": { + "type": "Transitive", + "resolved": "3.125.15", + "contentHash": "XN8FKxVv8Mjmwu104/Hl9lM61pLY675s70gzwSj8KR5pwblo8HfWLcCuinh9kYsqujBkMH4HVRCEcRuU6al4BQ==", + "dependencies": { + "Newtonsoft.Json": "13.0.1", + "System.Buffers": "4.5.1", + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, + "Docker.DotNet.X509": { + "type": "Transitive", + "resolved": "3.125.15", + "contentHash": "ONQN7ImrL3tHStUUCCPHwrFFQVpIpE+7L6jaDAMwSF+yTEmeWBmRARQZDRuvfj/+WtB8RR0oTW0tT3qQMSyHOw==", + "dependencies": { + "Docker.DotNet": "3.125.15" + } + }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", "resolved": "1.1.1", @@ -882,6 +336,29 @@ "resolved": "4.4.0", "contentHash": "YhEdSQUsTx+C8m8Bw7ar5/VesXvCFMItyZF7G1AUY+OM0VPZUOeAVpJ4Wl6fydBGUYZxojTDR3I6Bj/+BPkJNA==" }, + "SharpZipLib": { + "type": "Transitive", + "resolved": "1.4.2", + "contentHash": "yjj+3zgz8zgXpiiC3ZdF/iyTBbz2fFvMxZFEBPUcwZjIvXOf37Ylm+K58hqMfIBt5JgU/Z2uoUS67JmTLe973A==" + }, + "SSH.NET": { + "type": "Transitive", + "resolved": "2023.0.0", + "contentHash": "g+3VDUrYhm0sqSxmlQFgRFrmBxhQvVh4pfn4pqjkX7WXE3tTjt1tIsOtjuz3mz/5s8gFFQVRydwCJ7Ohs54sJA==", + "dependencies": { + "SshNet.Security.Cryptography": "[1.3.0]" + } + }, + "SshNet.Security.Cryptography": { + "type": "Transitive", + "resolved": "1.3.0", + "contentHash": "5pBIXRjcSO/amY8WztpmNOhaaCNHY/B6CcYDI7FSTgqSyo/ZUojlLiKcsl+YGbxQuLX439qIkMfP0PHqxqJi/Q==" + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" + }, "System.Configuration.ConfigurationManager": { "type": "Transitive", "resolved": "8.0.0", @@ -1070,6 +547,18 @@ "System.Drawing.Common": "6.0.0" } }, + "Testcontainers": { + "type": "Transitive", + "resolved": "3.8.0", + "contentHash": "R0+4VTGtFm+Q50+dP7Rm+ZB6thjKdBF3mGYbh5dEf/qt+r35MdFx8tMsE0VIbtaqgJMsOSD06CUZB2wPqBe2cw==", + "dependencies": { + "Docker.DotNet": "3.125.15", + "Docker.DotNet.X509": "3.125.15", + "Microsoft.Extensions.Logging.Abstractions": "6.0.4", + "SSH.NET": "2023.0.0", + "SharpZipLib": "1.4.2" + } + }, "distributedlock": { "type": "Project", "dependencies": { From 791e3a8f4d0db0be1be9b2e352ed66fd6d9cd229 Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Mon, 27 May 2024 22:03:48 +0300 Subject: [PATCH 03/27] pass connection-string from postgres to LockTaker process --- docs/Developing DistributedLock.md | 6 +--- .../DistributedLockCoreTestCases.cs | 4 +-- .../Postgres/TestingPostgresProviders.cs | 2 ++ .../Shared/PostgresCredentials.cs | 35 ------------------- .../Infrastructure/TestingLockProvider.cs | 2 ++ src/DistributedLockTaker/Program.cs | 5 +-- 6 files changed, 10 insertions(+), 44 deletions(-) delete mode 100644 src/DistributedLock.Tests/Infrastructure/Shared/PostgresCredentials.cs diff --git a/docs/Developing DistributedLock.md b/docs/Developing DistributedLock.md index 380b8484..1e67e922 100644 --- a/docs/Developing DistributedLock.md +++ b/docs/Developing DistributedLock.md @@ -51,11 +51,7 @@ If the Oracle tests fail with `ORA-12541: TNS:no listener`, you may have to star ### Postgres -You can install Postgres from [here](https://www.enterprisedb.com/downloads/postgres-postgresql-downloads). - -In `C:\Program Files\PostgreSQL\\data\postgresql.conf`, update `max_connections` to 200. - -Add your username (e.g. postgres) and password to `DistributedLock.Tests/credentials/postgres.txt`, with the username on line 1 and the password on line 2. +Have docker installed, we are using https://testcontainers.com/modules/postgresql/ ### SQL Server diff --git a/src/DistributedLock.Tests/AbstractTestCases/DistributedLockCoreTestCases.cs b/src/DistributedLock.Tests/AbstractTestCases/DistributedLockCoreTestCases.cs index 66920ba1..ec6130c5 100644 --- a/src/DistributedLock.Tests/AbstractTestCases/DistributedLockCoreTestCases.cs +++ b/src/DistributedLock.Tests/AbstractTestCases/DistributedLockCoreTestCases.cs @@ -390,7 +390,7 @@ public async Task TestLockAbandonment() public void TestCrossProcess() { var lockName = this._lockProvider.GetUniqueSafeName(); - var command = this.RunLockTaker(this._lockProvider, this._lockProvider.GetCrossProcessLockType(), lockName); + var command = this.RunLockTaker(this._lockProvider, this._lockProvider.GetCrossProcessLockType(), lockName, this._lockProvider.GetConnectionStringForCrossProcessTest()); Assert.That(command.StandardOutput.ReadLineAsync().Wait(TimeSpan.FromSeconds(10)), Is.True); Assert.That(command.Task.Wait(TimeSpan.FromSeconds(.1)), Is.False); @@ -421,7 +421,7 @@ public void TestCrossProcessAbandonmentWithKill() private void CrossProcessAbandonmentHelper(bool asyncWait, bool kill) { var name = this._lockProvider.GetUniqueSafeName($"cpl-{asyncWait}-{kill}"); - var command = this.RunLockTaker(this._lockProvider, this._lockProvider.GetCrossProcessLockType(), name); + var command = this.RunLockTaker(this._lockProvider, this._lockProvider.GetCrossProcessLockType(), name, this._lockProvider.GetConnectionStringForCrossProcessTest()); Assert.That(command.StandardOutput.ReadLineAsync().Wait(TimeSpan.FromSeconds(10)), Is.True); Assert.That(command.Task.IsCompleted, Is.False); diff --git a/src/DistributedLock.Tests/Infrastructure/Postgres/TestingPostgresProviders.cs b/src/DistributedLock.Tests/Infrastructure/Postgres/TestingPostgresProviders.cs index 8d89e66d..0784a8fa 100644 --- a/src/DistributedLock.Tests/Infrastructure/Postgres/TestingPostgresProviders.cs +++ b/src/DistributedLock.Tests/Infrastructure/Postgres/TestingPostgresProviders.cs @@ -21,6 +21,8 @@ public override IDistributedLock CreateLockWithExactName(string name) => public override string GetSafeName(string name) => new PostgresAdvisoryLockKey(name, allowHashing: true).ToString(); + public override string GetConnectionStringForCrossProcessTest() => TestingPostgresDb.DefaultConnectionString; + internal static Action ToPostgresOptions((bool useMultiplexing, bool useTransaction, TimeSpan? keepaliveCadence) options) => o => { o.UseMultiplexing(options.useMultiplexing); diff --git a/src/DistributedLock.Tests/Infrastructure/Shared/PostgresCredentials.cs b/src/DistributedLock.Tests/Infrastructure/Shared/PostgresCredentials.cs deleted file mode 100644 index 3222829a..00000000 --- a/src/DistributedLock.Tests/Infrastructure/Shared/PostgresCredentials.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Npgsql; -using System; -using System.IO; - -namespace Medallion.Threading.Tests; - -internal static class PostgresCredentials -{ - private static (string username, string password) GetCredentials(string baseDirectory) - { - var file = Path.GetFullPath(Path.Combine(baseDirectory, "..", "..", "..", "credentials", "postgres.txt")); - if (!File.Exists(file)) { throw new InvalidOperationException($"Unable to find postgres credentials file {file}"); } - var lines = File.ReadAllLines(file); - if (lines.Length != 2) { throw new FormatException($"{file} must contain exactly 2 lines of text"); } - return (lines[0], lines[1]); - } - - public static string GetConnectionString(string baseDirectory) - { - var (username, password) = GetCredentials(baseDirectory); - - return new NpgsqlConnectionStringBuilder - { - Port = 5432, - Host = "localhost", - Database = "postgres", - Username = username, - Password = password, - PersistSecurityInfo = true, - ApplicationName = SqlServerCredentials.ApplicationName, - // set a high pool size so that we don't empty the pool through things like lock abandonment tests - MaxPoolSize = 500, - }.ConnectionString; - } -} diff --git a/src/DistributedLock.Tests/Infrastructure/TestingLockProvider.cs b/src/DistributedLock.Tests/Infrastructure/TestingLockProvider.cs index 407e04c2..708cbc93 100644 --- a/src/DistributedLock.Tests/Infrastructure/TestingLockProvider.cs +++ b/src/DistributedLock.Tests/Infrastructure/TestingLockProvider.cs @@ -15,6 +15,8 @@ public abstract class TestingLockProvider : ITestingNameProvider, IDi public virtual string GetCrossProcessLockType() => this.CreateLock(string.Empty).GetType().Name; public virtual void Dispose() => this.Strategy.Dispose(); + public virtual string GetConnectionStringForCrossProcessTest() => ""; + /// /// Returns a lock whose name is based on /// diff --git a/src/DistributedLockTaker/Program.cs b/src/DistributedLockTaker/Program.cs index c15d0a5a..119f104d 100644 --- a/src/DistributedLockTaker/Program.cs +++ b/src/DistributedLockTaker/Program.cs @@ -24,6 +24,7 @@ public static int Main(string[] args) { var type = args[0]; var name = args[1]; + var connectionString = args[2]; IDisposable? handle; switch (type) { @@ -40,10 +41,10 @@ public static int Main(string[] args) handle = new SqlDistributedSemaphore(name, maxCount: 5, connectionString: SqlServerCredentials.ConnectionString).Acquire(); break; case nameof(PostgresDistributedLock): - handle = new PostgresDistributedLock(new PostgresAdvisoryLockKey(name), PostgresCredentials.GetConnectionString(Environment.CurrentDirectory)).Acquire(); + handle = new PostgresDistributedLock(new PostgresAdvisoryLockKey(name), connectionString).Acquire(); break; case "Write" + nameof(PostgresDistributedReaderWriterLock): - handle = new PostgresDistributedReaderWriterLock(new PostgresAdvisoryLockKey(name), PostgresCredentials.GetConnectionString(Environment.CurrentDirectory)).AcquireWriteLock(); + handle = new PostgresDistributedReaderWriterLock(new PostgresAdvisoryLockKey(name), connectionString).AcquireWriteLock(); break; case nameof(MySqlDistributedLock): handle = new MySqlDistributedLock(name, MySqlCredentials.GetConnectionString(Environment.CurrentDirectory)).Acquire(); From 75aa661e7946e5b62fa33115e8850ae321622703 Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Mon, 3 Jun 2024 21:22:59 +0300 Subject: [PATCH 04/27] working-redis --- .github/workflows/ci.yaml | 21 ++++ src/Directory.Packages.props | 1 + src/DistributedLock.Core/packages.lock.json | 6 +- .../Data/ConnectionStringStrategyTestCases.cs | 10 +- .../Data/DbSemaphoreTestCases.cs | 10 +- ...onnectionOrTransactionStrategyTestCases.cs | 10 +- .../ExternalConnectionStrategyTestCases.cs | 10 +- .../ExternalTransactionStrategyTestCases.cs | 10 +- ...MultiplexingConnectionStrategyTestCases.cs | 10 +- .../Data/OwnedConnectionStrategyTestCases.cs | 10 +- .../Data/OwnedTransactionStrategyTestCases.cs | 10 +- ...erLockConnectionStringStrategyTestCases.cs | 10 +- .../DistributedLockCoreTestCases.cs | 11 ++- ...istributedReaderWriterLockCoreTestCases.cs | 11 ++- .../DistributedSemaphoreCoreTestCases.cs | 12 ++- ...pgradeableReaderWriterLockCoreTestCases.cs | 10 +- .../Redis/RedisExtensionTestCases.cs | 9 +- .../RedisSynchronizationCoreTestCases.cs | 17 ++-- .../ZooKeeperSynchronizationCoreTestCases.cs | 9 +- .../DistributedLock.Tests.csproj | 1 + ...ngAzureBlobLeaseSynchronizationStrategy.cs | 5 +- .../Data/TestingDbSynchronizationStrategy.cs | 12 +-- .../Infrastructure/Postgres/PostgresDb.cs | 7 +- .../Infrastructure/Redis/RedisServer.cs | 83 ++-------------- .../Infrastructure/Redis/RedisSetUpFixture.cs | 11 ++- .../Redis/TestingRedisDatabaseProvider.cs | 96 ++++++++++++++----- .../Redis/TestingRedisProviders.cs | 2 + .../TestingRedisSynchronizationStrategy.cs | 4 + .../Infrastructure/TestingLockProvider.cs | 8 +- .../TestingReaderWriterLockAsMutexProvider.cs | 6 +- .../TestingReaderWriterLockProvider.cs | 5 +- .../TestingSemaphoreAsMutexProvider.cs | 6 +- .../TestingSemaphoreProvider.cs | 5 +- .../TestingSynchronizationStrategy.cs | 6 +- .../AzureBlobLeaseDistributedLockTest.cs | 26 ++--- .../Tests/Redis/RedisDistributedLockTest.cs | 3 +- .../RedisDistributedReaderWriterLockTest.cs | 5 +- ...sDistributedSynchronizationProviderTest.cs | 3 +- src/DistributedLock.Tests/packages.lock.json | 9 ++ src/DistributedLockTaker/Program.cs | 45 ++++++--- src/DistributedLockTaker/packages.lock.json | 6 +- 41 files changed, 347 insertions(+), 204 deletions(-) create mode 100644 .github/workflows/ci.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000..24d35c5e --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,21 @@ +name: CI + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET 8 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Build + run: dotnet build src + + - name: Redis Tests + run: dotnet test src --filter Name~Redis \ No newline at end of file diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 8e978369..5096e976 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -23,6 +23,7 @@ + diff --git a/src/DistributedLock.Core/packages.lock.json b/src/DistributedLock.Core/packages.lock.json index 65a05592..8260dea4 100644 --- a/src/DistributedLock.Core/packages.lock.json +++ b/src/DistributedLock.Core/packages.lock.json @@ -148,9 +148,9 @@ "net8.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.5, )", - "resolved": "8.0.5", - "contentHash": "4ISW1Ndgz86FkIbu5rVlMqhhtojdy5rUlxC/N+9kPh9qbYcvHiCvYbHKzAPVIx9OPYIjT9trXt7JI42Y5Ukq6A==" + "requested": "[8.0.6, )", + "resolved": "8.0.6", + "contentHash": "E+lDylsTeP4ZiDmnEkiJ5wobnGaIJzFhOgZppznJCb69UZgbh6G3cfv1pnLDLLBx6JAgl0kAlnINDeT3uCuczQ==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", diff --git a/src/DistributedLock.Tests/AbstractTestCases/Data/ConnectionStringStrategyTestCases.cs b/src/DistributedLock.Tests/AbstractTestCases/Data/ConnectionStringStrategyTestCases.cs index e9cc4ce5..bf3a50d5 100644 --- a/src/DistributedLock.Tests/AbstractTestCases/Data/ConnectionStringStrategyTestCases.cs +++ b/src/DistributedLock.Tests/AbstractTestCases/Data/ConnectionStringStrategyTestCases.cs @@ -10,8 +10,14 @@ public abstract class ConnectionStringStrategyTestCases this._lockProvider = new TLockProvider(); - [TearDown] public void TearDown() => this._lockProvider.Dispose(); + [SetUp] + public async Task SetUp() + { + this._lockProvider = new TLockProvider(); + await this._lockProvider.SetupAsync(); + } + [TearDown] + public async Task TearDown() => await this._lockProvider.DisposeAsync(); /// /// Tests that internally-owned connections are properly cleaned up by disposing the lock handle diff --git a/src/DistributedLock.Tests/AbstractTestCases/Data/DbSemaphoreTestCases.cs b/src/DistributedLock.Tests/AbstractTestCases/Data/DbSemaphoreTestCases.cs index 91240ac4..4c86b75e 100644 --- a/src/DistributedLock.Tests/AbstractTestCases/Data/DbSemaphoreTestCases.cs +++ b/src/DistributedLock.Tests/AbstractTestCases/Data/DbSemaphoreTestCases.cs @@ -9,8 +9,14 @@ public abstract class DbSemaphoreTestCases { private TSemaphoreProvider _semaphoreProvider = default!; - [SetUp] public void SetUp() => this._semaphoreProvider = new TSemaphoreProvider(); - [TearDown] public void TearDown() => this._semaphoreProvider.Dispose(); + [SetUp] + public async Task SetUp() + { + this._semaphoreProvider = new TSemaphoreProvider(); + await this._semaphoreProvider.SetupAsync(); + } + [TearDown] + public async Task TearDown() => await this._semaphoreProvider.DisposeAsync(); /// /// This case and several that follow test "self-deadlock", where a semaphore acquire cannot possibly succeed because diff --git a/src/DistributedLock.Tests/AbstractTestCases/Data/ExternalConnectionOrTransactionStrategyTestCases.cs b/src/DistributedLock.Tests/AbstractTestCases/Data/ExternalConnectionOrTransactionStrategyTestCases.cs index 7a6d1e37..10d1cd5e 100644 --- a/src/DistributedLock.Tests/AbstractTestCases/Data/ExternalConnectionOrTransactionStrategyTestCases.cs +++ b/src/DistributedLock.Tests/AbstractTestCases/Data/ExternalConnectionOrTransactionStrategyTestCases.cs @@ -12,8 +12,14 @@ public abstract class ExternalConnectionOrTransactionStrategyTestCases this._lockProvider = new TLockProvider(); - [TearDown] public void TearDown() => this._lockProvider.Dispose(); + [SetUp] + public async Task SetUp() + { + this._lockProvider = new TLockProvider(); + await this._lockProvider.SetupAsync(); + } + [TearDown] + public async Task TearDown() => await this._lockProvider.DisposeAsync(); [Test] [NonParallelizable, Retry(tryCount: 3)] // timing sensitive for SqlSemaphore (see comment in that file regarding the 32ms wait) diff --git a/src/DistributedLock.Tests/AbstractTestCases/Data/ExternalConnectionStrategyTestCases.cs b/src/DistributedLock.Tests/AbstractTestCases/Data/ExternalConnectionStrategyTestCases.cs index cd42e918..fb554e48 100644 --- a/src/DistributedLock.Tests/AbstractTestCases/Data/ExternalConnectionStrategyTestCases.cs +++ b/src/DistributedLock.Tests/AbstractTestCases/Data/ExternalConnectionStrategyTestCases.cs @@ -8,8 +8,14 @@ public abstract class ExternalConnectionStrategyTestCases { private TLockProvider _lockProvider = default!; - [SetUp] public void SetUp() => this._lockProvider = new TLockProvider(); - [TearDown] public void TearDown() => this._lockProvider.Dispose(); + [SetUp] + public async Task SetUp() + { + this._lockProvider = new TLockProvider(); + await this._lockProvider.SetupAsync(); + } + [TearDown] + public async Task TearDown() => await this._lockProvider.DisposeAsync(); [Test] public void TestCloseLockOnClosedConnection() diff --git a/src/DistributedLock.Tests/AbstractTestCases/Data/ExternalTransactionStrategyTestCases.cs b/src/DistributedLock.Tests/AbstractTestCases/Data/ExternalTransactionStrategyTestCases.cs index ea4595a3..136bf325 100644 --- a/src/DistributedLock.Tests/AbstractTestCases/Data/ExternalTransactionStrategyTestCases.cs +++ b/src/DistributedLock.Tests/AbstractTestCases/Data/ExternalTransactionStrategyTestCases.cs @@ -10,8 +10,14 @@ public abstract class ExternalTransactionStrategyTestCases { private TLockProvider _lockProvider = default!; - [SetUp] public void SetUp() => this._lockProvider = new TLockProvider(); - [TearDown] public void TearDown() => this._lockProvider.Dispose(); + [SetUp] + public async Task SetUp() + { + this._lockProvider = new TLockProvider(); + await this._lockProvider.SetupAsync(); + } + [TearDown] + public async Task TearDown() => await this._lockProvider.DisposeAsync(); [Test] public void TestScopedToTransactionOnly() diff --git a/src/DistributedLock.Tests/AbstractTestCases/Data/MultiplexingConnectionStrategyTestCases.cs b/src/DistributedLock.Tests/AbstractTestCases/Data/MultiplexingConnectionStrategyTestCases.cs index d226de6d..5ce0c031 100644 --- a/src/DistributedLock.Tests/AbstractTestCases/Data/MultiplexingConnectionStrategyTestCases.cs +++ b/src/DistributedLock.Tests/AbstractTestCases/Data/MultiplexingConnectionStrategyTestCases.cs @@ -11,8 +11,14 @@ public abstract class MultiplexingConnectionStrategyTestCases this._lockProvider = new TLockProvider(); - [TearDown] public void TearDown() => this._lockProvider.Dispose(); + [SetUp] + public async Task SetUp() + { + this._lockProvider = new TLockProvider(); + await this._lockProvider.SetupAsync(); + } + [TearDown] + public async Task TearDown() => await this._lockProvider.DisposeAsync(); /// /// Similar to but demonstrates diff --git a/src/DistributedLock.Tests/AbstractTestCases/Data/OwnedConnectionStrategyTestCases.cs b/src/DistributedLock.Tests/AbstractTestCases/Data/OwnedConnectionStrategyTestCases.cs index 1e25fb3c..bdf7ba0c 100644 --- a/src/DistributedLock.Tests/AbstractTestCases/Data/OwnedConnectionStrategyTestCases.cs +++ b/src/DistributedLock.Tests/AbstractTestCases/Data/OwnedConnectionStrategyTestCases.cs @@ -10,8 +10,14 @@ public abstract class OwnedConnectionStrategyTestCases { private TLockProvider _lockProvider = default!; - [SetUp] public void SetUp() => this._lockProvider = new TLockProvider(); - [TearDown] public void TearDown() => this._lockProvider.Dispose(); + [SetUp] + public async Task SetUp() + { + this._lockProvider = new TLockProvider(); + await this._lockProvider.SetupAsync(); + } + [TearDown] + public async Task TearDown() => await this._lockProvider.DisposeAsync(); /// /// Tests that our idle session killer works, therefore validating our other tests that use it. diff --git a/src/DistributedLock.Tests/AbstractTestCases/Data/OwnedTransactionStrategyTestCases.cs b/src/DistributedLock.Tests/AbstractTestCases/Data/OwnedTransactionStrategyTestCases.cs index 31fc9126..a66d67cc 100644 --- a/src/DistributedLock.Tests/AbstractTestCases/Data/OwnedTransactionStrategyTestCases.cs +++ b/src/DistributedLock.Tests/AbstractTestCases/Data/OwnedTransactionStrategyTestCases.cs @@ -9,8 +9,14 @@ public abstract class OwnedTransactionStrategyTestCases { private TLockProvider _lockProvider = default!; - [SetUp] public void SetUp() => this._lockProvider = new TLockProvider(); - [TearDown] public void TearDown() => this._lockProvider.Dispose(); + [SetUp] + public async Task SetUp() + { + this._lockProvider = new TLockProvider(); + await this._lockProvider.SetupAsync(); + } + [TearDown] + public async Task TearDown() => await this._lockProvider.DisposeAsync(); /// /// Validates that we use the default isolation level to avoid the problem described diff --git a/src/DistributedLock.Tests/AbstractTestCases/Data/UpgradeableReaderWriterLockConnectionStringStrategyTestCases.cs b/src/DistributedLock.Tests/AbstractTestCases/Data/UpgradeableReaderWriterLockConnectionStringStrategyTestCases.cs index c66540e3..22480be8 100644 --- a/src/DistributedLock.Tests/AbstractTestCases/Data/UpgradeableReaderWriterLockConnectionStringStrategyTestCases.cs +++ b/src/DistributedLock.Tests/AbstractTestCases/Data/UpgradeableReaderWriterLockConnectionStringStrategyTestCases.cs @@ -9,8 +9,14 @@ public abstract class UpgradeableReaderWriterLockConnectionStringStrategyTestCas { private TLockProvider _lockProvider = default!; - [SetUp] public void SetUp() => this._lockProvider = new TLockProvider(); - [TearDown] public void TearDown() => this._lockProvider.Dispose(); + [SetUp] + public async Task SetUp() + { + this._lockProvider = new TLockProvider(); + await this._lockProvider.SetupAsync(); + } + [TearDown] + public async Task TearDown() => await this._lockProvider.DisposeAsync(); /// /// Tests the logic where upgrading a connection stops and restarts the keepalive diff --git a/src/DistributedLock.Tests/AbstractTestCases/DistributedLockCoreTestCases.cs b/src/DistributedLock.Tests/AbstractTestCases/DistributedLockCoreTestCases.cs index ec6130c5..08f9c243 100644 --- a/src/DistributedLock.Tests/AbstractTestCases/DistributedLockCoreTestCases.cs +++ b/src/DistributedLock.Tests/AbstractTestCases/DistributedLockCoreTestCases.cs @@ -14,14 +14,19 @@ public abstract class DistributedLockCoreTestCases private TLockProvider _lockProvider = default!; private readonly List _cleanupActions = []; - [SetUp] public void SetUp() => this._lockProvider = new TLockProvider(); + [SetUp] + public async Task SetUp() + { + this._lockProvider = new TLockProvider(); + await this._lockProvider.SetupAsync(); + } [TearDown] - public void TearDown() + public async Task TearDown() { this._cleanupActions.ForEach(a => a()); this._cleanupActions.Clear(); - this._lockProvider.Dispose(); + await this._lockProvider.DisposeAsync(); } [Test] diff --git a/src/DistributedLock.Tests/AbstractTestCases/DistributedReaderWriterLockCoreTestCases.cs b/src/DistributedLock.Tests/AbstractTestCases/DistributedReaderWriterLockCoreTestCases.cs index fcfb8686..bab2435f 100644 --- a/src/DistributedLock.Tests/AbstractTestCases/DistributedReaderWriterLockCoreTestCases.cs +++ b/src/DistributedLock.Tests/AbstractTestCases/DistributedReaderWriterLockCoreTestCases.cs @@ -8,8 +8,15 @@ public abstract class DistributedReaderWriterLockCoreTestCases this._lockProvider = new TLockProvider(); - [TearDown] public void TearDown() => this._lockProvider.Dispose(); + [SetUp] + public async Task SetUp() + { + this._lockProvider = new TLockProvider(); + await this._lockProvider.SetupAsync(); + } + + [TearDown] + public async Task TearDown() => await this._lockProvider.DisposeAsync(); [Test] public async Task TestMultipleReadersSingleWriter() diff --git a/src/DistributedLock.Tests/AbstractTestCases/DistributedSemaphoreCoreTestCases.cs b/src/DistributedLock.Tests/AbstractTestCases/DistributedSemaphoreCoreTestCases.cs index c0c934a1..3caf3361 100644 --- a/src/DistributedLock.Tests/AbstractTestCases/DistributedSemaphoreCoreTestCases.cs +++ b/src/DistributedLock.Tests/AbstractTestCases/DistributedSemaphoreCoreTestCases.cs @@ -10,8 +10,14 @@ public abstract class DistributedSemaphoreCoreTestCases this._semaphoreProvider = new TSemaphoreProvider(); - [TearDown] public void TearDown() => this._semaphoreProvider.Dispose(); + [SetUp] + public async Task SetUp() + { + this._semaphoreProvider = new TSemaphoreProvider(); + await this._semaphoreProvider.SetupAsync(); + } + [TearDown] + public async Task TearDown() => await this._semaphoreProvider.DisposeAsync(); [Test] public void TestMaxCount() @@ -46,7 +52,7 @@ public void TestConcurrencyHandling() Thread.Sleep(10); Interlocked.Decrement(ref counter); } - }, + }, TaskCreationOptions.LongRunning // dedicated thread )) .ToArray(); diff --git a/src/DistributedLock.Tests/AbstractTestCases/DistributedUpgradeableReaderWriterLockCoreTestCases.cs b/src/DistributedLock.Tests/AbstractTestCases/DistributedUpgradeableReaderWriterLockCoreTestCases.cs index dd316422..191d6eb9 100644 --- a/src/DistributedLock.Tests/AbstractTestCases/DistributedUpgradeableReaderWriterLockCoreTestCases.cs +++ b/src/DistributedLock.Tests/AbstractTestCases/DistributedUpgradeableReaderWriterLockCoreTestCases.cs @@ -8,8 +8,14 @@ public abstract class DistributedUpgradeableReaderWriterLockCoreTestCases this._lockProvider = new TLockProvider(); - [TearDown] public void TearDown() => this._lockProvider.Dispose(); + [SetUp] + public async Task SetUp() + { + this._lockProvider = new TLockProvider(); + await this._lockProvider.SetupAsync(); + } + [TearDown] + public async Task TearDown() => await this._lockProvider.DisposeAsync(); [Test] public void TestMultipleReadersSingleWriter() diff --git a/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisExtensionTestCases.cs b/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisExtensionTestCases.cs index 913fb918..cfbf14bc 100644 --- a/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisExtensionTestCases.cs +++ b/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisExtensionTestCases.cs @@ -9,10 +9,13 @@ public abstract class RedisExtensionTestCases private TLockProvider _provider = default!; [SetUp] - public void SetUp() => this._provider = new TLockProvider(); - + public async Task SetUp() + { + this._provider = new TLockProvider(); + await this._provider.SetupAsync(); + } [TearDown] - public void TearDown() => this._provider.Dispose(); + public async Task TearDown() => await this._provider.DisposeAsync(); [Test] [NonParallelizable, Retry(tryCount: 3)] // timing-sensitive diff --git a/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs b/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs index 1cc53335..99049d4a 100644 --- a/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs +++ b/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs @@ -14,10 +14,13 @@ public abstract class RedisSynchronizationCoreTestCases private TLockProvider _provider = default!; [SetUp] - public void SetUp() => this._provider = new TLockProvider(); - + public async Task SetUp() + { + this._provider = new TLockProvider(); + await this._provider.SetupAsync(); + } [TearDown] - public void TearDown() => this._provider.Dispose(); + public async Task TearDown() => await this._provider.DisposeAsync(); [Test] public void TestMajorityFaultingDatabasesCauseAcquireToThrow() @@ -104,7 +107,7 @@ public async Task TestFailedAcquireReleasesWhatHasAlreadyBeenAcquired() var failDatabase = CreateDatabaseMock(); MockDatabase(failDatabase, () => { @event.Wait(); return false; }); - this._provider.Strategy.DatabaseProvider.Databases = new[] { RedisServer.GetDefaultServer(0).Multiplexer.GetDatabase(), failDatabase.Object }; + this._provider.Strategy.DatabaseProvider.Databases = new[] { RedisServer.CreateDatabase(_provider.Strategy.DatabaseProvider.Redis), failDatabase.Object }; var @lock = this._provider.CreateLock("lock"); var acquireTask = @lock.TryAcquireAsync().AsTask(); @@ -112,7 +115,7 @@ public async Task TestFailedAcquireReleasesWhatHasAlreadyBeenAcquired() @event.Set(); Assert.That(await acquireTask, Is.Null); - this._provider.Strategy.DatabaseProvider.Databases = new[] { RedisServer.GetDefaultServer(0).Multiplexer.GetDatabase() }; + this._provider.Strategy.DatabaseProvider.Databases = new[] { RedisServer.CreateDatabase(_provider.Strategy.DatabaseProvider.Redis) }; var singleDatabaseLock = this._provider.CreateLock("lock"); using var handle = await singleDatabaseLock.TryAcquireAsync(); Assert.That(handle, Is.Not.Null); @@ -135,9 +138,9 @@ public void TestAcquireWithLockPrefix() using var explicitPrefixHandle = explicitPrefixLock.TryAcquire(); Assert.That(explicitPrefixHandle, Is.Null); - static IDatabase CreateDatabase(string? keyPrefix = null) + IDatabase CreateDatabase(string? keyPrefix = null) { - var database = RedisServer.GetDefaultServer(0).Multiplexer.GetDatabase(); + var database = RedisServer.CreateDatabase(_provider.Strategy.DatabaseProvider.Redis); return keyPrefix is null ? database : database.WithKeyPrefix(keyPrefix); } } diff --git a/src/DistributedLock.Tests/AbstractTestCases/ZooKeeper/ZooKeeperSynchronizationCoreTestCases.cs b/src/DistributedLock.Tests/AbstractTestCases/ZooKeeper/ZooKeeperSynchronizationCoreTestCases.cs index b6aa6426..5aa46c86 100644 --- a/src/DistributedLock.Tests/AbstractTestCases/ZooKeeper/ZooKeeperSynchronizationCoreTestCases.cs +++ b/src/DistributedLock.Tests/AbstractTestCases/ZooKeeper/ZooKeeperSynchronizationCoreTestCases.cs @@ -13,10 +13,13 @@ public abstract class ZooKeeperSynchronizationCoreTestCases private TLockProvider _provider = default!; [SetUp] - public void SetUp() => this._provider = new TLockProvider(); - + public async Task SetUp() + { + this._provider = new TLockProvider(); + await this._provider.SetupAsync(); + } [TearDown] - public void TearDown() => this._provider.Dispose(); + public async Task TearDown() => await this._provider.DisposeAsync(); [Test] public async Task TestDoesNotAttemptToCreateOrDeleteExistingNode() diff --git a/src/DistributedLock.Tests/DistributedLock.Tests.csproj b/src/DistributedLock.Tests/DistributedLock.Tests.csproj index 3b5b484e..02f5697f 100644 --- a/src/DistributedLock.Tests/DistributedLock.Tests.csproj +++ b/src/DistributedLock.Tests/DistributedLock.Tests.csproj @@ -28,6 +28,7 @@ + diff --git a/src/DistributedLock.Tests/Infrastructure/Azure/TestingAzureBlobLeaseSynchronizationStrategy.cs b/src/DistributedLock.Tests/Infrastructure/Azure/TestingAzureBlobLeaseSynchronizationStrategy.cs index eaedd1a6..9c550deb 100644 --- a/src/DistributedLock.Tests/Infrastructure/Azure/TestingAzureBlobLeaseSynchronizationStrategy.cs +++ b/src/DistributedLock.Tests/Infrastructure/Azure/TestingAzureBlobLeaseSynchronizationStrategy.cs @@ -43,10 +43,11 @@ public override void PrepareForHighContention(ref int maxConcurrentAcquires) this.CreateBlobBeforeLockIsCreated = true; } - public override void Dispose() + public override ValueTask DisposeAsync() { try { this._disposables.Dispose(); } - finally { base.Dispose(); } + finally { base.DisposeAsync(); } + return ValueTask.CompletedTask; } private class HandleLostScope : IDisposable diff --git a/src/DistributedLock.Tests/Infrastructure/Data/TestingDbSynchronizationStrategy.cs b/src/DistributedLock.Tests/Infrastructure/Data/TestingDbSynchronizationStrategy.cs index 91e8bc0e..b0064b34 100644 --- a/src/DistributedLock.Tests/Infrastructure/Data/TestingDbSynchronizationStrategy.cs +++ b/src/DistributedLock.Tests/Infrastructure/Data/TestingDbSynchronizationStrategy.cs @@ -27,7 +27,7 @@ protected TestingDbSynchronizationStrategy() : base(new TDb()) { } public new TDb Db => (TDb)base.Db; - public override void Dispose() + public override ValueTask DisposeAsync() { // if we have a uniquely-named connection, clear it's pool to avoid "leaking" connections into pools we'll never // use again @@ -37,7 +37,7 @@ public override void Dispose() this.Db.ClearPool(connection); } - base.Dispose(); + return base.DisposeAsync(); } } @@ -167,10 +167,10 @@ public override TestingDbConnectionOptions GetConnectionOptions() return new TestingDbConnectionOptions { Connection = connection }; } - public override void Dispose() + public override ValueTask DisposeAsync() { this._disposables.Dispose(); - base.Dispose(); + return base.DisposeAsync(); } } @@ -211,9 +211,9 @@ public override TestingDbConnectionOptions GetConnectionOptions() return new TestingDbConnectionOptions { Transaction = transaction }; } - public override void Dispose() + public override ValueTask DisposeAsync() { this._disposables.Dispose(); - base.Dispose(); + return base.DisposeAsync(); } } diff --git a/src/DistributedLock.Tests/Infrastructure/Postgres/PostgresDb.cs b/src/DistributedLock.Tests/Infrastructure/Postgres/PostgresDb.cs index 515bd5f3..e7f8ccb5 100644 --- a/src/DistributedLock.Tests/Infrastructure/Postgres/PostgresDb.cs +++ b/src/DistributedLock.Tests/Infrastructure/Postgres/PostgresDb.cs @@ -8,6 +8,11 @@ public class PostgresDb static PostgresDb() { - Container.StartAsync().Wait(); + Initialize().Wait(); + } + + private static async Task Initialize() + { + await Container.StartAsync(); } } \ No newline at end of file diff --git a/src/DistributedLock.Tests/Infrastructure/Redis/RedisServer.cs b/src/DistributedLock.Tests/Infrastructure/Redis/RedisServer.cs index d524205a..31c639db 100644 --- a/src/DistributedLock.Tests/Infrastructure/Redis/RedisServer.cs +++ b/src/DistributedLock.Tests/Infrastructure/Redis/RedisServer.cs @@ -1,40 +1,13 @@ -using Medallion.Shell; -using StackExchange.Redis; +using StackExchange.Redis; +using Testcontainers.Redis; namespace Medallion.Threading.Tests.Redis; internal class RedisServer { - // redis default is 6379, so go one above that - private static readonly int MinDynamicPort = RedisPorts.DefaultPorts.Max() + 1, MaxDynamicPort = MinDynamicPort + 100; - - // it's important for this to be lazy because it doesn't work when running on Linux - private static readonly Lazy WslPath = new( - () => Directory.GetDirectories(@"C:\Windows\WinSxS") - .Select(d => Path.Combine(d, "wsl.exe")) - .Where(File.Exists) - .OrderByDescending(File.GetCreationTimeUtc) - .First() - ); - - private static readonly Dictionary ActiveServersByPort = []; - private static readonly RedisServer[] DefaultServers = new RedisServer[RedisPorts.DefaultPorts.Count]; - - private readonly Command _command; - - public RedisServer(bool allowAdmin = false) : this(null, allowAdmin) { } - - private RedisServer(int? port, bool allowAdmin) + public RedisServer(int port, bool allowAdmin) { - lock (ActiveServersByPort) - { - this.Port = port ?? Enumerable.Range(MinDynamicPort, count: MaxDynamicPort - MinDynamicPort + 1) - .First(p => !ActiveServersByPort.ContainsKey(p)); - this._command = Command.Run(WslPath.Value, ["redis-server", "--port", this.Port], options: o => o.StartInfo(si => si.RedirectStandardInput = false)) - .RedirectTo(Console.Out) - .RedirectStandardErrorTo(Console.Error); - ActiveServersByPort.Add(this.Port, this); - } + this.Port = port; this.Multiplexer = ConnectionMultiplexer.Connect($"localhost:{this.Port},abortConnect=false{(allowAdmin ? ",allowAdmin=true" : string.Empty)}"); // Clean the db to ensure it is empty. Running an arbitrary command also ensures that // the db successfully spun up before we proceed (Connect seemingly can complete before that happens). @@ -43,49 +16,11 @@ private RedisServer(int? port, bool allowAdmin) this.Multiplexer.GetDatabase().Execute("flushall", Array.Empty(), CommandFlags.DemandMaster); } - public int ProcessId => this._command.ProcessId; public int Port { get; } public ConnectionMultiplexer Multiplexer { get; } - - public static RedisServer GetDefaultServer(int index) - { - lock (DefaultServers) - { - return DefaultServers[index] ??= new RedisServer(RedisPorts.DefaultPorts[index], allowAdmin: false); - } - } - - public static void DisposeAll() - { - lock (ActiveServersByPort) - { - var shutdownTasks = ActiveServersByPort.Values - .Select(async server => - { - // When testing the case of a server outage, we'll have manually shut down some servers. - // In that case, we shouldn't attempt to connect to them since that will fail. - var isConnected = server.Multiplexer.GetServers().Any(s => s.IsConnected); - server.Multiplexer.Dispose(); - try - { - if (isConnected) - { - using var adminMultiplexer = await ConnectionMultiplexer.ConnectAsync($"localhost:{server.Port},allowAdmin=true"); - adminMultiplexer.GetServer("localhost", server.Port).Shutdown(ShutdownMode.Never); - } - } - finally - { - if (!await server._command.Task.TryWaitAsync(TimeSpan.FromSeconds(5))) - { - server._command.Kill(); - throw new InvalidOperationException("Forced to kill Redis server"); - } - } - }) - .ToArray(); - ActiveServersByPort.Clear(); - Task.WaitAll(shutdownTasks); - } - } + + public static RedisServer Create(RedisContainer container, bool allowAdmin = false) + => new RedisServer(container.GetMappedPublicPort(RedisBuilder.RedisPort), allowAdmin); + public static IDatabase CreateDatabase(RedisContainer container, bool allowAdmin = false) + => Create(container, allowAdmin).Multiplexer.GetDatabase(); } diff --git a/src/DistributedLock.Tests/Infrastructure/Redis/RedisSetUpFixture.cs b/src/DistributedLock.Tests/Infrastructure/Redis/RedisSetUpFixture.cs index 0226708a..e2bbdfe5 100644 --- a/src/DistributedLock.Tests/Infrastructure/Redis/RedisSetUpFixture.cs +++ b/src/DistributedLock.Tests/Infrastructure/Redis/RedisSetUpFixture.cs @@ -1,13 +1,20 @@ using NUnit.Framework; +using Testcontainers.Redis; namespace Medallion.Threading.Tests.Redis; [SetUpFixture] public class RedisSetUpFixture { + public static RedisContainer Redis; + [OneTimeSetUp] - public void OneTimeSetUp() { } + public static async Task OneTimeSetUp() + { + Redis = new RedisBuilder().Build(); + await Redis.StartAsync(); + } [OneTimeTearDown] - public void OneTimeTearDown() => RedisServer.DisposeAll(); + public async Task OneTimeTearDown() => await Redis.DisposeAsync(); } diff --git a/src/DistributedLock.Tests/Infrastructure/Redis/TestingRedisDatabaseProvider.cs b/src/DistributedLock.Tests/Infrastructure/Redis/TestingRedisDatabaseProvider.cs index 875ec6c5..8b7eb18c 100644 --- a/src/DistributedLock.Tests/Infrastructure/Redis/TestingRedisDatabaseProvider.cs +++ b/src/DistributedLock.Tests/Infrastructure/Redis/TestingRedisDatabaseProvider.cs @@ -1,62 +1,108 @@ -using NUnit.Framework; -using StackExchange.Redis; +using StackExchange.Redis; using StackExchange.Redis.KeyspaceIsolation; -using System.Diagnostics; +using Testcontainers.Redis; namespace Medallion.Threading.Tests.Redis; public abstract class TestingRedisDatabaseProvider { - protected TestingRedisDatabaseProvider(IEnumerable databases) - { - this.Databases = databases.ToArray(); - } - - protected TestingRedisDatabaseProvider(int count) - : this(Enumerable.Range(0, count).Select(i => RedisServer.GetDefaultServer(i).Multiplexer.GetDatabase())) - { - } - // publicly settable so that callers can alter the dbs in use public IReadOnlyList Databases { get; set; } + public RedisContainer Redis { get; protected set; } + public abstract string ConnectionStrings { get; } public virtual string CrossProcessLockTypeSuffix => this.Databases.Count.ToString(); + + public abstract ValueTask SetupAsync(); + public abstract ValueTask DisposeAsync(); } public sealed class TestingRedisSingleDatabaseProvider : TestingRedisDatabaseProvider { - public TestingRedisSingleDatabaseProvider() : base(count: 1) { } + public override string ConnectionStrings => Redis.GetConnectionString(); + public override async ValueTask SetupAsync() + { + this.Redis = new RedisBuilder().Build(); + await this.Redis.StartAsync(); + + this.Databases = [RedisServer.CreateDatabase(this.Redis)]; + } + + public override ValueTask DisposeAsync() => this.Redis.DisposeAsync(); } public sealed class TestingRedisWithKeyPrefixSingleDatabaseProvider : TestingRedisDatabaseProvider { - public TestingRedisWithKeyPrefixSingleDatabaseProvider() - : base(new[] { RedisServer.GetDefaultServer(0).Multiplexer.GetDatabase().WithKeyPrefix("distributed_locks:") }) { } - + public override string ConnectionStrings => Redis.GetConnectionString(); public override string CrossProcessLockTypeSuffix => "1WithPrefix"; + + public override async ValueTask SetupAsync() + { + this.Redis = new RedisBuilder().Build(); + await this.Redis.StartAsync(); + + this.Databases = [RedisServer.CreateDatabase(this.Redis).WithKeyPrefix("distributed_locks:")]; + } + + public override ValueTask DisposeAsync() => this.Redis.DisposeAsync(); } public sealed class TestingRedis3DatabaseProvider : TestingRedisDatabaseProvider { - public TestingRedis3DatabaseProvider() : base(count: 3) { } + private RedisContainer[] _redises = default!; + + public override string ConnectionStrings => string.Join("||", _redises.Select(x => x.GetConnectionString())); + + public override async ValueTask SetupAsync() + { + this._redises = Enumerable.Range(0, 3).Select(_ => new RedisBuilder().Build()).ToArray(); + await Task.WhenAll(this._redises.Select(redis => redis.StartAsync())); + + this.Redis = _redises[0]; + + this.Databases = this._redises.Select(redis => RedisServer.CreateDatabase(redis)).ToArray(); + } + + public override async ValueTask DisposeAsync() + { + foreach (var redis in this._redises) + { + await redis.DisposeAsync(); + } + } } public sealed class TestingRedis2x1DatabaseProvider : TestingRedisDatabaseProvider { - private static readonly IDatabase DeadDatabase; + private IDatabase DeadDatabase; + private RedisContainer[] _redises = default!; - static TestingRedis2x1DatabaseProvider() + public override string ConnectionStrings => string.Join("||", _redises.Select(x => x.GetConnectionString())); + + public override async ValueTask SetupAsync() { - var server = new RedisServer(allowAdmin: true); + var redis = new RedisBuilder().Build(); + await redis.StartAsync(); + + var server = RedisServer.Create(redis, allowAdmin: true); DeadDatabase = server.Multiplexer.GetDatabase(); - using var process = Process.GetProcessById(server.ProcessId); server.Multiplexer.GetServer($"localhost:{server.Port}").Shutdown(ShutdownMode.Never); - Assert.That(process.WaitForExit(5000), Is.True); + await redis.StopAsync(); + + this._redises = Enumerable.Range(0, 2).Select(_ => new RedisBuilder().Build()).ToArray(); + await Task.WhenAll(this._redises.Select(redis => redis.StartAsync())); + + this.Redis = _redises[0]; + + Databases = this._redises.Select(redis => RedisServer.CreateDatabase(redis)).Append(DeadDatabase).ToArray(); } - public TestingRedis2x1DatabaseProvider() - : base(Enumerable.Range(0, 2).Select(i => RedisServer.GetDefaultServer(i).Multiplexer.GetDatabase()).Append(DeadDatabase)) + public override async ValueTask DisposeAsync() { + foreach (var redis in this._redises) + { + await redis.DisposeAsync(); + } } public override string CrossProcessLockTypeSuffix => "2x1"; diff --git a/src/DistributedLock.Tests/Infrastructure/Redis/TestingRedisProviders.cs b/src/DistributedLock.Tests/Infrastructure/Redis/TestingRedisProviders.cs index 10b5f144..a7ab9ca3 100644 --- a/src/DistributedLock.Tests/Infrastructure/Redis/TestingRedisProviders.cs +++ b/src/DistributedLock.Tests/Infrastructure/Redis/TestingRedisProviders.cs @@ -19,6 +19,8 @@ public override IDistributedLock CreateLockWithExactName(string name) public override string GetSafeName(string name) => new RedisDistributedLock(name, this.Strategy.DatabaseProvider.Databases).Name; public override string GetCrossProcessLockType() => $"{nameof(RedisDistributedLock)}{this.Strategy.DatabaseProvider.CrossProcessLockTypeSuffix}"; + + public override string GetConnectionStringForCrossProcessTest() => this.Strategy.DatabaseProvider.ConnectionStrings; } public sealed class TestingRedisDistributedReaderWriterLockProvider : TestingReaderWriterLockProvider> diff --git a/src/DistributedLock.Tests/Infrastructure/Redis/TestingRedisSynchronizationStrategy.cs b/src/DistributedLock.Tests/Infrastructure/Redis/TestingRedisSynchronizationStrategy.cs index 041ac2d8..7cfddcc7 100644 --- a/src/DistributedLock.Tests/Infrastructure/Redis/TestingRedisSynchronizationStrategy.cs +++ b/src/DistributedLock.Tests/Infrastructure/Redis/TestingRedisSynchronizationStrategy.cs @@ -57,6 +57,10 @@ public void RegisterKillHandleAction(Action action) } } + public override string GetConnectionStringForCrossProcessTest() => this.DatabaseProvider.ConnectionStrings; + + public override ValueTask SetupAsync() => this.DatabaseProvider.SetupAsync(); + private class HandleLostScope : IDisposable { private TestingRedisSynchronizationStrategy? _strategy; diff --git a/src/DistributedLock.Tests/Infrastructure/TestingLockProvider.cs b/src/DistributedLock.Tests/Infrastructure/TestingLockProvider.cs index 708cbc93..4c069300 100644 --- a/src/DistributedLock.Tests/Infrastructure/TestingLockProvider.cs +++ b/src/DistributedLock.Tests/Infrastructure/TestingLockProvider.cs @@ -1,6 +1,6 @@ namespace Medallion.Threading.Tests; -public abstract class TestingLockProvider : ITestingNameProvider, IDisposable +public abstract class TestingLockProvider : ITestingNameProvider, IAsyncDisposable where TStrategy : TestingSynchronizationStrategy, new() { private readonly Lazy _lazyStrategy = new(() => new TStrategy()); @@ -13,9 +13,11 @@ public abstract class TestingLockProvider : ITestingNameProvider, IDi public abstract string GetSafeName(string name); public virtual string GetCrossProcessLockType() => this.CreateLock(string.Empty).GetType().Name; - public virtual void Dispose() => this.Strategy.Dispose(); - public virtual string GetConnectionStringForCrossProcessTest() => ""; + public virtual ValueTask SetupAsync() => this.Strategy.SetupAsync(); + public virtual ValueTask DisposeAsync() => this.Strategy.DisposeAsync(); + + public virtual string GetConnectionStringForCrossProcessTest() => this.Strategy.GetConnectionStringForCrossProcessTest(); /// /// Returns a lock whose name is based on diff --git a/src/DistributedLock.Tests/Infrastructure/TestingReaderWriterLockAsMutexProvider.cs b/src/DistributedLock.Tests/Infrastructure/TestingReaderWriterLockAsMutexProvider.cs index 87a542b0..382228ec 100644 --- a/src/DistributedLock.Tests/Infrastructure/TestingReaderWriterLockAsMutexProvider.cs +++ b/src/DistributedLock.Tests/Infrastructure/TestingReaderWriterLockAsMutexProvider.cs @@ -25,10 +25,10 @@ public override IDistributedLock CreateLockWithExactName(string name) => public override string GetCrossProcessLockType() => this._readerWriterLockProvider.GetCrossProcessLockType(ReaderWriterLockType.Write); - public override void Dispose() + public override async ValueTask DisposeAsync() { - this._readerWriterLockProvider.Dispose(); - base.Dispose(); + await this._readerWriterLockProvider.DisposeAsync(); + await base.DisposeAsync(); } private bool GetShouldUseUpgradeLock() diff --git a/src/DistributedLock.Tests/Infrastructure/TestingReaderWriterLockProvider.cs b/src/DistributedLock.Tests/Infrastructure/TestingReaderWriterLockProvider.cs index 8e137c74..468200e2 100644 --- a/src/DistributedLock.Tests/Infrastructure/TestingReaderWriterLockProvider.cs +++ b/src/DistributedLock.Tests/Infrastructure/TestingReaderWriterLockProvider.cs @@ -1,6 +1,6 @@ namespace Medallion.Threading.Tests; -public abstract class TestingReaderWriterLockProvider : ITestingNameProvider, IDisposable +public abstract class TestingReaderWriterLockProvider : ITestingNameProvider, IAsyncDisposable where TStrategy : TestingSynchronizationStrategy, new() { public TStrategy Strategy { get; } = new TStrategy(); @@ -17,7 +17,8 @@ public virtual string GetCrossProcessLockType(ReaderWriterLockType type) => public IDistributedReaderWriterLock CreateReaderWriterLock(string baseName) => this.CreateReaderWriterLockWithExactName(this.GetUniqueSafeName(baseName)); - public void Dispose() => this.Strategy.Dispose(); + public ValueTask DisposeAsync() => this.Strategy.DisposeAsync(); + public ValueTask SetupAsync() => this.Strategy.SetupAsync(); } public abstract class TestingUpgradeableReaderWriterLockProvider : TestingReaderWriterLockProvider diff --git a/src/DistributedLock.Tests/Infrastructure/TestingSemaphoreAsMutexProvider.cs b/src/DistributedLock.Tests/Infrastructure/TestingSemaphoreAsMutexProvider.cs index 0bb03996..b69d0f26 100644 --- a/src/DistributedLock.Tests/Infrastructure/TestingSemaphoreAsMutexProvider.cs +++ b/src/DistributedLock.Tests/Infrastructure/TestingSemaphoreAsMutexProvider.cs @@ -14,7 +14,6 @@ public abstract class TestingSemaphoreAsMutexProvider this._semaphoreProvider.Strategy; @@ -47,10 +46,11 @@ public override IDistributedLock CreateLockWithExactName(string name) public override string GetSafeName(string name) => this._semaphoreProvider.GetSafeName(name); - public override void Dispose() + public override async ValueTask DisposeAsync() { this._disposables.Dispose(); - base.Dispose(); + await this._semaphoreProvider.DisposeAsync(); + await base.DisposeAsync(); } private class SemaphoreAsMutex : IDistributedLock diff --git a/src/DistributedLock.Tests/Infrastructure/TestingSemaphoreProvider.cs b/src/DistributedLock.Tests/Infrastructure/TestingSemaphoreProvider.cs index febd92e1..d0b3f43a 100644 --- a/src/DistributedLock.Tests/Infrastructure/TestingSemaphoreProvider.cs +++ b/src/DistributedLock.Tests/Infrastructure/TestingSemaphoreProvider.cs @@ -1,6 +1,6 @@ namespace Medallion.Threading.Tests; -public abstract class TestingSemaphoreProvider : ITestingNameProvider, IDisposable +public abstract class TestingSemaphoreProvider : ITestingNameProvider, IAsyncDisposable where TStrategy : TestingSynchronizationStrategy, new() { public TStrategy Strategy { get; } = new TStrategy(); @@ -17,5 +17,6 @@ public virtual string GetCrossProcessLockType() => public IDistributedSemaphore CreateSemaphore(string baseName, int maxCount) => this.CreateSemaphoreWithExactName(this.GetUniqueSafeName(baseName), maxCount); - public void Dispose() => this.Strategy.Dispose(); + public ValueTask DisposeAsync() => this.Strategy.DisposeAsync(); + public ValueTask SetupAsync() => this.Strategy.SetupAsync(); } diff --git a/src/DistributedLock.Tests/Infrastructure/TestingSynchronizationStrategy.cs b/src/DistributedLock.Tests/Infrastructure/TestingSynchronizationStrategy.cs index 8a58ea08..c8c67d3c 100644 --- a/src/DistributedLock.Tests/Infrastructure/TestingSynchronizationStrategy.cs +++ b/src/DistributedLock.Tests/Infrastructure/TestingSynchronizationStrategy.cs @@ -4,7 +4,7 @@ /// Manages the underlying approach to synchronization. Having this class allows us to parameterize tests by /// synchronization strategy (e. g. only connection string-based strategies) /// -public abstract class TestingSynchronizationStrategy : IDisposable +public abstract class TestingSynchronizationStrategy : IAsyncDisposable { /// /// Whether or not abandoning a ticket held in another process will cause that ticket @@ -16,5 +16,7 @@ public virtual void PrepareForHandleAbandonment() { } public virtual void PerformAdditionalCleanupForHandleAbandonment() { } public virtual IDisposable? PrepareForHandleLost() => null; public virtual void PrepareForHighContention(ref int maxConcurrentAcquires) { } - public virtual void Dispose() { } + public virtual string GetConnectionStringForCrossProcessTest() => ""; + public virtual ValueTask SetupAsync() => ValueTask.CompletedTask; + public virtual ValueTask DisposeAsync() => ValueTask.CompletedTask; } diff --git a/src/DistributedLock.Tests/Tests/Azure/AzureBlobLeaseDistributedLockTest.cs b/src/DistributedLock.Tests/Tests/Azure/AzureBlobLeaseDistributedLockTest.cs index 409e475d..9427ee65 100644 --- a/src/DistributedLock.Tests/Tests/Azure/AzureBlobLeaseDistributedLockTest.cs +++ b/src/DistributedLock.Tests/Tests/Azure/AzureBlobLeaseDistributedLockTest.cs @@ -49,7 +49,7 @@ public async Task TestLockOnDifferentBlobClientTypes( async ValueTask TestAsync() { - using var provider = new TestingAzureBlobLeaseDistributedLockProvider(); + await using var provider = new TestingAzureBlobLeaseDistributedLockProvider(); var name = provider.GetUniqueSafeName(); var client = CreateClient(type, name); @@ -71,7 +71,7 @@ async ValueTask TestAsync() [Test] public async Task TestWrapperCreateIfNotExists([Values] BlobClientType type) { - using var provider = new TestingAzureBlobLeaseDistributedLockProvider(); + await using var provider = new TestingAzureBlobLeaseDistributedLockProvider(); var name = provider.GetUniqueSafeName(); var client = CreateClient(type, name); var wrapper = new BlobClientWrapper(client); @@ -96,9 +96,9 @@ public async Task TestWrapperCreateIfNotExists([Values] BlobClientType type) } [Test] - public void TestCanUseLeaseIdForBlobOperations() + public async Task TestCanUseLeaseIdForBlobOperations() { - using var provider = new TestingAzureBlobLeaseDistributedLockProvider(); + await using var provider = new TestingAzureBlobLeaseDistributedLockProvider(); var name = provider.GetUniqueSafeName(); var client = new PageBlobClient(AzureCredentials.ConnectionString, AzureCredentials.DefaultBlobContainerName, name); const int BlobSize = 512; @@ -121,9 +121,9 @@ public void TestCanUseLeaseIdForBlobOperations() } [Test] - public void TestThrowsIfContainerDoesNotExist() + public async Task TestThrowsIfContainerDoesNotExist() { - using var provider = new TestingAzureBlobLeaseDistributedLockProvider(); + await using var provider = new TestingAzureBlobLeaseDistributedLockProvider(); provider.Strategy.ContainerName = "does-not-exist"; var @lock = provider.CreateLock(nameof(TestThrowsIfContainerDoesNotExist)); @@ -132,9 +132,9 @@ public void TestThrowsIfContainerDoesNotExist() } [Test] - public void TestCanAcquireIfContainerLeased() + public async Task TestCanAcquireIfContainerLeased() { - using var provider = new TestingAzureBlobLeaseDistributedLockProvider(); + await using var provider = new TestingAzureBlobLeaseDistributedLockProvider(); provider.Strategy.ContainerName = "leased-container" + TargetFramework.Current.Replace('.', '-'); var containerClient = new BlobContainerClient(AzureCredentials.ConnectionString, provider.Strategy.ContainerName); @@ -159,7 +159,7 @@ public void TestCanAcquireIfContainerLeased() [Test] public async Task TestSuccessfulRenewal() { - using var provider = new TestingAzureBlobLeaseDistributedLockProvider(); + await using var provider = new TestingAzureBlobLeaseDistributedLockProvider(); provider.Strategy.Options = o => o.RenewalCadence(TimeSpan.FromSeconds(.05)); var @lock = provider.CreateLock(nameof(TestSuccessfulRenewal)); @@ -170,9 +170,9 @@ public async Task TestSuccessfulRenewal() [Test] [NonParallelizable, Retry(tryCount: 3)] // timing-sensitive - public void TestTriggersHandleLostIfLeaseExpiresNaturally() + public async Task TestTriggersHandleLostIfLeaseExpiresNaturally() { - using var provider = new TestingAzureBlobLeaseDistributedLockProvider(); + await using var provider = new TestingAzureBlobLeaseDistributedLockProvider(); provider.Strategy.Options = o => o.RenewalCadence(Timeout.InfiniteTimeSpan).Duration(TimeSpan.FromSeconds(15)); var @lock = provider.CreateLock(nameof(TestTriggersHandleLostIfLeaseExpiresNaturally)); @@ -188,9 +188,9 @@ public void TestTriggersHandleLostIfLeaseExpiresNaturally() } [Test] - public void TestExitsDespiteLongSleepTime() + public async Task TestExitsDespiteLongSleepTime() { - using var provider = new TestingAzureBlobLeaseDistributedLockProvider(); + await using var provider = new TestingAzureBlobLeaseDistributedLockProvider(); provider.Strategy.Options = o => o.BusyWaitSleepTime(TimeSpan.FromSeconds(30), TimeSpan.FromMinutes(1)); var @lock = provider.CreateLock(nameof(TestExitsDespiteLongSleepTime)); diff --git a/src/DistributedLock.Tests/Tests/Redis/RedisDistributedLockTest.cs b/src/DistributedLock.Tests/Tests/Redis/RedisDistributedLockTest.cs index 6ea2d757..b55d7bf5 100644 --- a/src/DistributedLock.Tests/Tests/Redis/RedisDistributedLockTest.cs +++ b/src/DistributedLock.Tests/Tests/Redis/RedisDistributedLockTest.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using StackExchange.Redis; using System.Globalization; +using Testcontainers.Redis; namespace Medallion.Threading.Tests.Redis; @@ -47,7 +48,7 @@ public async Task TestCanAcquireLockWhenCurrentCultureIsTurkishTurkey() CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("tr-TR"); var @lock = new RedisDistributedLock( TestHelper.UniqueName, - RedisServer.GetDefaultServer(0).Multiplexer.GetDatabase() + RedisServer.CreateDatabase(RedisSetUpFixture.Redis) ); await (await @lock.AcquireAsync()).DisposeAsync(); } diff --git a/src/DistributedLock.Tests/Tests/Redis/RedisDistributedReaderWriterLockTest.cs b/src/DistributedLock.Tests/Tests/Redis/RedisDistributedReaderWriterLockTest.cs index 0a8ea930..91e62c93 100644 --- a/src/DistributedLock.Tests/Tests/Redis/RedisDistributedReaderWriterLockTest.cs +++ b/src/DistributedLock.Tests/Tests/Redis/RedisDistributedReaderWriterLockTest.cs @@ -2,6 +2,7 @@ using Moq; using NUnit.Framework; using StackExchange.Redis; +using Testcontainers.Redis; namespace Medallion.Threading.Tests.Redis; @@ -36,7 +37,7 @@ public async Task TestCanExtendReadLock() { var @lock = new RedisDistributedReaderWriterLock( TestHelper.UniqueName, - RedisServer.GetDefaultServer(0).Multiplexer.GetDatabase(), + RedisServer.CreateDatabase(RedisSetUpFixture.Redis), o => o.Expiry(TimeSpan.FromSeconds(0.3)).BusyWaitSleepTime(TimeSpan.FromMilliseconds(1), TimeSpan.FromMilliseconds(5)) ); @@ -57,7 +58,7 @@ public async Task TestReadLockAbandonment() { var @lock = new RedisDistributedReaderWriterLock( TestHelper.UniqueName, - RedisServer.GetDefaultServer(0).Multiplexer.GetDatabase(), + RedisServer.CreateDatabase(RedisSetUpFixture.Redis), o => o.Expiry(TimeSpan.FromSeconds(1)) .ExtensionCadence(TimeSpan.FromSeconds(0.1)) .BusyWaitSleepTime(TimeSpan.FromMilliseconds(10), TimeSpan.FromMilliseconds(50)) diff --git a/src/DistributedLock.Tests/Tests/Redis/RedisDistributedSynchronizationProviderTest.cs b/src/DistributedLock.Tests/Tests/Redis/RedisDistributedSynchronizationProviderTest.cs index 4cdd3f6d..d0c04d9d 100644 --- a/src/DistributedLock.Tests/Tests/Redis/RedisDistributedSynchronizationProviderTest.cs +++ b/src/DistributedLock.Tests/Tests/Redis/RedisDistributedSynchronizationProviderTest.cs @@ -1,6 +1,7 @@ using Medallion.Threading.Redis; using NUnit.Framework; using StackExchange.Redis; +using Testcontainers.Redis; namespace Medallion.Threading.Tests.Redis; @@ -18,7 +19,7 @@ public void TestArgumentValidation() [Test] public async Task BasicTest() { - var provider = new RedisDistributedSynchronizationProvider(RedisServer.GetDefaultServer(0).Multiplexer.GetDatabase()); + var provider = new RedisDistributedSynchronizationProvider(RedisServer.CreateDatabase(RedisSetUpFixture.Redis)); const string LockName = TargetFramework.Current + "ProviderBasicTest"; await using (await provider.AcquireLockAsync(LockName)) diff --git a/src/DistributedLock.Tests/packages.lock.json b/src/DistributedLock.Tests/packages.lock.json index 72f442b6..2c87cc69 100644 --- a/src/DistributedLock.Tests/packages.lock.json +++ b/src/DistributedLock.Tests/packages.lock.json @@ -68,6 +68,15 @@ "Testcontainers": "3.8.0" } }, + "Testcontainers.Redis": { + "type": "Direct", + "requested": "[3.8.0, )", + "resolved": "3.8.0", + "contentHash": "sPPSCuxLH/7ycjJUOLkIqspwFiU1eVZhxWAJ2JROZlYDscOP94Lm4IgNg/sMeSQI4VJ7ptcW/B/Sjw5tAnG6Ww==", + "dependencies": { + "Testcontainers": "3.8.0" + } + }, "Azure.Core": { "type": "Transitive", "resolved": "1.36.0", diff --git a/src/DistributedLockTaker/Program.cs b/src/DistributedLockTaker/Program.cs index 119f104d..cad5c24c 100644 --- a/src/DistributedLockTaker/Program.cs +++ b/src/DistributedLockTaker/Program.cs @@ -25,6 +25,7 @@ public static int Main(string[] args) var type = args[0]; var name = args[1]; var connectionString = args[2]; + var connectionStrings = connectionString.Split("||", StringSplitOptions.None); IDisposable? handle; switch (type) { @@ -78,38 +79,47 @@ public static int Main(string[] args) handle = new FileDistributedLock(new FileInfo(name)).Acquire(); break; case nameof(RedisDistributedLock) + "1": - handle = AcquireRedisLock(name, serverCount: 1); + ValidateConnectionStringCount(connectionStrings, 1); + handle = AcquireRedisLock(name, serverCount: 1, connectionStrings); break; case nameof(RedisDistributedLock) + "3": - handle = AcquireRedisLock(name, serverCount: 3); + ValidateConnectionStringCount(connectionStrings, 3); + handle = AcquireRedisLock(name, serverCount: 3, connectionStrings); break; case nameof(RedisDistributedLock) + "2x1": - handle = AcquireRedisLock(name, serverCount: 2); // we know the last will fail; don't bother (we also don't know its port) + ValidateConnectionStringCount(connectionStrings, 2); + handle = AcquireRedisLock(name, serverCount: 2, connectionStrings); // we know the last will fail; don't bother (we also don't know its port) break; case nameof(RedisDistributedLock) + "1WithPrefix": - handle = AcquireRedisLock("distributed_locks:" + name, serverCount: 1); + ValidateConnectionStringCount(connectionStrings, 1); + handle = AcquireRedisLock("distributed_locks:" + name, serverCount: 1, connectionStrings); break; case "Write" + nameof(RedisDistributedReaderWriterLock) + "1": - handle = AcquireRedisWriteLock(name, serverCount: 1); + ValidateConnectionStringCount(connectionStrings, 1); + handle = AcquireRedisWriteLock(name, serverCount: 1, connectionStrings); break; case "Write" + nameof(RedisDistributedReaderWriterLock) + "3": - handle = AcquireRedisWriteLock(name, serverCount: 3); + ValidateConnectionStringCount(connectionStrings, 3); + handle = AcquireRedisWriteLock(name, serverCount: 3, connectionStrings); break; case "Write" + nameof(RedisDistributedReaderWriterLock) + "2x1": - handle = AcquireRedisWriteLock(name, serverCount: 2); // we know the last will fail; don't bother (we also don't know its port) + ValidateConnectionStringCount(connectionStrings, 2); + handle = AcquireRedisWriteLock(name, serverCount: 2, connectionStrings); // we know the last will fail; don't bother (we also don't know its port) break; case "Write" + nameof(RedisDistributedReaderWriterLock) + "1WithPrefix": - handle = AcquireRedisWriteLock("distributed_locks:" + name, serverCount: 1); + ValidateConnectionStringCount(connectionStrings, 1); + handle = AcquireRedisWriteLock("distributed_locks:" + name, serverCount: 1,connectionStrings); break; case string _ when type.StartsWith(nameof(RedisDistributedSemaphore)): { + ValidateConnectionStringCount(connectionStrings, 1); var maxCount = type.EndsWith("1AsMutex") ? 1 : type.EndsWith("5AsMutex") ? 5 : throw new ArgumentException(type); handle = new RedisDistributedSemaphore( name, maxCount, - GetRedisDatabases(serverCount: 1).Single(), + GetRedisDatabases(serverCount: 1, connectionStrings).Single(), // in order to see abandonment work in a reasonable timeframe, use very short expiry options => options.Expiry(TimeSpan.FromSeconds(1)) .BusyWaitSleepTime(TimeSpan.FromSeconds(.1), TimeSpan.FromSeconds(.3)) @@ -151,14 +161,19 @@ public static int Main(string[] args) return 0; } - private static IDistributedSynchronizationHandle AcquireRedisLock(string name, int serverCount) => - new RedisDistributedLock(name, GetRedisDatabases(serverCount), RedisOptions).Acquire(); + private static void ValidateConnectionStringCount(string[] connectionStrings, int expectedCount) + { + if (connectionStrings.Length != expectedCount) throw new ArgumentOutOfRangeException("Invalid connection strings count"); + } + + private static IDistributedSynchronizationHandle AcquireRedisLock(string name, int serverCount, string[] connectionStrings) => + new RedisDistributedLock(name, GetRedisDatabases(serverCount, connectionStrings), RedisOptions).Acquire(); - private static IDistributedSynchronizationHandle AcquireRedisWriteLock(string name, int serverCount) => - new RedisDistributedReaderWriterLock(name, GetRedisDatabases(serverCount), RedisOptions).AcquireWriteLock(); + private static IDistributedSynchronizationHandle AcquireRedisWriteLock(string name, int serverCount, string[] connectionStrings) => + new RedisDistributedReaderWriterLock(name, GetRedisDatabases(serverCount, connectionStrings), RedisOptions).AcquireWriteLock(); - private static IEnumerable GetRedisDatabases(int serverCount) => RedisPorts.DefaultPorts.Take(serverCount) - .Select(port => ConnectionMultiplexer.Connect($"localhost:{port}").GetDatabase()); + private static IEnumerable GetRedisDatabases(int serverCount, string[] connectionStrings) => Enumerable.Range(0, serverCount) + .Select(i => ConnectionMultiplexer.Connect(connectionStrings[i]).GetDatabase()); private static void RedisOptions(RedisDistributedSynchronizationOptionsBuilder options) => options.Expiry(TimeSpan.FromSeconds(.5)) // short expiry for abandonment testing diff --git a/src/DistributedLockTaker/packages.lock.json b/src/DistributedLockTaker/packages.lock.json index 79dacffd..d0797acf 100644 --- a/src/DistributedLockTaker/packages.lock.json +++ b/src/DistributedLockTaker/packages.lock.json @@ -590,9 +590,9 @@ "net8.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.5, )", - "resolved": "8.0.5", - "contentHash": "4ISW1Ndgz86FkIbu5rVlMqhhtojdy5rUlxC/N+9kPh9qbYcvHiCvYbHKzAPVIx9OPYIjT9trXt7JI42Y5Ukq6A==" + "requested": "[8.0.6, )", + "resolved": "8.0.6", + "contentHash": "E+lDylsTeP4ZiDmnEkiJ5wobnGaIJzFhOgZppznJCb69UZgbh6G3cfv1pnLDLLBx6JAgl0kAlnINDeT3uCuczQ==" }, "Azure.Core": { "type": "Transitive", From 55e4145ff49159f68f4de9866966bab00e297980 Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Mon, 3 Jun 2024 21:28:05 +0300 Subject: [PATCH 05/27] fix net472 --- src/DistributedLockTaker/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DistributedLockTaker/Program.cs b/src/DistributedLockTaker/Program.cs index cad5c24c..09044daf 100644 --- a/src/DistributedLockTaker/Program.cs +++ b/src/DistributedLockTaker/Program.cs @@ -25,7 +25,7 @@ public static int Main(string[] args) var type = args[0]; var name = args[1]; var connectionString = args[2]; - var connectionStrings = connectionString.Split("||", StringSplitOptions.None); + var connectionStrings = connectionString.Split(new[] { "||" }, StringSplitOptions.None); IDisposable? handle; switch (type) { From 881f8c44296442915e8f849dbe62d7b338c05fad Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Mon, 3 Jun 2024 21:32:29 +0300 Subject: [PATCH 06/27] faster dead-redis setup --- .../Redis/TestingRedisDatabaseProvider.cs | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/DistributedLock.Tests/Infrastructure/Redis/TestingRedisDatabaseProvider.cs b/src/DistributedLock.Tests/Infrastructure/Redis/TestingRedisDatabaseProvider.cs index 8b7eb18c..a23be470 100644 --- a/src/DistributedLock.Tests/Infrastructure/Redis/TestingRedisDatabaseProvider.cs +++ b/src/DistributedLock.Tests/Infrastructure/Redis/TestingRedisDatabaseProvider.cs @@ -7,8 +7,8 @@ namespace Medallion.Threading.Tests.Redis; public abstract class TestingRedisDatabaseProvider { // publicly settable so that callers can alter the dbs in use - public IReadOnlyList Databases { get; set; } - public RedisContainer Redis { get; protected set; } + public IReadOnlyList Databases { get; set; } = default!; + public RedisContainer Redis { get; protected set; } = default!; public abstract string ConnectionStrings { get; } public virtual string CrossProcessLockTypeSuffix => this.Databases.Count.ToString(); @@ -19,7 +19,7 @@ public abstract class TestingRedisDatabaseProvider public sealed class TestingRedisSingleDatabaseProvider : TestingRedisDatabaseProvider { - public override string ConnectionStrings => Redis.GetConnectionString(); + public override string ConnectionStrings => this.Redis.GetConnectionString(); public override async ValueTask SetupAsync() { this.Redis = new RedisBuilder().Build(); @@ -33,7 +33,7 @@ public override async ValueTask SetupAsync() public sealed class TestingRedisWithKeyPrefixSingleDatabaseProvider : TestingRedisDatabaseProvider { - public override string ConnectionStrings => Redis.GetConnectionString(); + public override string ConnectionStrings => this.Redis.GetConnectionString(); public override string CrossProcessLockTypeSuffix => "1WithPrefix"; public override async ValueTask SetupAsync() @@ -74,21 +74,23 @@ public override async ValueTask DisposeAsync() public sealed class TestingRedis2x1DatabaseProvider : TestingRedisDatabaseProvider { - private IDatabase DeadDatabase; + private static IDatabase DeadDatabase; private RedisContainer[] _redises = default!; public override string ConnectionStrings => string.Join("||", _redises.Select(x => x.GetConnectionString())); - public override async ValueTask SetupAsync() + static TestingRedis2x1DatabaseProvider() { var redis = new RedisBuilder().Build(); - await redis.StartAsync(); - + redis.StartAsync().GetAwaiter().GetResult(); var server = RedisServer.Create(redis, allowAdmin: true); DeadDatabase = server.Multiplexer.GetDatabase(); server.Multiplexer.GetServer($"localhost:{server.Port}").Shutdown(ShutdownMode.Never); - await redis.StopAsync(); + redis.StopAsync().GetAwaiter().GetResult(); + } + public override async ValueTask SetupAsync() + { this._redises = Enumerable.Range(0, 2).Select(_ => new RedisBuilder().Build()).ToArray(); await Task.WhenAll(this._redises.Select(redis => redis.StartAsync())); From 5efd124cb0d59457d09309087348b1aa891e41ee Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Mon, 3 Jun 2024 21:51:44 +0300 Subject: [PATCH 07/27] postgresql setup --- .github/workflows/ci.yaml | 8 ++++++-- .../Infrastructure/Data/TestingDb.cs | 3 +++ .../Data/TestingDbSynchronizationStrategy.cs | 16 +++------------- .../Postgres/TestingPostgresDb.cs | 18 +++++++++++++----- .../Postgres/TestingPostgresProviders.cs | 2 +- .../Tests/Postgres/PostgresBehaviorTest.cs | 14 +++++++------- .../Postgres/PostgresDistributedLockTest.cs | 6 +++--- ...sDistributedSynchronizationProviderTest.cs | 2 +- .../Tests/Postgres/PostgresSetUpFixture.cs | 19 +++++++++++++++++++ 9 files changed, 56 insertions(+), 32 deletions(-) create mode 100644 src/DistributedLock.Tests/Tests/Postgres/PostgresSetUpFixture.cs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 24d35c5e..3a752081 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -4,7 +4,11 @@ on: [push] jobs: build: - runs-on: ubuntu-latest + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + categort: [Redis, Postgres] + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -18,4 +22,4 @@ jobs: run: dotnet build src - name: Redis Tests - run: dotnet test src --filter Name~Redis \ No newline at end of file + run: dotnet test src --filter Name~${{ matrix.categort }} \ No newline at end of file diff --git a/src/DistributedLock.Tests/Infrastructure/Data/TestingDb.cs b/src/DistributedLock.Tests/Infrastructure/Data/TestingDb.cs index 2cbe9e64..52e7c19a 100644 --- a/src/DistributedLock.Tests/Infrastructure/Data/TestingDb.cs +++ b/src/DistributedLock.Tests/Infrastructure/Data/TestingDb.cs @@ -58,6 +58,9 @@ public void ClearPool(DbConnection connection) public abstract IsolationLevel GetIsolationLevel(DbConnection connection); public virtual void PrepareForHighContention(ref int maxConcurrentAcquires) { } + + public virtual ValueTask SetupAsync() => ValueTask.CompletedTask; + public virtual ValueTask DisposeAsync() => ValueTask.CompletedTask; } public enum TransactionSupport diff --git a/src/DistributedLock.Tests/Infrastructure/Data/TestingDbSynchronizationStrategy.cs b/src/DistributedLock.Tests/Infrastructure/Data/TestingDbSynchronizationStrategy.cs index b0064b34..0bd48839 100644 --- a/src/DistributedLock.Tests/Infrastructure/Data/TestingDbSynchronizationStrategy.cs +++ b/src/DistributedLock.Tests/Infrastructure/Data/TestingDbSynchronizationStrategy.cs @@ -18,6 +18,9 @@ protected TestingDbSynchronizationStrategy(TestingDb db) public override void PrepareForHighContention(ref int maxConcurrentAcquires) => this.Db.PrepareForHighContention(ref maxConcurrentAcquires); + + public override ValueTask SetupAsync() => this.Db.SetupAsync(); + public override ValueTask DisposeAsync() => this.Db.DisposeAsync(); } public abstract class TestingDbSynchronizationStrategy : TestingDbSynchronizationStrategy @@ -26,19 +29,6 @@ public abstract class TestingDbSynchronizationStrategy : TestingDbSynchroni protected TestingDbSynchronizationStrategy() : base(new TDb()) { } public new TDb Db => (TDb)base.Db; - - public override ValueTask DisposeAsync() - { - // if we have a uniquely-named connection, clear it's pool to avoid "leaking" connections into pools we'll never - // use again - if (!Equals(this.Db.ApplicationName, new TDb().ApplicationName)) - { - using var connection = this.Db.CreateConnection(); - this.Db.ClearPool(connection); - } - - return base.DisposeAsync(); - } } public abstract class TestingConnectionStringSynchronizationStrategy : TestingDbSynchronizationStrategy diff --git a/src/DistributedLock.Tests/Infrastructure/Postgres/TestingPostgresDb.cs b/src/DistributedLock.Tests/Infrastructure/Postgres/TestingPostgresDb.cs index e47157f9..b8e13177 100644 --- a/src/DistributedLock.Tests/Infrastructure/Postgres/TestingPostgresDb.cs +++ b/src/DistributedLock.Tests/Infrastructure/Postgres/TestingPostgresDb.cs @@ -2,17 +2,17 @@ using Medallion.Threading.Tests.Data; using Npgsql; using NpgsqlTypes; -using NUnit.Framework; using System.Data; using System.Data.Common; +using Testcontainers.PostgreSql; namespace Medallion.Threading.Tests.Postgres; public sealed class TestingPostgresDb : TestingPrimaryClientDb { - internal static readonly string DefaultConnectionString = PostgresDb.Container.GetConnectionString(); + private readonly PostgreSqlContainer _container = new PostgreSqlBuilder().Build(); - private readonly NpgsqlConnectionStringBuilder _connectionStringBuilder = new(DefaultConnectionString); + private NpgsqlConnectionStringBuilder _connectionStringBuilder; public override DbConnectionStringBuilder ConnectionStringBuilder => this._connectionStringBuilder; @@ -30,7 +30,7 @@ public override int CountActiveSessions(string applicationName) { Invariant.Require(applicationName.Length <= this.MaxApplicationNameLength); - using var connection = new NpgsqlConnection(DefaultConnectionString); + using var connection = new NpgsqlConnection(_container.GetConnectionString()); connection.Open(); using var command = connection.CreateCommand(); command.CommandText = "SELECT COUNT(*)::int FROM pg_stat_activity WHERE application_name = @applicationName"; @@ -50,7 +50,7 @@ public override IsolationLevel GetIsolationLevel(DbConnection connection) public override async Task KillSessionsAsync(string applicationName, DateTimeOffset? idleSince) { - using var connection = new NpgsqlConnection(DefaultConnectionString); + using var connection = new NpgsqlConnection(_container.GetConnectionString()); await connection.OpenAsync(); using var command = connection.CreateCommand(); // based on https://stackoverflow.com/questions/13236160/is-there-a-timeout-for-idle-postgresql-connections @@ -67,4 +67,12 @@ @idleSince IS NULL await command.ExecuteNonQueryAsync(); } + + public override async ValueTask SetupAsync() + { + await _container.StartAsync(); + this._connectionStringBuilder = new NpgsqlConnectionStringBuilder(_container.GetConnectionString()); + } + + public override async ValueTask DisposeAsync() => await _container.DisposeAsync(); } diff --git a/src/DistributedLock.Tests/Infrastructure/Postgres/TestingPostgresProviders.cs b/src/DistributedLock.Tests/Infrastructure/Postgres/TestingPostgresProviders.cs index 0784a8fa..542fa7c2 100644 --- a/src/DistributedLock.Tests/Infrastructure/Postgres/TestingPostgresProviders.cs +++ b/src/DistributedLock.Tests/Infrastructure/Postgres/TestingPostgresProviders.cs @@ -21,7 +21,7 @@ public override IDistributedLock CreateLockWithExactName(string name) => public override string GetSafeName(string name) => new PostgresAdvisoryLockKey(name, allowHashing: true).ToString(); - public override string GetConnectionStringForCrossProcessTest() => TestingPostgresDb.DefaultConnectionString; + public override string GetConnectionStringForCrossProcessTest() => this.Strategy.Db.ConnectionString; internal static Action ToPostgresOptions((bool useMultiplexing, bool useTransaction, TimeSpan? keepaliveCadence) options) => o => { diff --git a/src/DistributedLock.Tests/Tests/Postgres/PostgresBehaviorTest.cs b/src/DistributedLock.Tests/Tests/Postgres/PostgresBehaviorTest.cs index 74ff8e6b..059d4ff5 100644 --- a/src/DistributedLock.Tests/Tests/Postgres/PostgresBehaviorTest.cs +++ b/src/DistributedLock.Tests/Tests/Postgres/PostgresBehaviorTest.cs @@ -18,7 +18,7 @@ public class PostgresBehaviorTest [Test] public async Task TestPostgresCommandAutomaticallyParticipatesInTransaction() { - using var connection = new NpgsqlConnection(TestingPostgresDb.DefaultConnectionString); + using var connection = new NpgsqlConnection(PostgresSetUpFixture.PostgreSql.GetConnectionString()); await connection.OpenAsync(); using var transaction = @@ -66,7 +66,7 @@ private async Task TestTransactionCancellationOrTimeoutRecovery(bool useTimeout) async Task RunTransactionWithAbortAsync(bool useSavePoint) { - using var connection = new NpgsqlConnection(TestingPostgresDb.DefaultConnectionString); + using var connection = new NpgsqlConnection(PostgresSetUpFixture.PostgreSql.GetConnectionString()); await connection.OpenAsync(); using (connection.BeginTransaction()) @@ -102,7 +102,7 @@ async Task RunTransactionWithAbortAsync(bool useSavePoint) [Test] public async Task TestCanDetectTransactionWithBeginTransactionException() { - using var connection = new NpgsqlConnection(TestingPostgresDb.DefaultConnectionString); + using var connection = new NpgsqlConnection(PostgresSetUpFixture.PostgreSql.GetConnectionString()); await connection.OpenAsync(); Assert.DoesNotThrow(() => connection.BeginTransaction().Dispose()); @@ -116,7 +116,7 @@ public async Task TestCanDetectTransactionWithBeginTransactionException() [Test] public async Task TestDoesNotDetectConnectionBreakViaState() { - using var connection = new NpgsqlConnection(TestingPostgresDb.DefaultConnectionString); + using var connection = new NpgsqlConnection(PostgresSetUpFixture.PostgreSql.GetConnectionString()); await connection.OpenAsync(); using var getPidCommand = connection.CreateCommand(); @@ -127,7 +127,7 @@ public async Task TestDoesNotDetectConnectionBreakViaState() connection.StateChange += (_, _2) => stateChangedEvent.Set(); // kill the connection from the back end - using var killingConnection = new NpgsqlConnection(TestingPostgresDb.DefaultConnectionString); + using var killingConnection = new NpgsqlConnection(PostgresSetUpFixture.PostgreSql.GetConnectionString()); await killingConnection.OpenAsync(); using var killCommand = killingConnection.CreateCommand(); killCommand.CommandText = $"SELECT pg_terminate_backend({pid})"; @@ -145,7 +145,7 @@ public async Task TestExecutingQueryOnKilledConnectionFiresStateChanged() { using var stateChangedEvent = new ManualResetEventSlim(initialState: false); - using var connection = new NpgsqlConnection(TestingPostgresDb.DefaultConnectionString); + using var connection = new NpgsqlConnection(PostgresSetUpFixture.PostgreSql.GetConnectionString()); await connection.OpenAsync(); connection.StateChange += (o, e) => stateChangedEvent.Set(); @@ -156,7 +156,7 @@ public async Task TestExecutingQueryOnKilledConnectionFiresStateChanged() Assert.That(connection.State, Is.EqualTo(ConnectionState.Open)); // kill the connection from the back end - using var killingConnection = new NpgsqlConnection(TestingPostgresDb.DefaultConnectionString); + using var killingConnection = new NpgsqlConnection(PostgresSetUpFixture.PostgreSql.GetConnectionString()); await killingConnection.OpenAsync(); using var killCommand = killingConnection.CreateCommand(); killCommand.CommandText = $"SELECT pg_terminate_backend({pid})"; diff --git a/src/DistributedLock.Tests/Tests/Postgres/PostgresDistributedLockTest.cs b/src/DistributedLock.Tests/Tests/Postgres/PostgresDistributedLockTest.cs index ebba92a1..205262e3 100644 --- a/src/DistributedLock.Tests/Tests/Postgres/PostgresDistributedLockTest.cs +++ b/src/DistributedLock.Tests/Tests/Postgres/PostgresDistributedLockTest.cs @@ -17,7 +17,7 @@ public void TestValidatesConstructorArguments() [Test] public async Task TestInt64AndInt32PairKeyNamespacesAreDifferent() { - var connectionString = TestingPostgresDb.DefaultConnectionString; + var connectionString = PostgresSetUpFixture.PostgreSql.GetConnectionString(); var key1 = new PostgresAdvisoryLockKey(0); var key2 = new PostgresAdvisoryLockKey(0, 0); var @lock1 = new PostgresDistributedLock(key1, connectionString); @@ -33,11 +33,11 @@ public async Task TestInt64AndInt32PairKeyNamespacesAreDifferent() [Test] public async Task TestWorksWithAmbientTransaction() { - using var connection = new NpgsqlConnection(TestingPostgresDb.DefaultConnectionString); + using var connection = new NpgsqlConnection(PostgresSetUpFixture.PostgreSql.GetConnectionString()); await connection.OpenAsync(); var connectionLock = new PostgresDistributedLock(new PostgresAdvisoryLockKey("AmbTrans"), connection); - var otherLock = new PostgresDistributedLock(connectionLock.Key, TestingPostgresDb.DefaultConnectionString); + var otherLock = new PostgresDistributedLock(connectionLock.Key, PostgresSetUpFixture.PostgreSql.GetConnectionString()); using var otherLockHandle = await otherLock.AcquireAsync(); using (var transaction = connection.BeginTransaction()) diff --git a/src/DistributedLock.Tests/Tests/Postgres/PostgresDistributedSynchronizationProviderTest.cs b/src/DistributedLock.Tests/Tests/Postgres/PostgresDistributedSynchronizationProviderTest.cs index 27074811..2d54ed52 100644 --- a/src/DistributedLock.Tests/Tests/Postgres/PostgresDistributedSynchronizationProviderTest.cs +++ b/src/DistributedLock.Tests/Tests/Postgres/PostgresDistributedSynchronizationProviderTest.cs @@ -16,7 +16,7 @@ public void TestArgumentValidation() [Test] public async Task BasicTest() { - var provider = new PostgresDistributedSynchronizationProvider(TestingPostgresDb.DefaultConnectionString); + var provider = new PostgresDistributedSynchronizationProvider(PostgresSetUpFixture.PostgreSql.GetConnectionString()); const string LockName = TargetFramework.Current + "ProviderBasicTest"; await using (await provider.AcquireLockAsync(LockName)) diff --git a/src/DistributedLock.Tests/Tests/Postgres/PostgresSetUpFixture.cs b/src/DistributedLock.Tests/Tests/Postgres/PostgresSetUpFixture.cs new file mode 100644 index 00000000..0bf86406 --- /dev/null +++ b/src/DistributedLock.Tests/Tests/Postgres/PostgresSetUpFixture.cs @@ -0,0 +1,19 @@ +using NUnit.Framework; +using Testcontainers.PostgreSql; + +namespace Medallion.Threading.Tests.Postgres; + +public class PostgresSetUpFixture +{ + public static PostgreSqlContainer PostgreSql; + + [OneTimeSetUp] + public static async Task OneTimeSetUp() + { + PostgreSql = new PostgreSqlBuilder().Build(); + await PostgreSql.StartAsync(); + } + + [OneTimeTearDown] + public async Task OneTimeTearDown() => await PostgreSql.DisposeAsync(); +} \ No newline at end of file From 01e284f123e4dbdb77fd859ded10ff1e50da9ada Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Mon, 3 Jun 2024 21:55:40 +0300 Subject: [PATCH 08/27] only windows for now --- .github/workflows/ci.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3a752081..711b081f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -6,9 +6,8 @@ jobs: build: strategy: matrix: - os: [ubuntu-latest, windows-latest] categort: [Redis, Postgres] - runs-on: ${{ matrix.os }} + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From 27a768091331a9a7b94b0a8d18b93c5fcb495d7e Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Mon, 3 Jun 2024 22:09:17 +0300 Subject: [PATCH 09/27] postgresql fixes --- .../Infrastructure/Redis/RedisSetUpFixture.cs | 2 +- .../Infrastructure/Shared/RedisPorts.cs | 10 ---------- .../Tests/Postgres/PostgresBehaviorTest.cs | 2 +- .../Tests/Postgres/PostgresSetUpFixture.cs | 3 ++- 4 files changed, 4 insertions(+), 13 deletions(-) delete mode 100644 src/DistributedLock.Tests/Infrastructure/Shared/RedisPorts.cs diff --git a/src/DistributedLock.Tests/Infrastructure/Redis/RedisSetUpFixture.cs b/src/DistributedLock.Tests/Infrastructure/Redis/RedisSetUpFixture.cs index e2bbdfe5..8147cfcd 100644 --- a/src/DistributedLock.Tests/Infrastructure/Redis/RedisSetUpFixture.cs +++ b/src/DistributedLock.Tests/Infrastructure/Redis/RedisSetUpFixture.cs @@ -16,5 +16,5 @@ public static async Task OneTimeSetUp() } [OneTimeTearDown] - public async Task OneTimeTearDown() => await Redis.DisposeAsync(); + public static async Task OneTimeTearDown() => await Redis.DisposeAsync(); } diff --git a/src/DistributedLock.Tests/Infrastructure/Shared/RedisPorts.cs b/src/DistributedLock.Tests/Infrastructure/Shared/RedisPorts.cs deleted file mode 100644 index e970f9d8..00000000 --- a/src/DistributedLock.Tests/Infrastructure/Shared/RedisPorts.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Medallion.Threading.Tests; - -internal static class RedisPorts -{ - // 6379 is the redis default, so don't use that - public static readonly IReadOnlyList DefaultPorts = Enumerable.Range(6380, count: 10).ToArray(); -} diff --git a/src/DistributedLock.Tests/Tests/Postgres/PostgresBehaviorTest.cs b/src/DistributedLock.Tests/Tests/Postgres/PostgresBehaviorTest.cs index 059d4ff5..710d9d61 100644 --- a/src/DistributedLock.Tests/Tests/Postgres/PostgresBehaviorTest.cs +++ b/src/DistributedLock.Tests/Tests/Postgres/PostgresBehaviorTest.cs @@ -135,7 +135,7 @@ public async Task TestDoesNotDetectConnectionBreakViaState() Assert.That(stateChangedEvent.Wait(TimeSpan.FromSeconds(.1)), Is.False); - Assert.Throws(() => getPidCommand.ExecuteScalar()); + Assert.Throws(() => getPidCommand.ExecuteScalar()); Assert.That(stateChangedEvent.Wait(TimeSpan.FromSeconds(5)), Is.True); } diff --git a/src/DistributedLock.Tests/Tests/Postgres/PostgresSetUpFixture.cs b/src/DistributedLock.Tests/Tests/Postgres/PostgresSetUpFixture.cs index 0bf86406..b6ba4569 100644 --- a/src/DistributedLock.Tests/Tests/Postgres/PostgresSetUpFixture.cs +++ b/src/DistributedLock.Tests/Tests/Postgres/PostgresSetUpFixture.cs @@ -3,6 +3,7 @@ namespace Medallion.Threading.Tests.Postgres; +[SetUpFixture] public class PostgresSetUpFixture { public static PostgreSqlContainer PostgreSql; @@ -15,5 +16,5 @@ public static async Task OneTimeSetUp() } [OneTimeTearDown] - public async Task OneTimeTearDown() => await PostgreSql.DisposeAsync(); + public static async Task OneTimeTearDown() => await PostgreSql.DisposeAsync(); } \ No newline at end of file From 2fd65941f9aa90a38b4fdd514a7744e09176b840 Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Mon, 3 Jun 2024 22:36:20 +0300 Subject: [PATCH 10/27] add mssql --- .github/workflows/ci.yaml | 2 +- src/Directory.Packages.props | 1 + .../DistributedLock.Tests.csproj | 1 + .../SqlServer/TestingSqlServerDb.cs | 32 +++++++++++++++---- .../SqlServer/SqlDatabaseConnectionTest.cs | 4 +-- .../Tests/SqlServer/SqlDistributedLockTest.cs | 10 +++--- .../SqlDistributedReaderWriterLockTest.cs | 8 ++--- .../SqlServer/SqlDistributedSemaphoreTest.cs | 10 +++--- ...lDistributedSynchronizationProviderTest.cs | 2 +- .../Tests/SqlServer/SqlServerSetUpFixture.cs | 20 ++++++++++++ src/DistributedLock.Tests/packages.lock.json | 9 ++++++ 11 files changed, 74 insertions(+), 25 deletions(-) create mode 100644 src/DistributedLock.Tests/Tests/SqlServer/SqlServerSetUpFixture.cs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 711b081f..5b2fdb6f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -6,7 +6,7 @@ jobs: build: strategy: matrix: - categort: [Redis, Postgres] + categort: [Redis, Postgres, SqlServer] runs-on: ubuntu-latest steps: diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 5096e976..523a07f6 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -22,6 +22,7 @@ + diff --git a/src/DistributedLock.Tests/DistributedLock.Tests.csproj b/src/DistributedLock.Tests/DistributedLock.Tests.csproj index 02f5697f..5369119c 100644 --- a/src/DistributedLock.Tests/DistributedLock.Tests.csproj +++ b/src/DistributedLock.Tests/DistributedLock.Tests.csproj @@ -27,6 +27,7 @@ + diff --git a/src/DistributedLock.Tests/Infrastructure/SqlServer/TestingSqlServerDb.cs b/src/DistributedLock.Tests/Infrastructure/SqlServer/TestingSqlServerDb.cs index a7f8327a..73f9cb3f 100644 --- a/src/DistributedLock.Tests/Infrastructure/SqlServer/TestingSqlServerDb.cs +++ b/src/DistributedLock.Tests/Infrastructure/SqlServer/TestingSqlServerDb.cs @@ -2,6 +2,7 @@ using Medallion.Threading.Tests.Data; using System.Data; using System.Data.Common; +using Testcontainers.MsSql; namespace Medallion.Threading.Tests.SqlServer; @@ -9,10 +10,9 @@ public interface ITestingSqlServerDb { } public sealed class TestingSqlServerDb : TestingPrimaryClientDb, ITestingSqlServerDb { - internal static readonly string DefaultConnectionString = SqlServerCredentials.ConnectionString; + private MsSqlContainer _container = new MsSqlBuilder().Build(); - private readonly Microsoft.Data.SqlClient.SqlConnectionStringBuilder _connectionStringBuilder = - new(DefaultConnectionString); + private Microsoft.Data.SqlClient.SqlConnectionStringBuilder _connectionStringBuilder; public override DbConnectionStringBuilder ConnectionStringBuilder => this._connectionStringBuilder; @@ -25,7 +25,7 @@ public override int CountActiveSessions(string applicationName) { Invariant.Require(applicationName.Length <= this.MaxApplicationNameLength); - using var connection = new Microsoft.Data.SqlClient.SqlConnection(DefaultConnectionString); + using var connection = new Microsoft.Data.SqlClient.SqlConnection(_container.GetConnectionString()); connection.Open(); using var command = connection.CreateCommand(); command.CommandText = $@"SELECT COUNT(*) FROM sys.dm_exec_sessions WHERE program_name = @applicationName"; @@ -54,7 +54,7 @@ FROM sys.dm_exec_sessions public override async Task KillSessionsAsync(string applicationName, DateTimeOffset? idleSince) { - using var connection = new Microsoft.Data.SqlClient.SqlConnection(DefaultConnectionString); + using var connection = new Microsoft.Data.SqlClient.SqlConnection(_container.GetConnectionString()); await connection.OpenAsync(); var findIdleSessionsCommand = connection.CreateCommand(); @@ -89,12 +89,22 @@ @idleSince IS NULL catch (Exception ex) { Console.WriteLine($"Failed to kill {spid}: {ex}"); } } } + + public override async ValueTask SetupAsync() + { + await _container.StartAsync(); + _connectionStringBuilder = new(_container.GetConnectionString()); + + } + + public override async ValueTask DisposeAsync() => await _container.StopAsync(); } public sealed class TestingSystemDataSqlServerDb : TestingDb, ITestingSqlServerDb { - private readonly System.Data.SqlClient.SqlConnectionStringBuilder _connectionStringBuilder = - new(TestingSqlServerDb.DefaultConnectionString); + private MsSqlContainer _container = new MsSqlBuilder().Build(); + + private System.Data.SqlClient.SqlConnectionStringBuilder _connectionStringBuilder; public override DbConnectionStringBuilder ConnectionStringBuilder => this._connectionStringBuilder; @@ -107,4 +117,12 @@ public sealed class TestingSystemDataSqlServerDb : TestingDb, ITestingSqlServerD public override IsolationLevel GetIsolationLevel(DbConnection connection) => new TestingSqlServerDb().GetIsolationLevel(connection); public override DbConnection CreateConnection() => new System.Data.SqlClient.SqlConnection(this.ConnectionStringBuilder.ConnectionString); + + public override async ValueTask SetupAsync() + { + await _container.StartAsync(); + _connectionStringBuilder = new(_container.GetConnectionString()); + } + + public override async ValueTask DisposeAsync() => await _container.StopAsync(); } diff --git a/src/DistributedLock.Tests/Tests/SqlServer/SqlDatabaseConnectionTest.cs b/src/DistributedLock.Tests/Tests/SqlServer/SqlDatabaseConnectionTest.cs index 6ec43b83..a9bbcbe4 100644 --- a/src/DistributedLock.Tests/Tests/SqlServer/SqlDatabaseConnectionTest.cs +++ b/src/DistributedLock.Tests/Tests/SqlServer/SqlDatabaseConnectionTest.cs @@ -68,8 +68,8 @@ public async Task TestExecuteNonQueryCanCancel([Values] bool isAsync, [Values] b private static SqlDatabaseConnection CreateConnection(bool isSystemDataSqlClient) => new( isSystemDataSqlClient - ? new System.Data.SqlClient.SqlConnection(TestingSqlServerDb.DefaultConnectionString).As() - : new Microsoft.Data.SqlClient.SqlConnection(TestingSqlServerDb.DefaultConnectionString), + ? new System.Data.SqlClient.SqlConnection(SqlServerSetUpFixture.SqlServer.GetConnectionString()).As() + : new Microsoft.Data.SqlClient.SqlConnection(SqlServerSetUpFixture.SqlServer.GetConnectionString()), isExternallyOwned: false ); } diff --git a/src/DistributedLock.Tests/Tests/SqlServer/SqlDistributedLockTest.cs b/src/DistributedLock.Tests/Tests/SqlServer/SqlDistributedLockTest.cs index b3f3cf29..4d30faaa 100644 --- a/src/DistributedLock.Tests/Tests/SqlServer/SqlDistributedLockTest.cs +++ b/src/DistributedLock.Tests/Tests/SqlServer/SqlDistributedLockTest.cs @@ -10,13 +10,13 @@ public class SqlDistributedLockTest [Test] public void TestBadConstructorArguments() { - Assert.Catch(() => new SqlDistributedLock(null!, TestingSqlServerDb.DefaultConnectionString)); - Assert.Catch(() => new SqlDistributedLock(null!, TestingSqlServerDb.DefaultConnectionString, exactName: true)); + Assert.Catch(() => new SqlDistributedLock(null!, SqlServerSetUpFixture.SqlServer.GetConnectionString())); + Assert.Catch(() => new SqlDistributedLock(null!, SqlServerSetUpFixture.SqlServer.GetConnectionString(), exactName: true)); Assert.Catch(() => new SqlDistributedLock("a", default(string)!)); Assert.Catch(() => new SqlDistributedLock("a", default(IDbTransaction)!)); Assert.Catch(() => new SqlDistributedLock("a", default(IDbConnection)!)); - Assert.Catch(() => new SqlDistributedLock(new string('a', SqlDistributedLock.MaxNameLength + 1), TestingSqlServerDb.DefaultConnectionString, exactName: true)); - Assert.DoesNotThrow(() => new SqlDistributedLock(new string('a', SqlDistributedLock.MaxNameLength), TestingSqlServerDb.DefaultConnectionString, exactName: true)); + Assert.Catch(() => new SqlDistributedLock(new string('a', SqlDistributedLock.MaxNameLength + 1), SqlServerSetUpFixture.SqlServer.GetConnectionString(), exactName: true)); + Assert.DoesNotThrow(() => new SqlDistributedLock(new string('a', SqlDistributedLock.MaxNameLength), SqlServerSetUpFixture.SqlServer.GetConnectionString(), exactName: true)); } [Test] @@ -38,7 +38,7 @@ public void TestGetSafeLockNameCompat() [Test] public async Task TestSqlCommandMustParticipateInTransaction() { - using var connection = new SqlConnection(TestingSqlServerDb.DefaultConnectionString); + using var connection = new SqlConnection(SqlServerSetUpFixture.SqlServer.GetConnectionString()); await connection.OpenAsync(); using var transaction = connection.BeginTransaction(); diff --git a/src/DistributedLock.Tests/Tests/SqlServer/SqlDistributedReaderWriterLockTest.cs b/src/DistributedLock.Tests/Tests/SqlServer/SqlDistributedReaderWriterLockTest.cs index 461b4352..13cd0a7e 100644 --- a/src/DistributedLock.Tests/Tests/SqlServer/SqlDistributedReaderWriterLockTest.cs +++ b/src/DistributedLock.Tests/Tests/SqlServer/SqlDistributedReaderWriterLockTest.cs @@ -9,13 +9,13 @@ public sealed class SqlDistributedReaderWriterLockTest [Test] public void TestBadConstructorArguments() { - Assert.Catch(() => new SqlDistributedReaderWriterLock(null!, TestingSqlServerDb.DefaultConnectionString)); - Assert.Catch(() => new SqlDistributedReaderWriterLock(null!, TestingSqlServerDb.DefaultConnectionString, exactName: true)); + Assert.Catch(() => new SqlDistributedReaderWriterLock(null!, SqlServerSetUpFixture.SqlServer.GetConnectionString())); + Assert.Catch(() => new SqlDistributedReaderWriterLock(null!, SqlServerSetUpFixture.SqlServer.GetConnectionString(), exactName: true)); Assert.Catch(() => new SqlDistributedReaderWriterLock("a", default(string)!)); Assert.Catch(() => new SqlDistributedReaderWriterLock("a", default(DbTransaction)!)); Assert.Catch(() => new SqlDistributedReaderWriterLock("a", default(DbConnection)!)); - Assert.Catch(() => new SqlDistributedReaderWriterLock(new string('a', SqlDistributedReaderWriterLock.MaxNameLength + 1), TestingSqlServerDb.DefaultConnectionString, exactName: true)); - Assert.DoesNotThrow(() => new SqlDistributedReaderWriterLock(new string('a', SqlDistributedReaderWriterLock.MaxNameLength), TestingSqlServerDb.DefaultConnectionString, exactName: true)); + Assert.Catch(() => new SqlDistributedReaderWriterLock(new string('a', SqlDistributedReaderWriterLock.MaxNameLength + 1), SqlServerSetUpFixture.SqlServer.GetConnectionString(), exactName: true)); + Assert.DoesNotThrow(() => new SqlDistributedReaderWriterLock(new string('a', SqlDistributedReaderWriterLock.MaxNameLength), SqlServerSetUpFixture.SqlServer.GetConnectionString(), exactName: true)); } [Test] diff --git a/src/DistributedLock.Tests/Tests/SqlServer/SqlDistributedSemaphoreTest.cs b/src/DistributedLock.Tests/Tests/SqlServer/SqlDistributedSemaphoreTest.cs index f13a3155..ab43e9ec 100644 --- a/src/DistributedLock.Tests/Tests/SqlServer/SqlDistributedSemaphoreTest.cs +++ b/src/DistributedLock.Tests/Tests/SqlServer/SqlDistributedSemaphoreTest.cs @@ -12,9 +12,9 @@ public sealed class SqlDistributedSemaphoreTest [Test] public void TestBadConstructorArguments() { - Assert.Catch(() => new SqlDistributedSemaphore(null!, 1, TestingSqlServerDb.DefaultConnectionString)); - Assert.Catch(() => new SqlDistributedSemaphore("a", -1, TestingSqlServerDb.DefaultConnectionString)); - Assert.Catch(() => new SqlDistributedSemaphore("a", 0, TestingSqlServerDb.DefaultConnectionString)); + Assert.Catch(() => new SqlDistributedSemaphore(null!, 1, SqlServerSetUpFixture.SqlServer.GetConnectionString())); + Assert.Catch(() => new SqlDistributedSemaphore("a", -1, SqlServerSetUpFixture.SqlServer.GetConnectionString())); + Assert.Catch(() => new SqlDistributedSemaphore("a", 0, SqlServerSetUpFixture.SqlServer.GetConnectionString())); Assert.Catch(() => new SqlDistributedSemaphore("a", 1, default(string)!)); Assert.Catch(() => new SqlDistributedSemaphore("a", 1, default(IDbConnection)!)); Assert.Catch(() => new SqlDistributedSemaphore("a", 1, default(IDbTransaction)!)); @@ -22,7 +22,7 @@ public void TestBadConstructorArguments() var random = new Random(1234); var bytes = new byte[10000]; random.NextBytes(bytes); - Assert.DoesNotThrow(() => new SqlDistributedSemaphore(Encoding.UTF8.GetString(bytes), int.MaxValue, TestingSqlServerDb.DefaultConnectionString)); + Assert.DoesNotThrow(() => new SqlDistributedSemaphore(Encoding.UTF8.GetString(bytes), int.MaxValue, SqlServerSetUpFixture.SqlServer.GetConnectionString())); } [Test] @@ -70,7 +70,7 @@ public void TestNameManglingCompatibility() [Test] public void TestTicketsTakenOnBothConnectionAndTransactionForThatConnection() { - using var connection = new SqlConnection(TestingSqlServerDb.DefaultConnectionString); + using var connection = new SqlConnection(SqlServerSetUpFixture.SqlServer.GetConnectionString()); connection.Open(); var semaphore1 = new SqlDistributedSemaphore( diff --git a/src/DistributedLock.Tests/Tests/SqlServer/SqlDistributedSynchronizationProviderTest.cs b/src/DistributedLock.Tests/Tests/SqlServer/SqlDistributedSynchronizationProviderTest.cs index 473e7a44..c7a82835 100644 --- a/src/DistributedLock.Tests/Tests/SqlServer/SqlDistributedSynchronizationProviderTest.cs +++ b/src/DistributedLock.Tests/Tests/SqlServer/SqlDistributedSynchronizationProviderTest.cs @@ -17,7 +17,7 @@ public void TestArgumentValidation() [Test] public async Task BasicTest() { - var provider = new SqlDistributedSynchronizationProvider(TestingSqlServerDb.DefaultConnectionString); + var provider = new SqlDistributedSynchronizationProvider(SqlServerSetUpFixture.SqlServer.GetConnectionString()); const string LockName = TargetFramework.Current + "ProviderBasicTest"; await using (await provider.AcquireLockAsync(LockName)) diff --git a/src/DistributedLock.Tests/Tests/SqlServer/SqlServerSetUpFixture.cs b/src/DistributedLock.Tests/Tests/SqlServer/SqlServerSetUpFixture.cs new file mode 100644 index 00000000..0fd621a0 --- /dev/null +++ b/src/DistributedLock.Tests/Tests/SqlServer/SqlServerSetUpFixture.cs @@ -0,0 +1,20 @@ +using NUnit.Framework; +using Testcontainers.MsSql; + +namespace Medallion.Threading.Tests.SqlServer; + +[SetUpFixture] +public class SqlServerSetUpFixture +{ + public static MsSqlContainer SqlServer; + + [OneTimeSetUp] + public static async Task OneTimeSetUp() + { + SqlServer = new MsSqlBuilder().Build(); + await SqlServer.StartAsync(); + } + + [OneTimeTearDown] + public static async Task OneTimeTearDown() => await SqlServer.DisposeAsync(); +} \ No newline at end of file diff --git a/src/DistributedLock.Tests/packages.lock.json b/src/DistributedLock.Tests/packages.lock.json index 2c87cc69..00cb9b01 100644 --- a/src/DistributedLock.Tests/packages.lock.json +++ b/src/DistributedLock.Tests/packages.lock.json @@ -59,6 +59,15 @@ "runtime.native.System.Data.SqlClient.sni": "4.7.0" } }, + "Testcontainers.MsSql": { + "type": "Direct", + "requested": "[3.8.0, )", + "resolved": "3.8.0", + "contentHash": "4SUnPddk4VbXDZkeSxyCSQA1pFlvlMh5KA0iYXiRykG71F2+F0fyweogoTnD/dVU2NDMVwxJSzqz6SN1wzPAZw==", + "dependencies": { + "Testcontainers": "3.8.0" + } + }, "Testcontainers.PostgreSql": { "type": "Direct", "requested": "[3.8.0, )", From 64d06ce4504cdee801040c5a8e28a509542d3ab3 Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Mon, 3 Jun 2024 22:38:06 +0300 Subject: [PATCH 11/27] simplify docs --- docs/Developing DistributedLock.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/Developing DistributedLock.md b/docs/Developing DistributedLock.md index 1e67e922..de29dd5d 100644 --- a/docs/Developing DistributedLock.md +++ b/docs/Developing DistributedLock.md @@ -55,15 +55,11 @@ Have docker installed, we are using https://testcontainers.com/modules/postgresq ### SQL Server -Download SQL developer edition from [here](https://www.microsoft.com/en-us/sql-server/sql-server-downloads). - -The tests connect via integrated security. +Have docker installed, we are using https://testcontainers.com/modules/mssql/ ### Redis -Install Redis locally. On Windows, install it via WSL as described [here](https://developer.redis.com/create/windows/). - -You do not need it running as a service: the tests will start and stop instances automatically. +Have docker installed, we are using https://testcontainers.com/modules/redis/ ### ZooKeeper From 067524ecc764f50b156e988948c9d23bb17f0aba Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Mon, 3 Jun 2024 22:49:48 +0300 Subject: [PATCH 12/27] sql server integration fix --- .../Shared/SqlServerCredentials.cs | 21 ------------------- src/DistributedLockTaker/Program.cs | 8 +++---- 2 files changed, 4 insertions(+), 25 deletions(-) delete mode 100644 src/DistributedLock.Tests/Infrastructure/Shared/SqlServerCredentials.cs diff --git a/src/DistributedLock.Tests/Infrastructure/Shared/SqlServerCredentials.cs b/src/DistributedLock.Tests/Infrastructure/Shared/SqlServerCredentials.cs deleted file mode 100644 index d9598e32..00000000 --- a/src/DistributedLock.Tests/Infrastructure/Shared/SqlServerCredentials.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Medallion.Threading.Tests; - -internal static class SqlServerCredentials -{ - public static readonly string ApplicationName = $"{typeof(SqlServerCredentials).Assembly.GetName().Name} ({TargetFramework.Current})"; - - public static readonly string ConnectionString = new Microsoft.Data.SqlClient.SqlConnectionStringBuilder - { - DataSource = @"localhost", // localhost for SQL Developer, .\SQLEXPRESS for express - InitialCatalog = "master", - IntegratedSecurity = true, - ApplicationName = ApplicationName, - // set a high pool size so that we don't empty the pool through things like lock abandonment tests - MaxPoolSize = 10000, - // Allows us to connect to SQLExpress with Microsoft.Data.SqlClient while still being compatible - // with System.Data.SqlClient (alternative would be building the connection string with System.Data.SqlClient - // and doing TrustServerCertificate = true). - Encrypt = false, - } - .ConnectionString; -} diff --git a/src/DistributedLockTaker/Program.cs b/src/DistributedLockTaker/Program.cs index 09044daf..f158a6c0 100644 --- a/src/DistributedLockTaker/Program.cs +++ b/src/DistributedLockTaker/Program.cs @@ -30,16 +30,16 @@ public static int Main(string[] args) switch (type) { case nameof(SqlDistributedLock): - handle = new SqlDistributedLock(name, SqlServerCredentials.ConnectionString).Acquire(); + handle = new SqlDistributedLock(name, connectionString).Acquire(); break; case "Write" + nameof(SqlDistributedReaderWriterLock): - handle = new SqlDistributedReaderWriterLock(name, SqlServerCredentials.ConnectionString).AcquireWriteLock(); + handle = new SqlDistributedReaderWriterLock(name, connectionString).AcquireWriteLock(); break; case nameof(SqlDistributedSemaphore) + "1AsMutex": - handle = new SqlDistributedSemaphore(name, maxCount: 1, connectionString: SqlServerCredentials.ConnectionString).Acquire(); + handle = new SqlDistributedSemaphore(name, maxCount: 1, connectionString: connectionString).Acquire(); break; case nameof(SqlDistributedSemaphore) + "5AsMutex": - handle = new SqlDistributedSemaphore(name, maxCount: 5, connectionString: SqlServerCredentials.ConnectionString).Acquire(); + handle = new SqlDistributedSemaphore(name, maxCount: 5, connectionString: connectionString).Acquire(); break; case nameof(PostgresDistributedLock): handle = new PostgresDistributedLock(new PostgresAdvisoryLockKey(name), connectionString).Acquire(); From 625ba4c152ec5870604d42b5d53e7f687385c5f3 Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Mon, 3 Jun 2024 22:50:01 +0300 Subject: [PATCH 13/27] better redis parallel test --- .../Redis/RedisSynchronizationCoreTestCases.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs b/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs index 99049d4a..06ea3bf5 100644 --- a/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs +++ b/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs @@ -161,11 +161,11 @@ private static void MockDatabase(Mock mockDatabase, Func return mockDatabase.Setup(d => d.ScriptEvaluate(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(() => RedisResult.Create(returns())); mockDatabase.Setup(d => d.ScriptEvaluateAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(() => Task.Run(() => RedisResult.Create(returns()))); + .ReturnsAsync(() => RedisResult.Create(returns())); mockDatabase.Setup(d => d.ScriptEvaluate(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(() => RedisResult.Create(returns())); mockDatabase.Setup(d => d.ScriptEvaluateAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(() => Task.Run(() => RedisResult.Create(returns()))); + .ReturnsAsync(() => RedisResult.Create(returns())); mockDatabase.Setup(d => d.SortedSetRemove(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(() => (bool)RedisResult.Create(returns())); mockDatabase.Setup(d => d.SortedSetRemoveAsync(It.IsAny(), It.IsAny(), It.IsAny())) From 0355abe33ddd48f21fbe6b6f3aa1e0b7637f4cc4 Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Tue, 4 Jun 2024 07:11:50 +0300 Subject: [PATCH 14/27] fix db cross-process --- .../Infrastructure/Data/TestingDbSynchronizationStrategy.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/DistributedLock.Tests/Infrastructure/Data/TestingDbSynchronizationStrategy.cs b/src/DistributedLock.Tests/Infrastructure/Data/TestingDbSynchronizationStrategy.cs index 0bd48839..3478279c 100644 --- a/src/DistributedLock.Tests/Infrastructure/Data/TestingDbSynchronizationStrategy.cs +++ b/src/DistributedLock.Tests/Infrastructure/Data/TestingDbSynchronizationStrategy.cs @@ -51,6 +51,8 @@ public sealed override TestingDbConnectionOptions GetConnectionOptions() => public sealed override IDisposable? PrepareForHandleLost() => new HandleLostScope(this.Db.SetUniqueApplicationName(nameof(this.PrepareForHandleLost)), this.Db); + public override string GetConnectionStringForCrossProcessTest() => this.Db.ConnectionString; + private class HandleLostScope : IDisposable { private string? _applicationName; From 808ba0405eddd7242a8b8c7a51e2f79156dc0bf7 Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Tue, 4 Jun 2024 07:25:15 +0300 Subject: [PATCH 15/27] fix tests --- .../RedisSynchronizationCoreTestCases.cs | 4 +-- .../SqlServer/TestingSqlServerDb.cs | 25 ++++++------------- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs b/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs index 06ea3bf5..165cdbf3 100644 --- a/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs +++ b/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs @@ -157,7 +157,7 @@ private static void MockDatabase(Mock mockDatabase, Func return mockDatabase.Setup(d => d.StringSet(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(returns); mockDatabase.Setup(d => d.StringSetAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(() => Task.Run(returns)); + .ReturnsAsync(() => returns()); mockDatabase.Setup(d => d.ScriptEvaluate(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(() => RedisResult.Create(returns())); mockDatabase.Setup(d => d.ScriptEvaluateAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) @@ -169,7 +169,7 @@ private static void MockDatabase(Mock mockDatabase, Func return mockDatabase.Setup(d => d.SortedSetRemove(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(() => (bool)RedisResult.Create(returns())); mockDatabase.Setup(d => d.SortedSetRemoveAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(() => Task.Run(() => (bool)RedisResult.Create(returns()))); + .ReturnsAsync(() => (bool)RedisResult.Create(returns())); mockDatabase.Setup(d => d.IsConnected(It.IsAny(), It.IsAny())) .Returns(true); } diff --git a/src/DistributedLock.Tests/Infrastructure/SqlServer/TestingSqlServerDb.cs b/src/DistributedLock.Tests/Infrastructure/SqlServer/TestingSqlServerDb.cs index 73f9cb3f..bbcfb321 100644 --- a/src/DistributedLock.Tests/Infrastructure/SqlServer/TestingSqlServerDb.cs +++ b/src/DistributedLock.Tests/Infrastructure/SqlServer/TestingSqlServerDb.cs @@ -10,8 +10,6 @@ public interface ITestingSqlServerDb { } public sealed class TestingSqlServerDb : TestingPrimaryClientDb, ITestingSqlServerDb { - private MsSqlContainer _container = new MsSqlBuilder().Build(); - private Microsoft.Data.SqlClient.SqlConnectionStringBuilder _connectionStringBuilder; public override DbConnectionStringBuilder ConnectionStringBuilder => this._connectionStringBuilder; @@ -25,7 +23,7 @@ public override int CountActiveSessions(string applicationName) { Invariant.Require(applicationName.Length <= this.MaxApplicationNameLength); - using var connection = new Microsoft.Data.SqlClient.SqlConnection(_container.GetConnectionString()); + using var connection = new Microsoft.Data.SqlClient.SqlConnection(SqlServerSetUpFixture.SqlServer.GetConnectionString()); connection.Open(); using var command = connection.CreateCommand(); command.CommandText = $@"SELECT COUNT(*) FROM sys.dm_exec_sessions WHERE program_name = @applicationName"; @@ -54,7 +52,7 @@ FROM sys.dm_exec_sessions public override async Task KillSessionsAsync(string applicationName, DateTimeOffset? idleSince) { - using var connection = new Microsoft.Data.SqlClient.SqlConnection(_container.GetConnectionString()); + using var connection = new Microsoft.Data.SqlClient.SqlConnection(SqlServerSetUpFixture.SqlServer.GetConnectionString()); await connection.OpenAsync(); var findIdleSessionsCommand = connection.CreateCommand(); @@ -90,20 +88,15 @@ @idleSince IS NULL } } - public override async ValueTask SetupAsync() + public override ValueTask SetupAsync() { - await _container.StartAsync(); - _connectionStringBuilder = new(_container.GetConnectionString()); - + _connectionStringBuilder = new(SqlServerSetUpFixture.SqlServer.GetConnectionString()); + return ValueTask.CompletedTask; } - - public override async ValueTask DisposeAsync() => await _container.StopAsync(); } public sealed class TestingSystemDataSqlServerDb : TestingDb, ITestingSqlServerDb { - private MsSqlContainer _container = new MsSqlBuilder().Build(); - private System.Data.SqlClient.SqlConnectionStringBuilder _connectionStringBuilder; public override DbConnectionStringBuilder ConnectionStringBuilder => this._connectionStringBuilder; @@ -118,11 +111,9 @@ public sealed class TestingSystemDataSqlServerDb : TestingDb, ITestingSqlServerD public override DbConnection CreateConnection() => new System.Data.SqlClient.SqlConnection(this.ConnectionStringBuilder.ConnectionString); - public override async ValueTask SetupAsync() + public override ValueTask SetupAsync() { - await _container.StartAsync(); - _connectionStringBuilder = new(_container.GetConnectionString()); + _connectionStringBuilder = new(SqlServerSetUpFixture.SqlServer.GetConnectionString()); + return ValueTask.CompletedTask; } - - public override async ValueTask DisposeAsync() => await _container.StopAsync(); } From 16079c46be1786f97c5531e1d98650165854e1df Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Tue, 4 Jun 2024 10:41:41 +0300 Subject: [PATCH 16/27] fix dynamic test --- .../ExternalTransactionStrategyTestCases.cs | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/DistributedLock.Tests/AbstractTestCases/Data/ExternalTransactionStrategyTestCases.cs b/src/DistributedLock.Tests/AbstractTestCases/Data/ExternalTransactionStrategyTestCases.cs index 136bf325..d6c9f06e 100644 --- a/src/DistributedLock.Tests/AbstractTestCases/Data/ExternalTransactionStrategyTestCases.cs +++ b/src/DistributedLock.Tests/AbstractTestCases/Data/ExternalTransactionStrategyTestCases.cs @@ -20,7 +20,7 @@ public async Task SetUp() public async Task TearDown() => await this._lockProvider.DisposeAsync(); [Test] - public void TestScopedToTransactionOnly() + public async Task TestScopedToTransactionOnly() { this._lockProvider.Strategy.StartAmbient(); @@ -30,8 +30,8 @@ public void TestScopedToTransactionOnly() Assert.That(this._lockProvider.CreateLock(nameof(TestScopedToTransactionOnly)).IsHeld(), Is.True); // create a lock of the same type on the underlying connection of the ambient transaction - using dynamic specificConnectionProvider = Activator.CreateInstance( - ReplaceGenericParameter(typeof(TLockProvider), this._lockProvider.Strategy.GetType(), typeof(SpecificConnectionStrategy)) + await using dynamic specificConnectionProvider = Activator.CreateInstance( + ReplaceType(typeof(TLockProvider), this._lockProvider.Strategy.GetType(), typeof(SpecificConnectionStrategy)) )!; specificConnectionProvider.Strategy.Test = this; Assert.Catch(() => ((IDistributedLock)specificConnectionProvider.CreateLock(nameof(TestScopedToTransactionOnly))).Acquire()); @@ -45,8 +45,29 @@ static Type ReplaceGenericParameter(Type type, Type old, Type @new) var newGenericArguments = type.GetGenericArguments() .Select(a => ReplaceGenericParameter(a, old, @new)) .ToArray(); - return type.GetGenericTypeDefinition() + var result = type.GetGenericTypeDefinition() .MakeGenericType(newGenericArguments); + + return result; + } + + static Type ReplaceType(Type type, Type targetType, Type replaceType) + { + if (!type.IsGenericType) + { + return type; // Not a generic type, return as is + } + + var genericTypeDefinition = type.GetGenericTypeDefinition(); + var typeArguments = type.GetGenericArguments(); + + var replacedArguments = new Type[typeArguments.Length]; + for (int i = 0; i < typeArguments.Length; i++) + { + replacedArguments[i] = ReplaceType(typeArguments[i], targetType, replaceType); + } + + return genericTypeDefinition.MakeGenericType(replacedArguments); } } @@ -56,6 +77,11 @@ static Type ReplaceGenericParameter(Type type, Type old, Type @new) /// private class SpecificConnectionStrategy : TestingDbSynchronizationStrategy { + public SpecificConnectionStrategy() + { + Console.WriteLine("SpecificConnectionStrategy created"); + } + public ExternalTransactionStrategyTestCases? Test { get; set; } public override TestingDbConnectionOptions GetConnectionOptions() => From e52f516aedca9a75a4fe23fea58ceff057b828f7 Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Tue, 4 Jun 2024 22:49:19 +0300 Subject: [PATCH 17/27] remove unused file --- .../Tests/GlobalSetupTest.cs | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 src/DistributedLock.Tests/Tests/GlobalSetupTest.cs diff --git a/src/DistributedLock.Tests/Tests/GlobalSetupTest.cs b/src/DistributedLock.Tests/Tests/GlobalSetupTest.cs deleted file mode 100644 index f9686f26..00000000 --- a/src/DistributedLock.Tests/Tests/GlobalSetupTest.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Medallion.Threading.Tests.Postgres; -using NUnit.Framework; - -namespace Medallion.Threading.Tests; - -public class GlobalSetupTest -{ - - [OneTimeSetUp] - public async Task GlobalSetup() - { - // await PostgresDb.Container.StartAsync(); - } - - [OneTimeTearDown] - public async Task GlobalTeardown() - { - await PostgresDb.Container.StopAsync(); - } -} \ No newline at end of file From b954f2013ff4c8901cf2a3c16950169dceba8994 Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Tue, 4 Jun 2024 22:51:59 +0300 Subject: [PATCH 18/27] remove unused file --- .../Infrastructure/Postgres/PostgresDb.cs | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 src/DistributedLock.Tests/Infrastructure/Postgres/PostgresDb.cs diff --git a/src/DistributedLock.Tests/Infrastructure/Postgres/PostgresDb.cs b/src/DistributedLock.Tests/Infrastructure/Postgres/PostgresDb.cs deleted file mode 100644 index e7f8ccb5..00000000 --- a/src/DistributedLock.Tests/Infrastructure/Postgres/PostgresDb.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Testcontainers.PostgreSql; - -namespace Medallion.Threading.Tests.Postgres; - -public class PostgresDb -{ - public static PostgreSqlContainer Container { get; } = new PostgreSqlBuilder().Build(); - - static PostgresDb() - { - Initialize().Wait(); - } - - private static async Task Initialize() - { - await Container.StartAsync(); - } -} \ No newline at end of file From 8a0fc880b3f249eea75355ce67f17599269011c1 Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Tue, 4 Jun 2024 22:59:45 +0300 Subject: [PATCH 19/27] fix mssql --- .../Infrastructure/Data/TestingDbSynchronizationStrategy.cs | 4 ++-- .../Infrastructure/TestingSynchronizationStrategy.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/DistributedLock.Tests/Infrastructure/Data/TestingDbSynchronizationStrategy.cs b/src/DistributedLock.Tests/Infrastructure/Data/TestingDbSynchronizationStrategy.cs index 3478279c..76f10a34 100644 --- a/src/DistributedLock.Tests/Infrastructure/Data/TestingDbSynchronizationStrategy.cs +++ b/src/DistributedLock.Tests/Infrastructure/Data/TestingDbSynchronizationStrategy.cs @@ -21,6 +21,8 @@ public override void PrepareForHighContention(ref int maxConcurrentAcquires) => public override ValueTask SetupAsync() => this.Db.SetupAsync(); public override ValueTask DisposeAsync() => this.Db.DisposeAsync(); + + public override string GetConnectionStringForCrossProcessTest() => this.Db.ConnectionString; } public abstract class TestingDbSynchronizationStrategy : TestingDbSynchronizationStrategy @@ -51,8 +53,6 @@ public sealed override TestingDbConnectionOptions GetConnectionOptions() => public sealed override IDisposable? PrepareForHandleLost() => new HandleLostScope(this.Db.SetUniqueApplicationName(nameof(this.PrepareForHandleLost)), this.Db); - public override string GetConnectionStringForCrossProcessTest() => this.Db.ConnectionString; - private class HandleLostScope : IDisposable { private string? _applicationName; diff --git a/src/DistributedLock.Tests/Infrastructure/TestingSynchronizationStrategy.cs b/src/DistributedLock.Tests/Infrastructure/TestingSynchronizationStrategy.cs index c8c67d3c..e5830738 100644 --- a/src/DistributedLock.Tests/Infrastructure/TestingSynchronizationStrategy.cs +++ b/src/DistributedLock.Tests/Infrastructure/TestingSynchronizationStrategy.cs @@ -16,7 +16,7 @@ public virtual void PrepareForHandleAbandonment() { } public virtual void PerformAdditionalCleanupForHandleAbandonment() { } public virtual IDisposable? PrepareForHandleLost() => null; public virtual void PrepareForHighContention(ref int maxConcurrentAcquires) { } - public virtual string GetConnectionStringForCrossProcessTest() => ""; + public virtual string GetConnectionStringForCrossProcessTest() => throw new NotSupportedException(""); public virtual ValueTask SetupAsync() => ValueTask.CompletedTask; public virtual ValueTask DisposeAsync() => ValueTask.CompletedTask; } From 34dd6cafa5a2a24bf9ee7e53dd43f3e6efdf51ed Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Tue, 4 Jun 2024 23:17:11 +0300 Subject: [PATCH 20/27] fix mssql --- .../ExternalTransactionStrategyTestCases.cs | 25 ++----------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/src/DistributedLock.Tests/AbstractTestCases/Data/ExternalTransactionStrategyTestCases.cs b/src/DistributedLock.Tests/AbstractTestCases/Data/ExternalTransactionStrategyTestCases.cs index d6c9f06e..2c5233ac 100644 --- a/src/DistributedLock.Tests/AbstractTestCases/Data/ExternalTransactionStrategyTestCases.cs +++ b/src/DistributedLock.Tests/AbstractTestCases/Data/ExternalTransactionStrategyTestCases.cs @@ -31,7 +31,7 @@ public async Task TestScopedToTransactionOnly() // create a lock of the same type on the underlying connection of the ambient transaction await using dynamic specificConnectionProvider = Activator.CreateInstance( - ReplaceType(typeof(TLockProvider), this._lockProvider.Strategy.GetType(), typeof(SpecificConnectionStrategy)) + ReplaceGenericParameter(typeof(TLockProvider), this._lockProvider.Strategy.GetType(), typeof(SpecificConnectionStrategy)) )!; specificConnectionProvider.Strategy.Test = this; Assert.Catch(() => ((IDistributedLock)specificConnectionProvider.CreateLock(nameof(TestScopedToTransactionOnly))).Acquire()); @@ -45,29 +45,8 @@ static Type ReplaceGenericParameter(Type type, Type old, Type @new) var newGenericArguments = type.GetGenericArguments() .Select(a => ReplaceGenericParameter(a, old, @new)) .ToArray(); - var result = type.GetGenericTypeDefinition() + return type.GetGenericTypeDefinition() .MakeGenericType(newGenericArguments); - - return result; - } - - static Type ReplaceType(Type type, Type targetType, Type replaceType) - { - if (!type.IsGenericType) - { - return type; // Not a generic type, return as is - } - - var genericTypeDefinition = type.GetGenericTypeDefinition(); - var typeArguments = type.GetGenericArguments(); - - var replacedArguments = new Type[typeArguments.Length]; - for (int i = 0; i < typeArguments.Length; i++) - { - replacedArguments[i] = ReplaceType(typeArguments[i], targetType, replaceType); - } - - return genericTypeDefinition.MakeGenericType(replacedArguments); } } From 5269f518b39f4aaf1ed5b01f4599dc3cdfc50ccd Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Tue, 4 Jun 2024 23:31:07 +0300 Subject: [PATCH 21/27] Update ci.yaml --- .github/workflows/ci.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5b2fdb6f..22dff7f6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -6,7 +6,7 @@ jobs: build: strategy: matrix: - categort: [Redis, Postgres, SqlServer] + category: [Redis, Postgres, SqlServer] runs-on: ubuntu-latest steps: @@ -20,5 +20,5 @@ jobs: - name: Build run: dotnet build src - - name: Redis Tests - run: dotnet test src --filter Name~${{ matrix.categort }} \ No newline at end of file + - name: Run Tests + run: dotnet test src --filter Name~${{ matrix.category }} From 5258551ddfb16c058803fc09fcb8ed53c9e4d1ce Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Wed, 5 Jun 2024 07:57:24 +0300 Subject: [PATCH 22/27] fix redis --- .../Redis/RedisSynchronizationCoreTestCases.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs b/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs index 165cdbf3..99049d4a 100644 --- a/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs +++ b/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs @@ -157,19 +157,19 @@ private static void MockDatabase(Mock mockDatabase, Func return mockDatabase.Setup(d => d.StringSet(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(returns); mockDatabase.Setup(d => d.StringSetAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .ReturnsAsync(() => returns()); + .Returns(() => Task.Run(returns)); mockDatabase.Setup(d => d.ScriptEvaluate(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(() => RedisResult.Create(returns())); mockDatabase.Setup(d => d.ScriptEvaluateAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .ReturnsAsync(() => RedisResult.Create(returns())); + .Returns(() => Task.Run(() => RedisResult.Create(returns()))); mockDatabase.Setup(d => d.ScriptEvaluate(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(() => RedisResult.Create(returns())); mockDatabase.Setup(d => d.ScriptEvaluateAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .ReturnsAsync(() => RedisResult.Create(returns())); + .Returns(() => Task.Run(() => RedisResult.Create(returns()))); mockDatabase.Setup(d => d.SortedSetRemove(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(() => (bool)RedisResult.Create(returns())); mockDatabase.Setup(d => d.SortedSetRemoveAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .ReturnsAsync(() => (bool)RedisResult.Create(returns())); + .Returns(() => Task.Run(() => (bool)RedisResult.Create(returns()))); mockDatabase.Setup(d => d.IsConnected(It.IsAny(), It.IsAny())) .Returns(true); } From 58388f0f6279f1ef4113b4370ff941c11eeaa567 Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Wed, 5 Jun 2024 08:12:23 +0300 Subject: [PATCH 23/27] clean redis --- .../Redis/RedisSynchronizationCoreTestCases.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs b/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs index 99049d4a..6a2b072e 100644 --- a/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs +++ b/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs @@ -70,7 +70,8 @@ public void TestMajorityFaultingDatabasesCauseReleaseToThrow() new List { 1, 2, 4 }.ForEach(i => MockDatabase(databases[i], () => throw new DataMisalignedException())); var aggregateException = Assert.Throws(() => handle.Dispose())!; - Assert.That(aggregateException.InnerException, Is.InstanceOf()); + Assert.That(aggregateException.InnerExceptions, Has.Count.EqualTo(3)); + Assert.That(aggregateException.InnerExceptions, Is.All.InstanceOf()); } [Test] From a5dcd71cea17c9101ecad290732fe2802c42f169 Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Wed, 5 Jun 2024 08:50:21 +0300 Subject: [PATCH 24/27] Update RedisSynchronizationCoreTestCases.cs --- .../AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs b/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs index 6a2b072e..9399f4f5 100644 --- a/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs +++ b/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs @@ -23,6 +23,7 @@ public async Task SetUp() public async Task TearDown() => await this._provider.DisposeAsync(); [Test] + [NonParallelizable, Retry(tryCount: 3)] // unstable in CI public void TestMajorityFaultingDatabasesCauseAcquireToThrow() { var databases = Enumerable.Range(0, 3).Select(_ => CreateDatabaseMock()).ToArray(); From 9e4a4ca6d6b017b12388a311f361b995fab4653e Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Wed, 5 Jun 2024 08:50:38 +0300 Subject: [PATCH 25/27] Update RedisSynchronizationCoreTestCases.cs --- .../Redis/RedisSynchronizationCoreTestCases.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs b/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs index 9399f4f5..6379d651 100644 --- a/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs +++ b/src/DistributedLock.Tests/AbstractTestCases/Redis/RedisSynchronizationCoreTestCases.cs @@ -23,7 +23,7 @@ public async Task SetUp() public async Task TearDown() => await this._provider.DisposeAsync(); [Test] - [NonParallelizable, Retry(tryCount: 3)] // unstable in CI + [Retry(tryCount: 3)] // unstable in CI public void TestMajorityFaultingDatabasesCauseAcquireToThrow() { var databases = Enumerable.Range(0, 3).Select(_ => CreateDatabaseMock()).ToArray(); From bb42e9065e2fa9299781919a57415d38adfa744a Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Wed, 5 Jun 2024 22:48:46 +0300 Subject: [PATCH 26/27] add mysql & mariadb --- .github/workflows/ci.yaml | 2 +- src/Directory.Packages.props | 2 ++ .../DistributedLock.Tests.csproj | 2 ++ .../Infrastructure/MySql/TestingMySqlDb.cs | 5 ++- .../Infrastructure/Shared/MySqlCredentials.cs | 36 ------------------- .../Tests/MySql/MySqlSetUpFixture.cs | 28 +++++++++++++++ src/DistributedLock.Tests/packages.lock.json | 18 ++++++++++ src/DistributedLockTaker/Program.cs | 4 +-- 8 files changed, 55 insertions(+), 42 deletions(-) delete mode 100644 src/DistributedLock.Tests/Infrastructure/Shared/MySqlCredentials.cs create mode 100644 src/DistributedLock.Tests/Tests/MySql/MySqlSetUpFixture.cs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 22dff7f6..fa3cebce 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -6,7 +6,7 @@ jobs: build: strategy: matrix: - category: [Redis, Postgres, SqlServer] + category: [Redis, Postgres, SqlServer, MySql] runs-on: ubuntu-latest steps: diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 523a07f6..4a7ed574 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -22,7 +22,9 @@ + + diff --git a/src/DistributedLock.Tests/DistributedLock.Tests.csproj b/src/DistributedLock.Tests/DistributedLock.Tests.csproj index 5369119c..1115a383 100644 --- a/src/DistributedLock.Tests/DistributedLock.Tests.csproj +++ b/src/DistributedLock.Tests/DistributedLock.Tests.csproj @@ -27,7 +27,9 @@ + + diff --git a/src/DistributedLock.Tests/Infrastructure/MySql/TestingMySqlDb.cs b/src/DistributedLock.Tests/Infrastructure/MySql/TestingMySqlDb.cs index 827e2937..477c19bc 100644 --- a/src/DistributedLock.Tests/Infrastructure/MySql/TestingMySqlDb.cs +++ b/src/DistributedLock.Tests/Infrastructure/MySql/TestingMySqlDb.cs @@ -1,6 +1,5 @@ using Medallion.Threading.Tests.Data; using MySqlConnector; -using NUnit.Framework; using System.Data; using System.Data.Common; @@ -12,7 +11,7 @@ public class TestingMySqlDb : TestingPrimaryClientDb private readonly MySqlConnectionStringBuilder _connectionStringBuilder; public TestingMySqlDb() - : this(MySqlCredentials.GetConnectionString(TestContext.CurrentContext.TestDirectory)) + : this(MySqlSetUpFixture.MySql.GetConnectionString()) { } @@ -88,7 +87,7 @@ JOIN information_schema.processlist p public sealed class TestingMariaDbDb : TestingMySqlDb { - public TestingMariaDbDb() : base(MariaDbCredentials.GetConnectionString(TestContext.CurrentContext.TestDirectory)) { } + public TestingMariaDbDb() : base(MySqlSetUpFixture.MariaDb.GetConnectionString()) { } protected override string IsolationLevelVariableName => "tx_isolation"; } diff --git a/src/DistributedLock.Tests/Infrastructure/Shared/MySqlCredentials.cs b/src/DistributedLock.Tests/Infrastructure/Shared/MySqlCredentials.cs deleted file mode 100644 index d2b9aa0d..00000000 --- a/src/DistributedLock.Tests/Infrastructure/Shared/MySqlCredentials.cs +++ /dev/null @@ -1,36 +0,0 @@ -using MySqlConnector; -using System; -using System.IO; - -namespace Medallion.Threading.Tests; - -internal static class MySqlCredentials -{ - private static (string username, string password) GetCredentials(string baseDirectory) - { - var file = Path.GetFullPath(Path.Combine(baseDirectory, "..", "..", "..", "credentials", "mysql.txt")); - if (!File.Exists(file)) { throw new InvalidOperationException($"Unable to find mysql credentials file {file}"); } - var lines = File.ReadAllLines(file); - if (lines.Length != 2) { throw new FormatException($"{file} must contain exactly 2 lines of text"); } - return (lines[0], lines[1]); - } - - public static string GetConnectionString(string baseDirectory) - { - var (username, password) = GetCredentials(baseDirectory); - - return new MySqlConnectionStringBuilder - { - Port = 3307, - Server = "localhost", - Database = "mysql", - UserID = username, - Password = password, - PersistSecurityInfo = true, - // set a high pool size so that we don't empty the pool through things like lock abandonment tests - MaximumPoolSize = 500, - // workaround for https://github.com/mysql-net/MySqlConnector/issues/1448 - TlsVersion = "Tls12" - }.ConnectionString; - } -} diff --git a/src/DistributedLock.Tests/Tests/MySql/MySqlSetUpFixture.cs b/src/DistributedLock.Tests/Tests/MySql/MySqlSetUpFixture.cs new file mode 100644 index 00000000..5816ac92 --- /dev/null +++ b/src/DistributedLock.Tests/Tests/MySql/MySqlSetUpFixture.cs @@ -0,0 +1,28 @@ +using NUnit.Framework; +using Testcontainers.MariaDb; +using Testcontainers.MySql; + +namespace Medallion.Threading.Tests.MySql; + +[SetUpFixture] +public class MySqlSetUpFixture +{ + public static MySqlContainer MySql; + public static MariaDbContainer MariaDb; + + [OneTimeSetUp] + public static async Task OneTimeSetUp() + { + MySql = new MySqlBuilder().Build(); + MariaDb = new MariaDbBuilder().Build(); + + await Task.WhenAll(MySql.StartAsync(), MariaDb.StartAsync()); + } + + [OneTimeTearDown] + public static async Task OneTimeTearDown() + { + await MySql.DisposeAsync(); + await MariaDb.DisposeAsync(); + } +} \ No newline at end of file diff --git a/src/DistributedLock.Tests/packages.lock.json b/src/DistributedLock.Tests/packages.lock.json index 00cb9b01..ad24aaff 100644 --- a/src/DistributedLock.Tests/packages.lock.json +++ b/src/DistributedLock.Tests/packages.lock.json @@ -59,6 +59,15 @@ "runtime.native.System.Data.SqlClient.sni": "4.7.0" } }, + "Testcontainers.MariaDb": { + "type": "Direct", + "requested": "[3.8.0, )", + "resolved": "3.8.0", + "contentHash": "RBmpBaN1kzOxcFfUVxKcWc5Z2IkBoPNKQglRHScT5dTT05YtjEqL8b0T9GBBfSa0WNLXkgAGiN2mkqijwsMz/g==", + "dependencies": { + "Testcontainers": "3.8.0" + } + }, "Testcontainers.MsSql": { "type": "Direct", "requested": "[3.8.0, )", @@ -68,6 +77,15 @@ "Testcontainers": "3.8.0" } }, + "Testcontainers.MySql": { + "type": "Direct", + "requested": "[3.8.0, )", + "resolved": "3.8.0", + "contentHash": "q72RjwMoivYhGjTlf0Id48IwXfR1Hx/NqwKN+b3WVoseA5tHxMnZV3tjb6vEyEwzwKAaUeWDEauyvqqptaDWSw==", + "dependencies": { + "Testcontainers": "3.8.0" + } + }, "Testcontainers.PostgreSql": { "type": "Direct", "requested": "[3.8.0, )", diff --git a/src/DistributedLockTaker/Program.cs b/src/DistributedLockTaker/Program.cs index f158a6c0..f34bf765 100644 --- a/src/DistributedLockTaker/Program.cs +++ b/src/DistributedLockTaker/Program.cs @@ -48,10 +48,10 @@ public static int Main(string[] args) handle = new PostgresDistributedReaderWriterLock(new PostgresAdvisoryLockKey(name), connectionString).AcquireWriteLock(); break; case nameof(MySqlDistributedLock): - handle = new MySqlDistributedLock(name, MySqlCredentials.GetConnectionString(Environment.CurrentDirectory)).Acquire(); + handle = new MySqlDistributedLock(name, connectionString).Acquire(); break; case "MariaDB" + nameof(MySqlDistributedLock): - handle = new MySqlDistributedLock(name, MariaDbCredentials.GetConnectionString(Environment.CurrentDirectory)).Acquire(); + handle = new MySqlDistributedLock(name, connectionString).Acquire(); break; case nameof(OracleDistributedLock): handle = new OracleDistributedLock(name, OracleCredentials.GetConnectionString(Environment.CurrentDirectory)).Acquire(); From b49c9887bc5ed088d64c8686d2a8c27ba6456bab Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Fri, 9 Aug 2024 12:29:06 +0300 Subject: [PATCH 27/27] fix compilation --- .../Infrastructure/Postgres/TestingPostgresDb.cs | 4 ++-- .../Tests/Postgres/PostgresDistributedLockTest.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/DistributedLock.Tests/Infrastructure/Postgres/TestingPostgresDb.cs b/src/DistributedLock.Tests/Infrastructure/Postgres/TestingPostgresDb.cs index b8e13177..fc007e43 100644 --- a/src/DistributedLock.Tests/Infrastructure/Postgres/TestingPostgresDb.cs +++ b/src/DistributedLock.Tests/Infrastructure/Postgres/TestingPostgresDb.cs @@ -12,9 +12,9 @@ public sealed class TestingPostgresDb : TestingPrimaryClientDb { private readonly PostgreSqlContainer _container = new PostgreSqlBuilder().Build(); - private NpgsqlConnectionStringBuilder _connectionStringBuilder; + private NpgsqlConnectionStringBuilder? _connectionStringBuilder; - public override DbConnectionStringBuilder ConnectionStringBuilder => this._connectionStringBuilder; + public override DbConnectionStringBuilder ConnectionStringBuilder => this._connectionStringBuilder!; // https://til.hashrocket.com/posts/8f87c65a0a-postgresqls-max-identifier-length-is-63-bytes public override int MaxApplicationNameLength => 63; diff --git a/src/DistributedLock.Tests/Tests/Postgres/PostgresDistributedLockTest.cs b/src/DistributedLock.Tests/Tests/Postgres/PostgresDistributedLockTest.cs index f81fb514..f1bc81d7 100644 --- a/src/DistributedLock.Tests/Tests/Postgres/PostgresDistributedLockTest.cs +++ b/src/DistributedLock.Tests/Tests/Postgres/PostgresDistributedLockTest.cs @@ -24,7 +24,7 @@ public void TestValidatesConstructorArguments() [Test] public void TestMultiplexingWithDbDataSourceThrowNotSupportedException() { - using var dataSource = new NpgsqlDataSourceBuilder(TestingPostgresDb.DefaultConnectionString).Build(); + using var dataSource = new NpgsqlDataSourceBuilder(PostgresSetUpFixture.PostgreSql.GetConnectionString()).Build(); Assert.Throws(() => new PostgresDistributedLock(new(0), dataSource, opt => opt.UseMultiplexing())); } @@ -33,7 +33,7 @@ public void TestMultiplexingWithDbDataSourceThrowNotSupportedException() [Test] public async Task TestDbDataSourceConstructorWorks() { - using var dataSource = new NpgsqlDataSourceBuilder(TestingPostgresDb.DefaultConnectionString).Build(); + using var dataSource = new NpgsqlDataSourceBuilder(PostgresSetUpFixture.PostgreSql.GetConnectionString()).Build(); PostgresDistributedLock @lock = new(new(5, 5), dataSource); await using (await @lock.AcquireAsync()) {