From d50c0673c6486f050cda798a04f823ba5ce13675 Mon Sep 17 00:00:00 2001 From: Roger Floriano <31597636+petruki@users.noreply.github.com> Date: Thu, 14 Aug 2025 22:02:11 -0700 Subject: [PATCH 1/2] Fixes #360 - updated reflect-config.json reachability metadata (#361) * Fixes #360 - updated reflect-config.json reachability metadata * test: increased shouldStartAndKillWorker delay * test: increased shouldStartAndKillWorker delay * test-ci: update maven cache * test-ci: add coverage phase for matrix * test-ci: updated matrix to use ubuntu-22.04 * test-ci: updated all matrices to use ubuntu-22.04 --- .github/workflows/master-2.yml | 4 +- .github/workflows/master.yml | 4 +- .github/workflows/sonar.yml | 2 +- .github/workflows/staging.yml | 2 +- pom.xml | 3 +- .../com/switcherapi/client/model/Entry.java | 16 +++--- .../client/model/SwitcherBuilder.java | 2 +- .../switcher-client/reflect-config.json | 49 ++++++++++++++-- .../client/SwitcherLocal1Test.java | 22 +++---- .../client/SwitcherLocal3Test.java | 2 +- .../switcherapi/client/model/ModelTest.java | 4 +- .../nativeimage/NativeReflectConfigTest.java | 57 ++++++++++++++++++- 12 files changed, 130 insertions(+), 37 deletions(-) diff --git a/.github/workflows/master-2.yml b/.github/workflows/master-2.yml index b94ab7f..1f64f7f 100644 --- a/.github/workflows/master-2.yml +++ b/.github/workflows/master-2.yml @@ -9,7 +9,7 @@ on: jobs: build-scan: name: SonarCloud Scan - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 @@ -35,7 +35,7 @@ jobs: fail-fast: false matrix: java: ['11', '17', '21'] - os: [ubuntu-latest, windows-latest] + os: [ubuntu-22.04, windows-latest] runs-on: ${{ matrix.os }} steps: diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index c8000c7..1e282a1 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -9,7 +9,7 @@ on: jobs: build-scan: name: SonarCloud Scan - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 @@ -35,7 +35,7 @@ jobs: fail-fast: false matrix: java: ['8', '11', '17', '21'] - os: [ubuntu-latest, windows-latest] + os: [ubuntu-22.04, windows-latest] runs-on: ${{ matrix.os }} steps: diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index db54d6e..2f67530 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -11,7 +11,7 @@ on: jobs: sonar-analysis: name: SonarCloud Analysis for PR - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Get PR details diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index 9e8bccf..a48d1fd 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -9,7 +9,7 @@ on: required: true default: '17' os: - description: 'Operating System (ubuntu-20.04, ubuntu-latest, windows-latest)' + description: 'Operating System (ubuntu-22.04, ubuntu-latest, windows-latest)' required: true default: 'ubuntu-latest' diff --git a/pom.xml b/pom.xml index 1b3a748..630b33a 100644 --- a/pom.xml +++ b/pom.xml @@ -73,7 +73,7 @@ 3.14.0 3.3.1 3.11.2 - 3.5.2 + 3.5.3 3.2.8 3.11.0.3922 0.8.13 @@ -228,7 +228,6 @@ org.sonarsource.scanner.maven sonar-maven-plugin - ${sonar-maven-plugin.version} verify diff --git a/src/main/java/com/switcherapi/client/model/Entry.java b/src/main/java/com/switcherapi/client/model/Entry.java index a75a935..a1f913a 100644 --- a/src/main/java/com/switcherapi/client/model/Entry.java +++ b/src/main/java/com/switcherapi/client/model/Entry.java @@ -11,27 +11,29 @@ public class Entry { private final String strategy; private final String input; - - private Entry(final String strategy, final String input) { + + public Entry(String strategy, String input) { this.strategy = strategy; this.input = input; } - - private Entry(final StrategyValidator strategy, final String input) { + + public Entry(StrategyValidator strategy, String input) { this(strategy.toString(), input); } /** + * Creates a new Entry with the given strategy and input. + * * @param strategy Validator used to evaluate the Switcher * @param input follow the required format documented into each strategy type * @return new Entry * @see StrategyValidator */ - public static Entry build(final StrategyValidator strategy, final String input) { + public static Entry of(StrategyValidator strategy, String input) { return new Entry(strategy, input); } - - public static Entry build(final String strategy, final String input) { + + public static Entry of(String strategy, String input) { return new Entry(strategy, input); } diff --git a/src/main/java/com/switcherapi/client/model/SwitcherBuilder.java b/src/main/java/com/switcherapi/client/model/SwitcherBuilder.java index 7dd36ba..a7d393c 100644 --- a/src/main/java/com/switcherapi/client/model/SwitcherBuilder.java +++ b/src/main/java/com/switcherapi/client/model/SwitcherBuilder.java @@ -95,7 +95,7 @@ public SwitcherBuilder restrictRelay(boolean restrictRelay) { */ public SwitcherBuilder check(StrategyValidator strategy, String input) { if (StringUtils.isNotBlank(input)) { - entry.add(Entry.build(strategy, input)); + entry.add(Entry.of(strategy, input)); } return this; diff --git a/src/main/resources/META-INF/native-image/com.switcherapi/switcher-client/reflect-config.json b/src/main/resources/META-INF/native-image/com.switcherapi/switcher-client/reflect-config.json index 700b356..a66c838 100644 --- a/src/main/resources/META-INF/native-image/com.switcherapi/switcher-client/reflect-config.json +++ b/src/main/resources/META-INF/native-image/com.switcherapi/switcher-client/reflect-config.json @@ -8,7 +8,10 @@ "methods": [ { "name": "", - "parameterTypes": [] + "parameterTypes": [ + "java.lang.String", + "java.lang.String" + ] } ] }, @@ -21,7 +24,9 @@ "methods": [ { "name": "", - "parameterTypes": [] + "parameterTypes": [ + "com.switcherapi.client.model.Entry[]" + ] } ] }, @@ -34,7 +39,14 @@ "methods": [ { "name": "", - "parameterTypes": [] + "parameterTypes": [ + "java.lang.String", + "java.lang.String", + "boolean", + "com.switcherapi.client.model.criteria.StrategyConfig[]", + "java.lang.String[]", + "com.switcherapi.client.model.criteria.Relay" + ] } ] }, @@ -73,7 +85,12 @@ "methods": [ { "name": "", - "parameterTypes": [] + "parameterTypes": [ + "java.lang.String", + "java.lang.String", + "boolean", + "com.switcherapi.client.model.criteria.Config[]" + ] } ] }, @@ -99,7 +116,29 @@ "methods": [ { "name": "", - "parameterTypes": [] + "parameterTypes": [ + "java.lang.String", + "java.lang.String", + "java.lang.String", + "boolean", + "java.lang.String[]" + ] + } + ] + }, + { + "name": "com.switcherapi.client.model.criteria.Relay", + "condition": { + "typeReachable": "com.switcherapi.client.model.criteria.Snapshot" + }, + "allDeclaredFields": true, + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.String", + "boolean" + ] } ] }, diff --git a/src/test/java/com/switcherapi/client/SwitcherLocal1Test.java b/src/test/java/com/switcherapi/client/SwitcherLocal1Test.java index a9b5d8e..9c5f184 100644 --- a/src/test/java/com/switcherapi/client/SwitcherLocal1Test.java +++ b/src/test/java/com/switcherapi/client/SwitcherLocal1Test.java @@ -115,7 +115,7 @@ static Stream dateTestArguments() { @MethodSource("dateTestArguments") void localShouldTest_dateValidation(String useCaseKey, String input, boolean expected) { SwitcherRequest switcher = Switchers.getSwitcher(useCaseKey); - Entry entry = Entry.build(StrategyValidator.DATE, input); + Entry entry = Entry.of(StrategyValidator.DATE, input); assertEquals(expected, switcher.prepareEntry(entry).isItOn()); } @@ -130,7 +130,7 @@ void localShouldTestChained_dateValidation(String useCaseKey, String input, bool @Test void localShouldReturnFalse_dateValidationWrongFormat() { SwitcherRequest switcher = Switchers.getSwitcher(Switchers.USECASE33); - Entry input = Entry.build(StrategyValidator.DATE, "2019/121/13"); + Entry input = Entry.of(StrategyValidator.DATE, "2019/121/13"); switcher.prepareEntry(input); assertThrows(SwitcherInvalidTimeFormat.class, switcher::isItOn); @@ -157,7 +157,7 @@ static Stream valueTestArguments() { @MethodSource("valueTestArguments") void localShouldTest_valueValidation(String useCaseKey, String input, boolean expected) { SwitcherRequest switcher = Switchers.getSwitcher(useCaseKey); - Entry entry = Entry.build(StrategyValidator.VALUE, input); + Entry entry = Entry.of(StrategyValidator.VALUE, input); switcher.prepareEntry(entry); assertEquals(expected, switcher.isItOn()); @@ -198,7 +198,7 @@ static Stream numericTestArguments() { @MethodSource("numericTestArguments") void localShouldTest_numericValidation(String useCaseKey, String input, boolean expected) { SwitcherRequest switcher = Switchers.getSwitcher(useCaseKey); - Entry entry = Entry.build(StrategyValidator.NUMERIC, input); + Entry entry = Entry.of(StrategyValidator.NUMERIC, input); switcher.prepareEntry(entry); assertEquals(expected, switcher.isItOn()); @@ -215,7 +215,7 @@ void localShouldTestChained_numericValidation(String useCaseKey, String input, b @Test void localShouldReturnException_invalidNumericInput() { SwitcherRequest switcher = Switchers.getSwitcher(Switchers.USECASE81); - Entry input = Entry.build(StrategyValidator.NUMERIC, "INVALID_NUMBER"); + Entry input = Entry.of(StrategyValidator.NUMERIC, "INVALID_NUMBER"); switcher.prepareEntry(input); assertThrows(SwitcherInvalidNumericFormat.class, switcher::isItOn); @@ -239,7 +239,7 @@ static Stream timeTestArguments() { @MethodSource("timeTestArguments") void localShouldTest_timeValidation(String useCaseKey, String input, boolean expected) { SwitcherRequest switcher = Switchers.getSwitcher(useCaseKey); - Entry entry = Entry.build(StrategyValidator.TIME, input); + Entry entry = Entry.of(StrategyValidator.TIME, input); switcher.prepareEntry(entry); assertEquals(expected, switcher.isItOn()); @@ -255,7 +255,7 @@ void localShouldTestChained_timeValidation(String useCaseKey, String input, bool @Test void localShouldReturnFalse_timeValidationWrongFormat() { SwitcherRequest switcher = Switchers.getSwitcher(Switchers.USECASE53); - Entry input = Entry.build(StrategyValidator.TIME, "2019-12-10"); + Entry input = Entry.of(StrategyValidator.TIME, "2019-12-10"); switcher.prepareEntry(input); assertThrows(SwitcherInvalidTimeFormat.class, switcher::isItOn); @@ -279,7 +279,7 @@ static Stream networkTestArguments() { @MethodSource("networkTestArguments") void localShouldTest_networkValidation(String useCaseKey, String input, boolean expected) { SwitcherRequest switcher = Switchers.getSwitcher(useCaseKey); - Entry entry = Entry.build(StrategyValidator.NETWORK, input); + Entry entry = Entry.of(StrategyValidator.NETWORK, input); switcher.prepareEntry(entry); assertEquals(expected, switcher.isItOn()); @@ -301,7 +301,7 @@ void localShouldReturnFalse_strategyRequiresInput() { @Test void localShouldReturnFalse_invalidStrategyInput() { SwitcherRequest switcher = Switchers.getSwitcher(Switchers.USECASE33); - switcher.prepareEntry(Entry.build(StrategyValidator.INVALID, "Value")); + switcher.prepareEntry(Entry.of(StrategyValidator.INVALID, "Value")); assertFalse(switcher.isItOn()); } @@ -328,7 +328,7 @@ static Stream regexTestArguments() { @MethodSource("regexTestArguments") void localShouldTest_regexValidation(String useCaseKey, String input, boolean expected) { SwitcherRequest switcher = Switchers.getSwitcher(useCaseKey); - Entry entry = Entry.build(StrategyValidator.REGEX, input); + Entry entry = Entry.of(StrategyValidator.REGEX, input); switcher.prepareEntry(entry); assertEquals(expected, switcher.isItOn()); @@ -357,7 +357,7 @@ static Stream payloadTestArguments() { @MethodSource("payloadTestArguments") void localShouldTest_payloadValidation(String useCaseKey, String input, boolean expected) { SwitcherRequest switcher = Switchers.getSwitcher(useCaseKey); - Entry entry = Entry.build(StrategyValidator.PAYLOAD, input); + Entry entry = Entry.of(StrategyValidator.PAYLOAD, input); switcher.prepareEntry(entry); assertEquals(expected, switcher.isItOn()); diff --git a/src/test/java/com/switcherapi/client/SwitcherLocal3Test.java b/src/test/java/com/switcherapi/client/SwitcherLocal3Test.java index b380c34..9969f58 100644 --- a/src/test/java/com/switcherapi/client/SwitcherLocal3Test.java +++ b/src/test/java/com/switcherapi/client/SwitcherLocal3Test.java @@ -79,7 +79,7 @@ static Stream failTestArguments() { void localShouldReturnError(String useCaseKey, String strategyValidator, String input, Class error) { SwitcherRequest switcher = Switchers.getSwitcher(useCaseKey); - switcher.prepareEntry(Entry.build(strategyValidator, input)); + switcher.prepareEntry(Entry.of(strategyValidator, input)); assertThrows(error, switcher::isItOn); } diff --git a/src/test/java/com/switcherapi/client/model/ModelTest.java b/src/test/java/com/switcherapi/client/model/ModelTest.java index 923a0e3..01ac7de 100644 --- a/src/test/java/com/switcherapi/client/model/ModelTest.java +++ b/src/test/java/com/switcherapi/client/model/ModelTest.java @@ -9,8 +9,8 @@ class ModelTest { @Test void testModelEntry() { - Entry entry1 = Entry.build(StrategyValidator.DATE, "2019-12-10"); - Entry entry2 = Entry.build(StrategyValidator.VALUE, "Value"); + Entry entry1 = Entry.of(StrategyValidator.DATE, "2019-12-10"); + Entry entry2 = Entry.of(StrategyValidator.VALUE, "Value"); assertNotEquals(true, entry1.equals(entry2)); assertNotNull(entry1.toString()); diff --git a/src/test/java/metainf/nativeimage/NativeReflectConfigTest.java b/src/test/java/metainf/nativeimage/NativeReflectConfigTest.java index f3cbf8c..89e193c 100644 --- a/src/test/java/metainf/nativeimage/NativeReflectConfigTest.java +++ b/src/test/java/metainf/nativeimage/NativeReflectConfigTest.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import java.lang.reflect.Array; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -39,8 +40,7 @@ void shouldApplyReflection() { ReflectJson[] reflectJson = new Gson().fromJson(reflectContent, ReflectJson[].class); for (ReflectJson json : reflectJson) { - assertDoesNotThrow(() -> Class.forName(json.name), - String.format("Class %s not reachable", json.name)); + assertConstructorsReachable(json); if (Objects.nonNull(json.condition)) { assertDoesNotThrow(() -> Class.forName(json.condition.typeReachable), @@ -49,12 +49,65 @@ void shouldApplyReflection() { } } + private void assertConstructorsReachable(ReflectJson json) { + if (Objects.nonNull(json.methods)) { + for (ReflectMethod method : json.methods) { + if ("".equals(method.name)) { + assertDoesNotThrow(() -> Class.forName(json.name).getDeclaredConstructor(getArgClasses(method.parameterTypes)), + String.format("Constructor (%s) not reachable in class %s", + String.join(", ", method.parameterTypes), json.name)); + } + } + } + } + + private Class[] getArgClasses(String[] parameterTypes) throws ClassNotFoundException { + if (parameterTypes == null || parameterTypes.length == 0) { + return new Class[0]; + } + + Class[] classes = new Class[parameterTypes.length]; + for (int i = 0; i < parameterTypes.length; i++) { + String paramType = parameterTypes[i]; + if (paramType.endsWith("[]")) { + String baseType = paramType.substring(0, paramType.length() - 2); + Class baseClass = getPrimitiveOrClass(baseType); + classes[i] = Array.newInstance(baseClass, 0).getClass(); + } else { + classes[i] = getPrimitiveOrClass(paramType); + } + } + + return classes; + } + + private Class getPrimitiveOrClass(String typeName) throws ClassNotFoundException { + switch (typeName) { + case "boolean": return boolean.class; + case "byte": return byte.class; + case "char": return char.class; + case "short": return short.class; + case "int": return int.class; + case "long": return long.class; + case "float": return float.class; + case "double": return double.class; + case "void": return void.class; + default: return Class.forName(typeName); + } + } + static class ReflectJson { String name; ReflectCondition condition; + ReflectMethod[] methods; } static class ReflectCondition { String typeReachable; } + + static class ReflectMethod { + String name; + String[] parameterTypes; + } } From 6678eb84a355b65a82d055af6e2ca0ec29dc0c6e Mon Sep 17 00:00:00 2001 From: petruki <31597636+petruki@users.noreply.github.com> Date: Thu, 14 Aug 2025 22:10:30 -0700 Subject: [PATCH 2/2] test-ci: updated release to use ubuntu-22.04 --- .github/workflows/release.yml | 4 ++-- README.md | 4 ++-- pom.xml | 2 +- .../client/validator/RegexValidatorV8Test.java | 12 ++++++------ 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4aa1d6c..ba6a968 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,7 +12,7 @@ jobs: fail-fast: false matrix: java: ['8', '11', '17', '21'] - os: [ubuntu-latest, windows-latest] + os: [ubuntu-22.04, windows-latest] runs-on: ${{ matrix.os }} steps: @@ -40,7 +40,7 @@ jobs: publish: name: Publish Release needs: [build-test] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 diff --git a/README.md b/README.md index ee6944d..73fa186 100644 --- a/README.md +++ b/README.md @@ -171,8 +171,8 @@ Loading information into the switcher can be made by using *prepareEntry*, in ca ```java List entries = new ArrayList<>(); -entries.add(Entry.build(StrategyValidator.DATE, "2019-12-10")); -entries.add(Entry.build(StrategyValidator.DATE, "2020-12-10")); +entries.add(Entry.of(StrategyValidator.DATE, "2019-12-10")); +entries.add(Entry.of(StrategyValidator.DATE, "2020-12-10")); switcher.prepareEntry(entries); switcher.isItOn(); diff --git a/pom.xml b/pom.xml index 630b33a..598df0a 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ com.switcherapi switcher-client jar - 1.8.0 + 1.8.1-SNAPSHOT Switcher Client Switcher Client SDK for working with Switcher API diff --git a/src/test/java/com/switcherapi/client/validator/RegexValidatorV8Test.java b/src/test/java/com/switcherapi/client/validator/RegexValidatorV8Test.java index afd6224..91eed53 100644 --- a/src/test/java/com/switcherapi/client/validator/RegexValidatorV8Test.java +++ b/src/test/java/com/switcherapi/client/validator/RegexValidatorV8Test.java @@ -41,7 +41,7 @@ void shouldFailEvilRegexInput(EntryOperation operation, boolean expected) { //given RegexValidatorV8 regexValidator = new RegexValidatorV8(); StrategyConfig strategyConfig = givenStrategy(operation, Collections.singletonList(EVIL_REGEX)); - Entry entry = Entry.build(StrategyValidator.REGEX, EVIL_INPUT); + Entry entry = Entry.of(StrategyValidator.REGEX, EVIL_INPUT); //test boolean actual = assertTimeoutPreemptively(Duration.ofMillis(5000), () -> regexValidator.process(strategyConfig, entry)); @@ -53,7 +53,7 @@ void shouldBlackListEvilInput_immediateReturnNextCall() { //given RegexValidatorV8 regexValidator = new RegexValidatorV8(); StrategyConfig strategyConfig = givenStrategy(EntryOperation.EXIST, Collections.singletonList(EVIL_REGEX)); - Entry entry = Entry.build(StrategyValidator.REGEX, EVIL_INPUT); + Entry entry = Entry.of(StrategyValidator.REGEX, EVIL_INPUT); //test boolean result = assertTimeoutPreemptively(Duration.ofMillis(4000), () -> regexValidator.process(strategyConfig, entry)); @@ -70,11 +70,11 @@ void shouldBlackListEvilInput_immediateReturnNextCall_similarInput() { StrategyConfig strategyConfig = givenStrategy(EntryOperation.EXIST, Collections.singletonList(EVIL_REGEX)); //test - Entry entry1 = Entry.build(StrategyValidator.REGEX, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + Entry entry1 = Entry.of(StrategyValidator.REGEX, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); boolean result = assertTimeoutPreemptively(Duration.ofMillis(4000), () -> regexValidator.process(strategyConfig, entry1)); assertFalse(result); - Entry entry2 = Entry.build(StrategyValidator.REGEX, "bbbbaaaaaaaaaaaaaaa"); + Entry entry2 = Entry.of(StrategyValidator.REGEX, "bbbbaaaaaaaaaaaaaaa"); result = assertTimeoutPreemptively(Duration.ofMillis(100), () -> regexValidator.process(strategyConfig, entry2)); assertFalse(result); } @@ -84,7 +84,7 @@ void shouldFail_nullInput() { //given RegexValidatorV8 regexValidator = new RegexValidatorV8(); StrategyConfig strategyConfig = givenStrategy(EntryOperation.EXIST, Collections.singletonList(EVIL_REGEX)); - Entry entry = Entry.build(StrategyValidator.REGEX, null); + Entry entry = Entry.of(StrategyValidator.REGEX, null); //test boolean result = regexValidator.process(strategyConfig, entry); @@ -96,7 +96,7 @@ void shouldCompleteWorkerThreadsAfterTimeout() { //given RegexValidatorV8 regexValidator = new RegexValidatorV8(); StrategyConfig strategyConfig = givenStrategy(EntryOperation.EXIST, Collections.singletonList(EVIL_REGEX)); - Entry entry = Entry.build(StrategyValidator.REGEX, EVIL_INPUT); + Entry entry = Entry.of(StrategyValidator.REGEX, EVIL_INPUT); //test boolean result = regexValidator.process(strategyConfig, entry);