diff --git a/.github/workflows/csharp.yml b/.github/workflows/csharp.yml index eeae290b73..e7916a82e8 100644 --- a/.github/workflows/csharp.yml +++ b/.github/workflows/csharp.yml @@ -71,14 +71,14 @@ jobs: working-directory: ./csharp run: dotnet format --verify-no-changes --verbosity diagnostic - - uses: ./.github/workflows/test-benchmark - with: - language-flag: -csharp - - name: Test dotnet ${{ matrix.dotnet }} working-directory: ./csharp run: dotnet test --framework net${{ matrix.dotnet }} "-l:html;LogFileName=TestReport.html" --results-directory . -warnaserror + - uses: ./.github/workflows/test-benchmark + with: + language-flag: -csharp + - name: Upload test reports if: always() continue-on-error: true @@ -88,6 +88,7 @@ jobs: path: | csharp/TestReport.html benchmarks/results/* + utils/clusters/** lint-rust: timeout-minutes: 10 diff --git a/csharp/tests/AsyncClientTests.cs b/csharp/tests/Integration/GetAndSet.cs similarity index 80% rename from csharp/tests/AsyncClientTests.cs rename to csharp/tests/Integration/GetAndSet.cs index e9adfdf97b..ed37512337 100644 --- a/csharp/tests/AsyncClientTests.cs +++ b/csharp/tests/Integration/GetAndSet.cs @@ -2,19 +2,14 @@ * Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ -namespace tests; +namespace tests.Integration; using Glide; -// TODO - need to start a new redis server for each test? -public class AsyncClientTests -{ - [OneTimeSetUp] - public void Setup() - { - Glide.Logger.SetLoggerConfig(Glide.Level.Info); - } +using static tests.Integration.IntegrationTestBase; +public class GetAndSet +{ private async Task GetAndSetRandomValues(AsyncClient client) { var key = Guid.NewGuid().ToString(); @@ -27,7 +22,7 @@ private async Task GetAndSetRandomValues(AsyncClient client) [Test] public async Task GetReturnsLastSet() { - using (var client = new AsyncClient("localhost", 6379, false)) + using (var client = new AsyncClient("localhost", TestConfiguration.STANDALONE_PORTS[0], false)) { await GetAndSetRandomValues(client); } @@ -36,7 +31,7 @@ public async Task GetReturnsLastSet() [Test] public async Task GetAndSetCanHandleNonASCIIUnicode() { - using (var client = new AsyncClient("localhost", 6379, false)) + using (var client = new AsyncClient("localhost", TestConfiguration.STANDALONE_PORTS[0], false)) { var key = Guid.NewGuid().ToString(); var value = "שלום hello 汉字"; @@ -49,7 +44,7 @@ public async Task GetAndSetCanHandleNonASCIIUnicode() [Test] public async Task GetReturnsNull() { - using (var client = new AsyncClient("localhost", 6379, false)) + using (var client = new AsyncClient("localhost", TestConfiguration.STANDALONE_PORTS[0], false)) { var result = await client.GetAsync(Guid.NewGuid().ToString()); Assert.That(result, Is.EqualTo(null)); @@ -59,7 +54,7 @@ public async Task GetReturnsNull() [Test] public async Task GetReturnsEmptyString() { - using (var client = new AsyncClient("localhost", 6379, false)) + using (var client = new AsyncClient("localhost", TestConfiguration.STANDALONE_PORTS[0], false)) { var key = Guid.NewGuid().ToString(); var value = ""; @@ -72,7 +67,7 @@ public async Task GetReturnsEmptyString() [Test] public async Task HandleVeryLargeInput() { - using (var client = new AsyncClient("localhost", 6379, false)) + using (var client = new AsyncClient("localhost", TestConfiguration.STANDALONE_PORTS[0], false)) { var key = Guid.NewGuid().ToString(); var value = Guid.NewGuid().ToString(); @@ -92,7 +87,7 @@ public async Task HandleVeryLargeInput() [Test] public void ConcurrentOperationsWork() { - using (var client = new AsyncClient("localhost", 6379, false)) + using (var client = new AsyncClient("localhost", TestConfiguration.STANDALONE_PORTS[0], false)) { var operations = new List(); diff --git a/csharp/tests/Integration/IntegrationTestBase.cs b/csharp/tests/Integration/IntegrationTestBase.cs new file mode 100644 index 0000000000..635e0544cf --- /dev/null +++ b/csharp/tests/Integration/IntegrationTestBase.cs @@ -0,0 +1,134 @@ +/** + * Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 + */ + +using System.Diagnostics; + +// Note: All IT should be in the same namespace +namespace tests.Integration; + +[SetUpFixture] +public class IntegrationTestBase +{ + internal class TestConfiguration + { + public static List STANDALONE_PORTS { get; internal set; } = new(); + public static List CLUSTER_PORTS { get; internal set; } = new(); + public static Version REDIS_VERSION { get; internal set; } = new(); + } + + [OneTimeSetUp] + public void SetUp() + { + // Stop all if weren't stopped on previous test run + StopRedis(false); + + // Delete dirs if stop failed due to https://github.com/aws/glide-for-redis/issues/849 + Directory.Delete(Path.Combine(_scriptDir, "clusters"), true); + + // Start cluster + TestConfiguration.CLUSTER_PORTS = StartRedis(true); + // Start standalone + TestConfiguration.STANDALONE_PORTS = StartRedis(false); + // Get redis version + TestConfiguration.REDIS_VERSION = GetRedisVersion(); + + TestContext.Progress.WriteLine($"Cluster ports = {string.Join(',', TestConfiguration.CLUSTER_PORTS)}"); + TestContext.Progress.WriteLine($"Standalone ports = {string.Join(',', TestConfiguration.STANDALONE_PORTS)}"); + TestContext.Progress.WriteLine($"Redis version = {TestConfiguration.REDIS_VERSION}"); + } + + [OneTimeTearDown] + public void TearDown() + { + // Stop all + StopRedis(true); + } + + private readonly string _scriptDir; + + // Nunit requires a public default constructor. These variables would be set in SetUp method. + public IntegrationTestBase() + { + string? projectDir = Directory.GetCurrentDirectory(); + while (!(Path.GetFileName(projectDir) == "csharp" || projectDir == null)) + projectDir = Path.GetDirectoryName(projectDir); + + if (projectDir == null) + throw new FileNotFoundException("Can't detect the project dir. Are you running tests from `csharp` directory?"); + + _scriptDir = Path.Combine(projectDir, "..", "utils"); + } + + internal List StartRedis(bool cluster, bool tls = false, string? name = null) + { + string cmd = $"start {(cluster ? "--cluster-mode" : "-r 0")} {(tls ? " --tls" : "")} {(name != null ? " --prefix " + name : "")}"; + return ParsePortsFromOutput(RunClusterManager(cmd, false)); + } + + /// + /// Stop all instances on the given . + /// + internal void StopRedis(bool keepLogs, string? name = null) + { + string cmd = $"stop --prefix {name ?? "redis-cluster"} {(keepLogs ? "--keep-folder" : "")}"; + RunClusterManager(cmd, true); + } + + private string RunClusterManager(string cmd, bool ignoreExitCode) + { + ProcessStartInfo info = new() + { + WorkingDirectory = _scriptDir, + FileName = "python3", + Arguments = "cluster_manager.py " + cmd, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + }; + Process? script = Process.Start(info); + script?.WaitForExit(); + string? error = script?.StandardError.ReadToEnd(); + string? output = script?.StandardOutput.ReadToEnd(); + int? exit_code = script?.ExitCode; + + TestContext.Progress.WriteLine($"cluster_manager.py stdout\n====\n{output}\n====\ncluster_manager.py stderr\n====\n{error}\n====\n"); + + if (!ignoreExitCode && exit_code != 0) + throw new ApplicationException($"cluster_manager.py script failed: exit code {exit_code}."); + + return output ?? ""; + } + + private static List ParsePortsFromOutput(string output) + { + List ports = new(); + foreach (string line in output.Split("\n")) + { + if (!line.StartsWith("CLUSTER_NODES=")) + continue; + + string[] addresses = line.Split("=")[1].Split(","); + foreach (string address in addresses) + ports.Add(uint.Parse(address.Split(":")[1])); + } + return ports; + } + + private static Version GetRedisVersion() + { + ProcessStartInfo info = new() + { + FileName = "redis-server", + Arguments = "-v", + UseShellExecute = false, + RedirectStandardOutput = true, + }; + Process? proc = Process.Start(info); + proc?.WaitForExit(); + string output = proc?.StandardOutput.ReadToEnd() ?? ""; + + // Redis server v=7.2.3 sha=00000000:0 malloc=jemalloc-5.3.0 bits=64 build=7504b1fedf883f2 + return new Version(output.Split(" ")[2].Split("=")[1]); + } +}