-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduces test matrix based on Redis versions [8.0-M1, 7.4.1, 7.2.6,…
… 6.2.16] Use docker composer to bring up the test env using `redislabs/client-libs-test` image. When run against older Redis version some tests are using commands available only in newer Redis server versions. To resolve this we are introducing two new annotations/rules - Introduce `SinceRedisVersion` annotation/Rule - for conditionally running tests based on Redis server version contacted - Introduce `EnableOnCommad` annotation/Rule - for conditionally running tests based on command availability on the server And mark respective tests with the least Redis Version required by the test - SinceRedisVersion ("7.4.0") - Mark tests using commands/modifiers introduced with Redis 7.4.0 - SinceRedisVersion ("7.2.0") - Mark tests using commands/modifiers introduced with Redis 7.2.0 - SinceRedisVersion ("7.0.0") - Mark tests using commands/modifiers introduced with Redis 7.0.0 The same approach used to mark CSC tests - Disabled client-side caching tests for versions below 7.4 Fix in Jedis Client against Redis server 6.x - Fix NPW on CommandInfo - some of the array elements returned are available based from given RedisServer aclCategories (as of Redis 6.0) , tips , (as of Redis 7.0) subcommands - Fix NPE AccessControlLogEntry when used against Redis 6 Starting with Redis version 7.2.0: Added entry ID, timestamp created, and timestamp last updated fields. Fix Test failures against 6.x - Fix JedisPooledClientSideCacheTest - Fix AccessControlListCommandsTest.aclLogTest:372 » NullPointer - Fix AccessControlListCommandsTest.aclLogWithEntryID:473 » NullPointer - Fix StreamsCommandsTest - Fix StreamsPipelineCommandsTest xadd - Starting with Redis version 7.0.0: Added support for the <ms>-* explicit ID form. - Test env migrated to use native Redis server TLS instead of using stunnel Not all tests were migrated - Disable Modules test in containerized test env ModuleTest uses custom test module to test load/unload/sendCommand. Requires pre-build test module on the same os like test container to avoid errors - Disable UDS tests in containerized test env No easy way to make unix sockets work on MAC with docker
- Loading branch information
Showing
127 changed files
with
2,523 additions
and
619 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
--- | ||
|
||
name: Build and Test using containerized environment | ||
|
||
on: | ||
push: | ||
paths-ignore: | ||
- 'docs/**' | ||
- '**/*.md' | ||
- '**/*.rst' | ||
branches: | ||
- master | ||
- '[0-9].*' | ||
pull_request: | ||
branches: | ||
- master | ||
- '[0-9].*' | ||
schedule: | ||
- cron: '0 1 * * *' # nightly build | ||
workflow_dispatch: | ||
inputs: | ||
specific_test: | ||
description: 'Run specific test(s) (optional)' | ||
required: false | ||
default: '' | ||
jobs: | ||
|
||
build: | ||
name: Build and Test | ||
runs-on: ubuntu-latest | ||
env: | ||
REDIS_ENV_WORK_DIR: ${{ github.workspace }}/redis-env-work | ||
REDIS_ENV_CONF_DIR: ${{ github.workspace }}/src/test/resources/env | ||
CLIENT_LIBS_IMAGE_PREFIX: "redislabs/client-libs-test" | ||
strategy: | ||
fail-fast: false | ||
matrix: | ||
redis_version: | ||
- "8.0-M01" | ||
- "7.4.1" | ||
- "7.2.6" | ||
- "6.2.16" | ||
steps: | ||
- uses: actions/checkout@v2 | ||
- name: Set up publishing to maven central | ||
uses: actions/setup-java@v2 | ||
with: | ||
java-version: '8' | ||
distribution: 'temurin' | ||
- name: System setup | ||
run: | | ||
sudo apt update | ||
sudo apt install -y make | ||
make compile-module | ||
- name: Cache dependencies | ||
uses: actions/cache@v2 | ||
with: | ||
path: | | ||
~/.m2/repository | ||
/var/cache/apt | ||
key: jedis-${{hashFiles('**/pom.xml')}} | ||
# Set up Docker Compose environment | ||
- name: Set up Docker Compose environment | ||
run: | | ||
mkdir -m 777 $REDIS_ENV_WORK_DIR | ||
export CLIENT_LIBS_TEST_IMAGE="${CLIENT_LIBS_IMAGE_PREFIX}:${{ matrix.redis_version }}" | ||
export COMPOSE_ENV_FILES="src/test/resources/env/.env" | ||
if [[ "${{ matrix.redis_version }}" == "6.2.16" ]]; then | ||
COMPOSE_ENV_FILES+=",src/test/resources/env/.env.v${{ matrix.redis_version }}" | ||
fi | ||
docker compose -f src/test/resources/env/docker-compose.yml up -d | ||
- name: Maven offline | ||
run: | | ||
mvn -q dependency:go-offline | ||
- name: Build docs | ||
run: | | ||
mvn javadoc:jar | ||
# Run Tests | ||
- name: Run Maven tests | ||
run: | | ||
export TEST_ENV_PROVIDER=docker | ||
export TEST_WORK_FOLDER=$REDIS_ENV_WORK_DIR | ||
echo $TEST_WORK_FOLDER | ||
if [ -z "$TESTS" ]; then | ||
mvn clean compile test | ||
else | ||
mvn -Dtest=$SPECIFIC_TEST clean compile test | ||
fi | ||
env: | ||
TESTS: ${{ github.event.inputs.specific_test || '' }} | ||
- name: Publish Test Results | ||
uses: EnricoMi/publish-unit-test-result-action@v2 | ||
if: always() | ||
with: | ||
files: | | ||
target/surefire-reports/**/*.xml | ||
# Collect logs on failure | ||
- name: Collect logs on failure | ||
if: failure() # This runs only if the previous steps failed | ||
run: | | ||
echo "Collecting logs from $WORK_DIR..." | ||
ls -la $REDIS_ENV_WORK_DIR | ||
# Upload logs as artifacts | ||
- name: Upload logs on failure | ||
if: failure() | ||
uses: actions/upload-artifact@v3 | ||
with: | ||
name: redis-env-work-logs | ||
path: ${{ env.REDIS_ENV_WORK_DIR }} | ||
# Bring down the Docker Compose test environment | ||
- name: Tear down Docker Compose environment | ||
if: always() | ||
run: | | ||
docker compose $COMPOSE_ENV_FILES -f src/test/resources/env/docker-compose.yml down | ||
continue-on-error: true | ||
# Upload code coverage | ||
- name: Upload coverage to Codecov | ||
uses: codecov/codecov-action@v4 | ||
with: | ||
fail_ci_if_error: false | ||
token: ${{ secrets.CODECOV_TOKEN }} | ||
- name: Upload test results to Codecov | ||
if: ${{ github.event_name == 'schedule' || (github.event_name == 'push') || github.event_name == 'workflow_dispatch'}} | ||
uses: codecov/test-results-action@v1 | ||
with: | ||
fail_ci_if_error: false | ||
files: ./target/surefire-reports/TEST* | ||
token: ${{ secrets.CODECOV_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,7 +11,9 @@ build/ | |
bin/ | ||
tags | ||
.idea | ||
.run | ||
*.aof | ||
*.rdb | ||
redis-git | ||
appendonlydir/ | ||
.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 11 additions & 0 deletions
11
src/test/java/io/redis/test/annotations/EnabledOnCommand.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package io.redis.test.annotations; | ||
|
||
import java.lang.annotation.*; | ||
|
||
@Inherited | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@Target({ElementType.METHOD, ElementType.TYPE}) | ||
public @interface EnabledOnCommand { | ||
String value(); | ||
String subCommand() default ""; | ||
} |
11 changes: 11 additions & 0 deletions
11
src/test/java/io/redis/test/annotations/SinceRedisVersion.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package io.redis.test.annotations; | ||
|
||
import java.lang.annotation.*; | ||
|
||
@Inherited | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@Target({ElementType.METHOD, ElementType.TYPE}) | ||
public @interface SinceRedisVersion { | ||
String value(); | ||
String message() default ""; | ||
} |
126 changes: 126 additions & 0 deletions
126
src/test/java/io/redis/test/utils/EnabledOnCommandRule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package io.redis.test.utils; | ||
|
||
import io.redis.test.annotations.EnabledOnCommand; | ||
import org.junit.Assume; | ||
import org.junit.rules.TestRule; | ||
import org.junit.runner.Description; | ||
import org.junit.runners.model.Statement; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import redis.clients.jedis.*; | ||
import redis.clients.jedis.resps.CommandInfo; | ||
|
||
import java.lang.reflect.Method; | ||
import java.util.Map; | ||
|
||
|
||
public class EnabledOnCommandRule implements TestRule { | ||
private static final Logger logger = LoggerFactory.getLogger(EnabledOnCommandRule.class); | ||
|
||
private final HostAndPort hostPort; | ||
private final JedisClientConfig config; | ||
|
||
public EnabledOnCommandRule(HostAndPort hostPort, JedisClientConfig config) { | ||
this.hostPort = hostPort; | ||
this.config = config; | ||
} | ||
|
||
public EnabledOnCommandRule(EndpointConfig endpointConfig) { | ||
this.hostPort = endpointConfig.getHostAndPort(); | ||
this.config = endpointConfig.getClientConfigBuilder().build(); | ||
} | ||
|
||
@Override | ||
public Statement apply(Statement base, Description description) { | ||
return new Statement() { | ||
@Override | ||
public void evaluate() throws Throwable { | ||
try (Jedis jedisClient = new Jedis(hostPort, config)) { | ||
String[] command = getCommandFromAnnotations(description); | ||
|
||
if (command != null && !isCommandAvailable(jedisClient, command[0],command[1])) { | ||
Assume.assumeTrue("Test requires Redis command '" + command[0] + " " + command[1] + "' to be available, but it was not found.", false); | ||
} | ||
|
||
base.evaluate(); | ||
} | ||
} | ||
|
||
/** | ||
* Retrieves the command from either class-level or method-level annotations. | ||
* | ||
* @param description The test description containing annotations. | ||
* @return The Redis array containing command, subcommand from the annotations, or null if not found. | ||
*/ | ||
private String[] getCommandFromAnnotations(Description description) { | ||
// Retrieve class-level and method-level annotations | ||
EnabledOnCommand descriptionCommandAnnotation = description.getAnnotation(EnabledOnCommand.class); | ||
if (descriptionCommandAnnotation != null) { | ||
return new String[] {descriptionCommandAnnotation.value(), descriptionCommandAnnotation.subCommand()}; | ||
} | ||
|
||
EnabledOnCommand methodCommand = getMethodAnnotation(description); | ||
if (methodCommand != null) { | ||
return new String[] {methodCommand.value(), methodCommand.subCommand()}; | ||
} | ||
|
||
EnabledOnCommand classCommand = description.getTestClass().getAnnotation(EnabledOnCommand.class); | ||
if (classCommand != null) { | ||
return new String[] {classCommand.value(), classCommand.subCommand()}; | ||
} | ||
|
||
return null; | ||
} | ||
|
||
private EnabledOnCommand getMethodAnnotation(Description description) { | ||
try { | ||
// description.getAnnotation() does not return anootaion when used | ||
// with parametrised tests | ||
String methodName = description.getMethodName(); | ||
if (methodName != null) { | ||
Class<?> testClass = description.getTestClass(); | ||
if (testClass != null) { | ||
for (Method method : testClass.getDeclaredMethods()) { | ||
if (method.getName().equals(methodName)) { | ||
return method.getAnnotation(EnabledOnCommand.class); | ||
} | ||
} | ||
} | ||
} | ||
} catch (Exception e) { | ||
// Handle any potential exceptions here | ||
throw new RuntimeException("Could not resolve EnabledOnCommand annotation",e); | ||
} | ||
return null; | ||
} | ||
|
||
/** | ||
* Checks if the specified Redis command is available. | ||
*/ | ||
private boolean isCommandAvailable(Jedis jedisClient, String command, String subCommand) { | ||
try { | ||
Object raw = jedisClient.sendCommand(redis.clients.jedis.Protocol.Command.COMMAND); | ||
Map<String, CommandInfo> commandList = BuilderFactory.COMMAND_INFO_RESPONSE.build(raw); | ||
CommandInfo commandInfo = commandList.get(command.toLowerCase()); | ||
if (commandInfo != null) { | ||
// If a subCommand is provided, check for the subcommand under this command | ||
if (subCommand != null && !subCommand.isEmpty()) { | ||
// Check if this command supports the provided subcommand | ||
for (String supportedSubCommand : commandInfo.getSubcommands()) { | ||
if (subCommand.equalsIgnoreCase(supportedSubCommand)) { | ||
return true; | ||
} | ||
} | ||
return false; // Subcommand not found | ||
} | ||
return true; // Command found (no subcommand required) | ||
} | ||
return false; // Command not found | ||
} catch (Exception e) { | ||
logger.error("Error checking command '{}' availability: {}", command, e.getMessage()); | ||
return false; | ||
} | ||
} | ||
}; | ||
} | ||
} |
Oops, something went wrong.