From b382a3c975fd25b44768b90577d57eee19da5f62 Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Mon, 4 Mar 2024 11:47:05 -0800 Subject: [PATCH] C#: Make IT to manage redis. (#116) * Make IT to run redis. Signed-off-by: Yury-Fridlyand --- .github/workflows/csharp.yml | 9 +- .../GetAndSet.cs} | 23 ++- .../tests/Integration/IntegrationTestBase.cs | 135 ++++++++++++++++++ csharp/tests/Unit/Placeholder.cs | 9 ++ 4 files changed, 157 insertions(+), 19 deletions(-) rename csharp/tests/{AsyncClientTests.cs => Integration/GetAndSet.cs} (81%) create mode 100644 csharp/tests/Integration/IntegrationTestBase.cs create mode 100644 csharp/tests/Unit/Placeholder.cs diff --git a/.github/workflows/csharp.yml b/.github/workflows/csharp.yml index eeae290b73..e47b427800 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 + - name: Test dotnet ${{ matrix.dotnet }} + working-directory: ./csharp + run: dotnet test --framework net${{ matrix.dotnet }} "-l:html;LogFileName=TestReport.html" --results-directory . -warnaserror -- NUnit.WorkDirectory=$PWD + - 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 - - 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 81% rename from csharp/tests/AsyncClientTests.cs rename to csharp/tests/Integration/GetAndSet.cs index e9adfdf97b..5f7cc70d28 100644 --- a/csharp/tests/AsyncClientTests.cs +++ b/csharp/tests/Integration/GetAndSet.cs @@ -2,19 +2,12 @@ * 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 +public class GetAndSet { - [OneTimeSetUp] - public void Setup() - { - Glide.Logger.SetLoggerConfig(Glide.Level.Info); - } - private async Task GetAndSetRandomValues(AsyncClient client) { var key = Guid.NewGuid().ToString(); @@ -27,7 +20,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", IntegrationTestBase.STANDALONE_PORTS[0], false)) { await GetAndSetRandomValues(client); } @@ -36,7 +29,7 @@ public async Task GetReturnsLastSet() [Test] public async Task GetAndSetCanHandleNonASCIIUnicode() { - using (var client = new AsyncClient("localhost", 6379, false)) + using (var client = new AsyncClient("localhost", IntegrationTestBase.STANDALONE_PORTS[0], false)) { var key = Guid.NewGuid().ToString(); var value = "שלום hello 汉字"; @@ -49,7 +42,7 @@ public async Task GetAndSetCanHandleNonASCIIUnicode() [Test] public async Task GetReturnsNull() { - using (var client = new AsyncClient("localhost", 6379, false)) + using (var client = new AsyncClient("localhost", IntegrationTestBase.STANDALONE_PORTS[0], false)) { var result = await client.GetAsync(Guid.NewGuid().ToString()); Assert.That(result, Is.EqualTo(null)); @@ -59,7 +52,7 @@ public async Task GetReturnsNull() [Test] public async Task GetReturnsEmptyString() { - using (var client = new AsyncClient("localhost", 6379, false)) + using (var client = new AsyncClient("localhost", IntegrationTestBase.STANDALONE_PORTS[0], false)) { var key = Guid.NewGuid().ToString(); var value = ""; @@ -72,7 +65,7 @@ public async Task GetReturnsEmptyString() [Test] public async Task HandleVeryLargeInput() { - using (var client = new AsyncClient("localhost", 6379, false)) + using (var client = new AsyncClient("localhost", IntegrationTestBase.STANDALONE_PORTS[0], false)) { var key = Guid.NewGuid().ToString(); var value = Guid.NewGuid().ToString(); @@ -92,7 +85,7 @@ public async Task HandleVeryLargeInput() [Test] public void ConcurrentOperationsWork() { - using (var client = new AsyncClient("localhost", 6379, false)) + using (var client = new AsyncClient("localhost", IntegrationTestBase.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..6488c1d830 --- /dev/null +++ b/csharp/tests/Integration/IntegrationTestBase.cs @@ -0,0 +1,135 @@ +/** + * 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 +{ + [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 + CLUSTER_PORTS = StartRedis(true); + // Start standalone + STANDALONE_PORTS = StartRedis(false); + + // Get redis version + REDIS_VERSION = GetRedisVersion(); + + TestContext.Progress.WriteLine($"Cluster ports = {string.Join(',', CLUSTER_PORTS)}"); + TestContext.Progress.WriteLine($"Standalone ports = {string.Join(',', STANDALONE_PORTS)}"); + TestContext.Progress.WriteLine($"Redis version = {REDIS_VERSION}"); + } + + [OneTimeTearDown] + public void TearDown() + { + // Stop all + StopRedis(true); + } + + public static List STANDALONE_PORTS { get; private set; } = new(); + public static List CLUSTER_PORTS { get; private set; } = new(); + public static Version REDIS_VERSION { get; private set; } = new(); + + private readonly string scriptDir; + + // Nunit requires a public default constructor. These variables would be set in SetUp method. + public IntegrationTestBase() + { + STANDALONE_PORTS = new(); + CLUSTER_PORTS = new(); + REDIS_VERSION = new(); + + string path = TestContext.CurrentContext.WorkDirectory; + if (Path.GetFileName(path) != "csharp") + throw new FileNotFoundException("`WorkDirectory` is incorrect or not defined. Please ensure the WorkDirectory was set by passing `-- NUnit.WorkDirectory=` to the `dotnet test` command."); + + scriptDir = Path.Combine(path, "..", "utils"); + } + + + public List StartRedis(bool cluster, bool tls = false, string? name = null) + { + var cmd = $"start {(cluster ? "--cluster-mode" : "-r 0")} {(tls ? " --tls" : "")} {(name != null ? " --prefix " + name : "")}"; + return ParsePortsFromOutput(RunClusterManager(cmd, false)); + } + + /// + /// Stop all instances on the given . + /// + public void StopRedis(bool keepLogs, string? name = null) + { + var cmd = $"stop --prefix {name ?? "redis-cluster"} {(keepLogs ? "--keep-folder" : "")}"; + RunClusterManager(cmd, true); + } + + + private string RunClusterManager(string cmd, bool ignoreExitCode) + { + var info = new ProcessStartInfo + { + WorkingDirectory = scriptDir, + FileName = "python3", + Arguments = "cluster_manager.py " + cmd, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + }; + var script = Process.Start(info); + script?.WaitForExit(); + var error = script?.StandardError.ReadToEnd(); + var output = script?.StandardOutput.ReadToEnd(); + var 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 List ParsePortsFromOutput(string output) + { + var ports = new List(); + foreach (var line in output.Split("\n")) + { + if (!line.StartsWith("CLUSTER_NODES=")) + continue; + + var addresses = line.Split("=")[1].Split(","); + foreach (var address in addresses) + ports.Add(uint.Parse(address.Split(":")[1])); + } + return ports; + } + + private Version GetRedisVersion() + { + var info = new ProcessStartInfo + { + FileName = "redis-server", + Arguments = "-v", + UseShellExecute = false, + RedirectStandardOutput = true, + }; + var proc = Process.Start(info); + proc?.WaitForExit(); + var 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]); + } +} diff --git a/csharp/tests/Unit/Placeholder.cs b/csharp/tests/Unit/Placeholder.cs new file mode 100644 index 0000000000..773dfe1fc4 --- /dev/null +++ b/csharp/tests/Unit/Placeholder.cs @@ -0,0 +1,9 @@ +/** + * Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 + */ + +namespace tests.Unit; + +using Glide; + +// John Travolta is looking for tests