From 7348be1ba79c56aa92ed7d35b39bb8e95962c5e1 Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Wed, 31 Jan 2024 09:47:14 -0800 Subject: [PATCH] Java: Add IT framework (#863) * Add IT framework (#63) Signed-off-by: Yury-Fridlyand --- .github/workflows/java.yml | 9 +- java/client/build.gradle | 3 +- java/integTest/build.gradle | 107 ++++++++++++++++++ .../src/test/java/glide/CommandTests.java | 42 +++++++ .../src/test/java/glide/ConnectionTests.java | 26 +++++ .../test/java/glide/TestConfiguration.java | 15 +++ .../test/resources/junit-platform.properties | 2 + utils/cluster_manager.py | 4 +- 8 files changed, 199 insertions(+), 9 deletions(-) create mode 100644 java/integTest/src/test/java/glide/CommandTests.java create mode 100644 java/integTest/src/test/java/glide/ConnectionTests.java create mode 100644 java/integTest/src/test/java/glide/TestConfiguration.java create mode 100644 java/integTest/src/test/resources/junit-platform.properties diff --git a/.github/workflows/java.yml b/.github/workflows/java.yml index a4cf018e7a..e1f23f92d0 100644 --- a/.github/workflows/java.yml +++ b/.github/workflows/java.yml @@ -64,11 +64,7 @@ jobs: with: redis-version: ${{ matrix.redis }} - - name: Build rust part - working-directory: java - run: cargo build --release - - - name: Build java part + - name: Build java client working-directory: java run: ./gradlew --continue build @@ -77,10 +73,11 @@ jobs: continue-on-error: true uses: actions/upload-artifact@v4 with: - name: test-reports-${{ matrix.java }} + name: test-reports-java-${{ matrix.java }}-redis-${{ matrix.redis }}-${{ matrix.os }} path: | java/client/build/reports/** java/integTest/build/reports/** + utils/clusters/** build-amazonlinux-latest: if: github.repository_owner == 'aws' diff --git a/java/client/build.gradle b/java/client/build.gradle index 6118a27984..07962a8b02 100644 --- a/java/client/build.gradle +++ b/java/client/build.gradle @@ -110,7 +110,7 @@ tasks.register('buildAll') { finalizedBy 'build' } -compileJava.dependsOn('protobuf', 'buildRustRelease') +compileJava.dependsOn('protobuf') clean.dependsOn('cleanProtobuf', 'cleanRust') test.dependsOn('buildRust') testFfi.dependsOn('buildRust') @@ -127,4 +127,3 @@ tasks.withType(Test) { } jvmArgs "-Djava.library.path=${projectDir}/../target/debug" } - diff --git a/java/integTest/build.gradle b/java/integTest/build.gradle index e69de29bb2..f5d9451698 100644 --- a/java/integTest/build.gradle +++ b/java/integTest/build.gradle @@ -0,0 +1,107 @@ +plugins { + id 'java-library' +} + +repositories { + mavenCentral() +} + +dependencies { + // client + implementation project(':client') + + // lombok + testCompileOnly 'org.projectlombok:lombok:1.18.30' + testAnnotationProcessor 'org.projectlombok:lombok:1.18.30' + + // junit + testImplementation 'org.junit.jupiter:junit-jupiter:5.6.2' + testImplementation 'org.mockito:mockito-junit-jupiter:3.12.4' +} + +def standaloneRedisPorts = [] +def clusterRedisPorts = [] + +ext { + extractPortsFromClusterManagerOutput = { String output, ArrayList dest -> + for (def line : output.split("\n")) { + if (!line.startsWith("CLUSTER_NODES=")) + continue + + def addresses = line.split("=")[1].split(",") + for (def address : addresses) + dest << address.split(":")[1] + } + } +} + +tasks.register('stopAllAfterTests', Exec) { + workingDir "${project.rootDir}/../utils" + commandLine 'python3', 'cluster_manager.py', 'stop', '--prefix', 'redis-cluster', '--keep-folder' +} + +// We need to call for stop before and after the test, but gradle doesn't support executing a task +// twice. So there are two identical tasks with different names. +// We need to call for stop in case if previous test run was interrupted/crashed and didn't stop. +tasks.register('stopAllBeforeTests', Exec) { + workingDir "${project.rootDir}/../utils" + commandLine 'python3', 'cluster_manager.py', 'stop', '--prefix', 'redis-cluster' + ignoreExitValue true // ignore fail if servers are stopped before +} + +// delete dirs if stop failed due to https://github.com/aws/glide-for-redis/issues/849 +tasks.register('clearDirs', Delete) { + delete "${project.rootDir}/../utils/clusters" +} + +tasks.register('startCluster') { + doLast { + new ByteArrayOutputStream().withStream { os -> + exec { + workingDir "${project.rootDir}/../utils" + commandLine 'python3', 'cluster_manager.py', 'start', '--cluster-mode' + standardOutput = os + } + extractPortsFromClusterManagerOutput(os.toString(), clusterRedisPorts) + } + } +} + +tasks.register('startStandalone') { + doLast { + new ByteArrayOutputStream().withStream { os -> + exec { + workingDir "${project.rootDir}/../utils" + commandLine 'python3', 'cluster_manager.py', 'start', '-r', '0' + standardOutput = os + } + extractPortsFromClusterManagerOutput(os.toString(), standaloneRedisPorts) + } + } +} + +test.dependsOn 'stopAllBeforeTests' +stopAllBeforeTests.finalizedBy 'clearDirs' +clearDirs.finalizedBy 'startStandalone' +clearDirs.finalizedBy 'startCluster' +test.finalizedBy 'stopAllAfterTests' +test.dependsOn ':client:buildRustRelease' + +tasks.withType(Test) { + doFirst { + println "Cluster ports = ${clusterRedisPorts}" + println "Standalone ports = ${standaloneRedisPorts}" + systemProperty 'test.redis.standalone.ports', standaloneRedisPorts.join(',') + systemProperty 'test.redis.cluster.ports', clusterRedisPorts.join(',') + } + + testLogging { + exceptionFormat "full" + events "started", "skipped", "passed", "failed" + showStandardStreams true + } + jvmArgs "-Djava.library.path=${project.rootDir}/target/release" + afterTest { desc, result -> + logger.quiet "${desc.className}.${desc.name}: ${result.resultType} ${(result.getEndTime() - result.getStartTime())/1000}s" + } +} diff --git a/java/integTest/src/test/java/glide/CommandTests.java b/java/integTest/src/test/java/glide/CommandTests.java new file mode 100644 index 0000000000..74d872cbd9 --- /dev/null +++ b/java/integTest/src/test/java/glide/CommandTests.java @@ -0,0 +1,42 @@ +package glide; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import glide.api.RedisClient; +import glide.api.models.configuration.NodeAddress; +import glide.api.models.configuration.RedisClientConfiguration; +import java.util.concurrent.TimeUnit; +import lombok.SneakyThrows; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class CommandTests { + + private static RedisClient regularClient = null; + + @BeforeAll + @SneakyThrows + public static void init() { + regularClient = + RedisClient.CreateClient( + RedisClientConfiguration.builder() + .address( + NodeAddress.builder().port(TestConfiguration.STANDALONE_PORTS[0]).build()) + .build()) + .get(10, TimeUnit.SECONDS); + } + + @AfterAll + @SneakyThrows + public static void deinit() { + regularClient.close(); + } + + @Test + @SneakyThrows + public void custom_command_info() { + var data = regularClient.customCommand(new String[] {"info"}).get(10, TimeUnit.SECONDS); + assertTrue(((String) data).contains("# Stats")); + } +} diff --git a/java/integTest/src/test/java/glide/ConnectionTests.java b/java/integTest/src/test/java/glide/ConnectionTests.java new file mode 100644 index 0000000000..c04c5e0666 --- /dev/null +++ b/java/integTest/src/test/java/glide/ConnectionTests.java @@ -0,0 +1,26 @@ +package glide; + +import glide.api.RedisClient; +import glide.api.models.configuration.NodeAddress; +import glide.api.models.configuration.RedisClientConfiguration; +import java.util.concurrent.TimeUnit; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; + +public class ConnectionTests { + + @Test + @SneakyThrows + public void basic_client() { + var regularClient = + RedisClient.CreateClient( + RedisClientConfiguration.builder() + .address( + NodeAddress.builder().port(TestConfiguration.STANDALONE_PORTS[0]).build()) + .build()) + .get(10, TimeUnit.SECONDS); + regularClient.close(); + } + + // TODO cluster client once implemented +} diff --git a/java/integTest/src/test/java/glide/TestConfiguration.java b/java/integTest/src/test/java/glide/TestConfiguration.java new file mode 100644 index 0000000000..385458cc48 --- /dev/null +++ b/java/integTest/src/test/java/glide/TestConfiguration.java @@ -0,0 +1,15 @@ +package glide; + +import java.util.Arrays; + +public final class TestConfiguration { + // All redis servers are hosted on localhost + public static final int[] STANDALONE_PORTS = getPortsFromProperty("test.redis.standalone.ports"); + public static final int[] CLUSTER_PORTS = getPortsFromProperty("test.redis.cluster.ports"); + + private static int[] getPortsFromProperty(String propName) { + return Arrays.stream(System.getProperty(propName).split(",")) + .mapToInt(Integer::parseInt) + .toArray(); + } +} diff --git a/java/integTest/src/test/resources/junit-platform.properties b/java/integTest/src/test/resources/junit-platform.properties new file mode 100644 index 0000000000..3249fb3c49 --- /dev/null +++ b/java/integTest/src/test/resources/junit-platform.properties @@ -0,0 +1,2 @@ +junit.jupiter.displayname.generator.default = \ + org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores diff --git a/utils/cluster_manager.py b/utils/cluster_manager.py index ca8d2b4c1e..650d7f9095 100644 --- a/utils/cluster_manager.py +++ b/utils/cluster_manager.py @@ -1,3 +1,5 @@ +#!/usr/bin/python3 + # Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 import argparse @@ -260,7 +262,7 @@ def create_cluster_folder(path: str, prefix: str) -> str: str: The full path of the cluster folder """ time = datetime.now(timezone.utc) - time_str = time.strftime("%Y-%m-%dT%H:%M:%SZ") + time_str = time.strftime("%Y-%m-%dT%H-%M-%SZ") cluster_folder = f"{path}/{prefix}-{time_str}-{get_random_string(6)}" logging.debug(f"## Creating cluster folder in {cluster_folder}") Path(cluster_folder).mkdir(exist_ok=True)